diff --git a/.bazelignore b/.bazelignore deleted file mode 100644 index f97913c854f..00000000000 --- a/.bazelignore +++ /dev/null @@ -1,2 +0,0 @@ -bin -_bin diff --git a/.bazelrc b/.bazelrc deleted file mode 100644 index 4a099d9162d..00000000000 --- a/.bazelrc +++ /dev/null @@ -1,7 +0,0 @@ -# Include git version info -build --workspace_status_command hack/build/print-workspace-status.sh -# Show timestamps with each bazel message -build --show_timestamps - -# import per-user options -try-import %workspace%/user.bazelrc diff --git a/.clomonitor.yml b/.clomonitor.yml new file mode 100644 index 00000000000..641c5864df9 --- /dev/null +++ b/.clomonitor.yml @@ -0,0 +1,9 @@ +# License scanning information +licenseScanning: + # URL with the repository's license scanning results + # + # CLOMonitor can extract license scanning results from FOSSA and Snyk badges + # in the repository README.md file automatically. If your repository uses a + # different scanning solution, this url can be set to pass the corresponding + # check. + url: https://github.com/cert-manager/cert-manager/blob/master/LICENSES diff --git a/.github/ISSUE_TEMPLATE/bug.md b/.github/ISSUE_TEMPLATE/bug.md index 9875e274837..142bc3bad99 100644 --- a/.github/ISSUE_TEMPLATE/bug.md +++ b/.github/ISSUE_TEMPLATE/bug.md @@ -7,7 +7,7 @@ about: Report a bug to help us improve cert-manager @@ -30,10 +30,10 @@ gain an understanding of the problem.--> **Anything else we need to know?**: -**Environment details:**: +**Environment details**: - Kubernetes version: - Cloud-provider/provisioner: - cert-manager version: -- Install method: e.g. helm/static manifests +- Install method: e.g., helm/static manifests /kind bug diff --git a/.github/ISSUE_TEMPLATE/feature-request.md b/.github/ISSUE_TEMPLATE/feature-request.md index 6dc87ac4b36..debbd25f376 100644 --- a/.github/ISSUE_TEMPLATE/feature-request.md +++ b/.github/ISSUE_TEMPLATE/feature-request.md @@ -20,7 +20,7 @@ about: Suggest an idea to improve cert-manager - Kubernetes version: - Cloud-provider/provisioner: - cert-manager version: -- Install method: e.g. helm/static manifests +- Install method: e.g., helm/static manifests /kind feature diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index fd5d3caefb6..173a95d0fea 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -18,9 +18,14 @@ Thanks for opening a pull request! Here are some tips to get everything merged s ### Kind + +/kind -

+

-Build Status +Build Status Go Report Card
Artifact Hub +Scorecard score +CLOMonitor +
+

# cert-manager @@ -33,18 +37,28 @@ Documentation for cert-manager can be found at [cert-manager.io](https://cert-ma For the common use-case of automatically issuing TLS certificates for Ingress resources, see the [cert-manager nginx-ingress quick start guide](https://cert-manager.io/docs/tutorials/acme/nginx-ingress/). -For a more comprensive guide to issuing your first certificate, see our [getting started guide](https://cert-manager.io/docs/getting-started/). +For a more comprehensive guide to issuing your first certificate, see our [getting started guide](https://cert-manager.io/docs/getting-started/). ### Installation [Installation](https://cert-manager.io/docs/installation/) is documented on the website, with a variety of supported methods. +## Developing cert-manager + +We actively welcome contributions and we support both Linux and macOS environments for development. + +Different platforms have different requirements; we document everything on our [Building cert-manager](https://cert-manager.io/docs/contributing/building/) +website page. + +Note in particular that macOS has several extra requirements, to ensure that modern tools are installed and available. Read the page before +getting started! + ## Troubleshooting If you encounter any issues whilst using cert-manager, we have a number of ways to get help: - A [troubleshooting guide](https://cert-manager.io/docs/faq/troubleshooting/) on our website. -- Our official [Kubernetes Slack channel](https://cert-manager.io/docs/contributing/#slack) - the quickest way to ask! +- Our official [Kubernetes Slack channel](https://cert-manager.io/docs/contributing/#slack) - the quickest way to ask! ([#cert-manager](https://kubernetes.slack.com/messages/cert-manager) and [#cert-manager-dev](https://kubernetes.slack.com/messages/cert-manager-dev)) - [Searching for an existing issue](https://github.com/cert-manager/cert-manager/issues). If you believe you've found a bug and cannot find an existing issue, feel free to [open a new issue](https://github.com/cert-manager/cert-manager/issues)! @@ -52,9 +66,9 @@ Be sure to include as much information as you can about your environment. ## Community -The `cert-manager-dev` Google Group is used for project wide announcements and development coordination. -Anybody can join the group by visiting [here](https://groups.google.com/forum/#!forum/cert-manager-dev) -and clicking "Join Group". A Google account is required to join the group. +The [`cert-manager-dev` Google Group](https://groups.google.com/forum/#!forum/cert-manager-dev) +is used for project wide announcements and development coordination. +Anybody with a Google account can join the group by visiting the group and clicking "Join Group". ### Meetings diff --git a/RELEASE.md b/RELEASE.md new file mode 100644 index 00000000000..1ab0a3a35be --- /dev/null +++ b/RELEASE.md @@ -0,0 +1,17 @@ +# Releases + +## Schedule + +The release schedule for cert-manager is defined on the [cert-manager website](https://cert-manager.io/docs/releases/). + +## Process + +The release process is described in detail on the [cert-manager website](https://cert-manager.io/docs/contributing/release-process/). + +## Artifacts + +The cert-manager project will produce the following artifacts each release. For documentation on how those artifacts are produced see the "Process" section. + +- *Container Images* - Container images for the cert-manager project are published for all cert-manager components. +- *Helm chart* - An official Helm chart is maintained within this repo and published to `charts.jetstack.io` on each cert-manager release. +- *Binaries* - Until version 1.15 the cmctl binary was maintained within this repo and published as part of the cert-manager release. For releases after 1.15 the CLI has moved to its [own repository](https://github.com/cert-manager/cmctl). Binary builds are still available for download from this new location. \ No newline at end of file diff --git a/ROADMAP.md b/ROADMAP.md index 04d7df47854..9d2adfdc7f3 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -1,66 +1,4 @@ Roadmap ======= -The roadmap items are categorised into themes based on the larger goals we want to achieve with cert-manager. - - -While this is a summary of the direction we want to go we welcome all PRs, even if they don't fall under any of the roadmap items -listed here. We unfortunately can't merge every change, and if you're looking to contribute a new feature you might want to -check the [contributing guide](https://cert-manager.io/docs/contributing/) on the cert-manager website. - - -### Integration with other projects in the cloud-native landscape - -cert-manager should be able to deliver and manage X.509 certificates to popular -projects in the cloud-native ecosystem. - -- Service Mesh Integration: While we have good Istio and Open Service Mesh integration, expand to other projects such as Linkerd, cilium - -### Adoption of upstream APIs - -Continue to support latest APIs for upstream K8s and related SIGs. - -- Kubernetes APIs: keep up to date with Kubernetes API changes and release cadence -- CSR API: support the sig-auth CSR API for certificate requests in kubernetes -- [Trust Anchor Sets](https://github.com/kubernetes/enhancements/pull/3258) -- Gateway API - -### Extensibility - -Widen the scope of integrations with cert-manager. - -- EST support: support a standard for ACME-like issuance within an enterprise -- External DNS plugin: enable ACME DNS01 requests to be completed using external-dns -- Improve external issuer development experience: documentation and examples for people developing external issuers - -### PKI lifecycle - -Enable best-practice PKI management with cert-manager. - -- Handle CA certs being renewed: deal with the cases where the CA cert is renewed and allow for all signed certs to be renewed -- Make cert-manager a viable way to create and manage private PKI deployments at scale -- Trust root distribution: handle distributing all trust roots within a cluster, solving trust for private and public certificates - -See also [trust-manager](https://cert-manager.io/docs/projects/trust/) for more on trust distribution. - -### End-user experience - -- Graduate alpha / beta features in good time: - - SIG-Auth CSR API support - - SIG-Network Gateway API support -- Easier diagnosis of problems: improve cert-manager output to make status clearer, and provide tools to aid debugging -- Improve the new contributor experience - -### Developer experience - -- Better user experience for installation, operation and use with applications -- Zero test flakiness and increased testing confidence -- Improve release process by adding more automation - -### Shrinking Core - -Minimise the surface area of cert-manager, reducing attack surface, binary size, container size and default deployment complexity - -- Move "core" issuers with dependencies (ACME, Vault, Venafi) into external issuers, which might still be bundled by default -- Likewise, change all "core" DNS solvers into external solvers -- Provide a minimal "pick and mix" distribution of cert-manager which allows users to specify exactly which issuer types / DNS solvers they want to install +The cert-manager project roadmap has moved to the [cert-manager/community repo](https://github.com/cert-manager/community/blob/main/ROADMAP.md). diff --git a/SECURITY.md b/SECURITY.md index f8b6fbacd3a..3b96b8a7292 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -1,54 +1,3 @@ -# Vulnerability Reporting Process +# Security -Security is the number one priority for cert-manager. If you think you've found a -security vulnerability in a cert-manager project, you're in the right place. - -Our reporting procedure is a work-in-progress, and will evolve over time. We -welcome advice, feedback and pull requests for improving our security -reporting processes. - -## Covered Repositories and Issues - -When we say "a security vulnerability in cert-manager" we mean a security issue -in any repository under the [cert-manger GitHub organization](https://github.com/cert-manager/). - -This reporting process is intended only for security issues in the cert-manager -project itself, and doesn't apply to applications _using_ cert-manager or to -issues which do not affect security. - -Broadly speaking, if the issue cannot be fixed by a change to one of the covered -repositories above, then it might not be appropriate to use this reporting -mechanism and a GitHub issue in the appropriate repo or a question in Slack -might be a better choice. - -All that said, **if you're unsure** please reach out using this process before -raising your issue through another channel. We'd rather err on the side of -caution! - -## Security Contacts - -The people who should have access to read your security report are listed in -[`SECURITY_CONTACTS.md`](./SECURITY_CONTACTS.md) - -## Reporting Process - -1. Describe the issue in English, ideally with some example configuration or - code which allows the issue to be reproduced. Explain why you believe this - to be a security issue in cert-manager, if that's not obvious. -2. Put that information into an email. Use a descriptive title. -3. Send the email to [`cert-manager-security@googlegroups.com`](mailto:cert-manager-security@googlegroups.com) - -## Response - -Response times could be affected by weekends, holidays, breaks or time zone -differences. That said, the security response team will endeavour to reply as -soon as possible, ideally within 3 working days. - -If the team concludes that the reported issue is indeed a security -vulnerability in a cert-manager project, at least two members of the security -response team will discuss the next steps together as soon as possible, ideally -within 24 hours. - -As soon as the team decides that the report is of a genuine vulnerability, -one of the team will respond to the reporter acknowledging the issue and -establishing a disclosure timeline, which should be as soon as possible. +Please refer to the [cert-manager organisation security document](https://github.com/cert-manager/community/blob/main/SECURITY.md). diff --git a/SECURITY_CONTACTS.md b/SECURITY_CONTACTS.md index 59dcefdbaab..11de532497c 100644 --- a/SECURITY_CONTACTS.md +++ b/SECURITY_CONTACTS.md @@ -1,16 +1,3 @@ -# Security Contacts +# Security contacts -This file lists people who (should) have access to read security reports -made via the cert-manager vulnerability reporting process. - -If you think you've found a security issue in cert-manager, don't reach -out to any of these people individually - follow the details in -SECURITY.md and report your vulnerability via e-mail. - -- [irbekrm](https://github.com/irbekrm) -- [SgtCoDFish](https://github.com/SgtCoDFish) -- [jakexks](https://github.com/jakexks) -- [JoshVanL](https://github.com/JoshVanL) -- [maelvls](https://github.com/maelvls) -- [wallrj](https://github.com/wallrj) -- [munnerz](https://github.com/munnerz) +Please refer to the [cert-manager organisation security contacts](https://github.com/cert-manager/community/blob/main/SECURITY_CONTACTS.md). diff --git a/USERS.md b/USERS.md index 5d364248b42..fe046be96f5 100644 --- a/USERS.md +++ b/USERS.md @@ -1,27 +1,3 @@ -# cert-manager Users +# Users -We love hearing about it when people use and enjoy cert-manager! - -## Organization Users - -Please feel free to send PRs to add your org to the list, or to reach out to a maintainer with your details; they'll gladly add you! -We'd love for you to share your cert-manager story with the world! - -| Organization | Usage | Links | -| :----------: | :---: | :---: | -| [Atomist ](https://atomist.com/) | Securing ingresses | [Kubernetes, ingress-nginx, cert-manager & external-dns](https://blog.atomist.com/kubernetes-ingress-nginx-cert-manager-external-dns/) | -| [Jetstack text ](https://jetstack.io) | Securing MySQL inside Kubernetes | [Blog](https://blog.jetstack.io/blog/securing-mysql-with-cert-manager/) | -| [JFrog ](https://jfrog.com/) | Securing ingresses | | -| [Urssaf Caisse nationale ](https://urssaf.org) | Securing ingresses | | -| [Azusa Pacific University Logo ](https://www.apu.edu) | Securing Ingresses | [@azusapacificuniversity](https://github.com/azusapacificuniversity) [www.apu.edu](https://www.apu.edu) | - -## Individuals - -As an open source project we welcome all kinds of users; please feel free to raise a PR to add yourself to the list. -Plus, if you've written something about cert-manager throw in a link too for others to enjoy! - -| Name | GitHub | Usage | Links | -| :--: | :----: | :---: | :---: | -| Maartje Eyskens | [@meyskens](https://github.com/meyskens) | Securing ingresses | | -| Noah Kantrowitz | [@coderanger](https://github.com/coderanger) | Many things! | [Lessons Learned From Two Years Of Kubernetes](https://coderanger.net/lessons-learned/) | -| Dipto Chakrabarty | [@DiptoChakrabarty](https://github.com/DiptoChakrabarty) | Securing Ingress | [Cert Manager in Kubernetes with external DNS provider](https://diptochakrabarty.medium.com/cert-manager-in-kubernetes-with-external-dns-provider-64ae5d7f577b) | +Please refer to the [cert-manager organisation users list](https://github.com/cert-manager/community/blob/main/USERS.md). diff --git a/cmd/acmesolver/LICENSE b/cmd/acmesolver/LICENSE new file mode 100644 index 00000000000..d6456956733 --- /dev/null +++ b/cmd/acmesolver/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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. diff --git a/cmd/acmesolver/LICENSES b/cmd/acmesolver/LICENSES new file mode 100644 index 00000000000..910f137ba5d --- /dev/null +++ b/cmd/acmesolver/LICENSES @@ -0,0 +1,87 @@ +This LICENSES file is generated by the `licenses` module in makefile-modules[0]. + +The licenses below the "---" are determined by the go-licenses tool[1]. + +The aim of this file is to collect the licenses of all dependencies, and provide +a single source of truth for licenses used by this project. + +## For Developers + +If CI reports that this file is out of date, you should be careful to check that the +new licenses are acceptable for this project before running `make generate-go-licenses` +to update this file. + +Acceptable licenses are those allowlisted by the CNCF[2]. + +You MUST NOT add any new dependencies whose licenses are not allowlisted by the CNCF, +or which do not have an explicit license exception[3]. + +## For Users + +If this file was included in a release artifact, it is a snapshot of the licenses of all dependencies at the time of the release. + +You can retrieve the actual license text by following these steps: + +1. Find the dependency name in this file +2. Go to the source code repository of this project, and go to the tag corresponding to this release. +3. Find the exact version of the dependency in the `go.mod` file +4. Search for the dependency at the correct version in the [Go package index](https://pkg.go.dev/). + +## Links + +[0]: https://github.com/cert-manager/makefile-modules/ +[1]: https://github.com/google/go-licenses +[2]: https://github.com/cncf/foundation/blob/db4179134ebe7fa00b140a050c19147db808b6fa/policies-guidance/allowed-third-party-license-policy.md#cncf-allowlist-license-policy +[3]: https://github.com/cncf/foundation/blob/db4179134ebe7fa00b140a050c19147db808b6fa/license-exceptions/README.md + +--- + +github.com/beorn7/perks/quantile,MIT +github.com/blang/semver/v4,MIT +github.com/cert-manager/cert-manager,Apache-2.0 +github.com/cert-manager/cert-manager/acmesolver-binary,Apache-2.0 +github.com/cespare/xxhash/v2,MIT +github.com/davecgh/go-spew/spew,ISC +github.com/fxamacker/cbor/v2,MIT +github.com/go-logr/logr,Apache-2.0 +github.com/go-logr/zapr,Apache-2.0 +github.com/gogo/protobuf,BSD-3-Clause +github.com/json-iterator/go,MIT +github.com/modern-go/concurrent,Apache-2.0 +github.com/modern-go/reflect2,Apache-2.0 +github.com/munnerz/goautoneg,BSD-3-Clause +github.com/pmezard/go-difflib/difflib,BSD-3-Clause +github.com/prometheus/client_golang/internal/github.com/golang/gddo/httputil,BSD-3-Clause +github.com/prometheus/client_golang/prometheus,Apache-2.0 +github.com/prometheus/client_model/go,Apache-2.0 +github.com/prometheus/common,Apache-2.0 +github.com/prometheus/procfs,Apache-2.0 +github.com/spf13/cobra,Apache-2.0 +github.com/spf13/pflag,BSD-3-Clause +github.com/x448/float16,MIT +go.opentelemetry.io/otel,Apache-2.0 +go.opentelemetry.io/otel/trace,Apache-2.0 +go.uber.org/multierr,MIT +go.uber.org/zap,MIT +go.yaml.in/yaml/v2,Apache-2.0 +golang.org/x/net,BSD-3-Clause +golang.org/x/sys/unix,BSD-3-Clause +golang.org/x/text,BSD-3-Clause +google.golang.org/protobuf,BSD-3-Clause +gopkg.in/inf.v0,BSD-3-Clause +k8s.io/api/core/v1,Apache-2.0 +k8s.io/apiextensions-apiserver/pkg/apis/apiextensions,Apache-2.0 +k8s.io/apimachinery/pkg,Apache-2.0 +k8s.io/apimachinery/third_party/forked/golang/reflect,BSD-3-Clause +k8s.io/component-base,Apache-2.0 +k8s.io/klog/v2,Apache-2.0 +k8s.io/utils,Apache-2.0 +k8s.io/utils/internal/third_party/forked/golang/net,BSD-3-Clause +sigs.k8s.io/gateway-api/apis/v1,Apache-2.0 +sigs.k8s.io/json,Apache-2.0 +sigs.k8s.io/json,BSD-3-Clause +sigs.k8s.io/randfill,Apache-2.0 +sigs.k8s.io/structured-merge-diff/v6/value,Apache-2.0 +sigs.k8s.io/yaml,MIT +sigs.k8s.io/yaml,Apache-2.0 +sigs.k8s.io/yaml,BSD-3-Clause diff --git a/cmd/acmesolver/app/app.go b/cmd/acmesolver/app/app.go index 0705a7649c4..e55c9de8795 100644 --- a/cmd/acmesolver/app/app.go +++ b/cmd/acmesolver/app/app.go @@ -18,40 +18,57 @@ package app import ( "context" + "errors" + "fmt" + "net/http" "time" "github.com/spf13/cobra" + "k8s.io/component-base/logs" - "github.com/cert-manager/cert-manager/cmd/util" "github.com/cert-manager/cert-manager/pkg/issuer/acme/http/solver" logf "github.com/cert-manager/cert-manager/pkg/logs" ) -func NewACMESolverCommand(stopCh <-chan struct{}) *cobra.Command { +func NewACMESolverCommand(_ context.Context) *cobra.Command { s := new(solver.HTTP01Solver) + logOptions := logs.NewOptions() cmd := &cobra.Command{ Use: "acmesolver", Short: "HTTP server used to solve ACME challenges.", + + SilenceErrors: true, // Errors are already logged when calling cmd.Execute() + SilenceUsage: true, // Don't print usage on every error + + PreRunE: func(cmd *cobra.Command, args []string) error { + if err := logf.ValidateAndApply(logOptions); err != nil { + return fmt.Errorf("error validating options: %s", err) + } + + return nil + }, + // nolint:contextcheck // False positive RunE: func(cmd *cobra.Command, args []string) error { - rootCtx := util.ContextWithStopCh(context.Background(), stopCh) - rootCtx = logf.NewContext(rootCtx, logf.Log, "acmesolver") - log := logf.FromContext(rootCtx) + runCtx := cmd.Context() + log := logf.FromContext(runCtx) completedCh := make(chan struct{}) go func() { defer close(completedCh) - <-stopCh + <-runCtx.Done() + // allow a timeout for graceful shutdown - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + shutdownCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() - if err := s.Shutdown(ctx); err != nil { + // nolint: contextcheck + if err := s.Shutdown(shutdownCtx); err != nil { log.Error(err, "error shutting down acmesolver server") } }() - if err := s.Listen(log); err != nil { + if err := s.Listen(log); err != nil && !errors.Is(err, http.ErrServerClosed) { return err } @@ -66,5 +83,7 @@ func NewACMESolverCommand(stopCh <-chan struct{}) *cobra.Command { cmd.Flags().StringVar(&s.Token, "token", "", "the challenge token to verify against") cmd.Flags().StringVar(&s.Key, "key", "", "the challenge key to respond with") + // TODO(@inteon): use flags to configure the log configuration (https://github.com/cert-manager/cert-manager/issues/6021) + return cmd } diff --git a/cmd/acmesolver/go.mod b/cmd/acmesolver/go.mod new file mode 100644 index 00000000000..d6cb4c249fe --- /dev/null +++ b/cmd/acmesolver/go.mod @@ -0,0 +1,59 @@ +module github.com/cert-manager/cert-manager/acmesolver-binary + +go 1.25.0 + +// Do not remove this comment: +// please place any replace statements here at the top for visibility and add a +// comment to it as to when it can be removed + +replace github.com/cert-manager/cert-manager => ../../ + +require ( + github.com/cert-manager/cert-manager v0.0.0-00010101000000-000000000000 + github.com/spf13/cobra v1.10.1 + k8s.io/component-base v0.34.1 +) + +require ( + github.com/beorn7/perks v1.0.1 // indirect + github.com/blang/semver/v4 v4.0.0 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/fxamacker/cbor/v2 v2.9.0 // indirect + github.com/go-logr/logr v1.4.3 // indirect + github.com/go-logr/zapr v1.3.0 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/google/go-cmp v0.7.0 // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + github.com/prometheus/client_golang v1.23.2 // indirect + github.com/prometheus/client_model v0.6.2 // indirect + github.com/prometheus/common v0.66.1 // indirect + github.com/prometheus/procfs v0.17.0 // indirect + github.com/spf13/pflag v1.0.10 // indirect + github.com/x448/float16 v0.8.4 // indirect + go.opentelemetry.io/otel v1.37.0 // indirect + go.opentelemetry.io/otel/trace v1.37.0 // indirect + go.uber.org/multierr v1.11.0 // indirect + go.uber.org/zap v1.27.0 // indirect + go.yaml.in/yaml/v2 v2.4.2 // indirect + golang.org/x/net v0.46.0 // indirect + golang.org/x/sys v0.37.0 // indirect + golang.org/x/text v0.30.0 // indirect + google.golang.org/protobuf v1.36.10 // indirect + gopkg.in/inf.v0 v0.9.1 // indirect + k8s.io/api v0.34.1 // indirect + k8s.io/apiextensions-apiserver v0.34.1 // indirect + k8s.io/apimachinery v0.34.1 // indirect + k8s.io/klog/v2 v2.130.1 // indirect + k8s.io/utils v0.0.0-20250820121507-0af2bda4dd1d // indirect + sigs.k8s.io/gateway-api v1.4.0 // indirect + sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect + sigs.k8s.io/randfill v1.0.0 // indirect + sigs.k8s.io/structured-merge-diff/v6 v6.3.0 // indirect + sigs.k8s.io/yaml v1.6.0 // indirect +) diff --git a/cmd/acmesolver/go.sum b/cmd/acmesolver/go.sum new file mode 100644 index 00000000000..96c901d6e32 --- /dev/null +++ b/cmd/acmesolver/go.sum @@ -0,0 +1,147 @@ +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= +github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM= +github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= +github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= +github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ= +github.com/go-logr/zapr v1.3.0/go.mod h1:YKepepNBd1u/oyhd/yQmtjVXmm9uML4IXUgMOwR8/Gg= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= +github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8= +github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o= +github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg= +github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= +github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= +github.com/prometheus/common v0.66.1 h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9ZoGs= +github.com/prometheus/common v0.66.1/go.mod h1:gcaUsgf3KfRSwHY4dIMXLPV0K/Wg1oZ8+SbZk/HH/dA= +github.com/prometheus/procfs v0.17.0 h1:FuLQ+05u4ZI+SS/w9+BWEM2TXiHKsUQ9TADiRH7DuK0= +github.com/prometheus/procfs v0.17.0/go.mod h1:oPQLaDAMRbA+u8H5Pbfq+dl3VDAvHxMUOVhe0wYB2zw= +github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= +github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/spf13/cobra v1.10.1 h1:lJeBwCfmrnXthfAupyUTzJ/J4Nc1RsHC/mSRU2dll/s= +github.com/spf13/cobra v1.10.1/go.mod h1:7SmJGaTHFVBY0jW4NXGluQoLvhqFQM+6XSKD+P4XaB0= +github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= +github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= +github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ= +go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I= +go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4= +go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= +go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= +go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI= +go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU= +go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= +go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.46.0 h1:giFlY12I07fugqwPuWJi68oOnpfqFnJIJzaIIm2JVV4= +golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ= +golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k= +golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= +google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= +gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +k8s.io/api v0.34.1 h1:jC+153630BMdlFukegoEL8E/yT7aLyQkIVuwhmwDgJM= +k8s.io/api v0.34.1/go.mod h1:SB80FxFtXn5/gwzCoN6QCtPD7Vbu5w2n1S0J5gFfTYk= +k8s.io/apiextensions-apiserver v0.34.1 h1:NNPBva8FNAPt1iSVwIE0FsdrVriRXMsaWFMqJbII2CI= +k8s.io/apiextensions-apiserver v0.34.1/go.mod h1:hP9Rld3zF5Ay2Of3BeEpLAToP+l4s5UlxiHfqRaRcMc= +k8s.io/apimachinery v0.34.1 h1:dTlxFls/eikpJxmAC7MVE8oOeP1zryV7iRyIjB0gky4= +k8s.io/apimachinery v0.34.1/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw= +k8s.io/component-base v0.34.1 h1:v7xFgG+ONhytZNFpIz5/kecwD+sUhVE6HU7qQUiRM4A= +k8s.io/component-base v0.34.1/go.mod h1:mknCpLlTSKHzAQJJnnHVKqjxR7gBeHRv0rPXA7gdtQ0= +k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= +k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= +k8s.io/utils v0.0.0-20250820121507-0af2bda4dd1d h1:wAhiDyZ4Tdtt7e46e9M5ZSAJ/MnPGPs+Ki1gHw4w1R0= +k8s.io/utils v0.0.0-20250820121507-0af2bda4dd1d/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +sigs.k8s.io/gateway-api v1.4.0 h1:ZwlNM6zOHq0h3WUX2gfByPs2yAEsy/EenYJB78jpQfQ= +sigs.k8s.io/gateway-api v1.4.0/go.mod h1:AR5RSqciWP98OPckEjOjh2XJhAe2Na4LHyXD2FUY7Qk= +sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg= +sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= +sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= +sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= +sigs.k8s.io/structured-merge-diff/v6 v6.3.0 h1:jTijUJbW353oVOd9oTlifJqOGEkUw2jB/fXCbTiQEco= +sigs.k8s.io/structured-merge-diff/v6 v6.3.0/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE= +sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs= +sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4= diff --git a/cmd/acmesolver/main.go b/cmd/acmesolver/main.go index 0d10c4ac519..e6430ec4ba4 100644 --- a/cmd/acmesolver/main.go +++ b/cmd/acmesolver/main.go @@ -17,11 +17,11 @@ limitations under the License. package main import ( - "fmt" - "os" + "context" - "github.com/cert-manager/cert-manager/cmd/acmesolver/app" - "github.com/cert-manager/cert-manager/cmd/util" + "github.com/cert-manager/cert-manager/acmesolver-binary/app" + "github.com/cert-manager/cert-manager/internal/cmd/util" + logf "github.com/cert-manager/cert-manager/pkg/logs" ) // acmesolver solves ACME http-01 challenges. This is intended to run as a pod @@ -29,13 +29,17 @@ import ( // cert-manager. func main() { - stopCh, exit := util.SetupExitHandler(util.GracefulShutdown) + ctx, exit := util.SetupExitHandler(context.Background(), util.GracefulShutdown) defer exit() // This function might call os.Exit, so defer last - cmd := app.NewACMESolverCommand(stopCh) + logf.InitLogs() + defer logf.FlushLogs() + ctx = logf.NewContext(ctx, logf.Log, "acmesolver") - if err := cmd.Execute(); err != nil { - fmt.Fprintf(os.Stderr, "%s\n", err) + cmd := app.NewACMESolverCommand(ctx) + + if err := cmd.ExecuteContext(ctx); err != nil { + logf.Log.Error(err, "error executing command") util.SetExitCode(err) } } diff --git a/cmd/cainjector/LICENSE b/cmd/cainjector/LICENSE new file mode 100644 index 00000000000..d6456956733 --- /dev/null +++ b/cmd/cainjector/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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. diff --git a/cmd/cainjector/LICENSES b/cmd/cainjector/LICENSES new file mode 100644 index 00000000000..0615212b770 --- /dev/null +++ b/cmd/cainjector/LICENSES @@ -0,0 +1,117 @@ +This LICENSES file is generated by the `licenses` module in makefile-modules[0]. + +The licenses below the "---" are determined by the go-licenses tool[1]. + +The aim of this file is to collect the licenses of all dependencies, and provide +a single source of truth for licenses used by this project. + +## For Developers + +If CI reports that this file is out of date, you should be careful to check that the +new licenses are acceptable for this project before running `make generate-go-licenses` +to update this file. + +Acceptable licenses are those allowlisted by the CNCF[2]. + +You MUST NOT add any new dependencies whose licenses are not allowlisted by the CNCF, +or which do not have an explicit license exception[3]. + +## For Users + +If this file was included in a release artifact, it is a snapshot of the licenses of all dependencies at the time of the release. + +You can retrieve the actual license text by following these steps: + +1. Find the dependency name in this file +2. Go to the source code repository of this project, and go to the tag corresponding to this release. +3. Find the exact version of the dependency in the `go.mod` file +4. Search for the dependency at the correct version in the [Go package index](https://pkg.go.dev/). + +## Links + +[0]: https://github.com/cert-manager/makefile-modules/ +[1]: https://github.com/google/go-licenses +[2]: https://github.com/cncf/foundation/blob/db4179134ebe7fa00b140a050c19147db808b6fa/policies-guidance/allowed-third-party-license-policy.md#cncf-allowlist-license-policy +[3]: https://github.com/cncf/foundation/blob/db4179134ebe7fa00b140a050c19147db808b6fa/license-exceptions/README.md + +--- + +github.com/Azure/go-ntlmssp,MIT +github.com/beorn7/perks/quantile,MIT +github.com/blang/semver/v4,MIT +github.com/cert-manager/cert-manager,Apache-2.0 +github.com/cert-manager/cert-manager/cainjector-binary,Apache-2.0 +github.com/cespare/xxhash/v2,MIT +github.com/davecgh/go-spew/spew,ISC +github.com/emicklei/go-restful/v3,MIT +github.com/evanphx/json-patch/v5,BSD-3-Clause +github.com/fsnotify/fsnotify,BSD-3-Clause +github.com/fxamacker/cbor/v2,MIT +github.com/go-asn1-ber/asn1-ber,MIT +github.com/go-ldap/ldap/v3,MIT +github.com/go-logr/logr,Apache-2.0 +github.com/go-logr/zapr,Apache-2.0 +github.com/go-openapi/jsonpointer,Apache-2.0 +github.com/go-openapi/jsonreference,Apache-2.0 +github.com/go-openapi/swag,Apache-2.0 +github.com/go-openapi/swag/jsonname,Apache-2.0 +github.com/gogo/protobuf,BSD-3-Clause +github.com/google/btree,Apache-2.0 +github.com/google/gnostic-models,Apache-2.0 +github.com/google/uuid,BSD-3-Clause +github.com/josharian/intern,MIT +github.com/json-iterator/go,MIT +github.com/mailru/easyjson,MIT +github.com/modern-go/concurrent,Apache-2.0 +github.com/modern-go/reflect2,Apache-2.0 +github.com/munnerz/goautoneg,BSD-3-Clause +github.com/pmezard/go-difflib/difflib,BSD-3-Clause +github.com/prometheus/client_golang/internal/github.com/golang/gddo/httputil,BSD-3-Clause +github.com/prometheus/client_golang/prometheus,Apache-2.0 +github.com/prometheus/client_model/go,Apache-2.0 +github.com/prometheus/common,Apache-2.0 +github.com/prometheus/procfs,Apache-2.0 +github.com/spf13/cobra,Apache-2.0 +github.com/spf13/pflag,BSD-3-Clause +github.com/x448/float16,MIT +go.opentelemetry.io/otel,Apache-2.0 +go.opentelemetry.io/otel/trace,Apache-2.0 +go.uber.org/multierr,MIT +go.uber.org/zap,MIT +go.yaml.in/yaml/v2,Apache-2.0 +go.yaml.in/yaml/v3,MIT +golang.org/x/crypto,BSD-3-Clause +golang.org/x/net,BSD-3-Clause +golang.org/x/oauth2,BSD-3-Clause +golang.org/x/sync/errgroup,BSD-3-Clause +golang.org/x/sys/unix,BSD-3-Clause +golang.org/x/term,BSD-3-Clause +golang.org/x/text,BSD-3-Clause +golang.org/x/time/rate,BSD-3-Clause +gomodules.xyz/jsonpatch/v2,Apache-2.0 +google.golang.org/protobuf,BSD-3-Clause +gopkg.in/evanphx/json-patch.v4,BSD-3-Clause +gopkg.in/inf.v0,BSD-3-Clause +gopkg.in/yaml.v3,MIT +k8s.io/api,Apache-2.0 +k8s.io/apiextensions-apiserver/pkg/apis/apiextensions,Apache-2.0 +k8s.io/apimachinery/pkg,Apache-2.0 +k8s.io/apimachinery/third_party/forked/golang,BSD-3-Clause +k8s.io/client-go,Apache-2.0 +k8s.io/component-base,Apache-2.0 +k8s.io/klog/v2,Apache-2.0 +k8s.io/kube-aggregator/pkg/apis/apiregistration,Apache-2.0 +k8s.io/kube-openapi/pkg,Apache-2.0 +k8s.io/kube-openapi/pkg/internal/third_party/go-json-experiment/json,BSD-3-Clause +k8s.io/kube-openapi/pkg/validation/spec,Apache-2.0 +k8s.io/utils,Apache-2.0 +k8s.io/utils/internal/third_party/forked/golang,BSD-3-Clause +sigs.k8s.io/controller-runtime,Apache-2.0 +sigs.k8s.io/gateway-api/apis/v1,Apache-2.0 +sigs.k8s.io/json,Apache-2.0 +sigs.k8s.io/json,BSD-3-Clause +sigs.k8s.io/randfill,Apache-2.0 +sigs.k8s.io/structured-merge-diff/v6,Apache-2.0 +sigs.k8s.io/yaml,MIT +sigs.k8s.io/yaml,Apache-2.0 +sigs.k8s.io/yaml,BSD-3-Clause diff --git a/cmd/cainjector/app/cainjector.go b/cmd/cainjector/app/cainjector.go new file mode 100644 index 00000000000..2292fb6454b --- /dev/null +++ b/cmd/cainjector/app/cainjector.go @@ -0,0 +1,179 @@ +/* +Copyright 2020 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. +*/ + +package app + +import ( + "context" + "fmt" + "os" + "path/filepath" + + "github.com/spf13/cobra" + + "github.com/cert-manager/cert-manager/cainjector-binary/app/options" + config "github.com/cert-manager/cert-manager/internal/apis/config/cainjector" + "github.com/cert-manager/cert-manager/internal/apis/config/cainjector/validation" + cainjectorconfigfile "github.com/cert-manager/cert-manager/pkg/cainjector/configfile" + logf "github.com/cert-manager/cert-manager/pkg/logs" + "github.com/cert-manager/cert-manager/pkg/util" + "github.com/cert-manager/cert-manager/pkg/util/configfile" + utilfeature "github.com/cert-manager/cert-manager/pkg/util/feature" +) + +const componentController = "cainjector" + +func NewCAInjectorCommand(ctx context.Context) *cobra.Command { + return newCAInjectorCommand( + ctx, + func(ctx context.Context, cfg *config.CAInjectorConfiguration) error { + log := logf.FromContext(ctx, componentController) + + versionInfo := util.VersionInfo() + log.Info("starting cert-manager ca-injector", "version", versionInfo.GitVersion, "git_commit", versionInfo.GitCommit, "go_version", versionInfo.GoVersion, "platform", versionInfo.Platform) + + return Run(cfg, ctx) + }, + os.Args[1:], + ) +} + +func newCAInjectorCommand( + setupCtx context.Context, + run func(context.Context, *config.CAInjectorConfiguration) error, + allArgs []string, +) *cobra.Command { + log := logf.FromContext(setupCtx, componentController) + + cainjectorFlags := options.NewCAInjectorFlags() + cainjectorConfig, err := options.NewCAInjectorConfiguration() + if err != nil { + log.Error(err, "Failed to create new cainjector configuration") + os.Exit(1) + } + + cmd := &cobra.Command{ + Use: componentController, + Long: ` +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.`, + + SilenceErrors: true, // We already log errors in main.go + SilenceUsage: true, // Don't print usage on every error + + PreRunE: func(cmd *cobra.Command, args []string) error { + if err := loadConfigFromFile( + cmd, allArgs, cainjectorFlags.Config, cainjectorConfig, + func() error { + // set feature gates from initial flags-based config + if err := utilfeature.DefaultMutableFeatureGate.SetFromMap(cainjectorConfig.FeatureGates); err != nil { + return fmt.Errorf("failed to set feature gates from initial flags-based config: %w", err) + } + + return nil + }, + ); err != nil { + return err + } + + if err := validation.ValidateCAInjectorConfiguration(cainjectorConfig, nil); len(err) > 0 { + return fmt.Errorf("error validating flags: %w", err.ToAggregate()) + } + + // ValidateCAInjectorConfiguration should already have validated the + // logging flags, the logging API does not have an Apply-only function + // so we validate again here. This should not catch any validation errors + // anymore. + if err := logf.ValidateAndApply(&cainjectorConfig.Logging); err != nil { + return fmt.Errorf("failed to validate cainjector logging flags: %w", err) + } + + return nil + }, + // nolint:contextcheck // False positive + RunE: func(cmd *cobra.Command, args []string) error { + return run(cmd.Context(), cainjectorConfig) + }, + } + + cainjectorFlags.AddFlags(cmd.Flags()) + options.AddConfigFlags(cmd.Flags(), cainjectorConfig) + + // explicitly set provided args in case it does not equal os.Args[:1], + // e.g., when running tests + cmd.SetArgs(allArgs) + + return cmd +} + +// loadConfigFromFile loads the configuration from the provided config file +// path, if one is provided. After loading the config file, the flags are +// re-parsed to ensure that any flags provided to the command line override +// those provided in the config file. +// The newConfigHook is called when the options have been loaded from the +// flags (but not yet the config file) and is re-called after the config file +// has been loaded. This allows us to use the feature flags set by the flags +// while loading the config file. +func loadConfigFromFile( + cmd *cobra.Command, + allArgs []string, + configFilePath string, + cfg *config.CAInjectorConfiguration, + newConfigHook func() error, +) error { + if err := newConfigHook(); err != nil { + return err + } + + if len(configFilePath) > 0 { + // compute absolute path based on current working dir + cainjectorConfigFile, err := filepath.Abs(configFilePath) + if err != nil { + return fmt.Errorf("failed to load config file %s, error %v", configFilePath, err) + } + + loader, err := configfile.NewConfigurationFSLoader(nil, cainjectorConfigFile) + if err != nil { + return fmt.Errorf("failed to load config file %s, error %v", configFilePath, err) + } + + cainjectorConfigFromFile := cainjectorconfigfile.New() + if err := loader.Load(cainjectorConfigFromFile); err != nil { + return fmt.Errorf("failed to load config file %s, error %v", configFilePath, err) + } + + cainjectorConfigFromFile.Config.DeepCopyInto(cfg) + + _, args, err := cmd.Root().Find(allArgs) + if err != nil { + return fmt.Errorf("failed to re-parse flags: %w", err) + } + + if err := cmd.ParseFlags(args); err != nil { + return fmt.Errorf("failed to re-parse flags: %w", err) + } + + if err := newConfigHook(); err != nil { + return err + } + } + + return nil +} diff --git a/cmd/cainjector/app/cainjector_test.go b/cmd/cainjector/app/cainjector_test.go new file mode 100644 index 00000000000..b5da8406c49 --- /dev/null +++ b/cmd/cainjector/app/cainjector_test.go @@ -0,0 +1,201 @@ +/* +Copyright 2021 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. +*/ + +package app + +import ( + "context" + "fmt" + "io" + "os" + "path" + "reflect" + "testing" + + logsapi "k8s.io/component-base/logs/api/v1" + + "github.com/cert-manager/cert-manager/cainjector-binary/app/options" + config "github.com/cert-manager/cert-manager/internal/apis/config/cainjector" +) + +func testCmdCommand(t *testing.T, tempDir string, yaml string, args func(string) []string) (*config.CAInjectorConfiguration, error) { + var tempFilePath string + + func() { + tempFile, err := os.CreateTemp(tempDir, "config-*.yaml") + if err != nil { + t.Error(err) + } + defer tempFile.Close() + + tempFilePath = tempFile.Name() + + if _, err := tempFile.WriteString(yaml); err != nil { + t.Error(err) + } + }() + + var finalConfig *config.CAInjectorConfiguration + + if err := logsapi.ResetForTest(nil); err != nil { + t.Error(err) + } + + cmd := newCAInjectorCommand(t.Context(), func(ctx context.Context, cc *config.CAInjectorConfiguration) error { + finalConfig = cc + return nil + }, args(tempFilePath)) + + cmd.SetErr(io.Discard) + cmd.SetOut(io.Discard) + + err := cmd.ExecuteContext(t.Context()) + return finalConfig, err +} + +func TestFlagsAndConfigFile(t *testing.T) { + type testCase struct { + yaml string + args func(string) []string + expError bool + expConfig func(string) *config.CAInjectorConfiguration + } + + configFromDefaults := func( + fn func(string, *config.CAInjectorConfiguration), + ) func(string) *config.CAInjectorConfiguration { + defaults, err := options.NewCAInjectorConfiguration() + if err != nil { + t.Error(err) + } + return func(tempDir string) *config.CAInjectorConfiguration { + fn(tempDir, defaults) + return defaults + } + } + + tests := []testCase{ + { + yaml: ``, + args: func(tempFilePath string) []string { + return []string{"--kubeconfig=valid"} + }, + expConfig: configFromDefaults(func(tempDir string, cc *config.CAInjectorConfiguration) { + cc.KubeConfig = "valid" + }), + }, + { + yaml: ` +apiVersion: cainjector.config.cert-manager.io/v1alpha1 +kind: CAInjectorConfiguration +kubeConfig: "" +`, + args: func(tempFilePath string) []string { + return []string{"--config=" + tempFilePath, "--kubeconfig=valid"} + }, + expConfig: configFromDefaults(func(tempDir string, cc *config.CAInjectorConfiguration) { + cc.KubeConfig = "valid" + }), + }, + { + yaml: ` +apiVersion: cainjector.config.cert-manager.io/v1alpha1 +kind: CAInjectorConfiguration +kubeConfig: valid +`, + args: func(tempFilePath string) []string { + return []string{"--config=" + tempFilePath} + }, + expConfig: configFromDefaults(func(tempDir string, cc *config.CAInjectorConfiguration) { + cc.KubeConfig = path.Join(tempDir, "valid") + }), + }, + { + yaml: ` +apiVersion: cainjector.config.cert-manager.io/v1alpha1 +kind: CAInjectorConfiguration +enableDataSourceConfig: {} +`, + args: func(tempFilePath string) []string { + return []string{"--config=" + tempFilePath} + }, + expConfig: configFromDefaults(func(tempDir string, cc *config.CAInjectorConfiguration) { + }), + }, + { + yaml: ` +apiVersion: cainjector.config.cert-manager.io/v1alpha1 +kind: CAInjectorConfiguration +enableDataSourceConfig: nil +`, + args: func(tempFilePath string) []string { + return []string{"--config=" + tempFilePath} + }, + expError: true, + }, + { + yaml: ` +apiVersion: cainjector.config.cert-manager.io/v1alpha1 +kind: CAInjectorConfiguration +enableInjectableConfig: + validatingWebhookConfigurations: false +`, + args: func(tempFilePath string) []string { + return []string{"--config=" + tempFilePath, "--enable-mutatingwebhookconfigurations-injectable=false"} + }, + expConfig: configFromDefaults(func(tempDir string, cc *config.CAInjectorConfiguration) { + cc.EnableInjectableConfig.ValidatingWebhookConfigurations = false + cc.EnableInjectableConfig.MutatingWebhookConfigurations = false + }), + }, + { + yaml: ` +apiVersion: cainjector.config.cert-manager.io/v1alpha1 +kind: CAInjectorConfiguration +logging: + verbosity: 2 + format: text +`, + args: func(tempFilePath string) []string { + return []string{"--config=" + tempFilePath} + }, + expConfig: configFromDefaults(func(tempDir string, cc *config.CAInjectorConfiguration) { + cc.Logging.Verbosity = 2 + cc.Logging.Format = "text" + }), + }, + } + + for i, tc := range tests { + t.Run(fmt.Sprintf("test-%d", i), func(t *testing.T) { + tempDir := t.TempDir() + + config, err := testCmdCommand(t, tempDir, tc.yaml, tc.args) + if tc.expError != (err != nil) { + if err == nil { + t.Error("expected error, got nil") + } else { + t.Errorf("unexpected error: %v", err) + } + } else if !tc.expError { + expConfig := tc.expConfig(tempDir) + if !reflect.DeepEqual(config, expConfig) { + t.Errorf("expected config %v but got %v", expConfig, config) + } + } + }) + } +} diff --git a/cmd/cainjector/app/controller.go b/cmd/cainjector/app/controller.go new file mode 100644 index 00000000000..75a5d0e4dbf --- /dev/null +++ b/cmd/cainjector/app/controller.go @@ -0,0 +1,317 @@ +/* +Copyright 2020 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. +*/ + +package app + +import ( + "context" + "crypto/tls" + "fmt" + "net" + "net/http" + "time" + + corev1 "k8s.io/api/core/v1" + apiext "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + utilruntime "k8s.io/apimachinery/pkg/util/runtime" + "k8s.io/apimachinery/pkg/util/wait" + kscheme "k8s.io/client-go/kubernetes/scheme" + "k8s.io/client-go/rest" + "k8s.io/client-go/tools/leaderelection/resourcelock" + ciphers "k8s.io/component-base/cli/flag" + apireg "k8s.io/kube-aggregator/pkg/apis/apiregistration/v1" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/cache" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/manager" + metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server" + + config "github.com/cert-manager/cert-manager/internal/apis/config/cainjector" + "github.com/cert-manager/cert-manager/internal/apis/config/shared" + cmscheme "github.com/cert-manager/cert-manager/pkg/client/clientset/versioned/scheme" + "github.com/cert-manager/cert-manager/pkg/controller/cainjector" + logf "github.com/cert-manager/cert-manager/pkg/logs" + cmservertls "github.com/cert-manager/cert-manager/pkg/server/tls" + "github.com/cert-manager/cert-manager/pkg/server/tls/authority" + "github.com/cert-manager/cert-manager/pkg/util" + "github.com/cert-manager/cert-manager/pkg/util/profiling" +) + +const ( + // This is intended to mitigate "slowloris" attacks by limiting the time a + // deliberately slow client can spend sending HTTP headers. + // This default value is copied from: + // * kubernetes api-server: + // https://github.com/kubernetes/kubernetes/blob/9e028b40b9e970142191259effe796b3dab39828/staging/src/k8s.io/apiserver/pkg/server/secure_serving.go#L165-L173 + // * controller-runtime: + // https://github.com/kubernetes-sigs/controller-runtime/blob/1ea2be573f7887a9fbd766e9a921c5af344da6eb/pkg/internal/httpserver/server.go#L14 + defaultReadHeaderTimeout = 32 * time.Second +) + +func Run(opts *config.CAInjectorConfiguration, ctx context.Context) error { + log := logf.FromContext(ctx) + + restConfig := util.RestConfigWithUserAgent(ctrl.GetConfigOrDie(), "cainjector") + + var defaultNamespaces map[string]cache.Config + if opts.Namespace != "" { + // If a namespace has been provided, only watch resources in that namespace + defaultNamespaces = map[string]cache.Config{ + opts.Namespace: {}, + } + } + + metricsServerCertificateSource := buildCertificateSource(opts.MetricsTLSConfig, restConfig) + metricsServerOptions, err := buildMetricsServerOptions(opts, metricsServerCertificateSource) + if err != nil { + return err + } + + scheme := runtime.NewScheme() + utilruntime.Must(kscheme.AddToScheme(scheme)) + utilruntime.Must(cmscheme.AddToScheme(scheme)) + utilruntime.Must(apiext.AddToScheme(scheme)) + utilruntime.Must(apireg.AddToScheme(scheme)) + + mgr, err := ctrl.NewManager( + restConfig, + ctrl.Options{ + Scheme: scheme, + Cache: cache.Options{ + ReaderFailOnMissingInformer: true, + DefaultNamespaces: defaultNamespaces, + }, + Client: client.Options{ + Cache: &client.CacheOptions{ + // Why do we disable the cache for v1.Secret? + // + // 1. To reduce memory use of cainjector, by disabling + // in-memory cache of Secret resources. + // 2. To reduce the load on the K8S API server when + // cainjector starts up, caused by the initial listing of + // Secret resources in the cluster. + // + // Clusters may contain many and / or large Secret + // resources. + // For example OpenShift clusters may have thousands of + // ServiceAccounts and each of these has a Secret with the + // associated token. + // Or where helm is used, there will be large Secret + // resources containing the configuration of each Helm + // deployment. + // + // Ordinarily, the controller-runtime client would implicitly + // initialize a client-go cache which would list every + // Secret, including the entire data of every Secret. + // This initial list operation can place enormous load on + // the K8S API server. + // + // The problem can be alleviated by disabling the implicit cache: + // * Here in the client CacheOptions and, + // * in NewControllerManagedBy.Watches, by supplying the + // builder.OnlyMetadata option. + // + // The disadvantage is that this will cause *increased* + // ongoing load on the K8S API server later, because the + // reconciler for each injectable will GET the source Secret + // directly from the K8S API server every time the + // injectable is reconciled. + DisableFor: []client.Object{ + &corev1.Secret{}, + }, + }, + }, + LeaderElection: opts.LeaderElectionConfig.Enabled, + LeaderElectionNamespace: opts.LeaderElectionConfig.Namespace, + LeaderElectionID: "cert-manager-cainjector-leader-election", + LeaderElectionReleaseOnCancel: true, + LeaderElectionResourceLock: resourcelock.LeasesResourceLock, + LeaseDuration: &opts.LeaderElectionConfig.LeaseDuration, + RenewDeadline: &opts.LeaderElectionConfig.RenewDeadline, + RetryPeriod: &opts.LeaderElectionConfig.RetryPeriod, + Metrics: *metricsServerOptions, + }) + if err != nil { + return fmt.Errorf("error creating manager: %v", err) + } + + if metricsServerCertificateSource != nil { + if err := mgr.Add(metricsServerCertificateSource); err != nil { + return err + } + } + + // if a PprofAddr is provided, start the pprof listener + if opts.EnablePprof { + lc := net.ListenConfig{} + pprofListener, err := lc.Listen(ctx, "tcp", opts.PprofAddress) + if err != nil { + return err + } + + profilerMux := http.NewServeMux() + // Add pprof endpoints to this mux + profiling.Install(profilerMux) + log.V(logf.InfoLevel).Info("running go profiler on", "address", opts.PprofAddress) + server := &http.Server{ + Handler: profilerMux, + ReadHeaderTimeout: defaultReadHeaderTimeout, // Mitigation for G112: Potential slowloris attack + } + + if err := mgr.Add(runnableNoLeaderElectionFunc(func(ctx context.Context) error { + <-ctx.Done() + + // allow a timeout for graceful shutdown + shutdownCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + + // nolint: contextcheck + return server.Shutdown(shutdownCtx) + })); err != nil { + return err + } + + if err := mgr.Add(runnableNoLeaderElectionFunc(func(ctx context.Context) error { + if err := server.Serve(pprofListener); err != http.ErrServerClosed { + return err + } + return nil + })); err != nil { + return err + } + } + + // If cainjector has been configured to watch Certificate CRDs (true by default) + // (--enable-certificates-data-source=true), poll kubeapiserver for 5 minutes or till + // certificate CRD is found. + if opts.EnableDataSourceConfig.Certificates { + directClient, err := client.New(mgr.GetConfig(), client.Options{ + Scheme: mgr.GetScheme(), + Mapper: mgr.GetRESTMapper(), + }) + if err != nil { + return fmt.Errorf("failed to create client: %w", err) + } + err = wait.PollUntilContextTimeout(ctx, time.Second, time.Minute*5, true, func(ctx context.Context) (bool, error) { + certsCRDName := types.NamespacedName{Name: "certificates.cert-manager.io"} + certsCRD := apiext.CustomResourceDefinition{} + err := directClient.Get(ctx, certsCRDName, &certsCRD) + if apierrors.IsNotFound(err) { + log.Info("cainjector has been configured to watch certificates, but certificates.cert-manager.io CRD not found, retrying with a backoff...") + return false, nil + } else if err != nil { + log.Error(err, "error checking if certificates.cert-manager.io CRD is installed") + return false, err + } + log.V(logf.DebugLevel).Info("certificates.cert-manager.io CRD found") + return true, nil + }) + if err != nil { + log.Error(err, "error retrieving certificate.cert-manager.io CRDs") + return err + } + } + + setupOptions := cainjector.SetupOptions{ + Namespace: opts.Namespace, + EnableCertificatesDataSource: opts.EnableDataSourceConfig.Certificates, + EnabledReconcilersFor: map[string]bool{ + cainjector.MutatingWebhookConfigurationName: opts.EnableInjectableConfig.MutatingWebhookConfigurations, + cainjector.ValidatingWebhookConfigurationName: opts.EnableInjectableConfig.ValidatingWebhookConfigurations, + cainjector.APIServiceName: opts.EnableInjectableConfig.APIServices, + cainjector.CustomResourceDefinitionName: opts.EnableInjectableConfig.CustomResourceDefinitions, + }, + } + + err = cainjector.RegisterAllInjectors(ctx, mgr, setupOptions) + if err != nil { + log.Error(err, "failed to register controllers") + return err + } + + if err = mgr.Start(ctx); err != nil { + return fmt.Errorf("error running manager: %v", err) + } + + return nil +} + +type runnableNoLeaderElectionFunc func(context.Context) error + +func (r runnableNoLeaderElectionFunc) Start(ctx context.Context) error { + return r(ctx) +} + +func (runnableNoLeaderElectionFunc) NeedLeaderElection() bool { + // By default, a runnable in c/r is leader election aware. + // Since we need to run this runnable for all replicas, this runnable must NOT be leader election aware. + return false +} + +var _ manager.Runnable = runnableNoLeaderElectionFunc(nil) + +var _ manager.LeaderElectionRunnable = runnableNoLeaderElectionFunc(nil) + +func buildMetricsServerOptions(opts *config.CAInjectorConfiguration, cs cmservertls.CertificateSource) (*metricsserver.Options, error) { + msOptions := metricsserver.Options{ + BindAddress: opts.MetricsListenAddress, + } + if cs != nil { + metricsCipherSuites, err := ciphers.TLSCipherSuites(opts.MetricsTLSConfig.CipherSuites) + if err != nil { + return nil, err + } + metricsMinVersion, err := ciphers.TLSVersion(opts.MetricsTLSConfig.MinTLSVersion) + if err != nil { + return nil, err + } + msOptions.SecureServing = true + msOptions.TLSOpts = []func(*tls.Config){ + func(cfg *tls.Config) { + cfg.CipherSuites = metricsCipherSuites + cfg.MinVersion = metricsMinVersion + cfg.GetCertificate = cs.GetCertificate + }, + } + } + return &msOptions, nil +} + +func buildCertificateSource(tlsConfig shared.TLSConfig, restCfg *rest.Config) cmservertls.CertificateSource { + switch { + case tlsConfig.FilesystemConfigProvided(): + return &cmservertls.FileCertificateSource{ + CertPath: tlsConfig.Filesystem.CertFile, + KeyPath: tlsConfig.Filesystem.KeyFile, + } + + case tlsConfig.DynamicConfigProvided(): + return &cmservertls.DynamicSource{ + DNSNames: tlsConfig.Dynamic.DNSNames, + Authority: &authority.DynamicAuthority{ + SecretNamespace: tlsConfig.Dynamic.SecretNamespace, + SecretName: tlsConfig.Dynamic.SecretName, + SecretLabels: map[string]string{"app.kubernetes.io/managed-by": "cert-manager-cainjector"}, + LeafDuration: tlsConfig.Dynamic.LeafDuration, + RESTConfig: restCfg, + }, + } + } + return nil +} diff --git a/cmd/cainjector/app/options/options.go b/cmd/cainjector/app/options/options.go new file mode 100644 index 00000000000..82e7320692d --- /dev/null +++ b/cmd/cainjector/app/options/options.go @@ -0,0 +1,141 @@ +/* +Copyright 2020 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. +*/ + +package options + +import ( + "flag" + "strings" + + "github.com/spf13/pflag" + cliflag "k8s.io/component-base/cli/flag" + ctrlconfig "sigs.k8s.io/controller-runtime/pkg/client/config" + + config "github.com/cert-manager/cert-manager/internal/apis/config/cainjector" + configscheme "github.com/cert-manager/cert-manager/internal/apis/config/cainjector/scheme" + configv1alpha1 "github.com/cert-manager/cert-manager/pkg/apis/config/cainjector/v1alpha1" + logf "github.com/cert-manager/cert-manager/pkg/logs" + utilfeature "github.com/cert-manager/cert-manager/pkg/util/feature" +) + +// CAInjectorFlags defines options that can only be configured via flags. +type CAInjectorFlags struct { + // Path to a file containing a CAInjectorConfiguration resource + Config string +} + +func NewCAInjectorFlags() *CAInjectorFlags { + return &CAInjectorFlags{} +} + +func (f *CAInjectorFlags) AddFlags(fs *pflag.FlagSet) { + fs.StringVar(&f.Config, "config", "", "Path to a file containing a CAInjectorConfiguration object used to configure the controller") +} + +func NewCAInjectorConfiguration() (*config.CAInjectorConfiguration, error) { + scheme, _, err := configscheme.NewSchemeAndCodecs() + if err != nil { + return nil, err + } + versioned := &configv1alpha1.CAInjectorConfiguration{} + scheme.Default(versioned) + config := &config.CAInjectorConfiguration{} + if err := scheme.Convert(versioned, config, nil); err != nil { + return nil, err + } + return config, nil +} + +func AddConfigFlags(fs *pflag.FlagSet, c *config.CAInjectorConfiguration) { + fs.StringVar(&c.KubeConfig, "kubeconfig", c.KubeConfig, ""+ + "Paths to a kubeconfig. Only required if out-of-cluster.") + fs.StringVar(&c.Namespace, "namespace", c.Namespace, ""+ + "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.") + fs.BoolVar(&c.LeaderElectionConfig.Enabled, "leader-elect", c.LeaderElectionConfig.Enabled, ""+ + "If true, cainjector will perform leader election between instances to ensure no more "+ + "than one instance of cainjector operates at a time") + fs.StringVar(&c.LeaderElectionConfig.Namespace, "leader-election-namespace", c.LeaderElectionConfig.Namespace, ""+ + "Namespace used to perform leader election. Only used if leader election is enabled") + fs.DurationVar(&c.LeaderElectionConfig.LeaseDuration, "leader-election-lease-duration", c.LeaderElectionConfig.LeaseDuration, ""+ + "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.") + fs.DurationVar(&c.LeaderElectionConfig.RenewDeadline, "leader-election-renew-deadline", c.LeaderElectionConfig.RenewDeadline, ""+ + "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.") + fs.DurationVar(&c.LeaderElectionConfig.RetryPeriod, "leader-election-retry-period", c.LeaderElectionConfig.RetryPeriod, ""+ + "The duration the clients should wait between attempting acquisition and renewal "+ + "of a leadership. This is only applicable if leader election is enabled.") + + fs.BoolVar(&c.EnableDataSourceConfig.Certificates, "enable-certificates-data-source", c.EnableDataSourceConfig.Certificates, ""+ + "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") + fs.BoolVar(&c.EnableInjectableConfig.ValidatingWebhookConfigurations, "enable-validatingwebhookconfigurations-injectable", c.EnableInjectableConfig.ValidatingWebhookConfigurations, ""+ + "Inject CA data to annotated ValidatingWebhookConfigurations. This functionality is required "+ + "for cainjector to correctly function as cert-manager's internal component") + fs.BoolVar(&c.EnableInjectableConfig.MutatingWebhookConfigurations, "enable-mutatingwebhookconfigurations-injectable", c.EnableInjectableConfig.MutatingWebhookConfigurations, ""+ + "Inject CA data to annotated MutatingWebhookConfigurations. This functionality is required for "+ + "cainjector to work correctly as cert-manager's internal component") + fs.BoolVar(&c.EnableInjectableConfig.CustomResourceDefinitions, "enable-customresourcedefinitions-injectable", c.EnableInjectableConfig.CustomResourceDefinitions, ""+ + "Inject CA data to annotated CustomResourceDefinitions. This functionality is not required if "+ + "cainjector is only used as cert-manager's internal component and setting it to false might slightly reduce memory consumption") + fs.BoolVar(&c.EnableInjectableConfig.APIServices, "enable-apiservices-injectable", c.EnableInjectableConfig.APIServices, ""+ + "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") + + fs.BoolVar(&c.EnablePprof, "enable-profiling", c.EnablePprof, ""+ + "Enable profiling for controller.") + fs.StringVar(&c.PprofAddress, "profiler-address", c.PprofAddress, + "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.") + + fs.Var(cliflag.NewMapStringBool(&c.FeatureGates), "feature-gates", "A set of key=value pairs that describe feature gates for alpha/experimental features. "+ + "Options are:\n"+strings.Join(utilfeature.DefaultFeatureGate.KnownFeatures(), "\n")) + + logf.AddFlags(&c.Logging, fs) + + fs.StringVar(&c.MetricsListenAddress, "metrics-listen-address", c.MetricsListenAddress, "The host and port that the metrics endpoint should listen on. The value '0' disables the metrics server") + fs.StringVar(&c.MetricsTLSConfig.Filesystem.CertFile, "metrics-tls-cert-file", c.MetricsTLSConfig.Filesystem.CertFile, "path to the file containing the TLS certificate to serve metrics with") + fs.StringVar(&c.MetricsTLSConfig.Filesystem.KeyFile, "metrics-tls-private-key-file", c.MetricsTLSConfig.Filesystem.KeyFile, "path to the file containing the TLS private key to serve metrics with") + + fs.DurationVar(&c.MetricsTLSConfig.Dynamic.LeafDuration, "metrics-dynamic-serving-leaf-duration", c.MetricsTLSConfig.Dynamic.LeafDuration, "leaf duration of metrics serving certificates") + fs.StringVar(&c.MetricsTLSConfig.Dynamic.SecretNamespace, "metrics-dynamic-serving-ca-secret-namespace", c.MetricsTLSConfig.Dynamic.SecretNamespace, "namespace of the secret used to store the CA that signs metrics serving certificates") + fs.StringVar(&c.MetricsTLSConfig.Dynamic.SecretName, "metrics-dynamic-serving-ca-secret-name", c.MetricsTLSConfig.Dynamic.SecretName, "name of the secret used to store the CA that signs serving certificates") + fs.StringSliceVar(&c.MetricsTLSConfig.Dynamic.DNSNames, "metrics-dynamic-serving-dns-names", c.MetricsTLSConfig.Dynamic.DNSNames, "DNS names that should be present on certificates generated by the metrics dynamic serving CA") + + tlsCipherPossibleValues := cliflag.TLSCipherPossibleValues() + fs.StringSliceVar(&c.MetricsTLSConfig.CipherSuites, "metrics-tls-cipher-suites", c.MetricsTLSConfig.CipherSuites, + "Comma-separated list of cipher suites for the metrics server. "+ + "If omitted, the default Go cipher suites will be used. "+ + "Possible values: "+strings.Join(tlsCipherPossibleValues, ",")) + tlsPossibleVersions := cliflag.TLSPossibleVersions() + fs.StringVar(&c.MetricsTLSConfig.MinTLSVersion, "metrics-tls-min-version", c.MetricsTLSConfig.MinTLSVersion, + "Minimum TLS version supported by the metrics server. If omitted, the default Go minimum version will be used. "+ + "Possible values: "+strings.Join(tlsPossibleVersions, ", ")) + + // The controller-runtime flag (--kubeconfig) that we need + // relies on the "flag" package but we use "spf13/pflag". + var controllerRuntimeFlags flag.FlagSet + ctrlconfig.RegisterFlags(&controllerRuntimeFlags) + controllerRuntimeFlags.VisitAll(func(f *flag.Flag) { + fs.AddGoFlag(f) + }) +} diff --git a/cmd/cainjector/app/start.go b/cmd/cainjector/app/start.go deleted file mode 100644 index 713c80ed8f2..00000000000 --- a/cmd/cainjector/app/start.go +++ /dev/null @@ -1,244 +0,0 @@ -/* -Copyright 2020 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. -*/ - -package app - -import ( - "context" - "fmt" - "io" - "net" - "net/http" - "time" - - "github.com/go-logr/logr" - "github.com/spf13/cobra" - "github.com/spf13/pflag" - "golang.org/x/sync/errgroup" - _ "k8s.io/client-go/plugin/pkg/client/auth" - "k8s.io/client-go/tools/leaderelection/resourcelock" - ctrl "sigs.k8s.io/controller-runtime" - - cmdutil "github.com/cert-manager/cert-manager/cmd/util" - "github.com/cert-manager/cert-manager/pkg/api" - "github.com/cert-manager/cert-manager/pkg/controller/cainjector" - logf "github.com/cert-manager/cert-manager/pkg/logs" - "github.com/cert-manager/cert-manager/pkg/util" - utilfeature "github.com/cert-manager/cert-manager/pkg/util/feature" - "github.com/cert-manager/cert-manager/pkg/util/profiling" -) - -// InjectorControllerOptions is a struct having injector controller options values -type InjectorControllerOptions struct { - Namespace string - LeaderElect bool - LeaderElectionNamespace string - LeaseDuration time.Duration - RenewDeadline time.Duration - RetryPeriod time.Duration - - StdOut io.Writer - StdErr io.Writer - - // EnablePprof determines whether Go profiler should be run. - EnablePprof bool - // PprofAddr is the address at which Go profiler will be run if enabled. - // The profiler should never be exposed on a public address. - PprofAddr string - - // logger to be used by this controller - log logr.Logger -} - -// AddFlags adds the various flags for injector controller options -func (o *InjectorControllerOptions) AddFlags(fs *pflag.FlagSet) { - fs.StringVar(&o.Namespace, "namespace", "", ""+ - "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.") - fs.BoolVar(&o.LeaderElect, "leader-elect", cmdutil.DefaultLeaderElect, ""+ - "If true, cainjector will perform leader election between instances to ensure no more "+ - "than one instance of cainjector operates at a time") - fs.StringVar(&o.LeaderElectionNamespace, "leader-election-namespace", cmdutil.DefaultLeaderElectionNamespace, ""+ - "Namespace used to perform leader election. Only used if leader election is enabled") - fs.DurationVar(&o.LeaseDuration, "leader-election-lease-duration", cmdutil.DefaultLeaderElectionLeaseDuration, ""+ - "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.") - fs.DurationVar(&o.RenewDeadline, "leader-election-renew-deadline", cmdutil.DefaultLeaderElectionRenewDeadline, ""+ - "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.") - fs.DurationVar(&o.RetryPeriod, "leader-election-retry-period", cmdutil.DefaultLeaderElectionRetryPeriod, ""+ - "The duration the clients should wait between attempting acquisition and renewal "+ - "of a leadership. This is only applicable if leader election is enabled.") - - fs.BoolVar(&o.EnablePprof, "enable-profiling", cmdutil.DefaultEnableProfiling, "Enable profiling for cainjector") - fs.StringVar(&o.PprofAddr, "profiler-address", cmdutil.DefaultProfilerAddr, "Address of the Go profiler (pprof) if enabled. This should never be exposed on a public interface.") - - utilfeature.DefaultMutableFeatureGate.AddFlag(fs) -} - -// NewInjectorControllerOptions returns a new InjectorControllerOptions -func NewInjectorControllerOptions(out, errOut io.Writer) *InjectorControllerOptions { - o := &InjectorControllerOptions{ - StdOut: out, - StdErr: errOut, - } - - return o -} - -// NewCommandStartInjectorController is a CLI handler for starting cert-manager -func NewCommandStartInjectorController(ctx context.Context, out, errOut io.Writer) *cobra.Command { - o := NewInjectorControllerOptions(out, errOut) - - cmd := &cobra.Command{ - Use: "ca-injector", - Short: fmt.Sprintf("CA Injection Controller for Kubernetes (%s) (%s)", util.AppVersion, util.AppGitCommit), - Long: ` -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.`, - - // TODO: Refactor this function from this package - RunE: func(cmd *cobra.Command, args []string) error { - o.log = logf.Log.WithName("ca-injector") - - logf.V(logf.InfoLevel).InfoS("starting", "version", util.AppVersion, "revision", util.AppGitCommit) - return o.RunInjectorController(ctx) - }, - } - - flags := cmd.Flags() - o.AddFlags(flags) - - return cmd -} - -func (o InjectorControllerOptions) RunInjectorController(ctx context.Context) error { - mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{ - Scheme: api.Scheme, - Namespace: o.Namespace, - LeaderElection: o.LeaderElect, - LeaderElectionNamespace: o.LeaderElectionNamespace, - LeaderElectionID: "cert-manager-cainjector-leader-election", - LeaderElectionReleaseOnCancel: true, - LeaderElectionResourceLock: resourcelock.LeasesResourceLock, - LeaseDuration: &o.LeaseDuration, - RenewDeadline: &o.RenewDeadline, - RetryPeriod: &o.RetryPeriod, - MetricsBindAddress: "0", - }) - if err != nil { - return fmt.Errorf("error creating manager: %v", err) - } - - g, gctx := errgroup.WithContext(ctx) - - // if a PprofAddr is provided, start the pprof listener - if o.EnablePprof { - pprofListener, err := net.Listen("tcp", o.PprofAddr) - if err != nil { - return err - } - - profilerMux := http.NewServeMux() - // Add pprof endpoints to this mux - profiling.Install(profilerMux) - o.log.V(logf.InfoLevel).Info("running go profiler on", "address", o.PprofAddr) - server := &http.Server{ - Handler: profilerMux, - } - g.Go(func() error { - <-gctx.Done() - // allow a timeout for graceful shutdown - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) - defer cancel() - - if err := server.Shutdown(ctx); err != nil { - return err - } - return nil - }) - g.Go(func() error { - if err := server.Serve(pprofListener); err != http.ErrServerClosed { - return err - } - return nil - }) - } - - g.Go(func() (err error) { - defer func() { - o.log.Error(err, "manager goroutine exited") - }() - - if err = mgr.Start(gctx); err != nil { - return fmt.Errorf("error running manager: %v", err) - } - return nil - }) - - select { - case <-gctx.Done(): // Exit early if we are shutting down or if the manager has exited with an error - // Wait for error group to complete and return - return g.Wait() - case <-mgr.Elected(): // Don't launch the controllers unless we have been elected leader - // Continue with setting up controller - } - - // Retry the start up of the certificate based controller in case the - // cert-manager CRDs have not been installed yet or in case the CRD API is - // not working. E.g. The conversion webhook has not yet had its CA bundle - // injected by the secret based controller, which is launched in its own - // goroutine. - // When shutting down, return the last error if there is one. - // Never retry if the controller exits cleanly. - g.Go(func() (err error) { - for { - err = cainjector.RegisterCertificateBased(gctx, mgr) - if err == nil { - return - } - o.log.Error(err, "Error registering certificate based controllers. Retrying after 5 seconds.") - select { - case <-time.After(time.Second * 5): - case <-gctx.Done(): - return - } - } - }) - - // Secrets based controller is started in its own goroutine so that it can - // perform injection of the CA bundle into any webhooks required by the - // cert-manager CRD API. - // We do not retry this controller because it only interacts with core APIs - // which should always be in a working state. - g.Go(func() (err error) { - if err = cainjector.RegisterSecretBased(gctx, mgr); err != nil { - return fmt.Errorf("error registering secret controller: %v", err) - } - return - }) - - return g.Wait() -} diff --git a/cmd/cainjector/go.mod b/cmd/cainjector/go.mod new file mode 100644 index 00000000000..cee8ab98e1b --- /dev/null +++ b/cmd/cainjector/go.mod @@ -0,0 +1,87 @@ +module github.com/cert-manager/cert-manager/cainjector-binary + +go 1.25.0 + +// Do not remove this comment: +// please place any replace statements here at the top for visibility and add a +// comment to it as to when it can be removed + +replace github.com/cert-manager/cert-manager => ../../ + +require ( + github.com/cert-manager/cert-manager v0.0.0-00010101000000-000000000000 + github.com/spf13/cobra v1.10.1 + github.com/spf13/pflag v1.0.10 + k8s.io/api v0.34.1 + k8s.io/apiextensions-apiserver v0.34.1 + k8s.io/apimachinery v0.34.1 + k8s.io/client-go v0.34.1 + k8s.io/component-base v0.34.1 + k8s.io/kube-aggregator v0.34.1 + sigs.k8s.io/controller-runtime v0.22.3 +) + +require ( + github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 // indirect + github.com/beorn7/perks v1.0.1 // indirect + github.com/blang/semver/v4 v4.0.0 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/emicklei/go-restful/v3 v3.13.0 // indirect + github.com/evanphx/json-patch/v5 v5.9.11 // indirect + github.com/fsnotify/fsnotify v1.9.0 // indirect + github.com/fxamacker/cbor/v2 v2.9.0 // indirect + github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667 // indirect + github.com/go-ldap/ldap/v3 v3.4.12 // indirect + github.com/go-logr/logr v1.4.3 // indirect + github.com/go-logr/zapr v1.3.0 // indirect + github.com/go-openapi/jsonpointer v0.22.1 // indirect + github.com/go-openapi/jsonreference v0.21.2 // indirect + github.com/go-openapi/swag v0.23.1 // indirect + github.com/go-openapi/swag/jsonname v0.25.1 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/google/btree v1.1.3 // indirect + github.com/google/gnostic-models v0.7.0 // indirect + github.com/google/go-cmp v0.7.0 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/josharian/intern v1.0.0 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/mailru/easyjson v0.9.0 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + github.com/prometheus/client_golang v1.23.2 // indirect + github.com/prometheus/client_model v0.6.2 // indirect + github.com/prometheus/common v0.66.1 // indirect + github.com/prometheus/procfs v0.17.0 // indirect + github.com/x448/float16 v0.8.4 // indirect + go.opentelemetry.io/otel v1.37.0 // indirect + go.opentelemetry.io/otel/trace v1.37.0 // indirect + go.uber.org/multierr v1.11.0 // indirect + go.uber.org/zap v1.27.0 // indirect + go.yaml.in/yaml/v2 v2.4.2 // indirect + go.yaml.in/yaml/v3 v3.0.4 // indirect + golang.org/x/crypto v0.43.0 // indirect + golang.org/x/net v0.46.0 // indirect + golang.org/x/oauth2 v0.32.0 // indirect + golang.org/x/sync v0.17.0 // indirect + golang.org/x/sys v0.37.0 // indirect + golang.org/x/term v0.36.0 // indirect + golang.org/x/text v0.30.0 // indirect + golang.org/x/time v0.14.0 // indirect + gomodules.xyz/jsonpatch/v2 v2.5.0 // indirect + google.golang.org/protobuf v1.36.10 // indirect + gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect + gopkg.in/inf.v0 v0.9.1 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect + k8s.io/klog/v2 v2.130.1 // indirect + k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 // indirect + k8s.io/utils v0.0.0-20250820121507-0af2bda4dd1d // indirect + sigs.k8s.io/gateway-api v1.4.0 // indirect + sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect + sigs.k8s.io/randfill v1.0.0 // indirect + sigs.k8s.io/structured-merge-diff/v6 v6.3.0 // indirect + sigs.k8s.io/yaml v1.6.0 // indirect +) diff --git a/cmd/cainjector/go.sum b/cmd/cainjector/go.sum new file mode 100644 index 00000000000..4ae491ad407 --- /dev/null +++ b/cmd/cainjector/go.sum @@ -0,0 +1,233 @@ +github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 h1:mFRzDkZVAjdal+s7s0MwaRv9igoPqLRdzOLzw/8Xvq8= +github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU= +github.com/alexbrainman/sspi v0.0.0-20250919150558-7d374ff0d59e h1:4dAU9FXIyQktpoUAgOJK3OTFc/xug0PCXYCqU0FgDKI= +github.com/alexbrainman/sspi v0.0.0-20250919150558-7d374ff0d59e/go.mod h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4= +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= +github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/emicklei/go-restful/v3 v3.13.0 h1:C4Bl2xDndpU6nJ4bc1jXd+uTmYPVUwkD6bFY/oTyCes= +github.com/emicklei/go-restful/v3 v3.13.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/evanphx/json-patch v5.9.0+incompatible h1:fBXyNpNMuTTDdquAq/uisOr2lShz4oaXpDTX2bLe7ls= +github.com/evanphx/json-patch v5.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/evanphx/json-patch/v5 v5.9.11 h1:/8HVnzMq13/3x9TPvjG08wUGqBTmZBsCWzjTM0wiaDU= +github.com/evanphx/json-patch/v5 v5.9.11/go.mod h1:3j+LviiESTElxA4p3EMKAB9HXj3/XEtnUf6OZxqIQTM= +github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= +github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= +github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM= +github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= +github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667 h1:BP4M0CvQ4S3TGls2FvczZtj5Re/2ZzkV9VwqPHH/3Bo= +github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= +github.com/go-ldap/ldap/v3 v3.4.12 h1:1b81mv7MagXZ7+1r7cLTWmyuTqVqdwbtJSjC0DAp9s4= +github.com/go-ldap/ldap/v3 v3.4.12/go.mod h1:+SPAGcTtOfmGsCb3h1RFiq4xpp4N636G75OEace8lNo= +github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= +github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ= +github.com/go-logr/zapr v1.3.0/go.mod h1:YKepepNBd1u/oyhd/yQmtjVXmm9uML4IXUgMOwR8/Gg= +github.com/go-openapi/jsonpointer v0.22.1 h1:sHYI1He3b9NqJ4wXLoJDKmUmHkWy/L7rtEo92JUxBNk= +github.com/go-openapi/jsonpointer v0.22.1/go.mod h1:pQT9OsLkfz1yWoMgYFy4x3U5GY5nUlsOn1qSBH5MkCM= +github.com/go-openapi/jsonreference v0.21.2 h1:Wxjda4M/BBQllegefXrY/9aq1fxBA8sI5M/lFU6tSWU= +github.com/go-openapi/jsonreference v0.21.2/go.mod h1:pp3PEjIsJ9CZDGCNOyXIQxsNuroxm8FAJ/+quA0yKzQ= +github.com/go-openapi/swag v0.23.1 h1:lpsStH0n2ittzTnbaSloVZLuB5+fvSY/+hnagBjSNZU= +github.com/go-openapi/swag v0.23.1/go.mod h1:STZs8TbRvEQQKUA+JZNAm3EWlgaOBGpyFDqQnDHMef0= +github.com/go-openapi/swag/jsonname v0.25.1 h1:Sgx+qbwa4ej6AomWC6pEfXrA6uP2RkaNjA9BR8a1RJU= +github.com/go-openapi/swag/jsonname v0.25.1/go.mod h1:71Tekow6UOLBD3wS7XhdT98g5J5GR13NOTQ9/6Q11Zo= +github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= +github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg= +github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= +github.com/google/gnostic-models v0.7.0 h1:qwTtogB15McXDaNqTZdzPJRHvaVJlAl+HVQnLmJEJxo= +github.com/google/gnostic-models v0.7.0/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= +github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db h1:097atOisP2aRj7vFgYQBbFN4U4JNXUNYpxael3UzMyo= +github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= +github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/jcmturner/aescts/v2 v2.0.0 h1:9YKLH6ey7H4eDBXW8khjYslgyqG2xZikXP0EQFKrle8= +github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs= +github.com/jcmturner/dnsutils/v2 v2.0.0 h1:lltnkeZGL0wILNvrNiVCR6Ro5PGU/SeBvVO/8c/iPbo= +github.com/jcmturner/dnsutils/v2 v2.0.0/go.mod h1:b0TnjGOvI/n42bZa+hmXL+kFJZsFT7G4t3HTlQ184QM= +github.com/jcmturner/gofork v1.7.6 h1:QH0l3hzAU1tfT3rZCnW5zXl+orbkNMMRGJfdJjHVETg= +github.com/jcmturner/gofork v1.7.6/go.mod h1:1622LH6i/EZqLloHfE7IeZ0uEJwMSUyQ/nDd82IeqRo= +github.com/jcmturner/goidentity/v6 v6.0.1 h1:VKnZd2oEIMorCTsFBnJWbExfNN7yZr3EhJAxwOkZg6o= +github.com/jcmturner/goidentity/v6 v6.0.1/go.mod h1:X1YW3bgtvwAXju7V3LCIMpY0Gbxyjn/mY9zx4tFonSg= +github.com/jcmturner/gokrb5/v8 v8.4.4 h1:x1Sv4HaTpepFkXbt2IkL29DXRf8sOfZXo8eRKh687T8= +github.com/jcmturner/gokrb5/v8 v8.4.4/go.mod h1:1btQEpgT6k+unzCwX1KdWMEwPPkkgBtP+F6aCACiMrs= +github.com/jcmturner/rpc/v2 v2.0.3 h1:7FXXj8Ti1IaVFpSAziCZWNzbNuZmnvw/i6CqLNdWfZY= +github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= +github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4= +github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8= +github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/onsi/ginkgo/v2 v2.22.0 h1:Yed107/8DjTr0lKCNt7Dn8yQ6ybuDRQoMGrNFKzMfHg= +github.com/onsi/ginkgo/v2 v2.22.0/go.mod h1:7Du3c42kxCUegi0IImZ1wUQzMBVecgIHjR1C+NkhLQo= +github.com/onsi/gomega v1.36.1 h1:bJDPBO7ibjxcbHMgSCoo4Yj18UWbKDlLwX1x9sybDcw= +github.com/onsi/gomega v1.36.1/go.mod h1:PvZbdDc8J6XJEpDK4HCuRBm8a6Fzp9/DmhC9C7yFlog= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o= +github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg= +github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= +github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= +github.com/prometheus/common v0.66.1 h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9ZoGs= +github.com/prometheus/common v0.66.1/go.mod h1:gcaUsgf3KfRSwHY4dIMXLPV0K/Wg1oZ8+SbZk/HH/dA= +github.com/prometheus/procfs v0.17.0 h1:FuLQ+05u4ZI+SS/w9+BWEM2TXiHKsUQ9TADiRH7DuK0= +github.com/prometheus/procfs v0.17.0/go.mod h1:oPQLaDAMRbA+u8H5Pbfq+dl3VDAvHxMUOVhe0wYB2zw= +github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= +github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/spf13/cobra v1.10.1 h1:lJeBwCfmrnXthfAupyUTzJ/J4Nc1RsHC/mSRU2dll/s= +github.com/spf13/cobra v1.10.1/go.mod h1:7SmJGaTHFVBY0jW4NXGluQoLvhqFQM+6XSKD+P4XaB0= +github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= +github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= +github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ= +go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I= +go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4= +go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= +go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= +go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI= +go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU= +go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= +go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04= +golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.46.0 h1:giFlY12I07fugqwPuWJi68oOnpfqFnJIJzaIIm2JVV4= +golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210= +golang.org/x/oauth2 v0.32.0 h1:jsCblLleRMDrxMN29H3z/k1KliIvpLgCkE6R8FXXNgY= +golang.org/x/oauth2 v0.32.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug= +golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ= +golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/term v0.36.0 h1:zMPR+aF8gfksFprF/Nc/rd1wRS1EI6nDBGyWAvDzx2Q= +golang.org/x/term v0.36.0/go.mod h1:Qu394IJq6V6dCBRgwqshf3mPF85AqzYEzofzRdZkWss= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k= +golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM= +golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI= +golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.37.0 h1:DVSRzp7FwePZW356yEAChSdNcQo6Nsp+fex1SUW09lE= +golang.org/x/tools v0.37.0/go.mod h1:MBN5QPQtLMHVdvsbtarmTNukZDdgwdwlO5qGacAzF0w= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gomodules.xyz/jsonpatch/v2 v2.5.0 h1:JELs8RLM12qJGXU4u/TO3V25KW8GreMKl9pdkk14RM0= +gomodules.xyz/jsonpatch/v2 v2.5.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY= +google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= +google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/evanphx/json-patch.v4 v4.13.0 h1:czT3CmqEaQ1aanPc5SdlgQrrEIb8w/wwCvWWnfEbYzo= +gopkg.in/evanphx/json-patch.v4 v4.13.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= +gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= +gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +k8s.io/api v0.34.1 h1:jC+153630BMdlFukegoEL8E/yT7aLyQkIVuwhmwDgJM= +k8s.io/api v0.34.1/go.mod h1:SB80FxFtXn5/gwzCoN6QCtPD7Vbu5w2n1S0J5gFfTYk= +k8s.io/apiextensions-apiserver v0.34.1 h1:NNPBva8FNAPt1iSVwIE0FsdrVriRXMsaWFMqJbII2CI= +k8s.io/apiextensions-apiserver v0.34.1/go.mod h1:hP9Rld3zF5Ay2Of3BeEpLAToP+l4s5UlxiHfqRaRcMc= +k8s.io/apimachinery v0.34.1 h1:dTlxFls/eikpJxmAC7MVE8oOeP1zryV7iRyIjB0gky4= +k8s.io/apimachinery v0.34.1/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw= +k8s.io/client-go v0.34.1 h1:ZUPJKgXsnKwVwmKKdPfw4tB58+7/Ik3CrjOEhsiZ7mY= +k8s.io/client-go v0.34.1/go.mod h1:kA8v0FP+tk6sZA0yKLRG67LWjqufAoSHA2xVGKw9Of8= +k8s.io/component-base v0.34.1 h1:v7xFgG+ONhytZNFpIz5/kecwD+sUhVE6HU7qQUiRM4A= +k8s.io/component-base v0.34.1/go.mod h1:mknCpLlTSKHzAQJJnnHVKqjxR7gBeHRv0rPXA7gdtQ0= +k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= +k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= +k8s.io/kube-aggregator v0.34.1 h1:WNLV0dVNoFKmuyvdWLd92iDSyD/TSTjqwaPj0U9XAEU= +k8s.io/kube-aggregator v0.34.1/go.mod h1:RU8j+5ERfp0h+gIvWtxRPfsa5nK7rboDm8RST8BJfYQ= +k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 h1:Y3gxNAuB0OBLImH611+UDZcmKS3g6CthxToOb37KgwE= +k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912/go.mod h1:kdmbQkyfwUagLfXIad1y2TdrjPFWp2Q89B3qkRwf/pQ= +k8s.io/utils v0.0.0-20250820121507-0af2bda4dd1d h1:wAhiDyZ4Tdtt7e46e9M5ZSAJ/MnPGPs+Ki1gHw4w1R0= +k8s.io/utils v0.0.0-20250820121507-0af2bda4dd1d/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +sigs.k8s.io/controller-runtime v0.22.3 h1:I7mfqz/a/WdmDCEnXmSPm8/b/yRTy6JsKKENTijTq8Y= +sigs.k8s.io/controller-runtime v0.22.3/go.mod h1:+QX1XUpTXN4mLoblf4tqr5CQcyHPAki2HLXqQMY6vh8= +sigs.k8s.io/gateway-api v1.4.0 h1:ZwlNM6zOHq0h3WUX2gfByPs2yAEsy/EenYJB78jpQfQ= +sigs.k8s.io/gateway-api v1.4.0/go.mod h1:AR5RSqciWP98OPckEjOjh2XJhAe2Na4LHyXD2FUY7Qk= +sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg= +sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= +sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= +sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= +sigs.k8s.io/structured-merge-diff/v6 v6.3.0 h1:jTijUJbW353oVOd9oTlifJqOGEkUw2jB/fXCbTiQEco= +sigs.k8s.io/structured-merge-diff/v6 v6.3.0/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE= +sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs= +sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4= diff --git a/cmd/cainjector/main.go b/cmd/cainjector/main.go index 887562cba49..716066b13ec 100644 --- a/cmd/cainjector/main.go +++ b/cmd/cainjector/main.go @@ -18,35 +18,29 @@ package main import ( "context" - "flag" - - "os" ctrl "sigs.k8s.io/controller-runtime" - "github.com/cert-manager/cert-manager/cmd/cainjector/app" - "github.com/cert-manager/cert-manager/cmd/util" + "github.com/cert-manager/cert-manager/cainjector-binary/app" + "github.com/cert-manager/cert-manager/internal/cmd/util" logf "github.com/cert-manager/cert-manager/pkg/logs" ) func main() { // Set up signal handlers and a cancellable context which gets cancelled on // when either SIGINT or SIGTERM are received. - stopCh, exit := util.SetupExitHandler(util.GracefulShutdown) + ctx, exit := util.SetupExitHandler(context.Background(), util.GracefulShutdown) defer exit() // This function might call os.Exit, so defer last - logf.InitLogs(flag.CommandLine) + logf.InitLogs() defer logf.FlushLogs() ctrl.SetLogger(logf.Log) + ctx = logf.NewContext(ctx, logf.Log) - ctx := util.ContextWithStopCh(context.Background(), stopCh) - - cmd := app.NewCommandStartInjectorController(ctx, os.Stdout, os.Stderr) - cmd.Flags().AddGoFlagSet(flag.CommandLine) + cmd := app.NewCAInjectorCommand(ctx) - flag.CommandLine.Parse([]string{}) - if err := cmd.Execute(); err != nil { - cmd.PrintErrln(err) + if err := cmd.ExecuteContext(ctx); err != nil { + logf.Log.Error(err, "error executing command") util.SetExitCode(err) } } diff --git a/cmd/controller/LICENSE b/cmd/controller/LICENSE new file mode 100644 index 00000000000..d6456956733 --- /dev/null +++ b/cmd/controller/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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. diff --git a/cmd/controller/LICENSES b/cmd/controller/LICENSES new file mode 100644 index 00000000000..1c7aaa7fc40 --- /dev/null +++ b/cmd/controller/LICENSES @@ -0,0 +1,219 @@ +This LICENSES file is generated by the `licenses` module in makefile-modules[0]. + +The licenses below the "---" are determined by the go-licenses tool[1]. + +The aim of this file is to collect the licenses of all dependencies, and provide +a single source of truth for licenses used by this project. + +## For Developers + +If CI reports that this file is out of date, you should be careful to check that the +new licenses are acceptable for this project before running `make generate-go-licenses` +to update this file. + +Acceptable licenses are those allowlisted by the CNCF[2]. + +You MUST NOT add any new dependencies whose licenses are not allowlisted by the CNCF, +or which do not have an explicit license exception[3]. + +## For Users + +If this file was included in a release artifact, it is a snapshot of the licenses of all dependencies at the time of the release. + +You can retrieve the actual license text by following these steps: + +1. Find the dependency name in this file +2. Go to the source code repository of this project, and go to the tag corresponding to this release. +3. Find the exact version of the dependency in the `go.mod` file +4. Search for the dependency at the correct version in the [Go package index](https://pkg.go.dev/). + +## Links + +[0]: https://github.com/cert-manager/makefile-modules/ +[1]: https://github.com/google/go-licenses +[2]: https://github.com/cncf/foundation/blob/db4179134ebe7fa00b140a050c19147db808b6fa/policies-guidance/allowed-third-party-license-policy.md#cncf-allowlist-license-policy +[3]: https://github.com/cncf/foundation/blob/db4179134ebe7fa00b140a050c19147db808b6fa/license-exceptions/README.md + +--- + +cloud.google.com/go/auth,Apache-2.0 +cloud.google.com/go/auth/oauth2adapt,Apache-2.0 +cloud.google.com/go/compute/metadata,Apache-2.0 +github.com/Azure/azure-sdk-for-go/sdk/azcore,MIT +github.com/Azure/azure-sdk-for-go/sdk/azidentity,MIT +github.com/Azure/azure-sdk-for-go/sdk/internal,MIT +github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/dns/armdns,MIT +github.com/Azure/go-ntlmssp,MIT +github.com/AzureAD/microsoft-authentication-library-for-go/apps,MIT +github.com/Khan/genqlient/graphql,MIT +github.com/Venafi/vcert/v5,Apache-2.0 +github.com/akamai/AkamaiOPEN-edgegrid-golang/v12/pkg,Apache-2.0 +github.com/aws/aws-sdk-go-v2,Apache-2.0 +github.com/aws/aws-sdk-go-v2/config,Apache-2.0 +github.com/aws/aws-sdk-go-v2/credentials,Apache-2.0 +github.com/aws/aws-sdk-go-v2/feature/ec2/imds,Apache-2.0 +github.com/aws/aws-sdk-go-v2/internal/configsources,Apache-2.0 +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2,Apache-2.0 +github.com/aws/aws-sdk-go-v2/internal/ini,Apache-2.0 +github.com/aws/aws-sdk-go-v2/internal/sync/singleflight,BSD-3-Clause +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding,Apache-2.0 +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url,Apache-2.0 +github.com/aws/aws-sdk-go-v2/service/route53,Apache-2.0 +github.com/aws/aws-sdk-go-v2/service/sso,Apache-2.0 +github.com/aws/aws-sdk-go-v2/service/ssooidc,Apache-2.0 +github.com/aws/aws-sdk-go-v2/service/sts,Apache-2.0 +github.com/aws/smithy-go,Apache-2.0 +github.com/aws/smithy-go/internal/sync/singleflight,BSD-3-Clause +github.com/benbjohnson/clock,MIT +github.com/beorn7/perks/quantile,MIT +github.com/blang/semver/v4,MIT +github.com/cenkalti/backoff/v4,MIT +github.com/cenkalti/backoff/v5,MIT +github.com/cert-manager/cert-manager,Apache-2.0 +github.com/cert-manager/cert-manager/controller-binary,Apache-2.0 +github.com/cert-manager/cert-manager/pkg/issuer/acme/dns/azuredns,MIT +github.com/cert-manager/cert-manager/pkg/issuer/acme/dns/clouddns,MIT +github.com/cert-manager/cert-manager/pkg/issuer/acme/dns/cloudflare,MIT +github.com/cert-manager/cert-manager/pkg/issuer/acme/dns/route53,MIT +github.com/cert-manager/cert-manager/pkg/issuer/acme/dns/util,MIT +github.com/cert-manager/cert-manager/third_party/forked/acme,BSD-3-Clause +github.com/cespare/xxhash/v2,MIT +github.com/coreos/go-semver/semver,Apache-2.0 +github.com/coreos/go-systemd/v22/journal,Apache-2.0 +github.com/davecgh/go-spew/spew,ISC +github.com/digitalocean/godo,MIT +github.com/digitalocean/godo,BSD-3-Clause +github.com/emicklei/go-restful/v3,MIT +github.com/felixge/httpsnoop,MIT +github.com/fxamacker/cbor/v2,MIT +github.com/go-asn1-ber/asn1-ber,MIT +github.com/go-http-utils/headers,MIT +github.com/go-jose/go-jose/v4,Apache-2.0 +github.com/go-jose/go-jose/v4/json,BSD-3-Clause +github.com/go-ldap/ldap/v3,MIT +github.com/go-logr/logr,Apache-2.0 +github.com/go-logr/stdr,Apache-2.0 +github.com/go-logr/zapr,Apache-2.0 +github.com/go-openapi/jsonpointer,Apache-2.0 +github.com/go-openapi/jsonreference,Apache-2.0 +github.com/go-openapi/swag,Apache-2.0 +github.com/go-openapi/swag/jsonname,Apache-2.0 +github.com/go-ozzo/ozzo-validation/v4,MIT +github.com/gogo/protobuf,BSD-3-Clause +github.com/golang-jwt/jwt/v5,MIT +github.com/golang/protobuf/proto,BSD-3-Clause +github.com/golang/snappy,BSD-3-Clause +github.com/google/btree,Apache-2.0 +github.com/google/certificate-transparency-go,Apache-2.0 +github.com/google/gnostic-models,Apache-2.0 +github.com/google/go-cmp/cmp,BSD-3-Clause +github.com/google/go-querystring/query,BSD-3-Clause +github.com/google/s2a-go,Apache-2.0 +github.com/google/uuid,BSD-3-Clause +github.com/googleapis/enterprise-certificate-proxy/client,Apache-2.0 +github.com/googleapis/gax-go/v2,BSD-3-Clause +github.com/gorilla/websocket,BSD-2-Clause +github.com/grpc-ecosystem/go-grpc-prometheus,Apache-2.0 +github.com/grpc-ecosystem/grpc-gateway/v2,BSD-3-Clause +github.com/hashicorp/errwrap,MPL-2.0 +github.com/hashicorp/go-cleanhttp,MPL-2.0 +github.com/hashicorp/go-hmac-drbg/hmacdrbg,MIT +github.com/hashicorp/go-multierror,MPL-2.0 +github.com/hashicorp/go-retryablehttp,MPL-2.0 +github.com/hashicorp/go-rootcerts,MPL-2.0 +github.com/hashicorp/go-secure-stdlib/cryptoutil,MPL-2.0 +github.com/hashicorp/go-secure-stdlib/parseutil,MPL-2.0 +github.com/hashicorp/go-secure-stdlib/strutil,MPL-2.0 +github.com/hashicorp/go-sockaddr,MPL-2.0 +github.com/hashicorp/hcl,MPL-2.0 +github.com/hashicorp/vault/api,MPL-2.0 +github.com/hashicorp/vault/sdk/helper,MPL-2.0 +github.com/josharian/intern,MIT +github.com/json-iterator/go,MIT +github.com/kylelemons/godebug,Apache-2.0 +github.com/mailru/easyjson,MIT +github.com/miekg/dns,BSD-3-Clause +github.com/mitchellh/go-homedir,MIT +github.com/mitchellh/mapstructure,MIT +github.com/modern-go/concurrent,Apache-2.0 +github.com/modern-go/reflect2,Apache-2.0 +github.com/munnerz/goautoneg,BSD-3-Clause +github.com/nrdcg/goacmedns,MIT +github.com/pavlo-v-chernykh/keystore-go/v4,MIT +github.com/pierrec/lz4,BSD-3-Clause +github.com/pkg/browser,BSD-2-Clause +github.com/pmezard/go-difflib/difflib,BSD-3-Clause +github.com/prometheus/client_golang/internal/github.com/golang/gddo/httputil,BSD-3-Clause +github.com/prometheus/client_golang/prometheus,Apache-2.0 +github.com/prometheus/client_model/go,Apache-2.0 +github.com/prometheus/common,Apache-2.0 +github.com/prometheus/procfs,Apache-2.0 +github.com/ryanuber/go-glob,MIT +github.com/sosodev/duration,MIT +github.com/spf13/cobra,Apache-2.0 +github.com/spf13/pflag,BSD-3-Clause +github.com/stretchr/objx,MIT +github.com/stretchr/testify,MIT +github.com/vektah/gqlparser/v2,MIT +github.com/x448/float16,MIT +github.com/youmark/pkcs8,MIT +go.etcd.io/etcd/api/v3,Apache-2.0 +go.etcd.io/etcd/client/pkg/v3,Apache-2.0 +go.etcd.io/etcd/client/v3,Apache-2.0 +go.opentelemetry.io/auto/sdk,Apache-2.0 +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc,Apache-2.0 +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp,Apache-2.0 +go.opentelemetry.io/otel,Apache-2.0 +go.opentelemetry.io/otel/exporters/otlp/otlptrace,Apache-2.0 +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc,Apache-2.0 +go.opentelemetry.io/otel/metric,Apache-2.0 +go.opentelemetry.io/otel/sdk,Apache-2.0 +go.opentelemetry.io/otel/trace,Apache-2.0 +go.opentelemetry.io/proto/otlp,Apache-2.0 +go.uber.org/multierr,MIT +go.uber.org/ratelimit,MIT +go.uber.org/zap,MIT +go.yaml.in/yaml/v2,Apache-2.0 +go.yaml.in/yaml/v3,MIT +golang.org/x/crypto,BSD-3-Clause +golang.org/x/net,BSD-3-Clause +golang.org/x/oauth2,BSD-3-Clause +golang.org/x/sync/errgroup,BSD-3-Clause +golang.org/x/sys,BSD-3-Clause +golang.org/x/term,BSD-3-Clause +golang.org/x/text,BSD-3-Clause +golang.org/x/time/rate,BSD-3-Clause +google.golang.org/api,BSD-3-Clause +google.golang.org/api/internal/third_party/uritemplates,BSD-3-Clause +google.golang.org/genproto/googleapis/api,Apache-2.0 +google.golang.org/genproto/googleapis/rpc,Apache-2.0 +google.golang.org/grpc,Apache-2.0 +google.golang.org/protobuf,BSD-3-Clause +gopkg.in/evanphx/json-patch.v4,BSD-3-Clause +gopkg.in/inf.v0,BSD-3-Clause +gopkg.in/ini.v1,Apache-2.0 +gopkg.in/yaml.v2,Apache-2.0 +gopkg.in/yaml.v3,MIT +k8s.io/api,Apache-2.0 +k8s.io/apiextensions-apiserver/pkg/apis/apiextensions,Apache-2.0 +k8s.io/apimachinery/pkg,Apache-2.0 +k8s.io/apimachinery/third_party/forked/golang,BSD-3-Clause +k8s.io/apiserver/pkg,Apache-2.0 +k8s.io/client-go,Apache-2.0 +k8s.io/component-base,Apache-2.0 +k8s.io/klog/v2,Apache-2.0 +k8s.io/kube-openapi/pkg,Apache-2.0 +k8s.io/kube-openapi/pkg/internal/third_party/go-json-experiment/json,BSD-3-Clause +k8s.io/kube-openapi/pkg/validation/spec,Apache-2.0 +k8s.io/utils,Apache-2.0 +k8s.io/utils/internal/third_party/forked/golang,BSD-3-Clause +sigs.k8s.io/apiserver-network-proxy/konnectivity-client,Apache-2.0 +sigs.k8s.io/gateway-api,Apache-2.0 +sigs.k8s.io/json,Apache-2.0 +sigs.k8s.io/json,BSD-3-Clause +sigs.k8s.io/randfill,Apache-2.0 +sigs.k8s.io/structured-merge-diff/v6,Apache-2.0 +sigs.k8s.io/yaml,MIT +sigs.k8s.io/yaml,Apache-2.0 +sigs.k8s.io/yaml,BSD-3-Clause +software.sslmate.com/src/go-pkcs12,BSD-3-Clause diff --git a/cmd/controller/app/controller.go b/cmd/controller/app/controller.go index ad2b683d361..861878fe1c8 100644 --- a/cmd/controller/app/controller.go +++ b/cmd/controller/app/controller.go @@ -25,35 +25,54 @@ import ( "os" "time" + "github.com/go-logr/logr" "golang.org/x/sync/errgroup" "k8s.io/apimachinery/pkg/api/resource" utilerrors "k8s.io/apimachinery/pkg/util/errors" + "k8s.io/apimachinery/pkg/util/sets" "k8s.io/client-go/kubernetes" + "k8s.io/client-go/rest" "k8s.io/client-go/tools/leaderelection" "k8s.io/client-go/tools/leaderelection/resourcelock" "k8s.io/client-go/tools/record" - "k8s.io/utils/clock" - "github.com/cert-manager/cert-manager/cmd/controller/app/options" - cmdutil "github.com/cert-manager/cert-manager/cmd/util" + "github.com/cert-manager/cert-manager/controller-binary/app/options" + config "github.com/cert-manager/cert-manager/internal/apis/config/controller" + "github.com/cert-manager/cert-manager/internal/apis/config/shared" "github.com/cert-manager/cert-manager/internal/controller/feature" - "github.com/cert-manager/cert-manager/pkg/acme/accounts" "github.com/cert-manager/cert-manager/pkg/controller" - "github.com/cert-manager/cert-manager/pkg/controller/clusterissuers" + "github.com/cert-manager/cert-manager/pkg/healthz" dnsutil "github.com/cert-manager/cert-manager/pkg/issuer/acme/dns/util" logf "github.com/cert-manager/cert-manager/pkg/logs" - "github.com/cert-manager/cert-manager/pkg/metrics" + "github.com/cert-manager/cert-manager/pkg/server" + "github.com/cert-manager/cert-manager/pkg/server/tls" + "github.com/cert-manager/cert-manager/pkg/server/tls/authority" + "github.com/cert-manager/cert-manager/pkg/util" utilfeature "github.com/cert-manager/cert-manager/pkg/util/feature" "github.com/cert-manager/cert-manager/pkg/util/profiling" ) -func Run(opts *options.ControllerOptions, stopCh <-chan struct{}) error { - rootCtx, cancelContext := context.WithCancel(cmdutil.ContextWithStopCh(context.Background(), stopCh)) +const ( + // This is intended to mitigate "slowloris" attacks by limiting the time a + // deliberately slow client can spend sending HTTP headers. + // This default value is copied from: + // * kubernetes api-server: + // https://github.com/kubernetes/kubernetes/blob/9e028b40b9e970142191259effe796b3dab39828/staging/src/k8s.io/apiserver/pkg/server/secure_serving.go#L165-L173 + // * controller-runtime: + // https://github.com/kubernetes-sigs/controller-runtime/blob/1ea2be573f7887a9fbd766e9a921c5af344da6eb/pkg/internal/httpserver/server.go#L14 + defaultReadHeaderTimeout = 32 * time.Second +) + +func Run(rootCtx context.Context, opts *config.ControllerConfiguration) error { + rootCtx, cancelContext := context.WithCancel(rootCtx) defer cancelContext() - rootCtx = logf.NewContext(rootCtx, logf.Log, "controller") + log := logf.FromContext(rootCtx) g, rootCtx := errgroup.WithContext(rootCtx) + versionInfo := util.VersionInfo() + log.Info("starting cert-manager controller", "version", versionInfo.GitVersion, "git_commit", versionInfo.GitCommit, "go_version", versionInfo.GoVersion, "platform", versionInfo.Platform) + ctxFactory, err := buildControllerContextFactory(rootCtx, opts) if err != nil { return err @@ -66,26 +85,47 @@ func Run(opts *options.ControllerOptions, stopCh <-chan struct{}) error { return err } - enabledControllers := opts.EnabledControllers() - log.Info(fmt.Sprintf("enabled controllers: %s", enabledControllers.List())) + enabledControllers := options.EnabledControllers(opts) + log.Info(fmt.Sprintf("enabled controllers: %s", sets.List(enabledControllers))) + + // start the CertificateSource if provided + certificateSource := buildCertificateSource(log, opts.MetricsTLSConfig, ctx.RESTConfig) + if certificateSource != nil { + log.V(logf.InfoLevel).Info("listening for secure connections", "address", opts.MetricsListenAddress) + g.Go(func() error { + if err := certificateSource.Start(rootCtx); (err != nil) && !errors.Is(err, context.Canceled) { + return err + } + return nil + }) + } else { + log.V(logf.InfoLevel).Info("listening for insecure connections", "address", opts.MetricsListenAddress) + } // Start metrics server - metricsLn, err := net.Listen("tcp", opts.MetricsListenAddress) + metricsLn, err := server.Listen(rootCtx, "tcp", opts.MetricsListenAddress, + server.WithCertificateSource(certificateSource), + server.WithTLSCipherSuites(opts.MetricsTLSConfig.CipherSuites), + server.WithTLSMinVersion(opts.MetricsTLSConfig.MinTLSVersion), + ) if err != nil { return fmt.Errorf("failed to listen on prometheus address %s: %v", opts.MetricsListenAddress, err) } + + ctx.Metrics.SetupACMECollector(ctx.SharedInformerFactory.Acme().V1().Challenges().Lister()) + ctx.Metrics.SetupCertificateCollector(ctx.SharedInformerFactory.Certmanager().V1().Certificates().Lister()) + ctx.Metrics.SetupIssuerCollector(ctx.SharedInformerFactory.Certmanager().V1().Issuers().Lister()) + ctx.Metrics.SetupClusterIssuerCollector(ctx.SharedInformerFactory.Certmanager().V1().ClusterIssuers().Lister()) metricsServer := ctx.Metrics.NewServer(metricsLn) g.Go(func() error { <-rootCtx.Done() // allow a timeout for graceful shutdown - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + shutdownCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() - if err := metricsServer.Shutdown(ctx); err != nil { - return err - } - return nil + // nolint: contextcheck + return metricsServer.Shutdown(shutdownCtx) }) g.Go(func() error { log.V(logf.InfoLevel).Info("starting metrics server", "address", metricsLn.Addr()) @@ -97,7 +137,8 @@ func Run(opts *options.ControllerOptions, stopCh <-chan struct{}) error { // Start profiler if it is enabled if opts.EnablePprof { - profilerLn, err := net.Listen("tcp", opts.PprofAddress) + lc := net.ListenConfig{} + profilerLn, err := lc.Listen(rootCtx, "tcp", opts.PprofAddress) if err != nil { return fmt.Errorf("failed to listen on profiler address %s: %v", opts.PprofAddress, err) } @@ -105,19 +146,18 @@ func Run(opts *options.ControllerOptions, stopCh <-chan struct{}) error { // Add pprof endpoints to this mux profiling.Install(profilerMux) profilerServer := &http.Server{ - Handler: profilerMux, + Handler: profilerMux, + ReadHeaderTimeout: defaultReadHeaderTimeout, // Mitigation for G112: Potential slowloris attack } g.Go(func() error { <-rootCtx.Done() // allow a timeout for graceful shutdown - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + shutdownCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() - if err := profilerServer.Shutdown(ctx); err != nil { - return err - } - return nil + // nolint: contextcheck + return profilerServer.Shutdown(shutdownCtx) }) g.Go(func() error { log.V(logf.InfoLevel).Info("starting profiler", "address", profilerLn.Addr()) @@ -127,16 +167,25 @@ func Run(opts *options.ControllerOptions, stopCh <-chan struct{}) error { return nil }) } + lc := net.ListenConfig{} + healthzListener, err := lc.Listen(rootCtx, "tcp", opts.HealthzListenAddress) + if err != nil { + return fmt.Errorf("failed to listen on healthz address %s: %v", opts.HealthzListenAddress, err) + } + healthzServer := healthz.NewServer(opts.LeaderElectionConfig.HealthzTimeout) + g.Go(func() error { + log.V(logf.InfoLevel).Info("starting healthz server", "address", healthzListener.Addr()) + return healthzServer.Start(rootCtx, healthzListener) + }) elected := make(chan struct{}) - if opts.LeaderElect { + if opts.LeaderElectionConfig.Enabled { g.Go(func() error { log.V(logf.InfoLevel).Info("starting leader election") ctx, err := ctxFactory.Build("leader-election") if err != nil { return err } - errorCh := make(chan error, 1) if err := startLeaderElection(rootCtx, opts, ctx.Client, ctx.Recorder, leaderelection.LeaderCallbacks{ OnStartedLeading: func(_ context.Context) { @@ -151,7 +200,7 @@ func Run(opts *options.ControllerOptions, stopCh <-chan struct{}) error { errorCh <- errors.New("leader election lost") } }, - }); err != nil { + }, healthzServer.LeaderHealthzAdaptor); err != nil { return err } @@ -179,13 +228,7 @@ func Run(opts *options.ControllerOptions, stopCh <-chan struct{}) error { // only run a controller if it's been enabled if !enabledControllers.Has(n) { - log.V(logf.InfoLevel).Info("not starting controller as it's disabled") - continue - } - - // don't run clusterissuers controller if scoped to a single namespace - if ctx.Namespace != "" && n == clusterissuers.ControllerName { - log.V(logf.InfoLevel).Info("not starting controller as cert-manager has been scoped to a single namespace") + log.V(logf.InfoLevel).Info("skipping disabled controller") continue } @@ -204,17 +247,16 @@ func Run(opts *options.ControllerOptions, stopCh <-chan struct{}) error { g.Go(func() error { log.V(logf.InfoLevel).Info("starting controller") - // TODO: make this either a constant or a command line flag - workers := 5 - return iface.Run(workers, rootCtx.Done()) + return iface.Run(opts.NumberOfConcurrentWorkers, rootCtx) }) } log.V(logf.DebugLevel).Info("starting shared informer factories") ctx.SharedInformerFactory.Start(rootCtx.Done()) ctx.KubeSharedInformerFactory.Start(rootCtx.Done()) + ctx.HTTP01ResourceMetadataInformersFactory.Start(rootCtx.Done()) - if utilfeature.DefaultFeatureGate.Enabled(feature.ExperimentalGatewayAPISupport) { + if utilfeature.DefaultFeatureGate.Enabled(feature.ExperimentalGatewayAPISupport) && opts.EnableGatewayAPI { ctx.GWShared.Start(rootCtx.Done()) } @@ -224,15 +266,23 @@ func Run(opts *options.ControllerOptions, stopCh <-chan struct{}) error { } log.V(logf.InfoLevel).Info("control loops exited") + if utilfeature.DefaultFeatureGate.Enabled(feature.ExperimentalGatewayAPISupport) && opts.EnableGatewayAPI { + ctx.GWShared.Shutdown() + } + + ctx.HTTP01ResourceMetadataInformersFactory.Shutdown() + ctx.KubeSharedInformerFactory.Shutdown() + ctx.SharedInformerFactory.Shutdown() + return nil } // buildControllerContextFactory builds a new controller ContextFactory which // can build controller contexts for each component. -func buildControllerContextFactory(ctx context.Context, opts *options.ControllerOptions) (*controller.ContextFactory, error) { +func buildControllerContextFactory(ctx context.Context, opts *config.ControllerConfiguration) (*controller.ContextFactory, error) { log := logf.FromContext(ctx) - nameservers := opts.DNS01RecursiveNameservers + nameservers := opts.ACMEDNS01Config.RecursiveNameservers if len(nameservers) == 0 { nameservers = dnsutil.RecursiveNameservers } @@ -241,53 +291,49 @@ func buildControllerContextFactory(ctx context.Context, opts *options.Controller WithValues("nameservers", nameservers). Info("configured acme dns01 nameservers") - http01SolverResourceRequestCPU, err := resource.ParseQuantity(opts.ACMEHTTP01SolverResourceRequestCPU) + http01SolverResourceRequestCPU, err := resource.ParseQuantity(opts.ACMEHTTP01Config.SolverResourceRequestCPU) if err != nil { return nil, fmt.Errorf("error parsing ACMEHTTP01SolverResourceRequestCPU: %w", err) } - http01SolverResourceRequestMemory, err := resource.ParseQuantity(opts.ACMEHTTP01SolverResourceRequestMemory) + http01SolverResourceRequestMemory, err := resource.ParseQuantity(opts.ACMEHTTP01Config.SolverResourceRequestMemory) if err != nil { return nil, fmt.Errorf("error parsing ACMEHTTP01SolverResourceRequestMemory: %w", err) } - http01SolverResourceLimitsCPU, err := resource.ParseQuantity(opts.ACMEHTTP01SolverResourceLimitsCPU) + http01SolverResourceLimitsCPU, err := resource.ParseQuantity(opts.ACMEHTTP01Config.SolverResourceLimitsCPU) if err != nil { return nil, fmt.Errorf("error parsing ACMEHTTP01SolverResourceLimitsCPU: %w", err) } - http01SolverResourceLimitsMemory, err := resource.ParseQuantity(opts.ACMEHTTP01SolverResourceLimitsMemory) + http01SolverResourceLimitsMemory, err := resource.ParseQuantity(opts.ACMEHTTP01Config.SolverResourceLimitsMemory) if err != nil { return nil, fmt.Errorf("error parsing ACMEHTTP01SolverResourceLimitsMemory: %w", err) } - acmeAccountRegistry := accounts.NewDefaultRegistry() + ACMEHTTP01SolverRunAsNonRoot := opts.ACMEHTTP01Config.SolverRunAsNonRoot ctxFactory, err := controller.NewContextFactory(ctx, controller.ContextOptions{ - Kubeconfig: opts.Kubeconfig, + Kubeconfig: opts.KubeConfig, KubernetesAPIQPS: opts.KubernetesAPIQPS, KubernetesAPIBurst: opts.KubernetesAPIBurst, APIServerHost: opts.APIServerHost, Namespace: opts.Namespace, - Clock: clock.RealClock{}, - Metrics: metrics.New(log, clock.RealClock{}), - ACMEOptions: controller.ACMEOptions{ HTTP01SolverResourceRequestCPU: http01SolverResourceRequestCPU, HTTP01SolverResourceRequestMemory: http01SolverResourceRequestMemory, HTTP01SolverResourceLimitsCPU: http01SolverResourceLimitsCPU, HTTP01SolverResourceLimitsMemory: http01SolverResourceLimitsMemory, - HTTP01SolverImage: opts.ACMEHTTP01SolverImage, + ACMEHTTP01SolverRunAsNonRoot: ACMEHTTP01SolverRunAsNonRoot, + HTTP01SolverImage: opts.ACMEHTTP01Config.SolverImage, // Allows specifying a list of custom nameservers to perform HTTP01 checks on. - HTTP01SolverNameservers: opts.ACMEHTTP01SolverNameservers, + HTTP01SolverNameservers: opts.ACMEHTTP01Config.SolverNameservers, DNS01Nameservers: nameservers, - DNS01CheckRetryPeriod: opts.DNS01CheckRetryPeriod, - DNS01CheckAuthoritative: !opts.DNS01RecursiveNameserversOnly, - - AccountRegistry: acmeAccountRegistry, + DNS01CheckRetryPeriod: opts.ACMEDNS01Config.CheckRetryPeriod, + DNS01CheckAuthoritative: !opts.ACMEDNS01Config.RecursiveNameserversOnly, }, SchedulerOptions: controller.SchedulerOptions{ @@ -301,16 +347,21 @@ func buildControllerContextFactory(ctx context.Context, opts *options.Controller }, IngressShimOptions: controller.IngressShimOptions{ - DefaultIssuerName: opts.DefaultIssuerName, - DefaultIssuerKind: opts.DefaultIssuerKind, - DefaultIssuerGroup: opts.DefaultIssuerGroup, - DefaultAutoCertificateAnnotations: opts.DefaultAutoCertificateAnnotations, + DefaultIssuerName: opts.IngressShimConfig.DefaultIssuerName, + DefaultIssuerKind: opts.IngressShimConfig.DefaultIssuerKind, + DefaultIssuerGroup: opts.IngressShimConfig.DefaultIssuerGroup, + DefaultAutoCertificateAnnotations: opts.IngressShimConfig.DefaultAutoCertificateAnnotations, + ExtraCertificateAnnotations: opts.IngressShimConfig.ExtraCertificateAnnotations, }, CertificateOptions: controller.CertificateOptions{ EnableOwnerRef: opts.EnableCertificateOwnerRef, CopiedAnnotationPrefixes: opts.CopiedAnnotationPrefixes, }, + + ConfigOptions: controller.ConfigOptions{ + EnableGatewayAPI: opts.EnableGatewayAPI, + }, }) if err != nil { return nil, err @@ -319,7 +370,7 @@ func buildControllerContextFactory(ctx context.Context, opts *options.Controller return ctxFactory, nil } -func startLeaderElection(ctx context.Context, opts *options.ControllerOptions, leaderElectionClient kubernetes.Interface, recorder record.EventRecorder, callbacks leaderelection.LeaderCallbacks) error { +func startLeaderElection(ctx context.Context, opts *config.ControllerConfiguration, leaderElectionClient kubernetes.Interface, recorder record.EventRecorder, callbacks leaderelection.LeaderCallbacks, healthzAdaptor *leaderelection.HealthzAdaptor) error { // Identity used to distinguish between multiple controller manager instances id, err := os.Hostname() if err != nil { @@ -335,7 +386,7 @@ func startLeaderElection(ctx context.Context, opts *options.ControllerOptions, l // We only support leases for leader election. Previously we supported ConfigMap & Lease objects for leader // election. ml, err := resourcelock.New(resourcelock.LeasesResourceLock, - opts.LeaderElectionNamespace, + opts.LeaderElectionConfig.Namespace, lockName, leaderElectionClient.CoreV1(), leaderElectionClient.CoordinationV1(), @@ -348,11 +399,12 @@ func startLeaderElection(ctx context.Context, opts *options.ControllerOptions, l // Try and become the leader and start controller manager loops le, err := leaderelection.NewLeaderElector(leaderelection.LeaderElectionConfig{ Lock: ml, - LeaseDuration: opts.LeaderElectionLeaseDuration, - RenewDeadline: opts.LeaderElectionRenewDeadline, - RetryPeriod: opts.LeaderElectionRetryPeriod, + LeaseDuration: opts.LeaderElectionConfig.LeaseDuration, + RenewDeadline: opts.LeaderElectionConfig.RenewDeadline, + RetryPeriod: opts.LeaderElectionConfig.RetryPeriod, ReleaseOnCancel: true, Callbacks: callbacks, + WatchDog: healthzAdaptor, }) if err != nil { return err @@ -362,3 +414,29 @@ func startLeaderElection(ctx context.Context, opts *options.ControllerOptions, l return nil } + +func buildCertificateSource(log logr.Logger, tlsConfig shared.TLSConfig, restCfg *rest.Config) tls.CertificateSource { + switch { + case tlsConfig.FilesystemConfigProvided(): + log.V(logf.InfoLevel).Info("using TLS certificate from local filesystem", "private_key_path", tlsConfig.Filesystem.KeyFile, "certificate", tlsConfig.Filesystem.CertFile) + return &tls.FileCertificateSource{ + CertPath: tlsConfig.Filesystem.CertFile, + KeyPath: tlsConfig.Filesystem.KeyFile, + } + case tlsConfig.DynamicConfigProvided(): + log.V(logf.InfoLevel).Info("using dynamic certificate generating using CA stored in Secret resource", "secret_namespace", tlsConfig.Dynamic.SecretNamespace, "secret_name", tlsConfig.Dynamic.SecretName) + return &tls.DynamicSource{ + DNSNames: tlsConfig.Dynamic.DNSNames, + Authority: &authority.DynamicAuthority{ + SecretNamespace: tlsConfig.Dynamic.SecretNamespace, + SecretName: tlsConfig.Dynamic.SecretName, + SecretLabels: map[string]string{"app.kubernetes.io/managed-by": "cert-manager"}, + LeafDuration: tlsConfig.Dynamic.LeafDuration, + RESTConfig: restCfg, + }, + } + default: + log.V(logf.WarnLevel).Info("serving insecurely as tls certificate data not provided") + } + return nil +} diff --git a/cmd/controller/app/options/options.go b/cmd/controller/app/options/options.go index d0a2459f933..072bae31482 100644 --- a/cmd/controller/app/options/options.go +++ b/cmd/controller/app/options/options.go @@ -17,409 +17,228 @@ limitations under the License. package options import ( - "errors" "fmt" - "net" "strings" - "time" "github.com/spf13/pflag" + utilruntime "k8s.io/apimachinery/pkg/util/runtime" "k8s.io/apimachinery/pkg/util/sets" + cliflag "k8s.io/component-base/cli/flag" - cmdutil "github.com/cert-manager/cert-manager/cmd/util" + config "github.com/cert-manager/cert-manager/internal/apis/config/controller" + configscheme "github.com/cert-manager/cert-manager/internal/apis/config/controller/scheme" + defaults "github.com/cert-manager/cert-manager/internal/apis/config/controller/v1alpha1" "github.com/cert-manager/cert-manager/internal/controller/feature" - cm "github.com/cert-manager/cert-manager/pkg/apis/certmanager" - challengescontroller "github.com/cert-manager/cert-manager/pkg/controller/acmechallenges" - orderscontroller "github.com/cert-manager/cert-manager/pkg/controller/acmeorders" + configv1alpha1 "github.com/cert-manager/cert-manager/pkg/apis/config/controller/v1alpha1" shimgatewaycontroller "github.com/cert-manager/cert-manager/pkg/controller/certificate-shim/gateways" - shimingresscontroller "github.com/cert-manager/cert-manager/pkg/controller/certificate-shim/ingresses" - cracmecontroller "github.com/cert-manager/cert-manager/pkg/controller/certificaterequests/acme" - crapprovercontroller "github.com/cert-manager/cert-manager/pkg/controller/certificaterequests/approver" - crcacontroller "github.com/cert-manager/cert-manager/pkg/controller/certificaterequests/ca" - crselfsignedcontroller "github.com/cert-manager/cert-manager/pkg/controller/certificaterequests/selfsigned" - crvaultcontroller "github.com/cert-manager/cert-manager/pkg/controller/certificaterequests/vault" - crvenaficontroller "github.com/cert-manager/cert-manager/pkg/controller/certificaterequests/venafi" - "github.com/cert-manager/cert-manager/pkg/controller/certificates/issuing" - "github.com/cert-manager/cert-manager/pkg/controller/certificates/keymanager" - certificatesmetricscontroller "github.com/cert-manager/cert-manager/pkg/controller/certificates/metrics" - "github.com/cert-manager/cert-manager/pkg/controller/certificates/readiness" - "github.com/cert-manager/cert-manager/pkg/controller/certificates/requestmanager" - "github.com/cert-manager/cert-manager/pkg/controller/certificates/revisionmanager" - "github.com/cert-manager/cert-manager/pkg/controller/certificates/trigger" - csracmecontroller "github.com/cert-manager/cert-manager/pkg/controller/certificatesigningrequests/acme" - csrcacontroller "github.com/cert-manager/cert-manager/pkg/controller/certificatesigningrequests/ca" - csrselfsignedcontroller "github.com/cert-manager/cert-manager/pkg/controller/certificatesigningrequests/selfsigned" - csrvaultcontroller "github.com/cert-manager/cert-manager/pkg/controller/certificatesigningrequests/vault" - csrvenaficontroller "github.com/cert-manager/cert-manager/pkg/controller/certificatesigningrequests/venafi" - clusterissuerscontroller "github.com/cert-manager/cert-manager/pkg/controller/clusterissuers" - issuerscontroller "github.com/cert-manager/cert-manager/pkg/controller/issuers" logf "github.com/cert-manager/cert-manager/pkg/logs" - "github.com/cert-manager/cert-manager/pkg/util" utilfeature "github.com/cert-manager/cert-manager/pkg/util/feature" ) -type ControllerOptions struct { - APIServerHost string - Kubeconfig string - KubernetesAPIQPS float32 - KubernetesAPIBurst int - - ClusterResourceNamespace string - Namespace string - - LeaderElect bool - LeaderElectionNamespace string - LeaderElectionLeaseDuration time.Duration - LeaderElectionRenewDeadline time.Duration - LeaderElectionRetryPeriod time.Duration - - controllers []string - - ACMEHTTP01SolverImage string - ACMEHTTP01SolverResourceRequestCPU string - ACMEHTTP01SolverResourceRequestMemory string - ACMEHTTP01SolverResourceLimitsCPU string - ACMEHTTP01SolverResourceLimitsMemory string - // Allows specifying a list of custom nameservers to perform HTTP01 checks on. - ACMEHTTP01SolverNameservers []string - - ClusterIssuerAmbientCredentials bool - IssuerAmbientCredentials bool - - // Default issuer/certificates details consumed by ingress-shim - DefaultIssuerName string - DefaultIssuerKind string - DefaultIssuerGroup string - DefaultAutoCertificateAnnotations []string - - // Allows specifying a list of custom nameservers to perform DNS checks on. - DNS01RecursiveNameservers []string - // Allows controlling if recursive nameservers are only used for all checks. - // Normally authoritative nameservers are used for checking propagation. - DNS01RecursiveNameserversOnly bool - - EnableCertificateOwnerRef bool - - MaxConcurrentChallenges int - - // The host and port address, separated by a ':', that the Prometheus server - // should expose metrics on. - MetricsListenAddress string - // PprofAddress is the address on which Go profiler will run. Should be - // in form :. - PprofAddress string - // EnablePprof determines whether pprof should be enabled. - EnablePprof bool - - // DNSO1CheckRetryPeriod is the period of time after which to check if - // challenge URL can be reached by cert-manager controller. This is used - // for both DNS-01 and HTTP-01 challenges. - DNS01CheckRetryPeriod time.Duration - - // Annotations copied Certificate -> CertificateRequest, - // CertificateRequest -> Order. Slice of string literals that are - // treated as prefixes for annotation keys. - CopiedAnnotationPrefixes []string +// ControllerFlags defines options that can only be configured via flags. +type ControllerFlags struct { + // Path to a file containing a ControllerConfiguration resource + Config string } -const ( - defaultAPIServerHost = "" - defaultKubeconfig = "" - defaultKubernetesAPIQPS float32 = 20 - defaultKubernetesAPIBurst = 50 - - defaultClusterResourceNamespace = "kube-system" - defaultNamespace = "" - - defaultClusterIssuerAmbientCredentials = true - defaultIssuerAmbientCredentials = false - - defaultTLSACMEIssuerName = "" - defaultTLSACMEIssuerKind = "Issuer" - defaultTLSACMEIssuerGroup = cm.GroupName - defaultEnableCertificateOwnerRef = false - - defaultDNS01RecursiveNameserversOnly = false - - defaultMaxConcurrentChallenges = 60 - - defaultPrometheusMetricsServerAddress = "0.0.0.0:9402" - - // default time period to wait between checking DNS01 and HTTP01 challenge propagation - defaultDNS01CheckRetryPeriod = 10 * time.Second -) - -var ( - defaultACMEHTTP01SolverImage = fmt.Sprintf("quay.io/jetstack/cert-manager-acmesolver:%s", util.AppVersion) - defaultACMEHTTP01SolverResourceRequestCPU = "10m" - defaultACMEHTTP01SolverResourceRequestMemory = "64Mi" - defaultACMEHTTP01SolverResourceLimitsCPU = "100m" - defaultACMEHTTP01SolverResourceLimitsMemory = "64Mi" - - defaultAutoCertificateAnnotations = []string{"kubernetes.io/tls-acme"} - - allControllers = []string{ - issuerscontroller.ControllerName, - clusterissuerscontroller.ControllerName, - certificatesmetricscontroller.ControllerName, - shimingresscontroller.ControllerName, - shimgatewaycontroller.ControllerName, - orderscontroller.ControllerName, - challengescontroller.ControllerName, - cracmecontroller.CRControllerName, - crapprovercontroller.ControllerName, - crcacontroller.CRControllerName, - crselfsignedcontroller.CRControllerName, - crvaultcontroller.CRControllerName, - crvenaficontroller.CRControllerName, - // certificate controllers - trigger.ControllerName, - issuing.ControllerName, - keymanager.ControllerName, - requestmanager.ControllerName, - readiness.ControllerName, - revisionmanager.ControllerName, - } +func NewControllerFlags() *ControllerFlags { + return &ControllerFlags{} +} - defaultEnabledControllers = []string{ - issuerscontroller.ControllerName, - clusterissuerscontroller.ControllerName, - certificatesmetricscontroller.ControllerName, - shimingresscontroller.ControllerName, - orderscontroller.ControllerName, - challengescontroller.ControllerName, - cracmecontroller.CRControllerName, - crapprovercontroller.ControllerName, - crcacontroller.CRControllerName, - crselfsignedcontroller.CRControllerName, - crvaultcontroller.CRControllerName, - crvenaficontroller.CRControllerName, - // certificate controllers - trigger.ControllerName, - issuing.ControllerName, - keymanager.ControllerName, - requestmanager.ControllerName, - readiness.ControllerName, - revisionmanager.ControllerName, - } +func (f *ControllerFlags) AddFlags(fs *pflag.FlagSet) { + fs.StringVar(&f.Config, "config", "", "Path to a file containing a ControllerConfiguration object used to configure the controller") +} - experimentalCertificateSigningRequestControllers = []string{ - csracmecontroller.CSRControllerName, - csrcacontroller.CSRControllerName, - csrselfsignedcontroller.CSRControllerName, - csrvenaficontroller.CSRControllerName, - csrvaultcontroller.CSRControllerName, - } - // Annotations that will be copied from Certificate to CertificateRequest and to Order. - // By default, copy all annotations except for the ones applied by kubectl, fluxcd, argocd. - defaultCopiedAnnotationPrefixes = []string{ - "*", - "-kubectl.kubernetes.io/", - "-fluxcd.io/", - "-argocd.argoproj.io/", +func NewControllerConfiguration() (*config.ControllerConfiguration, error) { + scheme, _, err := configscheme.NewSchemeAndCodecs() + if err != nil { + return nil, err } -) - -func NewControllerOptions() *ControllerOptions { - return &ControllerOptions{ - APIServerHost: defaultAPIServerHost, - ClusterResourceNamespace: defaultClusterResourceNamespace, - KubernetesAPIQPS: defaultKubernetesAPIQPS, - KubernetesAPIBurst: defaultKubernetesAPIBurst, - Namespace: defaultNamespace, - LeaderElect: cmdutil.DefaultLeaderElect, - LeaderElectionNamespace: cmdutil.DefaultLeaderElectionNamespace, - LeaderElectionLeaseDuration: cmdutil.DefaultLeaderElectionLeaseDuration, - LeaderElectionRenewDeadline: cmdutil.DefaultLeaderElectionRenewDeadline, - LeaderElectionRetryPeriod: cmdutil.DefaultLeaderElectionRetryPeriod, - controllers: defaultEnabledControllers, - ClusterIssuerAmbientCredentials: defaultClusterIssuerAmbientCredentials, - IssuerAmbientCredentials: defaultIssuerAmbientCredentials, - DefaultIssuerName: defaultTLSACMEIssuerName, - DefaultIssuerKind: defaultTLSACMEIssuerKind, - DefaultIssuerGroup: defaultTLSACMEIssuerGroup, - DefaultAutoCertificateAnnotations: defaultAutoCertificateAnnotations, - ACMEHTTP01SolverNameservers: []string{}, - DNS01RecursiveNameservers: []string{}, - DNS01RecursiveNameserversOnly: defaultDNS01RecursiveNameserversOnly, - EnableCertificateOwnerRef: defaultEnableCertificateOwnerRef, - MetricsListenAddress: defaultPrometheusMetricsServerAddress, - DNS01CheckRetryPeriod: defaultDNS01CheckRetryPeriod, - EnablePprof: cmdutil.DefaultEnableProfiling, - PprofAddress: cmdutil.DefaultProfilerAddr, + versioned := &configv1alpha1.ControllerConfiguration{} + scheme.Default(versioned) + config := &config.ControllerConfiguration{} + if err := scheme.Convert(versioned, config, nil); err != nil { + return nil, err } + return config, nil } -func (s *ControllerOptions) AddFlags(fs *pflag.FlagSet) { - fs.StringVar(&s.APIServerHost, "master", defaultAPIServerHost, ""+ +func AddConfigFlags(fs *pflag.FlagSet, c *config.ControllerConfiguration) { + fs.StringVar(&c.APIServerHost, "master", c.APIServerHost, ""+ "Optional apiserver host address to connect to. If not specified, autoconfiguration "+ "will be attempted.") - fs.StringVar(&s.Kubeconfig, "kubeconfig", defaultKubeconfig, ""+ + fs.StringVar(&c.KubeConfig, "kubeconfig", c.KubeConfig, ""+ "Paths to a kubeconfig. Only required if out-of-cluster.") - fs.Float32Var(&s.KubernetesAPIQPS, "kube-api-qps", defaultKubernetesAPIQPS, "indicates the maximum queries-per-second requests to the Kubernetes apiserver") - fs.IntVar(&s.KubernetesAPIBurst, "kube-api-burst", defaultKubernetesAPIBurst, "the maximum burst queries-per-second of requests sent to the Kubernetes apiserver") - fs.StringVar(&s.ClusterResourceNamespace, "cluster-resource-namespace", defaultClusterResourceNamespace, ""+ + fs.Float32Var(&c.KubernetesAPIQPS, "kube-api-qps", c.KubernetesAPIQPS, "indicates the maximum queries-per-second requests to the Kubernetes apiserver") + fs.IntVar(&c.KubernetesAPIBurst, "kube-api-burst", c.KubernetesAPIBurst, "the maximum burst queries-per-second of requests sent to the Kubernetes apiserver") + fs.StringVar(&c.ClusterResourceNamespace, "cluster-resource-namespace", c.ClusterResourceNamespace, ""+ "Namespace to store resources owned by cluster scoped resources such as ClusterIssuer in. "+ "This must be specified if ClusterIssuers are enabled.") - fs.StringVar(&s.Namespace, "namespace", defaultNamespace, ""+ + fs.StringVar(&c.Namespace, "namespace", c.Namespace, ""+ "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") - fs.BoolVar(&s.LeaderElect, "leader-elect", cmdutil.DefaultLeaderElect, ""+ + fs.BoolVar(&c.LeaderElectionConfig.Enabled, "leader-elect", c.LeaderElectionConfig.Enabled, ""+ "If true, cert-manager will perform leader election between instances to ensure no more "+ "than one instance of cert-manager operates at a time") - fs.StringVar(&s.LeaderElectionNamespace, "leader-election-namespace", cmdutil.DefaultLeaderElectionNamespace, ""+ + fs.StringVar(&c.LeaderElectionConfig.Namespace, "leader-election-namespace", c.LeaderElectionConfig.Namespace, ""+ "Namespace used to perform leader election. Only used if leader election is enabled") - fs.DurationVar(&s.LeaderElectionLeaseDuration, "leader-election-lease-duration", cmdutil.DefaultLeaderElectionLeaseDuration, ""+ + fs.DurationVar(&c.LeaderElectionConfig.LeaseDuration, "leader-election-lease-duration", c.LeaderElectionConfig.LeaseDuration, ""+ "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.") - fs.DurationVar(&s.LeaderElectionRenewDeadline, "leader-election-renew-deadline", cmdutil.DefaultLeaderElectionRenewDeadline, ""+ + fs.DurationVar(&c.LeaderElectionConfig.RenewDeadline, "leader-election-renew-deadline", c.LeaderElectionConfig.RenewDeadline, ""+ "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.") - fs.DurationVar(&s.LeaderElectionRetryPeriod, "leader-election-retry-period", cmdutil.DefaultLeaderElectionRetryPeriod, ""+ + fs.DurationVar(&c.LeaderElectionConfig.RetryPeriod, "leader-election-retry-period", c.LeaderElectionConfig.RetryPeriod, ""+ "The duration the clients should wait between attempting acquisition and renewal "+ "of a leadership. This is only applicable if leader election is enabled.") - fs.StringSliceVar(&s.controllers, "controllers", []string{"*"}, fmt.Sprintf(""+ + fs.StringSliceVar(&c.Controllers, "controllers", c.Controllers, fmt.Sprintf(""+ "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'.\nAll controllers: %s", - strings.Join(allControllers, ", "))) + strings.Join(defaults.AllControllers, ", "))) + + fs.StringVar(&c.ACMEHTTP01Config.SolverImage, "acme-http01-solver-image", c.ACMEHTTP01Config.SolverImage, ""+ + "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.") // HTTP-01 solver pod configuration via flags is a now deprecated - // mechanism- please use pod template instead when adding any new + // mechanism - please use pod template instead when adding any new // configuration options // https://github.com/cert-manager/cert-manager/blob/f1d7c432763100c3fb6eb6a1654d29060b479b3c/pkg/apis/acme/v1/types_issuer.go#L270 // These flags however will not be deprecated for backwards compatibility purposes. - fs.StringVar(&s.ACMEHTTP01SolverImage, "acme-http01-solver-image", defaultACMEHTTP01SolverImage, ""+ - "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.") - - fs.StringVar(&s.ACMEHTTP01SolverResourceRequestCPU, "acme-http01-solver-resource-request-cpu", defaultACMEHTTP01SolverResourceRequestCPU, ""+ + fs.StringVar(&c.ACMEHTTP01Config.SolverResourceRequestCPU, "acme-http01-solver-resource-request-cpu", c.ACMEHTTP01Config.SolverResourceRequestCPU, ""+ "Defines the resource request CPU size when spawning new ACME HTTP01 challenge solver pods.") - fs.StringVar(&s.ACMEHTTP01SolverResourceRequestMemory, "acme-http01-solver-resource-request-memory", defaultACMEHTTP01SolverResourceRequestMemory, ""+ + fs.StringVar(&c.ACMEHTTP01Config.SolverResourceRequestMemory, "acme-http01-solver-resource-request-memory", c.ACMEHTTP01Config.SolverResourceRequestMemory, ""+ "Defines the resource request Memory size when spawning new ACME HTTP01 challenge solver pods.") - fs.StringVar(&s.ACMEHTTP01SolverResourceLimitsCPU, "acme-http01-solver-resource-limits-cpu", defaultACMEHTTP01SolverResourceLimitsCPU, ""+ + fs.StringVar(&c.ACMEHTTP01Config.SolverResourceLimitsCPU, "acme-http01-solver-resource-limits-cpu", c.ACMEHTTP01Config.SolverResourceLimitsCPU, ""+ "Defines the resource limits CPU size when spawning new ACME HTTP01 challenge solver pods.") - fs.StringVar(&s.ACMEHTTP01SolverResourceLimitsMemory, "acme-http01-solver-resource-limits-memory", defaultACMEHTTP01SolverResourceLimitsMemory, ""+ + fs.StringVar(&c.ACMEHTTP01Config.SolverResourceLimitsMemory, "acme-http01-solver-resource-limits-memory", c.ACMEHTTP01Config.SolverResourceLimitsMemory, ""+ "Defines the resource limits Memory size when spawning new ACME HTTP01 challenge solver pods.") - fs.StringSliceVar(&s.ACMEHTTP01SolverNameservers, "acme-http01-solver-nameservers", - []string{}, "A list of comma separated dns server endpoints used for "+ + fs.BoolVar(&c.ACMEHTTP01Config.SolverRunAsNonRoot, "acme-http01-solver-run-as-non-root", c.ACMEHTTP01Config.SolverRunAsNonRoot, ""+ + "Defines the ability to run the http01 solver as root for troubleshooting issues") + + fs.StringSliceVar(&c.ACMEHTTP01Config.SolverNameservers, "acme-http01-solver-nameservers", + c.ACMEHTTP01Config.SolverNameservers, "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") - fs.BoolVar(&s.ClusterIssuerAmbientCredentials, "cluster-issuer-ambient-credentials", defaultClusterIssuerAmbientCredentials, ""+ + fs.BoolVar(&c.ClusterIssuerAmbientCredentials, "cluster-issuer-ambient-credentials", c.ClusterIssuerAmbientCredentials, ""+ "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.") - fs.BoolVar(&s.IssuerAmbientCredentials, "issuer-ambient-credentials", defaultIssuerAmbientCredentials, ""+ + fs.BoolVar(&c.IssuerAmbientCredentials, "issuer-ambient-credentials", c.IssuerAmbientCredentials, ""+ "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.") - fs.StringSliceVar(&s.DefaultAutoCertificateAnnotations, "auto-certificate-annotations", defaultAutoCertificateAnnotations, ""+ - "The annotation consumed by the ingress-shim controller to indicate a ingress is requesting a certificate") - fs.StringVar(&s.DefaultIssuerName, "default-issuer-name", defaultTLSACMEIssuerName, ""+ + fs.StringSliceVar(&c.IngressShimConfig.DefaultAutoCertificateAnnotations, "auto-certificate-annotations", c.IngressShimConfig.DefaultAutoCertificateAnnotations, ""+ + "The annotation consumed by the ingress-shim controller to indicate an ingress is requesting a certificate") + fs.StringSliceVar(&c.IngressShimConfig.ExtraCertificateAnnotations, "extra-certificate-annotations", []string{}, ""+ + "Extra annotation to be added by the ingress-shim controller to certificate object") + fs.StringVar(&c.IngressShimConfig.DefaultIssuerName, "default-issuer-name", c.IngressShimConfig.DefaultIssuerName, ""+ "Name of the Issuer to use when the tls is requested but issuer name is not specified on the ingress resource.") - fs.StringVar(&s.DefaultIssuerKind, "default-issuer-kind", defaultTLSACMEIssuerKind, ""+ + fs.StringVar(&c.IngressShimConfig.DefaultIssuerKind, "default-issuer-kind", c.IngressShimConfig.DefaultIssuerKind, ""+ "Kind of the Issuer to use when the tls is requested but issuer kind is not specified on the ingress resource.") - fs.StringVar(&s.DefaultIssuerGroup, "default-issuer-group", defaultTLSACMEIssuerGroup, ""+ + fs.StringVar(&c.IngressShimConfig.DefaultIssuerGroup, "default-issuer-group", c.IngressShimConfig.DefaultIssuerGroup, ""+ "Group of the Issuer to use when the tls is requested but issuer group is not specified on the ingress resource.") - fs.StringSliceVar(&s.DNS01RecursiveNameservers, "dns01-recursive-nameservers", - []string{}, "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") - fs.BoolVar(&s.DNS01RecursiveNameserversOnly, "dns01-recursive-nameservers-only", - defaultDNS01RecursiveNameserversOnly, + + fs.StringSliceVar(&c.ACMEDNS01Config.RecursiveNameservers, "dns01-recursive-nameservers", + c.ACMEDNS01Config.RecursiveNameservers, "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,[2001:4860:4860::8888]: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.") + fs.BoolVar(&c.ACMEDNS01Config.RecursiveNameserversOnly, "dns01-recursive-nameservers-only", + c.ACMEDNS01Config.RecursiveNameserversOnly, "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.") + fs.DurationVar(&c.ACMEDNS01Config.CheckRetryPeriod, "dns01-check-retry-period", c.ACMEDNS01Config.CheckRetryPeriod, ""+ + "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") - fs.BoolVar(&s.EnableCertificateOwnerRef, "enable-certificate-owner-ref", defaultEnableCertificateOwnerRef, ""+ + fs.BoolVar(&c.EnableCertificateOwnerRef, "enable-certificate-owner-ref", c.EnableCertificateOwnerRef, ""+ "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.") - fs.StringSliceVar(&s.CopiedAnnotationPrefixes, "copied-annotation-prefixes", defaultCopiedAnnotationPrefixes, "Specify which annotations should/shouldn't be copied"+ + fs.BoolVar(&c.EnableGatewayAPI, "enable-gateway-api", c.EnableGatewayAPI, ""+ + "Whether gateway API integration is enabled within cert-manager. The ExperimentalGatewayAPISupport "+ + "feature gate must also be enabled (default as of 1.15).") + fs.StringSliceVar(&c.CopiedAnnotationPrefixes, "copied-annotation-prefixes", c.CopiedAnnotationPrefixes, "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"+ + "A prefix starting with a dash(-) specifies an annotation that shouldn't be copied. Example: '*,-kubectl.kubernetes.io/'- all annotations"+ "will be copied apart from the ones where the key is prefixed with 'kubectl.kubernetes.io/'.") + fs.Var(cliflag.NewMapStringBool(&c.FeatureGates), "feature-gates", "A set of key=value pairs that describe feature gates for alpha/experimental features. "+ + "Options are:\n"+strings.Join(utilfeature.DefaultFeatureGate.KnownFeatures(), "\n")) - fs.IntVar(&s.MaxConcurrentChallenges, "max-concurrent-challenges", defaultMaxConcurrentChallenges, ""+ + fs.IntVar(&c.NumberOfConcurrentWorkers, "concurrent-workers", c.NumberOfConcurrentWorkers, ""+ + "The number of concurrent workers for each controller.") + fs.IntVar(&c.MaxConcurrentChallenges, "max-concurrent-challenges", c.MaxConcurrentChallenges, ""+ "The maximum number of challenges that can be scheduled as 'processing' at once.") - fs.DurationVar(&s.DNS01CheckRetryPeriod, "dns01-check-retry-period", defaultDNS01CheckRetryPeriod, ""+ - "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") - fs.StringVar(&s.MetricsListenAddress, "metrics-listen-address", defaultPrometheusMetricsServerAddress, ""+ + fs.StringVar(&c.MetricsListenAddress, "metrics-listen-address", c.MetricsListenAddress, ""+ "The host and port that the metrics endpoint should listen on.") - fs.BoolVar(&s.EnablePprof, "enable-profiling", cmdutil.DefaultEnableProfiling, ""+ + fs.BoolVar(&c.EnablePprof, "enable-profiling", c.EnablePprof, ""+ "Enable profiling for controller.") - fs.StringVar(&s.PprofAddress, "profiler-address", cmdutil.DefaultProfilerAddr, + fs.StringVar(&c.PprofAddress, "profiler-address", c.PprofAddress, "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.") -} - -func (o *ControllerOptions) Validate() error { - if len(o.DefaultIssuerKind) == 0 { - return errors.New("the --default-issuer-kind flag must not be empty") - } - - if o.KubernetesAPIBurst <= 0 { - return fmt.Errorf("invalid value for kube-api-burst: %v must be higher than 0", o.KubernetesAPIBurst) - } - - if o.KubernetesAPIQPS <= 0 { - return fmt.Errorf("invalid value for kube-api-qps: %v must be higher than 0", o.KubernetesAPIQPS) - } - - if float32(o.KubernetesAPIBurst) < o.KubernetesAPIQPS { - return fmt.Errorf("invalid value for kube-api-burst: %v must be higher or equal to kube-api-qps: %v", o.KubernetesAPIQPS, o.KubernetesAPIQPS) - } - - for _, server := range append(o.DNS01RecursiveNameservers, o.ACMEHTTP01SolverNameservers...) { - // ensure all servers have a port number - _, _, err := net.SplitHostPort(server) - if err != nil { - return fmt.Errorf("invalid DNS server (%v): %v", err, server) - } - } - - errs := []error{} - allControllersSet := sets.NewString(allControllers...) - for _, controller := range o.controllers { - if controller == "*" { - continue - } - - controller = strings.TrimPrefix(controller, "-") - if !allControllersSet.Has(controller) { - errs = append(errs, fmt.Errorf("%q is not in the list of known controllers", controller)) - } - } - if len(errs) > 0 { - return fmt.Errorf("validation failed for '--controllers': %v", errs) - } - - return nil + fs.StringVar(&c.MetricsTLSConfig.Filesystem.CertFile, "metrics-tls-cert-file", c.MetricsTLSConfig.Filesystem.CertFile, "path to the file containing the TLS certificate to serve with") + fs.StringVar(&c.MetricsTLSConfig.Filesystem.KeyFile, "metrics-tls-private-key-file", c.MetricsTLSConfig.Filesystem.KeyFile, "path to the file containing the TLS private key to serve with") + + fs.DurationVar(&c.MetricsTLSConfig.Dynamic.LeafDuration, "metrics-dynamic-serving-leaf-duration", c.MetricsTLSConfig.Dynamic.LeafDuration, "leaf duration of serving certificates") + fs.StringVar(&c.MetricsTLSConfig.Dynamic.SecretNamespace, "metrics-dynamic-serving-ca-secret-namespace", c.MetricsTLSConfig.Dynamic.SecretNamespace, "namespace of the secret used to store the CA that signs serving certificates") + fs.StringVar(&c.MetricsTLSConfig.Dynamic.SecretName, "metrics-dynamic-serving-ca-secret-name", c.MetricsTLSConfig.Dynamic.SecretName, "name of the secret used to store the CA that signs serving certificates") + fs.StringSliceVar(&c.MetricsTLSConfig.Dynamic.DNSNames, "metrics-dynamic-serving-dns-names", c.MetricsTLSConfig.Dynamic.DNSNames, "DNS names that should be present on certificates generated by the dynamic serving CA") + tlsCipherPossibleValues := cliflag.TLSCipherPossibleValues() + fs.StringSliceVar(&c.MetricsTLSConfig.CipherSuites, "metrics-tls-cipher-suites", c.MetricsTLSConfig.CipherSuites, + "Comma-separated list of cipher suites for the server. "+ + "If omitted, the default Go cipher suites will be used. "+ + "Possible values: "+strings.Join(tlsCipherPossibleValues, ",")) + tlsPossibleVersions := cliflag.TLSPossibleVersions() + fs.StringVar(&c.MetricsTLSConfig.MinTLSVersion, "metrics-tls-min-version", c.MetricsTLSConfig.MinTLSVersion, + "Minimum TLS version supported. If omitted, the default Go minimum version will be used. "+ + "Possible values: "+strings.Join(tlsPossibleVersions, ", ")) + + // The healthz related flags are given the prefix "internal-" and are hidden, + // to discourage users from overriding them. + // We may want to rename or remove these flags when we have feedback from + // end-users about whether the default liveness + // probe and the separate healthz server are a good and correct way to + // mitigate unexpected deadlocks in the controller-manager process. + // + // TODO(wallrj) Consider merging the metrics, pprof and healthz servers, and + // having a single --secure-port flag, like Kubernetes components do. + fs.StringVar(&c.HealthzListenAddress, "internal-healthz-listen-address", c.HealthzListenAddress, ""+ + "The host and port that the healthz server should listen on. "+ + "The healthz server serves the /livez endpoint, which is called by the LivenessProbe.") + utilruntime.Must(fs.MarkHidden("internal-healthz-listen-address")) + + fs.DurationVar(&c.LeaderElectionConfig.HealthzTimeout, "internal-healthz-leader-election-timeout", c.LeaderElectionConfig.HealthzTimeout, ""+ + "Leader election healthz checks within this timeout period after the lease expires will still return healthy") + utilruntime.Must(fs.MarkHidden("internal-healthz-leader-election-timeout")) + + logf.AddFlags(&c.Logging, fs) } -func (o *ControllerOptions) EnabledControllers() sets.String { +func EnabledControllers(o *config.ControllerConfiguration) sets.Set[string] { var disabled []string - enabled := sets.NewString() + enabled := sets.New[string]() - for _, controller := range o.controllers { + for _, controller := range o.Controllers { switch { case controller == "*": - enabled = enabled.Insert(defaultEnabledControllers...) + enabled = enabled.Insert(defaults.DefaultEnabledControllers...) case strings.HasPrefix(controller, "-"): disabled = append(disabled, strings.TrimPrefix(controller, "-")) default: @@ -427,17 +246,34 @@ func (o *ControllerOptions) EnabledControllers() sets.String { } } - enabled = enabled.Delete(disabled...) + // Detect if "*" was implied (in case only disabled controllers were specified) + if len(disabled) > 0 && len(enabled) == 0 { + enabled = enabled.Insert(defaults.DefaultEnabledControllers...) + } if utilfeature.DefaultFeatureGate.Enabled(feature.ExperimentalCertificateSigningRequestControllers) { logf.Log.Info("enabling all experimental certificatesigningrequest controllers") - enabled = enabled.Insert(experimentalCertificateSigningRequestControllers...) + enabled = enabled.Insert(defaults.ExperimentalCertificateSigningRequestControllers...) } - if utilfeature.DefaultFeatureGate.Enabled(feature.ExperimentalGatewayAPISupport) { + if utilfeature.DefaultFeatureGate.Enabled(feature.ExperimentalGatewayAPISupport) && o.EnableGatewayAPI { logf.Log.Info("enabling the sig-network Gateway API certificate-shim and HTTP-01 solver") enabled = enabled.Insert(shimgatewaycontroller.ControllerName) } + if utilfeature.DefaultFeatureGate.Enabled(feature.ValidateCAA) { + logf.Log.Info("the ValidateCAA feature flag has been removed and is now a no-op") + } + + // If running namespaced, remove all cluster-scoped controllers. + if o.Namespace != "" { + logf.Log.Info("disabling all cluster-scoped controllers as cert-manager is scoped to a single namespace", + "controllers", strings.Join(defaults.ClusterScopedControllers, ", ")) + enabled = enabled.Delete(defaults.ClusterScopedControllers...) + } + + // Only after all controllers have been added, remove the disabled ones. + enabled = enabled.Delete(disabled...) + return enabled } diff --git a/cmd/controller/app/options/options_test.go b/cmd/controller/app/options/options_test.go index 3a0ca143928..80b513d42af 100644 --- a/cmd/controller/app/options/options_test.go +++ b/cmd/controller/app/options/options_test.go @@ -20,42 +20,53 @@ import ( "testing" "k8s.io/apimachinery/pkg/util/sets" + + config "github.com/cert-manager/cert-manager/internal/apis/config/controller" + defaults "github.com/cert-manager/cert-manager/internal/apis/config/controller/v1alpha1" ) func TestEnabledControllers(t *testing.T) { tests := map[string]struct { controllers []string - expEnabled sets.String + expEnabled sets.Set[string] }{ "if no controllers enabled, return empty": { controllers: []string{}, - expEnabled: sets.NewString(), + expEnabled: sets.New[string](), }, "if some controllers enabled, return list": { controllers: []string{"foo", "bar"}, - expEnabled: sets.NewString("foo", "bar"), + expEnabled: sets.New("foo", "bar"), }, "if some controllers enabled, one then disabled, return list without disabled": { controllers: []string{"foo", "bar", "-foo"}, - expEnabled: sets.NewString("bar"), + expEnabled: sets.New("bar"), }, "if all default controllers enabled, return all default controllers": { controllers: []string{"*"}, - expEnabled: sets.NewString(defaultEnabledControllers...), + expEnabled: sets.New(defaults.DefaultEnabledControllers...), }, - "if all controllers enabled, some diabled, return all controllers with disabled": { + "if all controllers enabled, some disabled, return all controllers with disabled": { controllers: []string{"*", "-clusterissuers", "-issuers"}, - expEnabled: sets.NewString(defaultEnabledControllers...).Delete("clusterissuers", "issuers"), + expEnabled: sets.New(defaults.DefaultEnabledControllers...).Delete("clusterissuers", "issuers"), + }, + "if only disabled controllers are specified, implicitly enable all default controllers": { + controllers: []string{"-clusterissuers", "-issuers"}, + expEnabled: sets.New(defaults.DefaultEnabledControllers...).Delete("clusterissuers", "issuers"), + }, + "if both enabled and disabled controllers are specified, return specified controllers": { + controllers: []string{"foo", "-bar"}, + expEnabled: sets.New("foo"), }, } for name, test := range tests { t.Run(name, func(t *testing.T) { - o := ControllerOptions{ - controllers: test.controllers, + o := config.ControllerConfiguration{ + Controllers: test.controllers, } - got := o.EnabledControllers() + got := EnabledControllers(&o) if !got.Equal(test.expEnabled) { t.Errorf("got unexpected enabled, exp=%s got=%s", test.expEnabled, got) diff --git a/cmd/controller/app/start.go b/cmd/controller/app/start.go index 08784260a4f..e3ade262791 100644 --- a/cmd/controller/app/start.go +++ b/cmd/controller/app/start.go @@ -17,13 +17,21 @@ limitations under the License. package app import ( + "context" "fmt" + "os" + "path/filepath" "github.com/spf13/cobra" - utilerrors "k8s.io/apimachinery/pkg/util/errors" - _ "k8s.io/client-go/plugin/pkg/client/auth" - "github.com/cert-manager/cert-manager/cmd/controller/app/options" + "github.com/cert-manager/cert-manager/controller-binary/app/options" + config "github.com/cert-manager/cert-manager/internal/apis/config/controller" + "github.com/cert-manager/cert-manager/internal/apis/config/controller/validation" + controllerconfigfile "github.com/cert-manager/cert-manager/pkg/controller/configfile" + logf "github.com/cert-manager/cert-manager/pkg/logs" + "github.com/cert-manager/cert-manager/pkg/util/configfile" + utilfeature "github.com/cert-manager/cert-manager/pkg/util/feature" + _ "github.com/cert-manager/cert-manager/pkg/controller/acmechallenges" _ "github.com/cert-manager/cert-manager/pkg/controller/acmeorders" _ "github.com/cert-manager/cert-manager/pkg/controller/certificate-shim/gateways" @@ -36,30 +44,34 @@ import ( _ "github.com/cert-manager/cert-manager/pkg/issuer/selfsigned" _ "github.com/cert-manager/cert-manager/pkg/issuer/vault" _ "github.com/cert-manager/cert-manager/pkg/issuer/venafi" - logf "github.com/cert-manager/cert-manager/pkg/logs" - "github.com/cert-manager/cert-manager/pkg/util" - utilfeature "github.com/cert-manager/cert-manager/pkg/util/feature" ) -type CertManagerControllerOptions struct { - ControllerOptions *options.ControllerOptions -} +const componentController = "controller" -func NewCertManagerControllerOptions() *CertManagerControllerOptions { - o := &CertManagerControllerOptions{ - ControllerOptions: options.NewControllerOptions(), - } - - return o +func NewServerCommand(ctx context.Context) *cobra.Command { + return newServerCommand( + ctx, + Run, + os.Args[1:], + ) } -// NewCommandStartCertManagerController is a CLI handler for starting cert-manager -func NewCommandStartCertManagerController(stopCh <-chan struct{}) *cobra.Command { - o := NewCertManagerControllerOptions() +func newServerCommand( + setupCtx context.Context, + run func(context.Context, *config.ControllerConfiguration) error, + allArgs []string, +) *cobra.Command { + log := logf.FromContext(setupCtx, componentController) + + controllerFlags := options.NewControllerFlags() + controllerConfig, err := options.NewControllerConfiguration() + if err != nil { + log.Error(err, "Failed to create new controller configuration") + os.Exit(1) + } cmd := &cobra.Command{ - Use: "cert-manager-controller", - Short: fmt.Sprintf("Automated TLS controller for Kubernetes (%s) (%s)", util.AppVersion, util.AppGitCommit), + Use: componentController, Long: ` cert-manager is a Kubernetes addon to automate the management and issuance of TLS certificates from various issuing sources. @@ -67,35 +79,105 @@ 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.`, - RunE: func(cmd *cobra.Command, args []string) error { - if err := o.Validate(args); err != nil { - return fmt.Errorf("error validating options: %s", err) + SilenceErrors: true, // We already log errors in main.go + SilenceUsage: true, // Don't print usage on every error + + PreRunE: func(cmd *cobra.Command, args []string) error { + if err := loadConfigFromFile( + cmd, allArgs, controllerFlags.Config, controllerConfig, + func() error { + // set feature gates from initial flags-based config + if err := utilfeature.DefaultMutableFeatureGate.SetFromMap(controllerConfig.FeatureGates); err != nil { + return fmt.Errorf("failed to set feature gates from initial flags-based config: %w", err) + } + + return nil + }, + ); err != nil { + return err } - logf.Log.V(logf.InfoLevel).Info("starting controller", "version", util.AppVersion, "git-commit", util.AppGitCommit) - if err := o.RunCertManagerController(stopCh); err != nil { - cmd.SilenceUsage = true // Don't display usage information when exiting because of an error - return err + if err := validation.ValidateControllerConfiguration(controllerConfig, nil); len(err) > 0 { + return fmt.Errorf("error validating flags: %w", err.ToAggregate()) + } + + // ValidateControllerConfiguration should already have validated the + // logging flags, the logging API does not have an Apply-only function + // so we validate again here. This should not catch any validation errors + // anymore. + if err := logf.ValidateAndApply(&controllerConfig.Logging); err != nil { + return fmt.Errorf("failed to validate controller logging flags: %w", err) } return nil }, - SilenceErrors: true, // Errors are already logged when calling cmd.Execute() + // nolint:contextcheck // False positive + RunE: func(cmd *cobra.Command, args []string) error { + return run(cmd.Context(), controllerConfig) + }, } - flags := cmd.Flags() - o.ControllerOptions.AddFlags(flags) - utilfeature.DefaultMutableFeatureGate.AddFlag(flags) + controllerFlags.AddFlags(cmd.Flags()) + options.AddConfigFlags(cmd.Flags(), controllerConfig) + + // explicitly set provided args in case it does not equal os.Args[:1], + // e.g., when running tests + cmd.SetArgs(allArgs) return cmd } -func (o CertManagerControllerOptions) Validate(args []string) error { - errors := []error{} - errors = append(errors, o.ControllerOptions.Validate()) - return utilerrors.NewAggregate(errors) -} +// loadConfigFromFile loads the configuration from the provided config file +// path, if one is provided. After loading the config file, the flags are +// re-parsed to ensure that any flags provided to the command line override +// those provided in the config file. +// The newConfigHook is called when the options have been loaded from the +// flags (but not yet the config file) and is re-called after the config file +// has been loaded. This allows us to use the feature flags set by the flags +// while loading the config file. +func loadConfigFromFile( + cmd *cobra.Command, + allArgs []string, + configFilePath string, + cfg *config.ControllerConfiguration, + newConfigHook func() error, +) error { + if err := newConfigHook(); err != nil { + return err + } + + if len(configFilePath) > 0 { + // compute absolute path based on current working dir + controllerConfigFile, err := filepath.Abs(configFilePath) + if err != nil { + return fmt.Errorf("failed to load config file %s, error %v", configFilePath, err) + } + + loader, err := configfile.NewConfigurationFSLoader(nil, controllerConfigFile) + if err != nil { + return fmt.Errorf("failed to load config file %s, error %v", configFilePath, err) + } + + controllerConfigFromFile := controllerconfigfile.New() + if err := loader.Load(controllerConfigFromFile); err != nil { + return fmt.Errorf("failed to load config file %s, error %v", configFilePath, err) + } + + controllerConfigFromFile.Config.DeepCopyInto(cfg) + + _, args, err := cmd.Root().Find(allArgs) + if err != nil { + return fmt.Errorf("failed to re-parse flags: %w", err) + } + + if err := cmd.ParseFlags(args); err != nil { + return fmt.Errorf("failed to re-parse flags: %w", err) + } + + if err := newConfigHook(); err != nil { + return err + } + } -func (o CertManagerControllerOptions) RunCertManagerController(stopCh <-chan struct{}) error { - return Run(o.ControllerOptions, stopCh) + return nil } diff --git a/cmd/controller/app/start_test.go b/cmd/controller/app/start_test.go new file mode 100644 index 00000000000..9cf9908716e --- /dev/null +++ b/cmd/controller/app/start_test.go @@ -0,0 +1,213 @@ +/* +Copyright 2021 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. +*/ + +package app + +import ( + "context" + "fmt" + "io" + "os" + "path" + "reflect" + "testing" + + logsapi "k8s.io/component-base/logs/api/v1" + + "github.com/cert-manager/cert-manager/controller-binary/app/options" + config "github.com/cert-manager/cert-manager/internal/apis/config/controller" +) + +func testCmdCommand(t *testing.T, tempDir string, yaml string, args func(string) []string) (*config.ControllerConfiguration, error) { + var tempFilePath string + + func() { + tempFile, err := os.CreateTemp(tempDir, "config-*.yaml") + if err != nil { + t.Error(err) + } + defer tempFile.Close() + + tempFilePath = tempFile.Name() + + if _, err := tempFile.WriteString(yaml); err != nil { + t.Error(err) + } + }() + + var finalConfig *config.ControllerConfiguration + + if err := logsapi.ResetForTest(nil); err != nil { + t.Error(err) + } + cmd := newServerCommand(t.Context(), func(ctx context.Context, cc *config.ControllerConfiguration) error { + finalConfig = cc + return nil + }, args(tempFilePath)) + + cmd.SetErr(io.Discard) + cmd.SetOut(io.Discard) + + err := cmd.ExecuteContext(t.Context()) + return finalConfig, err +} + +func TestFlagsAndConfigFile(t *testing.T) { + type testCase struct { + yaml string + args func(string) []string + expError bool + expConfig func(string) *config.ControllerConfiguration + } + + configFromDefaults := func( + fn func(string, *config.ControllerConfiguration), + ) func(string) *config.ControllerConfiguration { + defaults, err := options.NewControllerConfiguration() + if err != nil { + t.Error(err) + } + return func(tempDir string) *config.ControllerConfiguration { + fn(tempDir, defaults) + return defaults + } + } + + tests := []testCase{ + { + yaml: ``, + args: func(tempFilePath string) []string { + return []string{"--kubeconfig=valid"} + }, + expConfig: configFromDefaults(func(tempDir string, cc *config.ControllerConfiguration) { + cc.KubeConfig = "valid" + }), + }, + { + yaml: ` +apiVersion: controller.config.cert-manager.io/v1alpha1 +kind: ControllerConfiguration +kubeConfig: "" +`, + args: func(tempFilePath string) []string { + return []string{"--config=" + tempFilePath, "--kubeconfig=valid"} + }, + expConfig: configFromDefaults(func(tempDir string, cc *config.ControllerConfiguration) { + cc.KubeConfig = "valid" + }), + }, + { + yaml: ` +apiVersion: controller.config.cert-manager.io/v1alpha1 +kind: ControllerConfiguration +kubeConfig: valid +`, + args: func(tempFilePath string) []string { + return []string{"--config=" + tempFilePath} + }, + expConfig: configFromDefaults(func(tempDir string, cc *config.ControllerConfiguration) { + cc.KubeConfig = path.Join(tempDir, "valid") + }), + }, + { + yaml: ` +apiVersion: controller.config.cert-manager.io/v1alpha1 +kind: ControllerConfiguration +ingressShimConfig: {} +`, + args: func(tempFilePath string) []string { + return []string{"--config=" + tempFilePath} + }, + expConfig: configFromDefaults(func(tempDir string, cc *config.ControllerConfiguration) { + }), + }, + { + yaml: ` +apiVersion: controller.config.cert-manager.io/v1alpha1 +kind: ControllerConfiguration +ingressShimConfig: nil +`, + args: func(tempFilePath string) []string { + return []string{"--config=" + tempFilePath} + }, + expError: true, + }, + { + yaml: ` +apiVersion: controller.config.cert-manager.io/v1alpha1 +kind: ControllerConfiguration +ingressShimConfig: + defaultIssuerName: aaaa +`, + args: func(tempFilePath string) []string { + return []string{"--config=" + tempFilePath, "--default-issuer-kind=bbbb"} + }, + expConfig: configFromDefaults(func(tempDir string, cc *config.ControllerConfiguration) { + cc.IngressShimConfig.DefaultIssuerName = "aaaa" + cc.IngressShimConfig.DefaultIssuerKind = "bbbb" + }), + }, + { + yaml: ` +apiVersion: controller.config.cert-manager.io/v1alpha1 +kind: ControllerConfiguration +logging: + verbosity: 2 + format: text +`, + args: func(tempFilePath string) []string { + return []string{"--config=" + tempFilePath} + }, + expConfig: configFromDefaults(func(tempDir string, cc *config.ControllerConfiguration) { + cc.Logging.Verbosity = 2 + cc.Logging.Format = "text" + }), + }, + { + yaml: ` +apiVersion: controller.config.cert-manager.io/v1alpha1 +kind: ControllerConfiguration +ingressShimConfig: {} +`, + args: func(tempFilePath string) []string { + return []string{"--config=" + tempFilePath, "--extra-certificate-annotations", "venafi.cert-manager.io/custom-fields"} + }, + expConfig: configFromDefaults(func(tempDir string, cc *config.ControllerConfiguration) { + cc.IngressShimConfig.ExtraCertificateAnnotations = []string{"venafi.cert-manager.io/custom-fields"} + }), + }, + } + + for i, tc := range tests { + t.Run(fmt.Sprintf("test-%d", i), func(t *testing.T) { + tempDir := t.TempDir() + + config, err := testCmdCommand(t, tempDir, tc.yaml, tc.args) + if tc.expError != (err != nil) { + if err == nil { + t.Error("expected error, got nil") + } else { + t.Errorf("unexpected error: %v", err) + } + } else if !tc.expError { + expConfig := tc.expConfig(tempDir) + if !reflect.DeepEqual(config, expConfig) { + t.Errorf("expected config %v but got %v", expConfig, config) + } + } + }) + } +} diff --git a/cmd/controller/go.mod b/cmd/controller/go.mod new file mode 100644 index 00000000000..b724fec0230 --- /dev/null +++ b/cmd/controller/go.mod @@ -0,0 +1,179 @@ +module github.com/cert-manager/cert-manager/controller-binary + +go 1.25.0 + +// Do not remove this comment: +// please place any replace statements here at the top for visibility and add a +// comment to it as to when it can be removed + +replace github.com/cert-manager/cert-manager => ../../ + +require ( + github.com/cert-manager/cert-manager v0.0.0-00010101000000-000000000000 + github.com/go-logr/logr v1.4.3 + github.com/spf13/cobra v1.10.1 + github.com/spf13/pflag v1.0.10 + golang.org/x/sync v0.17.0 + k8s.io/apimachinery v0.34.1 + k8s.io/client-go v0.34.1 + k8s.io/component-base v0.34.1 +) + +require ( + cloud.google.com/go/auth v0.17.0 // indirect + cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect + cloud.google.com/go/compute/metadata v0.9.0 // indirect + github.com/Azure/azure-sdk-for-go/sdk/azcore v1.19.1 // indirect + github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.0 // indirect + github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2 // indirect + github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/dns/armdns v1.2.0 // indirect + github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 // indirect + github.com/AzureAD/microsoft-authentication-library-for-go v1.5.0 // indirect + github.com/Khan/genqlient v0.8.1 // indirect + github.com/Venafi/vcert/v5 v5.12.2 // indirect + github.com/akamai/AkamaiOPEN-edgegrid-golang/v12 v12.1.0 // indirect + github.com/aws/aws-sdk-go-v2 v1.39.4 // indirect + github.com/aws/aws-sdk-go-v2/config v1.31.15 // indirect + github.com/aws/aws-sdk-go-v2/credentials v1.18.19 // indirect + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.11 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.11 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.11 // indirect + github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.2 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.11 // indirect + github.com/aws/aws-sdk-go-v2/service/route53 v1.59.1 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.29.8 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.3 // indirect + github.com/aws/aws-sdk-go-v2/service/sts v1.38.9 // indirect + github.com/aws/smithy-go v1.23.1 // indirect + github.com/benbjohnson/clock v1.3.5 // indirect + github.com/beorn7/perks v1.0.1 // indirect + github.com/blang/semver/v4 v4.0.0 // indirect + github.com/cenkalti/backoff/v4 v4.3.0 // indirect + github.com/cenkalti/backoff/v5 v5.0.3 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/coreos/go-semver v0.3.1 // indirect + github.com/coreos/go-systemd/v22 v22.5.0 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/digitalocean/godo v1.167.0 // indirect + github.com/emicklei/go-restful/v3 v3.13.0 // indirect + github.com/felixge/httpsnoop v1.0.4 // indirect + github.com/fxamacker/cbor/v2 v2.9.0 // indirect + github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667 // indirect + github.com/go-http-utils/headers v0.0.0-20181008091004-fed159eddc2a // indirect + github.com/go-jose/go-jose/v4 v4.1.2 // indirect + github.com/go-ldap/ldap/v3 v3.4.12 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + github.com/go-logr/zapr v1.3.0 // indirect + github.com/go-openapi/jsonpointer v0.22.1 // indirect + github.com/go-openapi/jsonreference v0.21.2 // indirect + github.com/go-openapi/swag v0.23.1 // indirect + github.com/go-openapi/swag/jsonname v0.25.1 // indirect + github.com/go-ozzo/ozzo-validation/v4 v4.3.0 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang-jwt/jwt/v5 v5.3.0 // indirect + github.com/golang/protobuf v1.5.4 // indirect + github.com/golang/snappy v0.0.4 // indirect + github.com/google/btree v1.1.3 // indirect + github.com/google/certificate-transparency-go v1.3.1 // indirect + github.com/google/gnostic-models v0.7.0 // indirect + github.com/google/go-cmp v0.7.0 // indirect + github.com/google/go-querystring v1.1.0 // indirect + github.com/google/s2a-go v0.1.9 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/googleapis/enterprise-certificate-proxy v0.3.6 // indirect + github.com/googleapis/gax-go/v2 v2.15.0 // indirect + github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 // indirect + github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1 // indirect + github.com/hashicorp/errwrap v1.1.0 // indirect + github.com/hashicorp/go-cleanhttp v0.5.2 // indirect + github.com/hashicorp/go-hmac-drbg v0.0.0-20210916214228-a6e5a68489f6 // indirect + github.com/hashicorp/go-multierror v1.1.1 // indirect + github.com/hashicorp/go-retryablehttp v0.7.8 // indirect + github.com/hashicorp/go-rootcerts v1.0.2 // indirect + github.com/hashicorp/go-secure-stdlib/cryptoutil v0.1.1 // indirect + github.com/hashicorp/go-secure-stdlib/parseutil v0.2.0 // indirect + github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 // indirect + github.com/hashicorp/go-sockaddr v1.0.7 // indirect + github.com/hashicorp/hcl v1.0.1-vault-7 // indirect + github.com/hashicorp/vault/api v1.22.0 // indirect + github.com/hashicorp/vault/sdk v0.20.0 // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/josharian/intern v1.0.0 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/kylelemons/godebug v1.1.0 // indirect + github.com/mailru/easyjson v0.9.0 // indirect + github.com/miekg/dns v1.1.68 // indirect + github.com/mitchellh/go-homedir v1.1.0 // indirect + github.com/mitchellh/mapstructure v1.5.0 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/nrdcg/goacmedns v0.2.0 // indirect + github.com/pavlo-v-chernykh/keystore-go/v4 v4.5.0 // indirect + github.com/pierrec/lz4 v2.6.1+incompatible // indirect + github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + github.com/prometheus/client_golang v1.23.2 // indirect + github.com/prometheus/client_model v0.6.2 // indirect + github.com/prometheus/common v0.66.1 // indirect + github.com/prometheus/procfs v0.17.0 // indirect + github.com/ryanuber/go-glob v1.0.0 // indirect + github.com/sosodev/duration v1.3.1 // indirect + github.com/stretchr/objx v0.5.2 // indirect + github.com/stretchr/testify v1.11.1 // indirect + github.com/vektah/gqlparser/v2 v2.5.30 // indirect + github.com/x448/float16 v0.8.4 // indirect + github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect + go.etcd.io/etcd/api/v3 v3.6.4 // indirect + go.etcd.io/etcd/client/pkg/v3 v3.6.4 // indirect + go.etcd.io/etcd/client/v3 v3.6.4 // indirect + go.opentelemetry.io/auto/sdk v1.1.0 // indirect + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 // indirect + go.opentelemetry.io/otel v1.37.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.37.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.37.0 // indirect + go.opentelemetry.io/otel/metric v1.37.0 // indirect + go.opentelemetry.io/otel/sdk v1.37.0 // indirect + go.opentelemetry.io/otel/trace v1.37.0 // indirect + go.opentelemetry.io/proto/otlp v1.7.0 // indirect + go.uber.org/multierr v1.11.0 // indirect + go.uber.org/ratelimit v0.3.1 // indirect + go.uber.org/zap v1.27.0 // indirect + go.yaml.in/yaml/v2 v2.4.2 // indirect + go.yaml.in/yaml/v3 v3.0.4 // indirect + golang.org/x/crypto v0.43.0 // indirect + golang.org/x/mod v0.28.0 // indirect + golang.org/x/net v0.46.0 // indirect + golang.org/x/oauth2 v0.32.0 // indirect + golang.org/x/sys v0.37.0 // indirect + golang.org/x/term v0.36.0 // indirect + golang.org/x/text v0.30.0 // indirect + golang.org/x/time v0.14.0 // indirect + golang.org/x/tools v0.37.0 // indirect + google.golang.org/api v0.253.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20250804133106-a7a43d27e69b // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20251014184007-4626949a642f // indirect + google.golang.org/grpc v1.76.0 // indirect + google.golang.org/protobuf v1.36.10 // indirect + gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect + gopkg.in/inf.v0 v0.9.1 // indirect + gopkg.in/ini.v1 v1.67.0 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect + k8s.io/api v0.34.1 // indirect + k8s.io/apiextensions-apiserver v0.34.1 // indirect + k8s.io/apiserver v0.34.1 // indirect + k8s.io/klog/v2 v2.130.1 // indirect + k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 // indirect + k8s.io/utils v0.0.0-20250820121507-0af2bda4dd1d // indirect + sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.33.0 // indirect + sigs.k8s.io/gateway-api v1.4.0 // indirect + sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect + sigs.k8s.io/randfill v1.0.0 // indirect + sigs.k8s.io/structured-merge-diff/v6 v6.3.0 // indirect + sigs.k8s.io/yaml v1.6.0 // indirect + software.sslmate.com/src/go-pkcs12 v0.6.0 // indirect +) diff --git a/cmd/controller/go.sum b/cmd/controller/go.sum new file mode 100644 index 00000000000..aede19ec984 --- /dev/null +++ b/cmd/controller/go.sum @@ -0,0 +1,487 @@ +cloud.google.com/go/auth v0.17.0 h1:74yCm7hCj2rUyyAocqnFzsAYXgJhrG26XCFimrc/Kz4= +cloud.google.com/go/auth v0.17.0/go.mod h1:6wv/t5/6rOPAX4fJiRjKkJCvswLwdet7G8+UGXt7nCQ= +cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc= +cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c= +cloud.google.com/go/compute/metadata v0.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdBtwLoEkH9Zs= +cloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10= +github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 h1:He8afgbRMd7mFxO99hRNu+6tazq8nFF9lIwo9JFroBk= +github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.19.1 h1:5YTBM8QDVIBN3sxBil89WfdAAqDZbyJTgh688DSxX5w= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.19.1/go.mod h1:YD5h/ldMsG0XiIw7PdyNhLxaM317eFh5yNLccNfGdyw= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.0 h1:KpMC6LFL7mqpExyMC9jVOYRiVhLmamjeZfRsUpB7l4s= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.0/go.mod h1:J7MUC/wtRpfGVbQ5sIItY5/FuVWmvzlY21WAOfQnq/I= +github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.2 h1:yz1bePFlP5Vws5+8ez6T3HWXPmwOK7Yvq8QxDBD3SKY= +github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.2/go.mod h1:Pa9ZNPuoNu/GztvBSKk9J1cDJW6vk/n0zLtV4mgd8N8= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2 h1:9iefClla7iYpfYWdzPCRDozdmndjTm8DXdpCzPajMgA= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2/go.mod h1:XtLgD3ZD34DAaVIIAyG3objl5DynM3CQ/vMcbBNJZGI= +github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/dns/armdns v1.2.0 h1:lpOxwrQ919lCZoNCd69rVt8u1eLZuMORrGXqy8sNf3c= +github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/dns/armdns v1.2.0/go.mod h1:fSvRkb8d26z9dbL40Uf/OO6Vo9iExtZK3D0ulRV+8M0= +github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 h1:mFRzDkZVAjdal+s7s0MwaRv9igoPqLRdzOLzw/8Xvq8= +github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU= +github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1 h1:WJTmL004Abzc5wDB5VtZG2PJk5ndYDgVacGqfirKxjM= +github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1/go.mod h1:tCcJZ0uHAmvjsVYzEFivsRTN00oz5BEsRgQHu5JZ9WE= +github.com/AzureAD/microsoft-authentication-library-for-go v1.5.0 h1:XkkQbfMyuH2jTSjQjSoihryI8GINRcs4xp8lNawg0FI= +github.com/AzureAD/microsoft-authentication-library-for-go v1.5.0/go.mod h1:HKpQxkWaGLJ+D/5H8QRpyQXA1eKjxkFlOMwck5+33Jk= +github.com/Khan/genqlient v0.8.1 h1:wtOCc8N9rNynRLXN3k3CnfzheCUNKBcvXmVv5zt6WCs= +github.com/Khan/genqlient v0.8.1/go.mod h1:R2G6DzjBvCbhjsEajfRjbWdVglSH/73kSivC9TLWVjU= +github.com/Venafi/vcert/v5 v5.12.2 h1:Ee3/A9fZRiisuwuz22/Nqgl19H0ztQjWv35AC63qPcA= +github.com/Venafi/vcert/v5 v5.12.2/go.mod h1:x3l0pB0q0E6wuhPe7nzfkUEwwraK7amnBWQ4LtT1bbw= +github.com/akamai/AkamaiOPEN-edgegrid-golang/v12 v12.1.0 h1:feVgyeLunm1eepTK9urvVpyhXCgEuSnfUxyYfMCtD0g= +github.com/akamai/AkamaiOPEN-edgegrid-golang/v12 v12.1.0/go.mod h1:Bf6hnZkloZnfL4I/gFGnMMMdMHiu/ERnSOWtFgnodDk= +github.com/alexbrainman/sspi v0.0.0-20250919150558-7d374ff0d59e h1:4dAU9FXIyQktpoUAgOJK3OTFc/xug0PCXYCqU0FgDKI= +github.com/alexbrainman/sspi v0.0.0-20250919150558-7d374ff0d59e/go.mod h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4= +github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883 h1:bvNMNQO63//z+xNgfBlViaCIJKLlCJ6/fmUseuG0wVQ= +github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= +github.com/asaskevich/govalidator v0.0.0-20200108200545-475eaeb16496/go.mod h1:oGkLhpf+kjZl6xBf758TQhh5XrAeiJv/7FRz/2spLIg= +github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so= +github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= +github.com/aws/aws-sdk-go-v2 v1.39.4 h1:qTsQKcdQPHnfGYBBs+Btl8QwxJeoWcOcPcixK90mRhg= +github.com/aws/aws-sdk-go-v2 v1.39.4/go.mod h1:yWSxrnioGUZ4WVv9TgMrNUeLV3PFESn/v+6T/Su8gnM= +github.com/aws/aws-sdk-go-v2/config v1.31.15 h1:gE3M4xuNXfC/9bG4hyowGm/35uQTi7bUKeYs5e/6uvU= +github.com/aws/aws-sdk-go-v2/config v1.31.15/go.mod h1:HvnvGJoE2I95KAIW8kkWVPJ4XhdrlvwJpV6pEzFQa8o= +github.com/aws/aws-sdk-go-v2/credentials v1.18.19 h1:Jc1zzwkSY1QbkEcLujwqRTXOdvW8ppND3jRBb/VhBQc= +github.com/aws/aws-sdk-go-v2/credentials v1.18.19/go.mod h1:DIfQ9fAk5H0pGtnqfqkbSIzky82qYnGvh06ASQXXg6A= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.11 h1:X7X4YKb+c0rkI6d4uJ5tEMxXgCZ+jZ/D6mvkno8c8Uw= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.11/go.mod h1:EqM6vPZQsZHYvC4Cai35UDg/f5NCEU+vp0WfbVqVcZc= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.11 h1:7AANQZkF3ihM8fbdftpjhken0TP9sBzFbV/Ze/Y4HXA= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.11/go.mod h1:NTF4QCGkm6fzVwncpkFQqoquQyOolcyXfbpC98urj+c= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.11 h1:ShdtWUZT37LCAA4Mw2kJAJtzaszfSHFb5n25sdcv4YE= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.11/go.mod h1:7bUb2sSr2MZ3M/N+VyETLTQtInemHXb/Fl3s8CLzm0Y= +github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4 h1:WKuaxf++XKWlHWu9ECbMlha8WOEGm0OUEZqm4K/Gcfk= +github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4/go.mod h1:ZWy7j6v1vWGmPReu0iSGvRiise4YI5SkR3OHKTZ6Wuc= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.2 h1:xtuxji5CS0JknaXoACOunXOYOQzgfTvGAc9s2QdCJA4= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.2/go.mod h1:zxwi0DIR0rcRcgdbl7E2MSOvxDyyXGBlScvBkARFaLQ= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.11 h1:GpMf3z2KJa4RnJ0ew3Hac+hRFYLZ9DDjfgXjuW+pB54= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.11/go.mod h1:6MZP3ZI4QQsgUCFTwMZA2V0sEriNQ8k2hmoHF3qjimQ= +github.com/aws/aws-sdk-go-v2/service/route53 v1.59.1 h1:KuoA/cmy/yK8n9v/d6WH36cZwGxFOrn0TmZ4lNN3MKQ= +github.com/aws/aws-sdk-go-v2/service/route53 v1.59.1/go.mod h1:BymbICXBfXQHO6i+yTBhocA9a6DM0uMDQqYelqa9wzs= +github.com/aws/aws-sdk-go-v2/service/sso v1.29.8 h1:M5nimZmugcZUO9wG7iVtROxPhiqyZX6ejS1lxlDPbTU= +github.com/aws/aws-sdk-go-v2/service/sso v1.29.8/go.mod h1:mbef/pgKhtKRwrigPPs7SSSKZgytzP8PQ6P6JAAdqyM= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.3 h1:S5GuJZpYxE0lKeMHKn+BRTz6PTFpgThyJ+5mYfux7BM= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.3/go.mod h1:X4OF+BTd7HIb3L+tc4UlWHVrpgwZZIVENU15pRDVTI0= +github.com/aws/aws-sdk-go-v2/service/sts v1.38.9 h1:Ekml5vGg6sHSZLZJQJagefnVe6PmqC2oiRkBq4F7fU0= +github.com/aws/aws-sdk-go-v2/service/sts v1.38.9/go.mod h1:/e15V+o1zFHWdH3u7lpI3rVBcxszktIKuHKCY2/py+k= +github.com/aws/smithy-go v1.23.1 h1:sLvcH6dfAFwGkHLZ7dGiYF7aK6mg4CgKA/iDKjLDt9M= +github.com/aws/smithy-go v1.23.1/go.mod h1:LEj2LM3rBRQJxPZTB4KuzZkaZYnZPnvgIhb4pu07mx0= +github.com/benbjohnson/clock v1.3.5 h1:VvXlSJBzZpA/zum6Sj74hxwYI2DIxRWuNIoXAzHZz5o= +github.com/benbjohnson/clock v1.3.5/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= +github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= +github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= +github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM= +github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr4= +github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03VsM8rvUec= +github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= +github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/digitalocean/godo v1.167.0 h1:/KHyVKBkUNT7oiZLPcUL45rNrxeQ2t0JdzreqbUI+Jw= +github.com/digitalocean/godo v1.167.0/go.mod h1:xQsWpVCCbkDrWisHA72hPzPlnC+4W5w/McZY5ij9uvU= +github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= +github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= +github.com/emicklei/go-restful/v3 v3.13.0 h1:C4Bl2xDndpU6nJ4bc1jXd+uTmYPVUwkD6bFY/oTyCes= +github.com/emicklei/go-restful/v3 v3.13.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/evanphx/json-patch v5.9.0+incompatible h1:fBXyNpNMuTTDdquAq/uisOr2lShz4oaXpDTX2bLe7ls= +github.com/evanphx/json-patch/v5 v5.9.11 h1:/8HVnzMq13/3x9TPvjG08wUGqBTmZBsCWzjTM0wiaDU= +github.com/evanphx/json-patch/v5 v5.9.11/go.mod h1:3j+LviiESTElxA4p3EMKAB9HXj3/XEtnUf6OZxqIQTM= +github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= +github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= +github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo= +github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= +github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= +github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= +github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= +github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= +github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= +github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM= +github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= +github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667 h1:BP4M0CvQ4S3TGls2FvczZtj5Re/2ZzkV9VwqPHH/3Bo= +github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= +github.com/go-http-utils/headers v0.0.0-20181008091004-fed159eddc2a h1:v6zMvHuY9yue4+QkG/HQ/W67wvtQmWJ4SDo9aK/GIno= +github.com/go-http-utils/headers v0.0.0-20181008091004-fed159eddc2a/go.mod h1:I79BieaU4fxrw4LMXby6q5OS9XnoR9UIKLOzDFjUmuw= +github.com/go-jose/go-jose/v4 v4.1.2 h1:TK/7NqRQZfgAh+Td8AlsrvtPoUyiHh0LqVvokh+1vHI= +github.com/go-jose/go-jose/v4 v4.1.2/go.mod h1:22cg9HWM1pOlnRiY+9cQYJ9XHmya1bYW8OeDM6Ku6Oo= +github.com/go-ldap/ldap/v3 v3.4.12 h1:1b81mv7MagXZ7+1r7cLTWmyuTqVqdwbtJSjC0DAp9s4= +github.com/go-ldap/ldap/v3 v3.4.12/go.mod h1:+SPAGcTtOfmGsCb3h1RFiq4xpp4N636G75OEace8lNo= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= +github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ= +github.com/go-logr/zapr v1.3.0/go.mod h1:YKepepNBd1u/oyhd/yQmtjVXmm9uML4IXUgMOwR8/Gg= +github.com/go-openapi/jsonpointer v0.22.1 h1:sHYI1He3b9NqJ4wXLoJDKmUmHkWy/L7rtEo92JUxBNk= +github.com/go-openapi/jsonpointer v0.22.1/go.mod h1:pQT9OsLkfz1yWoMgYFy4x3U5GY5nUlsOn1qSBH5MkCM= +github.com/go-openapi/jsonreference v0.21.2 h1:Wxjda4M/BBQllegefXrY/9aq1fxBA8sI5M/lFU6tSWU= +github.com/go-openapi/jsonreference v0.21.2/go.mod h1:pp3PEjIsJ9CZDGCNOyXIQxsNuroxm8FAJ/+quA0yKzQ= +github.com/go-openapi/swag v0.23.1 h1:lpsStH0n2ittzTnbaSloVZLuB5+fvSY/+hnagBjSNZU= +github.com/go-openapi/swag v0.23.1/go.mod h1:STZs8TbRvEQQKUA+JZNAm3EWlgaOBGpyFDqQnDHMef0= +github.com/go-openapi/swag/jsonname v0.25.1 h1:Sgx+qbwa4ej6AomWC6pEfXrA6uP2RkaNjA9BR8a1RJU= +github.com/go-openapi/swag/jsonname v0.25.1/go.mod h1:71Tekow6UOLBD3wS7XhdT98g5J5GR13NOTQ9/6Q11Zo= +github.com/go-ozzo/ozzo-validation/v4 v4.3.0 h1:byhDUpfEwjsVQb1vBunvIjh2BHQ9ead57VkAEY4V+Es= +github.com/go-ozzo/ozzo-validation/v4 v4.3.0/go.mod h1:2NKgrcHl3z6cJs+3Oo940FPRiTzuqKbvfrL2RxCj6Ew= +github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= +github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= +github.com/go-test/deep v1.1.1 h1:0r/53hagsehfO4bzD2Pgr/+RgHqhmf+k1Bpse2cTu1U= +github.com/go-test/deep v1.1.1/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= +github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo= +github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= +github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg= +github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= +github.com/google/certificate-transparency-go v1.3.1 h1:akbcTfQg0iZlANZLn0L9xOeWtyCIdeoYhKrqi5iH3Go= +github.com/google/certificate-transparency-go v1.3.1/go.mod h1:gg+UQlx6caKEDQ9EElFOujyxEQEfOiQzAt6782Bvi8k= +github.com/google/gnostic-models v0.7.0 h1:qwTtogB15McXDaNqTZdzPJRHvaVJlAl+HVQnLmJEJxo= +github.com/google/gnostic-models v0.7.0/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= +github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db h1:097atOisP2aRj7vFgYQBbFN4U4JNXUNYpxael3UzMyo= +github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= +github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0= +github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/enterprise-certificate-proxy v0.3.6 h1:GW/XbdyBFQ8Qe+YAmFU9uHLo7OnF5tL52HFAgMmyrf4= +github.com/googleapis/enterprise-certificate-proxy v0.3.6/go.mod h1:MkHOF77EYAE7qfSuSS9PU6g4Nt4e11cnsDUowfwewLA= +github.com/googleapis/gax-go/v2 v2.15.0 h1:SyjDc1mGgZU5LncH8gimWo9lW1DtIfPibOG81vgd/bo= +github.com/googleapis/gax-go/v2 v2.15.0/go.mod h1:zVVkkxAQHa1RQpg9z2AUCMnKhi0Qld9rcmyfL1OZhoc= +github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 h1:JeSE6pjso5THxAzdVpqr6/geYxZytqFMBCOtn/ujyeo= +github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674/go.mod h1:r4w70xmWCQKmi1ONH4KIaBptdivuRPyosB9RmPlGEwA= +github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 h1:UH//fgunKIs4JdUbpDl1VZCDaL56wXCB/5+wF6uHfaI= +github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.0.1 h1:qnpSQwGEnkcRpTqNOIR6bJbR0gAorgP9CSALpRcKoAA= +github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.0.1/go.mod h1:lXGCsh6c22WGtjr+qGHj1otzZpV/1kwTMAqkwZsnWRU= +github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.0 h1:FbSCl+KggFl+Ocym490i/EyXF4lPgLoUtcSWquBM0Rs= +github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.0/go.mod h1:qOchhhIlmRcqk/O9uCo/puJlyo07YINaIqdZfZG3Jkc= +github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92BcuyuQ/YW4NSIpoGtfXNho= +github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1 h1:X5VWvz21y3gzm9Nw/kaUeku/1+uBhcekkmy4IkffJww= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1/go.mod h1:Zanoh4+gvIgluNqcfMVTJueD4wSS5hT7zTt4Mrutd90= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= +github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= +github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= +github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k= +github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= +github.com/hashicorp/go-hmac-drbg v0.0.0-20210916214228-a6e5a68489f6 h1:kBoJV4Xl5FLtBfnBjDvBxeNSy2IRITSGs73HQsFUEjY= +github.com/hashicorp/go-hmac-drbg v0.0.0-20210916214228-a6e5a68489f6/go.mod h1:y+HSOcOGB48PkUxNyLAiCiY6rEENu+E+Ss4LG8QHwf4= +github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= +github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= +github.com/hashicorp/go-retryablehttp v0.7.8 h1:ylXZWnqa7Lhqpk0L1P1LzDtGcCR0rPVUrx/c8Unxc48= +github.com/hashicorp/go-retryablehttp v0.7.8/go.mod h1:rjiScheydd+CxvumBsIrFKlx3iS0jrZ7LvzFGFmuKbw= +github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc= +github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= +github.com/hashicorp/go-secure-stdlib/cryptoutil v0.1.1 h1:VaLXp47MqD1Y2K6QVrA9RooQiPyCgAbnfeJg44wKuJk= +github.com/hashicorp/go-secure-stdlib/cryptoutil v0.1.1/go.mod h1:hH8rgXHh9fPSDPerG6WzABHsHF+9ZpLhRI1LPk4JZ8c= +github.com/hashicorp/go-secure-stdlib/parseutil v0.2.0 h1:U+kC2dOhMFQctRfhK0gRctKAPTloZdMU5ZJxaesJ/VM= +github.com/hashicorp/go-secure-stdlib/parseutil v0.2.0/go.mod h1:Ll013mhdmsVDuoIXVfBtvgGJsXDYkTw1kooNcoCXuE0= +github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 h1:kes8mmyCpxJsI7FTwtzRqEy9CdjCtrXrXGuOpxEA7Ts= +github.com/hashicorp/go-secure-stdlib/strutil v0.1.2/go.mod h1:Gou2R9+il93BqX25LAKCLuM+y9U2T4hlwvT1yprcna4= +github.com/hashicorp/go-sockaddr v1.0.7 h1:G+pTkSO01HpR5qCxg7lxfsFEZaG+C0VssTy/9dbT+Fw= +github.com/hashicorp/go-sockaddr v1.0.7/go.mod h1:FZQbEYa1pxkQ7WLpyXJ6cbjpT8q0YgQaK/JakXqGyWw= +github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= +github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/hcl v1.0.1-vault-7 h1:ag5OxFVy3QYTFTJODRzTKVZ6xvdfLLCA1cy/Y6xGI0I= +github.com/hashicorp/hcl v1.0.1-vault-7/go.mod h1:XYhtn6ijBSAj6n4YqAaf7RBPS4I06AItNorpy+MoQNM= +github.com/hashicorp/vault/api v1.22.0 h1:+HYFquE35/B74fHoIeXlZIP2YADVboaPjaSicHEZiH0= +github.com/hashicorp/vault/api v1.22.0/go.mod h1:IUZA2cDvr4Ok3+NtK2Oq/r+lJeXkeCrHRmqdyWfpmGM= +github.com/hashicorp/vault/sdk v0.20.0 h1:a4ulj2gICzw/qH0A4+6o36qAHxkUdcmgpMaSSjqE3dc= +github.com/hashicorp/vault/sdk v0.20.0/go.mod h1:xEjAt/n/2lHBAkYiRPRmvf1d5B6HlisPh2pELlRCosk= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/jcmturner/aescts/v2 v2.0.0 h1:9YKLH6ey7H4eDBXW8khjYslgyqG2xZikXP0EQFKrle8= +github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs= +github.com/jcmturner/dnsutils/v2 v2.0.0 h1:lltnkeZGL0wILNvrNiVCR6Ro5PGU/SeBvVO/8c/iPbo= +github.com/jcmturner/dnsutils/v2 v2.0.0/go.mod h1:b0TnjGOvI/n42bZa+hmXL+kFJZsFT7G4t3HTlQ184QM= +github.com/jcmturner/gofork v1.7.6 h1:QH0l3hzAU1tfT3rZCnW5zXl+orbkNMMRGJfdJjHVETg= +github.com/jcmturner/gofork v1.7.6/go.mod h1:1622LH6i/EZqLloHfE7IeZ0uEJwMSUyQ/nDd82IeqRo= +github.com/jcmturner/goidentity/v6 v6.0.1 h1:VKnZd2oEIMorCTsFBnJWbExfNN7yZr3EhJAxwOkZg6o= +github.com/jcmturner/goidentity/v6 v6.0.1/go.mod h1:X1YW3bgtvwAXju7V3LCIMpY0Gbxyjn/mY9zx4tFonSg= +github.com/jcmturner/gokrb5/v8 v8.4.4 h1:x1Sv4HaTpepFkXbt2IkL29DXRf8sOfZXo8eRKh687T8= +github.com/jcmturner/gokrb5/v8 v8.4.4/go.mod h1:1btQEpgT6k+unzCwX1KdWMEwPPkkgBtP+F6aCACiMrs= +github.com/jcmturner/rpc/v2 v2.0.3 h1:7FXXj8Ti1IaVFpSAziCZWNzbNuZmnvw/i6CqLNdWfZY= +github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc= +github.com/jonboulle/clockwork v0.5.0 h1:Hyh9A8u51kptdkR+cqRpT1EebBwTn1oK9YfGYbdFz6I= +github.com/jonboulle/clockwork v0.5.0/go.mod h1:3mZlmanh0g2NDKO5TWZVJAfofYk64M7XN3SzBPjZF60= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/keybase/go-keychain v0.0.1 h1:way+bWYa6lDppZoZcgMbYsvC7GxljxrskdNInRtuthU= +github.com/keybase/go-keychain v0.0.1/go.mod h1:PdEILRW3i9D8JcdM+FmY6RwkHGnhHxXwkPPMeUgOK1k= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= +github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4= +github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= +github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= +github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/miekg/dns v1.1.68 h1:jsSRkNozw7G/mnmXULynzMNIsgY2dHC8LO6U6Ij2JEA= +github.com/miekg/dns v1.1.68/go.mod h1:fujopn7TB3Pu3JM69XaawiU0wqjpL9/8xGop5UrTPps= +github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8= +github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/nrdcg/goacmedns v0.2.0 h1:ADMbThobzEMnr6kg2ohs4KGa3LFqmgiBA22/6jUWJR0= +github.com/nrdcg/goacmedns v0.2.0/go.mod h1:T5o6+xvSLrQpugmwHvrSNkzWht0UGAwj2ACBMhh73Cg= +github.com/onsi/ginkgo/v2 v2.21.0 h1:7rg/4f3rB88pb5obDgNZrNHrQ4e6WpjonchcpuBRnZM= +github.com/onsi/ginkgo/v2 v2.21.0/go.mod h1:7Du3c42kxCUegi0IImZ1wUQzMBVecgIHjR1C+NkhLQo= +github.com/onsi/gomega v1.35.1 h1:Cwbd75ZBPxFSuZ6T+rN/WCb/gOc6YgFBXLlZLhC7Ds4= +github.com/onsi/gomega v1.35.1/go.mod h1:PvZbdDc8J6XJEpDK4HCuRBm8a6Fzp9/DmhC9C7yFlog= +github.com/pavlo-v-chernykh/keystore-go/v4 v4.5.0 h1:2nosf3P75OZv2/ZO/9Px5ZgZ5gbKrzA3joN1QMfOGMQ= +github.com/pavlo-v-chernykh/keystore-go/v4 v4.5.0/go.mod h1:lAVhWwbNaveeJmxrxuSTxMgKpF6DjnuVpn6T8WiBwYQ= +github.com/pierrec/lz4 v2.6.1+incompatible h1:9UY3+iC23yxF0UfGaYrGplQ+79Rg+h/q9FV9ix19jjM= +github.com/pierrec/lz4 v2.6.1+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= +github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ= +github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o= +github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg= +github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= +github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= +github.com/prometheus/common v0.66.1 h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9ZoGs= +github.com/prometheus/common v0.66.1/go.mod h1:gcaUsgf3KfRSwHY4dIMXLPV0K/Wg1oZ8+SbZk/HH/dA= +github.com/prometheus/procfs v0.17.0 h1:FuLQ+05u4ZI+SS/w9+BWEM2TXiHKsUQ9TADiRH7DuK0= +github.com/prometheus/procfs v0.17.0/go.mod h1:oPQLaDAMRbA+u8H5Pbfq+dl3VDAvHxMUOVhe0wYB2zw= +github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= +github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk= +github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc= +github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8= +github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/soheilhy/cmux v0.1.5 h1:jjzc5WVemNEDTLwv9tlmemhC73tI08BNOIGwBOo10Js= +github.com/soheilhy/cmux v0.1.5/go.mod h1:T7TcVDs9LWfQgPlPsdngu6I6QIoyIFZDDC6sNE1GqG0= +github.com/sosodev/duration v1.3.1 h1:qtHBDMQ6lvMQsL15g4aopM4HEfOaYuhWBw3NPTtlqq4= +github.com/sosodev/duration v1.3.1/go.mod h1:RQIBBX0+fMLc/D9+Jb/fwvVmo0eZvDDEERAikUR6SDg= +github.com/spf13/cobra v1.10.1 h1:lJeBwCfmrnXthfAupyUTzJ/J4Nc1RsHC/mSRU2dll/s= +github.com/spf13/cobra v1.10.1/go.mod h1:7SmJGaTHFVBY0jW4NXGluQoLvhqFQM+6XSKD+P4XaB0= +github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= +github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/tmc/grpc-websocket-proxy v0.0.0-20220101234140-673ab2c3ae75 h1:6fotK7otjonDflCTK0BCfls4SPy3NcCVb5dqqmbRknE= +github.com/tmc/grpc-websocket-proxy v0.0.0-20220101234140-673ab2c3ae75/go.mod h1:KO6IkyS8Y3j8OdNO85qEYBsRPuteD+YciPomcXdrMnk= +github.com/vektah/gqlparser/v2 v2.5.30 h1:EqLwGAFLIzt1wpx1IPpY67DwUujF1OfzgEyDsLrN6kE= +github.com/vektah/gqlparser/v2 v2.5.30/go.mod h1:D1/VCZtV3LPnQrcPBeR/q5jkSQIPti0uYCP/RI0gIeo= +github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= +github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= +github.com/xiang90/probing v0.0.0-20221125231312-a49e3df8f510 h1:S2dVYn90KE98chqDkyE9Z4N61UnQd+KOfgp5Iu53llk= +github.com/xiang90/probing v0.0.0-20221125231312-a49e3df8f510/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= +github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 h1:ilQV1hzziu+LLM3zUTJ0trRztfwgjqKnBWNtSRkbmwM= +github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78/go.mod h1:aL8wCCfTfSfmXjznFBSZNN13rSJjlIOI1fUNAtF7rmI= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +go.etcd.io/bbolt v1.4.2 h1:IrUHp260R8c+zYx/Tm8QZr04CX+qWS5PGfPdevhdm1I= +go.etcd.io/bbolt v1.4.2/go.mod h1:Is8rSHO/b4f3XigBC0lL0+4FwAQv3HXEEIgFMuKHceM= +go.etcd.io/etcd/api/v3 v3.6.4 h1:7F6N7toCKcV72QmoUKa23yYLiiljMrT4xCeBL9BmXdo= +go.etcd.io/etcd/api/v3 v3.6.4/go.mod h1:eFhhvfR8Px1P6SEuLT600v+vrhdDTdcfMzmnxVXXSbk= +go.etcd.io/etcd/client/pkg/v3 v3.6.4 h1:9HBYrjppeOfFjBjaMTRxT3R7xT0GLK8EJMVC4xg6ok0= +go.etcd.io/etcd/client/pkg/v3 v3.6.4/go.mod h1:sbdzr2cl3HzVmxNw//PH7aLGVtY4QySjQFuaCgcRFAI= +go.etcd.io/etcd/client/v3 v3.6.4 h1:YOMrCfMhRzY8NgtzUsHl8hC2EBSnuqbR3dh84Uryl7A= +go.etcd.io/etcd/client/v3 v3.6.4/go.mod h1:jaNNHCyg2FdALyKWnd7hxZXZxZANb0+KGY+YQaEMISo= +go.etcd.io/etcd/pkg/v3 v3.6.4 h1:fy8bmXIec1Q35/jRZ0KOes8vuFxbvdN0aAFqmEfJZWA= +go.etcd.io/etcd/pkg/v3 v3.6.4/go.mod h1:kKcYWP8gHuBRcteyv6MXWSN0+bVMnfgqiHueIZnKMtE= +go.etcd.io/etcd/server/v3 v3.6.4 h1:LsCA7CzjVt+8WGrdsnh6RhC0XqCsLkBly3ve5rTxMAU= +go.etcd.io/etcd/server/v3 v3.6.4/go.mod h1:aYCL/h43yiONOv0QIR82kH/2xZ7m+IWYjzRmyQfnCAg= +go.etcd.io/raft/v3 v3.6.0 h1:5NtvbDVYpnfZWcIHgGRk9DyzkBIXOi8j+DDp1IcnUWQ= +go.etcd.io/raft/v3 v3.6.0/go.mod h1:nLvLevg6+xrVtHUmVaTcTz603gQPHfh7kUAwV6YpfGo= +go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= +go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 h1:q4XOmH/0opmeuJtPsbFNivyl7bCt7yRBbeEm2sC/XtQ= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0/go.mod h1:snMWehoOh2wsEwnvvwtDyFCxVeDAODenXHtn5vzrKjo= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 h1:F7Jx+6hwnZ41NSFTO5q4LYDtJRXBf2PD0rNBkeB/lus= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0/go.mod h1:UHB22Z8QsdRDrnAtX4PntOl36ajSxcdUMt1sF7Y6E7Q= +go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ= +go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.37.0 h1:Ahq7pZmv87yiyn3jeFz/LekZmPLLdKejuO3NcK9MssM= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.37.0/go.mod h1:MJTqhM0im3mRLw1i8uGHnCvUEeS7VwRyxlLC78PA18M= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.37.0 h1:EtFWSnwW9hGObjkIdmlnWSydO+Qs8OwzfzXLUPg4xOc= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.37.0/go.mod h1:QjUEoiGCPkvFZ/MjK6ZZfNOS6mfVEVKYE99dFhuN2LI= +go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE= +go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E= +go.opentelemetry.io/otel/sdk v1.37.0 h1:ItB0QUqnjesGRvNcmAcU0LyvkVyGJ2xftD29bWdDvKI= +go.opentelemetry.io/otel/sdk v1.37.0/go.mod h1:VredYzxUvuo2q3WRcDnKDjbdvmO0sCzOvVAiY+yUkAg= +go.opentelemetry.io/otel/sdk/metric v1.37.0 h1:90lI228XrB9jCMuSdA0673aubgRobVZFhbjxHHspCPc= +go.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps= +go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4= +go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0= +go.opentelemetry.io/proto/otlp v1.7.0 h1:jX1VolD6nHuFzOYso2E73H85i92Mv8JQYk0K9vz09os= +go.opentelemetry.io/proto/otlp v1.7.0/go.mod h1:fSKjH6YJ7HDlwzltzyMj036AJ3ejJLCgCSHGj4efDDo= +go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= +go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y= +go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/ratelimit v0.3.1 h1:K4qVE+byfv/B3tC+4nYWP7v/6SimcO7HzHekoMNBma0= +go.uber.org/ratelimit v0.3.1/go.mod h1:6euWsTB6U/Nb3X++xEUXA8ciPJvr19Q/0h1+oDcJhRk= +go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= +go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= +go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI= +go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU= +go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= +go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04= +golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.28.0 h1:gQBtGhjxykdjY9YhZpSlZIsbnaE2+PgjfLWUQTnoZ1U= +golang.org/x/mod v0.28.0/go.mod h1:yfB/L0NOf/kmEbXjzCPOx1iK1fRutOydrCMsqRhEBxI= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.46.0 h1:giFlY12I07fugqwPuWJi68oOnpfqFnJIJzaIIm2JVV4= +golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210= +golang.org/x/oauth2 v0.32.0 h1:jsCblLleRMDrxMN29H3z/k1KliIvpLgCkE6R8FXXNgY= +golang.org/x/oauth2 v0.32.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug= +golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ= +golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/term v0.36.0 h1:zMPR+aF8gfksFprF/Nc/rd1wRS1EI6nDBGyWAvDzx2Q= +golang.org/x/term v0.36.0/go.mod h1:Qu394IJq6V6dCBRgwqshf3mPF85AqzYEzofzRdZkWss= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k= +golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM= +golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI= +golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.37.0 h1:DVSRzp7FwePZW356yEAChSdNcQo6Nsp+fex1SUW09lE= +golang.org/x/tools v0.37.0/go.mod h1:MBN5QPQtLMHVdvsbtarmTNukZDdgwdwlO5qGacAzF0w= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gomodules.xyz/jsonpatch/v2 v2.5.0 h1:JELs8RLM12qJGXU4u/TO3V25KW8GreMKl9pdkk14RM0= +gomodules.xyz/jsonpatch/v2 v2.5.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY= +gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= +gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= +google.golang.org/api v0.253.0 h1:apU86Eq9Q2eQco3NsUYFpVTfy7DwemojL7LmbAj7g/I= +google.golang.org/api v0.253.0/go.mod h1:PX09ad0r/4du83vZVAaGg7OaeyGnaUmT/CYPNvtLCbw= +google.golang.org/genproto v0.0.0-20250603155806-513f23925822 h1:rHWScKit0gvAPuOnu87KpaYtjK5zBMLcULh7gxkCXu4= +google.golang.org/genproto v0.0.0-20250603155806-513f23925822/go.mod h1:HubltRL7rMh0LfnQPkMH4NPDFEWp0jw3vixw7jEM53s= +google.golang.org/genproto/googleapis/api v0.0.0-20250804133106-a7a43d27e69b h1:ULiyYQ0FdsJhwwZUwbaXpZF5yUE3h+RA+gxvBu37ucc= +google.golang.org/genproto/googleapis/api v0.0.0-20250804133106-a7a43d27e69b/go.mod h1:oDOGiMSXHL4sDTJvFvIB9nRQCGdLP1o/iVaqQK8zB+M= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251014184007-4626949a642f h1:1FTH6cpXFsENbPR5Bu8NQddPSaUUE6NA2XdZdDSAJK4= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251014184007-4626949a642f/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= +google.golang.org/grpc v1.76.0 h1:UnVkv1+uMLYXoIz6o7chp59WfQUYA2ex/BXQ9rHZu7A= +google.golang.org/grpc v1.76.0/go.mod h1:Ju12QI8M6iQJtbcsV+awF5a4hfJMLi4X0JLo94ULZ6c= +google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= +google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/evanphx/json-patch.v4 v4.13.0 h1:czT3CmqEaQ1aanPc5SdlgQrrEIb8w/wwCvWWnfEbYzo= +gopkg.in/evanphx/json-patch.v4 v4.13.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= +gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= +gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= +gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc= +gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +k8s.io/api v0.34.1 h1:jC+153630BMdlFukegoEL8E/yT7aLyQkIVuwhmwDgJM= +k8s.io/api v0.34.1/go.mod h1:SB80FxFtXn5/gwzCoN6QCtPD7Vbu5w2n1S0J5gFfTYk= +k8s.io/apiextensions-apiserver v0.34.1 h1:NNPBva8FNAPt1iSVwIE0FsdrVriRXMsaWFMqJbII2CI= +k8s.io/apiextensions-apiserver v0.34.1/go.mod h1:hP9Rld3zF5Ay2Of3BeEpLAToP+l4s5UlxiHfqRaRcMc= +k8s.io/apimachinery v0.34.1 h1:dTlxFls/eikpJxmAC7MVE8oOeP1zryV7iRyIjB0gky4= +k8s.io/apimachinery v0.34.1/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw= +k8s.io/apiserver v0.34.1 h1:U3JBGdgANK3dfFcyknWde1G6X1F4bg7PXuvlqt8lITA= +k8s.io/apiserver v0.34.1/go.mod h1:eOOc9nrVqlBI1AFCvVzsob0OxtPZUCPiUJL45JOTBG0= +k8s.io/client-go v0.34.1 h1:ZUPJKgXsnKwVwmKKdPfw4tB58+7/Ik3CrjOEhsiZ7mY= +k8s.io/client-go v0.34.1/go.mod h1:kA8v0FP+tk6sZA0yKLRG67LWjqufAoSHA2xVGKw9Of8= +k8s.io/component-base v0.34.1 h1:v7xFgG+ONhytZNFpIz5/kecwD+sUhVE6HU7qQUiRM4A= +k8s.io/component-base v0.34.1/go.mod h1:mknCpLlTSKHzAQJJnnHVKqjxR7gBeHRv0rPXA7gdtQ0= +k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= +k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= +k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 h1:Y3gxNAuB0OBLImH611+UDZcmKS3g6CthxToOb37KgwE= +k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912/go.mod h1:kdmbQkyfwUagLfXIad1y2TdrjPFWp2Q89B3qkRwf/pQ= +k8s.io/utils v0.0.0-20250820121507-0af2bda4dd1d h1:wAhiDyZ4Tdtt7e46e9M5ZSAJ/MnPGPs+Ki1gHw4w1R0= +k8s.io/utils v0.0.0-20250820121507-0af2bda4dd1d/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.33.0 h1:qPrZsv1cwQiFeieFlRqT627fVZ+tyfou/+S5S0H5ua0= +sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.33.0/go.mod h1:Ve9uj1L+deCXFrPOk1LpFXqTg7LCFzFso6PA48q/XZw= +sigs.k8s.io/controller-runtime v0.22.3 h1:I7mfqz/a/WdmDCEnXmSPm8/b/yRTy6JsKKENTijTq8Y= +sigs.k8s.io/controller-runtime v0.22.3/go.mod h1:+QX1XUpTXN4mLoblf4tqr5CQcyHPAki2HLXqQMY6vh8= +sigs.k8s.io/gateway-api v1.4.0 h1:ZwlNM6zOHq0h3WUX2gfByPs2yAEsy/EenYJB78jpQfQ= +sigs.k8s.io/gateway-api v1.4.0/go.mod h1:AR5RSqciWP98OPckEjOjh2XJhAe2Na4LHyXD2FUY7Qk= +sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg= +sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= +sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= +sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= +sigs.k8s.io/structured-merge-diff/v6 v6.3.0 h1:jTijUJbW353oVOd9oTlifJqOGEkUw2jB/fXCbTiQEco= +sigs.k8s.io/structured-merge-diff/v6 v6.3.0/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE= +sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs= +sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4= +software.sslmate.com/src/go-pkcs12 v0.6.0 h1:f3sQittAeF+pao32Vb+mkli+ZyT+VwKaD014qFGq6oU= +software.sslmate.com/src/go-pkcs12 v0.6.0/go.mod h1:Qiz0EyvDRJjjxGyUQa2cCNZn/wMyzrRJ/qcDXOQazLI= diff --git a/cmd/controller/main.go b/cmd/controller/main.go index 89577e78180..d5cf2c22f89 100644 --- a/cmd/controller/main.go +++ b/cmd/controller/main.go @@ -17,26 +17,27 @@ limitations under the License. package main import ( + "context" "flag" - "github.com/cert-manager/cert-manager/cmd/controller/app" - "github.com/cert-manager/cert-manager/cmd/util" + "github.com/cert-manager/cert-manager/controller-binary/app" + "github.com/cert-manager/cert-manager/internal/cmd/util" logf "github.com/cert-manager/cert-manager/pkg/logs" ) func main() { - stopCh, exit := util.SetupExitHandler(util.GracefulShutdown) + ctx, exit := util.SetupExitHandler(context.Background(), util.GracefulShutdown) defer exit() // This function might call os.Exit, so defer last - logf.InitLogs(flag.CommandLine) + logf.InitLogs() defer logf.FlushLogs() + ctx = logf.NewContext(ctx, logf.Log, "controller") - cmd := app.NewCommandStartCertManagerController(stopCh) + cmd := app.NewServerCommand(ctx) cmd.Flags().AddGoFlagSet(flag.CommandLine) - flag.CommandLine.Parse([]string{}) - if err := cmd.Execute(); err != nil { - logf.Log.Error(err, "error while executing") + if err := cmd.ExecuteContext(ctx); err != nil { + logf.Log.Error(err, "error executing command") util.SetExitCode(err) } } diff --git a/cmd/ctl/cmd/cmd.go b/cmd/ctl/cmd/cmd.go deleted file mode 100644 index 81128106379..00000000000 --- a/cmd/ctl/cmd/cmd.go +++ /dev/null @@ -1,86 +0,0 @@ -/* -Copyright 2020 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. -*/ - -package cmd - -import ( - "context" - "flag" - "fmt" - "io" - "os" - - "github.com/spf13/cobra" - "k8s.io/cli-runtime/pkg/genericclioptions" - "k8s.io/klog/v2" - - "github.com/cert-manager/cert-manager/cmd/ctl/pkg/build" - "github.com/cert-manager/cert-manager/cmd/ctl/pkg/build/commands" -) - -func NewCertManagerCtlCommand(ctx context.Context, in io.Reader, out, err io.Writer) *cobra.Command { - cmds := &cobra.Command{ - Use: build.Name(), - Short: "cert-manager CLI tool to manage and configure cert-manager resources", - Long: build.WithTemplate(` -{{.BuildName}} is a CLI tool manage and configure cert-manager resources for Kubernetes`), - CompletionOptions: cobra.CompletionOptions{ - DisableDefaultCmd: true, - }, - } - cmds.SetUsageTemplate(usageTemplate()) - - cmds.Flags().AddGoFlagSet(flag.CommandLine) - flag.CommandLine.Parse([]string{}) - fakefs := flag.NewFlagSet("fake", flag.ExitOnError) - klog.InitFlags(fakefs) - if err := fakefs.Parse([]string{"-logtostderr=false"}); err != nil { - fmt.Fprintf(os.Stderr, "%s\n", err) - os.Exit(1) - } - - ioStreams := genericclioptions.IOStreams{In: in, Out: out, ErrOut: err} - for _, registerCmd := range commands.Commands() { - cmds.AddCommand(registerCmd(ctx, ioStreams)) - } - - return cmds -} - -func usageTemplate() string { - return fmt.Sprintf(`Usage:{{if .Runnable}} %s {{end}}{{if .HasAvailableSubCommands}} %s [command]{{end}}{{if gt (len .Aliases) 0}} - -Aliases: - {{.NameAndAliases}}{{end}}{{if .HasExample}} - -Examples: -{{.Example}}{{end}}{{if .HasAvailableSubCommands}} - -Available Commands:{{range .Commands}}{{if (or .IsAvailableCommand (eq .Name "help"))}} - {{rpad .Name .NamePadding }} {{.Short}}{{end}}{{end}}{{end}}{{if .HasAvailableLocalFlags}} - -Flags: -{{.LocalFlags.FlagUsages | trimTrailingWhitespaces}}{{end}}{{if .HasAvailableInheritedFlags}} - -Global Flags: -{{.InheritedFlags.FlagUsages | trimTrailingWhitespaces}}{{end}}{{if .HasHelpSubCommands}} - -Additional help topics:{{range .Commands}}{{if .IsAdditionalHelpTopicCommand}} - {{rpad .CommandPath .CommandPathPadding}} {{.Short}}{{end}}{{end}}{{end}}{{if .HasAvailableSubCommands}} - -Use "%s [command] --help" for more information about a command.{{end}} -`, build.Name(), build.Name(), build.Name()) -} diff --git a/cmd/ctl/main.go b/cmd/ctl/main.go deleted file mode 100644 index 6b2939d8c17..00000000000 --- a/cmd/ctl/main.go +++ /dev/null @@ -1,39 +0,0 @@ -/* -Copyright 2020 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. -*/ - -package main - -import ( - "context" - "fmt" - "os" - - ctlcmd "github.com/cert-manager/cert-manager/cmd/ctl/cmd" - "github.com/cert-manager/cert-manager/cmd/util" -) - -func main() { - stopCh, exit := util.SetupExitHandler(util.AlwaysErrCode) - defer exit() // This function might call os.Exit, so defer last - - ctx := util.ContextWithStopCh(context.Background(), stopCh) - cmd := ctlcmd.NewCertManagerCtlCommand(ctx, os.Stdin, os.Stdout, os.Stderr) - - if err := cmd.Execute(); err != nil { - fmt.Fprintf(os.Stderr, "%s\n", err) - util.SetExitCode(err) - } -} diff --git a/cmd/ctl/pkg/approve/approve.go b/cmd/ctl/pkg/approve/approve.go deleted file mode 100644 index e4230966f03..00000000000 --- a/cmd/ctl/pkg/approve/approve.go +++ /dev/null @@ -1,142 +0,0 @@ -/* -Copyright 2021 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. -*/ - -package approve - -import ( - "context" - "errors" - "fmt" - - "github.com/spf13/cobra" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/cli-runtime/pkg/genericclioptions" - cmdutil "k8s.io/kubectl/pkg/cmd/util" - "k8s.io/kubectl/pkg/util/i18n" - "k8s.io/kubectl/pkg/util/templates" - - "github.com/cert-manager/cert-manager/cmd/ctl/pkg/build" - "github.com/cert-manager/cert-manager/cmd/ctl/pkg/factory" - apiutil "github.com/cert-manager/cert-manager/pkg/api/util" - cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" - cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" -) - -var ( - example = templates.Examples(i18n.T(build.WithTemplate(` -# Approve a CertificateRequest with the name 'my-cr' -{{.BuildName}} approve my-cr - -# Approve a CertificateRequest in namespace default -{{.BuildName}} approve my-cr --namespace default - -# Approve a CertificateRequest giving a custom reason and message -{{.BuildName}} approve my-cr --reason "ManualApproval" --reason "Approved by PKI department" -`))) -) - -// Options is a struct to support create certificaterequest command -type Options struct { - // Reason is the string that will be set on the Reason field of the Approved - // condition. - Reason string - // Message is the string that will be set on the Message field of the - // Approved condition. - Message string - - genericclioptions.IOStreams - *factory.Factory -} - -// newOptions returns initialized Options -func newOptions(ioStreams genericclioptions.IOStreams) *Options { - return &Options{ - IOStreams: ioStreams, - } -} - -func NewCmdApprove(ctx context.Context, ioStreams genericclioptions.IOStreams) *cobra.Command { - o := newOptions(ioStreams) - - cmd := &cobra.Command{ - Use: "approve", - Short: "Approve a CertificateRequest", - Long: `Mark a CertificateRequest as Approved, so it may be signed by a configured Issuer.`, - Example: example, - ValidArgsFunction: factory.ValidArgsListCertificateRequests(ctx, &o.Factory), - Run: func(cmd *cobra.Command, args []string) { - cmdutil.CheckErr(o.Validate(args)) - cmdutil.CheckErr(o.Run(ctx, args)) - }, - } - - cmd.Flags().StringVar(&o.Reason, "reason", "KubectlCertManager", - "The reason to give as to what approved this CertificateRequest.") - cmd.Flags().StringVar(&o.Message, "message", fmt.Sprintf("manually approved by %q", build.Name()), - "The message to give as to why this CertificateRequest was approved.") - - o.Factory = factory.New(ctx, cmd) - - return cmd -} - -// Validate validates the provided options -func (o *Options) Validate(args []string) error { - if len(args) < 1 { - return errors.New("the name of the CertificateRequest to approve has to be provided as an argument") - } - if len(args) > 1 { - return errors.New("only one argument can be passed: the name of the CertificateRequest") - } - - if len(o.Reason) == 0 { - return errors.New("a reason must be given as to who approved this CertificateRequest") - } - - if len(o.Message) == 0 { - return errors.New("a message must be given as to why this CertificateRequest is approved") - } - - return nil -} - -// Run executes approve command -func (o *Options) Run(ctx context.Context, args []string) error { - cr, err := o.CMClient.CertmanagerV1().CertificateRequests(o.Namespace).Get(ctx, args[0], metav1.GetOptions{}) - if err != nil { - return err - } - - if apiutil.CertificateRequestIsApproved(cr) { - return errors.New("CertificateRequest is already approved") - } - - if apiutil.CertificateRequestIsDenied(cr) { - return errors.New("CertificateRequest is already denied") - } - - apiutil.SetCertificateRequestCondition(cr, cmapi.CertificateRequestConditionApproved, - cmmeta.ConditionTrue, o.Reason, o.Message) - - _, err = o.CMClient.CertmanagerV1().CertificateRequests(o.Namespace).UpdateStatus(ctx, cr, metav1.UpdateOptions{}) - if err != nil { - return err - } - - fmt.Fprintf(o.Out, "Approved CertificateRequest '%s/%s'\n", cr.Namespace, cr.Name) - - return nil -} diff --git a/cmd/ctl/pkg/approve/approve_test.go b/cmd/ctl/pkg/approve/approve_test.go deleted file mode 100644 index 8c7ed6faa61..00000000000 --- a/cmd/ctl/pkg/approve/approve_test.go +++ /dev/null @@ -1,84 +0,0 @@ -/* -Copyright 2021 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. -*/ - -package approve - -import ( - "testing" -) - -func TestValidate(t *testing.T) { - tests := map[string]struct { - args []string - reason, message string - expErr bool - expErrMsg string - }{ - "CR name not passed as arg throws error": { - args: []string{}, - reason: "", - message: "", - expErr: true, - expErrMsg: "the name of the CertificateRequest to approve has to be provided as an argument", - }, - "multiple CR names passed as arg throws error": { - args: []string{"cr-1", "cr-1"}, - reason: "", - message: "", - expErr: true, - expErrMsg: "only one argument can be passed: the name of the CertificateRequest", - }, - "empty reason given should throw error": { - args: []string{"cr-1"}, - reason: "", - message: "", - expErr: true, - expErrMsg: "a reason must be given as to who approved this CertificateRequest", - }, - "empty message given should throw error": { - args: []string{"cr-1"}, - reason: "foo", - message: "", - expErr: true, - expErrMsg: "a message must be given as to why this CertificateRequest is approved", - }, - "all fields populated should not error": { - args: []string{"cr-1"}, - reason: "foo", - message: "bar", - expErr: false, - }, - } - - for name, test := range tests { - t.Run(name, func(t *testing.T) { - opts := &Options{ - Reason: test.reason, - Message: test.message, - } - - // Validating args and flags - err := opts.Validate(test.args) - if (err != nil) != test.expErr { - t.Errorf("unexpected error, exp=%t got=%v", - test.expErr, err) - } - if err != nil && err.Error() != test.expErrMsg { - t.Errorf("got unexpected error when validating args and flags, expected: %v; actual: %v", test.expErrMsg, err) - } - }) - } -} diff --git a/cmd/ctl/pkg/build/build.go b/cmd/ctl/pkg/build/build.go deleted file mode 100644 index 144be39931e..00000000000 --- a/cmd/ctl/pkg/build/build.go +++ /dev/null @@ -1,43 +0,0 @@ -/* -Copyright 2020 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. -*/ - -package build - -import ( - "bytes" - "text/template" -) - -// name is the build time configurable name of the build (name of the target -// binary name). -var name = "cmctl" - -// Name returns the build name. -func Name() string { - return name -} - -// WithTemplate returns a string that has the build name templated out with the -// configured build name. Build name templates on '{{ .BuildName }}' variable. -func WithTemplate(str string) string { - tmpl := template.Must(template.New("build-name").Parse(str)) - var buf bytes.Buffer - if err := tmpl.Execute(&buf, struct{ BuildName string }{name}); err != nil { - // We panic here as it should never be possible that this template fails. - panic(err) - } - return buf.String() -} diff --git a/cmd/ctl/pkg/build/commands/commands.go b/cmd/ctl/pkg/build/commands/commands.go deleted file mode 100644 index 3cbce2f2157..00000000000 --- a/cmd/ctl/pkg/build/commands/commands.go +++ /dev/null @@ -1,71 +0,0 @@ -/* -Copyright 2020 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. -*/ - -package commands - -import ( - "context" - "strings" - - "github.com/spf13/cobra" - "k8s.io/cli-runtime/pkg/genericclioptions" - - "github.com/cert-manager/cert-manager/cmd/ctl/pkg/approve" - "github.com/cert-manager/cert-manager/cmd/ctl/pkg/check" - "github.com/cert-manager/cert-manager/cmd/ctl/pkg/completion" - "github.com/cert-manager/cert-manager/cmd/ctl/pkg/convert" - "github.com/cert-manager/cert-manager/cmd/ctl/pkg/create" - "github.com/cert-manager/cert-manager/cmd/ctl/pkg/deny" - "github.com/cert-manager/cert-manager/cmd/ctl/pkg/experimental" - "github.com/cert-manager/cert-manager/cmd/ctl/pkg/inspect" - "github.com/cert-manager/cert-manager/cmd/ctl/pkg/renew" - "github.com/cert-manager/cert-manager/cmd/ctl/pkg/status" - "github.com/cert-manager/cert-manager/cmd/ctl/pkg/upgrade" - "github.com/cert-manager/cert-manager/cmd/ctl/pkg/version" -) - -// registerCompletion gates whether the completion command is registered. -// Specifically useful when building the CLI as a kubectl plugin which does not -// support completion. -var registerCompletion = "false" - -type RegisterCommandFunc func(context.Context, genericclioptions.IOStreams) *cobra.Command - -// Commands returns the cobra Commands that should be registered for the CLI -// build. -func Commands() []RegisterCommandFunc { - cmds := []RegisterCommandFunc{ - version.NewCmdVersion, - convert.NewCmdConvert, - create.NewCmdCreate, - renew.NewCmdRenew, - status.NewCmdStatus, - inspect.NewCmdInspect, - approve.NewCmdApprove, - deny.NewCmdDeny, - check.NewCmdCheck, - upgrade.NewCmdUpgrade, - - // Experimental features - experimental.NewCmdExperimental, - } - - if strings.ToLower(registerCompletion) == "true" { - cmds = append(cmds, completion.NewCmdCompletion) - } - - return cmds -} diff --git a/cmd/ctl/pkg/check/api/api.go b/cmd/ctl/pkg/check/api/api.go deleted file mode 100644 index 5e76c00c75c..00000000000 --- a/cmd/ctl/pkg/check/api/api.go +++ /dev/null @@ -1,150 +0,0 @@ -/* -Copyright 2021 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. -*/ - -package api - -import ( - "context" - "errors" - "fmt" - "log" - "runtime" - "time" - - "github.com/spf13/cobra" - "k8s.io/apimachinery/pkg/util/wait" - "k8s.io/cli-runtime/pkg/genericclioptions" - "k8s.io/kubectl/pkg/scheme" - "k8s.io/kubectl/pkg/util/i18n" - "k8s.io/kubectl/pkg/util/templates" - - "github.com/cert-manager/cert-manager/cmd/ctl/pkg/factory" - cmcmdutil "github.com/cert-manager/cert-manager/cmd/util" - "github.com/cert-manager/cert-manager/pkg/util/cmapichecker" -) - -// Options is a struct to support check api command -type Options struct { - // APIChecker is used to check that the cert-manager CRDs have been installed on the K8S - // API server and that the cert-manager webhooks are all working - APIChecker cmapichecker.Interface - - // Time before timeout when waiting - Wait time.Duration - - // Time between checks when waiting - Interval time.Duration - - // Print details regarding encountered errors - Verbose bool - - genericclioptions.IOStreams - *factory.Factory -} - -var checkApiDesc = templates.LongDesc(i18n.T(` -This check attempts to perform a dry-run create of a cert-manager *v1alpha2* -Certificate resource in order to verify that CRDs are installed and all the -required webhooks are reachable by the K8S API server. -We use v1alpha2 API to ensure that the API server has also connected to the -cert-manager conversion webhook.`)) - -// NewOptions returns initialized Options -func NewOptions(ioStreams genericclioptions.IOStreams) *Options { - return &Options{ - IOStreams: ioStreams, - } -} - -// Complete takes the command arguments and factory and infers any remaining options. -func (o *Options) Complete() error { - var err error - - // We pass the scheme that is used in the RESTConfig's NegotiatedSerializer, - // this makes sure that the cmapi is also added to NegotiatedSerializer's scheme - // see: https://github.com/cert-manager/cert-manager/pull/4205#discussion_r668660271 - o.APIChecker, err = cmapichecker.New(o.RESTConfig, scheme.Scheme, o.Namespace) - if err != nil { - return fmt.Errorf("Error: %v", err) - } - - return nil -} - -// NewCmdCheckApi returns a cobra command for checking creating cert-manager resources against the K8S API server -func NewCmdCheckApi(ctx context.Context, ioStreams genericclioptions.IOStreams) *cobra.Command { - o := NewOptions(ioStreams) - - cmd := &cobra.Command{ - Use: "api", - Short: "Check if the cert-manager API is ready", - Long: checkApiDesc, - RunE: func(cmd *cobra.Command, args []string) error { - if err := o.Complete(); err != nil { - return err - } - o.Run(ctx) - return nil - }, - SilenceUsage: true, - SilenceErrors: true, - } - cmd.Flags().DurationVar(&o.Wait, "wait", 0, "Wait until the cert-manager API is ready (default 0s)") - cmd.Flags().DurationVar(&o.Interval, "interval", 5*time.Second, "Time between checks when waiting, must include unit, e.g. 1m or 10m") - cmd.Flags().BoolVarP(&o.Verbose, "verbose", "v", false, "Print detailed error messages") - - o.Factory = factory.New(ctx, cmd) - - return cmd -} - -// Run executes check api command -func (o *Options) Run(ctx context.Context) { - if !o.Verbose { - log.SetFlags(0) // Disable prefixing logs with timestamps. - } - log.SetOutput(o.ErrOut) // Log all intermediate errors to stderr - - pollContext, cancel := context.WithTimeout(ctx, o.Wait) - defer cancel() - - pollErr := wait.PollImmediateUntil(o.Interval, func() (done bool, err error) { - if err := o.APIChecker.Check(ctx); err != nil { - if !o.Verbose && errors.Unwrap(err) != nil { - err = errors.Unwrap(err) - } - - log.Printf("Not ready: %v", err) - return false, nil - } - - return true, nil - }, pollContext.Done()) - - log.SetOutput(o.Out) // Log conclusion to stdout - - if pollErr != nil { - if errors.Is(pollContext.Err(), context.DeadlineExceeded) && o.Wait > 0 { - log.Printf("Timed out after %s", o.Wait) - } - - cmcmdutil.SetExitCode(pollContext.Err()) - - runtime.Goexit() // Do soft exit (handle all defers, that should set correct exit code) - } - - log.Printf("The cert-manager API is ready") -} diff --git a/cmd/ctl/pkg/completion/bash.go b/cmd/ctl/pkg/completion/bash.go deleted file mode 100644 index a561ecd8876..00000000000 --- a/cmd/ctl/pkg/completion/bash.go +++ /dev/null @@ -1,46 +0,0 @@ -/* -Copyright 2021 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. -*/ - -package completion - -import ( - "github.com/spf13/cobra" - "k8s.io/cli-runtime/pkg/genericclioptions" - "k8s.io/kubectl/pkg/cmd/util" - - "github.com/cert-manager/cert-manager/cmd/ctl/pkg/build" -) - -func newCmdCompletionBash(ioStreams genericclioptions.IOStreams) *cobra.Command { - return &cobra.Command{ - Use: "bash", - Short: "Generate cert-manager CLI scripts for a Bash shell", - Long: build.WithTemplate(`To load completions: -Bash: - $ source <({{.BuildName}} completion bash) - # To load completions for each session, execute once: - # Linux: - $ {{.BuildName}} completion bash > /etc/bash_completion.d/{{.BuildName}} - - # macOS: - $ {{.BuildName}} completion bash > /usr/local/etc/bash_completion.d/{{.BuildName}} -`), - DisableFlagsInUseLine: true, - Run: func(cmd *cobra.Command, args []string) { - util.CheckErr(cmd.Root().GenBashCompletion(ioStreams.Out)) - }, - } -} diff --git a/cmd/ctl/pkg/completion/completion.go b/cmd/ctl/pkg/completion/completion.go deleted file mode 100644 index 6ba46a65158..00000000000 --- a/cmd/ctl/pkg/completion/completion.go +++ /dev/null @@ -1,39 +0,0 @@ -/* -Copyright 2021 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. -*/ - -package completion - -import ( - "context" - - "github.com/spf13/cobra" - "k8s.io/cli-runtime/pkg/genericclioptions" -) - -func NewCmdCompletion(ctx context.Context, ioStreams genericclioptions.IOStreams) *cobra.Command { - cmds := &cobra.Command{ - Use: "completion", - Short: "Generate completion scripts for the cert-manager CLI", - Long: "Generate completion for the cert-manager CLI so arguments and flags can be suggested and auto-completed", - } - - cmds.AddCommand(newCmdCompletionBash(ioStreams)) - cmds.AddCommand(newCmdCompletionZSH(ioStreams)) - cmds.AddCommand(newCmdCompletionFish(ioStreams)) - cmds.AddCommand(newCmdCompletionPowerShell(ioStreams)) - - return cmds -} diff --git a/cmd/ctl/pkg/completion/fish.go b/cmd/ctl/pkg/completion/fish.go deleted file mode 100644 index b77f74cad7e..00000000000 --- a/cmd/ctl/pkg/completion/fish.go +++ /dev/null @@ -1,42 +0,0 @@ -/* -Copyright 2021 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. -*/ - -package completion - -import ( - "github.com/spf13/cobra" - "k8s.io/cli-runtime/pkg/genericclioptions" - "k8s.io/kubectl/pkg/cmd/util" - - "github.com/cert-manager/cert-manager/cmd/ctl/pkg/build" -) - -func newCmdCompletionFish(ioStreams genericclioptions.IOStreams) *cobra.Command { - return &cobra.Command{ - Use: "fish", - Short: "Generate cert-manager CLI scripts for a Fish shell", - Long: build.WithTemplate(`To load completions: - $ {{.BuildName}} completion fish | source - - # To load completions for each session, execute once: - $ {{.BuildName}} completion fish > ~/.config/fish/completions/{{.BuildName}}.fish -`), - DisableFlagsInUseLine: true, - Run: func(cmd *cobra.Command, args []string) { - util.CheckErr(cmd.Root().GenFishCompletion(ioStreams.Out, true)) - }, - } -} diff --git a/cmd/ctl/pkg/completion/powershell.go b/cmd/ctl/pkg/completion/powershell.go deleted file mode 100644 index 68e6b3b01c7..00000000000 --- a/cmd/ctl/pkg/completion/powershell.go +++ /dev/null @@ -1,43 +0,0 @@ -/* -Copyright 2021 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. -*/ - -package completion - -import ( - "github.com/spf13/cobra" - "k8s.io/cli-runtime/pkg/genericclioptions" - "k8s.io/kubectl/pkg/cmd/util" - - "github.com/cert-manager/cert-manager/cmd/ctl/pkg/build" -) - -func newCmdCompletionPowerShell(ioStreams genericclioptions.IOStreams) *cobra.Command { - return &cobra.Command{ - Use: "powershell", - Short: "Generate cert-manager CLI scripts for a PowerShell shell", - Long: build.WithTemplate(`To load completions: - PS> {{.BuildName}} completion powershell | Out-String | Invoke-Expression - - # To load completions for every new session, run: - PS> {{.BuildName}} completion powershell > {{.BuildName}}.ps1 - # and source this file from your PowerShell profile. -`), - DisableFlagsInUseLine: true, - Run: func(cmd *cobra.Command, args []string) { - util.CheckErr(cmd.Root().GenPowerShellCompletion(ioStreams.Out)) - }, - } -} diff --git a/cmd/ctl/pkg/completion/zsh.go b/cmd/ctl/pkg/completion/zsh.go deleted file mode 100644 index 5f8c2fbea68..00000000000 --- a/cmd/ctl/pkg/completion/zsh.go +++ /dev/null @@ -1,45 +0,0 @@ -/* -Copyright 2021 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. -*/ - -package completion - -import ( - "github.com/spf13/cobra" - "k8s.io/cli-runtime/pkg/genericclioptions" - "k8s.io/kubectl/pkg/cmd/util" - - "github.com/cert-manager/cert-manager/cmd/ctl/pkg/build" -) - -func newCmdCompletionZSH(ioStreams genericclioptions.IOStreams) *cobra.Command { - return &cobra.Command{ - Use: "zsh", - Short: "Generation cert-manager CLI scripts for a ZSH shell", - Long: build.WithTemplate(`To load completions: - # If shell completion is not already enabled in your environment, - # you will need to enable it. You can execute the following once: - $ echo "autoload -U compinit; compinit" >> ~/.zshrc - - # To load completions for each session, execute once: - $ {{.BuildName}} completion zsh > "${fpath[1]}/_{{.BuildName}}" - # You will need to start a new shell for this setup to take effect. -`), - DisableFlagsInUseLine: true, - Run: func(cmd *cobra.Command, args []string) { - util.CheckErr(cmd.Root().GenZshCompletion(ioStreams.Out)) - }, - } -} diff --git a/cmd/ctl/pkg/convert/convert.go b/cmd/ctl/pkg/convert/convert.go deleted file mode 100644 index 49228b24528..00000000000 --- a/cmd/ctl/pkg/convert/convert.go +++ /dev/null @@ -1,276 +0,0 @@ -/* -Copyright 2020 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. -*/ - -package convert - -import ( - "context" - "fmt" - - "github.com/cert-manager/cert-manager/cmd/ctl/pkg/build" - logf "github.com/cert-manager/cert-manager/pkg/logs" - - "github.com/spf13/cobra" - metainternalversion "k8s.io/apimachinery/pkg/apis/meta/internalversion" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/apimachinery/pkg/runtime/serializer" - apijson "k8s.io/apimachinery/pkg/runtime/serializer/json" - "k8s.io/cli-runtime/pkg/genericclioptions" - "k8s.io/cli-runtime/pkg/printers" - "k8s.io/cli-runtime/pkg/resource" - cmdutil "k8s.io/kubectl/pkg/cmd/util" - "k8s.io/kubectl/pkg/util/i18n" - "k8s.io/kubectl/pkg/util/templates" - - "github.com/cert-manager/cert-manager/pkg/ctl" -) - -var ( - example = templates.Examples(i18n.T(build.WithTemplate(` - # Convert 'cert.yaml' to latest version and print to stdout. - {{.BuildName}} convert -f cert.yaml - - # Convert kustomize overlay under current directory to 'cert-manager.io/v1alpha3' - {{.BuildName}} convert -k . --output-version cert-manager.io/v1alpha3`))) - - longDesc = templates.LongDesc(i18n.T(` -Convert cert-manager config files between different API versions. Both YAML -and JSON formats are accepted. - -The command takes filename, directory, or URL as input, and converts into the -format of the version specified by --output-version flag. If target version is -not specified or not supported, it will convert to the latest version - -The default output will be printed to stdout in YAML format. One can use -o option -to change to output destination.`)) -) - -var ( - // Use this scheme as it has the internal cert-manager types - // and their conversion functions registered. - scheme = ctl.Scheme -) - -// Options is a struct to support convert command -type Options struct { - PrintFlags *genericclioptions.PrintFlags - Printer printers.ResourcePrinter - - OutputVersion string - - resource.FilenameOptions - genericclioptions.IOStreams -} - -// NewOptions returns initialized Options -func NewOptions(ioStreams genericclioptions.IOStreams) *Options { - return &Options{ - IOStreams: ioStreams, - PrintFlags: genericclioptions.NewPrintFlags("converted").WithDefaultOutput("yaml"), - } -} - -// NewCmdConvert returns a cobra command for converting cert-manager resources -func NewCmdConvert(ctx context.Context, ioStreams genericclioptions.IOStreams) *cobra.Command { - o := NewOptions(ioStreams) - - cmd := &cobra.Command{ - Use: "convert", - Short: "Convert cert-manager config files between different API versions", - Long: longDesc, - Example: example, - DisableFlagsInUseLine: true, - Run: func(cmd *cobra.Command, args []string) { - cmdutil.CheckErr(o.Complete()) - cmdutil.CheckErr(o.Run()) - }, - } - - cmd.Flags().StringVar(&o.OutputVersion, "output-version", o.OutputVersion, "Output the formatted object with the given group version (for ex: 'cert-manager.io/v1alpha3').") - cmdutil.AddFilenameOptionFlags(cmd, &o.FilenameOptions, "Path to a file containing cert-manager resources to be converted.") - o.PrintFlags.AddFlags(cmd) - - return cmd -} - -// Complete collects information required to run Convert command from command line. -func (o *Options) Complete() error { - err := o.FilenameOptions.RequireFilenameOrKustomize() - if err != nil { - return err - } - - // build the printer - o.Printer, err = o.PrintFlags.ToPrinter() - if err != nil { - return err - } - - return nil -} - -// Run executes convert command -func (o *Options) Run() error { - builder := new(resource.Builder) - - r := builder. - WithScheme(scheme). - LocalParam(true).FilenameParam(false, &o.FilenameOptions).Flatten().Do() - - if err := r.Err(); err != nil { - return err - } - - singleItemImplied := false - infos, err := r.IntoSingleItemImplied(&singleItemImplied).Infos() - if err != nil { - return err - } - - if len(infos) == 0 { - return fmt.Errorf("no objects passed to convert") - } - - var specifiedOutputVersion schema.GroupVersion - if len(o.OutputVersion) > 0 { - specifiedOutputVersion, err = schema.ParseGroupVersion(o.OutputVersion) - if err != nil { - return err - } - } - - factory := serializer.NewCodecFactory(scheme) - serializer := apijson.NewSerializerWithOptions(apijson.DefaultMetaFactory, scheme, scheme, apijson.SerializerOptions{}) - encoder := factory.WithoutConversion().EncoderForVersion(serializer, nil) - objects, err := asVersionedObject(infos, !singleItemImplied, specifiedOutputVersion, encoder) - if err != nil { - return err - } - - return o.Printer.PrintObj(objects, o.Out) -} - -// asVersionedObject converts a list of infos into a single object - either a List containing -// the objects as children, or if only a single Object is present, as that object. The provided -// version will be preferred as the conversion target, but the Object's mapping version will be -// used if that version is not present. -func asVersionedObject(infos []*resource.Info, forceList bool, specifiedOutputVersion schema.GroupVersion, encoder runtime.Encoder) (runtime.Object, error) { - objects, err := asVersionedObjects(infos, specifiedOutputVersion, encoder) - if err != nil { - return nil, err - } - - var object runtime.Object - if len(objects) == 1 && !forceList { - object = objects[0] - } else { - object = &metainternalversion.List{Items: objects} - - targetVersions := []schema.GroupVersion{} - if !specifiedOutputVersion.Empty() { - targetVersions = append(targetVersions, specifiedOutputVersion) - } - // This is needed so we are able to handle the List object when converting - // multiple resources - targetVersions = append(targetVersions, schema.GroupVersion{Group: "", Version: "v1"}) - - converted, err := tryConvert(object, targetVersions...) - if err != nil { - return nil, err - } - - object = converted - } - - actualVersion := object.GetObjectKind().GroupVersionKind() - - if actualVersion.Version != specifiedOutputVersion.Version { - defaultVersionInfo := "" - if len(actualVersion.Version) > 0 { - defaultVersionInfo = fmt.Sprintf("Defaulting to %q", actualVersion.Version) - } - logf.V(logf.WarnLevel).Infof("info: the output version specified is invalid. %s\n", defaultVersionInfo) - } - - return object, nil -} - -// asVersionedObjects converts a list of infos into versioned objects. The provided -// version will be preferred as the conversion target, but the Object's mapping version will be -// used if that version is not present. -func asVersionedObjects(infos []*resource.Info, specifiedOutputVersion schema.GroupVersion, encoder runtime.Encoder) ([]runtime.Object, error) { - objects := []runtime.Object{} - for _, info := range infos { - if info.Object == nil { - continue - } - - targetVersions := []schema.GroupVersion{} - // objects that are not part of api.Scheme must be converted to JSON - if !specifiedOutputVersion.Empty() { - _, _, err := scheme.ObjectKinds(info.Object) - if err != nil { - if runtime.IsNotRegisteredError(err) { - data, err := runtime.Encode(encoder, info.Object) - if err != nil { - return nil, err - } - objects = append(objects, &runtime.Unknown{Raw: data}) - continue - } - - return nil, err - } - - targetVersions = append(targetVersions, specifiedOutputVersion) - } else { - gvks, _, err := scheme.ObjectKinds(info.Object) - if err == nil { - for _, gvk := range gvks { - targetVersions = append(targetVersions, scheme.PrioritizedVersionsForGroup(gvk.Group)...) - } - } - } - - converted, err := tryConvert(info.Object, targetVersions...) - if err != nil { - return nil, err - } - objects = append(objects, converted) - } - - return objects, nil -} - -// tryConvert attempts to convert the given object to the provided versions in order. This function assumes -// the object is in internal version. -func tryConvert(object runtime.Object, versions ...schema.GroupVersion) (runtime.Object, error) { - var last error - for _, version := range versions { - if version.Empty() { - return object, nil - } - obj, err := scheme.ConvertToVersion(object, version) - if err != nil { - last = err - continue - } - return obj, nil - } - - return nil, last -} diff --git a/cmd/ctl/pkg/create/certificaterequest/certificaterequest.go b/cmd/ctl/pkg/create/certificaterequest/certificaterequest.go deleted file mode 100644 index 20a6d79daa0..00000000000 --- a/cmd/ctl/pkg/create/certificaterequest/certificaterequest.go +++ /dev/null @@ -1,345 +0,0 @@ -/* -Copyright 2020 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. -*/ - -package certificaterequest - -import ( - "context" - "encoding/pem" - "errors" - "fmt" - "os" - "time" - - "github.com/spf13/cobra" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/apimachinery/pkg/util/wait" - "k8s.io/cli-runtime/pkg/genericclioptions" - "k8s.io/cli-runtime/pkg/resource" - cmdutil "k8s.io/kubectl/pkg/cmd/util" - "k8s.io/kubectl/pkg/util/i18n" - "k8s.io/kubectl/pkg/util/templates" - - "github.com/cert-manager/cert-manager/cmd/ctl/pkg/build" - "github.com/cert-manager/cert-manager/cmd/ctl/pkg/factory" - apiutil "github.com/cert-manager/cert-manager/pkg/api/util" - cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" - cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" - "github.com/cert-manager/cert-manager/pkg/ctl" - "github.com/cert-manager/cert-manager/pkg/util/pki" -) - -var ( - long = templates.LongDesc(i18n.T(` -Create a new CertificateRequest resource based on a Certificate resource, by generating a private key locally and create a 'certificate signing request' to be submitted to a cert-manager Issuer.`)) - - example = templates.Examples(i18n.T(build.WithTemplate(` -# Create a CertificateRequest with the name 'my-cr', saving the private key in a file named 'my-cr.key'. -{{.BuildName}} create certificaterequest my-cr --from-certificate-file my-certificate.yaml - -# Create a CertificateRequest in namespace default, provided no conflict with namespace defined in file. -{{.BuildName}} create certificaterequest my-cr --namespace default --from-certificate-file my-certificate.yaml - -# Create a CertificateRequest and store private key in file 'new.key'. -{{.BuildName}} create certificaterequest my-cr --from-certificate-file my-certificate.yaml --output-key-file new.key - -# Create a CertificateRequest, wait for it to be signed for up to 5 minutes (default) and store the x509 certificate in file 'new.crt'. -{{.BuildName}} create certificaterequest my-cr --from-certificate-file my-certificate.yaml --fetch-certificate --output-cert-file new.crt - -# Create a CertificateRequest, wait for it to be signed for up to 20 minutes and store the x509 certificate in file 'my-cr.crt'. -{{.BuildName}} create certificaterequest my-cr --from-certificate-file my-certificate.yaml --fetch-certificate --timeout 20m -`))) -) - -var ( - // Dedicated scheme used by the ctl tool that has the internal cert-manager types, - // and their conversion functions registered - scheme = ctl.Scheme -) - -// Options is a struct to support create certificaterequest command -type Options struct { - // Name of file that the generated private key will be stored in - // If not specified, the private key will be written to .key - KeyFilename string - // If true, will wait for CertificateRequest to be ready to store the x509 certificate in a file - // Command will block until CertificateRequest is ready or timeout as specified by Timeout happens - FetchCert bool - // Name of file that the generated x509 certificate will be stored in if --fetch-certificate flag is set - // If not specified, the private key will be written to .crt - CertFileName string - // Path to a file containing a Certificate resource used as a template - // when generating the CertificateRequest resource - // Required - InputFilename string - // Length of time the command blocks to wait on CertificateRequest to be ready if --fetch-certificate flag is set - // If not specified, default value is 5 minutes - Timeout time.Duration - - genericclioptions.IOStreams - *factory.Factory -} - -// NewOptions returns initialized Options -func NewOptions(ioStreams genericclioptions.IOStreams) *Options { - return &Options{ - IOStreams: ioStreams, - } -} - -// NewCmdCreateCR returns a cobra command for create CertificateRequest -func NewCmdCreateCR(ctx context.Context, ioStreams genericclioptions.IOStreams) *cobra.Command { - o := NewOptions(ioStreams) - - cmd := &cobra.Command{ - Use: "certificaterequest", - Aliases: []string{"cr"}, - Short: "Create a cert-manager CertificateRequest resource, using a Certificate resource as a template", - Long: long, - Example: example, - ValidArgsFunction: factory.ValidArgsListCertificateRequests(ctx, &o.Factory), - Run: func(cmd *cobra.Command, args []string) { - cmdutil.CheckErr(o.Validate(args)) - cmdutil.CheckErr(o.Run(ctx, args)) - }, - } - cmd.Flags().StringVar(&o.InputFilename, "from-certificate-file", o.InputFilename, - "Path to a file containing a Certificate resource used as a template when generating the CertificateRequest resource") - cmd.Flags().StringVar(&o.KeyFilename, "output-key-file", o.KeyFilename, - "Name of file that the generated private key will be written to") - cmd.Flags().StringVar(&o.CertFileName, "output-certificate-file", o.CertFileName, - "Name of the file the certificate is to be stored in") - cmd.Flags().BoolVar(&o.FetchCert, "fetch-certificate", o.FetchCert, - "If set to true, command will wait for CertificateRequest to be signed to store x509 certificate in a file") - cmd.Flags().DurationVar(&o.Timeout, "timeout", 5*time.Minute, - "Time before timeout when waiting for CertificateRequest to be signed, must include unit, e.g. 10m or 1h") - - o.Factory = factory.New(ctx, cmd) - - return cmd -} - -// Validate validates the provided options -func (o *Options) Validate(args []string) error { - if len(args) < 1 { - return errors.New("the name of the CertificateRequest to be created has to be provided as argument") - } - if len(args) > 1 { - return errors.New("only one argument can be passed in: the name of the CertificateRequest") - } - - if o.InputFilename == "" { - return errors.New("the path to a YAML manifest of a Certificate resource cannot be empty, please specify by using --from-certificate-file flag") - } - - if o.KeyFilename != "" && o.CertFileName != "" && o.KeyFilename == o.CertFileName { - return errors.New("the file to store private key cannot be the same as the file to store certificate") - } - - if !o.FetchCert && o.CertFileName != "" { - return errors.New("cannot specify file to store certificate if not waiting for and fetching certificate, please set --fetch-certificate flag") - } - - return nil -} - -// Run executes create certificaterequest command -func (o *Options) Run(ctx context.Context, args []string) error { - builder := new(resource.Builder) - - // Read file as internal API version - r := builder. - WithScheme(scheme, schema.GroupVersion{Group: cmapi.SchemeGroupVersion.Group, Version: runtime.APIVersionInternal}). - LocalParam(true).ContinueOnError(). - NamespaceParam(o.Namespace).DefaultNamespace(). - FilenameParam(o.EnforceNamespace, &resource.FilenameOptions{Filenames: []string{o.InputFilename}}).Flatten().Do() - - if err := r.Err(); err != nil { - return err - } - - singleItemImplied := false - infos, err := r.IntoSingleItemImplied(&singleItemImplied).Infos() - if err != nil { - return err - } - - // Ensure only one object per command - if len(infos) == 0 { - return fmt.Errorf("no objects found in manifest file %q. Expected one Certificate object", o.InputFilename) - } - if len(infos) > 1 { - return fmt.Errorf("multiple objects found in manifest file %q. Expected only one Certificate object", o.InputFilename) - } - info := infos[0] - // Convert to v1 because that version is needed for functions that follow - crtObj, err := scheme.ConvertToVersion(info.Object, cmapi.SchemeGroupVersion) - if err != nil { - return fmt.Errorf("failed to convert object into version v1: %w", err) - } - - // Cast Object into Certificate - crt, ok := crtObj.(*cmapi.Certificate) - if !ok { - return errors.New("decoded object is not a v1 Certificate") - } - - crt = crt.DeepCopy() - if crt.Spec.PrivateKey == nil { - crt.Spec.PrivateKey = &cmapi.CertificatePrivateKey{} - } - - signer, err := pki.GeneratePrivateKeyForCertificate(crt) - if err != nil { - return fmt.Errorf("error when generating new private key for CertificateRequest: %w", err) - } - - keyData, err := pki.EncodePrivateKey(signer, crt.Spec.PrivateKey.Encoding) - if err != nil { - return fmt.Errorf("failed to encode new private key for CertificateRequest: %w", err) - } - - crName := args[0] - - // Storing private key to file - keyFileName := crName + ".key" - if o.KeyFilename != "" { - keyFileName = o.KeyFilename - } - if err := os.WriteFile(keyFileName, keyData, 0600); err != nil { - return fmt.Errorf("error when writing private key to file: %w", err) - } - fmt.Fprintf(o.ErrOut, "Private key written to file %s\n", keyFileName) - - // Build CertificateRequest with name as specified by argument - req, err := buildCertificateRequest(crt, keyData, crName) - if err != nil { - return fmt.Errorf("error when building CertificateRequest: %w", err) - } - - ns := crt.Namespace - if ns == "" { - ns = o.Namespace - } - req, err = o.CMClient.CertmanagerV1().CertificateRequests(ns).Create(ctx, req, metav1.CreateOptions{}) - if err != nil { - return fmt.Errorf("error creating CertificateRequest: %w", err) - } - fmt.Fprintf(o.ErrOut, "CertificateRequest %s has been created in namespace %s\n", req.Name, req.Namespace) - - if o.FetchCert { - fmt.Fprintf(o.ErrOut, "CertificateRequest %v in namespace %v has not been signed yet. Wait until it is signed...\n", - req.Name, req.Namespace) - err = wait.Poll(time.Second, o.Timeout, func() (done bool, err error) { - req, err = o.CMClient.CertmanagerV1().CertificateRequests(req.Namespace).Get(ctx, req.Name, metav1.GetOptions{}) - if err != nil { - return false, nil - } - return apiutil.CertificateRequestHasCondition(req, cmapi.CertificateRequestCondition{ - Type: cmapi.CertificateRequestConditionReady, - Status: cmmeta.ConditionTrue, - }) && len(req.Status.Certificate) > 0, nil - }) - if err != nil { - return fmt.Errorf("error when waiting for CertificateRequest to be signed: %w", err) - } - fmt.Fprintf(o.ErrOut, "CertificateRequest %v in namespace %v has been signed\n", req.Name, req.Namespace) - - // Fetch x509 certificate and store to file - actualCertFileName := req.Name + ".crt" - if o.CertFileName != "" { - actualCertFileName = o.CertFileName - } - err = fetchCertificateFromCR(req, actualCertFileName) - if err != nil { - return fmt.Errorf("error when writing certificate to file: %w", err) - } - fmt.Fprintf(o.ErrOut, "Certificate written to file %s\n", actualCertFileName) - } - - return nil -} - -// Builds a CertificateRequest -func buildCertificateRequest(crt *cmapi.Certificate, pk []byte, crName string) (*cmapi.CertificateRequest, error) { - csrPEM, err := generateCSR(crt, pk) - if err != nil { - return nil, err - } - - cr := &cmapi.CertificateRequest{ - ObjectMeta: metav1.ObjectMeta{ - Name: crName, - Annotations: crt.Annotations, - Labels: crt.Labels, - }, - Spec: cmapi.CertificateRequestSpec{ - Request: csrPEM, - Duration: crt.Spec.Duration, - IssuerRef: crt.Spec.IssuerRef, - IsCA: crt.Spec.IsCA, - Usages: crt.Spec.Usages, - }, - } - - return cr, nil -} - -func generateCSR(crt *cmapi.Certificate, pk []byte) ([]byte, error) { - csr, err := pki.GenerateCSR(crt) - if err != nil { - return nil, err - } - - signer, err := pki.DecodePrivateKeyBytes(pk) - if err != nil { - return nil, err - } - - csrDER, err := pki.EncodeCSR(csr, signer) - if err != nil { - return nil, err - } - - csrPEM := pem.EncodeToMemory(&pem.Block{ - Type: "CERTIFICATE REQUEST", Bytes: csrDER, - }) - - return csrPEM, nil -} - -// fetchCertificateFromCR fetches the x509 certificate from a CR and stores the -// certificate in file specified by certFilename. Assumes CR is ready, -// otherwise returns error. -func fetchCertificateFromCR(req *cmapi.CertificateRequest, certFileName string) error { - // If CR not ready yet, error - if !apiutil.CertificateRequestHasCondition(req, cmapi.CertificateRequestCondition{ - Type: cmapi.CertificateRequestConditionReady, - Status: cmmeta.ConditionTrue, - }) || len(req.Status.Certificate) == 0 { - return errors.New("CertificateRequest is not ready yet, unable to fetch certificate") - } - - // Store certificate to file - err := os.WriteFile(certFileName, req.Status.Certificate, 0600) - if err != nil { - return fmt.Errorf("error when writing certificate to file: %w", err) - } - - return nil -} diff --git a/cmd/ctl/pkg/create/certificaterequest/certificaterequest_test.go b/cmd/ctl/pkg/create/certificaterequest/certificaterequest_test.go deleted file mode 100644 index 75a03c0102c..00000000000 --- a/cmd/ctl/pkg/create/certificaterequest/certificaterequest_test.go +++ /dev/null @@ -1,246 +0,0 @@ -/* -Copyright 2020 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. -*/ - -package certificaterequest - -import ( - "context" - "os" - "testing" - - "github.com/cert-manager/cert-manager/cmd/ctl/pkg/factory" -) - -func TestValidate(t *testing.T) { - tests := map[string]struct { - inputFile string - inputArgs []string - keyFilename string - certFilename string - fetchCert bool - - expErr bool - expErrMsg string - }{ - "CR name not passed as arg throws error": { - inputFile: "example.yaml", - inputArgs: []string{}, - expErr: true, - expErrMsg: "the name of the CertificateRequest to be created has to be provided as argument", - }, - "More than one arg throws error": { - inputFile: "example.yaml", - inputArgs: []string{"hello", "World"}, - expErr: true, - expErrMsg: "only one argument can be passed in: the name of the CertificateRequest", - }, - "not specifying path to yaml manifest throws error": { - inputFile: "", - inputArgs: []string{"hello"}, - expErr: true, - expErrMsg: "the path to a YAML manifest of a Certificate resource cannot be empty, please specify by using --from-certificate-file flag", - }, - "key filename and cert filename are optional flags": { - inputFile: "example.yaml", - inputArgs: []string{"hello"}, - keyFilename: "", - certFilename: "", - expErr: false, - }, - "identical key filename and cert filename throws error": { - inputFile: "example.yaml", - inputArgs: []string{"hello"}, - keyFilename: "same", - certFilename: "same", - expErr: true, - expErrMsg: "the file to store private key cannot be the same as the file to store certificate", - }, - "cannot specify cert filename without fetch-certificate flag": { - inputFile: "example.yaml", - inputArgs: []string{"hello"}, - certFilename: "cert.crt", - fetchCert: false, - expErr: true, - expErrMsg: "cannot specify file to store certificate if not waiting for and fetching certificate, please set --fetch-certificate flag", - }, - } - - for name, test := range tests { - t.Run(name, func(t *testing.T) { - opts := &Options{ - InputFilename: test.inputFile, - KeyFilename: test.keyFilename, - CertFileName: test.certFilename, - FetchCert: test.fetchCert, - } - - // Validating args and flags - err := opts.Validate(test.inputArgs) - if err != nil { - if !test.expErr { - t.Fatalf("got unexpected error when validating args and flags: %v", err) - } - if err.Error() != test.expErrMsg { - t.Fatalf("got unexpected error when validating args and flags, expected: %v; actual: %v", test.expErrMsg, err) - } - } else if test.expErr { - // got no error - t.Errorf("expected but got no error validating args and flags") - } - }) - } -} - -// Test Run tests the Run function's error behaviour up where it fails before interacting with -// other components, e.g. writing private key to file. -func TestRun(t *testing.T) { - const ( - crName = "testcr-3" - ns1 = "testns-1" - ns2 = "testns-2" - ) - - tests := map[string]struct { - inputFileContent string - inputArgs []string - inputNamespace string - keyFilename string - certFilename string - fetchCert bool - - expErr bool - expErrMsg string - }{ - // Build clients - "conflicting namespaces defined in flag and file": { - inputFileContent: `--- -apiVersion: cert-manager.io/v1 -kind: Certificate -metadata: - name: testcert-1 - namespace: testns-1 -spec: - isCA: true - secretName: ca-key-pair - commonName: my-csi-app - issuerRef: - name: selfsigned-issuer - kind: Issuer - group: cert-manager.io -`, - inputArgs: []string{crName}, - inputNamespace: ns2, - keyFilename: "", - expErr: true, - expErrMsg: "the namespace from the provided object \"testns-1\" does not match the namespace \"testns-2\". You must pass '--namespace=testns-1' to perform this operation.", - }, - "file passed in defines resource other than certificate": { - inputFileContent: `--- -apiVersion: cert-manager.io/v1 -kind: Issuer -metadata: - name: ca-issuer - namespace: testns-1 -spec: - ca: - secretName: ca-key-pair -`, - inputArgs: []string{crName}, - inputNamespace: ns1, - keyFilename: "", - expErr: true, - expErrMsg: "decoded object is not a v1 Certificate", - }, - "empty manifest file throws error": { - inputFileContent: ``, - inputArgs: []string{crName}, - inputNamespace: ns1, - keyFilename: "", - expErr: true, - expErrMsg: "no objects found in manifest file \"testfile.yaml\". Expected one Certificate object", - }, - "manifest file with multiple objects throws error": { - inputFileContent: `--- -apiVersion: cert-manager.io/v1 -kind: Issuer -metadata: - name: ca-issuer - namespace: testns-1 -spec: - ca: - secretName: ca-key-pair ---- -apiVersion: cert-manager.io/v1 -kind: Certificate -metadata: - name: testcert-1 - namespace: testns-1 -spec: - isCA: true - secretName: ca-key-pair - commonName: my-csi-app - issuerRef: - name: selfsigned-issuer - kind: Issuer - group: cert-manager.io`, - inputArgs: []string{crName}, - inputNamespace: ns1, - keyFilename: "", - expErr: true, - expErrMsg: "multiple objects found in manifest file \"testfile.yaml\". Expected only one Certificate object", - }, - } - - for name, test := range tests { - t.Run(name, func(t *testing.T) { - if err := os.WriteFile("testfile.yaml", []byte(test.inputFileContent), 0644); err != nil { - t.Fatalf("error creating test file %#v", err) - } - defer os.Remove("testfile.yaml") - - // Options to run create CR command - opts := &Options{ - InputFilename: "testfile.yaml", - KeyFilename: test.keyFilename, - CertFileName: test.certFilename, - Factory: &factory.Factory{ - Namespace: test.inputNamespace, - EnforceNamespace: test.inputNamespace != "", - }, - } - - // Validating args and flags - err := opts.Validate(test.inputArgs) - if err != nil { - t.Fatal(err) - } - - // Create CR - err = opts.Run(context.TODO(), test.inputArgs) - if err != nil { - if !test.expErr { - t.Fatalf("got unexpected error when trying to create CR: %v", err) - } - if err.Error() != test.expErrMsg { - t.Fatalf("got unexpected error when trying to create CR, expected: %v; actual: %v", test.expErrMsg, err) - } - } else if test.expErr { - // got no error - t.Errorf("expected but got no error when creating CR") - } - }) - } -} diff --git a/cmd/ctl/pkg/create/certificatesigningrequest/certificatesigningrequest.go b/cmd/ctl/pkg/create/certificatesigningrequest/certificatesigningrequest.go deleted file mode 100644 index a77de3b7688..00000000000 --- a/cmd/ctl/pkg/create/certificatesigningrequest/certificatesigningrequest.go +++ /dev/null @@ -1,423 +0,0 @@ -/* -Copyright 2021 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. -*/ - -package certificatesigningrequest - -import ( - "context" - "encoding/pem" - "errors" - "fmt" - "os" - "strconv" - "time" - - experimentalapi "github.com/cert-manager/cert-manager/pkg/apis/experimental/v1alpha1" - "github.com/spf13/cobra" - certificatesv1 "k8s.io/api/certificates/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/apimachinery/pkg/util/wait" - "k8s.io/cli-runtime/pkg/genericclioptions" - "k8s.io/cli-runtime/pkg/resource" - "k8s.io/client-go/discovery" - cmdutil "k8s.io/kubectl/pkg/cmd/util" - "k8s.io/kubectl/pkg/util/i18n" - "k8s.io/kubectl/pkg/util/templates" - - "github.com/cert-manager/cert-manager/cmd/ctl/pkg/build" - "github.com/cert-manager/cert-manager/cmd/ctl/pkg/factory" - apiutil "github.com/cert-manager/cert-manager/pkg/api/util" - "github.com/cert-manager/cert-manager/pkg/apis/certmanager" - cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" - "github.com/cert-manager/cert-manager/pkg/ctl" - "github.com/cert-manager/cert-manager/pkg/util/pki" -) - -var ( - long = templates.LongDesc(i18n.T(` -Experimental. Only supported for Kubernetes versions 1.19+. Requires -cert-manager versions 1.4+ with experimental controllers enabled. - -Create a new CertificateSigningRequest resource based on a Certificate resource, by generating a private key locally and create a 'certificate signing request' to be submitted to a cert-manager Issuer.`)) - - example = templates.Examples(i18n.T(build.WithTemplate(` -# Create a CertificateSigningRequest with the name 'my-csr', saving the private key in a file named 'my-cr.key'. -{{.BuildName}} x create certificatesigningrequest my-csr --from-certificate-file my-certificate.yaml - -# Create a CertificateSigningRequest and store private key in file 'new.key'. -{{.BuildName}} x create certificatesigningrequest my-csr --from-certificate-file my-certificate.yaml --output-key-file new.key - -# Create a CertificateSigningRequest, wait for it to be signed for up to 5 minutes (default) and store the x509 certificate in file 'new.crt'. -{{.BuildName}} x create csr my-cr -f my-certificate.yaml -c new.crt -w - -# Create a CertificateSigningRequest, wait for it to be signed for up to 20 minutes and store the x509 certificate in file 'my-cr.crt'. -{{.BuildName}} x create csr my-cr --from-certificate-file my-certificate.yaml --fetch-certificate --timeout 20m -`))) -) - -var ( - // Dedicated scheme used by the ctl tool that has the internal cert-manager types, - // and their conversion functions registered - scheme = ctl.Scheme -) - -// Options is a struct to support create certificatesigningrequest command -type Options struct { - // Name of file that the generated private key will be stored in If not - // specified, the private key will be written to '.key'. - KeyFilename string - - // If true, will wait for CertificateSigingRequest to be ready to store the - // x509 certificate in a file. - // Command will block until CertificateSigningRequest is ready or timeout as - // specified by Timeout happens. - FetchCert bool - - // Name of file that the generated x509 certificate will be stored in if - // --fetch-certificate flag is set If not specified, the private key will be - // written to '.crt'. - CertFileName string - - // Path to a file containing a Certificate resource used as a template when - // generating the CertificateSigningRequest resource. - // Required. - InputFilename string - - // Length of time the command blocks to wait on CertificateSigningRequest to - // be ready if --fetch-certificate flag is set If not specified, default - // value is 5 minutes. - Timeout time.Duration - - genericclioptions.IOStreams - *factory.Factory -} - -// NewOptions returns initialized Options -func NewOptions(ioStreams genericclioptions.IOStreams) *Options { - return &Options{ - IOStreams: ioStreams, - } -} - -// NewCmdCreateCSR returns a cobra command for create CertificateSigningRequest -func NewCmdCreateCSR(ctx context.Context, ioStreams genericclioptions.IOStreams) *cobra.Command { - o := NewOptions(ioStreams) - - cmd := &cobra.Command{ - Use: "certificatesigningrequest", - Aliases: []string{"csr"}, - Short: "Create a Kubernetes CertificateSigningRequest resource, using a Certificate resource as a template", - Long: long, - Example: example, - ValidArgsFunction: factory.ValidArgsListCertificateSigningRequests(ctx, &o.Factory), - Run: func(cmd *cobra.Command, args []string) { - cmdutil.CheckErr(o.Validate(args)) - cmdutil.CheckErr(o.Run(ctx, args)) - }, - } - cmd.Flags().StringVarP(&o.InputFilename, "from-certificate-file", "f", o.InputFilename, - "Path to a file containing a Certificate resource used as a template when generating the CertificateSigningRequest resource") - cmd.Flags().StringVarP(&o.KeyFilename, "output-key-file", "k", o.KeyFilename, - "Name of file that the generated private key will be written to") - cmd.Flags().StringVarP(&o.CertFileName, "output-certificate-file", "c", o.CertFileName, - "Name of the file the certificate is to be stored in") - cmd.Flags().BoolVarP(&o.FetchCert, "fetch-certificate", "w", o.FetchCert, - "If set to true, command will wait for CertificateSigningRequest to be signed to store x509 certificate in a file") - cmd.Flags().DurationVar(&o.Timeout, "timeout", 5*time.Minute, - "Time before timeout when waiting for CertificateSigningRequest to be signed, must include unit, e.g. 10m or 1h") - - o.Factory = factory.New(ctx, cmd) - - return cmd -} - -// Validate validates the provided options -func (o *Options) Validate(args []string) error { - if len(args) < 1 { - return errors.New("the name of the CertificateSigningRequest to be created has to be provided as argument") - } - if len(args) > 1 { - return errors.New("only one argument can be passed in: the name of the CertificateSigningRequest") - } - - if o.InputFilename == "" { - return errors.New("the path to a YAML manifest of a Certificate resource cannot be empty, please specify by using --from-certificate-file or -f flag") - } - - if o.KeyFilename != "" && o.CertFileName != "" && o.KeyFilename == o.CertFileName { - return errors.New("the file to store private key cannot be the same as the file to store certificate") - } - - if !o.FetchCert && o.CertFileName != "" { - return errors.New("cannot specify file to store certificate if not waiting for and fetching certificate, please set --fetch-certificate or -w flag") - } - - return nil -} - -// Run executes create certificatesigningrequest command -func (o *Options) Run(ctx context.Context, args []string) error { - builder := new(resource.Builder) - - // Read file as internal API version - r := builder. - WithScheme(scheme, schema.GroupVersion{Group: cmapi.SchemeGroupVersion.Group, Version: runtime.APIVersionInternal}). - LocalParam(true).ContinueOnError(). - FilenameParam(false, &resource.FilenameOptions{Filenames: []string{o.InputFilename}}).Flatten().Do() - - if err := r.Err(); err != nil { - return err - } - - singleItemImplied := false - infos, err := r.IntoSingleItemImplied(&singleItemImplied).Infos() - if err != nil { - return err - } - - // Ensure only one object per command - if len(infos) == 0 { - return fmt.Errorf("no objects found in manifest file %q. Expected one Certificate object", o.InputFilename) - } - if len(infos) > 1 { - return fmt.Errorf("multiple objects found in manifest file %q. Expected only one Certificate object", o.InputFilename) - } - info := infos[0] - // Convert to v1 because that version is needed for functions that follow - crtObj, err := scheme.ConvertToVersion(info.Object, cmapi.SchemeGroupVersion) - if err != nil { - return fmt.Errorf("failed to convert object into version v1: %s", err) - } - - // Cast Object into Certificate - crt, ok := crtObj.(*cmapi.Certificate) - if !ok { - return errors.New("decoded object is not a v1 Certificate") - } - - crt = crt.DeepCopy() - if crt.Spec.PrivateKey == nil { - crt.Spec.PrivateKey = &cmapi.CertificatePrivateKey{} - } - - if len(crt.Namespace) == 0 { - // Default to the 'default' Namespace if no Namespaced defined on the - // Certificate - crt.Namespace = "default" - } - - signer, err := pki.GeneratePrivateKeyForCertificate(crt) - if err != nil { - return fmt.Errorf("error when generating new private key for CertificateSigningRequest: %s", err) - } - - keyPEM, err := pki.EncodePrivateKey(signer, crt.Spec.PrivateKey.Encoding) - if err != nil { - return fmt.Errorf("failed to encode new private key for CertificateSigningRequest: %s", err) - } - - csrName := args[0] - - // Storing private key to file - keyFileName := csrName + ".key" - if o.KeyFilename != "" { - keyFileName = o.KeyFilename - } - if err := os.WriteFile(keyFileName, keyPEM, 0600); err != nil { - return fmt.Errorf("error when writing private key to file: %s", err) - } - fmt.Fprintf(o.Out, "Private key written to file %s\n", keyFileName) - - signerName, err := buildSignerName(o.KubeClient.Discovery(), crt) - if err != nil { - return fmt.Errorf("failed to build signerName from Certificate: %s", err) - } - - // Build CertificateSigningRequest with name as specified by argument - req, err := buildCertificateSigningRequest(crt, keyPEM, csrName, signerName) - if err != nil { - return fmt.Errorf("error when building CertificateSigningRequest: %s", err) - } - - req, err = o.KubeClient.CertificatesV1().CertificateSigningRequests().Create(ctx, req, metav1.CreateOptions{}) - if err != nil { - return fmt.Errorf("error creating CertificateSigningRequest: %s", err) - } - fmt.Fprintf(o.Out, "CertificateSigningRequest %s has been created\n", req.Name) - - if o.FetchCert { - fmt.Fprintf(o.Out, "CertificateSigningRequest %s has not been signed yet. Wait until it is signed...\n", req.Name) - - err = wait.Poll(time.Second, o.Timeout, func() (done bool, err error) { - req, err = o.KubeClient.CertificatesV1().CertificateSigningRequests().Get(ctx, req.Name, metav1.GetOptions{}) - if err != nil { - return false, err - } - return len(req.Status.Certificate) > 0, nil - }) - if err != nil { - return fmt.Errorf("error when waiting for CertificateSigningRequest to be signed: %s", err) - } - - fmt.Fprintf(o.Out, "CertificateSigningRequest %s has been signed\n", req.Name) - - // Fetch x509 certificate and store to file - actualCertFileName := req.Name + ".crt" - if o.CertFileName != "" { - actualCertFileName = o.CertFileName - } - - err = storeCertificate(req, actualCertFileName) - if err != nil { - return fmt.Errorf("error when writing certificate to file: %s", err) - } - fmt.Fprintf(o.Out, "Certificate written to file %s\n", actualCertFileName) - } - - return nil -} - -// buildSignerName with generate a Kubernetes CertificateSigningRequest signer -// name, based on the input Certificate's IssuerRef. This function will use the -// Discovery API to fetch the resource definition of the referenced Issuer -// Kind. -// The signer name format follows that of cert-manager. -func buildSignerName(client discovery.DiscoveryInterface, crt *cmapi.Certificate) (string, error) { - targetGroup := crt.Spec.IssuerRef.Group - if len(targetGroup) == 0 { - targetGroup = certmanager.GroupName - } - - targetKind := crt.Spec.IssuerRef.Kind - if len(targetKind) == 0 { - targetKind = cmapi.IssuerKind - } - - grouplist, err := client.ServerGroups() - if err != nil { - return "", err - } - - for _, group := range grouplist.Groups { - if group.Name != targetGroup { - continue - } - - for _, version := range group.Versions { - resources, err := client.ServerResourcesForGroupVersion(version.GroupVersion) - if err != nil { - return "", err - } - - for _, resource := range resources.APIResources { - if resource.Kind != targetKind { - continue - } - - if resource.Namespaced { - return fmt.Sprintf("%s.%s/%s.%s", resource.Name, targetGroup, crt.Namespace, crt.Spec.IssuerRef.Name), nil - } - - return fmt.Sprintf("%s.%s/%s", resource.Name, targetGroup, crt.Spec.IssuerRef.Name), nil - } - } - } - - return "", fmt.Errorf("issuer references a resource definition which does not exist group=%s kind=%s", - targetGroup, targetKind) -} - -// Builds a CertificateSigningRequest -func buildCertificateSigningRequest(crt *cmapi.Certificate, pk []byte, crName, signerName string) (*certificatesv1.CertificateSigningRequest, error) { - csrPEM, err := generateCSR(crt, pk) - if err != nil { - return nil, err - } - - ku, eku, err := pki.BuildKeyUsages(crt.Spec.Usages, crt.Spec.IsCA) - if err != nil { - return nil, err - } - - csr := &certificatesv1.CertificateSigningRequest{ - ObjectMeta: metav1.ObjectMeta{ - Name: crName, - Annotations: crt.Annotations, - Labels: crt.Labels, - }, - Spec: certificatesv1.CertificateSigningRequestSpec{ - Request: csrPEM, - SignerName: signerName, - Usages: append(apiutil.KubeKeyUsageStrings(ku), apiutil.KubeExtKeyUsageStrings(eku)...), - }, - } - - if csr.Annotations == nil { - csr.Annotations = make(map[string]string) - } - csr.Annotations[experimentalapi.CertificateSigningRequestIsCAAnnotationKey] = strconv.FormatBool(crt.Spec.IsCA) - if crt.Spec.Duration != nil { - duration := crt.Spec.Duration.Duration - csr.Annotations[experimentalapi.CertificateSigningRequestDurationAnnotationKey] = duration.String() - seconds := int32(duration.Seconds()) // technically this could overflow but I do not think it matters - csr.Spec.ExpirationSeconds = &seconds // if this is less than 600, the API server will fail the request - } - - return csr, nil -} - -func generateCSR(crt *cmapi.Certificate, pk []byte) ([]byte, error) { - csr, err := pki.GenerateCSR(crt) - if err != nil { - return nil, err - } - - signer, err := pki.DecodePrivateKeyBytes(pk) - if err != nil { - return nil, err - } - - csrDER, err := pki.EncodeCSR(csr, signer) - if err != nil { - return nil, err - } - - csrPEM := pem.EncodeToMemory(&pem.Block{ - Type: "CERTIFICATE REQUEST", Bytes: csrDER, - }) - - return csrPEM, nil -} - -// storeCertificate fetches the x509 certificate from a -// CertificateSigningRequest and stores the certificate in file specified by -// certFilename. Assumes request is signed, otherwise returns error. -func storeCertificate(req *certificatesv1.CertificateSigningRequest, fileName string) error { - // If request not signed yet, error - if len(req.Status.Certificate) == 0 { - return errors.New("CertificateSigningRequest is not ready yet, unable to fetch certificate") - } - - // Store certificate to file - err := os.WriteFile(fileName, req.Status.Certificate, 0600) - if err != nil { - return fmt.Errorf("error when writing certificate to file: %s", err) - } - - return nil -} diff --git a/cmd/ctl/pkg/create/certificatesigningrequest/certificatesigningrequest_test.go b/cmd/ctl/pkg/create/certificatesigningrequest/certificatesigningrequest_test.go deleted file mode 100644 index 71eae78ab2d..00000000000 --- a/cmd/ctl/pkg/create/certificatesigningrequest/certificatesigningrequest_test.go +++ /dev/null @@ -1,101 +0,0 @@ -/* -Copyright 2020 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. -*/ - -package certificatesigningrequest - -import ( - "testing" -) - -func Test_Validate(t *testing.T) { - tests := map[string]struct { - inputFile string - inputArgs []string - keyFilename string - certFilename string - fetchCert bool - - expErr bool - expErrMsg string - }{ - "CSR name not passed as arg throws error": { - inputFile: "example.yaml", - inputArgs: []string{}, - expErr: true, - expErrMsg: "the name of the CertificateSigningRequest to be created has to be provided as argument", - }, - "More than one arg throws error": { - inputFile: "example.yaml", - inputArgs: []string{"hello", "World"}, - expErr: true, - expErrMsg: "only one argument can be passed in: the name of the CertificateSigningRequest", - }, - "not specifying path to yaml manifest throws error": { - inputFile: "", - inputArgs: []string{"hello"}, - expErr: true, - expErrMsg: "the path to a YAML manifest of a Certificate resource cannot be empty, please specify by using --from-certificate-file or -f flag", - }, - "key filename and cert filename are optional flags": { - inputFile: "example.yaml", - inputArgs: []string{"hello"}, - keyFilename: "", - certFilename: "", - expErr: false, - }, - "identical key filename and cert filename throws error": { - inputFile: "example.yaml", - inputArgs: []string{"hello"}, - keyFilename: "same", - certFilename: "same", - expErr: true, - expErrMsg: "the file to store private key cannot be the same as the file to store certificate", - }, - "cannot specify cert filename without fetch-certificate flag": { - inputFile: "example.yaml", - inputArgs: []string{"hello"}, - certFilename: "cert.crt", - fetchCert: false, - expErr: true, - expErrMsg: "cannot specify file to store certificate if not waiting for and fetching certificate, please set --fetch-certificate or -w flag", - }, - } - - for name, test := range tests { - t.Run(name, func(t *testing.T) { - opts := &Options{ - InputFilename: test.inputFile, - KeyFilename: test.keyFilename, - CertFileName: test.certFilename, - FetchCert: test.fetchCert, - } - - // Validating args and flags - err := opts.Validate(test.inputArgs) - if err != nil { - if !test.expErr { - t.Fatalf("got unexpected error when validating args and flags: %v", err) - } - if err.Error() != test.expErrMsg { - t.Fatalf("got unexpected error when validating args and flags, expected: %v; actual: %v", test.expErrMsg, err) - } - } else if test.expErr { - // got no error - t.Errorf("expected but got no error validating args and flags") - } - }) - } -} diff --git a/cmd/ctl/pkg/create/create.go b/cmd/ctl/pkg/create/create.go deleted file mode 100644 index 7b46ad30db2..00000000000 --- a/cmd/ctl/pkg/create/create.go +++ /dev/null @@ -1,42 +0,0 @@ -/* -Copyright 2020 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. -*/ - -package create - -import ( - "context" - - "github.com/spf13/cobra" - "k8s.io/cli-runtime/pkg/genericclioptions" - - "github.com/cert-manager/cert-manager/cmd/ctl/pkg/create/certificaterequest" -) - -func NewCmdCreate(ctx context.Context, ioStreams genericclioptions.IOStreams) *cobra.Command { - cmds := NewCmdCreateBare() - cmds.AddCommand(certificaterequest.NewCmdCreateCR(ctx, ioStreams)) - - return cmds -} - -// NewCmdCreateBare creates a bare Create Command, without any subcommands -func NewCmdCreateBare() *cobra.Command { - return &cobra.Command{ - Use: "create", - Short: "Create cert-manager resources", - Long: `Create cert-manager resources e.g. a CertificateRequest`, - } -} diff --git a/cmd/ctl/pkg/deny/deny.go b/cmd/ctl/pkg/deny/deny.go deleted file mode 100644 index 2d398773532..00000000000 --- a/cmd/ctl/pkg/deny/deny.go +++ /dev/null @@ -1,142 +0,0 @@ -/* -Copyright 2021 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. -*/ - -package deny - -import ( - "context" - "errors" - "fmt" - - "github.com/spf13/cobra" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/cli-runtime/pkg/genericclioptions" - cmdutil "k8s.io/kubectl/pkg/cmd/util" - "k8s.io/kubectl/pkg/util/i18n" - "k8s.io/kubectl/pkg/util/templates" - - "github.com/cert-manager/cert-manager/cmd/ctl/pkg/build" - "github.com/cert-manager/cert-manager/cmd/ctl/pkg/factory" - apiutil "github.com/cert-manager/cert-manager/pkg/api/util" - cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" - cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" -) - -var ( - example = templates.Examples(i18n.T(build.WithTemplate(` -# Deny a CertificateRequest with the name 'my-cr' -{{.BuildName}} deny my-cr - -# Deny a CertificateRequest in namespace default -{{.BuildName}} deny my-cr --namespace default - -# Deny a CertificateRequest giving a custom reason and message -{{.BuildName}} deny my-cr --reason "ManualDenial" --reason "Denied by PKI department" -`))) -) - -// Options is a struct to support create certificaterequest command -type Options struct { - // Reason is the string that will be set on the Reason field of the Denied - // condition. - Reason string - // Message is the string that will be set on the Message field of the - // Denied condition. - Message string - - genericclioptions.IOStreams - *factory.Factory -} - -// NewOptions returns initialized Options -func NewOptions(ioStreams genericclioptions.IOStreams) *Options { - return &Options{ - IOStreams: ioStreams, - } -} - -func NewCmdDeny(ctx context.Context, ioStreams genericclioptions.IOStreams) *cobra.Command { - o := NewOptions(ioStreams) - - cmd := &cobra.Command{ - Use: "deny", - Short: "Deny a CertificateRequest", - Long: `Mark a CertificateRequest as Denied, so it may never be signed by a configured Issuer.`, - Example: example, - ValidArgsFunction: factory.ValidArgsListCertificateRequests(ctx, &o.Factory), - Run: func(cmd *cobra.Command, args []string) { - cmdutil.CheckErr(o.Validate(args)) - cmdutil.CheckErr(o.Run(ctx, args)) - }, - } - - cmd.Flags().StringVar(&o.Reason, "reason", "KubectlCertManager", - "The reason to give as to what denied this CertificateRequest.") - cmd.Flags().StringVar(&o.Message, "message", fmt.Sprintf("manually denied by %q", build.Name()), - "The message to give as to why this CertificateRequest was denied.") - - o.Factory = factory.New(ctx, cmd) - - return cmd -} - -// Validate validates the provided options -func (o *Options) Validate(args []string) error { - if len(args) < 1 { - return errors.New("the name of the CertificateRequest to deny has to be provided as an argument") - } - if len(args) > 1 { - return errors.New("only one argument can be passed: the name of the CertificateRequest") - } - - if len(o.Reason) == 0 { - return errors.New("a reason must be given as to who denied this CertificateRequest") - } - - if len(o.Message) == 0 { - return errors.New("a message must be given as to why this CertificateRequest is denied") - } - - return nil -} - -// Run executes deny command -func (o *Options) Run(ctx context.Context, args []string) error { - cr, err := o.CMClient.CertmanagerV1().CertificateRequests(o.Namespace).Get(ctx, args[0], metav1.GetOptions{}) - if err != nil { - return err - } - - if apiutil.CertificateRequestIsApproved(cr) { - return errors.New("CertificateRequest is already approved") - } - - if apiutil.CertificateRequestIsDenied(cr) { - return errors.New("CertificateRequest is already denied") - } - - apiutil.SetCertificateRequestCondition(cr, cmapi.CertificateRequestConditionDenied, - cmmeta.ConditionTrue, o.Reason, o.Message) - - _, err = o.CMClient.CertmanagerV1().CertificateRequests(o.Namespace).UpdateStatus(ctx, cr, metav1.UpdateOptions{}) - if err != nil { - return err - } - - fmt.Fprintf(o.Out, "Denied CertificateRequest '%s/%s'\n", cr.Namespace, cr.Name) - - return nil -} diff --git a/cmd/ctl/pkg/deny/deny_test.go b/cmd/ctl/pkg/deny/deny_test.go deleted file mode 100644 index ba4fda33df7..00000000000 --- a/cmd/ctl/pkg/deny/deny_test.go +++ /dev/null @@ -1,84 +0,0 @@ -/* -Copyright 2021 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. -*/ - -package deny - -import ( - "testing" -) - -func TestValidate(t *testing.T) { - tests := map[string]struct { - args []string - reason, message string - expErr bool - expErrMsg string - }{ - "CR name not passed as arg throws error": { - args: []string{}, - reason: "", - message: "", - expErr: true, - expErrMsg: "the name of the CertificateRequest to deny has to be provided as an argument", - }, - "multiple CR names passed as arg throws error": { - args: []string{"cr-1", "cr-1"}, - reason: "", - message: "", - expErr: true, - expErrMsg: "only one argument can be passed: the name of the CertificateRequest", - }, - "empty reason given should throw error": { - args: []string{"cr-1"}, - reason: "", - message: "", - expErr: true, - expErrMsg: "a reason must be given as to who denied this CertificateRequest", - }, - "empty message given should throw error": { - args: []string{"cr-1"}, - reason: "foo", - message: "", - expErr: true, - expErrMsg: "a message must be given as to why this CertificateRequest is denied", - }, - "all fields populated should not error": { - args: []string{"cr-1"}, - reason: "foo", - message: "bar", - expErr: false, - }, - } - - for name, test := range tests { - t.Run(name, func(t *testing.T) { - opts := &Options{ - Reason: test.reason, - Message: test.message, - } - - // Validating args and flags - err := opts.Validate(test.args) - if (err != nil) != test.expErr { - t.Errorf("unexpected error, exp=%t got=%v", - test.expErr, err) - } - if err != nil && err.Error() != test.expErrMsg { - t.Errorf("got unexpected error when validating args and flags, expected: %v; actual: %v", test.expErrMsg, err) - } - }) - } -} diff --git a/cmd/ctl/pkg/experimental/experimental.go b/cmd/ctl/pkg/experimental/experimental.go deleted file mode 100644 index 3ba19322e64..00000000000 --- a/cmd/ctl/pkg/experimental/experimental.go +++ /dev/null @@ -1,46 +0,0 @@ -/* -Copyright 2021 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. -*/ - -package experimental - -import ( - "context" - - "github.com/spf13/cobra" - "k8s.io/cli-runtime/pkg/genericclioptions" - - "github.com/cert-manager/cert-manager/cmd/ctl/pkg/create" - "github.com/cert-manager/cert-manager/cmd/ctl/pkg/create/certificatesigningrequest" - "github.com/cert-manager/cert-manager/cmd/ctl/pkg/install" - "github.com/cert-manager/cert-manager/cmd/ctl/pkg/uninstall" -) - -func NewCmdExperimental(ctx context.Context, ioStreams genericclioptions.IOStreams) *cobra.Command { - cmds := &cobra.Command{ - Use: "experimental", - Aliases: []string{"x"}, - Short: "Interact with experimental features", - Long: "Interact with experimental features", - } - - create := create.NewCmdCreateBare() - create.AddCommand(certificatesigningrequest.NewCmdCreateCSR(ctx, ioStreams)) - cmds.AddCommand(create) - cmds.AddCommand(install.NewCmdInstall(ctx, ioStreams)) - cmds.AddCommand(uninstall.NewCmd(ctx, ioStreams)) - - return cmds -} diff --git a/cmd/ctl/pkg/factory/validargs.go b/cmd/ctl/pkg/factory/validargs.go deleted file mode 100644 index 48b1d1a18b8..00000000000 --- a/cmd/ctl/pkg/factory/validargs.go +++ /dev/null @@ -1,152 +0,0 @@ -/* -Copyright 2021 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. -*/ - -package factory - -import ( - "context" - - "github.com/spf13/cobra" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -// ValidArgsListCertificates returns a cobra ValidArgsFunction for listing Certificates. -func ValidArgsListCertificates(ctx context.Context, factory **Factory) func(_ *cobra.Command, args []string, _ string) ([]string, cobra.ShellCompDirective) { - return func(_ *cobra.Command, args []string, _ string) ([]string, cobra.ShellCompDirective) { - if len(args) > 0 { - return nil, cobra.ShellCompDirectiveNoFileComp - } - - f := (*factory) - if err := f.complete(); err != nil { - return nil, cobra.ShellCompDirectiveNoFileComp - } - - certList, err := f.CMClient.CertmanagerV1().Certificates(f.Namespace).List(ctx, metav1.ListOptions{}) - if err != nil { - return nil, cobra.ShellCompDirectiveError - } - - var names []string - for _, cert := range certList.Items { - names = append(names, cert.Name) - } - - return names, cobra.ShellCompDirectiveNoFileComp - } -} - -// ValidArgsListSecrets returns a cobra ValidArgsFunction for listing Secrets. -func ValidArgsListSecrets(ctx context.Context, factory **Factory) func(_ *cobra.Command, args []string, _ string) ([]string, cobra.ShellCompDirective) { - return func(_ *cobra.Command, args []string, _ string) ([]string, cobra.ShellCompDirective) { - if len(args) > 0 { - return nil, cobra.ShellCompDirectiveNoFileComp - } - - f := (*factory) - if err := f.complete(); err != nil { - return nil, cobra.ShellCompDirectiveNoFileComp - } - - secretsList, err := f.KubeClient.CoreV1().Secrets(f.Namespace).List(ctx, metav1.ListOptions{}) - if err != nil { - return nil, cobra.ShellCompDirectiveError - } - - var names []string - for _, secret := range secretsList.Items { - names = append(names, secret.Name) - } - - return names, cobra.ShellCompDirectiveNoFileComp - } -} - -// ValidArgsListCertificateSigningRequests returns a cobra ValidArgsFunction for -// listing CertificateSigningRequests. -func ValidArgsListCertificateSigningRequests(ctx context.Context, factory **Factory) func(_ *cobra.Command, args []string, _ string) ([]string, cobra.ShellCompDirective) { - return func(_ *cobra.Command, args []string, _ string) ([]string, cobra.ShellCompDirective) { - if len(args) > 0 { - return nil, cobra.ShellCompDirectiveNoFileComp - } - - f := (*factory) - if err := f.complete(); err != nil { - return nil, cobra.ShellCompDirectiveNoFileComp - } - - csrList, err := f.KubeClient.CertificatesV1().CertificateSigningRequests().List(ctx, metav1.ListOptions{}) - if err != nil { - return nil, cobra.ShellCompDirectiveError - } - - var names []string - for _, csr := range csrList.Items { - names = append(names, csr.Name) - } - - return names, cobra.ShellCompDirectiveNoFileComp - } -} - -// ValidArgsListCertificateRequests returns a cobra ValidArgsFunction for listing -// CertificateRequests. -func ValidArgsListCertificateRequests(ctx context.Context, factory **Factory) func(_ *cobra.Command, args []string, _ string) ([]string, cobra.ShellCompDirective) { - return func(_ *cobra.Command, args []string, _ string) ([]string, cobra.ShellCompDirective) { - if len(args) > 0 { - return nil, cobra.ShellCompDirectiveNoFileComp - } - f := (*factory) - if err := f.complete(); err != nil { - return nil, cobra.ShellCompDirectiveNoFileComp - } - crList, err := f.CMClient.CertmanagerV1().CertificateRequests(f.Namespace).List(ctx, metav1.ListOptions{}) - if err != nil { - return nil, cobra.ShellCompDirectiveError - } - var names []string - for _, cr := range crList.Items { - names = append(names, cr.Name) - } - return names, cobra.ShellCompDirectiveNoFileComp - } -} - -// validArgsListNamespaces returns a cobra ValidArgsFunction for listing -// namespaces. -func validArgsListNamespaces(ctx context.Context, factory *Factory) func(_ *cobra.Command, args []string, _ string) ([]string, cobra.ShellCompDirective) { - return func(_ *cobra.Command, args []string, _ string) ([]string, cobra.ShellCompDirective) { - if len(args) > 0 { - return nil, cobra.ShellCompDirectiveNoFileComp - } - - if err := factory.complete(); err != nil { - return nil, cobra.ShellCompDirectiveNoFileComp - } - - namespaceList, err := factory.KubeClient.CoreV1().Namespaces().List(ctx, metav1.ListOptions{}) - if err != nil { - return nil, cobra.ShellCompDirectiveError - } - - var names []string - for _, namespace := range namespaceList.Items { - names = append(names, namespace.Name) - } - - return names, cobra.ShellCompDirectiveNoFileComp - } -} diff --git a/cmd/ctl/pkg/inspect/inspect.go b/cmd/ctl/pkg/inspect/inspect.go deleted file mode 100644 index 3ed399fa8e5..00000000000 --- a/cmd/ctl/pkg/inspect/inspect.go +++ /dev/null @@ -1,38 +0,0 @@ -/* -Copyright 2020 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. -*/ - -package inspect - -import ( - "context" - - "github.com/spf13/cobra" - "k8s.io/cli-runtime/pkg/genericclioptions" - - "github.com/cert-manager/cert-manager/cmd/ctl/pkg/inspect/secret" -) - -func NewCmdInspect(ctx context.Context, ioStreams genericclioptions.IOStreams) *cobra.Command { - cmds := &cobra.Command{ - Use: "inspect", - Short: "Get details on certificate related resources", - Long: `Get details on certificate related resources, e.g. secrets`, - } - - cmds.AddCommand(secret.NewCmdInspectSecret(ctx, ioStreams)) - - return cmds -} diff --git a/cmd/ctl/pkg/inspect/secret/secret.go b/cmd/ctl/pkg/inspect/secret/secret.go deleted file mode 100644 index 39ab20a54d1..00000000000 --- a/cmd/ctl/pkg/inspect/secret/secret.go +++ /dev/null @@ -1,356 +0,0 @@ -/* -Copyright 2020 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. -*/ - -package secret - -import ( - "bytes" - "context" - "crypto/x509" - "errors" - "fmt" - "net/url" - "strings" - "text/template" - "time" - - "github.com/spf13/cobra" - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/cli-runtime/pkg/genericclioptions" - cmdutil "k8s.io/kubectl/pkg/cmd/util" - "k8s.io/kubectl/pkg/util/i18n" - "k8s.io/kubectl/pkg/util/templates" - k8sclock "k8s.io/utils/clock" - - "github.com/cert-manager/cert-manager/cmd/ctl/pkg/build" - "github.com/cert-manager/cert-manager/cmd/ctl/pkg/factory" - cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" - "github.com/cert-manager/cert-manager/pkg/util/pki" -) - -var clock k8sclock.Clock = k8sclock.RealClock{} - -const validForTemplate = `Valid for: - DNS Names: {{ .DNSNames }} - URIs: {{ .URIs }} - IP Addresses: {{ .IPAddresses }} - Email Addresses: {{ .EmailAddresses }} - Usages: {{ .KeyUsage }}` - -const validityPeriodTemplate = `Validity period: - Not Before: {{ .NotBefore }} - Not After: {{ .NotAfter }}` - -const issuedByTemplate = `Issued By: - Common Name: {{ .CommonName }} - Organization: {{ .CommonName }} - OrganizationalUnit: {{ .OrganizationalUnit }} - Country: {{ .Country }}` - -const issuedForTemplate = `Issued For: - Common Name: {{ .CommonName }} - Organization: {{ .CommonName }} - OrganizationalUnit: {{ .OrganizationalUnit }} - Country: {{ .Country }}` - -const certificateTemplate = `Certificate: - Signing Algorithm: {{ .SigningAlgorithm }} - Public Key Algorithm: {{ .PublicKeyAlgorithm }} - Serial Number: {{ .SerialNumber }} - Fingerprints: {{ .Fingerprints }} - Is a CA certificate: {{ .IsCACertificate }} - CRL: {{ .CRL }} - OCSP: {{ .OCSP }}` - -const debuggingTemplate = `Debugging: - Trusted by this computer: {{ .TrustedByThisComputer }} - CRL Status: {{ .CRLStatus }} - OCSP Status: {{ .OCSPStatus }}` - -var ( - long = templates.LongDesc(i18n.T(` -Get details about a kubernetes.io/tls typed secret`)) - - example = templates.Examples(i18n.T(build.WithTemplate(` -# Query information about a secret with name 'my-crt' in namespace 'my-namespace' -{{.BuildName}} inspect secret my-crt --namespace my-namespace -`))) -) - -// Options is a struct to support status certificate command -type Options struct { - genericclioptions.IOStreams - *factory.Factory -} - -// NewOptions returns initialized Options -func NewOptions(ioStreams genericclioptions.IOStreams) *Options { - return &Options{ - IOStreams: ioStreams, - } -} - -// NewCmdInspectSecret returns a cobra command for status certificate -func NewCmdInspectSecret(ctx context.Context, ioStreams genericclioptions.IOStreams) *cobra.Command { - o := NewOptions(ioStreams) - - cmd := &cobra.Command{ - Use: "secret", - Short: "Get details about a kubernetes.io/tls typed secret", - Long: long, - Example: example, - ValidArgsFunction: factory.ValidArgsListSecrets(ctx, &o.Factory), - Run: func(cmd *cobra.Command, args []string) { - cmdutil.CheckErr(o.Validate(args)) - cmdutil.CheckErr(o.Run(ctx, args)) - }, - } - - o.Factory = factory.New(ctx, cmd) - - return cmd -} - -// Validate validates the provided options -func (o *Options) Validate(args []string) error { - if len(args) < 1 { - return errors.New("the name of the Secret has to be provided as argument") - } - if len(args) > 1 { - return errors.New("only one argument can be passed in: the name of the Secret") - } - return nil -} - -// Run executes status certificate command -func (o *Options) Run(ctx context.Context, args []string) error { - secret, err := o.KubeClient.CoreV1().Secrets(o.Namespace).Get(ctx, args[0], metav1.GetOptions{}) - if err != nil { - return fmt.Errorf("error when finding Secret %q: %w\n", args[0], err) - } - - certData := secret.Data[corev1.TLSCertKey] - certs, err := splitPEMs(certData) - if err != nil { - return err - } - if len(certs) < 1 { - return errors.New("no PEM data found in secret") - } - - intermediates := [][]byte(nil) - if len(certs) > 1 { - intermediates = certs[1:] - } - - // we only want to inspect the leaf certificate - x509Cert, err := pki.DecodeX509CertificateBytes(certs[0]) - if err != nil { - return fmt.Errorf("error when parsing 'tls.crt': %w", err) - } - - out := []string{ - describeValidFor(x509Cert), - describeValidityPeriod(x509Cert), - describeIssuedBy(x509Cert), - describeIssuedFor(x509Cert), - describeCertificate(x509Cert), - describeDebugging(x509Cert, intermediates, secret.Data[cmmeta.TLSCAKey]), - } - - fmt.Println(strings.Join(out, "\n\n")) - - return nil -} - -func describeValidFor(cert *x509.Certificate) string { - var b bytes.Buffer - template.Must(template.New("validForTemplate").Parse(validForTemplate)).Execute(&b, struct { - DNSNames string - URIs string - IPAddresses string - EmailAddresses string - KeyUsage string - }{ - DNSNames: printSlice(cert.DNSNames), - URIs: printSlice(pki.URLsToString(cert.URIs)), - IPAddresses: printSlice(pki.IPAddressesToString(cert.IPAddresses)), - EmailAddresses: printSlice(cert.EmailAddresses), - KeyUsage: printKeyUsage(pki.BuildCertManagerKeyUsages(cert.KeyUsage, cert.ExtKeyUsage)), - }) - - return b.String() -} - -func describeValidityPeriod(cert *x509.Certificate) string { - var b bytes.Buffer - template.Must(template.New("validityPeriodTemplate").Parse(validityPeriodTemplate)).Execute(&b, struct { - NotBefore string - NotAfter string - }{ - NotBefore: cert.NotBefore.Format(time.RFC1123), - NotAfter: cert.NotAfter.Format(time.RFC1123), - }) - - return b.String() -} - -func describeIssuedBy(cert *x509.Certificate) string { - var b bytes.Buffer - template.Must(template.New("issuedByTemplate").Parse(issuedByTemplate)).Execute(&b, struct { - CommonName string - Organization string - OrganizationalUnit string - Country string - }{ - CommonName: printOrNone(cert.Issuer.CommonName), - Organization: printSliceOrOne(cert.Issuer.Organization), - OrganizationalUnit: printSliceOrOne(cert.Issuer.Organization), - Country: printSliceOrOne(cert.Issuer.Country), - }) - - return b.String() -} - -func describeIssuedFor(cert *x509.Certificate) string { - var b bytes.Buffer - template.Must(template.New("issuedForTemplate").Parse(issuedForTemplate)).Execute(&b, struct { - CommonName string - Organization string - OrganizationalUnit string - Country string - }{ - CommonName: printOrNone(cert.Subject.CommonName), - Organization: printSliceOrOne(cert.Subject.Organization), - OrganizationalUnit: printSliceOrOne(cert.Subject.Organization), - Country: printSliceOrOne(cert.Subject.Country), - }) - - return b.String() -} - -func describeCertificate(cert *x509.Certificate) string { - var b bytes.Buffer - template.Must(template.New("certificateTemplate").Parse(certificateTemplate)).Execute(&b, struct { - SigningAlgorithm string - PublicKeyAlgorithm string - SerialNumber string - Fingerprints string - IsCACertificate bool - CRL string - OCSP string - }{ - SigningAlgorithm: cert.SignatureAlgorithm.String(), - PublicKeyAlgorithm: cert.PublicKeyAlgorithm.String(), - SerialNumber: cert.SerialNumber.String(), - Fingerprints: fingerprintCert(cert), - IsCACertificate: cert.IsCA, - CRL: printSliceOrOne(cert.CRLDistributionPoints), - OCSP: printSliceOrOne(cert.OCSPServer), - }) - - return b.String() -} - -func describeDebugging(cert *x509.Certificate, intermediates [][]byte, ca []byte) string { - var b bytes.Buffer - template.Must(template.New("debuggingTemplate").Parse(debuggingTemplate)).Execute(&b, struct { - TrustedByThisComputer string - CRLStatus string - OCSPStatus string - }{ - TrustedByThisComputer: describeTrusted(cert, intermediates), - CRLStatus: describeCRL(cert), - OCSPStatus: describeOCSP(cert, intermediates, ca), - }) - - return b.String() -} - -func describeCRL(cert *x509.Certificate) string { - if len(cert.CRLDistributionPoints) < 1 { - return "No CRL endpoints set" - } - - hasChecked := false - for _, crlURL := range cert.CRLDistributionPoints { - u, err := url.Parse(crlURL) - if err != nil { - return fmt.Sprintf("Invalid CRL URL: %v", err) - } - if u.Scheme != "ldap" && u.Scheme != "https" { - continue - } - - hasChecked = true - valid, err := checkCRLValidCert(cert, crlURL) - if err != nil { - return fmt.Sprintf("Cannot check CRL: %s", err.Error()) - } - if !valid { - return fmt.Sprintf("Revoked by %s", crlURL) - } - } - - if !hasChecked { - return "No CRL endpoints we support found" - } - - return "Valid" -} - -func describeOCSP(cert *x509.Certificate, intermediates [][]byte, ca []byte) string { - if len(ca) > 1 { - intermediates = append([][]byte{ca}, intermediates...) - } - if len(intermediates) < 1 { - return "Cannot check OCSP, does not have a CA or intermediate certificate provided" - } - issuerCert, err := pki.DecodeX509CertificateBytes(intermediates[len(intermediates)-1]) - if err != nil { - return fmt.Sprintf("Cannot parse intermediate certificate: %s", err.Error()) - } - - valid, err := checkOCSPValidCert(cert, issuerCert) - if err != nil { - return fmt.Sprintf("Cannot check OCSP: %s", err.Error()) - } - - if !valid { - return "Marked as revoked" - } - - return "valid" -} - -func describeTrusted(cert *x509.Certificate, intermediates [][]byte) string { - systemPool, err := x509.SystemCertPool() - if err != nil { - return fmt.Sprintf("Error getting system CA store: %s", err.Error()) - } - for _, intermediate := range intermediates { - systemPool.AppendCertsFromPEM(intermediate) - } - _, err = cert.Verify(x509.VerifyOptions{ - Roots: systemPool, - CurrentTime: clock.Now(), - }) - if err == nil { - return "yes" - } - return fmt.Sprintf("no: %s", err.Error()) -} diff --git a/cmd/ctl/pkg/inspect/secret/secret_test.go b/cmd/ctl/pkg/inspect/secret/secret_test.go deleted file mode 100644 index fbe63ff64ed..00000000000 --- a/cmd/ctl/pkg/inspect/secret/secret_test.go +++ /dev/null @@ -1,392 +0,0 @@ -/* -Copyright 2020 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. -*/ - -package secret - -import ( - "crypto/x509" - "strings" - "testing" - "time" - - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - fakeclock "k8s.io/utils/clock/testing" - - v1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" - "github.com/cert-manager/cert-manager/pkg/util/pki" - "github.com/cert-manager/cert-manager/test/unit/gen" -) - -var ( - testCert string - testCertSerial string - testCertFingerprint string - testNotBefore string - testNotAfter string -) - -func init() { - caKey, err := pki.GenerateECPrivateKey(256) - if err != nil { - panic(err) - } - caCertificateTemplate := gen.Certificate( - "ca", - gen.SetCertificateCommonName("testing-ca"), - gen.SetCertificateIsCA(true), - gen.SetCertificateKeyAlgorithm(v1.ECDSAKeyAlgorithm), - gen.SetCertificateKeySize(256), - gen.SetCertificateKeyUsages( - v1.UsageDigitalSignature, - v1.UsageKeyEncipherment, - v1.UsageCertSign, - ), - gen.SetCertificateNotBefore(metav1.Time{Time: time.Now().Add(-time.Hour)}), - gen.SetCertificateNotAfter(metav1.Time{Time: time.Now().Add(time.Hour)}), - ) - caCertificateTemplate.Spec.Subject = &v1.X509Subject{ - Organizations: []string{"Internet Widgets, Inc."}, - Countries: []string{"US"}, - OrganizationalUnits: []string{"WWW"}, - Localities: []string{"San Francisco"}, - Provinces: []string{"California"}, - } - caX509Cert, err := pki.GenerateTemplate(caCertificateTemplate) - if err != nil { - panic(err) - } - _, caCert, err := pki.SignCertificate(caX509Cert, caX509Cert, caKey.Public(), caKey) - if err != nil { - panic(err) - } - - testCertKey, err := pki.GenerateECPrivateKey(256) - if err != nil { - panic(err) - } - testCertTemplate := gen.Certificate( - "testing-cert", - gen.SetCertificateDNSNames("cert-manager.test"), - gen.SetCertificateIPs("10.0.0.1"), - gen.SetCertificateURIs("spiffe://cert-manager.test"), - gen.SetCertificateEmails("test@cert-manager.io"), - gen.SetCertificateIsCA(true), - gen.SetCertificateKeyAlgorithm(v1.ECDSAKeyAlgorithm), - gen.SetCertificateIsCA(false), - gen.SetCertificateKeySize(256), - gen.SetCertificateKeyUsages( - v1.UsageDigitalSignature, - v1.UsageKeyEncipherment, - v1.UsageServerAuth, - v1.UsageClientAuth, - ), - gen.SetCertificateNotBefore(metav1.Time{Time: time.Now().Add(-30 * time.Minute)}), - gen.SetCertificateNotAfter(metav1.Time{Time: time.Now().Add(30 * time.Minute)}), - ) - testCertTemplate.Spec.Subject = &v1.X509Subject{ - Organizations: []string{"cncf"}, - Countries: []string{"GB"}, - OrganizationalUnits: []string{"cert-manager"}, - } - testX509Cert, err := pki.GenerateTemplate(testCertTemplate) - if err != nil { - panic(err) - } - - testCertPEM, testCertGo, err := pki.SignCertificate(testX509Cert, caCert, testCertKey.Public(), caKey) - if err != nil { - panic(err) - } - - testCert = string(testCertPEM) - testCertSerial = testCertGo.SerialNumber.String() - testCertFingerprint = fingerprintCert(testCertGo) - testNotBefore = testCertGo.NotBefore.Format(time.RFC1123) - testNotAfter = testCertGo.NotAfter.Format(time.RFC1123) -} - -func MustParseCertificate(t *testing.T, certData string) *x509.Certificate { - x509Cert, err := pki.DecodeX509CertificateBytes([]byte(certData)) - if err != nil { - t.Fatalf("error when parsing crt: %v", err) - } - - return x509Cert -} - -func Test_describeCRL(t *testing.T) { - tests := []struct { - name string - cert *x509.Certificate - want string - }{ - { - name: "Print cert without CRL", - cert: MustParseCertificate(t, testCert), - want: "No CRL endpoints set", - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := describeCRL(tt.cert); got != tt.want { - t.Errorf("describeCRL() = %v, want %v", makeInvisibleVisible(got), makeInvisibleVisible(tt.want)) - } - }) - } -} - -func Test_describeCertificate(t *testing.T) { - tests := []struct { - name string - cert *x509.Certificate - want string - }{ - { - name: "Describe test certificate", - cert: MustParseCertificate(t, testCert), - want: `Certificate: - Signing Algorithm: ECDSA-SHA256 - Public Key Algorithm: ECDSA - Serial Number: ` + testCertSerial + ` - Fingerprints: ` + testCertFingerprint + ` - Is a CA certificate: false - CRL: - OCSP: `, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := describeCertificate(tt.cert); got != tt.want { - t.Errorf("describeCertificate() = %v, want %v", makeInvisibleVisible(got), makeInvisibleVisible(tt.want)) - } - }) - } -} - -func Test_describeDebugging(t *testing.T) { - type args struct { - cert *x509.Certificate - intermediates [][]byte - ca []byte - } - tests := []struct { - name string - args args - want string - }{ - { - name: "Debug test cert without trusting CA", - args: args{ - cert: MustParseCertificate(t, testCert), - intermediates: nil, - ca: nil, - }, - want: `Debugging: - Trusted by this computer: no: x509: certificate signed by unknown authority - CRL Status: No CRL endpoints set - OCSP Status: Cannot check OCSP, does not have a CA or intermediate certificate provided`, - }, - // TODO: add fake clock and test with trusting CA - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := describeDebugging(tt.args.cert, tt.args.intermediates, tt.args.ca); got != tt.want { - t.Errorf("describeDebugging() = %v, want %v", makeInvisibleVisible(got), makeInvisibleVisible(tt.want)) - } - }) - } -} - -func Test_describeIssuedBy(t *testing.T) { - tests := []struct { - name string - cert *x509.Certificate - want string - }{ - { - name: "Describe test certificate", - cert: MustParseCertificate(t, testCert), - want: `Issued By: - Common Name: testing-ca - Organization: testing-ca - OrganizationalUnit: Internet Widgets, Inc. - Country: US`, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := describeIssuedBy(tt.cert); got != tt.want { - t.Errorf("describeIssuedBy() = %v, want %v", makeInvisibleVisible(got), makeInvisibleVisible(tt.want)) - } - }) - } -} - -func Test_describeIssuedFor(t *testing.T) { - tests := []struct { - name string - cert *x509.Certificate - want string - }{ - { - name: "Describe test cert", - cert: MustParseCertificate(t, testCert), - want: `Issued For: - Common Name: - Organization: - OrganizationalUnit: cncf - Country: GB`, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := describeIssuedFor(tt.cert); got != tt.want { - t.Errorf("describeIssuedFor() = %v, want %v", makeInvisibleVisible(got), makeInvisibleVisible(tt.want)) - } - }) - } -} - -func Test_describeOCSP(t *testing.T) { - type args struct { - cert *x509.Certificate - intermediates [][]byte - ca []byte - } - tests := []struct { - name string - args args - want string - }{ - { - name: "Describe cert with no OCSP", - args: args{ - cert: MustParseCertificate(t, testCert), - }, - want: "Cannot check OCSP, does not have a CA or intermediate certificate provided", - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := describeOCSP(tt.args.cert, tt.args.intermediates, tt.args.ca); got != tt.want { - t.Errorf("describeOCSP() = %v, want %v", makeInvisibleVisible(got), makeInvisibleVisible(tt.want)) - } - }) - } -} - -func Test_describeTrusted(t *testing.T) { - // set clock to when our test cert was trusted - t1, _ := time.Parse("Thu, 27 Nov 2020 10:00:00 UTC", time.RFC1123) - clock = fakeclock.NewFakeClock(t1) - type args struct { - cert *x509.Certificate - intermediates [][]byte - } - tests := []struct { - name string - args args - want string - }{ - { - name: "Describe test certificate", - args: args{ - cert: MustParseCertificate(t, testCert), - intermediates: nil, - }, - want: "no: x509: certificate signed by unknown authority", - }, - { - name: "Describe test certificate with adding it to the trust store", - args: args{ - cert: MustParseCertificate(t, testCert), - intermediates: [][]byte{[]byte(testCert)}, - }, - want: "yes", - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := describeTrusted(tt.args.cert, tt.args.intermediates); got != tt.want { - t.Errorf("describeTrusted() = %v, want %v", makeInvisibleVisible(got), makeInvisibleVisible(tt.want)) - } - }) - } -} - -func Test_describeValidFor(t *testing.T) { - tests := []struct { - name string - cert *x509.Certificate - want string - }{ - { - name: "Describe test certificate", - cert: MustParseCertificate(t, testCert), - want: `Valid for: - DNS Names: - - cert-manager.test - URIs: - - spiffe://cert-manager.test - IP Addresses: - - 10.0.0.1 - Email Addresses: - - test@cert-manager.io - Usages: - - digital signature - - key encipherment - - server auth - - client auth`, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := describeValidFor(tt.cert); got != tt.want { - t.Errorf("describeValidFor() = %v, want %v", makeInvisibleVisible(got), makeInvisibleVisible(tt.want)) - } - }) - } -} - -func Test_describeValidityPeriod(t *testing.T) { - tests := []struct { - name string - cert *x509.Certificate - want string - }{ - { - name: "Describe test certificate", - cert: MustParseCertificate(t, testCert), - want: `Validity period: - Not Before: ` + testNotBefore + ` - Not After: ` + testNotAfter, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := describeValidityPeriod(tt.cert); got != tt.want { - t.Errorf("describeValidityPeriod() = %v, want %v", makeInvisibleVisible(got), makeInvisibleVisible(tt.want)) - } - }) - } -} - -func makeInvisibleVisible(in string) string { - in = strings.Replace(in, "\n", "\\n\n", -1) - in = strings.Replace(in, "\t", "\\t", -1) - - return in -} diff --git a/cmd/ctl/pkg/inspect/secret/util.go b/cmd/ctl/pkg/inspect/secret/util.go deleted file mode 100644 index 62e1591bca6..00000000000 --- a/cmd/ctl/pkg/inspect/secret/util.go +++ /dev/null @@ -1,185 +0,0 @@ -/* -Copyright 2020 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. -*/ - -package secret - -import ( - "bytes" - "crypto" - "crypto/sha256" - "crypto/x509" - "encoding/pem" - "errors" - "fmt" - "io" - "net/http" - "net/url" - "strings" - - "golang.org/x/crypto/ocsp" - - cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" -) - -func fingerprintCert(cert *x509.Certificate) string { - if cert == nil { - return "" - } - fingerprint := sha256.Sum256(cert.Raw) - - var buf bytes.Buffer - for i, f := range fingerprint { - if i > 0 { - fmt.Fprintf(&buf, ":") - } - fmt.Fprintf(&buf, "%02X", f) - } - - return buf.String() -} - -func checkOCSPValidCert(leafCert, issuerCert *x509.Certificate) (bool, error) { - if len(leafCert.OCSPServer) < 1 { - return false, errors.New("No OCSP Server set") - } - buffer, err := ocsp.CreateRequest(leafCert, issuerCert, &ocsp.RequestOptions{Hash: crypto.SHA1}) - if err != nil { - return false, fmt.Errorf("error creating OCSP request: %w", err) - } - - for _, ocspServer := range leafCert.OCSPServer { - httpRequest, err := http.NewRequest(http.MethodPost, ocspServer, bytes.NewBuffer(buffer)) - if err != nil { - return false, fmt.Errorf("error creating HTTP request: %w", err) - } - ocspUrl, err := url.Parse(ocspServer) - if err != nil { - return false, fmt.Errorf("error parsing OCSP URL: %w", err) - } - httpRequest.Header.Add("Content-Type", "application/ocsp-request") - httpRequest.Header.Add("Accept", "application/ocsp-response") - httpRequest.Header.Add("Host", ocspUrl.Host) - httpClient := &http.Client{} - httpResponse, err := httpClient.Do(httpRequest) - if err != nil { - return false, fmt.Errorf("error making HTTP request: %w", err) - } - defer httpResponse.Body.Close() - output, err := io.ReadAll(httpResponse.Body) - if err != nil { - return false, fmt.Errorf("error reading HTTP body: %w", err) - } - ocspResponse, err := ocsp.ParseResponse(output, issuerCert) - if err != nil { - return false, fmt.Errorf("error reading OCSP response: %w", err) - } - - if ocspResponse.Status == ocsp.Revoked { - // one OCSP revoked it do not trust - return false, nil - } - } - - return true, nil -} - -func checkCRLValidCert(cert *x509.Certificate, url string) (bool, error) { - resp, err := http.Get(url) - if err != nil { - return false, fmt.Errorf("error getting HTTP response: %w", err) - } - - body, err := io.ReadAll(resp.Body) - if err != nil { - return false, fmt.Errorf("error reading HTTP body: %w", err) - } - resp.Body.Close() - - crl, err := x509.ParseCRL(body) - if err != nil { - return false, fmt.Errorf("error parsing HTTP body: %w", err) - } - - // TODO: check CRL signature - - for _, revoked := range crl.TBSCertList.RevokedCertificates { - if cert.SerialNumber.Cmp(revoked.SerialNumber) == 0 { - return false, nil - } - } - - return true, nil -} - -func printSlice(in []string) string { - if len(in) < 1 { - return "" - } - - return "\n\t\t- " + strings.Trim(strings.Join(in, "\n\t\t- "), " ") -} - -func printSliceOrOne(in []string) string { - if len(in) < 1 { - return "" - } else if len(in) == 1 { - return in[0] - } - - return printSlice(in) -} - -func printOrNone(in string) string { - if in == "" { - return "" - } - - return in -} - -func printKeyUsage(in []cmapi.KeyUsage) string { - if len(in) < 1 { - return " " - } - - var usageStrings []string - for _, usage := range in { - usageStrings = append(usageStrings, string(usage)) - } - - return "\n\t\t- " + strings.Trim(strings.Join(usageStrings, "\n\t\t- "), " ") -} - -func splitPEMs(certData []byte) ([][]byte, error) { - certs := [][]byte(nil) - for { - block, rest := pem.Decode(certData) - if block == nil { - break // got no more certs to decode - } - // ignore private key data - if block.Type == "CERTIFICATE" { - buf := bytes.NewBuffer(nil) - err := pem.Encode(buf, block) - if err != nil { - return nil, fmt.Errorf("error when reencoding PEM: %s", err) - } - certs = append(certs, buf.Bytes()) - } - certData = rest - } - return certs, nil -} diff --git a/cmd/ctl/pkg/inspect/secret/util_test.go b/cmd/ctl/pkg/inspect/secret/util_test.go deleted file mode 100644 index 4d7489508c2..00000000000 --- a/cmd/ctl/pkg/inspect/secret/util_test.go +++ /dev/null @@ -1,236 +0,0 @@ -/* -Copyright 2020 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. -*/ - -package secret - -import ( - "crypto/x509" - "reflect" - "testing" - - cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" -) - -const testCertForFingerprinting = `-----BEGIN CERTIFICATE----- -MIICljCCAhugAwIBAgIUNAQr779ga/BNXyCpK7ddFbjAK98wCgYIKoZIzj0EAwMw -aTELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExFjAUBgNVBAcTDVNh -biBGcmFuY2lzY28xHzAdBgNVBAoTFkludGVybmV0IFdpZGdldHMsIEluYy4xDDAK -BgNVBAsTA1dXVzAeFw0yMTAyMjYxMDM1MDBaFw0yMjAyMjYxMDM1MDBaMDMxCzAJ -BgNVBAYTAkdCMQ0wCwYDVQQKEwRjbmNmMRUwEwYDVQQLEwxjZXJ0LW1hbmFnZXIw -WTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAATd5gWH2rkzWBGrr1jCR6JDB0dZOizZ -jCt2gnzNfzZmEg3rqxPvIakfT1lsjL2HrQyBRMQGGZhj7RkN7/VUM+VUo4HWMIHT -MA4GA1UdDwEB/wQEAwIFoDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIw -DAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQUCUEeUFyT7U3e6zP4q4VYEr2x0KcwHwYD -VR0jBBgwFoAUFkKAaJ18Vg9xFx3K7d5b7HjoSSMwVAYDVR0RBE0wS4IRY2VydC1t -YW5hZ2VyLnRlc3SBFHRlc3RAY2VydC1tYW5hZ2VyLmlvhwQKAAABhhpzcGlmZmU6 -Ly9jZXJ0LW1hbmFnZXIudGVzdDAKBggqhkjOPQQDAwNpADBmAjEA3Fv1aP+dBtBh -+DThW0QQO/Xl0CHQRKnJmJ8JjnleaMYFVdHf7dcf0ZeyOC26aUkdAjEA/fvxvhcz -Dtj+gY2rewoeJv5Pslli+SEObUslRaVtUMGxwUbmPU2fKuZHWBfe2FfA ------END CERTIFICATE----- -` - -func Test_fingerprintCert(t *testing.T) { - tests := []struct { - name string - cert *x509.Certificate - want string - }{ - { - name: "Fingerprint a valid cert", - cert: MustParseCertificate(t, testCertForFingerprinting), - want: "FF:D0:A8:85:0B:A4:5A:E1:FC:55:40:E1:FC:07:09:F1:02:AE:B9:EB:28:C4:01:23:B9:4F:C8:FA:9B:EF:F4:C1", - }, - { - name: "Fingerprint nil", - cert: nil, - want: "", - }, - { - name: "Fingerprint invalid cert", - cert: &x509.Certificate{Raw: []byte("fake")}, - want: "B5:D5:4C:39:E6:66:71:C9:73:1B:9F:47:1E:58:5D:82:62:CD:4F:54:96:3F:0C:93:08:2D:8D:CF:33:4D:4C:78", - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := fingerprintCert(tt.cert); got != tt.want { - t.Errorf("fingerprintCert() = %v, want %v", got, tt.want) - } - }) - } -} - -func Test_printKeyUsage(t *testing.T) { - type args struct { - in []cmapi.KeyUsage - } - tests := []struct { - name string - args args - want string - }{ - // TODO: Add test cases. - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := printKeyUsage(tt.args.in); got != tt.want { - t.Errorf("printKeyUsage() = %v, want %v", got, tt.want) - } - }) - } -} - -func Test_printOrNone(t *testing.T) { - tests := []struct { - name string - in string - want string - }{ - { - name: "Print none on empty", - in: "", - want: "", - }, - { - name: "Print value on not empty", - in: "ok", - want: "ok", - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := printOrNone(tt.in); got != tt.want { - t.Errorf("printOrNone() = %v, want %v", got, tt.want) - } - }) - } -} - -func Test_printSlice(t *testing.T) { - tests := []struct { - name string - in []string - want string - }{ - { - name: "Print test slice multiple objects", - in: []string{"test", "ok"}, - want: ` - - test - - ok`, - }, - { - name: "Print test slice one object", - in: []string{"test"}, - want: "\n\t\t- test", - }, - { - name: "Print nil slice", - in: nil, - want: "", - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := printSlice(tt.in); got != tt.want { - t.Errorf("printSlice() = %v, want %v", got, tt.want) - } - }) - } -} - -func Test_printSliceOrOne(t *testing.T) { - tests := []struct { - name string - in []string - want string - }{ - { - name: "Print test slice multiple objects", - in: []string{"test", "ok"}, - want: ` - - test - - ok`, - }, - { - name: "Print test slice one object", - in: []string{"test"}, - want: "test", - }, - { - name: "Print nil slice", - in: nil, - want: "", - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := printSliceOrOne(tt.in); got != tt.want { - t.Errorf("printSliceOrOne() = %v, want %v", got, tt.want) - } - }) - } -} - -func Test_splitPEMs(t *testing.T) { - type args struct { - certData []byte - } - tests := []struct { - name string - certData []byte - want [][]byte - wantErr bool - }{ - { - name: "Single PEM in file", - certData: []byte(testCert), - want: [][]byte{[]byte(testCert)}, - wantErr: false, - }, - { - name: "2 PEMs in file", - certData: []byte(testCert + "\n" + testCert), - want: [][]byte{[]byte(testCert), []byte(testCert)}, - wantErr: false, - }, - { - name: "Invalid input after a valid PEM", - certData: []byte(testCert + "\n\ninvalid"), - want: [][]byte{[]byte(testCert)}, - wantErr: false, - }, - { - name: "Invalid input without PEM block", - certData: []byte("invalid"), - want: nil, - wantErr: false, - }, - // TODO: somehow find an error case the PEM encoder/decoder is quite error resistant - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got, err := splitPEMs(tt.certData) - if (err != nil) != tt.wantErr { - t.Errorf("splitPEMs() error = %v, wantErr %v", err, tt.wantErr) - return - } - if !reflect.DeepEqual(got, tt.want) { - t.Errorf("splitPEMs() got = %v, want %v", got, tt.want) - } - }) - } -} diff --git a/cmd/ctl/pkg/install/helm/applycrd.go b/cmd/ctl/pkg/install/helm/applycrd.go deleted file mode 100644 index 4f1c8721af6..00000000000 --- a/cmd/ctl/pkg/install/helm/applycrd.go +++ /dev/null @@ -1,59 +0,0 @@ -/* -Copyright 2021 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. -*/ - -package helm - -import ( - "log" - "time" - - "helm.sh/helm/v3/pkg/action" - "k8s.io/cli-runtime/pkg/resource" -) - -// CreateCRDs creates cert manager CRDs. Before calling this function, we -// made sure that the CRDs are not yet installed on the cluster. -func CreateCRDs(allCRDs []*resource.Info, cfg *action.Configuration) error { - log.Printf("Creating the cert-manager CRDs") - // Create all CRDs - rr, err := cfg.KubeClient.Create(allCRDs) - if err != nil { - return err - } - createdCRDs := rr.Created - - // Invalidate the local cache, since it will not have the new CRDs - // present. - discoveryClient, err := cfg.RESTClientGetter.ToDiscoveryClient() - if err != nil { - return err - } - - log.Printf("Clearing discovery cache") - discoveryClient.Invalidate() - - // Give time for the CRD to be recognized. - if err := cfg.KubeClient.Wait(createdCRDs, 60*time.Second); err != nil { - return err - } - - // Make sure to force a rebuild of the cache. - if _, err := discoveryClient.ServerGroups(); err != nil { - return err - } - - return nil -} diff --git a/cmd/ctl/pkg/install/helm/resource.go b/cmd/ctl/pkg/install/helm/resource.go deleted file mode 100644 index 324b2554fb0..00000000000 --- a/cmd/ctl/pkg/install/helm/resource.go +++ /dev/null @@ -1,85 +0,0 @@ -/* -Copyright 2021 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. -*/ - -package helm - -import ( - "bytes" - "fmt" - - "helm.sh/helm/v3/pkg/kube" - apierrors "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/cli-runtime/pkg/resource" -) - -const ( - customResourceDefinitionGroup = "apiextensions.k8s.io" - customResourceDefinitionKind = "CustomResourceDefinition" -) - -// Build a list of resource.Info objects from a rendered manifest. -func ParseMultiDocumentYAML(manifest string, kubeClient kube.Interface) ([]*resource.Info, error) { - resources := make([]*resource.Info, 0) - - res, err := kubeClient.Build(bytes.NewBufferString(manifest), false) - if err != nil { - return nil, fmt.Errorf("Parsing the CRDs from the rendered manifest was not successful: %w", err) - } - resources = append(resources, res...) - - return resources, nil -} - -func filterResources(resources []*resource.Info, filter func(*resource.Info) bool) []*resource.Info { - filtered := make([]*resource.Info, 0) - for _, res := range resources { - if filter(res) { - filtered = append(filtered, res) - } - } - - return filtered -} - -// Retrieve the latest version of the resources from the kubernetes cluster. -func FetchResources(resources []*resource.Info, kubeClient kube.Interface) ([]*resource.Info, error) { - detected := make([]*resource.Info, 0) - - for _, info := range resources { - helper := resource.NewHelper(info.Client, info.Mapping) - obj, err := helper.Get(info.Namespace, info.Name) - if err != nil { - if apierrors.IsNotFound(err) { - continue - } - - return nil, err - } - - info.Object = obj - detected = append(detected, info) - } - - return detected, nil -} - -// Filter resources that are Custom Resource Definitions. -func FilterCrdResources(resources []*resource.Info) []*resource.Info { - return filterResources(resources, func(res *resource.Info) bool { - groupVersionKind := res.Object.GetObjectKind().GroupVersionKind() - return (groupVersionKind.Group == customResourceDefinitionGroup) && (groupVersionKind.Kind == customResourceDefinitionKind) - }) -} diff --git a/cmd/ctl/pkg/install/install.go b/cmd/ctl/pkg/install/install.go deleted file mode 100644 index 9a0a727e742..00000000000 --- a/cmd/ctl/pkg/install/install.go +++ /dev/null @@ -1,281 +0,0 @@ -/* -Copyright 2021 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. -*/ - -package install - -import ( - "context" - "fmt" - "io" - "log" - "os" - "strings" - "time" - - "github.com/spf13/cobra" - "helm.sh/helm/v3/pkg/action" - "helm.sh/helm/v3/pkg/chart" - "helm.sh/helm/v3/pkg/chart/loader" - "helm.sh/helm/v3/pkg/cli" - "helm.sh/helm/v3/pkg/cli/values" - "helm.sh/helm/v3/pkg/getter" - "helm.sh/helm/v3/pkg/release" - "k8s.io/cli-runtime/pkg/genericclioptions" - - "github.com/cert-manager/cert-manager/cmd/ctl/pkg/build" - "github.com/cert-manager/cert-manager/cmd/ctl/pkg/install/helm" -) - -type InstallOptions struct { - settings *cli.EnvSettings - client *action.Install - cfg *action.Configuration - valueOpts *values.Options - - ChartName string - DryRun bool - Wait bool - - genericclioptions.IOStreams -} - -const ( - installCRDsFlagName = "installCRDs" - defaultCertManagerNamespace = "cert-manager" -) - -func installDesc() string { - return build.WithTemplate(`This command installs cert-manager. It uses the Helm libraries to do so. - -The latest published cert-manager chart in the "https://charts.jetstack.io" repo is used. -Most of the features supported by 'helm install' are also supported by this command. -In addition, this command will always correctly install the required CRD resources. - -Some example uses: - $ {{.BuildName}} x install -or - $ {{.BuildName}} x install -n new-cert-manager -or - $ {{.BuildName}} x install --version v1.4.0 -or - $ {{.BuildName}} x install --set prometheus.enabled=false - -To override values in the cert-manager chart, use either the '--values' flag and -pass in a file or use the '--set' flag and pass configuration from the command line. -`) -} - -func NewCmdInstall(ctx context.Context, ioStreams genericclioptions.IOStreams) *cobra.Command { - settings := cli.New() - cfg := new(action.Configuration) - - options := &InstallOptions{ - settings: settings, - cfg: cfg, - client: action.NewInstall(cfg), - valueOpts: &values.Options{}, - - IOStreams: ioStreams, - } - - cmd := &cobra.Command{ - Use: "install", - Short: "Install cert-manager", - Long: installDesc(), - RunE: func(cmd *cobra.Command, args []string) error { - options.client.Namespace = settings.Namespace() - - rel, err := options.runInstall(ctx) - if err != nil { - return err - } - - if options.DryRun { - fmt.Fprintf(ioStreams.Out, "%s", rel.Manifest) - return nil - } - - printReleaseSummary(ioStreams.Out, rel) - return nil - }, - SilenceUsage: true, - SilenceErrors: true, - } - - settings.AddFlags(cmd.Flags()) - - // The Helm cli.New function does not provide an easy way to - // override the default of the namespace flag. - // See https://github.com/helm/helm/issues/9790 - // - // Here we set the default value shown in the usage message. - cmd.Flag("namespace").DefValue = defaultCertManagerNamespace - // Here we set the default value. - // The returned error is ignored because - // pflag.stringValue.Set always returns a nil. - cmd.Flag("namespace").Value.Set(defaultCertManagerNamespace) - - addInstallUninstallFlags(cmd.Flags(), &options.client.Timeout, &options.Wait) - - addInstallFlags(cmd.Flags(), options.client) - addValueOptionsFlags(cmd.Flags(), options.valueOpts) - addChartPathOptionsFlags(cmd.Flags(), &options.client.ChartPathOptions) - - cmd.Flags().BoolVar(&options.client.CreateNamespace, "create-namespace", true, "Create the release namespace if not present") - cmd.Flags().MarkHidden("create-namespace") - cmd.Flags().StringVar(&options.ChartName, "chart-name", "cert-manager", "Name of the chart to install") - cmd.Flags().MarkHidden("chart-name") - cmd.Flags().BoolVar(&options.DryRun, "dry-run", false, "Simulate install and output manifest") - - return cmd -} - -// The overall strategy is to install the CRDs first, and not as part of a Helm -// release, and then to install a Helm release without the CRDs. This is to -// ensure that CRDs are not removed by a subsequent helm uninstall or by a -// future cmctl uninstall. We want the removal of CRDs to only be performed by -// an administrator who understands that the consequences of removing CRDs will -// be the garbage collection of all the related CRs in the cluster. We first -// do a dry-run install of the chart (effectively helm template -// --validate=false) to render the CRDs from the CRD templates in the Chart. -// The ClientOnly option is required, otherwise Helm will return an error in -// case the CRDs are already installed in the cluster. We then extract the -// CRDs from the resulting dry-run manifests and install those first. Finally, -// we perform a helm install to install the remaining non-CRD resources and -// wait for those to be "Ready". -// This creates a Helm "release" artifact in a Secret in the target namespace, which contains -// a record of all the resources installed by Helm (except the CRDs). -func (o *InstallOptions) runInstall(ctx context.Context) (*release.Release, error) { - log.SetFlags(0) // Disable prefixing logs with timestamps. - log.SetOutput(o.ErrOut) // Log everything to stderr so dry-run output does not get corrupted. - - // Find chart - cp, err := o.client.ChartPathOptions.LocateChart(o.ChartName, o.settings) - if err != nil { - return nil, err - } - - chart, err := loader.Load(cp) - if err != nil { - return nil, err - } - - // Check if chart is installable - if err := checkIfInstallable(chart); err != nil { - return nil, err - } - - // Console print if chart is deprecated - if chart.Metadata.Deprecated { - log.Printf("This chart is deprecated") - } - - // Merge all values flags - p := getter.All(o.settings) - chartValues, err := o.valueOpts.MergeValues(p) - if err != nil { - return nil, err - } - - // Dryrun template generation (used for rendering the CRDs in /templates) - o.client.DryRun = true // Do not apply install - o.client.ClientOnly = true // Do not validate against cluster (otherwise double CRDs can cause error) - chartValues[installCRDsFlagName] = true // Make sure to render CRDs - dryRunResult, err := o.client.Run(chart, chartValues) - if err != nil { - return nil, err - } - - if o.DryRun { - return dryRunResult, nil - } - - if err := o.cfg.Init(o.settings.RESTClientGetter(), o.settings.Namespace(), os.Getenv("HELM_DRIVER"), log.Printf); err != nil { - return nil, err - } - - // Extract the resource.Info objects from the manifest - resources, err := helm.ParseMultiDocumentYAML(dryRunResult.Manifest, o.cfg.KubeClient) - if err != nil { - return nil, err - } - - // Filter resource.Info objects and only keep the CRDs - crds := helm.FilterCrdResources(resources) - - // Abort in case CRDs were not found in chart - if len(crds) == 0 { - return nil, fmt.Errorf("Found no CRDs in provided cert-manager chart.") - } - - // Make sure that no CRDs are currently installed - originalCRDs, err := helm.FetchResources(crds, o.cfg.KubeClient) - if err != nil { - return nil, err - } - - if len(originalCRDs) > 0 { - return nil, fmt.Errorf("Found existing installed cert-manager CRDs! Cannot continue with installation.") - } - - // Install CRDs - if err := helm.CreateCRDs(crds, o.cfg); err != nil { - return nil, err - } - - // Install chart - o.client.DryRun = false // Apply DryRun cli flags - o.client.ClientOnly = false // Perform install against cluster - - o.client.Wait = o.Wait // Wait for resources to be ready - // If part of the install fails and the Atomic option is set to True, - // all resource installs are reverted. Atomic cannot be enabled without - // waiting (if Atomic=True is set, the value for Wait is overwritten with True), - // so only enable Atomic if we are waiting. - o.client.Atomic = o.Wait - // The cert-manager chart currently has only a startupapicheck hook, - // if waiting is disabled, this hook should be disabled too; otherwise - // the hook will still wait for the installation to succeed. - o.client.DisableHooks = !o.Wait - - chartValues[installCRDsFlagName] = false // Do not render CRDs, as this might cause problems when uninstalling using helm - - return o.client.Run(chart, chartValues) -} - -func printReleaseSummary(out io.Writer, rel *release.Release) { - fmt.Fprintf(out, "NAME: %s\n", rel.Name) - if !rel.Info.LastDeployed.IsZero() { - fmt.Fprintf(out, "LAST DEPLOYED: %s\n", rel.Info.LastDeployed.Format(time.ANSIC)) - } - fmt.Fprintf(out, "NAMESPACE: %s\n", rel.Namespace) - fmt.Fprintf(out, "STATUS: %s\n", rel.Info.Status.String()) - fmt.Fprintf(out, "REVISION: %d\n", rel.Version) - fmt.Fprintf(out, "DESCRIPTION: %s\n", rel.Info.Description) - - if len(rel.Info.Notes) > 0 { - fmt.Fprintf(out, "NOTES:\n%s\n", strings.TrimSpace(rel.Info.Notes)) - } -} - -// Only Application chart type are installable. -func checkIfInstallable(ch *chart.Chart) error { - switch ch.Metadata.Type { - case "", "application": - return nil - } - return fmt.Errorf("%s charts are not installable", ch.Metadata.Type) -} diff --git a/cmd/ctl/pkg/install/util.go b/cmd/ctl/pkg/install/util.go deleted file mode 100644 index 9ad04e8239b..00000000000 --- a/cmd/ctl/pkg/install/util.go +++ /dev/null @@ -1,70 +0,0 @@ -/* -Copyright 2021 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. -*/ - -package install - -import ( - "os" - "path/filepath" - "time" - - "github.com/spf13/pflag" - "helm.sh/helm/v3/pkg/action" - "helm.sh/helm/v3/pkg/cli/values" - "k8s.io/client-go/util/homedir" -) - -// Flags that are shared between the Install and the Uninstall command -func addInstallUninstallFlags(f *pflag.FlagSet, timeout *time.Duration, wait *bool) { - f.DurationVar(timeout, "timeout", 300*time.Second, "Time to wait for any individual Kubernetes operation (like Jobs for hooks)") - f.MarkHidden("timeout") - f.BoolVar(wait, "wait", true, "If set, will wait until all Pods, PVCs, Services, and minimum number of Pods of a Deployment, StatefulSet, or ReplicaSet are in a ready state before marking the release as successful. It will wait for as long as --timeout") - f.MarkHidden("wait") -} - -func addInstallFlags(f *pflag.FlagSet, client *action.Install) { - f.StringVar(&client.ReleaseName, "release-name", "cert-manager", "Name of the helm release") - f.MarkHidden("release-name") - f.BoolVarP(&client.GenerateName, "generate-name", "g", false, "Generate the name (instead of using the default 'cert-manager' value)") - f.MarkHidden("generate-name") - f.StringVar(&client.NameTemplate, "name-template", "", "Specify template used to name the release") - f.MarkHidden("name-template") - f.StringVar(&client.Description, "description", "Cert-manager was installed using the cert-manager CLI", "Add a custom description") - f.MarkHidden("description") -} - -func addValueOptionsFlags(f *pflag.FlagSet, v *values.Options) { - f.StringSliceVarP(&v.ValueFiles, "values", "f", []string{}, "Specify values in a YAML file or a URL (can specify multiple)") - f.StringArrayVar(&v.Values, "set", []string{}, "Set values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2)") - f.StringArrayVar(&v.StringValues, "set-string", []string{}, "Set STRING values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2)") - f.MarkHidden("set-string") - f.StringArrayVar(&v.FileValues, "set-file", []string{}, "Set values from respective files specified via the command line (can specify multiple or separate values with commas: key1=path1,key2=path2)") - f.MarkHidden("set-file") -} - -// defaultKeyring returns the expanded path to the default keyring. -func defaultKeyring() string { - if v, ok := os.LookupEnv("GNUPGHOME"); ok { - return filepath.Join(v, "pubring.gpg") - } - return filepath.Join(homedir.HomeDir(), ".gnupg", "pubring.gpg") -} - -func addChartPathOptionsFlags(f *pflag.FlagSet, c *action.ChartPathOptions) { - c.Keyring = defaultKeyring() - c.RepoURL = "https://charts.jetstack.io" - f.StringVar(&c.Version, "version", "", "specify a version constraint for the chart version to use. This constraint can be a specific tag (e.g. 1.1.1) or it may reference a valid range (e.g. ^2.0.0). If this is not specified, the latest version is used") -} diff --git a/cmd/ctl/pkg/renew/renew.go b/cmd/ctl/pkg/renew/renew.go deleted file mode 100644 index f001d8fe743..00000000000 --- a/cmd/ctl/pkg/renew/renew.go +++ /dev/null @@ -1,210 +0,0 @@ -/* -Copyright 2020 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. -*/ - -package renew - -import ( - "context" - "errors" - "fmt" - - "github.com/spf13/cobra" - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/cli-runtime/pkg/genericclioptions" - "k8s.io/client-go/kubernetes" - cmdutil "k8s.io/kubectl/pkg/cmd/util" - "k8s.io/kubectl/pkg/util/i18n" - "k8s.io/kubectl/pkg/util/templates" - - "github.com/cert-manager/cert-manager/cmd/ctl/pkg/build" - "github.com/cert-manager/cert-manager/cmd/ctl/pkg/factory" - apiutil "github.com/cert-manager/cert-manager/pkg/api/util" - cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" - cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" - cmclient "github.com/cert-manager/cert-manager/pkg/client/clientset/versioned" -) - -var ( - long = templates.LongDesc(i18n.T(` -Mark cert-manager Certificate resources for manual renewal.`)) - - example = templates.Examples(i18n.T(build.WithTemplate(` -# Renew the Certificates named 'my-app' and 'vault' in the current context namespace. -{{.BuildName}} renew my-app vault - -# Renew all Certificates in the 'kube-system' namespace. -{{.BuildName}} renew --namespace kube-system --all - -# Renew all Certificates in all namespaces, provided those Certificates have the label 'app=my-service' -{{.BuildName}} renew --all-namespaces -l app=my-service`))) -) - -// Options is a struct to support renew command -type Options struct { - LabelSelector string - All bool - AllNamespaces bool - - genericclioptions.IOStreams - *factory.Factory -} - -// NewOptions returns initialized Options -func NewOptions(ioStreams genericclioptions.IOStreams) *Options { - return &Options{ - IOStreams: ioStreams, - } -} - -// NewCmdRenew returns a cobra command for renewing Certificates -func NewCmdRenew(ctx context.Context, ioStreams genericclioptions.IOStreams) *cobra.Command { - o := NewOptions(ioStreams) - cmd := &cobra.Command{ - Use: "renew", - Short: "Mark a Certificate for manual renewal", - Long: long, - Example: example, - ValidArgsFunction: factory.ValidArgsListCertificates(ctx, &o.Factory), - Run: func(cmd *cobra.Command, args []string) { - cmdutil.CheckErr(o.Validate(cmd, args)) - cmdutil.CheckErr(o.Run(ctx, args)) - }, - } - - cmd.Flags().StringVarP(&o.LabelSelector, "selector", "l", o.LabelSelector, "Selector (label query) to filter on, supports '=', '==', and '!='.(e.g. -l key1=value1,key2=value2)") - cmd.Flags().BoolVarP(&o.AllNamespaces, "all-namespaces", "A", o.AllNamespaces, "If present, mark Certificates across namespaces for manual renewal. Namespace in current context is ignored even if specified with --namespace.") - cmd.Flags().BoolVar(&o.All, "all", o.All, "Renew all Certificates in the given Namespace, or all namespaces with --all-namespaces enabled.") - - o.Factory = factory.New(ctx, cmd) - - return cmd -} - -// Validate validates the provided options -func (o *Options) Validate(cmd *cobra.Command, args []string) error { - if len(o.LabelSelector) > 0 && len(args) > 0 { - return errors.New("cannot specify Certificate names in conjunction with label selectors") - } - - if len(o.LabelSelector) > 0 && o.All { - return errors.New("cannot specify label selectors in conjunction with --all flag") - } - - if o.All && len(args) > 0 { - return errors.New("cannot specify Certificate names in conjunction with --all flag") - } - - if o.All && cmd.PersistentFlags().Changed("namespace") { - return errors.New("cannot specify --namespace flag in conjunction with --all flag") - } - - return nil -} - -// Complete takes the command arguments and factory and infers any remaining options. -func (o *Options) Complete(f cmdutil.Factory) error { - var err error - o.Namespace, _, err = f.ToRawKubeConfigLoader().Namespace() - if err != nil { - return err - } - - o.RESTConfig, err = f.ToRESTConfig() - if err != nil { - return err - } - - o.CMClient, err = cmclient.NewForConfig(o.RESTConfig) - if err != nil { - return err - } - - return nil -} - -// Run executes renew command -func (o *Options) Run(ctx context.Context, args []string) error { - - nss := []corev1.Namespace{{ObjectMeta: metav1.ObjectMeta{Name: o.Namespace}}} - - if o.AllNamespaces { - kubeClient, err := kubernetes.NewForConfig(o.RESTConfig) - if err != nil { - return err - } - - nsList, err := kubeClient.CoreV1().Namespaces().List(ctx, metav1.ListOptions{}) - if err != nil { - return err - } - - nss = nsList.Items - } - - var crts []cmapi.Certificate - for _, ns := range nss { - switch { - case o.All, len(o.LabelSelector) > 0: - crtsList, err := o.CMClient.CertmanagerV1().Certificates(ns.Name).List(ctx, metav1.ListOptions{ - LabelSelector: o.LabelSelector, - }) - if err != nil { - return err - } - - crts = append(crts, crtsList.Items...) - - default: - for _, crtName := range args { - crt, err := o.CMClient.CertmanagerV1().Certificates(ns.Name).Get(ctx, crtName, metav1.GetOptions{}) - if err != nil { - return err - } - - crts = append(crts, *crt) - } - } - } - - if len(crts) == 0 { - if o.AllNamespaces { - fmt.Fprintln(o.ErrOut, "No Certificates found") - } else { - fmt.Fprintf(o.ErrOut, "No Certificates found in %s namespace.\n", o.Namespace) - } - - return nil - } - - for _, crt := range crts { - if err := o.renewCertificate(ctx, &crt); err != nil { - return err - } - } - - return nil -} - -func (o *Options) renewCertificate(ctx context.Context, crt *cmapi.Certificate) error { - apiutil.SetCertificateCondition(crt, crt.Generation, cmapi.CertificateConditionIssuing, cmmeta.ConditionTrue, "ManuallyTriggered", "Certificate re-issuance manually triggered") - _, err := o.CMClient.CertmanagerV1().Certificates(crt.Namespace).UpdateStatus(ctx, crt, metav1.UpdateOptions{}) - if err != nil { - return fmt.Errorf("failed to trigger issuance of Certificate %s/%s: %v", crt.Namespace, crt.Name, err) - } - fmt.Fprintf(o.Out, "Manually triggered issuance of Certificate %s/%s\n", crt.Namespace, crt.Name) - return nil -} diff --git a/cmd/ctl/pkg/renew/renew_test.go b/cmd/ctl/pkg/renew/renew_test.go deleted file mode 100644 index c7bcb173d33..00000000000 --- a/cmd/ctl/pkg/renew/renew_test.go +++ /dev/null @@ -1,101 +0,0 @@ -/* -Copyright 2020 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. -*/ - -package renew - -import ( - "context" - "testing" - - "k8s.io/cli-runtime/pkg/genericclioptions" -) - -type stringFlag struct { - name, value string -} - -func TestValidate(t *testing.T) { - tests := map[string]struct { - options *Options - args []string - setStringFlags []stringFlag - expErr bool - }{ - "If there are arguments, as well as label selector, error": { - options: &Options{ - LabelSelector: "foo=bar", - }, - args: []string{"abc"}, - expErr: true, - }, - "If there are all certificates selected, as well as label selector, error": { - options: &Options{ - LabelSelector: "foo=bar", - All: true, - }, - args: []string{""}, - expErr: true, - }, - "If there are all certificates selected, as well as arguments, error": { - options: &Options{ - All: true, - }, - args: []string{"abc"}, - expErr: true, - }, - "If all certificates in all namespaces selected, don't error": { - options: &Options{ - All: true, - AllNamespaces: true, - }, - expErr: false, - }, - "If --namespace and --all namespace specified, error": { - options: &Options{ - All: true, - }, - setStringFlags: []stringFlag{ - {name: "namespace", value: "foo"}, - }, - expErr: true, - }, - } - - for name, test := range tests { - t.Run(name, func(t *testing.T) { - cmd := NewCmdRenew(context.TODO(), genericclioptions.IOStreams{}) - - // This is normally registered in the main func. We add here to test - // against flags normally inherited. - kubeConfigFlags := genericclioptions.NewConfigFlags(true) - kubeConfigFlags.AddFlags(cmd.PersistentFlags()) - - if test.setStringFlags != nil { - for _, s := range test.setStringFlags { - if err := cmd.PersistentFlags().Set(s.name, s.value); err != nil { - t.Fatal(err) - } - } - } - - err := test.options.Validate(cmd, test.args) - if test.expErr != (err != nil) { - t.Errorf("expected error=%t got=%v", - test.expErr, err) - } - }) - } -} diff --git a/cmd/ctl/pkg/status/certificate/certificate.go b/cmd/ctl/pkg/status/certificate/certificate.go deleted file mode 100644 index 72b96887173..00000000000 --- a/cmd/ctl/pkg/status/certificate/certificate.go +++ /dev/null @@ -1,393 +0,0 @@ -/* -Copyright 2020 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. -*/ - -package certificate - -import ( - "context" - "errors" - "fmt" - "time" - - "github.com/spf13/cobra" - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/cli-runtime/pkg/genericclioptions" - "k8s.io/client-go/kubernetes" - "k8s.io/client-go/tools/reference" - cmdutil "k8s.io/kubectl/pkg/cmd/util" - "k8s.io/kubectl/pkg/util/i18n" - "k8s.io/kubectl/pkg/util/templates" - - "github.com/cert-manager/cert-manager/cmd/ctl/pkg/build" - "github.com/cert-manager/cert-manager/cmd/ctl/pkg/factory" - cmacme "github.com/cert-manager/cert-manager/pkg/apis/acme/v1" - cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" - cmclient "github.com/cert-manager/cert-manager/pkg/client/clientset/versioned" - "github.com/cert-manager/cert-manager/pkg/ctl" - "github.com/cert-manager/cert-manager/pkg/util/predicate" -) - -var ( - long = templates.LongDesc(i18n.T(` -Get details about the current status of a cert-manager Certificate resource, including information on related resources like CertificateRequest or Order.`)) - - example = templates.Examples(i18n.T(build.WithTemplate(` -# Query status of Certificate with name 'my-crt' in namespace 'my-namespace' -{{.BuildName}} status certificate my-crt --namespace my-namespace -`))) -) - -// Options is a struct to support status certificate command -type Options struct { - genericclioptions.IOStreams - *factory.Factory -} - -// Data is a struct containing the information to build a CertificateStatus -type Data struct { - Certificate *cmapi.Certificate - CrtEvents *corev1.EventList - Issuer cmapi.GenericIssuer - IssuerKind string - IssuerError error - IssuerEvents *corev1.EventList - Secret *corev1.Secret - SecretError error - SecretEvents *corev1.EventList - Req *cmapi.CertificateRequest - ReqError error - ReqEvents *corev1.EventList - Order *cmacme.Order - OrderError error - Challenges []*cmacme.Challenge - ChallengeErr error -} - -// NewOptions returns initialized Options -func NewOptions(ioStreams genericclioptions.IOStreams) *Options { - return &Options{ - IOStreams: ioStreams, - } -} - -// NewCmdStatusCert returns a cobra command for status certificate -func NewCmdStatusCert(ctx context.Context, ioStreams genericclioptions.IOStreams) *cobra.Command { - o := NewOptions(ioStreams) - - cmd := &cobra.Command{ - Use: "certificate", - Short: "Get details about the current status of a cert-manager Certificate resource", - Long: long, - Example: example, - ValidArgsFunction: factory.ValidArgsListCertificates(ctx, &o.Factory), - Run: func(cmd *cobra.Command, args []string) { - cmdutil.CheckErr(o.Validate(args)) - cmdutil.CheckErr(o.Run(ctx, args)) - }, - } - - o.Factory = factory.New(ctx, cmd) - - return cmd -} - -// Validate validates the provided options -func (o *Options) Validate(args []string) error { - if len(args) < 1 { - return errors.New("the name of the Certificate has to be provided as argument") - } - if len(args) > 1 { - return errors.New("only one argument can be passed in: the name of the Certificate") - } - return nil -} - -// Run executes status certificate command -func (o *Options) Run(ctx context.Context, args []string) error { - data, err := o.GetResources(ctx, args[0]) - if err != nil { - return err - } - - // Build status of Certificate with data gathered - status := StatusFromResources(data) - - fmt.Fprintf(o.Out, status.String()) - - return nil -} - -// GetResources collects all related resources of the Certificate and any errors while doing so -// in a Data struct and returns it. -// Returns error if error occurs when finding Certificate resource or while preparing to find other resources, -// e.g. when creating clientSet -func (o *Options) GetResources(ctx context.Context, crtName string) (*Data, error) { - clientSet, err := kubernetes.NewForConfig(o.RESTConfig) - if err != nil { - return nil, err - } - - crt, err := o.CMClient.CertmanagerV1().Certificates(o.Namespace).Get(ctx, crtName, metav1.GetOptions{}) - if err != nil { - return nil, fmt.Errorf("error when getting Certificate resource: %v", err) - } - - crtRef, err := reference.GetReference(ctl.Scheme, crt) - if err != nil { - return nil, err - } - // If no events found, crtEvents would be nil and handled down the line in DescribeEvents - crtEvents, err := clientSet.CoreV1().Events(crt.Namespace).Search(ctl.Scheme, crtRef) - if err != nil { - return nil, err - } - - issuer, issuerKind, issuerError := getGenericIssuer(o.CMClient, ctx, crt) - var issuerEvents *corev1.EventList - if issuer != nil { - issuerRef, err := reference.GetReference(ctl.Scheme, issuer) - if err != nil { - return nil, err - } - // If no events found, issuerEvents would be nil and handled down the line in DescribeEvents - issuerEvents, err = clientSet.CoreV1().Events(issuer.GetNamespace()).Search(ctl.Scheme, issuerRef) - if err != nil { - return nil, err - } - } - - secret, secretErr := clientSet.CoreV1().Secrets(crt.Namespace).Get(ctx, crt.Spec.SecretName, metav1.GetOptions{}) - if secretErr != nil { - secretErr = fmt.Errorf("error when finding Secret %q: %w\n", crt.Spec.SecretName, secretErr) - } - var secretEvents *corev1.EventList - if secret != nil { - secretRef, err := reference.GetReference(ctl.Scheme, secret) - if err != nil { - return nil, err - } - // If no events found, secretEvents would be nil and handled down the line in DescribeEvents - secretEvents, err = clientSet.CoreV1().Events(secret.Namespace).Search(ctl.Scheme, secretRef) - if err != nil { - return nil, err - } - } - - // TODO: What about timing issues? When I query condition it's not ready yet, but then looking for cr it's finished and deleted - // Try find the CertificateRequest that is owned by crt and has the correct revision - req, reqErr := findMatchingCR(o.CMClient, ctx, crt) - if reqErr != nil { - reqErr = fmt.Errorf("error when finding CertificateRequest: %w\n", reqErr) - } else if req == nil { - reqErr = errors.New("No CertificateRequest found for this Certificate\n") - } - - var reqEvents *corev1.EventList - if req != nil { - reqRef, err := reference.GetReference(ctl.Scheme, req) - if err != nil { - return nil, err - } - // If no events found, reqEvents would be nil and handled down the line in DescribeEvents - reqEvents, err = clientSet.CoreV1().Events(req.Namespace).Search(ctl.Scheme, reqRef) - if err != nil { - return nil, err - } - } - - var ( - order *cmacme.Order - orderErr error - challenges []*cmacme.Challenge - challengeErr error - ) - - // Nothing to output about Order and Challenge if no CR or not ACME Issuer - if req != nil && issuer != nil && issuer.GetSpec().ACME != nil { - // Get Order - order, orderErr = findMatchingOrder(o.CMClient, ctx, req) - if orderErr != nil { - orderErr = fmt.Errorf("error when finding Order: %w\n", orderErr) - } else if order == nil { - orderErr = errors.New("No Order found for this Certificate\n") - } - - if order != nil { - challenges, challengeErr = findMatchingChallenges(o.CMClient, ctx, order) - if challengeErr != nil { - challengeErr = fmt.Errorf("error when finding Challenges: %w\n", challengeErr) - } else if len(challenges) == 0 { - challengeErr = errors.New("No Challenges found for this Certificate\n") - } - } - } - - return &Data{ - Certificate: crt, - CrtEvents: crtEvents, - Issuer: issuer, - IssuerKind: issuerKind, - IssuerError: issuerError, - IssuerEvents: issuerEvents, - Secret: secret, - SecretError: secretErr, - SecretEvents: secretEvents, - Req: req, - ReqError: reqErr, - ReqEvents: reqEvents, - Order: order, - OrderError: orderErr, - Challenges: challenges, - ChallengeErr: challengeErr, - }, nil -} - -// StatusFromResources takes in a Data struct and returns a CertificateStatus built using -// the information in data. -func StatusFromResources(data *Data) *CertificateStatus { - return newCertificateStatusFromCert(data.Certificate). - withEvents(data.CrtEvents). - withGenericIssuer(data.Issuer, data.IssuerKind, data.IssuerEvents, data.IssuerError). - withSecret(data.Secret, data.SecretEvents, data.SecretError). - withCR(data.Req, data.ReqEvents, data.ReqError). - withOrder(data.Order, data.OrderError). - withChallenges(data.Challenges, data.ChallengeErr) -} - -// formatStringSlice takes in a string slice and formats the contents of the slice -// into a single string where each element of the slice is prefixed with "- " and on a new line -func formatStringSlice(strings []string) string { - result := "" - for _, str := range strings { - result += "- " + str + "\n" - } - return result -} - -// formatTimeString returns the time as a string -// If nil, return "" -func formatTimeString(t *metav1.Time) string { - if t == nil { - return "" - } - return t.Time.Format(time.RFC3339) -} - -// findMatchingCR tries to find a CertificateRequest that is owned by crt and has the correct revision annotated from reqs. -// If none found returns nil -// If one found returns the CR -// If multiple found or error occurs when listing CRs, returns error -func findMatchingCR(cmClient cmclient.Interface, ctx context.Context, crt *cmapi.Certificate) (*cmapi.CertificateRequest, error) { - reqs, err := cmClient.CertmanagerV1().CertificateRequests(crt.Namespace).List(ctx, metav1.ListOptions{}) - if err != nil { - return nil, fmt.Errorf("error when listing CertificateRequest resources: %w", err) - } - - possibleMatches := []*cmapi.CertificateRequest{} - - // CertificateRequest revisions begin from 1. - // If no revision is set on the Certificate then assume the revision on the CertificateRequest should be 1. - // If revision is set on the Certificate then revision on the CertificateRequest should be crt.Status.Revision + 1. - nextRevision := 1 - if crt.Status.Revision != nil { - nextRevision = *crt.Status.Revision + 1 - } - for _, req := range reqs.Items { - if predicate.CertificateRequestRevision(nextRevision)(&req) && - predicate.ResourceOwnedBy(crt)(&req) { - possibleMatches = append(possibleMatches, req.DeepCopy()) - } - } - - if len(possibleMatches) < 1 { - return nil, nil - } else if len(possibleMatches) == 1 { - return possibleMatches[0], nil - } else { - return nil, errors.New("found multiple certificate requests with expected revision and owner") - } -} - -// findMatchingOrder tries to find an Order that is owned by req. -// If none found returns nil -// If one found returns the Order -// If multiple found or error occurs when listing Orders, returns error -func findMatchingOrder(cmClient cmclient.Interface, ctx context.Context, req *cmapi.CertificateRequest) (*cmacme.Order, error) { - orders, err := cmClient.AcmeV1().Orders(req.Namespace).List(ctx, metav1.ListOptions{}) - if err != nil { - return nil, err - } - - possibleMatches := []*cmacme.Order{} - for _, order := range orders.Items { - if predicate.ResourceOwnedBy(req)(&order) { - possibleMatches = append(possibleMatches, order.DeepCopy()) - } - } - - if len(possibleMatches) < 1 { - return nil, nil - } else if len(possibleMatches) == 1 { - return possibleMatches[0], nil - } else { - return nil, fmt.Errorf("found multiple orders owned by CertificateRequest %s", req.Name) - } -} - -func getGenericIssuer(cmClient cmclient.Interface, ctx context.Context, crt *cmapi.Certificate) (cmapi.GenericIssuer, string, error) { - issuerKind := crt.Spec.IssuerRef.Kind - if issuerKind == "" { - issuerKind = "Issuer" - } - - if crt.Spec.IssuerRef.Group != "cert-manager.io" && crt.Spec.IssuerRef.Group != "" { - // TODO: Support Issuers/ClusterIssuers from other groups as well - return nil, "", fmt.Errorf("The %s %q is not of the group cert-manager.io, this command currently does not support third party issuers.\nTo get more information about %q, try 'kubectl describe'\n", - issuerKind, crt.Spec.IssuerRef.Name, crt.Spec.IssuerRef.Name) - } else if issuerKind == "Issuer" { - issuer, issuerErr := cmClient.CertmanagerV1().Issuers(crt.Namespace).Get(ctx, crt.Spec.IssuerRef.Name, metav1.GetOptions{}) - if issuerErr != nil { - issuerErr = fmt.Errorf("error when getting Issuer: %v\n", issuerErr) - } - return issuer, issuerKind, issuerErr - } else { - // ClusterIssuer - clusterIssuer, issuerErr := cmClient.CertmanagerV1().ClusterIssuers().Get(ctx, crt.Spec.IssuerRef.Name, metav1.GetOptions{}) - if issuerErr != nil { - issuerErr = fmt.Errorf("error when getting ClusterIssuer: %v\n", issuerErr) - } - return clusterIssuer, issuerKind, issuerErr - } -} - -// findMatchingChallenges tries to find Challenges that are owned by order. -// If none found returns empty slice. -func findMatchingChallenges(cmClient cmclient.Interface, ctx context.Context, order *cmacme.Order) ([]*cmacme.Challenge, error) { - challenges, err := cmClient.AcmeV1().Challenges(order.Namespace).List(ctx, metav1.ListOptions{}) - if err != nil { - return nil, err - } - - possibleMatches := []*cmacme.Challenge{} - for _, challenge := range challenges.Items { - if predicate.ResourceOwnedBy(order)(&challenge) { - possibleMatches = append(possibleMatches, challenge.DeepCopy()) - } - } - - return possibleMatches, nil -} diff --git a/cmd/ctl/pkg/status/certificate/certificate_test.go b/cmd/ctl/pkg/status/certificate/certificate_test.go deleted file mode 100644 index 837635d9e6f..00000000000 --- a/cmd/ctl/pkg/status/certificate/certificate_test.go +++ /dev/null @@ -1,486 +0,0 @@ -/* -Copyright 2020 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. -*/ - -package certificate - -import ( - "crypto/x509" - "errors" - "math/big" - "strings" - "testing" - "time" - - "github.com/stretchr/testify/assert" - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - - cmacme "github.com/cert-manager/cert-manager/pkg/apis/acme/v1" - cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" - cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" - "github.com/cert-manager/cert-manager/test/unit/gen" -) - -func TestFormatStringSlice(t *testing.T) { - tests := map[string]struct { - slice []string - expOutput string - }{ - // Newlines are part of the expected output - "Empty slice returns empty string": { - slice: []string{}, - expOutput: ``, - }, - "Slice with one element returns string with one line": { - slice: []string{"hello"}, - expOutput: `- hello -`, - }, - "Slice with multiple elements returns string with multiple lines": { - slice: []string{"hello", "World", "another line"}, - expOutput: `- hello -- World -- another line -`, - }, - } - - for name, test := range tests { - t.Run(name, func(t *testing.T) { - if actualOutput := formatStringSlice(test.slice); actualOutput != test.expOutput { - t.Errorf("Unexpected output; expected: \n%s\nactual: \n%s", test.expOutput, actualOutput) - } - }) - } -} - -func TestCRInfoString(t *testing.T) { - tests := map[string]struct { - cr *cmapi.CertificateRequest - err error - expOutput string - }{ - // Newlines are part of the expected output - "Nil pointer output correct": { - cr: nil, - err: errors.New("No CertificateRequest found for this Certificate\n"), - expOutput: `No CertificateRequest found for this Certificate -`, - }, - "CR with no condition output correct": { - cr: &cmapi.CertificateRequest{Status: cmapi.CertificateRequestStatus{Conditions: []cmapi.CertificateRequestCondition{}}}, - expOutput: `CertificateRequest: - Name: - Namespace: - Conditions: - No Conditions set - Events: -`, - }, - "CR with conditions output correct": { - cr: &cmapi.CertificateRequest{ - Status: cmapi.CertificateRequestStatus{ - Conditions: []cmapi.CertificateRequestCondition{ - {Type: cmapi.CertificateRequestConditionReady, Status: cmmeta.ConditionTrue, Message: "example"}, - }}}, - expOutput: `CertificateRequest: - Name: - Namespace: - Conditions: - Ready: True, Reason: , Message: example - Events: -`, - }, - } - - for name, test := range tests { - t.Run(name, func(t *testing.T) { - actualOutput := (&CertificateStatus{}).withCR(test.cr, nil, test.err).CRStatus.String() - if strings.ReplaceAll(actualOutput, " \n", "\n") != strings.ReplaceAll(test.expOutput, " \n", "\n") { - t.Errorf("Unexpected output; expected: \n%s\nactual: \n%s", test.expOutput, actualOutput) - } - }) - } -} - -func TestKeyUsageToString(t *testing.T) { - tests := map[string]struct { - usage x509.KeyUsage - expOutput string - }{ - "no key usage set": { - usage: x509.KeyUsage(0), - expOutput: "", - }, - "key usage Digital Signature": { - usage: x509.KeyUsageDigitalSignature, - expOutput: "Digital Signature", - }, - "key usage Digital Signature and Data Encipherment": { - usage: x509.KeyUsageDigitalSignature | x509.KeyUsageDataEncipherment, - expOutput: "Digital Signature, Data Encipherment", - }, - "key usage with three usages is ordered": { - usage: x509.KeyUsageDigitalSignature | x509.KeyUsageDataEncipherment | x509.KeyUsageContentCommitment, - expOutput: "Digital Signature, Content Commitment, Data Encipherment", - }, - } - for name, test := range tests { - t.Run(name, func(t *testing.T) { - if actualOutput := keyUsageToString(test.usage); actualOutput != test.expOutput { - t.Errorf("Unexpected output; expected: \n%s\nactual: \n%s", test.expOutput, actualOutput) - } - }) - } -} - -func TestExtKeyUsageToString(t *testing.T) { - tests := map[string]struct { - extUsage []x509.ExtKeyUsage - expOutput string - expError bool - expErrorOutput string - }{ - "no extended key usage": { - extUsage: []x509.ExtKeyUsage{}, - expOutput: "", - }, - "extended key usage Any": { - extUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageAny}, - expOutput: "Any", - }, - "multiple extended key usages": { - extUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageEmailProtection}, - expOutput: "Client Authentication, Email Protection", - }, - "undefined extended key usage": { - extUsage: []x509.ExtKeyUsage{x509.ExtKeyUsage(42)}, - expOutput: "", - expError: true, - expErrorOutput: "error when converting Extended Usages to string: encountered unknown Extended Usage with code 42", - }, - } - for name, test := range tests { - t.Run(name, func(t *testing.T) { - actualOutput, err := extKeyUsageToString(test.extUsage) - if err != nil { - if !test.expError || test.expErrorOutput != err.Error() { - t.Errorf("got unexpected error. This test expects an error: %t. expected error: %q, actual error: %q", - test.expError, test.expErrorOutput, err.Error()) - } - } else if test.expError { - t.Errorf("expects error: %q, but did not get any", test.expErrorOutput) - } - if actualOutput != test.expOutput { - t.Errorf("Unexpected output; expected: \n%s\nactual: \n%s", test.expOutput, actualOutput) - } - }) - } -} - -func TestStatusFromResources(t *testing.T) { - timestamp, err := time.Parse(time.RFC3339, "2020-09-16T09:26:18Z") - if err != nil { - t.Fatal(err) - } - - tlsCrt := []byte(`-----BEGIN CERTIFICATE----- -MIICyTCCAbGgAwIBAgIRAOL4jtyULBSEYyGdqQn9YzowDQYJKoZIhvcNAQELBQAw -DzENMAsGA1UEAxMEdGVzdDAeFw0yMDA3MzAxNjExNDNaFw0yMDEwMjgxNjExNDNa -MA8xDTALBgNVBAMTBHRlc3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB -AQDdfNmjh5ag7f6U1hj1OAx/dEN9kQzPsSlBMXGb/Ho4k5iegrFd6w8JkYdCthFv -lfg3bIhw5tCKaw1o57HnWKBKKGt7XpeIu1mEcv8pveMIPO7TZ4+oElgX880NfJmL -DkjEcctEo/+FurudO1aEbNfbNWpzudYKj7gGtYshBytqaYt4/APqWARJBFCYVVys -wexZ0fLi5cBD8H1bQ1Ec3OCr5Mrq9thAGkj+rVlgYR0AZVGa9+SCOj27t6YCmyzR -AJSEQ35v58Zfxp5tNyYd6wcAswJ9YipnUXvwahF95PNlRmMhp3Eo15m9FxehcVXU -BOfxykMwZN7onMhuHiiwiB+NAgMBAAGjIDAeMA4GA1UdDwEB/wQEAwIFoDAMBgNV -HRMBAf8EAjAAMA0GCSqGSIb3DQEBCwUAA4IBAQALrnldWjTBTvV5WKapUHUG0rhA -vp2Cf+5FsPw8vKScXp4L+wKGdPOjhHz6NOiw5wu8A0HxlVUFawRpagkjFkeTL78O -9ghBHLiqn9xNPIKC6ID3WpnN5terwQxQeO/M54sVMslUWCcZm9Pu4Eb//2e6wEdu -eMmpfeISQmCsBC1CTmpxUjeUg5DEQ0X1TQykXq+bG2iso6RYPxZTFTHJFzXiDYEc -/X7H+bOmpo/dMrXapwfvp2gD+BEq96iVpf/DBzGYNs/657LAHJ4YtxtAZCa1CK9G -MA6koCR/K23HZfML8vT6lcHvQJp9XXaHRIe9NX/M/2f6VpfO7JjKWLou5k5a ------END CERTIFICATE-----`) - - serialNum, _ := new(big.Int).SetString("301696114246524167282555582613204853562", 10) - ns := "ns1" - dummyEventList := &corev1.EventList{ - Items: []corev1.Event{{ - Type: "type", - Reason: "reason", - Message: "message", - }}, - } - - tests := map[string]struct { - inputData *Data - expOutput *CertificateStatus - }{ - "Correct information extracted from Certificate resource": { - inputData: &Data{ - Certificate: gen.Certificate("test-crt", - gen.SetCertificateNamespace(ns), - gen.SetCertificateNotAfter(metav1.Time{Time: timestamp}), - gen.SetCertificateNotBefore(metav1.Time{Time: timestamp}), - gen.SetCertificateRenewalTime(metav1.Time{Time: timestamp}), - gen.SetCertificateStatusCondition(cmapi.CertificateCondition{Type: cmapi.CertificateConditionReady, - Status: cmmeta.ConditionTrue, Message: "Certificate is up to date and has not expired"}), - gen.SetCertificateDNSNames("example.com"), - ), - CrtEvents: dummyEventList, - }, - expOutput: &CertificateStatus{ - Name: "test-crt", - Namespace: ns, - CreationTime: metav1.Time{}, - Conditions: []cmapi.CertificateCondition{{Type: cmapi.CertificateConditionReady, - Status: cmmeta.ConditionTrue, Message: "Certificate is up to date and has not expired"}}, - DNSNames: []string{"example.com"}, - Events: dummyEventList, - NotBefore: &metav1.Time{Time: timestamp}, - NotAfter: &metav1.Time{Time: timestamp}, - RenewalTime: &metav1.Time{Time: timestamp}, - }, - }, - "Issuer correctly with Kind Issuer": { - inputData: &Data{ - Certificate: gen.Certificate("test-crt", - gen.SetCertificateNamespace(ns)), - Issuer: gen.Issuer("test-issuer"), - IssuerKind: "Issuer", - IssuerError: nil, - IssuerEvents: dummyEventList, - }, - expOutput: &CertificateStatus{ - Name: "test-crt", - Namespace: ns, - CreationTime: metav1.Time{}, - IssuerStatus: &IssuerStatus{ - Name: "test-issuer", - Kind: "Issuer", - Events: dummyEventList, - }, - }, - }, - "Issuer correctly with Kind ClusterIssuer": { - inputData: &Data{ - Certificate: gen.Certificate("test-crt", - gen.SetCertificateNamespace(ns)), - Issuer: gen.Issuer("test-clusterissuer"), - IssuerKind: "ClusterIssuer", - IssuerError: nil, - IssuerEvents: dummyEventList, - }, - expOutput: &CertificateStatus{ - Name: "test-crt", - Namespace: ns, - CreationTime: metav1.Time{}, - IssuerStatus: &IssuerStatus{ - Name: "test-clusterissuer", - Kind: "ClusterIssuer", - Events: dummyEventList, - }, - }, - }, - "Correct information extracted from Secret resource": { - inputData: &Data{ - Certificate: gen.Certificate("test-crt", - gen.SetCertificateNamespace(ns)), - Secret: gen.Secret("existing-tls-secret", - gen.SetSecretNamespace(ns), - gen.SetSecretData(map[string][]byte{"tls.crt": tlsCrt})), - SecretError: nil, - SecretEvents: dummyEventList, - }, - expOutput: &CertificateStatus{ - Name: "test-crt", - Namespace: ns, - CreationTime: metav1.Time{}, - SecretStatus: &SecretStatus{ - Error: nil, - Name: "existing-tls-secret", - IssuerCountry: nil, - IssuerOrganisation: nil, - IssuerCommonName: "test", - KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment, - ExtKeyUsage: nil, - PublicKeyAlgorithm: x509.RSA, - SignatureAlgorithm: x509.SHA256WithRSA, - SubjectKeyId: nil, - AuthorityKeyId: nil, - SerialNumber: serialNum, - Events: dummyEventList, - }, - }, - }, - "Correct information extracted from CR resource": { - inputData: &Data{ - Certificate: gen.Certificate("test-crt", - gen.SetCertificateNamespace(ns)), - Req: gen.CertificateRequest("test-req", - gen.SetCertificateRequestNamespace(ns), - gen.SetCertificateRequestStatusCondition(cmapi.CertificateRequestCondition{Type: cmapi.CertificateRequestConditionReady, Status: cmmeta.ConditionFalse, Reason: "Pending", Message: "Waiting on certificate issuance from order default/example-order: \"pending\""})), - ReqError: nil, - ReqEvents: dummyEventList, - }, - expOutput: &CertificateStatus{ - Name: "test-crt", - Namespace: ns, - CreationTime: metav1.Time{}, - CRStatus: &CRStatus{ - Error: nil, - Name: "test-req", - Namespace: ns, - Conditions: []cmapi.CertificateRequestCondition{{Type: cmapi.CertificateRequestConditionReady, Status: cmmeta.ConditionFalse, Reason: "Pending", Message: "Waiting on certificate issuance from order default/example-order: \"pending\""}}, - Events: dummyEventList, - }, - }, - }, - "Correct information extracted from Order resource": { - inputData: &Data{ - Certificate: gen.Certificate("test-crt", - gen.SetCertificateNamespace(ns)), - Order: &cmacme.Order{ - TypeMeta: metav1.TypeMeta{}, - ObjectMeta: metav1.ObjectMeta{Name: "example-order", Namespace: ns}, - Spec: cmacme.OrderSpec{Request: []byte("dummyCSR"), DNSNames: []string{"www.example.com"}}, - Status: cmacme.OrderStatus{}, - }, - OrderError: nil, - }, - expOutput: &CertificateStatus{ - Name: "test-crt", - Namespace: ns, - CreationTime: metav1.Time{}, - OrderStatus: &OrderStatus{ - Error: nil, - Name: "example-order", - State: "", - Reason: "", - Authorizations: nil, - FailureTime: nil, - }, - }, - }, - "Correct information extracted from Challenge resources": { - inputData: &Data{ - Certificate: gen.Certificate("test-crt", - gen.SetCertificateNamespace(ns)), - Challenges: []*cmacme.Challenge{ - { - TypeMeta: metav1.TypeMeta{}, - ObjectMeta: metav1.ObjectMeta{Name: "test-challenge1", Namespace: ns}, - Spec: cmacme.ChallengeSpec{ - Type: "HTTP-01", - Token: "token", - Key: "key", - }, - Status: cmacme.ChallengeStatus{ - Processing: false, - Presented: false, - Reason: "reason", - State: "state", - }, - }, - { - TypeMeta: metav1.TypeMeta{}, - ObjectMeta: metav1.ObjectMeta{Name: "test-challenge2", Namespace: ns}, - Spec: cmacme.ChallengeSpec{ - Type: "HTTP-01", - Token: "token", - Key: "key", - }, - Status: cmacme.ChallengeStatus{ - Processing: false, - Presented: false, - Reason: "reason", - State: "state", - }, - }, - }, - ChallengeErr: nil, - }, - expOutput: &CertificateStatus{ - Name: "test-crt", - Namespace: ns, - CreationTime: metav1.Time{}, - ChallengeStatusList: &ChallengeStatusList{ - ChallengeStatuses: []*ChallengeStatus{ - { - Name: "test-challenge1", - Type: "HTTP-01", - Token: "token", - Key: "key", - State: "state", - Reason: "reason", - Processing: false, - Presented: false, - }, - { - Name: "test-challenge2", - Type: "HTTP-01", - Token: "token", - Key: "key", - State: "state", - Reason: "reason", - Processing: false, - Presented: false, - }, - }, - }, - }, - }, - "When error, ignore rest of the info about the resource": { - inputData: &Data{ - Certificate: gen.Certificate("test-crt", - gen.SetCertificateNamespace(ns)), - CrtEvents: nil, - Issuer: gen.Issuer("test-issuer"), - IssuerKind: "", - IssuerError: errors.New("dummy error"), - IssuerEvents: dummyEventList, - Secret: gen.Secret("test-secret"), - SecretError: errors.New("dummy error"), - SecretEvents: dummyEventList, - Req: gen.CertificateRequest("test-req"), - ReqError: errors.New("dummy error"), - ReqEvents: dummyEventList, - Order: &cmacme.Order{ - ObjectMeta: metav1.ObjectMeta{Name: "test-order"}, - }, - OrderError: errors.New("dummy error"), - Challenges: []*cmacme.Challenge{{ObjectMeta: metav1.ObjectMeta{Name: "test-challenge"}}}, - ChallengeErr: errors.New("dummy error"), - }, - expOutput: &CertificateStatus{ - Name: "test-crt", - Namespace: ns, - CreationTime: metav1.Time{}, - IssuerStatus: &IssuerStatus{Error: errors.New("dummy error")}, - SecretStatus: &SecretStatus{Error: errors.New("dummy error")}, - CRStatus: &CRStatus{Error: errors.New("dummy error")}, - OrderStatus: &OrderStatus{Error: errors.New("dummy error")}, - ChallengeStatusList: &ChallengeStatusList{Error: errors.New("dummy error")}, - }, - }, - } - for name, test := range tests { - t.Run(name, func(t *testing.T) { - got := StatusFromResources(test.inputData) - assert.Equal(t, test.expOutput, got) - }) - } -} diff --git a/cmd/ctl/pkg/status/certificate/types.go b/cmd/ctl/pkg/status/certificate/types.go deleted file mode 100644 index 8b5916fb5ab..00000000000 --- a/cmd/ctl/pkg/status/certificate/types.go +++ /dev/null @@ -1,509 +0,0 @@ -/* -Copyright 2020 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. -*/ - -package certificate - -import ( - "bytes" - "crypto/x509" - "encoding/hex" - "fmt" - "math/big" - "strings" - - v1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/kubectl/pkg/describe" - - "github.com/cert-manager/cert-manager/cmd/ctl/pkg/status/util" - cmacme "github.com/cert-manager/cert-manager/pkg/apis/acme/v1" - cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" - "github.com/cert-manager/cert-manager/pkg/util/pki" -) - -type CertificateStatus struct { - // Name of the Certificate resource - Name string - // Namespace of the Certificate resource - Namespace string - // Creation Time of Certificate resource - CreationTime metav1.Time - // Conditions of Certificate resource - Conditions []cmapi.CertificateCondition - // DNS Names of Certificate resource - DNSNames []string - // Events of Certificate resource - Events *v1.EventList - // Not Before of Certificate resource - NotBefore *metav1.Time - // Not After of Certificate resource - NotAfter *metav1.Time - // Renewal Time of Certificate resource - RenewalTime *metav1.Time - - IssuerStatus *IssuerStatus - - SecretStatus *SecretStatus - - CRStatus *CRStatus - - OrderStatus *OrderStatus - - ChallengeStatusList *ChallengeStatusList -} - -type IssuerStatus struct { - // If Error is not nil, there was a problem getting the status of the Issuer/ClusterIssuer resource, - // so the rest of the fields is unusable - Error error - // Name of the Issuer/ClusterIssuer resource - Name string - // Kind of the resource, can be Issuer or ClusterIssuer - Kind string - // Conditions of Issuer/ClusterIssuer resource - Conditions []cmapi.IssuerCondition - // Events of Issuer/ClusterIssuer resource - Events *v1.EventList -} - -type SecretStatus struct { - // If Error is not nil, there was a problem getting the status of the Secret resource, - // so the rest of the fields is unusable - Error error - // Name of the Secret resource - Name string - // Issuer Countries of the x509 certificate in the Secret - IssuerCountry []string - // Issuer Organisations of the x509 certificate in the Secret - IssuerOrganisation []string - // Issuer Common Name of the x509 certificate in the Secret - IssuerCommonName string - // Key Usage of the x509 certificate in the Secret - KeyUsage x509.KeyUsage - // Extended Key Usage of the x509 certificate in the Secret - ExtKeyUsage []x509.ExtKeyUsage - // Public Key Algorithm of the x509 certificate in the Secret - PublicKeyAlgorithm x509.PublicKeyAlgorithm - // Signature Algorithm of the x509 certificate in the Secret - SignatureAlgorithm x509.SignatureAlgorithm - // Subject Key Id of the x509 certificate in the Secret - SubjectKeyId []byte - // Authority Key Id of the x509 certificate in the Secret - AuthorityKeyId []byte - // Serial Number of the x509 certificate in the Secret - SerialNumber *big.Int - // Events of Secret resource - Events *v1.EventList -} - -type CRStatus struct { - // If Error is not nil, there was a problem getting the status of the CertificateRequest resource, - // so the rest of the fields is unusable - Error error - // Name of the CertificateRequest resource - Name string - // Namespace of the CertificateRequest resource - Namespace string - // Conditions of CertificateRequest resource - Conditions []cmapi.CertificateRequestCondition - // Events of CertificateRequest resource - Events *v1.EventList -} - -type OrderStatus struct { - // If Error is not nil, there was a problem getting the status of the Order resource, - // so the rest of the fields is unusable - Error error - // Name of the Order resource - Name string - // State of Order resource - State cmacme.State - // Reason why the Order resource is in its State - Reason string - // What authorizations must be completed to validate the DNS names specified on the Order - Authorizations []cmacme.ACMEAuthorization - // Time the Order failed - FailureTime *metav1.Time -} - -type ChallengeStatusList struct { - // If Error is not nil, there was a problem getting the status of the Order resource, - // so the rest of the fields is unusable - Error error - ChallengeStatuses []*ChallengeStatus -} - -type ChallengeStatus struct { - Name string - Type cmacme.ACMEChallengeType - Token string - Key string - State cmacme.State - Reason string - Processing bool - Presented bool -} - -func newCertificateStatusFromCert(crt *cmapi.Certificate) *CertificateStatus { - if crt == nil { - return nil - } - return &CertificateStatus{ - Name: crt.Name, Namespace: crt.Namespace, CreationTime: crt.CreationTimestamp, - Conditions: crt.Status.Conditions, DNSNames: crt.Spec.DNSNames, - NotBefore: crt.Status.NotBefore, NotAfter: crt.Status.NotAfter, RenewalTime: crt.Status.RenewalTime} -} - -func (status *CertificateStatus) withEvents(events *v1.EventList) *CertificateStatus { - status.Events = events - return status -} - -func (status *CertificateStatus) withGenericIssuer(genericIssuer cmapi.GenericIssuer, issuerKind string, issuerEvents *v1.EventList, err error) *CertificateStatus { - if err != nil { - status.IssuerStatus = &IssuerStatus{Error: err} - return status - } - if genericIssuer == nil { - return status - } - if issuerKind == "ClusterIssuer" { - status.IssuerStatus = &IssuerStatus{Name: genericIssuer.GetName(), Kind: "ClusterIssuer", - Conditions: genericIssuer.GetStatus().Conditions, Events: issuerEvents} - return status - } - status.IssuerStatus = &IssuerStatus{Name: genericIssuer.GetName(), Kind: "Issuer", - Conditions: genericIssuer.GetStatus().Conditions, Events: issuerEvents} - return status -} - -func (status *CertificateStatus) withSecret(secret *v1.Secret, secretEvents *v1.EventList, err error) *CertificateStatus { - if err != nil { - status.SecretStatus = &SecretStatus{Error: err} - return status - } - if secret == nil { - return status - } - certData := secret.Data["tls.crt"] - - if len(certData) == 0 { - status.SecretStatus = &SecretStatus{Error: fmt.Errorf("error: 'tls.crt' of Secret %q is not set\n", secret.Name)} - return status - } - - x509Cert, err := pki.DecodeX509CertificateBytes(certData) - if err != nil { - status.SecretStatus = &SecretStatus{Error: fmt.Errorf("error when parsing 'tls.crt' of Secret %q: %s\n", secret.Name, err)} - return status - } - - status.SecretStatus = &SecretStatus{Error: nil, Name: secret.Name, IssuerCountry: x509Cert.Issuer.Country, - IssuerOrganisation: x509Cert.Issuer.Organization, - IssuerCommonName: x509Cert.Issuer.CommonName, KeyUsage: x509Cert.KeyUsage, - ExtKeyUsage: x509Cert.ExtKeyUsage, PublicKeyAlgorithm: x509Cert.PublicKeyAlgorithm, - SignatureAlgorithm: x509Cert.SignatureAlgorithm, - SubjectKeyId: x509Cert.SubjectKeyId, AuthorityKeyId: x509Cert.AuthorityKeyId, - SerialNumber: x509Cert.SerialNumber, Events: secretEvents} - return status -} - -func (status *CertificateStatus) withCR(req *cmapi.CertificateRequest, events *v1.EventList, err error) *CertificateStatus { - if err != nil { - status.CRStatus = &CRStatus{Error: err} - return status - } - if req == nil { - return status - } - status.CRStatus = &CRStatus{Name: req.Name, Namespace: req.Namespace, Conditions: req.Status.Conditions, Events: events} - return status -} - -func (status *CertificateStatus) withOrder(order *cmacme.Order, err error) *CertificateStatus { - if err != nil { - status.OrderStatus = &OrderStatus{Error: err} - return status - } - if order == nil { - return status - } - - status.OrderStatus = &OrderStatus{Name: order.Name, State: order.Status.State, - Reason: order.Status.Reason, Authorizations: order.Status.Authorizations, - FailureTime: order.Status.FailureTime} - return status -} - -func (status *CertificateStatus) withChallenges(challenges []*cmacme.Challenge, err error) *CertificateStatus { - if err != nil { - status.ChallengeStatusList = &ChallengeStatusList{Error: err} - return status - } - if len(challenges) == 0 { - return status - } - - var list []*ChallengeStatus - for _, challenge := range challenges { - list = append(list, &ChallengeStatus{ - Name: challenge.Name, - Type: challenge.Spec.Type, - Token: challenge.Spec.Token, - Key: challenge.Spec.Key, - State: challenge.Status.State, - Reason: challenge.Status.Reason, - Processing: challenge.Status.Processing, - Presented: challenge.Status.Presented, - }) - } - status.ChallengeStatusList = &ChallengeStatusList{ChallengeStatuses: list} - return status -} - -func (status *CertificateStatus) String() string { - output := "" - output += fmt.Sprintf("Name: %s\n", status.Name) - output += fmt.Sprintf("Namespace: %s\n", status.Namespace) - output += fmt.Sprintf("Created at: %s\n", formatTimeString(&status.CreationTime)) - - // Output one line about each type of Condition that is set. - // Certificate can have multiple Conditions of different types set, e.g. "Ready" or "Issuing" - conditionMsg := "" - for _, con := range status.Conditions { - conditionMsg += fmt.Sprintf(" %s: %s, Reason: %s, Message: %s\n", con.Type, con.Status, con.Reason, con.Message) - } - if conditionMsg == "" { - conditionMsg = " No Conditions set\n" - } - output += fmt.Sprintf("Conditions:\n%s", conditionMsg) - - output += fmt.Sprintf("DNS Names:\n%s", formatStringSlice(status.DNSNames)) - - output += eventsToString(status.Events, 0) - - output += status.IssuerStatus.String() - output += status.SecretStatus.String() - - output += fmt.Sprintf("Not Before: %s\n", formatTimeString(status.NotBefore)) - output += fmt.Sprintf("Not After: %s\n", formatTimeString(status.NotAfter)) - output += fmt.Sprintf("Renewal Time: %s\n", formatTimeString(status.RenewalTime)) - - output += status.CRStatus.String() - - // OrderStatus is nil is not found or Issuer/ClusterIssuer is not ACME Issuer - if status.OrderStatus != nil { - output += status.OrderStatus.String() - } - - if status.ChallengeStatusList != nil { - output += status.ChallengeStatusList.String() - } - - return output -} - -// String returns the information about the status of a Issuer/ClusterIssuer as a string to be printed as output -func (issuerStatus *IssuerStatus) String() string { - if issuerStatus.Error != nil { - return issuerStatus.Error.Error() - } - - issuerFormat := `Issuer: - Name: %s - Kind: %s - Conditions: - %s` - conditionMsg := "" - for _, con := range issuerStatus.Conditions { - conditionMsg += fmt.Sprintf(" %s: %s, Reason: %s, Message: %s\n", con.Type, con.Status, con.Reason, con.Message) - } - if conditionMsg == "" { - conditionMsg = " No Conditions set\n" - } - output := fmt.Sprintf(issuerFormat, issuerStatus.Name, issuerStatus.Kind, conditionMsg) - output += eventsToString(issuerStatus.Events, 1) - return output -} - -// String returns the information about the status of a Secret as a string to be printed as output -func (secretStatus *SecretStatus) String() string { - if secretStatus.Error != nil { - return secretStatus.Error.Error() - } - - secretFormat := `Secret: - Name: %s - Issuer Country: %s - Issuer Organisation: %s - Issuer Common Name: %s - Key Usage: %s - Extended Key Usages: %s - Public Key Algorithm: %s - Signature Algorithm: %s - Subject Key ID: %s - Authority Key ID: %s - Serial Number: %s -` - - extKeyUsageString, err := extKeyUsageToString(secretStatus.ExtKeyUsage) - if err != nil { - extKeyUsageString = err.Error() - } - output := fmt.Sprintf(secretFormat, secretStatus.Name, strings.Join(secretStatus.IssuerCountry, ", "), - strings.Join(secretStatus.IssuerOrganisation, ", "), - secretStatus.IssuerCommonName, keyUsageToString(secretStatus.KeyUsage), - extKeyUsageString, secretStatus.PublicKeyAlgorithm, secretStatus.SignatureAlgorithm, - hex.EncodeToString(secretStatus.SubjectKeyId), hex.EncodeToString(secretStatus.AuthorityKeyId), - hex.EncodeToString(secretStatus.SerialNumber.Bytes())) - output += eventsToString(secretStatus.Events, 1) - return output -} - -var ( - keyUsageToStringMap = map[int]string{ - 1: "Digital Signature", - 2: "Content Commitment", - 4: "Key Encipherment", - 8: "Data Encipherment", - 16: "Key Agreement", - 32: "Cert Sign", - 64: "CRL Sign", - 128: "Encipher Only", - 256: "Decipher Only", - } - keyUsagePossibleValues = []int{256, 128, 64, 32, 16, 8, 4, 2, 1} - extKeyUsageStringValues = []string{"Any", "Server Authentication", "Client Authentication", "Code Signing", "Email Protection", - "IPSEC End System", "IPSEC Tunnel", "IPSEC User", "Time Stamping", "OCSP Signing", "Microsoft Server Gated Crypto", - "Netscape Server Gated Crypto", "Microsoft Commercial Code Signing", "Microsoft Kernel Code Signing", - } -) - -func keyUsageToString(usage x509.KeyUsage) string { - usageInt := int(usage) - var usageStrings []string - for _, val := range keyUsagePossibleValues { - if usageInt >= val { - usageInt -= val - usageStrings = append(usageStrings, keyUsageToStringMap[val]) - } - if usageInt == 0 { - break - } - } - // Reversing because that's usually the order the usages are printed - for i := 0; i < len(usageStrings)/2; i++ { - opp := len(usageStrings) - 1 - i - usageStrings[i], usageStrings[opp] = usageStrings[opp], usageStrings[i] - } - return strings.Join(usageStrings, ", ") -} - -func extKeyUsageToString(extUsages []x509.ExtKeyUsage) (string, error) { - var extUsageStrings []string - for _, extUsage := range extUsages { - if extUsage < 0 || int(extUsage) >= len(extKeyUsageStringValues) { - return "", fmt.Errorf("error when converting Extended Usages to string: encountered unknown Extended Usage with code %d", extUsage) - } - extUsageStrings = append(extUsageStrings, extKeyUsageStringValues[extUsage]) - } - return strings.Join(extUsageStrings, ", "), nil -} - -// String returns the information about the status of a CR as a string to be printed as output -func (crStatus *CRStatus) String() string { - if crStatus.Error != nil { - return crStatus.Error.Error() - } - - crFormat := ` - Name: %s - Namespace: %s - Conditions: - %s` - conditionMsg := "" - for _, con := range crStatus.Conditions { - conditionMsg += fmt.Sprintf(" %s: %s, Reason: %s, Message: %s\n", con.Type, con.Status, con.Reason, con.Message) - } - if conditionMsg == "" { - conditionMsg = " No Conditions set\n" - } - infos := fmt.Sprintf(crFormat, crStatus.Name, crStatus.Namespace, conditionMsg) - infos = fmt.Sprintf("CertificateRequest:%s", infos) - - infos += eventsToString(crStatus.Events, 1) - return infos -} - -// String returns the information about the status of a CR as a string to be printed as output -func (orderStatus *OrderStatus) String() string { - if orderStatus.Error != nil { - return orderStatus.Error.Error() - } - - output := "Order:\n" - output += fmt.Sprintf(" Name: %s\n", orderStatus.Name) - output += fmt.Sprintf(" State: %s, Reason: %s\n", orderStatus.State, orderStatus.Reason) - authString := "" - for _, auth := range orderStatus.Authorizations { - wildcardString := "nil (bool pointer not set)" - if auth.Wildcard != nil { - wildcardString = fmt.Sprintf("%t", *auth.Wildcard) - } - authString += fmt.Sprintf(" URL: %s, Identifier: %s, Initial State: %s, Wildcard: %s\n", auth.URL, auth.Identifier, auth.InitialState, wildcardString) - } - if authString == "" { - output += " No Authorizations for this Order\n" - } else { - output += " Authorizations:\n" - output += authString - } - if orderStatus.FailureTime != nil { - output += fmt.Sprintf(" FailureTime: %s\n", formatTimeString(orderStatus.FailureTime)) - } - - return output -} - -func (c *ChallengeStatusList) String() string { - if c.Error != nil { - return c.Error.Error() - } - - challengeStrings := []string{} - for _, challengeStatus := range c.ChallengeStatuses { - challengeStrings = append(challengeStrings, challengeStatus.String()) - } - output := "Challenges:\n" - output += formatStringSlice(challengeStrings) - return output -} - -func (challengeStatus *ChallengeStatus) String() string { - return fmt.Sprintf("Name: %s, Type: %s, Token: %s, Key: %s, State: %s, Reason: %s, Processing: %t, Presented: %t", - challengeStatus.Name, challengeStatus.Type, challengeStatus.Token, challengeStatus.Key, challengeStatus.State, - challengeStatus.Reason, challengeStatus.Processing, challengeStatus.Presented) -} - -func eventsToString(events *v1.EventList, baseLevel int) string { - var buf bytes.Buffer - defer buf.Reset() - tabWriter := util.NewTabWriter(&buf) - prefixWriter := describe.NewPrefixWriter(tabWriter) - util.DescribeEvents(events, prefixWriter, baseLevel) - tabWriter.Flush() - return buf.String() -} diff --git a/cmd/ctl/pkg/status/status.go b/cmd/ctl/pkg/status/status.go deleted file mode 100644 index 5c1806829d4..00000000000 --- a/cmd/ctl/pkg/status/status.go +++ /dev/null @@ -1,38 +0,0 @@ -/* -Copyright 2020 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. -*/ - -package status - -import ( - "context" - - "github.com/spf13/cobra" - "k8s.io/cli-runtime/pkg/genericclioptions" - - "github.com/cert-manager/cert-manager/cmd/ctl/pkg/status/certificate" -) - -func NewCmdStatus(ctx context.Context, ioStreams genericclioptions.IOStreams) *cobra.Command { - cmds := &cobra.Command{ - Use: "status", - Short: "Get details on current status of cert-manager resources", - Long: `Get details on current status of cert-manager resources, e.g. Certificate`, - } - - cmds.AddCommand(certificate.NewCmdStatusCert(ctx, ioStreams)) - - return cmds -} diff --git a/cmd/ctl/pkg/status/util/util.go b/cmd/ctl/pkg/status/util/util.go deleted file mode 100644 index 203344c034b..00000000000 --- a/cmd/ctl/pkg/status/util/util.go +++ /dev/null @@ -1,93 +0,0 @@ -/* -Copyright 2020 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. -*/ - -package util - -import ( - "fmt" - "io" - "sort" - "strings" - "text/tabwriter" - "time" - - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/util/duration" - "k8s.io/kubectl/pkg/describe" - "k8s.io/kubectl/pkg/util/event" -) - -// This file contains functions that are copied from "k8s.io/kubectl/pkg/describe". -// DescribeEvents was slightly modified. The other functions are copied over. -// The purpose of this is to be able to reuse the PrefixWriter interface defined in the describe package, -// and because we need to indent certain lines differently than the original function. - -// DescribeEvents writes a formatted string of the Events in el with PrefixWriter. -// The intended use is for w to be created with a *tabWriter.Writer underneath, and the caller -// of DescribeEvents would need to call Flush() on that *tabWriter.Writer to actually print the output. -func DescribeEvents(el *corev1.EventList, w describe.PrefixWriter, baseLevel int) { - if el == nil || len(el.Items) == 0 { - w.Write(baseLevel, "Events:\t\n") - w.Flush() - return - } - w.Flush() - sort.Sort(event.SortableEvents(el.Items)) - w.Write(baseLevel, "Events:\n") - w.Write(baseLevel+1, "Type\tReason\tAge\tFrom\tMessage\n") - w.Write(baseLevel+1, "----\t------\t----\t----\t-------\n") - for _, e := range el.Items { - var interval string - if e.Count > 1 { - interval = fmt.Sprintf("%s (x%d over %s)", translateTimestampSince(e.LastTimestamp), e.Count, translateTimestampSince(e.FirstTimestamp)) - } else { - interval = translateTimestampSince(e.FirstTimestamp) - } - w.Write(baseLevel+1, "%v\t%v\t%s\t%v\t%v\n", - e.Type, - e.Reason, - interval, - formatEventSource(e.Source), - strings.TrimSpace(e.Message), - ) - } - w.Flush() -} - -// NewTabWriter returns a *tabwriter.Writer with fixed parameters to be used in the status command -func NewTabWriter(writer io.Writer) *tabwriter.Writer { - return tabwriter.NewWriter(writer, 0, 8, 2, ' ', 0) -} - -// formatEventSource formats EventSource as a comma separated string excluding Host when empty -func formatEventSource(es corev1.EventSource) string { - EventSourceString := []string{es.Component} - if len(es.Host) > 0 { - EventSourceString = append(EventSourceString, es.Host) - } - return strings.Join(EventSourceString, ", ") -} - -// translateTimestampSince returns the elapsed time since timestamp in -// human-readable approximation. -func translateTimestampSince(timestamp metav1.Time) string { - if timestamp.IsZero() { - return "" - } - - return duration.HumanDuration(time.Since(timestamp.Time)) -} diff --git a/cmd/ctl/pkg/uninstall/uninstall.go b/cmd/ctl/pkg/uninstall/uninstall.go deleted file mode 100644 index 6e14410f2ab..00000000000 --- a/cmd/ctl/pkg/uninstall/uninstall.go +++ /dev/null @@ -1,146 +0,0 @@ -/* -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. -*/ - -package uninstall - -import ( - "context" - "errors" - "fmt" - "log" - "os" - "time" - - "github.com/spf13/cobra" - "helm.sh/helm/v3/pkg/action" - "helm.sh/helm/v3/pkg/cli" - "helm.sh/helm/v3/pkg/release" - "helm.sh/helm/v3/pkg/storage/driver" - "k8s.io/cli-runtime/pkg/genericclioptions" - - "github.com/cert-manager/cert-manager/cmd/ctl/pkg/build" -) - -type options struct { - settings *cli.EnvSettings - client *action.Uninstall - cfg *action.Configuration - - disableHooks bool - dryRun bool - wait bool - - genericclioptions.IOStreams -} - -const ( - defaultCertManagerNamespace = "cert-manager" - releaseName = "cert-manager" -) - -func description() string { - return build.WithTemplate(`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: - $ {{.BuildName}} x uninstall -or - $ {{.BuildName}} x uninstall --namespace my-cert-manager -or - $ {{.BuildName}} x uninstall --dry-run -or - $ {{.BuildName}} x uninstall --no-hooks -`) -} - -func NewCmd(ctx context.Context, ioStreams genericclioptions.IOStreams) *cobra.Command { - settings := cli.New() - cfg := new(action.Configuration) - - options := options{ - settings: settings, - cfg: cfg, - client: action.NewUninstall(cfg), - - IOStreams: ioStreams, - } - - cmd := &cobra.Command{ - Use: "uninstall", - Short: "Uninstall cert-manager", - Long: description(), - RunE: func(cmd *cobra.Command, args []string) error { - res, err := run(ctx, options) - if err != nil { - return fmt.Errorf("run: %v", err) - } - - if options.dryRun { - fmt.Fprintf(ioStreams.Out, "%s", res.Release.Manifest) - return nil - } - - return nil - }, - SilenceUsage: true, - SilenceErrors: true, - } - - settings.AddFlags(cmd.Flags()) - - // The Helm cli.New function does not provide an easy way to - // override the default of the namespace flag. - // See https://github.com/helm/helm/issues/9790 - // - // set the default value shown in the usage message. - cmd.Flag("namespace").DefValue = defaultCertManagerNamespace - - // The returned error is ignored because - // pflag.stringValue.Set always returns a nil. - cmd.Flag("namespace").Value.Set(defaultCertManagerNamespace) - - cmd.Flags().DurationVar(&options.client.Timeout, "timeout", 5*time.Minute, "time to wait for any individual Kubernetes operation (like Jobs for hooks)") - cmd.Flags().BoolVar(&options.wait, "wait", true, "if set, will wait until all the resources are deleted before returning. It will wait for as long as --timeout") - cmd.Flags().BoolVar(&options.dryRun, "dry-run", false, "simulate uninstall and output manifests to be deleted") - cmd.Flags().BoolVar(&options.disableHooks, "no-hooks", false, "prevent hooks from running during uninstallation (pre- and post-uninstall hooks)") - - return cmd -} - -// run assumes cert-manager was installed as a Helm release named cert-manager. -// this is not configurable to avoid uninstalling non-cert-manager releases. -func run(ctx context.Context, o options) (*release.UninstallReleaseResponse, error) { - log.SetFlags(0) // disable prefixing logs with timestamps. - - if err := o.cfg.Init(o.settings.RESTClientGetter(), o.settings.Namespace(), os.Getenv("HELM_DRIVER"), log.Printf); err != nil { - return nil, fmt.Errorf("o.cfg.Init: %v", err) - } - - o.client.DisableHooks = o.disableHooks - o.client.DryRun = o.dryRun - o.client.Wait = o.wait - - res, err := o.client.Run(releaseName) - - if errors.Is(err, driver.ErrReleaseNotFound) { - log.Fatalf("release %v not found in namespace %v, did you use the correct namespace?", releaseName, o.settings.Namespace()) - } - - return res, nil -} diff --git a/cmd/ctl/pkg/upgrade/migrateapiversion/command.go b/cmd/ctl/pkg/upgrade/migrateapiversion/command.go deleted file mode 100644 index eb2d51f2b6e..00000000000 --- a/cmd/ctl/pkg/upgrade/migrateapiversion/command.go +++ /dev/null @@ -1,140 +0,0 @@ -/* -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. -*/ - -package migrateapiversion - -import ( - "context" - - "github.com/spf13/cobra" - apiextinstall "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/install" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/cli-runtime/pkg/genericclioptions" - cmdutil "k8s.io/kubectl/pkg/cmd/util" - "k8s.io/kubectl/pkg/util/i18n" - "k8s.io/kubectl/pkg/util/templates" - "sigs.k8s.io/controller-runtime/pkg/client" - - "github.com/cert-manager/cert-manager/cmd/ctl/pkg/build" - "github.com/cert-manager/cert-manager/cmd/ctl/pkg/factory" - acmeinstall "github.com/cert-manager/cert-manager/internal/apis/acme/install" - cminstall "github.com/cert-manager/cert-manager/internal/apis/certmanager/install" -) - -var ( - long = templates.LongDesc(i18n.T(` -Ensures resources in your Kubernetes cluster are persisted in the v1 API version. - -This must be run prior to upgrading to ensure your cluster is ready to upgrade to cert-manager v1.7 and beyond. - -This command must be run with a cluster running cert-manager v1.0 or greater.`)) - - example = templates.Examples(i18n.T(build.WithTemplate(` -# Check the cert-manager installation is ready to be upgraded to v1.7 and perform necessary migrations -# to ensure that the kube-apiserver has stored only v1 API versions. -{{.BuildName}} upgrade migrate-api-version - -# Force migrations to be run, even if the 'status.storedVersion' field on the CRDs does not contain -# old, deprecated API versions. -# This should only be used if you have manually edited/patched the CRDs already. -# It will force a read and a write of ALL cert-manager resources unconditionally. -{{.BuildName}} upgrade migrate-api-version --skip-stored-version-check -`))) -) - -// Options is a struct to support renew command -type Options struct { - genericclioptions.IOStreams - *factory.Factory - - client client.Client - skipStoredVersionCheck bool - qps float32 - burst int -} - -// NewOptions returns initialized Options -func NewOptions(ioStreams genericclioptions.IOStreams) *Options { - return &Options{ - IOStreams: ioStreams, - } -} - -// NewCmdMigrate returns a cobra command for updating resources in an apiserver -// to force a new storage version to be used. -func NewCmdMigrate(ctx context.Context, ioStreams genericclioptions.IOStreams) *cobra.Command { - o := NewOptions(ioStreams) - cmd := &cobra.Command{ - Use: "migrate-api-version", - Short: "Migrate all existing persisted cert-manager resources to the v1 API version", - Long: long, - Example: example, - Run: func(cmd *cobra.Command, args []string) { - cmdutil.CheckErr(o.Validate(args)) - cmdutil.CheckErr(o.Complete()) - cmdutil.CheckErr(o.Run(ctx, args)) - }, - } - - cmd.Flags().BoolVar(&o.skipStoredVersionCheck, "skip-stored-version-check", o.skipStoredVersionCheck, ""+ - "If true, all resources will be read and written regardless of the 'status.storedVersions' on the CRD resource. "+ - "Use this mode if you have previously manually modified the 'status.storedVersions' field on CRD resources.") - cmd.Flags().Float32Var(&o.qps, "qps", 5, "Indicates the maximum QPS to the apiserver from the client.") - cmd.Flags().IntVar(&o.burst, "burst", 10, "Maximum burst value for queries set to the apiserver from the client.") - o.Factory = factory.New(ctx, cmd) - - return cmd -} - -// Validate validates the provided options -func (o *Options) Validate(_ []string) error { - return nil -} - -// Complete takes the command arguments and factory and infers any remaining options. -func (o *Options) Complete() error { - var err error - scheme := runtime.NewScheme() - apiextinstall.Install(scheme) - cminstall.Install(scheme) - acmeinstall.Install(scheme) - - if o.qps != 0 { - o.RESTConfig.QPS = o.qps - } - if o.burst != 0 { - o.RESTConfig.Burst = o.burst - } - o.client, err = client.New(o.RESTConfig, client.Options{Scheme: scheme}) - if err != nil { - return err - } - - return nil -} - -// Run executes renew command -func (o *Options) Run(ctx context.Context, args []string) error { - _, err := NewMigrator(o.client, o.skipStoredVersionCheck, o.Out, o.ErrOut).Run(ctx, "v1", []string{ - "certificates.cert-manager.io", - "certificaterequests.cert-manager.io", - "issuers.cert-manager.io", - "clusterissuers.cert-manager.io", - "orders.acme.cert-manager.io", - "challenges.acme.cert-manager.io", - }) - return err -} diff --git a/cmd/ctl/pkg/upgrade/migrateapiversion/migrator.go b/cmd/ctl/pkg/upgrade/migrateapiversion/migrator.go deleted file mode 100644 index a7d6bd4d9d0..00000000000 --- a/cmd/ctl/pkg/upgrade/migrateapiversion/migrator.go +++ /dev/null @@ -1,284 +0,0 @@ -/* -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. -*/ - -package migrateapiversion - -import ( - "context" - "fmt" - "io" - "time" - - apierrors "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/apimachinery/pkg/util/sets" - "k8s.io/apimachinery/pkg/util/wait" - "k8s.io/client-go/util/retry" - - apiext "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" - "sigs.k8s.io/controller-runtime/pkg/client" -) - -type Migrator struct { - // Client used for API interactions - Client client.Client - - // If true, skip checking the 'status.storedVersion' before running the migration. - // By default, migration will only be run if the CRD contains storedVersions other - // than the desired target version. - SkipStoredVersionCheck bool - - // Writers to write informational & error messages to - Out, ErrOut io.Writer -} - -// NewMigrator creates a new migrator with the given API client. -// If either of out or errOut are nil, log messages will be discarded. -func NewMigrator(client client.Client, skipStoredVersionCheck bool, out, errOut io.Writer) *Migrator { - if out == nil { - out = io.Discard - } - if errOut == nil { - errOut = io.Discard - } - - return &Migrator{ - Client: client, - SkipStoredVersionCheck: skipStoredVersionCheck, - Out: out, - ErrOut: errOut, - } -} - -// Run begins the migration of all the named CRDs. -// It will attempt to migrate all resources defined as part of these CRDs to the -// given 'targetVersion', and after completion will update the `status.storedVersions` -// field on the corresponding CRD version to only contain the given targetVersion. -// Returns 'true' if a migration was actually performed, and false if migration was not required. -func (m *Migrator) Run(ctx context.Context, targetVersion string, names []string) (bool, error) { - fmt.Fprintf(m.Out, "Checking all CustomResourceDefinitions have storage version set to \"%s\"\n", targetVersion) - allTargetVersion, allCRDs, err := m.ensureCRDStorageVersionEquals(ctx, targetVersion, names) - if err != nil { - return false, err - } - if !allTargetVersion { - fmt.Fprintf(m.ErrOut, "It looks like you are running a version of cert-manager that does not set the storage version of CRDs to %q. You MUST upgrade to cert-manager v1.0-v1.6 before migrating resources for v1.7.\n", targetVersion) - return false, fmt.Errorf("preflight checks failed") - } - fmt.Fprintf(m.Out, "All CustomResourceDefinitions have %q configured as the storage version.\n", targetVersion) - - crdsRequiringMigration := allCRDs - if !m.SkipStoredVersionCheck { - fmt.Fprintf(m.Out, "Looking for CRDs that contain resources that require migrating to %q...\n", targetVersion) - crdsRequiringMigration, err = m.discoverCRDsRequiringMigration(ctx, targetVersion, names) - if err != nil { - fmt.Fprintf(m.ErrOut, "Failed to determine resource types that require migration: %v\n", err) - return false, err - } - if len(crdsRequiringMigration) == 0 { - fmt.Fprintln(m.Out, "Nothing to do. cert-manager CRDs do not have \"status.storedVersions\" containing old API versions. You may proceed to upgrade to cert-manager v1.7.") - return false, nil - } - } else { - fmt.Fprintln(m.Out, "Forcing migration of all CRD resources as --skip-stored-version-check=true") - } - - fmt.Fprintf(m.Out, "Found %d resource types that require migration:\n", len(crdsRequiringMigration)) - for _, crd := range crdsRequiringMigration { - fmt.Fprintf(m.Out, " - %s (%s)\n", crd.Name, crd.Spec.Names.Kind) - } - - for _, crd := range crdsRequiringMigration { - if err := m.migrateResourcesForCRD(ctx, crd); err != nil { - fmt.Fprintf(m.ErrOut, "Failed to migrate resource: %v\n", err) - return false, err - } - } - - fmt.Fprintf(m.Out, "Patching CRD resources to set \"status.storedVersions\" to %q...\n", targetVersion) - if err := m.patchCRDStoredVersions(ctx, crdsRequiringMigration); err != nil { - fmt.Fprintf(m.ErrOut, "Failed to patch \"status.storedVersions\" field: %v\n", err) - return false, err - } - - fmt.Fprintln(m.Out, "Successfully migrated all cert-manager resource types. It is now safe to upgrade to cert-manager v1.7.") - return true, nil -} - -func (m *Migrator) ensureCRDStorageVersionEquals(ctx context.Context, vers string, names []string) (bool, []*apiext.CustomResourceDefinition, error) { - var crds []*apiext.CustomResourceDefinition - for _, crdName := range names { - crd := &apiext.CustomResourceDefinition{} - if err := m.Client.Get(ctx, client.ObjectKey{Name: crdName}, crd); err != nil { - return false, nil, err - } - - // Discover the storage version - storageVersion := storageVersionForCRD(crd) - - if storageVersion != vers { - fmt.Fprintf(m.Out, "CustomResourceDefinition object %q has storage version set to %q.\n", crdName, storageVersion) - return false, nil, nil - } - - crds = append(crds, crd) - } - - return true, crds, nil -} - -func (m *Migrator) discoverCRDsRequiringMigration(ctx context.Context, desiredStorageVersion string, names []string) ([]*apiext.CustomResourceDefinition, error) { - var requireMigration []*apiext.CustomResourceDefinition - for _, name := range names { - crd := &apiext.CustomResourceDefinition{} - if err := m.Client.Get(ctx, client.ObjectKey{Name: name}, crd); err != nil { - return nil, err - } - // If no versions are stored, there's nothing to migrate. - if len(crd.Status.StoredVersions) == 0 { - continue - } - // If more than one entry exists in `storedVersions` OR if the only element in there is not - // the desired version, perform a migration. - if len(crd.Status.StoredVersions) > 1 || crd.Status.StoredVersions[0] != desiredStorageVersion { - requireMigration = append(requireMigration, crd) - } - } - return requireMigration, nil -} - -func (m *Migrator) migrateResourcesForCRD(ctx context.Context, crd *apiext.CustomResourceDefinition) error { - startTime := time.Now() - timeFormat := "15:04:05" - fmt.Fprintf(m.Out, "Migrating %q objects in group %q - this may take a while (started at %s)...\n", crd.Spec.Names.Kind, crd.Spec.Group, startTime.Format(timeFormat)) - list := &unstructured.UnstructuredList{} - list.SetGroupVersionKind(schema.GroupVersionKind{ - Group: crd.Spec.Group, - Version: storageVersionForCRD(crd), - Kind: crd.Spec.Names.ListKind, - }) - if err := m.Client.List(ctx, list); err != nil { - return err - } - fmt.Fprintf(m.Out, " %d resources to migrate...\n", len(list.Items)) - for _, obj := range list.Items { - // retry on any kind of error to handle cases where e.g. the network connection to the apiserver fails - if err := retry.OnError(wait.Backoff{ - Duration: time.Second, // wait 1s between attempts - Steps: 3, // allow up to 3 attempts per object - }, func(err error) bool { - // Retry on any errors that are not otherwise skipped/ignored - return handleUpdateErr(err) != nil - }, func() error { return m.Client.Update(ctx, &obj) }); handleUpdateErr(err) != nil { - return err - } - } - // add 500ms to the duration to ensure we always round up - duration := time.Now().Sub(startTime) + (time.Millisecond * 500) - fmt.Fprintf(m.Out, " Successfully migrated %d %s objects in %s\n", len(list.Items), crd.Spec.Names.Kind, duration.Round(time.Second)) - return nil -} - -// patchCRDStoredVersions will patch the `status.storedVersions` field of all passed in CRDs to be -// set to an array containing JUST the current storage version. -// This is only safe to run after a successful migration (i.e. a read/write of all resources of the given CRD type). -func (m *Migrator) patchCRDStoredVersions(ctx context.Context, crds []*apiext.CustomResourceDefinition) error { - for _, crd := range crds { - // fetch a fresh copy of the CRD to avoid any conflict errors - freshCRD := &apiext.CustomResourceDefinition{} - if err := m.Client.Get(ctx, client.ObjectKey{Name: crd.Name}, freshCRD); err != nil { - return err - } - - // Check the latest copy of the CRD to ensure that: - // 1) the storage version is the same as it was at the start of the migration - // 2) the status.storedVersion field has not changed, and if it has, it has only added the new/desired storage version - // This helps to avoid cases where the storage version was changed by a third-party midway through the migration, - // which could lead to corrupted apiservers when we patch the status.storedVersions field below. - expectedStorageVersion := storageVersionForCRD(crd) - if storageVersionForCRD(freshCRD) != expectedStorageVersion { - return newUnexpectedChangeError(crd) - } - newlyAddedVersions := storedVersionsAdded(crd, freshCRD) - if newlyAddedVersions.Len() != 0 && !newlyAddedVersions.Equal(sets.NewString(expectedStorageVersion)) { - return newUnexpectedChangeError(crd) - } - - // Set the `status.storedVersions` field to the target storage version - freshCRD.Status.StoredVersions = []string{storageVersionForCRD(crd)} - - if err := m.Client.Status().Update(ctx, freshCRD); err != nil { - return err - } - } - - return nil -} - -// storageVersionForCRD discovers the storage version for a given CRD. -func storageVersionForCRD(crd *apiext.CustomResourceDefinition) string { - storageVersion := "" - for _, v := range crd.Spec.Versions { - if v.Storage { - storageVersion = v.Name - break - } - } - return storageVersion -} - -// storedVersionsAdded returns a list of any versions added to the `status.storedVersions` field on -// a CRD resource. -func storedVersionsAdded(old, new *apiext.CustomResourceDefinition) sets.String { - oldStoredVersions := sets.NewString(old.Status.StoredVersions...) - newStoredVersions := sets.NewString(new.Status.StoredVersions...) - return newStoredVersions.Difference(oldStoredVersions) -} - -// newUnexpectedChangeError creates a new 'error' that informs users that a change to the CRDs -// was detected during the migration process and so the migration must be re-run. -func newUnexpectedChangeError(crd *apiext.CustomResourceDefinition) error { - errorFmt := "" + - "The CRD %q unexpectedly changed during the migration. " + - "This means that either an object was persisted in a non-storage version during the migration, " + - "or the storage version was changed by someone else (or some automated deployment tooling) whilst the migration " + - "was in progress.\n\n" + - "All automated deployment tooling should be in a stable state (i.e. no upgrades to cert-manager CRDs should be" + - "in progress whilst the migration is running).\n\n" + - "Please ensure no changes to the CRDs are made during the migration process and re-run the migration until you" + - "no longer see this message." - return fmt.Errorf(errorFmt, crd.Name) -} - -// handleUpdateErr will absorb certain types of errors that we know can be skipped/passed on -// during a migration of a particular object. -func handleUpdateErr(err error) error { - if err == nil { - return nil - } - // If the resource no longer exists, don't return the error as the object no longer - // needs updating to the new API version. - if apierrors.IsNotFound(err) { - return nil - } - // If there was a conflict, another client must have written the object already which - // means we don't need to force an update. - if apierrors.IsConflict(err) { - return nil - } - return err -} diff --git a/cmd/ctl/pkg/upgrade/upgrade.go b/cmd/ctl/pkg/upgrade/upgrade.go deleted file mode 100644 index 2b34d0f47ca..00000000000 --- a/cmd/ctl/pkg/upgrade/upgrade.go +++ /dev/null @@ -1,38 +0,0 @@ -/* -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. -*/ - -package upgrade - -import ( - "context" - - "github.com/spf13/cobra" - "k8s.io/cli-runtime/pkg/genericclioptions" - - "github.com/cert-manager/cert-manager/cmd/ctl/pkg/upgrade/migrateapiversion" -) - -func NewCmdUpgrade(ctx context.Context, ioStreams genericclioptions.IOStreams) *cobra.Command { - cmds := &cobra.Command{ - Use: "upgrade", - Short: "Tools that assist in upgrading cert-manager", - Long: `Note: this command does NOT actually upgrade cert-manager installations`, - } - - cmds.AddCommand(migrateapiversion.NewCmdMigrate(ctx, ioStreams)) - - return cmds -} diff --git a/cmd/ctl/pkg/version/version.go b/cmd/ctl/pkg/version/version.go deleted file mode 100644 index 9d1cbd800cb..00000000000 --- a/cmd/ctl/pkg/version/version.go +++ /dev/null @@ -1,190 +0,0 @@ -/* -Copyright 2020 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. -*/ - -package version - -import ( - "context" - "encoding/json" - "errors" - "fmt" - - "github.com/spf13/cobra" - "k8s.io/cli-runtime/pkg/genericclioptions" - cmdutil "k8s.io/kubectl/pkg/cmd/util" - "k8s.io/kubectl/pkg/scheme" - "sigs.k8s.io/yaml" - - "github.com/cert-manager/cert-manager/cmd/ctl/pkg/build" - "github.com/cert-manager/cert-manager/cmd/ctl/pkg/factory" - "github.com/cert-manager/cert-manager/pkg/util" - "github.com/cert-manager/cert-manager/pkg/util/versionchecker" -) - -// Version is a struct for version information -type Version struct { - ClientVersion *util.Version `json:"clientVersion,omitempty"` - ServerVersion *versionchecker.Version `json:"serverVersion,omitempty"` -} - -// Options is a struct to support version command -type Options struct { - // If true, don't try to retrieve the installed version - ClientOnly bool - - // If true, only prints the version number. - Short bool - - // Output is the target output format for the version string. This may be of - // value "", "json" or "yaml". - Output string - - VersionChecker versionchecker.Interface - - genericclioptions.IOStreams - *factory.Factory -} - -// NewOptions returns initialized Options -func NewOptions(ioStreams genericclioptions.IOStreams) *Options { - return &Options{ - IOStreams: ioStreams, - } -} - -func versionLong() string { - return build.WithTemplate(`Print the cert-manager CLI version and the deployed cert-manager version. -The CLI version is embedded in the binary and directly displayed. Determining -the the deployed cert-manager version is done by querying the cert-manger -resources. First, the tool looks at the labels of the cert-manager CRD -resources. Then, it searches for the labels of the resources related the the -cert-manager webhook linked in the CRDs. It also tries to derive the version -from the docker image tag of that webhook service. After gathering all this -version information, the tool checks if all versions are the same and returns -that version. If no version information is found or the found versions differ, -an error will be displayed. - -The '--client' flag can be used to disable the logic that tries to determine the installed -cert-manager version. - -Some example uses: - $ {{.BuildName}} version -or - $ {{.BuildName}} version --client -or - $ {{.BuildName}} version --short -or - $ {{.BuildName}} version -o yaml -`) -} - -// NewCmdVersion returns a cobra command for fetching versions -func NewCmdVersion(ctx context.Context, ioStreams genericclioptions.IOStreams) *cobra.Command { - o := NewOptions(ioStreams) - - cmd := &cobra.Command{ - Use: "version", - Short: "Print the cert-manager CLI version and the deployed cert-manager version", - Long: versionLong(), - Run: func(cmd *cobra.Command, args []string) { - cmdutil.CheckErr(o.Validate()) - cmdutil.CheckErr(o.Complete()) - cmdutil.CheckErr(o.Run(ctx)) - }, - } - - cmd.Flags().BoolVar(&o.ClientOnly, "client", o.ClientOnly, "If true, shows client version only (no server required).") - cmd.Flags().BoolVar(&o.Short, "short", o.Short, "If true, print just the version number.") - cmd.Flags().StringVarP(&o.Output, "output", "o", o.Output, "One of 'yaml' or 'json'.") - - o.Factory = factory.New(ctx, cmd) - - return cmd -} - -// Validate validates the provided options -func (o *Options) Validate() error { - switch o.Output { - case "", "yaml", "json": - return nil - default: - return errors.New(`--output must be '', 'yaml' or 'json'`) - } -} - -// Complete takes the command arguments and factory and infers any remaining options. -func (o *Options) Complete() error { - if o.ClientOnly { - return nil - } - - versionChecker, err := versionchecker.New(o.RESTConfig, scheme.Scheme) - if err != nil { - return err - } - o.VersionChecker = versionChecker - return nil -} - -// Run executes version command -func (o *Options) Run(ctx context.Context) error { - var ( - serverVersion *versionchecker.Version - serverErr error - versionInfo Version - ) - - clientVersion := util.VersionInfo() - versionInfo.ClientVersion = &clientVersion - - if !o.ClientOnly { - serverVersion, serverErr = o.VersionChecker.Version(ctx) - versionInfo.ServerVersion = serverVersion - } - - switch o.Output { - case "": - if o.Short { - fmt.Fprintf(o.Out, "Client Version: %s\n", clientVersion.GitVersion) - if serverVersion != nil { - fmt.Fprintf(o.Out, "Server Version: %s\n", serverVersion.Detected) - } - } else { - fmt.Fprintf(o.Out, "Client Version: %s\n", fmt.Sprintf("%#v", clientVersion)) - if serverVersion != nil { - fmt.Fprintf(o.Out, "Server Version: %s\n", fmt.Sprintf("%#v", serverVersion)) - } - } - case "yaml": - marshalled, err := yaml.Marshal(&versionInfo) - if err != nil { - return err - } - fmt.Fprint(o.Out, string(marshalled)) - case "json": - marshalled, err := json.MarshalIndent(&versionInfo, "", " ") - if err != nil { - return err - } - fmt.Fprintln(o.Out, string(marshalled)) - default: - // There is a bug in the program if we hit this case. - // However, we follow a policy of never panicking. - return fmt.Errorf("VersionOptions were not validated: --output=%q should have been rejected", o.Output) - } - - return serverErr -} diff --git a/cmd/startupapicheck/LICENSE b/cmd/startupapicheck/LICENSE new file mode 100644 index 00000000000..d6456956733 --- /dev/null +++ b/cmd/startupapicheck/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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. diff --git a/cmd/startupapicheck/LICENSES b/cmd/startupapicheck/LICENSES new file mode 100644 index 00000000000..d5b111d6997 --- /dev/null +++ b/cmd/startupapicheck/LICENSES @@ -0,0 +1,127 @@ +This LICENSES file is generated by the `licenses` module in makefile-modules[0]. + +The licenses below the "---" are determined by the go-licenses tool[1]. + +The aim of this file is to collect the licenses of all dependencies, and provide +a single source of truth for licenses used by this project. + +## For Developers + +If CI reports that this file is out of date, you should be careful to check that the +new licenses are acceptable for this project before running `make generate-go-licenses` +to update this file. + +Acceptable licenses are those allowlisted by the CNCF[2]. + +You MUST NOT add any new dependencies whose licenses are not allowlisted by the CNCF, +or which do not have an explicit license exception[3]. + +## For Users + +If this file was included in a release artifact, it is a snapshot of the licenses of all dependencies at the time of the release. + +You can retrieve the actual license text by following these steps: + +1. Find the dependency name in this file +2. Go to the source code repository of this project, and go to the tag corresponding to this release. +3. Find the exact version of the dependency in the `go.mod` file +4. Search for the dependency at the correct version in the [Go package index](https://pkg.go.dev/). + +## Links + +[0]: https://github.com/cert-manager/makefile-modules/ +[1]: https://github.com/google/go-licenses +[2]: https://github.com/cncf/foundation/blob/db4179134ebe7fa00b140a050c19147db808b6fa/policies-guidance/allowed-third-party-license-policy.md#cncf-allowlist-license-policy +[3]: https://github.com/cncf/foundation/blob/db4179134ebe7fa00b140a050c19147db808b6fa/license-exceptions/README.md + +--- + +github.com/Azure/go-ntlmssp,MIT +github.com/beorn7/perks/quantile,MIT +github.com/blang/semver/v4,MIT +github.com/cert-manager/cert-manager,Apache-2.0 +github.com/cert-manager/cert-manager/startupapicheck-binary,Apache-2.0 +github.com/cespare/xxhash/v2,MIT +github.com/davecgh/go-spew/spew,ISC +github.com/emicklei/go-restful/v3,MIT +github.com/evanphx/json-patch/v5,BSD-3-Clause +github.com/fsnotify/fsnotify,BSD-3-Clause +github.com/fxamacker/cbor/v2,MIT +github.com/go-asn1-ber/asn1-ber,MIT +github.com/go-errors/errors,MIT +github.com/go-ldap/ldap/v3,MIT +github.com/go-logr/logr,Apache-2.0 +github.com/go-logr/zapr,Apache-2.0 +github.com/go-openapi/jsonpointer,Apache-2.0 +github.com/go-openapi/jsonreference,Apache-2.0 +github.com/go-openapi/swag,Apache-2.0 +github.com/go-openapi/swag/jsonname,Apache-2.0 +github.com/gogo/protobuf,BSD-3-Clause +github.com/google/btree,Apache-2.0 +github.com/google/gnostic-models,Apache-2.0 +github.com/google/uuid,BSD-3-Clause +github.com/gregjones/httpcache,MIT +github.com/josharian/intern,MIT +github.com/json-iterator/go,MIT +github.com/liggitt/tabwriter,BSD-3-Clause +github.com/mailru/easyjson,MIT +github.com/moby/term,Apache-2.0 +github.com/modern-go/concurrent,Apache-2.0 +github.com/modern-go/reflect2,Apache-2.0 +github.com/monochromegane/go-gitignore,MIT +github.com/munnerz/goautoneg,BSD-3-Clause +github.com/peterbourgon/diskv,MIT +github.com/pmezard/go-difflib/difflib,BSD-3-Clause +github.com/prometheus/client_golang/internal/github.com/golang/gddo/httputil,BSD-3-Clause +github.com/prometheus/client_golang/prometheus,Apache-2.0 +github.com/prometheus/client_model/go,Apache-2.0 +github.com/prometheus/common,Apache-2.0 +github.com/prometheus/procfs,Apache-2.0 +github.com/spf13/cobra,Apache-2.0 +github.com/spf13/pflag,BSD-3-Clause +github.com/x448/float16,MIT +github.com/xlab/treeprint,MIT +go.opentelemetry.io/otel,Apache-2.0 +go.opentelemetry.io/otel/trace,Apache-2.0 +go.uber.org/multierr,MIT +go.uber.org/zap,MIT +go.yaml.in/yaml/v2,Apache-2.0 +go.yaml.in/yaml/v3,MIT +golang.org/x/crypto,BSD-3-Clause +golang.org/x/net,BSD-3-Clause +golang.org/x/oauth2,BSD-3-Clause +golang.org/x/sync/errgroup,BSD-3-Clause +golang.org/x/sys/unix,BSD-3-Clause +golang.org/x/term,BSD-3-Clause +golang.org/x/text,BSD-3-Clause +golang.org/x/time/rate,BSD-3-Clause +gomodules.xyz/jsonpatch/v2,Apache-2.0 +google.golang.org/protobuf,BSD-3-Clause +gopkg.in/evanphx/json-patch.v4,BSD-3-Clause +gopkg.in/inf.v0,BSD-3-Clause +gopkg.in/yaml.v3,MIT +k8s.io/api,Apache-2.0 +k8s.io/apiextensions-apiserver/pkg/apis/apiextensions,Apache-2.0 +k8s.io/apimachinery/pkg,Apache-2.0 +k8s.io/apimachinery/third_party/forked/golang,BSD-3-Clause +k8s.io/cli-runtime/pkg,Apache-2.0 +k8s.io/client-go,Apache-2.0 +k8s.io/client-go/third_party/forked/golang/template,BSD-3-Clause +k8s.io/component-base,Apache-2.0 +k8s.io/klog/v2,Apache-2.0 +k8s.io/kube-openapi/pkg,Apache-2.0 +k8s.io/kube-openapi/pkg/internal/third_party/go-json-experiment/json,BSD-3-Clause +k8s.io/kube-openapi/pkg/validation/spec,Apache-2.0 +k8s.io/utils,Apache-2.0 +k8s.io/utils/internal/third_party/forked/golang,BSD-3-Clause +sigs.k8s.io/controller-runtime,Apache-2.0 +sigs.k8s.io/gateway-api/apis/v1,Apache-2.0 +sigs.k8s.io/json,Apache-2.0 +sigs.k8s.io/json,BSD-3-Clause +sigs.k8s.io/kustomize/api,Apache-2.0 +sigs.k8s.io/kustomize/kyaml,Apache-2.0 +sigs.k8s.io/randfill,Apache-2.0 +sigs.k8s.io/structured-merge-diff/v6,Apache-2.0 +sigs.k8s.io/yaml,MIT +sigs.k8s.io/yaml,Apache-2.0 +sigs.k8s.io/yaml,BSD-3-Clause diff --git a/cmd/startupapicheck/go.mod b/cmd/startupapicheck/go.mod new file mode 100644 index 00000000000..f1920b6fdda --- /dev/null +++ b/cmd/startupapicheck/go.mod @@ -0,0 +1,98 @@ +module github.com/cert-manager/cert-manager/startupapicheck-binary + +go 1.25.0 + +// Do not remove this comment: +// please place any replace statements here at the top for visibility and add a +// comment to it as to when it can be removed + +replace github.com/cert-manager/cert-manager => ../../ + +require ( + github.com/cert-manager/cert-manager v0.0.0-00010101000000-000000000000 + github.com/spf13/cobra v1.10.1 + github.com/spf13/pflag v1.0.10 + k8s.io/apimachinery v0.34.1 + k8s.io/cli-runtime v0.34.1 + k8s.io/client-go v0.34.1 + k8s.io/component-base v0.34.1 + sigs.k8s.io/controller-runtime v0.22.3 +) + +require ( + github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect + github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 // indirect + github.com/beorn7/perks v1.0.1 // indirect + github.com/blang/semver/v4 v4.0.0 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/emicklei/go-restful/v3 v3.13.0 // indirect + github.com/evanphx/json-patch/v5 v5.9.11 // indirect + github.com/fsnotify/fsnotify v1.9.0 // indirect + github.com/fxamacker/cbor/v2 v2.9.0 // indirect + github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667 // indirect + github.com/go-errors/errors v1.5.1 // indirect + github.com/go-ldap/ldap/v3 v3.4.12 // indirect + github.com/go-logr/logr v1.4.3 // indirect + github.com/go-logr/zapr v1.3.0 // indirect + github.com/go-openapi/jsonpointer v0.22.1 // indirect + github.com/go-openapi/jsonreference v0.21.2 // indirect + github.com/go-openapi/swag v0.23.1 // indirect + github.com/go-openapi/swag/jsonname v0.25.1 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/google/btree v1.1.3 // indirect + github.com/google/gnostic-models v0.7.0 // indirect + github.com/google/go-cmp v0.7.0 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/josharian/intern v1.0.0 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect + github.com/mailru/easyjson v0.9.0 // indirect + github.com/moby/term v0.5.0 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect + github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/peterbourgon/diskv v2.0.1+incompatible // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + github.com/prometheus/client_golang v1.23.2 // indirect + github.com/prometheus/client_model v0.6.2 // indirect + github.com/prometheus/common v0.66.1 // indirect + github.com/prometheus/procfs v0.17.0 // indirect + github.com/sergi/go-diff v1.3.1 // indirect + github.com/x448/float16 v0.8.4 // indirect + github.com/xlab/treeprint v1.2.0 // indirect + go.opentelemetry.io/otel v1.37.0 // indirect + go.opentelemetry.io/otel/trace v1.37.0 // indirect + go.uber.org/multierr v1.11.0 // indirect + go.uber.org/zap v1.27.0 // indirect + go.yaml.in/yaml/v2 v2.4.2 // indirect + go.yaml.in/yaml/v3 v3.0.4 // indirect + golang.org/x/crypto v0.43.0 // indirect + golang.org/x/net v0.46.0 // indirect + golang.org/x/oauth2 v0.32.0 // indirect + golang.org/x/sync v0.17.0 // indirect + golang.org/x/sys v0.37.0 // indirect + golang.org/x/term v0.36.0 // indirect + golang.org/x/text v0.30.0 // indirect + golang.org/x/time v0.14.0 // indirect + gomodules.xyz/jsonpatch/v2 v2.5.0 // indirect + google.golang.org/protobuf v1.36.10 // indirect + gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect + gopkg.in/inf.v0 v0.9.1 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect + k8s.io/api v0.34.1 // indirect + k8s.io/apiextensions-apiserver v0.34.1 // indirect + k8s.io/klog/v2 v2.130.1 // indirect + k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 // indirect + k8s.io/utils v0.0.0-20250820121507-0af2bda4dd1d // indirect + sigs.k8s.io/gateway-api v1.4.0 // indirect + sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect + sigs.k8s.io/kustomize/api v0.20.1 // indirect + sigs.k8s.io/kustomize/kyaml v0.20.1 // indirect + sigs.k8s.io/randfill v1.0.0 // indirect + sigs.k8s.io/structured-merge-diff/v6 v6.3.0 // indirect + sigs.k8s.io/yaml v1.6.0 // indirect +) diff --git a/cmd/startupapicheck/go.sum b/cmd/startupapicheck/go.sum new file mode 100644 index 00000000000..5e3311382c4 --- /dev/null +++ b/cmd/startupapicheck/go.sum @@ -0,0 +1,267 @@ +github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0= +github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= +github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 h1:mFRzDkZVAjdal+s7s0MwaRv9igoPqLRdzOLzw/8Xvq8= +github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU= +github.com/alexbrainman/sspi v0.0.0-20250919150558-7d374ff0d59e h1:4dAU9FXIyQktpoUAgOJK3OTFc/xug0PCXYCqU0FgDKI= +github.com/alexbrainman/sspi v0.0.0-20250919150558-7d374ff0d59e/go.mod h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4= +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= +github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= +github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= +github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/emicklei/go-restful/v3 v3.13.0 h1:C4Bl2xDndpU6nJ4bc1jXd+uTmYPVUwkD6bFY/oTyCes= +github.com/emicklei/go-restful/v3 v3.13.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/evanphx/json-patch v5.9.0+incompatible h1:fBXyNpNMuTTDdquAq/uisOr2lShz4oaXpDTX2bLe7ls= +github.com/evanphx/json-patch v5.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/evanphx/json-patch/v5 v5.9.11 h1:/8HVnzMq13/3x9TPvjG08wUGqBTmZBsCWzjTM0wiaDU= +github.com/evanphx/json-patch/v5 v5.9.11/go.mod h1:3j+LviiESTElxA4p3EMKAB9HXj3/XEtnUf6OZxqIQTM= +github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= +github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= +github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM= +github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= +github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667 h1:BP4M0CvQ4S3TGls2FvczZtj5Re/2ZzkV9VwqPHH/3Bo= +github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= +github.com/go-errors/errors v1.5.1 h1:ZwEMSLRCapFLflTpT7NKaAc7ukJ8ZPEjzlxt8rPN8bk= +github.com/go-errors/errors v1.5.1/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= +github.com/go-ldap/ldap/v3 v3.4.12 h1:1b81mv7MagXZ7+1r7cLTWmyuTqVqdwbtJSjC0DAp9s4= +github.com/go-ldap/ldap/v3 v3.4.12/go.mod h1:+SPAGcTtOfmGsCb3h1RFiq4xpp4N636G75OEace8lNo= +github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= +github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ= +github.com/go-logr/zapr v1.3.0/go.mod h1:YKepepNBd1u/oyhd/yQmtjVXmm9uML4IXUgMOwR8/Gg= +github.com/go-openapi/jsonpointer v0.22.1 h1:sHYI1He3b9NqJ4wXLoJDKmUmHkWy/L7rtEo92JUxBNk= +github.com/go-openapi/jsonpointer v0.22.1/go.mod h1:pQT9OsLkfz1yWoMgYFy4x3U5GY5nUlsOn1qSBH5MkCM= +github.com/go-openapi/jsonreference v0.21.2 h1:Wxjda4M/BBQllegefXrY/9aq1fxBA8sI5M/lFU6tSWU= +github.com/go-openapi/jsonreference v0.21.2/go.mod h1:pp3PEjIsJ9CZDGCNOyXIQxsNuroxm8FAJ/+quA0yKzQ= +github.com/go-openapi/swag v0.23.1 h1:lpsStH0n2ittzTnbaSloVZLuB5+fvSY/+hnagBjSNZU= +github.com/go-openapi/swag v0.23.1/go.mod h1:STZs8TbRvEQQKUA+JZNAm3EWlgaOBGpyFDqQnDHMef0= +github.com/go-openapi/swag/jsonname v0.25.1 h1:Sgx+qbwa4ej6AomWC6pEfXrA6uP2RkaNjA9BR8a1RJU= +github.com/go-openapi/swag/jsonname v0.25.1/go.mod h1:71Tekow6UOLBD3wS7XhdT98g5J5GR13NOTQ9/6Q11Zo= +github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= +github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg= +github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= +github.com/google/gnostic-models v0.7.0 h1:qwTtogB15McXDaNqTZdzPJRHvaVJlAl+HVQnLmJEJxo= +github.com/google/gnostic-models v0.7.0/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= +github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db h1:097atOisP2aRj7vFgYQBbFN4U4JNXUNYpxael3UzMyo= +github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 h1:+ngKgrYPPJrOjhax5N+uePQ0Fh1Z7PheYoUI/0nzkPA= +github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= +github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= +github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/jcmturner/aescts/v2 v2.0.0 h1:9YKLH6ey7H4eDBXW8khjYslgyqG2xZikXP0EQFKrle8= +github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs= +github.com/jcmturner/dnsutils/v2 v2.0.0 h1:lltnkeZGL0wILNvrNiVCR6Ro5PGU/SeBvVO/8c/iPbo= +github.com/jcmturner/dnsutils/v2 v2.0.0/go.mod h1:b0TnjGOvI/n42bZa+hmXL+kFJZsFT7G4t3HTlQ184QM= +github.com/jcmturner/gofork v1.7.6 h1:QH0l3hzAU1tfT3rZCnW5zXl+orbkNMMRGJfdJjHVETg= +github.com/jcmturner/gofork v1.7.6/go.mod h1:1622LH6i/EZqLloHfE7IeZ0uEJwMSUyQ/nDd82IeqRo= +github.com/jcmturner/goidentity/v6 v6.0.1 h1:VKnZd2oEIMorCTsFBnJWbExfNN7yZr3EhJAxwOkZg6o= +github.com/jcmturner/goidentity/v6 v6.0.1/go.mod h1:X1YW3bgtvwAXju7V3LCIMpY0Gbxyjn/mY9zx4tFonSg= +github.com/jcmturner/gokrb5/v8 v8.4.4 h1:x1Sv4HaTpepFkXbt2IkL29DXRf8sOfZXo8eRKh687T8= +github.com/jcmturner/gokrb5/v8 v8.4.4/go.mod h1:1btQEpgT6k+unzCwX1KdWMEwPPkkgBtP+F6aCACiMrs= +github.com/jcmturner/rpc/v2 v2.0.3 h1:7FXXj8Ti1IaVFpSAziCZWNzbNuZmnvw/i6CqLNdWfZY= +github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= +github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de h1:9TO3cAIGXtEhnIaL+V+BEER86oLrvS+kWobKpbJuye0= +github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE= +github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4= +github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= +github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= +github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8= +github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 h1:n6/2gBQ3RWajuToeY6ZtZTIKv2v7ThUy5KKusIT0yc0= +github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00/go.mod h1:Pm3mSP3c5uWn86xMLZ5Sa7JB9GsEZySvHYXCTK4E9q4= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/onsi/ginkgo/v2 v2.22.0 h1:Yed107/8DjTr0lKCNt7Dn8yQ6ybuDRQoMGrNFKzMfHg= +github.com/onsi/ginkgo/v2 v2.22.0/go.mod h1:7Du3c42kxCUegi0IImZ1wUQzMBVecgIHjR1C+NkhLQo= +github.com/onsi/gomega v1.36.1 h1:bJDPBO7ibjxcbHMgSCoo4Yj18UWbKDlLwX1x9sybDcw= +github.com/onsi/gomega v1.36.1/go.mod h1:PvZbdDc8J6XJEpDK4HCuRBm8a6Fzp9/DmhC9C7yFlog= +github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI= +github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o= +github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg= +github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= +github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= +github.com/prometheus/common v0.66.1 h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9ZoGs= +github.com/prometheus/common v0.66.1/go.mod h1:gcaUsgf3KfRSwHY4dIMXLPV0K/Wg1oZ8+SbZk/HH/dA= +github.com/prometheus/procfs v0.17.0 h1:FuLQ+05u4ZI+SS/w9+BWEM2TXiHKsUQ9TADiRH7DuK0= +github.com/prometheus/procfs v0.17.0/go.mod h1:oPQLaDAMRbA+u8H5Pbfq+dl3VDAvHxMUOVhe0wYB2zw= +github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= +github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8= +github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I= +github.com/spf13/cobra v1.10.1 h1:lJeBwCfmrnXthfAupyUTzJ/J4Nc1RsHC/mSRU2dll/s= +github.com/spf13/cobra v1.10.1/go.mod h1:7SmJGaTHFVBY0jW4NXGluQoLvhqFQM+6XSKD+P4XaB0= +github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= +github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= +github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= +github.com/xlab/treeprint v1.2.0 h1:HzHnuAF1plUN2zGlAFHbSQP2qJ0ZAD3XF5XD7OesXRQ= +github.com/xlab/treeprint v1.2.0/go.mod h1:gj5Gd3gPdKtR1ikdDK6fnFLdmIS0X30kTTuNd/WEJu0= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ= +go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I= +go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4= +go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= +go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= +go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI= +go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU= +go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= +go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04= +golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.46.0 h1:giFlY12I07fugqwPuWJi68oOnpfqFnJIJzaIIm2JVV4= +golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210= +golang.org/x/oauth2 v0.32.0 h1:jsCblLleRMDrxMN29H3z/k1KliIvpLgCkE6R8FXXNgY= +golang.org/x/oauth2 v0.32.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug= +golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ= +golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/term v0.36.0 h1:zMPR+aF8gfksFprF/Nc/rd1wRS1EI6nDBGyWAvDzx2Q= +golang.org/x/term v0.36.0/go.mod h1:Qu394IJq6V6dCBRgwqshf3mPF85AqzYEzofzRdZkWss= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k= +golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM= +golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI= +golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.37.0 h1:DVSRzp7FwePZW356yEAChSdNcQo6Nsp+fex1SUW09lE= +golang.org/x/tools v0.37.0/go.mod h1:MBN5QPQtLMHVdvsbtarmTNukZDdgwdwlO5qGacAzF0w= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gomodules.xyz/jsonpatch/v2 v2.5.0 h1:JELs8RLM12qJGXU4u/TO3V25KW8GreMKl9pdkk14RM0= +gomodules.xyz/jsonpatch/v2 v2.5.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY= +google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= +google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/evanphx/json-patch.v4 v4.13.0 h1:czT3CmqEaQ1aanPc5SdlgQrrEIb8w/wwCvWWnfEbYzo= +gopkg.in/evanphx/json-patch.v4 v4.13.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= +gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= +gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +k8s.io/api v0.34.1 h1:jC+153630BMdlFukegoEL8E/yT7aLyQkIVuwhmwDgJM= +k8s.io/api v0.34.1/go.mod h1:SB80FxFtXn5/gwzCoN6QCtPD7Vbu5w2n1S0J5gFfTYk= +k8s.io/apiextensions-apiserver v0.34.1 h1:NNPBva8FNAPt1iSVwIE0FsdrVriRXMsaWFMqJbII2CI= +k8s.io/apiextensions-apiserver v0.34.1/go.mod h1:hP9Rld3zF5Ay2Of3BeEpLAToP+l4s5UlxiHfqRaRcMc= +k8s.io/apimachinery v0.34.1 h1:dTlxFls/eikpJxmAC7MVE8oOeP1zryV7iRyIjB0gky4= +k8s.io/apimachinery v0.34.1/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw= +k8s.io/cli-runtime v0.34.1 h1:btlgAgTrYd4sk8vJTRG6zVtqBKt9ZMDeQZo2PIzbL7M= +k8s.io/cli-runtime v0.34.1/go.mod h1:aVA65c+f0MZiMUPbseU/M9l1Wo2byeaGwUuQEQVVveE= +k8s.io/client-go v0.34.1 h1:ZUPJKgXsnKwVwmKKdPfw4tB58+7/Ik3CrjOEhsiZ7mY= +k8s.io/client-go v0.34.1/go.mod h1:kA8v0FP+tk6sZA0yKLRG67LWjqufAoSHA2xVGKw9Of8= +k8s.io/component-base v0.34.1 h1:v7xFgG+ONhytZNFpIz5/kecwD+sUhVE6HU7qQUiRM4A= +k8s.io/component-base v0.34.1/go.mod h1:mknCpLlTSKHzAQJJnnHVKqjxR7gBeHRv0rPXA7gdtQ0= +k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= +k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= +k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 h1:Y3gxNAuB0OBLImH611+UDZcmKS3g6CthxToOb37KgwE= +k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912/go.mod h1:kdmbQkyfwUagLfXIad1y2TdrjPFWp2Q89B3qkRwf/pQ= +k8s.io/utils v0.0.0-20250820121507-0af2bda4dd1d h1:wAhiDyZ4Tdtt7e46e9M5ZSAJ/MnPGPs+Ki1gHw4w1R0= +k8s.io/utils v0.0.0-20250820121507-0af2bda4dd1d/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +sigs.k8s.io/controller-runtime v0.22.3 h1:I7mfqz/a/WdmDCEnXmSPm8/b/yRTy6JsKKENTijTq8Y= +sigs.k8s.io/controller-runtime v0.22.3/go.mod h1:+QX1XUpTXN4mLoblf4tqr5CQcyHPAki2HLXqQMY6vh8= +sigs.k8s.io/gateway-api v1.4.0 h1:ZwlNM6zOHq0h3WUX2gfByPs2yAEsy/EenYJB78jpQfQ= +sigs.k8s.io/gateway-api v1.4.0/go.mod h1:AR5RSqciWP98OPckEjOjh2XJhAe2Na4LHyXD2FUY7Qk= +sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg= +sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= +sigs.k8s.io/kustomize/api v0.20.1 h1:iWP1Ydh3/lmldBnH/S5RXgT98vWYMaTUL1ADcr+Sv7I= +sigs.k8s.io/kustomize/api v0.20.1/go.mod h1:t6hUFxO+Ph0VxIk1sKp1WS0dOjbPCtLJ4p8aADLwqjM= +sigs.k8s.io/kustomize/kyaml v0.20.1 h1:PCMnA2mrVbRP3NIB6v9kYCAc38uvFLVs8j/CD567A78= +sigs.k8s.io/kustomize/kyaml v0.20.1/go.mod h1:0EmkQHRUsJxY8Ug9Niig1pUMSCGHxQ5RklbpV/Ri6po= +sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= +sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= +sigs.k8s.io/structured-merge-diff/v6 v6.3.0 h1:jTijUJbW353oVOd9oTlifJqOGEkUw2jB/fXCbTiQEco= +sigs.k8s.io/structured-merge-diff/v6 v6.3.0/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE= +sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs= +sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4= diff --git a/cmd/startupapicheck/main.go b/cmd/startupapicheck/main.go new file mode 100644 index 00000000000..265299eadc5 --- /dev/null +++ b/cmd/startupapicheck/main.go @@ -0,0 +1,79 @@ +/* +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. +*/ + +package main + +import ( + "context" + + "github.com/spf13/cobra" + "github.com/spf13/pflag" + "k8s.io/component-base/logs" + ctrl "sigs.k8s.io/controller-runtime" + + "github.com/cert-manager/cert-manager/internal/cmd/util" + logf "github.com/cert-manager/cert-manager/pkg/logs" + "github.com/cert-manager/cert-manager/startupapicheck-binary/pkg/check" +) + +func main() { + ctx, exit := util.SetupExitHandler(context.Background(), util.AlwaysErrCode) + defer exit() // This function might call os.Exit, so defer last + + logf.InitLogs() + defer logf.FlushLogs() + ctrl.SetLogger(logf.Log) + ctx = logf.NewContext(ctx, logf.Log, "startupapicheck") + + logOptions := logs.NewOptions() + + cmd := &cobra.Command{ + Use: "startupapicheck", + Short: "Check that cert-manager started successfully", + Long: "Check that cert-manager started successfully", + PersistentPreRunE: func(cmd *cobra.Command, args []string) error { + return logf.ValidateAndApply(logOptions) + }, + + SilenceErrors: true, // Errors are already logged when calling cmd.Execute() + SilenceUsage: true, // Don't print usage on every error + } + + { + var logFlags pflag.FlagSet + logf.AddFlagsNonDeprecated(logOptions, &logFlags) + + logFlags.VisitAll(func(f *pflag.Flag) { + switch f.Name { + case "v": + // "cmctl check api" already had a "v" flag that did not require any value; to maintain compatibility with cmctl + // and backwards compatibility we allow the "v" logging flag to be set without a value + // and default to "2" (which will result in the same behaviour as before). + f.NoOptDefVal = "2" + cmd.PersistentFlags().AddFlag(f) + default: + cmd.PersistentFlags().AddFlag(f) + } + }) + } + + cmd.AddCommand(check.NewCmdCheck(ctx)) + + if err := cmd.ExecuteContext(ctx); err != nil { + logf.Log.Error(err, "error executing command") + util.SetExitCode(err) + } +} diff --git a/cmd/startupapicheck/pkg/check/api/api.go b/cmd/startupapicheck/pkg/check/api/api.go new file mode 100644 index 00000000000..7090c25bd79 --- /dev/null +++ b/cmd/startupapicheck/pkg/check/api/api.go @@ -0,0 +1,133 @@ +/* +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. +*/ + +package api + +import ( + "context" + "errors" + "fmt" + "io" + "time" + + "github.com/spf13/cobra" + "k8s.io/apimachinery/pkg/util/wait" + + cmcmdutil "github.com/cert-manager/cert-manager/internal/cmd/util" + logf "github.com/cert-manager/cert-manager/pkg/logs" + "github.com/cert-manager/cert-manager/pkg/util/cmapichecker" + "github.com/cert-manager/cert-manager/startupapicheck-binary/pkg/factory" +) + +// Options is a struct to support check api command +type Options struct { + // APIChecker is used to check that the cert-manager CRDs have been installed on the K8S + // API server and that the cert-manager webhooks are all working + APIChecker cmapichecker.Interface + + // Time before timeout when waiting + Wait time.Duration + + // Time between checks when waiting + Interval time.Duration + + *factory.Factory +} + +// Complete takes the command arguments and factory and infers any remaining options. +func (o *Options) Complete() error { + var err error + + o.APIChecker, err = cmapichecker.New( + o.RESTConfig, + o.Namespace, + ) + if err != nil { + return err + } + + return nil +} + +// NewCmdCheckApi returns a cobra command for checking creating cert-manager resources against the K8S API server +func NewCmdCheckApi(setupCtx context.Context) *cobra.Command { + o := &Options{} + + cmd := &cobra.Command{ + Use: "api", + Short: "Check if the cert-manager API is ready", + Long: ` +This check attempts to perform a dry-run create of a cert-manager *v1* +Certificate resource in order to verify that CRDs are installed and all the +required webhooks are reachable by the K8S API server.`, + + PreRunE: func(cmd *cobra.Command, args []string) error { + return o.Complete() + }, + // nolint:contextcheck // False positive + RunE: func(cmd *cobra.Command, args []string) error { + return o.Run(cmd.Context(), cmd.OutOrStdout()) + }, + } + cmd.Flags().DurationVar(&o.Wait, "wait", 0, "Wait until the cert-manager API is ready (default 0s = poll once)") + cmd.Flags().DurationVar(&o.Interval, "interval", 5*time.Second, "Time between checks when waiting, must include unit, e.g., 1m or 10m") + + o.Factory = factory.New(cmd) + + return cmd +} + +// Run executes check api command +func (o *Options) Run(ctx context.Context, out io.Writer) error { + log := logf.FromContext(ctx, "checkAPI") + + start := time.Now() + var lastError error + pollErr := wait.PollUntilContextCancel(ctx, o.Interval, true, func(ctx context.Context) (bool, error) { + if err := o.APIChecker.Check(ctx); err != nil { + simpleError := cmapichecker.TranslateToSimpleError(err) + if simpleError != nil { + log.V(2).Info("Not ready", "err", simpleError, "underlyingError", err) + lastError = simpleError + } else { + log.V(2).Info("Not ready", "err", err) + lastError = err + } + + if time.Since(start) > o.Wait { + return false, context.DeadlineExceeded + } + return false, nil + } + + return true, nil + }) + + if pollErr != nil { + if errors.Is(pollErr, context.DeadlineExceeded) && o.Wait > 0 { + log.V(2).Info("Timed out", "after", o.Wait, "err", pollErr) + cmcmdutil.SetExitCode(pollErr) + } else { + cmcmdutil.SetExitCode(lastError) + } + + return lastError + } + + fmt.Fprintln(out, "The cert-manager API is ready") + + return nil +} diff --git a/cmd/ctl/pkg/check/check.go b/cmd/startupapicheck/pkg/check/check.go similarity index 76% rename from cmd/ctl/pkg/check/check.go rename to cmd/startupapicheck/pkg/check/check.go index 583fbd92a0f..bba44ab7980 100644 --- a/cmd/ctl/pkg/check/check.go +++ b/cmd/startupapicheck/pkg/check/check.go @@ -1,5 +1,5 @@ /* -Copyright 2021 The cert-manager Authors. +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. @@ -20,15 +20,14 @@ import ( "context" "github.com/spf13/cobra" - "k8s.io/cli-runtime/pkg/genericclioptions" - "github.com/cert-manager/cert-manager/cmd/ctl/pkg/check/api" + "github.com/cert-manager/cert-manager/startupapicheck-binary/pkg/check/api" ) // NewCmdCheck returns a cobra command for checking cert-manager components. -func NewCmdCheck(ctx context.Context, ioStreams genericclioptions.IOStreams) *cobra.Command { +func NewCmdCheck(ctx context.Context) *cobra.Command { cmds := NewCmdCreateBare() - cmds.AddCommand(api.NewCmdCheckApi(ctx, ioStreams)) + cmds.AddCommand(api.NewCmdCheckApi(ctx)) return cmds } diff --git a/cmd/ctl/pkg/factory/factory.go b/cmd/startupapicheck/pkg/factory/factory.go similarity index 65% rename from cmd/ctl/pkg/factory/factory.go rename to cmd/startupapicheck/pkg/factory/factory.go index 87f1eb18983..3309ad6ddc6 100644 --- a/cmd/ctl/pkg/factory/factory.go +++ b/cmd/startupapicheck/pkg/factory/factory.go @@ -1,5 +1,5 @@ /* -Copyright 2021 The cert-manager Authors. +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. @@ -17,29 +17,20 @@ limitations under the License. package factory import ( - "context" - "github.com/spf13/cobra" "k8s.io/cli-runtime/pkg/genericclioptions" - "k8s.io/client-go/kubernetes" "k8s.io/client-go/rest" - "k8s.io/kubectl/pkg/cmd/util" // Load all auth plugins _ "k8s.io/client-go/plugin/pkg/client/auth" - - cmclient "github.com/cert-manager/cert-manager/pkg/client/clientset/versioned" -) - -var ( - kubeConfigFlags = genericclioptions.NewConfigFlags(true) - factory = util.NewFactory(kubeConfigFlags) ) // Factory provides a set of clients and configurations to authenticate and // access a target Kubernetes cluster. Factory will ensure that its fields are // populated and valid during command execution. type Factory struct { + restClientGetter genericclioptions.RESTClientGetter + // Namespace is the namespace that the user has requested with the // "--namespace" / "-n" flag. Defaults to "default" if the flag was not // provided. @@ -51,13 +42,6 @@ type Factory struct { // RESTConfig is a Kubernetes REST config that contains the user's // authentication and access configuration. RESTConfig *rest.Config - - // CMClient is a Kubernetes clientset for interacting with cert-manager APIs. - CMClient cmclient.Interface - - // KubeClient is a Kubernetes clientset for interacting with the base - // Kubernetes APIs. - KubeClient kubernetes.Interface } // New returns a new Factory. The supplied command will have flags registered @@ -65,20 +49,26 @@ type Factory struct { // populated when the command is executed using the cobra PreRun. If a PreRun // is already defined, it will be executed _after_ Factory has been populated, // making it available. -func New(ctx context.Context, cmd *cobra.Command) *Factory { +func New(cmd *cobra.Command) *Factory { f := new(Factory) + kubeConfigFlags := genericclioptions.NewConfigFlags(true) + f.restClientGetter = kubeConfigFlags + kubeConfigFlags.AddFlags(cmd.Flags()) - cmd.RegisterFlagCompletionFunc("namespace", validArgsListNamespaces(ctx, f)) // Setup a PreRun to populate the Factory. Catch the existing PreRun command // if one was defined, and execute it second. - existingPreRun := cmd.PreRun - cmd.PreRun = func(cmd *cobra.Command, args []string) { - util.CheckErr(f.complete()) + existingPreRun := cmd.PreRunE + cmd.PreRunE = func(cmd *cobra.Command, args []string) error { + if err := f.complete(); err != nil { + return err + } + if existingPreRun != nil { - existingPreRun(cmd, args) + return existingPreRun(cmd, args) } + return nil } return f @@ -89,22 +79,12 @@ func New(ctx context.Context, cmd *cobra.Command) *Factory { func (f *Factory) complete() error { var err error - f.Namespace, f.EnforceNamespace, err = factory.ToRawKubeConfigLoader().Namespace() - if err != nil { - return err - } - - f.RESTConfig, err = factory.ToRESTConfig() - if err != nil { - return err - } - - f.KubeClient, err = kubernetes.NewForConfig(f.RESTConfig) + f.Namespace, f.EnforceNamespace, err = f.restClientGetter.ToRawKubeConfigLoader().Namespace() if err != nil { return err } - f.CMClient, err = cmclient.NewForConfig(f.RESTConfig) + f.RESTConfig, err = f.restClientGetter.ToRESTConfig() if err != nil { return err } diff --git a/cmd/util/context.go b/cmd/util/context.go deleted file mode 100644 index 6f3c57f3865..00000000000 --- a/cmd/util/context.go +++ /dev/null @@ -1,36 +0,0 @@ -/* -Copyright 2021 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. -*/ - -package util - -import ( - "context" -) - -// ContextWithStopCh will wrap a context with a stop channel. -// When the provided stopCh closes, the cancel() will be called on the context. -// This provides a convenient way to represent a stop channel as a context. -func ContextWithStopCh(ctx context.Context, stopCh <-chan struct{}) context.Context { - ctx, cancel := context.WithCancel(ctx) - go func() { - defer cancel() - select { - case <-ctx.Done(): - case <-stopCh: - } - }() - return ctx -} diff --git a/cmd/util/defaults.go b/cmd/util/defaults.go deleted file mode 100644 index d3d0bf60e30..00000000000 --- a/cmd/util/defaults.go +++ /dev/null @@ -1,32 +0,0 @@ -/* -Copyright 2021 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. -*/ - -package util - -import ( - "time" -) - -const ( - DefaultLeaderElect = true - DefaultLeaderElectionNamespace = "kube-system" - DefaultLeaderElectionLeaseDuration = 60 * time.Second - DefaultLeaderElectionRenewDeadline = 40 * time.Second - DefaultLeaderElectionRetryPeriod = 15 * time.Second - - DefaultEnableProfiling = false - DefaultProfilerAddr = "localhost:6060" -) diff --git a/cmd/webhook/LICENSE b/cmd/webhook/LICENSE new file mode 100644 index 00000000000..d6456956733 --- /dev/null +++ b/cmd/webhook/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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. diff --git a/cmd/webhook/LICENSES b/cmd/webhook/LICENSES new file mode 100644 index 00000000000..2fc955bbc25 --- /dev/null +++ b/cmd/webhook/LICENSES @@ -0,0 +1,141 @@ +This LICENSES file is generated by the `licenses` module in makefile-modules[0]. + +The licenses below the "---" are determined by the go-licenses tool[1]. + +The aim of this file is to collect the licenses of all dependencies, and provide +a single source of truth for licenses used by this project. + +## For Developers + +If CI reports that this file is out of date, you should be careful to check that the +new licenses are acceptable for this project before running `make generate-go-licenses` +to update this file. + +Acceptable licenses are those allowlisted by the CNCF[2]. + +You MUST NOT add any new dependencies whose licenses are not allowlisted by the CNCF, +or which do not have an explicit license exception[3]. + +## For Users + +If this file was included in a release artifact, it is a snapshot of the licenses of all dependencies at the time of the release. + +You can retrieve the actual license text by following these steps: + +1. Find the dependency name in this file +2. Go to the source code repository of this project, and go to the tag corresponding to this release. +3. Find the exact version of the dependency in the `go.mod` file +4. Search for the dependency at the correct version in the [Go package index](https://pkg.go.dev/). + +## Links + +[0]: https://github.com/cert-manager/makefile-modules/ +[1]: https://github.com/google/go-licenses +[2]: https://github.com/cncf/foundation/blob/db4179134ebe7fa00b140a050c19147db808b6fa/policies-guidance/allowed-third-party-license-policy.md#cncf-allowlist-license-policy +[3]: https://github.com/cncf/foundation/blob/db4179134ebe7fa00b140a050c19147db808b6fa/license-exceptions/README.md + +--- + +cel.dev/expr,Apache-2.0 +github.com/Azure/go-ntlmssp,MIT +github.com/antlr4-go/antlr/v4,BSD-3-Clause +github.com/beorn7/perks/quantile,MIT +github.com/blang/semver/v4,MIT +github.com/cenkalti/backoff/v5,MIT +github.com/cert-manager/cert-manager,Apache-2.0 +github.com/cert-manager/cert-manager/webhook-binary,Apache-2.0 +github.com/cespare/xxhash/v2,MIT +github.com/davecgh/go-spew/spew,ISC +github.com/emicklei/go-restful/v3,MIT +github.com/evanphx/json-patch/v5,BSD-3-Clause +github.com/felixge/httpsnoop,MIT +github.com/fsnotify/fsnotify,BSD-3-Clause +github.com/fxamacker/cbor/v2,MIT +github.com/go-asn1-ber/asn1-ber,MIT +github.com/go-ldap/ldap/v3,MIT +github.com/go-logr/logr,Apache-2.0 +github.com/go-logr/stdr,Apache-2.0 +github.com/go-logr/zapr,Apache-2.0 +github.com/go-openapi/jsonpointer,Apache-2.0 +github.com/go-openapi/jsonreference,Apache-2.0 +github.com/go-openapi/swag,Apache-2.0 +github.com/go-openapi/swag/jsonname,Apache-2.0 +github.com/gogo/protobuf,BSD-3-Clause +github.com/google/btree,Apache-2.0 +github.com/google/cel-go,Apache-2.0 +github.com/google/cel-go,BSD-3-Clause +github.com/google/gnostic-models,Apache-2.0 +github.com/google/uuid,BSD-3-Clause +github.com/grpc-ecosystem/grpc-gateway/v2,BSD-3-Clause +github.com/josharian/intern,MIT +github.com/json-iterator/go,MIT +github.com/mailru/easyjson,MIT +github.com/modern-go/concurrent,Apache-2.0 +github.com/modern-go/reflect2,Apache-2.0 +github.com/munnerz/goautoneg,BSD-3-Clause +github.com/pmezard/go-difflib/difflib,BSD-3-Clause +github.com/prometheus/client_golang/internal/github.com/golang/gddo/httputil,BSD-3-Clause +github.com/prometheus/client_golang/prometheus,Apache-2.0 +github.com/prometheus/client_model/go,Apache-2.0 +github.com/prometheus/common,Apache-2.0 +github.com/prometheus/procfs,Apache-2.0 +github.com/spf13/cobra,Apache-2.0 +github.com/spf13/pflag,BSD-3-Clause +github.com/stoewer/go-strcase,MIT +github.com/x448/float16,MIT +go.opentelemetry.io/auto/sdk,Apache-2.0 +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp,Apache-2.0 +go.opentelemetry.io/otel,Apache-2.0 +go.opentelemetry.io/otel/exporters/otlp/otlptrace,Apache-2.0 +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc,Apache-2.0 +go.opentelemetry.io/otel/metric,Apache-2.0 +go.opentelemetry.io/otel/sdk,Apache-2.0 +go.opentelemetry.io/otel/trace,Apache-2.0 +go.opentelemetry.io/proto/otlp,Apache-2.0 +go.uber.org/multierr,MIT +go.uber.org/zap,MIT +go.yaml.in/yaml/v2,Apache-2.0 +go.yaml.in/yaml/v3,MIT +golang.org/x/crypto,BSD-3-Clause +golang.org/x/exp/slices,BSD-3-Clause +golang.org/x/net,BSD-3-Clause +golang.org/x/oauth2,BSD-3-Clause +golang.org/x/sync,BSD-3-Clause +golang.org/x/sys/unix,BSD-3-Clause +golang.org/x/term,BSD-3-Clause +golang.org/x/text,BSD-3-Clause +golang.org/x/time/rate,BSD-3-Clause +gomodules.xyz/jsonpatch/v2,Apache-2.0 +google.golang.org/genproto/googleapis/api,Apache-2.0 +google.golang.org/genproto/googleapis/rpc,Apache-2.0 +google.golang.org/grpc,Apache-2.0 +google.golang.org/protobuf,BSD-3-Clause +gopkg.in/evanphx/json-patch.v4,BSD-3-Clause +gopkg.in/inf.v0,BSD-3-Clause +gopkg.in/yaml.v3,MIT +k8s.io/api,Apache-2.0 +k8s.io/apiextensions-apiserver/pkg/apis/apiextensions,Apache-2.0 +k8s.io/apimachinery/pkg,Apache-2.0 +k8s.io/apimachinery/third_party/forked/golang,BSD-3-Clause +k8s.io/apiserver,Apache-2.0 +k8s.io/client-go,Apache-2.0 +k8s.io/component-base,Apache-2.0 +k8s.io/klog/v2,Apache-2.0 +k8s.io/kube-openapi/pkg,Apache-2.0 +k8s.io/kube-openapi/pkg/internal/third_party/go-json-experiment/json,BSD-3-Clause +k8s.io/kube-openapi/pkg/internal/third_party/govalidator,MIT +k8s.io/kube-openapi/pkg/validation/errors,Apache-2.0 +k8s.io/kube-openapi/pkg/validation/spec,Apache-2.0 +k8s.io/kube-openapi/pkg/validation/strfmt,Apache-2.0 +k8s.io/utils,Apache-2.0 +k8s.io/utils/internal/third_party/forked/golang,BSD-3-Clause +sigs.k8s.io/apiserver-network-proxy/konnectivity-client,Apache-2.0 +sigs.k8s.io/controller-runtime,Apache-2.0 +sigs.k8s.io/gateway-api/apis/v1,Apache-2.0 +sigs.k8s.io/json,Apache-2.0 +sigs.k8s.io/json,BSD-3-Clause +sigs.k8s.io/randfill,Apache-2.0 +sigs.k8s.io/structured-merge-diff/v6,Apache-2.0 +sigs.k8s.io/yaml,MIT +sigs.k8s.io/yaml,Apache-2.0 +sigs.k8s.io/yaml,BSD-3-Clause diff --git a/cmd/webhook/app/options/globalflags.go b/cmd/webhook/app/options/globalflags.go deleted file mode 100644 index 5498e3b01aa..00000000000 --- a/cmd/webhook/app/options/globalflags.go +++ /dev/null @@ -1,36 +0,0 @@ -/* -Copyright 2021 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. -*/ - -package options - -import ( - "flag" - "os" - - "github.com/spf13/pflag" - - "github.com/cert-manager/cert-manager/pkg/logs" -) - -func AddGlobalFlags(fs *pflag.FlagSet) { - addKlogFlags(fs) -} - -func addKlogFlags(fs *pflag.FlagSet) { - local := flag.NewFlagSet(os.Args[0], flag.ExitOnError) - logs.InitLogs(local) - fs.AddGoFlagSet(local) -} diff --git a/cmd/webhook/app/webhook.go b/cmd/webhook/app/webhook.go index 137c7337a2a..a229427b2e6 100644 --- a/cmd/webhook/app/webhook.go +++ b/cmd/webhook/app/webhook.go @@ -23,29 +23,47 @@ import ( "path/filepath" "github.com/spf13/cobra" - "github.com/spf13/pflag" - cliflag "k8s.io/component-base/cli/flag" - cmdutil "github.com/cert-manager/cert-manager/cmd/util" - "github.com/cert-manager/cert-manager/cmd/webhook/app/options" config "github.com/cert-manager/cert-manager/internal/apis/config/webhook" + "github.com/cert-manager/cert-manager/internal/apis/config/webhook/validation" cmwebhook "github.com/cert-manager/cert-manager/internal/webhook" logf "github.com/cert-manager/cert-manager/pkg/logs" "github.com/cert-manager/cert-manager/pkg/util" + "github.com/cert-manager/cert-manager/pkg/util/configfile" utilfeature "github.com/cert-manager/cert-manager/pkg/util/feature" - "github.com/cert-manager/cert-manager/pkg/webhook/configfile" + webhookconfigfile "github.com/cert-manager/cert-manager/pkg/webhook/configfile" + "github.com/cert-manager/cert-manager/pkg/webhook/options" ) const componentWebhook = "webhook" -func NewServerCommand(stopCh <-chan struct{}) *cobra.Command { - ctx := cmdutil.ContextWithStopCh(context.Background(), stopCh) - log := logf.Log - ctx = logf.NewContext(ctx, log, componentWebhook) +func NewServerCommand(ctx context.Context) *cobra.Command { + return newServerCommand( + ctx, + func(ctx context.Context, webhookConfig *config.WebhookConfiguration) error { + log := logf.FromContext(ctx, componentWebhook) + + versionInfo := util.VersionInfo() + log.Info("starting cert-manager webhook", "version", versionInfo.GitVersion, "git_commit", versionInfo.GitCommit, "go_version", versionInfo.GoVersion, "platform", versionInfo.Platform) + + srv, err := cmwebhook.NewCertManagerWebhookServer(log, *webhookConfig) + if err != nil { + return err + } + + return srv.Run(ctx) + }, + os.Args[1:], + ) +} + +func newServerCommand( + setupCtx context.Context, + run func(context.Context, *config.WebhookConfiguration) error, + allArgs []string, +) *cobra.Command { + log := logf.FromContext(setupCtx, componentWebhook) - cleanFlagSet := pflag.NewFlagSet(componentWebhook, pflag.ContinueOnError) - // Replaces all instances of `_` in flag names with `-` - cleanFlagSet.SetNormalizeFunc(cliflag.WordSepNormalizeFunc) webhookFlags := options.NewWebhookFlags() webhookConfig, err := options.NewWebhookConfiguration() if err != nil { @@ -54,156 +72,113 @@ func NewServerCommand(stopCh <-chan struct{}) *cobra.Command { } cmd := &cobra.Command{ - Use: componentWebhook, - Long: fmt.Sprintf("Webhook component providing API validation, mutation and conversion functionality for cert-manager (%s) (%s)", util.AppVersion, util.AppGitCommit), - // The webhook has special flag parsing requirements to handle precedence of providing - // configuration via versioned configuration files and flag values. - // Setting DisableFlagParsing=true prevents Cobra from interfering with flag parsing - // at all, and instead we handle it all in the RunE below. - DisableFlagParsing: true, - Run: func(cmd *cobra.Command, args []string) { - // initial flag parse, since we disable cobra's flag parsing - if err := cleanFlagSet.Parse(args); err != nil { - log.Error(err, "Failed to parse webhook flag") - cmd.Usage() - os.Exit(1) - } - - // check if there are non-flag arguments in the command line - cmds := cleanFlagSet.Args() - if len(cmds) > 0 { - log.Error(nil, "Unknown command", "command", cmds[0]) - cmd.Usage() - os.Exit(1) - } - - // short-circuit on help - help, err := cleanFlagSet.GetBool("help") - if err != nil { - log.Info(`"help" flag is non-bool, programmer error, please correct`) - os.Exit(1) - } - if help { - cmd.Help() - return + Use: componentWebhook, + Long: ` +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.`, + + SilenceErrors: true, // We already log errors in main.go + SilenceUsage: true, // Don't print usage on every error + + PreRunE: func(cmd *cobra.Command, args []string) error { + if err := loadConfigFromFile( + cmd, allArgs, webhookFlags.Config, webhookConfig, + func() error { + // set feature gates from initial flags-based config + if err := utilfeature.DefaultMutableFeatureGate.SetFromMap(webhookConfig.FeatureGates); err != nil { + return fmt.Errorf("failed to set feature gates from initial flags-based config: %w", err) + } + + return nil + }, + ); err != nil { + return err } - // set feature gates from initial flags-based config - if err := utilfeature.DefaultMutableFeatureGate.SetFromMap(webhookConfig.FeatureGates); err != nil { - log.Error(err, "Failed to set feature gates from initial flags-based config") - os.Exit(1) + if err := validation.ValidateWebhookConfiguration(webhookConfig, nil); len(err) > 0 { + return fmt.Errorf("error validating flags: %w", err.ToAggregate()) } - if err := options.ValidateWebhookFlags(webhookFlags); err != nil { - log.Error(err, "Failed to validate webhook flags") - os.Exit(1) + // ValidateWebhookConfiguration should already have validated the + // logging flags, the logging API does not have an Apply-only function + // so we validate again here. This should not catch any validation errors + // anymore. + if err := logf.ValidateAndApply(&webhookConfig.Logging); err != nil { + return fmt.Errorf("failed to validate webhook logging flags: %w", err) } - if configFile := webhookFlags.Config; len(configFile) > 0 { - webhookConfig, err = loadConfigFile(configFile) - if err != nil { - log.Error(err, "Failed to load webhook config file", "path", configFile) - os.Exit(1) - } - - if err := webhookConfigFlagPrecedence(webhookConfig, args); err != nil { - log.Error(err, "Failed to merge flags with config file values") - os.Exit(1) - } - // update feature gates based on new config - if err := utilfeature.DefaultMutableFeatureGate.SetFromMap(webhookConfig.FeatureGates); err != nil { - log.Error(err, "Failed to set feature gates from config file") - os.Exit(1) - } - } - - srv, err := cmwebhook.NewCertManagerWebhookServer(log, *webhookFlags, *webhookConfig) - if err != nil { - log.Error(err, "Failed initialising server") - os.Exit(1) - } - - if err := srv.Run(ctx); err != nil { - log.Error(err, "Failed running server") - os.Exit(1) - } + return nil + }, + // nolint:contextcheck // False positive + RunE: func(cmd *cobra.Command, args []string) error { + return run(cmd.Context(), webhookConfig) }, } - webhookFlags.AddFlags(cleanFlagSet) - options.AddConfigFlags(cleanFlagSet, webhookConfig) - options.AddGlobalFlags(cleanFlagSet) + webhookFlags.AddFlags(cmd.Flags()) + options.AddConfigFlags(cmd.Flags(), webhookConfig) - cleanFlagSet.BoolP("help", "h", false, fmt.Sprintf("help for %s", cmd.Name())) - - // ugly, but necessary, because Cobra's default UsageFunc and HelpFunc pollute the flagset with global flags - const usageFmt = "Usage:\n %s\n\nFlags:\n%s" - cmd.SetUsageFunc(func(cmd *cobra.Command) error { - fmt.Fprintf(cmd.OutOrStderr(), usageFmt, cmd.UseLine(), cleanFlagSet.FlagUsagesWrapped(2)) - return nil - }) - cmd.SetHelpFunc(func(cmd *cobra.Command, args []string) { - fmt.Fprintf(cmd.OutOrStdout(), "%s\n\n"+usageFmt, cmd.Long, cmd.UseLine(), cleanFlagSet.FlagUsagesWrapped(2)) - }) + // explicitly set provided args in case it does not equal os.Args[:1], + // e.g., when running tests + cmd.SetArgs(allArgs) return cmd } -// newFlagSetWithGlobals constructs a new pflag.FlagSet with global flags registered -// on it. -func newFlagSetWithGlobals() *pflag.FlagSet { - fs := pflag.NewFlagSet("", pflag.ExitOnError) - // set the normalize func, similar to k8s.io/component-base/cli//flags.go:InitFlags - fs.SetNormalizeFunc(cliflag.WordSepNormalizeFunc) - // explicitly add flags from libs that register global flags - options.AddGlobalFlags(fs) - return fs -} - -// newFakeFlagSet constructs a pflag.FlagSet with the same flags as fs, but where -// all values have noop Set implementations -func newFakeFlagSet(fs *pflag.FlagSet) *pflag.FlagSet { - ret := pflag.NewFlagSet("", pflag.ExitOnError) - ret.SetNormalizeFunc(fs.GetNormalizeFunc()) - fs.VisitAll(func(f *pflag.Flag) { - ret.VarP(cliflag.NoOp{}, f.Name, f.Shorthand, f.Usage) - }) - return ret -} - -// webhookConfigFlagPrecedence re-parses flags over the WebhookConfiguration object. -// We must enforce flag precedence by re-parsing the command line into the new object. -// This is necessary to preserve backwards-compatibility across binary upgrades. -// See issue #56171 for more details. -func webhookConfigFlagPrecedence(cfg *config.WebhookConfiguration, args []string) error { - // We use a throwaway webhookFlags and a fake global flagset to avoid double-parses, - // as some Set implementations accumulate values from multiple flag invocations. - fs := newFakeFlagSet(newFlagSetWithGlobals()) - // register throwaway KubeletFlags - options.NewWebhookFlags().AddFlags(fs) - // register new WebhookConfiguration - options.AddConfigFlags(fs, cfg) - // re-parse flags - if err := fs.Parse(args); err != nil { +// loadConfigFromFile loads the configuration from the provided config file +// path, if one is provided. After loading the config file, the flags are +// re-parsed to ensure that any flags provided to the command line override +// those provided in the config file. +// The newConfigHook is called when the options have been loaded from the +// flags (but not yet the config file) and is re-called after the config file +// has been loaded. This allows us to use the feature flags set by the flags +// while loading the config file. +func loadConfigFromFile( + cmd *cobra.Command, + allArgs []string, + configFilePath string, + cfg *config.WebhookConfiguration, + newConfigHook func() error, +) error { + if err := newConfigHook(); err != nil { return err } - return nil -} -func loadConfigFile(name string) (*config.WebhookConfiguration, error) { - const errFmt = "failed to load webhook config file %s, error %v" - // compute absolute path based on current working dir - webhookConfigFile, err := filepath.Abs(name) - if err != nil { - return nil, fmt.Errorf(errFmt, name, err) - } - loader, err := configfile.NewFSLoader(configfile.NewRealFS(), webhookConfigFile) - if err != nil { - return nil, fmt.Errorf(errFmt, name, err) + if len(configFilePath) > 0 { + // compute absolute path based on current working dir + webhookConfigFile, err := filepath.Abs(configFilePath) + if err != nil { + return fmt.Errorf("failed to load config file %s, error %v", configFilePath, err) + } + + loader, err := configfile.NewConfigurationFSLoader(nil, webhookConfigFile) + if err != nil { + return fmt.Errorf("failed to load config file %s, error %v", configFilePath, err) + } + + webhookConfigFromFile := webhookconfigfile.New() + if err := loader.Load(webhookConfigFromFile); err != nil { + return fmt.Errorf("failed to load config file %s, error %v", configFilePath, err) + } + + webhookConfigFromFile.Config.DeepCopyInto(cfg) + + _, args, err := cmd.Root().Find(allArgs) + if err != nil { + return fmt.Errorf("failed to re-parse flags: %w", err) + } + + if err := cmd.ParseFlags(args); err != nil { + return fmt.Errorf("failed to re-parse flags: %w", err) + } + + if err := newConfigHook(); err != nil { + return err + } } - cfg, err := loader.Load() - if err != nil { - return nil, fmt.Errorf(errFmt, name, err) - } - return cfg, nil + + return nil } diff --git a/cmd/webhook/app/webhook_test.go b/cmd/webhook/app/webhook_test.go index 36c30d4c62a..d9612b5bcc2 100644 --- a/cmd/webhook/app/webhook_test.go +++ b/cmd/webhook/app/webhook_test.go @@ -17,41 +17,186 @@ limitations under the License. package app import ( + "context" + "fmt" + "io" + "os" + "path" + "reflect" "testing" - "github.com/cert-manager/cert-manager/cmd/webhook/app/options" + logsapi "k8s.io/component-base/logs/api/v1" + + config "github.com/cert-manager/cert-manager/internal/apis/config/webhook" + "github.com/cert-manager/cert-manager/pkg/webhook/options" ) -// Test to ensure flags take precedence over config options. -func TestWebhookConfigFlagPrecedence_FlagsTakePrecedence(t *testing.T) { - cfg, err := options.NewWebhookConfiguration() - if err != nil { - t.Fatal(err) - } +func testCmdCommand(t *testing.T, tempDir string, yaml string, args func(string) []string) (*config.WebhookConfiguration, error) { + var tempFilePath string - cfg.KubeConfig = "" - if err := webhookConfigFlagPrecedence(cfg, []string{"--kubeconfig=valid"}); err != nil { - t.Fatal(err) - } + func() { + tempFile, err := os.CreateTemp(tempDir, "config-*.yaml") + if err != nil { + t.Error(err) + } + defer tempFile.Close() + + tempFilePath = tempFile.Name() + + if _, err := tempFile.WriteString(yaml); err != nil { + t.Error(err) + } + }() + + var finalConfig *config.WebhookConfiguration - if cfg.KubeConfig != "valid" { - t.Errorf("unexpected field value %q, expected %q", cfg.KubeConfig, "valid") + if err := logsapi.ResetForTest(nil); err != nil { + t.Error(err) } + + cmd := newServerCommand(t.Context(), func(ctx context.Context, cc *config.WebhookConfiguration) error { + finalConfig = cc + return nil + }, args(tempFilePath)) + + cmd.SetErr(io.Discard) + cmd.SetOut(io.Discard) + + err := cmd.ExecuteContext(t.Context()) + return finalConfig, err } -// Test to ensure that when flags are not provided, config provided values are preserved. -func TestWebhookConfigFlagPrecedence_ConfigPersistsWithoutFlags(t *testing.T) { - cfg, err := options.NewWebhookConfiguration() - if err != nil { - t.Fatal(err) +func TestFlagsAndConfigFile(t *testing.T) { + type testCase struct { + yaml string + args func(string) []string + expError bool + expConfig func(string) *config.WebhookConfiguration } - cfg.KubeConfig = "valid" - if err := webhookConfigFlagPrecedence(cfg, []string{}); err != nil { - t.Fatal(err) + configFromDefaults := func( + fn func(string, *config.WebhookConfiguration), + ) func(string) *config.WebhookConfiguration { + defaults, err := options.NewWebhookConfiguration() + if err != nil { + t.Error(err) + } + return func(tempDir string) *config.WebhookConfiguration { + fn(tempDir, defaults) + return defaults + } } - if cfg.KubeConfig != "valid" { - t.Errorf("unexpected field value %q, expected %q", cfg.KubeConfig, "valid") + tests := []testCase{ + { + yaml: ``, + args: func(tempFilePath string) []string { + return []string{"--kubeconfig=valid"} + }, + expConfig: configFromDefaults(func(tempDir string, cc *config.WebhookConfiguration) { + cc.KubeConfig = "valid" + }), + }, + { + yaml: ` +apiVersion: webhook.config.cert-manager.io/v1alpha1 +kind: WebhookConfiguration +kubeConfig: "" +`, + args: func(tempFilePath string) []string { + return []string{"--config=" + tempFilePath, "--kubeconfig=valid"} + }, + expConfig: configFromDefaults(func(tempDir string, cc *config.WebhookConfiguration) { + cc.KubeConfig = "valid" + }), + }, + { + yaml: ` +apiVersion: webhook.config.cert-manager.io/v1alpha1 +kind: WebhookConfiguration +kubeConfig: valid +`, + args: func(tempFilePath string) []string { + return []string{"--config=" + tempFilePath} + }, + expConfig: configFromDefaults(func(tempDir string, cc *config.WebhookConfiguration) { + cc.KubeConfig = path.Join(tempDir, "valid") + }), + }, + { + yaml: ` +apiVersion: webhook.config.cert-manager.io/v1alpha1 +kind: WebhookConfiguration +tlsConfig: {} +`, + args: func(tempFilePath string) []string { + return []string{"--config=" + tempFilePath} + }, + expConfig: configFromDefaults(func(tempDir string, cc *config.WebhookConfiguration) { + }), + }, + { + yaml: ` +apiVersion: webhook.config.cert-manager.io/v1alpha1 +kind: WebhookConfiguration +tlsConfig: nil +`, + args: func(tempFilePath string) []string { + return []string{"--config=" + tempFilePath} + }, + expError: true, + }, + { + yaml: ` +apiVersion: webhook.config.cert-manager.io/v1alpha1 +kind: WebhookConfiguration +tlsConfig: + filesystem: + certFile: aaaa +`, + args: func(tempFilePath string) []string { + return []string{"--config=" + tempFilePath, "--tls-private-key-file=bbbb"} + }, + expConfig: configFromDefaults(func(tempDir string, cc *config.WebhookConfiguration) { + cc.TLSConfig.Filesystem.CertFile = path.Join(tempDir, "aaaa") + cc.TLSConfig.Filesystem.KeyFile = "bbbb" + }), + }, + { + yaml: ` +apiVersion: webhook.config.cert-manager.io/v1alpha1 +kind: WebhookConfiguration +logging: + verbosity: 2 + format: text +`, + args: func(tempFilePath string) []string { + return []string{"--config=" + tempFilePath} + }, + expConfig: configFromDefaults(func(tempDir string, cc *config.WebhookConfiguration) { + cc.Logging.Verbosity = 2 + cc.Logging.Format = "text" + }), + }, + } + + for i, tc := range tests { + t.Run(fmt.Sprintf("test-%d", i), func(t *testing.T) { + tempDir := t.TempDir() + + config, err := testCmdCommand(t, tempDir, tc.yaml, tc.args) + if tc.expError != (err != nil) { + if err == nil { + t.Error("expected error, got nil") + } else { + t.Errorf("unexpected error: %v", err) + } + } else if !tc.expError { + expConfig := tc.expConfig(tempDir) + if !reflect.DeepEqual(config, expConfig) { + t.Errorf("expected config %v but got %v", expConfig, config) + } + } + }) } } diff --git a/cmd/webhook/go.mod b/cmd/webhook/go.mod new file mode 100644 index 00000000000..2df844976aa --- /dev/null +++ b/cmd/webhook/go.mod @@ -0,0 +1,107 @@ +module github.com/cert-manager/cert-manager/webhook-binary + +go 1.25.0 + +// Do not remove this comment: +// please place any replace statements here at the top for visibility and add a +// comment to it as to when it can be removed + +replace github.com/cert-manager/cert-manager => ../../ + +require ( + github.com/cert-manager/cert-manager v0.0.0-00010101000000-000000000000 + github.com/spf13/cobra v1.10.1 + k8s.io/component-base v0.34.1 + sigs.k8s.io/controller-runtime v0.22.3 +) + +require ( + cel.dev/expr v0.24.0 // indirect + github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 // indirect + github.com/antlr4-go/antlr/v4 v4.13.1 // indirect + github.com/beorn7/perks v1.0.1 // indirect + github.com/blang/semver/v4 v4.0.0 // indirect + github.com/cenkalti/backoff/v5 v5.0.3 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/emicklei/go-restful/v3 v3.13.0 // indirect + github.com/evanphx/json-patch/v5 v5.9.11 // indirect + github.com/felixge/httpsnoop v1.0.4 // indirect + github.com/fsnotify/fsnotify v1.9.0 // indirect + github.com/fxamacker/cbor/v2 v2.9.0 // indirect + github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667 // indirect + github.com/go-ldap/ldap/v3 v3.4.12 // indirect + github.com/go-logr/logr v1.4.3 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + github.com/go-logr/zapr v1.3.0 // indirect + github.com/go-openapi/jsonpointer v0.22.1 // indirect + github.com/go-openapi/jsonreference v0.21.2 // indirect + github.com/go-openapi/swag v0.23.1 // indirect + github.com/go-openapi/swag/jsonname v0.25.1 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/google/btree v1.1.3 // indirect + github.com/google/cel-go v0.26.0 // indirect + github.com/google/gnostic-models v0.7.0 // indirect + github.com/google/go-cmp v0.7.0 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1 // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/josharian/intern v1.0.0 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/mailru/easyjson v0.9.0 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + github.com/prometheus/client_golang v1.23.2 // indirect + github.com/prometheus/client_model v0.6.2 // indirect + github.com/prometheus/common v0.66.1 // indirect + github.com/prometheus/procfs v0.17.0 // indirect + github.com/spf13/pflag v1.0.10 // indirect + github.com/stoewer/go-strcase v1.3.1 // indirect + github.com/x448/float16 v0.8.4 // indirect + go.opentelemetry.io/auto/sdk v1.1.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 // indirect + go.opentelemetry.io/otel v1.37.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.37.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.37.0 // indirect + go.opentelemetry.io/otel/metric v1.37.0 // indirect + go.opentelemetry.io/otel/sdk v1.37.0 // indirect + go.opentelemetry.io/otel/trace v1.37.0 // indirect + go.opentelemetry.io/proto/otlp v1.7.0 // indirect + go.uber.org/multierr v1.11.0 // indirect + go.uber.org/zap v1.27.0 // indirect + go.yaml.in/yaml/v2 v2.4.2 // indirect + go.yaml.in/yaml/v3 v3.0.4 // indirect + golang.org/x/crypto v0.43.0 // indirect + golang.org/x/exp v0.0.0-20250718183923-645b1fa84792 // indirect + golang.org/x/net v0.46.0 // indirect + golang.org/x/oauth2 v0.32.0 // indirect + golang.org/x/sync v0.17.0 // indirect + golang.org/x/sys v0.37.0 // indirect + golang.org/x/term v0.36.0 // indirect + golang.org/x/text v0.30.0 // indirect + golang.org/x/time v0.14.0 // indirect + gomodules.xyz/jsonpatch/v2 v2.5.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20250804133106-a7a43d27e69b // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20251014184007-4626949a642f // indirect + google.golang.org/grpc v1.76.0 // indirect + google.golang.org/protobuf v1.36.10 // indirect + gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect + gopkg.in/inf.v0 v0.9.1 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect + k8s.io/api v0.34.1 // indirect + k8s.io/apiextensions-apiserver v0.34.1 // indirect + k8s.io/apimachinery v0.34.1 // indirect + k8s.io/apiserver v0.34.1 // indirect + k8s.io/client-go v0.34.1 // indirect + k8s.io/klog/v2 v2.130.1 // indirect + k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 // indirect + k8s.io/utils v0.0.0-20250820121507-0af2bda4dd1d // indirect + sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.33.0 // indirect + sigs.k8s.io/gateway-api v1.4.0 // indirect + sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect + sigs.k8s.io/randfill v1.0.0 // indirect + sigs.k8s.io/structured-merge-diff/v6 v6.3.0 // indirect + sigs.k8s.io/yaml v1.6.0 // indirect +) diff --git a/cmd/webhook/go.sum b/cmd/webhook/go.sum new file mode 100644 index 00000000000..1a18bcde361 --- /dev/null +++ b/cmd/webhook/go.sum @@ -0,0 +1,288 @@ +cel.dev/expr v0.24.0 h1:56OvJKSH3hDGL0ml5uSxZmz3/3Pq4tJ+fb1unVLAFcY= +cel.dev/expr v0.24.0/go.mod h1:hLPLo1W4QUmuYdA72RBX06QTs6MXw941piREPl3Yfiw= +github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 h1:He8afgbRMd7mFxO99hRNu+6tazq8nFF9lIwo9JFroBk= +github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8= +github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 h1:mFRzDkZVAjdal+s7s0MwaRv9igoPqLRdzOLzw/8Xvq8= +github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU= +github.com/alexbrainman/sspi v0.0.0-20250919150558-7d374ff0d59e h1:4dAU9FXIyQktpoUAgOJK3OTFc/xug0PCXYCqU0FgDKI= +github.com/alexbrainman/sspi v0.0.0-20250919150558-7d374ff0d59e/go.mod h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4= +github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYWrPrQ= +github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw= +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= +github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= +github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM= +github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/emicklei/go-restful/v3 v3.13.0 h1:C4Bl2xDndpU6nJ4bc1jXd+uTmYPVUwkD6bFY/oTyCes= +github.com/emicklei/go-restful/v3 v3.13.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/evanphx/json-patch v5.9.0+incompatible h1:fBXyNpNMuTTDdquAq/uisOr2lShz4oaXpDTX2bLe7ls= +github.com/evanphx/json-patch v5.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/evanphx/json-patch/v5 v5.9.11 h1:/8HVnzMq13/3x9TPvjG08wUGqBTmZBsCWzjTM0wiaDU= +github.com/evanphx/json-patch/v5 v5.9.11/go.mod h1:3j+LviiESTElxA4p3EMKAB9HXj3/XEtnUf6OZxqIQTM= +github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= +github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= +github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= +github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM= +github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= +github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667 h1:BP4M0CvQ4S3TGls2FvczZtj5Re/2ZzkV9VwqPHH/3Bo= +github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= +github.com/go-ldap/ldap/v3 v3.4.12 h1:1b81mv7MagXZ7+1r7cLTWmyuTqVqdwbtJSjC0DAp9s4= +github.com/go-ldap/ldap/v3 v3.4.12/go.mod h1:+SPAGcTtOfmGsCb3h1RFiq4xpp4N636G75OEace8lNo= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= +github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ= +github.com/go-logr/zapr v1.3.0/go.mod h1:YKepepNBd1u/oyhd/yQmtjVXmm9uML4IXUgMOwR8/Gg= +github.com/go-openapi/jsonpointer v0.22.1 h1:sHYI1He3b9NqJ4wXLoJDKmUmHkWy/L7rtEo92JUxBNk= +github.com/go-openapi/jsonpointer v0.22.1/go.mod h1:pQT9OsLkfz1yWoMgYFy4x3U5GY5nUlsOn1qSBH5MkCM= +github.com/go-openapi/jsonreference v0.21.2 h1:Wxjda4M/BBQllegefXrY/9aq1fxBA8sI5M/lFU6tSWU= +github.com/go-openapi/jsonreference v0.21.2/go.mod h1:pp3PEjIsJ9CZDGCNOyXIQxsNuroxm8FAJ/+quA0yKzQ= +github.com/go-openapi/swag v0.23.1 h1:lpsStH0n2ittzTnbaSloVZLuB5+fvSY/+hnagBjSNZU= +github.com/go-openapi/swag v0.23.1/go.mod h1:STZs8TbRvEQQKUA+JZNAm3EWlgaOBGpyFDqQnDHMef0= +github.com/go-openapi/swag/jsonname v0.25.1 h1:Sgx+qbwa4ej6AomWC6pEfXrA6uP2RkaNjA9BR8a1RJU= +github.com/go-openapi/swag/jsonname v0.25.1/go.mod h1:71Tekow6UOLBD3wS7XhdT98g5J5GR13NOTQ9/6Q11Zo= +github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= +github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg= +github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= +github.com/google/cel-go v0.26.0 h1:DPGjXackMpJWH680oGY4lZhYjIameYmR+/6RBdDGmaI= +github.com/google/cel-go v0.26.0/go.mod h1:A9O8OU9rdvrK5MQyrqfIxo1a0u4g3sF8KB6PUIaryMM= +github.com/google/gnostic-models v0.7.0 h1:qwTtogB15McXDaNqTZdzPJRHvaVJlAl+HVQnLmJEJxo= +github.com/google/gnostic-models v0.7.0/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= +github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db h1:097atOisP2aRj7vFgYQBbFN4U4JNXUNYpxael3UzMyo= +github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1 h1:X5VWvz21y3gzm9Nw/kaUeku/1+uBhcekkmy4IkffJww= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1/go.mod h1:Zanoh4+gvIgluNqcfMVTJueD4wSS5hT7zTt4Mrutd90= +github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= +github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/jcmturner/aescts/v2 v2.0.0 h1:9YKLH6ey7H4eDBXW8khjYslgyqG2xZikXP0EQFKrle8= +github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs= +github.com/jcmturner/dnsutils/v2 v2.0.0 h1:lltnkeZGL0wILNvrNiVCR6Ro5PGU/SeBvVO/8c/iPbo= +github.com/jcmturner/dnsutils/v2 v2.0.0/go.mod h1:b0TnjGOvI/n42bZa+hmXL+kFJZsFT7G4t3HTlQ184QM= +github.com/jcmturner/gofork v1.7.6 h1:QH0l3hzAU1tfT3rZCnW5zXl+orbkNMMRGJfdJjHVETg= +github.com/jcmturner/gofork v1.7.6/go.mod h1:1622LH6i/EZqLloHfE7IeZ0uEJwMSUyQ/nDd82IeqRo= +github.com/jcmturner/goidentity/v6 v6.0.1 h1:VKnZd2oEIMorCTsFBnJWbExfNN7yZr3EhJAxwOkZg6o= +github.com/jcmturner/goidentity/v6 v6.0.1/go.mod h1:X1YW3bgtvwAXju7V3LCIMpY0Gbxyjn/mY9zx4tFonSg= +github.com/jcmturner/gokrb5/v8 v8.4.4 h1:x1Sv4HaTpepFkXbt2IkL29DXRf8sOfZXo8eRKh687T8= +github.com/jcmturner/gokrb5/v8 v8.4.4/go.mod h1:1btQEpgT6k+unzCwX1KdWMEwPPkkgBtP+F6aCACiMrs= +github.com/jcmturner/rpc/v2 v2.0.3 h1:7FXXj8Ti1IaVFpSAziCZWNzbNuZmnvw/i6CqLNdWfZY= +github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= +github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4= +github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8= +github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/onsi/ginkgo/v2 v2.22.0 h1:Yed107/8DjTr0lKCNt7Dn8yQ6ybuDRQoMGrNFKzMfHg= +github.com/onsi/ginkgo/v2 v2.22.0/go.mod h1:7Du3c42kxCUegi0IImZ1wUQzMBVecgIHjR1C+NkhLQo= +github.com/onsi/gomega v1.36.1 h1:bJDPBO7ibjxcbHMgSCoo4Yj18UWbKDlLwX1x9sybDcw= +github.com/onsi/gomega v1.36.1/go.mod h1:PvZbdDc8J6XJEpDK4HCuRBm8a6Fzp9/DmhC9C7yFlog= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o= +github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg= +github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= +github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= +github.com/prometheus/common v0.66.1 h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9ZoGs= +github.com/prometheus/common v0.66.1/go.mod h1:gcaUsgf3KfRSwHY4dIMXLPV0K/Wg1oZ8+SbZk/HH/dA= +github.com/prometheus/procfs v0.17.0 h1:FuLQ+05u4ZI+SS/w9+BWEM2TXiHKsUQ9TADiRH7DuK0= +github.com/prometheus/procfs v0.17.0/go.mod h1:oPQLaDAMRbA+u8H5Pbfq+dl3VDAvHxMUOVhe0wYB2zw= +github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= +github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/spf13/cobra v1.10.1 h1:lJeBwCfmrnXthfAupyUTzJ/J4Nc1RsHC/mSRU2dll/s= +github.com/spf13/cobra v1.10.1/go.mod h1:7SmJGaTHFVBY0jW4NXGluQoLvhqFQM+6XSKD+P4XaB0= +github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= +github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/stoewer/go-strcase v1.3.1 h1:iS0MdW+kVTxgMoE1LAZyMiYJFKlOzLooE4MxjirtkAs= +github.com/stoewer/go-strcase v1.3.1/go.mod h1:fAH5hQ5pehh+j3nZfvwdk2RgEgQjAoM8wodgtPmh1xo= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= +github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= +go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 h1:F7Jx+6hwnZ41NSFTO5q4LYDtJRXBf2PD0rNBkeB/lus= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0/go.mod h1:UHB22Z8QsdRDrnAtX4PntOl36ajSxcdUMt1sF7Y6E7Q= +go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ= +go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.37.0 h1:Ahq7pZmv87yiyn3jeFz/LekZmPLLdKejuO3NcK9MssM= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.37.0/go.mod h1:MJTqhM0im3mRLw1i8uGHnCvUEeS7VwRyxlLC78PA18M= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.37.0 h1:EtFWSnwW9hGObjkIdmlnWSydO+Qs8OwzfzXLUPg4xOc= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.37.0/go.mod h1:QjUEoiGCPkvFZ/MjK6ZZfNOS6mfVEVKYE99dFhuN2LI= +go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE= +go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E= +go.opentelemetry.io/otel/sdk v1.37.0 h1:ItB0QUqnjesGRvNcmAcU0LyvkVyGJ2xftD29bWdDvKI= +go.opentelemetry.io/otel/sdk v1.37.0/go.mod h1:VredYzxUvuo2q3WRcDnKDjbdvmO0sCzOvVAiY+yUkAg= +go.opentelemetry.io/otel/sdk/metric v1.37.0 h1:90lI228XrB9jCMuSdA0673aubgRobVZFhbjxHHspCPc= +go.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps= +go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4= +go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0= +go.opentelemetry.io/proto/otlp v1.7.0 h1:jX1VolD6nHuFzOYso2E73H85i92Mv8JQYk0K9vz09os= +go.opentelemetry.io/proto/otlp v1.7.0/go.mod h1:fSKjH6YJ7HDlwzltzyMj036AJ3ejJLCgCSHGj4efDDo= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= +go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= +go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI= +go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU= +go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= +go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04= +golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0= +golang.org/x/exp v0.0.0-20250718183923-645b1fa84792 h1:R9PFI6EUdfVKgwKjZef7QIwGcBKu86OEFpJ9nUEP2l4= +golang.org/x/exp v0.0.0-20250718183923-645b1fa84792/go.mod h1:A+z0yzpGtvnG90cToK5n2tu8UJVP2XUATh+r+sfOOOc= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.46.0 h1:giFlY12I07fugqwPuWJi68oOnpfqFnJIJzaIIm2JVV4= +golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210= +golang.org/x/oauth2 v0.32.0 h1:jsCblLleRMDrxMN29H3z/k1KliIvpLgCkE6R8FXXNgY= +golang.org/x/oauth2 v0.32.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug= +golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ= +golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/term v0.36.0 h1:zMPR+aF8gfksFprF/Nc/rd1wRS1EI6nDBGyWAvDzx2Q= +golang.org/x/term v0.36.0/go.mod h1:Qu394IJq6V6dCBRgwqshf3mPF85AqzYEzofzRdZkWss= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k= +golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM= +golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI= +golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.37.0 h1:DVSRzp7FwePZW356yEAChSdNcQo6Nsp+fex1SUW09lE= +golang.org/x/tools v0.37.0/go.mod h1:MBN5QPQtLMHVdvsbtarmTNukZDdgwdwlO5qGacAzF0w= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gomodules.xyz/jsonpatch/v2 v2.5.0 h1:JELs8RLM12qJGXU4u/TO3V25KW8GreMKl9pdkk14RM0= +gomodules.xyz/jsonpatch/v2 v2.5.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY= +gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= +gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= +google.golang.org/genproto/googleapis/api v0.0.0-20250804133106-a7a43d27e69b h1:ULiyYQ0FdsJhwwZUwbaXpZF5yUE3h+RA+gxvBu37ucc= +google.golang.org/genproto/googleapis/api v0.0.0-20250804133106-a7a43d27e69b/go.mod h1:oDOGiMSXHL4sDTJvFvIB9nRQCGdLP1o/iVaqQK8zB+M= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251014184007-4626949a642f h1:1FTH6cpXFsENbPR5Bu8NQddPSaUUE6NA2XdZdDSAJK4= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251014184007-4626949a642f/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= +google.golang.org/grpc v1.76.0 h1:UnVkv1+uMLYXoIz6o7chp59WfQUYA2ex/BXQ9rHZu7A= +google.golang.org/grpc v1.76.0/go.mod h1:Ju12QI8M6iQJtbcsV+awF5a4hfJMLi4X0JLo94ULZ6c= +google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= +google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/evanphx/json-patch.v4 v4.13.0 h1:czT3CmqEaQ1aanPc5SdlgQrrEIb8w/wwCvWWnfEbYzo= +gopkg.in/evanphx/json-patch.v4 v4.13.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= +gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= +gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +k8s.io/api v0.34.1 h1:jC+153630BMdlFukegoEL8E/yT7aLyQkIVuwhmwDgJM= +k8s.io/api v0.34.1/go.mod h1:SB80FxFtXn5/gwzCoN6QCtPD7Vbu5w2n1S0J5gFfTYk= +k8s.io/apiextensions-apiserver v0.34.1 h1:NNPBva8FNAPt1iSVwIE0FsdrVriRXMsaWFMqJbII2CI= +k8s.io/apiextensions-apiserver v0.34.1/go.mod h1:hP9Rld3zF5Ay2Of3BeEpLAToP+l4s5UlxiHfqRaRcMc= +k8s.io/apimachinery v0.34.1 h1:dTlxFls/eikpJxmAC7MVE8oOeP1zryV7iRyIjB0gky4= +k8s.io/apimachinery v0.34.1/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw= +k8s.io/apiserver v0.34.1 h1:U3JBGdgANK3dfFcyknWde1G6X1F4bg7PXuvlqt8lITA= +k8s.io/apiserver v0.34.1/go.mod h1:eOOc9nrVqlBI1AFCvVzsob0OxtPZUCPiUJL45JOTBG0= +k8s.io/client-go v0.34.1 h1:ZUPJKgXsnKwVwmKKdPfw4tB58+7/Ik3CrjOEhsiZ7mY= +k8s.io/client-go v0.34.1/go.mod h1:kA8v0FP+tk6sZA0yKLRG67LWjqufAoSHA2xVGKw9Of8= +k8s.io/component-base v0.34.1 h1:v7xFgG+ONhytZNFpIz5/kecwD+sUhVE6HU7qQUiRM4A= +k8s.io/component-base v0.34.1/go.mod h1:mknCpLlTSKHzAQJJnnHVKqjxR7gBeHRv0rPXA7gdtQ0= +k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= +k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= +k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 h1:Y3gxNAuB0OBLImH611+UDZcmKS3g6CthxToOb37KgwE= +k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912/go.mod h1:kdmbQkyfwUagLfXIad1y2TdrjPFWp2Q89B3qkRwf/pQ= +k8s.io/utils v0.0.0-20250820121507-0af2bda4dd1d h1:wAhiDyZ4Tdtt7e46e9M5ZSAJ/MnPGPs+Ki1gHw4w1R0= +k8s.io/utils v0.0.0-20250820121507-0af2bda4dd1d/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.33.0 h1:qPrZsv1cwQiFeieFlRqT627fVZ+tyfou/+S5S0H5ua0= +sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.33.0/go.mod h1:Ve9uj1L+deCXFrPOk1LpFXqTg7LCFzFso6PA48q/XZw= +sigs.k8s.io/controller-runtime v0.22.3 h1:I7mfqz/a/WdmDCEnXmSPm8/b/yRTy6JsKKENTijTq8Y= +sigs.k8s.io/controller-runtime v0.22.3/go.mod h1:+QX1XUpTXN4mLoblf4tqr5CQcyHPAki2HLXqQMY6vh8= +sigs.k8s.io/gateway-api v1.4.0 h1:ZwlNM6zOHq0h3WUX2gfByPs2yAEsy/EenYJB78jpQfQ= +sigs.k8s.io/gateway-api v1.4.0/go.mod h1:AR5RSqciWP98OPckEjOjh2XJhAe2Na4LHyXD2FUY7Qk= +sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg= +sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= +sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= +sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= +sigs.k8s.io/structured-merge-diff/v6 v6.3.0 h1:jTijUJbW353oVOd9oTlifJqOGEkUw2jB/fXCbTiQEco= +sigs.k8s.io/structured-merge-diff/v6 v6.3.0/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE= +sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs= +sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4= diff --git a/cmd/webhook/main.go b/cmd/webhook/main.go index 95577d03735..c46e92a5a37 100644 --- a/cmd/webhook/main.go +++ b/cmd/webhook/main.go @@ -17,25 +17,27 @@ limitations under the License. package main import ( - "flag" + "context" - "github.com/cert-manager/cert-manager/cmd/util" - "github.com/cert-manager/cert-manager/cmd/webhook/app" + ctrl "sigs.k8s.io/controller-runtime" + + "github.com/cert-manager/cert-manager/internal/cmd/util" logf "github.com/cert-manager/cert-manager/pkg/logs" + "github.com/cert-manager/cert-manager/webhook-binary/app" ) func main() { - stopCh, exit := util.SetupExitHandler(util.GracefulShutdown) + ctx, exit := util.SetupExitHandler(context.Background(), util.GracefulShutdown) defer exit() // This function might call os.Exit, so defer last - logf.InitLogs(flag.CommandLine) + logf.InitLogs() defer logf.FlushLogs() + ctrl.SetLogger(logf.Log) + ctx = logf.NewContext(ctx, logf.Log, "webhook") - cmd := app.NewServerCommand(stopCh) - cmd.Flags().AddGoFlagSet(flag.CommandLine) + cmd := app.NewServerCommand(ctx) - flag.CommandLine.Parse([]string{}) - if err := cmd.Execute(); err != nil { + if err := cmd.ExecuteContext(ctx); err != nil { logf.Log.Error(err, "error executing command") util.SetExitCode(err) } diff --git a/deploy/charts/cert-manager/.helmignore b/deploy/charts/cert-manager/.helmignore index 8842b308440..3d9914294bd 100644 --- a/deploy/charts/cert-manager/.helmignore +++ b/deploy/charts/cert-manager/.helmignore @@ -20,7 +20,6 @@ .idea/ *.tmproj -BUILD.bazel Chart.template.yaml README.template.md OWNERS diff --git a/deploy/charts/cert-manager/Chart.template.yaml b/deploy/charts/cert-manager/Chart.template.yaml index e3271dd7bcf..5e179880201 100644 --- a/deploy/charts/cert-manager/Chart.template.yaml +++ b/deploy/charts/cert-manager/Chart.template.yaml @@ -1,22 +1,27 @@ -apiVersion: v1 +apiVersion: v2 + name: cert-manager -# The version and appVersion fields are set automatically by the release tool -version: v0.1.0 -appVersion: v0.1.0 -kubeVersion: ">= 1.20.0-0" description: A Helm chart for cert-manager -home: https://github.com/cert-manager/cert-manager -icon: https://raw.githubusercontent.com/cert-manager/cert-manager/d53c0b9270f8cd90d908460d69502694e1838f5f/logo/logo-small.png +home: https://cert-manager.io +icon: https://raw.githubusercontent.com/cert-manager/community/4d35a69437d21b76322157e6284be4cd64e6d2b7/logo/logo-small.png keywords: - cert-manager - kube-lego - letsencrypt - tls -sources: - - https://github.com/cert-manager/cert-manager +annotations: + artifacthub.io/license: Apache-2.0 + artifacthub.io/category: security + artifacthub.io/prerelease: "{{IS_PRERELEASE}}" maintainers: - name: cert-manager-maintainers email: cert-manager-maintainers@googlegroups.com url: https://cert-manager.io -annotations: - artifacthub.io/prerelease: "{{IS_PRERELEASE}}" +sources: + - https://github.com/cert-manager/cert-manager + +kubeVersion: ">= 1.22.0-0" + +# The version and appVersion fields are set automatically by the release tool +version: v0.0.0 +appVersion: v0.0.0 \ No newline at end of file diff --git a/deploy/charts/cert-manager/README.template.md b/deploy/charts/cert-manager/README.template.md index 4fd1e752dda..233732c298d 100644 --- a/deploy/charts/cert-manager/README.template.md +++ b/deploy/charts/cert-manager/README.template.md @@ -1,35 +1,33 @@ # cert-manager -cert-manager is a Kubernetes addon to automate the management and issuance of -TLS certificates from various issuing sources. +cert-manager creates TLS certificates for workloads in your Kubernetes or OpenShift cluster and renews the certificates before they expire. -It will ensure certificates are valid and up to date periodically, and attempt -to renew certificates at an appropriate time before expiry. +cert-manager can obtain certificates from a [variety of certificate authorities](https://cert-manager.io/docs/configuration/issuers/), including: +[Let's Encrypt](https://cert-manager.io/docs/configuration/acme/), [HashiCorp Vault](https://cert-manager.io/docs/configuration/vault/), +[Venafi](https://cert-manager.io/docs/configuration/venafi/) and [private PKI](https://cert-manager.io/docs/configuration/ca/). ## Prerequisites -- Kubernetes 1.20+ +- Kubernetes 1.22+ ## Installing the Chart Full installation instructions, including details on how to configure extra -functionality in cert-manager can be found in the [installation docs](https://cert-manager.io/docs/installation/kubernetes/). +functionality in cert-manager can be found in the [installation docs](https://cert-manager.io/docs/installation/helm/). -Before installing the chart, you must first install the cert-manager CustomResourceDefinition resources. -This is performed in a separate step to allow you to easily uninstall and reinstall cert-manager without deleting your installed custom resources. - -```bash -$ kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/{{RELEASE_VERSION}}/cert-manager.crds.yaml -``` - -To install the chart with the release name `my-release`: +To install the chart with the release name `cert-manager`: ```console -## Add the Jetstack Helm repository -$ helm repo add jetstack https://charts.jetstack.io +# Add the Jetstack Helm repository +helm repo add jetstack https://charts.jetstack.io --force-update -## Install the cert-manager helm chart -$ helm install my-release --namespace cert-manager --version {{RELEASE_VERSION}} jetstack/cert-manager +# Install the cert-manager helm chart +helm install \ + cert-manager jetstack/cert-manager \ + --namespace cert-manager \ + --create-namespace \ + --version {{RELEASE_VERSION}} \ + --set crds.enabled=true ``` In order to begin issuing certificates, you will need to set up a ClusterIssuer @@ -53,166 +51,1964 @@ are documented in our full [upgrading guide](https://cert-manager.io/docs/instal ## Uninstalling the Chart -To uninstall/delete the `my-release` deployment: +To uninstall/delete the `cert-manager` deployment: ```console -$ helm delete my-release +helm delete cert-manager --namespace cert-manager ``` The command removes all the Kubernetes components associated with the chart and deletes the release. If you want to completely uninstall cert-manager from your cluster, you will also need to -delete the previously installed CustomResourceDefinition resources: +delete the previously installed CustomResourceDefinition resources. -```console -$ kubectl delete -f https://github.com/cert-manager/cert-manager/releases/download/{{RELEASE_VERSION}}/cert-manager.crds.yaml -``` +> ☢️ This will remove all `Issuer`,`ClusterIssuer`,`Certificate`,`CertificateRequest`,`Order` and `Challenge` resources from the cluster: +> +> ```console +> kubectl delete crd \ +> issuers.cert-manager.io \ +> clusterissuers.cert-manager.io \ +> certificates.cert-manager.io \ +> certificaterequests.cert-manager.io \ +> orders.acme.cert-manager.io \ +> challenges.acme.cert-manager.io +> ``` ## Configuration + + +### Global + +#### **global.imagePullSecrets** ~ `array` +> Default value: +> ```yaml +> [] +> ``` + +Reference to one or more secrets to be used when pulling images. For more information, see [Pull an Image from a Private Registry](https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/). + +For example: + +```yaml +imagePullSecrets: + - name: "image-pull-secret" +``` +#### **global.nodeSelector** ~ `object` +> Default value: +> ```yaml +> {} +> ``` + +Global node selector + +The nodeSelector on Pods tells Kubernetes to schedule Pods on the nodes with matching labels. For more information, see [Assigning Pods to Nodes](https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/). + +If a component-specific nodeSelector is also set, it will take precedence. + +#### **global.commonLabels** ~ `object` +> Default value: +> ```yaml +> {} +> ``` + +Labels to apply to all resources. +Please note that this does not add labels to the resources created dynamically by the controllers. For these resources, you have to add the labels in the template in the cert-manager custom resource: For example, podTemplate/ ingressTemplate in ACMEChallengeSolverHTTP01Ingress. For more information, see the [cert-manager documentation](https://cert-manager.io/docs/reference/api-docs/#acme.cert-manager.io/v1.ACMEChallengeSolverHTTP01Ingress). +For example, secretTemplate in CertificateSpec +For more information, see the [cert-manager documentation](https://cert-manager.io/docs/reference/api-docs/#cert-manager.io/v1.CertificateSpec). +#### **global.revisionHistoryLimit** ~ `number` + +The number of old ReplicaSets to retain to allow rollback (if not set, the default Kubernetes value is set to 10). + +#### **global.priorityClassName** ~ `string` +> Default value: +> ```yaml +> "" +> ``` + +The optional priority class to be used for the cert-manager pods. +#### **global.hostUsers** ~ `bool` + +Set all pods to run in a user namespace without host access. Experimental: may be removed once the Kubernetes User Namespaces feature is GA. + +Requirements: + - Kubernetes ≥ 1.33, or + - Kubernetes 1.27–1.32 with UserNamespacesSupport feature gate enabled. + +Set to false to run pods in a user namespace without host access. + +See [limitations](https://kubernetes.io/docs/concepts/workloads/pods/user-namespaces/#limitations) for details. + +#### **global.rbac.create** ~ `bool` +> Default value: +> ```yaml +> true +> ``` + +Create required ClusterRoles and ClusterRoleBindings for cert-manager. +#### **global.rbac.aggregateClusterRoles** ~ `bool` +> Default value: +> ```yaml +> true +> ``` + +Aggregate ClusterRoles to Kubernetes default user-facing roles. For more information, see [User-facing roles](https://kubernetes.io/docs/reference/access-authn-authz/rbac/#user-facing-roles) +#### **global.podSecurityPolicy.enabled** ~ `bool` +> Default value: +> ```yaml +> false +> ``` + +Create PodSecurityPolicy for cert-manager. + +Note that PodSecurityPolicy was deprecated in Kubernetes 1.21 and removed in Kubernetes 1.25. +#### **global.podSecurityPolicy.useAppArmor** ~ `bool` +> Default value: +> ```yaml +> true +> ``` + +Configure the PodSecurityPolicy to use AppArmor. +#### **global.logLevel** ~ `number` +> Default value: +> ```yaml +> 2 +> ``` + +Set the verbosity of cert-manager. A range of 0 - 6, with 6 being the most verbose. +#### **global.leaderElection.namespace** ~ `string` +> Default value: +> ```yaml +> kube-system +> ``` + +Override the namespace used for the leader election lease. +#### **global.leaderElection.leaseDuration** ~ `string` + +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. + +#### **global.leaderElection.renewDeadline** ~ `string` + +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. + +#### **global.leaderElection.retryPeriod** ~ `string` + +The duration the clients should wait between attempting acquisition and renewal of a leadership. + +#### **installCRDs** ~ `bool` +> Default value: +> ```yaml +> false +> ``` + +This option is equivalent to setting crds.enabled=true and crds.keep=true. Deprecated: use crds.enabled and crds.keep instead. +#### **crds.enabled** ~ `bool` +> Default value: +> ```yaml +> false +> ``` + +This option decides if the CRDs should be installed as part of the Helm installation. +#### **crds.keep** ~ `bool` +> Default value: +> ```yaml +> true +> ``` + +This option makes it so that the "helm.sh/resource-policy": keep annotation is added to the CRD. This will prevent Helm from uninstalling the CRD when the Helm release is uninstalled. WARNING: when the CRDs are removed, all cert-manager custom resources +(Certificates, Issuers, ...) will be removed too by the garbage collector. +### Controller + +#### **replicaCount** ~ `number` +> Default value: +> ```yaml +> 1 +> ``` + +The number of replicas of the cert-manager controller to run. + +The default is 1, but in production set this to 2 or 3 to provide high availability. + +If `replicas > 1`, consider setting `podDisruptionBudget.enabled=true`. + +Note that cert-manager uses leader election to ensure that there can only be a single instance active at a time. +#### **strategy** ~ `object` +> Default value: +> ```yaml +> {} +> ``` + +Deployment update strategy for the cert-manager controller deployment. For more information, see the [Kubernetes documentation](https://kubernetes.io/docs/concepts/workloads/controllers/deployment/#strategy). + +For example: + +```yaml +strategy: + type: RollingUpdate + rollingUpdate: + maxSurge: 0 + maxUnavailable: 1 +``` +#### **podDisruptionBudget.enabled** ~ `bool` +> Default value: +> ```yaml +> false +> ``` + +Enable or disable the PodDisruptionBudget resource. + +This prevents downtime during voluntary disruptions such as during a Node upgrade. For example, the PodDisruptionBudget will block `kubectl drain` if it is used on the Node where the only remaining cert-manager +Pod is currently running. +#### **podDisruptionBudget.minAvailable** ~ `unknown` + +This configures the minimum available pods for disruptions. It can either be set to an integer (e.g., 1) or a percentage value (e.g., 25%). +It cannot be used if `maxUnavailable` is set. + + +#### **podDisruptionBudget.maxUnavailable** ~ `unknown` + +This configures the maximum unavailable pods for disruptions. It can either be set to an integer (e.g., 1) or a percentage value (e.g., 25%). it cannot be used if `minAvailable` is set. + + +#### **featureGates** ~ `string` +> Default value: +> ```yaml +> "" +> ``` + +A comma-separated list of feature gates that should be enabled on the controller pod. +#### **maxConcurrentChallenges** ~ `number` +> Default value: +> ```yaml +> 60 +> ``` + +The maximum number of challenges that can be scheduled as 'processing' at once. +#### **image.registry** ~ `string` + +The container registry to pull the manager image from. + +#### **image.repository** ~ `string` +> Default value: +> ```yaml +> quay.io/jetstack/cert-manager-controller +> ``` + +The container image for the cert-manager controller. + +#### **image.tag** ~ `string` + +Override the image tag to deploy by setting this variable. If no value is set, the chart's appVersion is used. + +#### **image.digest** ~ `string` + +Setting a digest will override any tag. + +#### **image.pullPolicy** ~ `string` +> Default value: +> ```yaml +> IfNotPresent +> ``` + +Kubernetes imagePullPolicy on Deployment. +#### **clusterResourceNamespace** ~ `string` +> Default value: +> ```yaml +> "" +> ``` + +Override the namespace used to store DNS provider credentials etc. for ClusterIssuer resources. By default, the same namespace as cert-manager is deployed within is used. This namespace will not be automatically created by the Helm chart. +#### **namespace** ~ `string` +> Default value: +> ```yaml +> "" +> ``` + +This namespace allows you to define where the services are installed into. If not set then they use the namespace of the release. This is helpful when installing cert manager as a chart dependency (sub chart). +#### **fullnameOverride** ~ `string` + +Override the "cert-manager.fullname" value. This value is used as part of most of the names of the resources created by this Helm chart. + +#### **nameOverride** ~ `string` + +Override the "cert-manager.name" value, which is used to annotate some of the resources that are created by this Chart (using "app.kubernetes.io/name"). NOTE: There are some inconsistencies in the Helm chart when it comes to these annotations (some resources use, e.g., "cainjector.name" which resolves to the value "cainjector"). + +#### **serviceAccount.create** ~ `bool` +> Default value: +> ```yaml +> true +> ``` + +Specifies whether a service account should be created. +#### **serviceAccount.name** ~ `string` + +The name of the service account to use. +If not set and create is true, a name is generated using the fullname template. + +#### **serviceAccount.annotations** ~ `object` + +Optional additional annotations to add to the controller's Service Account. Templates are allowed for both keys and values. +Example using templating: + +```yaml +annotations: + "{{ .Chart.Name }}-helm-chart/version": "{{ .Chart.Version }}" +``` + +#### **serviceAccount.labels** ~ `object` + +Optional additional labels to add to the controller's Service Account. + +#### **serviceAccount.automountServiceAccountToken** ~ `bool` +> Default value: +> ```yaml +> true +> ``` + +Automount API credentials for a Service Account. +#### **automountServiceAccountToken** ~ `bool` + +Automounting API credentials for a particular pod. + +#### **enableCertificateOwnerRef** ~ `bool` +> Default value: +> ```yaml +> false +> ``` + +When this flag is enabled, secrets will be automatically removed when the certificate resource is deleted. +#### **config** ~ `object` +> Default value: +> ```yaml +> {} +> ``` + +This property is used to configure options for the controller pod. This allows setting options that would usually be provided using flags. + +If `apiVersion` and `kind` are unspecified they default to the current latest version (currently `controller.config.cert-manager.io/v1alpha1`). You can pin the version by specifying the `apiVersion` yourself. + +For example: + +```yaml +config: + apiVersion: controller.config.cert-manager.io/v1alpha1 + kind: ControllerConfiguration + logging: + verbosity: 2 + format: text + leaderElectionConfig: + namespace: kube-system + kubernetesAPIQPS: 9000 + kubernetesAPIBurst: 9000 + numberOfConcurrentWorkers: 200 + enableGatewayAPI: true + # Feature gates as of v1.18.1. Listed with their default values. + # See https://cert-manager.io/docs/cli/controller/ + featureGates: + AdditionalCertificateOutputFormats: true # GA - default=true + AllAlpha: false # ALPHA - default=false + AllBeta: false # BETA - default=false + ExperimentalCertificateSigningRequestControllers: false # ALPHA - default=false + ExperimentalGatewayAPISupport: true # BETA - default=true + LiteralCertificateSubject: true # BETA - default=true + NameConstraints: true # BETA - default=true + OtherNames: false # ALPHA - default=false + SecretsFilteredCaching: true # BETA - default=true + ServerSideApply: false # ALPHA - default=false + StableCertificateRequestName: true # BETA - default=true + UseCertificateRequestBasicConstraints: false # ALPHA - default=false + UseDomainQualifiedFinalizer: true # GA - default=true + ValidateCAA: false # ALPHA - default=false + DefaultPrivateKeyRotationPolicyAlways: true # BETA - default=true + ACMEHTTP01IngressPathTypeExact: true # BETA - default=true + # Configure the metrics server for TLS + # See https://cert-manager.io/docs/devops-tips/prometheus-metrics/#tls + metricsTLSConfig: + dynamic: + secretNamespace: "cert-manager" + secretName: "cert-manager-metrics-ca" + dnsNames: + - cert-manager-metrics +``` +#### **dns01RecursiveNameservers** ~ `string` +> Default value: +> ```yaml +> "" +> ``` + +A comma-separated string with the host and port of the recursive nameservers cert-manager should query. +#### **dns01RecursiveNameserversOnly** ~ `bool` +> Default value: +> ```yaml +> false +> ``` + +Forces cert-manager to use only the recursive nameservers for verification. Enabling this option could cause the DNS01 self check to take longer owing to caching performed by the recursive nameservers. +#### **disableAutoApproval** ~ `bool` +> Default value: +> ```yaml +> false +> ``` + +Option to disable cert-manager's build-in auto-approver. The auto-approver approves all CertificateRequests that reference issuers matching the 'approveSignerNames' option. This 'disableAutoApproval' option is useful when you want to make all approval decisions using a different approver (like approver-policy - https://github.com/cert-manager/approver-policy). +#### **approveSignerNames** ~ `array` +> Default value: +> ```yaml +> - issuers.cert-manager.io/* +> - clusterissuers.cert-manager.io/* +> ``` + +List of signer names that cert-manager will approve by default. CertificateRequests referencing these signer names will be auto-approved by cert-manager. Defaults to just approving the cert-manager.io Issuer and ClusterIssuer issuers. When set to an empty array, ALL issuers will be auto-approved by cert-manager. To disable the auto-approval, because, e.g., you are using approver-policy, you can enable 'disableAutoApproval'. +ref: https://cert-manager.io/docs/concepts/certificaterequest/#approval + +#### **extraArgs** ~ `array` +> Default value: +> ```yaml +> [] +> ``` + +Additional command line flags to pass to cert-manager controller binary. To see all available flags run `docker run quay.io/jetstack/cert-manager-controller: --help`. + +Use this flag to enable or disable arbitrary controllers. For example, to disable the CertificateRequests approver. + +For example: + +```yaml +extraArgs: + - --controllers=*,-certificaterequests-approver +``` +#### **extraEnv** ~ `array` +> Default value: +> ```yaml +> [] +> ``` + +Additional environment variables to pass to cert-manager controller binary. +For example: + +```yaml +extraEnv: +- name: SOME_VAR + value: 'some value' +``` +#### **resources** ~ `object` +> Default value: +> ```yaml +> {} +> ``` + +Resources to provide to the cert-manager controller pod. + +For example: + +```yaml +requests: + cpu: 10m + memory: 32Mi +``` + +For more information, see [Resource Management for Pods and Containers](https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/). +#### **securityContext** ~ `object` +> Default value: +> ```yaml +> runAsNonRoot: true +> seccompProfile: +> type: RuntimeDefault +> ``` + +Pod Security Context. +For more information, see [Configure a Security Context for a Pod or Container](https://kubernetes.io/docs/tasks/configure-pod-container/security-context/). + +#### **containerSecurityContext** ~ `object` +> Default value: +> ```yaml +> allowPrivilegeEscalation: false +> capabilities: +> drop: +> - ALL +> readOnlyRootFilesystem: true +> ``` + +Container Security Context to be set on the controller component container. For more information, see [Configure a Security Context for a Pod or Container](https://kubernetes.io/docs/tasks/configure-pod-container/security-context/). + +#### **volumes** ~ `array` +> Default value: +> ```yaml +> [] +> ``` + +Additional volumes to add to the cert-manager controller pod. +#### **volumeMounts** ~ `array` +> Default value: +> ```yaml +> [] +> ``` + +Additional volume mounts to add to the cert-manager controller container. +#### **deploymentAnnotations** ~ `object` + +Optional additional annotations to add to the controller Deployment. + +#### **podAnnotations** ~ `object` + +Optional additional annotations to add to the controller Pods. + +#### **podLabels** ~ `object` +> Default value: +> ```yaml +> {} +> ``` + +Optional additional labels to add to the controller Pods. +#### **serviceAnnotations** ~ `object` + +Optional annotations to add to the controller Service. + +#### **serviceLabels** ~ `object` + +Optional additional labels to add to the controller Service. + +#### **serviceIPFamilyPolicy** ~ `string` + +Optionally set the IP family policy for the controller Service to configure dual-stack; see [Configure dual-stack](https://kubernetes.io/docs/concepts/services-networking/dual-stack/#services). + +#### **serviceIPFamilies** ~ `array` + +Optionally set the IP families for the controller Service that should be supported, in the order in which they should be applied to ClusterIP. Can be IPv4 and/or IPv6. + +#### **podDnsPolicy** ~ `string` + +Pod DNS policy. +For more information, see [Pod's DNS Policy](https://kubernetes.io/docs/concepts/services-networking/dns-pod-service/#pod-s-dns-policy). + +#### **podDnsConfig** ~ `object` + +Pod DNS configuration. The podDnsConfig field is optional and can work with any podDnsPolicy settings. However, when a Pod's dnsPolicy is set to "None", the dnsConfig field has to be specified. For more information, see [Pod's DNS Config](https://kubernetes.io/docs/concepts/services-networking/dns-pod-service/#pod-dns-config). + +#### **hostAliases** ~ `array` +> Default value: +> ```yaml +> [] +> ``` + +Optional hostAliases for cert-manager-controller pods. May be useful when performing ACME DNS-01 self checks. +#### **nodeSelector** ~ `object` +> Default value: +> ```yaml +> kubernetes.io/os: linux +> ``` + +The nodeSelector on Pods tells Kubernetes to schedule Pods on the nodes with matching labels. For more information, see [Assigning Pods to Nodes](https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/). + +This default ensures that Pods are only scheduled to Linux nodes. It prevents Pods being scheduled to Windows nodes in a mixed OS cluster. + +#### **ingressShim.defaultIssuerName** ~ `string` + +Optional default issuer to use for ingress resources. + +#### **ingressShim.defaultIssuerKind** ~ `string` + +Optional default issuer kind to use for ingress resources. + +#### **ingressShim.defaultIssuerGroup** ~ `string` + +Optional default issuer group to use for ingress resources. + +#### **http_proxy** ~ `string` + +Configures the HTTP_PROXY environment variable where a HTTP proxy is required. + +#### **https_proxy** ~ `string` + +Configures the HTTPS_PROXY environment variable where a HTTP proxy is required. + +#### **no_proxy** ~ `string` + +Configures the NO_PROXY environment variable where a HTTP proxy is required, but certain domains should be excluded. + +#### **affinity** ~ `object` +> Default value: +> ```yaml +> {} +> ``` + +A Kubernetes Affinity, if required. For more information, see [Affinity v1 core](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.27/#affinity-v1-core). + +For example: + +```yaml +affinity: + nodeAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + nodeSelectorTerms: + - matchExpressions: + - key: foo.bar.com/role + operator: In + values: + - master +``` +#### **tolerations** ~ `array` +> Default value: +> ```yaml +> [] +> ``` + +A list of Kubernetes Tolerations, if required. For more information, see [Toleration v1 core](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.27/#toleration-v1-core). + +For example: + +```yaml +tolerations: +- key: foo.bar.com/role + operator: Equal + value: master + effect: NoSchedule +``` +#### **topologySpreadConstraints** ~ `array` +> Default value: +> ```yaml +> [] +> ``` + +A list of Kubernetes TopologySpreadConstraints, if required. For more information, see [Topology spread constraint v1 core](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.27/#topologyspreadconstraint-v1-core + +For example: + +```yaml +topologySpreadConstraints: +- maxSkew: 2 + topologyKey: topology.kubernetes.io/zone + whenUnsatisfiable: ScheduleAnyway + labelSelector: + matchLabels: + app.kubernetes.io/instance: cert-manager + app.kubernetes.io/component: controller +``` +#### **livenessProbe** ~ `object` +> Default value: +> ```yaml +> enabled: true +> failureThreshold: 8 +> initialDelaySeconds: 10 +> periodSeconds: 10 +> successThreshold: 1 +> timeoutSeconds: 15 +> ``` + +LivenessProbe settings for the controller container of the controller Pod. + +This is enabled by default, in order to enable the clock-skew liveness probe that restarts the controller in case of a skew between the system clock and the monotonic clock. LivenessProbe durations and thresholds are based on those used for the Kubernetes controller-manager. For more information see the following on the +[Kubernetes GitHub repository](https://github.com/kubernetes/kubernetes/blob/806b30170c61a38fedd54cc9ede4cd6275a1ad3b/cmd/kubeadm/app/util/staticpod/utils.go#L241-L245) + +#### **enableServiceLinks** ~ `bool` +> Default value: +> ```yaml +> false +> ``` + +enableServiceLinks indicates whether information about services should be injected into the pod's environment variables, matching the syntax of Docker links. +### Prometheus + +#### **prometheus.enabled** ~ `bool` +> Default value: +> ```yaml +> true +> ``` + +Enable Prometheus monitoring for the cert-manager controller and webhook. If you use the Prometheus Operator, set prometheus.podmonitor.enabled or prometheus.servicemonitor.enabled, to create a PodMonitor or a +ServiceMonitor resource. +Otherwise, 'prometheus.io' annotations are added to the cert-manager and cert-manager-webhook Deployments. Note that you cannot enable both PodMonitor and ServiceMonitor as they are mutually exclusive. Enabling both will result in an error. +#### **prometheus.servicemonitor.enabled** ~ `bool` +> Default value: +> ```yaml +> false +> ``` + +Create a ServiceMonitor to add cert-manager to Prometheus. +#### **prometheus.servicemonitor.namespace** ~ `string` + +The namespace that the service monitor should live in, defaults to the cert-manager namespace. + +#### **prometheus.servicemonitor.prometheusInstance** ~ `string` +> Default value: +> ```yaml +> default +> ``` + +Specifies the `prometheus` label on the created ServiceMonitor. This is used when different Prometheus instances have label selectors matching different ServiceMonitors. +#### **prometheus.servicemonitor.targetPort** ~ `string,integer` +> Default value: +> ```yaml +> http-metrics +> ``` + +The target port to set on the ServiceMonitor. This must match the port that the cert-manager controller is listening on for metrics. + +#### **prometheus.servicemonitor.path** ~ `string` +> Default value: +> ```yaml +> /metrics +> ``` + +The path to scrape for metrics. +#### **prometheus.servicemonitor.interval** ~ `string` +> Default value: +> ```yaml +> 60s +> ``` + +The interval to scrape metrics. +#### **prometheus.servicemonitor.scrapeTimeout** ~ `string` +> Default value: +> ```yaml +> 30s +> ``` + +The timeout before a metrics scrape fails. +#### **prometheus.servicemonitor.labels** ~ `object` +> Default value: +> ```yaml +> {} +> ``` + +Additional labels to add to the ServiceMonitor. +#### **prometheus.servicemonitor.annotations** ~ `object` +> Default value: +> ```yaml +> {} +> ``` + +Additional annotations to add to the ServiceMonitor. +#### **prometheus.servicemonitor.honorLabels** ~ `bool` +> Default value: +> ```yaml +> false +> ``` + +Keep labels from scraped data, overriding server-side labels. +#### **prometheus.servicemonitor.endpointAdditionalProperties** ~ `object` +> Default value: +> ```yaml +> {} +> ``` + +EndpointAdditionalProperties allows setting additional properties on the endpoint such as relabelings, metricRelabelings etc. + +For example: + +```yaml +endpointAdditionalProperties: + relabelings: + - action: replace + sourceLabels: + - __meta_kubernetes_pod_node_name + targetLabel: instance +``` + + + +#### **prometheus.podmonitor.enabled** ~ `bool` +> Default value: +> ```yaml +> false +> ``` + +Create a PodMonitor to add cert-manager to Prometheus. +#### **prometheus.podmonitor.namespace** ~ `string` + +The namespace that the pod monitor should live in, defaults to the cert-manager namespace. + +#### **prometheus.podmonitor.prometheusInstance** ~ `string` +> Default value: +> ```yaml +> default +> ``` + +Specifies the `prometheus` label on the created PodMonitor. This is used when different Prometheus instances have label selectors matching different PodMonitors. +#### **prometheus.podmonitor.path** ~ `string` +> Default value: +> ```yaml +> /metrics +> ``` + +The path to scrape for metrics. +#### **prometheus.podmonitor.interval** ~ `string` +> Default value: +> ```yaml +> 60s +> ``` + +The interval to scrape metrics. +#### **prometheus.podmonitor.scrapeTimeout** ~ `string` +> Default value: +> ```yaml +> 30s +> ``` + +The timeout before a metrics scrape fails. +#### **prometheus.podmonitor.labels** ~ `object` +> Default value: +> ```yaml +> {} +> ``` + +Additional labels to add to the PodMonitor. +#### **prometheus.podmonitor.annotations** ~ `object` +> Default value: +> ```yaml +> {} +> ``` + +Additional annotations to add to the PodMonitor. +#### **prometheus.podmonitor.honorLabels** ~ `bool` +> Default value: +> ```yaml +> false +> ``` + +Keep labels from scraped data, overriding server-side labels. +#### **prometheus.podmonitor.endpointAdditionalProperties** ~ `object` +> Default value: +> ```yaml +> {} +> ``` + +EndpointAdditionalProperties allows setting additional properties on the endpoint such as relabelings, metricRelabelings etc. + +For example: + +```yaml +endpointAdditionalProperties: + relabelings: + - action: replace + sourceLabels: + - __meta_kubernetes_pod_node_name + targetLabel: instance + # Configure the PodMonitor for TLS connections + # See https://cert-manager.io/docs/devops-tips/prometheus-metrics/#tls + scheme: https + tlsConfig: + serverName: cert-manager-metrics + ca: + secret: + name: cert-manager-metrics-ca + key: "tls.crt" +``` + + + +### Webhook + +#### **webhook.replicaCount** ~ `number` +> Default value: +> ```yaml +> 1 +> ``` + +Number of replicas of the cert-manager webhook to run. + +The default is 1, but in production set this to 2 or 3 to provide high availability. + +If `replicas > 1`, consider setting `webhook.podDisruptionBudget.enabled=true`. +#### **webhook.timeoutSeconds** ~ `number` +> Default value: +> ```yaml +> 30 +> ``` + +The number of seconds the API server should wait for the webhook to respond before treating the call as a failure. The value must be between 1 and 30 seconds. For more information, see +[Validating webhook configuration v1](https://kubernetes.io/docs/reference/kubernetes-api/extend-resources/validating-webhook-configuration-v1/). + +The default is set to the maximum value of 30 seconds as users sometimes report that the connection between the K8S API server and the cert-manager webhook server times out. If *this* timeout is reached, the error message will be "context deadline exceeded", which doesn't help the user diagnose what phase of the HTTPS connection timed out. For example, it could be during DNS resolution, TCP connection, TLS negotiation, HTTP negotiation, or slow HTTP response from the webhook server. By setting this timeout to its maximum value the underlying timeout error message has more chance of being returned to the end user. +#### **webhook.config** ~ `object` +> Default value: +> ```yaml +> {} +> ``` + +This is used to configure options for the webhook pod. This allows setting options that would usually be provided using flags. + +If `apiVersion` and `kind` are unspecified they default to the current latest version (currently `webhook.config.cert-manager.io/v1alpha1`). You can pin the version by specifying the `apiVersion` yourself. + +For example: + +```yaml +apiVersion: webhook.config.cert-manager.io/v1alpha1 +kind: WebhookConfiguration +# The port that the webhook listens on for requests. +# In GKE private clusters, by default Kubernetes apiservers are allowed to +# talk to the cluster nodes only on 443 and 10250. Configuring +# securePort: 10250 therefore will work out-of-the-box without needing to add firewall +# rules or requiring NET_BIND_SERVICE capabilities to bind port numbers < 1000. +# This should be uncommented and set as a default by the chart once +# the apiVersion of WebhookConfiguration graduates beyond v1alpha1. +securePort: 10250 +# Configure the metrics server for TLS +# See https://cert-manager.io/docs/devops-tips/prometheus-metrics/#tls +metricsTLSConfig: + dynamic: + secretNamespace: "cert-manager" + secretName: "cert-manager-metrics-ca" + dnsNames: + - cert-manager-metrics +``` +#### **webhook.strategy** ~ `object` +> Default value: +> ```yaml +> {} +> ``` + +The update strategy for the cert-manager webhook deployment. For more information, see the [Kubernetes documentation](https://kubernetes.io/docs/concepts/workloads/controllers/deployment/#strategy) + +For example: + +```yaml +strategy: + type: RollingUpdate + rollingUpdate: + maxSurge: 0 + maxUnavailable: 1 +``` +#### **webhook.securityContext** ~ `object` +> Default value: +> ```yaml +> runAsNonRoot: true +> seccompProfile: +> type: RuntimeDefault +> ``` + +Pod Security Context to be set on the webhook component Pod. For more information, see [Configure a Security Context for a Pod or Container](https://kubernetes.io/docs/tasks/configure-pod-container/security-context/). + +#### **webhook.containerSecurityContext** ~ `object` +> Default value: +> ```yaml +> allowPrivilegeEscalation: false +> capabilities: +> drop: +> - ALL +> readOnlyRootFilesystem: true +> ``` + +Container Security Context to be set on the webhook component container. For more information, see [Configure a Security Context for a Pod or Container](https://kubernetes.io/docs/tasks/configure-pod-container/security-context/). + +#### **webhook.podDisruptionBudget.enabled** ~ `bool` +> Default value: +> ```yaml +> false +> ``` + +Enable or disable the PodDisruptionBudget resource. + +This prevents downtime during voluntary disruptions such as during a Node upgrade. For example, the PodDisruptionBudget will block `kubectl drain` if it is used on the Node where the only remaining cert-manager +Pod is currently running. +#### **webhook.podDisruptionBudget.minAvailable** ~ `unknown` + +This property configures the minimum available pods for disruptions. Can either be set to an integer (e.g., 1) or a percentage value (e.g., 25%). +It cannot be used if `maxUnavailable` is set. + + +#### **webhook.podDisruptionBudget.maxUnavailable** ~ `unknown` + +This property configures the maximum unavailable pods for disruptions. Can either be set to an integer (e.g., 1) or a percentage value (e.g., 25%). +It cannot be used if `minAvailable` is set. + + +#### **webhook.deploymentAnnotations** ~ `object` + +Optional additional annotations to add to the webhook Deployment. + +#### **webhook.podAnnotations** ~ `object` + +Optional additional annotations to add to the webhook Pods. + +#### **webhook.serviceAnnotations** ~ `object` + +Optional additional annotations to add to the webhook Service. + +#### **webhook.mutatingWebhookConfigurationAnnotations** ~ `object` + +Optional additional annotations to add to the webhook MutatingWebhookConfiguration. + +#### **webhook.validatingWebhookConfigurationAnnotations** ~ `object` + +Optional additional annotations to add to the webhook ValidatingWebhookConfiguration. -The following table lists the configurable parameters of the cert-manager chart and their default values. - -| Parameter | Description | Default | -| --------- | ----------- | ------- | -| `global.imagePullSecrets` | Reference to one or more secrets to be used when pulling images | `[]` | -| `global.commonLabels` | Labels to apply to all resources | `{}` | -| `global.rbac.create` | If `true`, create and use RBAC resources (includes sub-charts) | `true` | -| `global.priorityClassName`| Priority class name for cert-manager and webhook pods | `""` | -| `global.podSecurityPolicy.enabled` | If `true`, create and use PodSecurityPolicy (includes sub-charts) | `false` | -| `global.podSecurityPolicy.useAppArmor` | If `true`, use Apparmor seccomp profile in PSP | `true` | -| `global.leaderElection.namespace` | Override the namespace used to store the ConfigMap for leader election | `kube-system` | -| `global.leaderElection.leaseDuration` | 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 | | -| `global.leaderElection.renewDeadline` | 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 | | -| `global.leaderElection.retryPeriod` | The duration the clients should wait between attempting acquisition and renewal of a leadership | | -| `installCRDs` | If true, CRD resources will be installed as part of the Helm chart. If enabled, when uninstalling CRD resources will be deleted causing all installed custom resources to be DELETED | `false` | -| `image.repository` | Image repository | `quay.io/jetstack/cert-manager-controller` | -| `image.tag` | Image tag | `{{RELEASE_VERSION}}` | -| `image.pullPolicy` | Image pull policy | `IfNotPresent` | -| `replicaCount` | Number of cert-manager replicas | `1` | -| `clusterResourceNamespace` | Override the namespace used to store DNS provider credentials etc. for ClusterIssuer resources | Same namespace as cert-manager pod | -| `featureGates` | Set of comma-separated key=value pairs that describe feature gates on the controller. Some feature gates may also have to be enabled on other components, and can be set supplying the `feature-gate` flag to `.extraArgs` | `` | -| `extraArgs` | Optional flags for cert-manager | `[]` | -| `extraEnv` | Optional environment variables for cert-manager | `[]` | -| `serviceAccount.create` | If `true`, create a new service account | `true` | -| `serviceAccount.name` | Service account to be used. If not set and `serviceAccount.create` is `true`, a name is generated using the fullname template | | -| `serviceAccount.annotations` | Annotations to add to the service account | | -| `serviceAccount.automountServiceAccountToken` | Automount API credentials for the Service Account | `true` | -| `volumes` | Optional volumes for cert-manager | `[]` | -| `volumeMounts` | Optional volume mounts for cert-manager | `[]` | -| `resources` | CPU/memory resource requests/limits | `{}` | -| `securityContext` | Security context for the controller pod assignment | refer to [Default Security Contexts](#default-security-contexts) | -| `containerSecurityContext` | Security context to be set on the controller component container | refer to [Default Security Contexts](#default-security-contexts) | -| `nodeSelector` | Node labels for pod assignment | `{}` | -| `affinity` | Node affinity for pod assignment | `{}` | -| `tolerations` | Node tolerations for pod assignment | `[]` | -| `topologySpreadConstraints` | Topology spread constraints for pod assignment | `[]` | -| `ingressShim.defaultIssuerName` | Optional default issuer to use for ingress resources | | -| `ingressShim.defaultIssuerKind` | Optional default issuer kind to use for ingress resources | | -| `ingressShim.defaultIssuerGroup` | Optional default issuer group to use for ingress resources | | -| `prometheus.enabled` | Enable Prometheus monitoring | `true` | -| `prometheus.servicemonitor.enabled` | Enable Prometheus Operator ServiceMonitor monitoring | `false` | -| `prometheus.servicemonitor.namespace` | Define namespace where to deploy the ServiceMonitor resource | (namespace where you are deploying) | -| `prometheus.servicemonitor.prometheusInstance` | Prometheus Instance definition | `default` | -| `prometheus.servicemonitor.targetPort` | Prometheus scrape port | `9402` | -| `prometheus.servicemonitor.path` | Prometheus scrape path | `/metrics` | -| `prometheus.servicemonitor.interval` | Prometheus scrape interval | `60s` | -| `prometheus.servicemonitor.labels` | Add custom labels to ServiceMonitor | | -| `prometheus.servicemonitor.scrapeTimeout` | Prometheus scrape timeout | `30s` | -| `prometheus.servicemonitor.honorLabels` | Enable label honoring for metrics scraped by Prometheus (see [Prometheus scrape config docs](https://prometheus.io/docs/prometheus/latest/configuration/configuration/#scrape_config) for details). By setting `honorLabels` to `true`, Prometheus will prefer label contents given by cert-manager on conflicts. Can be used to remove the "exported_namespace" label for example. | `false` | -| `podAnnotations` | Annotations to add to the cert-manager pod | `{}` | -| `deploymentAnnotations` | Annotations to add to the cert-manager deployment | `{}` | -| `podDnsPolicy` | Optional cert-manager pod [DNS policy](https://kubernetes.io/docs/concepts/services-networking/dns-pod-service/#pods-dns-policy) | | -| `podDnsConfig` | Optional cert-manager pod [DNS configurations](https://kubernetes.io/docs/concepts/services-networking/dns-pod-service/#pods-dns-config) | | -| `podLabels` | Labels to add to the cert-manager pod | `{}` | -| `serviceLabels` | Labels to add to the cert-manager controller service | `{}` | -| `serviceAnnotations` | Annotations to add to the cert-manager service | `{}` | -| `http_proxy` | Value of the `HTTP_PROXY` environment variable in the cert-manager pod | | -| `https_proxy` | Value of the `HTTPS_PROXY` environment variable in the cert-manager pod | | -| `no_proxy` | Value of the `NO_PROXY` environment variable in the cert-manager pod | | -| `webhook.replicaCount` | Number of cert-manager webhook replicas | `1` | -| `webhook.timeoutSeconds` | Seconds the API server should wait the webhook to respond before treating the call as a failure. | `10` | -| `webhook.podAnnotations` | Annotations to add to the webhook pods | `{}` | -| `webhook.podLabels` | Labels to add to the cert-manager webhook pod | `{}` | -| `webhook.serviceLabels` | Labels to add to the cert-manager webhook service | `{}` | -| `webhook.deploymentAnnotations` | Annotations to add to the webhook deployment | `{}` | -| `webhook.mutatingWebhookConfigurationAnnotations` | Annotations to add to the mutating webhook configuration | `{}` | -| `webhook.validatingWebhookConfigurationAnnotations` | Annotations to add to the validating webhook configuration | `{}` | -| `webhook.serviceAnnotations` | Annotations to add to the webhook service | `{}` | -| `webhook.config` | WebhookConfiguration YAML used to configure flags for the webhook. Generates a ConfigMap containing contents of the field. See `values.yaml` for example. | `{}` | -| `webhook.extraArgs` | Optional flags for cert-manager webhook component | `[]` | -| `webhook.serviceAccount.create` | If `true`, create a new service account for the webhook component | `true` | -| `webhook.serviceAccount.name` | Service account for the webhook component to be used. If not set and `webhook.serviceAccount.create` is `true`, a name is generated using the fullname template | | -| `webhook.serviceAccount.annotations` | Annotations to add to the service account for the webhook component | | -| `webhook.serviceAccount.automountServiceAccountToken` | Automount API credentials for the webhook Service Account | | -| `webhook.resources` | CPU/memory resource requests/limits for the webhook pods | `{}` | -| `webhook.nodeSelector` | Node labels for webhook pod assignment | `{}` | -| `webhook.networkPolicy.enabled` | Enable default network policies for webhooks egress and ingress traffic | `false` | -| `webhook.networkPolicy.ingress` | Sets ingress policy block. See NetworkPolicy documentation. See `values.yaml` for example. | `{}` | -| `webhook.networkPolicy.egress` | Sets ingress policy block. See NetworkPolicy documentation. See `values.yaml` for example. | `{}` | -| `webhook.affinity` | Node affinity for webhook pod assignment | `{}` | -| `webhook.tolerations` | Node tolerations for webhook pod assignment | `[]` | -| `webhook.topologySpreadConstraints` | Topology spread constraints for webhook pod assignment | `[]` | -| `webhook.image.repository` | Webhook image repository | `quay.io/jetstack/cert-manager-webhook` | -| `webhook.image.tag` | Webhook image tag | `{{RELEASE_VERSION}}` | -| `webhook.image.pullPolicy` | Webhook image pull policy | `IfNotPresent` | -| `webhook.securePort` | The port that the webhook should listen on for requests. | `10250` | -| `webhook.securityContext` | Security context for webhook pod assignment | refer to [Default Security Contexts](#default-security-contexts) | -| `webhook.containerSecurityContext` | Security context to be set on the webhook component container | refer to [Default Security Contexts](#default-security-contexts) | -| `webhook.hostNetwork` | If `true`, run the Webhook on the host network. | `false` | -| `webhook.serviceType` | The type of the `Service`. | `ClusterIP` | -| `webhook.loadBalancerIP` | The specific load balancer IP to use (when `serviceType` is `LoadBalancer`). | | -| `webhook.url.host` | The host to use to reach the webhook, instead of using internal cluster DNS for the service. | | -| `webhook.livenessProbe.failureThreshold` | The liveness probe failure threshold | `3` | -| `webhook.livenessProbe.initialDelaySeconds` | The liveness probe initial delay (in seconds) | `60` | -| `webhook.livenessProbe.periodSeconds` | The liveness probe period (in seconds) | `10` | -| `webhook.livenessProbe.successThreshold` | The liveness probe success threshold | `1` | -| `webhook.livenessProbe.timeoutSeconds` | The liveness probe timeout (in seconds) | `1` | -| `webhook.readinessProbe.failureThreshold` | The readiness probe failure threshold | `3` | -| `webhook.readinessProbe.initialDelaySeconds` | The readiness probe initial delay (in seconds) | `5` | -| `webhook.readinessProbe.periodSeconds` | The readiness probe period (in seconds) | `5` | -| `webhook.readinessProbe.successThreshold` | The readiness probe success threshold | `1` | -| `webhook.readinessProbe.timeoutSeconds` | The readiness probe timeout (in seconds) | `1` | -| `cainjector.enabled` | Toggles whether the cainjector component should be installed (required for the webhook component to work) | `true` | -| `cainjector.replicaCount` | Number of cert-manager cainjector replicas | `1` | -| `cainjector.podAnnotations` | Annotations to add to the cainjector pods | `{}` | -| `cainjector.podLabels` | Labels to add to the cert-manager cainjector pod | `{}` | -| `cainjector.deploymentAnnotations` | Annotations to add to the cainjector deployment | `{}` | -| `cainjector.extraArgs` | Optional flags for cert-manager cainjector component | `[]` | -| `cainjector.serviceAccount.create` | If `true`, create a new service account for the cainjector component | `true` | -| `cainjector.serviceAccount.name` | Service account for the cainjector component to be used. If not set and `cainjector.serviceAccount.create` is `true`, a name is generated using the fullname template | | -| `cainjector.serviceAccount.annotations` | Annotations to add to the service account for the cainjector component | | -| `cainjector.serviceAccount.automountServiceAccountToken` | Automount API credentials for the cainjector Service Account | `true` | -| `cainjector.resources` | CPU/memory resource requests/limits for the cainjector pods | `{}` | -| `cainjector.nodeSelector` | Node labels for cainjector pod assignment | `{}` | -| `cainjector.affinity` | Node affinity for cainjector pod assignment | `{}` | -| `cainjector.tolerations` | Node tolerations for cainjector pod assignment | `[]` | -| `cainjector.topologySpreadConstraints` | Topology spread constraints for cainjector pod assignment | `[]` | -| `cainjector.image.repository` | cainjector image repository | `quay.io/jetstack/cert-manager-cainjector` | -| `cainjector.image.tag` | cainjector image tag | `{{RELEASE_VERSION}}` | -| `cainjector.image.pullPolicy` | cainjector image pull policy | `IfNotPresent` | -| `cainjector.securityContext` | Security context for cainjector pod assignment | refer to [Default Security Contexts](#default-security-contexts) | -| `cainjector.containerSecurityContext` | Security context to be set on cainjector component container | refer to [Default Security Contexts](#default-security-contexts) | -| `startupapicheck.enabled` | Toggles whether the startupapicheck Job should be installed | `true` | -| `startupapicheck.securityContext` | Security context for startupapicheck pod assignment | refer to [Default Security Contexts](#default-security-contexts) | -| `startupapicheck.containerSecurityContext` | Security context to be set on startupapicheck component container | refer to [Default Security Contexts](#default-security-contexts) | -| `startupapicheck.timeout` | Timeout for 'kubectl check api' command | `1m` | -| `startupapicheck.backoffLimit` | Job backoffLimit | `4` | -| `startupapicheck.jobAnnotations` | Optional additional annotations to add to the startupapicheck Job | `{}` | -| `startupapicheck.podAnnotations` | Optional additional annotations to add to the startupapicheck Pods | `{}` | -| `startupapicheck.extraArgs` | Optional additional arguments for startupapicheck | `[]` | -| `startupapicheck.resources` | CPU/memory resource requests/limits for the startupapicheck pod | `{}` | -| `startupapicheck.nodeSelector` | Node labels for startupapicheck pod assignment | `{}` | -| `startupapicheck.affinity` | Node affinity for startupapicheck pod assignment | `{}` | -| `startupapicheck.tolerations` | Node tolerations for startupapicheck pod assignment | `[]` | -| `startupapicheck.podLabels` | Optional additional labels to add to the startupapicheck Pods | `{}` | -| `startupapicheck.image.repository` | startupapicheck image repository | `quay.io/jetstack/cert-manager-ctl` | -| `startupapicheck.image.tag` | startupapicheck image tag | `{{RELEASE_VERSION}}` | -| `startupapicheck.image.pullPolicy` | startupapicheck image pull policy | `IfNotPresent` | -| `startupapicheck.serviceAccount.create` | If `true`, create a new service account for the startupapicheck component | `true` | -| `startupapicheck.serviceAccount.name` | Service account for the startupapicheck component to be used. If not set and `startupapicheck.serviceAccount.create` is `true`, a name is generated using the fullname template | | -| `startupapicheck.serviceAccount.annotations` | Annotations to add to the service account for the startupapicheck component | | -| `startupapicheck.serviceAccount.automountServiceAccountToken` | Automount API credentials for the startupapicheck Service Account | `true` | +#### **webhook.validatingWebhookConfiguration.namespaceSelector** ~ `object` +> Default value: +> ```yaml +> matchExpressions: +> - key: cert-manager.io/disable-validation +> operator: NotIn +> values: +> - "true" +> ``` + +Configure spec.namespaceSelector for validating webhooks. + +#### **webhook.mutatingWebhookConfiguration.namespaceSelector** ~ `object` +> Default value: +> ```yaml +> {} +> ``` + +Configure spec.namespaceSelector for mutating webhooks. + +#### **webhook.extraArgs** ~ `array` +> Default value: +> ```yaml +> [] +> ``` + +Additional command line flags to pass to cert-manager webhook binary. To see all available flags run `docker run quay.io/jetstack/cert-manager-webhook: --help`. +#### **webhook.extraEnv** ~ `array` +> Default value: +> ```yaml +> [] +> ``` + +Additional environment variables to pass to cert-manager webhook binary. +For example: + +```yaml +extraEnv: +- name: SOME_VAR + value: 'some value' +``` +#### **webhook.featureGates** ~ `string` +> Default value: +> ```yaml +> "" +> ``` + +Comma separated list of feature gates that should be enabled on the webhook pod. +#### **webhook.resources** ~ `object` +> Default value: +> ```yaml +> {} +> ``` + +Resources to provide to the cert-manager webhook pod. + +For example: + +```yaml +requests: + cpu: 10m + memory: 32Mi +``` + +For more information, see [Resource Management for Pods and Containers](https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/). +#### **webhook.livenessProbe** ~ `object` +> Default value: +> ```yaml +> failureThreshold: 3 +> initialDelaySeconds: 60 +> periodSeconds: 10 +> successThreshold: 1 +> timeoutSeconds: 1 +> ``` + +Liveness probe values. +For more information, see [Container probes](https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle/#container-probes). + +#### **webhook.readinessProbe** ~ `object` +> Default value: +> ```yaml +> failureThreshold: 3 +> initialDelaySeconds: 5 +> periodSeconds: 5 +> successThreshold: 1 +> timeoutSeconds: 1 +> ``` + +Readiness probe values. +For more information, see [Container probes](https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle/#container-probes). + +#### **webhook.nodeSelector** ~ `object` +> Default value: +> ```yaml +> kubernetes.io/os: linux +> ``` + +The nodeSelector on Pods tells Kubernetes to schedule Pods on the nodes with matching labels. For more information, see [Assigning Pods to Nodes](https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/). + +This default ensures that Pods are only scheduled to Linux nodes. It prevents Pods being scheduled to Windows nodes in a mixed OS cluster. + +#### **webhook.affinity** ~ `object` +> Default value: +> ```yaml +> {} +> ``` + +A Kubernetes Affinity, if required. For more information, see [Affinity v1 core](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.27/#affinity-v1-core). + +For example: + +```yaml +affinity: + nodeAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + nodeSelectorTerms: + - matchExpressions: + - key: foo.bar.com/role + operator: In + values: + - master +``` +#### **webhook.tolerations** ~ `array` +> Default value: +> ```yaml +> [] +> ``` + +A list of Kubernetes Tolerations, if required. For more information, see [Toleration v1 core](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.27/#toleration-v1-core). + +For example: + +```yaml +tolerations: +- key: foo.bar.com/role + operator: Equal + value: master + effect: NoSchedule +``` +#### **webhook.topologySpreadConstraints** ~ `array` +> Default value: +> ```yaml +> [] +> ``` + +A list of Kubernetes TopologySpreadConstraints, if required. For more information, see [Topology spread constraint v1 core](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.27/#topologyspreadconstraint-v1-core). + +For example: + +```yaml +topologySpreadConstraints: +- maxSkew: 2 + topologyKey: topology.kubernetes.io/zone + whenUnsatisfiable: ScheduleAnyway + labelSelector: + matchLabels: + app.kubernetes.io/instance: cert-manager + app.kubernetes.io/component: controller +``` +#### **webhook.podLabels** ~ `object` +> Default value: +> ```yaml +> {} +> ``` + +Optional additional labels to add to the Webhook Pods. +#### **webhook.serviceLabels** ~ `object` +> Default value: +> ```yaml +> {} +> ``` + +Optional additional labels to add to the Webhook Service. +#### **webhook.serviceIPFamilyPolicy** ~ `string` +> Default value: +> ```yaml +> "" +> ``` + +Optionally set the IP family policy for the controller Service to configure dual-stack; see [Configure dual-stack](https://kubernetes.io/docs/concepts/services-networking/dual-stack/#services). +#### **webhook.serviceIPFamilies** ~ `array` +> Default value: +> ```yaml +> [] +> ``` + +Optionally set the IP families for the controller Service that should be supported, in the order in which they should be applied to ClusterIP. Can be IPv4 and/or IPv6. +#### **webhook.image.registry** ~ `string` + +The container registry to pull the webhook image from. + +#### **webhook.image.repository** ~ `string` +> Default value: +> ```yaml +> quay.io/jetstack/cert-manager-webhook +> ``` + +The container image for the cert-manager webhook + +#### **webhook.image.tag** ~ `string` + +Override the image tag to deploy by setting this variable. If no value is set, the chart's appVersion will be used. + +#### **webhook.image.digest** ~ `string` + +Setting a digest will override any tag + +#### **webhook.image.pullPolicy** ~ `string` +> Default value: +> ```yaml +> IfNotPresent +> ``` + +Kubernetes imagePullPolicy on Deployment. +#### **webhook.serviceAccount.create** ~ `bool` +> Default value: +> ```yaml +> true +> ``` + +Specifies whether a service account should be created. +#### **webhook.serviceAccount.name** ~ `string` + +The name of the service account to use. +If not set and create is true, a name is generated using the fullname template. + +#### **webhook.serviceAccount.annotations** ~ `object` + +Optional additional annotations to add to the webhook's Service Account. + +#### **webhook.serviceAccount.labels** ~ `object` + +Optional additional labels to add to the webhook's Service Account. + +#### **webhook.serviceAccount.automountServiceAccountToken** ~ `bool` +> Default value: +> ```yaml +> true +> ``` + +Automount API credentials for a Service Account. +#### **webhook.automountServiceAccountToken** ~ `bool` + +Automounting API credentials for a particular pod. + +#### **webhook.securePort** ~ `number` +> Default value: +> ```yaml +> 10250 +> ``` + +The port that the webhook listens on for requests. In GKE private clusters, by default Kubernetes apiservers are allowed to talk to the cluster nodes only on 443 and 10250. Configuring securePort: 10250, therefore will work out-of-the-box without needing to add firewall rules or requiring NET_BIND_SERVICE capabilities to bind port numbers <1000. +#### **webhook.hostNetwork** ~ `bool` +> Default value: +> ```yaml +> false +> ``` + +Specifies if the webhook should be started in hostNetwork mode. + +Required for use in some managed kubernetes clusters (such as AWS EKS) with custom. CNI (such as calico), because control-plane managed by AWS cannot communicate with pods' IP CIDR and admission webhooks are not working + +Since the default port for the webhook conflicts with kubelet on the host network, `webhook.securePort` should be changed to an available port if running in hostNetwork mode. +#### **webhook.serviceType** ~ `string` +> Default value: +> ```yaml +> ClusterIP +> ``` + +Specifies how the service should be handled. Useful if you want to expose the webhook outside of the cluster. In some cases, the control plane cannot reach internal services. +#### **webhook.loadBalancerIP** ~ `string` + +Specify the load balancer IP for the created service. + +#### **webhook.url** ~ `object` +> Default value: +> ```yaml +> {} +> ``` + +Overrides the mutating webhook and validating webhook so they reach the webhook service using the `url` field instead of a service. +#### **webhook.networkPolicy.enabled** ~ `bool` +> Default value: +> ```yaml +> false +> ``` + +Create network policies for the webhooks. +#### **webhook.networkPolicy.ingress** ~ `array` +> Default value: +> ```yaml +> - from: +> - ipBlock: +> cidr: 0.0.0.0/0 +> - ipBlock: +> cidr: ::/0 +> ``` + +Ingress rule for the webhook network policy. By default, it allows all inbound traffic. + +#### **webhook.networkPolicy.egress** ~ `array` +> Default value: +> ```yaml +> - ports: +> - port: 80 +> protocol: TCP +> - port: 443 +> protocol: TCP +> - port: 53 +> protocol: TCP +> - port: 53 +> protocol: UDP +> - port: 6443 +> protocol: TCP +> to: +> - ipBlock: +> cidr: 0.0.0.0/0 +> - ipBlock: +> cidr: ::/0 +> ``` + +Egress rule for the webhook network policy. By default, it allows all outbound traffic to ports 80 and 443, as well as DNS ports. + +#### **webhook.volumes** ~ `array` +> Default value: +> ```yaml +> [] +> ``` + +Additional volumes to add to the cert-manager controller pod. +#### **webhook.volumeMounts** ~ `array` +> Default value: +> ```yaml +> [] +> ``` + +Additional volume mounts to add to the cert-manager controller container. +#### **webhook.enableServiceLinks** ~ `bool` +> Default value: +> ```yaml +> false +> ``` + +enableServiceLinks indicates whether information about services should be injected into the pod's environment variables, matching the syntax of Docker links. +### CA Injector + +#### **cainjector.enabled** ~ `bool` +> Default value: +> ```yaml +> true +> ``` + +Create the CA Injector deployment +#### **cainjector.replicaCount** ~ `number` +> Default value: +> ```yaml +> 1 +> ``` + +The number of replicas of the cert-manager cainjector to run. + +The default is 1, but in production set this to 2 or 3 to provide high availability. + +If `replicas > 1`, consider setting `cainjector.podDisruptionBudget.enabled=true`. + +Note that cert-manager uses leader election to ensure that there can only be a single instance active at a time. +#### **cainjector.config** ~ `object` +> Default value: +> ```yaml +> {} +> ``` + +This is used to configure options for the cainjector pod. It allows setting options that are usually provided via flags. + +If `apiVersion` and `kind` are unspecified they default to the current latest version (currently `cainjector.config.cert-manager.io/v1alpha1`). You can pin the version by specifying the `apiVersion` yourself. + +For example: + +```yaml +apiVersion: cainjector.config.cert-manager.io/v1alpha1 +kind: CAInjectorConfiguration +logging: + verbosity: 2 + format: text +leaderElectionConfig: + namespace: kube-system +# Configure the metrics server for TLS +# See https://cert-manager.io/docs/devops-tips/prometheus-metrics/#tls +metricsTLSConfig: + dynamic: + secretNamespace: "cert-manager" + secretName: "cert-manager-metrics-ca" + dnsNames: + - cert-manager-metrics +``` +#### **cainjector.strategy** ~ `object` +> Default value: +> ```yaml +> {} +> ``` + +Deployment update strategy for the cert-manager cainjector deployment. For more information, see the [Kubernetes documentation](https://kubernetes.io/docs/concepts/workloads/controllers/deployment/#strategy). + +For example: + +```yaml +strategy: + type: RollingUpdate + rollingUpdate: + maxSurge: 0 + maxUnavailable: 1 +``` +#### **cainjector.securityContext** ~ `object` +> Default value: +> ```yaml +> runAsNonRoot: true +> seccompProfile: +> type: RuntimeDefault +> ``` + +Pod Security Context to be set on the cainjector component Pod. For more information, see [Configure a Security Context for a Pod or Container](https://kubernetes.io/docs/tasks/configure-pod-container/security-context/). + +#### **cainjector.containerSecurityContext** ~ `object` +> Default value: +> ```yaml +> allowPrivilegeEscalation: false +> capabilities: +> drop: +> - ALL +> readOnlyRootFilesystem: true +> ``` + +Container Security Context to be set on the cainjector component container. For more information, see [Configure a Security Context for a Pod or Container](https://kubernetes.io/docs/tasks/configure-pod-container/security-context/). + +#### **cainjector.podDisruptionBudget.enabled** ~ `bool` +> Default value: +> ```yaml +> false +> ``` + +Enable or disable the PodDisruptionBudget resource. + +This prevents downtime during voluntary disruptions such as during a Node upgrade. For example, the PodDisruptionBudget will block `kubectl drain` if it is used on the Node where the only remaining cert-manager +Pod is currently running. +#### **cainjector.podDisruptionBudget.minAvailable** ~ `unknown` + +`minAvailable` configures the minimum available pods for disruptions. It can either be set to +an integer (e.g., 1) or a percentage value (e.g., 25%). +Cannot be used if `maxUnavailable` is set. + + +#### **cainjector.podDisruptionBudget.maxUnavailable** ~ `unknown` + +`maxUnavailable` configures the maximum unavailable pods for disruptions. It can either be set to +an integer (e.g., 1) or a percentage value (e.g., 25%). +Cannot be used if `minAvailable` is set. + + +#### **cainjector.deploymentAnnotations** ~ `object` + +Optional additional annotations to add to the cainjector Deployment. + +#### **cainjector.podAnnotations** ~ `object` + +Optional additional annotations to add to the cainjector Pods. + +#### **cainjector.serviceAnnotations** ~ `object` + +Optional additional annotations to add to the cainjector metrics Service. + +#### **cainjector.extraArgs** ~ `array` +> Default value: +> ```yaml +> [] +> ``` + +Additional command line flags to pass to cert-manager cainjector binary. To see all available flags run `docker run quay.io/jetstack/cert-manager-cainjector: --help`. +#### **cainjector.extraEnv** ~ `array` +> Default value: +> ```yaml +> [] +> ``` + +Additional environment variables to pass to cert-manager cainjector binary. +For example: + +```yaml +extraEnv: +- name: SOME_VAR + value: 'some value' +``` +#### **cainjector.featureGates** ~ `string` +> Default value: +> ```yaml +> "" +> ``` + +Comma separated list of feature gates that should be enabled on the cainjector pod. +#### **cainjector.resources** ~ `object` +> Default value: +> ```yaml +> {} +> ``` + +Resources to provide to the cert-manager cainjector pod. + +For example: + +```yaml +requests: + cpu: 10m + memory: 32Mi +``` + +For more information, see [Resource Management for Pods and Containers](https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/). +#### **cainjector.nodeSelector** ~ `object` +> Default value: +> ```yaml +> kubernetes.io/os: linux +> ``` + +The nodeSelector on Pods tells Kubernetes to schedule Pods on the nodes with matching labels. For more information, see [Assigning Pods to Nodes](https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/). + +This default ensures that Pods are only scheduled to Linux nodes. It prevents Pods being scheduled to Windows nodes in a mixed OS cluster. + +#### **cainjector.affinity** ~ `object` +> Default value: +> ```yaml +> {} +> ``` + +A Kubernetes Affinity, if required. For more information, see [Affinity v1 core](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.27/#affinity-v1-core). + +For example: + +```yaml +affinity: + nodeAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + nodeSelectorTerms: + - matchExpressions: + - key: foo.bar.com/role + operator: In + values: + - master +``` +#### **cainjector.tolerations** ~ `array` +> Default value: +> ```yaml +> [] +> ``` + +A list of Kubernetes Tolerations, if required. For more information, see [Toleration v1 core](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.27/#toleration-v1-core). + +For example: + +```yaml +tolerations: +- key: foo.bar.com/role + operator: Equal + value: master + effect: NoSchedule +``` +#### **cainjector.topologySpreadConstraints** ~ `array` +> Default value: +> ```yaml +> [] +> ``` + +A list of Kubernetes TopologySpreadConstraints, if required. For more information, see [Topology spread constraint v1 core](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.27/#topologyspreadconstraint-v1-core). + +For example: + +```yaml +topologySpreadConstraints: +- maxSkew: 2 + topologyKey: topology.kubernetes.io/zone + whenUnsatisfiable: ScheduleAnyway + labelSelector: + matchLabels: + app.kubernetes.io/instance: cert-manager + app.kubernetes.io/component: controller +``` +#### **cainjector.podLabels** ~ `object` +> Default value: +> ```yaml +> {} +> ``` + +Optional additional labels to add to the CA Injector Pods. +#### **cainjector.serviceLabels** ~ `object` +> Default value: +> ```yaml +> {} +> ``` + +Optional additional labels to add to the CA Injector metrics Service. +#### **cainjector.image.registry** ~ `string` + +The container registry to pull the cainjector image from. + +#### **cainjector.image.repository** ~ `string` +> Default value: +> ```yaml +> quay.io/jetstack/cert-manager-cainjector +> ``` + +The container image for the cert-manager cainjector + +#### **cainjector.image.tag** ~ `string` + +Override the image tag to deploy by setting this variable. If no value is set, the chart's appVersion will be used. + +#### **cainjector.image.digest** ~ `string` + +Setting a digest will override any tag. + +#### **cainjector.image.pullPolicy** ~ `string` +> Default value: +> ```yaml +> IfNotPresent +> ``` + +Kubernetes imagePullPolicy on Deployment. +#### **cainjector.serviceAccount.create** ~ `bool` +> Default value: +> ```yaml +> true +> ``` + +Specifies whether a service account should be created. +#### **cainjector.serviceAccount.name** ~ `string` + +The name of the service account to use. +If not set and create is true, a name is generated using the fullname template + +#### **cainjector.serviceAccount.annotations** ~ `object` + +Optional additional annotations to add to the cainjector's Service Account. + +#### **cainjector.serviceAccount.labels** ~ `object` + +Optional additional labels to add to the cainjector's Service Account. + +#### **cainjector.serviceAccount.automountServiceAccountToken** ~ `bool` +> Default value: +> ```yaml +> true +> ``` + +Automount API credentials for a Service Account. +#### **cainjector.automountServiceAccountToken** ~ `bool` + +Automounting API credentials for a particular pod. + +#### **cainjector.volumes** ~ `array` +> Default value: +> ```yaml +> [] +> ``` + +Additional volumes to add to the cert-manager controller pod. +#### **cainjector.volumeMounts** ~ `array` +> Default value: +> ```yaml +> [] +> ``` + +Additional volume mounts to add to the cert-manager controller container. +#### **cainjector.enableServiceLinks** ~ `bool` +> Default value: +> ```yaml +> false +> ``` + +enableServiceLinks indicates whether information about services should be injected into the pod's environment variables, matching the syntax of Docker links. +### ACME Solver + +#### **acmesolver.image.registry** ~ `string` + +The container registry to pull the acmesolver image from. + +#### **acmesolver.image.repository** ~ `string` +> Default value: +> ```yaml +> quay.io/jetstack/cert-manager-acmesolver +> ``` + +The container image for the cert-manager acmesolver. + +#### **acmesolver.image.tag** ~ `string` + +Override the image tag to deploy by setting this variable. If no value is set, the chart's appVersion is used. + +#### **acmesolver.image.digest** ~ `string` + +Setting a digest will override any tag. + +#### **acmesolver.image.pullPolicy** ~ `string` +> Default value: +> ```yaml +> IfNotPresent +> ``` + +Kubernetes imagePullPolicy on Deployment. +### Startup API Check + + +This startupapicheck is a Helm post-install hook that waits for the webhook endpoints to become available. The check is implemented using a Kubernetes Job - if you are injecting mesh sidecar proxies into cert-manager pods, ensure that they are not injected into this Job's pod. Otherwise, the installation may time out owing to the Job never being completed because the sidecar proxy does not exit. For more information, see [this note](https://github.com/cert-manager/cert-manager/pull/4414). +#### **startupapicheck.enabled** ~ `bool` +> Default value: +> ```yaml +> true +> ``` + +Enables the startup api check. +#### **startupapicheck.securityContext** ~ `object` +> Default value: +> ```yaml +> runAsNonRoot: true +> seccompProfile: +> type: RuntimeDefault +> ``` + +Pod Security Context to be set on the startupapicheck component Pod. For more information, see [Configure a Security Context for a Pod or Container](https://kubernetes.io/docs/tasks/configure-pod-container/security-context/). + +#### **startupapicheck.containerSecurityContext** ~ `object` +> Default value: +> ```yaml +> allowPrivilegeEscalation: false +> capabilities: +> drop: +> - ALL +> readOnlyRootFilesystem: true +> ``` + +Container Security Context to be set on the controller component container. For more information, see [Configure a Security Context for a Pod or Container](https://kubernetes.io/docs/tasks/configure-pod-container/security-context/). + +#### **startupapicheck.timeout** ~ `string` +> Default value: +> ```yaml +> 1m +> ``` + +Timeout for 'kubectl check api' command. +#### **startupapicheck.backoffLimit** ~ `number` +> Default value: +> ```yaml +> 4 +> ``` + +Job backoffLimit +#### **startupapicheck.jobAnnotations** ~ `object` +> Default value: +> ```yaml +> helm.sh/hook: post-install +> helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded +> helm.sh/hook-weight: "1" +> ``` + +Optional additional annotations to add to the startupapicheck Job. + +#### **startupapicheck.podAnnotations** ~ `object` + +Optional additional annotations to add to the startupapicheck Pods. + +#### **startupapicheck.extraArgs** ~ `array` +> Default value: +> ```yaml +> - -v +> ``` + +Additional command line flags to pass to startupapicheck binary. To see all available flags run `docker run quay.io/jetstack/cert-manager-startupapicheck: --help`. + +Verbose logging is enabled by default so that if startupapicheck fails, you can know what exactly caused the failure. Verbose logs include details of the webhook URL, IP address and TCP connect errors for example. + +#### **startupapicheck.extraEnv** ~ `array` +> Default value: +> ```yaml +> [] +> ``` + +Additional environment variables to pass to cert-manager startupapicheck binary. +For example: + +```yaml +extraEnv: +- name: SOME_VAR + value: 'some value' +``` +#### **startupapicheck.resources** ~ `object` +> Default value: +> ```yaml +> {} +> ``` + +Resources to provide to the cert-manager controller pod. + +For example: + +```yaml +requests: + cpu: 10m + memory: 32Mi +``` + +For more information, see [Resource Management for Pods and Containers](https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/). +#### **startupapicheck.nodeSelector** ~ `object` +> Default value: +> ```yaml +> kubernetes.io/os: linux +> ``` + +The nodeSelector on Pods tells Kubernetes to schedule Pods on the nodes with matching labels. For more information, see [Assigning Pods to Nodes](https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/). + +This default ensures that Pods are only scheduled to Linux nodes. It prevents Pods being scheduled to Windows nodes in a mixed OS cluster. + +#### **startupapicheck.affinity** ~ `object` +> Default value: +> ```yaml +> {} +> ``` + +A Kubernetes Affinity, if required. For more information, see [Affinity v1 core](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.27/#affinity-v1-core). +For example: + +```yaml +affinity: + nodeAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + nodeSelectorTerms: + - matchExpressions: + - key: foo.bar.com/role + operator: In + values: + - master +``` +#### **startupapicheck.tolerations** ~ `array` +> Default value: +> ```yaml +> [] +> ``` + +A list of Kubernetes Tolerations, if required. For more information, see [Toleration v1 core](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.27/#toleration-v1-core). + +For example: + +```yaml +tolerations: +- key: foo.bar.com/role + operator: Equal + value: master + effect: NoSchedule +``` +#### **startupapicheck.podLabels** ~ `object` +> Default value: +> ```yaml +> {} +> ``` + +Optional additional labels to add to the startupapicheck Pods. +#### **startupapicheck.image.registry** ~ `string` + +The container registry to pull the startupapicheck image from. + +#### **startupapicheck.image.repository** ~ `string` +> Default value: +> ```yaml +> quay.io/jetstack/cert-manager-startupapicheck +> ``` + +The container image for the cert-manager startupapicheck. + +#### **startupapicheck.image.tag** ~ `string` + +Override the image tag to deploy by setting this variable. If no value is set, the chart's appVersion is used. + +#### **startupapicheck.image.digest** ~ `string` + +Setting a digest will override any tag. + +#### **startupapicheck.image.pullPolicy** ~ `string` +> Default value: +> ```yaml +> IfNotPresent +> ``` + +Kubernetes imagePullPolicy on Deployment. +#### **startupapicheck.rbac.annotations** ~ `object` +> Default value: +> ```yaml +> helm.sh/hook: post-install +> helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded +> helm.sh/hook-weight: "-5" +> ``` + +annotations for the startup API Check job RBAC and PSP resources. + +#### **startupapicheck.automountServiceAccountToken** ~ `bool` + +Automounting API credentials for a particular pod. + +#### **startupapicheck.serviceAccount.create** ~ `bool` +> Default value: +> ```yaml +> true +> ``` + +Specifies whether a service account should be created. +#### **startupapicheck.serviceAccount.name** ~ `string` + +The name of the service account to use. +If not set and create is true, a name is generated using the fullname template. + +#### **startupapicheck.serviceAccount.annotations** ~ `object` +> Default value: +> ```yaml +> helm.sh/hook: post-install +> helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded +> helm.sh/hook-weight: "-5" +> ``` + +Optional additional annotations to add to the Job's Service Account. + +#### **startupapicheck.serviceAccount.automountServiceAccountToken** ~ `bool` +> Default value: +> ```yaml +> true +> ``` + +Automount API credentials for a Service Account. + +#### **startupapicheck.serviceAccount.labels** ~ `object` + +Optional additional labels to add to the startupapicheck's Service Account. + +#### **startupapicheck.volumes** ~ `array` +> Default value: +> ```yaml +> [] +> ``` + +Additional volumes to add to the cert-manager controller pod. +#### **startupapicheck.volumeMounts** ~ `array` +> Default value: +> ```yaml +> [] +> ``` + +Additional volume mounts to add to the cert-manager controller container. +#### **startupapicheck.enableServiceLinks** ~ `bool` +> Default value: +> ```yaml +> false +> ``` + +enableServiceLinks indicates whether information about services should be injected into pod's environment variables, matching the syntax of Docker links. +#### **extraObjects** ~ `array` +> Default value: +> ```yaml +> [] +> ``` + +Create dynamic manifests via values. + +For example: + +```yaml +extraObjects: + - | + apiVersion: v1 + kind: ConfigMap + metadata: + name: '{{ template "cert-manager.fullname" . }}-extra-configmap' +``` + ### Default Security Contexts The default pod-level and container-level security contexts, below, adhere to the [restricted](https://kubernetes.io/docs/concepts/security/pod-security-standards/#restricted) Pod Security Standards policies. diff --git a/deploy/charts/cert-manager/templates/NOTES.txt b/deploy/charts/cert-manager/templates/NOTES.txt index 1025354604d..4d0b4b6048f 100644 --- a/deploy/charts/cert-manager/templates/NOTES.txt +++ b/deploy/charts/cert-manager/templates/NOTES.txt @@ -1,3 +1,12 @@ +{{- if .Values.installCRDs }} +⚠️ WARNING: `installCRDs` is deprecated, use `crds.enabled` instead. + +{{- end }} +⚠️ WARNING: New default private key rotation policy for Certificate resources. +The default private key rotation policy for Certificate resources was +changed to `Always` in cert-manager >= v1.18.0. +Learn more in the [1.18 release notes](https://cert-manager.io/docs/releases/release-notes/release-notes-1.18). + cert-manager {{ .Chart.AppVersion }} has been deployed successfully! In order to begin issuing certificates, you will need to set up a ClusterIssuer diff --git a/deploy/charts/cert-manager/templates/_helpers.tpl b/deploy/charts/cert-manager/templates/_helpers.tpl index 90db4af2681..f85373f3dc3 100644 --- a/deploy/charts/cert-manager/templates/_helpers.tpl +++ b/deploy/charts/cert-manager/templates/_helpers.tpl @@ -152,7 +152,7 @@ Labels that should be added on each resource */}} {{- define "labels" -}} app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} -{{- if eq (default "helm" .Values.creator) "helm" }} +{{- if eq .Values.creator "helm" }} app.kubernetes.io/managed-by: {{ .Release.Service }} helm.sh/chart: {{ include "chartName" . }} {{- end -}} @@ -172,3 +172,42 @@ https://github.com/helm/helm/issues/5358 {{- define "cert-manager.namespace" -}} {{ .Values.namespace | default .Release.Namespace }} {{- end -}} + +{{/* +Util function for generating the image URL based on the provided options. +IMPORTANT: This function is standardized across all charts in the cert-manager GH organization. +Any changes to this function should also be made in cert-manager, trust-manager, approver-policy, ... +See https://github.com/cert-manager/cert-manager/issues/6329 for a list of linked PRs. +*/}} +{{- define "image" -}} +{{- $defaultTag := index . 1 -}} +{{- with index . 0 -}} +{{- if .registry -}}{{ printf "%s/%s" .registry .repository }}{{- else -}}{{- .repository -}}{{- end -}} +{{- if .digest -}}{{ printf "@%s" .digest }}{{- else -}}{{ printf ":%s" (default $defaultTag .tag) }}{{- end -}} +{{- end }} +{{- end }} + +{{/* +Labels for the CRD resources. +*/}} +{{- define "cert-manager.crd-labels" -}} +app: "{{ template "cert-manager.name" . }}" +app.kubernetes.io/name: "{{ template "cert-manager.name" . }}" +app.kubernetes.io/instance: "{{ .Release.Name }}" +app.kubernetes.io/component: "crds" +{{ include "labels" . }} +{{- end -}} + +{{/* +Check that the user has not set both .installCRDs and .crds.enabled or +set .installCRDs and disabled .crds.keep. +.installCRDs is deprecated and users should use .crds.enabled and .crds.keep instead. +*/}} +{{- define "cert-manager.crd-check" -}} + {{- if and (.Values.installCRDs) (.Values.crds.enabled) }} + {{- fail "ERROR: the deprecated .installCRDs option cannot be enabled at the same time as its replacement .crds.enabled" }} + {{- end }} + {{- if and (.Values.installCRDs) (not .Values.crds.keep) }} + {{- fail "ERROR: .crds.keep is not compatible with .installCRDs, please use .crds.enabled and .crds.keep instead" }} + {{- end }} +{{- end -}} diff --git a/deploy/charts/cert-manager/templates/cainjector-config.yaml b/deploy/charts/cert-manager/templates/cainjector-config.yaml new file mode 100644 index 00000000000..994cfa347fe --- /dev/null +++ b/deploy/charts/cert-manager/templates/cainjector-config.yaml @@ -0,0 +1,19 @@ +{{- if .Values.cainjector.config -}} +{{- $config := .Values.cainjector.config -}} +{{- $_ := set $config "apiVersion" (default "cainjector.config.cert-manager.io/v1alpha1" $config.apiVersion) -}} +{{- $_ := set $config "kind" (default "CAInjectorConfiguration" $config.kind) -}} +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ include "cainjector.fullname" . }} + namespace: {{ include "cert-manager.namespace" . }} + labels: + app: {{ include "cainjector.name" . }} + app.kubernetes.io/name: {{ include "cainjector.name" . }} + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/component: "cainjector" + {{- include "labels" . | nindent 4 }} +data: + config.yaml: | + {{- $config | toYaml | nindent 4 }} +{{- end -}} \ No newline at end of file diff --git a/deploy/charts/cert-manager/templates/cainjector-deployment.yaml b/deploy/charts/cert-manager/templates/cainjector-deployment.yaml index fbfed0fceaf..490de5af748 100644 --- a/deploy/charts/cert-manager/templates/cainjector-deployment.yaml +++ b/deploy/charts/cert-manager/templates/cainjector-deployment.yaml @@ -16,6 +16,10 @@ metadata: {{- end }} spec: replicas: {{ .Values.cainjector.replicaCount }} + {{- /* The if statement below is equivalent to {{- if $value }} but will also return true for 0. */ -}} + {{- if not (has (quote .Values.global.revisionHistoryLimit) (list "" (quote ""))) }} + revisionHistoryLimit: {{ .Values.global.revisionHistoryLimit }} + {{- end }} selector: matchLabels: app.kubernetes.io/name: {{ include "cainjector.name" . }} @@ -40,28 +44,48 @@ spec: annotations: {{- toYaml . | nindent 8 }} {{- end }} + {{- if and .Values.prometheus.enabled (not (or .Values.prometheus.servicemonitor.enabled .Values.prometheus.podmonitor.enabled)) }} + {{- if not .Values.cainjector.podAnnotations }} + annotations: + {{- end }} + prometheus.io/path: "/metrics" + prometheus.io/scrape: 'true' + prometheus.io/port: '9402' + {{- end }} spec: + {{- if not .Values.cainjector.serviceAccount.create }} + {{- with .Values.global.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- end }} serviceAccountName: {{ template "cainjector.serviceAccountName" . }} {{- if hasKey .Values.cainjector "automountServiceAccountToken" }} automountServiceAccountToken: {{ .Values.cainjector.automountServiceAccountToken }} {{- end }} + enableServiceLinks: {{ .Values.cainjector.enableServiceLinks }} {{- with .Values.global.priorityClassName }} priorityClassName: {{ . | quote }} {{- end }} + {{- if (hasKey .Values.global "hostUsers") }} + hostUsers: {{ .Values.global.hostUsers }} + {{- end }} {{- with .Values.cainjector.securityContext }} securityContext: {{- toYaml . | nindent 8 }} {{- end }} containers: - name: {{ .Chart.Name }}-cainjector - {{- with .Values.cainjector.image }} - image: "{{- if .registry -}}{{ .registry }}/{{- end -}}{{ .repository }}{{- if (.digest) -}} @{{ .digest }}{{- else -}}:{{ default $.Chart.AppVersion .tag }} {{- end -}}" - {{- end }} + image: "{{ template "image" (tuple .Values.cainjector.image $.Chart.AppVersion) }}" imagePullPolicy: {{ .Values.cainjector.image.pullPolicy }} args: - {{- if .Values.global.logLevel }} + {{- /* The if statement below is equivalent to {{- if $value }} but will also return true for 0. */ -}} + {{- if not (has (quote .Values.global.logLevel) (list "" (quote ""))) }} - --v={{ .Values.global.logLevel }} {{- end }} + {{- if .Values.cainjector.config }} + - --config=/var/cert-manager/config/config.yaml + {{- end }} {{- with .Values.global.leaderElection }} - --leader-election-namespace={{ .namespace }} {{- if .leaseDuration }} @@ -74,14 +98,29 @@ spec: - --leader-election-retry-period={{ .retryPeriod }} {{- end }} {{- end }} + {{- with .Values.cainjector.featureGates}} + - --feature-gates={{ . }} + {{- end}} {{- with .Values.cainjector.extraArgs }} {{- toYaml . | nindent 10 }} {{- end }} + {{- if not .Values.prometheus.enabled }} + - --metrics-listen-address=0 + {{- end }} + {{- if .Values.prometheus.enabled }} + ports: + - containerPort: 9402 + name: http-metrics + protocol: TCP + {{- end }} env: - name: POD_NAMESPACE valueFrom: fieldRef: fieldPath: metadata.namespace + {{- with .Values.cainjector.extraEnv }} + {{- toYaml . | nindent 10 }} + {{- end }} {{- with .Values.cainjector.containerSecurityContext }} securityContext: {{- toYaml . | nindent 12 }} @@ -90,9 +129,21 @@ spec: resources: {{- toYaml . | nindent 12 }} {{- end }} - {{- with .Values.cainjector.nodeSelector }} + {{- if or .Values.cainjector.config .Values.cainjector.volumeMounts }} + volumeMounts: + {{- if .Values.cainjector.config }} + - name: config + mountPath: /var/cert-manager/config + {{- end }} + {{- with .Values.cainjector.volumeMounts }} + {{- toYaml . | nindent 12 }} + {{- end }} + {{- end }} + {{- with (coalesce .Values.cainjector.nodeSelector .Values.global.nodeSelector) }} nodeSelector: - {{- toYaml . | nindent 8 }} + {{- range $key, $value := . }} + {{ $key }}: {{ $value | quote }} + {{- end }} {{- end }} {{- with .Values.cainjector.affinity }} affinity: @@ -106,4 +157,15 @@ spec: topologySpreadConstraints: {{- toYaml . | nindent 8 }} {{- end }} + {{- if or .Values.cainjector.volumes .Values.cainjector.config }} + volumes: + {{- if .Values.cainjector.config }} + - name: config + configMap: + name: {{ include "cainjector.fullname" . }} + {{- end }} + {{ with .Values.cainjector.volumes }} + {{- toYaml . | nindent 8 }} + {{- end }} + {{- end }} {{- end }} diff --git a/deploy/charts/cert-manager/templates/cainjector-poddisruptionbudget.yaml b/deploy/charts/cert-manager/templates/cainjector-poddisruptionbudget.yaml new file mode 100644 index 00000000000..6a7d60913fd --- /dev/null +++ b/deploy/charts/cert-manager/templates/cainjector-poddisruptionbudget.yaml @@ -0,0 +1,29 @@ +{{- if .Values.cainjector.podDisruptionBudget.enabled }} +apiVersion: policy/v1 +kind: PodDisruptionBudget +metadata: + name: {{ include "cainjector.fullname" . }} + namespace: {{ include "cert-manager.namespace" . }} + labels: + app: {{ include "cainjector.name" . }} + app.kubernetes.io/name: {{ include "cainjector.name" . }} + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/component: "cainjector" + {{- include "labels" . | nindent 4 }} +spec: + selector: + matchLabels: + app.kubernetes.io/name: {{ include "cainjector.name" . }} + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/component: "cainjector" + + {{- if not (or (hasKey .Values.cainjector.podDisruptionBudget "minAvailable") (hasKey .Values.cainjector.podDisruptionBudget "maxUnavailable")) }} + minAvailable: 1 # Default value because minAvailable and maxUnavailable are not set + {{- end }} + {{- if hasKey .Values.cainjector.podDisruptionBudget "minAvailable" }} + minAvailable: {{ .Values.cainjector.podDisruptionBudget.minAvailable }} + {{- end }} + {{- if hasKey .Values.cainjector.podDisruptionBudget "maxUnavailable" }} + maxUnavailable: {{ .Values.cainjector.podDisruptionBudget.maxUnavailable }} + {{- end }} +{{- end }} diff --git a/deploy/charts/cert-manager/templates/cainjector-rbac.yaml b/deploy/charts/cert-manager/templates/cainjector-rbac.yaml index 0393f92be19..511073c6de1 100644 --- a/deploy/charts/cert-manager/templates/cainjector-rbac.yaml +++ b/deploy/charts/cert-manager/templates/cainjector-rbac.yaml @@ -22,13 +22,13 @@ rules: verbs: ["get", "create", "update", "patch"] - apiGroups: ["admissionregistration.k8s.io"] resources: ["validatingwebhookconfigurations", "mutatingwebhookconfigurations"] - verbs: ["get", "list", "watch", "update"] + verbs: ["get", "list", "watch", "update", "patch"] - apiGroups: ["apiregistration.k8s.io"] resources: ["apiservices"] - verbs: ["get", "list", "watch", "update"] + verbs: ["get", "list", "watch", "update", "patch"] - apiGroups: ["apiextensions.k8s.io"] resources: ["customresourcedefinitions"] - verbs: ["get", "list", "watch", "update"] + verbs: ["get", "list", "watch", "update", "patch"] --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding @@ -101,3 +101,56 @@ subjects: namespace: {{ include "cert-manager.namespace" . }} {{- end }} {{- end }} +{{- $certmanagerNamespace := include "cert-manager.namespace" . }} +{{- if (.Values.cainjector.config.metricsTLSConfig).dynamic }} +{{- if $certmanagerNamespace | eq .Values.cainjector.config.metricsTLSConfig.dynamic.secretNamespace }} + +--- + +# Metrics server dynamic TLS serving certificate rules +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: {{ template "cainjector.fullname" . }}:dynamic-serving + namespace: {{ include "cert-manager.namespace" . }} + labels: + app: {{ include "cainjector.name" . }} + app.kubernetes.io/name: {{ include "cainjector.name" . }} + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/component: "cainjector" + {{- include "labels" . | nindent 4 }} +rules: + - apiGroups: [""] + resources: ["secrets"] + resourceNames: + # Allow cainjector to read and update the metrics CA Secret when dynamic TLS is + # enabled for the metrics server and if the Secret is configured to be in the + # same namespace as cert-manager. + - {{ .Values.cainjector.config.metricsTLSConfig.dynamic.secretName | quote }} + verbs: ["get", "list", "watch", "update"] + # It's not possible to grant CREATE permission on a single resourceName. + - apiGroups: [""] + resources: ["secrets"] + verbs: ["create"] +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: {{ include "cainjector.fullname" . }}:dynamic-serving + namespace: {{ include "cert-manager.namespace" . }} + labels: + app: {{ include "cainjector.name" . }} + app.kubernetes.io/name: {{ include "cainjector.name" . }} + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/component: "cainjector" + {{- include "labels" . | nindent 4 }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: {{ template "cainjector.fullname" . }}:dynamic-serving +subjects: + - kind: ServiceAccount + name: {{ template "cainjector.serviceAccountName" . }} + namespace: {{ include "cert-manager.namespace" . }} +{{- end }} +{{- end }} diff --git a/deploy/charts/cert-manager/templates/cainjector-service.yaml b/deploy/charts/cert-manager/templates/cainjector-service.yaml new file mode 100644 index 00000000000..dd0e64db251 --- /dev/null +++ b/deploy/charts/cert-manager/templates/cainjector-service.yaml @@ -0,0 +1,32 @@ +{{- if .Values.cainjector.enabled }} +{{- if and .Values.prometheus.enabled (not .Values.prometheus.podmonitor.enabled) }} +apiVersion: v1 +kind: Service +metadata: + name: {{ template "cainjector.fullname" . }} + namespace: {{ include "cert-manager.namespace" . }} +{{- with .Values.cainjector.serviceAnnotations }} + annotations: +{{ toYaml . | indent 4 }} +{{- end }} + labels: + app: {{ include "cainjector.name" . }} + app.kubernetes.io/name: {{ include "cainjector.name" . }} + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/component: "cainjector" + {{- include "labels" . | nindent 4 }} + {{- with .Values.cainjector.serviceLabels }} + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + type: ClusterIP + ports: + - protocol: TCP + port: 9402 + name: http-metrics + selector: + app.kubernetes.io/name: {{ include "cainjector.name" . }} + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/component: "cainjector" +{{- end }} +{{- end }} diff --git a/deploy/charts/cert-manager/templates/controller-config.yaml b/deploy/charts/cert-manager/templates/controller-config.yaml new file mode 100644 index 00000000000..46d2cc2476b --- /dev/null +++ b/deploy/charts/cert-manager/templates/controller-config.yaml @@ -0,0 +1,19 @@ +{{- if .Values.config -}} +{{- $config := .Values.config -}} +{{- $_ := set $config "apiVersion" (default "controller.config.cert-manager.io/v1alpha1" $config.apiVersion) -}} +{{- $_ := set $config "kind" (default "ControllerConfiguration" $config.kind) -}} +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ include "cert-manager.fullname" . }} + namespace: {{ include "cert-manager.namespace" . }} + labels: + app: {{ include "cert-manager.name" . }} + app.kubernetes.io/name: {{ include "cert-manager.name" . }} + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/component: "controller" + {{- include "labels" . | nindent 4 }} +data: + config.yaml: | + {{- $config | toYaml | nindent 4 }} +{{- end -}} \ No newline at end of file diff --git a/deploy/charts/cert-manager/templates/crd-acme.cert-manager.io_challenges.yaml b/deploy/charts/cert-manager/templates/crd-acme.cert-manager.io_challenges.yaml new file mode 100644 index 00000000000..5bed0cd8d55 --- /dev/null +++ b/deploy/charts/cert-manager/templates/crd-acme.cert-manager.io_challenges.yaml @@ -0,0 +1,3281 @@ +{{- if or .Values.crds.enabled .Values.installCRDs }} +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: "challenges.acme.cert-manager.io" + {{- if .Values.crds.keep }} + annotations: + helm.sh/resource-policy: keep + {{- end }} + labels: + {{- include "cert-manager.crd-labels" . | nindent 4 }} +spec: + group: acme.cert-manager.io + names: + categories: + - cert-manager + - cert-manager-acme + kind: Challenge + listKind: ChallengeList + plural: challenges + singular: challenge + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .status.state + name: State + type: string + - jsonPath: .spec.dnsName + name: Domain + type: string + - jsonPath: .status.reason + name: Reason + priority: 1 + type: string + - description: CreationTimestamp is a timestamp representing the server time when this object was created. It is not guaranteed to be set in happens-before order across separate operations. Clients may not set this value. It is represented in RFC3339 form and is in UTC. + jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1 + schema: + openAPIV3Schema: + description: Challenge is a type to represent a Challenge request with an ACME server + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + properties: + authorizationURL: + description: |- + The URL to the ACME Authorization resource that this + challenge is a part of. + type: string + dnsName: + description: |- + 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`. + type: string + issuerRef: + description: |- + 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. + properties: + group: + description: |- + Group of the issuer being referred to. + Defaults to 'cert-manager.io'. + type: string + kind: + description: |- + Kind of the issuer being referred to. + Defaults to 'Issuer'. + type: string + name: + description: Name of the issuer being referred to. + type: string + required: + - name + type: object + key: + description: |- + 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: + `.`. + For DNS01 challenges, this is the base64 encoded SHA256 sum of the + `.` + text that must be set as the TXT record content. + type: string + solver: + description: |- + Contains the domain solving configuration that should be used to + solve this challenge resource. + properties: + dns01: + description: |- + Configures cert-manager to attempt to complete authorizations by + performing the DNS01 challenge flow. + properties: + acmeDNS: + description: |- + Use the 'ACME DNS' (https://github.com/joohoi/acme-dns) API to manage + DNS01 challenge records. + properties: + accountSecretRef: + description: |- + A reference to a specific 'key' within a Secret resource. + In some instances, `key` is a required field. + properties: + key: + description: |- + 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. + type: string + name: + description: |- + Name of the resource being referred to. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + required: + - name + type: object + host: + type: string + required: + - accountSecretRef + - host + type: object + akamai: + description: Use the Akamai DNS zone management API to manage DNS01 challenge records. + properties: + accessTokenSecretRef: + description: |- + A reference to a specific 'key' within a Secret resource. + In some instances, `key` is a required field. + properties: + key: + description: |- + 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. + type: string + name: + description: |- + Name of the resource being referred to. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + required: + - name + type: object + clientSecretSecretRef: + description: |- + A reference to a specific 'key' within a Secret resource. + In some instances, `key` is a required field. + properties: + key: + description: |- + 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. + type: string + name: + description: |- + Name of the resource being referred to. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + required: + - name + type: object + clientTokenSecretRef: + description: |- + A reference to a specific 'key' within a Secret resource. + In some instances, `key` is a required field. + properties: + key: + description: |- + 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. + type: string + name: + description: |- + Name of the resource being referred to. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + required: + - name + type: object + serviceConsumerDomain: + type: string + required: + - accessTokenSecretRef + - clientSecretSecretRef + - clientTokenSecretRef + - serviceConsumerDomain + type: object + azureDNS: + description: Use the Microsoft Azure DNS API to manage DNS01 challenge records. + properties: + clientID: + description: |- + Auth: Azure Service Principal: + The ClientID of the Azure Service Principal used to authenticate with Azure DNS. + If set, ClientSecret and TenantID must also be set. + type: string + clientSecretSecretRef: + description: |- + Auth: Azure Service Principal: + A reference to a Secret containing the password associated with the Service Principal. + If set, ClientID and TenantID must also be set. + properties: + key: + description: |- + 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. + type: string + name: + description: |- + Name of the resource being referred to. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + required: + - name + type: object + environment: + description: name of the Azure environment (default AzurePublicCloud) + enum: + - AzurePublicCloud + - AzureChinaCloud + - AzureGermanCloud + - AzureUSGovernmentCloud + type: string + hostedZoneName: + description: name of the DNS zone that should be used + type: string + managedIdentity: + description: |- + Auth: Azure Workload Identity or Azure Managed Service Identity: + Settings to enable Azure Workload Identity or Azure Managed Service Identity + If set, ClientID, ClientSecret and TenantID must not be set. + properties: + clientID: + description: client ID of the managed identity, cannot be used at the same time as resourceID + type: string + resourceID: + description: |- + resource ID of the managed identity, cannot be used at the same time as clientID + Cannot be used for Azure Managed Service Identity + type: string + tenantID: + description: tenant ID of the managed identity, cannot be used at the same time as resourceID + type: string + type: object + resourceGroupName: + description: resource group the DNS zone is located in + type: string + subscriptionID: + description: ID of the Azure subscription + type: string + tenantID: + description: |- + Auth: Azure Service Principal: + The TenantID of the Azure Service Principal used to authenticate with Azure DNS. + If set, ClientID and ClientSecret must also be set. + type: string + required: + - resourceGroupName + - subscriptionID + type: object + cloudDNS: + description: Use the Google Cloud DNS API to manage DNS01 challenge records. + properties: + hostedZoneName: + description: |- + 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. + type: string + project: + type: string + serviceAccountSecretRef: + description: |- + A reference to a specific 'key' within a Secret resource. + In some instances, `key` is a required field. + properties: + key: + description: |- + 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. + type: string + name: + description: |- + Name of the resource being referred to. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + required: + - name + type: object + required: + - project + type: object + cloudflare: + description: Use the Cloudflare API to manage DNS01 challenge records. + properties: + apiKeySecretRef: + description: |- + 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. + properties: + key: + description: |- + 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. + type: string + name: + description: |- + Name of the resource being referred to. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + required: + - name + type: object + apiTokenSecretRef: + description: API token used to authenticate with Cloudflare. + properties: + key: + description: |- + 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. + type: string + name: + description: |- + Name of the resource being referred to. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + required: + - name + type: object + email: + description: Email of the account, only required when using API key based authentication. + type: string + type: object + cnameStrategy: + description: |- + CNAMEStrategy configures how the DNS01 provider should handle CNAME + records when found in DNS zones. + enum: + - None + - Follow + type: string + digitalocean: + description: Use the DigitalOcean DNS API to manage DNS01 challenge records. + properties: + tokenSecretRef: + description: |- + A reference to a specific 'key' within a Secret resource. + In some instances, `key` is a required field. + properties: + key: + description: |- + 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. + type: string + name: + description: |- + Name of the resource being referred to. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + required: + - name + type: object + required: + - tokenSecretRef + type: object + rfc2136: + description: |- + Use RFC2136 ("Dynamic Updates in the Domain Name System") (https://datatracker.ietf.org/doc/rfc2136/) + to manage DNS01 challenge records. + properties: + nameserver: + description: |- + 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. + type: string + protocol: + description: Protocol to use for dynamic DNS update queries. Valid values are (case-sensitive) ``TCP`` and ``UDP``; ``UDP`` (default). + enum: + - TCP + - UDP + type: string + tsigAlgorithm: + description: |- + 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``. + type: string + tsigKeyName: + description: |- + The TSIG Key name configured in the DNS. + If ``tsigSecretSecretRef`` is defined, this field is required. + type: string + tsigSecretSecretRef: + description: |- + The name of the secret containing the TSIG value. + If ``tsigKeyName`` is defined, this field is required. + properties: + key: + description: |- + 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. + type: string + name: + description: |- + Name of the resource being referred to. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + required: + - name + type: object + required: + - nameserver + type: object + route53: + description: Use the AWS Route53 API to manage DNS01 challenge records. + properties: + accessKeyID: + description: |- + 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 + type: string + accessKeyIDSecretRef: + description: |- + 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 + properties: + key: + description: |- + 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. + type: string + name: + description: |- + Name of the resource being referred to. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + required: + - name + type: object + auth: + description: Auth configures how cert-manager authenticates. + properties: + kubernetes: + description: |- + Kubernetes authenticates with Route53 using AssumeRoleWithWebIdentity + by passing a bound ServiceAccount token. + properties: + serviceAccountRef: + description: |- + A reference to a service account that will be used to request a bound + token (also known as "projected token"). To use this field, you must + configure an RBAC rule to let cert-manager request a token. + properties: + audiences: + description: |- + TokenAudiences is an optional list of audiences to include in the + token passed to AWS. The default token consisting of the issuer's namespace + and name is always included. + If unset the audience defaults to `sts.amazonaws.com`. + items: + type: string + type: array + x-kubernetes-list-type: atomic + name: + description: Name of the ServiceAccount used to request a token. + type: string + required: + - name + type: object + required: + - serviceAccountRef + type: object + required: + - kubernetes + type: object + hostedZoneID: + description: If set, the provider will manage only this zone in Route53 and will not do a lookup using the route53:ListHostedZonesByName api call. + type: string + region: + description: |- + Override the AWS region. + + Route53 is a global service and does not have regional endpoints but the + region specified here (or via environment variables) is used as a hint to + help compute the correct AWS credential scope and partition when it + connects to Route53. See: + - [Amazon Route 53 endpoints and quotas](https://docs.aws.amazon.com/general/latest/gr/r53.html) + - [Global services](https://docs.aws.amazon.com/whitepapers/latest/aws-fault-isolation-boundaries/global-services.html) + + If you omit this region field, cert-manager will use the region from + AWS_REGION and AWS_DEFAULT_REGION environment variables, if they are set + in the cert-manager controller Pod. + + The `region` field is not needed if you use [IAM Roles for Service Accounts (IRSA)](https://docs.aws.amazon.com/eks/latest/userguide/iam-roles-for-service-accounts.html). + Instead an AWS_REGION environment variable is added to the cert-manager controller Pod by: + [Amazon EKS Pod Identity Webhook](https://github.com/aws/amazon-eks-pod-identity-webhook). + In this case this `region` field value is ignored. + + The `region` field is not needed if you use [EKS Pod Identities](https://docs.aws.amazon.com/eks/latest/userguide/pod-identities.html). + Instead an AWS_REGION environment variable is added to the cert-manager controller Pod by: + [Amazon EKS Pod Identity Agent](https://github.com/aws/eks-pod-identity-agent), + In this case this `region` field value is ignored. + type: string + role: + description: |- + 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 + type: string + secretAccessKeySecretRef: + description: |- + 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 + properties: + key: + description: |- + 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. + type: string + name: + description: |- + Name of the resource being referred to. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + required: + - name + type: object + type: object + webhook: + description: |- + Configure an external webhook based DNS01 challenge solver to manage + DNS01 challenge records. + properties: + config: + description: |- + 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. + x-kubernetes-preserve-unknown-fields: true + groupName: + description: |- + 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. + type: string + solverName: + description: |- + 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'. + type: string + required: + - groupName + - solverName + type: object + type: object + http01: + description: |- + 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. + properties: + gatewayHTTPRoute: + description: |- + 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. + properties: + labels: + additionalProperties: + type: string + description: |- + Custom labels that will be applied to HTTPRoutes created by cert-manager + while solving HTTP-01 challenges. + type: object + parentRefs: + description: |- + 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 + items: + description: |- + ParentReference identifies an API object (usually a Gateway) that can be considered + a parent of this resource (usually a route). There are two kinds of parent resources + with "Core" support: + + * Gateway (Gateway conformance profile) + * Service (Mesh conformance profile, ClusterIP Services only) + + This API may be extended in the future to support additional kinds of parent + resources. + + The API object must be valid in the cluster; the Group and Kind must + be registered in the cluster for this reference to be valid. + properties: + group: + default: gateway.networking.k8s.io + description: |- + Group is the group of the referent. + When unspecified, "gateway.networking.k8s.io" is inferred. + To set the core API group (such as for a "Service" kind referent), + Group must be explicitly set to "" (empty string). + + Support: Core + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Gateway + description: |- + Kind is kind of the referent. + + There are two kinds of parent resources with "Core" support: + + * Gateway (Gateway conformance profile) + * Service (Mesh conformance profile, ClusterIP Services only) + + Support for other resources is Implementation-Specific. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: |- + Name is the name of the referent. + + Support: Core + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the referent. When unspecified, this refers + to the local namespace of the Route. + + Note that there are specific rules for ParentRefs which cross namespace + boundaries. Cross-namespace references are only valid if they are explicitly + allowed by something in the namespace they are referring to. For example: + Gateway has the AllowedRoutes field, and ReferenceGrant provides a + generic way to enable any other kind of cross-namespace reference. + + + ParentRefs from a Route to a Service in the same namespace are "producer" + routes, which apply default routing rules to inbound connections from + any namespace to the Service. + + ParentRefs from a Route to a Service in a different namespace are + "consumer" routes, and these routing rules are only applied to outbound + connections originating from the same namespace as the Route, for which + the intended destination of the connections are a Service targeted as a + ParentRef of the Route. + + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: |- + Port is the network port this Route targets. It can be interpreted + differently based on the type of parent resource. + + When the parent resource is a Gateway, this targets all listeners + listening on the specified port that also support this kind of Route(and + select this Route). It's not recommended to set `Port` unless the + networking behaviors specified in a Route must apply to a specific port + as opposed to a listener(s) whose port(s) may be changed. When both Port + and SectionName are specified, the name and port of the selected listener + must match both specified values. + + + When the parent resource is a Service, this targets a specific port in the + Service spec. When both Port (experimental) and SectionName are specified, + the name and port of the selected port must match both specified values. + + + Implementations MAY choose to support other parent resources. + Implementations supporting other types of parent resources MUST clearly + document how/if Port is interpreted. + + For the purpose of status, an attachment is considered successful as + long as the parent resource accepts it partially. For example, Gateway + listeners can restrict which Routes can attach to them by Route kind, + namespace, or hostname. If 1 of 2 Gateway listeners accept attachment + from the referencing Route, the Route MUST be considered successfully + attached. If no Gateway listeners accept attachment from this Route, + the Route MUST be considered detached from the Gateway. + + Support: Extended + format: int32 + maximum: 65535 + minimum: 1 + type: integer + sectionName: + description: |- + SectionName is the name of a section within the target resource. In the + following resources, SectionName is interpreted as the following: + + * Gateway: Listener name. When both Port (experimental) and SectionName + are specified, the name and port of the selected listener must match + both specified values. + * Service: Port name. When both Port (experimental) and SectionName + are specified, the name and port of the selected listener must match + both specified values. + + Implementations MAY choose to support attaching Routes to other resources. + If that is the case, they MUST clearly document how SectionName is + interpreted. + + When unspecified (empty string), this will reference the entire resource. + For the purpose of status, an attachment is considered successful if at + least one section in the parent resource accepts it. For example, Gateway + listeners can restrict which Routes can attach to them by Route kind, + namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from + the referencing Route, the Route MUST be considered successfully + attached. If no Gateway listeners accept attachment from this Route, the + Route MUST be considered detached from the Gateway. + + Support: Core + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - name + type: object + type: array + x-kubernetes-list-type: atomic + podTemplate: + description: |- + Optional pod template used to configure the ACME challenge solver pods + used for HTTP01 challenges. + properties: + metadata: + description: |- + 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. + properties: + annotations: + additionalProperties: + type: string + description: Annotations that should be added to the created ACME HTTP01 solver pods. + type: object + labels: + additionalProperties: + type: string + description: Labels that should be added to the created ACME HTTP01 solver pods. + type: object + type: object + spec: + description: |- + PodSpec defines overrides for the HTTP01 challenge solver pod. + Check ACMEChallengeSolverHTTP01IngressPodSpec to find out currently supported fields. + All other fields will be ignored. + properties: + affinity: + description: If specified, the pod's scheduling constraints + properties: + nodeAffinity: + description: Describes node affinity scheduling rules for the pod. + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node matches the corresponding matchExpressions; the + node(s) with the highest sum are the most preferred. + items: + description: |- + An empty preferred scheduling term matches all objects with implicit weight 0 + (i.e. it's a no-op). A null preferred scheduling term matches no objects (i.e. is also a no-op). + properties: + preference: + description: A node selector term, associated with the corresponding weight. + properties: + matchExpressions: + description: A list of node selector requirements by node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: The label key that the selector applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: A list of node selector requirements by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: The label key that the selector applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + weight: + description: Weight associated with matching the corresponding nodeSelectorTerm, in the range 1-100. + format: int32 + type: integer + required: + - preference + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to an update), the system + may or may not try to eventually evict the pod from its node. + properties: + nodeSelectorTerms: + description: Required. A list of node selector terms. The terms are ORed. + items: + description: |- + A null or empty node selector term matches no objects. The requirements of + them are ANDed. + The TopologySelectorTerm type implements a subset of the NodeSelectorTerm. + properties: + matchExpressions: + description: A list of node selector requirements by node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: The label key that the selector applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: A list of node selector requirements by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: The label key that the selector applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + type: array + x-kubernetes-list-type: atomic + required: + - nodeSelectorTerms + type: object + x-kubernetes-map-type: atomic + type: object + podAffinity: + description: Describes pod affinity scheduling rules (e.g. co-locate this pod in the same node, zone, etc. as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: The weights of all of the matched WeightedPodAffinityTerm fields are added per-node to find the most preferred node(s) + properties: + podAffinityTerm: + description: Required. A pod affinity term, associated with the corresponding weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + podAntiAffinity: + description: Describes pod anti-affinity scheduling rules (e.g. avoid putting this pod in the same node, zone, etc. as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the anti-affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling anti-affinity expressions, etc.), + compute a sum by iterating through the elements of this field and subtracting + "weight" from the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: The weights of all of the matched WeightedPodAffinityTerm fields are added per-node to find the most preferred node(s) + properties: + podAffinityTerm: + description: Required. A pod affinity term, associated with the corresponding weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the anti-affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the anti-affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + type: object + imagePullSecrets: + description: If specified, the pod's imagePullSecrets + items: + description: |- + LocalObjectReference contains enough information to let you locate the + referenced object inside the same namespace. + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + type: object + x-kubernetes-map-type: atomic + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + nodeSelector: + additionalProperties: + type: string + description: |- + 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/ + type: object + priorityClassName: + description: If specified, the pod's priorityClassName. + type: string + resources: + description: |- + If specified, the pod's resource requirements. + These values override the global resource configuration flags. + Note that when only specifying resource limits, ensure they are greater than or equal + to the corresponding global resource requests configured via controller flags + (--acme-http01-solver-resource-request-cpu, --acme-http01-solver-resource-request-memory). + Kubernetes will reject pod creation if limits are lower than requests, causing challenge failures. + properties: + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to the global values configured via controller flags. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + securityContext: + description: If specified, the pod's security context + properties: + fsGroup: + description: |- + A special supplemental group that applies to all containers in a pod. + Some volume types allow the Kubelet to change the ownership of that volume + to be owned by the pod: + + 1. The owning GID will be the FSGroup + 2. The setgid bit is set (new files created in the volume will be owned by FSGroup) + 3. The permission bits are OR'd with rw-rw---- + + If unset, the Kubelet will not modify the ownership and permissions of any volume. + Note that this field cannot be set when spec.os.name is windows. + format: int64 + type: integer + fsGroupChangePolicy: + description: |- + fsGroupChangePolicy defines behavior of changing ownership and permission of the volume + before being exposed inside Pod. This field will only apply to + volume types which support fsGroup based ownership(and permissions). + It will have no effect on ephemeral volume types such as: secret, configmaps + and emptydir. + Valid values are "OnRootMismatch" and "Always". If not specified, "Always" is used. + Note that this field cannot be set when spec.os.name is windows. + type: string + runAsGroup: + description: |- + The GID to run the entrypoint of the container process. + Uses runtime default if unset. + May also be set in SecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext takes precedence + for that container. + Note that this field cannot be set when spec.os.name is windows. + format: int64 + type: integer + runAsNonRoot: + description: |- + Indicates that the container must run as a non-root user. + If true, the Kubelet will validate the image at runtime to ensure that it + does not run as UID 0 (root) and fail to start the container if it does. + If unset or false, no such validation will be performed. + May also be set in SecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext takes precedence. + type: boolean + runAsUser: + description: |- + The UID to run the entrypoint of the container process. + Defaults to user specified in image metadata if unspecified. + May also be set in SecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext takes precedence + for that container. + Note that this field cannot be set when spec.os.name is windows. + format: int64 + type: integer + seLinuxOptions: + description: |- + The SELinux context to be applied to all containers. + If unspecified, the container runtime will allocate a random SELinux context for each + container. May also be set in SecurityContext. If set in + both SecurityContext and PodSecurityContext, the value specified in SecurityContext + takes precedence for that container. + Note that this field cannot be set when spec.os.name is windows. + properties: + level: + description: Level is SELinux level label that applies to the container. + type: string + role: + description: Role is a SELinux role label that applies to the container. + type: string + type: + description: Type is a SELinux type label that applies to the container. + type: string + user: + description: User is a SELinux user label that applies to the container. + type: string + type: object + seccompProfile: + description: |- + The seccomp options to use by the containers in this pod. + Note that this field cannot be set when spec.os.name is windows. + properties: + localhostProfile: + description: |- + localhostProfile indicates a profile defined in a file on the node should be used. + The profile must be preconfigured on the node to work. + Must be a descending path, relative to the kubelet's configured seccomp profile location. + Must be set if type is "Localhost". Must NOT be set for any other type. + type: string + type: + description: |- + type indicates which kind of seccomp profile will be applied. + Valid options are: + + Localhost - a profile defined in a file on the node should be used. + RuntimeDefault - the container runtime default profile should be used. + Unconfined - no profile should be applied. + type: string + required: + - type + type: object + supplementalGroups: + description: |- + A list of groups applied to the first process run in each container, in addition + to the container's primary GID, the fsGroup (if specified), and group memberships + defined in the container image for the uid of the container process. If unspecified, + no additional groups are added to any container. Note that group memberships + defined in the container image for the uid of the container process are still effective, + even if they are not included in this list. + Note that this field cannot be set when spec.os.name is windows. + items: + format: int64 + type: integer + type: array + x-kubernetes-list-type: atomic + sysctls: + description: |- + Sysctls hold a list of namespaced sysctls used for the pod. Pods with unsupported + sysctls (by the container runtime) might fail to launch. + Note that this field cannot be set when spec.os.name is windows. + items: + description: Sysctl defines a kernel parameter to be set + properties: + name: + description: Name of a property to set + type: string + value: + description: Value of a property to set + type: string + required: + - name + - value + type: object + type: array + x-kubernetes-list-type: atomic + type: object + serviceAccountName: + description: If specified, the pod's service account + type: string + tolerations: + description: If specified, the pod's tolerations. + items: + description: |- + The pod this Toleration is attached to tolerates any taint that matches + the triple using the matching operator . + properties: + effect: + description: |- + Effect indicates the taint effect to match. Empty means match all taint effects. + When specified, allowed values are NoSchedule, PreferNoSchedule and NoExecute. + type: string + key: + description: |- + Key is the taint key that the toleration applies to. Empty means match all taint keys. + If the key is empty, operator must be Exists; this combination means to match all values and all keys. + type: string + operator: + description: |- + Operator represents a key's relationship to the value. + Valid operators are Exists and Equal. Defaults to Equal. + Exists is equivalent to wildcard for value, so that a pod can + tolerate all taints of a particular category. + type: string + tolerationSeconds: + description: |- + TolerationSeconds represents the period of time the toleration (which must be + of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default, + it is not set, which means tolerate the taint forever (do not evict). Zero and + negative values will be treated as 0 (evict immediately) by the system. + format: int64 + type: integer + value: + description: |- + Value is the taint value the toleration matches to. + If the operator is Exists, the value should be empty, otherwise just a regular string. + type: string + type: object + type: array + x-kubernetes-list-type: atomic + type: object + type: object + serviceType: + description: |- + Optional service type for Kubernetes solver service. Supported values + are NodePort or ClusterIP. If unset, defaults to NodePort. + type: string + type: object + ingress: + description: |- + 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. + properties: + class: + description: |- + 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. + type: string + ingressClassName: + description: |- + 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. + type: string + ingressTemplate: + description: |- + Optional ingress template used to configure the ACME challenge solver + ingress used for HTTP01 challenges. + properties: + metadata: + description: |- + 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. + properties: + annotations: + additionalProperties: + type: string + description: Annotations that should be added to the created ACME HTTP01 solver ingress. + type: object + labels: + additionalProperties: + type: string + description: Labels that should be added to the created ACME HTTP01 solver ingress. + type: object + type: object + type: object + name: + description: |- + 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. + type: string + podTemplate: + description: |- + Optional pod template used to configure the ACME challenge solver pods + used for HTTP01 challenges. + properties: + metadata: + description: |- + 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. + properties: + annotations: + additionalProperties: + type: string + description: Annotations that should be added to the created ACME HTTP01 solver pods. + type: object + labels: + additionalProperties: + type: string + description: Labels that should be added to the created ACME HTTP01 solver pods. + type: object + type: object + spec: + description: |- + PodSpec defines overrides for the HTTP01 challenge solver pod. + Check ACMEChallengeSolverHTTP01IngressPodSpec to find out currently supported fields. + All other fields will be ignored. + properties: + affinity: + description: If specified, the pod's scheduling constraints + properties: + nodeAffinity: + description: Describes node affinity scheduling rules for the pod. + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node matches the corresponding matchExpressions; the + node(s) with the highest sum are the most preferred. + items: + description: |- + An empty preferred scheduling term matches all objects with implicit weight 0 + (i.e. it's a no-op). A null preferred scheduling term matches no objects (i.e. is also a no-op). + properties: + preference: + description: A node selector term, associated with the corresponding weight. + properties: + matchExpressions: + description: A list of node selector requirements by node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: The label key that the selector applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: A list of node selector requirements by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: The label key that the selector applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + weight: + description: Weight associated with matching the corresponding nodeSelectorTerm, in the range 1-100. + format: int32 + type: integer + required: + - preference + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to an update), the system + may or may not try to eventually evict the pod from its node. + properties: + nodeSelectorTerms: + description: Required. A list of node selector terms. The terms are ORed. + items: + description: |- + A null or empty node selector term matches no objects. The requirements of + them are ANDed. + The TopologySelectorTerm type implements a subset of the NodeSelectorTerm. + properties: + matchExpressions: + description: A list of node selector requirements by node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: The label key that the selector applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: A list of node selector requirements by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: The label key that the selector applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + type: array + x-kubernetes-list-type: atomic + required: + - nodeSelectorTerms + type: object + x-kubernetes-map-type: atomic + type: object + podAffinity: + description: Describes pod affinity scheduling rules (e.g. co-locate this pod in the same node, zone, etc. as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: The weights of all of the matched WeightedPodAffinityTerm fields are added per-node to find the most preferred node(s) + properties: + podAffinityTerm: + description: Required. A pod affinity term, associated with the corresponding weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + podAntiAffinity: + description: Describes pod anti-affinity scheduling rules (e.g. avoid putting this pod in the same node, zone, etc. as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the anti-affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling anti-affinity expressions, etc.), + compute a sum by iterating through the elements of this field and subtracting + "weight" from the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: The weights of all of the matched WeightedPodAffinityTerm fields are added per-node to find the most preferred node(s) + properties: + podAffinityTerm: + description: Required. A pod affinity term, associated with the corresponding weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the anti-affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the anti-affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + type: object + imagePullSecrets: + description: If specified, the pod's imagePullSecrets + items: + description: |- + LocalObjectReference contains enough information to let you locate the + referenced object inside the same namespace. + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + type: object + x-kubernetes-map-type: atomic + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + nodeSelector: + additionalProperties: + type: string + description: |- + 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/ + type: object + priorityClassName: + description: If specified, the pod's priorityClassName. + type: string + resources: + description: |- + If specified, the pod's resource requirements. + These values override the global resource configuration flags. + Note that when only specifying resource limits, ensure they are greater than or equal + to the corresponding global resource requests configured via controller flags + (--acme-http01-solver-resource-request-cpu, --acme-http01-solver-resource-request-memory). + Kubernetes will reject pod creation if limits are lower than requests, causing challenge failures. + properties: + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to the global values configured via controller flags. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + securityContext: + description: If specified, the pod's security context + properties: + fsGroup: + description: |- + A special supplemental group that applies to all containers in a pod. + Some volume types allow the Kubelet to change the ownership of that volume + to be owned by the pod: + + 1. The owning GID will be the FSGroup + 2. The setgid bit is set (new files created in the volume will be owned by FSGroup) + 3. The permission bits are OR'd with rw-rw---- + + If unset, the Kubelet will not modify the ownership and permissions of any volume. + Note that this field cannot be set when spec.os.name is windows. + format: int64 + type: integer + fsGroupChangePolicy: + description: |- + fsGroupChangePolicy defines behavior of changing ownership and permission of the volume + before being exposed inside Pod. This field will only apply to + volume types which support fsGroup based ownership(and permissions). + It will have no effect on ephemeral volume types such as: secret, configmaps + and emptydir. + Valid values are "OnRootMismatch" and "Always". If not specified, "Always" is used. + Note that this field cannot be set when spec.os.name is windows. + type: string + runAsGroup: + description: |- + The GID to run the entrypoint of the container process. + Uses runtime default if unset. + May also be set in SecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext takes precedence + for that container. + Note that this field cannot be set when spec.os.name is windows. + format: int64 + type: integer + runAsNonRoot: + description: |- + Indicates that the container must run as a non-root user. + If true, the Kubelet will validate the image at runtime to ensure that it + does not run as UID 0 (root) and fail to start the container if it does. + If unset or false, no such validation will be performed. + May also be set in SecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext takes precedence. + type: boolean + runAsUser: + description: |- + The UID to run the entrypoint of the container process. + Defaults to user specified in image metadata if unspecified. + May also be set in SecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext takes precedence + for that container. + Note that this field cannot be set when spec.os.name is windows. + format: int64 + type: integer + seLinuxOptions: + description: |- + The SELinux context to be applied to all containers. + If unspecified, the container runtime will allocate a random SELinux context for each + container. May also be set in SecurityContext. If set in + both SecurityContext and PodSecurityContext, the value specified in SecurityContext + takes precedence for that container. + Note that this field cannot be set when spec.os.name is windows. + properties: + level: + description: Level is SELinux level label that applies to the container. + type: string + role: + description: Role is a SELinux role label that applies to the container. + type: string + type: + description: Type is a SELinux type label that applies to the container. + type: string + user: + description: User is a SELinux user label that applies to the container. + type: string + type: object + seccompProfile: + description: |- + The seccomp options to use by the containers in this pod. + Note that this field cannot be set when spec.os.name is windows. + properties: + localhostProfile: + description: |- + localhostProfile indicates a profile defined in a file on the node should be used. + The profile must be preconfigured on the node to work. + Must be a descending path, relative to the kubelet's configured seccomp profile location. + Must be set if type is "Localhost". Must NOT be set for any other type. + type: string + type: + description: |- + type indicates which kind of seccomp profile will be applied. + Valid options are: + + Localhost - a profile defined in a file on the node should be used. + RuntimeDefault - the container runtime default profile should be used. + Unconfined - no profile should be applied. + type: string + required: + - type + type: object + supplementalGroups: + description: |- + A list of groups applied to the first process run in each container, in addition + to the container's primary GID, the fsGroup (if specified), and group memberships + defined in the container image for the uid of the container process. If unspecified, + no additional groups are added to any container. Note that group memberships + defined in the container image for the uid of the container process are still effective, + even if they are not included in this list. + Note that this field cannot be set when spec.os.name is windows. + items: + format: int64 + type: integer + type: array + x-kubernetes-list-type: atomic + sysctls: + description: |- + Sysctls hold a list of namespaced sysctls used for the pod. Pods with unsupported + sysctls (by the container runtime) might fail to launch. + Note that this field cannot be set when spec.os.name is windows. + items: + description: Sysctl defines a kernel parameter to be set + properties: + name: + description: Name of a property to set + type: string + value: + description: Value of a property to set + type: string + required: + - name + - value + type: object + type: array + x-kubernetes-list-type: atomic + type: object + serviceAccountName: + description: If specified, the pod's service account + type: string + tolerations: + description: If specified, the pod's tolerations. + items: + description: |- + The pod this Toleration is attached to tolerates any taint that matches + the triple using the matching operator . + properties: + effect: + description: |- + Effect indicates the taint effect to match. Empty means match all taint effects. + When specified, allowed values are NoSchedule, PreferNoSchedule and NoExecute. + type: string + key: + description: |- + Key is the taint key that the toleration applies to. Empty means match all taint keys. + If the key is empty, operator must be Exists; this combination means to match all values and all keys. + type: string + operator: + description: |- + Operator represents a key's relationship to the value. + Valid operators are Exists and Equal. Defaults to Equal. + Exists is equivalent to wildcard for value, so that a pod can + tolerate all taints of a particular category. + type: string + tolerationSeconds: + description: |- + TolerationSeconds represents the period of time the toleration (which must be + of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default, + it is not set, which means tolerate the taint forever (do not evict). Zero and + negative values will be treated as 0 (evict immediately) by the system. + format: int64 + type: integer + value: + description: |- + Value is the taint value the toleration matches to. + If the operator is Exists, the value should be empty, otherwise just a regular string. + type: string + type: object + type: array + x-kubernetes-list-type: atomic + type: object + type: object + serviceType: + description: |- + Optional service type for Kubernetes solver service. Supported values + are NodePort or ClusterIP. If unset, defaults to NodePort. + type: string + type: object + type: object + selector: + description: |- + 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. + properties: + dnsNames: + description: |- + 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. + items: + type: string + type: array + x-kubernetes-list-type: atomic + dnsZones: + description: |- + 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. + items: + type: string + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + A label selector that is used to refine the set of certificate's that + this challenge solver will apply to. + type: object + type: object + type: object + token: + description: |- + The ACME challenge token for this challenge. + This is the raw value returned from the ACME server. + type: string + type: + description: |- + The type of ACME challenge this resource represents. + One of "HTTP-01" or "DNS-01". + enum: + - HTTP-01 + - DNS-01 + type: string + url: + description: |- + The URL of the ACME Challenge resource for this challenge. + This can be used to lookup details about the status of this challenge. + type: string + wildcard: + description: |- + wildcard will be true if this challenge is for a wildcard identifier, + for example '*.example.com'. + type: boolean + required: + - authorizationURL + - dnsName + - issuerRef + - key + - solver + - token + - type + - url + type: object + status: + properties: + presented: + description: |- + 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). + type: boolean + processing: + description: |- + 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. + type: boolean + reason: + description: |- + Contains human readable information on why the Challenge is in the + current state. + type: string + state: + description: |- + Contains the current 'state' of the challenge. + If not set, the state of the challenge is unknown. + enum: + - valid + - ready + - pending + - processing + - invalid + - expired + - errored + type: string + type: object + required: + - metadata + - spec + type: object + served: true + storage: true + subresources: + status: {} +{{- end }} diff --git a/deploy/charts/cert-manager/templates/crd-acme.cert-manager.io_orders.yaml b/deploy/charts/cert-manager/templates/crd-acme.cert-manager.io_orders.yaml new file mode 100644 index 00000000000..3242fc41c23 --- /dev/null +++ b/deploy/charts/cert-manager/templates/crd-acme.cert-manager.io_orders.yaml @@ -0,0 +1,274 @@ +{{- if or .Values.crds.enabled .Values.installCRDs }} +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: "orders.acme.cert-manager.io" + {{- if .Values.crds.keep }} + annotations: + helm.sh/resource-policy: keep + {{- end }} + labels: + {{- include "cert-manager.crd-labels" . | nindent 4 }} +spec: + group: acme.cert-manager.io + names: + categories: + - cert-manager + - cert-manager-acme + kind: Order + listKind: OrderList + plural: orders + singular: order + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .status.state + name: State + type: string + - jsonPath: .spec.issuerRef.name + name: Issuer + priority: 1 + type: string + - jsonPath: .status.reason + name: Reason + priority: 1 + type: string + - description: CreationTimestamp is a timestamp representing the server time when this object was created. It is not guaranteed to be set in happens-before order across separate operations. Clients may not set this value. It is represented in RFC3339 form and is in UTC. + jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1 + schema: + openAPIV3Schema: + description: Order is a type to represent an Order with an ACME server + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + properties: + commonName: + description: |- + 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. + type: string + dnsNames: + description: |- + 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. + items: + type: string + type: array + x-kubernetes-list-type: atomic + duration: + description: |- + Duration is the duration for the not after date for the requested certificate. + this is set on order creation as pe the ACME spec. + type: string + ipAddresses: + description: |- + 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. + items: + type: string + type: array + x-kubernetes-list-type: atomic + issuerRef: + description: |- + 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. + properties: + group: + description: |- + Group of the issuer being referred to. + Defaults to 'cert-manager.io'. + type: string + kind: + description: |- + Kind of the issuer being referred to. + Defaults to 'Issuer'. + type: string + name: + description: Name of the issuer being referred to. + type: string + required: + - name + type: object + profile: + description: |- + Profile allows requesting a certificate profile from the ACME server. + Supported profiles are listed by the server's ACME directory URL. + type: string + request: + description: |- + Certificate signing request bytes in DER encoding. + This will be used when finalizing the order. + This field must be set on the order. + format: byte + type: string + required: + - issuerRef + - request + type: object + status: + properties: + authorizations: + description: |- + 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. + items: + description: |- + 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. + properties: + challenges: + description: |- + 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. + items: + description: |- + 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. + properties: + token: + description: |- + 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: + description: |- + 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. + type: string + url: + description: |- + URL is the URL of this challenge. It can be used to retrieve additional + metadata about the Challenge from the ACME server. + type: string + required: + - token + - type + - url + type: object + type: array + x-kubernetes-list-type: atomic + identifier: + description: Identifier is the DNS name to be validated as part of this authorization + type: string + initialState: + description: |- + 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. + enum: + - valid + - ready + - pending + - processing + - invalid + - expired + - errored + type: string + url: + description: URL is the URL of the Authorization that must be completed + type: string + wildcard: + description: |- + 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'. + type: boolean + required: + - url + type: object + type: array + x-kubernetes-list-type: atomic + certificate: + description: |- + 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. + format: byte + type: string + failureTime: + description: |- + FailureTime stores the time that this order failed. + This is used to influence garbage collection and back-off. + format: date-time + type: string + finalizeURL: + description: |- + FinalizeURL of the Order. + This is used to obtain certificates for this order once it has been completed. + type: string + reason: + description: |- + Reason optionally provides more information about a why the order is in + the current state. + type: string + state: + description: |- + State contains the current state of this Order resource. + States 'success' and 'expired' are 'final' + enum: + - valid + - ready + - pending + - processing + - invalid + - expired + - errored + type: string + url: + description: |- + 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. + type: string + type: object + required: + - metadata + - spec + type: object + served: true + storage: true + subresources: + status: {} +{{- end }} diff --git a/deploy/charts/cert-manager/templates/crd-cert-manager.io_certificaterequests.yaml b/deploy/charts/cert-manager/templates/crd-cert-manager.io_certificaterequests.yaml new file mode 100644 index 00000000000..e25ad1d0331 --- /dev/null +++ b/deploy/charts/cert-manager/templates/crd-cert-manager.io_certificaterequests.yaml @@ -0,0 +1,319 @@ +{{- if or .Values.crds.enabled .Values.installCRDs }} +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: "certificaterequests.cert-manager.io" + {{- if .Values.crds.keep }} + annotations: + helm.sh/resource-policy: keep + {{- end }} + labels: + {{- include "cert-manager.crd-labels" . | nindent 4 }} +spec: + group: cert-manager.io + names: + categories: + - cert-manager + kind: CertificateRequest + listKind: CertificateRequestList + plural: certificaterequests + shortNames: + - cr + - crs + singular: certificaterequest + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .status.conditions[?(@.type == "Approved")].status + name: Approved + type: string + - jsonPath: .status.conditions[?(@.type == "Denied")].status + name: Denied + type: string + - jsonPath: .status.conditions[?(@.type == "Ready")].status + name: Ready + type: string + - jsonPath: .spec.issuerRef.name + name: Issuer + type: string + - jsonPath: .spec.username + name: Requester + type: string + - jsonPath: .status.conditions[?(@.type == "Ready")].message + name: Status + priority: 1 + type: string + - description: CreationTimestamp is a timestamp representing the server time when this object was created. It is not guaranteed to be set in happens-before order across separate operations. Clients may not set this value. It is represented in RFC3339 form and is in UTC. + jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1 + schema: + openAPIV3Schema: + description: |- + 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. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: |- + Specification of the desired state of the CertificateRequest resource. + https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status + properties: + duration: + description: |- + 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. + type: string + extra: + additionalProperties: + items: + type: string + type: array + description: |- + Extra contains extra attributes of the user that created the CertificateRequest. + Populated by the cert-manager webhook on creation and immutable. + type: object + groups: + description: |- + Groups contains group membership of the user that created the CertificateRequest. + Populated by the cert-manager webhook on creation and immutable. + items: + type: string + type: array + x-kubernetes-list-type: atomic + isCA: + description: |- + 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`. + type: boolean + issuerRef: + description: |- + 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. + properties: + group: + description: |- + Group of the issuer being referred to. + Defaults to 'cert-manager.io'. + type: string + kind: + description: |- + Kind of the issuer being referred to. + Defaults to 'Issuer'. + type: string + name: + description: Name of the issuer being referred to. + type: string + required: + - name + type: object + request: + description: |- + 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. + format: byte + type: string + uid: + description: |- + UID contains the uid of the user that created the CertificateRequest. + Populated by the cert-manager webhook on creation and immutable. + type: string + usages: + description: |- + 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`. + items: + description: |- + 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" + enum: + - 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 + type: string + type: array + x-kubernetes-list-type: atomic + username: + description: |- + Username contains the name of the user that created the CertificateRequest. + Populated by the cert-manager webhook on creation and immutable. + type: string + required: + - issuerRef + - request + type: object + status: + description: |- + 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 + properties: + ca: + description: |- + 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. + format: byte + type: string + certificate: + description: |- + 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. + format: byte + type: string + conditions: + description: |- + List of status conditions to indicate the status of a CertificateRequest. + Known condition types are `Ready`, `InvalidRequest`, `Approved` and `Denied`. + items: + description: CertificateRequestCondition contains condition information for a CertificateRequest. + properties: + lastTransitionTime: + description: |- + LastTransitionTime is the timestamp corresponding to the last status + change of this condition. + format: date-time + type: string + message: + description: |- + Message is a human readable description of the details of the last + transition, complementing reason. + type: string + reason: + description: |- + Reason is a brief machine readable explanation for the condition's last + transition. + type: string + status: + description: Status of the condition, one of (`True`, `False`, `Unknown`). + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: |- + Type of the condition, known values are (`Ready`, `InvalidRequest`, + `Approved`, `Denied`). + type: string + required: + - status + - type + type: object + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + failureTime: + description: |- + FailureTime stores the time that this CertificateRequest failed. This is + used to influence garbage collection and back-off. + format: date-time + type: string + type: object + type: object + served: true + storage: true + subresources: + status: {} +{{- end }} diff --git a/deploy/charts/cert-manager/templates/crd-cert-manager.io_certificates.yaml b/deploy/charts/cert-manager/templates/crd-cert-manager.io_certificates.yaml new file mode 100644 index 00000000000..6689de66e15 --- /dev/null +++ b/deploy/charts/cert-manager/templates/crd-cert-manager.io_certificates.yaml @@ -0,0 +1,816 @@ +{{- if or .Values.crds.enabled .Values.installCRDs }} +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: "certificates.cert-manager.io" + {{- if .Values.crds.keep }} + annotations: + helm.sh/resource-policy: keep + {{- end }} + labels: + {{- include "cert-manager.crd-labels" . | nindent 4 }} +spec: + group: cert-manager.io + names: + categories: + - cert-manager + kind: Certificate + listKind: CertificateList + plural: certificates + shortNames: + - cert + - certs + singular: certificate + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .status.conditions[?(@.type == "Ready")].status + name: Ready + type: string + - jsonPath: .spec.secretName + name: Secret + type: string + - jsonPath: .spec.issuerRef.name + name: Issuer + priority: 1 + type: string + - jsonPath: .status.conditions[?(@.type == "Ready")].message + name: Status + priority: 1 + type: string + - description: CreationTimestamp is a timestamp representing the server time when this object was created. It is not guaranteed to be set in happens-before order across separate operations. Clients may not set this value. It is represented in RFC3339 form and is in UTC. + jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1 + schema: + openAPIV3Schema: + description: |- + 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`). + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: |- + Specification of the desired state of the Certificate resource. + https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status + properties: + additionalOutputFormats: + description: |- + Defines extra output formats of the private key and signed certificate chain + to be written to this Certificate's target Secret. + items: + description: |- + CertificateAdditionalOutputFormat defines an additional output format of a + Certificate resource. These contain supplementary data formats of the signed + certificate chain and paired private key. + properties: + type: + description: |- + Type is the name of the format type that should be written to the + Certificate's target Secret. + enum: + - DER + - CombinedPEM + type: string + required: + - type + type: object + type: array + x-kubernetes-list-type: atomic + commonName: + description: |- + 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. + type: string + dnsNames: + description: Requested DNS subject alternative names. + items: + type: string + type: array + x-kubernetes-list-type: atomic + duration: + description: |- + 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. + type: string + emailAddresses: + description: Requested email subject alternative names. + items: + type: string + type: array + x-kubernetes-list-type: atomic + encodeUsagesInRequest: + description: |- + 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. + type: boolean + ipAddresses: + description: Requested IP address subject alternative names. + items: + type: string + type: array + x-kubernetes-list-type: atomic + isCA: + description: |- + 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`. + type: boolean + issuerRef: + description: |- + 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. + properties: + group: + description: |- + Group of the issuer being referred to. + Defaults to 'cert-manager.io'. + type: string + kind: + description: |- + Kind of the issuer being referred to. + Defaults to 'Issuer'. + type: string + name: + description: Name of the issuer being referred to. + type: string + required: + - name + type: object + keystores: + description: Additional keystore output formats to be stored in the Certificate's Secret. + properties: + jks: + description: |- + JKS configures options for storing a JKS keystore in the + `spec.secretName` Secret resource. + properties: + alias: + description: |- + Alias specifies the alias of the key in the keystore, required by the JKS format. + If not provided, the default alias `certificate` will be used. + type: string + create: + description: |- + 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` or `password`. + 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 + type: boolean + password: + description: |- + Password provides a literal password used to encrypt the JKS keystore. + Mutually exclusive with passwordSecretRef. + One of password or passwordSecretRef must provide a password with a non-zero length. + type: string + passwordSecretRef: + description: |- + PasswordSecretRef is a reference to a non-empty key in a Secret resource + containing the password used to encrypt the JKS keystore. + Mutually exclusive with password. + One of password or passwordSecretRef must provide a password with a non-zero length. + properties: + key: + description: |- + 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. + type: string + name: + description: |- + Name of the resource being referred to. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + required: + - name + type: object + required: + - create + type: object + pkcs12: + description: |- + PKCS12 configures options for storing a PKCS12 keystore in the + `spec.secretName` Secret resource. + properties: + create: + description: |- + 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` or in `password`. + 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 + type: boolean + password: + description: |- + Password provides a literal password used to encrypt the PKCS#12 keystore. + Mutually exclusive with passwordSecretRef. + One of password or passwordSecretRef must provide a password with a non-zero length. + type: string + passwordSecretRef: + description: |- + PasswordSecretRef is a reference to a non-empty key in a Secret resource + containing the password used to encrypt the PKCS#12 keystore. + Mutually exclusive with password. + One of password or passwordSecretRef must provide a password with a non-zero length. + properties: + key: + description: |- + 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. + type: string + name: + description: |- + Name of the resource being referred to. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + required: + - name + type: object + profile: + description: |- + Profile specifies the key and certificate encryption algorithms and the HMAC algorithm + used to create the PKCS12 keystore. Default value is `LegacyRC2` for backward compatibility. + + If provided, allowed values are: + `LegacyRC2`: Deprecated. Not supported by default in OpenSSL 3 or Java 20. + `LegacyDES`: Less secure algorithm. Use this option for maximal compatibility. + `Modern2023`: Secure algorithm. Use this option in case you have to always use secure algorithms + (e.g., because of company policy). Please note that the security of the algorithm is not that important + in reality, because the unencrypted certificate and private key are also stored in the Secret. + enum: + - LegacyRC2 + - LegacyDES + - Modern2023 + type: string + required: + - create + type: object + type: object + literalSubject: + description: |- + 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. + type: string + nameConstraints: + description: |- + x.509 certificate NameConstraint extension which MUST NOT be used in a non-CA certificate. + More Info: https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.10 + + This is an Alpha Feature and is only enabled with the + `--feature-gates=NameConstraints=true` option set on both + the controller and webhook components. + properties: + critical: + description: if true then the name constraints are marked critical. + type: boolean + excluded: + description: |- + Excluded contains the constraints which must be disallowed. Any name matching a + restriction in the excluded field is invalid regardless + of information appearing in the permitted + properties: + dnsDomains: + description: DNSDomains is a list of DNS domains that are permitted or excluded. + items: + type: string + type: array + x-kubernetes-list-type: atomic + emailAddresses: + description: EmailAddresses is a list of Email Addresses that are permitted or excluded. + items: + type: string + type: array + x-kubernetes-list-type: atomic + ipRanges: + description: |- + IPRanges is a list of IP Ranges that are permitted or excluded. + This should be a valid CIDR notation. + items: + type: string + type: array + x-kubernetes-list-type: atomic + uriDomains: + description: URIDomains is a list of URI domains that are permitted or excluded. + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + permitted: + description: Permitted contains the constraints in which the names must be located. + properties: + dnsDomains: + description: DNSDomains is a list of DNS domains that are permitted or excluded. + items: + type: string + type: array + x-kubernetes-list-type: atomic + emailAddresses: + description: EmailAddresses is a list of Email Addresses that are permitted or excluded. + items: + type: string + type: array + x-kubernetes-list-type: atomic + ipRanges: + description: |- + IPRanges is a list of IP Ranges that are permitted or excluded. + This should be a valid CIDR notation. + items: + type: string + type: array + x-kubernetes-list-type: atomic + uriDomains: + description: URIDomains is a list of URI domains that are permitted or excluded. + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + type: object + otherNames: + description: |- + `otherNames` is an escape hatch for SAN that allows any type. We currently restrict the support to string like otherNames, cf RFC 5280 p 37 + Any UTF8 String valued otherName can be passed with by setting the keys oid: x.x.x.x and UTF8Value: somevalue for `otherName`. + Most commonly this would be UPN set with oid: 1.3.6.1.4.1.311.20.2.3 + You should ensure that any OID passed is valid for the UTF8String type as we do not explicitly validate this. + items: + properties: + oid: + description: |- + OID is the object identifier for the otherName SAN. + The object identifier must be expressed as a dotted string, for + example, "1.2.840.113556.1.4.221". + type: string + utf8Value: + description: |- + utf8Value is the string value of the otherName SAN. + The utf8Value accepts any valid UTF8 string to set as value for the otherName SAN. + type: string + type: object + type: array + x-kubernetes-list-type: atomic + privateKey: + description: |- + Private key options. These include the key algorithm and size, the used + encoding and the rotation policy. + properties: + algorithm: + description: |- + 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. + enum: + - RSA + - ECDSA + - Ed25519 + type: string + encoding: + description: |- + 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. + enum: + - PKCS1 + - PKCS8 + type: string + rotationPolicy: + description: |- + 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 exist 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 `Always`. + The default was changed from `Never` to `Always` in cert-manager >=v1.18.0. + The new default can be disabled by setting the + `--feature-gates=DefaultPrivateKeyRotationPolicyAlways=false` option on + the controller component. + enum: + - Never + - Always + type: string + size: + description: |- + 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. + type: integer + type: object + renewBefore: + description: |- + 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 1/3 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. + Cannot be set if the `renewBeforePercentage` field is set. + type: string + renewBeforePercentage: + description: |- + `renewBeforePercentage` is like `renewBefore`, except it is a relative percentage + rather than an absolute duration. For example, if a certificate is valid for 60 + minutes, and `renewBeforePercentage=25`, cert-manager will begin to attempt to + renew the certificate 45 minutes after it was issued (i.e. when there are 15 + minutes (25%) 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. + + Value must be an integer in the range (0,100). The minimum effective + `renewBefore` derived from the `renewBeforePercentage` and `duration` fields is 5 + minutes. + Cannot be set if the `renewBefore` field is set. + format: int32 + type: integer + revisionHistoryLimit: + description: |- + 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. + Default value is `1`. + format: int32 + type: integer + secretName: + description: |- + 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. + type: string + secretTemplate: + description: |- + 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. + properties: + annotations: + additionalProperties: + type: string + description: Annotations is a key value map to be copied to the target Kubernetes Secret. + type: object + labels: + additionalProperties: + type: string + description: Labels is a key value map to be copied to the target Kubernetes Secret. + type: object + type: object + signatureAlgorithm: + description: |- + Signature algorithm to use. + Allowed values for RSA keys: SHA256WithRSA, SHA384WithRSA, SHA512WithRSA. + Allowed values for ECDSA keys: ECDSAWithSHA256, ECDSAWithSHA384, ECDSAWithSHA512. + Allowed values for Ed25519 keys: PureEd25519. + enum: + - SHA256WithRSA + - SHA384WithRSA + - SHA512WithRSA + - ECDSAWithSHA256 + - ECDSAWithSHA384 + - ECDSAWithSHA512 + - PureEd25519 + type: string + subject: + description: |- + 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. + properties: + countries: + description: Countries to be used on the Certificate. + items: + type: string + type: array + x-kubernetes-list-type: atomic + localities: + description: Cities to be used on the Certificate. + items: + type: string + type: array + x-kubernetes-list-type: atomic + organizationalUnits: + description: Organizational Units to be used on the Certificate. + items: + type: string + type: array + x-kubernetes-list-type: atomic + organizations: + description: Organizations to be used on the Certificate. + items: + type: string + type: array + x-kubernetes-list-type: atomic + postalCodes: + description: Postal codes to be used on the Certificate. + items: + type: string + type: array + x-kubernetes-list-type: atomic + provinces: + description: State/Provinces to be used on the Certificate. + items: + type: string + type: array + x-kubernetes-list-type: atomic + serialNumber: + description: Serial number to be used on the Certificate. + type: string + streetAddresses: + description: Street addresses to be used on the Certificate. + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + uris: + description: Requested URI subject alternative names. + items: + type: string + type: array + x-kubernetes-list-type: atomic + usages: + description: |- + 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`. + items: + description: |- + 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" + enum: + - 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 + type: string + type: array + x-kubernetes-list-type: atomic + required: + - issuerRef + - secretName + type: object + status: + description: |- + 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 + properties: + conditions: + description: |- + List of status conditions to indicate the status of certificates. + Known condition types are `Ready` and `Issuing`. + items: + description: CertificateCondition contains condition information for a Certificate. + properties: + lastTransitionTime: + description: |- + LastTransitionTime is the timestamp corresponding to the last status + change of this condition. + format: date-time + type: string + message: + description: |- + Message is a human readable description of the details of the last + transition, complementing reason. + type: string + observedGeneration: + description: |- + 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. + format: int64 + type: integer + reason: + description: |- + Reason is a brief machine readable explanation for the condition's last + transition. + type: string + status: + description: Status of the condition, one of (`True`, `False`, `Unknown`). + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: Type of the condition, known values are (`Ready`, `Issuing`). + type: string + required: + - status + - type + type: object + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + failedIssuanceAttempts: + description: |- + 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). + type: integer + lastFailureTime: + description: |- + LastFailureTime is set only if the latest 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. + format: date-time + type: string + nextPrivateKeySecretName: + description: |- + 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. + type: string + notAfter: + description: |- + The expiration time of the certificate stored in the secret named + by this resource in `spec.secretName`. + format: date-time + type: string + notBefore: + description: |- + The time after which the certificate stored in the secret named + by this resource in `spec.secretName` is valid. + format: date-time + type: string + renewalTime: + description: |- + RenewalTime is the time at which the certificate will be next + renewed. + If not set, no upcoming renewal is scheduled. + format: date-time + type: string + revision: + description: |- + 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. + type: integer + type: object + type: object + served: true + storage: true + subresources: + status: {} +{{- end }} diff --git a/deploy/charts/cert-manager/templates/crd-cert-manager.io_clusterissuers.yaml b/deploy/charts/cert-manager/templates/crd-cert-manager.io_clusterissuers.yaml new file mode 100644 index 00000000000..790d4b5c3e0 --- /dev/null +++ b/deploy/charts/cert-manager/templates/crd-cert-manager.io_clusterissuers.yaml @@ -0,0 +1,3815 @@ +{{- if or .Values.crds.enabled .Values.installCRDs }} +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: "clusterissuers.cert-manager.io" + {{- if .Values.crds.keep }} + annotations: + helm.sh/resource-policy: keep + {{- end }} + labels: + {{- include "cert-manager.crd-labels" . | nindent 4 }} +spec: + group: cert-manager.io + names: + categories: + - cert-manager + kind: ClusterIssuer + listKind: ClusterIssuerList + plural: clusterissuers + shortNames: + - ciss + singular: clusterissuer + scope: Cluster + versions: + - additionalPrinterColumns: + - jsonPath: .status.conditions[?(@.type == "Ready")].status + name: Ready + type: string + - jsonPath: .status.conditions[?(@.type == "Ready")].message + name: Status + priority: 1 + type: string + - description: CreationTimestamp is a timestamp representing the server time when this object was created. It is not guaranteed to be set in happens-before order across separate operations. Clients may not set this value. It is represented in RFC3339 form and is in UTC. + jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1 + schema: + openAPIV3Schema: + description: |- + 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. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: Desired state of the ClusterIssuer resource. + properties: + acme: + description: |- + ACME configures this issuer to communicate with a RFC8555 (ACME) server + to obtain signed x509 certificates. + properties: + caBundle: + description: |- + 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. + format: byte + type: string + disableAccountKeyGeneration: + description: |- + 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. + type: boolean + email: + description: |- + 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. + type: string + enableDurationFeature: + description: |- + 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. + type: boolean + externalAccountBinding: + description: |- + 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. + properties: + keyAlgorithm: + description: |- + 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. + enum: + - HS256 + - HS384 + - HS512 + type: string + keyID: + description: keyID is the ID of the CA key that the External Account is bound to. + type: string + keySecretRef: + description: |- + 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. + properties: + key: + description: |- + 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. + type: string + name: + description: |- + Name of the resource being referred to. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + required: + - name + type: object + required: + - keyID + - keySecretRef + type: object + preferredChain: + description: |- + 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 cross-sign 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 combined set of + ACME default and alternative chains that has a root-most certificate with + this value as its issuer's commonname. + maxLength: 64 + type: string + privateKeySecretRef: + description: |- + 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. + properties: + key: + description: |- + 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. + type: string + name: + description: |- + Name of the resource being referred to. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + required: + - name + type: object + profile: + description: |- + Profile allows requesting a certificate profile from the ACME server. + Supported profiles are listed by the server's ACME directory URL. + type: string + server: + description: |- + 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. + type: string + skipTLSVerify: + description: |- + 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. + type: boolean + solvers: + description: |- + 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/ + items: + description: |- + 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. + properties: + dns01: + description: |- + Configures cert-manager to attempt to complete authorizations by + performing the DNS01 challenge flow. + properties: + acmeDNS: + description: |- + Use the 'ACME DNS' (https://github.com/joohoi/acme-dns) API to manage + DNS01 challenge records. + properties: + accountSecretRef: + description: |- + A reference to a specific 'key' within a Secret resource. + In some instances, `key` is a required field. + properties: + key: + description: |- + 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. + type: string + name: + description: |- + Name of the resource being referred to. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + required: + - name + type: object + host: + type: string + required: + - accountSecretRef + - host + type: object + akamai: + description: Use the Akamai DNS zone management API to manage DNS01 challenge records. + properties: + accessTokenSecretRef: + description: |- + A reference to a specific 'key' within a Secret resource. + In some instances, `key` is a required field. + properties: + key: + description: |- + 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. + type: string + name: + description: |- + Name of the resource being referred to. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + required: + - name + type: object + clientSecretSecretRef: + description: |- + A reference to a specific 'key' within a Secret resource. + In some instances, `key` is a required field. + properties: + key: + description: |- + 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. + type: string + name: + description: |- + Name of the resource being referred to. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + required: + - name + type: object + clientTokenSecretRef: + description: |- + A reference to a specific 'key' within a Secret resource. + In some instances, `key` is a required field. + properties: + key: + description: |- + 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. + type: string + name: + description: |- + Name of the resource being referred to. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + required: + - name + type: object + serviceConsumerDomain: + type: string + required: + - accessTokenSecretRef + - clientSecretSecretRef + - clientTokenSecretRef + - serviceConsumerDomain + type: object + azureDNS: + description: Use the Microsoft Azure DNS API to manage DNS01 challenge records. + properties: + clientID: + description: |- + Auth: Azure Service Principal: + The ClientID of the Azure Service Principal used to authenticate with Azure DNS. + If set, ClientSecret and TenantID must also be set. + type: string + clientSecretSecretRef: + description: |- + Auth: Azure Service Principal: + A reference to a Secret containing the password associated with the Service Principal. + If set, ClientID and TenantID must also be set. + properties: + key: + description: |- + 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. + type: string + name: + description: |- + Name of the resource being referred to. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + required: + - name + type: object + environment: + description: name of the Azure environment (default AzurePublicCloud) + enum: + - AzurePublicCloud + - AzureChinaCloud + - AzureGermanCloud + - AzureUSGovernmentCloud + type: string + hostedZoneName: + description: name of the DNS zone that should be used + type: string + managedIdentity: + description: |- + Auth: Azure Workload Identity or Azure Managed Service Identity: + Settings to enable Azure Workload Identity or Azure Managed Service Identity + If set, ClientID, ClientSecret and TenantID must not be set. + properties: + clientID: + description: client ID of the managed identity, cannot be used at the same time as resourceID + type: string + resourceID: + description: |- + resource ID of the managed identity, cannot be used at the same time as clientID + Cannot be used for Azure Managed Service Identity + type: string + tenantID: + description: tenant ID of the managed identity, cannot be used at the same time as resourceID + type: string + type: object + resourceGroupName: + description: resource group the DNS zone is located in + type: string + subscriptionID: + description: ID of the Azure subscription + type: string + tenantID: + description: |- + Auth: Azure Service Principal: + The TenantID of the Azure Service Principal used to authenticate with Azure DNS. + If set, ClientID and ClientSecret must also be set. + type: string + required: + - resourceGroupName + - subscriptionID + type: object + cloudDNS: + description: Use the Google Cloud DNS API to manage DNS01 challenge records. + properties: + hostedZoneName: + description: |- + 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. + type: string + project: + type: string + serviceAccountSecretRef: + description: |- + A reference to a specific 'key' within a Secret resource. + In some instances, `key` is a required field. + properties: + key: + description: |- + 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. + type: string + name: + description: |- + Name of the resource being referred to. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + required: + - name + type: object + required: + - project + type: object + cloudflare: + description: Use the Cloudflare API to manage DNS01 challenge records. + properties: + apiKeySecretRef: + description: |- + 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. + properties: + key: + description: |- + 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. + type: string + name: + description: |- + Name of the resource being referred to. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + required: + - name + type: object + apiTokenSecretRef: + description: API token used to authenticate with Cloudflare. + properties: + key: + description: |- + 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. + type: string + name: + description: |- + Name of the resource being referred to. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + required: + - name + type: object + email: + description: Email of the account, only required when using API key based authentication. + type: string + type: object + cnameStrategy: + description: |- + CNAMEStrategy configures how the DNS01 provider should handle CNAME + records when found in DNS zones. + enum: + - None + - Follow + type: string + digitalocean: + description: Use the DigitalOcean DNS API to manage DNS01 challenge records. + properties: + tokenSecretRef: + description: |- + A reference to a specific 'key' within a Secret resource. + In some instances, `key` is a required field. + properties: + key: + description: |- + 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. + type: string + name: + description: |- + Name of the resource being referred to. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + required: + - name + type: object + required: + - tokenSecretRef + type: object + rfc2136: + description: |- + Use RFC2136 ("Dynamic Updates in the Domain Name System") (https://datatracker.ietf.org/doc/rfc2136/) + to manage DNS01 challenge records. + properties: + nameserver: + description: |- + 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. + type: string + protocol: + description: Protocol to use for dynamic DNS update queries. Valid values are (case-sensitive) ``TCP`` and ``UDP``; ``UDP`` (default). + enum: + - TCP + - UDP + type: string + tsigAlgorithm: + description: |- + 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``. + type: string + tsigKeyName: + description: |- + The TSIG Key name configured in the DNS. + If ``tsigSecretSecretRef`` is defined, this field is required. + type: string + tsigSecretSecretRef: + description: |- + The name of the secret containing the TSIG value. + If ``tsigKeyName`` is defined, this field is required. + properties: + key: + description: |- + 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. + type: string + name: + description: |- + Name of the resource being referred to. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + required: + - name + type: object + required: + - nameserver + type: object + route53: + description: Use the AWS Route53 API to manage DNS01 challenge records. + properties: + accessKeyID: + description: |- + 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 + type: string + accessKeyIDSecretRef: + description: |- + 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 + properties: + key: + description: |- + 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. + type: string + name: + description: |- + Name of the resource being referred to. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + required: + - name + type: object + auth: + description: Auth configures how cert-manager authenticates. + properties: + kubernetes: + description: |- + Kubernetes authenticates with Route53 using AssumeRoleWithWebIdentity + by passing a bound ServiceAccount token. + properties: + serviceAccountRef: + description: |- + A reference to a service account that will be used to request a bound + token (also known as "projected token"). To use this field, you must + configure an RBAC rule to let cert-manager request a token. + properties: + audiences: + description: |- + TokenAudiences is an optional list of audiences to include in the + token passed to AWS. The default token consisting of the issuer's namespace + and name is always included. + If unset the audience defaults to `sts.amazonaws.com`. + items: + type: string + type: array + x-kubernetes-list-type: atomic + name: + description: Name of the ServiceAccount used to request a token. + type: string + required: + - name + type: object + required: + - serviceAccountRef + type: object + required: + - kubernetes + type: object + hostedZoneID: + description: If set, the provider will manage only this zone in Route53 and will not do a lookup using the route53:ListHostedZonesByName api call. + type: string + region: + description: |- + Override the AWS region. + + Route53 is a global service and does not have regional endpoints but the + region specified here (or via environment variables) is used as a hint to + help compute the correct AWS credential scope and partition when it + connects to Route53. See: + - [Amazon Route 53 endpoints and quotas](https://docs.aws.amazon.com/general/latest/gr/r53.html) + - [Global services](https://docs.aws.amazon.com/whitepapers/latest/aws-fault-isolation-boundaries/global-services.html) + + If you omit this region field, cert-manager will use the region from + AWS_REGION and AWS_DEFAULT_REGION environment variables, if they are set + in the cert-manager controller Pod. + + The `region` field is not needed if you use [IAM Roles for Service Accounts (IRSA)](https://docs.aws.amazon.com/eks/latest/userguide/iam-roles-for-service-accounts.html). + Instead an AWS_REGION environment variable is added to the cert-manager controller Pod by: + [Amazon EKS Pod Identity Webhook](https://github.com/aws/amazon-eks-pod-identity-webhook). + In this case this `region` field value is ignored. + + The `region` field is not needed if you use [EKS Pod Identities](https://docs.aws.amazon.com/eks/latest/userguide/pod-identities.html). + Instead an AWS_REGION environment variable is added to the cert-manager controller Pod by: + [Amazon EKS Pod Identity Agent](https://github.com/aws/eks-pod-identity-agent), + In this case this `region` field value is ignored. + type: string + role: + description: |- + 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 + type: string + secretAccessKeySecretRef: + description: |- + 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 + properties: + key: + description: |- + 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. + type: string + name: + description: |- + Name of the resource being referred to. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + required: + - name + type: object + type: object + webhook: + description: |- + Configure an external webhook based DNS01 challenge solver to manage + DNS01 challenge records. + properties: + config: + description: |- + 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. + x-kubernetes-preserve-unknown-fields: true + groupName: + description: |- + 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. + type: string + solverName: + description: |- + 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'. + type: string + required: + - groupName + - solverName + type: object + type: object + http01: + description: |- + 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. + properties: + gatewayHTTPRoute: + description: |- + 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. + properties: + labels: + additionalProperties: + type: string + description: |- + Custom labels that will be applied to HTTPRoutes created by cert-manager + while solving HTTP-01 challenges. + type: object + parentRefs: + description: |- + 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 + items: + description: |- + ParentReference identifies an API object (usually a Gateway) that can be considered + a parent of this resource (usually a route). There are two kinds of parent resources + with "Core" support: + + * Gateway (Gateway conformance profile) + * Service (Mesh conformance profile, ClusterIP Services only) + + This API may be extended in the future to support additional kinds of parent + resources. + + The API object must be valid in the cluster; the Group and Kind must + be registered in the cluster for this reference to be valid. + properties: + group: + default: gateway.networking.k8s.io + description: |- + Group is the group of the referent. + When unspecified, "gateway.networking.k8s.io" is inferred. + To set the core API group (such as for a "Service" kind referent), + Group must be explicitly set to "" (empty string). + + Support: Core + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Gateway + description: |- + Kind is kind of the referent. + + There are two kinds of parent resources with "Core" support: + + * Gateway (Gateway conformance profile) + * Service (Mesh conformance profile, ClusterIP Services only) + + Support for other resources is Implementation-Specific. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: |- + Name is the name of the referent. + + Support: Core + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the referent. When unspecified, this refers + to the local namespace of the Route. + + Note that there are specific rules for ParentRefs which cross namespace + boundaries. Cross-namespace references are only valid if they are explicitly + allowed by something in the namespace they are referring to. For example: + Gateway has the AllowedRoutes field, and ReferenceGrant provides a + generic way to enable any other kind of cross-namespace reference. + + + ParentRefs from a Route to a Service in the same namespace are "producer" + routes, which apply default routing rules to inbound connections from + any namespace to the Service. + + ParentRefs from a Route to a Service in a different namespace are + "consumer" routes, and these routing rules are only applied to outbound + connections originating from the same namespace as the Route, for which + the intended destination of the connections are a Service targeted as a + ParentRef of the Route. + + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: |- + Port is the network port this Route targets. It can be interpreted + differently based on the type of parent resource. + + When the parent resource is a Gateway, this targets all listeners + listening on the specified port that also support this kind of Route(and + select this Route). It's not recommended to set `Port` unless the + networking behaviors specified in a Route must apply to a specific port + as opposed to a listener(s) whose port(s) may be changed. When both Port + and SectionName are specified, the name and port of the selected listener + must match both specified values. + + + When the parent resource is a Service, this targets a specific port in the + Service spec. When both Port (experimental) and SectionName are specified, + the name and port of the selected port must match both specified values. + + + Implementations MAY choose to support other parent resources. + Implementations supporting other types of parent resources MUST clearly + document how/if Port is interpreted. + + For the purpose of status, an attachment is considered successful as + long as the parent resource accepts it partially. For example, Gateway + listeners can restrict which Routes can attach to them by Route kind, + namespace, or hostname. If 1 of 2 Gateway listeners accept attachment + from the referencing Route, the Route MUST be considered successfully + attached. If no Gateway listeners accept attachment from this Route, + the Route MUST be considered detached from the Gateway. + + Support: Extended + format: int32 + maximum: 65535 + minimum: 1 + type: integer + sectionName: + description: |- + SectionName is the name of a section within the target resource. In the + following resources, SectionName is interpreted as the following: + + * Gateway: Listener name. When both Port (experimental) and SectionName + are specified, the name and port of the selected listener must match + both specified values. + * Service: Port name. When both Port (experimental) and SectionName + are specified, the name and port of the selected listener must match + both specified values. + + Implementations MAY choose to support attaching Routes to other resources. + If that is the case, they MUST clearly document how SectionName is + interpreted. + + When unspecified (empty string), this will reference the entire resource. + For the purpose of status, an attachment is considered successful if at + least one section in the parent resource accepts it. For example, Gateway + listeners can restrict which Routes can attach to them by Route kind, + namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from + the referencing Route, the Route MUST be considered successfully + attached. If no Gateway listeners accept attachment from this Route, the + Route MUST be considered detached from the Gateway. + + Support: Core + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - name + type: object + type: array + x-kubernetes-list-type: atomic + podTemplate: + description: |- + Optional pod template used to configure the ACME challenge solver pods + used for HTTP01 challenges. + properties: + metadata: + description: |- + 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. + properties: + annotations: + additionalProperties: + type: string + description: Annotations that should be added to the created ACME HTTP01 solver pods. + type: object + labels: + additionalProperties: + type: string + description: Labels that should be added to the created ACME HTTP01 solver pods. + type: object + type: object + spec: + description: |- + PodSpec defines overrides for the HTTP01 challenge solver pod. + Check ACMEChallengeSolverHTTP01IngressPodSpec to find out currently supported fields. + All other fields will be ignored. + properties: + affinity: + description: If specified, the pod's scheduling constraints + properties: + nodeAffinity: + description: Describes node affinity scheduling rules for the pod. + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node matches the corresponding matchExpressions; the + node(s) with the highest sum are the most preferred. + items: + description: |- + An empty preferred scheduling term matches all objects with implicit weight 0 + (i.e. it's a no-op). A null preferred scheduling term matches no objects (i.e. is also a no-op). + properties: + preference: + description: A node selector term, associated with the corresponding weight. + properties: + matchExpressions: + description: A list of node selector requirements by node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: The label key that the selector applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: A list of node selector requirements by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: The label key that the selector applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + weight: + description: Weight associated with matching the corresponding nodeSelectorTerm, in the range 1-100. + format: int32 + type: integer + required: + - preference + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to an update), the system + may or may not try to eventually evict the pod from its node. + properties: + nodeSelectorTerms: + description: Required. A list of node selector terms. The terms are ORed. + items: + description: |- + A null or empty node selector term matches no objects. The requirements of + them are ANDed. + The TopologySelectorTerm type implements a subset of the NodeSelectorTerm. + properties: + matchExpressions: + description: A list of node selector requirements by node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: The label key that the selector applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: A list of node selector requirements by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: The label key that the selector applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + type: array + x-kubernetes-list-type: atomic + required: + - nodeSelectorTerms + type: object + x-kubernetes-map-type: atomic + type: object + podAffinity: + description: Describes pod affinity scheduling rules (e.g. co-locate this pod in the same node, zone, etc. as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: The weights of all of the matched WeightedPodAffinityTerm fields are added per-node to find the most preferred node(s) + properties: + podAffinityTerm: + description: Required. A pod affinity term, associated with the corresponding weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + podAntiAffinity: + description: Describes pod anti-affinity scheduling rules (e.g. avoid putting this pod in the same node, zone, etc. as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the anti-affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling anti-affinity expressions, etc.), + compute a sum by iterating through the elements of this field and subtracting + "weight" from the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: The weights of all of the matched WeightedPodAffinityTerm fields are added per-node to find the most preferred node(s) + properties: + podAffinityTerm: + description: Required. A pod affinity term, associated with the corresponding weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the anti-affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the anti-affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + type: object + imagePullSecrets: + description: If specified, the pod's imagePullSecrets + items: + description: |- + LocalObjectReference contains enough information to let you locate the + referenced object inside the same namespace. + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + type: object + x-kubernetes-map-type: atomic + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + nodeSelector: + additionalProperties: + type: string + description: |- + 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/ + type: object + priorityClassName: + description: If specified, the pod's priorityClassName. + type: string + resources: + description: |- + If specified, the pod's resource requirements. + These values override the global resource configuration flags. + Note that when only specifying resource limits, ensure they are greater than or equal + to the corresponding global resource requests configured via controller flags + (--acme-http01-solver-resource-request-cpu, --acme-http01-solver-resource-request-memory). + Kubernetes will reject pod creation if limits are lower than requests, causing challenge failures. + properties: + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to the global values configured via controller flags. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + securityContext: + description: If specified, the pod's security context + properties: + fsGroup: + description: |- + A special supplemental group that applies to all containers in a pod. + Some volume types allow the Kubelet to change the ownership of that volume + to be owned by the pod: + + 1. The owning GID will be the FSGroup + 2. The setgid bit is set (new files created in the volume will be owned by FSGroup) + 3. The permission bits are OR'd with rw-rw---- + + If unset, the Kubelet will not modify the ownership and permissions of any volume. + Note that this field cannot be set when spec.os.name is windows. + format: int64 + type: integer + fsGroupChangePolicy: + description: |- + fsGroupChangePolicy defines behavior of changing ownership and permission of the volume + before being exposed inside Pod. This field will only apply to + volume types which support fsGroup based ownership(and permissions). + It will have no effect on ephemeral volume types such as: secret, configmaps + and emptydir. + Valid values are "OnRootMismatch" and "Always". If not specified, "Always" is used. + Note that this field cannot be set when spec.os.name is windows. + type: string + runAsGroup: + description: |- + The GID to run the entrypoint of the container process. + Uses runtime default if unset. + May also be set in SecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext takes precedence + for that container. + Note that this field cannot be set when spec.os.name is windows. + format: int64 + type: integer + runAsNonRoot: + description: |- + Indicates that the container must run as a non-root user. + If true, the Kubelet will validate the image at runtime to ensure that it + does not run as UID 0 (root) and fail to start the container if it does. + If unset or false, no such validation will be performed. + May also be set in SecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext takes precedence. + type: boolean + runAsUser: + description: |- + The UID to run the entrypoint of the container process. + Defaults to user specified in image metadata if unspecified. + May also be set in SecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext takes precedence + for that container. + Note that this field cannot be set when spec.os.name is windows. + format: int64 + type: integer + seLinuxOptions: + description: |- + The SELinux context to be applied to all containers. + If unspecified, the container runtime will allocate a random SELinux context for each + container. May also be set in SecurityContext. If set in + both SecurityContext and PodSecurityContext, the value specified in SecurityContext + takes precedence for that container. + Note that this field cannot be set when spec.os.name is windows. + properties: + level: + description: Level is SELinux level label that applies to the container. + type: string + role: + description: Role is a SELinux role label that applies to the container. + type: string + type: + description: Type is a SELinux type label that applies to the container. + type: string + user: + description: User is a SELinux user label that applies to the container. + type: string + type: object + seccompProfile: + description: |- + The seccomp options to use by the containers in this pod. + Note that this field cannot be set when spec.os.name is windows. + properties: + localhostProfile: + description: |- + localhostProfile indicates a profile defined in a file on the node should be used. + The profile must be preconfigured on the node to work. + Must be a descending path, relative to the kubelet's configured seccomp profile location. + Must be set if type is "Localhost". Must NOT be set for any other type. + type: string + type: + description: |- + type indicates which kind of seccomp profile will be applied. + Valid options are: + + Localhost - a profile defined in a file on the node should be used. + RuntimeDefault - the container runtime default profile should be used. + Unconfined - no profile should be applied. + type: string + required: + - type + type: object + supplementalGroups: + description: |- + A list of groups applied to the first process run in each container, in addition + to the container's primary GID, the fsGroup (if specified), and group memberships + defined in the container image for the uid of the container process. If unspecified, + no additional groups are added to any container. Note that group memberships + defined in the container image for the uid of the container process are still effective, + even if they are not included in this list. + Note that this field cannot be set when spec.os.name is windows. + items: + format: int64 + type: integer + type: array + x-kubernetes-list-type: atomic + sysctls: + description: |- + Sysctls hold a list of namespaced sysctls used for the pod. Pods with unsupported + sysctls (by the container runtime) might fail to launch. + Note that this field cannot be set when spec.os.name is windows. + items: + description: Sysctl defines a kernel parameter to be set + properties: + name: + description: Name of a property to set + type: string + value: + description: Value of a property to set + type: string + required: + - name + - value + type: object + type: array + x-kubernetes-list-type: atomic + type: object + serviceAccountName: + description: If specified, the pod's service account + type: string + tolerations: + description: If specified, the pod's tolerations. + items: + description: |- + The pod this Toleration is attached to tolerates any taint that matches + the triple using the matching operator . + properties: + effect: + description: |- + Effect indicates the taint effect to match. Empty means match all taint effects. + When specified, allowed values are NoSchedule, PreferNoSchedule and NoExecute. + type: string + key: + description: |- + Key is the taint key that the toleration applies to. Empty means match all taint keys. + If the key is empty, operator must be Exists; this combination means to match all values and all keys. + type: string + operator: + description: |- + Operator represents a key's relationship to the value. + Valid operators are Exists and Equal. Defaults to Equal. + Exists is equivalent to wildcard for value, so that a pod can + tolerate all taints of a particular category. + type: string + tolerationSeconds: + description: |- + TolerationSeconds represents the period of time the toleration (which must be + of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default, + it is not set, which means tolerate the taint forever (do not evict). Zero and + negative values will be treated as 0 (evict immediately) by the system. + format: int64 + type: integer + value: + description: |- + Value is the taint value the toleration matches to. + If the operator is Exists, the value should be empty, otherwise just a regular string. + type: string + type: object + type: array + x-kubernetes-list-type: atomic + type: object + type: object + serviceType: + description: |- + Optional service type for Kubernetes solver service. Supported values + are NodePort or ClusterIP. If unset, defaults to NodePort. + type: string + type: object + ingress: + description: |- + 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. + properties: + class: + description: |- + 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. + type: string + ingressClassName: + description: |- + 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. + type: string + ingressTemplate: + description: |- + Optional ingress template used to configure the ACME challenge solver + ingress used for HTTP01 challenges. + properties: + metadata: + description: |- + 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. + properties: + annotations: + additionalProperties: + type: string + description: Annotations that should be added to the created ACME HTTP01 solver ingress. + type: object + labels: + additionalProperties: + type: string + description: Labels that should be added to the created ACME HTTP01 solver ingress. + type: object + type: object + type: object + name: + description: |- + 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. + type: string + podTemplate: + description: |- + Optional pod template used to configure the ACME challenge solver pods + used for HTTP01 challenges. + properties: + metadata: + description: |- + 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. + properties: + annotations: + additionalProperties: + type: string + description: Annotations that should be added to the created ACME HTTP01 solver pods. + type: object + labels: + additionalProperties: + type: string + description: Labels that should be added to the created ACME HTTP01 solver pods. + type: object + type: object + spec: + description: |- + PodSpec defines overrides for the HTTP01 challenge solver pod. + Check ACMEChallengeSolverHTTP01IngressPodSpec to find out currently supported fields. + All other fields will be ignored. + properties: + affinity: + description: If specified, the pod's scheduling constraints + properties: + nodeAffinity: + description: Describes node affinity scheduling rules for the pod. + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node matches the corresponding matchExpressions; the + node(s) with the highest sum are the most preferred. + items: + description: |- + An empty preferred scheduling term matches all objects with implicit weight 0 + (i.e. it's a no-op). A null preferred scheduling term matches no objects (i.e. is also a no-op). + properties: + preference: + description: A node selector term, associated with the corresponding weight. + properties: + matchExpressions: + description: A list of node selector requirements by node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: The label key that the selector applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: A list of node selector requirements by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: The label key that the selector applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + weight: + description: Weight associated with matching the corresponding nodeSelectorTerm, in the range 1-100. + format: int32 + type: integer + required: + - preference + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to an update), the system + may or may not try to eventually evict the pod from its node. + properties: + nodeSelectorTerms: + description: Required. A list of node selector terms. The terms are ORed. + items: + description: |- + A null or empty node selector term matches no objects. The requirements of + them are ANDed. + The TopologySelectorTerm type implements a subset of the NodeSelectorTerm. + properties: + matchExpressions: + description: A list of node selector requirements by node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: The label key that the selector applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: A list of node selector requirements by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: The label key that the selector applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + type: array + x-kubernetes-list-type: atomic + required: + - nodeSelectorTerms + type: object + x-kubernetes-map-type: atomic + type: object + podAffinity: + description: Describes pod affinity scheduling rules (e.g. co-locate this pod in the same node, zone, etc. as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: The weights of all of the matched WeightedPodAffinityTerm fields are added per-node to find the most preferred node(s) + properties: + podAffinityTerm: + description: Required. A pod affinity term, associated with the corresponding weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + podAntiAffinity: + description: Describes pod anti-affinity scheduling rules (e.g. avoid putting this pod in the same node, zone, etc. as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the anti-affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling anti-affinity expressions, etc.), + compute a sum by iterating through the elements of this field and subtracting + "weight" from the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: The weights of all of the matched WeightedPodAffinityTerm fields are added per-node to find the most preferred node(s) + properties: + podAffinityTerm: + description: Required. A pod affinity term, associated with the corresponding weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the anti-affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the anti-affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + type: object + imagePullSecrets: + description: If specified, the pod's imagePullSecrets + items: + description: |- + LocalObjectReference contains enough information to let you locate the + referenced object inside the same namespace. + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + type: object + x-kubernetes-map-type: atomic + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + nodeSelector: + additionalProperties: + type: string + description: |- + 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/ + type: object + priorityClassName: + description: If specified, the pod's priorityClassName. + type: string + resources: + description: |- + If specified, the pod's resource requirements. + These values override the global resource configuration flags. + Note that when only specifying resource limits, ensure they are greater than or equal + to the corresponding global resource requests configured via controller flags + (--acme-http01-solver-resource-request-cpu, --acme-http01-solver-resource-request-memory). + Kubernetes will reject pod creation if limits are lower than requests, causing challenge failures. + properties: + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to the global values configured via controller flags. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + securityContext: + description: If specified, the pod's security context + properties: + fsGroup: + description: |- + A special supplemental group that applies to all containers in a pod. + Some volume types allow the Kubelet to change the ownership of that volume + to be owned by the pod: + + 1. The owning GID will be the FSGroup + 2. The setgid bit is set (new files created in the volume will be owned by FSGroup) + 3. The permission bits are OR'd with rw-rw---- + + If unset, the Kubelet will not modify the ownership and permissions of any volume. + Note that this field cannot be set when spec.os.name is windows. + format: int64 + type: integer + fsGroupChangePolicy: + description: |- + fsGroupChangePolicy defines behavior of changing ownership and permission of the volume + before being exposed inside Pod. This field will only apply to + volume types which support fsGroup based ownership(and permissions). + It will have no effect on ephemeral volume types such as: secret, configmaps + and emptydir. + Valid values are "OnRootMismatch" and "Always". If not specified, "Always" is used. + Note that this field cannot be set when spec.os.name is windows. + type: string + runAsGroup: + description: |- + The GID to run the entrypoint of the container process. + Uses runtime default if unset. + May also be set in SecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext takes precedence + for that container. + Note that this field cannot be set when spec.os.name is windows. + format: int64 + type: integer + runAsNonRoot: + description: |- + Indicates that the container must run as a non-root user. + If true, the Kubelet will validate the image at runtime to ensure that it + does not run as UID 0 (root) and fail to start the container if it does. + If unset or false, no such validation will be performed. + May also be set in SecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext takes precedence. + type: boolean + runAsUser: + description: |- + The UID to run the entrypoint of the container process. + Defaults to user specified in image metadata if unspecified. + May also be set in SecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext takes precedence + for that container. + Note that this field cannot be set when spec.os.name is windows. + format: int64 + type: integer + seLinuxOptions: + description: |- + The SELinux context to be applied to all containers. + If unspecified, the container runtime will allocate a random SELinux context for each + container. May also be set in SecurityContext. If set in + both SecurityContext and PodSecurityContext, the value specified in SecurityContext + takes precedence for that container. + Note that this field cannot be set when spec.os.name is windows. + properties: + level: + description: Level is SELinux level label that applies to the container. + type: string + role: + description: Role is a SELinux role label that applies to the container. + type: string + type: + description: Type is a SELinux type label that applies to the container. + type: string + user: + description: User is a SELinux user label that applies to the container. + type: string + type: object + seccompProfile: + description: |- + The seccomp options to use by the containers in this pod. + Note that this field cannot be set when spec.os.name is windows. + properties: + localhostProfile: + description: |- + localhostProfile indicates a profile defined in a file on the node should be used. + The profile must be preconfigured on the node to work. + Must be a descending path, relative to the kubelet's configured seccomp profile location. + Must be set if type is "Localhost". Must NOT be set for any other type. + type: string + type: + description: |- + type indicates which kind of seccomp profile will be applied. + Valid options are: + + Localhost - a profile defined in a file on the node should be used. + RuntimeDefault - the container runtime default profile should be used. + Unconfined - no profile should be applied. + type: string + required: + - type + type: object + supplementalGroups: + description: |- + A list of groups applied to the first process run in each container, in addition + to the container's primary GID, the fsGroup (if specified), and group memberships + defined in the container image for the uid of the container process. If unspecified, + no additional groups are added to any container. Note that group memberships + defined in the container image for the uid of the container process are still effective, + even if they are not included in this list. + Note that this field cannot be set when spec.os.name is windows. + items: + format: int64 + type: integer + type: array + x-kubernetes-list-type: atomic + sysctls: + description: |- + Sysctls hold a list of namespaced sysctls used for the pod. Pods with unsupported + sysctls (by the container runtime) might fail to launch. + Note that this field cannot be set when spec.os.name is windows. + items: + description: Sysctl defines a kernel parameter to be set + properties: + name: + description: Name of a property to set + type: string + value: + description: Value of a property to set + type: string + required: + - name + - value + type: object + type: array + x-kubernetes-list-type: atomic + type: object + serviceAccountName: + description: If specified, the pod's service account + type: string + tolerations: + description: If specified, the pod's tolerations. + items: + description: |- + The pod this Toleration is attached to tolerates any taint that matches + the triple using the matching operator . + properties: + effect: + description: |- + Effect indicates the taint effect to match. Empty means match all taint effects. + When specified, allowed values are NoSchedule, PreferNoSchedule and NoExecute. + type: string + key: + description: |- + Key is the taint key that the toleration applies to. Empty means match all taint keys. + If the key is empty, operator must be Exists; this combination means to match all values and all keys. + type: string + operator: + description: |- + Operator represents a key's relationship to the value. + Valid operators are Exists and Equal. Defaults to Equal. + Exists is equivalent to wildcard for value, so that a pod can + tolerate all taints of a particular category. + type: string + tolerationSeconds: + description: |- + TolerationSeconds represents the period of time the toleration (which must be + of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default, + it is not set, which means tolerate the taint forever (do not evict). Zero and + negative values will be treated as 0 (evict immediately) by the system. + format: int64 + type: integer + value: + description: |- + Value is the taint value the toleration matches to. + If the operator is Exists, the value should be empty, otherwise just a regular string. + type: string + type: object + type: array + x-kubernetes-list-type: atomic + type: object + type: object + serviceType: + description: |- + Optional service type for Kubernetes solver service. Supported values + are NodePort or ClusterIP. If unset, defaults to NodePort. + type: string + type: object + type: object + selector: + description: |- + 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. + properties: + dnsNames: + description: |- + 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. + items: + type: string + type: array + x-kubernetes-list-type: atomic + dnsZones: + description: |- + 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. + items: + type: string + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + A label selector that is used to refine the set of certificate's that + this challenge solver will apply to. + type: object + type: object + type: object + type: array + x-kubernetes-list-type: atomic + required: + - privateKeySecretRef + - server + type: object + ca: + description: |- + 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. + properties: + crlDistributionPoints: + description: |- + 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. + items: + type: string + type: array + x-kubernetes-list-type: atomic + issuingCertificateURLs: + description: |- + IssuingCertificateURLs is a list of URLs which this issuer should embed into certificates + it creates. See https://www.rfc-editor.org/rfc/rfc5280#section-4.2.2.1 for more details. + As an example, such a URL might be "http://ca.domain.com/ca.crt". + items: + type: string + type: array + x-kubernetes-list-type: atomic + ocspServers: + description: |- + 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". + items: + type: string + type: array + x-kubernetes-list-type: atomic + secretName: + description: |- + SecretName is the name of the secret used to sign Certificates issued + by this Issuer. + type: string + required: + - secretName + type: object + selfSigned: + description: |- + SelfSigned configures this issuer to 'self sign' certificates using the + private key used to create the CertificateRequest object. + properties: + crlDistributionPoints: + description: |- + 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. + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + vault: + description: |- + Vault configures this issuer to sign certificates using a HashiCorp Vault + PKI backend. + properties: + auth: + description: Auth configures how cert-manager authenticates with the Vault server. + properties: + appRole: + description: |- + AppRole authenticates with Vault using the App Role auth mechanism, + with the role and secret stored in a Kubernetes Secret resource. + properties: + path: + description: |- + Path where the App Role authentication backend is mounted in Vault, e.g: + "approle" + type: string + roleId: + description: |- + RoleID configured in the App Role authentication backend when setting + up the authentication backend in Vault. + type: string + secretRef: + description: |- + 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. + properties: + key: + description: |- + 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. + type: string + name: + description: |- + Name of the resource being referred to. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + required: + - name + type: object + required: + - path + - roleId + - secretRef + type: object + clientCertificate: + description: |- + ClientCertificate authenticates with Vault by presenting a client + certificate during the request's TLS handshake. + Works only when using HTTPS protocol. + properties: + mountPath: + description: |- + 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/cert" will be used. + type: string + name: + description: |- + Name of the certificate role to authenticate against. + If not set, matching any certificate role, if available. + type: string + secretName: + description: |- + Reference to Kubernetes Secret of type "kubernetes.io/tls" (hence containing + tls.crt and tls.key) used to authenticate to Vault using TLS client + authentication. + type: string + type: object + kubernetes: + description: |- + Kubernetes authenticates with Vault by passing the ServiceAccount + token stored in the named Secret resource to the Vault server. + properties: + mountPath: + description: |- + 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. + type: string + role: + description: |- + A required field containing the Vault Role to assume. A Role binds a + Kubernetes ServiceAccount with a set of Vault policies. + type: string + secretRef: + description: |- + The required Secret field containing a Kubernetes ServiceAccount JWT used + for authenticating with Vault. Use of 'ambient credentials' is not + supported. + properties: + key: + description: |- + 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. + type: string + name: + description: |- + Name of the resource being referred to. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + required: + - name + type: object + serviceAccountRef: + description: |- + 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. + properties: + audiences: + description: |- + TokenAudiences is an optional list of extra audiences to include in the token passed to Vault. The default token + consisting of the issuer's namespace and name is always included. + items: + type: string + type: array + x-kubernetes-list-type: atomic + name: + description: Name of the ServiceAccount used to request a token. + type: string + required: + - name + type: object + required: + - role + type: object + tokenSecretRef: + description: TokenSecretRef authenticates with Vault by presenting a token. + properties: + key: + description: |- + 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. + type: string + name: + description: |- + Name of the resource being referred to. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + required: + - name + type: object + type: object + caBundle: + description: |- + 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. + format: byte + type: string + caBundleSecretRef: + description: |- + 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'. + properties: + key: + description: |- + 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. + type: string + name: + description: |- + Name of the resource being referred to. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + required: + - name + type: object + clientCertSecretRef: + description: |- + Reference to a Secret containing a PEM-encoded Client Certificate to use when the + Vault server requires mTLS. + properties: + key: + description: |- + 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. + type: string + name: + description: |- + Name of the resource being referred to. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + required: + - name + type: object + clientKeySecretRef: + description: |- + Reference to a Secret containing a PEM-encoded Client Private Key to use when the + Vault server requires mTLS. + properties: + key: + description: |- + 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. + type: string + name: + description: |- + Name of the resource being referred to. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + required: + - name + type: object + namespace: + description: |- + 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 + type: string + path: + description: |- + Path is the mount path of the Vault PKI backend's `sign` endpoint, e.g: + "my_pki_mount/sign/my-role-name". + type: string + server: + description: 'Server is the connection address for the Vault server, e.g: "https://vault.example.com:8200".' + type: string + serverName: + description: |- + ServerName is used to verify the hostname on the returned certificates + by the Vault server. + type: string + required: + - auth + - path + - server + type: object + venafi: + description: |- + Venafi configures this issuer to sign certificates using a Venafi TPP + or Venafi Cloud policy zone. + properties: + cloud: + description: |- + Cloud specifies the Venafi cloud configuration settings. + Only one of TPP or Cloud may be specified. + properties: + apiTokenSecretRef: + description: APITokenSecretRef is a secret key selector for the Venafi Cloud API token. + properties: + key: + description: |- + 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. + type: string + name: + description: |- + Name of the resource being referred to. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + required: + - name + type: object + url: + description: |- + URL is the base URL for Venafi Cloud. + Defaults to "https://api.venafi.cloud/". + type: string + required: + - apiTokenSecretRef + type: object + tpp: + description: |- + TPP specifies Trust Protection Platform configuration settings. + Only one of TPP or Cloud may be specified. + properties: + caBundle: + description: |- + 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. + format: byte + type: string + caBundleSecretRef: + description: |- + Reference to a Secret containing a 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. Mutually exclusive with CABundle. + If neither CABundle nor CABundleSecretRef is defined, the certificate bundle in + the cert-manager controller container is used to validate the TLS connection. + properties: + key: + description: |- + 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. + type: string + name: + description: |- + Name of the resource being referred to. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + required: + - name + type: object + credentialsRef: + description: |- + CredentialsRef is a reference to a Secret containing the Venafi TPP API credentials. + The secret must contain the key 'access-token' for the Access Token Authentication, + or two keys, 'username' and 'password' for the API Keys Authentication. + properties: + name: + description: |- + Name of the resource being referred to. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + required: + - name + type: object + url: + description: |- + URL is the base URL for the vedsdk endpoint of the Venafi TPP instance, + for example: "https://tpp.example.com/vedsdk". + type: string + required: + - credentialsRef + - url + type: object + zone: + description: |- + 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. + type: string + required: + - zone + type: object + type: object + status: + description: Status of the ClusterIssuer. This is set and managed automatically. + properties: + acme: + description: |- + ACME specific status options. + This field should only be set if the Issuer is configured to use an ACME + server to issue certificates. + properties: + lastPrivateKeyHash: + description: |- + 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 + type: string + lastRegisteredEmail: + 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 + type: string + uri: + description: |- + URI is the unique account identifier, which can also be used to retrieve + account details from the CA + type: string + type: object + conditions: + description: |- + List of status conditions to indicate the status of a CertificateRequest. + Known condition types are `Ready`. + items: + description: IssuerCondition contains condition information for an Issuer. + properties: + lastTransitionTime: + description: |- + LastTransitionTime is the timestamp corresponding to the last status + change of this condition. + format: date-time + type: string + message: + description: |- + Message is a human readable description of the details of the last + transition, complementing reason. + type: string + observedGeneration: + description: |- + 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. + format: int64 + type: integer + reason: + description: |- + Reason is a brief machine readable explanation for the condition's last + transition. + type: string + status: + description: Status of the condition, one of (`True`, `False`, `Unknown`). + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: Type of the condition, known values are (`Ready`). + type: string + required: + - status + - type + type: object + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} +{{- end }} diff --git a/deploy/charts/cert-manager/templates/crd-cert-manager.io_issuers.yaml b/deploy/charts/cert-manager/templates/crd-cert-manager.io_issuers.yaml new file mode 100644 index 00000000000..43277f84076 --- /dev/null +++ b/deploy/charts/cert-manager/templates/crd-cert-manager.io_issuers.yaml @@ -0,0 +1,3814 @@ +{{- if or .Values.crds.enabled .Values.installCRDs }} +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: "issuers.cert-manager.io" + {{- if .Values.crds.keep }} + annotations: + helm.sh/resource-policy: keep + {{- end }} + labels: + {{- include "cert-manager.crd-labels" . | nindent 4 }} +spec: + group: cert-manager.io + names: + categories: + - cert-manager + kind: Issuer + listKind: IssuerList + plural: issuers + shortNames: + - iss + singular: issuer + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .status.conditions[?(@.type == "Ready")].status + name: Ready + type: string + - jsonPath: .status.conditions[?(@.type == "Ready")].message + name: Status + priority: 1 + type: string + - description: CreationTimestamp is a timestamp representing the server time when this object was created. It is not guaranteed to be set in happens-before order across separate operations. Clients may not set this value. It is represented in RFC3339 form and is in UTC. + jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1 + schema: + openAPIV3Schema: + description: |- + 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. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: Desired state of the Issuer resource. + properties: + acme: + description: |- + ACME configures this issuer to communicate with a RFC8555 (ACME) server + to obtain signed x509 certificates. + properties: + caBundle: + description: |- + 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. + format: byte + type: string + disableAccountKeyGeneration: + description: |- + 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. + type: boolean + email: + description: |- + 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. + type: string + enableDurationFeature: + description: |- + 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. + type: boolean + externalAccountBinding: + description: |- + 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. + properties: + keyAlgorithm: + description: |- + 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. + enum: + - HS256 + - HS384 + - HS512 + type: string + keyID: + description: keyID is the ID of the CA key that the External Account is bound to. + type: string + keySecretRef: + description: |- + 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. + properties: + key: + description: |- + 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. + type: string + name: + description: |- + Name of the resource being referred to. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + required: + - name + type: object + required: + - keyID + - keySecretRef + type: object + preferredChain: + description: |- + 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 cross-sign 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 combined set of + ACME default and alternative chains that has a root-most certificate with + this value as its issuer's commonname. + maxLength: 64 + type: string + privateKeySecretRef: + description: |- + 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. + properties: + key: + description: |- + 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. + type: string + name: + description: |- + Name of the resource being referred to. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + required: + - name + type: object + profile: + description: |- + Profile allows requesting a certificate profile from the ACME server. + Supported profiles are listed by the server's ACME directory URL. + type: string + server: + description: |- + 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. + type: string + skipTLSVerify: + description: |- + 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. + type: boolean + solvers: + description: |- + 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/ + items: + description: |- + 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. + properties: + dns01: + description: |- + Configures cert-manager to attempt to complete authorizations by + performing the DNS01 challenge flow. + properties: + acmeDNS: + description: |- + Use the 'ACME DNS' (https://github.com/joohoi/acme-dns) API to manage + DNS01 challenge records. + properties: + accountSecretRef: + description: |- + A reference to a specific 'key' within a Secret resource. + In some instances, `key` is a required field. + properties: + key: + description: |- + 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. + type: string + name: + description: |- + Name of the resource being referred to. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + required: + - name + type: object + host: + type: string + required: + - accountSecretRef + - host + type: object + akamai: + description: Use the Akamai DNS zone management API to manage DNS01 challenge records. + properties: + accessTokenSecretRef: + description: |- + A reference to a specific 'key' within a Secret resource. + In some instances, `key` is a required field. + properties: + key: + description: |- + 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. + type: string + name: + description: |- + Name of the resource being referred to. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + required: + - name + type: object + clientSecretSecretRef: + description: |- + A reference to a specific 'key' within a Secret resource. + In some instances, `key` is a required field. + properties: + key: + description: |- + 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. + type: string + name: + description: |- + Name of the resource being referred to. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + required: + - name + type: object + clientTokenSecretRef: + description: |- + A reference to a specific 'key' within a Secret resource. + In some instances, `key` is a required field. + properties: + key: + description: |- + 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. + type: string + name: + description: |- + Name of the resource being referred to. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + required: + - name + type: object + serviceConsumerDomain: + type: string + required: + - accessTokenSecretRef + - clientSecretSecretRef + - clientTokenSecretRef + - serviceConsumerDomain + type: object + azureDNS: + description: Use the Microsoft Azure DNS API to manage DNS01 challenge records. + properties: + clientID: + description: |- + Auth: Azure Service Principal: + The ClientID of the Azure Service Principal used to authenticate with Azure DNS. + If set, ClientSecret and TenantID must also be set. + type: string + clientSecretSecretRef: + description: |- + Auth: Azure Service Principal: + A reference to a Secret containing the password associated with the Service Principal. + If set, ClientID and TenantID must also be set. + properties: + key: + description: |- + 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. + type: string + name: + description: |- + Name of the resource being referred to. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + required: + - name + type: object + environment: + description: name of the Azure environment (default AzurePublicCloud) + enum: + - AzurePublicCloud + - AzureChinaCloud + - AzureGermanCloud + - AzureUSGovernmentCloud + type: string + hostedZoneName: + description: name of the DNS zone that should be used + type: string + managedIdentity: + description: |- + Auth: Azure Workload Identity or Azure Managed Service Identity: + Settings to enable Azure Workload Identity or Azure Managed Service Identity + If set, ClientID, ClientSecret and TenantID must not be set. + properties: + clientID: + description: client ID of the managed identity, cannot be used at the same time as resourceID + type: string + resourceID: + description: |- + resource ID of the managed identity, cannot be used at the same time as clientID + Cannot be used for Azure Managed Service Identity + type: string + tenantID: + description: tenant ID of the managed identity, cannot be used at the same time as resourceID + type: string + type: object + resourceGroupName: + description: resource group the DNS zone is located in + type: string + subscriptionID: + description: ID of the Azure subscription + type: string + tenantID: + description: |- + Auth: Azure Service Principal: + The TenantID of the Azure Service Principal used to authenticate with Azure DNS. + If set, ClientID and ClientSecret must also be set. + type: string + required: + - resourceGroupName + - subscriptionID + type: object + cloudDNS: + description: Use the Google Cloud DNS API to manage DNS01 challenge records. + properties: + hostedZoneName: + description: |- + 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. + type: string + project: + type: string + serviceAccountSecretRef: + description: |- + A reference to a specific 'key' within a Secret resource. + In some instances, `key` is a required field. + properties: + key: + description: |- + 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. + type: string + name: + description: |- + Name of the resource being referred to. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + required: + - name + type: object + required: + - project + type: object + cloudflare: + description: Use the Cloudflare API to manage DNS01 challenge records. + properties: + apiKeySecretRef: + description: |- + 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. + properties: + key: + description: |- + 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. + type: string + name: + description: |- + Name of the resource being referred to. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + required: + - name + type: object + apiTokenSecretRef: + description: API token used to authenticate with Cloudflare. + properties: + key: + description: |- + 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. + type: string + name: + description: |- + Name of the resource being referred to. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + required: + - name + type: object + email: + description: Email of the account, only required when using API key based authentication. + type: string + type: object + cnameStrategy: + description: |- + CNAMEStrategy configures how the DNS01 provider should handle CNAME + records when found in DNS zones. + enum: + - None + - Follow + type: string + digitalocean: + description: Use the DigitalOcean DNS API to manage DNS01 challenge records. + properties: + tokenSecretRef: + description: |- + A reference to a specific 'key' within a Secret resource. + In some instances, `key` is a required field. + properties: + key: + description: |- + 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. + type: string + name: + description: |- + Name of the resource being referred to. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + required: + - name + type: object + required: + - tokenSecretRef + type: object + rfc2136: + description: |- + Use RFC2136 ("Dynamic Updates in the Domain Name System") (https://datatracker.ietf.org/doc/rfc2136/) + to manage DNS01 challenge records. + properties: + nameserver: + description: |- + 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. + type: string + protocol: + description: Protocol to use for dynamic DNS update queries. Valid values are (case-sensitive) ``TCP`` and ``UDP``; ``UDP`` (default). + enum: + - TCP + - UDP + type: string + tsigAlgorithm: + description: |- + 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``. + type: string + tsigKeyName: + description: |- + The TSIG Key name configured in the DNS. + If ``tsigSecretSecretRef`` is defined, this field is required. + type: string + tsigSecretSecretRef: + description: |- + The name of the secret containing the TSIG value. + If ``tsigKeyName`` is defined, this field is required. + properties: + key: + description: |- + 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. + type: string + name: + description: |- + Name of the resource being referred to. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + required: + - name + type: object + required: + - nameserver + type: object + route53: + description: Use the AWS Route53 API to manage DNS01 challenge records. + properties: + accessKeyID: + description: |- + 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 + type: string + accessKeyIDSecretRef: + description: |- + 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 + properties: + key: + description: |- + 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. + type: string + name: + description: |- + Name of the resource being referred to. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + required: + - name + type: object + auth: + description: Auth configures how cert-manager authenticates. + properties: + kubernetes: + description: |- + Kubernetes authenticates with Route53 using AssumeRoleWithWebIdentity + by passing a bound ServiceAccount token. + properties: + serviceAccountRef: + description: |- + A reference to a service account that will be used to request a bound + token (also known as "projected token"). To use this field, you must + configure an RBAC rule to let cert-manager request a token. + properties: + audiences: + description: |- + TokenAudiences is an optional list of audiences to include in the + token passed to AWS. The default token consisting of the issuer's namespace + and name is always included. + If unset the audience defaults to `sts.amazonaws.com`. + items: + type: string + type: array + x-kubernetes-list-type: atomic + name: + description: Name of the ServiceAccount used to request a token. + type: string + required: + - name + type: object + required: + - serviceAccountRef + type: object + required: + - kubernetes + type: object + hostedZoneID: + description: If set, the provider will manage only this zone in Route53 and will not do a lookup using the route53:ListHostedZonesByName api call. + type: string + region: + description: |- + Override the AWS region. + + Route53 is a global service and does not have regional endpoints but the + region specified here (or via environment variables) is used as a hint to + help compute the correct AWS credential scope and partition when it + connects to Route53. See: + - [Amazon Route 53 endpoints and quotas](https://docs.aws.amazon.com/general/latest/gr/r53.html) + - [Global services](https://docs.aws.amazon.com/whitepapers/latest/aws-fault-isolation-boundaries/global-services.html) + + If you omit this region field, cert-manager will use the region from + AWS_REGION and AWS_DEFAULT_REGION environment variables, if they are set + in the cert-manager controller Pod. + + The `region` field is not needed if you use [IAM Roles for Service Accounts (IRSA)](https://docs.aws.amazon.com/eks/latest/userguide/iam-roles-for-service-accounts.html). + Instead an AWS_REGION environment variable is added to the cert-manager controller Pod by: + [Amazon EKS Pod Identity Webhook](https://github.com/aws/amazon-eks-pod-identity-webhook). + In this case this `region` field value is ignored. + + The `region` field is not needed if you use [EKS Pod Identities](https://docs.aws.amazon.com/eks/latest/userguide/pod-identities.html). + Instead an AWS_REGION environment variable is added to the cert-manager controller Pod by: + [Amazon EKS Pod Identity Agent](https://github.com/aws/eks-pod-identity-agent), + In this case this `region` field value is ignored. + type: string + role: + description: |- + 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 + type: string + secretAccessKeySecretRef: + description: |- + 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 + properties: + key: + description: |- + 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. + type: string + name: + description: |- + Name of the resource being referred to. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + required: + - name + type: object + type: object + webhook: + description: |- + Configure an external webhook based DNS01 challenge solver to manage + DNS01 challenge records. + properties: + config: + description: |- + 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. + x-kubernetes-preserve-unknown-fields: true + groupName: + description: |- + 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. + type: string + solverName: + description: |- + 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'. + type: string + required: + - groupName + - solverName + type: object + type: object + http01: + description: |- + 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. + properties: + gatewayHTTPRoute: + description: |- + 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. + properties: + labels: + additionalProperties: + type: string + description: |- + Custom labels that will be applied to HTTPRoutes created by cert-manager + while solving HTTP-01 challenges. + type: object + parentRefs: + description: |- + 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 + items: + description: |- + ParentReference identifies an API object (usually a Gateway) that can be considered + a parent of this resource (usually a route). There are two kinds of parent resources + with "Core" support: + + * Gateway (Gateway conformance profile) + * Service (Mesh conformance profile, ClusterIP Services only) + + This API may be extended in the future to support additional kinds of parent + resources. + + The API object must be valid in the cluster; the Group and Kind must + be registered in the cluster for this reference to be valid. + properties: + group: + default: gateway.networking.k8s.io + description: |- + Group is the group of the referent. + When unspecified, "gateway.networking.k8s.io" is inferred. + To set the core API group (such as for a "Service" kind referent), + Group must be explicitly set to "" (empty string). + + Support: Core + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Gateway + description: |- + Kind is kind of the referent. + + There are two kinds of parent resources with "Core" support: + + * Gateway (Gateway conformance profile) + * Service (Mesh conformance profile, ClusterIP Services only) + + Support for other resources is Implementation-Specific. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: |- + Name is the name of the referent. + + Support: Core + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the referent. When unspecified, this refers + to the local namespace of the Route. + + Note that there are specific rules for ParentRefs which cross namespace + boundaries. Cross-namespace references are only valid if they are explicitly + allowed by something in the namespace they are referring to. For example: + Gateway has the AllowedRoutes field, and ReferenceGrant provides a + generic way to enable any other kind of cross-namespace reference. + + + ParentRefs from a Route to a Service in the same namespace are "producer" + routes, which apply default routing rules to inbound connections from + any namespace to the Service. + + ParentRefs from a Route to a Service in a different namespace are + "consumer" routes, and these routing rules are only applied to outbound + connections originating from the same namespace as the Route, for which + the intended destination of the connections are a Service targeted as a + ParentRef of the Route. + + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: |- + Port is the network port this Route targets. It can be interpreted + differently based on the type of parent resource. + + When the parent resource is a Gateway, this targets all listeners + listening on the specified port that also support this kind of Route(and + select this Route). It's not recommended to set `Port` unless the + networking behaviors specified in a Route must apply to a specific port + as opposed to a listener(s) whose port(s) may be changed. When both Port + and SectionName are specified, the name and port of the selected listener + must match both specified values. + + + When the parent resource is a Service, this targets a specific port in the + Service spec. When both Port (experimental) and SectionName are specified, + the name and port of the selected port must match both specified values. + + + Implementations MAY choose to support other parent resources. + Implementations supporting other types of parent resources MUST clearly + document how/if Port is interpreted. + + For the purpose of status, an attachment is considered successful as + long as the parent resource accepts it partially. For example, Gateway + listeners can restrict which Routes can attach to them by Route kind, + namespace, or hostname. If 1 of 2 Gateway listeners accept attachment + from the referencing Route, the Route MUST be considered successfully + attached. If no Gateway listeners accept attachment from this Route, + the Route MUST be considered detached from the Gateway. + + Support: Extended + format: int32 + maximum: 65535 + minimum: 1 + type: integer + sectionName: + description: |- + SectionName is the name of a section within the target resource. In the + following resources, SectionName is interpreted as the following: + + * Gateway: Listener name. When both Port (experimental) and SectionName + are specified, the name and port of the selected listener must match + both specified values. + * Service: Port name. When both Port (experimental) and SectionName + are specified, the name and port of the selected listener must match + both specified values. + + Implementations MAY choose to support attaching Routes to other resources. + If that is the case, they MUST clearly document how SectionName is + interpreted. + + When unspecified (empty string), this will reference the entire resource. + For the purpose of status, an attachment is considered successful if at + least one section in the parent resource accepts it. For example, Gateway + listeners can restrict which Routes can attach to them by Route kind, + namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from + the referencing Route, the Route MUST be considered successfully + attached. If no Gateway listeners accept attachment from this Route, the + Route MUST be considered detached from the Gateway. + + Support: Core + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - name + type: object + type: array + x-kubernetes-list-type: atomic + podTemplate: + description: |- + Optional pod template used to configure the ACME challenge solver pods + used for HTTP01 challenges. + properties: + metadata: + description: |- + 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. + properties: + annotations: + additionalProperties: + type: string + description: Annotations that should be added to the created ACME HTTP01 solver pods. + type: object + labels: + additionalProperties: + type: string + description: Labels that should be added to the created ACME HTTP01 solver pods. + type: object + type: object + spec: + description: |- + PodSpec defines overrides for the HTTP01 challenge solver pod. + Check ACMEChallengeSolverHTTP01IngressPodSpec to find out currently supported fields. + All other fields will be ignored. + properties: + affinity: + description: If specified, the pod's scheduling constraints + properties: + nodeAffinity: + description: Describes node affinity scheduling rules for the pod. + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node matches the corresponding matchExpressions; the + node(s) with the highest sum are the most preferred. + items: + description: |- + An empty preferred scheduling term matches all objects with implicit weight 0 + (i.e. it's a no-op). A null preferred scheduling term matches no objects (i.e. is also a no-op). + properties: + preference: + description: A node selector term, associated with the corresponding weight. + properties: + matchExpressions: + description: A list of node selector requirements by node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: The label key that the selector applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: A list of node selector requirements by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: The label key that the selector applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + weight: + description: Weight associated with matching the corresponding nodeSelectorTerm, in the range 1-100. + format: int32 + type: integer + required: + - preference + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to an update), the system + may or may not try to eventually evict the pod from its node. + properties: + nodeSelectorTerms: + description: Required. A list of node selector terms. The terms are ORed. + items: + description: |- + A null or empty node selector term matches no objects. The requirements of + them are ANDed. + The TopologySelectorTerm type implements a subset of the NodeSelectorTerm. + properties: + matchExpressions: + description: A list of node selector requirements by node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: The label key that the selector applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: A list of node selector requirements by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: The label key that the selector applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + type: array + x-kubernetes-list-type: atomic + required: + - nodeSelectorTerms + type: object + x-kubernetes-map-type: atomic + type: object + podAffinity: + description: Describes pod affinity scheduling rules (e.g. co-locate this pod in the same node, zone, etc. as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: The weights of all of the matched WeightedPodAffinityTerm fields are added per-node to find the most preferred node(s) + properties: + podAffinityTerm: + description: Required. A pod affinity term, associated with the corresponding weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + podAntiAffinity: + description: Describes pod anti-affinity scheduling rules (e.g. avoid putting this pod in the same node, zone, etc. as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the anti-affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling anti-affinity expressions, etc.), + compute a sum by iterating through the elements of this field and subtracting + "weight" from the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: The weights of all of the matched WeightedPodAffinityTerm fields are added per-node to find the most preferred node(s) + properties: + podAffinityTerm: + description: Required. A pod affinity term, associated with the corresponding weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the anti-affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the anti-affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + type: object + imagePullSecrets: + description: If specified, the pod's imagePullSecrets + items: + description: |- + LocalObjectReference contains enough information to let you locate the + referenced object inside the same namespace. + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + type: object + x-kubernetes-map-type: atomic + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + nodeSelector: + additionalProperties: + type: string + description: |- + 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/ + type: object + priorityClassName: + description: If specified, the pod's priorityClassName. + type: string + resources: + description: |- + If specified, the pod's resource requirements. + These values override the global resource configuration flags. + Note that when only specifying resource limits, ensure they are greater than or equal + to the corresponding global resource requests configured via controller flags + (--acme-http01-solver-resource-request-cpu, --acme-http01-solver-resource-request-memory). + Kubernetes will reject pod creation if limits are lower than requests, causing challenge failures. + properties: + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to the global values configured via controller flags. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + securityContext: + description: If specified, the pod's security context + properties: + fsGroup: + description: |- + A special supplemental group that applies to all containers in a pod. + Some volume types allow the Kubelet to change the ownership of that volume + to be owned by the pod: + + 1. The owning GID will be the FSGroup + 2. The setgid bit is set (new files created in the volume will be owned by FSGroup) + 3. The permission bits are OR'd with rw-rw---- + + If unset, the Kubelet will not modify the ownership and permissions of any volume. + Note that this field cannot be set when spec.os.name is windows. + format: int64 + type: integer + fsGroupChangePolicy: + description: |- + fsGroupChangePolicy defines behavior of changing ownership and permission of the volume + before being exposed inside Pod. This field will only apply to + volume types which support fsGroup based ownership(and permissions). + It will have no effect on ephemeral volume types such as: secret, configmaps + and emptydir. + Valid values are "OnRootMismatch" and "Always". If not specified, "Always" is used. + Note that this field cannot be set when spec.os.name is windows. + type: string + runAsGroup: + description: |- + The GID to run the entrypoint of the container process. + Uses runtime default if unset. + May also be set in SecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext takes precedence + for that container. + Note that this field cannot be set when spec.os.name is windows. + format: int64 + type: integer + runAsNonRoot: + description: |- + Indicates that the container must run as a non-root user. + If true, the Kubelet will validate the image at runtime to ensure that it + does not run as UID 0 (root) and fail to start the container if it does. + If unset or false, no such validation will be performed. + May also be set in SecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext takes precedence. + type: boolean + runAsUser: + description: |- + The UID to run the entrypoint of the container process. + Defaults to user specified in image metadata if unspecified. + May also be set in SecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext takes precedence + for that container. + Note that this field cannot be set when spec.os.name is windows. + format: int64 + type: integer + seLinuxOptions: + description: |- + The SELinux context to be applied to all containers. + If unspecified, the container runtime will allocate a random SELinux context for each + container. May also be set in SecurityContext. If set in + both SecurityContext and PodSecurityContext, the value specified in SecurityContext + takes precedence for that container. + Note that this field cannot be set when spec.os.name is windows. + properties: + level: + description: Level is SELinux level label that applies to the container. + type: string + role: + description: Role is a SELinux role label that applies to the container. + type: string + type: + description: Type is a SELinux type label that applies to the container. + type: string + user: + description: User is a SELinux user label that applies to the container. + type: string + type: object + seccompProfile: + description: |- + The seccomp options to use by the containers in this pod. + Note that this field cannot be set when spec.os.name is windows. + properties: + localhostProfile: + description: |- + localhostProfile indicates a profile defined in a file on the node should be used. + The profile must be preconfigured on the node to work. + Must be a descending path, relative to the kubelet's configured seccomp profile location. + Must be set if type is "Localhost". Must NOT be set for any other type. + type: string + type: + description: |- + type indicates which kind of seccomp profile will be applied. + Valid options are: + + Localhost - a profile defined in a file on the node should be used. + RuntimeDefault - the container runtime default profile should be used. + Unconfined - no profile should be applied. + type: string + required: + - type + type: object + supplementalGroups: + description: |- + A list of groups applied to the first process run in each container, in addition + to the container's primary GID, the fsGroup (if specified), and group memberships + defined in the container image for the uid of the container process. If unspecified, + no additional groups are added to any container. Note that group memberships + defined in the container image for the uid of the container process are still effective, + even if they are not included in this list. + Note that this field cannot be set when spec.os.name is windows. + items: + format: int64 + type: integer + type: array + x-kubernetes-list-type: atomic + sysctls: + description: |- + Sysctls hold a list of namespaced sysctls used for the pod. Pods with unsupported + sysctls (by the container runtime) might fail to launch. + Note that this field cannot be set when spec.os.name is windows. + items: + description: Sysctl defines a kernel parameter to be set + properties: + name: + description: Name of a property to set + type: string + value: + description: Value of a property to set + type: string + required: + - name + - value + type: object + type: array + x-kubernetes-list-type: atomic + type: object + serviceAccountName: + description: If specified, the pod's service account + type: string + tolerations: + description: If specified, the pod's tolerations. + items: + description: |- + The pod this Toleration is attached to tolerates any taint that matches + the triple using the matching operator . + properties: + effect: + description: |- + Effect indicates the taint effect to match. Empty means match all taint effects. + When specified, allowed values are NoSchedule, PreferNoSchedule and NoExecute. + type: string + key: + description: |- + Key is the taint key that the toleration applies to. Empty means match all taint keys. + If the key is empty, operator must be Exists; this combination means to match all values and all keys. + type: string + operator: + description: |- + Operator represents a key's relationship to the value. + Valid operators are Exists and Equal. Defaults to Equal. + Exists is equivalent to wildcard for value, so that a pod can + tolerate all taints of a particular category. + type: string + tolerationSeconds: + description: |- + TolerationSeconds represents the period of time the toleration (which must be + of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default, + it is not set, which means tolerate the taint forever (do not evict). Zero and + negative values will be treated as 0 (evict immediately) by the system. + format: int64 + type: integer + value: + description: |- + Value is the taint value the toleration matches to. + If the operator is Exists, the value should be empty, otherwise just a regular string. + type: string + type: object + type: array + x-kubernetes-list-type: atomic + type: object + type: object + serviceType: + description: |- + Optional service type for Kubernetes solver service. Supported values + are NodePort or ClusterIP. If unset, defaults to NodePort. + type: string + type: object + ingress: + description: |- + 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. + properties: + class: + description: |- + 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. + type: string + ingressClassName: + description: |- + 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. + type: string + ingressTemplate: + description: |- + Optional ingress template used to configure the ACME challenge solver + ingress used for HTTP01 challenges. + properties: + metadata: + description: |- + 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. + properties: + annotations: + additionalProperties: + type: string + description: Annotations that should be added to the created ACME HTTP01 solver ingress. + type: object + labels: + additionalProperties: + type: string + description: Labels that should be added to the created ACME HTTP01 solver ingress. + type: object + type: object + type: object + name: + description: |- + 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. + type: string + podTemplate: + description: |- + Optional pod template used to configure the ACME challenge solver pods + used for HTTP01 challenges. + properties: + metadata: + description: |- + 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. + properties: + annotations: + additionalProperties: + type: string + description: Annotations that should be added to the created ACME HTTP01 solver pods. + type: object + labels: + additionalProperties: + type: string + description: Labels that should be added to the created ACME HTTP01 solver pods. + type: object + type: object + spec: + description: |- + PodSpec defines overrides for the HTTP01 challenge solver pod. + Check ACMEChallengeSolverHTTP01IngressPodSpec to find out currently supported fields. + All other fields will be ignored. + properties: + affinity: + description: If specified, the pod's scheduling constraints + properties: + nodeAffinity: + description: Describes node affinity scheduling rules for the pod. + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node matches the corresponding matchExpressions; the + node(s) with the highest sum are the most preferred. + items: + description: |- + An empty preferred scheduling term matches all objects with implicit weight 0 + (i.e. it's a no-op). A null preferred scheduling term matches no objects (i.e. is also a no-op). + properties: + preference: + description: A node selector term, associated with the corresponding weight. + properties: + matchExpressions: + description: A list of node selector requirements by node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: The label key that the selector applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: A list of node selector requirements by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: The label key that the selector applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + weight: + description: Weight associated with matching the corresponding nodeSelectorTerm, in the range 1-100. + format: int32 + type: integer + required: + - preference + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to an update), the system + may or may not try to eventually evict the pod from its node. + properties: + nodeSelectorTerms: + description: Required. A list of node selector terms. The terms are ORed. + items: + description: |- + A null or empty node selector term matches no objects. The requirements of + them are ANDed. + The TopologySelectorTerm type implements a subset of the NodeSelectorTerm. + properties: + matchExpressions: + description: A list of node selector requirements by node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: The label key that the selector applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: A list of node selector requirements by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: The label key that the selector applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + type: array + x-kubernetes-list-type: atomic + required: + - nodeSelectorTerms + type: object + x-kubernetes-map-type: atomic + type: object + podAffinity: + description: Describes pod affinity scheduling rules (e.g. co-locate this pod in the same node, zone, etc. as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: The weights of all of the matched WeightedPodAffinityTerm fields are added per-node to find the most preferred node(s) + properties: + podAffinityTerm: + description: Required. A pod affinity term, associated with the corresponding weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + podAntiAffinity: + description: Describes pod anti-affinity scheduling rules (e.g. avoid putting this pod in the same node, zone, etc. as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the anti-affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling anti-affinity expressions, etc.), + compute a sum by iterating through the elements of this field and subtracting + "weight" from the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: The weights of all of the matched WeightedPodAffinityTerm fields are added per-node to find the most preferred node(s) + properties: + podAffinityTerm: + description: Required. A pod affinity term, associated with the corresponding weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the anti-affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the anti-affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + type: object + imagePullSecrets: + description: If specified, the pod's imagePullSecrets + items: + description: |- + LocalObjectReference contains enough information to let you locate the + referenced object inside the same namespace. + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + type: object + x-kubernetes-map-type: atomic + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + nodeSelector: + additionalProperties: + type: string + description: |- + 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/ + type: object + priorityClassName: + description: If specified, the pod's priorityClassName. + type: string + resources: + description: |- + If specified, the pod's resource requirements. + These values override the global resource configuration flags. + Note that when only specifying resource limits, ensure they are greater than or equal + to the corresponding global resource requests configured via controller flags + (--acme-http01-solver-resource-request-cpu, --acme-http01-solver-resource-request-memory). + Kubernetes will reject pod creation if limits are lower than requests, causing challenge failures. + properties: + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to the global values configured via controller flags. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + securityContext: + description: If specified, the pod's security context + properties: + fsGroup: + description: |- + A special supplemental group that applies to all containers in a pod. + Some volume types allow the Kubelet to change the ownership of that volume + to be owned by the pod: + + 1. The owning GID will be the FSGroup + 2. The setgid bit is set (new files created in the volume will be owned by FSGroup) + 3. The permission bits are OR'd with rw-rw---- + + If unset, the Kubelet will not modify the ownership and permissions of any volume. + Note that this field cannot be set when spec.os.name is windows. + format: int64 + type: integer + fsGroupChangePolicy: + description: |- + fsGroupChangePolicy defines behavior of changing ownership and permission of the volume + before being exposed inside Pod. This field will only apply to + volume types which support fsGroup based ownership(and permissions). + It will have no effect on ephemeral volume types such as: secret, configmaps + and emptydir. + Valid values are "OnRootMismatch" and "Always". If not specified, "Always" is used. + Note that this field cannot be set when spec.os.name is windows. + type: string + runAsGroup: + description: |- + The GID to run the entrypoint of the container process. + Uses runtime default if unset. + May also be set in SecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext takes precedence + for that container. + Note that this field cannot be set when spec.os.name is windows. + format: int64 + type: integer + runAsNonRoot: + description: |- + Indicates that the container must run as a non-root user. + If true, the Kubelet will validate the image at runtime to ensure that it + does not run as UID 0 (root) and fail to start the container if it does. + If unset or false, no such validation will be performed. + May also be set in SecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext takes precedence. + type: boolean + runAsUser: + description: |- + The UID to run the entrypoint of the container process. + Defaults to user specified in image metadata if unspecified. + May also be set in SecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext takes precedence + for that container. + Note that this field cannot be set when spec.os.name is windows. + format: int64 + type: integer + seLinuxOptions: + description: |- + The SELinux context to be applied to all containers. + If unspecified, the container runtime will allocate a random SELinux context for each + container. May also be set in SecurityContext. If set in + both SecurityContext and PodSecurityContext, the value specified in SecurityContext + takes precedence for that container. + Note that this field cannot be set when spec.os.name is windows. + properties: + level: + description: Level is SELinux level label that applies to the container. + type: string + role: + description: Role is a SELinux role label that applies to the container. + type: string + type: + description: Type is a SELinux type label that applies to the container. + type: string + user: + description: User is a SELinux user label that applies to the container. + type: string + type: object + seccompProfile: + description: |- + The seccomp options to use by the containers in this pod. + Note that this field cannot be set when spec.os.name is windows. + properties: + localhostProfile: + description: |- + localhostProfile indicates a profile defined in a file on the node should be used. + The profile must be preconfigured on the node to work. + Must be a descending path, relative to the kubelet's configured seccomp profile location. + Must be set if type is "Localhost". Must NOT be set for any other type. + type: string + type: + description: |- + type indicates which kind of seccomp profile will be applied. + Valid options are: + + Localhost - a profile defined in a file on the node should be used. + RuntimeDefault - the container runtime default profile should be used. + Unconfined - no profile should be applied. + type: string + required: + - type + type: object + supplementalGroups: + description: |- + A list of groups applied to the first process run in each container, in addition + to the container's primary GID, the fsGroup (if specified), and group memberships + defined in the container image for the uid of the container process. If unspecified, + no additional groups are added to any container. Note that group memberships + defined in the container image for the uid of the container process are still effective, + even if they are not included in this list. + Note that this field cannot be set when spec.os.name is windows. + items: + format: int64 + type: integer + type: array + x-kubernetes-list-type: atomic + sysctls: + description: |- + Sysctls hold a list of namespaced sysctls used for the pod. Pods with unsupported + sysctls (by the container runtime) might fail to launch. + Note that this field cannot be set when spec.os.name is windows. + items: + description: Sysctl defines a kernel parameter to be set + properties: + name: + description: Name of a property to set + type: string + value: + description: Value of a property to set + type: string + required: + - name + - value + type: object + type: array + x-kubernetes-list-type: atomic + type: object + serviceAccountName: + description: If specified, the pod's service account + type: string + tolerations: + description: If specified, the pod's tolerations. + items: + description: |- + The pod this Toleration is attached to tolerates any taint that matches + the triple using the matching operator . + properties: + effect: + description: |- + Effect indicates the taint effect to match. Empty means match all taint effects. + When specified, allowed values are NoSchedule, PreferNoSchedule and NoExecute. + type: string + key: + description: |- + Key is the taint key that the toleration applies to. Empty means match all taint keys. + If the key is empty, operator must be Exists; this combination means to match all values and all keys. + type: string + operator: + description: |- + Operator represents a key's relationship to the value. + Valid operators are Exists and Equal. Defaults to Equal. + Exists is equivalent to wildcard for value, so that a pod can + tolerate all taints of a particular category. + type: string + tolerationSeconds: + description: |- + TolerationSeconds represents the period of time the toleration (which must be + of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default, + it is not set, which means tolerate the taint forever (do not evict). Zero and + negative values will be treated as 0 (evict immediately) by the system. + format: int64 + type: integer + value: + description: |- + Value is the taint value the toleration matches to. + If the operator is Exists, the value should be empty, otherwise just a regular string. + type: string + type: object + type: array + x-kubernetes-list-type: atomic + type: object + type: object + serviceType: + description: |- + Optional service type for Kubernetes solver service. Supported values + are NodePort or ClusterIP. If unset, defaults to NodePort. + type: string + type: object + type: object + selector: + description: |- + 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. + properties: + dnsNames: + description: |- + 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. + items: + type: string + type: array + x-kubernetes-list-type: atomic + dnsZones: + description: |- + 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. + items: + type: string + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + A label selector that is used to refine the set of certificate's that + this challenge solver will apply to. + type: object + type: object + type: object + type: array + x-kubernetes-list-type: atomic + required: + - privateKeySecretRef + - server + type: object + ca: + description: |- + 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. + properties: + crlDistributionPoints: + description: |- + 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. + items: + type: string + type: array + x-kubernetes-list-type: atomic + issuingCertificateURLs: + description: |- + IssuingCertificateURLs is a list of URLs which this issuer should embed into certificates + it creates. See https://www.rfc-editor.org/rfc/rfc5280#section-4.2.2.1 for more details. + As an example, such a URL might be "http://ca.domain.com/ca.crt". + items: + type: string + type: array + x-kubernetes-list-type: atomic + ocspServers: + description: |- + 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". + items: + type: string + type: array + x-kubernetes-list-type: atomic + secretName: + description: |- + SecretName is the name of the secret used to sign Certificates issued + by this Issuer. + type: string + required: + - secretName + type: object + selfSigned: + description: |- + SelfSigned configures this issuer to 'self sign' certificates using the + private key used to create the CertificateRequest object. + properties: + crlDistributionPoints: + description: |- + 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. + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + vault: + description: |- + Vault configures this issuer to sign certificates using a HashiCorp Vault + PKI backend. + properties: + auth: + description: Auth configures how cert-manager authenticates with the Vault server. + properties: + appRole: + description: |- + AppRole authenticates with Vault using the App Role auth mechanism, + with the role and secret stored in a Kubernetes Secret resource. + properties: + path: + description: |- + Path where the App Role authentication backend is mounted in Vault, e.g: + "approle" + type: string + roleId: + description: |- + RoleID configured in the App Role authentication backend when setting + up the authentication backend in Vault. + type: string + secretRef: + description: |- + 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. + properties: + key: + description: |- + 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. + type: string + name: + description: |- + Name of the resource being referred to. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + required: + - name + type: object + required: + - path + - roleId + - secretRef + type: object + clientCertificate: + description: |- + ClientCertificate authenticates with Vault by presenting a client + certificate during the request's TLS handshake. + Works only when using HTTPS protocol. + properties: + mountPath: + description: |- + 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/cert" will be used. + type: string + name: + description: |- + Name of the certificate role to authenticate against. + If not set, matching any certificate role, if available. + type: string + secretName: + description: |- + Reference to Kubernetes Secret of type "kubernetes.io/tls" (hence containing + tls.crt and tls.key) used to authenticate to Vault using TLS client + authentication. + type: string + type: object + kubernetes: + description: |- + Kubernetes authenticates with Vault by passing the ServiceAccount + token stored in the named Secret resource to the Vault server. + properties: + mountPath: + description: |- + 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. + type: string + role: + description: |- + A required field containing the Vault Role to assume. A Role binds a + Kubernetes ServiceAccount with a set of Vault policies. + type: string + secretRef: + description: |- + The required Secret field containing a Kubernetes ServiceAccount JWT used + for authenticating with Vault. Use of 'ambient credentials' is not + supported. + properties: + key: + description: |- + 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. + type: string + name: + description: |- + Name of the resource being referred to. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + required: + - name + type: object + serviceAccountRef: + description: |- + 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. + properties: + audiences: + description: |- + TokenAudiences is an optional list of extra audiences to include in the token passed to Vault. The default token + consisting of the issuer's namespace and name is always included. + items: + type: string + type: array + x-kubernetes-list-type: atomic + name: + description: Name of the ServiceAccount used to request a token. + type: string + required: + - name + type: object + required: + - role + type: object + tokenSecretRef: + description: TokenSecretRef authenticates with Vault by presenting a token. + properties: + key: + description: |- + 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. + type: string + name: + description: |- + Name of the resource being referred to. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + required: + - name + type: object + type: object + caBundle: + description: |- + 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. + format: byte + type: string + caBundleSecretRef: + description: |- + 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'. + properties: + key: + description: |- + 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. + type: string + name: + description: |- + Name of the resource being referred to. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + required: + - name + type: object + clientCertSecretRef: + description: |- + Reference to a Secret containing a PEM-encoded Client Certificate to use when the + Vault server requires mTLS. + properties: + key: + description: |- + 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. + type: string + name: + description: |- + Name of the resource being referred to. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + required: + - name + type: object + clientKeySecretRef: + description: |- + Reference to a Secret containing a PEM-encoded Client Private Key to use when the + Vault server requires mTLS. + properties: + key: + description: |- + 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. + type: string + name: + description: |- + Name of the resource being referred to. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + required: + - name + type: object + namespace: + description: |- + 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 + type: string + path: + description: |- + Path is the mount path of the Vault PKI backend's `sign` endpoint, e.g: + "my_pki_mount/sign/my-role-name". + type: string + server: + description: 'Server is the connection address for the Vault server, e.g: "https://vault.example.com:8200".' + type: string + serverName: + description: |- + ServerName is used to verify the hostname on the returned certificates + by the Vault server. + type: string + required: + - auth + - path + - server + type: object + venafi: + description: |- + Venafi configures this issuer to sign certificates using a Venafi TPP + or Venafi Cloud policy zone. + properties: + cloud: + description: |- + Cloud specifies the Venafi cloud configuration settings. + Only one of TPP or Cloud may be specified. + properties: + apiTokenSecretRef: + description: APITokenSecretRef is a secret key selector for the Venafi Cloud API token. + properties: + key: + description: |- + 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. + type: string + name: + description: |- + Name of the resource being referred to. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + required: + - name + type: object + url: + description: |- + URL is the base URL for Venafi Cloud. + Defaults to "https://api.venafi.cloud/". + type: string + required: + - apiTokenSecretRef + type: object + tpp: + description: |- + TPP specifies Trust Protection Platform configuration settings. + Only one of TPP or Cloud may be specified. + properties: + caBundle: + description: |- + 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. + format: byte + type: string + caBundleSecretRef: + description: |- + Reference to a Secret containing a 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. Mutually exclusive with CABundle. + If neither CABundle nor CABundleSecretRef is defined, the certificate bundle in + the cert-manager controller container is used to validate the TLS connection. + properties: + key: + description: |- + 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. + type: string + name: + description: |- + Name of the resource being referred to. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + required: + - name + type: object + credentialsRef: + description: |- + CredentialsRef is a reference to a Secret containing the Venafi TPP API credentials. + The secret must contain the key 'access-token' for the Access Token Authentication, + or two keys, 'username' and 'password' for the API Keys Authentication. + properties: + name: + description: |- + Name of the resource being referred to. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + required: + - name + type: object + url: + description: |- + URL is the base URL for the vedsdk endpoint of the Venafi TPP instance, + for example: "https://tpp.example.com/vedsdk". + type: string + required: + - credentialsRef + - url + type: object + zone: + description: |- + 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. + type: string + required: + - zone + type: object + type: object + status: + description: Status of the Issuer. This is set and managed automatically. + properties: + acme: + description: |- + ACME specific status options. + This field should only be set if the Issuer is configured to use an ACME + server to issue certificates. + properties: + lastPrivateKeyHash: + description: |- + 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 + type: string + lastRegisteredEmail: + 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 + type: string + uri: + description: |- + URI is the unique account identifier, which can also be used to retrieve + account details from the CA + type: string + type: object + conditions: + description: |- + List of status conditions to indicate the status of a CertificateRequest. + Known condition types are `Ready`. + items: + description: IssuerCondition contains condition information for an Issuer. + properties: + lastTransitionTime: + description: |- + LastTransitionTime is the timestamp corresponding to the last status + change of this condition. + format: date-time + type: string + message: + description: |- + Message is a human readable description of the details of the last + transition, complementing reason. + type: string + observedGeneration: + description: |- + 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. + format: int64 + type: integer + reason: + description: |- + Reason is a brief machine readable explanation for the condition's last + transition. + type: string + status: + description: Status of the condition, one of (`True`, `False`, `Unknown`). + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: Type of the condition, known values are (`Ready`). + type: string + required: + - status + - type + type: object + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} +{{- end }} diff --git a/deploy/charts/cert-manager/templates/deployment.yaml b/deploy/charts/cert-manager/templates/deployment.yaml index 7f99a979653..71472fa7f7e 100644 --- a/deploy/charts/cert-manager/templates/deployment.yaml +++ b/deploy/charts/cert-manager/templates/deployment.yaml @@ -15,6 +15,10 @@ metadata: {{- end }} spec: replicas: {{ .Values.replicaCount }} + {{- /* The if statement below is equivalent to {{- if $value }} but will also return true for 0. */ -}} + {{- if not (has (quote .Values.global.revisionHistoryLimit) (list "" (quote ""))) }} + revisionHistoryLimit: {{ .Values.global.revisionHistoryLimit }} + {{- end }} selector: matchLabels: app.kubernetes.io/name: {{ template "cert-manager.name" . }} @@ -39,7 +43,7 @@ spec: annotations: {{- toYaml . | nindent 8 }} {{- end }} - {{- if and .Values.prometheus.enabled (not .Values.prometheus.servicemonitor.enabled) }} + {{- if and .Values.prometheus.enabled (not (or .Values.prometheus.servicemonitor.enabled .Values.prometheus.podmonitor.enabled)) }} {{- if not .Values.podAnnotations }} annotations: {{- end }} @@ -48,31 +52,51 @@ spec: prometheus.io/port: '9402' {{- end }} spec: + {{- if not .Values.serviceAccount.create }} + {{- with .Values.global.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- end }} serviceAccountName: {{ template "cert-manager.serviceAccountName" . }} {{- if hasKey .Values "automountServiceAccountToken" }} automountServiceAccountToken: {{ .Values.automountServiceAccountToken }} {{- end }} + enableServiceLinks: {{ .Values.enableServiceLinks }} {{- with .Values.global.priorityClassName }} priorityClassName: {{ . | quote }} {{- end }} + {{- if (hasKey .Values.global "hostUsers") }} + hostUsers: {{ .Values.global.hostUsers }} + {{- end }} {{- with .Values.securityContext }} securityContext: {{- toYaml . | nindent 8 }} {{- end }} - {{- with .Values.volumes }} + {{- if or .Values.volumes .Values.config}} volumes: + {{- if .Values.config }} + - name: config + configMap: + name: {{ include "cert-manager.fullname" . }} + {{- end }} + {{ with .Values.volumes }} {{- toYaml . | nindent 8 }} + {{- end }} {{- end }} containers: - name: {{ .Chart.Name }}-controller - {{- with .Values.image }} - image: "{{- if .registry -}}{{ .registry }}/{{- end -}}{{ .repository }}{{- if (.digest) -}} @{{ .digest }}{{- else -}}:{{ default $.Chart.AppVersion .tag }} {{- end -}}" - {{- end }} + image: "{{ template "image" (tuple .Values.image $.Chart.AppVersion) }}" imagePullPolicy: {{ .Values.image.pullPolicy }} args: - {{- if .Values.global.logLevel }} + {{- /* The if statement below is equivalent to {{- if $value }} but will also return true for 0. */ -}} + {{- if not (has (quote .Values.global.logLevel) (list "" (quote ""))) }} - --v={{ .Values.global.logLevel }} {{- end }} + {{- if .Values.config }} + - --config=/var/cert-manager/config/config.yaml + {{- end }} + {{- $config := default .Values.config "" }} {{- if .Values.clusterResourceNamespace }} - --cluster-resource-namespace={{ .Values.clusterResourceNamespace }} {{- else }} @@ -90,6 +114,9 @@ spec: - --leader-election-retry-period={{ .retryPeriod }} {{- end }} {{- end }} + {{- with .Values.acmesolver.image }} + - --acme-http01-solver-image={{- if .registry -}}{{ .registry }}/{{- end -}}{{ .repository }}{{- if (.digest) -}} @{{ .digest }}{{- else -}}:{{ default $.Chart.AppVersion .tag }} {{- end -}} + {{- end }} {{- with .Values.extraArgs }} {{- toYaml . | nindent 10 }} {{- end }} @@ -107,17 +134,41 @@ spec: {{- if .Values.featureGates }} - --feature-gates={{ .Values.featureGates }} {{- end }} + {{- if .Values.maxConcurrentChallenges }} + - --max-concurrent-challenges={{ .Values.maxConcurrentChallenges }} + {{- end }} + {{- if .Values.enableCertificateOwnerRef }} + - --enable-certificate-owner-ref=true + {{- end }} + {{- if .Values.dns01RecursiveNameserversOnly }} + - --dns01-recursive-nameservers-only=true + {{- end }} + {{- with .Values.dns01RecursiveNameservers }} + - --dns01-recursive-nameservers={{ . }} + {{- end }} + {{- if .Values.disableAutoApproval }} + - --controllers=-certificaterequests-approver + {{- end }} ports: - containerPort: 9402 name: http-metrics protocol: TCP + - containerPort: 9403 + name: http-healthz + protocol: TCP {{- with .Values.containerSecurityContext }} securityContext: {{- toYaml . | nindent 12 }} {{- end }} - {{- with .Values.volumeMounts }} + {{- if or .Values.config .Values.volumeMounts }} volumeMounts: + {{- if .Values.config }} + - name: config + mountPath: /var/cert-manager/config + {{- end }} + {{- with .Values.volumeMounts }} {{- toYaml . | nindent 12 }} + {{- end }} {{- end }} env: - name: POD_NAMESPACE @@ -143,9 +194,29 @@ spec: resources: {{- toYaml . | nindent 12 }} {{- end }} - {{- with .Values.nodeSelector }} + + {{- with .Values.livenessProbe }} + {{- if .enabled }} + # LivenessProbe settings are based on those used for the Kubernetes + # controller-manager. See: + # https://github.com/kubernetes/kubernetes/blob/806b30170c61a38fedd54cc9ede4cd6275a1ad3b/cmd/kubeadm/app/util/staticpod/utils.go#L241-L245 + livenessProbe: + httpGet: + port: http-healthz + path: /livez + scheme: HTTP + initialDelaySeconds: {{ .initialDelaySeconds }} + periodSeconds: {{ .periodSeconds }} + timeoutSeconds: {{ .timeoutSeconds }} + successThreshold: {{ .successThreshold }} + failureThreshold: {{ .failureThreshold }} + {{- end }} + {{- end }} + {{- with (coalesce .Values.nodeSelector .Values.global.nodeSelector) }} nodeSelector: - {{- toYaml . | nindent 8 }} + {{- range $key, $value := . }} + {{ $key }}: {{ $value | quote }} + {{- end }} {{- end }} {{- with .Values.affinity }} affinity: @@ -166,3 +237,6 @@ spec: dnsConfig: {{- toYaml . | nindent 8 }} {{- end }} + {{- with .Values.hostAliases }} + hostAliases: {{ toYaml . | nindent 8 }} + {{- end }} diff --git a/deploy/charts/cert-manager/templates/extras-objects.yaml b/deploy/charts/cert-manager/templates/extras-objects.yaml new file mode 100644 index 00000000000..9ec3a7e9b2b --- /dev/null +++ b/deploy/charts/cert-manager/templates/extras-objects.yaml @@ -0,0 +1,4 @@ +{{ range .Values.extraObjects }} +--- +{{ tpl . $ }} +{{ end }} diff --git a/deploy/charts/cert-manager/templates/networkpolicy-egress.yaml b/deploy/charts/cert-manager/templates/networkpolicy-egress.yaml index 09712009d66..37f90bd2ef7 100644 --- a/deploy/charts/cert-manager/templates/networkpolicy-egress.yaml +++ b/deploy/charts/cert-manager/templates/networkpolicy-egress.yaml @@ -11,13 +11,9 @@ spec: {{- end }} podSelector: matchLabels: - app: {{ include "webhook.name" . }} app.kubernetes.io/name: {{ include "webhook.name" . }} app.kubernetes.io/instance: {{ .Release.Name }} app.kubernetes.io/component: "webhook" - {{- with .Values.webhook.podLabels }} - {{- toYaml . | nindent 6 }} - {{- end }} policyTypes: - Egress {{- end }} diff --git a/deploy/charts/cert-manager/templates/networkpolicy-webhooks.yaml b/deploy/charts/cert-manager/templates/networkpolicy-webhooks.yaml index 349877a8b3e..3a0ed7a70af 100644 --- a/deploy/charts/cert-manager/templates/networkpolicy-webhooks.yaml +++ b/deploy/charts/cert-manager/templates/networkpolicy-webhooks.yaml @@ -12,13 +12,9 @@ spec: {{- end }} podSelector: matchLabels: - app: {{ include "webhook.name" . }} - app.kubernetes.io/name: {{ include "webhook.name" . }} - app.kubernetes.io/instance: {{ .Release.Name }} - app.kubernetes.io/component: "webhook" - {{- with .Values.webhook.podLabels }} - {{- toYaml . | nindent 6 }} - {{- end }} + app.kubernetes.io/name: {{ include "webhook.name" . }} + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/component: "webhook" policyTypes: - Ingress diff --git a/deploy/charts/cert-manager/templates/poddisruptionbudget.yaml b/deploy/charts/cert-manager/templates/poddisruptionbudget.yaml new file mode 100644 index 00000000000..ae71eed29cf --- /dev/null +++ b/deploy/charts/cert-manager/templates/poddisruptionbudget.yaml @@ -0,0 +1,29 @@ +{{- if .Values.podDisruptionBudget.enabled }} +apiVersion: policy/v1 +kind: PodDisruptionBudget +metadata: + name: {{ include "cert-manager.fullname" . }} + namespace: {{ include "cert-manager.namespace" . }} + labels: + app: {{ include "cert-manager.name" . }} + app.kubernetes.io/name: {{ include "cert-manager.name" . }} + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/component: "controller" + {{- include "labels" . | nindent 4 }} +spec: + selector: + matchLabels: + app.kubernetes.io/name: {{ include "cert-manager.name" . }} + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/component: "controller" + + {{- if not (or (hasKey .Values.podDisruptionBudget "minAvailable") (hasKey .Values.podDisruptionBudget "maxUnavailable")) }} + minAvailable: 1 # Default value because minAvailable and maxUnavailable are not set + {{- end }} + {{- if hasKey .Values.podDisruptionBudget "minAvailable" }} + minAvailable: {{ .Values.podDisruptionBudget.minAvailable }} + {{- end }} + {{- if hasKey .Values.podDisruptionBudget "maxUnavailable" }} + maxUnavailable: {{ .Values.podDisruptionBudget.maxUnavailable }} + {{- end }} +{{- end }} diff --git a/deploy/charts/cert-manager/templates/podmonitor.yaml b/deploy/charts/cert-manager/templates/podmonitor.yaml new file mode 100644 index 00000000000..72d2dfe5930 --- /dev/null +++ b/deploy/charts/cert-manager/templates/podmonitor.yaml @@ -0,0 +1,63 @@ +{{- if and .Values.prometheus.enabled (and .Values.prometheus.podmonitor.enabled .Values.prometheus.servicemonitor.enabled) }} +{{- fail "Either .Values.prometheus.podmonitor.enabled or .Values.prometheus.servicemonitor.enabled can be enabled at a time, but not both." }} +{{- else if and .Values.prometheus.enabled .Values.prometheus.podmonitor.enabled }} +apiVersion: monitoring.coreos.com/v1 +kind: PodMonitor +metadata: + name: {{ template "cert-manager.fullname" . }} +{{- if .Values.prometheus.podmonitor.namespace }} + namespace: {{ .Values.prometheus.podmonitor.namespace }} +{{- else }} + namespace: {{ include "cert-manager.namespace" . }} +{{- end }} + labels: + app: {{ include "cert-manager.name" . }} + app.kubernetes.io/name: {{ include "cert-manager.name" . }} + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/component: "controller" + {{- include "labels" . | nindent 4 }} + prometheus: {{ .Values.prometheus.podmonitor.prometheusInstance }} + {{- with .Values.prometheus.podmonitor.labels }} + {{- toYaml . | nindent 4 }} + {{- end }} +{{- if .Values.prometheus.podmonitor.annotations }} + annotations: + {{- with .Values.prometheus.podmonitor.annotations }} + {{- toYaml . | nindent 4 }} + {{- end }} +{{- end }} +spec: + jobLabel: app.kubernetes.io/name + selector: + matchExpressions: + - key: app.kubernetes.io/name + operator: In + values: + - {{ include "cainjector.name" . }} + - {{ template "cert-manager.name" . }} + - {{ include "webhook.name" . }} + - key: app.kubernetes.io/instance + operator: In + values: + - {{ .Release.Name }} + - key: app.kubernetes.io/component + operator: In + values: + - cainjector + - controller + - webhook +{{- if .Values.prometheus.podmonitor.namespace }} + namespaceSelector: + matchNames: + - {{ include "cert-manager.namespace" . }} +{{- end }} + podMetricsEndpoints: + - port: http-metrics + path: {{ .Values.prometheus.podmonitor.path }} + interval: {{ .Values.prometheus.podmonitor.interval }} + scrapeTimeout: {{ .Values.prometheus.podmonitor.scrapeTimeout }} + honorLabels: {{ .Values.prometheus.podmonitor.honorLabels }} + {{- with .Values.prometheus.podmonitor.endpointAdditionalProperties }} + {{- toYaml . | nindent 6 }} + {{- end }} +{{- end }} diff --git a/deploy/charts/cert-manager/templates/rbac.yaml b/deploy/charts/cert-manager/templates/rbac.yaml index 361b1a223cd..7acd5711c89 100644 --- a/deploy/charts/cert-manager/templates/rbac.yaml +++ b/deploy/charts/cert-manager/templates/rbac.yaml @@ -39,10 +39,53 @@ roleRef: kind: Role name: {{ template "cert-manager.fullname" . }}:leaderelection subjects: - - apiGroup: "" - kind: ServiceAccount + - kind: ServiceAccount + name: {{ template "cert-manager.serviceAccountName" . }} + namespace: {{ include "cert-manager.namespace" . }} + +--- + +{{- if .Values.serviceAccount.create }} +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: {{ template "cert-manager.fullname" . }}-tokenrequest + namespace: {{ include "cert-manager.namespace" . }} + labels: + app: {{ include "cert-manager.name" . }} + app.kubernetes.io/name: {{ include "cert-manager.name" . }} + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/component: "controller" + {{- include "labels" . | nindent 4 }} +rules: + - apiGroups: [""] + resources: ["serviceaccounts/token"] + resourceNames: ["{{ template "cert-manager.serviceAccountName" . }}"] + verbs: ["create"] + +--- + +# grant cert-manager permission to create tokens for the serviceaccount +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: {{ include "cert-manager.fullname" . }}-tokenrequest + namespace: {{ include "cert-manager.namespace" . }} + labels: + app: {{ include "cert-manager.name" . }} + app.kubernetes.io/name: {{ include "cert-manager.name" . }} + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/component: "controller" + {{- include "labels" . | nindent 4 }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: {{ template "cert-manager.fullname" . }}-tokenrequest +subjects: + - kind: ServiceAccount name: {{ template "cert-manager.serviceAccountName" . }} namespace: {{ include "cert-manager.namespace" . }} +{{- end }} --- @@ -70,7 +113,6 @@ rules: - apiGroups: [""] resources: ["events"] verbs: ["create", "patch"] - --- # ClusterIssuer controller role @@ -214,8 +256,8 @@ rules: - apiGroups: ["networking.k8s.io"] resources: ["ingresses"] verbs: ["get", "list", "watch", "create", "delete", "update"] - - apiGroups: [ "gateway.networking.k8s.io" ] - resources: [ "httproutes" ] + - apiGroups: ["gateway.networking.k8s.io"] + resources: ["httproutes"] verbs: ["get", "list", "watch", "create", "delete", "update"] # We require the ability to specify a custom hostname when we are creating # new ingress resources. @@ -399,6 +441,26 @@ subjects: namespace: {{ include "cert-manager.namespace" . }} kind: ServiceAccount +{{- if .Values.global.rbac.aggregateClusterRoles }} +--- + +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: {{ template "cert-manager.fullname" . }}-cluster-view + labels: + app: {{ include "cert-manager.name" . }} + app.kubernetes.io/name: {{ include "cert-manager.name" . }} + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/component: "controller" + {{- include "labels" . | nindent 4 }} + rbac.authorization.k8s.io/aggregate-to-cluster-reader: "true" +rules: + - apiGroups: ["cert-manager.io"] + resources: ["clusterissuers"] + verbs: ["get", "list", "watch"] + +{{- end }} --- apiVersion: rbac.authorization.k8s.io/v1 @@ -415,6 +477,7 @@ metadata: rbac.authorization.k8s.io/aggregate-to-view: "true" rbac.authorization.k8s.io/aggregate-to-edit: "true" rbac.authorization.k8s.io/aggregate-to-admin: "true" + rbac.authorization.k8s.io/aggregate-to-cluster-reader: "true" {{- end }} rules: - apiGroups: ["cert-manager.io"] @@ -454,6 +517,8 @@ rules: --- +{{- if not .Values.disableAutoApproval -}} + # Permission to approve CertificateRequests referencing cert-manager.io Issuers and ClusterIssuers apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole @@ -469,7 +534,12 @@ rules: - apiGroups: ["cert-manager.io"] resources: ["signers"] verbs: ["approve"] - resourceNames: ["issuers.cert-manager.io/*", "clusterissuers.cert-manager.io/*"] + {{- with .Values.approveSignerNames }} + resourceNames: + {{- range . }} + - {{ . | quote }} + {{- end }} + {{- end }} --- @@ -494,8 +564,10 @@ subjects: --- +{{- end -}} + # Permission to: -# - Update and sign CertificatSigningeRequests referencing cert-manager.io Issuers and ClusterIssuers +# - Update and sign CertificateSigningRequests referencing cert-manager.io Issuers and ClusterIssuers # - Perform SubjectAccessReviews to test whether users are able to reference Namespaced Issuers apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole diff --git a/deploy/charts/cert-manager/templates/service.yaml b/deploy/charts/cert-manager/templates/service.yaml index ec34d5878f6..360ec645efd 100644 --- a/deploy/charts/cert-manager/templates/service.yaml +++ b/deploy/charts/cert-manager/templates/service.yaml @@ -1,4 +1,4 @@ -{{- if .Values.prometheus.enabled }} +{{- if and .Values.prometheus.enabled (not .Values.prometheus.podmonitor.enabled) }} apiVersion: v1 kind: Service metadata: @@ -19,6 +19,12 @@ metadata: {{- end }} spec: type: ClusterIP + {{- if .Values.serviceIPFamilyPolicy }} + ipFamilyPolicy: {{ .Values.serviceIPFamilyPolicy }} + {{- end }} + {{- if .Values.serviceIPFamilies }} + ipFamilies: {{ .Values.serviceIPFamilies | toYaml | nindent 2 }} + {{- end }} ports: - protocol: TCP port: 9402 diff --git a/deploy/charts/cert-manager/templates/serviceaccount.yaml b/deploy/charts/cert-manager/templates/serviceaccount.yaml index 6026842ffb8..fac93d0a009 100644 --- a/deploy/charts/cert-manager/templates/serviceaccount.yaml +++ b/deploy/charts/cert-manager/templates/serviceaccount.yaml @@ -11,7 +11,10 @@ metadata: namespace: {{ include "cert-manager.namespace" . }} {{- with .Values.serviceAccount.annotations }} annotations: - {{- toYaml . | nindent 4 }} + {{- range $k, $v := . }} + {{- $value := $v | quote }} + {{- printf "%s: %s" (tpl $k $) (tpl $value $) | nindent 4 }} + {{- end }} {{- end }} labels: app: {{ include "cert-manager.name" . }} @@ -20,6 +23,6 @@ metadata: app.kubernetes.io/component: "controller" {{- include "labels" . | nindent 4 }} {{- with .Values.serviceAccount.labels }} - {{ toYaml . | nindent 4 }} + {{- toYaml . | nindent 4 }} {{- end }} {{- end }} diff --git a/deploy/charts/cert-manager/templates/servicemonitor.yaml b/deploy/charts/cert-manager/templates/servicemonitor.yaml index 9d9e89992ee..76f358f000e 100644 --- a/deploy/charts/cert-manager/templates/servicemonitor.yaml +++ b/deploy/charts/cert-manager/templates/servicemonitor.yaml @@ -1,4 +1,6 @@ -{{- if and .Values.prometheus.enabled .Values.prometheus.servicemonitor.enabled }} +{{- if and .Values.prometheus.enabled (and .Values.prometheus.podmonitor.enabled .Values.prometheus.servicemonitor.enabled) }} +{{- fail "Either .Values.prometheus.podmonitor.enabled or .Values.prometheus.servicemonitor.enabled can be enabled at a time, but not both." }} +{{- else if and .Values.prometheus.enabled .Values.prometheus.servicemonitor.enabled }} apiVersion: monitoring.coreos.com/v1 kind: ServiceMonitor metadata: @@ -14,7 +16,9 @@ metadata: app.kubernetes.io/instance: {{ .Release.Name }} app.kubernetes.io/component: "controller" {{- include "labels" . | nindent 4 }} + {{- if .Values.prometheus.servicemonitor.prometheusInstance }} prometheus: {{ .Values.prometheus.servicemonitor.prometheusInstance }} + {{- end }} {{- with .Values.prometheus.servicemonitor.labels }} {{- toYaml . | nindent 4 }} {{- end }} @@ -25,12 +29,25 @@ metadata: {{- end }} {{- end }} spec: - jobLabel: {{ template "cert-manager.fullname" . }} + jobLabel: app.kubernetes.io/name selector: - matchLabels: - app.kubernetes.io/name: {{ template "cert-manager.name" . }} - app.kubernetes.io/instance: {{ .Release.Name }} - app.kubernetes.io/component: "controller" + matchExpressions: + - key: app.kubernetes.io/name + operator: In + values: + - {{ include "cainjector.name" . }} + - {{ template "cert-manager.name" . }} + - {{ include "webhook.name" . }} + - key: app.kubernetes.io/instance + operator: In + values: + - {{ .Release.Name }} + - key: app.kubernetes.io/component + operator: In + values: + - cainjector + - controller + - webhook {{- if .Values.prometheus.servicemonitor.namespace }} namespaceSelector: matchNames: @@ -39,7 +56,14 @@ spec: endpoints: - targetPort: {{ .Values.prometheus.servicemonitor.targetPort }} path: {{ .Values.prometheus.servicemonitor.path }} + {{- if .Values.prometheus.servicemonitor.interval }} interval: {{ .Values.prometheus.servicemonitor.interval }} + {{- end }} + {{- if .Values.prometheus.servicemonitor.scrapeTimeout }} scrapeTimeout: {{ .Values.prometheus.servicemonitor.scrapeTimeout }} + {{- end }} honorLabels: {{ .Values.prometheus.servicemonitor.honorLabels }} + {{- with .Values.prometheus.servicemonitor.endpointAdditionalProperties }} + {{- toYaml . | nindent 4 }} + {{- end }} {{- end }} diff --git a/deploy/charts/cert-manager/templates/startupapicheck-job.yaml b/deploy/charts/cert-manager/templates/startupapicheck-job.yaml index f55b5fe15f7..e8694d17492 100644 --- a/deploy/charts/cert-manager/templates/startupapicheck-job.yaml +++ b/deploy/charts/cert-manager/templates/startupapicheck-job.yaml @@ -34,18 +34,27 @@ spec: spec: restartPolicy: OnFailure serviceAccountName: {{ template "startupapicheck.serviceAccountName" . }} + {{- if hasKey .Values.startupapicheck "automountServiceAccountToken" }} + automountServiceAccountToken: {{ .Values.startupapicheck.automountServiceAccountToken }} + {{- end }} + enableServiceLinks: {{ .Values.startupapicheck.enableServiceLinks }} {{- with .Values.global.priorityClassName }} priorityClassName: {{ . | quote }} {{- end }} + {{- if (hasKey .Values.global "hostUsers") }} + hostUsers: {{ .Values.global.hostUsers }} + {{- end }} {{- with .Values.startupapicheck.securityContext }} securityContext: {{- toYaml . | nindent 8 }} {{- end }} + {{- with .Values.global.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} containers: - name: {{ .Chart.Name }}-startupapicheck - {{- with .Values.startupapicheck.image }} - image: "{{- if .registry -}}{{ .registry }}/{{- end -}}{{ .repository }}{{- if (.digest) -}} @{{ .digest }}{{- else -}}:{{ default $.Chart.AppVersion .tag }} {{- end -}}" - {{- end }} + image: "{{ template "image" (tuple .Values.startupapicheck.image $.Chart.AppVersion) }}" imagePullPolicy: {{ .Values.startupapicheck.image.pullPolicy }} args: - check @@ -58,13 +67,27 @@ spec: securityContext: {{- toYaml . | nindent 12 }} {{- end }} + env: + - name: POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + {{- with .Values.startupapicheck.extraEnv }} + {{- toYaml . | nindent 10 }} + {{- end }} {{- with .Values.startupapicheck.resources }} resources: {{- toYaml . | nindent 12 }} {{- end }} - {{- with .Values.startupapicheck.nodeSelector }} + {{- with .Values.startupapicheck.volumeMounts }} + volumeMounts: + {{- toYaml . | nindent 12 }} + {{- end }} + {{- with (coalesce .Values.startupapicheck.nodeSelector .Values.global.nodeSelector) }} nodeSelector: - {{- toYaml . | nindent 8 }} + {{- range $key, $value := . }} + {{ $key }}: {{ $value | quote }} + {{- end }} {{- end }} {{- with .Values.startupapicheck.affinity }} affinity: @@ -74,4 +97,8 @@ spec: tolerations: {{- toYaml . | nindent 8 }} {{- end }} + {{- with .Values.startupapicheck.volumes }} + volumes: + {{- toYaml . | nindent 8 }} + {{- end }} {{- end }} diff --git a/deploy/charts/cert-manager/templates/startupapicheck-rbac.yaml b/deploy/charts/cert-manager/templates/startupapicheck-rbac.yaml index 606e725641e..ab8c30fbfe0 100644 --- a/deploy/charts/cert-manager/templates/startupapicheck-rbac.yaml +++ b/deploy/charts/cert-manager/templates/startupapicheck-rbac.yaml @@ -18,7 +18,7 @@ metadata: {{- end }} rules: - apiGroups: ["cert-manager.io"] - resources: ["certificates"] + resources: ["certificaterequests"] verbs: ["create"] --- apiVersion: rbac.authorization.k8s.io/v1 diff --git a/deploy/charts/cert-manager/templates/webhook-config.yaml b/deploy/charts/cert-manager/templates/webhook-config.yaml index ccee8e5c333..cd8b67f5529 100644 --- a/deploy/charts/cert-manager/templates/webhook-config.yaml +++ b/deploy/charts/cert-manager/templates/webhook-config.yaml @@ -1,12 +1,7 @@ {{- if .Values.webhook.config -}} - {{- if not .Values.webhook.config.apiVersion -}} - {{- fail "webhook.config.apiVersion must be set" -}} - {{- end -}} - - {{- if not .Values.webhook.config.kind -}} - {{- fail "webhook.config.kind must be set" -}} - {{- end -}} -{{- end -}} +{{- $config := .Values.webhook.config -}} +{{- $_ := set $config "apiVersion" (default "webhook.config.cert-manager.io/v1alpha1" $config.apiVersion) -}} +{{- $_ := set $config "kind" (default "WebhookConfiguration" $config.kind) -}} apiVersion: v1 kind: ConfigMap metadata: @@ -17,8 +12,8 @@ metadata: app.kubernetes.io/name: {{ include "webhook.name" . }} app.kubernetes.io/instance: {{ .Release.Name }} app.kubernetes.io/component: "webhook" + {{- include "labels" . | nindent 4 }} data: - {{- if .Values.webhook.config }} config.yaml: | - {{ .Values.webhook.config | toYaml | nindent 4 }} - {{- end }} + {{- $config | toYaml | nindent 4 }} +{{- end -}} \ No newline at end of file diff --git a/deploy/charts/cert-manager/templates/webhook-deployment.yaml b/deploy/charts/cert-manager/templates/webhook-deployment.yaml index 9e27afd61c9..bead20eaad8 100644 --- a/deploy/charts/cert-manager/templates/webhook-deployment.yaml +++ b/deploy/charts/cert-manager/templates/webhook-deployment.yaml @@ -15,6 +15,10 @@ metadata: {{- end }} spec: replicas: {{ .Values.webhook.replicaCount }} + {{- /* The if statement below is equivalent to {{- if $value }} but will also return true for 0. */ -}} + {{- if not (has (quote .Values.global.revisionHistoryLimit) (list "" (quote ""))) }} + revisionHistoryLimit: {{ .Values.global.revisionHistoryLimit }} + {{- end }} selector: matchLabels: app.kubernetes.io/name: {{ include "webhook.name" . }} @@ -39,14 +43,32 @@ spec: annotations: {{- toYaml . | nindent 8 }} {{- end }} + {{- if and .Values.prometheus.enabled (not (or .Values.prometheus.servicemonitor.enabled .Values.prometheus.podmonitor.enabled)) }} + {{- if not .Values.webhook.podAnnotations }} + annotations: + {{- end }} + prometheus.io/path: "/metrics" + prometheus.io/scrape: 'true' + prometheus.io/port: '9402' + {{- end }} spec: + {{- if not .Values.webhook.serviceAccount.create }} + {{- with .Values.global.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- end }} serviceAccountName: {{ template "webhook.serviceAccountName" . }} {{- if hasKey .Values.webhook "automountServiceAccountToken" }} automountServiceAccountToken: {{ .Values.webhook.automountServiceAccountToken }} {{- end }} + enableServiceLinks: {{ .Values.webhook.enableServiceLinks }} {{- with .Values.global.priorityClassName }} priorityClassName: {{ . | quote }} {{- end }} + {{- if (hasKey .Values.global "hostUsers") }} + hostUsers: {{ .Values.global.hostUsers }} + {{- end }} {{- with .Values.webhook.securityContext }} securityContext: {{- toYaml . | nindent 8 }} @@ -54,14 +76,16 @@ spec: {{- if .Values.webhook.hostNetwork }} hostNetwork: true {{- end }} + {{- if .Values.webhook.hostNetwork }} + dnsPolicy: ClusterFirstWithHostNet + {{- end }} containers: - name: {{ .Chart.Name }}-webhook - {{- with .Values.webhook.image }} - image: "{{- if .registry -}}{{ .registry }}/{{- end -}}{{ .repository }}{{- if (.digest) -}} @{{ .digest }}{{- else -}}:{{ default $.Chart.AppVersion .tag }} {{- end -}}" - {{- end }} + image: "{{ template "image" (tuple .Values.webhook.image $.Chart.AppVersion) }}" imagePullPolicy: {{ .Values.webhook.image.pullPolicy }} args: - {{- if .Values.global.logLevel }} + {{- /* The if statement below is equivalent to {{- if $value }} but will also return true for 0. */ -}} + {{- if not (has (quote .Values.global.logLevel) (list "" (quote ""))) }} - --v={{ .Values.global.logLevel }} {{- end }} {{- if .Values.webhook.config }} @@ -71,6 +95,9 @@ spec: {{ if not $config.securePort -}} - --secure-port={{ .Values.webhook.securePort }} {{- end }} + {{- if .Values.webhook.featureGates }} + - --feature-gates={{ .Values.webhook.featureGates }} + {{- end }} {{- $tlsConfig := default $config.tlsConfig "" }} {{ if or (not $config.tlsConfig) (and (not $tlsConfig.dynamic) (not $tlsConfig.filesystem) ) -}} - --dynamic-serving-ca-secret-namespace=$(POD_NAMESPACE) @@ -78,13 +105,16 @@ spec: - --dynamic-serving-dns-names={{ template "webhook.fullname" . }} - --dynamic-serving-dns-names={{ template "webhook.fullname" . }}.$(POD_NAMESPACE) - --dynamic-serving-dns-names={{ template "webhook.fullname" . }}.$(POD_NAMESPACE).svc - {{ if .Values.webhook.url.host }} + {{- if .Values.webhook.url.host }} - --dynamic-serving-dns-names={{ .Values.webhook.url.host }} {{- end }} {{- end }} {{- with .Values.webhook.extraArgs }} {{- toYaml . | nindent 10 }} {{- end }} + {{- if not .Values.prometheus.enabled }} + - --metrics-listen-address=0 + {{- end }} ports: - name: https protocol: TCP @@ -102,14 +132,15 @@ spec: {{- else }} containerPort: 6080 {{- end }} + {{- if .Values.prometheus.enabled }} + - containerPort: 9402 + name: http-metrics + protocol: TCP + {{- end }} livenessProbe: httpGet: path: /livez - {{- if $config.healthzPort }} - port: {{ $config.healthzPort }} - {{- else }} - port: 6080 - {{- end }} + port: healthcheck scheme: HTTP initialDelaySeconds: {{ .Values.webhook.livenessProbe.initialDelaySeconds }} periodSeconds: {{ .Values.webhook.livenessProbe.periodSeconds }} @@ -119,11 +150,7 @@ spec: readinessProbe: httpGet: path: /healthz - {{- if $config.healthzPort }} - port: {{ $config.healthzPort }} - {{- else }} - port: 6080 - {{- end }} + port: healthcheck scheme: HTTP initialDelaySeconds: {{ .Values.webhook.readinessProbe.initialDelaySeconds }} periodSeconds: {{ .Values.webhook.readinessProbe.periodSeconds }} @@ -139,18 +166,28 @@ spec: valueFrom: fieldRef: fieldPath: metadata.namespace + {{- with .Values.webhook.extraEnv }} + {{- toYaml . | nindent 10 }} + {{- end }} {{- with .Values.webhook.resources }} resources: {{- toYaml . | nindent 12 }} {{- end }} - {{- if .Values.webhook.config }} + {{- if or .Values.webhook.config .Values.webhook.volumeMounts }} volumeMounts: + {{- if .Values.webhook.config }} - name: config mountPath: /var/cert-manager/config + {{- end }} + {{- with .Values.webhook.volumeMounts }} + {{- toYaml . | nindent 12 }} + {{- end }} {{- end }} - {{- with .Values.webhook.nodeSelector }} + {{- with (coalesce .Values.webhook.nodeSelector .Values.global.nodeSelector) }} nodeSelector: - {{- toYaml . | nindent 8 }} + {{- range $key, $value := . }} + {{ $key }}: {{ $value | quote }} + {{- end }} {{- end }} {{- with .Values.webhook.affinity }} affinity: @@ -164,9 +201,14 @@ spec: topologySpreadConstraints: {{- toYaml . | nindent 8 }} {{- end }} - {{- if .Values.webhook.config }} + {{- if or .Values.webhook.config .Values.webhook.volumes }} volumes: + {{- if .Values.webhook.config }} - name: config configMap: name: {{ include "webhook.fullname" . }} + {{- end }} + {{- with .Values.webhook.volumes }} + {{- toYaml . | nindent 8 }} + {{- end }} {{- end }} diff --git a/deploy/charts/cert-manager/templates/webhook-mutating-webhook.yaml b/deploy/charts/cert-manager/templates/webhook-mutating-webhook.yaml index f3db011efc4..9ea29777dc3 100644 --- a/deploy/charts/cert-manager/templates/webhook-mutating-webhook.yaml +++ b/deploy/charts/cert-manager/templates/webhook-mutating-webhook.yaml @@ -15,17 +15,19 @@ metadata: {{- end }} webhooks: - name: webhook.cert-manager.io + {{- with .Values.webhook.mutatingWebhookConfiguration.namespaceSelector }} + namespaceSelector: + {{- toYaml . | nindent 6 }} + {{- end }} rules: - apiGroups: - "cert-manager.io" - - "acme.cert-manager.io" apiVersions: - "v1" operations: - CREATE - - UPDATE resources: - - "*/*" + - "certificaterequests" admissionReviewVersions: ["v1"] # This webhook only accepts v1 cert-manager resources. # Equivalent matchPolicy ensures that non-v1 resource requests are sent to @@ -43,4 +45,4 @@ webhooks: name: {{ template "webhook.fullname" . }} namespace: {{ include "cert-manager.namespace" . }} path: /mutate - {{- end }} + {{- end }} \ No newline at end of file diff --git a/deploy/charts/cert-manager/templates/webhook-poddisruptionbudget.yaml b/deploy/charts/cert-manager/templates/webhook-poddisruptionbudget.yaml new file mode 100644 index 00000000000..ab2a48109e4 --- /dev/null +++ b/deploy/charts/cert-manager/templates/webhook-poddisruptionbudget.yaml @@ -0,0 +1,29 @@ +{{- if .Values.webhook.podDisruptionBudget.enabled }} +apiVersion: policy/v1 +kind: PodDisruptionBudget +metadata: + name: {{ include "webhook.fullname" . }} + namespace: {{ include "cert-manager.namespace" . }} + labels: + app: {{ include "webhook.name" . }} + app.kubernetes.io/name: {{ include "webhook.name" . }} + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/component: "webhook" + {{- include "labels" . | nindent 4 }} +spec: + selector: + matchLabels: + app.kubernetes.io/name: {{ include "webhook.name" . }} + app.kubernetes.io/instance: {{ .Release.Name }} + app.kubernetes.io/component: "webhook" + + {{- if not (or (hasKey .Values.webhook.podDisruptionBudget "minAvailable") (hasKey .Values.webhook.podDisruptionBudget "maxUnavailable")) }} + minAvailable: 1 # Default value because minAvailable and maxUnavailable are not set + {{- end }} + {{- if hasKey .Values.webhook.podDisruptionBudget "minAvailable" }} + minAvailable: {{ .Values.webhook.podDisruptionBudget.minAvailable }} + {{- end }} + {{- if hasKey .Values.webhook.podDisruptionBudget "maxUnavailable" }} + maxUnavailable: {{ .Values.webhook.podDisruptionBudget.maxUnavailable }} + {{- end }} +{{- end }} diff --git a/deploy/charts/cert-manager/templates/webhook-psp-clusterrole.yaml b/deploy/charts/cert-manager/templates/webhook-psp-clusterrole.yaml index 2a8808e7dc2..f6fa4c55e5b 100644 --- a/deploy/charts/cert-manager/templates/webhook-psp-clusterrole.yaml +++ b/deploy/charts/cert-manager/templates/webhook-psp-clusterrole.yaml @@ -15,4 +15,4 @@ rules: verbs: ['use'] resourceNames: - {{ template "webhook.fullname" . }} -{{- end }} +{{- end }} diff --git a/deploy/charts/cert-manager/templates/webhook-rbac.yaml b/deploy/charts/cert-manager/templates/webhook-rbac.yaml index b075ffd460e..b99325e03b3 100644 --- a/deploy/charts/cert-manager/templates/webhook-rbac.yaml +++ b/deploy/charts/cert-manager/templates/webhook-rbac.yaml @@ -15,6 +15,15 @@ rules: resources: ["secrets"] resourceNames: - '{{ template "webhook.fullname" . }}-ca' + {{- $certmanagerNamespace := include "cert-manager.namespace" . }} + {{- with (.Values.webhook.config.metricsTLSConfig).dynamic }} + {{- if $certmanagerNamespace | eq .secretNamespace }} + # Allow webhook to read and update the metrics CA Secret when dynamic TLS is + # enabled for the metrics server and if the Secret is configured to be in the + # same namespace as cert-manager. + - {{ .secretName | quote }} + {{- end }} + {{- end }} verbs: ["get", "list", "watch", "update"] # It's not possible to grant CREATE permission on a single resourceName. - apiGroups: [""] @@ -38,8 +47,7 @@ roleRef: kind: Role name: {{ template "webhook.fullname" . }}:dynamic-serving subjects: -- apiGroup: "" - kind: ServiceAccount +- kind: ServiceAccount name: {{ template "webhook.serviceAccountName" . }} namespace: {{ include "cert-manager.namespace" . }} @@ -76,8 +84,7 @@ roleRef: kind: ClusterRole name: {{ template "webhook.fullname" . }}:subjectaccessreviews subjects: -- apiGroup: "" - kind: ServiceAccount +- kind: ServiceAccount name: {{ template "webhook.serviceAccountName" . }} namespace: {{ include "cert-manager.namespace" . }} {{- end }} diff --git a/deploy/charts/cert-manager/templates/webhook-service.yaml b/deploy/charts/cert-manager/templates/webhook-service.yaml index 5f93950495f..cd5010f203d 100644 --- a/deploy/charts/cert-manager/templates/webhook-service.yaml +++ b/deploy/charts/cert-manager/templates/webhook-service.yaml @@ -18,6 +18,12 @@ metadata: {{- end }} spec: type: {{ .Values.webhook.serviceType }} + {{- if .Values.webhook.serviceIPFamilyPolicy }} + ipFamilyPolicy: {{ .Values.webhook.serviceIPFamilyPolicy }} + {{- end }} + {{- if .Values.webhook.serviceIPFamilies }} + ipFamilies: {{ .Values.webhook.serviceIPFamilies | toYaml | nindent 2 }} + {{- end }} {{- with .Values.webhook.loadBalancerIP }} loadBalancerIP: {{ . }} {{- end }} @@ -26,6 +32,12 @@ spec: port: 443 protocol: TCP targetPort: "https" +{{- if and .Values.prometheus.enabled (not .Values.prometheus.podmonitor.enabled) }} + - name: metrics + port: 9402 + protocol: TCP + targetPort: "http-metrics" +{{- end }} selector: app.kubernetes.io/name: {{ include "webhook.name" . }} app.kubernetes.io/instance: {{ .Release.Name }} diff --git a/deploy/charts/cert-manager/templates/webhook-validating-webhook.yaml b/deploy/charts/cert-manager/templates/webhook-validating-webhook.yaml index a5d168e29c8..76235fdee60 100644 --- a/deploy/charts/cert-manager/templates/webhook-validating-webhook.yaml +++ b/deploy/charts/cert-manager/templates/webhook-validating-webhook.yaml @@ -15,16 +15,10 @@ metadata: {{- end }} webhooks: - name: webhook.cert-manager.io + {{- with .Values.webhook.validatingWebhookConfiguration.namespaceSelector }} namespaceSelector: - matchExpressions: - - key: "cert-manager.io/disable-validation" - operator: "NotIn" - values: - - "true" - - key: "name" - operator: "NotIn" - values: - - {{ include "cert-manager.namespace" . }} + {{- toYaml . | nindent 6 }} + {{- end }} rules: - apiGroups: - "cert-manager.io" diff --git a/deploy/charts/cert-manager/values.linter.exceptions b/deploy/charts/cert-manager/values.linter.exceptions new file mode 100644 index 00000000000..6636fec753d --- /dev/null +++ b/deploy/charts/cert-manager/values.linter.exceptions @@ -0,0 +1,4 @@ +value missing from templates: crds.enabled +value missing from templates: crds.keep +value missing from templates: acmesolver.image.pullPolicy +value missing from templates: enabled \ No newline at end of file diff --git a/deploy/charts/cert-manager/values.schema.json b/deploy/charts/cert-manager/values.schema.json new file mode 100644 index 00000000000..a89a9f84d43 --- /dev/null +++ b/deploy/charts/cert-manager/values.schema.json @@ -0,0 +1,2159 @@ +{ + "$defs": { + "helm-values": { + "additionalProperties": false, + "properties": { + "acmesolver": { + "$ref": "#/$defs/helm-values.acmesolver" + }, + "affinity": { + "$ref": "#/$defs/helm-values.affinity" + }, + "approveSignerNames": { + "$ref": "#/$defs/helm-values.approveSignerNames" + }, + "automountServiceAccountToken": { + "$ref": "#/$defs/helm-values.automountServiceAccountToken" + }, + "cainjector": { + "$ref": "#/$defs/helm-values.cainjector" + }, + "clusterResourceNamespace": { + "$ref": "#/$defs/helm-values.clusterResourceNamespace" + }, + "config": { + "$ref": "#/$defs/helm-values.config" + }, + "containerSecurityContext": { + "$ref": "#/$defs/helm-values.containerSecurityContext" + }, + "crds": { + "$ref": "#/$defs/helm-values.crds" + }, + "creator": { + "$ref": "#/$defs/helm-values.creator" + }, + "deploymentAnnotations": { + "$ref": "#/$defs/helm-values.deploymentAnnotations" + }, + "disableAutoApproval": { + "$ref": "#/$defs/helm-values.disableAutoApproval" + }, + "dns01RecursiveNameservers": { + "$ref": "#/$defs/helm-values.dns01RecursiveNameservers" + }, + "dns01RecursiveNameserversOnly": { + "$ref": "#/$defs/helm-values.dns01RecursiveNameserversOnly" + }, + "enableCertificateOwnerRef": { + "$ref": "#/$defs/helm-values.enableCertificateOwnerRef" + }, + "enableServiceLinks": { + "$ref": "#/$defs/helm-values.enableServiceLinks" + }, + "enabled": { + "$ref": "#/$defs/helm-values.enabled" + }, + "extraArgs": { + "$ref": "#/$defs/helm-values.extraArgs" + }, + "extraEnv": { + "$ref": "#/$defs/helm-values.extraEnv" + }, + "extraObjects": { + "$ref": "#/$defs/helm-values.extraObjects" + }, + "featureGates": { + "$ref": "#/$defs/helm-values.featureGates" + }, + "fullnameOverride": { + "$ref": "#/$defs/helm-values.fullnameOverride" + }, + "global": { + "$ref": "#/$defs/helm-values.global" + }, + "hostAliases": { + "$ref": "#/$defs/helm-values.hostAliases" + }, + "http_proxy": { + "$ref": "#/$defs/helm-values.http_proxy" + }, + "https_proxy": { + "$ref": "#/$defs/helm-values.https_proxy" + }, + "image": { + "$ref": "#/$defs/helm-values.image" + }, + "ingressShim": { + "$ref": "#/$defs/helm-values.ingressShim" + }, + "installCRDs": { + "$ref": "#/$defs/helm-values.installCRDs" + }, + "livenessProbe": { + "$ref": "#/$defs/helm-values.livenessProbe" + }, + "maxConcurrentChallenges": { + "$ref": "#/$defs/helm-values.maxConcurrentChallenges" + }, + "nameOverride": { + "$ref": "#/$defs/helm-values.nameOverride" + }, + "namespace": { + "$ref": "#/$defs/helm-values.namespace" + }, + "no_proxy": { + "$ref": "#/$defs/helm-values.no_proxy" + }, + "nodeSelector": { + "$ref": "#/$defs/helm-values.nodeSelector" + }, + "podAnnotations": { + "$ref": "#/$defs/helm-values.podAnnotations" + }, + "podDisruptionBudget": { + "$ref": "#/$defs/helm-values.podDisruptionBudget" + }, + "podDnsConfig": { + "$ref": "#/$defs/helm-values.podDnsConfig" + }, + "podDnsPolicy": { + "$ref": "#/$defs/helm-values.podDnsPolicy" + }, + "podLabels": { + "$ref": "#/$defs/helm-values.podLabels" + }, + "prometheus": { + "$ref": "#/$defs/helm-values.prometheus" + }, + "replicaCount": { + "$ref": "#/$defs/helm-values.replicaCount" + }, + "resources": { + "$ref": "#/$defs/helm-values.resources" + }, + "securityContext": { + "$ref": "#/$defs/helm-values.securityContext" + }, + "serviceAccount": { + "$ref": "#/$defs/helm-values.serviceAccount" + }, + "serviceAnnotations": { + "$ref": "#/$defs/helm-values.serviceAnnotations" + }, + "serviceIPFamilies": { + "$ref": "#/$defs/helm-values.serviceIPFamilies" + }, + "serviceIPFamilyPolicy": { + "$ref": "#/$defs/helm-values.serviceIPFamilyPolicy" + }, + "serviceLabels": { + "$ref": "#/$defs/helm-values.serviceLabels" + }, + "startupapicheck": { + "$ref": "#/$defs/helm-values.startupapicheck" + }, + "strategy": { + "$ref": "#/$defs/helm-values.strategy" + }, + "tolerations": { + "$ref": "#/$defs/helm-values.tolerations" + }, + "topologySpreadConstraints": { + "$ref": "#/$defs/helm-values.topologySpreadConstraints" + }, + "volumeMounts": { + "$ref": "#/$defs/helm-values.volumeMounts" + }, + "volumes": { + "$ref": "#/$defs/helm-values.volumes" + }, + "webhook": { + "$ref": "#/$defs/helm-values.webhook" + } + }, + "type": "object" + }, + "helm-values.acmesolver": { + "additionalProperties": false, + "properties": { + "image": { + "$ref": "#/$defs/helm-values.acmesolver.image" + } + }, + "type": "object" + }, + "helm-values.acmesolver.image": { + "additionalProperties": false, + "properties": { + "digest": { + "$ref": "#/$defs/helm-values.acmesolver.image.digest" + }, + "pullPolicy": { + "$ref": "#/$defs/helm-values.acmesolver.image.pullPolicy" + }, + "registry": { + "$ref": "#/$defs/helm-values.acmesolver.image.registry" + }, + "repository": { + "$ref": "#/$defs/helm-values.acmesolver.image.repository" + }, + "tag": { + "$ref": "#/$defs/helm-values.acmesolver.image.tag" + } + }, + "type": "object" + }, + "helm-values.acmesolver.image.digest": { + "description": "Setting a digest will override any tag.", + "type": "string" + }, + "helm-values.acmesolver.image.pullPolicy": { + "default": "IfNotPresent", + "description": "Kubernetes imagePullPolicy on Deployment.", + "type": "string" + }, + "helm-values.acmesolver.image.registry": { + "description": "The container registry to pull the acmesolver image from.", + "type": "string" + }, + "helm-values.acmesolver.image.repository": { + "default": "quay.io/jetstack/cert-manager-acmesolver", + "description": "The container image for the cert-manager acmesolver.", + "type": "string" + }, + "helm-values.acmesolver.image.tag": { + "description": "Override the image tag to deploy by setting this variable. If no value is set, the chart's appVersion is used.", + "type": "string" + }, + "helm-values.affinity": { + "default": {}, + "description": "A Kubernetes Affinity, if required. For more information, see [Affinity v1 core](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.27/#affinity-v1-core).\n\nFor example:\naffinity:\n nodeAffinity:\n requiredDuringSchedulingIgnoredDuringExecution:\n nodeSelectorTerms:\n - matchExpressions:\n - key: foo.bar.com/role\n operator: In\n values:\n - master", + "type": "object" + }, + "helm-values.approveSignerNames": { + "default": [ + "issuers.cert-manager.io/*", + "clusterissuers.cert-manager.io/*" + ], + "description": "List of signer names that cert-manager will approve by default. CertificateRequests referencing these signer names will be auto-approved by cert-manager. Defaults to just approving the cert-manager.io Issuer and ClusterIssuer issuers. When set to an empty array, ALL issuers will be auto-approved by cert-manager. To disable the auto-approval, because, e.g., you are using approver-policy, you can enable 'disableAutoApproval'.\nref: https://cert-manager.io/docs/concepts/certificaterequest/#approval", + "items": {}, + "type": "array" + }, + "helm-values.automountServiceAccountToken": { + "description": "Automounting API credentials for a particular pod.", + "type": "boolean" + }, + "helm-values.cainjector": { + "additionalProperties": false, + "properties": { + "affinity": { + "$ref": "#/$defs/helm-values.cainjector.affinity" + }, + "automountServiceAccountToken": { + "$ref": "#/$defs/helm-values.cainjector.automountServiceAccountToken" + }, + "config": { + "$ref": "#/$defs/helm-values.cainjector.config" + }, + "containerSecurityContext": { + "$ref": "#/$defs/helm-values.cainjector.containerSecurityContext" + }, + "deploymentAnnotations": { + "$ref": "#/$defs/helm-values.cainjector.deploymentAnnotations" + }, + "enableServiceLinks": { + "$ref": "#/$defs/helm-values.cainjector.enableServiceLinks" + }, + "enabled": { + "$ref": "#/$defs/helm-values.cainjector.enabled" + }, + "extraArgs": { + "$ref": "#/$defs/helm-values.cainjector.extraArgs" + }, + "extraEnv": { + "$ref": "#/$defs/helm-values.cainjector.extraEnv" + }, + "featureGates": { + "$ref": "#/$defs/helm-values.cainjector.featureGates" + }, + "image": { + "$ref": "#/$defs/helm-values.cainjector.image" + }, + "nodeSelector": { + "$ref": "#/$defs/helm-values.cainjector.nodeSelector" + }, + "podAnnotations": { + "$ref": "#/$defs/helm-values.cainjector.podAnnotations" + }, + "podDisruptionBudget": { + "$ref": "#/$defs/helm-values.cainjector.podDisruptionBudget" + }, + "podLabels": { + "$ref": "#/$defs/helm-values.cainjector.podLabels" + }, + "replicaCount": { + "$ref": "#/$defs/helm-values.cainjector.replicaCount" + }, + "resources": { + "$ref": "#/$defs/helm-values.cainjector.resources" + }, + "securityContext": { + "$ref": "#/$defs/helm-values.cainjector.securityContext" + }, + "serviceAccount": { + "$ref": "#/$defs/helm-values.cainjector.serviceAccount" + }, + "serviceAnnotations": { + "$ref": "#/$defs/helm-values.cainjector.serviceAnnotations" + }, + "serviceLabels": { + "$ref": "#/$defs/helm-values.cainjector.serviceLabels" + }, + "strategy": { + "$ref": "#/$defs/helm-values.cainjector.strategy" + }, + "tolerations": { + "$ref": "#/$defs/helm-values.cainjector.tolerations" + }, + "topologySpreadConstraints": { + "$ref": "#/$defs/helm-values.cainjector.topologySpreadConstraints" + }, + "volumeMounts": { + "$ref": "#/$defs/helm-values.cainjector.volumeMounts" + }, + "volumes": { + "$ref": "#/$defs/helm-values.cainjector.volumes" + } + }, + "type": "object" + }, + "helm-values.cainjector.affinity": { + "default": {}, + "description": "A Kubernetes Affinity, if required. For more information, see [Affinity v1 core](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.27/#affinity-v1-core).\n\nFor example:\naffinity:\n nodeAffinity:\n requiredDuringSchedulingIgnoredDuringExecution:\n nodeSelectorTerms:\n - matchExpressions:\n - key: foo.bar.com/role\n operator: In\n values:\n - master", + "type": "object" + }, + "helm-values.cainjector.automountServiceAccountToken": { + "description": "Automounting API credentials for a particular pod.", + "type": "boolean" + }, + "helm-values.cainjector.config": { + "default": {}, + "description": "This is used to configure options for the cainjector pod. It allows setting options that are usually provided via flags.\n\nIf `apiVersion` and `kind` are unspecified they default to the current latest version (currently `cainjector.config.cert-manager.io/v1alpha1`). You can pin the version by specifying the `apiVersion` yourself.\n\nFor example:\napiVersion: cainjector.config.cert-manager.io/v1alpha1\nkind: CAInjectorConfiguration\nlogging:\n verbosity: 2\n format: text\nleaderElectionConfig:\n namespace: kube-system\n# Configure the metrics server for TLS\n# See https://cert-manager.io/docs/devops-tips/prometheus-metrics/#tls\nmetricsTLSConfig:\n dynamic:\n secretNamespace: \"cert-manager\"\n secretName: \"cert-manager-metrics-ca\"\n dnsNames:\n - cert-manager-metrics", + "type": "object" + }, + "helm-values.cainjector.containerSecurityContext": { + "default": { + "allowPrivilegeEscalation": false, + "capabilities": { + "drop": [ + "ALL" + ] + }, + "readOnlyRootFilesystem": true + }, + "description": "Container Security Context to be set on the cainjector component container. For more information, see [Configure a Security Context for a Pod or Container](https://kubernetes.io/docs/tasks/configure-pod-container/security-context/).", + "type": "object" + }, + "helm-values.cainjector.deploymentAnnotations": { + "description": "Optional additional annotations to add to the cainjector Deployment.", + "type": "object" + }, + "helm-values.cainjector.enableServiceLinks": { + "default": false, + "description": "enableServiceLinks indicates whether information about services should be injected into the pod's environment variables, matching the syntax of Docker links.", + "type": "boolean" + }, + "helm-values.cainjector.enabled": { + "default": true, + "description": "Create the CA Injector deployment", + "type": "boolean" + }, + "helm-values.cainjector.extraArgs": { + "default": [], + "description": "Additional command line flags to pass to cert-manager cainjector binary. To see all available flags run `docker run quay.io/jetstack/cert-manager-cainjector: --help`.", + "items": {}, + "type": "array" + }, + "helm-values.cainjector.extraEnv": { + "default": [], + "description": "Additional environment variables to pass to cert-manager cainjector binary.\nFor example:\nextraEnv:\n- name: SOME_VAR\n value: 'some value'", + "items": {}, + "type": "array" + }, + "helm-values.cainjector.featureGates": { + "default": "", + "description": "Comma separated list of feature gates that should be enabled on the cainjector pod.", + "type": "string" + }, + "helm-values.cainjector.image": { + "additionalProperties": false, + "properties": { + "digest": { + "$ref": "#/$defs/helm-values.cainjector.image.digest" + }, + "pullPolicy": { + "$ref": "#/$defs/helm-values.cainjector.image.pullPolicy" + }, + "registry": { + "$ref": "#/$defs/helm-values.cainjector.image.registry" + }, + "repository": { + "$ref": "#/$defs/helm-values.cainjector.image.repository" + }, + "tag": { + "$ref": "#/$defs/helm-values.cainjector.image.tag" + } + }, + "type": "object" + }, + "helm-values.cainjector.image.digest": { + "description": "Setting a digest will override any tag.", + "type": "string" + }, + "helm-values.cainjector.image.pullPolicy": { + "default": "IfNotPresent", + "description": "Kubernetes imagePullPolicy on Deployment.", + "type": "string" + }, + "helm-values.cainjector.image.registry": { + "description": "The container registry to pull the cainjector image from.", + "type": "string" + }, + "helm-values.cainjector.image.repository": { + "default": "quay.io/jetstack/cert-manager-cainjector", + "description": "The container image for the cert-manager cainjector", + "type": "string" + }, + "helm-values.cainjector.image.tag": { + "description": "Override the image tag to deploy by setting this variable. If no value is set, the chart's appVersion will be used.", + "type": "string" + }, + "helm-values.cainjector.nodeSelector": { + "default": { + "kubernetes.io/os": "linux" + }, + "description": "The nodeSelector on Pods tells Kubernetes to schedule Pods on the nodes with matching labels. For more information, see [Assigning Pods to Nodes](https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/).\n\nThis default ensures that Pods are only scheduled to Linux nodes. It prevents Pods being scheduled to Windows nodes in a mixed OS cluster.", + "type": "object" + }, + "helm-values.cainjector.podAnnotations": { + "description": "Optional additional annotations to add to the cainjector Pods.", + "type": "object" + }, + "helm-values.cainjector.podDisruptionBudget": { + "additionalProperties": false, + "properties": { + "enabled": { + "$ref": "#/$defs/helm-values.cainjector.podDisruptionBudget.enabled" + }, + "maxUnavailable": { + "$ref": "#/$defs/helm-values.cainjector.podDisruptionBudget.maxUnavailable" + }, + "minAvailable": { + "$ref": "#/$defs/helm-values.cainjector.podDisruptionBudget.minAvailable" + } + }, + "type": "object" + }, + "helm-values.cainjector.podDisruptionBudget.enabled": { + "default": false, + "description": "Enable or disable the PodDisruptionBudget resource.\n\nThis prevents downtime during voluntary disruptions such as during a Node upgrade. For example, the PodDisruptionBudget will block `kubectl drain` if it is used on the Node where the only remaining cert-manager\nPod is currently running.", + "type": "boolean" + }, + "helm-values.cainjector.podDisruptionBudget.maxUnavailable": { + "description": "`maxUnavailable` configures the maximum unavailable pods for disruptions. It can either be set to\nan integer (e.g., 1) or a percentage value (e.g., 25%).\nCannot be used if `minAvailable` is set." + }, + "helm-values.cainjector.podDisruptionBudget.minAvailable": { + "description": "`minAvailable` configures the minimum available pods for disruptions. It can either be set to\nan integer (e.g., 1) or a percentage value (e.g., 25%).\nCannot be used if `maxUnavailable` is set." + }, + "helm-values.cainjector.podLabels": { + "default": {}, + "description": "Optional additional labels to add to the CA Injector Pods.", + "type": "object" + }, + "helm-values.cainjector.replicaCount": { + "default": 1, + "description": "The number of replicas of the cert-manager cainjector to run.\n\nThe default is 1, but in production set this to 2 or 3 to provide high availability.\n\nIf `replicas > 1`, consider setting `cainjector.podDisruptionBudget.enabled=true`.\n\nNote that cert-manager uses leader election to ensure that there can only be a single instance active at a time.", + "type": "number" + }, + "helm-values.cainjector.resources": { + "default": {}, + "description": "Resources to provide to the cert-manager cainjector pod.\n\nFor example:\nrequests:\n cpu: 10m\n memory: 32Mi\nFor more information, see [Resource Management for Pods and Containers](https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/).", + "type": "object" + }, + "helm-values.cainjector.securityContext": { + "default": { + "runAsNonRoot": true, + "seccompProfile": { + "type": "RuntimeDefault" + } + }, + "description": "Pod Security Context to be set on the cainjector component Pod. For more information, see [Configure a Security Context for a Pod or Container](https://kubernetes.io/docs/tasks/configure-pod-container/security-context/).", + "type": "object" + }, + "helm-values.cainjector.serviceAccount": { + "additionalProperties": false, + "properties": { + "annotations": { + "$ref": "#/$defs/helm-values.cainjector.serviceAccount.annotations" + }, + "automountServiceAccountToken": { + "$ref": "#/$defs/helm-values.cainjector.serviceAccount.automountServiceAccountToken" + }, + "create": { + "$ref": "#/$defs/helm-values.cainjector.serviceAccount.create" + }, + "labels": { + "$ref": "#/$defs/helm-values.cainjector.serviceAccount.labels" + }, + "name": { + "$ref": "#/$defs/helm-values.cainjector.serviceAccount.name" + } + }, + "type": "object" + }, + "helm-values.cainjector.serviceAccount.annotations": { + "description": "Optional additional annotations to add to the cainjector's Service Account.", + "type": "object" + }, + "helm-values.cainjector.serviceAccount.automountServiceAccountToken": { + "default": true, + "description": "Automount API credentials for a Service Account.", + "type": "boolean" + }, + "helm-values.cainjector.serviceAccount.create": { + "default": true, + "description": "Specifies whether a service account should be created.", + "type": "boolean" + }, + "helm-values.cainjector.serviceAccount.labels": { + "description": "Optional additional labels to add to the cainjector's Service Account.", + "type": "object" + }, + "helm-values.cainjector.serviceAccount.name": { + "description": "The name of the service account to use.\nIf not set and create is true, a name is generated using the fullname template", + "type": "string" + }, + "helm-values.cainjector.serviceAnnotations": { + "description": "Optional additional annotations to add to the cainjector metrics Service.", + "type": "object" + }, + "helm-values.cainjector.serviceLabels": { + "default": {}, + "description": "Optional additional labels to add to the CA Injector metrics Service.", + "type": "object" + }, + "helm-values.cainjector.strategy": { + "default": {}, + "description": "Deployment update strategy for the cert-manager cainjector deployment. For more information, see the [Kubernetes documentation](https://kubernetes.io/docs/concepts/workloads/controllers/deployment/#strategy).\n\nFor example:\nstrategy:\n type: RollingUpdate\n rollingUpdate:\n maxSurge: 0\n maxUnavailable: 1", + "type": "object" + }, + "helm-values.cainjector.tolerations": { + "default": [], + "description": "A list of Kubernetes Tolerations, if required. For more information, see [Toleration v1 core](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.27/#toleration-v1-core).\n\nFor example:\ntolerations:\n- key: foo.bar.com/role\n operator: Equal\n value: master\n effect: NoSchedule", + "items": {}, + "type": "array" + }, + "helm-values.cainjector.topologySpreadConstraints": { + "default": [], + "description": "A list of Kubernetes TopologySpreadConstraints, if required. For more information, see [Topology spread constraint v1 core](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.27/#topologyspreadconstraint-v1-core).\n\nFor example:\ntopologySpreadConstraints:\n- maxSkew: 2\n topologyKey: topology.kubernetes.io/zone\n whenUnsatisfiable: ScheduleAnyway\n labelSelector:\n matchLabels:\n app.kubernetes.io/instance: cert-manager\n app.kubernetes.io/component: controller", + "items": {}, + "type": "array" + }, + "helm-values.cainjector.volumeMounts": { + "default": [], + "description": "Additional volume mounts to add to the cert-manager controller container.", + "items": {}, + "type": "array" + }, + "helm-values.cainjector.volumes": { + "default": [], + "description": "Additional volumes to add to the cert-manager controller pod.", + "items": {}, + "type": "array" + }, + "helm-values.clusterResourceNamespace": { + "default": "", + "description": "Override the namespace used to store DNS provider credentials etc. for ClusterIssuer resources. By default, the same namespace as cert-manager is deployed within is used. This namespace will not be automatically created by the Helm chart.", + "type": "string" + }, + "helm-values.config": { + "default": {}, + "description": "This property is used to configure options for the controller pod. This allows setting options that would usually be provided using flags.\n\nIf `apiVersion` and `kind` are unspecified they default to the current latest version (currently `controller.config.cert-manager.io/v1alpha1`). You can pin the version by specifying the `apiVersion` yourself.\n\nFor example:\nconfig:\n apiVersion: controller.config.cert-manager.io/v1alpha1\n kind: ControllerConfiguration\n logging:\n verbosity: 2\n format: text\n leaderElectionConfig:\n namespace: kube-system\n kubernetesAPIQPS: 9000\n kubernetesAPIBurst: 9000\n numberOfConcurrentWorkers: 200\n enableGatewayAPI: true\n # Feature gates as of v1.18.1. Listed with their default values.\n # See https://cert-manager.io/docs/cli/controller/\n featureGates:\n AdditionalCertificateOutputFormats: true # GA - default=true\n AllAlpha: false # ALPHA - default=false\n AllBeta: false # BETA - default=false\n ExperimentalCertificateSigningRequestControllers: false # ALPHA - default=false\n ExperimentalGatewayAPISupport: true # BETA - default=true\n LiteralCertificateSubject: true # BETA - default=true\n NameConstraints: true # BETA - default=true\n OtherNames: false # ALPHA - default=false\n SecretsFilteredCaching: true # BETA - default=true\n ServerSideApply: false # ALPHA - default=false\n StableCertificateRequestName: true # BETA - default=true\n UseCertificateRequestBasicConstraints: false # ALPHA - default=false\n UseDomainQualifiedFinalizer: true # GA - default=true\n ValidateCAA: false # ALPHA - default=false\n DefaultPrivateKeyRotationPolicyAlways: true # BETA - default=true\n ACMEHTTP01IngressPathTypeExact: true # BETA - default=true\n # Configure the metrics server for TLS\n # See https://cert-manager.io/docs/devops-tips/prometheus-metrics/#tls\n metricsTLSConfig:\n dynamic:\n secretNamespace: \"cert-manager\"\n secretName: \"cert-manager-metrics-ca\"\n dnsNames:\n - cert-manager-metrics", + "type": "object" + }, + "helm-values.containerSecurityContext": { + "default": { + "allowPrivilegeEscalation": false, + "capabilities": { + "drop": [ + "ALL" + ] + }, + "readOnlyRootFilesystem": true + }, + "description": "Container Security Context to be set on the controller component container. For more information, see [Configure a Security Context for a Pod or Container](https://kubernetes.io/docs/tasks/configure-pod-container/security-context/).", + "type": "object" + }, + "helm-values.crds": { + "additionalProperties": false, + "properties": { + "enabled": { + "$ref": "#/$defs/helm-values.crds.enabled" + }, + "keep": { + "$ref": "#/$defs/helm-values.crds.keep" + } + }, + "type": "object" + }, + "helm-values.crds.enabled": { + "default": false, + "description": "This option decides if the CRDs should be installed as part of the Helm installation.", + "type": "boolean" + }, + "helm-values.crds.keep": { + "default": true, + "description": "This option makes it so that the \"helm.sh/resource-policy\": keep annotation is added to the CRD. This will prevent Helm from uninstalling the CRD when the Helm release is uninstalled. WARNING: when the CRDs are removed, all cert-manager custom resources\n(Certificates, Issuers, ...) will be removed too by the garbage collector.", + "type": "boolean" + }, + "helm-values.creator": { + "default": "helm", + "description": "Field used by our release pipeline to produce the static manifests. The field defaults to \"helm\" but is set to \"static\" when we render the static YAML manifests.", + "type": "string" + }, + "helm-values.deploymentAnnotations": { + "description": "Optional additional annotations to add to the controller Deployment.", + "type": "object" + }, + "helm-values.disableAutoApproval": { + "default": false, + "description": "Option to disable cert-manager's build-in auto-approver. The auto-approver approves all CertificateRequests that reference issuers matching the 'approveSignerNames' option. This 'disableAutoApproval' option is useful when you want to make all approval decisions using a different approver (like approver-policy - https://github.com/cert-manager/approver-policy).", + "type": "boolean" + }, + "helm-values.dns01RecursiveNameservers": { + "default": "", + "description": "A comma-separated string with the host and port of the recursive nameservers cert-manager should query.", + "type": "string" + }, + "helm-values.dns01RecursiveNameserversOnly": { + "default": false, + "description": "Forces cert-manager to use only the recursive nameservers for verification. Enabling this option could cause the DNS01 self check to take longer owing to caching performed by the recursive nameservers.", + "type": "boolean" + }, + "helm-values.enableCertificateOwnerRef": { + "default": false, + "description": "When this flag is enabled, secrets will be automatically removed when the certificate resource is deleted.", + "type": "boolean" + }, + "helm-values.enableServiceLinks": { + "default": false, + "description": "enableServiceLinks indicates whether information about services should be injected into the pod's environment variables, matching the syntax of Docker links.", + "type": "boolean" + }, + "helm-values.enabled": { + "default": true, + "description": "Field that can be used as a condition when cert-manager is a dependency. This definition is only here as a placeholder such that it is included in the json schema. See https://helm.sh/docs/chart_best_practices/dependencies/#conditions-and-tags for more info.", + "type": "boolean" + }, + "helm-values.extraArgs": { + "default": [], + "description": "Additional command line flags to pass to cert-manager controller binary. To see all available flags run `docker run quay.io/jetstack/cert-manager-controller: --help`.\n\nUse this flag to enable or disable arbitrary controllers. For example, to disable the CertificateRequests approver.\n\nFor example:\nextraArgs:\n - --controllers=*,-certificaterequests-approver", + "items": {}, + "type": "array" + }, + "helm-values.extraEnv": { + "default": [], + "description": "Additional environment variables to pass to cert-manager controller binary.\nFor example:\nextraEnv:\n- name: SOME_VAR\n value: 'some value'", + "items": {}, + "type": "array" + }, + "helm-values.extraObjects": { + "default": [], + "description": "Create dynamic manifests via values.\n\nFor example:\nextraObjects:\n - |\n apiVersion: v1\n kind: ConfigMap\n metadata:\n name: '{{ template \"cert-manager.fullname\" . }}-extra-configmap'", + "items": {}, + "type": "array" + }, + "helm-values.featureGates": { + "default": "", + "description": "A comma-separated list of feature gates that should be enabled on the controller pod.", + "type": "string" + }, + "helm-values.fullnameOverride": { + "description": "Override the \"cert-manager.fullname\" value. This value is used as part of most of the names of the resources created by this Helm chart.", + "type": "string" + }, + "helm-values.global": { + "description": "Global values shared across all (sub)charts", + "properties": { + "commonLabels": { + "$ref": "#/$defs/helm-values.global.commonLabels" + }, + "hostUsers": { + "$ref": "#/$defs/helm-values.global.hostUsers" + }, + "imagePullSecrets": { + "$ref": "#/$defs/helm-values.global.imagePullSecrets" + }, + "leaderElection": { + "$ref": "#/$defs/helm-values.global.leaderElection" + }, + "logLevel": { + "$ref": "#/$defs/helm-values.global.logLevel" + }, + "nodeSelector": { + "$ref": "#/$defs/helm-values.global.nodeSelector" + }, + "podSecurityPolicy": { + "$ref": "#/$defs/helm-values.global.podSecurityPolicy" + }, + "priorityClassName": { + "$ref": "#/$defs/helm-values.global.priorityClassName" + }, + "rbac": { + "$ref": "#/$defs/helm-values.global.rbac" + }, + "revisionHistoryLimit": { + "$ref": "#/$defs/helm-values.global.revisionHistoryLimit" + } + }, + "type": "object" + }, + "helm-values.global.commonLabels": { + "default": {}, + "description": "Labels to apply to all resources.\nPlease note that this does not add labels to the resources created dynamically by the controllers. For these resources, you have to add the labels in the template in the cert-manager custom resource: For example, podTemplate/ ingressTemplate in ACMEChallengeSolverHTTP01Ingress. For more information, see the [cert-manager documentation](https://cert-manager.io/docs/reference/api-docs/#acme.cert-manager.io/v1.ACMEChallengeSolverHTTP01Ingress).\nFor example, secretTemplate in CertificateSpec\nFor more information, see the [cert-manager documentation](https://cert-manager.io/docs/reference/api-docs/#cert-manager.io/v1.CertificateSpec).", + "type": "object" + }, + "helm-values.global.hostUsers": { + "description": "Set all pods to run in a user namespace without host access. Experimental: may be removed once the Kubernetes User Namespaces feature is GA.\n\nRequirements:\n - Kubernetes ≥ 1.33, or\n - Kubernetes 1.27–1.32 with UserNamespacesSupport feature gate enabled.\n\nSet to false to run pods in a user namespace without host access.\n\nSee [limitations](https://kubernetes.io/docs/concepts/workloads/pods/user-namespaces/#limitations) for details.", + "type": "boolean" + }, + "helm-values.global.imagePullSecrets": { + "default": [], + "description": "Reference to one or more secrets to be used when pulling images. For more information, see [Pull an Image from a Private Registry](https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/).\n\nFor example:\nimagePullSecrets:\n - name: \"image-pull-secret\"", + "items": {}, + "type": "array" + }, + "helm-values.global.leaderElection": { + "properties": { + "leaseDuration": { + "$ref": "#/$defs/helm-values.global.leaderElection.leaseDuration" + }, + "namespace": { + "$ref": "#/$defs/helm-values.global.leaderElection.namespace" + }, + "renewDeadline": { + "$ref": "#/$defs/helm-values.global.leaderElection.renewDeadline" + }, + "retryPeriod": { + "$ref": "#/$defs/helm-values.global.leaderElection.retryPeriod" + } + }, + "type": "object" + }, + "helm-values.global.leaderElection.leaseDuration": { + "description": "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.", + "type": "string" + }, + "helm-values.global.leaderElection.namespace": { + "default": "kube-system", + "description": "Override the namespace used for the leader election lease.", + "type": "string" + }, + "helm-values.global.leaderElection.renewDeadline": { + "description": "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.", + "type": "string" + }, + "helm-values.global.leaderElection.retryPeriod": { + "description": "The duration the clients should wait between attempting acquisition and renewal of a leadership.", + "type": "string" + }, + "helm-values.global.logLevel": { + "default": 2, + "description": "Set the verbosity of cert-manager. A range of 0 - 6, with 6 being the most verbose.", + "type": "number" + }, + "helm-values.global.nodeSelector": { + "default": {}, + "description": "Global node selector\n\nThe nodeSelector on Pods tells Kubernetes to schedule Pods on the nodes with matching labels. For more information, see [Assigning Pods to Nodes](https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/).\n\nIf a component-specific nodeSelector is also set, it will take precedence.", + "type": "object" + }, + "helm-values.global.podSecurityPolicy": { + "properties": { + "enabled": { + "$ref": "#/$defs/helm-values.global.podSecurityPolicy.enabled" + }, + "useAppArmor": { + "$ref": "#/$defs/helm-values.global.podSecurityPolicy.useAppArmor" + } + }, + "type": "object" + }, + "helm-values.global.podSecurityPolicy.enabled": { + "default": false, + "description": "Create PodSecurityPolicy for cert-manager.\n\nNote that PodSecurityPolicy was deprecated in Kubernetes 1.21 and removed in Kubernetes 1.25.", + "type": "boolean" + }, + "helm-values.global.podSecurityPolicy.useAppArmor": { + "default": true, + "description": "Configure the PodSecurityPolicy to use AppArmor.", + "type": "boolean" + }, + "helm-values.global.priorityClassName": { + "default": "", + "description": "The optional priority class to be used for the cert-manager pods.", + "type": "string" + }, + "helm-values.global.rbac": { + "properties": { + "aggregateClusterRoles": { + "$ref": "#/$defs/helm-values.global.rbac.aggregateClusterRoles" + }, + "create": { + "$ref": "#/$defs/helm-values.global.rbac.create" + } + }, + "type": "object" + }, + "helm-values.global.rbac.aggregateClusterRoles": { + "default": true, + "description": "Aggregate ClusterRoles to Kubernetes default user-facing roles. For more information, see [User-facing roles](https://kubernetes.io/docs/reference/access-authn-authz/rbac/#user-facing-roles)", + "type": "boolean" + }, + "helm-values.global.rbac.create": { + "default": true, + "description": "Create required ClusterRoles and ClusterRoleBindings for cert-manager.", + "type": "boolean" + }, + "helm-values.global.revisionHistoryLimit": { + "description": "The number of old ReplicaSets to retain to allow rollback (if not set, the default Kubernetes value is set to 10).", + "type": "number" + }, + "helm-values.hostAliases": { + "default": [], + "description": "Optional hostAliases for cert-manager-controller pods. May be useful when performing ACME DNS-01 self checks.", + "items": {}, + "type": "array" + }, + "helm-values.http_proxy": { + "description": "Configures the HTTP_PROXY environment variable where a HTTP proxy is required.", + "type": "string" + }, + "helm-values.https_proxy": { + "description": "Configures the HTTPS_PROXY environment variable where a HTTP proxy is required.", + "type": "string" + }, + "helm-values.image": { + "additionalProperties": false, + "properties": { + "digest": { + "$ref": "#/$defs/helm-values.image.digest" + }, + "pullPolicy": { + "$ref": "#/$defs/helm-values.image.pullPolicy" + }, + "registry": { + "$ref": "#/$defs/helm-values.image.registry" + }, + "repository": { + "$ref": "#/$defs/helm-values.image.repository" + }, + "tag": { + "$ref": "#/$defs/helm-values.image.tag" + } + }, + "type": "object" + }, + "helm-values.image.digest": { + "description": "Setting a digest will override any tag.", + "type": "string" + }, + "helm-values.image.pullPolicy": { + "default": "IfNotPresent", + "description": "Kubernetes imagePullPolicy on Deployment.", + "type": "string" + }, + "helm-values.image.registry": { + "description": "The container registry to pull the manager image from.", + "type": "string" + }, + "helm-values.image.repository": { + "default": "quay.io/jetstack/cert-manager-controller", + "description": "The container image for the cert-manager controller.", + "type": "string" + }, + "helm-values.image.tag": { + "description": "Override the image tag to deploy by setting this variable. If no value is set, the chart's appVersion is used.", + "type": "string" + }, + "helm-values.ingressShim": { + "additionalProperties": false, + "properties": { + "defaultIssuerGroup": { + "$ref": "#/$defs/helm-values.ingressShim.defaultIssuerGroup" + }, + "defaultIssuerKind": { + "$ref": "#/$defs/helm-values.ingressShim.defaultIssuerKind" + }, + "defaultIssuerName": { + "$ref": "#/$defs/helm-values.ingressShim.defaultIssuerName" + } + }, + "type": "object" + }, + "helm-values.ingressShim.defaultIssuerGroup": { + "description": "Optional default issuer group to use for ingress resources.", + "type": "string" + }, + "helm-values.ingressShim.defaultIssuerKind": { + "description": "Optional default issuer kind to use for ingress resources.", + "type": "string" + }, + "helm-values.ingressShim.defaultIssuerName": { + "description": "Optional default issuer to use for ingress resources.", + "type": "string" + }, + "helm-values.installCRDs": { + "default": false, + "description": "This option is equivalent to setting crds.enabled=true and crds.keep=true. Deprecated: use crds.enabled and crds.keep instead.", + "type": "boolean" + }, + "helm-values.livenessProbe": { + "default": { + "enabled": true, + "failureThreshold": 8, + "initialDelaySeconds": 10, + "periodSeconds": 10, + "successThreshold": 1, + "timeoutSeconds": 15 + }, + "description": "LivenessProbe settings for the controller container of the controller Pod.\n\nThis is enabled by default, in order to enable the clock-skew liveness probe that restarts the controller in case of a skew between the system clock and the monotonic clock. LivenessProbe durations and thresholds are based on those used for the Kubernetes controller-manager. For more information see the following on the\n[Kubernetes GitHub repository](https://github.com/kubernetes/kubernetes/blob/806b30170c61a38fedd54cc9ede4cd6275a1ad3b/cmd/kubeadm/app/util/staticpod/utils.go#L241-L245)", + "type": "object" + }, + "helm-values.maxConcurrentChallenges": { + "default": 60, + "description": "The maximum number of challenges that can be scheduled as 'processing' at once.", + "type": "number" + }, + "helm-values.nameOverride": { + "description": "Override the \"cert-manager.name\" value, which is used to annotate some of the resources that are created by this Chart (using \"app.kubernetes.io/name\"). NOTE: There are some inconsistencies in the Helm chart when it comes to these annotations (some resources use, e.g., \"cainjector.name\" which resolves to the value \"cainjector\").", + "type": "string" + }, + "helm-values.namespace": { + "default": "", + "description": "This namespace allows you to define where the services are installed into. If not set then they use the namespace of the release. This is helpful when installing cert manager as a chart dependency (sub chart).", + "type": "string" + }, + "helm-values.no_proxy": { + "description": "Configures the NO_PROXY environment variable where a HTTP proxy is required, but certain domains should be excluded.", + "type": "string" + }, + "helm-values.nodeSelector": { + "default": { + "kubernetes.io/os": "linux" + }, + "description": "The nodeSelector on Pods tells Kubernetes to schedule Pods on the nodes with matching labels. For more information, see [Assigning Pods to Nodes](https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/).\n\nThis default ensures that Pods are only scheduled to Linux nodes. It prevents Pods being scheduled to Windows nodes in a mixed OS cluster.", + "type": "object" + }, + "helm-values.podAnnotations": { + "description": "Optional additional annotations to add to the controller Pods.", + "type": "object" + }, + "helm-values.podDisruptionBudget": { + "additionalProperties": false, + "properties": { + "enabled": { + "$ref": "#/$defs/helm-values.podDisruptionBudget.enabled" + }, + "maxUnavailable": { + "$ref": "#/$defs/helm-values.podDisruptionBudget.maxUnavailable" + }, + "minAvailable": { + "$ref": "#/$defs/helm-values.podDisruptionBudget.minAvailable" + } + }, + "type": "object" + }, + "helm-values.podDisruptionBudget.enabled": { + "default": false, + "description": "Enable or disable the PodDisruptionBudget resource.\n\nThis prevents downtime during voluntary disruptions such as during a Node upgrade. For example, the PodDisruptionBudget will block `kubectl drain` if it is used on the Node where the only remaining cert-manager\nPod is currently running.", + "type": "boolean" + }, + "helm-values.podDisruptionBudget.maxUnavailable": { + "description": "This configures the maximum unavailable pods for disruptions. It can either be set to an integer (e.g., 1) or a percentage value (e.g., 25%). it cannot be used if `minAvailable` is set." + }, + "helm-values.podDisruptionBudget.minAvailable": { + "description": "This configures the minimum available pods for disruptions. It can either be set to an integer (e.g., 1) or a percentage value (e.g., 25%).\nIt cannot be used if `maxUnavailable` is set." + }, + "helm-values.podDnsConfig": { + "description": "Pod DNS configuration. The podDnsConfig field is optional and can work with any podDnsPolicy settings. However, when a Pod's dnsPolicy is set to \"None\", the dnsConfig field has to be specified. For more information, see [Pod's DNS Config](https://kubernetes.io/docs/concepts/services-networking/dns-pod-service/#pod-dns-config).", + "type": "object" + }, + "helm-values.podDnsPolicy": { + "description": "Pod DNS policy.\nFor more information, see [Pod's DNS Policy](https://kubernetes.io/docs/concepts/services-networking/dns-pod-service/#pod-s-dns-policy).", + "type": "string" + }, + "helm-values.podLabels": { + "default": {}, + "description": "Optional additional labels to add to the controller Pods.", + "type": "object" + }, + "helm-values.prometheus": { + "additionalProperties": false, + "properties": { + "enabled": { + "$ref": "#/$defs/helm-values.prometheus.enabled" + }, + "podmonitor": { + "$ref": "#/$defs/helm-values.prometheus.podmonitor" + }, + "servicemonitor": { + "$ref": "#/$defs/helm-values.prometheus.servicemonitor" + } + }, + "type": "object" + }, + "helm-values.prometheus.enabled": { + "default": true, + "description": "Enable Prometheus monitoring for the cert-manager controller and webhook. If you use the Prometheus Operator, set prometheus.podmonitor.enabled or prometheus.servicemonitor.enabled, to create a PodMonitor or a\nServiceMonitor resource.\nOtherwise, 'prometheus.io' annotations are added to the cert-manager and cert-manager-webhook Deployments. Note that you cannot enable both PodMonitor and ServiceMonitor as they are mutually exclusive. Enabling both will result in an error.", + "type": "boolean" + }, + "helm-values.prometheus.podmonitor": { + "additionalProperties": false, + "properties": { + "annotations": { + "$ref": "#/$defs/helm-values.prometheus.podmonitor.annotations" + }, + "enabled": { + "$ref": "#/$defs/helm-values.prometheus.podmonitor.enabled" + }, + "endpointAdditionalProperties": { + "$ref": "#/$defs/helm-values.prometheus.podmonitor.endpointAdditionalProperties" + }, + "honorLabels": { + "$ref": "#/$defs/helm-values.prometheus.podmonitor.honorLabels" + }, + "interval": { + "$ref": "#/$defs/helm-values.prometheus.podmonitor.interval" + }, + "labels": { + "$ref": "#/$defs/helm-values.prometheus.podmonitor.labels" + }, + "namespace": { + "$ref": "#/$defs/helm-values.prometheus.podmonitor.namespace" + }, + "path": { + "$ref": "#/$defs/helm-values.prometheus.podmonitor.path" + }, + "prometheusInstance": { + "$ref": "#/$defs/helm-values.prometheus.podmonitor.prometheusInstance" + }, + "scrapeTimeout": { + "$ref": "#/$defs/helm-values.prometheus.podmonitor.scrapeTimeout" + } + }, + "type": "object" + }, + "helm-values.prometheus.podmonitor.annotations": { + "default": {}, + "description": "Additional annotations to add to the PodMonitor.", + "type": "object" + }, + "helm-values.prometheus.podmonitor.enabled": { + "default": false, + "description": "Create a PodMonitor to add cert-manager to Prometheus.", + "type": "boolean" + }, + "helm-values.prometheus.podmonitor.endpointAdditionalProperties": { + "default": {}, + "description": "EndpointAdditionalProperties allows setting additional properties on the endpoint such as relabelings, metricRelabelings etc.\n\nFor example:\nendpointAdditionalProperties:\n relabelings:\n - action: replace\n sourceLabels:\n - __meta_kubernetes_pod_node_name\n targetLabel: instance\n # Configure the PodMonitor for TLS connections\n # See https://cert-manager.io/docs/devops-tips/prometheus-metrics/#tls\n scheme: https\n tlsConfig:\n serverName: cert-manager-metrics\n ca:\n secret:\n name: cert-manager-metrics-ca\n key: \"tls.crt\"", + "type": "object" + }, + "helm-values.prometheus.podmonitor.honorLabels": { + "default": false, + "description": "Keep labels from scraped data, overriding server-side labels.", + "type": "boolean" + }, + "helm-values.prometheus.podmonitor.interval": { + "default": "60s", + "description": "The interval to scrape metrics.", + "type": "string" + }, + "helm-values.prometheus.podmonitor.labels": { + "default": {}, + "description": "Additional labels to add to the PodMonitor.", + "type": "object" + }, + "helm-values.prometheus.podmonitor.namespace": { + "description": "The namespace that the pod monitor should live in, defaults to the cert-manager namespace.", + "type": "string" + }, + "helm-values.prometheus.podmonitor.path": { + "default": "/metrics", + "description": "The path to scrape for metrics.", + "type": "string" + }, + "helm-values.prometheus.podmonitor.prometheusInstance": { + "default": "default", + "description": "Specifies the `prometheus` label on the created PodMonitor. This is used when different Prometheus instances have label selectors matching different PodMonitors.", + "type": "string" + }, + "helm-values.prometheus.podmonitor.scrapeTimeout": { + "default": "30s", + "description": "The timeout before a metrics scrape fails.", + "type": "string" + }, + "helm-values.prometheus.servicemonitor": { + "additionalProperties": false, + "properties": { + "annotations": { + "$ref": "#/$defs/helm-values.prometheus.servicemonitor.annotations" + }, + "enabled": { + "$ref": "#/$defs/helm-values.prometheus.servicemonitor.enabled" + }, + "endpointAdditionalProperties": { + "$ref": "#/$defs/helm-values.prometheus.servicemonitor.endpointAdditionalProperties" + }, + "honorLabels": { + "$ref": "#/$defs/helm-values.prometheus.servicemonitor.honorLabels" + }, + "interval": { + "$ref": "#/$defs/helm-values.prometheus.servicemonitor.interval" + }, + "labels": { + "$ref": "#/$defs/helm-values.prometheus.servicemonitor.labels" + }, + "namespace": { + "$ref": "#/$defs/helm-values.prometheus.servicemonitor.namespace" + }, + "path": { + "$ref": "#/$defs/helm-values.prometheus.servicemonitor.path" + }, + "prometheusInstance": { + "$ref": "#/$defs/helm-values.prometheus.servicemonitor.prometheusInstance" + }, + "scrapeTimeout": { + "$ref": "#/$defs/helm-values.prometheus.servicemonitor.scrapeTimeout" + }, + "targetPort": { + "$ref": "#/$defs/helm-values.prometheus.servicemonitor.targetPort" + } + }, + "type": "object" + }, + "helm-values.prometheus.servicemonitor.annotations": { + "default": {}, + "description": "Additional annotations to add to the ServiceMonitor.", + "type": "object" + }, + "helm-values.prometheus.servicemonitor.enabled": { + "default": false, + "description": "Create a ServiceMonitor to add cert-manager to Prometheus.", + "type": "boolean" + }, + "helm-values.prometheus.servicemonitor.endpointAdditionalProperties": { + "default": {}, + "description": "EndpointAdditionalProperties allows setting additional properties on the endpoint such as relabelings, metricRelabelings etc.\n\nFor example:\nendpointAdditionalProperties:\n relabelings:\n - action: replace\n sourceLabels:\n - __meta_kubernetes_pod_node_name\n targetLabel: instance", + "type": "object" + }, + "helm-values.prometheus.servicemonitor.honorLabels": { + "default": false, + "description": "Keep labels from scraped data, overriding server-side labels.", + "type": "boolean" + }, + "helm-values.prometheus.servicemonitor.interval": { + "default": "60s", + "description": "The interval to scrape metrics.", + "type": "string" + }, + "helm-values.prometheus.servicemonitor.labels": { + "default": {}, + "description": "Additional labels to add to the ServiceMonitor.", + "type": "object" + }, + "helm-values.prometheus.servicemonitor.namespace": { + "description": "The namespace that the service monitor should live in, defaults to the cert-manager namespace.", + "type": "string" + }, + "helm-values.prometheus.servicemonitor.path": { + "default": "/metrics", + "description": "The path to scrape for metrics.", + "type": "string" + }, + "helm-values.prometheus.servicemonitor.prometheusInstance": { + "default": "default", + "description": "Specifies the `prometheus` label on the created ServiceMonitor. This is used when different Prometheus instances have label selectors matching different ServiceMonitors.", + "type": "string" + }, + "helm-values.prometheus.servicemonitor.scrapeTimeout": { + "default": "30s", + "description": "The timeout before a metrics scrape fails.", + "type": "string" + }, + "helm-values.prometheus.servicemonitor.targetPort": { + "default": "http-metrics", + "description": "The target port to set on the ServiceMonitor. This must match the port that the cert-manager controller is listening on for metrics." + }, + "helm-values.replicaCount": { + "default": 1, + "description": "The number of replicas of the cert-manager controller to run.\n\nThe default is 1, but in production set this to 2 or 3 to provide high availability.\n\nIf `replicas > 1`, consider setting `podDisruptionBudget.enabled=true`.\n\nNote that cert-manager uses leader election to ensure that there can only be a single instance active at a time.", + "type": "number" + }, + "helm-values.resources": { + "default": {}, + "description": "Resources to provide to the cert-manager controller pod.\n\nFor example:\nrequests:\n cpu: 10m\n memory: 32Mi\nFor more information, see [Resource Management for Pods and Containers](https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/).", + "type": "object" + }, + "helm-values.securityContext": { + "default": { + "runAsNonRoot": true, + "seccompProfile": { + "type": "RuntimeDefault" + } + }, + "description": "Pod Security Context.\nFor more information, see [Configure a Security Context for a Pod or Container](https://kubernetes.io/docs/tasks/configure-pod-container/security-context/).", + "type": "object" + }, + "helm-values.serviceAccount": { + "additionalProperties": false, + "properties": { + "annotations": { + "$ref": "#/$defs/helm-values.serviceAccount.annotations" + }, + "automountServiceAccountToken": { + "$ref": "#/$defs/helm-values.serviceAccount.automountServiceAccountToken" + }, + "create": { + "$ref": "#/$defs/helm-values.serviceAccount.create" + }, + "labels": { + "$ref": "#/$defs/helm-values.serviceAccount.labels" + }, + "name": { + "$ref": "#/$defs/helm-values.serviceAccount.name" + } + }, + "type": "object" + }, + "helm-values.serviceAccount.annotations": { + "description": "Optional additional annotations to add to the controller's Service Account. Templates are allowed for both keys and values.\nExample using templating:\nannotations:\n \"{{ .Chart.Name }}-helm-chart/version\": \"{{ .Chart.Version }}\"", + "type": "object" + }, + "helm-values.serviceAccount.automountServiceAccountToken": { + "default": true, + "description": "Automount API credentials for a Service Account.", + "type": "boolean" + }, + "helm-values.serviceAccount.create": { + "default": true, + "description": "Specifies whether a service account should be created.", + "type": "boolean" + }, + "helm-values.serviceAccount.labels": { + "description": "Optional additional labels to add to the controller's Service Account.", + "type": "object" + }, + "helm-values.serviceAccount.name": { + "description": "The name of the service account to use.\nIf not set and create is true, a name is generated using the fullname template.", + "type": "string" + }, + "helm-values.serviceAnnotations": { + "description": "Optional annotations to add to the controller Service.", + "type": "object" + }, + "helm-values.serviceIPFamilies": { + "description": "Optionally set the IP families for the controller Service that should be supported, in the order in which they should be applied to ClusterIP. Can be IPv4 and/or IPv6.", + "items": {}, + "type": "array" + }, + "helm-values.serviceIPFamilyPolicy": { + "description": "Optionally set the IP family policy for the controller Service to configure dual-stack; see [Configure dual-stack](https://kubernetes.io/docs/concepts/services-networking/dual-stack/#services).", + "type": "string" + }, + "helm-values.serviceLabels": { + "description": "Optional additional labels to add to the controller Service.", + "type": "object" + }, + "helm-values.startupapicheck": { + "additionalProperties": false, + "properties": { + "affinity": { + "$ref": "#/$defs/helm-values.startupapicheck.affinity" + }, + "automountServiceAccountToken": { + "$ref": "#/$defs/helm-values.startupapicheck.automountServiceAccountToken" + }, + "backoffLimit": { + "$ref": "#/$defs/helm-values.startupapicheck.backoffLimit" + }, + "containerSecurityContext": { + "$ref": "#/$defs/helm-values.startupapicheck.containerSecurityContext" + }, + "enableServiceLinks": { + "$ref": "#/$defs/helm-values.startupapicheck.enableServiceLinks" + }, + "enabled": { + "$ref": "#/$defs/helm-values.startupapicheck.enabled" + }, + "extraArgs": { + "$ref": "#/$defs/helm-values.startupapicheck.extraArgs" + }, + "extraEnv": { + "$ref": "#/$defs/helm-values.startupapicheck.extraEnv" + }, + "image": { + "$ref": "#/$defs/helm-values.startupapicheck.image" + }, + "jobAnnotations": { + "$ref": "#/$defs/helm-values.startupapicheck.jobAnnotations" + }, + "nodeSelector": { + "$ref": "#/$defs/helm-values.startupapicheck.nodeSelector" + }, + "podAnnotations": { + "$ref": "#/$defs/helm-values.startupapicheck.podAnnotations" + }, + "podLabels": { + "$ref": "#/$defs/helm-values.startupapicheck.podLabels" + }, + "rbac": { + "$ref": "#/$defs/helm-values.startupapicheck.rbac" + }, + "resources": { + "$ref": "#/$defs/helm-values.startupapicheck.resources" + }, + "securityContext": { + "$ref": "#/$defs/helm-values.startupapicheck.securityContext" + }, + "serviceAccount": { + "$ref": "#/$defs/helm-values.startupapicheck.serviceAccount" + }, + "timeout": { + "$ref": "#/$defs/helm-values.startupapicheck.timeout" + }, + "tolerations": { + "$ref": "#/$defs/helm-values.startupapicheck.tolerations" + }, + "volumeMounts": { + "$ref": "#/$defs/helm-values.startupapicheck.volumeMounts" + }, + "volumes": { + "$ref": "#/$defs/helm-values.startupapicheck.volumes" + } + }, + "type": "object" + }, + "helm-values.startupapicheck.affinity": { + "default": {}, + "description": "A Kubernetes Affinity, if required. For more information, see [Affinity v1 core](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.27/#affinity-v1-core).\nFor example:\naffinity:\n nodeAffinity:\n requiredDuringSchedulingIgnoredDuringExecution:\n nodeSelectorTerms:\n - matchExpressions:\n - key: foo.bar.com/role\n operator: In\n values:\n - master", + "type": "object" + }, + "helm-values.startupapicheck.automountServiceAccountToken": { + "description": "Automounting API credentials for a particular pod.", + "type": "boolean" + }, + "helm-values.startupapicheck.backoffLimit": { + "default": 4, + "description": "Job backoffLimit", + "type": "number" + }, + "helm-values.startupapicheck.containerSecurityContext": { + "default": { + "allowPrivilegeEscalation": false, + "capabilities": { + "drop": [ + "ALL" + ] + }, + "readOnlyRootFilesystem": true + }, + "description": "Container Security Context to be set on the controller component container. For more information, see [Configure a Security Context for a Pod or Container](https://kubernetes.io/docs/tasks/configure-pod-container/security-context/).", + "type": "object" + }, + "helm-values.startupapicheck.enableServiceLinks": { + "default": false, + "description": "enableServiceLinks indicates whether information about services should be injected into pod's environment variables, matching the syntax of Docker links.", + "type": "boolean" + }, + "helm-values.startupapicheck.enabled": { + "default": true, + "description": "Enables the startup api check.", + "type": "boolean" + }, + "helm-values.startupapicheck.extraArgs": { + "default": [ + "-v" + ], + "description": "Additional command line flags to pass to startupapicheck binary. To see all available flags run `docker run quay.io/jetstack/cert-manager-startupapicheck: --help`.\n\nVerbose logging is enabled by default so that if startupapicheck fails, you can know what exactly caused the failure. Verbose logs include details of the webhook URL, IP address and TCP connect errors for example.", + "items": {}, + "type": "array" + }, + "helm-values.startupapicheck.extraEnv": { + "default": [], + "description": "Additional environment variables to pass to cert-manager startupapicheck binary.\nFor example:\nextraEnv:\n- name: SOME_VAR\n value: 'some value'", + "items": {}, + "type": "array" + }, + "helm-values.startupapicheck.image": { + "additionalProperties": false, + "properties": { + "digest": { + "$ref": "#/$defs/helm-values.startupapicheck.image.digest" + }, + "pullPolicy": { + "$ref": "#/$defs/helm-values.startupapicheck.image.pullPolicy" + }, + "registry": { + "$ref": "#/$defs/helm-values.startupapicheck.image.registry" + }, + "repository": { + "$ref": "#/$defs/helm-values.startupapicheck.image.repository" + }, + "tag": { + "$ref": "#/$defs/helm-values.startupapicheck.image.tag" + } + }, + "type": "object" + }, + "helm-values.startupapicheck.image.digest": { + "description": "Setting a digest will override any tag.", + "type": "string" + }, + "helm-values.startupapicheck.image.pullPolicy": { + "default": "IfNotPresent", + "description": "Kubernetes imagePullPolicy on Deployment.", + "type": "string" + }, + "helm-values.startupapicheck.image.registry": { + "description": "The container registry to pull the startupapicheck image from.", + "type": "string" + }, + "helm-values.startupapicheck.image.repository": { + "default": "quay.io/jetstack/cert-manager-startupapicheck", + "description": "The container image for the cert-manager startupapicheck.", + "type": "string" + }, + "helm-values.startupapicheck.image.tag": { + "description": "Override the image tag to deploy by setting this variable. If no value is set, the chart's appVersion is used.", + "type": "string" + }, + "helm-values.startupapicheck.jobAnnotations": { + "default": { + "helm.sh/hook": "post-install", + "helm.sh/hook-delete-policy": "before-hook-creation,hook-succeeded", + "helm.sh/hook-weight": "1" + }, + "description": "Optional additional annotations to add to the startupapicheck Job.", + "type": "object" + }, + "helm-values.startupapicheck.nodeSelector": { + "default": { + "kubernetes.io/os": "linux" + }, + "description": "The nodeSelector on Pods tells Kubernetes to schedule Pods on the nodes with matching labels. For more information, see [Assigning Pods to Nodes](https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/).\n\nThis default ensures that Pods are only scheduled to Linux nodes. It prevents Pods being scheduled to Windows nodes in a mixed OS cluster.", + "type": "object" + }, + "helm-values.startupapicheck.podAnnotations": { + "description": "Optional additional annotations to add to the startupapicheck Pods.", + "type": "object" + }, + "helm-values.startupapicheck.podLabels": { + "default": {}, + "description": "Optional additional labels to add to the startupapicheck Pods.", + "type": "object" + }, + "helm-values.startupapicheck.rbac": { + "additionalProperties": false, + "properties": { + "annotations": { + "$ref": "#/$defs/helm-values.startupapicheck.rbac.annotations" + } + }, + "type": "object" + }, + "helm-values.startupapicheck.rbac.annotations": { + "default": { + "helm.sh/hook": "post-install", + "helm.sh/hook-delete-policy": "before-hook-creation,hook-succeeded", + "helm.sh/hook-weight": "-5" + }, + "description": "annotations for the startup API Check job RBAC and PSP resources.", + "type": "object" + }, + "helm-values.startupapicheck.resources": { + "default": {}, + "description": "Resources to provide to the cert-manager controller pod.\n\nFor example:\nrequests:\n cpu: 10m\n memory: 32Mi\nFor more information, see [Resource Management for Pods and Containers](https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/).", + "type": "object" + }, + "helm-values.startupapicheck.securityContext": { + "default": { + "runAsNonRoot": true, + "seccompProfile": { + "type": "RuntimeDefault" + } + }, + "description": "Pod Security Context to be set on the startupapicheck component Pod. For more information, see [Configure a Security Context for a Pod or Container](https://kubernetes.io/docs/tasks/configure-pod-container/security-context/).", + "type": "object" + }, + "helm-values.startupapicheck.serviceAccount": { + "additionalProperties": false, + "properties": { + "annotations": { + "$ref": "#/$defs/helm-values.startupapicheck.serviceAccount.annotations" + }, + "automountServiceAccountToken": { + "$ref": "#/$defs/helm-values.startupapicheck.serviceAccount.automountServiceAccountToken" + }, + "create": { + "$ref": "#/$defs/helm-values.startupapicheck.serviceAccount.create" + }, + "labels": { + "$ref": "#/$defs/helm-values.startupapicheck.serviceAccount.labels" + }, + "name": { + "$ref": "#/$defs/helm-values.startupapicheck.serviceAccount.name" + } + }, + "type": "object" + }, + "helm-values.startupapicheck.serviceAccount.annotations": { + "default": { + "helm.sh/hook": "post-install", + "helm.sh/hook-delete-policy": "before-hook-creation,hook-succeeded", + "helm.sh/hook-weight": "-5" + }, + "description": "Optional additional annotations to add to the Job's Service Account.", + "type": "object" + }, + "helm-values.startupapicheck.serviceAccount.automountServiceAccountToken": { + "default": true, + "description": "Automount API credentials for a Service Account.", + "type": "boolean" + }, + "helm-values.startupapicheck.serviceAccount.create": { + "default": true, + "description": "Specifies whether a service account should be created.", + "type": "boolean" + }, + "helm-values.startupapicheck.serviceAccount.labels": { + "description": "Optional additional labels to add to the startupapicheck's Service Account.", + "type": "object" + }, + "helm-values.startupapicheck.serviceAccount.name": { + "description": "The name of the service account to use.\nIf not set and create is true, a name is generated using the fullname template.", + "type": "string" + }, + "helm-values.startupapicheck.timeout": { + "default": "1m", + "description": "Timeout for 'kubectl check api' command.", + "type": "string" + }, + "helm-values.startupapicheck.tolerations": { + "default": [], + "description": "A list of Kubernetes Tolerations, if required. For more information, see [Toleration v1 core](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.27/#toleration-v1-core).\n\nFor example:\ntolerations:\n- key: foo.bar.com/role\n operator: Equal\n value: master\n effect: NoSchedule", + "items": {}, + "type": "array" + }, + "helm-values.startupapicheck.volumeMounts": { + "default": [], + "description": "Additional volume mounts to add to the cert-manager controller container.", + "items": {}, + "type": "array" + }, + "helm-values.startupapicheck.volumes": { + "default": [], + "description": "Additional volumes to add to the cert-manager controller pod.", + "items": {}, + "type": "array" + }, + "helm-values.strategy": { + "default": {}, + "description": "Deployment update strategy for the cert-manager controller deployment. For more information, see the [Kubernetes documentation](https://kubernetes.io/docs/concepts/workloads/controllers/deployment/#strategy).\n\nFor example:\nstrategy:\n type: RollingUpdate\n rollingUpdate:\n maxSurge: 0\n maxUnavailable: 1", + "type": "object" + }, + "helm-values.tolerations": { + "default": [], + "description": "A list of Kubernetes Tolerations, if required. For more information, see [Toleration v1 core](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.27/#toleration-v1-core).\n\nFor example:\ntolerations:\n- key: foo.bar.com/role\n operator: Equal\n value: master\n effect: NoSchedule", + "items": {}, + "type": "array" + }, + "helm-values.topologySpreadConstraints": { + "default": [], + "description": "A list of Kubernetes TopologySpreadConstraints, if required. For more information, see [Topology spread constraint v1 core](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.27/#topologyspreadconstraint-v1-core\n\nFor example:\ntopologySpreadConstraints:\n- maxSkew: 2\n topologyKey: topology.kubernetes.io/zone\n whenUnsatisfiable: ScheduleAnyway\n labelSelector:\n matchLabels:\n app.kubernetes.io/instance: cert-manager\n app.kubernetes.io/component: controller", + "items": {}, + "type": "array" + }, + "helm-values.volumeMounts": { + "default": [], + "description": "Additional volume mounts to add to the cert-manager controller container.", + "items": {}, + "type": "array" + }, + "helm-values.volumes": { + "default": [], + "description": "Additional volumes to add to the cert-manager controller pod.", + "items": {}, + "type": "array" + }, + "helm-values.webhook": { + "additionalProperties": false, + "properties": { + "affinity": { + "$ref": "#/$defs/helm-values.webhook.affinity" + }, + "automountServiceAccountToken": { + "$ref": "#/$defs/helm-values.webhook.automountServiceAccountToken" + }, + "config": { + "$ref": "#/$defs/helm-values.webhook.config" + }, + "containerSecurityContext": { + "$ref": "#/$defs/helm-values.webhook.containerSecurityContext" + }, + "deploymentAnnotations": { + "$ref": "#/$defs/helm-values.webhook.deploymentAnnotations" + }, + "enableServiceLinks": { + "$ref": "#/$defs/helm-values.webhook.enableServiceLinks" + }, + "extraArgs": { + "$ref": "#/$defs/helm-values.webhook.extraArgs" + }, + "extraEnv": { + "$ref": "#/$defs/helm-values.webhook.extraEnv" + }, + "featureGates": { + "$ref": "#/$defs/helm-values.webhook.featureGates" + }, + "hostNetwork": { + "$ref": "#/$defs/helm-values.webhook.hostNetwork" + }, + "image": { + "$ref": "#/$defs/helm-values.webhook.image" + }, + "livenessProbe": { + "$ref": "#/$defs/helm-values.webhook.livenessProbe" + }, + "loadBalancerIP": { + "$ref": "#/$defs/helm-values.webhook.loadBalancerIP" + }, + "mutatingWebhookConfiguration": { + "$ref": "#/$defs/helm-values.webhook.mutatingWebhookConfiguration" + }, + "mutatingWebhookConfigurationAnnotations": { + "$ref": "#/$defs/helm-values.webhook.mutatingWebhookConfigurationAnnotations" + }, + "networkPolicy": { + "$ref": "#/$defs/helm-values.webhook.networkPolicy" + }, + "nodeSelector": { + "$ref": "#/$defs/helm-values.webhook.nodeSelector" + }, + "podAnnotations": { + "$ref": "#/$defs/helm-values.webhook.podAnnotations" + }, + "podDisruptionBudget": { + "$ref": "#/$defs/helm-values.webhook.podDisruptionBudget" + }, + "podLabels": { + "$ref": "#/$defs/helm-values.webhook.podLabels" + }, + "readinessProbe": { + "$ref": "#/$defs/helm-values.webhook.readinessProbe" + }, + "replicaCount": { + "$ref": "#/$defs/helm-values.webhook.replicaCount" + }, + "resources": { + "$ref": "#/$defs/helm-values.webhook.resources" + }, + "securePort": { + "$ref": "#/$defs/helm-values.webhook.securePort" + }, + "securityContext": { + "$ref": "#/$defs/helm-values.webhook.securityContext" + }, + "serviceAccount": { + "$ref": "#/$defs/helm-values.webhook.serviceAccount" + }, + "serviceAnnotations": { + "$ref": "#/$defs/helm-values.webhook.serviceAnnotations" + }, + "serviceIPFamilies": { + "$ref": "#/$defs/helm-values.webhook.serviceIPFamilies" + }, + "serviceIPFamilyPolicy": { + "$ref": "#/$defs/helm-values.webhook.serviceIPFamilyPolicy" + }, + "serviceLabels": { + "$ref": "#/$defs/helm-values.webhook.serviceLabels" + }, + "serviceType": { + "$ref": "#/$defs/helm-values.webhook.serviceType" + }, + "strategy": { + "$ref": "#/$defs/helm-values.webhook.strategy" + }, + "timeoutSeconds": { + "$ref": "#/$defs/helm-values.webhook.timeoutSeconds" + }, + "tolerations": { + "$ref": "#/$defs/helm-values.webhook.tolerations" + }, + "topologySpreadConstraints": { + "$ref": "#/$defs/helm-values.webhook.topologySpreadConstraints" + }, + "url": { + "$ref": "#/$defs/helm-values.webhook.url" + }, + "validatingWebhookConfiguration": { + "$ref": "#/$defs/helm-values.webhook.validatingWebhookConfiguration" + }, + "validatingWebhookConfigurationAnnotations": { + "$ref": "#/$defs/helm-values.webhook.validatingWebhookConfigurationAnnotations" + }, + "volumeMounts": { + "$ref": "#/$defs/helm-values.webhook.volumeMounts" + }, + "volumes": { + "$ref": "#/$defs/helm-values.webhook.volumes" + } + }, + "type": "object" + }, + "helm-values.webhook.affinity": { + "default": {}, + "description": "A Kubernetes Affinity, if required. For more information, see [Affinity v1 core](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.27/#affinity-v1-core).\n\nFor example:\naffinity:\n nodeAffinity:\n requiredDuringSchedulingIgnoredDuringExecution:\n nodeSelectorTerms:\n - matchExpressions:\n - key: foo.bar.com/role\n operator: In\n values:\n - master", + "type": "object" + }, + "helm-values.webhook.automountServiceAccountToken": { + "description": "Automounting API credentials for a particular pod.", + "type": "boolean" + }, + "helm-values.webhook.config": { + "default": {}, + "description": "This is used to configure options for the webhook pod. This allows setting options that would usually be provided using flags.\n\nIf `apiVersion` and `kind` are unspecified they default to the current latest version (currently `webhook.config.cert-manager.io/v1alpha1`). You can pin the version by specifying the `apiVersion` yourself.\n\nFor example:\napiVersion: webhook.config.cert-manager.io/v1alpha1\nkind: WebhookConfiguration\n# The port that the webhook listens on for requests.\n# In GKE private clusters, by default Kubernetes apiservers are allowed to\n# talk to the cluster nodes only on 443 and 10250. Configuring\n# securePort: 10250 therefore will work out-of-the-box without needing to add firewall\n# rules or requiring NET_BIND_SERVICE capabilities to bind port numbers < 1000.\n# This should be uncommented and set as a default by the chart once\n# the apiVersion of WebhookConfiguration graduates beyond v1alpha1.\nsecurePort: 10250\n# Configure the metrics server for TLS\n# See https://cert-manager.io/docs/devops-tips/prometheus-metrics/#tls\nmetricsTLSConfig:\n dynamic:\n secretNamespace: \"cert-manager\"\n secretName: \"cert-manager-metrics-ca\"\n dnsNames:\n - cert-manager-metrics", + "type": "object" + }, + "helm-values.webhook.containerSecurityContext": { + "default": { + "allowPrivilegeEscalation": false, + "capabilities": { + "drop": [ + "ALL" + ] + }, + "readOnlyRootFilesystem": true + }, + "description": "Container Security Context to be set on the webhook component container. For more information, see [Configure a Security Context for a Pod or Container](https://kubernetes.io/docs/tasks/configure-pod-container/security-context/).", + "type": "object" + }, + "helm-values.webhook.deploymentAnnotations": { + "description": "Optional additional annotations to add to the webhook Deployment.", + "type": "object" + }, + "helm-values.webhook.enableServiceLinks": { + "default": false, + "description": "enableServiceLinks indicates whether information about services should be injected into the pod's environment variables, matching the syntax of Docker links.", + "type": "boolean" + }, + "helm-values.webhook.extraArgs": { + "default": [], + "description": "Additional command line flags to pass to cert-manager webhook binary. To see all available flags run `docker run quay.io/jetstack/cert-manager-webhook: --help`.", + "items": {}, + "type": "array" + }, + "helm-values.webhook.extraEnv": { + "default": [], + "description": "Additional environment variables to pass to cert-manager webhook binary.\nFor example:\nextraEnv:\n- name: SOME_VAR\n value: 'some value'", + "items": {}, + "type": "array" + }, + "helm-values.webhook.featureGates": { + "default": "", + "description": "Comma separated list of feature gates that should be enabled on the webhook pod.", + "type": "string" + }, + "helm-values.webhook.hostNetwork": { + "default": false, + "description": "Specifies if the webhook should be started in hostNetwork mode.\n\nRequired for use in some managed kubernetes clusters (such as AWS EKS) with custom. CNI (such as calico), because control-plane managed by AWS cannot communicate with pods' IP CIDR and admission webhooks are not working\n\nSince the default port for the webhook conflicts with kubelet on the host network, `webhook.securePort` should be changed to an available port if running in hostNetwork mode.", + "type": "boolean" + }, + "helm-values.webhook.image": { + "additionalProperties": false, + "properties": { + "digest": { + "$ref": "#/$defs/helm-values.webhook.image.digest" + }, + "pullPolicy": { + "$ref": "#/$defs/helm-values.webhook.image.pullPolicy" + }, + "registry": { + "$ref": "#/$defs/helm-values.webhook.image.registry" + }, + "repository": { + "$ref": "#/$defs/helm-values.webhook.image.repository" + }, + "tag": { + "$ref": "#/$defs/helm-values.webhook.image.tag" + } + }, + "type": "object" + }, + "helm-values.webhook.image.digest": { + "description": "Setting a digest will override any tag", + "type": "string" + }, + "helm-values.webhook.image.pullPolicy": { + "default": "IfNotPresent", + "description": "Kubernetes imagePullPolicy on Deployment.", + "type": "string" + }, + "helm-values.webhook.image.registry": { + "description": "The container registry to pull the webhook image from.", + "type": "string" + }, + "helm-values.webhook.image.repository": { + "default": "quay.io/jetstack/cert-manager-webhook", + "description": "The container image for the cert-manager webhook", + "type": "string" + }, + "helm-values.webhook.image.tag": { + "description": "Override the image tag to deploy by setting this variable. If no value is set, the chart's appVersion will be used.", + "type": "string" + }, + "helm-values.webhook.livenessProbe": { + "default": { + "failureThreshold": 3, + "initialDelaySeconds": 60, + "periodSeconds": 10, + "successThreshold": 1, + "timeoutSeconds": 1 + }, + "description": "Liveness probe values.\nFor more information, see [Container probes](https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle/#container-probes).", + "type": "object" + }, + "helm-values.webhook.loadBalancerIP": { + "description": "Specify the load balancer IP for the created service.", + "type": "string" + }, + "helm-values.webhook.mutatingWebhookConfiguration": { + "additionalProperties": false, + "properties": { + "namespaceSelector": { + "$ref": "#/$defs/helm-values.webhook.mutatingWebhookConfiguration.namespaceSelector" + } + }, + "type": "object" + }, + "helm-values.webhook.mutatingWebhookConfiguration.namespaceSelector": { + "default": {}, + "description": "Configure spec.namespaceSelector for mutating webhooks.", + "type": "object" + }, + "helm-values.webhook.mutatingWebhookConfigurationAnnotations": { + "description": "Optional additional annotations to add to the webhook MutatingWebhookConfiguration.", + "type": "object" + }, + "helm-values.webhook.networkPolicy": { + "additionalProperties": false, + "properties": { + "egress": { + "$ref": "#/$defs/helm-values.webhook.networkPolicy.egress" + }, + "enabled": { + "$ref": "#/$defs/helm-values.webhook.networkPolicy.enabled" + }, + "ingress": { + "$ref": "#/$defs/helm-values.webhook.networkPolicy.ingress" + } + }, + "type": "object" + }, + "helm-values.webhook.networkPolicy.egress": { + "default": [ + { + "ports": [ + { + "port": 80, + "protocol": "TCP" + }, + { + "port": 443, + "protocol": "TCP" + }, + { + "port": 53, + "protocol": "TCP" + }, + { + "port": 53, + "protocol": "UDP" + }, + { + "port": 6443, + "protocol": "TCP" + } + ], + "to": [ + { + "ipBlock": { + "cidr": "0.0.0.0/0" + } + }, + { + "ipBlock": { + "cidr": "::/0" + } + } + ] + } + ], + "description": "Egress rule for the webhook network policy. By default, it allows all outbound traffic to ports 80 and 443, as well as DNS ports.", + "items": {}, + "type": "array" + }, + "helm-values.webhook.networkPolicy.enabled": { + "default": false, + "description": "Create network policies for the webhooks.", + "type": "boolean" + }, + "helm-values.webhook.networkPolicy.ingress": { + "default": [ + { + "from": [ + { + "ipBlock": { + "cidr": "0.0.0.0/0" + } + }, + { + "ipBlock": { + "cidr": "::/0" + } + } + ] + } + ], + "description": "Ingress rule for the webhook network policy. By default, it allows all inbound traffic.", + "items": {}, + "type": "array" + }, + "helm-values.webhook.nodeSelector": { + "default": { + "kubernetes.io/os": "linux" + }, + "description": "The nodeSelector on Pods tells Kubernetes to schedule Pods on the nodes with matching labels. For more information, see [Assigning Pods to Nodes](https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/).\n\nThis default ensures that Pods are only scheduled to Linux nodes. It prevents Pods being scheduled to Windows nodes in a mixed OS cluster.", + "type": "object" + }, + "helm-values.webhook.podAnnotations": { + "description": "Optional additional annotations to add to the webhook Pods.", + "type": "object" + }, + "helm-values.webhook.podDisruptionBudget": { + "additionalProperties": false, + "properties": { + "enabled": { + "$ref": "#/$defs/helm-values.webhook.podDisruptionBudget.enabled" + }, + "maxUnavailable": { + "$ref": "#/$defs/helm-values.webhook.podDisruptionBudget.maxUnavailable" + }, + "minAvailable": { + "$ref": "#/$defs/helm-values.webhook.podDisruptionBudget.minAvailable" + } + }, + "type": "object" + }, + "helm-values.webhook.podDisruptionBudget.enabled": { + "default": false, + "description": "Enable or disable the PodDisruptionBudget resource.\n\nThis prevents downtime during voluntary disruptions such as during a Node upgrade. For example, the PodDisruptionBudget will block `kubectl drain` if it is used on the Node where the only remaining cert-manager\nPod is currently running.", + "type": "boolean" + }, + "helm-values.webhook.podDisruptionBudget.maxUnavailable": { + "description": "This property configures the maximum unavailable pods for disruptions. Can either be set to an integer (e.g., 1) or a percentage value (e.g., 25%).\nIt cannot be used if `minAvailable` is set." + }, + "helm-values.webhook.podDisruptionBudget.minAvailable": { + "description": "This property configures the minimum available pods for disruptions. Can either be set to an integer (e.g., 1) or a percentage value (e.g., 25%).\nIt cannot be used if `maxUnavailable` is set." + }, + "helm-values.webhook.podLabels": { + "default": {}, + "description": "Optional additional labels to add to the Webhook Pods.", + "type": "object" + }, + "helm-values.webhook.readinessProbe": { + "default": { + "failureThreshold": 3, + "initialDelaySeconds": 5, + "periodSeconds": 5, + "successThreshold": 1, + "timeoutSeconds": 1 + }, + "description": "Readiness probe values.\nFor more information, see [Container probes](https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle/#container-probes).", + "type": "object" + }, + "helm-values.webhook.replicaCount": { + "default": 1, + "description": "Number of replicas of the cert-manager webhook to run.\n\nThe default is 1, but in production set this to 2 or 3 to provide high availability.\n\nIf `replicas > 1`, consider setting `webhook.podDisruptionBudget.enabled=true`.", + "type": "number" + }, + "helm-values.webhook.resources": { + "default": {}, + "description": "Resources to provide to the cert-manager webhook pod.\n\nFor example:\nrequests:\n cpu: 10m\n memory: 32Mi\nFor more information, see [Resource Management for Pods and Containers](https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/).", + "type": "object" + }, + "helm-values.webhook.securePort": { + "default": 10250, + "description": "The port that the webhook listens on for requests. In GKE private clusters, by default Kubernetes apiservers are allowed to talk to the cluster nodes only on 443 and 10250. Configuring securePort: 10250, therefore will work out-of-the-box without needing to add firewall rules or requiring NET_BIND_SERVICE capabilities to bind port numbers <1000.", + "type": "number" + }, + "helm-values.webhook.securityContext": { + "default": { + "runAsNonRoot": true, + "seccompProfile": { + "type": "RuntimeDefault" + } + }, + "description": "Pod Security Context to be set on the webhook component Pod. For more information, see [Configure a Security Context for a Pod or Container](https://kubernetes.io/docs/tasks/configure-pod-container/security-context/).", + "type": "object" + }, + "helm-values.webhook.serviceAccount": { + "additionalProperties": false, + "properties": { + "annotations": { + "$ref": "#/$defs/helm-values.webhook.serviceAccount.annotations" + }, + "automountServiceAccountToken": { + "$ref": "#/$defs/helm-values.webhook.serviceAccount.automountServiceAccountToken" + }, + "create": { + "$ref": "#/$defs/helm-values.webhook.serviceAccount.create" + }, + "labels": { + "$ref": "#/$defs/helm-values.webhook.serviceAccount.labels" + }, + "name": { + "$ref": "#/$defs/helm-values.webhook.serviceAccount.name" + } + }, + "type": "object" + }, + "helm-values.webhook.serviceAccount.annotations": { + "description": "Optional additional annotations to add to the webhook's Service Account.", + "type": "object" + }, + "helm-values.webhook.serviceAccount.automountServiceAccountToken": { + "default": true, + "description": "Automount API credentials for a Service Account.", + "type": "boolean" + }, + "helm-values.webhook.serviceAccount.create": { + "default": true, + "description": "Specifies whether a service account should be created.", + "type": "boolean" + }, + "helm-values.webhook.serviceAccount.labels": { + "description": "Optional additional labels to add to the webhook's Service Account.", + "type": "object" + }, + "helm-values.webhook.serviceAccount.name": { + "description": "The name of the service account to use.\nIf not set and create is true, a name is generated using the fullname template.", + "type": "string" + }, + "helm-values.webhook.serviceAnnotations": { + "description": "Optional additional annotations to add to the webhook Service.", + "type": "object" + }, + "helm-values.webhook.serviceIPFamilies": { + "default": [], + "description": "Optionally set the IP families for the controller Service that should be supported, in the order in which they should be applied to ClusterIP. Can be IPv4 and/or IPv6.", + "items": {}, + "type": "array" + }, + "helm-values.webhook.serviceIPFamilyPolicy": { + "default": "", + "description": "Optionally set the IP family policy for the controller Service to configure dual-stack; see [Configure dual-stack](https://kubernetes.io/docs/concepts/services-networking/dual-stack/#services).", + "type": "string" + }, + "helm-values.webhook.serviceLabels": { + "default": {}, + "description": "Optional additional labels to add to the Webhook Service.", + "type": "object" + }, + "helm-values.webhook.serviceType": { + "default": "ClusterIP", + "description": "Specifies how the service should be handled. Useful if you want to expose the webhook outside of the cluster. In some cases, the control plane cannot reach internal services.", + "type": "string" + }, + "helm-values.webhook.strategy": { + "default": {}, + "description": "The update strategy for the cert-manager webhook deployment. For more information, see the [Kubernetes documentation](https://kubernetes.io/docs/concepts/workloads/controllers/deployment/#strategy)\n\nFor example:\nstrategy:\n type: RollingUpdate\n rollingUpdate:\n maxSurge: 0\n maxUnavailable: 1", + "type": "object" + }, + "helm-values.webhook.timeoutSeconds": { + "default": 30, + "description": "The number of seconds the API server should wait for the webhook to respond before treating the call as a failure. The value must be between 1 and 30 seconds. For more information, see\n[Validating webhook configuration v1](https://kubernetes.io/docs/reference/kubernetes-api/extend-resources/validating-webhook-configuration-v1/).\n\nThe default is set to the maximum value of 30 seconds as users sometimes report that the connection between the K8S API server and the cert-manager webhook server times out. If *this* timeout is reached, the error message will be \"context deadline exceeded\", which doesn't help the user diagnose what phase of the HTTPS connection timed out. For example, it could be during DNS resolution, TCP connection, TLS negotiation, HTTP negotiation, or slow HTTP response from the webhook server. By setting this timeout to its maximum value the underlying timeout error message has more chance of being returned to the end user.", + "type": "number" + }, + "helm-values.webhook.tolerations": { + "default": [], + "description": "A list of Kubernetes Tolerations, if required. For more information, see [Toleration v1 core](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.27/#toleration-v1-core).\n\nFor example:\ntolerations:\n- key: foo.bar.com/role\n operator: Equal\n value: master\n effect: NoSchedule", + "items": {}, + "type": "array" + }, + "helm-values.webhook.topologySpreadConstraints": { + "default": [], + "description": "A list of Kubernetes TopologySpreadConstraints, if required. For more information, see [Topology spread constraint v1 core](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.27/#topologyspreadconstraint-v1-core).\n\nFor example:\ntopologySpreadConstraints:\n- maxSkew: 2\n topologyKey: topology.kubernetes.io/zone\n whenUnsatisfiable: ScheduleAnyway\n labelSelector:\n matchLabels:\n app.kubernetes.io/instance: cert-manager\n app.kubernetes.io/component: controller", + "items": {}, + "type": "array" + }, + "helm-values.webhook.url": { + "default": {}, + "description": "Overrides the mutating webhook and validating webhook so they reach the webhook service using the `url` field instead of a service.", + "type": "object" + }, + "helm-values.webhook.validatingWebhookConfiguration": { + "additionalProperties": false, + "properties": { + "namespaceSelector": { + "$ref": "#/$defs/helm-values.webhook.validatingWebhookConfiguration.namespaceSelector" + } + }, + "type": "object" + }, + "helm-values.webhook.validatingWebhookConfiguration.namespaceSelector": { + "default": { + "matchExpressions": [ + { + "key": "cert-manager.io/disable-validation", + "operator": "NotIn", + "values": [ + "true" + ] + } + ] + }, + "description": "Configure spec.namespaceSelector for validating webhooks.", + "type": "object" + }, + "helm-values.webhook.validatingWebhookConfigurationAnnotations": { + "description": "Optional additional annotations to add to the webhook ValidatingWebhookConfiguration.", + "type": "object" + }, + "helm-values.webhook.volumeMounts": { + "default": [], + "description": "Additional volume mounts to add to the cert-manager controller container.", + "items": {}, + "type": "array" + }, + "helm-values.webhook.volumes": { + "default": [], + "description": "Additional volumes to add to the cert-manager controller pod.", + "items": {}, + "type": "array" + } + }, + "$ref": "#/$defs/helm-values", + "$schema": "http://json-schema.org/draft-07/schema#" +} diff --git a/deploy/charts/cert-manager/values.yaml b/deploy/charts/cert-manager/values.yaml index 295fea4eeb2..dbb3fd388e2 100644 --- a/deploy/charts/cert-manager/values.yaml +++ b/deploy/charts/cert-manager/values.yaml @@ -1,81 +1,184 @@ +# +docs:section=Global + # Default values for cert-manager. # This is a YAML-formatted file. # Declare variables to be passed into your templates. global: - # Reference to one or more secrets to be used when pulling images - # ref: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/ + # Reference to one or more secrets to be used when pulling images. + # For more information, see [Pull an Image from a Private Registry](https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/). + # + # For example: + # imagePullSecrets: + # - name: "image-pull-secret" imagePullSecrets: [] - # - name: "image-pull-secret" - # Labels to apply to all resources + # Global node selector + # + # The nodeSelector on Pods tells Kubernetes to schedule Pods on the nodes with + # matching labels. + # For more information, see [Assigning Pods to Nodes](https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/). + # + # If a component-specific nodeSelector is also set, it will take precedence. + # +docs:property + nodeSelector: {} + + # Labels to apply to all resources. # Please note that this does not add labels to the resources created dynamically by the controllers. # For these resources, you have to add the labels in the template in the cert-manager custom resource: - # eg. podTemplate/ ingressTemplate in ACMEChallengeSolverHTTP01Ingress - # ref: https://cert-manager.io/docs/reference/api-docs/#acme.cert-manager.io/v1.ACMEChallengeSolverHTTP01Ingress - # eg. secretTemplate in CertificateSpec - # ref: https://cert-manager.io/docs/reference/api-docs/#cert-manager.io/v1.CertificateSpec + # For example, podTemplate/ ingressTemplate in ACMEChallengeSolverHTTP01Ingress + # For more information, see the [cert-manager documentation](https://cert-manager.io/docs/reference/api-docs/#acme.cert-manager.io/v1.ACMEChallengeSolverHTTP01Ingress). + # For example, secretTemplate in CertificateSpec + # For more information, see the [cert-manager documentation](https://cert-manager.io/docs/reference/api-docs/#cert-manager.io/v1.CertificateSpec). commonLabels: {} - # team_name: dev - # Optional priority class to be used for the cert-manager pods + # The number of old ReplicaSets to retain to allow rollback (if not set, the default Kubernetes value is set to 10). + # +docs:property + # revisionHistoryLimit: 1 + + # The optional priority class to be used for the cert-manager pods. priorityClassName: "" + + # Set all pods to run in a user namespace without host access. + # Experimental: may be removed once the Kubernetes User Namespaces feature is GA. + # + # Requirements: + # - Kubernetes ≥ 1.33, or + # - Kubernetes 1.27–1.32 with UserNamespacesSupport feature gate enabled. + # + # Set to false to run pods in a user namespace without host access. + # + # See [limitations](https://kubernetes.io/docs/concepts/workloads/pods/user-namespaces/#limitations) for details. + # +docs:property + # hostUsers: false + rbac: + # Create required ClusterRoles and ClusterRoleBindings for cert-manager. create: true - # Aggregate ClusterRoles to Kubernetes default user-facing roles. Ref: https://kubernetes.io/docs/reference/access-authn-authz/rbac/#user-facing-roles + # Aggregate ClusterRoles to Kubernetes default user-facing roles. For more information, see [User-facing roles](https://kubernetes.io/docs/reference/access-authn-authz/rbac/#user-facing-roles) aggregateClusterRoles: true podSecurityPolicy: + # Create PodSecurityPolicy for cert-manager. + # + # Note that PodSecurityPolicy was deprecated in Kubernetes 1.21 and removed in Kubernetes 1.25. enabled: false + # Configure the PodSecurityPolicy to use AppArmor. useAppArmor: true - # Set the verbosity of cert-manager. Range of 0 - 6 with 6 being the most verbose. + # Set the verbosity of cert-manager. A range of 0 - 6, with 6 being the most verbose. logLevel: 2 leaderElection: - # Override the namespace used for the leader election lease + # Override the namespace used for the leader election lease. namespace: "kube-system" # 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. + # +docs:property # leaseDuration: 60s # 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. + # +docs:property # renewDeadline: 40s # The duration the clients should wait between attempting acquisition and # renewal of a leadership. + # +docs:property # retryPeriod: 15s +# This option is equivalent to setting crds.enabled=true and crds.keep=true. +# Deprecated: use crds.enabled and crds.keep instead. installCRDs: false +crds: + # This option decides if the CRDs should be installed + # as part of the Helm installation. + enabled: false + + # This option makes it so that the "helm.sh/resource-policy": keep + # annotation is added to the CRD. This will prevent Helm from uninstalling + # the CRD when the Helm release is uninstalled. + # WARNING: when the CRDs are removed, all cert-manager custom resources + # (Certificates, Issuers, ...) will be removed too by the garbage collector. + keep: true + +# +docs:section=Controller + +# The number of replicas of the cert-manager controller to run. +# +# The default is 1, but in production set this to 2 or 3 to provide high +# availability. +# +# If `replicas > 1`, consider setting `podDisruptionBudget.enabled=true`. +# +# Note that cert-manager uses leader election to ensure that there can +# only be a single instance active at a time. replicaCount: 1 +# Deployment update strategy for the cert-manager controller deployment. +# For more information, see the [Kubernetes documentation](https://kubernetes.io/docs/concepts/workloads/controllers/deployment/#strategy). +# +# For example: +# strategy: +# type: RollingUpdate +# rollingUpdate: +# maxSurge: 0 +# maxUnavailable: 1 strategy: {} - # type: RollingUpdate - # rollingUpdate: - # maxSurge: 0 - # maxUnavailable: 1 -# Comma separated list of feature gates that should be enabled on the +podDisruptionBudget: + # Enable or disable the PodDisruptionBudget resource. + # + # This prevents downtime during voluntary disruptions such as during a Node upgrade. + # For example, the PodDisruptionBudget will block `kubectl drain` + # if it is used on the Node where the only remaining cert-manager + # Pod is currently running. + enabled: false + + # This configures the minimum available pods for disruptions. It can either be set to + # an integer (e.g., 1) or a percentage value (e.g., 25%). + # It cannot be used if `maxUnavailable` is set. + # +docs:property + # +docs:type=unknown + # minAvailable: 1 + + # This configures the maximum unavailable pods for disruptions. It can either be set to + # an integer (e.g., 1) or a percentage value (e.g., 25%). + # it cannot be used if `minAvailable` is set. + # +docs:property + # +docs:type=unknown + # maxUnavailable: 1 + +# A comma-separated list of feature gates that should be enabled on the # controller pod. featureGates: "" +# The maximum number of challenges that can be scheduled as 'processing' at once. +maxConcurrentChallenges: 60 + image: - repository: quay.io/jetstack/cert-manager-controller - # You can manage a registry with + # The container registry to pull the manager image from. + # +docs:property # registry: quay.io - # repository: jetstack/cert-manager-controller + + # The container image for the cert-manager controller. + # +docs:property + repository: quay.io/jetstack/cert-manager-controller # Override the image tag to deploy by setting this variable. - # If no value is set, the chart's appVersion will be used. - # tag: canary + # If no value is set, the chart's appVersion is used. + # +docs:property + # tag: vX.Y.Z - # Setting a digest will override any tag + # Setting a digest will override any tag. + # +docs:property # digest: sha256:0e072dddd1f7f8fc8909a2ca6f65e76c5f0d2fcfb8be47935ae3457e8bbceb20 + + # Kubernetes imagePullPolicy on Deployment. pullPolicy: IfNotPresent # Override the namespace used to store DNS provider credentials etc. for ClusterIssuer @@ -83,119 +186,282 @@ image: # used. This namespace will not be automatically created by the Helm chart. clusterResourceNamespace: "" -# This namespace allows you to define where the services will be installed into -# if not set then they will use the namespace of the release -# This is helpful when installing cert manager as a chart dependency (sub chart) +# This namespace allows you to define where the services are installed into. +# If not set then they use the namespace of the release. +# This is helpful when installing cert manager as a chart dependency (sub chart). namespace: "" +# Override the "cert-manager.fullname" value. This value is used as part of +# most of the names of the resources created by this Helm chart. +# +docs:property +# fullnameOverride: "my-cert-manager" + +# Override the "cert-manager.name" value, which is used to annotate some of +# the resources that are created by this Chart (using "app.kubernetes.io/name"). +# NOTE: There are some inconsistencies in the Helm chart when it comes to +# these annotations (some resources use, e.g., "cainjector.name" which resolves +# to the value "cainjector"). +# +docs:property +# nameOverride: "my-cert-manager" + serviceAccount: - # Specifies whether a service account should be created + # Specifies whether a service account should be created. create: true + # The name of the service account to use. - # If not set and create is true, a name is generated using the fullname template + # If not set and create is true, a name is generated using the fullname template. + # +docs:property # name: "" - # Optional additional annotations to add to the controller's ServiceAccount + + # Optional additional annotations to add to the controller's Service Account. Templates are allowed for both keys and values. + # Example using templating: + # annotations: + # "{{ .Chart.Name }}-helm-chart/version": "{{ .Chart.Version }}" + # +docs:property # annotations: {} - # Automount API credentials for a Service Account. - # Optional additional labels to add to the controller's ServiceAccount + + # Optional additional labels to add to the controller's Service Account. + # +docs:property # labels: {} + + # Automount API credentials for a Service Account. automountServiceAccountToken: true -# Automounting API credentials for a particular pod +# Automounting API credentials for a particular pod. +# +docs:property # automountServiceAccountToken: true +# When this flag is enabled, secrets will be automatically removed when the certificate resource is deleted. +enableCertificateOwnerRef: false + +# This property is used to configure options for the controller pod. +# This allows setting options that would usually be provided using flags. +# +# If `apiVersion` and `kind` are unspecified they default to the current latest +# version (currently `controller.config.cert-manager.io/v1alpha1`). You can pin +# the version by specifying the `apiVersion` yourself. +# +# For example: +# config: +# apiVersion: controller.config.cert-manager.io/v1alpha1 +# kind: ControllerConfiguration +# logging: +# verbosity: 2 +# format: text +# leaderElectionConfig: +# namespace: kube-system +# kubernetesAPIQPS: 9000 +# kubernetesAPIBurst: 9000 +# numberOfConcurrentWorkers: 200 +# enableGatewayAPI: true +# # Feature gates as of v1.18.1. Listed with their default values. +# # See https://cert-manager.io/docs/cli/controller/ +# featureGates: +# AdditionalCertificateOutputFormats: true # GA - default=true +# AllAlpha: false # ALPHA - default=false +# AllBeta: false # BETA - default=false +# ExperimentalCertificateSigningRequestControllers: false # ALPHA - default=false +# ExperimentalGatewayAPISupport: true # BETA - default=true +# LiteralCertificateSubject: true # BETA - default=true +# NameConstraints: true # BETA - default=true +# OtherNames: false # ALPHA - default=false +# SecretsFilteredCaching: true # BETA - default=true +# ServerSideApply: false # ALPHA - default=false +# StableCertificateRequestName: true # BETA - default=true +# UseCertificateRequestBasicConstraints: false # ALPHA - default=false +# UseDomainQualifiedFinalizer: true # GA - default=true +# ValidateCAA: false # ALPHA - default=false +# DefaultPrivateKeyRotationPolicyAlways: true # BETA - default=true +# ACMEHTTP01IngressPathTypeExact: true # BETA - default=true +# # Configure the metrics server for TLS +# # See https://cert-manager.io/docs/devops-tips/prometheus-metrics/#tls +# metricsTLSConfig: +# dynamic: +# secretNamespace: "cert-manager" +# secretName: "cert-manager-metrics-ca" +# dnsNames: +# - cert-manager-metrics +config: {} + +# Setting Nameservers for DNS01 Self Check. +# For more information, see the [cert-manager documentation](https://cert-manager.io/docs/configuration/acme/dns01/#setting-nameservers-for-dns01-self-check). + +# A comma-separated string with the host and port of the recursive nameservers cert-manager should query. +dns01RecursiveNameservers: "" + +# Forces cert-manager to use only the recursive nameservers for verification. +# Enabling this option could cause the DNS01 self check to take longer owing to caching performed by the recursive nameservers. +dns01RecursiveNameserversOnly: false + +# Option to disable cert-manager's build-in auto-approver. The auto-approver +# approves all CertificateRequests that reference issuers matching the 'approveSignerNames' +# option. This 'disableAutoApproval' option is useful when you want to make all approval decisions +# using a different approver (like approver-policy - https://github.com/cert-manager/approver-policy). +disableAutoApproval: false + +# List of signer names that cert-manager will approve by default. CertificateRequests +# referencing these signer names will be auto-approved by cert-manager. Defaults to just +# approving the cert-manager.io Issuer and ClusterIssuer issuers. When set to an empty +# array, ALL issuers will be auto-approved by cert-manager. To disable the auto-approval, +# because, e.g., you are using approver-policy, you can enable 'disableAutoApproval'. +# ref: https://cert-manager.io/docs/concepts/certificaterequest/#approval +# +docs:property +approveSignerNames: +- issuers.cert-manager.io/* +- clusterissuers.cert-manager.io/* + # Additional command line flags to pass to cert-manager controller binary. -# To see all available flags run docker run quay.io/jetstack/cert-manager-controller: --help +# To see all available flags run `docker run quay.io/jetstack/cert-manager-controller: --help`. +# +# Use this flag to enable or disable arbitrary controllers. For example, to disable the CertificateRequests approver. +# +# For example: +# extraArgs: +# - --controllers=*,-certificaterequests-approver extraArgs: [] - # When this flag is enabled, secrets will be automatically removed when the certificate resource is deleted - # - --enable-certificate-owner-ref=true - # Use this flag to enabled or disable arbitrary controllers, for example, disable the CertificiateRequests approver - # - --controllers=*,-certificaterequests-approver +# Additional environment variables to pass to cert-manager controller binary. +# For example: +# extraEnv: +# - name: SOME_VAR +# value: 'some value' extraEnv: [] -# - name: SOME_VAR -# value: 'some value' +# Resources to provide to the cert-manager controller pod. +# +# For example: +# requests: +# cpu: 10m +# memory: 32Mi +# +# For more information, see [Resource Management for Pods and Containers](https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/). resources: {} - # requests: - # cpu: 10m - # memory: 32Mi -# Pod Security Context -# ref: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/ +# Pod Security Context. +# For more information, see [Configure a Security Context for a Pod or Container](https://kubernetes.io/docs/tasks/configure-pod-container/security-context/). +# +docs:property securityContext: runAsNonRoot: true seccompProfile: type: RuntimeDefault -# Container Security Context to be set on the controller component container -# ref: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/ +# Container Security Context to be set on the controller component container. +# For more information, see [Configure a Security Context for a Pod or Container](https://kubernetes.io/docs/tasks/configure-pod-container/security-context/). +# +docs:property containerSecurityContext: allowPrivilegeEscalation: false capabilities: drop: - ALL - # readOnlyRootFilesystem: true - # runAsNonRoot: true - + readOnlyRootFilesystem: true +# Additional volumes to add to the cert-manager controller pod. volumes: [] +# Additional volume mounts to add to the cert-manager controller container. volumeMounts: [] -# Optional additional annotations to add to the controller Deployment +# Optional additional annotations to add to the controller Deployment. +# +docs:property # deploymentAnnotations: {} -# Optional additional annotations to add to the controller Pods +# Optional additional annotations to add to the controller Pods. +# +docs:property # podAnnotations: {} +# Optional additional labels to add to the controller Pods. podLabels: {} -# Optional annotations to add to the controller Service +# Optional annotations to add to the controller Service. +# +docs:property # serviceAnnotations: {} -# Optional additional labels to add to the controller Service +# Optional additional labels to add to the controller Service. +# +docs:property # serviceLabels: {} -# Optional DNS settings, useful if you have a public and private DNS zone for -# the same domain on Route 53. What follows is an example of ensuring +# Optionally set the IP family policy for the controller Service to configure dual-stack; see [Configure dual-stack](https://kubernetes.io/docs/concepts/services-networking/dual-stack/#services). +# +docs:property +# serviceIPFamilyPolicy: "" + +# Optionally set the IP families for the controller Service that should be supported, in the order in which they should be applied to ClusterIP. Can be IPv4 and/or IPv6. +# +docs:property +# serviceIPFamilies: [] + +# Optional DNS settings. These are useful if you have a public and private DNS zone for +# the same domain on Route 53. The following is an example of ensuring # cert-manager can access an ingress or DNS TXT records at all times. -# NOTE: This requires Kubernetes 1.10 or `CustomPodDNS` feature gate enabled for +# Note that this requires Kubernetes 1.10 or `CustomPodDNS` feature gate enabled for # the cluster to work. + +# Pod DNS policy. +# For more information, see [Pod's DNS Policy](https://kubernetes.io/docs/concepts/services-networking/dns-pod-service/#pod-s-dns-policy). +# +docs:property # podDnsPolicy: "None" + +# Pod DNS configuration. The podDnsConfig field is optional and can work with any podDnsPolicy +# settings. However, when a Pod's dnsPolicy is set to "None", the dnsConfig field has to be specified. +# For more information, see [Pod's DNS Config](https://kubernetes.io/docs/concepts/services-networking/dns-pod-service/#pod-dns-config). +# +docs:property # podDnsConfig: # nameservers: # - "1.1.1.1" # - "8.8.8.8" +# Optional hostAliases for cert-manager-controller pods. May be useful when performing ACME DNS-01 self checks. +hostAliases: [] +# - ip: 127.0.0.1 +# hostnames: +# - foo.local +# - bar.local +# - ip: 10.1.2.3 +# hostnames: +# - foo.remote +# - bar.remote + +# The nodeSelector on Pods tells Kubernetes to schedule Pods on the nodes with +# matching labels. +# For more information, see [Assigning Pods to Nodes](https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/). +# +# This default ensures that Pods are only scheduled to Linux nodes. +# It prevents Pods being scheduled to Windows nodes in a mixed OS cluster. +# +docs:property nodeSelector: kubernetes.io/os: linux +# +docs:ignore ingressShim: {} + + # Optional default issuer to use for ingress resources. + # +docs:property=ingressShim.defaultIssuerName # defaultIssuerName: "" + + # Optional default issuer kind to use for ingress resources. + # +docs:property=ingressShim.defaultIssuerKind # defaultIssuerKind: "" + + # Optional default issuer group to use for ingress resources. + # +docs:property=ingressShim.defaultIssuerGroup # defaultIssuerGroup: "" -prometheus: - enabled: true - servicemonitor: - enabled: false - prometheusInstance: default - targetPort: 9402 - path: /metrics - interval: 60s - scrapeTimeout: 30s - labels: {} - annotations: {} - honorLabels: false +# Use these variables to configure the HTTP_PROXY environment variables. -# Use these variables to configure the HTTP_PROXY environment variables +# Configures the HTTP_PROXY environment variable where a HTTP proxy is required. +# +docs:property # http_proxy: "http://proxy:8080" + +# Configures the HTTPS_PROXY environment variable where a HTTP proxy is required. +# +docs:property # https_proxy: "https://proxy:8080" + +# Configures the NO_PROXY environment variable where a HTTP proxy is required, +# but certain domains should be excluded. +# +docs:property # no_proxy: 127.0.0.1,localhost -# expects input structure as per specification https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.11/#affinity-v1-core -# for example: +# A Kubernetes Affinity, if required. For more information, see [Affinity v1 core](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.27/#affinity-v1-core). +# +# For example: # affinity: # nodeAffinity: # requiredDuringSchedulingIgnoredDuringExecution: @@ -207,8 +473,9 @@ prometheus: # - master affinity: {} -# expects input structure as per specification https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.11/#toleration-v1-core -# for example: +# A list of Kubernetes Tolerations, if required. For more information, see [Toleration v1 core](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.27/#toleration-v1-core). +# +# For example: # tolerations: # - key: foo.bar.com/role # operator: Equal @@ -216,8 +483,9 @@ affinity: {} # effect: NoSchedule tolerations: [] -# expects input structure as per specification https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.24/#topologyspreadconstraint-v1-core -# for example: +# A list of Kubernetes TopologySpreadConstraints, if required. For more information, see [Topology spread constraint v1 core](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.27/#topologyspreadconstraint-v1-core +# +# For example: # topologySpreadConstraints: # - maxSkew: 2 # topologyKey: topology.kubernetes.io/zone @@ -228,85 +496,339 @@ tolerations: [] # app.kubernetes.io/component: controller topologySpreadConstraints: [] +# LivenessProbe settings for the controller container of the controller Pod. +# +# This is enabled by default, in order to enable the clock-skew liveness probe that +# restarts the controller in case of a skew between the system clock and the monotonic clock. +# LivenessProbe durations and thresholds are based on those used for the Kubernetes +# controller-manager. For more information see the following on the +# [Kubernetes GitHub repository](https://github.com/kubernetes/kubernetes/blob/806b30170c61a38fedd54cc9ede4cd6275a1ad3b/cmd/kubeadm/app/util/staticpod/utils.go#L241-L245) +# +docs:property +livenessProbe: + enabled: true + initialDelaySeconds: 10 + periodSeconds: 10 + timeoutSeconds: 15 + successThreshold: 1 + failureThreshold: 8 + +# enableServiceLinks indicates whether information about services should be +# injected into the pod's environment variables, matching the syntax of Docker +# links. +enableServiceLinks: false + +# +docs:section=Prometheus + +prometheus: + # Enable Prometheus monitoring for the cert-manager controller and webhook. + # If you use the Prometheus Operator, set prometheus.podmonitor.enabled or + # prometheus.servicemonitor.enabled, to create a PodMonitor or a + # ServiceMonitor resource. + # Otherwise, 'prometheus.io' annotations are added to the cert-manager and + # cert-manager-webhook Deployments. + # Note that you cannot enable both PodMonitor and ServiceMonitor as they are + # mutually exclusive. Enabling both will result in an error. + enabled: true + + servicemonitor: + # Create a ServiceMonitor to add cert-manager to Prometheus. + enabled: false + + # The namespace that the service monitor should live in, defaults + # to the cert-manager namespace. + # +docs:property + # namespace: cert-manager + + # Specifies the `prometheus` label on the created ServiceMonitor. This is + # used when different Prometheus instances have label selectors matching + # different ServiceMonitors. + prometheusInstance: default + + # The target port to set on the ServiceMonitor. This must match the port that the + # cert-manager controller is listening on for metrics. + # +docs:type=string,integer + targetPort: http-metrics + + # The path to scrape for metrics. + path: /metrics + + # The interval to scrape metrics. + interval: 60s + + # The timeout before a metrics scrape fails. + scrapeTimeout: 30s + + # Additional labels to add to the ServiceMonitor. + labels: {} + + # Additional annotations to add to the ServiceMonitor. + annotations: {} + + # Keep labels from scraped data, overriding server-side labels. + honorLabels: false + + # EndpointAdditionalProperties allows setting additional properties on the + # endpoint such as relabelings, metricRelabelings etc. + # + # For example: + # endpointAdditionalProperties: + # relabelings: + # - action: replace + # sourceLabels: + # - __meta_kubernetes_pod_node_name + # targetLabel: instance + # + # +docs:property + endpointAdditionalProperties: {} + + # Note that you cannot enable both PodMonitor and ServiceMonitor as they are mutually exclusive. Enabling both will result in an error. + podmonitor: + # Create a PodMonitor to add cert-manager to Prometheus. + enabled: false + + # The namespace that the pod monitor should live in, defaults + # to the cert-manager namespace. + # +docs:property + # namespace: cert-manager + + # Specifies the `prometheus` label on the created PodMonitor. This is + # used when different Prometheus instances have label selectors matching + # different PodMonitors. + prometheusInstance: default + + # The path to scrape for metrics. + path: /metrics + + # The interval to scrape metrics. + interval: 60s + + # The timeout before a metrics scrape fails. + scrapeTimeout: 30s + + # Additional labels to add to the PodMonitor. + labels: {} + + # Additional annotations to add to the PodMonitor. + annotations: {} + + # Keep labels from scraped data, overriding server-side labels. + honorLabels: false + + # EndpointAdditionalProperties allows setting additional properties on the + # endpoint such as relabelings, metricRelabelings etc. + # + # For example: + # endpointAdditionalProperties: + # relabelings: + # - action: replace + # sourceLabels: + # - __meta_kubernetes_pod_node_name + # targetLabel: instance + # # Configure the PodMonitor for TLS connections + # # See https://cert-manager.io/docs/devops-tips/prometheus-metrics/#tls + # scheme: https + # tlsConfig: + # serverName: cert-manager-metrics + # ca: + # secret: + # name: cert-manager-metrics-ca + # key: "tls.crt" + # + # +docs:property + endpointAdditionalProperties: {} + +# +docs:section=Webhook + webhook: + # Number of replicas of the cert-manager webhook to run. + # + # The default is 1, but in production set this to 2 or 3 to provide high + # availability. + # + # If `replicas > 1`, consider setting `webhook.podDisruptionBudget.enabled=true`. replicaCount: 1 - timeoutSeconds: 10 - - # Used to configure options for the webhook pod. - # This allows setting options that'd usually be provided via flags. - # An APIVersion and Kind must be specified in your values.yaml file. - # Flags will override options that are set here. - config: - # apiVersion: webhook.config.cert-manager.io/v1alpha1 - # kind: WebhookConfiguration - - # The port that the webhook should listen on for requests. - # In GKE private clusters, by default kubernetes apiservers are allowed to - # talk to the cluster nodes only on 443 and 10250. so configuring - # securePort: 10250, will work out of the box without needing to add firewall - # rules or requiring NET_BIND_SERVICE capabilities to bind port numbers <1000. - # This should be uncommented and set as a default by the chart once we graduate - # the apiVersion of WebhookConfiguration past v1alpha1. - # securePort: 10250 + # The number of seconds the API server should wait for the webhook to respond before treating the call as a failure. + # The value must be between 1 and 30 seconds. For more information, see + # [Validating webhook configuration v1](https://kubernetes.io/docs/reference/kubernetes-api/extend-resources/validating-webhook-configuration-v1/). + # + # The default is set to the maximum value of 30 seconds as + # users sometimes report that the connection between the K8S API server and + # the cert-manager webhook server times out. + # If *this* timeout is reached, the error message will be "context deadline exceeded", + # which doesn't help the user diagnose what phase of the HTTPS connection timed out. + # For example, it could be during DNS resolution, TCP connection, TLS + # negotiation, HTTP negotiation, or slow HTTP response from the webhook + # server. + # By setting this timeout to its maximum value the underlying timeout error + # message has more chance of being returned to the end user. + timeoutSeconds: 30 + + # This is used to configure options for the webhook pod. + # This allows setting options that would usually be provided using flags. + # + # If `apiVersion` and `kind` are unspecified they default to the current latest + # version (currently `webhook.config.cert-manager.io/v1alpha1`). You can pin + # the version by specifying the `apiVersion` yourself. + # + # For example: + # apiVersion: webhook.config.cert-manager.io/v1alpha1 + # kind: WebhookConfiguration + # # The port that the webhook listens on for requests. + # # In GKE private clusters, by default Kubernetes apiservers are allowed to + # # talk to the cluster nodes only on 443 and 10250. Configuring + # # securePort: 10250 therefore will work out-of-the-box without needing to add firewall + # # rules or requiring NET_BIND_SERVICE capabilities to bind port numbers < 1000. + # # This should be uncommented and set as a default by the chart once + # # the apiVersion of WebhookConfiguration graduates beyond v1alpha1. + # securePort: 10250 + # # Configure the metrics server for TLS + # # See https://cert-manager.io/docs/devops-tips/prometheus-metrics/#tls + # metricsTLSConfig: + # dynamic: + # secretNamespace: "cert-manager" + # secretName: "cert-manager-metrics-ca" + # dnsNames: + # - cert-manager-metrics + config: {} + + # The update strategy for the cert-manager webhook deployment. + # For more information, see the [Kubernetes documentation](https://kubernetes.io/docs/concepts/workloads/controllers/deployment/#strategy) + # + # For example: + # strategy: + # type: RollingUpdate + # rollingUpdate: + # maxSurge: 0 + # maxUnavailable: 1 strategy: {} - # type: RollingUpdate - # rollingUpdate: - # maxSurge: 0 - # maxUnavailable: 1 - # Pod Security Context to be set on the webhook component Pod - # ref: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/ + # Pod Security Context to be set on the webhook component Pod. + # For more information, see [Configure a Security Context for a Pod or Container](https://kubernetes.io/docs/tasks/configure-pod-container/security-context/). + # +docs:property securityContext: runAsNonRoot: true seccompProfile: type: RuntimeDefault - # Container Security Context to be set on the webhook component container - # ref: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/ + # Container Security Context to be set on the webhook component container. + # For more information, see [Configure a Security Context for a Pod or Container](https://kubernetes.io/docs/tasks/configure-pod-container/security-context/). + # +docs:property containerSecurityContext: allowPrivilegeEscalation: false capabilities: drop: - ALL - # readOnlyRootFilesystem: true - # runAsNonRoot: true + readOnlyRootFilesystem: true + + podDisruptionBudget: + # Enable or disable the PodDisruptionBudget resource. + # + # This prevents downtime during voluntary disruptions such as during a Node upgrade. + # For example, the PodDisruptionBudget will block `kubectl drain` + # if it is used on the Node where the only remaining cert-manager + # Pod is currently running. + enabled: false - # Optional additional annotations to add to the webhook Deployment + # This property configures the minimum available pods for disruptions. Can either be set to + # an integer (e.g., 1) or a percentage value (e.g., 25%). + # It cannot be used if `maxUnavailable` is set. + # +docs:property + # +docs:type=unknown + # minAvailable: 1 + + # This property configures the maximum unavailable pods for disruptions. Can either be set to + # an integer (e.g., 1) or a percentage value (e.g., 25%). + # It cannot be used if `minAvailable` is set. + # +docs:property + # +docs:type=unknown + # maxUnavailable: 1 + + # Optional additional annotations to add to the webhook Deployment. + # +docs:property # deploymentAnnotations: {} - # Optional additional annotations to add to the webhook Pods + # Optional additional annotations to add to the webhook Pods. + # +docs:property # podAnnotations: {} - # Optional additional annotations to add to the webhook Service + # Optional additional annotations to add to the webhook Service. + # +docs:property # serviceAnnotations: {} - # Optional additional annotations to add to the webhook MutatingWebhookConfiguration + # Optional additional annotations to add to the webhook MutatingWebhookConfiguration. + # +docs:property # mutatingWebhookConfigurationAnnotations: {} - # Optional additional annotations to add to the webhook ValidatingWebhookConfiguration + # Optional additional annotations to add to the webhook ValidatingWebhookConfiguration. + # +docs:property # validatingWebhookConfigurationAnnotations: {} + validatingWebhookConfiguration: + # Configure spec.namespaceSelector for validating webhooks. + # +docs:property + namespaceSelector: + matchExpressions: + - key: "cert-manager.io/disable-validation" + operator: "NotIn" + values: + - "true" + + mutatingWebhookConfiguration: + # Configure spec.namespaceSelector for mutating webhooks. + # +docs:property + namespaceSelector: {} + # matchLabels: + # key: value + # matchExpressions: + # - key: kubernetes.io/metadata.name + # operator: NotIn + # values: + # - kube-system + + # Additional command line flags to pass to cert-manager webhook binary. - # To see all available flags run docker run quay.io/jetstack/cert-manager-webhook: --help + # To see all available flags run `docker run quay.io/jetstack/cert-manager-webhook: --help`. extraArgs: [] - # Path to a file containing a WebhookConfiguration object used to configure the webhook + # Path to a file containing a WebhookConfiguration object used to configure the webhook. # - --config= + # Additional environment variables to pass to cert-manager webhook binary. + # For example: + # extraEnv: + # - name: SOME_VAR + # value: 'some value' + extraEnv: [] + + # Comma separated list of feature gates that should be enabled on the + # webhook pod. + featureGates: "" + + # Resources to provide to the cert-manager webhook pod. + # + # For example: + # requests: + # cpu: 10m + # memory: 32Mi + # + # For more information, see [Resource Management for Pods and Containers](https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/). resources: {} - # requests: - # cpu: 10m - # memory: 32Mi - ## Liveness and readiness probe values - ## Ref: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle/#container-probes - ## + # Liveness probe values. + # For more information, see [Container probes](https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle/#container-probes). + # + # +docs:property livenessProbe: failureThreshold: 3 initialDelaySeconds: 60 periodSeconds: 10 successThreshold: 1 timeoutSeconds: 1 + + # Readiness probe values. + # For more information, see [Container probes](https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle/#container-probes). + # + # +docs:property readinessProbe: failureThreshold: 3 initialDelaySeconds: 5 @@ -314,57 +836,115 @@ webhook: successThreshold: 1 timeoutSeconds: 1 + # The nodeSelector on Pods tells Kubernetes to schedule Pods on the nodes with + # matching labels. + # For more information, see [Assigning Pods to Nodes](https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/). + # + # This default ensures that Pods are only scheduled to Linux nodes. + # It prevents Pods being scheduled to Windows nodes in a mixed OS cluster. + # +docs:property nodeSelector: kubernetes.io/os: linux + # A Kubernetes Affinity, if required. For more information, see [Affinity v1 core](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.27/#affinity-v1-core). + # + # For example: + # affinity: + # nodeAffinity: + # requiredDuringSchedulingIgnoredDuringExecution: + # nodeSelectorTerms: + # - matchExpressions: + # - key: foo.bar.com/role + # operator: In + # values: + # - master affinity: {} + # A list of Kubernetes Tolerations, if required. For more information, see [Toleration v1 core](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.27/#toleration-v1-core). + # + # For example: + # tolerations: + # - key: foo.bar.com/role + # operator: Equal + # value: master + # effect: NoSchedule tolerations: [] + # A list of Kubernetes TopologySpreadConstraints, if required. For more information, see [Topology spread constraint v1 core](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.27/#topologyspreadconstraint-v1-core). + # + # For example: + # topologySpreadConstraints: + # - maxSkew: 2 + # topologyKey: topology.kubernetes.io/zone + # whenUnsatisfiable: ScheduleAnyway + # labelSelector: + # matchLabels: + # app.kubernetes.io/instance: cert-manager + # app.kubernetes.io/component: controller topologySpreadConstraints: [] - # Optional additional labels to add to the Webhook Pods + # Optional additional labels to add to the Webhook Pods. podLabels: {} - # Optional additional labels to add to the Webhook Service + # Optional additional labels to add to the Webhook Service. serviceLabels: {} + # Optionally set the IP family policy for the controller Service to configure dual-stack; see [Configure dual-stack](https://kubernetes.io/docs/concepts/services-networking/dual-stack/#services). + serviceIPFamilyPolicy: "" + + # Optionally set the IP families for the controller Service that should be supported, in the order in which they should be applied to ClusterIP. Can be IPv4 and/or IPv6. + serviceIPFamilies: [] + image: - repository: quay.io/jetstack/cert-manager-webhook - # You can manage a registry with + # The container registry to pull the webhook image from. + # +docs:property # registry: quay.io - # repository: jetstack/cert-manager-webhook + + # The container image for the cert-manager webhook + # +docs:property + repository: quay.io/jetstack/cert-manager-webhook # Override the image tag to deploy by setting this variable. # If no value is set, the chart's appVersion will be used. - # tag: canary + # +docs:property + # tag: vX.Y.Z # Setting a digest will override any tag + # +docs:property # digest: sha256:0e072dddd1f7f8fc8909a2ca6f65e76c5f0d2fcfb8be47935ae3457e8bbceb20 + # Kubernetes imagePullPolicy on Deployment. pullPolicy: IfNotPresent serviceAccount: - # Specifies whether a service account should be created + # Specifies whether a service account should be created. create: true + # The name of the service account to use. - # If not set and create is true, a name is generated using the fullname template + # If not set and create is true, a name is generated using the fullname template. + # +docs:property # name: "" - # Optional additional annotations to add to the controller's ServiceAccount + + # Optional additional annotations to add to the webhook's Service Account. + # +docs:property # annotations: {} - # Optional additional labels to add to the webhook's ServiceAccount + + # Optional additional labels to add to the webhook's Service Account. + # +docs:property # labels: {} + # Automount API credentials for a Service Account. automountServiceAccountToken: true - # Automounting API credentials for a particular pod + # Automounting API credentials for a particular pod. + # +docs:property # automountServiceAccountToken: true - # The port that the webhook should listen on for requests. - # In GKE private clusters, by default kubernetes apiservers are allowed to - # talk to the cluster nodes only on 443 and 10250. so configuring - # securePort: 10250, will work out of the box without needing to add firewall - # rules or requiring NET_BIND_SERVICE capabilities to bind port numbers <1000 + # The port that the webhook listens on for requests. + # In GKE private clusters, by default Kubernetes apiservers are allowed to + # talk to the cluster nodes only on 443 and 10250. Configuring + # securePort: 10250, therefore will work out-of-the-box without needing to add firewall + # rules or requiring NET_BIND_SERVICE capabilities to bind port numbers <1000. securePort: 10250 # Specifies if the webhook should be started in hostNetwork mode. @@ -379,10 +959,13 @@ webhook: hostNetwork: false # Specifies how the service should be handled. Useful if you want to expose the - # webhook to outside of the cluster. In some cases, the control plane cannot + # webhook outside of the cluster. In some cases, the control plane cannot # reach internal services. serviceType: ClusterIP - # loadBalancerIP: + + # Specify the load balancer IP for the created service. + # +docs:property + # loadBalancerIP: "10.10.10.10" # Overrides the mutating webhook and validating webhook so they reach the webhook # service using the `url` field instead of a service. @@ -391,11 +974,22 @@ webhook: # Enables default network policies for webhooks. networkPolicy: + # Create network policies for the webhooks. enabled: false + + # Ingress rule for the webhook network policy. By default, it allows all + # inbound traffic. + # +docs:property ingress: - from: - ipBlock: cidr: 0.0.0.0/0 + - ipBlock: + cidr: "::/0" + + # Egress rule for the webhook network policy. By default, it allows all + # outbound traffic to ports 80 and 443, as well as DNS ports. + # +docs:property egress: - ports: - port: 80 @@ -406,197 +1000,495 @@ webhook: protocol: TCP - port: 53 protocol: UDP + # On OpenShift and OKD, the Kubernetes API server listens on. + # port 6443. + - port: 6443 + protocol: TCP to: - ipBlock: cidr: 0.0.0.0/0 + - ipBlock: + cidr: "::/0" + + # Additional volumes to add to the cert-manager controller pod. + volumes: [] + + # Additional volume mounts to add to the cert-manager controller container. + volumeMounts: [] + + # enableServiceLinks indicates whether information about services should be + # injected into the pod's environment variables, matching the syntax of Docker + # links. + enableServiceLinks: false + +# +docs:section=CA Injector cainjector: + # Create the CA Injector deployment enabled: true + + # The number of replicas of the cert-manager cainjector to run. + # + # The default is 1, but in production set this to 2 or 3 to provide high + # availability. + # + # If `replicas > 1`, consider setting `cainjector.podDisruptionBudget.enabled=true`. + # + # Note that cert-manager uses leader election to ensure that there can + # only be a single instance active at a time. replicaCount: 1 + # This is used to configure options for the cainjector pod. + # It allows setting options that are usually provided via flags. + # + # If `apiVersion` and `kind` are unspecified they default to the current latest + # version (currently `cainjector.config.cert-manager.io/v1alpha1`). You can pin + # the version by specifying the `apiVersion` yourself. + # + # For example: + # apiVersion: cainjector.config.cert-manager.io/v1alpha1 + # kind: CAInjectorConfiguration + # logging: + # verbosity: 2 + # format: text + # leaderElectionConfig: + # namespace: kube-system + # # Configure the metrics server for TLS + # # See https://cert-manager.io/docs/devops-tips/prometheus-metrics/#tls + # metricsTLSConfig: + # dynamic: + # secretNamespace: "cert-manager" + # secretName: "cert-manager-metrics-ca" + # dnsNames: + # - cert-manager-metrics + config: {} + + # Deployment update strategy for the cert-manager cainjector deployment. + # For more information, see the [Kubernetes documentation](https://kubernetes.io/docs/concepts/workloads/controllers/deployment/#strategy). + # + # For example: + # strategy: + # type: RollingUpdate + # rollingUpdate: + # maxSurge: 0 + # maxUnavailable: 1 strategy: {} - # type: RollingUpdate - # rollingUpdate: - # maxSurge: 0 - # maxUnavailable: 1 # Pod Security Context to be set on the cainjector component Pod - # ref: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/ + # For more information, see [Configure a Security Context for a Pod or Container](https://kubernetes.io/docs/tasks/configure-pod-container/security-context/). + # +docs:property securityContext: runAsNonRoot: true seccompProfile: type: RuntimeDefault # Container Security Context to be set on the cainjector component container - # ref: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/ + # For more information, see [Configure a Security Context for a Pod or Container](https://kubernetes.io/docs/tasks/configure-pod-container/security-context/). + # +docs:property containerSecurityContext: allowPrivilegeEscalation: false capabilities: drop: - ALL - # readOnlyRootFilesystem: true - # runAsNonRoot: true - + readOnlyRootFilesystem: true + + podDisruptionBudget: + # Enable or disable the PodDisruptionBudget resource. + # + # This prevents downtime during voluntary disruptions such as during a Node upgrade. + # For example, the PodDisruptionBudget will block `kubectl drain` + # if it is used on the Node where the only remaining cert-manager + # Pod is currently running. + enabled: false - # Optional additional annotations to add to the cainjector Deployment + # `minAvailable` configures the minimum available pods for disruptions. It can either be set to + # an integer (e.g., 1) or a percentage value (e.g., 25%). + # Cannot be used if `maxUnavailable` is set. + # +docs:property + # +docs:type=unknown + # minAvailable: 1 + + # `maxUnavailable` configures the maximum unavailable pods for disruptions. It can either be set to + # an integer (e.g., 1) or a percentage value (e.g., 25%). + # Cannot be used if `minAvailable` is set. + # +docs:property + # +docs:type=unknown + # maxUnavailable: 1 + + # Optional additional annotations to add to the cainjector Deployment. + # +docs:property # deploymentAnnotations: {} - # Optional additional annotations to add to the cainjector Pods + # Optional additional annotations to add to the cainjector Pods. + # +docs:property # podAnnotations: {} + # Optional additional annotations to add to the cainjector metrics Service. + # +docs:property + # serviceAnnotations: {} + # Additional command line flags to pass to cert-manager cainjector binary. - # To see all available flags run docker run quay.io/jetstack/cert-manager-cainjector: --help + # To see all available flags run `docker run quay.io/jetstack/cert-manager-cainjector: --help`. extraArgs: [] - # Enable profiling for cainjector + # Enable profiling for cainjector. # - --enable-profiling=true + # Additional environment variables to pass to cert-manager cainjector binary. + # For example: + # extraEnv: + # - name: SOME_VAR + # value: 'some value' + extraEnv: [] + + # Comma separated list of feature gates that should be enabled on the + # cainjector pod. + featureGates: "" + + # Resources to provide to the cert-manager cainjector pod. + # + # For example: + # requests: + # cpu: 10m + # memory: 32Mi + # + # For more information, see [Resource Management for Pods and Containers](https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/). resources: {} - # requests: - # cpu: 10m - # memory: 32Mi + + # The nodeSelector on Pods tells Kubernetes to schedule Pods on the nodes with + # matching labels. + # For more information, see [Assigning Pods to Nodes](https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/). + # + # This default ensures that Pods are only scheduled to Linux nodes. + # It prevents Pods being scheduled to Windows nodes in a mixed OS cluster. + # +docs:property nodeSelector: kubernetes.io/os: linux + # A Kubernetes Affinity, if required. For more information, see [Affinity v1 core](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.27/#affinity-v1-core). + # + # For example: + # affinity: + # nodeAffinity: + # requiredDuringSchedulingIgnoredDuringExecution: + # nodeSelectorTerms: + # - matchExpressions: + # - key: foo.bar.com/role + # operator: In + # values: + # - master affinity: {} + # A list of Kubernetes Tolerations, if required. For more information, see [Toleration v1 core](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.27/#toleration-v1-core). + # + # For example: + # tolerations: + # - key: foo.bar.com/role + # operator: Equal + # value: master + # effect: NoSchedule tolerations: [] + # A list of Kubernetes TopologySpreadConstraints, if required. For more information, see [Topology spread constraint v1 core](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.27/#topologyspreadconstraint-v1-core). + # + # For example: + # topologySpreadConstraints: + # - maxSkew: 2 + # topologyKey: topology.kubernetes.io/zone + # whenUnsatisfiable: ScheduleAnyway + # labelSelector: + # matchLabels: + # app.kubernetes.io/instance: cert-manager + # app.kubernetes.io/component: controller topologySpreadConstraints: [] - # Optional additional labels to add to the CA Injector Pods + # Optional additional labels to add to the CA Injector Pods. podLabels: {} + # Optional additional labels to add to the CA Injector metrics Service. + serviceLabels: {} + image: - repository: quay.io/jetstack/cert-manager-cainjector - # You can manage a registry with + # The container registry to pull the cainjector image from. + # +docs:property # registry: quay.io - # repository: jetstack/cert-manager-cainjector + + # The container image for the cert-manager cainjector + # +docs:property + repository: quay.io/jetstack/cert-manager-cainjector # Override the image tag to deploy by setting this variable. # If no value is set, the chart's appVersion will be used. - # tag: canary + # +docs:property + # tag: vX.Y.Z - # Setting a digest will override any tag + # Setting a digest will override any tag. + # +docs:property # digest: sha256:0e072dddd1f7f8fc8909a2ca6f65e76c5f0d2fcfb8be47935ae3457e8bbceb20 + # Kubernetes imagePullPolicy on Deployment. pullPolicy: IfNotPresent serviceAccount: - # Specifies whether a service account should be created + # Specifies whether a service account should be created. create: true + # The name of the service account to use. # If not set and create is true, a name is generated using the fullname template + # +docs:property # name: "" - # Optional additional annotations to add to the controller's ServiceAccount + + # Optional additional annotations to add to the cainjector's Service Account. + # +docs:property # annotations: {} - # Automount API credentials for a Service Account. - # Optional additional labels to add to the cainjector's ServiceAccount + + # Optional additional labels to add to the cainjector's Service Account. + # +docs:property # labels: {} + + # Automount API credentials for a Service Account. automountServiceAccountToken: true - # Automounting API credentials for a particular pod + # Automounting API credentials for a particular pod. + # +docs:property # automountServiceAccountToken: true + # Additional volumes to add to the cert-manager controller pod. + volumes: [] + + # Additional volume mounts to add to the cert-manager controller container. + volumeMounts: [] + + # enableServiceLinks indicates whether information about services should be + # injected into the pod's environment variables, matching the syntax of Docker + # links. + enableServiceLinks: false + +# +docs:section=ACME Solver + +acmesolver: + image: + # The container registry to pull the acmesolver image from. + # +docs:property + # registry: quay.io + + # The container image for the cert-manager acmesolver. + # +docs:property + repository: quay.io/jetstack/cert-manager-acmesolver + + # Override the image tag to deploy by setting this variable. + # If no value is set, the chart's appVersion is used. + # +docs:property + # tag: vX.Y.Z + + # Setting a digest will override any tag. + # +docs:property + # digest: sha256:0e072dddd1f7f8fc8909a2ca6f65e76c5f0d2fcfb8be47935ae3457e8bbceb20 + + # Kubernetes imagePullPolicy on Deployment. + pullPolicy: IfNotPresent + +# +docs:section=Startup API Check # This startupapicheck is a Helm post-install hook that waits for the webhook # endpoints to become available. -# The check is implemented using a Kubernetes Job- if you are injecting mesh -# sidecar proxies into cert-manager pods, you probably want to ensure that they -# are not injected into this Job's pod. Otherwise the installation may time out -# due to the Job never being completed because the sidecar proxy does not exit. -# See https://github.com/cert-manager/cert-manager/pull/4414 for context. +# The check is implemented using a Kubernetes Job - if you are injecting mesh +# sidecar proxies into cert-manager pods, ensure that they +# are not injected into this Job's pod. Otherwise, the installation may time out +# owing to the Job never being completed because the sidecar proxy does not exit. +# For more information, see [this note](https://github.com/cert-manager/cert-manager/pull/4414). + startupapicheck: + # Enables the startup api check. enabled: true - # Pod Security Context to be set on the startupapicheck component Pod - # ref: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/ + # Pod Security Context to be set on the startupapicheck component Pod. + # For more information, see [Configure a Security Context for a Pod or Container](https://kubernetes.io/docs/tasks/configure-pod-container/security-context/). + # +docs:property securityContext: runAsNonRoot: true seccompProfile: type: RuntimeDefault - # Container Security Context to be set on the controller component container - # ref: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/ + # Container Security Context to be set on the controller component container. + # For more information, see [Configure a Security Context for a Pod or Container](https://kubernetes.io/docs/tasks/configure-pod-container/security-context/). + # +docs:property containerSecurityContext: allowPrivilegeEscalation: false capabilities: drop: - ALL - # readOnlyRootFilesystem: true - # runAsNonRoot: true + readOnlyRootFilesystem: true - # Timeout for 'kubectl check api' command + # Timeout for 'kubectl check api' command. timeout: 1m # Job backoffLimit backoffLimit: 4 - # Optional additional annotations to add to the startupapicheck Job + # Optional additional annotations to add to the startupapicheck Job. + # +docs:property jobAnnotations: helm.sh/hook: post-install helm.sh/hook-weight: "1" helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded - # Optional additional annotations to add to the startupapicheck Pods + # Optional additional annotations to add to the startupapicheck Pods. + # +docs:property # podAnnotations: {} # Additional command line flags to pass to startupapicheck binary. - # To see all available flags run docker run quay.io/jetstack/cert-manager-ctl: --help - extraArgs: [] - + # To see all available flags run `docker run quay.io/jetstack/cert-manager-startupapicheck: --help`. + # + # Verbose logging is enabled by default so that if startupapicheck fails, you + # can know what exactly caused the failure. Verbose logs include details of + # the webhook URL, IP address and TCP connect errors for example. + # +docs:property + extraArgs: + - -v + + # Additional environment variables to pass to cert-manager startupapicheck binary. + # For example: + # extraEnv: + # - name: SOME_VAR + # value: 'some value' + extraEnv: [] + + # Resources to provide to the cert-manager controller pod. + # + # For example: + # requests: + # cpu: 10m + # memory: 32Mi + # + # For more information, see [Resource Management for Pods and Containers](https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/). resources: {} - # requests: - # cpu: 10m - # memory: 32Mi + + # The nodeSelector on Pods tells Kubernetes to schedule Pods on the nodes with + # matching labels. + # For more information, see [Assigning Pods to Nodes](https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/). + # + # This default ensures that Pods are only scheduled to Linux nodes. + # It prevents Pods being scheduled to Windows nodes in a mixed OS cluster. + # +docs:property nodeSelector: kubernetes.io/os: linux + # A Kubernetes Affinity, if required. For more information, see [Affinity v1 core](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.27/#affinity-v1-core). + # For example: + # affinity: + # nodeAffinity: + # requiredDuringSchedulingIgnoredDuringExecution: + # nodeSelectorTerms: + # - matchExpressions: + # - key: foo.bar.com/role + # operator: In + # values: + # - master affinity: {} + # A list of Kubernetes Tolerations, if required. For more information, see [Toleration v1 core](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.27/#toleration-v1-core). + # + # For example: + # tolerations: + # - key: foo.bar.com/role + # operator: Equal + # value: master + # effect: NoSchedule tolerations: [] - # Optional additional labels to add to the startupapicheck Pods + # Optional additional labels to add to the startupapicheck Pods. podLabels: {} image: - repository: quay.io/jetstack/cert-manager-ctl - # You can manage a registry with + # The container registry to pull the startupapicheck image from. + # +docs:property # registry: quay.io - # repository: jetstack/cert-manager-ctl + + # The container image for the cert-manager startupapicheck. + # +docs:property + repository: quay.io/jetstack/cert-manager-startupapicheck # Override the image tag to deploy by setting this variable. - # If no value is set, the chart's appVersion will be used. - # tag: canary + # If no value is set, the chart's appVersion is used. + # +docs:property + # tag: vX.Y.Z - # Setting a digest will override any tag + # Setting a digest will override any tag. + # +docs:property # digest: sha256:0e072dddd1f7f8fc8909a2ca6f65e76c5f0d2fcfb8be47935ae3457e8bbceb20 + # Kubernetes imagePullPolicy on Deployment. pullPolicy: IfNotPresent rbac: - # annotations for the startup API Check job RBAC and PSP resources + # annotations for the startup API Check job RBAC and PSP resources. + # +docs:property annotations: helm.sh/hook: post-install helm.sh/hook-weight: "-5" helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded + # Automounting API credentials for a particular pod. + # +docs:property + # automountServiceAccountToken: true + serviceAccount: - # Specifies whether a service account should be created + # Specifies whether a service account should be created. create: true # The name of the service account to use. - # If not set and create is true, a name is generated using the fullname template + # If not set and create is true, a name is generated using the fullname template. + # +docs:property # name: "" - # Optional additional annotations to add to the Job's ServiceAccount + # Optional additional annotations to add to the Job's Service Account. + # +docs:property annotations: helm.sh/hook: post-install helm.sh/hook-weight: "-5" helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded # Automount API credentials for a Service Account. + # +docs:property automountServiceAccountToken: true - # Optional additional labels to add to the startupapicheck's ServiceAccount + # Optional additional labels to add to the startupapicheck's Service Account. + # +docs:property # labels: {} + + # Additional volumes to add to the cert-manager controller pod. + volumes: [] + + # Additional volume mounts to add to the cert-manager controller container. + volumeMounts: [] + + # enableServiceLinks indicates whether information about services should be + # injected into pod's environment variables, matching the syntax of Docker + # links. + enableServiceLinks: false + +# Create dynamic manifests via values. +# +# For example: +# extraObjects: +# - | +# apiVersion: v1 +# kind: ConfigMap +# metadata: +# name: '{{ template "cert-manager.fullname" . }}-extra-configmap' +extraObjects: [] + +# Field used by our release pipeline to produce the static manifests. +# The field defaults to "helm" but is set to "static" when we render +# the static YAML manifests. +# +docs:hidden +creator: "helm" + +# Field that can be used as a condition when cert-manager is a dependency. +# This definition is only here as a placeholder such that it is included in +# the json schema. +# See https://helm.sh/docs/chart_best_practices/dependencies/#conditions-and-tags +# for more info. +# +docs:hidden +enabled: true diff --git a/deploy/crds/README.md b/deploy/crds/README.md index 328559e88ee..fba79fed242 100644 --- a/deploy/crds/README.md +++ b/deploy/crds/README.md @@ -1,18 +1,8 @@ # CRDs source directory -> **WARNING**: if you are an end-user, you do NOT need to use the files in this -> directory. These files are for **development purposes only**. +> **WARNING**: if you are an end-user, you probably should NOT need to use the +> files in this directory. These files are for **reference, development and testing purposes only**. This directory contains 'source code' used to build our CustomResourceDefinition -resources in a way that can be consumed by all our different deployment methods. - -This package exposes a number of different Bazel targets: - -* `templates`: the Helm templates for the CRD manifests -* `crds`: the templated CRD manifests (after running `helm template`) -* `crd.templated`: for each CRD type, the one CRD after running `helm template` -* `templated_files`: a filegroup containing all of the individual templated CRD files - -Most users should never utilise the files in this directory directly. Instead, Bazel -build targets in other packages (i.e. `//deploy/manifests`, `//deploy/charts` etc) -will be configured to automatically consume the appropriate artifact listed above. +resources consumed by our officially supported deployment methods (e.g. the Helm chart). +The CRDs in this directory might be incomplete, and should **NOT** be used to provision the operator. \ No newline at end of file diff --git a/deploy/crds/acme.cert-manager.io_challenges.yaml b/deploy/crds/acme.cert-manager.io_challenges.yaml new file mode 100644 index 00000000000..051aad61bf0 --- /dev/null +++ b/deploy/crds/acme.cert-manager.io_challenges.yaml @@ -0,0 +1,3476 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.19.0 + name: challenges.acme.cert-manager.io +spec: + group: acme.cert-manager.io + names: + categories: + - cert-manager + - cert-manager-acme + kind: Challenge + listKind: ChallengeList + plural: challenges + singular: challenge + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .status.state + name: State + type: string + - jsonPath: .spec.dnsName + name: Domain + type: string + - jsonPath: .status.reason + name: Reason + priority: 1 + type: string + - description: CreationTimestamp is a timestamp representing the server time when + this object was created. It is not guaranteed to be set in happens-before + order across separate operations. Clients may not set this value. It is represented + in RFC3339 form and is in UTC. + jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1 + schema: + openAPIV3Schema: + description: Challenge is a type to represent a Challenge request with an + ACME server + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + properties: + authorizationURL: + description: |- + The URL to the ACME Authorization resource that this + challenge is a part of. + type: string + dnsName: + description: |- + 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`. + type: string + issuerRef: + description: |- + 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. + properties: + group: + description: |- + Group of the issuer being referred to. + Defaults to 'cert-manager.io'. + type: string + kind: + description: |- + Kind of the issuer being referred to. + Defaults to 'Issuer'. + type: string + name: + description: Name of the issuer being referred to. + type: string + required: + - name + type: object + key: + description: |- + 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: + `.`. + For DNS01 challenges, this is the base64 encoded SHA256 sum of the + `.` + text that must be set as the TXT record content. + type: string + solver: + description: |- + Contains the domain solving configuration that should be used to + solve this challenge resource. + properties: + dns01: + description: |- + Configures cert-manager to attempt to complete authorizations by + performing the DNS01 challenge flow. + properties: + acmeDNS: + description: |- + Use the 'ACME DNS' (https://github.com/joohoi/acme-dns) API to manage + DNS01 challenge records. + properties: + accountSecretRef: + description: |- + A reference to a specific 'key' within a Secret resource. + In some instances, `key` is a required field. + properties: + key: + description: |- + 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. + type: string + name: + description: |- + Name of the resource being referred to. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + required: + - name + type: object + host: + type: string + required: + - accountSecretRef + - host + type: object + akamai: + description: Use the Akamai DNS zone management API to manage + DNS01 challenge records. + properties: + accessTokenSecretRef: + description: |- + A reference to a specific 'key' within a Secret resource. + In some instances, `key` is a required field. + properties: + key: + description: |- + 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. + type: string + name: + description: |- + Name of the resource being referred to. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + required: + - name + type: object + clientSecretSecretRef: + description: |- + A reference to a specific 'key' within a Secret resource. + In some instances, `key` is a required field. + properties: + key: + description: |- + 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. + type: string + name: + description: |- + Name of the resource being referred to. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + required: + - name + type: object + clientTokenSecretRef: + description: |- + A reference to a specific 'key' within a Secret resource. + In some instances, `key` is a required field. + properties: + key: + description: |- + 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. + type: string + name: + description: |- + Name of the resource being referred to. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + required: + - name + type: object + serviceConsumerDomain: + type: string + required: + - accessTokenSecretRef + - clientSecretSecretRef + - clientTokenSecretRef + - serviceConsumerDomain + type: object + azureDNS: + description: Use the Microsoft Azure DNS API to manage DNS01 + challenge records. + properties: + clientID: + description: |- + Auth: Azure Service Principal: + The ClientID of the Azure Service Principal used to authenticate with Azure DNS. + If set, ClientSecret and TenantID must also be set. + type: string + clientSecretSecretRef: + description: |- + Auth: Azure Service Principal: + A reference to a Secret containing the password associated with the Service Principal. + If set, ClientID and TenantID must also be set. + properties: + key: + description: |- + 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. + type: string + name: + description: |- + Name of the resource being referred to. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + required: + - name + type: object + environment: + description: name of the Azure environment (default AzurePublicCloud) + enum: + - AzurePublicCloud + - AzureChinaCloud + - AzureGermanCloud + - AzureUSGovernmentCloud + type: string + hostedZoneName: + description: name of the DNS zone that should be used + type: string + managedIdentity: + description: |- + Auth: Azure Workload Identity or Azure Managed Service Identity: + Settings to enable Azure Workload Identity or Azure Managed Service Identity + If set, ClientID, ClientSecret and TenantID must not be set. + properties: + clientID: + description: client ID of the managed identity, cannot + be used at the same time as resourceID + type: string + resourceID: + description: |- + resource ID of the managed identity, cannot be used at the same time as clientID + Cannot be used for Azure Managed Service Identity + type: string + tenantID: + description: tenant ID of the managed identity, cannot + be used at the same time as resourceID + type: string + type: object + resourceGroupName: + description: resource group the DNS zone is located in + type: string + subscriptionID: + description: ID of the Azure subscription + type: string + tenantID: + description: |- + Auth: Azure Service Principal: + The TenantID of the Azure Service Principal used to authenticate with Azure DNS. + If set, ClientID and ClientSecret must also be set. + type: string + required: + - resourceGroupName + - subscriptionID + type: object + cloudDNS: + description: Use the Google Cloud DNS API to manage DNS01 + challenge records. + properties: + hostedZoneName: + description: |- + 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. + type: string + project: + type: string + serviceAccountSecretRef: + description: |- + A reference to a specific 'key' within a Secret resource. + In some instances, `key` is a required field. + properties: + key: + description: |- + 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. + type: string + name: + description: |- + Name of the resource being referred to. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + required: + - name + type: object + required: + - project + type: object + cloudflare: + description: Use the Cloudflare API to manage DNS01 challenge + records. + properties: + apiKeySecretRef: + description: |- + 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. + properties: + key: + description: |- + 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. + type: string + name: + description: |- + Name of the resource being referred to. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + required: + - name + type: object + apiTokenSecretRef: + description: API token used to authenticate with Cloudflare. + properties: + key: + description: |- + 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. + type: string + name: + description: |- + Name of the resource being referred to. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + required: + - name + type: object + email: + description: Email of the account, only required when + using API key based authentication. + type: string + type: object + cnameStrategy: + description: |- + CNAMEStrategy configures how the DNS01 provider should handle CNAME + records when found in DNS zones. + enum: + - None + - Follow + type: string + digitalocean: + description: Use the DigitalOcean DNS API to manage DNS01 + challenge records. + properties: + tokenSecretRef: + description: |- + A reference to a specific 'key' within a Secret resource. + In some instances, `key` is a required field. + properties: + key: + description: |- + 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. + type: string + name: + description: |- + Name of the resource being referred to. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + required: + - name + type: object + required: + - tokenSecretRef + type: object + rfc2136: + description: |- + Use RFC2136 ("Dynamic Updates in the Domain Name System") (https://datatracker.ietf.org/doc/rfc2136/) + to manage DNS01 challenge records. + properties: + nameserver: + description: |- + 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. + type: string + protocol: + description: Protocol to use for dynamic DNS update queries. + Valid values are (case-sensitive) ``TCP`` and ``UDP``; + ``UDP`` (default). + enum: + - TCP + - UDP + type: string + tsigAlgorithm: + description: |- + 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``. + type: string + tsigKeyName: + description: |- + The TSIG Key name configured in the DNS. + If ``tsigSecretSecretRef`` is defined, this field is required. + type: string + tsigSecretSecretRef: + description: |- + The name of the secret containing the TSIG value. + If ``tsigKeyName`` is defined, this field is required. + properties: + key: + description: |- + 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. + type: string + name: + description: |- + Name of the resource being referred to. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + required: + - name + type: object + required: + - nameserver + type: object + route53: + description: Use the AWS Route53 API to manage DNS01 challenge + records. + properties: + accessKeyID: + description: |- + 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 + type: string + accessKeyIDSecretRef: + description: |- + 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 + properties: + key: + description: |- + 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. + type: string + name: + description: |- + Name of the resource being referred to. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + required: + - name + type: object + auth: + description: Auth configures how cert-manager authenticates. + properties: + kubernetes: + description: |- + Kubernetes authenticates with Route53 using AssumeRoleWithWebIdentity + by passing a bound ServiceAccount token. + properties: + serviceAccountRef: + description: |- + A reference to a service account that will be used to request a bound + token (also known as "projected token"). To use this field, you must + configure an RBAC rule to let cert-manager request a token. + properties: + audiences: + description: |- + TokenAudiences is an optional list of audiences to include in the + token passed to AWS. The default token consisting of the issuer's namespace + and name is always included. + If unset the audience defaults to `sts.amazonaws.com`. + items: + type: string + type: array + x-kubernetes-list-type: atomic + name: + description: Name of the ServiceAccount used + to request a token. + type: string + required: + - name + type: object + required: + - serviceAccountRef + type: object + required: + - kubernetes + type: object + hostedZoneID: + description: If set, the provider will manage only this + zone in Route53 and will not do a lookup using the route53:ListHostedZonesByName + api call. + type: string + region: + description: |- + Override the AWS region. + + Route53 is a global service and does not have regional endpoints but the + region specified here (or via environment variables) is used as a hint to + help compute the correct AWS credential scope and partition when it + connects to Route53. See: + - [Amazon Route 53 endpoints and quotas](https://docs.aws.amazon.com/general/latest/gr/r53.html) + - [Global services](https://docs.aws.amazon.com/whitepapers/latest/aws-fault-isolation-boundaries/global-services.html) + + If you omit this region field, cert-manager will use the region from + AWS_REGION and AWS_DEFAULT_REGION environment variables, if they are set + in the cert-manager controller Pod. + + The `region` field is not needed if you use [IAM Roles for Service Accounts (IRSA)](https://docs.aws.amazon.com/eks/latest/userguide/iam-roles-for-service-accounts.html). + Instead an AWS_REGION environment variable is added to the cert-manager controller Pod by: + [Amazon EKS Pod Identity Webhook](https://github.com/aws/amazon-eks-pod-identity-webhook). + In this case this `region` field value is ignored. + + The `region` field is not needed if you use [EKS Pod Identities](https://docs.aws.amazon.com/eks/latest/userguide/pod-identities.html). + Instead an AWS_REGION environment variable is added to the cert-manager controller Pod by: + [Amazon EKS Pod Identity Agent](https://github.com/aws/eks-pod-identity-agent), + In this case this `region` field value is ignored. + type: string + role: + description: |- + 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 + type: string + secretAccessKeySecretRef: + description: |- + 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 + properties: + key: + description: |- + 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. + type: string + name: + description: |- + Name of the resource being referred to. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + required: + - name + type: object + type: object + webhook: + description: |- + Configure an external webhook based DNS01 challenge solver to manage + DNS01 challenge records. + properties: + config: + description: |- + 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. + x-kubernetes-preserve-unknown-fields: true + groupName: + description: |- + 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. + type: string + solverName: + description: |- + 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'. + type: string + required: + - groupName + - solverName + type: object + type: object + http01: + description: |- + 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. + properties: + gatewayHTTPRoute: + description: |- + 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. + properties: + labels: + additionalProperties: + type: string + description: |- + Custom labels that will be applied to HTTPRoutes created by cert-manager + while solving HTTP-01 challenges. + type: object + parentRefs: + description: |- + 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 + items: + description: |- + ParentReference identifies an API object (usually a Gateway) that can be considered + a parent of this resource (usually a route). There are two kinds of parent resources + with "Core" support: + + * Gateway (Gateway conformance profile) + * Service (Mesh conformance profile, ClusterIP Services only) + + This API may be extended in the future to support additional kinds of parent + resources. + + The API object must be valid in the cluster; the Group and Kind must + be registered in the cluster for this reference to be valid. + properties: + group: + default: gateway.networking.k8s.io + description: |- + Group is the group of the referent. + When unspecified, "gateway.networking.k8s.io" is inferred. + To set the core API group (such as for a "Service" kind referent), + Group must be explicitly set to "" (empty string). + + Support: Core + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Gateway + description: |- + Kind is kind of the referent. + + There are two kinds of parent resources with "Core" support: + + * Gateway (Gateway conformance profile) + * Service (Mesh conformance profile, ClusterIP Services only) + + Support for other resources is Implementation-Specific. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: |- + Name is the name of the referent. + + Support: Core + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the referent. When unspecified, this refers + to the local namespace of the Route. + + Note that there are specific rules for ParentRefs which cross namespace + boundaries. Cross-namespace references are only valid if they are explicitly + allowed by something in the namespace they are referring to. For example: + Gateway has the AllowedRoutes field, and ReferenceGrant provides a + generic way to enable any other kind of cross-namespace reference. + + + ParentRefs from a Route to a Service in the same namespace are "producer" + routes, which apply default routing rules to inbound connections from + any namespace to the Service. + + ParentRefs from a Route to a Service in a different namespace are + "consumer" routes, and these routing rules are only applied to outbound + connections originating from the same namespace as the Route, for which + the intended destination of the connections are a Service targeted as a + ParentRef of the Route. + + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: |- + Port is the network port this Route targets. It can be interpreted + differently based on the type of parent resource. + + When the parent resource is a Gateway, this targets all listeners + listening on the specified port that also support this kind of Route(and + select this Route). It's not recommended to set `Port` unless the + networking behaviors specified in a Route must apply to a specific port + as opposed to a listener(s) whose port(s) may be changed. When both Port + and SectionName are specified, the name and port of the selected listener + must match both specified values. + + + When the parent resource is a Service, this targets a specific port in the + Service spec. When both Port (experimental) and SectionName are specified, + the name and port of the selected port must match both specified values. + + + Implementations MAY choose to support other parent resources. + Implementations supporting other types of parent resources MUST clearly + document how/if Port is interpreted. + + For the purpose of status, an attachment is considered successful as + long as the parent resource accepts it partially. For example, Gateway + listeners can restrict which Routes can attach to them by Route kind, + namespace, or hostname. If 1 of 2 Gateway listeners accept attachment + from the referencing Route, the Route MUST be considered successfully + attached. If no Gateway listeners accept attachment from this Route, + the Route MUST be considered detached from the Gateway. + + Support: Extended + format: int32 + maximum: 65535 + minimum: 1 + type: integer + sectionName: + description: |- + SectionName is the name of a section within the target resource. In the + following resources, SectionName is interpreted as the following: + + * Gateway: Listener name. When both Port (experimental) and SectionName + are specified, the name and port of the selected listener must match + both specified values. + * Service: Port name. When both Port (experimental) and SectionName + are specified, the name and port of the selected listener must match + both specified values. + + Implementations MAY choose to support attaching Routes to other resources. + If that is the case, they MUST clearly document how SectionName is + interpreted. + + When unspecified (empty string), this will reference the entire resource. + For the purpose of status, an attachment is considered successful if at + least one section in the parent resource accepts it. For example, Gateway + listeners can restrict which Routes can attach to them by Route kind, + namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from + the referencing Route, the Route MUST be considered successfully + attached. If no Gateway listeners accept attachment from this Route, the + Route MUST be considered detached from the Gateway. + + Support: Core + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - name + type: object + type: array + x-kubernetes-list-type: atomic + podTemplate: + description: |- + Optional pod template used to configure the ACME challenge solver pods + used for HTTP01 challenges. + properties: + metadata: + description: |- + 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. + properties: + annotations: + additionalProperties: + type: string + description: Annotations that should be added + to the created ACME HTTP01 solver pods. + type: object + labels: + additionalProperties: + type: string + description: Labels that should be added to the + created ACME HTTP01 solver pods. + type: object + type: object + spec: + description: |- + PodSpec defines overrides for the HTTP01 challenge solver pod. + Check ACMEChallengeSolverHTTP01IngressPodSpec to find out currently supported fields. + All other fields will be ignored. + properties: + affinity: + description: If specified, the pod's scheduling + constraints + properties: + nodeAffinity: + description: Describes node affinity scheduling + rules for the pod. + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node matches the corresponding matchExpressions; the + node(s) with the highest sum are the most preferred. + items: + description: |- + An empty preferred scheduling term matches all objects with implicit weight 0 + (i.e. it's a no-op). A null preferred scheduling term matches no objects (i.e. is also a no-op). + properties: + preference: + description: A node selector term, + associated with the corresponding + weight. + properties: + matchExpressions: + description: A list of node + selector requirements by node's + labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: The label + key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: A list of node + selector requirements by node's + fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: The label + key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + weight: + description: Weight associated with + matching the corresponding nodeSelectorTerm, + in the range 1-100. + format: int32 + type: integer + required: + - preference + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to an update), the system + may or may not try to eventually evict the pod from its node. + properties: + nodeSelectorTerms: + description: Required. A list of node + selector terms. The terms are ORed. + items: + description: |- + A null or empty node selector term matches no objects. The requirements of + them are ANDed. + The TopologySelectorTerm type implements a subset of the NodeSelectorTerm. + properties: + matchExpressions: + description: A list of node + selector requirements by node's + labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: The label + key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: A list of node + selector requirements by node's + fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: The label + key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + type: array + x-kubernetes-list-type: atomic + required: + - nodeSelectorTerms + type: object + x-kubernetes-map-type: atomic + type: object + podAffinity: + description: Describes pod affinity scheduling + rules (e.g. co-locate this pod in the same + node, zone, etc. as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: The weights of all of the + matched WeightedPodAffinityTerm fields + are added per-node to find the most + preferred node(s) + properties: + podAffinityTerm: + description: Required. A pod affinity + term, associated with the corresponding + weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is + the label key that + the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is + the label key that + the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + podAntiAffinity: + description: Describes pod anti-affinity scheduling + rules (e.g. avoid putting this pod in the + same node, zone, etc. as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the anti-affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling anti-affinity expressions, etc.), + compute a sum by iterating through the elements of this field and subtracting + "weight" from the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: The weights of all of the + matched WeightedPodAffinityTerm fields + are added per-node to find the most + preferred node(s) + properties: + podAffinityTerm: + description: Required. A pod affinity + term, associated with the corresponding + weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is + the label key that + the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is + the label key that + the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the anti-affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the anti-affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + type: object + imagePullSecrets: + description: If specified, the pod's imagePullSecrets + items: + description: |- + LocalObjectReference contains enough information to let you locate the + referenced object inside the same namespace. + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + type: object + x-kubernetes-map-type: atomic + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + nodeSelector: + additionalProperties: + type: string + description: |- + 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/ + type: object + priorityClassName: + description: If specified, the pod's priorityClassName. + type: string + resources: + description: |- + If specified, the pod's resource requirements. + These values override the global resource configuration flags. + Note that when only specifying resource limits, ensure they are greater than or equal + to the corresponding global resource requests configured via controller flags + (--acme-http01-solver-resource-request-cpu, --acme-http01-solver-resource-request-memory). + Kubernetes will reject pod creation if limits are lower than requests, causing challenge failures. + properties: + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to the global values configured via controller flags. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + securityContext: + description: If specified, the pod's security + context + properties: + fsGroup: + description: |- + A special supplemental group that applies to all containers in a pod. + Some volume types allow the Kubelet to change the ownership of that volume + to be owned by the pod: + + 1. The owning GID will be the FSGroup + 2. The setgid bit is set (new files created in the volume will be owned by FSGroup) + 3. The permission bits are OR'd with rw-rw---- + + If unset, the Kubelet will not modify the ownership and permissions of any volume. + Note that this field cannot be set when spec.os.name is windows. + format: int64 + type: integer + fsGroupChangePolicy: + description: |- + fsGroupChangePolicy defines behavior of changing ownership and permission of the volume + before being exposed inside Pod. This field will only apply to + volume types which support fsGroup based ownership(and permissions). + It will have no effect on ephemeral volume types such as: secret, configmaps + and emptydir. + Valid values are "OnRootMismatch" and "Always". If not specified, "Always" is used. + Note that this field cannot be set when spec.os.name is windows. + type: string + runAsGroup: + description: |- + The GID to run the entrypoint of the container process. + Uses runtime default if unset. + May also be set in SecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext takes precedence + for that container. + Note that this field cannot be set when spec.os.name is windows. + format: int64 + type: integer + runAsNonRoot: + description: |- + Indicates that the container must run as a non-root user. + If true, the Kubelet will validate the image at runtime to ensure that it + does not run as UID 0 (root) and fail to start the container if it does. + If unset or false, no such validation will be performed. + May also be set in SecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext takes precedence. + type: boolean + runAsUser: + description: |- + The UID to run the entrypoint of the container process. + Defaults to user specified in image metadata if unspecified. + May also be set in SecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext takes precedence + for that container. + Note that this field cannot be set when spec.os.name is windows. + format: int64 + type: integer + seLinuxOptions: + description: |- + The SELinux context to be applied to all containers. + If unspecified, the container runtime will allocate a random SELinux context for each + container. May also be set in SecurityContext. If set in + both SecurityContext and PodSecurityContext, the value specified in SecurityContext + takes precedence for that container. + Note that this field cannot be set when spec.os.name is windows. + properties: + level: + description: Level is SELinux level label + that applies to the container. + type: string + role: + description: Role is a SELinux role label + that applies to the container. + type: string + type: + description: Type is a SELinux type label + that applies to the container. + type: string + user: + description: User is a SELinux user label + that applies to the container. + type: string + type: object + seccompProfile: + description: |- + The seccomp options to use by the containers in this pod. + Note that this field cannot be set when spec.os.name is windows. + properties: + localhostProfile: + description: |- + localhostProfile indicates a profile defined in a file on the node should be used. + The profile must be preconfigured on the node to work. + Must be a descending path, relative to the kubelet's configured seccomp profile location. + Must be set if type is "Localhost". Must NOT be set for any other type. + type: string + type: + description: |- + type indicates which kind of seccomp profile will be applied. + Valid options are: + + Localhost - a profile defined in a file on the node should be used. + RuntimeDefault - the container runtime default profile should be used. + Unconfined - no profile should be applied. + type: string + required: + - type + type: object + supplementalGroups: + description: |- + A list of groups applied to the first process run in each container, in addition + to the container's primary GID, the fsGroup (if specified), and group memberships + defined in the container image for the uid of the container process. If unspecified, + no additional groups are added to any container. Note that group memberships + defined in the container image for the uid of the container process are still effective, + even if they are not included in this list. + Note that this field cannot be set when spec.os.name is windows. + items: + format: int64 + type: integer + type: array + x-kubernetes-list-type: atomic + sysctls: + description: |- + Sysctls hold a list of namespaced sysctls used for the pod. Pods with unsupported + sysctls (by the container runtime) might fail to launch. + Note that this field cannot be set when spec.os.name is windows. + items: + description: Sysctl defines a kernel parameter + to be set + properties: + name: + description: Name of a property to set + type: string + value: + description: Value of a property to + set + type: string + required: + - name + - value + type: object + type: array + x-kubernetes-list-type: atomic + type: object + serviceAccountName: + description: If specified, the pod's service account + type: string + tolerations: + description: If specified, the pod's tolerations. + items: + description: |- + The pod this Toleration is attached to tolerates any taint that matches + the triple using the matching operator . + properties: + effect: + description: |- + Effect indicates the taint effect to match. Empty means match all taint effects. + When specified, allowed values are NoSchedule, PreferNoSchedule and NoExecute. + type: string + key: + description: |- + Key is the taint key that the toleration applies to. Empty means match all taint keys. + If the key is empty, operator must be Exists; this combination means to match all values and all keys. + type: string + operator: + description: |- + Operator represents a key's relationship to the value. + Valid operators are Exists and Equal. Defaults to Equal. + Exists is equivalent to wildcard for value, so that a pod can + tolerate all taints of a particular category. + type: string + tolerationSeconds: + description: |- + TolerationSeconds represents the period of time the toleration (which must be + of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default, + it is not set, which means tolerate the taint forever (do not evict). Zero and + negative values will be treated as 0 (evict immediately) by the system. + format: int64 + type: integer + value: + description: |- + Value is the taint value the toleration matches to. + If the operator is Exists, the value should be empty, otherwise just a regular string. + type: string + type: object + type: array + x-kubernetes-list-type: atomic + type: object + type: object + serviceType: + description: |- + Optional service type for Kubernetes solver service. Supported values + are NodePort or ClusterIP. If unset, defaults to NodePort. + type: string + type: object + ingress: + description: |- + 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. + properties: + class: + description: |- + 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. + type: string + ingressClassName: + description: |- + 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. + type: string + ingressTemplate: + description: |- + Optional ingress template used to configure the ACME challenge solver + ingress used for HTTP01 challenges. + properties: + metadata: + description: |- + 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. + properties: + annotations: + additionalProperties: + type: string + description: Annotations that should be added + to the created ACME HTTP01 solver ingress. + type: object + labels: + additionalProperties: + type: string + description: Labels that should be added to the + created ACME HTTP01 solver ingress. + type: object + type: object + type: object + name: + description: |- + 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. + type: string + podTemplate: + description: |- + Optional pod template used to configure the ACME challenge solver pods + used for HTTP01 challenges. + properties: + metadata: + description: |- + 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. + properties: + annotations: + additionalProperties: + type: string + description: Annotations that should be added + to the created ACME HTTP01 solver pods. + type: object + labels: + additionalProperties: + type: string + description: Labels that should be added to the + created ACME HTTP01 solver pods. + type: object + type: object + spec: + description: |- + PodSpec defines overrides for the HTTP01 challenge solver pod. + Check ACMEChallengeSolverHTTP01IngressPodSpec to find out currently supported fields. + All other fields will be ignored. + properties: + affinity: + description: If specified, the pod's scheduling + constraints + properties: + nodeAffinity: + description: Describes node affinity scheduling + rules for the pod. + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node matches the corresponding matchExpressions; the + node(s) with the highest sum are the most preferred. + items: + description: |- + An empty preferred scheduling term matches all objects with implicit weight 0 + (i.e. it's a no-op). A null preferred scheduling term matches no objects (i.e. is also a no-op). + properties: + preference: + description: A node selector term, + associated with the corresponding + weight. + properties: + matchExpressions: + description: A list of node + selector requirements by node's + labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: The label + key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: A list of node + selector requirements by node's + fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: The label + key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + weight: + description: Weight associated with + matching the corresponding nodeSelectorTerm, + in the range 1-100. + format: int32 + type: integer + required: + - preference + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to an update), the system + may or may not try to eventually evict the pod from its node. + properties: + nodeSelectorTerms: + description: Required. A list of node + selector terms. The terms are ORed. + items: + description: |- + A null or empty node selector term matches no objects. The requirements of + them are ANDed. + The TopologySelectorTerm type implements a subset of the NodeSelectorTerm. + properties: + matchExpressions: + description: A list of node + selector requirements by node's + labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: The label + key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: A list of node + selector requirements by node's + fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: The label + key that the selector + applies to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + type: array + x-kubernetes-list-type: atomic + required: + - nodeSelectorTerms + type: object + x-kubernetes-map-type: atomic + type: object + podAffinity: + description: Describes pod affinity scheduling + rules (e.g. co-locate this pod in the same + node, zone, etc. as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: The weights of all of the + matched WeightedPodAffinityTerm fields + are added per-node to find the most + preferred node(s) + properties: + podAffinityTerm: + description: Required. A pod affinity + term, associated with the corresponding + weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is + the label key that + the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is + the label key that + the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + podAntiAffinity: + description: Describes pod anti-affinity scheduling + rules (e.g. avoid putting this pod in the + same node, zone, etc. as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the anti-affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling anti-affinity expressions, etc.), + compute a sum by iterating through the elements of this field and subtracting + "weight" from the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: The weights of all of the + matched WeightedPodAffinityTerm fields + are added per-node to find the most + preferred node(s) + properties: + podAffinityTerm: + description: Required. A pod affinity + term, associated with the corresponding + weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is + the label key that + the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is + the label key that + the selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the anti-affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the anti-affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the + label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + type: object + imagePullSecrets: + description: If specified, the pod's imagePullSecrets + items: + description: |- + LocalObjectReference contains enough information to let you locate the + referenced object inside the same namespace. + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + type: object + x-kubernetes-map-type: atomic + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + nodeSelector: + additionalProperties: + type: string + description: |- + 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/ + type: object + priorityClassName: + description: If specified, the pod's priorityClassName. + type: string + resources: + description: |- + If specified, the pod's resource requirements. + These values override the global resource configuration flags. + Note that when only specifying resource limits, ensure they are greater than or equal + to the corresponding global resource requests configured via controller flags + (--acme-http01-solver-resource-request-cpu, --acme-http01-solver-resource-request-memory). + Kubernetes will reject pod creation if limits are lower than requests, causing challenge failures. + properties: + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to the global values configured via controller flags. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + securityContext: + description: If specified, the pod's security + context + properties: + fsGroup: + description: |- + A special supplemental group that applies to all containers in a pod. + Some volume types allow the Kubelet to change the ownership of that volume + to be owned by the pod: + + 1. The owning GID will be the FSGroup + 2. The setgid bit is set (new files created in the volume will be owned by FSGroup) + 3. The permission bits are OR'd with rw-rw---- + + If unset, the Kubelet will not modify the ownership and permissions of any volume. + Note that this field cannot be set when spec.os.name is windows. + format: int64 + type: integer + fsGroupChangePolicy: + description: |- + fsGroupChangePolicy defines behavior of changing ownership and permission of the volume + before being exposed inside Pod. This field will only apply to + volume types which support fsGroup based ownership(and permissions). + It will have no effect on ephemeral volume types such as: secret, configmaps + and emptydir. + Valid values are "OnRootMismatch" and "Always". If not specified, "Always" is used. + Note that this field cannot be set when spec.os.name is windows. + type: string + runAsGroup: + description: |- + The GID to run the entrypoint of the container process. + Uses runtime default if unset. + May also be set in SecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext takes precedence + for that container. + Note that this field cannot be set when spec.os.name is windows. + format: int64 + type: integer + runAsNonRoot: + description: |- + Indicates that the container must run as a non-root user. + If true, the Kubelet will validate the image at runtime to ensure that it + does not run as UID 0 (root) and fail to start the container if it does. + If unset or false, no such validation will be performed. + May also be set in SecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext takes precedence. + type: boolean + runAsUser: + description: |- + The UID to run the entrypoint of the container process. + Defaults to user specified in image metadata if unspecified. + May also be set in SecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext takes precedence + for that container. + Note that this field cannot be set when spec.os.name is windows. + format: int64 + type: integer + seLinuxOptions: + description: |- + The SELinux context to be applied to all containers. + If unspecified, the container runtime will allocate a random SELinux context for each + container. May also be set in SecurityContext. If set in + both SecurityContext and PodSecurityContext, the value specified in SecurityContext + takes precedence for that container. + Note that this field cannot be set when spec.os.name is windows. + properties: + level: + description: Level is SELinux level label + that applies to the container. + type: string + role: + description: Role is a SELinux role label + that applies to the container. + type: string + type: + description: Type is a SELinux type label + that applies to the container. + type: string + user: + description: User is a SELinux user label + that applies to the container. + type: string + type: object + seccompProfile: + description: |- + The seccomp options to use by the containers in this pod. + Note that this field cannot be set when spec.os.name is windows. + properties: + localhostProfile: + description: |- + localhostProfile indicates a profile defined in a file on the node should be used. + The profile must be preconfigured on the node to work. + Must be a descending path, relative to the kubelet's configured seccomp profile location. + Must be set if type is "Localhost". Must NOT be set for any other type. + type: string + type: + description: |- + type indicates which kind of seccomp profile will be applied. + Valid options are: + + Localhost - a profile defined in a file on the node should be used. + RuntimeDefault - the container runtime default profile should be used. + Unconfined - no profile should be applied. + type: string + required: + - type + type: object + supplementalGroups: + description: |- + A list of groups applied to the first process run in each container, in addition + to the container's primary GID, the fsGroup (if specified), and group memberships + defined in the container image for the uid of the container process. If unspecified, + no additional groups are added to any container. Note that group memberships + defined in the container image for the uid of the container process are still effective, + even if they are not included in this list. + Note that this field cannot be set when spec.os.name is windows. + items: + format: int64 + type: integer + type: array + x-kubernetes-list-type: atomic + sysctls: + description: |- + Sysctls hold a list of namespaced sysctls used for the pod. Pods with unsupported + sysctls (by the container runtime) might fail to launch. + Note that this field cannot be set when spec.os.name is windows. + items: + description: Sysctl defines a kernel parameter + to be set + properties: + name: + description: Name of a property to set + type: string + value: + description: Value of a property to + set + type: string + required: + - name + - value + type: object + type: array + x-kubernetes-list-type: atomic + type: object + serviceAccountName: + description: If specified, the pod's service account + type: string + tolerations: + description: If specified, the pod's tolerations. + items: + description: |- + The pod this Toleration is attached to tolerates any taint that matches + the triple using the matching operator . + properties: + effect: + description: |- + Effect indicates the taint effect to match. Empty means match all taint effects. + When specified, allowed values are NoSchedule, PreferNoSchedule and NoExecute. + type: string + key: + description: |- + Key is the taint key that the toleration applies to. Empty means match all taint keys. + If the key is empty, operator must be Exists; this combination means to match all values and all keys. + type: string + operator: + description: |- + Operator represents a key's relationship to the value. + Valid operators are Exists and Equal. Defaults to Equal. + Exists is equivalent to wildcard for value, so that a pod can + tolerate all taints of a particular category. + type: string + tolerationSeconds: + description: |- + TolerationSeconds represents the period of time the toleration (which must be + of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default, + it is not set, which means tolerate the taint forever (do not evict). Zero and + negative values will be treated as 0 (evict immediately) by the system. + format: int64 + type: integer + value: + description: |- + Value is the taint value the toleration matches to. + If the operator is Exists, the value should be empty, otherwise just a regular string. + type: string + type: object + type: array + x-kubernetes-list-type: atomic + type: object + type: object + serviceType: + description: |- + Optional service type for Kubernetes solver service. Supported values + are NodePort or ClusterIP. If unset, defaults to NodePort. + type: string + type: object + type: object + selector: + description: |- + 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. + properties: + dnsNames: + description: |- + 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. + items: + type: string + type: array + x-kubernetes-list-type: atomic + dnsZones: + description: |- + 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. + items: + type: string + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + A label selector that is used to refine the set of certificate's that + this challenge solver will apply to. + type: object + type: object + type: object + token: + description: |- + The ACME challenge token for this challenge. + This is the raw value returned from the ACME server. + type: string + type: + description: |- + The type of ACME challenge this resource represents. + One of "HTTP-01" or "DNS-01". + enum: + - HTTP-01 + - DNS-01 + type: string + url: + description: |- + The URL of the ACME Challenge resource for this challenge. + This can be used to lookup details about the status of this challenge. + type: string + wildcard: + description: |- + wildcard will be true if this challenge is for a wildcard identifier, + for example '*.example.com'. + type: boolean + required: + - authorizationURL + - dnsName + - issuerRef + - key + - solver + - token + - type + - url + type: object + status: + properties: + presented: + description: |- + 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). + type: boolean + processing: + description: |- + 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. + type: boolean + reason: + description: |- + Contains human readable information on why the Challenge is in the + current state. + type: string + state: + description: |- + Contains the current 'state' of the challenge. + If not set, the state of the challenge is unknown. + enum: + - valid + - ready + - pending + - processing + - invalid + - expired + - errored + type: string + type: object + required: + - metadata + - spec + type: object + served: true + storage: true + subresources: + status: {} diff --git a/deploy/crds/acme.cert-manager.io_orders.yaml b/deploy/crds/acme.cert-manager.io_orders.yaml new file mode 100644 index 00000000000..24bbef6cf8e --- /dev/null +++ b/deploy/crds/acme.cert-manager.io_orders.yaml @@ -0,0 +1,274 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.19.0 + name: orders.acme.cert-manager.io +spec: + group: acme.cert-manager.io + names: + categories: + - cert-manager + - cert-manager-acme + kind: Order + listKind: OrderList + plural: orders + singular: order + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .status.state + name: State + type: string + - jsonPath: .spec.issuerRef.name + name: Issuer + priority: 1 + type: string + - jsonPath: .status.reason + name: Reason + priority: 1 + type: string + - description: CreationTimestamp is a timestamp representing the server time when + this object was created. It is not guaranteed to be set in happens-before + order across separate operations. Clients may not set this value. It is represented + in RFC3339 form and is in UTC. + jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1 + schema: + openAPIV3Schema: + description: Order is a type to represent an Order with an ACME server + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + properties: + commonName: + description: |- + 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. + type: string + dnsNames: + description: |- + 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. + items: + type: string + type: array + x-kubernetes-list-type: atomic + duration: + description: |- + Duration is the duration for the not after date for the requested certificate. + this is set on order creation as pe the ACME spec. + type: string + ipAddresses: + description: |- + 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. + items: + type: string + type: array + x-kubernetes-list-type: atomic + issuerRef: + description: |- + 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. + properties: + group: + description: |- + Group of the issuer being referred to. + Defaults to 'cert-manager.io'. + type: string + kind: + description: |- + Kind of the issuer being referred to. + Defaults to 'Issuer'. + type: string + name: + description: Name of the issuer being referred to. + type: string + required: + - name + type: object + profile: + description: |- + Profile allows requesting a certificate profile from the ACME server. + Supported profiles are listed by the server's ACME directory URL. + type: string + request: + description: |- + Certificate signing request bytes in DER encoding. + This will be used when finalizing the order. + This field must be set on the order. + format: byte + type: string + required: + - issuerRef + - request + type: object + status: + properties: + authorizations: + description: |- + 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. + items: + description: |- + 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. + properties: + challenges: + description: |- + 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. + items: + description: |- + 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. + properties: + token: + description: |- + 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: + description: |- + 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. + type: string + url: + description: |- + URL is the URL of this challenge. It can be used to retrieve additional + metadata about the Challenge from the ACME server. + type: string + required: + - token + - type + - url + type: object + type: array + x-kubernetes-list-type: atomic + identifier: + description: Identifier is the DNS name to be validated as part + of this authorization + type: string + initialState: + description: |- + 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. + enum: + - valid + - ready + - pending + - processing + - invalid + - expired + - errored + type: string + url: + description: URL is the URL of the Authorization that must be + completed + type: string + wildcard: + description: |- + 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'. + type: boolean + required: + - url + type: object + type: array + x-kubernetes-list-type: atomic + certificate: + description: |- + 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. + format: byte + type: string + failureTime: + description: |- + FailureTime stores the time that this order failed. + This is used to influence garbage collection and back-off. + format: date-time + type: string + finalizeURL: + description: |- + FinalizeURL of the Order. + This is used to obtain certificates for this order once it has been completed. + type: string + reason: + description: |- + Reason optionally provides more information about a why the order is in + the current state. + type: string + state: + description: |- + State contains the current state of this Order resource. + States 'success' and 'expired' are 'final' + enum: + - valid + - ready + - pending + - processing + - invalid + - expired + - errored + type: string + url: + description: |- + 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. + type: string + type: object + required: + - metadata + - spec + type: object + served: true + storage: true + subresources: + status: {} diff --git a/deploy/crds/cert-manager.io_certificaterequests.yaml b/deploy/crds/cert-manager.io_certificaterequests.yaml new file mode 100644 index 00000000000..bb24f443e69 --- /dev/null +++ b/deploy/crds/cert-manager.io_certificaterequests.yaml @@ -0,0 +1,319 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.19.0 + name: certificaterequests.cert-manager.io +spec: + group: cert-manager.io + names: + categories: + - cert-manager + kind: CertificateRequest + listKind: CertificateRequestList + plural: certificaterequests + shortNames: + - cr + - crs + singular: certificaterequest + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .status.conditions[?(@.type == "Approved")].status + name: Approved + type: string + - jsonPath: .status.conditions[?(@.type == "Denied")].status + name: Denied + type: string + - jsonPath: .status.conditions[?(@.type == "Ready")].status + name: Ready + type: string + - jsonPath: .spec.issuerRef.name + name: Issuer + type: string + - jsonPath: .spec.username + name: Requester + type: string + - jsonPath: .status.conditions[?(@.type == "Ready")].message + name: Status + priority: 1 + type: string + - description: CreationTimestamp is a timestamp representing the server time when + this object was created. It is not guaranteed to be set in happens-before + order across separate operations. Clients may not set this value. It is represented + in RFC3339 form and is in UTC. + jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1 + schema: + openAPIV3Schema: + description: |- + 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. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: |- + Specification of the desired state of the CertificateRequest resource. + https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status + properties: + duration: + description: |- + 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. + type: string + extra: + additionalProperties: + items: + type: string + type: array + description: |- + Extra contains extra attributes of the user that created the CertificateRequest. + Populated by the cert-manager webhook on creation and immutable. + type: object + groups: + description: |- + Groups contains group membership of the user that created the CertificateRequest. + Populated by the cert-manager webhook on creation and immutable. + items: + type: string + type: array + x-kubernetes-list-type: atomic + isCA: + description: |- + 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`. + type: boolean + issuerRef: + description: |- + 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. + properties: + group: + description: |- + Group of the issuer being referred to. + Defaults to 'cert-manager.io'. + type: string + kind: + description: |- + Kind of the issuer being referred to. + Defaults to 'Issuer'. + type: string + name: + description: Name of the issuer being referred to. + type: string + required: + - name + type: object + request: + description: |- + 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. + format: byte + type: string + uid: + description: |- + UID contains the uid of the user that created the CertificateRequest. + Populated by the cert-manager webhook on creation and immutable. + type: string + usages: + description: |- + 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`. + items: + description: |- + 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" + enum: + - 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 + type: string + type: array + x-kubernetes-list-type: atomic + username: + description: |- + Username contains the name of the user that created the CertificateRequest. + Populated by the cert-manager webhook on creation and immutable. + type: string + required: + - issuerRef + - request + type: object + status: + description: |- + 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 + properties: + ca: + description: |- + 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. + format: byte + type: string + certificate: + description: |- + 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. + format: byte + type: string + conditions: + description: |- + List of status conditions to indicate the status of a CertificateRequest. + Known condition types are `Ready`, `InvalidRequest`, `Approved` and `Denied`. + items: + description: CertificateRequestCondition contains condition information + for a CertificateRequest. + properties: + lastTransitionTime: + description: |- + LastTransitionTime is the timestamp corresponding to the last status + change of this condition. + format: date-time + type: string + message: + description: |- + Message is a human readable description of the details of the last + transition, complementing reason. + type: string + reason: + description: |- + Reason is a brief machine readable explanation for the condition's last + transition. + type: string + status: + description: Status of the condition, one of (`True`, `False`, + `Unknown`). + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: |- + Type of the condition, known values are (`Ready`, `InvalidRequest`, + `Approved`, `Denied`). + type: string + required: + - status + - type + type: object + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + failureTime: + description: |- + FailureTime stores the time that this CertificateRequest failed. This is + used to influence garbage collection and back-off. + format: date-time + type: string + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/deploy/crds/cert-manager.io_certificates.yaml b/deploy/crds/cert-manager.io_certificates.yaml new file mode 100644 index 00000000000..340b020df55 --- /dev/null +++ b/deploy/crds/cert-manager.io_certificates.yaml @@ -0,0 +1,827 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.19.0 + name: certificates.cert-manager.io +spec: + group: cert-manager.io + names: + categories: + - cert-manager + kind: Certificate + listKind: CertificateList + plural: certificates + shortNames: + - cert + - certs + singular: certificate + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .status.conditions[?(@.type == "Ready")].status + name: Ready + type: string + - jsonPath: .spec.secretName + name: Secret + type: string + - jsonPath: .spec.issuerRef.name + name: Issuer + priority: 1 + type: string + - jsonPath: .status.conditions[?(@.type == "Ready")].message + name: Status + priority: 1 + type: string + - description: CreationTimestamp is a timestamp representing the server time when + this object was created. It is not guaranteed to be set in happens-before + order across separate operations. Clients may not set this value. It is represented + in RFC3339 form and is in UTC. + jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1 + schema: + openAPIV3Schema: + description: |- + 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`). + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: |- + Specification of the desired state of the Certificate resource. + https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status + properties: + additionalOutputFormats: + description: |- + Defines extra output formats of the private key and signed certificate chain + to be written to this Certificate's target Secret. + items: + description: |- + CertificateAdditionalOutputFormat defines an additional output format of a + Certificate resource. These contain supplementary data formats of the signed + certificate chain and paired private key. + properties: + type: + description: |- + Type is the name of the format type that should be written to the + Certificate's target Secret. + enum: + - DER + - CombinedPEM + type: string + required: + - type + type: object + type: array + x-kubernetes-list-type: atomic + commonName: + description: |- + 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. + type: string + dnsNames: + description: Requested DNS subject alternative names. + items: + type: string + type: array + x-kubernetes-list-type: atomic + duration: + description: |- + 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. + type: string + emailAddresses: + description: Requested email subject alternative names. + items: + type: string + type: array + x-kubernetes-list-type: atomic + encodeUsagesInRequest: + description: |- + 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. + type: boolean + ipAddresses: + description: Requested IP address subject alternative names. + items: + type: string + type: array + x-kubernetes-list-type: atomic + isCA: + description: |- + 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`. + type: boolean + issuerRef: + description: |- + 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. + properties: + group: + description: |- + Group of the issuer being referred to. + Defaults to 'cert-manager.io'. + type: string + kind: + description: |- + Kind of the issuer being referred to. + Defaults to 'Issuer'. + type: string + name: + description: Name of the issuer being referred to. + type: string + required: + - name + type: object + keystores: + description: Additional keystore output formats to be stored in the + Certificate's Secret. + properties: + jks: + description: |- + JKS configures options for storing a JKS keystore in the + `spec.secretName` Secret resource. + properties: + alias: + description: |- + Alias specifies the alias of the key in the keystore, required by the JKS format. + If not provided, the default alias `certificate` will be used. + type: string + create: + description: |- + 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` or `password`. + 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 + type: boolean + password: + description: |- + Password provides a literal password used to encrypt the JKS keystore. + Mutually exclusive with passwordSecretRef. + One of password or passwordSecretRef must provide a password with a non-zero length. + type: string + passwordSecretRef: + description: |- + PasswordSecretRef is a reference to a non-empty key in a Secret resource + containing the password used to encrypt the JKS keystore. + Mutually exclusive with password. + One of password or passwordSecretRef must provide a password with a non-zero length. + properties: + key: + description: |- + 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. + type: string + name: + description: |- + Name of the resource being referred to. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + required: + - name + type: object + required: + - create + type: object + pkcs12: + description: |- + PKCS12 configures options for storing a PKCS12 keystore in the + `spec.secretName` Secret resource. + properties: + create: + description: |- + 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` or in `password`. + 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 + type: boolean + password: + description: |- + Password provides a literal password used to encrypt the PKCS#12 keystore. + Mutually exclusive with passwordSecretRef. + One of password or passwordSecretRef must provide a password with a non-zero length. + type: string + passwordSecretRef: + description: |- + PasswordSecretRef is a reference to a non-empty key in a Secret resource + containing the password used to encrypt the PKCS#12 keystore. + Mutually exclusive with password. + One of password or passwordSecretRef must provide a password with a non-zero length. + properties: + key: + description: |- + 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. + type: string + name: + description: |- + Name of the resource being referred to. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + required: + - name + type: object + profile: + description: |- + Profile specifies the key and certificate encryption algorithms and the HMAC algorithm + used to create the PKCS12 keystore. Default value is `LegacyRC2` for backward compatibility. + + If provided, allowed values are: + `LegacyRC2`: Deprecated. Not supported by default in OpenSSL 3 or Java 20. + `LegacyDES`: Less secure algorithm. Use this option for maximal compatibility. + `Modern2023`: Secure algorithm. Use this option in case you have to always use secure algorithms + (e.g., because of company policy). Please note that the security of the algorithm is not that important + in reality, because the unencrypted certificate and private key are also stored in the Secret. + enum: + - LegacyRC2 + - LegacyDES + - Modern2023 + type: string + required: + - create + type: object + type: object + literalSubject: + description: |- + 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. + type: string + nameConstraints: + description: |- + x.509 certificate NameConstraint extension which MUST NOT be used in a non-CA certificate. + More Info: https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.10 + + This is an Alpha Feature and is only enabled with the + `--feature-gates=NameConstraints=true` option set on both + the controller and webhook components. + properties: + critical: + description: if true then the name constraints are marked critical. + type: boolean + excluded: + description: |- + Excluded contains the constraints which must be disallowed. Any name matching a + restriction in the excluded field is invalid regardless + of information appearing in the permitted + properties: + dnsDomains: + description: DNSDomains is a list of DNS domains that are + permitted or excluded. + items: + type: string + type: array + x-kubernetes-list-type: atomic + emailAddresses: + description: EmailAddresses is a list of Email Addresses that + are permitted or excluded. + items: + type: string + type: array + x-kubernetes-list-type: atomic + ipRanges: + description: |- + IPRanges is a list of IP Ranges that are permitted or excluded. + This should be a valid CIDR notation. + items: + type: string + type: array + x-kubernetes-list-type: atomic + uriDomains: + description: URIDomains is a list of URI domains that are + permitted or excluded. + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + permitted: + description: Permitted contains the constraints in which the names + must be located. + properties: + dnsDomains: + description: DNSDomains is a list of DNS domains that are + permitted or excluded. + items: + type: string + type: array + x-kubernetes-list-type: atomic + emailAddresses: + description: EmailAddresses is a list of Email Addresses that + are permitted or excluded. + items: + type: string + type: array + x-kubernetes-list-type: atomic + ipRanges: + description: |- + IPRanges is a list of IP Ranges that are permitted or excluded. + This should be a valid CIDR notation. + items: + type: string + type: array + x-kubernetes-list-type: atomic + uriDomains: + description: URIDomains is a list of URI domains that are + permitted or excluded. + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + type: object + otherNames: + description: |- + `otherNames` is an escape hatch for SAN that allows any type. We currently restrict the support to string like otherNames, cf RFC 5280 p 37 + Any UTF8 String valued otherName can be passed with by setting the keys oid: x.x.x.x and UTF8Value: somevalue for `otherName`. + Most commonly this would be UPN set with oid: 1.3.6.1.4.1.311.20.2.3 + You should ensure that any OID passed is valid for the UTF8String type as we do not explicitly validate this. + items: + properties: + oid: + description: |- + OID is the object identifier for the otherName SAN. + The object identifier must be expressed as a dotted string, for + example, "1.2.840.113556.1.4.221". + type: string + utf8Value: + description: |- + utf8Value is the string value of the otherName SAN. + The utf8Value accepts any valid UTF8 string to set as value for the otherName SAN. + type: string + type: object + type: array + x-kubernetes-list-type: atomic + privateKey: + description: |- + Private key options. These include the key algorithm and size, the used + encoding and the rotation policy. + properties: + algorithm: + description: |- + 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. + enum: + - RSA + - ECDSA + - Ed25519 + type: string + encoding: + description: |- + 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. + enum: + - PKCS1 + - PKCS8 + type: string + rotationPolicy: + description: |- + 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 exist 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 `Always`. + The default was changed from `Never` to `Always` in cert-manager >=v1.18.0. + The new default can be disabled by setting the + `--feature-gates=DefaultPrivateKeyRotationPolicyAlways=false` option on + the controller component. + enum: + - Never + - Always + type: string + size: + description: |- + 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. + type: integer + type: object + renewBefore: + description: |- + 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 1/3 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. + Cannot be set if the `renewBeforePercentage` field is set. + type: string + renewBeforePercentage: + description: |- + `renewBeforePercentage` is like `renewBefore`, except it is a relative percentage + rather than an absolute duration. For example, if a certificate is valid for 60 + minutes, and `renewBeforePercentage=25`, cert-manager will begin to attempt to + renew the certificate 45 minutes after it was issued (i.e. when there are 15 + minutes (25%) 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. + + Value must be an integer in the range (0,100). The minimum effective + `renewBefore` derived from the `renewBeforePercentage` and `duration` fields is 5 + minutes. + Cannot be set if the `renewBefore` field is set. + format: int32 + type: integer + revisionHistoryLimit: + description: |- + 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. + Default value is `1`. + format: int32 + type: integer + secretName: + description: |- + 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. + type: string + secretTemplate: + description: |- + 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. + properties: + annotations: + additionalProperties: + type: string + description: Annotations is a key value map to be copied to the + target Kubernetes Secret. + type: object + labels: + additionalProperties: + type: string + description: Labels is a key value map to be copied to the target + Kubernetes Secret. + type: object + type: object + signatureAlgorithm: + description: |- + Signature algorithm to use. + Allowed values for RSA keys: SHA256WithRSA, SHA384WithRSA, SHA512WithRSA. + Allowed values for ECDSA keys: ECDSAWithSHA256, ECDSAWithSHA384, ECDSAWithSHA512. + Allowed values for Ed25519 keys: PureEd25519. + enum: + - SHA256WithRSA + - SHA384WithRSA + - SHA512WithRSA + - ECDSAWithSHA256 + - ECDSAWithSHA384 + - ECDSAWithSHA512 + - PureEd25519 + type: string + subject: + description: |- + 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. + properties: + countries: + description: Countries to be used on the Certificate. + items: + type: string + type: array + x-kubernetes-list-type: atomic + localities: + description: Cities to be used on the Certificate. + items: + type: string + type: array + x-kubernetes-list-type: atomic + organizationalUnits: + description: Organizational Units to be used on the Certificate. + items: + type: string + type: array + x-kubernetes-list-type: atomic + organizations: + description: Organizations to be used on the Certificate. + items: + type: string + type: array + x-kubernetes-list-type: atomic + postalCodes: + description: Postal codes to be used on the Certificate. + items: + type: string + type: array + x-kubernetes-list-type: atomic + provinces: + description: State/Provinces to be used on the Certificate. + items: + type: string + type: array + x-kubernetes-list-type: atomic + serialNumber: + description: Serial number to be used on the Certificate. + type: string + streetAddresses: + description: Street addresses to be used on the Certificate. + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + uris: + description: Requested URI subject alternative names. + items: + type: string + type: array + x-kubernetes-list-type: atomic + usages: + description: |- + 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`. + items: + description: |- + 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" + enum: + - 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 + type: string + type: array + x-kubernetes-list-type: atomic + required: + - issuerRef + - secretName + type: object + status: + description: |- + 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 + properties: + conditions: + description: |- + List of status conditions to indicate the status of certificates. + Known condition types are `Ready` and `Issuing`. + items: + description: CertificateCondition contains condition information + for a Certificate. + properties: + lastTransitionTime: + description: |- + LastTransitionTime is the timestamp corresponding to the last status + change of this condition. + format: date-time + type: string + message: + description: |- + Message is a human readable description of the details of the last + transition, complementing reason. + type: string + observedGeneration: + description: |- + 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. + format: int64 + type: integer + reason: + description: |- + Reason is a brief machine readable explanation for the condition's last + transition. + type: string + status: + description: Status of the condition, one of (`True`, `False`, + `Unknown`). + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: Type of the condition, known values are (`Ready`, + `Issuing`). + type: string + required: + - status + - type + type: object + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + failedIssuanceAttempts: + description: |- + 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). + type: integer + lastFailureTime: + description: |- + LastFailureTime is set only if the latest 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. + format: date-time + type: string + nextPrivateKeySecretName: + description: |- + 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. + type: string + notAfter: + description: |- + The expiration time of the certificate stored in the secret named + by this resource in `spec.secretName`. + format: date-time + type: string + notBefore: + description: |- + The time after which the certificate stored in the secret named + by this resource in `spec.secretName` is valid. + format: date-time + type: string + renewalTime: + description: |- + RenewalTime is the time at which the certificate will be next + renewed. + If not set, no upcoming renewal is scheduled. + format: date-time + type: string + revision: + description: |- + 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. + type: integer + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/deploy/crds/cert-manager.io_clusterissuers.yaml b/deploy/crds/cert-manager.io_clusterissuers.yaml new file mode 100644 index 00000000000..c90eadf8cf9 --- /dev/null +++ b/deploy/crds/cert-manager.io_clusterissuers.yaml @@ -0,0 +1,4068 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.19.0 + name: clusterissuers.cert-manager.io +spec: + group: cert-manager.io + names: + categories: + - cert-manager + kind: ClusterIssuer + listKind: ClusterIssuerList + plural: clusterissuers + shortNames: + - ciss + singular: clusterissuer + scope: Cluster + versions: + - additionalPrinterColumns: + - jsonPath: .status.conditions[?(@.type == "Ready")].status + name: Ready + type: string + - jsonPath: .status.conditions[?(@.type == "Ready")].message + name: Status + priority: 1 + type: string + - description: CreationTimestamp is a timestamp representing the server time when + this object was created. It is not guaranteed to be set in happens-before + order across separate operations. Clients may not set this value. It is represented + in RFC3339 form and is in UTC. + jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1 + schema: + openAPIV3Schema: + description: |- + 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. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: Desired state of the ClusterIssuer resource. + properties: + acme: + description: |- + ACME configures this issuer to communicate with a RFC8555 (ACME) server + to obtain signed x509 certificates. + properties: + caBundle: + description: |- + 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. + format: byte + type: string + disableAccountKeyGeneration: + description: |- + 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. + type: boolean + email: + description: |- + 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. + type: string + enableDurationFeature: + description: |- + 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. + type: boolean + externalAccountBinding: + description: |- + 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. + properties: + keyAlgorithm: + description: |- + 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. + enum: + - HS256 + - HS384 + - HS512 + type: string + keyID: + description: keyID is the ID of the CA key that the External + Account is bound to. + type: string + keySecretRef: + description: |- + 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. + properties: + key: + description: |- + 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. + type: string + name: + description: |- + Name of the resource being referred to. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + required: + - name + type: object + required: + - keyID + - keySecretRef + type: object + preferredChain: + description: |- + 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 cross-sign 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 combined set of + ACME default and alternative chains that has a root-most certificate with + this value as its issuer's commonname. + maxLength: 64 + type: string + privateKeySecretRef: + description: |- + 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. + properties: + key: + description: |- + 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. + type: string + name: + description: |- + Name of the resource being referred to. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + required: + - name + type: object + profile: + description: |- + Profile allows requesting a certificate profile from the ACME server. + Supported profiles are listed by the server's ACME directory URL. + type: string + server: + description: |- + 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. + type: string + skipTLSVerify: + description: |- + 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. + type: boolean + solvers: + description: |- + 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/ + items: + description: |- + 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. + properties: + dns01: + description: |- + Configures cert-manager to attempt to complete authorizations by + performing the DNS01 challenge flow. + properties: + acmeDNS: + description: |- + Use the 'ACME DNS' (https://github.com/joohoi/acme-dns) API to manage + DNS01 challenge records. + properties: + accountSecretRef: + description: |- + A reference to a specific 'key' within a Secret resource. + In some instances, `key` is a required field. + properties: + key: + description: |- + 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. + type: string + name: + description: |- + Name of the resource being referred to. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + required: + - name + type: object + host: + type: string + required: + - accountSecretRef + - host + type: object + akamai: + description: Use the Akamai DNS zone management API + to manage DNS01 challenge records. + properties: + accessTokenSecretRef: + description: |- + A reference to a specific 'key' within a Secret resource. + In some instances, `key` is a required field. + properties: + key: + description: |- + 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. + type: string + name: + description: |- + Name of the resource being referred to. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + required: + - name + type: object + clientSecretSecretRef: + description: |- + A reference to a specific 'key' within a Secret resource. + In some instances, `key` is a required field. + properties: + key: + description: |- + 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. + type: string + name: + description: |- + Name of the resource being referred to. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + required: + - name + type: object + clientTokenSecretRef: + description: |- + A reference to a specific 'key' within a Secret resource. + In some instances, `key` is a required field. + properties: + key: + description: |- + 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. + type: string + name: + description: |- + Name of the resource being referred to. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + required: + - name + type: object + serviceConsumerDomain: + type: string + required: + - accessTokenSecretRef + - clientSecretSecretRef + - clientTokenSecretRef + - serviceConsumerDomain + type: object + azureDNS: + description: Use the Microsoft Azure DNS API to manage + DNS01 challenge records. + properties: + clientID: + description: |- + Auth: Azure Service Principal: + The ClientID of the Azure Service Principal used to authenticate with Azure DNS. + If set, ClientSecret and TenantID must also be set. + type: string + clientSecretSecretRef: + description: |- + Auth: Azure Service Principal: + A reference to a Secret containing the password associated with the Service Principal. + If set, ClientID and TenantID must also be set. + properties: + key: + description: |- + 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. + type: string + name: + description: |- + Name of the resource being referred to. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + required: + - name + type: object + environment: + description: name of the Azure environment (default + AzurePublicCloud) + enum: + - AzurePublicCloud + - AzureChinaCloud + - AzureGermanCloud + - AzureUSGovernmentCloud + type: string + hostedZoneName: + description: name of the DNS zone that should be + used + type: string + managedIdentity: + description: |- + Auth: Azure Workload Identity or Azure Managed Service Identity: + Settings to enable Azure Workload Identity or Azure Managed Service Identity + If set, ClientID, ClientSecret and TenantID must not be set. + properties: + clientID: + description: client ID of the managed identity, + cannot be used at the same time as resourceID + type: string + resourceID: + description: |- + resource ID of the managed identity, cannot be used at the same time as clientID + Cannot be used for Azure Managed Service Identity + type: string + tenantID: + description: tenant ID of the managed identity, + cannot be used at the same time as resourceID + type: string + type: object + resourceGroupName: + description: resource group the DNS zone is located + in + type: string + subscriptionID: + description: ID of the Azure subscription + type: string + tenantID: + description: |- + Auth: Azure Service Principal: + The TenantID of the Azure Service Principal used to authenticate with Azure DNS. + If set, ClientID and ClientSecret must also be set. + type: string + required: + - resourceGroupName + - subscriptionID + type: object + cloudDNS: + description: Use the Google Cloud DNS API to manage + DNS01 challenge records. + properties: + hostedZoneName: + description: |- + 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. + type: string + project: + type: string + serviceAccountSecretRef: + description: |- + A reference to a specific 'key' within a Secret resource. + In some instances, `key` is a required field. + properties: + key: + description: |- + 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. + type: string + name: + description: |- + Name of the resource being referred to. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + required: + - name + type: object + required: + - project + type: object + cloudflare: + description: Use the Cloudflare API to manage DNS01 + challenge records. + properties: + apiKeySecretRef: + description: |- + 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. + properties: + key: + description: |- + 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. + type: string + name: + description: |- + Name of the resource being referred to. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + required: + - name + type: object + apiTokenSecretRef: + description: API token used to authenticate with + Cloudflare. + properties: + key: + description: |- + 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. + type: string + name: + description: |- + Name of the resource being referred to. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + required: + - name + type: object + email: + description: Email of the account, only required + when using API key based authentication. + type: string + type: object + cnameStrategy: + description: |- + CNAMEStrategy configures how the DNS01 provider should handle CNAME + records when found in DNS zones. + enum: + - None + - Follow + type: string + digitalocean: + description: Use the DigitalOcean DNS API to manage + DNS01 challenge records. + properties: + tokenSecretRef: + description: |- + A reference to a specific 'key' within a Secret resource. + In some instances, `key` is a required field. + properties: + key: + description: |- + 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. + type: string + name: + description: |- + Name of the resource being referred to. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + required: + - name + type: object + required: + - tokenSecretRef + type: object + rfc2136: + description: |- + Use RFC2136 ("Dynamic Updates in the Domain Name System") (https://datatracker.ietf.org/doc/rfc2136/) + to manage DNS01 challenge records. + properties: + nameserver: + description: |- + 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. + type: string + protocol: + description: Protocol to use for dynamic DNS update + queries. Valid values are (case-sensitive) ``TCP`` + and ``UDP``; ``UDP`` (default). + enum: + - TCP + - UDP + type: string + tsigAlgorithm: + description: |- + 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``. + type: string + tsigKeyName: + description: |- + The TSIG Key name configured in the DNS. + If ``tsigSecretSecretRef`` is defined, this field is required. + type: string + tsigSecretSecretRef: + description: |- + The name of the secret containing the TSIG value. + If ``tsigKeyName`` is defined, this field is required. + properties: + key: + description: |- + 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. + type: string + name: + description: |- + Name of the resource being referred to. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + required: + - name + type: object + required: + - nameserver + type: object + route53: + description: Use the AWS Route53 API to manage DNS01 + challenge records. + properties: + accessKeyID: + description: |- + 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 + type: string + accessKeyIDSecretRef: + description: |- + 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 + properties: + key: + description: |- + 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. + type: string + name: + description: |- + Name of the resource being referred to. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + required: + - name + type: object + auth: + description: Auth configures how cert-manager authenticates. + properties: + kubernetes: + description: |- + Kubernetes authenticates with Route53 using AssumeRoleWithWebIdentity + by passing a bound ServiceAccount token. + properties: + serviceAccountRef: + description: |- + A reference to a service account that will be used to request a bound + token (also known as "projected token"). To use this field, you must + configure an RBAC rule to let cert-manager request a token. + properties: + audiences: + description: |- + TokenAudiences is an optional list of audiences to include in the + token passed to AWS. The default token consisting of the issuer's namespace + and name is always included. + If unset the audience defaults to `sts.amazonaws.com`. + items: + type: string + type: array + x-kubernetes-list-type: atomic + name: + description: Name of the ServiceAccount + used to request a token. + type: string + required: + - name + type: object + required: + - serviceAccountRef + type: object + required: + - kubernetes + type: object + hostedZoneID: + description: If set, the provider will manage only + this zone in Route53 and will not do a lookup + using the route53:ListHostedZonesByName api call. + type: string + region: + description: |- + Override the AWS region. + + Route53 is a global service and does not have regional endpoints but the + region specified here (or via environment variables) is used as a hint to + help compute the correct AWS credential scope and partition when it + connects to Route53. See: + - [Amazon Route 53 endpoints and quotas](https://docs.aws.amazon.com/general/latest/gr/r53.html) + - [Global services](https://docs.aws.amazon.com/whitepapers/latest/aws-fault-isolation-boundaries/global-services.html) + + If you omit this region field, cert-manager will use the region from + AWS_REGION and AWS_DEFAULT_REGION environment variables, if they are set + in the cert-manager controller Pod. + + The `region` field is not needed if you use [IAM Roles for Service Accounts (IRSA)](https://docs.aws.amazon.com/eks/latest/userguide/iam-roles-for-service-accounts.html). + Instead an AWS_REGION environment variable is added to the cert-manager controller Pod by: + [Amazon EKS Pod Identity Webhook](https://github.com/aws/amazon-eks-pod-identity-webhook). + In this case this `region` field value is ignored. + + The `region` field is not needed if you use [EKS Pod Identities](https://docs.aws.amazon.com/eks/latest/userguide/pod-identities.html). + Instead an AWS_REGION environment variable is added to the cert-manager controller Pod by: + [Amazon EKS Pod Identity Agent](https://github.com/aws/eks-pod-identity-agent), + In this case this `region` field value is ignored. + type: string + role: + description: |- + 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 + type: string + secretAccessKeySecretRef: + description: |- + 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 + properties: + key: + description: |- + 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. + type: string + name: + description: |- + Name of the resource being referred to. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + required: + - name + type: object + type: object + webhook: + description: |- + Configure an external webhook based DNS01 challenge solver to manage + DNS01 challenge records. + properties: + config: + description: |- + 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. + x-kubernetes-preserve-unknown-fields: true + groupName: + description: |- + 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. + type: string + solverName: + description: |- + 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'. + type: string + required: + - groupName + - solverName + type: object + type: object + http01: + description: |- + 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. + properties: + gatewayHTTPRoute: + description: |- + 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. + properties: + labels: + additionalProperties: + type: string + description: |- + Custom labels that will be applied to HTTPRoutes created by cert-manager + while solving HTTP-01 challenges. + type: object + parentRefs: + description: |- + 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 + items: + description: |- + ParentReference identifies an API object (usually a Gateway) that can be considered + a parent of this resource (usually a route). There are two kinds of parent resources + with "Core" support: + + * Gateway (Gateway conformance profile) + * Service (Mesh conformance profile, ClusterIP Services only) + + This API may be extended in the future to support additional kinds of parent + resources. + + The API object must be valid in the cluster; the Group and Kind must + be registered in the cluster for this reference to be valid. + properties: + group: + default: gateway.networking.k8s.io + description: |- + Group is the group of the referent. + When unspecified, "gateway.networking.k8s.io" is inferred. + To set the core API group (such as for a "Service" kind referent), + Group must be explicitly set to "" (empty string). + + Support: Core + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Gateway + description: |- + Kind is kind of the referent. + + There are two kinds of parent resources with "Core" support: + + * Gateway (Gateway conformance profile) + * Service (Mesh conformance profile, ClusterIP Services only) + + Support for other resources is Implementation-Specific. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: |- + Name is the name of the referent. + + Support: Core + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the referent. When unspecified, this refers + to the local namespace of the Route. + + Note that there are specific rules for ParentRefs which cross namespace + boundaries. Cross-namespace references are only valid if they are explicitly + allowed by something in the namespace they are referring to. For example: + Gateway has the AllowedRoutes field, and ReferenceGrant provides a + generic way to enable any other kind of cross-namespace reference. + + + ParentRefs from a Route to a Service in the same namespace are "producer" + routes, which apply default routing rules to inbound connections from + any namespace to the Service. + + ParentRefs from a Route to a Service in a different namespace are + "consumer" routes, and these routing rules are only applied to outbound + connections originating from the same namespace as the Route, for which + the intended destination of the connections are a Service targeted as a + ParentRef of the Route. + + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: |- + Port is the network port this Route targets. It can be interpreted + differently based on the type of parent resource. + + When the parent resource is a Gateway, this targets all listeners + listening on the specified port that also support this kind of Route(and + select this Route). It's not recommended to set `Port` unless the + networking behaviors specified in a Route must apply to a specific port + as opposed to a listener(s) whose port(s) may be changed. When both Port + and SectionName are specified, the name and port of the selected listener + must match both specified values. + + + When the parent resource is a Service, this targets a specific port in the + Service spec. When both Port (experimental) and SectionName are specified, + the name and port of the selected port must match both specified values. + + + Implementations MAY choose to support other parent resources. + Implementations supporting other types of parent resources MUST clearly + document how/if Port is interpreted. + + For the purpose of status, an attachment is considered successful as + long as the parent resource accepts it partially. For example, Gateway + listeners can restrict which Routes can attach to them by Route kind, + namespace, or hostname. If 1 of 2 Gateway listeners accept attachment + from the referencing Route, the Route MUST be considered successfully + attached. If no Gateway listeners accept attachment from this Route, + the Route MUST be considered detached from the Gateway. + + Support: Extended + format: int32 + maximum: 65535 + minimum: 1 + type: integer + sectionName: + description: |- + SectionName is the name of a section within the target resource. In the + following resources, SectionName is interpreted as the following: + + * Gateway: Listener name. When both Port (experimental) and SectionName + are specified, the name and port of the selected listener must match + both specified values. + * Service: Port name. When both Port (experimental) and SectionName + are specified, the name and port of the selected listener must match + both specified values. + + Implementations MAY choose to support attaching Routes to other resources. + If that is the case, they MUST clearly document how SectionName is + interpreted. + + When unspecified (empty string), this will reference the entire resource. + For the purpose of status, an attachment is considered successful if at + least one section in the parent resource accepts it. For example, Gateway + listeners can restrict which Routes can attach to them by Route kind, + namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from + the referencing Route, the Route MUST be considered successfully + attached. If no Gateway listeners accept attachment from this Route, the + Route MUST be considered detached from the Gateway. + + Support: Core + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - name + type: object + type: array + x-kubernetes-list-type: atomic + podTemplate: + description: |- + Optional pod template used to configure the ACME challenge solver pods + used for HTTP01 challenges. + properties: + metadata: + description: |- + 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. + properties: + annotations: + additionalProperties: + type: string + description: Annotations that should be + added to the created ACME HTTP01 solver + pods. + type: object + labels: + additionalProperties: + type: string + description: Labels that should be added + to the created ACME HTTP01 solver pods. + type: object + type: object + spec: + description: |- + PodSpec defines overrides for the HTTP01 challenge solver pod. + Check ACMEChallengeSolverHTTP01IngressPodSpec to find out currently supported fields. + All other fields will be ignored. + properties: + affinity: + description: If specified, the pod's scheduling + constraints + properties: + nodeAffinity: + description: Describes node affinity + scheduling rules for the pod. + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node matches the corresponding matchExpressions; the + node(s) with the highest sum are the most preferred. + items: + description: |- + An empty preferred scheduling term matches all objects with implicit weight 0 + (i.e. it's a no-op). A null preferred scheduling term matches no objects (i.e. is also a no-op). + properties: + preference: + description: A node selector + term, associated with the + corresponding weight. + properties: + matchExpressions: + description: A list of + node selector requirements + by node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: The + label key that + the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: A list of + node selector requirements + by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: The + label key that + the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + weight: + description: Weight associated + with matching the corresponding + nodeSelectorTerm, in the + range 1-100. + format: int32 + type: integer + required: + - preference + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to an update), the system + may or may not try to eventually evict the pod from its node. + properties: + nodeSelectorTerms: + description: Required. A list + of node selector terms. The + terms are ORed. + items: + description: |- + A null or empty node selector term matches no objects. The requirements of + them are ANDed. + The TopologySelectorTerm type implements a subset of the NodeSelectorTerm. + properties: + matchExpressions: + description: A list of + node selector requirements + by node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: The + label key that + the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: A list of + node selector requirements + by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: The + label key that + the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + type: array + x-kubernetes-list-type: atomic + required: + - nodeSelectorTerms + type: object + x-kubernetes-map-type: atomic + type: object + podAffinity: + description: Describes pod affinity + scheduling rules (e.g. co-locate this + pod in the same node, zone, etc. as + some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: The weights of all + of the matched WeightedPodAffinityTerm + fields are added per-node to + find the most preferred node(s) + properties: + podAffinityTerm: + description: Required. A pod + affinity term, associated + with the corresponding weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: matchExpressions + is a list of label + selector requirements. + The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key + is the label + key that the + selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions + is a list of label + selector requirements. + The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key + is the label + key that the + selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key + is the label key + that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key + is the label key + that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + podAntiAffinity: + description: Describes pod anti-affinity + scheduling rules (e.g. avoid putting + this pod in the same node, zone, etc. + as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the anti-affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling anti-affinity expressions, etc.), + compute a sum by iterating through the elements of this field and subtracting + "weight" from the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: The weights of all + of the matched WeightedPodAffinityTerm + fields are added per-node to + find the most preferred node(s) + properties: + podAffinityTerm: + description: Required. A pod + affinity term, associated + with the corresponding weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: matchExpressions + is a list of label + selector requirements. + The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key + is the label + key that the + selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions + is a list of label + selector requirements. + The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key + is the label + key that the + selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the anti-affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the anti-affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key + is the label key + that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key + is the label key + that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + type: object + imagePullSecrets: + description: If specified, the pod's imagePullSecrets + items: + description: |- + LocalObjectReference contains enough information to let you locate the + referenced object inside the same namespace. + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + type: object + x-kubernetes-map-type: atomic + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + nodeSelector: + additionalProperties: + type: string + description: |- + 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/ + type: object + priorityClassName: + description: If specified, the pod's priorityClassName. + type: string + resources: + description: |- + If specified, the pod's resource requirements. + These values override the global resource configuration flags. + Note that when only specifying resource limits, ensure they are greater than or equal + to the corresponding global resource requests configured via controller flags + (--acme-http01-solver-resource-request-cpu, --acme-http01-solver-resource-request-memory). + Kubernetes will reject pod creation if limits are lower than requests, causing challenge failures. + properties: + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to the global values configured via controller flags. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + securityContext: + description: If specified, the pod's security + context + properties: + fsGroup: + description: |- + A special supplemental group that applies to all containers in a pod. + Some volume types allow the Kubelet to change the ownership of that volume + to be owned by the pod: + + 1. The owning GID will be the FSGroup + 2. The setgid bit is set (new files created in the volume will be owned by FSGroup) + 3. The permission bits are OR'd with rw-rw---- + + If unset, the Kubelet will not modify the ownership and permissions of any volume. + Note that this field cannot be set when spec.os.name is windows. + format: int64 + type: integer + fsGroupChangePolicy: + description: |- + fsGroupChangePolicy defines behavior of changing ownership and permission of the volume + before being exposed inside Pod. This field will only apply to + volume types which support fsGroup based ownership(and permissions). + It will have no effect on ephemeral volume types such as: secret, configmaps + and emptydir. + Valid values are "OnRootMismatch" and "Always". If not specified, "Always" is used. + Note that this field cannot be set when spec.os.name is windows. + type: string + runAsGroup: + description: |- + The GID to run the entrypoint of the container process. + Uses runtime default if unset. + May also be set in SecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext takes precedence + for that container. + Note that this field cannot be set when spec.os.name is windows. + format: int64 + type: integer + runAsNonRoot: + description: |- + Indicates that the container must run as a non-root user. + If true, the Kubelet will validate the image at runtime to ensure that it + does not run as UID 0 (root) and fail to start the container if it does. + If unset or false, no such validation will be performed. + May also be set in SecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext takes precedence. + type: boolean + runAsUser: + description: |- + The UID to run the entrypoint of the container process. + Defaults to user specified in image metadata if unspecified. + May also be set in SecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext takes precedence + for that container. + Note that this field cannot be set when spec.os.name is windows. + format: int64 + type: integer + seLinuxOptions: + description: |- + The SELinux context to be applied to all containers. + If unspecified, the container runtime will allocate a random SELinux context for each + container. May also be set in SecurityContext. If set in + both SecurityContext and PodSecurityContext, the value specified in SecurityContext + takes precedence for that container. + Note that this field cannot be set when spec.os.name is windows. + properties: + level: + description: Level is SELinux level + label that applies to the container. + type: string + role: + description: Role is a SELinux role + label that applies to the container. + type: string + type: + description: Type is a SELinux type + label that applies to the container. + type: string + user: + description: User is a SELinux user + label that applies to the container. + type: string + type: object + seccompProfile: + description: |- + The seccomp options to use by the containers in this pod. + Note that this field cannot be set when spec.os.name is windows. + properties: + localhostProfile: + description: |- + localhostProfile indicates a profile defined in a file on the node should be used. + The profile must be preconfigured on the node to work. + Must be a descending path, relative to the kubelet's configured seccomp profile location. + Must be set if type is "Localhost". Must NOT be set for any other type. + type: string + type: + description: |- + type indicates which kind of seccomp profile will be applied. + Valid options are: + + Localhost - a profile defined in a file on the node should be used. + RuntimeDefault - the container runtime default profile should be used. + Unconfined - no profile should be applied. + type: string + required: + - type + type: object + supplementalGroups: + description: |- + A list of groups applied to the first process run in each container, in addition + to the container's primary GID, the fsGroup (if specified), and group memberships + defined in the container image for the uid of the container process. If unspecified, + no additional groups are added to any container. Note that group memberships + defined in the container image for the uid of the container process are still effective, + even if they are not included in this list. + Note that this field cannot be set when spec.os.name is windows. + items: + format: int64 + type: integer + type: array + x-kubernetes-list-type: atomic + sysctls: + description: |- + Sysctls hold a list of namespaced sysctls used for the pod. Pods with unsupported + sysctls (by the container runtime) might fail to launch. + Note that this field cannot be set when spec.os.name is windows. + items: + description: Sysctl defines a kernel + parameter to be set + properties: + name: + description: Name of a property + to set + type: string + value: + description: Value of a property + to set + type: string + required: + - name + - value + type: object + type: array + x-kubernetes-list-type: atomic + type: object + serviceAccountName: + description: If specified, the pod's service + account + type: string + tolerations: + description: If specified, the pod's tolerations. + items: + description: |- + The pod this Toleration is attached to tolerates any taint that matches + the triple using the matching operator . + properties: + effect: + description: |- + Effect indicates the taint effect to match. Empty means match all taint effects. + When specified, allowed values are NoSchedule, PreferNoSchedule and NoExecute. + type: string + key: + description: |- + Key is the taint key that the toleration applies to. Empty means match all taint keys. + If the key is empty, operator must be Exists; this combination means to match all values and all keys. + type: string + operator: + description: |- + Operator represents a key's relationship to the value. + Valid operators are Exists and Equal. Defaults to Equal. + Exists is equivalent to wildcard for value, so that a pod can + tolerate all taints of a particular category. + type: string + tolerationSeconds: + description: |- + TolerationSeconds represents the period of time the toleration (which must be + of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default, + it is not set, which means tolerate the taint forever (do not evict). Zero and + negative values will be treated as 0 (evict immediately) by the system. + format: int64 + type: integer + value: + description: |- + Value is the taint value the toleration matches to. + If the operator is Exists, the value should be empty, otherwise just a regular string. + type: string + type: object + type: array + x-kubernetes-list-type: atomic + type: object + type: object + serviceType: + description: |- + Optional service type for Kubernetes solver service. Supported values + are NodePort or ClusterIP. If unset, defaults to NodePort. + type: string + type: object + ingress: + description: |- + 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. + properties: + class: + description: |- + 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. + type: string + ingressClassName: + description: |- + 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. + type: string + ingressTemplate: + description: |- + Optional ingress template used to configure the ACME challenge solver + ingress used for HTTP01 challenges. + properties: + metadata: + description: |- + 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. + properties: + annotations: + additionalProperties: + type: string + description: Annotations that should be + added to the created ACME HTTP01 solver + ingress. + type: object + labels: + additionalProperties: + type: string + description: Labels that should be added + to the created ACME HTTP01 solver ingress. + type: object + type: object + type: object + name: + description: |- + 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. + type: string + podTemplate: + description: |- + Optional pod template used to configure the ACME challenge solver pods + used for HTTP01 challenges. + properties: + metadata: + description: |- + 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. + properties: + annotations: + additionalProperties: + type: string + description: Annotations that should be + added to the created ACME HTTP01 solver + pods. + type: object + labels: + additionalProperties: + type: string + description: Labels that should be added + to the created ACME HTTP01 solver pods. + type: object + type: object + spec: + description: |- + PodSpec defines overrides for the HTTP01 challenge solver pod. + Check ACMEChallengeSolverHTTP01IngressPodSpec to find out currently supported fields. + All other fields will be ignored. + properties: + affinity: + description: If specified, the pod's scheduling + constraints + properties: + nodeAffinity: + description: Describes node affinity + scheduling rules for the pod. + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node matches the corresponding matchExpressions; the + node(s) with the highest sum are the most preferred. + items: + description: |- + An empty preferred scheduling term matches all objects with implicit weight 0 + (i.e. it's a no-op). A null preferred scheduling term matches no objects (i.e. is also a no-op). + properties: + preference: + description: A node selector + term, associated with the + corresponding weight. + properties: + matchExpressions: + description: A list of + node selector requirements + by node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: The + label key that + the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: A list of + node selector requirements + by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: The + label key that + the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + weight: + description: Weight associated + with matching the corresponding + nodeSelectorTerm, in the + range 1-100. + format: int32 + type: integer + required: + - preference + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to an update), the system + may or may not try to eventually evict the pod from its node. + properties: + nodeSelectorTerms: + description: Required. A list + of node selector terms. The + terms are ORed. + items: + description: |- + A null or empty node selector term matches no objects. The requirements of + them are ANDed. + The TopologySelectorTerm type implements a subset of the NodeSelectorTerm. + properties: + matchExpressions: + description: A list of + node selector requirements + by node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: The + label key that + the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: A list of + node selector requirements + by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: The + label key that + the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + type: array + x-kubernetes-list-type: atomic + required: + - nodeSelectorTerms + type: object + x-kubernetes-map-type: atomic + type: object + podAffinity: + description: Describes pod affinity + scheduling rules (e.g. co-locate this + pod in the same node, zone, etc. as + some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: The weights of all + of the matched WeightedPodAffinityTerm + fields are added per-node to + find the most preferred node(s) + properties: + podAffinityTerm: + description: Required. A pod + affinity term, associated + with the corresponding weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: matchExpressions + is a list of label + selector requirements. + The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key + is the label + key that the + selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions + is a list of label + selector requirements. + The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key + is the label + key that the + selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key + is the label key + that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key + is the label key + that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + podAntiAffinity: + description: Describes pod anti-affinity + scheduling rules (e.g. avoid putting + this pod in the same node, zone, etc. + as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the anti-affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling anti-affinity expressions, etc.), + compute a sum by iterating through the elements of this field and subtracting + "weight" from the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: The weights of all + of the matched WeightedPodAffinityTerm + fields are added per-node to + find the most preferred node(s) + properties: + podAffinityTerm: + description: Required. A pod + affinity term, associated + with the corresponding weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: matchExpressions + is a list of label + selector requirements. + The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key + is the label + key that the + selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions + is a list of label + selector requirements. + The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key + is the label + key that the + selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the anti-affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the anti-affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key + is the label key + that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key + is the label key + that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + type: object + imagePullSecrets: + description: If specified, the pod's imagePullSecrets + items: + description: |- + LocalObjectReference contains enough information to let you locate the + referenced object inside the same namespace. + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + type: object + x-kubernetes-map-type: atomic + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + nodeSelector: + additionalProperties: + type: string + description: |- + 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/ + type: object + priorityClassName: + description: If specified, the pod's priorityClassName. + type: string + resources: + description: |- + If specified, the pod's resource requirements. + These values override the global resource configuration flags. + Note that when only specifying resource limits, ensure they are greater than or equal + to the corresponding global resource requests configured via controller flags + (--acme-http01-solver-resource-request-cpu, --acme-http01-solver-resource-request-memory). + Kubernetes will reject pod creation if limits are lower than requests, causing challenge failures. + properties: + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to the global values configured via controller flags. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + securityContext: + description: If specified, the pod's security + context + properties: + fsGroup: + description: |- + A special supplemental group that applies to all containers in a pod. + Some volume types allow the Kubelet to change the ownership of that volume + to be owned by the pod: + + 1. The owning GID will be the FSGroup + 2. The setgid bit is set (new files created in the volume will be owned by FSGroup) + 3. The permission bits are OR'd with rw-rw---- + + If unset, the Kubelet will not modify the ownership and permissions of any volume. + Note that this field cannot be set when spec.os.name is windows. + format: int64 + type: integer + fsGroupChangePolicy: + description: |- + fsGroupChangePolicy defines behavior of changing ownership and permission of the volume + before being exposed inside Pod. This field will only apply to + volume types which support fsGroup based ownership(and permissions). + It will have no effect on ephemeral volume types such as: secret, configmaps + and emptydir. + Valid values are "OnRootMismatch" and "Always". If not specified, "Always" is used. + Note that this field cannot be set when spec.os.name is windows. + type: string + runAsGroup: + description: |- + The GID to run the entrypoint of the container process. + Uses runtime default if unset. + May also be set in SecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext takes precedence + for that container. + Note that this field cannot be set when spec.os.name is windows. + format: int64 + type: integer + runAsNonRoot: + description: |- + Indicates that the container must run as a non-root user. + If true, the Kubelet will validate the image at runtime to ensure that it + does not run as UID 0 (root) and fail to start the container if it does. + If unset or false, no such validation will be performed. + May also be set in SecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext takes precedence. + type: boolean + runAsUser: + description: |- + The UID to run the entrypoint of the container process. + Defaults to user specified in image metadata if unspecified. + May also be set in SecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext takes precedence + for that container. + Note that this field cannot be set when spec.os.name is windows. + format: int64 + type: integer + seLinuxOptions: + description: |- + The SELinux context to be applied to all containers. + If unspecified, the container runtime will allocate a random SELinux context for each + container. May also be set in SecurityContext. If set in + both SecurityContext and PodSecurityContext, the value specified in SecurityContext + takes precedence for that container. + Note that this field cannot be set when spec.os.name is windows. + properties: + level: + description: Level is SELinux level + label that applies to the container. + type: string + role: + description: Role is a SELinux role + label that applies to the container. + type: string + type: + description: Type is a SELinux type + label that applies to the container. + type: string + user: + description: User is a SELinux user + label that applies to the container. + type: string + type: object + seccompProfile: + description: |- + The seccomp options to use by the containers in this pod. + Note that this field cannot be set when spec.os.name is windows. + properties: + localhostProfile: + description: |- + localhostProfile indicates a profile defined in a file on the node should be used. + The profile must be preconfigured on the node to work. + Must be a descending path, relative to the kubelet's configured seccomp profile location. + Must be set if type is "Localhost". Must NOT be set for any other type. + type: string + type: + description: |- + type indicates which kind of seccomp profile will be applied. + Valid options are: + + Localhost - a profile defined in a file on the node should be used. + RuntimeDefault - the container runtime default profile should be used. + Unconfined - no profile should be applied. + type: string + required: + - type + type: object + supplementalGroups: + description: |- + A list of groups applied to the first process run in each container, in addition + to the container's primary GID, the fsGroup (if specified), and group memberships + defined in the container image for the uid of the container process. If unspecified, + no additional groups are added to any container. Note that group memberships + defined in the container image for the uid of the container process are still effective, + even if they are not included in this list. + Note that this field cannot be set when spec.os.name is windows. + items: + format: int64 + type: integer + type: array + x-kubernetes-list-type: atomic + sysctls: + description: |- + Sysctls hold a list of namespaced sysctls used for the pod. Pods with unsupported + sysctls (by the container runtime) might fail to launch. + Note that this field cannot be set when spec.os.name is windows. + items: + description: Sysctl defines a kernel + parameter to be set + properties: + name: + description: Name of a property + to set + type: string + value: + description: Value of a property + to set + type: string + required: + - name + - value + type: object + type: array + x-kubernetes-list-type: atomic + type: object + serviceAccountName: + description: If specified, the pod's service + account + type: string + tolerations: + description: If specified, the pod's tolerations. + items: + description: |- + The pod this Toleration is attached to tolerates any taint that matches + the triple using the matching operator . + properties: + effect: + description: |- + Effect indicates the taint effect to match. Empty means match all taint effects. + When specified, allowed values are NoSchedule, PreferNoSchedule and NoExecute. + type: string + key: + description: |- + Key is the taint key that the toleration applies to. Empty means match all taint keys. + If the key is empty, operator must be Exists; this combination means to match all values and all keys. + type: string + operator: + description: |- + Operator represents a key's relationship to the value. + Valid operators are Exists and Equal. Defaults to Equal. + Exists is equivalent to wildcard for value, so that a pod can + tolerate all taints of a particular category. + type: string + tolerationSeconds: + description: |- + TolerationSeconds represents the period of time the toleration (which must be + of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default, + it is not set, which means tolerate the taint forever (do not evict). Zero and + negative values will be treated as 0 (evict immediately) by the system. + format: int64 + type: integer + value: + description: |- + Value is the taint value the toleration matches to. + If the operator is Exists, the value should be empty, otherwise just a regular string. + type: string + type: object + type: array + x-kubernetes-list-type: atomic + type: object + type: object + serviceType: + description: |- + Optional service type for Kubernetes solver service. Supported values + are NodePort or ClusterIP. If unset, defaults to NodePort. + type: string + type: object + type: object + selector: + description: |- + 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. + properties: + dnsNames: + description: |- + 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. + items: + type: string + type: array + x-kubernetes-list-type: atomic + dnsZones: + description: |- + 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. + items: + type: string + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + A label selector that is used to refine the set of certificate's that + this challenge solver will apply to. + type: object + type: object + type: object + type: array + x-kubernetes-list-type: atomic + required: + - privateKeySecretRef + - server + type: object + ca: + description: |- + 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. + properties: + crlDistributionPoints: + description: |- + 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. + items: + type: string + type: array + x-kubernetes-list-type: atomic + issuingCertificateURLs: + description: |- + IssuingCertificateURLs is a list of URLs which this issuer should embed into certificates + it creates. See https://www.rfc-editor.org/rfc/rfc5280#section-4.2.2.1 for more details. + As an example, such a URL might be "http://ca.domain.com/ca.crt". + items: + type: string + type: array + x-kubernetes-list-type: atomic + ocspServers: + description: |- + 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". + items: + type: string + type: array + x-kubernetes-list-type: atomic + secretName: + description: |- + SecretName is the name of the secret used to sign Certificates issued + by this Issuer. + type: string + required: + - secretName + type: object + selfSigned: + description: |- + SelfSigned configures this issuer to 'self sign' certificates using the + private key used to create the CertificateRequest object. + properties: + crlDistributionPoints: + description: |- + 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. + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + vault: + description: |- + Vault configures this issuer to sign certificates using a HashiCorp Vault + PKI backend. + properties: + auth: + description: Auth configures how cert-manager authenticates with + the Vault server. + properties: + appRole: + description: |- + AppRole authenticates with Vault using the App Role auth mechanism, + with the role and secret stored in a Kubernetes Secret resource. + properties: + path: + description: |- + Path where the App Role authentication backend is mounted in Vault, e.g: + "approle" + type: string + roleId: + description: |- + RoleID configured in the App Role authentication backend when setting + up the authentication backend in Vault. + type: string + secretRef: + description: |- + 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. + properties: + key: + description: |- + 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. + type: string + name: + description: |- + Name of the resource being referred to. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + required: + - name + type: object + required: + - path + - roleId + - secretRef + type: object + clientCertificate: + description: |- + ClientCertificate authenticates with Vault by presenting a client + certificate during the request's TLS handshake. + Works only when using HTTPS protocol. + properties: + mountPath: + description: |- + 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/cert" will be used. + type: string + name: + description: |- + Name of the certificate role to authenticate against. + If not set, matching any certificate role, if available. + type: string + secretName: + description: |- + Reference to Kubernetes Secret of type "kubernetes.io/tls" (hence containing + tls.crt and tls.key) used to authenticate to Vault using TLS client + authentication. + type: string + type: object + kubernetes: + description: |- + Kubernetes authenticates with Vault by passing the ServiceAccount + token stored in the named Secret resource to the Vault server. + properties: + mountPath: + description: |- + 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. + type: string + role: + description: |- + A required field containing the Vault Role to assume. A Role binds a + Kubernetes ServiceAccount with a set of Vault policies. + type: string + secretRef: + description: |- + The required Secret field containing a Kubernetes ServiceAccount JWT used + for authenticating with Vault. Use of 'ambient credentials' is not + supported. + properties: + key: + description: |- + 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. + type: string + name: + description: |- + Name of the resource being referred to. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + required: + - name + type: object + serviceAccountRef: + description: |- + 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. + properties: + audiences: + description: |- + TokenAudiences is an optional list of extra audiences to include in the token passed to Vault. The default token + consisting of the issuer's namespace and name is always included. + items: + type: string + type: array + x-kubernetes-list-type: atomic + name: + description: Name of the ServiceAccount used to request + a token. + type: string + required: + - name + type: object + required: + - role + type: object + tokenSecretRef: + description: TokenSecretRef authenticates with Vault by presenting + a token. + properties: + key: + description: |- + 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. + type: string + name: + description: |- + Name of the resource being referred to. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + required: + - name + type: object + type: object + caBundle: + description: |- + 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. + format: byte + type: string + caBundleSecretRef: + description: |- + 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'. + properties: + key: + description: |- + 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. + type: string + name: + description: |- + Name of the resource being referred to. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + required: + - name + type: object + clientCertSecretRef: + description: |- + Reference to a Secret containing a PEM-encoded Client Certificate to use when the + Vault server requires mTLS. + properties: + key: + description: |- + 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. + type: string + name: + description: |- + Name of the resource being referred to. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + required: + - name + type: object + clientKeySecretRef: + description: |- + Reference to a Secret containing a PEM-encoded Client Private Key to use when the + Vault server requires mTLS. + properties: + key: + description: |- + 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. + type: string + name: + description: |- + Name of the resource being referred to. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + required: + - name + type: object + namespace: + description: |- + 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 + type: string + path: + description: |- + Path is the mount path of the Vault PKI backend's `sign` endpoint, e.g: + "my_pki_mount/sign/my-role-name". + type: string + server: + description: 'Server is the connection address for the Vault server, + e.g: "https://vault.example.com:8200".' + type: string + serverName: + description: |- + ServerName is used to verify the hostname on the returned certificates + by the Vault server. + type: string + required: + - auth + - path + - server + type: object + venafi: + description: |- + Venafi configures this issuer to sign certificates using a Venafi TPP + or Venafi Cloud policy zone. + properties: + cloud: + description: |- + Cloud specifies the Venafi cloud configuration settings. + Only one of TPP or Cloud may be specified. + properties: + apiTokenSecretRef: + description: APITokenSecretRef is a secret key selector for + the Venafi Cloud API token. + properties: + key: + description: |- + 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. + type: string + name: + description: |- + Name of the resource being referred to. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + required: + - name + type: object + url: + description: |- + URL is the base URL for Venafi Cloud. + Defaults to "https://api.venafi.cloud/". + type: string + required: + - apiTokenSecretRef + type: object + tpp: + description: |- + TPP specifies Trust Protection Platform configuration settings. + Only one of TPP or Cloud may be specified. + properties: + caBundle: + description: |- + 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. + format: byte + type: string + caBundleSecretRef: + description: |- + Reference to a Secret containing a 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. Mutually exclusive with CABundle. + If neither CABundle nor CABundleSecretRef is defined, the certificate bundle in + the cert-manager controller container is used to validate the TLS connection. + properties: + key: + description: |- + 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. + type: string + name: + description: |- + Name of the resource being referred to. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + required: + - name + type: object + credentialsRef: + description: |- + CredentialsRef is a reference to a Secret containing the Venafi TPP API credentials. + The secret must contain the key 'access-token' for the Access Token Authentication, + or two keys, 'username' and 'password' for the API Keys Authentication. + properties: + name: + description: |- + Name of the resource being referred to. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + required: + - name + type: object + url: + description: |- + URL is the base URL for the vedsdk endpoint of the Venafi TPP instance, + for example: "https://tpp.example.com/vedsdk". + type: string + required: + - credentialsRef + - url + type: object + zone: + description: |- + 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. + type: string + required: + - zone + type: object + type: object + status: + description: Status of the ClusterIssuer. This is set and managed automatically. + properties: + acme: + description: |- + ACME specific status options. + This field should only be set if the Issuer is configured to use an ACME + server to issue certificates. + properties: + lastPrivateKeyHash: + description: |- + 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 + type: string + lastRegisteredEmail: + 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 + type: string + uri: + description: |- + URI is the unique account identifier, which can also be used to retrieve + account details from the CA + type: string + type: object + conditions: + description: |- + List of status conditions to indicate the status of a CertificateRequest. + Known condition types are `Ready`. + items: + description: IssuerCondition contains condition information for + an Issuer. + properties: + lastTransitionTime: + description: |- + LastTransitionTime is the timestamp corresponding to the last status + change of this condition. + format: date-time + type: string + message: + description: |- + Message is a human readable description of the details of the last + transition, complementing reason. + type: string + observedGeneration: + description: |- + 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. + format: int64 + type: integer + reason: + description: |- + Reason is a brief machine readable explanation for the condition's last + transition. + type: string + status: + description: Status of the condition, one of (`True`, `False`, + `Unknown`). + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: Type of the condition, known values are (`Ready`). + type: string + required: + - status + - type + type: object + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} diff --git a/deploy/crds/cert-manager.io_issuers.yaml b/deploy/crds/cert-manager.io_issuers.yaml new file mode 100644 index 00000000000..1a47c509ff7 --- /dev/null +++ b/deploy/crds/cert-manager.io_issuers.yaml @@ -0,0 +1,4067 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.19.0 + name: issuers.cert-manager.io +spec: + group: cert-manager.io + names: + categories: + - cert-manager + kind: Issuer + listKind: IssuerList + plural: issuers + shortNames: + - iss + singular: issuer + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .status.conditions[?(@.type == "Ready")].status + name: Ready + type: string + - jsonPath: .status.conditions[?(@.type == "Ready")].message + name: Status + priority: 1 + type: string + - description: CreationTimestamp is a timestamp representing the server time when + this object was created. It is not guaranteed to be set in happens-before + order across separate operations. Clients may not set this value. It is represented + in RFC3339 form and is in UTC. + jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1 + schema: + openAPIV3Schema: + description: |- + 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. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: Desired state of the Issuer resource. + properties: + acme: + description: |- + ACME configures this issuer to communicate with a RFC8555 (ACME) server + to obtain signed x509 certificates. + properties: + caBundle: + description: |- + 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. + format: byte + type: string + disableAccountKeyGeneration: + description: |- + 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. + type: boolean + email: + description: |- + 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. + type: string + enableDurationFeature: + description: |- + 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. + type: boolean + externalAccountBinding: + description: |- + 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. + properties: + keyAlgorithm: + description: |- + 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. + enum: + - HS256 + - HS384 + - HS512 + type: string + keyID: + description: keyID is the ID of the CA key that the External + Account is bound to. + type: string + keySecretRef: + description: |- + 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. + properties: + key: + description: |- + 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. + type: string + name: + description: |- + Name of the resource being referred to. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + required: + - name + type: object + required: + - keyID + - keySecretRef + type: object + preferredChain: + description: |- + 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 cross-sign 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 combined set of + ACME default and alternative chains that has a root-most certificate with + this value as its issuer's commonname. + maxLength: 64 + type: string + privateKeySecretRef: + description: |- + 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. + properties: + key: + description: |- + 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. + type: string + name: + description: |- + Name of the resource being referred to. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + required: + - name + type: object + profile: + description: |- + Profile allows requesting a certificate profile from the ACME server. + Supported profiles are listed by the server's ACME directory URL. + type: string + server: + description: |- + 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. + type: string + skipTLSVerify: + description: |- + 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. + type: boolean + solvers: + description: |- + 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/ + items: + description: |- + 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. + properties: + dns01: + description: |- + Configures cert-manager to attempt to complete authorizations by + performing the DNS01 challenge flow. + properties: + acmeDNS: + description: |- + Use the 'ACME DNS' (https://github.com/joohoi/acme-dns) API to manage + DNS01 challenge records. + properties: + accountSecretRef: + description: |- + A reference to a specific 'key' within a Secret resource. + In some instances, `key` is a required field. + properties: + key: + description: |- + 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. + type: string + name: + description: |- + Name of the resource being referred to. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + required: + - name + type: object + host: + type: string + required: + - accountSecretRef + - host + type: object + akamai: + description: Use the Akamai DNS zone management API + to manage DNS01 challenge records. + properties: + accessTokenSecretRef: + description: |- + A reference to a specific 'key' within a Secret resource. + In some instances, `key` is a required field. + properties: + key: + description: |- + 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. + type: string + name: + description: |- + Name of the resource being referred to. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + required: + - name + type: object + clientSecretSecretRef: + description: |- + A reference to a specific 'key' within a Secret resource. + In some instances, `key` is a required field. + properties: + key: + description: |- + 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. + type: string + name: + description: |- + Name of the resource being referred to. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + required: + - name + type: object + clientTokenSecretRef: + description: |- + A reference to a specific 'key' within a Secret resource. + In some instances, `key` is a required field. + properties: + key: + description: |- + 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. + type: string + name: + description: |- + Name of the resource being referred to. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + required: + - name + type: object + serviceConsumerDomain: + type: string + required: + - accessTokenSecretRef + - clientSecretSecretRef + - clientTokenSecretRef + - serviceConsumerDomain + type: object + azureDNS: + description: Use the Microsoft Azure DNS API to manage + DNS01 challenge records. + properties: + clientID: + description: |- + Auth: Azure Service Principal: + The ClientID of the Azure Service Principal used to authenticate with Azure DNS. + If set, ClientSecret and TenantID must also be set. + type: string + clientSecretSecretRef: + description: |- + Auth: Azure Service Principal: + A reference to a Secret containing the password associated with the Service Principal. + If set, ClientID and TenantID must also be set. + properties: + key: + description: |- + 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. + type: string + name: + description: |- + Name of the resource being referred to. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + required: + - name + type: object + environment: + description: name of the Azure environment (default + AzurePublicCloud) + enum: + - AzurePublicCloud + - AzureChinaCloud + - AzureGermanCloud + - AzureUSGovernmentCloud + type: string + hostedZoneName: + description: name of the DNS zone that should be + used + type: string + managedIdentity: + description: |- + Auth: Azure Workload Identity or Azure Managed Service Identity: + Settings to enable Azure Workload Identity or Azure Managed Service Identity + If set, ClientID, ClientSecret and TenantID must not be set. + properties: + clientID: + description: client ID of the managed identity, + cannot be used at the same time as resourceID + type: string + resourceID: + description: |- + resource ID of the managed identity, cannot be used at the same time as clientID + Cannot be used for Azure Managed Service Identity + type: string + tenantID: + description: tenant ID of the managed identity, + cannot be used at the same time as resourceID + type: string + type: object + resourceGroupName: + description: resource group the DNS zone is located + in + type: string + subscriptionID: + description: ID of the Azure subscription + type: string + tenantID: + description: |- + Auth: Azure Service Principal: + The TenantID of the Azure Service Principal used to authenticate with Azure DNS. + If set, ClientID and ClientSecret must also be set. + type: string + required: + - resourceGroupName + - subscriptionID + type: object + cloudDNS: + description: Use the Google Cloud DNS API to manage + DNS01 challenge records. + properties: + hostedZoneName: + description: |- + 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. + type: string + project: + type: string + serviceAccountSecretRef: + description: |- + A reference to a specific 'key' within a Secret resource. + In some instances, `key` is a required field. + properties: + key: + description: |- + 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. + type: string + name: + description: |- + Name of the resource being referred to. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + required: + - name + type: object + required: + - project + type: object + cloudflare: + description: Use the Cloudflare API to manage DNS01 + challenge records. + properties: + apiKeySecretRef: + description: |- + 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. + properties: + key: + description: |- + 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. + type: string + name: + description: |- + Name of the resource being referred to. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + required: + - name + type: object + apiTokenSecretRef: + description: API token used to authenticate with + Cloudflare. + properties: + key: + description: |- + 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. + type: string + name: + description: |- + Name of the resource being referred to. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + required: + - name + type: object + email: + description: Email of the account, only required + when using API key based authentication. + type: string + type: object + cnameStrategy: + description: |- + CNAMEStrategy configures how the DNS01 provider should handle CNAME + records when found in DNS zones. + enum: + - None + - Follow + type: string + digitalocean: + description: Use the DigitalOcean DNS API to manage + DNS01 challenge records. + properties: + tokenSecretRef: + description: |- + A reference to a specific 'key' within a Secret resource. + In some instances, `key` is a required field. + properties: + key: + description: |- + 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. + type: string + name: + description: |- + Name of the resource being referred to. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + required: + - name + type: object + required: + - tokenSecretRef + type: object + rfc2136: + description: |- + Use RFC2136 ("Dynamic Updates in the Domain Name System") (https://datatracker.ietf.org/doc/rfc2136/) + to manage DNS01 challenge records. + properties: + nameserver: + description: |- + 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. + type: string + protocol: + description: Protocol to use for dynamic DNS update + queries. Valid values are (case-sensitive) ``TCP`` + and ``UDP``; ``UDP`` (default). + enum: + - TCP + - UDP + type: string + tsigAlgorithm: + description: |- + 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``. + type: string + tsigKeyName: + description: |- + The TSIG Key name configured in the DNS. + If ``tsigSecretSecretRef`` is defined, this field is required. + type: string + tsigSecretSecretRef: + description: |- + The name of the secret containing the TSIG value. + If ``tsigKeyName`` is defined, this field is required. + properties: + key: + description: |- + 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. + type: string + name: + description: |- + Name of the resource being referred to. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + required: + - name + type: object + required: + - nameserver + type: object + route53: + description: Use the AWS Route53 API to manage DNS01 + challenge records. + properties: + accessKeyID: + description: |- + 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 + type: string + accessKeyIDSecretRef: + description: |- + 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 + properties: + key: + description: |- + 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. + type: string + name: + description: |- + Name of the resource being referred to. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + required: + - name + type: object + auth: + description: Auth configures how cert-manager authenticates. + properties: + kubernetes: + description: |- + Kubernetes authenticates with Route53 using AssumeRoleWithWebIdentity + by passing a bound ServiceAccount token. + properties: + serviceAccountRef: + description: |- + A reference to a service account that will be used to request a bound + token (also known as "projected token"). To use this field, you must + configure an RBAC rule to let cert-manager request a token. + properties: + audiences: + description: |- + TokenAudiences is an optional list of audiences to include in the + token passed to AWS. The default token consisting of the issuer's namespace + and name is always included. + If unset the audience defaults to `sts.amazonaws.com`. + items: + type: string + type: array + x-kubernetes-list-type: atomic + name: + description: Name of the ServiceAccount + used to request a token. + type: string + required: + - name + type: object + required: + - serviceAccountRef + type: object + required: + - kubernetes + type: object + hostedZoneID: + description: If set, the provider will manage only + this zone in Route53 and will not do a lookup + using the route53:ListHostedZonesByName api call. + type: string + region: + description: |- + Override the AWS region. + + Route53 is a global service and does not have regional endpoints but the + region specified here (or via environment variables) is used as a hint to + help compute the correct AWS credential scope and partition when it + connects to Route53. See: + - [Amazon Route 53 endpoints and quotas](https://docs.aws.amazon.com/general/latest/gr/r53.html) + - [Global services](https://docs.aws.amazon.com/whitepapers/latest/aws-fault-isolation-boundaries/global-services.html) + + If you omit this region field, cert-manager will use the region from + AWS_REGION and AWS_DEFAULT_REGION environment variables, if they are set + in the cert-manager controller Pod. + + The `region` field is not needed if you use [IAM Roles for Service Accounts (IRSA)](https://docs.aws.amazon.com/eks/latest/userguide/iam-roles-for-service-accounts.html). + Instead an AWS_REGION environment variable is added to the cert-manager controller Pod by: + [Amazon EKS Pod Identity Webhook](https://github.com/aws/amazon-eks-pod-identity-webhook). + In this case this `region` field value is ignored. + + The `region` field is not needed if you use [EKS Pod Identities](https://docs.aws.amazon.com/eks/latest/userguide/pod-identities.html). + Instead an AWS_REGION environment variable is added to the cert-manager controller Pod by: + [Amazon EKS Pod Identity Agent](https://github.com/aws/eks-pod-identity-agent), + In this case this `region` field value is ignored. + type: string + role: + description: |- + 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 + type: string + secretAccessKeySecretRef: + description: |- + 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 + properties: + key: + description: |- + 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. + type: string + name: + description: |- + Name of the resource being referred to. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + required: + - name + type: object + type: object + webhook: + description: |- + Configure an external webhook based DNS01 challenge solver to manage + DNS01 challenge records. + properties: + config: + description: |- + 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. + x-kubernetes-preserve-unknown-fields: true + groupName: + description: |- + 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. + type: string + solverName: + description: |- + 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'. + type: string + required: + - groupName + - solverName + type: object + type: object + http01: + description: |- + 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. + properties: + gatewayHTTPRoute: + description: |- + 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. + properties: + labels: + additionalProperties: + type: string + description: |- + Custom labels that will be applied to HTTPRoutes created by cert-manager + while solving HTTP-01 challenges. + type: object + parentRefs: + description: |- + 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 + items: + description: |- + ParentReference identifies an API object (usually a Gateway) that can be considered + a parent of this resource (usually a route). There are two kinds of parent resources + with "Core" support: + + * Gateway (Gateway conformance profile) + * Service (Mesh conformance profile, ClusterIP Services only) + + This API may be extended in the future to support additional kinds of parent + resources. + + The API object must be valid in the cluster; the Group and Kind must + be registered in the cluster for this reference to be valid. + properties: + group: + default: gateway.networking.k8s.io + description: |- + Group is the group of the referent. + When unspecified, "gateway.networking.k8s.io" is inferred. + To set the core API group (such as for a "Service" kind referent), + Group must be explicitly set to "" (empty string). + + Support: Core + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Gateway + description: |- + Kind is kind of the referent. + + There are two kinds of parent resources with "Core" support: + + * Gateway (Gateway conformance profile) + * Service (Mesh conformance profile, ClusterIP Services only) + + Support for other resources is Implementation-Specific. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: |- + Name is the name of the referent. + + Support: Core + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the referent. When unspecified, this refers + to the local namespace of the Route. + + Note that there are specific rules for ParentRefs which cross namespace + boundaries. Cross-namespace references are only valid if they are explicitly + allowed by something in the namespace they are referring to. For example: + Gateway has the AllowedRoutes field, and ReferenceGrant provides a + generic way to enable any other kind of cross-namespace reference. + + + ParentRefs from a Route to a Service in the same namespace are "producer" + routes, which apply default routing rules to inbound connections from + any namespace to the Service. + + ParentRefs from a Route to a Service in a different namespace are + "consumer" routes, and these routing rules are only applied to outbound + connections originating from the same namespace as the Route, for which + the intended destination of the connections are a Service targeted as a + ParentRef of the Route. + + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: |- + Port is the network port this Route targets. It can be interpreted + differently based on the type of parent resource. + + When the parent resource is a Gateway, this targets all listeners + listening on the specified port that also support this kind of Route(and + select this Route). It's not recommended to set `Port` unless the + networking behaviors specified in a Route must apply to a specific port + as opposed to a listener(s) whose port(s) may be changed. When both Port + and SectionName are specified, the name and port of the selected listener + must match both specified values. + + + When the parent resource is a Service, this targets a specific port in the + Service spec. When both Port (experimental) and SectionName are specified, + the name and port of the selected port must match both specified values. + + + Implementations MAY choose to support other parent resources. + Implementations supporting other types of parent resources MUST clearly + document how/if Port is interpreted. + + For the purpose of status, an attachment is considered successful as + long as the parent resource accepts it partially. For example, Gateway + listeners can restrict which Routes can attach to them by Route kind, + namespace, or hostname. If 1 of 2 Gateway listeners accept attachment + from the referencing Route, the Route MUST be considered successfully + attached. If no Gateway listeners accept attachment from this Route, + the Route MUST be considered detached from the Gateway. + + Support: Extended + format: int32 + maximum: 65535 + minimum: 1 + type: integer + sectionName: + description: |- + SectionName is the name of a section within the target resource. In the + following resources, SectionName is interpreted as the following: + + * Gateway: Listener name. When both Port (experimental) and SectionName + are specified, the name and port of the selected listener must match + both specified values. + * Service: Port name. When both Port (experimental) and SectionName + are specified, the name and port of the selected listener must match + both specified values. + + Implementations MAY choose to support attaching Routes to other resources. + If that is the case, they MUST clearly document how SectionName is + interpreted. + + When unspecified (empty string), this will reference the entire resource. + For the purpose of status, an attachment is considered successful if at + least one section in the parent resource accepts it. For example, Gateway + listeners can restrict which Routes can attach to them by Route kind, + namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from + the referencing Route, the Route MUST be considered successfully + attached. If no Gateway listeners accept attachment from this Route, the + Route MUST be considered detached from the Gateway. + + Support: Core + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - name + type: object + type: array + x-kubernetes-list-type: atomic + podTemplate: + description: |- + Optional pod template used to configure the ACME challenge solver pods + used for HTTP01 challenges. + properties: + metadata: + description: |- + 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. + properties: + annotations: + additionalProperties: + type: string + description: Annotations that should be + added to the created ACME HTTP01 solver + pods. + type: object + labels: + additionalProperties: + type: string + description: Labels that should be added + to the created ACME HTTP01 solver pods. + type: object + type: object + spec: + description: |- + PodSpec defines overrides for the HTTP01 challenge solver pod. + Check ACMEChallengeSolverHTTP01IngressPodSpec to find out currently supported fields. + All other fields will be ignored. + properties: + affinity: + description: If specified, the pod's scheduling + constraints + properties: + nodeAffinity: + description: Describes node affinity + scheduling rules for the pod. + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node matches the corresponding matchExpressions; the + node(s) with the highest sum are the most preferred. + items: + description: |- + An empty preferred scheduling term matches all objects with implicit weight 0 + (i.e. it's a no-op). A null preferred scheduling term matches no objects (i.e. is also a no-op). + properties: + preference: + description: A node selector + term, associated with the + corresponding weight. + properties: + matchExpressions: + description: A list of + node selector requirements + by node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: The + label key that + the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: A list of + node selector requirements + by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: The + label key that + the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + weight: + description: Weight associated + with matching the corresponding + nodeSelectorTerm, in the + range 1-100. + format: int32 + type: integer + required: + - preference + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to an update), the system + may or may not try to eventually evict the pod from its node. + properties: + nodeSelectorTerms: + description: Required. A list + of node selector terms. The + terms are ORed. + items: + description: |- + A null or empty node selector term matches no objects. The requirements of + them are ANDed. + The TopologySelectorTerm type implements a subset of the NodeSelectorTerm. + properties: + matchExpressions: + description: A list of + node selector requirements + by node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: The + label key that + the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: A list of + node selector requirements + by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: The + label key that + the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + type: array + x-kubernetes-list-type: atomic + required: + - nodeSelectorTerms + type: object + x-kubernetes-map-type: atomic + type: object + podAffinity: + description: Describes pod affinity + scheduling rules (e.g. co-locate this + pod in the same node, zone, etc. as + some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: The weights of all + of the matched WeightedPodAffinityTerm + fields are added per-node to + find the most preferred node(s) + properties: + podAffinityTerm: + description: Required. A pod + affinity term, associated + with the corresponding weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: matchExpressions + is a list of label + selector requirements. + The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key + is the label + key that the + selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions + is a list of label + selector requirements. + The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key + is the label + key that the + selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key + is the label key + that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key + is the label key + that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + podAntiAffinity: + description: Describes pod anti-affinity + scheduling rules (e.g. avoid putting + this pod in the same node, zone, etc. + as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the anti-affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling anti-affinity expressions, etc.), + compute a sum by iterating through the elements of this field and subtracting + "weight" from the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: The weights of all + of the matched WeightedPodAffinityTerm + fields are added per-node to + find the most preferred node(s) + properties: + podAffinityTerm: + description: Required. A pod + affinity term, associated + with the corresponding weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: matchExpressions + is a list of label + selector requirements. + The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key + is the label + key that the + selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions + is a list of label + selector requirements. + The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key + is the label + key that the + selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the anti-affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the anti-affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key + is the label key + that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key + is the label key + that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + type: object + imagePullSecrets: + description: If specified, the pod's imagePullSecrets + items: + description: |- + LocalObjectReference contains enough information to let you locate the + referenced object inside the same namespace. + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + type: object + x-kubernetes-map-type: atomic + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + nodeSelector: + additionalProperties: + type: string + description: |- + 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/ + type: object + priorityClassName: + description: If specified, the pod's priorityClassName. + type: string + resources: + description: |- + If specified, the pod's resource requirements. + These values override the global resource configuration flags. + Note that when only specifying resource limits, ensure they are greater than or equal + to the corresponding global resource requests configured via controller flags + (--acme-http01-solver-resource-request-cpu, --acme-http01-solver-resource-request-memory). + Kubernetes will reject pod creation if limits are lower than requests, causing challenge failures. + properties: + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to the global values configured via controller flags. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + securityContext: + description: If specified, the pod's security + context + properties: + fsGroup: + description: |- + A special supplemental group that applies to all containers in a pod. + Some volume types allow the Kubelet to change the ownership of that volume + to be owned by the pod: + + 1. The owning GID will be the FSGroup + 2. The setgid bit is set (new files created in the volume will be owned by FSGroup) + 3. The permission bits are OR'd with rw-rw---- + + If unset, the Kubelet will not modify the ownership and permissions of any volume. + Note that this field cannot be set when spec.os.name is windows. + format: int64 + type: integer + fsGroupChangePolicy: + description: |- + fsGroupChangePolicy defines behavior of changing ownership and permission of the volume + before being exposed inside Pod. This field will only apply to + volume types which support fsGroup based ownership(and permissions). + It will have no effect on ephemeral volume types such as: secret, configmaps + and emptydir. + Valid values are "OnRootMismatch" and "Always". If not specified, "Always" is used. + Note that this field cannot be set when spec.os.name is windows. + type: string + runAsGroup: + description: |- + The GID to run the entrypoint of the container process. + Uses runtime default if unset. + May also be set in SecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext takes precedence + for that container. + Note that this field cannot be set when spec.os.name is windows. + format: int64 + type: integer + runAsNonRoot: + description: |- + Indicates that the container must run as a non-root user. + If true, the Kubelet will validate the image at runtime to ensure that it + does not run as UID 0 (root) and fail to start the container if it does. + If unset or false, no such validation will be performed. + May also be set in SecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext takes precedence. + type: boolean + runAsUser: + description: |- + The UID to run the entrypoint of the container process. + Defaults to user specified in image metadata if unspecified. + May also be set in SecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext takes precedence + for that container. + Note that this field cannot be set when spec.os.name is windows. + format: int64 + type: integer + seLinuxOptions: + description: |- + The SELinux context to be applied to all containers. + If unspecified, the container runtime will allocate a random SELinux context for each + container. May also be set in SecurityContext. If set in + both SecurityContext and PodSecurityContext, the value specified in SecurityContext + takes precedence for that container. + Note that this field cannot be set when spec.os.name is windows. + properties: + level: + description: Level is SELinux level + label that applies to the container. + type: string + role: + description: Role is a SELinux role + label that applies to the container. + type: string + type: + description: Type is a SELinux type + label that applies to the container. + type: string + user: + description: User is a SELinux user + label that applies to the container. + type: string + type: object + seccompProfile: + description: |- + The seccomp options to use by the containers in this pod. + Note that this field cannot be set when spec.os.name is windows. + properties: + localhostProfile: + description: |- + localhostProfile indicates a profile defined in a file on the node should be used. + The profile must be preconfigured on the node to work. + Must be a descending path, relative to the kubelet's configured seccomp profile location. + Must be set if type is "Localhost". Must NOT be set for any other type. + type: string + type: + description: |- + type indicates which kind of seccomp profile will be applied. + Valid options are: + + Localhost - a profile defined in a file on the node should be used. + RuntimeDefault - the container runtime default profile should be used. + Unconfined - no profile should be applied. + type: string + required: + - type + type: object + supplementalGroups: + description: |- + A list of groups applied to the first process run in each container, in addition + to the container's primary GID, the fsGroup (if specified), and group memberships + defined in the container image for the uid of the container process. If unspecified, + no additional groups are added to any container. Note that group memberships + defined in the container image for the uid of the container process are still effective, + even if they are not included in this list. + Note that this field cannot be set when spec.os.name is windows. + items: + format: int64 + type: integer + type: array + x-kubernetes-list-type: atomic + sysctls: + description: |- + Sysctls hold a list of namespaced sysctls used for the pod. Pods with unsupported + sysctls (by the container runtime) might fail to launch. + Note that this field cannot be set when spec.os.name is windows. + items: + description: Sysctl defines a kernel + parameter to be set + properties: + name: + description: Name of a property + to set + type: string + value: + description: Value of a property + to set + type: string + required: + - name + - value + type: object + type: array + x-kubernetes-list-type: atomic + type: object + serviceAccountName: + description: If specified, the pod's service + account + type: string + tolerations: + description: If specified, the pod's tolerations. + items: + description: |- + The pod this Toleration is attached to tolerates any taint that matches + the triple using the matching operator . + properties: + effect: + description: |- + Effect indicates the taint effect to match. Empty means match all taint effects. + When specified, allowed values are NoSchedule, PreferNoSchedule and NoExecute. + type: string + key: + description: |- + Key is the taint key that the toleration applies to. Empty means match all taint keys. + If the key is empty, operator must be Exists; this combination means to match all values and all keys. + type: string + operator: + description: |- + Operator represents a key's relationship to the value. + Valid operators are Exists and Equal. Defaults to Equal. + Exists is equivalent to wildcard for value, so that a pod can + tolerate all taints of a particular category. + type: string + tolerationSeconds: + description: |- + TolerationSeconds represents the period of time the toleration (which must be + of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default, + it is not set, which means tolerate the taint forever (do not evict). Zero and + negative values will be treated as 0 (evict immediately) by the system. + format: int64 + type: integer + value: + description: |- + Value is the taint value the toleration matches to. + If the operator is Exists, the value should be empty, otherwise just a regular string. + type: string + type: object + type: array + x-kubernetes-list-type: atomic + type: object + type: object + serviceType: + description: |- + Optional service type for Kubernetes solver service. Supported values + are NodePort or ClusterIP. If unset, defaults to NodePort. + type: string + type: object + ingress: + description: |- + 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. + properties: + class: + description: |- + 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. + type: string + ingressClassName: + description: |- + 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. + type: string + ingressTemplate: + description: |- + Optional ingress template used to configure the ACME challenge solver + ingress used for HTTP01 challenges. + properties: + metadata: + description: |- + 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. + properties: + annotations: + additionalProperties: + type: string + description: Annotations that should be + added to the created ACME HTTP01 solver + ingress. + type: object + labels: + additionalProperties: + type: string + description: Labels that should be added + to the created ACME HTTP01 solver ingress. + type: object + type: object + type: object + name: + description: |- + 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. + type: string + podTemplate: + description: |- + Optional pod template used to configure the ACME challenge solver pods + used for HTTP01 challenges. + properties: + metadata: + description: |- + 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. + properties: + annotations: + additionalProperties: + type: string + description: Annotations that should be + added to the created ACME HTTP01 solver + pods. + type: object + labels: + additionalProperties: + type: string + description: Labels that should be added + to the created ACME HTTP01 solver pods. + type: object + type: object + spec: + description: |- + PodSpec defines overrides for the HTTP01 challenge solver pod. + Check ACMEChallengeSolverHTTP01IngressPodSpec to find out currently supported fields. + All other fields will be ignored. + properties: + affinity: + description: If specified, the pod's scheduling + constraints + properties: + nodeAffinity: + description: Describes node affinity + scheduling rules for the pod. + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node matches the corresponding matchExpressions; the + node(s) with the highest sum are the most preferred. + items: + description: |- + An empty preferred scheduling term matches all objects with implicit weight 0 + (i.e. it's a no-op). A null preferred scheduling term matches no objects (i.e. is also a no-op). + properties: + preference: + description: A node selector + term, associated with the + corresponding weight. + properties: + matchExpressions: + description: A list of + node selector requirements + by node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: The + label key that + the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: A list of + node selector requirements + by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: The + label key that + the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + weight: + description: Weight associated + with matching the corresponding + nodeSelectorTerm, in the + range 1-100. + format: int32 + type: integer + required: + - preference + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to an update), the system + may or may not try to eventually evict the pod from its node. + properties: + nodeSelectorTerms: + description: Required. A list + of node selector terms. The + terms are ORed. + items: + description: |- + A null or empty node selector term matches no objects. The requirements of + them are ANDed. + The TopologySelectorTerm type implements a subset of the NodeSelectorTerm. + properties: + matchExpressions: + description: A list of + node selector requirements + by node's labels. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: The + label key that + the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchFields: + description: A list of + node selector requirements + by node's fields. + items: + description: |- + A node selector requirement is a selector that contains values, a key, and an operator + that relates the key and values. + properties: + key: + description: The + label key that + the selector applies + to. + type: string + operator: + description: |- + Represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. + type: string + values: + description: |- + An array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. If the operator is Gt or Lt, the values + array must have a single element, which will be interpreted as an integer. + This array is replaced during a strategic merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + type: object + x-kubernetes-map-type: atomic + type: array + x-kubernetes-list-type: atomic + required: + - nodeSelectorTerms + type: object + x-kubernetes-map-type: atomic + type: object + podAffinity: + description: Describes pod affinity + scheduling rules (e.g. co-locate this + pod in the same node, zone, etc. as + some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling affinity expressions, etc.), + compute a sum by iterating through the elements of this field and adding + "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: The weights of all + of the matched WeightedPodAffinityTerm + fields are added per-node to + find the most preferred node(s) + properties: + podAffinityTerm: + description: Required. A pod + affinity term, associated + with the corresponding weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: matchExpressions + is a list of label + selector requirements. + The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key + is the label + key that the + selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions + is a list of label + selector requirements. + The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key + is the label + key that the + selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key + is the label key + that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key + is the label key + that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + podAntiAffinity: + description: Describes pod anti-affinity + scheduling rules (e.g. avoid putting + this pod in the same node, zone, etc. + as some other pod(s)). + properties: + preferredDuringSchedulingIgnoredDuringExecution: + description: |- + The scheduler will prefer to schedule pods to nodes that satisfy + the anti-affinity expressions specified by this field, but it may choose + a node that violates one or more of the expressions. The node that is + most preferred is the one with the greatest sum of weights, i.e. + for each node that meets all of the scheduling requirements (resource + request, requiredDuringScheduling anti-affinity expressions, etc.), + compute a sum by iterating through the elements of this field and subtracting + "weight" from the sum if the node has pods which matches the corresponding podAffinityTerm; the + node(s) with the highest sum are the most preferred. + items: + description: The weights of all + of the matched WeightedPodAffinityTerm + fields are added per-node to + find the most preferred node(s) + properties: + podAffinityTerm: + description: Required. A pod + affinity term, associated + with the corresponding weight. + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: matchExpressions + is a list of label + selector requirements. + The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key + is the label + key that the + selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions + is a list of label + selector requirements. + The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key + is the label + key that the + selector applies + to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + weight: + description: |- + weight associated with matching the corresponding podAffinityTerm, + in the range 1-100. + format: int32 + type: integer + required: + - podAffinityTerm + - weight + type: object + type: array + x-kubernetes-list-type: atomic + requiredDuringSchedulingIgnoredDuringExecution: + description: |- + If the anti-affinity requirements specified by this field are not met at + scheduling time, the pod will not be scheduled onto the node. + If the anti-affinity requirements specified by this field cease to be met + at some point during pod execution (e.g. due to a pod label update), the + system may or may not try to eventually evict the pod from its node. + When there are multiple elements, the lists of nodes corresponding to each + podAffinityTerm are intersected, i.e. all terms must be satisfied. + items: + description: |- + Defines a set of pods (namely those matching the labelSelector + relative to the given namespace(s)) that this pod should be + co-located (affinity) or not co-located (anti-affinity) with, + where co-located is defined as running on a node whose value of + the label with key matches that of any node on which + a pod of the set of pods is running + properties: + labelSelector: + description: |- + A label query over a set of resources, in this case pods. + If it's null, this PodAffinityTerm matches with no Pods. + properties: + matchExpressions: + description: matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key + is the label key + that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + matchLabelKeys: + description: |- + MatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both matchLabelKeys and labelSelector. + Also, matchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + mismatchLabelKeys: + description: |- + MismatchLabelKeys is a set of pod label keys to select which pods will + be taken into consideration. The keys are used to lookup values from the + incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` + to select the group of existing pods which pods will be taken into consideration + for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming + pod labels will be ignored. The default value is empty. + The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. + Also, mismatchLabelKeys cannot be set when labelSelector isn't set. + items: + type: string + type: array + x-kubernetes-list-type: atomic + namespaceSelector: + description: |- + A label query over the set of namespaces that the term applies to. + The term is applied to the union of the namespaces selected by this field + and the ones listed in the namespaces field. + null selector and null or empty namespaces list means "this pod's namespace". + An empty selector ({}) matches all namespaces. + properties: + matchExpressions: + description: matchExpressions + is a list of label selector + requirements. The requirements + are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key + is the label key + that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + namespaces: + description: |- + namespaces specifies a static list of namespace names that the term applies to. + The term is applied to the union of the namespaces listed in this field + and the ones selected by namespaceSelector. + null or empty namespaces list and null namespaceSelector means "this pod's namespace". + items: + type: string + type: array + x-kubernetes-list-type: atomic + topologyKey: + description: |- + This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching + the labelSelector in the specified namespaces, where co-located is defined as running on a node + whose value of the label with key topologyKey matches that of any node on which any of the + selected pods is running. + Empty topologyKey is not allowed. + type: string + required: + - topologyKey + type: object + type: array + x-kubernetes-list-type: atomic + type: object + type: object + imagePullSecrets: + description: If specified, the pod's imagePullSecrets + items: + description: |- + LocalObjectReference contains enough information to let you locate the + referenced object inside the same namespace. + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + type: object + x-kubernetes-map-type: atomic + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + nodeSelector: + additionalProperties: + type: string + description: |- + 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/ + type: object + priorityClassName: + description: If specified, the pod's priorityClassName. + type: string + resources: + description: |- + If specified, the pod's resource requirements. + These values override the global resource configuration flags. + Note that when only specifying resource limits, ensure they are greater than or equal + to the corresponding global resource requests configured via controller flags + (--acme-http01-solver-resource-request-cpu, --acme-http01-solver-resource-request-memory). + Kubernetes will reject pod creation if limits are lower than requests, causing challenge failures. + properties: + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Limits describes the maximum amount of compute resources allowed. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: |- + Requests describes the minimum amount of compute resources required. + If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + otherwise to the global values configured via controller flags. Requests cannot exceed Limits. + More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + type: object + type: object + securityContext: + description: If specified, the pod's security + context + properties: + fsGroup: + description: |- + A special supplemental group that applies to all containers in a pod. + Some volume types allow the Kubelet to change the ownership of that volume + to be owned by the pod: + + 1. The owning GID will be the FSGroup + 2. The setgid bit is set (new files created in the volume will be owned by FSGroup) + 3. The permission bits are OR'd with rw-rw---- + + If unset, the Kubelet will not modify the ownership and permissions of any volume. + Note that this field cannot be set when spec.os.name is windows. + format: int64 + type: integer + fsGroupChangePolicy: + description: |- + fsGroupChangePolicy defines behavior of changing ownership and permission of the volume + before being exposed inside Pod. This field will only apply to + volume types which support fsGroup based ownership(and permissions). + It will have no effect on ephemeral volume types such as: secret, configmaps + and emptydir. + Valid values are "OnRootMismatch" and "Always". If not specified, "Always" is used. + Note that this field cannot be set when spec.os.name is windows. + type: string + runAsGroup: + description: |- + The GID to run the entrypoint of the container process. + Uses runtime default if unset. + May also be set in SecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext takes precedence + for that container. + Note that this field cannot be set when spec.os.name is windows. + format: int64 + type: integer + runAsNonRoot: + description: |- + Indicates that the container must run as a non-root user. + If true, the Kubelet will validate the image at runtime to ensure that it + does not run as UID 0 (root) and fail to start the container if it does. + If unset or false, no such validation will be performed. + May also be set in SecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext takes precedence. + type: boolean + runAsUser: + description: |- + The UID to run the entrypoint of the container process. + Defaults to user specified in image metadata if unspecified. + May also be set in SecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext takes precedence + for that container. + Note that this field cannot be set when spec.os.name is windows. + format: int64 + type: integer + seLinuxOptions: + description: |- + The SELinux context to be applied to all containers. + If unspecified, the container runtime will allocate a random SELinux context for each + container. May also be set in SecurityContext. If set in + both SecurityContext and PodSecurityContext, the value specified in SecurityContext + takes precedence for that container. + Note that this field cannot be set when spec.os.name is windows. + properties: + level: + description: Level is SELinux level + label that applies to the container. + type: string + role: + description: Role is a SELinux role + label that applies to the container. + type: string + type: + description: Type is a SELinux type + label that applies to the container. + type: string + user: + description: User is a SELinux user + label that applies to the container. + type: string + type: object + seccompProfile: + description: |- + The seccomp options to use by the containers in this pod. + Note that this field cannot be set when spec.os.name is windows. + properties: + localhostProfile: + description: |- + localhostProfile indicates a profile defined in a file on the node should be used. + The profile must be preconfigured on the node to work. + Must be a descending path, relative to the kubelet's configured seccomp profile location. + Must be set if type is "Localhost". Must NOT be set for any other type. + type: string + type: + description: |- + type indicates which kind of seccomp profile will be applied. + Valid options are: + + Localhost - a profile defined in a file on the node should be used. + RuntimeDefault - the container runtime default profile should be used. + Unconfined - no profile should be applied. + type: string + required: + - type + type: object + supplementalGroups: + description: |- + A list of groups applied to the first process run in each container, in addition + to the container's primary GID, the fsGroup (if specified), and group memberships + defined in the container image for the uid of the container process. If unspecified, + no additional groups are added to any container. Note that group memberships + defined in the container image for the uid of the container process are still effective, + even if they are not included in this list. + Note that this field cannot be set when spec.os.name is windows. + items: + format: int64 + type: integer + type: array + x-kubernetes-list-type: atomic + sysctls: + description: |- + Sysctls hold a list of namespaced sysctls used for the pod. Pods with unsupported + sysctls (by the container runtime) might fail to launch. + Note that this field cannot be set when spec.os.name is windows. + items: + description: Sysctl defines a kernel + parameter to be set + properties: + name: + description: Name of a property + to set + type: string + value: + description: Value of a property + to set + type: string + required: + - name + - value + type: object + type: array + x-kubernetes-list-type: atomic + type: object + serviceAccountName: + description: If specified, the pod's service + account + type: string + tolerations: + description: If specified, the pod's tolerations. + items: + description: |- + The pod this Toleration is attached to tolerates any taint that matches + the triple using the matching operator . + properties: + effect: + description: |- + Effect indicates the taint effect to match. Empty means match all taint effects. + When specified, allowed values are NoSchedule, PreferNoSchedule and NoExecute. + type: string + key: + description: |- + Key is the taint key that the toleration applies to. Empty means match all taint keys. + If the key is empty, operator must be Exists; this combination means to match all values and all keys. + type: string + operator: + description: |- + Operator represents a key's relationship to the value. + Valid operators are Exists and Equal. Defaults to Equal. + Exists is equivalent to wildcard for value, so that a pod can + tolerate all taints of a particular category. + type: string + tolerationSeconds: + description: |- + TolerationSeconds represents the period of time the toleration (which must be + of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default, + it is not set, which means tolerate the taint forever (do not evict). Zero and + negative values will be treated as 0 (evict immediately) by the system. + format: int64 + type: integer + value: + description: |- + Value is the taint value the toleration matches to. + If the operator is Exists, the value should be empty, otherwise just a regular string. + type: string + type: object + type: array + x-kubernetes-list-type: atomic + type: object + type: object + serviceType: + description: |- + Optional service type for Kubernetes solver service. Supported values + are NodePort or ClusterIP. If unset, defaults to NodePort. + type: string + type: object + type: object + selector: + description: |- + 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. + properties: + dnsNames: + description: |- + 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. + items: + type: string + type: array + x-kubernetes-list-type: atomic + dnsZones: + description: |- + 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. + items: + type: string + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + A label selector that is used to refine the set of certificate's that + this challenge solver will apply to. + type: object + type: object + type: object + type: array + x-kubernetes-list-type: atomic + required: + - privateKeySecretRef + - server + type: object + ca: + description: |- + 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. + properties: + crlDistributionPoints: + description: |- + 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. + items: + type: string + type: array + x-kubernetes-list-type: atomic + issuingCertificateURLs: + description: |- + IssuingCertificateURLs is a list of URLs which this issuer should embed into certificates + it creates. See https://www.rfc-editor.org/rfc/rfc5280#section-4.2.2.1 for more details. + As an example, such a URL might be "http://ca.domain.com/ca.crt". + items: + type: string + type: array + x-kubernetes-list-type: atomic + ocspServers: + description: |- + 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". + items: + type: string + type: array + x-kubernetes-list-type: atomic + secretName: + description: |- + SecretName is the name of the secret used to sign Certificates issued + by this Issuer. + type: string + required: + - secretName + type: object + selfSigned: + description: |- + SelfSigned configures this issuer to 'self sign' certificates using the + private key used to create the CertificateRequest object. + properties: + crlDistributionPoints: + description: |- + 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. + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object + vault: + description: |- + Vault configures this issuer to sign certificates using a HashiCorp Vault + PKI backend. + properties: + auth: + description: Auth configures how cert-manager authenticates with + the Vault server. + properties: + appRole: + description: |- + AppRole authenticates with Vault using the App Role auth mechanism, + with the role and secret stored in a Kubernetes Secret resource. + properties: + path: + description: |- + Path where the App Role authentication backend is mounted in Vault, e.g: + "approle" + type: string + roleId: + description: |- + RoleID configured in the App Role authentication backend when setting + up the authentication backend in Vault. + type: string + secretRef: + description: |- + 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. + properties: + key: + description: |- + 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. + type: string + name: + description: |- + Name of the resource being referred to. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + required: + - name + type: object + required: + - path + - roleId + - secretRef + type: object + clientCertificate: + description: |- + ClientCertificate authenticates with Vault by presenting a client + certificate during the request's TLS handshake. + Works only when using HTTPS protocol. + properties: + mountPath: + description: |- + 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/cert" will be used. + type: string + name: + description: |- + Name of the certificate role to authenticate against. + If not set, matching any certificate role, if available. + type: string + secretName: + description: |- + Reference to Kubernetes Secret of type "kubernetes.io/tls" (hence containing + tls.crt and tls.key) used to authenticate to Vault using TLS client + authentication. + type: string + type: object + kubernetes: + description: |- + Kubernetes authenticates with Vault by passing the ServiceAccount + token stored in the named Secret resource to the Vault server. + properties: + mountPath: + description: |- + 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. + type: string + role: + description: |- + A required field containing the Vault Role to assume. A Role binds a + Kubernetes ServiceAccount with a set of Vault policies. + type: string + secretRef: + description: |- + The required Secret field containing a Kubernetes ServiceAccount JWT used + for authenticating with Vault. Use of 'ambient credentials' is not + supported. + properties: + key: + description: |- + 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. + type: string + name: + description: |- + Name of the resource being referred to. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + required: + - name + type: object + serviceAccountRef: + description: |- + 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. + properties: + audiences: + description: |- + TokenAudiences is an optional list of extra audiences to include in the token passed to Vault. The default token + consisting of the issuer's namespace and name is always included. + items: + type: string + type: array + x-kubernetes-list-type: atomic + name: + description: Name of the ServiceAccount used to request + a token. + type: string + required: + - name + type: object + required: + - role + type: object + tokenSecretRef: + description: TokenSecretRef authenticates with Vault by presenting + a token. + properties: + key: + description: |- + 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. + type: string + name: + description: |- + Name of the resource being referred to. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + required: + - name + type: object + type: object + caBundle: + description: |- + 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. + format: byte + type: string + caBundleSecretRef: + description: |- + 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'. + properties: + key: + description: |- + 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. + type: string + name: + description: |- + Name of the resource being referred to. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + required: + - name + type: object + clientCertSecretRef: + description: |- + Reference to a Secret containing a PEM-encoded Client Certificate to use when the + Vault server requires mTLS. + properties: + key: + description: |- + 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. + type: string + name: + description: |- + Name of the resource being referred to. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + required: + - name + type: object + clientKeySecretRef: + description: |- + Reference to a Secret containing a PEM-encoded Client Private Key to use when the + Vault server requires mTLS. + properties: + key: + description: |- + 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. + type: string + name: + description: |- + Name of the resource being referred to. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + required: + - name + type: object + namespace: + description: |- + 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 + type: string + path: + description: |- + Path is the mount path of the Vault PKI backend's `sign` endpoint, e.g: + "my_pki_mount/sign/my-role-name". + type: string + server: + description: 'Server is the connection address for the Vault server, + e.g: "https://vault.example.com:8200".' + type: string + serverName: + description: |- + ServerName is used to verify the hostname on the returned certificates + by the Vault server. + type: string + required: + - auth + - path + - server + type: object + venafi: + description: |- + Venafi configures this issuer to sign certificates using a Venafi TPP + or Venafi Cloud policy zone. + properties: + cloud: + description: |- + Cloud specifies the Venafi cloud configuration settings. + Only one of TPP or Cloud may be specified. + properties: + apiTokenSecretRef: + description: APITokenSecretRef is a secret key selector for + the Venafi Cloud API token. + properties: + key: + description: |- + 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. + type: string + name: + description: |- + Name of the resource being referred to. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + required: + - name + type: object + url: + description: |- + URL is the base URL for Venafi Cloud. + Defaults to "https://api.venafi.cloud/". + type: string + required: + - apiTokenSecretRef + type: object + tpp: + description: |- + TPP specifies Trust Protection Platform configuration settings. + Only one of TPP or Cloud may be specified. + properties: + caBundle: + description: |- + 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. + format: byte + type: string + caBundleSecretRef: + description: |- + Reference to a Secret containing a 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. Mutually exclusive with CABundle. + If neither CABundle nor CABundleSecretRef is defined, the certificate bundle in + the cert-manager controller container is used to validate the TLS connection. + properties: + key: + description: |- + 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. + type: string + name: + description: |- + Name of the resource being referred to. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + required: + - name + type: object + credentialsRef: + description: |- + CredentialsRef is a reference to a Secret containing the Venafi TPP API credentials. + The secret must contain the key 'access-token' for the Access Token Authentication, + or two keys, 'username' and 'password' for the API Keys Authentication. + properties: + name: + description: |- + Name of the resource being referred to. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + required: + - name + type: object + url: + description: |- + URL is the base URL for the vedsdk endpoint of the Venafi TPP instance, + for example: "https://tpp.example.com/vedsdk". + type: string + required: + - credentialsRef + - url + type: object + zone: + description: |- + 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. + type: string + required: + - zone + type: object + type: object + status: + description: Status of the Issuer. This is set and managed automatically. + properties: + acme: + description: |- + ACME specific status options. + This field should only be set if the Issuer is configured to use an ACME + server to issue certificates. + properties: + lastPrivateKeyHash: + description: |- + 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 + type: string + lastRegisteredEmail: + 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 + type: string + uri: + description: |- + URI is the unique account identifier, which can also be used to retrieve + account details from the CA + type: string + type: object + conditions: + description: |- + List of status conditions to indicate the status of a CertificateRequest. + Known condition types are `Ready`. + items: + description: IssuerCondition contains condition information for + an Issuer. + properties: + lastTransitionTime: + description: |- + LastTransitionTime is the timestamp corresponding to the last status + change of this condition. + format: date-time + type: string + message: + description: |- + Message is a human readable description of the details of the last + transition, complementing reason. + type: string + observedGeneration: + description: |- + 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. + format: int64 + type: integer + reason: + description: |- + Reason is a brief machine readable explanation for the condition's last + transition. + type: string + status: + description: Status of the condition, one of (`True`, `False`, + `Unknown`). + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: Type of the condition, known values are (`Ready`). + type: string + required: + - status + - type + type: object + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} diff --git a/deploy/crds/crd-certificaterequests.yaml b/deploy/crds/crd-certificaterequests.yaml deleted file mode 100644 index 1c0fb415c11..00000000000 --- a/deploy/crds/crd-certificaterequests.yaml +++ /dev/null @@ -1,197 +0,0 @@ -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - name: certificaterequests.cert-manager.io - labels: - app: '{{ template "cert-manager.name" . }}' - app.kubernetes.io/name: '{{ template "cert-manager.name" . }}' - app.kubernetes.io/instance: '{{ .Release.Name }}' - # Generated labels {{- include "labels" . | nindent 4 }} -spec: - group: cert-manager.io - names: - kind: CertificateRequest - listKind: CertificateRequestList - plural: certificaterequests - shortNames: - - cr - - crs - singular: certificaterequest - categories: - - cert-manager - scope: Namespaced - versions: - - name: v1 - subresources: - status: {} - additionalPrinterColumns: - - jsonPath: .status.conditions[?(@.type=="Approved")].status - name: Approved - type: string - - jsonPath: .status.conditions[?(@.type=="Denied")].status - name: Denied - type: string - - jsonPath: .status.conditions[?(@.type=="Ready")].status - name: Ready - type: string - - jsonPath: .spec.issuerRef.name - name: Issuer - type: string - - jsonPath: .spec.username - name: Requestor - type: string - - jsonPath: .status.conditions[?(@.type=="Ready")].message - name: Status - priority: 1 - type: string - - jsonPath: .metadata.creationTimestamp - description: CreationTimestamp is a timestamp representing the server time when this object was created. It is not guaranteed to be set in happens-before order across separate operations. Clients may not set this value. It is represented in RFC3339 form and is in UTC. - name: Age - type: date - schema: - openAPIV3Schema: - description: "A CertificateRequest is used to request a signed certificate from one of the configured issuers. \n 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. \n A CertificateRequest is a one-shot resource, meaning it represents a single point in time request for a certificate and cannot be re-used." - type: object - required: - - spec - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: Desired state of the CertificateRequest resource. - type: object - required: - - issuerRef - - request - properties: - duration: - description: The requested 'duration' (i.e. lifetime) of the Certificate. This option may be ignored/overridden by some issuer types. - type: string - extra: - description: Extra contains extra attributes of the user that created the CertificateRequest. Populated by the cert-manager webhook on creation and immutable. - type: object - additionalProperties: - type: array - items: - type: string - groups: - description: Groups contains group membership of the user that created the CertificateRequest. Populated by the cert-manager webhook on creation and immutable. - type: array - items: - type: string - x-kubernetes-list-type: atomic - isCA: - description: 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`. - type: boolean - issuerRef: - description: 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. - type: object - required: - - name - properties: - group: - description: Group of the resource being referred to. - type: string - kind: - description: Kind of the resource being referred to. - type: string - name: - description: Name of the resource being referred to. - type: string - request: - description: The PEM-encoded x509 certificate signing request to be submitted to the CA for signing. - type: string - format: byte - uid: - description: UID contains the uid of the user that created the CertificateRequest. Populated by the cert-manager webhook on creation and immutable. - type: string - usages: - description: 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. - type: array - items: - description: "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 \n 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\"" - type: string - enum: - - 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 - username: - description: Username contains the name of the user that created the CertificateRequest. Populated by the cert-manager webhook on creation and immutable. - type: string - status: - description: Status of the CertificateRequest. This is set and managed automatically. - type: object - properties: - ca: - description: 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. - type: string - format: byte - certificate: - description: 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. - type: string - format: byte - conditions: - description: List of status conditions to indicate the status of a CertificateRequest. Known condition types are `Ready` and `InvalidRequest`. - type: array - items: - description: CertificateRequestCondition contains condition information for a CertificateRequest. - type: object - required: - - status - - type - properties: - lastTransitionTime: - description: LastTransitionTime is the timestamp corresponding to the last status change of this condition. - type: string - format: date-time - message: - description: Message is a human readable description of the details of the last transition, complementing reason. - type: string - reason: - description: Reason is a brief machine readable explanation for the condition's last transition. - type: string - status: - description: Status of the condition, one of (`True`, `False`, `Unknown`). - type: string - enum: - - "True" - - "False" - - Unknown - type: - description: Type of the condition, known values are (`Ready`, `InvalidRequest`, `Approved`, `Denied`). - type: string - x-kubernetes-list-map-keys: - - type - x-kubernetes-list-type: map - failureTime: - description: FailureTime stores the time that this CertificateRequest failed. This is used to influence garbage collection and back-off. - type: string - format: date-time - served: true - storage: true diff --git a/deploy/crds/crd-certificates.yaml b/deploy/crds/crd-certificates.yaml deleted file mode 100644 index f4d21987512..00000000000 --- a/deploy/crds/crd-certificates.yaml +++ /dev/null @@ -1,370 +0,0 @@ -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - name: certificates.cert-manager.io - labels: - app: '{{ template "cert-manager.name" . }}' - app.kubernetes.io/name: '{{ template "cert-manager.name" . }}' - app.kubernetes.io/instance: '{{ .Release.Name }}' - # Generated labels {{- include "labels" . | nindent 4 }} -spec: - group: cert-manager.io - names: - kind: Certificate - listKind: CertificateList - plural: certificates - shortNames: - - cert - - certs - singular: certificate - categories: - - cert-manager - scope: Namespaced - versions: - - name: v1 - subresources: - status: {} - additionalPrinterColumns: - - jsonPath: .status.conditions[?(@.type=="Ready")].status - name: Ready - type: string - - jsonPath: .spec.secretName - name: Secret - type: string - - jsonPath: .spec.issuerRef.name - name: Issuer - priority: 1 - type: string - - jsonPath: .status.conditions[?(@.type=="Ready")].message - name: Status - priority: 1 - type: string - - jsonPath: .metadata.creationTimestamp - description: CreationTimestamp is a timestamp representing the server time when this object was created. It is not guaranteed to be set in happens-before order across separate operations. Clients may not set this value. It is represented in RFC3339 form and is in UTC. - name: Age - type: date - schema: - openAPIV3Schema: - description: "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`. \n The stored certificate will be renewed before it expires (as configured by `spec.renewBefore`)." - type: object - required: - - spec - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: Desired state of the Certificate resource. - type: object - required: - - issuerRef - - secretName - properties: - additionalOutputFormats: - description: 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. - type: array - items: - description: CertificateAdditionalOutputFormat defines an additional output format of a Certificate resource. These contain supplementary data formats of the signed certificate chain and paired private key. - type: object - required: - - type - properties: - type: - description: Type is the name of the format type that should be written to the Certificate's target Secret. - type: string - enum: - - DER - - CombinedPEM - commonName: - description: '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' - type: string - dnsNames: - description: DNSNames is a list of DNS subjectAltNames to be set on the Certificate. - type: array - items: - type: string - duration: - description: 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 2/3 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 - type: string - emailAddresses: - description: EmailAddresses is a list of email subjectAltNames to be set on the Certificate. - type: array - items: - type: string - encodeUsagesInRequest: - description: EncodeUsagesInRequest controls whether key usages should be present in the CertificateRequest - type: boolean - ipAddresses: - description: IPAddresses is a list of IP address subjectAltNames to be set on the Certificate. - type: array - items: - type: string - isCA: - description: IsCA will mark this Certificate as valid for certificate signing. This will automatically add the `cert sign` usage to the list of `usages`. - type: boolean - issuerRef: - description: 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. - type: object - required: - - name - properties: - group: - description: Group of the resource being referred to. - type: string - kind: - description: Kind of the resource being referred to. - type: string - name: - description: Name of the resource being referred to. - type: string - keystores: - description: Keystores configures additional keystore output formats stored in the `secretName` Secret resource. - type: object - properties: - jks: - description: JKS configures options for storing a JKS keystore in the `spec.secretName` Secret resource. - type: object - required: - - create - - passwordSecretRef - properties: - create: - description: 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 only be updated upon re-issuance. 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 - type: boolean - passwordSecretRef: - description: PasswordSecretRef is a reference to a key in a Secret resource containing the password used to encrypt the JKS keystore. - type: object - required: - - name - properties: - key: - description: 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. - type: string - name: - description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - pkcs12: - description: PKCS12 configures options for storing a PKCS12 keystore in the `spec.secretName` Secret resource. - type: object - required: - - create - - passwordSecretRef - properties: - create: - description: 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 only be updated upon re-issuance. 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 - type: boolean - passwordSecretRef: - description: PasswordSecretRef is a reference to a key in a Secret resource containing the password used to encrypt the PKCS12 keystore. - type: object - required: - - name - properties: - key: - description: 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. - type: string - name: - description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - literalSubject: - description: LiteralSubject is an LDAP formatted string that represents the [X.509 Subject field](https://datatracker.ietf.org/doc/html/rfc5280#section-4.1.2.6). 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. - type: string - privateKey: - description: Options to control private keys used for the Certificate. - type: object - properties: - algorithm: - description: 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. - type: string - enum: - - RSA - - ECDSA - - Ed25519 - encoding: - description: 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. - type: string - enum: - - PKCS1 - - PKCS8 - rotationPolicy: - description: 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. - type: string - enum: - - Never - - Always - size: - description: 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. - type: integer - renewBefore: - description: How long before the currently issued certificate's expiry cert-manager should renew the certificate. The default is 2/3 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 - type: string - revisionHistoryLimit: - description: 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`. - type: integer - format: int32 - secretName: - description: 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. - type: string - secretTemplate: - description: 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. - type: object - properties: - annotations: - description: Annotations is a key value map to be copied to the target Kubernetes Secret. - type: object - additionalProperties: - type: string - labels: - description: Labels is a key value map to be copied to the target Kubernetes Secret. - type: object - additionalProperties: - type: string - subject: - description: Full X509 name specification (https://golang.org/pkg/crypto/x509/pkix/#Name). - type: object - properties: - countries: - description: Countries to be used on the Certificate. - type: array - items: - type: string - localities: - description: Cities to be used on the Certificate. - type: array - items: - type: string - organizationalUnits: - description: Organizational Units to be used on the Certificate. - type: array - items: - type: string - organizations: - description: Organizations to be used on the Certificate. - type: array - items: - type: string - postalCodes: - description: Postal codes to be used on the Certificate. - type: array - items: - type: string - provinces: - description: State/Provinces to be used on the Certificate. - type: array - items: - type: string - serialNumber: - description: Serial number to be used on the Certificate. - type: string - streetAddresses: - description: Street addresses to be used on the Certificate. - type: array - items: - type: string - uris: - description: URIs is a list of URI subjectAltNames to be set on the Certificate. - type: array - items: - type: string - usages: - description: Usages is the set of x509 usages that are requested for the certificate. Defaults to `digital signature` and `key encipherment` if not specified. - type: array - items: - description: "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 \n 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\"" - type: string - enum: - - 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 - status: - description: Status of the Certificate. This is set and managed automatically. - type: object - properties: - conditions: - description: List of status conditions to indicate the status of certificates. Known condition types are `Ready` and `Issuing`. - type: array - items: - description: CertificateCondition contains condition information for an Certificate. - type: object - required: - - status - - type - properties: - lastTransitionTime: - description: LastTransitionTime is the timestamp corresponding to the last status change of this condition. - type: string - format: date-time - message: - description: Message is a human readable description of the details of the last transition, complementing reason. - type: string - observedGeneration: - description: 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. - type: integer - format: int64 - reason: - description: Reason is a brief machine readable explanation for the condition's last transition. - type: string - status: - description: Status of the condition, one of (`True`, `False`, `Unknown`). - type: string - enum: - - "True" - - "False" - - Unknown - type: - description: Type of the condition, known values are (`Ready`, `Issuing`). - type: string - x-kubernetes-list-map-keys: - - type - x-kubernetes-list-type: map - failedIssuanceAttempts: - description: 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). - type: integer - lastFailureTime: - description: 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. - type: string - format: date-time - nextPrivateKeySecretName: - description: 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. - type: string - notAfter: - description: The expiration time of the certificate stored in the secret named by this resource in `spec.secretName`. - type: string - format: date-time - notBefore: - description: The time after which the certificate stored in the secret named by this resource in spec.secretName is valid. - type: string - format: date-time - renewalTime: - description: RenewalTime is the time at which the certificate will be next renewed. If not set, no upcoming renewal is scheduled. - type: string - format: date-time - revision: - description: "The current 'revision' of the certificate as issued. \n 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. \n Upon issuance, this field will be set to the value of the annotation on the CertificateRequest resource used to issue the certificate. \n 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." - type: integer - served: true - storage: true diff --git a/deploy/crds/crd-challenges.yaml b/deploy/crds/crd-challenges.yaml deleted file mode 100644 index a010a9bb2b9..00000000000 --- a/deploy/crds/crd-challenges.yaml +++ /dev/null @@ -1,1061 +0,0 @@ -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - name: challenges.acme.cert-manager.io - labels: - app: '{{ template "cert-manager.name" . }}' - app.kubernetes.io/name: '{{ template "cert-manager.name" . }}' - app.kubernetes.io/instance: '{{ .Release.Name }}' - # Generated labels {{- include "labels" . | nindent 4 }} -spec: - group: acme.cert-manager.io - names: - kind: Challenge - listKind: ChallengeList - plural: challenges - singular: challenge - categories: - - cert-manager - - cert-manager-acme - scope: Namespaced - versions: - - additionalPrinterColumns: - - jsonPath: .status.state - name: State - type: string - - jsonPath: .spec.dnsName - name: Domain - type: string - - jsonPath: .status.reason - name: Reason - priority: 1 - type: string - - description: CreationTimestamp is a timestamp representing the server time when this object was created. It is not guaranteed to be set in happens-before order across separate operations. Clients may not set this value. It is represented in RFC3339 form and is in UTC. - jsonPath: .metadata.creationTimestamp - name: Age - type: date - name: v1 - schema: - openAPIV3Schema: - description: Challenge is a type to represent a Challenge request with an ACME server - type: object - required: - - metadata - - spec - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - type: object - required: - - authorizationURL - - dnsName - - issuerRef - - key - - solver - - token - - type - - url - properties: - authorizationURL: - description: The URL to the ACME Authorization resource that this challenge is a part of. - type: string - dnsName: - description: 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`. - type: string - issuerRef: - description: 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. - type: object - required: - - name - properties: - group: - description: Group of the resource being referred to. - type: string - kind: - description: Kind of the resource being referred to. - type: string - name: - description: Name of the resource being referred to. - type: string - key: - description: '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: `.`. For DNS01 challenges, this is the base64 encoded SHA256 sum of the `.` text that must be set as the TXT record content.' - type: string - solver: - description: Contains the domain solving configuration that should be used to solve this challenge resource. - type: object - properties: - dns01: - description: Configures cert-manager to attempt to complete authorizations by performing the DNS01 challenge flow. - type: object - properties: - acmeDNS: - description: Use the 'ACME DNS' (https://github.com/joohoi/acme-dns) API to manage DNS01 challenge records. - type: object - required: - - accountSecretRef - - host - properties: - accountSecretRef: - description: A reference to a specific 'key' within a Secret resource. In some instances, `key` is a required field. - type: object - required: - - name - properties: - key: - description: 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. - type: string - name: - description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - host: - type: string - akamai: - description: Use the Akamai DNS zone management API to manage DNS01 challenge records. - type: object - required: - - accessTokenSecretRef - - clientSecretSecretRef - - clientTokenSecretRef - - serviceConsumerDomain - properties: - accessTokenSecretRef: - description: A reference to a specific 'key' within a Secret resource. In some instances, `key` is a required field. - type: object - required: - - name - properties: - key: - description: 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. - type: string - name: - description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - clientSecretSecretRef: - description: A reference to a specific 'key' within a Secret resource. In some instances, `key` is a required field. - type: object - required: - - name - properties: - key: - description: 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. - type: string - name: - description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - clientTokenSecretRef: - description: A reference to a specific 'key' within a Secret resource. In some instances, `key` is a required field. - type: object - required: - - name - properties: - key: - description: 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. - type: string - name: - description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - serviceConsumerDomain: - type: string - azureDNS: - description: Use the Microsoft Azure DNS API to manage DNS01 challenge records. - type: object - required: - - resourceGroupName - - subscriptionID - properties: - clientID: - description: if both this and ClientSecret are left unset MSI will be used - type: string - clientSecretSecretRef: - description: if both this and ClientID are left unset MSI will be used - type: object - required: - - name - properties: - key: - description: 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. - type: string - name: - description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - environment: - description: name of the Azure environment (default AzurePublicCloud) - type: string - enum: - - AzurePublicCloud - - AzureChinaCloud - - AzureGermanCloud - - AzureUSGovernmentCloud - hostedZoneName: - description: name of the DNS zone that should be used - type: string - managedIdentity: - description: managed identity configuration, can not be used at the same time as clientID, clientSecretSecretRef or tenantID - type: object - properties: - clientID: - description: client ID of the managed identity, can not be used at the same time as resourceID - type: string - resourceID: - description: resource ID of the managed identity, can not be used at the same time as clientID - type: string - resourceGroupName: - description: resource group the DNS zone is located in - type: string - subscriptionID: - description: ID of the Azure subscription - type: string - tenantID: - description: when specifying ClientID and ClientSecret then this field is also needed - type: string - cloudDNS: - description: Use the Google Cloud DNS API to manage DNS01 challenge records. - type: object - required: - - project - properties: - hostedZoneName: - description: 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. - type: string - project: - type: string - serviceAccountSecretRef: - description: A reference to a specific 'key' within a Secret resource. In some instances, `key` is a required field. - type: object - required: - - name - properties: - key: - description: 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. - type: string - name: - description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - cloudflare: - description: Use the Cloudflare API to manage DNS01 challenge records. - type: object - properties: - apiKeySecretRef: - description: '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.' - type: object - required: - - name - properties: - key: - description: 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. - type: string - name: - description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - apiTokenSecretRef: - description: API token used to authenticate with Cloudflare. - type: object - required: - - name - properties: - key: - description: 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. - type: string - name: - description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - email: - description: Email of the account, only required when using API key based authentication. - type: string - cnameStrategy: - description: CNAMEStrategy configures how the DNS01 provider should handle CNAME records when found in DNS zones. - type: string - enum: - - None - - Follow - digitalocean: - description: Use the DigitalOcean DNS API to manage DNS01 challenge records. - type: object - required: - - tokenSecretRef - properties: - tokenSecretRef: - description: A reference to a specific 'key' within a Secret resource. In some instances, `key` is a required field. - type: object - required: - - name - properties: - key: - description: 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. - type: string - name: - description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - rfc2136: - description: Use RFC2136 ("Dynamic Updates in the Domain Name System") (https://datatracker.ietf.org/doc/rfc2136/) to manage DNS01 challenge records. - type: object - required: - - nameserver - properties: - nameserver: - description: 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. - type: string - tsigAlgorithm: - description: '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``.' - type: string - tsigKeyName: - description: The TSIG Key name configured in the DNS. If ``tsigSecretSecretRef`` is defined, this field is required. - type: string - tsigSecretSecretRef: - description: The name of the secret containing the TSIG value. If ``tsigKeyName`` is defined, this field is required. - type: object - required: - - name - properties: - key: - description: 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. - type: string - name: - description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - route53: - description: Use the AWS Route53 API to manage DNS01 challenge records. - type: object - required: - - region - properties: - accessKeyID: - description: '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' - type: string - accessKeyIDSecretRef: - description: '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' - type: object - required: - - name - properties: - key: - description: 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. - type: string - name: - description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - hostedZoneID: - description: If set, the provider will manage only this zone in Route53 and will not do an lookup using the route53:ListHostedZonesByName api call. - type: string - region: - description: Always set the region when using AccessKeyID and SecretAccessKey - type: string - role: - description: 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 - type: string - secretAccessKeySecretRef: - description: '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' - type: object - required: - - name - properties: - key: - description: 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. - type: string - name: - description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - webhook: - description: Configure an external webhook based DNS01 challenge solver to manage DNS01 challenge records. - type: object - required: - - groupName - - solverName - properties: - config: - description: 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. - x-kubernetes-preserve-unknown-fields: true - groupName: - description: 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. - type: string - solverName: - description: 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'. - type: string - http01: - description: 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. - type: object - properties: - gatewayHTTPRoute: - description: 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. - type: object - properties: - labels: - description: Custom labels that will be applied to HTTPRoutes created by cert-manager while solving HTTP-01 challenges. - type: object - additionalProperties: - type: string - parentRefs: - description: '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/v1alpha2/api-types/httproute/#attaching-to-gateways' - type: array - items: - description: "ParentReference identifies an API object (usually a Gateway) that can be considered a parent of this resource (usually a route). The only kind of parent resource with \"Core\" support is Gateway. This API may be extended in the future to support additional kinds of parent resources, such as HTTPRoute. \n The API object must be valid in the cluster; the Group and Kind must be registered in the cluster for this reference to be valid." - type: object - required: - - name - properties: - group: - description: "Group is the group of the referent. \n Support: Core" - type: string - default: gateway.networking.k8s.io - maxLength: 253 - pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - kind: - description: "Kind is kind of the referent. \n Support: Core (Gateway) \n Support: Custom (Other Resources)" - type: string - default: Gateway - maxLength: 63 - minLength: 1 - pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ - name: - description: "Name is the name of the referent. \n Support: Core" - type: string - maxLength: 253 - minLength: 1 - namespace: - description: "Namespace is the namespace of the referent. When unspecified (or empty string), this refers to the local namespace of the Route. \n Support: Core" - type: string - maxLength: 63 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ - port: - description: "Port is the network port this Route targets. It can be interpreted differently based on the type of parent resource. \n When the parent resource is a Gateway, this targets all listeners listening on the specified port that also support this kind of Route(and select this Route). It's not recommended to set `Port` unless the networking behaviors specified in a Route must apply to a specific port as opposed to a listener(s) whose port(s) may be changed. When both Port and SectionName are specified, the name and port of the selected listener must match both specified values. \n Implementations MAY choose to support other parent resources. Implementations supporting other types of parent resources MUST clearly document how/if Port is interpreted. \n For the purpose of status, an attachment is considered successful as long as the parent resource accepts it partially. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Extended \n " - type: integer - format: int32 - maximum: 65535 - minimum: 1 - sectionName: - description: "SectionName is the name of a section within the target resource. In the following resources, SectionName is interpreted as the following: \n * Gateway: Listener Name. When both Port (experimental) and SectionName are specified, the name and port of the selected listener must match both specified values. \n Implementations MAY choose to support attaching Routes to other resources. If that is the case, they MUST clearly document how SectionName is interpreted. \n When unspecified (empty string), this will reference the entire resource. For the purpose of status, an attachment is considered successful if at least one section in the parent resource accepts it. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Core" - type: string - maxLength: 253 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - serviceType: - description: Optional service type for Kubernetes solver service. Supported values are NodePort or ClusterIP. If unset, defaults to NodePort. - type: string - ingress: - description: 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. - type: object - properties: - class: - description: 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. - type: string - ingressTemplate: - description: Optional ingress template used to configure the ACME challenge solver ingress used for HTTP01 challenges. - type: object - properties: - metadata: - description: 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. - type: object - properties: - annotations: - description: Annotations that should be added to the created ACME HTTP01 solver ingress. - type: object - additionalProperties: - type: string - labels: - description: Labels that should be added to the created ACME HTTP01 solver ingress. - type: object - additionalProperties: - type: string - name: - description: 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. - type: string - podTemplate: - description: Optional pod template used to configure the ACME challenge solver pods used for HTTP01 challenges. - type: object - properties: - metadata: - description: 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. - type: object - properties: - annotations: - description: Annotations that should be added to the create ACME HTTP01 solver pods. - type: object - additionalProperties: - type: string - labels: - description: Labels that should be added to the created ACME HTTP01 solver pods. - type: object - additionalProperties: - type: string - spec: - description: 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. - type: object - properties: - affinity: - description: If specified, the pod's scheduling constraints - type: object - properties: - nodeAffinity: - description: Describes node affinity scheduling rules for the pod. - type: object - properties: - preferredDuringSchedulingIgnoredDuringExecution: - description: The scheduler will prefer to schedule pods to nodes that satisfy the affinity expressions specified by this field, but it may choose a node that violates one or more of the expressions. The node that is most preferred is the one with the greatest sum of weights, i.e. for each node that meets all of the scheduling requirements (resource request, requiredDuringScheduling affinity expressions, etc.), compute a sum by iterating through the elements of this field and adding "weight" to the sum if the node matches the corresponding matchExpressions; the node(s) with the highest sum are the most preferred. - type: array - items: - description: An empty preferred scheduling term matches all objects with implicit weight 0 (i.e. it's a no-op). A null preferred scheduling term matches no objects (i.e. is also a no-op). - type: object - required: - - preference - - weight - properties: - preference: - description: A node selector term, associated with the corresponding weight. - type: object - properties: - matchExpressions: - description: A list of node selector requirements by node's labels. - type: array - items: - description: A node selector requirement is a selector that contains values, a key, and an operator that relates the key and values. - type: object - required: - - key - - operator - properties: - key: - description: The label key that the selector applies to. - type: string - operator: - description: Represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. - type: string - values: - description: An array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. If the operator is Gt or Lt, the values array must have a single element, which will be interpreted as an integer. This array is replaced during a strategic merge patch. - type: array - items: - type: string - matchFields: - description: A list of node selector requirements by node's fields. - type: array - items: - description: A node selector requirement is a selector that contains values, a key, and an operator that relates the key and values. - type: object - required: - - key - - operator - properties: - key: - description: The label key that the selector applies to. - type: string - operator: - description: Represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. - type: string - values: - description: An array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. If the operator is Gt or Lt, the values array must have a single element, which will be interpreted as an integer. This array is replaced during a strategic merge patch. - type: array - items: - type: string - x-kubernetes-map-type: atomic - weight: - description: Weight associated with matching the corresponding nodeSelectorTerm, in the range 1-100. - type: integer - format: int32 - requiredDuringSchedulingIgnoredDuringExecution: - description: If the affinity requirements specified by this field are not met at scheduling time, the pod will not be scheduled onto the node. If the affinity requirements specified by this field cease to be met at some point during pod execution (e.g. due to an update), the system may or may not try to eventually evict the pod from its node. - type: object - required: - - nodeSelectorTerms - properties: - nodeSelectorTerms: - description: Required. A list of node selector terms. The terms are ORed. - type: array - items: - description: A null or empty node selector term matches no objects. The requirements of them are ANDed. The TopologySelectorTerm type implements a subset of the NodeSelectorTerm. - type: object - properties: - matchExpressions: - description: A list of node selector requirements by node's labels. - type: array - items: - description: A node selector requirement is a selector that contains values, a key, and an operator that relates the key and values. - type: object - required: - - key - - operator - properties: - key: - description: The label key that the selector applies to. - type: string - operator: - description: Represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. - type: string - values: - description: An array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. If the operator is Gt or Lt, the values array must have a single element, which will be interpreted as an integer. This array is replaced during a strategic merge patch. - type: array - items: - type: string - matchFields: - description: A list of node selector requirements by node's fields. - type: array - items: - description: A node selector requirement is a selector that contains values, a key, and an operator that relates the key and values. - type: object - required: - - key - - operator - properties: - key: - description: The label key that the selector applies to. - type: string - operator: - description: Represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. - type: string - values: - description: An array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. If the operator is Gt or Lt, the values array must have a single element, which will be interpreted as an integer. This array is replaced during a strategic merge patch. - type: array - items: - type: string - x-kubernetes-map-type: atomic - x-kubernetes-map-type: atomic - podAffinity: - description: Describes pod affinity scheduling rules (e.g. co-locate this pod in the same node, zone, etc. as some other pod(s)). - type: object - properties: - preferredDuringSchedulingIgnoredDuringExecution: - description: The scheduler will prefer to schedule pods to nodes that satisfy the affinity expressions specified by this field, but it may choose a node that violates one or more of the expressions. The node that is most preferred is the one with the greatest sum of weights, i.e. for each node that meets all of the scheduling requirements (resource request, requiredDuringScheduling affinity expressions, etc.), compute a sum by iterating through the elements of this field and adding "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the node(s) with the highest sum are the most preferred. - type: array - items: - description: The weights of all of the matched WeightedPodAffinityTerm fields are added per-node to find the most preferred node(s) - type: object - required: - - podAffinityTerm - - weight - properties: - podAffinityTerm: - description: Required. A pod affinity term, associated with the corresponding weight. - type: object - required: - - topologyKey - properties: - labelSelector: - description: A label query over a set of resources, in this case pods. - type: object - properties: - matchExpressions: - description: matchExpressions is a list of label selector requirements. The requirements are ANDed. - type: array - items: - description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. - type: object - required: - - key - - operator - properties: - key: - description: key is the label key that the selector applies to. - type: string - operator: - description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. - type: array - items: - type: string - matchLabels: - description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - additionalProperties: - type: string - x-kubernetes-map-type: atomic - namespaceSelector: - description: A label query over the set of namespaces that the term applies to. The term is applied to the union of the namespaces selected by this field and the ones listed in the namespaces field. null selector and null or empty namespaces list means "this pod's namespace". An empty selector ({}) matches all namespaces. - type: object - properties: - matchExpressions: - description: matchExpressions is a list of label selector requirements. The requirements are ANDed. - type: array - items: - description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. - type: object - required: - - key - - operator - properties: - key: - description: key is the label key that the selector applies to. - type: string - operator: - description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. - type: array - items: - type: string - matchLabels: - description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - additionalProperties: - type: string - x-kubernetes-map-type: atomic - namespaces: - description: namespaces specifies a static list of namespace names that the term applies to. The term is applied to the union of the namespaces listed in this field and the ones selected by namespaceSelector. null or empty namespaces list and null namespaceSelector means "this pod's namespace". - type: array - items: - type: string - topologyKey: - description: This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching the labelSelector in the specified namespaces, where co-located is defined as running on a node whose value of the label with key topologyKey matches that of any node on which any of the selected pods is running. Empty topologyKey is not allowed. - type: string - weight: - description: weight associated with matching the corresponding podAffinityTerm, in the range 1-100. - type: integer - format: int32 - requiredDuringSchedulingIgnoredDuringExecution: - description: If the affinity requirements specified by this field are not met at scheduling time, the pod will not be scheduled onto the node. If the affinity requirements specified by this field cease to be met at some point during pod execution (e.g. due to a pod label update), the system may or may not try to eventually evict the pod from its node. When there are multiple elements, the lists of nodes corresponding to each podAffinityTerm are intersected, i.e. all terms must be satisfied. - type: array - items: - description: Defines a set of pods (namely those matching the labelSelector relative to the given namespace(s)) that this pod should be co-located (affinity) or not co-located (anti-affinity) with, where co-located is defined as running on a node whose value of the label with key matches that of any node on which a pod of the set of pods is running - type: object - required: - - topologyKey - properties: - labelSelector: - description: A label query over a set of resources, in this case pods. - type: object - properties: - matchExpressions: - description: matchExpressions is a list of label selector requirements. The requirements are ANDed. - type: array - items: - description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. - type: object - required: - - key - - operator - properties: - key: - description: key is the label key that the selector applies to. - type: string - operator: - description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. - type: array - items: - type: string - matchLabels: - description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - additionalProperties: - type: string - x-kubernetes-map-type: atomic - namespaceSelector: - description: A label query over the set of namespaces that the term applies to. The term is applied to the union of the namespaces selected by this field and the ones listed in the namespaces field. null selector and null or empty namespaces list means "this pod's namespace". An empty selector ({}) matches all namespaces. - type: object - properties: - matchExpressions: - description: matchExpressions is a list of label selector requirements. The requirements are ANDed. - type: array - items: - description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. - type: object - required: - - key - - operator - properties: - key: - description: key is the label key that the selector applies to. - type: string - operator: - description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. - type: array - items: - type: string - matchLabels: - description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - additionalProperties: - type: string - x-kubernetes-map-type: atomic - namespaces: - description: namespaces specifies a static list of namespace names that the term applies to. The term is applied to the union of the namespaces listed in this field and the ones selected by namespaceSelector. null or empty namespaces list and null namespaceSelector means "this pod's namespace". - type: array - items: - type: string - topologyKey: - description: This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching the labelSelector in the specified namespaces, where co-located is defined as running on a node whose value of the label with key topologyKey matches that of any node on which any of the selected pods is running. Empty topologyKey is not allowed. - type: string - podAntiAffinity: - description: Describes pod anti-affinity scheduling rules (e.g. avoid putting this pod in the same node, zone, etc. as some other pod(s)). - type: object - properties: - preferredDuringSchedulingIgnoredDuringExecution: - description: The scheduler will prefer to schedule pods to nodes that satisfy the anti-affinity expressions specified by this field, but it may choose a node that violates one or more of the expressions. The node that is most preferred is the one with the greatest sum of weights, i.e. for each node that meets all of the scheduling requirements (resource request, requiredDuringScheduling anti-affinity expressions, etc.), compute a sum by iterating through the elements of this field and adding "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the node(s) with the highest sum are the most preferred. - type: array - items: - description: The weights of all of the matched WeightedPodAffinityTerm fields are added per-node to find the most preferred node(s) - type: object - required: - - podAffinityTerm - - weight - properties: - podAffinityTerm: - description: Required. A pod affinity term, associated with the corresponding weight. - type: object - required: - - topologyKey - properties: - labelSelector: - description: A label query over a set of resources, in this case pods. - type: object - properties: - matchExpressions: - description: matchExpressions is a list of label selector requirements. The requirements are ANDed. - type: array - items: - description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. - type: object - required: - - key - - operator - properties: - key: - description: key is the label key that the selector applies to. - type: string - operator: - description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. - type: array - items: - type: string - matchLabels: - description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - additionalProperties: - type: string - x-kubernetes-map-type: atomic - namespaceSelector: - description: A label query over the set of namespaces that the term applies to. The term is applied to the union of the namespaces selected by this field and the ones listed in the namespaces field. null selector and null or empty namespaces list means "this pod's namespace". An empty selector ({}) matches all namespaces. - type: object - properties: - matchExpressions: - description: matchExpressions is a list of label selector requirements. The requirements are ANDed. - type: array - items: - description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. - type: object - required: - - key - - operator - properties: - key: - description: key is the label key that the selector applies to. - type: string - operator: - description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. - type: array - items: - type: string - matchLabels: - description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - additionalProperties: - type: string - x-kubernetes-map-type: atomic - namespaces: - description: namespaces specifies a static list of namespace names that the term applies to. The term is applied to the union of the namespaces listed in this field and the ones selected by namespaceSelector. null or empty namespaces list and null namespaceSelector means "this pod's namespace". - type: array - items: - type: string - topologyKey: - description: This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching the labelSelector in the specified namespaces, where co-located is defined as running on a node whose value of the label with key topologyKey matches that of any node on which any of the selected pods is running. Empty topologyKey is not allowed. - type: string - weight: - description: weight associated with matching the corresponding podAffinityTerm, in the range 1-100. - type: integer - format: int32 - requiredDuringSchedulingIgnoredDuringExecution: - description: If the anti-affinity requirements specified by this field are not met at scheduling time, the pod will not be scheduled onto the node. If the anti-affinity requirements specified by this field cease to be met at some point during pod execution (e.g. due to a pod label update), the system may or may not try to eventually evict the pod from its node. When there are multiple elements, the lists of nodes corresponding to each podAffinityTerm are intersected, i.e. all terms must be satisfied. - type: array - items: - description: Defines a set of pods (namely those matching the labelSelector relative to the given namespace(s)) that this pod should be co-located (affinity) or not co-located (anti-affinity) with, where co-located is defined as running on a node whose value of the label with key matches that of any node on which a pod of the set of pods is running - type: object - required: - - topologyKey - properties: - labelSelector: - description: A label query over a set of resources, in this case pods. - type: object - properties: - matchExpressions: - description: matchExpressions is a list of label selector requirements. The requirements are ANDed. - type: array - items: - description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. - type: object - required: - - key - - operator - properties: - key: - description: key is the label key that the selector applies to. - type: string - operator: - description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. - type: array - items: - type: string - matchLabels: - description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - additionalProperties: - type: string - x-kubernetes-map-type: atomic - namespaceSelector: - description: A label query over the set of namespaces that the term applies to. The term is applied to the union of the namespaces selected by this field and the ones listed in the namespaces field. null selector and null or empty namespaces list means "this pod's namespace". An empty selector ({}) matches all namespaces. - type: object - properties: - matchExpressions: - description: matchExpressions is a list of label selector requirements. The requirements are ANDed. - type: array - items: - description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. - type: object - required: - - key - - operator - properties: - key: - description: key is the label key that the selector applies to. - type: string - operator: - description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. - type: array - items: - type: string - matchLabels: - description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - additionalProperties: - type: string - x-kubernetes-map-type: atomic - namespaces: - description: namespaces specifies a static list of namespace names that the term applies to. The term is applied to the union of the namespaces listed in this field and the ones selected by namespaceSelector. null or empty namespaces list and null namespaceSelector means "this pod's namespace". - type: array - items: - type: string - topologyKey: - description: This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching the labelSelector in the specified namespaces, where co-located is defined as running on a node whose value of the label with key topologyKey matches that of any node on which any of the selected pods is running. Empty topologyKey is not allowed. - type: string - nodeSelector: - description: '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/' - type: object - additionalProperties: - type: string - priorityClassName: - description: If specified, the pod's priorityClassName. - type: string - serviceAccountName: - description: If specified, the pod's service account - type: string - tolerations: - description: If specified, the pod's tolerations. - type: array - items: - description: The pod this Toleration is attached to tolerates any taint that matches the triple using the matching operator . - type: object - properties: - effect: - description: Effect indicates the taint effect to match. Empty means match all taint effects. When specified, allowed values are NoSchedule, PreferNoSchedule and NoExecute. - type: string - key: - description: Key is the taint key that the toleration applies to. Empty means match all taint keys. If the key is empty, operator must be Exists; this combination means to match all values and all keys. - type: string - operator: - description: Operator represents a key's relationship to the value. Valid operators are Exists and Equal. Defaults to Equal. Exists is equivalent to wildcard for value, so that a pod can tolerate all taints of a particular category. - type: string - tolerationSeconds: - description: TolerationSeconds represents the period of time the toleration (which must be of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default, it is not set, which means tolerate the taint forever (do not evict). Zero and negative values will be treated as 0 (evict immediately) by the system. - type: integer - format: int64 - value: - description: Value is the taint value the toleration matches to. If the operator is Exists, the value should be empty, otherwise just a regular string. - type: string - serviceType: - description: Optional service type for Kubernetes solver service. Supported values are NodePort or ClusterIP. If unset, defaults to NodePort. - type: string - selector: - description: 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. - type: object - properties: - dnsNames: - description: 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. - type: array - items: - type: string - dnsZones: - description: 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. - type: array - items: - type: string - matchLabels: - description: A label selector that is used to refine the set of certificate's that this challenge solver will apply to. - type: object - additionalProperties: - type: string - token: - description: The ACME challenge token for this challenge. This is the raw value returned from the ACME server. - type: string - type: - description: The type of ACME challenge this resource represents. One of "HTTP-01" or "DNS-01". - type: string - enum: - - HTTP-01 - - DNS-01 - url: - description: The URL of the ACME Challenge resource for this challenge. This can be used to lookup details about the status of this challenge. - type: string - wildcard: - description: wildcard will be true if this challenge is for a wildcard identifier, for example '*.example.com'. - type: boolean - status: - type: object - properties: - presented: - description: 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). - type: boolean - processing: - description: 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. - type: boolean - reason: - description: Contains human readable information on why the Challenge is in the current state. - type: string - state: - description: Contains the current 'state' of the challenge. If not set, the state of the challenge is unknown. - type: string - enum: - - valid - - ready - - pending - - processing - - invalid - - expired - - errored - served: true - storage: true - subresources: - status: {} diff --git a/deploy/crds/crd-clusterissuers.yaml b/deploy/crds/crd-clusterissuers.yaml deleted file mode 100644 index ae3a813ecff..00000000000 --- a/deploy/crds/crd-clusterissuers.yaml +++ /dev/null @@ -1,1288 +0,0 @@ -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - name: clusterissuers.cert-manager.io - labels: - app: '{{ template "cert-manager.name" . }}' - app.kubernetes.io/name: '{{ template "cert-manager.name" . }}' - app.kubernetes.io/instance: '{{ .Release.Name }}' - # Generated labels {{- include "labels" . | nindent 4 }} -spec: - group: cert-manager.io - names: - kind: ClusterIssuer - listKind: ClusterIssuerList - plural: clusterissuers - singular: clusterissuer - categories: - - cert-manager - scope: Cluster - versions: - - name: v1 - subresources: - status: {} - additionalPrinterColumns: - - jsonPath: .status.conditions[?(@.type=="Ready")].status - name: Ready - type: string - - jsonPath: .status.conditions[?(@.type=="Ready")].message - name: Status - priority: 1 - type: string - - jsonPath: .metadata.creationTimestamp - description: CreationTimestamp is a timestamp representing the server time when this object was created. It is not guaranteed to be set in happens-before order across separate operations. Clients may not set this value. It is represented in RFC3339 form and is in UTC. - name: Age - type: date - schema: - openAPIV3Schema: - description: 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. - type: object - required: - - spec - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: Desired state of the ClusterIssuer resource. - type: object - properties: - acme: - description: ACME configures this issuer to communicate with a RFC8555 (ACME) server to obtain signed x509 certificates. - type: object - required: - - privateKeySecretRef - - server - properties: - disableAccountKeyGeneration: - description: 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. - type: boolean - email: - description: 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. - type: string - enableDurationFeature: - description: 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. - type: boolean - externalAccountBinding: - description: 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. - type: object - required: - - keyID - - keySecretRef - properties: - keyAlgorithm: - description: '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.' - type: string - enum: - - HS256 - - HS384 - - HS512 - keyID: - description: keyID is the ID of the CA key that the External Account is bound to. - type: string - keySecretRef: - description: 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. - type: object - required: - - name - properties: - key: - description: 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. - type: string - name: - description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - preferredChain: - description: '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' - type: string - maxLength: 64 - privateKeySecretRef: - description: 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. - type: object - required: - - name - properties: - key: - description: 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. - type: string - name: - description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - server: - description: '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.' - type: string - skipTLSVerify: - description: Enables or disables validation of the ACME server TLS certificate. If true, requests to the ACME server will not have their TLS certificate validated (i.e. insecure connections will be allowed). Only enable this option in development environments. The cert-manager system installed roots will be used to verify connections to the ACME server if this is false. Defaults to false. - type: boolean - solvers: - description: '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/' - type: array - items: - description: 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. - type: object - properties: - dns01: - description: Configures cert-manager to attempt to complete authorizations by performing the DNS01 challenge flow. - type: object - properties: - acmeDNS: - description: Use the 'ACME DNS' (https://github.com/joohoi/acme-dns) API to manage DNS01 challenge records. - type: object - required: - - accountSecretRef - - host - properties: - accountSecretRef: - description: A reference to a specific 'key' within a Secret resource. In some instances, `key` is a required field. - type: object - required: - - name - properties: - key: - description: 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. - type: string - name: - description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - host: - type: string - akamai: - description: Use the Akamai DNS zone management API to manage DNS01 challenge records. - type: object - required: - - accessTokenSecretRef - - clientSecretSecretRef - - clientTokenSecretRef - - serviceConsumerDomain - properties: - accessTokenSecretRef: - description: A reference to a specific 'key' within a Secret resource. In some instances, `key` is a required field. - type: object - required: - - name - properties: - key: - description: 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. - type: string - name: - description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - clientSecretSecretRef: - description: A reference to a specific 'key' within a Secret resource. In some instances, `key` is a required field. - type: object - required: - - name - properties: - key: - description: 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. - type: string - name: - description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - clientTokenSecretRef: - description: A reference to a specific 'key' within a Secret resource. In some instances, `key` is a required field. - type: object - required: - - name - properties: - key: - description: 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. - type: string - name: - description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - serviceConsumerDomain: - type: string - azureDNS: - description: Use the Microsoft Azure DNS API to manage DNS01 challenge records. - type: object - required: - - resourceGroupName - - subscriptionID - properties: - clientID: - description: if both this and ClientSecret are left unset MSI will be used - type: string - clientSecretSecretRef: - description: if both this and ClientID are left unset MSI will be used - type: object - required: - - name - properties: - key: - description: 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. - type: string - name: - description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - environment: - description: name of the Azure environment (default AzurePublicCloud) - type: string - enum: - - AzurePublicCloud - - AzureChinaCloud - - AzureGermanCloud - - AzureUSGovernmentCloud - hostedZoneName: - description: name of the DNS zone that should be used - type: string - managedIdentity: - description: managed identity configuration, can not be used at the same time as clientID, clientSecretSecretRef or tenantID - type: object - properties: - clientID: - description: client ID of the managed identity, can not be used at the same time as resourceID - type: string - resourceID: - description: resource ID of the managed identity, can not be used at the same time as clientID - type: string - resourceGroupName: - description: resource group the DNS zone is located in - type: string - subscriptionID: - description: ID of the Azure subscription - type: string - tenantID: - description: when specifying ClientID and ClientSecret then this field is also needed - type: string - cloudDNS: - description: Use the Google Cloud DNS API to manage DNS01 challenge records. - type: object - required: - - project - properties: - hostedZoneName: - description: 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. - type: string - project: - type: string - serviceAccountSecretRef: - description: A reference to a specific 'key' within a Secret resource. In some instances, `key` is a required field. - type: object - required: - - name - properties: - key: - description: 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. - type: string - name: - description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - cloudflare: - description: Use the Cloudflare API to manage DNS01 challenge records. - type: object - properties: - apiKeySecretRef: - description: '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.' - type: object - required: - - name - properties: - key: - description: 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. - type: string - name: - description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - apiTokenSecretRef: - description: API token used to authenticate with Cloudflare. - type: object - required: - - name - properties: - key: - description: 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. - type: string - name: - description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - email: - description: Email of the account, only required when using API key based authentication. - type: string - cnameStrategy: - description: CNAMEStrategy configures how the DNS01 provider should handle CNAME records when found in DNS zones. - type: string - enum: - - None - - Follow - digitalocean: - description: Use the DigitalOcean DNS API to manage DNS01 challenge records. - type: object - required: - - tokenSecretRef - properties: - tokenSecretRef: - description: A reference to a specific 'key' within a Secret resource. In some instances, `key` is a required field. - type: object - required: - - name - properties: - key: - description: 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. - type: string - name: - description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - rfc2136: - description: Use RFC2136 ("Dynamic Updates in the Domain Name System") (https://datatracker.ietf.org/doc/rfc2136/) to manage DNS01 challenge records. - type: object - required: - - nameserver - properties: - nameserver: - description: 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. - type: string - tsigAlgorithm: - description: '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``.' - type: string - tsigKeyName: - description: The TSIG Key name configured in the DNS. If ``tsigSecretSecretRef`` is defined, this field is required. - type: string - tsigSecretSecretRef: - description: The name of the secret containing the TSIG value. If ``tsigKeyName`` is defined, this field is required. - type: object - required: - - name - properties: - key: - description: 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. - type: string - name: - description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - route53: - description: Use the AWS Route53 API to manage DNS01 challenge records. - type: object - required: - - region - properties: - accessKeyID: - description: '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' - type: string - accessKeyIDSecretRef: - description: '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' - type: object - required: - - name - properties: - key: - description: 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. - type: string - name: - description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - hostedZoneID: - description: If set, the provider will manage only this zone in Route53 and will not do an lookup using the route53:ListHostedZonesByName api call. - type: string - region: - description: Always set the region when using AccessKeyID and SecretAccessKey - type: string - role: - description: 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 - type: string - secretAccessKeySecretRef: - description: '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' - type: object - required: - - name - properties: - key: - description: 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. - type: string - name: - description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - webhook: - description: Configure an external webhook based DNS01 challenge solver to manage DNS01 challenge records. - type: object - required: - - groupName - - solverName - properties: - config: - description: 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. - x-kubernetes-preserve-unknown-fields: true - groupName: - description: 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. - type: string - solverName: - description: 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'. - type: string - http01: - description: 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. - type: object - properties: - gatewayHTTPRoute: - description: 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. - type: object - properties: - labels: - description: Custom labels that will be applied to HTTPRoutes created by cert-manager while solving HTTP-01 challenges. - type: object - additionalProperties: - type: string - parentRefs: - description: '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/v1alpha2/api-types/httproute/#attaching-to-gateways' - type: array - items: - description: "ParentReference identifies an API object (usually a Gateway) that can be considered a parent of this resource (usually a route). The only kind of parent resource with \"Core\" support is Gateway. This API may be extended in the future to support additional kinds of parent resources, such as HTTPRoute. \n The API object must be valid in the cluster; the Group and Kind must be registered in the cluster for this reference to be valid." - type: object - required: - - name - properties: - group: - description: "Group is the group of the referent. \n Support: Core" - type: string - default: gateway.networking.k8s.io - maxLength: 253 - pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - kind: - description: "Kind is kind of the referent. \n Support: Core (Gateway) \n Support: Custom (Other Resources)" - type: string - default: Gateway - maxLength: 63 - minLength: 1 - pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ - name: - description: "Name is the name of the referent. \n Support: Core" - type: string - maxLength: 253 - minLength: 1 - namespace: - description: "Namespace is the namespace of the referent. When unspecified (or empty string), this refers to the local namespace of the Route. \n Support: Core" - type: string - maxLength: 63 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ - port: - description: "Port is the network port this Route targets. It can be interpreted differently based on the type of parent resource. \n When the parent resource is a Gateway, this targets all listeners listening on the specified port that also support this kind of Route(and select this Route). It's not recommended to set `Port` unless the networking behaviors specified in a Route must apply to a specific port as opposed to a listener(s) whose port(s) may be changed. When both Port and SectionName are specified, the name and port of the selected listener must match both specified values. \n Implementations MAY choose to support other parent resources. Implementations supporting other types of parent resources MUST clearly document how/if Port is interpreted. \n For the purpose of status, an attachment is considered successful as long as the parent resource accepts it partially. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Extended \n " - type: integer - format: int32 - maximum: 65535 - minimum: 1 - sectionName: - description: "SectionName is the name of a section within the target resource. In the following resources, SectionName is interpreted as the following: \n * Gateway: Listener Name. When both Port (experimental) and SectionName are specified, the name and port of the selected listener must match both specified values. \n Implementations MAY choose to support attaching Routes to other resources. If that is the case, they MUST clearly document how SectionName is interpreted. \n When unspecified (empty string), this will reference the entire resource. For the purpose of status, an attachment is considered successful if at least one section in the parent resource accepts it. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Core" - type: string - maxLength: 253 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - serviceType: - description: Optional service type for Kubernetes solver service. Supported values are NodePort or ClusterIP. If unset, defaults to NodePort. - type: string - ingress: - description: 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. - type: object - properties: - class: - description: 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. - type: string - ingressTemplate: - description: Optional ingress template used to configure the ACME challenge solver ingress used for HTTP01 challenges. - type: object - properties: - metadata: - description: 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. - type: object - properties: - annotations: - description: Annotations that should be added to the created ACME HTTP01 solver ingress. - type: object - additionalProperties: - type: string - labels: - description: Labels that should be added to the created ACME HTTP01 solver ingress. - type: object - additionalProperties: - type: string - name: - description: 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. - type: string - podTemplate: - description: Optional pod template used to configure the ACME challenge solver pods used for HTTP01 challenges. - type: object - properties: - metadata: - description: 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. - type: object - properties: - annotations: - description: Annotations that should be added to the create ACME HTTP01 solver pods. - type: object - additionalProperties: - type: string - labels: - description: Labels that should be added to the created ACME HTTP01 solver pods. - type: object - additionalProperties: - type: string - spec: - description: 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. - type: object - properties: - affinity: - description: If specified, the pod's scheduling constraints - type: object - properties: - nodeAffinity: - description: Describes node affinity scheduling rules for the pod. - type: object - properties: - preferredDuringSchedulingIgnoredDuringExecution: - description: The scheduler will prefer to schedule pods to nodes that satisfy the affinity expressions specified by this field, but it may choose a node that violates one or more of the expressions. The node that is most preferred is the one with the greatest sum of weights, i.e. for each node that meets all of the scheduling requirements (resource request, requiredDuringScheduling affinity expressions, etc.), compute a sum by iterating through the elements of this field and adding "weight" to the sum if the node matches the corresponding matchExpressions; the node(s) with the highest sum are the most preferred. - type: array - items: - description: An empty preferred scheduling term matches all objects with implicit weight 0 (i.e. it's a no-op). A null preferred scheduling term matches no objects (i.e. is also a no-op). - type: object - required: - - preference - - weight - properties: - preference: - description: A node selector term, associated with the corresponding weight. - type: object - properties: - matchExpressions: - description: A list of node selector requirements by node's labels. - type: array - items: - description: A node selector requirement is a selector that contains values, a key, and an operator that relates the key and values. - type: object - required: - - key - - operator - properties: - key: - description: The label key that the selector applies to. - type: string - operator: - description: Represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. - type: string - values: - description: An array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. If the operator is Gt or Lt, the values array must have a single element, which will be interpreted as an integer. This array is replaced during a strategic merge patch. - type: array - items: - type: string - matchFields: - description: A list of node selector requirements by node's fields. - type: array - items: - description: A node selector requirement is a selector that contains values, a key, and an operator that relates the key and values. - type: object - required: - - key - - operator - properties: - key: - description: The label key that the selector applies to. - type: string - operator: - description: Represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. - type: string - values: - description: An array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. If the operator is Gt or Lt, the values array must have a single element, which will be interpreted as an integer. This array is replaced during a strategic merge patch. - type: array - items: - type: string - x-kubernetes-map-type: atomic - weight: - description: Weight associated with matching the corresponding nodeSelectorTerm, in the range 1-100. - type: integer - format: int32 - requiredDuringSchedulingIgnoredDuringExecution: - description: If the affinity requirements specified by this field are not met at scheduling time, the pod will not be scheduled onto the node. If the affinity requirements specified by this field cease to be met at some point during pod execution (e.g. due to an update), the system may or may not try to eventually evict the pod from its node. - type: object - required: - - nodeSelectorTerms - properties: - nodeSelectorTerms: - description: Required. A list of node selector terms. The terms are ORed. - type: array - items: - description: A null or empty node selector term matches no objects. The requirements of them are ANDed. The TopologySelectorTerm type implements a subset of the NodeSelectorTerm. - type: object - properties: - matchExpressions: - description: A list of node selector requirements by node's labels. - type: array - items: - description: A node selector requirement is a selector that contains values, a key, and an operator that relates the key and values. - type: object - required: - - key - - operator - properties: - key: - description: The label key that the selector applies to. - type: string - operator: - description: Represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. - type: string - values: - description: An array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. If the operator is Gt or Lt, the values array must have a single element, which will be interpreted as an integer. This array is replaced during a strategic merge patch. - type: array - items: - type: string - matchFields: - description: A list of node selector requirements by node's fields. - type: array - items: - description: A node selector requirement is a selector that contains values, a key, and an operator that relates the key and values. - type: object - required: - - key - - operator - properties: - key: - description: The label key that the selector applies to. - type: string - operator: - description: Represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. - type: string - values: - description: An array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. If the operator is Gt or Lt, the values array must have a single element, which will be interpreted as an integer. This array is replaced during a strategic merge patch. - type: array - items: - type: string - x-kubernetes-map-type: atomic - x-kubernetes-map-type: atomic - podAffinity: - description: Describes pod affinity scheduling rules (e.g. co-locate this pod in the same node, zone, etc. as some other pod(s)). - type: object - properties: - preferredDuringSchedulingIgnoredDuringExecution: - description: The scheduler will prefer to schedule pods to nodes that satisfy the affinity expressions specified by this field, but it may choose a node that violates one or more of the expressions. The node that is most preferred is the one with the greatest sum of weights, i.e. for each node that meets all of the scheduling requirements (resource request, requiredDuringScheduling affinity expressions, etc.), compute a sum by iterating through the elements of this field and adding "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the node(s) with the highest sum are the most preferred. - type: array - items: - description: The weights of all of the matched WeightedPodAffinityTerm fields are added per-node to find the most preferred node(s) - type: object - required: - - podAffinityTerm - - weight - properties: - podAffinityTerm: - description: Required. A pod affinity term, associated with the corresponding weight. - type: object - required: - - topologyKey - properties: - labelSelector: - description: A label query over a set of resources, in this case pods. - type: object - properties: - matchExpressions: - description: matchExpressions is a list of label selector requirements. The requirements are ANDed. - type: array - items: - description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. - type: object - required: - - key - - operator - properties: - key: - description: key is the label key that the selector applies to. - type: string - operator: - description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. - type: array - items: - type: string - matchLabels: - description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - additionalProperties: - type: string - x-kubernetes-map-type: atomic - namespaceSelector: - description: A label query over the set of namespaces that the term applies to. The term is applied to the union of the namespaces selected by this field and the ones listed in the namespaces field. null selector and null or empty namespaces list means "this pod's namespace". An empty selector ({}) matches all namespaces. - type: object - properties: - matchExpressions: - description: matchExpressions is a list of label selector requirements. The requirements are ANDed. - type: array - items: - description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. - type: object - required: - - key - - operator - properties: - key: - description: key is the label key that the selector applies to. - type: string - operator: - description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. - type: array - items: - type: string - matchLabels: - description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - additionalProperties: - type: string - x-kubernetes-map-type: atomic - namespaces: - description: namespaces specifies a static list of namespace names that the term applies to. The term is applied to the union of the namespaces listed in this field and the ones selected by namespaceSelector. null or empty namespaces list and null namespaceSelector means "this pod's namespace". - type: array - items: - type: string - topologyKey: - description: This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching the labelSelector in the specified namespaces, where co-located is defined as running on a node whose value of the label with key topologyKey matches that of any node on which any of the selected pods is running. Empty topologyKey is not allowed. - type: string - weight: - description: weight associated with matching the corresponding podAffinityTerm, in the range 1-100. - type: integer - format: int32 - requiredDuringSchedulingIgnoredDuringExecution: - description: If the affinity requirements specified by this field are not met at scheduling time, the pod will not be scheduled onto the node. If the affinity requirements specified by this field cease to be met at some point during pod execution (e.g. due to a pod label update), the system may or may not try to eventually evict the pod from its node. When there are multiple elements, the lists of nodes corresponding to each podAffinityTerm are intersected, i.e. all terms must be satisfied. - type: array - items: - description: Defines a set of pods (namely those matching the labelSelector relative to the given namespace(s)) that this pod should be co-located (affinity) or not co-located (anti-affinity) with, where co-located is defined as running on a node whose value of the label with key matches that of any node on which a pod of the set of pods is running - type: object - required: - - topologyKey - properties: - labelSelector: - description: A label query over a set of resources, in this case pods. - type: object - properties: - matchExpressions: - description: matchExpressions is a list of label selector requirements. The requirements are ANDed. - type: array - items: - description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. - type: object - required: - - key - - operator - properties: - key: - description: key is the label key that the selector applies to. - type: string - operator: - description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. - type: array - items: - type: string - matchLabels: - description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - additionalProperties: - type: string - x-kubernetes-map-type: atomic - namespaceSelector: - description: A label query over the set of namespaces that the term applies to. The term is applied to the union of the namespaces selected by this field and the ones listed in the namespaces field. null selector and null or empty namespaces list means "this pod's namespace". An empty selector ({}) matches all namespaces. - type: object - properties: - matchExpressions: - description: matchExpressions is a list of label selector requirements. The requirements are ANDed. - type: array - items: - description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. - type: object - required: - - key - - operator - properties: - key: - description: key is the label key that the selector applies to. - type: string - operator: - description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. - type: array - items: - type: string - matchLabels: - description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - additionalProperties: - type: string - x-kubernetes-map-type: atomic - namespaces: - description: namespaces specifies a static list of namespace names that the term applies to. The term is applied to the union of the namespaces listed in this field and the ones selected by namespaceSelector. null or empty namespaces list and null namespaceSelector means "this pod's namespace". - type: array - items: - type: string - topologyKey: - description: This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching the labelSelector in the specified namespaces, where co-located is defined as running on a node whose value of the label with key topologyKey matches that of any node on which any of the selected pods is running. Empty topologyKey is not allowed. - type: string - podAntiAffinity: - description: Describes pod anti-affinity scheduling rules (e.g. avoid putting this pod in the same node, zone, etc. as some other pod(s)). - type: object - properties: - preferredDuringSchedulingIgnoredDuringExecution: - description: The scheduler will prefer to schedule pods to nodes that satisfy the anti-affinity expressions specified by this field, but it may choose a node that violates one or more of the expressions. The node that is most preferred is the one with the greatest sum of weights, i.e. for each node that meets all of the scheduling requirements (resource request, requiredDuringScheduling anti-affinity expressions, etc.), compute a sum by iterating through the elements of this field and adding "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the node(s) with the highest sum are the most preferred. - type: array - items: - description: The weights of all of the matched WeightedPodAffinityTerm fields are added per-node to find the most preferred node(s) - type: object - required: - - podAffinityTerm - - weight - properties: - podAffinityTerm: - description: Required. A pod affinity term, associated with the corresponding weight. - type: object - required: - - topologyKey - properties: - labelSelector: - description: A label query over a set of resources, in this case pods. - type: object - properties: - matchExpressions: - description: matchExpressions is a list of label selector requirements. The requirements are ANDed. - type: array - items: - description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. - type: object - required: - - key - - operator - properties: - key: - description: key is the label key that the selector applies to. - type: string - operator: - description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. - type: array - items: - type: string - matchLabels: - description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - additionalProperties: - type: string - x-kubernetes-map-type: atomic - namespaceSelector: - description: A label query over the set of namespaces that the term applies to. The term is applied to the union of the namespaces selected by this field and the ones listed in the namespaces field. null selector and null or empty namespaces list means "this pod's namespace". An empty selector ({}) matches all namespaces. - type: object - properties: - matchExpressions: - description: matchExpressions is a list of label selector requirements. The requirements are ANDed. - type: array - items: - description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. - type: object - required: - - key - - operator - properties: - key: - description: key is the label key that the selector applies to. - type: string - operator: - description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. - type: array - items: - type: string - matchLabels: - description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - additionalProperties: - type: string - x-kubernetes-map-type: atomic - namespaces: - description: namespaces specifies a static list of namespace names that the term applies to. The term is applied to the union of the namespaces listed in this field and the ones selected by namespaceSelector. null or empty namespaces list and null namespaceSelector means "this pod's namespace". - type: array - items: - type: string - topologyKey: - description: This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching the labelSelector in the specified namespaces, where co-located is defined as running on a node whose value of the label with key topologyKey matches that of any node on which any of the selected pods is running. Empty topologyKey is not allowed. - type: string - weight: - description: weight associated with matching the corresponding podAffinityTerm, in the range 1-100. - type: integer - format: int32 - requiredDuringSchedulingIgnoredDuringExecution: - description: If the anti-affinity requirements specified by this field are not met at scheduling time, the pod will not be scheduled onto the node. If the anti-affinity requirements specified by this field cease to be met at some point during pod execution (e.g. due to a pod label update), the system may or may not try to eventually evict the pod from its node. When there are multiple elements, the lists of nodes corresponding to each podAffinityTerm are intersected, i.e. all terms must be satisfied. - type: array - items: - description: Defines a set of pods (namely those matching the labelSelector relative to the given namespace(s)) that this pod should be co-located (affinity) or not co-located (anti-affinity) with, where co-located is defined as running on a node whose value of the label with key matches that of any node on which a pod of the set of pods is running - type: object - required: - - topologyKey - properties: - labelSelector: - description: A label query over a set of resources, in this case pods. - type: object - properties: - matchExpressions: - description: matchExpressions is a list of label selector requirements. The requirements are ANDed. - type: array - items: - description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. - type: object - required: - - key - - operator - properties: - key: - description: key is the label key that the selector applies to. - type: string - operator: - description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. - type: array - items: - type: string - matchLabels: - description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - additionalProperties: - type: string - x-kubernetes-map-type: atomic - namespaceSelector: - description: A label query over the set of namespaces that the term applies to. The term is applied to the union of the namespaces selected by this field and the ones listed in the namespaces field. null selector and null or empty namespaces list means "this pod's namespace". An empty selector ({}) matches all namespaces. - type: object - properties: - matchExpressions: - description: matchExpressions is a list of label selector requirements. The requirements are ANDed. - type: array - items: - description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. - type: object - required: - - key - - operator - properties: - key: - description: key is the label key that the selector applies to. - type: string - operator: - description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. - type: array - items: - type: string - matchLabels: - description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - additionalProperties: - type: string - x-kubernetes-map-type: atomic - namespaces: - description: namespaces specifies a static list of namespace names that the term applies to. The term is applied to the union of the namespaces listed in this field and the ones selected by namespaceSelector. null or empty namespaces list and null namespaceSelector means "this pod's namespace". - type: array - items: - type: string - topologyKey: - description: This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching the labelSelector in the specified namespaces, where co-located is defined as running on a node whose value of the label with key topologyKey matches that of any node on which any of the selected pods is running. Empty topologyKey is not allowed. - type: string - nodeSelector: - description: '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/' - type: object - additionalProperties: - type: string - priorityClassName: - description: If specified, the pod's priorityClassName. - type: string - serviceAccountName: - description: If specified, the pod's service account - type: string - tolerations: - description: If specified, the pod's tolerations. - type: array - items: - description: The pod this Toleration is attached to tolerates any taint that matches the triple using the matching operator . - type: object - properties: - effect: - description: Effect indicates the taint effect to match. Empty means match all taint effects. When specified, allowed values are NoSchedule, PreferNoSchedule and NoExecute. - type: string - key: - description: Key is the taint key that the toleration applies to. Empty means match all taint keys. If the key is empty, operator must be Exists; this combination means to match all values and all keys. - type: string - operator: - description: Operator represents a key's relationship to the value. Valid operators are Exists and Equal. Defaults to Equal. Exists is equivalent to wildcard for value, so that a pod can tolerate all taints of a particular category. - type: string - tolerationSeconds: - description: TolerationSeconds represents the period of time the toleration (which must be of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default, it is not set, which means tolerate the taint forever (do not evict). Zero and negative values will be treated as 0 (evict immediately) by the system. - type: integer - format: int64 - value: - description: Value is the taint value the toleration matches to. If the operator is Exists, the value should be empty, otherwise just a regular string. - type: string - serviceType: - description: Optional service type for Kubernetes solver service. Supported values are NodePort or ClusterIP. If unset, defaults to NodePort. - type: string - selector: - description: 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. - type: object - properties: - dnsNames: - description: 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. - type: array - items: - type: string - dnsZones: - description: 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. - type: array - items: - type: string - matchLabels: - description: A label selector that is used to refine the set of certificate's that this challenge solver will apply to. - type: object - additionalProperties: - type: string - ca: - description: 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. - type: object - required: - - secretName - properties: - crlDistributionPoints: - description: 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. - type: array - items: - type: string - ocspServers: - description: 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". - type: array - items: - type: string - secretName: - description: SecretName is the name of the secret used to sign Certificates issued by this Issuer. - type: string - selfSigned: - description: SelfSigned configures this issuer to 'self sign' certificates using the private key used to create the CertificateRequest object. - type: object - properties: - crlDistributionPoints: - description: 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. - type: array - items: - type: string - vault: - description: Vault configures this issuer to sign certificates using a HashiCorp Vault PKI backend. - type: object - required: - - auth - - path - - server - properties: - auth: - description: Auth configures how cert-manager authenticates with the Vault server. - type: object - properties: - appRole: - description: AppRole authenticates with Vault using the App Role auth mechanism, with the role and secret stored in a Kubernetes Secret resource. - type: object - required: - - path - - roleId - - secretRef - properties: - path: - description: 'Path where the App Role authentication backend is mounted in Vault, e.g: "approle"' - type: string - roleId: - description: RoleID configured in the App Role authentication backend when setting up the authentication backend in Vault. - type: string - secretRef: - description: 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. - type: object - required: - - name - properties: - key: - description: 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. - type: string - name: - description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - kubernetes: - description: Kubernetes authenticates with Vault by passing the ServiceAccount token stored in the named Secret resource to the Vault server. - type: object - required: - - role - - secretRef - properties: - mountPath: - description: 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. - type: string - role: - description: A required field containing the Vault Role to assume. A Role binds a Kubernetes ServiceAccount with a set of Vault policies. - type: string - secretRef: - description: The required Secret field containing a Kubernetes ServiceAccount JWT used for authenticating with Vault. Use of 'ambient credentials' is not supported. - type: object - required: - - name - properties: - key: - description: 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. - type: string - name: - description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - tokenSecretRef: - description: TokenSecretRef authenticates with Vault by presenting a token. - type: object - required: - - name - properties: - key: - description: 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. - type: string - name: - description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - caBundle: - description: PEM-encoded CA bundle (base64-encoded) used to validate Vault server certificate. Only used if the Server URL is using HTTPS protocol. This parameter is ignored for plain HTTP protocol connection. If not set the system root certificates are used to validate the TLS connection. Mutually exclusive with CABundleSecretRef. If neither CABundle nor CABundleSecretRef are defined, the cert-manager controller system root certificates are used to validate the TLS connection. - type: string - format: byte - caBundleSecretRef: - description: CABundleSecretRef is a reference to a Secret which contains the CABundle which will be used when connecting to Vault when using HTTPS. Mutually exclusive with CABundle. If neither CABundleSecretRef nor CABundle are defined, the cert-manager controller system root certificates are used to validate the TLS connection. If no key for the Secret is specified, cert-manager will default to 'ca.crt'. - type: object - required: - - name - properties: - key: - description: 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. - type: string - name: - description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - namespace: - description: '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' - type: string - path: - description: 'Path is the mount path of the Vault PKI backend''s `sign` endpoint, e.g: "my_pki_mount/sign/my-role-name".' - type: string - server: - description: 'Server is the connection address for the Vault server, e.g: "https://vault.example.com:8200".' - type: string - venafi: - description: Venafi configures this issuer to sign certificates using a Venafi TPP or Venafi Cloud policy zone. - type: object - required: - - zone - properties: - cloud: - description: Cloud specifies the Venafi cloud configuration settings. Only one of TPP or Cloud may be specified. - type: object - required: - - apiTokenSecretRef - properties: - apiTokenSecretRef: - description: APITokenSecretRef is a secret key selector for the Venafi Cloud API token. - type: object - required: - - name - properties: - key: - description: 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. - type: string - name: - description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - url: - description: URL is the base URL for Venafi Cloud. Defaults to "https://api.venafi.cloud/v1". - type: string - tpp: - description: TPP specifies Trust Protection Platform configuration settings. Only one of TPP or Cloud may be specified. - type: object - required: - - credentialsRef - - url - properties: - caBundle: - description: CABundle is a PEM encoded TLS certificate to use to verify connections to the TPP instance. If specified, system roots will not be used and the issuing CA for the TPP instance must be verifiable using the provided root. If not specified, the connection will be verified using the cert-manager system root certificates. - type: string - format: byte - credentialsRef: - description: 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'. - type: object - required: - - name - properties: - name: - description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - url: - description: 'URL is the base URL for the vedsdk endpoint of the Venafi TPP instance, for example: "https://tpp.example.com/vedsdk".' - type: string - zone: - description: 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. - type: string - status: - description: Status of the ClusterIssuer. This is set and managed automatically. - type: object - properties: - acme: - description: ACME specific status options. This field should only be set if the Issuer is configured to use an ACME server to issue certificates. - type: object - properties: - lastRegisteredEmail: - 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 - type: string - uri: - description: URI is the unique account identifier, which can also be used to retrieve account details from the CA - type: string - conditions: - description: List of status conditions to indicate the status of a CertificateRequest. Known condition types are `Ready`. - type: array - items: - description: IssuerCondition contains condition information for an Issuer. - type: object - required: - - status - - type - properties: - lastTransitionTime: - description: LastTransitionTime is the timestamp corresponding to the last status change of this condition. - type: string - format: date-time - message: - description: Message is a human readable description of the details of the last transition, complementing reason. - type: string - observedGeneration: - description: 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. - type: integer - format: int64 - reason: - description: Reason is a brief machine readable explanation for the condition's last transition. - type: string - status: - description: Status of the condition, one of (`True`, `False`, `Unknown`). - type: string - enum: - - "True" - - "False" - - Unknown - type: - description: Type of the condition, known values are (`Ready`). - type: string - x-kubernetes-list-map-keys: - - type - x-kubernetes-list-type: map - served: true - storage: true diff --git a/deploy/crds/crd-issuers.yaml b/deploy/crds/crd-issuers.yaml deleted file mode 100644 index fb79d488989..00000000000 --- a/deploy/crds/crd-issuers.yaml +++ /dev/null @@ -1,1288 +0,0 @@ -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - name: issuers.cert-manager.io - labels: - app: '{{ template "cert-manager.name" . }}' - app.kubernetes.io/name: '{{ template "cert-manager.name" . }}' - app.kubernetes.io/instance: '{{ .Release.Name }}' - # Generated labels {{- include "labels" . | nindent 4 }} -spec: - group: cert-manager.io - names: - kind: Issuer - listKind: IssuerList - plural: issuers - singular: issuer - categories: - - cert-manager - scope: Namespaced - versions: - - name: v1 - subresources: - status: {} - additionalPrinterColumns: - - jsonPath: .status.conditions[?(@.type=="Ready")].status - name: Ready - type: string - - jsonPath: .status.conditions[?(@.type=="Ready")].message - name: Status - priority: 1 - type: string - - jsonPath: .metadata.creationTimestamp - description: CreationTimestamp is a timestamp representing the server time when this object was created. It is not guaranteed to be set in happens-before order across separate operations. Clients may not set this value. It is represented in RFC3339 form and is in UTC. - name: Age - type: date - schema: - openAPIV3Schema: - description: 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. - type: object - required: - - spec - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: Desired state of the Issuer resource. - type: object - properties: - acme: - description: ACME configures this issuer to communicate with a RFC8555 (ACME) server to obtain signed x509 certificates. - type: object - required: - - privateKeySecretRef - - server - properties: - disableAccountKeyGeneration: - description: 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. - type: boolean - email: - description: 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. - type: string - enableDurationFeature: - description: 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. - type: boolean - externalAccountBinding: - description: 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. - type: object - required: - - keyID - - keySecretRef - properties: - keyAlgorithm: - description: '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.' - type: string - enum: - - HS256 - - HS384 - - HS512 - keyID: - description: keyID is the ID of the CA key that the External Account is bound to. - type: string - keySecretRef: - description: 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. - type: object - required: - - name - properties: - key: - description: 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. - type: string - name: - description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - preferredChain: - description: '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' - type: string - maxLength: 64 - privateKeySecretRef: - description: 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. - type: object - required: - - name - properties: - key: - description: 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. - type: string - name: - description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - server: - description: '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.' - type: string - skipTLSVerify: - description: Enables or disables validation of the ACME server TLS certificate. If true, requests to the ACME server will not have their TLS certificate validated (i.e. insecure connections will be allowed). Only enable this option in development environments. The cert-manager system installed roots will be used to verify connections to the ACME server if this is false. Defaults to false. - type: boolean - solvers: - description: '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/' - type: array - items: - description: 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. - type: object - properties: - dns01: - description: Configures cert-manager to attempt to complete authorizations by performing the DNS01 challenge flow. - type: object - properties: - acmeDNS: - description: Use the 'ACME DNS' (https://github.com/joohoi/acme-dns) API to manage DNS01 challenge records. - type: object - required: - - accountSecretRef - - host - properties: - accountSecretRef: - description: A reference to a specific 'key' within a Secret resource. In some instances, `key` is a required field. - type: object - required: - - name - properties: - key: - description: 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. - type: string - name: - description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - host: - type: string - akamai: - description: Use the Akamai DNS zone management API to manage DNS01 challenge records. - type: object - required: - - accessTokenSecretRef - - clientSecretSecretRef - - clientTokenSecretRef - - serviceConsumerDomain - properties: - accessTokenSecretRef: - description: A reference to a specific 'key' within a Secret resource. In some instances, `key` is a required field. - type: object - required: - - name - properties: - key: - description: 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. - type: string - name: - description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - clientSecretSecretRef: - description: A reference to a specific 'key' within a Secret resource. In some instances, `key` is a required field. - type: object - required: - - name - properties: - key: - description: 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. - type: string - name: - description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - clientTokenSecretRef: - description: A reference to a specific 'key' within a Secret resource. In some instances, `key` is a required field. - type: object - required: - - name - properties: - key: - description: 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. - type: string - name: - description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - serviceConsumerDomain: - type: string - azureDNS: - description: Use the Microsoft Azure DNS API to manage DNS01 challenge records. - type: object - required: - - resourceGroupName - - subscriptionID - properties: - clientID: - description: if both this and ClientSecret are left unset MSI will be used - type: string - clientSecretSecretRef: - description: if both this and ClientID are left unset MSI will be used - type: object - required: - - name - properties: - key: - description: 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. - type: string - name: - description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - environment: - description: name of the Azure environment (default AzurePublicCloud) - type: string - enum: - - AzurePublicCloud - - AzureChinaCloud - - AzureGermanCloud - - AzureUSGovernmentCloud - hostedZoneName: - description: name of the DNS zone that should be used - type: string - managedIdentity: - description: managed identity configuration, can not be used at the same time as clientID, clientSecretSecretRef or tenantID - type: object - properties: - clientID: - description: client ID of the managed identity, can not be used at the same time as resourceID - type: string - resourceID: - description: resource ID of the managed identity, can not be used at the same time as clientID - type: string - resourceGroupName: - description: resource group the DNS zone is located in - type: string - subscriptionID: - description: ID of the Azure subscription - type: string - tenantID: - description: when specifying ClientID and ClientSecret then this field is also needed - type: string - cloudDNS: - description: Use the Google Cloud DNS API to manage DNS01 challenge records. - type: object - required: - - project - properties: - hostedZoneName: - description: 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. - type: string - project: - type: string - serviceAccountSecretRef: - description: A reference to a specific 'key' within a Secret resource. In some instances, `key` is a required field. - type: object - required: - - name - properties: - key: - description: 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. - type: string - name: - description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - cloudflare: - description: Use the Cloudflare API to manage DNS01 challenge records. - type: object - properties: - apiKeySecretRef: - description: '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.' - type: object - required: - - name - properties: - key: - description: 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. - type: string - name: - description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - apiTokenSecretRef: - description: API token used to authenticate with Cloudflare. - type: object - required: - - name - properties: - key: - description: 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. - type: string - name: - description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - email: - description: Email of the account, only required when using API key based authentication. - type: string - cnameStrategy: - description: CNAMEStrategy configures how the DNS01 provider should handle CNAME records when found in DNS zones. - type: string - enum: - - None - - Follow - digitalocean: - description: Use the DigitalOcean DNS API to manage DNS01 challenge records. - type: object - required: - - tokenSecretRef - properties: - tokenSecretRef: - description: A reference to a specific 'key' within a Secret resource. In some instances, `key` is a required field. - type: object - required: - - name - properties: - key: - description: 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. - type: string - name: - description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - rfc2136: - description: Use RFC2136 ("Dynamic Updates in the Domain Name System") (https://datatracker.ietf.org/doc/rfc2136/) to manage DNS01 challenge records. - type: object - required: - - nameserver - properties: - nameserver: - description: 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. - type: string - tsigAlgorithm: - description: '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``.' - type: string - tsigKeyName: - description: The TSIG Key name configured in the DNS. If ``tsigSecretSecretRef`` is defined, this field is required. - type: string - tsigSecretSecretRef: - description: The name of the secret containing the TSIG value. If ``tsigKeyName`` is defined, this field is required. - type: object - required: - - name - properties: - key: - description: 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. - type: string - name: - description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - route53: - description: Use the AWS Route53 API to manage DNS01 challenge records. - type: object - required: - - region - properties: - accessKeyID: - description: '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' - type: string - accessKeyIDSecretRef: - description: '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' - type: object - required: - - name - properties: - key: - description: 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. - type: string - name: - description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - hostedZoneID: - description: If set, the provider will manage only this zone in Route53 and will not do an lookup using the route53:ListHostedZonesByName api call. - type: string - region: - description: Always set the region when using AccessKeyID and SecretAccessKey - type: string - role: - description: 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 - type: string - secretAccessKeySecretRef: - description: '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' - type: object - required: - - name - properties: - key: - description: 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. - type: string - name: - description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - webhook: - description: Configure an external webhook based DNS01 challenge solver to manage DNS01 challenge records. - type: object - required: - - groupName - - solverName - properties: - config: - description: 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. - x-kubernetes-preserve-unknown-fields: true - groupName: - description: 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. - type: string - solverName: - description: 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'. - type: string - http01: - description: 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. - type: object - properties: - gatewayHTTPRoute: - description: 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. - type: object - properties: - labels: - description: Custom labels that will be applied to HTTPRoutes created by cert-manager while solving HTTP-01 challenges. - type: object - additionalProperties: - type: string - parentRefs: - description: '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/v1alpha2/api-types/httproute/#attaching-to-gateways' - type: array - items: - description: "ParentReference identifies an API object (usually a Gateway) that can be considered a parent of this resource (usually a route). The only kind of parent resource with \"Core\" support is Gateway. This API may be extended in the future to support additional kinds of parent resources, such as HTTPRoute. \n The API object must be valid in the cluster; the Group and Kind must be registered in the cluster for this reference to be valid." - type: object - required: - - name - properties: - group: - description: "Group is the group of the referent. \n Support: Core" - type: string - default: gateway.networking.k8s.io - maxLength: 253 - pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - kind: - description: "Kind is kind of the referent. \n Support: Core (Gateway) \n Support: Custom (Other Resources)" - type: string - default: Gateway - maxLength: 63 - minLength: 1 - pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ - name: - description: "Name is the name of the referent. \n Support: Core" - type: string - maxLength: 253 - minLength: 1 - namespace: - description: "Namespace is the namespace of the referent. When unspecified (or empty string), this refers to the local namespace of the Route. \n Support: Core" - type: string - maxLength: 63 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ - port: - description: "Port is the network port this Route targets. It can be interpreted differently based on the type of parent resource. \n When the parent resource is a Gateway, this targets all listeners listening on the specified port that also support this kind of Route(and select this Route). It's not recommended to set `Port` unless the networking behaviors specified in a Route must apply to a specific port as opposed to a listener(s) whose port(s) may be changed. When both Port and SectionName are specified, the name and port of the selected listener must match both specified values. \n Implementations MAY choose to support other parent resources. Implementations supporting other types of parent resources MUST clearly document how/if Port is interpreted. \n For the purpose of status, an attachment is considered successful as long as the parent resource accepts it partially. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Extended \n " - type: integer - format: int32 - maximum: 65535 - minimum: 1 - sectionName: - description: "SectionName is the name of a section within the target resource. In the following resources, SectionName is interpreted as the following: \n * Gateway: Listener Name. When both Port (experimental) and SectionName are specified, the name and port of the selected listener must match both specified values. \n Implementations MAY choose to support attaching Routes to other resources. If that is the case, they MUST clearly document how SectionName is interpreted. \n When unspecified (empty string), this will reference the entire resource. For the purpose of status, an attachment is considered successful if at least one section in the parent resource accepts it. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway. \n Support: Core" - type: string - maxLength: 253 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - serviceType: - description: Optional service type for Kubernetes solver service. Supported values are NodePort or ClusterIP. If unset, defaults to NodePort. - type: string - ingress: - description: 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. - type: object - properties: - class: - description: 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. - type: string - ingressTemplate: - description: Optional ingress template used to configure the ACME challenge solver ingress used for HTTP01 challenges. - type: object - properties: - metadata: - description: 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. - type: object - properties: - annotations: - description: Annotations that should be added to the created ACME HTTP01 solver ingress. - type: object - additionalProperties: - type: string - labels: - description: Labels that should be added to the created ACME HTTP01 solver ingress. - type: object - additionalProperties: - type: string - name: - description: 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. - type: string - podTemplate: - description: Optional pod template used to configure the ACME challenge solver pods used for HTTP01 challenges. - type: object - properties: - metadata: - description: 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. - type: object - properties: - annotations: - description: Annotations that should be added to the create ACME HTTP01 solver pods. - type: object - additionalProperties: - type: string - labels: - description: Labels that should be added to the created ACME HTTP01 solver pods. - type: object - additionalProperties: - type: string - spec: - description: 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. - type: object - properties: - affinity: - description: If specified, the pod's scheduling constraints - type: object - properties: - nodeAffinity: - description: Describes node affinity scheduling rules for the pod. - type: object - properties: - preferredDuringSchedulingIgnoredDuringExecution: - description: The scheduler will prefer to schedule pods to nodes that satisfy the affinity expressions specified by this field, but it may choose a node that violates one or more of the expressions. The node that is most preferred is the one with the greatest sum of weights, i.e. for each node that meets all of the scheduling requirements (resource request, requiredDuringScheduling affinity expressions, etc.), compute a sum by iterating through the elements of this field and adding "weight" to the sum if the node matches the corresponding matchExpressions; the node(s) with the highest sum are the most preferred. - type: array - items: - description: An empty preferred scheduling term matches all objects with implicit weight 0 (i.e. it's a no-op). A null preferred scheduling term matches no objects (i.e. is also a no-op). - type: object - required: - - preference - - weight - properties: - preference: - description: A node selector term, associated with the corresponding weight. - type: object - properties: - matchExpressions: - description: A list of node selector requirements by node's labels. - type: array - items: - description: A node selector requirement is a selector that contains values, a key, and an operator that relates the key and values. - type: object - required: - - key - - operator - properties: - key: - description: The label key that the selector applies to. - type: string - operator: - description: Represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. - type: string - values: - description: An array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. If the operator is Gt or Lt, the values array must have a single element, which will be interpreted as an integer. This array is replaced during a strategic merge patch. - type: array - items: - type: string - matchFields: - description: A list of node selector requirements by node's fields. - type: array - items: - description: A node selector requirement is a selector that contains values, a key, and an operator that relates the key and values. - type: object - required: - - key - - operator - properties: - key: - description: The label key that the selector applies to. - type: string - operator: - description: Represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. - type: string - values: - description: An array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. If the operator is Gt or Lt, the values array must have a single element, which will be interpreted as an integer. This array is replaced during a strategic merge patch. - type: array - items: - type: string - x-kubernetes-map-type: atomic - weight: - description: Weight associated with matching the corresponding nodeSelectorTerm, in the range 1-100. - type: integer - format: int32 - requiredDuringSchedulingIgnoredDuringExecution: - description: If the affinity requirements specified by this field are not met at scheduling time, the pod will not be scheduled onto the node. If the affinity requirements specified by this field cease to be met at some point during pod execution (e.g. due to an update), the system may or may not try to eventually evict the pod from its node. - type: object - required: - - nodeSelectorTerms - properties: - nodeSelectorTerms: - description: Required. A list of node selector terms. The terms are ORed. - type: array - items: - description: A null or empty node selector term matches no objects. The requirements of them are ANDed. The TopologySelectorTerm type implements a subset of the NodeSelectorTerm. - type: object - properties: - matchExpressions: - description: A list of node selector requirements by node's labels. - type: array - items: - description: A node selector requirement is a selector that contains values, a key, and an operator that relates the key and values. - type: object - required: - - key - - operator - properties: - key: - description: The label key that the selector applies to. - type: string - operator: - description: Represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. - type: string - values: - description: An array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. If the operator is Gt or Lt, the values array must have a single element, which will be interpreted as an integer. This array is replaced during a strategic merge patch. - type: array - items: - type: string - matchFields: - description: A list of node selector requirements by node's fields. - type: array - items: - description: A node selector requirement is a selector that contains values, a key, and an operator that relates the key and values. - type: object - required: - - key - - operator - properties: - key: - description: The label key that the selector applies to. - type: string - operator: - description: Represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt. - type: string - values: - description: An array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. If the operator is Gt or Lt, the values array must have a single element, which will be interpreted as an integer. This array is replaced during a strategic merge patch. - type: array - items: - type: string - x-kubernetes-map-type: atomic - x-kubernetes-map-type: atomic - podAffinity: - description: Describes pod affinity scheduling rules (e.g. co-locate this pod in the same node, zone, etc. as some other pod(s)). - type: object - properties: - preferredDuringSchedulingIgnoredDuringExecution: - description: The scheduler will prefer to schedule pods to nodes that satisfy the affinity expressions specified by this field, but it may choose a node that violates one or more of the expressions. The node that is most preferred is the one with the greatest sum of weights, i.e. for each node that meets all of the scheduling requirements (resource request, requiredDuringScheduling affinity expressions, etc.), compute a sum by iterating through the elements of this field and adding "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the node(s) with the highest sum are the most preferred. - type: array - items: - description: The weights of all of the matched WeightedPodAffinityTerm fields are added per-node to find the most preferred node(s) - type: object - required: - - podAffinityTerm - - weight - properties: - podAffinityTerm: - description: Required. A pod affinity term, associated with the corresponding weight. - type: object - required: - - topologyKey - properties: - labelSelector: - description: A label query over a set of resources, in this case pods. - type: object - properties: - matchExpressions: - description: matchExpressions is a list of label selector requirements. The requirements are ANDed. - type: array - items: - description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. - type: object - required: - - key - - operator - properties: - key: - description: key is the label key that the selector applies to. - type: string - operator: - description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. - type: array - items: - type: string - matchLabels: - description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - additionalProperties: - type: string - x-kubernetes-map-type: atomic - namespaceSelector: - description: A label query over the set of namespaces that the term applies to. The term is applied to the union of the namespaces selected by this field and the ones listed in the namespaces field. null selector and null or empty namespaces list means "this pod's namespace". An empty selector ({}) matches all namespaces. - type: object - properties: - matchExpressions: - description: matchExpressions is a list of label selector requirements. The requirements are ANDed. - type: array - items: - description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. - type: object - required: - - key - - operator - properties: - key: - description: key is the label key that the selector applies to. - type: string - operator: - description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. - type: array - items: - type: string - matchLabels: - description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - additionalProperties: - type: string - x-kubernetes-map-type: atomic - namespaces: - description: namespaces specifies a static list of namespace names that the term applies to. The term is applied to the union of the namespaces listed in this field and the ones selected by namespaceSelector. null or empty namespaces list and null namespaceSelector means "this pod's namespace". - type: array - items: - type: string - topologyKey: - description: This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching the labelSelector in the specified namespaces, where co-located is defined as running on a node whose value of the label with key topologyKey matches that of any node on which any of the selected pods is running. Empty topologyKey is not allowed. - type: string - weight: - description: weight associated with matching the corresponding podAffinityTerm, in the range 1-100. - type: integer - format: int32 - requiredDuringSchedulingIgnoredDuringExecution: - description: If the affinity requirements specified by this field are not met at scheduling time, the pod will not be scheduled onto the node. If the affinity requirements specified by this field cease to be met at some point during pod execution (e.g. due to a pod label update), the system may or may not try to eventually evict the pod from its node. When there are multiple elements, the lists of nodes corresponding to each podAffinityTerm are intersected, i.e. all terms must be satisfied. - type: array - items: - description: Defines a set of pods (namely those matching the labelSelector relative to the given namespace(s)) that this pod should be co-located (affinity) or not co-located (anti-affinity) with, where co-located is defined as running on a node whose value of the label with key matches that of any node on which a pod of the set of pods is running - type: object - required: - - topologyKey - properties: - labelSelector: - description: A label query over a set of resources, in this case pods. - type: object - properties: - matchExpressions: - description: matchExpressions is a list of label selector requirements. The requirements are ANDed. - type: array - items: - description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. - type: object - required: - - key - - operator - properties: - key: - description: key is the label key that the selector applies to. - type: string - operator: - description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. - type: array - items: - type: string - matchLabels: - description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - additionalProperties: - type: string - x-kubernetes-map-type: atomic - namespaceSelector: - description: A label query over the set of namespaces that the term applies to. The term is applied to the union of the namespaces selected by this field and the ones listed in the namespaces field. null selector and null or empty namespaces list means "this pod's namespace". An empty selector ({}) matches all namespaces. - type: object - properties: - matchExpressions: - description: matchExpressions is a list of label selector requirements. The requirements are ANDed. - type: array - items: - description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. - type: object - required: - - key - - operator - properties: - key: - description: key is the label key that the selector applies to. - type: string - operator: - description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. - type: array - items: - type: string - matchLabels: - description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - additionalProperties: - type: string - x-kubernetes-map-type: atomic - namespaces: - description: namespaces specifies a static list of namespace names that the term applies to. The term is applied to the union of the namespaces listed in this field and the ones selected by namespaceSelector. null or empty namespaces list and null namespaceSelector means "this pod's namespace". - type: array - items: - type: string - topologyKey: - description: This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching the labelSelector in the specified namespaces, where co-located is defined as running on a node whose value of the label with key topologyKey matches that of any node on which any of the selected pods is running. Empty topologyKey is not allowed. - type: string - podAntiAffinity: - description: Describes pod anti-affinity scheduling rules (e.g. avoid putting this pod in the same node, zone, etc. as some other pod(s)). - type: object - properties: - preferredDuringSchedulingIgnoredDuringExecution: - description: The scheduler will prefer to schedule pods to nodes that satisfy the anti-affinity expressions specified by this field, but it may choose a node that violates one or more of the expressions. The node that is most preferred is the one with the greatest sum of weights, i.e. for each node that meets all of the scheduling requirements (resource request, requiredDuringScheduling anti-affinity expressions, etc.), compute a sum by iterating through the elements of this field and adding "weight" to the sum if the node has pods which matches the corresponding podAffinityTerm; the node(s) with the highest sum are the most preferred. - type: array - items: - description: The weights of all of the matched WeightedPodAffinityTerm fields are added per-node to find the most preferred node(s) - type: object - required: - - podAffinityTerm - - weight - properties: - podAffinityTerm: - description: Required. A pod affinity term, associated with the corresponding weight. - type: object - required: - - topologyKey - properties: - labelSelector: - description: A label query over a set of resources, in this case pods. - type: object - properties: - matchExpressions: - description: matchExpressions is a list of label selector requirements. The requirements are ANDed. - type: array - items: - description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. - type: object - required: - - key - - operator - properties: - key: - description: key is the label key that the selector applies to. - type: string - operator: - description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. - type: array - items: - type: string - matchLabels: - description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - additionalProperties: - type: string - x-kubernetes-map-type: atomic - namespaceSelector: - description: A label query over the set of namespaces that the term applies to. The term is applied to the union of the namespaces selected by this field and the ones listed in the namespaces field. null selector and null or empty namespaces list means "this pod's namespace". An empty selector ({}) matches all namespaces. - type: object - properties: - matchExpressions: - description: matchExpressions is a list of label selector requirements. The requirements are ANDed. - type: array - items: - description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. - type: object - required: - - key - - operator - properties: - key: - description: key is the label key that the selector applies to. - type: string - operator: - description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. - type: array - items: - type: string - matchLabels: - description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - additionalProperties: - type: string - x-kubernetes-map-type: atomic - namespaces: - description: namespaces specifies a static list of namespace names that the term applies to. The term is applied to the union of the namespaces listed in this field and the ones selected by namespaceSelector. null or empty namespaces list and null namespaceSelector means "this pod's namespace". - type: array - items: - type: string - topologyKey: - description: This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching the labelSelector in the specified namespaces, where co-located is defined as running on a node whose value of the label with key topologyKey matches that of any node on which any of the selected pods is running. Empty topologyKey is not allowed. - type: string - weight: - description: weight associated with matching the corresponding podAffinityTerm, in the range 1-100. - type: integer - format: int32 - requiredDuringSchedulingIgnoredDuringExecution: - description: If the anti-affinity requirements specified by this field are not met at scheduling time, the pod will not be scheduled onto the node. If the anti-affinity requirements specified by this field cease to be met at some point during pod execution (e.g. due to a pod label update), the system may or may not try to eventually evict the pod from its node. When there are multiple elements, the lists of nodes corresponding to each podAffinityTerm are intersected, i.e. all terms must be satisfied. - type: array - items: - description: Defines a set of pods (namely those matching the labelSelector relative to the given namespace(s)) that this pod should be co-located (affinity) or not co-located (anti-affinity) with, where co-located is defined as running on a node whose value of the label with key matches that of any node on which a pod of the set of pods is running - type: object - required: - - topologyKey - properties: - labelSelector: - description: A label query over a set of resources, in this case pods. - type: object - properties: - matchExpressions: - description: matchExpressions is a list of label selector requirements. The requirements are ANDed. - type: array - items: - description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. - type: object - required: - - key - - operator - properties: - key: - description: key is the label key that the selector applies to. - type: string - operator: - description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. - type: array - items: - type: string - matchLabels: - description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - additionalProperties: - type: string - x-kubernetes-map-type: atomic - namespaceSelector: - description: A label query over the set of namespaces that the term applies to. The term is applied to the union of the namespaces selected by this field and the ones listed in the namespaces field. null selector and null or empty namespaces list means "this pod's namespace". An empty selector ({}) matches all namespaces. - type: object - properties: - matchExpressions: - description: matchExpressions is a list of label selector requirements. The requirements are ANDed. - type: array - items: - description: A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. - type: object - required: - - key - - operator - properties: - key: - description: key is the label key that the selector applies to. - type: string - operator: - description: operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. - type: string - values: - description: values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch. - type: array - items: - type: string - matchLabels: - description: matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is "key", the operator is "In", and the values array contains only "value". The requirements are ANDed. - type: object - additionalProperties: - type: string - x-kubernetes-map-type: atomic - namespaces: - description: namespaces specifies a static list of namespace names that the term applies to. The term is applied to the union of the namespaces listed in this field and the ones selected by namespaceSelector. null or empty namespaces list and null namespaceSelector means "this pod's namespace". - type: array - items: - type: string - topologyKey: - description: This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching the labelSelector in the specified namespaces, where co-located is defined as running on a node whose value of the label with key topologyKey matches that of any node on which any of the selected pods is running. Empty topologyKey is not allowed. - type: string - nodeSelector: - description: '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/' - type: object - additionalProperties: - type: string - priorityClassName: - description: If specified, the pod's priorityClassName. - type: string - serviceAccountName: - description: If specified, the pod's service account - type: string - tolerations: - description: If specified, the pod's tolerations. - type: array - items: - description: The pod this Toleration is attached to tolerates any taint that matches the triple using the matching operator . - type: object - properties: - effect: - description: Effect indicates the taint effect to match. Empty means match all taint effects. When specified, allowed values are NoSchedule, PreferNoSchedule and NoExecute. - type: string - key: - description: Key is the taint key that the toleration applies to. Empty means match all taint keys. If the key is empty, operator must be Exists; this combination means to match all values and all keys. - type: string - operator: - description: Operator represents a key's relationship to the value. Valid operators are Exists and Equal. Defaults to Equal. Exists is equivalent to wildcard for value, so that a pod can tolerate all taints of a particular category. - type: string - tolerationSeconds: - description: TolerationSeconds represents the period of time the toleration (which must be of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default, it is not set, which means tolerate the taint forever (do not evict). Zero and negative values will be treated as 0 (evict immediately) by the system. - type: integer - format: int64 - value: - description: Value is the taint value the toleration matches to. If the operator is Exists, the value should be empty, otherwise just a regular string. - type: string - serviceType: - description: Optional service type for Kubernetes solver service. Supported values are NodePort or ClusterIP. If unset, defaults to NodePort. - type: string - selector: - description: 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. - type: object - properties: - dnsNames: - description: 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. - type: array - items: - type: string - dnsZones: - description: 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. - type: array - items: - type: string - matchLabels: - description: A label selector that is used to refine the set of certificate's that this challenge solver will apply to. - type: object - additionalProperties: - type: string - ca: - description: 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. - type: object - required: - - secretName - properties: - crlDistributionPoints: - description: 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. - type: array - items: - type: string - ocspServers: - description: 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". - type: array - items: - type: string - secretName: - description: SecretName is the name of the secret used to sign Certificates issued by this Issuer. - type: string - selfSigned: - description: SelfSigned configures this issuer to 'self sign' certificates using the private key used to create the CertificateRequest object. - type: object - properties: - crlDistributionPoints: - description: 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. - type: array - items: - type: string - vault: - description: Vault configures this issuer to sign certificates using a HashiCorp Vault PKI backend. - type: object - required: - - auth - - path - - server - properties: - auth: - description: Auth configures how cert-manager authenticates with the Vault server. - type: object - properties: - appRole: - description: AppRole authenticates with Vault using the App Role auth mechanism, with the role and secret stored in a Kubernetes Secret resource. - type: object - required: - - path - - roleId - - secretRef - properties: - path: - description: 'Path where the App Role authentication backend is mounted in Vault, e.g: "approle"' - type: string - roleId: - description: RoleID configured in the App Role authentication backend when setting up the authentication backend in Vault. - type: string - secretRef: - description: 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. - type: object - required: - - name - properties: - key: - description: 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. - type: string - name: - description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - kubernetes: - description: Kubernetes authenticates with Vault by passing the ServiceAccount token stored in the named Secret resource to the Vault server. - type: object - required: - - role - - secretRef - properties: - mountPath: - description: 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. - type: string - role: - description: A required field containing the Vault Role to assume. A Role binds a Kubernetes ServiceAccount with a set of Vault policies. - type: string - secretRef: - description: The required Secret field containing a Kubernetes ServiceAccount JWT used for authenticating with Vault. Use of 'ambient credentials' is not supported. - type: object - required: - - name - properties: - key: - description: 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. - type: string - name: - description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - tokenSecretRef: - description: TokenSecretRef authenticates with Vault by presenting a token. - type: object - required: - - name - properties: - key: - description: 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. - type: string - name: - description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - caBundle: - description: PEM-encoded CA bundle (base64-encoded) used to validate Vault server certificate. Only used if the Server URL is using HTTPS protocol. This parameter is ignored for plain HTTP protocol connection. If not set the system root certificates are used to validate the TLS connection. Mutually exclusive with CABundleSecretRef. If neither CABundle nor CABundleSecretRef are defined, the cert-manager controller system root certificates are used to validate the TLS connection. - type: string - format: byte - caBundleSecretRef: - description: CABundleSecretRef is a reference to a Secret which contains the CABundle which will be used when connecting to Vault when using HTTPS. Mutually exclusive with CABundle. If neither CABundleSecretRef nor CABundle are defined, the cert-manager controller system root certificates are used to validate the TLS connection. If no key for the Secret is specified, cert-manager will default to 'ca.crt'. - type: object - required: - - name - properties: - key: - description: 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. - type: string - name: - description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - namespace: - description: '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' - type: string - path: - description: 'Path is the mount path of the Vault PKI backend''s `sign` endpoint, e.g: "my_pki_mount/sign/my-role-name".' - type: string - server: - description: 'Server is the connection address for the Vault server, e.g: "https://vault.example.com:8200".' - type: string - venafi: - description: Venafi configures this issuer to sign certificates using a Venafi TPP or Venafi Cloud policy zone. - type: object - required: - - zone - properties: - cloud: - description: Cloud specifies the Venafi cloud configuration settings. Only one of TPP or Cloud may be specified. - type: object - required: - - apiTokenSecretRef - properties: - apiTokenSecretRef: - description: APITokenSecretRef is a secret key selector for the Venafi Cloud API token. - type: object - required: - - name - properties: - key: - description: 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. - type: string - name: - description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - url: - description: URL is the base URL for Venafi Cloud. Defaults to "https://api.venafi.cloud/v1". - type: string - tpp: - description: TPP specifies Trust Protection Platform configuration settings. Only one of TPP or Cloud may be specified. - type: object - required: - - credentialsRef - - url - properties: - caBundle: - description: CABundle is a PEM encoded TLS certificate to use to verify connections to the TPP instance. If specified, system roots will not be used and the issuing CA for the TPP instance must be verifiable using the provided root. If not specified, the connection will be verified using the cert-manager system root certificates. - type: string - format: byte - credentialsRef: - description: 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'. - type: object - required: - - name - properties: - name: - description: 'Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - url: - description: 'URL is the base URL for the vedsdk endpoint of the Venafi TPP instance, for example: "https://tpp.example.com/vedsdk".' - type: string - zone: - description: 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. - type: string - status: - description: Status of the Issuer. This is set and managed automatically. - type: object - properties: - acme: - description: ACME specific status options. This field should only be set if the Issuer is configured to use an ACME server to issue certificates. - type: object - properties: - lastRegisteredEmail: - 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 - type: string - uri: - description: URI is the unique account identifier, which can also be used to retrieve account details from the CA - type: string - conditions: - description: List of status conditions to indicate the status of a CertificateRequest. Known condition types are `Ready`. - type: array - items: - description: IssuerCondition contains condition information for an Issuer. - type: object - required: - - status - - type - properties: - lastTransitionTime: - description: LastTransitionTime is the timestamp corresponding to the last status change of this condition. - type: string - format: date-time - message: - description: Message is a human readable description of the details of the last transition, complementing reason. - type: string - observedGeneration: - description: 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. - type: integer - format: int64 - reason: - description: Reason is a brief machine readable explanation for the condition's last transition. - type: string - status: - description: Status of the condition, one of (`True`, `False`, `Unknown`). - type: string - enum: - - "True" - - "False" - - Unknown - type: - description: Type of the condition, known values are (`Ready`). - type: string - x-kubernetes-list-map-keys: - - type - x-kubernetes-list-type: map - served: true - storage: true diff --git a/deploy/crds/crd-orders.yaml b/deploy/crds/crd-orders.yaml deleted file mode 100644 index 96069909499..00000000000 --- a/deploy/crds/crd-orders.yaml +++ /dev/null @@ -1,179 +0,0 @@ -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - name: orders.acme.cert-manager.io - labels: - app: '{{ template "cert-manager.name" . }}' - app.kubernetes.io/name: '{{ template "cert-manager.name" . }}' - app.kubernetes.io/instance: '{{ .Release.Name }}' - # Generated labels {{- include "labels" . | nindent 4 }} -spec: - group: acme.cert-manager.io - names: - kind: Order - listKind: OrderList - plural: orders - singular: order - categories: - - cert-manager - - cert-manager-acme - scope: Namespaced - versions: - - name: v1 - subresources: - status: {} - additionalPrinterColumns: - - jsonPath: .status.state - name: State - type: string - - jsonPath: .spec.issuerRef.name - name: Issuer - priority: 1 - type: string - - jsonPath: .status.reason - name: Reason - priority: 1 - type: string - - jsonPath: .metadata.creationTimestamp - description: CreationTimestamp is a timestamp representing the server time when this object was created. It is not guaranteed to be set in happens-before order across separate operations. Clients may not set this value. It is represented in RFC3339 form and is in UTC. - name: Age - type: date - schema: - openAPIV3Schema: - description: Order is a type to represent an Order with an ACME server - type: object - required: - - metadata - - spec - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - type: object - required: - - issuerRef - - request - properties: - commonName: - description: 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. - type: string - dnsNames: - description: 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. - type: array - items: - type: string - duration: - description: Duration is the duration for the not after date for the requested certificate. this is set on order creation as pe the ACME spec. - type: string - ipAddresses: - description: 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. - type: array - items: - type: string - issuerRef: - description: 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. - type: object - required: - - name - properties: - group: - description: Group of the resource being referred to. - type: string - kind: - description: Kind of the resource being referred to. - type: string - name: - description: Name of the resource being referred to. - type: string - request: - description: Certificate signing request bytes in DER encoding. This will be used when finalizing the order. This field must be set on the order. - type: string - format: byte - status: - type: object - properties: - authorizations: - description: 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. - type: array - items: - description: 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. - type: object - required: - - url - properties: - challenges: - description: 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. - type: array - items: - description: 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. - type: object - required: - - token - - type - - url - properties: - token: - description: 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: - description: 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. - type: string - url: - description: URL is the URL of this challenge. It can be used to retrieve additional metadata about the Challenge from the ACME server. - type: string - identifier: - description: Identifier is the DNS name to be validated as part of this authorization - type: string - initialState: - description: 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. - type: string - enum: - - valid - - ready - - pending - - processing - - invalid - - expired - - errored - url: - description: URL is the URL of the Authorization that must be completed - type: string - wildcard: - description: 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'. - type: boolean - certificate: - description: 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. - type: string - format: byte - failureTime: - description: FailureTime stores the time that this order failed. This is used to influence garbage collection and back-off. - type: string - format: date-time - finalizeURL: - description: FinalizeURL of the Order. This is used to obtain certificates for this order once it has been completed. - type: string - reason: - description: Reason optionally provides more information about a why the order is in the current state. - type: string - state: - description: State contains the current state of this Order resource. States 'success' and 'expired' are 'final' - type: string - enum: - - valid - - ready - - pending - - processing - - invalid - - expired - - errored - url: - description: 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. - type: string - served: true - storage: true diff --git a/deploy/manifests/README.md b/deploy/manifests/README.md index a692c67d6a1..17aa8fbd4ef 100644 --- a/deploy/manifests/README.md +++ b/deploy/manifests/README.md @@ -11,17 +11,3 @@ automatically from the [official helm chart](../charts/cert-manager). When a new release of cert-manager is cut, these manifests will be automatically generated and published as an asset **attached to the GitHub release**. - -## How can I generate my own manifests? - -If you want to build a copy of your own manifests for testing purposes, you -can do so using Bazel. - -To build the manifests, run: - -```bash -$ bazel build //deploy/manifests:cert-manager.yaml -``` - -This will generate the static deployment manifests at -`bazel-bin/deploy/manifests/cert-manager.yaml`. diff --git a/design/20190708.certificate-request-crd.md b/design/20190708.certificate-request-crd.md index 4d2551abd2c..7f3dc1a2026 100644 --- a/design/20190708.certificate-request-crd.md +++ b/design/20190708.certificate-request-crd.md @@ -11,7 +11,7 @@ approvers: - "@munnerz" editor: "@joshvanl" creation-date: 2019-07-08 -last-updated: 2021-03-24 +last-updated: 2023-03-24 status: implementable --- @@ -52,6 +52,10 @@ status: implementable * [Version Skew Strategy](#version-skew-strategy) +:warning: Parts of this design are out of date with regards to the current implementation. + +See also https://cert-manager.io/docs/concepts/certificaterequest/. + ## Summary Currently, certificates issued via cert-manager rely on the `Certificate` @@ -97,7 +101,7 @@ same code base and repository. - Change the implementation of the `Certificate` controller to rely on the `CertificateRequest` resource to resolve the request. - Update documentation detailing this new behaviour and how it can be used to - develop out-of-tree implantations of an issuer `CertificateRequest` + develop out-of-tree implementations of an issuer `CertificateRequest` controller. - Create a boilerplate/scaffolding example code to help quick start developers on creating a controller with best practices. @@ -107,7 +111,7 @@ same code base and repository. - This proposal does not document or explore possible or planned integrations using this new functionality. - This proposal will not investigate possible alignment or merging with the - Kubernetes internal `CertificateSigningRequest` resource. Although is is of + Kubernetes internal `CertificateSigningRequest` resource. Although it is of interest, the motivation is mostly in order to get a built-in approval workflow for CertificateRequests. The feasibility of being able to implement a solution using the built-in type in the near future however is small, so we'd rather @@ -223,11 +227,9 @@ 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. -When a CertificateRequest has been Denied, it is the responsibility of the -referenced issuer to then add a Ready condition with the status of "False", -along with a relevant Reason and Message. - - +A CertificateRequest that is Denied is considered to be in a final, failed +state. If it was created for an issuance of a Certificate, the associated +issuance will be failed. ##### RBAC Approved and Denied conditions are set by requesting against the `/status` @@ -241,14 +243,14 @@ rejected by the API server. Setting the Approved or Denied conditions are restricted by the approver having sufficient RBAC permissions. These permissions are based upon the request -itself- specifically the request's IssuerRef: +itself - specifically the request's IssuerRef: ```yaml apiGroups: ["cert-manager.io"] resources: ["signers"] verbs: ["approve"] resourceNames: - # namesapced signers + # namespaced signers - "./." # cluster scoped signers - "./" @@ -361,7 +363,7 @@ The webhook will keep a cache of the [Discovery API](https://github.com/kubernetes/client-go/blob/f6ce18ae578c8cca64d14ab9687824d9e1305a67/discovery/discovery_client.go#L55) which will be used to determine whether a referenced signer is namespaced or not. If it is namespaced, the `` will be populated with the -namesapce that the CertificateRequest resides in. If the scope of the resource +namespace that the CertificateRequest resides in. If the scope of the resource cannot be determined, the request will be rejected. @@ -397,8 +399,13 @@ minimal as possible in that the single goal of them is to enable its owning `CertificateRequest` has been observed, the general flow is as follows: - Check the group belongs to the owning `Issuer`, exit if not. -- Check if `CertificateRequest` is in a failed state, exit if true. TODO: more - tightly define what a 'failed state' exactly is. +- Check if `CertificateRequest` is in a terminal failed state. + A controller may choose to add additional conditions to a failed `CertificateRequest`, but must not attempt to issue a certificate. + Currently terminal failed states are: + - `Ready` condition with a `Failed` reason // usually set by the issuer + - `InvalidRequest` condition with `True` status // usually set by the issuer + - `Denied` condition with `True` status // usually set by approver + - Check the `Issuer` type is of the same type, exit if not. - Verify the Spec of the `CertificateRequest`. - If a certificate exits then update the status if needed and exit. @@ -427,50 +434,21 @@ this resource. #### Issuing Controller -Since external issuers have been built before the addition of Approved and -Denied conditions, the issuing controller needs to be permissive. An external -issuer may not honour an Approved condition and will sign and set a -CertificateRequest as being Ready, before the request has been approved. The -issuing controller must mark issuance as being successful in this case. In -practice, this means that the issuing controller is never concerned with -Approved conditions. - -External issuers that do not honour Denied conditions will sign -CertificateRequests, even if they have a Denied condition set. In this case, the -issuing controller will successfully complete the issuance of the Certificate. - - -External Issuers and internal issuers that honour the Denied condition will -never sign CertificateRequests with the Denied condition set, and thus never set -Ready condition. In this case, the issuing controller will consider this -CertificateRequest as failed, and will set the condition `Issuing=False` -as well as setting the status field `lastFailureTime`. Note that the issuing -controller is not responsible for setting the `Ready=False` condition on -the CertificateRequests; that's the issuer's responsibility. - -- The Certificate is clearly reported as Failed to users who may miss the - Denied request from a cursory view. -- The Spec may genuinely be violating the policy, and so can be changed by the - user. This will cause an immediate reissue. -- The policy may be misconfigured, and as such, the Certificate will be retired - later with no user intervention. -- The [manual renew - command](https://cert-manager.io/docs/usage/kubectl-plugin/#renew) relies on - the Issuing condition. In the case of policy being misconfigured, the user - is able to immediately retry the request using the CLI plugin. +Issuing controller considers all Denied CertificateRequests to be in a final failed state. +The issuance will be failed and will be repeatedly retried with an exponential backoff ../20220118.certificate-issuance-exponential-backoff.md. +If the cause of the denial was a misconfigured Certificate spec, the issuance will be retried immediately once the spec is corrected. +If the cause of the denial was misconfigured policy resources, a user who has fixed the resources and wants to retry immediately can do so using [cmctl renew](https://cert-manager.io/docs/reference/cmctl/#renew) -### Failure +The issuing controller does not check Approved condition. It is the issuer's +responsibility not to issue certificates for CertificateRequests that have not +been approved. -The `CertificateRequest` resource has a `FailureTime` field in its Status. If -the `CertificateRequest` fails for any reason then this field is set to the -current time. This field can then be used by a higher order controller, such as -the `Certificate` controller, to take further action and facilitate a backoff. +### Failure -The `Certificate` controller will retry all failed `CertificateRequest` resources -by creating a new request with an identical Spec, only when the `FailureTime` -field is a least 1 hour in the past. The old failed `CertificateRequest` will be -deleted and the new `CertificateRequest` resource will be created with the same -name. +A `CertificateRequest` is considered in a final failed state if: +- it has a Ready condition with Failed reason +- it has a Denied condition with True status +- it has InvalidRequest reason with True status ### Internal API Resource Behaviour diff --git a/design/20200326.extensible-certificate-controller.md b/design/20200326.extensible-certificate-controller.md index 41a781bf4cc..d71b1bf34ef 100644 --- a/design/20200326.extensible-certificate-controller.md +++ b/design/20200326.extensible-certificate-controller.md @@ -63,7 +63,7 @@ We have outstanding feature requests that are currently difficult to implement w design: * Allow private key rotation when renewing certificates [#2402](https://github.com/cert-manager/cert-manager/issues/2402) -* Allowing alternative Secret output formats (e.g. single .pem file priv/cert output) [#843](https://github.com/cert-manager/cert-manager/issues/843) +* Allowing alternative Secret output formats (e.g., single .pem file priv/cert output) [#843](https://github.com/cert-manager/cert-manager/issues/843) * Add support for JKS, PKCS12 and PEM files [#586](https://github.com/cert-manager/cert-manager/issues/586) * Make certificate renewal easier to test [#2578](https://github.com/cert-manager/cert-manager/issues/2578) @@ -74,7 +74,7 @@ areas of the controller over time and continue to make improvements. * Make it easier to maintain the Certificates controller * Make it easier to *extend* the Certificates controller -* Make it possible to 'hook in' to the state of the controller (e.g. manually triggering renewal) +* Make it possible to 'hook in' to the state of the controller (e.g., manually triggering renewal) ### Non-goals @@ -241,7 +241,7 @@ resources that are owned by Certificates. * Delete all owned Secret resources with the `cert-manager.io/next-private-key: "true"` * Ensure `status.nextPrivateKeySecretName` is unset - we may want to consider not doing this in case a user has manually specified this field - and pointed it at an 'un-owned' Secret. This depends whether we want to + and pointed it at an 'un-owned' Secret. This depends on whether we want to support this as a mode of operation. When creating a 'next private key' Secret resource, the diff --git a/design/20210209.certificates.k8s.io-adoption.md b/design/20210209.certificates.k8s.io-adoption.md index 1dcb3c092e7..ae58cc1889d 100644 --- a/design/20210209.certificates.k8s.io-adoption.md +++ b/design/20210209.certificates.k8s.io-adoption.md @@ -141,7 +141,7 @@ cert-manager will enforce an [RBAC](https://kubernetes.io/docs/reference/access-authn-authz/rbac/) noun and verb whereby the requester must have this role bound to them in order for the `CertificateSigningRequest` referencing a namespaced `Issuer` be approved by the -cert-manager controller. See [here](#conditions). +cert-manager controller. See [Conditions](#conditions). This will be done via a [`SubjectAccessReview`](https://github.com/kubernetes/api/blob/4a626d306b987a4096cf0784ec01af1be2f6d67f/authorization/v1/types.go#L52) @@ -168,13 +168,13 @@ rules: [Until 1.22](https://github.com/kubernetes/kubernetes/pull/99494) `CertificateSigningRequests` did not include a `duration` field. To have parity with the `CertificateRequest` resource, the duration field will be moved to the -annotation `experimental.cert-manager.io/request-duration` who's value is a [Go +annotation `experimental.cert-manager.io/request-duration` whose value is a [Go time duration string](https://golang.org/pkg/time/#Duration.String). When 1.22 is released, cert-manager can optimistically read the `expirationSeconds` `CertificateSigningRequest` field to discover the requested duration. If this field hasn't been set or the user is using an older version of -Kubernetes, cert-manager can fallback to this annotation. +Kubernetes, cert-manager can fall back to this annotation. ### CA Field @@ -227,16 +227,16 @@ conflicts with other external signer projects. ```yaml # Namespaced issuer reference - # e.g. `issuers.cert-manager.io/my-namespace.my-issuer + # e.g., `issuers.cert-manager.io/my-namespace.my-issuer signerName: issuers.cert-manager.io/. # Cluster scoped issuer reference - # e.g. `clusterissuers.cert-manager.io/my-issuer + # e.g., `clusterissuers.cert-manager.io/my-issuer signerName: clusterissuers.cert-manager.io/ ``` Using the same approach of referencing by _just_ name, rather than issuer type -(e.g. CA, Vault etc.), keeps the behaviour of this resource in line with +(e.g., CA, Vault etc.), keeps the behaviour of this resource in line with `CertificateRequests` for end users. Each `CertificateSigningRequest` controller will behave in the same way as the diff --git a/design/20220118.certificate-issuance-exponential-backoff.md b/design/20220118.certificate-issuance-exponential-backoff.md index aebb8a68907..aea2b9e6168 100644 --- a/design/20220118.certificate-issuance-exponential-backoff.md +++ b/design/20220118.certificate-issuance-exponential-backoff.md @@ -46,7 +46,7 @@ created. ## Motivation -Currently failed issuances are retried once an hour without a backoff or time limit. This means that 1) continuous failures in large installations can overwhelm external services 2) rate limits can be easily hit in case of longer lasting issuance problems (see [Let'sEncrypt rate limts](https://letsencrypt.org/docs/rate-limits/)) +Currently failed issuances are retried once an hour without a backoff or time limit. This means that 1) continuous failures in large installations can overwhelm external services 2) rate limits can be easily hit in case of longer lasting issuance problems (see [Let'sEncrypt rate limits](https://letsencrypt.org/docs/rate-limits/)) ### Goals @@ -73,14 +73,14 @@ Currently failed issuances are retried once an hour without a backoff or time li ## Proposal -Exponential backoff will be implemented by exponentially increasing the delays between a failed issuance ([`Issuing` condition set to false in `certificates-issuing` controller](https://github.com/jetstack/cert-manager/blob/196d0011ca46037186a826365bcd6316d9b9462a/pkg/controller/certificates/issuing/issuing_controller.go#L341)) and a new issuance ([`Issuing` condition set to true in `certificates-trigger` controller](https://github.com/jetstack/cert-manager/blob/d5503c2ed2df272ec1bd94ebd223408fad29df1f/pkg/controller/certificates/trigger/trigger_controller.go#L184)). From a user perspective, this will correspond to the delay between a `CertificateRequest` having failed and new `CertificateRequest`s being created. +Exponential backoff will be implemented by exponentially increasing the delays between a failed issuance ([`Issuing` condition set to false in `certificates-issuing` controller](https://github.com/cert-manager/cert-manager/blob/196d0011ca46037186a826365bcd6316d9b9462a/pkg/controller/certificates/issuing/issuing_controller.go#L341)) and a new issuance ([`Issuing` condition set to true in `certificates-trigger` controller](https://github.com/cert-manager/cert-manager/blob/d5503c2ed2df272ec1bd94ebd223408fad29df1f/pkg/controller/certificates/trigger/trigger_controller.go#L184)). From a user perspective, this will correspond to the delay between a `CertificateRequest` having failed and new `CertificateRequest`s being created. A new `IssuanceAttempts` status field will be added to `Certificate` that will be used to record the number of consecutive failed issuances. -Similarly to [`status.LastFailureTime`](https://github.com/jetstack/cert-manager/blob/196d0011ca46037186a826365bcd6316d9b9462a/pkg/apis/certmanager/v1/types_certificate.go#L385-L391), `status.IssuanceAttempts` field will only be set for a `Certificate` whose issuance is currently failing and will be removed after a successful issuance. +Similarly to [`status.LastFailureTime`](https://github.com/cert-manager/cert-manager/blob/196d0011ca46037186a826365bcd6316d9b9462a/pkg/apis/certmanager/v1/types_certificate.go#L385-L391), `status.IssuanceAttempts` field will only be set for a `Certificate` whose issuance is currently failing and will be removed after a successful issuance. -`IssuanceAttempts` will be set by [`certificates-issuing` controller](https://github.com/jetstack/cert-manager/tree/ce1424162ea4f363bdb7aa4f201432ec63da1145/pkg/controller/certificates/issuing) after a failed issuance by either bumping the already existing value by 1 or setting it to 1 (first failure). In case of a succeeded issuance, `certificates-issuing` controller will ensure that `status.IssuanceAttempts` is not set. +`IssuanceAttempts` will be set by [`certificates-issuing` controller](https://github.com/cert-manager/cert-manager/tree/ce1424162ea4f363bdb7aa4f201432ec63da1145/pkg/controller/certificates/issuing) after a failed issuance by either bumping the already existing value by 1 or setting it to 1 (first failure). In case of a succeeded issuance, `certificates-issuing` controller will ensure that `status.IssuanceAttempts` is not set. -The delay till the next issuance will then be calculated by [`certificates-trigger` controller](https://github.com/jetstack/cert-manager/tree/ce1424162ea4f363bdb7aa4f201432ec63da1145/pkg/controller/certificates/trigger) using the formula `if status.LastFailureTime != nil then next_issuance_attempt_time = status.LastFailureTime + time.Hour x 2 ^ (status.IssuanceAttempts- 1)` (binary exponential- so the sequence will be 1h, 2h, 4h, 8h etc). This ensures that the first delay is 1 hour from the last failure time which is the current behaviour. In case of continuous failures, the delay should keep increasing up to a maximum backoff period of 32h, after which it should be retried every 32h whilst the failures persist. +The delay till the next issuance will then be calculated by [`certificates-trigger` controller](https://github.com/cert-manager/cert-manager/tree/ce1424162ea4f363bdb7aa4f201432ec63da1145/pkg/controller/certificates/trigger) using the formula `if status.LastFailureTime != nil then next_issuance_attempt_time = status.LastFailureTime + time.Hour x 2 ^ (status.IssuanceAttempts - 1)` (binary exponential, so the sequence will be 1h, 2h, 4h, 8h etc). This ensures that the first delay is 1 hour from the last failure time which is the current behaviour. In case of continuous failures, the delay should keep increasing up to a maximum backoff period of 32h, after which it should be retried every 32h whilst the failures persist. ### API changes @@ -106,9 +106,9 @@ Large part of the these examples show what is already the _current_ behaviour, t 1. A `CertificateRequest` fails. This is the 3rd failed issuance in a row -2. `certificates-issuing` controller reconciles the failed `CertificateRequest`, bumps the `status.IssuanceAttempts` by 1 as well as updating the `status.LastFailureTime` to the time when `CertificateRequest` failed and setting the [`Issuing` condition](https://github.com/jetstack/cert-manager/blob/196d0011ca46037186a826365bcd6316d9b9462a/pkg/apis/certmanager/v1/types_certificate.go#L480-L495) to false ([here-ish](https://github.com/jetstack/cert-manager/blob/196d0011ca46037186a826365bcd6316d9b9462a/pkg/controller/certificates/issuing/issuing_controller.go#L326-L351)) +2. `certificates-issuing` controller reconciles the failed `CertificateRequest`, bumps the `status.IssuanceAttempts` by 1 as well as updating the `status.LastFailureTime` to the time when `CertificateRequest` failed and setting the [`Issuing` condition](https://github.com/cert-manager/cert-manager/blob/196d0011ca46037186a826365bcd6316d9b9462a/pkg/apis/certmanager/v1/types_certificate.go#L480-L495) to false (in [`failIssueCertificate`](https://github.com/cert-manager/cert-manager/blob/196d0011ca46037186a826365bcd6316d9b9462a/pkg/controller/certificates/issuing/issuing_controller.go#L326-L351)) -3. `certificates-trigger` controller parses the `Certificate` with the false `Issuing` condition, calculates the backoff period (in this case it will be status.LastFailureTime + 2h ^ (3 - 1), so roughly in 4 hours) [here-ish](https://github.com/jetstack/cert-manager/blob/8dc603e7f5ef64288478b2e7a769a5415ae54ab0/pkg/controller/certificates/trigger/trigger_controller.go#L201) and enqueues the `Certificate` to be reconciled in 4 hours ([here](https://github.com/jetstack/cert-manager/blob/master/pkg/controller/certificates/trigger/trigger_controller.go#L161)) +3. `certificates-trigger` controller parses the `Certificate` with the false `Issuing` condition, calculates the backoff period (in this case it will be status.LastFailureTime + 2h ^ (3 - 1), so roughly in 4 hours) in [`shouldBackoffReissuingOnFailure`](https://github.com/cert-manager/cert-manager/blob/8dc603e7f5ef64288478b2e7a769a5415ae54ab0/pkg/controller/certificates/trigger/trigger_controller.go#L201) and enqueues the `Certificate` to be reconciled in 4 hours ([`c.scheduleRecheckOfCertificateIfRequired`](https://github.com/cert-manager/cert-manager/blob/8dc603e7f5ef64288478b2e7a769a5415ae54ab0/pkg/controller/certificates/trigger/trigger_controller.go#L161)) 4. In 4 hours, `Certificate` gets reconciled again and `certificates-trigger` controller sets the `Issuing` condition to true. This time the `CertificateRequest` succeeds. @@ -122,9 +122,9 @@ Large part of the these examples show what is already the _current_ behaviour, t 2. `certificates-issuing` controller reconciles the failed `CertificateRequest`, bumps the `status.IssuanceAttempts` by 1 as well as updating the `status.LastFailureTime` to the time when `CertificateRequest` failed and setting the `Issuing` condition to false -3. `certificates-trigger` controller parses the `Certificate` with the false `Issuing` condition, calculates the backoff period (in this case it will be `status.LastFailureTime + 2h ^ (3 - 1)`, so roughly in 4 hours) [here-ish](https://github.com/jetstack/cert-manager/blob/8dc603e7f5ef64288478b2e7a769a5415ae54ab0/pkg/controller/certificates/trigger/trigger_controller.go#L201) and enqueues the `Certificate` to be reconciled in 4 hours ([here](https://github.com/jetstack/cert-manager/blob/master/pkg/controller/certificates/trigger/trigger_controller.go#L161)) +3. `certificates-trigger` controller parses the `Certificate` with the false `Issuing` condition, calculates the backoff period (in this case it will be `status.LastFailureTime + 2h ^ (3 - 1)`, so roughly in 4 hours) in [`shouldBackoffReissuingOnFailure`](https://github.com/cert-manager/cert-manager/blob/8dc603e7f5ef64288478b2e7a769a5415ae54ab0/pkg/controller/certificates/trigger/trigger_controller.go#L201) and enqueues the `Certificate` to be reconciled in 4 hours ([`c.scheduleRecheckOfCertificateIfRequired`](https://github.com/cert-manager/cert-manager/blob/8dc603e7f5ef64288478b2e7a769a5415ae54ab0/pkg/controller/certificates/trigger/trigger_controller.go#L161)) -4. User fixes the reason for failure (i.e some networking setup) and runs `cmctl renew ` to force immediate re-issuance, which [adds `Issuing` condition to the `Certificate`](https://github.com/jetstack/cert-manager/blob/ce1424162ea4f363bdb7aa4f201432ec63da1145/cmd/ctl/pkg/renew/renew.go#L203) thus signalling the other controllers that issuance is in progress and bypassing the `certificates-issuing` controller's [check for whether a backoff is needed](https://github.com/jetstack/cert-manager/blob/ce1424162ea4f363bdb7aa4f201432ec63da1145/pkg/controller/certificates/trigger/trigger_controller.go#L158-L163) +4. User fixes the reason for failure (i.e some networking setup) and runs `cmctl renew ` to force immediate re-issuance, which [adds `Issuing` condition to the `Certificate`](https://github.com/cert-manager/cert-manager/blob/ce1424162ea4f363bdb7aa4f201432ec63da1145/cmd/ctl/pkg/renew/renew.go#L203) thus signalling the other controllers that issuance is in progress and bypassing the `certificates-issuing` controller's [check for whether a backoff is needed](https://github.com/cert-manager/cert-manager/blob/ce1424162ea4f363bdb7aa4f201432ec63da1145/pkg/controller/certificates/trigger/trigger_controller.go#L158-L163) 5. A new `CertificateRequest` is created and succeeds @@ -138,15 +138,15 @@ Large part of the these examples show what is already the _current_ behaviour, t 2. `certificates-issuing` controller reconciles the failed `CertificateRequest`, bumps the `status.IssuanceAttempts` by 1 as well as updating the `status.LastFailureTime` to the time when `CertificateRequest` failed and setting the `Issuing` condition to false -3. `certificates-trigger` controller parses the `Certificate` with the false `Issuing` condition, calculates the backoff period (in this case it will be `status.LastFailureTime + 2h ^ (3 - 1)`, so roughly in 4 hours) [here-ish](https://github.com/jetstack/cert-manager/blob/8dc603e7f5ef64288478b2e7a769a5415ae54ab0/pkg/controller/certificates/trigger/trigger_controller.go#L201) and enqueues the `Certificate` to be reconciled in 4 hours ([here](https://github.com/jetstack/cert-manager/blob/master/pkg/controller/certificates/trigger/trigger_controller.go#L161)) +3. `certificates-trigger` controller parses the `Certificate` with the false `Issuing` condition, calculates the backoff period (in this case it will be `status.LastFailureTime + 2h ^ (3 - 1)`, so roughly in 4 hours) in [`shouldBackoffReissuingOnFailure`](https://github.com/cert-manager/cert-manager/blob/8dc603e7f5ef64288478b2e7a769a5415ae54ab0/pkg/controller/certificates/trigger/trigger_controller.go#L201) and enqueues the `Certificate` to be reconciled in 4 hours ([`c.scheduleRecheckOfCertificateIfRequired`](https://github.com/cert-manager/cert-manager/blob/8dc603e7f5ef64288478b2e7a769a5415ae54ab0/pkg/controller/certificates/trigger/trigger_controller.go#L161)) -4. User thinks that they have fixed the failure (i.e some networking setup) and runs `cmctl renew ` to force immediate re-issuance, which [adds `Issuing` condition to the `Certificate`](https://github.com/jetstack/cert-manager/blob/ce1424162ea4f363bdb7aa4f201432ec63da1145/cmd/ctl/pkg/renew/renew.go#L203) thus signalling the other controllers that issuance is in progress and bypassing the `certificates-issuing` controller's [check for whether a backoff is needed](https://github.com/jetstack/cert-manager/blob/ce1424162ea4f363bdb7aa4f201432ec63da1145/pkg/controller/certificates/trigger/trigger_controller.go#L158-L163) +4. User thinks that they have fixed the failure (i.e some networking setup) and runs `cmctl renew ` to force immediate re-issuance, which [adds `Issuing` condition to the `Certificate`](https://github.com/cert-manager/cert-manager/blob/ce1424162ea4f363bdb7aa4f201432ec63da1145/cmd/ctl/pkg/renew/renew.go#L203) thus signalling the other controllers that issuance is in progress and bypassing the `certificates-issuing` controller's [check for whether a backoff is needed](https://github.com/cert-manager/cert-manager/blob/ce1424162ea4f363bdb7aa4f201432ec63da1145/pkg/controller/certificates/trigger/trigger_controller.go#L158-L163) 5. A new `CertificateRequest` is created and fails again 6. `certificates-issuing` controller reconciles the `Certificate` and the failed `CertificateRequest`, bumps `status.IssuanceAttempts` to 4, sets the `Issuing` condition to false and sets `status.LastFailureTime` to now -7. `certificates-trigger` controller parses the `Certificate` with the false `Issuing` condition, calculates the backoff period (in this case it will be `status.LastFailureTime + 2h ^ (4 - 1)`, so roughly in 8 hours) [here-ish](https://github.com/jetstack/cert-manager/blob/8dc603e7f5ef64288478b2e7a769a5415ae54ab0/pkg/controller/certificates/trigger/trigger_controller.go#L201) and enqueues the `Certificate` to be reconciled in 8 hours ([here](https://github.com/jetstack/cert-manager/blob/master/pkg/controller/certificates/trigger/trigger_controller.go#L161)) +7. `certificates-trigger` controller parses the `Certificate` with the false `Issuing` condition, calculates the backoff period (in this case it will be `status.LastFailureTime + 2h ^ (4 - 1)`, so roughly in 8 hours) in [`shouldBackoffReissuingOnFailure`](https://github.com/cert-manager/cert-manager/blob/8dc603e7f5ef64288478b2e7a769a5415ae54ab0/pkg/controller/certificates/trigger/trigger_controller.go#L201) and enqueues the `Certificate` to be reconciled in 8 hours ([`c.scheduleRecheckOfCertificateIfRequired`](https://github.com/cert-manager/cert-manager/blob/8dc603e7f5ef64288478b2e7a769a5415ae54ab0/pkg/controller/certificates/trigger/trigger_controller.go#L161)) #### Example certificate statuses @@ -197,7 +197,7 @@ Events: ### Test Plan -The example flows described in [Examples](#Examples) and [Upgrading](#Upgrading) will be tested via integration tests ([similar to the current integration tests for certificates](https://github.com/jetstack/cert-manager/tree/master/test/integration/certificates)) +The example flows described in [Examples](#Examples) and [Upgrading](#Upgrading) will be tested via integration tests ([similar to the current integration tests for certificates](https://github.com/cert-manager/cert-manager/tree/master/test/integration/certificates)) ### Upgrading diff --git a/design/20220118.server-side-apply.md b/design/20220118.server-side-apply.md index f4f238c886f..fd4cedfe20a 100644 --- a/design/20220118.server-side-apply.md +++ b/design/20220118.server-side-apply.md @@ -112,7 +112,7 @@ cert-manager-certificates-[issuing,trigger,keymanager,readiness] cert-manager-certificaterequests-[acme,approver,ca,selfsigned,vault,venafi] cert-manager-clusterissuers-[acme,ca,selfsigned,vault,venafi] cert-manager-issuers-[acme,ca,selfsigned,vault,venafi] -cert-manager-cainjector # base field manager of cert-manager ca-injector +cert-manager-cainjector # base field manager of cert-manager cainjector cert-manager-webhook # base field manager of cert-manager webhook cert-manager-cmctl ``` @@ -170,11 +170,11 @@ Some fields, such as the Certificate Issuing Condition are managed by more than one controller (issuing and trigger Certificate controllers, and cmctl), and as such, will need to make use of the `force` parameter in their API calls. This option tells the API server to revoke management of that field from the previous -owner, overwrite the field, and change owner ship to the new client. Since some +owner, overwrite the field, and change ownership to the new client. Since some fields, such as the Issuing Condition, may have an undefined number of potential managers (both internal and external to the cert-manager controller), using the same manager for things is not a possibility. You can read more about the -`force` paramerter on the Kubernetes documentation on +`force` parameter on the Kubernetes documentation on [Server-Side Apply](https://kubernetes.io/docs/reference/using-api/server-side-apply/), and in particular the [Conflicts](https://kubernetes.io/docs/reference/using-api/server-side-apply/#conflicts) @@ -191,7 +191,7 @@ parameter to true since it, [never wants to give up ownership claim, and always wants to overwrite values](https://kubernetes.io/docs/reference/using-api/server-side-apply/#conflicts). See -[here](https://kubernetes.io/docs/reference/using-api/server-side-apply/#using-server-side-apply-in-a-controller). +[Using Server-Side Apply in a controller](https://kubernetes.io/docs/reference/using-api/server-side-apply/#using-server-side-apply-in-a-controller). ### client-go Testing @@ -199,7 +199,7 @@ The [fake client-go client](https://github.com/kubernetes/client-go/issues/970) does not support the Apply PATCH call for mocking API calls and events. This means that significant controller unit-testing will either need to moved to testing against a real API server as integration tests, the controller -[test framework must add custom support for Apply](https://github.com/jetstack/cert-manager/blob/master/pkg/controller/test/context_builder.go), +[test framework must add custom support for Apply](https://github.com/cert-manager/cert-manager/blob/master/pkg/controller/test/context_builder.go), or a new testing framework should be developed. We can also PR this upstream but will take time to be released so a stop gap would always be needed. @@ -208,7 +208,7 @@ will take time to be released so a stop gap would always be needed. Some API fields will need to have some metadata updated to function better under Server-Side Apply. One such example is adding `x-kubernetes-list-type=map` and `x-kubernetes-list-map-keys=Type` to the [Certificates Status Condition -slice](https://github.com/jetstack/cert-manager/blob/0ca1ce9a6a1d7c311afd4b3e786975759249132a/pkg/apis/certmanager/v1/types_certificate.go#L385), +slice](https://github.com/cert-manager/cert-manager/blob/0ca1ce9a6a1d7c311afd4b3e786975759249132a/pkg/apis/certmanager/v1/types_certificate.go#L385), so that controllers are able to apply distinct condition types, without conflicting with other controller conditions (i.e. the Ready and Issuing conditions). Integration tests will be able to ensure cert-manager have set diff --git a/design/20220614-timeouts.md b/design/20220614-timeouts.md index 7d8505f7da7..9fe454fe394 100644 --- a/design/20220614-timeouts.md +++ b/design/20220614-timeouts.md @@ -56,7 +56,7 @@ of engagement through reactions to posts (which isn't a perfect indicator of dem popular relative to what we normally see). This design will largely talk about ACME since ACME issuer users are almost certainly by far the biggest section of the -current cert-manager userbase, but the principles here apply equally to the Venafi issuer, where an instance of +current cert-manager user base, but the principles here apply equally to the Venafi issuer, where an instance of Venafi TPP might be deployed on-prem and could be slow for any number of reasons. We should address Venafi issuers in a similar way, but in a separate piece of work. @@ -128,14 +128,14 @@ The affected controllers appear to be those which have an `accounts.Getter`: - [acmeorders](https://github.com/cert-manager/cert-manager/blob/c16b3cca7b418ba0d0b2bf1066514b8762984517/pkg/controller/acmeorders/controller.go#L50) These timeouts have two issues. One is that the location they're added is unintuitive; the timeouts are added in -_logging_ middleware which which doesn't otherwise mention that it also introduces timeouts. +_logging_ middleware which doesn't otherwise mention that it also introduces timeouts. That's confusing; we might reasonably expect a timeout on writing the logs themselves (i.e. the actual operation of writing to a log) but this functionality doesn't manage that. The second issue is that these timeouts effectively duplicate HTTP client timeouts. -HTTP client timeouts belong on the underlying HTTP client; that's where we could set more finegrained controls such as +HTTP client timeouts belong on the underlying HTTP client; that's where we could set more fine-grained controls such as TLS handshake, dialer and overall HTTP request timeouts. HTTP client timeouts are [desirable](https://blog.cloudflare.com/the-complete-guide-to-golang-net-http-timeouts/), and we [actually already have them](https://github.com/cert-manager/cert-manager/blob/e116d416f3b14863d05753739cbdf72d66923357/pkg/acme/accounts/client.go#L58-L75) for our ACME clients. @@ -152,7 +152,7 @@ We propose to update the overall timeout for our HTTP clients for ACME requests **not** to make this configurable by users. As mentioned above, we already have HTTP timeouts on the HTTP clients we build for use with ACME clients, as seen -[here](https://github.com/cert-manager/cert-manager/blob/e116d416f3b14863d05753739cbdf72d66923357/pkg/acme/accounts/client.go#L58-L75). +in [`BuildHTTPClient`](https://github.com/cert-manager/cert-manager/blob/e116d416f3b14863d05753739cbdf72d66923357/pkg/acme/accounts/client.go#L58-L75). The dialer and TLS handshake timeouts are set to 30 and 10 seconds respectively, and both are likely fine to keep as they are and in any case unlikely to be a problem for people experiencing the issues detailed in [#5080](https://github.com/cert-manager/cert-manager/issues/5080). @@ -214,7 +214,7 @@ The idea here is "if it's good enough for crossplane why should it not be good e The current cert-manager timeouts are arbitrary. Likely the crossplane timeouts are also arbitrary. We can at least have confidence that a big project with a tonne of controllers and CRDs is using longer timeouts and clearly not seeing -world-ending problems, and people want to _increase_ the timeouts from that base too, as envidenced by the above open +world-ending problems, and people want to _increase_ the timeouts from that base too, as evidenced by the above open issue. Another relevant timeout is certbot, which has a [45s](https://github.com/certbot/certbot/blob/295fc5e33a68c945d2f62e84ed8e6aaecfe93102/acme/acme/client.py#L46) diff --git a/design/20220720-per-certificate-owner-ref.md b/design/20220720-per-certificate-owner-ref.md new file mode 100644 index 00000000000..563bdc3f580 --- /dev/null +++ b/design/20220720-per-certificate-owner-ref.md @@ -0,0 +1,272 @@ +# Design: Per-Certificate Secret Owner Reference + +> 🌟 This design document was originally written by Maël Valais on 20 July 2022 in order to facilitate Denis Romanenko's feature request presented in [#5158](https://github.com/cert-manager/cert-manager/pull/5158). + +- [Release Signoff Checklist](#release-signoff-checklist) +- [Summary](#summary) +- [Stories](#stories) +- [Questions](#questions) +- [Proposal](#proposal) +- [Design Details](#design-details) + - [Test Plan](#test-plan) + - [Graduation Criteria](#graduation-criteria) + - [Upgrade / Downgrade Strategy](#upgrade--downgrade-strategy) + - [Supported Versions](#supported-versions) +- [Alternatives](#alternatives) + + +## Release Signoff Checklist + +This checklist contains actions which must be completed before a PR implementing this design can be merged. + +- [ ] This design doc has been discussed and approved +- [ ] Test plan has been agreed upon and the tests implemented +- [ ] Feature gate status has been agreed upon (whether the new functionality will be placed behind a feature gate or not) +- [ ] Graduation criteria is in place if required (if the new functionality is placed behind a feature gate, how will it graduate between stages) +- [ ] User-facing documentation has been PR-ed against the release branch in [cert-manager/website](https://github.com/cert-manager/website) + +## Summary + +The existing flag `--enable-certificate-owner-ref` allows you to configure cert-manager to delete Secret resources when the associated Certificate is removed. + +We propose to introduce a new field, `deletionPolicy`, on the Certificate resource so that users can decide whether or not the Secret resource should be removed. + +And since the semantics of `--enable-certificate-owner-ref` are different from the semantics of `deletionPolicy`, we propose to deprecate `--enable-certificate-owner-ref` and introduce a new flag, `--default-secret-deletion-policy`, that will set the default value of `deletionPolicy` when it is not set. + +## Stories + +**Story 1: managed cert-manager installations and "dev" clusters** + +[Flant](https://flant.com) manages large multi-tenant Kubernetes clusters. The installation of cert-manager is managed by Flant, and customers cannot edit cert-manager's configuration. Customers have access to a "prod" cluster and a "dev" cluster. On both clusters, Flant uses `--enable-certificate-owner-ref=false` to lower the chance of outages of their managed components such as the ingress controller. + +On the "dev" cluster, customers are given long-lived namespaces in which they install and uninstall their applications over and over with random names, including Certificate resources. With hundreds of customers deploying approximately ten times a day to the "dev" cluster, the Secret resources that are left over by cert-manager accumulate (around 10,000 Secret resources after a few months), and the Kubernetes API becomes slow, with people having to wait for 10 seconds to list the secrets in a given namespace. + +To solve this problem, Flant aims at using `deletionPolicy: Orphan` on the certificates used for their managed components and use `--default-secret-deletion-policy=Delete` for the rest of the Certificates. Users won't have to change their Certificate resources. + +On the "prod" cluster, Flant recommends customers to keep the Secret resource on removal to lower the risk of outages. Flant aims to use `--default-secret-deletion-policy=Orphan` for the "prod" cluster and also aims to document the reason for this difference between "prod" and "dev". + +## Questions + +**Is this feature too niche?** + +I think that the user of the Certificate resource should be deciding on the fate of the Secret resource, not the person operating the cert-manager installation. + +**Why is there a new "duplicate" flag `--default-secret-deletion-policy` that does the same thing as `--enable-certificate-owner-ref`?** + +The existing flag `--enable-certificate-owner-ref` does not match the new API (`Delete` and `Orphan`), that is why we decided to add a new flag to reflect the new API. + +**Do we intend to add more to `Delete` and `Orphan`?** + +No, I don't think there will be another value. The intent of these two values (as opposed to using a boolean) is to make the API more explicit, but a boolean could have done the trick. + +**Will `--enable-certificate-owner-ref` be removed?** + +We intend to remove `--enable-certificate-owner-ref` within 3 to 6 releases. Or maybe never since the maintenance burden won't be high. We will strongly recommend users to switch to `--default-secret-deletion-policy`. + +**Why did we choose `deletionPolicy` over `cleanupPolicy`?** + +During the design process, we initially considered using `cleanupPolicy` with +values `[OnDelete|Never]`, but ultimately chose `deletionPolicy` with values +`[Delete|Orphan]` because it is slightly more declarative, and a bit more +familiar to the ecosystem ([Crossplane](https://docs.crossplane.io/v1.20/concepts/managed-resources/#deletionpolicy), +[FluxCD](https://fluxcd.io/flux/components/kustomize/kustomizations/#deletion-policy), and +[External Secrets Operator](https://external-secrets.io/latest/guides/ownership-deletion-policy/#deletion-policy) +all use `deletionPolicy`). + +Note that while `deletionPolicy` has a slightly different meaning in Crossplane +(where it works more like finalizers), in cert-manager it simply controls +whether the secret gets deleted along with the certificate without complex +coordination mechanisms. + +## Proposal + +cert-manager has the ability to set the owner reference field in generated Secret resources. +The option is global, and takes the form of the flag `--enable-certificate-owner-ref` set in +the cert-manager controller Deployment resource. + +Let us take an example Certificate resource: + +```yaml +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + name: cert-1 + namespace: ns-1 + uid: 1e0adf8 +spec: + secretName: cert-1 +``` + +When `--enable-certificate-owner-ref` is passed to the cert-manager controller, cert-manager, +when issuing the X.509 certificate, will create a Secret resource that looks like this: + +```yaml +apiVersion: v1 +kind: Secret +metadata: + name: cert-1 + namespace: ns-1 + ownerReferences: + - controller: true + blockOwnerDeletion: false + uid: 1e0adf8 + name: cert-1 + kind: Certificate + apiVersion: cert-manager.io/v1 +data: + tls.crt: "..." + tls.key: "..." + ca.crt: "..." +``` + +The proposition is to add a new field `deletionPolicy` to the Certificate resource: + +```yaml +apiVersion: cert-manager.io/v1 +kind: Certificate +spec: + secretName: cert-1 + deletionPolicy: [Delete|Orphan] # ✨ Can be left empty. +``` + +The new field `deletionPolicy` has three possible values: + +1. When not set, the value set by `--default-secret-deletion-policy` is inherited. +2. When `Delete`, the owner reference is always created on the Secret resource. +3. When `Orphan`, the owner reference is never created on the Secret resource. + +> At first, the proposed field was named `certificateOwnerRef` and was a +> nullable boolean. James Munnelly reminded us that the Kubernetes API +> never uses boolean fields, and instead uses the string type with +> "meaningful values". On top of being more readable, it also makes the +> field extensible. + +When changing the value of the field `deletionPolicy` from `Delete` to `Orphan`, +the associated Secret resource immediately loses its owner reference. The user +doesn't need to wait until the certificate is renewed. +Along with this new field, we propose to deprecate the flag `--enable-certificate-owner-ref` +and introduce the new flag `--default-secret-deletion-policy`. Its values are as follows: + +- When `--default-secret-deletion-policy` is set to `Orphan`, the Certificate resources + that don't have the `deletionPolicy` field set will have their associated Secret + resources updated (i.e., the owner reference gets removed) on the next issuance of + the Certificate. +- When `--default-secret-deletion-policy` is set to `Delete`, the Certificate resources + that don't have the `deletionPolicy` field set will have their associated Secret + resources updated (i.e., the owner reference gets added) on the next issuance of + the Certificate. + +The effect of changing `--default-secret-deletion-policy` from `Orphan` to `Delete` +or from `Delete` to `Orphan` is not immediate: the change requires a re-issuance +of the Certificate resources. + +The default value for `--default-secret-deletion-policy` is `Orphan`. + +When changing the flag from `Orphan` to `Delete`, the existing Certificate +resources that don't have `deletionPolicy` set are immediately affected, meaning +that their associated Secrets will gain a new owner reference. When changing the +flag from `Delete` to `Orphan`, the Secrets associated to Certificates that +have no `deletionPolicy` set will see their owner reference immediately removed. + +The reason we decided to deprecate `--enable-certificate-owner-ref` is because +this flag behaves differently from how the new `deletionPolicy` behaves: + +- When `--enable-certificate-owner-ref` is not passed (or is set to false), the + existing Secret resources that have an owner reference are not changed even + after a re-issuance. With `--default-secret-deletion-policy` and given that + `deletionPolicy` is not set, the behavior is slightly different: unlike with + the old flag, the existing Secret resources will have their owner references + removed. +- When `--enable-certificate-owner-ref` is set to true, the behavior is the same + as when `--default-secret-deletion-policy` is set to `Delete` and + `deletionPolicy` is not set. + +The deprecated flag `--enable-certificate-owner-ref` keeps precedence over the new flag +in order to keep backwards compatibility. + +When upgrading to the new flag, users can refer to the following table: + +| If... | then they should replace it with... | +| ----- | ----------------------------------- | +| `--enable-certificate-owner-ref` not passed to the controller | No change needed | +| `--enable-certificate-owner-ref=false` | Replace with `--default-secret-deletion-policy=Orphan` | +| `--enable-certificate-owner-ref=true` | Replace with `--default-secret-deletion-policy=Delete` | + +## Design Details + +cert-manager would have to change in a few places. + +**Mutating webhook** + +We propose to have no "value defaulting" for `deletionPolicy` because the +"empty" value has a meaning for us: when `deletionPolicy` is empty, the presence +or not of the flag `--enable-certificate-owner-ref` takes over. To give more +context, some other resources, such as the Pod resource, will mutate the object +when the value is "empty", for example the `imagePullPolicy` value will default +to `IfNotPresent`. + +**PostIssuancePolicyChain** + +In ([policies.go#L95](https://github.com/cert-manager/cert-manager/blob/b78af1ef867f8776715cae3dd6a8b83049c4d9b2/internal/controller/certificates/policies/policies.go#L95-L104)), cert-manager does a few sanity checks right after the issuer (either an +internal or an external issuer) has filled the CertificateRequest's status +with the signed certificate. + +One of the checks is called +[`SecretOwnerReferenceValueMismatch`](https://github.com/cert-manager/cert-manager/blob/b78af1ef867f8776715cae3dd6a8b83049c4d9b2/internal/controller/certificates/policies/checks.go#L511) +and checks that the owner reference on the Secret resource matches the one +on the Certificate resource. + +### Test Plan + +- Unit tests for the changes in the secret manager controller. +- Integration tests (either fake client or envtest) checking various API behaviours. + +### Graduation Criteria + +We propose to release this feature in GA immediately and skip the "beta" +phase that consists of gathering user feedback, since this feature has a +low user-facing surface. We think that we will be able to take a good +decision (e.g., the name of the new field, whether it is a boolean or a +string, and which values the field can take) while developing the feature +in the PR. + +We don't think this feature needs to be [feature gated][feature gate]. + +[feature gate]: https://cert-manager.io/docs/installation/featureflags/#list-of-current-feature-gates + +### Upgrade / Downgrade Strategy + +Upgrading from a version without this feature to a version with this +feature won't require intervention. + +Downgrading requires manual intervention: removing the new flag +`--default-secret-deletion-policy` from the Deployment, adding the corresponding +`--enable-certificate-owner-ref` and emptying the `deletionPolicy` field from +every Certificate in the cluster. + +### Supported Versions + +This feature will be supported in all the versions of Kubernetes that are supported by cert-manager. + +## Alternatives + +**CSI driver** + +It is possible to use a +[`csi-driver`](https://github.com/cert-manager/csi-driver) to circumvent +the problem of "too many ephemeral Secret resources stored in etcd". Using +a CSI driver, no Secret resource is created, alleviating the issue. Since Flant offers its customers the capability to use Certificate resources, +and wants to keep supporting the Certificate type, switching from Certificate +resources to a CSI driver isn't an option. + +**Ad-hoc tool to delete orphaned Secrets** + +It would be possible to develop a custom tool that removes Secret resources that aren’t referenced by any Certificate resource, possibly using an annotation. + +**Multiple installations of cert-manager** + +Another solution would be to install cert-manager twice: once with `--enable-certificate-owner-ref=true`, and the other without. But running multiple instances of cert-manager is not supported. + +**Removal of the ephemeral dev namespace** + +Flant reported that developers are using long-term dev namespaces, meaning that they can't rely on the removal of the dev namespace in order to have the leftover Secrets removed. diff --git a/design/20221205-memory-management.md b/design/20221205-memory-management.md new file mode 100644 index 00000000000..b5a24ad9e95 --- /dev/null +++ b/design/20221205-memory-management.md @@ -0,0 +1,725 @@ +# Memory consumption reduction + + +- [Release Signoff Checklist](#release-signoff-checklist) +- [Summary](#summary) +- [Motivation](#motivation) + - [Goals](#goals) + - [Non-Goals](#non-goals) + - [Nice-to-Have](#nice-to-have) + - [Must-not](#must-not) +- [Proposal](#proposal) + - [Background](#background) + - [User Stories](#user-stories) + - [Story 1](#story-1) + - [Risks and Mitigations](#risks-and-mitigations) +- [Design Details](#design-details) + - [Implementation](#implementation) + - [Metrics](#metrics) + - [cluster-with-many-cert-manager-unrelated-secrets](#cluster-with-large-cert-manager-unrelated-secrets) + - [cert-manager-v1-11](#cert-manager-v111) + - [partial metadata prototype](#partial-metadata-prototype) + - [issuance-of-a-large-number-of-certificates](#issuance-of-a-large-number-of-certificates) + - [latest cert-manager](#latest-cert-manager) + - [partial metadata prototype](#partial-metadata) + - [Pros](#pros) + - [Cons](#cons) + - [Test Plan](#test-plan) + - [Graduation Criteria](#graduation-criteria) + - [Upgrade / Downgrade Strategy](#upgrade--downgrade-strategy) + - [Supported Versions](#supported-versions) + - [Notes](#notes) + - [Current state](#current-state) + - [Secrets for Certificates](#secrets-for-certificates) + - [Secrets for issuers](#secrets-for-clusterissuers) + - [Upstream mechanisms](#upstream-mechanisms) + - [Filtering](#filtering) + - [Partial object metadata](#partial-object-metadata) + - [Transform functions](#transform-functions) +- [Production Readiness](#production-readiness) +- [Drawbacks](#drawbacks) +- [Alternatives](#alternatives) + - [Transform functions](#use-transform-functions-to-remove-data-for-non-labelled-secrets-before-adding-them-to-informers-cache) + - [PartialMetadata only](#use-partialmetadata-only) + - [Paging limit](#use-paging-to-limit-the-memory-spike-when-controller-starts-up) + - [Filter watched Secrets](#filter-the-secrets-to-watch-with-a-label) + - [Custom filter](#allow-users-to-pass-a-custom-filter) + - [Standalone typed cache](#use-a-standalone-typed-cache-populated-from-different-sources) + + +## Release Signoff Checklist + +This checklist contains actions which must be completed before a PR implementing this design can be merged. + + +- [ ] This design doc has been discussed and approved +- [ ] Test plan has been agreed upon and the tests implemented +- [ ] Feature gate status has been agreed upon (whether the new functionality will be placed behind a feature gate or not) +- [ ] Graduation criteria is in place if required (if the new functionality is placed behind a feature gate, how will it graduate between stages) +- [ ] User-facing documentation has been PR-ed against the release branch in [cert-manager/website] + + +## Summary + +[cert-manager's controller](https://cert-manager.io/docs/cli/controller/) watches and caches all `Secret` resources in cluster. +This causes high memory consumption for cert-manager controller pods in clusters which contain many large `Secret`s such as Helm release `Secret`s. + +This proposal suggests a mechanism how to avoid caching cert-manager unrelated `Secret` data. + +## Motivation + +### Goals + +- make cert-manager installation more reliable (no OOM kills caused by events against large cert-manager unrelated `Secret`s) + +- reduce cost of running cert-manager installation (need to allocate less memory) + +- make it easier to predict how much memory needs to be allocated to cert-manager controller + +### Non-Goals + +- memory improvements related to caching objects other than `Secret`s + +- memory improvements related to caching cert-manager related `Secret`s + +- rewrite cert-manager controllers as controller-runtime controllers + +#### Nice to have + +- have this mechanism eventually be on by default (users shouldn't need to have to discover a feature flag to not cache unrelated `Secret`s) + +- use the same mechanism to improve memory consumption by cainjector. This proposal focuses on controller only as it is the more complex part however we need to fix this problem in cainjector too and it would be nice to be consistent + + > 📖 Update: In [#7161: Reduce memory usage by only caching the metadata of Secret resources](https://github.com/cert-manager/cert-manager/pull/716199) + > we addressed the high startup memory usage of cainjector with metadata-only caching features of controller-runtime. + > We did not use the split cache design that was implemented for the + > controller, and this contradicts the goal above: "use the same mechanism to + > improve memory consumption by cainjector ... to be consistent". + > Why? Because the split cache mechanism is overkill for cainjector. + > The split cache design is designed to reduce memory use **and** minimize the + > ongoing load on the K8S API server; which is appropriate for the controller + > because it has multiple controller loops each reading Secret resources every + > time a Certificate is reconciled. + > It is not necessary for cainjector, because cainjector reads relatively few + > Secret resources, infrequently; `cainjector` only reads Secrets having the + > `cert-manager.io/allow-direct-injection` or Secrets created from + > Certificates having that annotation. And it only reads the Secret data once + > during while reconciling the target resource. + +#### Must not + +- make our controllers less reliable (i.e by introducing edge cases where a cert-manager related event does not trigger a reconcile). Given the wide usage of cert-manager and the various different usage scenarios, any such edge case would be likely to occur for some users + +- make our issuance flow harder to reason about or less intuitive + +- break any existing installation/issuance flows (i.e where some resources, such as issuer `Secret`s are created after the issuer and the flow relies on the `Secret` creation event to trigger the issuer reconcile) + +- significantly slow down issuance + +## Proposal + +The current `Secret`s informer will have a filter to watch only `Secret`s that are known to be cert-manager related (using a label selector). +A new informer will be added that knows how to watch `PartialMetadata` for `Secret`s. This informer will have a filter to watch only `Secret`s that don't have a known cert-manager label. This will ensure that for each `Secret` either full data is cached in the typed informer's cache or metadata only is cached in metadata informer's cache. +Cert-manager will label `cert.spec.secretName` and temporary private key `Secret`s. These are the most frequently accessed `Secret` resources. Users could also optionally apply the label to other `Secret`s that cert-manager controller needs to watch to ensure that those get cached. + +This will reduce the excessive memory consumption caused by caching full contents of cert-manager unrelated `Secret`s whilst still ensuring that most of the `Secret`s that cert-manager needs frequently are retrieved from cache and cert-manager relevant events are not missed. + +### Background + +The excessive memory consumption comes from the amount of cluster objects being stored in the [shared informers caches](https://github.com/kubernetes/client-go/blob/v12.0.0/tools/cache/shared_informer.go#L47-L58), mostly from `Secret`s. +cert-manager uses client-go's [informer factory](https://github.com/kubernetes/client-go/tree/master/informers) to create informers for core types. We have [auto-generated informers](https://github.com/cert-manager/cert-manager/tree/v1.10.1/pkg/client/informers/externalversions) for cert-manager.io types. These informers do not directly expose the cache or the [ListerWatcher](https://github.com/kubernetes/client-go/blob/v12.0.0/tools/cache/shared_informer.go#L188) which is responsible for listing and setting up watches for objects. +When cert-manager controller starts, all `Secret`s are listed and processed, which causes a memory spike. +When there is change to `Secret`s, the cache gets resynced, which can also cause a memory spike. +For the rest of the time, `Secret`s remain in controller's cache. + +cert-manager needs to watch all `Secret`s in the cluster because some user created `Secret`s, for example issuer credentials, might not be labelled and we do want to trigger issuer reconciles when those `Secret`s change because: + +- in cases where an issuer gets created and is unready because its credential has not yet been applied/is incorrect and a user at some point applies or corrects it, it is a better user experience that the creation/update event triggers an immediate reconcile instead of the user having to wait for the failed issuer to be reconciled again after the backoff period ([max wait can be 5 minutes for the issuers workqueue](https://github.com/cert-manager/cert-manager/blob/v1.10.1/pkg/controller/issuers/controller.go#L70)) + +- in cases where an issuer credential change should trigger issuer status update (i.e Venafi credentials `Secret` gets updated with incorrect credentials) it is a better user experience if the update event caused a reconcile and the issuer status would be changed to unready instead of failing at issuance time + +- in some cases a missing `Secret` does not cause issuer reconcile ([such as a missing ACME EAB key where we explicitly rely on `Secret` events to retry issuer setup](https://github.com/cert-manager/cert-manager/blob/v1.10.1/pkg/issuer/acme/setup.go#L228)). In this case, it is more efficient as well as a better user experience to reconcile on `Secret` creation event as that way we avoid wasting CPU cycles whilst waiting for the user to create the `Secret` and when the `Secret` does get created, the issuer will be reconciled immediately. + +The caching mechanism is required for ensuring quick issuance and not taking too much of kube apiserver's resources. `Secret`s with the issued X.509 certificates and with temporary private keys get retrieved a number of times during issuance and all the control loops involved in issuance need full `Secret` data. Currently the `Secret`s are retrieved from informers cache. Retrieving them from kube apiserver would mean a large number of additional calls to kube apiserver, which is undesirable. The default cert-manager installation uses a rate-limited client (20QPS with a burst of 50). There is also server-side [API Priority and Fairness system](https://kubernetes.io/docs/concepts/cluster-administration/flow-control/) that prevents rogue clients from overwhelming kube apiserver. Both these mechanisms mean that the result of a large number of additional calls will be slower issuance as cert-manager will get rate limited (either client-side or server-side). The rate limiting can be modified to allow higher throughput for cert-manager, but this would have an impact of kube apiserver's availability for other tenants, so in either case additional API calls would have a cost for the user. + +### User Stories + +#### Story 1 + +User has a cluster with 4 cert-manager `Certificate`s and 30k other (cert-manager unrelated) `Secret`s. +They observe unreasonably high memory consumption in proportion to the amount of cert-manager resources. + +See issue description here https://github.com/cert-manager/cert-manager/issues/4722 + +### Risks and Mitigations + +- Risk of slowing down issuance in cases where cert-manager needs to retrieve unlabelled `Secret`s, such as CA issuer's `Secret`. + Users could mitigate this by labelling the `Secret`s. + +- Risk of unintentionally or intentionally overwhelming kube apiserver with the additional requests. + A default cert-manager installation uses rate limiting (default 50 QPS with a burst of 20). This should be sufficient to ensure that in case of a large number of additional requests from cert-manager controller, the kube apiserver is not slowed down. Cert-manager controller allows to configure rate limiting QPS and burst (there is no upper limit). Since 1.20, Kubernetes by default uses [API Priority and Fairness](https://kubernetes.io/docs/concepts/cluster-administration/flow-control/) for fine-grained server side rate limiting, which should prevent clients that don't sufficiently rate limit themselves from overwhelming the kube apiserver. + In a cluster where API Priority and Fairness is disabled and cert-manager's rate limiter has been configured with a very high QPS and burst, it might be possible to overwhelm kube apiserver. However, this is already possible today, if a user has the rights to configure cert-manager installation, i.e by creating a large number of cert-manager resources in a tight loop. + To limit the possibility of overwhelming the kube apiserver: + - we should ensure that control loops that access secrets do not unnecessarily retry on errors (i.e if a secret is not found or has invalid data). + This should already be the case today, but worth reading through all possible paths + - we could store initialized clients for all issuers as we already do for ACME issuer instead of retrieving credential secrets every time a certificate request needs to be signed + - recommend that users label `Secret` resources + - start with a non-GA implementation (this design suggests that the implementation starts as an alpha feature) to catch any potential edge cases and gate GA on user feedback from larger installations + + +## Design details +### Implementation + +Ensure that `certificate.Spec.SecretName` `Secret` as well as the `Secret` with temporary private key are labelled with a `controller.cert-manager.io/fao: true` [^2] label. +The temporary private key `Secret` is short lived so it should be okay to only label it on creation. +The `certificate.Spec.SecretName` `Secret` should be checked for the label value on every reconcile of the owning `Certificate`, same as with the secret template labels and annotations, see [`c.ensureSecretData`](https://github.com/cert-manager/cert-manager/blob/v1.10.1/pkg/controller/certificates/issuing/issuing_controller.go#L187-L191). + +Add a partial metadata informers factory, set up with [a client-go client that knows how to make GET/LIST/WATCH requests for `PartialMetadata`](https://github.com/kubernetes/client-go/blob/v0.26.0/metadata/metadata.go#L50-L58). +Add a filter to ensure that any informers for this factory will list _only_ resources that are _not_ labelled with a known 'cert-manager' label. + + +```go +import ( + ... + "k8s.io/client-go/metadata" + ... +) +metadataOnlyClient := metadata.NewForConfigOrDie(restConfig) + +metadataLabelSelector, _ := notKnownCertManagerSecretLabelSelector() + +metadataSharedInformerFactory := metadatainformer.NewFilteredSharedInformerFactory(metadataOnlyClient, resyncPeriod, opts.Namespace, func(listOptions *metav1.ListOptions) { + // select only objects that do not have a known cert-manager label + listOptions.LabelSelector = metadataLabelSelector +}) + +func notKnownCertManagerSecretLabelSelector() (string, error) { + r, _ := labels.NewRequirement("controller.cert-manager.io/fao", selection.DoesNotExist, make([]string, 0)) + sel := labels.NewSelector().Add(*r) + return sel.String(), nil +} +``` + +Create informer a partial metadata informer that watches events for `Secret` GVK: + +```go + metadataSecretsInformer := metadataSharedInformerFactory.ForResource(corev1.SchemeGroupVersion.WithResource("secrets")) +``` + +Add a label selector to the existing `Secret`s informer created for [typed informers factory](https://github.com/cert-manager/cert-manager/blob/v1.10.1/pkg/controller/context.go#L264) to ensure that only `Secret` that _do_ have a known cert-manager label are watched: + +```go +import ( + ... + kubeinternalinterfaces "k8s.io/client-go/informers/internalinterfaces" + coreinformers "k8s.io/client-go/informers/core/v1" + "k8s.io/client-go/kubernetes" + ... +) +concreteSecretsInformer := NewFilteredSecretsInformer(factory, kubeClient) // factory is the existing typed informers factory + +func NewFilteredSecretsInformer(factory kubeinternalinterfaces.SharedInformerFactory, client kubernetes.Interface) coreinformers.SecretInformer { + return &filteredSecretsInformer{ + factory: factory, + client: client, + newInformer: newFilteredSecretsInformer, + } +} + +type filteredSecretsInformer struct { + factory kubeinternalinterfaces.SharedInformerFactory + client kubernetes.Interface + newInformer kubeinternalinterfaces.NewInformerFunc + namespace string +} + +func (f *filteredSecretsInformer) Informer() cache.SharedIndexInformer { + return f.factory.InformerFor(&corev1.Secret{}, f.newInformer) +} + +func (f *filteredSecretsInformer) Lister() corelisters.SecretLister { + return corelisters.NewSecretLister(f.Informer().GetIndexer()) +} + +func newFilteredSecretsInformer(client kubernetes.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { + secretLabelSelector, _ := knownCertManagerSecretLabelSelector() + return coreinformers.NewFilteredSecretInformer(client, "", resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, func(listOptions *metav1.ListOptions) { + listOptions.LabelSelector = secretLabelSelector + }) +} + +func knownCertManagerSecretLabelSelector() (string, error) { + r, _ := labels.NewRequirement("controller.cert-manager.io/fao", selection.Exists, make([]string, 0)) + sel := labels.NewSelector().Add(*r) + return sel.String(), nil +} +``` + +Create a new `Secret`s getter function. The function will check for the `Secret` in both typed and `PartialMetadata` cache. +- If the object is found in both caches, it assumes that either cache must be stale and get the `Secret` from kube apiserver[^1] +- If the object is found in `PartialMetadata` cache, it will get it from kube apiserver +- If the object is found in the typed cache, it will get it from there +- If the object is not found, it will return NotFound error + +```go +func SecretGetter(ctx context.Context, liveSecretsClient typedcorev1.SecretsGetter, cacheSecretsClient corelisters.SecretLister, partialMetadataClient cache.GenericLister, name string, namespace string) (*corev1.Secret, error) { + var secretFoundInTypedCache, secretFoundInMetadataCache bool + secret, err := cacheSecretsClient.Secrets(namespace).Get(name) + if err == nil { + secretFoundInTypedCache = true + } + + if err != nil && !apierrors.IsNotFound(err) { + return nil, fmt.Errorf("error retrieving secret from the typed cache: %w", err) + } + _, partialMetadataGetErr := partialMetadataClient.ByNamespace(namespace).Get(name) + if partialMetadataGetErr == nil { + secretFoundInMetadataCache = true + } + + if partialMetadataGetErr != nil && !apierrors.IsNotFound(partialMetadataGetErr) { + return nil, fmt.Errorf("error retrieving object from partial object metadata cache: %w", err) + } + + if secretFoundInMetadataCache && secretFoundInTypedCache { + return liveSecretsClient.Secrets(namespace).Get(ctx, name, metav1.GetOptions{}) + } + + if secretFoundInTypedCache { + return secret, nil + } + + if secretFoundInMetadataCache { + return liveSecretsClient.Secrets(namespace).Get(ctx, name, metav1.GetOptions{}) + } + + return nil, partialMetadataGetErr +} + +``` + +Use the new `Secret`s getter in all control loops that need to get any `Secret`: + +```go + ... + // Fetch and parse the 'next private key secret' + nextPrivateKeySecret, err := SecretGetter(ctx, c.secretLiveClient, c.secretLister, c.metadataSecretLister, *crt.Status.NextPrivateKeySecretName, crt.Namespace) + ... + +``` + +### Metrics + +The following metrics are based on [a prototype implementation of this design](https://github.com/cert-manager/cert-manager/tree/ffe820d310ff2d8bf8efb36ab43b8acd2100be18). +The tests were run on a kind cluster. + +#### Cluster with large cert-manager unrelated secrets + +Test the memory spike caused by the initial LIST-ing of `Secret`s, the size of cache after the initial LIST has been processed and a spike caused by changes to `Secret` resources. + +##### cert-manager v1.11 + +Create 300 cert-manager unrelated `Secret`s of size ~1Mb: + +![screenshot of a terminal using dd to create generic kubernetes secrets `foo-{0..300}`](/design/images/20221205-memory-management/createsecrets.png) + +Install cert-manager from [latest master with client-go metrics enabled](https://github.com/cert-manager/cert-manager/tree/24af3abab8a43d51e29897a3c57a531a35599db6). + +Wait for cert-manager to start and populate the caches. + +Apply a label to all `Secret`s to initiate cache resync: + +![screenshot of `kubectl label secret --all foo=bar`](/design/images/20221205-memory-management/labelsecret.png) + +Observe that memory consumption spikes on controller startup when all `Secret`s are initially listed, there is a second smaller spike around the time the `Secret`s got labelled and that memory consumption remains high: + +![screenshot of Grafana graph of prometheus metrics for Memory consumption (spikes to 1.15 GB, declines, spikes to 800 MB and then steadies out at 700 MB -- which is high)](/design/images/20221205-memory-management/latestmastersecrets.png) + +##### partial metadata prototype + +Create 300 cert-manager unrelated `Secret`s of size ~1Mb: + +![screenshot of a terminal using dd to create generic kubernetes secrets `foo-{0..300}`](/design/images/20221205-memory-management/createsecrets.png) + +Deploy cert-manager from [partial metadata prototype](https://github.com/cert-manager/cert-manager/tree/ffe820d310ff2d8bf8efb36ab43b8acd2100be18). + +Wait for cert-manager to start and populate the caches. + +Apply a label to all `Secret`s to initiate cache resync: + +![screenshot of `kubectl label secret --all foo=bar`](/design/images/20221205-memory-management/labelsecret.png) + +Observe that the memory consumption is significantly lower: + +![screenshot of Grafana graph of prometheus metrics for Memory consumption ending around 27.5 MB](/design/images/20221205-memory-management/partialmetadatasecrets.png) + +#### Issuance of a large number of `Certificate`s + +This scenario tests issuing 500 certificates from 10 cert-manager [CA issuers](https://cert-manager.io/docs/configuration/ca/). +The CA issuers have been set up with CA certificates that do not have known cert-manager labels. + +Here is a script that sets up the issuers, creates the `Certificate`s, waits for them to become ready and outputs the total time taken https://gist.github.com/irbekrm/bc56a917a164b1a3a097bda483def0b8. + +##### latest cert-manager + +This test was run against a version of cert-manager that corresponds to v1.11.0-alpha.2 with some added client-go metrics https://github.com/cert-manager/cert-manager/tree/24af3abab8a43d51e29897a3c57a531a35599db6. +Run a script to set up 10 CA issuers, create 500 certificates and observe the time taken for all certs to be issued: +![screenshot of "Script started running at 11:18:15/start: 1673263095 end: 1673263512/runtime is 417 seconds](/design/images/20221205-memory-management/masterissuanceterminal.png) + +Observe resource consumption, request rate and latency for cert-manager controller: +![Grafana graphs showing Memory consumption/CPU consumption/Rate of request to kube apiserver/Rate limiting per call to kube apiserver (up to 1.8s)/Total reconciles across all control loops](/design/images/20221205-memory-management/mastercertmanager.png) + +Observe resource consumption and rate of requests for `Secret` resources for kube apiserver: +![Grafana graphs showing Memory consumption/CPU consumption/Requests for secrets to kube apiserver (rising to 25)](/design/images/20221205-memory-management/masterkubeapiserver.png) + +##### partial metadata + +Run a script to set up 10 CA issuers, create 500 certificates and observe the time taken for all certs to be issued: +![screenshot of "Script started running at 11:35:05/start: 1673264105 end: 1673264701/runtime is 596 seconds"](/design/images/20221205-memory-management/partialnolabels.png) + +Observe resource consumption, request rate and latency for cert-manager controller: +![Grafana graphs showing Memory consumption/CPU consumption/Rate of request to kube apiserver/Rate limiting per call to kube apiserver (dropping from 2s)/Total reconciles across all control loops](/design/images/20221205-memory-management/partialnolabelscertmanager.png) + +Observe resource consumption and rate of requests for `Secret` resources for kube apiserver: +![Grafana graphs showing Memory consumption/CPU consumption/Requests for secrets to kube apiserver (peaking below 25)](/design/images/20221205-memory-management/partialnolabelskubeapiserver.png) + +The issuance is slightly slowed down because on each issuance cert-manager needs to get the unlabelled CA `Secret` directly from kube apiserver. +Users could mitigate this by adding cert-manager labels to the CA `Secret`s. +Run a modified version of the same script, but [with CA `Secret`s labelled](https://gist.github.com/irbekrm/bc56a917a164b1a3a097bda483def0b8#file-measure-issuance-time-sh-L31-L34): + +![screenshot of "Script started running at 12:20:48/start: 1673266848 end: 1673267347/runtime is 499 seconds](/design/images/20221205-memory-management/partiallabels.png) + +For CA issuers, normally a `Secret` will be retrieved once per issuer reconcile and once per certificate request signing. In some cases, two `Secret`s might be retrieved during certificate request signing see [secrets for issuers](#secrets-for-clusterissuers). We could look into improving this, by initializing a client with credentials and sharing with certificate request controllers, similarly to how it's currently done with [ACME clients](https://github.com/cert-manager/cert-manager/blob/v1.11.0/pkg/controller/context.go#L188-L190). + +### Pros + +- In most setups in majority of cases where a control loop needs a `Secret` it would still be retrieved from cache (as it is certificate secrets that get parsed most frequently and those will be labelled in practically all cases) + +- Memory consumption improvements appear quite significant + +- Once graduated to GA would work for all installations without needing to discover a flag to set + +### Cons + +- All cluster `Secret`s are still listed + +- Slower issuance in cases where cert-manager needs to retrieve unlabelled `Secret`s +### Test Plan + +Unit and e2e tests (largely updating our existing e2e tests and writing unit tests for any new functions). + +We do not currently have any automated tests that observe resource consumption/do load testing. + +See [Metrics](#metrics) for how to test resource consumption/issuance speed manually. + +### Graduation Criteria + +Alpha (cert-manager 1.12): + +- feature implemented behind a feature flag + +- CI tests pass for all supported Kubernetes versions + +- this design discussed and merged + +Beta: + +User feedback: +- does this solve the target use case (memory consumption reduction for clusters with large number of cert-manager unrelated `Secret`s)? +- does this work in cases where large number of `Certificate`s need to be issued around the same time (i.e is the slight slowdown of issuance acceptable)? + +GA: + +- TODO: define criteria which should be a certain number of working installations + +### Upgrade / Downgrade Strategy + +Recommend users to upgrade to cert-manager v1.11 first to ensure that all `Certificate` `Secret`s are labelled to avoid spike in apiserver calls on controller startup. + +### Supported Versions + +This feature will work with all versions of Kubernetes currently supported by cert-manager. + +`PartialMetadata` support by kube apiserver has been GA [since Kubernetes 1.15](https://github.com/kubernetes/enhancements/tree/master/keps/sig-api-machinery/2334-graduate-server-side-get-and-partial-objects-to-GA#implementation-history). +[The oldest Kubernetes version supported by cert-manager 1.12 will be 1.22](https://cert-manager.io/docs/installation/supported-releases/#upcoming-releases). + +### Notes +#### Current state + +This sections lists all `Secret`s that _need_ to be watched by cert-manager controller's reconcile loops. + +##### Secrets for Certificates + +- `certificate.spec.secretName` `Secret`s (that contain the issued certs). These can be created by cert-manager or pre-created by users or external tools (i.e ingress controller). If created by cert-manager, they [will have a number of `cert-manager.io` annotations](https://github.com/cert-manager/cert-manager/blob/2f24231383173cf8ef66858c24e7d2f01c699219/internal/controller/certificates/secrets.go#L35-L52). Secrets without annotations will cause re-issuance (see https://cert-manager.io/docs/faq/#when-do-certs-get-re-issued) and upon successful issuance cert-manager.io annotations will be added. + +- The temporary `Secret`s that get created for each issuance and contain the private key of that the certificate request is signed with. These can only be created by cert-manager controller and are all labelled with `cert-manager.io/next-private-key: true` label. + +##### Secrets for [Cluster]Issuers + +The issuers and clusterissuers controllers set up watches for all events on all secrets, but have [a filter](https://github.com/cert-manager/cert-manager/blob/2f24231383173cf8ef66858c24e7d2f01c699219/pkg/controller/issuers/controller.go#L100) to determine whether an event should cause a reconcile. + +**ACME issuer** + +- the secret referenced by `issuer.spec.acme.privateKeySecretRef`. This can be created by user (for an already existing ACME account) or by cert-manager. Cert-manager does not currently add any labels or annotations to this secret. + +A number of optional secrets that will always be created by users with no labelling enforced: + + +- the secret referenced in `issuer.spec.acme.externalAccountBinding`. + +- the secret referenced by `issuer.spec.acme.solvers.dns01.acmeDNS.accountSecretRef`. + +- the secret referenced in `issuer.spec.acme.solvers.dns01.akamai.clientSecretSecretRef` + +- the secret referenced in `issuer.spec.acme.solvers.dns01.akamai.accessTokenSecretRef` + +- the secret referenced in `issuer.spec.acme.solvers.dns01.azureDNS.clientSecretSecretRef` + +- the secret referenced in `issuer.spec.acme.solvers.dns01.cloudDNS.serviceAccountSecretRef` + +- the secret referenced in `issuer.spec.acme.solvers.dns01.cloudflare.apiTokenSecretRef` + +- the secret referenced in `issuer.spec.acme.solvers.dns01.cloudflare.apiKeySecretRef` + +- the secret referenced in `issuer.spec.acme.solvers.dns01.digitalocean.tokenSecretRef` + +- the secret referenced in `issuer.spec.acme.solvers.dns01.rfc2136.tsigSecretSecretRef` + +- the secret referenced in `issuer.spec.acme.solvers.dns01.route53.accessKeyIDSecretRef` + +- the secret referenced in `issuer.spec.acme.solvers.dns01.route53.secretAccessKeySecretRef` + +The ACME account key secret and, if configured, the secret with EAB key will be returned once per issuer reconcile (on events against issuer or the account key or EAB key secret). The ACME client initialized with the credentials is then stored in a registry shared with orders controller, so the secrets are _not_ retrieved again when a certificate request for the issuer needs to be signed. +For a DNS-01 challenge, one (possibly two in case of AWS) calls for secrets will be made during issuance to retrieve the relevant credentials secret. + +**CA** + +- the secret referenced by `issuer.spec.ca.secretName`. This will always be created by user. No labelling is currently enforced. + +This will be retrieved twice when the issuer is reconciled (when an event occurs against the issuer or its secret) and once when a certificate request for the issuer is being signed. + +**Vault** + +- the optional secret referenced by `issuers.spec.vault.caBundleSecretRef`. Always created by user with no labelling enforced + +One of the following credentials secrets: + + - secret referenced by `issuers.spec.vault.auth.appRole.secretRef`. Always created by user with no labelling enforced + + - secret referenced by `issuers.spec.vault.auth.kubernetes.secretRef`. Always created by user with no labelling enforced + + - secret referenced by `issuers.spec.vault.auth.tokenSecretRef`. Always created by user with no labelling enforced + +The configured credentials `Secret`s and, if configured, CA bundle `Secret` will be retrieved every time the issuer is reconciled (on events against the issuer and either of the `Secret`s) and every time a certificate request needs to be signed. + +**Venafi** + +One of: + +- the secret referenced by `issuers.spec.venafi.tpp.secretRef`. Always created by user with no labelling enforced + +- the secret referenced by `issuers.spec.venafi.cloud.secretRef`. Always created by user with no labelling enforced + +The configured `Secret` will be retrieved when the issuer is reconciled (events against issuer and its secret) and when a certificate request is signed. + +#### Upstream mechanisms + +There are a number of existing upstream mechanisms how to limit what gets stored in the cache. This section focuses on what is available for client-go informers which we use in cert-manager controllers, but there is a controller-runtime wrapper available for each of these mechanisms that should make it usable in cainjector as well. + + ##### Filtering + +Filtering which objects get watched using [label or field selectors](https://github.com/kubernetes/apimachinery/blob/v0.26.0/pkg/apis/meta/v1/types.go#L328-L332). These selectors allow to filter what resources are retrieved during the initial list call and watch calls to kube apiserver by informer's `ListerWatcher` component (and therefore will end up in the cache). client-go informer factory allows configuring individual informers with [list options](https://github.com/kubernetes/client-go/blob/v12.0.0/informers/factory.go#L78-L84) that will be used [for list and watch calls](https://github.com/kubernetes/client-go/blob/v12.0.0/informers/core/v1/secret.go#L59-L72). +This mechanism is used by other projects that use client-go controllers, for example [istio](https://github.com/istio/istio/blob/1.16.0/pilot/pkg/status/distribution/state.go#L100-L103). +The same filtering mechanism is [also available for cert-manager.io resources](https://github.com/cert-manager/cert-manager/blob/v1.10.1/pkg/client/informers/externalversions/factory.go#L63-L69). We shouldn't need to filter what cert-manager.io resources we watch though. +This mechanism seems the most straightforward to use, but currently we don't have a way to identify all resources (secrets) we need to watch using a label or field selector, see [###Secrets]. + +##### Partial object metadata + +Caching only metadata for a given object. This mechanism relies on making list and watch calls against kube apiserver with a `PartialObjectMetadata` header. The apiserver then returns [PartialObjectMetadata](https://github.com/kubernetes/apimachinery/blob/v0.26.0/pkg/apis/meta/v1/types.go#L1425-L1447) instead of an object of a concrete type such as a `Secret`. The `PartialObjectMetadata` only contains the metadata and type information of the object. +To use this mechanism to ensure that metadata only is being cached for a particular resource type that triggers a reconcile, `ListerWatcher` of the informer for that type needs to use a client that knows how to make calls with `PartialObjectMetadata` header. Also if the reconcile loop can only retrieve `PartialObjectMetadata` types from cache. +client-go has a [metadata only client](https://github.com/kubernetes/client-go/blob/v0.25.5/metadata/metadata.go#L85-L99) that can be used to get, list and watch with `PartialObjectMetadata`. client-go also has a [metadata informer](https://github.com/kubernetes/client-go/blob/v0.25.5/metadata/metadatainformer/informer.go#L118-L142) that uses the metadata only client to list and watch resources. This informer implements the same [SharedIndexInformer interface](https://github.com/kubernetes/client-go/blob/v0.26.0/tools/cache/shared_informer.go#L219) as the core and cert-manager.io informers that we use currently, so it would fit our existing controller setup. +The downside to having metadata only in cache is that if the reconcile loop needs the whole object, it needs to make another call to the kube apiserver to get the actual object. We have a number of reconcile loops that retrieve and parse secret data numerous times, for example [readiness controller](https://github.com/cert-manager/cert-manager/blob/v1.10.1/pkg/controller/certificates/readiness/readiness_controller.go) retrieves and parses `spec.SecretName` secret for a `Certificate` on any event associated with the `Certificate`, any of its `CertificateRequest`s or the `spec.secretName` secret. +TODO: add which projects have adopted metadata-only watches, especially with client-go informers + +##### Transform functions + +Transforming the object before it gets placed into cache. Client-go allows configuring core informers with [transform functions](https://github.com/kubernetes/client-go/blob/v0.25.5/tools/cache/controller.go#L356-L365). These functions will get called with the object as an argument [before the object is placed into cache](https://github.com/kubernetes/client-go/blob/v0.25.5/tools/cache/controller.go#L420-L426). The transformer will need to convert the object to a concrete or metadata type if it wants to retrieve its fields. +This is a lesser used functionality in comparison with metadata only caching. +A couple usage examples: +- support for transform functions was added in controller-runtime [controller-runtime#1805](https://github.com/kubernetes-sigs/controller-runtime/pull/1805) with the goal of allowing users to remove managed fields and annotations +- Istio's pilot controller uses this mechanism to configure their client-go informers to [remove managed fields before putting object into cache](https://github.com/istio/istio/blob/1.16.0/pilot/pkg/config/kube/crdclient/client.go#L179) +I haven't seen any usage examples where non-metadata fields are modified using this mechanism. I cannot see a reason why new fields (i.e a label that signals that a transform was applied could not be _added_) as well as fields being removed. + +##### Future changes + +There is an open KEP for replacing initial LIST with a WATCH https://github.com/kubernetes/enhancements/pull/3667 + +Perhaps this would also reduce the memory spike on controller startup. + +## Production Readiness + + + +### How can this feature be enabled / disabled for an existing cert-manager installation? + + + +### Does this feature depend on any specific services running in the cluster? + +No + +### Will enabling / using this feature result in new API calls (i.e to Kubernetes apiserver or external services)? + +There will be additional calls to kube apiserver to retrieve unlabelled `Secret`s. + +See [Metrics](#metrics) and [Risks and Mitigation](#risks-and-mitigations) + +### Will enabling / using this feature result in increasing size or count of the existing API objects? + +No new objects will be created + +### Will enabling / using this feature result in significant increase of resource usage? (CPU, RAM...) + +No, see [Metrics](#metrics) + +## Alternatives + +### Use transform functions to remove `data` for non-labelled `Secret`s before adding them to informers cache + +Watch all `Secret`s as before. Use client-go's [transform functions mechanism](https://github.com/kubernetes/client-go/blob/v0.25.5/tools/cache/controller.go#L356-L365) to remove the `data` field for a `Secret` that does not have a known cert-manager label before it gets placed in informer's cache. In the same transform function add a custom `cert-manager.io/metadata-only` label to all `Secret`s whose `data` got removed (this label will only exist on the cached object). +In reconcilers, use a custom `Secret`s getter that can get the `Secret` either from kube apiserver or cache, depending on whether it has the `cert-manager.io/metadata-only` label that suggests that the `Secret`'s `data` has been removed. +Additionally, ensure that as many `Secret`s as we can (ACME registry account keys) get labelled. +Users would be encouraged to add a cert-manager label to all `Secret`s they create to reduce extra calls to kube apiserver. + +In practice: + +- cert-manager would cache the full `Secret` object for all `certificate.spec.secretName` `Secret`s and all `Secret`s containing temporary private keys in almost all cases and would retrieve these `Secret`s from cache in almost all cases (see the section about [Secrets for Certificates](#Secrets-for-Certificates)) + +- cert-manager would cache the full `Secret` object for all labelled user created `Secret`s (issuer credentials) + +- cert-manager would cache metadata only for user created unlabelled `Secret`s that are used by issuers/cluster-issuers and would call kube apiserver directly to retrieve `Secret` data for those `Secret`s + +- cert-manager would cache metadata for all other unrelated cluster `Secret`s + +This would need to start as an alpha feature and would require alpha/beta testing by actual users for us to be able to measure the gain in memory reduction in concrete cluster setup. + +irbekrm created a [commit with a prototype of this solution](https://github.com/cert-manager/cert-manager/commit/d44d4ed2e27fb9b7695a74ae254113f3166aadb4). +In the prototype [`Secrets Transformer` function](https://github.com/cert-manager/cert-manager/blob/d44d4ed2e27fb9b7695a74ae254113f3166aadb4/pkg/controller/util.go#L219-L238) +is the transform that gets applied to all `Secret`s before they are cached. If a `Secret` does not have any known cert-manager labels or annotations it removes `data`, `metadata.managedFields` and `metadata.Annotations` and applies a `cert-manager.io/metadata-only` label. +[`SecretGetter`](https://github.com/cert-manager/cert-manager/blob/d44d4ed2e27fb9b7695a74ae254113f3166aadb4/pkg/controller/util.go#L241-L261) is used by any control loop that needs to GET a `Secret`. It retrieves it from kube apiserver or cache depending on whether `cert-manager.io/metadata-only` label was found. + +#### Drawbacks + +- All cluster `Secret`s are still listed + +- The transform functions only get run before the object is placed into informer's cache. The full object will be in controller's memory for a period of time before that (in DeltaFIFO store (?)). So the users will still see memory spikes when events related to cert-manager unrelated cluster `Secret`s occur. +See performance of the prototype: + +Create 300 cert-manager unrelated `Secret`s of size ~1Mb: + +![screenshot of a terminal using dd to create generic kubernetes secrets `foo-{0..300}`](/design/images/20221205-memory-management/createsecrets.png) + +Deploy cert-manager from https://github.com/cert-manager/cert-manager/tree/d44d4ed2e27fb9b7695a74ae254113f3166aadb4 + +Wait for cert-manager caches to sync, then run a command to label all `Secret`s to make caches resync: + +![screenshot of `kubectl label secret --all foo=bar`](/design/images/20221205-memory-management/labelsecret.png) + +Observe that although altogether memory consumption remains quite low, there is a spike corresponding to the initial listing of `Secret`s: + +![screenshot of Grafana showing prometheus memory consumption graph with memory usage spiking at 900 MB](/design/images/20221205-memory-management/transformfunctionsgrafana.png) + +### Use PartialMetadata only + +We could cache PartialMetadata only for `Secret` objects. This would mean having +just one, metadata, informer for `Secret`s and always `GET` the `Secret`s +directly from kube apiserver. + +#### Drawbacks + +Large number of additional requests to kube apiserver. For a default cert-manager installation this would mean slow issuance as client-go rate limiting would kick in. The limits can be modified via cert-manager controller flags, however this would then mean less availability of kube apiserver to other cluster tenants. +Additionally, the `Secret`s that we actually need to cache are not likely going to be large in size, so there would be less value from memory savings perspective. + +Here is a branch that implements a very experimental version of using partial metadata only https://github.com/cert-manager/cert-manager/tree/a01db22e8148318e9a16ad3acea1506c0d1a3ccc + +The following metrics are approximate as the prototype could probably be optimized. Compare with [metrics section of this proposal](#issuance-of-a-large-number-of-certificates) for an approximate idea of the increase in kube apiserver calls during issuance. + +Deploy cert-manager from https://github.com/cert-manager/cert-manager/tree/a01db22e8148318e9a16ad3acea1506c0d1a3ccc + +Run a script to set up 10 CA issuers, create 500 certificates and observe that the time taken is significantly greater than for latest version of cert-manager: +![screenshot of "Script started running at 16:39:04/start: 1673368744 end: 1673369829/runtime is 1085 seconds"](/design/images/20221205-memory-management/partialonly.png) + +Observe high request latency for cert-manager: +![Grafana graphs showing Memory consumption/CPU consumption/Rate of request to kube apiserver/Rate limiting per call to kube apiserver (up to 2s)/Total reconciles across all control loops](/design/images/20221205-memory-management/partialonlycertmanager.png) + +Observe a large number of additional requests to kube apiserver: +![Grafana graphs showing Memory consumption/CPU consumption/Requests for secrets to kube apiserver (peaking above 25)](/design/images/20221205-memory-management/partialonlykubeapiserver.png) + +### Use paging to limit the memory spike when controller starts up + +LIST calls to kube apiserver can be [paginated](https://kubernetes.io/docs/reference/using-api/api-concepts/#retrieving-large-results-sets-in-chunks). +Perhaps not getting all objects at once on the initial LIST would limit the spike in memory when cert-manager controller starts up. + +However, currently it is not possible to paginate the initial LISTs made by client-go informers. +Although it is possible to set [page limit](https://github.com/kubernetes/apimachinery/blob/v0.26.0/pkg/apis/meta/v1/types.go#L371-L387) when creating a client-go informer factory or an individual informer, this will in practice not be used for the initial LIST. +LIST requests can be served either from etcd or [kube apiserver watch cache](https://github.com/kubernetes/apiserver/tree/v0.26.0/pkg/storage/cacher). +Watch cache does not support pagination, so if a request is forwarded to the cache, the response will contain a full list. +Client-go makes the initial LIST request [with resource version 0](https://github.com/kubernetes/client-go/blob/v0.26.0/tools/cache/reflector.go#L592-L596) for performance reasons (to ensure that watch cache is used) and this results in [the response being served from kube apiserver watch cache](https://github.com/kubernetes/apiserver/blob/v0.26.0/pkg/storage/cacher/cacher.go#L621-L635). + +There is currently an open PR to implement pagination from watch cache https://github.com/kubernetes/kubernetes/pull/108392. + +### Filter the Secrets to watch with a label + +Only watch `Secret`s with known `cert-manager.io` labels. Ensure that label gets applied to all `Secret`s we manage (such as `spec.secretName` `Secret` for `Certificate`). +We already ensure that all `spec.secretName` `Secret`s get annotated when synced - we can use the same mechanism to apply a label. +Users will have to ensure that `Secret`s they create are labelled. +We can help them to discover which `Secret`s that are currently deployed to cluster and need labelling with a `cmctl` command. +In terms of resource consumption and calls to apiserver, this would be the most efficient solution (only relevant `Secret`s are being listed/watched/cached and all relevant `Secret`s are cached in full). + +#### Drawbacks + +- Bad user experience - breaking change to adopt and introduces a potential footgun after adoption as even if users labelled all relevant `Secret`s in cluster at time of adoption, there would likely be no visible warning if an unlabelled `Secret` for an issuer got created at some point in future and things would just silently not work (i.e `Secret` data updates would not trigger issuer reconcile etc). + +- This feature would likely need to be opt-in 'forever' as else it would be a major breaking change when adopting and a potential footgun after adoption + +- Maintenance cost of the `cmctl` command: if a new user created `Secret` needs to be watched in a reconcile loop, the cmctl command would also need to be updated, which could be easily forgotten + +### Allow users to pass a custom filter + +Add a flag that allows users to pass a custom selector (a label or field filter) + +See an example flag implementation for cainjector in https://github.com/cert-manager/cert-manager/pull/5174 thanks to @aubm for working on this. + +It might work well for cases where 'known' selectors need to be passed that we could event document such as `type!=helm.sh/release.v1`. + +#### Drawbacks + +- bad user experience - no straightforward way to tell if the selector actually does what was expected and an easy footgun especially when users attempt to specify which `Secret`s _should_ (rather than _shouldn't_) be watched + +- users should aim to use 'negative' selectors, but that be complicated if there is a large number of random `Secret`s in cluster that don't have a unifying selector + +### Use a standalone typed cache populated from different sources + +As suggested by @sftim https://kubernetes.slack.com/archives/C0EG7JC6T/p1671478591357519 + +We could have a standalone cache for typed `Secret`s that gets populated by a standard watch for labelled `Secret`s as well as from `Secret`s that were retrieved in reconciler loops. A metadata only cache would also be maintained. +This should ensure that a `Secret` that our control loop needs, but is not labelled only gets retrieved from kube apiserver once. So it should provide the same memory improvements as the main design, but should avoid additional kube apiserver calls in cases where users have unlabelled cert-manager related `Secret`s in cluster. + +#### Drawbacks + +- complexity of implementation and maintenance of a custom caching mechanism + +[^1]: We thought this might happen when the known cert-manager label gets added to or removed from a `Secret`. There is a mechanism for removing such `Secret` from a cache that should no longer have it, see [this Slack conversation](https://kubernetes.slack.com/archives/C0EG7JC6T/p1671476139766499) and when experimenting with the prototype implementation I have not observed stale cache when adding/removing labels + +[^2]: fao = 'for attention of' diff --git a/design/20230302.gomod.md b/design/20230302.gomod.md new file mode 100644 index 00000000000..abea74fc1f9 --- /dev/null +++ b/design/20230302.gomod.md @@ -0,0 +1,366 @@ +# Design: More Modules + +NB: This design doc follows from a Hackathon by [@SgtCoDFish](https://github.com/SgtCoDFish) and [@inteon](https://github.com/inteon). + +The intention here is to describe what we did and what we discovered, with an eye to seeking consensus and merging upstream. + +## In Short + +### Assumptions / Axioms + +- It's hard or impossible to upgrade our dependencies months after a release +- We won't change our conservative approach to backports +- The fewer dependencies a go module has, the easier it is to maintain +- It's OK if people can't import our binaries as go modules + +### Solution + +- Create a go module for each binary +- Create go modules for integration and e2e tests +- Utilise local replace statements where possible + - i.e. Binaries have a local replace for the core cert-manager module + - This breaks imports of those binaries but means changes only require one PR +- We call `github.com/cert-manager/cert-manager` the **core module** +- We call all other new modules **secondary modules** + +### Pros + +- Each binary can be patched independently + - Side effects of a patch are limited to one binary when only that binary has the dependency + - For example, consider updating Helm before go module proliferation + - Updating the Helm version alone won't affect anything which doesn't import Helm + - **But:** Updating Helm also brings in Helm's updated dependencies which _would_ affect other binaries + - E.g., we and Helm depend on the k8s libraries + - That means that bumping Helm forces a bump of all k8s APIs for _all_ binaries + - With proliferation, bumping Helm would still bump the k8s libraries - but _only_ for cmctl! + - This includes forking a dependency or needing to `replace` one + - In summary: Proliferation gives us more control over our own destiny + +- Core go.mod dependencies are reduced + - All importers of `github.com/cert-manager/cert-manager` have fewer transitive dependencies + - Reduced chance of dependency conflicts for all importers + - Including us - in our subprojects! + - Many people need to import cert-manager! (pkg/apis, etc). + - We might split things more in the future - this is a good first step + +- Lays the groundwork for further splitting out binaries / packages + - This is the start of what we'll do if we want cmctl to be its own repo + - Or splitting `pkg/apis` into a separate module + - Or splitting issuers into a module (to isolate cloud SDK dependencies) + +### Cons + +- Using local `replace` statements for binaries will break external importers of those binaries + - We assume this won't be too destructive in most cases (since we don't see many importers of those binaries) + - If we need to make binaries importable again, we can change them to use regular import statements + - That would require two PRs in the event that we need to change the secondary module and the core module at the same time + - If the secondary module would've ended up in a separate repo anyway (e.g., cmctl) we'd have done this eventually + +- Increased complexity in working with the codebase + - E.g., `go test ./...` no longer tests _everything_, since it won't recurse into modules + - This can be alleviated with some Makefile work - `make test` can still test everything + - Go Workspaces (`go.work`) can also help in development environments to make things simpler + +## Longer Form Problem Statement + +**In short:** Some of our dependencies are complex which makes them hard to upgrade in already-released versions + +Upgrading the dependencies of even simple Go projects can be tricky and for a more complex project like cert-manager +it can be impossible to upgrade dependencies for older releases while satisfying all constraints that we place on +ourselves as maintainers. + +In our case, these constraints are to: + +1. Minimise / eliminate CVE reports for any supported release of cert-manager +2. Be conservative about upgrades, and avoid major version bumps in already-released software + +Since we have one `go.mod` file for all of our built binaries, it's not possible for us to be selective about upgrades, +either. If, say, only the `controller` component were to report as having a critical vulnerability, we'd have no +way of fixing only that one vulnerability while leaving everything else untouched. + +Essentially, our current project layout forces us to make difficult choices whenever we need to upgrade things. + +### Problem Example + +**In short:** An example of how upgrades can be particularly difficult in some cases, with no good options. + +At the time of writing, cert-manager 1.10 is still in support and depends on Helm because it's imported by `cmctl` (and +only `cmctl`). We can see the dependency in [go.mod](https://github.com/cert-manager/cert-manager/blob/f54dd1dc98900607e1db7bd4ac2512f0bfe39301/go.mod#L41). + +There's a vulnerability reported for Helm v3.10.3 ([1]) which we'd like to patch, but the only version with a fix +available is Helm v3.11.1. + +Between Helm 3.10 and 3.11, several of Helm's dependencies were upgraded, and crucially Helm has some of the same +dependencies that cert-manager does. That means that we can't easily _just_ upgrade Helm. + +Running `go get -u helm.sh/helm/v3` produces 56 different upgrades of _other_ dependencies. Notably, it bumps our +Kubernetes dependencies from v0.25.2 to v0.26.0 but there are several other changes. + +(NB: Helm is just an example here and we could have problems with any package) + +## Proposed Solution: Go Module Proliferation + +**In short:** Add several new `go.mod` files so individual components can be patched independently + +We can create several new Go modules so that each binary we build can have distinct dependencies. This would mean that +`cmctl` having a dependency on Helm would only affect `cmctl` and wouldn't force us to change any of the other +components we build in order to patch a Helm vulnerability. + +Plus, where we have testing-only dependencies (e.g., for integration or end-to-end tests) we could create a test module +so that those test dependencies don't pollute the main `go.mod`. + +### Terminology + +Currently cert-manager has one module name: `github.com/cert-manager/cert-manager`. This import path is widely used and +we can't break imports of this module. We'll call this the **"core" module.** + +This proposal also introduces several new modules which depend on the core module. We'll call these "secondary" modules. + +### Solution Detail + +First, we'll add a go.mod file for each binary we ship under `cmd/` - `acmesolver`, `cainjector`, `controller`, `ctl` and `webhook`. + +These new modules should resolve to having identical dependencies to what they currently have (i.e. we shouldn't bump any versions +at this stage). + +```text +cmd +├── acmesolver +│   ├── ... +│   ├── go.mod +│   ├── go.sum +│   ├── main.go +├── cainjector +│   ├── ... +│   ├── go.mod +│   ├── go.sum +│   └── main.go +├── controller +│   ├── ... +│   ├── go.mod +│   ├── go.sum +│   └── main.go +├── ctl +│   ├── go.mod +│   ├── go.sum +│   ├── main.go +│   └── ... +└── webhook +    ├── go.mod +    ├── go.sum +    ├── main.go +    └── ... +``` + +These changes will also require tweaks to how modules are built and tested, which will be done in our `Makefile`. + +After these changes, running `go mod tidy` on the core cert-manager module should clean a lot of dependencies but will +leave many SDKs since they're depended on by issuer logic which is in `pkg/`. + +As part of this process there will be several import paths which will need to be fixed, but nothing should break. + +### Workflow Example: Changing a Binary + +NB: See `Importing cert-manager / Development Experience` below for an exploration of the problems we face here and reasoning +behind the proposed solution. + +As an example of the kind of change being discussed, imagine adding a new field to our CRDs along with a feature gate. This +would require changes both to at least one secondary module (e.g., the controller) and to the core cert-manager module. + +In order to avoid having to make two PRs for this kind of change we propose to explicitly state that any external import of +the new modules under `cmd` is not supported. By breaking this kind of external import, we can use the `replace` directive +in the new `go.mod` files for each of the binaries to refer to the cert-manager package in the same repository. + +This means that every change to `pkg/` will automatically be picked up by all of the binaries that we build and test in CI. + +An example of the replace directive is given below: + +```gomod +module github.com/cert-manager/cert-manager/controller-binary + +go 1.19 + +replace github.com/cert-manager/cert-manager => ../../ + +require ( + github.com/cert-manager/cert-manager v0.0.0-00010101000000-000000000000 + ... +) +``` + +To be clear: using replace directives like this will break anyone who tries to import the `github.com/cert-manager/cert-manager/controller-binary` +module or anyone who was previously importing `github.com/cert-manager/cert-manager/cmd/controller` before this proposal. + +## Potential Issues + +### Importing cert-manager / Development Experience + +**In short:** Module replacements help developers but aren't respected by imports, meaning some changes could need two PRs or we'd have to break anyone importing certain modules + +**Useful Reference:** It helps to read [this StackOverflow comment](https://stackoverflow.com/a/71984158) to better understand the options we have + +The simplest development experience when working with multiple Go modules at once is to use either the `replace` directive +in `go.mod` or the `use` directive in `go.work` to point to local versions of a module. This allows both modules to be +developed in parallel on a local machine. + +For modules which we don't think should ever be imported by third parties, replace directives would work so that those +modules always use the version of cert-manager which is at the same commit as those modules. + +For example we could look at the `controller` component which would depend on the core cert-manager module. Its +`go.mod` might look like the example given above under "Workflow Example: Changing a Binary". + +An issue with this approach is that the `replace` statement wouldn't be respected if anyone imports the controller module +from a 3rd party project. Instead, that 3rd party would see an error relating to an unknown version of cert-manager. + +For this example involving `cmd/controller` it might well be acceptable for us to break 3rd party imports but for other +modules that might not be reasonable. In that case, we'll always have a fallback; using a 'regular' import of the core module. + +This would mean that we create two PRs for a change; the first changes the core module, and the second updates the secondary +module to import the new core module version created by the previous PR. + +UPDATE: As we implemented this design, it was decided that we didn't want to break imports of `cmctl` because it was +used in several other cert-manager subprojects, so cmctl uses the approach described above. + +#### Potential Solution for Developer Experience: Dynamic `go.work` + +We could introduce a make target which generates one or more `go.work` files locally to point all modules at local +development versions. This doesn't help with having to raise two PRs for a change, but it does help minimise the +burden of testing changes locally. + +This could mean that users won't notice if they forget to bump their `go.mod` files to point at a new release of the core +module - but tests should fail in CI to alert them of this problem. + +### Running Tests + +**In short:** Multiple modules in one repo break `go test ./...` + +Part of the migration to Make enabled the use of `go test` for testing. Under the hood, our make targets essentially +use `go test` themselves. + +The issue is that `go test` won't recurse into other modules. If we make `cmd/controller` a separate module, then +`go test ./pkg/... ./cmd/...` won't run any of the tests in `cmd/controller`. Any existing uses of `go test ./...` +which intend to test everything will silently start to not test everything. + +This can be mitigated by leaning more heavily on make; we can have `make test` run the tests for every module. It's a +shame to lose the ability to test everything with `go test` in this way, but the tradeoff ultimately seems worth it. + +### Test Modules + +**In short:** The test/ directory could (should) be a module but part of it is imported elsewhere. + +The `test/` directory at the root of the cert-manager repo today has several purposes. + +`test/unit` provides a library which is imported by several other packages, to aid with setting up data for unit tests. +For example, `pkg/controller/certificatesigningrequests/ca/ca_test.go` imports the `test/unit/gen` package to aid +with generating test data. `test/internal` has similar content to `test/unit`, but focusing more on utility functions. + +`test/integration` and `test/e2e` implement actual tests which are designed to run against cert-manager but which don't +fit under the category of unit tests. These test directories have external dependencies including on cloudflare-go and +the Hashicorp Vault API along with imports for the cmctl and cert-manager webhook code. + +Essentially, the `test/` directory has both _actual tests_ and _test utility code_. The actual tests import several +areas of cert-manager which become external modules under these proposals, and the utility code is imported by the core +cert-manager module. + +#### Solution: Split Test Code + +Since there are two types of code in `test/`, we can split it. + +There are [known external importers](https://pkg.go.dev/github.com/cert-manager/cert-manager@v1.11.0/test/unit/gen?tab=importedby) +of `test/unit/` which means it's difficult to move that without breaking people. + +As such, we could move test/e2e and test/integration or we could make them both independent modules and keep them +where they are. + +The diff for the main repository `go.mod` after separating out the tests is presented in footnote [2]. + +### Increased Time to Patch Everything + +Having multiple go.mod files will mean that when we share a dependency across many components (such as the Kubernetes +libraries) we'll have to update multiple files rather than just one. Alternatively, if we update a dependency for the +core `go.mod` file we'll maybe want to also update every other go.mod which imports that one. + +## Other Considered Approaches + +### Being Less Conservative + +The main issue we face with upgrading older versions of cert-manager is that we self-impose strict conservatism when +it comes to any kind of backport. In this view, any change for any reason is inherently seen as bad and to be avoided, +even if that change has no runtime impact for users. + +We don't need to do this. While we wouldn't seek to make major version upgrades in backports just for the fun of it, +we could choose to accept a larger subset of backports and rely on our tests to confirm that the change is sound. + +This doesn't solve the problems of allowing independent control over the dependencies of different binaries, though, +and doesn't reduce the attack surface of any of our components. + +### Aggressively Reducing Dependencies + +Rather than isolating dependencies, we could remove them by, e.g., vendoring subsets of their code into our repo. This +gives us a huge amount of control and allows us to preserve backwards compatibility very easily. + +It also creates a huge burden for us to maintain that vendored code, which is a drawback. We'd still have to track +e.g., Helm to see if there are any relevant vulnerabilities reported, and then we'd have to go and actually fix them +ourselves. If upstream code diverged significantly we might be left on our own trying to work out how to fix bugs - or +even trying to work out if we even have a bug. + +There's probably some low hanging fruit we could pick here, but we're unlikely to be able to fully remove a big chunk +of our dependencies. That means the problem won't go away - and there's always the chance that we need to add new +dependencies down the road. + +## Addendum: Groundwork for `pkg/apis` Module + +We've talked before about creating a separate `pkg/apis` module or repo to improve the experience for users who need +to import that specific path (which is common). + +Module proliferation could be a solution here by making that path a new module. + +Changing the `pkg/apis` module isn't really related to reducing dependencies so it's a little different to the rest of +this proposal and we don't propose to do it as part of this work. But the implementation of this design might inform +how we could approach solving the `pkg/apis` problem. + +## Footnotes + +[1] cert-manager likely isn't actively vulnerable to these specific Helm CVEs, but it's easy to imagine something being +reported which it's actually vulnerable to and which we'd _have_ to upgrade. + +[2] The diff from separating integration and e2e tests into their own modules: + +```diff +diff --git a/go.mod b/go.mod +index c95d5fbe3..ef3fcfc64 100644 +--- a/go.mod ++++ b/go.mod +@@ -10,7 +10,6 @@ require ( + github.com/Venafi/vcert/v4 v4.0.0-00010101000000-000000000000 + github.com/akamai/AkamaiOPEN-edgegrid-golang v1.2.2 + github.com/aws/aws-sdk-go v1.44.179 +- github.com/cloudflare/cloudflare-go v0.58.1 + github.com/cpu/goacmedns v0.1.1 + github.com/digitalocean/godo v1.93.0 + github.com/go-ldap/ldap/v3 v3.4.4 +@@ -22,14 +21,10 @@ require ( + github.com/kr/pretty v0.3.1 + github.com/miekg/dns v1.1.50 + github.com/mitchellh/go-homedir v1.1.0 +- github.com/munnerz/crd-schema-fuzz v1.0.0 + github.com/onsi/ginkgo/v2 v2.7.0 +- github.com/onsi/gomega v1.24.2 + github.com/pavlo-v-chernykh/keystore-go/v4 v4.4.1 + github.com/pkg/errors v0.9.1 + github.com/prometheus/client_golang v1.14.0 +- github.com/segmentio/encoding v0.3.6 +- github.com/sergi/go-diff v1.3.1 + github.com/spf13/cobra v1.6.1 + github.com/spf13/pflag v1.0.5 + github.com/stretchr/testify v1.8.1 +@@ -203,7 +198,7 @@ require ( + github.com/rubenv/sql-migrate v1.2.0 // indirect + github.com/russross/blackfriday/v2 v2.1.0 // indirect + github.com/ryanuber/go-glob v1.0.0 // indirect +- github.com/segmentio/asm v1.1.3 // indirect ++ github.com/sergi/go-diff v1.3.1 // indirect + github.com/shopspring/decimal v1.2.0 // indirect + github.com/sirupsen/logrus v1.9.0 // indirect + github.com/spf13/cast v1.4.1 // indirect +``` diff --git a/design/20230601.gateway-route-hostnames.md b/design/20230601.gateway-route-hostnames.md new file mode 100644 index 00000000000..3393102ef17 --- /dev/null +++ b/design/20230601.gateway-route-hostnames.md @@ -0,0 +1,214 @@ +# Inferring TLS Hostnames From Gateway Routes + + +- [Release Signoff Checklist](#release-signoff-checklist) +- [Summary](#summary) +- [Motivation](#motivation) + - [Goals](#goals) + - [Non-Goals](#non-goals) +- [Proposal](#proposal) + - [User Stories](#user-stories) + - [Story 1](#story-1) + - [Story 2](#story-2) + - [Notes/Constraints/Caveats (Optional)](#notesconstraintscaveats-optional) + - [Risks and Mitigations](#risks-and-mitigations) +- [Design Details](#design-details) + - [Test Plan](#test-plan) + - [Graduation Criteria](#graduation-criteria) + - [Upgrade / Downgrade Strategy](#upgrade--downgrade-strategy) + - [Supported Versions](#supported-versions) +- [Production Readiness](#production-readiness) +- [Drawbacks](#drawbacks) +- [Alternatives](#alternatives) + + +## Release Signoff Checklist + +This checklist contains actions which must be completed before a PR implementing this design can be merged. + + +- [ ] This design doc has been discussed and approved +- [ ] Test plan has been agreed upon and the tests implemented +- [ ] Feature gate status has been agreed upon (whether the new functionality will be placed behind a feature gate or not) +- [ ] Graduation criteria is in place if required (if the new functionality is placed behind a feature gate, how will it graduate between stages) +- [ ] User-facing documentation has been PR-ed against the release branch in [cert-manager/website] + + +## Summary + +For generating Gateway API certificates, use hostnames present in, e.g., `GRPCRoute`, `HTTPRoute`, and `TLSRoute` resources in addition to the `Gateway` listener hostnames. +This reduces configuration duplication, and allows the cluster owner to delegate permission to site owners to add hostnames. + +## Motivation + +Currently, the gateway-shim only looks at the `hostname` in [`Listener`](https://gateway-api.sigs.k8s.io/references/spec/#gateway.networking.k8s.io/v1beta1.Listener). +This field is optional, and its purpose is to filter which hostnames routes are allowed to match. +This double-configuration allows the cluster owner to set allowed hostnames in `GatewaySpec`, while individual site owners update their `HTTPRouteSpec`. +In cases where this permission model is unnecessary (either because all hostnames are allowed, or because the cluster and site owners are the same team), this leads to awkward duplication. +As with any configuration duplication, it is easy to miss an update in one place, causing difficult-to-find bugs, and requiring teams to maintain more internal documentation. +E.g. Envoy Gateway already supports running a `Gateway` without hostnames in the `Listener`. + +Another drawback inherent in using `Listener.hostname` is that it is a singleton. +To add another hostname, the entire `Listener` object must be duplicated, including `port`, `protocol` and `tls` fields. +This adds yet another source of duplication. + +### Goals + +* To be compliant with the intention of the Gateway API. +* To treat resources the same way as current Gateway API implementations, e.g., [Envoy Gateway](https://gateway.envoyproxy.io/). +* To remove duplicated configuration. + +### Non-Goals + +N/A + +## Proposal + +```yaml +apiVersion: gateway.networking.k8s.io/v1beta1 +kind: Gateway +metadata: + name: tls-basic +spec: + gatewayClassName: acme-lb + listeners: + - name: https-1 + hostname: 1.example.com + protocol: HTTPS + port: 443 + tls: + mode: Terminate + certificateRefs: + - name: default-cert + - name: https-2 + hostname: 2.example.com + protocol: HTTPS + port: 443 + tls: + mode: Terminate + certificateRefs: + - name: default-cert +--- +# An HTTPRoute that uses the two hosts. +``` + +Compare this with the following `HTTPRoute` and `Gateway`: + +```yaml +apiVersion: gateway.networking.k8s.io/v1beta1 +kind: Gateway +metadata: + name: example-gateway +spec: + gatewayClassName: example-gateway-class + listeners: + - name: http + protocol: HTTP + port: 80 +--- +apiVersion: gateway.networking.k8s.io/v1beta1 +kind: HTTPRoute +metadata: + name: example-route +spec: + parentRefs: + - name: example-gateway + hostnames: + - "1.example.com" + - "2.example.com" + rules: + - backendRefs: + - name: example-svc + port: 10080 +``` + +Note that `HTTPRouteSpec.hostnames` is a list, avoiding duplication. +As long as there are no hostnames in the `Listener`, this allows the hostnames as if they were present there. +If there are hostnames in the `Listener`, the spec says the `Listener` only deals with the intersection. + +Hostnames make more sense in _Route_ resources than in `Listener`s, as a single route may be used for both HTTP and HTTPS. + +See the Gateway API spec on [`GatewaySpec.listeners`](https://gateway-api.sigs.k8s.io/references/spec/#gateway.networking.k8s.io/v1beta1.GatewaySpec) for more information. + +### User Stories + +1. Site owner creates an `HTTPRoute` with two new hostnames, but doesn't change the `Gateway`. +2. cert-manager immediately picks them up and re-generates the certificate for the `Gateway`. + +### Risks and Mitigations + +Ultimately, this is nothing new, it's just about following the Gateway API spec. + +1. The gateway-shim needs to subscribe and react to all Route resources, which could add CPU/memory/API server load. +2. If the cluster owner and site owners are separate, requiring the cluster owner to allow specific hostnames may be beneficial. + +## Design Details + +This is based on the proof-of-concept in [tommie/cert-manager](https://github.com/cert-manager/cert-manager/compare/master...tommie:cert-manager:httproute). + +The easiest way to implement this is to generate synthetic listeners early in gateway-shim, and let the main controller logic stay the same. +`Listener`s with hostnames are not affected, since the intersection of routes and listeners determines the listener's capabilities. +A listener without hostname matches any hostname in attached routes, and they can simply be copied once for each route hostname. +I.e. the second example under [Proposal](#proposal) would be translated to the first. + +Some glue data types are needed to support all routes that can carry hostnames. +At the moment, these are: + +* [GRPCRoute](https://gateway-api.sigs.k8s.io/references/spec/#gateway.networking.k8s.io/v1alpha2.GRPCRoute) +* [HTTPRoute](https://gateway-api.sigs.k8s.io/references/spec/#gateway.networking.k8s.io/v1beta1.HTTPRoute) +* [TLSRoute](https://gateway-api.sigs.k8s.io/references/spec/#gateway.networking.k8s.io/v1alpha2.TLSRoute) + +### Test Plan + +Since this code deals with how cert-manager reacts to changes in CRDs, it is enough to focus on unit tests. +For a given set of `Gateway`s and Routes, it should generate a given synthetic `Gateway`. + +### Graduation Criteria + +N/A + +### Upgrade / Downgrade Strategy + +Downgrading to a cert-manager that does not support looking up hostnames in Routes may lead to unavailability. + +The change is upgrade-compatible, if all `Gateway`s already specify hostnames in `Listener`s. +However, if a `Gateway` does not specify hostnames in `Listener`s, upgrading may cause certificates to be issued for hostnames not previously seen. +In terms of security, this is not an issue, as the Routes have always existed; they simply didn't have a valid certificate. + +### Supported Versions + +* [Gateway API](https://gateway-api.sigs.k8s.io/references/spec/) is currently at v1beta1. + +## Production Readiness + +### How can this feature be enabled / disabled for an existing cert-manager installation? + +Since this can be implemented as an input transform, that transform could be behind a feature flag. +Indeed, the entire Gateway API support is already behind a feature flag: `ExperimentalGatewayAPISupport` + +It probably does not need a specific one. + +### Does this feature depend on any specific services running in the cluster? + +It requires Gateway API CRDs, but nothing more than is already required for gateway-shim. + +### Will enabling / using this feature result in new API calls (i.e. to Kubernetes apiserver or external services)? + +It will subscribe to `HTTPRoute`, `TLSRoute`, and similar resources, beyond the previously subscribed `Gateway` resources. + +### Will enabling / using this feature result in increasing size or count of the existing API objects? + +It will not write new objects. +Enabling the feature may cause cert-manager to recognize hostnames it wasn't aware of before, and therefore issue new `Certificate`s on upgrade. + +### Will enabling / using this feature result in significant increase of resource usage? (CPU, RAM...) + +No. Route resources are small. + +## Drawbacks + +None? cert-manager should follow the Gateway API spec. + +## Alternatives + +N/A diff --git a/design/20240122.scarf.md b/design/20240122.scarf.md new file mode 100644 index 00000000000..c6caaa73e66 --- /dev/null +++ b/design/20240122.scarf.md @@ -0,0 +1,62 @@ +# Proposal: Scarf.sh integration + + +- [Summary](#summary) + - [What is Scarf?](#what-is-scarf) +- [Motivation](#motivation) + - [Goals](#goals) +- [Proposal](#proposal) + - [How will this change impact our users?](#how-will-this-change-impact-our-users) + - [Known issues/limitations](#known-issueslimitations) + + +## Summary + +With our focus on CNCF graduation, CNCF aims for its projects to become [vendor-neutral](https://contribute.cncf.io/maintainers/community/vendor-neutrality/) wherever possible. The cert-manager project should uphold this aim. In doing so, it will need to take a further step to move on from its proud Jetstack legacy with a change to remove Jetstack from the container image repository name. + +In addition, Quay.io, the current container image registry for cert-manager, has limitations on the amount of analytic data it can provide due to the high volume of downloads that cert-manager receives. The cert-manager maintainers have also found that Quay has had several outages during 2023, and they want to manage that situation quickly in the future if required. + +Recently partnered with the Linux Foundation, Scarf is a service designed for open-source projects that will allow for a simple migration. + +Scarf will provide multiple benefits: +- Not being tied to a single container image/binary repository vendor gives the freedom to change vendors if required. +- Switching to a more neutral domain (e.g., cert-manager.io). +- Continuing to provide container images at significant volume while improving the analytic data of how the project is downloaded. + +### What is Scarf? + +The open-source Scarf Gateway is the power behind the Scarf platform. The Scarf Gateway serves as a centralised point of access for the distribution of containers and packages, regardless of their actual hosting location. The Gateway is positioned before an existing registry to reroute download traffic to the storage location while providing essential usage data that the registry does not readily share. It is understood that the Scarf gateway will not act as a full proxy for all image data to pass through but only as a proxy for image metadata, redirecting the download client to the actual hosting location, such as quay.io. + +## Motivation + +### Goals + +- Discontinue using the name "Jetstack" in all container image downloads. +- Continue to provide non-breaking changes for those already using the existing "Jetstack" container image download locations. +- Gain the freedom to change image repositories with ease when necessary. +- Improve observability and reporting to maintainers of how cert-manager is downloaded to serve its users better. + +## Proposal + +- Obtain a new custom "download" domain through the CNCF to be used for fronting all binary downloads. +- The creation of a free (OSS tier) Scarf account will be configured and managed by the cert-manager maintainers. +- Update documentation referencing "jetstack" binary paths, e.g., quay.io/jetstack/cert-manager-controller, and replace with the new download domain. +- Update helm charts referencing "jetstack" binary paths, replacing with the new download domain. +- Update code referencing "jetstack" binary paths, replacing with the new download domain. +- Add Scarf pixels to selective documentation pages, giving us insight into which pages are most useful or areas to focus on for improvement. +- Automate regular analytics gathering leveraging the Scarf API to publish relevant stats and info publicly. E.g. + - Region + - Operating System + - Container Tags / Versions + - Container Runtimes + + +### How will this change impact our users? + +Images and binaries should continue to be accessed from their existing locations. Therefore, there should be no impact on any existing downloads, automation, or mirroring. +Going forward, we would encourage users to use the new download paths by specifying the new domain in the documentation. + +Any users downloading from secure environments with limited internet connections through firewall restrictions will need to add "allowed" rules for the Scarf gateway domain in addition to any existing rules for the image repository, such as quay.io. These should be clearly documented. + +### Known issues/limitations +- Currently, the Scarf service only allows for custom domains and doesn't include custom paths. When speaking with members of the Scarf organisation, this is due to a technical limitation as the path is used in the image identification/verification process. Scarf is investigating a workaround; however, we may need to consider an additional hosting location/service to allow us to remove "jetstack" from the download path. An additional hosting location will increase existing maintenance and deployment process overheads. diff --git a/design/20240206.helm-resource-policy.md b/design/20240206.helm-resource-policy.md new file mode 100644 index 00000000000..4cea0534d76 --- /dev/null +++ b/design/20240206.helm-resource-policy.md @@ -0,0 +1,137 @@ + + +# Proposal: add "helm.sh/resource-policy: keep" CRD annotation and uniformise CRD options. + + +- [Release Signoff Checklist](#release-signoff-checklist) +- [Summary](#summary) +- [Motivation](#motivation) + - [Goals](#goals) + - [Non-Goals](#non-goals) +- [Proposal](#proposal) +- [Design Details](#design-details) +- [Drawbacks](#drawbacks) +- [Alternatives](#alternatives) + + +## Release Signoff Checklist + +This checklist contains actions which must be completed before a PR implementing this design can be merged. + + +- [ ] This design doc has been discussed and approved +- [ ] Test plan has been agreed upon and the tests implemented +- [ ] Feature gate status has been agreed upon (whether the new functionality will be placed behind a feature gate or not) +- [ ] Graduation criteria is in place if required (if the new functionality is placed behind a feature gate, how will it graduate between stages) +- [ ] User-facing documentation has been PR-ed against the release branch in [cert-manager/website] + + +## Summary + +Using Helm to install CRDs is difficult. We cannot use the Helm `crds/` folder to install CRDs because then CRDs are not upgraded when the Helm chart is upgraded. For that reason, we use the `templates/` folder to install CRDs. However, this means that the CRDs are removed when the Helm chart is uninstalled. This is not ideal because it means that all custom resources are removed too. + +## Motivation + + + +### Goals + + + +There are two use cases we want to support: +- install CRDs with Helm; safely and up-to-date +- manage CRDs with a tool different from Helm + +Right now, we have different Helm chart CRD options for the different cert-manager projects, we want a standardised solution across most of these projects: +- cert-manager: "installCRDs" +- trust-manager: "crds.enabled" +- approver-policy, istio-csr, csi-driver(-spiffe): \ + +### Non-Goals + + + +/ + +## Proposal + + + +I would like to introduce the following options to all Helm charts that install CRDs (based on https://github.com/cert-manager/cert-manager/pull/5777): +```yaml +crds: + # This option decides if the CRDs should be installed + # as part of the Helm installation. + enabled: true + + + # This option makes it so that the "helm.sh/resource-policy": keep + # annotation is added to the CRD. This will prevent Helm from uninstalling + # the CRD when the Helm release is uninstalled. + # WARNING: when the CRDs are removed, all cert-manager custom resources + # (Certificates, Issuers, ...) will be removed too by the garbage collector. + keep: true +``` + +**NOTE 1:** For backwards compatibility, the crds.enabled option would be false for the cert-manager chart. + +**NOTE 2:** For the cert-manager chart, instead of introducing two new options, we could use the existing `installCRDs` option and add a new `keepCRDs` option. + +## Design Details + + + +*Possible breaking change:* +This change will change the default uninstall behavior of the Helm chart. Before, the CRDs were removed when the Helm chart was uninstalled. Now, the CRDs will be kept by default. If the user wants to remove the CRDs too, they will have to manually delete them. This is a breaking change because it changes the default behavior of the Helm chart, but it will also make the lives of a lot of users much easier. I think the benefits outweigh the costs. + +*Info about the "helm.sh/resource-policy" annotation:* +Since we are using the templates/ folder to manage CRDs, which is required to allow templating and up-dating, the CRDs will be removed when we uninstall the chart. However, this annotation allows us to keep the resource even after the chart was uninstalled. We want to keep the CRDs to prevent accidental deletion of the custom resources. + +*The challenge with having only CRDs in a cluster, no webhooks:* +After uninstalling the Helm chart, we are left with only the CRDs. The ValidatingWebhookConfiguration and the MutatingWebhookConfiguration are removed too. This means that the CRs will be freely editable, potentially causing inconsistencies. Also, the `cmctl check api` command will still return successfully, because it can create CRs without any issues. A potential fix for the second problem would be to check that the webhook performs the required mutations/ validations. + +## Drawbacks + + + +This change will introduce new required steps in the following scenarios: + +- To fully uninstall the Helm chart, we now need to additionally run `kubectl delete …` +- To re-install a Helm chart, if the new install has the same name and namespace, the CRDs are adopted automatically, otherwise, the CRDs have to be updated to match the name and namespace of the new release. + +## Alternatives + + + +Install CRDs separately (e.g., using `kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.14.1/cert-manager.crds.yaml` or using a separate Helm chart) and manage them separately from the Helm chart. +This would require us to publish a separate Helm chart for the CRDs or a static manifest for the CRDs. diff --git a/design/20240518.push-to-multiple-registries.md b/design/20240518.push-to-multiple-registries.md new file mode 100644 index 00000000000..6a9cee1da00 --- /dev/null +++ b/design/20240518.push-to-multiple-registries.md @@ -0,0 +1,289 @@ + + +# Push image artifacts to multiple repositories + + + +- [Release Signoff Checklist](#release-signoff-checklist) +- [Summary](#summary) +- [Motivation](#motivation) + - [Goals](#goals) + - [Non-Goals](#non-goals) +- [Proposal](#proposal) + - [User Stories (Optional)](#user-stories-optional) + - [Story 1](#story-1) + - [Story 2](#story-2) + - [Notes/Constraints/Caveats (Optional)](#notesconstraintscaveats-optional) + - [Risks and Mitigations](#risks-and-mitigations) +- [Design Details](#design-details) + - [Test Plan](#test-plan) + - [Graduation Criteria](#graduation-criteria) + - [Upgrade / Downgrade Strategy](#upgrade--downgrade-strategy) + - [Supported Versions](#supported-versions) +- [Production Readiness](#production-readiness) +- [Drawbacks](#drawbacks) +- [Alternatives](#alternatives) + + +## Release Signoff Checklist + +This checklist contains actions which must be completed before a PR implementing this design can be merged. + + +- [ ] This design doc has been discussed and approved +- [ ] Test plan has been agreed upon and the tests implemented +- [ ] Feature gate status has been agreed upon (whether the new functionality will be placed behind a feature gate or not) +- [ ] Graduation criteria is in place if required (if the new functionality is placed behind a feature gate, how will it graduate between stages) +- [ ] User-facing documentation has been PR-ed against the release branch in [cert-manager/website] + + +## Summary + + +The cert-manager project, along with its sub-projects, currently utilizes the quay.io/jetstack registry for pushing OCI (Open Container Initiative) artifacts. This originates from the project's beginnings under Jetstack. However, to reflect the project's growth and establish a more neutral and independent identity, this proposal recommends adding a new OCI artifact registry location without the Jetstack branding. + +## Motivation + + +The primary motivation for this enhancement is to reinforce the independence and neutrality of the cert-manager project. Originally developed by Jetstack, cert-manager currently pushes OCI artifacts to the quay.io/jetstack registry. As the project has grown and evolved into a community-driven initiative, it is essential to establish a neutral artifact repository that better represents the project's diverse and independent nature. + +### Goals + + +- Decouple the cert-manager project from Jetstack’s branding to highlight its status as a community-driven project. +- Ensure all project documentation reflects the new registry to guide users appropriately without disrupting existing workflows. +- Continue pushing artifacts to the current quay.io/jetstack registry to avoid breaking changes for existing users while transitioning to the new registry. + +### Non-Goals + + +- This proposal does not aim to immediately deprecate the quay.io/jetstack registry but to introduce an additional registry. +- No changes to the functionality or features of cert-manager are included in this proposal. + +## Proposal + + + +### User Stories (Optional) + + + +#### Story 1 + +*As a* new user of cert-manager, *I want to* find clear documentation that directs me to the appropriate registry for downloading OCI artifacts, *so that* I can easily set up and use cert-manager in my environment without confusion about which registry to use. + +Details: + +- The updated documentation prominently lists the new neutral registry URL. +- The documentation includes notes that the artifacts are also available in the quay.io/jetstack registry for backward compatibility. + +#### Story 2 + +*As an* existing user of cert-manager, *I want to* continue receiving updates from the quay.io/jetstack registry while gradually transitioning to the new registry, *so that* my current setup remains functional without immediate changes, giving me time to update my configurations. + +Details: + +- Artifacts continue to be pushed to both the quay.io/jetstack and the new registry. +- A clear migration guide is provided, explaining how to switch to the new registry at a convenient time. + +### Notes/Constraints/Caveats (Optional) + + + +### Risks and Mitigations + + + +*Risk:* The new registry might introduce security vulnerabilities, such as unauthorized access to artifacts. + +*Mitigation:* +- Existing CI/CD will be used to publish images, this is proven and secure. +- The new registry should have the same access control restrictions as the current quay.io/jetstack registry - ensuring that only maintainers have write access. + +## Design Details + + +To implement the transition to a new OCI artifact registry, our existing CI/CD pipeline will be updated to push artifacts to both the existing quay.io/jetstack registry and the new registry. This dual-publishing approach ensures continuity and minimizes disruption for current users. We are considering multiple options for the new registry, with "ghcr.io/cert-manager" (GitHub Container Registry) and "quay.io/cert-manager" being the primary candidates. The final registry will be chosen based on community feedback on this proposal. Regardless of the registry, we will also need to update the CI pipeline to authenticate with the new registry, ensuring secure and seamless artifact uploads. + +Within projects using makefile modules we may need to make changes to the [OCI publish module](https://github.com/cert-manager/makefile-modules/tree/main/modules/oci-publish) to handle cases where we need different auth for different registries. After this, the pushing to multiple destinations is already supported by this module and would be a simple change to the config of each repos. + +A new E2E tests run should be performed by our nightly automation that runs the E2E suite against the new registry, to ensure that everything is working as expected. + +Once the images are being dual published, the official documentation and Helm chart will be updated to reflect the new repository. + +### Test Plan + + +By using existing automation that runs the E2E test suite each night we can add automated tests that will pull from the new registry. This tests multiple versions so is a good baseline that the image can be pulled. + +### Graduation Criteria + + +Since this proposal has no code changes, it does not have any feature flags. However its graduation should be managed. To accomplish this we should do the following: + +Alpha/Beta: +- Start publishing images to the new registry - we document the new registry but do not push it as the new default + +GA: +- Update the official Helm charts to use the new registry +- Update documentation to reflect that the new registry is the preferred one + +The criteria for graduation will be a based off maintainer confidence in the new registry, informed by the E2E test runs using the new registries and any feedback from early adopters. + +### Upgrade / Downgrade Strategy + + +Once we are happy to make this GA the Helm chart will be updated to use the new registry, this will mean that for users using the official Helm chart the change will be automatic. For other users nothing will break by them using the old registry, so they can update their deployment at their own convenience. Furthermore a user could choose to set the registry back to quay.io/jetstack in their Helm configuration if they so choose. + +### Supported Versions + + +N/A + +## Production Readiness + +N/A + +### How can this feature be enabled / disabled for an existing cert-manager installation? + + +N/A + +### Does this feature depend on any specific services running in the cluster? + + +N/A + +### Will enabling / using this feature result in new API calls (i.e to Kubernetes apiserver or external services)? + +N/A + +### Will enabling / using this feature result in increasing size or count of the existing API objects? + + +N/A + +### Will enabling / using this feature result in significant increase of resource usage? (CPU, RAM...) + + +N/A + +## Drawbacks + + +This proposal does not remove or break any functionality for users. For maintainers, pushing to multiple repositories would make gathering pull metrics more complex. + +## Alternatives + + +There are many competing container registries, the two currently in contention (ghcr and quay) were selected because we already have access and availability to push there. They also offer their services for free for open source projects such as ours. \ No newline at end of file diff --git a/design/20240625.push-charts-to-oci.md b/design/20240625.push-charts-to-oci.md new file mode 100644 index 00000000000..ba9b02edcd3 --- /dev/null +++ b/design/20240625.push-charts-to-oci.md @@ -0,0 +1,257 @@ + + +# Push cert-manager Helm Charts to an OCI Registry + + +- [Release Signoff Checklist](#release-signoff-checklist) +- [Summary](#summary) +- [Motivation](#motivation) + - [Goals](#goals) + - [Non-Goals](#non-goals) +- [Proposal](#proposal) + - [Risks and Mitigations](#risks-and-mitigations) +- [Design Details](#design-details) + - [Test Plan](#test-plan) + - [Graduation Criteria](#graduation-criteria) + - [Upgrade / Downgrade Strategy](#upgrade--downgrade-strategy) + - [Supported Versions](#supported-versions) +- [Production Readiness](#production-readiness) +- [Drawbacks](#drawbacks) +- [Alternatives](#alternatives) + + +## Release Signoff Checklist + +This checklist contains actions which must be completed before a PR implementing this design can be merged: + +- [ ] This design doc has been discussed and approved +- [ ] Test plan has been agreed upon and the tests implemented +- [ ] Feature gate status has been agreed upon (whether the new functionality will be placed behind a feature gate or not) +- [ ] Graduation criteria is in place if required (if the new functionality is placed behind a feature gate, how will it graduate between stages) +- [ ] User-facing documentation has been PR-ed against the release branch in [cert-manager/website] + +## Summary + + + +This design proposes to start pushing cert-manager's Helm charts to an OCI registry - `quay.io/jetstack`. + +It proposes no other changes - specifically, after this design is implemented charts would still be pushed to their current location +- `charts.jetstack.io` - for every release. + +## Motivation + + + +cert-manager's Helm charts are the primary way we encourage users to install the project. The same applies to sub-projects, each of which +has a Helm chart. + +All Helm charts for all projects are currently hosted in a Helm repository available at `https://charts.jetstack.io`. This is primarily +for simplicity and for historical reasons - that location was the easiest back when we started building charts and there's been no pressing +need to change. + +There is increasing pressure to change this. Firstly, there seems to be a movement away from Helm repositories in general. +There have been several [requests](https://github.com/cert-manager/cert-manager/issues/5566) for cert-manager to push charts to an OCI registry +and claims that certain tools no longer support Helm repositories. + +Secondly, we're conscious of trying to replace references to any one company in the cert-manager projects and the use of the Jetstack +domain name is obviously in tension with that. cert-manager seeks to be an entirely [vendor-neutral](https://contribute.cncf.io/maintainers/community/vendor-neutrality/) +project and the use of this domain for charts is one of the few remaining places where the cert-manager project still references Jetstack. + +While this proposal doesn't directly address this vendor-neutrality issue, it should make it simpler to address in the future as +migrating from one OCI registry to another should be simpler than migrating a repository. + +The use of the `jetstack.io` domain also implies that Jetstack (now part of Venafi) has some say over access to this repo and who can push to it. +The company could reasonably request that have non-Venafi maintainers not be given access to the chart repo. This hasn't been a problem in practice +but the risk is not ideal for a project which seeks to encourage contributors from any company. + +In addition, Venafi uses `charts.jetstack.io` for other, non-CNCF, charts. Access controls over who can push to the Helm repo are well +established and require code reviews, but given cert-manager's size and wide level of adoption it would be prudent to isolate it from +other unrelated projects. This mixing [has caused issues](https://github.com/cert-manager/cert-manager/issues/7117) for users. + +### Goals + + + +- Have charts be pullable from an OCI registry +- Force no change on users but give them the option to change to OCI registries + +### Non-Goals + + + +- Change anything about `charts.jetstack.io` +- Force anyone to change where they get their charts + +## Proposal + + + +### Risks and Mitigations + + + +### Risk 1 + +Since this proposal is only to add a new source for fetching Helm charts, there are few risks anticipated. + +One potential risk is that our current approach for signing Helm charts might need to be tweaked. We currently produce +"detached" `.prov` signature files for cert-manager which are served on `charts.jetstack.io` alongside the charts +themselves. + +Some experimentation may be required to work out how these detached signatures work with OCI registries. This isn't +urgent, as we'll continue to serve the signatures on our existing chart repository and there are other methods of +signing available with OCI registries - notably, using sigstore / cosign. Note that the detached signatures are only +relevant for cert-manager itself and not subprojects. + +### Risk 2 + +Changing cmrel will apply to future releases of cert-manager (v1.16.x) but also to past releases. Unless we make +efforts to tag a cmrel version which doesn't push charts to OCI registries, or otherwise disable the pushing of +charts for older cert-manager releases, we'll start pushing charts to OCI registries if we do a patch release of an +existing supported cert-manager version. + +This risk is minimal since - once again - the new registry won't be a default. + +## Design Details + + + +First, we'll create a new repository in quay.io, called `quay.io/jetstack/charts`. + +Next, add a new step to the release process which pushes Helm charts to an OCI registry. This would be a code change in cmrel. + +Once this publishing step is confirmed to work for new charts, we'll write a small one-off script which pushes all older versions +of the chart to the new registry, or else find some off-the-shelf script to do the same thing. + +### Test Plan + + + +Once the changes to cmrel are made, we should be able to do an alpha release of cert-manager v1.16.0 and install +cert-manager locally in a kind cluster using the chart from the OCI registry. + +### Graduation Criteria + +Obviously no feature gates will apply to this change. + +Once all cert-manager charts are pushed to the new registry and all older cert-manager charts are mirrored, +there'll be future work to publish subproject charts and the related mirroring of those charts. This design +does not attempt to solve that problem, and focuses on cert-manager first. + +### Upgrade / Downgrade Strategy + + + +N/A + +### Supported Versions + + + +N/A + +## Production Readiness + + +N/A + +### Does this feature depend on any specific services running in the cluster? + + + +N/A + +### Will enabling / using this feature result in new API calls (i.e to Kubernetes apiserver or external services)? + + + +N/A + +### Will enabling / using this feature result in increasing size or count of the existing API objects? + + + +N/A + +### Will enabling / using this feature result in significant increase of resource usage? (CPU, RAM...) + + + +N/A + +## Drawbacks + + + +This proposal does not remove or break any functionality for users. For maintainers, pushing to multiple repositories would make gathering pull metrics more complex. + +## Alternatives + + + +A reasonable alternative to using OCI registries would be for the cert-manager project to host its own +Helm chart repository (e.g., `charts.cert-manager.io`). + +This would require running additional infrastructure (similar to what `charts.jetstack.io` does), and would +not be satisfactory for those users who've been asking for an OCI registry for compatibility reasons. + +In short, running a repo seems to be more work for less gain than pushing to an OCI registry. diff --git a/design/acme-orders-challenges-crd.md b/design/acme-orders-challenges-crd.md index e80a6eaef73..5928295e75b 100644 --- a/design/acme-orders-challenges-crd.md +++ b/design/acme-orders-challenges-crd.md @@ -86,7 +86,7 @@ type OrderSpec struct { // CommonName is the common name as specified on the DER encoded CSR. // If CommonName is not specified, the first DNSName specified will be used // as the CommonName. - // At least on of CommonName or a DNSName must be set. + // At least one of CommonName or a DNSName must be set. // This field must match the corresponding field on the DER encoded CSR. CommonName string `json:"commonName,omitempty"` @@ -94,7 +94,7 @@ type OrderSpec struct { // validation process. // If CommonName is not specified, the first DNSName specified will be used // as the CommonName. - // At least on of CommonName or a DNSName must be set. + // At least one of CommonName or a DNSName must be set. // This field must match the corresponding field on the DER encoded CSR. DNSNames []string `json:"dnsNames,omitempty"` @@ -174,7 +174,7 @@ const ( Processing State = "processing" // 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. + // If an Order is marked 'invalid', one of its validations must be invalid for some reason. // This is a final state. Invalid State = "invalid" @@ -224,7 +224,7 @@ type ChallengeSpec struct { // challenge is a part of. AuthzURL string `json:"authzURL"` - // Type is the type of ACME challenge this resource represents, e.g. "dns01" + // Type is the type of ACME challenge this resource represents, e.g., "dns01" // or "http01" Type string `json:"type"` @@ -232,7 +232,7 @@ type ChallengeSpec struct { // This can be used to lookup details about the status of this challenge. URL string `json:"url"` - // DNSName is the identifier that this challenge is for, e.g. example.com. + // DNSName is the identifier that this challenge is for, e.g., example.com. DNSName string `json:"dnsName"` // Token is the ACME challenge token for this challenge. @@ -409,7 +409,7 @@ creating resources in order to solve http01 challenges). * keeping the `status` field up to date with details of the challenge so that the Order controller can make decisions based on the state of challenges. -One area to highlight, is the behaviour of the Challenge controller wrt. challenges +One area to highlight, is the behaviour of the Challenge controller w.r.t. challenges vs authorizations. Whilst this controller works with a single ACME *Challenge* only, in order to @@ -438,7 +438,7 @@ to review. ## Risks & mitigations -#### Introducing new resource types creates more cognitive overhead for users, and a steeper 'on-boarding' curve wrt debugging issues. +#### Introducing new resource types creates more cognitive overhead for users, and a steeper 'on-boarding' curve w.r.t. debugging issues. This is mitigated by: @@ -451,7 +451,7 @@ Order & Challenge. * The 'order' controller can aggregate failure reasons from the 'challenge' resources it is managing in a similar way. * We can also include debugging information on the Certificate resource itself, -e.g. storing messages such as `You can get more information about why this order +e.g., storing messages such as `You can get more information about why this order failed by running 'kubectl describe order -n ' ## Alternatives considered diff --git a/design/images/20221205-memory-management/createsecrets.png b/design/images/20221205-memory-management/createsecrets.png new file mode 100644 index 00000000000..7dc2379cf29 Binary files /dev/null and b/design/images/20221205-memory-management/createsecrets.png differ diff --git a/design/images/20221205-memory-management/labelsecret.png b/design/images/20221205-memory-management/labelsecret.png new file mode 100644 index 00000000000..8f3632cf09e Binary files /dev/null and b/design/images/20221205-memory-management/labelsecret.png differ diff --git a/design/images/20221205-memory-management/latestmastersecrets.png b/design/images/20221205-memory-management/latestmastersecrets.png new file mode 100644 index 00000000000..ec602593b9c Binary files /dev/null and b/design/images/20221205-memory-management/latestmastersecrets.png differ diff --git a/design/images/20221205-memory-management/mastercertmanager.png b/design/images/20221205-memory-management/mastercertmanager.png new file mode 100644 index 00000000000..39058cdf3ca Binary files /dev/null and b/design/images/20221205-memory-management/mastercertmanager.png differ diff --git a/design/images/20221205-memory-management/masterissuanceterminal.png b/design/images/20221205-memory-management/masterissuanceterminal.png new file mode 100644 index 00000000000..17a31a9e247 Binary files /dev/null and b/design/images/20221205-memory-management/masterissuanceterminal.png differ diff --git a/design/images/20221205-memory-management/masterkubeapiserver.png b/design/images/20221205-memory-management/masterkubeapiserver.png new file mode 100644 index 00000000000..5e5e8e18935 Binary files /dev/null and b/design/images/20221205-memory-management/masterkubeapiserver.png differ diff --git a/design/images/20221205-memory-management/partiallabels.png b/design/images/20221205-memory-management/partiallabels.png new file mode 100644 index 00000000000..86225773f9e Binary files /dev/null and b/design/images/20221205-memory-management/partiallabels.png differ diff --git a/design/images/20221205-memory-management/partialmetadatagrafana.png b/design/images/20221205-memory-management/partialmetadatagrafana.png new file mode 100644 index 00000000000..8ae7f062183 Binary files /dev/null and b/design/images/20221205-memory-management/partialmetadatagrafana.png differ diff --git a/design/images/20221205-memory-management/partialmetadatasecrets.png b/design/images/20221205-memory-management/partialmetadatasecrets.png new file mode 100644 index 00000000000..c1a2d1b6f1b Binary files /dev/null and b/design/images/20221205-memory-management/partialmetadatasecrets.png differ diff --git a/design/images/20221205-memory-management/partialmetadataterminal.png b/design/images/20221205-memory-management/partialmetadataterminal.png new file mode 100644 index 00000000000..4b15b640902 Binary files /dev/null and b/design/images/20221205-memory-management/partialmetadataterminal.png differ diff --git a/design/images/20221205-memory-management/partialnolabels.png b/design/images/20221205-memory-management/partialnolabels.png new file mode 100644 index 00000000000..5e65ad48a39 Binary files /dev/null and b/design/images/20221205-memory-management/partialnolabels.png differ diff --git a/design/images/20221205-memory-management/partialnolabelscertmanager.png b/design/images/20221205-memory-management/partialnolabelscertmanager.png new file mode 100644 index 00000000000..f70308f4941 Binary files /dev/null and b/design/images/20221205-memory-management/partialnolabelscertmanager.png differ diff --git a/design/images/20221205-memory-management/partialnolabelskubeapiserver.png b/design/images/20221205-memory-management/partialnolabelskubeapiserver.png new file mode 100644 index 00000000000..3682f84f96a Binary files /dev/null and b/design/images/20221205-memory-management/partialnolabelskubeapiserver.png differ diff --git a/design/images/20221205-memory-management/partialonly.png b/design/images/20221205-memory-management/partialonly.png new file mode 100644 index 00000000000..3b1657a04aa Binary files /dev/null and b/design/images/20221205-memory-management/partialonly.png differ diff --git a/design/images/20221205-memory-management/partialonlycertmanager.png b/design/images/20221205-memory-management/partialonlycertmanager.png new file mode 100644 index 00000000000..fcdf7c5e2c7 Binary files /dev/null and b/design/images/20221205-memory-management/partialonlycertmanager.png differ diff --git a/design/images/20221205-memory-management/partialonlykubeapiserver.png b/design/images/20221205-memory-management/partialonlykubeapiserver.png new file mode 100644 index 00000000000..51f581912a7 Binary files /dev/null and b/design/images/20221205-memory-management/partialonlykubeapiserver.png differ diff --git a/design/images/20221205-memory-management/transformfunctionsgrafana.png b/design/images/20221205-memory-management/transformfunctionsgrafana.png new file mode 100644 index 00000000000..5d2ad9238a2 Binary files /dev/null and b/design/images/20221205-memory-management/transformfunctionsgrafana.png differ diff --git a/design/images/20221205-memory-management/transformwithlimit.png b/design/images/20221205-memory-management/transformwithlimit.png new file mode 100644 index 00000000000..5baa15cf03c Binary files /dev/null and b/design/images/20221205-memory-management/transformwithlimit.png differ diff --git a/design/template.md b/design/template.md index f9625ea2b28..de30de2eef0 100644 --- a/design/template.md +++ b/design/template.md @@ -1,5 +1,5 @@ # CHANGEME: Title @@ -80,7 +80,7 @@ This is where we get down to the specifics of what the proposal actually is. What is the desired outcome and how do we measure success? This should have enough detail that reviewers can understand exactly what you're proposing, but should not include things like API designs or -implementation- those should go into "Design Details" below. +implementation - those should go into "Design Details" below. --> ### User Stories (Optional) diff --git a/gcb/build_cert_manager.yaml b/gcb/build_cert_manager.yaml new file mode 100644 index 00000000000..afac9108c63 --- /dev/null +++ b/gcb/build_cert_manager.yaml @@ -0,0 +1,39 @@ +# This cloudbuild config file is intended to be triggered when a tag is pushed to the cert-manager repo +# and will build a cert-manager release and push to Google Cloud Storage (GCS). + +# The release won't be published automatically; this file just defines the build steps. + +# The full release and publish process is documented here: +# https://cert-manager.io/docs/contributing/release-process/ + +timeout: 2700s # 45m + +steps: +# cert-manager relies on the git checkout to determine release version, among other things +# By default, gcb only does a shallow clone, so we need to "unshallow" to get more details +- name: gcr.io/cloud-builders/git + args: ['fetch', '--unshallow'] + +## Build release artifacts and push to a bucket +- name: 'europe-west1-docker.pkg.dev/cert-manager-tests-trusted/cert-manager-infra-images/make-dind:20240422-6b43e85-bookworm' + entrypoint: bash + args: + - -c + - | + set -eu -o pipefail + make vendor-go + make CMREL_KEY="${_KMS_KEY}" RELEASE_TARGET_BUCKET="${_RELEASE_TARGET_BUCKET}" -j8 upload-release + echo "Wrote to ${_RELEASE_TARGET_BUCKET}" + +tags: +- "cert-manager-tag-push" +- "ref-${REF_NAME}-${COMMIT_SHA}" + +substitutions: + _KMS_KEY: "projects/cert-manager-release/locations/europe-west1/keyRings/cert-manager-release/cryptoKeys/cert-manager-release-signing-key/cryptoKeyVersions/1" + _RELEASE_TARGET_BUCKET: "cert-manager-release" + +options: + # https://cloud.google.com/build/docs/optimize-builds/increase-vcpu-for-builds + # https://cloud.google.com/build/pricing + machineType: E2_HIGHCPU_32 diff --git a/go.mod b/go.mod index 31aa64600c1..364a1aaaf58 100644 --- a/go.mod +++ b/go.mod @@ -1,270 +1,192 @@ module github.com/cert-manager/cert-manager -go 1.19 +go 1.25.0 + +// Do not remove this comment: +// please place any replace statements here at the top for visibility and add a +// comment to it as to when it can be removed require ( - github.com/Azure/azure-sdk-for-go v66.0.0+incompatible - github.com/Azure/go-autorest/autorest v0.11.28 - github.com/Azure/go-autorest/autorest/adal v0.9.21 - github.com/Azure/go-autorest/autorest/to v0.4.0 - github.com/Venafi/vcert/v4 v4.22.1 - github.com/akamai/AkamaiOPEN-edgegrid-golang v1.2.1 - github.com/aws/aws-sdk-go v1.44.105 - github.com/cloudflare/cloudflare-go v0.50.0 - github.com/cpu/goacmedns v0.1.1 - github.com/digitalocean/godo v1.86.0 - github.com/go-ldap/ldap/v3 v3.4.4 - github.com/go-logr/logr v1.2.3 - github.com/google/gnostic v0.6.9 - github.com/google/gofuzz v1.2.0 - github.com/hashicorp/vault/api v1.8.0 - github.com/hashicorp/vault/sdk v0.6.0 - github.com/kr/pretty v0.3.0 - github.com/miekg/dns v1.1.50 - github.com/mitchellh/go-homedir v1.1.0 - github.com/munnerz/crd-schema-fuzz v1.0.0 - github.com/onsi/ginkgo/v2 v2.2.0 - github.com/onsi/gomega v1.20.2 - github.com/pavlo-v-chernykh/keystore-go/v4 v4.4.0 - github.com/pkg/errors v0.9.1 - github.com/prometheus/client_golang v1.13.0 - github.com/segmentio/encoding v0.3.5 - github.com/sergi/go-diff v1.2.0 - github.com/spf13/cobra v1.5.0 - github.com/spf13/pflag v1.0.5 - github.com/stretchr/testify v1.8.0 - golang.org/x/crypto v0.0.0-20220924013350-4ba4fb4dd9e7 - golang.org/x/oauth2 v0.0.0-20220909003341-f21342109be1 - golang.org/x/sync v0.0.0-20220923202941-7f9b1623fab7 - gomodules.xyz/jsonpatch/v2 v2.2.0 - google.golang.org/api v0.97.0 - helm.sh/helm/v3 v3.10.0 - k8s.io/api v0.25.2 - k8s.io/apiextensions-apiserver v0.25.2 - k8s.io/apimachinery v0.25.2 - k8s.io/apiserver v0.25.2 - k8s.io/cli-runtime v0.25.2 - k8s.io/client-go v0.25.2 - k8s.io/code-generator v0.25.2 - k8s.io/component-base v0.25.2 - k8s.io/klog/v2 v2.80.1 - k8s.io/kube-aggregator v0.25.2 - k8s.io/kube-openapi v0.0.0-20220803164354-a70c9af30aea - k8s.io/kubectl v0.25.2 - k8s.io/utils v0.0.0-20220922133306-665eaaec4324 - sigs.k8s.io/controller-runtime v0.13.0 - sigs.k8s.io/controller-tools v0.10.0 - sigs.k8s.io/gateway-api v0.5.0 - sigs.k8s.io/structured-merge-diff/v4 v4.2.3 - sigs.k8s.io/yaml v1.3.0 - software.sslmate.com/src/go-pkcs12 v0.2.0 + github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 + github.com/Azure/azure-sdk-for-go/sdk/azcore v1.19.1 + github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.0 + github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/dns/armdns v1.2.0 + github.com/Venafi/vcert/v5 v5.12.2 + github.com/akamai/AkamaiOPEN-edgegrid-golang/v12 v12.1.0 + github.com/aws/aws-sdk-go-v2 v1.39.4 + github.com/aws/aws-sdk-go-v2/config v1.31.15 + github.com/aws/aws-sdk-go-v2/credentials v1.18.19 + github.com/aws/aws-sdk-go-v2/service/route53 v1.59.1 + github.com/aws/aws-sdk-go-v2/service/sts v1.38.9 + github.com/aws/smithy-go v1.23.1 + github.com/digitalocean/godo v1.167.0 + github.com/go-ldap/ldap/v3 v3.4.12 + github.com/go-logr/logr v1.4.3 + github.com/go-openapi/jsonreference v0.21.2 + github.com/google/gnostic-models v0.7.0 + github.com/google/go-cmp v0.7.0 + github.com/hashicorp/vault/api v1.22.0 + github.com/hashicorp/vault/sdk v0.20.0 + github.com/miekg/dns v1.1.68 + github.com/nrdcg/goacmedns v0.2.0 + github.com/pavlo-v-chernykh/keystore-go/v4 v4.5.0 + github.com/prometheus/client_golang v1.23.2 + github.com/spf13/cobra v1.10.1 + github.com/spf13/pflag v1.0.10 + github.com/stretchr/testify v1.11.1 + golang.org/x/crypto v0.43.0 + golang.org/x/net v0.46.0 + golang.org/x/oauth2 v0.32.0 + golang.org/x/sync v0.17.0 + google.golang.org/api v0.253.0 + k8s.io/api v0.34.1 + k8s.io/apiextensions-apiserver v0.34.1 + k8s.io/apimachinery v0.34.1 + k8s.io/apiserver v0.34.1 + k8s.io/client-go v0.34.1 + k8s.io/component-base v0.34.1 + k8s.io/klog/v2 v2.130.1 + k8s.io/kube-aggregator v0.34.1 + k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 + k8s.io/utils v0.0.0-20250820121507-0af2bda4dd1d + sigs.k8s.io/controller-runtime v0.22.3 + sigs.k8s.io/gateway-api v1.4.0 + sigs.k8s.io/randfill v1.0.0 + sigs.k8s.io/structured-merge-diff/v6 v6.3.0 + software.sslmate.com/src/go-pkcs12 v0.6.0 ) require ( - cloud.google.com/go/compute v1.7.0 // indirect - github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect - github.com/Azure/go-autorest v14.2.0+incompatible // indirect - github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect - github.com/Azure/go-autorest/autorest/validation v0.3.1 // indirect - github.com/Azure/go-autorest/logger v0.2.1 // indirect - github.com/Azure/go-autorest/tracing v0.6.0 // indirect - github.com/Azure/go-ntlmssp v0.0.0-20220621081337-cb9428e4ac1e // indirect - github.com/BurntSushi/toml v1.1.0 // indirect - github.com/MakeNowJust/heredoc v1.0.0 // indirect - github.com/Masterminds/goutils v1.1.1 // indirect - github.com/Masterminds/semver/v3 v3.1.1 // indirect - github.com/Masterminds/sprig/v3 v3.2.2 // indirect - github.com/Masterminds/squirrel v1.5.3 // indirect + cel.dev/expr v0.24.0 // indirect + cloud.google.com/go/auth v0.17.0 // indirect + cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect + cloud.google.com/go/compute/metadata v0.9.0 // indirect + github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2 // indirect + github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 // indirect + github.com/AzureAD/microsoft-authentication-library-for-go v1.5.0 // indirect + github.com/Khan/genqlient v0.8.1 // indirect github.com/NYTimes/gziphandler v1.1.1 // indirect - github.com/PuerkitoBio/purell v1.1.1 // indirect - github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect - github.com/armon/go-metrics v0.3.9 // indirect - github.com/armon/go-radix v1.0.0 // indirect - github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535 // indirect + github.com/antlr4-go/antlr/v4 v4.13.1 // indirect + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.11 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.11 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.11 // indirect + github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.2 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.11 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.29.8 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.3 // indirect + github.com/benbjohnson/clock v1.3.5 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/blang/semver/v4 v4.0.0 // indirect - github.com/cenkalti/backoff/v3 v3.0.0 // indirect - github.com/cespare/xxhash/v2 v2.1.2 // indirect - github.com/chai2010/gettext-go v1.0.2 // indirect - github.com/containerd/containerd v1.6.6 // indirect - github.com/coreos/go-semver v0.3.0 // indirect - github.com/coreos/go-systemd/v22 v22.3.2 // indirect - github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect - github.com/cyphar/filepath-securejoin v0.2.3 // indirect - github.com/davecgh/go-spew v1.1.1 // indirect - github.com/docker/cli v20.10.17+incompatible // indirect - github.com/docker/distribution v2.8.1+incompatible // indirect - github.com/docker/docker v20.10.17+incompatible // indirect - github.com/docker/docker-credential-helpers v0.6.4 // indirect - github.com/docker/go-connections v0.4.0 // indirect - github.com/docker/go-metrics v0.0.1 // indirect - github.com/docker/go-units v0.4.0 // indirect - github.com/emicklei/go-restful/v3 v3.8.0 // indirect - github.com/evanphx/json-patch v5.6.0+incompatible // indirect - github.com/evanphx/json-patch/v5 v5.6.0 // indirect - github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d // indirect - github.com/fatih/camelcase v1.0.0 // indirect - github.com/fatih/color v1.13.0 // indirect - github.com/felixge/httpsnoop v1.0.1 // indirect - github.com/fsnotify/fsnotify v1.5.4 // indirect - github.com/go-asn1-ber/asn1-ber v1.5.4 // indirect - github.com/go-errors/errors v1.0.1 // indirect - github.com/go-gorp/gorp/v3 v3.0.2 // indirect - github.com/go-openapi/jsonpointer v0.19.5 // indirect - github.com/go-openapi/jsonreference v0.19.5 // indirect - github.com/go-openapi/swag v0.19.14 // indirect - github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 // indirect - github.com/gobuffalo/flect v0.2.5 // indirect - github.com/gobwas/glob v0.2.3 // indirect + github.com/cenkalti/backoff/v4 v4.3.0 // indirect + github.com/cenkalti/backoff/v5 v5.0.3 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/coreos/go-semver v0.3.1 // indirect + github.com/coreos/go-systemd/v22 v22.5.0 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/emicklei/go-restful/v3 v3.13.0 // indirect + github.com/evanphx/json-patch v5.9.0+incompatible // indirect + github.com/evanphx/json-patch/v5 v5.9.11 // indirect + github.com/felixge/httpsnoop v1.0.4 // indirect + github.com/frankban/quicktest v1.14.6 // indirect + github.com/fsnotify/fsnotify v1.9.0 // indirect + github.com/fxamacker/cbor/v2 v2.9.0 // indirect + github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667 // indirect + github.com/go-http-utils/headers v0.0.0-20181008091004-fed159eddc2a // indirect + github.com/go-jose/go-jose/v4 v4.1.2 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + github.com/go-logr/zapr v1.3.0 // indirect + github.com/go-openapi/jsonpointer v0.22.1 // indirect + github.com/go-openapi/swag v0.23.1 // indirect + github.com/go-openapi/swag/jsonname v0.25.1 // indirect + github.com/go-ozzo/ozzo-validation/v4 v4.3.0 // indirect github.com/gogo/protobuf v1.3.2 // indirect - github.com/golang-jwt/jwt/v4 v4.2.0 // indirect - github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect - github.com/golang/protobuf v1.5.2 // indirect + github.com/golang-jwt/jwt/v5 v5.3.0 // indirect + github.com/golang/protobuf v1.5.4 // indirect github.com/golang/snappy v0.0.4 // indirect - github.com/google/btree v1.0.1 // indirect - github.com/google/go-cmp v0.5.8 // indirect + github.com/google/btree v1.1.3 // indirect + github.com/google/cel-go v0.26.0 // indirect + github.com/google/certificate-transparency-go v1.3.1 // indirect github.com/google/go-querystring v1.1.0 // indirect - github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 // indirect - github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect - github.com/google/uuid v1.3.0 // indirect - github.com/googleapis/enterprise-certificate-proxy v0.1.0 // indirect - github.com/googleapis/gax-go/v2 v2.4.0 // indirect - github.com/gorilla/mux v1.8.0 // indirect - github.com/gosuri/uitable v0.0.4 // indirect - github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 // indirect + github.com/google/s2a-go v0.1.9 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/googleapis/enterprise-certificate-proxy v0.3.6 // indirect + github.com/googleapis/gax-go/v2 v2.15.0 // indirect + github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 // indirect github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 // indirect - github.com/grpc-ecosystem/grpc-gateway v1.16.0 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect - github.com/hashicorp/go-hclog v1.2.0 // indirect - github.com/hashicorp/go-immutable-radix v1.3.1 // indirect + github.com/hashicorp/go-hmac-drbg v0.0.0-20210916214228-a6e5a68489f6 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect - github.com/hashicorp/go-plugin v1.4.3 // indirect - github.com/hashicorp/go-retryablehttp v0.7.1 // indirect + github.com/hashicorp/go-retryablehttp v0.7.8 // indirect github.com/hashicorp/go-rootcerts v1.0.2 // indirect - github.com/hashicorp/go-secure-stdlib/mlock v0.1.1 // indirect - github.com/hashicorp/go-secure-stdlib/parseutil v0.1.6 // indirect + github.com/hashicorp/go-secure-stdlib/cryptoutil v0.1.1 // indirect + github.com/hashicorp/go-secure-stdlib/parseutil v0.2.0 // indirect github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 // indirect - github.com/hashicorp/go-sockaddr v1.0.2 // indirect - github.com/hashicorp/go-uuid v1.0.2 // indirect - github.com/hashicorp/go-version v1.2.0 // indirect - github.com/hashicorp/golang-lru v0.5.4 // indirect - github.com/hashicorp/hcl v1.0.0 // indirect - github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb // indirect - github.com/huandu/xstrings v1.3.2 // indirect - github.com/imdario/mergo v0.3.12 // indirect - github.com/inconshreveable/mousetrap v1.0.0 // indirect - github.com/jmespath/go-jmespath v0.4.0 // indirect - github.com/jmoiron/sqlx v1.3.5 // indirect + github.com/hashicorp/go-sockaddr v1.0.7 // indirect + github.com/hashicorp/hcl v1.0.1-vault-7 // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect - github.com/klauspost/compress v1.13.6 // indirect - github.com/kr/text v0.2.0 // indirect - github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect - github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect - github.com/lib/pq v1.10.6 // indirect - github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect - github.com/mailru/easyjson v0.7.6 // indirect - github.com/mattn/go-colorable v0.1.12 // indirect - github.com/mattn/go-isatty v0.0.14 // indirect - github.com/mattn/go-runewidth v0.0.13 // indirect - github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect - github.com/mitchellh/copystructure v1.2.0 // indirect - github.com/mitchellh/go-testing-interface v1.0.0 // indirect - github.com/mitchellh/go-wordwrap v1.0.0 // indirect + github.com/kylelemons/godebug v1.1.0 // indirect + github.com/mailru/easyjson v0.9.0 // indirect + github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect - github.com/mitchellh/reflectwalk v1.0.2 // indirect - github.com/moby/locker v1.0.1 // indirect - github.com/moby/spdystream v0.2.0 // indirect - github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect - github.com/modern-go/reflect2 v1.0.2 // indirect - github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect - github.com/morikuni/aec v1.0.0 // indirect + github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect - github.com/oklog/run v1.0.0 // indirect - github.com/opencontainers/go-digest v1.0.0 // indirect - github.com/opencontainers/image-spec v1.0.3-0.20211202183452-c5a74bcca799 // indirect - github.com/patrickmn/go-cache v2.1.0+incompatible // indirect - github.com/peterbourgon/diskv v2.0.1+incompatible // indirect - github.com/pierrec/lz4 v2.5.2+incompatible // indirect - github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/prometheus/client_model v0.2.0 // indirect - github.com/prometheus/common v0.37.0 // indirect - github.com/prometheus/procfs v0.8.0 // indirect - github.com/rivo/uniseg v0.2.0 // indirect - github.com/rogpeppe/go-internal v1.8.1 // indirect - github.com/rubenv/sql-migrate v1.1.2 // indirect - github.com/russross/blackfriday v1.5.2 // indirect - github.com/russross/blackfriday/v2 v2.1.0 // indirect + github.com/pierrec/lz4 v2.6.1+incompatible // indirect + github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + github.com/prometheus/client_model v0.6.2 // indirect + github.com/prometheus/common v0.66.1 // indirect + github.com/prometheus/procfs v0.17.0 // indirect github.com/ryanuber/go-glob v1.0.0 // indirect - github.com/segmentio/asm v1.1.3 // indirect - github.com/shopspring/decimal v1.2.0 // indirect - github.com/sirupsen/logrus v1.8.1 // indirect - github.com/spf13/cast v1.4.1 // indirect - github.com/stretchr/objx v0.4.0 // indirect - github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // indirect - github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect - github.com/xeipuuv/gojsonschema v1.2.0 // indirect - github.com/xlab/treeprint v1.1.0 // indirect - github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a // indirect - go.etcd.io/etcd/api/v3 v3.5.4 // indirect - go.etcd.io/etcd/client/pkg/v3 v3.5.4 // indirect - go.etcd.io/etcd/client/v3 v3.5.4 // indirect - go.opencensus.io v0.23.0 // indirect - go.opentelemetry.io/contrib v0.20.0 // indirect - go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.28.0 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.20.0 // indirect - go.opentelemetry.io/otel v1.3.0 // indirect - go.opentelemetry.io/otel/exporters/otlp v0.20.0 // indirect - go.opentelemetry.io/otel/metric v0.20.0 // indirect - go.opentelemetry.io/otel/sdk v1.3.0 // indirect - go.opentelemetry.io/otel/sdk/export/metric v0.20.0 // indirect - go.opentelemetry.io/otel/sdk/metric v0.20.0 // indirect - go.opentelemetry.io/otel/trace v1.3.0 // indirect - go.opentelemetry.io/proto/otlp v0.11.0 // indirect - go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5 // indirect - go.uber.org/atomic v1.9.0 // indirect - go.uber.org/multierr v1.6.0 // indirect - go.uber.org/zap v1.21.0 // indirect - golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect - golang.org/x/net v0.0.0-20220921155015-db77216a4ee9 // indirect - golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10 // indirect - golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect - golang.org/x/text v0.3.7 // indirect - golang.org/x/time v0.0.0-20220609170525-579cf78fd858 // indirect - golang.org/x/tools v0.1.12 // indirect - google.golang.org/appengine v1.6.7 // indirect - google.golang.org/genproto v0.0.0-20220624142145-8cd45d7dbd1f // indirect - google.golang.org/grpc v1.47.0 // indirect - google.golang.org/protobuf v1.28.1 // indirect + github.com/sosodev/duration v1.3.1 // indirect + github.com/stoewer/go-strcase v1.3.1 // indirect + github.com/stretchr/objx v0.5.2 // indirect + github.com/vektah/gqlparser/v2 v2.5.30 // indirect + github.com/x448/float16 v0.8.4 // indirect + github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect + go.etcd.io/etcd/api/v3 v3.6.4 // indirect + go.etcd.io/etcd/client/pkg/v3 v3.6.4 // indirect + go.etcd.io/etcd/client/v3 v3.6.4 // indirect + go.opentelemetry.io/auto/sdk v1.1.0 // indirect + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 // indirect + go.opentelemetry.io/otel v1.37.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.37.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.37.0 // indirect + go.opentelemetry.io/otel/metric v1.37.0 // indirect + go.opentelemetry.io/otel/sdk v1.37.0 // indirect + go.opentelemetry.io/otel/trace v1.37.0 // indirect + go.opentelemetry.io/proto/otlp v1.7.0 // indirect + go.uber.org/multierr v1.11.0 // indirect + go.uber.org/ratelimit v0.3.1 // indirect + go.uber.org/zap v1.27.0 // indirect + go.yaml.in/yaml/v2 v2.4.2 // indirect + go.yaml.in/yaml/v3 v3.0.4 // indirect + golang.org/x/exp v0.0.0-20250718183923-645b1fa84792 // indirect + golang.org/x/mod v0.28.0 // indirect + golang.org/x/sys v0.37.0 // indirect + golang.org/x/term v0.36.0 // indirect + golang.org/x/text v0.30.0 // indirect + golang.org/x/time v0.14.0 // indirect + golang.org/x/tools v0.37.0 // indirect + gomodules.xyz/jsonpatch/v2 v2.5.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20250804133106-a7a43d27e69b // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20251014184007-4626949a642f // indirect + google.golang.org/grpc v1.76.0 // indirect + google.golang.org/protobuf v1.36.10 // indirect + gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect - gopkg.in/ini.v1 v1.62.0 // indirect - gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect - gopkg.in/square/go-jose.v2 v2.5.1 // indirect + gopkg.in/ini.v1 v1.67.0 // indirect + gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/gengo v0.0.0-20211129171323-c02415ce4185 // indirect - oras.land/oras-go v1.2.0 // indirect - sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.32 // indirect - sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 // indirect - sigs.k8s.io/kustomize/api v0.12.1 // indirect - sigs.k8s.io/kustomize/kyaml v0.13.9 // indirect -) - -replace ( - github.com/miekg/dns v1.1.41 => github.com/miekg/dns v1.1.34 - - go.opentelemetry.io/contrib => go.opentelemetry.io/contrib v0.20.0 - go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc => go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.20.0 - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp => go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.20.0 - go.opentelemetry.io/otel => go.opentelemetry.io/otel v0.20.0 - go.opentelemetry.io/otel/exporters/otlp => go.opentelemetry.io/otel/exporters/otlp v0.20.0 - go.opentelemetry.io/otel/metric => go.opentelemetry.io/otel/metric v0.20.0 - go.opentelemetry.io/otel/oteltest => go.opentelemetry.io/otel/oteltest v0.20.0 - go.opentelemetry.io/otel/sdk => go.opentelemetry.io/otel/sdk v0.20.0 - go.opentelemetry.io/otel/sdk/export/metric => go.opentelemetry.io/otel/sdk/export/metric v0.20.0 - go.opentelemetry.io/otel/sdk/metric => go.opentelemetry.io/otel/sdk/metric v0.20.0 - go.opentelemetry.io/otel/trace => go.opentelemetry.io/otel/trace v0.20.0 - go.opentelemetry.io/proto/otlp => go.opentelemetry.io/proto/otlp v0.7.0 + k8s.io/kms v0.34.1 // indirect + sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.33.0 // indirect + sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect + sigs.k8s.io/yaml v1.6.0 // indirect ) diff --git a/go.sum b/go.sum index c5549acfc8a..eae0f89c46a 100644 --- a/go.sum +++ b/go.sum @@ -1,1703 +1,517 @@ -cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= -cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= -cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= -cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= -cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= -cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= -cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= -cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= -cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= -cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= -cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= -cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= -cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= -cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= -cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= -cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg= -cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8= -cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0= -cloud.google.com/go v0.83.0/go.mod h1:Z7MJUsANfY0pYPdw0lbnivPx4/vhy/e2FEkSkF7vAVY= -cloud.google.com/go v0.84.0/go.mod h1:RazrYuxIK6Kb7YrzzhPoLmCVzl7Sup4NrbKPg8KHSUM= -cloud.google.com/go v0.87.0/go.mod h1:TpDYlFy7vuLzZMMZ+B6iRiELaY7z/gJPaqbMx6mlWcY= -cloud.google.com/go v0.90.0/go.mod h1:kRX0mNRHe0e2rC6oNakvwQqzyDmg57xJ+SZU1eT2aDQ= -cloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI= -cloud.google.com/go v0.94.1/go.mod h1:qAlAugsXlC+JWO+Bke5vCtc9ONxjQT3drlTTnAplMW4= -cloud.google.com/go v0.97.0/go.mod h1:GF7l59pYBVlXQIBLx3a761cZ41F9bBH3JUlihCt2Udc= -cloud.google.com/go v0.99.0/go.mod h1:w0Xx2nLzqWJPuozYQX+hFfCSI8WioryfRDzkoI/Y2ZA= -cloud.google.com/go v0.100.2/go.mod h1:4Xra9TjzAeYHrl5+oeLlzbM2k3mjVhZh4UqTZ//w99A= -cloud.google.com/go v0.102.0/go.mod h1:oWcCzKlqJ5zgHQt9YsaeTY9KzIvjyy0ArmiBUgpQ+nc= -cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= -cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= -cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= -cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= -cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= -cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= -cloud.google.com/go/compute v0.1.0/go.mod h1:GAesmwr110a34z04OlxYkATPBEfVhkymfTBXtfbBFow= -cloud.google.com/go/compute v1.3.0/go.mod h1:cCZiE1NHEtai4wiufUhW8I8S1JKkAnhnQJWM7YD99wM= -cloud.google.com/go/compute v1.5.0/go.mod h1:9SMHyhJlzhlkJqrPAc839t2BZFTSk6Jdj6mkzQJeu0M= -cloud.google.com/go/compute v1.6.0/go.mod h1:T29tfhtVbq1wvAPo0E3+7vhgmkOYeXjhFvz/FMzPu0s= -cloud.google.com/go/compute v1.6.1/go.mod h1:g85FgpzFvNULZ+S8AYq87axRKuf2Kh7deLqV/jJ3thU= -cloud.google.com/go/compute v1.7.0 h1:v/k9Eueb8aAJ0vZuxKMrgm6kPhCLZU9HxFU+AFDs9Uk= -cloud.google.com/go/compute v1.7.0/go.mod h1:435lt8av5oL9P3fv1OEzSbSUe+ybHXGMPQHHZWZxy9U= -cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= -cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= -cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= -cloud.google.com/go/iam v0.3.0/go.mod h1:XzJPvDayI+9zsASAFO68Hk07u3z+f+JrT2xXNdp4bnY= -cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= -cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= -cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= -cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= -cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= -cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= -cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= -cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= -cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= -cloud.google.com/go/storage v1.22.1/go.mod h1:S8N1cAStu7BOeFfE8KAQzmyyLkK8p/vmRq6kuBTW58Y= -dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= -github.com/Azure/azure-sdk-for-go v66.0.0+incompatible h1:bmmC38SlE8/E81nNADlgmVGurPWMHDX2YNXVQMrBpEE= -github.com/Azure/azure-sdk-for-go v66.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= -github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= -github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= -github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= -github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs= -github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= -github.com/Azure/go-autorest/autorest v0.9.0/go.mod h1:xyHB1BMZT0cuDHU7I0+g046+BFDTQ8rEZB0s4Yfa6bI= -github.com/Azure/go-autorest/autorest v0.11.28 h1:ndAExarwr5Y+GaHE6VCaY1kyS/HwwGGyuimVhWsHOEM= -github.com/Azure/go-autorest/autorest v0.11.28/go.mod h1:MrkzG3Y3AH668QyF9KRk5neJnGgmhQ6krbhR8Q5eMvA= -github.com/Azure/go-autorest/autorest/adal v0.5.0/go.mod h1:8Z9fGy2MpX0PvDjB1pEgQTmVqjGhiHBW7RJJEciWzS0= -github.com/Azure/go-autorest/autorest/adal v0.9.18/go.mod h1:XVVeme+LZwABT8K5Lc3hA4nAe8LDBVle26gTrguhhPQ= -github.com/Azure/go-autorest/autorest/adal v0.9.21 h1:jjQnVFXPfekaqb8vIsv2G1lxshoW+oGv4MDlhRtnYZk= -github.com/Azure/go-autorest/autorest/adal v0.9.21/go.mod h1:zua7mBUaCc5YnSLKYgGJR/w5ePdMDA6H56upLsHzA9U= -github.com/Azure/go-autorest/autorest/date v0.1.0/go.mod h1:plvfp3oPSKwf2DNjlBjWF/7vwR+cUD/ELuzDCXwHUVA= -github.com/Azure/go-autorest/autorest/date v0.3.0 h1:7gUk1U5M/CQbp9WoqinNzJar+8KY+LPI6wiWrP/myHw= -github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74= -github.com/Azure/go-autorest/autorest/mocks v0.1.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= -github.com/Azure/go-autorest/autorest/mocks v0.2.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= -github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= -github.com/Azure/go-autorest/autorest/mocks v0.4.2 h1:PGN4EDXnuQbojHbU0UWoNvmu9AGVwYHG9/fkDYhtAfw= -github.com/Azure/go-autorest/autorest/mocks v0.4.2/go.mod h1:Vy7OitM9Kei0i1Oj+LvyAWMXJHeKH1MVlzFugfVrmyU= -github.com/Azure/go-autorest/autorest/to v0.4.0 h1:oXVqrxakqqV1UZdSazDOPOLvOIz+XA683u8EctwboHk= -github.com/Azure/go-autorest/autorest/to v0.4.0/go.mod h1:fE8iZBn7LQR7zH/9XU2NcPR4o9jEImooCeWJcYV/zLE= -github.com/Azure/go-autorest/autorest/validation v0.3.1 h1:AgyqjAd94fwNAoTjl/WQXg4VvFeRFpO+UhNyRXqF1ac= -github.com/Azure/go-autorest/autorest/validation v0.3.1/go.mod h1:yhLgjC0Wda5DYXl6JAsWyUe4KVNffhoDhG0zVzUMo3E= -github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6LSNgds39diKLz7Vrc= -github.com/Azure/go-autorest/logger v0.2.1 h1:IG7i4p/mDa2Ce4TRyAO8IHnVhAVF3RFU+ZtXWSmf4Tg= -github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= -github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk= -github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUMfuitfgcfuo= -github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= -github.com/Azure/go-ntlmssp v0.0.0-20220621081337-cb9428e4ac1e h1:NeAW1fUYUEWhft7pkxDf6WoUvEZJ/uOKsvtpjLnn8MU= -github.com/Azure/go-ntlmssp v0.0.0-20220621081337-cb9428e4ac1e/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU= -github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/toml v1.1.0 h1:ksErzDEI1khOiGPgpwuI7x2ebx/uXQNw7xJpn9Eq1+I= -github.com/BurntSushi/toml v1.1.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= -github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= -github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60= -github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= -github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ= -github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE= -github.com/Masterminds/goutils v1.1.0/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= -github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= -github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= -github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= -github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc= -github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= -github.com/Masterminds/sprig v2.22.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o= -github.com/Masterminds/sprig/v3 v3.2.2 h1:17jRggJu518dr3QaafizSXOjKYp94wKfABxUmyxvxX8= -github.com/Masterminds/sprig/v3 v3.2.2/go.mod h1:UoaO7Yp8KlPnJIYWTFkMaqPUYKTfGFPhxNuwnnxkKlk= -github.com/Masterminds/squirrel v1.5.3 h1:YPpoceAcxuzIljlr5iWpNKaql7hLeG1KLSrhvdHpkZc= -github.com/Masterminds/squirrel v1.5.3/go.mod h1:NNaOrjSoIDfDA40n7sr2tPNZRfjzjA400rg+riTZj10= -github.com/Microsoft/go-winio v0.5.1 h1:aPJp2QD7OOrhO5tQXqQoGSJc+DjDtWTGLOmNyAm6FgY= -github.com/Microsoft/hcsshim v0.9.3 h1:k371PzBuRrz2b+ebGuI2nVgVhgsVX60jMfSw80NECxo= -github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= +cel.dev/expr v0.24.0 h1:56OvJKSH3hDGL0ml5uSxZmz3/3Pq4tJ+fb1unVLAFcY= +cel.dev/expr v0.24.0/go.mod h1:hLPLo1W4QUmuYdA72RBX06QTs6MXw941piREPl3Yfiw= +cloud.google.com/go/auth v0.17.0 h1:74yCm7hCj2rUyyAocqnFzsAYXgJhrG26XCFimrc/Kz4= +cloud.google.com/go/auth v0.17.0/go.mod h1:6wv/t5/6rOPAX4fJiRjKkJCvswLwdet7G8+UGXt7nCQ= +cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc= +cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c= +cloud.google.com/go/compute/metadata v0.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdBtwLoEkH9Zs= +cloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10= +github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 h1:He8afgbRMd7mFxO99hRNu+6tazq8nFF9lIwo9JFroBk= +github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.19.1 h1:5YTBM8QDVIBN3sxBil89WfdAAqDZbyJTgh688DSxX5w= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.19.1/go.mod h1:YD5h/ldMsG0XiIw7PdyNhLxaM317eFh5yNLccNfGdyw= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.0 h1:KpMC6LFL7mqpExyMC9jVOYRiVhLmamjeZfRsUpB7l4s= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.0/go.mod h1:J7MUC/wtRpfGVbQ5sIItY5/FuVWmvzlY21WAOfQnq/I= +github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.2 h1:yz1bePFlP5Vws5+8ez6T3HWXPmwOK7Yvq8QxDBD3SKY= +github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.2/go.mod h1:Pa9ZNPuoNu/GztvBSKk9J1cDJW6vk/n0zLtV4mgd8N8= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2 h1:9iefClla7iYpfYWdzPCRDozdmndjTm8DXdpCzPajMgA= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2/go.mod h1:XtLgD3ZD34DAaVIIAyG3objl5DynM3CQ/vMcbBNJZGI= +github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/dns/armdns v1.2.0 h1:lpOxwrQ919lCZoNCd69rVt8u1eLZuMORrGXqy8sNf3c= +github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/dns/armdns v1.2.0/go.mod h1:fSvRkb8d26z9dbL40Uf/OO6Vo9iExtZK3D0ulRV+8M0= +github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 h1:mFRzDkZVAjdal+s7s0MwaRv9igoPqLRdzOLzw/8Xvq8= +github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU= +github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1 h1:WJTmL004Abzc5wDB5VtZG2PJk5ndYDgVacGqfirKxjM= +github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1/go.mod h1:tCcJZ0uHAmvjsVYzEFivsRTN00oz5BEsRgQHu5JZ9WE= +github.com/AzureAD/microsoft-authentication-library-for-go v1.5.0 h1:XkkQbfMyuH2jTSjQjSoihryI8GINRcs4xp8lNawg0FI= +github.com/AzureAD/microsoft-authentication-library-for-go v1.5.0/go.mod h1:HKpQxkWaGLJ+D/5H8QRpyQXA1eKjxkFlOMwck5+33Jk= +github.com/Khan/genqlient v0.8.1 h1:wtOCc8N9rNynRLXN3k3CnfzheCUNKBcvXmVv5zt6WCs= +github.com/Khan/genqlient v0.8.1/go.mod h1:R2G6DzjBvCbhjsEajfRjbWdVglSH/73kSivC9TLWVjU= github.com/NYTimes/gziphandler v1.1.1 h1:ZUDjpQae29j0ryrS0u/B8HZfJBtBQHjqw2rQ2cqUQ3I= github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c= -github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= -github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= -github.com/PuerkitoBio/purell v1.1.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= -github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI= -github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= -github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= -github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M= -github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= -github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d h1:UrqY+r/OJnIp5u0s1SbQ8dVfLCZJsnvazdBP5hS4iRs= -github.com/Venafi/vcert/v4 v4.22.1 h1:31A8mV0DAis5qn1cfUCU9eODjALNmZKKx9I9wDOIXZM= -github.com/Venafi/vcert/v4 v4.22.1/go.mod h1:4Nec3twWisOdS1unpDZ93sfau9eVSDS8Ot+Ry/gg0es= -github.com/agnivade/levenshtein v1.0.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4RqaHDIsdSBg7lM= -github.com/akamai/AkamaiOPEN-edgegrid-golang v1.2.1 h1:5BIsppVPdWJA29Yb5cYawQYeh5geN413WxAgBZvEtdA= -github.com/akamai/AkamaiOPEN-edgegrid-golang v1.2.1/go.mod h1:kX6YddBkXqqywAe8c9LyvgTCyFuZCTMF4cRPQhc3Fy8= -github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= -github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= -github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= +github.com/Venafi/vcert/v5 v5.12.2 h1:Ee3/A9fZRiisuwuz22/Nqgl19H0ztQjWv35AC63qPcA= +github.com/Venafi/vcert/v5 v5.12.2/go.mod h1:x3l0pB0q0E6wuhPe7nzfkUEwwraK7amnBWQ4LtT1bbw= +github.com/akamai/AkamaiOPEN-edgegrid-golang/v12 v12.1.0 h1:feVgyeLunm1eepTK9urvVpyhXCgEuSnfUxyYfMCtD0g= +github.com/akamai/AkamaiOPEN-edgegrid-golang/v12 v12.1.0/go.mod h1:Bf6hnZkloZnfL4I/gFGnMMMdMHiu/ERnSOWtFgnodDk= +github.com/alexbrainman/sspi v0.0.0-20250919150558-7d374ff0d59e h1:4dAU9FXIyQktpoUAgOJK3OTFc/xug0PCXYCqU0FgDKI= +github.com/alexbrainman/sspi v0.0.0-20250919150558-7d374ff0d59e/go.mod h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4= +github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883 h1:bvNMNQO63//z+xNgfBlViaCIJKLlCJ6/fmUseuG0wVQ= github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= -github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= -github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= -github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= -github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= -github.com/armon/go-metrics v0.3.9 h1:O2sNqxBdvq8Eq5xmzljcYzAORli6RWCvEym4cJf9m18= -github.com/armon/go-metrics v0.3.9/go.mod h1:4O98XIr/9W0sxpJ8UaYkvjk10Iff7SnFrb4QAOwNTFc= -github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= -github.com/armon/go-radix v1.0.0 h1:F4z6KzEeeQIMeLFa97iZU6vupzoecKdU5TX24SNppXI= -github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= -github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= -github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= -github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= -github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535 h1:4daAzAu0S6Vi7/lbWECcX0j45yZReDZ56BQsrVBOEEY= -github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535/go.mod h1:oGkLhpf+kjZl6xBf758TQhh5XrAeiJv/7FRz/2spLIg= -github.com/aws/aws-sdk-go v1.44.105 h1:UUwoD1PRKIj3ltrDUYTDQj5fOTK3XsnqolLpRTMmSEM= -github.com/aws/aws-sdk-go v1.44.105/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= -github.com/benbjohnson/clock v1.0.3/go.mod h1:bGMdMPoPVvcYyt1gHDf4J2KE153Yf9BuiUKYMaxlTDM= -github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= -github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= -github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= -github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYWrPrQ= +github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw= +github.com/asaskevich/govalidator v0.0.0-20200108200545-475eaeb16496/go.mod h1:oGkLhpf+kjZl6xBf758TQhh5XrAeiJv/7FRz/2spLIg= +github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so= +github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= +github.com/aws/aws-sdk-go-v2 v1.39.4 h1:qTsQKcdQPHnfGYBBs+Btl8QwxJeoWcOcPcixK90mRhg= +github.com/aws/aws-sdk-go-v2 v1.39.4/go.mod h1:yWSxrnioGUZ4WVv9TgMrNUeLV3PFESn/v+6T/Su8gnM= +github.com/aws/aws-sdk-go-v2/config v1.31.15 h1:gE3M4xuNXfC/9bG4hyowGm/35uQTi7bUKeYs5e/6uvU= +github.com/aws/aws-sdk-go-v2/config v1.31.15/go.mod h1:HvnvGJoE2I95KAIW8kkWVPJ4XhdrlvwJpV6pEzFQa8o= +github.com/aws/aws-sdk-go-v2/credentials v1.18.19 h1:Jc1zzwkSY1QbkEcLujwqRTXOdvW8ppND3jRBb/VhBQc= +github.com/aws/aws-sdk-go-v2/credentials v1.18.19/go.mod h1:DIfQ9fAk5H0pGtnqfqkbSIzky82qYnGvh06ASQXXg6A= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.11 h1:X7X4YKb+c0rkI6d4uJ5tEMxXgCZ+jZ/D6mvkno8c8Uw= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.11/go.mod h1:EqM6vPZQsZHYvC4Cai35UDg/f5NCEU+vp0WfbVqVcZc= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.11 h1:7AANQZkF3ihM8fbdftpjhken0TP9sBzFbV/Ze/Y4HXA= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.11/go.mod h1:NTF4QCGkm6fzVwncpkFQqoquQyOolcyXfbpC98urj+c= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.11 h1:ShdtWUZT37LCAA4Mw2kJAJtzaszfSHFb5n25sdcv4YE= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.11/go.mod h1:7bUb2sSr2MZ3M/N+VyETLTQtInemHXb/Fl3s8CLzm0Y= +github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4 h1:WKuaxf++XKWlHWu9ECbMlha8WOEGm0OUEZqm4K/Gcfk= +github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4/go.mod h1:ZWy7j6v1vWGmPReu0iSGvRiise4YI5SkR3OHKTZ6Wuc= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.2 h1:xtuxji5CS0JknaXoACOunXOYOQzgfTvGAc9s2QdCJA4= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.2/go.mod h1:zxwi0DIR0rcRcgdbl7E2MSOvxDyyXGBlScvBkARFaLQ= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.11 h1:GpMf3z2KJa4RnJ0ew3Hac+hRFYLZ9DDjfgXjuW+pB54= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.11/go.mod h1:6MZP3ZI4QQsgUCFTwMZA2V0sEriNQ8k2hmoHF3qjimQ= +github.com/aws/aws-sdk-go-v2/service/route53 v1.59.1 h1:KuoA/cmy/yK8n9v/d6WH36cZwGxFOrn0TmZ4lNN3MKQ= +github.com/aws/aws-sdk-go-v2/service/route53 v1.59.1/go.mod h1:BymbICXBfXQHO6i+yTBhocA9a6DM0uMDQqYelqa9wzs= +github.com/aws/aws-sdk-go-v2/service/sso v1.29.8 h1:M5nimZmugcZUO9wG7iVtROxPhiqyZX6ejS1lxlDPbTU= +github.com/aws/aws-sdk-go-v2/service/sso v1.29.8/go.mod h1:mbef/pgKhtKRwrigPPs7SSSKZgytzP8PQ6P6JAAdqyM= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.3 h1:S5GuJZpYxE0lKeMHKn+BRTz6PTFpgThyJ+5mYfux7BM= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.3/go.mod h1:X4OF+BTd7HIb3L+tc4UlWHVrpgwZZIVENU15pRDVTI0= +github.com/aws/aws-sdk-go-v2/service/sts v1.38.9 h1:Ekml5vGg6sHSZLZJQJagefnVe6PmqC2oiRkBq4F7fU0= +github.com/aws/aws-sdk-go-v2/service/sts v1.38.9/go.mod h1:/e15V+o1zFHWdH3u7lpI3rVBcxszktIKuHKCY2/py+k= +github.com/aws/smithy-go v1.23.1 h1:sLvcH6dfAFwGkHLZ7dGiYF7aK6mg4CgKA/iDKjLDt9M= +github.com/aws/smithy-go v1.23.1/go.mod h1:LEj2LM3rBRQJxPZTB4KuzZkaZYnZPnvgIhb4pu07mx0= +github.com/benbjohnson/clock v1.3.5 h1:VvXlSJBzZpA/zum6Sj74hxwYI2DIxRWuNIoXAzHZz5o= +github.com/benbjohnson/clock v1.3.5/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= -github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= -github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= -github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM= -github.com/blang/semver v3.5.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= -github.com/bshuster-repo/logrus-logstash-hook v1.0.0 h1:e+C0SB5R1pu//O4MQ3f9cFuPGoOVeF2fE4Og9otCc70= -github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= -github.com/bugsnag/bugsnag-go v0.0.0-20141110184014-b1d153021fcd h1:rFt+Y/IK1aEZkEHchZRSq9OQbsSzIT/OrI8YFFmRIng= -github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b h1:otBG+dV+YK+Soembjv71DPz3uX/V/6MMlSyD9JBQ6kQ= -github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0 h1:nvj0OLI3YqYXer/kZD8Ri1aaunCxIEsOst1BVJswV0o= -github.com/cenkalti/backoff/v3 v3.0.0 h1:ske+9nBpD9qZsTBoF41nW5L+AIuFBKMeze18XQ3eG1c= -github.com/cenkalti/backoff/v3 v3.0.0/go.mod h1:cIeZDE3IrqwwJl6VUwCN6trj1oXrTS4rc0ij+ULvLYs= -github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= -github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= -github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/chai2010/gettext-go v1.0.2 h1:1Lwwip6Q2QGsAdl/ZKPCwTe9fe0CjlUbqj5bFNSjIRk= -github.com/chai2010/gettext-go v1.0.2/go.mod h1:y+wnP2cHYaVj19NZhYKAwEMH2CI1gNHeQQ+5AjwawxA= -github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= -github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= -github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= -github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag= -github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I= -github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/cloudflare/cloudflare-go v0.50.0 h1:RS4tttMecD1rYCiMMfJeW8s9OEhCm85Y+70RJuOoxNA= -github.com/cloudflare/cloudflare-go v0.50.0/go.mod h1:4+j2gGo6xyrFiYmpa2y4mNzu7pPPN42kyv1b2EqiZGQ= -github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= -github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= -github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= -github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= -github.com/containerd/cgroups v1.0.3 h1:ADZftAkglvCiD44c77s5YmMqaP2pzVCFZvBmAlBdAP4= -github.com/containerd/containerd v1.6.6 h1:xJNPhbrmz8xAMDNoVjHy9YHtWwEQNS+CDkcIRh7t8Y0= -github.com/containerd/containerd v1.6.6/go.mod h1:ZoP1geJldzCVY3Tonoz7b1IXk8rIX0Nltt5QE4OMNk0= -github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= -github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= -github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= -github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= -github.com/coreos/go-oidc v2.1.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc= -github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= -github.com/coreos/go-semver v0.3.0 h1:wkHLiw0WNATZnSG7epLsujiMCgPAc9xhjJ4tgnAxmfM= -github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= -github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= -github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= -github.com/coreos/go-systemd/v22 v22.3.2 h1:D9/bQk5vlXQFZ6Kwuu6zaiXJ9oTPe68++AzAJc1DzSI= -github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= -github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= -github.com/coreos/pkg v0.0.0-20180108230652-97fdf19511ea/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= -github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= -github.com/cpu/goacmedns v0.1.1 h1:DM3H2NiN2oam7QljgGY5ygy4yDXhK5Z4JUnqaugs2C4= -github.com/cpu/goacmedns v0.1.1/go.mod h1:MuaouqEhPAHxsbqjgnck5zeghuwBP1dLnPoobeGqugQ= -github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= -github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= -github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= -github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w= -github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= -github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= +github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= +github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM= +github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr4= +github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03VsM8rvUec= +github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= +github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/creack/pty v1.1.11 h1:07n33Z8lZxZ2qwegKbObQohDhXDQxiMMz1NOUGYlesw= -github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/cyphar/filepath-securejoin v0.2.3 h1:YX6ebbZCZP7VkM3scTTokDgBL2TY741X51MTk3ycuNI= -github.com/cyphar/filepath-securejoin v0.2.3/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= -github.com/danieljoos/wincred v1.1.0/go.mod h1:XYlo+eRTsVA9aHGp7NGjFkPla4m+DCL7hqDjlFjiygg= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/denisenkom/go-mssqldb v0.9.0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= -github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= -github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= -github.com/digitalocean/godo v1.86.0 h1:GKB2HS+6lnYPn+9XLLsIVBWk3xk7v568EJnmdHuyhKA= -github.com/digitalocean/godo v1.86.0/go.mod h1:jELt1jkHVifd0rKaY0pt/m1QxGzbkkvoVVrDkR15/5A= -github.com/distribution/distribution/v3 v3.0.0-20220526142353-ffbd94cbe269 h1:hbCT8ZPPMqefiAWD2ZKjn7ypokIGViTvBBg/ExLSdCk= -github.com/docker/cli v20.10.17+incompatible h1:eO2KS7ZFeov5UJeaDmIs1NFEDRf32PaqRpvoEkKBy5M= -github.com/docker/cli v20.10.17+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= -github.com/docker/distribution v2.8.1+incompatible h1:Q50tZOPR6T/hjNsyc9g8/syEs6bk8XXApsHjKukMl68= -github.com/docker/distribution v2.8.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= -github.com/docker/docker v0.7.3-0.20190327010347-be7ac8be2ae0/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= -github.com/docker/docker v20.10.17+incompatible h1:JYCuMrWaVNophQTOrMMoSwudOVEfcegoZZrleKc1xwE= -github.com/docker/docker v20.10.17+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= -github.com/docker/docker-credential-helpers v0.6.4 h1:axCks+yV+2MR3/kZhAmy07yC56WZ2Pwu/fKWtKuZB0o= -github.com/docker/docker-credential-helpers v0.6.4/go.mod h1:ofX3UI0Gz1TteYBjtgs07O36Pyasyp66D2uKT7H8W1c= -github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= -github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= -github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c h1:+pKlWGMw7gf6bQ+oDZB4KHQFypsfjYlq/C4rfL7D3g8= -github.com/docker/go-metrics v0.0.1 h1:AgB/0SvBxihN0X8OR4SjsblXkbMvalQ8cjmtKQ2rQV8= -github.com/docker/go-metrics v0.0.1/go.mod h1:cG1hvH2utMXtqgqqYE9plW6lDxS3/5ayHzueweSI3Vw= -github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= -github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw= -github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= -github.com/docker/libtrust v0.0.0-20150114040149-fa567046d9b1 h1:ZClxb8laGDf5arXfYcAtECDFgAgHklGI8CxgjHnXKJ4= -github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM= -github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= -github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= -github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= -github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= -github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153 h1:yUdfgN0XgIJw7foRItutHYUIhlcKzcSf5vDpdhQAKTc= -github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= -github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= -github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= -github.com/emicklei/go-restful/v3 v3.8.0 h1:eCZ8ulSerjdAiaNpF7GxXIE7ZCMo1moN1qX+S609eVw= -github.com/emicklei/go-restful/v3 v3.8.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= -github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= -github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= -github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= -github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= -github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= -github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= -github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE= -github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/evanphx/json-patch v0.5.2/go.mod h1:ZWS5hhDbVDyob71nXKNL0+PWn6ToqBHMikGIFbs31qQ= -github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= -github.com/evanphx/json-patch v5.6.0+incompatible h1:jBYDEEiFBPxA0v50tFdvOzQQTCvpL6mnFh5mB2/l16U= -github.com/evanphx/json-patch v5.6.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= -github.com/evanphx/json-patch/v5 v5.6.0 h1:b91NhWfaz02IuVxO9faSllyAtNXHMPkC5J8sJCLunww= -github.com/evanphx/json-patch/v5 v5.6.0/go.mod h1:G79N1coSVB93tBe7j6PhzjmR3/2VvlbKOFpnXhI9Bw4= -github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d h1:105gxyaGwCFad8crR9dcMQWvV9Hvulu6hwUh4tWPJnM= -github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d/go.mod h1:ZZMPRZwes7CROmyNKgQzC3XPs6L/G2EJLHddWejkmf4= -github.com/fatih/camelcase v1.0.0 h1:hxNvNX/xYBp0ovncs8WyWZrOrpBNub/JfaMvbURyft8= -github.com/fatih/camelcase v1.0.0/go.mod h1:yN2Sb0lFhZJUdVvtELVWefmrXpuZESvPmqwoZc+/fpc= -github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= -github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= -github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/digitalocean/godo v1.167.0 h1:/KHyVKBkUNT7oiZLPcUL45rNrxeQ2t0JdzreqbUI+Jw= +github.com/digitalocean/godo v1.167.0/go.mod h1:xQsWpVCCbkDrWisHA72hPzPlnC+4W5w/McZY5ij9uvU= +github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= +github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= +github.com/emicklei/go-restful/v3 v3.13.0 h1:C4Bl2xDndpU6nJ4bc1jXd+uTmYPVUwkD6bFY/oTyCes= +github.com/emicklei/go-restful/v3 v3.13.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/evanphx/json-patch v5.9.0+incompatible h1:fBXyNpNMuTTDdquAq/uisOr2lShz4oaXpDTX2bLe7ls= +github.com/evanphx/json-patch v5.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/evanphx/json-patch/v5 v5.9.11 h1:/8HVnzMq13/3x9TPvjG08wUGqBTmZBsCWzjTM0wiaDU= +github.com/evanphx/json-patch/v5 v5.9.11/go.mod h1:3j+LviiESTElxA4p3EMKAB9HXj3/XEtnUf6OZxqIQTM= +github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= +github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo= -github.com/felixge/httpsnoop v1.0.1 h1:lvB5Jl89CsZtGIWuTcDM1E/vkVs49/Ml7JJe07l8SPQ= -github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= -github.com/flowstack/go-jsonschema v0.1.1/go.mod h1:yL7fNggx1o8rm9RlgXv7hTBWxdBM0rVwpMwimd3F3N0= -github.com/form3tech-oss/jwt-go v3.2.3+incompatible h1:7ZaBxOI7TMoYBfyA3cQHErNNyAWIKUMIwqxEtgHOs5c= -github.com/frankban/quicktest v1.13.0 h1:yNZif1OkDfNoDfb9zZa9aXIpejNR4F23Wely0c+Qdqk= -github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= -github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= -github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI= -github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= -github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= -github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= -github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= -github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= -github.com/go-asn1-ber/asn1-ber v1.5.4 h1:vXT6d/FNDiELJnLb6hGNa309LMsrCoYFvpwHDF0+Y1A= -github.com/go-asn1-ber/asn1-ber v1.5.4/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= -github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w= -github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= -github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-gorp/gorp/v3 v3.0.2 h1:ULqJXIekoqMx29FI5ekXXFoH1dT2Vc8UhnRzBg+Emz4= -github.com/go-gorp/gorp/v3 v3.0.2/go.mod h1:BJ3q1ejpV8cVALtcXvXaXyTOlMmJhWDxTmncaR6rwBY= -github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= -github.com/go-kit/log v0.2.0/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0= -github.com/go-ldap/ldap/v3 v3.4.4 h1:qPjipEpt+qDa6SI/h1fzuGWoRUY+qqQ9sOZq67/PYUs= -github.com/go-ldap/ldap/v3 v3.4.4/go.mod h1:fe1MsuN5eJJ1FeLT/LEBVdWfNWKh459R7aXgXtJC+aI= -github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= -github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= -github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= -github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= -github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= -github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= -github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0= -github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/zapr v1.2.3 h1:a9vnzlIBPQBBkeaR9IuMUfmVOrQlkoC4YfPoFkX3T7A= -github.com/go-openapi/analysis v0.0.0-20180825180245-b006789cd277/go.mod h1:k70tL6pCuVxPJOHXQ+wIac1FUrvNkHolPie/cLEU6hI= -github.com/go-openapi/analysis v0.17.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik= -github.com/go-openapi/analysis v0.18.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik= -github.com/go-openapi/analysis v0.19.2/go.mod h1:3P1osvZa9jKjb8ed2TPng3f0i/UY9snX6gxi44djMjk= -github.com/go-openapi/analysis v0.19.5/go.mod h1:hkEAkxagaIvIP7VTn8ygJNkd4kAYON2rCu0v0ObL0AU= -github.com/go-openapi/errors v0.17.0/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0= -github.com/go-openapi/errors v0.18.0/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0= -github.com/go-openapi/errors v0.19.2/go.mod h1:qX0BLWsyaKfvhluLejVpVNwNRdXZhEbTA4kxxpKBC94= -github.com/go-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1/go.mod h1:+35s3my2LFTysnkMfxsJBAMHj/DoqoB9knIWoYG/Vk0= -github.com/go-openapi/jsonpointer v0.17.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M= -github.com/go-openapi/jsonpointer v0.18.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M= -github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg= -github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= -github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY= -github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= -github.com/go-openapi/jsonreference v0.0.0-20160704190145-13c6e3589ad9/go.mod h1:W3Z9FmVs9qj+KR4zFKmDPGiLdk1D9Rlm7cyMvf57TTg= -github.com/go-openapi/jsonreference v0.17.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I= -github.com/go-openapi/jsonreference v0.18.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I= -github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc= -github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8= -github.com/go-openapi/jsonreference v0.19.5 h1:1WJP/wi4OjB4iV8KVbH73rQaoialJrqv8gitZLxGLtM= -github.com/go-openapi/jsonreference v0.19.5/go.mod h1:RdybgQwPxbL4UEjuAruzK1x3nE69AqPYEJeo/TWfEeg= -github.com/go-openapi/loads v0.17.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU= -github.com/go-openapi/loads v0.18.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU= -github.com/go-openapi/loads v0.19.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU= -github.com/go-openapi/loads v0.19.2/go.mod h1:QAskZPMX5V0C2gvfkGZzJlINuP7Hx/4+ix5jWFxsNPs= -github.com/go-openapi/loads v0.19.4/go.mod h1:zZVHonKd8DXyxyw4yfnVjPzBjIQcLt0CCsn0N0ZrQsk= -github.com/go-openapi/runtime v0.0.0-20180920151709-4f900dc2ade9/go.mod h1:6v9a6LTXWQCdL8k1AO3cvqx5OtZY/Y9wKTgaoP6YRfA= -github.com/go-openapi/runtime v0.19.0/go.mod h1:OwNfisksmmaZse4+gpV3Ne9AyMOlP1lt4sK4FXt0O64= -github.com/go-openapi/runtime v0.19.4/go.mod h1:X277bwSUBxVlCYR3r7xgZZGKVvBd/29gLDlFGtJ8NL4= -github.com/go-openapi/spec v0.0.0-20160808142527-6aced65f8501/go.mod h1:J8+jY1nAiCcj+friV/PDoE1/3eeccG9LYBs0tYvLOWc= -github.com/go-openapi/spec v0.17.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI= -github.com/go-openapi/spec v0.18.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI= -github.com/go-openapi/spec v0.19.2/go.mod h1:sCxk3jxKgioEJikev4fgkNmwS+3kuYdJtcsZsD5zxMY= -github.com/go-openapi/spec v0.19.3/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo= -github.com/go-openapi/strfmt v0.17.0/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU= -github.com/go-openapi/strfmt v0.18.0/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU= -github.com/go-openapi/strfmt v0.19.0/go.mod h1:+uW+93UVvGGq2qGaZxdDeJqSAqBqBdl+ZPMF/cC8nDY= -github.com/go-openapi/strfmt v0.19.3/go.mod h1:0yX7dbo8mKIvc3XSKp7MNfxw4JytCfCD6+bY1AVL9LU= -github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dpr1UfpPtxFw+EFuQ41HhCWZfha5jSVRG7C7I= -github.com/go-openapi/swag v0.17.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg= -github.com/go-openapi/swag v0.18.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg= -github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= -github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= -github.com/go-openapi/swag v0.19.14 h1:gm3vOOXfiuw5i9p5N9xJvfjvuofpyvLA9Wr6QfK5Fng= -github.com/go-openapi/swag v0.19.14/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= -github.com/go-openapi/validate v0.18.0/go.mod h1:Uh4HdOzKt19xGIGm1qHf/ofbX1YQ4Y+MYsct2VUrAJ4= -github.com/go-openapi/validate v0.19.2/go.mod h1:1tRCw7m3jtI8eNWEEliiAqUIcBztB2KDnRCRMUi7GTA= -github.com/go-openapi/validate v0.19.5/go.mod h1:8DJv2CVJQ6kGNpFW6eV9N3JviE1C85nY1c2z52x1Gk4= -github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= -github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= -github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= -github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= -github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= -github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I= -github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= -github.com/go-test/deep v1.0.2 h1:onZX1rnHT3Wv6cqNgYyFOOlgVKJrksuCMCRvJStbMYw= -github.com/gobuffalo/flect v0.2.5 h1:H6vvsv2an0lalEaCDRThvtBfmg44W/QHXBCYUXf/6S4= -github.com/gobuffalo/flect v0.2.5/go.mod h1:1ZyCLIbg0YD7sDkzvFdPoOydPtD8y9JQnrOROolUcM8= -github.com/gobuffalo/logger v1.0.6 h1:nnZNpxYo0zx+Aj9RfMPBm+x9zAU2OayFh/xrAWi34HU= -github.com/gobuffalo/logger v1.0.6/go.mod h1:J31TBEHR1QLV2683OXTAItYIg8pv2JMHnF/quuAbMjs= -github.com/gobuffalo/packd v1.0.1 h1:U2wXfRr4E9DH8IdsDLlRFwTZTK7hLfq9qT/QHXGVe/0= -github.com/gobuffalo/packd v1.0.1/go.mod h1:PP2POP3p3RXGz7Jh6eYEf93S7vA2za6xM7QT85L4+VY= -github.com/gobuffalo/packr/v2 v2.8.3 h1:xE1yzvnO56cUC0sTpKR3DIbxZgB54AftTFMhB2XEWlY= -github.com/gobuffalo/packr/v2 v2.8.3/go.mod h1:0SahksCVcx4IMnigTjiFuyldmTrdTctXsOdiU5KwbKc= -github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= -github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= +github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= +github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= +github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= +github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= +github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= +github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= +github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM= +github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= +github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667 h1:BP4M0CvQ4S3TGls2FvczZtj5Re/2ZzkV9VwqPHH/3Bo= +github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= +github.com/go-http-utils/headers v0.0.0-20181008091004-fed159eddc2a h1:v6zMvHuY9yue4+QkG/HQ/W67wvtQmWJ4SDo9aK/GIno= +github.com/go-http-utils/headers v0.0.0-20181008091004-fed159eddc2a/go.mod h1:I79BieaU4fxrw4LMXby6q5OS9XnoR9UIKLOzDFjUmuw= +github.com/go-jose/go-jose/v4 v4.1.2 h1:TK/7NqRQZfgAh+Td8AlsrvtPoUyiHh0LqVvokh+1vHI= +github.com/go-jose/go-jose/v4 v4.1.2/go.mod h1:22cg9HWM1pOlnRiY+9cQYJ9XHmya1bYW8OeDM6Ku6Oo= +github.com/go-ldap/ldap/v3 v3.4.12 h1:1b81mv7MagXZ7+1r7cLTWmyuTqVqdwbtJSjC0DAp9s4= +github.com/go-ldap/ldap/v3 v3.4.12/go.mod h1:+SPAGcTtOfmGsCb3h1RFiq4xpp4N636G75OEace8lNo= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= +github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ= +github.com/go-logr/zapr v1.3.0/go.mod h1:YKepepNBd1u/oyhd/yQmtjVXmm9uML4IXUgMOwR8/Gg= +github.com/go-openapi/jsonpointer v0.22.1 h1:sHYI1He3b9NqJ4wXLoJDKmUmHkWy/L7rtEo92JUxBNk= +github.com/go-openapi/jsonpointer v0.22.1/go.mod h1:pQT9OsLkfz1yWoMgYFy4x3U5GY5nUlsOn1qSBH5MkCM= +github.com/go-openapi/jsonreference v0.21.2 h1:Wxjda4M/BBQllegefXrY/9aq1fxBA8sI5M/lFU6tSWU= +github.com/go-openapi/jsonreference v0.21.2/go.mod h1:pp3PEjIsJ9CZDGCNOyXIQxsNuroxm8FAJ/+quA0yKzQ= +github.com/go-openapi/swag v0.23.1 h1:lpsStH0n2ittzTnbaSloVZLuB5+fvSY/+hnagBjSNZU= +github.com/go-openapi/swag v0.23.1/go.mod h1:STZs8TbRvEQQKUA+JZNAm3EWlgaOBGpyFDqQnDHMef0= +github.com/go-openapi/swag/jsonname v0.25.1 h1:Sgx+qbwa4ej6AomWC6pEfXrA6uP2RkaNjA9BR8a1RJU= +github.com/go-openapi/swag/jsonname v0.25.1/go.mod h1:71Tekow6UOLBD3wS7XhdT98g5J5GR13NOTQ9/6Q11Zo= +github.com/go-ozzo/ozzo-validation/v4 v4.3.0 h1:byhDUpfEwjsVQb1vBunvIjh2BHQ9ead57VkAEY4V+Es= +github.com/go-ozzo/ozzo-validation/v4 v4.3.0/go.mod h1:2NKgrcHl3z6cJs+3Oo940FPRiTzuqKbvfrL2RxCj6Ew= +github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= +github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= +github.com/go-test/deep v1.1.1 h1:0r/53hagsehfO4bzD2Pgr/+RgHqhmf+k1Bpse2cTu1U= +github.com/go-test/deep v1.1.1/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= -github.com/godror/godror v0.24.2/go.mod h1:wZv/9vPiUib6tkoDl+AZ/QLf5YZgMravZ7jxH2eQWAE= -github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= -github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= -github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= -github.com/golang-jwt/jwt/v4 v4.2.0 h1:besgBTC8w8HjP6NzQdxwKH9Z5oQMZ24ThTrHp3cZ8eU= -github.com/golang-jwt/jwt/v4 v4.2.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= -github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= -github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= -github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= -github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8= -github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= -github.com/golang/protobuf v0.0.0-20161109072736-4bd1920723d7/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= -github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= -github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= -github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= -github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= -github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= -github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= -github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= -github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= -github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo= +github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/gomodule/redigo v1.8.2 h1:H5XSIre1MB5NbPYFp+i1NBbb5qN1W8Y8YAQoAYbkm8k= -github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4= -github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA= -github.com/google/gnostic v0.6.9 h1:ZK/5VhkoX835RikCHpSUJV9a+S3e1zLh59YnyWeBW+0= -github.com/google/gnostic v0.6.9/go.mod h1:Nm8234We1lq6iB9OmlgNv3nH91XLLVZHCDayfA3xq+E= -github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= -github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg= +github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= +github.com/google/cel-go v0.26.0 h1:DPGjXackMpJWH680oGY4lZhYjIameYmR+/6RBdDGmaI= +github.com/google/cel-go v0.26.0/go.mod h1:A9O8OU9rdvrK5MQyrqfIxo1a0u4g3sF8KB6PUIaryMM= +github.com/google/certificate-transparency-go v1.3.1 h1:akbcTfQg0iZlANZLn0L9xOeWtyCIdeoYhKrqi5iH3Go= +github.com/google/certificate-transparency-go v1.3.1/go.mod h1:gg+UQlx6caKEDQ9EElFOujyxEQEfOiQzAt6782Bvi8k= +github.com/google/gnostic-models v0.7.0 h1:qwTtogB15McXDaNqTZdzPJRHvaVJlAl+HVQnLmJEJxo= +github.com/google/gnostic-models v0.7.0/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= -github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= -github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= -github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= -github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= -github.com/google/martian/v3 v3.2.1/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk= -github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 h1:K6RDEckDVWvDI9JAJYCmNdQXq6neHJOYx3V6jnqNEec= -github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= -github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= -github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= -github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= -github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/googleapis/enterprise-certificate-proxy v0.0.0-20220520183353-fd19c99a87aa/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8= -github.com/googleapis/enterprise-certificate-proxy v0.1.0 h1:zO8WHNx/MYiAKJ3d5spxZXZE6KHmIQGQcAzwUzV7qQw= -github.com/googleapis/enterprise-certificate-proxy v0.1.0/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8= -github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= -github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= -github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0= -github.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0eJc8R6ouapiM= -github.com/googleapis/gax-go/v2 v2.2.0/go.mod h1:as02EH8zWkzwUoLbBaFeQ+arQaj/OthfcblKl4IGNaM= -github.com/googleapis/gax-go/v2 v2.3.0/go.mod h1:b8LNqSzNabLiUpXKkY7HAR5jr6bIT99EXz9pXxye9YM= -github.com/googleapis/gax-go/v2 v2.4.0 h1:dS9eYAjhrE2RjmzYw2XAPvcXfmcQLtFEQWn0CR82awk= -github.com/googleapis/gax-go/v2 v2.4.0/go.mod h1:XOTVJ59hdnfJLIP/dh8n5CGryZR2LxK9wbMD5+iXC6c= -github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= -github.com/googleapis/gnostic v0.1.0/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= -github.com/googleapis/go-type-adapters v1.0.0/go.mod h1:zHW75FOG2aur7gAO2B+MLby+cLsWGBF62rFAi7WjWO4= -github.com/gophercloud/gophercloud v0.1.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8= -github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= -github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= -github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH4= -github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= -github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= -github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= -github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= -github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= -github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/gosuri/uitable v0.0.4 h1:IG2xLKRvErL3uhY6e1BylFzG+aJiwQviDDTfOKeKTpY= -github.com/gosuri/uitable v0.0.4/go.mod h1:tKR86bXuXPZazfOTG1FIzvjIdXzd0mo4Vtn16vt0PJo= -github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 h1:pdN6V1QBWetyv/0+wjACpqVH+eVULgEjkurDLq3goeM= -github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= -github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= -github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= -github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 h1:+9834+KizmvFV7pXQGSXQTsaWhq2GjuNUt0aUU0YBYw= +github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db h1:097atOisP2aRj7vFgYQBbFN4U4JNXUNYpxael3UzMyo= +github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= +github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0= +github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/enterprise-certificate-proxy v0.3.6 h1:GW/XbdyBFQ8Qe+YAmFU9uHLo7OnF5tL52HFAgMmyrf4= +github.com/googleapis/enterprise-certificate-proxy v0.3.6/go.mod h1:MkHOF77EYAE7qfSuSS9PU6g4Nt4e11cnsDUowfwewLA= +github.com/googleapis/gax-go/v2 v2.15.0 h1:SyjDc1mGgZU5LncH8gimWo9lW1DtIfPibOG81vgd/bo= +github.com/googleapis/gax-go/v2 v2.15.0/go.mod h1:zVVkkxAQHa1RQpg9z2AUCMnKhi0Qld9rcmyfL1OZhoc= +github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 h1:JeSE6pjso5THxAzdVpqr6/geYxZytqFMBCOtn/ujyeo= +github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674/go.mod h1:r4w70xmWCQKmi1ONH4KIaBptdivuRPyosB9RmPlGEwA= +github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 h1:UH//fgunKIs4JdUbpDl1VZCDaL56wXCB/5+wF6uHfaI= +github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.0.1 h1:qnpSQwGEnkcRpTqNOIR6bJbR0gAorgP9CSALpRcKoAA= +github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.0.1/go.mod h1:lXGCsh6c22WGtjr+qGHj1otzZpV/1kwTMAqkwZsnWRU= +github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.0 h1:FbSCl+KggFl+Ocym490i/EyXF4lPgLoUtcSWquBM0Rs= +github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.0/go.mod h1:qOchhhIlmRcqk/O9uCo/puJlyo07YINaIqdZfZG3Jkc= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92BcuyuQ/YW4NSIpoGtfXNho= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= -github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= -github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= -github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo= -github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= -github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 h1:2VTzZjLZBgl62/EtslCrtky5vbi9dd7HrQPQIx6wqiw= -github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplbc8s8sSb3V2oUCygFHVp8gC3Dn6U4MNI= -github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= -github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1 h1:X5VWvz21y3gzm9Nw/kaUeku/1+uBhcekkmy4IkffJww= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1/go.mod h1:Zanoh4+gvIgluNqcfMVTJueD4wSS5hT7zTt4Mrutd90= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= -github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= -github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= -github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= -github.com/hashicorp/go-hclog v0.14.1/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= -github.com/hashicorp/go-hclog v1.2.0 h1:La19f8d7WIlm4ogzNHB0JGqs5AUDAZ2UfCY4sJXcJdM= -github.com/hashicorp/go-hclog v1.2.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= -github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= -github.com/hashicorp/go-immutable-radix v1.3.1 h1:DKHmCUm2hRBK510BaiZlwvpD40f8bJFeZnpfm2KLowc= -github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= -github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= -github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= +github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k= +github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= +github.com/hashicorp/go-hmac-drbg v0.0.0-20210916214228-a6e5a68489f6 h1:kBoJV4Xl5FLtBfnBjDvBxeNSy2IRITSGs73HQsFUEjY= +github.com/hashicorp/go-hmac-drbg v0.0.0-20210916214228-a6e5a68489f6/go.mod h1:y+HSOcOGB48PkUxNyLAiCiY6rEENu+E+Ss4LG8QHwf4= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= -github.com/hashicorp/go-plugin v1.4.3 h1:DXmvivbWD5qdiBts9TpBC7BYL1Aia5sxbRgQB+v6UZM= -github.com/hashicorp/go-plugin v1.4.3/go.mod h1:5fGEH17QVwTTcR0zV7yhDPLLmFX9YSZ38b18Udy6vYQ= -github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= -github.com/hashicorp/go-retryablehttp v0.7.1 h1:sUiuQAnLlbvmExtFQs72iFW/HXeUn8Z1aJLQ4LJJbTQ= -github.com/hashicorp/go-retryablehttp v0.7.1/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY= -github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= +github.com/hashicorp/go-retryablehttp v0.7.8 h1:ylXZWnqa7Lhqpk0L1P1LzDtGcCR0rPVUrx/c8Unxc48= +github.com/hashicorp/go-retryablehttp v0.7.8/go.mod h1:rjiScheydd+CxvumBsIrFKlx3iS0jrZ7LvzFGFmuKbw= github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc= github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= -github.com/hashicorp/go-secure-stdlib/mlock v0.1.1 h1:cCRo8gK7oq6A2L6LICkUZ+/a5rLiRXFMf1Qd4xSwxTc= -github.com/hashicorp/go-secure-stdlib/mlock v0.1.1/go.mod h1:zq93CJChV6L9QTfGKtfBxKqD7BqqXx5O04A/ns2p5+I= -github.com/hashicorp/go-secure-stdlib/parseutil v0.1.6 h1:om4Al8Oy7kCm/B86rLCLah4Dt5Aa0Fr5rYBG60OzwHQ= -github.com/hashicorp/go-secure-stdlib/parseutil v0.1.6/go.mod h1:QmrqtbKuxxSWTN3ETMPuB+VtEiBJ/A9XhoYGv8E1uD8= -github.com/hashicorp/go-secure-stdlib/strutil v0.1.1/go.mod h1:gKOamz3EwoIoJq7mlMIRBpVTAUn8qPCrEclOKKWhD3U= +github.com/hashicorp/go-secure-stdlib/cryptoutil v0.1.1 h1:VaLXp47MqD1Y2K6QVrA9RooQiPyCgAbnfeJg44wKuJk= +github.com/hashicorp/go-secure-stdlib/cryptoutil v0.1.1/go.mod h1:hH8rgXHh9fPSDPerG6WzABHsHF+9ZpLhRI1LPk4JZ8c= +github.com/hashicorp/go-secure-stdlib/parseutil v0.2.0 h1:U+kC2dOhMFQctRfhK0gRctKAPTloZdMU5ZJxaesJ/VM= +github.com/hashicorp/go-secure-stdlib/parseutil v0.2.0/go.mod h1:Ll013mhdmsVDuoIXVfBtvgGJsXDYkTw1kooNcoCXuE0= github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 h1:kes8mmyCpxJsI7FTwtzRqEy9CdjCtrXrXGuOpxEA7Ts= github.com/hashicorp/go-secure-stdlib/strutil v0.1.2/go.mod h1:Gou2R9+il93BqX25LAKCLuM+y9U2T4hlwvT1yprcna4= -github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= -github.com/hashicorp/go-sockaddr v1.0.2 h1:ztczhD1jLxIRjVejw8gFomI1BQZOe2WoVOu0SyteCQc= -github.com/hashicorp/go-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjGlgmH/UkBUC97A= -github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= -github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/go-uuid v1.0.2 h1:cfejS+Tpcp13yd5nYHWDI6qVCny6wyX2Mt5SGur2IGE= -github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/go-version v1.2.0 h1:3vNe/fWF5CBgRIguda1meWhsZHy3m8gCJ5wx+dIzX/E= -github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= -github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= -github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc= -github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= -github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= -github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= -github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= -github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= -github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= -github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= -github.com/hashicorp/vault/api v1.8.0 h1:7765sW1XBt+qf4XKIYE4ebY9qc/yi9V2/egzGSUNMZU= -github.com/hashicorp/vault/api v1.8.0/go.mod h1:uJrw6D3y9Rv7hhmS17JQC50jbPDAZdjZoTtrCCxxs7E= -github.com/hashicorp/vault/sdk v0.6.0 h1:6Z+In5DXHiUfZvIZdMx7e2loL1PPyDjA4bVh9ZTIAhs= -github.com/hashicorp/vault/sdk v0.6.0/go.mod h1:+DRpzoXIdMvKc88R4qxr+edwy/RvH5QK8itmxLiDHLc= -github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb h1:b5rjCoWHc7eqmAS4/qyk21ZsHyb6Mxv/jykxvNTkU4M= -github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= -github.com/howeyc/gopass v0.0.0-20170109162249-bf9dde6d0d2c/go.mod h1:lADxMC39cJJqL93Duh1xhAs4I2Zs8mKS89XWXFGp9cs= -github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= -github.com/huandu/xstrings v1.3.1/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= -github.com/huandu/xstrings v1.3.2 h1:L18LIDzqlW6xN2rEkpdV8+oL/IXWJ1APd+vsdYy4Wdw= -github.com/huandu/xstrings v1.3.2/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= -github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= -github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= -github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= -github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= -github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU= -github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= -github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= -github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= -github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= -github.com/jhump/protoreflect v1.6.0 h1:h5jfMVslIg6l29nsMs0D8Wj17RDVdNYti0vDN/PZZoE= -github.com/jhump/protoreflect v1.6.0/go.mod h1:eaTn3RZAmMBcV0fifFvlm6VHNz3wSkYyXYWUh7ymB74= -github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= -github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= -github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= -github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= -github.com/jmoiron/sqlx v1.3.5 h1:vFFPA71p1o5gAeqtEAwLU4dnX2napprKtHr7PYIcN3g= -github.com/jmoiron/sqlx v1.3.5/go.mod h1:nRVWtLre0KfCLJvgxzCsLVMogSvQ1zNJtpYr2Ccp0mQ= -github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= -github.com/jonboulle/clockwork v0.2.2 h1:UOGuzwb1PwsrDAObMuhUnj0p5ULPj8V/xJ7Kx9qUBdQ= +github.com/hashicorp/go-sockaddr v1.0.7 h1:G+pTkSO01HpR5qCxg7lxfsFEZaG+C0VssTy/9dbT+Fw= +github.com/hashicorp/go-sockaddr v1.0.7/go.mod h1:FZQbEYa1pxkQ7WLpyXJ6cbjpT8q0YgQaK/JakXqGyWw= +github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= +github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/hcl v1.0.1-vault-7 h1:ag5OxFVy3QYTFTJODRzTKVZ6xvdfLLCA1cy/Y6xGI0I= +github.com/hashicorp/hcl v1.0.1-vault-7/go.mod h1:XYhtn6ijBSAj6n4YqAaf7RBPS4I06AItNorpy+MoQNM= +github.com/hashicorp/vault/api v1.22.0 h1:+HYFquE35/B74fHoIeXlZIP2YADVboaPjaSicHEZiH0= +github.com/hashicorp/vault/api v1.22.0/go.mod h1:IUZA2cDvr4Ok3+NtK2Oq/r+lJeXkeCrHRmqdyWfpmGM= +github.com/hashicorp/vault/sdk v0.20.0 h1:a4ulj2gICzw/qH0A4+6o36qAHxkUdcmgpMaSSjqE3dc= +github.com/hashicorp/vault/sdk v0.20.0/go.mod h1:xEjAt/n/2lHBAkYiRPRmvf1d5B6HlisPh2pELlRCosk= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/jcmturner/aescts/v2 v2.0.0 h1:9YKLH6ey7H4eDBXW8khjYslgyqG2xZikXP0EQFKrle8= +github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs= +github.com/jcmturner/dnsutils/v2 v2.0.0 h1:lltnkeZGL0wILNvrNiVCR6Ro5PGU/SeBvVO/8c/iPbo= +github.com/jcmturner/dnsutils/v2 v2.0.0/go.mod h1:b0TnjGOvI/n42bZa+hmXL+kFJZsFT7G4t3HTlQ184QM= +github.com/jcmturner/gofork v1.7.6 h1:QH0l3hzAU1tfT3rZCnW5zXl+orbkNMMRGJfdJjHVETg= +github.com/jcmturner/gofork v1.7.6/go.mod h1:1622LH6i/EZqLloHfE7IeZ0uEJwMSUyQ/nDd82IeqRo= +github.com/jcmturner/goidentity/v6 v6.0.1 h1:VKnZd2oEIMorCTsFBnJWbExfNN7yZr3EhJAxwOkZg6o= +github.com/jcmturner/goidentity/v6 v6.0.1/go.mod h1:X1YW3bgtvwAXju7V3LCIMpY0Gbxyjn/mY9zx4tFonSg= +github.com/jcmturner/gokrb5/v8 v8.4.4 h1:x1Sv4HaTpepFkXbt2IkL29DXRf8sOfZXo8eRKh687T8= +github.com/jcmturner/gokrb5/v8 v8.4.4/go.mod h1:1btQEpgT6k+unzCwX1KdWMEwPPkkgBtP+F6aCACiMrs= +github.com/jcmturner/rpc/v2 v2.0.3 h1:7FXXj8Ti1IaVFpSAziCZWNzbNuZmnvw/i6CqLNdWfZY= +github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc= +github.com/jonboulle/clockwork v0.5.0 h1:Hyh9A8u51kptdkR+cqRpT1EebBwTn1oK9YfGYbdFz6I= +github.com/jonboulle/clockwork v0.5.0/go.mod h1:3mZlmanh0g2NDKO5TWZVJAfofYk64M7XN3SzBPjZF60= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= -github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= -github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= -github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= -github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= -github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= -github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= -github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= -github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= -github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= -github.com/karrick/godirwalk v1.16.1 h1:DynhcF+bztK8gooS0+NDJFrdNZjJ3gzVzC545UNA9iw= -github.com/karrick/godirwalk v1.16.1/go.mod h1:j4mkqPuvaLI8mp1DroR3P6ad7cyYd4c1qeJ3RV7ULlk= -github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= -github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= +github.com/keybase/go-keychain v0.0.1 h1:way+bWYa6lDppZoZcgMbYsvC7GxljxrskdNInRtuthU= +github.com/keybase/go-keychain v0.0.1/go.mod h1:PdEILRW3i9D8JcdM+FmY6RwkHGnhHxXwkPPMeUgOK1k= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/klauspost/compress v1.13.6 h1:P76CopJELS0TiO2mebmnzgWaajssP/EszplttgQxcgc= -github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= -github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/kortschak/utter v1.0.1/go.mod h1:vSmSjbyrlKjjsL71193LmzBOKgwePk9DH6uFaWHIInc= -github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= -github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= -github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= -github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= -github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= -github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= -github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA= -github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= +github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 h1:SOEGU9fKiNWd/HOJuq6+3iTQz8KNCLtVX6idSoTLdUw= -github.com/lann/builder v0.0.0-20180802200727-47ae307949d0/go.mod h1:dXGbAdH5GtBTC4WfIxhKZfyBF/HBFgRZSWwZ9g/He9o= -github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 h1:P6pPBnrTSX3DEVR4fDembhRWSsG5rVo6hYhAB/ADZrk= -github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0/go.mod h1:vmVJ0l/dxyfGW6FmdpVm2joNMFikkuWg0EoCKLGUMNw= -github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= -github.com/lib/pq v1.10.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= -github.com/lib/pq v1.10.6 h1:jbk+ZieJ0D7EVGJYpL9QTz7/YW6UHbmdnZWYyK5cdBs= -github.com/lib/pq v1.10.6/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= -github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de h1:9TO3cAIGXtEhnIaL+V+BEER86oLrvS+kWobKpbJuye0= -github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE= -github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= -github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= -github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= -github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= -github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= -github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= -github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= -github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= -github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs= -github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA= -github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= -github.com/markbates/errx v1.1.0 h1:QDFeR+UP95dO12JgW+tgi2UVfo0V8YBHiUIOaeBPiEI= -github.com/markbates/errx v1.1.0/go.mod h1:PLa46Oex9KNbVDZhKel8v1OT7hD5JZ2eI7AHhA0wswc= -github.com/markbates/oncer v1.0.0 h1:E83IaVAHygyndzPimgUYJjbshhDTALZyXxvk9FOlQRY= -github.com/markbates/oncer v1.0.0/go.mod h1:Z59JA581E9GP6w96jai+TGqafHPW+cPfRxz2aSZ0mcI= -github.com/markbates/safe v1.0.1 h1:yjZkbvRM6IzKj9tlu/zMJLS0n/V351OZWRnF3QfaUxI= -github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0= -github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= -github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= -github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= -github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40= -github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= -github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= -github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= -github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= -github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= -github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= -github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= -github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= -github.com/mattn/go-oci8 v0.1.1/go.mod h1:wjDx6Xm9q7dFtHJvIlrI99JytznLw5wQ4R+9mNXJwGI= -github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= -github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= -github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU= -github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= -github.com/mattn/go-sqlite3 v1.11.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= -github.com/mattn/go-sqlite3 v1.14.6 h1:dNPt6NO46WmLVt2DLNpwczCmdV5boIZ6g/tlDrlRUbg= -github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= -github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= -github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 h1:I0XW9+e1XWDxdcEniV4rQAIOPUGDq67JSCiRCgGCZLI= -github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= -github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= -github.com/miekg/dns v1.1.50 h1:DQUfb9uc6smULcREF09Uc+/Gd46YWqJd5DbpPE9xkcA= -github.com/miekg/dns v1.1.50/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME= -github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= -github.com/mitchellh/cli v1.1.2/go.mod h1:6iaV0fGdElS6dPBx0EApTxHrcWvmJphyh2n8YBLPPZ4= -github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= -github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= -github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= -github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4= +github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= +github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= +github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/miekg/dns v1.1.68 h1:jsSRkNozw7G/mnmXULynzMNIsgY2dHC8LO6U6Ij2JEA= +github.com/miekg/dns v1.1.68/go.mod h1:fujopn7TB3Pu3JM69XaawiU0wqjpL9/8xGop5UrTPps= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= -github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= -github.com/mitchellh/go-testing-interface v1.0.0 h1:fzU/JVNcaqHQEcVFAKeR41fkiLdIPrefOvVG1VZ96U0= -github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= -github.com/mitchellh/go-wordwrap v1.0.0 h1:6GlHJ/LTGMrIJbwgdqdl2eEH8o+Exx/0m8ir9Gns0u4= -github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= -github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= -github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= -github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= -github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= -github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= -github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= -github.com/moby/locker v1.0.1 h1:fOXqR41zeveg4fFODix+1Ch4mj/gT0NE1XJbp/epuBg= -github.com/moby/locker v1.0.1/go.mod h1:S7SDdo5zpBK84bzzVlKr2V0hz+7x9hWbYC/kq7oQppc= -github.com/moby/spdystream v0.2.0 h1:cjW1zVyyoiM0T7b6UoySUFqzXMoqRckQtXwGPiBhOM8= -github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c= -github.com/moby/sys/mountinfo v0.5.0 h1:2Ks8/r6lopsxWi9m58nlwjaeSzUX9iiL1vj5qB/9ObI= -github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6 h1:dcztxKSvZ4Id8iPpHERQBbIJfabdt4wUm5qy3wOL2Zc= -github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6/go.mod h1:E2VnQOmVuvZB6UYnnDB0qG5Nq/1tD9acaOpo6xmt0Kw= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= -github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 h1:n6/2gBQ3RWajuToeY6ZtZTIKv2v7ThUy5KKusIT0yc0= -github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00/go.mod h1:Pm3mSP3c5uWn86xMLZ5Sa7JB9GsEZySvHYXCTK4E9q4= -github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= -github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= -github.com/munnerz/crd-schema-fuzz v1.0.0 h1:8erI9yzEnOGw9K5O+a8zZdoo8N/OwrFi7c7SjBtkHAs= -github.com/munnerz/crd-schema-fuzz v1.0.0/go.mod h1:4z/rcm37JxUkSsExFcLL6ZIT1SgDRdLiu7qq1evdVS0= -github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8= +github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= -github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= -github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32/go.mod h1:9wM+0iRr9ahx58uYLpLIr5fm8diHn0JbqRycJi6w0Ms= -github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= -github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= -github.com/oklog/run v1.0.0 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw= -github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= -github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= -github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= -github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= -github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= -github.com/onsi/ginkgo/v2 v2.2.0 h1:3ZNA3L1c5FYDFTTxbFeVGGD8jYvjYauHD30YgLxVsNI= -github.com/onsi/ginkgo/v2 v2.2.0/go.mod h1:MEH45j8TBi6u9BMogfbp0stKC5cdGjumZj5Y7AG4VIk= -github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= -github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= -github.com/onsi/gomega v1.20.2 h1:8uQq0zMgLEfa0vRrrBgaJF2gyW9Da9BmfGV+OyUzfkY= -github.com/onsi/gomega v1.20.2/go.mod h1:iYAIXgPSaDHak0LCMA+AWBpIKBr8WZicMxnE8luStNc= -github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= -github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= -github.com/opencontainers/image-spec v1.0.3-0.20211202183452-c5a74bcca799 h1:rc3tiVYb5z54aKaDfakKn0dDjIyPpTtszkjuMzyt7ec= -github.com/opencontainers/image-spec v1.0.3-0.20211202183452-c5a74bcca799/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= -github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= -github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY= -github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= -github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= -github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= -github.com/pavel-v-chernykh/keystore-go/v4 v4.1.0/go.mod h1:2ejgys4qY+iNVW1IittZhyRYA6MNv8TgM6VHqojbB9g= -github.com/pavlo-v-chernykh/keystore-go/v4 v4.4.0 h1:y9azNmMzvkNBPyczpNRwaV4bm0U6e7Oyrj7gi2/SNFI= -github.com/pavlo-v-chernykh/keystore-go/v4 v4.4.0/go.mod h1:lAVhWwbNaveeJmxrxuSTxMgKpF6DjnuVpn6T8WiBwYQ= -github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= -github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= -github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= -github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI= -github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= -github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5 h1:Ii+DKncOVM8Cu1Hc+ETb5K+23HdAMvESYE3ZJ5b5cMI= -github.com/pierrec/lz4 v2.5.2+incompatible h1:WCjObylUIOlKy/+7Abdn34TLIkXiA4UWUMhxq9m9ZXI= -github.com/pierrec/lz4 v2.5.2+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= +github.com/nrdcg/goacmedns v0.2.0 h1:ADMbThobzEMnr6kg2ohs4KGa3LFqmgiBA22/6jUWJR0= +github.com/nrdcg/goacmedns v0.2.0/go.mod h1:T5o6+xvSLrQpugmwHvrSNkzWht0UGAwj2ACBMhh73Cg= +github.com/onsi/ginkgo/v2 v2.22.0 h1:Yed107/8DjTr0lKCNt7Dn8yQ6ybuDRQoMGrNFKzMfHg= +github.com/onsi/ginkgo/v2 v2.22.0/go.mod h1:7Du3c42kxCUegi0IImZ1wUQzMBVecgIHjR1C+NkhLQo= +github.com/onsi/gomega v1.36.1 h1:bJDPBO7ibjxcbHMgSCoo4Yj18UWbKDlLwX1x9sybDcw= +github.com/onsi/gomega v1.36.1/go.mod h1:PvZbdDc8J6XJEpDK4HCuRBm8a6Fzp9/DmhC9C7yFlog= +github.com/pavlo-v-chernykh/keystore-go/v4 v4.5.0 h1:2nosf3P75OZv2/ZO/9Px5ZgZ5gbKrzA3joN1QMfOGMQ= +github.com/pavlo-v-chernykh/keystore-go/v4 v4.5.0/go.mod h1:lAVhWwbNaveeJmxrxuSTxMgKpF6DjnuVpn6T8WiBwYQ= +github.com/pierrec/lz4 v2.6.1+incompatible h1:9UY3+iC23yxF0UfGaYrGplQ+79Rg+h/q9FV9ix19jjM= +github.com/pierrec/lz4 v2.6.1+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= +github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ= +github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= -github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= -github.com/poy/onpar v0.0.0-20190519213022-ee068f8ea4d1 h1:oL4IBbcqwhhNWh31bjOX8C/OCy0zs9906d/VUru+bqg= -github.com/poy/onpar v0.0.0-20190519213022-ee068f8ea4d1/go.mod h1:nSbFQvMj97ZyhFRSJYtut+msi4sOY6zJDGCdSc+/rZU= -github.com/pquerna/cachecontrol v0.0.0-20171018203845-0dec1b30a021/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA= -github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= -github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= -github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= -github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g= -github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU= -github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= -github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= -github.com/prometheus/client_golang v1.11.1/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= -github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= -github.com/prometheus/client_golang v1.13.0 h1:b71QUfeo5M8gq2+evJdTPfZhYMAU0uKPkyPJ7TPsloU= -github.com/prometheus/client_golang v1.13.0/go.mod h1:vTeo+zgvILHsnnj/39Ou/1fPN5nJFOEMgftOUOmlvYQ= -github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= -github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M= -github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= -github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= -github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= -github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc= -github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4= -github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= -github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= -github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= -github.com/prometheus/common v0.37.0 h1:ccBbHCgIiT9uSoFY0vX8H3zsNR5eLt17/RQLUvn8pXE= -github.com/prometheus/common v0.37.0/go.mod h1:phzohg0JFMnBEFGxTDbfu3QyL5GI8gTQJFhYO5B3mfA= -github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= -github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= -github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= -github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= -github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= -github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= -github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= -github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= -github.com/prometheus/procfs v0.8.0 h1:ODq8ZFEaYeCaZOJlZZdJA2AbQR98dSHSM1KW/You5mo= -github.com/prometheus/procfs v0.8.0/go.mod h1:z7EfXMXOkbkqb9IINtpCn86r/to3BnA0uaxHdg830/4= -github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= -github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= -github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= -github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= -github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= -github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= -github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= -github.com/rogpeppe/go-internal v1.8.1 h1:geMPLpDpQOgVyCg5z5GoRwLHepNdb71NXb67XFkP+Eg= -github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o= -github.com/rubenv/sql-migrate v1.1.2 h1:9M6oj4e//owVVHYrFISmY9LBRw6gzkCNmD9MV36tZeQ= -github.com/rubenv/sql-migrate v1.1.2/go.mod h1:/7TZymwxN8VWumcIxw1jjHEcR1djpdkMHQPT4FWdnbQ= -github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo= -github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= -github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o= +github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg= +github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= +github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= +github.com/prometheus/common v0.66.1 h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9ZoGs= +github.com/prometheus/common v0.66.1/go.mod h1:gcaUsgf3KfRSwHY4dIMXLPV0K/Wg1oZ8+SbZk/HH/dA= +github.com/prometheus/procfs v0.17.0 h1:FuLQ+05u4ZI+SS/w9+BWEM2TXiHKsUQ9TADiRH7DuK0= +github.com/prometheus/procfs v0.17.0/go.mod h1:oPQLaDAMRbA+u8H5Pbfq+dl3VDAvHxMUOVhe0wYB2zw= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= +github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= +github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= -github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk= github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc= -github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= -github.com/segmentio/asm v1.1.3 h1:WM03sfUOENvvKexOLp+pCqgb/WDjsi7EK8gIsICtzhc= -github.com/segmentio/asm v1.1.3/go.mod h1:Ld3L4ZXGNcSLRg4JBsZ3//1+f/TjYl0Mzen/DQy1EJg= -github.com/segmentio/encoding v0.3.5 h1:UZEiaZ55nlXGDL92scoVuw00RmiRCazIEmvPSbSvt8Y= -github.com/segmentio/encoding v0.3.5/go.mod h1:n0JeuIqEQrQoPDGsjo8UNd1iA0U8d8+oHAA4E3G3OxM= -github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= -github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ= -github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= -github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ= -github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= -github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= -github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= -github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= -github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= -github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= -github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= -github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= -github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= -github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= -github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= -github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= +github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8= +github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/soheilhy/cmux v0.1.5 h1:jjzc5WVemNEDTLwv9tlmemhC73tI08BNOIGwBOo10Js= -github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= -github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= -github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= -github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= -github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= -github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= -github.com/spf13/cast v1.4.1 h1:s0hze+J0196ZfEMTs80N7UlFt0BDuQ7Q+JDnHiMWKdA= -github.com/spf13/cast v1.4.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= -github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= -github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= -github.com/spf13/cobra v1.2.1/go.mod h1:ExllRjgxM/piMAM+3tAZvg8fsklGAf3tPfi+i8t68Nk= -github.com/spf13/cobra v1.5.0 h1:X+jTBEBqF0bHN+9cSMgmfuvv2VHJ9ezmFNf9Y/XstYU= -github.com/spf13/cobra v1.5.0/go.mod h1:dWXEIy2H428czQCjInthrTRUg7yKbok+2Qi/yBIJoUM= -github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= -github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= -github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= -github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= -github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= -github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= -github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= -github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= -github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns= -github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= +github.com/soheilhy/cmux v0.1.5/go.mod h1:T7TcVDs9LWfQgPlPsdngu6I6QIoyIFZDDC6sNE1GqG0= +github.com/sosodev/duration v1.3.1 h1:qtHBDMQ6lvMQsL15g4aopM4HEfOaYuhWBw3NPTtlqq4= +github.com/sosodev/duration v1.3.1/go.mod h1:RQIBBX0+fMLc/D9+Jb/fwvVmo0eZvDDEERAikUR6SDg= +github.com/spf13/cobra v1.10.1 h1:lJeBwCfmrnXthfAupyUTzJ/J4Nc1RsHC/mSRU2dll/s= +github.com/spf13/cobra v1.10.1/go.mod h1:7SmJGaTHFVBY0jW4NXGluQoLvhqFQM+6XSKD+P4XaB0= +github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= +github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/stoewer/go-strcase v1.3.1 h1:iS0MdW+kVTxgMoE1LAZyMiYJFKlOzLooE4MxjirtkAs= +github.com/stoewer/go-strcase v1.3.1/go.mod h1:fAH5hQ5pehh+j3nZfvwdk2RgEgQjAoM8wodgtPmh1xo= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= -github.com/stretchr/objx v0.4.0 h1:M2gUjqZET1qApGOWNSnZ49BAIMX4F/1plDv3+l31EJ4= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= -github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= -github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= -github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= -github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= -github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= -github.com/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802 h1:uruHq4dN7GR16kFc5fp3d1RIYzJW5onx8Ybykw2YQFA= -github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= -github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= -github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= -github.com/urfave/cli/v2 v2.1.1/go.mod h1:SE9GqnLQmjVa0iPEY0f1w3ygNIYcIJ0OKPMoW2caLfQ= -github.com/vektah/gqlparser v1.1.2/go.mod h1:1ycwN7Ij5njmMkPPAOaRFY4rET2Enx7IkVv3vaXspKw= -github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c= -github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= -github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= -github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= -github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74= -github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= -github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 h1:eY9dn8+vbi4tKz5Qo6v2eYzo7kUS51QINcR5jNpbZS8= -github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= -github.com/xlab/treeprint v1.1.0 h1:G/1DjNkPpfZCFt9CSh6b5/nY4VimlbHF3Rh4obvtzDk= -github.com/xlab/treeprint v1.1.0/go.mod h1:gj5Gd3gPdKtR1ikdDK6fnFLdmIS0X30kTTuNd/WEJu0= -github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= -github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a h1:fZHgsYlfvtyqToslyjUt3VOPF4J7aK/3MPcK7xp3PDk= -github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a/go.mod h1:ul22v+Nro/R083muKhosV54bj5niojjWZvU8xrevuH4= -github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/tmc/grpc-websocket-proxy v0.0.0-20220101234140-673ab2c3ae75 h1:6fotK7otjonDflCTK0BCfls4SPy3NcCVb5dqqmbRknE= +github.com/tmc/grpc-websocket-proxy v0.0.0-20220101234140-673ab2c3ae75/go.mod h1:KO6IkyS8Y3j8OdNO85qEYBsRPuteD+YciPomcXdrMnk= +github.com/vektah/gqlparser/v2 v2.5.30 h1:EqLwGAFLIzt1wpx1IPpY67DwUujF1OfzgEyDsLrN6kE= +github.com/vektah/gqlparser/v2 v2.5.30/go.mod h1:D1/VCZtV3LPnQrcPBeR/q5jkSQIPti0uYCP/RI0gIeo= +github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= +github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= +github.com/xiang90/probing v0.0.0-20221125231312-a49e3df8f510 h1:S2dVYn90KE98chqDkyE9Z4N61UnQd+KOfgp5Iu53llk= +github.com/xiang90/probing v0.0.0-20221125231312-a49e3df8f510/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= +github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 h1:ilQV1hzziu+LLM3zUTJ0trRztfwgjqKnBWNtSRkbmwM= +github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78/go.mod h1:aL8wCCfTfSfmXjznFBSZNN13rSJjlIOI1fUNAtF7rmI= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= -github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= -github.com/yvasiyarov/go-metrics v0.0.0-20140926110328-57bccd1ccd43 h1:+lm10QQTNSBd8DVTNGHx7o/IKu9HYDvLMffDhbyLccI= -github.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50 h1:hlE8//ciYMztlGpl/VA+Zm1AcTPHYkHJPbHqE6WJUXE= -github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f h1:ERexzlUfuTvpE74urLSbIQW0Z/6hF9t8U4NsJLaioAY= -github.com/ziutek/mymysql v1.5.4 h1:GB0qdRGsTwQSBVYuVShFBKaXSnSnYYC2d9knnE1LHFs= -github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0= -go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= -go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= -go.etcd.io/bbolt v1.3.6 h1:/ecaJf0sk1l4l6V4awd65v2C3ILy7MSj+s/x1ADCIMU= -go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738 h1:VcrIfasaLFkyjk6KNlXQSzO+B0fZcnECiDrKJsfxka0= -go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg= -go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= -go.etcd.io/etcd/api/v3 v3.5.4 h1:OHVyt3TopwtUQ2GKdd5wu3PmmipR4FTwCqoEjSyRdIc= -go.etcd.io/etcd/api/v3 v3.5.4/go.mod h1:5GB2vv4A4AOn3yk7MftYGHkUfGtDHnEraIjym4dYz5A= -go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= -go.etcd.io/etcd/client/pkg/v3 v3.5.4 h1:lrneYvz923dvC14R54XcA7FXoZ3mlGZAgmwhfm7HqOg= -go.etcd.io/etcd/client/pkg/v3 v3.5.4/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= -go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ= -go.etcd.io/etcd/client/v2 v2.305.4 h1:Dcx3/MYyfKcPNLpR4VVQUP5KgYrBeJtktBwEKkw08Ao= -go.etcd.io/etcd/client/v3 v3.5.4 h1:p83BUL3tAYS0OT/r0qglgc3M1JjhM0diV8DSWAhVXv4= -go.etcd.io/etcd/client/v3 v3.5.4/go.mod h1:ZaRkVgBZC+L+dLCjTcF1hRXpgZXQPOvnA/Ak/gq3kiY= -go.etcd.io/etcd/pkg/v3 v3.5.4 h1:V5Dvl7S39ZDwjkKqJG2BfXgxZ3QREqqKifWQgIw5IM0= -go.etcd.io/etcd/raft/v3 v3.5.4 h1:YGrnAgRfgXloBNuqa+oBI/aRZMcK/1GS6trJePJ/Gqc= -go.etcd.io/etcd/server/v3 v3.5.4 h1:CMAZd0g8Bn5NRhynW6pKhc4FRg41/0QYy3d7aNm9874= -go.mongodb.org/mongo-driver v1.0.3/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= -go.mongodb.org/mongo-driver v1.1.1/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= -go.mongodb.org/mongo-driver v1.1.2/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= -go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= -go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= -go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= -go.opencensus.io v0.23.0 h1:gqCw0LfLxScz8irSi8exQc7fyQ0fKQU/qnC/X8+V/1M= -go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= -go.opentelemetry.io/contrib v0.20.0 h1:ubFQUn0VCZ0gPwIoJfBJVpeBlyRMxu8Mm/huKWYd9p0= -go.opentelemetry.io/contrib v0.20.0/go.mod h1:G/EtFaa6qaN7+LxqfIAT3GiZa7Wv5DTBUzl5H4LY0Kc= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.20.0 h1:sO4WKdPAudZGKPcpZT4MJn6JaDmpyLrMPDGGyA1SttE= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.20.0/go.mod h1:oVGt1LRbBOBq1A5BQLlUg9UaU/54aiHw8cgjV3aWZ/E= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.20.0 h1:Q3C9yzW6I9jqEc8sawxzxZmY48fs9u220KXq6d5s3XU= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.20.0/go.mod h1:2AboqHi0CiIZU0qwhtUfCYD1GeUzvvIXWNkhDt7ZMG4= -go.opentelemetry.io/otel v0.20.0 h1:eaP0Fqu7SXHwvjiqDq83zImeehOHX8doTvU9AwXON8g= -go.opentelemetry.io/otel v0.20.0/go.mod h1:Y3ugLH2oa81t5QO+Lty+zXf8zC9L26ax4Nzoxm/dooo= -go.opentelemetry.io/otel/exporters/otlp v0.20.0 h1:PTNgq9MRmQqqJY0REVbZFvwkYOA85vbdQU/nVfxDyqg= -go.opentelemetry.io/otel/exporters/otlp v0.20.0/go.mod h1:YIieizyaN77rtLJra0buKiNBOm9XQfkPEKBeuhoMwAM= -go.opentelemetry.io/otel/metric v0.20.0 h1:4kzhXFP+btKm4jwxpjIqjs41A7MakRFUS86bqLHTIw8= -go.opentelemetry.io/otel/metric v0.20.0/go.mod h1:598I5tYlH1vzBjn+BTuhzTCSb/9debfNp6R3s7Pr1eU= -go.opentelemetry.io/otel/oteltest v0.20.0 h1:HiITxCawalo5vQzdHfKeZurV8x7ljcqAgiWzF6Vaeaw= -go.opentelemetry.io/otel/oteltest v0.20.0/go.mod h1:L7bgKf9ZB7qCwT9Up7i9/pn0PWIa9FqQ2IQ8LoxiGnw= -go.opentelemetry.io/otel/sdk v0.20.0 h1:JsxtGXd06J8jrnya7fdI/U/MR6yXA5DtbZy+qoHQlr8= -go.opentelemetry.io/otel/sdk v0.20.0/go.mod h1:g/IcepuwNsoiX5Byy2nNV0ySUF1em498m7hBWC279Yc= -go.opentelemetry.io/otel/sdk/export/metric v0.20.0 h1:c5VRjxCXdQlx1HjzwGdQHzZaVI82b5EbBgOu2ljD92g= -go.opentelemetry.io/otel/sdk/export/metric v0.20.0/go.mod h1:h7RBNMsDJ5pmI1zExLi+bJK+Dr8NQCh0qGhm1KDnNlE= -go.opentelemetry.io/otel/sdk/metric v0.20.0 h1:7ao1wpzHRVKf0OQ7GIxiQJA6X7DLX9o14gmVon7mMK8= -go.opentelemetry.io/otel/sdk/metric v0.20.0/go.mod h1:knxiS8Xd4E/N+ZqKmUPf3gTTZ4/0TjTXukfxjzSTpHE= -go.opentelemetry.io/otel/trace v0.20.0 h1:1DL6EXUdcg95gukhuRRvLDO/4X5THh/5dIV52lqtnbw= -go.opentelemetry.io/otel/trace v0.20.0/go.mod h1:6GjCW8zgDjwGHGa6GkyeB8+/5vjT16gUEi0Nf1iBdgw= -go.opentelemetry.io/proto/otlp v0.7.0 h1:rwOQPCuKAKmwGKq2aVNnYIibI6wnV7EvzgfTCzcdGg8= -go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= -go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5 h1:+FNtrFTmVw0YZGpBGX56XDee331t6JAXeK2bcyhLOOc= -go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5/go.mod h1:nmDLcffg48OtT/PSW0Hg7FvpRQsQh5OSqIylirxKC7o= -go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= -go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= -go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= -go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= -go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= -go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= -go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= -go.uber.org/goleak v1.1.12 h1:gZAh5/EyT/HQwlpkCy6wTpqfH9H8Lz8zbm3dZh+OyzA= -go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= -go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4= -go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= -go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= -go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= -go.uber.org/zap v1.21.0 h1:WefMeulhovoZ2sYXz7st6K0sLj7bBhpiFaud4r4zST8= -go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw= -golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +go.etcd.io/bbolt v1.4.2 h1:IrUHp260R8c+zYx/Tm8QZr04CX+qWS5PGfPdevhdm1I= +go.etcd.io/bbolt v1.4.2/go.mod h1:Is8rSHO/b4f3XigBC0lL0+4FwAQv3HXEEIgFMuKHceM= +go.etcd.io/etcd/api/v3 v3.6.4 h1:7F6N7toCKcV72QmoUKa23yYLiiljMrT4xCeBL9BmXdo= +go.etcd.io/etcd/api/v3 v3.6.4/go.mod h1:eFhhvfR8Px1P6SEuLT600v+vrhdDTdcfMzmnxVXXSbk= +go.etcd.io/etcd/client/pkg/v3 v3.6.4 h1:9HBYrjppeOfFjBjaMTRxT3R7xT0GLK8EJMVC4xg6ok0= +go.etcd.io/etcd/client/pkg/v3 v3.6.4/go.mod h1:sbdzr2cl3HzVmxNw//PH7aLGVtY4QySjQFuaCgcRFAI= +go.etcd.io/etcd/client/v3 v3.6.4 h1:YOMrCfMhRzY8NgtzUsHl8hC2EBSnuqbR3dh84Uryl7A= +go.etcd.io/etcd/client/v3 v3.6.4/go.mod h1:jaNNHCyg2FdALyKWnd7hxZXZxZANb0+KGY+YQaEMISo= +go.etcd.io/etcd/pkg/v3 v3.6.4 h1:fy8bmXIec1Q35/jRZ0KOes8vuFxbvdN0aAFqmEfJZWA= +go.etcd.io/etcd/pkg/v3 v3.6.4/go.mod h1:kKcYWP8gHuBRcteyv6MXWSN0+bVMnfgqiHueIZnKMtE= +go.etcd.io/etcd/server/v3 v3.6.4 h1:LsCA7CzjVt+8WGrdsnh6RhC0XqCsLkBly3ve5rTxMAU= +go.etcd.io/etcd/server/v3 v3.6.4/go.mod h1:aYCL/h43yiONOv0QIR82kH/2xZ7m+IWYjzRmyQfnCAg= +go.etcd.io/raft/v3 v3.6.0 h1:5NtvbDVYpnfZWcIHgGRk9DyzkBIXOi8j+DDp1IcnUWQ= +go.etcd.io/raft/v3 v3.6.0/go.mod h1:nLvLevg6+xrVtHUmVaTcTz603gQPHfh7kUAwV6YpfGo= +go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= +go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 h1:q4XOmH/0opmeuJtPsbFNivyl7bCt7yRBbeEm2sC/XtQ= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0/go.mod h1:snMWehoOh2wsEwnvvwtDyFCxVeDAODenXHtn5vzrKjo= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 h1:F7Jx+6hwnZ41NSFTO5q4LYDtJRXBf2PD0rNBkeB/lus= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0/go.mod h1:UHB22Z8QsdRDrnAtX4PntOl36ajSxcdUMt1sF7Y6E7Q= +go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ= +go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.37.0 h1:Ahq7pZmv87yiyn3jeFz/LekZmPLLdKejuO3NcK9MssM= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.37.0/go.mod h1:MJTqhM0im3mRLw1i8uGHnCvUEeS7VwRyxlLC78PA18M= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.37.0 h1:EtFWSnwW9hGObjkIdmlnWSydO+Qs8OwzfzXLUPg4xOc= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.37.0/go.mod h1:QjUEoiGCPkvFZ/MjK6ZZfNOS6mfVEVKYE99dFhuN2LI= +go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE= +go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E= +go.opentelemetry.io/otel/sdk v1.37.0 h1:ItB0QUqnjesGRvNcmAcU0LyvkVyGJ2xftD29bWdDvKI= +go.opentelemetry.io/otel/sdk v1.37.0/go.mod h1:VredYzxUvuo2q3WRcDnKDjbdvmO0sCzOvVAiY+yUkAg= +go.opentelemetry.io/otel/sdk/metric v1.37.0 h1:90lI228XrB9jCMuSdA0673aubgRobVZFhbjxHHspCPc= +go.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps= +go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4= +go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0= +go.opentelemetry.io/proto/otlp v1.7.0 h1:jX1VolD6nHuFzOYso2E73H85i92Mv8JQYk0K9vz09os= +go.opentelemetry.io/proto/otlp v1.7.0/go.mod h1:fSKjH6YJ7HDlwzltzyMj036AJ3ejJLCgCSHGj4efDDo= +go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= +go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y= +go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/ratelimit v0.3.1 h1:K4qVE+byfv/B3tC+4nYWP7v/6SimcO7HzHekoMNBma0= +go.uber.org/ratelimit v0.3.1/go.mod h1:6euWsTB6U/Nb3X++xEUXA8ciPJvr19Q/0h1+oDcJhRk= +go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= +go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= +go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI= +go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU= +go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= +go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190320223903-b7391e95e576/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190617133340-57b3e21c3d56/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20200414173820-0848c9571904/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20220331220935-ae2d96664a29/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.0.0-20220924013350-4ba4fb4dd9e7 h1:WJywXQVIb56P2kAvXeMGTIgQ1ZHQxR60+F9dLsodECc= -golang.org/x/crypto v0.0.0-20220924013350-4ba4fb4dd9e7/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= -golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= -golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= -golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= -golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= -golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= -golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= -golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= -golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= -golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= -golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= -golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= -golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04= +golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0= +golang.org/x/exp v0.0.0-20250718183923-645b1fa84792 h1:R9PFI6EUdfVKgwKjZef7QIwGcBKu86OEFpJ9nUEP2l4= +golang.org/x/exp v0.0.0-20250718183923-645b1fa84792/go.mod h1:A+z0yzpGtvnG90cToK5n2tu8UJVP2XUATh+r+sfOOOc= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s= -golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180530234432-1e491301e022/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181005035420-146acd28ed58/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190320064053-1272bf9dcd53/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/mod v0.28.0 h1:gQBtGhjxykdjY9YhZpSlZIsbnaE2+PgjfLWUQTnoZ1U= +golang.org/x/mod v0.28.0/go.mod h1:yfB/L0NOf/kmEbXjzCPOx1iK1fRutOydrCMsqRhEBxI= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= -golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= -golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= -golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.0.0-20220325170049-de3da57026de/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.0.0-20220412020605-290c469a71a5/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.0.0-20220607020251-c690dde0001d/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.0.0-20220909164309-bea034e7d591/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= -golang.org/x/net v0.0.0-20220921155015-db77216a4ee9 h1:SdDGdqRuKrF2R4XGcnPzcvZ63c/55GvhoHUus0o+BNI= -golang.org/x/net v0.0.0-20220921155015-db77216a4ee9/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= -golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= -golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= -golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= -golang.org/x/oauth2 v0.0.0-20220608161450-d0670ef3b1eb/go.mod h1:jaDAt6Dkxork7LmZnYtzbRWj0W47D86a3TGe0YHBvmE= -golang.org/x/oauth2 v0.0.0-20220822191816-0ebed06d0094/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= -golang.org/x/oauth2 v0.0.0-20220909003341-f21342109be1 h1:lxqLZaMad/dJHMFZH0NiNpiEZI/nhgWhe4wgzpE+MuA= -golang.org/x/oauth2 v0.0.0-20220909003341-f21342109be1/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= -golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/net v0.46.0 h1:giFlY12I07fugqwPuWJi68oOnpfqFnJIJzaIIm2JVV4= +golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210= +golang.org/x/oauth2 v0.32.0 h1:jsCblLleRMDrxMN29H3z/k1KliIvpLgCkE6R8FXXNgY= +golang.org/x/oauth2 v0.32.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20220923202941-7f9b1623fab7 h1:ZrnxWX62AgTKOSagEqxvb3ffipvEDX2pl7E1TdqLqIc= -golang.org/x/sync v0.0.0-20220923202941-7f9b1623fab7/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190209173611-3b5209105503/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug= +golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190321052220-f7bb7a8bee54/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191002063906-3421d5a6bb1c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191022100944-742c48ecaeb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211110154304-99a53858aa08/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220328115105-d36c6a25d886/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220502124256-b6088ccd6cba/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220610221304-9f5ed59c137d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10 h1:WIoqL4EROvwiPdUtaip4VcDdpZ4kha7wBWZrbVKCIZg= -golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY= -golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ= +golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/term v0.36.0 h1:zMPR+aF8gfksFprF/Nc/rd1wRS1EI6nDBGyWAvDzx2Q= +golang.org/x/term v0.36.0/go.mod h1:Qu394IJq6V6dCBRgwqshf3mPF85AqzYEzofzRdZkWss= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= -golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20220609170525-579cf78fd858 h1:Dpdu/EMxGMFgq0CeYMh4fazTD2vtlZRYE7wyynxJb9U= -golang.org/x/time v0.0.0-20220609170525-579cf78fd858/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k= +golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM= +golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI= +golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190125232054-d66bd3c5d5a6/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= -golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190617190820-da514acc4774/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20190920225731-5eefd052ad72/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= -golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= -golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= -golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200505023115-26f46d2f7ef8/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= -golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= -golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo= -golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU= -golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.37.0 h1:DVSRzp7FwePZW356yEAChSdNcQo6Nsp+fex1SUW09lE= +golang.org/x/tools v0.37.0/go.mod h1:MBN5QPQtLMHVdvsbtarmTNukZDdgwdwlO5qGacAzF0w= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= -golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f h1:uF6paiQQebLeSXkrTqHqz0MXhXXS1KgF41eUdBNvxK0= -golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= -gomodules.xyz/jsonpatch/v2 v2.2.0 h1:4pT439QV83L+G9FkcCriY6EkpcK6r6bK+A5FBUMI7qY= -gomodules.xyz/jsonpatch/v2 v2.2.0/go.mod h1:WXp+iVDkoLQqPudfQ9GBlwB2eZ5DKOnjQZCYdOS8GPY= -google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= -google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= -google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= -google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= -google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= -google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= -google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= -google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= -google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= -google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU= -google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94= -google.golang.org/api v0.44.0/go.mod h1:EBOGZqzyhtvMDoxwS97ctnh0zUmYY6CxqXsc1AvkYD8= -google.golang.org/api v0.47.0/go.mod h1:Wbvgpq1HddcWVtzsVLyfLp8lDg6AA241LmgIL59tHXo= -google.golang.org/api v0.48.0/go.mod h1:71Pr1vy+TAZRPkPs/xlCf5SsU8WjuAWv1Pfjbtukyy4= -google.golang.org/api v0.50.0/go.mod h1:4bNT5pAuq5ji4SRZm+5QIkjny9JAyVD/3gaSihNefaw= -google.golang.org/api v0.51.0/go.mod h1:t4HdrdoNgyN5cbEfm7Lum0lcLDLiise1F8qDKX00sOU= -google.golang.org/api v0.54.0/go.mod h1:7C4bFFOvVDGXjfDTAsgGwDgAxRDeQ4X8NvUedIt6z3k= -google.golang.org/api v0.55.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE= -google.golang.org/api v0.56.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE= -google.golang.org/api v0.57.0/go.mod h1:dVPlbZyBo2/OjBpmvNdpn2GRm6rPy75jyU7bmhdrMgI= -google.golang.org/api v0.61.0/go.mod h1:xQRti5UdCmoCEqFxcz93fTl338AVqDgyaDRuOZ3hg9I= -google.golang.org/api v0.63.0/go.mod h1:gs4ij2ffTRXwuzzgJl/56BdwJaA194ijkfn++9tDuPo= -google.golang.org/api v0.67.0/go.mod h1:ShHKP8E60yPsKNw/w8w+VYaj9H6buA5UqDp8dhbQZ6g= -google.golang.org/api v0.70.0/go.mod h1:Bs4ZM2HGifEvXwd50TtW70ovgJffJYw2oRCOFU/SkfA= -google.golang.org/api v0.71.0/go.mod h1:4PyU6e6JogV1f9eA4voyrTY2batOLdgZ5qZ5HOCc4j8= -google.golang.org/api v0.74.0/go.mod h1:ZpfMZOVRMywNyvJFeqL9HRWBgAuRfSjJFpe9QtRRyDs= -google.golang.org/api v0.75.0/go.mod h1:pU9QmyHLnzlpar1Mjt4IbapUCy8J+6HD6GeELN69ljA= -google.golang.org/api v0.78.0/go.mod h1:1Sg78yoMLOhlQTeF+ARBoytAcH1NNyyl390YMy6rKmw= -google.golang.org/api v0.80.0/go.mod h1:xY3nI94gbvBrE0J6NHXhxOmW97HG7Khjkku6AFB3Hyg= -google.golang.org/api v0.84.0/go.mod h1:NTsGnUFJMYROtiquksZHBWtHfeMC7iYthki7Eq3pa8o= -google.golang.org/api v0.97.0 h1:x/vEL1XDF/2V4xzdNgFPaKHluRESo2aTsL7QzHnBtGQ= -google.golang.org/api v0.97.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s= -google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= -google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= -google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= -google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/genproto v0.0.0-20170818010345-ee236bd376b0/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= -google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= -google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= -google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= -google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210329143202-679c6ae281ee/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= -google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= -google.golang.org/genproto v0.0.0-20210513213006-bf773b8c8384/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A= -google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= -google.golang.org/genproto v0.0.0-20210604141403-392c879c8b08/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= -google.golang.org/genproto v0.0.0-20210608205507-b6d2f5bf0d7d/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= -google.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24= -google.golang.org/genproto v0.0.0-20210713002101-d411969a0d9a/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k= -google.golang.org/genproto v0.0.0-20210716133855-ce7ef5c701ea/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k= -google.golang.org/genproto v0.0.0-20210728212813-7823e685a01f/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= -google.golang.org/genproto v0.0.0-20210805201207-89edb61ffb67/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= -google.golang.org/genproto v0.0.0-20210813162853-db860fec028c/go.mod h1:cFeNkxwySK631ADgubI+/XFU/xp8FD5KIVV4rj8UC5w= -google.golang.org/genproto v0.0.0-20210821163610-241b8fcbd6c8/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= -google.golang.org/genproto v0.0.0-20210828152312-66f60bf46e71/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= -google.golang.org/genproto v0.0.0-20210831024726-fe130286e0e2/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= -google.golang.org/genproto v0.0.0-20210903162649-d08c68adba83/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= -google.golang.org/genproto v0.0.0-20210909211513-a8c4777a87af/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= -google.golang.org/genproto v0.0.0-20210924002016-3dee208752a0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20211206160659-862468c7d6e0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20211221195035-429b39de9b1c/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20220107163113-42d7afdf6368/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20220126215142-9970aeb2e350/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20220207164111-0872dc986b00/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20220218161850-94dd64e39d7c/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= -google.golang.org/genproto v0.0.0-20220222213610-43724f9ea8cf/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= -google.golang.org/genproto v0.0.0-20220304144024-325a89244dc8/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= -google.golang.org/genproto v0.0.0-20220310185008-1973136f34c6/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= -google.golang.org/genproto v0.0.0-20220324131243-acbaeb5b85eb/go.mod h1:hAL49I2IFola2sVEjAn7MEwsja0xp51I0tlGAf9hz4E= -google.golang.org/genproto v0.0.0-20220407144326-9054f6ed7bac/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= -google.golang.org/genproto v0.0.0-20220413183235-5e96e2839df9/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= -google.golang.org/genproto v0.0.0-20220414192740-2d67ff6cf2b4/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= -google.golang.org/genproto v0.0.0-20220421151946-72621c1f0bd3/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= -google.golang.org/genproto v0.0.0-20220429170224-98d788798c3e/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= -google.golang.org/genproto v0.0.0-20220505152158-f39f71e6c8f3/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= -google.golang.org/genproto v0.0.0-20220518221133-4f43b3371335/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= -google.golang.org/genproto v0.0.0-20220523171625-347a074981d8/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= -google.golang.org/genproto v0.0.0-20220608133413-ed9918b62aac/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= -google.golang.org/genproto v0.0.0-20220616135557-88e70c0c3a90/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= -google.golang.org/genproto v0.0.0-20220624142145-8cd45d7dbd1f h1:hJ/Y5SqPXbarffmAsApliUlcvMU+wScNGfyop4bZm8o= -google.golang.org/genproto v0.0.0-20220624142145-8cd45d7dbd1f/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= -google.golang.org/grpc v1.8.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= -google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= -google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= -google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= -google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= -google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= -google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= -google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= -google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= -google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= -google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= -google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= -google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= -google.golang.org/grpc v1.37.1/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= -google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= -google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= -google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= -google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= -google.golang.org/grpc v1.40.1/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= -google.golang.org/grpc v1.44.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= -google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ= -google.golang.org/grpc v1.46.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= -google.golang.org/grpc v1.46.2/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= -google.golang.org/grpc v1.47.0 h1:9n77onPX5F3qfFCqjy9dhn8PbNQsIKeVU04J9G7umt8= -google.golang.org/grpc v1.47.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= -google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= -google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= -google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= -google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= -google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= -google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= -google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= -google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= -google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= -google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= -google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= +gomodules.xyz/jsonpatch/v2 v2.5.0 h1:JELs8RLM12qJGXU4u/TO3V25KW8GreMKl9pdkk14RM0= +gomodules.xyz/jsonpatch/v2 v2.5.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY= +gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= +gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= +google.golang.org/api v0.253.0 h1:apU86Eq9Q2eQco3NsUYFpVTfy7DwemojL7LmbAj7g/I= +google.golang.org/api v0.253.0/go.mod h1:PX09ad0r/4du83vZVAaGg7OaeyGnaUmT/CYPNvtLCbw= +google.golang.org/genproto v0.0.0-20250603155806-513f23925822 h1:rHWScKit0gvAPuOnu87KpaYtjK5zBMLcULh7gxkCXu4= +google.golang.org/genproto v0.0.0-20250603155806-513f23925822/go.mod h1:HubltRL7rMh0LfnQPkMH4NPDFEWp0jw3vixw7jEM53s= +google.golang.org/genproto/googleapis/api v0.0.0-20250804133106-a7a43d27e69b h1:ULiyYQ0FdsJhwwZUwbaXpZF5yUE3h+RA+gxvBu37ucc= +google.golang.org/genproto/googleapis/api v0.0.0-20250804133106-a7a43d27e69b/go.mod h1:oDOGiMSXHL4sDTJvFvIB9nRQCGdLP1o/iVaqQK8zB+M= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251014184007-4626949a642f h1:1FTH6cpXFsENbPR5Bu8NQddPSaUUE6NA2XdZdDSAJK4= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251014184007-4626949a642f/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= +google.golang.org/grpc v1.76.0 h1:UnVkv1+uMLYXoIz6o7chp59WfQUYA2ex/BXQ9rHZu7A= +google.golang.org/grpc v1.76.0/go.mod h1:Ju12QI8M6iQJtbcsV+awF5a4hfJMLi4X0JLo94ULZ6c= +google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= +google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= -gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= -gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= -gopkg.in/h2non/gock.v1 v1.0.15 h1:SzLqcIlb/fDfg7UvukMpNcWsu7sI5tWwL+KCATZqks0= -gopkg.in/h2non/gock.v1 v1.0.15/go.mod h1:sX4zAkdYX1TRGJ2JY156cFspQn4yRWn6p9EMdODlynE= +gopkg.in/evanphx/json-patch.v4 v4.13.0 h1:czT3CmqEaQ1aanPc5SdlgQrrEIb8w/wwCvWWnfEbYzo= +gopkg.in/evanphx/json-patch.v4 v4.13.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= -gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -gopkg.in/ini.v1 v1.51.1/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -gopkg.in/ini.v1 v1.62.0 h1:duBzk771uxoUuOlyRLkHsygud9+5lrlGjdFBb4mSKDU= -gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8= -gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= -gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= -gopkg.in/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= -gopkg.in/square/go-jose.v2 v2.5.1 h1:7odma5RETjNHWJnR32wx8t+Io4djHE1PqxCFx3iiZ2w= -gopkg.in/square/go-jose.v2 v2.5.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= -gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= -gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= -gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= -gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= +gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc= +gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= -gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= -gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk= -gotest.tools/v3 v3.0.3 h1:4AuOwCGf4lLR9u3YOe2awrHygurzhO/HeQ6laiA6Sx0= -helm.sh/helm/v3 v3.10.0 h1:y/MYONZ/bsld9kHwqgBX2uPggnUr5hahpjwt9/jrHlI= -helm.sh/helm/v3 v3.10.0/go.mod h1:paPw0hO5KVfrCMbi1M8+P8xdfBri3IiJiVKATZsFR94= -honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= -honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -k8s.io/api v0.18.0/go.mod h1:q2HRQkfDzHMBZL9l/y9rH63PkQl4vae0xRT+8prbrK8= -k8s.io/api v0.25.2 h1:v6G8RyFcwf0HR5jQGIAYlvtRNrxMJQG1xJzaSeVnIS8= -k8s.io/api v0.25.2/go.mod h1:qP1Rn4sCVFwx/xIhe+we2cwBLTXNcheRyYXwajonhy0= -k8s.io/apiextensions-apiserver v0.18.0/go.mod h1:18Cwn1Xws4xnWQNC00FLq1E350b9lUF+aOdIWDOZxgo= -k8s.io/apiextensions-apiserver v0.25.2 h1:8uOQX17RE7XL02ngtnh3TgifY7EhekpK+/piwzQNnBo= -k8s.io/apiextensions-apiserver v0.25.2/go.mod h1:iRwwRDlWPfaHhuBfQ0WMa5skdQfrE18QXJaJvIDLvE8= -k8s.io/apimachinery v0.18.0/go.mod h1:9SnR/e11v5IbyPCGbvJViimtJ0SwHG4nfZFjU77ftcA= -k8s.io/apimachinery v0.25.2 h1:WbxfAjCx+AeN8Ilp9joWnyJ6xu9OMeS/fsfjK/5zaQs= -k8s.io/apimachinery v0.25.2/go.mod h1:hqqA1X0bsgsxI6dXsJ4HnNTBOmJNxyPp8dw3u2fSHwA= -k8s.io/apiserver v0.18.0/go.mod h1:3S2O6FeBBd6XTo0njUrLxiqk8GNy6wWOftjhJcXYnjw= -k8s.io/apiserver v0.25.2 h1:YePimobk187IMIdnmsMxsfIbC5p4eX3WSOrS9x6FEYw= -k8s.io/apiserver v0.25.2/go.mod h1:30r7xyQTREWCkG2uSjgjhQcKVvAAlqoD+YyrqR6Cn+I= -k8s.io/cli-runtime v0.25.2 h1:XOx+SKRjBpYMLY/J292BHTkmyDffl/qOx3YSuFZkTuc= -k8s.io/cli-runtime v0.25.2/go.mod h1:OQx3+/0st6x5YpkkJQlEWLC73V0wHsOFMC1/roxV8Oc= -k8s.io/client-go v0.18.0/go.mod h1:uQSYDYs4WhVZ9i6AIoEZuwUggLVEF64HOD37boKAtF8= -k8s.io/client-go v0.25.2 h1:SUPp9p5CwM0yXGQrwYurw9LWz+YtMwhWd0GqOsSiefo= -k8s.io/client-go v0.25.2/go.mod h1:i7cNU7N+yGQmJkewcRD2+Vuj4iz7b30kI8OcL3horQ4= -k8s.io/code-generator v0.18.0/go.mod h1:+UHX5rSbxmR8kzS+FAv7um6dtYrZokQvjHpDSYRVkTc= -k8s.io/code-generator v0.25.2 h1:qEHux0+E1c+j1MhsWn9+4Z6av8zrZBixOTPW064rSiY= -k8s.io/code-generator v0.25.2/go.mod h1:f61OcU2VqVQcjt/6TrU0sta1TA5hHkOO6ZZPwkL9Eys= -k8s.io/component-base v0.18.0/go.mod h1:u3BCg0z1uskkzrnAKFzulmYaEpZF7XC9Pf/uFyb1v2c= -k8s.io/component-base v0.25.2 h1:Nve/ZyHLUBHz1rqwkjXm/Re6IniNa5k7KgzxZpTfSQY= -k8s.io/component-base v0.25.2/go.mod h1:90W21YMr+Yjg7MX+DohmZLzjsBtaxQDDwaX4YxDkl60= -k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= -k8s.io/gengo v0.0.0-20200114144118-36b2048a9120/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= -k8s.io/gengo v0.0.0-20211129171323-c02415ce4185 h1:TT1WdmqqXareKxZ/oNXEUSwKlLiHzPMyB0t8BaFeBYI= -k8s.io/gengo v0.0.0-20211129171323-c02415ce4185/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= -k8s.io/klog v0.0.0-20181102134211-b9b56d5dfc92/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= -k8s.io/klog v0.3.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= -k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I= -k8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= -k8s.io/klog/v2 v2.80.1 h1:atnLQ121W371wYYFawwYx1aEY2eUfs4l3J72wtgAwV4= -k8s.io/klog/v2 v2.80.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= -k8s.io/kube-aggregator v0.25.2 h1:NJHDtwmQR0EfoIQ00JNT8QrBIOljojtxtpXcTQqWZeg= -k8s.io/kube-aggregator v0.25.2/go.mod h1:7N5x4bK6jyxkEYCd77mgiz2uGTwiVs18MRwLwCPeUz8= -k8s.io/kube-openapi v0.0.0-20200121204235-bf4fb3bd569c/go.mod h1:GRQhZsXIAJ1xR0C9bd8UpWHZ5plfAS9fzPjJuQ6JL3E= -k8s.io/kube-openapi v0.0.0-20220803164354-a70c9af30aea h1:3QOH5+2fGsY8e1qf+GIFpg+zw/JGNrgyZRQR7/m6uWg= -k8s.io/kube-openapi v0.0.0-20220803164354-a70c9af30aea/go.mod h1:C/N6wCaBHeBHkHUesQOQy2/MZqGgMAFPqGsGQLdbZBU= -k8s.io/kubectl v0.25.2 h1:2993lTeVimxKSWx/7z2PiJxUILygRa3tmC4QhFaeioA= -k8s.io/kubectl v0.25.2/go.mod h1:eoBGJtKUj7x38KXelz+dqVtbtbKwCqyKzJWmBHU0prg= -k8s.io/utils v0.0.0-20200324210504-a9aa75ae1b89/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew= -k8s.io/utils v0.0.0-20220922133306-665eaaec4324 h1:i+xdFemcSNuJvIfBlaYuXgRondKxK4z4prVPKzEaelI= -k8s.io/utils v0.0.0-20220922133306-665eaaec4324/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= -oras.land/oras-go v1.2.0 h1:yoKosVIbsPoFMqAIFHTnrmOuafHal+J/r+I5bdbVWu4= -oras.land/oras-go v1.2.0/go.mod h1:pFNs7oHp2dYsYMSS82HaX5l4mpnGO7hbpPN6EWH2ltc= -rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= -rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= -rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= -sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.7/go.mod h1:PHgbrJT7lCHcxMU+mDHEm+nx46H4zuuHZkDP6icnhu0= -sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.32 h1:2WjukG7txtEsbXsSKWtTibCdsyYAhcu6KFnttyDdZOQ= -sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.32/go.mod h1:fEO7lRTdivWO2qYVCVG7dEADOMo/MLDCVr8So2g88Uw= -sigs.k8s.io/controller-runtime v0.13.0 h1:iqa5RNciy7ADWnIc8QxCbOX5FEKVR3uxVxKHRMc2WIQ= -sigs.k8s.io/controller-runtime v0.13.0/go.mod h1:Zbz+el8Yg31jubvAEyglRZGdLAjplZl+PgtYNI6WNTI= -sigs.k8s.io/controller-tools v0.10.0 h1:0L5DTDTFB67jm9DkfrONgTGmfc/zYow0ZaHyppizU2U= -sigs.k8s.io/controller-tools v0.10.0/go.mod h1:uvr0EW6IsprfB0jpQq6evtKy+hHyHCXNfdWI5ONPx94= -sigs.k8s.io/gateway-api v0.5.0 h1:ze+k9fJqvmL8s1t3e4q1ST8RnN+f09dEv+gfacahlAE= -sigs.k8s.io/gateway-api v0.5.0/go.mod h1:x0AP6gugkFV8fC/oTlnOMU0pnmuzIR8LfIPRVUjxSqA= -sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 h1:iXTIw73aPyC+oRdyqqvVJuloN1p0AC/kzH07hu3NE+k= -sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= -sigs.k8s.io/kustomize/api v0.12.1 h1:7YM7gW3kYBwtKvoY216ZzY+8hM+lV53LUayghNRJ0vM= -sigs.k8s.io/kustomize/api v0.12.1/go.mod h1:y3JUhimkZkR6sbLNwfJHxvo1TCLwuwm14sCYnkH6S1s= -sigs.k8s.io/kustomize/kyaml v0.13.9 h1:Qz53EAaFFANyNgyOEJbT/yoIHygK40/ZcvU3rgry2Tk= -sigs.k8s.io/kustomize/kyaml v0.13.9/go.mod h1:QsRbD0/KcU+wdk0/L0fIp2KLnohkVzs6fQ85/nOXac4= -sigs.k8s.io/structured-merge-diff/v3 v3.0.0-20200116222232-67a7b8c61874/go.mod h1:PlARxl6Hbt/+BC80dRLi1qAmnMqwqDg62YvvVkZjemw= -sigs.k8s.io/structured-merge-diff/v3 v3.0.0/go.mod h1:PlARxl6Hbt/+BC80dRLi1qAmnMqwqDg62YvvVkZjemw= -sigs.k8s.io/structured-merge-diff/v4 v4.2.3 h1:PRbqxJClWWYMNV1dhaG4NsibJbArud9kFxnAMREiWFE= -sigs.k8s.io/structured-merge-diff/v4 v4.2.3/go.mod h1:qjx8mGObPmV2aSZepjQjbmb2ihdVs8cGKBraizNC69E= -sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= -sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= -sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= -sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= -software.sslmate.com/src/go-pkcs12 v0.0.0-20180114231543-2291e8f0f237/go.mod h1:/xvNRWUqm0+/ZMiF4EX00vrSCMsE4/NHb+Pt3freEeQ= -software.sslmate.com/src/go-pkcs12 v0.2.0 h1:nlFkj7bTysH6VkC4fGphtjXRbezREPgrHuJG20hBGPE= -software.sslmate.com/src/go-pkcs12 v0.2.0/go.mod h1:23rNcYsMabIc1otwLpTkCCPwUq6kQsTyowttG/as0kQ= +k8s.io/api v0.34.1 h1:jC+153630BMdlFukegoEL8E/yT7aLyQkIVuwhmwDgJM= +k8s.io/api v0.34.1/go.mod h1:SB80FxFtXn5/gwzCoN6QCtPD7Vbu5w2n1S0J5gFfTYk= +k8s.io/apiextensions-apiserver v0.34.1 h1:NNPBva8FNAPt1iSVwIE0FsdrVriRXMsaWFMqJbII2CI= +k8s.io/apiextensions-apiserver v0.34.1/go.mod h1:hP9Rld3zF5Ay2Of3BeEpLAToP+l4s5UlxiHfqRaRcMc= +k8s.io/apimachinery v0.34.1 h1:dTlxFls/eikpJxmAC7MVE8oOeP1zryV7iRyIjB0gky4= +k8s.io/apimachinery v0.34.1/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw= +k8s.io/apiserver v0.34.1 h1:U3JBGdgANK3dfFcyknWde1G6X1F4bg7PXuvlqt8lITA= +k8s.io/apiserver v0.34.1/go.mod h1:eOOc9nrVqlBI1AFCvVzsob0OxtPZUCPiUJL45JOTBG0= +k8s.io/client-go v0.34.1 h1:ZUPJKgXsnKwVwmKKdPfw4tB58+7/Ik3CrjOEhsiZ7mY= +k8s.io/client-go v0.34.1/go.mod h1:kA8v0FP+tk6sZA0yKLRG67LWjqufAoSHA2xVGKw9Of8= +k8s.io/component-base v0.34.1 h1:v7xFgG+ONhytZNFpIz5/kecwD+sUhVE6HU7qQUiRM4A= +k8s.io/component-base v0.34.1/go.mod h1:mknCpLlTSKHzAQJJnnHVKqjxR7gBeHRv0rPXA7gdtQ0= +k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= +k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= +k8s.io/kms v0.34.1 h1:iCFOvewDPzWM9fMTfyIPO+4MeuZ0tcZbugxLNSHFG4w= +k8s.io/kms v0.34.1/go.mod h1:s1CFkLG7w9eaTYvctOxosx88fl4spqmixnNpys0JAtM= +k8s.io/kube-aggregator v0.34.1 h1:WNLV0dVNoFKmuyvdWLd92iDSyD/TSTjqwaPj0U9XAEU= +k8s.io/kube-aggregator v0.34.1/go.mod h1:RU8j+5ERfp0h+gIvWtxRPfsa5nK7rboDm8RST8BJfYQ= +k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 h1:Y3gxNAuB0OBLImH611+UDZcmKS3g6CthxToOb37KgwE= +k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912/go.mod h1:kdmbQkyfwUagLfXIad1y2TdrjPFWp2Q89B3qkRwf/pQ= +k8s.io/utils v0.0.0-20250820121507-0af2bda4dd1d h1:wAhiDyZ4Tdtt7e46e9M5ZSAJ/MnPGPs+Ki1gHw4w1R0= +k8s.io/utils v0.0.0-20250820121507-0af2bda4dd1d/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.33.0 h1:qPrZsv1cwQiFeieFlRqT627fVZ+tyfou/+S5S0H5ua0= +sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.33.0/go.mod h1:Ve9uj1L+deCXFrPOk1LpFXqTg7LCFzFso6PA48q/XZw= +sigs.k8s.io/controller-runtime v0.22.3 h1:I7mfqz/a/WdmDCEnXmSPm8/b/yRTy6JsKKENTijTq8Y= +sigs.k8s.io/controller-runtime v0.22.3/go.mod h1:+QX1XUpTXN4mLoblf4tqr5CQcyHPAki2HLXqQMY6vh8= +sigs.k8s.io/gateway-api v1.4.0 h1:ZwlNM6zOHq0h3WUX2gfByPs2yAEsy/EenYJB78jpQfQ= +sigs.k8s.io/gateway-api v1.4.0/go.mod h1:AR5RSqciWP98OPckEjOjh2XJhAe2Na4LHyXD2FUY7Qk= +sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg= +sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= +sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= +sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= +sigs.k8s.io/structured-merge-diff/v6 v6.3.0 h1:jTijUJbW353oVOd9oTlifJqOGEkUw2jB/fXCbTiQEco= +sigs.k8s.io/structured-merge-diff/v6 v6.3.0/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE= +sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs= +sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4= +software.sslmate.com/src/go-pkcs12 v0.6.0 h1:f3sQittAeF+pao32Vb+mkli+ZyT+VwKaD014qFGq6oU= +software.sslmate.com/src/go-pkcs12 v0.6.0/go.mod h1:Qiz0EyvDRJjjxGyUQa2cCNZn/wMyzrRJ/qcDXOQazLI= diff --git a/hack/bin/tools.go b/hack/bin/tools.go deleted file mode 100644 index 18b591a8269..00000000000 --- a/hack/bin/tools.go +++ /dev/null @@ -1,35 +0,0 @@ -//go:build tools -// +build tools - -/* -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. -*/ - -// This file exists to force 'go mod' to fetch tool dependencies -// See: https://github.com/golang/go/wiki/Modules#how-can-i-track-tool-dependencies-for-a-module - -package bin - -import ( - _ "github.com/onsi/ginkgo/v2/ginkgo" - _ "k8s.io/code-generator/cmd/client-gen" - _ "k8s.io/code-generator/cmd/conversion-gen" - _ "k8s.io/code-generator/cmd/deepcopy-gen" - _ "k8s.io/code-generator/cmd/defaulter-gen" - _ "k8s.io/code-generator/cmd/informer-gen" - _ "k8s.io/code-generator/cmd/lister-gen" - _ "k8s.io/kube-openapi/cmd/openapi-gen" - _ "sigs.k8s.io/controller-tools/cmd/controller-gen" -) diff --git a/hack/boilerplate/boilerplate.generatego.txt b/hack/boilerplate-go.txt similarity index 100% rename from hack/boilerplate/boilerplate.generatego.txt rename to hack/boilerplate-go.txt diff --git a/hack/boilerplate/boilerplate.Makefile.txt b/hack/boilerplate-sh.txt similarity index 92% rename from hack/boilerplate/boilerplate.Makefile.txt rename to hack/boilerplate-sh.txt index 0a45273f9af..910b96cbe4d 100644 --- a/hack/boilerplate/boilerplate.Makefile.txt +++ b/hack/boilerplate-sh.txt @@ -1,4 +1,4 @@ -# Copyright YEAR The cert-manager Authors. +# 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. @@ -11,4 +11,3 @@ # 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. - diff --git a/hack/boilerplate/boilerplate.Dockerfile.txt b/hack/boilerplate-yaml.txt similarity index 100% rename from hack/boilerplate/boilerplate.Dockerfile.txt rename to hack/boilerplate-yaml.txt diff --git a/hack/build/.kazelcfg.json b/hack/build/.kazelcfg.json deleted file mode 100644 index 45b1b2e50a6..00000000000 --- a/hack/build/.kazelcfg.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "GoPrefix": "github.com/cert-manager/cert-manager", - "AddSourcesRules": true, - "SkippedPaths": ["_bin"] -} diff --git a/hack/build/nogo_config.json b/hack/build/nogo_config.json deleted file mode 100644 index 9c0275089d4..00000000000 --- a/hack/build/nogo_config.json +++ /dev/null @@ -1,107 +0,0 @@ -{ - "structtag": { - "exclude_files": { - "external/": "external tools don't pass vet" - } - }, - "asmdecl": { - "exclude_files": { - "external/": "external tools don't pass vet" - } - }, - "assign": { - "exclude_files": { - "external/": "external tools don't pass vet" - } - }, - "atomic": { - "exclude_files": { - "external/": "external tools don't pass vet" - } - }, - "bools": { - "exclude_files": { - "external/": "external tools don't pass vet" - } - }, - "buildtag": { - "exclude_files": { - "external/": "external tools don't pass vet" - } - }, - "cgocall": { - "exclude_files": { - "external/": "external tools don't pass vet" - } - }, - "composites": { - "exclude_files": { - "external/": "external tools don't pass vet" - } - }, - "copylocks": { - "exclude_files": { - "external/": "external tools don't pass vet" - } - }, - "httpresponse": { - "exclude_files": { - "external/": "external tools don't pass vet" - } - }, - "loopclosure": { - "exclude_files": { - "external/": "external tools don't pass vet" - } - }, - "lostcancel": { - "exclude_files": { - "external/": "external tools don't pass vet" - } - }, - "nilness": { - "exclude_files": { - "external/": "external tools don't pass vet" - } - }, - "nilfunc": { - "exclude_files": { - "external/": "external tools don't pass vet" - } - }, - "printf": { - "exclude_files": { - "external/": "external tools don't pass vet" - } - }, - "shift": { - "exclude_files": { - "external/": "external tools don't pass vet" - } - }, - "stdmethods": { - "exclude_files": { - "external/": "external tools don't pass vet" - } - }, - "tests": { - "exclude_files": { - "external/": "external tools don't pass vet" - } - }, - "unreachable": { - "exclude_files": { - "external/": "external tools don't pass vet" - } - }, - "unsafeptr": { - "exclude_files": { - "external/": "external tools don't pass vet" - } - }, - "unusedresult": { - "exclude_files": { - "external/": "external tools don't pass vet" - } - } -} diff --git a/hack/build/print-workspace-status.sh b/hack/build/print-workspace-status.sh deleted file mode 100755 index 252444b5892..00000000000 --- a/hack/build/print-workspace-status.sh +++ /dev/null @@ -1,53 +0,0 @@ -#!/usr/bin/env bash - -# Copyright 2020 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. - -# The only argument this script should ever be called with is '--verify-only' - -set -o errexit -set -o nounset -set -o pipefail - -SCRIPT_ROOT="$( cd "$( dirname "${BASH_SOURCE[0]}" )" > /dev/null && pwd )" -REPO_ROOT="$( cd "$( dirname "${BASH_SOURCE[0]}" )/../../" > /dev/null && pwd )" - -source "${SCRIPT_ROOT}/version.sh" -kube::version::get_version_vars - -APP_GIT_COMMIT=${APP_GIT_COMMIT:-$(git rev-parse HEAD)} -GIT_STATE="" -if [ ! -z "$(git status --porcelain)" ]; then - GIT_STATE="dirty" -fi - -cat < " - exit 1 -} - -go=${1:-} -controllergen=${2:-} -yq=${3:-} - -if [[ -z $go ]]; then - usage_and_exit -fi - -if [[ -z $controllergen ]]; then - usage_and_exit -fi - -if [[ -z $yq ]]; then - usage_and_exit -fi - -echo "+++ verifying that generated CRDs are up-to-date..." >&2 - -tmpdir="$(mktemp -d)" -trap 'rm -r $tmpdir' EXIT - -make PATCH_CRD_OUTPUT_DIR=$tmpdir patch-crds - -# Avoid diff -N so we handle empty files correctly -diff=$(diff -upr -x README.md -x BUILD.bazel "./deploy/crds" "$tmpdir" 2>/dev/null || true) - -if [[ -n "${diff}" ]]; then - echo "${diff}" >&2 - echo >&2 - echo "fatal: CRDs are out of date. Run 'make update-crds'" >&2 - exit 1 -fi - -echo "+++ success: generated CRDs are up-to-date" >&2 - -# Verify that CRDs don't contain status fields as that causes issues when they -# are managed by some CD tools. This check is necessary because currently -# controller-gen adds a status field that needs to be removed manually. -# See https://github.com/cert-manager/cert-manager/pull/4379 for context - -echo "+++ verifying that CRDs don't contain .status fields..." - -for file in ${tmpdir}/*.yaml; do - name=$($yq e '.metadata.name' $file) - echo "checking $name" - # Exit 1 if status is non-null - $yq e --exit-status=1 '.status==null' $file >/dev/null -done - -echo "+++ success: generated CRDs don't contain any status fields" diff --git a/hack/concat-yaml.sh b/hack/concat-yaml.sh index 9f7dff96593..b4c33c981ca 100755 --- a/hack/concat-yaml.sh +++ b/hack/concat-yaml.sh @@ -38,6 +38,7 @@ while (($#)); do # if there's at least one more file left, output the YAML file separator if [[ $# -gt 0 ]]; then + echo "" echo "---" fi done diff --git a/hack/containers/Containerfile.acmesolver b/hack/containers/Containerfile.acmesolver index 8928d472d87..4d7ef19f92a 100644 --- a/hack/containers/Containerfile.acmesolver +++ b/hack/containers/Containerfile.acmesolver @@ -1,7 +1,23 @@ +# 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. + ARG BASE_IMAGE FROM $BASE_IMAGE +LABEL org.opencontainers.image.source="https://github.com/cert-manager/cert-manager" + USER 1000 COPY acmesolver /app/cmd/acmesolver/acmesolver diff --git a/hack/containers/Containerfile.cainjector b/hack/containers/Containerfile.cainjector index f077db9c4e1..7cb978013b3 100644 --- a/hack/containers/Containerfile.cainjector +++ b/hack/containers/Containerfile.cainjector @@ -1,7 +1,23 @@ +# 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. + ARG BASE_IMAGE FROM $BASE_IMAGE +LABEL org.opencontainers.image.source="https://github.com/cert-manager/cert-manager" + USER 1000 COPY cainjector /app/cmd/cainjector/cainjector diff --git a/hack/containers/Containerfile.controller b/hack/containers/Containerfile.controller index 8dec5249d57..65866b947c2 100644 --- a/hack/containers/Containerfile.controller +++ b/hack/containers/Containerfile.controller @@ -1,7 +1,23 @@ +# 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. + ARG BASE_IMAGE FROM $BASE_IMAGE +LABEL org.opencontainers.image.source="https://github.com/cert-manager/cert-manager" + USER 1000 COPY controller /app/cmd/controller/controller diff --git a/hack/containers/Containerfile.ctl b/hack/containers/Containerfile.ctl deleted file mode 100644 index f5bfe5400a0..00000000000 --- a/hack/containers/Containerfile.ctl +++ /dev/null @@ -1,13 +0,0 @@ -ARG BASE_IMAGE - -FROM $BASE_IMAGE - -USER 1000 - -COPY ctl /app/cmd/ctl/ctl -COPY cert-manager.license /licenses/LICENSE -COPY cert-manager.licenses_notice /licenses/LICENSES - -ENTRYPOINT ["/app/cmd/ctl/ctl"] - -# vim: syntax=dockerfile diff --git a/hack/util/checkhash.sh b/hack/containers/Containerfile.startupapicheck old mode 100755 new mode 100644 similarity index 60% rename from hack/util/checkhash.sh rename to hack/containers/Containerfile.startupapicheck index bd01194ad06..2ef53bfd626 --- a/hack/util/checkhash.sh +++ b/hack/containers/Containerfile.startupapicheck @@ -1,6 +1,4 @@ -#!/usr/bin/env bash - -# Copyright 2021 The cert-manager Authors. +# 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. @@ -14,14 +12,18 @@ # See the License for the specific language governing permissions and # limitations under the License. -set -eu -o pipefail +ARG BASE_IMAGE + +FROM $BASE_IMAGE + +LABEL org.opencontainers.image.source="https://github.com/cert-manager/cert-manager" + +USER 1000 -# This script takes the hash of its first argument and verifies it against the -# hex hash given in its second argument +COPY startupapicheck /startupapicheck +COPY cert-manager.license /licenses/LICENSE +COPY cert-manager.licenses_notice /licenses/LICENSES -SHASUM=$(./hack/util/hash.sh "$1") +ENTRYPOINT ["/startupapicheck"] -if [ $SHASUM != "$2" ]; then - echo "invalid checksum for \"$1\": wanted \"$2\" but got \"$SHASUM\"" - exit 1 -fi +# vim: syntax=dockerfile diff --git a/hack/containers/Containerfile.webhook b/hack/containers/Containerfile.webhook index c97a771425f..9dee4e869f8 100644 --- a/hack/containers/Containerfile.webhook +++ b/hack/containers/Containerfile.webhook @@ -1,7 +1,23 @@ +# 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. + ARG BASE_IMAGE FROM $BASE_IMAGE +LABEL org.opencontainers.image.source="https://github.com/cert-manager/cert-manager" + USER 1000 COPY webhook /app/cmd/webhook/webhook diff --git a/hack/extractcrd/main.go b/hack/extractcrd/main.go index 312412c5ada..cec68498deb 100644 --- a/hack/extractcrd/main.go +++ b/hack/extractcrd/main.go @@ -61,6 +61,8 @@ func main() { os.Exit(1) } + outWriter := os.Stdout + docs := docSeparatorRegexp.Split(string(rawYAMLBytes), -1) decoder := crdDecoder() @@ -80,20 +82,20 @@ func main() { continue } - doc = string(strings.TrimPrefix(doc, "---")) - doc = string(strings.TrimSpace(doc)) + doc = strings.TrimPrefix(doc, "---") + doc = strings.TrimSpace(doc) if wantedCRDName == nil { if foundAny { - fmt.Println("---") + fmt.Fprintln(outWriter, "---") } - fmt.Println(doc) + fmt.Fprintln(outWriter, doc) foundAny = true continue } else { crdName := strings.ToLower(crd.Spec.Names.Plural) if crdName == *wantedCRDName { - fmt.Println(doc) + fmt.Fprintln(outWriter, doc) return } } diff --git a/hack/fetch-old-crd.sh b/hack/fetch-old-crd.sh deleted file mode 100755 index b1f38c6a75a..00000000000 --- a/hack/fetch-old-crd.sh +++ /dev/null @@ -1,57 +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 -eu -o pipefail - -# This script fetches old CRDs from GitHub releases but gracefully exits without an error -# if it encounters a 404. This handles the case where a git tag exists but no release -# exists, which would otherwise cause fetching the CRDs to fail. - -function print_help() { - echo "usage: $0 " > /dev/stderr -} - -if [[ -z "${1:-}" ]]; then - print_help - exit 1 -fi - -if [[ -z "${2:-}" ]]; then - print_help - exit 1 -fi - -url=$1 -destfile=$2 - -# make curl write to a temp file, since we don't want to write to destfile if -# we get a 404 from GitHub -outfile=$(mktemp) - -trap 'rm -f -- "$outfile"' EXIT - -STATUSCODE=$(curl --retry 3 --compressed --silent --location --output $outfile --write-out "%{http_code}" $url) - -if test $STATUSCODE -eq 404; then - # If a tag exists without a release, then we'll get a 404 here. This could happen during a release, for example. - # In this case, we don't error and don't write anything to destfile - exit 0 -elif test $STATUSCODE -ne 200; then - echo "Got status code $STATUSCODE for '$url' - possibly broken or in-progress release / GitHub down / rate limit" > /dev/stderr - exit 1 -fi - -cp $outfile $destfile diff --git a/hack/k8s-codegen.sh b/hack/k8s-codegen.sh index 4aa3a24dd39..43f6cec7582 100755 --- a/hack/k8s-codegen.sh +++ b/hack/k8s-codegen.sh @@ -18,55 +18,47 @@ set -o errexit set -o nounset set -o pipefail -go=$1 +clientgen=$1 +deepcopygen=$2 +informergen=$3 +listergen=$4 +defaultergen=$5 +conversiongen=$6 +openapigen=$7 +applyconfigurationgen=$8 -clientgen=$2 -deepcopygen=$3 -informergen=$4 -listergen=$5 -defaultergen=$6 -conversiongen=$7 - -# If the envvar "VERIFY_ONLY" is set, we only check if everything's up to date -# and don't actually generate anything - -VERIFY_FLAGS="" -VERB="Generating" - -if [[ ${VERIFY_ONLY:-} ]]; then - VERIFY_FLAGS="--verify-only" - VERB="Verifying" -fi - -export VERIFY_FLAGS -export VERB - -echo "+++ ${VERB} code..." >&2 +echo "+++ Generating code..." >&2 module_name="github.com/cert-manager/cert-manager" # Generate deepcopy functions for all internal and external APIs deepcopy_inputs=( - internal/apis/certmanager/v1alpha2 \ - internal/apis/certmanager/v1alpha3 \ - internal/apis/certmanager/v1beta1 \ pkg/apis/certmanager/v1 \ internal/apis/certmanager \ - internal/apis/acme/v1alpha2 \ - internal/apis/acme/v1alpha3 \ - internal/apis/acme/v1beta1 \ pkg/apis/acme/v1 \ internal/apis/acme \ + pkg/apis/config/cainjector/v1alpha1 \ + internal/apis/config/cainjector \ pkg/apis/config/webhook/v1alpha1 \ internal/apis/config/webhook \ + pkg/apis/config/controller/v1alpha1 \ + internal/apis/config/controller \ + pkg/apis/config/shared/v1alpha1 \ + internal/apis/config/shared \ pkg/apis/meta/v1 \ internal/apis/meta \ - pkg/webhook/handlers/testdata/apis/testgroup/v2 \ - pkg/webhook/handlers/testdata/apis/testgroup/v1 \ - pkg/webhook/handlers/testdata/apis/testgroup \ pkg/acme/webhook/apis/acme/v1alpha1 \ ) +# Used for generating apply configurations and client openapi specs. +# Separate to client_inputs because we need apply configurations for metav1, +# and client-gen has no way to exclude a input package just using markers in code. +api_inputs=( + pkg/apis/certmanager/v1 \ + pkg/apis/acme/v1 \ + pkg/apis/meta/v1 \ +) + client_subpackage="pkg/client" client_package="${module_name}/${client_subpackage}" # Generate clientsets, listers and informers for user-facing API types @@ -77,43 +69,28 @@ client_inputs=( # Generate defaulting functions to be used by the mutating webhook defaulter_inputs=( - internal/apis/certmanager/v1alpha2 \ - internal/apis/certmanager/v1alpha3 \ - internal/apis/certmanager/v1beta1 \ internal/apis/certmanager/v1 \ - internal/apis/acme/v1alpha2 \ - internal/apis/acme/v1alpha3 \ - internal/apis/acme/v1beta1 \ internal/apis/acme/v1 \ + internal/apis/config/shared/v1alpha1 \ + internal/apis/config/cainjector/v1alpha1 \ internal/apis/config/webhook/v1alpha1 \ + internal/apis/config/controller/v1alpha1 \ internal/apis/meta/v1 \ - pkg/webhook/handlers/testdata/apis/testgroup/v2 \ - pkg/webhook/handlers/testdata/apis/testgroup/v1 \ ) # Generate conversion functions to be used by the conversion webhook conversion_inputs=( - internal/apis/certmanager/v1alpha2 \ - internal/apis/certmanager/v1alpha3 \ - internal/apis/certmanager/v1beta1 \ internal/apis/certmanager/v1 \ - internal/apis/acme/v1alpha2 \ - internal/apis/acme/v1alpha3 \ - internal/apis/acme/v1beta1 \ internal/apis/acme/v1 \ + internal/apis/config/shared/v1alpha1 \ + internal/apis/config/cainjector/v1alpha1 \ internal/apis/config/webhook/v1alpha1 \ + internal/apis/config/controller/v1alpha1 \ internal/apis/meta/v1 \ - pkg/webhook/handlers/testdata/apis/testgroup/v2 \ - pkg/webhook/handlers/testdata/apis/testgroup/v1 \ ) # clean will delete files matching name in path. clean() { - if [[ ${VERIFY_ONLY:-} ]]; then - # don't delete files if we're only verifying - return 0 - fi - path=$1 name=$2 if [[ ! -d "$path" ]]; then @@ -122,118 +99,162 @@ clean() { find "$path" -name "$name" -delete } -mkcp() { - src="$1" - dst="$2" - mkdir -p "$(dirname "$dst")" - cp "$src" "$dst" +gen-openapi-acme() { + clean pkg/acme/webhook/openapi 'zz_generated.openapi.go' + echo "+++ Generating ACME openapi..." >&2 + mkdir -p hack/openapi_reports + "$openapigen" \ + --go-header-file "hack/boilerplate-go.txt" \ + --report-filename "hack/openapi_reports/acme.txt" \ + --output-dir ./pkg/acme/webhook/openapi/ \ + --output-pkg "github.com/cert-manager/cert-manager/pkg/acme/webhook/openapi" \ + --output-file zz_generated.openapi.go \ + "k8s.io/apimachinery/pkg/version" \ + "k8s.io/apimachinery/pkg/runtime" \ + "k8s.io/apimachinery/pkg/apis/meta/v1" \ + "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" \ + "github.com/cert-manager/cert-manager/pkg/acme/webhook/apis/acme/v1alpha1" } -# Export mkcp for use in sub-shells -export -f mkcp +gen-openapi-client() { + clean internal/generated/openapi 'zz_generated.openapi.go' + echo "+++ Generating client openapi..." >&2 + prefixed_inputs=( "${api_inputs[@]/#/$module_name/}" ) + "$openapigen" \ + --go-header-file "hack/boilerplate-go.txt" \ + --report-filename "hack/openapi_reports/client.txt" \ + --output-dir ./internal/generated/openapi/ \ + --output-pkg "github.com/cert-manager/cert-manager/internal/generated/openapi" \ + --output-file zz_generated.openapi.go \ + "k8s.io/api/core/v1" \ + "k8s.io/apimachinery/pkg/version" \ + "k8s.io/apimachinery/pkg/runtime" \ + "k8s.io/apimachinery/pkg/apis/meta/v1" \ + "k8s.io/apimachinery/pkg/api/resource" \ + "k8s.io/apimachinery/pkg/util/intstr" \ + "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" \ + "k8s.io/component-base/logs/api/v1" \ + "sigs.k8s.io/gateway-api/apis/v1" \ + "${prefixed_inputs[@]}" +} gen-deepcopy() { clean pkg/apis 'zz_generated.deepcopy.go' clean pkg/acme/webhook/apis 'zz_generated.deepcopy.go' clean pkg/webhook/handlers/testdata/apis 'zz_generated.deepcopy.go' - echo "+++ ${VERB} deepcopy methods..." >&2 + echo "+++ Generating deepcopy methods..." >&2 prefixed_inputs=( "${deepcopy_inputs[@]/#/$module_name/}" ) - joined=$( IFS=$','; echo "${prefixed_inputs[*]}" ) "$deepcopygen" \ - ${VERIFY_FLAGS} \ - --go-header-file hack/boilerplate/boilerplate.generatego.txt \ - --input-dirs "$joined" \ - --output-file-base zz_generated.deepcopy \ - --trim-path-prefix="$module_name" \ + --go-header-file hack/boilerplate-go.txt \ + --output-file zz_generated.deepcopy.go \ --bounding-dirs "${module_name}" \ - --output-base ./ + "${prefixed_inputs[@]}" +} + +gen-applyconfigurations() { + # This is a temporary hack to generate the schema YAMLs + # required to generate fake clientsets that actually works. + # Upstream issue: https://github.com/kubernetes/kubernetes/issues/126850 + models_schema=( "${module_name}/internal/generated/openapi/cmd/models-schema" ) + + clean "${client_subpackage}"/applyconfigurations '*.go' + echo "+++ Generating applyconfigurations..." >&2 + prefixed_inputs=( "${api_inputs[@]/#/$module_name/}" ) + "$applyconfigurationgen" \ + --go-header-file hack/boilerplate-go.txt \ + --openapi-schema <(go run "$models_schema") \ + --output-dir "${client_subpackage}"/applyconfigurations \ + --output-pkg "${client_package}"/applyconfigurations \ + "${prefixed_inputs[@]}" } gen-clientsets() { clean "${client_subpackage}"/clientset '*.go' - echo "+++ ${VERB} clientset..." >&2 + echo "+++ Generating clientsets..." >&2 prefixed_inputs=( "${client_inputs[@]/#/$module_name/}" ) joined=$( IFS=$','; echo "${prefixed_inputs[*]}" ) "$clientgen" \ - ${VERIFY_FLAGS} \ - --go-header-file hack/boilerplate/boilerplate.generatego.txt \ + --go-header-file hack/boilerplate-go.txt \ --clientset-name versioned \ + --apply-configuration-package "${client_package}"/applyconfigurations \ --input-base "" \ --input "$joined" \ - --trim-path-prefix="$module_name" \ - --output-package "${client_package}"/clientset \ - --output-base ./ + --output-dir "${client_subpackage}"/clientset \ + --output-pkg "${client_package}"/clientset } gen-listers() { clean "${client_subpackage}/listers" '*.go' - echo "+++ ${VERB} listers..." >&2 + echo "+++ Generating listers..." >&2 prefixed_inputs=( "${client_inputs[@]/#/$module_name/}" ) - joined=$( IFS=$','; echo "${prefixed_inputs[*]}" ) "$listergen" \ - ${VERIFY_FLAGS} \ - --go-header-file hack/boilerplate/boilerplate.generatego.txt \ - --input-dirs "$joined" \ - --trim-path-prefix="$module_name" \ - --output-package "${client_package}"/listers \ - --output-base ./ + --go-header-file hack/boilerplate-go.txt \ + --output-dir "${client_subpackage}"/listers \ + --output-pkg "${client_package}"/listers \ + "${prefixed_inputs[@]}" } gen-informers() { clean "${client_subpackage}"/informers '*.go' - echo "+++ ${VERB} informers..." >&2 + echo "+++ Generating informers..." >&2 prefixed_inputs=( "${client_inputs[@]/#/$module_name/}" ) - joined=$( IFS=$','; echo "${prefixed_inputs[*]}" ) "$informergen" \ - ${VERIFY_FLAGS} \ - --go-header-file hack/boilerplate/boilerplate.generatego.txt \ - --input-dirs "$joined" \ + --go-header-file hack/boilerplate-go.txt \ --versioned-clientset-package "${client_package}"/clientset/versioned \ --listers-package "${client_package}"/listers \ - --trim-path-prefix="$module_name" \ - --output-package "${client_package}"/informers \ - --output-base ./ + --output-dir "${client_subpackage}"/informers \ + --output-pkg "${client_package}"/informers \ + "${prefixed_inputs[@]}" } gen-defaulters() { clean internal/apis 'zz_generated.defaults.go' clean pkg/webhook/handlers/testdata/apis 'zz_generated.defaults.go' - echo "+++ ${VERB} defaulting functions..." >&2 - prefixed_inputs=( "${defaulter_inputs[@]/#/$module_name/}" ) - joined=$( IFS=$','; echo "${prefixed_inputs[*]}" ) + echo "+++ Generating defaulting functions..." >&2 + + DEFAULT_EXTRA_PEER_PKGS=( + github.com/cert-manager/cert-manager/internal/apis/meta \ + github.com/cert-manager/cert-manager/internal/apis/meta/v1 \ + github.com/cert-manager/cert-manager/internal/apis/config/shared \ + github.com/cert-manager/cert-manager/internal/apis/config/shared/v1alpha1 \ + github.com/cert-manager/cert-manager/pkg/apis/meta/v1 \ + github.com/cert-manager/cert-manager/pkg/apis/config/shared/v1alpha1 \ + ) + DEFAULT_PKGS=( "${defaulter_inputs[@]/#/$module_name/}" ) + "$defaultergen" \ - ${VERIFY_FLAGS} \ - --go-header-file hack/boilerplate/boilerplate.generatego.txt \ - --input-dirs "$joined" \ - --trim-path-prefix="$module_name" \ - -O zz_generated.defaults \ - --output-base ./ + --go-header-file hack/boilerplate-go.txt \ + --extra-peer-dirs "$( IFS=$','; echo "${DEFAULT_EXTRA_PEER_PKGS[*]}" )" \ + --output-file zz_generated.defaults.go \ + "${DEFAULT_PKGS[@]}" } gen-conversions() { clean internal/apis 'zz_generated.conversion.go' clean pkg/webhook/handlers/testdata/apis 'zz_generated.conversion.go' - echo "+++ ${VERB} conversion functions..." >&2 + echo "+++ Generating conversion functions..." >&2 CONVERSION_EXTRA_PEER_PKGS=( github.com/cert-manager/cert-manager/internal/apis/meta \ github.com/cert-manager/cert-manager/internal/apis/meta/v1 \ - github.com/cert-manager/cert-manager/pkg/apis/meta/v1 + github.com/cert-manager/cert-manager/internal/apis/config/shared \ + github.com/cert-manager/cert-manager/internal/apis/config/shared/v1alpha1 \ + github.com/cert-manager/cert-manager/pkg/apis/meta/v1 \ + github.com/cert-manager/cert-manager/pkg/apis/config/shared/v1alpha1 \ ) CONVERSION_PKGS=( "${conversion_inputs[@]/#/$module_name/}" ) "$conversiongen" \ - ${VERIFY_FLAGS} \ - --go-header-file hack/boilerplate/boilerplate.generatego.txt \ - --extra-peer-dirs $( IFS=$','; echo "${CONVERSION_EXTRA_PEER_PKGS[*]}" ) \ - --extra-dirs $( IFS=$','; echo "${CONVERSION_PKGS[*]}" ) \ - --input-dirs $( IFS=$','; echo "${CONVERSION_PKGS[*]}" ) \ - --trim-path-prefix="$module_name" \ - -O zz_generated.conversion \ - --output-base ./ + --go-header-file hack/boilerplate-go.txt \ + --extra-peer-dirs "$( IFS=$','; echo "${CONVERSION_EXTRA_PEER_PKGS[*]}" )" \ + --output-file zz_generated.conversion.go \ + "${CONVERSION_PKGS[@]}" } +gen-openapi-acme +gen-openapi-client gen-deepcopy +gen-applyconfigurations gen-clientsets gen-listers gen-informers diff --git a/hack/latest-base-images.sh b/hack/latest-base-images.sh index 6fb12c6aa1b..69c2f136160 100755 --- a/hack/latest-base-images.sh +++ b/hack/latest-base-images.sh @@ -19,17 +19,20 @@ set -eu -o pipefail # This script fetches the latest sha256 digest of each base image for each architecture we support on servers # and writes those hashes to Makefile-formatted variables for use in Makefiles. -# This in turn allows us to easily update all base images to their latest versions, while mantaining the use +# This in turn allows us to easily update all base images to their latest versions, while maintaining the use # of digests rather than tags when we refer to these base images. +CRANE=crane + TARGET=make/base_images.mk -STATIC_BASE=gcr.io/distroless/static -DYNAMIC_BASE=gcr.io/distroless/base +STATIC_BASE=gcr.io/distroless/static-debian12 +DYNAMIC_BASE=gcr.io/distroless/base-debian12 mkdir -p make -echo "# autogenerated by hack/latest-base-images.sh" > $TARGET +echo "# +skip_license_check" > $TARGET +echo "# autogenerated by hack/latest-base-images.sh" >> $TARGET echo "STATIC_BASE_IMAGE_amd64 := $STATIC_BASE@$(crane digest $STATIC_BASE:latest-amd64)" >> $TARGET echo "STATIC_BASE_IMAGE_arm64 := $STATIC_BASE@$(crane digest $STATIC_BASE:latest-arm64)" >> $TARGET diff --git a/hack/latest-kind-images.sh b/hack/latest-kind-images.sh index 6f02dc0290e..afb7b428b90 100755 --- a/hack/latest-kind-images.sh +++ b/hack/latest-kind-images.sh @@ -17,107 +17,42 @@ set -eu -o pipefail -# This script can be used to update kind images. However, you should check kind -# release notes as often specific images need to be used with a specific release -# of kind https://github.com/kubernetes-sigs/kind/releases - -export KIND_IMAGE_REPO="docker.io/kindest/node" - -CRANE=crane -TAGS=$(mktemp) - -trap 'rm -f -- "$TAGS"' EXIT - -if ! command -v $CRANE >/dev/null 2>&1; then - echo -e "Couldn't find crane. Try running:\ngo install github.com/google/go-containerregistry/cmd/crane@latest" >&2 - exit 1 -fi - -function latest_kind_tag () { - grep -E "^v$1" $TAGS | sort --version-sort | tail -1 -} - -$CRANE ls $KIND_IMAGE_REPO > $TAGS - -# the TAGS file will now look like: -# ... -# v1.19.4 -# v1.19.7 -# v1.20.0 -# v1.20.2 -# v1.20.7 -# ... - -LATEST_120_TAG=$(latest_kind_tag "1\\.20") -LATEST_121_TAG=$(latest_kind_tag "1\\.21") -LATEST_122_TAG=$(latest_kind_tag "1\\.22") -LATEST_123_TAG=$(latest_kind_tag "1\\.23") -LATEST_124_TAG=$(latest_kind_tag "1\\.24") -LATEST_125_TAG=$(latest_kind_tag "1\\.25") - - -LATEST_120_DIGEST=$(crane digest $KIND_IMAGE_REPO:$LATEST_120_TAG) -LATEST_121_DIGEST=$(crane digest $KIND_IMAGE_REPO:$LATEST_121_TAG) -LATEST_122_DIGEST=$(crane digest $KIND_IMAGE_REPO:$LATEST_122_TAG) -LATEST_123_DIGEST=$(crane digest $KIND_IMAGE_REPO:$LATEST_123_TAG) -LATEST_124_DIGEST=$(crane digest $KIND_IMAGE_REPO:$LATEST_124_TAG) -LATEST_125_DIGEST=$(crane digest $KIND_IMAGE_REPO:$LATEST_125_TAG) - -cat << EOF > ./make/kind_images.sh -# Copyright 2022 The cert-manager Authors. +# This script is used to update kind node image digests in the file: +# ./make/kind_images.sh. # -# 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 +# Each Kind release is accompanied by a set of compatible "node" images, for a +# range of different Kubernetes versions. +# The digests of these compatible node images are included in the release notes +# on GitHub. They look like: +# kindest/node:${K8S_VERSION}@sha256:${DIGEST} # -# 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. +# This script parses the GitHub release notes, extracts the `kindest/node` image +# references, and saves them to a shell script in the form of environment +# variables so that the script can be sourced by other scripts which pull +# the images and execute `kind`. +# This mechanism is fragile and depends on the Kind release manager using a +# consistent form for the release notes. +# It can be made more robust if / when Kind +# [provide machine-readable list of images for release](https://github.com/kubernetes-sigs/kind/issues/2376). -# generated by $0 +# kind version is maintained by Renovate using a custom regex manager +kind_version=v0.30.0 -KIND_IMAGE_K8S_120=$KIND_IMAGE_REPO@$LATEST_120_DIGEST -KIND_IMAGE_K8S_121=$KIND_IMAGE_REPO@$LATEST_121_DIGEST -KIND_IMAGE_K8S_122=$KIND_IMAGE_REPO@$LATEST_122_DIGEST -KIND_IMAGE_K8S_123=$KIND_IMAGE_REPO@$LATEST_123_DIGEST -KIND_IMAGE_K8S_124=$KIND_IMAGE_REPO@$LATEST_124_DIGEST -KIND_IMAGE_K8S_125=$KIND_IMAGE_REPO@$LATEST_125_DIGEST +cp ./hack/boilerplate-sh.txt ./make/kind_images.sh.tmp -# $KIND_IMAGE_REPO:$LATEST_120_TAG -KIND_IMAGE_SHA_K8S_120=$LATEST_120_DIGEST +cat << EOF >> ./make/kind_images.sh.tmp -# $KIND_IMAGE_REPO:$LATEST_121_TAG -KIND_IMAGE_SHA_K8S_121=$LATEST_121_DIGEST - -# $KIND_IMAGE_REPO:$LATEST_122_TAG -KIND_IMAGE_SHA_K8S_122=$LATEST_122_DIGEST - -# $KIND_IMAGE_REPO:$LATEST_123_TAG -KIND_IMAGE_SHA_K8S_123=$LATEST_123_DIGEST - -# $KIND_IMAGE_REPO:$LATEST_124_TAG -KIND_IMAGE_SHA_K8S_124=$LATEST_124_DIGEST - -# $KIND_IMAGE_REPO:$LATEST_125_TAG -KIND_IMAGE_SHA_K8S_125=$LATEST_125_DIGEST - -# note that these 'full' digests should be avoided since not all tools support them -# prefer KIND_IMAGE_K8S_*** instead -KIND_IMAGE_FULL_K8S_120=$KIND_IMAGE_REPO:$LATEST_120_TAG@$LATEST_120_DIGEST -KIND_IMAGE_FULL_K8S_121=$KIND_IMAGE_REPO:$LATEST_121_TAG@$LATEST_121_DIGEST -KIND_IMAGE_FULL_K8S_122=$KIND_IMAGE_REPO:$LATEST_122_TAG@$LATEST_122_DIGEST -KIND_IMAGE_FULL_K8S_123=$KIND_IMAGE_REPO:$LATEST_123_TAG@$LATEST_123_DIGEST -KIND_IMAGE_FULL_K8S_124=$KIND_IMAGE_REPO:$LATEST_124_TAG@$LATEST_124_DIGEST -KIND_IMAGE_FULL_K8S_125=$KIND_IMAGE_REPO:$LATEST_125_TAG@$LATEST_125_DIGEST +# generated by hack/latest-kind-images.sh from kind GH release ${kind_version} EOF -cat << EOF -# Images have been updated. -# Now check kind release notes and verify that if specific images are recommended to be used with the kind release that we are using, the script hasn't pulled in other images. -# https://github.com/kubernetes-sigs/kind/releases -EOF +curl -fsSL "https://api.github.com/repos/kubernetes-sigs/kind/releases/tags/${kind_version}" \ + | jq -r ' +[ .body | capture("- v?1\\.(?[0-9]+)(.(?[0-9]+))?: `kindest/node:v(?[^@]+)@sha256:(?[^`]+)`\r"; "g") ] + | sort_by(.minor) + | .[] + | "KIND_IMAGE_K8S_1\(.minor)=docker.io/kindest/node@sha256:\(.sha256)" +' >> ./make/kind_images.sh.tmp + +chmod +x ./make/kind_images.sh.tmp +mv ./make/kind_images.sh{.tmp,} diff --git a/hack/latest-kubebuilder-shas.sh b/hack/latest-kubebuilder-shas.sh index 8e92485a2cc..08110aafa63 100755 --- a/hack/latest-kubebuilder-shas.sh +++ b/hack/latest-kubebuilder-shas.sh @@ -23,6 +23,7 @@ set -eu -o pipefail if [ $# -ne 1 ]; then echo "error: incorrect number of args: usage ${0} " + echo "you can discover available versions by running gsutil ls gs://kubebuilder-tools" exit 1 fi @@ -32,7 +33,7 @@ version=$1 kubebuilder_tools_storage_url="https://storage.googleapis.com/kubebuilder-tools/kubebuilder-tools" -os_arches=("linux-amd64" "darwin-amd64" "darwin-arm64") +os_arches=("linux-amd64" "darwin-amd64" "darwin-arm64" "linux-arm64") output=$(printf "Kubebuilder tools SHAs for version %s:" "$version") diff --git a/hack/openapi_reports/acme.txt b/hack/openapi_reports/acme.txt new file mode 100644 index 00000000000..c63dc300d5d --- /dev/null +++ b/hack/openapi_reports/acme.txt @@ -0,0 +1,25 @@ +API rule violation: names_match,github.com/cert-manager/cert-manager/pkg/acme/webhook/apis/acme/v1alpha1,ChallengeResponse,Result +API rule violation: names_match,k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1,JSONSchemaProps,Ref +API rule violation: names_match,k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1,JSONSchemaProps,Schema +API rule violation: names_match,k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1,JSONSchemaProps,XEmbeddedResource +API rule violation: names_match,k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1,JSONSchemaProps,XIntOrString +API rule violation: names_match,k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1,JSONSchemaProps,XListMapKeys +API rule violation: names_match,k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1,JSONSchemaProps,XListType +API rule violation: names_match,k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1,JSONSchemaProps,XMapType +API rule violation: names_match,k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1,JSONSchemaProps,XPreserveUnknownFields +API rule violation: names_match,k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1,JSONSchemaProps,XValidations +API rule violation: names_match,k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1,JSONSchemaPropsOrArray,JSONSchemas +API rule violation: names_match,k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1,JSONSchemaPropsOrArray,Schema +API rule violation: names_match,k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1,JSONSchemaPropsOrBool,Allows +API rule violation: names_match,k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1,JSONSchemaPropsOrBool,Schema +API rule violation: names_match,k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1,JSONSchemaPropsOrStringArray,Property +API rule violation: names_match,k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1,JSONSchemaPropsOrStringArray,Schema +API rule violation: names_match,k8s.io/apimachinery/pkg/apis/meta/v1,APIResourceList,APIResources +API rule violation: names_match,k8s.io/apimachinery/pkg/apis/meta/v1,Duration,Duration +API rule violation: names_match,k8s.io/apimachinery/pkg/apis/meta/v1,InternalEvent,Object +API rule violation: names_match,k8s.io/apimachinery/pkg/apis/meta/v1,InternalEvent,Type +API rule violation: names_match,k8s.io/apimachinery/pkg/apis/meta/v1,MicroTime,Time +API rule violation: names_match,k8s.io/apimachinery/pkg/apis/meta/v1,StatusCause,Type +API rule violation: names_match,k8s.io/apimachinery/pkg/apis/meta/v1,Time,Time +API rule violation: names_match,k8s.io/apimachinery/pkg/runtime,Unknown,ContentEncoding +API rule violation: names_match,k8s.io/apimachinery/pkg/runtime,Unknown,ContentType diff --git a/hack/openapi_reports/client.txt b/hack/openapi_reports/client.txt new file mode 100644 index 00000000000..c1f9d0e3914 --- /dev/null +++ b/hack/openapi_reports/client.txt @@ -0,0 +1,94 @@ +API rule violation: names_match,github.com/cert-manager/cert-manager/pkg/apis/acme/v1,ACMEChallengeSolver,DNS01 +API rule violation: names_match,github.com/cert-manager/cert-manager/pkg/apis/acme/v1,ACMEChallengeSolver,HTTP01 +API rule violation: names_match,github.com/cert-manager/cert-manager/pkg/apis/acme/v1,ACMEChallengeSolverDNS01,DigitalOcean +API rule violation: names_match,github.com/cert-manager/cert-manager/pkg/apis/acme/v1,ACMEChallengeSolverDNS01,RFC2136 +API rule violation: names_match,github.com/cert-manager/cert-manager/pkg/apis/acme/v1,ACMEChallengeSolverHTTP01IngressPodTemplate,ACMEChallengeSolverHTTP01IngressPodObjectMeta +API rule violation: names_match,github.com/cert-manager/cert-manager/pkg/apis/acme/v1,ACMEChallengeSolverHTTP01IngressTemplate,ACMEChallengeSolverHTTP01IngressObjectMeta +API rule violation: names_match,github.com/cert-manager/cert-manager/pkg/apis/acme/v1,ACMEExternalAccountBinding,Key +API rule violation: names_match,github.com/cert-manager/cert-manager/pkg/apis/acme/v1,ACMEIssuer,PrivateKey +API rule violation: names_match,github.com/cert-manager/cert-manager/pkg/apis/acme/v1,ACMEIssuerDNS01ProviderAcmeDNS,AccountSecret +API rule violation: names_match,github.com/cert-manager/cert-manager/pkg/apis/acme/v1,ACMEIssuerDNS01ProviderAkamai,AccessToken +API rule violation: names_match,github.com/cert-manager/cert-manager/pkg/apis/acme/v1,ACMEIssuerDNS01ProviderAkamai,ClientSecret +API rule violation: names_match,github.com/cert-manager/cert-manager/pkg/apis/acme/v1,ACMEIssuerDNS01ProviderAkamai,ClientToken +API rule violation: names_match,github.com/cert-manager/cert-manager/pkg/apis/acme/v1,ACMEIssuerDNS01ProviderAzureDNS,ClientSecret +API rule violation: names_match,github.com/cert-manager/cert-manager/pkg/apis/acme/v1,ACMEIssuerDNS01ProviderCloudDNS,ServiceAccount +API rule violation: names_match,github.com/cert-manager/cert-manager/pkg/apis/acme/v1,ACMEIssuerDNS01ProviderCloudflare,APIKey +API rule violation: names_match,github.com/cert-manager/cert-manager/pkg/apis/acme/v1,ACMEIssuerDNS01ProviderCloudflare,APIToken +API rule violation: names_match,github.com/cert-manager/cert-manager/pkg/apis/acme/v1,ACMEIssuerDNS01ProviderDigitalOcean,Token +API rule violation: names_match,github.com/cert-manager/cert-manager/pkg/apis/acme/v1,ACMEIssuerDNS01ProviderRFC2136,TSIGSecret +API rule violation: names_match,github.com/cert-manager/cert-manager/pkg/apis/acme/v1,ACMEIssuerDNS01ProviderRoute53,SecretAccessKey +API rule violation: names_match,github.com/cert-manager/cert-manager/pkg/apis/acme/v1,ACMEIssuerDNS01ProviderRoute53,SecretAccessKeyID +API rule violation: names_match,github.com/cert-manager/cert-manager/pkg/apis/acme/v1,ServiceAccountRef,TokenAudiences +API rule violation: names_match,github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1,CertificateKeystores,PKCS12 +API rule violation: names_match,github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1,CertificateSpec,URIs +API rule violation: names_match,github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1,OtherName,UTF8Value +API rule violation: names_match,github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1,ServiceAccountRef,TokenAudiences +API rule violation: names_match,github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1,VaultClientCertificateAuth,Path +API rule violation: names_match,github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1,VaultKubernetesAuth,Path +API rule violation: names_match,k8s.io/api/core/v1,AzureDiskVolumeSource,DataDiskURI +API rule violation: names_match,k8s.io/api/core/v1,ContainerStatus,LastTerminationState +API rule violation: names_match,k8s.io/api/core/v1,DaemonEndpoint,Port +API rule violation: names_match,k8s.io/api/core/v1,Event,ReportingController +API rule violation: names_match,k8s.io/api/core/v1,FCVolumeSource,WWIDs +API rule violation: names_match,k8s.io/api/core/v1,GlusterfsPersistentVolumeSource,EndpointsName +API rule violation: names_match,k8s.io/api/core/v1,GlusterfsVolumeSource,EndpointsName +API rule violation: names_match,k8s.io/api/core/v1,ISCSIPersistentVolumeSource,DiscoveryCHAPAuth +API rule violation: names_match,k8s.io/api/core/v1,ISCSIPersistentVolumeSource,SessionCHAPAuth +API rule violation: names_match,k8s.io/api/core/v1,ISCSIVolumeSource,DiscoveryCHAPAuth +API rule violation: names_match,k8s.io/api/core/v1,ISCSIVolumeSource,SessionCHAPAuth +API rule violation: names_match,k8s.io/api/core/v1,NodeSpec,DoNotUseExternalID +API rule violation: names_match,k8s.io/api/core/v1,PersistentVolumeSource,CephFS +API rule violation: names_match,k8s.io/api/core/v1,PersistentVolumeSource,StorageOS +API rule violation: names_match,k8s.io/api/core/v1,PodSpec,DeprecatedServiceAccount +API rule violation: names_match,k8s.io/api/core/v1,RBDPersistentVolumeSource,CephMonitors +API rule violation: names_match,k8s.io/api/core/v1,RBDPersistentVolumeSource,RBDImage +API rule violation: names_match,k8s.io/api/core/v1,RBDPersistentVolumeSource,RBDPool +API rule violation: names_match,k8s.io/api/core/v1,RBDPersistentVolumeSource,RadosUser +API rule violation: names_match,k8s.io/api/core/v1,RBDVolumeSource,CephMonitors +API rule violation: names_match,k8s.io/api/core/v1,RBDVolumeSource,RBDImage +API rule violation: names_match,k8s.io/api/core/v1,RBDVolumeSource,RBDPool +API rule violation: names_match,k8s.io/api/core/v1,RBDVolumeSource,RadosUser +API rule violation: names_match,k8s.io/api/core/v1,VolumeSource,CephFS +API rule violation: names_match,k8s.io/api/core/v1,VolumeSource,StorageOS +API rule violation: names_match,k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1,JSONSchemaProps,Ref +API rule violation: names_match,k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1,JSONSchemaProps,Schema +API rule violation: names_match,k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1,JSONSchemaProps,XEmbeddedResource +API rule violation: names_match,k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1,JSONSchemaProps,XIntOrString +API rule violation: names_match,k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1,JSONSchemaProps,XListMapKeys +API rule violation: names_match,k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1,JSONSchemaProps,XListType +API rule violation: names_match,k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1,JSONSchemaProps,XMapType +API rule violation: names_match,k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1,JSONSchemaProps,XPreserveUnknownFields +API rule violation: names_match,k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1,JSONSchemaProps,XValidations +API rule violation: names_match,k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1,JSONSchemaPropsOrArray,JSONSchemas +API rule violation: names_match,k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1,JSONSchemaPropsOrArray,Schema +API rule violation: names_match,k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1,JSONSchemaPropsOrBool,Allows +API rule violation: names_match,k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1,JSONSchemaPropsOrBool,Schema +API rule violation: names_match,k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1,JSONSchemaPropsOrStringArray,Property +API rule violation: names_match,k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1,JSONSchemaPropsOrStringArray,Schema +API rule violation: names_match,k8s.io/apimachinery/pkg/api/resource,Quantity,Format +API rule violation: names_match,k8s.io/apimachinery/pkg/api/resource,Quantity,d +API rule violation: names_match,k8s.io/apimachinery/pkg/api/resource,Quantity,i +API rule violation: names_match,k8s.io/apimachinery/pkg/api/resource,Quantity,s +API rule violation: names_match,k8s.io/apimachinery/pkg/api/resource,int64Amount,scale +API rule violation: names_match,k8s.io/apimachinery/pkg/api/resource,int64Amount,value +API rule violation: names_match,k8s.io/apimachinery/pkg/apis/meta/v1,APIResourceList,APIResources +API rule violation: names_match,k8s.io/apimachinery/pkg/apis/meta/v1,Duration,Duration +API rule violation: names_match,k8s.io/apimachinery/pkg/apis/meta/v1,InternalEvent,Object +API rule violation: names_match,k8s.io/apimachinery/pkg/apis/meta/v1,InternalEvent,Type +API rule violation: names_match,k8s.io/apimachinery/pkg/apis/meta/v1,MicroTime,Time +API rule violation: names_match,k8s.io/apimachinery/pkg/apis/meta/v1,StatusCause,Type +API rule violation: names_match,k8s.io/apimachinery/pkg/apis/meta/v1,Time,Time +API rule violation: names_match,k8s.io/apimachinery/pkg/runtime,Unknown,ContentEncoding +API rule violation: names_match,k8s.io/apimachinery/pkg/runtime,Unknown,ContentType +API rule violation: names_match,k8s.io/apimachinery/pkg/util/intstr,IntOrString,IntVal +API rule violation: names_match,k8s.io/apimachinery/pkg/util/intstr,IntOrString,StrVal +API rule violation: names_match,k8s.io/apimachinery/pkg/util/intstr,IntOrString,Type +API rule violation: names_match,sigs.k8s.io/gateway-api/apis/v1,GRPCAuthConfig,AllowedRequestHeaders +API rule violation: names_match,sigs.k8s.io/gateway-api/apis/v1,HTTPAuthConfig,AllowedRequestHeaders +API rule violation: names_match,sigs.k8s.io/gateway-api/apis/v1,HTTPExternalAuthFilter,ExternalAuthProtocol +API rule violation: names_match,sigs.k8s.io/gateway-api/apis/v1,HTTPExternalAuthFilter,GRPCAuthConfig +API rule violation: names_match,sigs.k8s.io/gateway-api/apis/v1,HTTPExternalAuthFilter,HTTPAuthConfig +API rule violation: streaming_list_type_json_tags,github.com/cert-manager/cert-manager/pkg/apis/acme/v1,ChallengeList,ListMeta +API rule violation: streaming_list_type_json_tags,github.com/cert-manager/cert-manager/pkg/apis/acme/v1,OrderList,ListMeta +API rule violation: streaming_list_type_json_tags,github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1,ClusterIssuerList,ListMeta +API rule violation: streaming_list_type_json_tags,github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1,IssuerList,ListMeta diff --git a/hack/prune-junit-xml/prunexml.go b/hack/prune-junit-xml/prunexml.go index a9d0b910813..287dbe77e41 100644 --- a/hack/prune-junit-xml/prunexml.go +++ b/hack/prune-junit-xml/prunexml.go @@ -37,6 +37,7 @@ import ( "flag" "fmt" "io" + "log" "os" "regexp" "strconv" @@ -92,12 +93,14 @@ type JUnitFailure struct { var fuzzNameRegex = regexp.MustCompile(`^(.*)\/fuzz_\d+$`) func main() { + logger := log.New(os.Stderr, "", 0) + maxTextSize := flag.Int("max-text-size", 1, "maximum size of attribute or text (in MB)") flag.Parse() if flag.NArg() > 0 { for _, path := range flag.Args() { - fmt.Printf("processing junit xml file : %s\n", path) + logger.Printf("processing junit xml file : %s\n", path) xmlReader, err := os.Open(path) if err != nil { panic(err) @@ -108,7 +111,7 @@ func main() { panic(err) } - pruneXML(suites, *maxTextSize*1e6) // convert MB into bytes (roughly!) + pruneXML(logger, suites, *maxTextSize*1e6) // convert MB into bytes (roughly!) xmlWriter, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666) if err != nil { @@ -119,12 +122,12 @@ func main() { if err != nil { panic(err) } - fmt.Println("done.") + logger.Println("done.") } } } -func pruneXML(suites *JUnitTestSuites, maxBytes int) { +func pruneXML(logger *log.Logger, suites *JUnitTestSuites, maxBytes int) { // filter empty testSuites filteredSuites := []JUnitTestSuite{} for _, suite := range suites.Suites { @@ -140,7 +143,6 @@ func pruneXML(suites *JUnitTestSuites, maxBytes int) { filteredTestCases := []*JUnitTestCase{} fuzzTestCases := map[string]*JUnitTestCase{} for _, testcase := range suite.TestCases { - testcase := testcase matches := fuzzNameRegex.FindStringSubmatch(testcase.Name) if len(matches) > 1 { if ftc, ok := fuzzTestCases[matches[1]]; ok { @@ -182,14 +184,14 @@ func pruneXML(suites *JUnitTestSuites, maxBytes int) { for _, testcase := range suite.TestCases { if testcase.SkipMessage != nil { if len(testcase.SkipMessage.Message) > maxBytes { - fmt.Printf("clipping skip message in test case : %s\n", testcase.Name) + logger.Printf("clipping skip message in test case : %s\n", testcase.Name) testcase.SkipMessage.Message = "[... clipped...]" + testcase.SkipMessage.Message[len(testcase.SkipMessage.Message)-maxBytes:] } } if testcase.Failure != nil { if len(testcase.Failure.Contents) > maxBytes { - fmt.Printf("clipping failure message in test case : %s\n", testcase.Name) + logger.Printf("clipping failure message in test case : %s\n", testcase.Name) testcase.Failure.Contents = "[... clipped...]" + testcase.Failure.Contents[len(testcase.Failure.Contents)-maxBytes:] } diff --git a/hack/prune-junit-xml/prunexml_test.go b/hack/prune-junit-xml/prunexml_test.go index 4b7e49d3b0e..51870aece49 100644 --- a/hack/prune-junit-xml/prunexml_test.go +++ b/hack/prune-junit-xml/prunexml_test.go @@ -24,6 +24,8 @@ package main import ( "bufio" "bytes" + "log" + "os" "strings" "testing" @@ -92,11 +94,12 @@ func TestPruneXML(t *testing.T) { ` + logger := log.New(os.Stderr, "", 0) suites, _ := fetchXML(strings.NewReader(sourceXML)) - pruneXML(suites, 32) + pruneXML(logger, suites, 32) var output bytes.Buffer writer := bufio.NewWriter(&output) _ = streamXML(writer, suites) _ = writer.Flush() - assert.Equal(t, outputXML, string(output.Bytes()), "xml was not pruned correctly") + assert.Equal(t, outputXML, output.String(), "xml was not pruned correctly") } diff --git a/hack/sha256-of-plugin-tar.sh b/hack/sha256-of-plugin-tar.sh deleted file mode 100755 index 6c702b97eb7..00000000000 --- a/hack/sha256-of-plugin-tar.sh +++ /dev/null @@ -1,27 +0,0 @@ -#!/usr/bin/env bash -# Copyright 2020 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 - -version="$1" -platforms='darwin-amd64 linux-amd64 linux-arm linux-arm64 windows-amd64' -for platform in $platforms -do - curl -sSL -O $"https://github.com/cert-manager/cert-manager/releases/download/${version}/kubectl-cert_manager-${platform}.tar.gz" - sha256sum "kubectl-cert_manager-${platform}.tar.gz" - rm "kubectl-cert_manager-${platform}.tar.gz" -done diff --git a/hack/update-codegen.sh b/hack/update-codegen.sh deleted file mode 100755 index c61697a3596..00000000000 --- a/hack/update-codegen.sh +++ /dev/null @@ -1,26 +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 - -# This file is kept as backwards-compatibility for people with muscle memory who -# type "./hack/update-codegen.sh" and expect it to work, or for third party CI pipelines. - -# This script may be removed in the future. Prefer using `make update-codegen` directly. - -make update-codegen diff --git a/hack/update-crds.sh b/hack/update-crds.sh deleted file mode 100755 index 9b1a11603a8..00000000000 --- a/hack/update-crds.sh +++ /dev/null @@ -1,26 +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 - -# This file is kept as backwards-compatibility for people with muscle memory who -# type "./hack/update-crds.sh" and expect it to work, or for third party CI pipelines. - -# This script may be removed in the future. Prefer using `make update-crds` directly. - -make update-crds diff --git a/hack/update-deps.sh b/hack/update-deps.sh deleted file mode 100755 index 50abb18afd2..00000000000 --- a/hack/update-deps.sh +++ /dev/null @@ -1,77 +0,0 @@ -#!/usr/bin/env bash - -# Copyright 2020 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. - -# NB: This script requires bazel, and is no longer supported since we no longer support bazel -# It's preserved for now but might be removed in the future - -# Update vendor and bazel rules to match go.mod -# -# Usage: -# update-deps.sh [--patch|--minor] [packages] - -set -o nounset -set -o errexit -set -o pipefail - -if [[ -n "${BUILD_WORKSPACE_DIRECTORY:-}" ]]; then # Running inside bazel - echo "Updating modules..." >&2 -elif ! command -v bazel &>/dev/null; then - echo "This script is preserved for legacy reasons and requires bazel. You shouldn't need to run this as part of your normal development workflow" >&2 - echo "If you need to run this script, install bazel from https://bazel.build" >&2 - exit 1 -else - ( - set -o xtrace - bazel run //hack:update-deps -- "$@" - ) - exit 0 -fi - -go=$(realpath "$1") -export PATH=$(dirname "$go"):$PATH -gazelle=$(realpath "$2") -kazel=$(realpath "$3") -update_bazel=( - $(realpath "$4") - "$gazelle" - "$kazel" -) -update_deps_licenses=( - $(realpath "$5") - "$go" -) - -shift 5 - -cd "$BUILD_WORKSPACE_DIRECTORY" -trap 'echo "FAILED" >&2' ERR - -# Update hack/build/repos.bzl based of the go.mod file -"$gazelle" update-repos \ - --from_file=go.mod --to_macro=hack/build/repos.bzl%go_repositories \ - --build_file_generation=on --build_file_proto_mode=disable -prune=true - -# `gazelle update-repos` adds extra unneeded entries to the -# go.sum file, run `go mod tidy` to remove them -"$go" mod tidy - -# Update Bazel (changes in hack/build/repos.bzl might affect other bazel files) -"${update_bazel[@]}" - -# Update LICENSES -"${update_deps_licenses[@]}" - -echo "SUCCESS: updated modules" diff --git a/hack/update-gofmt.sh b/hack/update-gofmt.sh deleted file mode 100755 index 91372fbef94..00000000000 --- a/hack/update-gofmt.sh +++ /dev/null @@ -1,41 +0,0 @@ -#!/usr/bin/env bash - -# Copyright 2020 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 - -if [[ -n "${BUILD_WORKSPACE_DIRECTORY:-}" ]]; then # Running inside bazel - echo "Formatting go source files..." >&2 -elif ! command -v bazel &>/dev/null; then - echo "Install bazel at https://bazel.build" >&2 - exit 1 -else - ( - set -o xtrace - bazel run //hack:update-gofmt - ) - exit 0 -fi - -gofmt=$(realpath "$1") - -cd "$BUILD_WORKSPACE_DIRECTORY" - -export GO111MODULE=on - -echo "+++ Running gofmt" -find . -type f -name '*.go' | grep -v 'vendor/' | xargs "$gofmt" -s -w diff --git a/hack/util/hash.sh b/hack/util/hash.sh index 63add100942..ec4fdeeceb6 100755 --- a/hack/util/hash.sh +++ b/hack/util/hash.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash -# Copyright 2021 The cert-manager Authors. +# 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. @@ -14,9 +14,17 @@ # See the License for the specific language governing permissions and # limitations under the License. -set -eu -o pipefail +set -o errexit +set -o nounset +set -o pipefail + +# This script is used by the $(bin_dir)/metadata/cert-manager-manifests.tar.gz.metadata.json +# and $(bin_dir)/metadata/cert-manager-server-linux-amd64.tar.gz.metadata.json Make targets. # This script is a wrapper for outputting purely the sha256 hash of the input file, # ideally in a portable way. -sha256sum $1 | cut -d" " -f1 +case "$(uname -s)" in + Darwin*) shasum -a 256 "$1";; + *) sha256sum "$1" +esac | cut -d" " -f1 \ No newline at end of file diff --git a/hack/verify-chart-version.sh b/hack/verify-chart-version.sh index b69a08ba7d5..3d481aa51f6 100755 --- a/hack/verify-chart-version.sh +++ b/hack/verify-chart-version.sh @@ -22,8 +22,8 @@ chart_tarball=${1:-} DOCKER=${DOCKER:-docker} if [ -z "${chart_tarball}" ]; then - echo "usage: $0 " - exit 1 + echo "usage: $0 " + exit 1 fi chart_dir="deploy/charts/cert-manager" @@ -36,12 +36,12 @@ trap "rm -rf ${tmpdir}" EXIT tar -C "${tmpdir}" -xvf $chart_tarball if ! ${DOCKER} run -v "${tmpdir}":/workspace --workdir /workspace \ - quay.io/helmpack/chart-testing:v3.5.1 \ + quay.io/helmpack/chart-testing:v3.7.1 \ ct lint \ - --check-version-increment=false \ - --validate-maintainers=false \ - --charts "/workspace/cert-manager" \ - --debug; then + --check-version-increment=false \ + --validate-maintainers=false \ + --charts "/workspace/cert-manager" \ + --debug; then echo "Linting failed" exit 1 fi diff --git a/hack/verify-deps-licenses.sh b/hack/verify-deps-licenses.sh deleted file mode 100755 index aa2df2f2db9..00000000000 --- a/hack/verify-deps-licenses.sh +++ /dev/null @@ -1,28 +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 - -# This file is kept as backwards-compatibility for people with muscle memory who -# type "./hack/verify-deps-licenses.sh" and expect it to work, or for third party CI pipelines. - -# The replacement make target handles only licenses and doesn't verify anything relating to bazel - -# This script may be removed in the future. Prefer using `make` directly. - -make verify-licenses diff --git a/hack/verify-deps.sh b/hack/verify-deps.sh deleted file mode 100755 index b998523183c..00000000000 --- a/hack/verify-deps.sh +++ /dev/null @@ -1,71 +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. - -# NB: This script requires bazel, and is no longer supported since we no longer support bazel -# It's preserved for now but might be removed in the future - -set -o nounset -set -o errexit -set -o pipefail - -if [[ -n "${TEST_WORKSPACE:-}" ]]; then # Running inside bazel - echo "Checking modules for changes..." >&2 -elif ! command -v bazel &>/dev/null; then - echo "This script is preserved for legacy reasons and requires bazel. You shouldn't need to run this as part of your normal development workflow" >&2 - echo "If you need to run this script, install bazel from https://bazel.build" >&2 - exit 1 -else - ( - set -o xtrace - bazel test --test_output=streamed //hack:verify-deps - ) - exit 0 -fi - -tmpfiles=$TEST_TMPDIR/files - -( - mkdir -p "$tmpfiles" - rm -f bazel-* - cp -aL "." "$tmpfiles" - export BUILD_WORKSPACE_DIRECTORY=$tmpfiles - export HOME=$(realpath "$TEST_TMPDIR/home") - unset GOPATH - go=$(realpath "$2") - export PATH=$(dirname "$go"):$PATH - "$@" -) - -( - # Remove the platform/binary for gazelle and kazel - gazelle=$(dirname "$3") - kazel=$(dirname "$4") - rm -rf {.,"$tmpfiles"}/{"$gazelle","$kazel"} -) -# Avoid diff -N so we handle empty files correctly -diff=$(diff -upr \ - -x ".git" \ - -x "bazel-*" \ - -x "_output" \ - "." "$tmpfiles" 2>/dev/null || true) - -if [[ -n "${diff}" ]]; then - echo "${diff}" >&2 - echo >&2 - echo "ERROR: modules changed. Update with ./hack/update-deps.sh" >&2 - exit 1 -fi -echo "SUCCESS: modules up-to-date" diff --git a/hack/verify-errexit.sh b/hack/verify-errexit.sh index 288cb3b2aed..cb0d122fb64 100755 --- a/hack/verify-errexit.sh +++ b/hack/verify-errexit.sh @@ -33,7 +33,7 @@ echo "+++ validating all scripts set '-o errexit'" >&2 if [ "$*" != "" ]; then args="$*" else - args=$(ls "$(pwd)" | grep -v 'bazel-' | grep -v 'external/' | grep -v 'bin' | grep -v '_bin' ) + args=$(ls "$(pwd)" | grep -v 'external/' | grep -v 'bin' | grep -v '_bin' ) fi # Gather the list of files that appear to be shell scripts. diff --git a/hack/verify-gofmt.sh b/hack/verify-gofmt.sh deleted file mode 100755 index d2ae06a344a..00000000000 --- a/hack/verify-gofmt.sh +++ /dev/null @@ -1,26 +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 - -# This file is kept as backwards-compatibility for people with muscle memory who -# type "./hack/verify-gofmt.sh" and expect it to work, or for third party CI pipelines. - -# This script may be removed in the future. Prefer using `make` directly. - -make verify-imports diff --git a/hack/verify-goimports.sh b/hack/verify-goimports.sh deleted file mode 100755 index a84bed4783b..00000000000 --- a/hack/verify-goimports.sh +++ /dev/null @@ -1,45 +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 - -if [[ -z "${1:-}" ]]; then - echo "usage: $0 " >&2 - exit 1 -fi - -goimports=$(realpath "$1") - -# passing "-local" would be ideal, but it'll conflict with auto generated files ATM -# and cause churn when we want to update those files -#common_flags="-local github.com/cert-manager/cert-manager" - -common_flags="" - -echo "+++ running goimports" >&2 - -godirs=$(make --silent print-source-dirs) - -output=$($goimports $common_flags -l $godirs) - -if [ ! -z "${output}" ]; then - echo "${output}" | sed "s/^/goimports: broken file: /" - echo "+++ goimports failed; the following command may fix:" >&2 - echo "+++ $goimports $common_flags -w $godirs" >&2 - exit 1 -fi diff --git a/hack/verify-staticcheck.sh b/hack/verify-staticcheck.sh deleted file mode 100755 index 5b2b2063421..00000000000 --- a/hack/verify-staticcheck.sh +++ /dev/null @@ -1,83 +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 - -# NB: This script requires bazel, and is no longer supported since we no longer support bazel -# We want to add something like this to make, but since this script was never part of any CI -# pipeline it's not a priority. The script is kept for backwards compatibility for now but may -# change or be removed in the future. - -# See https://github.com/cert-manager/cert-manager/pull/3037#issue-440523030 - -# Currently only works on linux/amd64, darwin/amd64. - -REPO_ROOT="$( cd "$( dirname "${BASH_SOURCE[0]}" )/../" > /dev/null && pwd )" - -# See https://staticcheck.io/docs/checks -CHECKS=( - "all" - "-S1*" # Omit code simplifications for now. - "-ST1*" # Mostly stylistic, redundant w/ golint -) -export IFS=','; checks="${CHECKS[*]}"; unset IFS - -cd "${REPO_ROOT}" - -all_packages=() -while IFS='' read -r line; do - # Prepend './' to get staticcheck to treat these as paths, not packages. - all_packages+=("./$line") -done < <( find -L . \ - \( \ - -not \( \ - \( \ - -path ./_\* -o \ - -path ./.\* -o \ - -path ./vendor \ - \) -prune \ - \) \ - \) \ - -type f \ - -name \*.go \ - | sed 's|/[^/]*$||' \ - | sed 's|^./||' \ - | LC_ALL=C sort -u \ - | grep -vE "(third_party|generated|clientset_generated|hack|/_|bazel-)" -) - -some_failed=false -while read -r error; do - # Ignore compile errors caused by lack of files due to build tags. - # TODO: Add verification for these directories. - ignore_no_files="^-: build constraints exclude all Go files in .* \(compile\)" - if [[ $error =~ $ignore_no_files ]]; then - continue - fi - - some_failed=true - file="${error%%:*}" - pkg="$(dirname "$file")" - echo "$error" -done < <(bazel run //hack/bin:staticcheck -- -checks "${checks}" "${all_packages[@]}" 2>/dev/null || true) - -if $some_failed; then - echo - echo "Staticcheck failures detected, please fix and re-run this command." - exit 1 -fi diff --git a/hack/verify-upgrade.sh b/hack/verify-upgrade.sh index be7b6ac27be..940cd10d7a3 100755 --- a/hack/verify-upgrade.sh +++ b/hack/verify-upgrade.sh @@ -22,16 +22,22 @@ SCRIPT_ROOT="$( cd "$( dirname "${BASH_SOURCE[0]}" )" > /dev/null && pwd )" export REPO_ROOT="${SCRIPT_ROOT}/.." source "${REPO_ROOT}/hack/build/version.sh" -kube::version::last_published_release - -LATEST_RELEASE="${KUBE_LAST_RELEASE}" +# This script is copied from k8s and modified. +# It helps to determine the latest published release with the given prefix +# (if INITIAL_RELEASE is not explicitly set) and also provides the latest git +# commit hash +kube::version::last_published_release ${INITIAL_RELEASE_PREFIX:-"v*"} + +if [[ -z "${INITIAL_RELEASE:-}" ]]; then + INITIAL_RELEASE="${KUBE_LAST_RELEASE}" +fi usage_and_exit() { - echo "usage: $0 " >&2 + echo "usage: $0 " >&2 exit 1 } -if [[ -z "${1:-}" || -z "${2:-}" || -z "${3:-}" ||-z "${4:-}" || -z "${5:-}" ]]; then +if [[ -z "${1:-}" || -z "${2:-}" || -z "${3:-}" ||-z "${4:-}" || -z "${5:-}" || -z "${6:-}" ]]; then usage_and_exit fi @@ -41,6 +47,8 @@ ytt=$(realpath "$3") kubectl=$(realpath "$4") cmctl=$(realpath "$5") +HOST_ARCH=$6 + # Set up a fresh kind cluster $kind delete clusters kind || : @@ -61,29 +69,30 @@ HELM_URL="https://charts.jetstack.io" # cert-manager Helm chart location HELM_CHART="cmupgradetest/cert-manager" -echo "+++ Testing upgrading from ${LATEST_RELEASE} to commit ${KUBE_GIT_COMMIT} with Helm" +echo "+++ Testing upgrading from ${INITIAL_RELEASE} to commit ${KUBE_GIT_COMMIT} with Helm" # This will target the host's helm repository cache $helm repo add cmupgradetest $HELM_URL $helm repo update -# 1. INSTALL THE LATEST PUBLISHED HELM CHART +# 1. INSTALL THE INITIAL RELEASE'S PUBLISHED HELM CHART -echo "+++ Installing cert-manager ${LATEST_RELEASE} Helm chart into the cluster..." +echo "+++ Installing cert-manager ${INITIAL_RELEASE} Helm chart into the cluster..." # Upgrade or install latest published cert-manager Helm release +# We use the deprecated installCRDs=true value, to make the install work for older versions of cert-manager $helm upgrade \ --install \ --wait \ --namespace "${NAMESPACE}" \ --set installCRDs=true \ --create-namespace \ - --version "${LATEST_RELEASE}" \ + --version "${INITIAL_RELEASE}" \ "$RELEASE_NAME" \ "$HELM_CHART" # Wait for the cert-manager api to be available -$cmctl check api --wait=2m -v +$cmctl check api --wait=2m -v=5 echo "+++ Creating some cert-manager resources.." @@ -95,11 +104,11 @@ $kubectl wait --for=condition=Ready cert/test1 --timeout=180s # 2. BUILD AND UPGRADE TO HELM CHART FROM THE CURRENT MASTER -# e2e-setup-certamanager both builds and deploys the latest available chart based on the current checkout +# e2e-setup-certmanager both builds and deploys the latest available chart based on the current checkout make e2e-setup-certmanager # Wait for the cert-manager api to be available -$cmctl check api --wait=2m -v +$cmctl check api --wait=2m -v=5 # Test that the existing cert-manager resources can still be retrieved $kubectl get issuer/selfsigned-issuer cert/test1 @@ -124,18 +133,19 @@ $helm uninstall \ $kubectl delete "namespace/${NAMESPACE}" --wait + ############################################################ # VERIFY INSTALL, UPGRADE, UNINSTALL WITH STATIC MANIFESTS # ############################################################ -# 1. INSTALL THE LATEST PUBLISHED RELEASE WITH STATIC MANIFESTS +# 1. INSTALL THE INITIAL RELEASE'S STATIC MANIFESTS -echo "+++ Testing cert-manager upgrade from ${LATEST_RELEASE} to commit ${KUBE_GIT_COMMIT} using static manifests" +echo "+++ Testing cert-manager upgrade from ${INITIAL_RELEASE} to commit ${KUBE_GIT_COMMIT} using static manifests" -echo "+++ Installing cert-manager ${LATEST_RELEASE} using static manifests" +echo "+++ Installing cert-manager ${INITIAL_RELEASE} using static manifests" $kubectl apply \ - -f "https://github.com/cert-manager/cert-manager/releases/download/${LATEST_RELEASE}/cert-manager.yaml" \ + -f "https://github.com/cert-manager/cert-manager/releases/download/${INITIAL_RELEASE}/cert-manager.yaml" \ --wait $kubectl wait \ @@ -144,7 +154,7 @@ $kubectl wait \ --namespace "${NAMESPACE}" # Wait for the cert-manager api to be available -$cmctl check api --wait=2m -v +$cmctl check api --wait=2m -v=5 # Create a cert-manager issuer and cert $kubectl apply -f "${REPO_ROOT}/test/fixtures/cert-manager-resources.yaml" --selector=test="first" @@ -152,7 +162,7 @@ $kubectl apply -f "${REPO_ROOT}/test/fixtures/cert-manager-resources.yaml" --sel # Ensure cert becomes ready $kubectl wait --for=condition=Ready cert/test1 --timeout=180s -# 2. VERIFY UPGRADE TO THE LATEST BUILD FROM MASTER +# 2. VERIFY UPGRADE TO MASTER FROM THE INITIAL RELEASE MANIFEST_LOCATION=${REPO_ROOT}/_bin/yaml/cert-manager.yaml @@ -170,6 +180,7 @@ $ytt -f "${REPO_ROOT}/test/fixtures/upgrade/overlay/controller-ops.yaml" \ -f "${REPO_ROOT}/test/fixtures/upgrade/overlay/values.yaml" \ -f $MANIFEST_LOCATION \ --data-value app_version="${RELEASE_VERSION}" \ + --data-value arch="${HOST_ARCH}" \ --ignore-unknown-comments | kubectl apply -f - rollout_cmd="$kubectl rollout status deployment/cert-manager-webhook --namespace ${NAMESPACE}" @@ -186,7 +197,7 @@ until $rollout_cmd; do done # Wait for the cert-manager api to be available -$cmctl check api --wait=2m -v +$cmctl check api --wait=2m -v=5 # Test that the existing cert-manager resources can still be retrieved $kubectl get issuer/selfsigned-issuer cert/test1 @@ -204,3 +215,5 @@ $kubectl wait --for=condition=Ready cert/test2 --timeout=180s echo "+++ Uninstalling cert-manager" $kubectl delete -f $MANIFEST_LOCATION --wait + +echo "+++ Upgrade test for $INITIAL_RELEASE complete" diff --git a/hack/verify_boilerplate.py b/hack/verify_boilerplate.py deleted file mode 100755 index dc822b6b6c5..00000000000 --- a/hack/verify_boilerplate.py +++ /dev/null @@ -1,233 +0,0 @@ -#!/usr/bin/env python - -# +skip_license_check - -# Copyright 2015 The Kubernetes 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. - -# Verifies that all source files contain the necessary copyright boilerplate -# snippet. - -from __future__ import print_function - -import argparse -import datetime -import glob -import os -import re -import sys - - -def get_args(): - parser = argparse.ArgumentParser() - parser.add_argument( - "filenames", help="list of files to check, all files if unspecified", nargs='*') - - rootdir = os.path.dirname(__file__) + "/../" - rootdir = os.path.abspath(rootdir) - parser.add_argument("--rootdir", default=rootdir, - help="root directory to examine") - - default_boilerplate_dir = os.path.join(rootdir, "hack/boilerplate") - parser.add_argument("--boilerplate-dir", default=default_boilerplate_dir) - return parser.parse_args() - - -def get_refs(): - refs = {} - - for path in glob.glob(os.path.join(ARGS.boilerplate_dir, "boilerplate.*.txt")): - extension = os.path.basename(path).split(".")[1] - - ref_file = open(path, 'r', encoding="utf-8") - ref = ref_file.read().splitlines() - ref_file.close() - refs[extension] = ref - - return refs - - -def file_passes(filename, refs, regexs): # pylint: disable=too-many-locals - try: - with open(filename, 'r', encoding="utf-8") as fp: - data = fp.read() - except IOError: - return False - - if "zz_generate" in filename: - # Skip all zz_generate files - return True - - basename = os.path.basename(filename) - extension = file_extension(filename) - if extension != "": - ref = refs[extension] - else: - ref = refs[basename] - - # remove build tags from the top of Go files - if extension == "go": - con = regexs["go_build_constraints"] - (data, found) = con.subn("", data, 1) - - # remove shebang from the top of shell files - if extension == "sh" or extension == "py": - she = regexs["shebang"] - (data, found) = she.subn("", data, 1) - - data = data.splitlines() - - # if our test file is smaller than the reference it surely fails! - if len(ref) > len(data): - return False - - # trim our file to the same number of lines as the reference file - data = data[:len(ref)] - - year = regexs["year"] - for datum in data: - if year.search(datum): - return False - - # Replace all occurrences of the regex "2017|2016|2015|2014" with "YEAR" - when = regexs["date"] - for idx, datum in enumerate(data): - (data[idx], found) = when.subn('YEAR', datum) - if found != 0: - break - - # if we don't match the reference at this point, fail - if ref != data: - return False - - return True - - -def file_extension(filename): - return os.path.splitext(filename)[1].split(".")[-1].lower() - - -SKIPPED_DIRS = [ - 'Godeps', 'third_party', '_gopath', '_output', - 'external', '.git', 'vendor', '__init__.py', - 'node_modules', 'bin' -] - -# even when generated by bazel we will complain about some generated files -# not having the headers. since they're just generated, ignore them -IGNORE_HEADERS = [ - '// Code generated by', - '// +skip_license_check', - '# +skip_license_check', -] - - -def has_ignored_header(pathname): - with open(pathname, 'r', encoding="utf-8") as myfile: - try: - data = myfile.read() - except Exception as e: - # read() can fail if, e.g., the script tries to read a binary file; - # we could handle UnicodeDecodeError but if the script is recursing - # into a folder with binaries we probably want to know about it - # so print the name of the failed file and fail loudly - print("failed to read", pathname) - raise - - for header in IGNORE_HEADERS: - if header in data: - return True - return False - - -def normalize_files(files): - newfiles = [] - for pathname in files: - if any(x in pathname for x in SKIPPED_DIRS): - continue - newfiles.append(pathname) - for idx, pathname in enumerate(newfiles): - if not os.path.isabs(pathname): - newfiles[idx] = os.path.join(ARGS.rootdir, pathname) - return newfiles - - -def get_files(extensions): - files = [] - if ARGS.filenames: - files = ARGS.filenames - else: - for root, dirs, walkfiles in os.walk(ARGS.rootdir): - # don't visit certain dirs. This is just a performance improvement - # as we would prune these later in normalize_files(). But doing it - # cuts down the amount of filesystem walking we do and cuts down - # the size of the file list - for dpath in SKIPPED_DIRS: - if dpath in dirs: - dirs.remove(dpath) - - for name in walkfiles: - pathname = os.path.join(root, name) - files.append(pathname) - - files = normalize_files(files) - outfiles = [] - for pathname in files: - basename = os.path.basename(pathname) - extension = file_extension(pathname) - if extension in extensions or basename in extensions: - if not has_ignored_header(pathname): - outfiles.append(pathname) - return outfiles - -def get_dates(): - years = datetime.datetime.now().year - return '(%s)' % '|'.join((str(year) for year in range(2014, years+1))) - -def get_regexs(): - regexs = {} - # Search for "YEAR" which exists in the boilerplate, but shouldn't in the real thing - regexs["year"] = re.compile('YEAR') - # dates can be 2014, 2015, 2016 or 2017, company holder names can be anything - regexs["date"] = re.compile(get_dates()) - # strip the following build constraints/tags: - # //go:build - # // +build \n\n - regexs["go_build_constraints"] = re.compile( - r"^(//(go:build| \+build).*\n)+\n", re.MULTILINE) - # strip #!.* from shell/python scripts - regexs["shebang"] = re.compile(r"^(#!.*\n)\n*", re.MULTILINE) - return regexs - - -def main(): - regexs = get_regexs() - refs = get_refs() - filenames = get_files(refs.keys()) - nonconforming_files = [] - for filename in filenames: - if not file_passes(filename, refs, regexs): - nonconforming_files.append(filename) - - if nonconforming_files: - print('%d files have incorrect boilerplate headers:' % - len(nonconforming_files)) - for filename in sorted(nonconforming_files): - print(os.path.relpath(filename, ARGS.rootdir)) - sys.exit(1) - - -if __name__ == "__main__": - ARGS = get_args() - main() diff --git a/internal/apis/acme/doc.go b/internal/apis/acme/doc.go index 8aceeab4919..5d8a1ac9c7f 100644 --- a/internal/apis/acme/doc.go +++ b/internal/apis/acme/doc.go @@ -14,6 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ +// +kubebuilder:skip // +k8s:deepcopy-gen=package,register // Package acme is the internal version of the API. diff --git a/internal/apis/acme/fuzzer/fuzzer.go b/internal/apis/acme/fuzzer/fuzzer.go index 1b0a23ec089..337f92a640d 100644 --- a/internal/apis/acme/fuzzer/fuzzer.go +++ b/internal/apis/acme/fuzzer/fuzzer.go @@ -17,9 +17,9 @@ limitations under the License. package fuzzer import ( - fuzz "github.com/google/gofuzz" apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" runtimeserializer "k8s.io/apimachinery/pkg/runtime/serializer" + "sigs.k8s.io/randfill" "github.com/cert-manager/cert-manager/internal/apis/acme" v1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" @@ -28,21 +28,27 @@ import ( // Funcs returns the fuzzer functions for the apps api group. var Funcs = func(codecs runtimeserializer.CodecFactory) []interface{} { return []interface{}{ - func(s *acme.Order, c fuzz.Continue) { - c.FuzzNoCustom(s) // fuzz self without calling this function again + func(s *acme.Order, c randfill.Continue) { + c.FillNoCustom(s) // fuzz self without calling this function again + if s.Spec.IssuerRef.Group == "" { + s.Spec.IssuerRef.Group = "cert-manager.io" + } if s.Spec.IssuerRef.Kind == "" { s.Spec.IssuerRef.Kind = v1.IssuerKind } }, - func(s *acme.Challenge, c fuzz.Continue) { - c.FuzzNoCustom(s) // fuzz self without calling this function again + func(s *acme.Challenge, c randfill.Continue) { + c.FillNoCustom(s) // fuzz self without calling this function again + if s.Spec.IssuerRef.Group == "" { + s.Spec.IssuerRef.Group = "cert-manager.io" + } if s.Spec.IssuerRef.Kind == "" { s.Spec.IssuerRef.Kind = v1.IssuerKind } }, - func(s *apiextensionsv1.JSON, c fuzz.Continue) { + func(s *apiextensionsv1.JSON, c randfill.Continue) { // ensure the webhook's config is valid JSON s.Raw = []byte("{}") }, diff --git a/internal/apis/acme/install/install.go b/internal/apis/acme/install/install.go index b852a7c182a..c3e1815a053 100644 --- a/internal/apis/acme/install/install.go +++ b/internal/apis/acme/install/install.go @@ -23,19 +23,15 @@ import ( utilruntime "k8s.io/apimachinery/pkg/util/runtime" "github.com/cert-manager/cert-manager/internal/apis/acme" - cmapi "github.com/cert-manager/cert-manager/internal/apis/acme/v1" - "github.com/cert-manager/cert-manager/internal/apis/acme/v1alpha2" - "github.com/cert-manager/cert-manager/internal/apis/acme/v1alpha3" - "github.com/cert-manager/cert-manager/internal/apis/acme/v1beta1" + v1 "github.com/cert-manager/cert-manager/internal/apis/acme/v1" cmmetav1 "github.com/cert-manager/cert-manager/internal/apis/meta/v1" ) // Install registers the API group and adds types to a scheme func Install(scheme *runtime.Scheme) { utilruntime.Must(acme.AddToScheme(scheme)) - utilruntime.Must(v1alpha2.AddToScheme(scheme)) - utilruntime.Must(v1alpha3.AddToScheme(scheme)) - utilruntime.Must(v1beta1.AddToScheme(scheme)) - utilruntime.Must(cmapi.AddToScheme(scheme)) + // The first version in this list will be the default version used + utilruntime.Must(v1.AddToScheme(scheme)) + utilruntime.Must(cmmetav1.AddToScheme(scheme)) } diff --git a/internal/apis/acme/types_challenge.go b/internal/apis/acme/types_challenge.go index 213fe513049..21dc82f7327 100644 --- a/internal/apis/acme/types_challenge.go +++ b/internal/apis/acme/types_challenge.go @@ -52,9 +52,9 @@ type ChallengeSpec struct { // challenge is a part of. AuthorizationURL string - // dnsName is the identifier that this challenge is for, e.g. example.com. + // 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`. + // non-wildcard domain, e.g., for `*.example.com`, it must be `example.com`. DNSName string // wildcard will be true if this challenge is for a wildcard identifier, @@ -87,7 +87,7 @@ type ChallengeSpec struct { // 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. - IssuerRef cmmeta.ObjectReference + IssuerRef cmmeta.IssuerReference } // The type of ACME challenge. Only HTTP-01 and DNS-01 are supported. diff --git a/internal/apis/acme/types_issuer.go b/internal/apis/acme/types_issuer.go index 8175f86bcf0..7f916fac004 100644 --- a/internal/apis/acme/types_issuer.go +++ b/internal/apis/acme/types_issuer.go @@ -19,7 +19,7 @@ package acme import ( corev1 "k8s.io/api/core/v1" apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" - gwapi "sigs.k8s.io/gateway-api/apis/v1alpha2" + gwapi "sigs.k8s.io/gateway-api/apis/v1" cmmeta "github.com/cert-manager/cert-manager/internal/apis/meta" ) @@ -45,16 +45,26 @@ type ACMEIssuer struct { // 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: + // For example, for Let's Encrypt's DST cross-sign you would use: // "DST Root CA X3" or "ISRG Root X1" for the newer Let's Encrypt root CA. PreferredChain string - // Enables or disables validation of the ACME server TLS certificate. - // If true, requests to the ACME server will not have their TLS certificate - // validated (i.e. insecure connections will be allowed). + // 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. + CABundle []byte + + // 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. - // The cert-manager system installed roots will be used to verify connections - // to the ACME server if this is false. + // If CABundle and SkipTLSVerify are unset, the system certificate bundle inside + // the container is used to validate the TLS connection. // Defaults to false. SkipTLSVerify bool @@ -89,9 +99,13 @@ type ACMEIssuer struct { // 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. + // it, it will create an error on the Order. // Defaults to false. EnableDurationFeature bool + + // Profile allows requesting a certificate profile from the ACME server. + // Supported profiles are listed by the server's ACME directory URL. + Profile string `json:"profile,omitempty"` } // ACMEExternalAccountBinding is a reference to a CA external account of the ACME @@ -138,7 +152,7 @@ type ACMEChallengeSolver struct { // 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. + // (e.g., `*.example.com`) using the HTTP01 challenge mechanism. HTTP01 *ACMEChallengeSolverHTTP01 // Configures cert-manager to attempt to complete authorizations by @@ -202,16 +216,24 @@ type ACMEChallengeSolverHTTP01Ingress struct { // +optional ServiceType corev1.ServiceType - // 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 `ingressClassName` when creating Ingress + // resources 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. + IngressClassName *string + + // 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. Class *string // 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. + // ingress resources. Only one of `class`, `name` or `ingressClassName` may + // be specified. Name string // Optional pod template used to configure the ACME challenge solver pods @@ -239,6 +261,10 @@ type ACMEChallengeSolverHTTP01GatewayHTTPRoute struct { // the HTTPRoute. Usually, the parentRef references a Gateway. See: // https://gateway-api.sigs.k8s.io/v1alpha2/api-types/httproute/#attaching-to-gateways ParentRefs []gwapi.ParentReference + + // Optional pod template used to configure the ACME challenge solver pods + // used for HTTP01 challenges + PodTemplate *ACMEChallengeSolverHTTP01IngressPodTemplate } type ACMEChallengeSolverHTTP01IngressPodTemplate struct { @@ -250,14 +276,15 @@ type ACMEChallengeSolverHTTP01IngressPodTemplate struct { // PodSpec defines overrides for the HTTP01 challenge solver pod. // Only the 'priorityClassName', 'nodeSelector', 'affinity', - // 'serviceAccountName' and 'tolerations' fields are supported currently. + // 'serviceAccountName', 'tolerations', 'imagePullSecrets', 'securityContext', + // and 'resources' fields are supported currently. // All other fields will be ignored. // +optional Spec ACMEChallengeSolverHTTP01IngressPodSpec } type ACMEChallengeSolverHTTP01IngressPodObjectMeta struct { - // Annotations that should be added to the create ACME HTTP01 solver pods. + // Annotations that should be added to the created ACME HTTP01 solver pods. Annotations map[string]string // Labels that should be added to the created ACME HTTP01 solver pods. @@ -282,6 +309,23 @@ type ACMEChallengeSolverHTTP01IngressPodSpec struct { // If specified, the pod's service account // +optional ServiceAccountName string + + // If specified, the pod's imagePullSecrets + // +optional + ImagePullSecrets []corev1.LocalObjectReference `json:"imagePullSecrets,omitempty" patchMergeKey:"name" patchStrategy:"merge"` + + // If specified, the pod's security context + // +optional + SecurityContext *ACMEChallengeSolverHTTP01IngressPodSecurityContext `json:"securityContext,omitempty"` + + // If specified, the pod's resource requirements. + // These values override the global resource configuration flags. + // Note that when only specifying resource limits, ensure they are greater than or equal + // to the corresponding global resource requests configured via controller flags + // (--acme-http01-solver-resource-request-cpu, --acme-http01-solver-resource-request-memory). + // Kubernetes will reject pod creation if limits are lower than requests, causing challenge failures. + // +optional + Resources *ACMEChallengeSolverHTTP01IngressPodResources `json:"resources,omitempty"` } type ACMEChallengeSolverHTTP01IngressTemplate struct { @@ -339,6 +383,95 @@ type ACMEChallengeSolverDNS01 struct { Webhook *ACMEIssuerDNS01ProviderWebhook } +type ACMEChallengeSolverHTTP01IngressPodSecurityContext struct { + // The SELinux context to be applied to all containers. + // If unspecified, the container runtime will allocate a random SELinux context for each + // container. May also be set in SecurityContext. If set in + // both SecurityContext and PodSecurityContext, the value specified in SecurityContext + // takes precedence for that container. + // Note that this field cannot be set when spec.os.name is windows. + // +optional + SELinuxOptions *corev1.SELinuxOptions `json:"seLinuxOptions,omitempty"` + // The UID to run the entrypoint of the container process. + // Defaults to user specified in image metadata if unspecified. + // May also be set in SecurityContext. If set in both SecurityContext and + // PodSecurityContext, the value specified in SecurityContext takes precedence + // for that container. + // Note that this field cannot be set when spec.os.name is windows. + // +optional + RunAsUser *int64 `json:"runAsUser,omitempty"` + // The GID to run the entrypoint of the container process. + // Uses runtime default if unset. + // May also be set in SecurityContext. If set in both SecurityContext and + // PodSecurityContext, the value specified in SecurityContext takes precedence + // for that container. + // Note that this field cannot be set when spec.os.name is windows. + // +optional + RunAsGroup *int64 `json:"runAsGroup,omitempty"` + // Indicates that the container must run as a non-root user. + // If true, the Kubelet will validate the image at runtime to ensure that it + // does not run as UID 0 (root) and fail to start the container if it does. + // If unset or false, no such validation will be performed. + // May also be set in SecurityContext. If set in both SecurityContext and + // PodSecurityContext, the value specified in SecurityContext takes precedence. + // +optional + RunAsNonRoot *bool `json:"runAsNonRoot,omitempty"` + // A list of groups applied to the first process run in each container, in addition + // to the container's primary GID, the fsGroup (if specified), and group memberships + // defined in the container image for the uid of the container process. If unspecified, + // no additional groups are added to any container. Note that group memberships + // defined in the container image for the uid of the container process are still effective, + // even if they are not included in this list. + // Note that this field cannot be set when spec.os.name is windows. + // +optional + SupplementalGroups []int64 `json:"supplementalGroups,omitempty"` + // A special supplemental group that applies to all containers in a pod. + // Some volume types allow the Kubelet to change the ownership of that volume + // to be owned by the pod: + // + // 1. The owning GID will be the FSGroup + // 2. The setgid bit is set (new files created in the volume will be owned by FSGroup) + // 3. The permission bits are OR'd with rw-rw---- + // + // If unset, the Kubelet will not modify the ownership and permissions of any volume. + // Note that this field cannot be set when spec.os.name is windows. + // +optional + FSGroup *int64 `json:"fsGroup,omitempty"` + // Sysctls hold a list of namespaced sysctls used for the pod. Pods with unsupported + // sysctls (by the container runtime) might fail to launch. + // Note that this field cannot be set when spec.os.name is windows. + // +optional + Sysctls []corev1.Sysctl `json:"sysctls,omitempty"` + // fsGroupChangePolicy defines behavior of changing ownership and permission of the volume + // before being exposed inside Pod. This field will only apply to + // volume types which support fsGroup based ownership(and permissions). + // It will have no effect on ephemeral volume types such as: secret, configmaps + // and emptydir. + // Valid values are "OnRootMismatch" and "Always". If not specified, "Always" is used. + // Note that this field cannot be set when spec.os.name is windows. + // +optional + FSGroupChangePolicy *corev1.PodFSGroupChangePolicy `json:"fsGroupChangePolicy,omitempty"` + // The seccomp options to use by the containers in this pod. + // Note that this field cannot be set when spec.os.name is windows. + // +optional + SeccompProfile *corev1.SeccompProfile `json:"seccompProfile,omitempty"` +} + +// ACMEChallengeSolverHTTP01IngressPodResources defines resource requirements for ACME HTTP01 solver pods. +// To keep API surface essential, this trims down the 'corev1.ResourceRequirements' type to only include the Requests and Limits fields. +type ACMEChallengeSolverHTTP01IngressPodResources struct { + // Limits describes the maximum amount of compute resources allowed. + // More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + // +optional + Limits corev1.ResourceList + // Requests describes the minimum amount of compute resources required. + // If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + // otherwise to the global values configured via controller flags. Requests cannot exceed Limits. + // More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + // +optional + Requests corev1.ResourceList +} + // 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). @@ -399,6 +532,9 @@ type ACMEIssuerDNS01ProviderDigitalOcean struct { // ACMEIssuerDNS01ProviderRoute53 is a structure containing the Route 53 // configuration for AWS type ACMEIssuerDNS01ProviderRoute53 struct { + // Auth configures how cert-manager authenticates. + Auth *Route53Auth + // 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 @@ -424,13 +560,42 @@ type ACMEIssuerDNS01ProviderRoute53 struct { // or the inferred credentials from environment variables, shared credentials file or AWS Instance metadata Role string - // If set, the provider will manage only this zone in Route53 and will not do an lookup using the route53:ListHostedZonesByName api call. + // If set, the provider will manage only this zone in Route53 and will not do a lookup using the route53:ListHostedZonesByName api call. HostedZoneID string // Always set the region when using AccessKeyID and SecretAccessKey Region string } +// Route53Auth is configuration used to authenticate with a Route53. +type Route53Auth struct { + // Kubernetes authenticates with Route53 using AssumeRoleWithWebIdentity + // by passing a bound ServiceAccount token. + Kubernetes *Route53KubernetesAuth +} + +// Route53KubernetesAuth is a configuration to authenticate against Route53 +// using a bound Kubernetes ServiceAccount token. +type Route53KubernetesAuth struct { + // A reference to a service account that will be used to request a bound + // token (also known as "projected token"). To use this field, you must + // configure an RBAC rule to let cert-manager request a token. + ServiceAccountRef *ServiceAccountRef +} + +// ServiceAccountRef is a service account used by cert-manager to request a +// token. The expiration of the token is also set by cert-manager to 10 minutes. +type ServiceAccountRef struct { + // Name of the ServiceAccount used to request a token. + Name string + + // TokenAudiences is an optional list of audiences to include in the + // token passed to AWS. The default token consisting of the issuer's namespace + // and name is always included. + // If unset the audience defaults to `sts.amazonaws.com`. + TokenAudiences []string +} + // ACMEIssuerDNS01ProviderAzureDNS is a structure containing the // configuration for Azure DNS type ACMEIssuerDNS01ProviderAzureDNS struct { @@ -458,6 +623,8 @@ type AzureManagedIdentity struct { ClientID string ResourceID string + + TenantID string } type AzureDNSEnvironment string @@ -499,8 +666,19 @@ type ACMEIssuerDNS01ProviderRFC2136 struct { // Supported values are (case-insensitive): ``HMACMD5`` (default), // ``HMACSHA1``, ``HMACSHA256`` or ``HMACSHA512``. TSIGAlgorithm string + + // Protocol to use for dynamic DNS update queries. Valid values are (case-sensitive) ``TCP`` and ``UDP``; ``UDP`` (default). + // +optional + Protocol RFC2136UpdateProtocol } +type RFC2136UpdateProtocol string + +const ( + RFC2136UpdateProtocolTCP RFC2136UpdateProtocol = "TCP" + RFC2136UpdateProtocolUDP RFC2136UpdateProtocol = "UDP" +) + // ACMEIssuerDNS01ProviderWebhook specifies configuration for a webhook DNS01 // provider, including where to POST ChallengePayload resources. type ACMEIssuerDNS01ProviderWebhook struct { @@ -512,14 +690,14 @@ type ACMEIssuerDNS01ProviderWebhook struct { // 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'. + // This will typically be the name of the provider, e.g., 'cloudflare'. SolverName string // 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 + // 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. @@ -535,4 +713,9 @@ type ACMEIssuerStatus struct { // ACME account, in order to track changes made to registered account // associated with the Issuer LastRegisteredEmail string + + // 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 + LastPrivateKeyHash string } diff --git a/internal/apis/acme/types_order.go b/internal/apis/acme/types_order.go index f4cd996820b..51883a3e65a 100644 --- a/internal/apis/acme/types_order.go +++ b/internal/apis/acme/types_order.go @@ -54,7 +54,7 @@ type OrderSpec struct { // 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. - IssuerRef cmmeta.ObjectReference + IssuerRef cmmeta.IssuerReference // CommonName is the common name as specified on the DER encoded CSR. // If specified, this value must also be present in `dnsNames` or `ipAddresses`. @@ -74,6 +74,11 @@ type OrderSpec struct { // Duration is the duration for the not after date for the requested certificate. // this is set on order creation as pe the ACME spec. Duration *metav1.Duration + + // Profile allows requesting a certificate profile from the ACME server. + // Supported profiles are listed by the server's ACME directory URL. + // +optional + Profile string `json:"profile,omitempty"` } type OrderStatus struct { @@ -158,7 +163,7 @@ type ACMEChallenge struct { // This is used to compute the 'key' that must also be presented. Token string - // Type is the type of challenge being offered, e.g. 'http-01', 'dns-01', + // 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 @@ -204,7 +209,7 @@ const ( Processing State = "processing" // 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. + // If an Order is marked 'invalid', one of its validations must be invalid for some reason. // This is a final state. Invalid State = "invalid" diff --git a/internal/apis/acme/v1/defaults.go b/internal/apis/acme/v1/defaults.go index 66334146b9a..107e9b53bbc 100644 --- a/internal/apis/acme/v1/defaults.go +++ b/internal/apis/acme/v1/defaults.go @@ -18,8 +18,46 @@ package v1 import ( "k8s.io/apimachinery/pkg/runtime" + + acmev1 "github.com/cert-manager/cert-manager/pkg/apis/acme/v1" ) func addDefaultingFuncs(scheme *runtime.Scheme) error { + scheme.AddTypeDefaultingFunc(&acmev1.Challenge{}, func(obj interface{}) { SetObjectDefaults_Challenge(obj.(*acmev1.Challenge)) }) + scheme.AddTypeDefaultingFunc(&acmev1.ChallengeList{}, func(obj interface{}) { SetObjectDefaults_ChallengeList(obj.(*acmev1.ChallengeList)) }) + scheme.AddTypeDefaultingFunc(&acmev1.Order{}, func(obj interface{}) { SetObjectDefaults_Order(obj.(*acmev1.Order)) }) + scheme.AddTypeDefaultingFunc(&acmev1.OrderList{}, func(obj interface{}) { SetObjectDefaults_OrderList(obj.(*acmev1.OrderList)) }) return RegisterDefaults(scheme) } + +func SetObjectDefaults_Challenge(in *acmev1.Challenge) { + if in.Spec.IssuerRef.Kind == "" { + in.Spec.IssuerRef.Kind = "Issuer" + } + if in.Spec.IssuerRef.Group == "" { + in.Spec.IssuerRef.Group = "cert-manager.io" + } +} + +func SetObjectDefaults_ChallengeList(in *acmev1.ChallengeList) { + for i := range in.Items { + a := &in.Items[i] + SetObjectDefaults_Challenge(a) + } +} + +func SetObjectDefaults_Order(in *acmev1.Order) { + if in.Spec.IssuerRef.Kind == "" { + in.Spec.IssuerRef.Kind = "Issuer" + } + if in.Spec.IssuerRef.Group == "" { + in.Spec.IssuerRef.Group = "cert-manager.io" + } +} + +func SetObjectDefaults_OrderList(in *acmev1.OrderList) { + for i := range in.Items { + a := &in.Items[i] + SetObjectDefaults_Order(a) + } +} diff --git a/internal/apis/acme/v1/zz_generated.conversion.go b/internal/apis/acme/v1/zz_generated.conversion.go index bf64a7ee2a9..9493f21815e 100644 --- a/internal/apis/acme/v1/zz_generated.conversion.go +++ b/internal/apis/acme/v1/zz_generated.conversion.go @@ -27,14 +27,14 @@ import ( acme "github.com/cert-manager/cert-manager/internal/apis/acme" meta "github.com/cert-manager/cert-manager/internal/apis/meta" metav1 "github.com/cert-manager/cert-manager/internal/apis/meta/v1" - v1 "github.com/cert-manager/cert-manager/pkg/apis/acme/v1" + acmev1 "github.com/cert-manager/cert-manager/pkg/apis/acme/v1" apismetav1 "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" corev1 "k8s.io/api/core/v1" apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" pkgapismetav1 "k8s.io/apimachinery/pkg/apis/meta/v1" conversion "k8s.io/apimachinery/pkg/conversion" runtime "k8s.io/apimachinery/pkg/runtime" - v1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" + apisv1 "sigs.k8s.io/gateway-api/apis/v1" ) func init() { @@ -44,350 +44,400 @@ func init() { // RegisterConversions adds conversion functions to the given scheme. // Public to allow building arbitrary schemes. func RegisterConversions(s *runtime.Scheme) error { - if err := s.AddGeneratedConversionFunc((*v1.ACMEAuthorization)(nil), (*acme.ACMEAuthorization)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1_ACMEAuthorization_To_acme_ACMEAuthorization(a.(*v1.ACMEAuthorization), b.(*acme.ACMEAuthorization), scope) + if err := s.AddGeneratedConversionFunc((*acmev1.ACMEAuthorization)(nil), (*acme.ACMEAuthorization)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1_ACMEAuthorization_To_acme_ACMEAuthorization(a.(*acmev1.ACMEAuthorization), b.(*acme.ACMEAuthorization), scope) }); err != nil { return err } - if err := s.AddGeneratedConversionFunc((*acme.ACMEAuthorization)(nil), (*v1.ACMEAuthorization)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_acme_ACMEAuthorization_To_v1_ACMEAuthorization(a.(*acme.ACMEAuthorization), b.(*v1.ACMEAuthorization), scope) + if err := s.AddGeneratedConversionFunc((*acme.ACMEAuthorization)(nil), (*acmev1.ACMEAuthorization)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_acme_ACMEAuthorization_To_v1_ACMEAuthorization(a.(*acme.ACMEAuthorization), b.(*acmev1.ACMEAuthorization), scope) }); err != nil { return err } - if err := s.AddGeneratedConversionFunc((*v1.ACMEChallenge)(nil), (*acme.ACMEChallenge)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1_ACMEChallenge_To_acme_ACMEChallenge(a.(*v1.ACMEChallenge), b.(*acme.ACMEChallenge), scope) + if err := s.AddGeneratedConversionFunc((*acmev1.ACMEChallenge)(nil), (*acme.ACMEChallenge)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1_ACMEChallenge_To_acme_ACMEChallenge(a.(*acmev1.ACMEChallenge), b.(*acme.ACMEChallenge), scope) }); err != nil { return err } - if err := s.AddGeneratedConversionFunc((*acme.ACMEChallenge)(nil), (*v1.ACMEChallenge)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_acme_ACMEChallenge_To_v1_ACMEChallenge(a.(*acme.ACMEChallenge), b.(*v1.ACMEChallenge), scope) + if err := s.AddGeneratedConversionFunc((*acme.ACMEChallenge)(nil), (*acmev1.ACMEChallenge)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_acme_ACMEChallenge_To_v1_ACMEChallenge(a.(*acme.ACMEChallenge), b.(*acmev1.ACMEChallenge), scope) }); err != nil { return err } - if err := s.AddGeneratedConversionFunc((*v1.ACMEChallengeSolver)(nil), (*acme.ACMEChallengeSolver)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1_ACMEChallengeSolver_To_acme_ACMEChallengeSolver(a.(*v1.ACMEChallengeSolver), b.(*acme.ACMEChallengeSolver), scope) + if err := s.AddGeneratedConversionFunc((*acmev1.ACMEChallengeSolver)(nil), (*acme.ACMEChallengeSolver)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1_ACMEChallengeSolver_To_acme_ACMEChallengeSolver(a.(*acmev1.ACMEChallengeSolver), b.(*acme.ACMEChallengeSolver), scope) }); err != nil { return err } - if err := s.AddGeneratedConversionFunc((*acme.ACMEChallengeSolver)(nil), (*v1.ACMEChallengeSolver)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_acme_ACMEChallengeSolver_To_v1_ACMEChallengeSolver(a.(*acme.ACMEChallengeSolver), b.(*v1.ACMEChallengeSolver), scope) + if err := s.AddGeneratedConversionFunc((*acme.ACMEChallengeSolver)(nil), (*acmev1.ACMEChallengeSolver)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_acme_ACMEChallengeSolver_To_v1_ACMEChallengeSolver(a.(*acme.ACMEChallengeSolver), b.(*acmev1.ACMEChallengeSolver), scope) }); err != nil { return err } - if err := s.AddGeneratedConversionFunc((*v1.ACMEChallengeSolverDNS01)(nil), (*acme.ACMEChallengeSolverDNS01)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1_ACMEChallengeSolverDNS01_To_acme_ACMEChallengeSolverDNS01(a.(*v1.ACMEChallengeSolverDNS01), b.(*acme.ACMEChallengeSolverDNS01), scope) + if err := s.AddGeneratedConversionFunc((*acmev1.ACMEChallengeSolverDNS01)(nil), (*acme.ACMEChallengeSolverDNS01)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1_ACMEChallengeSolverDNS01_To_acme_ACMEChallengeSolverDNS01(a.(*acmev1.ACMEChallengeSolverDNS01), b.(*acme.ACMEChallengeSolverDNS01), scope) }); err != nil { return err } - if err := s.AddGeneratedConversionFunc((*acme.ACMEChallengeSolverDNS01)(nil), (*v1.ACMEChallengeSolverDNS01)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_acme_ACMEChallengeSolverDNS01_To_v1_ACMEChallengeSolverDNS01(a.(*acme.ACMEChallengeSolverDNS01), b.(*v1.ACMEChallengeSolverDNS01), scope) + if err := s.AddGeneratedConversionFunc((*acme.ACMEChallengeSolverDNS01)(nil), (*acmev1.ACMEChallengeSolverDNS01)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_acme_ACMEChallengeSolverDNS01_To_v1_ACMEChallengeSolverDNS01(a.(*acme.ACMEChallengeSolverDNS01), b.(*acmev1.ACMEChallengeSolverDNS01), scope) }); err != nil { return err } - if err := s.AddGeneratedConversionFunc((*v1.ACMEChallengeSolverHTTP01)(nil), (*acme.ACMEChallengeSolverHTTP01)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1_ACMEChallengeSolverHTTP01_To_acme_ACMEChallengeSolverHTTP01(a.(*v1.ACMEChallengeSolverHTTP01), b.(*acme.ACMEChallengeSolverHTTP01), scope) + if err := s.AddGeneratedConversionFunc((*acmev1.ACMEChallengeSolverHTTP01)(nil), (*acme.ACMEChallengeSolverHTTP01)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1_ACMEChallengeSolverHTTP01_To_acme_ACMEChallengeSolverHTTP01(a.(*acmev1.ACMEChallengeSolverHTTP01), b.(*acme.ACMEChallengeSolverHTTP01), scope) }); err != nil { return err } - if err := s.AddGeneratedConversionFunc((*acme.ACMEChallengeSolverHTTP01)(nil), (*v1.ACMEChallengeSolverHTTP01)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_acme_ACMEChallengeSolverHTTP01_To_v1_ACMEChallengeSolverHTTP01(a.(*acme.ACMEChallengeSolverHTTP01), b.(*v1.ACMEChallengeSolverHTTP01), scope) + if err := s.AddGeneratedConversionFunc((*acme.ACMEChallengeSolverHTTP01)(nil), (*acmev1.ACMEChallengeSolverHTTP01)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_acme_ACMEChallengeSolverHTTP01_To_v1_ACMEChallengeSolverHTTP01(a.(*acme.ACMEChallengeSolverHTTP01), b.(*acmev1.ACMEChallengeSolverHTTP01), scope) }); err != nil { return err } - if err := s.AddGeneratedConversionFunc((*v1.ACMEChallengeSolverHTTP01GatewayHTTPRoute)(nil), (*acme.ACMEChallengeSolverHTTP01GatewayHTTPRoute)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1_ACMEChallengeSolverHTTP01GatewayHTTPRoute_To_acme_ACMEChallengeSolverHTTP01GatewayHTTPRoute(a.(*v1.ACMEChallengeSolverHTTP01GatewayHTTPRoute), b.(*acme.ACMEChallengeSolverHTTP01GatewayHTTPRoute), scope) + if err := s.AddGeneratedConversionFunc((*acmev1.ACMEChallengeSolverHTTP01GatewayHTTPRoute)(nil), (*acme.ACMEChallengeSolverHTTP01GatewayHTTPRoute)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1_ACMEChallengeSolverHTTP01GatewayHTTPRoute_To_acme_ACMEChallengeSolverHTTP01GatewayHTTPRoute(a.(*acmev1.ACMEChallengeSolverHTTP01GatewayHTTPRoute), b.(*acme.ACMEChallengeSolverHTTP01GatewayHTTPRoute), scope) }); err != nil { return err } - if err := s.AddGeneratedConversionFunc((*acme.ACMEChallengeSolverHTTP01GatewayHTTPRoute)(nil), (*v1.ACMEChallengeSolverHTTP01GatewayHTTPRoute)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_acme_ACMEChallengeSolverHTTP01GatewayHTTPRoute_To_v1_ACMEChallengeSolverHTTP01GatewayHTTPRoute(a.(*acme.ACMEChallengeSolverHTTP01GatewayHTTPRoute), b.(*v1.ACMEChallengeSolverHTTP01GatewayHTTPRoute), scope) + if err := s.AddGeneratedConversionFunc((*acme.ACMEChallengeSolverHTTP01GatewayHTTPRoute)(nil), (*acmev1.ACMEChallengeSolverHTTP01GatewayHTTPRoute)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_acme_ACMEChallengeSolverHTTP01GatewayHTTPRoute_To_v1_ACMEChallengeSolverHTTP01GatewayHTTPRoute(a.(*acme.ACMEChallengeSolverHTTP01GatewayHTTPRoute), b.(*acmev1.ACMEChallengeSolverHTTP01GatewayHTTPRoute), scope) }); err != nil { return err } - if err := s.AddGeneratedConversionFunc((*v1.ACMEChallengeSolverHTTP01Ingress)(nil), (*acme.ACMEChallengeSolverHTTP01Ingress)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1_ACMEChallengeSolverHTTP01Ingress_To_acme_ACMEChallengeSolverHTTP01Ingress(a.(*v1.ACMEChallengeSolverHTTP01Ingress), b.(*acme.ACMEChallengeSolverHTTP01Ingress), scope) + if err := s.AddGeneratedConversionFunc((*acmev1.ACMEChallengeSolverHTTP01Ingress)(nil), (*acme.ACMEChallengeSolverHTTP01Ingress)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1_ACMEChallengeSolverHTTP01Ingress_To_acme_ACMEChallengeSolverHTTP01Ingress(a.(*acmev1.ACMEChallengeSolverHTTP01Ingress), b.(*acme.ACMEChallengeSolverHTTP01Ingress), scope) }); err != nil { return err } - if err := s.AddGeneratedConversionFunc((*acme.ACMEChallengeSolverHTTP01Ingress)(nil), (*v1.ACMEChallengeSolverHTTP01Ingress)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_acme_ACMEChallengeSolverHTTP01Ingress_To_v1_ACMEChallengeSolverHTTP01Ingress(a.(*acme.ACMEChallengeSolverHTTP01Ingress), b.(*v1.ACMEChallengeSolverHTTP01Ingress), scope) + if err := s.AddGeneratedConversionFunc((*acme.ACMEChallengeSolverHTTP01Ingress)(nil), (*acmev1.ACMEChallengeSolverHTTP01Ingress)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_acme_ACMEChallengeSolverHTTP01Ingress_To_v1_ACMEChallengeSolverHTTP01Ingress(a.(*acme.ACMEChallengeSolverHTTP01Ingress), b.(*acmev1.ACMEChallengeSolverHTTP01Ingress), scope) }); err != nil { return err } - if err := s.AddGeneratedConversionFunc((*v1.ACMEChallengeSolverHTTP01IngressObjectMeta)(nil), (*acme.ACMEChallengeSolverHTTP01IngressObjectMeta)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1_ACMEChallengeSolverHTTP01IngressObjectMeta_To_acme_ACMEChallengeSolverHTTP01IngressObjectMeta(a.(*v1.ACMEChallengeSolverHTTP01IngressObjectMeta), b.(*acme.ACMEChallengeSolverHTTP01IngressObjectMeta), scope) + if err := s.AddGeneratedConversionFunc((*acmev1.ACMEChallengeSolverHTTP01IngressObjectMeta)(nil), (*acme.ACMEChallengeSolverHTTP01IngressObjectMeta)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1_ACMEChallengeSolverHTTP01IngressObjectMeta_To_acme_ACMEChallengeSolverHTTP01IngressObjectMeta(a.(*acmev1.ACMEChallengeSolverHTTP01IngressObjectMeta), b.(*acme.ACMEChallengeSolverHTTP01IngressObjectMeta), scope) }); err != nil { return err } - if err := s.AddGeneratedConversionFunc((*acme.ACMEChallengeSolverHTTP01IngressObjectMeta)(nil), (*v1.ACMEChallengeSolverHTTP01IngressObjectMeta)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_acme_ACMEChallengeSolverHTTP01IngressObjectMeta_To_v1_ACMEChallengeSolverHTTP01IngressObjectMeta(a.(*acme.ACMEChallengeSolverHTTP01IngressObjectMeta), b.(*v1.ACMEChallengeSolverHTTP01IngressObjectMeta), scope) + if err := s.AddGeneratedConversionFunc((*acme.ACMEChallengeSolverHTTP01IngressObjectMeta)(nil), (*acmev1.ACMEChallengeSolverHTTP01IngressObjectMeta)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_acme_ACMEChallengeSolverHTTP01IngressObjectMeta_To_v1_ACMEChallengeSolverHTTP01IngressObjectMeta(a.(*acme.ACMEChallengeSolverHTTP01IngressObjectMeta), b.(*acmev1.ACMEChallengeSolverHTTP01IngressObjectMeta), scope) }); err != nil { return err } - if err := s.AddGeneratedConversionFunc((*v1.ACMEChallengeSolverHTTP01IngressPodObjectMeta)(nil), (*acme.ACMEChallengeSolverHTTP01IngressPodObjectMeta)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1_ACMEChallengeSolverHTTP01IngressPodObjectMeta_To_acme_ACMEChallengeSolverHTTP01IngressPodObjectMeta(a.(*v1.ACMEChallengeSolverHTTP01IngressPodObjectMeta), b.(*acme.ACMEChallengeSolverHTTP01IngressPodObjectMeta), scope) + if err := s.AddGeneratedConversionFunc((*acmev1.ACMEChallengeSolverHTTP01IngressPodObjectMeta)(nil), (*acme.ACMEChallengeSolverHTTP01IngressPodObjectMeta)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1_ACMEChallengeSolverHTTP01IngressPodObjectMeta_To_acme_ACMEChallengeSolverHTTP01IngressPodObjectMeta(a.(*acmev1.ACMEChallengeSolverHTTP01IngressPodObjectMeta), b.(*acme.ACMEChallengeSolverHTTP01IngressPodObjectMeta), scope) }); err != nil { return err } - if err := s.AddGeneratedConversionFunc((*acme.ACMEChallengeSolverHTTP01IngressPodObjectMeta)(nil), (*v1.ACMEChallengeSolverHTTP01IngressPodObjectMeta)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_acme_ACMEChallengeSolverHTTP01IngressPodObjectMeta_To_v1_ACMEChallengeSolverHTTP01IngressPodObjectMeta(a.(*acme.ACMEChallengeSolverHTTP01IngressPodObjectMeta), b.(*v1.ACMEChallengeSolverHTTP01IngressPodObjectMeta), scope) + if err := s.AddGeneratedConversionFunc((*acme.ACMEChallengeSolverHTTP01IngressPodObjectMeta)(nil), (*acmev1.ACMEChallengeSolverHTTP01IngressPodObjectMeta)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_acme_ACMEChallengeSolverHTTP01IngressPodObjectMeta_To_v1_ACMEChallengeSolverHTTP01IngressPodObjectMeta(a.(*acme.ACMEChallengeSolverHTTP01IngressPodObjectMeta), b.(*acmev1.ACMEChallengeSolverHTTP01IngressPodObjectMeta), scope) }); err != nil { return err } - if err := s.AddGeneratedConversionFunc((*v1.ACMEChallengeSolverHTTP01IngressPodSpec)(nil), (*acme.ACMEChallengeSolverHTTP01IngressPodSpec)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1_ACMEChallengeSolverHTTP01IngressPodSpec_To_acme_ACMEChallengeSolverHTTP01IngressPodSpec(a.(*v1.ACMEChallengeSolverHTTP01IngressPodSpec), b.(*acme.ACMEChallengeSolverHTTP01IngressPodSpec), scope) + if err := s.AddGeneratedConversionFunc((*acmev1.ACMEChallengeSolverHTTP01IngressPodResources)(nil), (*acme.ACMEChallengeSolverHTTP01IngressPodResources)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1_ACMEChallengeSolverHTTP01IngressPodResources_To_acme_ACMEChallengeSolverHTTP01IngressPodResources(a.(*acmev1.ACMEChallengeSolverHTTP01IngressPodResources), b.(*acme.ACMEChallengeSolverHTTP01IngressPodResources), scope) }); err != nil { return err } - if err := s.AddGeneratedConversionFunc((*acme.ACMEChallengeSolverHTTP01IngressPodSpec)(nil), (*v1.ACMEChallengeSolverHTTP01IngressPodSpec)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_acme_ACMEChallengeSolverHTTP01IngressPodSpec_To_v1_ACMEChallengeSolverHTTP01IngressPodSpec(a.(*acme.ACMEChallengeSolverHTTP01IngressPodSpec), b.(*v1.ACMEChallengeSolverHTTP01IngressPodSpec), scope) + if err := s.AddGeneratedConversionFunc((*acme.ACMEChallengeSolverHTTP01IngressPodResources)(nil), (*acmev1.ACMEChallengeSolverHTTP01IngressPodResources)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_acme_ACMEChallengeSolverHTTP01IngressPodResources_To_v1_ACMEChallengeSolverHTTP01IngressPodResources(a.(*acme.ACMEChallengeSolverHTTP01IngressPodResources), b.(*acmev1.ACMEChallengeSolverHTTP01IngressPodResources), scope) }); err != nil { return err } - if err := s.AddGeneratedConversionFunc((*v1.ACMEChallengeSolverHTTP01IngressPodTemplate)(nil), (*acme.ACMEChallengeSolverHTTP01IngressPodTemplate)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1_ACMEChallengeSolverHTTP01IngressPodTemplate_To_acme_ACMEChallengeSolverHTTP01IngressPodTemplate(a.(*v1.ACMEChallengeSolverHTTP01IngressPodTemplate), b.(*acme.ACMEChallengeSolverHTTP01IngressPodTemplate), scope) + if err := s.AddGeneratedConversionFunc((*acmev1.ACMEChallengeSolverHTTP01IngressPodSecurityContext)(nil), (*acme.ACMEChallengeSolverHTTP01IngressPodSecurityContext)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1_ACMEChallengeSolverHTTP01IngressPodSecurityContext_To_acme_ACMEChallengeSolverHTTP01IngressPodSecurityContext(a.(*acmev1.ACMEChallengeSolverHTTP01IngressPodSecurityContext), b.(*acme.ACMEChallengeSolverHTTP01IngressPodSecurityContext), scope) }); err != nil { return err } - if err := s.AddGeneratedConversionFunc((*acme.ACMEChallengeSolverHTTP01IngressPodTemplate)(nil), (*v1.ACMEChallengeSolverHTTP01IngressPodTemplate)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_acme_ACMEChallengeSolverHTTP01IngressPodTemplate_To_v1_ACMEChallengeSolverHTTP01IngressPodTemplate(a.(*acme.ACMEChallengeSolverHTTP01IngressPodTemplate), b.(*v1.ACMEChallengeSolverHTTP01IngressPodTemplate), scope) + if err := s.AddGeneratedConversionFunc((*acme.ACMEChallengeSolverHTTP01IngressPodSecurityContext)(nil), (*acmev1.ACMEChallengeSolverHTTP01IngressPodSecurityContext)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_acme_ACMEChallengeSolverHTTP01IngressPodSecurityContext_To_v1_ACMEChallengeSolverHTTP01IngressPodSecurityContext(a.(*acme.ACMEChallengeSolverHTTP01IngressPodSecurityContext), b.(*acmev1.ACMEChallengeSolverHTTP01IngressPodSecurityContext), scope) }); err != nil { return err } - if err := s.AddGeneratedConversionFunc((*v1.ACMEChallengeSolverHTTP01IngressTemplate)(nil), (*acme.ACMEChallengeSolverHTTP01IngressTemplate)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1_ACMEChallengeSolverHTTP01IngressTemplate_To_acme_ACMEChallengeSolverHTTP01IngressTemplate(a.(*v1.ACMEChallengeSolverHTTP01IngressTemplate), b.(*acme.ACMEChallengeSolverHTTP01IngressTemplate), scope) + if err := s.AddGeneratedConversionFunc((*acmev1.ACMEChallengeSolverHTTP01IngressPodSpec)(nil), (*acme.ACMEChallengeSolverHTTP01IngressPodSpec)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1_ACMEChallengeSolverHTTP01IngressPodSpec_To_acme_ACMEChallengeSolverHTTP01IngressPodSpec(a.(*acmev1.ACMEChallengeSolverHTTP01IngressPodSpec), b.(*acme.ACMEChallengeSolverHTTP01IngressPodSpec), scope) }); err != nil { return err } - if err := s.AddGeneratedConversionFunc((*acme.ACMEChallengeSolverHTTP01IngressTemplate)(nil), (*v1.ACMEChallengeSolverHTTP01IngressTemplate)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_acme_ACMEChallengeSolverHTTP01IngressTemplate_To_v1_ACMEChallengeSolverHTTP01IngressTemplate(a.(*acme.ACMEChallengeSolverHTTP01IngressTemplate), b.(*v1.ACMEChallengeSolverHTTP01IngressTemplate), scope) + if err := s.AddGeneratedConversionFunc((*acme.ACMEChallengeSolverHTTP01IngressPodSpec)(nil), (*acmev1.ACMEChallengeSolverHTTP01IngressPodSpec)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_acme_ACMEChallengeSolverHTTP01IngressPodSpec_To_v1_ACMEChallengeSolverHTTP01IngressPodSpec(a.(*acme.ACMEChallengeSolverHTTP01IngressPodSpec), b.(*acmev1.ACMEChallengeSolverHTTP01IngressPodSpec), scope) }); err != nil { return err } - if err := s.AddGeneratedConversionFunc((*v1.ACMEExternalAccountBinding)(nil), (*acme.ACMEExternalAccountBinding)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1_ACMEExternalAccountBinding_To_acme_ACMEExternalAccountBinding(a.(*v1.ACMEExternalAccountBinding), b.(*acme.ACMEExternalAccountBinding), scope) + if err := s.AddGeneratedConversionFunc((*acmev1.ACMEChallengeSolverHTTP01IngressPodTemplate)(nil), (*acme.ACMEChallengeSolverHTTP01IngressPodTemplate)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1_ACMEChallengeSolverHTTP01IngressPodTemplate_To_acme_ACMEChallengeSolverHTTP01IngressPodTemplate(a.(*acmev1.ACMEChallengeSolverHTTP01IngressPodTemplate), b.(*acme.ACMEChallengeSolverHTTP01IngressPodTemplate), scope) }); err != nil { return err } - if err := s.AddGeneratedConversionFunc((*acme.ACMEExternalAccountBinding)(nil), (*v1.ACMEExternalAccountBinding)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_acme_ACMEExternalAccountBinding_To_v1_ACMEExternalAccountBinding(a.(*acme.ACMEExternalAccountBinding), b.(*v1.ACMEExternalAccountBinding), scope) + if err := s.AddGeneratedConversionFunc((*acme.ACMEChallengeSolverHTTP01IngressPodTemplate)(nil), (*acmev1.ACMEChallengeSolverHTTP01IngressPodTemplate)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_acme_ACMEChallengeSolverHTTP01IngressPodTemplate_To_v1_ACMEChallengeSolverHTTP01IngressPodTemplate(a.(*acme.ACMEChallengeSolverHTTP01IngressPodTemplate), b.(*acmev1.ACMEChallengeSolverHTTP01IngressPodTemplate), scope) }); err != nil { return err } - if err := s.AddGeneratedConversionFunc((*v1.ACMEIssuerDNS01ProviderAcmeDNS)(nil), (*acme.ACMEIssuerDNS01ProviderAcmeDNS)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1_ACMEIssuerDNS01ProviderAcmeDNS_To_acme_ACMEIssuerDNS01ProviderAcmeDNS(a.(*v1.ACMEIssuerDNS01ProviderAcmeDNS), b.(*acme.ACMEIssuerDNS01ProviderAcmeDNS), scope) + if err := s.AddGeneratedConversionFunc((*acmev1.ACMEChallengeSolverHTTP01IngressTemplate)(nil), (*acme.ACMEChallengeSolverHTTP01IngressTemplate)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1_ACMEChallengeSolverHTTP01IngressTemplate_To_acme_ACMEChallengeSolverHTTP01IngressTemplate(a.(*acmev1.ACMEChallengeSolverHTTP01IngressTemplate), b.(*acme.ACMEChallengeSolverHTTP01IngressTemplate), scope) }); err != nil { return err } - if err := s.AddGeneratedConversionFunc((*acme.ACMEIssuerDNS01ProviderAcmeDNS)(nil), (*v1.ACMEIssuerDNS01ProviderAcmeDNS)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_acme_ACMEIssuerDNS01ProviderAcmeDNS_To_v1_ACMEIssuerDNS01ProviderAcmeDNS(a.(*acme.ACMEIssuerDNS01ProviderAcmeDNS), b.(*v1.ACMEIssuerDNS01ProviderAcmeDNS), scope) + if err := s.AddGeneratedConversionFunc((*acme.ACMEChallengeSolverHTTP01IngressTemplate)(nil), (*acmev1.ACMEChallengeSolverHTTP01IngressTemplate)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_acme_ACMEChallengeSolverHTTP01IngressTemplate_To_v1_ACMEChallengeSolverHTTP01IngressTemplate(a.(*acme.ACMEChallengeSolverHTTP01IngressTemplate), b.(*acmev1.ACMEChallengeSolverHTTP01IngressTemplate), scope) }); err != nil { return err } - if err := s.AddGeneratedConversionFunc((*v1.ACMEIssuerDNS01ProviderAkamai)(nil), (*acme.ACMEIssuerDNS01ProviderAkamai)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1_ACMEIssuerDNS01ProviderAkamai_To_acme_ACMEIssuerDNS01ProviderAkamai(a.(*v1.ACMEIssuerDNS01ProviderAkamai), b.(*acme.ACMEIssuerDNS01ProviderAkamai), scope) + if err := s.AddGeneratedConversionFunc((*acmev1.ACMEExternalAccountBinding)(nil), (*acme.ACMEExternalAccountBinding)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1_ACMEExternalAccountBinding_To_acme_ACMEExternalAccountBinding(a.(*acmev1.ACMEExternalAccountBinding), b.(*acme.ACMEExternalAccountBinding), scope) }); err != nil { return err } - if err := s.AddGeneratedConversionFunc((*acme.ACMEIssuerDNS01ProviderAkamai)(nil), (*v1.ACMEIssuerDNS01ProviderAkamai)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_acme_ACMEIssuerDNS01ProviderAkamai_To_v1_ACMEIssuerDNS01ProviderAkamai(a.(*acme.ACMEIssuerDNS01ProviderAkamai), b.(*v1.ACMEIssuerDNS01ProviderAkamai), scope) + if err := s.AddGeneratedConversionFunc((*acme.ACMEExternalAccountBinding)(nil), (*acmev1.ACMEExternalAccountBinding)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_acme_ACMEExternalAccountBinding_To_v1_ACMEExternalAccountBinding(a.(*acme.ACMEExternalAccountBinding), b.(*acmev1.ACMEExternalAccountBinding), scope) }); err != nil { return err } - if err := s.AddGeneratedConversionFunc((*v1.ACMEIssuerDNS01ProviderAzureDNS)(nil), (*acme.ACMEIssuerDNS01ProviderAzureDNS)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1_ACMEIssuerDNS01ProviderAzureDNS_To_acme_ACMEIssuerDNS01ProviderAzureDNS(a.(*v1.ACMEIssuerDNS01ProviderAzureDNS), b.(*acme.ACMEIssuerDNS01ProviderAzureDNS), scope) + if err := s.AddGeneratedConversionFunc((*acmev1.ACMEIssuerDNS01ProviderAcmeDNS)(nil), (*acme.ACMEIssuerDNS01ProviderAcmeDNS)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1_ACMEIssuerDNS01ProviderAcmeDNS_To_acme_ACMEIssuerDNS01ProviderAcmeDNS(a.(*acmev1.ACMEIssuerDNS01ProviderAcmeDNS), b.(*acme.ACMEIssuerDNS01ProviderAcmeDNS), scope) }); err != nil { return err } - if err := s.AddGeneratedConversionFunc((*acme.ACMEIssuerDNS01ProviderAzureDNS)(nil), (*v1.ACMEIssuerDNS01ProviderAzureDNS)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_acme_ACMEIssuerDNS01ProviderAzureDNS_To_v1_ACMEIssuerDNS01ProviderAzureDNS(a.(*acme.ACMEIssuerDNS01ProviderAzureDNS), b.(*v1.ACMEIssuerDNS01ProviderAzureDNS), scope) + if err := s.AddGeneratedConversionFunc((*acme.ACMEIssuerDNS01ProviderAcmeDNS)(nil), (*acmev1.ACMEIssuerDNS01ProviderAcmeDNS)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_acme_ACMEIssuerDNS01ProviderAcmeDNS_To_v1_ACMEIssuerDNS01ProviderAcmeDNS(a.(*acme.ACMEIssuerDNS01ProviderAcmeDNS), b.(*acmev1.ACMEIssuerDNS01ProviderAcmeDNS), scope) }); err != nil { return err } - if err := s.AddGeneratedConversionFunc((*v1.ACMEIssuerDNS01ProviderCloudDNS)(nil), (*acme.ACMEIssuerDNS01ProviderCloudDNS)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1_ACMEIssuerDNS01ProviderCloudDNS_To_acme_ACMEIssuerDNS01ProviderCloudDNS(a.(*v1.ACMEIssuerDNS01ProviderCloudDNS), b.(*acme.ACMEIssuerDNS01ProviderCloudDNS), scope) + if err := s.AddGeneratedConversionFunc((*acmev1.ACMEIssuerDNS01ProviderAkamai)(nil), (*acme.ACMEIssuerDNS01ProviderAkamai)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1_ACMEIssuerDNS01ProviderAkamai_To_acme_ACMEIssuerDNS01ProviderAkamai(a.(*acmev1.ACMEIssuerDNS01ProviderAkamai), b.(*acme.ACMEIssuerDNS01ProviderAkamai), scope) }); err != nil { return err } - if err := s.AddGeneratedConversionFunc((*acme.ACMEIssuerDNS01ProviderCloudDNS)(nil), (*v1.ACMEIssuerDNS01ProviderCloudDNS)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_acme_ACMEIssuerDNS01ProviderCloudDNS_To_v1_ACMEIssuerDNS01ProviderCloudDNS(a.(*acme.ACMEIssuerDNS01ProviderCloudDNS), b.(*v1.ACMEIssuerDNS01ProviderCloudDNS), scope) + if err := s.AddGeneratedConversionFunc((*acme.ACMEIssuerDNS01ProviderAkamai)(nil), (*acmev1.ACMEIssuerDNS01ProviderAkamai)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_acme_ACMEIssuerDNS01ProviderAkamai_To_v1_ACMEIssuerDNS01ProviderAkamai(a.(*acme.ACMEIssuerDNS01ProviderAkamai), b.(*acmev1.ACMEIssuerDNS01ProviderAkamai), scope) }); err != nil { return err } - if err := s.AddGeneratedConversionFunc((*v1.ACMEIssuerDNS01ProviderCloudflare)(nil), (*acme.ACMEIssuerDNS01ProviderCloudflare)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1_ACMEIssuerDNS01ProviderCloudflare_To_acme_ACMEIssuerDNS01ProviderCloudflare(a.(*v1.ACMEIssuerDNS01ProviderCloudflare), b.(*acme.ACMEIssuerDNS01ProviderCloudflare), scope) + if err := s.AddGeneratedConversionFunc((*acmev1.ACMEIssuerDNS01ProviderAzureDNS)(nil), (*acme.ACMEIssuerDNS01ProviderAzureDNS)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1_ACMEIssuerDNS01ProviderAzureDNS_To_acme_ACMEIssuerDNS01ProviderAzureDNS(a.(*acmev1.ACMEIssuerDNS01ProviderAzureDNS), b.(*acme.ACMEIssuerDNS01ProviderAzureDNS), scope) }); err != nil { return err } - if err := s.AddGeneratedConversionFunc((*acme.ACMEIssuerDNS01ProviderCloudflare)(nil), (*v1.ACMEIssuerDNS01ProviderCloudflare)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_acme_ACMEIssuerDNS01ProviderCloudflare_To_v1_ACMEIssuerDNS01ProviderCloudflare(a.(*acme.ACMEIssuerDNS01ProviderCloudflare), b.(*v1.ACMEIssuerDNS01ProviderCloudflare), scope) + if err := s.AddGeneratedConversionFunc((*acme.ACMEIssuerDNS01ProviderAzureDNS)(nil), (*acmev1.ACMEIssuerDNS01ProviderAzureDNS)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_acme_ACMEIssuerDNS01ProviderAzureDNS_To_v1_ACMEIssuerDNS01ProviderAzureDNS(a.(*acme.ACMEIssuerDNS01ProviderAzureDNS), b.(*acmev1.ACMEIssuerDNS01ProviderAzureDNS), scope) }); err != nil { return err } - if err := s.AddGeneratedConversionFunc((*v1.ACMEIssuerDNS01ProviderDigitalOcean)(nil), (*acme.ACMEIssuerDNS01ProviderDigitalOcean)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1_ACMEIssuerDNS01ProviderDigitalOcean_To_acme_ACMEIssuerDNS01ProviderDigitalOcean(a.(*v1.ACMEIssuerDNS01ProviderDigitalOcean), b.(*acme.ACMEIssuerDNS01ProviderDigitalOcean), scope) + if err := s.AddGeneratedConversionFunc((*acmev1.ACMEIssuerDNS01ProviderCloudDNS)(nil), (*acme.ACMEIssuerDNS01ProviderCloudDNS)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1_ACMEIssuerDNS01ProviderCloudDNS_To_acme_ACMEIssuerDNS01ProviderCloudDNS(a.(*acmev1.ACMEIssuerDNS01ProviderCloudDNS), b.(*acme.ACMEIssuerDNS01ProviderCloudDNS), scope) }); err != nil { return err } - if err := s.AddGeneratedConversionFunc((*acme.ACMEIssuerDNS01ProviderDigitalOcean)(nil), (*v1.ACMEIssuerDNS01ProviderDigitalOcean)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_acme_ACMEIssuerDNS01ProviderDigitalOcean_To_v1_ACMEIssuerDNS01ProviderDigitalOcean(a.(*acme.ACMEIssuerDNS01ProviderDigitalOcean), b.(*v1.ACMEIssuerDNS01ProviderDigitalOcean), scope) + if err := s.AddGeneratedConversionFunc((*acme.ACMEIssuerDNS01ProviderCloudDNS)(nil), (*acmev1.ACMEIssuerDNS01ProviderCloudDNS)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_acme_ACMEIssuerDNS01ProviderCloudDNS_To_v1_ACMEIssuerDNS01ProviderCloudDNS(a.(*acme.ACMEIssuerDNS01ProviderCloudDNS), b.(*acmev1.ACMEIssuerDNS01ProviderCloudDNS), scope) }); err != nil { return err } - if err := s.AddGeneratedConversionFunc((*v1.ACMEIssuerDNS01ProviderRFC2136)(nil), (*acme.ACMEIssuerDNS01ProviderRFC2136)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1_ACMEIssuerDNS01ProviderRFC2136_To_acme_ACMEIssuerDNS01ProviderRFC2136(a.(*v1.ACMEIssuerDNS01ProviderRFC2136), b.(*acme.ACMEIssuerDNS01ProviderRFC2136), scope) + if err := s.AddGeneratedConversionFunc((*acmev1.ACMEIssuerDNS01ProviderCloudflare)(nil), (*acme.ACMEIssuerDNS01ProviderCloudflare)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1_ACMEIssuerDNS01ProviderCloudflare_To_acme_ACMEIssuerDNS01ProviderCloudflare(a.(*acmev1.ACMEIssuerDNS01ProviderCloudflare), b.(*acme.ACMEIssuerDNS01ProviderCloudflare), scope) }); err != nil { return err } - if err := s.AddGeneratedConversionFunc((*acme.ACMEIssuerDNS01ProviderRFC2136)(nil), (*v1.ACMEIssuerDNS01ProviderRFC2136)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_acme_ACMEIssuerDNS01ProviderRFC2136_To_v1_ACMEIssuerDNS01ProviderRFC2136(a.(*acme.ACMEIssuerDNS01ProviderRFC2136), b.(*v1.ACMEIssuerDNS01ProviderRFC2136), scope) + if err := s.AddGeneratedConversionFunc((*acme.ACMEIssuerDNS01ProviderCloudflare)(nil), (*acmev1.ACMEIssuerDNS01ProviderCloudflare)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_acme_ACMEIssuerDNS01ProviderCloudflare_To_v1_ACMEIssuerDNS01ProviderCloudflare(a.(*acme.ACMEIssuerDNS01ProviderCloudflare), b.(*acmev1.ACMEIssuerDNS01ProviderCloudflare), scope) }); err != nil { return err } - if err := s.AddGeneratedConversionFunc((*v1.ACMEIssuerDNS01ProviderRoute53)(nil), (*acme.ACMEIssuerDNS01ProviderRoute53)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1_ACMEIssuerDNS01ProviderRoute53_To_acme_ACMEIssuerDNS01ProviderRoute53(a.(*v1.ACMEIssuerDNS01ProviderRoute53), b.(*acme.ACMEIssuerDNS01ProviderRoute53), scope) + if err := s.AddGeneratedConversionFunc((*acmev1.ACMEIssuerDNS01ProviderDigitalOcean)(nil), (*acme.ACMEIssuerDNS01ProviderDigitalOcean)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1_ACMEIssuerDNS01ProviderDigitalOcean_To_acme_ACMEIssuerDNS01ProviderDigitalOcean(a.(*acmev1.ACMEIssuerDNS01ProviderDigitalOcean), b.(*acme.ACMEIssuerDNS01ProviderDigitalOcean), scope) }); err != nil { return err } - if err := s.AddGeneratedConversionFunc((*acme.ACMEIssuerDNS01ProviderRoute53)(nil), (*v1.ACMEIssuerDNS01ProviderRoute53)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_acme_ACMEIssuerDNS01ProviderRoute53_To_v1_ACMEIssuerDNS01ProviderRoute53(a.(*acme.ACMEIssuerDNS01ProviderRoute53), b.(*v1.ACMEIssuerDNS01ProviderRoute53), scope) + if err := s.AddGeneratedConversionFunc((*acme.ACMEIssuerDNS01ProviderDigitalOcean)(nil), (*acmev1.ACMEIssuerDNS01ProviderDigitalOcean)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_acme_ACMEIssuerDNS01ProviderDigitalOcean_To_v1_ACMEIssuerDNS01ProviderDigitalOcean(a.(*acme.ACMEIssuerDNS01ProviderDigitalOcean), b.(*acmev1.ACMEIssuerDNS01ProviderDigitalOcean), scope) }); err != nil { return err } - if err := s.AddGeneratedConversionFunc((*v1.ACMEIssuerDNS01ProviderWebhook)(nil), (*acme.ACMEIssuerDNS01ProviderWebhook)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1_ACMEIssuerDNS01ProviderWebhook_To_acme_ACMEIssuerDNS01ProviderWebhook(a.(*v1.ACMEIssuerDNS01ProviderWebhook), b.(*acme.ACMEIssuerDNS01ProviderWebhook), scope) + if err := s.AddGeneratedConversionFunc((*acmev1.ACMEIssuerDNS01ProviderRFC2136)(nil), (*acme.ACMEIssuerDNS01ProviderRFC2136)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1_ACMEIssuerDNS01ProviderRFC2136_To_acme_ACMEIssuerDNS01ProviderRFC2136(a.(*acmev1.ACMEIssuerDNS01ProviderRFC2136), b.(*acme.ACMEIssuerDNS01ProviderRFC2136), scope) }); err != nil { return err } - if err := s.AddGeneratedConversionFunc((*acme.ACMEIssuerDNS01ProviderWebhook)(nil), (*v1.ACMEIssuerDNS01ProviderWebhook)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_acme_ACMEIssuerDNS01ProviderWebhook_To_v1_ACMEIssuerDNS01ProviderWebhook(a.(*acme.ACMEIssuerDNS01ProviderWebhook), b.(*v1.ACMEIssuerDNS01ProviderWebhook), scope) + if err := s.AddGeneratedConversionFunc((*acme.ACMEIssuerDNS01ProviderRFC2136)(nil), (*acmev1.ACMEIssuerDNS01ProviderRFC2136)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_acme_ACMEIssuerDNS01ProviderRFC2136_To_v1_ACMEIssuerDNS01ProviderRFC2136(a.(*acme.ACMEIssuerDNS01ProviderRFC2136), b.(*acmev1.ACMEIssuerDNS01ProviderRFC2136), scope) }); err != nil { return err } - if err := s.AddGeneratedConversionFunc((*v1.ACMEIssuerStatus)(nil), (*acme.ACMEIssuerStatus)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1_ACMEIssuerStatus_To_acme_ACMEIssuerStatus(a.(*v1.ACMEIssuerStatus), b.(*acme.ACMEIssuerStatus), scope) + if err := s.AddGeneratedConversionFunc((*acmev1.ACMEIssuerDNS01ProviderRoute53)(nil), (*acme.ACMEIssuerDNS01ProviderRoute53)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1_ACMEIssuerDNS01ProviderRoute53_To_acme_ACMEIssuerDNS01ProviderRoute53(a.(*acmev1.ACMEIssuerDNS01ProviderRoute53), b.(*acme.ACMEIssuerDNS01ProviderRoute53), scope) }); err != nil { return err } - if err := s.AddGeneratedConversionFunc((*acme.ACMEIssuerStatus)(nil), (*v1.ACMEIssuerStatus)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_acme_ACMEIssuerStatus_To_v1_ACMEIssuerStatus(a.(*acme.ACMEIssuerStatus), b.(*v1.ACMEIssuerStatus), scope) + if err := s.AddGeneratedConversionFunc((*acme.ACMEIssuerDNS01ProviderRoute53)(nil), (*acmev1.ACMEIssuerDNS01ProviderRoute53)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_acme_ACMEIssuerDNS01ProviderRoute53_To_v1_ACMEIssuerDNS01ProviderRoute53(a.(*acme.ACMEIssuerDNS01ProviderRoute53), b.(*acmev1.ACMEIssuerDNS01ProviderRoute53), scope) }); err != nil { return err } - if err := s.AddGeneratedConversionFunc((*v1.AzureManagedIdentity)(nil), (*acme.AzureManagedIdentity)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1_AzureManagedIdentity_To_acme_AzureManagedIdentity(a.(*v1.AzureManagedIdentity), b.(*acme.AzureManagedIdentity), scope) + if err := s.AddGeneratedConversionFunc((*acmev1.ACMEIssuerDNS01ProviderWebhook)(nil), (*acme.ACMEIssuerDNS01ProviderWebhook)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1_ACMEIssuerDNS01ProviderWebhook_To_acme_ACMEIssuerDNS01ProviderWebhook(a.(*acmev1.ACMEIssuerDNS01ProviderWebhook), b.(*acme.ACMEIssuerDNS01ProviderWebhook), scope) }); err != nil { return err } - if err := s.AddGeneratedConversionFunc((*acme.AzureManagedIdentity)(nil), (*v1.AzureManagedIdentity)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_acme_AzureManagedIdentity_To_v1_AzureManagedIdentity(a.(*acme.AzureManagedIdentity), b.(*v1.AzureManagedIdentity), scope) + if err := s.AddGeneratedConversionFunc((*acme.ACMEIssuerDNS01ProviderWebhook)(nil), (*acmev1.ACMEIssuerDNS01ProviderWebhook)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_acme_ACMEIssuerDNS01ProviderWebhook_To_v1_ACMEIssuerDNS01ProviderWebhook(a.(*acme.ACMEIssuerDNS01ProviderWebhook), b.(*acmev1.ACMEIssuerDNS01ProviderWebhook), scope) }); err != nil { return err } - if err := s.AddGeneratedConversionFunc((*v1.CertificateDNSNameSelector)(nil), (*acme.CertificateDNSNameSelector)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1_CertificateDNSNameSelector_To_acme_CertificateDNSNameSelector(a.(*v1.CertificateDNSNameSelector), b.(*acme.CertificateDNSNameSelector), scope) + if err := s.AddGeneratedConversionFunc((*acmev1.ACMEIssuerStatus)(nil), (*acme.ACMEIssuerStatus)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1_ACMEIssuerStatus_To_acme_ACMEIssuerStatus(a.(*acmev1.ACMEIssuerStatus), b.(*acme.ACMEIssuerStatus), scope) }); err != nil { return err } - if err := s.AddGeneratedConversionFunc((*acme.CertificateDNSNameSelector)(nil), (*v1.CertificateDNSNameSelector)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_acme_CertificateDNSNameSelector_To_v1_CertificateDNSNameSelector(a.(*acme.CertificateDNSNameSelector), b.(*v1.CertificateDNSNameSelector), scope) + if err := s.AddGeneratedConversionFunc((*acme.ACMEIssuerStatus)(nil), (*acmev1.ACMEIssuerStatus)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_acme_ACMEIssuerStatus_To_v1_ACMEIssuerStatus(a.(*acme.ACMEIssuerStatus), b.(*acmev1.ACMEIssuerStatus), scope) }); err != nil { return err } - if err := s.AddGeneratedConversionFunc((*v1.Challenge)(nil), (*acme.Challenge)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1_Challenge_To_acme_Challenge(a.(*v1.Challenge), b.(*acme.Challenge), scope) + if err := s.AddGeneratedConversionFunc((*acmev1.AzureManagedIdentity)(nil), (*acme.AzureManagedIdentity)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1_AzureManagedIdentity_To_acme_AzureManagedIdentity(a.(*acmev1.AzureManagedIdentity), b.(*acme.AzureManagedIdentity), scope) }); err != nil { return err } - if err := s.AddGeneratedConversionFunc((*acme.Challenge)(nil), (*v1.Challenge)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_acme_Challenge_To_v1_Challenge(a.(*acme.Challenge), b.(*v1.Challenge), scope) + if err := s.AddGeneratedConversionFunc((*acme.AzureManagedIdentity)(nil), (*acmev1.AzureManagedIdentity)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_acme_AzureManagedIdentity_To_v1_AzureManagedIdentity(a.(*acme.AzureManagedIdentity), b.(*acmev1.AzureManagedIdentity), scope) }); err != nil { return err } - if err := s.AddGeneratedConversionFunc((*v1.ChallengeList)(nil), (*acme.ChallengeList)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1_ChallengeList_To_acme_ChallengeList(a.(*v1.ChallengeList), b.(*acme.ChallengeList), scope) + if err := s.AddGeneratedConversionFunc((*acmev1.CertificateDNSNameSelector)(nil), (*acme.CertificateDNSNameSelector)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1_CertificateDNSNameSelector_To_acme_CertificateDNSNameSelector(a.(*acmev1.CertificateDNSNameSelector), b.(*acme.CertificateDNSNameSelector), scope) }); err != nil { return err } - if err := s.AddGeneratedConversionFunc((*acme.ChallengeList)(nil), (*v1.ChallengeList)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_acme_ChallengeList_To_v1_ChallengeList(a.(*acme.ChallengeList), b.(*v1.ChallengeList), scope) + if err := s.AddGeneratedConversionFunc((*acme.CertificateDNSNameSelector)(nil), (*acmev1.CertificateDNSNameSelector)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_acme_CertificateDNSNameSelector_To_v1_CertificateDNSNameSelector(a.(*acme.CertificateDNSNameSelector), b.(*acmev1.CertificateDNSNameSelector), scope) }); err != nil { return err } - if err := s.AddGeneratedConversionFunc((*v1.ChallengeSpec)(nil), (*acme.ChallengeSpec)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1_ChallengeSpec_To_acme_ChallengeSpec(a.(*v1.ChallengeSpec), b.(*acme.ChallengeSpec), scope) + if err := s.AddGeneratedConversionFunc((*acmev1.Challenge)(nil), (*acme.Challenge)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1_Challenge_To_acme_Challenge(a.(*acmev1.Challenge), b.(*acme.Challenge), scope) }); err != nil { return err } - if err := s.AddGeneratedConversionFunc((*acme.ChallengeSpec)(nil), (*v1.ChallengeSpec)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_acme_ChallengeSpec_To_v1_ChallengeSpec(a.(*acme.ChallengeSpec), b.(*v1.ChallengeSpec), scope) + if err := s.AddGeneratedConversionFunc((*acme.Challenge)(nil), (*acmev1.Challenge)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_acme_Challenge_To_v1_Challenge(a.(*acme.Challenge), b.(*acmev1.Challenge), scope) }); err != nil { return err } - if err := s.AddGeneratedConversionFunc((*v1.ChallengeStatus)(nil), (*acme.ChallengeStatus)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1_ChallengeStatus_To_acme_ChallengeStatus(a.(*v1.ChallengeStatus), b.(*acme.ChallengeStatus), scope) + if err := s.AddGeneratedConversionFunc((*acmev1.ChallengeList)(nil), (*acme.ChallengeList)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1_ChallengeList_To_acme_ChallengeList(a.(*acmev1.ChallengeList), b.(*acme.ChallengeList), scope) }); err != nil { return err } - if err := s.AddGeneratedConversionFunc((*acme.ChallengeStatus)(nil), (*v1.ChallengeStatus)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_acme_ChallengeStatus_To_v1_ChallengeStatus(a.(*acme.ChallengeStatus), b.(*v1.ChallengeStatus), scope) + if err := s.AddGeneratedConversionFunc((*acme.ChallengeList)(nil), (*acmev1.ChallengeList)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_acme_ChallengeList_To_v1_ChallengeList(a.(*acme.ChallengeList), b.(*acmev1.ChallengeList), scope) }); err != nil { return err } - if err := s.AddGeneratedConversionFunc((*v1.Order)(nil), (*acme.Order)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1_Order_To_acme_Order(a.(*v1.Order), b.(*acme.Order), scope) + if err := s.AddGeneratedConversionFunc((*acmev1.ChallengeSpec)(nil), (*acme.ChallengeSpec)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1_ChallengeSpec_To_acme_ChallengeSpec(a.(*acmev1.ChallengeSpec), b.(*acme.ChallengeSpec), scope) }); err != nil { return err } - if err := s.AddGeneratedConversionFunc((*acme.Order)(nil), (*v1.Order)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_acme_Order_To_v1_Order(a.(*acme.Order), b.(*v1.Order), scope) + if err := s.AddGeneratedConversionFunc((*acme.ChallengeSpec)(nil), (*acmev1.ChallengeSpec)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_acme_ChallengeSpec_To_v1_ChallengeSpec(a.(*acme.ChallengeSpec), b.(*acmev1.ChallengeSpec), scope) }); err != nil { return err } - if err := s.AddGeneratedConversionFunc((*v1.OrderList)(nil), (*acme.OrderList)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1_OrderList_To_acme_OrderList(a.(*v1.OrderList), b.(*acme.OrderList), scope) + if err := s.AddGeneratedConversionFunc((*acmev1.ChallengeStatus)(nil), (*acme.ChallengeStatus)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1_ChallengeStatus_To_acme_ChallengeStatus(a.(*acmev1.ChallengeStatus), b.(*acme.ChallengeStatus), scope) }); err != nil { return err } - if err := s.AddGeneratedConversionFunc((*acme.OrderList)(nil), (*v1.OrderList)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_acme_OrderList_To_v1_OrderList(a.(*acme.OrderList), b.(*v1.OrderList), scope) + if err := s.AddGeneratedConversionFunc((*acme.ChallengeStatus)(nil), (*acmev1.ChallengeStatus)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_acme_ChallengeStatus_To_v1_ChallengeStatus(a.(*acme.ChallengeStatus), b.(*acmev1.ChallengeStatus), scope) }); err != nil { return err } - if err := s.AddGeneratedConversionFunc((*v1.OrderSpec)(nil), (*acme.OrderSpec)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1_OrderSpec_To_acme_OrderSpec(a.(*v1.OrderSpec), b.(*acme.OrderSpec), scope) + if err := s.AddGeneratedConversionFunc((*acmev1.Order)(nil), (*acme.Order)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1_Order_To_acme_Order(a.(*acmev1.Order), b.(*acme.Order), scope) }); err != nil { return err } - if err := s.AddGeneratedConversionFunc((*acme.OrderSpec)(nil), (*v1.OrderSpec)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_acme_OrderSpec_To_v1_OrderSpec(a.(*acme.OrderSpec), b.(*v1.OrderSpec), scope) + if err := s.AddGeneratedConversionFunc((*acme.Order)(nil), (*acmev1.Order)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_acme_Order_To_v1_Order(a.(*acme.Order), b.(*acmev1.Order), scope) }); err != nil { return err } - if err := s.AddGeneratedConversionFunc((*v1.OrderStatus)(nil), (*acme.OrderStatus)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1_OrderStatus_To_acme_OrderStatus(a.(*v1.OrderStatus), b.(*acme.OrderStatus), scope) + if err := s.AddGeneratedConversionFunc((*acmev1.OrderList)(nil), (*acme.OrderList)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1_OrderList_To_acme_OrderList(a.(*acmev1.OrderList), b.(*acme.OrderList), scope) }); err != nil { return err } - if err := s.AddGeneratedConversionFunc((*acme.OrderStatus)(nil), (*v1.OrderStatus)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_acme_OrderStatus_To_v1_OrderStatus(a.(*acme.OrderStatus), b.(*v1.OrderStatus), scope) + if err := s.AddGeneratedConversionFunc((*acme.OrderList)(nil), (*acmev1.OrderList)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_acme_OrderList_To_v1_OrderList(a.(*acme.OrderList), b.(*acmev1.OrderList), scope) }); err != nil { return err } - if err := s.AddConversionFunc((*acme.ACMEIssuer)(nil), (*v1.ACMEIssuer)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_acme_ACMEIssuer_To_v1_ACMEIssuer(a.(*acme.ACMEIssuer), b.(*v1.ACMEIssuer), scope) + if err := s.AddGeneratedConversionFunc((*acmev1.OrderSpec)(nil), (*acme.OrderSpec)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1_OrderSpec_To_acme_OrderSpec(a.(*acmev1.OrderSpec), b.(*acme.OrderSpec), scope) }); err != nil { return err } - if err := s.AddConversionFunc((*v1.ACMEIssuer)(nil), (*acme.ACMEIssuer)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1_ACMEIssuer_To_acme_ACMEIssuer(a.(*v1.ACMEIssuer), b.(*acme.ACMEIssuer), scope) + if err := s.AddGeneratedConversionFunc((*acme.OrderSpec)(nil), (*acmev1.OrderSpec)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_acme_OrderSpec_To_v1_OrderSpec(a.(*acme.OrderSpec), b.(*acmev1.OrderSpec), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*acmev1.OrderStatus)(nil), (*acme.OrderStatus)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1_OrderStatus_To_acme_OrderStatus(a.(*acmev1.OrderStatus), b.(*acme.OrderStatus), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*acme.OrderStatus)(nil), (*acmev1.OrderStatus)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_acme_OrderStatus_To_v1_OrderStatus(a.(*acme.OrderStatus), b.(*acmev1.OrderStatus), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*acmev1.Route53Auth)(nil), (*acme.Route53Auth)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1_Route53Auth_To_acme_Route53Auth(a.(*acmev1.Route53Auth), b.(*acme.Route53Auth), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*acme.Route53Auth)(nil), (*acmev1.Route53Auth)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_acme_Route53Auth_To_v1_Route53Auth(a.(*acme.Route53Auth), b.(*acmev1.Route53Auth), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*acmev1.Route53KubernetesAuth)(nil), (*acme.Route53KubernetesAuth)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1_Route53KubernetesAuth_To_acme_Route53KubernetesAuth(a.(*acmev1.Route53KubernetesAuth), b.(*acme.Route53KubernetesAuth), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*acme.Route53KubernetesAuth)(nil), (*acmev1.Route53KubernetesAuth)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_acme_Route53KubernetesAuth_To_v1_Route53KubernetesAuth(a.(*acme.Route53KubernetesAuth), b.(*acmev1.Route53KubernetesAuth), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*acmev1.ServiceAccountRef)(nil), (*acme.ServiceAccountRef)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1_ServiceAccountRef_To_acme_ServiceAccountRef(a.(*acmev1.ServiceAccountRef), b.(*acme.ServiceAccountRef), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*acme.ServiceAccountRef)(nil), (*acmev1.ServiceAccountRef)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_acme_ServiceAccountRef_To_v1_ServiceAccountRef(a.(*acme.ServiceAccountRef), b.(*acmev1.ServiceAccountRef), scope) + }); err != nil { + return err + } + if err := s.AddConversionFunc((*acme.ACMEIssuer)(nil), (*acmev1.ACMEIssuer)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_acme_ACMEIssuer_To_v1_ACMEIssuer(a.(*acme.ACMEIssuer), b.(*acmev1.ACMEIssuer), scope) + }); err != nil { + return err + } + if err := s.AddConversionFunc((*acmev1.ACMEIssuer)(nil), (*acme.ACMEIssuer)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1_ACMEIssuer_To_acme_ACMEIssuer(a.(*acmev1.ACMEIssuer), b.(*acme.ACMEIssuer), scope) }); err != nil { return err } return nil } -func autoConvert_v1_ACMEAuthorization_To_acme_ACMEAuthorization(in *v1.ACMEAuthorization, out *acme.ACMEAuthorization, s conversion.Scope) error { +func autoConvert_v1_ACMEAuthorization_To_acme_ACMEAuthorization(in *acmev1.ACMEAuthorization, out *acme.ACMEAuthorization, s conversion.Scope) error { out.URL = in.URL out.Identifier = in.Identifier out.Wildcard = (*bool)(unsafe.Pointer(in.Wildcard)) @@ -397,25 +447,25 @@ func autoConvert_v1_ACMEAuthorization_To_acme_ACMEAuthorization(in *v1.ACMEAutho } // Convert_v1_ACMEAuthorization_To_acme_ACMEAuthorization is an autogenerated conversion function. -func Convert_v1_ACMEAuthorization_To_acme_ACMEAuthorization(in *v1.ACMEAuthorization, out *acme.ACMEAuthorization, s conversion.Scope) error { +func Convert_v1_ACMEAuthorization_To_acme_ACMEAuthorization(in *acmev1.ACMEAuthorization, out *acme.ACMEAuthorization, s conversion.Scope) error { return autoConvert_v1_ACMEAuthorization_To_acme_ACMEAuthorization(in, out, s) } -func autoConvert_acme_ACMEAuthorization_To_v1_ACMEAuthorization(in *acme.ACMEAuthorization, out *v1.ACMEAuthorization, s conversion.Scope) error { +func autoConvert_acme_ACMEAuthorization_To_v1_ACMEAuthorization(in *acme.ACMEAuthorization, out *acmev1.ACMEAuthorization, s conversion.Scope) error { out.URL = in.URL out.Identifier = in.Identifier out.Wildcard = (*bool)(unsafe.Pointer(in.Wildcard)) - out.InitialState = v1.State(in.InitialState) - out.Challenges = *(*[]v1.ACMEChallenge)(unsafe.Pointer(&in.Challenges)) + out.InitialState = acmev1.State(in.InitialState) + out.Challenges = *(*[]acmev1.ACMEChallenge)(unsafe.Pointer(&in.Challenges)) return nil } // Convert_acme_ACMEAuthorization_To_v1_ACMEAuthorization is an autogenerated conversion function. -func Convert_acme_ACMEAuthorization_To_v1_ACMEAuthorization(in *acme.ACMEAuthorization, out *v1.ACMEAuthorization, s conversion.Scope) error { +func Convert_acme_ACMEAuthorization_To_v1_ACMEAuthorization(in *acme.ACMEAuthorization, out *acmev1.ACMEAuthorization, s conversion.Scope) error { return autoConvert_acme_ACMEAuthorization_To_v1_ACMEAuthorization(in, out, s) } -func autoConvert_v1_ACMEChallenge_To_acme_ACMEChallenge(in *v1.ACMEChallenge, out *acme.ACMEChallenge, s conversion.Scope) error { +func autoConvert_v1_ACMEChallenge_To_acme_ACMEChallenge(in *acmev1.ACMEChallenge, out *acme.ACMEChallenge, s conversion.Scope) error { out.URL = in.URL out.Token = in.Token out.Type = in.Type @@ -423,11 +473,11 @@ func autoConvert_v1_ACMEChallenge_To_acme_ACMEChallenge(in *v1.ACMEChallenge, ou } // Convert_v1_ACMEChallenge_To_acme_ACMEChallenge is an autogenerated conversion function. -func Convert_v1_ACMEChallenge_To_acme_ACMEChallenge(in *v1.ACMEChallenge, out *acme.ACMEChallenge, s conversion.Scope) error { +func Convert_v1_ACMEChallenge_To_acme_ACMEChallenge(in *acmev1.ACMEChallenge, out *acme.ACMEChallenge, s conversion.Scope) error { return autoConvert_v1_ACMEChallenge_To_acme_ACMEChallenge(in, out, s) } -func autoConvert_acme_ACMEChallenge_To_v1_ACMEChallenge(in *acme.ACMEChallenge, out *v1.ACMEChallenge, s conversion.Scope) error { +func autoConvert_acme_ACMEChallenge_To_v1_ACMEChallenge(in *acme.ACMEChallenge, out *acmev1.ACMEChallenge, s conversion.Scope) error { out.URL = in.URL out.Token = in.Token out.Type = in.Type @@ -435,11 +485,11 @@ func autoConvert_acme_ACMEChallenge_To_v1_ACMEChallenge(in *acme.ACMEChallenge, } // Convert_acme_ACMEChallenge_To_v1_ACMEChallenge is an autogenerated conversion function. -func Convert_acme_ACMEChallenge_To_v1_ACMEChallenge(in *acme.ACMEChallenge, out *v1.ACMEChallenge, s conversion.Scope) error { +func Convert_acme_ACMEChallenge_To_v1_ACMEChallenge(in *acme.ACMEChallenge, out *acmev1.ACMEChallenge, s conversion.Scope) error { return autoConvert_acme_ACMEChallenge_To_v1_ACMEChallenge(in, out, s) } -func autoConvert_v1_ACMEChallengeSolver_To_acme_ACMEChallengeSolver(in *v1.ACMEChallengeSolver, out *acme.ACMEChallengeSolver, s conversion.Scope) error { +func autoConvert_v1_ACMEChallengeSolver_To_acme_ACMEChallengeSolver(in *acmev1.ACMEChallengeSolver, out *acme.ACMEChallengeSolver, s conversion.Scope) error { out.Selector = (*acme.CertificateDNSNameSelector)(unsafe.Pointer(in.Selector)) out.HTTP01 = (*acme.ACMEChallengeSolverHTTP01)(unsafe.Pointer(in.HTTP01)) if in.DNS01 != nil { @@ -455,16 +505,16 @@ func autoConvert_v1_ACMEChallengeSolver_To_acme_ACMEChallengeSolver(in *v1.ACMEC } // Convert_v1_ACMEChallengeSolver_To_acme_ACMEChallengeSolver is an autogenerated conversion function. -func Convert_v1_ACMEChallengeSolver_To_acme_ACMEChallengeSolver(in *v1.ACMEChallengeSolver, out *acme.ACMEChallengeSolver, s conversion.Scope) error { +func Convert_v1_ACMEChallengeSolver_To_acme_ACMEChallengeSolver(in *acmev1.ACMEChallengeSolver, out *acme.ACMEChallengeSolver, s conversion.Scope) error { return autoConvert_v1_ACMEChallengeSolver_To_acme_ACMEChallengeSolver(in, out, s) } -func autoConvert_acme_ACMEChallengeSolver_To_v1_ACMEChallengeSolver(in *acme.ACMEChallengeSolver, out *v1.ACMEChallengeSolver, s conversion.Scope) error { - out.Selector = (*v1.CertificateDNSNameSelector)(unsafe.Pointer(in.Selector)) - out.HTTP01 = (*v1.ACMEChallengeSolverHTTP01)(unsafe.Pointer(in.HTTP01)) +func autoConvert_acme_ACMEChallengeSolver_To_v1_ACMEChallengeSolver(in *acme.ACMEChallengeSolver, out *acmev1.ACMEChallengeSolver, s conversion.Scope) error { + out.Selector = (*acmev1.CertificateDNSNameSelector)(unsafe.Pointer(in.Selector)) + out.HTTP01 = (*acmev1.ACMEChallengeSolverHTTP01)(unsafe.Pointer(in.HTTP01)) if in.DNS01 != nil { in, out := &in.DNS01, &out.DNS01 - *out = new(v1.ACMEChallengeSolverDNS01) + *out = new(acmev1.ACMEChallengeSolverDNS01) if err := Convert_acme_ACMEChallengeSolverDNS01_To_v1_ACMEChallengeSolverDNS01(*in, *out, s); err != nil { return err } @@ -475,11 +525,11 @@ func autoConvert_acme_ACMEChallengeSolver_To_v1_ACMEChallengeSolver(in *acme.ACM } // Convert_acme_ACMEChallengeSolver_To_v1_ACMEChallengeSolver is an autogenerated conversion function. -func Convert_acme_ACMEChallengeSolver_To_v1_ACMEChallengeSolver(in *acme.ACMEChallengeSolver, out *v1.ACMEChallengeSolver, s conversion.Scope) error { +func Convert_acme_ACMEChallengeSolver_To_v1_ACMEChallengeSolver(in *acme.ACMEChallengeSolver, out *acmev1.ACMEChallengeSolver, s conversion.Scope) error { return autoConvert_acme_ACMEChallengeSolver_To_v1_ACMEChallengeSolver(in, out, s) } -func autoConvert_v1_ACMEChallengeSolverDNS01_To_acme_ACMEChallengeSolverDNS01(in *v1.ACMEChallengeSolverDNS01, out *acme.ACMEChallengeSolverDNS01, s conversion.Scope) error { +func autoConvert_v1_ACMEChallengeSolverDNS01_To_acme_ACMEChallengeSolverDNS01(in *acmev1.ACMEChallengeSolverDNS01, out *acme.ACMEChallengeSolverDNS01, s conversion.Scope) error { out.CNAMEStrategy = acme.CNAMEStrategy(in.CNAMEStrategy) if in.Akamai != nil { in, out := &in.Akamai, &out.Akamai @@ -558,15 +608,15 @@ func autoConvert_v1_ACMEChallengeSolverDNS01_To_acme_ACMEChallengeSolverDNS01(in } // Convert_v1_ACMEChallengeSolverDNS01_To_acme_ACMEChallengeSolverDNS01 is an autogenerated conversion function. -func Convert_v1_ACMEChallengeSolverDNS01_To_acme_ACMEChallengeSolverDNS01(in *v1.ACMEChallengeSolverDNS01, out *acme.ACMEChallengeSolverDNS01, s conversion.Scope) error { +func Convert_v1_ACMEChallengeSolverDNS01_To_acme_ACMEChallengeSolverDNS01(in *acmev1.ACMEChallengeSolverDNS01, out *acme.ACMEChallengeSolverDNS01, s conversion.Scope) error { return autoConvert_v1_ACMEChallengeSolverDNS01_To_acme_ACMEChallengeSolverDNS01(in, out, s) } -func autoConvert_acme_ACMEChallengeSolverDNS01_To_v1_ACMEChallengeSolverDNS01(in *acme.ACMEChallengeSolverDNS01, out *v1.ACMEChallengeSolverDNS01, s conversion.Scope) error { - out.CNAMEStrategy = v1.CNAMEStrategy(in.CNAMEStrategy) +func autoConvert_acme_ACMEChallengeSolverDNS01_To_v1_ACMEChallengeSolverDNS01(in *acme.ACMEChallengeSolverDNS01, out *acmev1.ACMEChallengeSolverDNS01, s conversion.Scope) error { + out.CNAMEStrategy = acmev1.CNAMEStrategy(in.CNAMEStrategy) if in.Akamai != nil { in, out := &in.Akamai, &out.Akamai - *out = new(v1.ACMEIssuerDNS01ProviderAkamai) + *out = new(acmev1.ACMEIssuerDNS01ProviderAkamai) if err := Convert_acme_ACMEIssuerDNS01ProviderAkamai_To_v1_ACMEIssuerDNS01ProviderAkamai(*in, *out, s); err != nil { return err } @@ -575,7 +625,7 @@ func autoConvert_acme_ACMEChallengeSolverDNS01_To_v1_ACMEChallengeSolverDNS01(in } if in.CloudDNS != nil { in, out := &in.CloudDNS, &out.CloudDNS - *out = new(v1.ACMEIssuerDNS01ProviderCloudDNS) + *out = new(acmev1.ACMEIssuerDNS01ProviderCloudDNS) if err := Convert_acme_ACMEIssuerDNS01ProviderCloudDNS_To_v1_ACMEIssuerDNS01ProviderCloudDNS(*in, *out, s); err != nil { return err } @@ -584,7 +634,7 @@ func autoConvert_acme_ACMEChallengeSolverDNS01_To_v1_ACMEChallengeSolverDNS01(in } if in.Cloudflare != nil { in, out := &in.Cloudflare, &out.Cloudflare - *out = new(v1.ACMEIssuerDNS01ProviderCloudflare) + *out = new(acmev1.ACMEIssuerDNS01ProviderCloudflare) if err := Convert_acme_ACMEIssuerDNS01ProviderCloudflare_To_v1_ACMEIssuerDNS01ProviderCloudflare(*in, *out, s); err != nil { return err } @@ -593,7 +643,7 @@ func autoConvert_acme_ACMEChallengeSolverDNS01_To_v1_ACMEChallengeSolverDNS01(in } if in.Route53 != nil { in, out := &in.Route53, &out.Route53 - *out = new(v1.ACMEIssuerDNS01ProviderRoute53) + *out = new(acmev1.ACMEIssuerDNS01ProviderRoute53) if err := Convert_acme_ACMEIssuerDNS01ProviderRoute53_To_v1_ACMEIssuerDNS01ProviderRoute53(*in, *out, s); err != nil { return err } @@ -602,7 +652,7 @@ func autoConvert_acme_ACMEChallengeSolverDNS01_To_v1_ACMEChallengeSolverDNS01(in } if in.AzureDNS != nil { in, out := &in.AzureDNS, &out.AzureDNS - *out = new(v1.ACMEIssuerDNS01ProviderAzureDNS) + *out = new(acmev1.ACMEIssuerDNS01ProviderAzureDNS) if err := Convert_acme_ACMEIssuerDNS01ProviderAzureDNS_To_v1_ACMEIssuerDNS01ProviderAzureDNS(*in, *out, s); err != nil { return err } @@ -611,7 +661,7 @@ func autoConvert_acme_ACMEChallengeSolverDNS01_To_v1_ACMEChallengeSolverDNS01(in } if in.DigitalOcean != nil { in, out := &in.DigitalOcean, &out.DigitalOcean - *out = new(v1.ACMEIssuerDNS01ProviderDigitalOcean) + *out = new(acmev1.ACMEIssuerDNS01ProviderDigitalOcean) if err := Convert_acme_ACMEIssuerDNS01ProviderDigitalOcean_To_v1_ACMEIssuerDNS01ProviderDigitalOcean(*in, *out, s); err != nil { return err } @@ -620,7 +670,7 @@ func autoConvert_acme_ACMEChallengeSolverDNS01_To_v1_ACMEChallengeSolverDNS01(in } if in.AcmeDNS != nil { in, out := &in.AcmeDNS, &out.AcmeDNS - *out = new(v1.ACMEIssuerDNS01ProviderAcmeDNS) + *out = new(acmev1.ACMEIssuerDNS01ProviderAcmeDNS) if err := Convert_acme_ACMEIssuerDNS01ProviderAcmeDNS_To_v1_ACMEIssuerDNS01ProviderAcmeDNS(*in, *out, s); err != nil { return err } @@ -629,70 +679,73 @@ func autoConvert_acme_ACMEChallengeSolverDNS01_To_v1_ACMEChallengeSolverDNS01(in } if in.RFC2136 != nil { in, out := &in.RFC2136, &out.RFC2136 - *out = new(v1.ACMEIssuerDNS01ProviderRFC2136) + *out = new(acmev1.ACMEIssuerDNS01ProviderRFC2136) if err := Convert_acme_ACMEIssuerDNS01ProviderRFC2136_To_v1_ACMEIssuerDNS01ProviderRFC2136(*in, *out, s); err != nil { return err } } else { out.RFC2136 = nil } - out.Webhook = (*v1.ACMEIssuerDNS01ProviderWebhook)(unsafe.Pointer(in.Webhook)) + out.Webhook = (*acmev1.ACMEIssuerDNS01ProviderWebhook)(unsafe.Pointer(in.Webhook)) return nil } // Convert_acme_ACMEChallengeSolverDNS01_To_v1_ACMEChallengeSolverDNS01 is an autogenerated conversion function. -func Convert_acme_ACMEChallengeSolverDNS01_To_v1_ACMEChallengeSolverDNS01(in *acme.ACMEChallengeSolverDNS01, out *v1.ACMEChallengeSolverDNS01, s conversion.Scope) error { +func Convert_acme_ACMEChallengeSolverDNS01_To_v1_ACMEChallengeSolverDNS01(in *acme.ACMEChallengeSolverDNS01, out *acmev1.ACMEChallengeSolverDNS01, s conversion.Scope) error { return autoConvert_acme_ACMEChallengeSolverDNS01_To_v1_ACMEChallengeSolverDNS01(in, out, s) } -func autoConvert_v1_ACMEChallengeSolverHTTP01_To_acme_ACMEChallengeSolverHTTP01(in *v1.ACMEChallengeSolverHTTP01, out *acme.ACMEChallengeSolverHTTP01, s conversion.Scope) error { +func autoConvert_v1_ACMEChallengeSolverHTTP01_To_acme_ACMEChallengeSolverHTTP01(in *acmev1.ACMEChallengeSolverHTTP01, out *acme.ACMEChallengeSolverHTTP01, s conversion.Scope) error { out.Ingress = (*acme.ACMEChallengeSolverHTTP01Ingress)(unsafe.Pointer(in.Ingress)) out.GatewayHTTPRoute = (*acme.ACMEChallengeSolverHTTP01GatewayHTTPRoute)(unsafe.Pointer(in.GatewayHTTPRoute)) return nil } // Convert_v1_ACMEChallengeSolverHTTP01_To_acme_ACMEChallengeSolverHTTP01 is an autogenerated conversion function. -func Convert_v1_ACMEChallengeSolverHTTP01_To_acme_ACMEChallengeSolverHTTP01(in *v1.ACMEChallengeSolverHTTP01, out *acme.ACMEChallengeSolverHTTP01, s conversion.Scope) error { +func Convert_v1_ACMEChallengeSolverHTTP01_To_acme_ACMEChallengeSolverHTTP01(in *acmev1.ACMEChallengeSolverHTTP01, out *acme.ACMEChallengeSolverHTTP01, s conversion.Scope) error { return autoConvert_v1_ACMEChallengeSolverHTTP01_To_acme_ACMEChallengeSolverHTTP01(in, out, s) } -func autoConvert_acme_ACMEChallengeSolverHTTP01_To_v1_ACMEChallengeSolverHTTP01(in *acme.ACMEChallengeSolverHTTP01, out *v1.ACMEChallengeSolverHTTP01, s conversion.Scope) error { - out.Ingress = (*v1.ACMEChallengeSolverHTTP01Ingress)(unsafe.Pointer(in.Ingress)) - out.GatewayHTTPRoute = (*v1.ACMEChallengeSolverHTTP01GatewayHTTPRoute)(unsafe.Pointer(in.GatewayHTTPRoute)) +func autoConvert_acme_ACMEChallengeSolverHTTP01_To_v1_ACMEChallengeSolverHTTP01(in *acme.ACMEChallengeSolverHTTP01, out *acmev1.ACMEChallengeSolverHTTP01, s conversion.Scope) error { + out.Ingress = (*acmev1.ACMEChallengeSolverHTTP01Ingress)(unsafe.Pointer(in.Ingress)) + out.GatewayHTTPRoute = (*acmev1.ACMEChallengeSolverHTTP01GatewayHTTPRoute)(unsafe.Pointer(in.GatewayHTTPRoute)) return nil } // Convert_acme_ACMEChallengeSolverHTTP01_To_v1_ACMEChallengeSolverHTTP01 is an autogenerated conversion function. -func Convert_acme_ACMEChallengeSolverHTTP01_To_v1_ACMEChallengeSolverHTTP01(in *acme.ACMEChallengeSolverHTTP01, out *v1.ACMEChallengeSolverHTTP01, s conversion.Scope) error { +func Convert_acme_ACMEChallengeSolverHTTP01_To_v1_ACMEChallengeSolverHTTP01(in *acme.ACMEChallengeSolverHTTP01, out *acmev1.ACMEChallengeSolverHTTP01, s conversion.Scope) error { return autoConvert_acme_ACMEChallengeSolverHTTP01_To_v1_ACMEChallengeSolverHTTP01(in, out, s) } -func autoConvert_v1_ACMEChallengeSolverHTTP01GatewayHTTPRoute_To_acme_ACMEChallengeSolverHTTP01GatewayHTTPRoute(in *v1.ACMEChallengeSolverHTTP01GatewayHTTPRoute, out *acme.ACMEChallengeSolverHTTP01GatewayHTTPRoute, s conversion.Scope) error { +func autoConvert_v1_ACMEChallengeSolverHTTP01GatewayHTTPRoute_To_acme_ACMEChallengeSolverHTTP01GatewayHTTPRoute(in *acmev1.ACMEChallengeSolverHTTP01GatewayHTTPRoute, out *acme.ACMEChallengeSolverHTTP01GatewayHTTPRoute, s conversion.Scope) error { out.ServiceType = corev1.ServiceType(in.ServiceType) out.Labels = *(*map[string]string)(unsafe.Pointer(&in.Labels)) - out.ParentRefs = *(*[]v1alpha2.ParentReference)(unsafe.Pointer(&in.ParentRefs)) + out.ParentRefs = *(*[]apisv1.ParentReference)(unsafe.Pointer(&in.ParentRefs)) + out.PodTemplate = (*acme.ACMEChallengeSolverHTTP01IngressPodTemplate)(unsafe.Pointer(in.PodTemplate)) return nil } // Convert_v1_ACMEChallengeSolverHTTP01GatewayHTTPRoute_To_acme_ACMEChallengeSolverHTTP01GatewayHTTPRoute is an autogenerated conversion function. -func Convert_v1_ACMEChallengeSolverHTTP01GatewayHTTPRoute_To_acme_ACMEChallengeSolverHTTP01GatewayHTTPRoute(in *v1.ACMEChallengeSolverHTTP01GatewayHTTPRoute, out *acme.ACMEChallengeSolverHTTP01GatewayHTTPRoute, s conversion.Scope) error { +func Convert_v1_ACMEChallengeSolverHTTP01GatewayHTTPRoute_To_acme_ACMEChallengeSolverHTTP01GatewayHTTPRoute(in *acmev1.ACMEChallengeSolverHTTP01GatewayHTTPRoute, out *acme.ACMEChallengeSolverHTTP01GatewayHTTPRoute, s conversion.Scope) error { return autoConvert_v1_ACMEChallengeSolverHTTP01GatewayHTTPRoute_To_acme_ACMEChallengeSolverHTTP01GatewayHTTPRoute(in, out, s) } -func autoConvert_acme_ACMEChallengeSolverHTTP01GatewayHTTPRoute_To_v1_ACMEChallengeSolverHTTP01GatewayHTTPRoute(in *acme.ACMEChallengeSolverHTTP01GatewayHTTPRoute, out *v1.ACMEChallengeSolverHTTP01GatewayHTTPRoute, s conversion.Scope) error { +func autoConvert_acme_ACMEChallengeSolverHTTP01GatewayHTTPRoute_To_v1_ACMEChallengeSolverHTTP01GatewayHTTPRoute(in *acme.ACMEChallengeSolverHTTP01GatewayHTTPRoute, out *acmev1.ACMEChallengeSolverHTTP01GatewayHTTPRoute, s conversion.Scope) error { out.ServiceType = corev1.ServiceType(in.ServiceType) out.Labels = *(*map[string]string)(unsafe.Pointer(&in.Labels)) - out.ParentRefs = *(*[]v1alpha2.ParentReference)(unsafe.Pointer(&in.ParentRefs)) + out.ParentRefs = *(*[]apisv1.ParentReference)(unsafe.Pointer(&in.ParentRefs)) + out.PodTemplate = (*acmev1.ACMEChallengeSolverHTTP01IngressPodTemplate)(unsafe.Pointer(in.PodTemplate)) return nil } // Convert_acme_ACMEChallengeSolverHTTP01GatewayHTTPRoute_To_v1_ACMEChallengeSolverHTTP01GatewayHTTPRoute is an autogenerated conversion function. -func Convert_acme_ACMEChallengeSolverHTTP01GatewayHTTPRoute_To_v1_ACMEChallengeSolverHTTP01GatewayHTTPRoute(in *acme.ACMEChallengeSolverHTTP01GatewayHTTPRoute, out *v1.ACMEChallengeSolverHTTP01GatewayHTTPRoute, s conversion.Scope) error { +func Convert_acme_ACMEChallengeSolverHTTP01GatewayHTTPRoute_To_v1_ACMEChallengeSolverHTTP01GatewayHTTPRoute(in *acme.ACMEChallengeSolverHTTP01GatewayHTTPRoute, out *acmev1.ACMEChallengeSolverHTTP01GatewayHTTPRoute, s conversion.Scope) error { return autoConvert_acme_ACMEChallengeSolverHTTP01GatewayHTTPRoute_To_v1_ACMEChallengeSolverHTTP01GatewayHTTPRoute(in, out, s) } -func autoConvert_v1_ACMEChallengeSolverHTTP01Ingress_To_acme_ACMEChallengeSolverHTTP01Ingress(in *v1.ACMEChallengeSolverHTTP01Ingress, out *acme.ACMEChallengeSolverHTTP01Ingress, s conversion.Scope) error { +func autoConvert_v1_ACMEChallengeSolverHTTP01Ingress_To_acme_ACMEChallengeSolverHTTP01Ingress(in *acmev1.ACMEChallengeSolverHTTP01Ingress, out *acme.ACMEChallengeSolverHTTP01Ingress, s conversion.Scope) error { out.ServiceType = corev1.ServiceType(in.ServiceType) + out.IngressClassName = (*string)(unsafe.Pointer(in.IngressClassName)) out.Class = (*string)(unsafe.Pointer(in.Class)) out.Name = in.Name out.PodTemplate = (*acme.ACMEChallengeSolverHTTP01IngressPodTemplate)(unsafe.Pointer(in.PodTemplate)) @@ -701,97 +754,162 @@ func autoConvert_v1_ACMEChallengeSolverHTTP01Ingress_To_acme_ACMEChallengeSolver } // Convert_v1_ACMEChallengeSolverHTTP01Ingress_To_acme_ACMEChallengeSolverHTTP01Ingress is an autogenerated conversion function. -func Convert_v1_ACMEChallengeSolverHTTP01Ingress_To_acme_ACMEChallengeSolverHTTP01Ingress(in *v1.ACMEChallengeSolverHTTP01Ingress, out *acme.ACMEChallengeSolverHTTP01Ingress, s conversion.Scope) error { +func Convert_v1_ACMEChallengeSolverHTTP01Ingress_To_acme_ACMEChallengeSolverHTTP01Ingress(in *acmev1.ACMEChallengeSolverHTTP01Ingress, out *acme.ACMEChallengeSolverHTTP01Ingress, s conversion.Scope) error { return autoConvert_v1_ACMEChallengeSolverHTTP01Ingress_To_acme_ACMEChallengeSolverHTTP01Ingress(in, out, s) } -func autoConvert_acme_ACMEChallengeSolverHTTP01Ingress_To_v1_ACMEChallengeSolverHTTP01Ingress(in *acme.ACMEChallengeSolverHTTP01Ingress, out *v1.ACMEChallengeSolverHTTP01Ingress, s conversion.Scope) error { +func autoConvert_acme_ACMEChallengeSolverHTTP01Ingress_To_v1_ACMEChallengeSolverHTTP01Ingress(in *acme.ACMEChallengeSolverHTTP01Ingress, out *acmev1.ACMEChallengeSolverHTTP01Ingress, s conversion.Scope) error { out.ServiceType = corev1.ServiceType(in.ServiceType) + out.IngressClassName = (*string)(unsafe.Pointer(in.IngressClassName)) out.Class = (*string)(unsafe.Pointer(in.Class)) out.Name = in.Name - out.PodTemplate = (*v1.ACMEChallengeSolverHTTP01IngressPodTemplate)(unsafe.Pointer(in.PodTemplate)) - out.IngressTemplate = (*v1.ACMEChallengeSolverHTTP01IngressTemplate)(unsafe.Pointer(in.IngressTemplate)) + out.PodTemplate = (*acmev1.ACMEChallengeSolverHTTP01IngressPodTemplate)(unsafe.Pointer(in.PodTemplate)) + out.IngressTemplate = (*acmev1.ACMEChallengeSolverHTTP01IngressTemplate)(unsafe.Pointer(in.IngressTemplate)) return nil } // Convert_acme_ACMEChallengeSolverHTTP01Ingress_To_v1_ACMEChallengeSolverHTTP01Ingress is an autogenerated conversion function. -func Convert_acme_ACMEChallengeSolverHTTP01Ingress_To_v1_ACMEChallengeSolverHTTP01Ingress(in *acme.ACMEChallengeSolverHTTP01Ingress, out *v1.ACMEChallengeSolverHTTP01Ingress, s conversion.Scope) error { +func Convert_acme_ACMEChallengeSolverHTTP01Ingress_To_v1_ACMEChallengeSolverHTTP01Ingress(in *acme.ACMEChallengeSolverHTTP01Ingress, out *acmev1.ACMEChallengeSolverHTTP01Ingress, s conversion.Scope) error { return autoConvert_acme_ACMEChallengeSolverHTTP01Ingress_To_v1_ACMEChallengeSolverHTTP01Ingress(in, out, s) } -func autoConvert_v1_ACMEChallengeSolverHTTP01IngressObjectMeta_To_acme_ACMEChallengeSolverHTTP01IngressObjectMeta(in *v1.ACMEChallengeSolverHTTP01IngressObjectMeta, out *acme.ACMEChallengeSolverHTTP01IngressObjectMeta, s conversion.Scope) error { +func autoConvert_v1_ACMEChallengeSolverHTTP01IngressObjectMeta_To_acme_ACMEChallengeSolverHTTP01IngressObjectMeta(in *acmev1.ACMEChallengeSolverHTTP01IngressObjectMeta, out *acme.ACMEChallengeSolverHTTP01IngressObjectMeta, s conversion.Scope) error { out.Annotations = *(*map[string]string)(unsafe.Pointer(&in.Annotations)) out.Labels = *(*map[string]string)(unsafe.Pointer(&in.Labels)) return nil } // Convert_v1_ACMEChallengeSolverHTTP01IngressObjectMeta_To_acme_ACMEChallengeSolverHTTP01IngressObjectMeta is an autogenerated conversion function. -func Convert_v1_ACMEChallengeSolverHTTP01IngressObjectMeta_To_acme_ACMEChallengeSolverHTTP01IngressObjectMeta(in *v1.ACMEChallengeSolverHTTP01IngressObjectMeta, out *acme.ACMEChallengeSolverHTTP01IngressObjectMeta, s conversion.Scope) error { +func Convert_v1_ACMEChallengeSolverHTTP01IngressObjectMeta_To_acme_ACMEChallengeSolverHTTP01IngressObjectMeta(in *acmev1.ACMEChallengeSolverHTTP01IngressObjectMeta, out *acme.ACMEChallengeSolverHTTP01IngressObjectMeta, s conversion.Scope) error { return autoConvert_v1_ACMEChallengeSolverHTTP01IngressObjectMeta_To_acme_ACMEChallengeSolverHTTP01IngressObjectMeta(in, out, s) } -func autoConvert_acme_ACMEChallengeSolverHTTP01IngressObjectMeta_To_v1_ACMEChallengeSolverHTTP01IngressObjectMeta(in *acme.ACMEChallengeSolverHTTP01IngressObjectMeta, out *v1.ACMEChallengeSolverHTTP01IngressObjectMeta, s conversion.Scope) error { +func autoConvert_acme_ACMEChallengeSolverHTTP01IngressObjectMeta_To_v1_ACMEChallengeSolverHTTP01IngressObjectMeta(in *acme.ACMEChallengeSolverHTTP01IngressObjectMeta, out *acmev1.ACMEChallengeSolverHTTP01IngressObjectMeta, s conversion.Scope) error { out.Annotations = *(*map[string]string)(unsafe.Pointer(&in.Annotations)) out.Labels = *(*map[string]string)(unsafe.Pointer(&in.Labels)) return nil } // Convert_acme_ACMEChallengeSolverHTTP01IngressObjectMeta_To_v1_ACMEChallengeSolverHTTP01IngressObjectMeta is an autogenerated conversion function. -func Convert_acme_ACMEChallengeSolverHTTP01IngressObjectMeta_To_v1_ACMEChallengeSolverHTTP01IngressObjectMeta(in *acme.ACMEChallengeSolverHTTP01IngressObjectMeta, out *v1.ACMEChallengeSolverHTTP01IngressObjectMeta, s conversion.Scope) error { +func Convert_acme_ACMEChallengeSolverHTTP01IngressObjectMeta_To_v1_ACMEChallengeSolverHTTP01IngressObjectMeta(in *acme.ACMEChallengeSolverHTTP01IngressObjectMeta, out *acmev1.ACMEChallengeSolverHTTP01IngressObjectMeta, s conversion.Scope) error { return autoConvert_acme_ACMEChallengeSolverHTTP01IngressObjectMeta_To_v1_ACMEChallengeSolverHTTP01IngressObjectMeta(in, out, s) } -func autoConvert_v1_ACMEChallengeSolverHTTP01IngressPodObjectMeta_To_acme_ACMEChallengeSolverHTTP01IngressPodObjectMeta(in *v1.ACMEChallengeSolverHTTP01IngressPodObjectMeta, out *acme.ACMEChallengeSolverHTTP01IngressPodObjectMeta, s conversion.Scope) error { +func autoConvert_v1_ACMEChallengeSolverHTTP01IngressPodObjectMeta_To_acme_ACMEChallengeSolverHTTP01IngressPodObjectMeta(in *acmev1.ACMEChallengeSolverHTTP01IngressPodObjectMeta, out *acme.ACMEChallengeSolverHTTP01IngressPodObjectMeta, s conversion.Scope) error { out.Annotations = *(*map[string]string)(unsafe.Pointer(&in.Annotations)) out.Labels = *(*map[string]string)(unsafe.Pointer(&in.Labels)) return nil } // Convert_v1_ACMEChallengeSolverHTTP01IngressPodObjectMeta_To_acme_ACMEChallengeSolverHTTP01IngressPodObjectMeta is an autogenerated conversion function. -func Convert_v1_ACMEChallengeSolverHTTP01IngressPodObjectMeta_To_acme_ACMEChallengeSolverHTTP01IngressPodObjectMeta(in *v1.ACMEChallengeSolverHTTP01IngressPodObjectMeta, out *acme.ACMEChallengeSolverHTTP01IngressPodObjectMeta, s conversion.Scope) error { +func Convert_v1_ACMEChallengeSolverHTTP01IngressPodObjectMeta_To_acme_ACMEChallengeSolverHTTP01IngressPodObjectMeta(in *acmev1.ACMEChallengeSolverHTTP01IngressPodObjectMeta, out *acme.ACMEChallengeSolverHTTP01IngressPodObjectMeta, s conversion.Scope) error { return autoConvert_v1_ACMEChallengeSolverHTTP01IngressPodObjectMeta_To_acme_ACMEChallengeSolverHTTP01IngressPodObjectMeta(in, out, s) } -func autoConvert_acme_ACMEChallengeSolverHTTP01IngressPodObjectMeta_To_v1_ACMEChallengeSolverHTTP01IngressPodObjectMeta(in *acme.ACMEChallengeSolverHTTP01IngressPodObjectMeta, out *v1.ACMEChallengeSolverHTTP01IngressPodObjectMeta, s conversion.Scope) error { +func autoConvert_acme_ACMEChallengeSolverHTTP01IngressPodObjectMeta_To_v1_ACMEChallengeSolverHTTP01IngressPodObjectMeta(in *acme.ACMEChallengeSolverHTTP01IngressPodObjectMeta, out *acmev1.ACMEChallengeSolverHTTP01IngressPodObjectMeta, s conversion.Scope) error { out.Annotations = *(*map[string]string)(unsafe.Pointer(&in.Annotations)) out.Labels = *(*map[string]string)(unsafe.Pointer(&in.Labels)) return nil } // Convert_acme_ACMEChallengeSolverHTTP01IngressPodObjectMeta_To_v1_ACMEChallengeSolverHTTP01IngressPodObjectMeta is an autogenerated conversion function. -func Convert_acme_ACMEChallengeSolverHTTP01IngressPodObjectMeta_To_v1_ACMEChallengeSolverHTTP01IngressPodObjectMeta(in *acme.ACMEChallengeSolverHTTP01IngressPodObjectMeta, out *v1.ACMEChallengeSolverHTTP01IngressPodObjectMeta, s conversion.Scope) error { +func Convert_acme_ACMEChallengeSolverHTTP01IngressPodObjectMeta_To_v1_ACMEChallengeSolverHTTP01IngressPodObjectMeta(in *acme.ACMEChallengeSolverHTTP01IngressPodObjectMeta, out *acmev1.ACMEChallengeSolverHTTP01IngressPodObjectMeta, s conversion.Scope) error { return autoConvert_acme_ACMEChallengeSolverHTTP01IngressPodObjectMeta_To_v1_ACMEChallengeSolverHTTP01IngressPodObjectMeta(in, out, s) } -func autoConvert_v1_ACMEChallengeSolverHTTP01IngressPodSpec_To_acme_ACMEChallengeSolverHTTP01IngressPodSpec(in *v1.ACMEChallengeSolverHTTP01IngressPodSpec, out *acme.ACMEChallengeSolverHTTP01IngressPodSpec, s conversion.Scope) error { +func autoConvert_v1_ACMEChallengeSolverHTTP01IngressPodResources_To_acme_ACMEChallengeSolverHTTP01IngressPodResources(in *acmev1.ACMEChallengeSolverHTTP01IngressPodResources, out *acme.ACMEChallengeSolverHTTP01IngressPodResources, s conversion.Scope) error { + out.Limits = *(*corev1.ResourceList)(unsafe.Pointer(&in.Limits)) + out.Requests = *(*corev1.ResourceList)(unsafe.Pointer(&in.Requests)) + return nil +} + +// Convert_v1_ACMEChallengeSolverHTTP01IngressPodResources_To_acme_ACMEChallengeSolverHTTP01IngressPodResources is an autogenerated conversion function. +func Convert_v1_ACMEChallengeSolverHTTP01IngressPodResources_To_acme_ACMEChallengeSolverHTTP01IngressPodResources(in *acmev1.ACMEChallengeSolverHTTP01IngressPodResources, out *acme.ACMEChallengeSolverHTTP01IngressPodResources, s conversion.Scope) error { + return autoConvert_v1_ACMEChallengeSolverHTTP01IngressPodResources_To_acme_ACMEChallengeSolverHTTP01IngressPodResources(in, out, s) +} + +func autoConvert_acme_ACMEChallengeSolverHTTP01IngressPodResources_To_v1_ACMEChallengeSolverHTTP01IngressPodResources(in *acme.ACMEChallengeSolverHTTP01IngressPodResources, out *acmev1.ACMEChallengeSolverHTTP01IngressPodResources, s conversion.Scope) error { + out.Limits = *(*corev1.ResourceList)(unsafe.Pointer(&in.Limits)) + out.Requests = *(*corev1.ResourceList)(unsafe.Pointer(&in.Requests)) + return nil +} + +// Convert_acme_ACMEChallengeSolverHTTP01IngressPodResources_To_v1_ACMEChallengeSolverHTTP01IngressPodResources is an autogenerated conversion function. +func Convert_acme_ACMEChallengeSolverHTTP01IngressPodResources_To_v1_ACMEChallengeSolverHTTP01IngressPodResources(in *acme.ACMEChallengeSolverHTTP01IngressPodResources, out *acmev1.ACMEChallengeSolverHTTP01IngressPodResources, s conversion.Scope) error { + return autoConvert_acme_ACMEChallengeSolverHTTP01IngressPodResources_To_v1_ACMEChallengeSolverHTTP01IngressPodResources(in, out, s) +} + +func autoConvert_v1_ACMEChallengeSolverHTTP01IngressPodSecurityContext_To_acme_ACMEChallengeSolverHTTP01IngressPodSecurityContext(in *acmev1.ACMEChallengeSolverHTTP01IngressPodSecurityContext, out *acme.ACMEChallengeSolverHTTP01IngressPodSecurityContext, s conversion.Scope) error { + out.SELinuxOptions = (*corev1.SELinuxOptions)(unsafe.Pointer(in.SELinuxOptions)) + out.RunAsUser = (*int64)(unsafe.Pointer(in.RunAsUser)) + out.RunAsGroup = (*int64)(unsafe.Pointer(in.RunAsGroup)) + out.RunAsNonRoot = (*bool)(unsafe.Pointer(in.RunAsNonRoot)) + out.SupplementalGroups = *(*[]int64)(unsafe.Pointer(&in.SupplementalGroups)) + out.FSGroup = (*int64)(unsafe.Pointer(in.FSGroup)) + out.Sysctls = *(*[]corev1.Sysctl)(unsafe.Pointer(&in.Sysctls)) + out.FSGroupChangePolicy = (*corev1.PodFSGroupChangePolicy)(unsafe.Pointer(in.FSGroupChangePolicy)) + out.SeccompProfile = (*corev1.SeccompProfile)(unsafe.Pointer(in.SeccompProfile)) + return nil +} + +// Convert_v1_ACMEChallengeSolverHTTP01IngressPodSecurityContext_To_acme_ACMEChallengeSolverHTTP01IngressPodSecurityContext is an autogenerated conversion function. +func Convert_v1_ACMEChallengeSolverHTTP01IngressPodSecurityContext_To_acme_ACMEChallengeSolverHTTP01IngressPodSecurityContext(in *acmev1.ACMEChallengeSolverHTTP01IngressPodSecurityContext, out *acme.ACMEChallengeSolverHTTP01IngressPodSecurityContext, s conversion.Scope) error { + return autoConvert_v1_ACMEChallengeSolverHTTP01IngressPodSecurityContext_To_acme_ACMEChallengeSolverHTTP01IngressPodSecurityContext(in, out, s) +} + +func autoConvert_acme_ACMEChallengeSolverHTTP01IngressPodSecurityContext_To_v1_ACMEChallengeSolverHTTP01IngressPodSecurityContext(in *acme.ACMEChallengeSolverHTTP01IngressPodSecurityContext, out *acmev1.ACMEChallengeSolverHTTP01IngressPodSecurityContext, s conversion.Scope) error { + out.SELinuxOptions = (*corev1.SELinuxOptions)(unsafe.Pointer(in.SELinuxOptions)) + out.RunAsUser = (*int64)(unsafe.Pointer(in.RunAsUser)) + out.RunAsGroup = (*int64)(unsafe.Pointer(in.RunAsGroup)) + out.RunAsNonRoot = (*bool)(unsafe.Pointer(in.RunAsNonRoot)) + out.SupplementalGroups = *(*[]int64)(unsafe.Pointer(&in.SupplementalGroups)) + out.FSGroup = (*int64)(unsafe.Pointer(in.FSGroup)) + out.Sysctls = *(*[]corev1.Sysctl)(unsafe.Pointer(&in.Sysctls)) + out.FSGroupChangePolicy = (*corev1.PodFSGroupChangePolicy)(unsafe.Pointer(in.FSGroupChangePolicy)) + out.SeccompProfile = (*corev1.SeccompProfile)(unsafe.Pointer(in.SeccompProfile)) + return nil +} + +// Convert_acme_ACMEChallengeSolverHTTP01IngressPodSecurityContext_To_v1_ACMEChallengeSolverHTTP01IngressPodSecurityContext is an autogenerated conversion function. +func Convert_acme_ACMEChallengeSolverHTTP01IngressPodSecurityContext_To_v1_ACMEChallengeSolverHTTP01IngressPodSecurityContext(in *acme.ACMEChallengeSolverHTTP01IngressPodSecurityContext, out *acmev1.ACMEChallengeSolverHTTP01IngressPodSecurityContext, s conversion.Scope) error { + return autoConvert_acme_ACMEChallengeSolverHTTP01IngressPodSecurityContext_To_v1_ACMEChallengeSolverHTTP01IngressPodSecurityContext(in, out, s) +} + +func autoConvert_v1_ACMEChallengeSolverHTTP01IngressPodSpec_To_acme_ACMEChallengeSolverHTTP01IngressPodSpec(in *acmev1.ACMEChallengeSolverHTTP01IngressPodSpec, out *acme.ACMEChallengeSolverHTTP01IngressPodSpec, s conversion.Scope) error { out.NodeSelector = *(*map[string]string)(unsafe.Pointer(&in.NodeSelector)) out.Affinity = (*corev1.Affinity)(unsafe.Pointer(in.Affinity)) out.Tolerations = *(*[]corev1.Toleration)(unsafe.Pointer(&in.Tolerations)) out.PriorityClassName = in.PriorityClassName out.ServiceAccountName = in.ServiceAccountName + out.ImagePullSecrets = *(*[]corev1.LocalObjectReference)(unsafe.Pointer(&in.ImagePullSecrets)) + out.SecurityContext = (*acme.ACMEChallengeSolverHTTP01IngressPodSecurityContext)(unsafe.Pointer(in.SecurityContext)) + out.Resources = (*acme.ACMEChallengeSolverHTTP01IngressPodResources)(unsafe.Pointer(in.Resources)) return nil } // Convert_v1_ACMEChallengeSolverHTTP01IngressPodSpec_To_acme_ACMEChallengeSolverHTTP01IngressPodSpec is an autogenerated conversion function. -func Convert_v1_ACMEChallengeSolverHTTP01IngressPodSpec_To_acme_ACMEChallengeSolverHTTP01IngressPodSpec(in *v1.ACMEChallengeSolverHTTP01IngressPodSpec, out *acme.ACMEChallengeSolverHTTP01IngressPodSpec, s conversion.Scope) error { +func Convert_v1_ACMEChallengeSolverHTTP01IngressPodSpec_To_acme_ACMEChallengeSolverHTTP01IngressPodSpec(in *acmev1.ACMEChallengeSolverHTTP01IngressPodSpec, out *acme.ACMEChallengeSolverHTTP01IngressPodSpec, s conversion.Scope) error { return autoConvert_v1_ACMEChallengeSolverHTTP01IngressPodSpec_To_acme_ACMEChallengeSolverHTTP01IngressPodSpec(in, out, s) } -func autoConvert_acme_ACMEChallengeSolverHTTP01IngressPodSpec_To_v1_ACMEChallengeSolverHTTP01IngressPodSpec(in *acme.ACMEChallengeSolverHTTP01IngressPodSpec, out *v1.ACMEChallengeSolverHTTP01IngressPodSpec, s conversion.Scope) error { +func autoConvert_acme_ACMEChallengeSolverHTTP01IngressPodSpec_To_v1_ACMEChallengeSolverHTTP01IngressPodSpec(in *acme.ACMEChallengeSolverHTTP01IngressPodSpec, out *acmev1.ACMEChallengeSolverHTTP01IngressPodSpec, s conversion.Scope) error { out.NodeSelector = *(*map[string]string)(unsafe.Pointer(&in.NodeSelector)) out.Affinity = (*corev1.Affinity)(unsafe.Pointer(in.Affinity)) out.Tolerations = *(*[]corev1.Toleration)(unsafe.Pointer(&in.Tolerations)) out.PriorityClassName = in.PriorityClassName out.ServiceAccountName = in.ServiceAccountName + out.ImagePullSecrets = *(*[]corev1.LocalObjectReference)(unsafe.Pointer(&in.ImagePullSecrets)) + out.SecurityContext = (*acmev1.ACMEChallengeSolverHTTP01IngressPodSecurityContext)(unsafe.Pointer(in.SecurityContext)) + out.Resources = (*acmev1.ACMEChallengeSolverHTTP01IngressPodResources)(unsafe.Pointer(in.Resources)) return nil } // Convert_acme_ACMEChallengeSolverHTTP01IngressPodSpec_To_v1_ACMEChallengeSolverHTTP01IngressPodSpec is an autogenerated conversion function. -func Convert_acme_ACMEChallengeSolverHTTP01IngressPodSpec_To_v1_ACMEChallengeSolverHTTP01IngressPodSpec(in *acme.ACMEChallengeSolverHTTP01IngressPodSpec, out *v1.ACMEChallengeSolverHTTP01IngressPodSpec, s conversion.Scope) error { +func Convert_acme_ACMEChallengeSolverHTTP01IngressPodSpec_To_v1_ACMEChallengeSolverHTTP01IngressPodSpec(in *acme.ACMEChallengeSolverHTTP01IngressPodSpec, out *acmev1.ACMEChallengeSolverHTTP01IngressPodSpec, s conversion.Scope) error { return autoConvert_acme_ACMEChallengeSolverHTTP01IngressPodSpec_To_v1_ACMEChallengeSolverHTTP01IngressPodSpec(in, out, s) } -func autoConvert_v1_ACMEChallengeSolverHTTP01IngressPodTemplate_To_acme_ACMEChallengeSolverHTTP01IngressPodTemplate(in *v1.ACMEChallengeSolverHTTP01IngressPodTemplate, out *acme.ACMEChallengeSolverHTTP01IngressPodTemplate, s conversion.Scope) error { +func autoConvert_v1_ACMEChallengeSolverHTTP01IngressPodTemplate_To_acme_ACMEChallengeSolverHTTP01IngressPodTemplate(in *acmev1.ACMEChallengeSolverHTTP01IngressPodTemplate, out *acme.ACMEChallengeSolverHTTP01IngressPodTemplate, s conversion.Scope) error { if err := Convert_v1_ACMEChallengeSolverHTTP01IngressPodObjectMeta_To_acme_ACMEChallengeSolverHTTP01IngressPodObjectMeta(&in.ACMEChallengeSolverHTTP01IngressPodObjectMeta, &out.ACMEChallengeSolverHTTP01IngressPodObjectMeta, s); err != nil { return err } @@ -802,11 +920,11 @@ func autoConvert_v1_ACMEChallengeSolverHTTP01IngressPodTemplate_To_acme_ACMEChal } // Convert_v1_ACMEChallengeSolverHTTP01IngressPodTemplate_To_acme_ACMEChallengeSolverHTTP01IngressPodTemplate is an autogenerated conversion function. -func Convert_v1_ACMEChallengeSolverHTTP01IngressPodTemplate_To_acme_ACMEChallengeSolverHTTP01IngressPodTemplate(in *v1.ACMEChallengeSolverHTTP01IngressPodTemplate, out *acme.ACMEChallengeSolverHTTP01IngressPodTemplate, s conversion.Scope) error { +func Convert_v1_ACMEChallengeSolverHTTP01IngressPodTemplate_To_acme_ACMEChallengeSolverHTTP01IngressPodTemplate(in *acmev1.ACMEChallengeSolverHTTP01IngressPodTemplate, out *acme.ACMEChallengeSolverHTTP01IngressPodTemplate, s conversion.Scope) error { return autoConvert_v1_ACMEChallengeSolverHTTP01IngressPodTemplate_To_acme_ACMEChallengeSolverHTTP01IngressPodTemplate(in, out, s) } -func autoConvert_acme_ACMEChallengeSolverHTTP01IngressPodTemplate_To_v1_ACMEChallengeSolverHTTP01IngressPodTemplate(in *acme.ACMEChallengeSolverHTTP01IngressPodTemplate, out *v1.ACMEChallengeSolverHTTP01IngressPodTemplate, s conversion.Scope) error { +func autoConvert_acme_ACMEChallengeSolverHTTP01IngressPodTemplate_To_v1_ACMEChallengeSolverHTTP01IngressPodTemplate(in *acme.ACMEChallengeSolverHTTP01IngressPodTemplate, out *acmev1.ACMEChallengeSolverHTTP01IngressPodTemplate, s conversion.Scope) error { if err := Convert_acme_ACMEChallengeSolverHTTP01IngressPodObjectMeta_To_v1_ACMEChallengeSolverHTTP01IngressPodObjectMeta(&in.ACMEChallengeSolverHTTP01IngressPodObjectMeta, &out.ACMEChallengeSolverHTTP01IngressPodObjectMeta, s); err != nil { return err } @@ -817,11 +935,11 @@ func autoConvert_acme_ACMEChallengeSolverHTTP01IngressPodTemplate_To_v1_ACMEChal } // Convert_acme_ACMEChallengeSolverHTTP01IngressPodTemplate_To_v1_ACMEChallengeSolverHTTP01IngressPodTemplate is an autogenerated conversion function. -func Convert_acme_ACMEChallengeSolverHTTP01IngressPodTemplate_To_v1_ACMEChallengeSolverHTTP01IngressPodTemplate(in *acme.ACMEChallengeSolverHTTP01IngressPodTemplate, out *v1.ACMEChallengeSolverHTTP01IngressPodTemplate, s conversion.Scope) error { +func Convert_acme_ACMEChallengeSolverHTTP01IngressPodTemplate_To_v1_ACMEChallengeSolverHTTP01IngressPodTemplate(in *acme.ACMEChallengeSolverHTTP01IngressPodTemplate, out *acmev1.ACMEChallengeSolverHTTP01IngressPodTemplate, s conversion.Scope) error { return autoConvert_acme_ACMEChallengeSolverHTTP01IngressPodTemplate_To_v1_ACMEChallengeSolverHTTP01IngressPodTemplate(in, out, s) } -func autoConvert_v1_ACMEChallengeSolverHTTP01IngressTemplate_To_acme_ACMEChallengeSolverHTTP01IngressTemplate(in *v1.ACMEChallengeSolverHTTP01IngressTemplate, out *acme.ACMEChallengeSolverHTTP01IngressTemplate, s conversion.Scope) error { +func autoConvert_v1_ACMEChallengeSolverHTTP01IngressTemplate_To_acme_ACMEChallengeSolverHTTP01IngressTemplate(in *acmev1.ACMEChallengeSolverHTTP01IngressTemplate, out *acme.ACMEChallengeSolverHTTP01IngressTemplate, s conversion.Scope) error { if err := Convert_v1_ACMEChallengeSolverHTTP01IngressObjectMeta_To_acme_ACMEChallengeSolverHTTP01IngressObjectMeta(&in.ACMEChallengeSolverHTTP01IngressObjectMeta, &out.ACMEChallengeSolverHTTP01IngressObjectMeta, s); err != nil { return err } @@ -829,11 +947,11 @@ func autoConvert_v1_ACMEChallengeSolverHTTP01IngressTemplate_To_acme_ACMEChallen } // Convert_v1_ACMEChallengeSolverHTTP01IngressTemplate_To_acme_ACMEChallengeSolverHTTP01IngressTemplate is an autogenerated conversion function. -func Convert_v1_ACMEChallengeSolverHTTP01IngressTemplate_To_acme_ACMEChallengeSolverHTTP01IngressTemplate(in *v1.ACMEChallengeSolverHTTP01IngressTemplate, out *acme.ACMEChallengeSolverHTTP01IngressTemplate, s conversion.Scope) error { +func Convert_v1_ACMEChallengeSolverHTTP01IngressTemplate_To_acme_ACMEChallengeSolverHTTP01IngressTemplate(in *acmev1.ACMEChallengeSolverHTTP01IngressTemplate, out *acme.ACMEChallengeSolverHTTP01IngressTemplate, s conversion.Scope) error { return autoConvert_v1_ACMEChallengeSolverHTTP01IngressTemplate_To_acme_ACMEChallengeSolverHTTP01IngressTemplate(in, out, s) } -func autoConvert_acme_ACMEChallengeSolverHTTP01IngressTemplate_To_v1_ACMEChallengeSolverHTTP01IngressTemplate(in *acme.ACMEChallengeSolverHTTP01IngressTemplate, out *v1.ACMEChallengeSolverHTTP01IngressTemplate, s conversion.Scope) error { +func autoConvert_acme_ACMEChallengeSolverHTTP01IngressTemplate_To_v1_ACMEChallengeSolverHTTP01IngressTemplate(in *acme.ACMEChallengeSolverHTTP01IngressTemplate, out *acmev1.ACMEChallengeSolverHTTP01IngressTemplate, s conversion.Scope) error { if err := Convert_acme_ACMEChallengeSolverHTTP01IngressObjectMeta_To_v1_ACMEChallengeSolverHTTP01IngressObjectMeta(&in.ACMEChallengeSolverHTTP01IngressObjectMeta, &out.ACMEChallengeSolverHTTP01IngressObjectMeta, s); err != nil { return err } @@ -841,11 +959,11 @@ func autoConvert_acme_ACMEChallengeSolverHTTP01IngressTemplate_To_v1_ACMEChallen } // Convert_acme_ACMEChallengeSolverHTTP01IngressTemplate_To_v1_ACMEChallengeSolverHTTP01IngressTemplate is an autogenerated conversion function. -func Convert_acme_ACMEChallengeSolverHTTP01IngressTemplate_To_v1_ACMEChallengeSolverHTTP01IngressTemplate(in *acme.ACMEChallengeSolverHTTP01IngressTemplate, out *v1.ACMEChallengeSolverHTTP01IngressTemplate, s conversion.Scope) error { +func Convert_acme_ACMEChallengeSolverHTTP01IngressTemplate_To_v1_ACMEChallengeSolverHTTP01IngressTemplate(in *acme.ACMEChallengeSolverHTTP01IngressTemplate, out *acmev1.ACMEChallengeSolverHTTP01IngressTemplate, s conversion.Scope) error { return autoConvert_acme_ACMEChallengeSolverHTTP01IngressTemplate_To_v1_ACMEChallengeSolverHTTP01IngressTemplate(in, out, s) } -func autoConvert_v1_ACMEExternalAccountBinding_To_acme_ACMEExternalAccountBinding(in *v1.ACMEExternalAccountBinding, out *acme.ACMEExternalAccountBinding, s conversion.Scope) error { +func autoConvert_v1_ACMEExternalAccountBinding_To_acme_ACMEExternalAccountBinding(in *acmev1.ACMEExternalAccountBinding, out *acme.ACMEExternalAccountBinding, s conversion.Scope) error { out.KeyID = in.KeyID if err := metav1.Convert_v1_SecretKeySelector_To_meta_SecretKeySelector(&in.Key, &out.Key, s); err != nil { return err @@ -855,28 +973,29 @@ func autoConvert_v1_ACMEExternalAccountBinding_To_acme_ACMEExternalAccountBindin } // Convert_v1_ACMEExternalAccountBinding_To_acme_ACMEExternalAccountBinding is an autogenerated conversion function. -func Convert_v1_ACMEExternalAccountBinding_To_acme_ACMEExternalAccountBinding(in *v1.ACMEExternalAccountBinding, out *acme.ACMEExternalAccountBinding, s conversion.Scope) error { +func Convert_v1_ACMEExternalAccountBinding_To_acme_ACMEExternalAccountBinding(in *acmev1.ACMEExternalAccountBinding, out *acme.ACMEExternalAccountBinding, s conversion.Scope) error { return autoConvert_v1_ACMEExternalAccountBinding_To_acme_ACMEExternalAccountBinding(in, out, s) } -func autoConvert_acme_ACMEExternalAccountBinding_To_v1_ACMEExternalAccountBinding(in *acme.ACMEExternalAccountBinding, out *v1.ACMEExternalAccountBinding, s conversion.Scope) error { +func autoConvert_acme_ACMEExternalAccountBinding_To_v1_ACMEExternalAccountBinding(in *acme.ACMEExternalAccountBinding, out *acmev1.ACMEExternalAccountBinding, s conversion.Scope) error { out.KeyID = in.KeyID if err := metav1.Convert_meta_SecretKeySelector_To_v1_SecretKeySelector(&in.Key, &out.Key, s); err != nil { return err } - out.KeyAlgorithm = v1.HMACKeyAlgorithm(in.KeyAlgorithm) + out.KeyAlgorithm = acmev1.HMACKeyAlgorithm(in.KeyAlgorithm) return nil } // Convert_acme_ACMEExternalAccountBinding_To_v1_ACMEExternalAccountBinding is an autogenerated conversion function. -func Convert_acme_ACMEExternalAccountBinding_To_v1_ACMEExternalAccountBinding(in *acme.ACMEExternalAccountBinding, out *v1.ACMEExternalAccountBinding, s conversion.Scope) error { +func Convert_acme_ACMEExternalAccountBinding_To_v1_ACMEExternalAccountBinding(in *acme.ACMEExternalAccountBinding, out *acmev1.ACMEExternalAccountBinding, s conversion.Scope) error { return autoConvert_acme_ACMEExternalAccountBinding_To_v1_ACMEExternalAccountBinding(in, out, s) } -func autoConvert_v1_ACMEIssuer_To_acme_ACMEIssuer(in *v1.ACMEIssuer, out *acme.ACMEIssuer, s conversion.Scope) error { +func autoConvert_v1_ACMEIssuer_To_acme_ACMEIssuer(in *acmev1.ACMEIssuer, out *acme.ACMEIssuer, s conversion.Scope) error { out.Email = in.Email out.Server = in.Server out.PreferredChain = in.PreferredChain + out.CABundle = *(*[]byte)(unsafe.Pointer(&in.CABundle)) out.SkipTLSVerify = in.SkipTLSVerify if in.ExternalAccountBinding != nil { in, out := &in.ExternalAccountBinding, &out.ExternalAccountBinding @@ -903,17 +1022,19 @@ func autoConvert_v1_ACMEIssuer_To_acme_ACMEIssuer(in *v1.ACMEIssuer, out *acme.A } out.DisableAccountKeyGeneration = in.DisableAccountKeyGeneration out.EnableDurationFeature = in.EnableDurationFeature + out.Profile = in.Profile return nil } -func autoConvert_acme_ACMEIssuer_To_v1_ACMEIssuer(in *acme.ACMEIssuer, out *v1.ACMEIssuer, s conversion.Scope) error { +func autoConvert_acme_ACMEIssuer_To_v1_ACMEIssuer(in *acme.ACMEIssuer, out *acmev1.ACMEIssuer, s conversion.Scope) error { out.Email = in.Email out.Server = in.Server out.PreferredChain = in.PreferredChain + out.CABundle = *(*[]byte)(unsafe.Pointer(&in.CABundle)) out.SkipTLSVerify = in.SkipTLSVerify if in.ExternalAccountBinding != nil { in, out := &in.ExternalAccountBinding, &out.ExternalAccountBinding - *out = new(v1.ACMEExternalAccountBinding) + *out = new(acmev1.ACMEExternalAccountBinding) if err := Convert_acme_ACMEExternalAccountBinding_To_v1_ACMEExternalAccountBinding(*in, *out, s); err != nil { return err } @@ -925,7 +1046,7 @@ func autoConvert_acme_ACMEIssuer_To_v1_ACMEIssuer(in *acme.ACMEIssuer, out *v1.A } if in.Solvers != nil { in, out := &in.Solvers, &out.Solvers - *out = make([]v1.ACMEChallengeSolver, len(*in)) + *out = make([]acmev1.ACMEChallengeSolver, len(*in)) for i := range *in { if err := Convert_acme_ACMEChallengeSolver_To_v1_ACMEChallengeSolver(&(*in)[i], &(*out)[i], s); err != nil { return err @@ -936,10 +1057,11 @@ func autoConvert_acme_ACMEIssuer_To_v1_ACMEIssuer(in *acme.ACMEIssuer, out *v1.A } out.DisableAccountKeyGeneration = in.DisableAccountKeyGeneration out.EnableDurationFeature = in.EnableDurationFeature + out.Profile = in.Profile return nil } -func autoConvert_v1_ACMEIssuerDNS01ProviderAcmeDNS_To_acme_ACMEIssuerDNS01ProviderAcmeDNS(in *v1.ACMEIssuerDNS01ProviderAcmeDNS, out *acme.ACMEIssuerDNS01ProviderAcmeDNS, s conversion.Scope) error { +func autoConvert_v1_ACMEIssuerDNS01ProviderAcmeDNS_To_acme_ACMEIssuerDNS01ProviderAcmeDNS(in *acmev1.ACMEIssuerDNS01ProviderAcmeDNS, out *acme.ACMEIssuerDNS01ProviderAcmeDNS, s conversion.Scope) error { out.Host = in.Host if err := metav1.Convert_v1_SecretKeySelector_To_meta_SecretKeySelector(&in.AccountSecret, &out.AccountSecret, s); err != nil { return err @@ -948,11 +1070,11 @@ func autoConvert_v1_ACMEIssuerDNS01ProviderAcmeDNS_To_acme_ACMEIssuerDNS01Provid } // Convert_v1_ACMEIssuerDNS01ProviderAcmeDNS_To_acme_ACMEIssuerDNS01ProviderAcmeDNS is an autogenerated conversion function. -func Convert_v1_ACMEIssuerDNS01ProviderAcmeDNS_To_acme_ACMEIssuerDNS01ProviderAcmeDNS(in *v1.ACMEIssuerDNS01ProviderAcmeDNS, out *acme.ACMEIssuerDNS01ProviderAcmeDNS, s conversion.Scope) error { +func Convert_v1_ACMEIssuerDNS01ProviderAcmeDNS_To_acme_ACMEIssuerDNS01ProviderAcmeDNS(in *acmev1.ACMEIssuerDNS01ProviderAcmeDNS, out *acme.ACMEIssuerDNS01ProviderAcmeDNS, s conversion.Scope) error { return autoConvert_v1_ACMEIssuerDNS01ProviderAcmeDNS_To_acme_ACMEIssuerDNS01ProviderAcmeDNS(in, out, s) } -func autoConvert_acme_ACMEIssuerDNS01ProviderAcmeDNS_To_v1_ACMEIssuerDNS01ProviderAcmeDNS(in *acme.ACMEIssuerDNS01ProviderAcmeDNS, out *v1.ACMEIssuerDNS01ProviderAcmeDNS, s conversion.Scope) error { +func autoConvert_acme_ACMEIssuerDNS01ProviderAcmeDNS_To_v1_ACMEIssuerDNS01ProviderAcmeDNS(in *acme.ACMEIssuerDNS01ProviderAcmeDNS, out *acmev1.ACMEIssuerDNS01ProviderAcmeDNS, s conversion.Scope) error { out.Host = in.Host if err := metav1.Convert_meta_SecretKeySelector_To_v1_SecretKeySelector(&in.AccountSecret, &out.AccountSecret, s); err != nil { return err @@ -961,11 +1083,11 @@ func autoConvert_acme_ACMEIssuerDNS01ProviderAcmeDNS_To_v1_ACMEIssuerDNS01Provid } // Convert_acme_ACMEIssuerDNS01ProviderAcmeDNS_To_v1_ACMEIssuerDNS01ProviderAcmeDNS is an autogenerated conversion function. -func Convert_acme_ACMEIssuerDNS01ProviderAcmeDNS_To_v1_ACMEIssuerDNS01ProviderAcmeDNS(in *acme.ACMEIssuerDNS01ProviderAcmeDNS, out *v1.ACMEIssuerDNS01ProviderAcmeDNS, s conversion.Scope) error { +func Convert_acme_ACMEIssuerDNS01ProviderAcmeDNS_To_v1_ACMEIssuerDNS01ProviderAcmeDNS(in *acme.ACMEIssuerDNS01ProviderAcmeDNS, out *acmev1.ACMEIssuerDNS01ProviderAcmeDNS, s conversion.Scope) error { return autoConvert_acme_ACMEIssuerDNS01ProviderAcmeDNS_To_v1_ACMEIssuerDNS01ProviderAcmeDNS(in, out, s) } -func autoConvert_v1_ACMEIssuerDNS01ProviderAkamai_To_acme_ACMEIssuerDNS01ProviderAkamai(in *v1.ACMEIssuerDNS01ProviderAkamai, out *acme.ACMEIssuerDNS01ProviderAkamai, s conversion.Scope) error { +func autoConvert_v1_ACMEIssuerDNS01ProviderAkamai_To_acme_ACMEIssuerDNS01ProviderAkamai(in *acmev1.ACMEIssuerDNS01ProviderAkamai, out *acme.ACMEIssuerDNS01ProviderAkamai, s conversion.Scope) error { out.ServiceConsumerDomain = in.ServiceConsumerDomain if err := metav1.Convert_v1_SecretKeySelector_To_meta_SecretKeySelector(&in.ClientToken, &out.ClientToken, s); err != nil { return err @@ -980,11 +1102,11 @@ func autoConvert_v1_ACMEIssuerDNS01ProviderAkamai_To_acme_ACMEIssuerDNS01Provide } // Convert_v1_ACMEIssuerDNS01ProviderAkamai_To_acme_ACMEIssuerDNS01ProviderAkamai is an autogenerated conversion function. -func Convert_v1_ACMEIssuerDNS01ProviderAkamai_To_acme_ACMEIssuerDNS01ProviderAkamai(in *v1.ACMEIssuerDNS01ProviderAkamai, out *acme.ACMEIssuerDNS01ProviderAkamai, s conversion.Scope) error { +func Convert_v1_ACMEIssuerDNS01ProviderAkamai_To_acme_ACMEIssuerDNS01ProviderAkamai(in *acmev1.ACMEIssuerDNS01ProviderAkamai, out *acme.ACMEIssuerDNS01ProviderAkamai, s conversion.Scope) error { return autoConvert_v1_ACMEIssuerDNS01ProviderAkamai_To_acme_ACMEIssuerDNS01ProviderAkamai(in, out, s) } -func autoConvert_acme_ACMEIssuerDNS01ProviderAkamai_To_v1_ACMEIssuerDNS01ProviderAkamai(in *acme.ACMEIssuerDNS01ProviderAkamai, out *v1.ACMEIssuerDNS01ProviderAkamai, s conversion.Scope) error { +func autoConvert_acme_ACMEIssuerDNS01ProviderAkamai_To_v1_ACMEIssuerDNS01ProviderAkamai(in *acme.ACMEIssuerDNS01ProviderAkamai, out *acmev1.ACMEIssuerDNS01ProviderAkamai, s conversion.Scope) error { out.ServiceConsumerDomain = in.ServiceConsumerDomain if err := metav1.Convert_meta_SecretKeySelector_To_v1_SecretKeySelector(&in.ClientToken, &out.ClientToken, s); err != nil { return err @@ -999,11 +1121,11 @@ func autoConvert_acme_ACMEIssuerDNS01ProviderAkamai_To_v1_ACMEIssuerDNS01Provide } // Convert_acme_ACMEIssuerDNS01ProviderAkamai_To_v1_ACMEIssuerDNS01ProviderAkamai is an autogenerated conversion function. -func Convert_acme_ACMEIssuerDNS01ProviderAkamai_To_v1_ACMEIssuerDNS01ProviderAkamai(in *acme.ACMEIssuerDNS01ProviderAkamai, out *v1.ACMEIssuerDNS01ProviderAkamai, s conversion.Scope) error { +func Convert_acme_ACMEIssuerDNS01ProviderAkamai_To_v1_ACMEIssuerDNS01ProviderAkamai(in *acme.ACMEIssuerDNS01ProviderAkamai, out *acmev1.ACMEIssuerDNS01ProviderAkamai, s conversion.Scope) error { return autoConvert_acme_ACMEIssuerDNS01ProviderAkamai_To_v1_ACMEIssuerDNS01ProviderAkamai(in, out, s) } -func autoConvert_v1_ACMEIssuerDNS01ProviderAzureDNS_To_acme_ACMEIssuerDNS01ProviderAzureDNS(in *v1.ACMEIssuerDNS01ProviderAzureDNS, out *acme.ACMEIssuerDNS01ProviderAzureDNS, s conversion.Scope) error { +func autoConvert_v1_ACMEIssuerDNS01ProviderAzureDNS_To_acme_ACMEIssuerDNS01ProviderAzureDNS(in *acmev1.ACMEIssuerDNS01ProviderAzureDNS, out *acme.ACMEIssuerDNS01ProviderAzureDNS, s conversion.Scope) error { out.ClientID = in.ClientID if in.ClientSecret != nil { in, out := &in.ClientSecret, &out.ClientSecret @@ -1024,11 +1146,11 @@ func autoConvert_v1_ACMEIssuerDNS01ProviderAzureDNS_To_acme_ACMEIssuerDNS01Provi } // Convert_v1_ACMEIssuerDNS01ProviderAzureDNS_To_acme_ACMEIssuerDNS01ProviderAzureDNS is an autogenerated conversion function. -func Convert_v1_ACMEIssuerDNS01ProviderAzureDNS_To_acme_ACMEIssuerDNS01ProviderAzureDNS(in *v1.ACMEIssuerDNS01ProviderAzureDNS, out *acme.ACMEIssuerDNS01ProviderAzureDNS, s conversion.Scope) error { +func Convert_v1_ACMEIssuerDNS01ProviderAzureDNS_To_acme_ACMEIssuerDNS01ProviderAzureDNS(in *acmev1.ACMEIssuerDNS01ProviderAzureDNS, out *acme.ACMEIssuerDNS01ProviderAzureDNS, s conversion.Scope) error { return autoConvert_v1_ACMEIssuerDNS01ProviderAzureDNS_To_acme_ACMEIssuerDNS01ProviderAzureDNS(in, out, s) } -func autoConvert_acme_ACMEIssuerDNS01ProviderAzureDNS_To_v1_ACMEIssuerDNS01ProviderAzureDNS(in *acme.ACMEIssuerDNS01ProviderAzureDNS, out *v1.ACMEIssuerDNS01ProviderAzureDNS, s conversion.Scope) error { +func autoConvert_acme_ACMEIssuerDNS01ProviderAzureDNS_To_v1_ACMEIssuerDNS01ProviderAzureDNS(in *acme.ACMEIssuerDNS01ProviderAzureDNS, out *acmev1.ACMEIssuerDNS01ProviderAzureDNS, s conversion.Scope) error { out.ClientID = in.ClientID if in.ClientSecret != nil { in, out := &in.ClientSecret, &out.ClientSecret @@ -1043,17 +1165,17 @@ func autoConvert_acme_ACMEIssuerDNS01ProviderAzureDNS_To_v1_ACMEIssuerDNS01Provi out.TenantID = in.TenantID out.ResourceGroupName = in.ResourceGroupName out.HostedZoneName = in.HostedZoneName - out.Environment = v1.AzureDNSEnvironment(in.Environment) - out.ManagedIdentity = (*v1.AzureManagedIdentity)(unsafe.Pointer(in.ManagedIdentity)) + out.Environment = acmev1.AzureDNSEnvironment(in.Environment) + out.ManagedIdentity = (*acmev1.AzureManagedIdentity)(unsafe.Pointer(in.ManagedIdentity)) return nil } // Convert_acme_ACMEIssuerDNS01ProviderAzureDNS_To_v1_ACMEIssuerDNS01ProviderAzureDNS is an autogenerated conversion function. -func Convert_acme_ACMEIssuerDNS01ProviderAzureDNS_To_v1_ACMEIssuerDNS01ProviderAzureDNS(in *acme.ACMEIssuerDNS01ProviderAzureDNS, out *v1.ACMEIssuerDNS01ProviderAzureDNS, s conversion.Scope) error { +func Convert_acme_ACMEIssuerDNS01ProviderAzureDNS_To_v1_ACMEIssuerDNS01ProviderAzureDNS(in *acme.ACMEIssuerDNS01ProviderAzureDNS, out *acmev1.ACMEIssuerDNS01ProviderAzureDNS, s conversion.Scope) error { return autoConvert_acme_ACMEIssuerDNS01ProviderAzureDNS_To_v1_ACMEIssuerDNS01ProviderAzureDNS(in, out, s) } -func autoConvert_v1_ACMEIssuerDNS01ProviderCloudDNS_To_acme_ACMEIssuerDNS01ProviderCloudDNS(in *v1.ACMEIssuerDNS01ProviderCloudDNS, out *acme.ACMEIssuerDNS01ProviderCloudDNS, s conversion.Scope) error { +func autoConvert_v1_ACMEIssuerDNS01ProviderCloudDNS_To_acme_ACMEIssuerDNS01ProviderCloudDNS(in *acmev1.ACMEIssuerDNS01ProviderCloudDNS, out *acme.ACMEIssuerDNS01ProviderCloudDNS, s conversion.Scope) error { if in.ServiceAccount != nil { in, out := &in.ServiceAccount, &out.ServiceAccount *out = new(meta.SecretKeySelector) @@ -1069,11 +1191,11 @@ func autoConvert_v1_ACMEIssuerDNS01ProviderCloudDNS_To_acme_ACMEIssuerDNS01Provi } // Convert_v1_ACMEIssuerDNS01ProviderCloudDNS_To_acme_ACMEIssuerDNS01ProviderCloudDNS is an autogenerated conversion function. -func Convert_v1_ACMEIssuerDNS01ProviderCloudDNS_To_acme_ACMEIssuerDNS01ProviderCloudDNS(in *v1.ACMEIssuerDNS01ProviderCloudDNS, out *acme.ACMEIssuerDNS01ProviderCloudDNS, s conversion.Scope) error { +func Convert_v1_ACMEIssuerDNS01ProviderCloudDNS_To_acme_ACMEIssuerDNS01ProviderCloudDNS(in *acmev1.ACMEIssuerDNS01ProviderCloudDNS, out *acme.ACMEIssuerDNS01ProviderCloudDNS, s conversion.Scope) error { return autoConvert_v1_ACMEIssuerDNS01ProviderCloudDNS_To_acme_ACMEIssuerDNS01ProviderCloudDNS(in, out, s) } -func autoConvert_acme_ACMEIssuerDNS01ProviderCloudDNS_To_v1_ACMEIssuerDNS01ProviderCloudDNS(in *acme.ACMEIssuerDNS01ProviderCloudDNS, out *v1.ACMEIssuerDNS01ProviderCloudDNS, s conversion.Scope) error { +func autoConvert_acme_ACMEIssuerDNS01ProviderCloudDNS_To_v1_ACMEIssuerDNS01ProviderCloudDNS(in *acme.ACMEIssuerDNS01ProviderCloudDNS, out *acmev1.ACMEIssuerDNS01ProviderCloudDNS, s conversion.Scope) error { if in.ServiceAccount != nil { in, out := &in.ServiceAccount, &out.ServiceAccount *out = new(apismetav1.SecretKeySelector) @@ -1089,11 +1211,11 @@ func autoConvert_acme_ACMEIssuerDNS01ProviderCloudDNS_To_v1_ACMEIssuerDNS01Provi } // Convert_acme_ACMEIssuerDNS01ProviderCloudDNS_To_v1_ACMEIssuerDNS01ProviderCloudDNS is an autogenerated conversion function. -func Convert_acme_ACMEIssuerDNS01ProviderCloudDNS_To_v1_ACMEIssuerDNS01ProviderCloudDNS(in *acme.ACMEIssuerDNS01ProviderCloudDNS, out *v1.ACMEIssuerDNS01ProviderCloudDNS, s conversion.Scope) error { +func Convert_acme_ACMEIssuerDNS01ProviderCloudDNS_To_v1_ACMEIssuerDNS01ProviderCloudDNS(in *acme.ACMEIssuerDNS01ProviderCloudDNS, out *acmev1.ACMEIssuerDNS01ProviderCloudDNS, s conversion.Scope) error { return autoConvert_acme_ACMEIssuerDNS01ProviderCloudDNS_To_v1_ACMEIssuerDNS01ProviderCloudDNS(in, out, s) } -func autoConvert_v1_ACMEIssuerDNS01ProviderCloudflare_To_acme_ACMEIssuerDNS01ProviderCloudflare(in *v1.ACMEIssuerDNS01ProviderCloudflare, out *acme.ACMEIssuerDNS01ProviderCloudflare, s conversion.Scope) error { +func autoConvert_v1_ACMEIssuerDNS01ProviderCloudflare_To_acme_ACMEIssuerDNS01ProviderCloudflare(in *acmev1.ACMEIssuerDNS01ProviderCloudflare, out *acme.ACMEIssuerDNS01ProviderCloudflare, s conversion.Scope) error { out.Email = in.Email if in.APIKey != nil { in, out := &in.APIKey, &out.APIKey @@ -1117,11 +1239,11 @@ func autoConvert_v1_ACMEIssuerDNS01ProviderCloudflare_To_acme_ACMEIssuerDNS01Pro } // Convert_v1_ACMEIssuerDNS01ProviderCloudflare_To_acme_ACMEIssuerDNS01ProviderCloudflare is an autogenerated conversion function. -func Convert_v1_ACMEIssuerDNS01ProviderCloudflare_To_acme_ACMEIssuerDNS01ProviderCloudflare(in *v1.ACMEIssuerDNS01ProviderCloudflare, out *acme.ACMEIssuerDNS01ProviderCloudflare, s conversion.Scope) error { +func Convert_v1_ACMEIssuerDNS01ProviderCloudflare_To_acme_ACMEIssuerDNS01ProviderCloudflare(in *acmev1.ACMEIssuerDNS01ProviderCloudflare, out *acme.ACMEIssuerDNS01ProviderCloudflare, s conversion.Scope) error { return autoConvert_v1_ACMEIssuerDNS01ProviderCloudflare_To_acme_ACMEIssuerDNS01ProviderCloudflare(in, out, s) } -func autoConvert_acme_ACMEIssuerDNS01ProviderCloudflare_To_v1_ACMEIssuerDNS01ProviderCloudflare(in *acme.ACMEIssuerDNS01ProviderCloudflare, out *v1.ACMEIssuerDNS01ProviderCloudflare, s conversion.Scope) error { +func autoConvert_acme_ACMEIssuerDNS01ProviderCloudflare_To_v1_ACMEIssuerDNS01ProviderCloudflare(in *acme.ACMEIssuerDNS01ProviderCloudflare, out *acmev1.ACMEIssuerDNS01ProviderCloudflare, s conversion.Scope) error { out.Email = in.Email if in.APIKey != nil { in, out := &in.APIKey, &out.APIKey @@ -1145,11 +1267,11 @@ func autoConvert_acme_ACMEIssuerDNS01ProviderCloudflare_To_v1_ACMEIssuerDNS01Pro } // Convert_acme_ACMEIssuerDNS01ProviderCloudflare_To_v1_ACMEIssuerDNS01ProviderCloudflare is an autogenerated conversion function. -func Convert_acme_ACMEIssuerDNS01ProviderCloudflare_To_v1_ACMEIssuerDNS01ProviderCloudflare(in *acme.ACMEIssuerDNS01ProviderCloudflare, out *v1.ACMEIssuerDNS01ProviderCloudflare, s conversion.Scope) error { +func Convert_acme_ACMEIssuerDNS01ProviderCloudflare_To_v1_ACMEIssuerDNS01ProviderCloudflare(in *acme.ACMEIssuerDNS01ProviderCloudflare, out *acmev1.ACMEIssuerDNS01ProviderCloudflare, s conversion.Scope) error { return autoConvert_acme_ACMEIssuerDNS01ProviderCloudflare_To_v1_ACMEIssuerDNS01ProviderCloudflare(in, out, s) } -func autoConvert_v1_ACMEIssuerDNS01ProviderDigitalOcean_To_acme_ACMEIssuerDNS01ProviderDigitalOcean(in *v1.ACMEIssuerDNS01ProviderDigitalOcean, out *acme.ACMEIssuerDNS01ProviderDigitalOcean, s conversion.Scope) error { +func autoConvert_v1_ACMEIssuerDNS01ProviderDigitalOcean_To_acme_ACMEIssuerDNS01ProviderDigitalOcean(in *acmev1.ACMEIssuerDNS01ProviderDigitalOcean, out *acme.ACMEIssuerDNS01ProviderDigitalOcean, s conversion.Scope) error { if err := metav1.Convert_v1_SecretKeySelector_To_meta_SecretKeySelector(&in.Token, &out.Token, s); err != nil { return err } @@ -1157,11 +1279,11 @@ func autoConvert_v1_ACMEIssuerDNS01ProviderDigitalOcean_To_acme_ACMEIssuerDNS01P } // Convert_v1_ACMEIssuerDNS01ProviderDigitalOcean_To_acme_ACMEIssuerDNS01ProviderDigitalOcean is an autogenerated conversion function. -func Convert_v1_ACMEIssuerDNS01ProviderDigitalOcean_To_acme_ACMEIssuerDNS01ProviderDigitalOcean(in *v1.ACMEIssuerDNS01ProviderDigitalOcean, out *acme.ACMEIssuerDNS01ProviderDigitalOcean, s conversion.Scope) error { +func Convert_v1_ACMEIssuerDNS01ProviderDigitalOcean_To_acme_ACMEIssuerDNS01ProviderDigitalOcean(in *acmev1.ACMEIssuerDNS01ProviderDigitalOcean, out *acme.ACMEIssuerDNS01ProviderDigitalOcean, s conversion.Scope) error { return autoConvert_v1_ACMEIssuerDNS01ProviderDigitalOcean_To_acme_ACMEIssuerDNS01ProviderDigitalOcean(in, out, s) } -func autoConvert_acme_ACMEIssuerDNS01ProviderDigitalOcean_To_v1_ACMEIssuerDNS01ProviderDigitalOcean(in *acme.ACMEIssuerDNS01ProviderDigitalOcean, out *v1.ACMEIssuerDNS01ProviderDigitalOcean, s conversion.Scope) error { +func autoConvert_acme_ACMEIssuerDNS01ProviderDigitalOcean_To_v1_ACMEIssuerDNS01ProviderDigitalOcean(in *acme.ACMEIssuerDNS01ProviderDigitalOcean, out *acmev1.ACMEIssuerDNS01ProviderDigitalOcean, s conversion.Scope) error { if err := metav1.Convert_meta_SecretKeySelector_To_v1_SecretKeySelector(&in.Token, &out.Token, s); err != nil { return err } @@ -1169,41 +1291,44 @@ func autoConvert_acme_ACMEIssuerDNS01ProviderDigitalOcean_To_v1_ACMEIssuerDNS01P } // Convert_acme_ACMEIssuerDNS01ProviderDigitalOcean_To_v1_ACMEIssuerDNS01ProviderDigitalOcean is an autogenerated conversion function. -func Convert_acme_ACMEIssuerDNS01ProviderDigitalOcean_To_v1_ACMEIssuerDNS01ProviderDigitalOcean(in *acme.ACMEIssuerDNS01ProviderDigitalOcean, out *v1.ACMEIssuerDNS01ProviderDigitalOcean, s conversion.Scope) error { +func Convert_acme_ACMEIssuerDNS01ProviderDigitalOcean_To_v1_ACMEIssuerDNS01ProviderDigitalOcean(in *acme.ACMEIssuerDNS01ProviderDigitalOcean, out *acmev1.ACMEIssuerDNS01ProviderDigitalOcean, s conversion.Scope) error { return autoConvert_acme_ACMEIssuerDNS01ProviderDigitalOcean_To_v1_ACMEIssuerDNS01ProviderDigitalOcean(in, out, s) } -func autoConvert_v1_ACMEIssuerDNS01ProviderRFC2136_To_acme_ACMEIssuerDNS01ProviderRFC2136(in *v1.ACMEIssuerDNS01ProviderRFC2136, out *acme.ACMEIssuerDNS01ProviderRFC2136, s conversion.Scope) error { +func autoConvert_v1_ACMEIssuerDNS01ProviderRFC2136_To_acme_ACMEIssuerDNS01ProviderRFC2136(in *acmev1.ACMEIssuerDNS01ProviderRFC2136, out *acme.ACMEIssuerDNS01ProviderRFC2136, s conversion.Scope) error { out.Nameserver = in.Nameserver if err := metav1.Convert_v1_SecretKeySelector_To_meta_SecretKeySelector(&in.TSIGSecret, &out.TSIGSecret, s); err != nil { return err } out.TSIGKeyName = in.TSIGKeyName out.TSIGAlgorithm = in.TSIGAlgorithm + out.Protocol = acme.RFC2136UpdateProtocol(in.Protocol) return nil } // Convert_v1_ACMEIssuerDNS01ProviderRFC2136_To_acme_ACMEIssuerDNS01ProviderRFC2136 is an autogenerated conversion function. -func Convert_v1_ACMEIssuerDNS01ProviderRFC2136_To_acme_ACMEIssuerDNS01ProviderRFC2136(in *v1.ACMEIssuerDNS01ProviderRFC2136, out *acme.ACMEIssuerDNS01ProviderRFC2136, s conversion.Scope) error { +func Convert_v1_ACMEIssuerDNS01ProviderRFC2136_To_acme_ACMEIssuerDNS01ProviderRFC2136(in *acmev1.ACMEIssuerDNS01ProviderRFC2136, out *acme.ACMEIssuerDNS01ProviderRFC2136, s conversion.Scope) error { return autoConvert_v1_ACMEIssuerDNS01ProviderRFC2136_To_acme_ACMEIssuerDNS01ProviderRFC2136(in, out, s) } -func autoConvert_acme_ACMEIssuerDNS01ProviderRFC2136_To_v1_ACMEIssuerDNS01ProviderRFC2136(in *acme.ACMEIssuerDNS01ProviderRFC2136, out *v1.ACMEIssuerDNS01ProviderRFC2136, s conversion.Scope) error { +func autoConvert_acme_ACMEIssuerDNS01ProviderRFC2136_To_v1_ACMEIssuerDNS01ProviderRFC2136(in *acme.ACMEIssuerDNS01ProviderRFC2136, out *acmev1.ACMEIssuerDNS01ProviderRFC2136, s conversion.Scope) error { out.Nameserver = in.Nameserver if err := metav1.Convert_meta_SecretKeySelector_To_v1_SecretKeySelector(&in.TSIGSecret, &out.TSIGSecret, s); err != nil { return err } out.TSIGKeyName = in.TSIGKeyName out.TSIGAlgorithm = in.TSIGAlgorithm + out.Protocol = acmev1.RFC2136UpdateProtocol(in.Protocol) return nil } // Convert_acme_ACMEIssuerDNS01ProviderRFC2136_To_v1_ACMEIssuerDNS01ProviderRFC2136 is an autogenerated conversion function. -func Convert_acme_ACMEIssuerDNS01ProviderRFC2136_To_v1_ACMEIssuerDNS01ProviderRFC2136(in *acme.ACMEIssuerDNS01ProviderRFC2136, out *v1.ACMEIssuerDNS01ProviderRFC2136, s conversion.Scope) error { +func Convert_acme_ACMEIssuerDNS01ProviderRFC2136_To_v1_ACMEIssuerDNS01ProviderRFC2136(in *acme.ACMEIssuerDNS01ProviderRFC2136, out *acmev1.ACMEIssuerDNS01ProviderRFC2136, s conversion.Scope) error { return autoConvert_acme_ACMEIssuerDNS01ProviderRFC2136_To_v1_ACMEIssuerDNS01ProviderRFC2136(in, out, s) } -func autoConvert_v1_ACMEIssuerDNS01ProviderRoute53_To_acme_ACMEIssuerDNS01ProviderRoute53(in *v1.ACMEIssuerDNS01ProviderRoute53, out *acme.ACMEIssuerDNS01ProviderRoute53, s conversion.Scope) error { +func autoConvert_v1_ACMEIssuerDNS01ProviderRoute53_To_acme_ACMEIssuerDNS01ProviderRoute53(in *acmev1.ACMEIssuerDNS01ProviderRoute53, out *acme.ACMEIssuerDNS01ProviderRoute53, s conversion.Scope) error { + out.Auth = (*acme.Route53Auth)(unsafe.Pointer(in.Auth)) out.AccessKeyID = in.AccessKeyID if in.SecretAccessKeyID != nil { in, out := &in.SecretAccessKeyID, &out.SecretAccessKeyID @@ -1224,11 +1349,12 @@ func autoConvert_v1_ACMEIssuerDNS01ProviderRoute53_To_acme_ACMEIssuerDNS01Provid } // Convert_v1_ACMEIssuerDNS01ProviderRoute53_To_acme_ACMEIssuerDNS01ProviderRoute53 is an autogenerated conversion function. -func Convert_v1_ACMEIssuerDNS01ProviderRoute53_To_acme_ACMEIssuerDNS01ProviderRoute53(in *v1.ACMEIssuerDNS01ProviderRoute53, out *acme.ACMEIssuerDNS01ProviderRoute53, s conversion.Scope) error { +func Convert_v1_ACMEIssuerDNS01ProviderRoute53_To_acme_ACMEIssuerDNS01ProviderRoute53(in *acmev1.ACMEIssuerDNS01ProviderRoute53, out *acme.ACMEIssuerDNS01ProviderRoute53, s conversion.Scope) error { return autoConvert_v1_ACMEIssuerDNS01ProviderRoute53_To_acme_ACMEIssuerDNS01ProviderRoute53(in, out, s) } -func autoConvert_acme_ACMEIssuerDNS01ProviderRoute53_To_v1_ACMEIssuerDNS01ProviderRoute53(in *acme.ACMEIssuerDNS01ProviderRoute53, out *v1.ACMEIssuerDNS01ProviderRoute53, s conversion.Scope) error { +func autoConvert_acme_ACMEIssuerDNS01ProviderRoute53_To_v1_ACMEIssuerDNS01ProviderRoute53(in *acme.ACMEIssuerDNS01ProviderRoute53, out *acmev1.ACMEIssuerDNS01ProviderRoute53, s conversion.Scope) error { + out.Auth = (*acmev1.Route53Auth)(unsafe.Pointer(in.Auth)) out.AccessKeyID = in.AccessKeyID if in.SecretAccessKeyID != nil { in, out := &in.SecretAccessKeyID, &out.SecretAccessKeyID @@ -1249,11 +1375,11 @@ func autoConvert_acme_ACMEIssuerDNS01ProviderRoute53_To_v1_ACMEIssuerDNS01Provid } // Convert_acme_ACMEIssuerDNS01ProviderRoute53_To_v1_ACMEIssuerDNS01ProviderRoute53 is an autogenerated conversion function. -func Convert_acme_ACMEIssuerDNS01ProviderRoute53_To_v1_ACMEIssuerDNS01ProviderRoute53(in *acme.ACMEIssuerDNS01ProviderRoute53, out *v1.ACMEIssuerDNS01ProviderRoute53, s conversion.Scope) error { +func Convert_acme_ACMEIssuerDNS01ProviderRoute53_To_v1_ACMEIssuerDNS01ProviderRoute53(in *acme.ACMEIssuerDNS01ProviderRoute53, out *acmev1.ACMEIssuerDNS01ProviderRoute53, s conversion.Scope) error { return autoConvert_acme_ACMEIssuerDNS01ProviderRoute53_To_v1_ACMEIssuerDNS01ProviderRoute53(in, out, s) } -func autoConvert_v1_ACMEIssuerDNS01ProviderWebhook_To_acme_ACMEIssuerDNS01ProviderWebhook(in *v1.ACMEIssuerDNS01ProviderWebhook, out *acme.ACMEIssuerDNS01ProviderWebhook, s conversion.Scope) error { +func autoConvert_v1_ACMEIssuerDNS01ProviderWebhook_To_acme_ACMEIssuerDNS01ProviderWebhook(in *acmev1.ACMEIssuerDNS01ProviderWebhook, out *acme.ACMEIssuerDNS01ProviderWebhook, s conversion.Scope) error { out.GroupName = in.GroupName out.SolverName = in.SolverName out.Config = (*apiextensionsv1.JSON)(unsafe.Pointer(in.Config)) @@ -1261,11 +1387,11 @@ func autoConvert_v1_ACMEIssuerDNS01ProviderWebhook_To_acme_ACMEIssuerDNS01Provid } // Convert_v1_ACMEIssuerDNS01ProviderWebhook_To_acme_ACMEIssuerDNS01ProviderWebhook is an autogenerated conversion function. -func Convert_v1_ACMEIssuerDNS01ProviderWebhook_To_acme_ACMEIssuerDNS01ProviderWebhook(in *v1.ACMEIssuerDNS01ProviderWebhook, out *acme.ACMEIssuerDNS01ProviderWebhook, s conversion.Scope) error { +func Convert_v1_ACMEIssuerDNS01ProviderWebhook_To_acme_ACMEIssuerDNS01ProviderWebhook(in *acmev1.ACMEIssuerDNS01ProviderWebhook, out *acme.ACMEIssuerDNS01ProviderWebhook, s conversion.Scope) error { return autoConvert_v1_ACMEIssuerDNS01ProviderWebhook_To_acme_ACMEIssuerDNS01ProviderWebhook(in, out, s) } -func autoConvert_acme_ACMEIssuerDNS01ProviderWebhook_To_v1_ACMEIssuerDNS01ProviderWebhook(in *acme.ACMEIssuerDNS01ProviderWebhook, out *v1.ACMEIssuerDNS01ProviderWebhook, s conversion.Scope) error { +func autoConvert_acme_ACMEIssuerDNS01ProviderWebhook_To_v1_ACMEIssuerDNS01ProviderWebhook(in *acme.ACMEIssuerDNS01ProviderWebhook, out *acmev1.ACMEIssuerDNS01ProviderWebhook, s conversion.Scope) error { out.GroupName = in.GroupName out.SolverName = in.SolverName out.Config = (*apiextensionsv1.JSON)(unsafe.Pointer(in.Config)) @@ -1273,55 +1399,59 @@ func autoConvert_acme_ACMEIssuerDNS01ProviderWebhook_To_v1_ACMEIssuerDNS01Provid } // Convert_acme_ACMEIssuerDNS01ProviderWebhook_To_v1_ACMEIssuerDNS01ProviderWebhook is an autogenerated conversion function. -func Convert_acme_ACMEIssuerDNS01ProviderWebhook_To_v1_ACMEIssuerDNS01ProviderWebhook(in *acme.ACMEIssuerDNS01ProviderWebhook, out *v1.ACMEIssuerDNS01ProviderWebhook, s conversion.Scope) error { +func Convert_acme_ACMEIssuerDNS01ProviderWebhook_To_v1_ACMEIssuerDNS01ProviderWebhook(in *acme.ACMEIssuerDNS01ProviderWebhook, out *acmev1.ACMEIssuerDNS01ProviderWebhook, s conversion.Scope) error { return autoConvert_acme_ACMEIssuerDNS01ProviderWebhook_To_v1_ACMEIssuerDNS01ProviderWebhook(in, out, s) } -func autoConvert_v1_ACMEIssuerStatus_To_acme_ACMEIssuerStatus(in *v1.ACMEIssuerStatus, out *acme.ACMEIssuerStatus, s conversion.Scope) error { +func autoConvert_v1_ACMEIssuerStatus_To_acme_ACMEIssuerStatus(in *acmev1.ACMEIssuerStatus, out *acme.ACMEIssuerStatus, s conversion.Scope) error { out.URI = in.URI out.LastRegisteredEmail = in.LastRegisteredEmail + out.LastPrivateKeyHash = in.LastPrivateKeyHash return nil } // Convert_v1_ACMEIssuerStatus_To_acme_ACMEIssuerStatus is an autogenerated conversion function. -func Convert_v1_ACMEIssuerStatus_To_acme_ACMEIssuerStatus(in *v1.ACMEIssuerStatus, out *acme.ACMEIssuerStatus, s conversion.Scope) error { +func Convert_v1_ACMEIssuerStatus_To_acme_ACMEIssuerStatus(in *acmev1.ACMEIssuerStatus, out *acme.ACMEIssuerStatus, s conversion.Scope) error { return autoConvert_v1_ACMEIssuerStatus_To_acme_ACMEIssuerStatus(in, out, s) } -func autoConvert_acme_ACMEIssuerStatus_To_v1_ACMEIssuerStatus(in *acme.ACMEIssuerStatus, out *v1.ACMEIssuerStatus, s conversion.Scope) error { +func autoConvert_acme_ACMEIssuerStatus_To_v1_ACMEIssuerStatus(in *acme.ACMEIssuerStatus, out *acmev1.ACMEIssuerStatus, s conversion.Scope) error { out.URI = in.URI out.LastRegisteredEmail = in.LastRegisteredEmail + out.LastPrivateKeyHash = in.LastPrivateKeyHash return nil } // Convert_acme_ACMEIssuerStatus_To_v1_ACMEIssuerStatus is an autogenerated conversion function. -func Convert_acme_ACMEIssuerStatus_To_v1_ACMEIssuerStatus(in *acme.ACMEIssuerStatus, out *v1.ACMEIssuerStatus, s conversion.Scope) error { +func Convert_acme_ACMEIssuerStatus_To_v1_ACMEIssuerStatus(in *acme.ACMEIssuerStatus, out *acmev1.ACMEIssuerStatus, s conversion.Scope) error { return autoConvert_acme_ACMEIssuerStatus_To_v1_ACMEIssuerStatus(in, out, s) } -func autoConvert_v1_AzureManagedIdentity_To_acme_AzureManagedIdentity(in *v1.AzureManagedIdentity, out *acme.AzureManagedIdentity, s conversion.Scope) error { +func autoConvert_v1_AzureManagedIdentity_To_acme_AzureManagedIdentity(in *acmev1.AzureManagedIdentity, out *acme.AzureManagedIdentity, s conversion.Scope) error { out.ClientID = in.ClientID out.ResourceID = in.ResourceID + out.TenantID = in.TenantID return nil } // Convert_v1_AzureManagedIdentity_To_acme_AzureManagedIdentity is an autogenerated conversion function. -func Convert_v1_AzureManagedIdentity_To_acme_AzureManagedIdentity(in *v1.AzureManagedIdentity, out *acme.AzureManagedIdentity, s conversion.Scope) error { +func Convert_v1_AzureManagedIdentity_To_acme_AzureManagedIdentity(in *acmev1.AzureManagedIdentity, out *acme.AzureManagedIdentity, s conversion.Scope) error { return autoConvert_v1_AzureManagedIdentity_To_acme_AzureManagedIdentity(in, out, s) } -func autoConvert_acme_AzureManagedIdentity_To_v1_AzureManagedIdentity(in *acme.AzureManagedIdentity, out *v1.AzureManagedIdentity, s conversion.Scope) error { +func autoConvert_acme_AzureManagedIdentity_To_v1_AzureManagedIdentity(in *acme.AzureManagedIdentity, out *acmev1.AzureManagedIdentity, s conversion.Scope) error { out.ClientID = in.ClientID out.ResourceID = in.ResourceID + out.TenantID = in.TenantID return nil } // Convert_acme_AzureManagedIdentity_To_v1_AzureManagedIdentity is an autogenerated conversion function. -func Convert_acme_AzureManagedIdentity_To_v1_AzureManagedIdentity(in *acme.AzureManagedIdentity, out *v1.AzureManagedIdentity, s conversion.Scope) error { +func Convert_acme_AzureManagedIdentity_To_v1_AzureManagedIdentity(in *acme.AzureManagedIdentity, out *acmev1.AzureManagedIdentity, s conversion.Scope) error { return autoConvert_acme_AzureManagedIdentity_To_v1_AzureManagedIdentity(in, out, s) } -func autoConvert_v1_CertificateDNSNameSelector_To_acme_CertificateDNSNameSelector(in *v1.CertificateDNSNameSelector, out *acme.CertificateDNSNameSelector, s conversion.Scope) error { +func autoConvert_v1_CertificateDNSNameSelector_To_acme_CertificateDNSNameSelector(in *acmev1.CertificateDNSNameSelector, out *acme.CertificateDNSNameSelector, s conversion.Scope) error { out.MatchLabels = *(*map[string]string)(unsafe.Pointer(&in.MatchLabels)) out.DNSNames = *(*[]string)(unsafe.Pointer(&in.DNSNames)) out.DNSZones = *(*[]string)(unsafe.Pointer(&in.DNSZones)) @@ -1329,11 +1459,11 @@ func autoConvert_v1_CertificateDNSNameSelector_To_acme_CertificateDNSNameSelecto } // Convert_v1_CertificateDNSNameSelector_To_acme_CertificateDNSNameSelector is an autogenerated conversion function. -func Convert_v1_CertificateDNSNameSelector_To_acme_CertificateDNSNameSelector(in *v1.CertificateDNSNameSelector, out *acme.CertificateDNSNameSelector, s conversion.Scope) error { +func Convert_v1_CertificateDNSNameSelector_To_acme_CertificateDNSNameSelector(in *acmev1.CertificateDNSNameSelector, out *acme.CertificateDNSNameSelector, s conversion.Scope) error { return autoConvert_v1_CertificateDNSNameSelector_To_acme_CertificateDNSNameSelector(in, out, s) } -func autoConvert_acme_CertificateDNSNameSelector_To_v1_CertificateDNSNameSelector(in *acme.CertificateDNSNameSelector, out *v1.CertificateDNSNameSelector, s conversion.Scope) error { +func autoConvert_acme_CertificateDNSNameSelector_To_v1_CertificateDNSNameSelector(in *acme.CertificateDNSNameSelector, out *acmev1.CertificateDNSNameSelector, s conversion.Scope) error { out.MatchLabels = *(*map[string]string)(unsafe.Pointer(&in.MatchLabels)) out.DNSNames = *(*[]string)(unsafe.Pointer(&in.DNSNames)) out.DNSZones = *(*[]string)(unsafe.Pointer(&in.DNSZones)) @@ -1341,11 +1471,11 @@ func autoConvert_acme_CertificateDNSNameSelector_To_v1_CertificateDNSNameSelecto } // Convert_acme_CertificateDNSNameSelector_To_v1_CertificateDNSNameSelector is an autogenerated conversion function. -func Convert_acme_CertificateDNSNameSelector_To_v1_CertificateDNSNameSelector(in *acme.CertificateDNSNameSelector, out *v1.CertificateDNSNameSelector, s conversion.Scope) error { +func Convert_acme_CertificateDNSNameSelector_To_v1_CertificateDNSNameSelector(in *acme.CertificateDNSNameSelector, out *acmev1.CertificateDNSNameSelector, s conversion.Scope) error { return autoConvert_acme_CertificateDNSNameSelector_To_v1_CertificateDNSNameSelector(in, out, s) } -func autoConvert_v1_Challenge_To_acme_Challenge(in *v1.Challenge, out *acme.Challenge, s conversion.Scope) error { +func autoConvert_v1_Challenge_To_acme_Challenge(in *acmev1.Challenge, out *acme.Challenge, s conversion.Scope) error { out.ObjectMeta = in.ObjectMeta if err := Convert_v1_ChallengeSpec_To_acme_ChallengeSpec(&in.Spec, &out.Spec, s); err != nil { return err @@ -1357,11 +1487,11 @@ func autoConvert_v1_Challenge_To_acme_Challenge(in *v1.Challenge, out *acme.Chal } // Convert_v1_Challenge_To_acme_Challenge is an autogenerated conversion function. -func Convert_v1_Challenge_To_acme_Challenge(in *v1.Challenge, out *acme.Challenge, s conversion.Scope) error { +func Convert_v1_Challenge_To_acme_Challenge(in *acmev1.Challenge, out *acme.Challenge, s conversion.Scope) error { return autoConvert_v1_Challenge_To_acme_Challenge(in, out, s) } -func autoConvert_acme_Challenge_To_v1_Challenge(in *acme.Challenge, out *v1.Challenge, s conversion.Scope) error { +func autoConvert_acme_Challenge_To_v1_Challenge(in *acme.Challenge, out *acmev1.Challenge, s conversion.Scope) error { out.ObjectMeta = in.ObjectMeta if err := Convert_acme_ChallengeSpec_To_v1_ChallengeSpec(&in.Spec, &out.Spec, s); err != nil { return err @@ -1373,11 +1503,11 @@ func autoConvert_acme_Challenge_To_v1_Challenge(in *acme.Challenge, out *v1.Chal } // Convert_acme_Challenge_To_v1_Challenge is an autogenerated conversion function. -func Convert_acme_Challenge_To_v1_Challenge(in *acme.Challenge, out *v1.Challenge, s conversion.Scope) error { +func Convert_acme_Challenge_To_v1_Challenge(in *acme.Challenge, out *acmev1.Challenge, s conversion.Scope) error { return autoConvert_acme_Challenge_To_v1_Challenge(in, out, s) } -func autoConvert_v1_ChallengeList_To_acme_ChallengeList(in *v1.ChallengeList, out *acme.ChallengeList, s conversion.Scope) error { +func autoConvert_v1_ChallengeList_To_acme_ChallengeList(in *acmev1.ChallengeList, out *acme.ChallengeList, s conversion.Scope) error { out.ListMeta = in.ListMeta if in.Items != nil { in, out := &in.Items, &out.Items @@ -1394,15 +1524,15 @@ func autoConvert_v1_ChallengeList_To_acme_ChallengeList(in *v1.ChallengeList, ou } // Convert_v1_ChallengeList_To_acme_ChallengeList is an autogenerated conversion function. -func Convert_v1_ChallengeList_To_acme_ChallengeList(in *v1.ChallengeList, out *acme.ChallengeList, s conversion.Scope) error { +func Convert_v1_ChallengeList_To_acme_ChallengeList(in *acmev1.ChallengeList, out *acme.ChallengeList, s conversion.Scope) error { return autoConvert_v1_ChallengeList_To_acme_ChallengeList(in, out, s) } -func autoConvert_acme_ChallengeList_To_v1_ChallengeList(in *acme.ChallengeList, out *v1.ChallengeList, s conversion.Scope) error { +func autoConvert_acme_ChallengeList_To_v1_ChallengeList(in *acme.ChallengeList, out *acmev1.ChallengeList, s conversion.Scope) error { out.ListMeta = in.ListMeta if in.Items != nil { in, out := &in.Items, &out.Items - *out = make([]v1.Challenge, len(*in)) + *out = make([]acmev1.Challenge, len(*in)) for i := range *in { if err := Convert_acme_Challenge_To_v1_Challenge(&(*in)[i], &(*out)[i], s); err != nil { return err @@ -1415,11 +1545,11 @@ func autoConvert_acme_ChallengeList_To_v1_ChallengeList(in *acme.ChallengeList, } // Convert_acme_ChallengeList_To_v1_ChallengeList is an autogenerated conversion function. -func Convert_acme_ChallengeList_To_v1_ChallengeList(in *acme.ChallengeList, out *v1.ChallengeList, s conversion.Scope) error { +func Convert_acme_ChallengeList_To_v1_ChallengeList(in *acme.ChallengeList, out *acmev1.ChallengeList, s conversion.Scope) error { return autoConvert_acme_ChallengeList_To_v1_ChallengeList(in, out, s) } -func autoConvert_v1_ChallengeSpec_To_acme_ChallengeSpec(in *v1.ChallengeSpec, out *acme.ChallengeSpec, s conversion.Scope) error { +func autoConvert_v1_ChallengeSpec_To_acme_ChallengeSpec(in *acmev1.ChallengeSpec, out *acme.ChallengeSpec, s conversion.Scope) error { out.URL = in.URL out.AuthorizationURL = in.AuthorizationURL out.DNSName = in.DNSName @@ -1430,40 +1560,40 @@ func autoConvert_v1_ChallengeSpec_To_acme_ChallengeSpec(in *v1.ChallengeSpec, ou if err := Convert_v1_ACMEChallengeSolver_To_acme_ACMEChallengeSolver(&in.Solver, &out.Solver, s); err != nil { return err } - if err := metav1.Convert_v1_ObjectReference_To_meta_ObjectReference(&in.IssuerRef, &out.IssuerRef, s); err != nil { + if err := metav1.Convert_v1_IssuerReference_To_meta_IssuerReference(&in.IssuerRef, &out.IssuerRef, s); err != nil { return err } return nil } // Convert_v1_ChallengeSpec_To_acme_ChallengeSpec is an autogenerated conversion function. -func Convert_v1_ChallengeSpec_To_acme_ChallengeSpec(in *v1.ChallengeSpec, out *acme.ChallengeSpec, s conversion.Scope) error { +func Convert_v1_ChallengeSpec_To_acme_ChallengeSpec(in *acmev1.ChallengeSpec, out *acme.ChallengeSpec, s conversion.Scope) error { return autoConvert_v1_ChallengeSpec_To_acme_ChallengeSpec(in, out, s) } -func autoConvert_acme_ChallengeSpec_To_v1_ChallengeSpec(in *acme.ChallengeSpec, out *v1.ChallengeSpec, s conversion.Scope) error { +func autoConvert_acme_ChallengeSpec_To_v1_ChallengeSpec(in *acme.ChallengeSpec, out *acmev1.ChallengeSpec, s conversion.Scope) error { out.URL = in.URL out.AuthorizationURL = in.AuthorizationURL out.DNSName = in.DNSName out.Wildcard = in.Wildcard - out.Type = v1.ACMEChallengeType(in.Type) + out.Type = acmev1.ACMEChallengeType(in.Type) out.Token = in.Token out.Key = in.Key if err := Convert_acme_ACMEChallengeSolver_To_v1_ACMEChallengeSolver(&in.Solver, &out.Solver, s); err != nil { return err } - if err := metav1.Convert_meta_ObjectReference_To_v1_ObjectReference(&in.IssuerRef, &out.IssuerRef, s); err != nil { + if err := metav1.Convert_meta_IssuerReference_To_v1_IssuerReference(&in.IssuerRef, &out.IssuerRef, s); err != nil { return err } return nil } // Convert_acme_ChallengeSpec_To_v1_ChallengeSpec is an autogenerated conversion function. -func Convert_acme_ChallengeSpec_To_v1_ChallengeSpec(in *acme.ChallengeSpec, out *v1.ChallengeSpec, s conversion.Scope) error { +func Convert_acme_ChallengeSpec_To_v1_ChallengeSpec(in *acme.ChallengeSpec, out *acmev1.ChallengeSpec, s conversion.Scope) error { return autoConvert_acme_ChallengeSpec_To_v1_ChallengeSpec(in, out, s) } -func autoConvert_v1_ChallengeStatus_To_acme_ChallengeStatus(in *v1.ChallengeStatus, out *acme.ChallengeStatus, s conversion.Scope) error { +func autoConvert_v1_ChallengeStatus_To_acme_ChallengeStatus(in *acmev1.ChallengeStatus, out *acme.ChallengeStatus, s conversion.Scope) error { out.Processing = in.Processing out.Presented = in.Presented out.Reason = in.Reason @@ -1472,24 +1602,24 @@ func autoConvert_v1_ChallengeStatus_To_acme_ChallengeStatus(in *v1.ChallengeStat } // Convert_v1_ChallengeStatus_To_acme_ChallengeStatus is an autogenerated conversion function. -func Convert_v1_ChallengeStatus_To_acme_ChallengeStatus(in *v1.ChallengeStatus, out *acme.ChallengeStatus, s conversion.Scope) error { +func Convert_v1_ChallengeStatus_To_acme_ChallengeStatus(in *acmev1.ChallengeStatus, out *acme.ChallengeStatus, s conversion.Scope) error { return autoConvert_v1_ChallengeStatus_To_acme_ChallengeStatus(in, out, s) } -func autoConvert_acme_ChallengeStatus_To_v1_ChallengeStatus(in *acme.ChallengeStatus, out *v1.ChallengeStatus, s conversion.Scope) error { +func autoConvert_acme_ChallengeStatus_To_v1_ChallengeStatus(in *acme.ChallengeStatus, out *acmev1.ChallengeStatus, s conversion.Scope) error { out.Processing = in.Processing out.Presented = in.Presented out.Reason = in.Reason - out.State = v1.State(in.State) + out.State = acmev1.State(in.State) return nil } // Convert_acme_ChallengeStatus_To_v1_ChallengeStatus is an autogenerated conversion function. -func Convert_acme_ChallengeStatus_To_v1_ChallengeStatus(in *acme.ChallengeStatus, out *v1.ChallengeStatus, s conversion.Scope) error { +func Convert_acme_ChallengeStatus_To_v1_ChallengeStatus(in *acme.ChallengeStatus, out *acmev1.ChallengeStatus, s conversion.Scope) error { return autoConvert_acme_ChallengeStatus_To_v1_ChallengeStatus(in, out, s) } -func autoConvert_v1_Order_To_acme_Order(in *v1.Order, out *acme.Order, s conversion.Scope) error { +func autoConvert_v1_Order_To_acme_Order(in *acmev1.Order, out *acme.Order, s conversion.Scope) error { out.ObjectMeta = in.ObjectMeta if err := Convert_v1_OrderSpec_To_acme_OrderSpec(&in.Spec, &out.Spec, s); err != nil { return err @@ -1501,11 +1631,11 @@ func autoConvert_v1_Order_To_acme_Order(in *v1.Order, out *acme.Order, s convers } // Convert_v1_Order_To_acme_Order is an autogenerated conversion function. -func Convert_v1_Order_To_acme_Order(in *v1.Order, out *acme.Order, s conversion.Scope) error { +func Convert_v1_Order_To_acme_Order(in *acmev1.Order, out *acme.Order, s conversion.Scope) error { return autoConvert_v1_Order_To_acme_Order(in, out, s) } -func autoConvert_acme_Order_To_v1_Order(in *acme.Order, out *v1.Order, s conversion.Scope) error { +func autoConvert_acme_Order_To_v1_Order(in *acme.Order, out *acmev1.Order, s conversion.Scope) error { out.ObjectMeta = in.ObjectMeta if err := Convert_acme_OrderSpec_To_v1_OrderSpec(&in.Spec, &out.Spec, s); err != nil { return err @@ -1517,11 +1647,11 @@ func autoConvert_acme_Order_To_v1_Order(in *acme.Order, out *v1.Order, s convers } // Convert_acme_Order_To_v1_Order is an autogenerated conversion function. -func Convert_acme_Order_To_v1_Order(in *acme.Order, out *v1.Order, s conversion.Scope) error { +func Convert_acme_Order_To_v1_Order(in *acme.Order, out *acmev1.Order, s conversion.Scope) error { return autoConvert_acme_Order_To_v1_Order(in, out, s) } -func autoConvert_v1_OrderList_To_acme_OrderList(in *v1.OrderList, out *acme.OrderList, s conversion.Scope) error { +func autoConvert_v1_OrderList_To_acme_OrderList(in *acmev1.OrderList, out *acme.OrderList, s conversion.Scope) error { out.ListMeta = in.ListMeta if in.Items != nil { in, out := &in.Items, &out.Items @@ -1538,15 +1668,15 @@ func autoConvert_v1_OrderList_To_acme_OrderList(in *v1.OrderList, out *acme.Orde } // Convert_v1_OrderList_To_acme_OrderList is an autogenerated conversion function. -func Convert_v1_OrderList_To_acme_OrderList(in *v1.OrderList, out *acme.OrderList, s conversion.Scope) error { +func Convert_v1_OrderList_To_acme_OrderList(in *acmev1.OrderList, out *acme.OrderList, s conversion.Scope) error { return autoConvert_v1_OrderList_To_acme_OrderList(in, out, s) } -func autoConvert_acme_OrderList_To_v1_OrderList(in *acme.OrderList, out *v1.OrderList, s conversion.Scope) error { +func autoConvert_acme_OrderList_To_v1_OrderList(in *acme.OrderList, out *acmev1.OrderList, s conversion.Scope) error { out.ListMeta = in.ListMeta if in.Items != nil { in, out := &in.Items, &out.Items - *out = make([]v1.Order, len(*in)) + *out = make([]acmev1.Order, len(*in)) for i := range *in { if err := Convert_acme_Order_To_v1_Order(&(*in)[i], &(*out)[i], s); err != nil { return err @@ -1559,45 +1689,47 @@ func autoConvert_acme_OrderList_To_v1_OrderList(in *acme.OrderList, out *v1.Orde } // Convert_acme_OrderList_To_v1_OrderList is an autogenerated conversion function. -func Convert_acme_OrderList_To_v1_OrderList(in *acme.OrderList, out *v1.OrderList, s conversion.Scope) error { +func Convert_acme_OrderList_To_v1_OrderList(in *acme.OrderList, out *acmev1.OrderList, s conversion.Scope) error { return autoConvert_acme_OrderList_To_v1_OrderList(in, out, s) } -func autoConvert_v1_OrderSpec_To_acme_OrderSpec(in *v1.OrderSpec, out *acme.OrderSpec, s conversion.Scope) error { +func autoConvert_v1_OrderSpec_To_acme_OrderSpec(in *acmev1.OrderSpec, out *acme.OrderSpec, s conversion.Scope) error { out.Request = *(*[]byte)(unsafe.Pointer(&in.Request)) - if err := metav1.Convert_v1_ObjectReference_To_meta_ObjectReference(&in.IssuerRef, &out.IssuerRef, s); err != nil { + if err := metav1.Convert_v1_IssuerReference_To_meta_IssuerReference(&in.IssuerRef, &out.IssuerRef, s); err != nil { return err } out.CommonName = in.CommonName out.DNSNames = *(*[]string)(unsafe.Pointer(&in.DNSNames)) out.IPAddresses = *(*[]string)(unsafe.Pointer(&in.IPAddresses)) out.Duration = (*pkgapismetav1.Duration)(unsafe.Pointer(in.Duration)) + out.Profile = in.Profile return nil } // Convert_v1_OrderSpec_To_acme_OrderSpec is an autogenerated conversion function. -func Convert_v1_OrderSpec_To_acme_OrderSpec(in *v1.OrderSpec, out *acme.OrderSpec, s conversion.Scope) error { +func Convert_v1_OrderSpec_To_acme_OrderSpec(in *acmev1.OrderSpec, out *acme.OrderSpec, s conversion.Scope) error { return autoConvert_v1_OrderSpec_To_acme_OrderSpec(in, out, s) } -func autoConvert_acme_OrderSpec_To_v1_OrderSpec(in *acme.OrderSpec, out *v1.OrderSpec, s conversion.Scope) error { +func autoConvert_acme_OrderSpec_To_v1_OrderSpec(in *acme.OrderSpec, out *acmev1.OrderSpec, s conversion.Scope) error { out.Request = *(*[]byte)(unsafe.Pointer(&in.Request)) - if err := metav1.Convert_meta_ObjectReference_To_v1_ObjectReference(&in.IssuerRef, &out.IssuerRef, s); err != nil { + if err := metav1.Convert_meta_IssuerReference_To_v1_IssuerReference(&in.IssuerRef, &out.IssuerRef, s); err != nil { return err } out.CommonName = in.CommonName out.DNSNames = *(*[]string)(unsafe.Pointer(&in.DNSNames)) out.IPAddresses = *(*[]string)(unsafe.Pointer(&in.IPAddresses)) out.Duration = (*pkgapismetav1.Duration)(unsafe.Pointer(in.Duration)) + out.Profile = in.Profile return nil } // Convert_acme_OrderSpec_To_v1_OrderSpec is an autogenerated conversion function. -func Convert_acme_OrderSpec_To_v1_OrderSpec(in *acme.OrderSpec, out *v1.OrderSpec, s conversion.Scope) error { +func Convert_acme_OrderSpec_To_v1_OrderSpec(in *acme.OrderSpec, out *acmev1.OrderSpec, s conversion.Scope) error { return autoConvert_acme_OrderSpec_To_v1_OrderSpec(in, out, s) } -func autoConvert_v1_OrderStatus_To_acme_OrderStatus(in *v1.OrderStatus, out *acme.OrderStatus, s conversion.Scope) error { +func autoConvert_v1_OrderStatus_To_acme_OrderStatus(in *acmev1.OrderStatus, out *acme.OrderStatus, s conversion.Scope) error { out.URL = in.URL out.FinalizeURL = in.FinalizeURL out.Authorizations = *(*[]acme.ACMEAuthorization)(unsafe.Pointer(&in.Authorizations)) @@ -1609,22 +1741,84 @@ func autoConvert_v1_OrderStatus_To_acme_OrderStatus(in *v1.OrderStatus, out *acm } // Convert_v1_OrderStatus_To_acme_OrderStatus is an autogenerated conversion function. -func Convert_v1_OrderStatus_To_acme_OrderStatus(in *v1.OrderStatus, out *acme.OrderStatus, s conversion.Scope) error { +func Convert_v1_OrderStatus_To_acme_OrderStatus(in *acmev1.OrderStatus, out *acme.OrderStatus, s conversion.Scope) error { return autoConvert_v1_OrderStatus_To_acme_OrderStatus(in, out, s) } -func autoConvert_acme_OrderStatus_To_v1_OrderStatus(in *acme.OrderStatus, out *v1.OrderStatus, s conversion.Scope) error { +func autoConvert_acme_OrderStatus_To_v1_OrderStatus(in *acme.OrderStatus, out *acmev1.OrderStatus, s conversion.Scope) error { out.URL = in.URL out.FinalizeURL = in.FinalizeURL out.Certificate = *(*[]byte)(unsafe.Pointer(&in.Certificate)) - out.State = v1.State(in.State) + out.State = acmev1.State(in.State) out.Reason = in.Reason - out.Authorizations = *(*[]v1.ACMEAuthorization)(unsafe.Pointer(&in.Authorizations)) + out.Authorizations = *(*[]acmev1.ACMEAuthorization)(unsafe.Pointer(&in.Authorizations)) out.FailureTime = (*pkgapismetav1.Time)(unsafe.Pointer(in.FailureTime)) return nil } // Convert_acme_OrderStatus_To_v1_OrderStatus is an autogenerated conversion function. -func Convert_acme_OrderStatus_To_v1_OrderStatus(in *acme.OrderStatus, out *v1.OrderStatus, s conversion.Scope) error { +func Convert_acme_OrderStatus_To_v1_OrderStatus(in *acme.OrderStatus, out *acmev1.OrderStatus, s conversion.Scope) error { return autoConvert_acme_OrderStatus_To_v1_OrderStatus(in, out, s) } + +func autoConvert_v1_Route53Auth_To_acme_Route53Auth(in *acmev1.Route53Auth, out *acme.Route53Auth, s conversion.Scope) error { + out.Kubernetes = (*acme.Route53KubernetesAuth)(unsafe.Pointer(in.Kubernetes)) + return nil +} + +// Convert_v1_Route53Auth_To_acme_Route53Auth is an autogenerated conversion function. +func Convert_v1_Route53Auth_To_acme_Route53Auth(in *acmev1.Route53Auth, out *acme.Route53Auth, s conversion.Scope) error { + return autoConvert_v1_Route53Auth_To_acme_Route53Auth(in, out, s) +} + +func autoConvert_acme_Route53Auth_To_v1_Route53Auth(in *acme.Route53Auth, out *acmev1.Route53Auth, s conversion.Scope) error { + out.Kubernetes = (*acmev1.Route53KubernetesAuth)(unsafe.Pointer(in.Kubernetes)) + return nil +} + +// Convert_acme_Route53Auth_To_v1_Route53Auth is an autogenerated conversion function. +func Convert_acme_Route53Auth_To_v1_Route53Auth(in *acme.Route53Auth, out *acmev1.Route53Auth, s conversion.Scope) error { + return autoConvert_acme_Route53Auth_To_v1_Route53Auth(in, out, s) +} + +func autoConvert_v1_Route53KubernetesAuth_To_acme_Route53KubernetesAuth(in *acmev1.Route53KubernetesAuth, out *acme.Route53KubernetesAuth, s conversion.Scope) error { + out.ServiceAccountRef = (*acme.ServiceAccountRef)(unsafe.Pointer(in.ServiceAccountRef)) + return nil +} + +// Convert_v1_Route53KubernetesAuth_To_acme_Route53KubernetesAuth is an autogenerated conversion function. +func Convert_v1_Route53KubernetesAuth_To_acme_Route53KubernetesAuth(in *acmev1.Route53KubernetesAuth, out *acme.Route53KubernetesAuth, s conversion.Scope) error { + return autoConvert_v1_Route53KubernetesAuth_To_acme_Route53KubernetesAuth(in, out, s) +} + +func autoConvert_acme_Route53KubernetesAuth_To_v1_Route53KubernetesAuth(in *acme.Route53KubernetesAuth, out *acmev1.Route53KubernetesAuth, s conversion.Scope) error { + out.ServiceAccountRef = (*acmev1.ServiceAccountRef)(unsafe.Pointer(in.ServiceAccountRef)) + return nil +} + +// Convert_acme_Route53KubernetesAuth_To_v1_Route53KubernetesAuth is an autogenerated conversion function. +func Convert_acme_Route53KubernetesAuth_To_v1_Route53KubernetesAuth(in *acme.Route53KubernetesAuth, out *acmev1.Route53KubernetesAuth, s conversion.Scope) error { + return autoConvert_acme_Route53KubernetesAuth_To_v1_Route53KubernetesAuth(in, out, s) +} + +func autoConvert_v1_ServiceAccountRef_To_acme_ServiceAccountRef(in *acmev1.ServiceAccountRef, out *acme.ServiceAccountRef, s conversion.Scope) error { + out.Name = in.Name + out.TokenAudiences = *(*[]string)(unsafe.Pointer(&in.TokenAudiences)) + return nil +} + +// Convert_v1_ServiceAccountRef_To_acme_ServiceAccountRef is an autogenerated conversion function. +func Convert_v1_ServiceAccountRef_To_acme_ServiceAccountRef(in *acmev1.ServiceAccountRef, out *acme.ServiceAccountRef, s conversion.Scope) error { + return autoConvert_v1_ServiceAccountRef_To_acme_ServiceAccountRef(in, out, s) +} + +func autoConvert_acme_ServiceAccountRef_To_v1_ServiceAccountRef(in *acme.ServiceAccountRef, out *acmev1.ServiceAccountRef, s conversion.Scope) error { + out.Name = in.Name + out.TokenAudiences = *(*[]string)(unsafe.Pointer(&in.TokenAudiences)) + return nil +} + +// Convert_acme_ServiceAccountRef_To_v1_ServiceAccountRef is an autogenerated conversion function. +func Convert_acme_ServiceAccountRef_To_v1_ServiceAccountRef(in *acme.ServiceAccountRef, out *acmev1.ServiceAccountRef, s conversion.Scope) error { + return autoConvert_acme_ServiceAccountRef_To_v1_ServiceAccountRef(in, out, s) +} diff --git a/internal/apis/acme/v1alpha2/conversion.go b/internal/apis/acme/v1alpha2/conversion.go deleted file mode 100644 index b1bdd5d2add..00000000000 --- a/internal/apis/acme/v1alpha2/conversion.go +++ /dev/null @@ -1,95 +0,0 @@ -/* -Copyright 2020 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. -*/ - -package v1alpha2 - -import ( - "k8s.io/apimachinery/pkg/conversion" - - "github.com/cert-manager/cert-manager/internal/apis/acme" -) - -func Convert_v1alpha2_ChallengeSpec_To_acme_ChallengeSpec(in *ChallengeSpec, out *acme.ChallengeSpec, s conversion.Scope) error { - if err := autoConvert_v1alpha2_ChallengeSpec_To_acme_ChallengeSpec(in, out, s); err != nil { - return err - } - - out.AuthorizationURL = in.AuthzURL - - switch in.Type { - case ACMEChallengeTypeHTTP01: - out.Type = acme.ACMEChallengeTypeHTTP01 - case ACMEChallengeTypeDNS01: - out.Type = acme.ACMEChallengeTypeDNS01 - default: - // this case should never be hit due to validation - out.Type = acme.ACMEChallengeType(in.Type) - } - - return nil -} - -func Convert_acme_ChallengeSpec_To_v1alpha2_ChallengeSpec(in *acme.ChallengeSpec, out *ChallengeSpec, s conversion.Scope) error { - if err := autoConvert_acme_ChallengeSpec_To_v1alpha2_ChallengeSpec(in, out, s); err != nil { - return err - } - - out.AuthzURL = in.AuthorizationURL - - switch in.Type { - case acme.ACMEChallengeTypeHTTP01: - out.Type = ACMEChallengeTypeHTTP01 - case acme.ACMEChallengeTypeDNS01: - out.Type = ACMEChallengeTypeDNS01 - default: - // this case should never be hit due to validation - out.Type = ACMEChallengeType(in.Type) - } - - return nil -} - -func Convert_v1alpha2_OrderSpec_To_acme_OrderSpec(in *OrderSpec, out *acme.OrderSpec, s conversion.Scope) error { - if err := autoConvert_v1alpha2_OrderSpec_To_acme_OrderSpec(in, out, s); err != nil { - return err - } - - out.Request = in.CSR - - return nil -} - -func Convert_acme_OrderSpec_To_v1alpha2_OrderSpec(in *acme.OrderSpec, out *OrderSpec, s conversion.Scope) error { - if err := autoConvert_acme_OrderSpec_To_v1alpha2_OrderSpec(in, out, s); err != nil { - return err - } - - out.CSR = in.Request - - return nil -} - -// Convert_acme_ACMEIssuer_To_v1alpha2_ACMEIssuer is explicitly defined to avoid issues in conversion-gen -// when referencing types in other API groups. -func Convert_acme_ACMEIssuer_To_v1alpha2_ACMEIssuer(in *acme.ACMEIssuer, out *ACMEIssuer, s conversion.Scope) error { - return autoConvert_acme_ACMEIssuer_To_v1alpha2_ACMEIssuer(in, out, s) -} - -// Convert_v1alpha2_ACMEIssuer_To_acme_ACMEIssuer is explicitly defined to avoid issues in conversion-gen -// when referencing types in other API groups. -func Convert_v1alpha2_ACMEIssuer_To_acme_ACMEIssuer(in *ACMEIssuer, out *acme.ACMEIssuer, s conversion.Scope) error { - return autoConvert_v1alpha2_ACMEIssuer_To_acme_ACMEIssuer(in, out, s) -} diff --git a/internal/apis/acme/v1alpha2/types.go b/internal/apis/acme/v1alpha2/types.go deleted file mode 100644 index d724a1ae5ac..00000000000 --- a/internal/apis/acme/v1alpha2/types.go +++ /dev/null @@ -1,38 +0,0 @@ -/* -Copyright 2020 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. -*/ - -package v1alpha2 - -const ( - // If this annotation is specified on a Certificate or Order resource when - // using the HTTP01 solver type, the ingress.name field of the HTTP01 - // solver's configuration will be set to the value given here. - // This is especially useful for users of Ingress controllers that maintain - // a 1:1 mapping between endpoint IP and Ingress resource. - ACMECertificateHTTP01IngressNameOverride = "acme.cert-manager.io/http01-override-ingress-name" - - // If this annotation is specified on a Certificate or Order resource when - // using the HTTP01 solver type, the ingress.class field of the HTTP01 - // solver's configuration will be set to the value given here. - // This is especially useful for users deploying many different ingress - // classes into a single cluster that want to be able to re-use a single - // solver for each ingress class. - ACMECertificateHTTP01IngressClassOverride = "acme.cert-manager.io/http01-override-ingress-class" - - // IngressEditInPlaceAnnotation is used to toggle the use of ingressClass instead - // of ingress on the created Certificate resource - IngressEditInPlaceAnnotationKey = "acme.cert-manager.io/http01-edit-in-place" -) diff --git a/internal/apis/acme/v1alpha2/types_challenge.go b/internal/apis/acme/v1alpha2/types_challenge.go deleted file mode 100644 index 16db715ba5d..00000000000 --- a/internal/apis/acme/v1alpha2/types_challenge.go +++ /dev/null @@ -1,145 +0,0 @@ -/* -Copyright 2020 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. -*/ - -package v1alpha2 - -import ( - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - - cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" -) - -// +genclient -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object - -// Challenge is a type to represent a Challenge request with an ACME server -// +k8s:openapi-gen=true -// +kubebuilder:printcolumn:name="State",type="string",JSONPath=".status.state" -// +kubebuilder:printcolumn:name="Domain",type="string",JSONPath=".spec.dnsName" -// +kubebuilder:printcolumn:name="Reason",type="string",JSONPath=".status.reason",description="",priority=1 -// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp",description="CreationTimestamp is a timestamp representing the server time when this object was created. It is not guaranteed to be set in happens-before order across separate operations. Clients may not set this value. It is represented in RFC3339 form and is in UTC." -// +kubebuilder:subresource:status -// +kubebuilder:resource:path=challenges -type Challenge struct { - metav1.TypeMeta `json:",inline"` - metav1.ObjectMeta `json:"metadata"` - - Spec ChallengeSpec `json:"spec,omitempty"` - Status ChallengeStatus `json:"status,omitempty"` -} - -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object - -// ChallengeList is a list of Challenges -type ChallengeList struct { - metav1.TypeMeta `json:",inline"` - metav1.ListMeta `json:"metadata"` - - Items []Challenge `json:"items"` -} - -type ChallengeSpec struct { - // URL is the URL of the ACME Challenge resource for this challenge. - // This can be used to lookup details about the status of this challenge. - URL string `json:"url"` - - // AuthzURL is the URL to the ACME Authorization resource that this - // challenge is a part of. - AuthzURL string `json:"authzURL"` - - // 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`. - DNSName string `json:"dnsName"` - - // Wildcard will be true if this challenge is for a wildcard identifier, - // for example '*.example.com'. - // +optional - Wildcard bool `json:"wildcard"` - - // Type is the type of ACME challenge this resource represents. - // One of "http-01" or "dns-01". - Type ACMEChallengeType `json:"type"` - - // Token is the ACME challenge token for this challenge. - // This is the raw value returned from the ACME server. - Token string `json:"token"` - - // Key is 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: - // `.`. - // For DNS01 challenges, this is the base64 encoded SHA256 sum of the - // `.` - // text that must be set as the TXT record content. - Key string `json:"key"` - - // Solver contains the domain solving configuration that should be used to - // solve this challenge resource. - Solver ACMEChallengeSolver `json:"solver"` - - // IssuerRef 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. - IssuerRef cmmeta.ObjectReference `json:"issuerRef"` -} - -// The type of ACME challenge. Only http-01 and dns-01 are supported. -// +kubebuilder:validation:Enum=http-01;dns-01 -type ACMEChallengeType string - -const ( - // ACMEChallengeTypeHTTP01 denotes a Challenge is of type http-01 - // More info: https://letsencrypt.org/docs/challenge-types/#http-01-challenge - ACMEChallengeTypeHTTP01 ACMEChallengeType = "http-01" - - // ACMEChallengeTypeDNS01 denotes a Challenge is of type dns-01 - // More info: https://letsencrypt.org/docs/challenge-types/#dns-01-challenge - ACMEChallengeTypeDNS01 ACMEChallengeType = "dns-01" -) - -type ChallengeStatus struct { - // Processing is 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. - // +optional - Processing bool `json:"processing"` - - // 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). - // +optional - Presented bool `json:"presented"` - - // Reason contains human readable information on why the Challenge is in the - // current state. - // +optional - Reason string `json:"reason,omitempty"` - - // State contains the current 'state' of the challenge. - // If not set, the state of the challenge is unknown. - // +optional - State State `json:"state,omitempty"` -} diff --git a/internal/apis/acme/v1alpha2/types_issuer.go b/internal/apis/acme/v1alpha2/types_issuer.go deleted file mode 100644 index 2866d5942b9..00000000000 --- a/internal/apis/acme/v1alpha2/types_issuer.go +++ /dev/null @@ -1,609 +0,0 @@ -/* -Copyright 2020 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. -*/ - -package v1alpha2 - -import ( - corev1 "k8s.io/api/core/v1" - apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" - gwapi "sigs.k8s.io/gateway-api/apis/v1alpha2" - - cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" -) - -// 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. -type ACMEIssuer struct { - // 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. - // +optional - Email string `json:"email,omitempty"` - - // 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. - Server string `json:"server"` - - // 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 - // +optional - // +kubebuilder:validation:MaxLength=64 - PreferredChain string `json:"preferredChain"` - - // Enables or disables validation of the ACME server TLS certificate. - // If true, requests to the ACME server will not have their TLS certificate - // validated (i.e. insecure connections will be allowed). - // Only enable this option in development environments. - // The cert-manager system installed roots will be used to verify connections - // to the ACME server if this is false. - // Defaults to false. - // +optional - SkipTLSVerify bool `json:"skipTLSVerify,omitempty"` - - // 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. - // +optional - ExternalAccountBinding *ACMEExternalAccountBinding `json:"externalAccountBinding,omitempty"` - - // 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. - PrivateKey cmmeta.SecretKeySelector `json:"privateKeySecretRef"` - - // 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/ - // +optional - Solvers []ACMEChallengeSolver `json:"solvers,omitempty"` - - // 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. - // +optional - DisableAccountKeyGeneration bool `json:"disableAccountKeyGeneration,omitempty"` - - // 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. - // +optional - EnableDurationFeature bool `json:"enableDurationFeature,omitempty"` -} - -// ACMEExternalAccountBinding is a reference to a CA external account of the ACME -// server. -type ACMEExternalAccountBinding struct { - // keyID is the ID of the CA key that the External Account is bound to. - KeyID string `json:"keyID"` - - // 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. - Key cmmeta.SecretKeySelector `json:"keySecretRef"` - - // 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. - // +optional - KeyAlgorithm HMACKeyAlgorithm `json:"keyAlgorithm,omitempty"` -} - -// HMACKeyAlgorithm is the name of a key algorithm used for HMAC encryption -// +kubebuilder:validation:Enum=HS256;HS384;HS512 -type HMACKeyAlgorithm string - -const ( - HS256 HMACKeyAlgorithm = "HS256" - HS384 HMACKeyAlgorithm = "HS384" - HS512 HMACKeyAlgorithm = "HS512" -) - -// Configures an issuer to solve challenges using the specified options. -// Only one of HTTP01 or DNS01 may be provided. -type ACMEChallengeSolver struct { - // 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. - // +optional - Selector *CertificateDNSNameSelector `json:"selector,omitempty"` - - // 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. - // +optional - HTTP01 *ACMEChallengeSolverHTTP01 `json:"http01,omitempty"` - - // Configures cert-manager to attempt to complete authorizations by - // performing the DNS01 challenge flow. - // +optional - DNS01 *ACMEChallengeSolverDNS01 `json:"dns01,omitempty"` -} - -// CertificateDomainSelector 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. -type CertificateDNSNameSelector struct { - // A label selector that is used to refine the set of certificate's that - // this challenge solver will apply to. - // +optional - MatchLabels map[string]string `json:"matchLabels,omitempty"` - - // 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. - // +optional - DNSNames []string `json:"dnsNames,omitempty"` - - // 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. - // +optional - DNSZones []string `json:"dnsZones,omitempty"` -} - -// 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. -type ACMEChallengeSolverHTTP01 struct { - // 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. - // +optional - Ingress *ACMEChallengeSolverHTTP01Ingress `json:"ingress,omitempty"` - - // 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. - // +optional - GatewayHTTPRoute *ACMEChallengeSolverHTTP01GatewayHTTPRoute `json:"gatewayHTTPRoute,omitempty"` -} - -type ACMEChallengeSolverHTTP01Ingress struct { - // Optional service type for Kubernetes solver service. Supported values - // are NodePort or ClusterIP. If unset, defaults to NodePort. - // +optional - ServiceType corev1.ServiceType `json:"serviceType,omitempty"` - - // 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. - // +optional - Class *string `json:"class,omitempty"` - - // 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. - // +optional - Name string `json:"name,omitempty"` - - // Optional pod template used to configure the ACME challenge solver pods - // used for HTTP01 challenges. - // +optional - PodTemplate *ACMEChallengeSolverHTTP01IngressPodTemplate `json:"podTemplate,omitempty"` - - // Optional ingress template used to configure the ACME challenge solver - // ingress used for HTTP01 challenges - // +optional - IngressTemplate *ACMEChallengeSolverHTTP01IngressTemplate `json:"ingressTemplate,omitempty"` -} - -type ACMEChallengeSolverHTTP01GatewayHTTPRoute struct { - // Optional service type for Kubernetes solver service. Supported values - // are NodePort or ClusterIP. If unset, defaults to NodePort. - // +optional - ServiceType corev1.ServiceType `json:"serviceType,omitempty"` - - // Custom labels that will be applied to HTTPRoutes created by cert-manager - // while solving HTTP-01 challenges. - // +optional - Labels map[string]string - - // 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/v1alpha2/api-types/httproute/#attaching-to-gateways - ParentRefs []gwapi.ParentReference -} - -type ACMEChallengeSolverHTTP01IngressPodTemplate struct { - // 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. - // +optional - ACMEChallengeSolverHTTP01IngressPodObjectMeta `json:"metadata"` - - // 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. - // +optional - Spec ACMEChallengeSolverHTTP01IngressPodSpec `json:"spec"` -} - -type ACMEChallengeSolverHTTP01IngressPodObjectMeta struct { - // Annotations that should be added to the create ACME HTTP01 solver pods. - // +optional - Annotations map[string]string `json:"annotations,omitempty"` - - // Labels that should be added to the created ACME HTTP01 solver pods. - // +optional - Labels map[string]string `json:"labels,omitempty"` -} - -type ACMEChallengeSolverHTTP01IngressPodSpec struct { - // 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/ - // +optional - NodeSelector map[string]string `json:"nodeSelector,omitempty"` - - // If specified, the pod's scheduling constraints - // +optional - Affinity *corev1.Affinity `json:"affinity,omitempty"` - - // If specified, the pod's tolerations. - // +optional - Tolerations []corev1.Toleration `json:"tolerations,omitempty"` - - // If specified, the pod's priorityClassName. - // +optional - PriorityClassName string `json:"priorityClassName,omitempty"` - - // If specified, the pod's service account - // +optional - ServiceAccountName string `json:"serviceAccountName,omitempty"` -} - -type ACMEChallengeSolverHTTP01IngressTemplate struct { - // 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. - // +optional - ACMEChallengeSolverHTTP01IngressObjectMeta `json:"metadata"` -} - -type ACMEChallengeSolverHTTP01IngressObjectMeta struct { - // Annotations that should be added to the created ACME HTTP01 solver ingress. - // +optional - Annotations map[string]string `json:"annotations,omitempty"` - - // Labels that should be added to the created ACME HTTP01 solver ingress. - // +optional - Labels map[string]string `json:"labels,omitempty"` -} - -// Used to configure a DNS01 challenge provider to be used when solving DNS01 -// challenges. -// Only one DNS provider may be configured per solver. -type ACMEChallengeSolverDNS01 struct { - // CNAMEStrategy configures how the DNS01 provider should handle CNAME - // records when found in DNS zones. - // +optional - CNAMEStrategy CNAMEStrategy `json:"cnameStrategy,omitempty"` - - // Use the Akamai DNS zone management API to manage DNS01 challenge records. - // +optional - Akamai *ACMEIssuerDNS01ProviderAkamai `json:"akamai,omitempty"` - - // Use the Google Cloud DNS API to manage DNS01 challenge records. - // +optional - CloudDNS *ACMEIssuerDNS01ProviderCloudDNS `json:"clouddns,omitempty"` - - // Use the Cloudflare API to manage DNS01 challenge records. - // +optional - Cloudflare *ACMEIssuerDNS01ProviderCloudflare `json:"cloudflare,omitempty"` - - // Use the AWS Route53 API to manage DNS01 challenge records. - // +optional - Route53 *ACMEIssuerDNS01ProviderRoute53 `json:"route53,omitempty"` - - // Use the Microsoft Azure DNS API to manage DNS01 challenge records. - // +optional - AzureDNS *ACMEIssuerDNS01ProviderAzureDNS `json:"azuredns,omitempty"` - - // Use the DigitalOcean DNS API to manage DNS01 challenge records. - // +optional - DigitalOcean *ACMEIssuerDNS01ProviderDigitalOcean `json:"digitalocean,omitempty"` - - // Use the 'ACME DNS' (https://github.com/joohoi/acme-dns) API to manage - // DNS01 challenge records. - // +optional - AcmeDNS *ACMEIssuerDNS01ProviderAcmeDNS `json:"acmedns,omitempty"` - - // Use RFC2136 ("Dynamic Updates in the Domain Name System") (https://datatracker.ietf.org/doc/rfc2136/) - // to manage DNS01 challenge records. - // +optional - RFC2136 *ACMEIssuerDNS01ProviderRFC2136 `json:"rfc2136,omitempty"` - - // Configure an external webhook based DNS01 challenge solver to manage - // DNS01 challenge records. - // +optional - Webhook *ACMEIssuerDNS01ProviderWebhook `json:"webhook,omitempty"` -} - -// 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). -// +kubebuilder:validation:Enum=None;Follow -type CNAMEStrategy string - -const ( - // NoneStrategy indicates that no CNAME resolution strategy should be used - // when determining which DNS zone to update during DNS01 challenges. - NoneStrategy = "None" - - // FollowStrategy will cause cert-manager to recurse through CNAMEs in - // order to determine which DNS zone to update during DNS01 challenges. - // This is useful if you do not want to grant cert-manager access to your - // root DNS zone, and instead delegate the _acme-challenge.example.com - // subdomain to some other, less privileged domain. - FollowStrategy = "Follow" -) - -// ACMEIssuerDNS01ProviderAkamai is a structure containing the DNS -// configuration for Akamai DNS—Zone Record Management API -type ACMEIssuerDNS01ProviderAkamai struct { - ServiceConsumerDomain string `json:"serviceConsumerDomain"` - ClientToken cmmeta.SecretKeySelector `json:"clientTokenSecretRef"` - ClientSecret cmmeta.SecretKeySelector `json:"clientSecretSecretRef"` - AccessToken cmmeta.SecretKeySelector `json:"accessTokenSecretRef"` -} - -// ACMEIssuerDNS01ProviderCloudDNS is a structure containing the DNS -// configuration for Google Cloud DNS -type ACMEIssuerDNS01ProviderCloudDNS struct { - // +optional - ServiceAccount *cmmeta.SecretKeySelector `json:"serviceAccountSecretRef,omitempty"` - Project string `json:"project"` - - // 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. - // +optional - HostedZoneName string `json:"hostedZoneName,omitempty"` -} - -// ACMEIssuerDNS01ProviderCloudflare is a structure containing the DNS -// configuration for Cloudflare. -// One of `apiKeySecretRef` or `apiTokenSecretRef` must be provided. -type ACMEIssuerDNS01ProviderCloudflare struct { - // Email of the account, only required when using API key based authentication. - // +optional - Email string `json:"email,omitempty"` - - // 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. - // +optional - APIKey *cmmeta.SecretKeySelector `json:"apiKeySecretRef,omitempty"` - - // API token used to authenticate with Cloudflare. - // +optional - APIToken *cmmeta.SecretKeySelector `json:"apiTokenSecretRef,omitempty"` -} - -// ACMEIssuerDNS01ProviderDigitalOcean is a structure containing the DNS -// configuration for DigitalOcean Domains -type ACMEIssuerDNS01ProviderDigitalOcean struct { - Token cmmeta.SecretKeySelector `json:"tokenSecretRef"` -} - -// ACMEIssuerDNS01ProviderRoute53 is a structure containing the Route 53 -// configuration for AWS -type ACMEIssuerDNS01ProviderRoute53 struct { - // The AccessKeyID is used for authentication. If not 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 - // +optional - AccessKeyID string `json:"accessKeyID,omitempty"` - - // If set, pull the AWS access key ID from a key within a kubernetes secret. - // see: https://docs.aws.amazon.com/sdk-for-go/v1/developer-guide/configuring-sdk.html#specifying-credentials - // +optional - SecretAccessKeyID *cmmeta.SecretKeySelector `json:"accessKeyIDSecretRef,omitempty"` - - // The SecretAccessKey is used for authentication. If not set we fall-back to using env vars, shared credentials file or AWS Instance metadata - // https://docs.aws.amazon.com/sdk-for-go/v1/developer-guide/configuring-sdk.html#specifying-credentials - // +optional - SecretAccessKey cmmeta.SecretKeySelector `json:"secretAccessKeySecretRef"` - - // 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 - // +optional - Role string `json:"role,omitempty"` - - // If set, the provider will manage only this zone in Route53 and will not do an lookup using the route53:ListHostedZonesByName api call. - // +optional - HostedZoneID string `json:"hostedZoneID,omitempty"` - - // Always set the region when using AccessKeyID and SecretAccessKey - Region string `json:"region"` -} - -// ACMEIssuerDNS01ProviderAzureDNS is a structure containing the -// configuration for Azure DNS -type ACMEIssuerDNS01ProviderAzureDNS struct { - // if both this and ClientSecret are left unset MSI will be used - // +optional - ClientID string `json:"clientID,omitempty"` - - // if both this and ClientID are left unset MSI will be used - // +optional - ClientSecret *cmmeta.SecretKeySelector `json:"clientSecretSecretRef,omitempty"` - - // ID of the Azure subscription - SubscriptionID string `json:"subscriptionID"` - - // when specifying ClientID and ClientSecret then this field is also needed - // +optional - TenantID string `json:"tenantID,omitempty"` - - // resource group the DNS zone is located in - ResourceGroupName string `json:"resourceGroupName"` - - // name of the DNS zone that should be used - // +optional - HostedZoneName string `json:"hostedZoneName,omitempty"` - - // name of the Azure environment (default AzurePublicCloud) - // +optional - Environment AzureDNSEnvironment `json:"environment,omitempty"` - - // managed identity configuration, can not be used at the same time as clientID, clientSecretSecretRef or tenantID - // +optional - ManagedIdentity *AzureManagedIdentity `json:"managedIdentity,omitempty"` -} - -type AzureManagedIdentity struct { - // client ID of the managed identity, can not be used at the same time as resourceID - // +optional - ClientID string `json:"clientID,omitempty"` - - // resource ID of the managed identity, can not be used at the same time as clientID - // +optional - ResourceID string `json:"resourceID,omitempty"` -} - -// +kubebuilder:validation:Enum=AzurePublicCloud;AzureChinaCloud;AzureGermanCloud;AzureUSGovernmentCloud -type AzureDNSEnvironment string - -const ( - AzurePublicCloud AzureDNSEnvironment = "AzurePublicCloud" - AzureChinaCloud AzureDNSEnvironment = "AzureChinaCloud" - AzureGermanCloud AzureDNSEnvironment = "AzureGermanCloud" - AzureUSGovernmentCloud AzureDNSEnvironment = "AzureUSGovernmentCloud" -) - -// ACMEIssuerDNS01ProviderAcmeDNS is a structure containing the -// configuration for ACME-DNS servers -type ACMEIssuerDNS01ProviderAcmeDNS struct { - Host string `json:"host"` - - AccountSecret cmmeta.SecretKeySelector `json:"accountSecretRef"` -} - -// ACMEIssuerDNS01ProviderRFC2136 is a structure containing the -// configuration for RFC2136 DNS -type ACMEIssuerDNS01ProviderRFC2136 struct { - // 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. - Nameserver string `json:"nameserver"` - - // The name of the secret containing the TSIG value. - // If ``tsigKeyName`` is defined, this field is required. - // +optional - TSIGSecret cmmeta.SecretKeySelector `json:"tsigSecretSecretRef,omitempty"` - - // The TSIG Key name configured in the DNS. - // If ``tsigSecretSecretRef`` is defined, this field is required. - // +optional - TSIGKeyName string `json:"tsigKeyName,omitempty"` - - // 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``. - // +optional - TSIGAlgorithm string `json:"tsigAlgorithm,omitempty"` -} - -// ACMEIssuerDNS01ProviderWebhook specifies configuration for a webhook DNS01 -// provider, including where to POST ChallengePayload resources. -type ACMEIssuerDNS01ProviderWebhook struct { - // 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. - GroupName string `json:"groupName"` - - // 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'. - SolverName string `json:"solverName"` - - // 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. - // +optional - Config *apiextensionsv1.JSON `json:"config,omitempty"` -} - -type ACMEIssuerStatus struct { - // URI is the unique account identifier, which can also be used to retrieve - // account details from the CA - // +optional - URI string `json:"uri,omitempty"` - - // LastRegisteredEmail is the email associated with the latest registered - // ACME account, in order to track changes made to registered account - // associated with the Issuer - // +optional - LastRegisteredEmail string `json:"lastRegisteredEmail,omitempty"` -} diff --git a/internal/apis/acme/v1alpha2/types_order.go b/internal/apis/acme/v1alpha2/types_order.go deleted file mode 100644 index 8ea31cd0c39..00000000000 --- a/internal/apis/acme/v1alpha2/types_order.go +++ /dev/null @@ -1,238 +0,0 @@ -/* -Copyright 2020 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. -*/ - -package v1alpha2 - -import ( - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - - cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" -) - -// +genclient -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object - -// Order is a type to represent an Order with an ACME server -// +k8s:openapi-gen=true -type Order struct { - metav1.TypeMeta `json:",inline"` - metav1.ObjectMeta `json:"metadata"` - - Spec OrderSpec `json:"spec,omitempty"` - Status OrderStatus `json:"status,omitempty"` -} - -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object - -// OrderList is a list of Orders -type OrderList struct { - metav1.TypeMeta `json:",inline"` - metav1.ListMeta `json:"metadata"` - - Items []Order `json:"items"` -} - -type OrderSpec struct { - // Certificate signing request bytes in DER encoding. - // This will be used when finalizing the order. - // This field must be set on the order. - CSR []byte `json:"csr"` - - // 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. - IssuerRef cmmeta.ObjectReference `json:"issuerRef"` - - // 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. - // +optional - CommonName string `json:"commonName,omitempty"` - - // 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. - //+optional - DNSNames []string `json:"dnsNames,omitempty"` - - // 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. - // +optional - IPAddresses []string `json:"ipAddresses,omitempty"` - - // Duration is the duration for the not after date for the requested certificate. - // this is set on order creation as pe the ACME spec. - // +optional - Duration *metav1.Duration `json:"duration,omitempty"` -} - -type OrderStatus struct { - // 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. - // +optional - URL string `json:"url,omitempty"` - - // FinalizeURL of the Order. - // This is used to obtain certificates for this order once it has been completed. - // +optional - FinalizeURL string `json:"finalizeURL,omitempty"` - - // 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. - // +optional - Authorizations []ACMEAuthorization `json:"authorizations,omitempty"` - - // 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. - // +optional - Certificate []byte `json:"certificate,omitempty"` - - // State contains the current state of this Order resource. - // States 'success' and 'expired' are 'final' - // +optional - State State `json:"state,omitempty"` - - // Reason optionally provides more information about a why the order is in - // the current state. - // +optional - Reason string `json:"reason,omitempty"` - - // FailureTime stores the time that this order failed. - // This is used to influence garbage collection and back-off. - // +optional - FailureTime *metav1.Time `json:"failureTime,omitempty"` -} - -// 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. -type ACMEAuthorization struct { - // URL is the URL of the Authorization that must be completed - URL string `json:"url"` - - // Identifier is the DNS name to be validated as part of this authorization - // +optional - Identifier string `json:"identifier,omitempty"` - - // 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'. - // +optional - Wildcard *bool `json:"wildcard,omitempty"` - - // 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. - // +optional - InitialState State `json:"initialState,omitempty"` - - // 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. - // +optional - Challenges []ACMEChallenge `json:"challenges,omitempty"` -} - -// 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. -type ACMEChallenge struct { - // URL is the URL of this challenge. It can be used to retrieve additional - // metadata about the Challenge from the ACME server. - URL string `json:"url"` - - // Token is the token that must be presented for this challenge. - // This is used to compute the 'key' that must also be presented. - Token string `json:"token"` - - // 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. - Type string `json:"type"` -} - -// 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. -// +kubebuilder:validation:Enum=valid;ready;pending;processing;invalid;expired;errored -type State string - -const ( - // Unknown is not a real state as part of the ACME spec. - // It is used to represent an unrecognised value. - Unknown State = "" - - // 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. - Valid State = "valid" - - // 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. - Ready State = "ready" - - // 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. - Pending State = "pending" - - // 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. - Processing State = "processing" - - // 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. - Invalid State = "invalid" - - // 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. - Expired State = "expired" - - // 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. - Errored State = "errored" -) diff --git a/internal/apis/acme/v1alpha2/zz_generated.conversion.go b/internal/apis/acme/v1alpha2/zz_generated.conversion.go deleted file mode 100644 index 3f3817ad9dd..00000000000 --- a/internal/apis/acme/v1alpha2/zz_generated.conversion.go +++ /dev/null @@ -1,1609 +0,0 @@ -//go:build !ignore_autogenerated -// +build !ignore_autogenerated - -/* -Copyright 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. -*/ - -// Code generated by conversion-gen. DO NOT EDIT. - -package v1alpha2 - -import ( - unsafe "unsafe" - - acme "github.com/cert-manager/cert-manager/internal/apis/acme" - meta "github.com/cert-manager/cert-manager/internal/apis/meta" - metav1 "github.com/cert-manager/cert-manager/internal/apis/meta/v1" - apismetav1 "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" - v1 "k8s.io/api/core/v1" - apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" - pkgapismetav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - conversion "k8s.io/apimachinery/pkg/conversion" - runtime "k8s.io/apimachinery/pkg/runtime" - apisv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" -) - -func init() { - localSchemeBuilder.Register(RegisterConversions) -} - -// RegisterConversions adds conversion functions to the given scheme. -// Public to allow building arbitrary schemes. -func RegisterConversions(s *runtime.Scheme) error { - if err := s.AddGeneratedConversionFunc((*ACMEAuthorization)(nil), (*acme.ACMEAuthorization)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1alpha2_ACMEAuthorization_To_acme_ACMEAuthorization(a.(*ACMEAuthorization), b.(*acme.ACMEAuthorization), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*acme.ACMEAuthorization)(nil), (*ACMEAuthorization)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_acme_ACMEAuthorization_To_v1alpha2_ACMEAuthorization(a.(*acme.ACMEAuthorization), b.(*ACMEAuthorization), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*ACMEChallenge)(nil), (*acme.ACMEChallenge)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1alpha2_ACMEChallenge_To_acme_ACMEChallenge(a.(*ACMEChallenge), b.(*acme.ACMEChallenge), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*acme.ACMEChallenge)(nil), (*ACMEChallenge)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_acme_ACMEChallenge_To_v1alpha2_ACMEChallenge(a.(*acme.ACMEChallenge), b.(*ACMEChallenge), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*ACMEChallengeSolver)(nil), (*acme.ACMEChallengeSolver)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1alpha2_ACMEChallengeSolver_To_acme_ACMEChallengeSolver(a.(*ACMEChallengeSolver), b.(*acme.ACMEChallengeSolver), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*acme.ACMEChallengeSolver)(nil), (*ACMEChallengeSolver)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_acme_ACMEChallengeSolver_To_v1alpha2_ACMEChallengeSolver(a.(*acme.ACMEChallengeSolver), b.(*ACMEChallengeSolver), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*ACMEChallengeSolverDNS01)(nil), (*acme.ACMEChallengeSolverDNS01)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1alpha2_ACMEChallengeSolverDNS01_To_acme_ACMEChallengeSolverDNS01(a.(*ACMEChallengeSolverDNS01), b.(*acme.ACMEChallengeSolverDNS01), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*acme.ACMEChallengeSolverDNS01)(nil), (*ACMEChallengeSolverDNS01)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_acme_ACMEChallengeSolverDNS01_To_v1alpha2_ACMEChallengeSolverDNS01(a.(*acme.ACMEChallengeSolverDNS01), b.(*ACMEChallengeSolverDNS01), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*ACMEChallengeSolverHTTP01)(nil), (*acme.ACMEChallengeSolverHTTP01)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1alpha2_ACMEChallengeSolverHTTP01_To_acme_ACMEChallengeSolverHTTP01(a.(*ACMEChallengeSolverHTTP01), b.(*acme.ACMEChallengeSolverHTTP01), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*acme.ACMEChallengeSolverHTTP01)(nil), (*ACMEChallengeSolverHTTP01)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_acme_ACMEChallengeSolverHTTP01_To_v1alpha2_ACMEChallengeSolverHTTP01(a.(*acme.ACMEChallengeSolverHTTP01), b.(*ACMEChallengeSolverHTTP01), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*ACMEChallengeSolverHTTP01GatewayHTTPRoute)(nil), (*acme.ACMEChallengeSolverHTTP01GatewayHTTPRoute)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1alpha2_ACMEChallengeSolverHTTP01GatewayHTTPRoute_To_acme_ACMEChallengeSolverHTTP01GatewayHTTPRoute(a.(*ACMEChallengeSolverHTTP01GatewayHTTPRoute), b.(*acme.ACMEChallengeSolverHTTP01GatewayHTTPRoute), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*acme.ACMEChallengeSolverHTTP01GatewayHTTPRoute)(nil), (*ACMEChallengeSolverHTTP01GatewayHTTPRoute)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_acme_ACMEChallengeSolverHTTP01GatewayHTTPRoute_To_v1alpha2_ACMEChallengeSolverHTTP01GatewayHTTPRoute(a.(*acme.ACMEChallengeSolverHTTP01GatewayHTTPRoute), b.(*ACMEChallengeSolverHTTP01GatewayHTTPRoute), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*ACMEChallengeSolverHTTP01Ingress)(nil), (*acme.ACMEChallengeSolverHTTP01Ingress)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1alpha2_ACMEChallengeSolverHTTP01Ingress_To_acme_ACMEChallengeSolverHTTP01Ingress(a.(*ACMEChallengeSolverHTTP01Ingress), b.(*acme.ACMEChallengeSolverHTTP01Ingress), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*acme.ACMEChallengeSolverHTTP01Ingress)(nil), (*ACMEChallengeSolverHTTP01Ingress)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_acme_ACMEChallengeSolverHTTP01Ingress_To_v1alpha2_ACMEChallengeSolverHTTP01Ingress(a.(*acme.ACMEChallengeSolverHTTP01Ingress), b.(*ACMEChallengeSolverHTTP01Ingress), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*ACMEChallengeSolverHTTP01IngressObjectMeta)(nil), (*acme.ACMEChallengeSolverHTTP01IngressObjectMeta)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1alpha2_ACMEChallengeSolverHTTP01IngressObjectMeta_To_acme_ACMEChallengeSolverHTTP01IngressObjectMeta(a.(*ACMEChallengeSolverHTTP01IngressObjectMeta), b.(*acme.ACMEChallengeSolverHTTP01IngressObjectMeta), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*acme.ACMEChallengeSolverHTTP01IngressObjectMeta)(nil), (*ACMEChallengeSolverHTTP01IngressObjectMeta)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_acme_ACMEChallengeSolverHTTP01IngressObjectMeta_To_v1alpha2_ACMEChallengeSolverHTTP01IngressObjectMeta(a.(*acme.ACMEChallengeSolverHTTP01IngressObjectMeta), b.(*ACMEChallengeSolverHTTP01IngressObjectMeta), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*ACMEChallengeSolverHTTP01IngressPodObjectMeta)(nil), (*acme.ACMEChallengeSolverHTTP01IngressPodObjectMeta)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1alpha2_ACMEChallengeSolverHTTP01IngressPodObjectMeta_To_acme_ACMEChallengeSolverHTTP01IngressPodObjectMeta(a.(*ACMEChallengeSolverHTTP01IngressPodObjectMeta), b.(*acme.ACMEChallengeSolverHTTP01IngressPodObjectMeta), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*acme.ACMEChallengeSolverHTTP01IngressPodObjectMeta)(nil), (*ACMEChallengeSolverHTTP01IngressPodObjectMeta)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_acme_ACMEChallengeSolverHTTP01IngressPodObjectMeta_To_v1alpha2_ACMEChallengeSolverHTTP01IngressPodObjectMeta(a.(*acme.ACMEChallengeSolverHTTP01IngressPodObjectMeta), b.(*ACMEChallengeSolverHTTP01IngressPodObjectMeta), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*ACMEChallengeSolverHTTP01IngressPodSpec)(nil), (*acme.ACMEChallengeSolverHTTP01IngressPodSpec)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1alpha2_ACMEChallengeSolverHTTP01IngressPodSpec_To_acme_ACMEChallengeSolverHTTP01IngressPodSpec(a.(*ACMEChallengeSolverHTTP01IngressPodSpec), b.(*acme.ACMEChallengeSolverHTTP01IngressPodSpec), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*acme.ACMEChallengeSolverHTTP01IngressPodSpec)(nil), (*ACMEChallengeSolverHTTP01IngressPodSpec)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_acme_ACMEChallengeSolverHTTP01IngressPodSpec_To_v1alpha2_ACMEChallengeSolverHTTP01IngressPodSpec(a.(*acme.ACMEChallengeSolverHTTP01IngressPodSpec), b.(*ACMEChallengeSolverHTTP01IngressPodSpec), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*ACMEChallengeSolverHTTP01IngressPodTemplate)(nil), (*acme.ACMEChallengeSolverHTTP01IngressPodTemplate)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1alpha2_ACMEChallengeSolverHTTP01IngressPodTemplate_To_acme_ACMEChallengeSolverHTTP01IngressPodTemplate(a.(*ACMEChallengeSolverHTTP01IngressPodTemplate), b.(*acme.ACMEChallengeSolverHTTP01IngressPodTemplate), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*acme.ACMEChallengeSolverHTTP01IngressPodTemplate)(nil), (*ACMEChallengeSolverHTTP01IngressPodTemplate)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_acme_ACMEChallengeSolverHTTP01IngressPodTemplate_To_v1alpha2_ACMEChallengeSolverHTTP01IngressPodTemplate(a.(*acme.ACMEChallengeSolverHTTP01IngressPodTemplate), b.(*ACMEChallengeSolverHTTP01IngressPodTemplate), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*ACMEChallengeSolverHTTP01IngressTemplate)(nil), (*acme.ACMEChallengeSolverHTTP01IngressTemplate)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1alpha2_ACMEChallengeSolverHTTP01IngressTemplate_To_acme_ACMEChallengeSolverHTTP01IngressTemplate(a.(*ACMEChallengeSolverHTTP01IngressTemplate), b.(*acme.ACMEChallengeSolverHTTP01IngressTemplate), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*acme.ACMEChallengeSolverHTTP01IngressTemplate)(nil), (*ACMEChallengeSolverHTTP01IngressTemplate)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_acme_ACMEChallengeSolverHTTP01IngressTemplate_To_v1alpha2_ACMEChallengeSolverHTTP01IngressTemplate(a.(*acme.ACMEChallengeSolverHTTP01IngressTemplate), b.(*ACMEChallengeSolverHTTP01IngressTemplate), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*ACMEExternalAccountBinding)(nil), (*acme.ACMEExternalAccountBinding)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1alpha2_ACMEExternalAccountBinding_To_acme_ACMEExternalAccountBinding(a.(*ACMEExternalAccountBinding), b.(*acme.ACMEExternalAccountBinding), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*acme.ACMEExternalAccountBinding)(nil), (*ACMEExternalAccountBinding)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_acme_ACMEExternalAccountBinding_To_v1alpha2_ACMEExternalAccountBinding(a.(*acme.ACMEExternalAccountBinding), b.(*ACMEExternalAccountBinding), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*ACMEIssuerDNS01ProviderAcmeDNS)(nil), (*acme.ACMEIssuerDNS01ProviderAcmeDNS)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1alpha2_ACMEIssuerDNS01ProviderAcmeDNS_To_acme_ACMEIssuerDNS01ProviderAcmeDNS(a.(*ACMEIssuerDNS01ProviderAcmeDNS), b.(*acme.ACMEIssuerDNS01ProviderAcmeDNS), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*acme.ACMEIssuerDNS01ProviderAcmeDNS)(nil), (*ACMEIssuerDNS01ProviderAcmeDNS)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_acme_ACMEIssuerDNS01ProviderAcmeDNS_To_v1alpha2_ACMEIssuerDNS01ProviderAcmeDNS(a.(*acme.ACMEIssuerDNS01ProviderAcmeDNS), b.(*ACMEIssuerDNS01ProviderAcmeDNS), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*ACMEIssuerDNS01ProviderAkamai)(nil), (*acme.ACMEIssuerDNS01ProviderAkamai)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1alpha2_ACMEIssuerDNS01ProviderAkamai_To_acme_ACMEIssuerDNS01ProviderAkamai(a.(*ACMEIssuerDNS01ProviderAkamai), b.(*acme.ACMEIssuerDNS01ProviderAkamai), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*acme.ACMEIssuerDNS01ProviderAkamai)(nil), (*ACMEIssuerDNS01ProviderAkamai)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_acme_ACMEIssuerDNS01ProviderAkamai_To_v1alpha2_ACMEIssuerDNS01ProviderAkamai(a.(*acme.ACMEIssuerDNS01ProviderAkamai), b.(*ACMEIssuerDNS01ProviderAkamai), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*ACMEIssuerDNS01ProviderAzureDNS)(nil), (*acme.ACMEIssuerDNS01ProviderAzureDNS)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1alpha2_ACMEIssuerDNS01ProviderAzureDNS_To_acme_ACMEIssuerDNS01ProviderAzureDNS(a.(*ACMEIssuerDNS01ProviderAzureDNS), b.(*acme.ACMEIssuerDNS01ProviderAzureDNS), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*acme.ACMEIssuerDNS01ProviderAzureDNS)(nil), (*ACMEIssuerDNS01ProviderAzureDNS)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_acme_ACMEIssuerDNS01ProviderAzureDNS_To_v1alpha2_ACMEIssuerDNS01ProviderAzureDNS(a.(*acme.ACMEIssuerDNS01ProviderAzureDNS), b.(*ACMEIssuerDNS01ProviderAzureDNS), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*ACMEIssuerDNS01ProviderCloudDNS)(nil), (*acme.ACMEIssuerDNS01ProviderCloudDNS)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1alpha2_ACMEIssuerDNS01ProviderCloudDNS_To_acme_ACMEIssuerDNS01ProviderCloudDNS(a.(*ACMEIssuerDNS01ProviderCloudDNS), b.(*acme.ACMEIssuerDNS01ProviderCloudDNS), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*acme.ACMEIssuerDNS01ProviderCloudDNS)(nil), (*ACMEIssuerDNS01ProviderCloudDNS)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_acme_ACMEIssuerDNS01ProviderCloudDNS_To_v1alpha2_ACMEIssuerDNS01ProviderCloudDNS(a.(*acme.ACMEIssuerDNS01ProviderCloudDNS), b.(*ACMEIssuerDNS01ProviderCloudDNS), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*ACMEIssuerDNS01ProviderCloudflare)(nil), (*acme.ACMEIssuerDNS01ProviderCloudflare)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1alpha2_ACMEIssuerDNS01ProviderCloudflare_To_acme_ACMEIssuerDNS01ProviderCloudflare(a.(*ACMEIssuerDNS01ProviderCloudflare), b.(*acme.ACMEIssuerDNS01ProviderCloudflare), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*acme.ACMEIssuerDNS01ProviderCloudflare)(nil), (*ACMEIssuerDNS01ProviderCloudflare)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_acme_ACMEIssuerDNS01ProviderCloudflare_To_v1alpha2_ACMEIssuerDNS01ProviderCloudflare(a.(*acme.ACMEIssuerDNS01ProviderCloudflare), b.(*ACMEIssuerDNS01ProviderCloudflare), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*ACMEIssuerDNS01ProviderDigitalOcean)(nil), (*acme.ACMEIssuerDNS01ProviderDigitalOcean)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1alpha2_ACMEIssuerDNS01ProviderDigitalOcean_To_acme_ACMEIssuerDNS01ProviderDigitalOcean(a.(*ACMEIssuerDNS01ProviderDigitalOcean), b.(*acme.ACMEIssuerDNS01ProviderDigitalOcean), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*acme.ACMEIssuerDNS01ProviderDigitalOcean)(nil), (*ACMEIssuerDNS01ProviderDigitalOcean)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_acme_ACMEIssuerDNS01ProviderDigitalOcean_To_v1alpha2_ACMEIssuerDNS01ProviderDigitalOcean(a.(*acme.ACMEIssuerDNS01ProviderDigitalOcean), b.(*ACMEIssuerDNS01ProviderDigitalOcean), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*ACMEIssuerDNS01ProviderRFC2136)(nil), (*acme.ACMEIssuerDNS01ProviderRFC2136)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1alpha2_ACMEIssuerDNS01ProviderRFC2136_To_acme_ACMEIssuerDNS01ProviderRFC2136(a.(*ACMEIssuerDNS01ProviderRFC2136), b.(*acme.ACMEIssuerDNS01ProviderRFC2136), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*acme.ACMEIssuerDNS01ProviderRFC2136)(nil), (*ACMEIssuerDNS01ProviderRFC2136)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_acme_ACMEIssuerDNS01ProviderRFC2136_To_v1alpha2_ACMEIssuerDNS01ProviderRFC2136(a.(*acme.ACMEIssuerDNS01ProviderRFC2136), b.(*ACMEIssuerDNS01ProviderRFC2136), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*ACMEIssuerDNS01ProviderRoute53)(nil), (*acme.ACMEIssuerDNS01ProviderRoute53)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1alpha2_ACMEIssuerDNS01ProviderRoute53_To_acme_ACMEIssuerDNS01ProviderRoute53(a.(*ACMEIssuerDNS01ProviderRoute53), b.(*acme.ACMEIssuerDNS01ProviderRoute53), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*acme.ACMEIssuerDNS01ProviderRoute53)(nil), (*ACMEIssuerDNS01ProviderRoute53)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_acme_ACMEIssuerDNS01ProviderRoute53_To_v1alpha2_ACMEIssuerDNS01ProviderRoute53(a.(*acme.ACMEIssuerDNS01ProviderRoute53), b.(*ACMEIssuerDNS01ProviderRoute53), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*ACMEIssuerDNS01ProviderWebhook)(nil), (*acme.ACMEIssuerDNS01ProviderWebhook)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1alpha2_ACMEIssuerDNS01ProviderWebhook_To_acme_ACMEIssuerDNS01ProviderWebhook(a.(*ACMEIssuerDNS01ProviderWebhook), b.(*acme.ACMEIssuerDNS01ProviderWebhook), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*acme.ACMEIssuerDNS01ProviderWebhook)(nil), (*ACMEIssuerDNS01ProviderWebhook)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_acme_ACMEIssuerDNS01ProviderWebhook_To_v1alpha2_ACMEIssuerDNS01ProviderWebhook(a.(*acme.ACMEIssuerDNS01ProviderWebhook), b.(*ACMEIssuerDNS01ProviderWebhook), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*ACMEIssuerStatus)(nil), (*acme.ACMEIssuerStatus)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1alpha2_ACMEIssuerStatus_To_acme_ACMEIssuerStatus(a.(*ACMEIssuerStatus), b.(*acme.ACMEIssuerStatus), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*acme.ACMEIssuerStatus)(nil), (*ACMEIssuerStatus)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_acme_ACMEIssuerStatus_To_v1alpha2_ACMEIssuerStatus(a.(*acme.ACMEIssuerStatus), b.(*ACMEIssuerStatus), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*AzureManagedIdentity)(nil), (*acme.AzureManagedIdentity)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1alpha2_AzureManagedIdentity_To_acme_AzureManagedIdentity(a.(*AzureManagedIdentity), b.(*acme.AzureManagedIdentity), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*acme.AzureManagedIdentity)(nil), (*AzureManagedIdentity)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_acme_AzureManagedIdentity_To_v1alpha2_AzureManagedIdentity(a.(*acme.AzureManagedIdentity), b.(*AzureManagedIdentity), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*CertificateDNSNameSelector)(nil), (*acme.CertificateDNSNameSelector)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1alpha2_CertificateDNSNameSelector_To_acme_CertificateDNSNameSelector(a.(*CertificateDNSNameSelector), b.(*acme.CertificateDNSNameSelector), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*acme.CertificateDNSNameSelector)(nil), (*CertificateDNSNameSelector)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_acme_CertificateDNSNameSelector_To_v1alpha2_CertificateDNSNameSelector(a.(*acme.CertificateDNSNameSelector), b.(*CertificateDNSNameSelector), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*Challenge)(nil), (*acme.Challenge)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1alpha2_Challenge_To_acme_Challenge(a.(*Challenge), b.(*acme.Challenge), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*acme.Challenge)(nil), (*Challenge)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_acme_Challenge_To_v1alpha2_Challenge(a.(*acme.Challenge), b.(*Challenge), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*ChallengeList)(nil), (*acme.ChallengeList)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1alpha2_ChallengeList_To_acme_ChallengeList(a.(*ChallengeList), b.(*acme.ChallengeList), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*acme.ChallengeList)(nil), (*ChallengeList)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_acme_ChallengeList_To_v1alpha2_ChallengeList(a.(*acme.ChallengeList), b.(*ChallengeList), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*ChallengeStatus)(nil), (*acme.ChallengeStatus)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1alpha2_ChallengeStatus_To_acme_ChallengeStatus(a.(*ChallengeStatus), b.(*acme.ChallengeStatus), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*acme.ChallengeStatus)(nil), (*ChallengeStatus)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_acme_ChallengeStatus_To_v1alpha2_ChallengeStatus(a.(*acme.ChallengeStatus), b.(*ChallengeStatus), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*Order)(nil), (*acme.Order)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1alpha2_Order_To_acme_Order(a.(*Order), b.(*acme.Order), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*acme.Order)(nil), (*Order)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_acme_Order_To_v1alpha2_Order(a.(*acme.Order), b.(*Order), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*OrderList)(nil), (*acme.OrderList)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1alpha2_OrderList_To_acme_OrderList(a.(*OrderList), b.(*acme.OrderList), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*acme.OrderList)(nil), (*OrderList)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_acme_OrderList_To_v1alpha2_OrderList(a.(*acme.OrderList), b.(*OrderList), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*OrderStatus)(nil), (*acme.OrderStatus)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1alpha2_OrderStatus_To_acme_OrderStatus(a.(*OrderStatus), b.(*acme.OrderStatus), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*acme.OrderStatus)(nil), (*OrderStatus)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_acme_OrderStatus_To_v1alpha2_OrderStatus(a.(*acme.OrderStatus), b.(*OrderStatus), scope) - }); err != nil { - return err - } - if err := s.AddConversionFunc((*acme.ACMEIssuer)(nil), (*ACMEIssuer)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_acme_ACMEIssuer_To_v1alpha2_ACMEIssuer(a.(*acme.ACMEIssuer), b.(*ACMEIssuer), scope) - }); err != nil { - return err - } - if err := s.AddConversionFunc((*acme.ChallengeSpec)(nil), (*ChallengeSpec)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_acme_ChallengeSpec_To_v1alpha2_ChallengeSpec(a.(*acme.ChallengeSpec), b.(*ChallengeSpec), scope) - }); err != nil { - return err - } - if err := s.AddConversionFunc((*acme.OrderSpec)(nil), (*OrderSpec)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_acme_OrderSpec_To_v1alpha2_OrderSpec(a.(*acme.OrderSpec), b.(*OrderSpec), scope) - }); err != nil { - return err - } - if err := s.AddConversionFunc((*ACMEIssuer)(nil), (*acme.ACMEIssuer)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1alpha2_ACMEIssuer_To_acme_ACMEIssuer(a.(*ACMEIssuer), b.(*acme.ACMEIssuer), scope) - }); err != nil { - return err - } - if err := s.AddConversionFunc((*ChallengeSpec)(nil), (*acme.ChallengeSpec)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1alpha2_ChallengeSpec_To_acme_ChallengeSpec(a.(*ChallengeSpec), b.(*acme.ChallengeSpec), scope) - }); err != nil { - return err - } - if err := s.AddConversionFunc((*OrderSpec)(nil), (*acme.OrderSpec)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1alpha2_OrderSpec_To_acme_OrderSpec(a.(*OrderSpec), b.(*acme.OrderSpec), scope) - }); err != nil { - return err - } - return nil -} - -func autoConvert_v1alpha2_ACMEAuthorization_To_acme_ACMEAuthorization(in *ACMEAuthorization, out *acme.ACMEAuthorization, s conversion.Scope) error { - out.URL = in.URL - out.Identifier = in.Identifier - out.Wildcard = (*bool)(unsafe.Pointer(in.Wildcard)) - out.InitialState = acme.State(in.InitialState) - out.Challenges = *(*[]acme.ACMEChallenge)(unsafe.Pointer(&in.Challenges)) - return nil -} - -// Convert_v1alpha2_ACMEAuthorization_To_acme_ACMEAuthorization is an autogenerated conversion function. -func Convert_v1alpha2_ACMEAuthorization_To_acme_ACMEAuthorization(in *ACMEAuthorization, out *acme.ACMEAuthorization, s conversion.Scope) error { - return autoConvert_v1alpha2_ACMEAuthorization_To_acme_ACMEAuthorization(in, out, s) -} - -func autoConvert_acme_ACMEAuthorization_To_v1alpha2_ACMEAuthorization(in *acme.ACMEAuthorization, out *ACMEAuthorization, s conversion.Scope) error { - out.URL = in.URL - out.Identifier = in.Identifier - out.Wildcard = (*bool)(unsafe.Pointer(in.Wildcard)) - out.InitialState = State(in.InitialState) - out.Challenges = *(*[]ACMEChallenge)(unsafe.Pointer(&in.Challenges)) - return nil -} - -// Convert_acme_ACMEAuthorization_To_v1alpha2_ACMEAuthorization is an autogenerated conversion function. -func Convert_acme_ACMEAuthorization_To_v1alpha2_ACMEAuthorization(in *acme.ACMEAuthorization, out *ACMEAuthorization, s conversion.Scope) error { - return autoConvert_acme_ACMEAuthorization_To_v1alpha2_ACMEAuthorization(in, out, s) -} - -func autoConvert_v1alpha2_ACMEChallenge_To_acme_ACMEChallenge(in *ACMEChallenge, out *acme.ACMEChallenge, s conversion.Scope) error { - out.URL = in.URL - out.Token = in.Token - out.Type = in.Type - return nil -} - -// Convert_v1alpha2_ACMEChallenge_To_acme_ACMEChallenge is an autogenerated conversion function. -func Convert_v1alpha2_ACMEChallenge_To_acme_ACMEChallenge(in *ACMEChallenge, out *acme.ACMEChallenge, s conversion.Scope) error { - return autoConvert_v1alpha2_ACMEChallenge_To_acme_ACMEChallenge(in, out, s) -} - -func autoConvert_acme_ACMEChallenge_To_v1alpha2_ACMEChallenge(in *acme.ACMEChallenge, out *ACMEChallenge, s conversion.Scope) error { - out.URL = in.URL - out.Token = in.Token - out.Type = in.Type - return nil -} - -// Convert_acme_ACMEChallenge_To_v1alpha2_ACMEChallenge is an autogenerated conversion function. -func Convert_acme_ACMEChallenge_To_v1alpha2_ACMEChallenge(in *acme.ACMEChallenge, out *ACMEChallenge, s conversion.Scope) error { - return autoConvert_acme_ACMEChallenge_To_v1alpha2_ACMEChallenge(in, out, s) -} - -func autoConvert_v1alpha2_ACMEChallengeSolver_To_acme_ACMEChallengeSolver(in *ACMEChallengeSolver, out *acme.ACMEChallengeSolver, s conversion.Scope) error { - out.Selector = (*acme.CertificateDNSNameSelector)(unsafe.Pointer(in.Selector)) - out.HTTP01 = (*acme.ACMEChallengeSolverHTTP01)(unsafe.Pointer(in.HTTP01)) - if in.DNS01 != nil { - in, out := &in.DNS01, &out.DNS01 - *out = new(acme.ACMEChallengeSolverDNS01) - if err := Convert_v1alpha2_ACMEChallengeSolverDNS01_To_acme_ACMEChallengeSolverDNS01(*in, *out, s); err != nil { - return err - } - } else { - out.DNS01 = nil - } - return nil -} - -// Convert_v1alpha2_ACMEChallengeSolver_To_acme_ACMEChallengeSolver is an autogenerated conversion function. -func Convert_v1alpha2_ACMEChallengeSolver_To_acme_ACMEChallengeSolver(in *ACMEChallengeSolver, out *acme.ACMEChallengeSolver, s conversion.Scope) error { - return autoConvert_v1alpha2_ACMEChallengeSolver_To_acme_ACMEChallengeSolver(in, out, s) -} - -func autoConvert_acme_ACMEChallengeSolver_To_v1alpha2_ACMEChallengeSolver(in *acme.ACMEChallengeSolver, out *ACMEChallengeSolver, s conversion.Scope) error { - out.Selector = (*CertificateDNSNameSelector)(unsafe.Pointer(in.Selector)) - out.HTTP01 = (*ACMEChallengeSolverHTTP01)(unsafe.Pointer(in.HTTP01)) - if in.DNS01 != nil { - in, out := &in.DNS01, &out.DNS01 - *out = new(ACMEChallengeSolverDNS01) - if err := Convert_acme_ACMEChallengeSolverDNS01_To_v1alpha2_ACMEChallengeSolverDNS01(*in, *out, s); err != nil { - return err - } - } else { - out.DNS01 = nil - } - return nil -} - -// Convert_acme_ACMEChallengeSolver_To_v1alpha2_ACMEChallengeSolver is an autogenerated conversion function. -func Convert_acme_ACMEChallengeSolver_To_v1alpha2_ACMEChallengeSolver(in *acme.ACMEChallengeSolver, out *ACMEChallengeSolver, s conversion.Scope) error { - return autoConvert_acme_ACMEChallengeSolver_To_v1alpha2_ACMEChallengeSolver(in, out, s) -} - -func autoConvert_v1alpha2_ACMEChallengeSolverDNS01_To_acme_ACMEChallengeSolverDNS01(in *ACMEChallengeSolverDNS01, out *acme.ACMEChallengeSolverDNS01, s conversion.Scope) error { - out.CNAMEStrategy = acme.CNAMEStrategy(in.CNAMEStrategy) - if in.Akamai != nil { - in, out := &in.Akamai, &out.Akamai - *out = new(acme.ACMEIssuerDNS01ProviderAkamai) - if err := Convert_v1alpha2_ACMEIssuerDNS01ProviderAkamai_To_acme_ACMEIssuerDNS01ProviderAkamai(*in, *out, s); err != nil { - return err - } - } else { - out.Akamai = nil - } - if in.CloudDNS != nil { - in, out := &in.CloudDNS, &out.CloudDNS - *out = new(acme.ACMEIssuerDNS01ProviderCloudDNS) - if err := Convert_v1alpha2_ACMEIssuerDNS01ProviderCloudDNS_To_acme_ACMEIssuerDNS01ProviderCloudDNS(*in, *out, s); err != nil { - return err - } - } else { - out.CloudDNS = nil - } - if in.Cloudflare != nil { - in, out := &in.Cloudflare, &out.Cloudflare - *out = new(acme.ACMEIssuerDNS01ProviderCloudflare) - if err := Convert_v1alpha2_ACMEIssuerDNS01ProviderCloudflare_To_acme_ACMEIssuerDNS01ProviderCloudflare(*in, *out, s); err != nil { - return err - } - } else { - out.Cloudflare = nil - } - if in.Route53 != nil { - in, out := &in.Route53, &out.Route53 - *out = new(acme.ACMEIssuerDNS01ProviderRoute53) - if err := Convert_v1alpha2_ACMEIssuerDNS01ProviderRoute53_To_acme_ACMEIssuerDNS01ProviderRoute53(*in, *out, s); err != nil { - return err - } - } else { - out.Route53 = nil - } - if in.AzureDNS != nil { - in, out := &in.AzureDNS, &out.AzureDNS - *out = new(acme.ACMEIssuerDNS01ProviderAzureDNS) - if err := Convert_v1alpha2_ACMEIssuerDNS01ProviderAzureDNS_To_acme_ACMEIssuerDNS01ProviderAzureDNS(*in, *out, s); err != nil { - return err - } - } else { - out.AzureDNS = nil - } - if in.DigitalOcean != nil { - in, out := &in.DigitalOcean, &out.DigitalOcean - *out = new(acme.ACMEIssuerDNS01ProviderDigitalOcean) - if err := Convert_v1alpha2_ACMEIssuerDNS01ProviderDigitalOcean_To_acme_ACMEIssuerDNS01ProviderDigitalOcean(*in, *out, s); err != nil { - return err - } - } else { - out.DigitalOcean = nil - } - if in.AcmeDNS != nil { - in, out := &in.AcmeDNS, &out.AcmeDNS - *out = new(acme.ACMEIssuerDNS01ProviderAcmeDNS) - if err := Convert_v1alpha2_ACMEIssuerDNS01ProviderAcmeDNS_To_acme_ACMEIssuerDNS01ProviderAcmeDNS(*in, *out, s); err != nil { - return err - } - } else { - out.AcmeDNS = nil - } - if in.RFC2136 != nil { - in, out := &in.RFC2136, &out.RFC2136 - *out = new(acme.ACMEIssuerDNS01ProviderRFC2136) - if err := Convert_v1alpha2_ACMEIssuerDNS01ProviderRFC2136_To_acme_ACMEIssuerDNS01ProviderRFC2136(*in, *out, s); err != nil { - return err - } - } else { - out.RFC2136 = nil - } - out.Webhook = (*acme.ACMEIssuerDNS01ProviderWebhook)(unsafe.Pointer(in.Webhook)) - return nil -} - -// Convert_v1alpha2_ACMEChallengeSolverDNS01_To_acme_ACMEChallengeSolverDNS01 is an autogenerated conversion function. -func Convert_v1alpha2_ACMEChallengeSolverDNS01_To_acme_ACMEChallengeSolverDNS01(in *ACMEChallengeSolverDNS01, out *acme.ACMEChallengeSolverDNS01, s conversion.Scope) error { - return autoConvert_v1alpha2_ACMEChallengeSolverDNS01_To_acme_ACMEChallengeSolverDNS01(in, out, s) -} - -func autoConvert_acme_ACMEChallengeSolverDNS01_To_v1alpha2_ACMEChallengeSolverDNS01(in *acme.ACMEChallengeSolverDNS01, out *ACMEChallengeSolverDNS01, s conversion.Scope) error { - out.CNAMEStrategy = CNAMEStrategy(in.CNAMEStrategy) - if in.Akamai != nil { - in, out := &in.Akamai, &out.Akamai - *out = new(ACMEIssuerDNS01ProviderAkamai) - if err := Convert_acme_ACMEIssuerDNS01ProviderAkamai_To_v1alpha2_ACMEIssuerDNS01ProviderAkamai(*in, *out, s); err != nil { - return err - } - } else { - out.Akamai = nil - } - if in.CloudDNS != nil { - in, out := &in.CloudDNS, &out.CloudDNS - *out = new(ACMEIssuerDNS01ProviderCloudDNS) - if err := Convert_acme_ACMEIssuerDNS01ProviderCloudDNS_To_v1alpha2_ACMEIssuerDNS01ProviderCloudDNS(*in, *out, s); err != nil { - return err - } - } else { - out.CloudDNS = nil - } - if in.Cloudflare != nil { - in, out := &in.Cloudflare, &out.Cloudflare - *out = new(ACMEIssuerDNS01ProviderCloudflare) - if err := Convert_acme_ACMEIssuerDNS01ProviderCloudflare_To_v1alpha2_ACMEIssuerDNS01ProviderCloudflare(*in, *out, s); err != nil { - return err - } - } else { - out.Cloudflare = nil - } - if in.Route53 != nil { - in, out := &in.Route53, &out.Route53 - *out = new(ACMEIssuerDNS01ProviderRoute53) - if err := Convert_acme_ACMEIssuerDNS01ProviderRoute53_To_v1alpha2_ACMEIssuerDNS01ProviderRoute53(*in, *out, s); err != nil { - return err - } - } else { - out.Route53 = nil - } - if in.AzureDNS != nil { - in, out := &in.AzureDNS, &out.AzureDNS - *out = new(ACMEIssuerDNS01ProviderAzureDNS) - if err := Convert_acme_ACMEIssuerDNS01ProviderAzureDNS_To_v1alpha2_ACMEIssuerDNS01ProviderAzureDNS(*in, *out, s); err != nil { - return err - } - } else { - out.AzureDNS = nil - } - if in.DigitalOcean != nil { - in, out := &in.DigitalOcean, &out.DigitalOcean - *out = new(ACMEIssuerDNS01ProviderDigitalOcean) - if err := Convert_acme_ACMEIssuerDNS01ProviderDigitalOcean_To_v1alpha2_ACMEIssuerDNS01ProviderDigitalOcean(*in, *out, s); err != nil { - return err - } - } else { - out.DigitalOcean = nil - } - if in.AcmeDNS != nil { - in, out := &in.AcmeDNS, &out.AcmeDNS - *out = new(ACMEIssuerDNS01ProviderAcmeDNS) - if err := Convert_acme_ACMEIssuerDNS01ProviderAcmeDNS_To_v1alpha2_ACMEIssuerDNS01ProviderAcmeDNS(*in, *out, s); err != nil { - return err - } - } else { - out.AcmeDNS = nil - } - if in.RFC2136 != nil { - in, out := &in.RFC2136, &out.RFC2136 - *out = new(ACMEIssuerDNS01ProviderRFC2136) - if err := Convert_acme_ACMEIssuerDNS01ProviderRFC2136_To_v1alpha2_ACMEIssuerDNS01ProviderRFC2136(*in, *out, s); err != nil { - return err - } - } else { - out.RFC2136 = nil - } - out.Webhook = (*ACMEIssuerDNS01ProviderWebhook)(unsafe.Pointer(in.Webhook)) - return nil -} - -// Convert_acme_ACMEChallengeSolverDNS01_To_v1alpha2_ACMEChallengeSolverDNS01 is an autogenerated conversion function. -func Convert_acme_ACMEChallengeSolverDNS01_To_v1alpha2_ACMEChallengeSolverDNS01(in *acme.ACMEChallengeSolverDNS01, out *ACMEChallengeSolverDNS01, s conversion.Scope) error { - return autoConvert_acme_ACMEChallengeSolverDNS01_To_v1alpha2_ACMEChallengeSolverDNS01(in, out, s) -} - -func autoConvert_v1alpha2_ACMEChallengeSolverHTTP01_To_acme_ACMEChallengeSolverHTTP01(in *ACMEChallengeSolverHTTP01, out *acme.ACMEChallengeSolverHTTP01, s conversion.Scope) error { - out.Ingress = (*acme.ACMEChallengeSolverHTTP01Ingress)(unsafe.Pointer(in.Ingress)) - out.GatewayHTTPRoute = (*acme.ACMEChallengeSolverHTTP01GatewayHTTPRoute)(unsafe.Pointer(in.GatewayHTTPRoute)) - return nil -} - -// Convert_v1alpha2_ACMEChallengeSolverHTTP01_To_acme_ACMEChallengeSolverHTTP01 is an autogenerated conversion function. -func Convert_v1alpha2_ACMEChallengeSolverHTTP01_To_acme_ACMEChallengeSolverHTTP01(in *ACMEChallengeSolverHTTP01, out *acme.ACMEChallengeSolverHTTP01, s conversion.Scope) error { - return autoConvert_v1alpha2_ACMEChallengeSolverHTTP01_To_acme_ACMEChallengeSolverHTTP01(in, out, s) -} - -func autoConvert_acme_ACMEChallengeSolverHTTP01_To_v1alpha2_ACMEChallengeSolverHTTP01(in *acme.ACMEChallengeSolverHTTP01, out *ACMEChallengeSolverHTTP01, s conversion.Scope) error { - out.Ingress = (*ACMEChallengeSolverHTTP01Ingress)(unsafe.Pointer(in.Ingress)) - out.GatewayHTTPRoute = (*ACMEChallengeSolverHTTP01GatewayHTTPRoute)(unsafe.Pointer(in.GatewayHTTPRoute)) - return nil -} - -// Convert_acme_ACMEChallengeSolverHTTP01_To_v1alpha2_ACMEChallengeSolverHTTP01 is an autogenerated conversion function. -func Convert_acme_ACMEChallengeSolverHTTP01_To_v1alpha2_ACMEChallengeSolverHTTP01(in *acme.ACMEChallengeSolverHTTP01, out *ACMEChallengeSolverHTTP01, s conversion.Scope) error { - return autoConvert_acme_ACMEChallengeSolverHTTP01_To_v1alpha2_ACMEChallengeSolverHTTP01(in, out, s) -} - -func autoConvert_v1alpha2_ACMEChallengeSolverHTTP01GatewayHTTPRoute_To_acme_ACMEChallengeSolverHTTP01GatewayHTTPRoute(in *ACMEChallengeSolverHTTP01GatewayHTTPRoute, out *acme.ACMEChallengeSolverHTTP01GatewayHTTPRoute, s conversion.Scope) error { - out.ServiceType = v1.ServiceType(in.ServiceType) - out.Labels = *(*map[string]string)(unsafe.Pointer(&in.Labels)) - out.ParentRefs = *(*[]apisv1alpha2.ParentReference)(unsafe.Pointer(&in.ParentRefs)) - return nil -} - -// Convert_v1alpha2_ACMEChallengeSolverHTTP01GatewayHTTPRoute_To_acme_ACMEChallengeSolverHTTP01GatewayHTTPRoute is an autogenerated conversion function. -func Convert_v1alpha2_ACMEChallengeSolverHTTP01GatewayHTTPRoute_To_acme_ACMEChallengeSolverHTTP01GatewayHTTPRoute(in *ACMEChallengeSolverHTTP01GatewayHTTPRoute, out *acme.ACMEChallengeSolverHTTP01GatewayHTTPRoute, s conversion.Scope) error { - return autoConvert_v1alpha2_ACMEChallengeSolverHTTP01GatewayHTTPRoute_To_acme_ACMEChallengeSolverHTTP01GatewayHTTPRoute(in, out, s) -} - -func autoConvert_acme_ACMEChallengeSolverHTTP01GatewayHTTPRoute_To_v1alpha2_ACMEChallengeSolverHTTP01GatewayHTTPRoute(in *acme.ACMEChallengeSolverHTTP01GatewayHTTPRoute, out *ACMEChallengeSolverHTTP01GatewayHTTPRoute, s conversion.Scope) error { - out.ServiceType = v1.ServiceType(in.ServiceType) - out.Labels = *(*map[string]string)(unsafe.Pointer(&in.Labels)) - out.ParentRefs = *(*[]apisv1alpha2.ParentReference)(unsafe.Pointer(&in.ParentRefs)) - return nil -} - -// Convert_acme_ACMEChallengeSolverHTTP01GatewayHTTPRoute_To_v1alpha2_ACMEChallengeSolverHTTP01GatewayHTTPRoute is an autogenerated conversion function. -func Convert_acme_ACMEChallengeSolverHTTP01GatewayHTTPRoute_To_v1alpha2_ACMEChallengeSolverHTTP01GatewayHTTPRoute(in *acme.ACMEChallengeSolverHTTP01GatewayHTTPRoute, out *ACMEChallengeSolverHTTP01GatewayHTTPRoute, s conversion.Scope) error { - return autoConvert_acme_ACMEChallengeSolverHTTP01GatewayHTTPRoute_To_v1alpha2_ACMEChallengeSolverHTTP01GatewayHTTPRoute(in, out, s) -} - -func autoConvert_v1alpha2_ACMEChallengeSolverHTTP01Ingress_To_acme_ACMEChallengeSolverHTTP01Ingress(in *ACMEChallengeSolverHTTP01Ingress, out *acme.ACMEChallengeSolverHTTP01Ingress, s conversion.Scope) error { - out.ServiceType = v1.ServiceType(in.ServiceType) - out.Class = (*string)(unsafe.Pointer(in.Class)) - out.Name = in.Name - out.PodTemplate = (*acme.ACMEChallengeSolverHTTP01IngressPodTemplate)(unsafe.Pointer(in.PodTemplate)) - out.IngressTemplate = (*acme.ACMEChallengeSolverHTTP01IngressTemplate)(unsafe.Pointer(in.IngressTemplate)) - return nil -} - -// Convert_v1alpha2_ACMEChallengeSolverHTTP01Ingress_To_acme_ACMEChallengeSolverHTTP01Ingress is an autogenerated conversion function. -func Convert_v1alpha2_ACMEChallengeSolverHTTP01Ingress_To_acme_ACMEChallengeSolverHTTP01Ingress(in *ACMEChallengeSolverHTTP01Ingress, out *acme.ACMEChallengeSolverHTTP01Ingress, s conversion.Scope) error { - return autoConvert_v1alpha2_ACMEChallengeSolverHTTP01Ingress_To_acme_ACMEChallengeSolverHTTP01Ingress(in, out, s) -} - -func autoConvert_acme_ACMEChallengeSolverHTTP01Ingress_To_v1alpha2_ACMEChallengeSolverHTTP01Ingress(in *acme.ACMEChallengeSolverHTTP01Ingress, out *ACMEChallengeSolverHTTP01Ingress, s conversion.Scope) error { - out.ServiceType = v1.ServiceType(in.ServiceType) - out.Class = (*string)(unsafe.Pointer(in.Class)) - out.Name = in.Name - out.PodTemplate = (*ACMEChallengeSolverHTTP01IngressPodTemplate)(unsafe.Pointer(in.PodTemplate)) - out.IngressTemplate = (*ACMEChallengeSolverHTTP01IngressTemplate)(unsafe.Pointer(in.IngressTemplate)) - return nil -} - -// Convert_acme_ACMEChallengeSolverHTTP01Ingress_To_v1alpha2_ACMEChallengeSolverHTTP01Ingress is an autogenerated conversion function. -func Convert_acme_ACMEChallengeSolverHTTP01Ingress_To_v1alpha2_ACMEChallengeSolverHTTP01Ingress(in *acme.ACMEChallengeSolverHTTP01Ingress, out *ACMEChallengeSolverHTTP01Ingress, s conversion.Scope) error { - return autoConvert_acme_ACMEChallengeSolverHTTP01Ingress_To_v1alpha2_ACMEChallengeSolverHTTP01Ingress(in, out, s) -} - -func autoConvert_v1alpha2_ACMEChallengeSolverHTTP01IngressObjectMeta_To_acme_ACMEChallengeSolverHTTP01IngressObjectMeta(in *ACMEChallengeSolverHTTP01IngressObjectMeta, out *acme.ACMEChallengeSolverHTTP01IngressObjectMeta, s conversion.Scope) error { - out.Annotations = *(*map[string]string)(unsafe.Pointer(&in.Annotations)) - out.Labels = *(*map[string]string)(unsafe.Pointer(&in.Labels)) - return nil -} - -// Convert_v1alpha2_ACMEChallengeSolverHTTP01IngressObjectMeta_To_acme_ACMEChallengeSolverHTTP01IngressObjectMeta is an autogenerated conversion function. -func Convert_v1alpha2_ACMEChallengeSolverHTTP01IngressObjectMeta_To_acme_ACMEChallengeSolverHTTP01IngressObjectMeta(in *ACMEChallengeSolverHTTP01IngressObjectMeta, out *acme.ACMEChallengeSolverHTTP01IngressObjectMeta, s conversion.Scope) error { - return autoConvert_v1alpha2_ACMEChallengeSolverHTTP01IngressObjectMeta_To_acme_ACMEChallengeSolverHTTP01IngressObjectMeta(in, out, s) -} - -func autoConvert_acme_ACMEChallengeSolverHTTP01IngressObjectMeta_To_v1alpha2_ACMEChallengeSolverHTTP01IngressObjectMeta(in *acme.ACMEChallengeSolverHTTP01IngressObjectMeta, out *ACMEChallengeSolverHTTP01IngressObjectMeta, s conversion.Scope) error { - out.Annotations = *(*map[string]string)(unsafe.Pointer(&in.Annotations)) - out.Labels = *(*map[string]string)(unsafe.Pointer(&in.Labels)) - return nil -} - -// Convert_acme_ACMEChallengeSolverHTTP01IngressObjectMeta_To_v1alpha2_ACMEChallengeSolverHTTP01IngressObjectMeta is an autogenerated conversion function. -func Convert_acme_ACMEChallengeSolverHTTP01IngressObjectMeta_To_v1alpha2_ACMEChallengeSolverHTTP01IngressObjectMeta(in *acme.ACMEChallengeSolverHTTP01IngressObjectMeta, out *ACMEChallengeSolverHTTP01IngressObjectMeta, s conversion.Scope) error { - return autoConvert_acme_ACMEChallengeSolverHTTP01IngressObjectMeta_To_v1alpha2_ACMEChallengeSolverHTTP01IngressObjectMeta(in, out, s) -} - -func autoConvert_v1alpha2_ACMEChallengeSolverHTTP01IngressPodObjectMeta_To_acme_ACMEChallengeSolverHTTP01IngressPodObjectMeta(in *ACMEChallengeSolverHTTP01IngressPodObjectMeta, out *acme.ACMEChallengeSolverHTTP01IngressPodObjectMeta, s conversion.Scope) error { - out.Annotations = *(*map[string]string)(unsafe.Pointer(&in.Annotations)) - out.Labels = *(*map[string]string)(unsafe.Pointer(&in.Labels)) - return nil -} - -// Convert_v1alpha2_ACMEChallengeSolverHTTP01IngressPodObjectMeta_To_acme_ACMEChallengeSolverHTTP01IngressPodObjectMeta is an autogenerated conversion function. -func Convert_v1alpha2_ACMEChallengeSolverHTTP01IngressPodObjectMeta_To_acme_ACMEChallengeSolverHTTP01IngressPodObjectMeta(in *ACMEChallengeSolverHTTP01IngressPodObjectMeta, out *acme.ACMEChallengeSolverHTTP01IngressPodObjectMeta, s conversion.Scope) error { - return autoConvert_v1alpha2_ACMEChallengeSolverHTTP01IngressPodObjectMeta_To_acme_ACMEChallengeSolverHTTP01IngressPodObjectMeta(in, out, s) -} - -func autoConvert_acme_ACMEChallengeSolverHTTP01IngressPodObjectMeta_To_v1alpha2_ACMEChallengeSolverHTTP01IngressPodObjectMeta(in *acme.ACMEChallengeSolverHTTP01IngressPodObjectMeta, out *ACMEChallengeSolverHTTP01IngressPodObjectMeta, s conversion.Scope) error { - out.Annotations = *(*map[string]string)(unsafe.Pointer(&in.Annotations)) - out.Labels = *(*map[string]string)(unsafe.Pointer(&in.Labels)) - return nil -} - -// Convert_acme_ACMEChallengeSolverHTTP01IngressPodObjectMeta_To_v1alpha2_ACMEChallengeSolverHTTP01IngressPodObjectMeta is an autogenerated conversion function. -func Convert_acme_ACMEChallengeSolverHTTP01IngressPodObjectMeta_To_v1alpha2_ACMEChallengeSolverHTTP01IngressPodObjectMeta(in *acme.ACMEChallengeSolverHTTP01IngressPodObjectMeta, out *ACMEChallengeSolverHTTP01IngressPodObjectMeta, s conversion.Scope) error { - return autoConvert_acme_ACMEChallengeSolverHTTP01IngressPodObjectMeta_To_v1alpha2_ACMEChallengeSolverHTTP01IngressPodObjectMeta(in, out, s) -} - -func autoConvert_v1alpha2_ACMEChallengeSolverHTTP01IngressPodSpec_To_acme_ACMEChallengeSolverHTTP01IngressPodSpec(in *ACMEChallengeSolverHTTP01IngressPodSpec, out *acme.ACMEChallengeSolverHTTP01IngressPodSpec, s conversion.Scope) error { - out.NodeSelector = *(*map[string]string)(unsafe.Pointer(&in.NodeSelector)) - out.Affinity = (*v1.Affinity)(unsafe.Pointer(in.Affinity)) - out.Tolerations = *(*[]v1.Toleration)(unsafe.Pointer(&in.Tolerations)) - out.PriorityClassName = in.PriorityClassName - out.ServiceAccountName = in.ServiceAccountName - return nil -} - -// Convert_v1alpha2_ACMEChallengeSolverHTTP01IngressPodSpec_To_acme_ACMEChallengeSolverHTTP01IngressPodSpec is an autogenerated conversion function. -func Convert_v1alpha2_ACMEChallengeSolverHTTP01IngressPodSpec_To_acme_ACMEChallengeSolverHTTP01IngressPodSpec(in *ACMEChallengeSolverHTTP01IngressPodSpec, out *acme.ACMEChallengeSolverHTTP01IngressPodSpec, s conversion.Scope) error { - return autoConvert_v1alpha2_ACMEChallengeSolverHTTP01IngressPodSpec_To_acme_ACMEChallengeSolverHTTP01IngressPodSpec(in, out, s) -} - -func autoConvert_acme_ACMEChallengeSolverHTTP01IngressPodSpec_To_v1alpha2_ACMEChallengeSolverHTTP01IngressPodSpec(in *acme.ACMEChallengeSolverHTTP01IngressPodSpec, out *ACMEChallengeSolverHTTP01IngressPodSpec, s conversion.Scope) error { - out.NodeSelector = *(*map[string]string)(unsafe.Pointer(&in.NodeSelector)) - out.Affinity = (*v1.Affinity)(unsafe.Pointer(in.Affinity)) - out.Tolerations = *(*[]v1.Toleration)(unsafe.Pointer(&in.Tolerations)) - out.PriorityClassName = in.PriorityClassName - out.ServiceAccountName = in.ServiceAccountName - return nil -} - -// Convert_acme_ACMEChallengeSolverHTTP01IngressPodSpec_To_v1alpha2_ACMEChallengeSolverHTTP01IngressPodSpec is an autogenerated conversion function. -func Convert_acme_ACMEChallengeSolverHTTP01IngressPodSpec_To_v1alpha2_ACMEChallengeSolverHTTP01IngressPodSpec(in *acme.ACMEChallengeSolverHTTP01IngressPodSpec, out *ACMEChallengeSolverHTTP01IngressPodSpec, s conversion.Scope) error { - return autoConvert_acme_ACMEChallengeSolverHTTP01IngressPodSpec_To_v1alpha2_ACMEChallengeSolverHTTP01IngressPodSpec(in, out, s) -} - -func autoConvert_v1alpha2_ACMEChallengeSolverHTTP01IngressPodTemplate_To_acme_ACMEChallengeSolverHTTP01IngressPodTemplate(in *ACMEChallengeSolverHTTP01IngressPodTemplate, out *acme.ACMEChallengeSolverHTTP01IngressPodTemplate, s conversion.Scope) error { - if err := Convert_v1alpha2_ACMEChallengeSolverHTTP01IngressPodObjectMeta_To_acme_ACMEChallengeSolverHTTP01IngressPodObjectMeta(&in.ACMEChallengeSolverHTTP01IngressPodObjectMeta, &out.ACMEChallengeSolverHTTP01IngressPodObjectMeta, s); err != nil { - return err - } - if err := Convert_v1alpha2_ACMEChallengeSolverHTTP01IngressPodSpec_To_acme_ACMEChallengeSolverHTTP01IngressPodSpec(&in.Spec, &out.Spec, s); err != nil { - return err - } - return nil -} - -// Convert_v1alpha2_ACMEChallengeSolverHTTP01IngressPodTemplate_To_acme_ACMEChallengeSolverHTTP01IngressPodTemplate is an autogenerated conversion function. -func Convert_v1alpha2_ACMEChallengeSolverHTTP01IngressPodTemplate_To_acme_ACMEChallengeSolverHTTP01IngressPodTemplate(in *ACMEChallengeSolverHTTP01IngressPodTemplate, out *acme.ACMEChallengeSolverHTTP01IngressPodTemplate, s conversion.Scope) error { - return autoConvert_v1alpha2_ACMEChallengeSolverHTTP01IngressPodTemplate_To_acme_ACMEChallengeSolverHTTP01IngressPodTemplate(in, out, s) -} - -func autoConvert_acme_ACMEChallengeSolverHTTP01IngressPodTemplate_To_v1alpha2_ACMEChallengeSolverHTTP01IngressPodTemplate(in *acme.ACMEChallengeSolverHTTP01IngressPodTemplate, out *ACMEChallengeSolverHTTP01IngressPodTemplate, s conversion.Scope) error { - if err := Convert_acme_ACMEChallengeSolverHTTP01IngressPodObjectMeta_To_v1alpha2_ACMEChallengeSolverHTTP01IngressPodObjectMeta(&in.ACMEChallengeSolverHTTP01IngressPodObjectMeta, &out.ACMEChallengeSolverHTTP01IngressPodObjectMeta, s); err != nil { - return err - } - if err := Convert_acme_ACMEChallengeSolverHTTP01IngressPodSpec_To_v1alpha2_ACMEChallengeSolverHTTP01IngressPodSpec(&in.Spec, &out.Spec, s); err != nil { - return err - } - return nil -} - -// Convert_acme_ACMEChallengeSolverHTTP01IngressPodTemplate_To_v1alpha2_ACMEChallengeSolverHTTP01IngressPodTemplate is an autogenerated conversion function. -func Convert_acme_ACMEChallengeSolverHTTP01IngressPodTemplate_To_v1alpha2_ACMEChallengeSolverHTTP01IngressPodTemplate(in *acme.ACMEChallengeSolverHTTP01IngressPodTemplate, out *ACMEChallengeSolverHTTP01IngressPodTemplate, s conversion.Scope) error { - return autoConvert_acme_ACMEChallengeSolverHTTP01IngressPodTemplate_To_v1alpha2_ACMEChallengeSolverHTTP01IngressPodTemplate(in, out, s) -} - -func autoConvert_v1alpha2_ACMEChallengeSolverHTTP01IngressTemplate_To_acme_ACMEChallengeSolverHTTP01IngressTemplate(in *ACMEChallengeSolverHTTP01IngressTemplate, out *acme.ACMEChallengeSolverHTTP01IngressTemplate, s conversion.Scope) error { - if err := Convert_v1alpha2_ACMEChallengeSolverHTTP01IngressObjectMeta_To_acme_ACMEChallengeSolverHTTP01IngressObjectMeta(&in.ACMEChallengeSolverHTTP01IngressObjectMeta, &out.ACMEChallengeSolverHTTP01IngressObjectMeta, s); err != nil { - return err - } - return nil -} - -// Convert_v1alpha2_ACMEChallengeSolverHTTP01IngressTemplate_To_acme_ACMEChallengeSolverHTTP01IngressTemplate is an autogenerated conversion function. -func Convert_v1alpha2_ACMEChallengeSolverHTTP01IngressTemplate_To_acme_ACMEChallengeSolverHTTP01IngressTemplate(in *ACMEChallengeSolverHTTP01IngressTemplate, out *acme.ACMEChallengeSolverHTTP01IngressTemplate, s conversion.Scope) error { - return autoConvert_v1alpha2_ACMEChallengeSolverHTTP01IngressTemplate_To_acme_ACMEChallengeSolverHTTP01IngressTemplate(in, out, s) -} - -func autoConvert_acme_ACMEChallengeSolverHTTP01IngressTemplate_To_v1alpha2_ACMEChallengeSolverHTTP01IngressTemplate(in *acme.ACMEChallengeSolverHTTP01IngressTemplate, out *ACMEChallengeSolverHTTP01IngressTemplate, s conversion.Scope) error { - if err := Convert_acme_ACMEChallengeSolverHTTP01IngressObjectMeta_To_v1alpha2_ACMEChallengeSolverHTTP01IngressObjectMeta(&in.ACMEChallengeSolverHTTP01IngressObjectMeta, &out.ACMEChallengeSolverHTTP01IngressObjectMeta, s); err != nil { - return err - } - return nil -} - -// Convert_acme_ACMEChallengeSolverHTTP01IngressTemplate_To_v1alpha2_ACMEChallengeSolverHTTP01IngressTemplate is an autogenerated conversion function. -func Convert_acme_ACMEChallengeSolverHTTP01IngressTemplate_To_v1alpha2_ACMEChallengeSolverHTTP01IngressTemplate(in *acme.ACMEChallengeSolverHTTP01IngressTemplate, out *ACMEChallengeSolverHTTP01IngressTemplate, s conversion.Scope) error { - return autoConvert_acme_ACMEChallengeSolverHTTP01IngressTemplate_To_v1alpha2_ACMEChallengeSolverHTTP01IngressTemplate(in, out, s) -} - -func autoConvert_v1alpha2_ACMEExternalAccountBinding_To_acme_ACMEExternalAccountBinding(in *ACMEExternalAccountBinding, out *acme.ACMEExternalAccountBinding, s conversion.Scope) error { - out.KeyID = in.KeyID - if err := metav1.Convert_v1_SecretKeySelector_To_meta_SecretKeySelector(&in.Key, &out.Key, s); err != nil { - return err - } - out.KeyAlgorithm = acme.HMACKeyAlgorithm(in.KeyAlgorithm) - return nil -} - -// Convert_v1alpha2_ACMEExternalAccountBinding_To_acme_ACMEExternalAccountBinding is an autogenerated conversion function. -func Convert_v1alpha2_ACMEExternalAccountBinding_To_acme_ACMEExternalAccountBinding(in *ACMEExternalAccountBinding, out *acme.ACMEExternalAccountBinding, s conversion.Scope) error { - return autoConvert_v1alpha2_ACMEExternalAccountBinding_To_acme_ACMEExternalAccountBinding(in, out, s) -} - -func autoConvert_acme_ACMEExternalAccountBinding_To_v1alpha2_ACMEExternalAccountBinding(in *acme.ACMEExternalAccountBinding, out *ACMEExternalAccountBinding, s conversion.Scope) error { - out.KeyID = in.KeyID - if err := metav1.Convert_meta_SecretKeySelector_To_v1_SecretKeySelector(&in.Key, &out.Key, s); err != nil { - return err - } - out.KeyAlgorithm = HMACKeyAlgorithm(in.KeyAlgorithm) - return nil -} - -// Convert_acme_ACMEExternalAccountBinding_To_v1alpha2_ACMEExternalAccountBinding is an autogenerated conversion function. -func Convert_acme_ACMEExternalAccountBinding_To_v1alpha2_ACMEExternalAccountBinding(in *acme.ACMEExternalAccountBinding, out *ACMEExternalAccountBinding, s conversion.Scope) error { - return autoConvert_acme_ACMEExternalAccountBinding_To_v1alpha2_ACMEExternalAccountBinding(in, out, s) -} - -func autoConvert_v1alpha2_ACMEIssuer_To_acme_ACMEIssuer(in *ACMEIssuer, out *acme.ACMEIssuer, s conversion.Scope) error { - out.Email = in.Email - out.Server = in.Server - out.PreferredChain = in.PreferredChain - out.SkipTLSVerify = in.SkipTLSVerify - if in.ExternalAccountBinding != nil { - in, out := &in.ExternalAccountBinding, &out.ExternalAccountBinding - *out = new(acme.ACMEExternalAccountBinding) - if err := Convert_v1alpha2_ACMEExternalAccountBinding_To_acme_ACMEExternalAccountBinding(*in, *out, s); err != nil { - return err - } - } else { - out.ExternalAccountBinding = nil - } - if err := metav1.Convert_v1_SecretKeySelector_To_meta_SecretKeySelector(&in.PrivateKey, &out.PrivateKey, s); err != nil { - return err - } - if in.Solvers != nil { - in, out := &in.Solvers, &out.Solvers - *out = make([]acme.ACMEChallengeSolver, len(*in)) - for i := range *in { - if err := Convert_v1alpha2_ACMEChallengeSolver_To_acme_ACMEChallengeSolver(&(*in)[i], &(*out)[i], s); err != nil { - return err - } - } - } else { - out.Solvers = nil - } - out.DisableAccountKeyGeneration = in.DisableAccountKeyGeneration - out.EnableDurationFeature = in.EnableDurationFeature - return nil -} - -func autoConvert_acme_ACMEIssuer_To_v1alpha2_ACMEIssuer(in *acme.ACMEIssuer, out *ACMEIssuer, s conversion.Scope) error { - out.Email = in.Email - out.Server = in.Server - out.PreferredChain = in.PreferredChain - out.SkipTLSVerify = in.SkipTLSVerify - if in.ExternalAccountBinding != nil { - in, out := &in.ExternalAccountBinding, &out.ExternalAccountBinding - *out = new(ACMEExternalAccountBinding) - if err := Convert_acme_ACMEExternalAccountBinding_To_v1alpha2_ACMEExternalAccountBinding(*in, *out, s); err != nil { - return err - } - } else { - out.ExternalAccountBinding = nil - } - if err := metav1.Convert_meta_SecretKeySelector_To_v1_SecretKeySelector(&in.PrivateKey, &out.PrivateKey, s); err != nil { - return err - } - if in.Solvers != nil { - in, out := &in.Solvers, &out.Solvers - *out = make([]ACMEChallengeSolver, len(*in)) - for i := range *in { - if err := Convert_acme_ACMEChallengeSolver_To_v1alpha2_ACMEChallengeSolver(&(*in)[i], &(*out)[i], s); err != nil { - return err - } - } - } else { - out.Solvers = nil - } - out.DisableAccountKeyGeneration = in.DisableAccountKeyGeneration - out.EnableDurationFeature = in.EnableDurationFeature - return nil -} - -func autoConvert_v1alpha2_ACMEIssuerDNS01ProviderAcmeDNS_To_acme_ACMEIssuerDNS01ProviderAcmeDNS(in *ACMEIssuerDNS01ProviderAcmeDNS, out *acme.ACMEIssuerDNS01ProviderAcmeDNS, s conversion.Scope) error { - out.Host = in.Host - if err := metav1.Convert_v1_SecretKeySelector_To_meta_SecretKeySelector(&in.AccountSecret, &out.AccountSecret, s); err != nil { - return err - } - return nil -} - -// Convert_v1alpha2_ACMEIssuerDNS01ProviderAcmeDNS_To_acme_ACMEIssuerDNS01ProviderAcmeDNS is an autogenerated conversion function. -func Convert_v1alpha2_ACMEIssuerDNS01ProviderAcmeDNS_To_acme_ACMEIssuerDNS01ProviderAcmeDNS(in *ACMEIssuerDNS01ProviderAcmeDNS, out *acme.ACMEIssuerDNS01ProviderAcmeDNS, s conversion.Scope) error { - return autoConvert_v1alpha2_ACMEIssuerDNS01ProviderAcmeDNS_To_acme_ACMEIssuerDNS01ProviderAcmeDNS(in, out, s) -} - -func autoConvert_acme_ACMEIssuerDNS01ProviderAcmeDNS_To_v1alpha2_ACMEIssuerDNS01ProviderAcmeDNS(in *acme.ACMEIssuerDNS01ProviderAcmeDNS, out *ACMEIssuerDNS01ProviderAcmeDNS, s conversion.Scope) error { - out.Host = in.Host - if err := metav1.Convert_meta_SecretKeySelector_To_v1_SecretKeySelector(&in.AccountSecret, &out.AccountSecret, s); err != nil { - return err - } - return nil -} - -// Convert_acme_ACMEIssuerDNS01ProviderAcmeDNS_To_v1alpha2_ACMEIssuerDNS01ProviderAcmeDNS is an autogenerated conversion function. -func Convert_acme_ACMEIssuerDNS01ProviderAcmeDNS_To_v1alpha2_ACMEIssuerDNS01ProviderAcmeDNS(in *acme.ACMEIssuerDNS01ProviderAcmeDNS, out *ACMEIssuerDNS01ProviderAcmeDNS, s conversion.Scope) error { - return autoConvert_acme_ACMEIssuerDNS01ProviderAcmeDNS_To_v1alpha2_ACMEIssuerDNS01ProviderAcmeDNS(in, out, s) -} - -func autoConvert_v1alpha2_ACMEIssuerDNS01ProviderAkamai_To_acme_ACMEIssuerDNS01ProviderAkamai(in *ACMEIssuerDNS01ProviderAkamai, out *acme.ACMEIssuerDNS01ProviderAkamai, s conversion.Scope) error { - out.ServiceConsumerDomain = in.ServiceConsumerDomain - if err := metav1.Convert_v1_SecretKeySelector_To_meta_SecretKeySelector(&in.ClientToken, &out.ClientToken, s); err != nil { - return err - } - if err := metav1.Convert_v1_SecretKeySelector_To_meta_SecretKeySelector(&in.ClientSecret, &out.ClientSecret, s); err != nil { - return err - } - if err := metav1.Convert_v1_SecretKeySelector_To_meta_SecretKeySelector(&in.AccessToken, &out.AccessToken, s); err != nil { - return err - } - return nil -} - -// Convert_v1alpha2_ACMEIssuerDNS01ProviderAkamai_To_acme_ACMEIssuerDNS01ProviderAkamai is an autogenerated conversion function. -func Convert_v1alpha2_ACMEIssuerDNS01ProviderAkamai_To_acme_ACMEIssuerDNS01ProviderAkamai(in *ACMEIssuerDNS01ProviderAkamai, out *acme.ACMEIssuerDNS01ProviderAkamai, s conversion.Scope) error { - return autoConvert_v1alpha2_ACMEIssuerDNS01ProviderAkamai_To_acme_ACMEIssuerDNS01ProviderAkamai(in, out, s) -} - -func autoConvert_acme_ACMEIssuerDNS01ProviderAkamai_To_v1alpha2_ACMEIssuerDNS01ProviderAkamai(in *acme.ACMEIssuerDNS01ProviderAkamai, out *ACMEIssuerDNS01ProviderAkamai, s conversion.Scope) error { - out.ServiceConsumerDomain = in.ServiceConsumerDomain - if err := metav1.Convert_meta_SecretKeySelector_To_v1_SecretKeySelector(&in.ClientToken, &out.ClientToken, s); err != nil { - return err - } - if err := metav1.Convert_meta_SecretKeySelector_To_v1_SecretKeySelector(&in.ClientSecret, &out.ClientSecret, s); err != nil { - return err - } - if err := metav1.Convert_meta_SecretKeySelector_To_v1_SecretKeySelector(&in.AccessToken, &out.AccessToken, s); err != nil { - return err - } - return nil -} - -// Convert_acme_ACMEIssuerDNS01ProviderAkamai_To_v1alpha2_ACMEIssuerDNS01ProviderAkamai is an autogenerated conversion function. -func Convert_acme_ACMEIssuerDNS01ProviderAkamai_To_v1alpha2_ACMEIssuerDNS01ProviderAkamai(in *acme.ACMEIssuerDNS01ProviderAkamai, out *ACMEIssuerDNS01ProviderAkamai, s conversion.Scope) error { - return autoConvert_acme_ACMEIssuerDNS01ProviderAkamai_To_v1alpha2_ACMEIssuerDNS01ProviderAkamai(in, out, s) -} - -func autoConvert_v1alpha2_ACMEIssuerDNS01ProviderAzureDNS_To_acme_ACMEIssuerDNS01ProviderAzureDNS(in *ACMEIssuerDNS01ProviderAzureDNS, out *acme.ACMEIssuerDNS01ProviderAzureDNS, s conversion.Scope) error { - out.ClientID = in.ClientID - if in.ClientSecret != nil { - in, out := &in.ClientSecret, &out.ClientSecret - *out = new(meta.SecretKeySelector) - if err := metav1.Convert_v1_SecretKeySelector_To_meta_SecretKeySelector(*in, *out, s); err != nil { - return err - } - } else { - out.ClientSecret = nil - } - out.SubscriptionID = in.SubscriptionID - out.TenantID = in.TenantID - out.ResourceGroupName = in.ResourceGroupName - out.HostedZoneName = in.HostedZoneName - out.Environment = acme.AzureDNSEnvironment(in.Environment) - out.ManagedIdentity = (*acme.AzureManagedIdentity)(unsafe.Pointer(in.ManagedIdentity)) - return nil -} - -// Convert_v1alpha2_ACMEIssuerDNS01ProviderAzureDNS_To_acme_ACMEIssuerDNS01ProviderAzureDNS is an autogenerated conversion function. -func Convert_v1alpha2_ACMEIssuerDNS01ProviderAzureDNS_To_acme_ACMEIssuerDNS01ProviderAzureDNS(in *ACMEIssuerDNS01ProviderAzureDNS, out *acme.ACMEIssuerDNS01ProviderAzureDNS, s conversion.Scope) error { - return autoConvert_v1alpha2_ACMEIssuerDNS01ProviderAzureDNS_To_acme_ACMEIssuerDNS01ProviderAzureDNS(in, out, s) -} - -func autoConvert_acme_ACMEIssuerDNS01ProviderAzureDNS_To_v1alpha2_ACMEIssuerDNS01ProviderAzureDNS(in *acme.ACMEIssuerDNS01ProviderAzureDNS, out *ACMEIssuerDNS01ProviderAzureDNS, s conversion.Scope) error { - out.ClientID = in.ClientID - if in.ClientSecret != nil { - in, out := &in.ClientSecret, &out.ClientSecret - *out = new(apismetav1.SecretKeySelector) - if err := metav1.Convert_meta_SecretKeySelector_To_v1_SecretKeySelector(*in, *out, s); err != nil { - return err - } - } else { - out.ClientSecret = nil - } - out.SubscriptionID = in.SubscriptionID - out.TenantID = in.TenantID - out.ResourceGroupName = in.ResourceGroupName - out.HostedZoneName = in.HostedZoneName - out.Environment = AzureDNSEnvironment(in.Environment) - out.ManagedIdentity = (*AzureManagedIdentity)(unsafe.Pointer(in.ManagedIdentity)) - return nil -} - -// Convert_acme_ACMEIssuerDNS01ProviderAzureDNS_To_v1alpha2_ACMEIssuerDNS01ProviderAzureDNS is an autogenerated conversion function. -func Convert_acme_ACMEIssuerDNS01ProviderAzureDNS_To_v1alpha2_ACMEIssuerDNS01ProviderAzureDNS(in *acme.ACMEIssuerDNS01ProviderAzureDNS, out *ACMEIssuerDNS01ProviderAzureDNS, s conversion.Scope) error { - return autoConvert_acme_ACMEIssuerDNS01ProviderAzureDNS_To_v1alpha2_ACMEIssuerDNS01ProviderAzureDNS(in, out, s) -} - -func autoConvert_v1alpha2_ACMEIssuerDNS01ProviderCloudDNS_To_acme_ACMEIssuerDNS01ProviderCloudDNS(in *ACMEIssuerDNS01ProviderCloudDNS, out *acme.ACMEIssuerDNS01ProviderCloudDNS, s conversion.Scope) error { - if in.ServiceAccount != nil { - in, out := &in.ServiceAccount, &out.ServiceAccount - *out = new(meta.SecretKeySelector) - if err := metav1.Convert_v1_SecretKeySelector_To_meta_SecretKeySelector(*in, *out, s); err != nil { - return err - } - } else { - out.ServiceAccount = nil - } - out.Project = in.Project - out.HostedZoneName = in.HostedZoneName - return nil -} - -// Convert_v1alpha2_ACMEIssuerDNS01ProviderCloudDNS_To_acme_ACMEIssuerDNS01ProviderCloudDNS is an autogenerated conversion function. -func Convert_v1alpha2_ACMEIssuerDNS01ProviderCloudDNS_To_acme_ACMEIssuerDNS01ProviderCloudDNS(in *ACMEIssuerDNS01ProviderCloudDNS, out *acme.ACMEIssuerDNS01ProviderCloudDNS, s conversion.Scope) error { - return autoConvert_v1alpha2_ACMEIssuerDNS01ProviderCloudDNS_To_acme_ACMEIssuerDNS01ProviderCloudDNS(in, out, s) -} - -func autoConvert_acme_ACMEIssuerDNS01ProviderCloudDNS_To_v1alpha2_ACMEIssuerDNS01ProviderCloudDNS(in *acme.ACMEIssuerDNS01ProviderCloudDNS, out *ACMEIssuerDNS01ProviderCloudDNS, s conversion.Scope) error { - if in.ServiceAccount != nil { - in, out := &in.ServiceAccount, &out.ServiceAccount - *out = new(apismetav1.SecretKeySelector) - if err := metav1.Convert_meta_SecretKeySelector_To_v1_SecretKeySelector(*in, *out, s); err != nil { - return err - } - } else { - out.ServiceAccount = nil - } - out.Project = in.Project - out.HostedZoneName = in.HostedZoneName - return nil -} - -// Convert_acme_ACMEIssuerDNS01ProviderCloudDNS_To_v1alpha2_ACMEIssuerDNS01ProviderCloudDNS is an autogenerated conversion function. -func Convert_acme_ACMEIssuerDNS01ProviderCloudDNS_To_v1alpha2_ACMEIssuerDNS01ProviderCloudDNS(in *acme.ACMEIssuerDNS01ProviderCloudDNS, out *ACMEIssuerDNS01ProviderCloudDNS, s conversion.Scope) error { - return autoConvert_acme_ACMEIssuerDNS01ProviderCloudDNS_To_v1alpha2_ACMEIssuerDNS01ProviderCloudDNS(in, out, s) -} - -func autoConvert_v1alpha2_ACMEIssuerDNS01ProviderCloudflare_To_acme_ACMEIssuerDNS01ProviderCloudflare(in *ACMEIssuerDNS01ProviderCloudflare, out *acme.ACMEIssuerDNS01ProviderCloudflare, s conversion.Scope) error { - out.Email = in.Email - if in.APIKey != nil { - in, out := &in.APIKey, &out.APIKey - *out = new(meta.SecretKeySelector) - if err := metav1.Convert_v1_SecretKeySelector_To_meta_SecretKeySelector(*in, *out, s); err != nil { - return err - } - } else { - out.APIKey = nil - } - if in.APIToken != nil { - in, out := &in.APIToken, &out.APIToken - *out = new(meta.SecretKeySelector) - if err := metav1.Convert_v1_SecretKeySelector_To_meta_SecretKeySelector(*in, *out, s); err != nil { - return err - } - } else { - out.APIToken = nil - } - return nil -} - -// Convert_v1alpha2_ACMEIssuerDNS01ProviderCloudflare_To_acme_ACMEIssuerDNS01ProviderCloudflare is an autogenerated conversion function. -func Convert_v1alpha2_ACMEIssuerDNS01ProviderCloudflare_To_acme_ACMEIssuerDNS01ProviderCloudflare(in *ACMEIssuerDNS01ProviderCloudflare, out *acme.ACMEIssuerDNS01ProviderCloudflare, s conversion.Scope) error { - return autoConvert_v1alpha2_ACMEIssuerDNS01ProviderCloudflare_To_acme_ACMEIssuerDNS01ProviderCloudflare(in, out, s) -} - -func autoConvert_acme_ACMEIssuerDNS01ProviderCloudflare_To_v1alpha2_ACMEIssuerDNS01ProviderCloudflare(in *acme.ACMEIssuerDNS01ProviderCloudflare, out *ACMEIssuerDNS01ProviderCloudflare, s conversion.Scope) error { - out.Email = in.Email - if in.APIKey != nil { - in, out := &in.APIKey, &out.APIKey - *out = new(apismetav1.SecretKeySelector) - if err := metav1.Convert_meta_SecretKeySelector_To_v1_SecretKeySelector(*in, *out, s); err != nil { - return err - } - } else { - out.APIKey = nil - } - if in.APIToken != nil { - in, out := &in.APIToken, &out.APIToken - *out = new(apismetav1.SecretKeySelector) - if err := metav1.Convert_meta_SecretKeySelector_To_v1_SecretKeySelector(*in, *out, s); err != nil { - return err - } - } else { - out.APIToken = nil - } - return nil -} - -// Convert_acme_ACMEIssuerDNS01ProviderCloudflare_To_v1alpha2_ACMEIssuerDNS01ProviderCloudflare is an autogenerated conversion function. -func Convert_acme_ACMEIssuerDNS01ProviderCloudflare_To_v1alpha2_ACMEIssuerDNS01ProviderCloudflare(in *acme.ACMEIssuerDNS01ProviderCloudflare, out *ACMEIssuerDNS01ProviderCloudflare, s conversion.Scope) error { - return autoConvert_acme_ACMEIssuerDNS01ProviderCloudflare_To_v1alpha2_ACMEIssuerDNS01ProviderCloudflare(in, out, s) -} - -func autoConvert_v1alpha2_ACMEIssuerDNS01ProviderDigitalOcean_To_acme_ACMEIssuerDNS01ProviderDigitalOcean(in *ACMEIssuerDNS01ProviderDigitalOcean, out *acme.ACMEIssuerDNS01ProviderDigitalOcean, s conversion.Scope) error { - if err := metav1.Convert_v1_SecretKeySelector_To_meta_SecretKeySelector(&in.Token, &out.Token, s); err != nil { - return err - } - return nil -} - -// Convert_v1alpha2_ACMEIssuerDNS01ProviderDigitalOcean_To_acme_ACMEIssuerDNS01ProviderDigitalOcean is an autogenerated conversion function. -func Convert_v1alpha2_ACMEIssuerDNS01ProviderDigitalOcean_To_acme_ACMEIssuerDNS01ProviderDigitalOcean(in *ACMEIssuerDNS01ProviderDigitalOcean, out *acme.ACMEIssuerDNS01ProviderDigitalOcean, s conversion.Scope) error { - return autoConvert_v1alpha2_ACMEIssuerDNS01ProviderDigitalOcean_To_acme_ACMEIssuerDNS01ProviderDigitalOcean(in, out, s) -} - -func autoConvert_acme_ACMEIssuerDNS01ProviderDigitalOcean_To_v1alpha2_ACMEIssuerDNS01ProviderDigitalOcean(in *acme.ACMEIssuerDNS01ProviderDigitalOcean, out *ACMEIssuerDNS01ProviderDigitalOcean, s conversion.Scope) error { - if err := metav1.Convert_meta_SecretKeySelector_To_v1_SecretKeySelector(&in.Token, &out.Token, s); err != nil { - return err - } - return nil -} - -// Convert_acme_ACMEIssuerDNS01ProviderDigitalOcean_To_v1alpha2_ACMEIssuerDNS01ProviderDigitalOcean is an autogenerated conversion function. -func Convert_acme_ACMEIssuerDNS01ProviderDigitalOcean_To_v1alpha2_ACMEIssuerDNS01ProviderDigitalOcean(in *acme.ACMEIssuerDNS01ProviderDigitalOcean, out *ACMEIssuerDNS01ProviderDigitalOcean, s conversion.Scope) error { - return autoConvert_acme_ACMEIssuerDNS01ProviderDigitalOcean_To_v1alpha2_ACMEIssuerDNS01ProviderDigitalOcean(in, out, s) -} - -func autoConvert_v1alpha2_ACMEIssuerDNS01ProviderRFC2136_To_acme_ACMEIssuerDNS01ProviderRFC2136(in *ACMEIssuerDNS01ProviderRFC2136, out *acme.ACMEIssuerDNS01ProviderRFC2136, s conversion.Scope) error { - out.Nameserver = in.Nameserver - if err := metav1.Convert_v1_SecretKeySelector_To_meta_SecretKeySelector(&in.TSIGSecret, &out.TSIGSecret, s); err != nil { - return err - } - out.TSIGKeyName = in.TSIGKeyName - out.TSIGAlgorithm = in.TSIGAlgorithm - return nil -} - -// Convert_v1alpha2_ACMEIssuerDNS01ProviderRFC2136_To_acme_ACMEIssuerDNS01ProviderRFC2136 is an autogenerated conversion function. -func Convert_v1alpha2_ACMEIssuerDNS01ProviderRFC2136_To_acme_ACMEIssuerDNS01ProviderRFC2136(in *ACMEIssuerDNS01ProviderRFC2136, out *acme.ACMEIssuerDNS01ProviderRFC2136, s conversion.Scope) error { - return autoConvert_v1alpha2_ACMEIssuerDNS01ProviderRFC2136_To_acme_ACMEIssuerDNS01ProviderRFC2136(in, out, s) -} - -func autoConvert_acme_ACMEIssuerDNS01ProviderRFC2136_To_v1alpha2_ACMEIssuerDNS01ProviderRFC2136(in *acme.ACMEIssuerDNS01ProviderRFC2136, out *ACMEIssuerDNS01ProviderRFC2136, s conversion.Scope) error { - out.Nameserver = in.Nameserver - if err := metav1.Convert_meta_SecretKeySelector_To_v1_SecretKeySelector(&in.TSIGSecret, &out.TSIGSecret, s); err != nil { - return err - } - out.TSIGKeyName = in.TSIGKeyName - out.TSIGAlgorithm = in.TSIGAlgorithm - return nil -} - -// Convert_acme_ACMEIssuerDNS01ProviderRFC2136_To_v1alpha2_ACMEIssuerDNS01ProviderRFC2136 is an autogenerated conversion function. -func Convert_acme_ACMEIssuerDNS01ProviderRFC2136_To_v1alpha2_ACMEIssuerDNS01ProviderRFC2136(in *acme.ACMEIssuerDNS01ProviderRFC2136, out *ACMEIssuerDNS01ProviderRFC2136, s conversion.Scope) error { - return autoConvert_acme_ACMEIssuerDNS01ProviderRFC2136_To_v1alpha2_ACMEIssuerDNS01ProviderRFC2136(in, out, s) -} - -func autoConvert_v1alpha2_ACMEIssuerDNS01ProviderRoute53_To_acme_ACMEIssuerDNS01ProviderRoute53(in *ACMEIssuerDNS01ProviderRoute53, out *acme.ACMEIssuerDNS01ProviderRoute53, s conversion.Scope) error { - out.AccessKeyID = in.AccessKeyID - if in.SecretAccessKeyID != nil { - in, out := &in.SecretAccessKeyID, &out.SecretAccessKeyID - *out = new(meta.SecretKeySelector) - if err := metav1.Convert_v1_SecretKeySelector_To_meta_SecretKeySelector(*in, *out, s); err != nil { - return err - } - } else { - out.SecretAccessKeyID = nil - } - if err := metav1.Convert_v1_SecretKeySelector_To_meta_SecretKeySelector(&in.SecretAccessKey, &out.SecretAccessKey, s); err != nil { - return err - } - out.Role = in.Role - out.HostedZoneID = in.HostedZoneID - out.Region = in.Region - return nil -} - -// Convert_v1alpha2_ACMEIssuerDNS01ProviderRoute53_To_acme_ACMEIssuerDNS01ProviderRoute53 is an autogenerated conversion function. -func Convert_v1alpha2_ACMEIssuerDNS01ProviderRoute53_To_acme_ACMEIssuerDNS01ProviderRoute53(in *ACMEIssuerDNS01ProviderRoute53, out *acme.ACMEIssuerDNS01ProviderRoute53, s conversion.Scope) error { - return autoConvert_v1alpha2_ACMEIssuerDNS01ProviderRoute53_To_acme_ACMEIssuerDNS01ProviderRoute53(in, out, s) -} - -func autoConvert_acme_ACMEIssuerDNS01ProviderRoute53_To_v1alpha2_ACMEIssuerDNS01ProviderRoute53(in *acme.ACMEIssuerDNS01ProviderRoute53, out *ACMEIssuerDNS01ProviderRoute53, s conversion.Scope) error { - out.AccessKeyID = in.AccessKeyID - if in.SecretAccessKeyID != nil { - in, out := &in.SecretAccessKeyID, &out.SecretAccessKeyID - *out = new(apismetav1.SecretKeySelector) - if err := metav1.Convert_meta_SecretKeySelector_To_v1_SecretKeySelector(*in, *out, s); err != nil { - return err - } - } else { - out.SecretAccessKeyID = nil - } - if err := metav1.Convert_meta_SecretKeySelector_To_v1_SecretKeySelector(&in.SecretAccessKey, &out.SecretAccessKey, s); err != nil { - return err - } - out.Role = in.Role - out.HostedZoneID = in.HostedZoneID - out.Region = in.Region - return nil -} - -// Convert_acme_ACMEIssuerDNS01ProviderRoute53_To_v1alpha2_ACMEIssuerDNS01ProviderRoute53 is an autogenerated conversion function. -func Convert_acme_ACMEIssuerDNS01ProviderRoute53_To_v1alpha2_ACMEIssuerDNS01ProviderRoute53(in *acme.ACMEIssuerDNS01ProviderRoute53, out *ACMEIssuerDNS01ProviderRoute53, s conversion.Scope) error { - return autoConvert_acme_ACMEIssuerDNS01ProviderRoute53_To_v1alpha2_ACMEIssuerDNS01ProviderRoute53(in, out, s) -} - -func autoConvert_v1alpha2_ACMEIssuerDNS01ProviderWebhook_To_acme_ACMEIssuerDNS01ProviderWebhook(in *ACMEIssuerDNS01ProviderWebhook, out *acme.ACMEIssuerDNS01ProviderWebhook, s conversion.Scope) error { - out.GroupName = in.GroupName - out.SolverName = in.SolverName - out.Config = (*apiextensionsv1.JSON)(unsafe.Pointer(in.Config)) - return nil -} - -// Convert_v1alpha2_ACMEIssuerDNS01ProviderWebhook_To_acme_ACMEIssuerDNS01ProviderWebhook is an autogenerated conversion function. -func Convert_v1alpha2_ACMEIssuerDNS01ProviderWebhook_To_acme_ACMEIssuerDNS01ProviderWebhook(in *ACMEIssuerDNS01ProviderWebhook, out *acme.ACMEIssuerDNS01ProviderWebhook, s conversion.Scope) error { - return autoConvert_v1alpha2_ACMEIssuerDNS01ProviderWebhook_To_acme_ACMEIssuerDNS01ProviderWebhook(in, out, s) -} - -func autoConvert_acme_ACMEIssuerDNS01ProviderWebhook_To_v1alpha2_ACMEIssuerDNS01ProviderWebhook(in *acme.ACMEIssuerDNS01ProviderWebhook, out *ACMEIssuerDNS01ProviderWebhook, s conversion.Scope) error { - out.GroupName = in.GroupName - out.SolverName = in.SolverName - out.Config = (*apiextensionsv1.JSON)(unsafe.Pointer(in.Config)) - return nil -} - -// Convert_acme_ACMEIssuerDNS01ProviderWebhook_To_v1alpha2_ACMEIssuerDNS01ProviderWebhook is an autogenerated conversion function. -func Convert_acme_ACMEIssuerDNS01ProviderWebhook_To_v1alpha2_ACMEIssuerDNS01ProviderWebhook(in *acme.ACMEIssuerDNS01ProviderWebhook, out *ACMEIssuerDNS01ProviderWebhook, s conversion.Scope) error { - return autoConvert_acme_ACMEIssuerDNS01ProviderWebhook_To_v1alpha2_ACMEIssuerDNS01ProviderWebhook(in, out, s) -} - -func autoConvert_v1alpha2_ACMEIssuerStatus_To_acme_ACMEIssuerStatus(in *ACMEIssuerStatus, out *acme.ACMEIssuerStatus, s conversion.Scope) error { - out.URI = in.URI - out.LastRegisteredEmail = in.LastRegisteredEmail - return nil -} - -// Convert_v1alpha2_ACMEIssuerStatus_To_acme_ACMEIssuerStatus is an autogenerated conversion function. -func Convert_v1alpha2_ACMEIssuerStatus_To_acme_ACMEIssuerStatus(in *ACMEIssuerStatus, out *acme.ACMEIssuerStatus, s conversion.Scope) error { - return autoConvert_v1alpha2_ACMEIssuerStatus_To_acme_ACMEIssuerStatus(in, out, s) -} - -func autoConvert_acme_ACMEIssuerStatus_To_v1alpha2_ACMEIssuerStatus(in *acme.ACMEIssuerStatus, out *ACMEIssuerStatus, s conversion.Scope) error { - out.URI = in.URI - out.LastRegisteredEmail = in.LastRegisteredEmail - return nil -} - -// Convert_acme_ACMEIssuerStatus_To_v1alpha2_ACMEIssuerStatus is an autogenerated conversion function. -func Convert_acme_ACMEIssuerStatus_To_v1alpha2_ACMEIssuerStatus(in *acme.ACMEIssuerStatus, out *ACMEIssuerStatus, s conversion.Scope) error { - return autoConvert_acme_ACMEIssuerStatus_To_v1alpha2_ACMEIssuerStatus(in, out, s) -} - -func autoConvert_v1alpha2_AzureManagedIdentity_To_acme_AzureManagedIdentity(in *AzureManagedIdentity, out *acme.AzureManagedIdentity, s conversion.Scope) error { - out.ClientID = in.ClientID - out.ResourceID = in.ResourceID - return nil -} - -// Convert_v1alpha2_AzureManagedIdentity_To_acme_AzureManagedIdentity is an autogenerated conversion function. -func Convert_v1alpha2_AzureManagedIdentity_To_acme_AzureManagedIdentity(in *AzureManagedIdentity, out *acme.AzureManagedIdentity, s conversion.Scope) error { - return autoConvert_v1alpha2_AzureManagedIdentity_To_acme_AzureManagedIdentity(in, out, s) -} - -func autoConvert_acme_AzureManagedIdentity_To_v1alpha2_AzureManagedIdentity(in *acme.AzureManagedIdentity, out *AzureManagedIdentity, s conversion.Scope) error { - out.ClientID = in.ClientID - out.ResourceID = in.ResourceID - return nil -} - -// Convert_acme_AzureManagedIdentity_To_v1alpha2_AzureManagedIdentity is an autogenerated conversion function. -func Convert_acme_AzureManagedIdentity_To_v1alpha2_AzureManagedIdentity(in *acme.AzureManagedIdentity, out *AzureManagedIdentity, s conversion.Scope) error { - return autoConvert_acme_AzureManagedIdentity_To_v1alpha2_AzureManagedIdentity(in, out, s) -} - -func autoConvert_v1alpha2_CertificateDNSNameSelector_To_acme_CertificateDNSNameSelector(in *CertificateDNSNameSelector, out *acme.CertificateDNSNameSelector, s conversion.Scope) error { - out.MatchLabels = *(*map[string]string)(unsafe.Pointer(&in.MatchLabels)) - out.DNSNames = *(*[]string)(unsafe.Pointer(&in.DNSNames)) - out.DNSZones = *(*[]string)(unsafe.Pointer(&in.DNSZones)) - return nil -} - -// Convert_v1alpha2_CertificateDNSNameSelector_To_acme_CertificateDNSNameSelector is an autogenerated conversion function. -func Convert_v1alpha2_CertificateDNSNameSelector_To_acme_CertificateDNSNameSelector(in *CertificateDNSNameSelector, out *acme.CertificateDNSNameSelector, s conversion.Scope) error { - return autoConvert_v1alpha2_CertificateDNSNameSelector_To_acme_CertificateDNSNameSelector(in, out, s) -} - -func autoConvert_acme_CertificateDNSNameSelector_To_v1alpha2_CertificateDNSNameSelector(in *acme.CertificateDNSNameSelector, out *CertificateDNSNameSelector, s conversion.Scope) error { - out.MatchLabels = *(*map[string]string)(unsafe.Pointer(&in.MatchLabels)) - out.DNSNames = *(*[]string)(unsafe.Pointer(&in.DNSNames)) - out.DNSZones = *(*[]string)(unsafe.Pointer(&in.DNSZones)) - return nil -} - -// Convert_acme_CertificateDNSNameSelector_To_v1alpha2_CertificateDNSNameSelector is an autogenerated conversion function. -func Convert_acme_CertificateDNSNameSelector_To_v1alpha2_CertificateDNSNameSelector(in *acme.CertificateDNSNameSelector, out *CertificateDNSNameSelector, s conversion.Scope) error { - return autoConvert_acme_CertificateDNSNameSelector_To_v1alpha2_CertificateDNSNameSelector(in, out, s) -} - -func autoConvert_v1alpha2_Challenge_To_acme_Challenge(in *Challenge, out *acme.Challenge, s conversion.Scope) error { - out.ObjectMeta = in.ObjectMeta - if err := Convert_v1alpha2_ChallengeSpec_To_acme_ChallengeSpec(&in.Spec, &out.Spec, s); err != nil { - return err - } - if err := Convert_v1alpha2_ChallengeStatus_To_acme_ChallengeStatus(&in.Status, &out.Status, s); err != nil { - return err - } - return nil -} - -// Convert_v1alpha2_Challenge_To_acme_Challenge is an autogenerated conversion function. -func Convert_v1alpha2_Challenge_To_acme_Challenge(in *Challenge, out *acme.Challenge, s conversion.Scope) error { - return autoConvert_v1alpha2_Challenge_To_acme_Challenge(in, out, s) -} - -func autoConvert_acme_Challenge_To_v1alpha2_Challenge(in *acme.Challenge, out *Challenge, s conversion.Scope) error { - out.ObjectMeta = in.ObjectMeta - if err := Convert_acme_ChallengeSpec_To_v1alpha2_ChallengeSpec(&in.Spec, &out.Spec, s); err != nil { - return err - } - if err := Convert_acme_ChallengeStatus_To_v1alpha2_ChallengeStatus(&in.Status, &out.Status, s); err != nil { - return err - } - return nil -} - -// Convert_acme_Challenge_To_v1alpha2_Challenge is an autogenerated conversion function. -func Convert_acme_Challenge_To_v1alpha2_Challenge(in *acme.Challenge, out *Challenge, s conversion.Scope) error { - return autoConvert_acme_Challenge_To_v1alpha2_Challenge(in, out, s) -} - -func autoConvert_v1alpha2_ChallengeList_To_acme_ChallengeList(in *ChallengeList, out *acme.ChallengeList, s conversion.Scope) error { - out.ListMeta = in.ListMeta - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]acme.Challenge, len(*in)) - for i := range *in { - if err := Convert_v1alpha2_Challenge_To_acme_Challenge(&(*in)[i], &(*out)[i], s); err != nil { - return err - } - } - } else { - out.Items = nil - } - return nil -} - -// Convert_v1alpha2_ChallengeList_To_acme_ChallengeList is an autogenerated conversion function. -func Convert_v1alpha2_ChallengeList_To_acme_ChallengeList(in *ChallengeList, out *acme.ChallengeList, s conversion.Scope) error { - return autoConvert_v1alpha2_ChallengeList_To_acme_ChallengeList(in, out, s) -} - -func autoConvert_acme_ChallengeList_To_v1alpha2_ChallengeList(in *acme.ChallengeList, out *ChallengeList, s conversion.Scope) error { - out.ListMeta = in.ListMeta - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]Challenge, len(*in)) - for i := range *in { - if err := Convert_acme_Challenge_To_v1alpha2_Challenge(&(*in)[i], &(*out)[i], s); err != nil { - return err - } - } - } else { - out.Items = nil - } - return nil -} - -// Convert_acme_ChallengeList_To_v1alpha2_ChallengeList is an autogenerated conversion function. -func Convert_acme_ChallengeList_To_v1alpha2_ChallengeList(in *acme.ChallengeList, out *ChallengeList, s conversion.Scope) error { - return autoConvert_acme_ChallengeList_To_v1alpha2_ChallengeList(in, out, s) -} - -func autoConvert_v1alpha2_ChallengeSpec_To_acme_ChallengeSpec(in *ChallengeSpec, out *acme.ChallengeSpec, s conversion.Scope) error { - out.URL = in.URL - // WARNING: in.AuthzURL requires manual conversion: does not exist in peer-type - out.DNSName = in.DNSName - out.Wildcard = in.Wildcard - out.Type = acme.ACMEChallengeType(in.Type) - out.Token = in.Token - out.Key = in.Key - if err := Convert_v1alpha2_ACMEChallengeSolver_To_acme_ACMEChallengeSolver(&in.Solver, &out.Solver, s); err != nil { - return err - } - if err := metav1.Convert_v1_ObjectReference_To_meta_ObjectReference(&in.IssuerRef, &out.IssuerRef, s); err != nil { - return err - } - return nil -} - -func autoConvert_acme_ChallengeSpec_To_v1alpha2_ChallengeSpec(in *acme.ChallengeSpec, out *ChallengeSpec, s conversion.Scope) error { - out.URL = in.URL - // WARNING: in.AuthorizationURL requires manual conversion: does not exist in peer-type - out.DNSName = in.DNSName - out.Wildcard = in.Wildcard - out.Type = ACMEChallengeType(in.Type) - out.Token = in.Token - out.Key = in.Key - if err := Convert_acme_ACMEChallengeSolver_To_v1alpha2_ACMEChallengeSolver(&in.Solver, &out.Solver, s); err != nil { - return err - } - if err := metav1.Convert_meta_ObjectReference_To_v1_ObjectReference(&in.IssuerRef, &out.IssuerRef, s); err != nil { - return err - } - return nil -} - -func autoConvert_v1alpha2_ChallengeStatus_To_acme_ChallengeStatus(in *ChallengeStatus, out *acme.ChallengeStatus, s conversion.Scope) error { - out.Processing = in.Processing - out.Presented = in.Presented - out.Reason = in.Reason - out.State = acme.State(in.State) - return nil -} - -// Convert_v1alpha2_ChallengeStatus_To_acme_ChallengeStatus is an autogenerated conversion function. -func Convert_v1alpha2_ChallengeStatus_To_acme_ChallengeStatus(in *ChallengeStatus, out *acme.ChallengeStatus, s conversion.Scope) error { - return autoConvert_v1alpha2_ChallengeStatus_To_acme_ChallengeStatus(in, out, s) -} - -func autoConvert_acme_ChallengeStatus_To_v1alpha2_ChallengeStatus(in *acme.ChallengeStatus, out *ChallengeStatus, s conversion.Scope) error { - out.Processing = in.Processing - out.Presented = in.Presented - out.Reason = in.Reason - out.State = State(in.State) - return nil -} - -// Convert_acme_ChallengeStatus_To_v1alpha2_ChallengeStatus is an autogenerated conversion function. -func Convert_acme_ChallengeStatus_To_v1alpha2_ChallengeStatus(in *acme.ChallengeStatus, out *ChallengeStatus, s conversion.Scope) error { - return autoConvert_acme_ChallengeStatus_To_v1alpha2_ChallengeStatus(in, out, s) -} - -func autoConvert_v1alpha2_Order_To_acme_Order(in *Order, out *acme.Order, s conversion.Scope) error { - out.ObjectMeta = in.ObjectMeta - if err := Convert_v1alpha2_OrderSpec_To_acme_OrderSpec(&in.Spec, &out.Spec, s); err != nil { - return err - } - if err := Convert_v1alpha2_OrderStatus_To_acme_OrderStatus(&in.Status, &out.Status, s); err != nil { - return err - } - return nil -} - -// Convert_v1alpha2_Order_To_acme_Order is an autogenerated conversion function. -func Convert_v1alpha2_Order_To_acme_Order(in *Order, out *acme.Order, s conversion.Scope) error { - return autoConvert_v1alpha2_Order_To_acme_Order(in, out, s) -} - -func autoConvert_acme_Order_To_v1alpha2_Order(in *acme.Order, out *Order, s conversion.Scope) error { - out.ObjectMeta = in.ObjectMeta - if err := Convert_acme_OrderSpec_To_v1alpha2_OrderSpec(&in.Spec, &out.Spec, s); err != nil { - return err - } - if err := Convert_acme_OrderStatus_To_v1alpha2_OrderStatus(&in.Status, &out.Status, s); err != nil { - return err - } - return nil -} - -// Convert_acme_Order_To_v1alpha2_Order is an autogenerated conversion function. -func Convert_acme_Order_To_v1alpha2_Order(in *acme.Order, out *Order, s conversion.Scope) error { - return autoConvert_acme_Order_To_v1alpha2_Order(in, out, s) -} - -func autoConvert_v1alpha2_OrderList_To_acme_OrderList(in *OrderList, out *acme.OrderList, s conversion.Scope) error { - out.ListMeta = in.ListMeta - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]acme.Order, len(*in)) - for i := range *in { - if err := Convert_v1alpha2_Order_To_acme_Order(&(*in)[i], &(*out)[i], s); err != nil { - return err - } - } - } else { - out.Items = nil - } - return nil -} - -// Convert_v1alpha2_OrderList_To_acme_OrderList is an autogenerated conversion function. -func Convert_v1alpha2_OrderList_To_acme_OrderList(in *OrderList, out *acme.OrderList, s conversion.Scope) error { - return autoConvert_v1alpha2_OrderList_To_acme_OrderList(in, out, s) -} - -func autoConvert_acme_OrderList_To_v1alpha2_OrderList(in *acme.OrderList, out *OrderList, s conversion.Scope) error { - out.ListMeta = in.ListMeta - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]Order, len(*in)) - for i := range *in { - if err := Convert_acme_Order_To_v1alpha2_Order(&(*in)[i], &(*out)[i], s); err != nil { - return err - } - } - } else { - out.Items = nil - } - return nil -} - -// Convert_acme_OrderList_To_v1alpha2_OrderList is an autogenerated conversion function. -func Convert_acme_OrderList_To_v1alpha2_OrderList(in *acme.OrderList, out *OrderList, s conversion.Scope) error { - return autoConvert_acme_OrderList_To_v1alpha2_OrderList(in, out, s) -} - -func autoConvert_v1alpha2_OrderSpec_To_acme_OrderSpec(in *OrderSpec, out *acme.OrderSpec, s conversion.Scope) error { - // WARNING: in.CSR requires manual conversion: does not exist in peer-type - if err := metav1.Convert_v1_ObjectReference_To_meta_ObjectReference(&in.IssuerRef, &out.IssuerRef, s); err != nil { - return err - } - out.CommonName = in.CommonName - out.DNSNames = *(*[]string)(unsafe.Pointer(&in.DNSNames)) - out.IPAddresses = *(*[]string)(unsafe.Pointer(&in.IPAddresses)) - out.Duration = (*pkgapismetav1.Duration)(unsafe.Pointer(in.Duration)) - return nil -} - -func autoConvert_acme_OrderSpec_To_v1alpha2_OrderSpec(in *acme.OrderSpec, out *OrderSpec, s conversion.Scope) error { - // WARNING: in.Request requires manual conversion: does not exist in peer-type - if err := metav1.Convert_meta_ObjectReference_To_v1_ObjectReference(&in.IssuerRef, &out.IssuerRef, s); err != nil { - return err - } - out.CommonName = in.CommonName - out.DNSNames = *(*[]string)(unsafe.Pointer(&in.DNSNames)) - out.IPAddresses = *(*[]string)(unsafe.Pointer(&in.IPAddresses)) - out.Duration = (*pkgapismetav1.Duration)(unsafe.Pointer(in.Duration)) - return nil -} - -func autoConvert_v1alpha2_OrderStatus_To_acme_OrderStatus(in *OrderStatus, out *acme.OrderStatus, s conversion.Scope) error { - out.URL = in.URL - out.FinalizeURL = in.FinalizeURL - out.Authorizations = *(*[]acme.ACMEAuthorization)(unsafe.Pointer(&in.Authorizations)) - out.Certificate = *(*[]byte)(unsafe.Pointer(&in.Certificate)) - out.State = acme.State(in.State) - out.Reason = in.Reason - out.FailureTime = (*pkgapismetav1.Time)(unsafe.Pointer(in.FailureTime)) - return nil -} - -// Convert_v1alpha2_OrderStatus_To_acme_OrderStatus is an autogenerated conversion function. -func Convert_v1alpha2_OrderStatus_To_acme_OrderStatus(in *OrderStatus, out *acme.OrderStatus, s conversion.Scope) error { - return autoConvert_v1alpha2_OrderStatus_To_acme_OrderStatus(in, out, s) -} - -func autoConvert_acme_OrderStatus_To_v1alpha2_OrderStatus(in *acme.OrderStatus, out *OrderStatus, s conversion.Scope) error { - out.URL = in.URL - out.FinalizeURL = in.FinalizeURL - out.Certificate = *(*[]byte)(unsafe.Pointer(&in.Certificate)) - out.State = State(in.State) - out.Reason = in.Reason - out.Authorizations = *(*[]ACMEAuthorization)(unsafe.Pointer(&in.Authorizations)) - out.FailureTime = (*pkgapismetav1.Time)(unsafe.Pointer(in.FailureTime)) - return nil -} - -// Convert_acme_OrderStatus_To_v1alpha2_OrderStatus is an autogenerated conversion function. -func Convert_acme_OrderStatus_To_v1alpha2_OrderStatus(in *acme.OrderStatus, out *OrderStatus, s conversion.Scope) error { - return autoConvert_acme_OrderStatus_To_v1alpha2_OrderStatus(in, out, s) -} diff --git a/internal/apis/acme/v1alpha2/zz_generated.deepcopy.go b/internal/apis/acme/v1alpha2/zz_generated.deepcopy.go deleted file mode 100644 index 38f02321e64..00000000000 --- a/internal/apis/acme/v1alpha2/zz_generated.deepcopy.go +++ /dev/null @@ -1,904 +0,0 @@ -//go:build !ignore_autogenerated -// +build !ignore_autogenerated - -/* -Copyright 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. -*/ - -// Code generated by deepcopy-gen. DO NOT EDIT. - -package v1alpha2 - -import ( - metav1 "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" - v1 "k8s.io/api/core/v1" - apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" - apismetav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - runtime "k8s.io/apimachinery/pkg/runtime" - apisv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" -) - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ACMEAuthorization) DeepCopyInto(out *ACMEAuthorization) { - *out = *in - if in.Wildcard != nil { - in, out := &in.Wildcard, &out.Wildcard - *out = new(bool) - **out = **in - } - if in.Challenges != nil { - in, out := &in.Challenges, &out.Challenges - *out = make([]ACMEChallenge, len(*in)) - copy(*out, *in) - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ACMEAuthorization. -func (in *ACMEAuthorization) DeepCopy() *ACMEAuthorization { - if in == nil { - return nil - } - out := new(ACMEAuthorization) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ACMEChallenge) DeepCopyInto(out *ACMEChallenge) { - *out = *in - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ACMEChallenge. -func (in *ACMEChallenge) DeepCopy() *ACMEChallenge { - if in == nil { - return nil - } - out := new(ACMEChallenge) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ACMEChallengeSolver) DeepCopyInto(out *ACMEChallengeSolver) { - *out = *in - if in.Selector != nil { - in, out := &in.Selector, &out.Selector - *out = new(CertificateDNSNameSelector) - (*in).DeepCopyInto(*out) - } - if in.HTTP01 != nil { - in, out := &in.HTTP01, &out.HTTP01 - *out = new(ACMEChallengeSolverHTTP01) - (*in).DeepCopyInto(*out) - } - if in.DNS01 != nil { - in, out := &in.DNS01, &out.DNS01 - *out = new(ACMEChallengeSolverDNS01) - (*in).DeepCopyInto(*out) - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ACMEChallengeSolver. -func (in *ACMEChallengeSolver) DeepCopy() *ACMEChallengeSolver { - if in == nil { - return nil - } - out := new(ACMEChallengeSolver) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ACMEChallengeSolverDNS01) DeepCopyInto(out *ACMEChallengeSolverDNS01) { - *out = *in - if in.Akamai != nil { - in, out := &in.Akamai, &out.Akamai - *out = new(ACMEIssuerDNS01ProviderAkamai) - **out = **in - } - if in.CloudDNS != nil { - in, out := &in.CloudDNS, &out.CloudDNS - *out = new(ACMEIssuerDNS01ProviderCloudDNS) - (*in).DeepCopyInto(*out) - } - if in.Cloudflare != nil { - in, out := &in.Cloudflare, &out.Cloudflare - *out = new(ACMEIssuerDNS01ProviderCloudflare) - (*in).DeepCopyInto(*out) - } - if in.Route53 != nil { - in, out := &in.Route53, &out.Route53 - *out = new(ACMEIssuerDNS01ProviderRoute53) - (*in).DeepCopyInto(*out) - } - if in.AzureDNS != nil { - in, out := &in.AzureDNS, &out.AzureDNS - *out = new(ACMEIssuerDNS01ProviderAzureDNS) - (*in).DeepCopyInto(*out) - } - if in.DigitalOcean != nil { - in, out := &in.DigitalOcean, &out.DigitalOcean - *out = new(ACMEIssuerDNS01ProviderDigitalOcean) - **out = **in - } - if in.AcmeDNS != nil { - in, out := &in.AcmeDNS, &out.AcmeDNS - *out = new(ACMEIssuerDNS01ProviderAcmeDNS) - **out = **in - } - if in.RFC2136 != nil { - in, out := &in.RFC2136, &out.RFC2136 - *out = new(ACMEIssuerDNS01ProviderRFC2136) - **out = **in - } - if in.Webhook != nil { - in, out := &in.Webhook, &out.Webhook - *out = new(ACMEIssuerDNS01ProviderWebhook) - (*in).DeepCopyInto(*out) - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ACMEChallengeSolverDNS01. -func (in *ACMEChallengeSolverDNS01) DeepCopy() *ACMEChallengeSolverDNS01 { - if in == nil { - return nil - } - out := new(ACMEChallengeSolverDNS01) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ACMEChallengeSolverHTTP01) DeepCopyInto(out *ACMEChallengeSolverHTTP01) { - *out = *in - if in.Ingress != nil { - in, out := &in.Ingress, &out.Ingress - *out = new(ACMEChallengeSolverHTTP01Ingress) - (*in).DeepCopyInto(*out) - } - if in.GatewayHTTPRoute != nil { - in, out := &in.GatewayHTTPRoute, &out.GatewayHTTPRoute - *out = new(ACMEChallengeSolverHTTP01GatewayHTTPRoute) - (*in).DeepCopyInto(*out) - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ACMEChallengeSolverHTTP01. -func (in *ACMEChallengeSolverHTTP01) DeepCopy() *ACMEChallengeSolverHTTP01 { - if in == nil { - return nil - } - out := new(ACMEChallengeSolverHTTP01) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ACMEChallengeSolverHTTP01GatewayHTTPRoute) DeepCopyInto(out *ACMEChallengeSolverHTTP01GatewayHTTPRoute) { - *out = *in - if in.Labels != nil { - in, out := &in.Labels, &out.Labels - *out = make(map[string]string, len(*in)) - for key, val := range *in { - (*out)[key] = val - } - } - if in.ParentRefs != nil { - in, out := &in.ParentRefs, &out.ParentRefs - *out = make([]apisv1alpha2.ParentReference, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ACMEChallengeSolverHTTP01GatewayHTTPRoute. -func (in *ACMEChallengeSolverHTTP01GatewayHTTPRoute) DeepCopy() *ACMEChallengeSolverHTTP01GatewayHTTPRoute { - if in == nil { - return nil - } - out := new(ACMEChallengeSolverHTTP01GatewayHTTPRoute) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ACMEChallengeSolverHTTP01Ingress) DeepCopyInto(out *ACMEChallengeSolverHTTP01Ingress) { - *out = *in - if in.Class != nil { - in, out := &in.Class, &out.Class - *out = new(string) - **out = **in - } - if in.PodTemplate != nil { - in, out := &in.PodTemplate, &out.PodTemplate - *out = new(ACMEChallengeSolverHTTP01IngressPodTemplate) - (*in).DeepCopyInto(*out) - } - if in.IngressTemplate != nil { - in, out := &in.IngressTemplate, &out.IngressTemplate - *out = new(ACMEChallengeSolverHTTP01IngressTemplate) - (*in).DeepCopyInto(*out) - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ACMEChallengeSolverHTTP01Ingress. -func (in *ACMEChallengeSolverHTTP01Ingress) DeepCopy() *ACMEChallengeSolverHTTP01Ingress { - if in == nil { - return nil - } - out := new(ACMEChallengeSolverHTTP01Ingress) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ACMEChallengeSolverHTTP01IngressObjectMeta) DeepCopyInto(out *ACMEChallengeSolverHTTP01IngressObjectMeta) { - *out = *in - if in.Annotations != nil { - in, out := &in.Annotations, &out.Annotations - *out = make(map[string]string, len(*in)) - for key, val := range *in { - (*out)[key] = val - } - } - if in.Labels != nil { - in, out := &in.Labels, &out.Labels - *out = make(map[string]string, len(*in)) - for key, val := range *in { - (*out)[key] = val - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ACMEChallengeSolverHTTP01IngressObjectMeta. -func (in *ACMEChallengeSolverHTTP01IngressObjectMeta) DeepCopy() *ACMEChallengeSolverHTTP01IngressObjectMeta { - if in == nil { - return nil - } - out := new(ACMEChallengeSolverHTTP01IngressObjectMeta) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ACMEChallengeSolverHTTP01IngressPodObjectMeta) DeepCopyInto(out *ACMEChallengeSolverHTTP01IngressPodObjectMeta) { - *out = *in - if in.Annotations != nil { - in, out := &in.Annotations, &out.Annotations - *out = make(map[string]string, len(*in)) - for key, val := range *in { - (*out)[key] = val - } - } - if in.Labels != nil { - in, out := &in.Labels, &out.Labels - *out = make(map[string]string, len(*in)) - for key, val := range *in { - (*out)[key] = val - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ACMEChallengeSolverHTTP01IngressPodObjectMeta. -func (in *ACMEChallengeSolverHTTP01IngressPodObjectMeta) DeepCopy() *ACMEChallengeSolverHTTP01IngressPodObjectMeta { - if in == nil { - return nil - } - out := new(ACMEChallengeSolverHTTP01IngressPodObjectMeta) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ACMEChallengeSolverHTTP01IngressPodSpec) DeepCopyInto(out *ACMEChallengeSolverHTTP01IngressPodSpec) { - *out = *in - if in.NodeSelector != nil { - in, out := &in.NodeSelector, &out.NodeSelector - *out = make(map[string]string, len(*in)) - for key, val := range *in { - (*out)[key] = val - } - } - if in.Affinity != nil { - in, out := &in.Affinity, &out.Affinity - *out = new(v1.Affinity) - (*in).DeepCopyInto(*out) - } - if in.Tolerations != nil { - in, out := &in.Tolerations, &out.Tolerations - *out = make([]v1.Toleration, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ACMEChallengeSolverHTTP01IngressPodSpec. -func (in *ACMEChallengeSolverHTTP01IngressPodSpec) DeepCopy() *ACMEChallengeSolverHTTP01IngressPodSpec { - if in == nil { - return nil - } - out := new(ACMEChallengeSolverHTTP01IngressPodSpec) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ACMEChallengeSolverHTTP01IngressPodTemplate) DeepCopyInto(out *ACMEChallengeSolverHTTP01IngressPodTemplate) { - *out = *in - in.ACMEChallengeSolverHTTP01IngressPodObjectMeta.DeepCopyInto(&out.ACMEChallengeSolverHTTP01IngressPodObjectMeta) - in.Spec.DeepCopyInto(&out.Spec) - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ACMEChallengeSolverHTTP01IngressPodTemplate. -func (in *ACMEChallengeSolverHTTP01IngressPodTemplate) DeepCopy() *ACMEChallengeSolverHTTP01IngressPodTemplate { - if in == nil { - return nil - } - out := new(ACMEChallengeSolverHTTP01IngressPodTemplate) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ACMEChallengeSolverHTTP01IngressTemplate) DeepCopyInto(out *ACMEChallengeSolverHTTP01IngressTemplate) { - *out = *in - in.ACMEChallengeSolverHTTP01IngressObjectMeta.DeepCopyInto(&out.ACMEChallengeSolverHTTP01IngressObjectMeta) - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ACMEChallengeSolverHTTP01IngressTemplate. -func (in *ACMEChallengeSolverHTTP01IngressTemplate) DeepCopy() *ACMEChallengeSolverHTTP01IngressTemplate { - if in == nil { - return nil - } - out := new(ACMEChallengeSolverHTTP01IngressTemplate) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ACMEExternalAccountBinding) DeepCopyInto(out *ACMEExternalAccountBinding) { - *out = *in - out.Key = in.Key - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ACMEExternalAccountBinding. -func (in *ACMEExternalAccountBinding) DeepCopy() *ACMEExternalAccountBinding { - if in == nil { - return nil - } - out := new(ACMEExternalAccountBinding) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ACMEIssuer) DeepCopyInto(out *ACMEIssuer) { - *out = *in - if in.ExternalAccountBinding != nil { - in, out := &in.ExternalAccountBinding, &out.ExternalAccountBinding - *out = new(ACMEExternalAccountBinding) - **out = **in - } - out.PrivateKey = in.PrivateKey - if in.Solvers != nil { - in, out := &in.Solvers, &out.Solvers - *out = make([]ACMEChallengeSolver, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ACMEIssuer. -func (in *ACMEIssuer) DeepCopy() *ACMEIssuer { - if in == nil { - return nil - } - out := new(ACMEIssuer) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ACMEIssuerDNS01ProviderAcmeDNS) DeepCopyInto(out *ACMEIssuerDNS01ProviderAcmeDNS) { - *out = *in - out.AccountSecret = in.AccountSecret - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ACMEIssuerDNS01ProviderAcmeDNS. -func (in *ACMEIssuerDNS01ProviderAcmeDNS) DeepCopy() *ACMEIssuerDNS01ProviderAcmeDNS { - if in == nil { - return nil - } - out := new(ACMEIssuerDNS01ProviderAcmeDNS) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ACMEIssuerDNS01ProviderAkamai) DeepCopyInto(out *ACMEIssuerDNS01ProviderAkamai) { - *out = *in - out.ClientToken = in.ClientToken - out.ClientSecret = in.ClientSecret - out.AccessToken = in.AccessToken - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ACMEIssuerDNS01ProviderAkamai. -func (in *ACMEIssuerDNS01ProviderAkamai) DeepCopy() *ACMEIssuerDNS01ProviderAkamai { - if in == nil { - return nil - } - out := new(ACMEIssuerDNS01ProviderAkamai) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ACMEIssuerDNS01ProviderAzureDNS) DeepCopyInto(out *ACMEIssuerDNS01ProviderAzureDNS) { - *out = *in - if in.ClientSecret != nil { - in, out := &in.ClientSecret, &out.ClientSecret - *out = new(metav1.SecretKeySelector) - **out = **in - } - if in.ManagedIdentity != nil { - in, out := &in.ManagedIdentity, &out.ManagedIdentity - *out = new(AzureManagedIdentity) - **out = **in - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ACMEIssuerDNS01ProviderAzureDNS. -func (in *ACMEIssuerDNS01ProviderAzureDNS) DeepCopy() *ACMEIssuerDNS01ProviderAzureDNS { - if in == nil { - return nil - } - out := new(ACMEIssuerDNS01ProviderAzureDNS) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ACMEIssuerDNS01ProviderCloudDNS) DeepCopyInto(out *ACMEIssuerDNS01ProviderCloudDNS) { - *out = *in - if in.ServiceAccount != nil { - in, out := &in.ServiceAccount, &out.ServiceAccount - *out = new(metav1.SecretKeySelector) - **out = **in - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ACMEIssuerDNS01ProviderCloudDNS. -func (in *ACMEIssuerDNS01ProviderCloudDNS) DeepCopy() *ACMEIssuerDNS01ProviderCloudDNS { - if in == nil { - return nil - } - out := new(ACMEIssuerDNS01ProviderCloudDNS) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ACMEIssuerDNS01ProviderCloudflare) DeepCopyInto(out *ACMEIssuerDNS01ProviderCloudflare) { - *out = *in - if in.APIKey != nil { - in, out := &in.APIKey, &out.APIKey - *out = new(metav1.SecretKeySelector) - **out = **in - } - if in.APIToken != nil { - in, out := &in.APIToken, &out.APIToken - *out = new(metav1.SecretKeySelector) - **out = **in - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ACMEIssuerDNS01ProviderCloudflare. -func (in *ACMEIssuerDNS01ProviderCloudflare) DeepCopy() *ACMEIssuerDNS01ProviderCloudflare { - if in == nil { - return nil - } - out := new(ACMEIssuerDNS01ProviderCloudflare) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ACMEIssuerDNS01ProviderDigitalOcean) DeepCopyInto(out *ACMEIssuerDNS01ProviderDigitalOcean) { - *out = *in - out.Token = in.Token - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ACMEIssuerDNS01ProviderDigitalOcean. -func (in *ACMEIssuerDNS01ProviderDigitalOcean) DeepCopy() *ACMEIssuerDNS01ProviderDigitalOcean { - if in == nil { - return nil - } - out := new(ACMEIssuerDNS01ProviderDigitalOcean) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ACMEIssuerDNS01ProviderRFC2136) DeepCopyInto(out *ACMEIssuerDNS01ProviderRFC2136) { - *out = *in - out.TSIGSecret = in.TSIGSecret - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ACMEIssuerDNS01ProviderRFC2136. -func (in *ACMEIssuerDNS01ProviderRFC2136) DeepCopy() *ACMEIssuerDNS01ProviderRFC2136 { - if in == nil { - return nil - } - out := new(ACMEIssuerDNS01ProviderRFC2136) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ACMEIssuerDNS01ProviderRoute53) DeepCopyInto(out *ACMEIssuerDNS01ProviderRoute53) { - *out = *in - if in.SecretAccessKeyID != nil { - in, out := &in.SecretAccessKeyID, &out.SecretAccessKeyID - *out = new(metav1.SecretKeySelector) - **out = **in - } - out.SecretAccessKey = in.SecretAccessKey - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ACMEIssuerDNS01ProviderRoute53. -func (in *ACMEIssuerDNS01ProviderRoute53) DeepCopy() *ACMEIssuerDNS01ProviderRoute53 { - if in == nil { - return nil - } - out := new(ACMEIssuerDNS01ProviderRoute53) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ACMEIssuerDNS01ProviderWebhook) DeepCopyInto(out *ACMEIssuerDNS01ProviderWebhook) { - *out = *in - if in.Config != nil { - in, out := &in.Config, &out.Config - *out = new(apiextensionsv1.JSON) - (*in).DeepCopyInto(*out) - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ACMEIssuerDNS01ProviderWebhook. -func (in *ACMEIssuerDNS01ProviderWebhook) DeepCopy() *ACMEIssuerDNS01ProviderWebhook { - if in == nil { - return nil - } - out := new(ACMEIssuerDNS01ProviderWebhook) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ACMEIssuerStatus) DeepCopyInto(out *ACMEIssuerStatus) { - *out = *in - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ACMEIssuerStatus. -func (in *ACMEIssuerStatus) DeepCopy() *ACMEIssuerStatus { - if in == nil { - return nil - } - out := new(ACMEIssuerStatus) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *AzureManagedIdentity) DeepCopyInto(out *AzureManagedIdentity) { - *out = *in - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AzureManagedIdentity. -func (in *AzureManagedIdentity) DeepCopy() *AzureManagedIdentity { - if in == nil { - return nil - } - out := new(AzureManagedIdentity) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *CertificateDNSNameSelector) DeepCopyInto(out *CertificateDNSNameSelector) { - *out = *in - if in.MatchLabels != nil { - in, out := &in.MatchLabels, &out.MatchLabels - *out = make(map[string]string, len(*in)) - for key, val := range *in { - (*out)[key] = val - } - } - if in.DNSNames != nil { - in, out := &in.DNSNames, &out.DNSNames - *out = make([]string, len(*in)) - copy(*out, *in) - } - if in.DNSZones != nil { - in, out := &in.DNSZones, &out.DNSZones - *out = make([]string, len(*in)) - copy(*out, *in) - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CertificateDNSNameSelector. -func (in *CertificateDNSNameSelector) DeepCopy() *CertificateDNSNameSelector { - if in == nil { - return nil - } - out := new(CertificateDNSNameSelector) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *Challenge) DeepCopyInto(out *Challenge) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - in.Spec.DeepCopyInto(&out.Spec) - out.Status = in.Status - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Challenge. -func (in *Challenge) DeepCopy() *Challenge { - if in == nil { - return nil - } - out := new(Challenge) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *Challenge) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ChallengeList) DeepCopyInto(out *ChallengeList) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ListMeta.DeepCopyInto(&out.ListMeta) - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]Challenge, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ChallengeList. -func (in *ChallengeList) DeepCopy() *ChallengeList { - if in == nil { - return nil - } - out := new(ChallengeList) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *ChallengeList) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ChallengeSpec) DeepCopyInto(out *ChallengeSpec) { - *out = *in - in.Solver.DeepCopyInto(&out.Solver) - out.IssuerRef = in.IssuerRef - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ChallengeSpec. -func (in *ChallengeSpec) DeepCopy() *ChallengeSpec { - if in == nil { - return nil - } - out := new(ChallengeSpec) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ChallengeStatus) DeepCopyInto(out *ChallengeStatus) { - *out = *in - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ChallengeStatus. -func (in *ChallengeStatus) DeepCopy() *ChallengeStatus { - if in == nil { - return nil - } - out := new(ChallengeStatus) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *Order) DeepCopyInto(out *Order) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - in.Spec.DeepCopyInto(&out.Spec) - in.Status.DeepCopyInto(&out.Status) - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Order. -func (in *Order) DeepCopy() *Order { - if in == nil { - return nil - } - out := new(Order) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *Order) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *OrderList) DeepCopyInto(out *OrderList) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ListMeta.DeepCopyInto(&out.ListMeta) - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]Order, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OrderList. -func (in *OrderList) DeepCopy() *OrderList { - if in == nil { - return nil - } - out := new(OrderList) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *OrderList) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *OrderSpec) DeepCopyInto(out *OrderSpec) { - *out = *in - if in.CSR != nil { - in, out := &in.CSR, &out.CSR - *out = make([]byte, len(*in)) - copy(*out, *in) - } - out.IssuerRef = in.IssuerRef - if in.DNSNames != nil { - in, out := &in.DNSNames, &out.DNSNames - *out = make([]string, len(*in)) - copy(*out, *in) - } - if in.IPAddresses != nil { - in, out := &in.IPAddresses, &out.IPAddresses - *out = make([]string, len(*in)) - copy(*out, *in) - } - if in.Duration != nil { - in, out := &in.Duration, &out.Duration - *out = new(apismetav1.Duration) - **out = **in - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OrderSpec. -func (in *OrderSpec) DeepCopy() *OrderSpec { - if in == nil { - return nil - } - out := new(OrderSpec) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *OrderStatus) DeepCopyInto(out *OrderStatus) { - *out = *in - if in.Authorizations != nil { - in, out := &in.Authorizations, &out.Authorizations - *out = make([]ACMEAuthorization, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - if in.Certificate != nil { - in, out := &in.Certificate, &out.Certificate - *out = make([]byte, len(*in)) - copy(*out, *in) - } - if in.FailureTime != nil { - in, out := &in.FailureTime, &out.FailureTime - *out = (*in).DeepCopy() - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OrderStatus. -func (in *OrderStatus) DeepCopy() *OrderStatus { - if in == nil { - return nil - } - out := new(OrderStatus) - in.DeepCopyInto(out) - return out -} diff --git a/internal/apis/acme/v1alpha2/zz_generated.defaults.go b/internal/apis/acme/v1alpha2/zz_generated.defaults.go deleted file mode 100644 index 10b31a62682..00000000000 --- a/internal/apis/acme/v1alpha2/zz_generated.defaults.go +++ /dev/null @@ -1,33 +0,0 @@ -//go:build !ignore_autogenerated -// +build !ignore_autogenerated - -/* -Copyright 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. -*/ - -// Code generated by defaulter-gen. DO NOT EDIT. - -package v1alpha2 - -import ( - runtime "k8s.io/apimachinery/pkg/runtime" -) - -// RegisterDefaults adds defaulters functions to the given scheme. -// Public to allow building arbitrary schemes. -// All generated defaulters are covering - they call all nested defaulters. -func RegisterDefaults(scheme *runtime.Scheme) error { - return nil -} diff --git a/internal/apis/acme/v1alpha3/conversion.go b/internal/apis/acme/v1alpha3/conversion.go deleted file mode 100644 index 0dda212b73e..00000000000 --- a/internal/apis/acme/v1alpha3/conversion.go +++ /dev/null @@ -1,95 +0,0 @@ -/* -Copyright 2020 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. -*/ - -package v1alpha3 - -import ( - "k8s.io/apimachinery/pkg/conversion" - - "github.com/cert-manager/cert-manager/internal/apis/acme" -) - -func Convert_v1alpha3_ChallengeSpec_To_acme_ChallengeSpec(in *ChallengeSpec, out *acme.ChallengeSpec, s conversion.Scope) error { - if err := autoConvert_v1alpha3_ChallengeSpec_To_acme_ChallengeSpec(in, out, s); err != nil { - return err - } - - out.AuthorizationURL = in.AuthzURL - - switch in.Type { - case ACMEChallengeTypeHTTP01: - out.Type = acme.ACMEChallengeTypeHTTP01 - case ACMEChallengeTypeDNS01: - out.Type = acme.ACMEChallengeTypeDNS01 - default: - // this case should never be hit due to validation - out.Type = acme.ACMEChallengeType(in.Type) - } - - return nil -} - -func Convert_acme_ChallengeSpec_To_v1alpha3_ChallengeSpec(in *acme.ChallengeSpec, out *ChallengeSpec, s conversion.Scope) error { - if err := autoConvert_acme_ChallengeSpec_To_v1alpha3_ChallengeSpec(in, out, s); err != nil { - return err - } - - out.AuthzURL = in.AuthorizationURL - - switch in.Type { - case acme.ACMEChallengeTypeHTTP01: - out.Type = ACMEChallengeTypeHTTP01 - case acme.ACMEChallengeTypeDNS01: - out.Type = ACMEChallengeTypeDNS01 - default: - // this case should never be hit due to validation - out.Type = ACMEChallengeType(in.Type) - } - - return nil -} - -func Convert_v1alpha3_OrderSpec_To_acme_OrderSpec(in *OrderSpec, out *acme.OrderSpec, s conversion.Scope) error { - if err := autoConvert_v1alpha3_OrderSpec_To_acme_OrderSpec(in, out, s); err != nil { - return err - } - - out.Request = in.CSR - - return nil -} - -func Convert_acme_OrderSpec_To_v1alpha3_OrderSpec(in *acme.OrderSpec, out *OrderSpec, s conversion.Scope) error { - if err := autoConvert_acme_OrderSpec_To_v1alpha3_OrderSpec(in, out, s); err != nil { - return err - } - - out.CSR = in.Request - - return nil -} - -// Convert_acme_ACMEIssuer_To_v1alpha3_ACMEIssuer is explicitly defined to avoid issues in conversion-gen -// when referencing types in other API groups. -func Convert_acme_ACMEIssuer_To_v1alpha3_ACMEIssuer(in *acme.ACMEIssuer, out *ACMEIssuer, s conversion.Scope) error { - return autoConvert_acme_ACMEIssuer_To_v1alpha3_ACMEIssuer(in, out, s) -} - -// Convert_v1alpha3_ACMEIssuer_To_acme_ACMEIssuer is explicitly defined to avoid issues in conversion-gen -// when referencing types in other API groups. -func Convert_v1alpha3_ACMEIssuer_To_acme_ACMEIssuer(in *ACMEIssuer, out *acme.ACMEIssuer, s conversion.Scope) error { - return autoConvert_v1alpha3_ACMEIssuer_To_acme_ACMEIssuer(in, out, s) -} diff --git a/internal/apis/acme/v1alpha3/defaults.go b/internal/apis/acme/v1alpha3/defaults.go deleted file mode 100644 index 23beb3dd257..00000000000 --- a/internal/apis/acme/v1alpha3/defaults.go +++ /dev/null @@ -1,25 +0,0 @@ -/* -Copyright 2020 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. -*/ - -package v1alpha3 - -import ( - "k8s.io/apimachinery/pkg/runtime" -) - -func addDefaultingFuncs(scheme *runtime.Scheme) error { - return RegisterDefaults(scheme) -} diff --git a/internal/apis/acme/v1alpha3/register.go b/internal/apis/acme/v1alpha3/register.go deleted file mode 100644 index e4ba55415cf..00000000000 --- a/internal/apis/acme/v1alpha3/register.go +++ /dev/null @@ -1,63 +0,0 @@ -/* -Copyright 2020 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. -*/ - -package v1alpha3 - -import ( - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/runtime/schema" - - "github.com/cert-manager/cert-manager/pkg/apis/acme" -) - -// SchemeGroupVersion is group version used to register these objects -var SchemeGroupVersion = schema.GroupVersion{Group: acme.GroupName, Version: "v1alpha3"} - -// Resource takes an unqualified resource and returns a Group qualified GroupResource -func Resource(resource string) schema.GroupResource { - return SchemeGroupVersion.WithResource(resource).GroupResource() -} - -var ( - SchemeBuilder runtime.SchemeBuilder - localSchemeBuilder = &SchemeBuilder - AddToScheme = localSchemeBuilder.AddToScheme -) - -func init() { - // We only register manually written functions here. The registration of the - // generated functions takes place in the generated files. The separation - // makes the code compile even when the generated files are missing. - localSchemeBuilder.Register(addDefaultingFuncs) - - // We only register manually written functions here. The registration of the - // generated functions takes place in the generated files. The separation - // makes the code compile even when the generated files are missing. - localSchemeBuilder.Register(addKnownTypes) -} - -// Adds the list of known types to api.Scheme. -func addKnownTypes(scheme *runtime.Scheme) error { - scheme.AddKnownTypes(SchemeGroupVersion, - &Order{}, - &OrderList{}, - &Challenge{}, - &ChallengeList{}, - ) - metav1.AddToGroupVersion(scheme, SchemeGroupVersion) - return nil -} diff --git a/internal/apis/acme/v1alpha3/types.go b/internal/apis/acme/v1alpha3/types.go deleted file mode 100644 index 11a671c7354..00000000000 --- a/internal/apis/acme/v1alpha3/types.go +++ /dev/null @@ -1,43 +0,0 @@ -/* -Copyright 2020 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. -*/ - -package v1alpha3 - -const ( - // If this annotation is specified on a Certificate or Order resource when - // using the HTTP01 solver type, the ingress.name field of the HTTP01 - // solver's configuration will be set to the value given here. - // This is especially useful for users of Ingress controllers that maintain - // a 1:1 mapping between endpoint IP and Ingress resource. - ACMECertificateHTTP01IngressNameOverride = "acme.cert-manager.io/http01-override-ingress-name" - - // If this annotation is specified on a Certificate or Order resource when - // using the HTTP01 solver type, the ingress.class field of the HTTP01 - // solver's configuration will be set to the value given here. - // This is especially useful for users deploying many different ingress - // classes into a single cluster that want to be able to re-use a single - // solver for each ingress class. - ACMECertificateHTTP01IngressClassOverride = "acme.cert-manager.io/http01-override-ingress-class" - - // IngressEditInPlaceAnnotation is used to toggle the use of ingressClass instead - // of ingress on the created Certificate resource - IngressEditInPlaceAnnotationKey = "acme.cert-manager.io/http01-edit-in-place" -) - -const ( - OrderKind = "Order" - ChallengeKind = "Challenge" -) diff --git a/internal/apis/acme/v1alpha3/types_challenge.go b/internal/apis/acme/v1alpha3/types_challenge.go deleted file mode 100644 index 63c1765d15b..00000000000 --- a/internal/apis/acme/v1alpha3/types_challenge.go +++ /dev/null @@ -1,145 +0,0 @@ -/* -Copyright 2020 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. -*/ - -package v1alpha3 - -import ( - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - - cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" -) - -// +genclient -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object - -// Challenge is a type to represent a Challenge request with an ACME server -// +k8s:openapi-gen=true -// +kubebuilder:printcolumn:name="State",type="string",JSONPath=".status.state" -// +kubebuilder:printcolumn:name="Domain",type="string",JSONPath=".spec.dnsName" -// +kubebuilder:printcolumn:name="Reason",type="string",JSONPath=".status.reason",description="",priority=1 -// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp",description="CreationTimestamp is a timestamp representing the server time when this object was created. It is not guaranteed to be set in happens-before order across separate operations. Clients may not set this value. It is represented in RFC3339 form and is in UTC." -// +kubebuilder:subresource:status -// +kubebuilder:resource:path=challenges -type Challenge struct { - metav1.TypeMeta `json:",inline"` - metav1.ObjectMeta `json:"metadata"` - - Spec ChallengeSpec `json:"spec,omitempty"` - Status ChallengeStatus `json:"status,omitempty"` -} - -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object - -// ChallengeList is a list of Challenges -type ChallengeList struct { - metav1.TypeMeta `json:",inline"` - metav1.ListMeta `json:"metadata"` - - Items []Challenge `json:"items"` -} - -type ChallengeSpec struct { - // URL is the URL of the ACME Challenge resource for this challenge. - // This can be used to lookup details about the status of this challenge. - URL string `json:"url"` - - // AuthzURL is the URL to the ACME Authorization resource that this - // challenge is a part of. - AuthzURL string `json:"authzURL"` - - // 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`. - DNSName string `json:"dnsName"` - - // Wildcard will be true if this challenge is for a wildcard identifier, - // for example '*.example.com'. - // +optional - Wildcard bool `json:"wildcard"` - - // Type is the type of ACME challenge this resource represents. - // One of "http-01" or "dns-01". - Type ACMEChallengeType `json:"type"` - - // Token is the ACME challenge token for this challenge. - // This is the raw value returned from the ACME server. - Token string `json:"token"` - - // Key is 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: - // `.`. - // For DNS01 challenges, this is the base64 encoded SHA256 sum of the - // `.` - // text that must be set as the TXT record content. - Key string `json:"key"` - - // Solver contains the domain solving configuration that should be used to - // solve this challenge resource. - Solver ACMEChallengeSolver `json:"solver"` - - // IssuerRef 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. - IssuerRef cmmeta.ObjectReference `json:"issuerRef"` -} - -// The type of ACME challenge. Only http-01 and dns-01 are supported. -// +kubebuilder:validation:Enum=http-01;dns-01 -type ACMEChallengeType string - -const ( - // ACMEChallengeTypeHTTP01 denotes a Challenge is of type http-01 - // More info: https://letsencrypt.org/docs/challenge-types/#http-01-challenge - ACMEChallengeTypeHTTP01 ACMEChallengeType = "http-01" - - // ACMEChallengeTypeDNS01 denotes a Challenge is of type dns-01 - // More info: https://letsencrypt.org/docs/challenge-types/#dns-01-challenge - ACMEChallengeTypeDNS01 ACMEChallengeType = "dns-01" -) - -type ChallengeStatus struct { - // Processing is 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. - // +optional - Processing bool `json:"processing"` - - // 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). - // +optional - Presented bool `json:"presented"` - - // Reason contains human readable information on why the Challenge is in the - // current state. - // +optional - Reason string `json:"reason,omitempty"` - - // State contains the current 'state' of the challenge. - // If not set, the state of the challenge is unknown. - // +optional - State State `json:"state,omitempty"` -} diff --git a/internal/apis/acme/v1alpha3/types_issuer.go b/internal/apis/acme/v1alpha3/types_issuer.go deleted file mode 100644 index 2735f336a3b..00000000000 --- a/internal/apis/acme/v1alpha3/types_issuer.go +++ /dev/null @@ -1,609 +0,0 @@ -/* -Copyright 2020 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. -*/ - -package v1alpha3 - -import ( - corev1 "k8s.io/api/core/v1" - apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" - gwapi "sigs.k8s.io/gateway-api/apis/v1alpha2" - - cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" -) - -// 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. -type ACMEIssuer struct { - // 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. - // +optional - Email string `json:"email,omitempty"` - - // 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. - Server string `json:"server"` - - // 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 - // +optional - // +kubebuilder:validation:MaxLength=64 - PreferredChain string `json:"preferredChain"` - - // Enables or disables validation of the ACME server TLS certificate. - // If true, requests to the ACME server will not have their TLS certificate - // validated (i.e. insecure connections will be allowed). - // Only enable this option in development environments. - // The cert-manager system installed roots will be used to verify connections - // to the ACME server if this is false. - // Defaults to false. - // +optional - SkipTLSVerify bool `json:"skipTLSVerify,omitempty"` - - // 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. - // +optional - ExternalAccountBinding *ACMEExternalAccountBinding `json:"externalAccountBinding,omitempty"` - - // 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. - PrivateKey cmmeta.SecretKeySelector `json:"privateKeySecretRef"` - - // 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/ - // +optional - Solvers []ACMEChallengeSolver `json:"solvers,omitempty"` - - // 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. - // +optional - DisableAccountKeyGeneration bool `json:"disableAccountKeyGeneration,omitempty"` - - // 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. - // +optional - EnableDurationFeature bool `json:"enableDurationFeature,omitempty"` -} - -// ACMEExternalAccountBinding is a reference to a CA external account of the ACME -// server. -type ACMEExternalAccountBinding struct { - // keyID is the ID of the CA key that the External Account is bound to. - KeyID string `json:"keyID"` - - // 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. - Key cmmeta.SecretKeySelector `json:"keySecretRef"` - - // 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. - // +optional - KeyAlgorithm HMACKeyAlgorithm `json:"keyAlgorithm,omitempty"` -} - -// HMACKeyAlgorithm is the name of a key algorithm used for HMAC encryption -// +kubebuilder:validation:Enum=HS256;HS384;HS512 -type HMACKeyAlgorithm string - -const ( - HS256 HMACKeyAlgorithm = "HS256" - HS384 HMACKeyAlgorithm = "HS384" - HS512 HMACKeyAlgorithm = "HS512" -) - -// Configures an issuer to solve challenges using the specified options. -// Only one of HTTP01 or DNS01 may be provided. -type ACMEChallengeSolver struct { - // 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. - // +optional - Selector *CertificateDNSNameSelector `json:"selector,omitempty"` - - // 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. - // +optional - HTTP01 *ACMEChallengeSolverHTTP01 `json:"http01,omitempty"` - - // Configures cert-manager to attempt to complete authorizations by - // performing the DNS01 challenge flow. - // +optional - DNS01 *ACMEChallengeSolverDNS01 `json:"dns01,omitempty"` -} - -// CertificateDomainSelector 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. -type CertificateDNSNameSelector struct { - // A label selector that is used to refine the set of certificate's that - // this challenge solver will apply to. - // +optional - MatchLabels map[string]string `json:"matchLabels,omitempty"` - - // 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. - // +optional - DNSNames []string `json:"dnsNames,omitempty"` - - // 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. - // +optional - DNSZones []string `json:"dnsZones,omitempty"` -} - -// 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. -type ACMEChallengeSolverHTTP01 struct { - // 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. - // +optional - Ingress *ACMEChallengeSolverHTTP01Ingress `json:"ingress,omitempty"` - - // 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. - // +optional - GatewayHTTPRoute *ACMEChallengeSolverHTTP01GatewayHTTPRoute `json:"gatewayHTTPRoute,omitempty"` -} - -type ACMEChallengeSolverHTTP01Ingress struct { - // Optional service type for Kubernetes solver service. Supported values - // are NodePort or ClusterIP. If unset, defaults to NodePort. - // +optional - ServiceType corev1.ServiceType `json:"serviceType,omitempty"` - - // 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. - // +optional - Class *string `json:"class,omitempty"` - - // 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. - // +optional - Name string `json:"name,omitempty"` - - // Optional pod template used to configure the ACME challenge solver pods - // used for HTTP01 challenges. - // +optional - PodTemplate *ACMEChallengeSolverHTTP01IngressPodTemplate `json:"podTemplate,omitempty"` - - // Optional ingress template used to configure the ACME challenge solver - // ingress used for HTTP01 challenges - // +optional - IngressTemplate *ACMEChallengeSolverHTTP01IngressTemplate `json:"ingressTemplate,omitempty"` -} - -type ACMEChallengeSolverHTTP01GatewayHTTPRoute struct { - // Optional service type for Kubernetes solver service. Supported values - // are NodePort or ClusterIP. If unset, defaults to NodePort. - // +optional - ServiceType corev1.ServiceType `json:"serviceType,omitempty"` - - // Custom labels that will be applied to HTTPRoutes created by cert-manager - // while solving HTTP-01 challenges. - // +optional - Labels map[string]string - - // 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/v1alpha2/api-types/httproute/#attaching-to-gateways - ParentRefs []gwapi.ParentReference -} - -type ACMEChallengeSolverHTTP01IngressPodTemplate struct { - // 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. - // +optional - ACMEChallengeSolverHTTP01IngressPodObjectMeta `json:"metadata"` - - // 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. - // +optional - Spec ACMEChallengeSolverHTTP01IngressPodSpec `json:"spec"` -} - -type ACMEChallengeSolverHTTP01IngressPodObjectMeta struct { - // Annotations that should be added to the create ACME HTTP01 solver pods. - // +optional - Annotations map[string]string `json:"annotations,omitempty"` - - // Labels that should be added to the created ACME HTTP01 solver pods. - // +optional - Labels map[string]string `json:"labels,omitempty"` -} - -type ACMEChallengeSolverHTTP01IngressPodSpec struct { - // 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/ - // +optional - NodeSelector map[string]string `json:"nodeSelector,omitempty"` - - // If specified, the pod's scheduling constraints - // +optional - Affinity *corev1.Affinity `json:"affinity,omitempty"` - - // If specified, the pod's tolerations. - // +optional - Tolerations []corev1.Toleration `json:"tolerations,omitempty"` - - // If specified, the pod's priorityClassName. - // +optional - PriorityClassName string `json:"priorityClassName,omitempty"` - - // If specified, the pod's service account - // +optional - ServiceAccountName string `json:"serviceAccountName,omitempty"` -} - -type ACMEChallengeSolverHTTP01IngressTemplate struct { - // 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. - // +optional - ACMEChallengeSolverHTTP01IngressObjectMeta `json:"metadata"` -} - -type ACMEChallengeSolverHTTP01IngressObjectMeta struct { - // Annotations that should be added to the created ACME HTTP01 solver ingress. - // +optional - Annotations map[string]string `json:"annotations,omitempty"` - - // Labels that should be added to the created ACME HTTP01 solver ingress. - // +optional - Labels map[string]string `json:"labels,omitempty"` -} - -// Used to configure a DNS01 challenge provider to be used when solving DNS01 -// challenges. -// Only one DNS provider may be configured per solver. -type ACMEChallengeSolverDNS01 struct { - // CNAMEStrategy configures how the DNS01 provider should handle CNAME - // records when found in DNS zones. - // +optional - CNAMEStrategy CNAMEStrategy `json:"cnameStrategy,omitempty"` - - // Use the Akamai DNS zone management API to manage DNS01 challenge records. - // +optional - Akamai *ACMEIssuerDNS01ProviderAkamai `json:"akamai,omitempty"` - - // Use the Google Cloud DNS API to manage DNS01 challenge records. - // +optional - CloudDNS *ACMEIssuerDNS01ProviderCloudDNS `json:"clouddns,omitempty"` - - // Use the Cloudflare API to manage DNS01 challenge records. - // +optional - Cloudflare *ACMEIssuerDNS01ProviderCloudflare `json:"cloudflare,omitempty"` - - // Use the AWS Route53 API to manage DNS01 challenge records. - // +optional - Route53 *ACMEIssuerDNS01ProviderRoute53 `json:"route53,omitempty"` - - // Use the Microsoft Azure DNS API to manage DNS01 challenge records. - // +optional - AzureDNS *ACMEIssuerDNS01ProviderAzureDNS `json:"azuredns,omitempty"` - - // Use the DigitalOcean DNS API to manage DNS01 challenge records. - // +optional - DigitalOcean *ACMEIssuerDNS01ProviderDigitalOcean `json:"digitalocean,omitempty"` - - // Use the 'ACME DNS' (https://github.com/joohoi/acme-dns) API to manage - // DNS01 challenge records. - // +optional - AcmeDNS *ACMEIssuerDNS01ProviderAcmeDNS `json:"acmedns,omitempty"` - - // Use RFC2136 ("Dynamic Updates in the Domain Name System") (https://datatracker.ietf.org/doc/rfc2136/) - // to manage DNS01 challenge records. - // +optional - RFC2136 *ACMEIssuerDNS01ProviderRFC2136 `json:"rfc2136,omitempty"` - - // Configure an external webhook based DNS01 challenge solver to manage - // DNS01 challenge records. - // +optional - Webhook *ACMEIssuerDNS01ProviderWebhook `json:"webhook,omitempty"` -} - -// 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). -// +kubebuilder:validation:Enum=None;Follow -type CNAMEStrategy string - -const ( - // NoneStrategy indicates that no CNAME resolution strategy should be used - // when determining which DNS zone to update during DNS01 challenges. - NoneStrategy = "None" - - // FollowStrategy will cause cert-manager to recurse through CNAMEs in - // order to determine which DNS zone to update during DNS01 challenges. - // This is useful if you do not want to grant cert-manager access to your - // root DNS zone, and instead delegate the _acme-challenge.example.com - // subdomain to some other, less privileged domain. - FollowStrategy = "Follow" -) - -// ACMEIssuerDNS01ProviderAkamai is a structure containing the DNS -// configuration for Akamai DNS—Zone Record Management API -type ACMEIssuerDNS01ProviderAkamai struct { - ServiceConsumerDomain string `json:"serviceConsumerDomain"` - ClientToken cmmeta.SecretKeySelector `json:"clientTokenSecretRef"` - ClientSecret cmmeta.SecretKeySelector `json:"clientSecretSecretRef"` - AccessToken cmmeta.SecretKeySelector `json:"accessTokenSecretRef"` -} - -// ACMEIssuerDNS01ProviderCloudDNS is a structure containing the DNS -// configuration for Google Cloud DNS -type ACMEIssuerDNS01ProviderCloudDNS struct { - // +optional - ServiceAccount *cmmeta.SecretKeySelector `json:"serviceAccountSecretRef,omitempty"` - Project string `json:"project"` - - // 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. - // +optional - HostedZoneName string `json:"hostedZoneName,omitempty"` -} - -// ACMEIssuerDNS01ProviderCloudflare is a structure containing the DNS -// configuration for Cloudflare. -// One of `apiKeySecretRef` or `apiTokenSecretRef` must be provided. -type ACMEIssuerDNS01ProviderCloudflare struct { - // Email of the account, only required when using API key based authentication. - // +optional - Email string `json:"email,omitempty"` - - // 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. - // +optional - APIKey *cmmeta.SecretKeySelector `json:"apiKeySecretRef,omitempty"` - - // API token used to authenticate with Cloudflare. - // +optional - APIToken *cmmeta.SecretKeySelector `json:"apiTokenSecretRef,omitempty"` -} - -// ACMEIssuerDNS01ProviderDigitalOcean is a structure containing the DNS -// configuration for DigitalOcean Domains -type ACMEIssuerDNS01ProviderDigitalOcean struct { - Token cmmeta.SecretKeySelector `json:"tokenSecretRef"` -} - -// ACMEIssuerDNS01ProviderRoute53 is a structure containing the Route 53 -// configuration for AWS -type ACMEIssuerDNS01ProviderRoute53 struct { - // The AccessKeyID is used for authentication. If not 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 - // +optional - AccessKeyID string `json:"accessKeyID,omitempty"` - - // If set, pull the AWS access key ID from a key within a kubernetes secret. - // see: https://docs.aws.amazon.com/sdk-for-go/v1/developer-guide/configuring-sdk.html#specifying-credentials - // +optional - SecretAccessKeyID *cmmeta.SecretKeySelector `json:"accessKeyIDSecretRef,omitempty"` - - // The SecretAccessKey is used for authentication. If not set we fall-back to using env vars, shared credentials file or AWS Instance metadata - // https://docs.aws.amazon.com/sdk-for-go/v1/developer-guide/configuring-sdk.html#specifying-credentials - // +optional - SecretAccessKey cmmeta.SecretKeySelector `json:"secretAccessKeySecretRef"` - - // 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 - // +optional - Role string `json:"role,omitempty"` - - // If set, the provider will manage only this zone in Route53 and will not do an lookup using the route53:ListHostedZonesByName api call. - // +optional - HostedZoneID string `json:"hostedZoneID,omitempty"` - - // Always set the region when using AccessKeyID and SecretAccessKey - Region string `json:"region"` -} - -// ACMEIssuerDNS01ProviderAzureDNS is a structure containing the -// configuration for Azure DNS -type ACMEIssuerDNS01ProviderAzureDNS struct { - // if both this and ClientSecret are left unset MSI will be used - // +optional - ClientID string `json:"clientID,omitempty"` - - // if both this and ClientID are left unset MSI will be used - // +optional - ClientSecret *cmmeta.SecretKeySelector `json:"clientSecretSecretRef,omitempty"` - - // ID of the Azure subscription - SubscriptionID string `json:"subscriptionID"` - - // when specifying ClientID and ClientSecret then this field is also needed - // +optional - TenantID string `json:"tenantID,omitempty"` - - // resource group the DNS zone is located in - ResourceGroupName string `json:"resourceGroupName"` - - // name of the DNS zone that should be used - // +optional - HostedZoneName string `json:"hostedZoneName,omitempty"` - - // name of the Azure environment (default AzurePublicCloud) - // +optional - Environment AzureDNSEnvironment `json:"environment,omitempty"` - - // managed identity configuration, can not be used at the same time as clientID, clientSecretSecretRef or tenantID - // +optional - ManagedIdentity *AzureManagedIdentity `json:"managedIdentity,omitempty"` -} - -type AzureManagedIdentity struct { - // client ID of the managed identity, can not be used at the same time as resourceID - // +optional - ClientID string `json:"clientID,omitempty"` - - // resource ID of the managed identity, can not be used at the same time as clientID - // +optional - ResourceID string `json:"resourceID,omitempty"` -} - -// +kubebuilder:validation:Enum=AzurePublicCloud;AzureChinaCloud;AzureGermanCloud;AzureUSGovernmentCloud -type AzureDNSEnvironment string - -const ( - AzurePublicCloud AzureDNSEnvironment = "AzurePublicCloud" - AzureChinaCloud AzureDNSEnvironment = "AzureChinaCloud" - AzureGermanCloud AzureDNSEnvironment = "AzureGermanCloud" - AzureUSGovernmentCloud AzureDNSEnvironment = "AzureUSGovernmentCloud" -) - -// ACMEIssuerDNS01ProviderAcmeDNS is a structure containing the -// configuration for ACME-DNS servers -type ACMEIssuerDNS01ProviderAcmeDNS struct { - Host string `json:"host"` - - AccountSecret cmmeta.SecretKeySelector `json:"accountSecretRef"` -} - -// ACMEIssuerDNS01ProviderRFC2136 is a structure containing the -// configuration for RFC2136 DNS -type ACMEIssuerDNS01ProviderRFC2136 struct { - // 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. - Nameserver string `json:"nameserver"` - - // The name of the secret containing the TSIG value. - // If ``tsigKeyName`` is defined, this field is required. - // +optional - TSIGSecret cmmeta.SecretKeySelector `json:"tsigSecretSecretRef,omitempty"` - - // The TSIG Key name configured in the DNS. - // If ``tsigSecretSecretRef`` is defined, this field is required. - // +optional - TSIGKeyName string `json:"tsigKeyName,omitempty"` - - // 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``. - // +optional - TSIGAlgorithm string `json:"tsigAlgorithm,omitempty"` -} - -// ACMEIssuerDNS01ProviderWebhook specifies configuration for a webhook DNS01 -// provider, including where to POST ChallengePayload resources. -type ACMEIssuerDNS01ProviderWebhook struct { - // 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. - GroupName string `json:"groupName"` - - // 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'. - SolverName string `json:"solverName"` - - // 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. - // +optional - Config *apiextensionsv1.JSON `json:"config,omitempty"` -} - -type ACMEIssuerStatus struct { - // URI is the unique account identifier, which can also be used to retrieve - // account details from the CA - // +optional - URI string `json:"uri,omitempty"` - - // LastRegisteredEmail is the email associated with the latest registered - // ACME account, in order to track changes made to registered account - // associated with the Issuer - // +optional - LastRegisteredEmail string `json:"lastRegisteredEmail,omitempty"` -} diff --git a/internal/apis/acme/v1alpha3/types_order.go b/internal/apis/acme/v1alpha3/types_order.go deleted file mode 100644 index 4f50ca46763..00000000000 --- a/internal/apis/acme/v1alpha3/types_order.go +++ /dev/null @@ -1,238 +0,0 @@ -/* -Copyright 2020 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. -*/ - -package v1alpha3 - -import ( - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - - cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" -) - -// +genclient -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object - -// Order is a type to represent an Order with an ACME server -// +k8s:openapi-gen=true -type Order struct { - metav1.TypeMeta `json:",inline"` - metav1.ObjectMeta `json:"metadata"` - - Spec OrderSpec `json:"spec,omitempty"` - Status OrderStatus `json:"status,omitempty"` -} - -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object - -// OrderList is a list of Orders -type OrderList struct { - metav1.TypeMeta `json:",inline"` - metav1.ListMeta `json:"metadata"` - - Items []Order `json:"items"` -} - -type OrderSpec struct { - // Certificate signing request bytes in DER encoding. - // This will be used when finalizing the order. - // This field must be set on the order. - CSR []byte `json:"csr"` - - // 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. - IssuerRef cmmeta.ObjectReference `json:"issuerRef"` - - // 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. - // +optional - CommonName string `json:"commonName,omitempty"` - - // 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. - //+optional - DNSNames []string `json:"dnsNames,omitempty"` - - // 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. - // +optional - IPAddresses []string `json:"ipAddresses,omitempty"` - - // Duration is the duration for the not after date for the requested certificate. - // this is set on order creation as pe the ACME spec. - // +optional - Duration *metav1.Duration `json:"duration,omitempty"` -} - -type OrderStatus struct { - // 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. - // +optional - URL string `json:"url,omitempty"` - - // FinalizeURL of the Order. - // This is used to obtain certificates for this order once it has been completed. - // +optional - FinalizeURL string `json:"finalizeURL,omitempty"` - - // 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. - // +optional - Authorizations []ACMEAuthorization `json:"authorizations,omitempty"` - - // 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. - // +optional - Certificate []byte `json:"certificate,omitempty"` - - // State contains the current state of this Order resource. - // States 'success' and 'expired' are 'final' - // +optional - State State `json:"state,omitempty"` - - // Reason optionally provides more information about a why the order is in - // the current state. - // +optional - Reason string `json:"reason,omitempty"` - - // FailureTime stores the time that this order failed. - // This is used to influence garbage collection and back-off. - // +optional - FailureTime *metav1.Time `json:"failureTime,omitempty"` -} - -// 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. -type ACMEAuthorization struct { - // URL is the URL of the Authorization that must be completed - URL string `json:"url"` - - // Identifier is the DNS name to be validated as part of this authorization - // +optional - Identifier string `json:"identifier,omitempty"` - - // 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'. - // +optional - Wildcard *bool `json:"wildcard,omitempty"` - - // 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. - // +optional - InitialState State `json:"initialState,omitempty"` - - // 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. - // +optional - Challenges []ACMEChallenge `json:"challenges,omitempty"` -} - -// 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. -type ACMEChallenge struct { - // URL is the URL of this challenge. It can be used to retrieve additional - // metadata about the Challenge from the ACME server. - URL string `json:"url"` - - // Token is the token that must be presented for this challenge. - // This is used to compute the 'key' that must also be presented. - Token string `json:"token"` - - // 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. - Type string `json:"type"` -} - -// 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. -// +kubebuilder:validation:Enum=valid;ready;pending;processing;invalid;expired;errored -type State string - -const ( - // Unknown is not a real state as part of the ACME spec. - // It is used to represent an unrecognised value. - Unknown State = "" - - // 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. - Valid State = "valid" - - // 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. - Ready State = "ready" - - // 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. - Pending State = "pending" - - // 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. - Processing State = "processing" - - // 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. - Invalid State = "invalid" - - // 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. - Expired State = "expired" - - // 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. - Errored State = "errored" -) diff --git a/internal/apis/acme/v1alpha3/zz_generated.conversion.go b/internal/apis/acme/v1alpha3/zz_generated.conversion.go deleted file mode 100644 index dbf2b0d3fc6..00000000000 --- a/internal/apis/acme/v1alpha3/zz_generated.conversion.go +++ /dev/null @@ -1,1609 +0,0 @@ -//go:build !ignore_autogenerated -// +build !ignore_autogenerated - -/* -Copyright 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. -*/ - -// Code generated by conversion-gen. DO NOT EDIT. - -package v1alpha3 - -import ( - unsafe "unsafe" - - acme "github.com/cert-manager/cert-manager/internal/apis/acme" - meta "github.com/cert-manager/cert-manager/internal/apis/meta" - metav1 "github.com/cert-manager/cert-manager/internal/apis/meta/v1" - apismetav1 "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" - v1 "k8s.io/api/core/v1" - apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" - pkgapismetav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - conversion "k8s.io/apimachinery/pkg/conversion" - runtime "k8s.io/apimachinery/pkg/runtime" - v1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" -) - -func init() { - localSchemeBuilder.Register(RegisterConversions) -} - -// RegisterConversions adds conversion functions to the given scheme. -// Public to allow building arbitrary schemes. -func RegisterConversions(s *runtime.Scheme) error { - if err := s.AddGeneratedConversionFunc((*ACMEAuthorization)(nil), (*acme.ACMEAuthorization)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1alpha3_ACMEAuthorization_To_acme_ACMEAuthorization(a.(*ACMEAuthorization), b.(*acme.ACMEAuthorization), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*acme.ACMEAuthorization)(nil), (*ACMEAuthorization)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_acme_ACMEAuthorization_To_v1alpha3_ACMEAuthorization(a.(*acme.ACMEAuthorization), b.(*ACMEAuthorization), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*ACMEChallenge)(nil), (*acme.ACMEChallenge)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1alpha3_ACMEChallenge_To_acme_ACMEChallenge(a.(*ACMEChallenge), b.(*acme.ACMEChallenge), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*acme.ACMEChallenge)(nil), (*ACMEChallenge)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_acme_ACMEChallenge_To_v1alpha3_ACMEChallenge(a.(*acme.ACMEChallenge), b.(*ACMEChallenge), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*ACMEChallengeSolver)(nil), (*acme.ACMEChallengeSolver)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1alpha3_ACMEChallengeSolver_To_acme_ACMEChallengeSolver(a.(*ACMEChallengeSolver), b.(*acme.ACMEChallengeSolver), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*acme.ACMEChallengeSolver)(nil), (*ACMEChallengeSolver)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_acme_ACMEChallengeSolver_To_v1alpha3_ACMEChallengeSolver(a.(*acme.ACMEChallengeSolver), b.(*ACMEChallengeSolver), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*ACMEChallengeSolverDNS01)(nil), (*acme.ACMEChallengeSolverDNS01)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1alpha3_ACMEChallengeSolverDNS01_To_acme_ACMEChallengeSolverDNS01(a.(*ACMEChallengeSolverDNS01), b.(*acme.ACMEChallengeSolverDNS01), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*acme.ACMEChallengeSolverDNS01)(nil), (*ACMEChallengeSolverDNS01)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_acme_ACMEChallengeSolverDNS01_To_v1alpha3_ACMEChallengeSolverDNS01(a.(*acme.ACMEChallengeSolverDNS01), b.(*ACMEChallengeSolverDNS01), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*ACMEChallengeSolverHTTP01)(nil), (*acme.ACMEChallengeSolverHTTP01)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1alpha3_ACMEChallengeSolverHTTP01_To_acme_ACMEChallengeSolverHTTP01(a.(*ACMEChallengeSolverHTTP01), b.(*acme.ACMEChallengeSolverHTTP01), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*acme.ACMEChallengeSolverHTTP01)(nil), (*ACMEChallengeSolverHTTP01)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_acme_ACMEChallengeSolverHTTP01_To_v1alpha3_ACMEChallengeSolverHTTP01(a.(*acme.ACMEChallengeSolverHTTP01), b.(*ACMEChallengeSolverHTTP01), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*ACMEChallengeSolverHTTP01GatewayHTTPRoute)(nil), (*acme.ACMEChallengeSolverHTTP01GatewayHTTPRoute)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1alpha3_ACMEChallengeSolverHTTP01GatewayHTTPRoute_To_acme_ACMEChallengeSolverHTTP01GatewayHTTPRoute(a.(*ACMEChallengeSolverHTTP01GatewayHTTPRoute), b.(*acme.ACMEChallengeSolverHTTP01GatewayHTTPRoute), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*acme.ACMEChallengeSolverHTTP01GatewayHTTPRoute)(nil), (*ACMEChallengeSolverHTTP01GatewayHTTPRoute)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_acme_ACMEChallengeSolverHTTP01GatewayHTTPRoute_To_v1alpha3_ACMEChallengeSolverHTTP01GatewayHTTPRoute(a.(*acme.ACMEChallengeSolverHTTP01GatewayHTTPRoute), b.(*ACMEChallengeSolverHTTP01GatewayHTTPRoute), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*ACMEChallengeSolverHTTP01Ingress)(nil), (*acme.ACMEChallengeSolverHTTP01Ingress)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1alpha3_ACMEChallengeSolverHTTP01Ingress_To_acme_ACMEChallengeSolverHTTP01Ingress(a.(*ACMEChallengeSolverHTTP01Ingress), b.(*acme.ACMEChallengeSolverHTTP01Ingress), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*acme.ACMEChallengeSolverHTTP01Ingress)(nil), (*ACMEChallengeSolverHTTP01Ingress)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_acme_ACMEChallengeSolverHTTP01Ingress_To_v1alpha3_ACMEChallengeSolverHTTP01Ingress(a.(*acme.ACMEChallengeSolverHTTP01Ingress), b.(*ACMEChallengeSolverHTTP01Ingress), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*ACMEChallengeSolverHTTP01IngressObjectMeta)(nil), (*acme.ACMEChallengeSolverHTTP01IngressObjectMeta)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1alpha3_ACMEChallengeSolverHTTP01IngressObjectMeta_To_acme_ACMEChallengeSolverHTTP01IngressObjectMeta(a.(*ACMEChallengeSolverHTTP01IngressObjectMeta), b.(*acme.ACMEChallengeSolverHTTP01IngressObjectMeta), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*acme.ACMEChallengeSolverHTTP01IngressObjectMeta)(nil), (*ACMEChallengeSolverHTTP01IngressObjectMeta)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_acme_ACMEChallengeSolverHTTP01IngressObjectMeta_To_v1alpha3_ACMEChallengeSolverHTTP01IngressObjectMeta(a.(*acme.ACMEChallengeSolverHTTP01IngressObjectMeta), b.(*ACMEChallengeSolverHTTP01IngressObjectMeta), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*ACMEChallengeSolverHTTP01IngressPodObjectMeta)(nil), (*acme.ACMEChallengeSolverHTTP01IngressPodObjectMeta)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1alpha3_ACMEChallengeSolverHTTP01IngressPodObjectMeta_To_acme_ACMEChallengeSolverHTTP01IngressPodObjectMeta(a.(*ACMEChallengeSolverHTTP01IngressPodObjectMeta), b.(*acme.ACMEChallengeSolverHTTP01IngressPodObjectMeta), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*acme.ACMEChallengeSolverHTTP01IngressPodObjectMeta)(nil), (*ACMEChallengeSolverHTTP01IngressPodObjectMeta)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_acme_ACMEChallengeSolverHTTP01IngressPodObjectMeta_To_v1alpha3_ACMEChallengeSolverHTTP01IngressPodObjectMeta(a.(*acme.ACMEChallengeSolverHTTP01IngressPodObjectMeta), b.(*ACMEChallengeSolverHTTP01IngressPodObjectMeta), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*ACMEChallengeSolverHTTP01IngressPodSpec)(nil), (*acme.ACMEChallengeSolverHTTP01IngressPodSpec)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1alpha3_ACMEChallengeSolverHTTP01IngressPodSpec_To_acme_ACMEChallengeSolverHTTP01IngressPodSpec(a.(*ACMEChallengeSolverHTTP01IngressPodSpec), b.(*acme.ACMEChallengeSolverHTTP01IngressPodSpec), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*acme.ACMEChallengeSolverHTTP01IngressPodSpec)(nil), (*ACMEChallengeSolverHTTP01IngressPodSpec)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_acme_ACMEChallengeSolverHTTP01IngressPodSpec_To_v1alpha3_ACMEChallengeSolverHTTP01IngressPodSpec(a.(*acme.ACMEChallengeSolverHTTP01IngressPodSpec), b.(*ACMEChallengeSolverHTTP01IngressPodSpec), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*ACMEChallengeSolverHTTP01IngressPodTemplate)(nil), (*acme.ACMEChallengeSolverHTTP01IngressPodTemplate)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1alpha3_ACMEChallengeSolverHTTP01IngressPodTemplate_To_acme_ACMEChallengeSolverHTTP01IngressPodTemplate(a.(*ACMEChallengeSolverHTTP01IngressPodTemplate), b.(*acme.ACMEChallengeSolverHTTP01IngressPodTemplate), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*acme.ACMEChallengeSolverHTTP01IngressPodTemplate)(nil), (*ACMEChallengeSolverHTTP01IngressPodTemplate)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_acme_ACMEChallengeSolverHTTP01IngressPodTemplate_To_v1alpha3_ACMEChallengeSolverHTTP01IngressPodTemplate(a.(*acme.ACMEChallengeSolverHTTP01IngressPodTemplate), b.(*ACMEChallengeSolverHTTP01IngressPodTemplate), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*ACMEChallengeSolverHTTP01IngressTemplate)(nil), (*acme.ACMEChallengeSolverHTTP01IngressTemplate)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1alpha3_ACMEChallengeSolverHTTP01IngressTemplate_To_acme_ACMEChallengeSolverHTTP01IngressTemplate(a.(*ACMEChallengeSolverHTTP01IngressTemplate), b.(*acme.ACMEChallengeSolverHTTP01IngressTemplate), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*acme.ACMEChallengeSolverHTTP01IngressTemplate)(nil), (*ACMEChallengeSolverHTTP01IngressTemplate)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_acme_ACMEChallengeSolverHTTP01IngressTemplate_To_v1alpha3_ACMEChallengeSolverHTTP01IngressTemplate(a.(*acme.ACMEChallengeSolverHTTP01IngressTemplate), b.(*ACMEChallengeSolverHTTP01IngressTemplate), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*ACMEExternalAccountBinding)(nil), (*acme.ACMEExternalAccountBinding)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1alpha3_ACMEExternalAccountBinding_To_acme_ACMEExternalAccountBinding(a.(*ACMEExternalAccountBinding), b.(*acme.ACMEExternalAccountBinding), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*acme.ACMEExternalAccountBinding)(nil), (*ACMEExternalAccountBinding)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_acme_ACMEExternalAccountBinding_To_v1alpha3_ACMEExternalAccountBinding(a.(*acme.ACMEExternalAccountBinding), b.(*ACMEExternalAccountBinding), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*ACMEIssuerDNS01ProviderAcmeDNS)(nil), (*acme.ACMEIssuerDNS01ProviderAcmeDNS)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1alpha3_ACMEIssuerDNS01ProviderAcmeDNS_To_acme_ACMEIssuerDNS01ProviderAcmeDNS(a.(*ACMEIssuerDNS01ProviderAcmeDNS), b.(*acme.ACMEIssuerDNS01ProviderAcmeDNS), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*acme.ACMEIssuerDNS01ProviderAcmeDNS)(nil), (*ACMEIssuerDNS01ProviderAcmeDNS)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_acme_ACMEIssuerDNS01ProviderAcmeDNS_To_v1alpha3_ACMEIssuerDNS01ProviderAcmeDNS(a.(*acme.ACMEIssuerDNS01ProviderAcmeDNS), b.(*ACMEIssuerDNS01ProviderAcmeDNS), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*ACMEIssuerDNS01ProviderAkamai)(nil), (*acme.ACMEIssuerDNS01ProviderAkamai)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1alpha3_ACMEIssuerDNS01ProviderAkamai_To_acme_ACMEIssuerDNS01ProviderAkamai(a.(*ACMEIssuerDNS01ProviderAkamai), b.(*acme.ACMEIssuerDNS01ProviderAkamai), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*acme.ACMEIssuerDNS01ProviderAkamai)(nil), (*ACMEIssuerDNS01ProviderAkamai)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_acme_ACMEIssuerDNS01ProviderAkamai_To_v1alpha3_ACMEIssuerDNS01ProviderAkamai(a.(*acme.ACMEIssuerDNS01ProviderAkamai), b.(*ACMEIssuerDNS01ProviderAkamai), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*ACMEIssuerDNS01ProviderAzureDNS)(nil), (*acme.ACMEIssuerDNS01ProviderAzureDNS)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1alpha3_ACMEIssuerDNS01ProviderAzureDNS_To_acme_ACMEIssuerDNS01ProviderAzureDNS(a.(*ACMEIssuerDNS01ProviderAzureDNS), b.(*acme.ACMEIssuerDNS01ProviderAzureDNS), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*acme.ACMEIssuerDNS01ProviderAzureDNS)(nil), (*ACMEIssuerDNS01ProviderAzureDNS)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_acme_ACMEIssuerDNS01ProviderAzureDNS_To_v1alpha3_ACMEIssuerDNS01ProviderAzureDNS(a.(*acme.ACMEIssuerDNS01ProviderAzureDNS), b.(*ACMEIssuerDNS01ProviderAzureDNS), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*ACMEIssuerDNS01ProviderCloudDNS)(nil), (*acme.ACMEIssuerDNS01ProviderCloudDNS)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1alpha3_ACMEIssuerDNS01ProviderCloudDNS_To_acme_ACMEIssuerDNS01ProviderCloudDNS(a.(*ACMEIssuerDNS01ProviderCloudDNS), b.(*acme.ACMEIssuerDNS01ProviderCloudDNS), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*acme.ACMEIssuerDNS01ProviderCloudDNS)(nil), (*ACMEIssuerDNS01ProviderCloudDNS)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_acme_ACMEIssuerDNS01ProviderCloudDNS_To_v1alpha3_ACMEIssuerDNS01ProviderCloudDNS(a.(*acme.ACMEIssuerDNS01ProviderCloudDNS), b.(*ACMEIssuerDNS01ProviderCloudDNS), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*ACMEIssuerDNS01ProviderCloudflare)(nil), (*acme.ACMEIssuerDNS01ProviderCloudflare)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1alpha3_ACMEIssuerDNS01ProviderCloudflare_To_acme_ACMEIssuerDNS01ProviderCloudflare(a.(*ACMEIssuerDNS01ProviderCloudflare), b.(*acme.ACMEIssuerDNS01ProviderCloudflare), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*acme.ACMEIssuerDNS01ProviderCloudflare)(nil), (*ACMEIssuerDNS01ProviderCloudflare)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_acme_ACMEIssuerDNS01ProviderCloudflare_To_v1alpha3_ACMEIssuerDNS01ProviderCloudflare(a.(*acme.ACMEIssuerDNS01ProviderCloudflare), b.(*ACMEIssuerDNS01ProviderCloudflare), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*ACMEIssuerDNS01ProviderDigitalOcean)(nil), (*acme.ACMEIssuerDNS01ProviderDigitalOcean)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1alpha3_ACMEIssuerDNS01ProviderDigitalOcean_To_acme_ACMEIssuerDNS01ProviderDigitalOcean(a.(*ACMEIssuerDNS01ProviderDigitalOcean), b.(*acme.ACMEIssuerDNS01ProviderDigitalOcean), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*acme.ACMEIssuerDNS01ProviderDigitalOcean)(nil), (*ACMEIssuerDNS01ProviderDigitalOcean)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_acme_ACMEIssuerDNS01ProviderDigitalOcean_To_v1alpha3_ACMEIssuerDNS01ProviderDigitalOcean(a.(*acme.ACMEIssuerDNS01ProviderDigitalOcean), b.(*ACMEIssuerDNS01ProviderDigitalOcean), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*ACMEIssuerDNS01ProviderRFC2136)(nil), (*acme.ACMEIssuerDNS01ProviderRFC2136)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1alpha3_ACMEIssuerDNS01ProviderRFC2136_To_acme_ACMEIssuerDNS01ProviderRFC2136(a.(*ACMEIssuerDNS01ProviderRFC2136), b.(*acme.ACMEIssuerDNS01ProviderRFC2136), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*acme.ACMEIssuerDNS01ProviderRFC2136)(nil), (*ACMEIssuerDNS01ProviderRFC2136)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_acme_ACMEIssuerDNS01ProviderRFC2136_To_v1alpha3_ACMEIssuerDNS01ProviderRFC2136(a.(*acme.ACMEIssuerDNS01ProviderRFC2136), b.(*ACMEIssuerDNS01ProviderRFC2136), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*ACMEIssuerDNS01ProviderRoute53)(nil), (*acme.ACMEIssuerDNS01ProviderRoute53)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1alpha3_ACMEIssuerDNS01ProviderRoute53_To_acme_ACMEIssuerDNS01ProviderRoute53(a.(*ACMEIssuerDNS01ProviderRoute53), b.(*acme.ACMEIssuerDNS01ProviderRoute53), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*acme.ACMEIssuerDNS01ProviderRoute53)(nil), (*ACMEIssuerDNS01ProviderRoute53)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_acme_ACMEIssuerDNS01ProviderRoute53_To_v1alpha3_ACMEIssuerDNS01ProviderRoute53(a.(*acme.ACMEIssuerDNS01ProviderRoute53), b.(*ACMEIssuerDNS01ProviderRoute53), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*ACMEIssuerDNS01ProviderWebhook)(nil), (*acme.ACMEIssuerDNS01ProviderWebhook)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1alpha3_ACMEIssuerDNS01ProviderWebhook_To_acme_ACMEIssuerDNS01ProviderWebhook(a.(*ACMEIssuerDNS01ProviderWebhook), b.(*acme.ACMEIssuerDNS01ProviderWebhook), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*acme.ACMEIssuerDNS01ProviderWebhook)(nil), (*ACMEIssuerDNS01ProviderWebhook)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_acme_ACMEIssuerDNS01ProviderWebhook_To_v1alpha3_ACMEIssuerDNS01ProviderWebhook(a.(*acme.ACMEIssuerDNS01ProviderWebhook), b.(*ACMEIssuerDNS01ProviderWebhook), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*ACMEIssuerStatus)(nil), (*acme.ACMEIssuerStatus)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1alpha3_ACMEIssuerStatus_To_acme_ACMEIssuerStatus(a.(*ACMEIssuerStatus), b.(*acme.ACMEIssuerStatus), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*acme.ACMEIssuerStatus)(nil), (*ACMEIssuerStatus)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_acme_ACMEIssuerStatus_To_v1alpha3_ACMEIssuerStatus(a.(*acme.ACMEIssuerStatus), b.(*ACMEIssuerStatus), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*AzureManagedIdentity)(nil), (*acme.AzureManagedIdentity)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1alpha3_AzureManagedIdentity_To_acme_AzureManagedIdentity(a.(*AzureManagedIdentity), b.(*acme.AzureManagedIdentity), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*acme.AzureManagedIdentity)(nil), (*AzureManagedIdentity)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_acme_AzureManagedIdentity_To_v1alpha3_AzureManagedIdentity(a.(*acme.AzureManagedIdentity), b.(*AzureManagedIdentity), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*CertificateDNSNameSelector)(nil), (*acme.CertificateDNSNameSelector)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1alpha3_CertificateDNSNameSelector_To_acme_CertificateDNSNameSelector(a.(*CertificateDNSNameSelector), b.(*acme.CertificateDNSNameSelector), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*acme.CertificateDNSNameSelector)(nil), (*CertificateDNSNameSelector)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_acme_CertificateDNSNameSelector_To_v1alpha3_CertificateDNSNameSelector(a.(*acme.CertificateDNSNameSelector), b.(*CertificateDNSNameSelector), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*Challenge)(nil), (*acme.Challenge)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1alpha3_Challenge_To_acme_Challenge(a.(*Challenge), b.(*acme.Challenge), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*acme.Challenge)(nil), (*Challenge)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_acme_Challenge_To_v1alpha3_Challenge(a.(*acme.Challenge), b.(*Challenge), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*ChallengeList)(nil), (*acme.ChallengeList)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1alpha3_ChallengeList_To_acme_ChallengeList(a.(*ChallengeList), b.(*acme.ChallengeList), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*acme.ChallengeList)(nil), (*ChallengeList)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_acme_ChallengeList_To_v1alpha3_ChallengeList(a.(*acme.ChallengeList), b.(*ChallengeList), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*ChallengeStatus)(nil), (*acme.ChallengeStatus)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1alpha3_ChallengeStatus_To_acme_ChallengeStatus(a.(*ChallengeStatus), b.(*acme.ChallengeStatus), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*acme.ChallengeStatus)(nil), (*ChallengeStatus)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_acme_ChallengeStatus_To_v1alpha3_ChallengeStatus(a.(*acme.ChallengeStatus), b.(*ChallengeStatus), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*Order)(nil), (*acme.Order)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1alpha3_Order_To_acme_Order(a.(*Order), b.(*acme.Order), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*acme.Order)(nil), (*Order)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_acme_Order_To_v1alpha3_Order(a.(*acme.Order), b.(*Order), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*OrderList)(nil), (*acme.OrderList)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1alpha3_OrderList_To_acme_OrderList(a.(*OrderList), b.(*acme.OrderList), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*acme.OrderList)(nil), (*OrderList)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_acme_OrderList_To_v1alpha3_OrderList(a.(*acme.OrderList), b.(*OrderList), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*OrderStatus)(nil), (*acme.OrderStatus)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1alpha3_OrderStatus_To_acme_OrderStatus(a.(*OrderStatus), b.(*acme.OrderStatus), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*acme.OrderStatus)(nil), (*OrderStatus)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_acme_OrderStatus_To_v1alpha3_OrderStatus(a.(*acme.OrderStatus), b.(*OrderStatus), scope) - }); err != nil { - return err - } - if err := s.AddConversionFunc((*acme.ACMEIssuer)(nil), (*ACMEIssuer)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_acme_ACMEIssuer_To_v1alpha3_ACMEIssuer(a.(*acme.ACMEIssuer), b.(*ACMEIssuer), scope) - }); err != nil { - return err - } - if err := s.AddConversionFunc((*acme.ChallengeSpec)(nil), (*ChallengeSpec)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_acme_ChallengeSpec_To_v1alpha3_ChallengeSpec(a.(*acme.ChallengeSpec), b.(*ChallengeSpec), scope) - }); err != nil { - return err - } - if err := s.AddConversionFunc((*acme.OrderSpec)(nil), (*OrderSpec)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_acme_OrderSpec_To_v1alpha3_OrderSpec(a.(*acme.OrderSpec), b.(*OrderSpec), scope) - }); err != nil { - return err - } - if err := s.AddConversionFunc((*ACMEIssuer)(nil), (*acme.ACMEIssuer)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1alpha3_ACMEIssuer_To_acme_ACMEIssuer(a.(*ACMEIssuer), b.(*acme.ACMEIssuer), scope) - }); err != nil { - return err - } - if err := s.AddConversionFunc((*ChallengeSpec)(nil), (*acme.ChallengeSpec)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1alpha3_ChallengeSpec_To_acme_ChallengeSpec(a.(*ChallengeSpec), b.(*acme.ChallengeSpec), scope) - }); err != nil { - return err - } - if err := s.AddConversionFunc((*OrderSpec)(nil), (*acme.OrderSpec)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1alpha3_OrderSpec_To_acme_OrderSpec(a.(*OrderSpec), b.(*acme.OrderSpec), scope) - }); err != nil { - return err - } - return nil -} - -func autoConvert_v1alpha3_ACMEAuthorization_To_acme_ACMEAuthorization(in *ACMEAuthorization, out *acme.ACMEAuthorization, s conversion.Scope) error { - out.URL = in.URL - out.Identifier = in.Identifier - out.Wildcard = (*bool)(unsafe.Pointer(in.Wildcard)) - out.InitialState = acme.State(in.InitialState) - out.Challenges = *(*[]acme.ACMEChallenge)(unsafe.Pointer(&in.Challenges)) - return nil -} - -// Convert_v1alpha3_ACMEAuthorization_To_acme_ACMEAuthorization is an autogenerated conversion function. -func Convert_v1alpha3_ACMEAuthorization_To_acme_ACMEAuthorization(in *ACMEAuthorization, out *acme.ACMEAuthorization, s conversion.Scope) error { - return autoConvert_v1alpha3_ACMEAuthorization_To_acme_ACMEAuthorization(in, out, s) -} - -func autoConvert_acme_ACMEAuthorization_To_v1alpha3_ACMEAuthorization(in *acme.ACMEAuthorization, out *ACMEAuthorization, s conversion.Scope) error { - out.URL = in.URL - out.Identifier = in.Identifier - out.Wildcard = (*bool)(unsafe.Pointer(in.Wildcard)) - out.InitialState = State(in.InitialState) - out.Challenges = *(*[]ACMEChallenge)(unsafe.Pointer(&in.Challenges)) - return nil -} - -// Convert_acme_ACMEAuthorization_To_v1alpha3_ACMEAuthorization is an autogenerated conversion function. -func Convert_acme_ACMEAuthorization_To_v1alpha3_ACMEAuthorization(in *acme.ACMEAuthorization, out *ACMEAuthorization, s conversion.Scope) error { - return autoConvert_acme_ACMEAuthorization_To_v1alpha3_ACMEAuthorization(in, out, s) -} - -func autoConvert_v1alpha3_ACMEChallenge_To_acme_ACMEChallenge(in *ACMEChallenge, out *acme.ACMEChallenge, s conversion.Scope) error { - out.URL = in.URL - out.Token = in.Token - out.Type = in.Type - return nil -} - -// Convert_v1alpha3_ACMEChallenge_To_acme_ACMEChallenge is an autogenerated conversion function. -func Convert_v1alpha3_ACMEChallenge_To_acme_ACMEChallenge(in *ACMEChallenge, out *acme.ACMEChallenge, s conversion.Scope) error { - return autoConvert_v1alpha3_ACMEChallenge_To_acme_ACMEChallenge(in, out, s) -} - -func autoConvert_acme_ACMEChallenge_To_v1alpha3_ACMEChallenge(in *acme.ACMEChallenge, out *ACMEChallenge, s conversion.Scope) error { - out.URL = in.URL - out.Token = in.Token - out.Type = in.Type - return nil -} - -// Convert_acme_ACMEChallenge_To_v1alpha3_ACMEChallenge is an autogenerated conversion function. -func Convert_acme_ACMEChallenge_To_v1alpha3_ACMEChallenge(in *acme.ACMEChallenge, out *ACMEChallenge, s conversion.Scope) error { - return autoConvert_acme_ACMEChallenge_To_v1alpha3_ACMEChallenge(in, out, s) -} - -func autoConvert_v1alpha3_ACMEChallengeSolver_To_acme_ACMEChallengeSolver(in *ACMEChallengeSolver, out *acme.ACMEChallengeSolver, s conversion.Scope) error { - out.Selector = (*acme.CertificateDNSNameSelector)(unsafe.Pointer(in.Selector)) - out.HTTP01 = (*acme.ACMEChallengeSolverHTTP01)(unsafe.Pointer(in.HTTP01)) - if in.DNS01 != nil { - in, out := &in.DNS01, &out.DNS01 - *out = new(acme.ACMEChallengeSolverDNS01) - if err := Convert_v1alpha3_ACMEChallengeSolverDNS01_To_acme_ACMEChallengeSolverDNS01(*in, *out, s); err != nil { - return err - } - } else { - out.DNS01 = nil - } - return nil -} - -// Convert_v1alpha3_ACMEChallengeSolver_To_acme_ACMEChallengeSolver is an autogenerated conversion function. -func Convert_v1alpha3_ACMEChallengeSolver_To_acme_ACMEChallengeSolver(in *ACMEChallengeSolver, out *acme.ACMEChallengeSolver, s conversion.Scope) error { - return autoConvert_v1alpha3_ACMEChallengeSolver_To_acme_ACMEChallengeSolver(in, out, s) -} - -func autoConvert_acme_ACMEChallengeSolver_To_v1alpha3_ACMEChallengeSolver(in *acme.ACMEChallengeSolver, out *ACMEChallengeSolver, s conversion.Scope) error { - out.Selector = (*CertificateDNSNameSelector)(unsafe.Pointer(in.Selector)) - out.HTTP01 = (*ACMEChallengeSolverHTTP01)(unsafe.Pointer(in.HTTP01)) - if in.DNS01 != nil { - in, out := &in.DNS01, &out.DNS01 - *out = new(ACMEChallengeSolverDNS01) - if err := Convert_acme_ACMEChallengeSolverDNS01_To_v1alpha3_ACMEChallengeSolverDNS01(*in, *out, s); err != nil { - return err - } - } else { - out.DNS01 = nil - } - return nil -} - -// Convert_acme_ACMEChallengeSolver_To_v1alpha3_ACMEChallengeSolver is an autogenerated conversion function. -func Convert_acme_ACMEChallengeSolver_To_v1alpha3_ACMEChallengeSolver(in *acme.ACMEChallengeSolver, out *ACMEChallengeSolver, s conversion.Scope) error { - return autoConvert_acme_ACMEChallengeSolver_To_v1alpha3_ACMEChallengeSolver(in, out, s) -} - -func autoConvert_v1alpha3_ACMEChallengeSolverDNS01_To_acme_ACMEChallengeSolverDNS01(in *ACMEChallengeSolverDNS01, out *acme.ACMEChallengeSolverDNS01, s conversion.Scope) error { - out.CNAMEStrategy = acme.CNAMEStrategy(in.CNAMEStrategy) - if in.Akamai != nil { - in, out := &in.Akamai, &out.Akamai - *out = new(acme.ACMEIssuerDNS01ProviderAkamai) - if err := Convert_v1alpha3_ACMEIssuerDNS01ProviderAkamai_To_acme_ACMEIssuerDNS01ProviderAkamai(*in, *out, s); err != nil { - return err - } - } else { - out.Akamai = nil - } - if in.CloudDNS != nil { - in, out := &in.CloudDNS, &out.CloudDNS - *out = new(acme.ACMEIssuerDNS01ProviderCloudDNS) - if err := Convert_v1alpha3_ACMEIssuerDNS01ProviderCloudDNS_To_acme_ACMEIssuerDNS01ProviderCloudDNS(*in, *out, s); err != nil { - return err - } - } else { - out.CloudDNS = nil - } - if in.Cloudflare != nil { - in, out := &in.Cloudflare, &out.Cloudflare - *out = new(acme.ACMEIssuerDNS01ProviderCloudflare) - if err := Convert_v1alpha3_ACMEIssuerDNS01ProviderCloudflare_To_acme_ACMEIssuerDNS01ProviderCloudflare(*in, *out, s); err != nil { - return err - } - } else { - out.Cloudflare = nil - } - if in.Route53 != nil { - in, out := &in.Route53, &out.Route53 - *out = new(acme.ACMEIssuerDNS01ProviderRoute53) - if err := Convert_v1alpha3_ACMEIssuerDNS01ProviderRoute53_To_acme_ACMEIssuerDNS01ProviderRoute53(*in, *out, s); err != nil { - return err - } - } else { - out.Route53 = nil - } - if in.AzureDNS != nil { - in, out := &in.AzureDNS, &out.AzureDNS - *out = new(acme.ACMEIssuerDNS01ProviderAzureDNS) - if err := Convert_v1alpha3_ACMEIssuerDNS01ProviderAzureDNS_To_acme_ACMEIssuerDNS01ProviderAzureDNS(*in, *out, s); err != nil { - return err - } - } else { - out.AzureDNS = nil - } - if in.DigitalOcean != nil { - in, out := &in.DigitalOcean, &out.DigitalOcean - *out = new(acme.ACMEIssuerDNS01ProviderDigitalOcean) - if err := Convert_v1alpha3_ACMEIssuerDNS01ProviderDigitalOcean_To_acme_ACMEIssuerDNS01ProviderDigitalOcean(*in, *out, s); err != nil { - return err - } - } else { - out.DigitalOcean = nil - } - if in.AcmeDNS != nil { - in, out := &in.AcmeDNS, &out.AcmeDNS - *out = new(acme.ACMEIssuerDNS01ProviderAcmeDNS) - if err := Convert_v1alpha3_ACMEIssuerDNS01ProviderAcmeDNS_To_acme_ACMEIssuerDNS01ProviderAcmeDNS(*in, *out, s); err != nil { - return err - } - } else { - out.AcmeDNS = nil - } - if in.RFC2136 != nil { - in, out := &in.RFC2136, &out.RFC2136 - *out = new(acme.ACMEIssuerDNS01ProviderRFC2136) - if err := Convert_v1alpha3_ACMEIssuerDNS01ProviderRFC2136_To_acme_ACMEIssuerDNS01ProviderRFC2136(*in, *out, s); err != nil { - return err - } - } else { - out.RFC2136 = nil - } - out.Webhook = (*acme.ACMEIssuerDNS01ProviderWebhook)(unsafe.Pointer(in.Webhook)) - return nil -} - -// Convert_v1alpha3_ACMEChallengeSolverDNS01_To_acme_ACMEChallengeSolverDNS01 is an autogenerated conversion function. -func Convert_v1alpha3_ACMEChallengeSolverDNS01_To_acme_ACMEChallengeSolverDNS01(in *ACMEChallengeSolverDNS01, out *acme.ACMEChallengeSolverDNS01, s conversion.Scope) error { - return autoConvert_v1alpha3_ACMEChallengeSolverDNS01_To_acme_ACMEChallengeSolverDNS01(in, out, s) -} - -func autoConvert_acme_ACMEChallengeSolverDNS01_To_v1alpha3_ACMEChallengeSolverDNS01(in *acme.ACMEChallengeSolverDNS01, out *ACMEChallengeSolverDNS01, s conversion.Scope) error { - out.CNAMEStrategy = CNAMEStrategy(in.CNAMEStrategy) - if in.Akamai != nil { - in, out := &in.Akamai, &out.Akamai - *out = new(ACMEIssuerDNS01ProviderAkamai) - if err := Convert_acme_ACMEIssuerDNS01ProviderAkamai_To_v1alpha3_ACMEIssuerDNS01ProviderAkamai(*in, *out, s); err != nil { - return err - } - } else { - out.Akamai = nil - } - if in.CloudDNS != nil { - in, out := &in.CloudDNS, &out.CloudDNS - *out = new(ACMEIssuerDNS01ProviderCloudDNS) - if err := Convert_acme_ACMEIssuerDNS01ProviderCloudDNS_To_v1alpha3_ACMEIssuerDNS01ProviderCloudDNS(*in, *out, s); err != nil { - return err - } - } else { - out.CloudDNS = nil - } - if in.Cloudflare != nil { - in, out := &in.Cloudflare, &out.Cloudflare - *out = new(ACMEIssuerDNS01ProviderCloudflare) - if err := Convert_acme_ACMEIssuerDNS01ProviderCloudflare_To_v1alpha3_ACMEIssuerDNS01ProviderCloudflare(*in, *out, s); err != nil { - return err - } - } else { - out.Cloudflare = nil - } - if in.Route53 != nil { - in, out := &in.Route53, &out.Route53 - *out = new(ACMEIssuerDNS01ProviderRoute53) - if err := Convert_acme_ACMEIssuerDNS01ProviderRoute53_To_v1alpha3_ACMEIssuerDNS01ProviderRoute53(*in, *out, s); err != nil { - return err - } - } else { - out.Route53 = nil - } - if in.AzureDNS != nil { - in, out := &in.AzureDNS, &out.AzureDNS - *out = new(ACMEIssuerDNS01ProviderAzureDNS) - if err := Convert_acme_ACMEIssuerDNS01ProviderAzureDNS_To_v1alpha3_ACMEIssuerDNS01ProviderAzureDNS(*in, *out, s); err != nil { - return err - } - } else { - out.AzureDNS = nil - } - if in.DigitalOcean != nil { - in, out := &in.DigitalOcean, &out.DigitalOcean - *out = new(ACMEIssuerDNS01ProviderDigitalOcean) - if err := Convert_acme_ACMEIssuerDNS01ProviderDigitalOcean_To_v1alpha3_ACMEIssuerDNS01ProviderDigitalOcean(*in, *out, s); err != nil { - return err - } - } else { - out.DigitalOcean = nil - } - if in.AcmeDNS != nil { - in, out := &in.AcmeDNS, &out.AcmeDNS - *out = new(ACMEIssuerDNS01ProviderAcmeDNS) - if err := Convert_acme_ACMEIssuerDNS01ProviderAcmeDNS_To_v1alpha3_ACMEIssuerDNS01ProviderAcmeDNS(*in, *out, s); err != nil { - return err - } - } else { - out.AcmeDNS = nil - } - if in.RFC2136 != nil { - in, out := &in.RFC2136, &out.RFC2136 - *out = new(ACMEIssuerDNS01ProviderRFC2136) - if err := Convert_acme_ACMEIssuerDNS01ProviderRFC2136_To_v1alpha3_ACMEIssuerDNS01ProviderRFC2136(*in, *out, s); err != nil { - return err - } - } else { - out.RFC2136 = nil - } - out.Webhook = (*ACMEIssuerDNS01ProviderWebhook)(unsafe.Pointer(in.Webhook)) - return nil -} - -// Convert_acme_ACMEChallengeSolverDNS01_To_v1alpha3_ACMEChallengeSolverDNS01 is an autogenerated conversion function. -func Convert_acme_ACMEChallengeSolverDNS01_To_v1alpha3_ACMEChallengeSolverDNS01(in *acme.ACMEChallengeSolverDNS01, out *ACMEChallengeSolverDNS01, s conversion.Scope) error { - return autoConvert_acme_ACMEChallengeSolverDNS01_To_v1alpha3_ACMEChallengeSolverDNS01(in, out, s) -} - -func autoConvert_v1alpha3_ACMEChallengeSolverHTTP01_To_acme_ACMEChallengeSolverHTTP01(in *ACMEChallengeSolverHTTP01, out *acme.ACMEChallengeSolverHTTP01, s conversion.Scope) error { - out.Ingress = (*acme.ACMEChallengeSolverHTTP01Ingress)(unsafe.Pointer(in.Ingress)) - out.GatewayHTTPRoute = (*acme.ACMEChallengeSolverHTTP01GatewayHTTPRoute)(unsafe.Pointer(in.GatewayHTTPRoute)) - return nil -} - -// Convert_v1alpha3_ACMEChallengeSolverHTTP01_To_acme_ACMEChallengeSolverHTTP01 is an autogenerated conversion function. -func Convert_v1alpha3_ACMEChallengeSolverHTTP01_To_acme_ACMEChallengeSolverHTTP01(in *ACMEChallengeSolverHTTP01, out *acme.ACMEChallengeSolverHTTP01, s conversion.Scope) error { - return autoConvert_v1alpha3_ACMEChallengeSolverHTTP01_To_acme_ACMEChallengeSolverHTTP01(in, out, s) -} - -func autoConvert_acme_ACMEChallengeSolverHTTP01_To_v1alpha3_ACMEChallengeSolverHTTP01(in *acme.ACMEChallengeSolverHTTP01, out *ACMEChallengeSolverHTTP01, s conversion.Scope) error { - out.Ingress = (*ACMEChallengeSolverHTTP01Ingress)(unsafe.Pointer(in.Ingress)) - out.GatewayHTTPRoute = (*ACMEChallengeSolverHTTP01GatewayHTTPRoute)(unsafe.Pointer(in.GatewayHTTPRoute)) - return nil -} - -// Convert_acme_ACMEChallengeSolverHTTP01_To_v1alpha3_ACMEChallengeSolverHTTP01 is an autogenerated conversion function. -func Convert_acme_ACMEChallengeSolverHTTP01_To_v1alpha3_ACMEChallengeSolverHTTP01(in *acme.ACMEChallengeSolverHTTP01, out *ACMEChallengeSolverHTTP01, s conversion.Scope) error { - return autoConvert_acme_ACMEChallengeSolverHTTP01_To_v1alpha3_ACMEChallengeSolverHTTP01(in, out, s) -} - -func autoConvert_v1alpha3_ACMEChallengeSolverHTTP01GatewayHTTPRoute_To_acme_ACMEChallengeSolverHTTP01GatewayHTTPRoute(in *ACMEChallengeSolverHTTP01GatewayHTTPRoute, out *acme.ACMEChallengeSolverHTTP01GatewayHTTPRoute, s conversion.Scope) error { - out.ServiceType = v1.ServiceType(in.ServiceType) - out.Labels = *(*map[string]string)(unsafe.Pointer(&in.Labels)) - out.ParentRefs = *(*[]v1alpha2.ParentReference)(unsafe.Pointer(&in.ParentRefs)) - return nil -} - -// Convert_v1alpha3_ACMEChallengeSolverHTTP01GatewayHTTPRoute_To_acme_ACMEChallengeSolverHTTP01GatewayHTTPRoute is an autogenerated conversion function. -func Convert_v1alpha3_ACMEChallengeSolverHTTP01GatewayHTTPRoute_To_acme_ACMEChallengeSolverHTTP01GatewayHTTPRoute(in *ACMEChallengeSolverHTTP01GatewayHTTPRoute, out *acme.ACMEChallengeSolverHTTP01GatewayHTTPRoute, s conversion.Scope) error { - return autoConvert_v1alpha3_ACMEChallengeSolverHTTP01GatewayHTTPRoute_To_acme_ACMEChallengeSolverHTTP01GatewayHTTPRoute(in, out, s) -} - -func autoConvert_acme_ACMEChallengeSolverHTTP01GatewayHTTPRoute_To_v1alpha3_ACMEChallengeSolverHTTP01GatewayHTTPRoute(in *acme.ACMEChallengeSolverHTTP01GatewayHTTPRoute, out *ACMEChallengeSolverHTTP01GatewayHTTPRoute, s conversion.Scope) error { - out.ServiceType = v1.ServiceType(in.ServiceType) - out.Labels = *(*map[string]string)(unsafe.Pointer(&in.Labels)) - out.ParentRefs = *(*[]v1alpha2.ParentReference)(unsafe.Pointer(&in.ParentRefs)) - return nil -} - -// Convert_acme_ACMEChallengeSolverHTTP01GatewayHTTPRoute_To_v1alpha3_ACMEChallengeSolverHTTP01GatewayHTTPRoute is an autogenerated conversion function. -func Convert_acme_ACMEChallengeSolverHTTP01GatewayHTTPRoute_To_v1alpha3_ACMEChallengeSolverHTTP01GatewayHTTPRoute(in *acme.ACMEChallengeSolverHTTP01GatewayHTTPRoute, out *ACMEChallengeSolverHTTP01GatewayHTTPRoute, s conversion.Scope) error { - return autoConvert_acme_ACMEChallengeSolverHTTP01GatewayHTTPRoute_To_v1alpha3_ACMEChallengeSolverHTTP01GatewayHTTPRoute(in, out, s) -} - -func autoConvert_v1alpha3_ACMEChallengeSolverHTTP01Ingress_To_acme_ACMEChallengeSolverHTTP01Ingress(in *ACMEChallengeSolverHTTP01Ingress, out *acme.ACMEChallengeSolverHTTP01Ingress, s conversion.Scope) error { - out.ServiceType = v1.ServiceType(in.ServiceType) - out.Class = (*string)(unsafe.Pointer(in.Class)) - out.Name = in.Name - out.PodTemplate = (*acme.ACMEChallengeSolverHTTP01IngressPodTemplate)(unsafe.Pointer(in.PodTemplate)) - out.IngressTemplate = (*acme.ACMEChallengeSolverHTTP01IngressTemplate)(unsafe.Pointer(in.IngressTemplate)) - return nil -} - -// Convert_v1alpha3_ACMEChallengeSolverHTTP01Ingress_To_acme_ACMEChallengeSolverHTTP01Ingress is an autogenerated conversion function. -func Convert_v1alpha3_ACMEChallengeSolverHTTP01Ingress_To_acme_ACMEChallengeSolverHTTP01Ingress(in *ACMEChallengeSolverHTTP01Ingress, out *acme.ACMEChallengeSolverHTTP01Ingress, s conversion.Scope) error { - return autoConvert_v1alpha3_ACMEChallengeSolverHTTP01Ingress_To_acme_ACMEChallengeSolverHTTP01Ingress(in, out, s) -} - -func autoConvert_acme_ACMEChallengeSolverHTTP01Ingress_To_v1alpha3_ACMEChallengeSolverHTTP01Ingress(in *acme.ACMEChallengeSolverHTTP01Ingress, out *ACMEChallengeSolverHTTP01Ingress, s conversion.Scope) error { - out.ServiceType = v1.ServiceType(in.ServiceType) - out.Class = (*string)(unsafe.Pointer(in.Class)) - out.Name = in.Name - out.PodTemplate = (*ACMEChallengeSolverHTTP01IngressPodTemplate)(unsafe.Pointer(in.PodTemplate)) - out.IngressTemplate = (*ACMEChallengeSolverHTTP01IngressTemplate)(unsafe.Pointer(in.IngressTemplate)) - return nil -} - -// Convert_acme_ACMEChallengeSolverHTTP01Ingress_To_v1alpha3_ACMEChallengeSolverHTTP01Ingress is an autogenerated conversion function. -func Convert_acme_ACMEChallengeSolverHTTP01Ingress_To_v1alpha3_ACMEChallengeSolverHTTP01Ingress(in *acme.ACMEChallengeSolverHTTP01Ingress, out *ACMEChallengeSolverHTTP01Ingress, s conversion.Scope) error { - return autoConvert_acme_ACMEChallengeSolverHTTP01Ingress_To_v1alpha3_ACMEChallengeSolverHTTP01Ingress(in, out, s) -} - -func autoConvert_v1alpha3_ACMEChallengeSolverHTTP01IngressObjectMeta_To_acme_ACMEChallengeSolverHTTP01IngressObjectMeta(in *ACMEChallengeSolverHTTP01IngressObjectMeta, out *acme.ACMEChallengeSolverHTTP01IngressObjectMeta, s conversion.Scope) error { - out.Annotations = *(*map[string]string)(unsafe.Pointer(&in.Annotations)) - out.Labels = *(*map[string]string)(unsafe.Pointer(&in.Labels)) - return nil -} - -// Convert_v1alpha3_ACMEChallengeSolverHTTP01IngressObjectMeta_To_acme_ACMEChallengeSolverHTTP01IngressObjectMeta is an autogenerated conversion function. -func Convert_v1alpha3_ACMEChallengeSolverHTTP01IngressObjectMeta_To_acme_ACMEChallengeSolverHTTP01IngressObjectMeta(in *ACMEChallengeSolverHTTP01IngressObjectMeta, out *acme.ACMEChallengeSolverHTTP01IngressObjectMeta, s conversion.Scope) error { - return autoConvert_v1alpha3_ACMEChallengeSolverHTTP01IngressObjectMeta_To_acme_ACMEChallengeSolverHTTP01IngressObjectMeta(in, out, s) -} - -func autoConvert_acme_ACMEChallengeSolverHTTP01IngressObjectMeta_To_v1alpha3_ACMEChallengeSolverHTTP01IngressObjectMeta(in *acme.ACMEChallengeSolverHTTP01IngressObjectMeta, out *ACMEChallengeSolverHTTP01IngressObjectMeta, s conversion.Scope) error { - out.Annotations = *(*map[string]string)(unsafe.Pointer(&in.Annotations)) - out.Labels = *(*map[string]string)(unsafe.Pointer(&in.Labels)) - return nil -} - -// Convert_acme_ACMEChallengeSolverHTTP01IngressObjectMeta_To_v1alpha3_ACMEChallengeSolverHTTP01IngressObjectMeta is an autogenerated conversion function. -func Convert_acme_ACMEChallengeSolverHTTP01IngressObjectMeta_To_v1alpha3_ACMEChallengeSolverHTTP01IngressObjectMeta(in *acme.ACMEChallengeSolverHTTP01IngressObjectMeta, out *ACMEChallengeSolverHTTP01IngressObjectMeta, s conversion.Scope) error { - return autoConvert_acme_ACMEChallengeSolverHTTP01IngressObjectMeta_To_v1alpha3_ACMEChallengeSolverHTTP01IngressObjectMeta(in, out, s) -} - -func autoConvert_v1alpha3_ACMEChallengeSolverHTTP01IngressPodObjectMeta_To_acme_ACMEChallengeSolverHTTP01IngressPodObjectMeta(in *ACMEChallengeSolverHTTP01IngressPodObjectMeta, out *acme.ACMEChallengeSolverHTTP01IngressPodObjectMeta, s conversion.Scope) error { - out.Annotations = *(*map[string]string)(unsafe.Pointer(&in.Annotations)) - out.Labels = *(*map[string]string)(unsafe.Pointer(&in.Labels)) - return nil -} - -// Convert_v1alpha3_ACMEChallengeSolverHTTP01IngressPodObjectMeta_To_acme_ACMEChallengeSolverHTTP01IngressPodObjectMeta is an autogenerated conversion function. -func Convert_v1alpha3_ACMEChallengeSolverHTTP01IngressPodObjectMeta_To_acme_ACMEChallengeSolverHTTP01IngressPodObjectMeta(in *ACMEChallengeSolverHTTP01IngressPodObjectMeta, out *acme.ACMEChallengeSolverHTTP01IngressPodObjectMeta, s conversion.Scope) error { - return autoConvert_v1alpha3_ACMEChallengeSolverHTTP01IngressPodObjectMeta_To_acme_ACMEChallengeSolverHTTP01IngressPodObjectMeta(in, out, s) -} - -func autoConvert_acme_ACMEChallengeSolverHTTP01IngressPodObjectMeta_To_v1alpha3_ACMEChallengeSolverHTTP01IngressPodObjectMeta(in *acme.ACMEChallengeSolverHTTP01IngressPodObjectMeta, out *ACMEChallengeSolverHTTP01IngressPodObjectMeta, s conversion.Scope) error { - out.Annotations = *(*map[string]string)(unsafe.Pointer(&in.Annotations)) - out.Labels = *(*map[string]string)(unsafe.Pointer(&in.Labels)) - return nil -} - -// Convert_acme_ACMEChallengeSolverHTTP01IngressPodObjectMeta_To_v1alpha3_ACMEChallengeSolverHTTP01IngressPodObjectMeta is an autogenerated conversion function. -func Convert_acme_ACMEChallengeSolverHTTP01IngressPodObjectMeta_To_v1alpha3_ACMEChallengeSolverHTTP01IngressPodObjectMeta(in *acme.ACMEChallengeSolverHTTP01IngressPodObjectMeta, out *ACMEChallengeSolverHTTP01IngressPodObjectMeta, s conversion.Scope) error { - return autoConvert_acme_ACMEChallengeSolverHTTP01IngressPodObjectMeta_To_v1alpha3_ACMEChallengeSolverHTTP01IngressPodObjectMeta(in, out, s) -} - -func autoConvert_v1alpha3_ACMEChallengeSolverHTTP01IngressPodSpec_To_acme_ACMEChallengeSolverHTTP01IngressPodSpec(in *ACMEChallengeSolverHTTP01IngressPodSpec, out *acme.ACMEChallengeSolverHTTP01IngressPodSpec, s conversion.Scope) error { - out.NodeSelector = *(*map[string]string)(unsafe.Pointer(&in.NodeSelector)) - out.Affinity = (*v1.Affinity)(unsafe.Pointer(in.Affinity)) - out.Tolerations = *(*[]v1.Toleration)(unsafe.Pointer(&in.Tolerations)) - out.PriorityClassName = in.PriorityClassName - out.ServiceAccountName = in.ServiceAccountName - return nil -} - -// Convert_v1alpha3_ACMEChallengeSolverHTTP01IngressPodSpec_To_acme_ACMEChallengeSolverHTTP01IngressPodSpec is an autogenerated conversion function. -func Convert_v1alpha3_ACMEChallengeSolverHTTP01IngressPodSpec_To_acme_ACMEChallengeSolverHTTP01IngressPodSpec(in *ACMEChallengeSolverHTTP01IngressPodSpec, out *acme.ACMEChallengeSolverHTTP01IngressPodSpec, s conversion.Scope) error { - return autoConvert_v1alpha3_ACMEChallengeSolverHTTP01IngressPodSpec_To_acme_ACMEChallengeSolverHTTP01IngressPodSpec(in, out, s) -} - -func autoConvert_acme_ACMEChallengeSolverHTTP01IngressPodSpec_To_v1alpha3_ACMEChallengeSolverHTTP01IngressPodSpec(in *acme.ACMEChallengeSolverHTTP01IngressPodSpec, out *ACMEChallengeSolverHTTP01IngressPodSpec, s conversion.Scope) error { - out.NodeSelector = *(*map[string]string)(unsafe.Pointer(&in.NodeSelector)) - out.Affinity = (*v1.Affinity)(unsafe.Pointer(in.Affinity)) - out.Tolerations = *(*[]v1.Toleration)(unsafe.Pointer(&in.Tolerations)) - out.PriorityClassName = in.PriorityClassName - out.ServiceAccountName = in.ServiceAccountName - return nil -} - -// Convert_acme_ACMEChallengeSolverHTTP01IngressPodSpec_To_v1alpha3_ACMEChallengeSolverHTTP01IngressPodSpec is an autogenerated conversion function. -func Convert_acme_ACMEChallengeSolverHTTP01IngressPodSpec_To_v1alpha3_ACMEChallengeSolverHTTP01IngressPodSpec(in *acme.ACMEChallengeSolverHTTP01IngressPodSpec, out *ACMEChallengeSolverHTTP01IngressPodSpec, s conversion.Scope) error { - return autoConvert_acme_ACMEChallengeSolverHTTP01IngressPodSpec_To_v1alpha3_ACMEChallengeSolverHTTP01IngressPodSpec(in, out, s) -} - -func autoConvert_v1alpha3_ACMEChallengeSolverHTTP01IngressPodTemplate_To_acme_ACMEChallengeSolverHTTP01IngressPodTemplate(in *ACMEChallengeSolverHTTP01IngressPodTemplate, out *acme.ACMEChallengeSolverHTTP01IngressPodTemplate, s conversion.Scope) error { - if err := Convert_v1alpha3_ACMEChallengeSolverHTTP01IngressPodObjectMeta_To_acme_ACMEChallengeSolverHTTP01IngressPodObjectMeta(&in.ACMEChallengeSolverHTTP01IngressPodObjectMeta, &out.ACMEChallengeSolverHTTP01IngressPodObjectMeta, s); err != nil { - return err - } - if err := Convert_v1alpha3_ACMEChallengeSolverHTTP01IngressPodSpec_To_acme_ACMEChallengeSolverHTTP01IngressPodSpec(&in.Spec, &out.Spec, s); err != nil { - return err - } - return nil -} - -// Convert_v1alpha3_ACMEChallengeSolverHTTP01IngressPodTemplate_To_acme_ACMEChallengeSolverHTTP01IngressPodTemplate is an autogenerated conversion function. -func Convert_v1alpha3_ACMEChallengeSolverHTTP01IngressPodTemplate_To_acme_ACMEChallengeSolverHTTP01IngressPodTemplate(in *ACMEChallengeSolverHTTP01IngressPodTemplate, out *acme.ACMEChallengeSolverHTTP01IngressPodTemplate, s conversion.Scope) error { - return autoConvert_v1alpha3_ACMEChallengeSolverHTTP01IngressPodTemplate_To_acme_ACMEChallengeSolverHTTP01IngressPodTemplate(in, out, s) -} - -func autoConvert_acme_ACMEChallengeSolverHTTP01IngressPodTemplate_To_v1alpha3_ACMEChallengeSolverHTTP01IngressPodTemplate(in *acme.ACMEChallengeSolverHTTP01IngressPodTemplate, out *ACMEChallengeSolverHTTP01IngressPodTemplate, s conversion.Scope) error { - if err := Convert_acme_ACMEChallengeSolverHTTP01IngressPodObjectMeta_To_v1alpha3_ACMEChallengeSolverHTTP01IngressPodObjectMeta(&in.ACMEChallengeSolverHTTP01IngressPodObjectMeta, &out.ACMEChallengeSolverHTTP01IngressPodObjectMeta, s); err != nil { - return err - } - if err := Convert_acme_ACMEChallengeSolverHTTP01IngressPodSpec_To_v1alpha3_ACMEChallengeSolverHTTP01IngressPodSpec(&in.Spec, &out.Spec, s); err != nil { - return err - } - return nil -} - -// Convert_acme_ACMEChallengeSolverHTTP01IngressPodTemplate_To_v1alpha3_ACMEChallengeSolverHTTP01IngressPodTemplate is an autogenerated conversion function. -func Convert_acme_ACMEChallengeSolverHTTP01IngressPodTemplate_To_v1alpha3_ACMEChallengeSolverHTTP01IngressPodTemplate(in *acme.ACMEChallengeSolverHTTP01IngressPodTemplate, out *ACMEChallengeSolverHTTP01IngressPodTemplate, s conversion.Scope) error { - return autoConvert_acme_ACMEChallengeSolverHTTP01IngressPodTemplate_To_v1alpha3_ACMEChallengeSolverHTTP01IngressPodTemplate(in, out, s) -} - -func autoConvert_v1alpha3_ACMEChallengeSolverHTTP01IngressTemplate_To_acme_ACMEChallengeSolverHTTP01IngressTemplate(in *ACMEChallengeSolverHTTP01IngressTemplate, out *acme.ACMEChallengeSolverHTTP01IngressTemplate, s conversion.Scope) error { - if err := Convert_v1alpha3_ACMEChallengeSolverHTTP01IngressObjectMeta_To_acme_ACMEChallengeSolverHTTP01IngressObjectMeta(&in.ACMEChallengeSolverHTTP01IngressObjectMeta, &out.ACMEChallengeSolverHTTP01IngressObjectMeta, s); err != nil { - return err - } - return nil -} - -// Convert_v1alpha3_ACMEChallengeSolverHTTP01IngressTemplate_To_acme_ACMEChallengeSolverHTTP01IngressTemplate is an autogenerated conversion function. -func Convert_v1alpha3_ACMEChallengeSolverHTTP01IngressTemplate_To_acme_ACMEChallengeSolverHTTP01IngressTemplate(in *ACMEChallengeSolverHTTP01IngressTemplate, out *acme.ACMEChallengeSolverHTTP01IngressTemplate, s conversion.Scope) error { - return autoConvert_v1alpha3_ACMEChallengeSolverHTTP01IngressTemplate_To_acme_ACMEChallengeSolverHTTP01IngressTemplate(in, out, s) -} - -func autoConvert_acme_ACMEChallengeSolverHTTP01IngressTemplate_To_v1alpha3_ACMEChallengeSolverHTTP01IngressTemplate(in *acme.ACMEChallengeSolverHTTP01IngressTemplate, out *ACMEChallengeSolverHTTP01IngressTemplate, s conversion.Scope) error { - if err := Convert_acme_ACMEChallengeSolverHTTP01IngressObjectMeta_To_v1alpha3_ACMEChallengeSolverHTTP01IngressObjectMeta(&in.ACMEChallengeSolverHTTP01IngressObjectMeta, &out.ACMEChallengeSolverHTTP01IngressObjectMeta, s); err != nil { - return err - } - return nil -} - -// Convert_acme_ACMEChallengeSolverHTTP01IngressTemplate_To_v1alpha3_ACMEChallengeSolverHTTP01IngressTemplate is an autogenerated conversion function. -func Convert_acme_ACMEChallengeSolverHTTP01IngressTemplate_To_v1alpha3_ACMEChallengeSolverHTTP01IngressTemplate(in *acme.ACMEChallengeSolverHTTP01IngressTemplate, out *ACMEChallengeSolverHTTP01IngressTemplate, s conversion.Scope) error { - return autoConvert_acme_ACMEChallengeSolverHTTP01IngressTemplate_To_v1alpha3_ACMEChallengeSolverHTTP01IngressTemplate(in, out, s) -} - -func autoConvert_v1alpha3_ACMEExternalAccountBinding_To_acme_ACMEExternalAccountBinding(in *ACMEExternalAccountBinding, out *acme.ACMEExternalAccountBinding, s conversion.Scope) error { - out.KeyID = in.KeyID - if err := metav1.Convert_v1_SecretKeySelector_To_meta_SecretKeySelector(&in.Key, &out.Key, s); err != nil { - return err - } - out.KeyAlgorithm = acme.HMACKeyAlgorithm(in.KeyAlgorithm) - return nil -} - -// Convert_v1alpha3_ACMEExternalAccountBinding_To_acme_ACMEExternalAccountBinding is an autogenerated conversion function. -func Convert_v1alpha3_ACMEExternalAccountBinding_To_acme_ACMEExternalAccountBinding(in *ACMEExternalAccountBinding, out *acme.ACMEExternalAccountBinding, s conversion.Scope) error { - return autoConvert_v1alpha3_ACMEExternalAccountBinding_To_acme_ACMEExternalAccountBinding(in, out, s) -} - -func autoConvert_acme_ACMEExternalAccountBinding_To_v1alpha3_ACMEExternalAccountBinding(in *acme.ACMEExternalAccountBinding, out *ACMEExternalAccountBinding, s conversion.Scope) error { - out.KeyID = in.KeyID - if err := metav1.Convert_meta_SecretKeySelector_To_v1_SecretKeySelector(&in.Key, &out.Key, s); err != nil { - return err - } - out.KeyAlgorithm = HMACKeyAlgorithm(in.KeyAlgorithm) - return nil -} - -// Convert_acme_ACMEExternalAccountBinding_To_v1alpha3_ACMEExternalAccountBinding is an autogenerated conversion function. -func Convert_acme_ACMEExternalAccountBinding_To_v1alpha3_ACMEExternalAccountBinding(in *acme.ACMEExternalAccountBinding, out *ACMEExternalAccountBinding, s conversion.Scope) error { - return autoConvert_acme_ACMEExternalAccountBinding_To_v1alpha3_ACMEExternalAccountBinding(in, out, s) -} - -func autoConvert_v1alpha3_ACMEIssuer_To_acme_ACMEIssuer(in *ACMEIssuer, out *acme.ACMEIssuer, s conversion.Scope) error { - out.Email = in.Email - out.Server = in.Server - out.PreferredChain = in.PreferredChain - out.SkipTLSVerify = in.SkipTLSVerify - if in.ExternalAccountBinding != nil { - in, out := &in.ExternalAccountBinding, &out.ExternalAccountBinding - *out = new(acme.ACMEExternalAccountBinding) - if err := Convert_v1alpha3_ACMEExternalAccountBinding_To_acme_ACMEExternalAccountBinding(*in, *out, s); err != nil { - return err - } - } else { - out.ExternalAccountBinding = nil - } - if err := metav1.Convert_v1_SecretKeySelector_To_meta_SecretKeySelector(&in.PrivateKey, &out.PrivateKey, s); err != nil { - return err - } - if in.Solvers != nil { - in, out := &in.Solvers, &out.Solvers - *out = make([]acme.ACMEChallengeSolver, len(*in)) - for i := range *in { - if err := Convert_v1alpha3_ACMEChallengeSolver_To_acme_ACMEChallengeSolver(&(*in)[i], &(*out)[i], s); err != nil { - return err - } - } - } else { - out.Solvers = nil - } - out.DisableAccountKeyGeneration = in.DisableAccountKeyGeneration - out.EnableDurationFeature = in.EnableDurationFeature - return nil -} - -func autoConvert_acme_ACMEIssuer_To_v1alpha3_ACMEIssuer(in *acme.ACMEIssuer, out *ACMEIssuer, s conversion.Scope) error { - out.Email = in.Email - out.Server = in.Server - out.PreferredChain = in.PreferredChain - out.SkipTLSVerify = in.SkipTLSVerify - if in.ExternalAccountBinding != nil { - in, out := &in.ExternalAccountBinding, &out.ExternalAccountBinding - *out = new(ACMEExternalAccountBinding) - if err := Convert_acme_ACMEExternalAccountBinding_To_v1alpha3_ACMEExternalAccountBinding(*in, *out, s); err != nil { - return err - } - } else { - out.ExternalAccountBinding = nil - } - if err := metav1.Convert_meta_SecretKeySelector_To_v1_SecretKeySelector(&in.PrivateKey, &out.PrivateKey, s); err != nil { - return err - } - if in.Solvers != nil { - in, out := &in.Solvers, &out.Solvers - *out = make([]ACMEChallengeSolver, len(*in)) - for i := range *in { - if err := Convert_acme_ACMEChallengeSolver_To_v1alpha3_ACMEChallengeSolver(&(*in)[i], &(*out)[i], s); err != nil { - return err - } - } - } else { - out.Solvers = nil - } - out.DisableAccountKeyGeneration = in.DisableAccountKeyGeneration - out.EnableDurationFeature = in.EnableDurationFeature - return nil -} - -func autoConvert_v1alpha3_ACMEIssuerDNS01ProviderAcmeDNS_To_acme_ACMEIssuerDNS01ProviderAcmeDNS(in *ACMEIssuerDNS01ProviderAcmeDNS, out *acme.ACMEIssuerDNS01ProviderAcmeDNS, s conversion.Scope) error { - out.Host = in.Host - if err := metav1.Convert_v1_SecretKeySelector_To_meta_SecretKeySelector(&in.AccountSecret, &out.AccountSecret, s); err != nil { - return err - } - return nil -} - -// Convert_v1alpha3_ACMEIssuerDNS01ProviderAcmeDNS_To_acme_ACMEIssuerDNS01ProviderAcmeDNS is an autogenerated conversion function. -func Convert_v1alpha3_ACMEIssuerDNS01ProviderAcmeDNS_To_acme_ACMEIssuerDNS01ProviderAcmeDNS(in *ACMEIssuerDNS01ProviderAcmeDNS, out *acme.ACMEIssuerDNS01ProviderAcmeDNS, s conversion.Scope) error { - return autoConvert_v1alpha3_ACMEIssuerDNS01ProviderAcmeDNS_To_acme_ACMEIssuerDNS01ProviderAcmeDNS(in, out, s) -} - -func autoConvert_acme_ACMEIssuerDNS01ProviderAcmeDNS_To_v1alpha3_ACMEIssuerDNS01ProviderAcmeDNS(in *acme.ACMEIssuerDNS01ProviderAcmeDNS, out *ACMEIssuerDNS01ProviderAcmeDNS, s conversion.Scope) error { - out.Host = in.Host - if err := metav1.Convert_meta_SecretKeySelector_To_v1_SecretKeySelector(&in.AccountSecret, &out.AccountSecret, s); err != nil { - return err - } - return nil -} - -// Convert_acme_ACMEIssuerDNS01ProviderAcmeDNS_To_v1alpha3_ACMEIssuerDNS01ProviderAcmeDNS is an autogenerated conversion function. -func Convert_acme_ACMEIssuerDNS01ProviderAcmeDNS_To_v1alpha3_ACMEIssuerDNS01ProviderAcmeDNS(in *acme.ACMEIssuerDNS01ProviderAcmeDNS, out *ACMEIssuerDNS01ProviderAcmeDNS, s conversion.Scope) error { - return autoConvert_acme_ACMEIssuerDNS01ProviderAcmeDNS_To_v1alpha3_ACMEIssuerDNS01ProviderAcmeDNS(in, out, s) -} - -func autoConvert_v1alpha3_ACMEIssuerDNS01ProviderAkamai_To_acme_ACMEIssuerDNS01ProviderAkamai(in *ACMEIssuerDNS01ProviderAkamai, out *acme.ACMEIssuerDNS01ProviderAkamai, s conversion.Scope) error { - out.ServiceConsumerDomain = in.ServiceConsumerDomain - if err := metav1.Convert_v1_SecretKeySelector_To_meta_SecretKeySelector(&in.ClientToken, &out.ClientToken, s); err != nil { - return err - } - if err := metav1.Convert_v1_SecretKeySelector_To_meta_SecretKeySelector(&in.ClientSecret, &out.ClientSecret, s); err != nil { - return err - } - if err := metav1.Convert_v1_SecretKeySelector_To_meta_SecretKeySelector(&in.AccessToken, &out.AccessToken, s); err != nil { - return err - } - return nil -} - -// Convert_v1alpha3_ACMEIssuerDNS01ProviderAkamai_To_acme_ACMEIssuerDNS01ProviderAkamai is an autogenerated conversion function. -func Convert_v1alpha3_ACMEIssuerDNS01ProviderAkamai_To_acme_ACMEIssuerDNS01ProviderAkamai(in *ACMEIssuerDNS01ProviderAkamai, out *acme.ACMEIssuerDNS01ProviderAkamai, s conversion.Scope) error { - return autoConvert_v1alpha3_ACMEIssuerDNS01ProviderAkamai_To_acme_ACMEIssuerDNS01ProviderAkamai(in, out, s) -} - -func autoConvert_acme_ACMEIssuerDNS01ProviderAkamai_To_v1alpha3_ACMEIssuerDNS01ProviderAkamai(in *acme.ACMEIssuerDNS01ProviderAkamai, out *ACMEIssuerDNS01ProviderAkamai, s conversion.Scope) error { - out.ServiceConsumerDomain = in.ServiceConsumerDomain - if err := metav1.Convert_meta_SecretKeySelector_To_v1_SecretKeySelector(&in.ClientToken, &out.ClientToken, s); err != nil { - return err - } - if err := metav1.Convert_meta_SecretKeySelector_To_v1_SecretKeySelector(&in.ClientSecret, &out.ClientSecret, s); err != nil { - return err - } - if err := metav1.Convert_meta_SecretKeySelector_To_v1_SecretKeySelector(&in.AccessToken, &out.AccessToken, s); err != nil { - return err - } - return nil -} - -// Convert_acme_ACMEIssuerDNS01ProviderAkamai_To_v1alpha3_ACMEIssuerDNS01ProviderAkamai is an autogenerated conversion function. -func Convert_acme_ACMEIssuerDNS01ProviderAkamai_To_v1alpha3_ACMEIssuerDNS01ProviderAkamai(in *acme.ACMEIssuerDNS01ProviderAkamai, out *ACMEIssuerDNS01ProviderAkamai, s conversion.Scope) error { - return autoConvert_acme_ACMEIssuerDNS01ProviderAkamai_To_v1alpha3_ACMEIssuerDNS01ProviderAkamai(in, out, s) -} - -func autoConvert_v1alpha3_ACMEIssuerDNS01ProviderAzureDNS_To_acme_ACMEIssuerDNS01ProviderAzureDNS(in *ACMEIssuerDNS01ProviderAzureDNS, out *acme.ACMEIssuerDNS01ProviderAzureDNS, s conversion.Scope) error { - out.ClientID = in.ClientID - if in.ClientSecret != nil { - in, out := &in.ClientSecret, &out.ClientSecret - *out = new(meta.SecretKeySelector) - if err := metav1.Convert_v1_SecretKeySelector_To_meta_SecretKeySelector(*in, *out, s); err != nil { - return err - } - } else { - out.ClientSecret = nil - } - out.SubscriptionID = in.SubscriptionID - out.TenantID = in.TenantID - out.ResourceGroupName = in.ResourceGroupName - out.HostedZoneName = in.HostedZoneName - out.Environment = acme.AzureDNSEnvironment(in.Environment) - out.ManagedIdentity = (*acme.AzureManagedIdentity)(unsafe.Pointer(in.ManagedIdentity)) - return nil -} - -// Convert_v1alpha3_ACMEIssuerDNS01ProviderAzureDNS_To_acme_ACMEIssuerDNS01ProviderAzureDNS is an autogenerated conversion function. -func Convert_v1alpha3_ACMEIssuerDNS01ProviderAzureDNS_To_acme_ACMEIssuerDNS01ProviderAzureDNS(in *ACMEIssuerDNS01ProviderAzureDNS, out *acme.ACMEIssuerDNS01ProviderAzureDNS, s conversion.Scope) error { - return autoConvert_v1alpha3_ACMEIssuerDNS01ProviderAzureDNS_To_acme_ACMEIssuerDNS01ProviderAzureDNS(in, out, s) -} - -func autoConvert_acme_ACMEIssuerDNS01ProviderAzureDNS_To_v1alpha3_ACMEIssuerDNS01ProviderAzureDNS(in *acme.ACMEIssuerDNS01ProviderAzureDNS, out *ACMEIssuerDNS01ProviderAzureDNS, s conversion.Scope) error { - out.ClientID = in.ClientID - if in.ClientSecret != nil { - in, out := &in.ClientSecret, &out.ClientSecret - *out = new(apismetav1.SecretKeySelector) - if err := metav1.Convert_meta_SecretKeySelector_To_v1_SecretKeySelector(*in, *out, s); err != nil { - return err - } - } else { - out.ClientSecret = nil - } - out.SubscriptionID = in.SubscriptionID - out.TenantID = in.TenantID - out.ResourceGroupName = in.ResourceGroupName - out.HostedZoneName = in.HostedZoneName - out.Environment = AzureDNSEnvironment(in.Environment) - out.ManagedIdentity = (*AzureManagedIdentity)(unsafe.Pointer(in.ManagedIdentity)) - return nil -} - -// Convert_acme_ACMEIssuerDNS01ProviderAzureDNS_To_v1alpha3_ACMEIssuerDNS01ProviderAzureDNS is an autogenerated conversion function. -func Convert_acme_ACMEIssuerDNS01ProviderAzureDNS_To_v1alpha3_ACMEIssuerDNS01ProviderAzureDNS(in *acme.ACMEIssuerDNS01ProviderAzureDNS, out *ACMEIssuerDNS01ProviderAzureDNS, s conversion.Scope) error { - return autoConvert_acme_ACMEIssuerDNS01ProviderAzureDNS_To_v1alpha3_ACMEIssuerDNS01ProviderAzureDNS(in, out, s) -} - -func autoConvert_v1alpha3_ACMEIssuerDNS01ProviderCloudDNS_To_acme_ACMEIssuerDNS01ProviderCloudDNS(in *ACMEIssuerDNS01ProviderCloudDNS, out *acme.ACMEIssuerDNS01ProviderCloudDNS, s conversion.Scope) error { - if in.ServiceAccount != nil { - in, out := &in.ServiceAccount, &out.ServiceAccount - *out = new(meta.SecretKeySelector) - if err := metav1.Convert_v1_SecretKeySelector_To_meta_SecretKeySelector(*in, *out, s); err != nil { - return err - } - } else { - out.ServiceAccount = nil - } - out.Project = in.Project - out.HostedZoneName = in.HostedZoneName - return nil -} - -// Convert_v1alpha3_ACMEIssuerDNS01ProviderCloudDNS_To_acme_ACMEIssuerDNS01ProviderCloudDNS is an autogenerated conversion function. -func Convert_v1alpha3_ACMEIssuerDNS01ProviderCloudDNS_To_acme_ACMEIssuerDNS01ProviderCloudDNS(in *ACMEIssuerDNS01ProviderCloudDNS, out *acme.ACMEIssuerDNS01ProviderCloudDNS, s conversion.Scope) error { - return autoConvert_v1alpha3_ACMEIssuerDNS01ProviderCloudDNS_To_acme_ACMEIssuerDNS01ProviderCloudDNS(in, out, s) -} - -func autoConvert_acme_ACMEIssuerDNS01ProviderCloudDNS_To_v1alpha3_ACMEIssuerDNS01ProviderCloudDNS(in *acme.ACMEIssuerDNS01ProviderCloudDNS, out *ACMEIssuerDNS01ProviderCloudDNS, s conversion.Scope) error { - if in.ServiceAccount != nil { - in, out := &in.ServiceAccount, &out.ServiceAccount - *out = new(apismetav1.SecretKeySelector) - if err := metav1.Convert_meta_SecretKeySelector_To_v1_SecretKeySelector(*in, *out, s); err != nil { - return err - } - } else { - out.ServiceAccount = nil - } - out.Project = in.Project - out.HostedZoneName = in.HostedZoneName - return nil -} - -// Convert_acme_ACMEIssuerDNS01ProviderCloudDNS_To_v1alpha3_ACMEIssuerDNS01ProviderCloudDNS is an autogenerated conversion function. -func Convert_acme_ACMEIssuerDNS01ProviderCloudDNS_To_v1alpha3_ACMEIssuerDNS01ProviderCloudDNS(in *acme.ACMEIssuerDNS01ProviderCloudDNS, out *ACMEIssuerDNS01ProviderCloudDNS, s conversion.Scope) error { - return autoConvert_acme_ACMEIssuerDNS01ProviderCloudDNS_To_v1alpha3_ACMEIssuerDNS01ProviderCloudDNS(in, out, s) -} - -func autoConvert_v1alpha3_ACMEIssuerDNS01ProviderCloudflare_To_acme_ACMEIssuerDNS01ProviderCloudflare(in *ACMEIssuerDNS01ProviderCloudflare, out *acme.ACMEIssuerDNS01ProviderCloudflare, s conversion.Scope) error { - out.Email = in.Email - if in.APIKey != nil { - in, out := &in.APIKey, &out.APIKey - *out = new(meta.SecretKeySelector) - if err := metav1.Convert_v1_SecretKeySelector_To_meta_SecretKeySelector(*in, *out, s); err != nil { - return err - } - } else { - out.APIKey = nil - } - if in.APIToken != nil { - in, out := &in.APIToken, &out.APIToken - *out = new(meta.SecretKeySelector) - if err := metav1.Convert_v1_SecretKeySelector_To_meta_SecretKeySelector(*in, *out, s); err != nil { - return err - } - } else { - out.APIToken = nil - } - return nil -} - -// Convert_v1alpha3_ACMEIssuerDNS01ProviderCloudflare_To_acme_ACMEIssuerDNS01ProviderCloudflare is an autogenerated conversion function. -func Convert_v1alpha3_ACMEIssuerDNS01ProviderCloudflare_To_acme_ACMEIssuerDNS01ProviderCloudflare(in *ACMEIssuerDNS01ProviderCloudflare, out *acme.ACMEIssuerDNS01ProviderCloudflare, s conversion.Scope) error { - return autoConvert_v1alpha3_ACMEIssuerDNS01ProviderCloudflare_To_acme_ACMEIssuerDNS01ProviderCloudflare(in, out, s) -} - -func autoConvert_acme_ACMEIssuerDNS01ProviderCloudflare_To_v1alpha3_ACMEIssuerDNS01ProviderCloudflare(in *acme.ACMEIssuerDNS01ProviderCloudflare, out *ACMEIssuerDNS01ProviderCloudflare, s conversion.Scope) error { - out.Email = in.Email - if in.APIKey != nil { - in, out := &in.APIKey, &out.APIKey - *out = new(apismetav1.SecretKeySelector) - if err := metav1.Convert_meta_SecretKeySelector_To_v1_SecretKeySelector(*in, *out, s); err != nil { - return err - } - } else { - out.APIKey = nil - } - if in.APIToken != nil { - in, out := &in.APIToken, &out.APIToken - *out = new(apismetav1.SecretKeySelector) - if err := metav1.Convert_meta_SecretKeySelector_To_v1_SecretKeySelector(*in, *out, s); err != nil { - return err - } - } else { - out.APIToken = nil - } - return nil -} - -// Convert_acme_ACMEIssuerDNS01ProviderCloudflare_To_v1alpha3_ACMEIssuerDNS01ProviderCloudflare is an autogenerated conversion function. -func Convert_acme_ACMEIssuerDNS01ProviderCloudflare_To_v1alpha3_ACMEIssuerDNS01ProviderCloudflare(in *acme.ACMEIssuerDNS01ProviderCloudflare, out *ACMEIssuerDNS01ProviderCloudflare, s conversion.Scope) error { - return autoConvert_acme_ACMEIssuerDNS01ProviderCloudflare_To_v1alpha3_ACMEIssuerDNS01ProviderCloudflare(in, out, s) -} - -func autoConvert_v1alpha3_ACMEIssuerDNS01ProviderDigitalOcean_To_acme_ACMEIssuerDNS01ProviderDigitalOcean(in *ACMEIssuerDNS01ProviderDigitalOcean, out *acme.ACMEIssuerDNS01ProviderDigitalOcean, s conversion.Scope) error { - if err := metav1.Convert_v1_SecretKeySelector_To_meta_SecretKeySelector(&in.Token, &out.Token, s); err != nil { - return err - } - return nil -} - -// Convert_v1alpha3_ACMEIssuerDNS01ProviderDigitalOcean_To_acme_ACMEIssuerDNS01ProviderDigitalOcean is an autogenerated conversion function. -func Convert_v1alpha3_ACMEIssuerDNS01ProviderDigitalOcean_To_acme_ACMEIssuerDNS01ProviderDigitalOcean(in *ACMEIssuerDNS01ProviderDigitalOcean, out *acme.ACMEIssuerDNS01ProviderDigitalOcean, s conversion.Scope) error { - return autoConvert_v1alpha3_ACMEIssuerDNS01ProviderDigitalOcean_To_acme_ACMEIssuerDNS01ProviderDigitalOcean(in, out, s) -} - -func autoConvert_acme_ACMEIssuerDNS01ProviderDigitalOcean_To_v1alpha3_ACMEIssuerDNS01ProviderDigitalOcean(in *acme.ACMEIssuerDNS01ProviderDigitalOcean, out *ACMEIssuerDNS01ProviderDigitalOcean, s conversion.Scope) error { - if err := metav1.Convert_meta_SecretKeySelector_To_v1_SecretKeySelector(&in.Token, &out.Token, s); err != nil { - return err - } - return nil -} - -// Convert_acme_ACMEIssuerDNS01ProviderDigitalOcean_To_v1alpha3_ACMEIssuerDNS01ProviderDigitalOcean is an autogenerated conversion function. -func Convert_acme_ACMEIssuerDNS01ProviderDigitalOcean_To_v1alpha3_ACMEIssuerDNS01ProviderDigitalOcean(in *acme.ACMEIssuerDNS01ProviderDigitalOcean, out *ACMEIssuerDNS01ProviderDigitalOcean, s conversion.Scope) error { - return autoConvert_acme_ACMEIssuerDNS01ProviderDigitalOcean_To_v1alpha3_ACMEIssuerDNS01ProviderDigitalOcean(in, out, s) -} - -func autoConvert_v1alpha3_ACMEIssuerDNS01ProviderRFC2136_To_acme_ACMEIssuerDNS01ProviderRFC2136(in *ACMEIssuerDNS01ProviderRFC2136, out *acme.ACMEIssuerDNS01ProviderRFC2136, s conversion.Scope) error { - out.Nameserver = in.Nameserver - if err := metav1.Convert_v1_SecretKeySelector_To_meta_SecretKeySelector(&in.TSIGSecret, &out.TSIGSecret, s); err != nil { - return err - } - out.TSIGKeyName = in.TSIGKeyName - out.TSIGAlgorithm = in.TSIGAlgorithm - return nil -} - -// Convert_v1alpha3_ACMEIssuerDNS01ProviderRFC2136_To_acme_ACMEIssuerDNS01ProviderRFC2136 is an autogenerated conversion function. -func Convert_v1alpha3_ACMEIssuerDNS01ProviderRFC2136_To_acme_ACMEIssuerDNS01ProviderRFC2136(in *ACMEIssuerDNS01ProviderRFC2136, out *acme.ACMEIssuerDNS01ProviderRFC2136, s conversion.Scope) error { - return autoConvert_v1alpha3_ACMEIssuerDNS01ProviderRFC2136_To_acme_ACMEIssuerDNS01ProviderRFC2136(in, out, s) -} - -func autoConvert_acme_ACMEIssuerDNS01ProviderRFC2136_To_v1alpha3_ACMEIssuerDNS01ProviderRFC2136(in *acme.ACMEIssuerDNS01ProviderRFC2136, out *ACMEIssuerDNS01ProviderRFC2136, s conversion.Scope) error { - out.Nameserver = in.Nameserver - if err := metav1.Convert_meta_SecretKeySelector_To_v1_SecretKeySelector(&in.TSIGSecret, &out.TSIGSecret, s); err != nil { - return err - } - out.TSIGKeyName = in.TSIGKeyName - out.TSIGAlgorithm = in.TSIGAlgorithm - return nil -} - -// Convert_acme_ACMEIssuerDNS01ProviderRFC2136_To_v1alpha3_ACMEIssuerDNS01ProviderRFC2136 is an autogenerated conversion function. -func Convert_acme_ACMEIssuerDNS01ProviderRFC2136_To_v1alpha3_ACMEIssuerDNS01ProviderRFC2136(in *acme.ACMEIssuerDNS01ProviderRFC2136, out *ACMEIssuerDNS01ProviderRFC2136, s conversion.Scope) error { - return autoConvert_acme_ACMEIssuerDNS01ProviderRFC2136_To_v1alpha3_ACMEIssuerDNS01ProviderRFC2136(in, out, s) -} - -func autoConvert_v1alpha3_ACMEIssuerDNS01ProviderRoute53_To_acme_ACMEIssuerDNS01ProviderRoute53(in *ACMEIssuerDNS01ProviderRoute53, out *acme.ACMEIssuerDNS01ProviderRoute53, s conversion.Scope) error { - out.AccessKeyID = in.AccessKeyID - if in.SecretAccessKeyID != nil { - in, out := &in.SecretAccessKeyID, &out.SecretAccessKeyID - *out = new(meta.SecretKeySelector) - if err := metav1.Convert_v1_SecretKeySelector_To_meta_SecretKeySelector(*in, *out, s); err != nil { - return err - } - } else { - out.SecretAccessKeyID = nil - } - if err := metav1.Convert_v1_SecretKeySelector_To_meta_SecretKeySelector(&in.SecretAccessKey, &out.SecretAccessKey, s); err != nil { - return err - } - out.Role = in.Role - out.HostedZoneID = in.HostedZoneID - out.Region = in.Region - return nil -} - -// Convert_v1alpha3_ACMEIssuerDNS01ProviderRoute53_To_acme_ACMEIssuerDNS01ProviderRoute53 is an autogenerated conversion function. -func Convert_v1alpha3_ACMEIssuerDNS01ProviderRoute53_To_acme_ACMEIssuerDNS01ProviderRoute53(in *ACMEIssuerDNS01ProviderRoute53, out *acme.ACMEIssuerDNS01ProviderRoute53, s conversion.Scope) error { - return autoConvert_v1alpha3_ACMEIssuerDNS01ProviderRoute53_To_acme_ACMEIssuerDNS01ProviderRoute53(in, out, s) -} - -func autoConvert_acme_ACMEIssuerDNS01ProviderRoute53_To_v1alpha3_ACMEIssuerDNS01ProviderRoute53(in *acme.ACMEIssuerDNS01ProviderRoute53, out *ACMEIssuerDNS01ProviderRoute53, s conversion.Scope) error { - out.AccessKeyID = in.AccessKeyID - if in.SecretAccessKeyID != nil { - in, out := &in.SecretAccessKeyID, &out.SecretAccessKeyID - *out = new(apismetav1.SecretKeySelector) - if err := metav1.Convert_meta_SecretKeySelector_To_v1_SecretKeySelector(*in, *out, s); err != nil { - return err - } - } else { - out.SecretAccessKeyID = nil - } - if err := metav1.Convert_meta_SecretKeySelector_To_v1_SecretKeySelector(&in.SecretAccessKey, &out.SecretAccessKey, s); err != nil { - return err - } - out.Role = in.Role - out.HostedZoneID = in.HostedZoneID - out.Region = in.Region - return nil -} - -// Convert_acme_ACMEIssuerDNS01ProviderRoute53_To_v1alpha3_ACMEIssuerDNS01ProviderRoute53 is an autogenerated conversion function. -func Convert_acme_ACMEIssuerDNS01ProviderRoute53_To_v1alpha3_ACMEIssuerDNS01ProviderRoute53(in *acme.ACMEIssuerDNS01ProviderRoute53, out *ACMEIssuerDNS01ProviderRoute53, s conversion.Scope) error { - return autoConvert_acme_ACMEIssuerDNS01ProviderRoute53_To_v1alpha3_ACMEIssuerDNS01ProviderRoute53(in, out, s) -} - -func autoConvert_v1alpha3_ACMEIssuerDNS01ProviderWebhook_To_acme_ACMEIssuerDNS01ProviderWebhook(in *ACMEIssuerDNS01ProviderWebhook, out *acme.ACMEIssuerDNS01ProviderWebhook, s conversion.Scope) error { - out.GroupName = in.GroupName - out.SolverName = in.SolverName - out.Config = (*apiextensionsv1.JSON)(unsafe.Pointer(in.Config)) - return nil -} - -// Convert_v1alpha3_ACMEIssuerDNS01ProviderWebhook_To_acme_ACMEIssuerDNS01ProviderWebhook is an autogenerated conversion function. -func Convert_v1alpha3_ACMEIssuerDNS01ProviderWebhook_To_acme_ACMEIssuerDNS01ProviderWebhook(in *ACMEIssuerDNS01ProviderWebhook, out *acme.ACMEIssuerDNS01ProviderWebhook, s conversion.Scope) error { - return autoConvert_v1alpha3_ACMEIssuerDNS01ProviderWebhook_To_acme_ACMEIssuerDNS01ProviderWebhook(in, out, s) -} - -func autoConvert_acme_ACMEIssuerDNS01ProviderWebhook_To_v1alpha3_ACMEIssuerDNS01ProviderWebhook(in *acme.ACMEIssuerDNS01ProviderWebhook, out *ACMEIssuerDNS01ProviderWebhook, s conversion.Scope) error { - out.GroupName = in.GroupName - out.SolverName = in.SolverName - out.Config = (*apiextensionsv1.JSON)(unsafe.Pointer(in.Config)) - return nil -} - -// Convert_acme_ACMEIssuerDNS01ProviderWebhook_To_v1alpha3_ACMEIssuerDNS01ProviderWebhook is an autogenerated conversion function. -func Convert_acme_ACMEIssuerDNS01ProviderWebhook_To_v1alpha3_ACMEIssuerDNS01ProviderWebhook(in *acme.ACMEIssuerDNS01ProviderWebhook, out *ACMEIssuerDNS01ProviderWebhook, s conversion.Scope) error { - return autoConvert_acme_ACMEIssuerDNS01ProviderWebhook_To_v1alpha3_ACMEIssuerDNS01ProviderWebhook(in, out, s) -} - -func autoConvert_v1alpha3_ACMEIssuerStatus_To_acme_ACMEIssuerStatus(in *ACMEIssuerStatus, out *acme.ACMEIssuerStatus, s conversion.Scope) error { - out.URI = in.URI - out.LastRegisteredEmail = in.LastRegisteredEmail - return nil -} - -// Convert_v1alpha3_ACMEIssuerStatus_To_acme_ACMEIssuerStatus is an autogenerated conversion function. -func Convert_v1alpha3_ACMEIssuerStatus_To_acme_ACMEIssuerStatus(in *ACMEIssuerStatus, out *acme.ACMEIssuerStatus, s conversion.Scope) error { - return autoConvert_v1alpha3_ACMEIssuerStatus_To_acme_ACMEIssuerStatus(in, out, s) -} - -func autoConvert_acme_ACMEIssuerStatus_To_v1alpha3_ACMEIssuerStatus(in *acme.ACMEIssuerStatus, out *ACMEIssuerStatus, s conversion.Scope) error { - out.URI = in.URI - out.LastRegisteredEmail = in.LastRegisteredEmail - return nil -} - -// Convert_acme_ACMEIssuerStatus_To_v1alpha3_ACMEIssuerStatus is an autogenerated conversion function. -func Convert_acme_ACMEIssuerStatus_To_v1alpha3_ACMEIssuerStatus(in *acme.ACMEIssuerStatus, out *ACMEIssuerStatus, s conversion.Scope) error { - return autoConvert_acme_ACMEIssuerStatus_To_v1alpha3_ACMEIssuerStatus(in, out, s) -} - -func autoConvert_v1alpha3_AzureManagedIdentity_To_acme_AzureManagedIdentity(in *AzureManagedIdentity, out *acme.AzureManagedIdentity, s conversion.Scope) error { - out.ClientID = in.ClientID - out.ResourceID = in.ResourceID - return nil -} - -// Convert_v1alpha3_AzureManagedIdentity_To_acme_AzureManagedIdentity is an autogenerated conversion function. -func Convert_v1alpha3_AzureManagedIdentity_To_acme_AzureManagedIdentity(in *AzureManagedIdentity, out *acme.AzureManagedIdentity, s conversion.Scope) error { - return autoConvert_v1alpha3_AzureManagedIdentity_To_acme_AzureManagedIdentity(in, out, s) -} - -func autoConvert_acme_AzureManagedIdentity_To_v1alpha3_AzureManagedIdentity(in *acme.AzureManagedIdentity, out *AzureManagedIdentity, s conversion.Scope) error { - out.ClientID = in.ClientID - out.ResourceID = in.ResourceID - return nil -} - -// Convert_acme_AzureManagedIdentity_To_v1alpha3_AzureManagedIdentity is an autogenerated conversion function. -func Convert_acme_AzureManagedIdentity_To_v1alpha3_AzureManagedIdentity(in *acme.AzureManagedIdentity, out *AzureManagedIdentity, s conversion.Scope) error { - return autoConvert_acme_AzureManagedIdentity_To_v1alpha3_AzureManagedIdentity(in, out, s) -} - -func autoConvert_v1alpha3_CertificateDNSNameSelector_To_acme_CertificateDNSNameSelector(in *CertificateDNSNameSelector, out *acme.CertificateDNSNameSelector, s conversion.Scope) error { - out.MatchLabels = *(*map[string]string)(unsafe.Pointer(&in.MatchLabels)) - out.DNSNames = *(*[]string)(unsafe.Pointer(&in.DNSNames)) - out.DNSZones = *(*[]string)(unsafe.Pointer(&in.DNSZones)) - return nil -} - -// Convert_v1alpha3_CertificateDNSNameSelector_To_acme_CertificateDNSNameSelector is an autogenerated conversion function. -func Convert_v1alpha3_CertificateDNSNameSelector_To_acme_CertificateDNSNameSelector(in *CertificateDNSNameSelector, out *acme.CertificateDNSNameSelector, s conversion.Scope) error { - return autoConvert_v1alpha3_CertificateDNSNameSelector_To_acme_CertificateDNSNameSelector(in, out, s) -} - -func autoConvert_acme_CertificateDNSNameSelector_To_v1alpha3_CertificateDNSNameSelector(in *acme.CertificateDNSNameSelector, out *CertificateDNSNameSelector, s conversion.Scope) error { - out.MatchLabels = *(*map[string]string)(unsafe.Pointer(&in.MatchLabels)) - out.DNSNames = *(*[]string)(unsafe.Pointer(&in.DNSNames)) - out.DNSZones = *(*[]string)(unsafe.Pointer(&in.DNSZones)) - return nil -} - -// Convert_acme_CertificateDNSNameSelector_To_v1alpha3_CertificateDNSNameSelector is an autogenerated conversion function. -func Convert_acme_CertificateDNSNameSelector_To_v1alpha3_CertificateDNSNameSelector(in *acme.CertificateDNSNameSelector, out *CertificateDNSNameSelector, s conversion.Scope) error { - return autoConvert_acme_CertificateDNSNameSelector_To_v1alpha3_CertificateDNSNameSelector(in, out, s) -} - -func autoConvert_v1alpha3_Challenge_To_acme_Challenge(in *Challenge, out *acme.Challenge, s conversion.Scope) error { - out.ObjectMeta = in.ObjectMeta - if err := Convert_v1alpha3_ChallengeSpec_To_acme_ChallengeSpec(&in.Spec, &out.Spec, s); err != nil { - return err - } - if err := Convert_v1alpha3_ChallengeStatus_To_acme_ChallengeStatus(&in.Status, &out.Status, s); err != nil { - return err - } - return nil -} - -// Convert_v1alpha3_Challenge_To_acme_Challenge is an autogenerated conversion function. -func Convert_v1alpha3_Challenge_To_acme_Challenge(in *Challenge, out *acme.Challenge, s conversion.Scope) error { - return autoConvert_v1alpha3_Challenge_To_acme_Challenge(in, out, s) -} - -func autoConvert_acme_Challenge_To_v1alpha3_Challenge(in *acme.Challenge, out *Challenge, s conversion.Scope) error { - out.ObjectMeta = in.ObjectMeta - if err := Convert_acme_ChallengeSpec_To_v1alpha3_ChallengeSpec(&in.Spec, &out.Spec, s); err != nil { - return err - } - if err := Convert_acme_ChallengeStatus_To_v1alpha3_ChallengeStatus(&in.Status, &out.Status, s); err != nil { - return err - } - return nil -} - -// Convert_acme_Challenge_To_v1alpha3_Challenge is an autogenerated conversion function. -func Convert_acme_Challenge_To_v1alpha3_Challenge(in *acme.Challenge, out *Challenge, s conversion.Scope) error { - return autoConvert_acme_Challenge_To_v1alpha3_Challenge(in, out, s) -} - -func autoConvert_v1alpha3_ChallengeList_To_acme_ChallengeList(in *ChallengeList, out *acme.ChallengeList, s conversion.Scope) error { - out.ListMeta = in.ListMeta - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]acme.Challenge, len(*in)) - for i := range *in { - if err := Convert_v1alpha3_Challenge_To_acme_Challenge(&(*in)[i], &(*out)[i], s); err != nil { - return err - } - } - } else { - out.Items = nil - } - return nil -} - -// Convert_v1alpha3_ChallengeList_To_acme_ChallengeList is an autogenerated conversion function. -func Convert_v1alpha3_ChallengeList_To_acme_ChallengeList(in *ChallengeList, out *acme.ChallengeList, s conversion.Scope) error { - return autoConvert_v1alpha3_ChallengeList_To_acme_ChallengeList(in, out, s) -} - -func autoConvert_acme_ChallengeList_To_v1alpha3_ChallengeList(in *acme.ChallengeList, out *ChallengeList, s conversion.Scope) error { - out.ListMeta = in.ListMeta - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]Challenge, len(*in)) - for i := range *in { - if err := Convert_acme_Challenge_To_v1alpha3_Challenge(&(*in)[i], &(*out)[i], s); err != nil { - return err - } - } - } else { - out.Items = nil - } - return nil -} - -// Convert_acme_ChallengeList_To_v1alpha3_ChallengeList is an autogenerated conversion function. -func Convert_acme_ChallengeList_To_v1alpha3_ChallengeList(in *acme.ChallengeList, out *ChallengeList, s conversion.Scope) error { - return autoConvert_acme_ChallengeList_To_v1alpha3_ChallengeList(in, out, s) -} - -func autoConvert_v1alpha3_ChallengeSpec_To_acme_ChallengeSpec(in *ChallengeSpec, out *acme.ChallengeSpec, s conversion.Scope) error { - out.URL = in.URL - // WARNING: in.AuthzURL requires manual conversion: does not exist in peer-type - out.DNSName = in.DNSName - out.Wildcard = in.Wildcard - out.Type = acme.ACMEChallengeType(in.Type) - out.Token = in.Token - out.Key = in.Key - if err := Convert_v1alpha3_ACMEChallengeSolver_To_acme_ACMEChallengeSolver(&in.Solver, &out.Solver, s); err != nil { - return err - } - if err := metav1.Convert_v1_ObjectReference_To_meta_ObjectReference(&in.IssuerRef, &out.IssuerRef, s); err != nil { - return err - } - return nil -} - -func autoConvert_acme_ChallengeSpec_To_v1alpha3_ChallengeSpec(in *acme.ChallengeSpec, out *ChallengeSpec, s conversion.Scope) error { - out.URL = in.URL - // WARNING: in.AuthorizationURL requires manual conversion: does not exist in peer-type - out.DNSName = in.DNSName - out.Wildcard = in.Wildcard - out.Type = ACMEChallengeType(in.Type) - out.Token = in.Token - out.Key = in.Key - if err := Convert_acme_ACMEChallengeSolver_To_v1alpha3_ACMEChallengeSolver(&in.Solver, &out.Solver, s); err != nil { - return err - } - if err := metav1.Convert_meta_ObjectReference_To_v1_ObjectReference(&in.IssuerRef, &out.IssuerRef, s); err != nil { - return err - } - return nil -} - -func autoConvert_v1alpha3_ChallengeStatus_To_acme_ChallengeStatus(in *ChallengeStatus, out *acme.ChallengeStatus, s conversion.Scope) error { - out.Processing = in.Processing - out.Presented = in.Presented - out.Reason = in.Reason - out.State = acme.State(in.State) - return nil -} - -// Convert_v1alpha3_ChallengeStatus_To_acme_ChallengeStatus is an autogenerated conversion function. -func Convert_v1alpha3_ChallengeStatus_To_acme_ChallengeStatus(in *ChallengeStatus, out *acme.ChallengeStatus, s conversion.Scope) error { - return autoConvert_v1alpha3_ChallengeStatus_To_acme_ChallengeStatus(in, out, s) -} - -func autoConvert_acme_ChallengeStatus_To_v1alpha3_ChallengeStatus(in *acme.ChallengeStatus, out *ChallengeStatus, s conversion.Scope) error { - out.Processing = in.Processing - out.Presented = in.Presented - out.Reason = in.Reason - out.State = State(in.State) - return nil -} - -// Convert_acme_ChallengeStatus_To_v1alpha3_ChallengeStatus is an autogenerated conversion function. -func Convert_acme_ChallengeStatus_To_v1alpha3_ChallengeStatus(in *acme.ChallengeStatus, out *ChallengeStatus, s conversion.Scope) error { - return autoConvert_acme_ChallengeStatus_To_v1alpha3_ChallengeStatus(in, out, s) -} - -func autoConvert_v1alpha3_Order_To_acme_Order(in *Order, out *acme.Order, s conversion.Scope) error { - out.ObjectMeta = in.ObjectMeta - if err := Convert_v1alpha3_OrderSpec_To_acme_OrderSpec(&in.Spec, &out.Spec, s); err != nil { - return err - } - if err := Convert_v1alpha3_OrderStatus_To_acme_OrderStatus(&in.Status, &out.Status, s); err != nil { - return err - } - return nil -} - -// Convert_v1alpha3_Order_To_acme_Order is an autogenerated conversion function. -func Convert_v1alpha3_Order_To_acme_Order(in *Order, out *acme.Order, s conversion.Scope) error { - return autoConvert_v1alpha3_Order_To_acme_Order(in, out, s) -} - -func autoConvert_acme_Order_To_v1alpha3_Order(in *acme.Order, out *Order, s conversion.Scope) error { - out.ObjectMeta = in.ObjectMeta - if err := Convert_acme_OrderSpec_To_v1alpha3_OrderSpec(&in.Spec, &out.Spec, s); err != nil { - return err - } - if err := Convert_acme_OrderStatus_To_v1alpha3_OrderStatus(&in.Status, &out.Status, s); err != nil { - return err - } - return nil -} - -// Convert_acme_Order_To_v1alpha3_Order is an autogenerated conversion function. -func Convert_acme_Order_To_v1alpha3_Order(in *acme.Order, out *Order, s conversion.Scope) error { - return autoConvert_acme_Order_To_v1alpha3_Order(in, out, s) -} - -func autoConvert_v1alpha3_OrderList_To_acme_OrderList(in *OrderList, out *acme.OrderList, s conversion.Scope) error { - out.ListMeta = in.ListMeta - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]acme.Order, len(*in)) - for i := range *in { - if err := Convert_v1alpha3_Order_To_acme_Order(&(*in)[i], &(*out)[i], s); err != nil { - return err - } - } - } else { - out.Items = nil - } - return nil -} - -// Convert_v1alpha3_OrderList_To_acme_OrderList is an autogenerated conversion function. -func Convert_v1alpha3_OrderList_To_acme_OrderList(in *OrderList, out *acme.OrderList, s conversion.Scope) error { - return autoConvert_v1alpha3_OrderList_To_acme_OrderList(in, out, s) -} - -func autoConvert_acme_OrderList_To_v1alpha3_OrderList(in *acme.OrderList, out *OrderList, s conversion.Scope) error { - out.ListMeta = in.ListMeta - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]Order, len(*in)) - for i := range *in { - if err := Convert_acme_Order_To_v1alpha3_Order(&(*in)[i], &(*out)[i], s); err != nil { - return err - } - } - } else { - out.Items = nil - } - return nil -} - -// Convert_acme_OrderList_To_v1alpha3_OrderList is an autogenerated conversion function. -func Convert_acme_OrderList_To_v1alpha3_OrderList(in *acme.OrderList, out *OrderList, s conversion.Scope) error { - return autoConvert_acme_OrderList_To_v1alpha3_OrderList(in, out, s) -} - -func autoConvert_v1alpha3_OrderSpec_To_acme_OrderSpec(in *OrderSpec, out *acme.OrderSpec, s conversion.Scope) error { - // WARNING: in.CSR requires manual conversion: does not exist in peer-type - if err := metav1.Convert_v1_ObjectReference_To_meta_ObjectReference(&in.IssuerRef, &out.IssuerRef, s); err != nil { - return err - } - out.CommonName = in.CommonName - out.DNSNames = *(*[]string)(unsafe.Pointer(&in.DNSNames)) - out.IPAddresses = *(*[]string)(unsafe.Pointer(&in.IPAddresses)) - out.Duration = (*pkgapismetav1.Duration)(unsafe.Pointer(in.Duration)) - return nil -} - -func autoConvert_acme_OrderSpec_To_v1alpha3_OrderSpec(in *acme.OrderSpec, out *OrderSpec, s conversion.Scope) error { - // WARNING: in.Request requires manual conversion: does not exist in peer-type - if err := metav1.Convert_meta_ObjectReference_To_v1_ObjectReference(&in.IssuerRef, &out.IssuerRef, s); err != nil { - return err - } - out.CommonName = in.CommonName - out.DNSNames = *(*[]string)(unsafe.Pointer(&in.DNSNames)) - out.IPAddresses = *(*[]string)(unsafe.Pointer(&in.IPAddresses)) - out.Duration = (*pkgapismetav1.Duration)(unsafe.Pointer(in.Duration)) - return nil -} - -func autoConvert_v1alpha3_OrderStatus_To_acme_OrderStatus(in *OrderStatus, out *acme.OrderStatus, s conversion.Scope) error { - out.URL = in.URL - out.FinalizeURL = in.FinalizeURL - out.Authorizations = *(*[]acme.ACMEAuthorization)(unsafe.Pointer(&in.Authorizations)) - out.Certificate = *(*[]byte)(unsafe.Pointer(&in.Certificate)) - out.State = acme.State(in.State) - out.Reason = in.Reason - out.FailureTime = (*pkgapismetav1.Time)(unsafe.Pointer(in.FailureTime)) - return nil -} - -// Convert_v1alpha3_OrderStatus_To_acme_OrderStatus is an autogenerated conversion function. -func Convert_v1alpha3_OrderStatus_To_acme_OrderStatus(in *OrderStatus, out *acme.OrderStatus, s conversion.Scope) error { - return autoConvert_v1alpha3_OrderStatus_To_acme_OrderStatus(in, out, s) -} - -func autoConvert_acme_OrderStatus_To_v1alpha3_OrderStatus(in *acme.OrderStatus, out *OrderStatus, s conversion.Scope) error { - out.URL = in.URL - out.FinalizeURL = in.FinalizeURL - out.Certificate = *(*[]byte)(unsafe.Pointer(&in.Certificate)) - out.State = State(in.State) - out.Reason = in.Reason - out.Authorizations = *(*[]ACMEAuthorization)(unsafe.Pointer(&in.Authorizations)) - out.FailureTime = (*pkgapismetav1.Time)(unsafe.Pointer(in.FailureTime)) - return nil -} - -// Convert_acme_OrderStatus_To_v1alpha3_OrderStatus is an autogenerated conversion function. -func Convert_acme_OrderStatus_To_v1alpha3_OrderStatus(in *acme.OrderStatus, out *OrderStatus, s conversion.Scope) error { - return autoConvert_acme_OrderStatus_To_v1alpha3_OrderStatus(in, out, s) -} diff --git a/internal/apis/acme/v1alpha3/zz_generated.deepcopy.go b/internal/apis/acme/v1alpha3/zz_generated.deepcopy.go deleted file mode 100644 index 8c2cefc084a..00000000000 --- a/internal/apis/acme/v1alpha3/zz_generated.deepcopy.go +++ /dev/null @@ -1,904 +0,0 @@ -//go:build !ignore_autogenerated -// +build !ignore_autogenerated - -/* -Copyright 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. -*/ - -// Code generated by deepcopy-gen. DO NOT EDIT. - -package v1alpha3 - -import ( - metav1 "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" - v1 "k8s.io/api/core/v1" - apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" - apismetav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - runtime "k8s.io/apimachinery/pkg/runtime" - v1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" -) - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ACMEAuthorization) DeepCopyInto(out *ACMEAuthorization) { - *out = *in - if in.Wildcard != nil { - in, out := &in.Wildcard, &out.Wildcard - *out = new(bool) - **out = **in - } - if in.Challenges != nil { - in, out := &in.Challenges, &out.Challenges - *out = make([]ACMEChallenge, len(*in)) - copy(*out, *in) - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ACMEAuthorization. -func (in *ACMEAuthorization) DeepCopy() *ACMEAuthorization { - if in == nil { - return nil - } - out := new(ACMEAuthorization) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ACMEChallenge) DeepCopyInto(out *ACMEChallenge) { - *out = *in - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ACMEChallenge. -func (in *ACMEChallenge) DeepCopy() *ACMEChallenge { - if in == nil { - return nil - } - out := new(ACMEChallenge) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ACMEChallengeSolver) DeepCopyInto(out *ACMEChallengeSolver) { - *out = *in - if in.Selector != nil { - in, out := &in.Selector, &out.Selector - *out = new(CertificateDNSNameSelector) - (*in).DeepCopyInto(*out) - } - if in.HTTP01 != nil { - in, out := &in.HTTP01, &out.HTTP01 - *out = new(ACMEChallengeSolverHTTP01) - (*in).DeepCopyInto(*out) - } - if in.DNS01 != nil { - in, out := &in.DNS01, &out.DNS01 - *out = new(ACMEChallengeSolverDNS01) - (*in).DeepCopyInto(*out) - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ACMEChallengeSolver. -func (in *ACMEChallengeSolver) DeepCopy() *ACMEChallengeSolver { - if in == nil { - return nil - } - out := new(ACMEChallengeSolver) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ACMEChallengeSolverDNS01) DeepCopyInto(out *ACMEChallengeSolverDNS01) { - *out = *in - if in.Akamai != nil { - in, out := &in.Akamai, &out.Akamai - *out = new(ACMEIssuerDNS01ProviderAkamai) - **out = **in - } - if in.CloudDNS != nil { - in, out := &in.CloudDNS, &out.CloudDNS - *out = new(ACMEIssuerDNS01ProviderCloudDNS) - (*in).DeepCopyInto(*out) - } - if in.Cloudflare != nil { - in, out := &in.Cloudflare, &out.Cloudflare - *out = new(ACMEIssuerDNS01ProviderCloudflare) - (*in).DeepCopyInto(*out) - } - if in.Route53 != nil { - in, out := &in.Route53, &out.Route53 - *out = new(ACMEIssuerDNS01ProviderRoute53) - (*in).DeepCopyInto(*out) - } - if in.AzureDNS != nil { - in, out := &in.AzureDNS, &out.AzureDNS - *out = new(ACMEIssuerDNS01ProviderAzureDNS) - (*in).DeepCopyInto(*out) - } - if in.DigitalOcean != nil { - in, out := &in.DigitalOcean, &out.DigitalOcean - *out = new(ACMEIssuerDNS01ProviderDigitalOcean) - **out = **in - } - if in.AcmeDNS != nil { - in, out := &in.AcmeDNS, &out.AcmeDNS - *out = new(ACMEIssuerDNS01ProviderAcmeDNS) - **out = **in - } - if in.RFC2136 != nil { - in, out := &in.RFC2136, &out.RFC2136 - *out = new(ACMEIssuerDNS01ProviderRFC2136) - **out = **in - } - if in.Webhook != nil { - in, out := &in.Webhook, &out.Webhook - *out = new(ACMEIssuerDNS01ProviderWebhook) - (*in).DeepCopyInto(*out) - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ACMEChallengeSolverDNS01. -func (in *ACMEChallengeSolverDNS01) DeepCopy() *ACMEChallengeSolverDNS01 { - if in == nil { - return nil - } - out := new(ACMEChallengeSolverDNS01) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ACMEChallengeSolverHTTP01) DeepCopyInto(out *ACMEChallengeSolverHTTP01) { - *out = *in - if in.Ingress != nil { - in, out := &in.Ingress, &out.Ingress - *out = new(ACMEChallengeSolverHTTP01Ingress) - (*in).DeepCopyInto(*out) - } - if in.GatewayHTTPRoute != nil { - in, out := &in.GatewayHTTPRoute, &out.GatewayHTTPRoute - *out = new(ACMEChallengeSolverHTTP01GatewayHTTPRoute) - (*in).DeepCopyInto(*out) - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ACMEChallengeSolverHTTP01. -func (in *ACMEChallengeSolverHTTP01) DeepCopy() *ACMEChallengeSolverHTTP01 { - if in == nil { - return nil - } - out := new(ACMEChallengeSolverHTTP01) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ACMEChallengeSolverHTTP01GatewayHTTPRoute) DeepCopyInto(out *ACMEChallengeSolverHTTP01GatewayHTTPRoute) { - *out = *in - if in.Labels != nil { - in, out := &in.Labels, &out.Labels - *out = make(map[string]string, len(*in)) - for key, val := range *in { - (*out)[key] = val - } - } - if in.ParentRefs != nil { - in, out := &in.ParentRefs, &out.ParentRefs - *out = make([]v1alpha2.ParentReference, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ACMEChallengeSolverHTTP01GatewayHTTPRoute. -func (in *ACMEChallengeSolverHTTP01GatewayHTTPRoute) DeepCopy() *ACMEChallengeSolverHTTP01GatewayHTTPRoute { - if in == nil { - return nil - } - out := new(ACMEChallengeSolverHTTP01GatewayHTTPRoute) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ACMEChallengeSolverHTTP01Ingress) DeepCopyInto(out *ACMEChallengeSolverHTTP01Ingress) { - *out = *in - if in.Class != nil { - in, out := &in.Class, &out.Class - *out = new(string) - **out = **in - } - if in.PodTemplate != nil { - in, out := &in.PodTemplate, &out.PodTemplate - *out = new(ACMEChallengeSolverHTTP01IngressPodTemplate) - (*in).DeepCopyInto(*out) - } - if in.IngressTemplate != nil { - in, out := &in.IngressTemplate, &out.IngressTemplate - *out = new(ACMEChallengeSolverHTTP01IngressTemplate) - (*in).DeepCopyInto(*out) - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ACMEChallengeSolverHTTP01Ingress. -func (in *ACMEChallengeSolverHTTP01Ingress) DeepCopy() *ACMEChallengeSolverHTTP01Ingress { - if in == nil { - return nil - } - out := new(ACMEChallengeSolverHTTP01Ingress) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ACMEChallengeSolverHTTP01IngressObjectMeta) DeepCopyInto(out *ACMEChallengeSolverHTTP01IngressObjectMeta) { - *out = *in - if in.Annotations != nil { - in, out := &in.Annotations, &out.Annotations - *out = make(map[string]string, len(*in)) - for key, val := range *in { - (*out)[key] = val - } - } - if in.Labels != nil { - in, out := &in.Labels, &out.Labels - *out = make(map[string]string, len(*in)) - for key, val := range *in { - (*out)[key] = val - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ACMEChallengeSolverHTTP01IngressObjectMeta. -func (in *ACMEChallengeSolverHTTP01IngressObjectMeta) DeepCopy() *ACMEChallengeSolverHTTP01IngressObjectMeta { - if in == nil { - return nil - } - out := new(ACMEChallengeSolverHTTP01IngressObjectMeta) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ACMEChallengeSolverHTTP01IngressPodObjectMeta) DeepCopyInto(out *ACMEChallengeSolverHTTP01IngressPodObjectMeta) { - *out = *in - if in.Annotations != nil { - in, out := &in.Annotations, &out.Annotations - *out = make(map[string]string, len(*in)) - for key, val := range *in { - (*out)[key] = val - } - } - if in.Labels != nil { - in, out := &in.Labels, &out.Labels - *out = make(map[string]string, len(*in)) - for key, val := range *in { - (*out)[key] = val - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ACMEChallengeSolverHTTP01IngressPodObjectMeta. -func (in *ACMEChallengeSolverHTTP01IngressPodObjectMeta) DeepCopy() *ACMEChallengeSolverHTTP01IngressPodObjectMeta { - if in == nil { - return nil - } - out := new(ACMEChallengeSolverHTTP01IngressPodObjectMeta) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ACMEChallengeSolverHTTP01IngressPodSpec) DeepCopyInto(out *ACMEChallengeSolverHTTP01IngressPodSpec) { - *out = *in - if in.NodeSelector != nil { - in, out := &in.NodeSelector, &out.NodeSelector - *out = make(map[string]string, len(*in)) - for key, val := range *in { - (*out)[key] = val - } - } - if in.Affinity != nil { - in, out := &in.Affinity, &out.Affinity - *out = new(v1.Affinity) - (*in).DeepCopyInto(*out) - } - if in.Tolerations != nil { - in, out := &in.Tolerations, &out.Tolerations - *out = make([]v1.Toleration, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ACMEChallengeSolverHTTP01IngressPodSpec. -func (in *ACMEChallengeSolverHTTP01IngressPodSpec) DeepCopy() *ACMEChallengeSolverHTTP01IngressPodSpec { - if in == nil { - return nil - } - out := new(ACMEChallengeSolverHTTP01IngressPodSpec) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ACMEChallengeSolverHTTP01IngressPodTemplate) DeepCopyInto(out *ACMEChallengeSolverHTTP01IngressPodTemplate) { - *out = *in - in.ACMEChallengeSolverHTTP01IngressPodObjectMeta.DeepCopyInto(&out.ACMEChallengeSolverHTTP01IngressPodObjectMeta) - in.Spec.DeepCopyInto(&out.Spec) - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ACMEChallengeSolverHTTP01IngressPodTemplate. -func (in *ACMEChallengeSolverHTTP01IngressPodTemplate) DeepCopy() *ACMEChallengeSolverHTTP01IngressPodTemplate { - if in == nil { - return nil - } - out := new(ACMEChallengeSolverHTTP01IngressPodTemplate) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ACMEChallengeSolverHTTP01IngressTemplate) DeepCopyInto(out *ACMEChallengeSolverHTTP01IngressTemplate) { - *out = *in - in.ACMEChallengeSolverHTTP01IngressObjectMeta.DeepCopyInto(&out.ACMEChallengeSolverHTTP01IngressObjectMeta) - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ACMEChallengeSolverHTTP01IngressTemplate. -func (in *ACMEChallengeSolverHTTP01IngressTemplate) DeepCopy() *ACMEChallengeSolverHTTP01IngressTemplate { - if in == nil { - return nil - } - out := new(ACMEChallengeSolverHTTP01IngressTemplate) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ACMEExternalAccountBinding) DeepCopyInto(out *ACMEExternalAccountBinding) { - *out = *in - out.Key = in.Key - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ACMEExternalAccountBinding. -func (in *ACMEExternalAccountBinding) DeepCopy() *ACMEExternalAccountBinding { - if in == nil { - return nil - } - out := new(ACMEExternalAccountBinding) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ACMEIssuer) DeepCopyInto(out *ACMEIssuer) { - *out = *in - if in.ExternalAccountBinding != nil { - in, out := &in.ExternalAccountBinding, &out.ExternalAccountBinding - *out = new(ACMEExternalAccountBinding) - **out = **in - } - out.PrivateKey = in.PrivateKey - if in.Solvers != nil { - in, out := &in.Solvers, &out.Solvers - *out = make([]ACMEChallengeSolver, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ACMEIssuer. -func (in *ACMEIssuer) DeepCopy() *ACMEIssuer { - if in == nil { - return nil - } - out := new(ACMEIssuer) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ACMEIssuerDNS01ProviderAcmeDNS) DeepCopyInto(out *ACMEIssuerDNS01ProviderAcmeDNS) { - *out = *in - out.AccountSecret = in.AccountSecret - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ACMEIssuerDNS01ProviderAcmeDNS. -func (in *ACMEIssuerDNS01ProviderAcmeDNS) DeepCopy() *ACMEIssuerDNS01ProviderAcmeDNS { - if in == nil { - return nil - } - out := new(ACMEIssuerDNS01ProviderAcmeDNS) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ACMEIssuerDNS01ProviderAkamai) DeepCopyInto(out *ACMEIssuerDNS01ProviderAkamai) { - *out = *in - out.ClientToken = in.ClientToken - out.ClientSecret = in.ClientSecret - out.AccessToken = in.AccessToken - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ACMEIssuerDNS01ProviderAkamai. -func (in *ACMEIssuerDNS01ProviderAkamai) DeepCopy() *ACMEIssuerDNS01ProviderAkamai { - if in == nil { - return nil - } - out := new(ACMEIssuerDNS01ProviderAkamai) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ACMEIssuerDNS01ProviderAzureDNS) DeepCopyInto(out *ACMEIssuerDNS01ProviderAzureDNS) { - *out = *in - if in.ClientSecret != nil { - in, out := &in.ClientSecret, &out.ClientSecret - *out = new(metav1.SecretKeySelector) - **out = **in - } - if in.ManagedIdentity != nil { - in, out := &in.ManagedIdentity, &out.ManagedIdentity - *out = new(AzureManagedIdentity) - **out = **in - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ACMEIssuerDNS01ProviderAzureDNS. -func (in *ACMEIssuerDNS01ProviderAzureDNS) DeepCopy() *ACMEIssuerDNS01ProviderAzureDNS { - if in == nil { - return nil - } - out := new(ACMEIssuerDNS01ProviderAzureDNS) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ACMEIssuerDNS01ProviderCloudDNS) DeepCopyInto(out *ACMEIssuerDNS01ProviderCloudDNS) { - *out = *in - if in.ServiceAccount != nil { - in, out := &in.ServiceAccount, &out.ServiceAccount - *out = new(metav1.SecretKeySelector) - **out = **in - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ACMEIssuerDNS01ProviderCloudDNS. -func (in *ACMEIssuerDNS01ProviderCloudDNS) DeepCopy() *ACMEIssuerDNS01ProviderCloudDNS { - if in == nil { - return nil - } - out := new(ACMEIssuerDNS01ProviderCloudDNS) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ACMEIssuerDNS01ProviderCloudflare) DeepCopyInto(out *ACMEIssuerDNS01ProviderCloudflare) { - *out = *in - if in.APIKey != nil { - in, out := &in.APIKey, &out.APIKey - *out = new(metav1.SecretKeySelector) - **out = **in - } - if in.APIToken != nil { - in, out := &in.APIToken, &out.APIToken - *out = new(metav1.SecretKeySelector) - **out = **in - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ACMEIssuerDNS01ProviderCloudflare. -func (in *ACMEIssuerDNS01ProviderCloudflare) DeepCopy() *ACMEIssuerDNS01ProviderCloudflare { - if in == nil { - return nil - } - out := new(ACMEIssuerDNS01ProviderCloudflare) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ACMEIssuerDNS01ProviderDigitalOcean) DeepCopyInto(out *ACMEIssuerDNS01ProviderDigitalOcean) { - *out = *in - out.Token = in.Token - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ACMEIssuerDNS01ProviderDigitalOcean. -func (in *ACMEIssuerDNS01ProviderDigitalOcean) DeepCopy() *ACMEIssuerDNS01ProviderDigitalOcean { - if in == nil { - return nil - } - out := new(ACMEIssuerDNS01ProviderDigitalOcean) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ACMEIssuerDNS01ProviderRFC2136) DeepCopyInto(out *ACMEIssuerDNS01ProviderRFC2136) { - *out = *in - out.TSIGSecret = in.TSIGSecret - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ACMEIssuerDNS01ProviderRFC2136. -func (in *ACMEIssuerDNS01ProviderRFC2136) DeepCopy() *ACMEIssuerDNS01ProviderRFC2136 { - if in == nil { - return nil - } - out := new(ACMEIssuerDNS01ProviderRFC2136) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ACMEIssuerDNS01ProviderRoute53) DeepCopyInto(out *ACMEIssuerDNS01ProviderRoute53) { - *out = *in - if in.SecretAccessKeyID != nil { - in, out := &in.SecretAccessKeyID, &out.SecretAccessKeyID - *out = new(metav1.SecretKeySelector) - **out = **in - } - out.SecretAccessKey = in.SecretAccessKey - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ACMEIssuerDNS01ProviderRoute53. -func (in *ACMEIssuerDNS01ProviderRoute53) DeepCopy() *ACMEIssuerDNS01ProviderRoute53 { - if in == nil { - return nil - } - out := new(ACMEIssuerDNS01ProviderRoute53) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ACMEIssuerDNS01ProviderWebhook) DeepCopyInto(out *ACMEIssuerDNS01ProviderWebhook) { - *out = *in - if in.Config != nil { - in, out := &in.Config, &out.Config - *out = new(apiextensionsv1.JSON) - (*in).DeepCopyInto(*out) - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ACMEIssuerDNS01ProviderWebhook. -func (in *ACMEIssuerDNS01ProviderWebhook) DeepCopy() *ACMEIssuerDNS01ProviderWebhook { - if in == nil { - return nil - } - out := new(ACMEIssuerDNS01ProviderWebhook) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ACMEIssuerStatus) DeepCopyInto(out *ACMEIssuerStatus) { - *out = *in - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ACMEIssuerStatus. -func (in *ACMEIssuerStatus) DeepCopy() *ACMEIssuerStatus { - if in == nil { - return nil - } - out := new(ACMEIssuerStatus) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *AzureManagedIdentity) DeepCopyInto(out *AzureManagedIdentity) { - *out = *in - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AzureManagedIdentity. -func (in *AzureManagedIdentity) DeepCopy() *AzureManagedIdentity { - if in == nil { - return nil - } - out := new(AzureManagedIdentity) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *CertificateDNSNameSelector) DeepCopyInto(out *CertificateDNSNameSelector) { - *out = *in - if in.MatchLabels != nil { - in, out := &in.MatchLabels, &out.MatchLabels - *out = make(map[string]string, len(*in)) - for key, val := range *in { - (*out)[key] = val - } - } - if in.DNSNames != nil { - in, out := &in.DNSNames, &out.DNSNames - *out = make([]string, len(*in)) - copy(*out, *in) - } - if in.DNSZones != nil { - in, out := &in.DNSZones, &out.DNSZones - *out = make([]string, len(*in)) - copy(*out, *in) - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CertificateDNSNameSelector. -func (in *CertificateDNSNameSelector) DeepCopy() *CertificateDNSNameSelector { - if in == nil { - return nil - } - out := new(CertificateDNSNameSelector) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *Challenge) DeepCopyInto(out *Challenge) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - in.Spec.DeepCopyInto(&out.Spec) - out.Status = in.Status - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Challenge. -func (in *Challenge) DeepCopy() *Challenge { - if in == nil { - return nil - } - out := new(Challenge) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *Challenge) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ChallengeList) DeepCopyInto(out *ChallengeList) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ListMeta.DeepCopyInto(&out.ListMeta) - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]Challenge, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ChallengeList. -func (in *ChallengeList) DeepCopy() *ChallengeList { - if in == nil { - return nil - } - out := new(ChallengeList) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *ChallengeList) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ChallengeSpec) DeepCopyInto(out *ChallengeSpec) { - *out = *in - in.Solver.DeepCopyInto(&out.Solver) - out.IssuerRef = in.IssuerRef - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ChallengeSpec. -func (in *ChallengeSpec) DeepCopy() *ChallengeSpec { - if in == nil { - return nil - } - out := new(ChallengeSpec) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ChallengeStatus) DeepCopyInto(out *ChallengeStatus) { - *out = *in - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ChallengeStatus. -func (in *ChallengeStatus) DeepCopy() *ChallengeStatus { - if in == nil { - return nil - } - out := new(ChallengeStatus) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *Order) DeepCopyInto(out *Order) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - in.Spec.DeepCopyInto(&out.Spec) - in.Status.DeepCopyInto(&out.Status) - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Order. -func (in *Order) DeepCopy() *Order { - if in == nil { - return nil - } - out := new(Order) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *Order) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *OrderList) DeepCopyInto(out *OrderList) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ListMeta.DeepCopyInto(&out.ListMeta) - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]Order, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OrderList. -func (in *OrderList) DeepCopy() *OrderList { - if in == nil { - return nil - } - out := new(OrderList) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *OrderList) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *OrderSpec) DeepCopyInto(out *OrderSpec) { - *out = *in - if in.CSR != nil { - in, out := &in.CSR, &out.CSR - *out = make([]byte, len(*in)) - copy(*out, *in) - } - out.IssuerRef = in.IssuerRef - if in.DNSNames != nil { - in, out := &in.DNSNames, &out.DNSNames - *out = make([]string, len(*in)) - copy(*out, *in) - } - if in.IPAddresses != nil { - in, out := &in.IPAddresses, &out.IPAddresses - *out = make([]string, len(*in)) - copy(*out, *in) - } - if in.Duration != nil { - in, out := &in.Duration, &out.Duration - *out = new(apismetav1.Duration) - **out = **in - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OrderSpec. -func (in *OrderSpec) DeepCopy() *OrderSpec { - if in == nil { - return nil - } - out := new(OrderSpec) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *OrderStatus) DeepCopyInto(out *OrderStatus) { - *out = *in - if in.Authorizations != nil { - in, out := &in.Authorizations, &out.Authorizations - *out = make([]ACMEAuthorization, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - if in.Certificate != nil { - in, out := &in.Certificate, &out.Certificate - *out = make([]byte, len(*in)) - copy(*out, *in) - } - if in.FailureTime != nil { - in, out := &in.FailureTime, &out.FailureTime - *out = (*in).DeepCopy() - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OrderStatus. -func (in *OrderStatus) DeepCopy() *OrderStatus { - if in == nil { - return nil - } - out := new(OrderStatus) - in.DeepCopyInto(out) - return out -} diff --git a/internal/apis/acme/v1beta1/conversion.go b/internal/apis/acme/v1beta1/conversion.go deleted file mode 100644 index 795423e132f..00000000000 --- a/internal/apis/acme/v1beta1/conversion.go +++ /dev/null @@ -1,35 +0,0 @@ -/* -Copyright 2021 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. -*/ - -package v1beta1 - -import ( - "k8s.io/apimachinery/pkg/conversion" - - "github.com/cert-manager/cert-manager/internal/apis/acme" -) - -// Convert_acme_ACMEIssuer_To_v1beta1_ACMEIssuer is explicitly defined to avoid issues in conversion-gen -// when referencing types in other API groups. -func Convert_acme_ACMEIssuer_To_v1beta1_ACMEIssuer(in *acme.ACMEIssuer, out *ACMEIssuer, s conversion.Scope) error { - return autoConvert_acme_ACMEIssuer_To_v1beta1_ACMEIssuer(in, out, s) -} - -// Convert_v1beta1_ACMEIssuer_To_acme_ACMEIssuer is explicitly defined to avoid issues in conversion-gen -// when referencing types in other API groups. -func Convert_v1beta1_ACMEIssuer_To_acme_ACMEIssuer(in *ACMEIssuer, out *acme.ACMEIssuer, s conversion.Scope) error { - return autoConvert_v1beta1_ACMEIssuer_To_acme_ACMEIssuer(in, out, s) -} diff --git a/internal/apis/acme/v1beta1/types.go b/internal/apis/acme/v1beta1/types.go deleted file mode 100644 index c02e8f82ec4..00000000000 --- a/internal/apis/acme/v1beta1/types.go +++ /dev/null @@ -1,43 +0,0 @@ -/* -Copyright 2020 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. -*/ - -package v1beta1 - -const ( - // If this annotation is specified on a Certificate or Order resource when - // using the HTTP01 solver type, the ingress.name field of the HTTP01 - // solver's configuration will be set to the value given here. - // This is especially useful for users of Ingress controllers that maintain - // a 1:1 mapping between endpoint IP and Ingress resource. - ACMECertificateHTTP01IngressNameOverride = "acme.cert-manager.io/http01-override-ingress-name" - - // If this annotation is specified on a Certificate or Order resource when - // using the HTTP01 solver type, the ingress.class field of the HTTP01 - // solver's configuration will be set to the value given here. - // This is especially useful for users deploying many different ingress - // classes into a single cluster that want to be able to re-use a single - // solver for each ingress class. - ACMECertificateHTTP01IngressClassOverride = "acme.cert-manager.io/http01-override-ingress-class" - - // IngressEditInPlaceAnnotation is used to toggle the use of ingressClass instead - // of ingress on the created Certificate resource - IngressEditInPlaceAnnotationKey = "acme.cert-manager.io/http01-edit-in-place" -) - -const ( - OrderKind = "Order" - ChallengeKind = "Challenge" -) diff --git a/internal/apis/acme/v1beta1/types_challenge.go b/internal/apis/acme/v1beta1/types_challenge.go deleted file mode 100644 index 6075b6227a1..00000000000 --- a/internal/apis/acme/v1beta1/types_challenge.go +++ /dev/null @@ -1,145 +0,0 @@ -/* -Copyright 2020 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. -*/ - -package v1beta1 - -import ( - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - - cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" -) - -// +genclient -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object - -// Challenge is a type to represent a Challenge request with an ACME server -// +k8s:openapi-gen=true -// +kubebuilder:printcolumn:name="State",type="string",JSONPath=".status.state" -// +kubebuilder:printcolumn:name="Domain",type="string",JSONPath=".spec.dnsName" -// +kubebuilder:printcolumn:name="Reason",type="string",JSONPath=".status.reason",description="",priority=1 -// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp",description="CreationTimestamp is a timestamp representing the server time when this object was created. It is not guaranteed to be set in happens-before order across separate operations. Clients may not set this value. It is represented in RFC3339 form and is in UTC." -// +kubebuilder:subresource:status -// +kubebuilder:resource:path=challenges -type Challenge struct { - metav1.TypeMeta `json:",inline"` - metav1.ObjectMeta `json:"metadata"` - - Spec ChallengeSpec `json:"spec"` - // +optional - Status ChallengeStatus `json:"status"` -} - -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object - -// ChallengeList is a list of Challenges -type ChallengeList struct { - metav1.TypeMeta `json:",inline"` - metav1.ListMeta `json:"metadata"` - - Items []Challenge `json:"items"` -} - -type ChallengeSpec struct { - // The URL of the ACME Challenge resource for this challenge. - // This can be used to lookup details about the status of this challenge. - URL string `json:"url"` - - // The URL to the ACME Authorization resource that this - // challenge is a part of. - AuthorizationURL string `json:"authorizationURL"` - - // 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`. - DNSName string `json:"dnsName"` - - // wildcard will be true if this challenge is for a wildcard identifier, - // for example '*.example.com'. - // +optional - Wildcard bool `json:"wildcard"` - - // The type of ACME challenge this resource represents. - // One of "HTTP-01" or "DNS-01". - Type ACMEChallengeType `json:"type"` - - // The ACME challenge token for this challenge. - // This is the raw value returned from the ACME server. - Token string `json:"token"` - - // 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: - // `.`. - // For DNS01 challenges, this is the base64 encoded SHA256 sum of the - // `.` - // text that must be set as the TXT record content. - Key string `json:"key"` - - // Contains the domain solving configuration that should be used to - // solve this challenge resource. - Solver ACMEChallengeSolver `json:"solver"` - - // 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. - IssuerRef cmmeta.ObjectReference `json:"issuerRef"` -} - -// The type of ACME challenge. Only HTTP-01 and DNS-01 are supported. -// +kubebuilder:validation:Enum=HTTP-01;DNS-01 -type ACMEChallengeType string - -const ( - // ACMEChallengeTypeHTTP01 denotes a Challenge is of type http-01 - // More info: https://letsencrypt.org/docs/challenge-types/#http-01-challenge - ACMEChallengeTypeHTTP01 ACMEChallengeType = "HTTP-01" - - // ACMEChallengeTypeDNS01 denotes a Challenge is of type dns-01 - // More info: https://letsencrypt.org/docs/challenge-types/#dns-01-challenge - ACMEChallengeTypeDNS01 ACMEChallengeType = "DNS-01" -) - -type ChallengeStatus struct { - // 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. - // +optional - Processing bool `json:"processing"` - - // 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). - // +optional - Presented bool `json:"presented"` - - // Contains human readable information on why the Challenge is in the - // current state. - // +optional - Reason string `json:"reason,omitempty"` - - // Contains the current 'state' of the challenge. - // If not set, the state of the challenge is unknown. - // +optional - State State `json:"state,omitempty"` -} diff --git a/internal/apis/acme/v1beta1/types_issuer.go b/internal/apis/acme/v1beta1/types_issuer.go deleted file mode 100644 index 23de024f138..00000000000 --- a/internal/apis/acme/v1beta1/types_issuer.go +++ /dev/null @@ -1,608 +0,0 @@ -/* -Copyright 2020 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. -*/ - -package v1beta1 - -import ( - corev1 "k8s.io/api/core/v1" - apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" - gwapi "sigs.k8s.io/gateway-api/apis/v1alpha2" - - cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" -) - -// 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. -type ACMEIssuer struct { - // 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. - // +optional - Email string `json:"email,omitempty"` - - // 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. - Server string `json:"server"` - - // 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 - // +optional - // +kubebuilder:validation:MaxLength=64 - PreferredChain string `json:"preferredChain"` - - // Enables or disables validation of the ACME server TLS certificate. - // If true, requests to the ACME server will not have their TLS certificate - // validated (i.e. insecure connections will be allowed). - // Only enable this option in development environments. - // The cert-manager system installed roots will be used to verify connections - // to the ACME server if this is false. - // Defaults to false. - // +optional - SkipTLSVerify bool `json:"skipTLSVerify,omitempty"` - - // 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. - // +optional - ExternalAccountBinding *ACMEExternalAccountBinding `json:"externalAccountBinding,omitempty"` - - // 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. - PrivateKey cmmeta.SecretKeySelector `json:"privateKeySecretRef"` - - // 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/ - // +optional - Solvers []ACMEChallengeSolver `json:"solvers,omitempty"` - - // 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. - // +optional - DisableAccountKeyGeneration bool `json:"disableAccountKeyGeneration,omitempty"` - - // 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. - // +optional - EnableDurationFeature bool `json:"enableDurationFeature,omitempty"` -} - -// ACMEExternalAccountBinding is a reference to a CA external account of the ACME -// server. -type ACMEExternalAccountBinding struct { - // keyID is the ID of the CA key that the External Account is bound to. - KeyID string `json:"keyID"` - - // 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. - Key cmmeta.SecretKeySelector `json:"keySecretRef"` - - // 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. - // +optional - KeyAlgorithm HMACKeyAlgorithm `json:"keyAlgorithm,omitempty"` -} - -// HMACKeyAlgorithm is the name of a key algorithm used for HMAC encryption -// +kubebuilder:validation:Enum=HS256;HS384;HS512 -type HMACKeyAlgorithm string - -const ( - HS256 HMACKeyAlgorithm = "HS256" - HS384 HMACKeyAlgorithm = "HS384" - HS512 HMACKeyAlgorithm = "HS512" -) - -// Configures an issuer to solve challenges using the specified options. -// Only one of HTTP01 or DNS01 may be provided. -type ACMEChallengeSolver struct { - // 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. - // +optional - Selector *CertificateDNSNameSelector `json:"selector,omitempty"` - - // 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. - // +optional - HTTP01 *ACMEChallengeSolverHTTP01 `json:"http01,omitempty"` - - // Configures cert-manager to attempt to complete authorizations by - // performing the DNS01 challenge flow. - // +optional - DNS01 *ACMEChallengeSolverDNS01 `json:"dns01,omitempty"` -} - -// CertificateDomainSelector 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. -type CertificateDNSNameSelector struct { - // A label selector that is used to refine the set of certificate's that - // this challenge solver will apply to. - // +optional - MatchLabels map[string]string `json:"matchLabels,omitempty"` - - // 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. - // +optional - DNSNames []string `json:"dnsNames,omitempty"` - - // 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. - // +optional - DNSZones []string `json:"dnsZones,omitempty"` -} - -// 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. -type ACMEChallengeSolverHTTP01 struct { - // 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. - // +optional - Ingress *ACMEChallengeSolverHTTP01Ingress `json:"ingress,omitempty"` - - // 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. - // +optional - GatewayHTTPRoute *ACMEChallengeSolverHTTP01GatewayHTTPRoute `json:"gatewayHTTPRoute,omitempty"` -} - -type ACMEChallengeSolverHTTP01Ingress struct { - // Optional service type for Kubernetes solver service. Supported values - // are NodePort or ClusterIP. If unset, defaults to NodePort. - // +optional - ServiceType corev1.ServiceType `json:"serviceType,omitempty"` - - // 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. - // +optional - Class *string `json:"class,omitempty"` - - // 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. - // +optional - Name string `json:"name,omitempty"` - - // Optional pod template used to configure the ACME challenge solver pods - // used for HTTP01 challenges - // +optional - PodTemplate *ACMEChallengeSolverHTTP01IngressPodTemplate `json:"podTemplate,omitempty"` - - // Optional ingress template used to configure the ACME challenge solver - // ingress used for HTTP01 challenges. - // +optional - IngressTemplate *ACMEChallengeSolverHTTP01IngressTemplate `json:"ingressTemplate,omitempty"` -} - -type ACMEChallengeSolverHTTP01GatewayHTTPRoute struct { - // Optional service type for Kubernetes solver service. Supported values - // are NodePort or ClusterIP. If unset, defaults to NodePort. - // +optional - ServiceType corev1.ServiceType `json:"serviceType,omitempty"` - - // Custom labels that will be applied to HTTPRoutes created by cert-manager - // while solving HTTP-01 challenges. - // +optional - Labels map[string]string `json:"labels,omitempty"` - - // 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/v1alpha2/api-types/httproute/#attaching-to-gateways - ParentRefs []gwapi.ParentReference `json:"parentRefs,omitempty"` -} - -type ACMEChallengeSolverHTTP01IngressPodTemplate struct { - // 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. - // +optional - ACMEChallengeSolverHTTP01IngressPodObjectMeta `json:"metadata"` - - // 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. - // +optional - Spec ACMEChallengeSolverHTTP01IngressPodSpec `json:"spec"` -} - -type ACMEChallengeSolverHTTP01IngressPodObjectMeta struct { - // Annotations that should be added to the create ACME HTTP01 solver pods. - // +optional - Annotations map[string]string `json:"annotations,omitempty"` - - // Labels that should be added to the created ACME HTTP01 solver pods. - // +optional - Labels map[string]string `json:"labels,omitempty"` -} - -type ACMEChallengeSolverHTTP01IngressPodSpec struct { - // 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/ - // +optional - NodeSelector map[string]string `json:"nodeSelector,omitempty"` - - // If specified, the pod's scheduling constraints - // +optional - Affinity *corev1.Affinity `json:"affinity,omitempty"` - - // If specified, the pod's tolerations. - // +optional - Tolerations []corev1.Toleration `json:"tolerations,omitempty"` - - // If specified, the pod's priorityClassName. - // +optional - PriorityClassName string `json:"priorityClassName,omitempty"` - - // If specified, the pod's service account - // +optional - ServiceAccountName string `json:"serviceAccountName,omitempty"` -} - -type ACMEChallengeSolverHTTP01IngressTemplate struct { - // 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. - // +optional - ACMEChallengeSolverHTTP01IngressObjectMeta `json:"metadata"` -} - -type ACMEChallengeSolverHTTP01IngressObjectMeta struct { - // Annotations that should be added to the created ACME HTTP01 solver ingress. - // +optional - Annotations map[string]string `json:"annotations,omitempty"` - - // Labels that should be added to the created ACME HTTP01 solver ingress. - // +optional - Labels map[string]string `json:"labels,omitempty"` -} - -// Used to configure a DNS01 challenge provider to be used when solving DNS01 -// challenges. -// Only one DNS provider may be configured per solver. -type ACMEChallengeSolverDNS01 struct { - // CNAMEStrategy configures how the DNS01 provider should handle CNAME - // records when found in DNS zones. - // +optional - CNAMEStrategy CNAMEStrategy `json:"cnameStrategy,omitempty"` - - // Use the Akamai DNS zone management API to manage DNS01 challenge records. - // +optional - Akamai *ACMEIssuerDNS01ProviderAkamai `json:"akamai,omitempty"` - - // Use the Google Cloud DNS API to manage DNS01 challenge records. - // +optional - CloudDNS *ACMEIssuerDNS01ProviderCloudDNS `json:"cloudDNS,omitempty"` - - // Use the Cloudflare API to manage DNS01 challenge records. - // +optional - Cloudflare *ACMEIssuerDNS01ProviderCloudflare `json:"cloudflare,omitempty"` - - // Use the AWS Route53 API to manage DNS01 challenge records. - // +optional - Route53 *ACMEIssuerDNS01ProviderRoute53 `json:"route53,omitempty"` - - // Use the Microsoft Azure DNS API to manage DNS01 challenge records. - // +optional - AzureDNS *ACMEIssuerDNS01ProviderAzureDNS `json:"azureDNS,omitempty"` - - // Use the DigitalOcean DNS API to manage DNS01 challenge records. - // +optional - DigitalOcean *ACMEIssuerDNS01ProviderDigitalOcean `json:"digitalocean,omitempty"` - - // Use the 'ACME DNS' (https://github.com/joohoi/acme-dns) API to manage - // DNS01 challenge records. - // +optional - AcmeDNS *ACMEIssuerDNS01ProviderAcmeDNS `json:"acmeDNS,omitempty"` - - // Use RFC2136 ("Dynamic Updates in the Domain Name System") (https://datatracker.ietf.org/doc/rfc2136/) - // to manage DNS01 challenge records. - // +optional - RFC2136 *ACMEIssuerDNS01ProviderRFC2136 `json:"rfc2136,omitempty"` - - // Configure an external webhook based DNS01 challenge solver to manage - // DNS01 challenge records. - // +optional - Webhook *ACMEIssuerDNS01ProviderWebhook `json:"webhook,omitempty"` -} - -// 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). -// +kubebuilder:validation:Enum=None;Follow -type CNAMEStrategy string - -const ( - // NoneStrategy indicates that no CNAME resolution strategy should be used - // when determining which DNS zone to update during DNS01 challenges. - NoneStrategy = "None" - - // FollowStrategy will cause cert-manager to recurse through CNAMEs in - // order to determine which DNS zone to update during DNS01 challenges. - // This is useful if you do not want to grant cert-manager access to your - // root DNS zone, and instead delegate the _acme-challenge.example.com - // subdomain to some other, less privileged domain. - FollowStrategy = "Follow" -) - -// ACMEIssuerDNS01ProviderAkamai is a structure containing the DNS -// configuration for Akamai DNS—Zone Record Management API -type ACMEIssuerDNS01ProviderAkamai struct { - ServiceConsumerDomain string `json:"serviceConsumerDomain"` - ClientToken cmmeta.SecretKeySelector `json:"clientTokenSecretRef"` - ClientSecret cmmeta.SecretKeySelector `json:"clientSecretSecretRef"` - AccessToken cmmeta.SecretKeySelector `json:"accessTokenSecretRef"` -} - -// ACMEIssuerDNS01ProviderCloudDNS is a structure containing the DNS -// configuration for Google Cloud DNS -type ACMEIssuerDNS01ProviderCloudDNS struct { - // +optional - ServiceAccount *cmmeta.SecretKeySelector `json:"serviceAccountSecretRef,omitempty"` - Project string `json:"project"` - - // 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. - // +optional - HostedZoneName string `json:"hostedZoneName,omitempty"` -} - -// ACMEIssuerDNS01ProviderCloudflare is a structure containing the DNS -// configuration for Cloudflare. -// One of `apiKeySecretRef` or `apiTokenSecretRef` must be provided. -type ACMEIssuerDNS01ProviderCloudflare struct { - // Email of the account, only required when using API key based authentication. - // +optional - Email string `json:"email,omitempty"` - - // 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. - // +optional - APIKey *cmmeta.SecretKeySelector `json:"apiKeySecretRef,omitempty"` - - // API token used to authenticate with Cloudflare. - // +optional - APIToken *cmmeta.SecretKeySelector `json:"apiTokenSecretRef,omitempty"` -} - -// ACMEIssuerDNS01ProviderDigitalOcean is a structure containing the DNS -// configuration for DigitalOcean Domains -type ACMEIssuerDNS01ProviderDigitalOcean struct { - Token cmmeta.SecretKeySelector `json:"tokenSecretRef"` -} - -// ACMEIssuerDNS01ProviderRoute53 is a structure containing the Route 53 -// configuration for AWS -type ACMEIssuerDNS01ProviderRoute53 struct { - // The AccessKeyID is used for authentication. If not 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 - // +optional - AccessKeyID string `json:"accessKeyID,omitempty"` - - // If set, pull the AWS access key ID from a key within a kubernetes secret. - // see: https://docs.aws.amazon.com/sdk-for-go/v1/developer-guide/configuring-sdk.html#specifying-credentials - // +optional - SecretAccessKeyID *cmmeta.SecretKeySelector `json:"accessKeyIDSecretRef,omitempty"` - - // The SecretAccessKey is used for authentication. If not set we fall-back to using env vars, shared credentials file or AWS Instance metadata - // https://docs.aws.amazon.com/sdk-for-go/v1/developer-guide/configuring-sdk.html#specifying-credentials - // +optional - SecretAccessKey cmmeta.SecretKeySelector `json:"secretAccessKeySecretRef"` - - // 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 - // +optional - Role string `json:"role,omitempty"` - - // If set, the provider will manage only this zone in Route53 and will not do an lookup using the route53:ListHostedZonesByName api call. - // +optional - HostedZoneID string `json:"hostedZoneID,omitempty"` - - // Always set the region when using AccessKeyID and SecretAccessKey - Region string `json:"region"` -} - -// ACMEIssuerDNS01ProviderAzureDNS is a structure containing the -// configuration for Azure DNS -type ACMEIssuerDNS01ProviderAzureDNS struct { - // if both this and ClientSecret are left unset MSI will be used - // +optional - ClientID string `json:"clientID,omitempty"` - - // if both this and ClientID are left unset MSI will be used - // +optional - ClientSecret *cmmeta.SecretKeySelector `json:"clientSecretSecretRef,omitempty"` - - // ID of the Azure subscription - SubscriptionID string `json:"subscriptionID"` - - // when specifying ClientID and ClientSecret then this field is also needed - // +optional - TenantID string `json:"tenantID,omitempty"` - - // resource group the DNS zone is located in - ResourceGroupName string `json:"resourceGroupName"` - - // name of the DNS zone that should be used - // +optional - HostedZoneName string `json:"hostedZoneName,omitempty"` - - // name of the Azure environment (default AzurePublicCloud) - // +optional - Environment AzureDNSEnvironment `json:"environment,omitempty"` - - // managed identity configuration, can not be used at the same time as clientID, clientSecretSecretRef or tenantID - // +optional - ManagedIdentity *AzureManagedIdentity `json:"managedIdentity,omitempty"` -} - -type AzureManagedIdentity struct { - // client ID of the managed identity, can not be used at the same time as resourceID - // +optional - ClientID string `json:"clientID,omitempty"` - - // resource ID of the managed identity, can not be used at the same time as clientID - // +optional - ResourceID string `json:"resourceID,omitempty"` -} - -// +kubebuilder:validation:Enum=AzurePublicCloud;AzureChinaCloud;AzureGermanCloud;AzureUSGovernmentCloud -type AzureDNSEnvironment string - -const ( - AzurePublicCloud AzureDNSEnvironment = "AzurePublicCloud" - AzureChinaCloud AzureDNSEnvironment = "AzureChinaCloud" - AzureGermanCloud AzureDNSEnvironment = "AzureGermanCloud" - AzureUSGovernmentCloud AzureDNSEnvironment = "AzureUSGovernmentCloud" -) - -// ACMEIssuerDNS01ProviderAcmeDNS is a structure containing the -// configuration for ACME-DNS servers -type ACMEIssuerDNS01ProviderAcmeDNS struct { - Host string `json:"host"` - - AccountSecret cmmeta.SecretKeySelector `json:"accountSecretRef"` -} - -// ACMEIssuerDNS01ProviderRFC2136 is a structure containing the -// configuration for RFC2136 DNS -type ACMEIssuerDNS01ProviderRFC2136 struct { - // 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. - Nameserver string `json:"nameserver"` - - // The name of the secret containing the TSIG value. - // If ``tsigKeyName`` is defined, this field is required. - // +optional - TSIGSecret cmmeta.SecretKeySelector `json:"tsigSecretSecretRef,omitempty"` - - // The TSIG Key name configured in the DNS. - // If ``tsigSecretSecretRef`` is defined, this field is required. - // +optional - TSIGKeyName string `json:"tsigKeyName,omitempty"` - - // 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``. - // +optional - TSIGAlgorithm string `json:"tsigAlgorithm,omitempty"` -} - -// ACMEIssuerDNS01ProviderWebhook specifies configuration for a webhook DNS01 -// provider, including where to POST ChallengePayload resources. -type ACMEIssuerDNS01ProviderWebhook struct { - // 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. - GroupName string `json:"groupName"` - - // 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'. - SolverName string `json:"solverName"` - - // 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. - // +optional - Config *apiextensionsv1.JSON `json:"config,omitempty"` -} - -type ACMEIssuerStatus struct { - // URI is the unique account identifier, which can also be used to retrieve - // account details from the CA - // +optional - URI string `json:"uri,omitempty"` - - // LastRegisteredEmail is the email associated with the latest registered - // ACME account, in order to track changes made to registered account - // associated with the Issuer - // +optional - LastRegisteredEmail string `json:"lastRegisteredEmail,omitempty"` -} diff --git a/internal/apis/acme/v1beta1/types_order.go b/internal/apis/acme/v1beta1/types_order.go deleted file mode 100644 index 7c760494250..00000000000 --- a/internal/apis/acme/v1beta1/types_order.go +++ /dev/null @@ -1,239 +0,0 @@ -/* -Copyright 2020 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. -*/ - -package v1beta1 - -import ( - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - - cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" -) - -// +genclient -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object - -// Order is a type to represent an Order with an ACME server -// +k8s:openapi-gen=true -type Order struct { - metav1.TypeMeta `json:",inline"` - metav1.ObjectMeta `json:"metadata"` - - Spec OrderSpec `json:"spec"` - // +optional - Status OrderStatus `json:"status"` -} - -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object - -// OrderList is a list of Orders -type OrderList struct { - metav1.TypeMeta `json:",inline"` - metav1.ListMeta `json:"metadata"` - - Items []Order `json:"items"` -} - -type OrderSpec struct { - // Certificate signing request bytes in DER encoding. - // This will be used when finalizing the order. - // This field must be set on the order. - Request []byte `json:"request"` - - // 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. - IssuerRef cmmeta.ObjectReference `json:"issuerRef"` - - // 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. - // +optional - CommonName string `json:"commonName,omitempty"` - - // 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. - //+optional - DNSNames []string `json:"dnsNames,omitempty"` - - // 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. - // +optional - IPAddresses []string `json:"ipAddresses,omitempty"` - - // Duration is the duration for the not after date for the requested certificate. - // this is set on order creation as pe the ACME spec. - // +optional - Duration *metav1.Duration `json:"duration,omitempty"` -} - -type OrderStatus struct { - // 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. - // +optional - URL string `json:"url,omitempty"` - - // FinalizeURL of the Order. - // This is used to obtain certificates for this order once it has been completed. - // +optional - FinalizeURL string `json:"finalizeURL,omitempty"` - - // 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. - // +optional - Authorizations []ACMEAuthorization `json:"authorizations,omitempty"` - - // 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. - // +optional - Certificate []byte `json:"certificate,omitempty"` - - // State contains the current state of this Order resource. - // States 'success' and 'expired' are 'final' - // +optional - State State `json:"state,omitempty"` - - // Reason optionally provides more information about a why the order is in - // the current state. - // +optional - Reason string `json:"reason,omitempty"` - - // FailureTime stores the time that this order failed. - // This is used to influence garbage collection and back-off. - // +optional - FailureTime *metav1.Time `json:"failureTime,omitempty"` -} - -// 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. -type ACMEAuthorization struct { - // URL is the URL of the Authorization that must be completed - URL string `json:"url"` - - // Identifier is the DNS name to be validated as part of this authorization - // +optional - Identifier string `json:"identifier,omitempty"` - - // 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'. - // +optional - Wildcard *bool `json:"wildcard,omitempty"` - - // 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. - // +optional - InitialState State `json:"initialState,omitempty"` - - // 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. - // +optional - Challenges []ACMEChallenge `json:"challenges,omitempty"` -} - -// 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. -type ACMEChallenge struct { - // URL is the URL of this challenge. It can be used to retrieve additional - // metadata about the Challenge from the ACME server. - URL string `json:"url"` - - // Token is the token that must be presented for this challenge. - // This is used to compute the 'key' that must also be presented. - Token string `json:"token"` - - // 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. - Type string `json:"type"` -} - -// 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. -// +kubebuilder:validation:Enum=valid;ready;pending;processing;invalid;expired;errored -type State string - -const ( - // Unknown is not a real state as part of the ACME spec. - // It is used to represent an unrecognised value. - Unknown State = "" - - // 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. - Valid State = "valid" - - // 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. - Ready State = "ready" - - // 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. - Pending State = "pending" - - // 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. - Processing State = "processing" - - // 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. - Invalid State = "invalid" - - // 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. - Expired State = "expired" - - // 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. - Errored State = "errored" -) diff --git a/internal/apis/acme/v1beta1/zz_generated.conversion.go b/internal/apis/acme/v1beta1/zz_generated.conversion.go deleted file mode 100644 index 07a975c2d3d..00000000000 --- a/internal/apis/acme/v1beta1/zz_generated.conversion.go +++ /dev/null @@ -1,1629 +0,0 @@ -//go:build !ignore_autogenerated -// +build !ignore_autogenerated - -/* -Copyright 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. -*/ - -// Code generated by conversion-gen. DO NOT EDIT. - -package v1beta1 - -import ( - unsafe "unsafe" - - acme "github.com/cert-manager/cert-manager/internal/apis/acme" - meta "github.com/cert-manager/cert-manager/internal/apis/meta" - metav1 "github.com/cert-manager/cert-manager/internal/apis/meta/v1" - apismetav1 "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" - v1 "k8s.io/api/core/v1" - apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" - pkgapismetav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - conversion "k8s.io/apimachinery/pkg/conversion" - runtime "k8s.io/apimachinery/pkg/runtime" - v1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" -) - -func init() { - localSchemeBuilder.Register(RegisterConversions) -} - -// RegisterConversions adds conversion functions to the given scheme. -// Public to allow building arbitrary schemes. -func RegisterConversions(s *runtime.Scheme) error { - if err := s.AddGeneratedConversionFunc((*ACMEAuthorization)(nil), (*acme.ACMEAuthorization)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1beta1_ACMEAuthorization_To_acme_ACMEAuthorization(a.(*ACMEAuthorization), b.(*acme.ACMEAuthorization), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*acme.ACMEAuthorization)(nil), (*ACMEAuthorization)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_acme_ACMEAuthorization_To_v1beta1_ACMEAuthorization(a.(*acme.ACMEAuthorization), b.(*ACMEAuthorization), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*ACMEChallenge)(nil), (*acme.ACMEChallenge)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1beta1_ACMEChallenge_To_acme_ACMEChallenge(a.(*ACMEChallenge), b.(*acme.ACMEChallenge), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*acme.ACMEChallenge)(nil), (*ACMEChallenge)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_acme_ACMEChallenge_To_v1beta1_ACMEChallenge(a.(*acme.ACMEChallenge), b.(*ACMEChallenge), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*ACMEChallengeSolver)(nil), (*acme.ACMEChallengeSolver)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1beta1_ACMEChallengeSolver_To_acme_ACMEChallengeSolver(a.(*ACMEChallengeSolver), b.(*acme.ACMEChallengeSolver), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*acme.ACMEChallengeSolver)(nil), (*ACMEChallengeSolver)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_acme_ACMEChallengeSolver_To_v1beta1_ACMEChallengeSolver(a.(*acme.ACMEChallengeSolver), b.(*ACMEChallengeSolver), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*ACMEChallengeSolverDNS01)(nil), (*acme.ACMEChallengeSolverDNS01)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1beta1_ACMEChallengeSolverDNS01_To_acme_ACMEChallengeSolverDNS01(a.(*ACMEChallengeSolverDNS01), b.(*acme.ACMEChallengeSolverDNS01), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*acme.ACMEChallengeSolverDNS01)(nil), (*ACMEChallengeSolverDNS01)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_acme_ACMEChallengeSolverDNS01_To_v1beta1_ACMEChallengeSolverDNS01(a.(*acme.ACMEChallengeSolverDNS01), b.(*ACMEChallengeSolverDNS01), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*ACMEChallengeSolverHTTP01)(nil), (*acme.ACMEChallengeSolverHTTP01)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1beta1_ACMEChallengeSolverHTTP01_To_acme_ACMEChallengeSolverHTTP01(a.(*ACMEChallengeSolverHTTP01), b.(*acme.ACMEChallengeSolverHTTP01), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*acme.ACMEChallengeSolverHTTP01)(nil), (*ACMEChallengeSolverHTTP01)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_acme_ACMEChallengeSolverHTTP01_To_v1beta1_ACMEChallengeSolverHTTP01(a.(*acme.ACMEChallengeSolverHTTP01), b.(*ACMEChallengeSolverHTTP01), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*ACMEChallengeSolverHTTP01GatewayHTTPRoute)(nil), (*acme.ACMEChallengeSolverHTTP01GatewayHTTPRoute)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1beta1_ACMEChallengeSolverHTTP01GatewayHTTPRoute_To_acme_ACMEChallengeSolverHTTP01GatewayHTTPRoute(a.(*ACMEChallengeSolverHTTP01GatewayHTTPRoute), b.(*acme.ACMEChallengeSolverHTTP01GatewayHTTPRoute), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*acme.ACMEChallengeSolverHTTP01GatewayHTTPRoute)(nil), (*ACMEChallengeSolverHTTP01GatewayHTTPRoute)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_acme_ACMEChallengeSolverHTTP01GatewayHTTPRoute_To_v1beta1_ACMEChallengeSolverHTTP01GatewayHTTPRoute(a.(*acme.ACMEChallengeSolverHTTP01GatewayHTTPRoute), b.(*ACMEChallengeSolverHTTP01GatewayHTTPRoute), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*ACMEChallengeSolverHTTP01Ingress)(nil), (*acme.ACMEChallengeSolverHTTP01Ingress)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1beta1_ACMEChallengeSolverHTTP01Ingress_To_acme_ACMEChallengeSolverHTTP01Ingress(a.(*ACMEChallengeSolverHTTP01Ingress), b.(*acme.ACMEChallengeSolverHTTP01Ingress), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*acme.ACMEChallengeSolverHTTP01Ingress)(nil), (*ACMEChallengeSolverHTTP01Ingress)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_acme_ACMEChallengeSolverHTTP01Ingress_To_v1beta1_ACMEChallengeSolverHTTP01Ingress(a.(*acme.ACMEChallengeSolverHTTP01Ingress), b.(*ACMEChallengeSolverHTTP01Ingress), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*ACMEChallengeSolverHTTP01IngressObjectMeta)(nil), (*acme.ACMEChallengeSolverHTTP01IngressObjectMeta)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1beta1_ACMEChallengeSolverHTTP01IngressObjectMeta_To_acme_ACMEChallengeSolverHTTP01IngressObjectMeta(a.(*ACMEChallengeSolverHTTP01IngressObjectMeta), b.(*acme.ACMEChallengeSolverHTTP01IngressObjectMeta), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*acme.ACMEChallengeSolverHTTP01IngressObjectMeta)(nil), (*ACMEChallengeSolverHTTP01IngressObjectMeta)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_acme_ACMEChallengeSolverHTTP01IngressObjectMeta_To_v1beta1_ACMEChallengeSolverHTTP01IngressObjectMeta(a.(*acme.ACMEChallengeSolverHTTP01IngressObjectMeta), b.(*ACMEChallengeSolverHTTP01IngressObjectMeta), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*ACMEChallengeSolverHTTP01IngressPodObjectMeta)(nil), (*acme.ACMEChallengeSolverHTTP01IngressPodObjectMeta)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1beta1_ACMEChallengeSolverHTTP01IngressPodObjectMeta_To_acme_ACMEChallengeSolverHTTP01IngressPodObjectMeta(a.(*ACMEChallengeSolverHTTP01IngressPodObjectMeta), b.(*acme.ACMEChallengeSolverHTTP01IngressPodObjectMeta), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*acme.ACMEChallengeSolverHTTP01IngressPodObjectMeta)(nil), (*ACMEChallengeSolverHTTP01IngressPodObjectMeta)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_acme_ACMEChallengeSolverHTTP01IngressPodObjectMeta_To_v1beta1_ACMEChallengeSolverHTTP01IngressPodObjectMeta(a.(*acme.ACMEChallengeSolverHTTP01IngressPodObjectMeta), b.(*ACMEChallengeSolverHTTP01IngressPodObjectMeta), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*ACMEChallengeSolverHTTP01IngressPodSpec)(nil), (*acme.ACMEChallengeSolverHTTP01IngressPodSpec)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1beta1_ACMEChallengeSolverHTTP01IngressPodSpec_To_acme_ACMEChallengeSolverHTTP01IngressPodSpec(a.(*ACMEChallengeSolverHTTP01IngressPodSpec), b.(*acme.ACMEChallengeSolverHTTP01IngressPodSpec), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*acme.ACMEChallengeSolverHTTP01IngressPodSpec)(nil), (*ACMEChallengeSolverHTTP01IngressPodSpec)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_acme_ACMEChallengeSolverHTTP01IngressPodSpec_To_v1beta1_ACMEChallengeSolverHTTP01IngressPodSpec(a.(*acme.ACMEChallengeSolverHTTP01IngressPodSpec), b.(*ACMEChallengeSolverHTTP01IngressPodSpec), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*ACMEChallengeSolverHTTP01IngressPodTemplate)(nil), (*acme.ACMEChallengeSolverHTTP01IngressPodTemplate)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1beta1_ACMEChallengeSolverHTTP01IngressPodTemplate_To_acme_ACMEChallengeSolverHTTP01IngressPodTemplate(a.(*ACMEChallengeSolverHTTP01IngressPodTemplate), b.(*acme.ACMEChallengeSolverHTTP01IngressPodTemplate), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*acme.ACMEChallengeSolverHTTP01IngressPodTemplate)(nil), (*ACMEChallengeSolverHTTP01IngressPodTemplate)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_acme_ACMEChallengeSolverHTTP01IngressPodTemplate_To_v1beta1_ACMEChallengeSolverHTTP01IngressPodTemplate(a.(*acme.ACMEChallengeSolverHTTP01IngressPodTemplate), b.(*ACMEChallengeSolverHTTP01IngressPodTemplate), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*ACMEChallengeSolverHTTP01IngressTemplate)(nil), (*acme.ACMEChallengeSolverHTTP01IngressTemplate)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1beta1_ACMEChallengeSolverHTTP01IngressTemplate_To_acme_ACMEChallengeSolverHTTP01IngressTemplate(a.(*ACMEChallengeSolverHTTP01IngressTemplate), b.(*acme.ACMEChallengeSolverHTTP01IngressTemplate), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*acme.ACMEChallengeSolverHTTP01IngressTemplate)(nil), (*ACMEChallengeSolverHTTP01IngressTemplate)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_acme_ACMEChallengeSolverHTTP01IngressTemplate_To_v1beta1_ACMEChallengeSolverHTTP01IngressTemplate(a.(*acme.ACMEChallengeSolverHTTP01IngressTemplate), b.(*ACMEChallengeSolverHTTP01IngressTemplate), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*ACMEExternalAccountBinding)(nil), (*acme.ACMEExternalAccountBinding)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1beta1_ACMEExternalAccountBinding_To_acme_ACMEExternalAccountBinding(a.(*ACMEExternalAccountBinding), b.(*acme.ACMEExternalAccountBinding), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*acme.ACMEExternalAccountBinding)(nil), (*ACMEExternalAccountBinding)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_acme_ACMEExternalAccountBinding_To_v1beta1_ACMEExternalAccountBinding(a.(*acme.ACMEExternalAccountBinding), b.(*ACMEExternalAccountBinding), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*ACMEIssuerDNS01ProviderAcmeDNS)(nil), (*acme.ACMEIssuerDNS01ProviderAcmeDNS)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1beta1_ACMEIssuerDNS01ProviderAcmeDNS_To_acme_ACMEIssuerDNS01ProviderAcmeDNS(a.(*ACMEIssuerDNS01ProviderAcmeDNS), b.(*acme.ACMEIssuerDNS01ProviderAcmeDNS), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*acme.ACMEIssuerDNS01ProviderAcmeDNS)(nil), (*ACMEIssuerDNS01ProviderAcmeDNS)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_acme_ACMEIssuerDNS01ProviderAcmeDNS_To_v1beta1_ACMEIssuerDNS01ProviderAcmeDNS(a.(*acme.ACMEIssuerDNS01ProviderAcmeDNS), b.(*ACMEIssuerDNS01ProviderAcmeDNS), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*ACMEIssuerDNS01ProviderAkamai)(nil), (*acme.ACMEIssuerDNS01ProviderAkamai)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1beta1_ACMEIssuerDNS01ProviderAkamai_To_acme_ACMEIssuerDNS01ProviderAkamai(a.(*ACMEIssuerDNS01ProviderAkamai), b.(*acme.ACMEIssuerDNS01ProviderAkamai), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*acme.ACMEIssuerDNS01ProviderAkamai)(nil), (*ACMEIssuerDNS01ProviderAkamai)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_acme_ACMEIssuerDNS01ProviderAkamai_To_v1beta1_ACMEIssuerDNS01ProviderAkamai(a.(*acme.ACMEIssuerDNS01ProviderAkamai), b.(*ACMEIssuerDNS01ProviderAkamai), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*ACMEIssuerDNS01ProviderAzureDNS)(nil), (*acme.ACMEIssuerDNS01ProviderAzureDNS)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1beta1_ACMEIssuerDNS01ProviderAzureDNS_To_acme_ACMEIssuerDNS01ProviderAzureDNS(a.(*ACMEIssuerDNS01ProviderAzureDNS), b.(*acme.ACMEIssuerDNS01ProviderAzureDNS), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*acme.ACMEIssuerDNS01ProviderAzureDNS)(nil), (*ACMEIssuerDNS01ProviderAzureDNS)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_acme_ACMEIssuerDNS01ProviderAzureDNS_To_v1beta1_ACMEIssuerDNS01ProviderAzureDNS(a.(*acme.ACMEIssuerDNS01ProviderAzureDNS), b.(*ACMEIssuerDNS01ProviderAzureDNS), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*ACMEIssuerDNS01ProviderCloudDNS)(nil), (*acme.ACMEIssuerDNS01ProviderCloudDNS)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1beta1_ACMEIssuerDNS01ProviderCloudDNS_To_acme_ACMEIssuerDNS01ProviderCloudDNS(a.(*ACMEIssuerDNS01ProviderCloudDNS), b.(*acme.ACMEIssuerDNS01ProviderCloudDNS), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*acme.ACMEIssuerDNS01ProviderCloudDNS)(nil), (*ACMEIssuerDNS01ProviderCloudDNS)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_acme_ACMEIssuerDNS01ProviderCloudDNS_To_v1beta1_ACMEIssuerDNS01ProviderCloudDNS(a.(*acme.ACMEIssuerDNS01ProviderCloudDNS), b.(*ACMEIssuerDNS01ProviderCloudDNS), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*ACMEIssuerDNS01ProviderCloudflare)(nil), (*acme.ACMEIssuerDNS01ProviderCloudflare)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1beta1_ACMEIssuerDNS01ProviderCloudflare_To_acme_ACMEIssuerDNS01ProviderCloudflare(a.(*ACMEIssuerDNS01ProviderCloudflare), b.(*acme.ACMEIssuerDNS01ProviderCloudflare), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*acme.ACMEIssuerDNS01ProviderCloudflare)(nil), (*ACMEIssuerDNS01ProviderCloudflare)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_acme_ACMEIssuerDNS01ProviderCloudflare_To_v1beta1_ACMEIssuerDNS01ProviderCloudflare(a.(*acme.ACMEIssuerDNS01ProviderCloudflare), b.(*ACMEIssuerDNS01ProviderCloudflare), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*ACMEIssuerDNS01ProviderDigitalOcean)(nil), (*acme.ACMEIssuerDNS01ProviderDigitalOcean)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1beta1_ACMEIssuerDNS01ProviderDigitalOcean_To_acme_ACMEIssuerDNS01ProviderDigitalOcean(a.(*ACMEIssuerDNS01ProviderDigitalOcean), b.(*acme.ACMEIssuerDNS01ProviderDigitalOcean), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*acme.ACMEIssuerDNS01ProviderDigitalOcean)(nil), (*ACMEIssuerDNS01ProviderDigitalOcean)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_acme_ACMEIssuerDNS01ProviderDigitalOcean_To_v1beta1_ACMEIssuerDNS01ProviderDigitalOcean(a.(*acme.ACMEIssuerDNS01ProviderDigitalOcean), b.(*ACMEIssuerDNS01ProviderDigitalOcean), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*ACMEIssuerDNS01ProviderRFC2136)(nil), (*acme.ACMEIssuerDNS01ProviderRFC2136)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1beta1_ACMEIssuerDNS01ProviderRFC2136_To_acme_ACMEIssuerDNS01ProviderRFC2136(a.(*ACMEIssuerDNS01ProviderRFC2136), b.(*acme.ACMEIssuerDNS01ProviderRFC2136), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*acme.ACMEIssuerDNS01ProviderRFC2136)(nil), (*ACMEIssuerDNS01ProviderRFC2136)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_acme_ACMEIssuerDNS01ProviderRFC2136_To_v1beta1_ACMEIssuerDNS01ProviderRFC2136(a.(*acme.ACMEIssuerDNS01ProviderRFC2136), b.(*ACMEIssuerDNS01ProviderRFC2136), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*ACMEIssuerDNS01ProviderRoute53)(nil), (*acme.ACMEIssuerDNS01ProviderRoute53)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1beta1_ACMEIssuerDNS01ProviderRoute53_To_acme_ACMEIssuerDNS01ProviderRoute53(a.(*ACMEIssuerDNS01ProviderRoute53), b.(*acme.ACMEIssuerDNS01ProviderRoute53), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*acme.ACMEIssuerDNS01ProviderRoute53)(nil), (*ACMEIssuerDNS01ProviderRoute53)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_acme_ACMEIssuerDNS01ProviderRoute53_To_v1beta1_ACMEIssuerDNS01ProviderRoute53(a.(*acme.ACMEIssuerDNS01ProviderRoute53), b.(*ACMEIssuerDNS01ProviderRoute53), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*ACMEIssuerDNS01ProviderWebhook)(nil), (*acme.ACMEIssuerDNS01ProviderWebhook)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1beta1_ACMEIssuerDNS01ProviderWebhook_To_acme_ACMEIssuerDNS01ProviderWebhook(a.(*ACMEIssuerDNS01ProviderWebhook), b.(*acme.ACMEIssuerDNS01ProviderWebhook), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*acme.ACMEIssuerDNS01ProviderWebhook)(nil), (*ACMEIssuerDNS01ProviderWebhook)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_acme_ACMEIssuerDNS01ProviderWebhook_To_v1beta1_ACMEIssuerDNS01ProviderWebhook(a.(*acme.ACMEIssuerDNS01ProviderWebhook), b.(*ACMEIssuerDNS01ProviderWebhook), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*ACMEIssuerStatus)(nil), (*acme.ACMEIssuerStatus)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1beta1_ACMEIssuerStatus_To_acme_ACMEIssuerStatus(a.(*ACMEIssuerStatus), b.(*acme.ACMEIssuerStatus), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*acme.ACMEIssuerStatus)(nil), (*ACMEIssuerStatus)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_acme_ACMEIssuerStatus_To_v1beta1_ACMEIssuerStatus(a.(*acme.ACMEIssuerStatus), b.(*ACMEIssuerStatus), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*AzureManagedIdentity)(nil), (*acme.AzureManagedIdentity)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1beta1_AzureManagedIdentity_To_acme_AzureManagedIdentity(a.(*AzureManagedIdentity), b.(*acme.AzureManagedIdentity), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*acme.AzureManagedIdentity)(nil), (*AzureManagedIdentity)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_acme_AzureManagedIdentity_To_v1beta1_AzureManagedIdentity(a.(*acme.AzureManagedIdentity), b.(*AzureManagedIdentity), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*CertificateDNSNameSelector)(nil), (*acme.CertificateDNSNameSelector)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1beta1_CertificateDNSNameSelector_To_acme_CertificateDNSNameSelector(a.(*CertificateDNSNameSelector), b.(*acme.CertificateDNSNameSelector), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*acme.CertificateDNSNameSelector)(nil), (*CertificateDNSNameSelector)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_acme_CertificateDNSNameSelector_To_v1beta1_CertificateDNSNameSelector(a.(*acme.CertificateDNSNameSelector), b.(*CertificateDNSNameSelector), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*Challenge)(nil), (*acme.Challenge)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1beta1_Challenge_To_acme_Challenge(a.(*Challenge), b.(*acme.Challenge), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*acme.Challenge)(nil), (*Challenge)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_acme_Challenge_To_v1beta1_Challenge(a.(*acme.Challenge), b.(*Challenge), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*ChallengeList)(nil), (*acme.ChallengeList)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1beta1_ChallengeList_To_acme_ChallengeList(a.(*ChallengeList), b.(*acme.ChallengeList), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*acme.ChallengeList)(nil), (*ChallengeList)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_acme_ChallengeList_To_v1beta1_ChallengeList(a.(*acme.ChallengeList), b.(*ChallengeList), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*ChallengeSpec)(nil), (*acme.ChallengeSpec)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1beta1_ChallengeSpec_To_acme_ChallengeSpec(a.(*ChallengeSpec), b.(*acme.ChallengeSpec), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*acme.ChallengeSpec)(nil), (*ChallengeSpec)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_acme_ChallengeSpec_To_v1beta1_ChallengeSpec(a.(*acme.ChallengeSpec), b.(*ChallengeSpec), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*ChallengeStatus)(nil), (*acme.ChallengeStatus)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1beta1_ChallengeStatus_To_acme_ChallengeStatus(a.(*ChallengeStatus), b.(*acme.ChallengeStatus), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*acme.ChallengeStatus)(nil), (*ChallengeStatus)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_acme_ChallengeStatus_To_v1beta1_ChallengeStatus(a.(*acme.ChallengeStatus), b.(*ChallengeStatus), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*Order)(nil), (*acme.Order)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1beta1_Order_To_acme_Order(a.(*Order), b.(*acme.Order), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*acme.Order)(nil), (*Order)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_acme_Order_To_v1beta1_Order(a.(*acme.Order), b.(*Order), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*OrderList)(nil), (*acme.OrderList)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1beta1_OrderList_To_acme_OrderList(a.(*OrderList), b.(*acme.OrderList), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*acme.OrderList)(nil), (*OrderList)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_acme_OrderList_To_v1beta1_OrderList(a.(*acme.OrderList), b.(*OrderList), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*OrderSpec)(nil), (*acme.OrderSpec)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1beta1_OrderSpec_To_acme_OrderSpec(a.(*OrderSpec), b.(*acme.OrderSpec), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*acme.OrderSpec)(nil), (*OrderSpec)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_acme_OrderSpec_To_v1beta1_OrderSpec(a.(*acme.OrderSpec), b.(*OrderSpec), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*OrderStatus)(nil), (*acme.OrderStatus)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1beta1_OrderStatus_To_acme_OrderStatus(a.(*OrderStatus), b.(*acme.OrderStatus), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*acme.OrderStatus)(nil), (*OrderStatus)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_acme_OrderStatus_To_v1beta1_OrderStatus(a.(*acme.OrderStatus), b.(*OrderStatus), scope) - }); err != nil { - return err - } - if err := s.AddConversionFunc((*acme.ACMEIssuer)(nil), (*ACMEIssuer)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_acme_ACMEIssuer_To_v1beta1_ACMEIssuer(a.(*acme.ACMEIssuer), b.(*ACMEIssuer), scope) - }); err != nil { - return err - } - if err := s.AddConversionFunc((*ACMEIssuer)(nil), (*acme.ACMEIssuer)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1beta1_ACMEIssuer_To_acme_ACMEIssuer(a.(*ACMEIssuer), b.(*acme.ACMEIssuer), scope) - }); err != nil { - return err - } - return nil -} - -func autoConvert_v1beta1_ACMEAuthorization_To_acme_ACMEAuthorization(in *ACMEAuthorization, out *acme.ACMEAuthorization, s conversion.Scope) error { - out.URL = in.URL - out.Identifier = in.Identifier - out.Wildcard = (*bool)(unsafe.Pointer(in.Wildcard)) - out.InitialState = acme.State(in.InitialState) - out.Challenges = *(*[]acme.ACMEChallenge)(unsafe.Pointer(&in.Challenges)) - return nil -} - -// Convert_v1beta1_ACMEAuthorization_To_acme_ACMEAuthorization is an autogenerated conversion function. -func Convert_v1beta1_ACMEAuthorization_To_acme_ACMEAuthorization(in *ACMEAuthorization, out *acme.ACMEAuthorization, s conversion.Scope) error { - return autoConvert_v1beta1_ACMEAuthorization_To_acme_ACMEAuthorization(in, out, s) -} - -func autoConvert_acme_ACMEAuthorization_To_v1beta1_ACMEAuthorization(in *acme.ACMEAuthorization, out *ACMEAuthorization, s conversion.Scope) error { - out.URL = in.URL - out.Identifier = in.Identifier - out.Wildcard = (*bool)(unsafe.Pointer(in.Wildcard)) - out.InitialState = State(in.InitialState) - out.Challenges = *(*[]ACMEChallenge)(unsafe.Pointer(&in.Challenges)) - return nil -} - -// Convert_acme_ACMEAuthorization_To_v1beta1_ACMEAuthorization is an autogenerated conversion function. -func Convert_acme_ACMEAuthorization_To_v1beta1_ACMEAuthorization(in *acme.ACMEAuthorization, out *ACMEAuthorization, s conversion.Scope) error { - return autoConvert_acme_ACMEAuthorization_To_v1beta1_ACMEAuthorization(in, out, s) -} - -func autoConvert_v1beta1_ACMEChallenge_To_acme_ACMEChallenge(in *ACMEChallenge, out *acme.ACMEChallenge, s conversion.Scope) error { - out.URL = in.URL - out.Token = in.Token - out.Type = in.Type - return nil -} - -// Convert_v1beta1_ACMEChallenge_To_acme_ACMEChallenge is an autogenerated conversion function. -func Convert_v1beta1_ACMEChallenge_To_acme_ACMEChallenge(in *ACMEChallenge, out *acme.ACMEChallenge, s conversion.Scope) error { - return autoConvert_v1beta1_ACMEChallenge_To_acme_ACMEChallenge(in, out, s) -} - -func autoConvert_acme_ACMEChallenge_To_v1beta1_ACMEChallenge(in *acme.ACMEChallenge, out *ACMEChallenge, s conversion.Scope) error { - out.URL = in.URL - out.Token = in.Token - out.Type = in.Type - return nil -} - -// Convert_acme_ACMEChallenge_To_v1beta1_ACMEChallenge is an autogenerated conversion function. -func Convert_acme_ACMEChallenge_To_v1beta1_ACMEChallenge(in *acme.ACMEChallenge, out *ACMEChallenge, s conversion.Scope) error { - return autoConvert_acme_ACMEChallenge_To_v1beta1_ACMEChallenge(in, out, s) -} - -func autoConvert_v1beta1_ACMEChallengeSolver_To_acme_ACMEChallengeSolver(in *ACMEChallengeSolver, out *acme.ACMEChallengeSolver, s conversion.Scope) error { - out.Selector = (*acme.CertificateDNSNameSelector)(unsafe.Pointer(in.Selector)) - out.HTTP01 = (*acme.ACMEChallengeSolverHTTP01)(unsafe.Pointer(in.HTTP01)) - if in.DNS01 != nil { - in, out := &in.DNS01, &out.DNS01 - *out = new(acme.ACMEChallengeSolverDNS01) - if err := Convert_v1beta1_ACMEChallengeSolverDNS01_To_acme_ACMEChallengeSolverDNS01(*in, *out, s); err != nil { - return err - } - } else { - out.DNS01 = nil - } - return nil -} - -// Convert_v1beta1_ACMEChallengeSolver_To_acme_ACMEChallengeSolver is an autogenerated conversion function. -func Convert_v1beta1_ACMEChallengeSolver_To_acme_ACMEChallengeSolver(in *ACMEChallengeSolver, out *acme.ACMEChallengeSolver, s conversion.Scope) error { - return autoConvert_v1beta1_ACMEChallengeSolver_To_acme_ACMEChallengeSolver(in, out, s) -} - -func autoConvert_acme_ACMEChallengeSolver_To_v1beta1_ACMEChallengeSolver(in *acme.ACMEChallengeSolver, out *ACMEChallengeSolver, s conversion.Scope) error { - out.Selector = (*CertificateDNSNameSelector)(unsafe.Pointer(in.Selector)) - out.HTTP01 = (*ACMEChallengeSolverHTTP01)(unsafe.Pointer(in.HTTP01)) - if in.DNS01 != nil { - in, out := &in.DNS01, &out.DNS01 - *out = new(ACMEChallengeSolverDNS01) - if err := Convert_acme_ACMEChallengeSolverDNS01_To_v1beta1_ACMEChallengeSolverDNS01(*in, *out, s); err != nil { - return err - } - } else { - out.DNS01 = nil - } - return nil -} - -// Convert_acme_ACMEChallengeSolver_To_v1beta1_ACMEChallengeSolver is an autogenerated conversion function. -func Convert_acme_ACMEChallengeSolver_To_v1beta1_ACMEChallengeSolver(in *acme.ACMEChallengeSolver, out *ACMEChallengeSolver, s conversion.Scope) error { - return autoConvert_acme_ACMEChallengeSolver_To_v1beta1_ACMEChallengeSolver(in, out, s) -} - -func autoConvert_v1beta1_ACMEChallengeSolverDNS01_To_acme_ACMEChallengeSolverDNS01(in *ACMEChallengeSolverDNS01, out *acme.ACMEChallengeSolverDNS01, s conversion.Scope) error { - out.CNAMEStrategy = acme.CNAMEStrategy(in.CNAMEStrategy) - if in.Akamai != nil { - in, out := &in.Akamai, &out.Akamai - *out = new(acme.ACMEIssuerDNS01ProviderAkamai) - if err := Convert_v1beta1_ACMEIssuerDNS01ProviderAkamai_To_acme_ACMEIssuerDNS01ProviderAkamai(*in, *out, s); err != nil { - return err - } - } else { - out.Akamai = nil - } - if in.CloudDNS != nil { - in, out := &in.CloudDNS, &out.CloudDNS - *out = new(acme.ACMEIssuerDNS01ProviderCloudDNS) - if err := Convert_v1beta1_ACMEIssuerDNS01ProviderCloudDNS_To_acme_ACMEIssuerDNS01ProviderCloudDNS(*in, *out, s); err != nil { - return err - } - } else { - out.CloudDNS = nil - } - if in.Cloudflare != nil { - in, out := &in.Cloudflare, &out.Cloudflare - *out = new(acme.ACMEIssuerDNS01ProviderCloudflare) - if err := Convert_v1beta1_ACMEIssuerDNS01ProviderCloudflare_To_acme_ACMEIssuerDNS01ProviderCloudflare(*in, *out, s); err != nil { - return err - } - } else { - out.Cloudflare = nil - } - if in.Route53 != nil { - in, out := &in.Route53, &out.Route53 - *out = new(acme.ACMEIssuerDNS01ProviderRoute53) - if err := Convert_v1beta1_ACMEIssuerDNS01ProviderRoute53_To_acme_ACMEIssuerDNS01ProviderRoute53(*in, *out, s); err != nil { - return err - } - } else { - out.Route53 = nil - } - if in.AzureDNS != nil { - in, out := &in.AzureDNS, &out.AzureDNS - *out = new(acme.ACMEIssuerDNS01ProviderAzureDNS) - if err := Convert_v1beta1_ACMEIssuerDNS01ProviderAzureDNS_To_acme_ACMEIssuerDNS01ProviderAzureDNS(*in, *out, s); err != nil { - return err - } - } else { - out.AzureDNS = nil - } - if in.DigitalOcean != nil { - in, out := &in.DigitalOcean, &out.DigitalOcean - *out = new(acme.ACMEIssuerDNS01ProviderDigitalOcean) - if err := Convert_v1beta1_ACMEIssuerDNS01ProviderDigitalOcean_To_acme_ACMEIssuerDNS01ProviderDigitalOcean(*in, *out, s); err != nil { - return err - } - } else { - out.DigitalOcean = nil - } - if in.AcmeDNS != nil { - in, out := &in.AcmeDNS, &out.AcmeDNS - *out = new(acme.ACMEIssuerDNS01ProviderAcmeDNS) - if err := Convert_v1beta1_ACMEIssuerDNS01ProviderAcmeDNS_To_acme_ACMEIssuerDNS01ProviderAcmeDNS(*in, *out, s); err != nil { - return err - } - } else { - out.AcmeDNS = nil - } - if in.RFC2136 != nil { - in, out := &in.RFC2136, &out.RFC2136 - *out = new(acme.ACMEIssuerDNS01ProviderRFC2136) - if err := Convert_v1beta1_ACMEIssuerDNS01ProviderRFC2136_To_acme_ACMEIssuerDNS01ProviderRFC2136(*in, *out, s); err != nil { - return err - } - } else { - out.RFC2136 = nil - } - out.Webhook = (*acme.ACMEIssuerDNS01ProviderWebhook)(unsafe.Pointer(in.Webhook)) - return nil -} - -// Convert_v1beta1_ACMEChallengeSolverDNS01_To_acme_ACMEChallengeSolverDNS01 is an autogenerated conversion function. -func Convert_v1beta1_ACMEChallengeSolverDNS01_To_acme_ACMEChallengeSolverDNS01(in *ACMEChallengeSolverDNS01, out *acme.ACMEChallengeSolverDNS01, s conversion.Scope) error { - return autoConvert_v1beta1_ACMEChallengeSolverDNS01_To_acme_ACMEChallengeSolverDNS01(in, out, s) -} - -func autoConvert_acme_ACMEChallengeSolverDNS01_To_v1beta1_ACMEChallengeSolverDNS01(in *acme.ACMEChallengeSolverDNS01, out *ACMEChallengeSolverDNS01, s conversion.Scope) error { - out.CNAMEStrategy = CNAMEStrategy(in.CNAMEStrategy) - if in.Akamai != nil { - in, out := &in.Akamai, &out.Akamai - *out = new(ACMEIssuerDNS01ProviderAkamai) - if err := Convert_acme_ACMEIssuerDNS01ProviderAkamai_To_v1beta1_ACMEIssuerDNS01ProviderAkamai(*in, *out, s); err != nil { - return err - } - } else { - out.Akamai = nil - } - if in.CloudDNS != nil { - in, out := &in.CloudDNS, &out.CloudDNS - *out = new(ACMEIssuerDNS01ProviderCloudDNS) - if err := Convert_acme_ACMEIssuerDNS01ProviderCloudDNS_To_v1beta1_ACMEIssuerDNS01ProviderCloudDNS(*in, *out, s); err != nil { - return err - } - } else { - out.CloudDNS = nil - } - if in.Cloudflare != nil { - in, out := &in.Cloudflare, &out.Cloudflare - *out = new(ACMEIssuerDNS01ProviderCloudflare) - if err := Convert_acme_ACMEIssuerDNS01ProviderCloudflare_To_v1beta1_ACMEIssuerDNS01ProviderCloudflare(*in, *out, s); err != nil { - return err - } - } else { - out.Cloudflare = nil - } - if in.Route53 != nil { - in, out := &in.Route53, &out.Route53 - *out = new(ACMEIssuerDNS01ProviderRoute53) - if err := Convert_acme_ACMEIssuerDNS01ProviderRoute53_To_v1beta1_ACMEIssuerDNS01ProviderRoute53(*in, *out, s); err != nil { - return err - } - } else { - out.Route53 = nil - } - if in.AzureDNS != nil { - in, out := &in.AzureDNS, &out.AzureDNS - *out = new(ACMEIssuerDNS01ProviderAzureDNS) - if err := Convert_acme_ACMEIssuerDNS01ProviderAzureDNS_To_v1beta1_ACMEIssuerDNS01ProviderAzureDNS(*in, *out, s); err != nil { - return err - } - } else { - out.AzureDNS = nil - } - if in.DigitalOcean != nil { - in, out := &in.DigitalOcean, &out.DigitalOcean - *out = new(ACMEIssuerDNS01ProviderDigitalOcean) - if err := Convert_acme_ACMEIssuerDNS01ProviderDigitalOcean_To_v1beta1_ACMEIssuerDNS01ProviderDigitalOcean(*in, *out, s); err != nil { - return err - } - } else { - out.DigitalOcean = nil - } - if in.AcmeDNS != nil { - in, out := &in.AcmeDNS, &out.AcmeDNS - *out = new(ACMEIssuerDNS01ProviderAcmeDNS) - if err := Convert_acme_ACMEIssuerDNS01ProviderAcmeDNS_To_v1beta1_ACMEIssuerDNS01ProviderAcmeDNS(*in, *out, s); err != nil { - return err - } - } else { - out.AcmeDNS = nil - } - if in.RFC2136 != nil { - in, out := &in.RFC2136, &out.RFC2136 - *out = new(ACMEIssuerDNS01ProviderRFC2136) - if err := Convert_acme_ACMEIssuerDNS01ProviderRFC2136_To_v1beta1_ACMEIssuerDNS01ProviderRFC2136(*in, *out, s); err != nil { - return err - } - } else { - out.RFC2136 = nil - } - out.Webhook = (*ACMEIssuerDNS01ProviderWebhook)(unsafe.Pointer(in.Webhook)) - return nil -} - -// Convert_acme_ACMEChallengeSolverDNS01_To_v1beta1_ACMEChallengeSolverDNS01 is an autogenerated conversion function. -func Convert_acme_ACMEChallengeSolverDNS01_To_v1beta1_ACMEChallengeSolverDNS01(in *acme.ACMEChallengeSolverDNS01, out *ACMEChallengeSolverDNS01, s conversion.Scope) error { - return autoConvert_acme_ACMEChallengeSolverDNS01_To_v1beta1_ACMEChallengeSolverDNS01(in, out, s) -} - -func autoConvert_v1beta1_ACMEChallengeSolverHTTP01_To_acme_ACMEChallengeSolverHTTP01(in *ACMEChallengeSolverHTTP01, out *acme.ACMEChallengeSolverHTTP01, s conversion.Scope) error { - out.Ingress = (*acme.ACMEChallengeSolverHTTP01Ingress)(unsafe.Pointer(in.Ingress)) - out.GatewayHTTPRoute = (*acme.ACMEChallengeSolverHTTP01GatewayHTTPRoute)(unsafe.Pointer(in.GatewayHTTPRoute)) - return nil -} - -// Convert_v1beta1_ACMEChallengeSolverHTTP01_To_acme_ACMEChallengeSolverHTTP01 is an autogenerated conversion function. -func Convert_v1beta1_ACMEChallengeSolverHTTP01_To_acme_ACMEChallengeSolverHTTP01(in *ACMEChallengeSolverHTTP01, out *acme.ACMEChallengeSolverHTTP01, s conversion.Scope) error { - return autoConvert_v1beta1_ACMEChallengeSolverHTTP01_To_acme_ACMEChallengeSolverHTTP01(in, out, s) -} - -func autoConvert_acme_ACMEChallengeSolverHTTP01_To_v1beta1_ACMEChallengeSolverHTTP01(in *acme.ACMEChallengeSolverHTTP01, out *ACMEChallengeSolverHTTP01, s conversion.Scope) error { - out.Ingress = (*ACMEChallengeSolverHTTP01Ingress)(unsafe.Pointer(in.Ingress)) - out.GatewayHTTPRoute = (*ACMEChallengeSolverHTTP01GatewayHTTPRoute)(unsafe.Pointer(in.GatewayHTTPRoute)) - return nil -} - -// Convert_acme_ACMEChallengeSolverHTTP01_To_v1beta1_ACMEChallengeSolverHTTP01 is an autogenerated conversion function. -func Convert_acme_ACMEChallengeSolverHTTP01_To_v1beta1_ACMEChallengeSolverHTTP01(in *acme.ACMEChallengeSolverHTTP01, out *ACMEChallengeSolverHTTP01, s conversion.Scope) error { - return autoConvert_acme_ACMEChallengeSolverHTTP01_To_v1beta1_ACMEChallengeSolverHTTP01(in, out, s) -} - -func autoConvert_v1beta1_ACMEChallengeSolverHTTP01GatewayHTTPRoute_To_acme_ACMEChallengeSolverHTTP01GatewayHTTPRoute(in *ACMEChallengeSolverHTTP01GatewayHTTPRoute, out *acme.ACMEChallengeSolverHTTP01GatewayHTTPRoute, s conversion.Scope) error { - out.ServiceType = v1.ServiceType(in.ServiceType) - out.Labels = *(*map[string]string)(unsafe.Pointer(&in.Labels)) - out.ParentRefs = *(*[]v1alpha2.ParentReference)(unsafe.Pointer(&in.ParentRefs)) - return nil -} - -// Convert_v1beta1_ACMEChallengeSolverHTTP01GatewayHTTPRoute_To_acme_ACMEChallengeSolverHTTP01GatewayHTTPRoute is an autogenerated conversion function. -func Convert_v1beta1_ACMEChallengeSolverHTTP01GatewayHTTPRoute_To_acme_ACMEChallengeSolverHTTP01GatewayHTTPRoute(in *ACMEChallengeSolverHTTP01GatewayHTTPRoute, out *acme.ACMEChallengeSolverHTTP01GatewayHTTPRoute, s conversion.Scope) error { - return autoConvert_v1beta1_ACMEChallengeSolverHTTP01GatewayHTTPRoute_To_acme_ACMEChallengeSolverHTTP01GatewayHTTPRoute(in, out, s) -} - -func autoConvert_acme_ACMEChallengeSolverHTTP01GatewayHTTPRoute_To_v1beta1_ACMEChallengeSolverHTTP01GatewayHTTPRoute(in *acme.ACMEChallengeSolverHTTP01GatewayHTTPRoute, out *ACMEChallengeSolverHTTP01GatewayHTTPRoute, s conversion.Scope) error { - out.ServiceType = v1.ServiceType(in.ServiceType) - out.Labels = *(*map[string]string)(unsafe.Pointer(&in.Labels)) - out.ParentRefs = *(*[]v1alpha2.ParentReference)(unsafe.Pointer(&in.ParentRefs)) - return nil -} - -// Convert_acme_ACMEChallengeSolverHTTP01GatewayHTTPRoute_To_v1beta1_ACMEChallengeSolverHTTP01GatewayHTTPRoute is an autogenerated conversion function. -func Convert_acme_ACMEChallengeSolverHTTP01GatewayHTTPRoute_To_v1beta1_ACMEChallengeSolverHTTP01GatewayHTTPRoute(in *acme.ACMEChallengeSolverHTTP01GatewayHTTPRoute, out *ACMEChallengeSolverHTTP01GatewayHTTPRoute, s conversion.Scope) error { - return autoConvert_acme_ACMEChallengeSolverHTTP01GatewayHTTPRoute_To_v1beta1_ACMEChallengeSolverHTTP01GatewayHTTPRoute(in, out, s) -} - -func autoConvert_v1beta1_ACMEChallengeSolverHTTP01Ingress_To_acme_ACMEChallengeSolverHTTP01Ingress(in *ACMEChallengeSolverHTTP01Ingress, out *acme.ACMEChallengeSolverHTTP01Ingress, s conversion.Scope) error { - out.ServiceType = v1.ServiceType(in.ServiceType) - out.Class = (*string)(unsafe.Pointer(in.Class)) - out.Name = in.Name - out.PodTemplate = (*acme.ACMEChallengeSolverHTTP01IngressPodTemplate)(unsafe.Pointer(in.PodTemplate)) - out.IngressTemplate = (*acme.ACMEChallengeSolverHTTP01IngressTemplate)(unsafe.Pointer(in.IngressTemplate)) - return nil -} - -// Convert_v1beta1_ACMEChallengeSolverHTTP01Ingress_To_acme_ACMEChallengeSolverHTTP01Ingress is an autogenerated conversion function. -func Convert_v1beta1_ACMEChallengeSolverHTTP01Ingress_To_acme_ACMEChallengeSolverHTTP01Ingress(in *ACMEChallengeSolverHTTP01Ingress, out *acme.ACMEChallengeSolverHTTP01Ingress, s conversion.Scope) error { - return autoConvert_v1beta1_ACMEChallengeSolverHTTP01Ingress_To_acme_ACMEChallengeSolverHTTP01Ingress(in, out, s) -} - -func autoConvert_acme_ACMEChallengeSolverHTTP01Ingress_To_v1beta1_ACMEChallengeSolverHTTP01Ingress(in *acme.ACMEChallengeSolverHTTP01Ingress, out *ACMEChallengeSolverHTTP01Ingress, s conversion.Scope) error { - out.ServiceType = v1.ServiceType(in.ServiceType) - out.Class = (*string)(unsafe.Pointer(in.Class)) - out.Name = in.Name - out.PodTemplate = (*ACMEChallengeSolverHTTP01IngressPodTemplate)(unsafe.Pointer(in.PodTemplate)) - out.IngressTemplate = (*ACMEChallengeSolverHTTP01IngressTemplate)(unsafe.Pointer(in.IngressTemplate)) - return nil -} - -// Convert_acme_ACMEChallengeSolverHTTP01Ingress_To_v1beta1_ACMEChallengeSolverHTTP01Ingress is an autogenerated conversion function. -func Convert_acme_ACMEChallengeSolverHTTP01Ingress_To_v1beta1_ACMEChallengeSolverHTTP01Ingress(in *acme.ACMEChallengeSolverHTTP01Ingress, out *ACMEChallengeSolverHTTP01Ingress, s conversion.Scope) error { - return autoConvert_acme_ACMEChallengeSolverHTTP01Ingress_To_v1beta1_ACMEChallengeSolverHTTP01Ingress(in, out, s) -} - -func autoConvert_v1beta1_ACMEChallengeSolverHTTP01IngressObjectMeta_To_acme_ACMEChallengeSolverHTTP01IngressObjectMeta(in *ACMEChallengeSolverHTTP01IngressObjectMeta, out *acme.ACMEChallengeSolverHTTP01IngressObjectMeta, s conversion.Scope) error { - out.Annotations = *(*map[string]string)(unsafe.Pointer(&in.Annotations)) - out.Labels = *(*map[string]string)(unsafe.Pointer(&in.Labels)) - return nil -} - -// Convert_v1beta1_ACMEChallengeSolverHTTP01IngressObjectMeta_To_acme_ACMEChallengeSolverHTTP01IngressObjectMeta is an autogenerated conversion function. -func Convert_v1beta1_ACMEChallengeSolverHTTP01IngressObjectMeta_To_acme_ACMEChallengeSolverHTTP01IngressObjectMeta(in *ACMEChallengeSolverHTTP01IngressObjectMeta, out *acme.ACMEChallengeSolverHTTP01IngressObjectMeta, s conversion.Scope) error { - return autoConvert_v1beta1_ACMEChallengeSolverHTTP01IngressObjectMeta_To_acme_ACMEChallengeSolverHTTP01IngressObjectMeta(in, out, s) -} - -func autoConvert_acme_ACMEChallengeSolverHTTP01IngressObjectMeta_To_v1beta1_ACMEChallengeSolverHTTP01IngressObjectMeta(in *acme.ACMEChallengeSolverHTTP01IngressObjectMeta, out *ACMEChallengeSolverHTTP01IngressObjectMeta, s conversion.Scope) error { - out.Annotations = *(*map[string]string)(unsafe.Pointer(&in.Annotations)) - out.Labels = *(*map[string]string)(unsafe.Pointer(&in.Labels)) - return nil -} - -// Convert_acme_ACMEChallengeSolverHTTP01IngressObjectMeta_To_v1beta1_ACMEChallengeSolverHTTP01IngressObjectMeta is an autogenerated conversion function. -func Convert_acme_ACMEChallengeSolverHTTP01IngressObjectMeta_To_v1beta1_ACMEChallengeSolverHTTP01IngressObjectMeta(in *acme.ACMEChallengeSolverHTTP01IngressObjectMeta, out *ACMEChallengeSolverHTTP01IngressObjectMeta, s conversion.Scope) error { - return autoConvert_acme_ACMEChallengeSolverHTTP01IngressObjectMeta_To_v1beta1_ACMEChallengeSolverHTTP01IngressObjectMeta(in, out, s) -} - -func autoConvert_v1beta1_ACMEChallengeSolverHTTP01IngressPodObjectMeta_To_acme_ACMEChallengeSolverHTTP01IngressPodObjectMeta(in *ACMEChallengeSolverHTTP01IngressPodObjectMeta, out *acme.ACMEChallengeSolverHTTP01IngressPodObjectMeta, s conversion.Scope) error { - out.Annotations = *(*map[string]string)(unsafe.Pointer(&in.Annotations)) - out.Labels = *(*map[string]string)(unsafe.Pointer(&in.Labels)) - return nil -} - -// Convert_v1beta1_ACMEChallengeSolverHTTP01IngressPodObjectMeta_To_acme_ACMEChallengeSolverHTTP01IngressPodObjectMeta is an autogenerated conversion function. -func Convert_v1beta1_ACMEChallengeSolverHTTP01IngressPodObjectMeta_To_acme_ACMEChallengeSolverHTTP01IngressPodObjectMeta(in *ACMEChallengeSolverHTTP01IngressPodObjectMeta, out *acme.ACMEChallengeSolverHTTP01IngressPodObjectMeta, s conversion.Scope) error { - return autoConvert_v1beta1_ACMEChallengeSolverHTTP01IngressPodObjectMeta_To_acme_ACMEChallengeSolverHTTP01IngressPodObjectMeta(in, out, s) -} - -func autoConvert_acme_ACMEChallengeSolverHTTP01IngressPodObjectMeta_To_v1beta1_ACMEChallengeSolverHTTP01IngressPodObjectMeta(in *acme.ACMEChallengeSolverHTTP01IngressPodObjectMeta, out *ACMEChallengeSolverHTTP01IngressPodObjectMeta, s conversion.Scope) error { - out.Annotations = *(*map[string]string)(unsafe.Pointer(&in.Annotations)) - out.Labels = *(*map[string]string)(unsafe.Pointer(&in.Labels)) - return nil -} - -// Convert_acme_ACMEChallengeSolverHTTP01IngressPodObjectMeta_To_v1beta1_ACMEChallengeSolverHTTP01IngressPodObjectMeta is an autogenerated conversion function. -func Convert_acme_ACMEChallengeSolverHTTP01IngressPodObjectMeta_To_v1beta1_ACMEChallengeSolverHTTP01IngressPodObjectMeta(in *acme.ACMEChallengeSolverHTTP01IngressPodObjectMeta, out *ACMEChallengeSolverHTTP01IngressPodObjectMeta, s conversion.Scope) error { - return autoConvert_acme_ACMEChallengeSolverHTTP01IngressPodObjectMeta_To_v1beta1_ACMEChallengeSolverHTTP01IngressPodObjectMeta(in, out, s) -} - -func autoConvert_v1beta1_ACMEChallengeSolverHTTP01IngressPodSpec_To_acme_ACMEChallengeSolverHTTP01IngressPodSpec(in *ACMEChallengeSolverHTTP01IngressPodSpec, out *acme.ACMEChallengeSolverHTTP01IngressPodSpec, s conversion.Scope) error { - out.NodeSelector = *(*map[string]string)(unsafe.Pointer(&in.NodeSelector)) - out.Affinity = (*v1.Affinity)(unsafe.Pointer(in.Affinity)) - out.Tolerations = *(*[]v1.Toleration)(unsafe.Pointer(&in.Tolerations)) - out.PriorityClassName = in.PriorityClassName - out.ServiceAccountName = in.ServiceAccountName - return nil -} - -// Convert_v1beta1_ACMEChallengeSolverHTTP01IngressPodSpec_To_acme_ACMEChallengeSolverHTTP01IngressPodSpec is an autogenerated conversion function. -func Convert_v1beta1_ACMEChallengeSolverHTTP01IngressPodSpec_To_acme_ACMEChallengeSolverHTTP01IngressPodSpec(in *ACMEChallengeSolverHTTP01IngressPodSpec, out *acme.ACMEChallengeSolverHTTP01IngressPodSpec, s conversion.Scope) error { - return autoConvert_v1beta1_ACMEChallengeSolverHTTP01IngressPodSpec_To_acme_ACMEChallengeSolverHTTP01IngressPodSpec(in, out, s) -} - -func autoConvert_acme_ACMEChallengeSolverHTTP01IngressPodSpec_To_v1beta1_ACMEChallengeSolverHTTP01IngressPodSpec(in *acme.ACMEChallengeSolverHTTP01IngressPodSpec, out *ACMEChallengeSolverHTTP01IngressPodSpec, s conversion.Scope) error { - out.NodeSelector = *(*map[string]string)(unsafe.Pointer(&in.NodeSelector)) - out.Affinity = (*v1.Affinity)(unsafe.Pointer(in.Affinity)) - out.Tolerations = *(*[]v1.Toleration)(unsafe.Pointer(&in.Tolerations)) - out.PriorityClassName = in.PriorityClassName - out.ServiceAccountName = in.ServiceAccountName - return nil -} - -// Convert_acme_ACMEChallengeSolverHTTP01IngressPodSpec_To_v1beta1_ACMEChallengeSolverHTTP01IngressPodSpec is an autogenerated conversion function. -func Convert_acme_ACMEChallengeSolverHTTP01IngressPodSpec_To_v1beta1_ACMEChallengeSolverHTTP01IngressPodSpec(in *acme.ACMEChallengeSolverHTTP01IngressPodSpec, out *ACMEChallengeSolverHTTP01IngressPodSpec, s conversion.Scope) error { - return autoConvert_acme_ACMEChallengeSolverHTTP01IngressPodSpec_To_v1beta1_ACMEChallengeSolverHTTP01IngressPodSpec(in, out, s) -} - -func autoConvert_v1beta1_ACMEChallengeSolverHTTP01IngressPodTemplate_To_acme_ACMEChallengeSolverHTTP01IngressPodTemplate(in *ACMEChallengeSolverHTTP01IngressPodTemplate, out *acme.ACMEChallengeSolverHTTP01IngressPodTemplate, s conversion.Scope) error { - if err := Convert_v1beta1_ACMEChallengeSolverHTTP01IngressPodObjectMeta_To_acme_ACMEChallengeSolverHTTP01IngressPodObjectMeta(&in.ACMEChallengeSolverHTTP01IngressPodObjectMeta, &out.ACMEChallengeSolverHTTP01IngressPodObjectMeta, s); err != nil { - return err - } - if err := Convert_v1beta1_ACMEChallengeSolverHTTP01IngressPodSpec_To_acme_ACMEChallengeSolverHTTP01IngressPodSpec(&in.Spec, &out.Spec, s); err != nil { - return err - } - return nil -} - -// Convert_v1beta1_ACMEChallengeSolverHTTP01IngressPodTemplate_To_acme_ACMEChallengeSolverHTTP01IngressPodTemplate is an autogenerated conversion function. -func Convert_v1beta1_ACMEChallengeSolverHTTP01IngressPodTemplate_To_acme_ACMEChallengeSolverHTTP01IngressPodTemplate(in *ACMEChallengeSolverHTTP01IngressPodTemplate, out *acme.ACMEChallengeSolverHTTP01IngressPodTemplate, s conversion.Scope) error { - return autoConvert_v1beta1_ACMEChallengeSolverHTTP01IngressPodTemplate_To_acme_ACMEChallengeSolverHTTP01IngressPodTemplate(in, out, s) -} - -func autoConvert_acme_ACMEChallengeSolverHTTP01IngressPodTemplate_To_v1beta1_ACMEChallengeSolverHTTP01IngressPodTemplate(in *acme.ACMEChallengeSolverHTTP01IngressPodTemplate, out *ACMEChallengeSolverHTTP01IngressPodTemplate, s conversion.Scope) error { - if err := Convert_acme_ACMEChallengeSolverHTTP01IngressPodObjectMeta_To_v1beta1_ACMEChallengeSolverHTTP01IngressPodObjectMeta(&in.ACMEChallengeSolverHTTP01IngressPodObjectMeta, &out.ACMEChallengeSolverHTTP01IngressPodObjectMeta, s); err != nil { - return err - } - if err := Convert_acme_ACMEChallengeSolverHTTP01IngressPodSpec_To_v1beta1_ACMEChallengeSolverHTTP01IngressPodSpec(&in.Spec, &out.Spec, s); err != nil { - return err - } - return nil -} - -// Convert_acme_ACMEChallengeSolverHTTP01IngressPodTemplate_To_v1beta1_ACMEChallengeSolverHTTP01IngressPodTemplate is an autogenerated conversion function. -func Convert_acme_ACMEChallengeSolverHTTP01IngressPodTemplate_To_v1beta1_ACMEChallengeSolverHTTP01IngressPodTemplate(in *acme.ACMEChallengeSolverHTTP01IngressPodTemplate, out *ACMEChallengeSolverHTTP01IngressPodTemplate, s conversion.Scope) error { - return autoConvert_acme_ACMEChallengeSolverHTTP01IngressPodTemplate_To_v1beta1_ACMEChallengeSolverHTTP01IngressPodTemplate(in, out, s) -} - -func autoConvert_v1beta1_ACMEChallengeSolverHTTP01IngressTemplate_To_acme_ACMEChallengeSolverHTTP01IngressTemplate(in *ACMEChallengeSolverHTTP01IngressTemplate, out *acme.ACMEChallengeSolverHTTP01IngressTemplate, s conversion.Scope) error { - if err := Convert_v1beta1_ACMEChallengeSolverHTTP01IngressObjectMeta_To_acme_ACMEChallengeSolverHTTP01IngressObjectMeta(&in.ACMEChallengeSolverHTTP01IngressObjectMeta, &out.ACMEChallengeSolverHTTP01IngressObjectMeta, s); err != nil { - return err - } - return nil -} - -// Convert_v1beta1_ACMEChallengeSolverHTTP01IngressTemplate_To_acme_ACMEChallengeSolverHTTP01IngressTemplate is an autogenerated conversion function. -func Convert_v1beta1_ACMEChallengeSolverHTTP01IngressTemplate_To_acme_ACMEChallengeSolverHTTP01IngressTemplate(in *ACMEChallengeSolverHTTP01IngressTemplate, out *acme.ACMEChallengeSolverHTTP01IngressTemplate, s conversion.Scope) error { - return autoConvert_v1beta1_ACMEChallengeSolverHTTP01IngressTemplate_To_acme_ACMEChallengeSolverHTTP01IngressTemplate(in, out, s) -} - -func autoConvert_acme_ACMEChallengeSolverHTTP01IngressTemplate_To_v1beta1_ACMEChallengeSolverHTTP01IngressTemplate(in *acme.ACMEChallengeSolverHTTP01IngressTemplate, out *ACMEChallengeSolverHTTP01IngressTemplate, s conversion.Scope) error { - if err := Convert_acme_ACMEChallengeSolverHTTP01IngressObjectMeta_To_v1beta1_ACMEChallengeSolverHTTP01IngressObjectMeta(&in.ACMEChallengeSolverHTTP01IngressObjectMeta, &out.ACMEChallengeSolverHTTP01IngressObjectMeta, s); err != nil { - return err - } - return nil -} - -// Convert_acme_ACMEChallengeSolverHTTP01IngressTemplate_To_v1beta1_ACMEChallengeSolverHTTP01IngressTemplate is an autogenerated conversion function. -func Convert_acme_ACMEChallengeSolverHTTP01IngressTemplate_To_v1beta1_ACMEChallengeSolverHTTP01IngressTemplate(in *acme.ACMEChallengeSolverHTTP01IngressTemplate, out *ACMEChallengeSolverHTTP01IngressTemplate, s conversion.Scope) error { - return autoConvert_acme_ACMEChallengeSolverHTTP01IngressTemplate_To_v1beta1_ACMEChallengeSolverHTTP01IngressTemplate(in, out, s) -} - -func autoConvert_v1beta1_ACMEExternalAccountBinding_To_acme_ACMEExternalAccountBinding(in *ACMEExternalAccountBinding, out *acme.ACMEExternalAccountBinding, s conversion.Scope) error { - out.KeyID = in.KeyID - if err := metav1.Convert_v1_SecretKeySelector_To_meta_SecretKeySelector(&in.Key, &out.Key, s); err != nil { - return err - } - out.KeyAlgorithm = acme.HMACKeyAlgorithm(in.KeyAlgorithm) - return nil -} - -// Convert_v1beta1_ACMEExternalAccountBinding_To_acme_ACMEExternalAccountBinding is an autogenerated conversion function. -func Convert_v1beta1_ACMEExternalAccountBinding_To_acme_ACMEExternalAccountBinding(in *ACMEExternalAccountBinding, out *acme.ACMEExternalAccountBinding, s conversion.Scope) error { - return autoConvert_v1beta1_ACMEExternalAccountBinding_To_acme_ACMEExternalAccountBinding(in, out, s) -} - -func autoConvert_acme_ACMEExternalAccountBinding_To_v1beta1_ACMEExternalAccountBinding(in *acme.ACMEExternalAccountBinding, out *ACMEExternalAccountBinding, s conversion.Scope) error { - out.KeyID = in.KeyID - if err := metav1.Convert_meta_SecretKeySelector_To_v1_SecretKeySelector(&in.Key, &out.Key, s); err != nil { - return err - } - out.KeyAlgorithm = HMACKeyAlgorithm(in.KeyAlgorithm) - return nil -} - -// Convert_acme_ACMEExternalAccountBinding_To_v1beta1_ACMEExternalAccountBinding is an autogenerated conversion function. -func Convert_acme_ACMEExternalAccountBinding_To_v1beta1_ACMEExternalAccountBinding(in *acme.ACMEExternalAccountBinding, out *ACMEExternalAccountBinding, s conversion.Scope) error { - return autoConvert_acme_ACMEExternalAccountBinding_To_v1beta1_ACMEExternalAccountBinding(in, out, s) -} - -func autoConvert_v1beta1_ACMEIssuer_To_acme_ACMEIssuer(in *ACMEIssuer, out *acme.ACMEIssuer, s conversion.Scope) error { - out.Email = in.Email - out.Server = in.Server - out.PreferredChain = in.PreferredChain - out.SkipTLSVerify = in.SkipTLSVerify - if in.ExternalAccountBinding != nil { - in, out := &in.ExternalAccountBinding, &out.ExternalAccountBinding - *out = new(acme.ACMEExternalAccountBinding) - if err := Convert_v1beta1_ACMEExternalAccountBinding_To_acme_ACMEExternalAccountBinding(*in, *out, s); err != nil { - return err - } - } else { - out.ExternalAccountBinding = nil - } - if err := metav1.Convert_v1_SecretKeySelector_To_meta_SecretKeySelector(&in.PrivateKey, &out.PrivateKey, s); err != nil { - return err - } - if in.Solvers != nil { - in, out := &in.Solvers, &out.Solvers - *out = make([]acme.ACMEChallengeSolver, len(*in)) - for i := range *in { - if err := Convert_v1beta1_ACMEChallengeSolver_To_acme_ACMEChallengeSolver(&(*in)[i], &(*out)[i], s); err != nil { - return err - } - } - } else { - out.Solvers = nil - } - out.DisableAccountKeyGeneration = in.DisableAccountKeyGeneration - out.EnableDurationFeature = in.EnableDurationFeature - return nil -} - -func autoConvert_acme_ACMEIssuer_To_v1beta1_ACMEIssuer(in *acme.ACMEIssuer, out *ACMEIssuer, s conversion.Scope) error { - out.Email = in.Email - out.Server = in.Server - out.PreferredChain = in.PreferredChain - out.SkipTLSVerify = in.SkipTLSVerify - if in.ExternalAccountBinding != nil { - in, out := &in.ExternalAccountBinding, &out.ExternalAccountBinding - *out = new(ACMEExternalAccountBinding) - if err := Convert_acme_ACMEExternalAccountBinding_To_v1beta1_ACMEExternalAccountBinding(*in, *out, s); err != nil { - return err - } - } else { - out.ExternalAccountBinding = nil - } - if err := metav1.Convert_meta_SecretKeySelector_To_v1_SecretKeySelector(&in.PrivateKey, &out.PrivateKey, s); err != nil { - return err - } - if in.Solvers != nil { - in, out := &in.Solvers, &out.Solvers - *out = make([]ACMEChallengeSolver, len(*in)) - for i := range *in { - if err := Convert_acme_ACMEChallengeSolver_To_v1beta1_ACMEChallengeSolver(&(*in)[i], &(*out)[i], s); err != nil { - return err - } - } - } else { - out.Solvers = nil - } - out.DisableAccountKeyGeneration = in.DisableAccountKeyGeneration - out.EnableDurationFeature = in.EnableDurationFeature - return nil -} - -func autoConvert_v1beta1_ACMEIssuerDNS01ProviderAcmeDNS_To_acme_ACMEIssuerDNS01ProviderAcmeDNS(in *ACMEIssuerDNS01ProviderAcmeDNS, out *acme.ACMEIssuerDNS01ProviderAcmeDNS, s conversion.Scope) error { - out.Host = in.Host - if err := metav1.Convert_v1_SecretKeySelector_To_meta_SecretKeySelector(&in.AccountSecret, &out.AccountSecret, s); err != nil { - return err - } - return nil -} - -// Convert_v1beta1_ACMEIssuerDNS01ProviderAcmeDNS_To_acme_ACMEIssuerDNS01ProviderAcmeDNS is an autogenerated conversion function. -func Convert_v1beta1_ACMEIssuerDNS01ProviderAcmeDNS_To_acme_ACMEIssuerDNS01ProviderAcmeDNS(in *ACMEIssuerDNS01ProviderAcmeDNS, out *acme.ACMEIssuerDNS01ProviderAcmeDNS, s conversion.Scope) error { - return autoConvert_v1beta1_ACMEIssuerDNS01ProviderAcmeDNS_To_acme_ACMEIssuerDNS01ProviderAcmeDNS(in, out, s) -} - -func autoConvert_acme_ACMEIssuerDNS01ProviderAcmeDNS_To_v1beta1_ACMEIssuerDNS01ProviderAcmeDNS(in *acme.ACMEIssuerDNS01ProviderAcmeDNS, out *ACMEIssuerDNS01ProviderAcmeDNS, s conversion.Scope) error { - out.Host = in.Host - if err := metav1.Convert_meta_SecretKeySelector_To_v1_SecretKeySelector(&in.AccountSecret, &out.AccountSecret, s); err != nil { - return err - } - return nil -} - -// Convert_acme_ACMEIssuerDNS01ProviderAcmeDNS_To_v1beta1_ACMEIssuerDNS01ProviderAcmeDNS is an autogenerated conversion function. -func Convert_acme_ACMEIssuerDNS01ProviderAcmeDNS_To_v1beta1_ACMEIssuerDNS01ProviderAcmeDNS(in *acme.ACMEIssuerDNS01ProviderAcmeDNS, out *ACMEIssuerDNS01ProviderAcmeDNS, s conversion.Scope) error { - return autoConvert_acme_ACMEIssuerDNS01ProviderAcmeDNS_To_v1beta1_ACMEIssuerDNS01ProviderAcmeDNS(in, out, s) -} - -func autoConvert_v1beta1_ACMEIssuerDNS01ProviderAkamai_To_acme_ACMEIssuerDNS01ProviderAkamai(in *ACMEIssuerDNS01ProviderAkamai, out *acme.ACMEIssuerDNS01ProviderAkamai, s conversion.Scope) error { - out.ServiceConsumerDomain = in.ServiceConsumerDomain - if err := metav1.Convert_v1_SecretKeySelector_To_meta_SecretKeySelector(&in.ClientToken, &out.ClientToken, s); err != nil { - return err - } - if err := metav1.Convert_v1_SecretKeySelector_To_meta_SecretKeySelector(&in.ClientSecret, &out.ClientSecret, s); err != nil { - return err - } - if err := metav1.Convert_v1_SecretKeySelector_To_meta_SecretKeySelector(&in.AccessToken, &out.AccessToken, s); err != nil { - return err - } - return nil -} - -// Convert_v1beta1_ACMEIssuerDNS01ProviderAkamai_To_acme_ACMEIssuerDNS01ProviderAkamai is an autogenerated conversion function. -func Convert_v1beta1_ACMEIssuerDNS01ProviderAkamai_To_acme_ACMEIssuerDNS01ProviderAkamai(in *ACMEIssuerDNS01ProviderAkamai, out *acme.ACMEIssuerDNS01ProviderAkamai, s conversion.Scope) error { - return autoConvert_v1beta1_ACMEIssuerDNS01ProviderAkamai_To_acme_ACMEIssuerDNS01ProviderAkamai(in, out, s) -} - -func autoConvert_acme_ACMEIssuerDNS01ProviderAkamai_To_v1beta1_ACMEIssuerDNS01ProviderAkamai(in *acme.ACMEIssuerDNS01ProviderAkamai, out *ACMEIssuerDNS01ProviderAkamai, s conversion.Scope) error { - out.ServiceConsumerDomain = in.ServiceConsumerDomain - if err := metav1.Convert_meta_SecretKeySelector_To_v1_SecretKeySelector(&in.ClientToken, &out.ClientToken, s); err != nil { - return err - } - if err := metav1.Convert_meta_SecretKeySelector_To_v1_SecretKeySelector(&in.ClientSecret, &out.ClientSecret, s); err != nil { - return err - } - if err := metav1.Convert_meta_SecretKeySelector_To_v1_SecretKeySelector(&in.AccessToken, &out.AccessToken, s); err != nil { - return err - } - return nil -} - -// Convert_acme_ACMEIssuerDNS01ProviderAkamai_To_v1beta1_ACMEIssuerDNS01ProviderAkamai is an autogenerated conversion function. -func Convert_acme_ACMEIssuerDNS01ProviderAkamai_To_v1beta1_ACMEIssuerDNS01ProviderAkamai(in *acme.ACMEIssuerDNS01ProviderAkamai, out *ACMEIssuerDNS01ProviderAkamai, s conversion.Scope) error { - return autoConvert_acme_ACMEIssuerDNS01ProviderAkamai_To_v1beta1_ACMEIssuerDNS01ProviderAkamai(in, out, s) -} - -func autoConvert_v1beta1_ACMEIssuerDNS01ProviderAzureDNS_To_acme_ACMEIssuerDNS01ProviderAzureDNS(in *ACMEIssuerDNS01ProviderAzureDNS, out *acme.ACMEIssuerDNS01ProviderAzureDNS, s conversion.Scope) error { - out.ClientID = in.ClientID - if in.ClientSecret != nil { - in, out := &in.ClientSecret, &out.ClientSecret - *out = new(meta.SecretKeySelector) - if err := metav1.Convert_v1_SecretKeySelector_To_meta_SecretKeySelector(*in, *out, s); err != nil { - return err - } - } else { - out.ClientSecret = nil - } - out.SubscriptionID = in.SubscriptionID - out.TenantID = in.TenantID - out.ResourceGroupName = in.ResourceGroupName - out.HostedZoneName = in.HostedZoneName - out.Environment = acme.AzureDNSEnvironment(in.Environment) - out.ManagedIdentity = (*acme.AzureManagedIdentity)(unsafe.Pointer(in.ManagedIdentity)) - return nil -} - -// Convert_v1beta1_ACMEIssuerDNS01ProviderAzureDNS_To_acme_ACMEIssuerDNS01ProviderAzureDNS is an autogenerated conversion function. -func Convert_v1beta1_ACMEIssuerDNS01ProviderAzureDNS_To_acme_ACMEIssuerDNS01ProviderAzureDNS(in *ACMEIssuerDNS01ProviderAzureDNS, out *acme.ACMEIssuerDNS01ProviderAzureDNS, s conversion.Scope) error { - return autoConvert_v1beta1_ACMEIssuerDNS01ProviderAzureDNS_To_acme_ACMEIssuerDNS01ProviderAzureDNS(in, out, s) -} - -func autoConvert_acme_ACMEIssuerDNS01ProviderAzureDNS_To_v1beta1_ACMEIssuerDNS01ProviderAzureDNS(in *acme.ACMEIssuerDNS01ProviderAzureDNS, out *ACMEIssuerDNS01ProviderAzureDNS, s conversion.Scope) error { - out.ClientID = in.ClientID - if in.ClientSecret != nil { - in, out := &in.ClientSecret, &out.ClientSecret - *out = new(apismetav1.SecretKeySelector) - if err := metav1.Convert_meta_SecretKeySelector_To_v1_SecretKeySelector(*in, *out, s); err != nil { - return err - } - } else { - out.ClientSecret = nil - } - out.SubscriptionID = in.SubscriptionID - out.TenantID = in.TenantID - out.ResourceGroupName = in.ResourceGroupName - out.HostedZoneName = in.HostedZoneName - out.Environment = AzureDNSEnvironment(in.Environment) - out.ManagedIdentity = (*AzureManagedIdentity)(unsafe.Pointer(in.ManagedIdentity)) - return nil -} - -// Convert_acme_ACMEIssuerDNS01ProviderAzureDNS_To_v1beta1_ACMEIssuerDNS01ProviderAzureDNS is an autogenerated conversion function. -func Convert_acme_ACMEIssuerDNS01ProviderAzureDNS_To_v1beta1_ACMEIssuerDNS01ProviderAzureDNS(in *acme.ACMEIssuerDNS01ProviderAzureDNS, out *ACMEIssuerDNS01ProviderAzureDNS, s conversion.Scope) error { - return autoConvert_acme_ACMEIssuerDNS01ProviderAzureDNS_To_v1beta1_ACMEIssuerDNS01ProviderAzureDNS(in, out, s) -} - -func autoConvert_v1beta1_ACMEIssuerDNS01ProviderCloudDNS_To_acme_ACMEIssuerDNS01ProviderCloudDNS(in *ACMEIssuerDNS01ProviderCloudDNS, out *acme.ACMEIssuerDNS01ProviderCloudDNS, s conversion.Scope) error { - if in.ServiceAccount != nil { - in, out := &in.ServiceAccount, &out.ServiceAccount - *out = new(meta.SecretKeySelector) - if err := metav1.Convert_v1_SecretKeySelector_To_meta_SecretKeySelector(*in, *out, s); err != nil { - return err - } - } else { - out.ServiceAccount = nil - } - out.Project = in.Project - out.HostedZoneName = in.HostedZoneName - return nil -} - -// Convert_v1beta1_ACMEIssuerDNS01ProviderCloudDNS_To_acme_ACMEIssuerDNS01ProviderCloudDNS is an autogenerated conversion function. -func Convert_v1beta1_ACMEIssuerDNS01ProviderCloudDNS_To_acme_ACMEIssuerDNS01ProviderCloudDNS(in *ACMEIssuerDNS01ProviderCloudDNS, out *acme.ACMEIssuerDNS01ProviderCloudDNS, s conversion.Scope) error { - return autoConvert_v1beta1_ACMEIssuerDNS01ProviderCloudDNS_To_acme_ACMEIssuerDNS01ProviderCloudDNS(in, out, s) -} - -func autoConvert_acme_ACMEIssuerDNS01ProviderCloudDNS_To_v1beta1_ACMEIssuerDNS01ProviderCloudDNS(in *acme.ACMEIssuerDNS01ProviderCloudDNS, out *ACMEIssuerDNS01ProviderCloudDNS, s conversion.Scope) error { - if in.ServiceAccount != nil { - in, out := &in.ServiceAccount, &out.ServiceAccount - *out = new(apismetav1.SecretKeySelector) - if err := metav1.Convert_meta_SecretKeySelector_To_v1_SecretKeySelector(*in, *out, s); err != nil { - return err - } - } else { - out.ServiceAccount = nil - } - out.Project = in.Project - out.HostedZoneName = in.HostedZoneName - return nil -} - -// Convert_acme_ACMEIssuerDNS01ProviderCloudDNS_To_v1beta1_ACMEIssuerDNS01ProviderCloudDNS is an autogenerated conversion function. -func Convert_acme_ACMEIssuerDNS01ProviderCloudDNS_To_v1beta1_ACMEIssuerDNS01ProviderCloudDNS(in *acme.ACMEIssuerDNS01ProviderCloudDNS, out *ACMEIssuerDNS01ProviderCloudDNS, s conversion.Scope) error { - return autoConvert_acme_ACMEIssuerDNS01ProviderCloudDNS_To_v1beta1_ACMEIssuerDNS01ProviderCloudDNS(in, out, s) -} - -func autoConvert_v1beta1_ACMEIssuerDNS01ProviderCloudflare_To_acme_ACMEIssuerDNS01ProviderCloudflare(in *ACMEIssuerDNS01ProviderCloudflare, out *acme.ACMEIssuerDNS01ProviderCloudflare, s conversion.Scope) error { - out.Email = in.Email - if in.APIKey != nil { - in, out := &in.APIKey, &out.APIKey - *out = new(meta.SecretKeySelector) - if err := metav1.Convert_v1_SecretKeySelector_To_meta_SecretKeySelector(*in, *out, s); err != nil { - return err - } - } else { - out.APIKey = nil - } - if in.APIToken != nil { - in, out := &in.APIToken, &out.APIToken - *out = new(meta.SecretKeySelector) - if err := metav1.Convert_v1_SecretKeySelector_To_meta_SecretKeySelector(*in, *out, s); err != nil { - return err - } - } else { - out.APIToken = nil - } - return nil -} - -// Convert_v1beta1_ACMEIssuerDNS01ProviderCloudflare_To_acme_ACMEIssuerDNS01ProviderCloudflare is an autogenerated conversion function. -func Convert_v1beta1_ACMEIssuerDNS01ProviderCloudflare_To_acme_ACMEIssuerDNS01ProviderCloudflare(in *ACMEIssuerDNS01ProviderCloudflare, out *acme.ACMEIssuerDNS01ProviderCloudflare, s conversion.Scope) error { - return autoConvert_v1beta1_ACMEIssuerDNS01ProviderCloudflare_To_acme_ACMEIssuerDNS01ProviderCloudflare(in, out, s) -} - -func autoConvert_acme_ACMEIssuerDNS01ProviderCloudflare_To_v1beta1_ACMEIssuerDNS01ProviderCloudflare(in *acme.ACMEIssuerDNS01ProviderCloudflare, out *ACMEIssuerDNS01ProviderCloudflare, s conversion.Scope) error { - out.Email = in.Email - if in.APIKey != nil { - in, out := &in.APIKey, &out.APIKey - *out = new(apismetav1.SecretKeySelector) - if err := metav1.Convert_meta_SecretKeySelector_To_v1_SecretKeySelector(*in, *out, s); err != nil { - return err - } - } else { - out.APIKey = nil - } - if in.APIToken != nil { - in, out := &in.APIToken, &out.APIToken - *out = new(apismetav1.SecretKeySelector) - if err := metav1.Convert_meta_SecretKeySelector_To_v1_SecretKeySelector(*in, *out, s); err != nil { - return err - } - } else { - out.APIToken = nil - } - return nil -} - -// Convert_acme_ACMEIssuerDNS01ProviderCloudflare_To_v1beta1_ACMEIssuerDNS01ProviderCloudflare is an autogenerated conversion function. -func Convert_acme_ACMEIssuerDNS01ProviderCloudflare_To_v1beta1_ACMEIssuerDNS01ProviderCloudflare(in *acme.ACMEIssuerDNS01ProviderCloudflare, out *ACMEIssuerDNS01ProviderCloudflare, s conversion.Scope) error { - return autoConvert_acme_ACMEIssuerDNS01ProviderCloudflare_To_v1beta1_ACMEIssuerDNS01ProviderCloudflare(in, out, s) -} - -func autoConvert_v1beta1_ACMEIssuerDNS01ProviderDigitalOcean_To_acme_ACMEIssuerDNS01ProviderDigitalOcean(in *ACMEIssuerDNS01ProviderDigitalOcean, out *acme.ACMEIssuerDNS01ProviderDigitalOcean, s conversion.Scope) error { - if err := metav1.Convert_v1_SecretKeySelector_To_meta_SecretKeySelector(&in.Token, &out.Token, s); err != nil { - return err - } - return nil -} - -// Convert_v1beta1_ACMEIssuerDNS01ProviderDigitalOcean_To_acme_ACMEIssuerDNS01ProviderDigitalOcean is an autogenerated conversion function. -func Convert_v1beta1_ACMEIssuerDNS01ProviderDigitalOcean_To_acme_ACMEIssuerDNS01ProviderDigitalOcean(in *ACMEIssuerDNS01ProviderDigitalOcean, out *acme.ACMEIssuerDNS01ProviderDigitalOcean, s conversion.Scope) error { - return autoConvert_v1beta1_ACMEIssuerDNS01ProviderDigitalOcean_To_acme_ACMEIssuerDNS01ProviderDigitalOcean(in, out, s) -} - -func autoConvert_acme_ACMEIssuerDNS01ProviderDigitalOcean_To_v1beta1_ACMEIssuerDNS01ProviderDigitalOcean(in *acme.ACMEIssuerDNS01ProviderDigitalOcean, out *ACMEIssuerDNS01ProviderDigitalOcean, s conversion.Scope) error { - if err := metav1.Convert_meta_SecretKeySelector_To_v1_SecretKeySelector(&in.Token, &out.Token, s); err != nil { - return err - } - return nil -} - -// Convert_acme_ACMEIssuerDNS01ProviderDigitalOcean_To_v1beta1_ACMEIssuerDNS01ProviderDigitalOcean is an autogenerated conversion function. -func Convert_acme_ACMEIssuerDNS01ProviderDigitalOcean_To_v1beta1_ACMEIssuerDNS01ProviderDigitalOcean(in *acme.ACMEIssuerDNS01ProviderDigitalOcean, out *ACMEIssuerDNS01ProviderDigitalOcean, s conversion.Scope) error { - return autoConvert_acme_ACMEIssuerDNS01ProviderDigitalOcean_To_v1beta1_ACMEIssuerDNS01ProviderDigitalOcean(in, out, s) -} - -func autoConvert_v1beta1_ACMEIssuerDNS01ProviderRFC2136_To_acme_ACMEIssuerDNS01ProviderRFC2136(in *ACMEIssuerDNS01ProviderRFC2136, out *acme.ACMEIssuerDNS01ProviderRFC2136, s conversion.Scope) error { - out.Nameserver = in.Nameserver - if err := metav1.Convert_v1_SecretKeySelector_To_meta_SecretKeySelector(&in.TSIGSecret, &out.TSIGSecret, s); err != nil { - return err - } - out.TSIGKeyName = in.TSIGKeyName - out.TSIGAlgorithm = in.TSIGAlgorithm - return nil -} - -// Convert_v1beta1_ACMEIssuerDNS01ProviderRFC2136_To_acme_ACMEIssuerDNS01ProviderRFC2136 is an autogenerated conversion function. -func Convert_v1beta1_ACMEIssuerDNS01ProviderRFC2136_To_acme_ACMEIssuerDNS01ProviderRFC2136(in *ACMEIssuerDNS01ProviderRFC2136, out *acme.ACMEIssuerDNS01ProviderRFC2136, s conversion.Scope) error { - return autoConvert_v1beta1_ACMEIssuerDNS01ProviderRFC2136_To_acme_ACMEIssuerDNS01ProviderRFC2136(in, out, s) -} - -func autoConvert_acme_ACMEIssuerDNS01ProviderRFC2136_To_v1beta1_ACMEIssuerDNS01ProviderRFC2136(in *acme.ACMEIssuerDNS01ProviderRFC2136, out *ACMEIssuerDNS01ProviderRFC2136, s conversion.Scope) error { - out.Nameserver = in.Nameserver - if err := metav1.Convert_meta_SecretKeySelector_To_v1_SecretKeySelector(&in.TSIGSecret, &out.TSIGSecret, s); err != nil { - return err - } - out.TSIGKeyName = in.TSIGKeyName - out.TSIGAlgorithm = in.TSIGAlgorithm - return nil -} - -// Convert_acme_ACMEIssuerDNS01ProviderRFC2136_To_v1beta1_ACMEIssuerDNS01ProviderRFC2136 is an autogenerated conversion function. -func Convert_acme_ACMEIssuerDNS01ProviderRFC2136_To_v1beta1_ACMEIssuerDNS01ProviderRFC2136(in *acme.ACMEIssuerDNS01ProviderRFC2136, out *ACMEIssuerDNS01ProviderRFC2136, s conversion.Scope) error { - return autoConvert_acme_ACMEIssuerDNS01ProviderRFC2136_To_v1beta1_ACMEIssuerDNS01ProviderRFC2136(in, out, s) -} - -func autoConvert_v1beta1_ACMEIssuerDNS01ProviderRoute53_To_acme_ACMEIssuerDNS01ProviderRoute53(in *ACMEIssuerDNS01ProviderRoute53, out *acme.ACMEIssuerDNS01ProviderRoute53, s conversion.Scope) error { - out.AccessKeyID = in.AccessKeyID - if in.SecretAccessKeyID != nil { - in, out := &in.SecretAccessKeyID, &out.SecretAccessKeyID - *out = new(meta.SecretKeySelector) - if err := metav1.Convert_v1_SecretKeySelector_To_meta_SecretKeySelector(*in, *out, s); err != nil { - return err - } - } else { - out.SecretAccessKeyID = nil - } - if err := metav1.Convert_v1_SecretKeySelector_To_meta_SecretKeySelector(&in.SecretAccessKey, &out.SecretAccessKey, s); err != nil { - return err - } - out.Role = in.Role - out.HostedZoneID = in.HostedZoneID - out.Region = in.Region - return nil -} - -// Convert_v1beta1_ACMEIssuerDNS01ProviderRoute53_To_acme_ACMEIssuerDNS01ProviderRoute53 is an autogenerated conversion function. -func Convert_v1beta1_ACMEIssuerDNS01ProviderRoute53_To_acme_ACMEIssuerDNS01ProviderRoute53(in *ACMEIssuerDNS01ProviderRoute53, out *acme.ACMEIssuerDNS01ProviderRoute53, s conversion.Scope) error { - return autoConvert_v1beta1_ACMEIssuerDNS01ProviderRoute53_To_acme_ACMEIssuerDNS01ProviderRoute53(in, out, s) -} - -func autoConvert_acme_ACMEIssuerDNS01ProviderRoute53_To_v1beta1_ACMEIssuerDNS01ProviderRoute53(in *acme.ACMEIssuerDNS01ProviderRoute53, out *ACMEIssuerDNS01ProviderRoute53, s conversion.Scope) error { - out.AccessKeyID = in.AccessKeyID - if in.SecretAccessKeyID != nil { - in, out := &in.SecretAccessKeyID, &out.SecretAccessKeyID - *out = new(apismetav1.SecretKeySelector) - if err := metav1.Convert_meta_SecretKeySelector_To_v1_SecretKeySelector(*in, *out, s); err != nil { - return err - } - } else { - out.SecretAccessKeyID = nil - } - if err := metav1.Convert_meta_SecretKeySelector_To_v1_SecretKeySelector(&in.SecretAccessKey, &out.SecretAccessKey, s); err != nil { - return err - } - out.Role = in.Role - out.HostedZoneID = in.HostedZoneID - out.Region = in.Region - return nil -} - -// Convert_acme_ACMEIssuerDNS01ProviderRoute53_To_v1beta1_ACMEIssuerDNS01ProviderRoute53 is an autogenerated conversion function. -func Convert_acme_ACMEIssuerDNS01ProviderRoute53_To_v1beta1_ACMEIssuerDNS01ProviderRoute53(in *acme.ACMEIssuerDNS01ProviderRoute53, out *ACMEIssuerDNS01ProviderRoute53, s conversion.Scope) error { - return autoConvert_acme_ACMEIssuerDNS01ProviderRoute53_To_v1beta1_ACMEIssuerDNS01ProviderRoute53(in, out, s) -} - -func autoConvert_v1beta1_ACMEIssuerDNS01ProviderWebhook_To_acme_ACMEIssuerDNS01ProviderWebhook(in *ACMEIssuerDNS01ProviderWebhook, out *acme.ACMEIssuerDNS01ProviderWebhook, s conversion.Scope) error { - out.GroupName = in.GroupName - out.SolverName = in.SolverName - out.Config = (*apiextensionsv1.JSON)(unsafe.Pointer(in.Config)) - return nil -} - -// Convert_v1beta1_ACMEIssuerDNS01ProviderWebhook_To_acme_ACMEIssuerDNS01ProviderWebhook is an autogenerated conversion function. -func Convert_v1beta1_ACMEIssuerDNS01ProviderWebhook_To_acme_ACMEIssuerDNS01ProviderWebhook(in *ACMEIssuerDNS01ProviderWebhook, out *acme.ACMEIssuerDNS01ProviderWebhook, s conversion.Scope) error { - return autoConvert_v1beta1_ACMEIssuerDNS01ProviderWebhook_To_acme_ACMEIssuerDNS01ProviderWebhook(in, out, s) -} - -func autoConvert_acme_ACMEIssuerDNS01ProviderWebhook_To_v1beta1_ACMEIssuerDNS01ProviderWebhook(in *acme.ACMEIssuerDNS01ProviderWebhook, out *ACMEIssuerDNS01ProviderWebhook, s conversion.Scope) error { - out.GroupName = in.GroupName - out.SolverName = in.SolverName - out.Config = (*apiextensionsv1.JSON)(unsafe.Pointer(in.Config)) - return nil -} - -// Convert_acme_ACMEIssuerDNS01ProviderWebhook_To_v1beta1_ACMEIssuerDNS01ProviderWebhook is an autogenerated conversion function. -func Convert_acme_ACMEIssuerDNS01ProviderWebhook_To_v1beta1_ACMEIssuerDNS01ProviderWebhook(in *acme.ACMEIssuerDNS01ProviderWebhook, out *ACMEIssuerDNS01ProviderWebhook, s conversion.Scope) error { - return autoConvert_acme_ACMEIssuerDNS01ProviderWebhook_To_v1beta1_ACMEIssuerDNS01ProviderWebhook(in, out, s) -} - -func autoConvert_v1beta1_ACMEIssuerStatus_To_acme_ACMEIssuerStatus(in *ACMEIssuerStatus, out *acme.ACMEIssuerStatus, s conversion.Scope) error { - out.URI = in.URI - out.LastRegisteredEmail = in.LastRegisteredEmail - return nil -} - -// Convert_v1beta1_ACMEIssuerStatus_To_acme_ACMEIssuerStatus is an autogenerated conversion function. -func Convert_v1beta1_ACMEIssuerStatus_To_acme_ACMEIssuerStatus(in *ACMEIssuerStatus, out *acme.ACMEIssuerStatus, s conversion.Scope) error { - return autoConvert_v1beta1_ACMEIssuerStatus_To_acme_ACMEIssuerStatus(in, out, s) -} - -func autoConvert_acme_ACMEIssuerStatus_To_v1beta1_ACMEIssuerStatus(in *acme.ACMEIssuerStatus, out *ACMEIssuerStatus, s conversion.Scope) error { - out.URI = in.URI - out.LastRegisteredEmail = in.LastRegisteredEmail - return nil -} - -// Convert_acme_ACMEIssuerStatus_To_v1beta1_ACMEIssuerStatus is an autogenerated conversion function. -func Convert_acme_ACMEIssuerStatus_To_v1beta1_ACMEIssuerStatus(in *acme.ACMEIssuerStatus, out *ACMEIssuerStatus, s conversion.Scope) error { - return autoConvert_acme_ACMEIssuerStatus_To_v1beta1_ACMEIssuerStatus(in, out, s) -} - -func autoConvert_v1beta1_AzureManagedIdentity_To_acme_AzureManagedIdentity(in *AzureManagedIdentity, out *acme.AzureManagedIdentity, s conversion.Scope) error { - out.ClientID = in.ClientID - out.ResourceID = in.ResourceID - return nil -} - -// Convert_v1beta1_AzureManagedIdentity_To_acme_AzureManagedIdentity is an autogenerated conversion function. -func Convert_v1beta1_AzureManagedIdentity_To_acme_AzureManagedIdentity(in *AzureManagedIdentity, out *acme.AzureManagedIdentity, s conversion.Scope) error { - return autoConvert_v1beta1_AzureManagedIdentity_To_acme_AzureManagedIdentity(in, out, s) -} - -func autoConvert_acme_AzureManagedIdentity_To_v1beta1_AzureManagedIdentity(in *acme.AzureManagedIdentity, out *AzureManagedIdentity, s conversion.Scope) error { - out.ClientID = in.ClientID - out.ResourceID = in.ResourceID - return nil -} - -// Convert_acme_AzureManagedIdentity_To_v1beta1_AzureManagedIdentity is an autogenerated conversion function. -func Convert_acme_AzureManagedIdentity_To_v1beta1_AzureManagedIdentity(in *acme.AzureManagedIdentity, out *AzureManagedIdentity, s conversion.Scope) error { - return autoConvert_acme_AzureManagedIdentity_To_v1beta1_AzureManagedIdentity(in, out, s) -} - -func autoConvert_v1beta1_CertificateDNSNameSelector_To_acme_CertificateDNSNameSelector(in *CertificateDNSNameSelector, out *acme.CertificateDNSNameSelector, s conversion.Scope) error { - out.MatchLabels = *(*map[string]string)(unsafe.Pointer(&in.MatchLabels)) - out.DNSNames = *(*[]string)(unsafe.Pointer(&in.DNSNames)) - out.DNSZones = *(*[]string)(unsafe.Pointer(&in.DNSZones)) - return nil -} - -// Convert_v1beta1_CertificateDNSNameSelector_To_acme_CertificateDNSNameSelector is an autogenerated conversion function. -func Convert_v1beta1_CertificateDNSNameSelector_To_acme_CertificateDNSNameSelector(in *CertificateDNSNameSelector, out *acme.CertificateDNSNameSelector, s conversion.Scope) error { - return autoConvert_v1beta1_CertificateDNSNameSelector_To_acme_CertificateDNSNameSelector(in, out, s) -} - -func autoConvert_acme_CertificateDNSNameSelector_To_v1beta1_CertificateDNSNameSelector(in *acme.CertificateDNSNameSelector, out *CertificateDNSNameSelector, s conversion.Scope) error { - out.MatchLabels = *(*map[string]string)(unsafe.Pointer(&in.MatchLabels)) - out.DNSNames = *(*[]string)(unsafe.Pointer(&in.DNSNames)) - out.DNSZones = *(*[]string)(unsafe.Pointer(&in.DNSZones)) - return nil -} - -// Convert_acme_CertificateDNSNameSelector_To_v1beta1_CertificateDNSNameSelector is an autogenerated conversion function. -func Convert_acme_CertificateDNSNameSelector_To_v1beta1_CertificateDNSNameSelector(in *acme.CertificateDNSNameSelector, out *CertificateDNSNameSelector, s conversion.Scope) error { - return autoConvert_acme_CertificateDNSNameSelector_To_v1beta1_CertificateDNSNameSelector(in, out, s) -} - -func autoConvert_v1beta1_Challenge_To_acme_Challenge(in *Challenge, out *acme.Challenge, s conversion.Scope) error { - out.ObjectMeta = in.ObjectMeta - if err := Convert_v1beta1_ChallengeSpec_To_acme_ChallengeSpec(&in.Spec, &out.Spec, s); err != nil { - return err - } - if err := Convert_v1beta1_ChallengeStatus_To_acme_ChallengeStatus(&in.Status, &out.Status, s); err != nil { - return err - } - return nil -} - -// Convert_v1beta1_Challenge_To_acme_Challenge is an autogenerated conversion function. -func Convert_v1beta1_Challenge_To_acme_Challenge(in *Challenge, out *acme.Challenge, s conversion.Scope) error { - return autoConvert_v1beta1_Challenge_To_acme_Challenge(in, out, s) -} - -func autoConvert_acme_Challenge_To_v1beta1_Challenge(in *acme.Challenge, out *Challenge, s conversion.Scope) error { - out.ObjectMeta = in.ObjectMeta - if err := Convert_acme_ChallengeSpec_To_v1beta1_ChallengeSpec(&in.Spec, &out.Spec, s); err != nil { - return err - } - if err := Convert_acme_ChallengeStatus_To_v1beta1_ChallengeStatus(&in.Status, &out.Status, s); err != nil { - return err - } - return nil -} - -// Convert_acme_Challenge_To_v1beta1_Challenge is an autogenerated conversion function. -func Convert_acme_Challenge_To_v1beta1_Challenge(in *acme.Challenge, out *Challenge, s conversion.Scope) error { - return autoConvert_acme_Challenge_To_v1beta1_Challenge(in, out, s) -} - -func autoConvert_v1beta1_ChallengeList_To_acme_ChallengeList(in *ChallengeList, out *acme.ChallengeList, s conversion.Scope) error { - out.ListMeta = in.ListMeta - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]acme.Challenge, len(*in)) - for i := range *in { - if err := Convert_v1beta1_Challenge_To_acme_Challenge(&(*in)[i], &(*out)[i], s); err != nil { - return err - } - } - } else { - out.Items = nil - } - return nil -} - -// Convert_v1beta1_ChallengeList_To_acme_ChallengeList is an autogenerated conversion function. -func Convert_v1beta1_ChallengeList_To_acme_ChallengeList(in *ChallengeList, out *acme.ChallengeList, s conversion.Scope) error { - return autoConvert_v1beta1_ChallengeList_To_acme_ChallengeList(in, out, s) -} - -func autoConvert_acme_ChallengeList_To_v1beta1_ChallengeList(in *acme.ChallengeList, out *ChallengeList, s conversion.Scope) error { - out.ListMeta = in.ListMeta - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]Challenge, len(*in)) - for i := range *in { - if err := Convert_acme_Challenge_To_v1beta1_Challenge(&(*in)[i], &(*out)[i], s); err != nil { - return err - } - } - } else { - out.Items = nil - } - return nil -} - -// Convert_acme_ChallengeList_To_v1beta1_ChallengeList is an autogenerated conversion function. -func Convert_acme_ChallengeList_To_v1beta1_ChallengeList(in *acme.ChallengeList, out *ChallengeList, s conversion.Scope) error { - return autoConvert_acme_ChallengeList_To_v1beta1_ChallengeList(in, out, s) -} - -func autoConvert_v1beta1_ChallengeSpec_To_acme_ChallengeSpec(in *ChallengeSpec, out *acme.ChallengeSpec, s conversion.Scope) error { - out.URL = in.URL - out.AuthorizationURL = in.AuthorizationURL - out.DNSName = in.DNSName - out.Wildcard = in.Wildcard - out.Type = acme.ACMEChallengeType(in.Type) - out.Token = in.Token - out.Key = in.Key - if err := Convert_v1beta1_ACMEChallengeSolver_To_acme_ACMEChallengeSolver(&in.Solver, &out.Solver, s); err != nil { - return err - } - if err := metav1.Convert_v1_ObjectReference_To_meta_ObjectReference(&in.IssuerRef, &out.IssuerRef, s); err != nil { - return err - } - return nil -} - -// Convert_v1beta1_ChallengeSpec_To_acme_ChallengeSpec is an autogenerated conversion function. -func Convert_v1beta1_ChallengeSpec_To_acme_ChallengeSpec(in *ChallengeSpec, out *acme.ChallengeSpec, s conversion.Scope) error { - return autoConvert_v1beta1_ChallengeSpec_To_acme_ChallengeSpec(in, out, s) -} - -func autoConvert_acme_ChallengeSpec_To_v1beta1_ChallengeSpec(in *acme.ChallengeSpec, out *ChallengeSpec, s conversion.Scope) error { - out.URL = in.URL - out.AuthorizationURL = in.AuthorizationURL - out.DNSName = in.DNSName - out.Wildcard = in.Wildcard - out.Type = ACMEChallengeType(in.Type) - out.Token = in.Token - out.Key = in.Key - if err := Convert_acme_ACMEChallengeSolver_To_v1beta1_ACMEChallengeSolver(&in.Solver, &out.Solver, s); err != nil { - return err - } - if err := metav1.Convert_meta_ObjectReference_To_v1_ObjectReference(&in.IssuerRef, &out.IssuerRef, s); err != nil { - return err - } - return nil -} - -// Convert_acme_ChallengeSpec_To_v1beta1_ChallengeSpec is an autogenerated conversion function. -func Convert_acme_ChallengeSpec_To_v1beta1_ChallengeSpec(in *acme.ChallengeSpec, out *ChallengeSpec, s conversion.Scope) error { - return autoConvert_acme_ChallengeSpec_To_v1beta1_ChallengeSpec(in, out, s) -} - -func autoConvert_v1beta1_ChallengeStatus_To_acme_ChallengeStatus(in *ChallengeStatus, out *acme.ChallengeStatus, s conversion.Scope) error { - out.Processing = in.Processing - out.Presented = in.Presented - out.Reason = in.Reason - out.State = acme.State(in.State) - return nil -} - -// Convert_v1beta1_ChallengeStatus_To_acme_ChallengeStatus is an autogenerated conversion function. -func Convert_v1beta1_ChallengeStatus_To_acme_ChallengeStatus(in *ChallengeStatus, out *acme.ChallengeStatus, s conversion.Scope) error { - return autoConvert_v1beta1_ChallengeStatus_To_acme_ChallengeStatus(in, out, s) -} - -func autoConvert_acme_ChallengeStatus_To_v1beta1_ChallengeStatus(in *acme.ChallengeStatus, out *ChallengeStatus, s conversion.Scope) error { - out.Processing = in.Processing - out.Presented = in.Presented - out.Reason = in.Reason - out.State = State(in.State) - return nil -} - -// Convert_acme_ChallengeStatus_To_v1beta1_ChallengeStatus is an autogenerated conversion function. -func Convert_acme_ChallengeStatus_To_v1beta1_ChallengeStatus(in *acme.ChallengeStatus, out *ChallengeStatus, s conversion.Scope) error { - return autoConvert_acme_ChallengeStatus_To_v1beta1_ChallengeStatus(in, out, s) -} - -func autoConvert_v1beta1_Order_To_acme_Order(in *Order, out *acme.Order, s conversion.Scope) error { - out.ObjectMeta = in.ObjectMeta - if err := Convert_v1beta1_OrderSpec_To_acme_OrderSpec(&in.Spec, &out.Spec, s); err != nil { - return err - } - if err := Convert_v1beta1_OrderStatus_To_acme_OrderStatus(&in.Status, &out.Status, s); err != nil { - return err - } - return nil -} - -// Convert_v1beta1_Order_To_acme_Order is an autogenerated conversion function. -func Convert_v1beta1_Order_To_acme_Order(in *Order, out *acme.Order, s conversion.Scope) error { - return autoConvert_v1beta1_Order_To_acme_Order(in, out, s) -} - -func autoConvert_acme_Order_To_v1beta1_Order(in *acme.Order, out *Order, s conversion.Scope) error { - out.ObjectMeta = in.ObjectMeta - if err := Convert_acme_OrderSpec_To_v1beta1_OrderSpec(&in.Spec, &out.Spec, s); err != nil { - return err - } - if err := Convert_acme_OrderStatus_To_v1beta1_OrderStatus(&in.Status, &out.Status, s); err != nil { - return err - } - return nil -} - -// Convert_acme_Order_To_v1beta1_Order is an autogenerated conversion function. -func Convert_acme_Order_To_v1beta1_Order(in *acme.Order, out *Order, s conversion.Scope) error { - return autoConvert_acme_Order_To_v1beta1_Order(in, out, s) -} - -func autoConvert_v1beta1_OrderList_To_acme_OrderList(in *OrderList, out *acme.OrderList, s conversion.Scope) error { - out.ListMeta = in.ListMeta - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]acme.Order, len(*in)) - for i := range *in { - if err := Convert_v1beta1_Order_To_acme_Order(&(*in)[i], &(*out)[i], s); err != nil { - return err - } - } - } else { - out.Items = nil - } - return nil -} - -// Convert_v1beta1_OrderList_To_acme_OrderList is an autogenerated conversion function. -func Convert_v1beta1_OrderList_To_acme_OrderList(in *OrderList, out *acme.OrderList, s conversion.Scope) error { - return autoConvert_v1beta1_OrderList_To_acme_OrderList(in, out, s) -} - -func autoConvert_acme_OrderList_To_v1beta1_OrderList(in *acme.OrderList, out *OrderList, s conversion.Scope) error { - out.ListMeta = in.ListMeta - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]Order, len(*in)) - for i := range *in { - if err := Convert_acme_Order_To_v1beta1_Order(&(*in)[i], &(*out)[i], s); err != nil { - return err - } - } - } else { - out.Items = nil - } - return nil -} - -// Convert_acme_OrderList_To_v1beta1_OrderList is an autogenerated conversion function. -func Convert_acme_OrderList_To_v1beta1_OrderList(in *acme.OrderList, out *OrderList, s conversion.Scope) error { - return autoConvert_acme_OrderList_To_v1beta1_OrderList(in, out, s) -} - -func autoConvert_v1beta1_OrderSpec_To_acme_OrderSpec(in *OrderSpec, out *acme.OrderSpec, s conversion.Scope) error { - out.Request = *(*[]byte)(unsafe.Pointer(&in.Request)) - if err := metav1.Convert_v1_ObjectReference_To_meta_ObjectReference(&in.IssuerRef, &out.IssuerRef, s); err != nil { - return err - } - out.CommonName = in.CommonName - out.DNSNames = *(*[]string)(unsafe.Pointer(&in.DNSNames)) - out.IPAddresses = *(*[]string)(unsafe.Pointer(&in.IPAddresses)) - out.Duration = (*pkgapismetav1.Duration)(unsafe.Pointer(in.Duration)) - return nil -} - -// Convert_v1beta1_OrderSpec_To_acme_OrderSpec is an autogenerated conversion function. -func Convert_v1beta1_OrderSpec_To_acme_OrderSpec(in *OrderSpec, out *acme.OrderSpec, s conversion.Scope) error { - return autoConvert_v1beta1_OrderSpec_To_acme_OrderSpec(in, out, s) -} - -func autoConvert_acme_OrderSpec_To_v1beta1_OrderSpec(in *acme.OrderSpec, out *OrderSpec, s conversion.Scope) error { - out.Request = *(*[]byte)(unsafe.Pointer(&in.Request)) - if err := metav1.Convert_meta_ObjectReference_To_v1_ObjectReference(&in.IssuerRef, &out.IssuerRef, s); err != nil { - return err - } - out.CommonName = in.CommonName - out.DNSNames = *(*[]string)(unsafe.Pointer(&in.DNSNames)) - out.IPAddresses = *(*[]string)(unsafe.Pointer(&in.IPAddresses)) - out.Duration = (*pkgapismetav1.Duration)(unsafe.Pointer(in.Duration)) - return nil -} - -// Convert_acme_OrderSpec_To_v1beta1_OrderSpec is an autogenerated conversion function. -func Convert_acme_OrderSpec_To_v1beta1_OrderSpec(in *acme.OrderSpec, out *OrderSpec, s conversion.Scope) error { - return autoConvert_acme_OrderSpec_To_v1beta1_OrderSpec(in, out, s) -} - -func autoConvert_v1beta1_OrderStatus_To_acme_OrderStatus(in *OrderStatus, out *acme.OrderStatus, s conversion.Scope) error { - out.URL = in.URL - out.FinalizeURL = in.FinalizeURL - out.Authorizations = *(*[]acme.ACMEAuthorization)(unsafe.Pointer(&in.Authorizations)) - out.Certificate = *(*[]byte)(unsafe.Pointer(&in.Certificate)) - out.State = acme.State(in.State) - out.Reason = in.Reason - out.FailureTime = (*pkgapismetav1.Time)(unsafe.Pointer(in.FailureTime)) - return nil -} - -// Convert_v1beta1_OrderStatus_To_acme_OrderStatus is an autogenerated conversion function. -func Convert_v1beta1_OrderStatus_To_acme_OrderStatus(in *OrderStatus, out *acme.OrderStatus, s conversion.Scope) error { - return autoConvert_v1beta1_OrderStatus_To_acme_OrderStatus(in, out, s) -} - -func autoConvert_acme_OrderStatus_To_v1beta1_OrderStatus(in *acme.OrderStatus, out *OrderStatus, s conversion.Scope) error { - out.URL = in.URL - out.FinalizeURL = in.FinalizeURL - out.Certificate = *(*[]byte)(unsafe.Pointer(&in.Certificate)) - out.State = State(in.State) - out.Reason = in.Reason - out.Authorizations = *(*[]ACMEAuthorization)(unsafe.Pointer(&in.Authorizations)) - out.FailureTime = (*pkgapismetav1.Time)(unsafe.Pointer(in.FailureTime)) - return nil -} - -// Convert_acme_OrderStatus_To_v1beta1_OrderStatus is an autogenerated conversion function. -func Convert_acme_OrderStatus_To_v1beta1_OrderStatus(in *acme.OrderStatus, out *OrderStatus, s conversion.Scope) error { - return autoConvert_acme_OrderStatus_To_v1beta1_OrderStatus(in, out, s) -} diff --git a/internal/apis/acme/v1beta1/zz_generated.deepcopy.go b/internal/apis/acme/v1beta1/zz_generated.deepcopy.go deleted file mode 100644 index b08315dcb0d..00000000000 --- a/internal/apis/acme/v1beta1/zz_generated.deepcopy.go +++ /dev/null @@ -1,904 +0,0 @@ -//go:build !ignore_autogenerated -// +build !ignore_autogenerated - -/* -Copyright 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. -*/ - -// Code generated by deepcopy-gen. DO NOT EDIT. - -package v1beta1 - -import ( - metav1 "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" - v1 "k8s.io/api/core/v1" - apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" - apismetav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - runtime "k8s.io/apimachinery/pkg/runtime" - v1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" -) - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ACMEAuthorization) DeepCopyInto(out *ACMEAuthorization) { - *out = *in - if in.Wildcard != nil { - in, out := &in.Wildcard, &out.Wildcard - *out = new(bool) - **out = **in - } - if in.Challenges != nil { - in, out := &in.Challenges, &out.Challenges - *out = make([]ACMEChallenge, len(*in)) - copy(*out, *in) - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ACMEAuthorization. -func (in *ACMEAuthorization) DeepCopy() *ACMEAuthorization { - if in == nil { - return nil - } - out := new(ACMEAuthorization) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ACMEChallenge) DeepCopyInto(out *ACMEChallenge) { - *out = *in - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ACMEChallenge. -func (in *ACMEChallenge) DeepCopy() *ACMEChallenge { - if in == nil { - return nil - } - out := new(ACMEChallenge) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ACMEChallengeSolver) DeepCopyInto(out *ACMEChallengeSolver) { - *out = *in - if in.Selector != nil { - in, out := &in.Selector, &out.Selector - *out = new(CertificateDNSNameSelector) - (*in).DeepCopyInto(*out) - } - if in.HTTP01 != nil { - in, out := &in.HTTP01, &out.HTTP01 - *out = new(ACMEChallengeSolverHTTP01) - (*in).DeepCopyInto(*out) - } - if in.DNS01 != nil { - in, out := &in.DNS01, &out.DNS01 - *out = new(ACMEChallengeSolverDNS01) - (*in).DeepCopyInto(*out) - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ACMEChallengeSolver. -func (in *ACMEChallengeSolver) DeepCopy() *ACMEChallengeSolver { - if in == nil { - return nil - } - out := new(ACMEChallengeSolver) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ACMEChallengeSolverDNS01) DeepCopyInto(out *ACMEChallengeSolverDNS01) { - *out = *in - if in.Akamai != nil { - in, out := &in.Akamai, &out.Akamai - *out = new(ACMEIssuerDNS01ProviderAkamai) - **out = **in - } - if in.CloudDNS != nil { - in, out := &in.CloudDNS, &out.CloudDNS - *out = new(ACMEIssuerDNS01ProviderCloudDNS) - (*in).DeepCopyInto(*out) - } - if in.Cloudflare != nil { - in, out := &in.Cloudflare, &out.Cloudflare - *out = new(ACMEIssuerDNS01ProviderCloudflare) - (*in).DeepCopyInto(*out) - } - if in.Route53 != nil { - in, out := &in.Route53, &out.Route53 - *out = new(ACMEIssuerDNS01ProviderRoute53) - (*in).DeepCopyInto(*out) - } - if in.AzureDNS != nil { - in, out := &in.AzureDNS, &out.AzureDNS - *out = new(ACMEIssuerDNS01ProviderAzureDNS) - (*in).DeepCopyInto(*out) - } - if in.DigitalOcean != nil { - in, out := &in.DigitalOcean, &out.DigitalOcean - *out = new(ACMEIssuerDNS01ProviderDigitalOcean) - **out = **in - } - if in.AcmeDNS != nil { - in, out := &in.AcmeDNS, &out.AcmeDNS - *out = new(ACMEIssuerDNS01ProviderAcmeDNS) - **out = **in - } - if in.RFC2136 != nil { - in, out := &in.RFC2136, &out.RFC2136 - *out = new(ACMEIssuerDNS01ProviderRFC2136) - **out = **in - } - if in.Webhook != nil { - in, out := &in.Webhook, &out.Webhook - *out = new(ACMEIssuerDNS01ProviderWebhook) - (*in).DeepCopyInto(*out) - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ACMEChallengeSolverDNS01. -func (in *ACMEChallengeSolverDNS01) DeepCopy() *ACMEChallengeSolverDNS01 { - if in == nil { - return nil - } - out := new(ACMEChallengeSolverDNS01) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ACMEChallengeSolverHTTP01) DeepCopyInto(out *ACMEChallengeSolverHTTP01) { - *out = *in - if in.Ingress != nil { - in, out := &in.Ingress, &out.Ingress - *out = new(ACMEChallengeSolverHTTP01Ingress) - (*in).DeepCopyInto(*out) - } - if in.GatewayHTTPRoute != nil { - in, out := &in.GatewayHTTPRoute, &out.GatewayHTTPRoute - *out = new(ACMEChallengeSolverHTTP01GatewayHTTPRoute) - (*in).DeepCopyInto(*out) - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ACMEChallengeSolverHTTP01. -func (in *ACMEChallengeSolverHTTP01) DeepCopy() *ACMEChallengeSolverHTTP01 { - if in == nil { - return nil - } - out := new(ACMEChallengeSolverHTTP01) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ACMEChallengeSolverHTTP01GatewayHTTPRoute) DeepCopyInto(out *ACMEChallengeSolverHTTP01GatewayHTTPRoute) { - *out = *in - if in.Labels != nil { - in, out := &in.Labels, &out.Labels - *out = make(map[string]string, len(*in)) - for key, val := range *in { - (*out)[key] = val - } - } - if in.ParentRefs != nil { - in, out := &in.ParentRefs, &out.ParentRefs - *out = make([]v1alpha2.ParentReference, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ACMEChallengeSolverHTTP01GatewayHTTPRoute. -func (in *ACMEChallengeSolverHTTP01GatewayHTTPRoute) DeepCopy() *ACMEChallengeSolverHTTP01GatewayHTTPRoute { - if in == nil { - return nil - } - out := new(ACMEChallengeSolverHTTP01GatewayHTTPRoute) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ACMEChallengeSolverHTTP01Ingress) DeepCopyInto(out *ACMEChallengeSolverHTTP01Ingress) { - *out = *in - if in.Class != nil { - in, out := &in.Class, &out.Class - *out = new(string) - **out = **in - } - if in.PodTemplate != nil { - in, out := &in.PodTemplate, &out.PodTemplate - *out = new(ACMEChallengeSolverHTTP01IngressPodTemplate) - (*in).DeepCopyInto(*out) - } - if in.IngressTemplate != nil { - in, out := &in.IngressTemplate, &out.IngressTemplate - *out = new(ACMEChallengeSolverHTTP01IngressTemplate) - (*in).DeepCopyInto(*out) - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ACMEChallengeSolverHTTP01Ingress. -func (in *ACMEChallengeSolverHTTP01Ingress) DeepCopy() *ACMEChallengeSolverHTTP01Ingress { - if in == nil { - return nil - } - out := new(ACMEChallengeSolverHTTP01Ingress) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ACMEChallengeSolverHTTP01IngressObjectMeta) DeepCopyInto(out *ACMEChallengeSolverHTTP01IngressObjectMeta) { - *out = *in - if in.Annotations != nil { - in, out := &in.Annotations, &out.Annotations - *out = make(map[string]string, len(*in)) - for key, val := range *in { - (*out)[key] = val - } - } - if in.Labels != nil { - in, out := &in.Labels, &out.Labels - *out = make(map[string]string, len(*in)) - for key, val := range *in { - (*out)[key] = val - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ACMEChallengeSolverHTTP01IngressObjectMeta. -func (in *ACMEChallengeSolverHTTP01IngressObjectMeta) DeepCopy() *ACMEChallengeSolverHTTP01IngressObjectMeta { - if in == nil { - return nil - } - out := new(ACMEChallengeSolverHTTP01IngressObjectMeta) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ACMEChallengeSolverHTTP01IngressPodObjectMeta) DeepCopyInto(out *ACMEChallengeSolverHTTP01IngressPodObjectMeta) { - *out = *in - if in.Annotations != nil { - in, out := &in.Annotations, &out.Annotations - *out = make(map[string]string, len(*in)) - for key, val := range *in { - (*out)[key] = val - } - } - if in.Labels != nil { - in, out := &in.Labels, &out.Labels - *out = make(map[string]string, len(*in)) - for key, val := range *in { - (*out)[key] = val - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ACMEChallengeSolverHTTP01IngressPodObjectMeta. -func (in *ACMEChallengeSolverHTTP01IngressPodObjectMeta) DeepCopy() *ACMEChallengeSolverHTTP01IngressPodObjectMeta { - if in == nil { - return nil - } - out := new(ACMEChallengeSolverHTTP01IngressPodObjectMeta) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ACMEChallengeSolverHTTP01IngressPodSpec) DeepCopyInto(out *ACMEChallengeSolverHTTP01IngressPodSpec) { - *out = *in - if in.NodeSelector != nil { - in, out := &in.NodeSelector, &out.NodeSelector - *out = make(map[string]string, len(*in)) - for key, val := range *in { - (*out)[key] = val - } - } - if in.Affinity != nil { - in, out := &in.Affinity, &out.Affinity - *out = new(v1.Affinity) - (*in).DeepCopyInto(*out) - } - if in.Tolerations != nil { - in, out := &in.Tolerations, &out.Tolerations - *out = make([]v1.Toleration, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ACMEChallengeSolverHTTP01IngressPodSpec. -func (in *ACMEChallengeSolverHTTP01IngressPodSpec) DeepCopy() *ACMEChallengeSolverHTTP01IngressPodSpec { - if in == nil { - return nil - } - out := new(ACMEChallengeSolverHTTP01IngressPodSpec) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ACMEChallengeSolverHTTP01IngressPodTemplate) DeepCopyInto(out *ACMEChallengeSolverHTTP01IngressPodTemplate) { - *out = *in - in.ACMEChallengeSolverHTTP01IngressPodObjectMeta.DeepCopyInto(&out.ACMEChallengeSolverHTTP01IngressPodObjectMeta) - in.Spec.DeepCopyInto(&out.Spec) - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ACMEChallengeSolverHTTP01IngressPodTemplate. -func (in *ACMEChallengeSolverHTTP01IngressPodTemplate) DeepCopy() *ACMEChallengeSolverHTTP01IngressPodTemplate { - if in == nil { - return nil - } - out := new(ACMEChallengeSolverHTTP01IngressPodTemplate) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ACMEChallengeSolverHTTP01IngressTemplate) DeepCopyInto(out *ACMEChallengeSolverHTTP01IngressTemplate) { - *out = *in - in.ACMEChallengeSolverHTTP01IngressObjectMeta.DeepCopyInto(&out.ACMEChallengeSolverHTTP01IngressObjectMeta) - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ACMEChallengeSolverHTTP01IngressTemplate. -func (in *ACMEChallengeSolverHTTP01IngressTemplate) DeepCopy() *ACMEChallengeSolverHTTP01IngressTemplate { - if in == nil { - return nil - } - out := new(ACMEChallengeSolverHTTP01IngressTemplate) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ACMEExternalAccountBinding) DeepCopyInto(out *ACMEExternalAccountBinding) { - *out = *in - out.Key = in.Key - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ACMEExternalAccountBinding. -func (in *ACMEExternalAccountBinding) DeepCopy() *ACMEExternalAccountBinding { - if in == nil { - return nil - } - out := new(ACMEExternalAccountBinding) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ACMEIssuer) DeepCopyInto(out *ACMEIssuer) { - *out = *in - if in.ExternalAccountBinding != nil { - in, out := &in.ExternalAccountBinding, &out.ExternalAccountBinding - *out = new(ACMEExternalAccountBinding) - **out = **in - } - out.PrivateKey = in.PrivateKey - if in.Solvers != nil { - in, out := &in.Solvers, &out.Solvers - *out = make([]ACMEChallengeSolver, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ACMEIssuer. -func (in *ACMEIssuer) DeepCopy() *ACMEIssuer { - if in == nil { - return nil - } - out := new(ACMEIssuer) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ACMEIssuerDNS01ProviderAcmeDNS) DeepCopyInto(out *ACMEIssuerDNS01ProviderAcmeDNS) { - *out = *in - out.AccountSecret = in.AccountSecret - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ACMEIssuerDNS01ProviderAcmeDNS. -func (in *ACMEIssuerDNS01ProviderAcmeDNS) DeepCopy() *ACMEIssuerDNS01ProviderAcmeDNS { - if in == nil { - return nil - } - out := new(ACMEIssuerDNS01ProviderAcmeDNS) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ACMEIssuerDNS01ProviderAkamai) DeepCopyInto(out *ACMEIssuerDNS01ProviderAkamai) { - *out = *in - out.ClientToken = in.ClientToken - out.ClientSecret = in.ClientSecret - out.AccessToken = in.AccessToken - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ACMEIssuerDNS01ProviderAkamai. -func (in *ACMEIssuerDNS01ProviderAkamai) DeepCopy() *ACMEIssuerDNS01ProviderAkamai { - if in == nil { - return nil - } - out := new(ACMEIssuerDNS01ProviderAkamai) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ACMEIssuerDNS01ProviderAzureDNS) DeepCopyInto(out *ACMEIssuerDNS01ProviderAzureDNS) { - *out = *in - if in.ClientSecret != nil { - in, out := &in.ClientSecret, &out.ClientSecret - *out = new(metav1.SecretKeySelector) - **out = **in - } - if in.ManagedIdentity != nil { - in, out := &in.ManagedIdentity, &out.ManagedIdentity - *out = new(AzureManagedIdentity) - **out = **in - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ACMEIssuerDNS01ProviderAzureDNS. -func (in *ACMEIssuerDNS01ProviderAzureDNS) DeepCopy() *ACMEIssuerDNS01ProviderAzureDNS { - if in == nil { - return nil - } - out := new(ACMEIssuerDNS01ProviderAzureDNS) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ACMEIssuerDNS01ProviderCloudDNS) DeepCopyInto(out *ACMEIssuerDNS01ProviderCloudDNS) { - *out = *in - if in.ServiceAccount != nil { - in, out := &in.ServiceAccount, &out.ServiceAccount - *out = new(metav1.SecretKeySelector) - **out = **in - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ACMEIssuerDNS01ProviderCloudDNS. -func (in *ACMEIssuerDNS01ProviderCloudDNS) DeepCopy() *ACMEIssuerDNS01ProviderCloudDNS { - if in == nil { - return nil - } - out := new(ACMEIssuerDNS01ProviderCloudDNS) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ACMEIssuerDNS01ProviderCloudflare) DeepCopyInto(out *ACMEIssuerDNS01ProviderCloudflare) { - *out = *in - if in.APIKey != nil { - in, out := &in.APIKey, &out.APIKey - *out = new(metav1.SecretKeySelector) - **out = **in - } - if in.APIToken != nil { - in, out := &in.APIToken, &out.APIToken - *out = new(metav1.SecretKeySelector) - **out = **in - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ACMEIssuerDNS01ProviderCloudflare. -func (in *ACMEIssuerDNS01ProviderCloudflare) DeepCopy() *ACMEIssuerDNS01ProviderCloudflare { - if in == nil { - return nil - } - out := new(ACMEIssuerDNS01ProviderCloudflare) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ACMEIssuerDNS01ProviderDigitalOcean) DeepCopyInto(out *ACMEIssuerDNS01ProviderDigitalOcean) { - *out = *in - out.Token = in.Token - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ACMEIssuerDNS01ProviderDigitalOcean. -func (in *ACMEIssuerDNS01ProviderDigitalOcean) DeepCopy() *ACMEIssuerDNS01ProviderDigitalOcean { - if in == nil { - return nil - } - out := new(ACMEIssuerDNS01ProviderDigitalOcean) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ACMEIssuerDNS01ProviderRFC2136) DeepCopyInto(out *ACMEIssuerDNS01ProviderRFC2136) { - *out = *in - out.TSIGSecret = in.TSIGSecret - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ACMEIssuerDNS01ProviderRFC2136. -func (in *ACMEIssuerDNS01ProviderRFC2136) DeepCopy() *ACMEIssuerDNS01ProviderRFC2136 { - if in == nil { - return nil - } - out := new(ACMEIssuerDNS01ProviderRFC2136) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ACMEIssuerDNS01ProviderRoute53) DeepCopyInto(out *ACMEIssuerDNS01ProviderRoute53) { - *out = *in - if in.SecretAccessKeyID != nil { - in, out := &in.SecretAccessKeyID, &out.SecretAccessKeyID - *out = new(metav1.SecretKeySelector) - **out = **in - } - out.SecretAccessKey = in.SecretAccessKey - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ACMEIssuerDNS01ProviderRoute53. -func (in *ACMEIssuerDNS01ProviderRoute53) DeepCopy() *ACMEIssuerDNS01ProviderRoute53 { - if in == nil { - return nil - } - out := new(ACMEIssuerDNS01ProviderRoute53) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ACMEIssuerDNS01ProviderWebhook) DeepCopyInto(out *ACMEIssuerDNS01ProviderWebhook) { - *out = *in - if in.Config != nil { - in, out := &in.Config, &out.Config - *out = new(apiextensionsv1.JSON) - (*in).DeepCopyInto(*out) - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ACMEIssuerDNS01ProviderWebhook. -func (in *ACMEIssuerDNS01ProviderWebhook) DeepCopy() *ACMEIssuerDNS01ProviderWebhook { - if in == nil { - return nil - } - out := new(ACMEIssuerDNS01ProviderWebhook) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ACMEIssuerStatus) DeepCopyInto(out *ACMEIssuerStatus) { - *out = *in - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ACMEIssuerStatus. -func (in *ACMEIssuerStatus) DeepCopy() *ACMEIssuerStatus { - if in == nil { - return nil - } - out := new(ACMEIssuerStatus) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *AzureManagedIdentity) DeepCopyInto(out *AzureManagedIdentity) { - *out = *in - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AzureManagedIdentity. -func (in *AzureManagedIdentity) DeepCopy() *AzureManagedIdentity { - if in == nil { - return nil - } - out := new(AzureManagedIdentity) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *CertificateDNSNameSelector) DeepCopyInto(out *CertificateDNSNameSelector) { - *out = *in - if in.MatchLabels != nil { - in, out := &in.MatchLabels, &out.MatchLabels - *out = make(map[string]string, len(*in)) - for key, val := range *in { - (*out)[key] = val - } - } - if in.DNSNames != nil { - in, out := &in.DNSNames, &out.DNSNames - *out = make([]string, len(*in)) - copy(*out, *in) - } - if in.DNSZones != nil { - in, out := &in.DNSZones, &out.DNSZones - *out = make([]string, len(*in)) - copy(*out, *in) - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CertificateDNSNameSelector. -func (in *CertificateDNSNameSelector) DeepCopy() *CertificateDNSNameSelector { - if in == nil { - return nil - } - out := new(CertificateDNSNameSelector) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *Challenge) DeepCopyInto(out *Challenge) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - in.Spec.DeepCopyInto(&out.Spec) - out.Status = in.Status - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Challenge. -func (in *Challenge) DeepCopy() *Challenge { - if in == nil { - return nil - } - out := new(Challenge) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *Challenge) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ChallengeList) DeepCopyInto(out *ChallengeList) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ListMeta.DeepCopyInto(&out.ListMeta) - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]Challenge, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ChallengeList. -func (in *ChallengeList) DeepCopy() *ChallengeList { - if in == nil { - return nil - } - out := new(ChallengeList) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *ChallengeList) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ChallengeSpec) DeepCopyInto(out *ChallengeSpec) { - *out = *in - in.Solver.DeepCopyInto(&out.Solver) - out.IssuerRef = in.IssuerRef - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ChallengeSpec. -func (in *ChallengeSpec) DeepCopy() *ChallengeSpec { - if in == nil { - return nil - } - out := new(ChallengeSpec) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ChallengeStatus) DeepCopyInto(out *ChallengeStatus) { - *out = *in - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ChallengeStatus. -func (in *ChallengeStatus) DeepCopy() *ChallengeStatus { - if in == nil { - return nil - } - out := new(ChallengeStatus) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *Order) DeepCopyInto(out *Order) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - in.Spec.DeepCopyInto(&out.Spec) - in.Status.DeepCopyInto(&out.Status) - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Order. -func (in *Order) DeepCopy() *Order { - if in == nil { - return nil - } - out := new(Order) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *Order) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *OrderList) DeepCopyInto(out *OrderList) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ListMeta.DeepCopyInto(&out.ListMeta) - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]Order, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OrderList. -func (in *OrderList) DeepCopy() *OrderList { - if in == nil { - return nil - } - out := new(OrderList) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *OrderList) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *OrderSpec) DeepCopyInto(out *OrderSpec) { - *out = *in - if in.Request != nil { - in, out := &in.Request, &out.Request - *out = make([]byte, len(*in)) - copy(*out, *in) - } - out.IssuerRef = in.IssuerRef - if in.DNSNames != nil { - in, out := &in.DNSNames, &out.DNSNames - *out = make([]string, len(*in)) - copy(*out, *in) - } - if in.IPAddresses != nil { - in, out := &in.IPAddresses, &out.IPAddresses - *out = make([]string, len(*in)) - copy(*out, *in) - } - if in.Duration != nil { - in, out := &in.Duration, &out.Duration - *out = new(apismetav1.Duration) - **out = **in - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OrderSpec. -func (in *OrderSpec) DeepCopy() *OrderSpec { - if in == nil { - return nil - } - out := new(OrderSpec) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *OrderStatus) DeepCopyInto(out *OrderStatus) { - *out = *in - if in.Authorizations != nil { - in, out := &in.Authorizations, &out.Authorizations - *out = make([]ACMEAuthorization, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - if in.Certificate != nil { - in, out := &in.Certificate, &out.Certificate - *out = make([]byte, len(*in)) - copy(*out, *in) - } - if in.FailureTime != nil { - in, out := &in.FailureTime, &out.FailureTime - *out = (*in).DeepCopy() - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OrderStatus. -func (in *OrderStatus) DeepCopy() *OrderStatus { - if in == nil { - return nil - } - out := new(OrderStatus) - in.DeepCopyInto(out) - return out -} diff --git a/internal/apis/acme/v1beta1/zz_generated.defaults.go b/internal/apis/acme/v1beta1/zz_generated.defaults.go deleted file mode 100644 index 176b36f98d6..00000000000 --- a/internal/apis/acme/v1beta1/zz_generated.defaults.go +++ /dev/null @@ -1,33 +0,0 @@ -//go:build !ignore_autogenerated -// +build !ignore_autogenerated - -/* -Copyright 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. -*/ - -// Code generated by defaulter-gen. DO NOT EDIT. - -package v1beta1 - -import ( - runtime "k8s.io/apimachinery/pkg/runtime" -) - -// RegisterDefaults adds defaulters functions to the given scheme. -// Public to allow building arbitrary schemes. -// All generated defaulters are covering - they call all nested defaulters. -func RegisterDefaults(scheme *runtime.Scheme) error { - return nil -} diff --git a/internal/apis/acme/validation/challenge.go b/internal/apis/acme/validation/challenge.go index 3707fb196d6..7d481859313 100644 --- a/internal/apis/acme/validation/challenge.go +++ b/internal/apis/acme/validation/challenge.go @@ -27,15 +27,15 @@ import ( ) func ValidateChallengeUpdate(a *admissionv1.AdmissionRequest, oldObj, newObj runtime.Object) (field.ErrorList, []string) { - old, ok := oldObj.(*cmacme.Challenge) - new := newObj.(*cmacme.Challenge) + oldChallenge, ok := oldObj.(*cmacme.Challenge) + newChallenge := newObj.(*cmacme.Challenge) // if oldObj is not set, the Update operation is always valid. - if !ok || old == nil { + if !ok || oldChallenge == nil { return nil, nil } el := field.ErrorList{} - if !reflect.DeepEqual(old.Spec, new.Spec) { + if !reflect.DeepEqual(oldChallenge.Spec, newChallenge.Spec) { el = append(el, field.Forbidden(field.NewPath("spec"), "challenge spec is immutable after creation")) } return el, nil diff --git a/internal/apis/acme/validation/order.go b/internal/apis/acme/validation/order.go index cb88e7ad248..37ceda41405 100644 --- a/internal/apis/acme/validation/order.go +++ b/internal/apis/acme/validation/order.go @@ -27,16 +27,16 @@ import ( ) func ValidateOrderUpdate(a *admissionv1.AdmissionRequest, oldObj, newObj runtime.Object) (field.ErrorList, []string) { - old, ok := oldObj.(*cmacme.Order) - new := newObj.(*cmacme.Order) + oldOrder, ok := oldObj.(*cmacme.Order) + newOrder := newObj.(*cmacme.Order) // if oldObj is not set, the Update operation is always valid. - if !ok || old == nil { + if !ok || oldOrder == nil { return nil, nil } el := field.ErrorList{} - el = append(el, ValidateOrderSpecUpdate(old.Spec, new.Spec, field.NewPath("spec"))...) - el = append(el, ValidateOrderStatusUpdate(old.Status, new.Status, field.NewPath("status"))...) + el = append(el, ValidateOrderSpecUpdate(oldOrder.Spec, newOrder.Spec, field.NewPath("spec"))...) + el = append(el, ValidateOrderStatusUpdate(oldOrder.Status, newOrder.Status, field.NewPath("status"))...) return el, nil } @@ -44,35 +44,35 @@ func ValidateOrder(a *admissionv1.AdmissionRequest, obj runtime.Object) (field.E return nil, nil } -func ValidateOrderSpecUpdate(old, new cmacme.OrderSpec, fldPath *field.Path) field.ErrorList { +func ValidateOrderSpecUpdate(oldOrder, newOrder cmacme.OrderSpec, fldPath *field.Path) field.ErrorList { el := field.ErrorList{} - if len(old.Request) > 0 && !bytes.Equal(old.Request, new.Request) { + if len(oldOrder.Request) > 0 && !bytes.Equal(oldOrder.Request, newOrder.Request) { el = append(el, field.Forbidden(fldPath.Child("request"), "field is immutable once set")) } return el } -func ValidateOrderStatusUpdate(old, new cmacme.OrderStatus, fldPath *field.Path) field.ErrorList { +func ValidateOrderStatusUpdate(oldStatus, newStatus cmacme.OrderStatus, fldPath *field.Path) field.ErrorList { el := field.ErrorList{} // once the order URL has been set, it cannot be changed - if old.URL != "" && old.URL != new.URL { + if oldStatus.URL != "" && oldStatus.URL != newStatus.URL { el = append(el, field.Forbidden(fldPath.Child("url"), "field is immutable once set")) } // once the FinalizeURL has been set, it cannot be changed - if old.FinalizeURL != "" && old.FinalizeURL != new.FinalizeURL { + if oldStatus.FinalizeURL != "" && oldStatus.FinalizeURL != newStatus.FinalizeURL { el = append(el, field.Forbidden(fldPath.Child("finalizeURL"), "field is immutable once set")) } // once the Certificate has been issued, it cannot be changed - if len(old.Certificate) > 0 && !bytes.Equal(old.Certificate, new.Certificate) { + if len(oldStatus.Certificate) > 0 && !bytes.Equal(oldStatus.Certificate, newStatus.Certificate) { el = append(el, field.Forbidden(fldPath.Child("certificate"), "field is immutable once set")) } - if len(old.Authorizations) > 0 { + if len(oldStatus.Authorizations) > 0 { fldPath := fldPath.Child("authorizations") // once at least one Authorization has been inserted, no more can be added // or deleted from the Order - if len(old.Authorizations) != len(new.Authorizations) { + if len(oldStatus.Authorizations) != len(newStatus.Authorizations) { el = append(el, field.Forbidden(fldPath, "field is immutable once set")) } @@ -80,43 +80,43 @@ func ValidateOrderStatusUpdate(old, new cmacme.OrderStatus, fldPath *field.Path) // the updates that the user requested on each Authorization. // fields on Authorization's cannot be changed after being set from // their zero value. - for i := range old.Authorizations { + for i := range oldStatus.Authorizations { fldPath := fldPath.Index(i) - old := old.Authorizations[i] - new := new.Authorizations[i] - if old.URL != "" && old.URL != new.URL { + oldAuthz := oldStatus.Authorizations[i] + newAuthz := newStatus.Authorizations[i] + if oldAuthz.URL != "" && oldAuthz.URL != newAuthz.URL { el = append(el, field.Forbidden(fldPath.Child("url"), "field is immutable once set")) } - if old.Identifier != "" && old.Identifier != new.Identifier { + if oldAuthz.Identifier != "" && oldAuthz.Identifier != newAuthz.Identifier { el = append(el, field.Forbidden(fldPath.Child("identifier"), "field is immutable once set")) } // don't allow the value of the Wildcard field to change unless the // old value is nil - if old.Wildcard != nil && (new.Wildcard == nil || *old.Wildcard != *new.Wildcard) { + if oldAuthz.Wildcard != nil && (newAuthz.Wildcard == nil || *oldAuthz.Wildcard != *newAuthz.Wildcard) { el = append(el, field.Forbidden(fldPath.Child("wildcard"), "field is immutable once set")) } - if old.InitialState != "" && (old.InitialState != new.InitialState) { + if oldAuthz.InitialState != "" && (oldAuthz.InitialState != newAuthz.InitialState) { el = append(el, field.Forbidden(fldPath.Child("initialState"), "field is immutable once set")) } - if len(old.Challenges) > 0 { + if len(oldAuthz.Challenges) > 0 { fldPath := fldPath.Child("challenges") - if len(old.Challenges) != len(new.Challenges) { + if len(oldAuthz.Challenges) != len(newAuthz.Challenges) { el = append(el, field.Forbidden(fldPath, "field is immutable once set")) } - for i := range old.Challenges { + for i := range oldAuthz.Challenges { fldPath := fldPath.Index(i) - old := old.Challenges[i] - new := new.Challenges[i] + oldChallenge := oldAuthz.Challenges[i] + newChallenge := newAuthz.Challenges[i] - if old.URL != "" && old.URL != new.URL { + if oldChallenge.URL != "" && oldChallenge.URL != newChallenge.URL { el = append(el, field.Forbidden(fldPath.Child("url"), "field is immutable once set")) } - if old.Type != "" && old.Type != new.Type { + if oldChallenge.Type != "" && oldChallenge.Type != newChallenge.Type { el = append(el, field.Forbidden(fldPath.Child("type"), "field is immutable once set")) } - if old.Token != "" && old.Token != new.Token { + if oldChallenge.Token != "" && oldChallenge.Token != newChallenge.Token { el = append(el, field.Forbidden(fldPath.Child("token"), "field is immutable once set")) } } diff --git a/internal/apis/acme/validation/order_test.go b/internal/apis/acme/validation/order_test.go index 6ce7b804f22..c817626df12 100644 --- a/internal/apis/acme/validation/order_test.go +++ b/internal/apis/acme/validation/order_test.go @@ -23,7 +23,7 @@ import ( admissionv1 "k8s.io/api/admission/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/validation/field" - "k8s.io/utils/pointer" + "k8s.io/utils/ptr" cmacme "github.com/cert-manager/cert-manager/internal/apis/acme" ) @@ -55,11 +55,11 @@ func testImmutableOrderField(t *testing.T, fldPath *field.Path, setter func(*cma field.Forbidden(fldPath, "field is immutable once set"), } var expectedWarnings []string - old := &cmacme.Order{} - new := &cmacme.Order{} - setter(old, testValueOptionOne) - setter(new, testValueOptionTwo) - errs, warnings := ValidateOrderUpdate(someAdmissionRequest, old, new) + oldOrder := &cmacme.Order{} + newOrder := &cmacme.Order{} + setter(oldOrder, testValueOptionOne) + setter(newOrder, testValueOptionTwo) + errs, warnings := ValidateOrderUpdate(someAdmissionRequest, oldOrder, newOrder) if len(errs) != len(expectedErrs) { t.Errorf("Expected errors %v but got %v", expectedErrs, errs) return @@ -77,11 +77,11 @@ func testImmutableOrderField(t *testing.T, fldPath *field.Path, setter func(*cma t.Run("should allow updates to "+fldPath.String()+" if not already set", func(t *testing.T) { expectedErrs := []*field.Error{} var expectedWarnings []string - old := &cmacme.Order{} - new := &cmacme.Order{} - setter(old, testValueNone) - setter(new, testValueOptionOne) - errs, warnings := ValidateOrderUpdate(someAdmissionRequest, old, new) + oldOrder := &cmacme.Order{} + newOrder := &cmacme.Order{} + setter(oldOrder, testValueNone) + setter(newOrder, testValueOptionOne) + errs, warnings := ValidateOrderUpdate(someAdmissionRequest, oldOrder, newOrder) if len(errs) != len(expectedErrs) { t.Errorf("Expected errors %v but got %v", expectedErrs, errs) return @@ -153,11 +153,11 @@ func TestValidateOrderUpdate(t *testing.T) { } case testValueOptionOne: o.Status.Authorizations = []cmacme.ACMEAuthorization{ - {Wildcard: pointer.BoolPtr(false)}, + {Wildcard: ptr.To(false)}, } case testValueOptionTwo: o.Status.Authorizations = []cmacme.ACMEAuthorization{ - {Wildcard: pointer.BoolPtr(true)}, + {Wildcard: ptr.To(true)}, } } }) diff --git a/internal/apis/acme/zz_generated.deepcopy.go b/internal/apis/acme/zz_generated.deepcopy.go index b26095d209e..3a0c70bde92 100644 --- a/internal/apis/acme/zz_generated.deepcopy.go +++ b/internal/apis/acme/zz_generated.deepcopy.go @@ -23,11 +23,11 @@ package acme import ( meta "github.com/cert-manager/cert-manager/internal/apis/meta" - v1 "k8s.io/api/core/v1" + corev1 "k8s.io/api/core/v1" apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" - v1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" + v1 "sigs.k8s.io/gateway-api/apis/v1" ) // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. @@ -202,11 +202,16 @@ func (in *ACMEChallengeSolverHTTP01GatewayHTTPRoute) DeepCopyInto(out *ACMEChall } if in.ParentRefs != nil { in, out := &in.ParentRefs, &out.ParentRefs - *out = make([]v1alpha2.ParentReference, len(*in)) + *out = make([]v1.ParentReference, len(*in)) for i := range *in { (*in)[i].DeepCopyInto(&(*out)[i]) } } + if in.PodTemplate != nil { + in, out := &in.PodTemplate, &out.PodTemplate + *out = new(ACMEChallengeSolverHTTP01IngressPodTemplate) + (*in).DeepCopyInto(*out) + } return } @@ -223,6 +228,11 @@ func (in *ACMEChallengeSolverHTTP01GatewayHTTPRoute) DeepCopy() *ACMEChallengeSo // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ACMEChallengeSolverHTTP01Ingress) DeepCopyInto(out *ACMEChallengeSolverHTTP01Ingress) { *out = *in + if in.IngressClassName != nil { + in, out := &in.IngressClassName, &out.IngressClassName + *out = new(string) + **out = **in + } if in.Class != nil { in, out := &in.Class, &out.Class *out = new(string) @@ -311,6 +321,97 @@ func (in *ACMEChallengeSolverHTTP01IngressPodObjectMeta) DeepCopy() *ACMEChallen return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ACMEChallengeSolverHTTP01IngressPodResources) DeepCopyInto(out *ACMEChallengeSolverHTTP01IngressPodResources) { + *out = *in + if in.Limits != nil { + in, out := &in.Limits, &out.Limits + *out = make(corev1.ResourceList, len(*in)) + for key, val := range *in { + (*out)[key] = val.DeepCopy() + } + } + if in.Requests != nil { + in, out := &in.Requests, &out.Requests + *out = make(corev1.ResourceList, len(*in)) + for key, val := range *in { + (*out)[key] = val.DeepCopy() + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ACMEChallengeSolverHTTP01IngressPodResources. +func (in *ACMEChallengeSolverHTTP01IngressPodResources) DeepCopy() *ACMEChallengeSolverHTTP01IngressPodResources { + if in == nil { + return nil + } + out := new(ACMEChallengeSolverHTTP01IngressPodResources) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ACMEChallengeSolverHTTP01IngressPodSecurityContext) DeepCopyInto(out *ACMEChallengeSolverHTTP01IngressPodSecurityContext) { + *out = *in + if in.SELinuxOptions != nil { + in, out := &in.SELinuxOptions, &out.SELinuxOptions + *out = new(corev1.SELinuxOptions) + **out = **in + } + if in.RunAsUser != nil { + in, out := &in.RunAsUser, &out.RunAsUser + *out = new(int64) + **out = **in + } + if in.RunAsGroup != nil { + in, out := &in.RunAsGroup, &out.RunAsGroup + *out = new(int64) + **out = **in + } + if in.RunAsNonRoot != nil { + in, out := &in.RunAsNonRoot, &out.RunAsNonRoot + *out = new(bool) + **out = **in + } + if in.SupplementalGroups != nil { + in, out := &in.SupplementalGroups, &out.SupplementalGroups + *out = make([]int64, len(*in)) + copy(*out, *in) + } + if in.FSGroup != nil { + in, out := &in.FSGroup, &out.FSGroup + *out = new(int64) + **out = **in + } + if in.Sysctls != nil { + in, out := &in.Sysctls, &out.Sysctls + *out = make([]corev1.Sysctl, len(*in)) + copy(*out, *in) + } + if in.FSGroupChangePolicy != nil { + in, out := &in.FSGroupChangePolicy, &out.FSGroupChangePolicy + *out = new(corev1.PodFSGroupChangePolicy) + **out = **in + } + if in.SeccompProfile != nil { + in, out := &in.SeccompProfile, &out.SeccompProfile + *out = new(corev1.SeccompProfile) + (*in).DeepCopyInto(*out) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ACMEChallengeSolverHTTP01IngressPodSecurityContext. +func (in *ACMEChallengeSolverHTTP01IngressPodSecurityContext) DeepCopy() *ACMEChallengeSolverHTTP01IngressPodSecurityContext { + if in == nil { + return nil + } + out := new(ACMEChallengeSolverHTTP01IngressPodSecurityContext) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ACMEChallengeSolverHTTP01IngressPodSpec) DeepCopyInto(out *ACMEChallengeSolverHTTP01IngressPodSpec) { *out = *in @@ -323,16 +424,31 @@ func (in *ACMEChallengeSolverHTTP01IngressPodSpec) DeepCopyInto(out *ACMEChallen } if in.Affinity != nil { in, out := &in.Affinity, &out.Affinity - *out = new(v1.Affinity) + *out = new(corev1.Affinity) (*in).DeepCopyInto(*out) } if in.Tolerations != nil { in, out := &in.Tolerations, &out.Tolerations - *out = make([]v1.Toleration, len(*in)) + *out = make([]corev1.Toleration, len(*in)) for i := range *in { (*in)[i].DeepCopyInto(&(*out)[i]) } } + if in.ImagePullSecrets != nil { + in, out := &in.ImagePullSecrets, &out.ImagePullSecrets + *out = make([]corev1.LocalObjectReference, len(*in)) + copy(*out, *in) + } + if in.SecurityContext != nil { + in, out := &in.SecurityContext, &out.SecurityContext + *out = new(ACMEChallengeSolverHTTP01IngressPodSecurityContext) + (*in).DeepCopyInto(*out) + } + if in.Resources != nil { + in, out := &in.Resources, &out.Resources + *out = new(ACMEChallengeSolverHTTP01IngressPodResources) + (*in).DeepCopyInto(*out) + } return } @@ -401,6 +517,11 @@ func (in *ACMEExternalAccountBinding) DeepCopy() *ACMEExternalAccountBinding { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ACMEIssuer) DeepCopyInto(out *ACMEIssuer) { *out = *in + if in.CABundle != nil { + in, out := &in.CABundle, &out.CABundle + *out = make([]byte, len(*in)) + copy(*out, *in) + } if in.ExternalAccountBinding != nil { in, out := &in.ExternalAccountBinding, &out.ExternalAccountBinding *out = new(ACMEExternalAccountBinding) @@ -573,6 +694,11 @@ func (in *ACMEIssuerDNS01ProviderRFC2136) DeepCopy() *ACMEIssuerDNS01ProviderRFC // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ACMEIssuerDNS01ProviderRoute53) DeepCopyInto(out *ACMEIssuerDNS01ProviderRoute53) { *out = *in + if in.Auth != nil { + in, out := &in.Auth, &out.Auth + *out = new(Route53Auth) + (*in).DeepCopyInto(*out) + } if in.SecretAccessKeyID != nil { in, out := &in.SecretAccessKeyID, &out.SecretAccessKeyID *out = new(meta.SecretKeySelector) @@ -902,3 +1028,66 @@ func (in *OrderStatus) DeepCopy() *OrderStatus { in.DeepCopyInto(out) return out } + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Route53Auth) DeepCopyInto(out *Route53Auth) { + *out = *in + if in.Kubernetes != nil { + in, out := &in.Kubernetes, &out.Kubernetes + *out = new(Route53KubernetesAuth) + (*in).DeepCopyInto(*out) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Route53Auth. +func (in *Route53Auth) DeepCopy() *Route53Auth { + if in == nil { + return nil + } + out := new(Route53Auth) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Route53KubernetesAuth) DeepCopyInto(out *Route53KubernetesAuth) { + *out = *in + if in.ServiceAccountRef != nil { + in, out := &in.ServiceAccountRef, &out.ServiceAccountRef + *out = new(ServiceAccountRef) + (*in).DeepCopyInto(*out) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Route53KubernetesAuth. +func (in *Route53KubernetesAuth) DeepCopy() *Route53KubernetesAuth { + if in == nil { + return nil + } + out := new(Route53KubernetesAuth) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ServiceAccountRef) DeepCopyInto(out *ServiceAccountRef) { + *out = *in + if in.TokenAudiences != nil { + in, out := &in.TokenAudiences, &out.TokenAudiences + *out = make([]string, len(*in)) + copy(*out, *in) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServiceAccountRef. +func (in *ServiceAccountRef) DeepCopy() *ServiceAccountRef { + if in == nil { + return nil + } + out := new(ServiceAccountRef) + in.DeepCopyInto(out) + return out +} diff --git a/internal/apis/certmanager/doc.go b/internal/apis/certmanager/doc.go index acd3460e9e0..2ada5adf0c9 100644 --- a/internal/apis/certmanager/doc.go +++ b/internal/apis/certmanager/doc.go @@ -14,6 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ +// +kubebuilder:skip // +k8s:deepcopy-gen=package,register // Package certmanager is the internal version of the API. diff --git a/internal/apis/certmanager/fuzzer/fuzzer.go b/internal/apis/certmanager/fuzzer/fuzzer.go index b9ceea48690..5db597d84e4 100644 --- a/internal/apis/certmanager/fuzzer/fuzzer.go +++ b/internal/apis/certmanager/fuzzer/fuzzer.go @@ -17,9 +17,9 @@ limitations under the License. package fuzzer import ( - fuzz "github.com/google/gofuzz" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtimeserializer "k8s.io/apimachinery/pkg/runtime/serializer" + "sigs.k8s.io/randfill" acmefuzzer "github.com/cert-manager/cert-manager/internal/apis/acme/fuzzer" "github.com/cert-manager/cert-manager/internal/apis/certmanager" @@ -29,12 +29,15 @@ import ( // Funcs returns the fuzzer functions for the apps api group. var Funcs = func(codecs runtimeserializer.CodecFactory) []interface{} { return append(acmefuzzer.Funcs(codecs), []interface{}{ - func(s *certmanager.Certificate, c fuzz.Continue) { - c.FuzzNoCustom(s) // fuzz self without calling this function again + func(s *certmanager.Certificate, c randfill.Continue) { + c.FillNoCustom(s) // fuzz self without calling this function again if len(s.Spec.DNSNames) == 0 { s.Spec.DNSNames = []string{s.Spec.CommonName} } + if s.Spec.IssuerRef.Group == "" { + s.Spec.IssuerRef.Group = "cert-manager.io" + } if s.Spec.IssuerRef.Kind == "" { s.Spec.IssuerRef.Kind = v1.IssuerKind } @@ -42,9 +45,12 @@ var Funcs = func(codecs runtimeserializer.CodecFactory) []interface{} { s.Spec.Duration = &metav1.Duration{Duration: v1.DefaultCertificateDuration} } }, - func(s *certmanager.CertificateRequest, c fuzz.Continue) { - c.FuzzNoCustom(s) // fuzz self without calling this function again + func(s *certmanager.CertificateRequest, c randfill.Continue) { + c.FillNoCustom(s) // fuzz self without calling this function again + if s.Spec.IssuerRef.Group == "" { + s.Spec.IssuerRef.Group = "cert-manager.io" + } if s.Spec.IssuerRef.Kind == "" { s.Spec.IssuerRef.Kind = v1.IssuerKind } diff --git a/internal/apis/certmanager/install/install.go b/internal/apis/certmanager/install/install.go index 6d9b3aec5b6..016ad76f256 100644 --- a/internal/apis/certmanager/install/install.go +++ b/internal/apis/certmanager/install/install.go @@ -24,9 +24,6 @@ import ( "github.com/cert-manager/cert-manager/internal/apis/certmanager" v1 "github.com/cert-manager/cert-manager/internal/apis/certmanager/v1" - "github.com/cert-manager/cert-manager/internal/apis/certmanager/v1alpha2" - "github.com/cert-manager/cert-manager/internal/apis/certmanager/v1alpha3" - "github.com/cert-manager/cert-manager/internal/apis/certmanager/v1beta1" cmmetav1 "github.com/cert-manager/cert-manager/internal/apis/meta/v1" ) @@ -35,9 +32,6 @@ func Install(scheme *runtime.Scheme) { utilruntime.Must(certmanager.AddToScheme(scheme)) // The first version in this list will be the default version used utilruntime.Must(v1.AddToScheme(scheme)) - utilruntime.Must(v1beta1.AddToScheme(scheme)) - utilruntime.Must(v1alpha3.AddToScheme(scheme)) - utilruntime.Must(v1alpha2.AddToScheme(scheme)) utilruntime.Must(cmmetav1.AddToScheme(scheme)) } diff --git a/internal/apis/certmanager/types.go b/internal/apis/certmanager/types.go index ac7a113f956..6a7aaa29cc2 100644 --- a/internal/apis/certmanager/types.go +++ b/internal/apis/certmanager/types.go @@ -30,6 +30,33 @@ const ( // Annotation key for certificate common name. CommonNameAnnotationKey = "cert-manager.io/common-name" + // Annotation key for emails subjectAltNames. + EmailsAnnotationKey = "cert-manager.io/email-sans" + + // Annotation key for subject organization. + SubjectOrganizationsAnnotationKey = "cert-manager.io/subject-organizations" + + // Annotation key for subject organizational units. + SubjectOrganizationalUnitsAnnotationKey = "cert-manager.io/subject-organizationalunits" + + // Annotation key for subject organizational units. + SubjectCountriesAnnotationKey = "cert-manager.io/subject-countries" + + // Annotation key for subject provinces. + SubjectProvincesAnnotationKey = "cert-manager.io/subject-provinces" + + // Annotation key for subject localities. + SubjectLocalitiesAnnotationKey = "cert-manager.io/subject-localities" + + // Annotation key for subject provinces. + SubjectStreetAddressesAnnotationKey = "cert-manager.io/subject-streetaddresses" + + // Annotation key for subject postal codes. + SubjectPostalCodesAnnotationKey = "cert-manager.io/subject-postalcodes" + + // Annotation key for subject serial number. + SubjectSerialNumberAnnotationKey = "cert-manager.io/subject-serialnumber" + // Annotation key the 'name' of the Issuer resource. IssuerNameAnnotationKey = "cert-manager.io/issuer-name" @@ -185,3 +212,15 @@ const ( UsageMicrosoftSGC KeyUsage = "microsoft sgc" UsageNetscapeSGC KeyUsage = "netscape sgc" ) + +type SignatureAlgorithm string + +const ( + SHA256WithRSA SignatureAlgorithm = "SHA256WithRSA" + SHA384WithRSA SignatureAlgorithm = "SHA384WithRSA" + SHA512WithRSA SignatureAlgorithm = "SHA512WithRSA" + ECDSAWithSHA256 SignatureAlgorithm = "ECDSAWithSHA256" + ECDSAWithSHA384 SignatureAlgorithm = "ECDSAWithSHA384" + ECDSAWithSHA512 SignatureAlgorithm = "ECDSAWithSHA512" + PureEd25519 SignatureAlgorithm = "PureEd25519" +) diff --git a/internal/apis/certmanager/types_certificate.go b/internal/apis/certmanager/types_certificate.go index 6163a19a9ec..028a267fcce 100644 --- a/internal/apis/certmanager/types_certificate.go +++ b/internal/apis/certmanager/types_certificate.go @@ -25,169 +25,271 @@ import ( // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object // 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`. +// 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`). type Certificate struct { metav1.TypeMeta + // Standard object's metadata. + // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata metav1.ObjectMeta - // Desired state of the Certificate resource. + // Specification of the desired state of the Certificate resource. + // https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status Spec CertificateSpec - // 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 Status CertificateStatus } // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object -// CertificateList is a list of Certificates +// CertificateList is a list of Certificates. type CertificateList struct { metav1.TypeMeta + // Standard list metadata. + // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds metav1.ListMeta + // List of Certificates Items []Certificate } type PrivateKeyAlgorithm string const ( - // Denotes the RSA private key type. + // RSA private key algorithm. RSAKeyAlgorithm PrivateKeyAlgorithm = "RSA" - // Denotes the ECDSA private key type. + // ECDSA private key algorithm. ECDSAKeyAlgorithm PrivateKeyAlgorithm = "ECDSA" - // Denotes the Ed25519 private key type. + // Ed25519 private key algorithm. Ed25519KeyAlgorithm PrivateKeyAlgorithm = "Ed25519" ) type PrivateKeyEncoding string const ( - // 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. PKCS1 PrivateKeyEncoding = "PKCS1" - // 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. PKCS8 PrivateKeyEncoding = "PKCS8" ) // CertificateSpec defines the desired state of Certificate. -// A valid Certificate requires at least one of a CommonName, DNSName, or -// URISAN to be valid. +// +// 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. type CertificateSpec struct { - // 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. Subject *X509Subject - // LiteralSubject is an LDAP formatted string that represents the [X.509 Subject field](https://datatracker.ietf.org/doc/html/rfc5280#section-4.1.2.6). - // 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. - // +optional - LiteralSubject string `json:"literalSubject,omitempty"` + // 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. + LiteralSubject string - // 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. CommonName string - // The requested 'duration' (i.e. lifetime) of the Certificate. - // This option may be ignored/overridden by some issuer types. - // If overridden and `renewBefore` is greater than the actual certificate - // duration, the certificate will be automatically renewed 2/3rds of the - // way through the certificate's duration. + // 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. Duration *metav1.Duration - // The amount of time before the currently issued certificate's `notAfter` - // time that cert-manager will begin to attempt to renew the certificate. - // If this value is greater than the total duration of the certificate - // (i.e. notAfter - notBefore), it will be automatically renewed 2/3rds of - // the way through the certificate's duration. + // 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 1/3 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. + // Cannot be set if the `renewBeforePercentage` field is set. + // +optional RenewBefore *metav1.Duration - // DNSNames is a list of DNS subjectAltNames to be set on the Certificate. + // `renewBeforePercentage` is like `renewBefore`, except it is a relative percentage + // rather than an absolute duration. For example, if a certificate is valid for 60 + // minutes, and `renewBeforePercentage=25`, cert-manager will begin to attempt to + // renew the certificate 45 minutes after it was issued (i.e. when there are 15 + // minutes (25%) 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. + // + // Value must be an integer in the range (0,100). The minimum effective + // `renewBefore` derived from the `renewBeforePercentage` and `duration` fields is 5 + // minutes. + // Cannot be set if the `renewBefore` field is set. + // +optional + RenewBeforePercentage *int32 + + // Requested DNS subject alternative names. DNSNames []string - // IPAddresses is a list of IP address subjectAltNames to be set on the Certificate. + // Requested IP address subject alternative names. IPAddresses []string - // URISANs is a list of URI subjectAltNames to be set on the Certificate. - URISANs []string + // Requested URI subject alternative names. + URIs []string + + // Requested email subject alternative names. + EmailAddresses []string - // EmailSANs is a list of email subjectAltNames to be set on the Certificate. - EmailSANs []string + // `otherNames` is an escape hatch for subject alternative names (SANs) which allows any string-like + // otherName as specified in RFC 5280 (https://www.rfc-editor.org/rfc/rfc5280#section-4.2.1.6). + // All `otherName`s must include an OID and a UTF-8 string value. For example, the OID for the UPN + // `otherName` is "1.3.6.1.4.1.311.20.2.3". + // No validation is performed on the given UTF-8 string, so users must ensure that the value is correct before use + // +optional + OtherNames []OtherName `json:"otherNames,omitempty"` - // 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. SecretName string - // 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. SecretTemplate *CertificateSecretTemplate - // Keystores configures additional keystore output formats stored in the - // `secretName` Secret resource. + // Additional keystore output formats to be stored in the Certificate's Secret. Keystores *CertificateKeystores - // 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. - IssuerRef cmmeta.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. + IssuerRef cmmeta.IssuerReference - // 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`. IsCA bool - // 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`. Usages []KeyUsage - // 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. PrivateKey *CertificatePrivateKey - // EncodeUsagesInRequest controls whether key usages should be present - // in the CertificateRequest + // Signature algorith to use. + SignatureAlgorithm SignatureAlgorithm + + // 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. EncodeUsagesInRequest *bool - // 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. + // Default value is `1`. RevisionHistoryLimit *int32 - // 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. + // Defines extra output formats of the private key and signed certificate chain + // to be written to this Certificate's target Secret. AdditionalOutputFormats []CertificateAdditionalOutputFormat + + // x.509 certificate NameConstraint extension which MUST NOT be used in a non-CA certificate. + // More Info: https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.10 + // + // This is an Alpha Feature and is only enabled with the + // `--feature-gates=NameConstraints=true` option set on both + // the controller and webhook components. + // +optional + NameConstraints *NameConstraints +} + +type OtherName struct { + // OID is the object identifier for the otherName SAN. + // The object identifier must be expressed as a dotted string, for + // example, "1.2.840.113556.1.4.221". + OID string `json:"oid,omitempty"` + + // utf8Value is the string value of the otherName SAN. Any UTF-8 string can be used, but no + // validation is performed. + UTF8Value string `json:"utf8Value,omitempty"` } // CertificatePrivateKey contains configuration options for private keys // used by the Certificate controller. -// This allows control of how private keys are rotated. +// These include the key algorithm and size, the used encoding and the +// rotation policy. type CertificatePrivateKey struct { // 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 + // already exist in the target `spec.secretName`. If one does exist 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 @@ -197,27 +299,49 @@ type CertificatePrivateKey struct { // 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. Encoding PrivateKeyEncoding // Algorithm is the private key algorithm of the corresponding private key - // for this certificate. If provided, allowed values are either `RSA` or `ECDSA` + // 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 `256` will be used for `ECDSA` key algorithm and - // key size of `2048` will be used for `RSA` key algorithm. + // 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. Algorithm PrivateKeyAlgorithm // 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 int } +// Denotes how private keys should be generated or sourced when a Certificate +// is being issued. +type PrivateKeyRotationPolicy string + +var ( + // RotationPolicyNever means a private key will only be generated if one + // does not already exist in the target `spec.secretName`. + // If one does exist but it does not have the correct algorithm or size, + // a warning will be raised to await user intervention. + RotationPolicyNever PrivateKeyRotationPolicy = "Never" + + // RotationPolicyAlways means a private key matching the specified + // requirements will be generated whenever a re-issuance occurs. + RotationPolicyAlways PrivateKeyRotationPolicy = "Always" +) + // CertificateOutputFormatType specifies which additional output formats should // be written to the Certificate's target Secret. // Allowed values are `DER` or `CombinedPEM`. @@ -229,17 +353,17 @@ type CertificatePrivateKey struct { type CertificateOutputFormatType string const ( - // AdditionalCertificateOutputFormatDER writes the Certificate's private key - // in DER binary format to the `key.der` target Secret Data key. - AdditionalCertificateOutputFormatDER CertificateOutputFormatType = "DER" + // CertificateOutputFormatDER writes the Certificate's private key in DER + // binary format to the `key.der` target Secret Data key. + CertificateOutputFormatDER CertificateOutputFormatType = "DER" - // AdditionalCertificateOutputFormatCombinedPEM writes the Certificate's - // signed certificate chain and private key, in PEM format, to the + // 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 // (` + \n + `). - AdditionalCertificateOutputFormatCombinedPEM CertificateOutputFormatType = "CombinedPEM" + CertificateOutputFormatCombinedPEM CertificateOutputFormatType = "CombinedPEM" ) // CertificateAdditionalOutputFormat defines an additional output format of a @@ -251,22 +375,6 @@ type CertificateAdditionalOutputFormat struct { Type CertificateOutputFormatType } -// Denotes how private keys should be generated or sourced when a Certificate -// is being issued. -type PrivateKeyRotationPolicy string - -var ( - // RotationPolicyNever means 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. - RotationPolicyNever PrivateKeyRotationPolicy = "Never" - - // RotationPolicyAlways means a private key matching the specified - // requirements will be generated whenever a re-issuance occurs. - RotationPolicyAlways PrivateKeyRotationPolicy = "Always" -) - // X509Subject Full X509 name specification type X509Subject struct { // Organizations to be used on the Certificate. @@ -299,51 +407,106 @@ type CertificateKeystores struct { PKCS12 *PKCS12Keystore } -// JKS configures options for storing a JKS keystore in the `spec.secretName` -// Secret resource. +// JKS configures options for storing a JKS keystore in the target secret. +// Either PasswordSecretRef or Password must be provided. type JKSKeystore struct { // 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 only be updated upon re-issuance. + // `passwordSecretRef` or `password`. + // 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` or `password` + // containing the issuing Certificate Authority Create bool - // PasswordSecretRef is a reference to a key in a Secret resource + // Alias specifies the alias of the key in the keystore, required by the JKS format. + // If not provided, the default alias `certificate` will be used. + // +optional + Alias *string `json:"alias,omitempty"` + + // PasswordSecretRef is a reference to a non-empty key in a Secret resource // containing the password used to encrypt the JKS keystore. + // Mutually exclusive with password. + // One of password or passwordSecretRef must provide a password with a non-zero length. + // +optional PasswordSecretRef cmmeta.SecretKeySelector + + // Password provides a literal password used to encrypt the JKS keystore. + // Mutually exclusive with passwordSecretRef. + // One of password or passwordSecretRef must provide a password with a non-zero length. + // +optional + Password *string } // PKCS12 configures options for storing a PKCS12 keystore in the // `spec.secretName` Secret resource. +// Either PasswordSecretRef or Password must be provided. type PKCS12Keystore struct { // 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 only be updated upon re-issuance. + // 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 Create bool - // PasswordSecretRef is a reference to a key in a Secret resource - // containing the password used to encrypt the PKCS12 keystore. + // Profile specifies the key and certificate encryption algorithms and the HMAC algorithm + // used to create the PKCS12 keystore. Default value is `LegacyRC2` for backward compatibility. + // + // If provided, allowed values are: + // `LegacyRC2`: Deprecated. Not supported by default in OpenSSL 3 or Java 20. + // `LegacyDES`: Less secure algorithm. Use this option for maximal compatibility. + // `Modern2023`: Secure algorithm. Use this option in case you have to always use secure algorithms + // (e.g., because of company policy). Please note that the security of the algorithm is not that important + // in reality, because the unencrypted certificate and private key are also stored in the Secret. + Profile PKCS12Profile + + // containing the password used to encrypt the PKCS#12 keystore. + // Mutually exclusive with password. + // One of password or passwordSecretRef must provide a password with a non-zero length. + // +optional PasswordSecretRef cmmeta.SecretKeySelector + + // Password provides a literal password used to encrypt the PKCS#12 keystore. + // Mutually exclusive with passwordSecretRef. + // One of password or passwordSecretRef must provide a password with a non-zero length. + // +optional + Password *string } +type PKCS12Profile string + +const ( + // see: https://pkg.go.dev/software.sslmate.com/src/go-pkcs12#LegacyRC2 + LegacyRC2PKCS12Profile PKCS12Profile = "LegacyRC2" + + // see: https://pkg.go.dev/software.sslmate.com/src/go-pkcs12#LegacyDES + LegacyDESPKCS12Profile PKCS12Profile = "LegacyDES" + + // see: https://pkg.go.dev/software.sslmate.com/src/go-pkcs12#Modern2023 + Modern2023PKCS12Profile PKCS12Profile = "Modern2023" +) + // CertificateStatus defines the observed state of Certificate type CertificateStatus struct { // List of status conditions to indicate the status of certificates. // Known condition types are `Ready` and `Issuing`. Conditions []CertificateCondition - // 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 latest 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. LastFailureTime *metav1.Time // The time after which the certificate stored in the secret named - // by this resource in spec.secretName is valid. + // by this resource in `spec.secretName` is valid. NotBefore *metav1.Time // The expiration time of the certificate stored in the secret named @@ -384,10 +547,10 @@ type CertificateStatus struct { // 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). - FailedIssuanceAttempts *int `json:"failedIssuanceAttempts,omitempty"` + FailedIssuanceAttempts *int } -// CertificateCondition contains condition information for an Certificate. +// CertificateCondition contains condition information for a Certificate. type CertificateCondition struct { // Type of the condition, known values are (`Ready`, `Issuing`). Type CertificateConditionType @@ -415,7 +578,7 @@ type CertificateCondition struct { ObservedGeneration int64 } -// CertificateConditionType represents an Certificate condition value. +// CertificateConditionType represents a Certificate condition value. type CertificateConditionType string const ( @@ -437,7 +600,7 @@ const ( // `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. + // 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. @@ -457,3 +620,41 @@ type CertificateSecretTemplate struct { // +optional Labels map[string]string } + +// NameConstraints is a type to represent x509 NameConstraints +type NameConstraints struct { + // if true then the name constraints are marked critical. + // + // +optional + Critical bool + // Permitted contains the constraints in which the names must be located. + // + // +optional + Permitted *NameConstraintItem + // Excluded contains the constraints which must be disallowed. Any name matching a + // restriction in the excluded field is invalid regardless + // of information appearing in the permitted + // + // +optional + Excluded *NameConstraintItem +} + +type NameConstraintItem struct { + // DNSDomains is a list of DNS domains that are permitted or excluded. + // + // +optional + DNSDomains []string + // IPRanges is a list of IP Ranges that are permitted or excluded. + // This should be a valid CIDR notation. + // + // +optional + IPRanges []string + // EmailAddresses is a list of Email Addresses that are permitted or excluded. + // + // +optional + EmailAddresses []string + // URIDomains is a list of URI domains that are permitted or excluded. + // + // +optional + URIDomains []string +} diff --git a/internal/apis/certmanager/types_certificaterequest.go b/internal/apis/certmanager/types_certificaterequest.go index d34762ef986..e5e4b8d073f 100644 --- a/internal/apis/certmanager/types_certificaterequest.go +++ b/internal/apis/certmanager/types_certificaterequest.go @@ -26,13 +26,20 @@ const ( // Pending indicates that a CertificateRequest is still in progress. CertificateRequestReasonPending = "Pending" - // Failed indicates that a CertificateRequest has failed, either due to - // timing out or some other critical failure. + // Failed indicates that a CertificateRequest has failed permanently, + // either due to timing out or some other critical failure. + // The `status.failureTime` field should be set in this case. CertificateRequestReasonFailed = "Failed" // Issued indicates that a CertificateRequest has been completed, and that // the `status.certificate` field is set. CertificateRequestReasonIssued = "Issued" + + // Denied is a Ready condition reason that indicates that a + // CertificateRequest has been denied, and the CertificateRequest will never + // be issued. + // The `status.failureTime` field should be set in this case. + CertificateRequestReasonDenied = "Denied" ) // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object @@ -41,58 +48,91 @@ const ( // 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 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. type CertificateRequest struct { metav1.TypeMeta + // Standard object's metadata. + // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata metav1.ObjectMeta - // Desired state of the CertificateRequest resource. + // Specification of the desired state of the CertificateRequest resource. + // https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status Spec CertificateRequestSpec - // 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 Status CertificateRequestStatus } // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object -// CertificateRequestList is a list of Certificates +// CertificateRequestList is a list of CertificateRequests. type CertificateRequestList struct { - metav1.TypeMeta + metav1.TypeMeta `json:",inline"` + // Standard list metadata. + // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds metav1.ListMeta + // List of CertificateRequests Items []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. type CertificateRequestSpec struct { - // 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. Duration *metav1.Duration - // 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. - IssuerRef cmmeta.ObjectReference - - // The PEM-encoded x509 certificate signing request to be submitted to the - // CA for signing. + // 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. + IssuerRef cmmeta.IssuerReference + + // 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. Request []byte - // 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`. IsCA bool - // 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. + // + // 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`. Usages []KeyUsage // Username contains the name of the user that created the CertificateRequest. @@ -113,17 +153,17 @@ type CertificateRequestSpec struct { // resulting signed certificate. type CertificateRequestStatus struct { // List of status conditions to indicate the status of a CertificateRequest. - // Known condition types are `Ready` and `InvalidRequest`. + // Known condition types are `Ready`, `InvalidRequest`, `Approved` and `Denied`. Conditions []CertificateRequestCondition - // The PEM encoded x509 certificate resulting from the certificate + // 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. Certificate []byte - // The PEM encoded x509 certificate of the signer, also known as the CA + // 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. @@ -136,8 +176,8 @@ type CertificateRequestStatus struct { // CertificateRequestCondition contains condition information for a CertificateRequest. type CertificateRequestCondition struct { - // Type of the condition, known values are (`Ready`, - // `InvalidRequest`, `Approved`, `Denied`). + // Type of the condition, known values are (`Ready`, `InvalidRequest`, + // `Approved`, `Denied`). Type CertificateRequestConditionType // Status of the condition, one of (`True`, `False`, `Unknown`). @@ -156,7 +196,7 @@ type CertificateRequestCondition struct { Message string } -// CertificateRequestConditionType represents an Certificate condition value. +// CertificateRequestConditionType represents a Certificate condition value. type CertificateRequestConditionType string const ( @@ -173,11 +213,13 @@ const ( // 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. + // `False`, and cannot be modified once set. Cannot be set alongside + // `Denied`. CertificateRequestConditionApproved CertificateRequestConditionType = "Approved" // 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. + // `False`, and cannot be modified once set. Cannot be set alongside + // `Approved`. CertificateRequestConditionDenied CertificateRequestConditionType = "Denied" ) diff --git a/internal/apis/certmanager/types_issuer.go b/internal/apis/certmanager/types_issuer.go index 786a52d14fd..2e11e560b50 100644 --- a/internal/apis/certmanager/types_issuer.go +++ b/internal/apis/certmanager/types_issuer.go @@ -132,24 +132,29 @@ type VenafiTPP struct { // for example: "https://tpp.example.com/vedsdk". URL string - // 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'. + // CredentialsRef is a reference to a Secret containing the Venafi TPP API credentials. + // The secret must contain the key 'access-token' for the Access Token Authentication, + // or two keys, 'username' and 'password' for the API Keys Authentication. CredentialsRef cmmeta.LocalObjectReference - // CABundle is a PEM encoded TLS certificate to use to verify connections to - // the TPP instance. - // If specified, system roots will not be used and the issuing CA for the - // TPP instance must be verifiable using the provided root. - // If not specified, the connection will be verified using the cert-manager - // system root certificates. + // 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. CABundle []byte + + // Reference to a Secret containing a 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. Mutually exclusive with CABundle. + // If neither CABundle nor CABundleSecretRef is defined, the certificate bundle in + // the cert-manager controller container is used to validate the TLS connection. + CABundleSecretRef *cmmeta.SecretKeySelector `json:"caBundleSecretRef,omitempty"` } // VenafiCloud defines connection configuration details for Venafi Cloud type VenafiCloud struct { // URL is the base URL for Venafi Cloud. - // Defaults to "https://api.venafi.cloud/v1". + // Defaults to "https://api.venafi.cloud/". URL string // APITokenSecretRef is a secret key selector for the Venafi Cloud API token. @@ -174,6 +179,10 @@ type VaultIssuer struct { // Server is the connection address for the Vault server, e.g: "https://vault.example.com:8200". Server string + // ServerName is used to verify the hostname on the returned certificates + // by the Vault server. + ServerName string + // Path is the mount path of the Vault PKI backend's `sign` endpoint, e.g: // "my_pki_mount/sign/my-role-name". Path string @@ -182,26 +191,37 @@ type VaultIssuer struct { // More about namespaces can be found here https://www.vaultproject.io/docs/enterprise/namespaces Namespace string - // PEM-encoded CA bundle (base64-encoded) used to validate Vault server - // certificate. Only used if the Server URL is using HTTPS protocol. This - // parameter is ignored for plain HTTP protocol connection. If not set the - // system root certificates are used to validate the TLS connection. - // Mutually exclusive with CABundleSecretRef. If neither CABundle nor CABundleSecretRef are defined, - // the cert-manager controller system root certificates are used to validate the TLS connection. + // 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. // +optional CABundle []byte - // CABundleSecretRef is a reference to a Secret which contains the CABundle which will be used when - // connecting to Vault when using HTTPS. - // Mutually exclusive with CABundle. If neither CABundleSecretRef nor CABundle are defined, the cert-manager - // controller system root certificates are used to validate the TLS connection. + // 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'. // +optional CABundleSecretRef *cmmeta.SecretKeySelector + + // Reference to a Secret containing a PEM-encoded Client Certificate to use when the + // Vault server requires mTLS. + // +optional + ClientCertSecretRef *cmmeta.SecretKeySelector + + // Reference to a Secret containing a PEM-encoded Client Private Key to use when the + // Vault server requires mTLS. + // +optional + ClientKeySecretRef *cmmeta.SecretKeySelector } -// VaultAuth is 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`, `clientCertificate` or `kubernetes`]. type VaultAuth struct { // TokenSecretRef authenticates with Vault by presenting a token. TokenSecretRef *cmmeta.SecretKeySelector @@ -210,6 +230,12 @@ type VaultAuth struct { // with the role and secret stored in a Kubernetes Secret resource. AppRole *VaultAppRole + // ClientCertificate authenticates with Vault by presenting a client + // certificate during the request's TLS handshake. + // Works only when using HTTPS protocol. + // +optional + ClientCertificate *VaultClientCertificateAuth + // Kubernetes authenticates with Vault by passing the ServiceAccount // token stored in the named Secret resource to the Vault server. Kubernetes *VaultKubernetesAuth @@ -233,7 +259,29 @@ type VaultAppRole struct { SecretRef cmmeta.SecretKeySelector } -// VaultKubernetesAuth is used to authenticate against Vault using a Kubernetes ServiceAccount token stored in +// VaultKubernetesAuth is used to authenticate against Vault using a client +// certificate stored in a Secret. +type VaultClientCertificateAuth struct { + // 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/cert" will be used. + // +optional + Path string + + // Reference to Kubernetes Secret of type "kubernetes.io/tls" (hence containing + // tls.crt and tls.key) used to authenticate to Vault using TLS client + // authentication. + // +optional + SecretName string + + // Name of the certificate role to authenticate against. + // If not set, matching any certificate role, if available. + // +optional + Name string +} + +// Authenticate against Vault using a Kubernetes ServiceAccount token stored in // a Secret. type VaultKubernetesAuth struct { // The Vault mountPath here is the mount path to use when authenticating with @@ -244,14 +292,39 @@ type VaultKubernetesAuth struct { // The required Secret field containing a Kubernetes ServiceAccount JWT used // for authenticating with Vault. Use of 'ambient credentials' is not - // supported. + // supported. This field should not be set if serviceAccountRef is set. + // +optional SecretRef cmmeta.SecretKeySelector + // Note: we don't use a pointer here for backwards compatibility. + + // 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. + // +optional + ServiceAccountRef *ServiceAccountRef // A required field containing the Vault Role to assume. A Role binds a // Kubernetes ServiceAccount with a set of Vault policies. Role string } +// 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. +type ServiceAccountRef struct { + // Name of the ServiceAccount used to request a token. + Name string + + // TokenAudiences is an optional list of extra audiences to include in the token passed to Vault. The default token + // consisting of the issuer's namespace and name is always included. + // +optional + TokenAudiences []string +} + // CAIssuer configures an issuer that can issue certificates from its provided // CA certificate. It contains the name of the private key to sign certificates, // holds the location for Certificate Revocation Lists (CRL) distribution @@ -273,6 +346,12 @@ type CAIssuer struct { // certificate will be issued with no OCSP servers set. For example, an // OCSP server URL could be "http://ocsp.int-x3.letsencrypt.org". OCSPServers []string + + // IssuingCertificateURLs is a list of URLs which this issuer should embed into certificates + // it creates. See https://www.rfc-editor.org/rfc/rfc5280#section-4.2.2.1 for more details. + // As an example, such a URL might be "http://ca.domain.com/ca.crt". + // +optional + IssuingCertificateURLs []string `json:"issuingCertificateURLs,omitempty"` } // IssuerStatus contains status information about an Issuer diff --git a/internal/apis/certmanager/v1/conversion.go b/internal/apis/certmanager/v1/conversion.go deleted file mode 100644 index f6187543ced..00000000000 --- a/internal/apis/certmanager/v1/conversion.go +++ /dev/null @@ -1,40 +0,0 @@ -/* -Copyright 2020 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. -*/ - -package v1 - -import ( - unsafe "unsafe" - - conversion "k8s.io/apimachinery/pkg/conversion" - - certmanager "github.com/cert-manager/cert-manager/internal/apis/certmanager" - v1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" -) - -// Convert_v1_CertificateSpec_To_certmanager_CertificateSpec -func Convert_v1_CertificateSpec_To_certmanager_CertificateSpec(in *v1.CertificateSpec, out *certmanager.CertificateSpec, s conversion.Scope) error { - out.URISANs = *(*[]string)(unsafe.Pointer(&in.URIs)) - out.EmailSANs = *(*[]string)(unsafe.Pointer(&in.EmailAddresses)) - return autoConvert_v1_CertificateSpec_To_certmanager_CertificateSpec(in, out, s) -} - -// Convert_certmanager_CertificateSpec_To_v1_CertificateSpec -func Convert_certmanager_CertificateSpec_To_v1_CertificateSpec(in *certmanager.CertificateSpec, out *v1.CertificateSpec, s conversion.Scope) error { - out.URIs = *(*[]string)(unsafe.Pointer(&in.URISANs)) - out.EmailAddresses = *(*[]string)(unsafe.Pointer(&in.EmailSANs)) - return autoConvert_certmanager_CertificateSpec_To_v1_CertificateSpec(in, out, s) -} diff --git a/internal/apis/certmanager/v1/defaults.go b/internal/apis/certmanager/v1/defaults.go index 66334146b9a..82b1ba147be 100644 --- a/internal/apis/certmanager/v1/defaults.go +++ b/internal/apis/certmanager/v1/defaults.go @@ -18,8 +18,84 @@ package v1 import ( "k8s.io/apimachinery/pkg/runtime" + + "github.com/cert-manager/cert-manager/internal/controller/feature" + cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" + utilfeature "github.com/cert-manager/cert-manager/pkg/util/feature" ) func addDefaultingFuncs(scheme *runtime.Scheme) error { + scheme.AddTypeDefaultingFunc(&cmapi.Certificate{}, func(obj interface{}) { SetObjectDefaults_Certificate(obj.(*cmapi.Certificate)) }) + scheme.AddTypeDefaultingFunc(&cmapi.CertificateList{}, func(obj interface{}) { SetObjectDefaults_CertificateList(obj.(*cmapi.CertificateList)) }) + scheme.AddTypeDefaultingFunc(&cmapi.CertificateRequest{}, func(obj interface{}) { SetObjectDefaults_CertificateRequest(obj.(*cmapi.CertificateRequest)) }) + scheme.AddTypeDefaultingFunc(&cmapi.CertificateRequestList{}, func(obj interface{}) { + SetObjectDefaults_CertificateRequestList(obj.(*cmapi.CertificateRequestList)) + }) return RegisterDefaults(scheme) } + +// SetRuntimeDefaults_Certificate mutates the supplied Certificate object, +// setting defaults for certain missing fields: +// - Sets the default private key rotation policy to: +// - Always, if the DefaultPrivateKeyRotationPolicyAlways feature is enabled +// - Never, if the DefaultPrivateKeyRotationPolicyAlways feature is disabled. +// +// NOTE: Do not supply Certificate objects retrieved from a client-go lister +// because you may corrupt the cache. Do a DeepCopy first. See: +// https://pkg.go.dev/github.com/cert-manager/cert-manager@v1.17.2/pkg/client/listers/certmanager/v1#CertificateNamespaceLister +// +// NOTE: This is deliberately not called `SetObjectDefault_`, because that would +// cause defaultergen to add this to the scheme default, which would be +// confusing because we don't (yet) have a defaulting webhook or use API default +// annotations. +// +// TODO(wallrj): When DefaultPrivateKeyRotationPolicyAlways is GA, the default +// value can probably be added as an API default by adding: +// +// `// +default="Always"` +// +// ... to the API struct. +func SetRuntimeDefaults_Certificate(in *cmapi.Certificate) { + if in.Spec.PrivateKey == nil { + in.Spec.PrivateKey = &cmapi.CertificatePrivateKey{} + } + if in.Spec.PrivateKey.RotationPolicy == "" { + defaultRotationPolicy := cmapi.RotationPolicyNever + if utilfeature.DefaultFeatureGate.Enabled(feature.DefaultPrivateKeyRotationPolicyAlways) { + defaultRotationPolicy = cmapi.RotationPolicyAlways + } + in.Spec.PrivateKey.RotationPolicy = defaultRotationPolicy + } +} + +func SetObjectDefaults_Certificate(in *cmapi.Certificate) { + if in.Spec.IssuerRef.Kind == "" { + in.Spec.IssuerRef.Kind = "Issuer" + } + if in.Spec.IssuerRef.Group == "" { + in.Spec.IssuerRef.Group = "cert-manager.io" + } +} + +func SetObjectDefaults_CertificateList(in *cmapi.CertificateList) { + for i := range in.Items { + a := &in.Items[i] + SetObjectDefaults_Certificate(a) + } +} + +func SetObjectDefaults_CertificateRequest(in *cmapi.CertificateRequest) { + if in.Spec.IssuerRef.Kind == "" { + in.Spec.IssuerRef.Kind = "Issuer" + } + if in.Spec.IssuerRef.Group == "" { + in.Spec.IssuerRef.Group = "cert-manager.io" + } +} + +func SetObjectDefaults_CertificateRequestList(in *cmapi.CertificateRequestList) { + for i := range in.Items { + a := &in.Items[i] + SetObjectDefaults_CertificateRequest(a) + } +} diff --git a/internal/apis/certmanager/v1/defaults_test.go b/internal/apis/certmanager/v1/defaults_test.go new file mode 100644 index 00000000000..ad36041a7d3 --- /dev/null +++ b/internal/apis/certmanager/v1/defaults_test.go @@ -0,0 +1,60 @@ +/* +Copyright 2020 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. +*/ + +package v1 + +import ( + "testing" + + "github.com/stretchr/testify/assert" + featuregatetesting "k8s.io/component-base/featuregate/testing" + + "github.com/cert-manager/cert-manager/internal/controller/feature" + cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" + utilfeature "github.com/cert-manager/cert-manager/pkg/util/feature" +) + +// Test_SetRuntimeDefaults_Certificate_PrivateKey_RotationPolicy demonstrates that +// the default rotation policy is set by the defaulting function and that the +// old default (`Never`) can be re-instated by disabling the +// DefaultPrivateKeyRotationPolicyAlways feature gate. +func Test_SetRuntimeDefaults_Certificate_PrivateKey_RotationPolicy(t *testing.T) { + t.Run("feature-enabled", func(t *testing.T) { + featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, feature.DefaultPrivateKeyRotationPolicyAlways, true) + in := &cmapi.Certificate{} + SetRuntimeDefaults_Certificate(in) + assert.Equal(t, cmapi.RotationPolicyAlways, in.Spec.PrivateKey.RotationPolicy) + }) + t.Run("feature-disabled", func(t *testing.T) { + featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, feature.DefaultPrivateKeyRotationPolicyAlways, false) + in := &cmapi.Certificate{} + SetRuntimeDefaults_Certificate(in) + assert.Equal(t, cmapi.RotationPolicyNever, in.Spec.PrivateKey.RotationPolicy) + }) + t.Run("explicit-rotation-policy", func(t *testing.T) { + const expectedRotationPolicy = cmapi.PrivateKeyRotationPolicy("neither-always-nor-never") + featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, feature.DefaultPrivateKeyRotationPolicyAlways, false) + in := &cmapi.Certificate{ + Spec: cmapi.CertificateSpec{ + PrivateKey: &cmapi.CertificatePrivateKey{ + RotationPolicy: expectedRotationPolicy, + }, + }, + } + SetRuntimeDefaults_Certificate(in) + assert.Equal(t, expectedRotationPolicy, in.Spec.PrivateKey.RotationPolicy) + }) +} diff --git a/internal/apis/certmanager/v1/zz_generated.conversion.go b/internal/apis/certmanager/v1/zz_generated.conversion.go index 9c0051e8d47..8e7938ea3e6 100644 --- a/internal/apis/certmanager/v1/zz_generated.conversion.go +++ b/internal/apis/certmanager/v1/zz_generated.conversion.go @@ -30,7 +30,7 @@ import ( meta "github.com/cert-manager/cert-manager/internal/apis/meta" internalapismetav1 "github.com/cert-manager/cert-manager/internal/apis/meta/v1" apisacmev1 "github.com/cert-manager/cert-manager/pkg/apis/acme/v1" - v1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" + certmanagerv1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" apismetav1 "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" conversion "k8s.io/apimachinery/pkg/conversion" @@ -44,374 +44,426 @@ func init() { // RegisterConversions adds conversion functions to the given scheme. // Public to allow building arbitrary schemes. func RegisterConversions(s *runtime.Scheme) error { - if err := s.AddGeneratedConversionFunc((*v1.CAIssuer)(nil), (*certmanager.CAIssuer)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1_CAIssuer_To_certmanager_CAIssuer(a.(*v1.CAIssuer), b.(*certmanager.CAIssuer), scope) + if err := s.AddGeneratedConversionFunc((*certmanagerv1.CAIssuer)(nil), (*certmanager.CAIssuer)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1_CAIssuer_To_certmanager_CAIssuer(a.(*certmanagerv1.CAIssuer), b.(*certmanager.CAIssuer), scope) }); err != nil { return err } - if err := s.AddGeneratedConversionFunc((*certmanager.CAIssuer)(nil), (*v1.CAIssuer)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_certmanager_CAIssuer_To_v1_CAIssuer(a.(*certmanager.CAIssuer), b.(*v1.CAIssuer), scope) + if err := s.AddGeneratedConversionFunc((*certmanager.CAIssuer)(nil), (*certmanagerv1.CAIssuer)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_certmanager_CAIssuer_To_v1_CAIssuer(a.(*certmanager.CAIssuer), b.(*certmanagerv1.CAIssuer), scope) }); err != nil { return err } - if err := s.AddGeneratedConversionFunc((*v1.Certificate)(nil), (*certmanager.Certificate)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1_Certificate_To_certmanager_Certificate(a.(*v1.Certificate), b.(*certmanager.Certificate), scope) + if err := s.AddGeneratedConversionFunc((*certmanagerv1.Certificate)(nil), (*certmanager.Certificate)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1_Certificate_To_certmanager_Certificate(a.(*certmanagerv1.Certificate), b.(*certmanager.Certificate), scope) }); err != nil { return err } - if err := s.AddGeneratedConversionFunc((*certmanager.Certificate)(nil), (*v1.Certificate)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_certmanager_Certificate_To_v1_Certificate(a.(*certmanager.Certificate), b.(*v1.Certificate), scope) + if err := s.AddGeneratedConversionFunc((*certmanager.Certificate)(nil), (*certmanagerv1.Certificate)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_certmanager_Certificate_To_v1_Certificate(a.(*certmanager.Certificate), b.(*certmanagerv1.Certificate), scope) }); err != nil { return err } - if err := s.AddGeneratedConversionFunc((*v1.CertificateAdditionalOutputFormat)(nil), (*certmanager.CertificateAdditionalOutputFormat)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1_CertificateAdditionalOutputFormat_To_certmanager_CertificateAdditionalOutputFormat(a.(*v1.CertificateAdditionalOutputFormat), b.(*certmanager.CertificateAdditionalOutputFormat), scope) + if err := s.AddGeneratedConversionFunc((*certmanagerv1.CertificateAdditionalOutputFormat)(nil), (*certmanager.CertificateAdditionalOutputFormat)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1_CertificateAdditionalOutputFormat_To_certmanager_CertificateAdditionalOutputFormat(a.(*certmanagerv1.CertificateAdditionalOutputFormat), b.(*certmanager.CertificateAdditionalOutputFormat), scope) }); err != nil { return err } - if err := s.AddGeneratedConversionFunc((*certmanager.CertificateAdditionalOutputFormat)(nil), (*v1.CertificateAdditionalOutputFormat)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_certmanager_CertificateAdditionalOutputFormat_To_v1_CertificateAdditionalOutputFormat(a.(*certmanager.CertificateAdditionalOutputFormat), b.(*v1.CertificateAdditionalOutputFormat), scope) + if err := s.AddGeneratedConversionFunc((*certmanager.CertificateAdditionalOutputFormat)(nil), (*certmanagerv1.CertificateAdditionalOutputFormat)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_certmanager_CertificateAdditionalOutputFormat_To_v1_CertificateAdditionalOutputFormat(a.(*certmanager.CertificateAdditionalOutputFormat), b.(*certmanagerv1.CertificateAdditionalOutputFormat), scope) }); err != nil { return err } - if err := s.AddGeneratedConversionFunc((*v1.CertificateCondition)(nil), (*certmanager.CertificateCondition)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1_CertificateCondition_To_certmanager_CertificateCondition(a.(*v1.CertificateCondition), b.(*certmanager.CertificateCondition), scope) + if err := s.AddGeneratedConversionFunc((*certmanagerv1.CertificateCondition)(nil), (*certmanager.CertificateCondition)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1_CertificateCondition_To_certmanager_CertificateCondition(a.(*certmanagerv1.CertificateCondition), b.(*certmanager.CertificateCondition), scope) }); err != nil { return err } - if err := s.AddGeneratedConversionFunc((*certmanager.CertificateCondition)(nil), (*v1.CertificateCondition)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_certmanager_CertificateCondition_To_v1_CertificateCondition(a.(*certmanager.CertificateCondition), b.(*v1.CertificateCondition), scope) + if err := s.AddGeneratedConversionFunc((*certmanager.CertificateCondition)(nil), (*certmanagerv1.CertificateCondition)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_certmanager_CertificateCondition_To_v1_CertificateCondition(a.(*certmanager.CertificateCondition), b.(*certmanagerv1.CertificateCondition), scope) }); err != nil { return err } - if err := s.AddGeneratedConversionFunc((*v1.CertificateKeystores)(nil), (*certmanager.CertificateKeystores)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1_CertificateKeystores_To_certmanager_CertificateKeystores(a.(*v1.CertificateKeystores), b.(*certmanager.CertificateKeystores), scope) + if err := s.AddGeneratedConversionFunc((*certmanagerv1.CertificateKeystores)(nil), (*certmanager.CertificateKeystores)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1_CertificateKeystores_To_certmanager_CertificateKeystores(a.(*certmanagerv1.CertificateKeystores), b.(*certmanager.CertificateKeystores), scope) }); err != nil { return err } - if err := s.AddGeneratedConversionFunc((*certmanager.CertificateKeystores)(nil), (*v1.CertificateKeystores)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_certmanager_CertificateKeystores_To_v1_CertificateKeystores(a.(*certmanager.CertificateKeystores), b.(*v1.CertificateKeystores), scope) + if err := s.AddGeneratedConversionFunc((*certmanager.CertificateKeystores)(nil), (*certmanagerv1.CertificateKeystores)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_certmanager_CertificateKeystores_To_v1_CertificateKeystores(a.(*certmanager.CertificateKeystores), b.(*certmanagerv1.CertificateKeystores), scope) }); err != nil { return err } - if err := s.AddGeneratedConversionFunc((*v1.CertificateList)(nil), (*certmanager.CertificateList)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1_CertificateList_To_certmanager_CertificateList(a.(*v1.CertificateList), b.(*certmanager.CertificateList), scope) + if err := s.AddGeneratedConversionFunc((*certmanagerv1.CertificateList)(nil), (*certmanager.CertificateList)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1_CertificateList_To_certmanager_CertificateList(a.(*certmanagerv1.CertificateList), b.(*certmanager.CertificateList), scope) }); err != nil { return err } - if err := s.AddGeneratedConversionFunc((*certmanager.CertificateList)(nil), (*v1.CertificateList)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_certmanager_CertificateList_To_v1_CertificateList(a.(*certmanager.CertificateList), b.(*v1.CertificateList), scope) + if err := s.AddGeneratedConversionFunc((*certmanager.CertificateList)(nil), (*certmanagerv1.CertificateList)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_certmanager_CertificateList_To_v1_CertificateList(a.(*certmanager.CertificateList), b.(*certmanagerv1.CertificateList), scope) }); err != nil { return err } - if err := s.AddGeneratedConversionFunc((*v1.CertificatePrivateKey)(nil), (*certmanager.CertificatePrivateKey)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1_CertificatePrivateKey_To_certmanager_CertificatePrivateKey(a.(*v1.CertificatePrivateKey), b.(*certmanager.CertificatePrivateKey), scope) + if err := s.AddGeneratedConversionFunc((*certmanagerv1.CertificatePrivateKey)(nil), (*certmanager.CertificatePrivateKey)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1_CertificatePrivateKey_To_certmanager_CertificatePrivateKey(a.(*certmanagerv1.CertificatePrivateKey), b.(*certmanager.CertificatePrivateKey), scope) }); err != nil { return err } - if err := s.AddGeneratedConversionFunc((*certmanager.CertificatePrivateKey)(nil), (*v1.CertificatePrivateKey)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_certmanager_CertificatePrivateKey_To_v1_CertificatePrivateKey(a.(*certmanager.CertificatePrivateKey), b.(*v1.CertificatePrivateKey), scope) + if err := s.AddGeneratedConversionFunc((*certmanager.CertificatePrivateKey)(nil), (*certmanagerv1.CertificatePrivateKey)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_certmanager_CertificatePrivateKey_To_v1_CertificatePrivateKey(a.(*certmanager.CertificatePrivateKey), b.(*certmanagerv1.CertificatePrivateKey), scope) }); err != nil { return err } - if err := s.AddGeneratedConversionFunc((*v1.CertificateRequest)(nil), (*certmanager.CertificateRequest)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1_CertificateRequest_To_certmanager_CertificateRequest(a.(*v1.CertificateRequest), b.(*certmanager.CertificateRequest), scope) + if err := s.AddGeneratedConversionFunc((*certmanagerv1.CertificateRequest)(nil), (*certmanager.CertificateRequest)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1_CertificateRequest_To_certmanager_CertificateRequest(a.(*certmanagerv1.CertificateRequest), b.(*certmanager.CertificateRequest), scope) }); err != nil { return err } - if err := s.AddGeneratedConversionFunc((*certmanager.CertificateRequest)(nil), (*v1.CertificateRequest)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_certmanager_CertificateRequest_To_v1_CertificateRequest(a.(*certmanager.CertificateRequest), b.(*v1.CertificateRequest), scope) + if err := s.AddGeneratedConversionFunc((*certmanager.CertificateRequest)(nil), (*certmanagerv1.CertificateRequest)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_certmanager_CertificateRequest_To_v1_CertificateRequest(a.(*certmanager.CertificateRequest), b.(*certmanagerv1.CertificateRequest), scope) }); err != nil { return err } - if err := s.AddGeneratedConversionFunc((*v1.CertificateRequestCondition)(nil), (*certmanager.CertificateRequestCondition)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1_CertificateRequestCondition_To_certmanager_CertificateRequestCondition(a.(*v1.CertificateRequestCondition), b.(*certmanager.CertificateRequestCondition), scope) + if err := s.AddGeneratedConversionFunc((*certmanagerv1.CertificateRequestCondition)(nil), (*certmanager.CertificateRequestCondition)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1_CertificateRequestCondition_To_certmanager_CertificateRequestCondition(a.(*certmanagerv1.CertificateRequestCondition), b.(*certmanager.CertificateRequestCondition), scope) }); err != nil { return err } - if err := s.AddGeneratedConversionFunc((*certmanager.CertificateRequestCondition)(nil), (*v1.CertificateRequestCondition)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_certmanager_CertificateRequestCondition_To_v1_CertificateRequestCondition(a.(*certmanager.CertificateRequestCondition), b.(*v1.CertificateRequestCondition), scope) + if err := s.AddGeneratedConversionFunc((*certmanager.CertificateRequestCondition)(nil), (*certmanagerv1.CertificateRequestCondition)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_certmanager_CertificateRequestCondition_To_v1_CertificateRequestCondition(a.(*certmanager.CertificateRequestCondition), b.(*certmanagerv1.CertificateRequestCondition), scope) }); err != nil { return err } - if err := s.AddGeneratedConversionFunc((*v1.CertificateRequestList)(nil), (*certmanager.CertificateRequestList)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1_CertificateRequestList_To_certmanager_CertificateRequestList(a.(*v1.CertificateRequestList), b.(*certmanager.CertificateRequestList), scope) + if err := s.AddGeneratedConversionFunc((*certmanagerv1.CertificateRequestList)(nil), (*certmanager.CertificateRequestList)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1_CertificateRequestList_To_certmanager_CertificateRequestList(a.(*certmanagerv1.CertificateRequestList), b.(*certmanager.CertificateRequestList), scope) }); err != nil { return err } - if err := s.AddGeneratedConversionFunc((*certmanager.CertificateRequestList)(nil), (*v1.CertificateRequestList)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_certmanager_CertificateRequestList_To_v1_CertificateRequestList(a.(*certmanager.CertificateRequestList), b.(*v1.CertificateRequestList), scope) + if err := s.AddGeneratedConversionFunc((*certmanager.CertificateRequestList)(nil), (*certmanagerv1.CertificateRequestList)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_certmanager_CertificateRequestList_To_v1_CertificateRequestList(a.(*certmanager.CertificateRequestList), b.(*certmanagerv1.CertificateRequestList), scope) }); err != nil { return err } - if err := s.AddGeneratedConversionFunc((*v1.CertificateRequestSpec)(nil), (*certmanager.CertificateRequestSpec)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1_CertificateRequestSpec_To_certmanager_CertificateRequestSpec(a.(*v1.CertificateRequestSpec), b.(*certmanager.CertificateRequestSpec), scope) + if err := s.AddGeneratedConversionFunc((*certmanagerv1.CertificateRequestSpec)(nil), (*certmanager.CertificateRequestSpec)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1_CertificateRequestSpec_To_certmanager_CertificateRequestSpec(a.(*certmanagerv1.CertificateRequestSpec), b.(*certmanager.CertificateRequestSpec), scope) }); err != nil { return err } - if err := s.AddGeneratedConversionFunc((*certmanager.CertificateRequestSpec)(nil), (*v1.CertificateRequestSpec)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_certmanager_CertificateRequestSpec_To_v1_CertificateRequestSpec(a.(*certmanager.CertificateRequestSpec), b.(*v1.CertificateRequestSpec), scope) + if err := s.AddGeneratedConversionFunc((*certmanager.CertificateRequestSpec)(nil), (*certmanagerv1.CertificateRequestSpec)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_certmanager_CertificateRequestSpec_To_v1_CertificateRequestSpec(a.(*certmanager.CertificateRequestSpec), b.(*certmanagerv1.CertificateRequestSpec), scope) }); err != nil { return err } - if err := s.AddGeneratedConversionFunc((*v1.CertificateRequestStatus)(nil), (*certmanager.CertificateRequestStatus)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1_CertificateRequestStatus_To_certmanager_CertificateRequestStatus(a.(*v1.CertificateRequestStatus), b.(*certmanager.CertificateRequestStatus), scope) + if err := s.AddGeneratedConversionFunc((*certmanagerv1.CertificateRequestStatus)(nil), (*certmanager.CertificateRequestStatus)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1_CertificateRequestStatus_To_certmanager_CertificateRequestStatus(a.(*certmanagerv1.CertificateRequestStatus), b.(*certmanager.CertificateRequestStatus), scope) }); err != nil { return err } - if err := s.AddGeneratedConversionFunc((*certmanager.CertificateRequestStatus)(nil), (*v1.CertificateRequestStatus)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_certmanager_CertificateRequestStatus_To_v1_CertificateRequestStatus(a.(*certmanager.CertificateRequestStatus), b.(*v1.CertificateRequestStatus), scope) + if err := s.AddGeneratedConversionFunc((*certmanager.CertificateRequestStatus)(nil), (*certmanagerv1.CertificateRequestStatus)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_certmanager_CertificateRequestStatus_To_v1_CertificateRequestStatus(a.(*certmanager.CertificateRequestStatus), b.(*certmanagerv1.CertificateRequestStatus), scope) }); err != nil { return err } - if err := s.AddGeneratedConversionFunc((*v1.CertificateSecretTemplate)(nil), (*certmanager.CertificateSecretTemplate)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1_CertificateSecretTemplate_To_certmanager_CertificateSecretTemplate(a.(*v1.CertificateSecretTemplate), b.(*certmanager.CertificateSecretTemplate), scope) + if err := s.AddGeneratedConversionFunc((*certmanagerv1.CertificateSecretTemplate)(nil), (*certmanager.CertificateSecretTemplate)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1_CertificateSecretTemplate_To_certmanager_CertificateSecretTemplate(a.(*certmanagerv1.CertificateSecretTemplate), b.(*certmanager.CertificateSecretTemplate), scope) }); err != nil { return err } - if err := s.AddGeneratedConversionFunc((*certmanager.CertificateSecretTemplate)(nil), (*v1.CertificateSecretTemplate)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_certmanager_CertificateSecretTemplate_To_v1_CertificateSecretTemplate(a.(*certmanager.CertificateSecretTemplate), b.(*v1.CertificateSecretTemplate), scope) + if err := s.AddGeneratedConversionFunc((*certmanager.CertificateSecretTemplate)(nil), (*certmanagerv1.CertificateSecretTemplate)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_certmanager_CertificateSecretTemplate_To_v1_CertificateSecretTemplate(a.(*certmanager.CertificateSecretTemplate), b.(*certmanagerv1.CertificateSecretTemplate), scope) }); err != nil { return err } - if err := s.AddGeneratedConversionFunc((*v1.CertificateStatus)(nil), (*certmanager.CertificateStatus)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1_CertificateStatus_To_certmanager_CertificateStatus(a.(*v1.CertificateStatus), b.(*certmanager.CertificateStatus), scope) + if err := s.AddGeneratedConversionFunc((*certmanagerv1.CertificateSpec)(nil), (*certmanager.CertificateSpec)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1_CertificateSpec_To_certmanager_CertificateSpec(a.(*certmanagerv1.CertificateSpec), b.(*certmanager.CertificateSpec), scope) }); err != nil { return err } - if err := s.AddGeneratedConversionFunc((*certmanager.CertificateStatus)(nil), (*v1.CertificateStatus)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_certmanager_CertificateStatus_To_v1_CertificateStatus(a.(*certmanager.CertificateStatus), b.(*v1.CertificateStatus), scope) + if err := s.AddGeneratedConversionFunc((*certmanager.CertificateSpec)(nil), (*certmanagerv1.CertificateSpec)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_certmanager_CertificateSpec_To_v1_CertificateSpec(a.(*certmanager.CertificateSpec), b.(*certmanagerv1.CertificateSpec), scope) }); err != nil { return err } - if err := s.AddGeneratedConversionFunc((*v1.ClusterIssuer)(nil), (*certmanager.ClusterIssuer)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1_ClusterIssuer_To_certmanager_ClusterIssuer(a.(*v1.ClusterIssuer), b.(*certmanager.ClusterIssuer), scope) + if err := s.AddGeneratedConversionFunc((*certmanagerv1.CertificateStatus)(nil), (*certmanager.CertificateStatus)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1_CertificateStatus_To_certmanager_CertificateStatus(a.(*certmanagerv1.CertificateStatus), b.(*certmanager.CertificateStatus), scope) }); err != nil { return err } - if err := s.AddGeneratedConversionFunc((*certmanager.ClusterIssuer)(nil), (*v1.ClusterIssuer)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_certmanager_ClusterIssuer_To_v1_ClusterIssuer(a.(*certmanager.ClusterIssuer), b.(*v1.ClusterIssuer), scope) + if err := s.AddGeneratedConversionFunc((*certmanager.CertificateStatus)(nil), (*certmanagerv1.CertificateStatus)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_certmanager_CertificateStatus_To_v1_CertificateStatus(a.(*certmanager.CertificateStatus), b.(*certmanagerv1.CertificateStatus), scope) }); err != nil { return err } - if err := s.AddGeneratedConversionFunc((*v1.ClusterIssuerList)(nil), (*certmanager.ClusterIssuerList)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1_ClusterIssuerList_To_certmanager_ClusterIssuerList(a.(*v1.ClusterIssuerList), b.(*certmanager.ClusterIssuerList), scope) + if err := s.AddGeneratedConversionFunc((*certmanagerv1.ClusterIssuer)(nil), (*certmanager.ClusterIssuer)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1_ClusterIssuer_To_certmanager_ClusterIssuer(a.(*certmanagerv1.ClusterIssuer), b.(*certmanager.ClusterIssuer), scope) }); err != nil { return err } - if err := s.AddGeneratedConversionFunc((*certmanager.ClusterIssuerList)(nil), (*v1.ClusterIssuerList)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_certmanager_ClusterIssuerList_To_v1_ClusterIssuerList(a.(*certmanager.ClusterIssuerList), b.(*v1.ClusterIssuerList), scope) + if err := s.AddGeneratedConversionFunc((*certmanager.ClusterIssuer)(nil), (*certmanagerv1.ClusterIssuer)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_certmanager_ClusterIssuer_To_v1_ClusterIssuer(a.(*certmanager.ClusterIssuer), b.(*certmanagerv1.ClusterIssuer), scope) }); err != nil { return err } - if err := s.AddGeneratedConversionFunc((*v1.Issuer)(nil), (*certmanager.Issuer)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1_Issuer_To_certmanager_Issuer(a.(*v1.Issuer), b.(*certmanager.Issuer), scope) + if err := s.AddGeneratedConversionFunc((*certmanagerv1.ClusterIssuerList)(nil), (*certmanager.ClusterIssuerList)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1_ClusterIssuerList_To_certmanager_ClusterIssuerList(a.(*certmanagerv1.ClusterIssuerList), b.(*certmanager.ClusterIssuerList), scope) }); err != nil { return err } - if err := s.AddGeneratedConversionFunc((*certmanager.Issuer)(nil), (*v1.Issuer)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_certmanager_Issuer_To_v1_Issuer(a.(*certmanager.Issuer), b.(*v1.Issuer), scope) + if err := s.AddGeneratedConversionFunc((*certmanager.ClusterIssuerList)(nil), (*certmanagerv1.ClusterIssuerList)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_certmanager_ClusterIssuerList_To_v1_ClusterIssuerList(a.(*certmanager.ClusterIssuerList), b.(*certmanagerv1.ClusterIssuerList), scope) }); err != nil { return err } - if err := s.AddGeneratedConversionFunc((*v1.IssuerCondition)(nil), (*certmanager.IssuerCondition)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1_IssuerCondition_To_certmanager_IssuerCondition(a.(*v1.IssuerCondition), b.(*certmanager.IssuerCondition), scope) + if err := s.AddGeneratedConversionFunc((*certmanagerv1.Issuer)(nil), (*certmanager.Issuer)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1_Issuer_To_certmanager_Issuer(a.(*certmanagerv1.Issuer), b.(*certmanager.Issuer), scope) }); err != nil { return err } - if err := s.AddGeneratedConversionFunc((*certmanager.IssuerCondition)(nil), (*v1.IssuerCondition)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_certmanager_IssuerCondition_To_v1_IssuerCondition(a.(*certmanager.IssuerCondition), b.(*v1.IssuerCondition), scope) + if err := s.AddGeneratedConversionFunc((*certmanager.Issuer)(nil), (*certmanagerv1.Issuer)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_certmanager_Issuer_To_v1_Issuer(a.(*certmanager.Issuer), b.(*certmanagerv1.Issuer), scope) }); err != nil { return err } - if err := s.AddGeneratedConversionFunc((*v1.IssuerConfig)(nil), (*certmanager.IssuerConfig)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1_IssuerConfig_To_certmanager_IssuerConfig(a.(*v1.IssuerConfig), b.(*certmanager.IssuerConfig), scope) + if err := s.AddGeneratedConversionFunc((*certmanagerv1.IssuerCondition)(nil), (*certmanager.IssuerCondition)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1_IssuerCondition_To_certmanager_IssuerCondition(a.(*certmanagerv1.IssuerCondition), b.(*certmanager.IssuerCondition), scope) }); err != nil { return err } - if err := s.AddGeneratedConversionFunc((*certmanager.IssuerConfig)(nil), (*v1.IssuerConfig)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_certmanager_IssuerConfig_To_v1_IssuerConfig(a.(*certmanager.IssuerConfig), b.(*v1.IssuerConfig), scope) + if err := s.AddGeneratedConversionFunc((*certmanager.IssuerCondition)(nil), (*certmanagerv1.IssuerCondition)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_certmanager_IssuerCondition_To_v1_IssuerCondition(a.(*certmanager.IssuerCondition), b.(*certmanagerv1.IssuerCondition), scope) }); err != nil { return err } - if err := s.AddGeneratedConversionFunc((*v1.IssuerList)(nil), (*certmanager.IssuerList)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1_IssuerList_To_certmanager_IssuerList(a.(*v1.IssuerList), b.(*certmanager.IssuerList), scope) + if err := s.AddGeneratedConversionFunc((*certmanagerv1.IssuerConfig)(nil), (*certmanager.IssuerConfig)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1_IssuerConfig_To_certmanager_IssuerConfig(a.(*certmanagerv1.IssuerConfig), b.(*certmanager.IssuerConfig), scope) }); err != nil { return err } - if err := s.AddGeneratedConversionFunc((*certmanager.IssuerList)(nil), (*v1.IssuerList)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_certmanager_IssuerList_To_v1_IssuerList(a.(*certmanager.IssuerList), b.(*v1.IssuerList), scope) + if err := s.AddGeneratedConversionFunc((*certmanager.IssuerConfig)(nil), (*certmanagerv1.IssuerConfig)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_certmanager_IssuerConfig_To_v1_IssuerConfig(a.(*certmanager.IssuerConfig), b.(*certmanagerv1.IssuerConfig), scope) }); err != nil { return err } - if err := s.AddGeneratedConversionFunc((*v1.IssuerSpec)(nil), (*certmanager.IssuerSpec)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1_IssuerSpec_To_certmanager_IssuerSpec(a.(*v1.IssuerSpec), b.(*certmanager.IssuerSpec), scope) + if err := s.AddGeneratedConversionFunc((*certmanagerv1.IssuerList)(nil), (*certmanager.IssuerList)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1_IssuerList_To_certmanager_IssuerList(a.(*certmanagerv1.IssuerList), b.(*certmanager.IssuerList), scope) }); err != nil { return err } - if err := s.AddGeneratedConversionFunc((*certmanager.IssuerSpec)(nil), (*v1.IssuerSpec)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_certmanager_IssuerSpec_To_v1_IssuerSpec(a.(*certmanager.IssuerSpec), b.(*v1.IssuerSpec), scope) + if err := s.AddGeneratedConversionFunc((*certmanager.IssuerList)(nil), (*certmanagerv1.IssuerList)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_certmanager_IssuerList_To_v1_IssuerList(a.(*certmanager.IssuerList), b.(*certmanagerv1.IssuerList), scope) }); err != nil { return err } - if err := s.AddGeneratedConversionFunc((*v1.IssuerStatus)(nil), (*certmanager.IssuerStatus)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1_IssuerStatus_To_certmanager_IssuerStatus(a.(*v1.IssuerStatus), b.(*certmanager.IssuerStatus), scope) + if err := s.AddGeneratedConversionFunc((*certmanagerv1.IssuerSpec)(nil), (*certmanager.IssuerSpec)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1_IssuerSpec_To_certmanager_IssuerSpec(a.(*certmanagerv1.IssuerSpec), b.(*certmanager.IssuerSpec), scope) }); err != nil { return err } - if err := s.AddGeneratedConversionFunc((*certmanager.IssuerStatus)(nil), (*v1.IssuerStatus)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_certmanager_IssuerStatus_To_v1_IssuerStatus(a.(*certmanager.IssuerStatus), b.(*v1.IssuerStatus), scope) + if err := s.AddGeneratedConversionFunc((*certmanager.IssuerSpec)(nil), (*certmanagerv1.IssuerSpec)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_certmanager_IssuerSpec_To_v1_IssuerSpec(a.(*certmanager.IssuerSpec), b.(*certmanagerv1.IssuerSpec), scope) }); err != nil { return err } - if err := s.AddGeneratedConversionFunc((*v1.JKSKeystore)(nil), (*certmanager.JKSKeystore)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1_JKSKeystore_To_certmanager_JKSKeystore(a.(*v1.JKSKeystore), b.(*certmanager.JKSKeystore), scope) + if err := s.AddGeneratedConversionFunc((*certmanagerv1.IssuerStatus)(nil), (*certmanager.IssuerStatus)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1_IssuerStatus_To_certmanager_IssuerStatus(a.(*certmanagerv1.IssuerStatus), b.(*certmanager.IssuerStatus), scope) }); err != nil { return err } - if err := s.AddGeneratedConversionFunc((*certmanager.JKSKeystore)(nil), (*v1.JKSKeystore)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_certmanager_JKSKeystore_To_v1_JKSKeystore(a.(*certmanager.JKSKeystore), b.(*v1.JKSKeystore), scope) + if err := s.AddGeneratedConversionFunc((*certmanager.IssuerStatus)(nil), (*certmanagerv1.IssuerStatus)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_certmanager_IssuerStatus_To_v1_IssuerStatus(a.(*certmanager.IssuerStatus), b.(*certmanagerv1.IssuerStatus), scope) }); err != nil { return err } - if err := s.AddGeneratedConversionFunc((*v1.PKCS12Keystore)(nil), (*certmanager.PKCS12Keystore)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1_PKCS12Keystore_To_certmanager_PKCS12Keystore(a.(*v1.PKCS12Keystore), b.(*certmanager.PKCS12Keystore), scope) + if err := s.AddGeneratedConversionFunc((*certmanagerv1.JKSKeystore)(nil), (*certmanager.JKSKeystore)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1_JKSKeystore_To_certmanager_JKSKeystore(a.(*certmanagerv1.JKSKeystore), b.(*certmanager.JKSKeystore), scope) }); err != nil { return err } - if err := s.AddGeneratedConversionFunc((*certmanager.PKCS12Keystore)(nil), (*v1.PKCS12Keystore)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_certmanager_PKCS12Keystore_To_v1_PKCS12Keystore(a.(*certmanager.PKCS12Keystore), b.(*v1.PKCS12Keystore), scope) + if err := s.AddGeneratedConversionFunc((*certmanager.JKSKeystore)(nil), (*certmanagerv1.JKSKeystore)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_certmanager_JKSKeystore_To_v1_JKSKeystore(a.(*certmanager.JKSKeystore), b.(*certmanagerv1.JKSKeystore), scope) }); err != nil { return err } - if err := s.AddGeneratedConversionFunc((*v1.SelfSignedIssuer)(nil), (*certmanager.SelfSignedIssuer)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1_SelfSignedIssuer_To_certmanager_SelfSignedIssuer(a.(*v1.SelfSignedIssuer), b.(*certmanager.SelfSignedIssuer), scope) + if err := s.AddGeneratedConversionFunc((*certmanagerv1.NameConstraintItem)(nil), (*certmanager.NameConstraintItem)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1_NameConstraintItem_To_certmanager_NameConstraintItem(a.(*certmanagerv1.NameConstraintItem), b.(*certmanager.NameConstraintItem), scope) }); err != nil { return err } - if err := s.AddGeneratedConversionFunc((*certmanager.SelfSignedIssuer)(nil), (*v1.SelfSignedIssuer)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_certmanager_SelfSignedIssuer_To_v1_SelfSignedIssuer(a.(*certmanager.SelfSignedIssuer), b.(*v1.SelfSignedIssuer), scope) + if err := s.AddGeneratedConversionFunc((*certmanager.NameConstraintItem)(nil), (*certmanagerv1.NameConstraintItem)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_certmanager_NameConstraintItem_To_v1_NameConstraintItem(a.(*certmanager.NameConstraintItem), b.(*certmanagerv1.NameConstraintItem), scope) }); err != nil { return err } - if err := s.AddGeneratedConversionFunc((*v1.VaultAppRole)(nil), (*certmanager.VaultAppRole)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1_VaultAppRole_To_certmanager_VaultAppRole(a.(*v1.VaultAppRole), b.(*certmanager.VaultAppRole), scope) + if err := s.AddGeneratedConversionFunc((*certmanagerv1.NameConstraints)(nil), (*certmanager.NameConstraints)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1_NameConstraints_To_certmanager_NameConstraints(a.(*certmanagerv1.NameConstraints), b.(*certmanager.NameConstraints), scope) }); err != nil { return err } - if err := s.AddGeneratedConversionFunc((*certmanager.VaultAppRole)(nil), (*v1.VaultAppRole)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_certmanager_VaultAppRole_To_v1_VaultAppRole(a.(*certmanager.VaultAppRole), b.(*v1.VaultAppRole), scope) + if err := s.AddGeneratedConversionFunc((*certmanager.NameConstraints)(nil), (*certmanagerv1.NameConstraints)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_certmanager_NameConstraints_To_v1_NameConstraints(a.(*certmanager.NameConstraints), b.(*certmanagerv1.NameConstraints), scope) }); err != nil { return err } - if err := s.AddGeneratedConversionFunc((*v1.VaultAuth)(nil), (*certmanager.VaultAuth)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1_VaultAuth_To_certmanager_VaultAuth(a.(*v1.VaultAuth), b.(*certmanager.VaultAuth), scope) + if err := s.AddGeneratedConversionFunc((*certmanagerv1.OtherName)(nil), (*certmanager.OtherName)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1_OtherName_To_certmanager_OtherName(a.(*certmanagerv1.OtherName), b.(*certmanager.OtherName), scope) }); err != nil { return err } - if err := s.AddGeneratedConversionFunc((*certmanager.VaultAuth)(nil), (*v1.VaultAuth)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_certmanager_VaultAuth_To_v1_VaultAuth(a.(*certmanager.VaultAuth), b.(*v1.VaultAuth), scope) + if err := s.AddGeneratedConversionFunc((*certmanager.OtherName)(nil), (*certmanagerv1.OtherName)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_certmanager_OtherName_To_v1_OtherName(a.(*certmanager.OtherName), b.(*certmanagerv1.OtherName), scope) }); err != nil { return err } - if err := s.AddGeneratedConversionFunc((*v1.VaultIssuer)(nil), (*certmanager.VaultIssuer)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1_VaultIssuer_To_certmanager_VaultIssuer(a.(*v1.VaultIssuer), b.(*certmanager.VaultIssuer), scope) + if err := s.AddGeneratedConversionFunc((*certmanagerv1.PKCS12Keystore)(nil), (*certmanager.PKCS12Keystore)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1_PKCS12Keystore_To_certmanager_PKCS12Keystore(a.(*certmanagerv1.PKCS12Keystore), b.(*certmanager.PKCS12Keystore), scope) }); err != nil { return err } - if err := s.AddGeneratedConversionFunc((*certmanager.VaultIssuer)(nil), (*v1.VaultIssuer)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_certmanager_VaultIssuer_To_v1_VaultIssuer(a.(*certmanager.VaultIssuer), b.(*v1.VaultIssuer), scope) + if err := s.AddGeneratedConversionFunc((*certmanager.PKCS12Keystore)(nil), (*certmanagerv1.PKCS12Keystore)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_certmanager_PKCS12Keystore_To_v1_PKCS12Keystore(a.(*certmanager.PKCS12Keystore), b.(*certmanagerv1.PKCS12Keystore), scope) }); err != nil { return err } - if err := s.AddGeneratedConversionFunc((*v1.VaultKubernetesAuth)(nil), (*certmanager.VaultKubernetesAuth)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1_VaultKubernetesAuth_To_certmanager_VaultKubernetesAuth(a.(*v1.VaultKubernetesAuth), b.(*certmanager.VaultKubernetesAuth), scope) + if err := s.AddGeneratedConversionFunc((*certmanagerv1.SelfSignedIssuer)(nil), (*certmanager.SelfSignedIssuer)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1_SelfSignedIssuer_To_certmanager_SelfSignedIssuer(a.(*certmanagerv1.SelfSignedIssuer), b.(*certmanager.SelfSignedIssuer), scope) }); err != nil { return err } - if err := s.AddGeneratedConversionFunc((*certmanager.VaultKubernetesAuth)(nil), (*v1.VaultKubernetesAuth)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_certmanager_VaultKubernetesAuth_To_v1_VaultKubernetesAuth(a.(*certmanager.VaultKubernetesAuth), b.(*v1.VaultKubernetesAuth), scope) + if err := s.AddGeneratedConversionFunc((*certmanager.SelfSignedIssuer)(nil), (*certmanagerv1.SelfSignedIssuer)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_certmanager_SelfSignedIssuer_To_v1_SelfSignedIssuer(a.(*certmanager.SelfSignedIssuer), b.(*certmanagerv1.SelfSignedIssuer), scope) }); err != nil { return err } - if err := s.AddGeneratedConversionFunc((*v1.VenafiCloud)(nil), (*certmanager.VenafiCloud)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1_VenafiCloud_To_certmanager_VenafiCloud(a.(*v1.VenafiCloud), b.(*certmanager.VenafiCloud), scope) + if err := s.AddGeneratedConversionFunc((*certmanagerv1.ServiceAccountRef)(nil), (*certmanager.ServiceAccountRef)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1_ServiceAccountRef_To_certmanager_ServiceAccountRef(a.(*certmanagerv1.ServiceAccountRef), b.(*certmanager.ServiceAccountRef), scope) }); err != nil { return err } - if err := s.AddGeneratedConversionFunc((*certmanager.VenafiCloud)(nil), (*v1.VenafiCloud)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_certmanager_VenafiCloud_To_v1_VenafiCloud(a.(*certmanager.VenafiCloud), b.(*v1.VenafiCloud), scope) + if err := s.AddGeneratedConversionFunc((*certmanager.ServiceAccountRef)(nil), (*certmanagerv1.ServiceAccountRef)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_certmanager_ServiceAccountRef_To_v1_ServiceAccountRef(a.(*certmanager.ServiceAccountRef), b.(*certmanagerv1.ServiceAccountRef), scope) }); err != nil { return err } - if err := s.AddGeneratedConversionFunc((*v1.VenafiIssuer)(nil), (*certmanager.VenafiIssuer)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1_VenafiIssuer_To_certmanager_VenafiIssuer(a.(*v1.VenafiIssuer), b.(*certmanager.VenafiIssuer), scope) + if err := s.AddGeneratedConversionFunc((*certmanagerv1.VaultAppRole)(nil), (*certmanager.VaultAppRole)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1_VaultAppRole_To_certmanager_VaultAppRole(a.(*certmanagerv1.VaultAppRole), b.(*certmanager.VaultAppRole), scope) }); err != nil { return err } - if err := s.AddGeneratedConversionFunc((*certmanager.VenafiIssuer)(nil), (*v1.VenafiIssuer)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_certmanager_VenafiIssuer_To_v1_VenafiIssuer(a.(*certmanager.VenafiIssuer), b.(*v1.VenafiIssuer), scope) + if err := s.AddGeneratedConversionFunc((*certmanager.VaultAppRole)(nil), (*certmanagerv1.VaultAppRole)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_certmanager_VaultAppRole_To_v1_VaultAppRole(a.(*certmanager.VaultAppRole), b.(*certmanagerv1.VaultAppRole), scope) }); err != nil { return err } - if err := s.AddGeneratedConversionFunc((*v1.VenafiTPP)(nil), (*certmanager.VenafiTPP)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1_VenafiTPP_To_certmanager_VenafiTPP(a.(*v1.VenafiTPP), b.(*certmanager.VenafiTPP), scope) + if err := s.AddGeneratedConversionFunc((*certmanagerv1.VaultAuth)(nil), (*certmanager.VaultAuth)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1_VaultAuth_To_certmanager_VaultAuth(a.(*certmanagerv1.VaultAuth), b.(*certmanager.VaultAuth), scope) }); err != nil { return err } - if err := s.AddGeneratedConversionFunc((*certmanager.VenafiTPP)(nil), (*v1.VenafiTPP)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_certmanager_VenafiTPP_To_v1_VenafiTPP(a.(*certmanager.VenafiTPP), b.(*v1.VenafiTPP), scope) + if err := s.AddGeneratedConversionFunc((*certmanager.VaultAuth)(nil), (*certmanagerv1.VaultAuth)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_certmanager_VaultAuth_To_v1_VaultAuth(a.(*certmanager.VaultAuth), b.(*certmanagerv1.VaultAuth), scope) }); err != nil { return err } - if err := s.AddGeneratedConversionFunc((*v1.X509Subject)(nil), (*certmanager.X509Subject)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1_X509Subject_To_certmanager_X509Subject(a.(*v1.X509Subject), b.(*certmanager.X509Subject), scope) + if err := s.AddGeneratedConversionFunc((*certmanagerv1.VaultClientCertificateAuth)(nil), (*certmanager.VaultClientCertificateAuth)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1_VaultClientCertificateAuth_To_certmanager_VaultClientCertificateAuth(a.(*certmanagerv1.VaultClientCertificateAuth), b.(*certmanager.VaultClientCertificateAuth), scope) }); err != nil { return err } - if err := s.AddGeneratedConversionFunc((*certmanager.X509Subject)(nil), (*v1.X509Subject)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_certmanager_X509Subject_To_v1_X509Subject(a.(*certmanager.X509Subject), b.(*v1.X509Subject), scope) + if err := s.AddGeneratedConversionFunc((*certmanager.VaultClientCertificateAuth)(nil), (*certmanagerv1.VaultClientCertificateAuth)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_certmanager_VaultClientCertificateAuth_To_v1_VaultClientCertificateAuth(a.(*certmanager.VaultClientCertificateAuth), b.(*certmanagerv1.VaultClientCertificateAuth), scope) }); err != nil { return err } - if err := s.AddConversionFunc((*certmanager.CertificateSpec)(nil), (*v1.CertificateSpec)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_certmanager_CertificateSpec_To_v1_CertificateSpec(a.(*certmanager.CertificateSpec), b.(*v1.CertificateSpec), scope) + if err := s.AddGeneratedConversionFunc((*certmanagerv1.VaultIssuer)(nil), (*certmanager.VaultIssuer)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1_VaultIssuer_To_certmanager_VaultIssuer(a.(*certmanagerv1.VaultIssuer), b.(*certmanager.VaultIssuer), scope) }); err != nil { return err } - if err := s.AddConversionFunc((*v1.CertificateSpec)(nil), (*certmanager.CertificateSpec)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1_CertificateSpec_To_certmanager_CertificateSpec(a.(*v1.CertificateSpec), b.(*certmanager.CertificateSpec), scope) + if err := s.AddGeneratedConversionFunc((*certmanager.VaultIssuer)(nil), (*certmanagerv1.VaultIssuer)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_certmanager_VaultIssuer_To_v1_VaultIssuer(a.(*certmanager.VaultIssuer), b.(*certmanagerv1.VaultIssuer), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*certmanagerv1.VaultKubernetesAuth)(nil), (*certmanager.VaultKubernetesAuth)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1_VaultKubernetesAuth_To_certmanager_VaultKubernetesAuth(a.(*certmanagerv1.VaultKubernetesAuth), b.(*certmanager.VaultKubernetesAuth), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*certmanager.VaultKubernetesAuth)(nil), (*certmanagerv1.VaultKubernetesAuth)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_certmanager_VaultKubernetesAuth_To_v1_VaultKubernetesAuth(a.(*certmanager.VaultKubernetesAuth), b.(*certmanagerv1.VaultKubernetesAuth), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*certmanagerv1.VenafiCloud)(nil), (*certmanager.VenafiCloud)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1_VenafiCloud_To_certmanager_VenafiCloud(a.(*certmanagerv1.VenafiCloud), b.(*certmanager.VenafiCloud), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*certmanager.VenafiCloud)(nil), (*certmanagerv1.VenafiCloud)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_certmanager_VenafiCloud_To_v1_VenafiCloud(a.(*certmanager.VenafiCloud), b.(*certmanagerv1.VenafiCloud), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*certmanagerv1.VenafiIssuer)(nil), (*certmanager.VenafiIssuer)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1_VenafiIssuer_To_certmanager_VenafiIssuer(a.(*certmanagerv1.VenafiIssuer), b.(*certmanager.VenafiIssuer), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*certmanager.VenafiIssuer)(nil), (*certmanagerv1.VenafiIssuer)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_certmanager_VenafiIssuer_To_v1_VenafiIssuer(a.(*certmanager.VenafiIssuer), b.(*certmanagerv1.VenafiIssuer), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*certmanagerv1.VenafiTPP)(nil), (*certmanager.VenafiTPP)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1_VenafiTPP_To_certmanager_VenafiTPP(a.(*certmanagerv1.VenafiTPP), b.(*certmanager.VenafiTPP), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*certmanager.VenafiTPP)(nil), (*certmanagerv1.VenafiTPP)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_certmanager_VenafiTPP_To_v1_VenafiTPP(a.(*certmanager.VenafiTPP), b.(*certmanagerv1.VenafiTPP), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*certmanagerv1.X509Subject)(nil), (*certmanager.X509Subject)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1_X509Subject_To_certmanager_X509Subject(a.(*certmanagerv1.X509Subject), b.(*certmanager.X509Subject), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*certmanager.X509Subject)(nil), (*certmanagerv1.X509Subject)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_certmanager_X509Subject_To_v1_X509Subject(a.(*certmanager.X509Subject), b.(*certmanagerv1.X509Subject), scope) }); err != nil { return err } return nil } -func autoConvert_v1_CAIssuer_To_certmanager_CAIssuer(in *v1.CAIssuer, out *certmanager.CAIssuer, s conversion.Scope) error { +func autoConvert_v1_CAIssuer_To_certmanager_CAIssuer(in *certmanagerv1.CAIssuer, out *certmanager.CAIssuer, s conversion.Scope) error { out.SecretName = in.SecretName out.CRLDistributionPoints = *(*[]string)(unsafe.Pointer(&in.CRLDistributionPoints)) out.OCSPServers = *(*[]string)(unsafe.Pointer(&in.OCSPServers)) + out.IssuingCertificateURLs = *(*[]string)(unsafe.Pointer(&in.IssuingCertificateURLs)) return nil } // Convert_v1_CAIssuer_To_certmanager_CAIssuer is an autogenerated conversion function. -func Convert_v1_CAIssuer_To_certmanager_CAIssuer(in *v1.CAIssuer, out *certmanager.CAIssuer, s conversion.Scope) error { +func Convert_v1_CAIssuer_To_certmanager_CAIssuer(in *certmanagerv1.CAIssuer, out *certmanager.CAIssuer, s conversion.Scope) error { return autoConvert_v1_CAIssuer_To_certmanager_CAIssuer(in, out, s) } -func autoConvert_certmanager_CAIssuer_To_v1_CAIssuer(in *certmanager.CAIssuer, out *v1.CAIssuer, s conversion.Scope) error { +func autoConvert_certmanager_CAIssuer_To_v1_CAIssuer(in *certmanager.CAIssuer, out *certmanagerv1.CAIssuer, s conversion.Scope) error { out.SecretName = in.SecretName out.CRLDistributionPoints = *(*[]string)(unsafe.Pointer(&in.CRLDistributionPoints)) out.OCSPServers = *(*[]string)(unsafe.Pointer(&in.OCSPServers)) + out.IssuingCertificateURLs = *(*[]string)(unsafe.Pointer(&in.IssuingCertificateURLs)) return nil } // Convert_certmanager_CAIssuer_To_v1_CAIssuer is an autogenerated conversion function. -func Convert_certmanager_CAIssuer_To_v1_CAIssuer(in *certmanager.CAIssuer, out *v1.CAIssuer, s conversion.Scope) error { +func Convert_certmanager_CAIssuer_To_v1_CAIssuer(in *certmanager.CAIssuer, out *certmanagerv1.CAIssuer, s conversion.Scope) error { return autoConvert_certmanager_CAIssuer_To_v1_CAIssuer(in, out, s) } -func autoConvert_v1_Certificate_To_certmanager_Certificate(in *v1.Certificate, out *certmanager.Certificate, s conversion.Scope) error { +func autoConvert_v1_Certificate_To_certmanager_Certificate(in *certmanagerv1.Certificate, out *certmanager.Certificate, s conversion.Scope) error { out.ObjectMeta = in.ObjectMeta if err := Convert_v1_CertificateSpec_To_certmanager_CertificateSpec(&in.Spec, &out.Spec, s); err != nil { return err @@ -423,11 +475,11 @@ func autoConvert_v1_Certificate_To_certmanager_Certificate(in *v1.Certificate, o } // Convert_v1_Certificate_To_certmanager_Certificate is an autogenerated conversion function. -func Convert_v1_Certificate_To_certmanager_Certificate(in *v1.Certificate, out *certmanager.Certificate, s conversion.Scope) error { +func Convert_v1_Certificate_To_certmanager_Certificate(in *certmanagerv1.Certificate, out *certmanager.Certificate, s conversion.Scope) error { return autoConvert_v1_Certificate_To_certmanager_Certificate(in, out, s) } -func autoConvert_certmanager_Certificate_To_v1_Certificate(in *certmanager.Certificate, out *v1.Certificate, s conversion.Scope) error { +func autoConvert_certmanager_Certificate_To_v1_Certificate(in *certmanager.Certificate, out *certmanagerv1.Certificate, s conversion.Scope) error { out.ObjectMeta = in.ObjectMeta if err := Convert_certmanager_CertificateSpec_To_v1_CertificateSpec(&in.Spec, &out.Spec, s); err != nil { return err @@ -439,31 +491,31 @@ func autoConvert_certmanager_Certificate_To_v1_Certificate(in *certmanager.Certi } // Convert_certmanager_Certificate_To_v1_Certificate is an autogenerated conversion function. -func Convert_certmanager_Certificate_To_v1_Certificate(in *certmanager.Certificate, out *v1.Certificate, s conversion.Scope) error { +func Convert_certmanager_Certificate_To_v1_Certificate(in *certmanager.Certificate, out *certmanagerv1.Certificate, s conversion.Scope) error { return autoConvert_certmanager_Certificate_To_v1_Certificate(in, out, s) } -func autoConvert_v1_CertificateAdditionalOutputFormat_To_certmanager_CertificateAdditionalOutputFormat(in *v1.CertificateAdditionalOutputFormat, out *certmanager.CertificateAdditionalOutputFormat, s conversion.Scope) error { +func autoConvert_v1_CertificateAdditionalOutputFormat_To_certmanager_CertificateAdditionalOutputFormat(in *certmanagerv1.CertificateAdditionalOutputFormat, out *certmanager.CertificateAdditionalOutputFormat, s conversion.Scope) error { out.Type = certmanager.CertificateOutputFormatType(in.Type) return nil } // Convert_v1_CertificateAdditionalOutputFormat_To_certmanager_CertificateAdditionalOutputFormat is an autogenerated conversion function. -func Convert_v1_CertificateAdditionalOutputFormat_To_certmanager_CertificateAdditionalOutputFormat(in *v1.CertificateAdditionalOutputFormat, out *certmanager.CertificateAdditionalOutputFormat, s conversion.Scope) error { +func Convert_v1_CertificateAdditionalOutputFormat_To_certmanager_CertificateAdditionalOutputFormat(in *certmanagerv1.CertificateAdditionalOutputFormat, out *certmanager.CertificateAdditionalOutputFormat, s conversion.Scope) error { return autoConvert_v1_CertificateAdditionalOutputFormat_To_certmanager_CertificateAdditionalOutputFormat(in, out, s) } -func autoConvert_certmanager_CertificateAdditionalOutputFormat_To_v1_CertificateAdditionalOutputFormat(in *certmanager.CertificateAdditionalOutputFormat, out *v1.CertificateAdditionalOutputFormat, s conversion.Scope) error { - out.Type = v1.CertificateOutputFormatType(in.Type) +func autoConvert_certmanager_CertificateAdditionalOutputFormat_To_v1_CertificateAdditionalOutputFormat(in *certmanager.CertificateAdditionalOutputFormat, out *certmanagerv1.CertificateAdditionalOutputFormat, s conversion.Scope) error { + out.Type = certmanagerv1.CertificateOutputFormatType(in.Type) return nil } // Convert_certmanager_CertificateAdditionalOutputFormat_To_v1_CertificateAdditionalOutputFormat is an autogenerated conversion function. -func Convert_certmanager_CertificateAdditionalOutputFormat_To_v1_CertificateAdditionalOutputFormat(in *certmanager.CertificateAdditionalOutputFormat, out *v1.CertificateAdditionalOutputFormat, s conversion.Scope) error { +func Convert_certmanager_CertificateAdditionalOutputFormat_To_v1_CertificateAdditionalOutputFormat(in *certmanager.CertificateAdditionalOutputFormat, out *certmanagerv1.CertificateAdditionalOutputFormat, s conversion.Scope) error { return autoConvert_certmanager_CertificateAdditionalOutputFormat_To_v1_CertificateAdditionalOutputFormat(in, out, s) } -func autoConvert_v1_CertificateCondition_To_certmanager_CertificateCondition(in *v1.CertificateCondition, out *certmanager.CertificateCondition, s conversion.Scope) error { +func autoConvert_v1_CertificateCondition_To_certmanager_CertificateCondition(in *certmanagerv1.CertificateCondition, out *certmanager.CertificateCondition, s conversion.Scope) error { out.Type = certmanager.CertificateConditionType(in.Type) out.Status = meta.ConditionStatus(in.Status) out.LastTransitionTime = (*metav1.Time)(unsafe.Pointer(in.LastTransitionTime)) @@ -474,12 +526,12 @@ func autoConvert_v1_CertificateCondition_To_certmanager_CertificateCondition(in } // Convert_v1_CertificateCondition_To_certmanager_CertificateCondition is an autogenerated conversion function. -func Convert_v1_CertificateCondition_To_certmanager_CertificateCondition(in *v1.CertificateCondition, out *certmanager.CertificateCondition, s conversion.Scope) error { +func Convert_v1_CertificateCondition_To_certmanager_CertificateCondition(in *certmanagerv1.CertificateCondition, out *certmanager.CertificateCondition, s conversion.Scope) error { return autoConvert_v1_CertificateCondition_To_certmanager_CertificateCondition(in, out, s) } -func autoConvert_certmanager_CertificateCondition_To_v1_CertificateCondition(in *certmanager.CertificateCondition, out *v1.CertificateCondition, s conversion.Scope) error { - out.Type = v1.CertificateConditionType(in.Type) +func autoConvert_certmanager_CertificateCondition_To_v1_CertificateCondition(in *certmanager.CertificateCondition, out *certmanagerv1.CertificateCondition, s conversion.Scope) error { + out.Type = certmanagerv1.CertificateConditionType(in.Type) out.Status = apismetav1.ConditionStatus(in.Status) out.LastTransitionTime = (*metav1.Time)(unsafe.Pointer(in.LastTransitionTime)) out.Reason = in.Reason @@ -489,11 +541,11 @@ func autoConvert_certmanager_CertificateCondition_To_v1_CertificateCondition(in } // Convert_certmanager_CertificateCondition_To_v1_CertificateCondition is an autogenerated conversion function. -func Convert_certmanager_CertificateCondition_To_v1_CertificateCondition(in *certmanager.CertificateCondition, out *v1.CertificateCondition, s conversion.Scope) error { +func Convert_certmanager_CertificateCondition_To_v1_CertificateCondition(in *certmanager.CertificateCondition, out *certmanagerv1.CertificateCondition, s conversion.Scope) error { return autoConvert_certmanager_CertificateCondition_To_v1_CertificateCondition(in, out, s) } -func autoConvert_v1_CertificateKeystores_To_certmanager_CertificateKeystores(in *v1.CertificateKeystores, out *certmanager.CertificateKeystores, s conversion.Scope) error { +func autoConvert_v1_CertificateKeystores_To_certmanager_CertificateKeystores(in *certmanagerv1.CertificateKeystores, out *certmanager.CertificateKeystores, s conversion.Scope) error { if in.JKS != nil { in, out := &in.JKS, &out.JKS *out = new(certmanager.JKSKeystore) @@ -516,14 +568,14 @@ func autoConvert_v1_CertificateKeystores_To_certmanager_CertificateKeystores(in } // Convert_v1_CertificateKeystores_To_certmanager_CertificateKeystores is an autogenerated conversion function. -func Convert_v1_CertificateKeystores_To_certmanager_CertificateKeystores(in *v1.CertificateKeystores, out *certmanager.CertificateKeystores, s conversion.Scope) error { +func Convert_v1_CertificateKeystores_To_certmanager_CertificateKeystores(in *certmanagerv1.CertificateKeystores, out *certmanager.CertificateKeystores, s conversion.Scope) error { return autoConvert_v1_CertificateKeystores_To_certmanager_CertificateKeystores(in, out, s) } -func autoConvert_certmanager_CertificateKeystores_To_v1_CertificateKeystores(in *certmanager.CertificateKeystores, out *v1.CertificateKeystores, s conversion.Scope) error { +func autoConvert_certmanager_CertificateKeystores_To_v1_CertificateKeystores(in *certmanager.CertificateKeystores, out *certmanagerv1.CertificateKeystores, s conversion.Scope) error { if in.JKS != nil { in, out := &in.JKS, &out.JKS - *out = new(v1.JKSKeystore) + *out = new(certmanagerv1.JKSKeystore) if err := Convert_certmanager_JKSKeystore_To_v1_JKSKeystore(*in, *out, s); err != nil { return err } @@ -532,7 +584,7 @@ func autoConvert_certmanager_CertificateKeystores_To_v1_CertificateKeystores(in } if in.PKCS12 != nil { in, out := &in.PKCS12, &out.PKCS12 - *out = new(v1.PKCS12Keystore) + *out = new(certmanagerv1.PKCS12Keystore) if err := Convert_certmanager_PKCS12Keystore_To_v1_PKCS12Keystore(*in, *out, s); err != nil { return err } @@ -543,11 +595,11 @@ func autoConvert_certmanager_CertificateKeystores_To_v1_CertificateKeystores(in } // Convert_certmanager_CertificateKeystores_To_v1_CertificateKeystores is an autogenerated conversion function. -func Convert_certmanager_CertificateKeystores_To_v1_CertificateKeystores(in *certmanager.CertificateKeystores, out *v1.CertificateKeystores, s conversion.Scope) error { +func Convert_certmanager_CertificateKeystores_To_v1_CertificateKeystores(in *certmanager.CertificateKeystores, out *certmanagerv1.CertificateKeystores, s conversion.Scope) error { return autoConvert_certmanager_CertificateKeystores_To_v1_CertificateKeystores(in, out, s) } -func autoConvert_v1_CertificateList_To_certmanager_CertificateList(in *v1.CertificateList, out *certmanager.CertificateList, s conversion.Scope) error { +func autoConvert_v1_CertificateList_To_certmanager_CertificateList(in *certmanagerv1.CertificateList, out *certmanager.CertificateList, s conversion.Scope) error { out.ListMeta = in.ListMeta if in.Items != nil { in, out := &in.Items, &out.Items @@ -564,15 +616,15 @@ func autoConvert_v1_CertificateList_To_certmanager_CertificateList(in *v1.Certif } // Convert_v1_CertificateList_To_certmanager_CertificateList is an autogenerated conversion function. -func Convert_v1_CertificateList_To_certmanager_CertificateList(in *v1.CertificateList, out *certmanager.CertificateList, s conversion.Scope) error { +func Convert_v1_CertificateList_To_certmanager_CertificateList(in *certmanagerv1.CertificateList, out *certmanager.CertificateList, s conversion.Scope) error { return autoConvert_v1_CertificateList_To_certmanager_CertificateList(in, out, s) } -func autoConvert_certmanager_CertificateList_To_v1_CertificateList(in *certmanager.CertificateList, out *v1.CertificateList, s conversion.Scope) error { +func autoConvert_certmanager_CertificateList_To_v1_CertificateList(in *certmanager.CertificateList, out *certmanagerv1.CertificateList, s conversion.Scope) error { out.ListMeta = in.ListMeta if in.Items != nil { in, out := &in.Items, &out.Items - *out = make([]v1.Certificate, len(*in)) + *out = make([]certmanagerv1.Certificate, len(*in)) for i := range *in { if err := Convert_certmanager_Certificate_To_v1_Certificate(&(*in)[i], &(*out)[i], s); err != nil { return err @@ -585,11 +637,11 @@ func autoConvert_certmanager_CertificateList_To_v1_CertificateList(in *certmanag } // Convert_certmanager_CertificateList_To_v1_CertificateList is an autogenerated conversion function. -func Convert_certmanager_CertificateList_To_v1_CertificateList(in *certmanager.CertificateList, out *v1.CertificateList, s conversion.Scope) error { +func Convert_certmanager_CertificateList_To_v1_CertificateList(in *certmanager.CertificateList, out *certmanagerv1.CertificateList, s conversion.Scope) error { return autoConvert_certmanager_CertificateList_To_v1_CertificateList(in, out, s) } -func autoConvert_v1_CertificatePrivateKey_To_certmanager_CertificatePrivateKey(in *v1.CertificatePrivateKey, out *certmanager.CertificatePrivateKey, s conversion.Scope) error { +func autoConvert_v1_CertificatePrivateKey_To_certmanager_CertificatePrivateKey(in *certmanagerv1.CertificatePrivateKey, out *certmanager.CertificatePrivateKey, s conversion.Scope) error { out.RotationPolicy = certmanager.PrivateKeyRotationPolicy(in.RotationPolicy) out.Encoding = certmanager.PrivateKeyEncoding(in.Encoding) out.Algorithm = certmanager.PrivateKeyAlgorithm(in.Algorithm) @@ -598,24 +650,24 @@ func autoConvert_v1_CertificatePrivateKey_To_certmanager_CertificatePrivateKey(i } // Convert_v1_CertificatePrivateKey_To_certmanager_CertificatePrivateKey is an autogenerated conversion function. -func Convert_v1_CertificatePrivateKey_To_certmanager_CertificatePrivateKey(in *v1.CertificatePrivateKey, out *certmanager.CertificatePrivateKey, s conversion.Scope) error { +func Convert_v1_CertificatePrivateKey_To_certmanager_CertificatePrivateKey(in *certmanagerv1.CertificatePrivateKey, out *certmanager.CertificatePrivateKey, s conversion.Scope) error { return autoConvert_v1_CertificatePrivateKey_To_certmanager_CertificatePrivateKey(in, out, s) } -func autoConvert_certmanager_CertificatePrivateKey_To_v1_CertificatePrivateKey(in *certmanager.CertificatePrivateKey, out *v1.CertificatePrivateKey, s conversion.Scope) error { - out.RotationPolicy = v1.PrivateKeyRotationPolicy(in.RotationPolicy) - out.Encoding = v1.PrivateKeyEncoding(in.Encoding) - out.Algorithm = v1.PrivateKeyAlgorithm(in.Algorithm) +func autoConvert_certmanager_CertificatePrivateKey_To_v1_CertificatePrivateKey(in *certmanager.CertificatePrivateKey, out *certmanagerv1.CertificatePrivateKey, s conversion.Scope) error { + out.RotationPolicy = certmanagerv1.PrivateKeyRotationPolicy(in.RotationPolicy) + out.Encoding = certmanagerv1.PrivateKeyEncoding(in.Encoding) + out.Algorithm = certmanagerv1.PrivateKeyAlgorithm(in.Algorithm) out.Size = in.Size return nil } // Convert_certmanager_CertificatePrivateKey_To_v1_CertificatePrivateKey is an autogenerated conversion function. -func Convert_certmanager_CertificatePrivateKey_To_v1_CertificatePrivateKey(in *certmanager.CertificatePrivateKey, out *v1.CertificatePrivateKey, s conversion.Scope) error { +func Convert_certmanager_CertificatePrivateKey_To_v1_CertificatePrivateKey(in *certmanager.CertificatePrivateKey, out *certmanagerv1.CertificatePrivateKey, s conversion.Scope) error { return autoConvert_certmanager_CertificatePrivateKey_To_v1_CertificatePrivateKey(in, out, s) } -func autoConvert_v1_CertificateRequest_To_certmanager_CertificateRequest(in *v1.CertificateRequest, out *certmanager.CertificateRequest, s conversion.Scope) error { +func autoConvert_v1_CertificateRequest_To_certmanager_CertificateRequest(in *certmanagerv1.CertificateRequest, out *certmanager.CertificateRequest, s conversion.Scope) error { out.ObjectMeta = in.ObjectMeta if err := Convert_v1_CertificateRequestSpec_To_certmanager_CertificateRequestSpec(&in.Spec, &out.Spec, s); err != nil { return err @@ -627,11 +679,11 @@ func autoConvert_v1_CertificateRequest_To_certmanager_CertificateRequest(in *v1. } // Convert_v1_CertificateRequest_To_certmanager_CertificateRequest is an autogenerated conversion function. -func Convert_v1_CertificateRequest_To_certmanager_CertificateRequest(in *v1.CertificateRequest, out *certmanager.CertificateRequest, s conversion.Scope) error { +func Convert_v1_CertificateRequest_To_certmanager_CertificateRequest(in *certmanagerv1.CertificateRequest, out *certmanager.CertificateRequest, s conversion.Scope) error { return autoConvert_v1_CertificateRequest_To_certmanager_CertificateRequest(in, out, s) } -func autoConvert_certmanager_CertificateRequest_To_v1_CertificateRequest(in *certmanager.CertificateRequest, out *v1.CertificateRequest, s conversion.Scope) error { +func autoConvert_certmanager_CertificateRequest_To_v1_CertificateRequest(in *certmanager.CertificateRequest, out *certmanagerv1.CertificateRequest, s conversion.Scope) error { out.ObjectMeta = in.ObjectMeta if err := Convert_certmanager_CertificateRequestSpec_To_v1_CertificateRequestSpec(&in.Spec, &out.Spec, s); err != nil { return err @@ -643,11 +695,11 @@ func autoConvert_certmanager_CertificateRequest_To_v1_CertificateRequest(in *cer } // Convert_certmanager_CertificateRequest_To_v1_CertificateRequest is an autogenerated conversion function. -func Convert_certmanager_CertificateRequest_To_v1_CertificateRequest(in *certmanager.CertificateRequest, out *v1.CertificateRequest, s conversion.Scope) error { +func Convert_certmanager_CertificateRequest_To_v1_CertificateRequest(in *certmanager.CertificateRequest, out *certmanagerv1.CertificateRequest, s conversion.Scope) error { return autoConvert_certmanager_CertificateRequest_To_v1_CertificateRequest(in, out, s) } -func autoConvert_v1_CertificateRequestCondition_To_certmanager_CertificateRequestCondition(in *v1.CertificateRequestCondition, out *certmanager.CertificateRequestCondition, s conversion.Scope) error { +func autoConvert_v1_CertificateRequestCondition_To_certmanager_CertificateRequestCondition(in *certmanagerv1.CertificateRequestCondition, out *certmanager.CertificateRequestCondition, s conversion.Scope) error { out.Type = certmanager.CertificateRequestConditionType(in.Type) out.Status = meta.ConditionStatus(in.Status) out.LastTransitionTime = (*metav1.Time)(unsafe.Pointer(in.LastTransitionTime)) @@ -657,12 +709,12 @@ func autoConvert_v1_CertificateRequestCondition_To_certmanager_CertificateReques } // Convert_v1_CertificateRequestCondition_To_certmanager_CertificateRequestCondition is an autogenerated conversion function. -func Convert_v1_CertificateRequestCondition_To_certmanager_CertificateRequestCondition(in *v1.CertificateRequestCondition, out *certmanager.CertificateRequestCondition, s conversion.Scope) error { +func Convert_v1_CertificateRequestCondition_To_certmanager_CertificateRequestCondition(in *certmanagerv1.CertificateRequestCondition, out *certmanager.CertificateRequestCondition, s conversion.Scope) error { return autoConvert_v1_CertificateRequestCondition_To_certmanager_CertificateRequestCondition(in, out, s) } -func autoConvert_certmanager_CertificateRequestCondition_To_v1_CertificateRequestCondition(in *certmanager.CertificateRequestCondition, out *v1.CertificateRequestCondition, s conversion.Scope) error { - out.Type = v1.CertificateRequestConditionType(in.Type) +func autoConvert_certmanager_CertificateRequestCondition_To_v1_CertificateRequestCondition(in *certmanager.CertificateRequestCondition, out *certmanagerv1.CertificateRequestCondition, s conversion.Scope) error { + out.Type = certmanagerv1.CertificateRequestConditionType(in.Type) out.Status = apismetav1.ConditionStatus(in.Status) out.LastTransitionTime = (*metav1.Time)(unsafe.Pointer(in.LastTransitionTime)) out.Reason = in.Reason @@ -671,11 +723,11 @@ func autoConvert_certmanager_CertificateRequestCondition_To_v1_CertificateReques } // Convert_certmanager_CertificateRequestCondition_To_v1_CertificateRequestCondition is an autogenerated conversion function. -func Convert_certmanager_CertificateRequestCondition_To_v1_CertificateRequestCondition(in *certmanager.CertificateRequestCondition, out *v1.CertificateRequestCondition, s conversion.Scope) error { +func Convert_certmanager_CertificateRequestCondition_To_v1_CertificateRequestCondition(in *certmanager.CertificateRequestCondition, out *certmanagerv1.CertificateRequestCondition, s conversion.Scope) error { return autoConvert_certmanager_CertificateRequestCondition_To_v1_CertificateRequestCondition(in, out, s) } -func autoConvert_v1_CertificateRequestList_To_certmanager_CertificateRequestList(in *v1.CertificateRequestList, out *certmanager.CertificateRequestList, s conversion.Scope) error { +func autoConvert_v1_CertificateRequestList_To_certmanager_CertificateRequestList(in *certmanagerv1.CertificateRequestList, out *certmanager.CertificateRequestList, s conversion.Scope) error { out.ListMeta = in.ListMeta if in.Items != nil { in, out := &in.Items, &out.Items @@ -692,15 +744,15 @@ func autoConvert_v1_CertificateRequestList_To_certmanager_CertificateRequestList } // Convert_v1_CertificateRequestList_To_certmanager_CertificateRequestList is an autogenerated conversion function. -func Convert_v1_CertificateRequestList_To_certmanager_CertificateRequestList(in *v1.CertificateRequestList, out *certmanager.CertificateRequestList, s conversion.Scope) error { +func Convert_v1_CertificateRequestList_To_certmanager_CertificateRequestList(in *certmanagerv1.CertificateRequestList, out *certmanager.CertificateRequestList, s conversion.Scope) error { return autoConvert_v1_CertificateRequestList_To_certmanager_CertificateRequestList(in, out, s) } -func autoConvert_certmanager_CertificateRequestList_To_v1_CertificateRequestList(in *certmanager.CertificateRequestList, out *v1.CertificateRequestList, s conversion.Scope) error { +func autoConvert_certmanager_CertificateRequestList_To_v1_CertificateRequestList(in *certmanager.CertificateRequestList, out *certmanagerv1.CertificateRequestList, s conversion.Scope) error { out.ListMeta = in.ListMeta if in.Items != nil { in, out := &in.Items, &out.Items - *out = make([]v1.CertificateRequest, len(*in)) + *out = make([]certmanagerv1.CertificateRequest, len(*in)) for i := range *in { if err := Convert_certmanager_CertificateRequest_To_v1_CertificateRequest(&(*in)[i], &(*out)[i], s); err != nil { return err @@ -713,13 +765,13 @@ func autoConvert_certmanager_CertificateRequestList_To_v1_CertificateRequestList } // Convert_certmanager_CertificateRequestList_To_v1_CertificateRequestList is an autogenerated conversion function. -func Convert_certmanager_CertificateRequestList_To_v1_CertificateRequestList(in *certmanager.CertificateRequestList, out *v1.CertificateRequestList, s conversion.Scope) error { +func Convert_certmanager_CertificateRequestList_To_v1_CertificateRequestList(in *certmanager.CertificateRequestList, out *certmanagerv1.CertificateRequestList, s conversion.Scope) error { return autoConvert_certmanager_CertificateRequestList_To_v1_CertificateRequestList(in, out, s) } -func autoConvert_v1_CertificateRequestSpec_To_certmanager_CertificateRequestSpec(in *v1.CertificateRequestSpec, out *certmanager.CertificateRequestSpec, s conversion.Scope) error { +func autoConvert_v1_CertificateRequestSpec_To_certmanager_CertificateRequestSpec(in *certmanagerv1.CertificateRequestSpec, out *certmanager.CertificateRequestSpec, s conversion.Scope) error { out.Duration = (*metav1.Duration)(unsafe.Pointer(in.Duration)) - if err := internalapismetav1.Convert_v1_ObjectReference_To_meta_ObjectReference(&in.IssuerRef, &out.IssuerRef, s); err != nil { + if err := internalapismetav1.Convert_v1_IssuerReference_To_meta_IssuerReference(&in.IssuerRef, &out.IssuerRef, s); err != nil { return err } out.Request = *(*[]byte)(unsafe.Pointer(&in.Request)) @@ -733,18 +785,18 @@ func autoConvert_v1_CertificateRequestSpec_To_certmanager_CertificateRequestSpec } // Convert_v1_CertificateRequestSpec_To_certmanager_CertificateRequestSpec is an autogenerated conversion function. -func Convert_v1_CertificateRequestSpec_To_certmanager_CertificateRequestSpec(in *v1.CertificateRequestSpec, out *certmanager.CertificateRequestSpec, s conversion.Scope) error { +func Convert_v1_CertificateRequestSpec_To_certmanager_CertificateRequestSpec(in *certmanagerv1.CertificateRequestSpec, out *certmanager.CertificateRequestSpec, s conversion.Scope) error { return autoConvert_v1_CertificateRequestSpec_To_certmanager_CertificateRequestSpec(in, out, s) } -func autoConvert_certmanager_CertificateRequestSpec_To_v1_CertificateRequestSpec(in *certmanager.CertificateRequestSpec, out *v1.CertificateRequestSpec, s conversion.Scope) error { +func autoConvert_certmanager_CertificateRequestSpec_To_v1_CertificateRequestSpec(in *certmanager.CertificateRequestSpec, out *certmanagerv1.CertificateRequestSpec, s conversion.Scope) error { out.Duration = (*metav1.Duration)(unsafe.Pointer(in.Duration)) - if err := internalapismetav1.Convert_meta_ObjectReference_To_v1_ObjectReference(&in.IssuerRef, &out.IssuerRef, s); err != nil { + if err := internalapismetav1.Convert_meta_IssuerReference_To_v1_IssuerReference(&in.IssuerRef, &out.IssuerRef, s); err != nil { return err } out.Request = *(*[]byte)(unsafe.Pointer(&in.Request)) out.IsCA = in.IsCA - out.Usages = *(*[]v1.KeyUsage)(unsafe.Pointer(&in.Usages)) + out.Usages = *(*[]certmanagerv1.KeyUsage)(unsafe.Pointer(&in.Usages)) out.Username = in.Username out.UID = in.UID out.Groups = *(*[]string)(unsafe.Pointer(&in.Groups)) @@ -753,11 +805,11 @@ func autoConvert_certmanager_CertificateRequestSpec_To_v1_CertificateRequestSpec } // Convert_certmanager_CertificateRequestSpec_To_v1_CertificateRequestSpec is an autogenerated conversion function. -func Convert_certmanager_CertificateRequestSpec_To_v1_CertificateRequestSpec(in *certmanager.CertificateRequestSpec, out *v1.CertificateRequestSpec, s conversion.Scope) error { +func Convert_certmanager_CertificateRequestSpec_To_v1_CertificateRequestSpec(in *certmanager.CertificateRequestSpec, out *certmanagerv1.CertificateRequestSpec, s conversion.Scope) error { return autoConvert_certmanager_CertificateRequestSpec_To_v1_CertificateRequestSpec(in, out, s) } -func autoConvert_v1_CertificateRequestStatus_To_certmanager_CertificateRequestStatus(in *v1.CertificateRequestStatus, out *certmanager.CertificateRequestStatus, s conversion.Scope) error { +func autoConvert_v1_CertificateRequestStatus_To_certmanager_CertificateRequestStatus(in *certmanagerv1.CertificateRequestStatus, out *certmanager.CertificateRequestStatus, s conversion.Scope) error { out.Conditions = *(*[]certmanager.CertificateRequestCondition)(unsafe.Pointer(&in.Conditions)) out.Certificate = *(*[]byte)(unsafe.Pointer(&in.Certificate)) out.CA = *(*[]byte)(unsafe.Pointer(&in.CA)) @@ -766,12 +818,12 @@ func autoConvert_v1_CertificateRequestStatus_To_certmanager_CertificateRequestSt } // Convert_v1_CertificateRequestStatus_To_certmanager_CertificateRequestStatus is an autogenerated conversion function. -func Convert_v1_CertificateRequestStatus_To_certmanager_CertificateRequestStatus(in *v1.CertificateRequestStatus, out *certmanager.CertificateRequestStatus, s conversion.Scope) error { +func Convert_v1_CertificateRequestStatus_To_certmanager_CertificateRequestStatus(in *certmanagerv1.CertificateRequestStatus, out *certmanager.CertificateRequestStatus, s conversion.Scope) error { return autoConvert_v1_CertificateRequestStatus_To_certmanager_CertificateRequestStatus(in, out, s) } -func autoConvert_certmanager_CertificateRequestStatus_To_v1_CertificateRequestStatus(in *certmanager.CertificateRequestStatus, out *v1.CertificateRequestStatus, s conversion.Scope) error { - out.Conditions = *(*[]v1.CertificateRequestCondition)(unsafe.Pointer(&in.Conditions)) +func autoConvert_certmanager_CertificateRequestStatus_To_v1_CertificateRequestStatus(in *certmanager.CertificateRequestStatus, out *certmanagerv1.CertificateRequestStatus, s conversion.Scope) error { + out.Conditions = *(*[]certmanagerv1.CertificateRequestCondition)(unsafe.Pointer(&in.Conditions)) out.Certificate = *(*[]byte)(unsafe.Pointer(&in.Certificate)) out.CA = *(*[]byte)(unsafe.Pointer(&in.CA)) out.FailureTime = (*metav1.Time)(unsafe.Pointer(in.FailureTime)) @@ -779,42 +831,44 @@ func autoConvert_certmanager_CertificateRequestStatus_To_v1_CertificateRequestSt } // Convert_certmanager_CertificateRequestStatus_To_v1_CertificateRequestStatus is an autogenerated conversion function. -func Convert_certmanager_CertificateRequestStatus_To_v1_CertificateRequestStatus(in *certmanager.CertificateRequestStatus, out *v1.CertificateRequestStatus, s conversion.Scope) error { +func Convert_certmanager_CertificateRequestStatus_To_v1_CertificateRequestStatus(in *certmanager.CertificateRequestStatus, out *certmanagerv1.CertificateRequestStatus, s conversion.Scope) error { return autoConvert_certmanager_CertificateRequestStatus_To_v1_CertificateRequestStatus(in, out, s) } -func autoConvert_v1_CertificateSecretTemplate_To_certmanager_CertificateSecretTemplate(in *v1.CertificateSecretTemplate, out *certmanager.CertificateSecretTemplate, s conversion.Scope) error { +func autoConvert_v1_CertificateSecretTemplate_To_certmanager_CertificateSecretTemplate(in *certmanagerv1.CertificateSecretTemplate, out *certmanager.CertificateSecretTemplate, s conversion.Scope) error { out.Annotations = *(*map[string]string)(unsafe.Pointer(&in.Annotations)) out.Labels = *(*map[string]string)(unsafe.Pointer(&in.Labels)) return nil } // Convert_v1_CertificateSecretTemplate_To_certmanager_CertificateSecretTemplate is an autogenerated conversion function. -func Convert_v1_CertificateSecretTemplate_To_certmanager_CertificateSecretTemplate(in *v1.CertificateSecretTemplate, out *certmanager.CertificateSecretTemplate, s conversion.Scope) error { +func Convert_v1_CertificateSecretTemplate_To_certmanager_CertificateSecretTemplate(in *certmanagerv1.CertificateSecretTemplate, out *certmanager.CertificateSecretTemplate, s conversion.Scope) error { return autoConvert_v1_CertificateSecretTemplate_To_certmanager_CertificateSecretTemplate(in, out, s) } -func autoConvert_certmanager_CertificateSecretTemplate_To_v1_CertificateSecretTemplate(in *certmanager.CertificateSecretTemplate, out *v1.CertificateSecretTemplate, s conversion.Scope) error { +func autoConvert_certmanager_CertificateSecretTemplate_To_v1_CertificateSecretTemplate(in *certmanager.CertificateSecretTemplate, out *certmanagerv1.CertificateSecretTemplate, s conversion.Scope) error { out.Annotations = *(*map[string]string)(unsafe.Pointer(&in.Annotations)) out.Labels = *(*map[string]string)(unsafe.Pointer(&in.Labels)) return nil } // Convert_certmanager_CertificateSecretTemplate_To_v1_CertificateSecretTemplate is an autogenerated conversion function. -func Convert_certmanager_CertificateSecretTemplate_To_v1_CertificateSecretTemplate(in *certmanager.CertificateSecretTemplate, out *v1.CertificateSecretTemplate, s conversion.Scope) error { +func Convert_certmanager_CertificateSecretTemplate_To_v1_CertificateSecretTemplate(in *certmanager.CertificateSecretTemplate, out *certmanagerv1.CertificateSecretTemplate, s conversion.Scope) error { return autoConvert_certmanager_CertificateSecretTemplate_To_v1_CertificateSecretTemplate(in, out, s) } -func autoConvert_v1_CertificateSpec_To_certmanager_CertificateSpec(in *v1.CertificateSpec, out *certmanager.CertificateSpec, s conversion.Scope) error { +func autoConvert_v1_CertificateSpec_To_certmanager_CertificateSpec(in *certmanagerv1.CertificateSpec, out *certmanager.CertificateSpec, s conversion.Scope) error { out.Subject = (*certmanager.X509Subject)(unsafe.Pointer(in.Subject)) out.LiteralSubject = in.LiteralSubject out.CommonName = in.CommonName out.Duration = (*metav1.Duration)(unsafe.Pointer(in.Duration)) out.RenewBefore = (*metav1.Duration)(unsafe.Pointer(in.RenewBefore)) + out.RenewBeforePercentage = (*int32)(unsafe.Pointer(in.RenewBeforePercentage)) out.DNSNames = *(*[]string)(unsafe.Pointer(&in.DNSNames)) out.IPAddresses = *(*[]string)(unsafe.Pointer(&in.IPAddresses)) - // WARNING: in.URIs requires manual conversion: does not exist in peer-type - // WARNING: in.EmailAddresses requires manual conversion: does not exist in peer-type + out.URIs = *(*[]string)(unsafe.Pointer(&in.URIs)) + out.OtherNames = *(*[]certmanager.OtherName)(unsafe.Pointer(&in.OtherNames)) + out.EmailAddresses = *(*[]string)(unsafe.Pointer(&in.EmailAddresses)) out.SecretName = in.SecretName out.SecretTemplate = (*certmanager.CertificateSecretTemplate)(unsafe.Pointer(in.SecretTemplate)) if in.Keystores != nil { @@ -826,52 +880,68 @@ func autoConvert_v1_CertificateSpec_To_certmanager_CertificateSpec(in *v1.Certif } else { out.Keystores = nil } - if err := internalapismetav1.Convert_v1_ObjectReference_To_meta_ObjectReference(&in.IssuerRef, &out.IssuerRef, s); err != nil { + if err := internalapismetav1.Convert_v1_IssuerReference_To_meta_IssuerReference(&in.IssuerRef, &out.IssuerRef, s); err != nil { return err } out.IsCA = in.IsCA out.Usages = *(*[]certmanager.KeyUsage)(unsafe.Pointer(&in.Usages)) out.PrivateKey = (*certmanager.CertificatePrivateKey)(unsafe.Pointer(in.PrivateKey)) + out.SignatureAlgorithm = certmanager.SignatureAlgorithm(in.SignatureAlgorithm) out.EncodeUsagesInRequest = (*bool)(unsafe.Pointer(in.EncodeUsagesInRequest)) out.RevisionHistoryLimit = (*int32)(unsafe.Pointer(in.RevisionHistoryLimit)) out.AdditionalOutputFormats = *(*[]certmanager.CertificateAdditionalOutputFormat)(unsafe.Pointer(&in.AdditionalOutputFormats)) + out.NameConstraints = (*certmanager.NameConstraints)(unsafe.Pointer(in.NameConstraints)) return nil } -func autoConvert_certmanager_CertificateSpec_To_v1_CertificateSpec(in *certmanager.CertificateSpec, out *v1.CertificateSpec, s conversion.Scope) error { - out.Subject = (*v1.X509Subject)(unsafe.Pointer(in.Subject)) +// Convert_v1_CertificateSpec_To_certmanager_CertificateSpec is an autogenerated conversion function. +func Convert_v1_CertificateSpec_To_certmanager_CertificateSpec(in *certmanagerv1.CertificateSpec, out *certmanager.CertificateSpec, s conversion.Scope) error { + return autoConvert_v1_CertificateSpec_To_certmanager_CertificateSpec(in, out, s) +} + +func autoConvert_certmanager_CertificateSpec_To_v1_CertificateSpec(in *certmanager.CertificateSpec, out *certmanagerv1.CertificateSpec, s conversion.Scope) error { + out.Subject = (*certmanagerv1.X509Subject)(unsafe.Pointer(in.Subject)) out.LiteralSubject = in.LiteralSubject out.CommonName = in.CommonName out.Duration = (*metav1.Duration)(unsafe.Pointer(in.Duration)) out.RenewBefore = (*metav1.Duration)(unsafe.Pointer(in.RenewBefore)) + out.RenewBeforePercentage = (*int32)(unsafe.Pointer(in.RenewBeforePercentage)) out.DNSNames = *(*[]string)(unsafe.Pointer(&in.DNSNames)) out.IPAddresses = *(*[]string)(unsafe.Pointer(&in.IPAddresses)) - // WARNING: in.URISANs requires manual conversion: does not exist in peer-type - // WARNING: in.EmailSANs requires manual conversion: does not exist in peer-type + out.URIs = *(*[]string)(unsafe.Pointer(&in.URIs)) + out.EmailAddresses = *(*[]string)(unsafe.Pointer(&in.EmailAddresses)) + out.OtherNames = *(*[]certmanagerv1.OtherName)(unsafe.Pointer(&in.OtherNames)) out.SecretName = in.SecretName - out.SecretTemplate = (*v1.CertificateSecretTemplate)(unsafe.Pointer(in.SecretTemplate)) + out.SecretTemplate = (*certmanagerv1.CertificateSecretTemplate)(unsafe.Pointer(in.SecretTemplate)) if in.Keystores != nil { in, out := &in.Keystores, &out.Keystores - *out = new(v1.CertificateKeystores) + *out = new(certmanagerv1.CertificateKeystores) if err := Convert_certmanager_CertificateKeystores_To_v1_CertificateKeystores(*in, *out, s); err != nil { return err } } else { out.Keystores = nil } - if err := internalapismetav1.Convert_meta_ObjectReference_To_v1_ObjectReference(&in.IssuerRef, &out.IssuerRef, s); err != nil { + if err := internalapismetav1.Convert_meta_IssuerReference_To_v1_IssuerReference(&in.IssuerRef, &out.IssuerRef, s); err != nil { return err } out.IsCA = in.IsCA - out.Usages = *(*[]v1.KeyUsage)(unsafe.Pointer(&in.Usages)) - out.PrivateKey = (*v1.CertificatePrivateKey)(unsafe.Pointer(in.PrivateKey)) + out.Usages = *(*[]certmanagerv1.KeyUsage)(unsafe.Pointer(&in.Usages)) + out.PrivateKey = (*certmanagerv1.CertificatePrivateKey)(unsafe.Pointer(in.PrivateKey)) + out.SignatureAlgorithm = certmanagerv1.SignatureAlgorithm(in.SignatureAlgorithm) out.EncodeUsagesInRequest = (*bool)(unsafe.Pointer(in.EncodeUsagesInRequest)) out.RevisionHistoryLimit = (*int32)(unsafe.Pointer(in.RevisionHistoryLimit)) - out.AdditionalOutputFormats = *(*[]v1.CertificateAdditionalOutputFormat)(unsafe.Pointer(&in.AdditionalOutputFormats)) + out.AdditionalOutputFormats = *(*[]certmanagerv1.CertificateAdditionalOutputFormat)(unsafe.Pointer(&in.AdditionalOutputFormats)) + out.NameConstraints = (*certmanagerv1.NameConstraints)(unsafe.Pointer(in.NameConstraints)) return nil } -func autoConvert_v1_CertificateStatus_To_certmanager_CertificateStatus(in *v1.CertificateStatus, out *certmanager.CertificateStatus, s conversion.Scope) error { +// Convert_certmanager_CertificateSpec_To_v1_CertificateSpec is an autogenerated conversion function. +func Convert_certmanager_CertificateSpec_To_v1_CertificateSpec(in *certmanager.CertificateSpec, out *certmanagerv1.CertificateSpec, s conversion.Scope) error { + return autoConvert_certmanager_CertificateSpec_To_v1_CertificateSpec(in, out, s) +} + +func autoConvert_v1_CertificateStatus_To_certmanager_CertificateStatus(in *certmanagerv1.CertificateStatus, out *certmanager.CertificateStatus, s conversion.Scope) error { out.Conditions = *(*[]certmanager.CertificateCondition)(unsafe.Pointer(&in.Conditions)) out.LastFailureTime = (*metav1.Time)(unsafe.Pointer(in.LastFailureTime)) out.NotBefore = (*metav1.Time)(unsafe.Pointer(in.NotBefore)) @@ -884,12 +954,12 @@ func autoConvert_v1_CertificateStatus_To_certmanager_CertificateStatus(in *v1.Ce } // Convert_v1_CertificateStatus_To_certmanager_CertificateStatus is an autogenerated conversion function. -func Convert_v1_CertificateStatus_To_certmanager_CertificateStatus(in *v1.CertificateStatus, out *certmanager.CertificateStatus, s conversion.Scope) error { +func Convert_v1_CertificateStatus_To_certmanager_CertificateStatus(in *certmanagerv1.CertificateStatus, out *certmanager.CertificateStatus, s conversion.Scope) error { return autoConvert_v1_CertificateStatus_To_certmanager_CertificateStatus(in, out, s) } -func autoConvert_certmanager_CertificateStatus_To_v1_CertificateStatus(in *certmanager.CertificateStatus, out *v1.CertificateStatus, s conversion.Scope) error { - out.Conditions = *(*[]v1.CertificateCondition)(unsafe.Pointer(&in.Conditions)) +func autoConvert_certmanager_CertificateStatus_To_v1_CertificateStatus(in *certmanager.CertificateStatus, out *certmanagerv1.CertificateStatus, s conversion.Scope) error { + out.Conditions = *(*[]certmanagerv1.CertificateCondition)(unsafe.Pointer(&in.Conditions)) out.LastFailureTime = (*metav1.Time)(unsafe.Pointer(in.LastFailureTime)) out.NotBefore = (*metav1.Time)(unsafe.Pointer(in.NotBefore)) out.NotAfter = (*metav1.Time)(unsafe.Pointer(in.NotAfter)) @@ -901,11 +971,11 @@ func autoConvert_certmanager_CertificateStatus_To_v1_CertificateStatus(in *certm } // Convert_certmanager_CertificateStatus_To_v1_CertificateStatus is an autogenerated conversion function. -func Convert_certmanager_CertificateStatus_To_v1_CertificateStatus(in *certmanager.CertificateStatus, out *v1.CertificateStatus, s conversion.Scope) error { +func Convert_certmanager_CertificateStatus_To_v1_CertificateStatus(in *certmanager.CertificateStatus, out *certmanagerv1.CertificateStatus, s conversion.Scope) error { return autoConvert_certmanager_CertificateStatus_To_v1_CertificateStatus(in, out, s) } -func autoConvert_v1_ClusterIssuer_To_certmanager_ClusterIssuer(in *v1.ClusterIssuer, out *certmanager.ClusterIssuer, s conversion.Scope) error { +func autoConvert_v1_ClusterIssuer_To_certmanager_ClusterIssuer(in *certmanagerv1.ClusterIssuer, out *certmanager.ClusterIssuer, s conversion.Scope) error { out.ObjectMeta = in.ObjectMeta if err := Convert_v1_IssuerSpec_To_certmanager_IssuerSpec(&in.Spec, &out.Spec, s); err != nil { return err @@ -917,11 +987,11 @@ func autoConvert_v1_ClusterIssuer_To_certmanager_ClusterIssuer(in *v1.ClusterIss } // Convert_v1_ClusterIssuer_To_certmanager_ClusterIssuer is an autogenerated conversion function. -func Convert_v1_ClusterIssuer_To_certmanager_ClusterIssuer(in *v1.ClusterIssuer, out *certmanager.ClusterIssuer, s conversion.Scope) error { +func Convert_v1_ClusterIssuer_To_certmanager_ClusterIssuer(in *certmanagerv1.ClusterIssuer, out *certmanager.ClusterIssuer, s conversion.Scope) error { return autoConvert_v1_ClusterIssuer_To_certmanager_ClusterIssuer(in, out, s) } -func autoConvert_certmanager_ClusterIssuer_To_v1_ClusterIssuer(in *certmanager.ClusterIssuer, out *v1.ClusterIssuer, s conversion.Scope) error { +func autoConvert_certmanager_ClusterIssuer_To_v1_ClusterIssuer(in *certmanager.ClusterIssuer, out *certmanagerv1.ClusterIssuer, s conversion.Scope) error { out.ObjectMeta = in.ObjectMeta if err := Convert_certmanager_IssuerSpec_To_v1_IssuerSpec(&in.Spec, &out.Spec, s); err != nil { return err @@ -933,11 +1003,11 @@ func autoConvert_certmanager_ClusterIssuer_To_v1_ClusterIssuer(in *certmanager.C } // Convert_certmanager_ClusterIssuer_To_v1_ClusterIssuer is an autogenerated conversion function. -func Convert_certmanager_ClusterIssuer_To_v1_ClusterIssuer(in *certmanager.ClusterIssuer, out *v1.ClusterIssuer, s conversion.Scope) error { +func Convert_certmanager_ClusterIssuer_To_v1_ClusterIssuer(in *certmanager.ClusterIssuer, out *certmanagerv1.ClusterIssuer, s conversion.Scope) error { return autoConvert_certmanager_ClusterIssuer_To_v1_ClusterIssuer(in, out, s) } -func autoConvert_v1_ClusterIssuerList_To_certmanager_ClusterIssuerList(in *v1.ClusterIssuerList, out *certmanager.ClusterIssuerList, s conversion.Scope) error { +func autoConvert_v1_ClusterIssuerList_To_certmanager_ClusterIssuerList(in *certmanagerv1.ClusterIssuerList, out *certmanager.ClusterIssuerList, s conversion.Scope) error { out.ListMeta = in.ListMeta if in.Items != nil { in, out := &in.Items, &out.Items @@ -954,15 +1024,15 @@ func autoConvert_v1_ClusterIssuerList_To_certmanager_ClusterIssuerList(in *v1.Cl } // Convert_v1_ClusterIssuerList_To_certmanager_ClusterIssuerList is an autogenerated conversion function. -func Convert_v1_ClusterIssuerList_To_certmanager_ClusterIssuerList(in *v1.ClusterIssuerList, out *certmanager.ClusterIssuerList, s conversion.Scope) error { +func Convert_v1_ClusterIssuerList_To_certmanager_ClusterIssuerList(in *certmanagerv1.ClusterIssuerList, out *certmanager.ClusterIssuerList, s conversion.Scope) error { return autoConvert_v1_ClusterIssuerList_To_certmanager_ClusterIssuerList(in, out, s) } -func autoConvert_certmanager_ClusterIssuerList_To_v1_ClusterIssuerList(in *certmanager.ClusterIssuerList, out *v1.ClusterIssuerList, s conversion.Scope) error { +func autoConvert_certmanager_ClusterIssuerList_To_v1_ClusterIssuerList(in *certmanager.ClusterIssuerList, out *certmanagerv1.ClusterIssuerList, s conversion.Scope) error { out.ListMeta = in.ListMeta if in.Items != nil { in, out := &in.Items, &out.Items - *out = make([]v1.ClusterIssuer, len(*in)) + *out = make([]certmanagerv1.ClusterIssuer, len(*in)) for i := range *in { if err := Convert_certmanager_ClusterIssuer_To_v1_ClusterIssuer(&(*in)[i], &(*out)[i], s); err != nil { return err @@ -975,11 +1045,11 @@ func autoConvert_certmanager_ClusterIssuerList_To_v1_ClusterIssuerList(in *certm } // Convert_certmanager_ClusterIssuerList_To_v1_ClusterIssuerList is an autogenerated conversion function. -func Convert_certmanager_ClusterIssuerList_To_v1_ClusterIssuerList(in *certmanager.ClusterIssuerList, out *v1.ClusterIssuerList, s conversion.Scope) error { +func Convert_certmanager_ClusterIssuerList_To_v1_ClusterIssuerList(in *certmanager.ClusterIssuerList, out *certmanagerv1.ClusterIssuerList, s conversion.Scope) error { return autoConvert_certmanager_ClusterIssuerList_To_v1_ClusterIssuerList(in, out, s) } -func autoConvert_v1_Issuer_To_certmanager_Issuer(in *v1.Issuer, out *certmanager.Issuer, s conversion.Scope) error { +func autoConvert_v1_Issuer_To_certmanager_Issuer(in *certmanagerv1.Issuer, out *certmanager.Issuer, s conversion.Scope) error { out.ObjectMeta = in.ObjectMeta if err := Convert_v1_IssuerSpec_To_certmanager_IssuerSpec(&in.Spec, &out.Spec, s); err != nil { return err @@ -991,11 +1061,11 @@ func autoConvert_v1_Issuer_To_certmanager_Issuer(in *v1.Issuer, out *certmanager } // Convert_v1_Issuer_To_certmanager_Issuer is an autogenerated conversion function. -func Convert_v1_Issuer_To_certmanager_Issuer(in *v1.Issuer, out *certmanager.Issuer, s conversion.Scope) error { +func Convert_v1_Issuer_To_certmanager_Issuer(in *certmanagerv1.Issuer, out *certmanager.Issuer, s conversion.Scope) error { return autoConvert_v1_Issuer_To_certmanager_Issuer(in, out, s) } -func autoConvert_certmanager_Issuer_To_v1_Issuer(in *certmanager.Issuer, out *v1.Issuer, s conversion.Scope) error { +func autoConvert_certmanager_Issuer_To_v1_Issuer(in *certmanager.Issuer, out *certmanagerv1.Issuer, s conversion.Scope) error { out.ObjectMeta = in.ObjectMeta if err := Convert_certmanager_IssuerSpec_To_v1_IssuerSpec(&in.Spec, &out.Spec, s); err != nil { return err @@ -1007,11 +1077,11 @@ func autoConvert_certmanager_Issuer_To_v1_Issuer(in *certmanager.Issuer, out *v1 } // Convert_certmanager_Issuer_To_v1_Issuer is an autogenerated conversion function. -func Convert_certmanager_Issuer_To_v1_Issuer(in *certmanager.Issuer, out *v1.Issuer, s conversion.Scope) error { +func Convert_certmanager_Issuer_To_v1_Issuer(in *certmanager.Issuer, out *certmanagerv1.Issuer, s conversion.Scope) error { return autoConvert_certmanager_Issuer_To_v1_Issuer(in, out, s) } -func autoConvert_v1_IssuerCondition_To_certmanager_IssuerCondition(in *v1.IssuerCondition, out *certmanager.IssuerCondition, s conversion.Scope) error { +func autoConvert_v1_IssuerCondition_To_certmanager_IssuerCondition(in *certmanagerv1.IssuerCondition, out *certmanager.IssuerCondition, s conversion.Scope) error { out.Type = certmanager.IssuerConditionType(in.Type) out.Status = meta.ConditionStatus(in.Status) out.LastTransitionTime = (*metav1.Time)(unsafe.Pointer(in.LastTransitionTime)) @@ -1022,12 +1092,12 @@ func autoConvert_v1_IssuerCondition_To_certmanager_IssuerCondition(in *v1.Issuer } // Convert_v1_IssuerCondition_To_certmanager_IssuerCondition is an autogenerated conversion function. -func Convert_v1_IssuerCondition_To_certmanager_IssuerCondition(in *v1.IssuerCondition, out *certmanager.IssuerCondition, s conversion.Scope) error { +func Convert_v1_IssuerCondition_To_certmanager_IssuerCondition(in *certmanagerv1.IssuerCondition, out *certmanager.IssuerCondition, s conversion.Scope) error { return autoConvert_v1_IssuerCondition_To_certmanager_IssuerCondition(in, out, s) } -func autoConvert_certmanager_IssuerCondition_To_v1_IssuerCondition(in *certmanager.IssuerCondition, out *v1.IssuerCondition, s conversion.Scope) error { - out.Type = v1.IssuerConditionType(in.Type) +func autoConvert_certmanager_IssuerCondition_To_v1_IssuerCondition(in *certmanager.IssuerCondition, out *certmanagerv1.IssuerCondition, s conversion.Scope) error { + out.Type = certmanagerv1.IssuerConditionType(in.Type) out.Status = apismetav1.ConditionStatus(in.Status) out.LastTransitionTime = (*metav1.Time)(unsafe.Pointer(in.LastTransitionTime)) out.Reason = in.Reason @@ -1037,11 +1107,11 @@ func autoConvert_certmanager_IssuerCondition_To_v1_IssuerCondition(in *certmanag } // Convert_certmanager_IssuerCondition_To_v1_IssuerCondition is an autogenerated conversion function. -func Convert_certmanager_IssuerCondition_To_v1_IssuerCondition(in *certmanager.IssuerCondition, out *v1.IssuerCondition, s conversion.Scope) error { +func Convert_certmanager_IssuerCondition_To_v1_IssuerCondition(in *certmanager.IssuerCondition, out *certmanagerv1.IssuerCondition, s conversion.Scope) error { return autoConvert_certmanager_IssuerCondition_To_v1_IssuerCondition(in, out, s) } -func autoConvert_v1_IssuerConfig_To_certmanager_IssuerConfig(in *v1.IssuerConfig, out *certmanager.IssuerConfig, s conversion.Scope) error { +func autoConvert_v1_IssuerConfig_To_certmanager_IssuerConfig(in *certmanagerv1.IssuerConfig, out *certmanager.IssuerConfig, s conversion.Scope) error { if in.ACME != nil { in, out := &in.ACME, &out.ACME *out = new(acme.ACMEIssuer) @@ -1075,11 +1145,11 @@ func autoConvert_v1_IssuerConfig_To_certmanager_IssuerConfig(in *v1.IssuerConfig } // Convert_v1_IssuerConfig_To_certmanager_IssuerConfig is an autogenerated conversion function. -func Convert_v1_IssuerConfig_To_certmanager_IssuerConfig(in *v1.IssuerConfig, out *certmanager.IssuerConfig, s conversion.Scope) error { +func Convert_v1_IssuerConfig_To_certmanager_IssuerConfig(in *certmanagerv1.IssuerConfig, out *certmanager.IssuerConfig, s conversion.Scope) error { return autoConvert_v1_IssuerConfig_To_certmanager_IssuerConfig(in, out, s) } -func autoConvert_certmanager_IssuerConfig_To_v1_IssuerConfig(in *certmanager.IssuerConfig, out *v1.IssuerConfig, s conversion.Scope) error { +func autoConvert_certmanager_IssuerConfig_To_v1_IssuerConfig(in *certmanager.IssuerConfig, out *certmanagerv1.IssuerConfig, s conversion.Scope) error { if in.ACME != nil { in, out := &in.ACME, &out.ACME *out = new(apisacmev1.ACMEIssuer) @@ -1089,20 +1159,20 @@ func autoConvert_certmanager_IssuerConfig_To_v1_IssuerConfig(in *certmanager.Iss } else { out.ACME = nil } - out.CA = (*v1.CAIssuer)(unsafe.Pointer(in.CA)) + out.CA = (*certmanagerv1.CAIssuer)(unsafe.Pointer(in.CA)) if in.Vault != nil { in, out := &in.Vault, &out.Vault - *out = new(v1.VaultIssuer) + *out = new(certmanagerv1.VaultIssuer) if err := Convert_certmanager_VaultIssuer_To_v1_VaultIssuer(*in, *out, s); err != nil { return err } } else { out.Vault = nil } - out.SelfSigned = (*v1.SelfSignedIssuer)(unsafe.Pointer(in.SelfSigned)) + out.SelfSigned = (*certmanagerv1.SelfSignedIssuer)(unsafe.Pointer(in.SelfSigned)) if in.Venafi != nil { in, out := &in.Venafi, &out.Venafi - *out = new(v1.VenafiIssuer) + *out = new(certmanagerv1.VenafiIssuer) if err := Convert_certmanager_VenafiIssuer_To_v1_VenafiIssuer(*in, *out, s); err != nil { return err } @@ -1113,11 +1183,11 @@ func autoConvert_certmanager_IssuerConfig_To_v1_IssuerConfig(in *certmanager.Iss } // Convert_certmanager_IssuerConfig_To_v1_IssuerConfig is an autogenerated conversion function. -func Convert_certmanager_IssuerConfig_To_v1_IssuerConfig(in *certmanager.IssuerConfig, out *v1.IssuerConfig, s conversion.Scope) error { +func Convert_certmanager_IssuerConfig_To_v1_IssuerConfig(in *certmanager.IssuerConfig, out *certmanagerv1.IssuerConfig, s conversion.Scope) error { return autoConvert_certmanager_IssuerConfig_To_v1_IssuerConfig(in, out, s) } -func autoConvert_v1_IssuerList_To_certmanager_IssuerList(in *v1.IssuerList, out *certmanager.IssuerList, s conversion.Scope) error { +func autoConvert_v1_IssuerList_To_certmanager_IssuerList(in *certmanagerv1.IssuerList, out *certmanager.IssuerList, s conversion.Scope) error { out.ListMeta = in.ListMeta if in.Items != nil { in, out := &in.Items, &out.Items @@ -1134,15 +1204,15 @@ func autoConvert_v1_IssuerList_To_certmanager_IssuerList(in *v1.IssuerList, out } // Convert_v1_IssuerList_To_certmanager_IssuerList is an autogenerated conversion function. -func Convert_v1_IssuerList_To_certmanager_IssuerList(in *v1.IssuerList, out *certmanager.IssuerList, s conversion.Scope) error { +func Convert_v1_IssuerList_To_certmanager_IssuerList(in *certmanagerv1.IssuerList, out *certmanager.IssuerList, s conversion.Scope) error { return autoConvert_v1_IssuerList_To_certmanager_IssuerList(in, out, s) } -func autoConvert_certmanager_IssuerList_To_v1_IssuerList(in *certmanager.IssuerList, out *v1.IssuerList, s conversion.Scope) error { +func autoConvert_certmanager_IssuerList_To_v1_IssuerList(in *certmanager.IssuerList, out *certmanagerv1.IssuerList, s conversion.Scope) error { out.ListMeta = in.ListMeta if in.Items != nil { in, out := &in.Items, &out.Items - *out = make([]v1.Issuer, len(*in)) + *out = make([]certmanagerv1.Issuer, len(*in)) for i := range *in { if err := Convert_certmanager_Issuer_To_v1_Issuer(&(*in)[i], &(*out)[i], s); err != nil { return err @@ -1155,11 +1225,11 @@ func autoConvert_certmanager_IssuerList_To_v1_IssuerList(in *certmanager.IssuerL } // Convert_certmanager_IssuerList_To_v1_IssuerList is an autogenerated conversion function. -func Convert_certmanager_IssuerList_To_v1_IssuerList(in *certmanager.IssuerList, out *v1.IssuerList, s conversion.Scope) error { +func Convert_certmanager_IssuerList_To_v1_IssuerList(in *certmanager.IssuerList, out *certmanagerv1.IssuerList, s conversion.Scope) error { return autoConvert_certmanager_IssuerList_To_v1_IssuerList(in, out, s) } -func autoConvert_v1_IssuerSpec_To_certmanager_IssuerSpec(in *v1.IssuerSpec, out *certmanager.IssuerSpec, s conversion.Scope) error { +func autoConvert_v1_IssuerSpec_To_certmanager_IssuerSpec(in *certmanagerv1.IssuerSpec, out *certmanager.IssuerSpec, s conversion.Scope) error { if err := Convert_v1_IssuerConfig_To_certmanager_IssuerConfig(&in.IssuerConfig, &out.IssuerConfig, s); err != nil { return err } @@ -1167,11 +1237,11 @@ func autoConvert_v1_IssuerSpec_To_certmanager_IssuerSpec(in *v1.IssuerSpec, out } // Convert_v1_IssuerSpec_To_certmanager_IssuerSpec is an autogenerated conversion function. -func Convert_v1_IssuerSpec_To_certmanager_IssuerSpec(in *v1.IssuerSpec, out *certmanager.IssuerSpec, s conversion.Scope) error { +func Convert_v1_IssuerSpec_To_certmanager_IssuerSpec(in *certmanagerv1.IssuerSpec, out *certmanager.IssuerSpec, s conversion.Scope) error { return autoConvert_v1_IssuerSpec_To_certmanager_IssuerSpec(in, out, s) } -func autoConvert_certmanager_IssuerSpec_To_v1_IssuerSpec(in *certmanager.IssuerSpec, out *v1.IssuerSpec, s conversion.Scope) error { +func autoConvert_certmanager_IssuerSpec_To_v1_IssuerSpec(in *certmanager.IssuerSpec, out *certmanagerv1.IssuerSpec, s conversion.Scope) error { if err := Convert_certmanager_IssuerConfig_To_v1_IssuerConfig(&in.IssuerConfig, &out.IssuerConfig, s); err != nil { return err } @@ -1179,105 +1249,207 @@ func autoConvert_certmanager_IssuerSpec_To_v1_IssuerSpec(in *certmanager.IssuerS } // Convert_certmanager_IssuerSpec_To_v1_IssuerSpec is an autogenerated conversion function. -func Convert_certmanager_IssuerSpec_To_v1_IssuerSpec(in *certmanager.IssuerSpec, out *v1.IssuerSpec, s conversion.Scope) error { +func Convert_certmanager_IssuerSpec_To_v1_IssuerSpec(in *certmanager.IssuerSpec, out *certmanagerv1.IssuerSpec, s conversion.Scope) error { return autoConvert_certmanager_IssuerSpec_To_v1_IssuerSpec(in, out, s) } -func autoConvert_v1_IssuerStatus_To_certmanager_IssuerStatus(in *v1.IssuerStatus, out *certmanager.IssuerStatus, s conversion.Scope) error { +func autoConvert_v1_IssuerStatus_To_certmanager_IssuerStatus(in *certmanagerv1.IssuerStatus, out *certmanager.IssuerStatus, s conversion.Scope) error { out.Conditions = *(*[]certmanager.IssuerCondition)(unsafe.Pointer(&in.Conditions)) out.ACME = (*acme.ACMEIssuerStatus)(unsafe.Pointer(in.ACME)) return nil } // Convert_v1_IssuerStatus_To_certmanager_IssuerStatus is an autogenerated conversion function. -func Convert_v1_IssuerStatus_To_certmanager_IssuerStatus(in *v1.IssuerStatus, out *certmanager.IssuerStatus, s conversion.Scope) error { +func Convert_v1_IssuerStatus_To_certmanager_IssuerStatus(in *certmanagerv1.IssuerStatus, out *certmanager.IssuerStatus, s conversion.Scope) error { return autoConvert_v1_IssuerStatus_To_certmanager_IssuerStatus(in, out, s) } -func autoConvert_certmanager_IssuerStatus_To_v1_IssuerStatus(in *certmanager.IssuerStatus, out *v1.IssuerStatus, s conversion.Scope) error { - out.Conditions = *(*[]v1.IssuerCondition)(unsafe.Pointer(&in.Conditions)) +func autoConvert_certmanager_IssuerStatus_To_v1_IssuerStatus(in *certmanager.IssuerStatus, out *certmanagerv1.IssuerStatus, s conversion.Scope) error { + out.Conditions = *(*[]certmanagerv1.IssuerCondition)(unsafe.Pointer(&in.Conditions)) out.ACME = (*apisacmev1.ACMEIssuerStatus)(unsafe.Pointer(in.ACME)) return nil } // Convert_certmanager_IssuerStatus_To_v1_IssuerStatus is an autogenerated conversion function. -func Convert_certmanager_IssuerStatus_To_v1_IssuerStatus(in *certmanager.IssuerStatus, out *v1.IssuerStatus, s conversion.Scope) error { +func Convert_certmanager_IssuerStatus_To_v1_IssuerStatus(in *certmanager.IssuerStatus, out *certmanagerv1.IssuerStatus, s conversion.Scope) error { return autoConvert_certmanager_IssuerStatus_To_v1_IssuerStatus(in, out, s) } -func autoConvert_v1_JKSKeystore_To_certmanager_JKSKeystore(in *v1.JKSKeystore, out *certmanager.JKSKeystore, s conversion.Scope) error { +func autoConvert_v1_JKSKeystore_To_certmanager_JKSKeystore(in *certmanagerv1.JKSKeystore, out *certmanager.JKSKeystore, s conversion.Scope) error { out.Create = in.Create + out.Alias = (*string)(unsafe.Pointer(in.Alias)) if err := internalapismetav1.Convert_v1_SecretKeySelector_To_meta_SecretKeySelector(&in.PasswordSecretRef, &out.PasswordSecretRef, s); err != nil { return err } + out.Password = (*string)(unsafe.Pointer(in.Password)) return nil } // Convert_v1_JKSKeystore_To_certmanager_JKSKeystore is an autogenerated conversion function. -func Convert_v1_JKSKeystore_To_certmanager_JKSKeystore(in *v1.JKSKeystore, out *certmanager.JKSKeystore, s conversion.Scope) error { +func Convert_v1_JKSKeystore_To_certmanager_JKSKeystore(in *certmanagerv1.JKSKeystore, out *certmanager.JKSKeystore, s conversion.Scope) error { return autoConvert_v1_JKSKeystore_To_certmanager_JKSKeystore(in, out, s) } -func autoConvert_certmanager_JKSKeystore_To_v1_JKSKeystore(in *certmanager.JKSKeystore, out *v1.JKSKeystore, s conversion.Scope) error { +func autoConvert_certmanager_JKSKeystore_To_v1_JKSKeystore(in *certmanager.JKSKeystore, out *certmanagerv1.JKSKeystore, s conversion.Scope) error { out.Create = in.Create + out.Alias = (*string)(unsafe.Pointer(in.Alias)) if err := internalapismetav1.Convert_meta_SecretKeySelector_To_v1_SecretKeySelector(&in.PasswordSecretRef, &out.PasswordSecretRef, s); err != nil { return err } + out.Password = (*string)(unsafe.Pointer(in.Password)) return nil } // Convert_certmanager_JKSKeystore_To_v1_JKSKeystore is an autogenerated conversion function. -func Convert_certmanager_JKSKeystore_To_v1_JKSKeystore(in *certmanager.JKSKeystore, out *v1.JKSKeystore, s conversion.Scope) error { +func Convert_certmanager_JKSKeystore_To_v1_JKSKeystore(in *certmanager.JKSKeystore, out *certmanagerv1.JKSKeystore, s conversion.Scope) error { return autoConvert_certmanager_JKSKeystore_To_v1_JKSKeystore(in, out, s) } -func autoConvert_v1_PKCS12Keystore_To_certmanager_PKCS12Keystore(in *v1.PKCS12Keystore, out *certmanager.PKCS12Keystore, s conversion.Scope) error { +func autoConvert_v1_NameConstraintItem_To_certmanager_NameConstraintItem(in *certmanagerv1.NameConstraintItem, out *certmanager.NameConstraintItem, s conversion.Scope) error { + out.DNSDomains = *(*[]string)(unsafe.Pointer(&in.DNSDomains)) + out.IPRanges = *(*[]string)(unsafe.Pointer(&in.IPRanges)) + out.EmailAddresses = *(*[]string)(unsafe.Pointer(&in.EmailAddresses)) + out.URIDomains = *(*[]string)(unsafe.Pointer(&in.URIDomains)) + return nil +} + +// Convert_v1_NameConstraintItem_To_certmanager_NameConstraintItem is an autogenerated conversion function. +func Convert_v1_NameConstraintItem_To_certmanager_NameConstraintItem(in *certmanagerv1.NameConstraintItem, out *certmanager.NameConstraintItem, s conversion.Scope) error { + return autoConvert_v1_NameConstraintItem_To_certmanager_NameConstraintItem(in, out, s) +} + +func autoConvert_certmanager_NameConstraintItem_To_v1_NameConstraintItem(in *certmanager.NameConstraintItem, out *certmanagerv1.NameConstraintItem, s conversion.Scope) error { + out.DNSDomains = *(*[]string)(unsafe.Pointer(&in.DNSDomains)) + out.IPRanges = *(*[]string)(unsafe.Pointer(&in.IPRanges)) + out.EmailAddresses = *(*[]string)(unsafe.Pointer(&in.EmailAddresses)) + out.URIDomains = *(*[]string)(unsafe.Pointer(&in.URIDomains)) + return nil +} + +// Convert_certmanager_NameConstraintItem_To_v1_NameConstraintItem is an autogenerated conversion function. +func Convert_certmanager_NameConstraintItem_To_v1_NameConstraintItem(in *certmanager.NameConstraintItem, out *certmanagerv1.NameConstraintItem, s conversion.Scope) error { + return autoConvert_certmanager_NameConstraintItem_To_v1_NameConstraintItem(in, out, s) +} + +func autoConvert_v1_NameConstraints_To_certmanager_NameConstraints(in *certmanagerv1.NameConstraints, out *certmanager.NameConstraints, s conversion.Scope) error { + out.Critical = in.Critical + out.Permitted = (*certmanager.NameConstraintItem)(unsafe.Pointer(in.Permitted)) + out.Excluded = (*certmanager.NameConstraintItem)(unsafe.Pointer(in.Excluded)) + return nil +} + +// Convert_v1_NameConstraints_To_certmanager_NameConstraints is an autogenerated conversion function. +func Convert_v1_NameConstraints_To_certmanager_NameConstraints(in *certmanagerv1.NameConstraints, out *certmanager.NameConstraints, s conversion.Scope) error { + return autoConvert_v1_NameConstraints_To_certmanager_NameConstraints(in, out, s) +} + +func autoConvert_certmanager_NameConstraints_To_v1_NameConstraints(in *certmanager.NameConstraints, out *certmanagerv1.NameConstraints, s conversion.Scope) error { + out.Critical = in.Critical + out.Permitted = (*certmanagerv1.NameConstraintItem)(unsafe.Pointer(in.Permitted)) + out.Excluded = (*certmanagerv1.NameConstraintItem)(unsafe.Pointer(in.Excluded)) + return nil +} + +// Convert_certmanager_NameConstraints_To_v1_NameConstraints is an autogenerated conversion function. +func Convert_certmanager_NameConstraints_To_v1_NameConstraints(in *certmanager.NameConstraints, out *certmanagerv1.NameConstraints, s conversion.Scope) error { + return autoConvert_certmanager_NameConstraints_To_v1_NameConstraints(in, out, s) +} + +func autoConvert_v1_OtherName_To_certmanager_OtherName(in *certmanagerv1.OtherName, out *certmanager.OtherName, s conversion.Scope) error { + out.OID = in.OID + out.UTF8Value = in.UTF8Value + return nil +} + +// Convert_v1_OtherName_To_certmanager_OtherName is an autogenerated conversion function. +func Convert_v1_OtherName_To_certmanager_OtherName(in *certmanagerv1.OtherName, out *certmanager.OtherName, s conversion.Scope) error { + return autoConvert_v1_OtherName_To_certmanager_OtherName(in, out, s) +} + +func autoConvert_certmanager_OtherName_To_v1_OtherName(in *certmanager.OtherName, out *certmanagerv1.OtherName, s conversion.Scope) error { + out.OID = in.OID + out.UTF8Value = in.UTF8Value + return nil +} + +// Convert_certmanager_OtherName_To_v1_OtherName is an autogenerated conversion function. +func Convert_certmanager_OtherName_To_v1_OtherName(in *certmanager.OtherName, out *certmanagerv1.OtherName, s conversion.Scope) error { + return autoConvert_certmanager_OtherName_To_v1_OtherName(in, out, s) +} + +func autoConvert_v1_PKCS12Keystore_To_certmanager_PKCS12Keystore(in *certmanagerv1.PKCS12Keystore, out *certmanager.PKCS12Keystore, s conversion.Scope) error { out.Create = in.Create + out.Profile = certmanager.PKCS12Profile(in.Profile) if err := internalapismetav1.Convert_v1_SecretKeySelector_To_meta_SecretKeySelector(&in.PasswordSecretRef, &out.PasswordSecretRef, s); err != nil { return err } + out.Password = (*string)(unsafe.Pointer(in.Password)) return nil } // Convert_v1_PKCS12Keystore_To_certmanager_PKCS12Keystore is an autogenerated conversion function. -func Convert_v1_PKCS12Keystore_To_certmanager_PKCS12Keystore(in *v1.PKCS12Keystore, out *certmanager.PKCS12Keystore, s conversion.Scope) error { +func Convert_v1_PKCS12Keystore_To_certmanager_PKCS12Keystore(in *certmanagerv1.PKCS12Keystore, out *certmanager.PKCS12Keystore, s conversion.Scope) error { return autoConvert_v1_PKCS12Keystore_To_certmanager_PKCS12Keystore(in, out, s) } -func autoConvert_certmanager_PKCS12Keystore_To_v1_PKCS12Keystore(in *certmanager.PKCS12Keystore, out *v1.PKCS12Keystore, s conversion.Scope) error { +func autoConvert_certmanager_PKCS12Keystore_To_v1_PKCS12Keystore(in *certmanager.PKCS12Keystore, out *certmanagerv1.PKCS12Keystore, s conversion.Scope) error { out.Create = in.Create + out.Profile = certmanagerv1.PKCS12Profile(in.Profile) if err := internalapismetav1.Convert_meta_SecretKeySelector_To_v1_SecretKeySelector(&in.PasswordSecretRef, &out.PasswordSecretRef, s); err != nil { return err } + out.Password = (*string)(unsafe.Pointer(in.Password)) return nil } // Convert_certmanager_PKCS12Keystore_To_v1_PKCS12Keystore is an autogenerated conversion function. -func Convert_certmanager_PKCS12Keystore_To_v1_PKCS12Keystore(in *certmanager.PKCS12Keystore, out *v1.PKCS12Keystore, s conversion.Scope) error { +func Convert_certmanager_PKCS12Keystore_To_v1_PKCS12Keystore(in *certmanager.PKCS12Keystore, out *certmanagerv1.PKCS12Keystore, s conversion.Scope) error { return autoConvert_certmanager_PKCS12Keystore_To_v1_PKCS12Keystore(in, out, s) } -func autoConvert_v1_SelfSignedIssuer_To_certmanager_SelfSignedIssuer(in *v1.SelfSignedIssuer, out *certmanager.SelfSignedIssuer, s conversion.Scope) error { +func autoConvert_v1_SelfSignedIssuer_To_certmanager_SelfSignedIssuer(in *certmanagerv1.SelfSignedIssuer, out *certmanager.SelfSignedIssuer, s conversion.Scope) error { out.CRLDistributionPoints = *(*[]string)(unsafe.Pointer(&in.CRLDistributionPoints)) return nil } // Convert_v1_SelfSignedIssuer_To_certmanager_SelfSignedIssuer is an autogenerated conversion function. -func Convert_v1_SelfSignedIssuer_To_certmanager_SelfSignedIssuer(in *v1.SelfSignedIssuer, out *certmanager.SelfSignedIssuer, s conversion.Scope) error { +func Convert_v1_SelfSignedIssuer_To_certmanager_SelfSignedIssuer(in *certmanagerv1.SelfSignedIssuer, out *certmanager.SelfSignedIssuer, s conversion.Scope) error { return autoConvert_v1_SelfSignedIssuer_To_certmanager_SelfSignedIssuer(in, out, s) } -func autoConvert_certmanager_SelfSignedIssuer_To_v1_SelfSignedIssuer(in *certmanager.SelfSignedIssuer, out *v1.SelfSignedIssuer, s conversion.Scope) error { +func autoConvert_certmanager_SelfSignedIssuer_To_v1_SelfSignedIssuer(in *certmanager.SelfSignedIssuer, out *certmanagerv1.SelfSignedIssuer, s conversion.Scope) error { out.CRLDistributionPoints = *(*[]string)(unsafe.Pointer(&in.CRLDistributionPoints)) return nil } // Convert_certmanager_SelfSignedIssuer_To_v1_SelfSignedIssuer is an autogenerated conversion function. -func Convert_certmanager_SelfSignedIssuer_To_v1_SelfSignedIssuer(in *certmanager.SelfSignedIssuer, out *v1.SelfSignedIssuer, s conversion.Scope) error { +func Convert_certmanager_SelfSignedIssuer_To_v1_SelfSignedIssuer(in *certmanager.SelfSignedIssuer, out *certmanagerv1.SelfSignedIssuer, s conversion.Scope) error { return autoConvert_certmanager_SelfSignedIssuer_To_v1_SelfSignedIssuer(in, out, s) } -func autoConvert_v1_VaultAppRole_To_certmanager_VaultAppRole(in *v1.VaultAppRole, out *certmanager.VaultAppRole, s conversion.Scope) error { +func autoConvert_v1_ServiceAccountRef_To_certmanager_ServiceAccountRef(in *certmanagerv1.ServiceAccountRef, out *certmanager.ServiceAccountRef, s conversion.Scope) error { + out.Name = in.Name + out.TokenAudiences = *(*[]string)(unsafe.Pointer(&in.TokenAudiences)) + return nil +} + +// Convert_v1_ServiceAccountRef_To_certmanager_ServiceAccountRef is an autogenerated conversion function. +func Convert_v1_ServiceAccountRef_To_certmanager_ServiceAccountRef(in *certmanagerv1.ServiceAccountRef, out *certmanager.ServiceAccountRef, s conversion.Scope) error { + return autoConvert_v1_ServiceAccountRef_To_certmanager_ServiceAccountRef(in, out, s) +} + +func autoConvert_certmanager_ServiceAccountRef_To_v1_ServiceAccountRef(in *certmanager.ServiceAccountRef, out *certmanagerv1.ServiceAccountRef, s conversion.Scope) error { + out.Name = in.Name + out.TokenAudiences = *(*[]string)(unsafe.Pointer(&in.TokenAudiences)) + return nil +} + +// Convert_certmanager_ServiceAccountRef_To_v1_ServiceAccountRef is an autogenerated conversion function. +func Convert_certmanager_ServiceAccountRef_To_v1_ServiceAccountRef(in *certmanager.ServiceAccountRef, out *certmanagerv1.ServiceAccountRef, s conversion.Scope) error { + return autoConvert_certmanager_ServiceAccountRef_To_v1_ServiceAccountRef(in, out, s) +} + +func autoConvert_v1_VaultAppRole_To_certmanager_VaultAppRole(in *certmanagerv1.VaultAppRole, out *certmanager.VaultAppRole, s conversion.Scope) error { out.Path = in.Path out.RoleId = in.RoleId if err := internalapismetav1.Convert_v1_SecretKeySelector_To_meta_SecretKeySelector(&in.SecretRef, &out.SecretRef, s); err != nil { @@ -1287,11 +1459,11 @@ func autoConvert_v1_VaultAppRole_To_certmanager_VaultAppRole(in *v1.VaultAppRole } // Convert_v1_VaultAppRole_To_certmanager_VaultAppRole is an autogenerated conversion function. -func Convert_v1_VaultAppRole_To_certmanager_VaultAppRole(in *v1.VaultAppRole, out *certmanager.VaultAppRole, s conversion.Scope) error { +func Convert_v1_VaultAppRole_To_certmanager_VaultAppRole(in *certmanagerv1.VaultAppRole, out *certmanager.VaultAppRole, s conversion.Scope) error { return autoConvert_v1_VaultAppRole_To_certmanager_VaultAppRole(in, out, s) } -func autoConvert_certmanager_VaultAppRole_To_v1_VaultAppRole(in *certmanager.VaultAppRole, out *v1.VaultAppRole, s conversion.Scope) error { +func autoConvert_certmanager_VaultAppRole_To_v1_VaultAppRole(in *certmanager.VaultAppRole, out *certmanagerv1.VaultAppRole, s conversion.Scope) error { out.Path = in.Path out.RoleId = in.RoleId if err := internalapismetav1.Convert_meta_SecretKeySelector_To_v1_SecretKeySelector(&in.SecretRef, &out.SecretRef, s); err != nil { @@ -1301,11 +1473,11 @@ func autoConvert_certmanager_VaultAppRole_To_v1_VaultAppRole(in *certmanager.Vau } // Convert_certmanager_VaultAppRole_To_v1_VaultAppRole is an autogenerated conversion function. -func Convert_certmanager_VaultAppRole_To_v1_VaultAppRole(in *certmanager.VaultAppRole, out *v1.VaultAppRole, s conversion.Scope) error { +func Convert_certmanager_VaultAppRole_To_v1_VaultAppRole(in *certmanager.VaultAppRole, out *certmanagerv1.VaultAppRole, s conversion.Scope) error { return autoConvert_certmanager_VaultAppRole_To_v1_VaultAppRole(in, out, s) } -func autoConvert_v1_VaultAuth_To_certmanager_VaultAuth(in *v1.VaultAuth, out *certmanager.VaultAuth, s conversion.Scope) error { +func autoConvert_v1_VaultAuth_To_certmanager_VaultAuth(in *certmanagerv1.VaultAuth, out *certmanager.VaultAuth, s conversion.Scope) error { if in.TokenSecretRef != nil { in, out := &in.TokenSecretRef, &out.TokenSecretRef *out = new(meta.SecretKeySelector) @@ -1324,6 +1496,7 @@ func autoConvert_v1_VaultAuth_To_certmanager_VaultAuth(in *v1.VaultAuth, out *ce } else { out.AppRole = nil } + out.ClientCertificate = (*certmanager.VaultClientCertificateAuth)(unsafe.Pointer(in.ClientCertificate)) if in.Kubernetes != nil { in, out := &in.Kubernetes, &out.Kubernetes *out = new(certmanager.VaultKubernetesAuth) @@ -1337,11 +1510,11 @@ func autoConvert_v1_VaultAuth_To_certmanager_VaultAuth(in *v1.VaultAuth, out *ce } // Convert_v1_VaultAuth_To_certmanager_VaultAuth is an autogenerated conversion function. -func Convert_v1_VaultAuth_To_certmanager_VaultAuth(in *v1.VaultAuth, out *certmanager.VaultAuth, s conversion.Scope) error { +func Convert_v1_VaultAuth_To_certmanager_VaultAuth(in *certmanagerv1.VaultAuth, out *certmanager.VaultAuth, s conversion.Scope) error { return autoConvert_v1_VaultAuth_To_certmanager_VaultAuth(in, out, s) } -func autoConvert_certmanager_VaultAuth_To_v1_VaultAuth(in *certmanager.VaultAuth, out *v1.VaultAuth, s conversion.Scope) error { +func autoConvert_certmanager_VaultAuth_To_v1_VaultAuth(in *certmanager.VaultAuth, out *certmanagerv1.VaultAuth, s conversion.Scope) error { if in.TokenSecretRef != nil { in, out := &in.TokenSecretRef, &out.TokenSecretRef *out = new(apismetav1.SecretKeySelector) @@ -1353,16 +1526,17 @@ func autoConvert_certmanager_VaultAuth_To_v1_VaultAuth(in *certmanager.VaultAuth } if in.AppRole != nil { in, out := &in.AppRole, &out.AppRole - *out = new(v1.VaultAppRole) + *out = new(certmanagerv1.VaultAppRole) if err := Convert_certmanager_VaultAppRole_To_v1_VaultAppRole(*in, *out, s); err != nil { return err } } else { out.AppRole = nil } + out.ClientCertificate = (*certmanagerv1.VaultClientCertificateAuth)(unsafe.Pointer(in.ClientCertificate)) if in.Kubernetes != nil { in, out := &in.Kubernetes, &out.Kubernetes - *out = new(v1.VaultKubernetesAuth) + *out = new(certmanagerv1.VaultKubernetesAuth) if err := Convert_certmanager_VaultKubernetesAuth_To_v1_VaultKubernetesAuth(*in, *out, s); err != nil { return err } @@ -1373,15 +1547,40 @@ func autoConvert_certmanager_VaultAuth_To_v1_VaultAuth(in *certmanager.VaultAuth } // Convert_certmanager_VaultAuth_To_v1_VaultAuth is an autogenerated conversion function. -func Convert_certmanager_VaultAuth_To_v1_VaultAuth(in *certmanager.VaultAuth, out *v1.VaultAuth, s conversion.Scope) error { +func Convert_certmanager_VaultAuth_To_v1_VaultAuth(in *certmanager.VaultAuth, out *certmanagerv1.VaultAuth, s conversion.Scope) error { return autoConvert_certmanager_VaultAuth_To_v1_VaultAuth(in, out, s) } -func autoConvert_v1_VaultIssuer_To_certmanager_VaultIssuer(in *v1.VaultIssuer, out *certmanager.VaultIssuer, s conversion.Scope) error { +func autoConvert_v1_VaultClientCertificateAuth_To_certmanager_VaultClientCertificateAuth(in *certmanagerv1.VaultClientCertificateAuth, out *certmanager.VaultClientCertificateAuth, s conversion.Scope) error { + out.Path = in.Path + out.SecretName = in.SecretName + out.Name = in.Name + return nil +} + +// Convert_v1_VaultClientCertificateAuth_To_certmanager_VaultClientCertificateAuth is an autogenerated conversion function. +func Convert_v1_VaultClientCertificateAuth_To_certmanager_VaultClientCertificateAuth(in *certmanagerv1.VaultClientCertificateAuth, out *certmanager.VaultClientCertificateAuth, s conversion.Scope) error { + return autoConvert_v1_VaultClientCertificateAuth_To_certmanager_VaultClientCertificateAuth(in, out, s) +} + +func autoConvert_certmanager_VaultClientCertificateAuth_To_v1_VaultClientCertificateAuth(in *certmanager.VaultClientCertificateAuth, out *certmanagerv1.VaultClientCertificateAuth, s conversion.Scope) error { + out.Path = in.Path + out.SecretName = in.SecretName + out.Name = in.Name + return nil +} + +// Convert_certmanager_VaultClientCertificateAuth_To_v1_VaultClientCertificateAuth is an autogenerated conversion function. +func Convert_certmanager_VaultClientCertificateAuth_To_v1_VaultClientCertificateAuth(in *certmanager.VaultClientCertificateAuth, out *certmanagerv1.VaultClientCertificateAuth, s conversion.Scope) error { + return autoConvert_certmanager_VaultClientCertificateAuth_To_v1_VaultClientCertificateAuth(in, out, s) +} + +func autoConvert_v1_VaultIssuer_To_certmanager_VaultIssuer(in *certmanagerv1.VaultIssuer, out *certmanager.VaultIssuer, s conversion.Scope) error { if err := Convert_v1_VaultAuth_To_certmanager_VaultAuth(&in.Auth, &out.Auth, s); err != nil { return err } out.Server = in.Server + out.ServerName = in.ServerName out.Path = in.Path out.Namespace = in.Namespace out.CABundle = *(*[]byte)(unsafe.Pointer(&in.CABundle)) @@ -1394,19 +1593,38 @@ func autoConvert_v1_VaultIssuer_To_certmanager_VaultIssuer(in *v1.VaultIssuer, o } else { out.CABundleSecretRef = nil } + if in.ClientCertSecretRef != nil { + in, out := &in.ClientCertSecretRef, &out.ClientCertSecretRef + *out = new(meta.SecretKeySelector) + if err := internalapismetav1.Convert_v1_SecretKeySelector_To_meta_SecretKeySelector(*in, *out, s); err != nil { + return err + } + } else { + out.ClientCertSecretRef = nil + } + if in.ClientKeySecretRef != nil { + in, out := &in.ClientKeySecretRef, &out.ClientKeySecretRef + *out = new(meta.SecretKeySelector) + if err := internalapismetav1.Convert_v1_SecretKeySelector_To_meta_SecretKeySelector(*in, *out, s); err != nil { + return err + } + } else { + out.ClientKeySecretRef = nil + } return nil } // Convert_v1_VaultIssuer_To_certmanager_VaultIssuer is an autogenerated conversion function. -func Convert_v1_VaultIssuer_To_certmanager_VaultIssuer(in *v1.VaultIssuer, out *certmanager.VaultIssuer, s conversion.Scope) error { +func Convert_v1_VaultIssuer_To_certmanager_VaultIssuer(in *certmanagerv1.VaultIssuer, out *certmanager.VaultIssuer, s conversion.Scope) error { return autoConvert_v1_VaultIssuer_To_certmanager_VaultIssuer(in, out, s) } -func autoConvert_certmanager_VaultIssuer_To_v1_VaultIssuer(in *certmanager.VaultIssuer, out *v1.VaultIssuer, s conversion.Scope) error { +func autoConvert_certmanager_VaultIssuer_To_v1_VaultIssuer(in *certmanager.VaultIssuer, out *certmanagerv1.VaultIssuer, s conversion.Scope) error { if err := Convert_certmanager_VaultAuth_To_v1_VaultAuth(&in.Auth, &out.Auth, s); err != nil { return err } out.Server = in.Server + out.ServerName = in.ServerName out.Path = in.Path out.Namespace = in.Namespace out.CABundle = *(*[]byte)(unsafe.Pointer(&in.CABundle)) @@ -1419,43 +1637,63 @@ func autoConvert_certmanager_VaultIssuer_To_v1_VaultIssuer(in *certmanager.Vault } else { out.CABundleSecretRef = nil } + if in.ClientCertSecretRef != nil { + in, out := &in.ClientCertSecretRef, &out.ClientCertSecretRef + *out = new(apismetav1.SecretKeySelector) + if err := internalapismetav1.Convert_meta_SecretKeySelector_To_v1_SecretKeySelector(*in, *out, s); err != nil { + return err + } + } else { + out.ClientCertSecretRef = nil + } + if in.ClientKeySecretRef != nil { + in, out := &in.ClientKeySecretRef, &out.ClientKeySecretRef + *out = new(apismetav1.SecretKeySelector) + if err := internalapismetav1.Convert_meta_SecretKeySelector_To_v1_SecretKeySelector(*in, *out, s); err != nil { + return err + } + } else { + out.ClientKeySecretRef = nil + } return nil } // Convert_certmanager_VaultIssuer_To_v1_VaultIssuer is an autogenerated conversion function. -func Convert_certmanager_VaultIssuer_To_v1_VaultIssuer(in *certmanager.VaultIssuer, out *v1.VaultIssuer, s conversion.Scope) error { +func Convert_certmanager_VaultIssuer_To_v1_VaultIssuer(in *certmanager.VaultIssuer, out *certmanagerv1.VaultIssuer, s conversion.Scope) error { return autoConvert_certmanager_VaultIssuer_To_v1_VaultIssuer(in, out, s) } -func autoConvert_v1_VaultKubernetesAuth_To_certmanager_VaultKubernetesAuth(in *v1.VaultKubernetesAuth, out *certmanager.VaultKubernetesAuth, s conversion.Scope) error { +func autoConvert_v1_VaultKubernetesAuth_To_certmanager_VaultKubernetesAuth(in *certmanagerv1.VaultKubernetesAuth, out *certmanager.VaultKubernetesAuth, s conversion.Scope) error { out.Path = in.Path if err := internalapismetav1.Convert_v1_SecretKeySelector_To_meta_SecretKeySelector(&in.SecretRef, &out.SecretRef, s); err != nil { return err } + out.ServiceAccountRef = (*certmanager.ServiceAccountRef)(unsafe.Pointer(in.ServiceAccountRef)) out.Role = in.Role return nil } // Convert_v1_VaultKubernetesAuth_To_certmanager_VaultKubernetesAuth is an autogenerated conversion function. -func Convert_v1_VaultKubernetesAuth_To_certmanager_VaultKubernetesAuth(in *v1.VaultKubernetesAuth, out *certmanager.VaultKubernetesAuth, s conversion.Scope) error { +func Convert_v1_VaultKubernetesAuth_To_certmanager_VaultKubernetesAuth(in *certmanagerv1.VaultKubernetesAuth, out *certmanager.VaultKubernetesAuth, s conversion.Scope) error { return autoConvert_v1_VaultKubernetesAuth_To_certmanager_VaultKubernetesAuth(in, out, s) } -func autoConvert_certmanager_VaultKubernetesAuth_To_v1_VaultKubernetesAuth(in *certmanager.VaultKubernetesAuth, out *v1.VaultKubernetesAuth, s conversion.Scope) error { +func autoConvert_certmanager_VaultKubernetesAuth_To_v1_VaultKubernetesAuth(in *certmanager.VaultKubernetesAuth, out *certmanagerv1.VaultKubernetesAuth, s conversion.Scope) error { out.Path = in.Path if err := internalapismetav1.Convert_meta_SecretKeySelector_To_v1_SecretKeySelector(&in.SecretRef, &out.SecretRef, s); err != nil { return err } + out.ServiceAccountRef = (*certmanagerv1.ServiceAccountRef)(unsafe.Pointer(in.ServiceAccountRef)) out.Role = in.Role return nil } // Convert_certmanager_VaultKubernetesAuth_To_v1_VaultKubernetesAuth is an autogenerated conversion function. -func Convert_certmanager_VaultKubernetesAuth_To_v1_VaultKubernetesAuth(in *certmanager.VaultKubernetesAuth, out *v1.VaultKubernetesAuth, s conversion.Scope) error { +func Convert_certmanager_VaultKubernetesAuth_To_v1_VaultKubernetesAuth(in *certmanager.VaultKubernetesAuth, out *certmanagerv1.VaultKubernetesAuth, s conversion.Scope) error { return autoConvert_certmanager_VaultKubernetesAuth_To_v1_VaultKubernetesAuth(in, out, s) } -func autoConvert_v1_VenafiCloud_To_certmanager_VenafiCloud(in *v1.VenafiCloud, out *certmanager.VenafiCloud, s conversion.Scope) error { +func autoConvert_v1_VenafiCloud_To_certmanager_VenafiCloud(in *certmanagerv1.VenafiCloud, out *certmanager.VenafiCloud, s conversion.Scope) error { out.URL = in.URL if err := internalapismetav1.Convert_v1_SecretKeySelector_To_meta_SecretKeySelector(&in.APITokenSecretRef, &out.APITokenSecretRef, s); err != nil { return err @@ -1464,11 +1702,11 @@ func autoConvert_v1_VenafiCloud_To_certmanager_VenafiCloud(in *v1.VenafiCloud, o } // Convert_v1_VenafiCloud_To_certmanager_VenafiCloud is an autogenerated conversion function. -func Convert_v1_VenafiCloud_To_certmanager_VenafiCloud(in *v1.VenafiCloud, out *certmanager.VenafiCloud, s conversion.Scope) error { +func Convert_v1_VenafiCloud_To_certmanager_VenafiCloud(in *certmanagerv1.VenafiCloud, out *certmanager.VenafiCloud, s conversion.Scope) error { return autoConvert_v1_VenafiCloud_To_certmanager_VenafiCloud(in, out, s) } -func autoConvert_certmanager_VenafiCloud_To_v1_VenafiCloud(in *certmanager.VenafiCloud, out *v1.VenafiCloud, s conversion.Scope) error { +func autoConvert_certmanager_VenafiCloud_To_v1_VenafiCloud(in *certmanager.VenafiCloud, out *certmanagerv1.VenafiCloud, s conversion.Scope) error { out.URL = in.URL if err := internalapismetav1.Convert_meta_SecretKeySelector_To_v1_SecretKeySelector(&in.APITokenSecretRef, &out.APITokenSecretRef, s); err != nil { return err @@ -1477,11 +1715,11 @@ func autoConvert_certmanager_VenafiCloud_To_v1_VenafiCloud(in *certmanager.Venaf } // Convert_certmanager_VenafiCloud_To_v1_VenafiCloud is an autogenerated conversion function. -func Convert_certmanager_VenafiCloud_To_v1_VenafiCloud(in *certmanager.VenafiCloud, out *v1.VenafiCloud, s conversion.Scope) error { +func Convert_certmanager_VenafiCloud_To_v1_VenafiCloud(in *certmanager.VenafiCloud, out *certmanagerv1.VenafiCloud, s conversion.Scope) error { return autoConvert_certmanager_VenafiCloud_To_v1_VenafiCloud(in, out, s) } -func autoConvert_v1_VenafiIssuer_To_certmanager_VenafiIssuer(in *v1.VenafiIssuer, out *certmanager.VenafiIssuer, s conversion.Scope) error { +func autoConvert_v1_VenafiIssuer_To_certmanager_VenafiIssuer(in *certmanagerv1.VenafiIssuer, out *certmanager.VenafiIssuer, s conversion.Scope) error { out.Zone = in.Zone if in.TPP != nil { in, out := &in.TPP, &out.TPP @@ -1505,15 +1743,15 @@ func autoConvert_v1_VenafiIssuer_To_certmanager_VenafiIssuer(in *v1.VenafiIssuer } // Convert_v1_VenafiIssuer_To_certmanager_VenafiIssuer is an autogenerated conversion function. -func Convert_v1_VenafiIssuer_To_certmanager_VenafiIssuer(in *v1.VenafiIssuer, out *certmanager.VenafiIssuer, s conversion.Scope) error { +func Convert_v1_VenafiIssuer_To_certmanager_VenafiIssuer(in *certmanagerv1.VenafiIssuer, out *certmanager.VenafiIssuer, s conversion.Scope) error { return autoConvert_v1_VenafiIssuer_To_certmanager_VenafiIssuer(in, out, s) } -func autoConvert_certmanager_VenafiIssuer_To_v1_VenafiIssuer(in *certmanager.VenafiIssuer, out *v1.VenafiIssuer, s conversion.Scope) error { +func autoConvert_certmanager_VenafiIssuer_To_v1_VenafiIssuer(in *certmanager.VenafiIssuer, out *certmanagerv1.VenafiIssuer, s conversion.Scope) error { out.Zone = in.Zone if in.TPP != nil { in, out := &in.TPP, &out.TPP - *out = new(v1.VenafiTPP) + *out = new(certmanagerv1.VenafiTPP) if err := Convert_certmanager_VenafiTPP_To_v1_VenafiTPP(*in, *out, s); err != nil { return err } @@ -1522,7 +1760,7 @@ func autoConvert_certmanager_VenafiIssuer_To_v1_VenafiIssuer(in *certmanager.Ven } if in.Cloud != nil { in, out := &in.Cloud, &out.Cloud - *out = new(v1.VenafiCloud) + *out = new(certmanagerv1.VenafiCloud) if err := Convert_certmanager_VenafiCloud_To_v1_VenafiCloud(*in, *out, s); err != nil { return err } @@ -1533,39 +1771,57 @@ func autoConvert_certmanager_VenafiIssuer_To_v1_VenafiIssuer(in *certmanager.Ven } // Convert_certmanager_VenafiIssuer_To_v1_VenafiIssuer is an autogenerated conversion function. -func Convert_certmanager_VenafiIssuer_To_v1_VenafiIssuer(in *certmanager.VenafiIssuer, out *v1.VenafiIssuer, s conversion.Scope) error { +func Convert_certmanager_VenafiIssuer_To_v1_VenafiIssuer(in *certmanager.VenafiIssuer, out *certmanagerv1.VenafiIssuer, s conversion.Scope) error { return autoConvert_certmanager_VenafiIssuer_To_v1_VenafiIssuer(in, out, s) } -func autoConvert_v1_VenafiTPP_To_certmanager_VenafiTPP(in *v1.VenafiTPP, out *certmanager.VenafiTPP, s conversion.Scope) error { +func autoConvert_v1_VenafiTPP_To_certmanager_VenafiTPP(in *certmanagerv1.VenafiTPP, out *certmanager.VenafiTPP, s conversion.Scope) error { out.URL = in.URL if err := internalapismetav1.Convert_v1_LocalObjectReference_To_meta_LocalObjectReference(&in.CredentialsRef, &out.CredentialsRef, s); err != nil { return err } out.CABundle = *(*[]byte)(unsafe.Pointer(&in.CABundle)) + if in.CABundleSecretRef != nil { + in, out := &in.CABundleSecretRef, &out.CABundleSecretRef + *out = new(meta.SecretKeySelector) + if err := internalapismetav1.Convert_v1_SecretKeySelector_To_meta_SecretKeySelector(*in, *out, s); err != nil { + return err + } + } else { + out.CABundleSecretRef = nil + } return nil } // Convert_v1_VenafiTPP_To_certmanager_VenafiTPP is an autogenerated conversion function. -func Convert_v1_VenafiTPP_To_certmanager_VenafiTPP(in *v1.VenafiTPP, out *certmanager.VenafiTPP, s conversion.Scope) error { +func Convert_v1_VenafiTPP_To_certmanager_VenafiTPP(in *certmanagerv1.VenafiTPP, out *certmanager.VenafiTPP, s conversion.Scope) error { return autoConvert_v1_VenafiTPP_To_certmanager_VenafiTPP(in, out, s) } -func autoConvert_certmanager_VenafiTPP_To_v1_VenafiTPP(in *certmanager.VenafiTPP, out *v1.VenafiTPP, s conversion.Scope) error { +func autoConvert_certmanager_VenafiTPP_To_v1_VenafiTPP(in *certmanager.VenafiTPP, out *certmanagerv1.VenafiTPP, s conversion.Scope) error { out.URL = in.URL if err := internalapismetav1.Convert_meta_LocalObjectReference_To_v1_LocalObjectReference(&in.CredentialsRef, &out.CredentialsRef, s); err != nil { return err } out.CABundle = *(*[]byte)(unsafe.Pointer(&in.CABundle)) + if in.CABundleSecretRef != nil { + in, out := &in.CABundleSecretRef, &out.CABundleSecretRef + *out = new(apismetav1.SecretKeySelector) + if err := internalapismetav1.Convert_meta_SecretKeySelector_To_v1_SecretKeySelector(*in, *out, s); err != nil { + return err + } + } else { + out.CABundleSecretRef = nil + } return nil } // Convert_certmanager_VenafiTPP_To_v1_VenafiTPP is an autogenerated conversion function. -func Convert_certmanager_VenafiTPP_To_v1_VenafiTPP(in *certmanager.VenafiTPP, out *v1.VenafiTPP, s conversion.Scope) error { +func Convert_certmanager_VenafiTPP_To_v1_VenafiTPP(in *certmanager.VenafiTPP, out *certmanagerv1.VenafiTPP, s conversion.Scope) error { return autoConvert_certmanager_VenafiTPP_To_v1_VenafiTPP(in, out, s) } -func autoConvert_v1_X509Subject_To_certmanager_X509Subject(in *v1.X509Subject, out *certmanager.X509Subject, s conversion.Scope) error { +func autoConvert_v1_X509Subject_To_certmanager_X509Subject(in *certmanagerv1.X509Subject, out *certmanager.X509Subject, s conversion.Scope) error { out.Organizations = *(*[]string)(unsafe.Pointer(&in.Organizations)) out.Countries = *(*[]string)(unsafe.Pointer(&in.Countries)) out.OrganizationalUnits = *(*[]string)(unsafe.Pointer(&in.OrganizationalUnits)) @@ -1578,11 +1834,11 @@ func autoConvert_v1_X509Subject_To_certmanager_X509Subject(in *v1.X509Subject, o } // Convert_v1_X509Subject_To_certmanager_X509Subject is an autogenerated conversion function. -func Convert_v1_X509Subject_To_certmanager_X509Subject(in *v1.X509Subject, out *certmanager.X509Subject, s conversion.Scope) error { +func Convert_v1_X509Subject_To_certmanager_X509Subject(in *certmanagerv1.X509Subject, out *certmanager.X509Subject, s conversion.Scope) error { return autoConvert_v1_X509Subject_To_certmanager_X509Subject(in, out, s) } -func autoConvert_certmanager_X509Subject_To_v1_X509Subject(in *certmanager.X509Subject, out *v1.X509Subject, s conversion.Scope) error { +func autoConvert_certmanager_X509Subject_To_v1_X509Subject(in *certmanager.X509Subject, out *certmanagerv1.X509Subject, s conversion.Scope) error { out.Organizations = *(*[]string)(unsafe.Pointer(&in.Organizations)) out.Countries = *(*[]string)(unsafe.Pointer(&in.Countries)) out.OrganizationalUnits = *(*[]string)(unsafe.Pointer(&in.OrganizationalUnits)) @@ -1595,6 +1851,6 @@ func autoConvert_certmanager_X509Subject_To_v1_X509Subject(in *certmanager.X509S } // Convert_certmanager_X509Subject_To_v1_X509Subject is an autogenerated conversion function. -func Convert_certmanager_X509Subject_To_v1_X509Subject(in *certmanager.X509Subject, out *v1.X509Subject, s conversion.Scope) error { +func Convert_certmanager_X509Subject_To_v1_X509Subject(in *certmanager.X509Subject, out *certmanagerv1.X509Subject, s conversion.Scope) error { return autoConvert_certmanager_X509Subject_To_v1_X509Subject(in, out, s) } diff --git a/internal/apis/certmanager/v1alpha2/const.go b/internal/apis/certmanager/v1alpha2/const.go deleted file mode 100644 index 2a26fcf47bf..00000000000 --- a/internal/apis/certmanager/v1alpha2/const.go +++ /dev/null @@ -1,43 +0,0 @@ -/* -Copyright 2020 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. -*/ - -package v1alpha2 - -import "time" - -const ( - // minimum permitted certificate duration by cert-manager - MinimumCertificateDuration = time.Hour - - // default certificate duration if Issuer.spec.duration is not set - DefaultCertificateDuration = time.Hour * 24 * 90 - - // minimum certificate duration before certificate expiration - MinimumRenewBefore = time.Minute * 5 - - // Deprecated: the default is now 2/3 of Certificate's duration - DefaultRenewBefore = time.Hour * 24 * 30 -) - -const ( - // Default index key for the Secret reference for Token authentication - DefaultVaultTokenAuthSecretKey = "token" - - // Default mount path location for Kubernetes ServiceAccount authentication - // (/v1/auth/kubernetes). The endpoint will then be called at `/login`, so - // left as the default, `/v1/auth/kubernetes/login` will be called. - DefaultVaultKubernetesAuthMountPath = "/v1/auth/kubernetes" -) diff --git a/internal/apis/certmanager/v1alpha2/conversion.go b/internal/apis/certmanager/v1alpha2/conversion.go deleted file mode 100644 index b631ff99ba6..00000000000 --- a/internal/apis/certmanager/v1alpha2/conversion.go +++ /dev/null @@ -1,127 +0,0 @@ -/* -Copyright 2020 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. -*/ - -package v1alpha2 - -import ( - "k8s.io/apimachinery/pkg/conversion" - - "github.com/cert-manager/cert-manager/internal/apis/certmanager" -) - -func Convert_v1alpha2_CertificateSpec_To_certmanager_CertificateSpec(in *CertificateSpec, out *certmanager.CertificateSpec, s conversion.Scope) error { - if err := autoConvert_v1alpha2_CertificateSpec_To_certmanager_CertificateSpec(in, out, s); err != nil { - return err - } - - if len(in.Organization) > 0 { - if out.Subject == nil { - out.Subject = &certmanager.X509Subject{} - } - - out.Subject.Organizations = in.Organization - } - - if in.KeyAlgorithm != "" || in.KeyEncoding != "" || in.KeySize != 0 { - if out.PrivateKey == nil { - out.PrivateKey = &certmanager.CertificatePrivateKey{} - } - - switch in.KeyAlgorithm { - case ECDSAKeyAlgorithm: - out.PrivateKey.Algorithm = certmanager.ECDSAKeyAlgorithm - case RSAKeyAlgorithm: - out.PrivateKey.Algorithm = certmanager.RSAKeyAlgorithm - default: - out.PrivateKey.Algorithm = certmanager.PrivateKeyAlgorithm(in.KeyAlgorithm) - } - - switch in.KeyEncoding { - case PKCS1: - out.PrivateKey.Encoding = certmanager.PKCS1 - case PKCS8: - out.PrivateKey.Encoding = certmanager.PKCS8 - default: - out.PrivateKey.Encoding = certmanager.PrivateKeyEncoding(in.KeyEncoding) - } - - out.PrivateKey.Size = in.KeySize - } - - return nil -} - -func Convert_certmanager_CertificateSpec_To_v1alpha2_CertificateSpec(in *certmanager.CertificateSpec, out *CertificateSpec, s conversion.Scope) error { - if err := autoConvert_certmanager_CertificateSpec_To_v1alpha2_CertificateSpec(in, out, s); err != nil { - return err - } - - if in.Subject != nil { - out.Organization = in.Subject.Organizations - } else { - out.Organization = nil - } - - if in.PrivateKey != nil { - switch in.PrivateKey.Algorithm { - case certmanager.ECDSAKeyAlgorithm: - out.KeyAlgorithm = ECDSAKeyAlgorithm - case certmanager.RSAKeyAlgorithm: - out.KeyAlgorithm = RSAKeyAlgorithm - default: - out.KeyAlgorithm = KeyAlgorithm(in.PrivateKey.Algorithm) - } - - switch in.PrivateKey.Encoding { - case certmanager.PKCS1: - out.KeyEncoding = PKCS1 - case certmanager.PKCS8: - out.KeyEncoding = PKCS8 - default: - out.KeyEncoding = KeyEncoding(in.PrivateKey.Encoding) - } - - out.KeySize = in.PrivateKey.Size - } - - return nil -} - -func Convert_certmanager_X509Subject_To_v1alpha2_X509Subject(in *certmanager.X509Subject, out *X509Subject, s conversion.Scope) error { - return autoConvert_certmanager_X509Subject_To_v1alpha2_X509Subject(in, out, s) -} - -func Convert_certmanager_CertificatePrivateKey_To_v1alpha2_CertificatePrivateKey(in *certmanager.CertificatePrivateKey, out *CertificatePrivateKey, s conversion.Scope) error { - return autoConvert_certmanager_CertificatePrivateKey_To_v1alpha2_CertificatePrivateKey(in, out, s) -} - -func Convert_v1alpha2_CertificateRequestSpec_To_certmanager_CertificateRequestSpec(in *CertificateRequestSpec, out *certmanager.CertificateRequestSpec, s conversion.Scope) error { - if err := autoConvert_v1alpha2_CertificateRequestSpec_To_certmanager_CertificateRequestSpec(in, out, s); err != nil { - return err - } - - out.Request = in.CSRPEM - return nil -} - -func Convert_certmanager_CertificateRequestSpec_To_v1alpha2_CertificateRequestSpec(in *certmanager.CertificateRequestSpec, out *CertificateRequestSpec, s conversion.Scope) error { - if err := autoConvert_certmanager_CertificateRequestSpec_To_v1alpha2_CertificateRequestSpec(in, out, s); err != nil { - return err - } - - out.CSRPEM = in.Request - return nil -} diff --git a/internal/apis/certmanager/v1alpha2/doc.go b/internal/apis/certmanager/v1alpha2/doc.go deleted file mode 100644 index 6dec230eb21..00000000000 --- a/internal/apis/certmanager/v1alpha2/doc.go +++ /dev/null @@ -1,23 +0,0 @@ -/* -Copyright 2020 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. -*/ - -// +k8s:conversion-gen=github.com/cert-manager/cert-manager/internal/apis/certmanager -// +k8s:conversion-gen-external-types=github.com/cert-manager/cert-manager/internal/apis/certmanager/v1alpha2 -// +k8s:defaulter-gen=TypeMeta -// +k8s:deepcopy-gen=package,register - -// +groupName=cert-manager.io -package v1alpha2 diff --git a/internal/apis/certmanager/v1alpha2/generic_issuer.go b/internal/apis/certmanager/v1alpha2/generic_issuer.go deleted file mode 100644 index d83335c0959..00000000000 --- a/internal/apis/certmanager/v1alpha2/generic_issuer.go +++ /dev/null @@ -1,85 +0,0 @@ -/* -Copyright 2020 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. -*/ - -package v1alpha2 - -import ( - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - - cmacme "github.com/cert-manager/cert-manager/internal/apis/acme/v1alpha2" -) - -type GenericIssuer interface { - runtime.Object - metav1.Object - - GetObjectMeta() *metav1.ObjectMeta - GetSpec() *IssuerSpec - GetStatus() *IssuerStatus -} - -var _ GenericIssuer = &Issuer{} -var _ GenericIssuer = &ClusterIssuer{} - -func (c *ClusterIssuer) GetObjectMeta() *metav1.ObjectMeta { - return &c.ObjectMeta -} -func (c *ClusterIssuer) GetSpec() *IssuerSpec { - return &c.Spec -} -func (c *ClusterIssuer) GetStatus() *IssuerStatus { - return &c.Status -} -func (c *ClusterIssuer) SetSpec(spec IssuerSpec) { - c.Spec = spec -} -func (c *ClusterIssuer) SetStatus(status IssuerStatus) { - c.Status = status -} -func (c *ClusterIssuer) Copy() GenericIssuer { - return c.DeepCopy() -} -func (c *Issuer) GetObjectMeta() *metav1.ObjectMeta { - return &c.ObjectMeta -} -func (c *Issuer) GetSpec() *IssuerSpec { - return &c.Spec -} -func (c *Issuer) GetStatus() *IssuerStatus { - return &c.Status -} -func (c *Issuer) SetSpec(spec IssuerSpec) { - c.Spec = spec -} -func (c *Issuer) SetStatus(status IssuerStatus) { - c.Status = status -} -func (c *Issuer) Copy() GenericIssuer { - return c.DeepCopy() -} - -// TODO: refactor these functions away -func (i *IssuerStatus) ACMEStatus() *cmacme.ACMEIssuerStatus { - // this is an edge case, but this will prevent panics - if i == nil { - return &cmacme.ACMEIssuerStatus{} - } - if i.ACME == nil { - i.ACME = &cmacme.ACMEIssuerStatus{} - } - return i.ACME -} diff --git a/internal/apis/certmanager/v1alpha2/register.go b/internal/apis/certmanager/v1alpha2/register.go deleted file mode 100644 index 227c7110fd7..00000000000 --- a/internal/apis/certmanager/v1alpha2/register.go +++ /dev/null @@ -1,67 +0,0 @@ -/* -Copyright 2020 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. -*/ - -package v1alpha2 - -import ( - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/runtime/schema" - - "github.com/cert-manager/cert-manager/pkg/apis/certmanager" -) - -// SchemeGroupVersion is group version used to register these objects -var SchemeGroupVersion = schema.GroupVersion{Group: certmanager.GroupName, Version: "v1alpha2"} - -// Resource takes an unqualified resource and returns a Group qualified GroupResource -func Resource(resource string) schema.GroupResource { - return SchemeGroupVersion.WithResource(resource).GroupResource() -} - -var ( - SchemeBuilder runtime.SchemeBuilder - localSchemeBuilder = &SchemeBuilder - AddToScheme = localSchemeBuilder.AddToScheme -) - -func init() { - // We only register manually written functions here. The registration of the - // generated functions takes place in the generated files. The separation - // makes the code compile even when the generated files are missing. - localSchemeBuilder.Register(addDefaultingFuncs) - - // We only register manually written functions here. The registration of the - // generated functions takes place in the generated files. The separation - // makes the code compile even when the generated files are missing. - localSchemeBuilder.Register(addKnownTypes) -} - -// Adds the list of known types to api.Scheme. -func addKnownTypes(scheme *runtime.Scheme) error { - scheme.AddKnownTypes(SchemeGroupVersion, - &Certificate{}, - &CertificateList{}, - &Issuer{}, - &IssuerList{}, - &ClusterIssuer{}, - &ClusterIssuerList{}, - &CertificateRequest{}, - &CertificateRequestList{}, - ) - metav1.AddToGroupVersion(scheme, SchemeGroupVersion) - return nil -} diff --git a/internal/apis/certmanager/v1alpha2/types.go b/internal/apis/certmanager/v1alpha2/types.go deleted file mode 100644 index 82b1564797f..00000000000 --- a/internal/apis/certmanager/v1alpha2/types.go +++ /dev/null @@ -1,205 +0,0 @@ -/* -Copyright 2020 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. -*/ - -package v1alpha2 - -// Common annotation keys added to resources. -const ( - // Annotation key for DNS subjectAltNames. - AltNamesAnnotationKey = "cert-manager.io/alt-names" - - // Annotation key for IP subjectAltNames. - IPSANAnnotationKey = "cert-manager.io/ip-sans" - - // Annotation key for URI subjectAltNames. - URISANAnnotationKey = "cert-manager.io/uri-sans" - - // Annotation key for certificate common name. - CommonNameAnnotationKey = "cert-manager.io/common-name" - - // Annotation key the 'name' of the Issuer resource. - IssuerNameAnnotationKey = "cert-manager.io/issuer-name" - - // Annotation key for the 'kind' of the Issuer resource. - IssuerKindAnnotationKey = "cert-manager.io/issuer-kind" - - // Annotation key for the 'group' of the Issuer resource. - IssuerGroupAnnotationKey = "cert-manager.io/issuer-group" - - // Annotation key for the name of the certificate that a resource is related to. - CertificateNameKey = "cert-manager.io/certificate-name" - - // Annotation key used to denote whether a Secret is named on a Certificate - // as a 'next private key' Secret resource. - IsNextPrivateKeySecretLabelKey = "cert-manager.io/next-private-key" -) - -// Deprecated annotation names for Secrets -// These will be removed in a future release. -const ( - DeprecatedIssuerNameAnnotationKey = "certmanager.k8s.io/issuer-name" - DeprecatedIssuerKindAnnotationKey = "certmanager.k8s.io/issuer-kind" -) - -const ( - // issuerNameAnnotation can be used to override the issuer specified on the - // created Certificate resource. - IngressIssuerNameAnnotationKey = "cert-manager.io/issuer" - // clusterIssuerNameAnnotation can be used to override the issuer specified on the - // created Certificate resource. The Certificate will reference the - // specified *ClusterIssuer* instead of normal issuer. - IngressClusterIssuerNameAnnotationKey = "cert-manager.io/cluster-issuer" - // acmeIssuerHTTP01IngressClassAnnotation can be used to override the http01 ingressClass - // if the challenge type is set to http01 - IngressACMEIssuerHTTP01IngressClassAnnotationKey = "acme.cert-manager.io/http01-ingress-class" - - // IngressClassAnnotationKey picks a specific "class" for the Ingress. The - // controller only processes Ingresses with this annotation either unset, or - // set to either the configured value or the empty string. - IngressClassAnnotationKey = "kubernetes.io/ingress.class" -) - -// Annotation names for CertificateRequests -const ( - // Annotation added to CertificateRequest resources to denote the name of - // a Secret resource containing the private key used to sign the CSR stored - // on the resource. - // This annotation *may* not be present, and is used by the 'self signing' - // issuer type to self-sign certificates. - CertificateRequestPrivateKeyAnnotationKey = "cert-manager.io/private-key-secret-name" - - // Annotation to declare the CertificateRequest "revision", belonging to a Certificate Resource - CertificateRequestRevisionAnnotationKey = "cert-manager.io/certificate-revision" -) - -const ( - // IssueTemporaryCertificateAnnotation is an annotation that can be added to - // Certificate resources. - // If it is present, a temporary internally signed certificate will be - // stored in the target Secret resource whilst the real Issuer is processing - // the certificate request. - IssueTemporaryCertificateAnnotation = "cert-manager.io/issue-temporary-certificate" -) - -// Common/known resource kinds. -const ( - ClusterIssuerKind = "ClusterIssuer" - IssuerKind = "Issuer" - CertificateKind = "Certificate" - CertificateRequestKind = "CertificateRequest" -) - -const ( - // WantInjectAnnotation is the annotation that specifies that a particular - // object wants injection of CAs. It takes the form of a reference to a certificate - // as namespace/name. - WantInjectAnnotation = "cert-manager.io/inject-ca-from" - - // WantInjectAPIServerCAAnnotation, if set to "true", will make the cainjector - // inject the CA certificate for the Kubernetes apiserver into the resource. - // It discovers the apiserver's CA by inspecting the service account credentials - // mounted into the cainjector pod. - WantInjectAPIServerCAAnnotation = "cert-manager.io/inject-apiserver-ca" - - // WantInjectFromSecretAnnotation is the annotation that specifies that a particular - // object wants injection of CAs. It takes the form of a reference to a Secret - // as namespace/name. - WantInjectFromSecretAnnotation = "cert-manager.io/inject-ca-from-secret" - - // AllowsInjectionFromSecretAnnotation is an annotation that must be added - // to Secret resource that want to denote that they can be directly - // injected into injectables that have a `inject-ca-from-secret` annotation. - // If an injectable references a Secret that does NOT have this annotation, - // the cainjector will refuse to inject the secret. - AllowsInjectionFromSecretAnnotation = "cert-manager.io/allow-direct-injection" -) - -// Issuer specific Annotations -const ( - // VenafiCustomFieldsAnnotationKey is the annotation that passes on JSON encoded custom fields to the Venafi issuer - // This will only work with Venafi TPP v19.3 and higher - // The value is an array with objects containing the name and value keys - // for example: `[{"name": "custom-field", "value": "custom-value"}]` - VenafiCustomFieldsAnnotationKey = "venafi.cert-manager.io/custom-fields" -) - -// 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" -// +kubebuilder:validation:Enum="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" -type KeyUsage string - -const ( - UsageSigning KeyUsage = "signing" - UsageDigitalSignature KeyUsage = "digital signature" - UsageContentCommitment KeyUsage = "content commitment" - UsageKeyEncipherment KeyUsage = "key encipherment" - UsageKeyAgreement KeyUsage = "key agreement" - UsageDataEncipherment KeyUsage = "data encipherment" - UsageCertSign KeyUsage = "cert sign" - UsageCRLSign KeyUsage = "crl sign" - UsageEncipherOnly KeyUsage = "encipher only" - UsageDecipherOnly KeyUsage = "decipher only" - UsageAny KeyUsage = "any" - UsageServerAuth KeyUsage = "server auth" - UsageClientAuth KeyUsage = "client auth" - UsageCodeSigning KeyUsage = "code signing" - UsageEmailProtection KeyUsage = "email protection" - UsageSMIME KeyUsage = "s/mime" - UsageIPsecEndSystem KeyUsage = "ipsec end system" - UsageIPsecTunnel KeyUsage = "ipsec tunnel" - UsageIPsecUser KeyUsage = "ipsec user" - UsageTimestamping KeyUsage = "timestamping" - UsageOCSPSigning KeyUsage = "ocsp signing" - UsageMicrosoftSGC KeyUsage = "microsoft sgc" - UsageNetscapeSGC KeyUsage = "netscape sgc" -) - -// DefaultKeyUsages contains the default list of key usages -func DefaultKeyUsages() []KeyUsage { - // The serverAuth EKU is required as of Mac OS Catalina: https://support.apple.com/en-us/HT210176 - // Without this usage, certificates will _always_ flag a warning in newer Mac OS browsers. - // We don't explicitly add it here as it leads to strange behaviour when a user sets isCA: true - // (in which case, 'serverAuth' on the CA can break a lot of clients). - // CAs can (and often do) opt to automatically add usages. - return []KeyUsage{UsageDigitalSignature, UsageKeyEncipherment} -} diff --git a/internal/apis/certmanager/v1alpha2/types_certificate.go b/internal/apis/certmanager/v1alpha2/types_certificate.go deleted file mode 100644 index 5872612d76b..00000000000 --- a/internal/apis/certmanager/v1alpha2/types_certificate.go +++ /dev/null @@ -1,503 +0,0 @@ -/* -Copyright 2020 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. -*/ - -package v1alpha2 - -import ( - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - - cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" -) - -// +genclient -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object - -// 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`). -// +k8s:openapi-gen=true -type Certificate struct { - metav1.TypeMeta `json:",inline"` - metav1.ObjectMeta `json:"metadata,omitempty"` - - // Desired state of the Certificate resource. - Spec CertificateSpec `json:"spec,omitempty"` - - // Status of the Certificate. This is set and managed automatically. - Status CertificateStatus `json:"status,omitempty"` -} - -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object - -// CertificateList is a list of Certificates -type CertificateList struct { - metav1.TypeMeta `json:",inline"` - metav1.ListMeta `json:"metadata"` - - Items []Certificate `json:"items"` -} - -// +kubebuilder:validation:Enum=rsa;ecdsa -type KeyAlgorithm string - -const ( - // Denotes the RSA private key type. - RSAKeyAlgorithm KeyAlgorithm = "rsa" - - // Denotes the ECDSA private key type. - ECDSAKeyAlgorithm KeyAlgorithm = "ecdsa" -) - -// +kubebuilder:validation:Enum=pkcs1;pkcs8 -type KeyEncoding string - -const ( - // 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 KeyEncoding = "pkcs1" - - // 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 KeyEncoding = "pkcs8" -) - -// CertificateSpec defines the desired state of Certificate. -type CertificateSpec struct { - // Full X509 name specification (https://golang.org/pkg/crypto/x509/pkix/#Name). - // +optional - Subject *X509Subject `json:"subject,omitempty"` - - // LiteralSubject is an LDAP formatted string that represents the [X.509 Subject field](https://datatracker.ietf.org/doc/html/rfc5280#section-4.1.2.6). - // 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. - // +optional - LiteralSubject string `json:"literalSubject,omitempty"` - - // 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 - // +optional - CommonName string `json:"commonName,omitempty"` - - // Organization is a list of organizations to be used on the Certificate. - // +optional - Organization []string `json:"organization,omitempty"` - - // 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 2/3 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 - // +optional - Duration *metav1.Duration `json:"duration,omitempty"` - - // How long before the currently issued certificate's expiry - // cert-manager should renew the certificate. The default is 2/3 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 - // +optional - RenewBefore *metav1.Duration `json:"renewBefore,omitempty"` - - // DNSNames is a list of DNS subjectAltNames to be set on the Certificate. - // +optional - DNSNames []string `json:"dnsNames,omitempty"` - - // IPAddresses is a list of IP address subjectAltNames to be set on the Certificate. - // +optional - IPAddresses []string `json:"ipAddresses,omitempty"` - - // URISANs is a list of URI subjectAltNames to be set on the Certificate. - // +optional - URISANs []string `json:"uriSANs,omitempty"` - - // EmailSANs is a list of email subjectAltNames to be set on the Certificate. - // +optional - EmailSANs []string `json:"emailSANs,omitempty"` - - // 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. - SecretName string `json:"secretName"` - - // 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. - // +optional - SecretTemplate *CertificateSecretTemplate `json:"secretTemplate,omitempty"` - - // Keystores configures additional keystore output formats stored in the - // `secretName` Secret resource. - // +optional - Keystores *CertificateKeystores `json:"keystores,omitempty"` - - // 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. - IssuerRef cmmeta.ObjectReference `json:"issuerRef"` - - // IsCA will mark this Certificate as valid for certificate signing. - // This will automatically add the `cert sign` usage to the list of `usages`. - // +optional - IsCA bool `json:"isCA,omitempty"` - - // Usages is the set of x509 usages that are requested for the certificate. - // Defaults to `digital signature` and `key encipherment` if not specified. - // +optional - Usages []KeyUsage `json:"usages,omitempty"` - - // KeySize is the key bit size of the corresponding private key for this certificate. - // If `keyAlgorithm` is set to `rsa`, valid values are `2048`, `4096` or `8192`, - // and will default to `2048` if not specified. - // If `keyAlgorithm` is set to `ecdsa`, valid values are `256`, `384` or `521`, - // and will default to `256` if not specified. - // No other values are allowed. - // +optional - KeySize int `json:"keySize,omitempty"` // Validated by webhook. Be mindful of adding OpenAPI validation- see https://github.com/cert-manager/cert-manager/issues/3644 . - - // KeyAlgorithm is the private key algorithm of the corresponding private key - // for this certificate. If provided, allowed values are either `rsa` or `ecdsa` - // If `keyAlgorithm` is specified and `keySize` 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. - // +optional - KeyAlgorithm KeyAlgorithm `json:"keyAlgorithm,omitempty"` - - // KeyEncoding is the private key cryptography standards (PKCS) - // 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. - // If KeyEncoding is not specified, then `pkcs1` will be used by default. - // +optional - KeyEncoding KeyEncoding `json:"keyEncoding,omitempty"` - - // Options to control private keys used for the Certificate. - // +optional - PrivateKey *CertificatePrivateKey `json:"privateKey,omitempty"` - - // EncodeUsagesInRequest controls whether key usages should be present - // in the CertificateRequest - // +optional - EncodeUsagesInRequest *bool `json:"encodeUsagesInRequest,omitempty"` - - // 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`. - // +kubebuilder:validation:ExclusiveMaximum=false - // +optional - RevisionHistoryLimit *int32 `json:"revisionHistoryLimit,omitempty"` // Validated by the validating webhook. - - // 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. - // +optional - AdditionalOutputFormats []CertificateAdditionalOutputFormat `json:"additionalOutputFormats,omitempty"` -} - -// CertificatePrivateKey contains configuration options for private keys -// used by the Certificate controller. -// This allows control of how private keys are rotated. -type CertificatePrivateKey struct { - // 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 - RotationPolicy PrivateKeyRotationPolicy `json:"rotationPolicy,omitempty"` -} - -// Denotes how private keys should be generated or sourced when a Certificate -// is being issued. -type PrivateKeyRotationPolicy string - -var ( - // RotationPolicyNever means 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. - RotationPolicyNever PrivateKeyRotationPolicy = "Never" - - // RotationPolicyAlways means a private key matching the specified - // requirements will be generated whenever a re-issuance occurs. - RotationPolicyAlways PrivateKeyRotationPolicy = "Always" -) - -// X509Subject Full X509 name specification -type X509Subject struct { - // Countries to be used on the Certificate. - // +optional - Countries []string `json:"countries,omitempty"` - // Organizational Units to be used on the Certificate. - // +optional - OrganizationalUnits []string `json:"organizationalUnits,omitempty"` - // Cities to be used on the Certificate. - // +optional - Localities []string `json:"localities,omitempty"` - // State/Provinces to be used on the Certificate. - // +optional - Provinces []string `json:"provinces,omitempty"` - // Street addresses to be used on the Certificate. - // +optional - StreetAddresses []string `json:"streetAddresses,omitempty"` - // Postal codes to be used on the Certificate. - // +optional - PostalCodes []string `json:"postalCodes,omitempty"` - // Serial number to be used on the Certificate. - // +optional - SerialNumber string `json:"serialNumber,omitempty"` -} - -// CertificateKeystores configures additional keystore output formats to be -// created in the Certificate's output Secret. -type CertificateKeystores struct { - // JKS configures options for storing a JKS keystore in the - // `spec.secretName` Secret resource. - JKS *JKSKeystore `json:"jks,omitempty"` - - // PKCS12 configures options for storing a PKCS12 keystore in the - // `spec.secretName` Secret resource. - PKCS12 *PKCS12Keystore `json:"pkcs12,omitempty"` -} - -// JKS configures options for storing a JKS keystore in the `spec.secretName` -// Secret resource. -type JKSKeystore struct { - // 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 only be updated upon re-issuance. - Create bool `json:"create"` - - // PasswordSecretRef is a reference to a key in a Secret resource - // containing the password used to encrypt the JKS keystore. - PasswordSecretRef cmmeta.SecretKeySelector `json:"passwordSecretRef"` -} - -// PKCS12 configures options for storing a PKCS12 keystore in the -// `spec.secretName` Secret resource. -type PKCS12Keystore struct { - // 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 only be updated upon re-issuance. - Create bool `json:"create"` - - // PasswordSecretRef is a reference to a key in a Secret resource - // containing the password used to encrypt the PKCS12 keystore. - PasswordSecretRef cmmeta.SecretKeySelector `json:"passwordSecretRef"` -} - -// CertificateStatus defines the observed state of Certificate -type CertificateStatus struct { - // List of status conditions to indicate the status of certificates. - // Known condition types are `Ready` and `Issuing`. - // +listType=map - // +listMapKey=type - // +optional - Conditions []CertificateCondition `json:"conditions,omitempty"` - - // 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. - // +optional - LastFailureTime *metav1.Time `json:"lastFailureTime,omitempty"` - - // The time after which the certificate stored in the secret named - // by this resource in spec.secretName is valid. - // +optional - NotBefore *metav1.Time `json:"notBefore,omitempty"` - - // The expiration time of the certificate stored in the secret named - // by this resource in `spec.secretName`. - // +optional - NotAfter *metav1.Time `json:"notAfter,omitempty"` - - // RenewalTime is the time at which the certificate will be next - // renewed. - // If not set, no upcoming renewal is scheduled. - // +optional - RenewalTime *metav1.Time `json:"renewalTime,omitempty"` - - // 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. - // +optional - Revision *int `json:"revision,omitempty"` - - // 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. - // +optional - NextPrivateKeySecretName *string `json:"nextPrivateKeySecretName,omitempty"` - - // 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). - // +optional - FailedIssuanceAttempts *int `json:"failedIssuanceAttempts,omitempty"` -} - -// CertificateCondition contains condition information for an Certificate. -type CertificateCondition struct { - // Type of the condition, known values are (`Ready`, `Issuing`). - Type CertificateConditionType `json:"type"` - - // Status of the condition, one of (`True`, `False`, `Unknown`). - Status cmmeta.ConditionStatus `json:"status"` - - // LastTransitionTime is the timestamp corresponding to the last status - // change of this condition. - // +optional - LastTransitionTime *metav1.Time `json:"lastTransitionTime,omitempty"` - - // Reason is a brief machine readable explanation for the condition's last - // transition. - // +optional - Reason string `json:"reason,omitempty"` - - // Message is a human readable description of the details of the last - // transition, complementing reason. - // +optional - Message string `json:"message,omitempty"` - - // 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. - // +optional - ObservedGeneration int64 `json:"observedGeneration,omitempty"` -} - -// CertificateConditionType represents an Certificate condition value. -type CertificateConditionType string - -const ( - // 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 - CertificateConditionReady CertificateConditionType = "Ready" - - // 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. - CertificateConditionIssuing CertificateConditionType = "Issuing" -) - -// CertificateSecretTemplate defines the default labels and annotations -// to be copied to the Kubernetes Secret resource named in `CertificateSpec.secretName`. -type CertificateSecretTemplate struct { - // Annotations is a key value map to be copied to the target Kubernetes Secret. - // +optional - Annotations map[string]string `json:"annotations,omitempty"` - - // Labels is a key value map to be copied to the target Kubernetes Secret. - // +optional - Labels map[string]string `json:"labels,omitempty"` -} - -// CertificateOutputFormatType specifies which output formats that can 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). -// +kubebuilder:validation:Enum=DER;CombinedPEM -type CertificateOutputFormatType string - -const ( - // CertificateOutputFormatDER writes the Certificate's private key in DER - // binary format to the `key.der` target Secret Data key. - CertificateOutputFormatDER CertificateOutputFormatType = "DER" - - // 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 - // (` + \n + `). - CertificateOutputFormatCombinedPEM CertificateOutputFormatType = "CombinedPEM" -) - -// CertificateAdditionalOutputFormat defines an additional output format of a -// Certificate resource. These contain supplementary data formats of the signed -// certificate chain and paired private key. -type CertificateAdditionalOutputFormat struct { - // Type is the name of the format type that should be written to the - // Certificate's target Secret. - Type CertificateOutputFormatType `json:"type"` -} diff --git a/internal/apis/certmanager/v1alpha2/types_certificaterequest.go b/internal/apis/certmanager/v1alpha2/types_certificaterequest.go deleted file mode 100644 index d6618255251..00000000000 --- a/internal/apis/certmanager/v1alpha2/types_certificaterequest.go +++ /dev/null @@ -1,209 +0,0 @@ -/* -Copyright 2020 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. -*/ - -package v1alpha2 - -import ( - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - - cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" -) - -const ( - // Pending indicates that a CertificateRequest is still in progress. - CertificateRequestReasonPending = "Pending" - - // Failed indicates that a CertificateRequest has failed, either due to - // timing out or some other critical failure. - CertificateRequestReasonFailed = "Failed" - - // Issued indicates that a CertificateRequest has been completed, and that - // the `status.certificate` field is set. - CertificateRequestReasonIssued = "Issued" - - // Denied is a Ready condition reason that indicates that a - // CertificateRequest has been denied, and the CertificateRequest will never - // be issued. - CertificateRequestReasonDenied = "Denied" -) - -// +genclient -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object - -// 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. -// +k8s:openapi-gen=true -type CertificateRequest struct { - metav1.TypeMeta `json:",inline"` - metav1.ObjectMeta `json:"metadata,omitempty"` - - // Desired state of the CertificateRequest resource. - Spec CertificateRequestSpec `json:"spec,omitempty"` - - // Status of the CertificateRequest. This is set and managed automatically. - Status CertificateRequestStatus `json:"status,omitempty"` -} - -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object - -// CertificateRequestList is a list of Certificates -type CertificateRequestList struct { - metav1.TypeMeta `json:",inline"` - metav1.ListMeta `json:"metadata"` - - Items []CertificateRequest `json:"items"` -} - -// CertificateRequestSpec defines the desired state of CertificateRequest -type CertificateRequestSpec struct { - // The requested 'duration' (i.e. lifetime) of the Certificate. - // This option may be ignored/overridden by some issuer types. - // +optional - Duration *metav1.Duration `json:"duration,omitempty"` - - // 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. - IssuerRef cmmeta.ObjectReference `json:"issuerRef"` - - // The PEM-encoded x509 certificate signing request to be submitted to the - // CA for signing. - CSRPEM []byte `json:"csr"` - - // 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`. - // +optional - IsCA bool `json:"isCA,omitempty"` - - // Usages is the set of x509 usages that are requested for the certificate. - // Defaults to `digital signature` and `key encipherment` if not specified. - // +optional - Usages []KeyUsage `json:"usages,omitempty"` - - // Username contains the name of the user that created the CertificateRequest. - // Populated by the cert-manager webhook on creation and immutable. - // +optional - Username string `json:"username,omitempty"` - // UID contains the uid of the user that created the CertificateRequest. - // Populated by the cert-manager webhook on creation and immutable. - // +optional - UID string `json:"uid,omitempty"` - // Groups contains group membership of the user that created the CertificateRequest. - // Populated by the cert-manager webhook on creation and immutable. - // +listType=atomic - // +optional - Groups []string `json:"groups,omitempty"` - // Extra contains extra attributes of the user that created the CertificateRequest. - // Populated by the cert-manager webhook on creation and immutable. - // +optional - Extra map[string][]string `json:"extra,omitempty"` -} - -// CertificateRequestStatus defines the observed state of CertificateRequest and -// resulting signed certificate. -type CertificateRequestStatus struct { - // List of status conditions to indicate the status of a CertificateRequest. - // Known condition types are `Ready` and `InvalidRequest`. - // +listType=map - // +listMapKey=type - // +optional - Conditions []CertificateRequestCondition `json:"conditions,omitempty"` - - // 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. - // +optional - Certificate []byte `json:"certificate,omitempty"` - - // 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. - // +optional - CA []byte `json:"ca,omitempty"` - - // FailureTime stores the time that this CertificateRequest failed. This is - // used to influence garbage collection and back-off. - // +optional - FailureTime *metav1.Time `json:"failureTime,omitempty"` -} - -// CertificateRequestCondition contains condition information for a CertificateRequest. -type CertificateRequestCondition struct { - // Type of the condition, known values are (`Ready`, - // `InvalidRequest`, `Approved`, `Denied`). - Type CertificateRequestConditionType `json:"type"` - - // Status of the condition, one of (`True`, `False`, `Unknown`). - Status cmmeta.ConditionStatus `json:"status"` - - // LastTransitionTime is the timestamp corresponding to the last status - // change of this condition. - // +optional - LastTransitionTime *metav1.Time `json:"lastTransitionTime,omitempty"` - - // Reason is a brief machine readable explanation for the condition's last - // transition. - // +optional - Reason string `json:"reason,omitempty"` - - // Message is a human readable description of the details of the last - // transition, complementing reason. - // +optional - Message string `json:"message,omitempty"` -} - -// CertificateRequestConditionType represents an Certificate condition value. -type CertificateRequestConditionType string - -const ( - // CertificateRequestConditionReady indicates that a certificate is ready for use. - // This is defined as: - // - The target certificate exists in CertificateRequest.Status - CertificateRequestConditionReady CertificateRequestConditionType = "Ready" - - // 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. - CertificateRequestConditionInvalidRequest CertificateRequestConditionType = "InvalidRequest" - - // 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`. - CertificateRequestConditionApproved CertificateRequestConditionType = "Approved" - - // 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`. - CertificateRequestConditionDenied CertificateRequestConditionType = "Denied" -) diff --git a/internal/apis/certmanager/v1alpha2/types_issuer.go b/internal/apis/certmanager/v1alpha2/types_issuer.go deleted file mode 100644 index 90893c1cf48..00000000000 --- a/internal/apis/certmanager/v1alpha2/types_issuer.go +++ /dev/null @@ -1,353 +0,0 @@ -/* -Copyright 2020 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. -*/ - -package v1alpha2 - -import ( - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - - cmacme "github.com/cert-manager/cert-manager/internal/apis/acme/v1alpha2" - cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" -) - -// +genclient -// +genclient:nonNamespaced -// +k8s:openapi-gen=true -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object - -// 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. -type ClusterIssuer struct { - metav1.TypeMeta `json:",inline"` - metav1.ObjectMeta `json:"metadata,omitempty"` - - // Desired state of the ClusterIssuer resource. - Spec IssuerSpec `json:"spec,omitempty"` - - // Status of the ClusterIssuer. This is set and managed automatically. - Status IssuerStatus `json:"status,omitempty"` -} - -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object - -// ClusterIssuerList is a list of Issuers -type ClusterIssuerList struct { - metav1.TypeMeta `json:",inline"` - metav1.ListMeta `json:"metadata"` - - Items []ClusterIssuer `json:"items"` -} - -// +genclient -// +k8s:openapi-gen=true -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object - -// 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. -type Issuer struct { - metav1.TypeMeta `json:",inline"` - metav1.ObjectMeta `json:"metadata,omitempty"` - - // Desired state of the Issuer resource. - Spec IssuerSpec `json:"spec,omitempty"` - - // Status of the Issuer. This is set and managed automatically. - Status IssuerStatus `json:"status,omitempty"` -} - -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object - -// IssuerList is a list of Issuers -type IssuerList struct { - metav1.TypeMeta `json:",inline"` - metav1.ListMeta `json:"metadata"` - - Items []Issuer `json:"items"` -} - -// IssuerSpec is the specification of an Issuer. This includes any -// configuration required for the issuer. -type IssuerSpec struct { - IssuerConfig `json:",inline"` -} - -// The configuration for the issuer. -// Only one of these can be set. -type IssuerConfig struct { - // ACME configures this issuer to communicate with a RFC8555 (ACME) server - // to obtain signed x509 certificates. - // +optional - ACME *cmacme.ACMEIssuer `json:"acme,omitempty"` - - // 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. - // +optional - CA *CAIssuer `json:"ca,omitempty"` - - // Vault configures this issuer to sign certificates using a HashiCorp Vault - // PKI backend. - // +optional - Vault *VaultIssuer `json:"vault,omitempty"` - - // SelfSigned configures this issuer to 'self sign' certificates using the - // private key used to create the CertificateRequest object. - // +optional - SelfSigned *SelfSignedIssuer `json:"selfSigned,omitempty"` - - // Venafi configures this issuer to sign certificates using a Venafi TPP - // or Venafi Cloud policy zone. - // +optional - Venafi *VenafiIssuer `json:"venafi,omitempty"` -} - -// Configures an issuer to sign certificates using a Venafi TPP -// or Cloud policy zone. -type VenafiIssuer struct { - // 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. - Zone string `json:"zone"` - - // TPP specifies Trust Protection Platform configuration settings. - // Only one of TPP or Cloud may be specified. - // +optional - TPP *VenafiTPP `json:"tpp,omitempty"` - - // Cloud specifies the Venafi cloud configuration settings. - // Only one of TPP or Cloud may be specified. - // +optional - Cloud *VenafiCloud `json:"cloud,omitempty"` -} - -// VenafiTPP defines connection configuration details for a Venafi TPP instance -type VenafiTPP struct { - // URL is the base URL for the vedsdk endpoint of the Venafi TPP instance, - // for example: "https://tpp.example.com/vedsdk". - URL string `json:"url"` - - // 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'. - CredentialsRef cmmeta.LocalObjectReference `json:"credentialsRef"` - - // CABundle is a PEM encoded TLS certificate to use to verify connections to - // the TPP instance. - // If specified, system roots will not be used and the issuing CA for the - // TPP instance must be verifiable using the provided root. - // If not specified, the connection will be verified using the cert-manager - // system root certificates. - // +optional - CABundle []byte `json:"caBundle,omitempty"` -} - -// VenafiCloud defines connection configuration details for Venafi Cloud -type VenafiCloud struct { - // URL is the base URL for Venafi Cloud. - // Defaults to "https://api.venafi.cloud/v1". - // +optional - URL string `json:"url,omitempty"` - - // APITokenSecretRef is a secret key selector for the Venafi Cloud API token. - APITokenSecretRef cmmeta.SecretKeySelector `json:"apiTokenSecretRef"` -} - -// Configures an issuer to 'self sign' certificates using the -// private key used to create the CertificateRequest object. -type SelfSignedIssuer struct { - // 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. - // +optional - CRLDistributionPoints []string `json:"crlDistributionPoints,omitempty"` -} - -// Configures an issuer to sign certificates using a HashiCorp Vault -// PKI backend. -type VaultIssuer struct { - // Auth configures how cert-manager authenticates with the Vault server. - Auth VaultAuth `json:"auth"` - - // Server is the connection address for the Vault server, e.g: "https://vault.example.com:8200". - Server string `json:"server"` - - // Path is the mount path of the Vault PKI backend's `sign` endpoint, e.g: - // "my_pki_mount/sign/my-role-name". - Path string `json:"path"` - - // 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 - // +optional - Namespace string `json:"namespace,omitempty"` - - // PEM-encoded CA bundle (base64-encoded) used to validate Vault server - // certificate. Only used if the Server URL is using HTTPS protocol. This - // parameter is ignored for plain HTTP protocol connection. If not set the - // system root certificates are used to validate the TLS connection. - // Mutually exclusive with CABundleSecretRef. If neither CABundle nor CABundleSecretRef are defined, - // the cert-manager controller system root certificates are used to validate the TLS connection. - // +optional - CABundle []byte `json:"caBundle,omitempty"` - - // CABundleSecretRef is a reference to a Secret which contains the CABundle which will be used when - // connecting to Vault when using HTTPS. - // Mutually exclusive with CABundle. If neither CABundleSecretRef nor CABundle are defined, the cert-manager - // controller system root certificates are used to validate the TLS connection. - // If no key for the Secret is specified, cert-manager will default to 'ca.crt'. - // +optional - CABundleSecretRef *cmmeta.SecretKeySelector `json:"caBundleSecretRef,omitempty"` -} - -// Configuration used to authenticate with a Vault server. -// Only one of `tokenSecretRef`, `appRole` or `kubernetes` may be specified. -type VaultAuth struct { - // TokenSecretRef authenticates with Vault by presenting a token. - // +optional - TokenSecretRef *cmmeta.SecretKeySelector `json:"tokenSecretRef,omitempty"` - - // AppRole authenticates with Vault using the App Role auth mechanism, - // with the role and secret stored in a Kubernetes Secret resource. - // +optional - AppRole *VaultAppRole `json:"appRole,omitempty"` - - // Kubernetes authenticates with Vault by passing the ServiceAccount - // token stored in the named Secret resource to the Vault server. - // +optional - Kubernetes *VaultKubernetesAuth `json:"kubernetes,omitempty"` -} - -// VaultAppRole authenticates with Vault using the App Role auth mechanism, -// with the role and secret stored in a Kubernetes Secret resource. -type VaultAppRole struct { - // Path where the App Role authentication backend is mounted in Vault, e.g: - // "approle" - Path string `json:"path"` - - // RoleID configured in the App Role authentication backend when setting - // up the authentication backend in Vault. - RoleId string `json:"roleId"` - - // 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. - SecretRef cmmeta.SecretKeySelector `json:"secretRef"` -} - -// Authenticate against Vault using a Kubernetes ServiceAccount token stored in -// a Secret. -type VaultKubernetesAuth struct { - // 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. - // +optional - Path string `json:"mountPath,omitempty"` - - // The required Secret field containing a Kubernetes ServiceAccount JWT used - // for authenticating with Vault. Use of 'ambient credentials' is not - // supported. - SecretRef cmmeta.SecretKeySelector `json:"secretRef"` - - // A required field containing the Vault Role to assume. A Role binds a - // Kubernetes ServiceAccount with a set of Vault policies. - Role string `json:"role"` -} - -type CAIssuer struct { - // SecretName is the name of the secret used to sign Certificates issued - // by this Issuer. - SecretName string `json:"secretName"` - - // 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. - // +optional - CRLDistributionPoints []string `json:"crlDistributionPoints,omitempty"` - - // 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". - // +optional - OCSPServers []string `json:"ocspServers,omitempty"` -} - -// IssuerStatus contains status information about an Issuer -type IssuerStatus struct { - // List of status conditions to indicate the status of a CertificateRequest. - // Known condition types are `Ready`. - // +listType=map - // +listMapKey=type - // +optional - Conditions []IssuerCondition `json:"conditions,omitempty"` - - // ACME specific status options. - // This field should only be set if the Issuer is configured to use an ACME - // server to issue certificates. - // +optional - ACME *cmacme.ACMEIssuerStatus `json:"acme,omitempty"` -} - -// IssuerCondition contains condition information for an Issuer. -type IssuerCondition struct { - // Type of the condition, known values are (`Ready`). - Type IssuerConditionType `json:"type"` - - // Status of the condition, one of (`True`, `False`, `Unknown`). - Status cmmeta.ConditionStatus `json:"status"` - - // LastTransitionTime is the timestamp corresponding to the last status - // change of this condition. - // +optional - LastTransitionTime *metav1.Time `json:"lastTransitionTime,omitempty"` - - // Reason is a brief machine readable explanation for the condition's last - // transition. - // +optional - Reason string `json:"reason,omitempty"` - - // Message is a human readable description of the details of the last - // transition, complementing reason. - // +optional - Message string `json:"message,omitempty"` - - // 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. - // +optional - ObservedGeneration int64 `json:"observedGeneration,omitempty"` -} - -// IssuerConditionType represents an Issuer condition value. -type IssuerConditionType string - -const ( - // 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. - IssuerConditionReady IssuerConditionType = "Ready" -) diff --git a/internal/apis/certmanager/v1alpha2/zz_generated.conversion.go b/internal/apis/certmanager/v1alpha2/zz_generated.conversion.go deleted file mode 100644 index f78c3937218..00000000000 --- a/internal/apis/certmanager/v1alpha2/zz_generated.conversion.go +++ /dev/null @@ -1,1610 +0,0 @@ -//go:build !ignore_autogenerated -// +build !ignore_autogenerated - -/* -Copyright 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. -*/ - -// Code generated by conversion-gen. DO NOT EDIT. - -package v1alpha2 - -import ( - unsafe "unsafe" - - acme "github.com/cert-manager/cert-manager/internal/apis/acme" - acmev1alpha2 "github.com/cert-manager/cert-manager/internal/apis/acme/v1alpha2" - certmanager "github.com/cert-manager/cert-manager/internal/apis/certmanager" - meta "github.com/cert-manager/cert-manager/internal/apis/meta" - apismetav1 "github.com/cert-manager/cert-manager/internal/apis/meta/v1" - metav1 "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" - conversion "k8s.io/apimachinery/pkg/conversion" - runtime "k8s.io/apimachinery/pkg/runtime" -) - -func init() { - localSchemeBuilder.Register(RegisterConversions) -} - -// RegisterConversions adds conversion functions to the given scheme. -// Public to allow building arbitrary schemes. -func RegisterConversions(s *runtime.Scheme) error { - if err := s.AddGeneratedConversionFunc((*CAIssuer)(nil), (*certmanager.CAIssuer)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1alpha2_CAIssuer_To_certmanager_CAIssuer(a.(*CAIssuer), b.(*certmanager.CAIssuer), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*certmanager.CAIssuer)(nil), (*CAIssuer)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_certmanager_CAIssuer_To_v1alpha2_CAIssuer(a.(*certmanager.CAIssuer), b.(*CAIssuer), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*Certificate)(nil), (*certmanager.Certificate)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1alpha2_Certificate_To_certmanager_Certificate(a.(*Certificate), b.(*certmanager.Certificate), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*certmanager.Certificate)(nil), (*Certificate)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_certmanager_Certificate_To_v1alpha2_Certificate(a.(*certmanager.Certificate), b.(*Certificate), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*CertificateAdditionalOutputFormat)(nil), (*certmanager.CertificateAdditionalOutputFormat)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1alpha2_CertificateAdditionalOutputFormat_To_certmanager_CertificateAdditionalOutputFormat(a.(*CertificateAdditionalOutputFormat), b.(*certmanager.CertificateAdditionalOutputFormat), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*certmanager.CertificateAdditionalOutputFormat)(nil), (*CertificateAdditionalOutputFormat)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_certmanager_CertificateAdditionalOutputFormat_To_v1alpha2_CertificateAdditionalOutputFormat(a.(*certmanager.CertificateAdditionalOutputFormat), b.(*CertificateAdditionalOutputFormat), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*CertificateCondition)(nil), (*certmanager.CertificateCondition)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1alpha2_CertificateCondition_To_certmanager_CertificateCondition(a.(*CertificateCondition), b.(*certmanager.CertificateCondition), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*certmanager.CertificateCondition)(nil), (*CertificateCondition)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_certmanager_CertificateCondition_To_v1alpha2_CertificateCondition(a.(*certmanager.CertificateCondition), b.(*CertificateCondition), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*CertificateKeystores)(nil), (*certmanager.CertificateKeystores)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1alpha2_CertificateKeystores_To_certmanager_CertificateKeystores(a.(*CertificateKeystores), b.(*certmanager.CertificateKeystores), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*certmanager.CertificateKeystores)(nil), (*CertificateKeystores)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_certmanager_CertificateKeystores_To_v1alpha2_CertificateKeystores(a.(*certmanager.CertificateKeystores), b.(*CertificateKeystores), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*CertificateList)(nil), (*certmanager.CertificateList)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1alpha2_CertificateList_To_certmanager_CertificateList(a.(*CertificateList), b.(*certmanager.CertificateList), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*certmanager.CertificateList)(nil), (*CertificateList)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_certmanager_CertificateList_To_v1alpha2_CertificateList(a.(*certmanager.CertificateList), b.(*CertificateList), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*CertificatePrivateKey)(nil), (*certmanager.CertificatePrivateKey)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1alpha2_CertificatePrivateKey_To_certmanager_CertificatePrivateKey(a.(*CertificatePrivateKey), b.(*certmanager.CertificatePrivateKey), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*CertificateRequest)(nil), (*certmanager.CertificateRequest)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1alpha2_CertificateRequest_To_certmanager_CertificateRequest(a.(*CertificateRequest), b.(*certmanager.CertificateRequest), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*certmanager.CertificateRequest)(nil), (*CertificateRequest)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_certmanager_CertificateRequest_To_v1alpha2_CertificateRequest(a.(*certmanager.CertificateRequest), b.(*CertificateRequest), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*CertificateRequestCondition)(nil), (*certmanager.CertificateRequestCondition)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1alpha2_CertificateRequestCondition_To_certmanager_CertificateRequestCondition(a.(*CertificateRequestCondition), b.(*certmanager.CertificateRequestCondition), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*certmanager.CertificateRequestCondition)(nil), (*CertificateRequestCondition)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_certmanager_CertificateRequestCondition_To_v1alpha2_CertificateRequestCondition(a.(*certmanager.CertificateRequestCondition), b.(*CertificateRequestCondition), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*CertificateRequestList)(nil), (*certmanager.CertificateRequestList)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1alpha2_CertificateRequestList_To_certmanager_CertificateRequestList(a.(*CertificateRequestList), b.(*certmanager.CertificateRequestList), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*certmanager.CertificateRequestList)(nil), (*CertificateRequestList)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_certmanager_CertificateRequestList_To_v1alpha2_CertificateRequestList(a.(*certmanager.CertificateRequestList), b.(*CertificateRequestList), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*CertificateRequestStatus)(nil), (*certmanager.CertificateRequestStatus)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1alpha2_CertificateRequestStatus_To_certmanager_CertificateRequestStatus(a.(*CertificateRequestStatus), b.(*certmanager.CertificateRequestStatus), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*certmanager.CertificateRequestStatus)(nil), (*CertificateRequestStatus)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_certmanager_CertificateRequestStatus_To_v1alpha2_CertificateRequestStatus(a.(*certmanager.CertificateRequestStatus), b.(*CertificateRequestStatus), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*CertificateSecretTemplate)(nil), (*certmanager.CertificateSecretTemplate)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1alpha2_CertificateSecretTemplate_To_certmanager_CertificateSecretTemplate(a.(*CertificateSecretTemplate), b.(*certmanager.CertificateSecretTemplate), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*certmanager.CertificateSecretTemplate)(nil), (*CertificateSecretTemplate)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_certmanager_CertificateSecretTemplate_To_v1alpha2_CertificateSecretTemplate(a.(*certmanager.CertificateSecretTemplate), b.(*CertificateSecretTemplate), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*CertificateStatus)(nil), (*certmanager.CertificateStatus)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1alpha2_CertificateStatus_To_certmanager_CertificateStatus(a.(*CertificateStatus), b.(*certmanager.CertificateStatus), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*certmanager.CertificateStatus)(nil), (*CertificateStatus)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_certmanager_CertificateStatus_To_v1alpha2_CertificateStatus(a.(*certmanager.CertificateStatus), b.(*CertificateStatus), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*ClusterIssuer)(nil), (*certmanager.ClusterIssuer)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1alpha2_ClusterIssuer_To_certmanager_ClusterIssuer(a.(*ClusterIssuer), b.(*certmanager.ClusterIssuer), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*certmanager.ClusterIssuer)(nil), (*ClusterIssuer)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_certmanager_ClusterIssuer_To_v1alpha2_ClusterIssuer(a.(*certmanager.ClusterIssuer), b.(*ClusterIssuer), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*ClusterIssuerList)(nil), (*certmanager.ClusterIssuerList)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1alpha2_ClusterIssuerList_To_certmanager_ClusterIssuerList(a.(*ClusterIssuerList), b.(*certmanager.ClusterIssuerList), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*certmanager.ClusterIssuerList)(nil), (*ClusterIssuerList)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_certmanager_ClusterIssuerList_To_v1alpha2_ClusterIssuerList(a.(*certmanager.ClusterIssuerList), b.(*ClusterIssuerList), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*Issuer)(nil), (*certmanager.Issuer)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1alpha2_Issuer_To_certmanager_Issuer(a.(*Issuer), b.(*certmanager.Issuer), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*certmanager.Issuer)(nil), (*Issuer)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_certmanager_Issuer_To_v1alpha2_Issuer(a.(*certmanager.Issuer), b.(*Issuer), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*IssuerCondition)(nil), (*certmanager.IssuerCondition)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1alpha2_IssuerCondition_To_certmanager_IssuerCondition(a.(*IssuerCondition), b.(*certmanager.IssuerCondition), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*certmanager.IssuerCondition)(nil), (*IssuerCondition)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_certmanager_IssuerCondition_To_v1alpha2_IssuerCondition(a.(*certmanager.IssuerCondition), b.(*IssuerCondition), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*IssuerConfig)(nil), (*certmanager.IssuerConfig)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1alpha2_IssuerConfig_To_certmanager_IssuerConfig(a.(*IssuerConfig), b.(*certmanager.IssuerConfig), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*certmanager.IssuerConfig)(nil), (*IssuerConfig)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_certmanager_IssuerConfig_To_v1alpha2_IssuerConfig(a.(*certmanager.IssuerConfig), b.(*IssuerConfig), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*IssuerList)(nil), (*certmanager.IssuerList)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1alpha2_IssuerList_To_certmanager_IssuerList(a.(*IssuerList), b.(*certmanager.IssuerList), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*certmanager.IssuerList)(nil), (*IssuerList)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_certmanager_IssuerList_To_v1alpha2_IssuerList(a.(*certmanager.IssuerList), b.(*IssuerList), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*IssuerSpec)(nil), (*certmanager.IssuerSpec)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1alpha2_IssuerSpec_To_certmanager_IssuerSpec(a.(*IssuerSpec), b.(*certmanager.IssuerSpec), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*certmanager.IssuerSpec)(nil), (*IssuerSpec)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_certmanager_IssuerSpec_To_v1alpha2_IssuerSpec(a.(*certmanager.IssuerSpec), b.(*IssuerSpec), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*IssuerStatus)(nil), (*certmanager.IssuerStatus)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1alpha2_IssuerStatus_To_certmanager_IssuerStatus(a.(*IssuerStatus), b.(*certmanager.IssuerStatus), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*certmanager.IssuerStatus)(nil), (*IssuerStatus)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_certmanager_IssuerStatus_To_v1alpha2_IssuerStatus(a.(*certmanager.IssuerStatus), b.(*IssuerStatus), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*JKSKeystore)(nil), (*certmanager.JKSKeystore)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1alpha2_JKSKeystore_To_certmanager_JKSKeystore(a.(*JKSKeystore), b.(*certmanager.JKSKeystore), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*certmanager.JKSKeystore)(nil), (*JKSKeystore)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_certmanager_JKSKeystore_To_v1alpha2_JKSKeystore(a.(*certmanager.JKSKeystore), b.(*JKSKeystore), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*PKCS12Keystore)(nil), (*certmanager.PKCS12Keystore)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1alpha2_PKCS12Keystore_To_certmanager_PKCS12Keystore(a.(*PKCS12Keystore), b.(*certmanager.PKCS12Keystore), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*certmanager.PKCS12Keystore)(nil), (*PKCS12Keystore)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_certmanager_PKCS12Keystore_To_v1alpha2_PKCS12Keystore(a.(*certmanager.PKCS12Keystore), b.(*PKCS12Keystore), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*SelfSignedIssuer)(nil), (*certmanager.SelfSignedIssuer)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1alpha2_SelfSignedIssuer_To_certmanager_SelfSignedIssuer(a.(*SelfSignedIssuer), b.(*certmanager.SelfSignedIssuer), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*certmanager.SelfSignedIssuer)(nil), (*SelfSignedIssuer)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_certmanager_SelfSignedIssuer_To_v1alpha2_SelfSignedIssuer(a.(*certmanager.SelfSignedIssuer), b.(*SelfSignedIssuer), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*VaultAppRole)(nil), (*certmanager.VaultAppRole)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1alpha2_VaultAppRole_To_certmanager_VaultAppRole(a.(*VaultAppRole), b.(*certmanager.VaultAppRole), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*certmanager.VaultAppRole)(nil), (*VaultAppRole)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_certmanager_VaultAppRole_To_v1alpha2_VaultAppRole(a.(*certmanager.VaultAppRole), b.(*VaultAppRole), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*VaultAuth)(nil), (*certmanager.VaultAuth)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1alpha2_VaultAuth_To_certmanager_VaultAuth(a.(*VaultAuth), b.(*certmanager.VaultAuth), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*certmanager.VaultAuth)(nil), (*VaultAuth)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_certmanager_VaultAuth_To_v1alpha2_VaultAuth(a.(*certmanager.VaultAuth), b.(*VaultAuth), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*VaultIssuer)(nil), (*certmanager.VaultIssuer)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1alpha2_VaultIssuer_To_certmanager_VaultIssuer(a.(*VaultIssuer), b.(*certmanager.VaultIssuer), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*certmanager.VaultIssuer)(nil), (*VaultIssuer)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_certmanager_VaultIssuer_To_v1alpha2_VaultIssuer(a.(*certmanager.VaultIssuer), b.(*VaultIssuer), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*VaultKubernetesAuth)(nil), (*certmanager.VaultKubernetesAuth)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1alpha2_VaultKubernetesAuth_To_certmanager_VaultKubernetesAuth(a.(*VaultKubernetesAuth), b.(*certmanager.VaultKubernetesAuth), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*certmanager.VaultKubernetesAuth)(nil), (*VaultKubernetesAuth)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_certmanager_VaultKubernetesAuth_To_v1alpha2_VaultKubernetesAuth(a.(*certmanager.VaultKubernetesAuth), b.(*VaultKubernetesAuth), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*VenafiCloud)(nil), (*certmanager.VenafiCloud)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1alpha2_VenafiCloud_To_certmanager_VenafiCloud(a.(*VenafiCloud), b.(*certmanager.VenafiCloud), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*certmanager.VenafiCloud)(nil), (*VenafiCloud)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_certmanager_VenafiCloud_To_v1alpha2_VenafiCloud(a.(*certmanager.VenafiCloud), b.(*VenafiCloud), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*VenafiIssuer)(nil), (*certmanager.VenafiIssuer)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1alpha2_VenafiIssuer_To_certmanager_VenafiIssuer(a.(*VenafiIssuer), b.(*certmanager.VenafiIssuer), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*certmanager.VenafiIssuer)(nil), (*VenafiIssuer)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_certmanager_VenafiIssuer_To_v1alpha2_VenafiIssuer(a.(*certmanager.VenafiIssuer), b.(*VenafiIssuer), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*VenafiTPP)(nil), (*certmanager.VenafiTPP)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1alpha2_VenafiTPP_To_certmanager_VenafiTPP(a.(*VenafiTPP), b.(*certmanager.VenafiTPP), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*certmanager.VenafiTPP)(nil), (*VenafiTPP)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_certmanager_VenafiTPP_To_v1alpha2_VenafiTPP(a.(*certmanager.VenafiTPP), b.(*VenafiTPP), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*X509Subject)(nil), (*certmanager.X509Subject)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1alpha2_X509Subject_To_certmanager_X509Subject(a.(*X509Subject), b.(*certmanager.X509Subject), scope) - }); err != nil { - return err - } - if err := s.AddConversionFunc((*certmanager.CertificatePrivateKey)(nil), (*CertificatePrivateKey)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_certmanager_CertificatePrivateKey_To_v1alpha2_CertificatePrivateKey(a.(*certmanager.CertificatePrivateKey), b.(*CertificatePrivateKey), scope) - }); err != nil { - return err - } - if err := s.AddConversionFunc((*certmanager.CertificateRequestSpec)(nil), (*CertificateRequestSpec)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_certmanager_CertificateRequestSpec_To_v1alpha2_CertificateRequestSpec(a.(*certmanager.CertificateRequestSpec), b.(*CertificateRequestSpec), scope) - }); err != nil { - return err - } - if err := s.AddConversionFunc((*certmanager.CertificateSpec)(nil), (*CertificateSpec)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_certmanager_CertificateSpec_To_v1alpha2_CertificateSpec(a.(*certmanager.CertificateSpec), b.(*CertificateSpec), scope) - }); err != nil { - return err - } - if err := s.AddConversionFunc((*certmanager.X509Subject)(nil), (*X509Subject)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_certmanager_X509Subject_To_v1alpha2_X509Subject(a.(*certmanager.X509Subject), b.(*X509Subject), scope) - }); err != nil { - return err - } - if err := s.AddConversionFunc((*CertificateRequestSpec)(nil), (*certmanager.CertificateRequestSpec)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1alpha2_CertificateRequestSpec_To_certmanager_CertificateRequestSpec(a.(*CertificateRequestSpec), b.(*certmanager.CertificateRequestSpec), scope) - }); err != nil { - return err - } - if err := s.AddConversionFunc((*CertificateSpec)(nil), (*certmanager.CertificateSpec)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1alpha2_CertificateSpec_To_certmanager_CertificateSpec(a.(*CertificateSpec), b.(*certmanager.CertificateSpec), scope) - }); err != nil { - return err - } - return nil -} - -func autoConvert_v1alpha2_CAIssuer_To_certmanager_CAIssuer(in *CAIssuer, out *certmanager.CAIssuer, s conversion.Scope) error { - out.SecretName = in.SecretName - out.CRLDistributionPoints = *(*[]string)(unsafe.Pointer(&in.CRLDistributionPoints)) - out.OCSPServers = *(*[]string)(unsafe.Pointer(&in.OCSPServers)) - return nil -} - -// Convert_v1alpha2_CAIssuer_To_certmanager_CAIssuer is an autogenerated conversion function. -func Convert_v1alpha2_CAIssuer_To_certmanager_CAIssuer(in *CAIssuer, out *certmanager.CAIssuer, s conversion.Scope) error { - return autoConvert_v1alpha2_CAIssuer_To_certmanager_CAIssuer(in, out, s) -} - -func autoConvert_certmanager_CAIssuer_To_v1alpha2_CAIssuer(in *certmanager.CAIssuer, out *CAIssuer, s conversion.Scope) error { - out.SecretName = in.SecretName - out.CRLDistributionPoints = *(*[]string)(unsafe.Pointer(&in.CRLDistributionPoints)) - out.OCSPServers = *(*[]string)(unsafe.Pointer(&in.OCSPServers)) - return nil -} - -// Convert_certmanager_CAIssuer_To_v1alpha2_CAIssuer is an autogenerated conversion function. -func Convert_certmanager_CAIssuer_To_v1alpha2_CAIssuer(in *certmanager.CAIssuer, out *CAIssuer, s conversion.Scope) error { - return autoConvert_certmanager_CAIssuer_To_v1alpha2_CAIssuer(in, out, s) -} - -func autoConvert_v1alpha2_Certificate_To_certmanager_Certificate(in *Certificate, out *certmanager.Certificate, s conversion.Scope) error { - out.ObjectMeta = in.ObjectMeta - if err := Convert_v1alpha2_CertificateSpec_To_certmanager_CertificateSpec(&in.Spec, &out.Spec, s); err != nil { - return err - } - if err := Convert_v1alpha2_CertificateStatus_To_certmanager_CertificateStatus(&in.Status, &out.Status, s); err != nil { - return err - } - return nil -} - -// Convert_v1alpha2_Certificate_To_certmanager_Certificate is an autogenerated conversion function. -func Convert_v1alpha2_Certificate_To_certmanager_Certificate(in *Certificate, out *certmanager.Certificate, s conversion.Scope) error { - return autoConvert_v1alpha2_Certificate_To_certmanager_Certificate(in, out, s) -} - -func autoConvert_certmanager_Certificate_To_v1alpha2_Certificate(in *certmanager.Certificate, out *Certificate, s conversion.Scope) error { - out.ObjectMeta = in.ObjectMeta - if err := Convert_certmanager_CertificateSpec_To_v1alpha2_CertificateSpec(&in.Spec, &out.Spec, s); err != nil { - return err - } - if err := Convert_certmanager_CertificateStatus_To_v1alpha2_CertificateStatus(&in.Status, &out.Status, s); err != nil { - return err - } - return nil -} - -// Convert_certmanager_Certificate_To_v1alpha2_Certificate is an autogenerated conversion function. -func Convert_certmanager_Certificate_To_v1alpha2_Certificate(in *certmanager.Certificate, out *Certificate, s conversion.Scope) error { - return autoConvert_certmanager_Certificate_To_v1alpha2_Certificate(in, out, s) -} - -func autoConvert_v1alpha2_CertificateAdditionalOutputFormat_To_certmanager_CertificateAdditionalOutputFormat(in *CertificateAdditionalOutputFormat, out *certmanager.CertificateAdditionalOutputFormat, s conversion.Scope) error { - out.Type = certmanager.CertificateOutputFormatType(in.Type) - return nil -} - -// Convert_v1alpha2_CertificateAdditionalOutputFormat_To_certmanager_CertificateAdditionalOutputFormat is an autogenerated conversion function. -func Convert_v1alpha2_CertificateAdditionalOutputFormat_To_certmanager_CertificateAdditionalOutputFormat(in *CertificateAdditionalOutputFormat, out *certmanager.CertificateAdditionalOutputFormat, s conversion.Scope) error { - return autoConvert_v1alpha2_CertificateAdditionalOutputFormat_To_certmanager_CertificateAdditionalOutputFormat(in, out, s) -} - -func autoConvert_certmanager_CertificateAdditionalOutputFormat_To_v1alpha2_CertificateAdditionalOutputFormat(in *certmanager.CertificateAdditionalOutputFormat, out *CertificateAdditionalOutputFormat, s conversion.Scope) error { - out.Type = CertificateOutputFormatType(in.Type) - return nil -} - -// Convert_certmanager_CertificateAdditionalOutputFormat_To_v1alpha2_CertificateAdditionalOutputFormat is an autogenerated conversion function. -func Convert_certmanager_CertificateAdditionalOutputFormat_To_v1alpha2_CertificateAdditionalOutputFormat(in *certmanager.CertificateAdditionalOutputFormat, out *CertificateAdditionalOutputFormat, s conversion.Scope) error { - return autoConvert_certmanager_CertificateAdditionalOutputFormat_To_v1alpha2_CertificateAdditionalOutputFormat(in, out, s) -} - -func autoConvert_v1alpha2_CertificateCondition_To_certmanager_CertificateCondition(in *CertificateCondition, out *certmanager.CertificateCondition, s conversion.Scope) error { - out.Type = certmanager.CertificateConditionType(in.Type) - out.Status = meta.ConditionStatus(in.Status) - out.LastTransitionTime = (*v1.Time)(unsafe.Pointer(in.LastTransitionTime)) - out.Reason = in.Reason - out.Message = in.Message - out.ObservedGeneration = in.ObservedGeneration - return nil -} - -// Convert_v1alpha2_CertificateCondition_To_certmanager_CertificateCondition is an autogenerated conversion function. -func Convert_v1alpha2_CertificateCondition_To_certmanager_CertificateCondition(in *CertificateCondition, out *certmanager.CertificateCondition, s conversion.Scope) error { - return autoConvert_v1alpha2_CertificateCondition_To_certmanager_CertificateCondition(in, out, s) -} - -func autoConvert_certmanager_CertificateCondition_To_v1alpha2_CertificateCondition(in *certmanager.CertificateCondition, out *CertificateCondition, s conversion.Scope) error { - out.Type = CertificateConditionType(in.Type) - out.Status = metav1.ConditionStatus(in.Status) - out.LastTransitionTime = (*v1.Time)(unsafe.Pointer(in.LastTransitionTime)) - out.Reason = in.Reason - out.Message = in.Message - out.ObservedGeneration = in.ObservedGeneration - return nil -} - -// Convert_certmanager_CertificateCondition_To_v1alpha2_CertificateCondition is an autogenerated conversion function. -func Convert_certmanager_CertificateCondition_To_v1alpha2_CertificateCondition(in *certmanager.CertificateCondition, out *CertificateCondition, s conversion.Scope) error { - return autoConvert_certmanager_CertificateCondition_To_v1alpha2_CertificateCondition(in, out, s) -} - -func autoConvert_v1alpha2_CertificateKeystores_To_certmanager_CertificateKeystores(in *CertificateKeystores, out *certmanager.CertificateKeystores, s conversion.Scope) error { - if in.JKS != nil { - in, out := &in.JKS, &out.JKS - *out = new(certmanager.JKSKeystore) - if err := Convert_v1alpha2_JKSKeystore_To_certmanager_JKSKeystore(*in, *out, s); err != nil { - return err - } - } else { - out.JKS = nil - } - if in.PKCS12 != nil { - in, out := &in.PKCS12, &out.PKCS12 - *out = new(certmanager.PKCS12Keystore) - if err := Convert_v1alpha2_PKCS12Keystore_To_certmanager_PKCS12Keystore(*in, *out, s); err != nil { - return err - } - } else { - out.PKCS12 = nil - } - return nil -} - -// Convert_v1alpha2_CertificateKeystores_To_certmanager_CertificateKeystores is an autogenerated conversion function. -func Convert_v1alpha2_CertificateKeystores_To_certmanager_CertificateKeystores(in *CertificateKeystores, out *certmanager.CertificateKeystores, s conversion.Scope) error { - return autoConvert_v1alpha2_CertificateKeystores_To_certmanager_CertificateKeystores(in, out, s) -} - -func autoConvert_certmanager_CertificateKeystores_To_v1alpha2_CertificateKeystores(in *certmanager.CertificateKeystores, out *CertificateKeystores, s conversion.Scope) error { - if in.JKS != nil { - in, out := &in.JKS, &out.JKS - *out = new(JKSKeystore) - if err := Convert_certmanager_JKSKeystore_To_v1alpha2_JKSKeystore(*in, *out, s); err != nil { - return err - } - } else { - out.JKS = nil - } - if in.PKCS12 != nil { - in, out := &in.PKCS12, &out.PKCS12 - *out = new(PKCS12Keystore) - if err := Convert_certmanager_PKCS12Keystore_To_v1alpha2_PKCS12Keystore(*in, *out, s); err != nil { - return err - } - } else { - out.PKCS12 = nil - } - return nil -} - -// Convert_certmanager_CertificateKeystores_To_v1alpha2_CertificateKeystores is an autogenerated conversion function. -func Convert_certmanager_CertificateKeystores_To_v1alpha2_CertificateKeystores(in *certmanager.CertificateKeystores, out *CertificateKeystores, s conversion.Scope) error { - return autoConvert_certmanager_CertificateKeystores_To_v1alpha2_CertificateKeystores(in, out, s) -} - -func autoConvert_v1alpha2_CertificateList_To_certmanager_CertificateList(in *CertificateList, out *certmanager.CertificateList, s conversion.Scope) error { - out.ListMeta = in.ListMeta - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]certmanager.Certificate, len(*in)) - for i := range *in { - if err := Convert_v1alpha2_Certificate_To_certmanager_Certificate(&(*in)[i], &(*out)[i], s); err != nil { - return err - } - } - } else { - out.Items = nil - } - return nil -} - -// Convert_v1alpha2_CertificateList_To_certmanager_CertificateList is an autogenerated conversion function. -func Convert_v1alpha2_CertificateList_To_certmanager_CertificateList(in *CertificateList, out *certmanager.CertificateList, s conversion.Scope) error { - return autoConvert_v1alpha2_CertificateList_To_certmanager_CertificateList(in, out, s) -} - -func autoConvert_certmanager_CertificateList_To_v1alpha2_CertificateList(in *certmanager.CertificateList, out *CertificateList, s conversion.Scope) error { - out.ListMeta = in.ListMeta - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]Certificate, len(*in)) - for i := range *in { - if err := Convert_certmanager_Certificate_To_v1alpha2_Certificate(&(*in)[i], &(*out)[i], s); err != nil { - return err - } - } - } else { - out.Items = nil - } - return nil -} - -// Convert_certmanager_CertificateList_To_v1alpha2_CertificateList is an autogenerated conversion function. -func Convert_certmanager_CertificateList_To_v1alpha2_CertificateList(in *certmanager.CertificateList, out *CertificateList, s conversion.Scope) error { - return autoConvert_certmanager_CertificateList_To_v1alpha2_CertificateList(in, out, s) -} - -func autoConvert_v1alpha2_CertificatePrivateKey_To_certmanager_CertificatePrivateKey(in *CertificatePrivateKey, out *certmanager.CertificatePrivateKey, s conversion.Scope) error { - out.RotationPolicy = certmanager.PrivateKeyRotationPolicy(in.RotationPolicy) - return nil -} - -// Convert_v1alpha2_CertificatePrivateKey_To_certmanager_CertificatePrivateKey is an autogenerated conversion function. -func Convert_v1alpha2_CertificatePrivateKey_To_certmanager_CertificatePrivateKey(in *CertificatePrivateKey, out *certmanager.CertificatePrivateKey, s conversion.Scope) error { - return autoConvert_v1alpha2_CertificatePrivateKey_To_certmanager_CertificatePrivateKey(in, out, s) -} - -func autoConvert_certmanager_CertificatePrivateKey_To_v1alpha2_CertificatePrivateKey(in *certmanager.CertificatePrivateKey, out *CertificatePrivateKey, s conversion.Scope) error { - out.RotationPolicy = PrivateKeyRotationPolicy(in.RotationPolicy) - // WARNING: in.Encoding requires manual conversion: does not exist in peer-type - // WARNING: in.Algorithm requires manual conversion: does not exist in peer-type - // WARNING: in.Size requires manual conversion: does not exist in peer-type - return nil -} - -func autoConvert_v1alpha2_CertificateRequest_To_certmanager_CertificateRequest(in *CertificateRequest, out *certmanager.CertificateRequest, s conversion.Scope) error { - out.ObjectMeta = in.ObjectMeta - if err := Convert_v1alpha2_CertificateRequestSpec_To_certmanager_CertificateRequestSpec(&in.Spec, &out.Spec, s); err != nil { - return err - } - if err := Convert_v1alpha2_CertificateRequestStatus_To_certmanager_CertificateRequestStatus(&in.Status, &out.Status, s); err != nil { - return err - } - return nil -} - -// Convert_v1alpha2_CertificateRequest_To_certmanager_CertificateRequest is an autogenerated conversion function. -func Convert_v1alpha2_CertificateRequest_To_certmanager_CertificateRequest(in *CertificateRequest, out *certmanager.CertificateRequest, s conversion.Scope) error { - return autoConvert_v1alpha2_CertificateRequest_To_certmanager_CertificateRequest(in, out, s) -} - -func autoConvert_certmanager_CertificateRequest_To_v1alpha2_CertificateRequest(in *certmanager.CertificateRequest, out *CertificateRequest, s conversion.Scope) error { - out.ObjectMeta = in.ObjectMeta - if err := Convert_certmanager_CertificateRequestSpec_To_v1alpha2_CertificateRequestSpec(&in.Spec, &out.Spec, s); err != nil { - return err - } - if err := Convert_certmanager_CertificateRequestStatus_To_v1alpha2_CertificateRequestStatus(&in.Status, &out.Status, s); err != nil { - return err - } - return nil -} - -// Convert_certmanager_CertificateRequest_To_v1alpha2_CertificateRequest is an autogenerated conversion function. -func Convert_certmanager_CertificateRequest_To_v1alpha2_CertificateRequest(in *certmanager.CertificateRequest, out *CertificateRequest, s conversion.Scope) error { - return autoConvert_certmanager_CertificateRequest_To_v1alpha2_CertificateRequest(in, out, s) -} - -func autoConvert_v1alpha2_CertificateRequestCondition_To_certmanager_CertificateRequestCondition(in *CertificateRequestCondition, out *certmanager.CertificateRequestCondition, s conversion.Scope) error { - out.Type = certmanager.CertificateRequestConditionType(in.Type) - out.Status = meta.ConditionStatus(in.Status) - out.LastTransitionTime = (*v1.Time)(unsafe.Pointer(in.LastTransitionTime)) - out.Reason = in.Reason - out.Message = in.Message - return nil -} - -// Convert_v1alpha2_CertificateRequestCondition_To_certmanager_CertificateRequestCondition is an autogenerated conversion function. -func Convert_v1alpha2_CertificateRequestCondition_To_certmanager_CertificateRequestCondition(in *CertificateRequestCondition, out *certmanager.CertificateRequestCondition, s conversion.Scope) error { - return autoConvert_v1alpha2_CertificateRequestCondition_To_certmanager_CertificateRequestCondition(in, out, s) -} - -func autoConvert_certmanager_CertificateRequestCondition_To_v1alpha2_CertificateRequestCondition(in *certmanager.CertificateRequestCondition, out *CertificateRequestCondition, s conversion.Scope) error { - out.Type = CertificateRequestConditionType(in.Type) - out.Status = metav1.ConditionStatus(in.Status) - out.LastTransitionTime = (*v1.Time)(unsafe.Pointer(in.LastTransitionTime)) - out.Reason = in.Reason - out.Message = in.Message - return nil -} - -// Convert_certmanager_CertificateRequestCondition_To_v1alpha2_CertificateRequestCondition is an autogenerated conversion function. -func Convert_certmanager_CertificateRequestCondition_To_v1alpha2_CertificateRequestCondition(in *certmanager.CertificateRequestCondition, out *CertificateRequestCondition, s conversion.Scope) error { - return autoConvert_certmanager_CertificateRequestCondition_To_v1alpha2_CertificateRequestCondition(in, out, s) -} - -func autoConvert_v1alpha2_CertificateRequestList_To_certmanager_CertificateRequestList(in *CertificateRequestList, out *certmanager.CertificateRequestList, s conversion.Scope) error { - out.ListMeta = in.ListMeta - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]certmanager.CertificateRequest, len(*in)) - for i := range *in { - if err := Convert_v1alpha2_CertificateRequest_To_certmanager_CertificateRequest(&(*in)[i], &(*out)[i], s); err != nil { - return err - } - } - } else { - out.Items = nil - } - return nil -} - -// Convert_v1alpha2_CertificateRequestList_To_certmanager_CertificateRequestList is an autogenerated conversion function. -func Convert_v1alpha2_CertificateRequestList_To_certmanager_CertificateRequestList(in *CertificateRequestList, out *certmanager.CertificateRequestList, s conversion.Scope) error { - return autoConvert_v1alpha2_CertificateRequestList_To_certmanager_CertificateRequestList(in, out, s) -} - -func autoConvert_certmanager_CertificateRequestList_To_v1alpha2_CertificateRequestList(in *certmanager.CertificateRequestList, out *CertificateRequestList, s conversion.Scope) error { - out.ListMeta = in.ListMeta - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]CertificateRequest, len(*in)) - for i := range *in { - if err := Convert_certmanager_CertificateRequest_To_v1alpha2_CertificateRequest(&(*in)[i], &(*out)[i], s); err != nil { - return err - } - } - } else { - out.Items = nil - } - return nil -} - -// Convert_certmanager_CertificateRequestList_To_v1alpha2_CertificateRequestList is an autogenerated conversion function. -func Convert_certmanager_CertificateRequestList_To_v1alpha2_CertificateRequestList(in *certmanager.CertificateRequestList, out *CertificateRequestList, s conversion.Scope) error { - return autoConvert_certmanager_CertificateRequestList_To_v1alpha2_CertificateRequestList(in, out, s) -} - -func autoConvert_v1alpha2_CertificateRequestSpec_To_certmanager_CertificateRequestSpec(in *CertificateRequestSpec, out *certmanager.CertificateRequestSpec, s conversion.Scope) error { - out.Duration = (*v1.Duration)(unsafe.Pointer(in.Duration)) - if err := apismetav1.Convert_v1_ObjectReference_To_meta_ObjectReference(&in.IssuerRef, &out.IssuerRef, s); err != nil { - return err - } - // WARNING: in.CSRPEM requires manual conversion: does not exist in peer-type - out.IsCA = in.IsCA - out.Usages = *(*[]certmanager.KeyUsage)(unsafe.Pointer(&in.Usages)) - out.Username = in.Username - out.UID = in.UID - out.Groups = *(*[]string)(unsafe.Pointer(&in.Groups)) - out.Extra = *(*map[string][]string)(unsafe.Pointer(&in.Extra)) - return nil -} - -func autoConvert_certmanager_CertificateRequestSpec_To_v1alpha2_CertificateRequestSpec(in *certmanager.CertificateRequestSpec, out *CertificateRequestSpec, s conversion.Scope) error { - out.Duration = (*v1.Duration)(unsafe.Pointer(in.Duration)) - if err := apismetav1.Convert_meta_ObjectReference_To_v1_ObjectReference(&in.IssuerRef, &out.IssuerRef, s); err != nil { - return err - } - // WARNING: in.Request requires manual conversion: does not exist in peer-type - out.IsCA = in.IsCA - out.Usages = *(*[]KeyUsage)(unsafe.Pointer(&in.Usages)) - out.Username = in.Username - out.UID = in.UID - out.Groups = *(*[]string)(unsafe.Pointer(&in.Groups)) - out.Extra = *(*map[string][]string)(unsafe.Pointer(&in.Extra)) - return nil -} - -func autoConvert_v1alpha2_CertificateRequestStatus_To_certmanager_CertificateRequestStatus(in *CertificateRequestStatus, out *certmanager.CertificateRequestStatus, s conversion.Scope) error { - out.Conditions = *(*[]certmanager.CertificateRequestCondition)(unsafe.Pointer(&in.Conditions)) - out.Certificate = *(*[]byte)(unsafe.Pointer(&in.Certificate)) - out.CA = *(*[]byte)(unsafe.Pointer(&in.CA)) - out.FailureTime = (*v1.Time)(unsafe.Pointer(in.FailureTime)) - return nil -} - -// Convert_v1alpha2_CertificateRequestStatus_To_certmanager_CertificateRequestStatus is an autogenerated conversion function. -func Convert_v1alpha2_CertificateRequestStatus_To_certmanager_CertificateRequestStatus(in *CertificateRequestStatus, out *certmanager.CertificateRequestStatus, s conversion.Scope) error { - return autoConvert_v1alpha2_CertificateRequestStatus_To_certmanager_CertificateRequestStatus(in, out, s) -} - -func autoConvert_certmanager_CertificateRequestStatus_To_v1alpha2_CertificateRequestStatus(in *certmanager.CertificateRequestStatus, out *CertificateRequestStatus, s conversion.Scope) error { - out.Conditions = *(*[]CertificateRequestCondition)(unsafe.Pointer(&in.Conditions)) - out.Certificate = *(*[]byte)(unsafe.Pointer(&in.Certificate)) - out.CA = *(*[]byte)(unsafe.Pointer(&in.CA)) - out.FailureTime = (*v1.Time)(unsafe.Pointer(in.FailureTime)) - return nil -} - -// Convert_certmanager_CertificateRequestStatus_To_v1alpha2_CertificateRequestStatus is an autogenerated conversion function. -func Convert_certmanager_CertificateRequestStatus_To_v1alpha2_CertificateRequestStatus(in *certmanager.CertificateRequestStatus, out *CertificateRequestStatus, s conversion.Scope) error { - return autoConvert_certmanager_CertificateRequestStatus_To_v1alpha2_CertificateRequestStatus(in, out, s) -} - -func autoConvert_v1alpha2_CertificateSecretTemplate_To_certmanager_CertificateSecretTemplate(in *CertificateSecretTemplate, out *certmanager.CertificateSecretTemplate, s conversion.Scope) error { - out.Annotations = *(*map[string]string)(unsafe.Pointer(&in.Annotations)) - out.Labels = *(*map[string]string)(unsafe.Pointer(&in.Labels)) - return nil -} - -// Convert_v1alpha2_CertificateSecretTemplate_To_certmanager_CertificateSecretTemplate is an autogenerated conversion function. -func Convert_v1alpha2_CertificateSecretTemplate_To_certmanager_CertificateSecretTemplate(in *CertificateSecretTemplate, out *certmanager.CertificateSecretTemplate, s conversion.Scope) error { - return autoConvert_v1alpha2_CertificateSecretTemplate_To_certmanager_CertificateSecretTemplate(in, out, s) -} - -func autoConvert_certmanager_CertificateSecretTemplate_To_v1alpha2_CertificateSecretTemplate(in *certmanager.CertificateSecretTemplate, out *CertificateSecretTemplate, s conversion.Scope) error { - out.Annotations = *(*map[string]string)(unsafe.Pointer(&in.Annotations)) - out.Labels = *(*map[string]string)(unsafe.Pointer(&in.Labels)) - return nil -} - -// Convert_certmanager_CertificateSecretTemplate_To_v1alpha2_CertificateSecretTemplate is an autogenerated conversion function. -func Convert_certmanager_CertificateSecretTemplate_To_v1alpha2_CertificateSecretTemplate(in *certmanager.CertificateSecretTemplate, out *CertificateSecretTemplate, s conversion.Scope) error { - return autoConvert_certmanager_CertificateSecretTemplate_To_v1alpha2_CertificateSecretTemplate(in, out, s) -} - -func autoConvert_v1alpha2_CertificateSpec_To_certmanager_CertificateSpec(in *CertificateSpec, out *certmanager.CertificateSpec, s conversion.Scope) error { - if in.Subject != nil { - in, out := &in.Subject, &out.Subject - *out = new(certmanager.X509Subject) - if err := Convert_v1alpha2_X509Subject_To_certmanager_X509Subject(*in, *out, s); err != nil { - return err - } - } else { - out.Subject = nil - } - out.LiteralSubject = in.LiteralSubject - out.CommonName = in.CommonName - // WARNING: in.Organization requires manual conversion: does not exist in peer-type - out.Duration = (*v1.Duration)(unsafe.Pointer(in.Duration)) - out.RenewBefore = (*v1.Duration)(unsafe.Pointer(in.RenewBefore)) - out.DNSNames = *(*[]string)(unsafe.Pointer(&in.DNSNames)) - out.IPAddresses = *(*[]string)(unsafe.Pointer(&in.IPAddresses)) - out.URISANs = *(*[]string)(unsafe.Pointer(&in.URISANs)) - out.EmailSANs = *(*[]string)(unsafe.Pointer(&in.EmailSANs)) - out.SecretName = in.SecretName - out.SecretTemplate = (*certmanager.CertificateSecretTemplate)(unsafe.Pointer(in.SecretTemplate)) - if in.Keystores != nil { - in, out := &in.Keystores, &out.Keystores - *out = new(certmanager.CertificateKeystores) - if err := Convert_v1alpha2_CertificateKeystores_To_certmanager_CertificateKeystores(*in, *out, s); err != nil { - return err - } - } else { - out.Keystores = nil - } - if err := apismetav1.Convert_v1_ObjectReference_To_meta_ObjectReference(&in.IssuerRef, &out.IssuerRef, s); err != nil { - return err - } - out.IsCA = in.IsCA - out.Usages = *(*[]certmanager.KeyUsage)(unsafe.Pointer(&in.Usages)) - // WARNING: in.KeySize requires manual conversion: does not exist in peer-type - // WARNING: in.KeyAlgorithm requires manual conversion: does not exist in peer-type - // WARNING: in.KeyEncoding requires manual conversion: does not exist in peer-type - if in.PrivateKey != nil { - in, out := &in.PrivateKey, &out.PrivateKey - *out = new(certmanager.CertificatePrivateKey) - if err := Convert_v1alpha2_CertificatePrivateKey_To_certmanager_CertificatePrivateKey(*in, *out, s); err != nil { - return err - } - } else { - out.PrivateKey = nil - } - out.EncodeUsagesInRequest = (*bool)(unsafe.Pointer(in.EncodeUsagesInRequest)) - out.RevisionHistoryLimit = (*int32)(unsafe.Pointer(in.RevisionHistoryLimit)) - out.AdditionalOutputFormats = *(*[]certmanager.CertificateAdditionalOutputFormat)(unsafe.Pointer(&in.AdditionalOutputFormats)) - return nil -} - -func autoConvert_certmanager_CertificateSpec_To_v1alpha2_CertificateSpec(in *certmanager.CertificateSpec, out *CertificateSpec, s conversion.Scope) error { - if in.Subject != nil { - in, out := &in.Subject, &out.Subject - *out = new(X509Subject) - if err := Convert_certmanager_X509Subject_To_v1alpha2_X509Subject(*in, *out, s); err != nil { - return err - } - } else { - out.Subject = nil - } - out.LiteralSubject = in.LiteralSubject - out.CommonName = in.CommonName - out.Duration = (*v1.Duration)(unsafe.Pointer(in.Duration)) - out.RenewBefore = (*v1.Duration)(unsafe.Pointer(in.RenewBefore)) - out.DNSNames = *(*[]string)(unsafe.Pointer(&in.DNSNames)) - out.IPAddresses = *(*[]string)(unsafe.Pointer(&in.IPAddresses)) - out.URISANs = *(*[]string)(unsafe.Pointer(&in.URISANs)) - out.EmailSANs = *(*[]string)(unsafe.Pointer(&in.EmailSANs)) - out.SecretName = in.SecretName - out.SecretTemplate = (*CertificateSecretTemplate)(unsafe.Pointer(in.SecretTemplate)) - if in.Keystores != nil { - in, out := &in.Keystores, &out.Keystores - *out = new(CertificateKeystores) - if err := Convert_certmanager_CertificateKeystores_To_v1alpha2_CertificateKeystores(*in, *out, s); err != nil { - return err - } - } else { - out.Keystores = nil - } - if err := apismetav1.Convert_meta_ObjectReference_To_v1_ObjectReference(&in.IssuerRef, &out.IssuerRef, s); err != nil { - return err - } - out.IsCA = in.IsCA - out.Usages = *(*[]KeyUsage)(unsafe.Pointer(&in.Usages)) - if in.PrivateKey != nil { - in, out := &in.PrivateKey, &out.PrivateKey - *out = new(CertificatePrivateKey) - if err := Convert_certmanager_CertificatePrivateKey_To_v1alpha2_CertificatePrivateKey(*in, *out, s); err != nil { - return err - } - } else { - out.PrivateKey = nil - } - out.EncodeUsagesInRequest = (*bool)(unsafe.Pointer(in.EncodeUsagesInRequest)) - out.RevisionHistoryLimit = (*int32)(unsafe.Pointer(in.RevisionHistoryLimit)) - out.AdditionalOutputFormats = *(*[]CertificateAdditionalOutputFormat)(unsafe.Pointer(&in.AdditionalOutputFormats)) - return nil -} - -func autoConvert_v1alpha2_CertificateStatus_To_certmanager_CertificateStatus(in *CertificateStatus, out *certmanager.CertificateStatus, s conversion.Scope) error { - out.Conditions = *(*[]certmanager.CertificateCondition)(unsafe.Pointer(&in.Conditions)) - out.LastFailureTime = (*v1.Time)(unsafe.Pointer(in.LastFailureTime)) - out.NotBefore = (*v1.Time)(unsafe.Pointer(in.NotBefore)) - out.NotAfter = (*v1.Time)(unsafe.Pointer(in.NotAfter)) - out.RenewalTime = (*v1.Time)(unsafe.Pointer(in.RenewalTime)) - out.Revision = (*int)(unsafe.Pointer(in.Revision)) - out.NextPrivateKeySecretName = (*string)(unsafe.Pointer(in.NextPrivateKeySecretName)) - out.FailedIssuanceAttempts = (*int)(unsafe.Pointer(in.FailedIssuanceAttempts)) - return nil -} - -// Convert_v1alpha2_CertificateStatus_To_certmanager_CertificateStatus is an autogenerated conversion function. -func Convert_v1alpha2_CertificateStatus_To_certmanager_CertificateStatus(in *CertificateStatus, out *certmanager.CertificateStatus, s conversion.Scope) error { - return autoConvert_v1alpha2_CertificateStatus_To_certmanager_CertificateStatus(in, out, s) -} - -func autoConvert_certmanager_CertificateStatus_To_v1alpha2_CertificateStatus(in *certmanager.CertificateStatus, out *CertificateStatus, s conversion.Scope) error { - out.Conditions = *(*[]CertificateCondition)(unsafe.Pointer(&in.Conditions)) - out.LastFailureTime = (*v1.Time)(unsafe.Pointer(in.LastFailureTime)) - out.NotBefore = (*v1.Time)(unsafe.Pointer(in.NotBefore)) - out.NotAfter = (*v1.Time)(unsafe.Pointer(in.NotAfter)) - out.RenewalTime = (*v1.Time)(unsafe.Pointer(in.RenewalTime)) - out.Revision = (*int)(unsafe.Pointer(in.Revision)) - out.NextPrivateKeySecretName = (*string)(unsafe.Pointer(in.NextPrivateKeySecretName)) - out.FailedIssuanceAttempts = (*int)(unsafe.Pointer(in.FailedIssuanceAttempts)) - return nil -} - -// Convert_certmanager_CertificateStatus_To_v1alpha2_CertificateStatus is an autogenerated conversion function. -func Convert_certmanager_CertificateStatus_To_v1alpha2_CertificateStatus(in *certmanager.CertificateStatus, out *CertificateStatus, s conversion.Scope) error { - return autoConvert_certmanager_CertificateStatus_To_v1alpha2_CertificateStatus(in, out, s) -} - -func autoConvert_v1alpha2_ClusterIssuer_To_certmanager_ClusterIssuer(in *ClusterIssuer, out *certmanager.ClusterIssuer, s conversion.Scope) error { - out.ObjectMeta = in.ObjectMeta - if err := Convert_v1alpha2_IssuerSpec_To_certmanager_IssuerSpec(&in.Spec, &out.Spec, s); err != nil { - return err - } - if err := Convert_v1alpha2_IssuerStatus_To_certmanager_IssuerStatus(&in.Status, &out.Status, s); err != nil { - return err - } - return nil -} - -// Convert_v1alpha2_ClusterIssuer_To_certmanager_ClusterIssuer is an autogenerated conversion function. -func Convert_v1alpha2_ClusterIssuer_To_certmanager_ClusterIssuer(in *ClusterIssuer, out *certmanager.ClusterIssuer, s conversion.Scope) error { - return autoConvert_v1alpha2_ClusterIssuer_To_certmanager_ClusterIssuer(in, out, s) -} - -func autoConvert_certmanager_ClusterIssuer_To_v1alpha2_ClusterIssuer(in *certmanager.ClusterIssuer, out *ClusterIssuer, s conversion.Scope) error { - out.ObjectMeta = in.ObjectMeta - if err := Convert_certmanager_IssuerSpec_To_v1alpha2_IssuerSpec(&in.Spec, &out.Spec, s); err != nil { - return err - } - if err := Convert_certmanager_IssuerStatus_To_v1alpha2_IssuerStatus(&in.Status, &out.Status, s); err != nil { - return err - } - return nil -} - -// Convert_certmanager_ClusterIssuer_To_v1alpha2_ClusterIssuer is an autogenerated conversion function. -func Convert_certmanager_ClusterIssuer_To_v1alpha2_ClusterIssuer(in *certmanager.ClusterIssuer, out *ClusterIssuer, s conversion.Scope) error { - return autoConvert_certmanager_ClusterIssuer_To_v1alpha2_ClusterIssuer(in, out, s) -} - -func autoConvert_v1alpha2_ClusterIssuerList_To_certmanager_ClusterIssuerList(in *ClusterIssuerList, out *certmanager.ClusterIssuerList, s conversion.Scope) error { - out.ListMeta = in.ListMeta - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]certmanager.ClusterIssuer, len(*in)) - for i := range *in { - if err := Convert_v1alpha2_ClusterIssuer_To_certmanager_ClusterIssuer(&(*in)[i], &(*out)[i], s); err != nil { - return err - } - } - } else { - out.Items = nil - } - return nil -} - -// Convert_v1alpha2_ClusterIssuerList_To_certmanager_ClusterIssuerList is an autogenerated conversion function. -func Convert_v1alpha2_ClusterIssuerList_To_certmanager_ClusterIssuerList(in *ClusterIssuerList, out *certmanager.ClusterIssuerList, s conversion.Scope) error { - return autoConvert_v1alpha2_ClusterIssuerList_To_certmanager_ClusterIssuerList(in, out, s) -} - -func autoConvert_certmanager_ClusterIssuerList_To_v1alpha2_ClusterIssuerList(in *certmanager.ClusterIssuerList, out *ClusterIssuerList, s conversion.Scope) error { - out.ListMeta = in.ListMeta - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]ClusterIssuer, len(*in)) - for i := range *in { - if err := Convert_certmanager_ClusterIssuer_To_v1alpha2_ClusterIssuer(&(*in)[i], &(*out)[i], s); err != nil { - return err - } - } - } else { - out.Items = nil - } - return nil -} - -// Convert_certmanager_ClusterIssuerList_To_v1alpha2_ClusterIssuerList is an autogenerated conversion function. -func Convert_certmanager_ClusterIssuerList_To_v1alpha2_ClusterIssuerList(in *certmanager.ClusterIssuerList, out *ClusterIssuerList, s conversion.Scope) error { - return autoConvert_certmanager_ClusterIssuerList_To_v1alpha2_ClusterIssuerList(in, out, s) -} - -func autoConvert_v1alpha2_Issuer_To_certmanager_Issuer(in *Issuer, out *certmanager.Issuer, s conversion.Scope) error { - out.ObjectMeta = in.ObjectMeta - if err := Convert_v1alpha2_IssuerSpec_To_certmanager_IssuerSpec(&in.Spec, &out.Spec, s); err != nil { - return err - } - if err := Convert_v1alpha2_IssuerStatus_To_certmanager_IssuerStatus(&in.Status, &out.Status, s); err != nil { - return err - } - return nil -} - -// Convert_v1alpha2_Issuer_To_certmanager_Issuer is an autogenerated conversion function. -func Convert_v1alpha2_Issuer_To_certmanager_Issuer(in *Issuer, out *certmanager.Issuer, s conversion.Scope) error { - return autoConvert_v1alpha2_Issuer_To_certmanager_Issuer(in, out, s) -} - -func autoConvert_certmanager_Issuer_To_v1alpha2_Issuer(in *certmanager.Issuer, out *Issuer, s conversion.Scope) error { - out.ObjectMeta = in.ObjectMeta - if err := Convert_certmanager_IssuerSpec_To_v1alpha2_IssuerSpec(&in.Spec, &out.Spec, s); err != nil { - return err - } - if err := Convert_certmanager_IssuerStatus_To_v1alpha2_IssuerStatus(&in.Status, &out.Status, s); err != nil { - return err - } - return nil -} - -// Convert_certmanager_Issuer_To_v1alpha2_Issuer is an autogenerated conversion function. -func Convert_certmanager_Issuer_To_v1alpha2_Issuer(in *certmanager.Issuer, out *Issuer, s conversion.Scope) error { - return autoConvert_certmanager_Issuer_To_v1alpha2_Issuer(in, out, s) -} - -func autoConvert_v1alpha2_IssuerCondition_To_certmanager_IssuerCondition(in *IssuerCondition, out *certmanager.IssuerCondition, s conversion.Scope) error { - out.Type = certmanager.IssuerConditionType(in.Type) - out.Status = meta.ConditionStatus(in.Status) - out.LastTransitionTime = (*v1.Time)(unsafe.Pointer(in.LastTransitionTime)) - out.Reason = in.Reason - out.Message = in.Message - out.ObservedGeneration = in.ObservedGeneration - return nil -} - -// Convert_v1alpha2_IssuerCondition_To_certmanager_IssuerCondition is an autogenerated conversion function. -func Convert_v1alpha2_IssuerCondition_To_certmanager_IssuerCondition(in *IssuerCondition, out *certmanager.IssuerCondition, s conversion.Scope) error { - return autoConvert_v1alpha2_IssuerCondition_To_certmanager_IssuerCondition(in, out, s) -} - -func autoConvert_certmanager_IssuerCondition_To_v1alpha2_IssuerCondition(in *certmanager.IssuerCondition, out *IssuerCondition, s conversion.Scope) error { - out.Type = IssuerConditionType(in.Type) - out.Status = metav1.ConditionStatus(in.Status) - out.LastTransitionTime = (*v1.Time)(unsafe.Pointer(in.LastTransitionTime)) - out.Reason = in.Reason - out.Message = in.Message - out.ObservedGeneration = in.ObservedGeneration - return nil -} - -// Convert_certmanager_IssuerCondition_To_v1alpha2_IssuerCondition is an autogenerated conversion function. -func Convert_certmanager_IssuerCondition_To_v1alpha2_IssuerCondition(in *certmanager.IssuerCondition, out *IssuerCondition, s conversion.Scope) error { - return autoConvert_certmanager_IssuerCondition_To_v1alpha2_IssuerCondition(in, out, s) -} - -func autoConvert_v1alpha2_IssuerConfig_To_certmanager_IssuerConfig(in *IssuerConfig, out *certmanager.IssuerConfig, s conversion.Scope) error { - if in.ACME != nil { - in, out := &in.ACME, &out.ACME - *out = new(acme.ACMEIssuer) - if err := acmev1alpha2.Convert_v1alpha2_ACMEIssuer_To_acme_ACMEIssuer(*in, *out, s); err != nil { - return err - } - } else { - out.ACME = nil - } - out.CA = (*certmanager.CAIssuer)(unsafe.Pointer(in.CA)) - if in.Vault != nil { - in, out := &in.Vault, &out.Vault - *out = new(certmanager.VaultIssuer) - if err := Convert_v1alpha2_VaultIssuer_To_certmanager_VaultIssuer(*in, *out, s); err != nil { - return err - } - } else { - out.Vault = nil - } - out.SelfSigned = (*certmanager.SelfSignedIssuer)(unsafe.Pointer(in.SelfSigned)) - if in.Venafi != nil { - in, out := &in.Venafi, &out.Venafi - *out = new(certmanager.VenafiIssuer) - if err := Convert_v1alpha2_VenafiIssuer_To_certmanager_VenafiIssuer(*in, *out, s); err != nil { - return err - } - } else { - out.Venafi = nil - } - return nil -} - -// Convert_v1alpha2_IssuerConfig_To_certmanager_IssuerConfig is an autogenerated conversion function. -func Convert_v1alpha2_IssuerConfig_To_certmanager_IssuerConfig(in *IssuerConfig, out *certmanager.IssuerConfig, s conversion.Scope) error { - return autoConvert_v1alpha2_IssuerConfig_To_certmanager_IssuerConfig(in, out, s) -} - -func autoConvert_certmanager_IssuerConfig_To_v1alpha2_IssuerConfig(in *certmanager.IssuerConfig, out *IssuerConfig, s conversion.Scope) error { - if in.ACME != nil { - in, out := &in.ACME, &out.ACME - *out = new(acmev1alpha2.ACMEIssuer) - if err := acmev1alpha2.Convert_acme_ACMEIssuer_To_v1alpha2_ACMEIssuer(*in, *out, s); err != nil { - return err - } - } else { - out.ACME = nil - } - out.CA = (*CAIssuer)(unsafe.Pointer(in.CA)) - if in.Vault != nil { - in, out := &in.Vault, &out.Vault - *out = new(VaultIssuer) - if err := Convert_certmanager_VaultIssuer_To_v1alpha2_VaultIssuer(*in, *out, s); err != nil { - return err - } - } else { - out.Vault = nil - } - out.SelfSigned = (*SelfSignedIssuer)(unsafe.Pointer(in.SelfSigned)) - if in.Venafi != nil { - in, out := &in.Venafi, &out.Venafi - *out = new(VenafiIssuer) - if err := Convert_certmanager_VenafiIssuer_To_v1alpha2_VenafiIssuer(*in, *out, s); err != nil { - return err - } - } else { - out.Venafi = nil - } - return nil -} - -// Convert_certmanager_IssuerConfig_To_v1alpha2_IssuerConfig is an autogenerated conversion function. -func Convert_certmanager_IssuerConfig_To_v1alpha2_IssuerConfig(in *certmanager.IssuerConfig, out *IssuerConfig, s conversion.Scope) error { - return autoConvert_certmanager_IssuerConfig_To_v1alpha2_IssuerConfig(in, out, s) -} - -func autoConvert_v1alpha2_IssuerList_To_certmanager_IssuerList(in *IssuerList, out *certmanager.IssuerList, s conversion.Scope) error { - out.ListMeta = in.ListMeta - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]certmanager.Issuer, len(*in)) - for i := range *in { - if err := Convert_v1alpha2_Issuer_To_certmanager_Issuer(&(*in)[i], &(*out)[i], s); err != nil { - return err - } - } - } else { - out.Items = nil - } - return nil -} - -// Convert_v1alpha2_IssuerList_To_certmanager_IssuerList is an autogenerated conversion function. -func Convert_v1alpha2_IssuerList_To_certmanager_IssuerList(in *IssuerList, out *certmanager.IssuerList, s conversion.Scope) error { - return autoConvert_v1alpha2_IssuerList_To_certmanager_IssuerList(in, out, s) -} - -func autoConvert_certmanager_IssuerList_To_v1alpha2_IssuerList(in *certmanager.IssuerList, out *IssuerList, s conversion.Scope) error { - out.ListMeta = in.ListMeta - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]Issuer, len(*in)) - for i := range *in { - if err := Convert_certmanager_Issuer_To_v1alpha2_Issuer(&(*in)[i], &(*out)[i], s); err != nil { - return err - } - } - } else { - out.Items = nil - } - return nil -} - -// Convert_certmanager_IssuerList_To_v1alpha2_IssuerList is an autogenerated conversion function. -func Convert_certmanager_IssuerList_To_v1alpha2_IssuerList(in *certmanager.IssuerList, out *IssuerList, s conversion.Scope) error { - return autoConvert_certmanager_IssuerList_To_v1alpha2_IssuerList(in, out, s) -} - -func autoConvert_v1alpha2_IssuerSpec_To_certmanager_IssuerSpec(in *IssuerSpec, out *certmanager.IssuerSpec, s conversion.Scope) error { - if err := Convert_v1alpha2_IssuerConfig_To_certmanager_IssuerConfig(&in.IssuerConfig, &out.IssuerConfig, s); err != nil { - return err - } - return nil -} - -// Convert_v1alpha2_IssuerSpec_To_certmanager_IssuerSpec is an autogenerated conversion function. -func Convert_v1alpha2_IssuerSpec_To_certmanager_IssuerSpec(in *IssuerSpec, out *certmanager.IssuerSpec, s conversion.Scope) error { - return autoConvert_v1alpha2_IssuerSpec_To_certmanager_IssuerSpec(in, out, s) -} - -func autoConvert_certmanager_IssuerSpec_To_v1alpha2_IssuerSpec(in *certmanager.IssuerSpec, out *IssuerSpec, s conversion.Scope) error { - if err := Convert_certmanager_IssuerConfig_To_v1alpha2_IssuerConfig(&in.IssuerConfig, &out.IssuerConfig, s); err != nil { - return err - } - return nil -} - -// Convert_certmanager_IssuerSpec_To_v1alpha2_IssuerSpec is an autogenerated conversion function. -func Convert_certmanager_IssuerSpec_To_v1alpha2_IssuerSpec(in *certmanager.IssuerSpec, out *IssuerSpec, s conversion.Scope) error { - return autoConvert_certmanager_IssuerSpec_To_v1alpha2_IssuerSpec(in, out, s) -} - -func autoConvert_v1alpha2_IssuerStatus_To_certmanager_IssuerStatus(in *IssuerStatus, out *certmanager.IssuerStatus, s conversion.Scope) error { - out.Conditions = *(*[]certmanager.IssuerCondition)(unsafe.Pointer(&in.Conditions)) - out.ACME = (*acme.ACMEIssuerStatus)(unsafe.Pointer(in.ACME)) - return nil -} - -// Convert_v1alpha2_IssuerStatus_To_certmanager_IssuerStatus is an autogenerated conversion function. -func Convert_v1alpha2_IssuerStatus_To_certmanager_IssuerStatus(in *IssuerStatus, out *certmanager.IssuerStatus, s conversion.Scope) error { - return autoConvert_v1alpha2_IssuerStatus_To_certmanager_IssuerStatus(in, out, s) -} - -func autoConvert_certmanager_IssuerStatus_To_v1alpha2_IssuerStatus(in *certmanager.IssuerStatus, out *IssuerStatus, s conversion.Scope) error { - out.Conditions = *(*[]IssuerCondition)(unsafe.Pointer(&in.Conditions)) - out.ACME = (*acmev1alpha2.ACMEIssuerStatus)(unsafe.Pointer(in.ACME)) - return nil -} - -// Convert_certmanager_IssuerStatus_To_v1alpha2_IssuerStatus is an autogenerated conversion function. -func Convert_certmanager_IssuerStatus_To_v1alpha2_IssuerStatus(in *certmanager.IssuerStatus, out *IssuerStatus, s conversion.Scope) error { - return autoConvert_certmanager_IssuerStatus_To_v1alpha2_IssuerStatus(in, out, s) -} - -func autoConvert_v1alpha2_JKSKeystore_To_certmanager_JKSKeystore(in *JKSKeystore, out *certmanager.JKSKeystore, s conversion.Scope) error { - out.Create = in.Create - if err := apismetav1.Convert_v1_SecretKeySelector_To_meta_SecretKeySelector(&in.PasswordSecretRef, &out.PasswordSecretRef, s); err != nil { - return err - } - return nil -} - -// Convert_v1alpha2_JKSKeystore_To_certmanager_JKSKeystore is an autogenerated conversion function. -func Convert_v1alpha2_JKSKeystore_To_certmanager_JKSKeystore(in *JKSKeystore, out *certmanager.JKSKeystore, s conversion.Scope) error { - return autoConvert_v1alpha2_JKSKeystore_To_certmanager_JKSKeystore(in, out, s) -} - -func autoConvert_certmanager_JKSKeystore_To_v1alpha2_JKSKeystore(in *certmanager.JKSKeystore, out *JKSKeystore, s conversion.Scope) error { - out.Create = in.Create - if err := apismetav1.Convert_meta_SecretKeySelector_To_v1_SecretKeySelector(&in.PasswordSecretRef, &out.PasswordSecretRef, s); err != nil { - return err - } - return nil -} - -// Convert_certmanager_JKSKeystore_To_v1alpha2_JKSKeystore is an autogenerated conversion function. -func Convert_certmanager_JKSKeystore_To_v1alpha2_JKSKeystore(in *certmanager.JKSKeystore, out *JKSKeystore, s conversion.Scope) error { - return autoConvert_certmanager_JKSKeystore_To_v1alpha2_JKSKeystore(in, out, s) -} - -func autoConvert_v1alpha2_PKCS12Keystore_To_certmanager_PKCS12Keystore(in *PKCS12Keystore, out *certmanager.PKCS12Keystore, s conversion.Scope) error { - out.Create = in.Create - if err := apismetav1.Convert_v1_SecretKeySelector_To_meta_SecretKeySelector(&in.PasswordSecretRef, &out.PasswordSecretRef, s); err != nil { - return err - } - return nil -} - -// Convert_v1alpha2_PKCS12Keystore_To_certmanager_PKCS12Keystore is an autogenerated conversion function. -func Convert_v1alpha2_PKCS12Keystore_To_certmanager_PKCS12Keystore(in *PKCS12Keystore, out *certmanager.PKCS12Keystore, s conversion.Scope) error { - return autoConvert_v1alpha2_PKCS12Keystore_To_certmanager_PKCS12Keystore(in, out, s) -} - -func autoConvert_certmanager_PKCS12Keystore_To_v1alpha2_PKCS12Keystore(in *certmanager.PKCS12Keystore, out *PKCS12Keystore, s conversion.Scope) error { - out.Create = in.Create - if err := apismetav1.Convert_meta_SecretKeySelector_To_v1_SecretKeySelector(&in.PasswordSecretRef, &out.PasswordSecretRef, s); err != nil { - return err - } - return nil -} - -// Convert_certmanager_PKCS12Keystore_To_v1alpha2_PKCS12Keystore is an autogenerated conversion function. -func Convert_certmanager_PKCS12Keystore_To_v1alpha2_PKCS12Keystore(in *certmanager.PKCS12Keystore, out *PKCS12Keystore, s conversion.Scope) error { - return autoConvert_certmanager_PKCS12Keystore_To_v1alpha2_PKCS12Keystore(in, out, s) -} - -func autoConvert_v1alpha2_SelfSignedIssuer_To_certmanager_SelfSignedIssuer(in *SelfSignedIssuer, out *certmanager.SelfSignedIssuer, s conversion.Scope) error { - out.CRLDistributionPoints = *(*[]string)(unsafe.Pointer(&in.CRLDistributionPoints)) - return nil -} - -// Convert_v1alpha2_SelfSignedIssuer_To_certmanager_SelfSignedIssuer is an autogenerated conversion function. -func Convert_v1alpha2_SelfSignedIssuer_To_certmanager_SelfSignedIssuer(in *SelfSignedIssuer, out *certmanager.SelfSignedIssuer, s conversion.Scope) error { - return autoConvert_v1alpha2_SelfSignedIssuer_To_certmanager_SelfSignedIssuer(in, out, s) -} - -func autoConvert_certmanager_SelfSignedIssuer_To_v1alpha2_SelfSignedIssuer(in *certmanager.SelfSignedIssuer, out *SelfSignedIssuer, s conversion.Scope) error { - out.CRLDistributionPoints = *(*[]string)(unsafe.Pointer(&in.CRLDistributionPoints)) - return nil -} - -// Convert_certmanager_SelfSignedIssuer_To_v1alpha2_SelfSignedIssuer is an autogenerated conversion function. -func Convert_certmanager_SelfSignedIssuer_To_v1alpha2_SelfSignedIssuer(in *certmanager.SelfSignedIssuer, out *SelfSignedIssuer, s conversion.Scope) error { - return autoConvert_certmanager_SelfSignedIssuer_To_v1alpha2_SelfSignedIssuer(in, out, s) -} - -func autoConvert_v1alpha2_VaultAppRole_To_certmanager_VaultAppRole(in *VaultAppRole, out *certmanager.VaultAppRole, s conversion.Scope) error { - out.Path = in.Path - out.RoleId = in.RoleId - if err := apismetav1.Convert_v1_SecretKeySelector_To_meta_SecretKeySelector(&in.SecretRef, &out.SecretRef, s); err != nil { - return err - } - return nil -} - -// Convert_v1alpha2_VaultAppRole_To_certmanager_VaultAppRole is an autogenerated conversion function. -func Convert_v1alpha2_VaultAppRole_To_certmanager_VaultAppRole(in *VaultAppRole, out *certmanager.VaultAppRole, s conversion.Scope) error { - return autoConvert_v1alpha2_VaultAppRole_To_certmanager_VaultAppRole(in, out, s) -} - -func autoConvert_certmanager_VaultAppRole_To_v1alpha2_VaultAppRole(in *certmanager.VaultAppRole, out *VaultAppRole, s conversion.Scope) error { - out.Path = in.Path - out.RoleId = in.RoleId - if err := apismetav1.Convert_meta_SecretKeySelector_To_v1_SecretKeySelector(&in.SecretRef, &out.SecretRef, s); err != nil { - return err - } - return nil -} - -// Convert_certmanager_VaultAppRole_To_v1alpha2_VaultAppRole is an autogenerated conversion function. -func Convert_certmanager_VaultAppRole_To_v1alpha2_VaultAppRole(in *certmanager.VaultAppRole, out *VaultAppRole, s conversion.Scope) error { - return autoConvert_certmanager_VaultAppRole_To_v1alpha2_VaultAppRole(in, out, s) -} - -func autoConvert_v1alpha2_VaultAuth_To_certmanager_VaultAuth(in *VaultAuth, out *certmanager.VaultAuth, s conversion.Scope) error { - if in.TokenSecretRef != nil { - in, out := &in.TokenSecretRef, &out.TokenSecretRef - *out = new(meta.SecretKeySelector) - if err := apismetav1.Convert_v1_SecretKeySelector_To_meta_SecretKeySelector(*in, *out, s); err != nil { - return err - } - } else { - out.TokenSecretRef = nil - } - if in.AppRole != nil { - in, out := &in.AppRole, &out.AppRole - *out = new(certmanager.VaultAppRole) - if err := Convert_v1alpha2_VaultAppRole_To_certmanager_VaultAppRole(*in, *out, s); err != nil { - return err - } - } else { - out.AppRole = nil - } - if in.Kubernetes != nil { - in, out := &in.Kubernetes, &out.Kubernetes - *out = new(certmanager.VaultKubernetesAuth) - if err := Convert_v1alpha2_VaultKubernetesAuth_To_certmanager_VaultKubernetesAuth(*in, *out, s); err != nil { - return err - } - } else { - out.Kubernetes = nil - } - return nil -} - -// Convert_v1alpha2_VaultAuth_To_certmanager_VaultAuth is an autogenerated conversion function. -func Convert_v1alpha2_VaultAuth_To_certmanager_VaultAuth(in *VaultAuth, out *certmanager.VaultAuth, s conversion.Scope) error { - return autoConvert_v1alpha2_VaultAuth_To_certmanager_VaultAuth(in, out, s) -} - -func autoConvert_certmanager_VaultAuth_To_v1alpha2_VaultAuth(in *certmanager.VaultAuth, out *VaultAuth, s conversion.Scope) error { - if in.TokenSecretRef != nil { - in, out := &in.TokenSecretRef, &out.TokenSecretRef - *out = new(metav1.SecretKeySelector) - if err := apismetav1.Convert_meta_SecretKeySelector_To_v1_SecretKeySelector(*in, *out, s); err != nil { - return err - } - } else { - out.TokenSecretRef = nil - } - if in.AppRole != nil { - in, out := &in.AppRole, &out.AppRole - *out = new(VaultAppRole) - if err := Convert_certmanager_VaultAppRole_To_v1alpha2_VaultAppRole(*in, *out, s); err != nil { - return err - } - } else { - out.AppRole = nil - } - if in.Kubernetes != nil { - in, out := &in.Kubernetes, &out.Kubernetes - *out = new(VaultKubernetesAuth) - if err := Convert_certmanager_VaultKubernetesAuth_To_v1alpha2_VaultKubernetesAuth(*in, *out, s); err != nil { - return err - } - } else { - out.Kubernetes = nil - } - return nil -} - -// Convert_certmanager_VaultAuth_To_v1alpha2_VaultAuth is an autogenerated conversion function. -func Convert_certmanager_VaultAuth_To_v1alpha2_VaultAuth(in *certmanager.VaultAuth, out *VaultAuth, s conversion.Scope) error { - return autoConvert_certmanager_VaultAuth_To_v1alpha2_VaultAuth(in, out, s) -} - -func autoConvert_v1alpha2_VaultIssuer_To_certmanager_VaultIssuer(in *VaultIssuer, out *certmanager.VaultIssuer, s conversion.Scope) error { - if err := Convert_v1alpha2_VaultAuth_To_certmanager_VaultAuth(&in.Auth, &out.Auth, s); err != nil { - return err - } - out.Server = in.Server - out.Path = in.Path - out.Namespace = in.Namespace - out.CABundle = *(*[]byte)(unsafe.Pointer(&in.CABundle)) - if in.CABundleSecretRef != nil { - in, out := &in.CABundleSecretRef, &out.CABundleSecretRef - *out = new(meta.SecretKeySelector) - if err := apismetav1.Convert_v1_SecretKeySelector_To_meta_SecretKeySelector(*in, *out, s); err != nil { - return err - } - } else { - out.CABundleSecretRef = nil - } - return nil -} - -// Convert_v1alpha2_VaultIssuer_To_certmanager_VaultIssuer is an autogenerated conversion function. -func Convert_v1alpha2_VaultIssuer_To_certmanager_VaultIssuer(in *VaultIssuer, out *certmanager.VaultIssuer, s conversion.Scope) error { - return autoConvert_v1alpha2_VaultIssuer_To_certmanager_VaultIssuer(in, out, s) -} - -func autoConvert_certmanager_VaultIssuer_To_v1alpha2_VaultIssuer(in *certmanager.VaultIssuer, out *VaultIssuer, s conversion.Scope) error { - if err := Convert_certmanager_VaultAuth_To_v1alpha2_VaultAuth(&in.Auth, &out.Auth, s); err != nil { - return err - } - out.Server = in.Server - out.Path = in.Path - out.Namespace = in.Namespace - out.CABundle = *(*[]byte)(unsafe.Pointer(&in.CABundle)) - if in.CABundleSecretRef != nil { - in, out := &in.CABundleSecretRef, &out.CABundleSecretRef - *out = new(metav1.SecretKeySelector) - if err := apismetav1.Convert_meta_SecretKeySelector_To_v1_SecretKeySelector(*in, *out, s); err != nil { - return err - } - } else { - out.CABundleSecretRef = nil - } - return nil -} - -// Convert_certmanager_VaultIssuer_To_v1alpha2_VaultIssuer is an autogenerated conversion function. -func Convert_certmanager_VaultIssuer_To_v1alpha2_VaultIssuer(in *certmanager.VaultIssuer, out *VaultIssuer, s conversion.Scope) error { - return autoConvert_certmanager_VaultIssuer_To_v1alpha2_VaultIssuer(in, out, s) -} - -func autoConvert_v1alpha2_VaultKubernetesAuth_To_certmanager_VaultKubernetesAuth(in *VaultKubernetesAuth, out *certmanager.VaultKubernetesAuth, s conversion.Scope) error { - out.Path = in.Path - if err := apismetav1.Convert_v1_SecretKeySelector_To_meta_SecretKeySelector(&in.SecretRef, &out.SecretRef, s); err != nil { - return err - } - out.Role = in.Role - return nil -} - -// Convert_v1alpha2_VaultKubernetesAuth_To_certmanager_VaultKubernetesAuth is an autogenerated conversion function. -func Convert_v1alpha2_VaultKubernetesAuth_To_certmanager_VaultKubernetesAuth(in *VaultKubernetesAuth, out *certmanager.VaultKubernetesAuth, s conversion.Scope) error { - return autoConvert_v1alpha2_VaultKubernetesAuth_To_certmanager_VaultKubernetesAuth(in, out, s) -} - -func autoConvert_certmanager_VaultKubernetesAuth_To_v1alpha2_VaultKubernetesAuth(in *certmanager.VaultKubernetesAuth, out *VaultKubernetesAuth, s conversion.Scope) error { - out.Path = in.Path - if err := apismetav1.Convert_meta_SecretKeySelector_To_v1_SecretKeySelector(&in.SecretRef, &out.SecretRef, s); err != nil { - return err - } - out.Role = in.Role - return nil -} - -// Convert_certmanager_VaultKubernetesAuth_To_v1alpha2_VaultKubernetesAuth is an autogenerated conversion function. -func Convert_certmanager_VaultKubernetesAuth_To_v1alpha2_VaultKubernetesAuth(in *certmanager.VaultKubernetesAuth, out *VaultKubernetesAuth, s conversion.Scope) error { - return autoConvert_certmanager_VaultKubernetesAuth_To_v1alpha2_VaultKubernetesAuth(in, out, s) -} - -func autoConvert_v1alpha2_VenafiCloud_To_certmanager_VenafiCloud(in *VenafiCloud, out *certmanager.VenafiCloud, s conversion.Scope) error { - out.URL = in.URL - if err := apismetav1.Convert_v1_SecretKeySelector_To_meta_SecretKeySelector(&in.APITokenSecretRef, &out.APITokenSecretRef, s); err != nil { - return err - } - return nil -} - -// Convert_v1alpha2_VenafiCloud_To_certmanager_VenafiCloud is an autogenerated conversion function. -func Convert_v1alpha2_VenafiCloud_To_certmanager_VenafiCloud(in *VenafiCloud, out *certmanager.VenafiCloud, s conversion.Scope) error { - return autoConvert_v1alpha2_VenafiCloud_To_certmanager_VenafiCloud(in, out, s) -} - -func autoConvert_certmanager_VenafiCloud_To_v1alpha2_VenafiCloud(in *certmanager.VenafiCloud, out *VenafiCloud, s conversion.Scope) error { - out.URL = in.URL - if err := apismetav1.Convert_meta_SecretKeySelector_To_v1_SecretKeySelector(&in.APITokenSecretRef, &out.APITokenSecretRef, s); err != nil { - return err - } - return nil -} - -// Convert_certmanager_VenafiCloud_To_v1alpha2_VenafiCloud is an autogenerated conversion function. -func Convert_certmanager_VenafiCloud_To_v1alpha2_VenafiCloud(in *certmanager.VenafiCloud, out *VenafiCloud, s conversion.Scope) error { - return autoConvert_certmanager_VenafiCloud_To_v1alpha2_VenafiCloud(in, out, s) -} - -func autoConvert_v1alpha2_VenafiIssuer_To_certmanager_VenafiIssuer(in *VenafiIssuer, out *certmanager.VenafiIssuer, s conversion.Scope) error { - out.Zone = in.Zone - if in.TPP != nil { - in, out := &in.TPP, &out.TPP - *out = new(certmanager.VenafiTPP) - if err := Convert_v1alpha2_VenafiTPP_To_certmanager_VenafiTPP(*in, *out, s); err != nil { - return err - } - } else { - out.TPP = nil - } - if in.Cloud != nil { - in, out := &in.Cloud, &out.Cloud - *out = new(certmanager.VenafiCloud) - if err := Convert_v1alpha2_VenafiCloud_To_certmanager_VenafiCloud(*in, *out, s); err != nil { - return err - } - } else { - out.Cloud = nil - } - return nil -} - -// Convert_v1alpha2_VenafiIssuer_To_certmanager_VenafiIssuer is an autogenerated conversion function. -func Convert_v1alpha2_VenafiIssuer_To_certmanager_VenafiIssuer(in *VenafiIssuer, out *certmanager.VenafiIssuer, s conversion.Scope) error { - return autoConvert_v1alpha2_VenafiIssuer_To_certmanager_VenafiIssuer(in, out, s) -} - -func autoConvert_certmanager_VenafiIssuer_To_v1alpha2_VenafiIssuer(in *certmanager.VenafiIssuer, out *VenafiIssuer, s conversion.Scope) error { - out.Zone = in.Zone - if in.TPP != nil { - in, out := &in.TPP, &out.TPP - *out = new(VenafiTPP) - if err := Convert_certmanager_VenafiTPP_To_v1alpha2_VenafiTPP(*in, *out, s); err != nil { - return err - } - } else { - out.TPP = nil - } - if in.Cloud != nil { - in, out := &in.Cloud, &out.Cloud - *out = new(VenafiCloud) - if err := Convert_certmanager_VenafiCloud_To_v1alpha2_VenafiCloud(*in, *out, s); err != nil { - return err - } - } else { - out.Cloud = nil - } - return nil -} - -// Convert_certmanager_VenafiIssuer_To_v1alpha2_VenafiIssuer is an autogenerated conversion function. -func Convert_certmanager_VenafiIssuer_To_v1alpha2_VenafiIssuer(in *certmanager.VenafiIssuer, out *VenafiIssuer, s conversion.Scope) error { - return autoConvert_certmanager_VenafiIssuer_To_v1alpha2_VenafiIssuer(in, out, s) -} - -func autoConvert_v1alpha2_VenafiTPP_To_certmanager_VenafiTPP(in *VenafiTPP, out *certmanager.VenafiTPP, s conversion.Scope) error { - out.URL = in.URL - if err := apismetav1.Convert_v1_LocalObjectReference_To_meta_LocalObjectReference(&in.CredentialsRef, &out.CredentialsRef, s); err != nil { - return err - } - out.CABundle = *(*[]byte)(unsafe.Pointer(&in.CABundle)) - return nil -} - -// Convert_v1alpha2_VenafiTPP_To_certmanager_VenafiTPP is an autogenerated conversion function. -func Convert_v1alpha2_VenafiTPP_To_certmanager_VenafiTPP(in *VenafiTPP, out *certmanager.VenafiTPP, s conversion.Scope) error { - return autoConvert_v1alpha2_VenafiTPP_To_certmanager_VenafiTPP(in, out, s) -} - -func autoConvert_certmanager_VenafiTPP_To_v1alpha2_VenafiTPP(in *certmanager.VenafiTPP, out *VenafiTPP, s conversion.Scope) error { - out.URL = in.URL - if err := apismetav1.Convert_meta_LocalObjectReference_To_v1_LocalObjectReference(&in.CredentialsRef, &out.CredentialsRef, s); err != nil { - return err - } - out.CABundle = *(*[]byte)(unsafe.Pointer(&in.CABundle)) - return nil -} - -// Convert_certmanager_VenafiTPP_To_v1alpha2_VenafiTPP is an autogenerated conversion function. -func Convert_certmanager_VenafiTPP_To_v1alpha2_VenafiTPP(in *certmanager.VenafiTPP, out *VenafiTPP, s conversion.Scope) error { - return autoConvert_certmanager_VenafiTPP_To_v1alpha2_VenafiTPP(in, out, s) -} - -func autoConvert_v1alpha2_X509Subject_To_certmanager_X509Subject(in *X509Subject, out *certmanager.X509Subject, s conversion.Scope) error { - out.Countries = *(*[]string)(unsafe.Pointer(&in.Countries)) - out.OrganizationalUnits = *(*[]string)(unsafe.Pointer(&in.OrganizationalUnits)) - out.Localities = *(*[]string)(unsafe.Pointer(&in.Localities)) - out.Provinces = *(*[]string)(unsafe.Pointer(&in.Provinces)) - out.StreetAddresses = *(*[]string)(unsafe.Pointer(&in.StreetAddresses)) - out.PostalCodes = *(*[]string)(unsafe.Pointer(&in.PostalCodes)) - out.SerialNumber = in.SerialNumber - return nil -} - -// Convert_v1alpha2_X509Subject_To_certmanager_X509Subject is an autogenerated conversion function. -func Convert_v1alpha2_X509Subject_To_certmanager_X509Subject(in *X509Subject, out *certmanager.X509Subject, s conversion.Scope) error { - return autoConvert_v1alpha2_X509Subject_To_certmanager_X509Subject(in, out, s) -} - -func autoConvert_certmanager_X509Subject_To_v1alpha2_X509Subject(in *certmanager.X509Subject, out *X509Subject, s conversion.Scope) error { - // WARNING: in.Organizations requires manual conversion: does not exist in peer-type - out.Countries = *(*[]string)(unsafe.Pointer(&in.Countries)) - out.OrganizationalUnits = *(*[]string)(unsafe.Pointer(&in.OrganizationalUnits)) - out.Localities = *(*[]string)(unsafe.Pointer(&in.Localities)) - out.Provinces = *(*[]string)(unsafe.Pointer(&in.Provinces)) - out.StreetAddresses = *(*[]string)(unsafe.Pointer(&in.StreetAddresses)) - out.PostalCodes = *(*[]string)(unsafe.Pointer(&in.PostalCodes)) - out.SerialNumber = in.SerialNumber - return nil -} diff --git a/internal/apis/certmanager/v1alpha2/zz_generated.deepcopy.go b/internal/apis/certmanager/v1alpha2/zz_generated.deepcopy.go deleted file mode 100644 index a8e69faa21b..00000000000 --- a/internal/apis/certmanager/v1alpha2/zz_generated.deepcopy.go +++ /dev/null @@ -1,1026 +0,0 @@ -//go:build !ignore_autogenerated -// +build !ignore_autogenerated - -/* -Copyright 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. -*/ - -// Code generated by deepcopy-gen. DO NOT EDIT. - -package v1alpha2 - -import ( - acmev1alpha2 "github.com/cert-manager/cert-manager/internal/apis/acme/v1alpha2" - metav1 "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" - runtime "k8s.io/apimachinery/pkg/runtime" -) - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *CAIssuer) DeepCopyInto(out *CAIssuer) { - *out = *in - if in.CRLDistributionPoints != nil { - in, out := &in.CRLDistributionPoints, &out.CRLDistributionPoints - *out = make([]string, len(*in)) - copy(*out, *in) - } - if in.OCSPServers != nil { - in, out := &in.OCSPServers, &out.OCSPServers - *out = make([]string, len(*in)) - copy(*out, *in) - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CAIssuer. -func (in *CAIssuer) DeepCopy() *CAIssuer { - if in == nil { - return nil - } - out := new(CAIssuer) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *Certificate) DeepCopyInto(out *Certificate) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - in.Spec.DeepCopyInto(&out.Spec) - in.Status.DeepCopyInto(&out.Status) - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Certificate. -func (in *Certificate) DeepCopy() *Certificate { - if in == nil { - return nil - } - out := new(Certificate) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *Certificate) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *CertificateAdditionalOutputFormat) DeepCopyInto(out *CertificateAdditionalOutputFormat) { - *out = *in - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CertificateAdditionalOutputFormat. -func (in *CertificateAdditionalOutputFormat) DeepCopy() *CertificateAdditionalOutputFormat { - if in == nil { - return nil - } - out := new(CertificateAdditionalOutputFormat) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *CertificateCondition) DeepCopyInto(out *CertificateCondition) { - *out = *in - if in.LastTransitionTime != nil { - in, out := &in.LastTransitionTime, &out.LastTransitionTime - *out = (*in).DeepCopy() - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CertificateCondition. -func (in *CertificateCondition) DeepCopy() *CertificateCondition { - if in == nil { - return nil - } - out := new(CertificateCondition) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *CertificateKeystores) DeepCopyInto(out *CertificateKeystores) { - *out = *in - if in.JKS != nil { - in, out := &in.JKS, &out.JKS - *out = new(JKSKeystore) - **out = **in - } - if in.PKCS12 != nil { - in, out := &in.PKCS12, &out.PKCS12 - *out = new(PKCS12Keystore) - **out = **in - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CertificateKeystores. -func (in *CertificateKeystores) DeepCopy() *CertificateKeystores { - if in == nil { - return nil - } - out := new(CertificateKeystores) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *CertificateList) DeepCopyInto(out *CertificateList) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ListMeta.DeepCopyInto(&out.ListMeta) - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]Certificate, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CertificateList. -func (in *CertificateList) DeepCopy() *CertificateList { - if in == nil { - return nil - } - out := new(CertificateList) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *CertificateList) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *CertificatePrivateKey) DeepCopyInto(out *CertificatePrivateKey) { - *out = *in - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CertificatePrivateKey. -func (in *CertificatePrivateKey) DeepCopy() *CertificatePrivateKey { - if in == nil { - return nil - } - out := new(CertificatePrivateKey) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *CertificateRequest) DeepCopyInto(out *CertificateRequest) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - in.Spec.DeepCopyInto(&out.Spec) - in.Status.DeepCopyInto(&out.Status) - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CertificateRequest. -func (in *CertificateRequest) DeepCopy() *CertificateRequest { - if in == nil { - return nil - } - out := new(CertificateRequest) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *CertificateRequest) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *CertificateRequestCondition) DeepCopyInto(out *CertificateRequestCondition) { - *out = *in - if in.LastTransitionTime != nil { - in, out := &in.LastTransitionTime, &out.LastTransitionTime - *out = (*in).DeepCopy() - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CertificateRequestCondition. -func (in *CertificateRequestCondition) DeepCopy() *CertificateRequestCondition { - if in == nil { - return nil - } - out := new(CertificateRequestCondition) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *CertificateRequestList) DeepCopyInto(out *CertificateRequestList) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ListMeta.DeepCopyInto(&out.ListMeta) - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]CertificateRequest, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CertificateRequestList. -func (in *CertificateRequestList) DeepCopy() *CertificateRequestList { - if in == nil { - return nil - } - out := new(CertificateRequestList) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *CertificateRequestList) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *CertificateRequestSpec) DeepCopyInto(out *CertificateRequestSpec) { - *out = *in - if in.Duration != nil { - in, out := &in.Duration, &out.Duration - *out = new(v1.Duration) - **out = **in - } - out.IssuerRef = in.IssuerRef - if in.CSRPEM != nil { - in, out := &in.CSRPEM, &out.CSRPEM - *out = make([]byte, len(*in)) - copy(*out, *in) - } - if in.Usages != nil { - in, out := &in.Usages, &out.Usages - *out = make([]KeyUsage, len(*in)) - copy(*out, *in) - } - if in.Groups != nil { - in, out := &in.Groups, &out.Groups - *out = make([]string, len(*in)) - copy(*out, *in) - } - if in.Extra != nil { - in, out := &in.Extra, &out.Extra - *out = make(map[string][]string, len(*in)) - for key, val := range *in { - var outVal []string - if val == nil { - (*out)[key] = nil - } else { - in, out := &val, &outVal - *out = make([]string, len(*in)) - copy(*out, *in) - } - (*out)[key] = outVal - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CertificateRequestSpec. -func (in *CertificateRequestSpec) DeepCopy() *CertificateRequestSpec { - if in == nil { - return nil - } - out := new(CertificateRequestSpec) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *CertificateRequestStatus) DeepCopyInto(out *CertificateRequestStatus) { - *out = *in - if in.Conditions != nil { - in, out := &in.Conditions, &out.Conditions - *out = make([]CertificateRequestCondition, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - if in.Certificate != nil { - in, out := &in.Certificate, &out.Certificate - *out = make([]byte, len(*in)) - copy(*out, *in) - } - if in.CA != nil { - in, out := &in.CA, &out.CA - *out = make([]byte, len(*in)) - copy(*out, *in) - } - if in.FailureTime != nil { - in, out := &in.FailureTime, &out.FailureTime - *out = (*in).DeepCopy() - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CertificateRequestStatus. -func (in *CertificateRequestStatus) DeepCopy() *CertificateRequestStatus { - if in == nil { - return nil - } - out := new(CertificateRequestStatus) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *CertificateSecretTemplate) DeepCopyInto(out *CertificateSecretTemplate) { - *out = *in - if in.Annotations != nil { - in, out := &in.Annotations, &out.Annotations - *out = make(map[string]string, len(*in)) - for key, val := range *in { - (*out)[key] = val - } - } - if in.Labels != nil { - in, out := &in.Labels, &out.Labels - *out = make(map[string]string, len(*in)) - for key, val := range *in { - (*out)[key] = val - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CertificateSecretTemplate. -func (in *CertificateSecretTemplate) DeepCopy() *CertificateSecretTemplate { - if in == nil { - return nil - } - out := new(CertificateSecretTemplate) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *CertificateSpec) DeepCopyInto(out *CertificateSpec) { - *out = *in - if in.Subject != nil { - in, out := &in.Subject, &out.Subject - *out = new(X509Subject) - (*in).DeepCopyInto(*out) - } - if in.Organization != nil { - in, out := &in.Organization, &out.Organization - *out = make([]string, len(*in)) - copy(*out, *in) - } - if in.Duration != nil { - in, out := &in.Duration, &out.Duration - *out = new(v1.Duration) - **out = **in - } - if in.RenewBefore != nil { - in, out := &in.RenewBefore, &out.RenewBefore - *out = new(v1.Duration) - **out = **in - } - if in.DNSNames != nil { - in, out := &in.DNSNames, &out.DNSNames - *out = make([]string, len(*in)) - copy(*out, *in) - } - if in.IPAddresses != nil { - in, out := &in.IPAddresses, &out.IPAddresses - *out = make([]string, len(*in)) - copy(*out, *in) - } - if in.URISANs != nil { - in, out := &in.URISANs, &out.URISANs - *out = make([]string, len(*in)) - copy(*out, *in) - } - if in.EmailSANs != nil { - in, out := &in.EmailSANs, &out.EmailSANs - *out = make([]string, len(*in)) - copy(*out, *in) - } - if in.SecretTemplate != nil { - in, out := &in.SecretTemplate, &out.SecretTemplate - *out = new(CertificateSecretTemplate) - (*in).DeepCopyInto(*out) - } - if in.Keystores != nil { - in, out := &in.Keystores, &out.Keystores - *out = new(CertificateKeystores) - (*in).DeepCopyInto(*out) - } - out.IssuerRef = in.IssuerRef - if in.Usages != nil { - in, out := &in.Usages, &out.Usages - *out = make([]KeyUsage, len(*in)) - copy(*out, *in) - } - if in.PrivateKey != nil { - in, out := &in.PrivateKey, &out.PrivateKey - *out = new(CertificatePrivateKey) - **out = **in - } - if in.EncodeUsagesInRequest != nil { - in, out := &in.EncodeUsagesInRequest, &out.EncodeUsagesInRequest - *out = new(bool) - **out = **in - } - if in.RevisionHistoryLimit != nil { - in, out := &in.RevisionHistoryLimit, &out.RevisionHistoryLimit - *out = new(int32) - **out = **in - } - if in.AdditionalOutputFormats != nil { - in, out := &in.AdditionalOutputFormats, &out.AdditionalOutputFormats - *out = make([]CertificateAdditionalOutputFormat, len(*in)) - copy(*out, *in) - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CertificateSpec. -func (in *CertificateSpec) DeepCopy() *CertificateSpec { - if in == nil { - return nil - } - out := new(CertificateSpec) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *CertificateStatus) DeepCopyInto(out *CertificateStatus) { - *out = *in - if in.Conditions != nil { - in, out := &in.Conditions, &out.Conditions - *out = make([]CertificateCondition, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - if in.LastFailureTime != nil { - in, out := &in.LastFailureTime, &out.LastFailureTime - *out = (*in).DeepCopy() - } - if in.NotBefore != nil { - in, out := &in.NotBefore, &out.NotBefore - *out = (*in).DeepCopy() - } - if in.NotAfter != nil { - in, out := &in.NotAfter, &out.NotAfter - *out = (*in).DeepCopy() - } - if in.RenewalTime != nil { - in, out := &in.RenewalTime, &out.RenewalTime - *out = (*in).DeepCopy() - } - if in.Revision != nil { - in, out := &in.Revision, &out.Revision - *out = new(int) - **out = **in - } - if in.NextPrivateKeySecretName != nil { - in, out := &in.NextPrivateKeySecretName, &out.NextPrivateKeySecretName - *out = new(string) - **out = **in - } - if in.FailedIssuanceAttempts != nil { - in, out := &in.FailedIssuanceAttempts, &out.FailedIssuanceAttempts - *out = new(int) - **out = **in - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CertificateStatus. -func (in *CertificateStatus) DeepCopy() *CertificateStatus { - if in == nil { - return nil - } - out := new(CertificateStatus) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ClusterIssuer) DeepCopyInto(out *ClusterIssuer) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - in.Spec.DeepCopyInto(&out.Spec) - in.Status.DeepCopyInto(&out.Status) - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterIssuer. -func (in *ClusterIssuer) DeepCopy() *ClusterIssuer { - if in == nil { - return nil - } - out := new(ClusterIssuer) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *ClusterIssuer) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ClusterIssuerList) DeepCopyInto(out *ClusterIssuerList) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ListMeta.DeepCopyInto(&out.ListMeta) - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]ClusterIssuer, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterIssuerList. -func (in *ClusterIssuerList) DeepCopy() *ClusterIssuerList { - if in == nil { - return nil - } - out := new(ClusterIssuerList) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *ClusterIssuerList) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *Issuer) DeepCopyInto(out *Issuer) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - in.Spec.DeepCopyInto(&out.Spec) - in.Status.DeepCopyInto(&out.Status) - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Issuer. -func (in *Issuer) DeepCopy() *Issuer { - if in == nil { - return nil - } - out := new(Issuer) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *Issuer) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *IssuerCondition) DeepCopyInto(out *IssuerCondition) { - *out = *in - if in.LastTransitionTime != nil { - in, out := &in.LastTransitionTime, &out.LastTransitionTime - *out = (*in).DeepCopy() - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new IssuerCondition. -func (in *IssuerCondition) DeepCopy() *IssuerCondition { - if in == nil { - return nil - } - out := new(IssuerCondition) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *IssuerConfig) DeepCopyInto(out *IssuerConfig) { - *out = *in - if in.ACME != nil { - in, out := &in.ACME, &out.ACME - *out = new(acmev1alpha2.ACMEIssuer) - (*in).DeepCopyInto(*out) - } - if in.CA != nil { - in, out := &in.CA, &out.CA - *out = new(CAIssuer) - (*in).DeepCopyInto(*out) - } - if in.Vault != nil { - in, out := &in.Vault, &out.Vault - *out = new(VaultIssuer) - (*in).DeepCopyInto(*out) - } - if in.SelfSigned != nil { - in, out := &in.SelfSigned, &out.SelfSigned - *out = new(SelfSignedIssuer) - (*in).DeepCopyInto(*out) - } - if in.Venafi != nil { - in, out := &in.Venafi, &out.Venafi - *out = new(VenafiIssuer) - (*in).DeepCopyInto(*out) - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new IssuerConfig. -func (in *IssuerConfig) DeepCopy() *IssuerConfig { - if in == nil { - return nil - } - out := new(IssuerConfig) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *IssuerList) DeepCopyInto(out *IssuerList) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ListMeta.DeepCopyInto(&out.ListMeta) - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]Issuer, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new IssuerList. -func (in *IssuerList) DeepCopy() *IssuerList { - if in == nil { - return nil - } - out := new(IssuerList) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *IssuerList) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *IssuerSpec) DeepCopyInto(out *IssuerSpec) { - *out = *in - in.IssuerConfig.DeepCopyInto(&out.IssuerConfig) - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new IssuerSpec. -func (in *IssuerSpec) DeepCopy() *IssuerSpec { - if in == nil { - return nil - } - out := new(IssuerSpec) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *IssuerStatus) DeepCopyInto(out *IssuerStatus) { - *out = *in - if in.Conditions != nil { - in, out := &in.Conditions, &out.Conditions - *out = make([]IssuerCondition, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - if in.ACME != nil { - in, out := &in.ACME, &out.ACME - *out = new(acmev1alpha2.ACMEIssuerStatus) - **out = **in - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new IssuerStatus. -func (in *IssuerStatus) DeepCopy() *IssuerStatus { - if in == nil { - return nil - } - out := new(IssuerStatus) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *JKSKeystore) DeepCopyInto(out *JKSKeystore) { - *out = *in - out.PasswordSecretRef = in.PasswordSecretRef - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new JKSKeystore. -func (in *JKSKeystore) DeepCopy() *JKSKeystore { - if in == nil { - return nil - } - out := new(JKSKeystore) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *PKCS12Keystore) DeepCopyInto(out *PKCS12Keystore) { - *out = *in - out.PasswordSecretRef = in.PasswordSecretRef - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PKCS12Keystore. -func (in *PKCS12Keystore) DeepCopy() *PKCS12Keystore { - if in == nil { - return nil - } - out := new(PKCS12Keystore) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *SelfSignedIssuer) DeepCopyInto(out *SelfSignedIssuer) { - *out = *in - if in.CRLDistributionPoints != nil { - in, out := &in.CRLDistributionPoints, &out.CRLDistributionPoints - *out = make([]string, len(*in)) - copy(*out, *in) - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SelfSignedIssuer. -func (in *SelfSignedIssuer) DeepCopy() *SelfSignedIssuer { - if in == nil { - return nil - } - out := new(SelfSignedIssuer) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *VaultAppRole) DeepCopyInto(out *VaultAppRole) { - *out = *in - out.SecretRef = in.SecretRef - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VaultAppRole. -func (in *VaultAppRole) DeepCopy() *VaultAppRole { - if in == nil { - return nil - } - out := new(VaultAppRole) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *VaultAuth) DeepCopyInto(out *VaultAuth) { - *out = *in - if in.TokenSecretRef != nil { - in, out := &in.TokenSecretRef, &out.TokenSecretRef - *out = new(metav1.SecretKeySelector) - **out = **in - } - if in.AppRole != nil { - in, out := &in.AppRole, &out.AppRole - *out = new(VaultAppRole) - **out = **in - } - if in.Kubernetes != nil { - in, out := &in.Kubernetes, &out.Kubernetes - *out = new(VaultKubernetesAuth) - **out = **in - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VaultAuth. -func (in *VaultAuth) DeepCopy() *VaultAuth { - if in == nil { - return nil - } - out := new(VaultAuth) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *VaultIssuer) DeepCopyInto(out *VaultIssuer) { - *out = *in - in.Auth.DeepCopyInto(&out.Auth) - if in.CABundle != nil { - in, out := &in.CABundle, &out.CABundle - *out = make([]byte, len(*in)) - copy(*out, *in) - } - if in.CABundleSecretRef != nil { - in, out := &in.CABundleSecretRef, &out.CABundleSecretRef - *out = new(metav1.SecretKeySelector) - **out = **in - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VaultIssuer. -func (in *VaultIssuer) DeepCopy() *VaultIssuer { - if in == nil { - return nil - } - out := new(VaultIssuer) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *VaultKubernetesAuth) DeepCopyInto(out *VaultKubernetesAuth) { - *out = *in - out.SecretRef = in.SecretRef - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VaultKubernetesAuth. -func (in *VaultKubernetesAuth) DeepCopy() *VaultKubernetesAuth { - if in == nil { - return nil - } - out := new(VaultKubernetesAuth) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *VenafiCloud) DeepCopyInto(out *VenafiCloud) { - *out = *in - out.APITokenSecretRef = in.APITokenSecretRef - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VenafiCloud. -func (in *VenafiCloud) DeepCopy() *VenafiCloud { - if in == nil { - return nil - } - out := new(VenafiCloud) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *VenafiIssuer) DeepCopyInto(out *VenafiIssuer) { - *out = *in - if in.TPP != nil { - in, out := &in.TPP, &out.TPP - *out = new(VenafiTPP) - (*in).DeepCopyInto(*out) - } - if in.Cloud != nil { - in, out := &in.Cloud, &out.Cloud - *out = new(VenafiCloud) - **out = **in - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VenafiIssuer. -func (in *VenafiIssuer) DeepCopy() *VenafiIssuer { - if in == nil { - return nil - } - out := new(VenafiIssuer) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *VenafiTPP) DeepCopyInto(out *VenafiTPP) { - *out = *in - out.CredentialsRef = in.CredentialsRef - if in.CABundle != nil { - in, out := &in.CABundle, &out.CABundle - *out = make([]byte, len(*in)) - copy(*out, *in) - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VenafiTPP. -func (in *VenafiTPP) DeepCopy() *VenafiTPP { - if in == nil { - return nil - } - out := new(VenafiTPP) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *X509Subject) DeepCopyInto(out *X509Subject) { - *out = *in - if in.Countries != nil { - in, out := &in.Countries, &out.Countries - *out = make([]string, len(*in)) - copy(*out, *in) - } - if in.OrganizationalUnits != nil { - in, out := &in.OrganizationalUnits, &out.OrganizationalUnits - *out = make([]string, len(*in)) - copy(*out, *in) - } - if in.Localities != nil { - in, out := &in.Localities, &out.Localities - *out = make([]string, len(*in)) - copy(*out, *in) - } - if in.Provinces != nil { - in, out := &in.Provinces, &out.Provinces - *out = make([]string, len(*in)) - copy(*out, *in) - } - if in.StreetAddresses != nil { - in, out := &in.StreetAddresses, &out.StreetAddresses - *out = make([]string, len(*in)) - copy(*out, *in) - } - if in.PostalCodes != nil { - in, out := &in.PostalCodes, &out.PostalCodes - *out = make([]string, len(*in)) - copy(*out, *in) - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new X509Subject. -func (in *X509Subject) DeepCopy() *X509Subject { - if in == nil { - return nil - } - out := new(X509Subject) - in.DeepCopyInto(out) - return out -} diff --git a/internal/apis/certmanager/v1alpha2/zz_generated.defaults.go b/internal/apis/certmanager/v1alpha2/zz_generated.defaults.go deleted file mode 100644 index 10b31a62682..00000000000 --- a/internal/apis/certmanager/v1alpha2/zz_generated.defaults.go +++ /dev/null @@ -1,33 +0,0 @@ -//go:build !ignore_autogenerated -// +build !ignore_autogenerated - -/* -Copyright 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. -*/ - -// Code generated by defaulter-gen. DO NOT EDIT. - -package v1alpha2 - -import ( - runtime "k8s.io/apimachinery/pkg/runtime" -) - -// RegisterDefaults adds defaulters functions to the given scheme. -// Public to allow building arbitrary schemes. -// All generated defaulters are covering - they call all nested defaulters. -func RegisterDefaults(scheme *runtime.Scheme) error { - return nil -} diff --git a/internal/apis/certmanager/v1alpha3/const.go b/internal/apis/certmanager/v1alpha3/const.go deleted file mode 100644 index 7cfa28c7723..00000000000 --- a/internal/apis/certmanager/v1alpha3/const.go +++ /dev/null @@ -1,43 +0,0 @@ -/* -Copyright 2020 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. -*/ - -package v1alpha3 - -import "time" - -const ( - // minimum permitted certificate duration by cert-manager - MinimumCertificateDuration = time.Hour - - // default certificate duration if Issuer.spec.duration is not set - DefaultCertificateDuration = time.Hour * 24 * 90 - - // minimum certificate duration before certificate expiration - MinimumRenewBefore = time.Minute * 5 - - // Deprecated: the default is now 2/3 of Certificate's duration - DefaultRenewBefore = time.Hour * 24 * 30 -) - -const ( - // Default index key for the Secret reference for Token authentication - DefaultVaultTokenAuthSecretKey = "token" - - // Default mount path location for Kubernetes ServiceAccount authentication - // (/v1/auth/kubernetes). The endpoint will then be called at `/login`, so - // left as the default, `/v1/auth/kubernetes/login` will be called. - DefaultVaultKubernetesAuthMountPath = "/v1/auth/kubernetes" -) diff --git a/internal/apis/certmanager/v1alpha3/conversion.go b/internal/apis/certmanager/v1alpha3/conversion.go deleted file mode 100644 index 538da2dd5d6..00000000000 --- a/internal/apis/certmanager/v1alpha3/conversion.go +++ /dev/null @@ -1,113 +0,0 @@ -/* -Copyright 2020 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. -*/ - -package v1alpha3 - -import ( - "k8s.io/apimachinery/pkg/conversion" - - "github.com/cert-manager/cert-manager/internal/apis/certmanager" -) - -func Convert_v1alpha3_CertificateSpec_To_certmanager_CertificateSpec(in *CertificateSpec, out *certmanager.CertificateSpec, s conversion.Scope) error { - if err := autoConvert_v1alpha3_CertificateSpec_To_certmanager_CertificateSpec(in, out, s); err != nil { - return err - } - - if in.KeyAlgorithm != "" || in.KeyEncoding != "" || in.KeySize != 0 { - if out.PrivateKey == nil { - out.PrivateKey = &certmanager.CertificatePrivateKey{} - } - - switch in.KeyAlgorithm { - case ECDSAKeyAlgorithm: - out.PrivateKey.Algorithm = certmanager.ECDSAKeyAlgorithm - case RSAKeyAlgorithm: - out.PrivateKey.Algorithm = certmanager.RSAKeyAlgorithm - default: - out.PrivateKey.Algorithm = certmanager.PrivateKeyAlgorithm(in.KeyAlgorithm) - } - - switch in.KeyEncoding { - case PKCS1: - out.PrivateKey.Encoding = certmanager.PKCS1 - case PKCS8: - out.PrivateKey.Encoding = certmanager.PKCS8 - default: - out.PrivateKey.Encoding = certmanager.PrivateKeyEncoding(in.KeyEncoding) - } - - out.PrivateKey.Size = in.KeySize - } - - return nil -} - -func Convert_certmanager_CertificateSpec_To_v1alpha3_CertificateSpec(in *certmanager.CertificateSpec, out *CertificateSpec, s conversion.Scope) error { - if err := autoConvert_certmanager_CertificateSpec_To_v1alpha3_CertificateSpec(in, out, s); err != nil { - return err - } - - if in.PrivateKey != nil { - switch in.PrivateKey.Algorithm { - case certmanager.ECDSAKeyAlgorithm: - out.KeyAlgorithm = ECDSAKeyAlgorithm - case certmanager.RSAKeyAlgorithm: - out.KeyAlgorithm = RSAKeyAlgorithm - default: - out.KeyAlgorithm = KeyAlgorithm(in.PrivateKey.Algorithm) - } - - switch in.PrivateKey.Encoding { - case certmanager.PKCS1: - out.KeyEncoding = PKCS1 - case certmanager.PKCS8: - out.KeyEncoding = PKCS8 - default: - out.KeyEncoding = KeyEncoding(in.PrivateKey.Encoding) - } - - out.KeySize = in.PrivateKey.Size - } - - return nil -} - -func Convert_certmanager_X509Subject_To_v1alpha3_X509Subject(in *certmanager.X509Subject, out *X509Subject, s conversion.Scope) error { - return autoConvert_certmanager_X509Subject_To_v1alpha3_X509Subject(in, out, s) -} - -func Convert_certmanager_CertificatePrivateKey_To_v1alpha3_CertificatePrivateKey(in *certmanager.CertificatePrivateKey, out *CertificatePrivateKey, s conversion.Scope) error { - return autoConvert_certmanager_CertificatePrivateKey_To_v1alpha3_CertificatePrivateKey(in, out, s) -} - -func Convert_v1alpha3_CertificateRequestSpec_To_certmanager_CertificateRequestSpec(in *CertificateRequestSpec, out *certmanager.CertificateRequestSpec, s conversion.Scope) error { - if err := autoConvert_v1alpha3_CertificateRequestSpec_To_certmanager_CertificateRequestSpec(in, out, s); err != nil { - return err - } - - out.Request = in.CSRPEM - return nil -} - -func Convert_certmanager_CertificateRequestSpec_To_v1alpha3_CertificateRequestSpec(in *certmanager.CertificateRequestSpec, out *CertificateRequestSpec, s conversion.Scope) error { - if err := autoConvert_certmanager_CertificateRequestSpec_To_v1alpha3_CertificateRequestSpec(in, out, s); err != nil { - return err - } - - out.CSRPEM = in.Request - return nil -} diff --git a/internal/apis/certmanager/v1alpha3/defaults.go b/internal/apis/certmanager/v1alpha3/defaults.go deleted file mode 100644 index 23beb3dd257..00000000000 --- a/internal/apis/certmanager/v1alpha3/defaults.go +++ /dev/null @@ -1,25 +0,0 @@ -/* -Copyright 2020 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. -*/ - -package v1alpha3 - -import ( - "k8s.io/apimachinery/pkg/runtime" -) - -func addDefaultingFuncs(scheme *runtime.Scheme) error { - return RegisterDefaults(scheme) -} diff --git a/internal/apis/certmanager/v1alpha3/doc.go b/internal/apis/certmanager/v1alpha3/doc.go deleted file mode 100644 index cd7f49f878a..00000000000 --- a/internal/apis/certmanager/v1alpha3/doc.go +++ /dev/null @@ -1,23 +0,0 @@ -/* -Copyright 2020 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. -*/ - -// +k8s:conversion-gen=github.com/cert-manager/cert-manager/internal/apis/certmanager -// +k8s:conversion-gen-external-types=github.com/cert-manager/cert-manager/internal/apis/certmanager/v1alpha3 -// +k8s:defaulter-gen=TypeMeta -// +k8s:deepcopy-gen=package,register - -// +groupName=cert-manager.io -package v1alpha3 diff --git a/internal/apis/certmanager/v1alpha3/generic_issuer.go b/internal/apis/certmanager/v1alpha3/generic_issuer.go deleted file mode 100644 index 4f443e0c1d9..00000000000 --- a/internal/apis/certmanager/v1alpha3/generic_issuer.go +++ /dev/null @@ -1,85 +0,0 @@ -/* -Copyright 2020 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. -*/ - -package v1alpha3 - -import ( - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - - cmacme "github.com/cert-manager/cert-manager/internal/apis/acme/v1alpha3" -) - -type GenericIssuer interface { - runtime.Object - metav1.Object - - GetObjectMeta() *metav1.ObjectMeta - GetSpec() *IssuerSpec - GetStatus() *IssuerStatus -} - -var _ GenericIssuer = &Issuer{} -var _ GenericIssuer = &ClusterIssuer{} - -func (c *ClusterIssuer) GetObjectMeta() *metav1.ObjectMeta { - return &c.ObjectMeta -} -func (c *ClusterIssuer) GetSpec() *IssuerSpec { - return &c.Spec -} -func (c *ClusterIssuer) GetStatus() *IssuerStatus { - return &c.Status -} -func (c *ClusterIssuer) SetSpec(spec IssuerSpec) { - c.Spec = spec -} -func (c *ClusterIssuer) SetStatus(status IssuerStatus) { - c.Status = status -} -func (c *ClusterIssuer) Copy() GenericIssuer { - return c.DeepCopy() -} -func (c *Issuer) GetObjectMeta() *metav1.ObjectMeta { - return &c.ObjectMeta -} -func (c *Issuer) GetSpec() *IssuerSpec { - return &c.Spec -} -func (c *Issuer) GetStatus() *IssuerStatus { - return &c.Status -} -func (c *Issuer) SetSpec(spec IssuerSpec) { - c.Spec = spec -} -func (c *Issuer) SetStatus(status IssuerStatus) { - c.Status = status -} -func (c *Issuer) Copy() GenericIssuer { - return c.DeepCopy() -} - -// TODO: refactor these functions away -func (i *IssuerStatus) ACMEStatus() *cmacme.ACMEIssuerStatus { - // this is an edge case, but this will prevent panics - if i == nil { - return &cmacme.ACMEIssuerStatus{} - } - if i.ACME == nil { - i.ACME = &cmacme.ACMEIssuerStatus{} - } - return i.ACME -} diff --git a/internal/apis/certmanager/v1alpha3/register.go b/internal/apis/certmanager/v1alpha3/register.go deleted file mode 100644 index a001b0efa28..00000000000 --- a/internal/apis/certmanager/v1alpha3/register.go +++ /dev/null @@ -1,67 +0,0 @@ -/* -Copyright 2020 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. -*/ - -package v1alpha3 - -import ( - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/runtime/schema" - - "github.com/cert-manager/cert-manager/pkg/apis/certmanager" -) - -// SchemeGroupVersion is group version used to register these objects -var SchemeGroupVersion = schema.GroupVersion{Group: certmanager.GroupName, Version: "v1alpha3"} - -// Resource takes an unqualified resource and returns a Group qualified GroupResource -func Resource(resource string) schema.GroupResource { - return SchemeGroupVersion.WithResource(resource).GroupResource() -} - -var ( - SchemeBuilder runtime.SchemeBuilder - localSchemeBuilder = &SchemeBuilder - AddToScheme = localSchemeBuilder.AddToScheme -) - -func init() { - // We only register manually written functions here. The registration of the - // generated functions takes place in the generated files. The separation - // makes the code compile even when the generated files are missing. - localSchemeBuilder.Register(addDefaultingFuncs) - - // We only register manually written functions here. The registration of the - // generated functions takes place in the generated files. The separation - // makes the code compile even when the generated files are missing. - localSchemeBuilder.Register(addKnownTypes) -} - -// Adds the list of known types to api.Scheme. -func addKnownTypes(scheme *runtime.Scheme) error { - scheme.AddKnownTypes(SchemeGroupVersion, - &Certificate{}, - &CertificateList{}, - &Issuer{}, - &IssuerList{}, - &ClusterIssuer{}, - &ClusterIssuerList{}, - &CertificateRequest{}, - &CertificateRequestList{}, - ) - metav1.AddToGroupVersion(scheme, SchemeGroupVersion) - return nil -} diff --git a/internal/apis/certmanager/v1alpha3/types.go b/internal/apis/certmanager/v1alpha3/types.go deleted file mode 100644 index 377ace3539e..00000000000 --- a/internal/apis/certmanager/v1alpha3/types.go +++ /dev/null @@ -1,195 +0,0 @@ -/* -Copyright 2020 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. -*/ - -package v1alpha3 - -// Common annotation keys added to resources. -const ( - // Annotation key for DNS subjectAltNames. - AltNamesAnnotationKey = "cert-manager.io/alt-names" - - // Annotation key for IP subjectAltNames. - IPSANAnnotationKey = "cert-manager.io/ip-sans" - - // Annotation key for URI subjectAltNames. - URISANAnnotationKey = "cert-manager.io/uri-sans" - - // Annotation key for certificate common name. - CommonNameAnnotationKey = "cert-manager.io/common-name" - - // Annotation key the 'name' of the Issuer resource. - IssuerNameAnnotationKey = "cert-manager.io/issuer-name" - - // Annotation key for the 'kind' of the Issuer resource. - IssuerKindAnnotationKey = "cert-manager.io/issuer-kind" - - // Annotation key for the 'group' of the Issuer resource. - IssuerGroupAnnotationKey = "cert-manager.io/issuer-group" - - // Annotation key for the name of the certificate that a resource is related to. - CertificateNameKey = "cert-manager.io/certificate-name" - - // Annotation key used to denote whether a Secret is named on a Certificate - // as a 'next private key' Secret resource. - IsNextPrivateKeySecretLabelKey = "cert-manager.io/next-private-key" -) - -// Deprecated annotation names for Secrets -// These will be removed in a future release. -const ( - DeprecatedIssuerNameAnnotationKey = "certmanager.k8s.io/issuer-name" - DeprecatedIssuerKindAnnotationKey = "certmanager.k8s.io/issuer-kind" -) - -const ( - // issuerNameAnnotation can be used to override the issuer specified on the - // created Certificate resource. - IngressIssuerNameAnnotationKey = "cert-manager.io/issuer" - // clusterIssuerNameAnnotation can be used to override the issuer specified on the - // created Certificate resource. The Certificate will reference the - // specified *ClusterIssuer* instead of normal issuer. - IngressClusterIssuerNameAnnotationKey = "cert-manager.io/cluster-issuer" - // acmeIssuerHTTP01IngressClassAnnotation can be used to override the http01 ingressClass - // if the challenge type is set to http01 - IngressACMEIssuerHTTP01IngressClassAnnotationKey = "acme.cert-manager.io/http01-ingress-class" - - // IngressClassAnnotationKey picks a specific "class" for the Ingress. The - // controller only processes Ingresses with this annotation either unset, or - // set to either the configured value or the empty string. - IngressClassAnnotationKey = "kubernetes.io/ingress.class" -) - -// Annotation names for CertificateRequests -const ( - // Annotation added to CertificateRequest resources to denote the name of - // a Secret resource containing the private key used to sign the CSR stored - // on the resource. - // This annotation *may* not be present, and is used by the 'self signing' - // issuer type to self-sign certificates. - CertificateRequestPrivateKeyAnnotationKey = "cert-manager.io/private-key-secret-name" - - // Annotation to declare the CertificateRequest "revision", belonging to a Certificate Resource - CertificateRequestRevisionAnnotationKey = "cert-manager.io/certificate-revision" -) - -const ( - // IssueTemporaryCertificateAnnotation is an annotation that can be added to - // Certificate resources. - // If it is present, a temporary internally signed certificate will be - // stored in the target Secret resource whilst the real Issuer is processing - // the certificate request. - IssueTemporaryCertificateAnnotation = "cert-manager.io/issue-temporary-certificate" -) - -// Common/known resource kinds. -const ( - ClusterIssuerKind = "ClusterIssuer" - IssuerKind = "Issuer" - CertificateKind = "Certificate" - CertificateRequestKind = "CertificateRequest" -) - -const ( - // WantInjectAnnotation is the annotation that specifies that a particular - // object wants injection of CAs. It takes the form of a reference to a certificate - // as namespace/name. The certificate is expected to have the is-serving-for annotations. - WantInjectAnnotation = "cert-manager.io/inject-ca-from" - - // WantInjectAPIServerCAAnnotation, if set to "true", will make the cainjector - // inject the CA certificate for the Kubernetes apiserver into the resource. - // It discovers the apiserver's CA by inspecting the service account credentials - // mounted into the cainjector pod. - WantInjectAPIServerCAAnnotation = "cert-manager.io/inject-apiserver-ca" - - // WantInjectFromSecretAnnotation is the annotation that specifies that a particular - // object wants injection of CAs. It takes the form of a reference to a Secret - // as namespace/name. - WantInjectFromSecretAnnotation = "cert-manager.io/inject-ca-from-secret" - - // AllowsInjectionFromSecretAnnotation is an annotation that must be added - // to Secret resource that want to denote that they can be directly - // injected into injectables that have a `inject-ca-from-secret` annotation. - // If an injectable references a Secret that does NOT have this annotation, - // the cainjector will refuse to inject the secret. - AllowsInjectionFromSecretAnnotation = "cert-manager.io/allow-direct-injection" -) - -// Issuer specific Annotations -const ( - // VenafiCustomFieldsAnnotationKey is the annotation that passes on JSON encoded custom fields to the Venafi issuer - // This will only work with Venafi TPP v19.3 and higher - // The value is an array with objects containing the name and value keys - // for example: `[{"name": "custom-field", "value": "custom-value"}]` - VenafiCustomFieldsAnnotationKey = "venafi.cert-manager.io/custom-fields" -) - -// 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" -// +kubebuilder:validation:Enum="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" -type KeyUsage string - -const ( - UsageSigning KeyUsage = "signing" - UsageDigitalSignature KeyUsage = "digital signature" - UsageContentCommitment KeyUsage = "content commitment" - UsageKeyEncipherment KeyUsage = "key encipherment" - UsageKeyAgreement KeyUsage = "key agreement" - UsageDataEncipherment KeyUsage = "data encipherment" - UsageCertSign KeyUsage = "cert sign" - UsageCRLSign KeyUsage = "crl sign" - UsageEncipherOnly KeyUsage = "encipher only" - UsageDecipherOnly KeyUsage = "decipher only" - UsageAny KeyUsage = "any" - UsageServerAuth KeyUsage = "server auth" - UsageClientAuth KeyUsage = "client auth" - UsageCodeSigning KeyUsage = "code signing" - UsageEmailProtection KeyUsage = "email protection" - UsageSMIME KeyUsage = "s/mime" - UsageIPsecEndSystem KeyUsage = "ipsec end system" - UsageIPsecTunnel KeyUsage = "ipsec tunnel" - UsageIPsecUser KeyUsage = "ipsec user" - UsageTimestamping KeyUsage = "timestamping" - UsageOCSPSigning KeyUsage = "ocsp signing" - UsageMicrosoftSGC KeyUsage = "microsoft sgc" - UsageNetscapeSGC KeyUsage = "netscape sgc" -) diff --git a/internal/apis/certmanager/v1alpha3/types_certificate.go b/internal/apis/certmanager/v1alpha3/types_certificate.go deleted file mode 100644 index 7748fdad0c3..00000000000 --- a/internal/apis/certmanager/v1alpha3/types_certificate.go +++ /dev/null @@ -1,510 +0,0 @@ -/* -Copyright 2020 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. -*/ - -package v1alpha3 - -import ( - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - - cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" -) - -// +genclient -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object - -// 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`). -// +k8s:openapi-gen=true -type Certificate struct { - metav1.TypeMeta `json:",inline"` - metav1.ObjectMeta `json:"metadata,omitempty"` - - // Desired state of the Certificate resource. - Spec CertificateSpec `json:"spec,omitempty"` - - // Status of the Certificate. This is set and managed automatically. - Status CertificateStatus `json:"status,omitempty"` -} - -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object - -// CertificateList is a list of Certificates -type CertificateList struct { - metav1.TypeMeta `json:",inline"` - metav1.ListMeta `json:"metadata"` - - Items []Certificate `json:"items"` -} - -// +kubebuilder:validation:Enum=rsa;ecdsa -type KeyAlgorithm string - -const ( - // Denotes the RSA private key type. - RSAKeyAlgorithm KeyAlgorithm = "rsa" - - // Denotes the ECDSA private key type. - ECDSAKeyAlgorithm KeyAlgorithm = "ecdsa" -) - -// +kubebuilder:validation:Enum=pkcs1;pkcs8 -type KeyEncoding string - -const ( - // 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 KeyEncoding = "pkcs1" - - // 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 KeyEncoding = "pkcs8" -) - -// CertificateSpec defines the desired state of Certificate. -// A valid Certificate requires at least one of a CommonName, DNSName, or -// URISAN to be valid. -type CertificateSpec struct { - // Full X509 name specification (https://golang.org/pkg/crypto/x509/pkix/#Name). - // +optional - Subject *X509Subject `json:"subject,omitempty"` - - // LiteralSubject is an LDAP formatted string that represents the [X.509 Subject field](https://datatracker.ietf.org/doc/html/rfc5280#section-4.1.2.6). - // 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. - // +optional - LiteralSubject string `json:"literalSubject,omitempty"` - - // 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 - // +optional - CommonName string `json:"commonName,omitempty"` - - // 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 2/3 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 - // +optional - Duration *metav1.Duration `json:"duration,omitempty"` - - // How long before the currently issued certificate's expiry - // cert-manager should renew the certificate. The default is 2/3 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 - // +optional - RenewBefore *metav1.Duration `json:"renewBefore,omitempty"` - - // DNSNames is a list of DNS subjectAltNames to be set on the Certificate. - // +optional - DNSNames []string `json:"dnsNames,omitempty"` - - // IPAddresses is a list of IP address subjectAltNames to be set on the Certificate. - // +optional - IPAddresses []string `json:"ipAddresses,omitempty"` - - // URISANs is a list of URI subjectAltNames to be set on the Certificate. - // +optional - URISANs []string `json:"uriSANs,omitempty"` - - // EmailSANs is a list of email subjectAltNames to be set on the Certificate. - // +optional - EmailSANs []string `json:"emailSANs,omitempty"` - - // 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. - SecretName string `json:"secretName"` - - // 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. - // +optional - SecretTemplate *CertificateSecretTemplate `json:"secretTemplate,omitempty"` - - // Keystores configures additional keystore output formats stored in the - // `secretName` Secret resource. - // +optional - Keystores *CertificateKeystores `json:"keystores,omitempty"` - - // 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. - IssuerRef cmmeta.ObjectReference `json:"issuerRef"` - - // IsCA will mark this Certificate as valid for certificate signing. - // This will automatically add the `cert sign` usage to the list of `usages`. - // +optional - IsCA bool `json:"isCA,omitempty"` - - // Usages is the set of x509 usages that are requested for the certificate. - // Defaults to `digital signature` and `key encipherment` if not specified. - // +optional - Usages []KeyUsage `json:"usages,omitempty"` - - // KeySize is the key bit size of the corresponding private key for this certificate. - // If `keyAlgorithm` is set to `rsa`, valid values are `2048`, `4096` or `8192`, - // and will default to `2048` if not specified. - // If `keyAlgorithm` is set to `ecdsa`, valid values are `256`, `384` or `521`, - // and will default to `256` if not specified. - // No other values are allowed. - // +optional - KeySize int `json:"keySize,omitempty"` // Validated by webhook. Be mindful of adding OpenAPI validation- see https://github.com/cert-manager/cert-manager/issues/3644 . - - // KeyAlgorithm is the private key algorithm of the corresponding private key - // for this certificate. If provided, allowed values are either `rsa` or `ecdsa` - // If `keyAlgorithm` is specified and `keySize` 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. - // +optional - KeyAlgorithm KeyAlgorithm `json:"keyAlgorithm,omitempty"` - - // KeyEncoding is the private key cryptography standards (PKCS) - // 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. - // If KeyEncoding is not specified, then `pkcs1` will be used by default. - // +optional - KeyEncoding KeyEncoding `json:"keyEncoding,omitempty"` - - // Options to control private keys used for the Certificate. - // +optional - PrivateKey *CertificatePrivateKey `json:"privateKey,omitempty"` - - // EncodeUsagesInRequest controls whether key usages should be present - // in the CertificateRequest - // +optional - EncodeUsagesInRequest *bool `json:"encodeUsagesInRequest,omitempty"` - - // 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`. - // +kubebuilder:validation:ExclusiveMaximum=false - // +optional - RevisionHistoryLimit *int32 `json:"revisionHistoryLimit,omitempty"` // Validated by the validating webhook. - - // 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. - // +optional - AdditionalOutputFormats []CertificateAdditionalOutputFormat `json:"additionalOutputFormats,omitempty"` -} - -// CertificatePrivateKey contains configuration options for private keys -// used by the Certificate controller. -// This allows control of how private keys are rotated. -type CertificatePrivateKey struct { - // 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 - RotationPolicy PrivateKeyRotationPolicy `json:"rotationPolicy,omitempty"` -} - -// Denotes how private keys should be generated or sourced when a Certificate -// is being issued. -type PrivateKeyRotationPolicy string - -var ( - // RotationPolicyNever means 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. - RotationPolicyNever PrivateKeyRotationPolicy = "Never" - - // RotationPolicyAlways means a private key matching the specified - // requirements will be generated whenever a re-issuance occurs. - RotationPolicyAlways PrivateKeyRotationPolicy = "Always" -) - -// X509Subject Full X509 name specification -type X509Subject struct { - // Organizations to be used on the Certificate. - // +optional - Organizations []string `json:"organizations,omitempty"` - // Countries to be used on the Certificate. - // +optional - Countries []string `json:"countries,omitempty"` - // Organizational Units to be used on the Certificate. - // +optional - OrganizationalUnits []string `json:"organizationalUnits,omitempty"` - // Cities to be used on the Certificate. - // +optional - Localities []string `json:"localities,omitempty"` - // State/Provinces to be used on the Certificate. - // +optional - Provinces []string `json:"provinces,omitempty"` - // Street addresses to be used on the Certificate. - // +optional - StreetAddresses []string `json:"streetAddresses,omitempty"` - // Postal codes to be used on the Certificate. - // +optional - PostalCodes []string `json:"postalCodes,omitempty"` - // Serial number to be used on the Certificate. - // +optional - SerialNumber string `json:"serialNumber,omitempty"` -} - -// CertificateKeystores configures additional keystore output formats to be -// created in the Certificate's output Secret. -type CertificateKeystores struct { - // JKS configures options for storing a JKS keystore in the - // `spec.secretName` Secret resource. - JKS *JKSKeystore `json:"jks,omitempty"` - - // PKCS12 configures options for storing a PKCS12 keystore in the - // `spec.secretName` Secret resource. - PKCS12 *PKCS12Keystore `json:"pkcs12,omitempty"` -} - -// JKS configures options for storing a JKS keystore in the `spec.secretName` -// Secret resource. -type JKSKeystore struct { - // 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 only be updated upon re-issuance. - // 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 bool `json:"create"` - - // PasswordSecretRef is a reference to a key in a Secret resource - // containing the password used to encrypt the JKS keystore. - PasswordSecretRef cmmeta.SecretKeySelector `json:"passwordSecretRef"` -} - -// PKCS12 configures options for storing a PKCS12 keystore in the -// `spec.secretName` Secret resource. -type PKCS12Keystore struct { - // 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 only be updated upon re-issuance. - // 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 bool `json:"create"` - - // PasswordSecretRef is a reference to a key in a Secret resource - // containing the password used to encrypt the PKCS12 keystore. - PasswordSecretRef cmmeta.SecretKeySelector `json:"passwordSecretRef"` -} - -// CertificateStatus defines the observed state of Certificate -type CertificateStatus struct { - // List of status conditions to indicate the status of certificates. - // Known condition types are `Ready` and `Issuing`. - // +listType=map - // +listMapKey=type - // +optional - Conditions []CertificateCondition `json:"conditions,omitempty"` - - // 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. - // +optional - LastFailureTime *metav1.Time `json:"lastFailureTime,omitempty"` - - // The time after which the certificate stored in the secret named - // by this resource in spec.secretName is valid. - // +optional - NotBefore *metav1.Time `json:"notBefore,omitempty"` - - // The expiration time of the certificate stored in the secret named - // by this resource in `spec.secretName`. - // +optional - NotAfter *metav1.Time `json:"notAfter,omitempty"` - - // RenewalTime is the time at which the certificate will be next - // renewed. - // If not set, no upcoming renewal is scheduled. - // +optional - RenewalTime *metav1.Time `json:"renewalTime,omitempty"` - - // 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. - // +optional - Revision *int `json:"revision,omitempty"` - - // 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. - // +optional - NextPrivateKeySecretName *string `json:"nextPrivateKeySecretName,omitempty"` - - // 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). - // +optional - FailedIssuanceAttempts *int `json:"failedIssuanceAttempts,omitempty"` -} - -// CertificateCondition contains condition information for an Certificate. -type CertificateCondition struct { - // Type of the condition, known values are (`Ready`, `Issuing`). - Type CertificateConditionType `json:"type"` - - // Status of the condition, one of (`True`, `False`, `Unknown`). - Status cmmeta.ConditionStatus `json:"status"` - - // LastTransitionTime is the timestamp corresponding to the last status - // change of this condition. - // +optional - LastTransitionTime *metav1.Time `json:"lastTransitionTime,omitempty"` - - // Reason is a brief machine readable explanation for the condition's last - // transition. - // +optional - Reason string `json:"reason,omitempty"` - - // Message is a human readable description of the details of the last - // transition, complementing reason. - // +optional - Message string `json:"message,omitempty"` - - // 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. - // +optional - ObservedGeneration int64 `json:"observedGeneration,omitempty"` -} - -// CertificateConditionType represents an Certificate condition value. -type CertificateConditionType string - -const ( - // 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 - CertificateConditionReady CertificateConditionType = "Ready" - - // 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. - CertificateConditionIssuing CertificateConditionType = "Issuing" -) - -// CertificateSecretTemplate defines the default labels and annotations -// to be copied to the Kubernetes Secret resource named in `CertificateSpec.secretName`. -type CertificateSecretTemplate struct { - // Annotations is a key value map to be copied to the target Kubernetes Secret. - // +optional - Annotations map[string]string `json:"annotations,omitempty"` - - // Labels is a key value map to be copied to the target Kubernetes Secret. - // +optional - Labels map[string]string `json:"labels,omitempty"` -} - -// 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). -// +kubebuilder:validation:Enum=DER;CombinedPEM -type CertificateOutputFormatType string - -const ( - // CertificateOutputFormatDER writes the Certificate's private key in DER - // binary format to the `key.der` target Secret Data key. - CertificateOutputFormatDER CertificateOutputFormatType = "DER" - - // 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 - // (` + \n + `). - CertificateOutputFormatCombinedPEM CertificateOutputFormatType = "CombinedPEM" -) - -// CertificateAdditionalOutputFormat defines an additional output format of a -// Certificate resource. These contain supplementary data formats of the signed -// certificate chain and paired private key. -type CertificateAdditionalOutputFormat struct { - // Type is the name of the format type that should be written to the - // Certificate's target Secret. - Type CertificateOutputFormatType `json:"type"` -} diff --git a/internal/apis/certmanager/v1alpha3/types_certificaterequest.go b/internal/apis/certmanager/v1alpha3/types_certificaterequest.go deleted file mode 100644 index 4001f2644bd..00000000000 --- a/internal/apis/certmanager/v1alpha3/types_certificaterequest.go +++ /dev/null @@ -1,207 +0,0 @@ -/* -Copyright 2020 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. -*/ - -package v1alpha3 - -import ( - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - - cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" -) - -const ( - // Pending indicates that a CertificateRequest is still in progress. - CertificateRequestReasonPending = "Pending" - - // Failed indicates that a CertificateRequest has failed, either due to - // timing out or some other critical failure. - CertificateRequestReasonFailed = "Failed" - - // Issued indicates that a CertificateRequest has been completed, and that - // the `status.certificate` field is set. - CertificateRequestReasonIssued = "Issued" - - // Denied is a Ready condition reason that indicates that a - // CertificateRequest has been denied, and the CertificateRequest will never - // be issued. - CertificateRequestReasonDenied = "Denied" -) - -// +genclient -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object - -// 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. -// +k8s:openapi-gen=true -type CertificateRequest struct { - metav1.TypeMeta `json:",inline"` - metav1.ObjectMeta `json:"metadata,omitempty"` - - // Desired state of the CertificateRequest resource. - Spec CertificateRequestSpec `json:"spec,omitempty"` - - // Status of the CertificateRequest. This is set and managed automatically. - Status CertificateRequestStatus `json:"status,omitempty"` -} - -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object - -// CertificateRequestList is a list of Certificates -type CertificateRequestList struct { - metav1.TypeMeta `json:",inline"` - metav1.ListMeta `json:"metadata"` - - Items []CertificateRequest `json:"items"` -} - -// CertificateRequestSpec defines the desired state of CertificateRequest -type CertificateRequestSpec struct { - // The requested 'duration' (i.e. lifetime) of the Certificate. - // This option may be ignored/overridden by some issuer types. - // +optional - Duration *metav1.Duration `json:"duration,omitempty"` - - // 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. - IssuerRef cmmeta.ObjectReference `json:"issuerRef"` - - // The PEM-encoded x509 certificate signing request to be submitted to the - // CA for signing. - CSRPEM []byte `json:"csr"` - - // 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`. - // +optional - IsCA bool `json:"isCA,omitempty"` - - // Usages is the set of x509 usages that are requested for the certificate. - // Defaults to `digital signature` and `key encipherment` if not specified. - // +optional - Usages []KeyUsage `json:"usages,omitempty"` - - // Username contains the name of the user that created the CertificateRequest. - // Populated by the cert-manager webhook on creation and immutable. - // +optional - Username string `json:"username,omitempty"` - // UID contains the uid of the user that created the CertificateRequest. - // Populated by the cert-manager webhook on creation and immutable. - // +optional - UID string `json:"uid,omitempty"` - // Groups contains group membership of the user that created the CertificateRequest. - // Populated by the cert-manager webhook on creation and immutable. - // +listType=atomic - // +optional - Groups []string `json:"groups,omitempty"` - // Extra contains extra attributes of the user that created the CertificateRequest. - // Populated by the cert-manager webhook on creation and immutable. - // +optional - Extra map[string][]string `json:"extra,omitempty"` -} - -// CertificateRequestStatus defines the observed state of CertificateRequest and -// resulting signed certificate. -type CertificateRequestStatus struct { - // List of status conditions to indicate the status of a CertificateRequest. - // Known condition types are `Ready` and `InvalidRequest`. - // +listType=map - // +listMapKey=type - // +optional - Conditions []CertificateRequestCondition `json:"conditions,omitempty"` - - // 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. - // +optional - Certificate []byte `json:"certificate,omitempty"` - - // 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. - // +optional - CA []byte `json:"ca,omitempty"` - - // FailureTime stores the time that this CertificateRequest failed. This is - // used to influence garbage collection and back-off. - // +optional - FailureTime *metav1.Time `json:"failureTime,omitempty"` -} - -// CertificateRequestCondition contains condition information for a CertificateRequest. -type CertificateRequestCondition struct { - // Type of the condition, known values are (`Ready`, - // `InvalidRequest`, `Approved`, `Denied`). - Type CertificateRequestConditionType `json:"type"` - - // Status of the condition, one of (`True`, `False`, `Unknown`). - Status cmmeta.ConditionStatus `json:"status"` - - // LastTransitionTime is the timestamp corresponding to the last status - // change of this condition. - // +optional - LastTransitionTime *metav1.Time `json:"lastTransitionTime,omitempty"` - - // Reason is a brief machine readable explanation for the condition's last - // transition. - // +optional - Reason string `json:"reason,omitempty"` - - // Message is a human readable description of the details of the last - // transition, complementing reason. - // +optional - Message string `json:"message,omitempty"` -} - -// CertificateRequestConditionType represents an Certificate condition value. -type CertificateRequestConditionType string - -const ( - // CertificateRequestConditionReady indicates that a certificate is ready for use. - // This is defined as: - // - The target certificate exists in CertificateRequest.Status - CertificateRequestConditionReady CertificateRequestConditionType = "Ready" - - // 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. - CertificateRequestConditionInvalidRequest CertificateRequestConditionType = "InvalidRequest" - - // 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. - CertificateRequestConditionApproved CertificateRequestConditionType = "Approved" - - // 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. - CertificateRequestConditionDenied CertificateRequestConditionType = "Denied" -) diff --git a/internal/apis/certmanager/v1alpha3/types_issuer.go b/internal/apis/certmanager/v1alpha3/types_issuer.go deleted file mode 100644 index 29ccd7ac599..00000000000 --- a/internal/apis/certmanager/v1alpha3/types_issuer.go +++ /dev/null @@ -1,353 +0,0 @@ -/* -Copyright 2020 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. -*/ - -package v1alpha3 - -import ( - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - - cmacme "github.com/cert-manager/cert-manager/internal/apis/acme/v1alpha3" - cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" -) - -// +genclient -// +genclient:nonNamespaced -// +k8s:openapi-gen=true -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object - -// 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. -type ClusterIssuer struct { - metav1.TypeMeta `json:",inline"` - metav1.ObjectMeta `json:"metadata,omitempty"` - - // Desired state of the ClusterIssuer resource. - Spec IssuerSpec `json:"spec,omitempty"` - - // Status of the ClusterIssuer. This is set and managed automatically. - Status IssuerStatus `json:"status,omitempty"` -} - -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object - -// ClusterIssuerList is a list of Issuers -type ClusterIssuerList struct { - metav1.TypeMeta `json:",inline"` - metav1.ListMeta `json:"metadata"` - - Items []ClusterIssuer `json:"items"` -} - -// +genclient -// +k8s:openapi-gen=true -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object - -// 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. -type Issuer struct { - metav1.TypeMeta `json:",inline"` - metav1.ObjectMeta `json:"metadata,omitempty"` - - // Desired state of the Issuer resource. - Spec IssuerSpec `json:"spec,omitempty"` - - // Status of the Issuer. This is set and managed automatically. - Status IssuerStatus `json:"status,omitempty"` -} - -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object - -// IssuerList is a list of Issuers -type IssuerList struct { - metav1.TypeMeta `json:",inline"` - metav1.ListMeta `json:"metadata"` - - Items []Issuer `json:"items"` -} - -// IssuerSpec is the specification of an Issuer. This includes any -// configuration required for the issuer. -type IssuerSpec struct { - IssuerConfig `json:",inline"` -} - -// The configuration for the issuer. -// Only one of these can be set. -type IssuerConfig struct { - // ACME configures this issuer to communicate with a RFC8555 (ACME) server - // to obtain signed x509 certificates. - // +optional - ACME *cmacme.ACMEIssuer `json:"acme,omitempty"` - - // 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. - // +optional - CA *CAIssuer `json:"ca,omitempty"` - - // Vault configures this issuer to sign certificates using a HashiCorp Vault - // PKI backend. - // +optional - Vault *VaultIssuer `json:"vault,omitempty"` - - // SelfSigned configures this issuer to 'self sign' certificates using the - // private key used to create the CertificateRequest object. - // +optional - SelfSigned *SelfSignedIssuer `json:"selfSigned,omitempty"` - - // Venafi configures this issuer to sign certificates using a Venafi TPP - // or Venafi Cloud policy zone. - // +optional - Venafi *VenafiIssuer `json:"venafi,omitempty"` -} - -// Configures an issuer to sign certificates using a Venafi TPP -// or Cloud policy zone. -type VenafiIssuer struct { - // 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. - Zone string `json:"zone"` - - // TPP specifies Trust Protection Platform configuration settings. - // Only one of TPP or Cloud may be specified. - // +optional - TPP *VenafiTPP `json:"tpp,omitempty"` - - // Cloud specifies the Venafi cloud configuration settings. - // Only one of TPP or Cloud may be specified. - // +optional - Cloud *VenafiCloud `json:"cloud,omitempty"` -} - -// VenafiTPP defines connection configuration details for a Venafi TPP instance -type VenafiTPP struct { - // URL is the base URL for the vedsdk endpoint of the Venafi TPP instance, - // for example: "https://tpp.example.com/vedsdk". - URL string `json:"url"` - - // 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'. - CredentialsRef cmmeta.LocalObjectReference `json:"credentialsRef"` - - // CABundle is a PEM encoded TLS certificate to use to verify connections to - // the TPP instance. - // If specified, system roots will not be used and the issuing CA for the - // TPP instance must be verifiable using the provided root. - // If not specified, the connection will be verified using the cert-manager - // system root certificates. - // +optional - CABundle []byte `json:"caBundle,omitempty"` -} - -// VenafiCloud defines connection configuration details for Venafi Cloud -type VenafiCloud struct { - // URL is the base URL for Venafi Cloud. - // Defaults to "https://api.venafi.cloud/v1". - // +optional - URL string `json:"url,omitempty"` - - // APITokenSecretRef is a secret key selector for the Venafi Cloud API token. - APITokenSecretRef cmmeta.SecretKeySelector `json:"apiTokenSecretRef"` -} - -// Configures an issuer to 'self sign' certificates using the -// private key used to create the CertificateRequest object. -type SelfSignedIssuer struct { - // 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. - // +optional - CRLDistributionPoints []string `json:"crlDistributionPoints,omitempty"` -} - -// Configures an issuer to sign certificates using a HashiCorp Vault -// PKI backend. -type VaultIssuer struct { - // Auth configures how cert-manager authenticates with the Vault server. - Auth VaultAuth `json:"auth"` - - // Server is the connection address for the Vault server, e.g: "https://vault.example.com:8200". - Server string `json:"server"` - - // Path is the mount path of the Vault PKI backend's `sign` endpoint, e.g: - // "my_pki_mount/sign/my-role-name". - Path string `json:"path"` - - // 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 - // +optional - Namespace string `json:"namespace,omitempty"` - - // PEM-encoded CA bundle (base64-encoded) used to validate Vault server - // certificate. Only used if the Server URL is using HTTPS protocol. This - // parameter is ignored for plain HTTP protocol connection. If not set the - // system root certificates are used to validate the TLS connection. - // Mutually exclusive with CABundleSecretRef. If neither CABundle nor CABundleSecretRef are defined, - // the cert-manager controller system root certificates are used to validate the TLS connection. - // +optional - CABundle []byte `json:"caBundle,omitempty"` - - // CABundleSecretRef is a reference to a Secret which contains the CABundle which will be used when - // connecting to Vault when using HTTPS. - // Mutually exclusive with CABundle. If neither CABundleSecretRef nor CABundle are defined, the cert-manager - // controller system root certificates are used to validate the TLS connection. - // If no key for the Secret is specified, cert-manager will default to 'ca.crt'. - // +optional - CABundleSecretRef *cmmeta.SecretKeySelector `json:"caBundleSecretRef,omitempty"` -} - -// Configuration used to authenticate with a Vault server. -// Only one of `tokenSecretRef`, `appRole` or `kubernetes` may be specified. -type VaultAuth struct { - // TokenSecretRef authenticates with Vault by presenting a token. - // +optional - TokenSecretRef *cmmeta.SecretKeySelector `json:"tokenSecretRef,omitempty"` - - // AppRole authenticates with Vault using the App Role auth mechanism, - // with the role and secret stored in a Kubernetes Secret resource. - // +optional - AppRole *VaultAppRole `json:"appRole,omitempty"` - - // Kubernetes authenticates with Vault by passing the ServiceAccount - // token stored in the named Secret resource to the Vault server. - // +optional - Kubernetes *VaultKubernetesAuth `json:"kubernetes,omitempty"` -} - -// VaultAppRole authenticates with Vault using the App Role auth mechanism, -// with the role and secret stored in a Kubernetes Secret resource. -type VaultAppRole struct { - // Path where the App Role authentication backend is mounted in Vault, e.g: - // "approle" - Path string `json:"path"` - - // RoleID configured in the App Role authentication backend when setting - // up the authentication backend in Vault. - RoleId string `json:"roleId"` - - // 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. - SecretRef cmmeta.SecretKeySelector `json:"secretRef"` -} - -// Authenticate against Vault using a Kubernetes ServiceAccount token stored in -// a Secret. -type VaultKubernetesAuth struct { - // 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. - // +optional - Path string `json:"mountPath,omitempty"` - - // The required Secret field containing a Kubernetes ServiceAccount JWT used - // for authenticating with Vault. Use of 'ambient credentials' is not - // supported. - SecretRef cmmeta.SecretKeySelector `json:"secretRef"` - - // A required field containing the Vault Role to assume. A Role binds a - // Kubernetes ServiceAccount with a set of Vault policies. - Role string `json:"role"` -} - -type CAIssuer struct { - // SecretName is the name of the secret used to sign Certificates issued - // by this Issuer. - SecretName string `json:"secretName"` - - // 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. - // +optional - CRLDistributionPoints []string `json:"crlDistributionPoints,omitempty"` - - // 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". - // +optional - OCSPServers []string `json:"ocspServers,omitempty"` -} - -// IssuerStatus contains status information about an Issuer -type IssuerStatus struct { - // List of status conditions to indicate the status of a CertificateRequest. - // Known condition types are `Ready`. - // +listType=map - // +listMapKey=type - // +optional - Conditions []IssuerCondition `json:"conditions,omitempty"` - - // ACME specific status options. - // This field should only be set if the Issuer is configured to use an ACME - // server to issue certificates. - // +optional - ACME *cmacme.ACMEIssuerStatus `json:"acme,omitempty"` -} - -// IssuerCondition contains condition information for an Issuer. -type IssuerCondition struct { - // Type of the condition, known values are (`Ready`). - Type IssuerConditionType `json:"type"` - - // Status of the condition, one of (`True`, `False`, `Unknown`). - Status cmmeta.ConditionStatus `json:"status"` - - // LastTransitionTime is the timestamp corresponding to the last status - // change of this condition. - // +optional - LastTransitionTime *metav1.Time `json:"lastTransitionTime,omitempty"` - - // Reason is a brief machine readable explanation for the condition's last - // transition. - // +optional - Reason string `json:"reason,omitempty"` - - // Message is a human readable description of the details of the last - // transition, complementing reason. - // +optional - Message string `json:"message,omitempty"` - - // 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. - // +optional - ObservedGeneration int64 `json:"observedGeneration,omitempty"` -} - -// IssuerConditionType represents an Issuer condition value. -type IssuerConditionType string - -const ( - // 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. - IssuerConditionReady IssuerConditionType = "Ready" -) diff --git a/internal/apis/certmanager/v1alpha3/zz_generated.conversion.go b/internal/apis/certmanager/v1alpha3/zz_generated.conversion.go deleted file mode 100644 index 844ae394ef9..00000000000 --- a/internal/apis/certmanager/v1alpha3/zz_generated.conversion.go +++ /dev/null @@ -1,1610 +0,0 @@ -//go:build !ignore_autogenerated -// +build !ignore_autogenerated - -/* -Copyright 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. -*/ - -// Code generated by conversion-gen. DO NOT EDIT. - -package v1alpha3 - -import ( - unsafe "unsafe" - - acme "github.com/cert-manager/cert-manager/internal/apis/acme" - acmev1alpha3 "github.com/cert-manager/cert-manager/internal/apis/acme/v1alpha3" - certmanager "github.com/cert-manager/cert-manager/internal/apis/certmanager" - meta "github.com/cert-manager/cert-manager/internal/apis/meta" - apismetav1 "github.com/cert-manager/cert-manager/internal/apis/meta/v1" - metav1 "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" - conversion "k8s.io/apimachinery/pkg/conversion" - runtime "k8s.io/apimachinery/pkg/runtime" -) - -func init() { - localSchemeBuilder.Register(RegisterConversions) -} - -// RegisterConversions adds conversion functions to the given scheme. -// Public to allow building arbitrary schemes. -func RegisterConversions(s *runtime.Scheme) error { - if err := s.AddGeneratedConversionFunc((*CAIssuer)(nil), (*certmanager.CAIssuer)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1alpha3_CAIssuer_To_certmanager_CAIssuer(a.(*CAIssuer), b.(*certmanager.CAIssuer), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*certmanager.CAIssuer)(nil), (*CAIssuer)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_certmanager_CAIssuer_To_v1alpha3_CAIssuer(a.(*certmanager.CAIssuer), b.(*CAIssuer), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*Certificate)(nil), (*certmanager.Certificate)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1alpha3_Certificate_To_certmanager_Certificate(a.(*Certificate), b.(*certmanager.Certificate), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*certmanager.Certificate)(nil), (*Certificate)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_certmanager_Certificate_To_v1alpha3_Certificate(a.(*certmanager.Certificate), b.(*Certificate), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*CertificateAdditionalOutputFormat)(nil), (*certmanager.CertificateAdditionalOutputFormat)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1alpha3_CertificateAdditionalOutputFormat_To_certmanager_CertificateAdditionalOutputFormat(a.(*CertificateAdditionalOutputFormat), b.(*certmanager.CertificateAdditionalOutputFormat), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*certmanager.CertificateAdditionalOutputFormat)(nil), (*CertificateAdditionalOutputFormat)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_certmanager_CertificateAdditionalOutputFormat_To_v1alpha3_CertificateAdditionalOutputFormat(a.(*certmanager.CertificateAdditionalOutputFormat), b.(*CertificateAdditionalOutputFormat), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*CertificateCondition)(nil), (*certmanager.CertificateCondition)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1alpha3_CertificateCondition_To_certmanager_CertificateCondition(a.(*CertificateCondition), b.(*certmanager.CertificateCondition), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*certmanager.CertificateCondition)(nil), (*CertificateCondition)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_certmanager_CertificateCondition_To_v1alpha3_CertificateCondition(a.(*certmanager.CertificateCondition), b.(*CertificateCondition), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*CertificateKeystores)(nil), (*certmanager.CertificateKeystores)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1alpha3_CertificateKeystores_To_certmanager_CertificateKeystores(a.(*CertificateKeystores), b.(*certmanager.CertificateKeystores), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*certmanager.CertificateKeystores)(nil), (*CertificateKeystores)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_certmanager_CertificateKeystores_To_v1alpha3_CertificateKeystores(a.(*certmanager.CertificateKeystores), b.(*CertificateKeystores), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*CertificateList)(nil), (*certmanager.CertificateList)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1alpha3_CertificateList_To_certmanager_CertificateList(a.(*CertificateList), b.(*certmanager.CertificateList), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*certmanager.CertificateList)(nil), (*CertificateList)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_certmanager_CertificateList_To_v1alpha3_CertificateList(a.(*certmanager.CertificateList), b.(*CertificateList), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*CertificatePrivateKey)(nil), (*certmanager.CertificatePrivateKey)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1alpha3_CertificatePrivateKey_To_certmanager_CertificatePrivateKey(a.(*CertificatePrivateKey), b.(*certmanager.CertificatePrivateKey), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*CertificateRequest)(nil), (*certmanager.CertificateRequest)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1alpha3_CertificateRequest_To_certmanager_CertificateRequest(a.(*CertificateRequest), b.(*certmanager.CertificateRequest), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*certmanager.CertificateRequest)(nil), (*CertificateRequest)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_certmanager_CertificateRequest_To_v1alpha3_CertificateRequest(a.(*certmanager.CertificateRequest), b.(*CertificateRequest), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*CertificateRequestCondition)(nil), (*certmanager.CertificateRequestCondition)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1alpha3_CertificateRequestCondition_To_certmanager_CertificateRequestCondition(a.(*CertificateRequestCondition), b.(*certmanager.CertificateRequestCondition), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*certmanager.CertificateRequestCondition)(nil), (*CertificateRequestCondition)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_certmanager_CertificateRequestCondition_To_v1alpha3_CertificateRequestCondition(a.(*certmanager.CertificateRequestCondition), b.(*CertificateRequestCondition), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*CertificateRequestList)(nil), (*certmanager.CertificateRequestList)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1alpha3_CertificateRequestList_To_certmanager_CertificateRequestList(a.(*CertificateRequestList), b.(*certmanager.CertificateRequestList), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*certmanager.CertificateRequestList)(nil), (*CertificateRequestList)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_certmanager_CertificateRequestList_To_v1alpha3_CertificateRequestList(a.(*certmanager.CertificateRequestList), b.(*CertificateRequestList), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*CertificateRequestStatus)(nil), (*certmanager.CertificateRequestStatus)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1alpha3_CertificateRequestStatus_To_certmanager_CertificateRequestStatus(a.(*CertificateRequestStatus), b.(*certmanager.CertificateRequestStatus), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*certmanager.CertificateRequestStatus)(nil), (*CertificateRequestStatus)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_certmanager_CertificateRequestStatus_To_v1alpha3_CertificateRequestStatus(a.(*certmanager.CertificateRequestStatus), b.(*CertificateRequestStatus), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*CertificateSecretTemplate)(nil), (*certmanager.CertificateSecretTemplate)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1alpha3_CertificateSecretTemplate_To_certmanager_CertificateSecretTemplate(a.(*CertificateSecretTemplate), b.(*certmanager.CertificateSecretTemplate), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*certmanager.CertificateSecretTemplate)(nil), (*CertificateSecretTemplate)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_certmanager_CertificateSecretTemplate_To_v1alpha3_CertificateSecretTemplate(a.(*certmanager.CertificateSecretTemplate), b.(*CertificateSecretTemplate), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*CertificateStatus)(nil), (*certmanager.CertificateStatus)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1alpha3_CertificateStatus_To_certmanager_CertificateStatus(a.(*CertificateStatus), b.(*certmanager.CertificateStatus), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*certmanager.CertificateStatus)(nil), (*CertificateStatus)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_certmanager_CertificateStatus_To_v1alpha3_CertificateStatus(a.(*certmanager.CertificateStatus), b.(*CertificateStatus), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*ClusterIssuer)(nil), (*certmanager.ClusterIssuer)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1alpha3_ClusterIssuer_To_certmanager_ClusterIssuer(a.(*ClusterIssuer), b.(*certmanager.ClusterIssuer), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*certmanager.ClusterIssuer)(nil), (*ClusterIssuer)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_certmanager_ClusterIssuer_To_v1alpha3_ClusterIssuer(a.(*certmanager.ClusterIssuer), b.(*ClusterIssuer), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*ClusterIssuerList)(nil), (*certmanager.ClusterIssuerList)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1alpha3_ClusterIssuerList_To_certmanager_ClusterIssuerList(a.(*ClusterIssuerList), b.(*certmanager.ClusterIssuerList), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*certmanager.ClusterIssuerList)(nil), (*ClusterIssuerList)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_certmanager_ClusterIssuerList_To_v1alpha3_ClusterIssuerList(a.(*certmanager.ClusterIssuerList), b.(*ClusterIssuerList), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*Issuer)(nil), (*certmanager.Issuer)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1alpha3_Issuer_To_certmanager_Issuer(a.(*Issuer), b.(*certmanager.Issuer), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*certmanager.Issuer)(nil), (*Issuer)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_certmanager_Issuer_To_v1alpha3_Issuer(a.(*certmanager.Issuer), b.(*Issuer), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*IssuerCondition)(nil), (*certmanager.IssuerCondition)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1alpha3_IssuerCondition_To_certmanager_IssuerCondition(a.(*IssuerCondition), b.(*certmanager.IssuerCondition), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*certmanager.IssuerCondition)(nil), (*IssuerCondition)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_certmanager_IssuerCondition_To_v1alpha3_IssuerCondition(a.(*certmanager.IssuerCondition), b.(*IssuerCondition), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*IssuerConfig)(nil), (*certmanager.IssuerConfig)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1alpha3_IssuerConfig_To_certmanager_IssuerConfig(a.(*IssuerConfig), b.(*certmanager.IssuerConfig), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*certmanager.IssuerConfig)(nil), (*IssuerConfig)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_certmanager_IssuerConfig_To_v1alpha3_IssuerConfig(a.(*certmanager.IssuerConfig), b.(*IssuerConfig), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*IssuerList)(nil), (*certmanager.IssuerList)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1alpha3_IssuerList_To_certmanager_IssuerList(a.(*IssuerList), b.(*certmanager.IssuerList), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*certmanager.IssuerList)(nil), (*IssuerList)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_certmanager_IssuerList_To_v1alpha3_IssuerList(a.(*certmanager.IssuerList), b.(*IssuerList), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*IssuerSpec)(nil), (*certmanager.IssuerSpec)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1alpha3_IssuerSpec_To_certmanager_IssuerSpec(a.(*IssuerSpec), b.(*certmanager.IssuerSpec), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*certmanager.IssuerSpec)(nil), (*IssuerSpec)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_certmanager_IssuerSpec_To_v1alpha3_IssuerSpec(a.(*certmanager.IssuerSpec), b.(*IssuerSpec), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*IssuerStatus)(nil), (*certmanager.IssuerStatus)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1alpha3_IssuerStatus_To_certmanager_IssuerStatus(a.(*IssuerStatus), b.(*certmanager.IssuerStatus), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*certmanager.IssuerStatus)(nil), (*IssuerStatus)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_certmanager_IssuerStatus_To_v1alpha3_IssuerStatus(a.(*certmanager.IssuerStatus), b.(*IssuerStatus), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*JKSKeystore)(nil), (*certmanager.JKSKeystore)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1alpha3_JKSKeystore_To_certmanager_JKSKeystore(a.(*JKSKeystore), b.(*certmanager.JKSKeystore), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*certmanager.JKSKeystore)(nil), (*JKSKeystore)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_certmanager_JKSKeystore_To_v1alpha3_JKSKeystore(a.(*certmanager.JKSKeystore), b.(*JKSKeystore), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*PKCS12Keystore)(nil), (*certmanager.PKCS12Keystore)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1alpha3_PKCS12Keystore_To_certmanager_PKCS12Keystore(a.(*PKCS12Keystore), b.(*certmanager.PKCS12Keystore), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*certmanager.PKCS12Keystore)(nil), (*PKCS12Keystore)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_certmanager_PKCS12Keystore_To_v1alpha3_PKCS12Keystore(a.(*certmanager.PKCS12Keystore), b.(*PKCS12Keystore), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*SelfSignedIssuer)(nil), (*certmanager.SelfSignedIssuer)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1alpha3_SelfSignedIssuer_To_certmanager_SelfSignedIssuer(a.(*SelfSignedIssuer), b.(*certmanager.SelfSignedIssuer), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*certmanager.SelfSignedIssuer)(nil), (*SelfSignedIssuer)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_certmanager_SelfSignedIssuer_To_v1alpha3_SelfSignedIssuer(a.(*certmanager.SelfSignedIssuer), b.(*SelfSignedIssuer), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*VaultAppRole)(nil), (*certmanager.VaultAppRole)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1alpha3_VaultAppRole_To_certmanager_VaultAppRole(a.(*VaultAppRole), b.(*certmanager.VaultAppRole), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*certmanager.VaultAppRole)(nil), (*VaultAppRole)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_certmanager_VaultAppRole_To_v1alpha3_VaultAppRole(a.(*certmanager.VaultAppRole), b.(*VaultAppRole), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*VaultAuth)(nil), (*certmanager.VaultAuth)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1alpha3_VaultAuth_To_certmanager_VaultAuth(a.(*VaultAuth), b.(*certmanager.VaultAuth), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*certmanager.VaultAuth)(nil), (*VaultAuth)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_certmanager_VaultAuth_To_v1alpha3_VaultAuth(a.(*certmanager.VaultAuth), b.(*VaultAuth), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*VaultIssuer)(nil), (*certmanager.VaultIssuer)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1alpha3_VaultIssuer_To_certmanager_VaultIssuer(a.(*VaultIssuer), b.(*certmanager.VaultIssuer), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*certmanager.VaultIssuer)(nil), (*VaultIssuer)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_certmanager_VaultIssuer_To_v1alpha3_VaultIssuer(a.(*certmanager.VaultIssuer), b.(*VaultIssuer), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*VaultKubernetesAuth)(nil), (*certmanager.VaultKubernetesAuth)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1alpha3_VaultKubernetesAuth_To_certmanager_VaultKubernetesAuth(a.(*VaultKubernetesAuth), b.(*certmanager.VaultKubernetesAuth), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*certmanager.VaultKubernetesAuth)(nil), (*VaultKubernetesAuth)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_certmanager_VaultKubernetesAuth_To_v1alpha3_VaultKubernetesAuth(a.(*certmanager.VaultKubernetesAuth), b.(*VaultKubernetesAuth), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*VenafiCloud)(nil), (*certmanager.VenafiCloud)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1alpha3_VenafiCloud_To_certmanager_VenafiCloud(a.(*VenafiCloud), b.(*certmanager.VenafiCloud), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*certmanager.VenafiCloud)(nil), (*VenafiCloud)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_certmanager_VenafiCloud_To_v1alpha3_VenafiCloud(a.(*certmanager.VenafiCloud), b.(*VenafiCloud), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*VenafiIssuer)(nil), (*certmanager.VenafiIssuer)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1alpha3_VenafiIssuer_To_certmanager_VenafiIssuer(a.(*VenafiIssuer), b.(*certmanager.VenafiIssuer), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*certmanager.VenafiIssuer)(nil), (*VenafiIssuer)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_certmanager_VenafiIssuer_To_v1alpha3_VenafiIssuer(a.(*certmanager.VenafiIssuer), b.(*VenafiIssuer), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*VenafiTPP)(nil), (*certmanager.VenafiTPP)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1alpha3_VenafiTPP_To_certmanager_VenafiTPP(a.(*VenafiTPP), b.(*certmanager.VenafiTPP), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*certmanager.VenafiTPP)(nil), (*VenafiTPP)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_certmanager_VenafiTPP_To_v1alpha3_VenafiTPP(a.(*certmanager.VenafiTPP), b.(*VenafiTPP), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*X509Subject)(nil), (*certmanager.X509Subject)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1alpha3_X509Subject_To_certmanager_X509Subject(a.(*X509Subject), b.(*certmanager.X509Subject), scope) - }); err != nil { - return err - } - if err := s.AddConversionFunc((*certmanager.CertificatePrivateKey)(nil), (*CertificatePrivateKey)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_certmanager_CertificatePrivateKey_To_v1alpha3_CertificatePrivateKey(a.(*certmanager.CertificatePrivateKey), b.(*CertificatePrivateKey), scope) - }); err != nil { - return err - } - if err := s.AddConversionFunc((*certmanager.CertificateRequestSpec)(nil), (*CertificateRequestSpec)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_certmanager_CertificateRequestSpec_To_v1alpha3_CertificateRequestSpec(a.(*certmanager.CertificateRequestSpec), b.(*CertificateRequestSpec), scope) - }); err != nil { - return err - } - if err := s.AddConversionFunc((*certmanager.CertificateSpec)(nil), (*CertificateSpec)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_certmanager_CertificateSpec_To_v1alpha3_CertificateSpec(a.(*certmanager.CertificateSpec), b.(*CertificateSpec), scope) - }); err != nil { - return err - } - if err := s.AddConversionFunc((*certmanager.X509Subject)(nil), (*X509Subject)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_certmanager_X509Subject_To_v1alpha3_X509Subject(a.(*certmanager.X509Subject), b.(*X509Subject), scope) - }); err != nil { - return err - } - if err := s.AddConversionFunc((*CertificateRequestSpec)(nil), (*certmanager.CertificateRequestSpec)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1alpha3_CertificateRequestSpec_To_certmanager_CertificateRequestSpec(a.(*CertificateRequestSpec), b.(*certmanager.CertificateRequestSpec), scope) - }); err != nil { - return err - } - if err := s.AddConversionFunc((*CertificateSpec)(nil), (*certmanager.CertificateSpec)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1alpha3_CertificateSpec_To_certmanager_CertificateSpec(a.(*CertificateSpec), b.(*certmanager.CertificateSpec), scope) - }); err != nil { - return err - } - return nil -} - -func autoConvert_v1alpha3_CAIssuer_To_certmanager_CAIssuer(in *CAIssuer, out *certmanager.CAIssuer, s conversion.Scope) error { - out.SecretName = in.SecretName - out.CRLDistributionPoints = *(*[]string)(unsafe.Pointer(&in.CRLDistributionPoints)) - out.OCSPServers = *(*[]string)(unsafe.Pointer(&in.OCSPServers)) - return nil -} - -// Convert_v1alpha3_CAIssuer_To_certmanager_CAIssuer is an autogenerated conversion function. -func Convert_v1alpha3_CAIssuer_To_certmanager_CAIssuer(in *CAIssuer, out *certmanager.CAIssuer, s conversion.Scope) error { - return autoConvert_v1alpha3_CAIssuer_To_certmanager_CAIssuer(in, out, s) -} - -func autoConvert_certmanager_CAIssuer_To_v1alpha3_CAIssuer(in *certmanager.CAIssuer, out *CAIssuer, s conversion.Scope) error { - out.SecretName = in.SecretName - out.CRLDistributionPoints = *(*[]string)(unsafe.Pointer(&in.CRLDistributionPoints)) - out.OCSPServers = *(*[]string)(unsafe.Pointer(&in.OCSPServers)) - return nil -} - -// Convert_certmanager_CAIssuer_To_v1alpha3_CAIssuer is an autogenerated conversion function. -func Convert_certmanager_CAIssuer_To_v1alpha3_CAIssuer(in *certmanager.CAIssuer, out *CAIssuer, s conversion.Scope) error { - return autoConvert_certmanager_CAIssuer_To_v1alpha3_CAIssuer(in, out, s) -} - -func autoConvert_v1alpha3_Certificate_To_certmanager_Certificate(in *Certificate, out *certmanager.Certificate, s conversion.Scope) error { - out.ObjectMeta = in.ObjectMeta - if err := Convert_v1alpha3_CertificateSpec_To_certmanager_CertificateSpec(&in.Spec, &out.Spec, s); err != nil { - return err - } - if err := Convert_v1alpha3_CertificateStatus_To_certmanager_CertificateStatus(&in.Status, &out.Status, s); err != nil { - return err - } - return nil -} - -// Convert_v1alpha3_Certificate_To_certmanager_Certificate is an autogenerated conversion function. -func Convert_v1alpha3_Certificate_To_certmanager_Certificate(in *Certificate, out *certmanager.Certificate, s conversion.Scope) error { - return autoConvert_v1alpha3_Certificate_To_certmanager_Certificate(in, out, s) -} - -func autoConvert_certmanager_Certificate_To_v1alpha3_Certificate(in *certmanager.Certificate, out *Certificate, s conversion.Scope) error { - out.ObjectMeta = in.ObjectMeta - if err := Convert_certmanager_CertificateSpec_To_v1alpha3_CertificateSpec(&in.Spec, &out.Spec, s); err != nil { - return err - } - if err := Convert_certmanager_CertificateStatus_To_v1alpha3_CertificateStatus(&in.Status, &out.Status, s); err != nil { - return err - } - return nil -} - -// Convert_certmanager_Certificate_To_v1alpha3_Certificate is an autogenerated conversion function. -func Convert_certmanager_Certificate_To_v1alpha3_Certificate(in *certmanager.Certificate, out *Certificate, s conversion.Scope) error { - return autoConvert_certmanager_Certificate_To_v1alpha3_Certificate(in, out, s) -} - -func autoConvert_v1alpha3_CertificateAdditionalOutputFormat_To_certmanager_CertificateAdditionalOutputFormat(in *CertificateAdditionalOutputFormat, out *certmanager.CertificateAdditionalOutputFormat, s conversion.Scope) error { - out.Type = certmanager.CertificateOutputFormatType(in.Type) - return nil -} - -// Convert_v1alpha3_CertificateAdditionalOutputFormat_To_certmanager_CertificateAdditionalOutputFormat is an autogenerated conversion function. -func Convert_v1alpha3_CertificateAdditionalOutputFormat_To_certmanager_CertificateAdditionalOutputFormat(in *CertificateAdditionalOutputFormat, out *certmanager.CertificateAdditionalOutputFormat, s conversion.Scope) error { - return autoConvert_v1alpha3_CertificateAdditionalOutputFormat_To_certmanager_CertificateAdditionalOutputFormat(in, out, s) -} - -func autoConvert_certmanager_CertificateAdditionalOutputFormat_To_v1alpha3_CertificateAdditionalOutputFormat(in *certmanager.CertificateAdditionalOutputFormat, out *CertificateAdditionalOutputFormat, s conversion.Scope) error { - out.Type = CertificateOutputFormatType(in.Type) - return nil -} - -// Convert_certmanager_CertificateAdditionalOutputFormat_To_v1alpha3_CertificateAdditionalOutputFormat is an autogenerated conversion function. -func Convert_certmanager_CertificateAdditionalOutputFormat_To_v1alpha3_CertificateAdditionalOutputFormat(in *certmanager.CertificateAdditionalOutputFormat, out *CertificateAdditionalOutputFormat, s conversion.Scope) error { - return autoConvert_certmanager_CertificateAdditionalOutputFormat_To_v1alpha3_CertificateAdditionalOutputFormat(in, out, s) -} - -func autoConvert_v1alpha3_CertificateCondition_To_certmanager_CertificateCondition(in *CertificateCondition, out *certmanager.CertificateCondition, s conversion.Scope) error { - out.Type = certmanager.CertificateConditionType(in.Type) - out.Status = meta.ConditionStatus(in.Status) - out.LastTransitionTime = (*v1.Time)(unsafe.Pointer(in.LastTransitionTime)) - out.Reason = in.Reason - out.Message = in.Message - out.ObservedGeneration = in.ObservedGeneration - return nil -} - -// Convert_v1alpha3_CertificateCondition_To_certmanager_CertificateCondition is an autogenerated conversion function. -func Convert_v1alpha3_CertificateCondition_To_certmanager_CertificateCondition(in *CertificateCondition, out *certmanager.CertificateCondition, s conversion.Scope) error { - return autoConvert_v1alpha3_CertificateCondition_To_certmanager_CertificateCondition(in, out, s) -} - -func autoConvert_certmanager_CertificateCondition_To_v1alpha3_CertificateCondition(in *certmanager.CertificateCondition, out *CertificateCondition, s conversion.Scope) error { - out.Type = CertificateConditionType(in.Type) - out.Status = metav1.ConditionStatus(in.Status) - out.LastTransitionTime = (*v1.Time)(unsafe.Pointer(in.LastTransitionTime)) - out.Reason = in.Reason - out.Message = in.Message - out.ObservedGeneration = in.ObservedGeneration - return nil -} - -// Convert_certmanager_CertificateCondition_To_v1alpha3_CertificateCondition is an autogenerated conversion function. -func Convert_certmanager_CertificateCondition_To_v1alpha3_CertificateCondition(in *certmanager.CertificateCondition, out *CertificateCondition, s conversion.Scope) error { - return autoConvert_certmanager_CertificateCondition_To_v1alpha3_CertificateCondition(in, out, s) -} - -func autoConvert_v1alpha3_CertificateKeystores_To_certmanager_CertificateKeystores(in *CertificateKeystores, out *certmanager.CertificateKeystores, s conversion.Scope) error { - if in.JKS != nil { - in, out := &in.JKS, &out.JKS - *out = new(certmanager.JKSKeystore) - if err := Convert_v1alpha3_JKSKeystore_To_certmanager_JKSKeystore(*in, *out, s); err != nil { - return err - } - } else { - out.JKS = nil - } - if in.PKCS12 != nil { - in, out := &in.PKCS12, &out.PKCS12 - *out = new(certmanager.PKCS12Keystore) - if err := Convert_v1alpha3_PKCS12Keystore_To_certmanager_PKCS12Keystore(*in, *out, s); err != nil { - return err - } - } else { - out.PKCS12 = nil - } - return nil -} - -// Convert_v1alpha3_CertificateKeystores_To_certmanager_CertificateKeystores is an autogenerated conversion function. -func Convert_v1alpha3_CertificateKeystores_To_certmanager_CertificateKeystores(in *CertificateKeystores, out *certmanager.CertificateKeystores, s conversion.Scope) error { - return autoConvert_v1alpha3_CertificateKeystores_To_certmanager_CertificateKeystores(in, out, s) -} - -func autoConvert_certmanager_CertificateKeystores_To_v1alpha3_CertificateKeystores(in *certmanager.CertificateKeystores, out *CertificateKeystores, s conversion.Scope) error { - if in.JKS != nil { - in, out := &in.JKS, &out.JKS - *out = new(JKSKeystore) - if err := Convert_certmanager_JKSKeystore_To_v1alpha3_JKSKeystore(*in, *out, s); err != nil { - return err - } - } else { - out.JKS = nil - } - if in.PKCS12 != nil { - in, out := &in.PKCS12, &out.PKCS12 - *out = new(PKCS12Keystore) - if err := Convert_certmanager_PKCS12Keystore_To_v1alpha3_PKCS12Keystore(*in, *out, s); err != nil { - return err - } - } else { - out.PKCS12 = nil - } - return nil -} - -// Convert_certmanager_CertificateKeystores_To_v1alpha3_CertificateKeystores is an autogenerated conversion function. -func Convert_certmanager_CertificateKeystores_To_v1alpha3_CertificateKeystores(in *certmanager.CertificateKeystores, out *CertificateKeystores, s conversion.Scope) error { - return autoConvert_certmanager_CertificateKeystores_To_v1alpha3_CertificateKeystores(in, out, s) -} - -func autoConvert_v1alpha3_CertificateList_To_certmanager_CertificateList(in *CertificateList, out *certmanager.CertificateList, s conversion.Scope) error { - out.ListMeta = in.ListMeta - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]certmanager.Certificate, len(*in)) - for i := range *in { - if err := Convert_v1alpha3_Certificate_To_certmanager_Certificate(&(*in)[i], &(*out)[i], s); err != nil { - return err - } - } - } else { - out.Items = nil - } - return nil -} - -// Convert_v1alpha3_CertificateList_To_certmanager_CertificateList is an autogenerated conversion function. -func Convert_v1alpha3_CertificateList_To_certmanager_CertificateList(in *CertificateList, out *certmanager.CertificateList, s conversion.Scope) error { - return autoConvert_v1alpha3_CertificateList_To_certmanager_CertificateList(in, out, s) -} - -func autoConvert_certmanager_CertificateList_To_v1alpha3_CertificateList(in *certmanager.CertificateList, out *CertificateList, s conversion.Scope) error { - out.ListMeta = in.ListMeta - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]Certificate, len(*in)) - for i := range *in { - if err := Convert_certmanager_Certificate_To_v1alpha3_Certificate(&(*in)[i], &(*out)[i], s); err != nil { - return err - } - } - } else { - out.Items = nil - } - return nil -} - -// Convert_certmanager_CertificateList_To_v1alpha3_CertificateList is an autogenerated conversion function. -func Convert_certmanager_CertificateList_To_v1alpha3_CertificateList(in *certmanager.CertificateList, out *CertificateList, s conversion.Scope) error { - return autoConvert_certmanager_CertificateList_To_v1alpha3_CertificateList(in, out, s) -} - -func autoConvert_v1alpha3_CertificatePrivateKey_To_certmanager_CertificatePrivateKey(in *CertificatePrivateKey, out *certmanager.CertificatePrivateKey, s conversion.Scope) error { - out.RotationPolicy = certmanager.PrivateKeyRotationPolicy(in.RotationPolicy) - return nil -} - -// Convert_v1alpha3_CertificatePrivateKey_To_certmanager_CertificatePrivateKey is an autogenerated conversion function. -func Convert_v1alpha3_CertificatePrivateKey_To_certmanager_CertificatePrivateKey(in *CertificatePrivateKey, out *certmanager.CertificatePrivateKey, s conversion.Scope) error { - return autoConvert_v1alpha3_CertificatePrivateKey_To_certmanager_CertificatePrivateKey(in, out, s) -} - -func autoConvert_certmanager_CertificatePrivateKey_To_v1alpha3_CertificatePrivateKey(in *certmanager.CertificatePrivateKey, out *CertificatePrivateKey, s conversion.Scope) error { - out.RotationPolicy = PrivateKeyRotationPolicy(in.RotationPolicy) - // WARNING: in.Encoding requires manual conversion: does not exist in peer-type - // WARNING: in.Algorithm requires manual conversion: does not exist in peer-type - // WARNING: in.Size requires manual conversion: does not exist in peer-type - return nil -} - -func autoConvert_v1alpha3_CertificateRequest_To_certmanager_CertificateRequest(in *CertificateRequest, out *certmanager.CertificateRequest, s conversion.Scope) error { - out.ObjectMeta = in.ObjectMeta - if err := Convert_v1alpha3_CertificateRequestSpec_To_certmanager_CertificateRequestSpec(&in.Spec, &out.Spec, s); err != nil { - return err - } - if err := Convert_v1alpha3_CertificateRequestStatus_To_certmanager_CertificateRequestStatus(&in.Status, &out.Status, s); err != nil { - return err - } - return nil -} - -// Convert_v1alpha3_CertificateRequest_To_certmanager_CertificateRequest is an autogenerated conversion function. -func Convert_v1alpha3_CertificateRequest_To_certmanager_CertificateRequest(in *CertificateRequest, out *certmanager.CertificateRequest, s conversion.Scope) error { - return autoConvert_v1alpha3_CertificateRequest_To_certmanager_CertificateRequest(in, out, s) -} - -func autoConvert_certmanager_CertificateRequest_To_v1alpha3_CertificateRequest(in *certmanager.CertificateRequest, out *CertificateRequest, s conversion.Scope) error { - out.ObjectMeta = in.ObjectMeta - if err := Convert_certmanager_CertificateRequestSpec_To_v1alpha3_CertificateRequestSpec(&in.Spec, &out.Spec, s); err != nil { - return err - } - if err := Convert_certmanager_CertificateRequestStatus_To_v1alpha3_CertificateRequestStatus(&in.Status, &out.Status, s); err != nil { - return err - } - return nil -} - -// Convert_certmanager_CertificateRequest_To_v1alpha3_CertificateRequest is an autogenerated conversion function. -func Convert_certmanager_CertificateRequest_To_v1alpha3_CertificateRequest(in *certmanager.CertificateRequest, out *CertificateRequest, s conversion.Scope) error { - return autoConvert_certmanager_CertificateRequest_To_v1alpha3_CertificateRequest(in, out, s) -} - -func autoConvert_v1alpha3_CertificateRequestCondition_To_certmanager_CertificateRequestCondition(in *CertificateRequestCondition, out *certmanager.CertificateRequestCondition, s conversion.Scope) error { - out.Type = certmanager.CertificateRequestConditionType(in.Type) - out.Status = meta.ConditionStatus(in.Status) - out.LastTransitionTime = (*v1.Time)(unsafe.Pointer(in.LastTransitionTime)) - out.Reason = in.Reason - out.Message = in.Message - return nil -} - -// Convert_v1alpha3_CertificateRequestCondition_To_certmanager_CertificateRequestCondition is an autogenerated conversion function. -func Convert_v1alpha3_CertificateRequestCondition_To_certmanager_CertificateRequestCondition(in *CertificateRequestCondition, out *certmanager.CertificateRequestCondition, s conversion.Scope) error { - return autoConvert_v1alpha3_CertificateRequestCondition_To_certmanager_CertificateRequestCondition(in, out, s) -} - -func autoConvert_certmanager_CertificateRequestCondition_To_v1alpha3_CertificateRequestCondition(in *certmanager.CertificateRequestCondition, out *CertificateRequestCondition, s conversion.Scope) error { - out.Type = CertificateRequestConditionType(in.Type) - out.Status = metav1.ConditionStatus(in.Status) - out.LastTransitionTime = (*v1.Time)(unsafe.Pointer(in.LastTransitionTime)) - out.Reason = in.Reason - out.Message = in.Message - return nil -} - -// Convert_certmanager_CertificateRequestCondition_To_v1alpha3_CertificateRequestCondition is an autogenerated conversion function. -func Convert_certmanager_CertificateRequestCondition_To_v1alpha3_CertificateRequestCondition(in *certmanager.CertificateRequestCondition, out *CertificateRequestCondition, s conversion.Scope) error { - return autoConvert_certmanager_CertificateRequestCondition_To_v1alpha3_CertificateRequestCondition(in, out, s) -} - -func autoConvert_v1alpha3_CertificateRequestList_To_certmanager_CertificateRequestList(in *CertificateRequestList, out *certmanager.CertificateRequestList, s conversion.Scope) error { - out.ListMeta = in.ListMeta - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]certmanager.CertificateRequest, len(*in)) - for i := range *in { - if err := Convert_v1alpha3_CertificateRequest_To_certmanager_CertificateRequest(&(*in)[i], &(*out)[i], s); err != nil { - return err - } - } - } else { - out.Items = nil - } - return nil -} - -// Convert_v1alpha3_CertificateRequestList_To_certmanager_CertificateRequestList is an autogenerated conversion function. -func Convert_v1alpha3_CertificateRequestList_To_certmanager_CertificateRequestList(in *CertificateRequestList, out *certmanager.CertificateRequestList, s conversion.Scope) error { - return autoConvert_v1alpha3_CertificateRequestList_To_certmanager_CertificateRequestList(in, out, s) -} - -func autoConvert_certmanager_CertificateRequestList_To_v1alpha3_CertificateRequestList(in *certmanager.CertificateRequestList, out *CertificateRequestList, s conversion.Scope) error { - out.ListMeta = in.ListMeta - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]CertificateRequest, len(*in)) - for i := range *in { - if err := Convert_certmanager_CertificateRequest_To_v1alpha3_CertificateRequest(&(*in)[i], &(*out)[i], s); err != nil { - return err - } - } - } else { - out.Items = nil - } - return nil -} - -// Convert_certmanager_CertificateRequestList_To_v1alpha3_CertificateRequestList is an autogenerated conversion function. -func Convert_certmanager_CertificateRequestList_To_v1alpha3_CertificateRequestList(in *certmanager.CertificateRequestList, out *CertificateRequestList, s conversion.Scope) error { - return autoConvert_certmanager_CertificateRequestList_To_v1alpha3_CertificateRequestList(in, out, s) -} - -func autoConvert_v1alpha3_CertificateRequestSpec_To_certmanager_CertificateRequestSpec(in *CertificateRequestSpec, out *certmanager.CertificateRequestSpec, s conversion.Scope) error { - out.Duration = (*v1.Duration)(unsafe.Pointer(in.Duration)) - if err := apismetav1.Convert_v1_ObjectReference_To_meta_ObjectReference(&in.IssuerRef, &out.IssuerRef, s); err != nil { - return err - } - // WARNING: in.CSRPEM requires manual conversion: does not exist in peer-type - out.IsCA = in.IsCA - out.Usages = *(*[]certmanager.KeyUsage)(unsafe.Pointer(&in.Usages)) - out.Username = in.Username - out.UID = in.UID - out.Groups = *(*[]string)(unsafe.Pointer(&in.Groups)) - out.Extra = *(*map[string][]string)(unsafe.Pointer(&in.Extra)) - return nil -} - -func autoConvert_certmanager_CertificateRequestSpec_To_v1alpha3_CertificateRequestSpec(in *certmanager.CertificateRequestSpec, out *CertificateRequestSpec, s conversion.Scope) error { - out.Duration = (*v1.Duration)(unsafe.Pointer(in.Duration)) - if err := apismetav1.Convert_meta_ObjectReference_To_v1_ObjectReference(&in.IssuerRef, &out.IssuerRef, s); err != nil { - return err - } - // WARNING: in.Request requires manual conversion: does not exist in peer-type - out.IsCA = in.IsCA - out.Usages = *(*[]KeyUsage)(unsafe.Pointer(&in.Usages)) - out.Username = in.Username - out.UID = in.UID - out.Groups = *(*[]string)(unsafe.Pointer(&in.Groups)) - out.Extra = *(*map[string][]string)(unsafe.Pointer(&in.Extra)) - return nil -} - -func autoConvert_v1alpha3_CertificateRequestStatus_To_certmanager_CertificateRequestStatus(in *CertificateRequestStatus, out *certmanager.CertificateRequestStatus, s conversion.Scope) error { - out.Conditions = *(*[]certmanager.CertificateRequestCondition)(unsafe.Pointer(&in.Conditions)) - out.Certificate = *(*[]byte)(unsafe.Pointer(&in.Certificate)) - out.CA = *(*[]byte)(unsafe.Pointer(&in.CA)) - out.FailureTime = (*v1.Time)(unsafe.Pointer(in.FailureTime)) - return nil -} - -// Convert_v1alpha3_CertificateRequestStatus_To_certmanager_CertificateRequestStatus is an autogenerated conversion function. -func Convert_v1alpha3_CertificateRequestStatus_To_certmanager_CertificateRequestStatus(in *CertificateRequestStatus, out *certmanager.CertificateRequestStatus, s conversion.Scope) error { - return autoConvert_v1alpha3_CertificateRequestStatus_To_certmanager_CertificateRequestStatus(in, out, s) -} - -func autoConvert_certmanager_CertificateRequestStatus_To_v1alpha3_CertificateRequestStatus(in *certmanager.CertificateRequestStatus, out *CertificateRequestStatus, s conversion.Scope) error { - out.Conditions = *(*[]CertificateRequestCondition)(unsafe.Pointer(&in.Conditions)) - out.Certificate = *(*[]byte)(unsafe.Pointer(&in.Certificate)) - out.CA = *(*[]byte)(unsafe.Pointer(&in.CA)) - out.FailureTime = (*v1.Time)(unsafe.Pointer(in.FailureTime)) - return nil -} - -// Convert_certmanager_CertificateRequestStatus_To_v1alpha3_CertificateRequestStatus is an autogenerated conversion function. -func Convert_certmanager_CertificateRequestStatus_To_v1alpha3_CertificateRequestStatus(in *certmanager.CertificateRequestStatus, out *CertificateRequestStatus, s conversion.Scope) error { - return autoConvert_certmanager_CertificateRequestStatus_To_v1alpha3_CertificateRequestStatus(in, out, s) -} - -func autoConvert_v1alpha3_CertificateSecretTemplate_To_certmanager_CertificateSecretTemplate(in *CertificateSecretTemplate, out *certmanager.CertificateSecretTemplate, s conversion.Scope) error { - out.Annotations = *(*map[string]string)(unsafe.Pointer(&in.Annotations)) - out.Labels = *(*map[string]string)(unsafe.Pointer(&in.Labels)) - return nil -} - -// Convert_v1alpha3_CertificateSecretTemplate_To_certmanager_CertificateSecretTemplate is an autogenerated conversion function. -func Convert_v1alpha3_CertificateSecretTemplate_To_certmanager_CertificateSecretTemplate(in *CertificateSecretTemplate, out *certmanager.CertificateSecretTemplate, s conversion.Scope) error { - return autoConvert_v1alpha3_CertificateSecretTemplate_To_certmanager_CertificateSecretTemplate(in, out, s) -} - -func autoConvert_certmanager_CertificateSecretTemplate_To_v1alpha3_CertificateSecretTemplate(in *certmanager.CertificateSecretTemplate, out *CertificateSecretTemplate, s conversion.Scope) error { - out.Annotations = *(*map[string]string)(unsafe.Pointer(&in.Annotations)) - out.Labels = *(*map[string]string)(unsafe.Pointer(&in.Labels)) - return nil -} - -// Convert_certmanager_CertificateSecretTemplate_To_v1alpha3_CertificateSecretTemplate is an autogenerated conversion function. -func Convert_certmanager_CertificateSecretTemplate_To_v1alpha3_CertificateSecretTemplate(in *certmanager.CertificateSecretTemplate, out *CertificateSecretTemplate, s conversion.Scope) error { - return autoConvert_certmanager_CertificateSecretTemplate_To_v1alpha3_CertificateSecretTemplate(in, out, s) -} - -func autoConvert_v1alpha3_CertificateSpec_To_certmanager_CertificateSpec(in *CertificateSpec, out *certmanager.CertificateSpec, s conversion.Scope) error { - if in.Subject != nil { - in, out := &in.Subject, &out.Subject - *out = new(certmanager.X509Subject) - if err := Convert_v1alpha3_X509Subject_To_certmanager_X509Subject(*in, *out, s); err != nil { - return err - } - } else { - out.Subject = nil - } - out.LiteralSubject = in.LiteralSubject - out.CommonName = in.CommonName - out.Duration = (*v1.Duration)(unsafe.Pointer(in.Duration)) - out.RenewBefore = (*v1.Duration)(unsafe.Pointer(in.RenewBefore)) - out.DNSNames = *(*[]string)(unsafe.Pointer(&in.DNSNames)) - out.IPAddresses = *(*[]string)(unsafe.Pointer(&in.IPAddresses)) - out.URISANs = *(*[]string)(unsafe.Pointer(&in.URISANs)) - out.EmailSANs = *(*[]string)(unsafe.Pointer(&in.EmailSANs)) - out.SecretName = in.SecretName - out.SecretTemplate = (*certmanager.CertificateSecretTemplate)(unsafe.Pointer(in.SecretTemplate)) - if in.Keystores != nil { - in, out := &in.Keystores, &out.Keystores - *out = new(certmanager.CertificateKeystores) - if err := Convert_v1alpha3_CertificateKeystores_To_certmanager_CertificateKeystores(*in, *out, s); err != nil { - return err - } - } else { - out.Keystores = nil - } - if err := apismetav1.Convert_v1_ObjectReference_To_meta_ObjectReference(&in.IssuerRef, &out.IssuerRef, s); err != nil { - return err - } - out.IsCA = in.IsCA - out.Usages = *(*[]certmanager.KeyUsage)(unsafe.Pointer(&in.Usages)) - // WARNING: in.KeySize requires manual conversion: does not exist in peer-type - // WARNING: in.KeyAlgorithm requires manual conversion: does not exist in peer-type - // WARNING: in.KeyEncoding requires manual conversion: does not exist in peer-type - if in.PrivateKey != nil { - in, out := &in.PrivateKey, &out.PrivateKey - *out = new(certmanager.CertificatePrivateKey) - if err := Convert_v1alpha3_CertificatePrivateKey_To_certmanager_CertificatePrivateKey(*in, *out, s); err != nil { - return err - } - } else { - out.PrivateKey = nil - } - out.EncodeUsagesInRequest = (*bool)(unsafe.Pointer(in.EncodeUsagesInRequest)) - out.RevisionHistoryLimit = (*int32)(unsafe.Pointer(in.RevisionHistoryLimit)) - out.AdditionalOutputFormats = *(*[]certmanager.CertificateAdditionalOutputFormat)(unsafe.Pointer(&in.AdditionalOutputFormats)) - return nil -} - -func autoConvert_certmanager_CertificateSpec_To_v1alpha3_CertificateSpec(in *certmanager.CertificateSpec, out *CertificateSpec, s conversion.Scope) error { - if in.Subject != nil { - in, out := &in.Subject, &out.Subject - *out = new(X509Subject) - if err := Convert_certmanager_X509Subject_To_v1alpha3_X509Subject(*in, *out, s); err != nil { - return err - } - } else { - out.Subject = nil - } - out.LiteralSubject = in.LiteralSubject - out.CommonName = in.CommonName - out.Duration = (*v1.Duration)(unsafe.Pointer(in.Duration)) - out.RenewBefore = (*v1.Duration)(unsafe.Pointer(in.RenewBefore)) - out.DNSNames = *(*[]string)(unsafe.Pointer(&in.DNSNames)) - out.IPAddresses = *(*[]string)(unsafe.Pointer(&in.IPAddresses)) - out.URISANs = *(*[]string)(unsafe.Pointer(&in.URISANs)) - out.EmailSANs = *(*[]string)(unsafe.Pointer(&in.EmailSANs)) - out.SecretName = in.SecretName - out.SecretTemplate = (*CertificateSecretTemplate)(unsafe.Pointer(in.SecretTemplate)) - if in.Keystores != nil { - in, out := &in.Keystores, &out.Keystores - *out = new(CertificateKeystores) - if err := Convert_certmanager_CertificateKeystores_To_v1alpha3_CertificateKeystores(*in, *out, s); err != nil { - return err - } - } else { - out.Keystores = nil - } - if err := apismetav1.Convert_meta_ObjectReference_To_v1_ObjectReference(&in.IssuerRef, &out.IssuerRef, s); err != nil { - return err - } - out.IsCA = in.IsCA - out.Usages = *(*[]KeyUsage)(unsafe.Pointer(&in.Usages)) - if in.PrivateKey != nil { - in, out := &in.PrivateKey, &out.PrivateKey - *out = new(CertificatePrivateKey) - if err := Convert_certmanager_CertificatePrivateKey_To_v1alpha3_CertificatePrivateKey(*in, *out, s); err != nil { - return err - } - } else { - out.PrivateKey = nil - } - out.EncodeUsagesInRequest = (*bool)(unsafe.Pointer(in.EncodeUsagesInRequest)) - out.RevisionHistoryLimit = (*int32)(unsafe.Pointer(in.RevisionHistoryLimit)) - out.AdditionalOutputFormats = *(*[]CertificateAdditionalOutputFormat)(unsafe.Pointer(&in.AdditionalOutputFormats)) - return nil -} - -func autoConvert_v1alpha3_CertificateStatus_To_certmanager_CertificateStatus(in *CertificateStatus, out *certmanager.CertificateStatus, s conversion.Scope) error { - out.Conditions = *(*[]certmanager.CertificateCondition)(unsafe.Pointer(&in.Conditions)) - out.LastFailureTime = (*v1.Time)(unsafe.Pointer(in.LastFailureTime)) - out.NotBefore = (*v1.Time)(unsafe.Pointer(in.NotBefore)) - out.NotAfter = (*v1.Time)(unsafe.Pointer(in.NotAfter)) - out.RenewalTime = (*v1.Time)(unsafe.Pointer(in.RenewalTime)) - out.Revision = (*int)(unsafe.Pointer(in.Revision)) - out.NextPrivateKeySecretName = (*string)(unsafe.Pointer(in.NextPrivateKeySecretName)) - out.FailedIssuanceAttempts = (*int)(unsafe.Pointer(in.FailedIssuanceAttempts)) - return nil -} - -// Convert_v1alpha3_CertificateStatus_To_certmanager_CertificateStatus is an autogenerated conversion function. -func Convert_v1alpha3_CertificateStatus_To_certmanager_CertificateStatus(in *CertificateStatus, out *certmanager.CertificateStatus, s conversion.Scope) error { - return autoConvert_v1alpha3_CertificateStatus_To_certmanager_CertificateStatus(in, out, s) -} - -func autoConvert_certmanager_CertificateStatus_To_v1alpha3_CertificateStatus(in *certmanager.CertificateStatus, out *CertificateStatus, s conversion.Scope) error { - out.Conditions = *(*[]CertificateCondition)(unsafe.Pointer(&in.Conditions)) - out.LastFailureTime = (*v1.Time)(unsafe.Pointer(in.LastFailureTime)) - out.NotBefore = (*v1.Time)(unsafe.Pointer(in.NotBefore)) - out.NotAfter = (*v1.Time)(unsafe.Pointer(in.NotAfter)) - out.RenewalTime = (*v1.Time)(unsafe.Pointer(in.RenewalTime)) - out.Revision = (*int)(unsafe.Pointer(in.Revision)) - out.NextPrivateKeySecretName = (*string)(unsafe.Pointer(in.NextPrivateKeySecretName)) - out.FailedIssuanceAttempts = (*int)(unsafe.Pointer(in.FailedIssuanceAttempts)) - return nil -} - -// Convert_certmanager_CertificateStatus_To_v1alpha3_CertificateStatus is an autogenerated conversion function. -func Convert_certmanager_CertificateStatus_To_v1alpha3_CertificateStatus(in *certmanager.CertificateStatus, out *CertificateStatus, s conversion.Scope) error { - return autoConvert_certmanager_CertificateStatus_To_v1alpha3_CertificateStatus(in, out, s) -} - -func autoConvert_v1alpha3_ClusterIssuer_To_certmanager_ClusterIssuer(in *ClusterIssuer, out *certmanager.ClusterIssuer, s conversion.Scope) error { - out.ObjectMeta = in.ObjectMeta - if err := Convert_v1alpha3_IssuerSpec_To_certmanager_IssuerSpec(&in.Spec, &out.Spec, s); err != nil { - return err - } - if err := Convert_v1alpha3_IssuerStatus_To_certmanager_IssuerStatus(&in.Status, &out.Status, s); err != nil { - return err - } - return nil -} - -// Convert_v1alpha3_ClusterIssuer_To_certmanager_ClusterIssuer is an autogenerated conversion function. -func Convert_v1alpha3_ClusterIssuer_To_certmanager_ClusterIssuer(in *ClusterIssuer, out *certmanager.ClusterIssuer, s conversion.Scope) error { - return autoConvert_v1alpha3_ClusterIssuer_To_certmanager_ClusterIssuer(in, out, s) -} - -func autoConvert_certmanager_ClusterIssuer_To_v1alpha3_ClusterIssuer(in *certmanager.ClusterIssuer, out *ClusterIssuer, s conversion.Scope) error { - out.ObjectMeta = in.ObjectMeta - if err := Convert_certmanager_IssuerSpec_To_v1alpha3_IssuerSpec(&in.Spec, &out.Spec, s); err != nil { - return err - } - if err := Convert_certmanager_IssuerStatus_To_v1alpha3_IssuerStatus(&in.Status, &out.Status, s); err != nil { - return err - } - return nil -} - -// Convert_certmanager_ClusterIssuer_To_v1alpha3_ClusterIssuer is an autogenerated conversion function. -func Convert_certmanager_ClusterIssuer_To_v1alpha3_ClusterIssuer(in *certmanager.ClusterIssuer, out *ClusterIssuer, s conversion.Scope) error { - return autoConvert_certmanager_ClusterIssuer_To_v1alpha3_ClusterIssuer(in, out, s) -} - -func autoConvert_v1alpha3_ClusterIssuerList_To_certmanager_ClusterIssuerList(in *ClusterIssuerList, out *certmanager.ClusterIssuerList, s conversion.Scope) error { - out.ListMeta = in.ListMeta - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]certmanager.ClusterIssuer, len(*in)) - for i := range *in { - if err := Convert_v1alpha3_ClusterIssuer_To_certmanager_ClusterIssuer(&(*in)[i], &(*out)[i], s); err != nil { - return err - } - } - } else { - out.Items = nil - } - return nil -} - -// Convert_v1alpha3_ClusterIssuerList_To_certmanager_ClusterIssuerList is an autogenerated conversion function. -func Convert_v1alpha3_ClusterIssuerList_To_certmanager_ClusterIssuerList(in *ClusterIssuerList, out *certmanager.ClusterIssuerList, s conversion.Scope) error { - return autoConvert_v1alpha3_ClusterIssuerList_To_certmanager_ClusterIssuerList(in, out, s) -} - -func autoConvert_certmanager_ClusterIssuerList_To_v1alpha3_ClusterIssuerList(in *certmanager.ClusterIssuerList, out *ClusterIssuerList, s conversion.Scope) error { - out.ListMeta = in.ListMeta - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]ClusterIssuer, len(*in)) - for i := range *in { - if err := Convert_certmanager_ClusterIssuer_To_v1alpha3_ClusterIssuer(&(*in)[i], &(*out)[i], s); err != nil { - return err - } - } - } else { - out.Items = nil - } - return nil -} - -// Convert_certmanager_ClusterIssuerList_To_v1alpha3_ClusterIssuerList is an autogenerated conversion function. -func Convert_certmanager_ClusterIssuerList_To_v1alpha3_ClusterIssuerList(in *certmanager.ClusterIssuerList, out *ClusterIssuerList, s conversion.Scope) error { - return autoConvert_certmanager_ClusterIssuerList_To_v1alpha3_ClusterIssuerList(in, out, s) -} - -func autoConvert_v1alpha3_Issuer_To_certmanager_Issuer(in *Issuer, out *certmanager.Issuer, s conversion.Scope) error { - out.ObjectMeta = in.ObjectMeta - if err := Convert_v1alpha3_IssuerSpec_To_certmanager_IssuerSpec(&in.Spec, &out.Spec, s); err != nil { - return err - } - if err := Convert_v1alpha3_IssuerStatus_To_certmanager_IssuerStatus(&in.Status, &out.Status, s); err != nil { - return err - } - return nil -} - -// Convert_v1alpha3_Issuer_To_certmanager_Issuer is an autogenerated conversion function. -func Convert_v1alpha3_Issuer_To_certmanager_Issuer(in *Issuer, out *certmanager.Issuer, s conversion.Scope) error { - return autoConvert_v1alpha3_Issuer_To_certmanager_Issuer(in, out, s) -} - -func autoConvert_certmanager_Issuer_To_v1alpha3_Issuer(in *certmanager.Issuer, out *Issuer, s conversion.Scope) error { - out.ObjectMeta = in.ObjectMeta - if err := Convert_certmanager_IssuerSpec_To_v1alpha3_IssuerSpec(&in.Spec, &out.Spec, s); err != nil { - return err - } - if err := Convert_certmanager_IssuerStatus_To_v1alpha3_IssuerStatus(&in.Status, &out.Status, s); err != nil { - return err - } - return nil -} - -// Convert_certmanager_Issuer_To_v1alpha3_Issuer is an autogenerated conversion function. -func Convert_certmanager_Issuer_To_v1alpha3_Issuer(in *certmanager.Issuer, out *Issuer, s conversion.Scope) error { - return autoConvert_certmanager_Issuer_To_v1alpha3_Issuer(in, out, s) -} - -func autoConvert_v1alpha3_IssuerCondition_To_certmanager_IssuerCondition(in *IssuerCondition, out *certmanager.IssuerCondition, s conversion.Scope) error { - out.Type = certmanager.IssuerConditionType(in.Type) - out.Status = meta.ConditionStatus(in.Status) - out.LastTransitionTime = (*v1.Time)(unsafe.Pointer(in.LastTransitionTime)) - out.Reason = in.Reason - out.Message = in.Message - out.ObservedGeneration = in.ObservedGeneration - return nil -} - -// Convert_v1alpha3_IssuerCondition_To_certmanager_IssuerCondition is an autogenerated conversion function. -func Convert_v1alpha3_IssuerCondition_To_certmanager_IssuerCondition(in *IssuerCondition, out *certmanager.IssuerCondition, s conversion.Scope) error { - return autoConvert_v1alpha3_IssuerCondition_To_certmanager_IssuerCondition(in, out, s) -} - -func autoConvert_certmanager_IssuerCondition_To_v1alpha3_IssuerCondition(in *certmanager.IssuerCondition, out *IssuerCondition, s conversion.Scope) error { - out.Type = IssuerConditionType(in.Type) - out.Status = metav1.ConditionStatus(in.Status) - out.LastTransitionTime = (*v1.Time)(unsafe.Pointer(in.LastTransitionTime)) - out.Reason = in.Reason - out.Message = in.Message - out.ObservedGeneration = in.ObservedGeneration - return nil -} - -// Convert_certmanager_IssuerCondition_To_v1alpha3_IssuerCondition is an autogenerated conversion function. -func Convert_certmanager_IssuerCondition_To_v1alpha3_IssuerCondition(in *certmanager.IssuerCondition, out *IssuerCondition, s conversion.Scope) error { - return autoConvert_certmanager_IssuerCondition_To_v1alpha3_IssuerCondition(in, out, s) -} - -func autoConvert_v1alpha3_IssuerConfig_To_certmanager_IssuerConfig(in *IssuerConfig, out *certmanager.IssuerConfig, s conversion.Scope) error { - if in.ACME != nil { - in, out := &in.ACME, &out.ACME - *out = new(acme.ACMEIssuer) - if err := acmev1alpha3.Convert_v1alpha3_ACMEIssuer_To_acme_ACMEIssuer(*in, *out, s); err != nil { - return err - } - } else { - out.ACME = nil - } - out.CA = (*certmanager.CAIssuer)(unsafe.Pointer(in.CA)) - if in.Vault != nil { - in, out := &in.Vault, &out.Vault - *out = new(certmanager.VaultIssuer) - if err := Convert_v1alpha3_VaultIssuer_To_certmanager_VaultIssuer(*in, *out, s); err != nil { - return err - } - } else { - out.Vault = nil - } - out.SelfSigned = (*certmanager.SelfSignedIssuer)(unsafe.Pointer(in.SelfSigned)) - if in.Venafi != nil { - in, out := &in.Venafi, &out.Venafi - *out = new(certmanager.VenafiIssuer) - if err := Convert_v1alpha3_VenafiIssuer_To_certmanager_VenafiIssuer(*in, *out, s); err != nil { - return err - } - } else { - out.Venafi = nil - } - return nil -} - -// Convert_v1alpha3_IssuerConfig_To_certmanager_IssuerConfig is an autogenerated conversion function. -func Convert_v1alpha3_IssuerConfig_To_certmanager_IssuerConfig(in *IssuerConfig, out *certmanager.IssuerConfig, s conversion.Scope) error { - return autoConvert_v1alpha3_IssuerConfig_To_certmanager_IssuerConfig(in, out, s) -} - -func autoConvert_certmanager_IssuerConfig_To_v1alpha3_IssuerConfig(in *certmanager.IssuerConfig, out *IssuerConfig, s conversion.Scope) error { - if in.ACME != nil { - in, out := &in.ACME, &out.ACME - *out = new(acmev1alpha3.ACMEIssuer) - if err := acmev1alpha3.Convert_acme_ACMEIssuer_To_v1alpha3_ACMEIssuer(*in, *out, s); err != nil { - return err - } - } else { - out.ACME = nil - } - out.CA = (*CAIssuer)(unsafe.Pointer(in.CA)) - if in.Vault != nil { - in, out := &in.Vault, &out.Vault - *out = new(VaultIssuer) - if err := Convert_certmanager_VaultIssuer_To_v1alpha3_VaultIssuer(*in, *out, s); err != nil { - return err - } - } else { - out.Vault = nil - } - out.SelfSigned = (*SelfSignedIssuer)(unsafe.Pointer(in.SelfSigned)) - if in.Venafi != nil { - in, out := &in.Venafi, &out.Venafi - *out = new(VenafiIssuer) - if err := Convert_certmanager_VenafiIssuer_To_v1alpha3_VenafiIssuer(*in, *out, s); err != nil { - return err - } - } else { - out.Venafi = nil - } - return nil -} - -// Convert_certmanager_IssuerConfig_To_v1alpha3_IssuerConfig is an autogenerated conversion function. -func Convert_certmanager_IssuerConfig_To_v1alpha3_IssuerConfig(in *certmanager.IssuerConfig, out *IssuerConfig, s conversion.Scope) error { - return autoConvert_certmanager_IssuerConfig_To_v1alpha3_IssuerConfig(in, out, s) -} - -func autoConvert_v1alpha3_IssuerList_To_certmanager_IssuerList(in *IssuerList, out *certmanager.IssuerList, s conversion.Scope) error { - out.ListMeta = in.ListMeta - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]certmanager.Issuer, len(*in)) - for i := range *in { - if err := Convert_v1alpha3_Issuer_To_certmanager_Issuer(&(*in)[i], &(*out)[i], s); err != nil { - return err - } - } - } else { - out.Items = nil - } - return nil -} - -// Convert_v1alpha3_IssuerList_To_certmanager_IssuerList is an autogenerated conversion function. -func Convert_v1alpha3_IssuerList_To_certmanager_IssuerList(in *IssuerList, out *certmanager.IssuerList, s conversion.Scope) error { - return autoConvert_v1alpha3_IssuerList_To_certmanager_IssuerList(in, out, s) -} - -func autoConvert_certmanager_IssuerList_To_v1alpha3_IssuerList(in *certmanager.IssuerList, out *IssuerList, s conversion.Scope) error { - out.ListMeta = in.ListMeta - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]Issuer, len(*in)) - for i := range *in { - if err := Convert_certmanager_Issuer_To_v1alpha3_Issuer(&(*in)[i], &(*out)[i], s); err != nil { - return err - } - } - } else { - out.Items = nil - } - return nil -} - -// Convert_certmanager_IssuerList_To_v1alpha3_IssuerList is an autogenerated conversion function. -func Convert_certmanager_IssuerList_To_v1alpha3_IssuerList(in *certmanager.IssuerList, out *IssuerList, s conversion.Scope) error { - return autoConvert_certmanager_IssuerList_To_v1alpha3_IssuerList(in, out, s) -} - -func autoConvert_v1alpha3_IssuerSpec_To_certmanager_IssuerSpec(in *IssuerSpec, out *certmanager.IssuerSpec, s conversion.Scope) error { - if err := Convert_v1alpha3_IssuerConfig_To_certmanager_IssuerConfig(&in.IssuerConfig, &out.IssuerConfig, s); err != nil { - return err - } - return nil -} - -// Convert_v1alpha3_IssuerSpec_To_certmanager_IssuerSpec is an autogenerated conversion function. -func Convert_v1alpha3_IssuerSpec_To_certmanager_IssuerSpec(in *IssuerSpec, out *certmanager.IssuerSpec, s conversion.Scope) error { - return autoConvert_v1alpha3_IssuerSpec_To_certmanager_IssuerSpec(in, out, s) -} - -func autoConvert_certmanager_IssuerSpec_To_v1alpha3_IssuerSpec(in *certmanager.IssuerSpec, out *IssuerSpec, s conversion.Scope) error { - if err := Convert_certmanager_IssuerConfig_To_v1alpha3_IssuerConfig(&in.IssuerConfig, &out.IssuerConfig, s); err != nil { - return err - } - return nil -} - -// Convert_certmanager_IssuerSpec_To_v1alpha3_IssuerSpec is an autogenerated conversion function. -func Convert_certmanager_IssuerSpec_To_v1alpha3_IssuerSpec(in *certmanager.IssuerSpec, out *IssuerSpec, s conversion.Scope) error { - return autoConvert_certmanager_IssuerSpec_To_v1alpha3_IssuerSpec(in, out, s) -} - -func autoConvert_v1alpha3_IssuerStatus_To_certmanager_IssuerStatus(in *IssuerStatus, out *certmanager.IssuerStatus, s conversion.Scope) error { - out.Conditions = *(*[]certmanager.IssuerCondition)(unsafe.Pointer(&in.Conditions)) - out.ACME = (*acme.ACMEIssuerStatus)(unsafe.Pointer(in.ACME)) - return nil -} - -// Convert_v1alpha3_IssuerStatus_To_certmanager_IssuerStatus is an autogenerated conversion function. -func Convert_v1alpha3_IssuerStatus_To_certmanager_IssuerStatus(in *IssuerStatus, out *certmanager.IssuerStatus, s conversion.Scope) error { - return autoConvert_v1alpha3_IssuerStatus_To_certmanager_IssuerStatus(in, out, s) -} - -func autoConvert_certmanager_IssuerStatus_To_v1alpha3_IssuerStatus(in *certmanager.IssuerStatus, out *IssuerStatus, s conversion.Scope) error { - out.Conditions = *(*[]IssuerCondition)(unsafe.Pointer(&in.Conditions)) - out.ACME = (*acmev1alpha3.ACMEIssuerStatus)(unsafe.Pointer(in.ACME)) - return nil -} - -// Convert_certmanager_IssuerStatus_To_v1alpha3_IssuerStatus is an autogenerated conversion function. -func Convert_certmanager_IssuerStatus_To_v1alpha3_IssuerStatus(in *certmanager.IssuerStatus, out *IssuerStatus, s conversion.Scope) error { - return autoConvert_certmanager_IssuerStatus_To_v1alpha3_IssuerStatus(in, out, s) -} - -func autoConvert_v1alpha3_JKSKeystore_To_certmanager_JKSKeystore(in *JKSKeystore, out *certmanager.JKSKeystore, s conversion.Scope) error { - out.Create = in.Create - if err := apismetav1.Convert_v1_SecretKeySelector_To_meta_SecretKeySelector(&in.PasswordSecretRef, &out.PasswordSecretRef, s); err != nil { - return err - } - return nil -} - -// Convert_v1alpha3_JKSKeystore_To_certmanager_JKSKeystore is an autogenerated conversion function. -func Convert_v1alpha3_JKSKeystore_To_certmanager_JKSKeystore(in *JKSKeystore, out *certmanager.JKSKeystore, s conversion.Scope) error { - return autoConvert_v1alpha3_JKSKeystore_To_certmanager_JKSKeystore(in, out, s) -} - -func autoConvert_certmanager_JKSKeystore_To_v1alpha3_JKSKeystore(in *certmanager.JKSKeystore, out *JKSKeystore, s conversion.Scope) error { - out.Create = in.Create - if err := apismetav1.Convert_meta_SecretKeySelector_To_v1_SecretKeySelector(&in.PasswordSecretRef, &out.PasswordSecretRef, s); err != nil { - return err - } - return nil -} - -// Convert_certmanager_JKSKeystore_To_v1alpha3_JKSKeystore is an autogenerated conversion function. -func Convert_certmanager_JKSKeystore_To_v1alpha3_JKSKeystore(in *certmanager.JKSKeystore, out *JKSKeystore, s conversion.Scope) error { - return autoConvert_certmanager_JKSKeystore_To_v1alpha3_JKSKeystore(in, out, s) -} - -func autoConvert_v1alpha3_PKCS12Keystore_To_certmanager_PKCS12Keystore(in *PKCS12Keystore, out *certmanager.PKCS12Keystore, s conversion.Scope) error { - out.Create = in.Create - if err := apismetav1.Convert_v1_SecretKeySelector_To_meta_SecretKeySelector(&in.PasswordSecretRef, &out.PasswordSecretRef, s); err != nil { - return err - } - return nil -} - -// Convert_v1alpha3_PKCS12Keystore_To_certmanager_PKCS12Keystore is an autogenerated conversion function. -func Convert_v1alpha3_PKCS12Keystore_To_certmanager_PKCS12Keystore(in *PKCS12Keystore, out *certmanager.PKCS12Keystore, s conversion.Scope) error { - return autoConvert_v1alpha3_PKCS12Keystore_To_certmanager_PKCS12Keystore(in, out, s) -} - -func autoConvert_certmanager_PKCS12Keystore_To_v1alpha3_PKCS12Keystore(in *certmanager.PKCS12Keystore, out *PKCS12Keystore, s conversion.Scope) error { - out.Create = in.Create - if err := apismetav1.Convert_meta_SecretKeySelector_To_v1_SecretKeySelector(&in.PasswordSecretRef, &out.PasswordSecretRef, s); err != nil { - return err - } - return nil -} - -// Convert_certmanager_PKCS12Keystore_To_v1alpha3_PKCS12Keystore is an autogenerated conversion function. -func Convert_certmanager_PKCS12Keystore_To_v1alpha3_PKCS12Keystore(in *certmanager.PKCS12Keystore, out *PKCS12Keystore, s conversion.Scope) error { - return autoConvert_certmanager_PKCS12Keystore_To_v1alpha3_PKCS12Keystore(in, out, s) -} - -func autoConvert_v1alpha3_SelfSignedIssuer_To_certmanager_SelfSignedIssuer(in *SelfSignedIssuer, out *certmanager.SelfSignedIssuer, s conversion.Scope) error { - out.CRLDistributionPoints = *(*[]string)(unsafe.Pointer(&in.CRLDistributionPoints)) - return nil -} - -// Convert_v1alpha3_SelfSignedIssuer_To_certmanager_SelfSignedIssuer is an autogenerated conversion function. -func Convert_v1alpha3_SelfSignedIssuer_To_certmanager_SelfSignedIssuer(in *SelfSignedIssuer, out *certmanager.SelfSignedIssuer, s conversion.Scope) error { - return autoConvert_v1alpha3_SelfSignedIssuer_To_certmanager_SelfSignedIssuer(in, out, s) -} - -func autoConvert_certmanager_SelfSignedIssuer_To_v1alpha3_SelfSignedIssuer(in *certmanager.SelfSignedIssuer, out *SelfSignedIssuer, s conversion.Scope) error { - out.CRLDistributionPoints = *(*[]string)(unsafe.Pointer(&in.CRLDistributionPoints)) - return nil -} - -// Convert_certmanager_SelfSignedIssuer_To_v1alpha3_SelfSignedIssuer is an autogenerated conversion function. -func Convert_certmanager_SelfSignedIssuer_To_v1alpha3_SelfSignedIssuer(in *certmanager.SelfSignedIssuer, out *SelfSignedIssuer, s conversion.Scope) error { - return autoConvert_certmanager_SelfSignedIssuer_To_v1alpha3_SelfSignedIssuer(in, out, s) -} - -func autoConvert_v1alpha3_VaultAppRole_To_certmanager_VaultAppRole(in *VaultAppRole, out *certmanager.VaultAppRole, s conversion.Scope) error { - out.Path = in.Path - out.RoleId = in.RoleId - if err := apismetav1.Convert_v1_SecretKeySelector_To_meta_SecretKeySelector(&in.SecretRef, &out.SecretRef, s); err != nil { - return err - } - return nil -} - -// Convert_v1alpha3_VaultAppRole_To_certmanager_VaultAppRole is an autogenerated conversion function. -func Convert_v1alpha3_VaultAppRole_To_certmanager_VaultAppRole(in *VaultAppRole, out *certmanager.VaultAppRole, s conversion.Scope) error { - return autoConvert_v1alpha3_VaultAppRole_To_certmanager_VaultAppRole(in, out, s) -} - -func autoConvert_certmanager_VaultAppRole_To_v1alpha3_VaultAppRole(in *certmanager.VaultAppRole, out *VaultAppRole, s conversion.Scope) error { - out.Path = in.Path - out.RoleId = in.RoleId - if err := apismetav1.Convert_meta_SecretKeySelector_To_v1_SecretKeySelector(&in.SecretRef, &out.SecretRef, s); err != nil { - return err - } - return nil -} - -// Convert_certmanager_VaultAppRole_To_v1alpha3_VaultAppRole is an autogenerated conversion function. -func Convert_certmanager_VaultAppRole_To_v1alpha3_VaultAppRole(in *certmanager.VaultAppRole, out *VaultAppRole, s conversion.Scope) error { - return autoConvert_certmanager_VaultAppRole_To_v1alpha3_VaultAppRole(in, out, s) -} - -func autoConvert_v1alpha3_VaultAuth_To_certmanager_VaultAuth(in *VaultAuth, out *certmanager.VaultAuth, s conversion.Scope) error { - if in.TokenSecretRef != nil { - in, out := &in.TokenSecretRef, &out.TokenSecretRef - *out = new(meta.SecretKeySelector) - if err := apismetav1.Convert_v1_SecretKeySelector_To_meta_SecretKeySelector(*in, *out, s); err != nil { - return err - } - } else { - out.TokenSecretRef = nil - } - if in.AppRole != nil { - in, out := &in.AppRole, &out.AppRole - *out = new(certmanager.VaultAppRole) - if err := Convert_v1alpha3_VaultAppRole_To_certmanager_VaultAppRole(*in, *out, s); err != nil { - return err - } - } else { - out.AppRole = nil - } - if in.Kubernetes != nil { - in, out := &in.Kubernetes, &out.Kubernetes - *out = new(certmanager.VaultKubernetesAuth) - if err := Convert_v1alpha3_VaultKubernetesAuth_To_certmanager_VaultKubernetesAuth(*in, *out, s); err != nil { - return err - } - } else { - out.Kubernetes = nil - } - return nil -} - -// Convert_v1alpha3_VaultAuth_To_certmanager_VaultAuth is an autogenerated conversion function. -func Convert_v1alpha3_VaultAuth_To_certmanager_VaultAuth(in *VaultAuth, out *certmanager.VaultAuth, s conversion.Scope) error { - return autoConvert_v1alpha3_VaultAuth_To_certmanager_VaultAuth(in, out, s) -} - -func autoConvert_certmanager_VaultAuth_To_v1alpha3_VaultAuth(in *certmanager.VaultAuth, out *VaultAuth, s conversion.Scope) error { - if in.TokenSecretRef != nil { - in, out := &in.TokenSecretRef, &out.TokenSecretRef - *out = new(metav1.SecretKeySelector) - if err := apismetav1.Convert_meta_SecretKeySelector_To_v1_SecretKeySelector(*in, *out, s); err != nil { - return err - } - } else { - out.TokenSecretRef = nil - } - if in.AppRole != nil { - in, out := &in.AppRole, &out.AppRole - *out = new(VaultAppRole) - if err := Convert_certmanager_VaultAppRole_To_v1alpha3_VaultAppRole(*in, *out, s); err != nil { - return err - } - } else { - out.AppRole = nil - } - if in.Kubernetes != nil { - in, out := &in.Kubernetes, &out.Kubernetes - *out = new(VaultKubernetesAuth) - if err := Convert_certmanager_VaultKubernetesAuth_To_v1alpha3_VaultKubernetesAuth(*in, *out, s); err != nil { - return err - } - } else { - out.Kubernetes = nil - } - return nil -} - -// Convert_certmanager_VaultAuth_To_v1alpha3_VaultAuth is an autogenerated conversion function. -func Convert_certmanager_VaultAuth_To_v1alpha3_VaultAuth(in *certmanager.VaultAuth, out *VaultAuth, s conversion.Scope) error { - return autoConvert_certmanager_VaultAuth_To_v1alpha3_VaultAuth(in, out, s) -} - -func autoConvert_v1alpha3_VaultIssuer_To_certmanager_VaultIssuer(in *VaultIssuer, out *certmanager.VaultIssuer, s conversion.Scope) error { - if err := Convert_v1alpha3_VaultAuth_To_certmanager_VaultAuth(&in.Auth, &out.Auth, s); err != nil { - return err - } - out.Server = in.Server - out.Path = in.Path - out.Namespace = in.Namespace - out.CABundle = *(*[]byte)(unsafe.Pointer(&in.CABundle)) - if in.CABundleSecretRef != nil { - in, out := &in.CABundleSecretRef, &out.CABundleSecretRef - *out = new(meta.SecretKeySelector) - if err := apismetav1.Convert_v1_SecretKeySelector_To_meta_SecretKeySelector(*in, *out, s); err != nil { - return err - } - } else { - out.CABundleSecretRef = nil - } - return nil -} - -// Convert_v1alpha3_VaultIssuer_To_certmanager_VaultIssuer is an autogenerated conversion function. -func Convert_v1alpha3_VaultIssuer_To_certmanager_VaultIssuer(in *VaultIssuer, out *certmanager.VaultIssuer, s conversion.Scope) error { - return autoConvert_v1alpha3_VaultIssuer_To_certmanager_VaultIssuer(in, out, s) -} - -func autoConvert_certmanager_VaultIssuer_To_v1alpha3_VaultIssuer(in *certmanager.VaultIssuer, out *VaultIssuer, s conversion.Scope) error { - if err := Convert_certmanager_VaultAuth_To_v1alpha3_VaultAuth(&in.Auth, &out.Auth, s); err != nil { - return err - } - out.Server = in.Server - out.Path = in.Path - out.Namespace = in.Namespace - out.CABundle = *(*[]byte)(unsafe.Pointer(&in.CABundle)) - if in.CABundleSecretRef != nil { - in, out := &in.CABundleSecretRef, &out.CABundleSecretRef - *out = new(metav1.SecretKeySelector) - if err := apismetav1.Convert_meta_SecretKeySelector_To_v1_SecretKeySelector(*in, *out, s); err != nil { - return err - } - } else { - out.CABundleSecretRef = nil - } - return nil -} - -// Convert_certmanager_VaultIssuer_To_v1alpha3_VaultIssuer is an autogenerated conversion function. -func Convert_certmanager_VaultIssuer_To_v1alpha3_VaultIssuer(in *certmanager.VaultIssuer, out *VaultIssuer, s conversion.Scope) error { - return autoConvert_certmanager_VaultIssuer_To_v1alpha3_VaultIssuer(in, out, s) -} - -func autoConvert_v1alpha3_VaultKubernetesAuth_To_certmanager_VaultKubernetesAuth(in *VaultKubernetesAuth, out *certmanager.VaultKubernetesAuth, s conversion.Scope) error { - out.Path = in.Path - if err := apismetav1.Convert_v1_SecretKeySelector_To_meta_SecretKeySelector(&in.SecretRef, &out.SecretRef, s); err != nil { - return err - } - out.Role = in.Role - return nil -} - -// Convert_v1alpha3_VaultKubernetesAuth_To_certmanager_VaultKubernetesAuth is an autogenerated conversion function. -func Convert_v1alpha3_VaultKubernetesAuth_To_certmanager_VaultKubernetesAuth(in *VaultKubernetesAuth, out *certmanager.VaultKubernetesAuth, s conversion.Scope) error { - return autoConvert_v1alpha3_VaultKubernetesAuth_To_certmanager_VaultKubernetesAuth(in, out, s) -} - -func autoConvert_certmanager_VaultKubernetesAuth_To_v1alpha3_VaultKubernetesAuth(in *certmanager.VaultKubernetesAuth, out *VaultKubernetesAuth, s conversion.Scope) error { - out.Path = in.Path - if err := apismetav1.Convert_meta_SecretKeySelector_To_v1_SecretKeySelector(&in.SecretRef, &out.SecretRef, s); err != nil { - return err - } - out.Role = in.Role - return nil -} - -// Convert_certmanager_VaultKubernetesAuth_To_v1alpha3_VaultKubernetesAuth is an autogenerated conversion function. -func Convert_certmanager_VaultKubernetesAuth_To_v1alpha3_VaultKubernetesAuth(in *certmanager.VaultKubernetesAuth, out *VaultKubernetesAuth, s conversion.Scope) error { - return autoConvert_certmanager_VaultKubernetesAuth_To_v1alpha3_VaultKubernetesAuth(in, out, s) -} - -func autoConvert_v1alpha3_VenafiCloud_To_certmanager_VenafiCloud(in *VenafiCloud, out *certmanager.VenafiCloud, s conversion.Scope) error { - out.URL = in.URL - if err := apismetav1.Convert_v1_SecretKeySelector_To_meta_SecretKeySelector(&in.APITokenSecretRef, &out.APITokenSecretRef, s); err != nil { - return err - } - return nil -} - -// Convert_v1alpha3_VenafiCloud_To_certmanager_VenafiCloud is an autogenerated conversion function. -func Convert_v1alpha3_VenafiCloud_To_certmanager_VenafiCloud(in *VenafiCloud, out *certmanager.VenafiCloud, s conversion.Scope) error { - return autoConvert_v1alpha3_VenafiCloud_To_certmanager_VenafiCloud(in, out, s) -} - -func autoConvert_certmanager_VenafiCloud_To_v1alpha3_VenafiCloud(in *certmanager.VenafiCloud, out *VenafiCloud, s conversion.Scope) error { - out.URL = in.URL - if err := apismetav1.Convert_meta_SecretKeySelector_To_v1_SecretKeySelector(&in.APITokenSecretRef, &out.APITokenSecretRef, s); err != nil { - return err - } - return nil -} - -// Convert_certmanager_VenafiCloud_To_v1alpha3_VenafiCloud is an autogenerated conversion function. -func Convert_certmanager_VenafiCloud_To_v1alpha3_VenafiCloud(in *certmanager.VenafiCloud, out *VenafiCloud, s conversion.Scope) error { - return autoConvert_certmanager_VenafiCloud_To_v1alpha3_VenafiCloud(in, out, s) -} - -func autoConvert_v1alpha3_VenafiIssuer_To_certmanager_VenafiIssuer(in *VenafiIssuer, out *certmanager.VenafiIssuer, s conversion.Scope) error { - out.Zone = in.Zone - if in.TPP != nil { - in, out := &in.TPP, &out.TPP - *out = new(certmanager.VenafiTPP) - if err := Convert_v1alpha3_VenafiTPP_To_certmanager_VenafiTPP(*in, *out, s); err != nil { - return err - } - } else { - out.TPP = nil - } - if in.Cloud != nil { - in, out := &in.Cloud, &out.Cloud - *out = new(certmanager.VenafiCloud) - if err := Convert_v1alpha3_VenafiCloud_To_certmanager_VenafiCloud(*in, *out, s); err != nil { - return err - } - } else { - out.Cloud = nil - } - return nil -} - -// Convert_v1alpha3_VenafiIssuer_To_certmanager_VenafiIssuer is an autogenerated conversion function. -func Convert_v1alpha3_VenafiIssuer_To_certmanager_VenafiIssuer(in *VenafiIssuer, out *certmanager.VenafiIssuer, s conversion.Scope) error { - return autoConvert_v1alpha3_VenafiIssuer_To_certmanager_VenafiIssuer(in, out, s) -} - -func autoConvert_certmanager_VenafiIssuer_To_v1alpha3_VenafiIssuer(in *certmanager.VenafiIssuer, out *VenafiIssuer, s conversion.Scope) error { - out.Zone = in.Zone - if in.TPP != nil { - in, out := &in.TPP, &out.TPP - *out = new(VenafiTPP) - if err := Convert_certmanager_VenafiTPP_To_v1alpha3_VenafiTPP(*in, *out, s); err != nil { - return err - } - } else { - out.TPP = nil - } - if in.Cloud != nil { - in, out := &in.Cloud, &out.Cloud - *out = new(VenafiCloud) - if err := Convert_certmanager_VenafiCloud_To_v1alpha3_VenafiCloud(*in, *out, s); err != nil { - return err - } - } else { - out.Cloud = nil - } - return nil -} - -// Convert_certmanager_VenafiIssuer_To_v1alpha3_VenafiIssuer is an autogenerated conversion function. -func Convert_certmanager_VenafiIssuer_To_v1alpha3_VenafiIssuer(in *certmanager.VenafiIssuer, out *VenafiIssuer, s conversion.Scope) error { - return autoConvert_certmanager_VenafiIssuer_To_v1alpha3_VenafiIssuer(in, out, s) -} - -func autoConvert_v1alpha3_VenafiTPP_To_certmanager_VenafiTPP(in *VenafiTPP, out *certmanager.VenafiTPP, s conversion.Scope) error { - out.URL = in.URL - if err := apismetav1.Convert_v1_LocalObjectReference_To_meta_LocalObjectReference(&in.CredentialsRef, &out.CredentialsRef, s); err != nil { - return err - } - out.CABundle = *(*[]byte)(unsafe.Pointer(&in.CABundle)) - return nil -} - -// Convert_v1alpha3_VenafiTPP_To_certmanager_VenafiTPP is an autogenerated conversion function. -func Convert_v1alpha3_VenafiTPP_To_certmanager_VenafiTPP(in *VenafiTPP, out *certmanager.VenafiTPP, s conversion.Scope) error { - return autoConvert_v1alpha3_VenafiTPP_To_certmanager_VenafiTPP(in, out, s) -} - -func autoConvert_certmanager_VenafiTPP_To_v1alpha3_VenafiTPP(in *certmanager.VenafiTPP, out *VenafiTPP, s conversion.Scope) error { - out.URL = in.URL - if err := apismetav1.Convert_meta_LocalObjectReference_To_v1_LocalObjectReference(&in.CredentialsRef, &out.CredentialsRef, s); err != nil { - return err - } - out.CABundle = *(*[]byte)(unsafe.Pointer(&in.CABundle)) - return nil -} - -// Convert_certmanager_VenafiTPP_To_v1alpha3_VenafiTPP is an autogenerated conversion function. -func Convert_certmanager_VenafiTPP_To_v1alpha3_VenafiTPP(in *certmanager.VenafiTPP, out *VenafiTPP, s conversion.Scope) error { - return autoConvert_certmanager_VenafiTPP_To_v1alpha3_VenafiTPP(in, out, s) -} - -func autoConvert_v1alpha3_X509Subject_To_certmanager_X509Subject(in *X509Subject, out *certmanager.X509Subject, s conversion.Scope) error { - out.Organizations = *(*[]string)(unsafe.Pointer(&in.Organizations)) - out.Countries = *(*[]string)(unsafe.Pointer(&in.Countries)) - out.OrganizationalUnits = *(*[]string)(unsafe.Pointer(&in.OrganizationalUnits)) - out.Localities = *(*[]string)(unsafe.Pointer(&in.Localities)) - out.Provinces = *(*[]string)(unsafe.Pointer(&in.Provinces)) - out.StreetAddresses = *(*[]string)(unsafe.Pointer(&in.StreetAddresses)) - out.PostalCodes = *(*[]string)(unsafe.Pointer(&in.PostalCodes)) - out.SerialNumber = in.SerialNumber - return nil -} - -// Convert_v1alpha3_X509Subject_To_certmanager_X509Subject is an autogenerated conversion function. -func Convert_v1alpha3_X509Subject_To_certmanager_X509Subject(in *X509Subject, out *certmanager.X509Subject, s conversion.Scope) error { - return autoConvert_v1alpha3_X509Subject_To_certmanager_X509Subject(in, out, s) -} - -func autoConvert_certmanager_X509Subject_To_v1alpha3_X509Subject(in *certmanager.X509Subject, out *X509Subject, s conversion.Scope) error { - out.Organizations = *(*[]string)(unsafe.Pointer(&in.Organizations)) - out.Countries = *(*[]string)(unsafe.Pointer(&in.Countries)) - out.OrganizationalUnits = *(*[]string)(unsafe.Pointer(&in.OrganizationalUnits)) - out.Localities = *(*[]string)(unsafe.Pointer(&in.Localities)) - out.Provinces = *(*[]string)(unsafe.Pointer(&in.Provinces)) - out.StreetAddresses = *(*[]string)(unsafe.Pointer(&in.StreetAddresses)) - out.PostalCodes = *(*[]string)(unsafe.Pointer(&in.PostalCodes)) - out.SerialNumber = in.SerialNumber - return nil -} diff --git a/internal/apis/certmanager/v1alpha3/zz_generated.deepcopy.go b/internal/apis/certmanager/v1alpha3/zz_generated.deepcopy.go deleted file mode 100644 index 36d7391ca2b..00000000000 --- a/internal/apis/certmanager/v1alpha3/zz_generated.deepcopy.go +++ /dev/null @@ -1,1026 +0,0 @@ -//go:build !ignore_autogenerated -// +build !ignore_autogenerated - -/* -Copyright 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. -*/ - -// Code generated by deepcopy-gen. DO NOT EDIT. - -package v1alpha3 - -import ( - acmev1alpha3 "github.com/cert-manager/cert-manager/internal/apis/acme/v1alpha3" - metav1 "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" - runtime "k8s.io/apimachinery/pkg/runtime" -) - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *CAIssuer) DeepCopyInto(out *CAIssuer) { - *out = *in - if in.CRLDistributionPoints != nil { - in, out := &in.CRLDistributionPoints, &out.CRLDistributionPoints - *out = make([]string, len(*in)) - copy(*out, *in) - } - if in.OCSPServers != nil { - in, out := &in.OCSPServers, &out.OCSPServers - *out = make([]string, len(*in)) - copy(*out, *in) - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CAIssuer. -func (in *CAIssuer) DeepCopy() *CAIssuer { - if in == nil { - return nil - } - out := new(CAIssuer) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *Certificate) DeepCopyInto(out *Certificate) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - in.Spec.DeepCopyInto(&out.Spec) - in.Status.DeepCopyInto(&out.Status) - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Certificate. -func (in *Certificate) DeepCopy() *Certificate { - if in == nil { - return nil - } - out := new(Certificate) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *Certificate) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *CertificateAdditionalOutputFormat) DeepCopyInto(out *CertificateAdditionalOutputFormat) { - *out = *in - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CertificateAdditionalOutputFormat. -func (in *CertificateAdditionalOutputFormat) DeepCopy() *CertificateAdditionalOutputFormat { - if in == nil { - return nil - } - out := new(CertificateAdditionalOutputFormat) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *CertificateCondition) DeepCopyInto(out *CertificateCondition) { - *out = *in - if in.LastTransitionTime != nil { - in, out := &in.LastTransitionTime, &out.LastTransitionTime - *out = (*in).DeepCopy() - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CertificateCondition. -func (in *CertificateCondition) DeepCopy() *CertificateCondition { - if in == nil { - return nil - } - out := new(CertificateCondition) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *CertificateKeystores) DeepCopyInto(out *CertificateKeystores) { - *out = *in - if in.JKS != nil { - in, out := &in.JKS, &out.JKS - *out = new(JKSKeystore) - **out = **in - } - if in.PKCS12 != nil { - in, out := &in.PKCS12, &out.PKCS12 - *out = new(PKCS12Keystore) - **out = **in - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CertificateKeystores. -func (in *CertificateKeystores) DeepCopy() *CertificateKeystores { - if in == nil { - return nil - } - out := new(CertificateKeystores) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *CertificateList) DeepCopyInto(out *CertificateList) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ListMeta.DeepCopyInto(&out.ListMeta) - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]Certificate, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CertificateList. -func (in *CertificateList) DeepCopy() *CertificateList { - if in == nil { - return nil - } - out := new(CertificateList) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *CertificateList) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *CertificatePrivateKey) DeepCopyInto(out *CertificatePrivateKey) { - *out = *in - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CertificatePrivateKey. -func (in *CertificatePrivateKey) DeepCopy() *CertificatePrivateKey { - if in == nil { - return nil - } - out := new(CertificatePrivateKey) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *CertificateRequest) DeepCopyInto(out *CertificateRequest) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - in.Spec.DeepCopyInto(&out.Spec) - in.Status.DeepCopyInto(&out.Status) - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CertificateRequest. -func (in *CertificateRequest) DeepCopy() *CertificateRequest { - if in == nil { - return nil - } - out := new(CertificateRequest) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *CertificateRequest) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *CertificateRequestCondition) DeepCopyInto(out *CertificateRequestCondition) { - *out = *in - if in.LastTransitionTime != nil { - in, out := &in.LastTransitionTime, &out.LastTransitionTime - *out = (*in).DeepCopy() - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CertificateRequestCondition. -func (in *CertificateRequestCondition) DeepCopy() *CertificateRequestCondition { - if in == nil { - return nil - } - out := new(CertificateRequestCondition) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *CertificateRequestList) DeepCopyInto(out *CertificateRequestList) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ListMeta.DeepCopyInto(&out.ListMeta) - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]CertificateRequest, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CertificateRequestList. -func (in *CertificateRequestList) DeepCopy() *CertificateRequestList { - if in == nil { - return nil - } - out := new(CertificateRequestList) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *CertificateRequestList) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *CertificateRequestSpec) DeepCopyInto(out *CertificateRequestSpec) { - *out = *in - if in.Duration != nil { - in, out := &in.Duration, &out.Duration - *out = new(v1.Duration) - **out = **in - } - out.IssuerRef = in.IssuerRef - if in.CSRPEM != nil { - in, out := &in.CSRPEM, &out.CSRPEM - *out = make([]byte, len(*in)) - copy(*out, *in) - } - if in.Usages != nil { - in, out := &in.Usages, &out.Usages - *out = make([]KeyUsage, len(*in)) - copy(*out, *in) - } - if in.Groups != nil { - in, out := &in.Groups, &out.Groups - *out = make([]string, len(*in)) - copy(*out, *in) - } - if in.Extra != nil { - in, out := &in.Extra, &out.Extra - *out = make(map[string][]string, len(*in)) - for key, val := range *in { - var outVal []string - if val == nil { - (*out)[key] = nil - } else { - in, out := &val, &outVal - *out = make([]string, len(*in)) - copy(*out, *in) - } - (*out)[key] = outVal - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CertificateRequestSpec. -func (in *CertificateRequestSpec) DeepCopy() *CertificateRequestSpec { - if in == nil { - return nil - } - out := new(CertificateRequestSpec) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *CertificateRequestStatus) DeepCopyInto(out *CertificateRequestStatus) { - *out = *in - if in.Conditions != nil { - in, out := &in.Conditions, &out.Conditions - *out = make([]CertificateRequestCondition, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - if in.Certificate != nil { - in, out := &in.Certificate, &out.Certificate - *out = make([]byte, len(*in)) - copy(*out, *in) - } - if in.CA != nil { - in, out := &in.CA, &out.CA - *out = make([]byte, len(*in)) - copy(*out, *in) - } - if in.FailureTime != nil { - in, out := &in.FailureTime, &out.FailureTime - *out = (*in).DeepCopy() - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CertificateRequestStatus. -func (in *CertificateRequestStatus) DeepCopy() *CertificateRequestStatus { - if in == nil { - return nil - } - out := new(CertificateRequestStatus) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *CertificateSecretTemplate) DeepCopyInto(out *CertificateSecretTemplate) { - *out = *in - if in.Annotations != nil { - in, out := &in.Annotations, &out.Annotations - *out = make(map[string]string, len(*in)) - for key, val := range *in { - (*out)[key] = val - } - } - if in.Labels != nil { - in, out := &in.Labels, &out.Labels - *out = make(map[string]string, len(*in)) - for key, val := range *in { - (*out)[key] = val - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CertificateSecretTemplate. -func (in *CertificateSecretTemplate) DeepCopy() *CertificateSecretTemplate { - if in == nil { - return nil - } - out := new(CertificateSecretTemplate) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *CertificateSpec) DeepCopyInto(out *CertificateSpec) { - *out = *in - if in.Subject != nil { - in, out := &in.Subject, &out.Subject - *out = new(X509Subject) - (*in).DeepCopyInto(*out) - } - if in.Duration != nil { - in, out := &in.Duration, &out.Duration - *out = new(v1.Duration) - **out = **in - } - if in.RenewBefore != nil { - in, out := &in.RenewBefore, &out.RenewBefore - *out = new(v1.Duration) - **out = **in - } - if in.DNSNames != nil { - in, out := &in.DNSNames, &out.DNSNames - *out = make([]string, len(*in)) - copy(*out, *in) - } - if in.IPAddresses != nil { - in, out := &in.IPAddresses, &out.IPAddresses - *out = make([]string, len(*in)) - copy(*out, *in) - } - if in.URISANs != nil { - in, out := &in.URISANs, &out.URISANs - *out = make([]string, len(*in)) - copy(*out, *in) - } - if in.EmailSANs != nil { - in, out := &in.EmailSANs, &out.EmailSANs - *out = make([]string, len(*in)) - copy(*out, *in) - } - if in.SecretTemplate != nil { - in, out := &in.SecretTemplate, &out.SecretTemplate - *out = new(CertificateSecretTemplate) - (*in).DeepCopyInto(*out) - } - if in.Keystores != nil { - in, out := &in.Keystores, &out.Keystores - *out = new(CertificateKeystores) - (*in).DeepCopyInto(*out) - } - out.IssuerRef = in.IssuerRef - if in.Usages != nil { - in, out := &in.Usages, &out.Usages - *out = make([]KeyUsage, len(*in)) - copy(*out, *in) - } - if in.PrivateKey != nil { - in, out := &in.PrivateKey, &out.PrivateKey - *out = new(CertificatePrivateKey) - **out = **in - } - if in.EncodeUsagesInRequest != nil { - in, out := &in.EncodeUsagesInRequest, &out.EncodeUsagesInRequest - *out = new(bool) - **out = **in - } - if in.RevisionHistoryLimit != nil { - in, out := &in.RevisionHistoryLimit, &out.RevisionHistoryLimit - *out = new(int32) - **out = **in - } - if in.AdditionalOutputFormats != nil { - in, out := &in.AdditionalOutputFormats, &out.AdditionalOutputFormats - *out = make([]CertificateAdditionalOutputFormat, len(*in)) - copy(*out, *in) - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CertificateSpec. -func (in *CertificateSpec) DeepCopy() *CertificateSpec { - if in == nil { - return nil - } - out := new(CertificateSpec) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *CertificateStatus) DeepCopyInto(out *CertificateStatus) { - *out = *in - if in.Conditions != nil { - in, out := &in.Conditions, &out.Conditions - *out = make([]CertificateCondition, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - if in.LastFailureTime != nil { - in, out := &in.LastFailureTime, &out.LastFailureTime - *out = (*in).DeepCopy() - } - if in.NotBefore != nil { - in, out := &in.NotBefore, &out.NotBefore - *out = (*in).DeepCopy() - } - if in.NotAfter != nil { - in, out := &in.NotAfter, &out.NotAfter - *out = (*in).DeepCopy() - } - if in.RenewalTime != nil { - in, out := &in.RenewalTime, &out.RenewalTime - *out = (*in).DeepCopy() - } - if in.Revision != nil { - in, out := &in.Revision, &out.Revision - *out = new(int) - **out = **in - } - if in.NextPrivateKeySecretName != nil { - in, out := &in.NextPrivateKeySecretName, &out.NextPrivateKeySecretName - *out = new(string) - **out = **in - } - if in.FailedIssuanceAttempts != nil { - in, out := &in.FailedIssuanceAttempts, &out.FailedIssuanceAttempts - *out = new(int) - **out = **in - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CertificateStatus. -func (in *CertificateStatus) DeepCopy() *CertificateStatus { - if in == nil { - return nil - } - out := new(CertificateStatus) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ClusterIssuer) DeepCopyInto(out *ClusterIssuer) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - in.Spec.DeepCopyInto(&out.Spec) - in.Status.DeepCopyInto(&out.Status) - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterIssuer. -func (in *ClusterIssuer) DeepCopy() *ClusterIssuer { - if in == nil { - return nil - } - out := new(ClusterIssuer) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *ClusterIssuer) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ClusterIssuerList) DeepCopyInto(out *ClusterIssuerList) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ListMeta.DeepCopyInto(&out.ListMeta) - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]ClusterIssuer, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterIssuerList. -func (in *ClusterIssuerList) DeepCopy() *ClusterIssuerList { - if in == nil { - return nil - } - out := new(ClusterIssuerList) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *ClusterIssuerList) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *Issuer) DeepCopyInto(out *Issuer) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - in.Spec.DeepCopyInto(&out.Spec) - in.Status.DeepCopyInto(&out.Status) - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Issuer. -func (in *Issuer) DeepCopy() *Issuer { - if in == nil { - return nil - } - out := new(Issuer) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *Issuer) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *IssuerCondition) DeepCopyInto(out *IssuerCondition) { - *out = *in - if in.LastTransitionTime != nil { - in, out := &in.LastTransitionTime, &out.LastTransitionTime - *out = (*in).DeepCopy() - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new IssuerCondition. -func (in *IssuerCondition) DeepCopy() *IssuerCondition { - if in == nil { - return nil - } - out := new(IssuerCondition) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *IssuerConfig) DeepCopyInto(out *IssuerConfig) { - *out = *in - if in.ACME != nil { - in, out := &in.ACME, &out.ACME - *out = new(acmev1alpha3.ACMEIssuer) - (*in).DeepCopyInto(*out) - } - if in.CA != nil { - in, out := &in.CA, &out.CA - *out = new(CAIssuer) - (*in).DeepCopyInto(*out) - } - if in.Vault != nil { - in, out := &in.Vault, &out.Vault - *out = new(VaultIssuer) - (*in).DeepCopyInto(*out) - } - if in.SelfSigned != nil { - in, out := &in.SelfSigned, &out.SelfSigned - *out = new(SelfSignedIssuer) - (*in).DeepCopyInto(*out) - } - if in.Venafi != nil { - in, out := &in.Venafi, &out.Venafi - *out = new(VenafiIssuer) - (*in).DeepCopyInto(*out) - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new IssuerConfig. -func (in *IssuerConfig) DeepCopy() *IssuerConfig { - if in == nil { - return nil - } - out := new(IssuerConfig) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *IssuerList) DeepCopyInto(out *IssuerList) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ListMeta.DeepCopyInto(&out.ListMeta) - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]Issuer, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new IssuerList. -func (in *IssuerList) DeepCopy() *IssuerList { - if in == nil { - return nil - } - out := new(IssuerList) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *IssuerList) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *IssuerSpec) DeepCopyInto(out *IssuerSpec) { - *out = *in - in.IssuerConfig.DeepCopyInto(&out.IssuerConfig) - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new IssuerSpec. -func (in *IssuerSpec) DeepCopy() *IssuerSpec { - if in == nil { - return nil - } - out := new(IssuerSpec) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *IssuerStatus) DeepCopyInto(out *IssuerStatus) { - *out = *in - if in.Conditions != nil { - in, out := &in.Conditions, &out.Conditions - *out = make([]IssuerCondition, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - if in.ACME != nil { - in, out := &in.ACME, &out.ACME - *out = new(acmev1alpha3.ACMEIssuerStatus) - **out = **in - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new IssuerStatus. -func (in *IssuerStatus) DeepCopy() *IssuerStatus { - if in == nil { - return nil - } - out := new(IssuerStatus) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *JKSKeystore) DeepCopyInto(out *JKSKeystore) { - *out = *in - out.PasswordSecretRef = in.PasswordSecretRef - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new JKSKeystore. -func (in *JKSKeystore) DeepCopy() *JKSKeystore { - if in == nil { - return nil - } - out := new(JKSKeystore) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *PKCS12Keystore) DeepCopyInto(out *PKCS12Keystore) { - *out = *in - out.PasswordSecretRef = in.PasswordSecretRef - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PKCS12Keystore. -func (in *PKCS12Keystore) DeepCopy() *PKCS12Keystore { - if in == nil { - return nil - } - out := new(PKCS12Keystore) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *SelfSignedIssuer) DeepCopyInto(out *SelfSignedIssuer) { - *out = *in - if in.CRLDistributionPoints != nil { - in, out := &in.CRLDistributionPoints, &out.CRLDistributionPoints - *out = make([]string, len(*in)) - copy(*out, *in) - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SelfSignedIssuer. -func (in *SelfSignedIssuer) DeepCopy() *SelfSignedIssuer { - if in == nil { - return nil - } - out := new(SelfSignedIssuer) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *VaultAppRole) DeepCopyInto(out *VaultAppRole) { - *out = *in - out.SecretRef = in.SecretRef - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VaultAppRole. -func (in *VaultAppRole) DeepCopy() *VaultAppRole { - if in == nil { - return nil - } - out := new(VaultAppRole) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *VaultAuth) DeepCopyInto(out *VaultAuth) { - *out = *in - if in.TokenSecretRef != nil { - in, out := &in.TokenSecretRef, &out.TokenSecretRef - *out = new(metav1.SecretKeySelector) - **out = **in - } - if in.AppRole != nil { - in, out := &in.AppRole, &out.AppRole - *out = new(VaultAppRole) - **out = **in - } - if in.Kubernetes != nil { - in, out := &in.Kubernetes, &out.Kubernetes - *out = new(VaultKubernetesAuth) - **out = **in - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VaultAuth. -func (in *VaultAuth) DeepCopy() *VaultAuth { - if in == nil { - return nil - } - out := new(VaultAuth) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *VaultIssuer) DeepCopyInto(out *VaultIssuer) { - *out = *in - in.Auth.DeepCopyInto(&out.Auth) - if in.CABundle != nil { - in, out := &in.CABundle, &out.CABundle - *out = make([]byte, len(*in)) - copy(*out, *in) - } - if in.CABundleSecretRef != nil { - in, out := &in.CABundleSecretRef, &out.CABundleSecretRef - *out = new(metav1.SecretKeySelector) - **out = **in - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VaultIssuer. -func (in *VaultIssuer) DeepCopy() *VaultIssuer { - if in == nil { - return nil - } - out := new(VaultIssuer) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *VaultKubernetesAuth) DeepCopyInto(out *VaultKubernetesAuth) { - *out = *in - out.SecretRef = in.SecretRef - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VaultKubernetesAuth. -func (in *VaultKubernetesAuth) DeepCopy() *VaultKubernetesAuth { - if in == nil { - return nil - } - out := new(VaultKubernetesAuth) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *VenafiCloud) DeepCopyInto(out *VenafiCloud) { - *out = *in - out.APITokenSecretRef = in.APITokenSecretRef - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VenafiCloud. -func (in *VenafiCloud) DeepCopy() *VenafiCloud { - if in == nil { - return nil - } - out := new(VenafiCloud) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *VenafiIssuer) DeepCopyInto(out *VenafiIssuer) { - *out = *in - if in.TPP != nil { - in, out := &in.TPP, &out.TPP - *out = new(VenafiTPP) - (*in).DeepCopyInto(*out) - } - if in.Cloud != nil { - in, out := &in.Cloud, &out.Cloud - *out = new(VenafiCloud) - **out = **in - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VenafiIssuer. -func (in *VenafiIssuer) DeepCopy() *VenafiIssuer { - if in == nil { - return nil - } - out := new(VenafiIssuer) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *VenafiTPP) DeepCopyInto(out *VenafiTPP) { - *out = *in - out.CredentialsRef = in.CredentialsRef - if in.CABundle != nil { - in, out := &in.CABundle, &out.CABundle - *out = make([]byte, len(*in)) - copy(*out, *in) - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VenafiTPP. -func (in *VenafiTPP) DeepCopy() *VenafiTPP { - if in == nil { - return nil - } - out := new(VenafiTPP) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *X509Subject) DeepCopyInto(out *X509Subject) { - *out = *in - if in.Organizations != nil { - in, out := &in.Organizations, &out.Organizations - *out = make([]string, len(*in)) - copy(*out, *in) - } - if in.Countries != nil { - in, out := &in.Countries, &out.Countries - *out = make([]string, len(*in)) - copy(*out, *in) - } - if in.OrganizationalUnits != nil { - in, out := &in.OrganizationalUnits, &out.OrganizationalUnits - *out = make([]string, len(*in)) - copy(*out, *in) - } - if in.Localities != nil { - in, out := &in.Localities, &out.Localities - *out = make([]string, len(*in)) - copy(*out, *in) - } - if in.Provinces != nil { - in, out := &in.Provinces, &out.Provinces - *out = make([]string, len(*in)) - copy(*out, *in) - } - if in.StreetAddresses != nil { - in, out := &in.StreetAddresses, &out.StreetAddresses - *out = make([]string, len(*in)) - copy(*out, *in) - } - if in.PostalCodes != nil { - in, out := &in.PostalCodes, &out.PostalCodes - *out = make([]string, len(*in)) - copy(*out, *in) - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new X509Subject. -func (in *X509Subject) DeepCopy() *X509Subject { - if in == nil { - return nil - } - out := new(X509Subject) - in.DeepCopyInto(out) - return out -} diff --git a/internal/apis/certmanager/v1alpha3/zz_generated.defaults.go b/internal/apis/certmanager/v1alpha3/zz_generated.defaults.go deleted file mode 100644 index 17fd22729d1..00000000000 --- a/internal/apis/certmanager/v1alpha3/zz_generated.defaults.go +++ /dev/null @@ -1,33 +0,0 @@ -//go:build !ignore_autogenerated -// +build !ignore_autogenerated - -/* -Copyright 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. -*/ - -// Code generated by defaulter-gen. DO NOT EDIT. - -package v1alpha3 - -import ( - runtime "k8s.io/apimachinery/pkg/runtime" -) - -// RegisterDefaults adds defaulters functions to the given scheme. -// Public to allow building arbitrary schemes. -// All generated defaulters are covering - they call all nested defaulters. -func RegisterDefaults(scheme *runtime.Scheme) error { - return nil -} diff --git a/internal/apis/certmanager/v1beta1/const.go b/internal/apis/certmanager/v1beta1/const.go deleted file mode 100644 index 7901c21ad01..00000000000 --- a/internal/apis/certmanager/v1beta1/const.go +++ /dev/null @@ -1,43 +0,0 @@ -/* -Copyright 2020 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. -*/ - -package v1beta1 - -import "time" - -const ( - // minimum permitted certificate duration by cert-manager - MinimumCertificateDuration = time.Hour - - // default certificate duration if Issuer.spec.duration is not set - DefaultCertificateDuration = time.Hour * 24 * 90 - - // minimum certificate duration before certificate expiration - MinimumRenewBefore = time.Minute * 5 - - // Deprecated: the default is now 2/3 of Certificate's duration - DefaultRenewBefore = time.Hour * 24 * 30 -) - -const ( - // Default index key for the Secret reference for Token authentication - DefaultVaultTokenAuthSecretKey = "token" - - // Default mount path location for Kubernetes ServiceAccount authentication - // (/v1/auth/kubernetes). The endpoint will then be called at `/login`, so - // left as the default, `/v1/auth/kubernetes/login` will be called. - DefaultVaultKubernetesAuthMountPath = "/v1/auth/kubernetes" -) diff --git a/internal/apis/certmanager/v1beta1/defaults.go b/internal/apis/certmanager/v1beta1/defaults.go deleted file mode 100644 index 7f5a9bfc623..00000000000 --- a/internal/apis/certmanager/v1beta1/defaults.go +++ /dev/null @@ -1,25 +0,0 @@ -/* -Copyright 2020 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. -*/ - -package v1beta1 - -import ( - "k8s.io/apimachinery/pkg/runtime" -) - -func addDefaultingFuncs(scheme *runtime.Scheme) error { - return RegisterDefaults(scheme) -} diff --git a/internal/apis/certmanager/v1beta1/doc.go b/internal/apis/certmanager/v1beta1/doc.go deleted file mode 100644 index 750300e6315..00000000000 --- a/internal/apis/certmanager/v1beta1/doc.go +++ /dev/null @@ -1,23 +0,0 @@ -/* -Copyright 2020 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. -*/ - -// +k8s:conversion-gen=github.com/cert-manager/cert-manager/internal/apis/certmanager -// +k8s:conversion-gen-external-types=github.com/cert-manager/cert-manager/internal/apis/certmanager/v1beta1 -// +k8s:defaulter-gen=TypeMeta -// +k8s:deepcopy-gen=package,register - -// +groupName=cert-manager.io -package v1beta1 diff --git a/internal/apis/certmanager/v1beta1/register.go b/internal/apis/certmanager/v1beta1/register.go deleted file mode 100644 index 4527804d801..00000000000 --- a/internal/apis/certmanager/v1beta1/register.go +++ /dev/null @@ -1,67 +0,0 @@ -/* -Copyright 2020 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. -*/ - -package v1beta1 - -import ( - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/runtime/schema" - - "github.com/cert-manager/cert-manager/pkg/apis/certmanager" -) - -// SchemeGroupVersion is group version used to register these objects -var SchemeGroupVersion = schema.GroupVersion{Group: certmanager.GroupName, Version: "v1beta1"} - -// Resource takes an unqualified resource and returns a Group qualified GroupResource -func Resource(resource string) schema.GroupResource { - return SchemeGroupVersion.WithResource(resource).GroupResource() -} - -var ( - SchemeBuilder runtime.SchemeBuilder - localSchemeBuilder = &SchemeBuilder - AddToScheme = localSchemeBuilder.AddToScheme -) - -func init() { - // We only register manually written functions here. The registration of the - // generated functions takes place in the generated files. The separation - // makes the code compile even when the generated files are missing. - localSchemeBuilder.Register(addDefaultingFuncs) - - // We only register manually written functions here. The registration of the - // generated functions takes place in the generated files. The separation - // makes the code compile even when the generated files are missing. - localSchemeBuilder.Register(addKnownTypes) -} - -// Adds the list of known types to api.Scheme. -func addKnownTypes(scheme *runtime.Scheme) error { - scheme.AddKnownTypes(SchemeGroupVersion, - &Certificate{}, - &CertificateList{}, - &Issuer{}, - &IssuerList{}, - &ClusterIssuer{}, - &ClusterIssuerList{}, - &CertificateRequest{}, - &CertificateRequestList{}, - ) - metav1.AddToGroupVersion(scheme, SchemeGroupVersion) - return nil -} diff --git a/internal/apis/certmanager/v1beta1/types.go b/internal/apis/certmanager/v1beta1/types.go deleted file mode 100644 index 2cd2b24a2d4..00000000000 --- a/internal/apis/certmanager/v1beta1/types.go +++ /dev/null @@ -1,195 +0,0 @@ -/* -Copyright 2020 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. -*/ - -package v1beta1 - -// Common annotation keys added to resources. -const ( - // Annotation key for DNS subjectAltNames. - AltNamesAnnotationKey = "cert-manager.io/alt-names" - - // Annotation key for IP subjectAltNames. - IPSANAnnotationKey = "cert-manager.io/ip-sans" - - // Annotation key for URI subjectAltNames. - URISANAnnotationKey = "cert-manager.io/uri-sans" - - // Annotation key for certificate common name. - CommonNameAnnotationKey = "cert-manager.io/common-name" - - // Annotation key the 'name' of the Issuer resource. - IssuerNameAnnotationKey = "cert-manager.io/issuer-name" - - // Annotation key for the 'kind' of the Issuer resource. - IssuerKindAnnotationKey = "cert-manager.io/issuer-kind" - - // Annotation key for the 'group' of the Issuer resource. - IssuerGroupAnnotationKey = "cert-manager.io/issuer-group" - - // Annotation key for the name of the certificate that a resource is related to. - CertificateNameKey = "cert-manager.io/certificate-name" - - // Annotation key used to denote whether a Secret is named on a Certificate - // as a 'next private key' Secret resource. - IsNextPrivateKeySecretLabelKey = "cert-manager.io/next-private-key" -) - -// Deprecated annotation names for Secrets -// These will be removed in a future release. -const ( - DeprecatedIssuerNameAnnotationKey = "certmanager.k8s.io/issuer-name" - DeprecatedIssuerKindAnnotationKey = "certmanager.k8s.io/issuer-kind" -) - -const ( - // issuerNameAnnotation can be used to override the issuer specified on the - // created Certificate resource. - IngressIssuerNameAnnotationKey = "cert-manager.io/issuer" - // clusterIssuerNameAnnotation can be used to override the issuer specified on the - // created Certificate resource. The Certificate will reference the - // specified *ClusterIssuer* instead of normal issuer. - IngressClusterIssuerNameAnnotationKey = "cert-manager.io/cluster-issuer" - // acmeIssuerHTTP01IngressClassAnnotation can be used to override the http01 ingressClass - // if the challenge type is set to http01 - IngressACMEIssuerHTTP01IngressClassAnnotationKey = "acme.cert-manager.io/http01-ingress-class" - - // IngressClassAnnotationKey picks a specific "class" for the Ingress. The - // controller only processes Ingresses with this annotation either unset, or - // set to either the configured value or the empty string. - IngressClassAnnotationKey = "kubernetes.io/ingress.class" -) - -// Annotation names for CertificateRequests -const ( - // Annotation added to CertificateRequest resources to denote the name of - // a Secret resource containing the private key used to sign the CSR stored - // on the resource. - // This annotation *may* not be present, and is used by the 'self signing' - // issuer type to self-sign certificates. - CertificateRequestPrivateKeyAnnotationKey = "cert-manager.io/private-key-secret-name" - - // Annotation to declare the CertificateRequest "revision", belonging to a Certificate Resource - CertificateRequestRevisionAnnotationKey = "cert-manager.io/certificate-revision" -) - -const ( - // IssueTemporaryCertificateAnnotation is an annotation that can be added to - // Certificate resources. - // If it is present, a temporary internally signed certificate will be - // stored in the target Secret resource whilst the real Issuer is processing - // the certificate request. - IssueTemporaryCertificateAnnotation = "cert-manager.io/issue-temporary-certificate" -) - -// Common/known resource kinds. -const ( - ClusterIssuerKind = "ClusterIssuer" - IssuerKind = "Issuer" - CertificateKind = "Certificate" - CertificateRequestKind = "CertificateRequest" -) - -const ( - // WantInjectAnnotation is the annotation that specifies that a particular - // object wants injection of CAs. It takes the form of a reference to a certificate - // as namespace/name. The certificate is expected to have the is-serving-for annotations. - WantInjectAnnotation = "cert-manager.io/inject-ca-from" - - // WantInjectAPIServerCAAnnotation, if set to "true", will make the cainjector - // inject the CA certificate for the Kubernetes apiserver into the resource. - // It discovers the apiserver's CA by inspecting the service account credentials - // mounted into the cainjector pod. - WantInjectAPIServerCAAnnotation = "cert-manager.io/inject-apiserver-ca" - - // WantInjectFromSecretAnnotation is the annotation that specifies that a particular - // object wants injection of CAs. It takes the form of a reference to a Secret - // as namespace/name. - WantInjectFromSecretAnnotation = "cert-manager.io/inject-ca-from-secret" - - // AllowsInjectionFromSecretAnnotation is an annotation that must be added - // to Secret resource that want to denote that they can be directly - // injected into injectables that have a `inject-ca-from-secret` annotation. - // If an injectable references a Secret that does NOT have this annotation, - // the cainjector will refuse to inject the secret. - AllowsInjectionFromSecretAnnotation = "cert-manager.io/allow-direct-injection" -) - -// Issuer specific Annotations -const ( - // VenafiCustomFieldsAnnotationKey is the annotation that passes on JSON encoded custom fields to the Venafi issuer - // This will only work with Venafi TPP v19.3 and higher - // The value is an array with objects containing the name and value keys - // for example: `[{"name": "custom-field", "value": "custom-value"}]` - VenafiCustomFieldsAnnotationKey = "venafi.cert-manager.io/custom-fields" -) - -// 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" -// +kubebuilder:validation:Enum="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" -type KeyUsage string - -const ( - UsageSigning KeyUsage = "signing" - UsageDigitalSignature KeyUsage = "digital signature" - UsageContentCommitment KeyUsage = "content commitment" - UsageKeyEncipherment KeyUsage = "key encipherment" - UsageKeyAgreement KeyUsage = "key agreement" - UsageDataEncipherment KeyUsage = "data encipherment" - UsageCertSign KeyUsage = "cert sign" - UsageCRLSign KeyUsage = "crl sign" - UsageEncipherOnly KeyUsage = "encipher only" - UsageDecipherOnly KeyUsage = "decipher only" - UsageAny KeyUsage = "any" - UsageServerAuth KeyUsage = "server auth" - UsageClientAuth KeyUsage = "client auth" - UsageCodeSigning KeyUsage = "code signing" - UsageEmailProtection KeyUsage = "email protection" - UsageSMIME KeyUsage = "s/mime" - UsageIPsecEndSystem KeyUsage = "ipsec end system" - UsageIPsecTunnel KeyUsage = "ipsec tunnel" - UsageIPsecUser KeyUsage = "ipsec user" - UsageTimestamping KeyUsage = "timestamping" - UsageOCSPSigning KeyUsage = "ocsp signing" - UsageMicrosoftSGC KeyUsage = "microsoft sgc" - UsageNetscapeSGC KeyUsage = "netscape sgc" -) diff --git a/internal/apis/certmanager/v1beta1/types_certificate.go b/internal/apis/certmanager/v1beta1/types_certificate.go deleted file mode 100644 index 2f2a5b18fbe..00000000000 --- a/internal/apis/certmanager/v1beta1/types_certificate.go +++ /dev/null @@ -1,508 +0,0 @@ -/* -Copyright 2020 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. -*/ - -package v1beta1 - -import ( - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - - cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" -) - -// +genclient -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object - -// 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`). -// +k8s:openapi-gen=true -type Certificate struct { - metav1.TypeMeta `json:",inline"` - metav1.ObjectMeta `json:"metadata,omitempty"` - - // Desired state of the Certificate resource. - Spec CertificateSpec `json:"spec"` - - // Status of the Certificate. This is set and managed automatically. - // +optional - Status CertificateStatus `json:"status"` -} - -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object - -// CertificateList is a list of Certificates -type CertificateList struct { - metav1.TypeMeta `json:",inline"` - metav1.ListMeta `json:"metadata"` - - Items []Certificate `json:"items"` -} - -// +kubebuilder:validation:Enum=RSA;ECDSA -type PrivateKeyAlgorithm string - -const ( - // Denotes the RSA private key type. - RSAKeyAlgorithm PrivateKeyAlgorithm = "RSA" - - // Denotes the ECDSA private key type. - ECDSAKeyAlgorithm PrivateKeyAlgorithm = "ECDSA" -) - -// +kubebuilder:validation:Enum=PKCS1;PKCS8 -type PrivateKeyEncoding string - -const ( - // 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 PrivateKeyEncoding = "PKCS1" - - // 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 PrivateKeyEncoding = "PKCS8" -) - -// CertificateSpec defines the desired state of Certificate. -// A valid Certificate requires at least one of a CommonName, DNSName, or -// URISAN to be valid. -type CertificateSpec struct { - // Full X509 name specification (https://golang.org/pkg/crypto/x509/pkix/#Name). - // +optional - Subject *X509Subject `json:"subject,omitempty"` - - // LiteralSubject is an LDAP formatted string that represents the [X.509 Subject field](https://datatracker.ietf.org/doc/html/rfc5280#section-4.1.2.6). - // 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. - // +optional - LiteralSubject string `json:"literalSubject,omitempty"` - - // 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 - // +optional - CommonName string `json:"commonName,omitempty"` - - // 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 2/3 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 - // +optional - Duration *metav1.Duration `json:"duration,omitempty"` - - // How long before the currently issued certificate's expiry - // cert-manager should renew the certificate. The default is 2/3 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 - // +optional - RenewBefore *metav1.Duration `json:"renewBefore,omitempty"` - - // DNSNames is a list of DNS subjectAltNames to be set on the Certificate. - // +optional - DNSNames []string `json:"dnsNames,omitempty"` - - // IPAddresses is a list of IP address subjectAltNames to be set on the Certificate. - // +optional - IPAddresses []string `json:"ipAddresses,omitempty"` - - // URISANs is a list of URI subjectAltNames to be set on the Certificate. - // +optional - URISANs []string `json:"uriSANs,omitempty"` - - // EmailSANs is a list of email subjectAltNames to be set on the Certificate. - // +optional - EmailSANs []string `json:"emailSANs,omitempty"` - - // 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. - SecretName string `json:"secretName"` - - // 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. - // +optional - SecretTemplate *CertificateSecretTemplate `json:"secretTemplate,omitempty"` - - // Keystores configures additional keystore output formats stored in the - // `secretName` Secret resource. - // +optional - Keystores *CertificateKeystores `json:"keystores,omitempty"` - - // 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. - IssuerRef cmmeta.ObjectReference `json:"issuerRef"` - - // IsCA will mark this Certificate as valid for certificate signing. - // This will automatically add the `cert sign` usage to the list of `usages`. - // +optional - IsCA bool `json:"isCA,omitempty"` - - // Usages is the set of x509 usages that are requested for the certificate. - // Defaults to `digital signature` and `key encipherment` if not specified. - // +optional - Usages []KeyUsage `json:"usages,omitempty"` - - // Options to control private keys used for the Certificate. - // +optional - PrivateKey *CertificatePrivateKey `json:"privateKey,omitempty"` - - // EncodeUsagesInRequest controls whether key usages should be present - // in the CertificateRequest - // +optional - EncodeUsagesInRequest *bool `json:"encodeUsagesInRequest,omitempty"` - - // 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`. - // +kubebuilder:validation:ExclusiveMaximum=false - // +optional - RevisionHistoryLimit *int32 `json:"revisionHistoryLimit,omitempty"` // Validated by the validating webhook. - - // 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. - // +optional - AdditionalOutputFormats []CertificateAdditionalOutputFormat `json:"additionalOutputFormats,omitempty"` -} - -// CertificatePrivateKey contains configuration options for private keys -// used by the Certificate controller. -// This allows control of how private keys are rotated. -type CertificatePrivateKey struct { - // 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 - RotationPolicy PrivateKeyRotationPolicy `json:"rotationPolicy,omitempty"` - - // 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 - Encoding PrivateKeyEncoding `json:"encoding,omitempty"` - - // Algorithm is the private key algorithm of the corresponding private key - // for this certificate. If provided, allowed values are either `RSA` 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. - // +optional - Algorithm PrivateKeyAlgorithm `json:"algorithm,omitempty"` - - // 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. - // No other values are allowed. - // +optional - Size int `json:"size,omitempty"` // Validated by webhook. Be mindful of adding OpenAPI validation- see https://github.com/cert-manager/cert-manager/issues/3644 . -} - -// Denotes how private keys should be generated or sourced when a Certificate -// is being issued. -type PrivateKeyRotationPolicy string - -var ( - // RotationPolicyNever means 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. - RotationPolicyNever PrivateKeyRotationPolicy = "Never" - - // RotationPolicyAlways means a private key matching the specified - // requirements will be generated whenever a re-issuance occurs. - RotationPolicyAlways PrivateKeyRotationPolicy = "Always" -) - -// X509Subject Full X509 name specification -type X509Subject struct { - // Organizations to be used on the Certificate. - // +optional - Organizations []string `json:"organizations,omitempty"` - // Countries to be used on the Certificate. - // +optional - Countries []string `json:"countries,omitempty"` - // Organizational Units to be used on the Certificate. - // +optional - OrganizationalUnits []string `json:"organizationalUnits,omitempty"` - // Cities to be used on the Certificate. - // +optional - Localities []string `json:"localities,omitempty"` - // State/Provinces to be used on the Certificate. - // +optional - Provinces []string `json:"provinces,omitempty"` - // Street addresses to be used on the Certificate. - // +optional - StreetAddresses []string `json:"streetAddresses,omitempty"` - // Postal codes to be used on the Certificate. - // +optional - PostalCodes []string `json:"postalCodes,omitempty"` - // Serial number to be used on the Certificate. - // +optional - SerialNumber string `json:"serialNumber,omitempty"` -} - -// CertificateKeystores configures additional keystore output formats to be -// created in the Certificate's output Secret. -type CertificateKeystores struct { - // JKS configures options for storing a JKS keystore in the - // `spec.secretName` Secret resource. - // +optional - JKS *JKSKeystore `json:"jks,omitempty"` - - // PKCS12 configures options for storing a PKCS12 keystore in the - // `spec.secretName` Secret resource. - // +optional - PKCS12 *PKCS12Keystore `json:"pkcs12,omitempty"` -} - -// JKS configures options for storing a JKS keystore in the `spec.secretName` -// Secret resource. -type JKSKeystore struct { - // 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 only be updated upon re-issuance. - Create bool `json:"create"` - - // PasswordSecretRef is a reference to a key in a Secret resource - // containing the password used to encrypt the JKS keystore. - PasswordSecretRef cmmeta.SecretKeySelector `json:"passwordSecretRef"` -} - -// PKCS12 configures options for storing a PKCS12 keystore in the -// `spec.secretName` Secret resource. -type PKCS12Keystore struct { - // 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 only be updated upon re-issuance. - Create bool `json:"create"` - - // PasswordSecretRef is a reference to a key in a Secret resource - // containing the password used to encrypt the PKCS12 keystore. - PasswordSecretRef cmmeta.SecretKeySelector `json:"passwordSecretRef"` -} - -// CertificateStatus defines the observed state of Certificate -type CertificateStatus struct { - // List of status conditions to indicate the status of certificates. - // Known condition types are `Ready` and `Issuing`. - // +listType=map - // +listMapKey=type - // +optional - Conditions []CertificateCondition `json:"conditions,omitempty"` - - // 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. - // +optional - LastFailureTime *metav1.Time `json:"lastFailureTime,omitempty"` - - // The time after which the certificate stored in the secret named - // by this resource in spec.secretName is valid. - // +optional - NotBefore *metav1.Time `json:"notBefore,omitempty"` - - // The expiration time of the certificate stored in the secret named - // by this resource in `spec.secretName`. - // +optional - NotAfter *metav1.Time `json:"notAfter,omitempty"` - - // RenewalTime is the time at which the certificate will be next - // renewed. - // If not set, no upcoming renewal is scheduled. - // +optional - RenewalTime *metav1.Time `json:"renewalTime,omitempty"` - - // 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. - // +optional - Revision *int `json:"revision,omitempty"` - - // 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. - // +optional - NextPrivateKeySecretName *string `json:"nextPrivateKeySecretName,omitempty"` - - // 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). - // +optional - FailedIssuanceAttempts *int `json:"failedIssuanceAttempts,omitempty"` -} - -// CertificateCondition contains condition information for an Certificate. -type CertificateCondition struct { - // Type of the condition, known values are (`Ready`, `Issuing`). - Type CertificateConditionType `json:"type"` - - // Status of the condition, one of (`True`, `False`, `Unknown`). - Status cmmeta.ConditionStatus `json:"status"` - - // LastTransitionTime is the timestamp corresponding to the last status - // change of this condition. - // +optional - LastTransitionTime *metav1.Time `json:"lastTransitionTime,omitempty"` - - // Reason is a brief machine readable explanation for the condition's last - // transition. - // +optional - Reason string `json:"reason,omitempty"` - - // Message is a human readable description of the details of the last - // transition, complementing reason. - // +optional - Message string `json:"message,omitempty"` - - // 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. - // +optional - ObservedGeneration int64 `json:"observedGeneration,omitempty"` -} - -// CertificateConditionType represents an Certificate condition value. -type CertificateConditionType string - -const ( - // 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 - CertificateConditionReady CertificateConditionType = "Ready" - - // 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. - CertificateConditionIssuing CertificateConditionType = "Issuing" -) - -// CertificateSecretTemplate defines the default labels and annotations -// to be copied to the Kubernetes Secret resource named in `CertificateSpec.secretName`. -type CertificateSecretTemplate struct { - // Annotations is a key value map to be copied to the target Kubernetes Secret. - // +optional - Annotations map[string]string `json:"annotations,omitempty"` - - // Labels is a key value map to be copied to the target Kubernetes Secret. - // +optional - Labels map[string]string `json:"labels,omitempty"` -} - -// 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). -// +kubebuilder:validation:Enum=DER;CombinedPEM -type CertificateOutputFormatType string - -const ( - // CertificateOutputFormatDER writes the Certificate's private key in DER - // binary format to the `key.der` target Secret Data key. - CertificateOutputFormatDER CertificateOutputFormatType = "DER" - - // 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 - // (` + \n + `). - CertificateOutputFormatCombinedPEM CertificateOutputFormatType = "CombinedPEM" -) - -// CertificateAdditionalOutputFormat defines an additional output format of a -// Certificate resource. These contain supplementary data formats of the signed -// certificate chain and paired private key. -type CertificateAdditionalOutputFormat struct { - // Type is the name of the format type that should be written to the - // Certificate's target Secret. - Type CertificateOutputFormatType `json:"type"` -} diff --git a/internal/apis/certmanager/v1beta1/types_certificaterequest.go b/internal/apis/certmanager/v1beta1/types_certificaterequest.go deleted file mode 100644 index 766e745860c..00000000000 --- a/internal/apis/certmanager/v1beta1/types_certificaterequest.go +++ /dev/null @@ -1,210 +0,0 @@ -/* -Copyright 2020 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. -*/ - -package v1beta1 - -import ( - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - - cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" -) - -const ( - // Pending indicates that a CertificateRequest is still in progress. - CertificateRequestReasonPending = "Pending" - - // Failed indicates that a CertificateRequest has failed, either due to - // timing out or some other critical failure. - CertificateRequestReasonFailed = "Failed" - - // Issued indicates that a CertificateRequest has been completed, and that - // the `status.certificate` field is set. - CertificateRequestReasonIssued = "Issued" - - // Denied is a Ready condition reason that indicates that a - // CertificateRequest has been denied, and the CertificateRequest will never - // be issued. - CertificateRequestReasonDenied = "Denied" -) - -// +genclient -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object - -// 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. -// +k8s:openapi-gen=true -type CertificateRequest struct { - metav1.TypeMeta `json:",inline"` - metav1.ObjectMeta `json:"metadata,omitempty"` - - // Desired state of the CertificateRequest resource. - Spec CertificateRequestSpec `json:"spec"` - - // Status of the CertificateRequest. This is set and managed automatically. - // +optional - Status CertificateRequestStatus `json:"status"` -} - -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object - -// CertificateRequestList is a list of Certificates -type CertificateRequestList struct { - metav1.TypeMeta `json:",inline"` - metav1.ListMeta `json:"metadata"` - - Items []CertificateRequest `json:"items"` -} - -// CertificateRequestSpec defines the desired state of CertificateRequest -type CertificateRequestSpec struct { - // The requested 'duration' (i.e. lifetime) of the Certificate. - // This option may be ignored/overridden by some issuer types. - // +optional - Duration *metav1.Duration `json:"duration,omitempty"` - - // 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. - IssuerRef cmmeta.ObjectReference `json:"issuerRef"` - - // The PEM-encoded x509 certificate signing request to be submitted to the - // CA for signing. - Request []byte `json:"request"` - - // 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`. - // +optional - IsCA bool `json:"isCA,omitempty"` - - // Usages is the set of x509 usages that are requested for the certificate. - // Defaults to `digital signature` and `key encipherment` if not specified. - // +optional - Usages []KeyUsage `json:"usages,omitempty"` - - // Username contains the name of the user that created the CertificateRequest. - // Populated by the cert-manager webhook on creation and immutable. - // +optional - Username string `json:"username,omitempty"` - // UID contains the uid of the user that created the CertificateRequest. - // Populated by the cert-manager webhook on creation and immutable. - // +optional - UID string `json:"uid,omitempty"` - // Groups contains group membership of the user that created the CertificateRequest. - // Populated by the cert-manager webhook on creation and immutable. - // +listType=atomic - // +optional - Groups []string `json:"groups,omitempty"` - // Extra contains extra attributes of the user that created the CertificateRequest. - // Populated by the cert-manager webhook on creation and immutable. - // +optional - Extra map[string][]string `json:"extra,omitempty"` -} - -// CertificateRequestStatus defines the observed state of CertificateRequest and -// resulting signed certificate. -type CertificateRequestStatus struct { - // List of status conditions to indicate the status of a CertificateRequest. - // Known condition types are `Ready` and `InvalidRequest`. - // +listType=map - // +listMapKey=type - // +optional - Conditions []CertificateRequestCondition `json:"conditions,omitempty"` - - // 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. - // +optional - Certificate []byte `json:"certificate,omitempty"` - - // 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. - // +optional - CA []byte `json:"ca,omitempty"` - - // FailureTime stores the time that this CertificateRequest failed. This is - // used to influence garbage collection and back-off. - // +optional - FailureTime *metav1.Time `json:"failureTime,omitempty"` -} - -// CertificateRequestCondition contains condition information for a CertificateRequest. -type CertificateRequestCondition struct { - // Type of the condition, known values are (`Ready`, - // `InvalidRequest`, `Approved`, `Denied`). - Type CertificateRequestConditionType `json:"type"` - - // Status of the condition, one of (`True`, `False`, `Unknown`). - Status cmmeta.ConditionStatus `json:"status"` - - // LastTransitionTime is the timestamp corresponding to the last status - // change of this condition. - // +optional - LastTransitionTime *metav1.Time `json:"lastTransitionTime,omitempty"` - - // Reason is a brief machine readable explanation for the condition's last - // transition. - // +optional - Reason string `json:"reason,omitempty"` - - // Message is a human readable description of the details of the last - // transition, complementing reason. - // +optional - Message string `json:"message,omitempty"` -} - -// CertificateRequestConditionType represents an Certificate condition value. -type CertificateRequestConditionType string - -const ( - // CertificateRequestConditionReady indicates that a certificate is ready for use. - // This is defined as: - // - The target certificate exists in CertificateRequest.Status - CertificateRequestConditionReady CertificateRequestConditionType = "Ready" - - // 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. - CertificateRequestConditionInvalidRequest CertificateRequestConditionType = "InvalidRequest" - - // 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`. - CertificateRequestConditionApproved CertificateRequestConditionType = "Approved" - - // 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`. - CertificateRequestConditionDenied CertificateRequestConditionType = "Denied" -) diff --git a/internal/apis/certmanager/v1beta1/types_issuer.go b/internal/apis/certmanager/v1beta1/types_issuer.go deleted file mode 100644 index baec4fdcaf0..00000000000 --- a/internal/apis/certmanager/v1beta1/types_issuer.go +++ /dev/null @@ -1,355 +0,0 @@ -/* -Copyright 2020 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. -*/ - -package v1beta1 - -import ( - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - - cmacme "github.com/cert-manager/cert-manager/internal/apis/acme/v1beta1" - cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" -) - -// +genclient -// +genclient:nonNamespaced -// +k8s:openapi-gen=true -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object - -// 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. -type ClusterIssuer struct { - metav1.TypeMeta `json:",inline"` - metav1.ObjectMeta `json:"metadata,omitempty"` - - // Desired state of the ClusterIssuer resource. - Spec IssuerSpec `json:"spec"` - - // Status of the ClusterIssuer. This is set and managed automatically. - // +optional - Status IssuerStatus `json:"status"` -} - -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object - -// ClusterIssuerList is a list of Issuers -type ClusterIssuerList struct { - metav1.TypeMeta `json:",inline"` - metav1.ListMeta `json:"metadata"` - - Items []ClusterIssuer `json:"items"` -} - -// +genclient -// +k8s:openapi-gen=true -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object - -// 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. -type Issuer struct { - metav1.TypeMeta `json:",inline"` - metav1.ObjectMeta `json:"metadata,omitempty"` - - // Desired state of the Issuer resource. - Spec IssuerSpec `json:"spec"` - - // Status of the Issuer. This is set and managed automatically. - // +optional - Status IssuerStatus `json:"status"` -} - -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object - -// IssuerList is a list of Issuers -type IssuerList struct { - metav1.TypeMeta `json:",inline"` - metav1.ListMeta `json:"metadata"` - - Items []Issuer `json:"items"` -} - -// IssuerSpec is the specification of an Issuer. This includes any -// configuration required for the issuer. -type IssuerSpec struct { - IssuerConfig `json:",inline"` -} - -// The configuration for the issuer. -// Only one of these can be set. -type IssuerConfig struct { - // ACME configures this issuer to communicate with a RFC8555 (ACME) server - // to obtain signed x509 certificates. - // +optional - ACME *cmacme.ACMEIssuer `json:"acme,omitempty"` - - // 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. - // +optional - CA *CAIssuer `json:"ca,omitempty"` - - // Vault configures this issuer to sign certificates using a HashiCorp Vault - // PKI backend. - // +optional - Vault *VaultIssuer `json:"vault,omitempty"` - - // SelfSigned configures this issuer to 'self sign' certificates using the - // private key used to create the CertificateRequest object. - // +optional - SelfSigned *SelfSignedIssuer `json:"selfSigned,omitempty"` - - // Venafi configures this issuer to sign certificates using a Venafi TPP - // or Venafi Cloud policy zone. - // +optional - Venafi *VenafiIssuer `json:"venafi,omitempty"` -} - -// Configures an issuer to sign certificates using a Venafi TPP -// or Cloud policy zone. -type VenafiIssuer struct { - // 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. - Zone string `json:"zone"` - - // TPP specifies Trust Protection Platform configuration settings. - // Only one of TPP or Cloud may be specified. - // +optional - TPP *VenafiTPP `json:"tpp,omitempty"` - - // Cloud specifies the Venafi cloud configuration settings. - // Only one of TPP or Cloud may be specified. - // +optional - Cloud *VenafiCloud `json:"cloud,omitempty"` -} - -// VenafiTPP defines connection configuration details for a Venafi TPP instance -type VenafiTPP struct { - // URL is the base URL for the vedsdk endpoint of the Venafi TPP instance, - // for example: "https://tpp.example.com/vedsdk". - URL string `json:"url"` - - // 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'. - CredentialsRef cmmeta.LocalObjectReference `json:"credentialsRef"` - - // CABundle is a PEM encoded TLS certificate to use to verify connections to - // the TPP instance. - // If specified, system roots will not be used and the issuing CA for the - // TPP instance must be verifiable using the provided root. - // If not specified, the connection will be verified using the cert-manager - // system root certificates. - // +optional - CABundle []byte `json:"caBundle,omitempty"` -} - -// VenafiCloud defines connection configuration details for Venafi Cloud -type VenafiCloud struct { - // URL is the base URL for Venafi Cloud. - // Defaults to "https://api.venafi.cloud/v1". - // +optional - URL string `json:"url,omitempty"` - - // APITokenSecretRef is a secret key selector for the Venafi Cloud API token. - APITokenSecretRef cmmeta.SecretKeySelector `json:"apiTokenSecretRef"` -} - -// Configures an issuer to 'self sign' certificates using the -// private key used to create the CertificateRequest object. -type SelfSignedIssuer struct { - // 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. - // +optional - CRLDistributionPoints []string `json:"crlDistributionPoints,omitempty"` -} - -// Configures an issuer to sign certificates using a HashiCorp Vault -// PKI backend. -type VaultIssuer struct { - // Auth configures how cert-manager authenticates with the Vault server. - Auth VaultAuth `json:"auth"` - - // Server is the connection address for the Vault server, e.g: "https://vault.example.com:8200". - Server string `json:"server"` - - // Path is the mount path of the Vault PKI backend's `sign` endpoint, e.g: - // "my_pki_mount/sign/my-role-name". - Path string `json:"path"` - - // 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 - // +optional - Namespace string `json:"namespace,omitempty"` - - // PEM-encoded CA bundle (base64-encoded) used to validate Vault server - // certificate. Only used if the Server URL is using HTTPS protocol. This - // parameter is ignored for plain HTTP protocol connection. If not set the - // system root certificates are used to validate the TLS connection. - // Mutually exclusive with CABundleSecretRef. If neither CABundle nor CABundleSecretRef are defined, - // the cert-manager controller system root certificates are used to validate the TLS connection. - // +optional - CABundle []byte `json:"caBundle,omitempty"` - - // CABundleSecretRef is a reference to a Secret which contains the CABundle which will be used when - // connecting to Vault when using HTTPS. - // Mutually exclusive with CABundle. If neither CABundleSecretRef nor CABundle are defined, the cert-manager - // controller system root certificates are used to validate the TLS connection. - // If no key for the Secret is specified, cert-manager will default to 'ca.crt'. - // +optional - CABundleSecretRef *cmmeta.SecretKeySelector `json:"caBundleSecretRef,omitempty"` -} - -// Configuration used to authenticate with a Vault server. -// Only one of `tokenSecretRef`, `appRole` or `kubernetes` may be specified. -type VaultAuth struct { - // TokenSecretRef authenticates with Vault by presenting a token. - // +optional - TokenSecretRef *cmmeta.SecretKeySelector `json:"tokenSecretRef,omitempty"` - - // AppRole authenticates with Vault using the App Role auth mechanism, - // with the role and secret stored in a Kubernetes Secret resource. - // +optional - AppRole *VaultAppRole `json:"appRole,omitempty"` - - // Kubernetes authenticates with Vault by passing the ServiceAccount - // token stored in the named Secret resource to the Vault server. - // +optional - Kubernetes *VaultKubernetesAuth `json:"kubernetes,omitempty"` -} - -// VaultAppRole authenticates with Vault using the App Role auth mechanism, -// with the role and secret stored in a Kubernetes Secret resource. -type VaultAppRole struct { - // Path where the App Role authentication backend is mounted in Vault, e.g: - // "approle" - Path string `json:"path"` - - // RoleID configured in the App Role authentication backend when setting - // up the authentication backend in Vault. - RoleId string `json:"roleId"` - - // 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. - SecretRef cmmeta.SecretKeySelector `json:"secretRef"` -} - -// Authenticate against Vault using a Kubernetes ServiceAccount token stored in -// a Secret. -type VaultKubernetesAuth struct { - // 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. - // +optional - Path string `json:"mountPath,omitempty"` - - // The required Secret field containing a Kubernetes ServiceAccount JWT used - // for authenticating with Vault. Use of 'ambient credentials' is not - // supported. - SecretRef cmmeta.SecretKeySelector `json:"secretRef"` - - // A required field containing the Vault Role to assume. A Role binds a - // Kubernetes ServiceAccount with a set of Vault policies. - Role string `json:"role"` -} - -type CAIssuer struct { - // SecretName is the name of the secret used to sign Certificates issued - // by this Issuer. - SecretName string `json:"secretName"` - - // 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. - // +optional - CRLDistributionPoints []string `json:"crlDistributionPoints,omitempty"` - - // 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". - // +optional - OCSPServers []string `json:"ocspServers,omitempty"` -} - -// IssuerStatus contains status information about an Issuer -type IssuerStatus struct { - // List of status conditions to indicate the status of a CertificateRequest. - // Known condition types are `Ready`. - // +listType=map - // +listMapKey=type - // +optional - Conditions []IssuerCondition `json:"conditions,omitempty"` - - // ACME specific status options. - // This field should only be set if the Issuer is configured to use an ACME - // server to issue certificates. - // +optional - ACME *cmacme.ACMEIssuerStatus `json:"acme,omitempty"` -} - -// IssuerCondition contains condition information for an Issuer. -type IssuerCondition struct { - // Type of the condition, known values are (`Ready`). - Type IssuerConditionType `json:"type"` - - // Status of the condition, one of (`True`, `False`, `Unknown`). - Status cmmeta.ConditionStatus `json:"status"` - - // LastTransitionTime is the timestamp corresponding to the last status - // change of this condition. - // +optional - LastTransitionTime *metav1.Time `json:"lastTransitionTime,omitempty"` - - // Reason is a brief machine readable explanation for the condition's last - // transition. - // +optional - Reason string `json:"reason,omitempty"` - - // Message is a human readable description of the details of the last - // transition, complementing reason. - // +optional - Message string `json:"message,omitempty"` - - // 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. - // +optional - ObservedGeneration int64 `json:"observedGeneration,omitempty"` -} - -// IssuerConditionType represents an Issuer condition value. -type IssuerConditionType string - -const ( - // 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. - IssuerConditionReady IssuerConditionType = "Ready" -) diff --git a/internal/apis/certmanager/v1beta1/zz_generated.conversion.go b/internal/apis/certmanager/v1beta1/zz_generated.conversion.go deleted file mode 100644 index 3ddc74291c4..00000000000 --- a/internal/apis/certmanager/v1beta1/zz_generated.conversion.go +++ /dev/null @@ -1,1608 +0,0 @@ -//go:build !ignore_autogenerated -// +build !ignore_autogenerated - -/* -Copyright 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. -*/ - -// Code generated by conversion-gen. DO NOT EDIT. - -package v1beta1 - -import ( - unsafe "unsafe" - - acme "github.com/cert-manager/cert-manager/internal/apis/acme" - acmev1beta1 "github.com/cert-manager/cert-manager/internal/apis/acme/v1beta1" - certmanager "github.com/cert-manager/cert-manager/internal/apis/certmanager" - meta "github.com/cert-manager/cert-manager/internal/apis/meta" - apismetav1 "github.com/cert-manager/cert-manager/internal/apis/meta/v1" - metav1 "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" - conversion "k8s.io/apimachinery/pkg/conversion" - runtime "k8s.io/apimachinery/pkg/runtime" -) - -func init() { - localSchemeBuilder.Register(RegisterConversions) -} - -// RegisterConversions adds conversion functions to the given scheme. -// Public to allow building arbitrary schemes. -func RegisterConversions(s *runtime.Scheme) error { - if err := s.AddGeneratedConversionFunc((*CAIssuer)(nil), (*certmanager.CAIssuer)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1beta1_CAIssuer_To_certmanager_CAIssuer(a.(*CAIssuer), b.(*certmanager.CAIssuer), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*certmanager.CAIssuer)(nil), (*CAIssuer)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_certmanager_CAIssuer_To_v1beta1_CAIssuer(a.(*certmanager.CAIssuer), b.(*CAIssuer), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*Certificate)(nil), (*certmanager.Certificate)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1beta1_Certificate_To_certmanager_Certificate(a.(*Certificate), b.(*certmanager.Certificate), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*certmanager.Certificate)(nil), (*Certificate)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_certmanager_Certificate_To_v1beta1_Certificate(a.(*certmanager.Certificate), b.(*Certificate), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*CertificateAdditionalOutputFormat)(nil), (*certmanager.CertificateAdditionalOutputFormat)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1beta1_CertificateAdditionalOutputFormat_To_certmanager_CertificateAdditionalOutputFormat(a.(*CertificateAdditionalOutputFormat), b.(*certmanager.CertificateAdditionalOutputFormat), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*certmanager.CertificateAdditionalOutputFormat)(nil), (*CertificateAdditionalOutputFormat)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_certmanager_CertificateAdditionalOutputFormat_To_v1beta1_CertificateAdditionalOutputFormat(a.(*certmanager.CertificateAdditionalOutputFormat), b.(*CertificateAdditionalOutputFormat), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*CertificateCondition)(nil), (*certmanager.CertificateCondition)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1beta1_CertificateCondition_To_certmanager_CertificateCondition(a.(*CertificateCondition), b.(*certmanager.CertificateCondition), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*certmanager.CertificateCondition)(nil), (*CertificateCondition)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_certmanager_CertificateCondition_To_v1beta1_CertificateCondition(a.(*certmanager.CertificateCondition), b.(*CertificateCondition), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*CertificateKeystores)(nil), (*certmanager.CertificateKeystores)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1beta1_CertificateKeystores_To_certmanager_CertificateKeystores(a.(*CertificateKeystores), b.(*certmanager.CertificateKeystores), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*certmanager.CertificateKeystores)(nil), (*CertificateKeystores)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_certmanager_CertificateKeystores_To_v1beta1_CertificateKeystores(a.(*certmanager.CertificateKeystores), b.(*CertificateKeystores), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*CertificateList)(nil), (*certmanager.CertificateList)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1beta1_CertificateList_To_certmanager_CertificateList(a.(*CertificateList), b.(*certmanager.CertificateList), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*certmanager.CertificateList)(nil), (*CertificateList)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_certmanager_CertificateList_To_v1beta1_CertificateList(a.(*certmanager.CertificateList), b.(*CertificateList), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*CertificatePrivateKey)(nil), (*certmanager.CertificatePrivateKey)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1beta1_CertificatePrivateKey_To_certmanager_CertificatePrivateKey(a.(*CertificatePrivateKey), b.(*certmanager.CertificatePrivateKey), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*certmanager.CertificatePrivateKey)(nil), (*CertificatePrivateKey)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_certmanager_CertificatePrivateKey_To_v1beta1_CertificatePrivateKey(a.(*certmanager.CertificatePrivateKey), b.(*CertificatePrivateKey), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*CertificateRequest)(nil), (*certmanager.CertificateRequest)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1beta1_CertificateRequest_To_certmanager_CertificateRequest(a.(*CertificateRequest), b.(*certmanager.CertificateRequest), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*certmanager.CertificateRequest)(nil), (*CertificateRequest)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_certmanager_CertificateRequest_To_v1beta1_CertificateRequest(a.(*certmanager.CertificateRequest), b.(*CertificateRequest), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*CertificateRequestCondition)(nil), (*certmanager.CertificateRequestCondition)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1beta1_CertificateRequestCondition_To_certmanager_CertificateRequestCondition(a.(*CertificateRequestCondition), b.(*certmanager.CertificateRequestCondition), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*certmanager.CertificateRequestCondition)(nil), (*CertificateRequestCondition)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_certmanager_CertificateRequestCondition_To_v1beta1_CertificateRequestCondition(a.(*certmanager.CertificateRequestCondition), b.(*CertificateRequestCondition), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*CertificateRequestList)(nil), (*certmanager.CertificateRequestList)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1beta1_CertificateRequestList_To_certmanager_CertificateRequestList(a.(*CertificateRequestList), b.(*certmanager.CertificateRequestList), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*certmanager.CertificateRequestList)(nil), (*CertificateRequestList)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_certmanager_CertificateRequestList_To_v1beta1_CertificateRequestList(a.(*certmanager.CertificateRequestList), b.(*CertificateRequestList), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*CertificateRequestSpec)(nil), (*certmanager.CertificateRequestSpec)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1beta1_CertificateRequestSpec_To_certmanager_CertificateRequestSpec(a.(*CertificateRequestSpec), b.(*certmanager.CertificateRequestSpec), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*certmanager.CertificateRequestSpec)(nil), (*CertificateRequestSpec)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_certmanager_CertificateRequestSpec_To_v1beta1_CertificateRequestSpec(a.(*certmanager.CertificateRequestSpec), b.(*CertificateRequestSpec), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*CertificateRequestStatus)(nil), (*certmanager.CertificateRequestStatus)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1beta1_CertificateRequestStatus_To_certmanager_CertificateRequestStatus(a.(*CertificateRequestStatus), b.(*certmanager.CertificateRequestStatus), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*certmanager.CertificateRequestStatus)(nil), (*CertificateRequestStatus)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_certmanager_CertificateRequestStatus_To_v1beta1_CertificateRequestStatus(a.(*certmanager.CertificateRequestStatus), b.(*CertificateRequestStatus), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*CertificateSecretTemplate)(nil), (*certmanager.CertificateSecretTemplate)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1beta1_CertificateSecretTemplate_To_certmanager_CertificateSecretTemplate(a.(*CertificateSecretTemplate), b.(*certmanager.CertificateSecretTemplate), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*certmanager.CertificateSecretTemplate)(nil), (*CertificateSecretTemplate)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_certmanager_CertificateSecretTemplate_To_v1beta1_CertificateSecretTemplate(a.(*certmanager.CertificateSecretTemplate), b.(*CertificateSecretTemplate), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*CertificateSpec)(nil), (*certmanager.CertificateSpec)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1beta1_CertificateSpec_To_certmanager_CertificateSpec(a.(*CertificateSpec), b.(*certmanager.CertificateSpec), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*certmanager.CertificateSpec)(nil), (*CertificateSpec)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_certmanager_CertificateSpec_To_v1beta1_CertificateSpec(a.(*certmanager.CertificateSpec), b.(*CertificateSpec), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*CertificateStatus)(nil), (*certmanager.CertificateStatus)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1beta1_CertificateStatus_To_certmanager_CertificateStatus(a.(*CertificateStatus), b.(*certmanager.CertificateStatus), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*certmanager.CertificateStatus)(nil), (*CertificateStatus)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_certmanager_CertificateStatus_To_v1beta1_CertificateStatus(a.(*certmanager.CertificateStatus), b.(*CertificateStatus), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*ClusterIssuer)(nil), (*certmanager.ClusterIssuer)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1beta1_ClusterIssuer_To_certmanager_ClusterIssuer(a.(*ClusterIssuer), b.(*certmanager.ClusterIssuer), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*certmanager.ClusterIssuer)(nil), (*ClusterIssuer)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_certmanager_ClusterIssuer_To_v1beta1_ClusterIssuer(a.(*certmanager.ClusterIssuer), b.(*ClusterIssuer), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*ClusterIssuerList)(nil), (*certmanager.ClusterIssuerList)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1beta1_ClusterIssuerList_To_certmanager_ClusterIssuerList(a.(*ClusterIssuerList), b.(*certmanager.ClusterIssuerList), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*certmanager.ClusterIssuerList)(nil), (*ClusterIssuerList)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_certmanager_ClusterIssuerList_To_v1beta1_ClusterIssuerList(a.(*certmanager.ClusterIssuerList), b.(*ClusterIssuerList), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*Issuer)(nil), (*certmanager.Issuer)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1beta1_Issuer_To_certmanager_Issuer(a.(*Issuer), b.(*certmanager.Issuer), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*certmanager.Issuer)(nil), (*Issuer)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_certmanager_Issuer_To_v1beta1_Issuer(a.(*certmanager.Issuer), b.(*Issuer), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*IssuerCondition)(nil), (*certmanager.IssuerCondition)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1beta1_IssuerCondition_To_certmanager_IssuerCondition(a.(*IssuerCondition), b.(*certmanager.IssuerCondition), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*certmanager.IssuerCondition)(nil), (*IssuerCondition)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_certmanager_IssuerCondition_To_v1beta1_IssuerCondition(a.(*certmanager.IssuerCondition), b.(*IssuerCondition), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*IssuerConfig)(nil), (*certmanager.IssuerConfig)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1beta1_IssuerConfig_To_certmanager_IssuerConfig(a.(*IssuerConfig), b.(*certmanager.IssuerConfig), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*certmanager.IssuerConfig)(nil), (*IssuerConfig)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_certmanager_IssuerConfig_To_v1beta1_IssuerConfig(a.(*certmanager.IssuerConfig), b.(*IssuerConfig), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*IssuerList)(nil), (*certmanager.IssuerList)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1beta1_IssuerList_To_certmanager_IssuerList(a.(*IssuerList), b.(*certmanager.IssuerList), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*certmanager.IssuerList)(nil), (*IssuerList)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_certmanager_IssuerList_To_v1beta1_IssuerList(a.(*certmanager.IssuerList), b.(*IssuerList), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*IssuerSpec)(nil), (*certmanager.IssuerSpec)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1beta1_IssuerSpec_To_certmanager_IssuerSpec(a.(*IssuerSpec), b.(*certmanager.IssuerSpec), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*certmanager.IssuerSpec)(nil), (*IssuerSpec)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_certmanager_IssuerSpec_To_v1beta1_IssuerSpec(a.(*certmanager.IssuerSpec), b.(*IssuerSpec), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*IssuerStatus)(nil), (*certmanager.IssuerStatus)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1beta1_IssuerStatus_To_certmanager_IssuerStatus(a.(*IssuerStatus), b.(*certmanager.IssuerStatus), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*certmanager.IssuerStatus)(nil), (*IssuerStatus)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_certmanager_IssuerStatus_To_v1beta1_IssuerStatus(a.(*certmanager.IssuerStatus), b.(*IssuerStatus), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*JKSKeystore)(nil), (*certmanager.JKSKeystore)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1beta1_JKSKeystore_To_certmanager_JKSKeystore(a.(*JKSKeystore), b.(*certmanager.JKSKeystore), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*certmanager.JKSKeystore)(nil), (*JKSKeystore)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_certmanager_JKSKeystore_To_v1beta1_JKSKeystore(a.(*certmanager.JKSKeystore), b.(*JKSKeystore), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*PKCS12Keystore)(nil), (*certmanager.PKCS12Keystore)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1beta1_PKCS12Keystore_To_certmanager_PKCS12Keystore(a.(*PKCS12Keystore), b.(*certmanager.PKCS12Keystore), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*certmanager.PKCS12Keystore)(nil), (*PKCS12Keystore)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_certmanager_PKCS12Keystore_To_v1beta1_PKCS12Keystore(a.(*certmanager.PKCS12Keystore), b.(*PKCS12Keystore), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*SelfSignedIssuer)(nil), (*certmanager.SelfSignedIssuer)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1beta1_SelfSignedIssuer_To_certmanager_SelfSignedIssuer(a.(*SelfSignedIssuer), b.(*certmanager.SelfSignedIssuer), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*certmanager.SelfSignedIssuer)(nil), (*SelfSignedIssuer)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_certmanager_SelfSignedIssuer_To_v1beta1_SelfSignedIssuer(a.(*certmanager.SelfSignedIssuer), b.(*SelfSignedIssuer), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*VaultAppRole)(nil), (*certmanager.VaultAppRole)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1beta1_VaultAppRole_To_certmanager_VaultAppRole(a.(*VaultAppRole), b.(*certmanager.VaultAppRole), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*certmanager.VaultAppRole)(nil), (*VaultAppRole)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_certmanager_VaultAppRole_To_v1beta1_VaultAppRole(a.(*certmanager.VaultAppRole), b.(*VaultAppRole), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*VaultAuth)(nil), (*certmanager.VaultAuth)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1beta1_VaultAuth_To_certmanager_VaultAuth(a.(*VaultAuth), b.(*certmanager.VaultAuth), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*certmanager.VaultAuth)(nil), (*VaultAuth)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_certmanager_VaultAuth_To_v1beta1_VaultAuth(a.(*certmanager.VaultAuth), b.(*VaultAuth), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*VaultIssuer)(nil), (*certmanager.VaultIssuer)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1beta1_VaultIssuer_To_certmanager_VaultIssuer(a.(*VaultIssuer), b.(*certmanager.VaultIssuer), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*certmanager.VaultIssuer)(nil), (*VaultIssuer)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_certmanager_VaultIssuer_To_v1beta1_VaultIssuer(a.(*certmanager.VaultIssuer), b.(*VaultIssuer), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*VaultKubernetesAuth)(nil), (*certmanager.VaultKubernetesAuth)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1beta1_VaultKubernetesAuth_To_certmanager_VaultKubernetesAuth(a.(*VaultKubernetesAuth), b.(*certmanager.VaultKubernetesAuth), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*certmanager.VaultKubernetesAuth)(nil), (*VaultKubernetesAuth)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_certmanager_VaultKubernetesAuth_To_v1beta1_VaultKubernetesAuth(a.(*certmanager.VaultKubernetesAuth), b.(*VaultKubernetesAuth), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*VenafiCloud)(nil), (*certmanager.VenafiCloud)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1beta1_VenafiCloud_To_certmanager_VenafiCloud(a.(*VenafiCloud), b.(*certmanager.VenafiCloud), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*certmanager.VenafiCloud)(nil), (*VenafiCloud)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_certmanager_VenafiCloud_To_v1beta1_VenafiCloud(a.(*certmanager.VenafiCloud), b.(*VenafiCloud), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*VenafiIssuer)(nil), (*certmanager.VenafiIssuer)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1beta1_VenafiIssuer_To_certmanager_VenafiIssuer(a.(*VenafiIssuer), b.(*certmanager.VenafiIssuer), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*certmanager.VenafiIssuer)(nil), (*VenafiIssuer)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_certmanager_VenafiIssuer_To_v1beta1_VenafiIssuer(a.(*certmanager.VenafiIssuer), b.(*VenafiIssuer), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*VenafiTPP)(nil), (*certmanager.VenafiTPP)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1beta1_VenafiTPP_To_certmanager_VenafiTPP(a.(*VenafiTPP), b.(*certmanager.VenafiTPP), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*certmanager.VenafiTPP)(nil), (*VenafiTPP)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_certmanager_VenafiTPP_To_v1beta1_VenafiTPP(a.(*certmanager.VenafiTPP), b.(*VenafiTPP), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*X509Subject)(nil), (*certmanager.X509Subject)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1beta1_X509Subject_To_certmanager_X509Subject(a.(*X509Subject), b.(*certmanager.X509Subject), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*certmanager.X509Subject)(nil), (*X509Subject)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_certmanager_X509Subject_To_v1beta1_X509Subject(a.(*certmanager.X509Subject), b.(*X509Subject), scope) - }); err != nil { - return err - } - return nil -} - -func autoConvert_v1beta1_CAIssuer_To_certmanager_CAIssuer(in *CAIssuer, out *certmanager.CAIssuer, s conversion.Scope) error { - out.SecretName = in.SecretName - out.CRLDistributionPoints = *(*[]string)(unsafe.Pointer(&in.CRLDistributionPoints)) - out.OCSPServers = *(*[]string)(unsafe.Pointer(&in.OCSPServers)) - return nil -} - -// Convert_v1beta1_CAIssuer_To_certmanager_CAIssuer is an autogenerated conversion function. -func Convert_v1beta1_CAIssuer_To_certmanager_CAIssuer(in *CAIssuer, out *certmanager.CAIssuer, s conversion.Scope) error { - return autoConvert_v1beta1_CAIssuer_To_certmanager_CAIssuer(in, out, s) -} - -func autoConvert_certmanager_CAIssuer_To_v1beta1_CAIssuer(in *certmanager.CAIssuer, out *CAIssuer, s conversion.Scope) error { - out.SecretName = in.SecretName - out.CRLDistributionPoints = *(*[]string)(unsafe.Pointer(&in.CRLDistributionPoints)) - out.OCSPServers = *(*[]string)(unsafe.Pointer(&in.OCSPServers)) - return nil -} - -// Convert_certmanager_CAIssuer_To_v1beta1_CAIssuer is an autogenerated conversion function. -func Convert_certmanager_CAIssuer_To_v1beta1_CAIssuer(in *certmanager.CAIssuer, out *CAIssuer, s conversion.Scope) error { - return autoConvert_certmanager_CAIssuer_To_v1beta1_CAIssuer(in, out, s) -} - -func autoConvert_v1beta1_Certificate_To_certmanager_Certificate(in *Certificate, out *certmanager.Certificate, s conversion.Scope) error { - out.ObjectMeta = in.ObjectMeta - if err := Convert_v1beta1_CertificateSpec_To_certmanager_CertificateSpec(&in.Spec, &out.Spec, s); err != nil { - return err - } - if err := Convert_v1beta1_CertificateStatus_To_certmanager_CertificateStatus(&in.Status, &out.Status, s); err != nil { - return err - } - return nil -} - -// Convert_v1beta1_Certificate_To_certmanager_Certificate is an autogenerated conversion function. -func Convert_v1beta1_Certificate_To_certmanager_Certificate(in *Certificate, out *certmanager.Certificate, s conversion.Scope) error { - return autoConvert_v1beta1_Certificate_To_certmanager_Certificate(in, out, s) -} - -func autoConvert_certmanager_Certificate_To_v1beta1_Certificate(in *certmanager.Certificate, out *Certificate, s conversion.Scope) error { - out.ObjectMeta = in.ObjectMeta - if err := Convert_certmanager_CertificateSpec_To_v1beta1_CertificateSpec(&in.Spec, &out.Spec, s); err != nil { - return err - } - if err := Convert_certmanager_CertificateStatus_To_v1beta1_CertificateStatus(&in.Status, &out.Status, s); err != nil { - return err - } - return nil -} - -// Convert_certmanager_Certificate_To_v1beta1_Certificate is an autogenerated conversion function. -func Convert_certmanager_Certificate_To_v1beta1_Certificate(in *certmanager.Certificate, out *Certificate, s conversion.Scope) error { - return autoConvert_certmanager_Certificate_To_v1beta1_Certificate(in, out, s) -} - -func autoConvert_v1beta1_CertificateAdditionalOutputFormat_To_certmanager_CertificateAdditionalOutputFormat(in *CertificateAdditionalOutputFormat, out *certmanager.CertificateAdditionalOutputFormat, s conversion.Scope) error { - out.Type = certmanager.CertificateOutputFormatType(in.Type) - return nil -} - -// Convert_v1beta1_CertificateAdditionalOutputFormat_To_certmanager_CertificateAdditionalOutputFormat is an autogenerated conversion function. -func Convert_v1beta1_CertificateAdditionalOutputFormat_To_certmanager_CertificateAdditionalOutputFormat(in *CertificateAdditionalOutputFormat, out *certmanager.CertificateAdditionalOutputFormat, s conversion.Scope) error { - return autoConvert_v1beta1_CertificateAdditionalOutputFormat_To_certmanager_CertificateAdditionalOutputFormat(in, out, s) -} - -func autoConvert_certmanager_CertificateAdditionalOutputFormat_To_v1beta1_CertificateAdditionalOutputFormat(in *certmanager.CertificateAdditionalOutputFormat, out *CertificateAdditionalOutputFormat, s conversion.Scope) error { - out.Type = CertificateOutputFormatType(in.Type) - return nil -} - -// Convert_certmanager_CertificateAdditionalOutputFormat_To_v1beta1_CertificateAdditionalOutputFormat is an autogenerated conversion function. -func Convert_certmanager_CertificateAdditionalOutputFormat_To_v1beta1_CertificateAdditionalOutputFormat(in *certmanager.CertificateAdditionalOutputFormat, out *CertificateAdditionalOutputFormat, s conversion.Scope) error { - return autoConvert_certmanager_CertificateAdditionalOutputFormat_To_v1beta1_CertificateAdditionalOutputFormat(in, out, s) -} - -func autoConvert_v1beta1_CertificateCondition_To_certmanager_CertificateCondition(in *CertificateCondition, out *certmanager.CertificateCondition, s conversion.Scope) error { - out.Type = certmanager.CertificateConditionType(in.Type) - out.Status = meta.ConditionStatus(in.Status) - out.LastTransitionTime = (*v1.Time)(unsafe.Pointer(in.LastTransitionTime)) - out.Reason = in.Reason - out.Message = in.Message - out.ObservedGeneration = in.ObservedGeneration - return nil -} - -// Convert_v1beta1_CertificateCondition_To_certmanager_CertificateCondition is an autogenerated conversion function. -func Convert_v1beta1_CertificateCondition_To_certmanager_CertificateCondition(in *CertificateCondition, out *certmanager.CertificateCondition, s conversion.Scope) error { - return autoConvert_v1beta1_CertificateCondition_To_certmanager_CertificateCondition(in, out, s) -} - -func autoConvert_certmanager_CertificateCondition_To_v1beta1_CertificateCondition(in *certmanager.CertificateCondition, out *CertificateCondition, s conversion.Scope) error { - out.Type = CertificateConditionType(in.Type) - out.Status = metav1.ConditionStatus(in.Status) - out.LastTransitionTime = (*v1.Time)(unsafe.Pointer(in.LastTransitionTime)) - out.Reason = in.Reason - out.Message = in.Message - out.ObservedGeneration = in.ObservedGeneration - return nil -} - -// Convert_certmanager_CertificateCondition_To_v1beta1_CertificateCondition is an autogenerated conversion function. -func Convert_certmanager_CertificateCondition_To_v1beta1_CertificateCondition(in *certmanager.CertificateCondition, out *CertificateCondition, s conversion.Scope) error { - return autoConvert_certmanager_CertificateCondition_To_v1beta1_CertificateCondition(in, out, s) -} - -func autoConvert_v1beta1_CertificateKeystores_To_certmanager_CertificateKeystores(in *CertificateKeystores, out *certmanager.CertificateKeystores, s conversion.Scope) error { - if in.JKS != nil { - in, out := &in.JKS, &out.JKS - *out = new(certmanager.JKSKeystore) - if err := Convert_v1beta1_JKSKeystore_To_certmanager_JKSKeystore(*in, *out, s); err != nil { - return err - } - } else { - out.JKS = nil - } - if in.PKCS12 != nil { - in, out := &in.PKCS12, &out.PKCS12 - *out = new(certmanager.PKCS12Keystore) - if err := Convert_v1beta1_PKCS12Keystore_To_certmanager_PKCS12Keystore(*in, *out, s); err != nil { - return err - } - } else { - out.PKCS12 = nil - } - return nil -} - -// Convert_v1beta1_CertificateKeystores_To_certmanager_CertificateKeystores is an autogenerated conversion function. -func Convert_v1beta1_CertificateKeystores_To_certmanager_CertificateKeystores(in *CertificateKeystores, out *certmanager.CertificateKeystores, s conversion.Scope) error { - return autoConvert_v1beta1_CertificateKeystores_To_certmanager_CertificateKeystores(in, out, s) -} - -func autoConvert_certmanager_CertificateKeystores_To_v1beta1_CertificateKeystores(in *certmanager.CertificateKeystores, out *CertificateKeystores, s conversion.Scope) error { - if in.JKS != nil { - in, out := &in.JKS, &out.JKS - *out = new(JKSKeystore) - if err := Convert_certmanager_JKSKeystore_To_v1beta1_JKSKeystore(*in, *out, s); err != nil { - return err - } - } else { - out.JKS = nil - } - if in.PKCS12 != nil { - in, out := &in.PKCS12, &out.PKCS12 - *out = new(PKCS12Keystore) - if err := Convert_certmanager_PKCS12Keystore_To_v1beta1_PKCS12Keystore(*in, *out, s); err != nil { - return err - } - } else { - out.PKCS12 = nil - } - return nil -} - -// Convert_certmanager_CertificateKeystores_To_v1beta1_CertificateKeystores is an autogenerated conversion function. -func Convert_certmanager_CertificateKeystores_To_v1beta1_CertificateKeystores(in *certmanager.CertificateKeystores, out *CertificateKeystores, s conversion.Scope) error { - return autoConvert_certmanager_CertificateKeystores_To_v1beta1_CertificateKeystores(in, out, s) -} - -func autoConvert_v1beta1_CertificateList_To_certmanager_CertificateList(in *CertificateList, out *certmanager.CertificateList, s conversion.Scope) error { - out.ListMeta = in.ListMeta - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]certmanager.Certificate, len(*in)) - for i := range *in { - if err := Convert_v1beta1_Certificate_To_certmanager_Certificate(&(*in)[i], &(*out)[i], s); err != nil { - return err - } - } - } else { - out.Items = nil - } - return nil -} - -// Convert_v1beta1_CertificateList_To_certmanager_CertificateList is an autogenerated conversion function. -func Convert_v1beta1_CertificateList_To_certmanager_CertificateList(in *CertificateList, out *certmanager.CertificateList, s conversion.Scope) error { - return autoConvert_v1beta1_CertificateList_To_certmanager_CertificateList(in, out, s) -} - -func autoConvert_certmanager_CertificateList_To_v1beta1_CertificateList(in *certmanager.CertificateList, out *CertificateList, s conversion.Scope) error { - out.ListMeta = in.ListMeta - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]Certificate, len(*in)) - for i := range *in { - if err := Convert_certmanager_Certificate_To_v1beta1_Certificate(&(*in)[i], &(*out)[i], s); err != nil { - return err - } - } - } else { - out.Items = nil - } - return nil -} - -// Convert_certmanager_CertificateList_To_v1beta1_CertificateList is an autogenerated conversion function. -func Convert_certmanager_CertificateList_To_v1beta1_CertificateList(in *certmanager.CertificateList, out *CertificateList, s conversion.Scope) error { - return autoConvert_certmanager_CertificateList_To_v1beta1_CertificateList(in, out, s) -} - -func autoConvert_v1beta1_CertificatePrivateKey_To_certmanager_CertificatePrivateKey(in *CertificatePrivateKey, out *certmanager.CertificatePrivateKey, s conversion.Scope) error { - out.RotationPolicy = certmanager.PrivateKeyRotationPolicy(in.RotationPolicy) - out.Encoding = certmanager.PrivateKeyEncoding(in.Encoding) - out.Algorithm = certmanager.PrivateKeyAlgorithm(in.Algorithm) - out.Size = in.Size - return nil -} - -// Convert_v1beta1_CertificatePrivateKey_To_certmanager_CertificatePrivateKey is an autogenerated conversion function. -func Convert_v1beta1_CertificatePrivateKey_To_certmanager_CertificatePrivateKey(in *CertificatePrivateKey, out *certmanager.CertificatePrivateKey, s conversion.Scope) error { - return autoConvert_v1beta1_CertificatePrivateKey_To_certmanager_CertificatePrivateKey(in, out, s) -} - -func autoConvert_certmanager_CertificatePrivateKey_To_v1beta1_CertificatePrivateKey(in *certmanager.CertificatePrivateKey, out *CertificatePrivateKey, s conversion.Scope) error { - out.RotationPolicy = PrivateKeyRotationPolicy(in.RotationPolicy) - out.Encoding = PrivateKeyEncoding(in.Encoding) - out.Algorithm = PrivateKeyAlgorithm(in.Algorithm) - out.Size = in.Size - return nil -} - -// Convert_certmanager_CertificatePrivateKey_To_v1beta1_CertificatePrivateKey is an autogenerated conversion function. -func Convert_certmanager_CertificatePrivateKey_To_v1beta1_CertificatePrivateKey(in *certmanager.CertificatePrivateKey, out *CertificatePrivateKey, s conversion.Scope) error { - return autoConvert_certmanager_CertificatePrivateKey_To_v1beta1_CertificatePrivateKey(in, out, s) -} - -func autoConvert_v1beta1_CertificateRequest_To_certmanager_CertificateRequest(in *CertificateRequest, out *certmanager.CertificateRequest, s conversion.Scope) error { - out.ObjectMeta = in.ObjectMeta - if err := Convert_v1beta1_CertificateRequestSpec_To_certmanager_CertificateRequestSpec(&in.Spec, &out.Spec, s); err != nil { - return err - } - if err := Convert_v1beta1_CertificateRequestStatus_To_certmanager_CertificateRequestStatus(&in.Status, &out.Status, s); err != nil { - return err - } - return nil -} - -// Convert_v1beta1_CertificateRequest_To_certmanager_CertificateRequest is an autogenerated conversion function. -func Convert_v1beta1_CertificateRequest_To_certmanager_CertificateRequest(in *CertificateRequest, out *certmanager.CertificateRequest, s conversion.Scope) error { - return autoConvert_v1beta1_CertificateRequest_To_certmanager_CertificateRequest(in, out, s) -} - -func autoConvert_certmanager_CertificateRequest_To_v1beta1_CertificateRequest(in *certmanager.CertificateRequest, out *CertificateRequest, s conversion.Scope) error { - out.ObjectMeta = in.ObjectMeta - if err := Convert_certmanager_CertificateRequestSpec_To_v1beta1_CertificateRequestSpec(&in.Spec, &out.Spec, s); err != nil { - return err - } - if err := Convert_certmanager_CertificateRequestStatus_To_v1beta1_CertificateRequestStatus(&in.Status, &out.Status, s); err != nil { - return err - } - return nil -} - -// Convert_certmanager_CertificateRequest_To_v1beta1_CertificateRequest is an autogenerated conversion function. -func Convert_certmanager_CertificateRequest_To_v1beta1_CertificateRequest(in *certmanager.CertificateRequest, out *CertificateRequest, s conversion.Scope) error { - return autoConvert_certmanager_CertificateRequest_To_v1beta1_CertificateRequest(in, out, s) -} - -func autoConvert_v1beta1_CertificateRequestCondition_To_certmanager_CertificateRequestCondition(in *CertificateRequestCondition, out *certmanager.CertificateRequestCondition, s conversion.Scope) error { - out.Type = certmanager.CertificateRequestConditionType(in.Type) - out.Status = meta.ConditionStatus(in.Status) - out.LastTransitionTime = (*v1.Time)(unsafe.Pointer(in.LastTransitionTime)) - out.Reason = in.Reason - out.Message = in.Message - return nil -} - -// Convert_v1beta1_CertificateRequestCondition_To_certmanager_CertificateRequestCondition is an autogenerated conversion function. -func Convert_v1beta1_CertificateRequestCondition_To_certmanager_CertificateRequestCondition(in *CertificateRequestCondition, out *certmanager.CertificateRequestCondition, s conversion.Scope) error { - return autoConvert_v1beta1_CertificateRequestCondition_To_certmanager_CertificateRequestCondition(in, out, s) -} - -func autoConvert_certmanager_CertificateRequestCondition_To_v1beta1_CertificateRequestCondition(in *certmanager.CertificateRequestCondition, out *CertificateRequestCondition, s conversion.Scope) error { - out.Type = CertificateRequestConditionType(in.Type) - out.Status = metav1.ConditionStatus(in.Status) - out.LastTransitionTime = (*v1.Time)(unsafe.Pointer(in.LastTransitionTime)) - out.Reason = in.Reason - out.Message = in.Message - return nil -} - -// Convert_certmanager_CertificateRequestCondition_To_v1beta1_CertificateRequestCondition is an autogenerated conversion function. -func Convert_certmanager_CertificateRequestCondition_To_v1beta1_CertificateRequestCondition(in *certmanager.CertificateRequestCondition, out *CertificateRequestCondition, s conversion.Scope) error { - return autoConvert_certmanager_CertificateRequestCondition_To_v1beta1_CertificateRequestCondition(in, out, s) -} - -func autoConvert_v1beta1_CertificateRequestList_To_certmanager_CertificateRequestList(in *CertificateRequestList, out *certmanager.CertificateRequestList, s conversion.Scope) error { - out.ListMeta = in.ListMeta - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]certmanager.CertificateRequest, len(*in)) - for i := range *in { - if err := Convert_v1beta1_CertificateRequest_To_certmanager_CertificateRequest(&(*in)[i], &(*out)[i], s); err != nil { - return err - } - } - } else { - out.Items = nil - } - return nil -} - -// Convert_v1beta1_CertificateRequestList_To_certmanager_CertificateRequestList is an autogenerated conversion function. -func Convert_v1beta1_CertificateRequestList_To_certmanager_CertificateRequestList(in *CertificateRequestList, out *certmanager.CertificateRequestList, s conversion.Scope) error { - return autoConvert_v1beta1_CertificateRequestList_To_certmanager_CertificateRequestList(in, out, s) -} - -func autoConvert_certmanager_CertificateRequestList_To_v1beta1_CertificateRequestList(in *certmanager.CertificateRequestList, out *CertificateRequestList, s conversion.Scope) error { - out.ListMeta = in.ListMeta - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]CertificateRequest, len(*in)) - for i := range *in { - if err := Convert_certmanager_CertificateRequest_To_v1beta1_CertificateRequest(&(*in)[i], &(*out)[i], s); err != nil { - return err - } - } - } else { - out.Items = nil - } - return nil -} - -// Convert_certmanager_CertificateRequestList_To_v1beta1_CertificateRequestList is an autogenerated conversion function. -func Convert_certmanager_CertificateRequestList_To_v1beta1_CertificateRequestList(in *certmanager.CertificateRequestList, out *CertificateRequestList, s conversion.Scope) error { - return autoConvert_certmanager_CertificateRequestList_To_v1beta1_CertificateRequestList(in, out, s) -} - -func autoConvert_v1beta1_CertificateRequestSpec_To_certmanager_CertificateRequestSpec(in *CertificateRequestSpec, out *certmanager.CertificateRequestSpec, s conversion.Scope) error { - out.Duration = (*v1.Duration)(unsafe.Pointer(in.Duration)) - if err := apismetav1.Convert_v1_ObjectReference_To_meta_ObjectReference(&in.IssuerRef, &out.IssuerRef, s); err != nil { - return err - } - out.Request = *(*[]byte)(unsafe.Pointer(&in.Request)) - out.IsCA = in.IsCA - out.Usages = *(*[]certmanager.KeyUsage)(unsafe.Pointer(&in.Usages)) - out.Username = in.Username - out.UID = in.UID - out.Groups = *(*[]string)(unsafe.Pointer(&in.Groups)) - out.Extra = *(*map[string][]string)(unsafe.Pointer(&in.Extra)) - return nil -} - -// Convert_v1beta1_CertificateRequestSpec_To_certmanager_CertificateRequestSpec is an autogenerated conversion function. -func Convert_v1beta1_CertificateRequestSpec_To_certmanager_CertificateRequestSpec(in *CertificateRequestSpec, out *certmanager.CertificateRequestSpec, s conversion.Scope) error { - return autoConvert_v1beta1_CertificateRequestSpec_To_certmanager_CertificateRequestSpec(in, out, s) -} - -func autoConvert_certmanager_CertificateRequestSpec_To_v1beta1_CertificateRequestSpec(in *certmanager.CertificateRequestSpec, out *CertificateRequestSpec, s conversion.Scope) error { - out.Duration = (*v1.Duration)(unsafe.Pointer(in.Duration)) - if err := apismetav1.Convert_meta_ObjectReference_To_v1_ObjectReference(&in.IssuerRef, &out.IssuerRef, s); err != nil { - return err - } - out.Request = *(*[]byte)(unsafe.Pointer(&in.Request)) - out.IsCA = in.IsCA - out.Usages = *(*[]KeyUsage)(unsafe.Pointer(&in.Usages)) - out.Username = in.Username - out.UID = in.UID - out.Groups = *(*[]string)(unsafe.Pointer(&in.Groups)) - out.Extra = *(*map[string][]string)(unsafe.Pointer(&in.Extra)) - return nil -} - -// Convert_certmanager_CertificateRequestSpec_To_v1beta1_CertificateRequestSpec is an autogenerated conversion function. -func Convert_certmanager_CertificateRequestSpec_To_v1beta1_CertificateRequestSpec(in *certmanager.CertificateRequestSpec, out *CertificateRequestSpec, s conversion.Scope) error { - return autoConvert_certmanager_CertificateRequestSpec_To_v1beta1_CertificateRequestSpec(in, out, s) -} - -func autoConvert_v1beta1_CertificateRequestStatus_To_certmanager_CertificateRequestStatus(in *CertificateRequestStatus, out *certmanager.CertificateRequestStatus, s conversion.Scope) error { - out.Conditions = *(*[]certmanager.CertificateRequestCondition)(unsafe.Pointer(&in.Conditions)) - out.Certificate = *(*[]byte)(unsafe.Pointer(&in.Certificate)) - out.CA = *(*[]byte)(unsafe.Pointer(&in.CA)) - out.FailureTime = (*v1.Time)(unsafe.Pointer(in.FailureTime)) - return nil -} - -// Convert_v1beta1_CertificateRequestStatus_To_certmanager_CertificateRequestStatus is an autogenerated conversion function. -func Convert_v1beta1_CertificateRequestStatus_To_certmanager_CertificateRequestStatus(in *CertificateRequestStatus, out *certmanager.CertificateRequestStatus, s conversion.Scope) error { - return autoConvert_v1beta1_CertificateRequestStatus_To_certmanager_CertificateRequestStatus(in, out, s) -} - -func autoConvert_certmanager_CertificateRequestStatus_To_v1beta1_CertificateRequestStatus(in *certmanager.CertificateRequestStatus, out *CertificateRequestStatus, s conversion.Scope) error { - out.Conditions = *(*[]CertificateRequestCondition)(unsafe.Pointer(&in.Conditions)) - out.Certificate = *(*[]byte)(unsafe.Pointer(&in.Certificate)) - out.CA = *(*[]byte)(unsafe.Pointer(&in.CA)) - out.FailureTime = (*v1.Time)(unsafe.Pointer(in.FailureTime)) - return nil -} - -// Convert_certmanager_CertificateRequestStatus_To_v1beta1_CertificateRequestStatus is an autogenerated conversion function. -func Convert_certmanager_CertificateRequestStatus_To_v1beta1_CertificateRequestStatus(in *certmanager.CertificateRequestStatus, out *CertificateRequestStatus, s conversion.Scope) error { - return autoConvert_certmanager_CertificateRequestStatus_To_v1beta1_CertificateRequestStatus(in, out, s) -} - -func autoConvert_v1beta1_CertificateSecretTemplate_To_certmanager_CertificateSecretTemplate(in *CertificateSecretTemplate, out *certmanager.CertificateSecretTemplate, s conversion.Scope) error { - out.Annotations = *(*map[string]string)(unsafe.Pointer(&in.Annotations)) - out.Labels = *(*map[string]string)(unsafe.Pointer(&in.Labels)) - return nil -} - -// Convert_v1beta1_CertificateSecretTemplate_To_certmanager_CertificateSecretTemplate is an autogenerated conversion function. -func Convert_v1beta1_CertificateSecretTemplate_To_certmanager_CertificateSecretTemplate(in *CertificateSecretTemplate, out *certmanager.CertificateSecretTemplate, s conversion.Scope) error { - return autoConvert_v1beta1_CertificateSecretTemplate_To_certmanager_CertificateSecretTemplate(in, out, s) -} - -func autoConvert_certmanager_CertificateSecretTemplate_To_v1beta1_CertificateSecretTemplate(in *certmanager.CertificateSecretTemplate, out *CertificateSecretTemplate, s conversion.Scope) error { - out.Annotations = *(*map[string]string)(unsafe.Pointer(&in.Annotations)) - out.Labels = *(*map[string]string)(unsafe.Pointer(&in.Labels)) - return nil -} - -// Convert_certmanager_CertificateSecretTemplate_To_v1beta1_CertificateSecretTemplate is an autogenerated conversion function. -func Convert_certmanager_CertificateSecretTemplate_To_v1beta1_CertificateSecretTemplate(in *certmanager.CertificateSecretTemplate, out *CertificateSecretTemplate, s conversion.Scope) error { - return autoConvert_certmanager_CertificateSecretTemplate_To_v1beta1_CertificateSecretTemplate(in, out, s) -} - -func autoConvert_v1beta1_CertificateSpec_To_certmanager_CertificateSpec(in *CertificateSpec, out *certmanager.CertificateSpec, s conversion.Scope) error { - out.Subject = (*certmanager.X509Subject)(unsafe.Pointer(in.Subject)) - out.LiteralSubject = in.LiteralSubject - out.CommonName = in.CommonName - out.Duration = (*v1.Duration)(unsafe.Pointer(in.Duration)) - out.RenewBefore = (*v1.Duration)(unsafe.Pointer(in.RenewBefore)) - out.DNSNames = *(*[]string)(unsafe.Pointer(&in.DNSNames)) - out.IPAddresses = *(*[]string)(unsafe.Pointer(&in.IPAddresses)) - out.URISANs = *(*[]string)(unsafe.Pointer(&in.URISANs)) - out.EmailSANs = *(*[]string)(unsafe.Pointer(&in.EmailSANs)) - out.SecretName = in.SecretName - out.SecretTemplate = (*certmanager.CertificateSecretTemplate)(unsafe.Pointer(in.SecretTemplate)) - if in.Keystores != nil { - in, out := &in.Keystores, &out.Keystores - *out = new(certmanager.CertificateKeystores) - if err := Convert_v1beta1_CertificateKeystores_To_certmanager_CertificateKeystores(*in, *out, s); err != nil { - return err - } - } else { - out.Keystores = nil - } - if err := apismetav1.Convert_v1_ObjectReference_To_meta_ObjectReference(&in.IssuerRef, &out.IssuerRef, s); err != nil { - return err - } - out.IsCA = in.IsCA - out.Usages = *(*[]certmanager.KeyUsage)(unsafe.Pointer(&in.Usages)) - out.PrivateKey = (*certmanager.CertificatePrivateKey)(unsafe.Pointer(in.PrivateKey)) - out.EncodeUsagesInRequest = (*bool)(unsafe.Pointer(in.EncodeUsagesInRequest)) - out.RevisionHistoryLimit = (*int32)(unsafe.Pointer(in.RevisionHistoryLimit)) - out.AdditionalOutputFormats = *(*[]certmanager.CertificateAdditionalOutputFormat)(unsafe.Pointer(&in.AdditionalOutputFormats)) - return nil -} - -// Convert_v1beta1_CertificateSpec_To_certmanager_CertificateSpec is an autogenerated conversion function. -func Convert_v1beta1_CertificateSpec_To_certmanager_CertificateSpec(in *CertificateSpec, out *certmanager.CertificateSpec, s conversion.Scope) error { - return autoConvert_v1beta1_CertificateSpec_To_certmanager_CertificateSpec(in, out, s) -} - -func autoConvert_certmanager_CertificateSpec_To_v1beta1_CertificateSpec(in *certmanager.CertificateSpec, out *CertificateSpec, s conversion.Scope) error { - out.Subject = (*X509Subject)(unsafe.Pointer(in.Subject)) - out.LiteralSubject = in.LiteralSubject - out.CommonName = in.CommonName - out.Duration = (*v1.Duration)(unsafe.Pointer(in.Duration)) - out.RenewBefore = (*v1.Duration)(unsafe.Pointer(in.RenewBefore)) - out.DNSNames = *(*[]string)(unsafe.Pointer(&in.DNSNames)) - out.IPAddresses = *(*[]string)(unsafe.Pointer(&in.IPAddresses)) - out.URISANs = *(*[]string)(unsafe.Pointer(&in.URISANs)) - out.EmailSANs = *(*[]string)(unsafe.Pointer(&in.EmailSANs)) - out.SecretName = in.SecretName - out.SecretTemplate = (*CertificateSecretTemplate)(unsafe.Pointer(in.SecretTemplate)) - if in.Keystores != nil { - in, out := &in.Keystores, &out.Keystores - *out = new(CertificateKeystores) - if err := Convert_certmanager_CertificateKeystores_To_v1beta1_CertificateKeystores(*in, *out, s); err != nil { - return err - } - } else { - out.Keystores = nil - } - if err := apismetav1.Convert_meta_ObjectReference_To_v1_ObjectReference(&in.IssuerRef, &out.IssuerRef, s); err != nil { - return err - } - out.IsCA = in.IsCA - out.Usages = *(*[]KeyUsage)(unsafe.Pointer(&in.Usages)) - out.PrivateKey = (*CertificatePrivateKey)(unsafe.Pointer(in.PrivateKey)) - out.EncodeUsagesInRequest = (*bool)(unsafe.Pointer(in.EncodeUsagesInRequest)) - out.RevisionHistoryLimit = (*int32)(unsafe.Pointer(in.RevisionHistoryLimit)) - out.AdditionalOutputFormats = *(*[]CertificateAdditionalOutputFormat)(unsafe.Pointer(&in.AdditionalOutputFormats)) - return nil -} - -// Convert_certmanager_CertificateSpec_To_v1beta1_CertificateSpec is an autogenerated conversion function. -func Convert_certmanager_CertificateSpec_To_v1beta1_CertificateSpec(in *certmanager.CertificateSpec, out *CertificateSpec, s conversion.Scope) error { - return autoConvert_certmanager_CertificateSpec_To_v1beta1_CertificateSpec(in, out, s) -} - -func autoConvert_v1beta1_CertificateStatus_To_certmanager_CertificateStatus(in *CertificateStatus, out *certmanager.CertificateStatus, s conversion.Scope) error { - out.Conditions = *(*[]certmanager.CertificateCondition)(unsafe.Pointer(&in.Conditions)) - out.LastFailureTime = (*v1.Time)(unsafe.Pointer(in.LastFailureTime)) - out.NotBefore = (*v1.Time)(unsafe.Pointer(in.NotBefore)) - out.NotAfter = (*v1.Time)(unsafe.Pointer(in.NotAfter)) - out.RenewalTime = (*v1.Time)(unsafe.Pointer(in.RenewalTime)) - out.Revision = (*int)(unsafe.Pointer(in.Revision)) - out.NextPrivateKeySecretName = (*string)(unsafe.Pointer(in.NextPrivateKeySecretName)) - out.FailedIssuanceAttempts = (*int)(unsafe.Pointer(in.FailedIssuanceAttempts)) - return nil -} - -// Convert_v1beta1_CertificateStatus_To_certmanager_CertificateStatus is an autogenerated conversion function. -func Convert_v1beta1_CertificateStatus_To_certmanager_CertificateStatus(in *CertificateStatus, out *certmanager.CertificateStatus, s conversion.Scope) error { - return autoConvert_v1beta1_CertificateStatus_To_certmanager_CertificateStatus(in, out, s) -} - -func autoConvert_certmanager_CertificateStatus_To_v1beta1_CertificateStatus(in *certmanager.CertificateStatus, out *CertificateStatus, s conversion.Scope) error { - out.Conditions = *(*[]CertificateCondition)(unsafe.Pointer(&in.Conditions)) - out.LastFailureTime = (*v1.Time)(unsafe.Pointer(in.LastFailureTime)) - out.NotBefore = (*v1.Time)(unsafe.Pointer(in.NotBefore)) - out.NotAfter = (*v1.Time)(unsafe.Pointer(in.NotAfter)) - out.RenewalTime = (*v1.Time)(unsafe.Pointer(in.RenewalTime)) - out.Revision = (*int)(unsafe.Pointer(in.Revision)) - out.NextPrivateKeySecretName = (*string)(unsafe.Pointer(in.NextPrivateKeySecretName)) - out.FailedIssuanceAttempts = (*int)(unsafe.Pointer(in.FailedIssuanceAttempts)) - return nil -} - -// Convert_certmanager_CertificateStatus_To_v1beta1_CertificateStatus is an autogenerated conversion function. -func Convert_certmanager_CertificateStatus_To_v1beta1_CertificateStatus(in *certmanager.CertificateStatus, out *CertificateStatus, s conversion.Scope) error { - return autoConvert_certmanager_CertificateStatus_To_v1beta1_CertificateStatus(in, out, s) -} - -func autoConvert_v1beta1_ClusterIssuer_To_certmanager_ClusterIssuer(in *ClusterIssuer, out *certmanager.ClusterIssuer, s conversion.Scope) error { - out.ObjectMeta = in.ObjectMeta - if err := Convert_v1beta1_IssuerSpec_To_certmanager_IssuerSpec(&in.Spec, &out.Spec, s); err != nil { - return err - } - if err := Convert_v1beta1_IssuerStatus_To_certmanager_IssuerStatus(&in.Status, &out.Status, s); err != nil { - return err - } - return nil -} - -// Convert_v1beta1_ClusterIssuer_To_certmanager_ClusterIssuer is an autogenerated conversion function. -func Convert_v1beta1_ClusterIssuer_To_certmanager_ClusterIssuer(in *ClusterIssuer, out *certmanager.ClusterIssuer, s conversion.Scope) error { - return autoConvert_v1beta1_ClusterIssuer_To_certmanager_ClusterIssuer(in, out, s) -} - -func autoConvert_certmanager_ClusterIssuer_To_v1beta1_ClusterIssuer(in *certmanager.ClusterIssuer, out *ClusterIssuer, s conversion.Scope) error { - out.ObjectMeta = in.ObjectMeta - if err := Convert_certmanager_IssuerSpec_To_v1beta1_IssuerSpec(&in.Spec, &out.Spec, s); err != nil { - return err - } - if err := Convert_certmanager_IssuerStatus_To_v1beta1_IssuerStatus(&in.Status, &out.Status, s); err != nil { - return err - } - return nil -} - -// Convert_certmanager_ClusterIssuer_To_v1beta1_ClusterIssuer is an autogenerated conversion function. -func Convert_certmanager_ClusterIssuer_To_v1beta1_ClusterIssuer(in *certmanager.ClusterIssuer, out *ClusterIssuer, s conversion.Scope) error { - return autoConvert_certmanager_ClusterIssuer_To_v1beta1_ClusterIssuer(in, out, s) -} - -func autoConvert_v1beta1_ClusterIssuerList_To_certmanager_ClusterIssuerList(in *ClusterIssuerList, out *certmanager.ClusterIssuerList, s conversion.Scope) error { - out.ListMeta = in.ListMeta - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]certmanager.ClusterIssuer, len(*in)) - for i := range *in { - if err := Convert_v1beta1_ClusterIssuer_To_certmanager_ClusterIssuer(&(*in)[i], &(*out)[i], s); err != nil { - return err - } - } - } else { - out.Items = nil - } - return nil -} - -// Convert_v1beta1_ClusterIssuerList_To_certmanager_ClusterIssuerList is an autogenerated conversion function. -func Convert_v1beta1_ClusterIssuerList_To_certmanager_ClusterIssuerList(in *ClusterIssuerList, out *certmanager.ClusterIssuerList, s conversion.Scope) error { - return autoConvert_v1beta1_ClusterIssuerList_To_certmanager_ClusterIssuerList(in, out, s) -} - -func autoConvert_certmanager_ClusterIssuerList_To_v1beta1_ClusterIssuerList(in *certmanager.ClusterIssuerList, out *ClusterIssuerList, s conversion.Scope) error { - out.ListMeta = in.ListMeta - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]ClusterIssuer, len(*in)) - for i := range *in { - if err := Convert_certmanager_ClusterIssuer_To_v1beta1_ClusterIssuer(&(*in)[i], &(*out)[i], s); err != nil { - return err - } - } - } else { - out.Items = nil - } - return nil -} - -// Convert_certmanager_ClusterIssuerList_To_v1beta1_ClusterIssuerList is an autogenerated conversion function. -func Convert_certmanager_ClusterIssuerList_To_v1beta1_ClusterIssuerList(in *certmanager.ClusterIssuerList, out *ClusterIssuerList, s conversion.Scope) error { - return autoConvert_certmanager_ClusterIssuerList_To_v1beta1_ClusterIssuerList(in, out, s) -} - -func autoConvert_v1beta1_Issuer_To_certmanager_Issuer(in *Issuer, out *certmanager.Issuer, s conversion.Scope) error { - out.ObjectMeta = in.ObjectMeta - if err := Convert_v1beta1_IssuerSpec_To_certmanager_IssuerSpec(&in.Spec, &out.Spec, s); err != nil { - return err - } - if err := Convert_v1beta1_IssuerStatus_To_certmanager_IssuerStatus(&in.Status, &out.Status, s); err != nil { - return err - } - return nil -} - -// Convert_v1beta1_Issuer_To_certmanager_Issuer is an autogenerated conversion function. -func Convert_v1beta1_Issuer_To_certmanager_Issuer(in *Issuer, out *certmanager.Issuer, s conversion.Scope) error { - return autoConvert_v1beta1_Issuer_To_certmanager_Issuer(in, out, s) -} - -func autoConvert_certmanager_Issuer_To_v1beta1_Issuer(in *certmanager.Issuer, out *Issuer, s conversion.Scope) error { - out.ObjectMeta = in.ObjectMeta - if err := Convert_certmanager_IssuerSpec_To_v1beta1_IssuerSpec(&in.Spec, &out.Spec, s); err != nil { - return err - } - if err := Convert_certmanager_IssuerStatus_To_v1beta1_IssuerStatus(&in.Status, &out.Status, s); err != nil { - return err - } - return nil -} - -// Convert_certmanager_Issuer_To_v1beta1_Issuer is an autogenerated conversion function. -func Convert_certmanager_Issuer_To_v1beta1_Issuer(in *certmanager.Issuer, out *Issuer, s conversion.Scope) error { - return autoConvert_certmanager_Issuer_To_v1beta1_Issuer(in, out, s) -} - -func autoConvert_v1beta1_IssuerCondition_To_certmanager_IssuerCondition(in *IssuerCondition, out *certmanager.IssuerCondition, s conversion.Scope) error { - out.Type = certmanager.IssuerConditionType(in.Type) - out.Status = meta.ConditionStatus(in.Status) - out.LastTransitionTime = (*v1.Time)(unsafe.Pointer(in.LastTransitionTime)) - out.Reason = in.Reason - out.Message = in.Message - out.ObservedGeneration = in.ObservedGeneration - return nil -} - -// Convert_v1beta1_IssuerCondition_To_certmanager_IssuerCondition is an autogenerated conversion function. -func Convert_v1beta1_IssuerCondition_To_certmanager_IssuerCondition(in *IssuerCondition, out *certmanager.IssuerCondition, s conversion.Scope) error { - return autoConvert_v1beta1_IssuerCondition_To_certmanager_IssuerCondition(in, out, s) -} - -func autoConvert_certmanager_IssuerCondition_To_v1beta1_IssuerCondition(in *certmanager.IssuerCondition, out *IssuerCondition, s conversion.Scope) error { - out.Type = IssuerConditionType(in.Type) - out.Status = metav1.ConditionStatus(in.Status) - out.LastTransitionTime = (*v1.Time)(unsafe.Pointer(in.LastTransitionTime)) - out.Reason = in.Reason - out.Message = in.Message - out.ObservedGeneration = in.ObservedGeneration - return nil -} - -// Convert_certmanager_IssuerCondition_To_v1beta1_IssuerCondition is an autogenerated conversion function. -func Convert_certmanager_IssuerCondition_To_v1beta1_IssuerCondition(in *certmanager.IssuerCondition, out *IssuerCondition, s conversion.Scope) error { - return autoConvert_certmanager_IssuerCondition_To_v1beta1_IssuerCondition(in, out, s) -} - -func autoConvert_v1beta1_IssuerConfig_To_certmanager_IssuerConfig(in *IssuerConfig, out *certmanager.IssuerConfig, s conversion.Scope) error { - if in.ACME != nil { - in, out := &in.ACME, &out.ACME - *out = new(acme.ACMEIssuer) - if err := acmev1beta1.Convert_v1beta1_ACMEIssuer_To_acme_ACMEIssuer(*in, *out, s); err != nil { - return err - } - } else { - out.ACME = nil - } - out.CA = (*certmanager.CAIssuer)(unsafe.Pointer(in.CA)) - if in.Vault != nil { - in, out := &in.Vault, &out.Vault - *out = new(certmanager.VaultIssuer) - if err := Convert_v1beta1_VaultIssuer_To_certmanager_VaultIssuer(*in, *out, s); err != nil { - return err - } - } else { - out.Vault = nil - } - out.SelfSigned = (*certmanager.SelfSignedIssuer)(unsafe.Pointer(in.SelfSigned)) - if in.Venafi != nil { - in, out := &in.Venafi, &out.Venafi - *out = new(certmanager.VenafiIssuer) - if err := Convert_v1beta1_VenafiIssuer_To_certmanager_VenafiIssuer(*in, *out, s); err != nil { - return err - } - } else { - out.Venafi = nil - } - return nil -} - -// Convert_v1beta1_IssuerConfig_To_certmanager_IssuerConfig is an autogenerated conversion function. -func Convert_v1beta1_IssuerConfig_To_certmanager_IssuerConfig(in *IssuerConfig, out *certmanager.IssuerConfig, s conversion.Scope) error { - return autoConvert_v1beta1_IssuerConfig_To_certmanager_IssuerConfig(in, out, s) -} - -func autoConvert_certmanager_IssuerConfig_To_v1beta1_IssuerConfig(in *certmanager.IssuerConfig, out *IssuerConfig, s conversion.Scope) error { - if in.ACME != nil { - in, out := &in.ACME, &out.ACME - *out = new(acmev1beta1.ACMEIssuer) - if err := acmev1beta1.Convert_acme_ACMEIssuer_To_v1beta1_ACMEIssuer(*in, *out, s); err != nil { - return err - } - } else { - out.ACME = nil - } - out.CA = (*CAIssuer)(unsafe.Pointer(in.CA)) - if in.Vault != nil { - in, out := &in.Vault, &out.Vault - *out = new(VaultIssuer) - if err := Convert_certmanager_VaultIssuer_To_v1beta1_VaultIssuer(*in, *out, s); err != nil { - return err - } - } else { - out.Vault = nil - } - out.SelfSigned = (*SelfSignedIssuer)(unsafe.Pointer(in.SelfSigned)) - if in.Venafi != nil { - in, out := &in.Venafi, &out.Venafi - *out = new(VenafiIssuer) - if err := Convert_certmanager_VenafiIssuer_To_v1beta1_VenafiIssuer(*in, *out, s); err != nil { - return err - } - } else { - out.Venafi = nil - } - return nil -} - -// Convert_certmanager_IssuerConfig_To_v1beta1_IssuerConfig is an autogenerated conversion function. -func Convert_certmanager_IssuerConfig_To_v1beta1_IssuerConfig(in *certmanager.IssuerConfig, out *IssuerConfig, s conversion.Scope) error { - return autoConvert_certmanager_IssuerConfig_To_v1beta1_IssuerConfig(in, out, s) -} - -func autoConvert_v1beta1_IssuerList_To_certmanager_IssuerList(in *IssuerList, out *certmanager.IssuerList, s conversion.Scope) error { - out.ListMeta = in.ListMeta - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]certmanager.Issuer, len(*in)) - for i := range *in { - if err := Convert_v1beta1_Issuer_To_certmanager_Issuer(&(*in)[i], &(*out)[i], s); err != nil { - return err - } - } - } else { - out.Items = nil - } - return nil -} - -// Convert_v1beta1_IssuerList_To_certmanager_IssuerList is an autogenerated conversion function. -func Convert_v1beta1_IssuerList_To_certmanager_IssuerList(in *IssuerList, out *certmanager.IssuerList, s conversion.Scope) error { - return autoConvert_v1beta1_IssuerList_To_certmanager_IssuerList(in, out, s) -} - -func autoConvert_certmanager_IssuerList_To_v1beta1_IssuerList(in *certmanager.IssuerList, out *IssuerList, s conversion.Scope) error { - out.ListMeta = in.ListMeta - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]Issuer, len(*in)) - for i := range *in { - if err := Convert_certmanager_Issuer_To_v1beta1_Issuer(&(*in)[i], &(*out)[i], s); err != nil { - return err - } - } - } else { - out.Items = nil - } - return nil -} - -// Convert_certmanager_IssuerList_To_v1beta1_IssuerList is an autogenerated conversion function. -func Convert_certmanager_IssuerList_To_v1beta1_IssuerList(in *certmanager.IssuerList, out *IssuerList, s conversion.Scope) error { - return autoConvert_certmanager_IssuerList_To_v1beta1_IssuerList(in, out, s) -} - -func autoConvert_v1beta1_IssuerSpec_To_certmanager_IssuerSpec(in *IssuerSpec, out *certmanager.IssuerSpec, s conversion.Scope) error { - if err := Convert_v1beta1_IssuerConfig_To_certmanager_IssuerConfig(&in.IssuerConfig, &out.IssuerConfig, s); err != nil { - return err - } - return nil -} - -// Convert_v1beta1_IssuerSpec_To_certmanager_IssuerSpec is an autogenerated conversion function. -func Convert_v1beta1_IssuerSpec_To_certmanager_IssuerSpec(in *IssuerSpec, out *certmanager.IssuerSpec, s conversion.Scope) error { - return autoConvert_v1beta1_IssuerSpec_To_certmanager_IssuerSpec(in, out, s) -} - -func autoConvert_certmanager_IssuerSpec_To_v1beta1_IssuerSpec(in *certmanager.IssuerSpec, out *IssuerSpec, s conversion.Scope) error { - if err := Convert_certmanager_IssuerConfig_To_v1beta1_IssuerConfig(&in.IssuerConfig, &out.IssuerConfig, s); err != nil { - return err - } - return nil -} - -// Convert_certmanager_IssuerSpec_To_v1beta1_IssuerSpec is an autogenerated conversion function. -func Convert_certmanager_IssuerSpec_To_v1beta1_IssuerSpec(in *certmanager.IssuerSpec, out *IssuerSpec, s conversion.Scope) error { - return autoConvert_certmanager_IssuerSpec_To_v1beta1_IssuerSpec(in, out, s) -} - -func autoConvert_v1beta1_IssuerStatus_To_certmanager_IssuerStatus(in *IssuerStatus, out *certmanager.IssuerStatus, s conversion.Scope) error { - out.Conditions = *(*[]certmanager.IssuerCondition)(unsafe.Pointer(&in.Conditions)) - out.ACME = (*acme.ACMEIssuerStatus)(unsafe.Pointer(in.ACME)) - return nil -} - -// Convert_v1beta1_IssuerStatus_To_certmanager_IssuerStatus is an autogenerated conversion function. -func Convert_v1beta1_IssuerStatus_To_certmanager_IssuerStatus(in *IssuerStatus, out *certmanager.IssuerStatus, s conversion.Scope) error { - return autoConvert_v1beta1_IssuerStatus_To_certmanager_IssuerStatus(in, out, s) -} - -func autoConvert_certmanager_IssuerStatus_To_v1beta1_IssuerStatus(in *certmanager.IssuerStatus, out *IssuerStatus, s conversion.Scope) error { - out.Conditions = *(*[]IssuerCondition)(unsafe.Pointer(&in.Conditions)) - out.ACME = (*acmev1beta1.ACMEIssuerStatus)(unsafe.Pointer(in.ACME)) - return nil -} - -// Convert_certmanager_IssuerStatus_To_v1beta1_IssuerStatus is an autogenerated conversion function. -func Convert_certmanager_IssuerStatus_To_v1beta1_IssuerStatus(in *certmanager.IssuerStatus, out *IssuerStatus, s conversion.Scope) error { - return autoConvert_certmanager_IssuerStatus_To_v1beta1_IssuerStatus(in, out, s) -} - -func autoConvert_v1beta1_JKSKeystore_To_certmanager_JKSKeystore(in *JKSKeystore, out *certmanager.JKSKeystore, s conversion.Scope) error { - out.Create = in.Create - if err := apismetav1.Convert_v1_SecretKeySelector_To_meta_SecretKeySelector(&in.PasswordSecretRef, &out.PasswordSecretRef, s); err != nil { - return err - } - return nil -} - -// Convert_v1beta1_JKSKeystore_To_certmanager_JKSKeystore is an autogenerated conversion function. -func Convert_v1beta1_JKSKeystore_To_certmanager_JKSKeystore(in *JKSKeystore, out *certmanager.JKSKeystore, s conversion.Scope) error { - return autoConvert_v1beta1_JKSKeystore_To_certmanager_JKSKeystore(in, out, s) -} - -func autoConvert_certmanager_JKSKeystore_To_v1beta1_JKSKeystore(in *certmanager.JKSKeystore, out *JKSKeystore, s conversion.Scope) error { - out.Create = in.Create - if err := apismetav1.Convert_meta_SecretKeySelector_To_v1_SecretKeySelector(&in.PasswordSecretRef, &out.PasswordSecretRef, s); err != nil { - return err - } - return nil -} - -// Convert_certmanager_JKSKeystore_To_v1beta1_JKSKeystore is an autogenerated conversion function. -func Convert_certmanager_JKSKeystore_To_v1beta1_JKSKeystore(in *certmanager.JKSKeystore, out *JKSKeystore, s conversion.Scope) error { - return autoConvert_certmanager_JKSKeystore_To_v1beta1_JKSKeystore(in, out, s) -} - -func autoConvert_v1beta1_PKCS12Keystore_To_certmanager_PKCS12Keystore(in *PKCS12Keystore, out *certmanager.PKCS12Keystore, s conversion.Scope) error { - out.Create = in.Create - if err := apismetav1.Convert_v1_SecretKeySelector_To_meta_SecretKeySelector(&in.PasswordSecretRef, &out.PasswordSecretRef, s); err != nil { - return err - } - return nil -} - -// Convert_v1beta1_PKCS12Keystore_To_certmanager_PKCS12Keystore is an autogenerated conversion function. -func Convert_v1beta1_PKCS12Keystore_To_certmanager_PKCS12Keystore(in *PKCS12Keystore, out *certmanager.PKCS12Keystore, s conversion.Scope) error { - return autoConvert_v1beta1_PKCS12Keystore_To_certmanager_PKCS12Keystore(in, out, s) -} - -func autoConvert_certmanager_PKCS12Keystore_To_v1beta1_PKCS12Keystore(in *certmanager.PKCS12Keystore, out *PKCS12Keystore, s conversion.Scope) error { - out.Create = in.Create - if err := apismetav1.Convert_meta_SecretKeySelector_To_v1_SecretKeySelector(&in.PasswordSecretRef, &out.PasswordSecretRef, s); err != nil { - return err - } - return nil -} - -// Convert_certmanager_PKCS12Keystore_To_v1beta1_PKCS12Keystore is an autogenerated conversion function. -func Convert_certmanager_PKCS12Keystore_To_v1beta1_PKCS12Keystore(in *certmanager.PKCS12Keystore, out *PKCS12Keystore, s conversion.Scope) error { - return autoConvert_certmanager_PKCS12Keystore_To_v1beta1_PKCS12Keystore(in, out, s) -} - -func autoConvert_v1beta1_SelfSignedIssuer_To_certmanager_SelfSignedIssuer(in *SelfSignedIssuer, out *certmanager.SelfSignedIssuer, s conversion.Scope) error { - out.CRLDistributionPoints = *(*[]string)(unsafe.Pointer(&in.CRLDistributionPoints)) - return nil -} - -// Convert_v1beta1_SelfSignedIssuer_To_certmanager_SelfSignedIssuer is an autogenerated conversion function. -func Convert_v1beta1_SelfSignedIssuer_To_certmanager_SelfSignedIssuer(in *SelfSignedIssuer, out *certmanager.SelfSignedIssuer, s conversion.Scope) error { - return autoConvert_v1beta1_SelfSignedIssuer_To_certmanager_SelfSignedIssuer(in, out, s) -} - -func autoConvert_certmanager_SelfSignedIssuer_To_v1beta1_SelfSignedIssuer(in *certmanager.SelfSignedIssuer, out *SelfSignedIssuer, s conversion.Scope) error { - out.CRLDistributionPoints = *(*[]string)(unsafe.Pointer(&in.CRLDistributionPoints)) - return nil -} - -// Convert_certmanager_SelfSignedIssuer_To_v1beta1_SelfSignedIssuer is an autogenerated conversion function. -func Convert_certmanager_SelfSignedIssuer_To_v1beta1_SelfSignedIssuer(in *certmanager.SelfSignedIssuer, out *SelfSignedIssuer, s conversion.Scope) error { - return autoConvert_certmanager_SelfSignedIssuer_To_v1beta1_SelfSignedIssuer(in, out, s) -} - -func autoConvert_v1beta1_VaultAppRole_To_certmanager_VaultAppRole(in *VaultAppRole, out *certmanager.VaultAppRole, s conversion.Scope) error { - out.Path = in.Path - out.RoleId = in.RoleId - if err := apismetav1.Convert_v1_SecretKeySelector_To_meta_SecretKeySelector(&in.SecretRef, &out.SecretRef, s); err != nil { - return err - } - return nil -} - -// Convert_v1beta1_VaultAppRole_To_certmanager_VaultAppRole is an autogenerated conversion function. -func Convert_v1beta1_VaultAppRole_To_certmanager_VaultAppRole(in *VaultAppRole, out *certmanager.VaultAppRole, s conversion.Scope) error { - return autoConvert_v1beta1_VaultAppRole_To_certmanager_VaultAppRole(in, out, s) -} - -func autoConvert_certmanager_VaultAppRole_To_v1beta1_VaultAppRole(in *certmanager.VaultAppRole, out *VaultAppRole, s conversion.Scope) error { - out.Path = in.Path - out.RoleId = in.RoleId - if err := apismetav1.Convert_meta_SecretKeySelector_To_v1_SecretKeySelector(&in.SecretRef, &out.SecretRef, s); err != nil { - return err - } - return nil -} - -// Convert_certmanager_VaultAppRole_To_v1beta1_VaultAppRole is an autogenerated conversion function. -func Convert_certmanager_VaultAppRole_To_v1beta1_VaultAppRole(in *certmanager.VaultAppRole, out *VaultAppRole, s conversion.Scope) error { - return autoConvert_certmanager_VaultAppRole_To_v1beta1_VaultAppRole(in, out, s) -} - -func autoConvert_v1beta1_VaultAuth_To_certmanager_VaultAuth(in *VaultAuth, out *certmanager.VaultAuth, s conversion.Scope) error { - if in.TokenSecretRef != nil { - in, out := &in.TokenSecretRef, &out.TokenSecretRef - *out = new(meta.SecretKeySelector) - if err := apismetav1.Convert_v1_SecretKeySelector_To_meta_SecretKeySelector(*in, *out, s); err != nil { - return err - } - } else { - out.TokenSecretRef = nil - } - if in.AppRole != nil { - in, out := &in.AppRole, &out.AppRole - *out = new(certmanager.VaultAppRole) - if err := Convert_v1beta1_VaultAppRole_To_certmanager_VaultAppRole(*in, *out, s); err != nil { - return err - } - } else { - out.AppRole = nil - } - if in.Kubernetes != nil { - in, out := &in.Kubernetes, &out.Kubernetes - *out = new(certmanager.VaultKubernetesAuth) - if err := Convert_v1beta1_VaultKubernetesAuth_To_certmanager_VaultKubernetesAuth(*in, *out, s); err != nil { - return err - } - } else { - out.Kubernetes = nil - } - return nil -} - -// Convert_v1beta1_VaultAuth_To_certmanager_VaultAuth is an autogenerated conversion function. -func Convert_v1beta1_VaultAuth_To_certmanager_VaultAuth(in *VaultAuth, out *certmanager.VaultAuth, s conversion.Scope) error { - return autoConvert_v1beta1_VaultAuth_To_certmanager_VaultAuth(in, out, s) -} - -func autoConvert_certmanager_VaultAuth_To_v1beta1_VaultAuth(in *certmanager.VaultAuth, out *VaultAuth, s conversion.Scope) error { - if in.TokenSecretRef != nil { - in, out := &in.TokenSecretRef, &out.TokenSecretRef - *out = new(metav1.SecretKeySelector) - if err := apismetav1.Convert_meta_SecretKeySelector_To_v1_SecretKeySelector(*in, *out, s); err != nil { - return err - } - } else { - out.TokenSecretRef = nil - } - if in.AppRole != nil { - in, out := &in.AppRole, &out.AppRole - *out = new(VaultAppRole) - if err := Convert_certmanager_VaultAppRole_To_v1beta1_VaultAppRole(*in, *out, s); err != nil { - return err - } - } else { - out.AppRole = nil - } - if in.Kubernetes != nil { - in, out := &in.Kubernetes, &out.Kubernetes - *out = new(VaultKubernetesAuth) - if err := Convert_certmanager_VaultKubernetesAuth_To_v1beta1_VaultKubernetesAuth(*in, *out, s); err != nil { - return err - } - } else { - out.Kubernetes = nil - } - return nil -} - -// Convert_certmanager_VaultAuth_To_v1beta1_VaultAuth is an autogenerated conversion function. -func Convert_certmanager_VaultAuth_To_v1beta1_VaultAuth(in *certmanager.VaultAuth, out *VaultAuth, s conversion.Scope) error { - return autoConvert_certmanager_VaultAuth_To_v1beta1_VaultAuth(in, out, s) -} - -func autoConvert_v1beta1_VaultIssuer_To_certmanager_VaultIssuer(in *VaultIssuer, out *certmanager.VaultIssuer, s conversion.Scope) error { - if err := Convert_v1beta1_VaultAuth_To_certmanager_VaultAuth(&in.Auth, &out.Auth, s); err != nil { - return err - } - out.Server = in.Server - out.Path = in.Path - out.Namespace = in.Namespace - out.CABundle = *(*[]byte)(unsafe.Pointer(&in.CABundle)) - if in.CABundleSecretRef != nil { - in, out := &in.CABundleSecretRef, &out.CABundleSecretRef - *out = new(meta.SecretKeySelector) - if err := apismetav1.Convert_v1_SecretKeySelector_To_meta_SecretKeySelector(*in, *out, s); err != nil { - return err - } - } else { - out.CABundleSecretRef = nil - } - return nil -} - -// Convert_v1beta1_VaultIssuer_To_certmanager_VaultIssuer is an autogenerated conversion function. -func Convert_v1beta1_VaultIssuer_To_certmanager_VaultIssuer(in *VaultIssuer, out *certmanager.VaultIssuer, s conversion.Scope) error { - return autoConvert_v1beta1_VaultIssuer_To_certmanager_VaultIssuer(in, out, s) -} - -func autoConvert_certmanager_VaultIssuer_To_v1beta1_VaultIssuer(in *certmanager.VaultIssuer, out *VaultIssuer, s conversion.Scope) error { - if err := Convert_certmanager_VaultAuth_To_v1beta1_VaultAuth(&in.Auth, &out.Auth, s); err != nil { - return err - } - out.Server = in.Server - out.Path = in.Path - out.Namespace = in.Namespace - out.CABundle = *(*[]byte)(unsafe.Pointer(&in.CABundle)) - if in.CABundleSecretRef != nil { - in, out := &in.CABundleSecretRef, &out.CABundleSecretRef - *out = new(metav1.SecretKeySelector) - if err := apismetav1.Convert_meta_SecretKeySelector_To_v1_SecretKeySelector(*in, *out, s); err != nil { - return err - } - } else { - out.CABundleSecretRef = nil - } - return nil -} - -// Convert_certmanager_VaultIssuer_To_v1beta1_VaultIssuer is an autogenerated conversion function. -func Convert_certmanager_VaultIssuer_To_v1beta1_VaultIssuer(in *certmanager.VaultIssuer, out *VaultIssuer, s conversion.Scope) error { - return autoConvert_certmanager_VaultIssuer_To_v1beta1_VaultIssuer(in, out, s) -} - -func autoConvert_v1beta1_VaultKubernetesAuth_To_certmanager_VaultKubernetesAuth(in *VaultKubernetesAuth, out *certmanager.VaultKubernetesAuth, s conversion.Scope) error { - out.Path = in.Path - if err := apismetav1.Convert_v1_SecretKeySelector_To_meta_SecretKeySelector(&in.SecretRef, &out.SecretRef, s); err != nil { - return err - } - out.Role = in.Role - return nil -} - -// Convert_v1beta1_VaultKubernetesAuth_To_certmanager_VaultKubernetesAuth is an autogenerated conversion function. -func Convert_v1beta1_VaultKubernetesAuth_To_certmanager_VaultKubernetesAuth(in *VaultKubernetesAuth, out *certmanager.VaultKubernetesAuth, s conversion.Scope) error { - return autoConvert_v1beta1_VaultKubernetesAuth_To_certmanager_VaultKubernetesAuth(in, out, s) -} - -func autoConvert_certmanager_VaultKubernetesAuth_To_v1beta1_VaultKubernetesAuth(in *certmanager.VaultKubernetesAuth, out *VaultKubernetesAuth, s conversion.Scope) error { - out.Path = in.Path - if err := apismetav1.Convert_meta_SecretKeySelector_To_v1_SecretKeySelector(&in.SecretRef, &out.SecretRef, s); err != nil { - return err - } - out.Role = in.Role - return nil -} - -// Convert_certmanager_VaultKubernetesAuth_To_v1beta1_VaultKubernetesAuth is an autogenerated conversion function. -func Convert_certmanager_VaultKubernetesAuth_To_v1beta1_VaultKubernetesAuth(in *certmanager.VaultKubernetesAuth, out *VaultKubernetesAuth, s conversion.Scope) error { - return autoConvert_certmanager_VaultKubernetesAuth_To_v1beta1_VaultKubernetesAuth(in, out, s) -} - -func autoConvert_v1beta1_VenafiCloud_To_certmanager_VenafiCloud(in *VenafiCloud, out *certmanager.VenafiCloud, s conversion.Scope) error { - out.URL = in.URL - if err := apismetav1.Convert_v1_SecretKeySelector_To_meta_SecretKeySelector(&in.APITokenSecretRef, &out.APITokenSecretRef, s); err != nil { - return err - } - return nil -} - -// Convert_v1beta1_VenafiCloud_To_certmanager_VenafiCloud is an autogenerated conversion function. -func Convert_v1beta1_VenafiCloud_To_certmanager_VenafiCloud(in *VenafiCloud, out *certmanager.VenafiCloud, s conversion.Scope) error { - return autoConvert_v1beta1_VenafiCloud_To_certmanager_VenafiCloud(in, out, s) -} - -func autoConvert_certmanager_VenafiCloud_To_v1beta1_VenafiCloud(in *certmanager.VenafiCloud, out *VenafiCloud, s conversion.Scope) error { - out.URL = in.URL - if err := apismetav1.Convert_meta_SecretKeySelector_To_v1_SecretKeySelector(&in.APITokenSecretRef, &out.APITokenSecretRef, s); err != nil { - return err - } - return nil -} - -// Convert_certmanager_VenafiCloud_To_v1beta1_VenafiCloud is an autogenerated conversion function. -func Convert_certmanager_VenafiCloud_To_v1beta1_VenafiCloud(in *certmanager.VenafiCloud, out *VenafiCloud, s conversion.Scope) error { - return autoConvert_certmanager_VenafiCloud_To_v1beta1_VenafiCloud(in, out, s) -} - -func autoConvert_v1beta1_VenafiIssuer_To_certmanager_VenafiIssuer(in *VenafiIssuer, out *certmanager.VenafiIssuer, s conversion.Scope) error { - out.Zone = in.Zone - if in.TPP != nil { - in, out := &in.TPP, &out.TPP - *out = new(certmanager.VenafiTPP) - if err := Convert_v1beta1_VenafiTPP_To_certmanager_VenafiTPP(*in, *out, s); err != nil { - return err - } - } else { - out.TPP = nil - } - if in.Cloud != nil { - in, out := &in.Cloud, &out.Cloud - *out = new(certmanager.VenafiCloud) - if err := Convert_v1beta1_VenafiCloud_To_certmanager_VenafiCloud(*in, *out, s); err != nil { - return err - } - } else { - out.Cloud = nil - } - return nil -} - -// Convert_v1beta1_VenafiIssuer_To_certmanager_VenafiIssuer is an autogenerated conversion function. -func Convert_v1beta1_VenafiIssuer_To_certmanager_VenafiIssuer(in *VenafiIssuer, out *certmanager.VenafiIssuer, s conversion.Scope) error { - return autoConvert_v1beta1_VenafiIssuer_To_certmanager_VenafiIssuer(in, out, s) -} - -func autoConvert_certmanager_VenafiIssuer_To_v1beta1_VenafiIssuer(in *certmanager.VenafiIssuer, out *VenafiIssuer, s conversion.Scope) error { - out.Zone = in.Zone - if in.TPP != nil { - in, out := &in.TPP, &out.TPP - *out = new(VenafiTPP) - if err := Convert_certmanager_VenafiTPP_To_v1beta1_VenafiTPP(*in, *out, s); err != nil { - return err - } - } else { - out.TPP = nil - } - if in.Cloud != nil { - in, out := &in.Cloud, &out.Cloud - *out = new(VenafiCloud) - if err := Convert_certmanager_VenafiCloud_To_v1beta1_VenafiCloud(*in, *out, s); err != nil { - return err - } - } else { - out.Cloud = nil - } - return nil -} - -// Convert_certmanager_VenafiIssuer_To_v1beta1_VenafiIssuer is an autogenerated conversion function. -func Convert_certmanager_VenafiIssuer_To_v1beta1_VenafiIssuer(in *certmanager.VenafiIssuer, out *VenafiIssuer, s conversion.Scope) error { - return autoConvert_certmanager_VenafiIssuer_To_v1beta1_VenafiIssuer(in, out, s) -} - -func autoConvert_v1beta1_VenafiTPP_To_certmanager_VenafiTPP(in *VenafiTPP, out *certmanager.VenafiTPP, s conversion.Scope) error { - out.URL = in.URL - if err := apismetav1.Convert_v1_LocalObjectReference_To_meta_LocalObjectReference(&in.CredentialsRef, &out.CredentialsRef, s); err != nil { - return err - } - out.CABundle = *(*[]byte)(unsafe.Pointer(&in.CABundle)) - return nil -} - -// Convert_v1beta1_VenafiTPP_To_certmanager_VenafiTPP is an autogenerated conversion function. -func Convert_v1beta1_VenafiTPP_To_certmanager_VenafiTPP(in *VenafiTPP, out *certmanager.VenafiTPP, s conversion.Scope) error { - return autoConvert_v1beta1_VenafiTPP_To_certmanager_VenafiTPP(in, out, s) -} - -func autoConvert_certmanager_VenafiTPP_To_v1beta1_VenafiTPP(in *certmanager.VenafiTPP, out *VenafiTPP, s conversion.Scope) error { - out.URL = in.URL - if err := apismetav1.Convert_meta_LocalObjectReference_To_v1_LocalObjectReference(&in.CredentialsRef, &out.CredentialsRef, s); err != nil { - return err - } - out.CABundle = *(*[]byte)(unsafe.Pointer(&in.CABundle)) - return nil -} - -// Convert_certmanager_VenafiTPP_To_v1beta1_VenafiTPP is an autogenerated conversion function. -func Convert_certmanager_VenafiTPP_To_v1beta1_VenafiTPP(in *certmanager.VenafiTPP, out *VenafiTPP, s conversion.Scope) error { - return autoConvert_certmanager_VenafiTPP_To_v1beta1_VenafiTPP(in, out, s) -} - -func autoConvert_v1beta1_X509Subject_To_certmanager_X509Subject(in *X509Subject, out *certmanager.X509Subject, s conversion.Scope) error { - out.Organizations = *(*[]string)(unsafe.Pointer(&in.Organizations)) - out.Countries = *(*[]string)(unsafe.Pointer(&in.Countries)) - out.OrganizationalUnits = *(*[]string)(unsafe.Pointer(&in.OrganizationalUnits)) - out.Localities = *(*[]string)(unsafe.Pointer(&in.Localities)) - out.Provinces = *(*[]string)(unsafe.Pointer(&in.Provinces)) - out.StreetAddresses = *(*[]string)(unsafe.Pointer(&in.StreetAddresses)) - out.PostalCodes = *(*[]string)(unsafe.Pointer(&in.PostalCodes)) - out.SerialNumber = in.SerialNumber - return nil -} - -// Convert_v1beta1_X509Subject_To_certmanager_X509Subject is an autogenerated conversion function. -func Convert_v1beta1_X509Subject_To_certmanager_X509Subject(in *X509Subject, out *certmanager.X509Subject, s conversion.Scope) error { - return autoConvert_v1beta1_X509Subject_To_certmanager_X509Subject(in, out, s) -} - -func autoConvert_certmanager_X509Subject_To_v1beta1_X509Subject(in *certmanager.X509Subject, out *X509Subject, s conversion.Scope) error { - out.Organizations = *(*[]string)(unsafe.Pointer(&in.Organizations)) - out.Countries = *(*[]string)(unsafe.Pointer(&in.Countries)) - out.OrganizationalUnits = *(*[]string)(unsafe.Pointer(&in.OrganizationalUnits)) - out.Localities = *(*[]string)(unsafe.Pointer(&in.Localities)) - out.Provinces = *(*[]string)(unsafe.Pointer(&in.Provinces)) - out.StreetAddresses = *(*[]string)(unsafe.Pointer(&in.StreetAddresses)) - out.PostalCodes = *(*[]string)(unsafe.Pointer(&in.PostalCodes)) - out.SerialNumber = in.SerialNumber - return nil -} - -// Convert_certmanager_X509Subject_To_v1beta1_X509Subject is an autogenerated conversion function. -func Convert_certmanager_X509Subject_To_v1beta1_X509Subject(in *certmanager.X509Subject, out *X509Subject, s conversion.Scope) error { - return autoConvert_certmanager_X509Subject_To_v1beta1_X509Subject(in, out, s) -} diff --git a/internal/apis/certmanager/v1beta1/zz_generated.deepcopy.go b/internal/apis/certmanager/v1beta1/zz_generated.deepcopy.go deleted file mode 100644 index 9eeea27d95b..00000000000 --- a/internal/apis/certmanager/v1beta1/zz_generated.deepcopy.go +++ /dev/null @@ -1,1026 +0,0 @@ -//go:build !ignore_autogenerated -// +build !ignore_autogenerated - -/* -Copyright 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. -*/ - -// Code generated by deepcopy-gen. DO NOT EDIT. - -package v1beta1 - -import ( - acmev1beta1 "github.com/cert-manager/cert-manager/internal/apis/acme/v1beta1" - metav1 "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" - runtime "k8s.io/apimachinery/pkg/runtime" -) - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *CAIssuer) DeepCopyInto(out *CAIssuer) { - *out = *in - if in.CRLDistributionPoints != nil { - in, out := &in.CRLDistributionPoints, &out.CRLDistributionPoints - *out = make([]string, len(*in)) - copy(*out, *in) - } - if in.OCSPServers != nil { - in, out := &in.OCSPServers, &out.OCSPServers - *out = make([]string, len(*in)) - copy(*out, *in) - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CAIssuer. -func (in *CAIssuer) DeepCopy() *CAIssuer { - if in == nil { - return nil - } - out := new(CAIssuer) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *Certificate) DeepCopyInto(out *Certificate) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - in.Spec.DeepCopyInto(&out.Spec) - in.Status.DeepCopyInto(&out.Status) - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Certificate. -func (in *Certificate) DeepCopy() *Certificate { - if in == nil { - return nil - } - out := new(Certificate) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *Certificate) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *CertificateAdditionalOutputFormat) DeepCopyInto(out *CertificateAdditionalOutputFormat) { - *out = *in - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CertificateAdditionalOutputFormat. -func (in *CertificateAdditionalOutputFormat) DeepCopy() *CertificateAdditionalOutputFormat { - if in == nil { - return nil - } - out := new(CertificateAdditionalOutputFormat) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *CertificateCondition) DeepCopyInto(out *CertificateCondition) { - *out = *in - if in.LastTransitionTime != nil { - in, out := &in.LastTransitionTime, &out.LastTransitionTime - *out = (*in).DeepCopy() - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CertificateCondition. -func (in *CertificateCondition) DeepCopy() *CertificateCondition { - if in == nil { - return nil - } - out := new(CertificateCondition) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *CertificateKeystores) DeepCopyInto(out *CertificateKeystores) { - *out = *in - if in.JKS != nil { - in, out := &in.JKS, &out.JKS - *out = new(JKSKeystore) - **out = **in - } - if in.PKCS12 != nil { - in, out := &in.PKCS12, &out.PKCS12 - *out = new(PKCS12Keystore) - **out = **in - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CertificateKeystores. -func (in *CertificateKeystores) DeepCopy() *CertificateKeystores { - if in == nil { - return nil - } - out := new(CertificateKeystores) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *CertificateList) DeepCopyInto(out *CertificateList) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ListMeta.DeepCopyInto(&out.ListMeta) - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]Certificate, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CertificateList. -func (in *CertificateList) DeepCopy() *CertificateList { - if in == nil { - return nil - } - out := new(CertificateList) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *CertificateList) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *CertificatePrivateKey) DeepCopyInto(out *CertificatePrivateKey) { - *out = *in - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CertificatePrivateKey. -func (in *CertificatePrivateKey) DeepCopy() *CertificatePrivateKey { - if in == nil { - return nil - } - out := new(CertificatePrivateKey) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *CertificateRequest) DeepCopyInto(out *CertificateRequest) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - in.Spec.DeepCopyInto(&out.Spec) - in.Status.DeepCopyInto(&out.Status) - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CertificateRequest. -func (in *CertificateRequest) DeepCopy() *CertificateRequest { - if in == nil { - return nil - } - out := new(CertificateRequest) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *CertificateRequest) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *CertificateRequestCondition) DeepCopyInto(out *CertificateRequestCondition) { - *out = *in - if in.LastTransitionTime != nil { - in, out := &in.LastTransitionTime, &out.LastTransitionTime - *out = (*in).DeepCopy() - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CertificateRequestCondition. -func (in *CertificateRequestCondition) DeepCopy() *CertificateRequestCondition { - if in == nil { - return nil - } - out := new(CertificateRequestCondition) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *CertificateRequestList) DeepCopyInto(out *CertificateRequestList) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ListMeta.DeepCopyInto(&out.ListMeta) - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]CertificateRequest, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CertificateRequestList. -func (in *CertificateRequestList) DeepCopy() *CertificateRequestList { - if in == nil { - return nil - } - out := new(CertificateRequestList) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *CertificateRequestList) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *CertificateRequestSpec) DeepCopyInto(out *CertificateRequestSpec) { - *out = *in - if in.Duration != nil { - in, out := &in.Duration, &out.Duration - *out = new(v1.Duration) - **out = **in - } - out.IssuerRef = in.IssuerRef - if in.Request != nil { - in, out := &in.Request, &out.Request - *out = make([]byte, len(*in)) - copy(*out, *in) - } - if in.Usages != nil { - in, out := &in.Usages, &out.Usages - *out = make([]KeyUsage, len(*in)) - copy(*out, *in) - } - if in.Groups != nil { - in, out := &in.Groups, &out.Groups - *out = make([]string, len(*in)) - copy(*out, *in) - } - if in.Extra != nil { - in, out := &in.Extra, &out.Extra - *out = make(map[string][]string, len(*in)) - for key, val := range *in { - var outVal []string - if val == nil { - (*out)[key] = nil - } else { - in, out := &val, &outVal - *out = make([]string, len(*in)) - copy(*out, *in) - } - (*out)[key] = outVal - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CertificateRequestSpec. -func (in *CertificateRequestSpec) DeepCopy() *CertificateRequestSpec { - if in == nil { - return nil - } - out := new(CertificateRequestSpec) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *CertificateRequestStatus) DeepCopyInto(out *CertificateRequestStatus) { - *out = *in - if in.Conditions != nil { - in, out := &in.Conditions, &out.Conditions - *out = make([]CertificateRequestCondition, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - if in.Certificate != nil { - in, out := &in.Certificate, &out.Certificate - *out = make([]byte, len(*in)) - copy(*out, *in) - } - if in.CA != nil { - in, out := &in.CA, &out.CA - *out = make([]byte, len(*in)) - copy(*out, *in) - } - if in.FailureTime != nil { - in, out := &in.FailureTime, &out.FailureTime - *out = (*in).DeepCopy() - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CertificateRequestStatus. -func (in *CertificateRequestStatus) DeepCopy() *CertificateRequestStatus { - if in == nil { - return nil - } - out := new(CertificateRequestStatus) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *CertificateSecretTemplate) DeepCopyInto(out *CertificateSecretTemplate) { - *out = *in - if in.Annotations != nil { - in, out := &in.Annotations, &out.Annotations - *out = make(map[string]string, len(*in)) - for key, val := range *in { - (*out)[key] = val - } - } - if in.Labels != nil { - in, out := &in.Labels, &out.Labels - *out = make(map[string]string, len(*in)) - for key, val := range *in { - (*out)[key] = val - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CertificateSecretTemplate. -func (in *CertificateSecretTemplate) DeepCopy() *CertificateSecretTemplate { - if in == nil { - return nil - } - out := new(CertificateSecretTemplate) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *CertificateSpec) DeepCopyInto(out *CertificateSpec) { - *out = *in - if in.Subject != nil { - in, out := &in.Subject, &out.Subject - *out = new(X509Subject) - (*in).DeepCopyInto(*out) - } - if in.Duration != nil { - in, out := &in.Duration, &out.Duration - *out = new(v1.Duration) - **out = **in - } - if in.RenewBefore != nil { - in, out := &in.RenewBefore, &out.RenewBefore - *out = new(v1.Duration) - **out = **in - } - if in.DNSNames != nil { - in, out := &in.DNSNames, &out.DNSNames - *out = make([]string, len(*in)) - copy(*out, *in) - } - if in.IPAddresses != nil { - in, out := &in.IPAddresses, &out.IPAddresses - *out = make([]string, len(*in)) - copy(*out, *in) - } - if in.URISANs != nil { - in, out := &in.URISANs, &out.URISANs - *out = make([]string, len(*in)) - copy(*out, *in) - } - if in.EmailSANs != nil { - in, out := &in.EmailSANs, &out.EmailSANs - *out = make([]string, len(*in)) - copy(*out, *in) - } - if in.SecretTemplate != nil { - in, out := &in.SecretTemplate, &out.SecretTemplate - *out = new(CertificateSecretTemplate) - (*in).DeepCopyInto(*out) - } - if in.Keystores != nil { - in, out := &in.Keystores, &out.Keystores - *out = new(CertificateKeystores) - (*in).DeepCopyInto(*out) - } - out.IssuerRef = in.IssuerRef - if in.Usages != nil { - in, out := &in.Usages, &out.Usages - *out = make([]KeyUsage, len(*in)) - copy(*out, *in) - } - if in.PrivateKey != nil { - in, out := &in.PrivateKey, &out.PrivateKey - *out = new(CertificatePrivateKey) - **out = **in - } - if in.EncodeUsagesInRequest != nil { - in, out := &in.EncodeUsagesInRequest, &out.EncodeUsagesInRequest - *out = new(bool) - **out = **in - } - if in.RevisionHistoryLimit != nil { - in, out := &in.RevisionHistoryLimit, &out.RevisionHistoryLimit - *out = new(int32) - **out = **in - } - if in.AdditionalOutputFormats != nil { - in, out := &in.AdditionalOutputFormats, &out.AdditionalOutputFormats - *out = make([]CertificateAdditionalOutputFormat, len(*in)) - copy(*out, *in) - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CertificateSpec. -func (in *CertificateSpec) DeepCopy() *CertificateSpec { - if in == nil { - return nil - } - out := new(CertificateSpec) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *CertificateStatus) DeepCopyInto(out *CertificateStatus) { - *out = *in - if in.Conditions != nil { - in, out := &in.Conditions, &out.Conditions - *out = make([]CertificateCondition, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - if in.LastFailureTime != nil { - in, out := &in.LastFailureTime, &out.LastFailureTime - *out = (*in).DeepCopy() - } - if in.NotBefore != nil { - in, out := &in.NotBefore, &out.NotBefore - *out = (*in).DeepCopy() - } - if in.NotAfter != nil { - in, out := &in.NotAfter, &out.NotAfter - *out = (*in).DeepCopy() - } - if in.RenewalTime != nil { - in, out := &in.RenewalTime, &out.RenewalTime - *out = (*in).DeepCopy() - } - if in.Revision != nil { - in, out := &in.Revision, &out.Revision - *out = new(int) - **out = **in - } - if in.NextPrivateKeySecretName != nil { - in, out := &in.NextPrivateKeySecretName, &out.NextPrivateKeySecretName - *out = new(string) - **out = **in - } - if in.FailedIssuanceAttempts != nil { - in, out := &in.FailedIssuanceAttempts, &out.FailedIssuanceAttempts - *out = new(int) - **out = **in - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CertificateStatus. -func (in *CertificateStatus) DeepCopy() *CertificateStatus { - if in == nil { - return nil - } - out := new(CertificateStatus) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ClusterIssuer) DeepCopyInto(out *ClusterIssuer) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - in.Spec.DeepCopyInto(&out.Spec) - in.Status.DeepCopyInto(&out.Status) - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterIssuer. -func (in *ClusterIssuer) DeepCopy() *ClusterIssuer { - if in == nil { - return nil - } - out := new(ClusterIssuer) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *ClusterIssuer) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ClusterIssuerList) DeepCopyInto(out *ClusterIssuerList) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ListMeta.DeepCopyInto(&out.ListMeta) - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]ClusterIssuer, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterIssuerList. -func (in *ClusterIssuerList) DeepCopy() *ClusterIssuerList { - if in == nil { - return nil - } - out := new(ClusterIssuerList) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *ClusterIssuerList) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *Issuer) DeepCopyInto(out *Issuer) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - in.Spec.DeepCopyInto(&out.Spec) - in.Status.DeepCopyInto(&out.Status) - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Issuer. -func (in *Issuer) DeepCopy() *Issuer { - if in == nil { - return nil - } - out := new(Issuer) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *Issuer) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *IssuerCondition) DeepCopyInto(out *IssuerCondition) { - *out = *in - if in.LastTransitionTime != nil { - in, out := &in.LastTransitionTime, &out.LastTransitionTime - *out = (*in).DeepCopy() - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new IssuerCondition. -func (in *IssuerCondition) DeepCopy() *IssuerCondition { - if in == nil { - return nil - } - out := new(IssuerCondition) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *IssuerConfig) DeepCopyInto(out *IssuerConfig) { - *out = *in - if in.ACME != nil { - in, out := &in.ACME, &out.ACME - *out = new(acmev1beta1.ACMEIssuer) - (*in).DeepCopyInto(*out) - } - if in.CA != nil { - in, out := &in.CA, &out.CA - *out = new(CAIssuer) - (*in).DeepCopyInto(*out) - } - if in.Vault != nil { - in, out := &in.Vault, &out.Vault - *out = new(VaultIssuer) - (*in).DeepCopyInto(*out) - } - if in.SelfSigned != nil { - in, out := &in.SelfSigned, &out.SelfSigned - *out = new(SelfSignedIssuer) - (*in).DeepCopyInto(*out) - } - if in.Venafi != nil { - in, out := &in.Venafi, &out.Venafi - *out = new(VenafiIssuer) - (*in).DeepCopyInto(*out) - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new IssuerConfig. -func (in *IssuerConfig) DeepCopy() *IssuerConfig { - if in == nil { - return nil - } - out := new(IssuerConfig) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *IssuerList) DeepCopyInto(out *IssuerList) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ListMeta.DeepCopyInto(&out.ListMeta) - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]Issuer, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new IssuerList. -func (in *IssuerList) DeepCopy() *IssuerList { - if in == nil { - return nil - } - out := new(IssuerList) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *IssuerList) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *IssuerSpec) DeepCopyInto(out *IssuerSpec) { - *out = *in - in.IssuerConfig.DeepCopyInto(&out.IssuerConfig) - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new IssuerSpec. -func (in *IssuerSpec) DeepCopy() *IssuerSpec { - if in == nil { - return nil - } - out := new(IssuerSpec) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *IssuerStatus) DeepCopyInto(out *IssuerStatus) { - *out = *in - if in.Conditions != nil { - in, out := &in.Conditions, &out.Conditions - *out = make([]IssuerCondition, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - if in.ACME != nil { - in, out := &in.ACME, &out.ACME - *out = new(acmev1beta1.ACMEIssuerStatus) - **out = **in - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new IssuerStatus. -func (in *IssuerStatus) DeepCopy() *IssuerStatus { - if in == nil { - return nil - } - out := new(IssuerStatus) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *JKSKeystore) DeepCopyInto(out *JKSKeystore) { - *out = *in - out.PasswordSecretRef = in.PasswordSecretRef - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new JKSKeystore. -func (in *JKSKeystore) DeepCopy() *JKSKeystore { - if in == nil { - return nil - } - out := new(JKSKeystore) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *PKCS12Keystore) DeepCopyInto(out *PKCS12Keystore) { - *out = *in - out.PasswordSecretRef = in.PasswordSecretRef - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PKCS12Keystore. -func (in *PKCS12Keystore) DeepCopy() *PKCS12Keystore { - if in == nil { - return nil - } - out := new(PKCS12Keystore) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *SelfSignedIssuer) DeepCopyInto(out *SelfSignedIssuer) { - *out = *in - if in.CRLDistributionPoints != nil { - in, out := &in.CRLDistributionPoints, &out.CRLDistributionPoints - *out = make([]string, len(*in)) - copy(*out, *in) - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SelfSignedIssuer. -func (in *SelfSignedIssuer) DeepCopy() *SelfSignedIssuer { - if in == nil { - return nil - } - out := new(SelfSignedIssuer) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *VaultAppRole) DeepCopyInto(out *VaultAppRole) { - *out = *in - out.SecretRef = in.SecretRef - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VaultAppRole. -func (in *VaultAppRole) DeepCopy() *VaultAppRole { - if in == nil { - return nil - } - out := new(VaultAppRole) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *VaultAuth) DeepCopyInto(out *VaultAuth) { - *out = *in - if in.TokenSecretRef != nil { - in, out := &in.TokenSecretRef, &out.TokenSecretRef - *out = new(metav1.SecretKeySelector) - **out = **in - } - if in.AppRole != nil { - in, out := &in.AppRole, &out.AppRole - *out = new(VaultAppRole) - **out = **in - } - if in.Kubernetes != nil { - in, out := &in.Kubernetes, &out.Kubernetes - *out = new(VaultKubernetesAuth) - **out = **in - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VaultAuth. -func (in *VaultAuth) DeepCopy() *VaultAuth { - if in == nil { - return nil - } - out := new(VaultAuth) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *VaultIssuer) DeepCopyInto(out *VaultIssuer) { - *out = *in - in.Auth.DeepCopyInto(&out.Auth) - if in.CABundle != nil { - in, out := &in.CABundle, &out.CABundle - *out = make([]byte, len(*in)) - copy(*out, *in) - } - if in.CABundleSecretRef != nil { - in, out := &in.CABundleSecretRef, &out.CABundleSecretRef - *out = new(metav1.SecretKeySelector) - **out = **in - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VaultIssuer. -func (in *VaultIssuer) DeepCopy() *VaultIssuer { - if in == nil { - return nil - } - out := new(VaultIssuer) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *VaultKubernetesAuth) DeepCopyInto(out *VaultKubernetesAuth) { - *out = *in - out.SecretRef = in.SecretRef - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VaultKubernetesAuth. -func (in *VaultKubernetesAuth) DeepCopy() *VaultKubernetesAuth { - if in == nil { - return nil - } - out := new(VaultKubernetesAuth) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *VenafiCloud) DeepCopyInto(out *VenafiCloud) { - *out = *in - out.APITokenSecretRef = in.APITokenSecretRef - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VenafiCloud. -func (in *VenafiCloud) DeepCopy() *VenafiCloud { - if in == nil { - return nil - } - out := new(VenafiCloud) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *VenafiIssuer) DeepCopyInto(out *VenafiIssuer) { - *out = *in - if in.TPP != nil { - in, out := &in.TPP, &out.TPP - *out = new(VenafiTPP) - (*in).DeepCopyInto(*out) - } - if in.Cloud != nil { - in, out := &in.Cloud, &out.Cloud - *out = new(VenafiCloud) - **out = **in - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VenafiIssuer. -func (in *VenafiIssuer) DeepCopy() *VenafiIssuer { - if in == nil { - return nil - } - out := new(VenafiIssuer) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *VenafiTPP) DeepCopyInto(out *VenafiTPP) { - *out = *in - out.CredentialsRef = in.CredentialsRef - if in.CABundle != nil { - in, out := &in.CABundle, &out.CABundle - *out = make([]byte, len(*in)) - copy(*out, *in) - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VenafiTPP. -func (in *VenafiTPP) DeepCopy() *VenafiTPP { - if in == nil { - return nil - } - out := new(VenafiTPP) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *X509Subject) DeepCopyInto(out *X509Subject) { - *out = *in - if in.Organizations != nil { - in, out := &in.Organizations, &out.Organizations - *out = make([]string, len(*in)) - copy(*out, *in) - } - if in.Countries != nil { - in, out := &in.Countries, &out.Countries - *out = make([]string, len(*in)) - copy(*out, *in) - } - if in.OrganizationalUnits != nil { - in, out := &in.OrganizationalUnits, &out.OrganizationalUnits - *out = make([]string, len(*in)) - copy(*out, *in) - } - if in.Localities != nil { - in, out := &in.Localities, &out.Localities - *out = make([]string, len(*in)) - copy(*out, *in) - } - if in.Provinces != nil { - in, out := &in.Provinces, &out.Provinces - *out = make([]string, len(*in)) - copy(*out, *in) - } - if in.StreetAddresses != nil { - in, out := &in.StreetAddresses, &out.StreetAddresses - *out = make([]string, len(*in)) - copy(*out, *in) - } - if in.PostalCodes != nil { - in, out := &in.PostalCodes, &out.PostalCodes - *out = make([]string, len(*in)) - copy(*out, *in) - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new X509Subject. -func (in *X509Subject) DeepCopy() *X509Subject { - if in == nil { - return nil - } - out := new(X509Subject) - in.DeepCopyInto(out) - return out -} diff --git a/internal/apis/certmanager/v1beta1/zz_generated.defaults.go b/internal/apis/certmanager/v1beta1/zz_generated.defaults.go deleted file mode 100644 index 176b36f98d6..00000000000 --- a/internal/apis/certmanager/v1beta1/zz_generated.defaults.go +++ /dev/null @@ -1,33 +0,0 @@ -//go:build !ignore_autogenerated -// +build !ignore_autogenerated - -/* -Copyright 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. -*/ - -// Code generated by defaulter-gen. DO NOT EDIT. - -package v1beta1 - -import ( - runtime "k8s.io/apimachinery/pkg/runtime" -) - -// RegisterDefaults adds defaulters functions to the given scheme. -// Public to allow building arbitrary schemes. -// All generated defaulters are covering - they call all nested defaulters. -func RegisterDefaults(scheme *runtime.Scheme) error { - return nil -} diff --git a/internal/apis/certmanager/validation/certificate.go b/internal/apis/certmanager/validation/certificate.go index 0a55386bba4..ea0e408d85f 100644 --- a/internal/apis/certmanager/validation/certificate.go +++ b/internal/apis/certmanager/validation/certificate.go @@ -20,7 +20,10 @@ import ( "fmt" "net" "net/mail" + "slices" "strings" + "time" + "unicode/utf8" admissionv1 "k8s.io/api/admission/v1" apivalidation "k8s.io/apimachinery/pkg/api/validation" @@ -38,24 +41,59 @@ import ( "github.com/cert-manager/cert-manager/pkg/util/pki" ) +// mapping for key algorithm to allowed signature algorithms +var keyAlgToAllowedSigAlgs = map[internalcmapi.PrivateKeyAlgorithm][]internalcmapi.SignatureAlgorithm{ + internalcmapi.RSAKeyAlgorithm: { + internalcmapi.SHA256WithRSA, + internalcmapi.SHA384WithRSA, + internalcmapi.SHA512WithRSA, + }, + internalcmapi.ECDSAKeyAlgorithm: { + internalcmapi.ECDSAWithSHA256, + internalcmapi.ECDSAWithSHA384, + internalcmapi.ECDSAWithSHA512, + }, + internalcmapi.Ed25519KeyAlgorithm: { + internalcmapi.PureEd25519, + }, +} + // Validation functions for cert-manager Certificate types func ValidateCertificateSpec(crt *internalcmapi.CertificateSpec, fldPath *field.Path) field.ErrorList { el := field.ErrorList{} if crt.SecretName == "" { el = append(el, field.Required(fldPath.Child("secretName"), "must be specified")) + } else { + for _, msg := range apivalidation.NameIsDNSSubdomain(crt.SecretName, false) { + el = append(el, field.Invalid(fldPath.Child("secretName"), crt.SecretName, msg)) + } } el = append(el, validateIssuerRef(crt.IssuerRef, fldPath)...) var commonName = crt.CommonName if crt.LiteralSubject != "" { - if !utilfeature.DefaultFeatureGate.Enabled(feature.LiteralCertificateSubject) { el = append(el, field.Forbidden(fldPath.Child("literalSubject"), "Feature gate LiteralCertificateSubject must be enabled on both webhook and controller to use the alpha `literalSubject` field")) } - sequence, err := pki.ParseSubjectStringToRdnSequence(crt.LiteralSubject) + if len(commonName) != 0 { + el = append(el, field.Invalid(fldPath.Child("commonName"), commonName, "When providing a `LiteralSubject` no `commonName` may be provided.")) + } + + if crt.Subject != nil && (len(crt.Subject.Organizations) > 0 || + len(crt.Subject.Countries) > 0 || + len(crt.Subject.OrganizationalUnits) > 0 || + len(crt.Subject.Localities) > 0 || + len(crt.Subject.Provinces) > 0 || + len(crt.Subject.StreetAddresses) > 0 || + len(crt.Subject.PostalCodes) > 0 || + len(crt.Subject.SerialNumber) > 0) { + el = append(el, field.Invalid(fldPath.Child("subject"), crt.Subject, "When providing a `LiteralSubject` no `Subject` properties may be provided.")) + } + + sequence, err := pki.UnmarshalSubjectStringToRDNSequence(crt.LiteralSubject) if err != nil { el = append(el, field.Invalid(fldPath.Child("literalSubject"), crt.LiteralSubject, err.Error())) } @@ -81,39 +119,55 @@ func ValidateCertificateSpec(crt *internalcmapi.CertificateSpec, fldPath *field. } } } - - if len(crt.CommonName) != 0 { - el = append(el, field.Invalid(fldPath.Child("commonName"), crt.CommonName, "When providing a `LiteralSubject` no `commonName` may be provided.")) - } - - if crt.Subject != nil && len(crt.Subject.Organizations)+len(crt.Subject.Countries)+len(crt.Subject.OrganizationalUnits)+len(crt.Subject.Localities)+len(crt.Subject.Provinces)+len(crt.Subject.StreetAddresses)+len(crt.Subject.PostalCodes) != 0 { - el = append(el, field.Invalid(fldPath.Child("subject"), crt.Subject, "When providing a `LiteralSubject` no `Subject` properties may be provided with the exception of `Subject.serialNumber`")) - } - } - if len(commonName) == 0 && len(crt.DNSNames) == 0 && len(crt.URISANs) == 0 && len(crt.EmailSANs) == 0 && len(crt.IPAddresses) == 0 { - el = append(el, field.Invalid(fldPath, "", "at least one of commonName, dnsNames, uris ipAddresses, or emailAddresses must be set")) + if len(commonName) == 0 && + len(crt.DNSNames) == 0 && + len(crt.URIs) == 0 && + len(crt.EmailAddresses) == 0 && + len(crt.IPAddresses) == 0 && + len(crt.OtherNames) == 0 { + el = append(el, field.Invalid(fldPath, "", "at least one of commonName (from the commonName field or from a literalSubject), dnsNames, uriSANs, ipAddresses, emailSANs or otherNames must be set")) } // if a common name has been specified, ensure it is no longer than 64 chars if len(commonName) > 64 { - el = append(el, field.TooLong(fldPath.Child("commonName"), crt.CommonName, 64)) + el = append(el, field.TooLong(fldPath.Child("commonName"), commonName, 64)) } if len(crt.IPAddresses) > 0 { el = append(el, validateIPAddresses(crt, fldPath)...) } - if len(crt.EmailSANs) > 0 { + if len(crt.EmailAddresses) > 0 { el = append(el, validateEmailAddresses(crt, fldPath)...) } + if len(crt.OtherNames) > 0 { + if !utilfeature.DefaultFeatureGate.Enabled(feature.OtherNames) { + el = append(el, field.Forbidden(fldPath.Child("OtherNames"), "Feature gate OtherNames must be enabled on both webhook and controller to use the alpha `otherNames` field")) + } else { + for i, otherName := range crt.OtherNames { + if otherName.OID == "" { + el = append(el, field.Required(fldPath.Child("otherNames").Index(i).Child("oid"), "must be specified")) + } + + if _, err := pki.ParseObjectIdentifier(otherName.OID); err != nil { + el = append(el, field.Invalid(fldPath.Child("otherNames").Index(i).Child("oid"), otherName.OID, "oid syntax invalid")) + } + + if otherName.UTF8Value == "" || !utf8.ValidString(otherName.UTF8Value) { + el = append(el, field.Required(fldPath.Child("otherNames").Index(i).Child("utf8Value"), "must be set to a valid non-empty UTF8 string")) + } + } + } + } + if crt.PrivateKey != nil { switch crt.PrivateKey.Algorithm { case "", internalcmapi.RSAKeyAlgorithm: - if crt.PrivateKey.Size > 0 && (crt.PrivateKey.Size < 2048 || crt.PrivateKey.Size > 8192) { - el = append(el, field.Invalid(fldPath.Child("privateKey", "size"), crt.PrivateKey.Size, "must be between 2048 & 8192 for rsa keyAlgorithm")) + if crt.PrivateKey.Size > 0 && (crt.PrivateKey.Size < pki.MinRSAKeySize || crt.PrivateKey.Size > pki.MaxRSAKeySize) { + el = append(el, field.Invalid(fldPath.Child("privateKey", "size"), crt.PrivateKey.Size, fmt.Sprintf("must be between %d and %d for rsa keyAlgorithm", pki.MinRSAKeySize, pki.MaxRSAKeySize))) } case internalcmapi.ECDSAKeyAlgorithm: if crt.PrivateKey.Size > 0 && crt.PrivateKey.Size != 256 && crt.PrivateKey.Size != 384 && crt.PrivateKey.Size != 521 { @@ -122,7 +176,19 @@ func ValidateCertificateSpec(crt *internalcmapi.CertificateSpec, fldPath *field. case internalcmapi.Ed25519KeyAlgorithm: break default: - el = append(el, field.Invalid(fldPath.Child("privateKey", "algorithm"), crt.PrivateKey.Algorithm, "must be either empty or one of rsa or ecdsa")) + el = append(el, field.Invalid(fldPath.Child("privateKey", "algorithm"), crt.PrivateKey.Algorithm, "must be either empty or one of rsa, ecdsa or ed25519")) + } + } + + if crt.SignatureAlgorithm != "" { + actualKeyAlg := internalcmapi.RSAKeyAlgorithm + if crt.PrivateKey != nil && crt.PrivateKey.Algorithm != "" { + actualKeyAlg = crt.PrivateKey.Algorithm + } + allowed, ok := keyAlgToAllowedSigAlgs[actualKeyAlg] + if ok && !slices.Contains(allowed, crt.SignatureAlgorithm) { + el = append(el, field.Invalid(fldPath.Child("signatureAlgorithm"), crt.SignatureAlgorithm, + fmt.Sprintf("for key algorithm %s the allowed signature algorithms are %v", actualKeyAlg, allowed))) } } @@ -145,15 +211,36 @@ func ValidateCertificateSpec(crt *internalcmapi.CertificateSpec, fldPath *field. } } + if crt.NameConstraints != nil { + if !utilfeature.DefaultFeatureGate.Enabled(feature.NameConstraints) { + el = append(el, field.Forbidden(fldPath.Child("nameConstraints"), "feature gate NameConstraints must be enabled")) + } else { + if !crt.IsCA { + el = append(el, field.Invalid(fldPath.Child("nameConstraints"), crt.NameConstraints, "isCa should be true when nameConstraints is set")) + } + + if crt.NameConstraints.Permitted == nil && crt.NameConstraints.Excluded == nil { + el = append(el, field.Invalid(fldPath.Child("nameConstraints"), crt.NameConstraints, "either permitted or excluded must be set")) + } + } + } + el = append(el, validateAdditionalOutputFormats(crt, fldPath)...) + if crt.Keystores != nil { + el = append(el, validateKeystores(crt, fldPath)...) + } + return el } -func ValidateCertificate(a *admissionv1.AdmissionRequest, obj runtime.Object) (field.ErrorList, []string) { +func ValidateCertificate(a *admissionv1.AdmissionRequest, obj runtime.Object) (allErrs field.ErrorList, warnings []string) { crt := obj.(*internalcmapi.Certificate) - allErrs := ValidateCertificateSpec(&crt.Spec, field.NewPath("spec")) - return allErrs, nil + allErrs = ValidateCertificateSpec(&crt.Spec, field.NewPath("spec")) + if crt.Spec.PrivateKey == nil || crt.Spec.PrivateKey.RotationPolicy == "" { + warnings = append(warnings, newDefaultPrivateKeyRotationPolicy) + } + return allErrs, warnings } func ValidateUpdateCertificate(a *admissionv1.AdmissionRequest, oldObj, obj runtime.Object) (field.ErrorList, []string) { @@ -162,26 +249,50 @@ func ValidateUpdateCertificate(a *admissionv1.AdmissionRequest, oldObj, obj runt return allErrs, nil } -func validateIssuerRef(issuerRef cmmeta.ObjectReference, fldPath *field.Path) field.ErrorList { +func validateIssuerRef(issuerRef cmmeta.IssuerReference, fldPath *field.Path) field.ErrorList { el := field.ErrorList{} issuerRefPath := fldPath.Child("issuerRef") if issuerRef.Name == "" { + // all issuerRefs must specify a name el = append(el, field.Required(issuerRefPath.Child("name"), "must be specified")) } + if issuerRef.Group == "" || issuerRef.Group == internalcmapi.SchemeGroupVersion.Group { + // if the user leaves the group blank, it's effectively defaulted to the built-in issuers (i.e. cert-manager.io) + // if the cert-manager.io group is used, we can do extra validation on the Kind + // if an external group is used, we don't have a mechanism currently to determine which Kinds are valid for those groups + // so we don't check switch issuerRef.Kind { case "": + // do nothing + case "Issuer", "ClusterIssuer": + // do nothing + default: - el = append(el, field.Invalid(issuerRefPath.Child("kind"), issuerRef.Kind, "must be one of Issuer or ClusterIssuer")) + kindPath := issuerRefPath.Child("kind") + errMsg := "must be one of Issuer or ClusterIssuer" + + if issuerRef.Group == "" { + // Sometimes the user sets a kind for an external issuer (e.g., "AWSPCAClusterIssuer" or "VenafiIssuer") but forgets + // to set the group (an easy mistake to make - see https://github.com/cert-manager/csi-driver/issues/197). + // If the users forgets the group but otherwise has a correct Kind set for an external issuer, we can give a hint + // as to what they need to do to fix. + + // If the user explicitly set the group to the cert-manager group though, we don't give the hint + errMsg += fmt.Sprintf(" (did you forget to set %s?)", kindPath.Child("group").String()) + } + + el = append(el, field.Invalid(kindPath, issuerRef.Kind, errMsg)) } } + return el } func validateIPAddresses(a *internalcmapi.CertificateSpec, fldPath *field.Path) field.ErrorList { - if len(a.IPAddresses) <= 0 { + if len(a.IPAddresses) == 0 { return nil } el := field.ErrorList{} @@ -195,11 +306,11 @@ func validateIPAddresses(a *internalcmapi.CertificateSpec, fldPath *field.Path) } func validateEmailAddresses(a *internalcmapi.CertificateSpec, fldPath *field.Path) field.ErrorList { - if len(a.EmailSANs) <= 0 { + if len(a.EmailAddresses) == 0 { return nil } el := field.ErrorList{} - for i, d := range a.EmailSANs { + for i, d := range a.EmailAddresses { e, err := mail.ParseAddress(d) if err != nil { el = append(el, field.Invalid(fldPath.Child("emailAddresses").Index(i), d, fmt.Sprintf("invalid email address: %s", err))) @@ -233,7 +344,7 @@ func validateSecretTemplateAnnotations(crt *internalcmapi.CertificateSpec, fldPa secretTemplateAnnotationsPath := fldPath.Child("secretTemplate", "annotations") for a := range crt.SecretTemplate.Annotations { - if strings.HasPrefix(a, "cert-manager.io/") { + if strings.HasPrefix(a, "cert-manager.io/") && a != "cert-manager.io/allow-direct-injection" { el = append(el, field.Invalid(secretTemplateAnnotationsPath, a, "cert-manager.io/* annotations are not allowed")) } } @@ -249,6 +360,13 @@ func ValidateDuration(crt *internalcmapi.CertificateSpec, fldPath *field.Path) f if duration < cmapi.MinimumCertificateDuration { el = append(el, field.Invalid(fldPath.Child("duration"), duration, fmt.Sprintf("certificate duration must be greater than %s", cmapi.MinimumCertificateDuration))) } + + // Must set at most one of spec.renewBefore or spec.renewBeforePercentage. + if crt.RenewBefore != nil && crt.RenewBeforePercentage != nil { + el = append(el, field.Invalid(fldPath.Child("renewBefore"), crt.RenewBefore.Duration, "renewBefore and renewBeforePercentage are mutually exclusive and cannot both be set")) + el = append(el, field.Invalid(fldPath.Child("renewBeforePercentage"), *crt.RenewBeforePercentage, "renewBefore and renewBeforePercentage are mutually exclusive and cannot both be set")) + } + // If spec.renewBefore is set, check that it is not less than the minimum. if crt.RenewBefore != nil && crt.RenewBefore.Duration < cmapi.MinimumRenewBefore { el = append(el, field.Invalid(fldPath.Child("renewBefore"), crt.RenewBefore.Duration, fmt.Sprintf("certificate renewBefore must be greater than %s", cmapi.MinimumRenewBefore))) @@ -257,19 +375,25 @@ func ValidateDuration(crt *internalcmapi.CertificateSpec, fldPath *field.Path) f if crt.RenewBefore != nil && crt.RenewBefore.Duration >= duration { el = append(el, field.Invalid(fldPath.Child("renewBefore"), crt.RenewBefore.Duration, fmt.Sprintf("certificate duration %s must be greater than renewBefore %s", duration, crt.RenewBefore.Duration))) } + + // If spec.renewBeforePercentage is set, check that it's within the allowed + // range. + if crt.RenewBeforePercentage != nil { + renewBefore := duration * time.Duration(100-*crt.RenewBeforePercentage) / 100 + if renewBefore < cmapi.MinimumRenewBefore { + el = append(el, field.Invalid(fldPath.Child("renewBeforePercentage"), *crt.RenewBeforePercentage, fmt.Sprintf("certificate renewBeforePercentage must result in a renewBefore greater than %s", cmapi.MinimumRenewBefore))) + } + if renewBefore >= duration { + el = append(el, field.Invalid(fldPath.Child("renewBeforePercentage"), *crt.RenewBeforePercentage, "certificate renewBeforePercentage must result in a renewBefore less than duration")) + } + } + return el } func validateAdditionalOutputFormats(crt *internalcmapi.CertificateSpec, fldPath *field.Path) field.ErrorList { var el field.ErrorList - if !utilfeature.DefaultFeatureGate.Enabled(feature.AdditionalCertificateOutputFormats) { - if len(crt.AdditionalOutputFormats) > 0 { - el = append(el, field.Forbidden(fldPath.Child("additionalOutputFormats"), "feature gate AdditionalCertificateOutputFormats must be enabled")) - } - return el - } - // Ensure the set of output formats is unique, keyed on "Type". aofSet := sets.NewString() for _, val := range crt.AdditionalOutputFormats { @@ -282,3 +406,45 @@ func validateAdditionalOutputFormats(crt *internalcmapi.CertificateSpec, fldPath return el } + +const ( + keystoresMutuallyExclusivePasswordsFmt = "exactly one of passwordSecretRef and password must be provided for %s keystores; cannot set both" + + keystoresPasswordRequiredFmt = "must set exactly one of passwordSecretRef and password must for %s keystores" + + keystoresLiteralPasswordMustNotBeEmptyFmt = "literal password cannot be empty if set on %s keystores" +) + +func validateKeystores(crt *internalcmapi.CertificateSpec, fldPath *field.Path) field.ErrorList { + var el field.ErrorList + + if crt.Keystores.JKS != nil { + if crt.Keystores.JKS.Password != nil && crt.Keystores.JKS.PasswordSecretRef.Name != "" { + el = append(el, field.Forbidden(fldPath.Child("keystores", "jks"), fmt.Sprintf(keystoresMutuallyExclusivePasswordsFmt, "JKS"))) + } + + if crt.Keystores.JKS.Password == nil && crt.Keystores.JKS.PasswordSecretRef.Name == "" { + el = append(el, field.Forbidden(fldPath.Child("keystores", "jks"), fmt.Sprintf(keystoresPasswordRequiredFmt, "JKS"))) + } + + if crt.Keystores.JKS.Password != nil && len(*crt.Keystores.JKS.Password) == 0 { + el = append(el, field.Forbidden(fldPath.Child("keystores", "jks", "password"), fmt.Sprintf(keystoresLiteralPasswordMustNotBeEmptyFmt, "JKS"))) + } + } + + if crt.Keystores.PKCS12 != nil { + if crt.Keystores.PKCS12.Password != nil && crt.Keystores.PKCS12.PasswordSecretRef.Name != "" { + el = append(el, field.Forbidden(fldPath.Child("keystores", "pkcs12"), fmt.Sprintf(keystoresMutuallyExclusivePasswordsFmt, "PKCS#12"))) + } + + if crt.Keystores.PKCS12.Password == nil && crt.Keystores.PKCS12.PasswordSecretRef.Name == "" { + el = append(el, field.Forbidden(fldPath.Child("keystores", "pkcs12"), fmt.Sprintf(keystoresPasswordRequiredFmt, "PKCS#12"))) + } + + if crt.Keystores.PKCS12.Password != nil && len(*crt.Keystores.PKCS12.Password) == 0 { + el = append(el, field.Forbidden(fldPath.Child("keystores", "pkcs12", "password"), fmt.Sprintf(keystoresLiteralPasswordMustNotBeEmptyFmt, "PKCS#12"))) + } + } + + return el +} diff --git a/internal/apis/certmanager/validation/certificate_for_issuer.go b/internal/apis/certmanager/validation/certificate_for_issuer.go deleted file mode 100644 index 473aac35057..00000000000 --- a/internal/apis/certmanager/validation/certificate_for_issuer.go +++ /dev/null @@ -1,81 +0,0 @@ -/* -Copyright 2020 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. -*/ - -package validation - -import ( - "fmt" - - "k8s.io/apimachinery/pkg/util/validation/field" - - cmapi "github.com/cert-manager/cert-manager/internal/apis/certmanager" -) - -func ValidateCertificateForIssuer(crt *cmapi.Certificate, issuerObj cmapi.GenericIssuer) field.ErrorList { - el := field.ErrorList{} - - path := field.NewPath("spec") - - switch { - case issuerObj.GetSpec().ACME != nil: - el = append(el, ValidateCertificateForACMEIssuer(&crt.Spec, issuerObj.GetSpec(), path)...) - case issuerObj.GetSpec().CA != nil: - case issuerObj.GetSpec().Vault != nil: - el = append(el, ValidateCertificateForVaultIssuer(&crt.Spec, issuerObj.GetSpec(), path)...) - case issuerObj.GetSpec().SelfSigned != nil: - case issuerObj.GetSpec().Venafi != nil: - default: - el = append(el, field.Invalid(path, "", fmt.Sprintf("no issuer specified for Issuer '%s/%s'", issuerObj.GetObjectMeta().Namespace, issuerObj.GetObjectMeta().Name))) - } - - return el -} - -func ValidateCertificateForACMEIssuer(crt *cmapi.CertificateSpec, issuer *cmapi.IssuerSpec, specPath *field.Path) field.ErrorList { - el := field.ErrorList{} - - if crt.IsCA { - el = append(el, field.Invalid(specPath.Child("isCA"), crt.IsCA, "ACME does not support CA certificates")) - } - - if crt.Subject != nil && len(crt.Subject.Organizations) != 0 { - el = append(el, field.Invalid(specPath.Child("subject", "organizations"), crt.Subject.Organizations, "ACME does not support setting the organization name")) - } - - if crt.Duration != nil { - el = append(el, field.Invalid(specPath.Child("duration"), crt.Duration, "ACME does not support certificate durations")) - } - - if len(crt.IPAddresses) != 0 { - el = append(el, field.Invalid(specPath.Child("ipAddresses"), crt.IPAddresses, "ACME does not support certificate ip addresses")) - } - - return el -} - -func ValidateCertificateForVaultIssuer(crt *cmapi.CertificateSpec, issuer *cmapi.IssuerSpec, specPath *field.Path) field.ErrorList { - el := field.ErrorList{} - - if crt.IsCA { - el = append(el, field.Invalid(specPath.Child("isCA"), crt.IsCA, "Vault issuer does not currently support CA certificates")) - } - - if crt.Subject != nil && len(crt.Subject.Organizations) != 0 { - el = append(el, field.Invalid(specPath.Child("subject", "organizations"), crt.Subject.Organizations, "Vault issuer does not currently support setting the organization name")) - } - - return el -} diff --git a/internal/apis/certmanager/validation/certificate_for_issuer_test.go b/internal/apis/certmanager/validation/certificate_for_issuer_test.go deleted file mode 100644 index 7b986d188a9..00000000000 --- a/internal/apis/certmanager/validation/certificate_for_issuer_test.go +++ /dev/null @@ -1,163 +0,0 @@ -/* -Copyright 2020 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. -*/ - -package validation - -import ( - "reflect" - "testing" - "time" - - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/util/validation/field" - - cmacme "github.com/cert-manager/cert-manager/internal/apis/acme" - cmapi "github.com/cert-manager/cert-manager/internal/apis/certmanager" - "github.com/cert-manager/cert-manager/test/unit/gen" -) - -const ( - defaultTestIssuerName = "test-issuer" - defaultTestNamespace = gen.DefaultTestNamespace -) - -func TestValidateCertificateForIssuer(t *testing.T) { - fldPath := field.NewPath("spec") - acmeIssuer := &cmapi.Issuer{ - ObjectMeta: metav1.ObjectMeta{ - Name: defaultTestIssuerName, - Namespace: defaultTestNamespace, - }, - Spec: cmapi.IssuerSpec{ - IssuerConfig: cmapi.IssuerConfig{ - ACME: &cmacme.ACMEIssuer{}, - }, - }, - } - scenarios := map[string]struct { - crt *cmapi.Certificate - issuer *cmapi.Issuer - errs []*field.Error - }{ - "valid basic certificate": { - crt: &cmapi.Certificate{ - Spec: cmapi.CertificateSpec{ - IssuerRef: validIssuerRef, - }, - }, - issuer: acmeIssuer, - }, - "certificate with RSA keyAlgorithm for ACME": { - crt: &cmapi.Certificate{ - Spec: cmapi.CertificateSpec{ - PrivateKey: &cmapi.CertificatePrivateKey{ - Algorithm: cmapi.RSAKeyAlgorithm, - }, - IssuerRef: validIssuerRef, - }, - }, - issuer: acmeIssuer, - }, - "certificate with ECDSA keyAlgorithm for ACME": { - crt: &cmapi.Certificate{ - Spec: cmapi.CertificateSpec{ - PrivateKey: &cmapi.CertificatePrivateKey{ - Algorithm: cmapi.ECDSAKeyAlgorithm, - }, - IssuerRef: validIssuerRef, - }, - }, - issuer: acmeIssuer, - }, - "acme certificate with organization set": { - crt: &cmapi.Certificate{ - Spec: cmapi.CertificateSpec{ - Subject: &cmapi.X509Subject{ - Organizations: []string{"shouldfailorg"}, - }, - IssuerRef: validIssuerRef, - }, - }, - issuer: acmeIssuer, - errs: []*field.Error{ - field.Invalid(fldPath.Child("subject", "organizations"), []string{"shouldfailorg"}, "ACME does not support setting the organization name"), - }, - }, - "acme certificate with duration set": { - crt: &cmapi.Certificate{ - Spec: cmapi.CertificateSpec{ - Duration: &metav1.Duration{Duration: time.Minute * 60}, - IssuerRef: validIssuerRef, - }, - }, - issuer: acmeIssuer, - errs: []*field.Error{ - field.Invalid(fldPath.Child("duration"), &metav1.Duration{Duration: time.Minute * 60}, "ACME does not support certificate durations"), - }, - }, - "acme certificate with ipAddresses set": { - crt: &cmapi.Certificate{ - Spec: cmapi.CertificateSpec{ - IPAddresses: []string{"127.0.0.1"}, - IssuerRef: validIssuerRef, - }, - }, - issuer: acmeIssuer, - errs: []*field.Error{ - field.Invalid(fldPath.Child("ipAddresses"), []string{"127.0.0.1"}, "ACME does not support certificate ip addresses"), - }, - }, - "acme certificate with renewBefore set": { - crt: &cmapi.Certificate{ - Spec: cmapi.CertificateSpec{ - RenewBefore: &metav1.Duration{Duration: time.Minute * 60}, - IssuerRef: validIssuerRef, - }, - }, - issuer: acmeIssuer, - errs: []*field.Error{}, - }, - "certificate with unspecified issuer type": { - crt: &cmapi.Certificate{ - Spec: cmapi.CertificateSpec{ - PrivateKey: &cmapi.CertificatePrivateKey{ - Algorithm: cmapi.ECDSAKeyAlgorithm, - }, - IssuerRef: validIssuerRef, - }, - }, - issuer: &cmapi.Issuer{}, - errs: []*field.Error{ - field.Invalid(fldPath, "", "no issuer specified for Issuer '/'"), - }, - }, - } - for n, s := range scenarios { - t.Run(n, func(t *testing.T) { - errs := ValidateCertificateForIssuer(s.crt, s.issuer) - if len(errs) != len(s.errs) { - t.Errorf("Expected %v but got %v", s.errs, errs) - return - } - for i, e := range errs { - expectedErr := s.errs[i] - if !reflect.DeepEqual(e, expectedErr) { - t.Errorf("Expected %v but got %v", expectedErr, e) - } - } - }) - } -} diff --git a/internal/apis/certmanager/validation/certificate_test.go b/internal/apis/certmanager/validation/certificate_test.go index 4d3280cefc2..9d74f3eb48b 100644 --- a/internal/apis/certmanager/validation/certificate_test.go +++ b/internal/apis/certmanager/validation/certificate_test.go @@ -22,21 +22,22 @@ import ( "testing" "time" + "github.com/stretchr/testify/assert" admissionv1 "k8s.io/api/admission/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/validation/field" featuregatetesting "k8s.io/component-base/featuregate/testing" + "k8s.io/utils/ptr" internalcmapi "github.com/cert-manager/cert-manager/internal/apis/certmanager" cmmeta "github.com/cert-manager/cert-manager/internal/apis/meta" "github.com/cert-manager/cert-manager/internal/webhook/feature" cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" utilfeature "github.com/cert-manager/cert-manager/pkg/util/feature" - "github.com/stretchr/testify/assert" ) var ( - validIssuerRef = cmmeta.ObjectReference{ + validIssuerRef = cmmeta.IssuerReference{ Name: "name", Kind: "ClusterIssuer", } @@ -50,21 +51,13 @@ var ( maxSecretTemplateAnnotationsBytesLimit = 256 * (1 << 10) // 256 kB ) -func strPtr(s string) *string { - return &s -} - -func int32Ptr(i int32) *int32 { - return &i -} - func TestValidateCertificate(t *testing.T) { fldPath := field.NewPath("spec") scenarios := map[string]struct { - cfg *internalcmapi.Certificate - a *admissionv1.AdmissionRequest - errs []*field.Error - warnings []string + cfg *internalcmapi.Certificate + a *admissionv1.AdmissionRequest + errs []*field.Error + nameConstraintsFeatureEnabled bool }{ "valid basic certificate": { cfg: &internalcmapi.Certificate{ @@ -76,24 +69,24 @@ func TestValidateCertificate(t *testing.T) { }, a: someAdmissionRequest, }, - "valid with blank issuerRef kind": { + "valid with blank issuerRef kind and no group": { cfg: &internalcmapi.Certificate{ Spec: internalcmapi.CertificateSpec{ CommonName: "testcn", SecretName: "abc", - IssuerRef: cmmeta.ObjectReference{ + IssuerRef: cmmeta.IssuerReference{ Name: "valid", }, }, }, a: someAdmissionRequest, }, - "valid with 'Issuer' issuerRef kind": { + "valid with 'Issuer' issuerRef kind and no group": { cfg: &internalcmapi.Certificate{ Spec: internalcmapi.CertificateSpec{ CommonName: "testcn", SecretName: "abc", - IssuerRef: cmmeta.ObjectReference{ + IssuerRef: cmmeta.IssuerReference{ Name: "valid", Kind: "Issuer", }, @@ -114,22 +107,50 @@ func TestValidateCertificate(t *testing.T) { }, a: someAdmissionRequest, }, - "invalid issuerRef kind": { + "valid with 'Issuer' issuerRef kind and explicit internal group": { cfg: &internalcmapi.Certificate{ Spec: internalcmapi.CertificateSpec{ CommonName: "testcn", SecretName: "abc", - IssuerRef: cmmeta.ObjectReference{ - Name: "valid", - Kind: "invalid", + IssuerRef: cmmeta.IssuerReference{ + Name: "valid", + Kind: "Issuer", + Group: "cert-manager.io", + }, + }, + }, + a: someAdmissionRequest, + }, + "invalid with external issuerRef kind and empty group": { + cfg: &internalcmapi.Certificate{ + Spec: internalcmapi.CertificateSpec{ + CommonName: "testcn", + SecretName: "abc", + IssuerRef: cmmeta.IssuerReference{ + Name: "abc", + Kind: "AWSPCAClusterIssuer", }, }, }, a: someAdmissionRequest, errs: []*field.Error{ - field.Invalid(fldPath.Child("issuerRef", "kind"), "invalid", "must be one of Issuer or ClusterIssuer"), + field.Invalid(fldPath.Child("issuerRef", "kind"), "AWSPCAClusterIssuer", "must be one of Issuer or ClusterIssuer (did you forget to set spec.issuerRef.kind.group?)"), }, }, + "valid with external issuerRef kind and external group": { + cfg: &internalcmapi.Certificate{ + Spec: internalcmapi.CertificateSpec{ + CommonName: "testcn", + SecretName: "abc", + IssuerRef: cmmeta.IssuerReference{ + Name: "abc", + Kind: "AWSPCAClusterIssuer", + Group: "awspca.cert-manager.io", + }, + }, + }, + a: someAdmissionRequest, + }, "certificate missing secretName": { cfg: &internalcmapi.Certificate{ Spec: internalcmapi.CertificateSpec{ @@ -142,6 +163,19 @@ func TestValidateCertificate(t *testing.T) { }, a: someAdmissionRequest, }, + "certificate invalid secretName": { + cfg: &internalcmapi.Certificate{ + Spec: internalcmapi.CertificateSpec{ + CommonName: "testcn", + IssuerRef: validIssuerRef, + SecretName: "testFoo", + }, + }, + errs: []*field.Error{ + field.Invalid(fldPath.Child("secretName"), "testFoo", "a lowercase RFC 1123 subdomain must consist of lower case alphanumeric characters, '-' or '.', and must start and end with an alphanumeric character (e.g. 'example.com', regex used for validation is '[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*')"), + }, + a: someAdmissionRequest, + }, "certificate with no domains, URIs or common name": { cfg: &internalcmapi.Certificate{ Spec: internalcmapi.CertificateSpec{ @@ -151,10 +185,10 @@ func TestValidateCertificate(t *testing.T) { }, a: someAdmissionRequest, errs: []*field.Error{ - field.Invalid(fldPath, "", "at least one of commonName, dnsNames, uris ipAddresses, or emailAddresses must be set"), + field.Invalid(fldPath, "", "at least one of commonName (from the commonName field or from a literalSubject), dnsNames, uriSANs, ipAddresses, emailSANs or otherNames must be set"), }, }, - "certificate with no issuerRef": { + "invalid with no issuerRef": { cfg: &internalcmapi.Certificate{ Spec: internalcmapi.CertificateSpec{ CommonName: "testcn", @@ -286,6 +320,20 @@ func TestValidateCertificate(t *testing.T) { }, a: someAdmissionRequest, }, + "valid certificate with ed25519 keyAlgorithm": { + cfg: &internalcmapi.Certificate{ + Spec: internalcmapi.CertificateSpec{ + CommonName: "testcn", + SecretName: "abc", + IssuerRef: validIssuerRef, + PrivateKey: &internalcmapi.CertificatePrivateKey{ + Size: 521, + Algorithm: internalcmapi.Ed25519KeyAlgorithm, + }, + }, + }, + a: someAdmissionRequest, + }, "valid certificate with keyAlgorithm not specified and keySize specified": { cfg: &internalcmapi.Certificate{ Spec: internalcmapi.CertificateSpec{ @@ -313,7 +361,7 @@ func TestValidateCertificate(t *testing.T) { }, a: someAdmissionRequest, errs: []*field.Error{ - field.Invalid(fldPath.Child("privateKey", "size"), 1024, "must be between 2048 & 8192 for rsa keyAlgorithm"), + field.Invalid(fldPath.Child("privateKey", "size"), 1024, "must be between 2048 and 8192 for rsa keyAlgorithm"), }, }, "certificate with rsa keyAlgorithm specified and invalid keysize 8196": { @@ -330,7 +378,7 @@ func TestValidateCertificate(t *testing.T) { }, a: someAdmissionRequest, errs: []*field.Error{ - field.Invalid(fldPath.Child("privateKey", "size"), 8196, "must be between 2048 & 8192 for rsa keyAlgorithm"), + field.Invalid(fldPath.Child("privateKey", "size"), 8196, "must be between 2048 and 8192 for rsa keyAlgorithm"), }, }, "certificate with ecdsa keyAlgorithm specified and invalid keysize": { @@ -363,7 +411,7 @@ func TestValidateCertificate(t *testing.T) { }, a: someAdmissionRequest, errs: []*field.Error{ - field.Invalid(fldPath.Child("privateKey", "algorithm"), internalcmapi.PrivateKeyAlgorithm("blah"), "must be either empty or one of rsa or ecdsa"), + field.Invalid(fldPath.Child("privateKey", "algorithm"), internalcmapi.PrivateKeyAlgorithm("blah"), "must be either empty or one of rsa, ecdsa or ed25519"), }, }, "valid certificate with ipAddresses": { @@ -483,7 +531,7 @@ func TestValidateCertificate(t *testing.T) { Spec: internalcmapi.CertificateSpec{ SecretName: "abc", IssuerRef: validIssuerRef, - URISANs: []string{ + URIs: []string{ "foo.bar", }, }, @@ -493,9 +541,9 @@ func TestValidateCertificate(t *testing.T) { "valid certificate with only email SAN": { cfg: &internalcmapi.Certificate{ Spec: internalcmapi.CertificateSpec{ - EmailSANs: []string{"alice@example.com"}, - SecretName: "abc", - IssuerRef: validIssuerRef, + EmailAddresses: []string{"alice@example.com"}, + SecretName: "abc", + IssuerRef: validIssuerRef, }, }, a: someAdmissionRequest, @@ -503,22 +551,22 @@ func TestValidateCertificate(t *testing.T) { "invalid certificate with incorrect email": { cfg: &internalcmapi.Certificate{ Spec: internalcmapi.CertificateSpec{ - EmailSANs: []string{"aliceexample.com"}, - SecretName: "abc", - IssuerRef: validIssuerRef, + EmailAddresses: []string{"alice.example.com"}, + SecretName: "abc", + IssuerRef: validIssuerRef, }, }, a: someAdmissionRequest, errs: []*field.Error{ - field.Invalid(fldPath.Child("emailAddresses").Index(0), "aliceexample.com", "invalid email address: mail: missing '@' or angle-addr"), + field.Invalid(fldPath.Child("emailAddresses").Index(0), "alice.example.com", "invalid email address: mail: missing '@' or angle-addr"), }, }, "invalid certificate with email formatted with name": { cfg: &internalcmapi.Certificate{ Spec: internalcmapi.CertificateSpec{ - EmailSANs: []string{"Alice "}, - SecretName: "abc", - IssuerRef: validIssuerRef, + EmailAddresses: []string{"Alice "}, + SecretName: "abc", + IssuerRef: validIssuerRef, }, }, a: someAdmissionRequest, @@ -529,9 +577,9 @@ func TestValidateCertificate(t *testing.T) { "invalid certificate with email formatted with mailto": { cfg: &internalcmapi.Certificate{ Spec: internalcmapi.CertificateSpec{ - EmailSANs: []string{"mailto:alice@example.com"}, - SecretName: "abc", - IssuerRef: validIssuerRef, + EmailAddresses: []string{"mailto:alice@example.com"}, + SecretName: "abc", + IssuerRef: validIssuerRef, }, }, a: someAdmissionRequest, @@ -545,7 +593,7 @@ func TestValidateCertificate(t *testing.T) { CommonName: "abc", SecretName: "abc", IssuerRef: validIssuerRef, - RevisionHistoryLimit: int32Ptr(1), + RevisionHistoryLimit: ptr.To(int32(1)), }, }, a: someAdmissionRequest, @@ -556,7 +604,7 @@ func TestValidateCertificate(t *testing.T) { CommonName: "abc", SecretName: "abc", IssuerRef: validIssuerRef, - RevisionHistoryLimit: int32Ptr(0), + RevisionHistoryLimit: ptr.To(int32(0)), }, }, a: someAdmissionRequest, @@ -573,9 +621,7 @@ func TestValidateCertificate(t *testing.T) { Annotations: map[string]string{}, Labels: map[string]string{}, }, - IssuerRef: cmmeta.ObjectReference{ - Name: "valid", - }, + IssuerRef: validIssuerRef, }, }, a: someAdmissionRequest, @@ -593,9 +639,7 @@ func TestValidateCertificate(t *testing.T) { "my-label.com/foo": "evn-production", }, }, - IssuerRef: cmmeta.ObjectReference{ - Name: "valid", - }, + IssuerRef: validIssuerRef, }, }, a: someAdmissionRequest, @@ -607,14 +651,13 @@ func TestValidateCertificate(t *testing.T) { SecretName: "abc", SecretTemplate: &internalcmapi.CertificateSecretTemplate{ Annotations: map[string]string{ - "app.com/valid": "valid", - "cert-manager.io/alt-names": "example.com", - "cert-manager.io/certificate-name": "selfsigned-cert", + "app.com/valid": "valid", + "cert-manager.io/alt-names": "example.com", + "cert-manager.io/certificate-name": "selfsigned-cert", + "cert-manager.io/allow-direct-injection": "true", }, }, - IssuerRef: cmmeta.ObjectReference{ - Name: "invalid", - }, + IssuerRef: validIssuerRef, }, }, a: someAdmissionRequest, @@ -633,9 +676,7 @@ func TestValidateCertificate(t *testing.T) { "app.com/invalid": strings.Repeat("0", maxSecretTemplateAnnotationsBytesLimit), }, }, - IssuerRef: cmmeta.ObjectReference{ - Name: "invalid", - }, + IssuerRef: validIssuerRef, }, }, a: someAdmissionRequest, @@ -653,9 +694,7 @@ func TestValidateCertificate(t *testing.T) { "app.com/invalid-chars": "invalid=chars", }, }, - IssuerRef: cmmeta.ObjectReference{ - Name: "invalid", - }, + IssuerRef: validIssuerRef, }, }, a: someAdmissionRequest, @@ -666,12 +705,201 @@ func TestValidateCertificate(t *testing.T) { "alphanumeric character (e.g. 'MyValue', or 'my_value', or '12345', regex used for validation is '(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])?')"), }, }, + "valid with name constraints": { + cfg: &internalcmapi.Certificate{ + Spec: internalcmapi.CertificateSpec{ + CommonName: "testcn", + SecretName: "abc", + IsCA: true, + NameConstraints: &internalcmapi.NameConstraints{ + Permitted: &internalcmapi.NameConstraintItem{ + DNSDomains: []string{"example.com"}, + }, + }, + IssuerRef: validIssuerRef, + }, + }, + a: someAdmissionRequest, + nameConstraintsFeatureEnabled: true, + }, + "invalid with name constraints": { + cfg: &internalcmapi.Certificate{ + Spec: internalcmapi.CertificateSpec{ + CommonName: "testcn", + SecretName: "abc", + IsCA: true, + NameConstraints: &internalcmapi.NameConstraints{}, + IssuerRef: validIssuerRef, + }, + }, + a: someAdmissionRequest, + errs: []*field.Error{ + field.Invalid( + fldPath.Child("nameConstraints"), &internalcmapi.NameConstraints{}, "either permitted or excluded must be set"), + }, + nameConstraintsFeatureEnabled: true, + }, + "valid name constraints with feature gate disabled": { + cfg: &internalcmapi.Certificate{ + Spec: internalcmapi.CertificateSpec{ + CommonName: "testcn", + SecretName: "abc", + IsCA: true, + NameConstraints: &internalcmapi.NameConstraints{ + Permitted: &internalcmapi.NameConstraintItem{ + DNSDomains: []string{"example.com"}, + }, + }, + IssuerRef: validIssuerRef, + }, + }, + a: someAdmissionRequest, + errs: []*field.Error{ + field.Forbidden( + fldPath.Child("nameConstraints"), "feature gate NameConstraints must be enabled"), + }, + }, + "signature algorithm SHA+RSA allowed for empty key (RSA)": { + cfg: &internalcmapi.Certificate{ + Spec: internalcmapi.CertificateSpec{ + CommonName: "testcn", + SecretName: "abc", + IssuerRef: validIssuerRef, + SignatureAlgorithm: internalcmapi.SHA256WithRSA, + }, + }, + }, + "signature algorithm SHA+RSA allowed for RSA key": { + cfg: &internalcmapi.Certificate{ + Spec: internalcmapi.CertificateSpec{ + CommonName: "testcn", + SecretName: "abc", + IssuerRef: validIssuerRef, + PrivateKey: &internalcmapi.CertificatePrivateKey{ + Algorithm: internalcmapi.RSAKeyAlgorithm, + Size: 3072, + }, + SignatureAlgorithm: internalcmapi.SHA256WithRSA, + }, + }, + }, + "signature algorithm SHA+RSA not allowed for ECDSA key": { + cfg: &internalcmapi.Certificate{ + Spec: internalcmapi.CertificateSpec{ + CommonName: "testcn", + SecretName: "abc", + IssuerRef: validIssuerRef, + PrivateKey: &internalcmapi.CertificatePrivateKey{ + Algorithm: internalcmapi.ECDSAKeyAlgorithm, + }, + SignatureAlgorithm: internalcmapi.SHA256WithRSA, + }, + }, + errs: []*field.Error{ + field.Invalid(fldPath.Child("signatureAlgorithm"), internalcmapi.SHA256WithRSA, + "for key algorithm ECDSA the allowed signature algorithms are [ECDSAWithSHA256 ECDSAWithSHA384 ECDSAWithSHA512]"), + }, + }, + "signature algorithm SHA+ECDSA allowed for ECDSA key": { + cfg: &internalcmapi.Certificate{ + Spec: internalcmapi.CertificateSpec{ + CommonName: "testcn", + SecretName: "abc", + IssuerRef: validIssuerRef, + PrivateKey: &internalcmapi.CertificatePrivateKey{ + Algorithm: internalcmapi.ECDSAKeyAlgorithm, + }, + SignatureAlgorithm: internalcmapi.ECDSAWithSHA256, + }, + }, + }, + "signature algorithm SHA+ECDSA not allowed for RSA key": { + cfg: &internalcmapi.Certificate{ + Spec: internalcmapi.CertificateSpec{ + CommonName: "testcn", + SecretName: "abc", + IssuerRef: validIssuerRef, + PrivateKey: &internalcmapi.CertificatePrivateKey{ + Algorithm: internalcmapi.RSAKeyAlgorithm, + }, + SignatureAlgorithm: internalcmapi.ECDSAWithSHA256, + }, + }, + errs: []*field.Error{ + field.Invalid(fldPath.Child("signatureAlgorithm"), internalcmapi.ECDSAWithSHA256, + "for key algorithm RSA the allowed signature algorithms are [SHA256WithRSA SHA384WithRSA SHA512WithRSA]"), + }, + }, + "signature algorithm Ed25519 not allowed for RSA key": { + cfg: &internalcmapi.Certificate{ + Spec: internalcmapi.CertificateSpec{ + CommonName: "testcn", + SecretName: "abc", + IssuerRef: validIssuerRef, + PrivateKey: &internalcmapi.CertificatePrivateKey{ + Algorithm: internalcmapi.RSAKeyAlgorithm, + }, + SignatureAlgorithm: internalcmapi.PureEd25519, + }, + }, + errs: []*field.Error{ + field.Invalid(fldPath.Child("signatureAlgorithm"), internalcmapi.PureEd25519, + "for key algorithm RSA the allowed signature algorithms are [SHA256WithRSA SHA384WithRSA SHA512WithRSA]"), + }, + }, + "signature algorithm Ed25519 not allowed for ECDSA key": { + cfg: &internalcmapi.Certificate{ + Spec: internalcmapi.CertificateSpec{ + CommonName: "testcn", + SecretName: "abc", + IssuerRef: validIssuerRef, + PrivateKey: &internalcmapi.CertificatePrivateKey{ + Algorithm: internalcmapi.ECDSAKeyAlgorithm, + }, + SignatureAlgorithm: internalcmapi.PureEd25519, + }, + }, + errs: []*field.Error{ + field.Invalid(fldPath.Child("signatureAlgorithm"), internalcmapi.PureEd25519, + "for key algorithm ECDSA the allowed signature algorithms are [ECDSAWithSHA256 ECDSAWithSHA384 ECDSAWithSHA512]"), + }, + }, + "signature algorithm Ed25519 allowed for Ed25519 key": { + cfg: &internalcmapi.Certificate{ + Spec: internalcmapi.CertificateSpec{ + CommonName: "testcn", + SecretName: "abc", + IssuerRef: validIssuerRef, + PrivateKey: &internalcmapi.CertificatePrivateKey{ + Algorithm: internalcmapi.Ed25519KeyAlgorithm, + }, + SignatureAlgorithm: internalcmapi.PureEd25519, + }, + }, + }, + "explicit rotation policy": { + cfg: &internalcmapi.Certificate{ + Spec: internalcmapi.CertificateSpec{ + CommonName: "testcn", + SecretName: "abc", + IssuerRef: validIssuerRef, + PrivateKey: &internalcmapi.CertificatePrivateKey{ + RotationPolicy: internalcmapi.RotationPolicyNever, + }, + }, + }, + }, } for n, s := range scenarios { t.Run(n, func(t *testing.T) { + featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultMutableFeatureGate, feature.NameConstraints, s.nameConstraintsFeatureEnabled) errs, warnings := ValidateCertificate(s.a, s.cfg) assert.ElementsMatch(t, errs, s.errs) - assert.ElementsMatch(t, warnings, s.warnings) + if s.cfg.Spec.PrivateKey == nil || s.cfg.Spec.PrivateKey.RotationPolicy == "" { + assert.Contains(t, warnings, newDefaultPrivateKeyRotationPolicy, "a warning is expected when the rotation policy is omitted.") + } else { + assert.NotContains(t, warnings, newDefaultPrivateKeyRotationPolicy) + } }) } } @@ -767,6 +995,64 @@ func TestValidateDuration(t *testing.T) { }, errs: []*field.Error{field.Invalid(fldPath.Child("renewBefore"), usefulDurations["one second"].Duration, fmt.Sprintf("certificate renewBefore must be greater than %s", cmapi.MinimumRenewBefore))}, }, + "renewBefore and renewBeforePercentage both set": { + cfg: &internalcmapi.Certificate{ + Spec: internalcmapi.CertificateSpec{ + RenewBefore: usefulDurations["one month"], + RenewBeforePercentage: ptr.To(int32(95)), + CommonName: "testcn", + SecretName: "abc", + IssuerRef: validIssuerRef, + }, + }, + errs: []*field.Error{ + field.Invalid(fldPath.Child("renewBefore"), usefulDurations["one month"].Duration, "renewBefore and renewBeforePercentage are mutually exclusive and cannot both be set"), + field.Invalid(fldPath.Child("renewBeforePercentage"), int32(95), "renewBefore and renewBeforePercentage are mutually exclusive and cannot both be set"), + }, + }, + "valid duration and renewBeforePercentage": { + cfg: &internalcmapi.Certificate{ + Spec: internalcmapi.CertificateSpec{ + Duration: usefulDurations["one year"], + RenewBeforePercentage: ptr.To(int32(95)), + CommonName: "testcn", + SecretName: "abc", + IssuerRef: validIssuerRef, + }, + }, + }, + "unset duration, valid renewBeforePercentage for default": { + cfg: &internalcmapi.Certificate{ + Spec: internalcmapi.CertificateSpec{ + RenewBeforePercentage: ptr.To(int32(95)), + CommonName: "testcn", + SecretName: "abc", + IssuerRef: validIssuerRef, + }, + }, + }, + "renewBeforePercentage is equal to duration": { + cfg: &internalcmapi.Certificate{ + Spec: internalcmapi.CertificateSpec{ + RenewBeforePercentage: ptr.To(int32(0)), + CommonName: "testcn", + SecretName: "abc", + IssuerRef: validIssuerRef, + }, + }, + errs: []*field.Error{field.Invalid(fldPath.Child("renewBeforePercentage"), int32(0), "certificate renewBeforePercentage must result in a renewBefore less than duration")}, + }, + "renewBeforePercentage results in less than the minimum permitted value": { + cfg: &internalcmapi.Certificate{ + Spec: internalcmapi.CertificateSpec{ + RenewBeforePercentage: ptr.To(int32(100)), + CommonName: "testcn", + SecretName: "abc", + IssuerRef: validIssuerRef, + }, + }, + errs: []*field.Error{field.Invalid(fldPath.Child("renewBeforePercentage"), int32(100), fmt.Sprintf("certificate renewBeforePercentage must result in a renewBefore greater than %s", cmapi.MinimumRenewBefore))}, + }, "duration is less than the minimum permitted value": { cfg: &internalcmapi.Certificate{ Spec: internalcmapi.CertificateSpec{ @@ -790,50 +1076,16 @@ func TestValidateDuration(t *testing.T) { func Test_validateAdditionalOutputFormats(t *testing.T) { tests := map[string]struct { - featureEnabled bool - spec *internalcmapi.CertificateSpec - expErr field.ErrorList + spec *internalcmapi.CertificateSpec + expErr field.ErrorList }{ - "if feature disabled and no formats defined, expect no error": { - featureEnabled: false, - spec: &internalcmapi.CertificateSpec{ - AdditionalOutputFormats: []internalcmapi.CertificateAdditionalOutputFormat{}, - }, - expErr: nil, - }, - "if feature disabled and 1 format defined, expect error": { - featureEnabled: false, - spec: &internalcmapi.CertificateSpec{ - AdditionalOutputFormats: []internalcmapi.CertificateAdditionalOutputFormat{ - {Type: internalcmapi.CertificateOutputFormatType("foo")}, - }, - }, - expErr: field.ErrorList{ - field.Forbidden(field.NewPath("spec", "additionalOutputFormats"), "feature gate AdditionalCertificateOutputFormats must be enabled"), - }, - }, - "if feature disabled and multiple formats defined, expect error": { - featureEnabled: false, - spec: &internalcmapi.CertificateSpec{ - AdditionalOutputFormats: []internalcmapi.CertificateAdditionalOutputFormat{ - {Type: internalcmapi.CertificateOutputFormatType("foo")}, - {Type: internalcmapi.CertificateOutputFormatType("bar")}, - {Type: internalcmapi.CertificateOutputFormatType("random")}, - }, - }, - expErr: field.ErrorList{ - field.Forbidden(field.NewPath("spec", "additionalOutputFormats"), "feature gate AdditionalCertificateOutputFormats must be enabled"), - }, - }, - "if feature enabled and no formats defined, expect no error": { - featureEnabled: true, + "if no formats defined, expect no error": { spec: &internalcmapi.CertificateSpec{ AdditionalOutputFormats: []internalcmapi.CertificateAdditionalOutputFormat{}, }, expErr: nil, }, - "if feature enabled and single format defined, expect no error": { - featureEnabled: true, + "if single format defined, expect no error": { spec: &internalcmapi.CertificateSpec{ AdditionalOutputFormats: []internalcmapi.CertificateAdditionalOutputFormat{ {Type: internalcmapi.CertificateOutputFormatType("foo")}, @@ -841,8 +1093,7 @@ func Test_validateAdditionalOutputFormats(t *testing.T) { }, expErr: nil, }, - "if feature enabled and multiple unique formats defined, expect no error": { - featureEnabled: true, + "if multiple unique formats defined, expect no error": { spec: &internalcmapi.CertificateSpec{ AdditionalOutputFormats: []internalcmapi.CertificateAdditionalOutputFormat{ {Type: internalcmapi.CertificateOutputFormatType("foo")}, @@ -852,8 +1103,7 @@ func Test_validateAdditionalOutputFormats(t *testing.T) { }, expErr: nil, }, - "if feature enabled and multiple formats defined but 2 non-unique, expect error": { - featureEnabled: true, + "if multiple formats defined but 2 non-unique, expect error": { spec: &internalcmapi.CertificateSpec{ AdditionalOutputFormats: []internalcmapi.CertificateAdditionalOutputFormat{ {Type: internalcmapi.CertificateOutputFormatType("foo")}, @@ -866,8 +1116,7 @@ func Test_validateAdditionalOutputFormats(t *testing.T) { field.Duplicate(field.NewPath("spec", "additionalOutputFormats").Key("type"), "foo"), }, }, - "if feature enabled and multiple formats defined but multiple non-unique, expect error": { - featureEnabled: true, + "if multiple formats defined but multiple non-unique, expect error": { spec: &internalcmapi.CertificateSpec{ AdditionalOutputFormats: []internalcmapi.CertificateAdditionalOutputFormat{ {Type: internalcmapi.CertificateOutputFormatType("foo")}, @@ -892,7 +1141,6 @@ func Test_validateAdditionalOutputFormats(t *testing.T) { for name, test := range tests { t.Run(name, func(t *testing.T) { - defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultMutableFeatureGate, feature.AdditionalCertificateOutputFormats, test.featureEnabled)() gotErr := validateAdditionalOutputFormats(test.spec, field.NewPath("spec")) assert.Equal(t, test.expErr, gotErr) }) @@ -942,6 +1190,22 @@ func Test_validateLiteralSubject(t *testing.T) { IssuerRef: validIssuerRef, }, }, + errs: []*field.Error{ + field.Invalid( + fldPath.Child("subject"), + &internalcmapi.X509Subject{SerialNumber: "1"}, "When providing a `LiteralSubject` no `Subject` properties may be provided."), + }, + a: someAdmissionRequest, + }, + "valid with a `literalSubject` containing CN with special characters, multiple DC and well-known rfc4514 and rfc5280 RDN OIDs": { + featureEnabled: true, + cfg: &internalcmapi.Certificate{ + Spec: internalcmapi.CertificateSpec{ + LiteralSubject: "CN=James \\\"Jim\\\" Smith\\, III,DC=dc,DC=net,UID=jamessmith,STREET=La Rambla,L=Barcelona,C=Spain,O=Acme,OU=IT,OU=Admins", + SecretName: "abc", + IssuerRef: validIssuerRef, + }, + }, a: someAdmissionRequest, }, "invalid with a `literalSubject` without CN and no dnsNames, ipAddresses, or emailAddress": { @@ -955,7 +1219,7 @@ func Test_validateLiteralSubject(t *testing.T) { }, a: someAdmissionRequest, errs: []*field.Error{ - field.Invalid(fldPath, "", "at least one of commonName, dnsNames, uris ipAddresses, or emailAddresses must be set"), + field.Invalid(fldPath, "", "at least one of commonName (from the commonName field or from a literalSubject), dnsNames, uriSANs, ipAddresses, emailSANs or otherNames must be set"), }, }, "invalid with a `literalSubject` and any `Subject` other than serialNumber": { @@ -972,7 +1236,7 @@ func Test_validateLiteralSubject(t *testing.T) { errs: []*field.Error{ field.Invalid( fldPath.Child("subject"), - &internalcmapi.X509Subject{Organizations: []string{"US"}}, "When providing a `LiteralSubject` no `Subject` properties may be provided with the exception of `Subject.serialNumber`"), + &internalcmapi.X509Subject{Organizations: []string{"US"}}, "When providing a `LiteralSubject` no `Subject` properties may be provided."), }, }, "invalid with a `literalSubject` and a `commonName`": { @@ -1012,10 +1276,163 @@ func Test_validateLiteralSubject(t *testing.T) { for name, test := range tests { t.Run(name, func(t *testing.T) { - defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultMutableFeatureGate, feature.LiteralCertificateSubject, test.featureEnabled)() + featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultMutableFeatureGate, feature.LiteralCertificateSubject, test.featureEnabled) + errs, warnings := ValidateCertificate(test.a, test.cfg) + assert.ElementsMatch(t, errs, test.errs) + // None of these test inputs include a privateKey field, so they will all result in this warning. + assert.ElementsMatch(t, warnings, []string{newDefaultPrivateKeyRotationPolicy}) + }) + } +} + +func Test_validateKeystores(t *testing.T) { + emptyString := "" + keystorePassword := "changeit" + + fldPath := field.NewPath("spec") + tests := map[string]struct { + cfg *internalcmapi.Certificate + a *admissionv1.AdmissionRequest + errs []*field.Error + }{ + "JKS PasswordSecretRef and Password are mutually exclusive": { + cfg: &internalcmapi.Certificate{ + Spec: internalcmapi.CertificateSpec{ + CommonName: "testcn", + SecretName: "abc", + IssuerRef: validIssuerRef, + Keystores: &internalcmapi.CertificateKeystores{ + JKS: &internalcmapi.JKSKeystore{ + PasswordSecretRef: cmmeta.SecretKeySelector{ + LocalObjectReference: cmmeta.LocalObjectReference{ + Name: "secret", + }, + }, + Password: &keystorePassword, + }, + }, + }, + }, + errs: []*field.Error{ + field.Forbidden(fldPath.Child("keystores", "jks"), fmt.Sprintf(keystoresMutuallyExclusivePasswordsFmt, "JKS")), + }, + a: someAdmissionRequest, + }, + "JKS one of PasswordSecretRef / Password is required (nil password)": { + cfg: &internalcmapi.Certificate{ + Spec: internalcmapi.CertificateSpec{ + CommonName: "testcn", + SecretName: "abc", + IssuerRef: validIssuerRef, + Keystores: &internalcmapi.CertificateKeystores{ + JKS: &internalcmapi.JKSKeystore{ + PasswordSecretRef: cmmeta.SecretKeySelector{}, + Password: nil, + }, + }, + }, + }, + errs: []*field.Error{ + field.Forbidden(fldPath.Child("keystores", "jks"), fmt.Sprintf(keystoresPasswordRequiredFmt, "JKS")), + }, + a: someAdmissionRequest, + }, + "JKS one of PasswordSecretRef / Password is required (empty strings)": { + cfg: &internalcmapi.Certificate{ + Spec: internalcmapi.CertificateSpec{ + CommonName: "testcn", + SecretName: "abc", + IssuerRef: validIssuerRef, + Keystores: &internalcmapi.CertificateKeystores{ + JKS: &internalcmapi.JKSKeystore{ + PasswordSecretRef: cmmeta.SecretKeySelector{ + LocalObjectReference: cmmeta.LocalObjectReference{ + Name: "", + }, + }, + Password: &emptyString, + }, + }, + }, + }, + errs: []*field.Error{ + field.Forbidden(fldPath.Child("keystores", "jks", "password"), fmt.Sprintf(keystoresLiteralPasswordMustNotBeEmptyFmt, "JKS")), + }, + a: someAdmissionRequest, + }, + "PKCS12 PasswordSecretRef and Password are mutually exclusive": { + cfg: &internalcmapi.Certificate{ + Spec: internalcmapi.CertificateSpec{ + CommonName: "testcn", + SecretName: "abc", + IssuerRef: validIssuerRef, + Keystores: &internalcmapi.CertificateKeystores{ + PKCS12: &internalcmapi.PKCS12Keystore{ + PasswordSecretRef: cmmeta.SecretKeySelector{ + LocalObjectReference: cmmeta.LocalObjectReference{ + Name: "secret", + }, + }, + Password: &keystorePassword, + }, + }, + }, + }, + errs: []*field.Error{ + field.Forbidden(fldPath.Child("keystores", "pkcs12"), fmt.Sprintf(keystoresMutuallyExclusivePasswordsFmt, "PKCS#12")), + }, + a: someAdmissionRequest, + }, + "PKCS12 one of PasswordSecretRef / Password is required (nil password)": { + cfg: &internalcmapi.Certificate{ + Spec: internalcmapi.CertificateSpec{ + CommonName: "testcn", + SecretName: "abc", + IssuerRef: validIssuerRef, + Keystores: &internalcmapi.CertificateKeystores{ + PKCS12: &internalcmapi.PKCS12Keystore{ + PasswordSecretRef: cmmeta.SecretKeySelector{}, + Password: nil, + }, + }, + }, + }, + errs: []*field.Error{ + field.Forbidden(fldPath.Child("keystores", "pkcs12"), fmt.Sprintf(keystoresPasswordRequiredFmt, "PKCS#12")), + }, + a: someAdmissionRequest, + }, + "PKCS12 one of PasswordSecretRef / Password is required (empty strings)": { + cfg: &internalcmapi.Certificate{ + Spec: internalcmapi.CertificateSpec{ + CommonName: "testcn", + SecretName: "abc", + IssuerRef: validIssuerRef, + Keystores: &internalcmapi.CertificateKeystores{ + PKCS12: &internalcmapi.PKCS12Keystore{ + PasswordSecretRef: cmmeta.SecretKeySelector{ + LocalObjectReference: cmmeta.LocalObjectReference{ + Name: "", + }, + }, + Password: &emptyString, + }, + }, + }, + }, + errs: []*field.Error{ + field.Forbidden(fldPath.Child("keystores", "pkcs12", "password"), fmt.Sprintf(keystoresLiteralPasswordMustNotBeEmptyFmt, "PKCS#12")), + }, + a: someAdmissionRequest, + }, + } + + for name, test := range tests { + t.Run(name, func(t *testing.T) { errs, warnings := ValidateCertificate(test.a, test.cfg) assert.ElementsMatch(t, errs, test.errs) - assert.ElementsMatch(t, warnings, []string{}) + // None of these test inputs include a privateKey field, so they will all result in this warning. + assert.ElementsMatch(t, warnings, []string{newDefaultPrivateKeyRotationPolicy}) }) } } diff --git a/internal/apis/certmanager/validation/certificaterequest.go b/internal/apis/certmanager/validation/certificaterequest.go index 4ceb3b5598c..1caf9fc9bc8 100644 --- a/internal/apis/certmanager/validation/certificaterequest.go +++ b/internal/apis/certmanager/validation/certificaterequest.go @@ -17,13 +17,10 @@ limitations under the License. package validation import ( - "crypto/x509" - "encoding/asn1" "fmt" "reflect" "strings" - "github.com/kr/pretty" admissionv1 "k8s.io/api/admission/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/util/validation/field" @@ -32,15 +29,13 @@ import ( cmmeta "github.com/cert-manager/cert-manager/internal/apis/meta" "github.com/cert-manager/cert-manager/pkg/apis/acme" "github.com/cert-manager/cert-manager/pkg/apis/certmanager" - "github.com/cert-manager/cert-manager/pkg/util" + cmapiv1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" "github.com/cert-manager/cert-manager/pkg/util/pki" ) -var defaultInternalKeyUsages = []cmapi.KeyUsage{cmapi.UsageDigitalSignature, cmapi.UsageKeyEncipherment} - func ValidateCertificateRequest(a *admissionv1.AdmissionRequest, obj runtime.Object) (field.ErrorList, []string) { cr := obj.(*cmapi.CertificateRequest) - allErrs := ValidateCertificateRequestSpec(&cr.Spec, field.NewPath("spec"), true) + allErrs := ValidateCertificateRequestSpec(&cr.Spec, field.NewPath("spec")) allErrs = append(allErrs, ValidateCertificateRequestApprovalCondition(cr.Status.Conditions, field.NewPath("status", "conditions"))...) @@ -74,7 +69,7 @@ func validateCertificateRequestAnnotations(objA, objB *cmapi.CertificateRequest, for k, v := range objA.Annotations { if strings.HasPrefix(k, certmanager.GroupName) || strings.HasPrefix(k, acme.GroupName) { - if vnew, ok := objB.Annotations[k]; !ok || v != vnew { + if vNew, ok := objB.Annotations[k]; !ok || v != vNew { el = append(el, field.Forbidden(fieldPath.Child(k), "cannot change cert-manager annotation after creation")) } } @@ -83,36 +78,59 @@ func validateCertificateRequestAnnotations(objA, objB *cmapi.CertificateRequest, return el } -func ValidateCertificateRequestSpec(crSpec *cmapi.CertificateRequestSpec, fldPath *field.Path, validateCSRContent bool) field.ErrorList { +func ValidateCertificateRequestSpec(crSpec *cmapi.CertificateRequestSpec, fldPath *field.Path) field.ErrorList { el := field.ErrorList{} el = append(el, validateIssuerRef(crSpec.IssuerRef, fldPath)...) + el = append(el, validateCertificateRequestSpecRequest(crSpec, fldPath)...) + + return el +} + +func validateCertificateRequestSpecRequest(crSpec *cmapi.CertificateRequestSpec, fldPath *field.Path) field.ErrorList { + el := field.ErrorList{} + if len(crSpec.Request) == 0 { el = append(el, field.Required(fldPath.Child("request"), "must be specified")) - } else { - csr, err := pki.DecodeX509CertificateRequestBytes(crSpec.Request) - if err != nil { - el = append(el, field.Invalid(fldPath.Child("request"), crSpec.Request, fmt.Sprintf("failed to decode csr: %s", err))) - } else { - // only compare usages if set on CR and in the CSR - if len(crSpec.Usages) > 0 && len(csr.Extensions) > 0 && validateCSRContent && !reflect.DeepEqual(crSpec.Usages, defaultInternalKeyUsages) { - if crSpec.IsCA { - crSpec.Usages = ensureCertSignIsSet(crSpec.Usages) - } - csrUsages, err := getCSRKeyUsage(crSpec, fldPath, csr, el) - if len(err) > 0 { - el = append(el, err...) - } else if len(csrUsages) > 0 && !isUsageEqual(csrUsages, crSpec.Usages) && !isUsageEqual(csrUsages, defaultInternalKeyUsages) { - el = append(el, field.Invalid(fldPath.Child("request"), crSpec.Request, fmt.Sprintf("csr key usages do not match specified usages, these should match if both are set: %s", pretty.Diff(patchDuplicateKeyUsage(csrUsages), patchDuplicateKeyUsage(crSpec.Usages))))) - } - } - } + return el + } + + usages := make([]cmapiv1.KeyUsage, 0, len(crSpec.Usages)) + for _, usage := range crSpec.Usages { + usages = append(usages, cmapiv1.KeyUsage(usage)) + } + + keyUsage, extKeyUsage, err := pki.KeyUsagesForCertificateOrCertificateRequest(usages, crSpec.IsCA) + if err != nil { + el = append(el, field.Invalid(fldPath.Child("usages"), crSpec.Usages, err.Error())) + return el + } + + _, err = pki.CertificateTemplateFromCSRPEM( + crSpec.Request, + pki.CertificateTemplateValidateAndOverrideBasicConstraints(crSpec.IsCA, nil), + pki.CertificateTemplateValidateAndOverrideKeyUsages(keyUsage, extKeyUsage), + ) + if err != nil { + // truncate the request to avoid creating a ridiculously long error message with the whole CSR in it + el = append(el, field.Invalid(fldPath.Child("request"), truncateString(string(crSpec.Request)), err.Error())) + return el } return el } +func truncateString(s string) string { + const maxLength = 100 + + if len(s) <= maxLength { + return s + } + + return s[:maxLength-3] + "..." +} + // ValidateCertificateRequestApprovalCondition will ensure that only a single // 'Approved' or 'Denied' condition may exist, and that they are set to True. func ValidateCertificateRequestApprovalCondition(crConds []cmapi.CertificateRequestCondition, fldPath *field.Path) field.ErrorList { @@ -191,100 +209,6 @@ func ValidateUpdateCertificateRequestApprovalCondition(oldCRConds, newCRConds [] return append(el, ValidateCertificateRequestApprovalCondition(newCRConds, fldPath)...) } -func getCSRKeyUsage(crSpec *cmapi.CertificateRequestSpec, fldPath *field.Path, csr *x509.CertificateRequest, el field.ErrorList) ([]cmapi.KeyUsage, field.ErrorList) { - var ekus []x509.ExtKeyUsage - var ku x509.KeyUsage - - for _, extension := range csr.Extensions { - if extension.Id.String() == asn1.ObjectIdentifier(pki.OIDExtensionExtendedKeyUsage).String() { - var asn1ExtendedUsages []asn1.ObjectIdentifier - _, err := asn1.Unmarshal(extension.Value, &asn1ExtendedUsages) - if err != nil { - el = append(el, field.Invalid(fldPath.Child("request"), crSpec.Request, fmt.Sprintf("failed to decode csr extended usages: %s", err))) - } else { - for _, asnExtUsage := range asn1ExtendedUsages { - eku, ok := pki.ExtKeyUsageFromOID(asnExtUsage) - if ok { - ekus = append(ekus, eku) - } - } - } - } - if extension.Id.String() == asn1.ObjectIdentifier(pki.OIDExtensionKeyUsage).String() { - // RFC 5280, 4.2.1.3 - var asn1bits asn1.BitString - _, err := asn1.Unmarshal(extension.Value, &asn1bits) - if err != nil { - el = append(el, field.Invalid(fldPath.Child("request"), crSpec.Request, fmt.Sprintf("failed to decode csr usages: %s", err))) - } else { - var usage int - for i := 0; i < 9; i++ { - if asn1bits.At(i) != 0 { - usage |= 1 << uint(i) - } - } - ku = x509.KeyUsage(usage) - } - } - } - - // convert usages to the internal API - var out []cmapi.KeyUsage - for _, usage := range pki.BuildCertManagerKeyUsages(ku, ekus) { - out = append(out, cmapi.KeyUsage(usage)) - } - return out, el -} - -func patchDuplicateKeyUsage(usages []cmapi.KeyUsage) []cmapi.KeyUsage { - // usage signing and digital signature are the same key use in x509 - // we should patch this for proper validation - - newUsages := []cmapi.KeyUsage(nil) - hasUsageSigning := false - for _, usage := range usages { - if (usage == cmapi.UsageSigning || usage == cmapi.UsageDigitalSignature) && !hasUsageSigning { - newUsages = append(newUsages, cmapi.UsageDigitalSignature) - // prevent having 2 UsageDigitalSignature in the slice - hasUsageSigning = true - } else if usage != cmapi.UsageSigning && usage != cmapi.UsageDigitalSignature { - newUsages = append(newUsages, usage) - } - } - - return newUsages -} - -func isUsageEqual(a, b []cmapi.KeyUsage) bool { - a = patchDuplicateKeyUsage(a) - b = patchDuplicateKeyUsage(b) - - var aStrings, bStrings []string - - for _, usage := range a { - aStrings = append(aStrings, string(usage)) - } - - for _, usage := range b { - bStrings = append(bStrings, string(usage)) - } - - return util.EqualUnsorted(aStrings, bStrings) -} - -// ensureCertSignIsSet adds UsageCertSign in case it is not set -// TODO: add a mutating webhook to make sure this is always set -// when isCA is true. -func ensureCertSignIsSet(list []cmapi.KeyUsage) []cmapi.KeyUsage { - for _, usage := range list { - if usage == cmapi.UsageCertSign { - return list - } - } - - return append(list, cmapi.UsageCertSign) -} - func getCertificateRequestCondition(conds []cmapi.CertificateRequestCondition, conditionType cmapi.CertificateRequestConditionType) *cmapi.CertificateRequestCondition { for _, cond := range conds { if cond.Type == conditionType { diff --git a/internal/apis/certmanager/validation/certificaterequest_test.go b/internal/apis/certmanager/validation/certificaterequest_test.go index bb70e0ae676..b7ef87c2091 100644 --- a/internal/apis/certmanager/validation/certificaterequest_test.go +++ b/internal/apis/certmanager/validation/certificaterequest_test.go @@ -17,8 +17,9 @@ limitations under the License. package validation import ( - "bytes" - "encoding/pem" + "crypto/x509" + "crypto/x509/pkix" + "encoding/asn1" "reflect" "testing" @@ -540,6 +541,81 @@ func TestValidateCertificateRequest(t *testing.T) { a: someAdmissionRequest, wantE: []*field.Error{}, }, + "Test csr with default usages and isCA": { + cr: &cminternal.CertificateRequest{ + Spec: cminternal.CertificateRequestSpec{ + Request: mustGenerateCSR(t, gen.Certificate("test", gen.SetCertificateDNSNames("example.com"), gen.SetCertificateKeyUsages(cmapi.UsageDigitalSignature, cmapi.UsageCertSign, cmapi.UsageKeyEncipherment), gen.SetCertificateIsCA(true))), + IssuerRef: validIssuerRef, + IsCA: true, + Usages: nil, + }, + }, + a: someAdmissionRequest, + wantE: []*field.Error{}, + }, + "Test cr with default usages": { + cr: &cminternal.CertificateRequest{ + Spec: cminternal.CertificateRequestSpec{ + // mustGenerateCSR will set the default usages for us + Request: mustGenerateCSR(t, gen.Certificate("test", gen.SetCertificateDNSNames("example.com"))), + IssuerRef: validIssuerRef, + Usages: []cminternal.KeyUsage{cminternal.UsageKeyEncipherment, cminternal.UsageDigitalSignature}, + }, + }, + a: someAdmissionRequest, + wantE: []*field.Error{}, + }, + "Test cr with default usages, without any encoded in csr": { + cr: &cminternal.CertificateRequest{ + Spec: cminternal.CertificateRequestSpec{ + // mustGenerateCSR will set the default usages for us + Request: mustGenerateCSR(t, gen.Certificate("test", gen.SetCertificateDNSNames("example.com")), func(cr *x509.CertificateRequest) error { + // manually remove extensions that encode default usages + cr.Extensions = nil + cr.ExtraExtensions = nil + + return nil + }), + IssuerRef: validIssuerRef, + Usages: []cminternal.KeyUsage{cminternal.UsageKeyEncipherment, cminternal.UsageDigitalSignature}, + }, + }, + a: someAdmissionRequest, + wantE: []*field.Error{}, + }, + "Test cr with default usages, with empty set encoded in csr": { + cr: &cminternal.CertificateRequest{ + Spec: cminternal.CertificateRequestSpec{ + // mustGenerateCSR will set the default usages for us + Request: mustGenerateCSR(t, gen.Certificate("test", gen.SetCertificateDNSNames("example.com")), func(cr *x509.CertificateRequest) error { + // manually remove extensions that encode default usages + cr.Extensions = nil + cr.ExtraExtensions = []pkix.Extension{ + { + Id: utilpki.OIDExtensionKeyUsage, + Critical: false, + Value: func(t *testing.T) []byte { + asn1KeyUsage, err := asn1.Marshal(asn1.BitString{Bytes: []byte{}, BitLength: 0}) + if err != nil { + t.Fatal(err) + } + + return asn1KeyUsage + }(t), + }, + } + + return nil + }), + IssuerRef: validIssuerRef, + Usages: []cminternal.KeyUsage{cminternal.UsageKeyEncipherment, cminternal.UsageDigitalSignature}, + }, + }, + a: someAdmissionRequest, + wantE: []*field.Error{ + field.Invalid(fldPath.Child("request"), nil, "encoded CSR error: the KeyUsages [] do not match the expected KeyUsages [ 'digital signature', 'key encipherment' ]"), + }, + }, "Error on csr not having all usages": { cr: &cminternal.CertificateRequest{ Spec: cminternal.CertificateRequestSpec{ @@ -550,7 +626,7 @@ func TestValidateCertificateRequest(t *testing.T) { }, a: someAdmissionRequest, wantE: []*field.Error{ - field.Invalid(fldPath.Child("request"), nil, "csr key usages do not match specified usages, these should match if both are set: [[]certmanager.KeyUsage[3] != []certmanager.KeyUsage[4]]"), + field.Invalid(fldPath.Child("request"), nil, "encoded CSR error: the ExtKeyUsages [ 'server auth' ] do not match the expected ExtKeyUsages [ 'server auth', 'client auth' ]"), }, }, "Error on cr not having all usages": { @@ -563,7 +639,7 @@ func TestValidateCertificateRequest(t *testing.T) { }, a: someAdmissionRequest, wantE: []*field.Error{ - field.Invalid(fldPath.Child("request"), nil, "csr key usages do not match specified usages, these should match if both are set: [[]certmanager.KeyUsage[4] != []certmanager.KeyUsage[2]]"), + field.Invalid(fldPath.Child("request"), nil, "encoded CSR error: the ExtKeyUsages [ 'server auth', 'client auth' ] do not match the expected ExtKeyUsages []"), }, }, "Test csr with any, signing, digital signature, key encipherment, server and client auth": { @@ -802,63 +878,10 @@ func TestValidateCertificateRequest(t *testing.T) { } } -func mustGenerateCSR(t *testing.T, crt *cmapi.Certificate) []byte { - // Create a new private key - pk, err := utilpki.GenerateRSAPrivateKey(2048) +func mustGenerateCSR(t *testing.T, crt *cmapi.Certificate, modifiers ...gen.CSRModifier) []byte { + csrPEM, _, err := gen.CSRForCertificate(crt, modifiers...) if err != nil { t.Fatal(err) } - - x509CSR, err := utilpki.GenerateCSR(crt) - if err != nil { - t.Fatal(err) - } - csrDER, err := utilpki.EncodeCSR(x509CSR, pk) - if err != nil { - t.Fatal(err) - } - - csrPEM := bytes.NewBuffer([]byte{}) - err = pem.Encode(csrPEM, &pem.Block{Type: "CERTIFICATE REQUEST", Bytes: csrDER}) - if err != nil { - t.Fatal(err) - } - - return csrPEM.Bytes() -} - -func Test_patchDuplicateKeyUsage(t *testing.T) { - tests := []struct { - name string - usages []cminternal.KeyUsage - want []cminternal.KeyUsage - }{ - { - name: "Test single KU", - usages: []cminternal.KeyUsage{cminternal.UsageKeyEncipherment}, - want: []cminternal.KeyUsage{cminternal.UsageKeyEncipherment}, - }, - { - name: "Test UsageSigning", - usages: []cminternal.KeyUsage{cminternal.UsageSigning}, - want: []cminternal.KeyUsage{cminternal.UsageDigitalSignature}, - }, - { - name: "Test multiple KU", - usages: []cminternal.KeyUsage{cminternal.UsageDigitalSignature, cminternal.UsageServerAuth, cminternal.UsageClientAuth}, - want: []cminternal.KeyUsage{cminternal.UsageDigitalSignature, cminternal.UsageServerAuth, cminternal.UsageClientAuth}, - }, - { - name: "Test double signing", - usages: []cminternal.KeyUsage{cminternal.UsageSigning, cminternal.UsageDigitalSignature}, - want: []cminternal.KeyUsage{cminternal.UsageDigitalSignature}, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := patchDuplicateKeyUsage(tt.usages); !reflect.DeepEqual(got, tt.want) { - t.Errorf("patchDuplicateKeyUsage() = %v, want %v", got, tt.want) - } - }) - } + return csrPEM } diff --git a/internal/apis/certmanager/validation/issuer.go b/internal/apis/certmanager/validation/issuer.go index 67153fa6a41..b9c7b7d9e94 100644 --- a/internal/apis/certmanager/validation/issuer.go +++ b/internal/apis/certmanager/validation/issuer.go @@ -24,6 +24,7 @@ import ( admissionv1 "k8s.io/api/admission/v1" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/util/validation" "k8s.io/apimachinery/pkg/util/validation/field" cmacme "github.com/cert-manager/cert-manager/internal/apis/acme" @@ -105,10 +106,24 @@ func ValidateIssuerConfig(iss *certmanager.IssuerConfig, fldPath *field.Path) (f func ValidateACMEIssuerConfig(iss *cmacme.ACMEIssuer, fldPath *field.Path) (field.ErrorList, []string) { var warnings []string + el := field.ErrorList{} + + if len(iss.CABundle) > 0 && iss.SkipTLSVerify { + el = append(el, field.Invalid(fldPath.Child("caBundle"), "", "caBundle and skipTLSVerify are mutually exclusive and cannot both be set")) + el = append(el, field.Invalid(fldPath.Child("skipTLSVerify"), iss.SkipTLSVerify, "caBundle and skipTLSVerify are mutually exclusive and cannot both be set")) + } + + if len(iss.CABundle) > 0 { + if err := validateCABundleNotEmpty(iss.CABundle); err != nil { + el = append(el, field.Invalid(fldPath.Child("caBundle"), "", err.Error())) + } + } + if len(iss.PrivateKey.Name) == 0 { el = append(el, field.Required(fldPath.Child("privateKeySecretRef", "name"), "private key secret name is a required field")) } + if len(iss.Server) == 0 { el = append(el, field.Required(fldPath.Child("server"), "acme server URL is a required field")) } @@ -121,13 +136,14 @@ func ValidateACMEIssuerConfig(iss *cmacme.ACMEIssuer, fldPath *field.Path) (fiel el = append(el, ValidateSecretKeySelector(&eab.Key, eabFldPath.Child("keySecretRef"))...) + // nolint:staticcheck // SA1019 accessing the deprecated eab.KeyAlgorithm field is intentional here. if len(eab.KeyAlgorithm) != 0 { warnings = append(warnings, deprecatedACMEEABKeyAlgorithmField) } } for i, sol := range iss.Solvers { - el = append(el, ValidateACMEIssuerChallengeSolverConfig(&sol, fldPath.Child("solvers").Index(i))...) + el = append(el, ValidateACMEIssuerChallengeSolverConfig(&sol, fldPath.Child("solvers").Index(i))...) // #nosec G601 -- False positive. See https://github.com/golang/go/discussions/56010 } return el, warnings @@ -181,9 +197,31 @@ func ValidateACMEIssuerChallengeSolverHTTP01Config(http01 *cmacme.ACMEChallengeS func ValidateACMEIssuerChallengeSolverHTTP01IngressConfig(ingress *cmacme.ACMEChallengeSolverHTTP01Ingress, fldPath *field.Path) field.ErrorList { el := field.ErrorList{} - if ingress.Class != nil && len(ingress.Name) > 0 { - el = append(el, field.Forbidden(fldPath, "only one of 'name' or 'class' should be specified")) + numFieldsSpecified := 0 + if ingress.Class != nil { + numFieldsSpecified++ + } + if ingress.IngressClassName != nil { + numFieldsSpecified++ + } + if len(ingress.Name) > 0 { + numFieldsSpecified++ + } + if numFieldsSpecified > 1 { + el = append(el, field.Forbidden(fldPath, "only one of 'ingressClassName', 'name' or 'class' should be specified")) } + + // Since "class" used to be a free string, let's have a stricter validation + // for "ingressClassName" since it is expected to be a valid resource name. + // A notable example is "azure/application-gateway" that is a valid value + // for "class" but not for "ingressClassName". + if ingress.IngressClassName != nil { + errs := validation.IsDNS1123Subdomain(*ingress.IngressClassName) + if len(errs) > 0 { + el = append(el, field.Invalid(fldPath.Child("ingressClassName"), *ingress.IngressClassName, "must be a valid IngressClass name: "+strings.Join(errs, ", "))) + } + } + switch ingress.ServiceType { case "", corev1.ServiceTypeClusterIP, corev1.ServiceTypeNodePort: default: @@ -217,6 +255,11 @@ func ValidateCAIssuerConfig(iss *certmanager.CAIssuer, fldPath *field.Path) fiel el = append(el, field.Invalid(fldPath.Child("ocspServer").Index(i), ocspURL, "must be a valid URL, e.g., http://ocsp.int-x3.letsencrypt.org")) } } + for i, issuerURL := range iss.IssuingCertificateURLs { + if issuerURL == "" { + el = append(el, field.Invalid(fldPath.Child("issuingCertificateURLs").Index(i), issuerURL, "must be a valid URL")) + } + } return el } @@ -226,36 +269,129 @@ func ValidateSelfSignedIssuerConfig(iss *certmanager.SelfSignedIssuer, fldPath * func ValidateVaultIssuerConfig(iss *certmanager.VaultIssuer, fldPath *field.Path) field.ErrorList { el := field.ErrorList{} + if len(iss.Server) == 0 { el = append(el, field.Required(fldPath.Child("server"), "")) } + if len(iss.Path) == 0 { el = append(el, field.Required(fldPath.Child("path"), "")) } - // check if caBundle is valid - certs := iss.CABundle - if len(certs) > 0 { - caCertPool := x509.NewCertPool() - ok := caCertPool.AppendCertsFromPEM(certs) - if !ok { - el = append(el, field.Invalid(fldPath.Child("caBundle"), "", "Specified CA bundle is invalid")) + if len(iss.CABundle) > 0 { + if err := validateCABundleNotEmpty(iss.CABundle); err != nil { + el = append(el, field.Invalid(fldPath.Child("caBundle"), "", err.Error())) } } if len(iss.CABundle) > 0 && iss.CABundleSecretRef != nil { - el = append(el, field.Invalid(fldPath.Child("caBundle"), iss.CABundle, "specified caBundle and caBundleSecretRef cannot be used together")) + // We don't use iss.CABundle for the "value interface{}" argument to field.Invalid for caBundle + // since printing the whole bundle verbatim won't help diagnose any issues + el = append(el, field.Invalid(fldPath.Child("caBundle"), "", "specified caBundle and caBundleSecretRef cannot be used together")) el = append(el, field.Invalid(fldPath.Child("caBundleSecretRef"), iss.CABundleSecretRef.Name, "specified caBundleSecretRef and caBundle cannot be used together")) } + if iss.ClientCertSecretRef != nil && iss.ClientKeySecretRef == nil { + el = append(el, field.Invalid(fldPath.Child("clientKeySecretRef"), "", "clientKeySecretRef must be provided when defining the clientCertSecretRef")) + } else if iss.ClientCertSecretRef == nil && iss.ClientKeySecretRef != nil { + el = append(el, field.Invalid(fldPath.Child("clientCertSecretRef"), "", "clientCertSecretRef must be provided when defining the clientKeySecretRef")) + } + + el = append(el, ValidateVaultIssuerAuth(&iss.Auth, fldPath.Child("auth"))...) + + return el +} + +func ValidateVaultIssuerAuth(auth *certmanager.VaultAuth, fldPath *field.Path) field.ErrorList { + el := field.ErrorList{} + + unionCount := 0 + if auth.TokenSecretRef != nil { + unionCount++ + } + + if auth.AppRole != nil { + if auth.AppRole.RoleId == "" { + el = append(el, field.Required(fldPath.Child("appRole", "roleId"), "")) + } + + if auth.AppRole.SecretRef.Name == "" { + el = append(el, field.Required(fldPath.Child("appRole", "secretRef", "name"), "")) + } + unionCount++ + } + + if auth.ClientCertificate != nil { + unionCount++ + } + + if auth.Kubernetes != nil { + unionCount++ + + if auth.Kubernetes.Role == "" { + el = append(el, field.Required(fldPath.Child("kubernetes", "role"), "")) + } + + kubeCount := 0 + if len(auth.Kubernetes.SecretRef.Name) > 0 { + kubeCount++ + } + + if auth.Kubernetes.ServiceAccountRef != nil { + kubeCount++ + if len(auth.Kubernetes.ServiceAccountRef.Name) == 0 { + el = append(el, field.Required(fldPath.Child("kubernetes", "serviceAccountRef", "name"), "")) + } + } + + if kubeCount == 0 { + el = append(el, field.Required(fldPath.Child("kubernetes"), "please supply one of: secretRef, serviceAccountRef")) + } + if kubeCount > 1 { + el = append(el, field.Forbidden(fldPath.Child("kubernetes"), "please supply one of: secretRef, serviceAccountRef")) + } + } + + if unionCount == 0 { + el = append(el, field.Required(fldPath, "please supply one of: appRole, kubernetes, tokenSecretRef, clientCertificate")) + } + + // Due to the fact that there has not been any "oneOf" validation on + // tokenSecretRef, appRole, and kubernetes, people may already have created + // Issuer resources in which they have set two of these fields instead of + // one. To avoid breaking these manifests, we don't check that the user has + // set a single field among these three. Instead, we documented in the API + // that it is the first field that is set gets used. + return el - // TODO: add validation for Vault authentication types } func ValidateVenafiTPP(tpp *certmanager.VenafiTPP, fldPath *field.Path) (el field.ErrorList) { if tpp.URL == "" { el = append(el, field.Required(fldPath.Child("url"), "")) } + + // TODO: validate CABundle using validateCABundleNotEmpty + + // Validate only one of CABundle/CABundleSecretRef is passed + el = append(el, validateVenafiTPPCABundleUnique(tpp, fldPath)...) + + return el +} + +func validateVenafiTPPCABundleUnique(tpp *certmanager.VenafiTPP, fldPath *field.Path) (el field.ErrorList) { + numCAs := 0 + if len(tpp.CABundle) > 0 { + numCAs++ + } + if tpp.CABundleSecretRef != nil { + numCAs++ + } + + if numCAs > 1 { + el = append(el, field.Forbidden(fldPath, "may not specify more than one of caBundle/caBundleSecretRef as TPP CA Bundle")) + } + return el } @@ -338,15 +474,20 @@ func ValidateACMEChallengeSolverDNS01(p *cmacme.ACMEChallengeSolverDNS01, fldPat el = append(el, field.Required(fldPath.Child("azureDNS", "tenantID"), "")) } if p.AzureDNS.ManagedIdentity != nil { - el = append(el, field.Forbidden(fldPath.Child("azureDNS", "managedIdentity"), "managed identity can not be used at the same time as clientID, clientSecretSecretRef or tenantID")) + el = append(el, field.Forbidden(fldPath.Child("azureDNS", "managedIdentity"), "managed identity cannot be used at the same time as clientID, clientSecretSecretRef or tenantID")) } - } else { - // using managed identity - if p.AzureDNS.ManagedIdentity != nil && len(p.AzureDNS.ManagedIdentity.ClientID) > 0 && len(p.AzureDNS.ManagedIdentity.ResourceID) > 0 { + } else if p.AzureDNS.ManagedIdentity != nil { + if len(p.AzureDNS.ManagedIdentity.ClientID) > 0 && len(p.AzureDNS.ManagedIdentity.ResourceID) > 0 { el = append(el, field.Forbidden(fldPath.Child("azureDNS", "managedIdentity"), "managedIdentityClientID and managedIdentityResourceID cannot both be specified")) } - + if len(p.AzureDNS.ManagedIdentity.TenantID) > 0 && len(p.AzureDNS.ManagedIdentity.ResourceID) > 0 { + el = append(el, field.Forbidden(fldPath.Child("azureDNS", "managedIdentity"), "managedIdentityTenantID and managedIdentityResourceID cannot both be specified")) + } + if len(p.AzureDNS.ManagedIdentity.TenantID) > 0 && len(p.AzureDNS.ManagedIdentity.ClientID) == 0 { + el = append(el, field.Required(fldPath.Child("azureDNS", "managedIdentity"), "managedIdentityClientID is required when using managedIdentityTenantID")) + } } + // SubscriptionID must always be defined if len(p.AzureDNS.SubscriptionID) == 0 { el = append(el, field.Required(fldPath.Child("azureDNS", "subscriptionID"), "")) @@ -405,10 +546,6 @@ func ValidateACMEChallengeSolverDNS01(p *cmacme.ACMEChallengeSolverDNS01, fldPat el = append(el, field.Forbidden(fldPath.Child("route53"), "may not specify more than one provider type")) } else { numProviders++ - // region is the only required field for route53 as ambient credentials can be used instead - if len(p.Route53.Region) == 0 { - el = append(el, field.Required(fldPath.Child("route53", "region"), "")) - } // We don't include a validation here asserting that either the // AccessKeyID or SecretAccessKeyID must be specified, because it is // valid to use neither when using ambient credentials. @@ -466,7 +603,7 @@ func ValidateACMEChallengeSolverDNS01(p *cmacme.ACMEChallengeSolverDNS01, fldPat } if len(ValidateSecretKeySelector(&p.RFC2136.TSIGSecret, fldPath.Child("rfc2136", "tsigSecretSecretRef"))) == 0 { - if len(p.RFC2136.TSIGKeyName) <= 0 { + if len(p.RFC2136.TSIGKeyName) == 0 { el = append(el, field.Required(fldPath.Child("rfc2136", "tsigKeyName"), "")) } @@ -500,3 +637,22 @@ func ValidateSecretKeySelector(sks *cmmeta.SecretKeySelector, fldPath *field.Pat } return el } + +// validateCABundleNotEmpty performs a soft check on the CA bundle to see if there's at least one +// valid CA certificate inside. +// This uses the standard library crypto/x509.CertPool.AppendCertsFromPEM function, which +// skips over invalid certificates rather than rejecting them. +func validateCABundleNotEmpty(bundle []byte) error { + // TODO: Change this function to actually validate certificates so that invalid certs + // are rejected or at least warned on. + // For example, something like: https://github.com/cert-manager/trust-manager/blob/21c839ff1128990e049eaf23000a9a8d6716c89e/pkg/util/pem.go#L26-L81 + + pool := x509.NewCertPool() + + ok := pool.AppendCertsFromPEM(bundle) + if !ok { + return fmt.Errorf("cert bundle didn't contain any valid certificates") + } + + return nil +} diff --git a/internal/apis/certmanager/validation/issuer_test.go b/internal/apis/certmanager/validation/issuer_test.go index 72036425b00..34d6c6d40ef 100644 --- a/internal/apis/certmanager/validation/issuer_test.go +++ b/internal/apis/certmanager/validation/issuer_test.go @@ -25,7 +25,8 @@ import ( corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/util/validation/field" "k8s.io/utils/clock" - gwapi "sigs.k8s.io/gateway-api/apis/v1alpha2" + "k8s.io/utils/ptr" + gwapi "sigs.k8s.io/gateway-api/apis/v1" cmacme "github.com/cert-manager/cert-manager/internal/apis/acme" cmapi "github.com/cert-manager/cert-manager/internal/apis/certmanager" @@ -46,6 +47,7 @@ var ( Key: "validkey", } // TODO (JS): Missing test for validCloudflareProvider + // nolint: unused validCloudflareProvider = cmacme.ACMEIssuerDNS01ProviderCloudflare{ APIKey: &validSecretKeyRef, Email: "valid", @@ -70,7 +72,7 @@ func TestValidateVaultIssuerConfig(t *testing.T) { clock.RealClock{}, ).CertBytes - fldPath := field.NewPath("") + fldPath := field.NewPath("spec") scenarios := map[string]struct { spec *cmapi.VaultIssuer errs []*field.Error @@ -86,9 +88,12 @@ func TestValidateVaultIssuerConfig(t *testing.T) { Name: "test-secret", }, }, + Auth: cmapi.VaultAuth{ + TokenSecretRef: &validSecretKeyRef, + }, }, errs: []*field.Error{ - field.Invalid(fldPath.Child("caBundle"), caBundle, "specified caBundle and caBundleSecretRef cannot be used together"), + field.Invalid(fldPath.Child("caBundle"), "", "specified caBundle and caBundleSecretRef cannot be used together"), field.Invalid(fldPath.Child("caBundleSecretRef"), "test-secret", "specified caBundleSecretRef and caBundle cannot be used together"), }, }, @@ -100,16 +105,68 @@ func TestValidateVaultIssuerConfig(t *testing.T) { errs: []*field.Error{ field.Required(fldPath.Child("server"), ""), field.Required(fldPath.Child("path"), ""), + field.Required(fldPath.Child("auth"), "please supply one of: appRole, kubernetes, tokenSecretRef, clientCertificate"), }, }, - "vault issuer with invalid fields": { + "vault issuer with a CA bundle containing no valid certificates": { spec: &cmapi.VaultIssuer{ Server: "something", Path: "a/b/c", CABundle: []byte("invalid"), + Auth: cmapi.VaultAuth{ + TokenSecretRef: &validSecretKeyRef, + }, }, errs: []*field.Error{ - field.Invalid(fldPath.Child("caBundle"), "", "Specified CA bundle is invalid"), + field.Invalid(fldPath.Child("caBundle"), "", "cert bundle didn't contain any valid certificates"), + }, + }, + "vault issuer define clientCertSecretRef but not clientKeySecretRef": { + spec: &cmapi.VaultIssuer{ + Server: "https://vault.example.com", + Path: "secret/path", + CABundleSecretRef: &cmmeta.SecretKeySelector{ + Key: "ca.crt", + LocalObjectReference: cmmeta.LocalObjectReference{ + Name: "test-secret", + }, + }, + ClientCertSecretRef: &cmmeta.SecretKeySelector{ + Key: "tls.crt", + LocalObjectReference: cmmeta.LocalObjectReference{ + Name: "test-secret", + }, + }, + Auth: cmapi.VaultAuth{ + TokenSecretRef: &validSecretKeyRef, + }, + }, + errs: []*field.Error{ + field.Invalid(fldPath.Child("clientKeySecretRef"), "", "clientKeySecretRef must be provided when defining the clientCertSecretRef"), + }, + }, + "vault issuer define clientKeySecretRef but not clientCertSecretRef": { + spec: &cmapi.VaultIssuer{ + Server: "https://vault.example.com", + Path: "secret/path", + CABundleSecretRef: &cmmeta.SecretKeySelector{ + Key: "ca.crt", + LocalObjectReference: cmmeta.LocalObjectReference{ + Name: "test-secret", + }, + }, + ClientKeySecretRef: &cmmeta.SecretKeySelector{ + Key: "tls.key", + LocalObjectReference: cmmeta.LocalObjectReference{ + Name: "test-secret", + }, + }, + Auth: cmapi.VaultAuth{ + TokenSecretRef: &validSecretKeyRef, + }, + }, + errs: []*field.Error{ + field.Invalid(fldPath.Child("clientCertSecretRef"), "", "clientCertSecretRef must be provided when defining the clientKeySecretRef"), }, }, } @@ -130,8 +187,180 @@ func TestValidateVaultIssuerConfig(t *testing.T) { } } +func TestValidateVaultIssuerAuth(t *testing.T) { + fldPath := field.NewPath("spec.auth") + scenarios := map[string]struct { + auth *cmapi.VaultAuth + errs []*field.Error + }{ + // For backwards compatibility, we allow the user to set all auth types. + // We have documented in the API the order of precedence. + "valid auth: all three auth types can be set simultaneously": { + auth: &cmapi.VaultAuth{ + AppRole: &cmapi.VaultAppRole{ + RoleId: "role-id", + SecretRef: cmmeta.SecretKeySelector{ + LocalObjectReference: cmmeta.LocalObjectReference{Name: "secret"}, + Key: "key", + }, + Path: "path", + }, + TokenSecretRef: &validSecretKeyRef, + Kubernetes: &cmapi.VaultKubernetesAuth{ + Path: "path", + Role: "role", + ServiceAccountRef: &cmapi.ServiceAccountRef{ + Name: "service-account", + }, + }, + }, + }, + "valid auth.tokenSecretRef": { + auth: &cmapi.VaultAuth{ + TokenSecretRef: &cmmeta.SecretKeySelector{ + LocalObjectReference: cmmeta.LocalObjectReference{ + Name: "secret", + }, + Key: "key", + }, + }, + }, + // The default value for auth.tokenSecretRef.key is 'token'. This + // behavior is not documented in the API reference, but we keep it for + // backward compatibility. + "invalid auth.tokenSecretRef: key can be omitted": { + auth: &cmapi.VaultAuth{ + TokenSecretRef: &cmmeta.SecretKeySelector{ + LocalObjectReference: cmmeta.LocalObjectReference{ + Name: "secret", + }, + }, + }, + }, + "valid auth.appRole": { + auth: &cmapi.VaultAuth{ + AppRole: &cmapi.VaultAppRole{ + RoleId: "role-id", + SecretRef: cmmeta.SecretKeySelector{ + LocalObjectReference: cmmeta.LocalObjectReference{Name: "secret"}, + Key: "key", + }, + Path: "path", + }, + }, + }, + // TODO(mael): The reason we allow the user to omit the key but we say + // in the documentation that "key must be specified" is because the + // controller-side validation doesn't check that the key is empty. We + // should add a check for that. + "valid auth.appRole: key can be omitted": { + auth: &cmapi.VaultAuth{ + AppRole: &cmapi.VaultAppRole{ + RoleId: "role-id", + SecretRef: cmmeta.SecretKeySelector{ + LocalObjectReference: cmmeta.LocalObjectReference{Name: "secret"}, + }, + Path: "path", + }, + }, + }, + "invalid auth.appRole: roleId is required": { + auth: &cmapi.VaultAuth{ + AppRole: &cmapi.VaultAppRole{ + SecretRef: cmmeta.SecretKeySelector{ + LocalObjectReference: cmmeta.LocalObjectReference{Name: "secret"}, + Key: "key", + }, + Path: "path", + }, + }, + errs: []*field.Error{ + field.Required(fldPath.Child("appRole").Child("roleId"), ""), + }, + }, + "valid auth.clientCertificate: all fields can be empty": { + auth: &cmapi.VaultAuth{ + ClientCertificate: &cmapi.VaultClientCertificateAuth{}, + }, + }, + // The field auth.kubernetes.secretRef.key defaults to 'token' if + // not specified. + "valid auth.kubernetes.secretRef: key can be left empty": { + auth: &cmapi.VaultAuth{ + Kubernetes: &cmapi.VaultKubernetesAuth{ + SecretRef: cmmeta.SecretKeySelector{ + LocalObjectReference: cmmeta.LocalObjectReference{Name: "secret"}, + }, + Role: "role", + }, + }, + }, + "valid auth.kubernetes.serviceAccountRef": { + auth: &cmapi.VaultAuth{ + Kubernetes: &cmapi.VaultKubernetesAuth{ + Path: "path", + Role: "role", + ServiceAccountRef: &cmapi.ServiceAccountRef{ + Name: "service-account", + }, + }, + }, + }, + "invalid auth.kubernetes: role is required": { + auth: &cmapi.VaultAuth{ + Kubernetes: &cmapi.VaultKubernetesAuth{ + Path: "path", + ServiceAccountRef: &cmapi.ServiceAccountRef{ + Name: "service-account", + }, + }, + }, + errs: []*field.Error{ + field.Required(fldPath.Child("kubernetes").Child("role"), ""), + }, + }, + "invalid auth.kubernetes: secretRef and serviceAccountRef mutually exclusive": { + auth: &cmapi.VaultAuth{ + Kubernetes: &cmapi.VaultKubernetesAuth{ + SecretRef: cmmeta.SecretKeySelector{ + LocalObjectReference: cmmeta.LocalObjectReference{Name: "secret"}, + }, + ServiceAccountRef: &cmapi.ServiceAccountRef{ + Name: "service-account", + }, + Role: "role", + }, + }, + errs: []*field.Error{ + field.Forbidden(fldPath.Child("kubernetes"), "please supply one of: secretRef, serviceAccountRef"), + }, + }, + } + for n, s := range scenarios { + t.Run(n, func(t *testing.T) { + errs := ValidateVaultIssuerAuth(s.auth, fldPath) + if len(errs) != len(s.errs) { + t.Errorf("Expected %v but got %v", s.errs, errs) + return + } + for i, e := range errs { + expectedErr := s.errs[i] + if !reflect.DeepEqual(e, expectedErr) { + t.Errorf("Expected %v but got %v", expectedErr, e) + } + } + }) + } +} + func TestValidateACMEIssuerConfig(t *testing.T) { - fldPath := field.NewPath("") + fldPath := (*field.Path)(nil) + + caBundle := unitcrypto.MustCreateCryptoBundle(t, + &pubcmapi.Certificate{Spec: pubcmapi.CertificateSpec{CommonName: "test"}}, + clock.RealClock{}, + ).CertBytes + scenarios := map[string]struct { spec *cmacme.ACMEIssuer errs []*field.Error @@ -147,6 +376,44 @@ func TestValidateACMEIssuerConfig(t *testing.T) { field.Required(fldPath.Child("server"), "acme server URL is a required field"), }, }, + "acme issuer with an invalid CA bundle": { + spec: &cmacme.ACMEIssuer{ + Email: "valid-email", + Server: "valid-server", + CABundle: []byte("abc123"), + PrivateKey: validSecretKeyRef, + Solvers: []cmacme.ACMEChallengeSolver{ + { + DNS01: &cmacme.ACMEChallengeSolverDNS01{ + CloudDNS: &validCloudDNSProvider, + }, + }, + }, + }, + errs: []*field.Error{ + field.Invalid(fldPath.Child("caBundle"), "", "cert bundle didn't contain any valid certificates"), + }, + }, + "acme issuer with both a CA bundle and SkipTLSVerify": { + spec: &cmacme.ACMEIssuer{ + Email: "valid-email", + Server: "valid-server", + CABundle: caBundle, + SkipTLSVerify: true, + PrivateKey: validSecretKeyRef, + Solvers: []cmacme.ACMEChallengeSolver{ + { + DNS01: &cmacme.ACMEChallengeSolverDNS01{ + CloudDNS: &validCloudDNSProvider, + }, + }, + }, + }, + errs: []*field.Error{ + field.Invalid(fldPath.Child("caBundle"), "", "caBundle and skipTLSVerify are mutually exclusive and cannot both be set"), + field.Invalid(fldPath.Child("skipTLSVerify"), true, "caBundle and skipTLSVerify are mutually exclusive and cannot both be set"), + }, + }, "acme solver without any config": { spec: &cmacme.ACMEIssuer{ Email: "valid-email", @@ -432,7 +699,8 @@ func TestValidateACMEIssuerConfig(t *testing.T) { } func TestValidateIssuerSpec(t *testing.T) { - fldPath := field.NewPath("") + fldPath := (*field.Path)(nil) + scenarios := map[string]struct { spec *cmapi.IssuerSpec errs field.ErrorList @@ -456,7 +724,7 @@ func TestValidateIssuerSpec(t *testing.T) { }, errs: []*field.Error{field.Required(fldPath.Child("ca", "secretName"), "")}, }, - "valid self signed issuer": { + "valid self-signed issuer": { spec: &cmapi.IssuerSpec{ IssuerConfig: cmapi.IssuerConfig{ SelfSigned: &cmapi.SelfSignedIssuer{}, @@ -525,6 +793,30 @@ func TestValidateIssuerSpec(t *testing.T) { field.Invalid(fldPath.Child("ca", "ocspServer").Index(0), "", `must be a valid URL, e.g., http://ocsp.int-x3.letsencrypt.org`), }, }, + "valid IssuingCertificateURLs": { + spec: &cmapi.IssuerSpec{ + IssuerConfig: cmapi.IssuerConfig{ + CA: &cmapi.CAIssuer{ + SecretName: "valid", + IssuingCertificateURLs: []string{"http://ca.example.com/ca.crt"}, + }, + }, + }, + errs: []*field.Error{}, + }, + "invalid IssuingCertificateURLs": { + spec: &cmapi.IssuerSpec{ + IssuerConfig: cmapi.IssuerConfig{ + CA: &cmapi.CAIssuer{ + SecretName: "valid", + IssuingCertificateURLs: []string{""}, + }, + }, + }, + errs: []*field.Error{ + field.Invalid(fldPath.Child("ca", "issuingCertificateURLs").Index(0), "", `must be a valid URL`), + }, + }, } for n, s := range scenarios { t.Run(n, func(t *testing.T) { @@ -536,20 +828,26 @@ func TestValidateIssuerSpec(t *testing.T) { } func TestValidateACMEIssuerHTTP01Config(t *testing.T) { - fldPath := field.NewPath("") + fldPath := (*field.Path)(nil) + scenarios := map[string]struct { isExpectedFailure bool cfg *cmacme.ACMEChallengeSolverHTTP01 errs []*field.Error }{ - "ingress field specified": { + "ingress name field specified": { cfg: &cmacme.ACMEChallengeSolverHTTP01{ Ingress: &cmacme.ACMEChallengeSolverHTTP01Ingress{Name: "abc"}, }, }, "ingress class field specified": { cfg: &cmacme.ACMEChallengeSolverHTTP01{ - Ingress: &cmacme.ACMEChallengeSolverHTTP01Ingress{Class: strPtr("abc")}, + Ingress: &cmacme.ACMEChallengeSolverHTTP01Ingress{Class: ptr.To("abc")}, + }, + }, + "ingressClassName field specified": { + cfg: &cmacme.ACMEChallengeSolverHTTP01{ + Ingress: &cmacme.ACMEChallengeSolverHTTP01Ingress{IngressClassName: ptr.To("abc")}, }, }, "neither field specified": { @@ -563,15 +861,59 @@ func TestValidateACMEIssuerHTTP01Config(t *testing.T) { field.Required(fldPath, "no HTTP01 solver type configured"), }, }, - "both fields specified": { + "both ingress class and ingressClassName specified": { + cfg: &cmacme.ACMEChallengeSolverHTTP01{ + Ingress: &cmacme.ACMEChallengeSolverHTTP01Ingress{ + Class: ptr.To("abc"), + IngressClassName: ptr.To("abc"), + }, + }, + errs: []*field.Error{ + field.Forbidden(fldPath.Child("ingress"), "only one of 'ingressClassName', 'name' or 'class' should be specified"), + }, + }, + "both ingress class and ingress name specified": { cfg: &cmacme.ACMEChallengeSolverHTTP01{ Ingress: &cmacme.ACMEChallengeSolverHTTP01Ingress{ + Class: ptr.To("abc"), Name: "abc", - Class: strPtr("abc"), }, }, errs: []*field.Error{ - field.Forbidden(fldPath.Child("ingress"), "only one of 'name' or 'class' should be specified"), + field.Forbidden(fldPath.Child("ingress"), "only one of 'ingressClassName', 'name' or 'class' should be specified"), + }, + }, + "both ingressClassName and ingress name specified": { + cfg: &cmacme.ACMEChallengeSolverHTTP01{ + Ingress: &cmacme.ACMEChallengeSolverHTTP01Ingress{ + IngressClassName: ptr.To("abc"), + Name: "abc", + }, + }, + errs: []*field.Error{ + field.Forbidden(fldPath.Child("ingress"), "only one of 'ingressClassName', 'name' or 'class' should be specified"), + }, + }, + "all three fields specified": { + cfg: &cmacme.ACMEChallengeSolverHTTP01{ + Ingress: &cmacme.ACMEChallengeSolverHTTP01Ingress{ + Name: "abc", + Class: ptr.To("abc"), + IngressClassName: ptr.To("abc"), + }, + }, + errs: []*field.Error{ + field.Forbidden(fldPath.Child("ingress"), "only one of 'ingressClassName', 'name' or 'class' should be specified"), + }, + }, + "ingressClassName is invalid": { + cfg: &cmacme.ACMEChallengeSolverHTTP01{ + Ingress: &cmacme.ACMEChallengeSolverHTTP01Ingress{ + IngressClassName: ptr.To("azure/application-gateway"), + }, + }, + errs: []*field.Error{ + field.Invalid(fldPath.Child("ingress", "ingressClassName"), "azure/application-gateway", `must be a valid IngressClass name: a lowercase RFC 1123 subdomain must consist of lower case alphanumeric characters, '-' or '.', and must start and end with an alphanumeric character (e.g. 'example.com', regex used for validation is '[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*')`), }, }, "acme issuer with valid http01 service config serviceType ClusterIP": { @@ -730,20 +1072,10 @@ func TestValidateACMEIssuerDNS01Config(t *testing.T) { field.Required(fldPath.Child("cloudflare", "email"), ""), }, }, - "missing route53 region": { + "empty route53 field should be valid because ambient credentials and region may be used instead": { cfg: &cmacme.ACMEChallengeSolverDNS01{ Route53: &cmacme.ACMEIssuerDNS01ProviderRoute53{}, }, - errs: []*field.Error{ - field.Required(fldPath.Child("route53", "region"), ""), - }, - }, - "missing route53 accessKeyID and accessKeyIDSecretRef should be valid because ambient credentials may be used instead": { - cfg: &cmacme.ACMEChallengeSolverDNS01{ - Route53: &cmacme.ACMEIssuerDNS01ProviderRoute53{ - Region: "valid", - }, - }, errs: []*field.Error{}, }, "both route53 accessKeyID and accessKeyIDSecretRef specified": { @@ -953,7 +1285,7 @@ func TestValidateACMEIssuerDNS01Config(t *testing.T) { errs: []*field.Error{ field.Required(fldPath.Child("azureDNS", "clientSecretSecretRef"), ""), field.Required(fldPath.Child("azureDNS", "tenantID"), ""), - field.Forbidden(fldPath.Child("azureDNS", "managedIdentity"), "managed identity can not be used at the same time as clientID, clientSecretSecretRef or tenantID"), + field.Forbidden(fldPath.Child("azureDNS", "managedIdentity"), "managed identity cannot be used at the same time as clientID, clientSecretSecretRef or tenantID"), field.Required(fldPath.Child("azureDNS", "subscriptionID"), ""), field.Required(fldPath.Child("azureDNS", "resourceGroupName"), ""), }, @@ -970,11 +1302,42 @@ func TestValidateACMEIssuerDNS01Config(t *testing.T) { errs: []*field.Error{ field.Required(fldPath.Child("azureDNS", "clientID"), ""), field.Required(fldPath.Child("azureDNS", "clientSecretSecretRef"), ""), - field.Forbidden(fldPath.Child("azureDNS", "managedIdentity"), "managed identity can not be used at the same time as clientID, clientSecretSecretRef or tenantID"), + field.Forbidden(fldPath.Child("azureDNS", "managedIdentity"), "managed identity cannot be used at the same time as clientID, clientSecretSecretRef or tenantID"), + field.Required(fldPath.Child("azureDNS", "subscriptionID"), ""), + field.Required(fldPath.Child("azureDNS", "resourceGroupName"), ""), + }, + }, + + "invalid azuredns managedIdentity tenantID used without managedIdentity clientID ": { + cfg: &cmacme.ACMEChallengeSolverDNS01{ + AzureDNS: &cmacme.ACMEIssuerDNS01ProviderAzureDNS{ + ManagedIdentity: &cmacme.AzureManagedIdentity{ + TenantID: "some-tenant-id", + }, + }, + }, + errs: []*field.Error{ + field.Required(fldPath.Child("azureDNS", "managedIdentity"), "managedIdentityClientID is required when using managedIdentityTenantID"), field.Required(fldPath.Child("azureDNS", "subscriptionID"), ""), field.Required(fldPath.Child("azureDNS", "resourceGroupName"), ""), }, }, + "invalid azuredns managedIdentity tenantID used with resourceID": { + cfg: &cmacme.ACMEChallengeSolverDNS01{ + AzureDNS: &cmacme.ACMEIssuerDNS01ProviderAzureDNS{ + SubscriptionID: "test", + ResourceGroupName: "test", + ManagedIdentity: &cmacme.AzureManagedIdentity{ + ResourceID: "test", + TenantID: "some-tenant-id", + }, + }, + }, + errs: []*field.Error{ + field.Forbidden(fldPath.Child("azureDNS", "managedIdentity"), "managedIdentityTenantID and managedIdentityResourceID cannot both be specified"), + field.Required(fldPath.Child("azureDNS", "managedIdentity"), "managedIdentityClientID is required when using managedIdentityTenantID"), + }, + }, "invalid azuredns clientSecret used with managedIdentity": { cfg: &cmacme.ACMEChallengeSolverDNS01{ AzureDNS: &cmacme.ACMEIssuerDNS01ProviderAzureDNS{ @@ -992,7 +1355,7 @@ func TestValidateACMEIssuerDNS01Config(t *testing.T) { errs: []*field.Error{ field.Required(fldPath.Child("azureDNS", "clientID"), ""), field.Required(fldPath.Child("azureDNS", "tenantID"), ""), - field.Forbidden(fldPath.Child("azureDNS", "managedIdentity"), "managed identity can not be used at the same time as clientID, clientSecretSecretRef or tenantID"), + field.Forbidden(fldPath.Child("azureDNS", "managedIdentity"), "managed identity cannot be used at the same time as clientID, clientSecretSecretRef or tenantID"), field.Required(fldPath.Child("azureDNS", "subscriptionID"), ""), field.Required(fldPath.Child("azureDNS", "resourceGroupName"), ""), }, @@ -1030,7 +1393,7 @@ func TestValidateACMEIssuerDNS01Config(t *testing.T) { }, errs: []*field.Error{}, }, - "invalid azuredns managedIdentity with both cliendID and resourceID": { + "invalid azuredns managedIdentity with both clientID and resourceID": { cfg: &cmacme.ACMEChallengeSolverDNS01{ AzureDNS: &cmacme.ACMEIssuerDNS01ProviderAzureDNS{ SubscriptionID: "test", @@ -1217,7 +1580,8 @@ func TestValidateSecretKeySelector(t *testing.T) { validKey := "key" // invalidName := cmmeta.LocalObjectReference{"-name-"} // invalidKey := "-key-" - fldPath := field.NewPath("") + fldPath := (*field.Path)(nil) + scenarios := map[string]struct { isExpectedFailure bool selector *cmmeta.SecretKeySelector @@ -1341,6 +1705,10 @@ func TestValidateVenafiIssuerConfig(t *testing.T) { } func TestValidateVenafiTPP(t *testing.T) { + caBundle := unitcrypto.MustCreateCryptoBundle(t, + &pubcmapi.Certificate{Spec: pubcmapi.CertificateSpec{CommonName: "test"}}, + clock.RealClock{}, + ).CertBytes fldPath := field.NewPath("test") scenarios := map[string]struct { cfg *cmapi.VenafiTPP @@ -1357,6 +1725,21 @@ func TestValidateVenafiTPP(t *testing.T) { field.Required(fldPath.Child("url"), ""), }, }, + "venafi TPP issuer defines both caBundle and caBundleSecretRef": { + cfg: &cmapi.VenafiTPP{ + URL: "https://tpp.example.com/vedsdk", + CABundle: caBundle, + CABundleSecretRef: &cmmeta.SecretKeySelector{ + Key: "ca.crt", + LocalObjectReference: cmmeta.LocalObjectReference{ + Name: "test-secret", + }, + }, + }, + errs: []*field.Error{ + field.Forbidden(fldPath, "may not specify more than one of caBundle/caBundleSecretRef as TPP CA Bundle"), + }, + }, } for n, s := range scenarios { diff --git a/internal/apis/certmanager/validation/util/nameserver_test.go b/internal/apis/certmanager/validation/util/nameserver_test.go index 94eb2f08095..09b4849af54 100644 --- a/internal/apis/certmanager/validation/util/nameserver_test.go +++ b/internal/apis/certmanager/validation/util/nameserver_test.go @@ -28,7 +28,7 @@ func TestValidNameserver(t *testing.T) { wantErr bool }{ { - name: "IPv4 with no port should should return port 53", + name: "IPv4 with no port should return port 53", nameserver: "8.8.8.8", want: "8.8.8.8:53", }, @@ -43,7 +43,7 @@ func TestValidNameserver(t *testing.T) { want: "8.8.8.8:5353", }, { - name: "IPv6 with no port should should return port 53", + name: "IPv6 with no port should return port 53", nameserver: "[2001:db8::1]", want: "[2001:db8::1]:53", }, @@ -58,7 +58,7 @@ func TestValidNameserver(t *testing.T) { want: "[2001:db8::1]:5353", }, { - name: "DNS name with no port should should return port 53", + name: "DNS name with no port should return port 53", nameserver: "nameserver.com", want: "nameserver.com:53", }, diff --git a/internal/apis/certmanager/validation/warnings.go b/internal/apis/certmanager/validation/warnings.go index dfe79eacc6a..a3af1f6e51a 100644 --- a/internal/apis/certmanager/validation/warnings.go +++ b/internal/apis/certmanager/validation/warnings.go @@ -21,4 +21,6 @@ package validation const ( // deprecatedACMEEABKeyAlgorithmField is raised when the deprecated keyAlgorithm field for an ACME issuer's external account binding (EAB) is set. deprecatedACMEEABKeyAlgorithmField = "ACME issuer spec field 'externalAccount.keyAlgorithm' is deprecated. The value of this field will be ignored." + // newDefaultPrivateKeyRotationPolicy is raised when the Certificate.Spec.PrivateKey.RotationPolicy is omitted. + newDefaultPrivateKeyRotationPolicy = "spec.privateKey.rotationPolicy: In cert-manager >= v1.18.0, the default value changed from `Never` to `Always`." ) diff --git a/internal/apis/certmanager/zz_generated.deepcopy.go b/internal/apis/certmanager/zz_generated.deepcopy.go index 121a2b5245c..d64f9cd5e8d 100644 --- a/internal/apis/certmanager/zz_generated.deepcopy.go +++ b/internal/apis/certmanager/zz_generated.deepcopy.go @@ -41,6 +41,11 @@ func (in *CAIssuer) DeepCopyInto(out *CAIssuer) { *out = make([]string, len(*in)) copy(*out, *in) } + if in.IssuingCertificateURLs != nil { + in, out := &in.IssuingCertificateURLs, &out.IssuingCertificateURLs + *out = make([]string, len(*in)) + copy(*out, *in) + } return } @@ -124,12 +129,12 @@ func (in *CertificateKeystores) DeepCopyInto(out *CertificateKeystores) { if in.JKS != nil { in, out := &in.JKS, &out.JKS *out = new(JKSKeystore) - **out = **in + (*in).DeepCopyInto(*out) } if in.PKCS12 != nil { in, out := &in.PKCS12, &out.PKCS12 *out = new(PKCS12Keystore) - **out = **in + (*in).DeepCopyInto(*out) } return } @@ -411,6 +416,11 @@ func (in *CertificateSpec) DeepCopyInto(out *CertificateSpec) { *out = new(v1.Duration) **out = **in } + if in.RenewBeforePercentage != nil { + in, out := &in.RenewBeforePercentage, &out.RenewBeforePercentage + *out = new(int32) + **out = **in + } if in.DNSNames != nil { in, out := &in.DNSNames, &out.DNSNames *out = make([]string, len(*in)) @@ -421,16 +431,21 @@ func (in *CertificateSpec) DeepCopyInto(out *CertificateSpec) { *out = make([]string, len(*in)) copy(*out, *in) } - if in.URISANs != nil { - in, out := &in.URISANs, &out.URISANs + if in.URIs != nil { + in, out := &in.URIs, &out.URIs *out = make([]string, len(*in)) copy(*out, *in) } - if in.EmailSANs != nil { - in, out := &in.EmailSANs, &out.EmailSANs + if in.EmailAddresses != nil { + in, out := &in.EmailAddresses, &out.EmailAddresses *out = make([]string, len(*in)) copy(*out, *in) } + if in.OtherNames != nil { + in, out := &in.OtherNames, &out.OtherNames + *out = make([]OtherName, len(*in)) + copy(*out, *in) + } if in.SecretTemplate != nil { in, out := &in.SecretTemplate, &out.SecretTemplate *out = new(CertificateSecretTemplate) @@ -467,6 +482,11 @@ func (in *CertificateSpec) DeepCopyInto(out *CertificateSpec) { *out = make([]CertificateAdditionalOutputFormat, len(*in)) copy(*out, *in) } + if in.NameConstraints != nil { + in, out := &in.NameConstraints, &out.NameConstraints + *out = new(NameConstraints) + (*in).DeepCopyInto(*out) + } return } @@ -765,7 +785,17 @@ func (in *IssuerStatus) DeepCopy() *IssuerStatus { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *JKSKeystore) DeepCopyInto(out *JKSKeystore) { *out = *in + if in.Alias != nil { + in, out := &in.Alias, &out.Alias + *out = new(string) + **out = **in + } out.PasswordSecretRef = in.PasswordSecretRef + if in.Password != nil { + in, out := &in.Password, &out.Password + *out = new(string) + **out = **in + } return } @@ -779,10 +809,93 @@ func (in *JKSKeystore) DeepCopy() *JKSKeystore { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NameConstraintItem) DeepCopyInto(out *NameConstraintItem) { + *out = *in + if in.DNSDomains != nil { + in, out := &in.DNSDomains, &out.DNSDomains + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.IPRanges != nil { + in, out := &in.IPRanges, &out.IPRanges + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.EmailAddresses != nil { + in, out := &in.EmailAddresses, &out.EmailAddresses + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.URIDomains != nil { + in, out := &in.URIDomains, &out.URIDomains + *out = make([]string, len(*in)) + copy(*out, *in) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NameConstraintItem. +func (in *NameConstraintItem) DeepCopy() *NameConstraintItem { + if in == nil { + return nil + } + out := new(NameConstraintItem) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NameConstraints) DeepCopyInto(out *NameConstraints) { + *out = *in + if in.Permitted != nil { + in, out := &in.Permitted, &out.Permitted + *out = new(NameConstraintItem) + (*in).DeepCopyInto(*out) + } + if in.Excluded != nil { + in, out := &in.Excluded, &out.Excluded + *out = new(NameConstraintItem) + (*in).DeepCopyInto(*out) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NameConstraints. +func (in *NameConstraints) DeepCopy() *NameConstraints { + if in == nil { + return nil + } + out := new(NameConstraints) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *OtherName) DeepCopyInto(out *OtherName) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OtherName. +func (in *OtherName) DeepCopy() *OtherName { + if in == nil { + return nil + } + out := new(OtherName) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *PKCS12Keystore) DeepCopyInto(out *PKCS12Keystore) { *out = *in out.PasswordSecretRef = in.PasswordSecretRef + if in.Password != nil { + in, out := &in.Password, &out.Password + *out = new(string) + **out = **in + } return } @@ -817,6 +930,27 @@ func (in *SelfSignedIssuer) DeepCopy() *SelfSignedIssuer { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ServiceAccountRef) DeepCopyInto(out *ServiceAccountRef) { + *out = *in + if in.TokenAudiences != nil { + in, out := &in.TokenAudiences, &out.TokenAudiences + *out = make([]string, len(*in)) + copy(*out, *in) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServiceAccountRef. +func (in *ServiceAccountRef) DeepCopy() *ServiceAccountRef { + if in == nil { + return nil + } + out := new(ServiceAccountRef) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *VaultAppRole) DeepCopyInto(out *VaultAppRole) { *out = *in @@ -847,10 +981,15 @@ func (in *VaultAuth) DeepCopyInto(out *VaultAuth) { *out = new(VaultAppRole) **out = **in } + if in.ClientCertificate != nil { + in, out := &in.ClientCertificate, &out.ClientCertificate + *out = new(VaultClientCertificateAuth) + **out = **in + } if in.Kubernetes != nil { in, out := &in.Kubernetes, &out.Kubernetes *out = new(VaultKubernetesAuth) - **out = **in + (*in).DeepCopyInto(*out) } return } @@ -865,6 +1004,22 @@ func (in *VaultAuth) DeepCopy() *VaultAuth { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *VaultClientCertificateAuth) DeepCopyInto(out *VaultClientCertificateAuth) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VaultClientCertificateAuth. +func (in *VaultClientCertificateAuth) DeepCopy() *VaultClientCertificateAuth { + if in == nil { + return nil + } + out := new(VaultClientCertificateAuth) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *VaultIssuer) DeepCopyInto(out *VaultIssuer) { *out = *in @@ -879,6 +1034,16 @@ func (in *VaultIssuer) DeepCopyInto(out *VaultIssuer) { *out = new(meta.SecretKeySelector) **out = **in } + if in.ClientCertSecretRef != nil { + in, out := &in.ClientCertSecretRef, &out.ClientCertSecretRef + *out = new(meta.SecretKeySelector) + **out = **in + } + if in.ClientKeySecretRef != nil { + in, out := &in.ClientKeySecretRef, &out.ClientKeySecretRef + *out = new(meta.SecretKeySelector) + **out = **in + } return } @@ -896,6 +1061,11 @@ func (in *VaultIssuer) DeepCopy() *VaultIssuer { func (in *VaultKubernetesAuth) DeepCopyInto(out *VaultKubernetesAuth) { *out = *in out.SecretRef = in.SecretRef + if in.ServiceAccountRef != nil { + in, out := &in.ServiceAccountRef, &out.ServiceAccountRef + *out = new(ServiceAccountRef) + (*in).DeepCopyInto(*out) + } return } @@ -961,6 +1131,11 @@ func (in *VenafiTPP) DeepCopyInto(out *VenafiTPP) { *out = make([]byte, len(*in)) copy(*out, *in) } + if in.CABundleSecretRef != nil { + in, out := &in.CABundleSecretRef, &out.CABundleSecretRef + *out = new(meta.SecretKeySelector) + **out = **in + } return } diff --git a/pkg/webhook/handlers/testdata/apis/testgroup/v2/doc.go b/internal/apis/config/cainjector/doc.go similarity index 70% rename from pkg/webhook/handlers/testdata/apis/testgroup/v2/doc.go rename to internal/apis/config/cainjector/doc.go index 80ce277398a..f6dec7e0fcc 100644 --- a/pkg/webhook/handlers/testdata/apis/testgroup/v2/doc.go +++ b/internal/apis/config/cainjector/doc.go @@ -1,5 +1,5 @@ /* -Copyright 2020 The cert-manager Authors. +Copyright 2021 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. @@ -14,9 +14,8 @@ See the License for the specific language governing permissions and limitations under the License. */ -// +k8s:conversion-gen=github.com/cert-manager/cert-manager/pkg/webhook/handlers/testdata/apis/testgroup // +k8s:deepcopy-gen=package,register -// +k8s:defaulter-gen=TypeMeta -// +groupName=testgroup.testing.cert-manager.io -package v2 +// Package cainjector is the internal version of the cainjector config API. +// +groupName=cainjector.config.cert-manager.io +package cainjector diff --git a/internal/apis/config/cainjector/fuzzer/fuzzer.go b/internal/apis/config/cainjector/fuzzer/fuzzer.go new file mode 100644 index 00000000000..bae55c461eb --- /dev/null +++ b/internal/apis/config/cainjector/fuzzer/fuzzer.go @@ -0,0 +1,56 @@ +/* +Copyright 2021 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. +*/ + +package fuzzer + +import ( + runtimeserializer "k8s.io/apimachinery/pkg/runtime/serializer" + logsapi "k8s.io/component-base/logs/api/v1" + "sigs.k8s.io/randfill" + + "github.com/cert-manager/cert-manager/internal/apis/config/cainjector" +) + +// Funcs returns the fuzzer functions for the cainjector config api group. +var Funcs = func(codecs runtimeserializer.CodecFactory) []interface{} { + return []interface{}{ + func(s *cainjector.CAInjectorConfiguration, c randfill.Continue) { + c.FillNoCustom(s) // fuzz self without calling this function again + + if s.PprofAddress == "" { + s.PprofAddress = "something:1234" + } + + if s.LeaderElectionConfig.Namespace == "" { + s.LeaderElectionConfig.Namespace = "something" + } + if s.LeaderElectionConfig.LeaseDuration == 0 { + s.LeaderElectionConfig.LeaseDuration = 1234 + } + if s.LeaderElectionConfig.RenewDeadline == 0 { + s.LeaderElectionConfig.RenewDeadline = 1234 + } + if s.LeaderElectionConfig.RetryPeriod == 0 { + s.LeaderElectionConfig.RetryPeriod = 1234 + } + if s.MetricsListenAddress == "" { + s.MetricsListenAddress = "something:1234" + } + + logsapi.SetRecommendedLoggingConfiguration(&s.Logging) + }, + } +} diff --git a/pkg/webhook/handlers/testdata/apis/testgroup/install/install.go b/internal/apis/config/cainjector/install/install.go similarity index 66% rename from pkg/webhook/handlers/testdata/apis/testgroup/install/install.go rename to internal/apis/config/cainjector/install/install.go index 0c89f497ebf..fae17582275 100644 --- a/pkg/webhook/handlers/testdata/apis/testgroup/install/install.go +++ b/internal/apis/config/cainjector/install/install.go @@ -1,5 +1,5 @@ /* -Copyright 2020 The cert-manager Authors. +Copyright 2021 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. @@ -22,14 +22,12 @@ import ( "k8s.io/apimachinery/pkg/runtime" utilruntime "k8s.io/apimachinery/pkg/util/runtime" - "github.com/cert-manager/cert-manager/pkg/webhook/handlers/testdata/apis/testgroup" - v1 "github.com/cert-manager/cert-manager/pkg/webhook/handlers/testdata/apis/testgroup/v1" - v2 "github.com/cert-manager/cert-manager/pkg/webhook/handlers/testdata/apis/testgroup/v2" + "github.com/cert-manager/cert-manager/internal/apis/config/cainjector" + "github.com/cert-manager/cert-manager/internal/apis/config/cainjector/v1alpha1" ) // Install registers the API group and adds types to a scheme func Install(scheme *runtime.Scheme) { - utilruntime.Must(testgroup.AddToScheme(scheme)) - utilruntime.Must(v1.AddToScheme(scheme)) - utilruntime.Must(v2.AddToScheme(scheme)) + utilruntime.Must(cainjector.AddToScheme(scheme)) + utilruntime.Must(v1alpha1.AddToScheme(scheme)) } diff --git a/pkg/webhook/handlers/testdata/apis/testgroup/install/roundtrip_test.go b/internal/apis/config/cainjector/install/roundtrip_test.go similarity index 76% rename from pkg/webhook/handlers/testdata/apis/testgroup/install/roundtrip_test.go rename to internal/apis/config/cainjector/install/roundtrip_test.go index 764dcedfacb..5ddf0200d6f 100644 --- a/pkg/webhook/handlers/testdata/apis/testgroup/install/roundtrip_test.go +++ b/internal/apis/config/cainjector/install/roundtrip_test.go @@ -1,5 +1,5 @@ /* -Copyright 2020 The cert-manager Authors. +Copyright 2021 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. @@ -21,9 +21,9 @@ import ( "k8s.io/apimachinery/pkg/api/apitesting/roundtrip" - "github.com/cert-manager/cert-manager/pkg/webhook/handlers/testdata/apis/testgroup/fuzzer" + configfuzzer "github.com/cert-manager/cert-manager/internal/apis/config/cainjector/fuzzer" ) func TestRoundTripTypes(t *testing.T) { - roundtrip.RoundTripTestForAPIGroup(t, Install, fuzzer.Funcs) + roundtrip.RoundTripTestForAPIGroup(t, Install, configfuzzer.Funcs) } diff --git a/pkg/webhook/handlers/testdata/apis/testgroup/register.go b/internal/apis/config/cainjector/register.go similarity index 79% rename from pkg/webhook/handlers/testdata/apis/testgroup/register.go rename to internal/apis/config/cainjector/register.go index 787f651c9ad..406efe1d7b0 100644 --- a/pkg/webhook/handlers/testdata/apis/testgroup/register.go +++ b/internal/apis/config/cainjector/register.go @@ -1,5 +1,5 @@ /* -Copyright 2020 The cert-manager Authors. +Copyright 2021 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. @@ -14,11 +14,13 @@ See the License for the specific language governing permissions and limitations under the License. */ -package testgroup +package cainjector import ( "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" + + "github.com/cert-manager/cert-manager/pkg/apis/config/cainjector" ) var ( @@ -27,7 +29,7 @@ var ( ) // SchemeGroupVersion is group version used to register these objects -var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: runtime.APIVersionInternal} +var SchemeGroupVersion = schema.GroupVersion{Group: cainjector.GroupName, Version: runtime.APIVersionInternal} // Resource takes an unqualified resource and returns a Group qualified GroupResource func Resource(resource string) schema.GroupResource { @@ -37,7 +39,8 @@ func Resource(resource string) schema.GroupResource { // Adds the list of known types to api.Scheme. func addKnownTypes(scheme *runtime.Scheme) error { scheme.AddKnownTypes(SchemeGroupVersion, - &TestType{}, + &CAInjectorConfiguration{}, + // Add new kinds to be registered here ) return nil } diff --git a/internal/apis/config/cainjector/scheme/scheme.go b/internal/apis/config/cainjector/scheme/scheme.go new file mode 100644 index 00000000000..88952b05984 --- /dev/null +++ b/internal/apis/config/cainjector/scheme/scheme.go @@ -0,0 +1,40 @@ +/* +Copyright 2021 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. +*/ + +package scheme + +import ( + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/serializer" + + config "github.com/cert-manager/cert-manager/internal/apis/config/cainjector" + configv1alpha1 "github.com/cert-manager/cert-manager/internal/apis/config/cainjector/v1alpha1" +) + +// NewSchemeAndCodecs is a utility function that returns a Scheme and CodecFactory +// that understand the types in the config.cert-manager.io API group. Passing mutators allows +// for adjusting the behavior of the CodecFactory, for example enable strict decoding. +func NewSchemeAndCodecs(mutators ...serializer.CodecFactoryOptionsMutator) (*runtime.Scheme, *serializer.CodecFactory, error) { + scheme := runtime.NewScheme() + if err := config.AddToScheme(scheme); err != nil { + return nil, nil, err + } + if err := configv1alpha1.AddToScheme(scheme); err != nil { + return nil, nil, err + } + codecs := serializer.NewCodecFactory(scheme, mutators...) + return scheme, &codecs, nil +} diff --git a/internal/apis/config/cainjector/types.go b/internal/apis/config/cainjector/types.go new file mode 100644 index 00000000000..0845c69e132 --- /dev/null +++ b/internal/apis/config/cainjector/types.go @@ -0,0 +1,100 @@ +/* +Copyright 2021 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. +*/ + +package cainjector + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + logsapi "k8s.io/component-base/logs/api/v1" + + shared "github.com/cert-manager/cert-manager/internal/apis/config/shared" +) + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +type CAInjectorConfiguration struct { + metav1.TypeMeta + + // Paths to a kubeconfig. Only required if out-of-cluster. + KubeConfig 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 + Namespace string + + // LeaderElectionConfig configures the behaviour of the leader election + LeaderElectionConfig shared.LeaderElectionConfig + + // EnableDataSourceConfig determines whether cainjector's control loops will watch + // cert-manager resources as potential sources of CA data. + EnableDataSourceConfig EnableDataSourceConfig + + // EnableInjectableConfig determines whether cainjector's control loops will watch + // cert-manager resources as potential targets for CA data injection. + EnableInjectableConfig EnableInjectableConfig + + // Enable profiling for cainjector. + EnablePprof bool + + // 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. + PprofAddress string + + // https://pkg.go.dev/k8s.io/component-base@v0.27.3/logs/api/v1#LoggingConfiguration + Logging logsapi.LoggingConfiguration + + // featureGates is a map of feature names to bools that enable or disable experimental + // features. + FeatureGates map[string]bool + + // The host and port that the metrics endpoint should listen on. + // The value "0" disables the metrics server. + // Defaults to '0.0.0.0:9402'. + MetricsListenAddress string + + // Metrics endpoint TLS config + MetricsTLSConfig shared.TLSConfig +} + +type EnableDataSourceConfig struct { + // Certificates determines whether cainjector's control loops will watch + // cert-manager Certificate resources as potential sources of CA data. + Certificates bool +} + +type EnableInjectableConfig struct { + // ValidatingWebhookConfigurations determines whether cainjector + // will spin up a control loop to inject CA data to annotated + // ValidatingWebhookConfigurations + ValidatingWebhookConfigurations bool + + // MutatingWebhookConfigurations determines whether cainjector + // will spin up a control loop to inject CA data to annotated + // MutatingWebhookConfigurations + MutatingWebhookConfigurations bool + + // CustomResourceDefinitions determines whether cainjector + // will spin up a control loop to inject CA data to annotated + // CustomResourceDefinitions + CustomResourceDefinitions bool + + // APIServices determines whether cainjector + // will spin up a control loop to inject CA data to annotated + // APIServices + APIServices bool +} diff --git a/internal/apis/config/cainjector/v1alpha1/defaults.go b/internal/apis/config/cainjector/v1alpha1/defaults.go new file mode 100644 index 00000000000..176f188209d --- /dev/null +++ b/internal/apis/config/cainjector/v1alpha1/defaults.go @@ -0,0 +1,64 @@ +/* +Copyright 2021 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. +*/ + +package v1alpha1 + +import ( + "k8s.io/apimachinery/pkg/runtime" + logsapi "k8s.io/component-base/logs/api/v1" + "k8s.io/utils/ptr" + + "github.com/cert-manager/cert-manager/pkg/apis/config/cainjector/v1alpha1" +) + +const defaultPrometheusMetricsServerAddress = "0.0.0.0:9402" + +func addDefaultingFuncs(scheme *runtime.Scheme) error { + return RegisterDefaults(scheme) +} + +func SetDefaults_CAInjectorConfiguration(obj *v1alpha1.CAInjectorConfiguration) { + if obj.PprofAddress == "" { + obj.PprofAddress = "localhost:6060" + } + + if obj.MetricsListenAddress == "" { + obj.MetricsListenAddress = defaultPrometheusMetricsServerAddress + } + + logsapi.SetRecommendedLoggingConfiguration(&obj.Logging) +} + +func SetDefaults_EnableDataSourceConfig(obj *v1alpha1.EnableDataSourceConfig) { + if obj.Certificates == nil { + obj.Certificates = ptr.To(true) + } +} + +func SetDefaults_EnableInjectableConfig(obj *v1alpha1.EnableInjectableConfig) { + if obj.MutatingWebhookConfigurations == nil { + obj.MutatingWebhookConfigurations = ptr.To(true) + } + if obj.ValidatingWebhookConfigurations == nil { + obj.ValidatingWebhookConfigurations = ptr.To(true) + } + if obj.CustomResourceDefinitions == nil { + obj.CustomResourceDefinitions = ptr.To(true) + } + if obj.APIServices == nil { + obj.APIServices = ptr.To(true) + } +} diff --git a/internal/apis/config/cainjector/v1alpha1/defaults_test.go b/internal/apis/config/cainjector/v1alpha1/defaults_test.go new file mode 100644 index 00000000000..dc479c0dcfe --- /dev/null +++ b/internal/apis/config/cainjector/v1alpha1/defaults_test.go @@ -0,0 +1,65 @@ +/* +Copyright 2021 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. +*/ + +package v1alpha1 + +import ( + "encoding/json" + "os" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/cert-manager/cert-manager/pkg/apis/config/cainjector/v1alpha1" +) + +func TestCAInjectorConfigurationDefaults(t *testing.T) { + tests := []struct { + name string + config *v1alpha1.CAInjectorConfiguration + jsonFilePath string + }{ + { + "v1alpha1", + &v1alpha1.CAInjectorConfiguration{}, + "testdata/defaults.json", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + SetObjectDefaults_CAInjectorConfiguration(tt.config) + + defaultData, err := json.MarshalIndent(tt.config, "", "\t") + if err != nil { + t.Fatal(err) + } + + if os.Getenv("UPDATE_DEFAULTS") == "true" { + if err := os.WriteFile(tt.jsonFilePath, defaultData, 0644); err != nil { + t.Fatal(err) + } + t.Log("cainjector config api defaults updated") + } + + expectedData, err := os.ReadFile(tt.jsonFilePath) + if err != nil { + t.Fatal(err) + } + + require.Equal(t, expectedData, defaultData) + }) + } +} diff --git a/internal/apis/acme/v1alpha3/doc.go b/internal/apis/config/cainjector/v1alpha1/doc.go similarity index 71% rename from internal/apis/acme/v1alpha3/doc.go rename to internal/apis/config/cainjector/v1alpha1/doc.go index f832fe514ab..a82f51e8876 100644 --- a/internal/apis/acme/v1alpha3/doc.go +++ b/internal/apis/config/cainjector/v1alpha1/doc.go @@ -1,5 +1,5 @@ /* -Copyright 2020 The cert-manager Authors. +Copyright 2021 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. @@ -14,10 +14,10 @@ See the License for the specific language governing permissions and limitations under the License. */ -// +k8s:conversion-gen=github.com/cert-manager/cert-manager/internal/apis/acme -// +k8s:conversion-gen-external-types=github.com/cert-manager/cert-manager/internal/apis/acme/v1alpha3 +// +k8s:conversion-gen=github.com/cert-manager/cert-manager/internal/apis/config/cainjector +// +k8s:conversion-gen-external-types=github.com/cert-manager/cert-manager/pkg/apis/config/cainjector/v1alpha1 // +k8s:defaulter-gen=TypeMeta -// +k8s:deepcopy-gen=package,register +// +k8s:defaulter-gen-input=github.com/cert-manager/cert-manager/pkg/apis/config/cainjector/v1alpha1 -// +groupName=acme.cert-manager.io -package v1alpha3 +// +groupName=cainjector.config.cert-manager.io +package v1alpha1 diff --git a/internal/apis/acme/v1beta1/register.go b/internal/apis/config/cainjector/v1alpha1/register.go similarity index 57% rename from internal/apis/acme/v1beta1/register.go rename to internal/apis/config/cainjector/v1alpha1/register.go index 38e93fa026f..46bddecf2aa 100644 --- a/internal/apis/acme/v1beta1/register.go +++ b/internal/apis/config/cainjector/v1alpha1/register.go @@ -1,5 +1,5 @@ /* -Copyright 2020 The cert-manager Authors. +Copyright 2021 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. @@ -14,18 +14,17 @@ See the License for the specific language governing permissions and limitations under the License. */ -package v1beta1 +package v1alpha1 import ( - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" - "github.com/cert-manager/cert-manager/pkg/apis/acme" + "github.com/cert-manager/cert-manager/pkg/apis/config/cainjector" + "github.com/cert-manager/cert-manager/pkg/apis/config/cainjector/v1alpha1" ) // SchemeGroupVersion is group version used to register these objects -var SchemeGroupVersion = schema.GroupVersion{Group: acme.GroupName, Version: "v1beta1"} +var SchemeGroupVersion = schema.GroupVersion{Group: cainjector.GroupName, Version: "v1alpha1"} // Resource takes an unqualified resource and returns a Group qualified GroupResource func Resource(resource string) schema.GroupResource { @@ -33,8 +32,7 @@ func Resource(resource string) schema.GroupResource { } var ( - SchemeBuilder runtime.SchemeBuilder - localSchemeBuilder = &SchemeBuilder + localSchemeBuilder = &v1alpha1.SchemeBuilder AddToScheme = localSchemeBuilder.AddToScheme ) @@ -43,21 +41,4 @@ func init() { // generated functions takes place in the generated files. The separation // makes the code compile even when the generated files are missing. localSchemeBuilder.Register(addDefaultingFuncs) - - // We only register manually written functions here. The registration of the - // generated functions takes place in the generated files. The separation - // makes the code compile even when the generated files are missing. - localSchemeBuilder.Register(addKnownTypes) -} - -// Adds the list of known types to api.Scheme. -func addKnownTypes(scheme *runtime.Scheme) error { - scheme.AddKnownTypes(SchemeGroupVersion, - &Order{}, - &OrderList{}, - &Challenge{}, - &ChallengeList{}, - ) - metav1.AddToGroupVersion(scheme, SchemeGroupVersion) - return nil } diff --git a/internal/apis/config/cainjector/v1alpha1/testdata/defaults.json b/internal/apis/config/cainjector/v1alpha1/testdata/defaults.json new file mode 100644 index 00000000000..afb652d6558 --- /dev/null +++ b/internal/apis/config/cainjector/v1alpha1/testdata/defaults.json @@ -0,0 +1,40 @@ +{ + "leaderElectionConfig": { + "enabled": true, + "namespace": "kube-system", + "leaseDuration": "1m0s", + "renewDeadline": "40s", + "retryPeriod": "15s" + }, + "enableDataSourceConfig": { + "certificates": true + }, + "enableInjectableConfig": { + "validatingWebhookConfigurations": true, + "mutatingWebhookConfigurations": true, + "customResourceDefinitions": true, + "apiServices": true + }, + "enablePprof": false, + "pprofAddress": "localhost:6060", + "logging": { + "format": "text", + "flushFrequency": "5s", + "verbosity": 0, + "options": { + "text": { + "infoBufferSize": "0" + }, + "json": { + "infoBufferSize": "0" + } + } + }, + "metricsListenAddress": "0.0.0.0:9402", + "metricsTLSConfig": { + "filesystem": {}, + "dynamic": { + "leafDuration": "168h0m0s" + } + } +} \ No newline at end of file diff --git a/internal/apis/config/cainjector/v1alpha1/zz_generated.conversion.go b/internal/apis/config/cainjector/v1alpha1/zz_generated.conversion.go new file mode 100644 index 00000000000..a3361a10370 --- /dev/null +++ b/internal/apis/config/cainjector/v1alpha1/zz_generated.conversion.go @@ -0,0 +1,195 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by conversion-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + unsafe "unsafe" + + cainjector "github.com/cert-manager/cert-manager/internal/apis/config/cainjector" + sharedv1alpha1 "github.com/cert-manager/cert-manager/internal/apis/config/shared/v1alpha1" + cainjectorv1alpha1 "github.com/cert-manager/cert-manager/pkg/apis/config/cainjector/v1alpha1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + conversion "k8s.io/apimachinery/pkg/conversion" + runtime "k8s.io/apimachinery/pkg/runtime" +) + +func init() { + localSchemeBuilder.Register(RegisterConversions) +} + +// RegisterConversions adds conversion functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterConversions(s *runtime.Scheme) error { + if err := s.AddGeneratedConversionFunc((*cainjectorv1alpha1.CAInjectorConfiguration)(nil), (*cainjector.CAInjectorConfiguration)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1alpha1_CAInjectorConfiguration_To_cainjector_CAInjectorConfiguration(a.(*cainjectorv1alpha1.CAInjectorConfiguration), b.(*cainjector.CAInjectorConfiguration), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*cainjector.CAInjectorConfiguration)(nil), (*cainjectorv1alpha1.CAInjectorConfiguration)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_cainjector_CAInjectorConfiguration_To_v1alpha1_CAInjectorConfiguration(a.(*cainjector.CAInjectorConfiguration), b.(*cainjectorv1alpha1.CAInjectorConfiguration), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*cainjectorv1alpha1.EnableDataSourceConfig)(nil), (*cainjector.EnableDataSourceConfig)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1alpha1_EnableDataSourceConfig_To_cainjector_EnableDataSourceConfig(a.(*cainjectorv1alpha1.EnableDataSourceConfig), b.(*cainjector.EnableDataSourceConfig), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*cainjector.EnableDataSourceConfig)(nil), (*cainjectorv1alpha1.EnableDataSourceConfig)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_cainjector_EnableDataSourceConfig_To_v1alpha1_EnableDataSourceConfig(a.(*cainjector.EnableDataSourceConfig), b.(*cainjectorv1alpha1.EnableDataSourceConfig), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*cainjectorv1alpha1.EnableInjectableConfig)(nil), (*cainjector.EnableInjectableConfig)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1alpha1_EnableInjectableConfig_To_cainjector_EnableInjectableConfig(a.(*cainjectorv1alpha1.EnableInjectableConfig), b.(*cainjector.EnableInjectableConfig), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*cainjector.EnableInjectableConfig)(nil), (*cainjectorv1alpha1.EnableInjectableConfig)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_cainjector_EnableInjectableConfig_To_v1alpha1_EnableInjectableConfig(a.(*cainjector.EnableInjectableConfig), b.(*cainjectorv1alpha1.EnableInjectableConfig), scope) + }); err != nil { + return err + } + return nil +} + +func autoConvert_v1alpha1_CAInjectorConfiguration_To_cainjector_CAInjectorConfiguration(in *cainjectorv1alpha1.CAInjectorConfiguration, out *cainjector.CAInjectorConfiguration, s conversion.Scope) error { + out.KubeConfig = in.KubeConfig + out.Namespace = in.Namespace + if err := sharedv1alpha1.Convert_v1alpha1_LeaderElectionConfig_To_shared_LeaderElectionConfig(&in.LeaderElectionConfig, &out.LeaderElectionConfig, s); err != nil { + return err + } + if err := Convert_v1alpha1_EnableDataSourceConfig_To_cainjector_EnableDataSourceConfig(&in.EnableDataSourceConfig, &out.EnableDataSourceConfig, s); err != nil { + return err + } + if err := Convert_v1alpha1_EnableInjectableConfig_To_cainjector_EnableInjectableConfig(&in.EnableInjectableConfig, &out.EnableInjectableConfig, s); err != nil { + return err + } + out.EnablePprof = in.EnablePprof + out.PprofAddress = in.PprofAddress + out.Logging = in.Logging + out.FeatureGates = *(*map[string]bool)(unsafe.Pointer(&in.FeatureGates)) + out.MetricsListenAddress = in.MetricsListenAddress + if err := sharedv1alpha1.Convert_v1alpha1_TLSConfig_To_shared_TLSConfig(&in.MetricsTLSConfig, &out.MetricsTLSConfig, s); err != nil { + return err + } + return nil +} + +// Convert_v1alpha1_CAInjectorConfiguration_To_cainjector_CAInjectorConfiguration is an autogenerated conversion function. +func Convert_v1alpha1_CAInjectorConfiguration_To_cainjector_CAInjectorConfiguration(in *cainjectorv1alpha1.CAInjectorConfiguration, out *cainjector.CAInjectorConfiguration, s conversion.Scope) error { + return autoConvert_v1alpha1_CAInjectorConfiguration_To_cainjector_CAInjectorConfiguration(in, out, s) +} + +func autoConvert_cainjector_CAInjectorConfiguration_To_v1alpha1_CAInjectorConfiguration(in *cainjector.CAInjectorConfiguration, out *cainjectorv1alpha1.CAInjectorConfiguration, s conversion.Scope) error { + out.KubeConfig = in.KubeConfig + out.Namespace = in.Namespace + if err := sharedv1alpha1.Convert_shared_LeaderElectionConfig_To_v1alpha1_LeaderElectionConfig(&in.LeaderElectionConfig, &out.LeaderElectionConfig, s); err != nil { + return err + } + if err := Convert_cainjector_EnableDataSourceConfig_To_v1alpha1_EnableDataSourceConfig(&in.EnableDataSourceConfig, &out.EnableDataSourceConfig, s); err != nil { + return err + } + if err := Convert_cainjector_EnableInjectableConfig_To_v1alpha1_EnableInjectableConfig(&in.EnableInjectableConfig, &out.EnableInjectableConfig, s); err != nil { + return err + } + out.EnablePprof = in.EnablePprof + out.PprofAddress = in.PprofAddress + out.Logging = in.Logging + out.FeatureGates = *(*map[string]bool)(unsafe.Pointer(&in.FeatureGates)) + out.MetricsListenAddress = in.MetricsListenAddress + if err := sharedv1alpha1.Convert_shared_TLSConfig_To_v1alpha1_TLSConfig(&in.MetricsTLSConfig, &out.MetricsTLSConfig, s); err != nil { + return err + } + return nil +} + +// Convert_cainjector_CAInjectorConfiguration_To_v1alpha1_CAInjectorConfiguration is an autogenerated conversion function. +func Convert_cainjector_CAInjectorConfiguration_To_v1alpha1_CAInjectorConfiguration(in *cainjector.CAInjectorConfiguration, out *cainjectorv1alpha1.CAInjectorConfiguration, s conversion.Scope) error { + return autoConvert_cainjector_CAInjectorConfiguration_To_v1alpha1_CAInjectorConfiguration(in, out, s) +} + +func autoConvert_v1alpha1_EnableDataSourceConfig_To_cainjector_EnableDataSourceConfig(in *cainjectorv1alpha1.EnableDataSourceConfig, out *cainjector.EnableDataSourceConfig, s conversion.Scope) error { + if err := v1.Convert_Pointer_bool_To_bool(&in.Certificates, &out.Certificates, s); err != nil { + return err + } + return nil +} + +// Convert_v1alpha1_EnableDataSourceConfig_To_cainjector_EnableDataSourceConfig is an autogenerated conversion function. +func Convert_v1alpha1_EnableDataSourceConfig_To_cainjector_EnableDataSourceConfig(in *cainjectorv1alpha1.EnableDataSourceConfig, out *cainjector.EnableDataSourceConfig, s conversion.Scope) error { + return autoConvert_v1alpha1_EnableDataSourceConfig_To_cainjector_EnableDataSourceConfig(in, out, s) +} + +func autoConvert_cainjector_EnableDataSourceConfig_To_v1alpha1_EnableDataSourceConfig(in *cainjector.EnableDataSourceConfig, out *cainjectorv1alpha1.EnableDataSourceConfig, s conversion.Scope) error { + if err := v1.Convert_bool_To_Pointer_bool(&in.Certificates, &out.Certificates, s); err != nil { + return err + } + return nil +} + +// Convert_cainjector_EnableDataSourceConfig_To_v1alpha1_EnableDataSourceConfig is an autogenerated conversion function. +func Convert_cainjector_EnableDataSourceConfig_To_v1alpha1_EnableDataSourceConfig(in *cainjector.EnableDataSourceConfig, out *cainjectorv1alpha1.EnableDataSourceConfig, s conversion.Scope) error { + return autoConvert_cainjector_EnableDataSourceConfig_To_v1alpha1_EnableDataSourceConfig(in, out, s) +} + +func autoConvert_v1alpha1_EnableInjectableConfig_To_cainjector_EnableInjectableConfig(in *cainjectorv1alpha1.EnableInjectableConfig, out *cainjector.EnableInjectableConfig, s conversion.Scope) error { + if err := v1.Convert_Pointer_bool_To_bool(&in.ValidatingWebhookConfigurations, &out.ValidatingWebhookConfigurations, s); err != nil { + return err + } + if err := v1.Convert_Pointer_bool_To_bool(&in.MutatingWebhookConfigurations, &out.MutatingWebhookConfigurations, s); err != nil { + return err + } + if err := v1.Convert_Pointer_bool_To_bool(&in.CustomResourceDefinitions, &out.CustomResourceDefinitions, s); err != nil { + return err + } + if err := v1.Convert_Pointer_bool_To_bool(&in.APIServices, &out.APIServices, s); err != nil { + return err + } + return nil +} + +// Convert_v1alpha1_EnableInjectableConfig_To_cainjector_EnableInjectableConfig is an autogenerated conversion function. +func Convert_v1alpha1_EnableInjectableConfig_To_cainjector_EnableInjectableConfig(in *cainjectorv1alpha1.EnableInjectableConfig, out *cainjector.EnableInjectableConfig, s conversion.Scope) error { + return autoConvert_v1alpha1_EnableInjectableConfig_To_cainjector_EnableInjectableConfig(in, out, s) +} + +func autoConvert_cainjector_EnableInjectableConfig_To_v1alpha1_EnableInjectableConfig(in *cainjector.EnableInjectableConfig, out *cainjectorv1alpha1.EnableInjectableConfig, s conversion.Scope) error { + if err := v1.Convert_bool_To_Pointer_bool(&in.ValidatingWebhookConfigurations, &out.ValidatingWebhookConfigurations, s); err != nil { + return err + } + if err := v1.Convert_bool_To_Pointer_bool(&in.MutatingWebhookConfigurations, &out.MutatingWebhookConfigurations, s); err != nil { + return err + } + if err := v1.Convert_bool_To_Pointer_bool(&in.CustomResourceDefinitions, &out.CustomResourceDefinitions, s); err != nil { + return err + } + if err := v1.Convert_bool_To_Pointer_bool(&in.APIServices, &out.APIServices, s); err != nil { + return err + } + return nil +} + +// Convert_cainjector_EnableInjectableConfig_To_v1alpha1_EnableInjectableConfig is an autogenerated conversion function. +func Convert_cainjector_EnableInjectableConfig_To_v1alpha1_EnableInjectableConfig(in *cainjector.EnableInjectableConfig, out *cainjectorv1alpha1.EnableInjectableConfig, s conversion.Scope) error { + return autoConvert_cainjector_EnableInjectableConfig_To_v1alpha1_EnableInjectableConfig(in, out, s) +} diff --git a/pkg/webhook/handlers/testdata/apis/testgroup/v1/zz_generated.defaults.go b/internal/apis/config/cainjector/v1alpha1/zz_generated.defaults.go similarity index 54% rename from pkg/webhook/handlers/testdata/apis/testgroup/v1/zz_generated.defaults.go rename to internal/apis/config/cainjector/v1alpha1/zz_generated.defaults.go index 11a39677c7d..cf8d49e82c2 100644 --- a/pkg/webhook/handlers/testdata/apis/testgroup/v1/zz_generated.defaults.go +++ b/internal/apis/config/cainjector/v1alpha1/zz_generated.defaults.go @@ -19,9 +19,11 @@ limitations under the License. // Code generated by defaulter-gen. DO NOT EDIT. -package v1 +package v1alpha1 import ( + sharedv1alpha1 "github.com/cert-manager/cert-manager/internal/apis/config/shared/v1alpha1" + cainjectorv1alpha1 "github.com/cert-manager/cert-manager/pkg/apis/config/cainjector/v1alpha1" runtime "k8s.io/apimachinery/pkg/runtime" ) @@ -29,10 +31,16 @@ import ( // Public to allow building arbitrary schemes. // All generated defaulters are covering - they call all nested defaulters. func RegisterDefaults(scheme *runtime.Scheme) error { - scheme.AddTypeDefaultingFunc(&TestType{}, func(obj interface{}) { SetObjectDefaults_TestType(obj.(*TestType)) }) + scheme.AddTypeDefaultingFunc(&cainjectorv1alpha1.CAInjectorConfiguration{}, func(obj interface{}) { + SetObjectDefaults_CAInjectorConfiguration(obj.(*cainjectorv1alpha1.CAInjectorConfiguration)) + }) return nil } -func SetObjectDefaults_TestType(in *TestType) { - SetDefaults_TestType(in) +func SetObjectDefaults_CAInjectorConfiguration(in *cainjectorv1alpha1.CAInjectorConfiguration) { + SetDefaults_CAInjectorConfiguration(in) + sharedv1alpha1.SetDefaults_LeaderElectionConfig(&in.LeaderElectionConfig) + SetDefaults_EnableDataSourceConfig(&in.EnableDataSourceConfig) + SetDefaults_EnableInjectableConfig(&in.EnableInjectableConfig) + sharedv1alpha1.SetDefaults_DynamicServingConfig(&in.MetricsTLSConfig.Dynamic) } diff --git a/internal/apis/config/cainjector/validation/validation.go b/internal/apis/config/cainjector/validation/validation.go new file mode 100644 index 00000000000..619fd8dd70b --- /dev/null +++ b/internal/apis/config/cainjector/validation/validation.go @@ -0,0 +1,34 @@ +/* +Copyright 2021 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. +*/ + +package validation + +import ( + "k8s.io/apimachinery/pkg/util/validation/field" + logsapi "k8s.io/component-base/logs/api/v1" + + config "github.com/cert-manager/cert-manager/internal/apis/config/cainjector" + sharedvalidation "github.com/cert-manager/cert-manager/internal/apis/config/shared/validation" +) + +func ValidateCAInjectorConfiguration(cfg *config.CAInjectorConfiguration, fldPath *field.Path) field.ErrorList { + var allErrors field.ErrorList + + allErrors = append(allErrors, logsapi.Validate(&cfg.Logging, nil, fldPath.Child("logging"))...) + allErrors = append(allErrors, sharedvalidation.ValidateLeaderElectionConfig(&cfg.LeaderElectionConfig, fldPath.Child("leaderElectionConfig"))...) + + return allErrors +} diff --git a/internal/apis/config/cainjector/validation/validation_test.go b/internal/apis/config/cainjector/validation/validation_test.go new file mode 100644 index 00000000000..b1a49bda00c --- /dev/null +++ b/internal/apis/config/cainjector/validation/validation_test.go @@ -0,0 +1,87 @@ +/* +Copyright 2021 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. +*/ + +package validation + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "k8s.io/apimachinery/pkg/util/validation/field" + logsapi "k8s.io/component-base/logs/api/v1" + + config "github.com/cert-manager/cert-manager/internal/apis/config/cainjector" + "github.com/cert-manager/cert-manager/internal/apis/config/shared" +) + +func TestValidateCAInjectorConfiguration(t *testing.T) { + tests := []struct { + name string + config *config.CAInjectorConfiguration + errs func(*config.CAInjectorConfiguration) field.ErrorList + }{ + { + "with valid config", + &config.CAInjectorConfiguration{ + Logging: logsapi.LoggingConfiguration{ + Format: "text", + }, + }, + nil, + }, + { + "with invalid logging config", + &config.CAInjectorConfiguration{ + Logging: logsapi.LoggingConfiguration{ + Format: "unknown", + }, + }, + func(wc *config.CAInjectorConfiguration) field.ErrorList { + return field.ErrorList{ + field.Invalid(field.NewPath("logging.format"), wc.Logging.Format, "Unsupported log format"), + } + }, + }, + { + "with invalid leader election config", + &config.CAInjectorConfiguration{ + Logging: logsapi.LoggingConfiguration{ + Format: "text", + }, + LeaderElectionConfig: shared.LeaderElectionConfig{ + Enabled: true, + }, + }, + func(cc *config.CAInjectorConfiguration) field.ErrorList { + return field.ErrorList{ + field.Invalid(field.NewPath("leaderElectionConfig.leaseDuration"), cc.LeaderElectionConfig.LeaseDuration, "must be greater than 0"), + field.Invalid(field.NewPath("leaderElectionConfig.renewDeadline"), cc.LeaderElectionConfig.RenewDeadline, "must be greater than 0"), + field.Invalid(field.NewPath("leaderElectionConfig.retryPeriod"), cc.LeaderElectionConfig.RetryPeriod, "must be greater than 0"), + } + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + errList := ValidateCAInjectorConfiguration(tt.config, nil) + var expErrs field.ErrorList + if tt.errs != nil { + expErrs = tt.errs(tt.config) + } + assert.ElementsMatch(t, expErrs, errList) + }) + } +} diff --git a/internal/apis/config/cainjector/zz_generated.deepcopy.go b/internal/apis/config/cainjector/zz_generated.deepcopy.go new file mode 100644 index 00000000000..2e8e4e88ea9 --- /dev/null +++ b/internal/apis/config/cainjector/zz_generated.deepcopy.go @@ -0,0 +1,95 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by deepcopy-gen. DO NOT EDIT. + +package cainjector + +import ( + runtime "k8s.io/apimachinery/pkg/runtime" +) + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *CAInjectorConfiguration) DeepCopyInto(out *CAInjectorConfiguration) { + *out = *in + out.TypeMeta = in.TypeMeta + out.LeaderElectionConfig = in.LeaderElectionConfig + out.EnableDataSourceConfig = in.EnableDataSourceConfig + out.EnableInjectableConfig = in.EnableInjectableConfig + in.Logging.DeepCopyInto(&out.Logging) + if in.FeatureGates != nil { + in, out := &in.FeatureGates, &out.FeatureGates + *out = make(map[string]bool, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + in.MetricsTLSConfig.DeepCopyInto(&out.MetricsTLSConfig) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CAInjectorConfiguration. +func (in *CAInjectorConfiguration) DeepCopy() *CAInjectorConfiguration { + if in == nil { + return nil + } + out := new(CAInjectorConfiguration) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *CAInjectorConfiguration) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *EnableDataSourceConfig) DeepCopyInto(out *EnableDataSourceConfig) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new EnableDataSourceConfig. +func (in *EnableDataSourceConfig) DeepCopy() *EnableDataSourceConfig { + if in == nil { + return nil + } + out := new(EnableDataSourceConfig) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *EnableInjectableConfig) DeepCopyInto(out *EnableInjectableConfig) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new EnableInjectableConfig. +func (in *EnableInjectableConfig) DeepCopy() *EnableInjectableConfig { + if in == nil { + return nil + } + out := new(EnableInjectableConfig) + in.DeepCopyInto(out) + return out +} diff --git a/pkg/webhook/handlers/testdata/apis/testgroup/v1/doc.go b/internal/apis/config/controller/doc.go similarity index 70% rename from pkg/webhook/handlers/testdata/apis/testgroup/v1/doc.go rename to internal/apis/config/controller/doc.go index 93e5807bae9..b65dd808f42 100644 --- a/pkg/webhook/handlers/testdata/apis/testgroup/v1/doc.go +++ b/internal/apis/config/controller/doc.go @@ -1,5 +1,5 @@ /* -Copyright 2020 The cert-manager Authors. +Copyright 2021 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. @@ -14,9 +14,8 @@ See the License for the specific language governing permissions and limitations under the License. */ -// +k8s:conversion-gen=github.com/cert-manager/cert-manager/pkg/webhook/handlers/testdata/apis/testgroup // +k8s:deepcopy-gen=package,register -// +k8s:defaulter-gen=TypeMeta -// +groupName=testgroup.testing.cert-manager.io -package v1 +// Package controller is the internal version of the controller config API. +// +groupName=controller.config.cert-manager.io +package controller diff --git a/internal/apis/config/controller/fuzzer/fuzzer.go b/internal/apis/config/controller/fuzzer/fuzzer.go new file mode 100644 index 00000000000..6f424453887 --- /dev/null +++ b/internal/apis/config/controller/fuzzer/fuzzer.go @@ -0,0 +1,119 @@ +/* +Copyright 2021 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. +*/ + +package fuzzer + +import ( + "time" + + runtimeserializer "k8s.io/apimachinery/pkg/runtime/serializer" + logsapi "k8s.io/component-base/logs/api/v1" + "sigs.k8s.io/randfill" + + "github.com/cert-manager/cert-manager/internal/apis/config/controller" +) + +// Funcs returns the fuzzer functions for the controller config api group. +var Funcs = func(codecs runtimeserializer.CodecFactory) []interface{} { + return []interface{}{ + // provide non-empty values for fields with defaults, so the defaulter doesn't change values during round-trip + func(s *controller.ControllerConfiguration, c randfill.Continue) { + c.FillNoCustom(s) // fuzz self without calling this function again + + if s.ClusterResourceNamespace == "" { + s.ClusterResourceNamespace = "test-roundtrip" + } + + if len(s.Controllers) == 0 { + s.Controllers = []string{"test-roundtrip"} + } + + if len(s.CopiedAnnotationPrefixes) == 0 { + s.CopiedAnnotationPrefixes = []string{"test-roundtrip"} + } + + if s.MetricsListenAddress == "" { + s.MetricsListenAddress = "test-roundtrip" + } + + if s.HealthzListenAddress == "" { + s.HealthzListenAddress = "test-roundtrip" + } + + if s.PprofAddress == "" { + s.PprofAddress = "test-roundtrip" + } + + logsapi.SetRecommendedLoggingConfiguration(&s.Logging) + + if s.LeaderElectionConfig.Namespace == "" { + s.LeaderElectionConfig.Namespace = "test-roundtrip" + } + + if s.LeaderElectionConfig.LeaseDuration == time.Duration(0) { + s.LeaderElectionConfig.LeaseDuration = time.Second * 8875 + } + + if s.LeaderElectionConfig.RenewDeadline == time.Duration(0) { + s.LeaderElectionConfig.RenewDeadline = time.Second * 8875 + } + + if s.LeaderElectionConfig.RetryPeriod == time.Duration(0) { + s.LeaderElectionConfig.RetryPeriod = time.Second * 8875 + } + + if s.LeaderElectionConfig.HealthzTimeout == time.Duration(0) { + s.LeaderElectionConfig.HealthzTimeout = time.Second * 8875 + } + + if s.IngressShimConfig.DefaultIssuerKind == "" { + s.IngressShimConfig.DefaultIssuerKind = "test-roundtrip" + } + + if s.IngressShimConfig.DefaultIssuerGroup == "" { + s.IngressShimConfig.DefaultIssuerGroup = "test-roundtrip" + } + + if len(s.IngressShimConfig.DefaultAutoCertificateAnnotations) == 0 { + s.IngressShimConfig.DefaultAutoCertificateAnnotations = []string{"test-roundtrip"} + } + + if s.ACMEHTTP01Config.SolverImage == "" { + s.ACMEHTTP01Config.SolverImage = "test-roundtrip" + } + + if s.ACMEHTTP01Config.SolverResourceRequestCPU == "" { + s.ACMEHTTP01Config.SolverResourceRequestCPU = "test-roundtrip" + } + + if s.ACMEHTTP01Config.SolverResourceRequestMemory == "" { + s.ACMEHTTP01Config.SolverResourceRequestMemory = "test-roundtrip" + } + + if s.ACMEHTTP01Config.SolverResourceLimitsCPU == "" { + s.ACMEHTTP01Config.SolverResourceLimitsCPU = "test-roundtrip" + } + + if s.ACMEHTTP01Config.SolverResourceLimitsMemory == "" { + s.ACMEHTTP01Config.SolverResourceLimitsMemory = "test-roundtrip" + } + + if s.ACMEDNS01Config.CheckRetryPeriod == time.Duration(0) { + s.ACMEDNS01Config.CheckRetryPeriod = time.Second * 8875 + } + }, + } +} diff --git a/internal/webhook/scheme.go b/internal/apis/config/controller/install/install.go similarity index 53% rename from internal/webhook/scheme.go rename to internal/apis/config/controller/install/install.go index 81db1203691..5836717f8b6 100644 --- a/internal/webhook/scheme.go +++ b/internal/apis/config/controller/install/install.go @@ -14,21 +14,20 @@ See the License for the specific language governing permissions and limitations under the License. */ -package webhook +// Package install installs the API group, making it available as an option to +// all of the API encoding/decoding machinery. +package install import ( "k8s.io/apimachinery/pkg/runtime" -) - -// Define a Scheme that has all cert-manager API types registered, including -// the internal API version, defaulting functions and conversion functions for -// all external versions. -// This scheme should *only* be used by the webhook as the conversion/defaulter -// functions are likely to change in the future. + utilruntime "k8s.io/apimachinery/pkg/util/runtime" -var ( - // Scheme is a Kubernetes runtime.Scheme with all internal and external API - // versions for cert-manager types registered. - // TODO: this type should not be exported - Scheme = runtime.NewScheme() + "github.com/cert-manager/cert-manager/internal/apis/config/controller" + "github.com/cert-manager/cert-manager/internal/apis/config/controller/v1alpha1" ) + +// Install registers the API group and adds types to a scheme +func Install(scheme *runtime.Scheme) { + utilruntime.Must(controller.AddToScheme(scheme)) + utilruntime.Must(v1alpha1.AddToScheme(scheme)) +} diff --git a/cmd/util/exit.go b/internal/apis/config/controller/install/roundtrip_test.go similarity index 67% rename from cmd/util/exit.go rename to internal/apis/config/controller/install/roundtrip_test.go index 206fc0cb016..1ef6dc62464 100644 --- a/cmd/util/exit.go +++ b/internal/apis/config/controller/install/roundtrip_test.go @@ -14,16 +14,16 @@ See the License for the specific language governing permissions and limitations under the License. */ -package util +package install import ( - "context" - "errors" + "testing" + + "k8s.io/apimachinery/pkg/api/apitesting/roundtrip" + + configfuzzer "github.com/cert-manager/cert-manager/internal/apis/config/controller/fuzzer" ) -// SetExitCode sets the exit code to 1 if the error is not a context.Canceled error. -func SetExitCode(err error) { - if (err != nil) && !errors.Is(err, context.Canceled) { - errorExitCodeChannel <- 1 // Indicate that there was an error - } +func TestRoundTripTypes(t *testing.T) { + roundtrip.RoundTripTestForAPIGroup(t, Install, configfuzzer.Funcs) } diff --git a/internal/apis/config/controller/register.go b/internal/apis/config/controller/register.go new file mode 100644 index 00000000000..d0a4ceb0188 --- /dev/null +++ b/internal/apis/config/controller/register.go @@ -0,0 +1,46 @@ +/* +Copyright 2021 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. +*/ + +package controller + +import ( + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + + "github.com/cert-manager/cert-manager/pkg/apis/config/controller" +) + +var ( + SchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes) + AddToScheme = SchemeBuilder.AddToScheme +) + +// SchemeGroupVersion is group version used to register these objects +var SchemeGroupVersion = schema.GroupVersion{Group: controller.GroupName, Version: runtime.APIVersionInternal} + +// Resource takes an unqualified resource and returns a Group qualified GroupResource +func Resource(resource string) schema.GroupResource { + return SchemeGroupVersion.WithResource(resource).GroupResource() +} + +// Adds the list of known types to api.Scheme. +func addKnownTypes(scheme *runtime.Scheme) error { + scheme.AddKnownTypes(SchemeGroupVersion, + &ControllerConfiguration{}, + // Add new kinds to be registered here + ) + return nil +} diff --git a/internal/apis/config/controller/scheme/scheme.go b/internal/apis/config/controller/scheme/scheme.go new file mode 100644 index 00000000000..f2abc05b199 --- /dev/null +++ b/internal/apis/config/controller/scheme/scheme.go @@ -0,0 +1,40 @@ +/* +Copyright 2021 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. +*/ + +package scheme + +import ( + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/serializer" + + config "github.com/cert-manager/cert-manager/internal/apis/config/controller" + configv1alpha1 "github.com/cert-manager/cert-manager/internal/apis/config/controller/v1alpha1" +) + +// NewSchemeAndCodecs is a utility function that returns a Scheme and CodecFactory +// that understand the types in the config.cert-manager.io API group. Passing mutators allows +// for adjusting the behavior of the CodecFactory, for example enable strict decoding. +func NewSchemeAndCodecs(mutators ...serializer.CodecFactoryOptionsMutator) (*runtime.Scheme, *serializer.CodecFactory, error) { + scheme := runtime.NewScheme() + if err := config.AddToScheme(scheme); err != nil { + return nil, nil, err + } + if err := configv1alpha1.AddToScheme(scheme); err != nil { + return nil, nil, err + } + codecs := serializer.NewCodecFactory(scheme, mutators...) + return scheme, &codecs, nil +} diff --git a/internal/apis/config/controller/types.go b/internal/apis/config/controller/types.go new file mode 100644 index 00000000000..dbc645b2df7 --- /dev/null +++ b/internal/apis/config/controller/types.go @@ -0,0 +1,225 @@ +/* +Copyright 2021 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. +*/ + +package controller + +import ( + "time" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + logsapi "k8s.io/component-base/logs/api/v1" + + shared "github.com/cert-manager/cert-manager/internal/apis/config/shared" +) + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +type ControllerConfiguration struct { + metav1.TypeMeta + + // Optional apiserver host address to connect to. If not specified, + // autoconfiguration will be attempted + APIServerHost string + + // Paths to a kubeconfig. Only required if out-of-cluster. + KubeConfig string + + // Indicates the maximum queries-per-second requests to the Kubernetes apiserver + KubernetesAPIQPS float32 + + // The maximum burst queries-per-second of requests sent to the Kubernetes apiserver + KubernetesAPIBurst int + + // 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 + Namespace string + + // Namespace to store resources owned by cluster scoped resources such as ClusterIssuer in. + ClusterResourceNamespace string + + // LeaderElectionConfig configures the behaviour of the leader election + LeaderElectionConfig LeaderElectionConfig + + // A list of controllers to enable. + // ['*'] enables all controllers, + // ['foo'] enables only the foo controller + // ['*', '-foo'] disables the controller named foo. + Controllers []string + + // 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. + IssuerAmbientCredentials 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. + ClusterIssuerAmbientCredentials 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. + EnableCertificateOwnerRef bool + + // Whether gateway API integration is enabled within cert-manager. The + // ExperimentalGatewayAPISupport feature gate must also be enabled (default + // as of 1.15). + EnableGatewayAPI bool + + // 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.kubernetes.io/'- all annotations will be copied apart from the + // ones where the key is prefixed with 'kubectl.kubernetes.io/'. + CopiedAnnotationPrefixes []string + + // The number of concurrent workers for each controller. + NumberOfConcurrentWorkers int + + // The maximum number of challenges that can be scheduled as 'processing' at once. + MaxConcurrentChallenges int + + // The host and port that the metrics endpoint should listen on. + MetricsListenAddress string + + // Metrics endpoint TLS config + MetricsTLSConfig shared.TLSConfig + + // The host and port address, separated by a ':', that the healthz server + // should listen on. + HealthzListenAddress string + + // Enable profiling for controller. + EnablePprof bool + + // 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. + PprofAddress string + + // https://pkg.go.dev/k8s.io/component-base@v0.27.3/logs/api/v1#LoggingConfiguration + Logging logsapi.LoggingConfiguration + + // featureGates is a map of feature names to bools that enable or disable experimental + // features. + FeatureGates map[string]bool + + // IngressShimConfig configures the behaviour of the ingress-shim controller + IngressShimConfig IngressShimConfig + + // ACMEHTTP01Config configures the behaviour of the ACME HTTP01 challenge solver + ACMEHTTP01Config ACMEHTTP01Config + + // ACMEDNS01Config configures the behaviour of the ACME DNS01 challenge solver + ACMEDNS01Config ACMEDNS01Config +} + +type LeaderElectionConfig struct { + shared.LeaderElectionConfig + + // Leader election healthz checks within this timeout period after the lease + // expires will still return healthy. + HealthzTimeout time.Duration +} + +type IngressShimConfig struct { + // 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. + DefaultIssuerName string + + // Kind of the Issuer to use when the TLS is requested but issuer kind is not + // specified on the ingress resource. + DefaultIssuerKind string + + // Group of the Issuer to use when the TLS is requested but issuer group is + // not specified on the ingress resource. + DefaultIssuerGroup string + + // The annotation consumed by the ingress-shim controller to indicate an ingress + // is requesting a certificate + DefaultAutoCertificateAnnotations []string + + // ExtraCertificateAnnotations is a list of annotations which should be copied from + // and ingress-like object to a Certificate. + ExtraCertificateAnnotations []string +} + +type ACMEHTTP01Config struct { + // 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. + SolverImage string + + // Defines the resource request CPU size when spawning new ACME HTTP01 + // challenge solver pods. + SolverResourceRequestCPU string + + // Defines the resource request Memory size when spawning new ACME HTTP01 + // challenge solver pods. + SolverResourceRequestMemory string + + // Defines the resource limits CPU size when spawning new ACME HTTP01 + // challenge solver pods. + SolverResourceLimitsCPU string + + // Defines the resource limits Memory size when spawning new ACME HTTP01 + // challenge solver pods. + SolverResourceLimitsMemory string + + // Defines the ability to run the http01 solver as root for troubleshooting + // issues + SolverRunAsNonRoot bool + + // 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. + SolverNameservers []string +} + +type ACMEDNS01Config struct { + // 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) + RecursiveNameservers []string + + // 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. + RecursiveNameserversOnly bool + + // 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 + CheckRetryPeriod time.Duration +} diff --git a/internal/apis/config/controller/v1alpha1/defaults.go b/internal/apis/config/controller/v1alpha1/defaults.go new file mode 100644 index 00000000000..614b9819473 --- /dev/null +++ b/internal/apis/config/controller/v1alpha1/defaults.go @@ -0,0 +1,332 @@ +/* +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. +*/ + +package v1alpha1 + +import ( + "fmt" + "time" + + "k8s.io/apimachinery/pkg/runtime" + logsapi "k8s.io/component-base/logs/api/v1" + + cm "github.com/cert-manager/cert-manager/pkg/apis/certmanager" + "github.com/cert-manager/cert-manager/pkg/apis/config/controller/v1alpha1" + sharedv1alpha1 "github.com/cert-manager/cert-manager/pkg/apis/config/shared/v1alpha1" + challengescontroller "github.com/cert-manager/cert-manager/pkg/controller/acmechallenges" + orderscontroller "github.com/cert-manager/cert-manager/pkg/controller/acmeorders" + shimgatewaycontroller "github.com/cert-manager/cert-manager/pkg/controller/certificate-shim/gateways" + shimingresscontroller "github.com/cert-manager/cert-manager/pkg/controller/certificate-shim/ingresses" + cracmecontroller "github.com/cert-manager/cert-manager/pkg/controller/certificaterequests/acme" + crapprovercontroller "github.com/cert-manager/cert-manager/pkg/controller/certificaterequests/approver" + crcacontroller "github.com/cert-manager/cert-manager/pkg/controller/certificaterequests/ca" + crselfsignedcontroller "github.com/cert-manager/cert-manager/pkg/controller/certificaterequests/selfsigned" + crvaultcontroller "github.com/cert-manager/cert-manager/pkg/controller/certificaterequests/vault" + crvenaficontroller "github.com/cert-manager/cert-manager/pkg/controller/certificaterequests/venafi" + "github.com/cert-manager/cert-manager/pkg/controller/certificates/issuing" + "github.com/cert-manager/cert-manager/pkg/controller/certificates/keymanager" + certificatesmetricscontroller "github.com/cert-manager/cert-manager/pkg/controller/certificates/metrics" + "github.com/cert-manager/cert-manager/pkg/controller/certificates/readiness" + "github.com/cert-manager/cert-manager/pkg/controller/certificates/requestmanager" + "github.com/cert-manager/cert-manager/pkg/controller/certificates/revisionmanager" + "github.com/cert-manager/cert-manager/pkg/controller/certificates/trigger" + csracmecontroller "github.com/cert-manager/cert-manager/pkg/controller/certificatesigningrequests/acme" + csrcacontroller "github.com/cert-manager/cert-manager/pkg/controller/certificatesigningrequests/ca" + csrselfsignedcontroller "github.com/cert-manager/cert-manager/pkg/controller/certificatesigningrequests/selfsigned" + csrvaultcontroller "github.com/cert-manager/cert-manager/pkg/controller/certificatesigningrequests/vault" + csrvenaficontroller "github.com/cert-manager/cert-manager/pkg/controller/certificatesigningrequests/venafi" + clusterissuerscontroller "github.com/cert-manager/cert-manager/pkg/controller/clusterissuers" + issuerscontroller "github.com/cert-manager/cert-manager/pkg/controller/issuers" + "github.com/cert-manager/cert-manager/pkg/util" +) + +var ( + defaultAPIServerHost = "" + defaultKubeconfig = "" + defaultKubernetesAPIQPS float32 = 20 + defaultKubernetesAPIBurst int32 = 50 + + defaultClusterResourceNamespace = "kube-system" + defaultNamespace = "" + + defaultEnableProfiling = false + defaultProfilerAddr = "localhost:6060" + + defaultClusterIssuerAmbientCredentials = true + defaultIssuerAmbientCredentials = false + + defaultTLSACMEIssuerName = "" + defaultTLSACMEIssuerKind = "Issuer" + defaultTLSACMEIssuerGroup = cm.GroupName + defaultEnableCertificateOwnerRef = false + defaultEnableGatewayAPI = false + + defaultDNS01RecursiveNameserversOnly = false + defaultDNS01RecursiveNameservers = []string{} + defaultDNS01CheckRetryPeriod = 10 * time.Second + + defaultNumberOfConcurrentWorkers int32 = 5 + defaultMaxConcurrentChallenges int32 = 60 + + defaultPrometheusMetricsServerAddress = "0.0.0.0:9402" + + defaultHealthzServerAddress = "0.0.0.0:9403" + // This default value is the same as used in Kubernetes controller-manager. + // See: + // https://github.com/kubernetes/kubernetes/blob/806b30170c61a38fedd54cc9ede4cd6275a1ad3b/cmd/kube-controller-manager/app/controllermanager.go#L202-L209 + defaultHealthzLeaderElectionTimeout = 20 * time.Second + + // default time period to wait between checking DNS01 and HTTP01 challenge propagation + defaultACMEHTTP01SolverImage = fmt.Sprintf("quay.io/jetstack/cert-manager-acmesolver:%s", util.AppVersion) + defaultACMEHTTP01SolverResourceRequestCPU = "10m" + defaultACMEHTTP01SolverResourceRequestMemory = "64Mi" + defaultACMEHTTP01SolverResourceLimitsCPU = "100m" + defaultACMEHTTP01SolverResourceLimitsMemory = "64Mi" + defaultACMEHTTP01SolverRunAsNonRoot = true + defaultACMEHTTP01SolverNameservers = []string{} + + defaultAutoCertificateAnnotations = []string{"kubernetes.io/tls-acme"} + defaultExtraCertificateAnnotations = []string{} + + AllControllers = []string{ + issuerscontroller.ControllerName, + clusterissuerscontroller.ControllerName, + certificatesmetricscontroller.ControllerName, + shimingresscontroller.ControllerName, + shimgatewaycontroller.ControllerName, + orderscontroller.ControllerName, + challengescontroller.ControllerName, + cracmecontroller.CRControllerName, + crapprovercontroller.ControllerName, + crcacontroller.CRControllerName, + crselfsignedcontroller.CRControllerName, + crvaultcontroller.CRControllerName, + crvenaficontroller.CRControllerName, + // certificate controllers + trigger.ControllerName, + issuing.ControllerName, + keymanager.ControllerName, + requestmanager.ControllerName, + readiness.ControllerName, + revisionmanager.ControllerName, + // experimental CSR controllers + csracmecontroller.CSRControllerName, + csrcacontroller.CSRControllerName, + csrselfsignedcontroller.CSRControllerName, + csrvenaficontroller.CSRControllerName, + csrvaultcontroller.CSRControllerName, + } + + DefaultEnabledControllers = []string{ + issuerscontroller.ControllerName, + clusterissuerscontroller.ControllerName, + certificatesmetricscontroller.ControllerName, + shimingresscontroller.ControllerName, + orderscontroller.ControllerName, + challengescontroller.ControllerName, + cracmecontroller.CRControllerName, + crapprovercontroller.ControllerName, + crcacontroller.CRControllerName, + crselfsignedcontroller.CRControllerName, + crvaultcontroller.CRControllerName, + crvenaficontroller.CRControllerName, + // certificate controllers + trigger.ControllerName, + issuing.ControllerName, + keymanager.ControllerName, + requestmanager.ControllerName, + readiness.ControllerName, + revisionmanager.ControllerName, + } + + ExperimentalCertificateSigningRequestControllers = []string{ + csracmecontroller.CSRControllerName, + csrcacontroller.CSRControllerName, + csrselfsignedcontroller.CSRControllerName, + csrvenaficontroller.CSRControllerName, + csrvaultcontroller.CSRControllerName, + } + + ClusterScopedControllers = []string{ + clusterissuerscontroller.ControllerName, + csracmecontroller.CSRControllerName, + csrcacontroller.CSRControllerName, + csrselfsignedcontroller.CSRControllerName, + csrvenaficontroller.CSRControllerName, + csrvaultcontroller.CSRControllerName, + } + + // Annotations that will be copied from Certificate to CertificateRequest and to Order. + // By default, copy all annotations except for the ones applied by kubectl, fluxcd, argocd. + defaultCopiedAnnotationPrefixes = []string{ + "*", + "-kubectl.kubernetes.io/", + "-fluxcd.io/", + "-argocd.argoproj.io/", + } +) + +func addDefaultingFuncs(scheme *runtime.Scheme) error { + return RegisterDefaults(scheme) +} + +func SetDefaults_ControllerConfiguration(obj *v1alpha1.ControllerConfiguration) { + if obj.APIServerHost == "" { + obj.APIServerHost = defaultAPIServerHost + } + + if obj.KubeConfig == "" { + obj.KubeConfig = defaultKubeconfig + } + + if obj.KubernetesAPIQPS == nil { + obj.KubernetesAPIQPS = &defaultKubernetesAPIQPS + } + + if obj.KubernetesAPIBurst == nil { + obj.KubernetesAPIBurst = &defaultKubernetesAPIBurst + } + + if obj.Namespace == "" { + obj.Namespace = defaultNamespace + } + + if obj.ClusterResourceNamespace == "" { + obj.ClusterResourceNamespace = defaultClusterResourceNamespace + } + + if len(obj.Controllers) == 0 { + obj.Controllers = []string{"*"} + } + + if obj.IssuerAmbientCredentials == nil { + obj.IssuerAmbientCredentials = &defaultIssuerAmbientCredentials + } + + if obj.ClusterIssuerAmbientCredentials == nil { + obj.ClusterIssuerAmbientCredentials = &defaultClusterIssuerAmbientCredentials + } + + if obj.EnableCertificateOwnerRef == nil { + obj.EnableCertificateOwnerRef = &defaultEnableCertificateOwnerRef + } + + if obj.EnableGatewayAPI == nil { + obj.EnableGatewayAPI = &defaultEnableGatewayAPI + } + + if len(obj.CopiedAnnotationPrefixes) == 0 { + obj.CopiedAnnotationPrefixes = defaultCopiedAnnotationPrefixes + } + + if obj.NumberOfConcurrentWorkers == nil { + obj.NumberOfConcurrentWorkers = &defaultNumberOfConcurrentWorkers + } + + if obj.MaxConcurrentChallenges == nil { + obj.MaxConcurrentChallenges = &defaultMaxConcurrentChallenges + } + + if obj.MetricsListenAddress == "" { + obj.MetricsListenAddress = defaultPrometheusMetricsServerAddress + } + + if obj.HealthzListenAddress == "" { + obj.HealthzListenAddress = defaultHealthzServerAddress + } + + if obj.EnablePprof == nil { + obj.EnablePprof = &defaultEnableProfiling + } + + if obj.PprofAddress == "" { + obj.PprofAddress = defaultProfilerAddr + } + + logsapi.SetRecommendedLoggingConfiguration(&obj.Logging) +} + +func SetDefaults_LeaderElectionConfig(obj *v1alpha1.LeaderElectionConfig) { + if obj.HealthzTimeout.IsZero() { + obj.HealthzTimeout = sharedv1alpha1.DurationFromTime(defaultHealthzLeaderElectionTimeout) + } +} + +func SetDefaults_IngressShimConfig(obj *v1alpha1.IngressShimConfig) { + if obj.DefaultIssuerName == "" { + obj.DefaultIssuerName = defaultTLSACMEIssuerName + } + + if obj.DefaultIssuerKind == "" { + obj.DefaultIssuerKind = defaultTLSACMEIssuerKind + } + + if obj.DefaultIssuerGroup == "" { + obj.DefaultIssuerGroup = defaultTLSACMEIssuerGroup + } + + if len(obj.DefaultAutoCertificateAnnotations) == 0 { + obj.DefaultAutoCertificateAnnotations = defaultAutoCertificateAnnotations + } + + if len(obj.ExtraCertificateAnnotations) == 0 { + obj.ExtraCertificateAnnotations = defaultExtraCertificateAnnotations + } +} + +func SetDefaults_ACMEHTTP01Config(obj *v1alpha1.ACMEHTTP01Config) { + if obj.SolverImage == "" { + obj.SolverImage = defaultACMEHTTP01SolverImage + } + + if obj.SolverResourceRequestCPU == "" { + obj.SolverResourceRequestCPU = defaultACMEHTTP01SolverResourceRequestCPU + } + + if obj.SolverResourceRequestMemory == "" { + obj.SolverResourceRequestMemory = defaultACMEHTTP01SolverResourceRequestMemory + } + + if obj.SolverResourceLimitsCPU == "" { + obj.SolverResourceLimitsCPU = defaultACMEHTTP01SolverResourceLimitsCPU + } + + if obj.SolverResourceLimitsMemory == "" { + obj.SolverResourceLimitsMemory = defaultACMEHTTP01SolverResourceLimitsMemory + } + + if obj.SolverRunAsNonRoot == nil { + obj.SolverRunAsNonRoot = &defaultACMEHTTP01SolverRunAsNonRoot + } + + if len(obj.SolverNameservers) == 0 { + obj.SolverNameservers = defaultACMEHTTP01SolverNameservers + } +} + +func SetDefaults_ACMEDNS01Config(obj *v1alpha1.ACMEDNS01Config) { + if len(obj.RecursiveNameservers) == 0 { + obj.RecursiveNameservers = defaultDNS01RecursiveNameservers + } + + if obj.RecursiveNameserversOnly == nil { + obj.RecursiveNameserversOnly = &defaultDNS01RecursiveNameserversOnly + } + + if obj.CheckRetryPeriod.IsZero() { + obj.CheckRetryPeriod = sharedv1alpha1.DurationFromTime(defaultDNS01CheckRetryPeriod) + } +} diff --git a/internal/apis/config/controller/v1alpha1/defaults_test.go b/internal/apis/config/controller/v1alpha1/defaults_test.go new file mode 100644 index 00000000000..65effc2d617 --- /dev/null +++ b/internal/apis/config/controller/v1alpha1/defaults_test.go @@ -0,0 +1,65 @@ +/* +Copyright 2021 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. +*/ + +package v1alpha1 + +import ( + "encoding/json" + "os" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/cert-manager/cert-manager/pkg/apis/config/controller/v1alpha1" +) + +const TestFileLocation = "testdata/defaults.json" + +func TestControllerConfigurationDefaults(t *testing.T) { + tests := []struct { + name string + config *v1alpha1.ControllerConfiguration + jsonFilePath string + }{ + { + "v1alpha1", + &v1alpha1.ControllerConfiguration{}, + "testdata/defaults.json", + }, + } + for _, tt := range tests { + SetObjectDefaults_ControllerConfiguration(tt.config) + + defaultData, err := json.MarshalIndent(tt.config, "", "\t") + if err != nil { + t.Fatal(err) + } + + if os.Getenv("UPDATE_DEFAULTS") == "true" { + if err := os.WriteFile(tt.jsonFilePath, defaultData, 0644); err != nil { + t.Fatal(err) + } + t.Log("controller config api defaults updated") + } + + expectedData, err := os.ReadFile(tt.jsonFilePath) + if err != nil { + t.Fatal(err) + } + + require.Equal(t, expectedData, defaultData) + } +} diff --git a/internal/apis/acme/v1alpha2/doc.go b/internal/apis/config/controller/v1alpha1/doc.go similarity index 71% rename from internal/apis/acme/v1alpha2/doc.go rename to internal/apis/config/controller/v1alpha1/doc.go index c7e251759ee..4a7f9fe7ed1 100644 --- a/internal/apis/acme/v1alpha2/doc.go +++ b/internal/apis/config/controller/v1alpha1/doc.go @@ -1,5 +1,5 @@ /* -Copyright 2020 The cert-manager Authors. +Copyright 2021 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. @@ -14,10 +14,10 @@ See the License for the specific language governing permissions and limitations under the License. */ -// +k8s:conversion-gen=github.com/cert-manager/cert-manager/internal/apis/acme -// +k8s:conversion-gen-external-types=github.com/cert-manager/cert-manager/internal/apis/acme/v1alpha2 +// +k8s:conversion-gen=github.com/cert-manager/cert-manager/internal/apis/config/controller +// +k8s:conversion-gen-external-types=github.com/cert-manager/cert-manager/pkg/apis/config/controller/v1alpha1 // +k8s:defaulter-gen=TypeMeta -// +k8s:deepcopy-gen=package,register +// +k8s:defaulter-gen-input=github.com/cert-manager/cert-manager/pkg/apis/config/controller/v1alpha1 -// +groupName=acme.cert-manager.io -package v1alpha2 +// +groupName=controller.config.cert-manager.io +package v1alpha1 diff --git a/internal/apis/acme/v1alpha2/register.go b/internal/apis/config/controller/v1alpha1/register.go similarity index 57% rename from internal/apis/acme/v1alpha2/register.go rename to internal/apis/config/controller/v1alpha1/register.go index 180f5eb9449..b054e3f36df 100644 --- a/internal/apis/acme/v1alpha2/register.go +++ b/internal/apis/config/controller/v1alpha1/register.go @@ -1,5 +1,5 @@ /* -Copyright 2020 The cert-manager Authors. +Copyright 2021 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. @@ -14,18 +14,17 @@ See the License for the specific language governing permissions and limitations under the License. */ -package v1alpha2 +package v1alpha1 import ( - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" - "github.com/cert-manager/cert-manager/pkg/apis/acme" + "github.com/cert-manager/cert-manager/pkg/apis/config/controller" + "github.com/cert-manager/cert-manager/pkg/apis/config/controller/v1alpha1" ) // SchemeGroupVersion is group version used to register these objects -var SchemeGroupVersion = schema.GroupVersion{Group: acme.GroupName, Version: "v1alpha2"} +var SchemeGroupVersion = schema.GroupVersion{Group: controller.GroupName, Version: "v1alpha1"} // Resource takes an unqualified resource and returns a Group qualified GroupResource func Resource(resource string) schema.GroupResource { @@ -33,8 +32,7 @@ func Resource(resource string) schema.GroupResource { } var ( - SchemeBuilder runtime.SchemeBuilder - localSchemeBuilder = &SchemeBuilder + localSchemeBuilder = &v1alpha1.SchemeBuilder AddToScheme = localSchemeBuilder.AddToScheme ) @@ -43,21 +41,4 @@ func init() { // generated functions takes place in the generated files. The separation // makes the code compile even when the generated files are missing. localSchemeBuilder.Register(addDefaultingFuncs) - - // We only register manually written functions here. The registration of the - // generated functions takes place in the generated files. The separation - // makes the code compile even when the generated files are missing. - localSchemeBuilder.Register(addKnownTypes) -} - -// Adds the list of known types to api.Scheme. -func addKnownTypes(scheme *runtime.Scheme) error { - scheme.AddKnownTypes(SchemeGroupVersion, - &Order{}, - &OrderList{}, - &Challenge{}, - &ChallengeList{}, - ) - metav1.AddToGroupVersion(scheme, SchemeGroupVersion) - return nil } diff --git a/internal/apis/config/controller/v1alpha1/testdata/defaults.json b/internal/apis/config/controller/v1alpha1/testdata/defaults.json new file mode 100644 index 00000000000..9df951afe27 --- /dev/null +++ b/internal/apis/config/controller/v1alpha1/testdata/defaults.json @@ -0,0 +1,70 @@ +{ + "kubernetesAPIQPS": 20, + "kubernetesAPIBurst": 50, + "clusterResourceNamespace": "kube-system", + "leaderElectionConfig": { + "enabled": true, + "namespace": "kube-system", + "leaseDuration": "1m0s", + "renewDeadline": "40s", + "retryPeriod": "15s", + "healthzTimeout": "20s" + }, + "controllers": [ + "*" + ], + "issuerAmbientCredentials": false, + "clusterIssuerAmbientCredentials": true, + "enableCertificateOwnerRef": false, + "enableGatewayAPI": false, + "copiedAnnotationPrefixes": [ + "*", + "-kubectl.kubernetes.io/", + "-fluxcd.io/", + "-argocd.argoproj.io/" + ], + "numberOfConcurrentWorkers": 5, + "maxConcurrentChallenges": 60, + "metricsListenAddress": "0.0.0.0:9402", + "metricsTLSConfig": { + "filesystem": {}, + "dynamic": { + "leafDuration": "168h0m0s" + } + }, + "healthzListenAddress": "0.0.0.0:9403", + "enablePprof": false, + "pprofAddress": "localhost:6060", + "logging": { + "format": "text", + "flushFrequency": "5s", + "verbosity": 0, + "options": { + "text": { + "infoBufferSize": "0" + }, + "json": { + "infoBufferSize": "0" + } + } + }, + "ingressShimConfig": { + "defaultIssuerKind": "Issuer", + "defaultIssuerGroup": "cert-manager.io", + "defaultAutoCertificateAnnotations": [ + "kubernetes.io/tls-acme" + ] + }, + "acmeHTTP01Config": { + "solverImage": "quay.io/jetstack/cert-manager-acmesolver:canary", + "solverResourceRequestCPU": "10m", + "solverResourceRequestMemory": "64Mi", + "solverResourceLimitsCPU": "100m", + "solverResourceLimitsMemory": "64Mi", + "solverRunAsNonRoot": true + }, + "acmeDNS01Config": { + "recursiveNameserversOnly": false, + "checkRetryPeriod": "10s" + } +} \ No newline at end of file diff --git a/internal/apis/config/controller/v1alpha1/zz_generated.conversion.go b/internal/apis/config/controller/v1alpha1/zz_generated.conversion.go new file mode 100644 index 00000000000..2446b8cb168 --- /dev/null +++ b/internal/apis/config/controller/v1alpha1/zz_generated.conversion.go @@ -0,0 +1,343 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by conversion-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + unsafe "unsafe" + + controller "github.com/cert-manager/cert-manager/internal/apis/config/controller" + sharedv1alpha1 "github.com/cert-manager/cert-manager/internal/apis/config/shared/v1alpha1" + controllerv1alpha1 "github.com/cert-manager/cert-manager/pkg/apis/config/controller/v1alpha1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + conversion "k8s.io/apimachinery/pkg/conversion" + runtime "k8s.io/apimachinery/pkg/runtime" +) + +func init() { + localSchemeBuilder.Register(RegisterConversions) +} + +// RegisterConversions adds conversion functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterConversions(s *runtime.Scheme) error { + if err := s.AddGeneratedConversionFunc((*controllerv1alpha1.ACMEDNS01Config)(nil), (*controller.ACMEDNS01Config)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1alpha1_ACMEDNS01Config_To_controller_ACMEDNS01Config(a.(*controllerv1alpha1.ACMEDNS01Config), b.(*controller.ACMEDNS01Config), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*controller.ACMEDNS01Config)(nil), (*controllerv1alpha1.ACMEDNS01Config)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_controller_ACMEDNS01Config_To_v1alpha1_ACMEDNS01Config(a.(*controller.ACMEDNS01Config), b.(*controllerv1alpha1.ACMEDNS01Config), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*controllerv1alpha1.ACMEHTTP01Config)(nil), (*controller.ACMEHTTP01Config)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1alpha1_ACMEHTTP01Config_To_controller_ACMEHTTP01Config(a.(*controllerv1alpha1.ACMEHTTP01Config), b.(*controller.ACMEHTTP01Config), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*controller.ACMEHTTP01Config)(nil), (*controllerv1alpha1.ACMEHTTP01Config)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_controller_ACMEHTTP01Config_To_v1alpha1_ACMEHTTP01Config(a.(*controller.ACMEHTTP01Config), b.(*controllerv1alpha1.ACMEHTTP01Config), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*controllerv1alpha1.ControllerConfiguration)(nil), (*controller.ControllerConfiguration)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1alpha1_ControllerConfiguration_To_controller_ControllerConfiguration(a.(*controllerv1alpha1.ControllerConfiguration), b.(*controller.ControllerConfiguration), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*controller.ControllerConfiguration)(nil), (*controllerv1alpha1.ControllerConfiguration)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_controller_ControllerConfiguration_To_v1alpha1_ControllerConfiguration(a.(*controller.ControllerConfiguration), b.(*controllerv1alpha1.ControllerConfiguration), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*controllerv1alpha1.IngressShimConfig)(nil), (*controller.IngressShimConfig)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1alpha1_IngressShimConfig_To_controller_IngressShimConfig(a.(*controllerv1alpha1.IngressShimConfig), b.(*controller.IngressShimConfig), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*controller.IngressShimConfig)(nil), (*controllerv1alpha1.IngressShimConfig)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_controller_IngressShimConfig_To_v1alpha1_IngressShimConfig(a.(*controller.IngressShimConfig), b.(*controllerv1alpha1.IngressShimConfig), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*controllerv1alpha1.LeaderElectionConfig)(nil), (*controller.LeaderElectionConfig)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1alpha1_LeaderElectionConfig_To_controller_LeaderElectionConfig(a.(*controllerv1alpha1.LeaderElectionConfig), b.(*controller.LeaderElectionConfig), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*controller.LeaderElectionConfig)(nil), (*controllerv1alpha1.LeaderElectionConfig)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_controller_LeaderElectionConfig_To_v1alpha1_LeaderElectionConfig(a.(*controller.LeaderElectionConfig), b.(*controllerv1alpha1.LeaderElectionConfig), scope) + }); err != nil { + return err + } + return nil +} + +func autoConvert_v1alpha1_ACMEDNS01Config_To_controller_ACMEDNS01Config(in *controllerv1alpha1.ACMEDNS01Config, out *controller.ACMEDNS01Config, s conversion.Scope) error { + out.RecursiveNameservers = *(*[]string)(unsafe.Pointer(&in.RecursiveNameservers)) + if err := v1.Convert_Pointer_bool_To_bool(&in.RecursiveNameserversOnly, &out.RecursiveNameserversOnly, s); err != nil { + return err + } + if err := sharedv1alpha1.Convert_Pointer_v1alpha1_Duration_To_time_Duration(&in.CheckRetryPeriod, &out.CheckRetryPeriod, s); err != nil { + return err + } + return nil +} + +// Convert_v1alpha1_ACMEDNS01Config_To_controller_ACMEDNS01Config is an autogenerated conversion function. +func Convert_v1alpha1_ACMEDNS01Config_To_controller_ACMEDNS01Config(in *controllerv1alpha1.ACMEDNS01Config, out *controller.ACMEDNS01Config, s conversion.Scope) error { + return autoConvert_v1alpha1_ACMEDNS01Config_To_controller_ACMEDNS01Config(in, out, s) +} + +func autoConvert_controller_ACMEDNS01Config_To_v1alpha1_ACMEDNS01Config(in *controller.ACMEDNS01Config, out *controllerv1alpha1.ACMEDNS01Config, s conversion.Scope) error { + out.RecursiveNameservers = *(*[]string)(unsafe.Pointer(&in.RecursiveNameservers)) + if err := v1.Convert_bool_To_Pointer_bool(&in.RecursiveNameserversOnly, &out.RecursiveNameserversOnly, s); err != nil { + return err + } + if err := sharedv1alpha1.Convert_time_Duration_To_Pointer_v1alpha1_Duration(&in.CheckRetryPeriod, &out.CheckRetryPeriod, s); err != nil { + return err + } + return nil +} + +// Convert_controller_ACMEDNS01Config_To_v1alpha1_ACMEDNS01Config is an autogenerated conversion function. +func Convert_controller_ACMEDNS01Config_To_v1alpha1_ACMEDNS01Config(in *controller.ACMEDNS01Config, out *controllerv1alpha1.ACMEDNS01Config, s conversion.Scope) error { + return autoConvert_controller_ACMEDNS01Config_To_v1alpha1_ACMEDNS01Config(in, out, s) +} + +func autoConvert_v1alpha1_ACMEHTTP01Config_To_controller_ACMEHTTP01Config(in *controllerv1alpha1.ACMEHTTP01Config, out *controller.ACMEHTTP01Config, s conversion.Scope) error { + out.SolverImage = in.SolverImage + out.SolverResourceRequestCPU = in.SolverResourceRequestCPU + out.SolverResourceRequestMemory = in.SolverResourceRequestMemory + out.SolverResourceLimitsCPU = in.SolverResourceLimitsCPU + out.SolverResourceLimitsMemory = in.SolverResourceLimitsMemory + if err := v1.Convert_Pointer_bool_To_bool(&in.SolverRunAsNonRoot, &out.SolverRunAsNonRoot, s); err != nil { + return err + } + out.SolverNameservers = *(*[]string)(unsafe.Pointer(&in.SolverNameservers)) + return nil +} + +// Convert_v1alpha1_ACMEHTTP01Config_To_controller_ACMEHTTP01Config is an autogenerated conversion function. +func Convert_v1alpha1_ACMEHTTP01Config_To_controller_ACMEHTTP01Config(in *controllerv1alpha1.ACMEHTTP01Config, out *controller.ACMEHTTP01Config, s conversion.Scope) error { + return autoConvert_v1alpha1_ACMEHTTP01Config_To_controller_ACMEHTTP01Config(in, out, s) +} + +func autoConvert_controller_ACMEHTTP01Config_To_v1alpha1_ACMEHTTP01Config(in *controller.ACMEHTTP01Config, out *controllerv1alpha1.ACMEHTTP01Config, s conversion.Scope) error { + out.SolverImage = in.SolverImage + out.SolverResourceRequestCPU = in.SolverResourceRequestCPU + out.SolverResourceRequestMemory = in.SolverResourceRequestMemory + out.SolverResourceLimitsCPU = in.SolverResourceLimitsCPU + out.SolverResourceLimitsMemory = in.SolverResourceLimitsMemory + if err := v1.Convert_bool_To_Pointer_bool(&in.SolverRunAsNonRoot, &out.SolverRunAsNonRoot, s); err != nil { + return err + } + out.SolverNameservers = *(*[]string)(unsafe.Pointer(&in.SolverNameservers)) + return nil +} + +// Convert_controller_ACMEHTTP01Config_To_v1alpha1_ACMEHTTP01Config is an autogenerated conversion function. +func Convert_controller_ACMEHTTP01Config_To_v1alpha1_ACMEHTTP01Config(in *controller.ACMEHTTP01Config, out *controllerv1alpha1.ACMEHTTP01Config, s conversion.Scope) error { + return autoConvert_controller_ACMEHTTP01Config_To_v1alpha1_ACMEHTTP01Config(in, out, s) +} + +func autoConvert_v1alpha1_ControllerConfiguration_To_controller_ControllerConfiguration(in *controllerv1alpha1.ControllerConfiguration, out *controller.ControllerConfiguration, s conversion.Scope) error { + out.KubeConfig = in.KubeConfig + out.APIServerHost = in.APIServerHost + if err := sharedv1alpha1.Convert_Pointer_float32_To_float32(&in.KubernetesAPIQPS, &out.KubernetesAPIQPS, s); err != nil { + return err + } + if err := sharedv1alpha1.Convert_Pointer_int32_To_int(&in.KubernetesAPIBurst, &out.KubernetesAPIBurst, s); err != nil { + return err + } + out.Namespace = in.Namespace + out.ClusterResourceNamespace = in.ClusterResourceNamespace + if err := Convert_v1alpha1_LeaderElectionConfig_To_controller_LeaderElectionConfig(&in.LeaderElectionConfig, &out.LeaderElectionConfig, s); err != nil { + return err + } + out.Controllers = *(*[]string)(unsafe.Pointer(&in.Controllers)) + if err := v1.Convert_Pointer_bool_To_bool(&in.IssuerAmbientCredentials, &out.IssuerAmbientCredentials, s); err != nil { + return err + } + if err := v1.Convert_Pointer_bool_To_bool(&in.ClusterIssuerAmbientCredentials, &out.ClusterIssuerAmbientCredentials, s); err != nil { + return err + } + if err := v1.Convert_Pointer_bool_To_bool(&in.EnableCertificateOwnerRef, &out.EnableCertificateOwnerRef, s); err != nil { + return err + } + if err := v1.Convert_Pointer_bool_To_bool(&in.EnableGatewayAPI, &out.EnableGatewayAPI, s); err != nil { + return err + } + out.CopiedAnnotationPrefixes = *(*[]string)(unsafe.Pointer(&in.CopiedAnnotationPrefixes)) + if err := sharedv1alpha1.Convert_Pointer_int32_To_int(&in.NumberOfConcurrentWorkers, &out.NumberOfConcurrentWorkers, s); err != nil { + return err + } + if err := sharedv1alpha1.Convert_Pointer_int32_To_int(&in.MaxConcurrentChallenges, &out.MaxConcurrentChallenges, s); err != nil { + return err + } + out.MetricsListenAddress = in.MetricsListenAddress + if err := sharedv1alpha1.Convert_v1alpha1_TLSConfig_To_shared_TLSConfig(&in.MetricsTLSConfig, &out.MetricsTLSConfig, s); err != nil { + return err + } + out.HealthzListenAddress = in.HealthzListenAddress + if err := v1.Convert_Pointer_bool_To_bool(&in.EnablePprof, &out.EnablePprof, s); err != nil { + return err + } + out.PprofAddress = in.PprofAddress + out.Logging = in.Logging + out.FeatureGates = *(*map[string]bool)(unsafe.Pointer(&in.FeatureGates)) + if err := Convert_v1alpha1_IngressShimConfig_To_controller_IngressShimConfig(&in.IngressShimConfig, &out.IngressShimConfig, s); err != nil { + return err + } + if err := Convert_v1alpha1_ACMEHTTP01Config_To_controller_ACMEHTTP01Config(&in.ACMEHTTP01Config, &out.ACMEHTTP01Config, s); err != nil { + return err + } + if err := Convert_v1alpha1_ACMEDNS01Config_To_controller_ACMEDNS01Config(&in.ACMEDNS01Config, &out.ACMEDNS01Config, s); err != nil { + return err + } + return nil +} + +// Convert_v1alpha1_ControllerConfiguration_To_controller_ControllerConfiguration is an autogenerated conversion function. +func Convert_v1alpha1_ControllerConfiguration_To_controller_ControllerConfiguration(in *controllerv1alpha1.ControllerConfiguration, out *controller.ControllerConfiguration, s conversion.Scope) error { + return autoConvert_v1alpha1_ControllerConfiguration_To_controller_ControllerConfiguration(in, out, s) +} + +func autoConvert_controller_ControllerConfiguration_To_v1alpha1_ControllerConfiguration(in *controller.ControllerConfiguration, out *controllerv1alpha1.ControllerConfiguration, s conversion.Scope) error { + out.APIServerHost = in.APIServerHost + out.KubeConfig = in.KubeConfig + if err := sharedv1alpha1.Convert_float32_To_Pointer_float32(&in.KubernetesAPIQPS, &out.KubernetesAPIQPS, s); err != nil { + return err + } + if err := sharedv1alpha1.Convert_int_To_Pointer_int32(&in.KubernetesAPIBurst, &out.KubernetesAPIBurst, s); err != nil { + return err + } + out.Namespace = in.Namespace + out.ClusterResourceNamespace = in.ClusterResourceNamespace + if err := Convert_controller_LeaderElectionConfig_To_v1alpha1_LeaderElectionConfig(&in.LeaderElectionConfig, &out.LeaderElectionConfig, s); err != nil { + return err + } + out.Controllers = *(*[]string)(unsafe.Pointer(&in.Controllers)) + if err := v1.Convert_bool_To_Pointer_bool(&in.IssuerAmbientCredentials, &out.IssuerAmbientCredentials, s); err != nil { + return err + } + if err := v1.Convert_bool_To_Pointer_bool(&in.ClusterIssuerAmbientCredentials, &out.ClusterIssuerAmbientCredentials, s); err != nil { + return err + } + if err := v1.Convert_bool_To_Pointer_bool(&in.EnableCertificateOwnerRef, &out.EnableCertificateOwnerRef, s); err != nil { + return err + } + if err := v1.Convert_bool_To_Pointer_bool(&in.EnableGatewayAPI, &out.EnableGatewayAPI, s); err != nil { + return err + } + out.CopiedAnnotationPrefixes = *(*[]string)(unsafe.Pointer(&in.CopiedAnnotationPrefixes)) + if err := sharedv1alpha1.Convert_int_To_Pointer_int32(&in.NumberOfConcurrentWorkers, &out.NumberOfConcurrentWorkers, s); err != nil { + return err + } + if err := sharedv1alpha1.Convert_int_To_Pointer_int32(&in.MaxConcurrentChallenges, &out.MaxConcurrentChallenges, s); err != nil { + return err + } + out.MetricsListenAddress = in.MetricsListenAddress + if err := sharedv1alpha1.Convert_shared_TLSConfig_To_v1alpha1_TLSConfig(&in.MetricsTLSConfig, &out.MetricsTLSConfig, s); err != nil { + return err + } + out.HealthzListenAddress = in.HealthzListenAddress + if err := v1.Convert_bool_To_Pointer_bool(&in.EnablePprof, &out.EnablePprof, s); err != nil { + return err + } + out.PprofAddress = in.PprofAddress + out.Logging = in.Logging + out.FeatureGates = *(*map[string]bool)(unsafe.Pointer(&in.FeatureGates)) + if err := Convert_controller_IngressShimConfig_To_v1alpha1_IngressShimConfig(&in.IngressShimConfig, &out.IngressShimConfig, s); err != nil { + return err + } + if err := Convert_controller_ACMEHTTP01Config_To_v1alpha1_ACMEHTTP01Config(&in.ACMEHTTP01Config, &out.ACMEHTTP01Config, s); err != nil { + return err + } + if err := Convert_controller_ACMEDNS01Config_To_v1alpha1_ACMEDNS01Config(&in.ACMEDNS01Config, &out.ACMEDNS01Config, s); err != nil { + return err + } + return nil +} + +// Convert_controller_ControllerConfiguration_To_v1alpha1_ControllerConfiguration is an autogenerated conversion function. +func Convert_controller_ControllerConfiguration_To_v1alpha1_ControllerConfiguration(in *controller.ControllerConfiguration, out *controllerv1alpha1.ControllerConfiguration, s conversion.Scope) error { + return autoConvert_controller_ControllerConfiguration_To_v1alpha1_ControllerConfiguration(in, out, s) +} + +func autoConvert_v1alpha1_IngressShimConfig_To_controller_IngressShimConfig(in *controllerv1alpha1.IngressShimConfig, out *controller.IngressShimConfig, s conversion.Scope) error { + out.DefaultIssuerName = in.DefaultIssuerName + out.DefaultIssuerKind = in.DefaultIssuerKind + out.DefaultIssuerGroup = in.DefaultIssuerGroup + out.DefaultAutoCertificateAnnotations = *(*[]string)(unsafe.Pointer(&in.DefaultAutoCertificateAnnotations)) + out.ExtraCertificateAnnotations = *(*[]string)(unsafe.Pointer(&in.ExtraCertificateAnnotations)) + return nil +} + +// Convert_v1alpha1_IngressShimConfig_To_controller_IngressShimConfig is an autogenerated conversion function. +func Convert_v1alpha1_IngressShimConfig_To_controller_IngressShimConfig(in *controllerv1alpha1.IngressShimConfig, out *controller.IngressShimConfig, s conversion.Scope) error { + return autoConvert_v1alpha1_IngressShimConfig_To_controller_IngressShimConfig(in, out, s) +} + +func autoConvert_controller_IngressShimConfig_To_v1alpha1_IngressShimConfig(in *controller.IngressShimConfig, out *controllerv1alpha1.IngressShimConfig, s conversion.Scope) error { + out.DefaultIssuerName = in.DefaultIssuerName + out.DefaultIssuerKind = in.DefaultIssuerKind + out.DefaultIssuerGroup = in.DefaultIssuerGroup + out.DefaultAutoCertificateAnnotations = *(*[]string)(unsafe.Pointer(&in.DefaultAutoCertificateAnnotations)) + out.ExtraCertificateAnnotations = *(*[]string)(unsafe.Pointer(&in.ExtraCertificateAnnotations)) + return nil +} + +// Convert_controller_IngressShimConfig_To_v1alpha1_IngressShimConfig is an autogenerated conversion function. +func Convert_controller_IngressShimConfig_To_v1alpha1_IngressShimConfig(in *controller.IngressShimConfig, out *controllerv1alpha1.IngressShimConfig, s conversion.Scope) error { + return autoConvert_controller_IngressShimConfig_To_v1alpha1_IngressShimConfig(in, out, s) +} + +func autoConvert_v1alpha1_LeaderElectionConfig_To_controller_LeaderElectionConfig(in *controllerv1alpha1.LeaderElectionConfig, out *controller.LeaderElectionConfig, s conversion.Scope) error { + if err := sharedv1alpha1.Convert_v1alpha1_LeaderElectionConfig_To_shared_LeaderElectionConfig(&in.LeaderElectionConfig, &out.LeaderElectionConfig, s); err != nil { + return err + } + if err := sharedv1alpha1.Convert_Pointer_v1alpha1_Duration_To_time_Duration(&in.HealthzTimeout, &out.HealthzTimeout, s); err != nil { + return err + } + return nil +} + +// Convert_v1alpha1_LeaderElectionConfig_To_controller_LeaderElectionConfig is an autogenerated conversion function. +func Convert_v1alpha1_LeaderElectionConfig_To_controller_LeaderElectionConfig(in *controllerv1alpha1.LeaderElectionConfig, out *controller.LeaderElectionConfig, s conversion.Scope) error { + return autoConvert_v1alpha1_LeaderElectionConfig_To_controller_LeaderElectionConfig(in, out, s) +} + +func autoConvert_controller_LeaderElectionConfig_To_v1alpha1_LeaderElectionConfig(in *controller.LeaderElectionConfig, out *controllerv1alpha1.LeaderElectionConfig, s conversion.Scope) error { + if err := sharedv1alpha1.Convert_shared_LeaderElectionConfig_To_v1alpha1_LeaderElectionConfig(&in.LeaderElectionConfig, &out.LeaderElectionConfig, s); err != nil { + return err + } + if err := sharedv1alpha1.Convert_time_Duration_To_Pointer_v1alpha1_Duration(&in.HealthzTimeout, &out.HealthzTimeout, s); err != nil { + return err + } + return nil +} + +// Convert_controller_LeaderElectionConfig_To_v1alpha1_LeaderElectionConfig is an autogenerated conversion function. +func Convert_controller_LeaderElectionConfig_To_v1alpha1_LeaderElectionConfig(in *controller.LeaderElectionConfig, out *controllerv1alpha1.LeaderElectionConfig, s conversion.Scope) error { + return autoConvert_controller_LeaderElectionConfig_To_v1alpha1_LeaderElectionConfig(in, out, s) +} diff --git a/pkg/webhook/handlers/testdata/apis/testgroup/v2/zz_generated.defaults.go b/internal/apis/config/controller/v1alpha1/zz_generated.defaults.go similarity index 51% rename from pkg/webhook/handlers/testdata/apis/testgroup/v2/zz_generated.defaults.go rename to internal/apis/config/controller/v1alpha1/zz_generated.defaults.go index fe83702e2ec..4b40bd6f2a8 100644 --- a/pkg/webhook/handlers/testdata/apis/testgroup/v2/zz_generated.defaults.go +++ b/internal/apis/config/controller/v1alpha1/zz_generated.defaults.go @@ -19,9 +19,11 @@ limitations under the License. // Code generated by defaulter-gen. DO NOT EDIT. -package v2 +package v1alpha1 import ( + sharedv1alpha1 "github.com/cert-manager/cert-manager/internal/apis/config/shared/v1alpha1" + controllerv1alpha1 "github.com/cert-manager/cert-manager/pkg/apis/config/controller/v1alpha1" runtime "k8s.io/apimachinery/pkg/runtime" ) @@ -29,10 +31,18 @@ import ( // Public to allow building arbitrary schemes. // All generated defaulters are covering - they call all nested defaulters. func RegisterDefaults(scheme *runtime.Scheme) error { - scheme.AddTypeDefaultingFunc(&TestType{}, func(obj interface{}) { SetObjectDefaults_TestType(obj.(*TestType)) }) + scheme.AddTypeDefaultingFunc(&controllerv1alpha1.ControllerConfiguration{}, func(obj interface{}) { + SetObjectDefaults_ControllerConfiguration(obj.(*controllerv1alpha1.ControllerConfiguration)) + }) return nil } -func SetObjectDefaults_TestType(in *TestType) { - SetDefaults_TestType(in) +func SetObjectDefaults_ControllerConfiguration(in *controllerv1alpha1.ControllerConfiguration) { + SetDefaults_ControllerConfiguration(in) + SetDefaults_LeaderElectionConfig(&in.LeaderElectionConfig) + sharedv1alpha1.SetDefaults_LeaderElectionConfig(&in.LeaderElectionConfig.LeaderElectionConfig) + sharedv1alpha1.SetDefaults_DynamicServingConfig(&in.MetricsTLSConfig.Dynamic) + SetDefaults_IngressShimConfig(&in.IngressShimConfig) + SetDefaults_ACMEHTTP01Config(&in.ACMEHTTP01Config) + SetDefaults_ACMEDNS01Config(&in.ACMEDNS01Config) } diff --git a/internal/apis/config/controller/validation/validation.go b/internal/apis/config/controller/validation/validation.go new file mode 100644 index 00000000000..64d103f29d3 --- /dev/null +++ b/internal/apis/config/controller/validation/validation.go @@ -0,0 +1,97 @@ +/* +Copyright 2021 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. +*/ + +package validation + +import ( + "net" + "net/url" + "strings" + + "k8s.io/apimachinery/pkg/util/sets" + "k8s.io/apimachinery/pkg/util/validation/field" + logsapi "k8s.io/component-base/logs/api/v1" + + config "github.com/cert-manager/cert-manager/internal/apis/config/controller" + defaults "github.com/cert-manager/cert-manager/internal/apis/config/controller/v1alpha1" + sharedvalidation "github.com/cert-manager/cert-manager/internal/apis/config/shared/validation" +) + +func ValidateControllerConfiguration(cfg *config.ControllerConfiguration, fldPath *field.Path) field.ErrorList { + var allErrors field.ErrorList + + allErrors = append(allErrors, logsapi.Validate(&cfg.Logging, nil, fldPath.Child("logging"))...) + allErrors = append(allErrors, sharedvalidation.ValidateTLSConfig(&cfg.MetricsTLSConfig, fldPath.Child("metricsTLSConfig"))...) + + if cfg.LeaderElectionConfig.Enabled && cfg.LeaderElectionConfig.HealthzTimeout <= 0 { + allErrors = append(allErrors, field.Invalid(fldPath.Child("leaderElectionConfig").Child("healthzTimeout"), cfg.LeaderElectionConfig.HealthzTimeout, "must be greater than 0")) + } + allErrors = append(allErrors, sharedvalidation.ValidateLeaderElectionConfig(&cfg.LeaderElectionConfig.LeaderElectionConfig, fldPath.Child("leaderElectionConfig"))...) + + if len(cfg.IngressShimConfig.DefaultIssuerKind) == 0 { + allErrors = append(allErrors, field.Required(fldPath.Child("ingressShimConfig").Child("defaultIssuerKind"), "must not be empty")) + } + + if cfg.KubernetesAPIBurst <= 0 { + allErrors = append(allErrors, field.Invalid(fldPath.Child("kubernetesAPIBurst"), cfg.KubernetesAPIBurst, "must be greater than 0")) + } + + if cfg.KubernetesAPIQPS <= 0 { + allErrors = append(allErrors, field.Invalid(fldPath.Child("kubernetesAPIQPS"), cfg.KubernetesAPIQPS, "must be greater than 0")) + } + + if float32(cfg.KubernetesAPIBurst) < cfg.KubernetesAPIQPS { + allErrors = append(allErrors, field.Invalid(fldPath.Child("kubernetesAPIBurst"), cfg.KubernetesAPIBurst, "must be higher or equal to kubernetesAPIQPS")) + } + + for i, server := range cfg.ACMEHTTP01Config.SolverNameservers { + // ensure all servers have a port number + _, _, err := net.SplitHostPort(server) + if err != nil { + allErrors = append(allErrors, field.Invalid(fldPath.Child("acmeHTTP01Config").Child("solverNameservers").Index(i), server, "must be in the format :")) + } + } + + for i, server := range cfg.ACMEDNS01Config.RecursiveNameservers { + // ensure all servers follow one of the following formats: + // - : + // - https:// + + if strings.HasPrefix(server, "https://") { + if u, err := url.ParseRequestURI(server); err != nil || u.Scheme != "https" || u.Host == "" { + allErrors = append(allErrors, field.Invalid(fldPath.Child("acmeDNS01Config").Child("recursiveNameservers").Index(i), server, "must be in the format https://")) + } + } else { + if _, _, err := net.SplitHostPort(server); err != nil { + allErrors = append(allErrors, field.Invalid(fldPath.Child("acmeDNS01Config").Child("recursiveNameservers").Index(i), server, "must be in the format :")) + } + } + } + + allControllersSet := sets.NewString(defaults.AllControllers...) + for i, controller := range cfg.Controllers { + if controller == "*" { + continue + } + + controller = strings.TrimPrefix(controller, "-") + if !allControllersSet.Has(controller) { + allErrors = append(allErrors, field.Invalid(fldPath.Child("controllers").Index(i), controller, "is not in the list of known controllers")) + } + } + + return allErrors +} diff --git a/internal/apis/config/controller/validation/validation_test.go b/internal/apis/config/controller/validation/validation_test.go new file mode 100644 index 00000000000..28208f9072c --- /dev/null +++ b/internal/apis/config/controller/validation/validation_test.go @@ -0,0 +1,393 @@ +/* +Copyright 2021 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. +*/ + +package validation + +import ( + "testing" + "time" + + "github.com/stretchr/testify/assert" + "k8s.io/apimachinery/pkg/util/validation/field" + logsapi "k8s.io/component-base/logs/api/v1" + + config "github.com/cert-manager/cert-manager/internal/apis/config/controller" + "github.com/cert-manager/cert-manager/internal/apis/config/shared" +) + +func TestValidateControllerConfiguration(t *testing.T) { + tests := []struct { + name string + config *config.ControllerConfiguration + errs func(*config.ControllerConfiguration) field.ErrorList + }{ + { + "with valid config", + &config.ControllerConfiguration{ + Logging: logsapi.LoggingConfiguration{ + Format: "text", + }, + IngressShimConfig: config.IngressShimConfig{ + DefaultIssuerKind: "Issuer", + }, + KubernetesAPIBurst: 1, + KubernetesAPIQPS: 1, + }, + nil, + }, + { + "with invalid logging config", + &config.ControllerConfiguration{ + Logging: logsapi.LoggingConfiguration{ + Format: "unknown", + }, + IngressShimConfig: config.IngressShimConfig{ + DefaultIssuerKind: "Issuer", + }, + KubernetesAPIBurst: 1, + KubernetesAPIQPS: 1, + }, + func(wc *config.ControllerConfiguration) field.ErrorList { + return field.ErrorList{ + field.Invalid(field.NewPath("logging.format"), wc.Logging.Format, "Unsupported log format"), + } + }, + }, + { + "with invalid leader election healthz timeout", + &config.ControllerConfiguration{ + Logging: logsapi.LoggingConfiguration{ + Format: "text", + }, + LeaderElectionConfig: config.LeaderElectionConfig{ + LeaderElectionConfig: shared.LeaderElectionConfig{ + Enabled: true, + LeaseDuration: time.Second, + RenewDeadline: time.Second, + RetryPeriod: time.Second, + }, + HealthzTimeout: 0, + }, + IngressShimConfig: config.IngressShimConfig{ + DefaultIssuerKind: "Issuer", + }, + KubernetesAPIBurst: 1, + KubernetesAPIQPS: 1, + }, + func(cc *config.ControllerConfiguration) field.ErrorList { + return field.ErrorList{ + field.Invalid(field.NewPath("leaderElectionConfig.healthzTimeout"), cc.LeaderElectionConfig.HealthzTimeout, "must be greater than 0"), + } + }, + }, + { + "with invalid leader election config", + &config.ControllerConfiguration{ + Logging: logsapi.LoggingConfiguration{ + Format: "text", + }, + LeaderElectionConfig: config.LeaderElectionConfig{ + LeaderElectionConfig: shared.LeaderElectionConfig{ + Enabled: true, + }, + }, + IngressShimConfig: config.IngressShimConfig{ + DefaultIssuerKind: "Issuer", + }, + KubernetesAPIBurst: 1, + KubernetesAPIQPS: 1, + }, + func(cc *config.ControllerConfiguration) field.ErrorList { + return field.ErrorList{ + field.Invalid(field.NewPath("leaderElectionConfig.healthzTimeout"), cc.LeaderElectionConfig.HealthzTimeout, "must be greater than 0"), + field.Invalid(field.NewPath("leaderElectionConfig.leaseDuration"), cc.LeaderElectionConfig.LeaseDuration, "must be greater than 0"), + field.Invalid(field.NewPath("leaderElectionConfig.renewDeadline"), cc.LeaderElectionConfig.RenewDeadline, "must be greater than 0"), + field.Invalid(field.NewPath("leaderElectionConfig.retryPeriod"), cc.LeaderElectionConfig.RetryPeriod, "must be greater than 0"), + } + }, + }, + { + "with invalid metrics tls config", + &config.ControllerConfiguration{ + Logging: logsapi.LoggingConfiguration{ + Format: "text", + }, + IngressShimConfig: config.IngressShimConfig{ + DefaultIssuerKind: "Issuer", + }, + KubernetesAPIBurst: 1, + KubernetesAPIQPS: 1, + MetricsTLSConfig: shared.TLSConfig{ + Filesystem: shared.FilesystemServingConfig{ + CertFile: "/test.crt", + KeyFile: "/test.key", + }, + Dynamic: shared.DynamicServingConfig{ + SecretNamespace: "cert-manager", + SecretName: "test", + DNSNames: []string{"example.com"}, + }, + }, + }, + func(cc *config.ControllerConfiguration) field.ErrorList { + return field.ErrorList{ + field.Invalid(field.NewPath("metricsTLSConfig"), &cc.MetricsTLSConfig, "cannot specify both filesystem based and dynamic TLS configuration"), + } + }, + }, + { + "with missing issuer kind", + &config.ControllerConfiguration{ + Logging: logsapi.LoggingConfiguration{ + Format: "text", + }, + KubernetesAPIBurst: 1, + KubernetesAPIQPS: 1, + }, + func(cc *config.ControllerConfiguration) field.ErrorList { + return field.ErrorList{ + field.Required(field.NewPath("ingressShimConfig.defaultIssuerKind"), "must not be empty"), + } + }, + }, + { + "with invalid kube-api-burst config", + &config.ControllerConfiguration{ + Logging: logsapi.LoggingConfiguration{ + Format: "text", + }, + IngressShimConfig: config.IngressShimConfig{ + DefaultIssuerKind: "Issuer", + }, + KubernetesAPIBurst: -1, // Must be positive + KubernetesAPIQPS: 1, + }, + func(cc *config.ControllerConfiguration) field.ErrorList { + return field.ErrorList{ + field.Invalid(field.NewPath("kubernetesAPIBurst"), cc.KubernetesAPIBurst, "must be greater than 0"), + field.Invalid(field.NewPath("kubernetesAPIBurst"), cc.KubernetesAPIBurst, "must be higher or equal to kubernetesAPIQPS"), + } + }, + }, + { + "with invalid kube-api-burst config", + &config.ControllerConfiguration{ + Logging: logsapi.LoggingConfiguration{ + Format: "text", + }, + IngressShimConfig: config.IngressShimConfig{ + DefaultIssuerKind: "Issuer", + }, + KubernetesAPIBurst: 1, // Must be greater than KubernetesAPIQPS + KubernetesAPIQPS: 2, + }, + func(cc *config.ControllerConfiguration) field.ErrorList { + return field.ErrorList{ + field.Invalid(field.NewPath("kubernetesAPIBurst"), cc.KubernetesAPIBurst, "must be higher or equal to kubernetesAPIQPS"), + } + }, + }, + { + "with invalid kube-api-qps config", + &config.ControllerConfiguration{ + Logging: logsapi.LoggingConfiguration{ + Format: "text", + }, + IngressShimConfig: config.IngressShimConfig{ + DefaultIssuerKind: "Issuer", + }, + KubernetesAPIBurst: 1, + KubernetesAPIQPS: -1, // Must be positive + }, + func(cc *config.ControllerConfiguration) field.ErrorList { + return field.ErrorList{ + field.Invalid(field.NewPath("kubernetesAPIQPS"), cc.KubernetesAPIQPS, "must be greater than 0"), + } + }, + }, + { + "with valid acme http solver nameservers", + &config.ControllerConfiguration{ + Logging: logsapi.LoggingConfiguration{ + Format: "text", + }, + IngressShimConfig: config.IngressShimConfig{ + DefaultIssuerKind: "Issuer", + }, + KubernetesAPIBurst: 1, + KubernetesAPIQPS: 1, + ACMEHTTP01Config: config.ACMEHTTP01Config{ + SolverNameservers: []string{ + "1.1.1.1:53", + "8.8.8.8:53", + }, + }, + }, + nil, + }, + { + "with invalid acme http solver nameserver missing port", + &config.ControllerConfiguration{ + Logging: logsapi.LoggingConfiguration{ + Format: "text", + }, + IngressShimConfig: config.IngressShimConfig{ + DefaultIssuerKind: "Issuer", + }, + KubernetesAPIBurst: 1, + KubernetesAPIQPS: 1, + ACMEHTTP01Config: config.ACMEHTTP01Config{ + SolverNameservers: []string{ + "1.1.1.1:53", + "8.8.8.8", + }, + }, + }, + func(cc *config.ControllerConfiguration) field.ErrorList { + return field.ErrorList{ + field.Invalid(field.NewPath("acmeHTTP01Config.solverNameservers[1]"), cc.ACMEHTTP01Config.SolverNameservers[1], "must be in the format :"), + } + }, + }, + { + "with valid acme dns recursive nameservers", + &config.ControllerConfiguration{ + Logging: logsapi.LoggingConfiguration{ + Format: "text", + }, + IngressShimConfig: config.IngressShimConfig{ + DefaultIssuerKind: "Issuer", + }, + KubernetesAPIBurst: 1, + KubernetesAPIQPS: 1, + ACMEDNS01Config: config.ACMEDNS01Config{ + RecursiveNameservers: []string{ + "1.1.1.1:53", + "https://example.com", + }, + }, + }, + nil, + }, + { + "with invalid acme dns recursive nameserver missing port", + &config.ControllerConfiguration{ + Logging: logsapi.LoggingConfiguration{ + Format: "text", + }, + IngressShimConfig: config.IngressShimConfig{ + DefaultIssuerKind: "Issuer", + }, + KubernetesAPIBurst: 1, + KubernetesAPIQPS: 1, + ACMEDNS01Config: config.ACMEDNS01Config{ + RecursiveNameservers: []string{ + "1.1.1.1", + "https://example.com", + }, + }, + }, + func(cc *config.ControllerConfiguration) field.ErrorList { + return field.ErrorList{ + field.Invalid(field.NewPath("acmeDNS01Config.recursiveNameservers[0]"), cc.ACMEDNS01Config.RecursiveNameservers[0], "must be in the format :"), + } + }, + }, + { + "with invalid acme dns recursive nameserver invalid url", + &config.ControllerConfiguration{ + Logging: logsapi.LoggingConfiguration{ + Format: "text", + }, + IngressShimConfig: config.IngressShimConfig{ + DefaultIssuerKind: "Issuer", + }, + KubernetesAPIBurst: 1, + KubernetesAPIQPS: 1, + ACMEDNS01Config: config.ACMEDNS01Config{ + RecursiveNameservers: []string{ + "1.1.1.1:53", + "https://", + }, + }, + }, + func(cc *config.ControllerConfiguration) field.ErrorList { + return field.ErrorList{ + field.Invalid(field.NewPath("acmeDNS01Config.recursiveNameservers[1]"), cc.ACMEDNS01Config.RecursiveNameservers[1], "must be in the format https://"), + } + }, + }, + { + "with valid controllers named", + &config.ControllerConfiguration{ + Logging: logsapi.LoggingConfiguration{ + Format: "text", + }, + IngressShimConfig: config.IngressShimConfig{ + DefaultIssuerKind: "Issuer", + }, + KubernetesAPIBurst: 1, + KubernetesAPIQPS: 1, + Controllers: []string{"issuers", "clusterissuers"}, + }, + nil, + }, + { + "with wildcard controllers named", + &config.ControllerConfiguration{ + Logging: logsapi.LoggingConfiguration{ + Format: "text", + }, + IngressShimConfig: config.IngressShimConfig{ + DefaultIssuerKind: "Issuer", + }, + KubernetesAPIBurst: 1, + KubernetesAPIQPS: 1, + Controllers: []string{"*"}, + }, + nil, + }, + { + "with invalid controllers named", + &config.ControllerConfiguration{ + Logging: logsapi.LoggingConfiguration{ + Format: "text", + }, + IngressShimConfig: config.IngressShimConfig{ + DefaultIssuerKind: "Issuer", + }, + KubernetesAPIBurst: 1, + KubernetesAPIQPS: 1, + Controllers: []string{"foo"}, + }, + func(cc *config.ControllerConfiguration) field.ErrorList { + return field.ErrorList{ + field.Invalid(field.NewPath("controllers").Index(0), "foo", "is not in the list of known controllers"), + } + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + errList := ValidateControllerConfiguration(tt.config, nil) + var expErrs field.ErrorList + if tt.errs != nil { + expErrs = tt.errs(tt.config) + } + assert.ElementsMatch(t, expErrs, errList) + }) + } +} diff --git a/internal/apis/config/controller/zz_generated.deepcopy.go b/internal/apis/config/controller/zz_generated.deepcopy.go new file mode 100644 index 00000000000..3e9a2441ff9 --- /dev/null +++ b/internal/apis/config/controller/zz_generated.deepcopy.go @@ -0,0 +1,159 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by deepcopy-gen. DO NOT EDIT. + +package controller + +import ( + runtime "k8s.io/apimachinery/pkg/runtime" +) + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ACMEDNS01Config) DeepCopyInto(out *ACMEDNS01Config) { + *out = *in + if in.RecursiveNameservers != nil { + in, out := &in.RecursiveNameservers, &out.RecursiveNameservers + *out = make([]string, len(*in)) + copy(*out, *in) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ACMEDNS01Config. +func (in *ACMEDNS01Config) DeepCopy() *ACMEDNS01Config { + if in == nil { + return nil + } + out := new(ACMEDNS01Config) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ACMEHTTP01Config) DeepCopyInto(out *ACMEHTTP01Config) { + *out = *in + if in.SolverNameservers != nil { + in, out := &in.SolverNameservers, &out.SolverNameservers + *out = make([]string, len(*in)) + copy(*out, *in) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ACMEHTTP01Config. +func (in *ACMEHTTP01Config) DeepCopy() *ACMEHTTP01Config { + if in == nil { + return nil + } + out := new(ACMEHTTP01Config) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ControllerConfiguration) DeepCopyInto(out *ControllerConfiguration) { + *out = *in + out.TypeMeta = in.TypeMeta + out.LeaderElectionConfig = in.LeaderElectionConfig + if in.Controllers != nil { + in, out := &in.Controllers, &out.Controllers + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.CopiedAnnotationPrefixes != nil { + in, out := &in.CopiedAnnotationPrefixes, &out.CopiedAnnotationPrefixes + *out = make([]string, len(*in)) + copy(*out, *in) + } + in.MetricsTLSConfig.DeepCopyInto(&out.MetricsTLSConfig) + in.Logging.DeepCopyInto(&out.Logging) + if in.FeatureGates != nil { + in, out := &in.FeatureGates, &out.FeatureGates + *out = make(map[string]bool, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + in.IngressShimConfig.DeepCopyInto(&out.IngressShimConfig) + in.ACMEHTTP01Config.DeepCopyInto(&out.ACMEHTTP01Config) + in.ACMEDNS01Config.DeepCopyInto(&out.ACMEDNS01Config) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ControllerConfiguration. +func (in *ControllerConfiguration) DeepCopy() *ControllerConfiguration { + if in == nil { + return nil + } + out := new(ControllerConfiguration) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ControllerConfiguration) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *IngressShimConfig) DeepCopyInto(out *IngressShimConfig) { + *out = *in + if in.DefaultAutoCertificateAnnotations != nil { + in, out := &in.DefaultAutoCertificateAnnotations, &out.DefaultAutoCertificateAnnotations + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.ExtraCertificateAnnotations != nil { + in, out := &in.ExtraCertificateAnnotations, &out.ExtraCertificateAnnotations + *out = make([]string, len(*in)) + copy(*out, *in) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new IngressShimConfig. +func (in *IngressShimConfig) DeepCopy() *IngressShimConfig { + if in == nil { + return nil + } + out := new(IngressShimConfig) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *LeaderElectionConfig) DeepCopyInto(out *LeaderElectionConfig) { + *out = *in + out.LeaderElectionConfig = in.LeaderElectionConfig + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LeaderElectionConfig. +func (in *LeaderElectionConfig) DeepCopy() *LeaderElectionConfig { + if in == nil { + return nil + } + out := new(LeaderElectionConfig) + in.DeepCopyInto(out) + return out +} diff --git a/pkg/webhook/handlers/testdata/apis/testgroup/doc.go b/internal/apis/config/shared/doc.go similarity index 77% rename from pkg/webhook/handlers/testdata/apis/testgroup/doc.go rename to internal/apis/config/shared/doc.go index 07adcdc514e..4c2afbe0660 100644 --- a/pkg/webhook/handlers/testdata/apis/testgroup/doc.go +++ b/internal/apis/config/shared/doc.go @@ -1,5 +1,5 @@ /* -Copyright 2020 The cert-manager Authors. +Copyright 2021 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. @@ -16,7 +16,5 @@ limitations under the License. // +k8s:deepcopy-gen=package,register -// +groupName=testgroup.testing.cert-manager.io -package testgroup - -const GroupName = "testgroup.testing.cert-manager.io" +// Package shared contains shared types for the cert-manager configuration API +package shared diff --git a/internal/apis/config/shared/types_leaderelection.go b/internal/apis/config/shared/types_leaderelection.go new file mode 100644 index 00000000000..d1bab2a2a50 --- /dev/null +++ b/internal/apis/config/shared/types_leaderelection.go @@ -0,0 +1,44 @@ +/* +Copyright 2021 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. +*/ + +package shared + +import "time" + +type LeaderElectionConfig struct { + // If true, cert-manager will perform leader election between instances to + // ensure no more than one instance of cert-manager operates at a time + Enabled bool + + // Namespace used to perform leader election. Only used if leader election is enabled + Namespace string + + // 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. + LeaseDuration 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. + RenewDeadline 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. + RetryPeriod time.Duration +} diff --git a/internal/apis/config/shared/types_tlsconfig.go b/internal/apis/config/shared/types_tlsconfig.go new file mode 100644 index 00000000000..d73210ce9de --- /dev/null +++ b/internal/apis/config/shared/types_tlsconfig.go @@ -0,0 +1,85 @@ +/* +Copyright 2021 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. +*/ + +package shared + +import "time" + +// TLSConfig configures how TLS certificates are sourced for serving. +// Only one of 'filesystem' or 'dynamic' may be specified. +type TLSConfig struct { + // 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. + CipherSuites []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. + MinTLSVersion string + + // 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. + Filesystem FilesystemServingConfig + + // When Dynamic serving is enabled, the controller will generate a CA used to sign + // certificates and persist it into a Kubernetes Secret resource (for other replicas of the + // controller to consume). + // It will then generate a certificate in-memory for itself using this CA to serve with. + Dynamic DynamicServingConfig +} + +func (c *TLSConfig) FilesystemConfigProvided() bool { + if c.Filesystem.KeyFile != "" || c.Filesystem.CertFile != "" { + return true + } + return false +} + +func (c *TLSConfig) DynamicConfigProvided() bool { + if c.Dynamic.SecretNamespace != "" || c.Dynamic.SecretName != "" || len(c.Dynamic.DNSNames) > 0 { + return true + } + return false +} + +// DynamicServingConfig makes the controller generate a CA and persist it into Secret resources. +// This CA will be used by all instances of the controller for signing serving certificates. +type DynamicServingConfig struct { + // Namespace of the Kubernetes Secret resource containing the TLS certificate + // used as a CA to sign dynamic serving certificates. + SecretNamespace string + + // Secret resource name containing the TLS certificate + // used as a CA to sign dynamic serving certificates. + SecretName string + + // DNSNames that must be present on serving certificates signed by the CA. + DNSNames []string + + // LeafDuration is a customizable duration on serving certificates signed by the CA. + LeafDuration time.Duration +} + +// 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. +type FilesystemServingConfig struct { + // Path to a file containing TLS certificate & chain to serve with + CertFile string + + // Path to a file containing a TLS private key to serve with + KeyFile string +} diff --git a/internal/apis/config/shared/v1alpha1/conversion.go b/internal/apis/config/shared/v1alpha1/conversion.go new file mode 100644 index 00000000000..17c767161d1 --- /dev/null +++ b/internal/apis/config/shared/v1alpha1/conversion.go @@ -0,0 +1,100 @@ +/* +Copyright 2021 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. +*/ + +package v1alpha1 + +import ( + "fmt" + "math" + "time" + + conversion "k8s.io/apimachinery/pkg/conversion" + + shared "github.com/cert-manager/cert-manager/internal/apis/config/shared" + "github.com/cert-manager/cert-manager/pkg/apis/config/shared/v1alpha1" +) + +// Convert_shared_TLSConfig_To_v1alpha1_TLSConfig is explicitly defined to avoid issues in conversion-gen +// when referencing types in other API groups. +func Convert_shared_TLSConfig_To_v1alpha1_TLSConfig(in *shared.TLSConfig, out *v1alpha1.TLSConfig, s conversion.Scope) error { + return autoConvert_shared_TLSConfig_To_v1alpha1_TLSConfig(in, out, s) +} + +// Convert_v1alpha1_TLSConfig_To_shared_TLSConfig is explicitly defined to avoid issues in conversion-gen +// when referencing types in other API groups. +func Convert_v1alpha1_TLSConfig_To_shared_TLSConfig(in *v1alpha1.TLSConfig, out *shared.TLSConfig, s conversion.Scope) error { + return autoConvert_v1alpha1_TLSConfig_To_shared_TLSConfig(in, out, s) +} + +// Convert_shared_LeaderElectionConfig_To_v1alpha1_LeaderElectionConfig is explicitly defined to avoid issues in conversion-gen +// when referencing types in other API groups. +func Convert_shared_LeaderElectionConfig_To_v1alpha1_LeaderElectionConfig(in *shared.LeaderElectionConfig, out *v1alpha1.LeaderElectionConfig, s conversion.Scope) error { + return autoConvert_shared_LeaderElectionConfig_To_v1alpha1_LeaderElectionConfig(in, out, s) +} + +// Convert_v1alpha1_LeaderElectionConfig_To_shared_LeaderElectionConfig is explicitly defined to avoid issues in conversion-gen +// when referencing types in other API groups. +func Convert_v1alpha1_LeaderElectionConfig_To_shared_LeaderElectionConfig(in *v1alpha1.LeaderElectionConfig, out *shared.LeaderElectionConfig, s conversion.Scope) error { + return autoConvert_v1alpha1_LeaderElectionConfig_To_shared_LeaderElectionConfig(in, out, s) +} + +func Convert_Pointer_float32_To_float32(in **float32, out *float32, s conversion.Scope) error { + if *in == nil { + *out = 0 + return nil + } + *out = float32(**in) + return nil +} + +func Convert_float32_To_Pointer_float32(in *float32, out **float32, s conversion.Scope) error { + temp := float32(*in) + *out = &temp + return nil +} + +func Convert_Pointer_int32_To_int(in **int32, out *int, s conversion.Scope) error { + if *in == nil { + *out = 0 + return nil + } + *out = int(**in) + return nil +} + +func Convert_int_To_Pointer_int32(in *int, out **int32, s conversion.Scope) error { + tempIn := *in + if tempIn > math.MaxInt32 || tempIn < math.MinInt32 { + return fmt.Errorf("value %d is out of range for int32 (must be between %d and %d)", tempIn, math.MinInt32, math.MaxInt32) + } + temp := int32(tempIn) + *out = &temp + return nil +} + +func Convert_Pointer_v1alpha1_Duration_To_time_Duration(in **v1alpha1.Duration, out *time.Duration, s conversion.Scope) error { + if *in == nil { + *out = 0 + return nil + } + *out = (*in).Duration.Duration + return nil +} + +func Convert_time_Duration_To_Pointer_v1alpha1_Duration(in *time.Duration, out **v1alpha1.Duration, s conversion.Scope) error { + *out = v1alpha1.DurationFromTime(*in) + return nil +} diff --git a/internal/apis/config/shared/v1alpha1/defaults.go b/internal/apis/config/shared/v1alpha1/defaults.go new file mode 100644 index 00000000000..e2939904bd1 --- /dev/null +++ b/internal/apis/config/shared/v1alpha1/defaults.go @@ -0,0 +1,61 @@ +/* +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. +*/ + +package v1alpha1 + +import ( + "time" + + "github.com/cert-manager/cert-manager/pkg/apis/config/shared/v1alpha1" +) + +var ( + defaultLeafDuration = time.Hour * 24 * 7 + + defaultLeaderElect = true + defaultLeaderElectionNamespace = "kube-system" + defaultLeaderElectionLeaseDuration = 60 * time.Second + defaultLeaderElectionRenewDeadline = 40 * time.Second + defaultLeaderElectionRetryPeriod = 15 * time.Second +) + +func SetDefaults_DynamicServingConfig(obj *v1alpha1.DynamicServingConfig) { + if obj.LeafDuration.IsZero() { + obj.LeafDuration = v1alpha1.DurationFromTime(defaultLeafDuration) + } +} + +func SetDefaults_LeaderElectionConfig(obj *v1alpha1.LeaderElectionConfig) { + if obj.Enabled == nil { + obj.Enabled = &defaultLeaderElect + } + + if obj.Namespace == "" { + obj.Namespace = defaultLeaderElectionNamespace + } + + if obj.LeaseDuration.IsZero() { + obj.LeaseDuration = v1alpha1.DurationFromTime(defaultLeaderElectionLeaseDuration) + } + + if obj.RenewDeadline.IsZero() { + obj.RenewDeadline = v1alpha1.DurationFromTime(defaultLeaderElectionRenewDeadline) + } + + if obj.RetryPeriod.IsZero() { + obj.RetryPeriod = v1alpha1.DurationFromTime(defaultLeaderElectionRetryPeriod) + } +} diff --git a/internal/apis/acme/v1beta1/doc.go b/internal/apis/config/shared/v1alpha1/doc.go similarity index 75% rename from internal/apis/acme/v1beta1/doc.go rename to internal/apis/config/shared/v1alpha1/doc.go index 8f5dec8a666..4514993b90a 100644 --- a/internal/apis/acme/v1beta1/doc.go +++ b/internal/apis/config/shared/v1alpha1/doc.go @@ -1,5 +1,5 @@ /* -Copyright 2020 The cert-manager Authors. +Copyright 2021 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. @@ -14,10 +14,9 @@ See the License for the specific language governing permissions and limitations under the License. */ -// +k8s:conversion-gen=github.com/cert-manager/cert-manager/internal/apis/acme -// +k8s:conversion-gen-external-types=github.com/cert-manager/cert-manager/internal/apis/acme/v1beta1 +// +k8s:conversion-gen=github.com/cert-manager/cert-manager/internal/apis/config/shared +// +k8s:conversion-gen-external-types=github.com/cert-manager/cert-manager/pkg/apis/config/shared/v1alpha1 // +k8s:defaulter-gen=TypeMeta -// +k8s:deepcopy-gen=package,register +// +k8s:defaulter-gen-input=github.com/cert-manager/cert-manager/pkg/apis/config/shared/v1alpha1 -// +groupName=acme.cert-manager.io -package v1beta1 +package v1alpha1 diff --git a/internal/apis/acme/v1beta1/defaults.go b/internal/apis/config/shared/v1alpha1/register.go similarity index 74% rename from internal/apis/acme/v1beta1/defaults.go rename to internal/apis/config/shared/v1alpha1/register.go index 7f5a9bfc623..5b6d0669f08 100644 --- a/internal/apis/acme/v1beta1/defaults.go +++ b/internal/apis/config/shared/v1alpha1/register.go @@ -1,5 +1,5 @@ /* -Copyright 2020 The cert-manager Authors. +Copyright 2021 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. @@ -14,12 +14,12 @@ See the License for the specific language governing permissions and limitations under the License. */ -package v1beta1 +package v1alpha1 import ( - "k8s.io/apimachinery/pkg/runtime" + "github.com/cert-manager/cert-manager/pkg/apis/config/controller/v1alpha1" ) -func addDefaultingFuncs(scheme *runtime.Scheme) error { - return RegisterDefaults(scheme) -} +var ( + localSchemeBuilder = &v1alpha1.SchemeBuilder +) diff --git a/internal/apis/config/shared/v1alpha1/zz_generated.conversion.go b/internal/apis/config/shared/v1alpha1/zz_generated.conversion.go new file mode 100644 index 00000000000..d0dbd137168 --- /dev/null +++ b/internal/apis/config/shared/v1alpha1/zz_generated.conversion.go @@ -0,0 +1,223 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by conversion-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + time "time" + unsafe "unsafe" + + shared "github.com/cert-manager/cert-manager/internal/apis/config/shared" + sharedv1alpha1 "github.com/cert-manager/cert-manager/pkg/apis/config/shared/v1alpha1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + conversion "k8s.io/apimachinery/pkg/conversion" + runtime "k8s.io/apimachinery/pkg/runtime" +) + +func init() { + localSchemeBuilder.Register(RegisterConversions) +} + +// RegisterConversions adds conversion functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterConversions(s *runtime.Scheme) error { + if err := s.AddGeneratedConversionFunc((*sharedv1alpha1.DynamicServingConfig)(nil), (*shared.DynamicServingConfig)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1alpha1_DynamicServingConfig_To_shared_DynamicServingConfig(a.(*sharedv1alpha1.DynamicServingConfig), b.(*shared.DynamicServingConfig), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*shared.DynamicServingConfig)(nil), (*sharedv1alpha1.DynamicServingConfig)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_shared_DynamicServingConfig_To_v1alpha1_DynamicServingConfig(a.(*shared.DynamicServingConfig), b.(*sharedv1alpha1.DynamicServingConfig), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*sharedv1alpha1.FilesystemServingConfig)(nil), (*shared.FilesystemServingConfig)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1alpha1_FilesystemServingConfig_To_shared_FilesystemServingConfig(a.(*sharedv1alpha1.FilesystemServingConfig), b.(*shared.FilesystemServingConfig), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*shared.FilesystemServingConfig)(nil), (*sharedv1alpha1.FilesystemServingConfig)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_shared_FilesystemServingConfig_To_v1alpha1_FilesystemServingConfig(a.(*shared.FilesystemServingConfig), b.(*sharedv1alpha1.FilesystemServingConfig), scope) + }); err != nil { + return err + } + if err := s.AddConversionFunc((**float32)(nil), (*float32)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_Pointer_float32_To_float32(a.(**float32), b.(*float32), scope) + }); err != nil { + return err + } + if err := s.AddConversionFunc((**int32)(nil), (*int)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_Pointer_int32_To_int(a.(**int32), b.(*int), scope) + }); err != nil { + return err + } + if err := s.AddConversionFunc((**sharedv1alpha1.Duration)(nil), (*time.Duration)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_Pointer_v1alpha1_Duration_To_time_Duration(a.(**sharedv1alpha1.Duration), b.(*time.Duration), scope) + }); err != nil { + return err + } + if err := s.AddConversionFunc((*float32)(nil), (**float32)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_float32_To_Pointer_float32(a.(*float32), b.(**float32), scope) + }); err != nil { + return err + } + if err := s.AddConversionFunc((*int)(nil), (**int32)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_int_To_Pointer_int32(a.(*int), b.(**int32), scope) + }); err != nil { + return err + } + if err := s.AddConversionFunc((*shared.LeaderElectionConfig)(nil), (*sharedv1alpha1.LeaderElectionConfig)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_shared_LeaderElectionConfig_To_v1alpha1_LeaderElectionConfig(a.(*shared.LeaderElectionConfig), b.(*sharedv1alpha1.LeaderElectionConfig), scope) + }); err != nil { + return err + } + if err := s.AddConversionFunc((*shared.TLSConfig)(nil), (*sharedv1alpha1.TLSConfig)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_shared_TLSConfig_To_v1alpha1_TLSConfig(a.(*shared.TLSConfig), b.(*sharedv1alpha1.TLSConfig), scope) + }); err != nil { + return err + } + if err := s.AddConversionFunc((*time.Duration)(nil), (**sharedv1alpha1.Duration)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_time_Duration_To_Pointer_v1alpha1_Duration(a.(*time.Duration), b.(**sharedv1alpha1.Duration), scope) + }); err != nil { + return err + } + if err := s.AddConversionFunc((*sharedv1alpha1.LeaderElectionConfig)(nil), (*shared.LeaderElectionConfig)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1alpha1_LeaderElectionConfig_To_shared_LeaderElectionConfig(a.(*sharedv1alpha1.LeaderElectionConfig), b.(*shared.LeaderElectionConfig), scope) + }); err != nil { + return err + } + if err := s.AddConversionFunc((*sharedv1alpha1.TLSConfig)(nil), (*shared.TLSConfig)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1alpha1_TLSConfig_To_shared_TLSConfig(a.(*sharedv1alpha1.TLSConfig), b.(*shared.TLSConfig), scope) + }); err != nil { + return err + } + return nil +} + +func autoConvert_v1alpha1_DynamicServingConfig_To_shared_DynamicServingConfig(in *sharedv1alpha1.DynamicServingConfig, out *shared.DynamicServingConfig, s conversion.Scope) error { + out.SecretNamespace = in.SecretNamespace + out.SecretName = in.SecretName + out.DNSNames = *(*[]string)(unsafe.Pointer(&in.DNSNames)) + if err := Convert_Pointer_v1alpha1_Duration_To_time_Duration(&in.LeafDuration, &out.LeafDuration, s); err != nil { + return err + } + return nil +} + +// Convert_v1alpha1_DynamicServingConfig_To_shared_DynamicServingConfig is an autogenerated conversion function. +func Convert_v1alpha1_DynamicServingConfig_To_shared_DynamicServingConfig(in *sharedv1alpha1.DynamicServingConfig, out *shared.DynamicServingConfig, s conversion.Scope) error { + return autoConvert_v1alpha1_DynamicServingConfig_To_shared_DynamicServingConfig(in, out, s) +} + +func autoConvert_shared_DynamicServingConfig_To_v1alpha1_DynamicServingConfig(in *shared.DynamicServingConfig, out *sharedv1alpha1.DynamicServingConfig, s conversion.Scope) error { + out.SecretNamespace = in.SecretNamespace + out.SecretName = in.SecretName + out.DNSNames = *(*[]string)(unsafe.Pointer(&in.DNSNames)) + if err := Convert_time_Duration_To_Pointer_v1alpha1_Duration(&in.LeafDuration, &out.LeafDuration, s); err != nil { + return err + } + return nil +} + +// Convert_shared_DynamicServingConfig_To_v1alpha1_DynamicServingConfig is an autogenerated conversion function. +func Convert_shared_DynamicServingConfig_To_v1alpha1_DynamicServingConfig(in *shared.DynamicServingConfig, out *sharedv1alpha1.DynamicServingConfig, s conversion.Scope) error { + return autoConvert_shared_DynamicServingConfig_To_v1alpha1_DynamicServingConfig(in, out, s) +} + +func autoConvert_v1alpha1_FilesystemServingConfig_To_shared_FilesystemServingConfig(in *sharedv1alpha1.FilesystemServingConfig, out *shared.FilesystemServingConfig, s conversion.Scope) error { + out.CertFile = in.CertFile + out.KeyFile = in.KeyFile + return nil +} + +// Convert_v1alpha1_FilesystemServingConfig_To_shared_FilesystemServingConfig is an autogenerated conversion function. +func Convert_v1alpha1_FilesystemServingConfig_To_shared_FilesystemServingConfig(in *sharedv1alpha1.FilesystemServingConfig, out *shared.FilesystemServingConfig, s conversion.Scope) error { + return autoConvert_v1alpha1_FilesystemServingConfig_To_shared_FilesystemServingConfig(in, out, s) +} + +func autoConvert_shared_FilesystemServingConfig_To_v1alpha1_FilesystemServingConfig(in *shared.FilesystemServingConfig, out *sharedv1alpha1.FilesystemServingConfig, s conversion.Scope) error { + out.CertFile = in.CertFile + out.KeyFile = in.KeyFile + return nil +} + +// Convert_shared_FilesystemServingConfig_To_v1alpha1_FilesystemServingConfig is an autogenerated conversion function. +func Convert_shared_FilesystemServingConfig_To_v1alpha1_FilesystemServingConfig(in *shared.FilesystemServingConfig, out *sharedv1alpha1.FilesystemServingConfig, s conversion.Scope) error { + return autoConvert_shared_FilesystemServingConfig_To_v1alpha1_FilesystemServingConfig(in, out, s) +} + +func autoConvert_v1alpha1_LeaderElectionConfig_To_shared_LeaderElectionConfig(in *sharedv1alpha1.LeaderElectionConfig, out *shared.LeaderElectionConfig, s conversion.Scope) error { + if err := v1.Convert_Pointer_bool_To_bool(&in.Enabled, &out.Enabled, s); err != nil { + return err + } + out.Namespace = in.Namespace + if err := Convert_Pointer_v1alpha1_Duration_To_time_Duration(&in.LeaseDuration, &out.LeaseDuration, s); err != nil { + return err + } + if err := Convert_Pointer_v1alpha1_Duration_To_time_Duration(&in.RenewDeadline, &out.RenewDeadline, s); err != nil { + return err + } + if err := Convert_Pointer_v1alpha1_Duration_To_time_Duration(&in.RetryPeriod, &out.RetryPeriod, s); err != nil { + return err + } + return nil +} + +func autoConvert_shared_LeaderElectionConfig_To_v1alpha1_LeaderElectionConfig(in *shared.LeaderElectionConfig, out *sharedv1alpha1.LeaderElectionConfig, s conversion.Scope) error { + if err := v1.Convert_bool_To_Pointer_bool(&in.Enabled, &out.Enabled, s); err != nil { + return err + } + out.Namespace = in.Namespace + if err := Convert_time_Duration_To_Pointer_v1alpha1_Duration(&in.LeaseDuration, &out.LeaseDuration, s); err != nil { + return err + } + if err := Convert_time_Duration_To_Pointer_v1alpha1_Duration(&in.RenewDeadline, &out.RenewDeadline, s); err != nil { + return err + } + if err := Convert_time_Duration_To_Pointer_v1alpha1_Duration(&in.RetryPeriod, &out.RetryPeriod, s); err != nil { + return err + } + return nil +} + +func autoConvert_v1alpha1_TLSConfig_To_shared_TLSConfig(in *sharedv1alpha1.TLSConfig, out *shared.TLSConfig, s conversion.Scope) error { + out.CipherSuites = *(*[]string)(unsafe.Pointer(&in.CipherSuites)) + out.MinTLSVersion = in.MinTLSVersion + if err := Convert_v1alpha1_FilesystemServingConfig_To_shared_FilesystemServingConfig(&in.Filesystem, &out.Filesystem, s); err != nil { + return err + } + if err := Convert_v1alpha1_DynamicServingConfig_To_shared_DynamicServingConfig(&in.Dynamic, &out.Dynamic, s); err != nil { + return err + } + return nil +} + +func autoConvert_shared_TLSConfig_To_v1alpha1_TLSConfig(in *shared.TLSConfig, out *sharedv1alpha1.TLSConfig, s conversion.Scope) error { + out.CipherSuites = *(*[]string)(unsafe.Pointer(&in.CipherSuites)) + out.MinTLSVersion = in.MinTLSVersion + if err := Convert_shared_FilesystemServingConfig_To_v1alpha1_FilesystemServingConfig(&in.Filesystem, &out.Filesystem, s); err != nil { + return err + } + if err := Convert_shared_DynamicServingConfig_To_v1alpha1_DynamicServingConfig(&in.Dynamic, &out.Dynamic, s); err != nil { + return err + } + return nil +} diff --git a/internal/apis/acme/v1alpha3/zz_generated.defaults.go b/internal/apis/config/shared/v1alpha1/zz_generated.defaults.go similarity index 98% rename from internal/apis/acme/v1alpha3/zz_generated.defaults.go rename to internal/apis/config/shared/v1alpha1/zz_generated.defaults.go index 17fd22729d1..48c7e75b495 100644 --- a/internal/apis/acme/v1alpha3/zz_generated.defaults.go +++ b/internal/apis/config/shared/v1alpha1/zz_generated.defaults.go @@ -19,7 +19,7 @@ limitations under the License. // Code generated by defaulter-gen. DO NOT EDIT. -package v1alpha3 +package v1alpha1 import ( runtime "k8s.io/apimachinery/pkg/runtime" diff --git a/internal/apis/config/shared/validation/validation.go b/internal/apis/config/shared/validation/validation.go new file mode 100644 index 00000000000..a38b716de50 --- /dev/null +++ b/internal/apis/config/shared/validation/validation.go @@ -0,0 +1,74 @@ +/* +Copyright 2021 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. +*/ + +package validation + +import ( + "k8s.io/apimachinery/pkg/util/validation/field" + + shared "github.com/cert-manager/cert-manager/internal/apis/config/shared" +) + +func ValidateTLSConfig(tlsConfig *shared.TLSConfig, fldPath *field.Path) field.ErrorList { + var allErrors field.ErrorList + + if tlsConfig.FilesystemConfigProvided() && tlsConfig.DynamicConfigProvided() { + allErrors = append(allErrors, field.Invalid(fldPath, tlsConfig, "cannot specify both filesystem based and dynamic TLS configuration")) + } else { + if tlsConfig.FilesystemConfigProvided() { + fileSystemPath := fldPath.Child("filesystem") + if tlsConfig.Filesystem.KeyFile == "" { + allErrors = append(allErrors, field.Required(fileSystemPath.Child("keyFile"), "must be specified when using filesystem based TLS config")) + } + if tlsConfig.Filesystem.CertFile == "" { + allErrors = append(allErrors, field.Required(fileSystemPath.Child("certFile"), "must be specified when using filesystem based TLS config")) + } + } else if tlsConfig.DynamicConfigProvided() { + dynamicPath := fldPath.Child("dynamic") + if tlsConfig.Dynamic.SecretNamespace == "" { + allErrors = append(allErrors, field.Required(dynamicPath.Child("secretNamespace"), "must be specified when using dynamic TLS config")) + } + if tlsConfig.Dynamic.SecretName == "" { + allErrors = append(allErrors, field.Required(dynamicPath.Child("secretName"), "must be specified when using dynamic TLS config")) + } + if len(tlsConfig.Dynamic.DNSNames) == 0 { + allErrors = append(allErrors, field.Required(dynamicPath.Child("dnsNames"), "must be specified when using dynamic TLS config")) + } + } + } + + return allErrors +} + +func ValidateLeaderElectionConfig(leaderElectionConfig *shared.LeaderElectionConfig, fldPath *field.Path) field.ErrorList { + var allErrors field.ErrorList + + if !leaderElectionConfig.Enabled { + return allErrors + } + + if leaderElectionConfig.LeaseDuration <= 0 { + allErrors = append(allErrors, field.Invalid(fldPath.Child("leaseDuration"), leaderElectionConfig.LeaseDuration, "must be greater than 0")) + } + if leaderElectionConfig.RenewDeadline <= 0 { + allErrors = append(allErrors, field.Invalid(fldPath.Child("renewDeadline"), leaderElectionConfig.RenewDeadline, "must be greater than 0")) + } + if leaderElectionConfig.RetryPeriod <= 0 { + allErrors = append(allErrors, field.Invalid(fldPath.Child("retryPeriod"), leaderElectionConfig.RetryPeriod, "must be greater than 0")) + } + + return allErrors +} diff --git a/internal/apis/config/shared/validation/validation_test.go b/internal/apis/config/shared/validation/validation_test.go new file mode 100644 index 00000000000..e99adccb254 --- /dev/null +++ b/internal/apis/config/shared/validation/validation_test.go @@ -0,0 +1,196 @@ +/* +Copyright 2021 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. +*/ + +package validation + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "k8s.io/apimachinery/pkg/util/validation/field" + + "github.com/cert-manager/cert-manager/internal/apis/config/shared" +) + +func TestValidateTLSConfig(t *testing.T) { + tests := []struct { + name string + config *shared.TLSConfig + errs func(*shared.TLSConfig) field.ErrorList + }{ + { + "with valid config", + &shared.TLSConfig{}, + nil, + }, + { + "with both filesystem and dynamic tls configured", + &shared.TLSConfig{ + Filesystem: shared.FilesystemServingConfig{ + CertFile: "/test.crt", + KeyFile: "/test.key", + }, + Dynamic: shared.DynamicServingConfig{ + SecretNamespace: "cert-manager", + SecretName: "test", + DNSNames: []string{"example.com"}, + }, + }, + func(cc *shared.TLSConfig) field.ErrorList { + return field.ErrorList{ + field.Invalid(nil, cc, "cannot specify both filesystem based and dynamic TLS configuration"), + } + }, + }, + { + "with valid filesystem tls config", + &shared.TLSConfig{ + Filesystem: shared.FilesystemServingConfig{ + CertFile: "/test.crt", + KeyFile: "/test.key", + }, + }, + nil, + }, + { + "with valid tls config missing keyfile", + &shared.TLSConfig{ + Filesystem: shared.FilesystemServingConfig{ + CertFile: "/test.crt", + }, + }, + func(cc *shared.TLSConfig) field.ErrorList { + return field.ErrorList{ + field.Required(field.NewPath("filesystem.keyFile"), "must be specified when using filesystem based TLS config"), + } + }, + }, + { + "with valid tls config missing certfile", + &shared.TLSConfig{ + Filesystem: shared.FilesystemServingConfig{ + KeyFile: "/test.key", + }, + }, + func(cc *shared.TLSConfig) field.ErrorList { + return field.ErrorList{ + field.Required(field.NewPath("filesystem.certFile"), "must be specified when using filesystem based TLS config"), + } + }, + }, + { + "with valid dynamic tls config", + &shared.TLSConfig{ + Dynamic: shared.DynamicServingConfig{ + SecretNamespace: "cert-manager", + SecretName: "test", + DNSNames: []string{"example.com"}, + }, + }, + nil, + }, + { + "with dynamic tls missing secret namespace", + &shared.TLSConfig{ + Dynamic: shared.DynamicServingConfig{ + SecretName: "test", + DNSNames: []string{"example.com"}, + }, + }, + func(cc *shared.TLSConfig) field.ErrorList { + return field.ErrorList{ + field.Required(field.NewPath("dynamic.secretNamespace"), "must be specified when using dynamic TLS config"), + } + }, + }, + { + "with dynamic tls missing secret name", + &shared.TLSConfig{ + Dynamic: shared.DynamicServingConfig{ + SecretNamespace: "cert-manager", + DNSNames: []string{"example.com"}, + }, + }, + func(cc *shared.TLSConfig) field.ErrorList { + return field.ErrorList{ + field.Required(field.NewPath("dynamic.secretName"), "must be specified when using dynamic TLS config"), + } + }, + }, + { + "with dynamic tls missing dns names", + &shared.TLSConfig{ + Dynamic: shared.DynamicServingConfig{ + SecretName: "test", + SecretNamespace: "cert-manager", + DNSNames: nil, + }, + }, + func(cc *shared.TLSConfig) field.ErrorList { + return field.ErrorList{ + field.Required(field.NewPath("dynamic.dnsNames"), "must be specified when using dynamic TLS config"), + } + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + errList := ValidateTLSConfig(tt.config, nil) + var expErrs field.ErrorList + if tt.errs != nil { + expErrs = tt.errs(tt.config) + } + assert.ElementsMatch(t, expErrs, errList) + }) + } +} + +func TestValidateLeaderElectionConfig(t *testing.T) { + tests := []struct { + name string + config *shared.LeaderElectionConfig + errs func(*shared.LeaderElectionConfig) field.ErrorList + }{ + { + "with valid config", + &shared.LeaderElectionConfig{}, + nil, + }, + { + "with leader election enabled but missing durations", + &shared.LeaderElectionConfig{ + Enabled: true, + }, + func(cc *shared.LeaderElectionConfig) field.ErrorList { + return field.ErrorList{ + field.Invalid(field.NewPath("leaseDuration"), cc.LeaseDuration, "must be greater than 0"), + field.Invalid(field.NewPath("renewDeadline"), cc.RenewDeadline, "must be greater than 0"), + field.Invalid(field.NewPath("retryPeriod"), cc.RetryPeriod, "must be greater than 0"), + } + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + errList := ValidateLeaderElectionConfig(tt.config, nil) + var expErrs field.ErrorList + if tt.errs != nil { + expErrs = tt.errs(tt.config) + } + assert.ElementsMatch(t, expErrs, errList) + }) + } +} diff --git a/internal/apis/config/shared/zz_generated.deepcopy.go b/internal/apis/config/shared/zz_generated.deepcopy.go new file mode 100644 index 00000000000..428716cdcf0 --- /dev/null +++ b/internal/apis/config/shared/zz_generated.deepcopy.go @@ -0,0 +1,98 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by deepcopy-gen. DO NOT EDIT. + +package shared + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *DynamicServingConfig) DeepCopyInto(out *DynamicServingConfig) { + *out = *in + if in.DNSNames != nil { + in, out := &in.DNSNames, &out.DNSNames + *out = make([]string, len(*in)) + copy(*out, *in) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DynamicServingConfig. +func (in *DynamicServingConfig) DeepCopy() *DynamicServingConfig { + if in == nil { + return nil + } + out := new(DynamicServingConfig) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *FilesystemServingConfig) DeepCopyInto(out *FilesystemServingConfig) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FilesystemServingConfig. +func (in *FilesystemServingConfig) DeepCopy() *FilesystemServingConfig { + if in == nil { + return nil + } + out := new(FilesystemServingConfig) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *LeaderElectionConfig) DeepCopyInto(out *LeaderElectionConfig) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LeaderElectionConfig. +func (in *LeaderElectionConfig) DeepCopy() *LeaderElectionConfig { + if in == nil { + return nil + } + out := new(LeaderElectionConfig) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TLSConfig) DeepCopyInto(out *TLSConfig) { + *out = *in + if in.CipherSuites != nil { + in, out := &in.CipherSuites, &out.CipherSuites + *out = make([]string, len(*in)) + copy(*out, *in) + } + out.Filesystem = in.Filesystem + in.Dynamic.DeepCopyInto(&out.Dynamic) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TLSConfig. +func (in *TLSConfig) DeepCopy() *TLSConfig { + if in == nil { + return nil + } + out := new(TLSConfig) + in.DeepCopyInto(out) + return out +} diff --git a/internal/apis/config/webhook/fuzzer/fuzzer.go b/internal/apis/config/webhook/fuzzer/fuzzer.go index f01a704e83f..c200fc788d8 100644 --- a/internal/apis/config/webhook/fuzzer/fuzzer.go +++ b/internal/apis/config/webhook/fuzzer/fuzzer.go @@ -17,9 +17,9 @@ limitations under the License. package fuzzer import ( - fuzz "github.com/google/gofuzz" runtimeserializer "k8s.io/apimachinery/pkg/runtime/serializer" - "k8s.io/utils/pointer" + logsapi "k8s.io/component-base/logs/api/v1" + "sigs.k8s.io/randfill" "github.com/cert-manager/cert-manager/internal/apis/config/webhook" ) @@ -27,18 +27,17 @@ import ( // Funcs returns the fuzzer functions for the webhook config api group. var Funcs = func(codecs runtimeserializer.CodecFactory) []interface{} { return []interface{}{ - func(s *webhook.WebhookConfiguration, c fuzz.Continue) { - c.FuzzNoCustom(s) // fuzz self without calling this function again + func(s *webhook.WebhookConfiguration, c randfill.Continue) { + c.FillNoCustom(s) // fuzz self without calling this function again - if s.HealthzPort == nil { - s.HealthzPort = pointer.Int(12) - } - if s.SecurePort == nil { - s.SecurePort = pointer.Int(123) - } if s.PprofAddress == "" { s.PprofAddress = "something:1234" } + if s.MetricsListenAddress == "" { + s.MetricsListenAddress = "something:1234" + } + + logsapi.SetRecommendedLoggingConfiguration(&s.Logging) }, } } diff --git a/internal/apis/config/webhook/types.go b/internal/apis/config/webhook/types.go index 0402eb4da4a..50c72bd50e8 100644 --- a/internal/apis/config/webhook/types.go +++ b/internal/apis/config/webhook/types.go @@ -18,6 +18,9 @@ package webhook import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + logsapi "k8s.io/component-base/logs/api/v1" + + shared "github.com/cert-manager/cert-manager/internal/apis/config/shared" ) // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object @@ -26,15 +29,17 @@ type WebhookConfiguration struct { metav1.TypeMeta // 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. - SecurePort *int + SecurePort 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. - HealthzPort *int + HealthzPort int32 // tlsConfig is used to configure the secure listener's TLS settings. - TLSConfig TLSConfig + TLSConfig shared.TLSConfig // 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. @@ -51,74 +56,18 @@ type WebhookConfiguration struct { // Defaults to 'localhost:6060'. PprofAddress string + // https://pkg.go.dev/k8s.io/component-base@v0.27.3/logs/api/v1#LoggingConfiguration + Logging logsapi.LoggingConfiguration + // featureGates is a map of feature names to bools that enable or disable experimental // features. - // Default: nil - // +optional FeatureGates map[string]bool -} - -// TLSConfig configures how TLS certificates are sourced for serving. -// Only one of 'filesystem' or 'dynamic' may be specified. -type TLSConfig struct { - // 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. - CipherSuites []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. - MinTLSVersion string - - // 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. - Filesystem FilesystemServingConfig - - // 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). - Dynamic DynamicServingConfig -} - -func (c *TLSConfig) FilesystemConfigProvided() bool { - if c.Filesystem.KeyFile != "" || c.Filesystem.CertFile != "" { - return true - } - return false -} - -func (c *TLSConfig) DynamicConfigProvided() bool { - if c.Dynamic.SecretNamespace != "" || c.Dynamic.SecretName != "" || len(c.Dynamic.DNSNames) > 0 { - return true - } - return false -} - -// 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. -type DynamicServingConfig struct { - // Namespace of the Kubernetes Secret resource containing the TLS certificate - // used as a CA to sign dynamic serving certificates. - SecretNamespace string - - // Namespace of the Kubernetes Secret resource containing the TLS certificate - // used as a CA to sign dynamic serving certificates. - SecretName string - - // DNSNames that must be present on serving certificates signed by the CA. - DNSNames []string -} -// 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. -type FilesystemServingConfig struct { - // Path to a file containing TLS certificate & chain to serve with - CertFile string + // The host and port that the metrics endpoint should listen on. + // The value "0" disables the metrics server. + // Defaults to '0.0.0.0:9402'. + MetricsListenAddress string - // Path to a file containing a TLS private key to server with - KeyFile string + // Metrics endpoint TLS config + MetricsTLSConfig shared.TLSConfig } diff --git a/internal/apis/config/webhook/v1alpha1/defaults.go b/internal/apis/config/webhook/v1alpha1/defaults.go index cc5373d031e..10f87ca762e 100644 --- a/internal/apis/config/webhook/v1alpha1/defaults.go +++ b/internal/apis/config/webhook/v1alpha1/defaults.go @@ -18,23 +18,32 @@ package v1alpha1 import ( "k8s.io/apimachinery/pkg/runtime" - "k8s.io/utils/pointer" + logsapi "k8s.io/component-base/logs/api/v1" + "k8s.io/utils/ptr" "github.com/cert-manager/cert-manager/pkg/apis/config/webhook/v1alpha1" ) +const defaultPrometheusMetricsServerAddress = "0.0.0.0:9402" + func addDefaultingFuncs(scheme *runtime.Scheme) error { return RegisterDefaults(scheme) } func SetDefaults_WebhookConfiguration(obj *v1alpha1.WebhookConfiguration) { if obj.SecurePort == nil { - obj.SecurePort = pointer.Int(6443) + obj.SecurePort = ptr.To(int32(6443)) } if obj.HealthzPort == nil { - obj.HealthzPort = pointer.Int(6080) + obj.HealthzPort = ptr.To(int32(6080)) } if obj.PprofAddress == "" { obj.PprofAddress = "localhost:6060" } + + if obj.MetricsListenAddress == "" { + obj.MetricsListenAddress = defaultPrometheusMetricsServerAddress + } + + logsapi.SetRecommendedLoggingConfiguration(&obj.Logging) } diff --git a/internal/apis/config/webhook/v1alpha1/defaults_test.go b/internal/apis/config/webhook/v1alpha1/defaults_test.go new file mode 100644 index 00000000000..e23764180b2 --- /dev/null +++ b/internal/apis/config/webhook/v1alpha1/defaults_test.go @@ -0,0 +1,65 @@ +/* +Copyright 2021 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. +*/ + +package v1alpha1 + +import ( + "encoding/json" + "os" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/cert-manager/cert-manager/pkg/apis/config/webhook/v1alpha1" +) + +func TestWebhookConfigurationDefaults(t *testing.T) { + tests := []struct { + name string + config *v1alpha1.WebhookConfiguration + jsonFilePath string + }{ + { + "v1alpha1", + &v1alpha1.WebhookConfiguration{}, + "testdata/defaults.json", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + SetObjectDefaults_WebhookConfiguration(tt.config) + + defaultData, err := json.MarshalIndent(tt.config, "", "\t") + if err != nil { + t.Fatal(err) + } + + if os.Getenv("UPDATE_DEFAULTS") == "true" { + if err := os.WriteFile(tt.jsonFilePath, defaultData, 0644); err != nil { + t.Fatal(err) + } + t.Log("webhook config api defaults updated") + } + + expectedData, err := os.ReadFile(tt.jsonFilePath) + if err != nil { + t.Fatal(err) + } + + require.Equal(t, expectedData, defaultData) + }) + } +} diff --git a/internal/apis/config/webhook/v1alpha1/testdata/defaults.json b/internal/apis/config/webhook/v1alpha1/testdata/defaults.json new file mode 100644 index 00000000000..decd2094391 --- /dev/null +++ b/internal/apis/config/webhook/v1alpha1/testdata/defaults.json @@ -0,0 +1,32 @@ +{ + "securePort": 6443, + "healthzPort": 6080, + "tlsConfig": { + "filesystem": {}, + "dynamic": { + "leafDuration": "168h0m0s" + } + }, + "enablePprof": false, + "pprofAddress": "localhost:6060", + "logging": { + "format": "text", + "flushFrequency": "5s", + "verbosity": 0, + "options": { + "text": { + "infoBufferSize": "0" + }, + "json": { + "infoBufferSize": "0" + } + } + }, + "metricsListenAddress": "0.0.0.0:9402", + "metricsTLSConfig": { + "filesystem": {}, + "dynamic": { + "leafDuration": "168h0m0s" + } + } +} \ No newline at end of file diff --git a/internal/apis/config/webhook/v1alpha1/zz_generated.conversion.go b/internal/apis/config/webhook/v1alpha1/zz_generated.conversion.go index a71a79ebe29..1554b121f34 100644 --- a/internal/apis/config/webhook/v1alpha1/zz_generated.conversion.go +++ b/internal/apis/config/webhook/v1alpha1/zz_generated.conversion.go @@ -24,8 +24,10 @@ package v1alpha1 import ( unsafe "unsafe" + sharedv1alpha1 "github.com/cert-manager/cert-manager/internal/apis/config/shared/v1alpha1" webhook "github.com/cert-manager/cert-manager/internal/apis/config/webhook" - v1alpha1 "github.com/cert-manager/cert-manager/pkg/apis/config/webhook/v1alpha1" + webhookv1alpha1 "github.com/cert-manager/cert-manager/pkg/apis/config/webhook/v1alpha1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" conversion "k8s.io/apimachinery/pkg/conversion" runtime "k8s.io/apimachinery/pkg/runtime" ) @@ -37,163 +39,71 @@ func init() { // RegisterConversions adds conversion functions to the given scheme. // Public to allow building arbitrary schemes. func RegisterConversions(s *runtime.Scheme) error { - if err := s.AddGeneratedConversionFunc((*v1alpha1.DynamicServingConfig)(nil), (*webhook.DynamicServingConfig)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1alpha1_DynamicServingConfig_To_webhook_DynamicServingConfig(a.(*v1alpha1.DynamicServingConfig), b.(*webhook.DynamicServingConfig), scope) + if err := s.AddGeneratedConversionFunc((*webhookv1alpha1.WebhookConfiguration)(nil), (*webhook.WebhookConfiguration)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1alpha1_WebhookConfiguration_To_webhook_WebhookConfiguration(a.(*webhookv1alpha1.WebhookConfiguration), b.(*webhook.WebhookConfiguration), scope) }); err != nil { return err } - if err := s.AddGeneratedConversionFunc((*webhook.DynamicServingConfig)(nil), (*v1alpha1.DynamicServingConfig)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_webhook_DynamicServingConfig_To_v1alpha1_DynamicServingConfig(a.(*webhook.DynamicServingConfig), b.(*v1alpha1.DynamicServingConfig), scope) + if err := s.AddGeneratedConversionFunc((*webhook.WebhookConfiguration)(nil), (*webhookv1alpha1.WebhookConfiguration)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_webhook_WebhookConfiguration_To_v1alpha1_WebhookConfiguration(a.(*webhook.WebhookConfiguration), b.(*webhookv1alpha1.WebhookConfiguration), scope) }); err != nil { return err } - if err := s.AddGeneratedConversionFunc((*v1alpha1.FilesystemServingConfig)(nil), (*webhook.FilesystemServingConfig)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1alpha1_FilesystemServingConfig_To_webhook_FilesystemServingConfig(a.(*v1alpha1.FilesystemServingConfig), b.(*webhook.FilesystemServingConfig), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*webhook.FilesystemServingConfig)(nil), (*v1alpha1.FilesystemServingConfig)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_webhook_FilesystemServingConfig_To_v1alpha1_FilesystemServingConfig(a.(*webhook.FilesystemServingConfig), b.(*v1alpha1.FilesystemServingConfig), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*v1alpha1.TLSConfig)(nil), (*webhook.TLSConfig)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1alpha1_TLSConfig_To_webhook_TLSConfig(a.(*v1alpha1.TLSConfig), b.(*webhook.TLSConfig), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*webhook.TLSConfig)(nil), (*v1alpha1.TLSConfig)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_webhook_TLSConfig_To_v1alpha1_TLSConfig(a.(*webhook.TLSConfig), b.(*v1alpha1.TLSConfig), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*v1alpha1.WebhookConfiguration)(nil), (*webhook.WebhookConfiguration)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1alpha1_WebhookConfiguration_To_webhook_WebhookConfiguration(a.(*v1alpha1.WebhookConfiguration), b.(*webhook.WebhookConfiguration), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*webhook.WebhookConfiguration)(nil), (*v1alpha1.WebhookConfiguration)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_webhook_WebhookConfiguration_To_v1alpha1_WebhookConfiguration(a.(*webhook.WebhookConfiguration), b.(*v1alpha1.WebhookConfiguration), scope) - }); err != nil { - return err - } - return nil -} - -func autoConvert_v1alpha1_DynamicServingConfig_To_webhook_DynamicServingConfig(in *v1alpha1.DynamicServingConfig, out *webhook.DynamicServingConfig, s conversion.Scope) error { - out.SecretNamespace = in.SecretNamespace - out.SecretName = in.SecretName - out.DNSNames = *(*[]string)(unsafe.Pointer(&in.DNSNames)) - return nil -} - -// Convert_v1alpha1_DynamicServingConfig_To_webhook_DynamicServingConfig is an autogenerated conversion function. -func Convert_v1alpha1_DynamicServingConfig_To_webhook_DynamicServingConfig(in *v1alpha1.DynamicServingConfig, out *webhook.DynamicServingConfig, s conversion.Scope) error { - return autoConvert_v1alpha1_DynamicServingConfig_To_webhook_DynamicServingConfig(in, out, s) -} - -func autoConvert_webhook_DynamicServingConfig_To_v1alpha1_DynamicServingConfig(in *webhook.DynamicServingConfig, out *v1alpha1.DynamicServingConfig, s conversion.Scope) error { - out.SecretNamespace = in.SecretNamespace - out.SecretName = in.SecretName - out.DNSNames = *(*[]string)(unsafe.Pointer(&in.DNSNames)) - return nil -} - -// Convert_webhook_DynamicServingConfig_To_v1alpha1_DynamicServingConfig is an autogenerated conversion function. -func Convert_webhook_DynamicServingConfig_To_v1alpha1_DynamicServingConfig(in *webhook.DynamicServingConfig, out *v1alpha1.DynamicServingConfig, s conversion.Scope) error { - return autoConvert_webhook_DynamicServingConfig_To_v1alpha1_DynamicServingConfig(in, out, s) -} - -func autoConvert_v1alpha1_FilesystemServingConfig_To_webhook_FilesystemServingConfig(in *v1alpha1.FilesystemServingConfig, out *webhook.FilesystemServingConfig, s conversion.Scope) error { - out.CertFile = in.CertFile - out.KeyFile = in.KeyFile - return nil -} - -// Convert_v1alpha1_FilesystemServingConfig_To_webhook_FilesystemServingConfig is an autogenerated conversion function. -func Convert_v1alpha1_FilesystemServingConfig_To_webhook_FilesystemServingConfig(in *v1alpha1.FilesystemServingConfig, out *webhook.FilesystemServingConfig, s conversion.Scope) error { - return autoConvert_v1alpha1_FilesystemServingConfig_To_webhook_FilesystemServingConfig(in, out, s) -} - -func autoConvert_webhook_FilesystemServingConfig_To_v1alpha1_FilesystemServingConfig(in *webhook.FilesystemServingConfig, out *v1alpha1.FilesystemServingConfig, s conversion.Scope) error { - out.CertFile = in.CertFile - out.KeyFile = in.KeyFile - return nil -} - -// Convert_webhook_FilesystemServingConfig_To_v1alpha1_FilesystemServingConfig is an autogenerated conversion function. -func Convert_webhook_FilesystemServingConfig_To_v1alpha1_FilesystemServingConfig(in *webhook.FilesystemServingConfig, out *v1alpha1.FilesystemServingConfig, s conversion.Scope) error { - return autoConvert_webhook_FilesystemServingConfig_To_v1alpha1_FilesystemServingConfig(in, out, s) -} - -func autoConvert_v1alpha1_TLSConfig_To_webhook_TLSConfig(in *v1alpha1.TLSConfig, out *webhook.TLSConfig, s conversion.Scope) error { - out.CipherSuites = *(*[]string)(unsafe.Pointer(&in.CipherSuites)) - out.MinTLSVersion = in.MinTLSVersion - if err := Convert_v1alpha1_FilesystemServingConfig_To_webhook_FilesystemServingConfig(&in.Filesystem, &out.Filesystem, s); err != nil { - return err - } - if err := Convert_v1alpha1_DynamicServingConfig_To_webhook_DynamicServingConfig(&in.Dynamic, &out.Dynamic, s); err != nil { - return err - } return nil } -// Convert_v1alpha1_TLSConfig_To_webhook_TLSConfig is an autogenerated conversion function. -func Convert_v1alpha1_TLSConfig_To_webhook_TLSConfig(in *v1alpha1.TLSConfig, out *webhook.TLSConfig, s conversion.Scope) error { - return autoConvert_v1alpha1_TLSConfig_To_webhook_TLSConfig(in, out, s) -} - -func autoConvert_webhook_TLSConfig_To_v1alpha1_TLSConfig(in *webhook.TLSConfig, out *v1alpha1.TLSConfig, s conversion.Scope) error { - out.CipherSuites = *(*[]string)(unsafe.Pointer(&in.CipherSuites)) - out.MinTLSVersion = in.MinTLSVersion - if err := Convert_webhook_FilesystemServingConfig_To_v1alpha1_FilesystemServingConfig(&in.Filesystem, &out.Filesystem, s); err != nil { +func autoConvert_v1alpha1_WebhookConfiguration_To_webhook_WebhookConfiguration(in *webhookv1alpha1.WebhookConfiguration, out *webhook.WebhookConfiguration, s conversion.Scope) error { + if err := v1.Convert_Pointer_int32_To_int32(&in.SecurePort, &out.SecurePort, s); err != nil { return err } - if err := Convert_webhook_DynamicServingConfig_To_v1alpha1_DynamicServingConfig(&in.Dynamic, &out.Dynamic, s); err != nil { + if err := v1.Convert_Pointer_int32_To_int32(&in.HealthzPort, &out.HealthzPort, s); err != nil { return err } - return nil -} - -// Convert_webhook_TLSConfig_To_v1alpha1_TLSConfig is an autogenerated conversion function. -func Convert_webhook_TLSConfig_To_v1alpha1_TLSConfig(in *webhook.TLSConfig, out *v1alpha1.TLSConfig, s conversion.Scope) error { - return autoConvert_webhook_TLSConfig_To_v1alpha1_TLSConfig(in, out, s) -} - -func autoConvert_v1alpha1_WebhookConfiguration_To_webhook_WebhookConfiguration(in *v1alpha1.WebhookConfiguration, out *webhook.WebhookConfiguration, s conversion.Scope) error { - out.SecurePort = (*int)(unsafe.Pointer(in.SecurePort)) - out.HealthzPort = (*int)(unsafe.Pointer(in.HealthzPort)) - if err := Convert_v1alpha1_TLSConfig_To_webhook_TLSConfig(&in.TLSConfig, &out.TLSConfig, s); err != nil { + if err := sharedv1alpha1.Convert_v1alpha1_TLSConfig_To_shared_TLSConfig(&in.TLSConfig, &out.TLSConfig, s); err != nil { return err } out.KubeConfig = in.KubeConfig out.APIServerHost = in.APIServerHost out.EnablePprof = in.EnablePprof out.PprofAddress = in.PprofAddress + out.Logging = in.Logging out.FeatureGates = *(*map[string]bool)(unsafe.Pointer(&in.FeatureGates)) + out.MetricsListenAddress = in.MetricsListenAddress + if err := sharedv1alpha1.Convert_v1alpha1_TLSConfig_To_shared_TLSConfig(&in.MetricsTLSConfig, &out.MetricsTLSConfig, s); err != nil { + return err + } return nil } // Convert_v1alpha1_WebhookConfiguration_To_webhook_WebhookConfiguration is an autogenerated conversion function. -func Convert_v1alpha1_WebhookConfiguration_To_webhook_WebhookConfiguration(in *v1alpha1.WebhookConfiguration, out *webhook.WebhookConfiguration, s conversion.Scope) error { +func Convert_v1alpha1_WebhookConfiguration_To_webhook_WebhookConfiguration(in *webhookv1alpha1.WebhookConfiguration, out *webhook.WebhookConfiguration, s conversion.Scope) error { return autoConvert_v1alpha1_WebhookConfiguration_To_webhook_WebhookConfiguration(in, out, s) } -func autoConvert_webhook_WebhookConfiguration_To_v1alpha1_WebhookConfiguration(in *webhook.WebhookConfiguration, out *v1alpha1.WebhookConfiguration, s conversion.Scope) error { - out.SecurePort = (*int)(unsafe.Pointer(in.SecurePort)) - out.HealthzPort = (*int)(unsafe.Pointer(in.HealthzPort)) - if err := Convert_webhook_TLSConfig_To_v1alpha1_TLSConfig(&in.TLSConfig, &out.TLSConfig, s); err != nil { +func autoConvert_webhook_WebhookConfiguration_To_v1alpha1_WebhookConfiguration(in *webhook.WebhookConfiguration, out *webhookv1alpha1.WebhookConfiguration, s conversion.Scope) error { + if err := v1.Convert_int32_To_Pointer_int32(&in.SecurePort, &out.SecurePort, s); err != nil { + return err + } + if err := v1.Convert_int32_To_Pointer_int32(&in.HealthzPort, &out.HealthzPort, s); err != nil { + return err + } + if err := sharedv1alpha1.Convert_shared_TLSConfig_To_v1alpha1_TLSConfig(&in.TLSConfig, &out.TLSConfig, s); err != nil { return err } out.KubeConfig = in.KubeConfig out.APIServerHost = in.APIServerHost out.EnablePprof = in.EnablePprof out.PprofAddress = in.PprofAddress + out.Logging = in.Logging out.FeatureGates = *(*map[string]bool)(unsafe.Pointer(&in.FeatureGates)) + out.MetricsListenAddress = in.MetricsListenAddress + if err := sharedv1alpha1.Convert_shared_TLSConfig_To_v1alpha1_TLSConfig(&in.MetricsTLSConfig, &out.MetricsTLSConfig, s); err != nil { + return err + } return nil } // Convert_webhook_WebhookConfiguration_To_v1alpha1_WebhookConfiguration is an autogenerated conversion function. -func Convert_webhook_WebhookConfiguration_To_v1alpha1_WebhookConfiguration(in *webhook.WebhookConfiguration, out *v1alpha1.WebhookConfiguration, s conversion.Scope) error { +func Convert_webhook_WebhookConfiguration_To_v1alpha1_WebhookConfiguration(in *webhook.WebhookConfiguration, out *webhookv1alpha1.WebhookConfiguration, s conversion.Scope) error { return autoConvert_webhook_WebhookConfiguration_To_v1alpha1_WebhookConfiguration(in, out, s) } diff --git a/internal/apis/config/webhook/v1alpha1/zz_generated.defaults.go b/internal/apis/config/webhook/v1alpha1/zz_generated.defaults.go index 39c82e224ab..d31ebe98b9b 100644 --- a/internal/apis/config/webhook/v1alpha1/zz_generated.defaults.go +++ b/internal/apis/config/webhook/v1alpha1/zz_generated.defaults.go @@ -22,7 +22,8 @@ limitations under the License. package v1alpha1 import ( - v1alpha1 "github.com/cert-manager/cert-manager/pkg/apis/config/webhook/v1alpha1" + sharedv1alpha1 "github.com/cert-manager/cert-manager/internal/apis/config/shared/v1alpha1" + webhookv1alpha1 "github.com/cert-manager/cert-manager/pkg/apis/config/webhook/v1alpha1" runtime "k8s.io/apimachinery/pkg/runtime" ) @@ -30,10 +31,14 @@ import ( // Public to allow building arbitrary schemes. // All generated defaulters are covering - they call all nested defaulters. func RegisterDefaults(scheme *runtime.Scheme) error { - scheme.AddTypeDefaultingFunc(&v1alpha1.WebhookConfiguration{}, func(obj interface{}) { SetObjectDefaults_WebhookConfiguration(obj.(*v1alpha1.WebhookConfiguration)) }) + scheme.AddTypeDefaultingFunc(&webhookv1alpha1.WebhookConfiguration{}, func(obj interface{}) { + SetObjectDefaults_WebhookConfiguration(obj.(*webhookv1alpha1.WebhookConfiguration)) + }) return nil } -func SetObjectDefaults_WebhookConfiguration(in *v1alpha1.WebhookConfiguration) { +func SetObjectDefaults_WebhookConfiguration(in *webhookv1alpha1.WebhookConfiguration) { SetDefaults_WebhookConfiguration(in) + sharedv1alpha1.SetDefaults_DynamicServingConfig(&in.TLSConfig.Dynamic) + sharedv1alpha1.SetDefaults_DynamicServingConfig(&in.MetricsTLSConfig.Dynamic) } diff --git a/internal/apis/config/webhook/validation/validation.go b/internal/apis/config/webhook/validation/validation.go index 1a130909911..a3b83c8468c 100644 --- a/internal/apis/config/webhook/validation/validation.go +++ b/internal/apis/config/webhook/validation/validation.go @@ -17,42 +17,25 @@ limitations under the License. package validation import ( - "fmt" - - utilerrors "k8s.io/apimachinery/pkg/util/errors" + "k8s.io/apimachinery/pkg/util/validation/field" + logsapi "k8s.io/component-base/logs/api/v1" + sharedvalidation "github.com/cert-manager/cert-manager/internal/apis/config/shared/validation" config "github.com/cert-manager/cert-manager/internal/apis/config/webhook" ) -func ValidateWebhookConfiguration(cfg *config.WebhookConfiguration) error { - var allErrors []error - if cfg.TLSConfig.FilesystemConfigProvided() && cfg.TLSConfig.DynamicConfigProvided() { - allErrors = append(allErrors, fmt.Errorf("invalid configuration: cannot specify both filesystem based and dynamic TLS configuration")) - } else { - if cfg.TLSConfig.FilesystemConfigProvided() { - if cfg.TLSConfig.Filesystem.KeyFile == "" { - allErrors = append(allErrors, fmt.Errorf("invalid configuration: tlsConfig.filesystem.keyFile (--tls-private-key-file) must be specified when using filesystem based TLS config")) - } - if cfg.TLSConfig.Filesystem.CertFile == "" { - allErrors = append(allErrors, fmt.Errorf("invalid configuration: tlsConfig.filesystem.certFile (--tls-cert-file) must be specified when using filesystem based TLS config")) - } - } else if cfg.TLSConfig.DynamicConfigProvided() { - if cfg.TLSConfig.Dynamic.SecretNamespace == "" { - allErrors = append(allErrors, fmt.Errorf("invalid configuration: tlsConfig.dynamic.secretNamespace (--dynamic-serving-ca-secret-namespace) must be specified when using dynamic TLS config")) - } - if cfg.TLSConfig.Dynamic.SecretName == "" { - allErrors = append(allErrors, fmt.Errorf("invalid configuration: tlsConfig.dynamic.secretName (--dynamic-serving-ca-secret-name) must be specified when using dynamic TLS config")) - } - if len(cfg.TLSConfig.Dynamic.DNSNames) == 0 { - allErrors = append(allErrors, fmt.Errorf("invalid configuration: tlsConfig.dynamic.dnsNames (--dynamic-serving-dns-names) must be specified when using dynamic TLS config")) - } - } - } - if cfg.HealthzPort == nil { - allErrors = append(allErrors, fmt.Errorf("invalid configuration: healthzPort must be specified")) +func ValidateWebhookConfiguration(cfg *config.WebhookConfiguration, fldPath *field.Path) field.ErrorList { + var allErrors field.ErrorList + + allErrors = append(allErrors, logsapi.Validate(&cfg.Logging, nil, fldPath.Child("logging"))...) + allErrors = append(allErrors, sharedvalidation.ValidateTLSConfig(&cfg.TLSConfig, fldPath.Child("tlsConfig"))...) + + if cfg.HealthzPort < 0 || cfg.HealthzPort > 65535 { + allErrors = append(allErrors, field.Invalid(fldPath.Child("healthzPort"), cfg.HealthzPort, "must be a valid port number")) } - if cfg.SecurePort == nil { - allErrors = append(allErrors, fmt.Errorf("invalid configuration: securePort must be specified")) + if cfg.SecurePort < 0 || cfg.SecurePort > 65535 { + allErrors = append(allErrors, field.Invalid(fldPath.Child("securePort"), cfg.SecurePort, "must be a valid port number")) } - return utilerrors.NewAggregate(allErrors) + + return allErrors } diff --git a/internal/apis/config/webhook/validation/validation_test.go b/internal/apis/config/webhook/validation/validation_test.go new file mode 100644 index 00000000000..19a781ae835 --- /dev/null +++ b/internal/apis/config/webhook/validation/validation_test.go @@ -0,0 +1,141 @@ +/* +Copyright 2021 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. +*/ + +package validation + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "k8s.io/apimachinery/pkg/util/validation/field" + logsapi "k8s.io/component-base/logs/api/v1" + + "github.com/cert-manager/cert-manager/internal/apis/config/shared" + config "github.com/cert-manager/cert-manager/internal/apis/config/webhook" +) + +func TestValidateWebhookConfiguration(t *testing.T) { + tests := []struct { + name string + config *config.WebhookConfiguration + errs func(*config.WebhookConfiguration) field.ErrorList + }{ + { + "with no tls config", + &config.WebhookConfiguration{ + Logging: logsapi.LoggingConfiguration{ + Format: "text", + }, + }, + nil, + }, + { + "with invalid logging config", + &config.WebhookConfiguration{ + Logging: logsapi.LoggingConfiguration{ + Format: "unknown", + }, + }, + func(wc *config.WebhookConfiguration) field.ErrorList { + return field.ErrorList{ + field.Invalid(field.NewPath("logging.format"), wc.Logging.Format, "Unsupported log format"), + } + }, + }, + { + "with invalid tls config", + &config.WebhookConfiguration{ + Logging: logsapi.LoggingConfiguration{ + Format: "text", + }, + TLSConfig: shared.TLSConfig{ + Filesystem: shared.FilesystemServingConfig{ + CertFile: "/test.crt", + KeyFile: "/test.key", + }, + Dynamic: shared.DynamicServingConfig{ + SecretNamespace: "cert-manager", + SecretName: "test", + DNSNames: []string{"example.com"}, + }, + }, + }, + func(wc *config.WebhookConfiguration) field.ErrorList { + return field.ErrorList{ + field.Invalid(field.NewPath("tlsConfig"), &wc.TLSConfig, "cannot specify both filesystem based and dynamic TLS configuration"), + } + }, + }, + { + "with valid healthz port", + &config.WebhookConfiguration{ + Logging: logsapi.LoggingConfiguration{ + Format: "text", + }, + HealthzPort: 8080, + }, + nil, + }, + { + "with invalid healthz port", + &config.WebhookConfiguration{ + Logging: logsapi.LoggingConfiguration{ + Format: "text", + }, + HealthzPort: 99999999, + }, + func(wc *config.WebhookConfiguration) field.ErrorList { + return field.ErrorList{ + field.Invalid(field.NewPath("healthzPort"), wc.HealthzPort, "must be a valid port number"), + } + }, + }, + { + "with valid secure port", + &config.WebhookConfiguration{ + Logging: logsapi.LoggingConfiguration{ + Format: "text", + }, + SecurePort: 8080, + }, + nil, + }, + { + "with invalid secure port", + &config.WebhookConfiguration{ + Logging: logsapi.LoggingConfiguration{ + Format: "text", + }, + SecurePort: 99999999, + }, + func(wc *config.WebhookConfiguration) field.ErrorList { + return field.ErrorList{ + field.Invalid(field.NewPath("securePort"), wc.SecurePort, "must be a valid port number"), + } + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + errList := ValidateWebhookConfiguration(tt.config, nil) + var expErrs field.ErrorList + if tt.errs != nil { + expErrs = tt.errs(tt.config) + } + assert.ElementsMatch(t, expErrs, errList) + }) + } +} diff --git a/internal/apis/config/webhook/zz_generated.deepcopy.go b/internal/apis/config/webhook/zz_generated.deepcopy.go index aeeba9d7f11..775767f48cf 100644 --- a/internal/apis/config/webhook/zz_generated.deepcopy.go +++ b/internal/apis/config/webhook/zz_generated.deepcopy.go @@ -25,81 +25,12 @@ import ( runtime "k8s.io/apimachinery/pkg/runtime" ) -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *DynamicServingConfig) DeepCopyInto(out *DynamicServingConfig) { - *out = *in - if in.DNSNames != nil { - in, out := &in.DNSNames, &out.DNSNames - *out = make([]string, len(*in)) - copy(*out, *in) - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DynamicServingConfig. -func (in *DynamicServingConfig) DeepCopy() *DynamicServingConfig { - if in == nil { - return nil - } - out := new(DynamicServingConfig) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *FilesystemServingConfig) DeepCopyInto(out *FilesystemServingConfig) { - *out = *in - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FilesystemServingConfig. -func (in *FilesystemServingConfig) DeepCopy() *FilesystemServingConfig { - if in == nil { - return nil - } - out := new(FilesystemServingConfig) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *TLSConfig) DeepCopyInto(out *TLSConfig) { - *out = *in - if in.CipherSuites != nil { - in, out := &in.CipherSuites, &out.CipherSuites - *out = make([]string, len(*in)) - copy(*out, *in) - } - out.Filesystem = in.Filesystem - in.Dynamic.DeepCopyInto(&out.Dynamic) - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TLSConfig. -func (in *TLSConfig) DeepCopy() *TLSConfig { - if in == nil { - return nil - } - out := new(TLSConfig) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *WebhookConfiguration) DeepCopyInto(out *WebhookConfiguration) { *out = *in out.TypeMeta = in.TypeMeta - if in.SecurePort != nil { - in, out := &in.SecurePort, &out.SecurePort - *out = new(int) - **out = **in - } - if in.HealthzPort != nil { - in, out := &in.HealthzPort, &out.HealthzPort - *out = new(int) - **out = **in - } in.TLSConfig.DeepCopyInto(&out.TLSConfig) + in.Logging.DeepCopyInto(&out.Logging) if in.FeatureGates != nil { in, out := &in.FeatureGates, &out.FeatureGates *out = make(map[string]bool, len(*in)) @@ -107,6 +38,7 @@ func (in *WebhookConfiguration) DeepCopyInto(out *WebhookConfiguration) { (*out)[key] = val } } + in.MetricsTLSConfig.DeepCopyInto(&out.MetricsTLSConfig) return } diff --git a/internal/apis/meta/types.go b/internal/apis/meta/types.go index 41df8e28545..6653e2f54c0 100644 --- a/internal/apis/meta/types.go +++ b/internal/apis/meta/types.go @@ -23,7 +23,7 @@ type ConditionStatus string // the condition; "ConditionFalse" means a resource is not in the condition; // "ConditionUnknown" means kubernetes can't decide if a resource is in the // condition or not. In the future, we could add other intermediate -// conditions, e.g. ConditionDegraded. +// conditions, e.g., ConditionDegraded. const ( // ConditionTrue represents the fact that a given condition is true ConditionTrue ConditionStatus = "True" @@ -36,7 +36,7 @@ const ( ) // A reference to an object in the same namespace as the referent. -// If the referent is a cluster-scoped resource (e.g. a ClusterIssuer), +// 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 @@ -47,16 +47,20 @@ type LocalObjectReference struct { Name string } -// ObjectReference is a reference to an object with a given name, kind and group. -type ObjectReference struct { - // Name of the resource being referred to. +// IssuerReference is a reference to a certificate issuer object with a given name, kind and group. +type IssuerReference struct { + // Name of the issuer being referred to. Name string - // Kind of the resource being referred to. + // Kind of the issuer being referred to. Kind string - // Group of the resource being referred to. + // Group of the issuer being referred to. Group string } +// ObjectReference is a reference to an object with a given name, kind and group. +// Deprecated: Use IssuerReference instead. +type ObjectReference = IssuerReference + // A reference to a specific 'key' within a Secret resource. // In some instances, `key` is a required field. type SecretKeySelector struct { diff --git a/internal/apis/meta/v1/conversion.go b/internal/apis/meta/v1/conversion.go index 199b4d75176..f0818a87b9d 100644 --- a/internal/apis/meta/v1/conversion.go +++ b/internal/apis/meta/v1/conversion.go @@ -35,16 +35,16 @@ func Convert_v1_LocalObjectReference_To_meta_LocalObjectReference(in *cmmeta.Loc return autoConvert_v1_LocalObjectReference_To_meta_LocalObjectReference(in, out, s) } -// Convert_meta_ObjectReference_To_v1_ObjectReference is explicitly defined to avoid issues in conversion-gen +// Convert_meta_IssuerReference_To_v1_IssuerReference is explicitly defined to avoid issues in conversion-gen // when referencing types in other API groups. -func Convert_meta_ObjectReference_To_v1_ObjectReference(in *meta.ObjectReference, out *cmmeta.ObjectReference, s conversion.Scope) error { - return autoConvert_meta_ObjectReference_To_v1_ObjectReference(in, out, s) +func Convert_meta_IssuerReference_To_v1_IssuerReference(in *meta.IssuerReference, out *cmmeta.IssuerReference, s conversion.Scope) error { + return autoConvert_meta_IssuerReference_To_v1_IssuerReference(in, out, s) } -// Convert_v1_ObjectReference_To_meta_ObjectReference is explicitly defined to avoid issues in conversion-gen +// Convert_v1_IssuerReference_To_meta_IssuerReference is explicitly defined to avoid issues in conversion-gen // when referencing types in other API groups. -func Convert_v1_ObjectReference_To_meta_ObjectReference(in *cmmeta.ObjectReference, out *meta.ObjectReference, s conversion.Scope) error { - return autoConvert_v1_ObjectReference_To_meta_ObjectReference(in, out, s) +func Convert_v1_IssuerReference_To_meta_IssuerReference(in *cmmeta.IssuerReference, out *meta.IssuerReference, s conversion.Scope) error { + return autoConvert_v1_IssuerReference_To_meta_IssuerReference(in, out, s) } // Convert_meta_SecretKeySelector_To_v1_SecretKeySelector is explicitly defined to avoid issues in conversion-gen diff --git a/internal/apis/meta/v1/zz_generated.conversion.go b/internal/apis/meta/v1/zz_generated.conversion.go index 7d9e338fd96..8924bd3e282 100644 --- a/internal/apis/meta/v1/zz_generated.conversion.go +++ b/internal/apis/meta/v1/zz_generated.conversion.go @@ -23,7 +23,7 @@ package v1 import ( meta "github.com/cert-manager/cert-manager/internal/apis/meta" - v1 "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" + metav1 "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" conversion "k8s.io/apimachinery/pkg/conversion" runtime "k8s.io/apimachinery/pkg/runtime" ) @@ -35,64 +35,64 @@ func init() { // RegisterConversions adds conversion functions to the given scheme. // Public to allow building arbitrary schemes. func RegisterConversions(s *runtime.Scheme) error { - if err := s.AddConversionFunc((*meta.LocalObjectReference)(nil), (*v1.LocalObjectReference)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_meta_LocalObjectReference_To_v1_LocalObjectReference(a.(*meta.LocalObjectReference), b.(*v1.LocalObjectReference), scope) + if err := s.AddConversionFunc((*meta.IssuerReference)(nil), (*metav1.IssuerReference)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_meta_IssuerReference_To_v1_IssuerReference(a.(*meta.IssuerReference), b.(*metav1.IssuerReference), scope) }); err != nil { return err } - if err := s.AddConversionFunc((*meta.ObjectReference)(nil), (*v1.ObjectReference)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_meta_ObjectReference_To_v1_ObjectReference(a.(*meta.ObjectReference), b.(*v1.ObjectReference), scope) + if err := s.AddConversionFunc((*meta.LocalObjectReference)(nil), (*metav1.LocalObjectReference)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_meta_LocalObjectReference_To_v1_LocalObjectReference(a.(*meta.LocalObjectReference), b.(*metav1.LocalObjectReference), scope) }); err != nil { return err } - if err := s.AddConversionFunc((*meta.SecretKeySelector)(nil), (*v1.SecretKeySelector)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_meta_SecretKeySelector_To_v1_SecretKeySelector(a.(*meta.SecretKeySelector), b.(*v1.SecretKeySelector), scope) + if err := s.AddConversionFunc((*meta.SecretKeySelector)(nil), (*metav1.SecretKeySelector)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_meta_SecretKeySelector_To_v1_SecretKeySelector(a.(*meta.SecretKeySelector), b.(*metav1.SecretKeySelector), scope) }); err != nil { return err } - if err := s.AddConversionFunc((*v1.LocalObjectReference)(nil), (*meta.LocalObjectReference)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1_LocalObjectReference_To_meta_LocalObjectReference(a.(*v1.LocalObjectReference), b.(*meta.LocalObjectReference), scope) + if err := s.AddConversionFunc((*metav1.IssuerReference)(nil), (*meta.IssuerReference)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1_IssuerReference_To_meta_IssuerReference(a.(*metav1.IssuerReference), b.(*meta.IssuerReference), scope) }); err != nil { return err } - if err := s.AddConversionFunc((*v1.ObjectReference)(nil), (*meta.ObjectReference)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1_ObjectReference_To_meta_ObjectReference(a.(*v1.ObjectReference), b.(*meta.ObjectReference), scope) + if err := s.AddConversionFunc((*metav1.LocalObjectReference)(nil), (*meta.LocalObjectReference)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1_LocalObjectReference_To_meta_LocalObjectReference(a.(*metav1.LocalObjectReference), b.(*meta.LocalObjectReference), scope) }); err != nil { return err } - if err := s.AddConversionFunc((*v1.SecretKeySelector)(nil), (*meta.SecretKeySelector)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1_SecretKeySelector_To_meta_SecretKeySelector(a.(*v1.SecretKeySelector), b.(*meta.SecretKeySelector), scope) + if err := s.AddConversionFunc((*metav1.SecretKeySelector)(nil), (*meta.SecretKeySelector)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1_SecretKeySelector_To_meta_SecretKeySelector(a.(*metav1.SecretKeySelector), b.(*meta.SecretKeySelector), scope) }); err != nil { return err } return nil } -func autoConvert_v1_LocalObjectReference_To_meta_LocalObjectReference(in *v1.LocalObjectReference, out *meta.LocalObjectReference, s conversion.Scope) error { +func autoConvert_v1_IssuerReference_To_meta_IssuerReference(in *metav1.IssuerReference, out *meta.IssuerReference, s conversion.Scope) error { out.Name = in.Name + out.Kind = in.Kind + out.Group = in.Group return nil } -func autoConvert_meta_LocalObjectReference_To_v1_LocalObjectReference(in *meta.LocalObjectReference, out *v1.LocalObjectReference, s conversion.Scope) error { +func autoConvert_meta_IssuerReference_To_v1_IssuerReference(in *meta.IssuerReference, out *metav1.IssuerReference, s conversion.Scope) error { out.Name = in.Name + out.Kind = in.Kind + out.Group = in.Group return nil } -func autoConvert_v1_ObjectReference_To_meta_ObjectReference(in *v1.ObjectReference, out *meta.ObjectReference, s conversion.Scope) error { +func autoConvert_v1_LocalObjectReference_To_meta_LocalObjectReference(in *metav1.LocalObjectReference, out *meta.LocalObjectReference, s conversion.Scope) error { out.Name = in.Name - out.Kind = in.Kind - out.Group = in.Group return nil } -func autoConvert_meta_ObjectReference_To_v1_ObjectReference(in *meta.ObjectReference, out *v1.ObjectReference, s conversion.Scope) error { +func autoConvert_meta_LocalObjectReference_To_v1_LocalObjectReference(in *meta.LocalObjectReference, out *metav1.LocalObjectReference, s conversion.Scope) error { out.Name = in.Name - out.Kind = in.Kind - out.Group = in.Group return nil } -func autoConvert_v1_SecretKeySelector_To_meta_SecretKeySelector(in *v1.SecretKeySelector, out *meta.SecretKeySelector, s conversion.Scope) error { +func autoConvert_v1_SecretKeySelector_To_meta_SecretKeySelector(in *metav1.SecretKeySelector, out *meta.SecretKeySelector, s conversion.Scope) error { if err := Convert_v1_LocalObjectReference_To_meta_LocalObjectReference(&in.LocalObjectReference, &out.LocalObjectReference, s); err != nil { return err } @@ -100,7 +100,7 @@ func autoConvert_v1_SecretKeySelector_To_meta_SecretKeySelector(in *v1.SecretKey return nil } -func autoConvert_meta_SecretKeySelector_To_v1_SecretKeySelector(in *meta.SecretKeySelector, out *v1.SecretKeySelector, s conversion.Scope) error { +func autoConvert_meta_SecretKeySelector_To_v1_SecretKeySelector(in *meta.SecretKeySelector, out *metav1.SecretKeySelector, s conversion.Scope) error { if err := Convert_meta_LocalObjectReference_To_v1_LocalObjectReference(&in.LocalObjectReference, &out.LocalObjectReference, s); err != nil { return err } diff --git a/internal/apis/meta/zz_generated.deepcopy.go b/internal/apis/meta/zz_generated.deepcopy.go index 6215a6fcf14..497f9f94b99 100644 --- a/internal/apis/meta/zz_generated.deepcopy.go +++ b/internal/apis/meta/zz_generated.deepcopy.go @@ -22,33 +22,33 @@ limitations under the License. package meta // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *LocalObjectReference) DeepCopyInto(out *LocalObjectReference) { +func (in *IssuerReference) DeepCopyInto(out *IssuerReference) { *out = *in return } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LocalObjectReference. -func (in *LocalObjectReference) DeepCopy() *LocalObjectReference { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new IssuerReference. +func (in *IssuerReference) DeepCopy() *IssuerReference { if in == nil { return nil } - out := new(LocalObjectReference) + out := new(IssuerReference) in.DeepCopyInto(out) return out } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ObjectReference) DeepCopyInto(out *ObjectReference) { +func (in *LocalObjectReference) DeepCopyInto(out *LocalObjectReference) { *out = *in return } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ObjectReference. -func (in *ObjectReference) DeepCopy() *ObjectReference { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LocalObjectReference. +func (in *LocalObjectReference) DeepCopy() *LocalObjectReference { if in == nil { return nil } - out := new(ObjectReference) + out := new(LocalObjectReference) in.DeepCopyInto(out) return out } diff --git a/internal/cainjector/bundle/bundle.go b/internal/cainjector/bundle/bundle.go new file mode 100644 index 00000000000..fb37e225260 --- /dev/null +++ b/internal/cainjector/bundle/bundle.go @@ -0,0 +1,81 @@ +/* +Copyright 2020 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. +*/ + +package bundle + +import ( + "bytes" + "crypto/x509" + "encoding/pem" + "fmt" + "time" + + "k8s.io/utils/set" + + "github.com/cert-manager/cert-manager/pkg/util/pki" +) + +// AppendCertificatesToBundle will append the provided certificates to the +// provided bundle, if the certificate already exists in the bundle then it is +// not re-added. +// +// Additionally expired certificates are removed from the bundle. +func AppendCertificatesToBundle(bundle []byte, additional []byte) ([]byte, error) { + certificatesFromBundle, err := pki.DecodeX509CertificateSetBytes(bundle) + if err != nil && len(bundle) != 0 { + return nil, fmt.Errorf("failed to parse bundle: %w", err) + } + + certificatesToMerge, err := pki.DecodeX509CertificateSetBytes(additional) + if err != nil && len(additional) != 0 { + return nil, fmt.Errorf("failed to parse additional certificates: %w", err) + } + + certificatesSeen := set.New[string]() + certificatesMerged := make([]*x509.Certificate, 0, len(certificatesFromBundle)+len(certificatesToMerge)) + + // We delete expired certificates from the bundle, for this we will + // repeatedly need the current time + now := time.Now() + + // Merge in all certificates that already exist in the bundle + for _, certificate := range certificatesFromBundle { + raw := string(certificate.Raw) + if !certificatesSeen.Has(raw) && !now.After(certificate.NotAfter) { + certificatesMerged = append(certificatesMerged, certificate) + certificatesSeen.Insert(raw) + } + } + + // Merge in all additional certificates + for _, certificate := range certificatesToMerge { + raw := string(certificate.Raw) + if !certificatesSeen.Has(raw) && !now.After(certificate.NotAfter) { + certificatesMerged = append(certificatesMerged, certificate) + certificatesSeen.Insert(raw) + } + } + + // Build the chain + buff := bytes.NewBuffer([]byte{}) + for _, certificate := range certificatesMerged { + if err := pem.Encode(buff, &pem.Block{Type: "CERTIFICATE", Bytes: certificate.Raw}); err != nil { + return nil, fmt.Errorf("failed encode certificate in PEM format: %w", err) + } + } + + return buff.Bytes(), nil +} diff --git a/internal/cainjector/bundle/bundle_test.go b/internal/cainjector/bundle/bundle_test.go new file mode 100644 index 00000000000..9601edf5fe4 --- /dev/null +++ b/internal/cainjector/bundle/bundle_test.go @@ -0,0 +1,137 @@ +/* +Copyright 2020 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. +*/ + +package bundle + +import ( + "bytes" + "crypto" + "crypto/x509" + "crypto/x509/pkix" + "testing" + "time" + + "github.com/cert-manager/cert-manager/pkg/util/pki" +) + +func TestAppendCertificatesToBundle(t *testing.T) { + // Create certificates for use in tests + expired := mustCreateCertificate(t, "expired", time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC), time.Date(2001, 1, 1, 0, 0, 0, 0, time.UTC)) + valid1 := mustCreateCertificate(t, "valid-1", time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC), time.Date(3000, 1, 1, 0, 0, 0, 0, time.UTC)) + valid2 := mustCreateCertificate(t, "valid-2", time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC), time.Date(3000, 1, 1, 0, 0, 0, 0, time.UTC)) + + cases := []struct { + Name string + Bundle []byte + Additional []byte + Expected []byte + ExpectErr bool + }{ + { + Name: "append_to_empty_bundle", + Bundle: nil, + Additional: valid1, + Expected: valid1, + }, + { + Name: "append_to_non_empty_bundle", + Bundle: valid1, + Additional: valid2, + Expected: joinPEM(valid1, valid2), + }, + { + Name: "removes_expired_certificates", + Bundle: joinPEM(valid1, expired), + Additional: valid2, + Expected: joinPEM(valid1, valid2), + }, + { + Name: "removes_duplicate_certificates", + Bundle: joinPEM(valid1, valid1), + Additional: valid2, + Expected: joinPEM(valid1, valid2), + }, + { + Name: "does_not_append_existing_certificates", + Bundle: joinPEM(valid1), + Additional: valid1, + Expected: joinPEM(valid1), + }, + { + Name: "does_not_append_expired_certificates", + Bundle: joinPEM(valid1), + Additional: expired, + Expected: joinPEM(valid1), + }, + } + + for _, test := range cases { + t.Run(test.Name, func(t *testing.T) { + result, err := AppendCertificatesToBundle(test.Bundle, test.Additional) + + if (err != nil) != test.ExpectErr { + t.Fatalf("unexpected error, expected error %t, got %q", test.ExpectErr, err) + } + + if !bytes.Equal(result, test.Expected) { + t.Fatalf("unexpected result, expected %q, got %q", test.Expected, result) + } + }) + } +} + +func mustCreateCertificate(t *testing.T, name string, notBefore, notAfter time.Time) []byte { + pk, err := pki.GenerateECPrivateKey(256) + if err != nil { + t.Fatal(err) + } + + template := &x509.Certificate{ + BasicConstraintsValid: true, + PublicKeyAlgorithm: x509.ECDSA, + PublicKey: pk.Public(), + IsCA: true, + Subject: pkix.Name{ + CommonName: name, + }, + NotBefore: notBefore, + NotAfter: notAfter, + KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, + } + + var ( + issuerKey crypto.PrivateKey + issuerCert *x509.Certificate + ) + + issuerKey = pk + issuerCert = template + + certPEM, _, err := pki.SignCertificate(template, issuerCert, pk.Public(), issuerKey) + if err != nil { + t.Fatal(err) + } + + return certPEM +} + +func joinPEM(first []byte, rest ...[]byte) []byte { + for _, b := range rest { + first = append(first, b...) + } + + return first +} diff --git a/internal/cainjector/feature/features.go b/internal/cainjector/feature/features.go index a6852fb4812..73ca549bb0d 100644 --- a/internal/cainjector/feature/features.go +++ b/internal/cainjector/feature/features.go @@ -14,26 +14,50 @@ See the License for the specific language governing permissions and limitations under the License. */ +// feature contains cainjector feature gate setup code. Do not import this +// package into any code that's shared with other components to prevent +// overwriting other component's feature gates, see i.e +// https://github.com/cert-manager/cert-manager/issues/6011 package feature import ( + utilruntime "k8s.io/apimachinery/pkg/util/runtime" "k8s.io/component-base/featuregate" utilfeature "github.com/cert-manager/cert-manager/pkg/util/feature" ) +// see https://kubernetes.io/docs/reference/command-line-tools-reference/feature-gates/#feature-stages + const ( -// FeatureName will enable XYZ feature. -// Fill this section out with additional details about the feature. -// -// Owner (responsible for graduating feature through to GA): @username -// Alpha: vX.Y -// Beta: ... -// FeatureName featuregate.Feature = "FeatureName" + // Copy & paste the following template when you add a new feature gate: + // ========================== START TEMPLATE ========================== + // Owner: @username + // Alpha: vX.Y + // Beta: ... + // + // FeatureName will enable XYZ feature. + // Fill this section out with additional details about the feature. + // FeatureName featuregate.Feature = "FeatureName" + // =========================== END TEMPLATE =========================== + + // Owner: @inteon + // Alpha: v1.12 + // + // ServerSideApply enables the use of ServerSideApply in all API calls. + ServerSideApply featuregate.Feature = "ServerSideApply" + + // Owner: @ThatsMrTalbot + // Alpha: v1.17 + // Beta: v1.19 + // + // CAInjectorMerging changes the ca-injector to merge new certs in instead + // of replacing them outright. + CAInjectorMerging featuregate.Feature = "CAInjectorMerging" ) func init() { - utilfeature.DefaultMutableFeatureGate.Add(cainjectorFeatureGates) + utilruntime.Must(utilfeature.DefaultMutableFeatureGate.Add(cainjectorFeatureGates)) } // cainjectorFeatureGates defines all feature gates for the cainjector component. @@ -43,4 +67,7 @@ func init() { // utilfeature.DefaultFeatureGate.Enabled(feature.FeatureName) // // Where utilfeature is github.com/cert-manager/cert-manager/pkg/util/feature. -var cainjectorFeatureGates = map[featuregate.Feature]featuregate.FeatureSpec{} +var cainjectorFeatureGates = map[featuregate.Feature]featuregate.FeatureSpec{ + ServerSideApply: {Default: false, PreRelease: featuregate.Alpha}, + CAInjectorMerging: {Default: true, PreRelease: featuregate.Beta}, +} diff --git a/internal/cmd/util/exit.go b/internal/cmd/util/exit.go new file mode 100644 index 00000000000..d8488b46525 --- /dev/null +++ b/internal/cmd/util/exit.go @@ -0,0 +1,46 @@ +/* +Copyright 2021 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. +*/ + +package util + +import ( + "context" + "errors" +) + +// SetExitCode sets the exit code to 1 if the error is not a context.Canceled error. +func SetExitCode(err error) { + switch { + case err == nil || errors.Is(err, context.Canceled): + // If the context was canceled, we don't need to set the exit code + case errors.Is(err, context.DeadlineExceeded): + SetExitCodeValue(124) // Indicate that there was a timeout error + default: + SetExitCodeValue(1) // Indicate that there was an error + } +} + +// SetExitCode sets the exit code to 1 if the error is not a context.Canceled error. +func SetExitCodeValue(code int) { + if code != 0 { + select { + case errorExitCodeChannel <- code: + default: + // The exit code has already been set to a non-zero value. + } + } + // If the exit code is 0, we don't need to set the exit code +} diff --git a/internal/cmd/util/exit_test.go b/internal/cmd/util/exit_test.go new file mode 100644 index 00000000000..8882cbb2b9a --- /dev/null +++ b/internal/cmd/util/exit_test.go @@ -0,0 +1,56 @@ +/* +Copyright 2020 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. +*/ + +package util + +import ( + "context" + "errors" + "fmt" + "testing" +) + +func TestSetExitCode(t *testing.T) { + tests := []struct { + name string + err error + expCode int + }{ + {"Test context.Canceled", context.Canceled, 0}, + {"Test wrapped context.Canceled", fmt.Errorf("wrapped: %w", context.Canceled), 0}, + {"Test context.DeadlineExceeded", context.DeadlineExceeded, 124}, + {"Test wrapped context.DeadlineExceeded", fmt.Errorf("wrapped: %w", context.DeadlineExceeded), 124}, + {"Test error", errors.New("error"), 1}, + {"Test nil", nil, 0}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Every testExitCode call has to be run in its own test, because + // it calls the test again filtered by the name of the subtest with + // the variable BE_CRASHER=1. + exitCode := testExitCode(t, func(t *testing.T) { + SetExitCode(tt.err) + + _, complete := SetupExitHandler(t.Context(), AlwaysErrCode) + complete() + }) + + if exitCode != tt.expCode { + t.Errorf("Test %s: expected exit code %d, got %d", tt.name, tt.expCode, exitCode) + } + }) + } +} diff --git a/cmd/util/signal.go b/internal/cmd/util/signal.go similarity index 78% rename from cmd/util/signal.go rename to internal/cmd/util/signal.go index 00174504a07..7994417e49d 100644 --- a/cmd/util/signal.go +++ b/internal/cmd/util/signal.go @@ -17,6 +17,8 @@ limitations under the License. package util import ( + "context" + "fmt" "os" "os/signal" "syscall" @@ -40,35 +42,35 @@ const ( ) // SetupExitHandler: -// A stop channel is returned which is closed on receiving a shutdown signal (SIGTERM +// A context is returned which is canceled on receiving a shutdown signal (SIGTERM // or SIGINT). If a second signal is caught, the program is terminated directly with // exit code 130. // SetupExitHandler also returns an exit function, this exit function calls os.Exit(...) // if there is a exit code in the errorExitCodeChannel. // The errorExitCodeChannel receives exit codes when SetExitCode is called or when // a shutdown signal is received (only if exitBehavior is AlwaysErrCode). -func SetupExitHandler(exitBehavior ExitBehavior) (<-chan struct{}, func()) { +func SetupExitHandler(parentCtx context.Context, exitBehavior ExitBehavior) (context.Context, func()) { close(onlyOneSignalHandler) // panics when called twice - stop := make(chan struct{}) + ctx, cancel := context.WithCancelCause(parentCtx) c := make(chan os.Signal, 2) signal.Notify(c, shutdownSignals...) go func() { - // first signal. Close stop chan and pass exit code to exitCodeChannel. - exitCode := 128 + int((<-c).(syscall.Signal)) + // first signal. Cancel context and pass exit code to errorExitCodeChannel. + signalInt := int((<-c).(syscall.Signal)) if exitBehavior == AlwaysErrCode { - errorExitCodeChannel <- exitCode + errorExitCodeChannel <- (128 + signalInt) } - close(stop) + cancel(fmt.Errorf("received signal %d", signalInt)) // second signal. Exit directly. <-c os.Exit(130) }() - return stop, func() { + return ctx, func() { select { - case signal := <-errorExitCodeChannel: - os.Exit(signal) + case signalInt := <-errorExitCodeChannel: + os.Exit(signalInt) default: // Do not exit, there are no exit codes in the channel, // so just continue and let the main function go out of diff --git a/cmd/util/signal_posix.go b/internal/cmd/util/signal_posix.go similarity index 100% rename from cmd/util/signal_posix.go rename to internal/cmd/util/signal_posix.go diff --git a/internal/cmd/util/signal_test.go b/internal/cmd/util/signal_test.go new file mode 100644 index 00000000000..dc3237be1bd --- /dev/null +++ b/internal/cmd/util/signal_test.go @@ -0,0 +1,123 @@ +//go:build !windows + +/* +Copyright 2020 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. +*/ + +package util + +import ( + "context" + "os" + "os/exec" + "syscall" + "testing" +) + +// based on https://go.dev/talks/2014/testing.slide#23 and +// https://stackoverflow.com/a/33404435 +func testExitCode( + t *testing.T, + fn func(t *testing.T), +) int { + if os.Getenv("BE_CRASHER") == "1" { + fn(t) + os.Exit(0) + } + + cmd := exec.CommandContext(t.Context(), os.Args[0], "-test.run="+t.Name()) + cmd.Env = append(os.Environ(), "BE_CRASHER=1") + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + err := cmd.Run() + + if e, ok := err.(*exec.ExitError); ok { + return e.ExitCode() + } + + return 0 +} + +func TestSetupExitHandlerAlwaysErrCodeSIGTERM(t *testing.T) { + exitCode := testExitCode(t, func(t *testing.T) { + ctx := context.WithoutCancel(t.Context()) + ctx, complete := SetupExitHandler(ctx, AlwaysErrCode) + defer complete() + + if err := syscall.Kill(syscall.Getpid(), syscall.SIGTERM); err != nil { + t.Fatal(err) + } + + // Wait for the program to shut down. + <-ctx.Done() + + if context.Cause(ctx).Error() != "received signal 15" { + t.Errorf("expected signal 15, got %s", ctx.Err().Error()) + os.Exit(99) + } + }) + + if exitCode != 143 { + t.Errorf("expected exit code 143, got %d", exitCode) + } +} + +func TestSetupExitHandlerAlwaysErrCodeSIGINT(t *testing.T) { + exitCode := testExitCode(t, func(t *testing.T) { + ctx := context.WithoutCancel(t.Context()) + ctx, complete := SetupExitHandler(ctx, AlwaysErrCode) + defer complete() + + if err := syscall.Kill(syscall.Getpid(), syscall.SIGINT); err != nil { + t.Fatal(err) + } + + // Wait for the program to shut down. + <-ctx.Done() + + if context.Cause(ctx).Error() != "received signal 2" { + t.Errorf("expected signal 2, got %s", ctx.Err().Error()) + os.Exit(99) + } + }) + + if exitCode != 130 { + t.Errorf("expected exit code 130, got %d", exitCode) + } +} + +func TestSetupExitHandlerGracefulShutdownSIGINT(t *testing.T) { + exitCode := testExitCode(t, func(t *testing.T) { + ctx := context.WithoutCancel(t.Context()) + ctx, complete := SetupExitHandler(ctx, GracefulShutdown) + defer complete() + + if err := syscall.Kill(syscall.Getpid(), syscall.SIGINT); err != nil { + t.Fatal(err) + } + + // Wait for the program to shut down. + <-ctx.Done() + + if context.Cause(ctx).Error() != "received signal 2" { + t.Errorf("expected signal 2, got %s", ctx.Err().Error()) + os.Exit(99) + } + }) + + if exitCode != 0 { + t.Errorf("expected exit code 0, got %d", exitCode) + } +} diff --git a/cmd/util/signal_windows.go b/internal/cmd/util/signal_windows.go similarity index 100% rename from cmd/util/signal_windows.go rename to internal/cmd/util/signal_windows.go diff --git a/internal/collectors/acme_collector.go b/internal/collectors/acme_collector.go new file mode 100644 index 00000000000..a1499e4aac3 --- /dev/null +++ b/internal/collectors/acme_collector.go @@ -0,0 +1,78 @@ +/* +Copyright 2025 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. +*/ + +package collectors + +import ( + "fmt" + + "github.com/prometheus/client_golang/prometheus" + "k8s.io/apimachinery/pkg/labels" + + acmemeta "github.com/cert-manager/cert-manager/pkg/apis/acme/v1" + cmacmelisters "github.com/cert-manager/cert-manager/pkg/client/listers/acme/v1" +) + +var ( + challengeValidStatuses = [...]acmemeta.State{acmemeta.Ready, acmemeta.Valid, acmemeta.Errored, acmemeta.Expired, acmemeta.Invalid, acmemeta.Processing, acmemeta.Unknown, acmemeta.Pending} + certChallengeMetricDesc = prometheus.NewDesc("certmanager_certificate_challenge_status", "The status of certificate challenges", []string{"status", "domain", "reason", "processing", "name", "namespace", "type"}, nil) +) + +type ACMECollector struct { + challengesLister cmacmelisters.ChallengeLister + certificateChallengeStatusMetric *prometheus.Desc +} + +func NewACMECollector(acmeInformers cmacmelisters.ChallengeLister) prometheus.Collector { + return &ACMECollector{ + challengesLister: acmeInformers, + certificateChallengeStatusMetric: certChallengeMetricDesc, + } +} + +func (ac *ACMECollector) Describe(ch chan<- *prometheus.Desc) { + ch <- ac.certificateChallengeStatusMetric +} + +func (ac *ACMECollector) Collect(ch chan<- prometheus.Metric) { + challengesList, err := ac.challengesLister.List(labels.Everything()) + if err != nil { + return + } + + for _, challenge := range challengesList { + for _, status := range challengeValidStatuses { + value := 0.0 + if string(challenge.Status.State) == string(status) { + value = 1.0 + } + + metric := prometheus.MustNewConstMetric( + ac.certificateChallengeStatusMetric, prometheus.GaugeValue, + value, + string(status), + challenge.Spec.DNSName, + challenge.Status.Reason, + fmt.Sprint(challenge.Status.Processing), + challenge.Name, + challenge.Namespace, + string(challenge.Spec.Type), + ) + + ch <- metric + } + } +} diff --git a/internal/collectors/certificate_collector.go b/internal/collectors/certificate_collector.go new file mode 100644 index 00000000000..fb03158c3fa --- /dev/null +++ b/internal/collectors/certificate_collector.go @@ -0,0 +1,196 @@ +/* +Copyright 2025 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. +*/ + +package collectors + +import ( + "github.com/prometheus/client_golang/prometheus" + "k8s.io/apimachinery/pkg/labels" + + cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" + cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" + cmlisters "github.com/cert-manager/cert-manager/pkg/client/listers/certmanager/v1" +) + +var ( + certReadyConditionStatuses = [...]cmmeta.ConditionStatus{cmmeta.ConditionTrue, cmmeta.ConditionFalse, cmmeta.ConditionUnknown} + certReadyStatusMetric = prometheus.NewDesc("certmanager_certificate_ready_status", "The ready status of the certificate.", []string{"name", "namespace", "condition", "issuer_name", "issuer_kind", "issuer_group"}, nil) + certNotAfterTimeSecondMetric = prometheus.NewDesc("certmanager_certificate_not_after_timestamp_seconds", "The timestamp after which the certificate is invalid, expressed as a Unix Epoch Time.", []string{"name", "namespace", "issuer_name", "issuer_kind", "issuer_group"}, nil) + certNotBeforeTimeSecondMetric = prometheus.NewDesc("certmanager_certificate_not_before_timestamp_seconds", "The timestamp before which the certificate is invalid, expressed as a Unix Epoch Time.", []string{"name", "namespace", "issuer_name", "issuer_kind", "issuer_group"}, nil) + certExpirationTimestampSeconds = prometheus.NewDesc("certmanager_certificate_expiration_timestamp_seconds", "The timestamp after which the certificate expires, expressed in Unix Epoch Time.", []string{"name", "namespace", "issuer_name", "issuer_kind", "issuer_group"}, nil) + certRenewalTimestampSeconds = prometheus.NewDesc("certmanager_certificate_renewal_timestamp_seconds", "The timestamp after which the certificate should be renewed, expressed in Unix Epoch Time.", []string{"name", "namespace", "issuer_name", "issuer_kind", "issuer_group"}, nil) +) + +type CertificateCollector struct { + certificatesLister cmlisters.CertificateLister + certificateReadyStatusMetric *prometheus.Desc + certificateNotAfterTimeSecondMetric *prometheus.Desc + certificateNotBeforeTimeSecondMetric *prometheus.Desc + certificateExpirationTimestampSeconds *prometheus.Desc + certificateRenewalTimestampSeconds *prometheus.Desc +} + +func NewCertificateCollector(certificatesLister cmlisters.CertificateLister) prometheus.Collector { + return &CertificateCollector{ + certificatesLister: certificatesLister, + certificateReadyStatusMetric: certReadyStatusMetric, + certificateNotAfterTimeSecondMetric: certNotAfterTimeSecondMetric, + certificateNotBeforeTimeSecondMetric: certNotBeforeTimeSecondMetric, + certificateExpirationTimestampSeconds: certExpirationTimestampSeconds, + certificateRenewalTimestampSeconds: certRenewalTimestampSeconds, + } +} + +func (cc *CertificateCollector) Describe(ch chan<- *prometheus.Desc) { + ch <- cc.certificateReadyStatusMetric + ch <- cc.certificateNotAfterTimeSecondMetric + ch <- cc.certificateNotBeforeTimeSecondMetric + ch <- cc.certificateExpirationTimestampSeconds + ch <- cc.certificateRenewalTimestampSeconds +} + +func (cc *CertificateCollector) Collect(ch chan<- prometheus.Metric) { + certsList, err := cc.certificatesLister.List(labels.Everything()) + if err != nil { + return + } + + for _, cert := range certsList { + cc.updateCertificateReadyStatus(cert, ch) + cc.updateCertificateNotAfter(cert, ch) + cc.updateCertificateNotBefore(cert, ch) + cc.updateCertificateExpiry(cert, ch) + cc.updateCertificateRenewalTime(cert, ch) + } +} + +func (cc *CertificateCollector) updateCertificateReadyStatus(cert *cmapi.Certificate, ch chan<- prometheus.Metric) { + setMetric := func(cert *cmapi.Certificate, ch chan<- prometheus.Metric, status cmmeta.ConditionStatus) { + for _, condition := range certReadyConditionStatuses { + value := 0.0 + + if status == condition { + value = 1.0 + } + + metric := prometheus.MustNewConstMetric( + cc.certificateReadyStatusMetric, prometheus.GaugeValue, + value, + cert.Name, + cert.Namespace, + string(condition), + cert.Spec.IssuerRef.Name, + cert.Spec.IssuerRef.Kind, + cert.Spec.IssuerRef.Group, + ) + + ch <- metric + } + } + + for _, st := range cert.Status.Conditions { + if st.Type == cmapi.CertificateConditionReady { + setMetric(cert, ch, st.Status) + return + } + } + + setMetric(cert, ch, cmmeta.ConditionUnknown) +} + +func (cc *CertificateCollector) updateCertificateNotAfter(cert *cmapi.Certificate, ch chan<- prometheus.Metric) { + notAfterTime := 0.0 + + if cert.Status.NotAfter != nil { + notAfterTime = float64(cert.Status.NotAfter.Unix()) + } + + metric := prometheus.MustNewConstMetric( + cc.certificateNotAfterTimeSecondMetric, + prometheus.GaugeValue, + notAfterTime, + cert.Name, + cert.Namespace, + cert.Spec.IssuerRef.Name, + cert.Spec.IssuerRef.Kind, + cert.Spec.IssuerRef.Group, + ) + + ch <- metric +} + +func (cc *CertificateCollector) updateCertificateNotBefore(cert *cmapi.Certificate, ch chan<- prometheus.Metric) { + notBeforeTime := 0.0 + + if cert.Status.NotBefore != nil { + notBeforeTime = float64(cert.Status.NotBefore.Unix()) + } + + metric := prometheus.MustNewConstMetric( + cc.certificateNotBeforeTimeSecondMetric, + prometheus.GaugeValue, + notBeforeTime, + cert.Name, + cert.Namespace, + cert.Spec.IssuerRef.Name, + cert.Spec.IssuerRef.Kind, + cert.Spec.IssuerRef.Group, + ) + + ch <- metric +} + +func (cc *CertificateCollector) updateCertificateExpiry(cert *cmapi.Certificate, ch chan<- prometheus.Metric) { + expiryTime := 0.0 + + if cert.Status.NotAfter != nil { + expiryTime = float64(cert.Status.NotAfter.Unix()) + } + + metric := prometheus.MustNewConstMetric( + cc.certificateExpirationTimestampSeconds, + prometheus.GaugeValue, + expiryTime, + cert.Name, + cert.Namespace, + cert.Spec.IssuerRef.Name, + cert.Spec.IssuerRef.Kind, + cert.Spec.IssuerRef.Group, + ) + + ch <- metric +} + +func (cc *CertificateCollector) updateCertificateRenewalTime(cert *cmapi.Certificate, ch chan<- prometheus.Metric) { + renewalTime := 0.0 + + if cert.Status.RenewalTime != nil { + renewalTime = float64(cert.Status.RenewalTime.Unix()) + } + + metric := prometheus.MustNewConstMetric( + cc.certificateRenewalTimestampSeconds, + prometheus.GaugeValue, + renewalTime, + cert.Name, + cert.Namespace, + cert.Spec.IssuerRef.Name, + cert.Spec.IssuerRef.Kind, + cert.Spec.IssuerRef.Group, + ) + + ch <- metric +} diff --git a/internal/collectors/clusterissuer_collector.go b/internal/collectors/clusterissuer_collector.go new file mode 100644 index 00000000000..0ab2dd17381 --- /dev/null +++ b/internal/collectors/clusterissuer_collector.go @@ -0,0 +1,87 @@ +/* +Copyright 2025 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. +*/ + +package collectors + +import ( + "github.com/prometheus/client_golang/prometheus" + "k8s.io/apimachinery/pkg/labels" + + cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" + cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" + cmlisters "github.com/cert-manager/cert-manager/pkg/client/listers/certmanager/v1" +) + +var ( + clusterIssuerReadyConditionStatuses = [...]cmmeta.ConditionStatus{cmmeta.ConditionTrue, cmmeta.ConditionFalse, cmmeta.ConditionUnknown} + clusterIssuerReadyStatusMetric = prometheus.NewDesc("certmanager_clusterissuer_ready_status", "The ready status of the ClusterIssuer.", []string{"name", "condition"}, nil) +) + +type ClusterIssuerCollector struct { + clusterIssuersLister cmlisters.ClusterIssuerLister + clusterIssuerReadyStatusMetric *prometheus.Desc +} + +func NewClusterIssuerCollector(clusterIssuersLister cmlisters.ClusterIssuerLister) prometheus.Collector { + return &ClusterIssuerCollector{ + clusterIssuersLister: clusterIssuersLister, + clusterIssuerReadyStatusMetric: clusterIssuerReadyStatusMetric, + } +} + +func (ic *ClusterIssuerCollector) Describe(ch chan<- *prometheus.Desc) { + ch <- ic.clusterIssuerReadyStatusMetric +} + +func (ic *ClusterIssuerCollector) Collect(ch chan<- prometheus.Metric) { + clusterIssuersList, err := ic.clusterIssuersLister.List(labels.Everything()) + if err != nil { + return + } + + for _, clusterissuer := range clusterIssuersList { + ic.updateClusterIssuerReadyStatus(clusterissuer, ch) + } +} + +func (ic *ClusterIssuerCollector) updateClusterIssuerReadyStatus(clusterissuer *cmapi.ClusterIssuer, ch chan<- prometheus.Metric) { + setMetric := func(clusterissuer *cmapi.ClusterIssuer, ch chan<- prometheus.Metric, status cmmeta.ConditionStatus) { + for _, condition := range clusterIssuerReadyConditionStatuses { + value := 0.0 + + if status == condition { + value = 1.0 + } + + metric := prometheus.MustNewConstMetric( + ic.clusterIssuerReadyStatusMetric, prometheus.GaugeValue, + value, + clusterissuer.Name, + string(condition), + ) + + ch <- metric + } + } + + for _, st := range clusterissuer.Status.Conditions { + if st.Type == cmapi.IssuerConditionReady { + setMetric(clusterissuer, ch, st.Status) + return + } + } + setMetric(clusterissuer, ch, cmmeta.ConditionUnknown) +} diff --git a/internal/collectors/issuer_collector.go b/internal/collectors/issuer_collector.go new file mode 100644 index 00000000000..6cb254e4ec4 --- /dev/null +++ b/internal/collectors/issuer_collector.go @@ -0,0 +1,88 @@ +/* +Copyright 2025 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. +*/ + +package collectors + +import ( + "github.com/prometheus/client_golang/prometheus" + "k8s.io/apimachinery/pkg/labels" + + cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" + cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" + cmlisters "github.com/cert-manager/cert-manager/pkg/client/listers/certmanager/v1" +) + +var ( + issuerReadyConditionStatuses = [...]cmmeta.ConditionStatus{cmmeta.ConditionTrue, cmmeta.ConditionFalse, cmmeta.ConditionUnknown} + issuerReadyStatusMetric = prometheus.NewDesc("certmanager_issuer_ready_status", "The ready status of the Issuer.", []string{"name", "namespace", "condition"}, nil) +) + +type IssuerCollector struct { + issuersLister cmlisters.IssuerLister + issuerReadyStatusMetric *prometheus.Desc +} + +func NewIssuerCollector(issuersLister cmlisters.IssuerLister) prometheus.Collector { + return &IssuerCollector{ + issuersLister: issuersLister, + issuerReadyStatusMetric: issuerReadyStatusMetric, + } +} + +func (ic *IssuerCollector) Describe(ch chan<- *prometheus.Desc) { + ch <- ic.issuerReadyStatusMetric +} + +func (ic *IssuerCollector) Collect(ch chan<- prometheus.Metric) { + issuersList, err := ic.issuersLister.List(labels.Everything()) + if err != nil { + return + } + + for _, issuer := range issuersList { + ic.updateIssuerReadyStatus(issuer, ch) + } +} + +func (ic *IssuerCollector) updateIssuerReadyStatus(issuer *cmapi.Issuer, ch chan<- prometheus.Metric) { + setMetric := func(issuer *cmapi.Issuer, ch chan<- prometheus.Metric, status cmmeta.ConditionStatus) { + for _, condition := range issuerReadyConditionStatuses { + value := 0.0 + + if status == condition { + value = 1.0 + } + + metric := prometheus.MustNewConstMetric( + ic.issuerReadyStatusMetric, prometheus.GaugeValue, + value, + issuer.Name, + issuer.Namespace, + string(condition), + ) + + ch <- metric + } + } + + for _, st := range issuer.Status.Conditions { + if st.Type == cmapi.IssuerConditionReady { + setMetric(issuer, ch, st.Status) + return + } + } + setMetric(issuer, ch, cmmeta.ConditionUnknown) +} diff --git a/internal/controller/certificaterequests/apply.go b/internal/controller/certificaterequests/apply.go index dfd1f2df11f..6d707bbc247 100644 --- a/internal/controller/certificaterequests/apply.go +++ b/internal/controller/certificaterequests/apply.go @@ -23,7 +23,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" apitypes "k8s.io/apimachinery/pkg/types" - "k8s.io/utils/pointer" + "k8s.io/utils/ptr" cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" cmclient "github.com/cert-manager/cert-manager/pkg/client/clientset/versioned" @@ -42,7 +42,7 @@ func Apply(ctx context.Context, cl cmclient.Interface, fieldManager string, req return cl.CertmanagerV1().CertificateRequests(req.Namespace).Patch( ctx, req.Name, apitypes.ApplyPatchType, reqData, - metav1.PatchOptions{Force: pointer.Bool(true), FieldManager: fieldManager}) + metav1.PatchOptions{Force: ptr.To(true), FieldManager: fieldManager}) } // ApplyStatus will make an Apply API call with the given client to the @@ -59,7 +59,7 @@ func ApplyStatus(ctx context.Context, cl cmclient.Interface, fieldManager string _, err = cl.CertmanagerV1().CertificateRequests(req.Namespace).Patch( ctx, req.Name, apitypes.ApplyPatchType, reqData, - metav1.PatchOptions{Force: pointer.Bool(true), FieldManager: fieldManager}, "status", + metav1.PatchOptions{Force: ptr.To(true), FieldManager: fieldManager}, "status", ) return err diff --git a/internal/controller/certificaterequests/apply_test.go b/internal/controller/certificaterequests/apply_test.go index c452ccbc815..69b34cef5c5 100644 --- a/internal/controller/certificaterequests/apply_test.go +++ b/internal/controller/certificaterequests/apply_test.go @@ -22,8 +22,8 @@ import ( "sync" "testing" - fuzz "github.com/google/gofuzz" "github.com/stretchr/testify/assert" + "sigs.k8s.io/randfill" cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" ) @@ -45,12 +45,12 @@ func Test_serializeApply(t *testing.T) { jobs := make(chan int) wg.Add(numJobs) - for i := 0; i < 3; i++ { + for range 3 { go func() { for j := range jobs { t.Run("fuzz_"+strconv.Itoa(j), func(t *testing.T) { var req cmapi.CertificateRequest - fuzz.New().NilChance(0.5).Fuzz(&req) + randfill.New().NilChance(0.5).Fill(&req) // Test regex with non-empty spec. reqData, err := serializeApply(&req) @@ -74,7 +74,7 @@ func Test_serializeApply(t *testing.T) { }() } - for i := 0; i < numJobs; i++ { + for i := range numJobs { jobs <- i } close(jobs) @@ -91,8 +91,8 @@ func Test_serializeApplyStatus(t *testing.T) { // meta/type object, empty spec. Status should be matched both via regex, and // when empty. const ( - expReg = `^{"kind":"CertificateRequest","apiVersion":"cert-manager.io/v1","metadata":{"name":"foo","namespace":"bar","creationTimestamp":null},"spec":{"issuerRef":{"name":""},"request":null},"status":{.*}}$` - expEmpty = `{"kind":"CertificateRequest","apiVersion":"cert-manager.io/v1","metadata":{"name":"foo","namespace":"bar","creationTimestamp":null},"spec":{"issuerRef":{"name":""},"request":null},"status":{}}` + expReg = `^{"kind":"CertificateRequest","apiVersion":"cert-manager.io/v1","metadata":{"name":"foo","namespace":"bar"},"spec":{"issuerRef":{"name":""},"request":null},"status":{.*}}$` + expEmpty = `{"kind":"CertificateRequest","apiVersion":"cert-manager.io/v1","metadata":{"name":"foo","namespace":"bar"},"spec":{"issuerRef":{"name":""},"request":null},"status":{}}` numJobs = 10000 ) @@ -100,12 +100,12 @@ func Test_serializeApplyStatus(t *testing.T) { jobs := make(chan int) wg.Add(numJobs) - for i := 0; i < 3; i++ { + for range 3 { go func() { for j := range jobs { t.Run("fuzz_"+strconv.Itoa(j), func(t *testing.T) { var req cmapi.CertificateRequest - fuzz.New().NilChance(0.5).Fuzz(&req) + randfill.New().NilChance(0.5).Fill(&req) req.Name = "foo" req.Namespace = "bar" @@ -131,7 +131,7 @@ func Test_serializeApplyStatus(t *testing.T) { }() } - for i := 0; i < numJobs; i++ { + for i := range numJobs { jobs <- i } close(jobs) diff --git a/internal/controller/certificates/apply.go b/internal/controller/certificates/apply.go index a3f09effc35..ae20a3b325d 100644 --- a/internal/controller/certificates/apply.go +++ b/internal/controller/certificates/apply.go @@ -23,13 +23,13 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" apitypes "k8s.io/apimachinery/pkg/types" - "k8s.io/utils/pointer" + "k8s.io/utils/ptr" cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" cmclient "github.com/cert-manager/cert-manager/pkg/client/clientset/versioned" ) -// Apply will make a Apply API call with the given client to the certificates +// Apply will make an Apply API call with the given client to the certificates // resource endpoint. All data in the given Certificate's status field is // dropped. // The given fieldManager is will be used as the FieldManager in the Apply @@ -43,7 +43,7 @@ func Apply(ctx context.Context, cl cmclient.Interface, fieldManager string, crt _, err = cl.CertmanagerV1().Certificates(crt.Namespace).Patch( ctx, crt.Name, apitypes.ApplyPatchType, crtData, - metav1.PatchOptions{Force: pointer.Bool(true), FieldManager: fieldManager}, + metav1.PatchOptions{Force: ptr.To(true), FieldManager: fieldManager}, ) return err @@ -62,7 +62,7 @@ func ApplyStatus(ctx context.Context, cl cmclient.Interface, fieldManager string _, err = cl.CertmanagerV1().Certificates(crt.Namespace).Patch( ctx, crt.Name, apitypes.ApplyPatchType, crtData, - metav1.PatchOptions{Force: pointer.Bool(true), FieldManager: fieldManager}, "status", + metav1.PatchOptions{Force: ptr.To(true), FieldManager: fieldManager}, "status", ) return err diff --git a/internal/controller/certificates/apply_test.go b/internal/controller/certificates/apply_test.go index 0928b290898..655f6d45b95 100644 --- a/internal/controller/certificates/apply_test.go +++ b/internal/controller/certificates/apply_test.go @@ -22,8 +22,8 @@ import ( "sync" "testing" - fuzz "github.com/google/gofuzz" "github.com/stretchr/testify/assert" + "sigs.k8s.io/randfill" cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" ) @@ -38,12 +38,12 @@ func Test_serializeApply(t *testing.T) { jobs := make(chan int) wg.Add(numJobs) - for i := 0; i < 3; i++ { + for range 3 { go func() { for j := range jobs { t.Run("fuzz_"+strconv.Itoa(j), func(t *testing.T) { var crt cmapi.Certificate - fuzz.New().NilChance(0.5).Fuzz(&crt) + randfill.New().NilChance(0.5).Fill(&crt) crt.ManagedFields = nil crtData, err := serializeApply(&crt) @@ -61,7 +61,7 @@ func Test_serializeApply(t *testing.T) { }() } - for i := 0; i < numJobs; i++ { + for i := range numJobs { jobs <- i } close(jobs) @@ -78,8 +78,8 @@ func Test_serializeApplyStatus(t *testing.T) { // meta/type object, empty spec. Status should be matched both via regex, and // when empty. const ( - expReg = `^{"kind":"Certificate","apiVersion":"cert-manager.io/v1","metadata":{"name":"foo","namespace":"bar","creationTimestamp":null},"spec":{"secretName":"","issuerRef":{"name":""}},"status":{.*}$` - expEmpty = `{"kind":"Certificate","apiVersion":"cert-manager.io/v1","metadata":{"name":"foo","namespace":"bar","creationTimestamp":null},"spec":{"secretName":"","issuerRef":{"name":""}},"status":{}}` + expReg = `^{"kind":"Certificate","apiVersion":"cert-manager.io/v1","metadata":{"name":"foo","namespace":"bar"},"spec":{"secretName":"","issuerRef":{"name":""}},"status":{.*}$` + expEmpty = `{"kind":"Certificate","apiVersion":"cert-manager.io/v1","metadata":{"name":"foo","namespace":"bar"},"spec":{"secretName":"","issuerRef":{"name":""}},"status":{}}` numJobs = 10000 ) @@ -87,12 +87,12 @@ func Test_serializeApplyStatus(t *testing.T) { jobs := make(chan int) wg.Add(numJobs) - for i := 0; i < 3; i++ { + for range 3 { go func() { for j := range jobs { t.Run("fuzz_"+strconv.Itoa(j), func(t *testing.T) { var crt cmapi.Certificate - fuzz.New().NilChance(0.5).Fuzz(&crt) + randfill.New().NilChance(0.5).Fill(&crt) crt.Name = "foo" crt.Namespace = "bar" @@ -118,7 +118,7 @@ func Test_serializeApplyStatus(t *testing.T) { }() } - for i := 0; i < numJobs; i++ { + for i := range numJobs { jobs <- i } close(jobs) diff --git a/internal/controller/certificates/certificates.go b/internal/controller/certificates/certificates.go new file mode 100644 index 00000000000..e2f1bfed998 --- /dev/null +++ b/internal/controller/certificates/certificates.go @@ -0,0 +1,113 @@ +/* +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. +*/ + +package certificates + +import ( + "context" + "slices" + "strings" + + apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/labels" + + internalinformers "github.com/cert-manager/cert-manager/internal/informers" + cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" + cmlisters "github.com/cert-manager/cert-manager/pkg/client/listers/certmanager/v1" +) + +// We determine whether a Certificate owns its Secret in order to prevent a CertificateRequest +// creation runaway. We use an annotation on the Secret to determine whether it is owned by a +// Certificate. We do not use the ownerReferences field on the Secret because the owner reference +// will not be set if the `--enable-certificate-owner-ref` flag is not set. +// +// We determine if the passed Certificate owns its Secret as follows: +// 1. If the target Secret exists and it is annotated with the name of this +// Certificate, then this Certificate is the owner. +// 2. If the target Secret exists and it is annotated with the name of another +// Certificate that has the Secret as its secretRef, then that Certificate +// is the owner instead. +// 3. If the target Secret exists and it is not annotated with the name of any +// Certificate, or it is annotated with the name of a Certificate that does +// not exist, or does not have the Secret as its secretRef, then the oldest +// Certificate which references it will be assumed to be the future owner. +func CertificateOwnsSecret( + ctx context.Context, + certificateLister cmlisters.CertificateLister, + secretLister internalinformers.SecretLister, + crt *cmapi.Certificate, +) (bool, []string, error) { + crts, err := certificateLister.Certificates(crt.Namespace).List(labels.Everything()) + if err != nil { + return false, nil, err + } + + var duplicateCrts []*cmapi.Certificate + for _, namespaceCrt := range crts { + // Check if it has the same Secret. + if namespaceCrt.Spec.SecretName == crt.Spec.SecretName { + // If it does, mark the Certificate as having a duplicate Secret. + duplicateCrts = append(duplicateCrts, namespaceCrt) + } + } + + // If there are no duplicates, return early. + if len(duplicateCrts) == 1 && duplicateCrts[0].Name == crt.Name { + return true, nil, nil + } + + slices.SortFunc(duplicateCrts, func(a, b *cmapi.Certificate) int { + switch { + case a.CreationTimestamp.Equal(&b.CreationTimestamp): + // If both Certificates were created at the same time, compare + // the names of the Certificates instead. + return strings.Compare(a.Name, b.Name) + case a.CreationTimestamp.Before(&b.CreationTimestamp): + // a was created before b + return -1 + default: + // b was created before a + return 1 + } + }) + + duplicateNames := make([]string, len(duplicateCrts)) + for i, duplicateCrt := range duplicateCrts { + duplicateNames[i] = duplicateCrt.Name + } + + // If the Secret does not exist, only the first Certificate in the list + // is the owner of the Secret. + ownerCertificate := duplicateNames[0] + + // Fetch the Secret and determine if it is owned by any of the Certificates. + secret, err := secretLister.Secrets(crt.Namespace).Get(crt.Spec.SecretName) + if err != nil && !apierrors.IsNotFound(err) { + return false, nil, err + } else if err == nil { + if annotation, hasAnnotation := secret.GetAnnotations()[cmapi.CertificateNameKey]; hasAnnotation && slices.Contains(duplicateNames, annotation) { + ownerCertificate = annotation + } + } + + // Return true in case the passed crt is the owner. + // Additionally, return the names of all other certificates that have the same SecretName value set. + isOwner := crt.Name == ownerCertificate + otherCertificatesWithSameSecretName := slices.DeleteFunc(duplicateNames, func(s string) bool { + return s == crt.Name + }) + return isOwner, otherCertificatesWithSameSecretName, nil +} diff --git a/internal/controller/certificates/certificates_test.go b/internal/controller/certificates/certificates_test.go new file mode 100644 index 00000000000..695f9226c3a --- /dev/null +++ b/internal/controller/certificates/certificates_test.go @@ -0,0 +1,203 @@ +/* +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. +*/ + +package certificates + +import ( + "testing" + "time" + + "github.com/stretchr/testify/assert" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + corev1listers "k8s.io/client-go/listers/core/v1" + "k8s.io/client-go/tools/cache" + + cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" + cmv1listers "github.com/cert-manager/cert-manager/pkg/client/listers/certmanager/v1" +) + +func TestCertificateOwnsSecret(t *testing.T) { + testNamespace := "test-namespace" + testSecretName := "test-secret" + testCreationTimestamp := time.Now() + + certificate := func(name string, creationTimestamp time.Time) *cmapi.Certificate { + return &cmapi.Certificate{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: testNamespace, + CreationTimestamp: metav1.Time{Time: creationTimestamp}, + }, + Spec: cmapi.CertificateSpec{ + SecretName: testSecretName, + }, + } + } + + tests := []struct { + name string + + selectedCertificate string + secrets []runtime.Object + certificates []runtime.Object + + expectedResult bool + expectedOtherOwners []string + expectedError error + }{ + { + name: "Certificate is only cert referencing the secret", + + selectedCertificate: "certificate-1", + secrets: []runtime.Object{}, + certificates: []runtime.Object{ + certificate("certificate-1", testCreationTimestamp), + }, + + expectedResult: true, + expectedOtherOwners: nil, + expectedError: nil, + }, + { + name: "Certificate has conflict, but is the oldest", + + selectedCertificate: "certificate-3", + secrets: []runtime.Object{}, + certificates: []runtime.Object{ + certificate("certificate-3", testCreationTimestamp), + certificate("certificate-2", testCreationTimestamp.Add(1*time.Second)), + certificate("certificate-1", testCreationTimestamp.Add(1*time.Second)), + }, + + expectedResult: true, + expectedOtherOwners: []string{"certificate-1", "certificate-2"}, + expectedError: nil, + }, + { + name: "Certificate has conflict, but has alphabetically lower name", + + selectedCertificate: "certificate-1", + secrets: []runtime.Object{}, + certificates: []runtime.Object{ + certificate("certificate-1", testCreationTimestamp), + certificate("certificate-2", testCreationTimestamp), + certificate("certificate-3", testCreationTimestamp), + }, + + expectedResult: true, + expectedOtherOwners: []string{"certificate-2", "certificate-3"}, + expectedError: nil, + }, + { + name: "Certificate has conflict, but annotation marks it as the owner", + + selectedCertificate: "certificate-3", + secrets: []runtime.Object{ + &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: testSecretName, + Namespace: testNamespace, + Annotations: map[string]string{ + cmapi.CertificateNameKey: "certificate-3", + }, + }, + }, + }, + certificates: []runtime.Object{ + certificate("certificate-1", testCreationTimestamp), + certificate("certificate-2", testCreationTimestamp), + certificate("certificate-3", testCreationTimestamp), + }, + + expectedResult: true, + expectedOtherOwners: []string{"certificate-1", "certificate-2"}, + expectedError: nil, + }, + { + name: "Certificate has conflict, is the oldest, but annotation marks another as the owner", + + selectedCertificate: "certificate-3", + secrets: []runtime.Object{ + &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: testSecretName, + Namespace: testNamespace, + Annotations: map[string]string{ + cmapi.CertificateNameKey: "certificate-2", + }, + }, + }, + }, + certificates: []runtime.Object{ + certificate("certificate-3", testCreationTimestamp), + certificate("certificate-2", testCreationTimestamp.Add(1*time.Second)), + certificate("certificate-1", testCreationTimestamp.Add(1*time.Second)), + }, + + expectedResult: false, + expectedOtherOwners: []string{"certificate-1", "certificate-2"}, + expectedError: nil, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Create a fake certificate lister + certIndexer := cache.NewIndexer(cache.MetaNamespaceKeyFunc, cache.Indexers{ + cache.NamespaceIndex: cache.MetaNamespaceIndexFunc, + }) + for _, crt := range tt.certificates { + if err := certIndexer.Add(crt); err != nil { + t.Fatal(err) + } + } + certificateLister := cmv1listers.NewCertificateLister(certIndexer) + + // Create a fake secret lister + secretIndexer := cache.NewIndexer(cache.MetaNamespaceKeyFunc, cache.Indexers{ + cache.NamespaceIndex: cache.MetaNamespaceIndexFunc, + }) + for _, secret := range tt.secrets { + if err := secretIndexer.Add(secret); err != nil { + t.Fatal(err) + } + } + secretLister := corev1listers.NewSecretLister(secretIndexer) + + // Find the selected Certificate + var selectedCrt *cmapi.Certificate + for _, crt := range tt.certificates { + if crt.(*cmapi.Certificate).Name == tt.selectedCertificate { + selectedCrt = crt.(*cmapi.Certificate) + break + } + } + if selectedCrt == nil { + t.Fatal("failed to find selected Certificate") + } + + // Call the function under test + result, owners, err := CertificateOwnsSecret(t.Context(), certificateLister, secretLister, selectedCrt) + + // Verify the result + assert.Equal(t, tt.expectedResult, result) + assert.Equal(t, tt.expectedOtherOwners, owners) + assert.Equal(t, tt.expectedError, err) + }) + } +} diff --git a/internal/controller/certificates/policies/checks.go b/internal/controller/certificates/policies/checks.go index 52d47d1ab13..02d6fa9800f 100644 --- a/internal/controller/certificates/policies/checks.go +++ b/internal/controller/certificates/policies/checks.go @@ -18,9 +18,10 @@ package policies import ( "bytes" - "crypto/tls" + "cmp" "crypto/x509" "fmt" + "slices" "strings" "time" @@ -29,13 +30,13 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/sets" "k8s.io/utils/clock" - "k8s.io/utils/pointer" - "sigs.k8s.io/structured-merge-diff/v4/fieldpath" - "sigs.k8s.io/structured-merge-diff/v4/value" + "k8s.io/utils/ptr" + "sigs.k8s.io/structured-merge-diff/v6/fieldpath" + "sigs.k8s.io/structured-merge-diff/v6/value" + cmmeta "github.com/cert-manager/cert-manager/internal/apis/meta" internalcertificates "github.com/cert-manager/cert-manager/internal/controller/certificates" cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" - "github.com/cert-manager/cert-manager/pkg/controller/certificates" "github.com/cert-manager/cert-manager/pkg/util/pki" ) @@ -62,51 +63,155 @@ func SecretIsMissingData(input Input) (string, string, bool) { } func SecretPublicKeysDiffer(input Input) (string, string, bool) { - pkData := input.Secret.Data[corev1.TLSPrivateKeyKey] - certData := input.Secret.Data[corev1.TLSCertKey] - // TODO: replace this with a generic decoder that can handle different - // formats such as JKS, P12 etc (i.e. add proper support for keystores) - _, err := tls.X509KeyPair(certData, pkData) + pk, err := pki.DecodePrivateKeyBytes(input.Secret.Data[corev1.TLSPrivateKeyKey]) if err != nil { - return InvalidKeyPair, fmt.Sprintf("Issuing certificate as Secret contains an invalid key-pair: %v", err), true + return InvalidKeyPair, fmt.Sprintf("Issuing certificate as Secret contains invalid private key data: %v", err), true } - return "", "", false -} - -func SecretPrivateKeyMatchesSpec(input Input) (string, string, bool) { - if input.Secret.Data == nil || len(input.Secret.Data[corev1.TLSPrivateKeyKey]) == 0 { - return SecretMismatch, "Existing issued Secret does not contain private key data", true + x509Cert, err := pki.DecodeX509CertificateBytes(input.Secret.Data[corev1.TLSCertKey]) + if err != nil { + return InvalidCertificate, fmt.Sprintf("Issuing certificate as Secret contains an invalid certificate: %v", err), true } - pkBytes := input.Secret.Data[corev1.TLSPrivateKeyKey] - pk, err := pki.DecodePrivateKeyBytes(pkBytes) + equal, err := pki.PublicKeysEqual(x509Cert.PublicKey, pk.Public()) if err != nil { - return SecretMismatch, fmt.Sprintf("Existing issued Secret contains invalid private key data: %v", err), true + return InvalidKeyPair, fmt.Sprintf("Secret contains an invalid key-pair: %v", err), true + } + if !equal { + return InvalidKeyPair, "Issuing certificate as Secret contains a private key that does not match the certificate", true } - violations, err := certificates.PrivateKeyMatchesSpec(pk, input.Certificate.Spec) + return "", "", false +} + +func SecretPrivateKeyMismatchesSpec(input Input) (string, string, bool) { + pk, err := pki.DecodePrivateKeyBytes(input.Secret.Data[corev1.TLSPrivateKeyKey]) if err != nil { - return SecretMismatch, fmt.Sprintf("Failed to check private key is up to date: %v", err), true + return InvalidKeyPair, fmt.Sprintf("Issuing certificate as Secret contains invalid private key data: %v", err), true } + + violations := pki.PrivateKeyMatchesSpec(pk, input.Certificate.Spec) if len(violations) > 0 { return SecretMismatch, fmt.Sprintf("Existing private key is not up to date for spec: %v", violations), true } return "", "", false } -func SecretIssuerAnnotationsNotUpToDate(input Input) (string, string, bool) { - name := input.Secret.Annotations[cmapi.IssuerNameAnnotationKey] - kind := input.Secret.Annotations[cmapi.IssuerKindAnnotationKey] - group := input.Secret.Annotations[cmapi.IssuerGroupAnnotationKey] - if name != input.Certificate.Spec.IssuerRef.Name || +// SecretKeystoreFormatMismatch - When the keystore is not defined, the keystore +// related fields are removed from the secret. +// When one or more key stores are defined, the +// corresponding secrets are generated. +// If the private key rotation is set to "Never", the key store related values are re-encoded +// as per the certificate specification +func SecretKeystoreFormatMismatch(input Input) (string, string, bool) { + _, issuerProvidesCA := input.Secret.Data[cmmeta.TLSCAKey] + + if input.Certificate.Spec.Keystores == nil { + if len(input.Secret.Data[cmapi.PKCS12SecretKey]) != 0 || + len(input.Secret.Data[cmapi.PKCS12TruststoreKey]) != 0 || + len(input.Secret.Data[cmapi.JKSSecretKey]) != 0 || + len(input.Secret.Data[cmapi.JKSTruststoreKey]) != 0 { + return SecretMismatch, "Keystore is not defined", true + } + return "", "", false + } + + if input.Certificate.Spec.Keystores.JKS != nil { + if input.Certificate.Spec.Keystores.JKS.Create { + if len(input.Secret.Data[cmapi.JKSSecretKey]) == 0 || + (len(input.Secret.Data[cmapi.JKSTruststoreKey]) == 0 && issuerProvidesCA) { + return SecretMismatch, "JKS Keystore key does not contain data", true + } + } else { + if len(input.Secret.Data[cmapi.JKSSecretKey]) != 0 || + len(input.Secret.Data[cmapi.JKSTruststoreKey]) != 0 { + return SecretMismatch, "JKS Keystore create disabled", true + } + } + } else { + if len(input.Secret.Data[cmapi.JKSSecretKey]) != 0 || + len(input.Secret.Data[cmapi.JKSTruststoreKey]) != 0 { + return SecretMismatch, "JKS Keystore not defined", true + } + } + + if input.Certificate.Spec.Keystores.PKCS12 != nil { + if input.Certificate.Spec.Keystores.PKCS12.Create { + if len(input.Secret.Data[cmapi.PKCS12SecretKey]) == 0 || + (len(input.Secret.Data[cmapi.PKCS12TruststoreKey]) == 0 && issuerProvidesCA) { + return SecretMismatch, "PKCS12 Keystore key does not contain data", true + } + } else { + if len(input.Secret.Data[cmapi.PKCS12SecretKey]) != 0 || + len(input.Secret.Data[cmapi.PKCS12TruststoreKey]) != 0 { + return SecretMismatch, "PKCS12 Keystore create disabled", true + } + } + } else { + if len(input.Secret.Data[cmapi.PKCS12SecretKey]) != 0 || + len(input.Secret.Data[cmapi.PKCS12TruststoreKey]) != 0 { + return SecretMismatch, "PKCS12 Keystore not defined", true + } + } + + return "", "", false +} + +// SecretIssuerAnnotationsMismatch - When the issuer annotations are defined, +// it must match the issuer ref. +func SecretIssuerAnnotationsMismatch(input Input) (string, string, bool) { + name, ok1 := input.Secret.Annotations[cmapi.IssuerNameAnnotationKey] + kind, ok2 := input.Secret.Annotations[cmapi.IssuerKindAnnotationKey] + group, ok3 := input.Secret.Annotations[cmapi.IssuerGroupAnnotationKey] + if (ok1 || ok2 || ok3) && // only check if an annotation is present + name != input.Certificate.Spec.IssuerRef.Name || !issuerKindsEqual(kind, input.Certificate.Spec.IssuerRef.Kind) || !issuerGroupsEqual(group, input.Certificate.Spec.IssuerRef.Group) { - return IncorrectIssuer, fmt.Sprintf("Issuing certificate as Secret was previously issued by %s", formatIssuerRef(name, kind, group)), true + return IncorrectIssuer, fmt.Sprintf("Issuing certificate as Secret was previously issued by %q", formatIssuerRef(name, kind, group)), true } return "", "", false } -func CurrentCertificateRequestNotValidForSpec(input Input) (string, string, bool) { +// SecretCertificateNameAnnotationsMismatch - When the CertificateName annotation is defined, +// it must match the name of the Certificate. +func SecretCertificateNameAnnotationsMismatch(input Input) (string, string, bool) { + name, ok := input.Secret.Annotations[cmapi.CertificateNameKey] + if (ok) && // only check if an annotation is present + name != input.Certificate.Name { + return IncorrectCertificate, fmt.Sprintf("Secret was issued for %q. If this message is not transient, you might have two conflicting Certificates pointing to the same secret.", name), true + } + return "", "", false +} + +// SecretPublicKeyDiffersFromCurrentCertificateRequest checks that the current CertificateRequest +// contains a CSR that is signed by the key stored in the Secret. A failure is often caused by the +// Secret being changed outside of the control of cert-manager, causing the current CertificateRequest +// to no longer match what is stored in the Secret. +func SecretPublicKeyDiffersFromCurrentCertificateRequest(input Input) (string, string, bool) { + if input.CurrentRevisionRequest == nil { + return "", "", false + } + pk, err := pki.DecodePrivateKeyBytes(input.Secret.Data[corev1.TLSPrivateKeyKey]) + if err != nil { + return InvalidKeyPair, fmt.Sprintf("Issuing certificate as Secret contains invalid private key data: %v", err), true + } + + csr, err := pki.DecodeX509CertificateRequestBytes(input.CurrentRevisionRequest.Spec.Request) + if err != nil { + return InvalidCertificateRequest, fmt.Sprintf("Failed to decode current CertificateRequest: %v", err), true + } + + equal, err := pki.PublicKeysEqual(csr.PublicKey, pk.Public()) + if err != nil { + return InvalidCertificateRequest, fmt.Sprintf("CertificateRequest's public key is invalid: %v", err), true + } + if !equal { + return SecretMismatch, "Secret contains a private key that does not match the current CertificateRequest", true + } + + return "", "", false +} + +func CurrentCertificateRequestMismatchesSpec(input Input) (string, string, bool) { if input.CurrentRevisionRequest == nil { // Fallback to comparing the Certificate spec with the issued certificate. // This case is encountered if the CertificateRequest that issued the current @@ -117,7 +222,7 @@ func CurrentCertificateRequestNotValidForSpec(input Input) (string, string, bool return currentSecretValidForSpec(input) } - violations, err := certificates.RequestMatchesSpec(input.CurrentRevisionRequest, input.Certificate.Spec) + violations, err := pki.RequestMatchesSpec(input.CurrentRevisionRequest, input.Certificate.Spec) if err != nil { // If parsing the request fails, we don't immediately trigger a re-issuance as // the existing certificate stored in the Secret may still be valid/up to date. @@ -134,16 +239,14 @@ func CurrentCertificateRequestNotValidForSpec(input Input) (string, string, bool // and is instead called by currentCertificateRequestValidForSpec if no there // is no existing CertificateRequest resource. func currentSecretValidForSpec(input Input) (string, string, bool) { - violations, err := certificates.SecretDataAltNamesMatchSpec(input.Secret, input.Certificate.Spec) + x509Cert, err := pki.DecodeX509CertificateBytes(input.Secret.Data[corev1.TLSCertKey]) if err != nil { - // This case should never be reached as we already check the certificate data can - // be parsed in an earlier policy check, but handle it anyway. - // TODO: log a message - return "", "", false + return InvalidCertificate, fmt.Sprintf("Issuing certificate as Secret contains an invalid certificate: %v", err), true } - + // nolint: staticcheck // FuzzyX509AltNamesMatchSpec is used here for backwards compatibility + violations := pki.FuzzyX509AltNamesMatchSpec(x509Cert, input.Certificate.Spec) if len(violations) > 0 { - return SecretMismatch, fmt.Sprintf("Existing issued Secret is not up to date for spec: %v", violations), true + return SecretMismatch, fmt.Sprintf("Issuing certificate as Existing issued Secret is not up to date for spec: %v", violations), true } return "", "", false @@ -153,28 +256,25 @@ func currentSecretValidForSpec(input Input) (string, string, bool) { // check whether an X.509 cert currently issued for a Certificate should be // renewed. func CurrentCertificateNearingExpiry(c clock.Clock) Func { - return func(input Input) (string, string, bool) { + x509Cert, err := pki.DecodeX509CertificateBytes(input.Secret.Data[corev1.TLSCertKey]) + if err != nil { + return InvalidCertificate, fmt.Sprintf("Issuing certificate as Secret contains an invalid certificate: %v", err), true + } // Determine if the certificate is nearing expiry solely by looking at // the actual cert, if it exists. We assume that at this point we have // called policy functions that check that input.Secret and // input.Secret.Data exists (SecretDoesNotExist and SecretIsMissingData). - x509cert, err := pki.DecodeX509CertificateBytes(input.Secret.Data[corev1.TLSCertKey]) - if err != nil { - // This case should never happen as it should always be caught by the - // secretPublicKeysMatch function beforehand, but handle it just in case. - return InvalidCertificate, fmt.Sprintf("Failed to decode stored certificate: %v", err), true - } - notBefore := metav1.NewTime(x509cert.NotBefore) - notAfter := metav1.NewTime(x509cert.NotAfter) + notBefore := metav1.NewTime(x509Cert.NotBefore) + notAfter := metav1.NewTime(x509Cert.NotAfter) crt := input.Certificate - renewalTime := certificates.RenewalTime(notBefore.Time, notAfter.Time, crt.Spec.RenewBefore) + renewalTime := pki.RenewalTime(notBefore.Time, notAfter.Time, crt.Spec.RenewBefore, crt.Spec.RenewBeforePercentage) renewIn := renewalTime.Time.Sub(c.Now()) if renewIn > 0 { - //renewal time is in future, no need to renew + // renewal time is in the future, no need to renew return "", "", false } @@ -186,21 +286,13 @@ func CurrentCertificateNearingExpiry(c clock.Clock) Func { // issued certificate has actually expired rather than just nearing expiry. func CurrentCertificateHasExpired(c clock.Clock) Func { return func(input Input) (string, string, bool) { - certData, ok := input.Secret.Data[corev1.TLSCertKey] - if !ok { - return MissingData, "Missing Certificate data", true - } - // TODO: replace this with a generic decoder that can handle different - // formats such as JKS, P12 etc (i.e. add proper support for keystores) - cert, err := pki.DecodeX509CertificateBytes(certData) + x509Cert, err := pki.DecodeX509CertificateBytes(input.Secret.Data[corev1.TLSCertKey]) if err != nil { - // This case should never happen as it should always be caught by the - // secretPublicKeysMatch function beforehand, but handle it just in case. - return InvalidCertificate, fmt.Sprintf("Failed to decode stored certificate: %v", err), true + return InvalidCertificate, fmt.Sprintf("Issuing certificate as Secret contains an invalid certificate: %v", err), true } - if c.Now().After(cert.NotAfter) { - return Expired, fmt.Sprintf("Certificate expired on %s", cert.NotAfter.Format(time.RFC1123)), true + if c.Now().After(x509Cert.NotAfter) { + return Expired, fmt.Sprintf("Certificate expired on %s", x509Cert.NotAfter.Format(time.RFC1123)), true } return "", "", false } @@ -239,145 +331,279 @@ func issuerGroupsEqual(l, r string) bool { return l == r } -// SecretTemplateMismatchesSecret will inspect the given Secret's Annotations +// SecretSecretTemplateMismatch will inspect the given Secret's Annotations // and Labels, and compare these maps against those that appear on the given // Certificate's SecretTemplate. -// Returns false if all the Certificate's SecretTemplate Annotations and Labels -// appear on the Secret, or put another way, the Certificate's SecretTemplate -// is a subset of that in the Secret's Annotations/Labels. -// Returns true otherwise. -func SecretTemplateMismatchesSecret(input Input) (string, string, bool) { +// NOTE: This function only compares the values of annotations and labels that +// exist both in the Certificate's SecretTemplate and the Secret. Missing and +// extra annotations or labels are detected by the SecretManagedLabelsAndAnnotationsManagedFieldsMismatch +// and SecretSecretTemplateManagedFieldsMismatch functions instead. +func SecretSecretTemplateMismatch(input Input) (string, string, bool) { if input.Certificate.Spec.SecretTemplate == nil { return "", "", false } - for kSpec, vSpec := range input.Certificate.Spec.SecretTemplate.Annotations { - if v, ok := input.Secret.Annotations[kSpec]; !ok || v != vSpec { - return SecretTemplateMismatch, "Certificate's SecretTemplate Annotations missing or incorrect value on Secret", true + if match, _ := mapsHaveMatchingValues(input.Certificate.Spec.SecretTemplate.Annotations, input.Secret.Annotations); !match { + return SecretTemplateMismatch, "Certificate's SecretTemplate Annotations missing or incorrect value on Secret", true + } + + if match, _ := mapsHaveMatchingValues(input.Certificate.Spec.SecretTemplate.Labels, input.Secret.Labels); !match { + return SecretTemplateMismatch, "Certificate's SecretTemplate Labels missing or incorrect value on Secret", true + } + + return "", "", false +} + +func certificateDataAnnotationsForSecret(secret *corev1.Secret) (annotations map[string]string, err error) { + var certificate *x509.Certificate + if len(secret.Data[corev1.TLSCertKey]) > 0 { + certificate, err = pki.DecodeX509CertificateBytes(secret.Data[corev1.TLSCertKey]) + if err != nil { + return nil, err } } - for kSpec, vSpec := range input.Certificate.Spec.SecretTemplate.Labels { - if v, ok := input.Secret.Labels[kSpec]; !ok || v != vSpec { - return SecretTemplateMismatch, "Certificate's SecretTemplate Labels missing or incorrect value on Secret", true + certificateAnnotations, err := internalcertificates.AnnotationsForCertificate(certificate) + if err != nil { + return nil, err + } + + return certificateAnnotations, nil +} + +func secretLabelsAndAnnotationsManagedFields(secret *corev1.Secret, fieldManager string) (labels, annotations sets.Set[string], err error) { + managedLabels, managedAnnotations := sets.New[string](), sets.New[string]() + + for _, managedField := range secret.ManagedFields { + // If the managed field isn't owned by the cert-manager controller, ignore. + if managedField.Manager != fieldManager || managedField.FieldsV1 == nil { + continue } + + // Decode the managed field. + var fieldset fieldpath.Set + if err := fieldset.FromJSON(bytes.NewReader(managedField.FieldsV1.Raw)); err != nil { + return nil, nil, err + } + + // Extract the labels and annotations of the managed fields. + metadata := fieldset.Children.Descend(fieldpath.PathElement{ + FieldName: ptr.To("metadata"), + }) + labels := metadata.Children.Descend(fieldpath.PathElement{ + FieldName: ptr.To("labels"), + }) + annotations := metadata.Children.Descend(fieldpath.PathElement{ + FieldName: ptr.To("annotations"), + }) + + // Gather the annotations and labels on the managed fields. Remove the '.' + // prefix which appears on managed field keys. + labels.Iterate(func(path fieldpath.Path) { + managedLabels.Insert(strings.TrimPrefix(path.String(), ".")) + }) + annotations.Iterate(func(path fieldpath.Path) { + managedAnnotations.Insert(strings.TrimPrefix(path.String(), ".")) + }) } - return "", "", false + return managedLabels, managedAnnotations, nil } -// SecretTemplateMismatchesSecretManagedFields will inspect the given Secret's +// SecretManagedLabelsAndAnnotationsManagedFieldsMismatch will inspect the given Secret's // managed fields for its Annotations and Labels, and compare this against the -// SecretTemplate on the given Certificate. Returns false if Annotations and +// Labels and Annotations that are managed by cert-manager. Returns false if Annotations and // Labels match on both the Certificate's SecretTemplate and the Secret's // managed fields, true otherwise. // Also returns true if the managed fields or signed certificate were not able // to be decoded. -func SecretTemplateMismatchesSecretManagedFields(fieldManager string) Func { +func SecretManagedLabelsAndAnnotationsManagedFieldsMismatch(fieldManager string) Func { return func(input Input) (string, string, bool) { - // Only attempt to decode the signed certificate, if one is available. - var x509cert *x509.Certificate - if len(input.Secret.Data[corev1.TLSCertKey]) > 0 { - var err error - x509cert, err = pki.DecodeX509CertificateBytes(input.Secret.Data[corev1.TLSCertKey]) - if err != nil { - // This case should never happen as it should always be caught by the - // secretPublicKeysMatch function beforehand, but handle it just in case. - return InvalidCertificate, fmt.Sprintf("Failed to decode stored certificate: %v", err), true - } + managedLabels, managedAnnotations, err := secretLabelsAndAnnotationsManagedFields(input.Secret, fieldManager) + if err != nil { + return ManagedFieldsParseError, fmt.Sprintf("failed to decode managed fields on Secret: %s", err), true } - baseAnnotations := internalcertificates.AnnotationsForCertificateSecret(input.Certificate, x509cert) + // Remove the non cert-manager annotations from the managed Annotations so we can compare + // 1 to 1 all the cert-manager annotations. + for k := range managedAnnotations { + if strings.HasPrefix(k, "cert-manager.io/") || + strings.HasPrefix(k, "controller.cert-manager.io/") { + continue + } + + delete(managedAnnotations, k) + } - managedLabels, managedAnnotations := sets.NewString(), sets.NewString() + // Ignore the CertificateName and IssuerRef annotations as these cannot be set by the postIssuance controller. + managedAnnotations.Delete( + cmapi.CertificateNameKey, // SecretCertificateNameAnnotationMismatch checks the value + cmapi.IssuerNameAnnotationKey, // SecretIssuerAnnotationsMismatch checks the value + cmapi.IssuerKindAnnotationKey, // SecretIssuerAnnotationsMismatch checks the value + cmapi.IssuerGroupAnnotationKey, // SecretIssuerAnnotationsMismatch checks the value + ) - for _, managedField := range input.Secret.ManagedFields { - // If the managed field isn't owned by the cert-manager controller, ignore. - if managedField.Manager != fieldManager || managedField.FieldsV1 == nil { + // Remove the non cert-manager labels from the managed labels so we can compare + // 1 to 1 all the cert-manager labels. + for k := range managedLabels { + if strings.HasPrefix(k, "cert-manager.io/") || + strings.HasPrefix(k, "controller.cert-manager.io/") { continue } - // Decode the managed field. - var fieldset fieldpath.Set - if err := fieldset.FromJSON(bytes.NewReader(managedField.FieldsV1.Raw)); err != nil { - return ManagedFieldsParseError, fmt.Sprintf("failed to decode managed fields on Secret: %s", err), true + delete(managedLabels, k) + } + + expCertificateDataAnnotations, err := certificateDataAnnotationsForSecret(input.Secret) + if err != nil { + return InvalidCertificate, fmt.Sprintf("Failed getting secret annotations: %v", err), true + } + + expLabels := sets.New[string]( + cmapi.PartOfCertManagerControllerLabelKey, // SecretBaseLabelsMismatch checks the value + ) + expAnnotations := sets.New[string]() + for k := range expCertificateDataAnnotations { // SecretCertificateDetailsAnnotationsMismatch checks the value + expAnnotations.Insert(k) + } + + if !managedLabels.Equal(expLabels) { + missingLabels := expLabels.Difference(managedLabels) + if len(missingLabels) > 0 { + return SecretManagedMetadataMismatch, fmt.Sprintf("Secret is missing these Managed Labels: %v", sets.List(missingLabels)), true } - // Extract the labels and annotations of the managed fields. - metadata := fieldset.Children.Descend(fieldpath.PathElement{ - FieldName: pointer.String("metadata"), - }) - labels := metadata.Children.Descend(fieldpath.PathElement{ - FieldName: pointer.String("labels"), - }) - annotations := metadata.Children.Descend(fieldpath.PathElement{ - FieldName: pointer.String("annotations"), - }) - - // Gather the annotations and labels on the managed fields. Remove the '.' - // prefix which appears on managed field keys. - labels.Iterate(func(path fieldpath.Path) { - managedLabels.Insert(strings.TrimPrefix(path.String(), ".")) - }) - annotations.Iterate(func(path fieldpath.Path) { - managedAnnotations.Insert(strings.TrimPrefix(path.String(), ".")) - }) - } - - // Remove the base Annotations from the managed Annotations so we can compare - // 1 to 1 against the SecretTemplate. - for k := range baseAnnotations { - managedAnnotations = managedAnnotations.Delete(k) + extraLabels := managedLabels.Difference(expLabels) + return SecretManagedMetadataMismatch, fmt.Sprintf("Secret has these extra Labels: %v", sets.List(extraLabels)), true } - // Check early for Secret Template being nil, and whether managed - // labels/annotations are not. - if input.Certificate.Spec.SecretTemplate == nil { - if len(managedLabels) > 0 || len(managedAnnotations) > 0 { - return SecretTemplateMismatch, "SecretTemplate is nil, but Secret contains extra managed entries", true + if !managedAnnotations.Equal(expAnnotations) { + missingAnnotations := expAnnotations.Difference(managedAnnotations) + if len(missingAnnotations) > 0 { + return SecretManagedMetadataMismatch, fmt.Sprintf("Secret is missing these Managed Annotations: %v", sets.List(missingAnnotations)), true } - // SecretTemplate is nil. Managed annotations and labels are also empty. - // Return false. - return "", "", false + + extraAnnotations := managedAnnotations.Difference(expAnnotations) + return SecretManagedMetadataMismatch, fmt.Sprintf("Secret has these extra Annotations: %v", sets.List(extraAnnotations)), true } - // SecretTemplate is not nil. Do length checks. - if len(input.Certificate.Spec.SecretTemplate.Labels) != len(managedLabels) || - len(input.Certificate.Spec.SecretTemplate.Annotations) != len(managedAnnotations) { - return SecretTemplateMismatch, "Certificate's SecretTemplate doesn't match Secret", true + return "", "", false + } +} + +// SecretSecretTemplateManagedFieldsMismatch will inspect the given Secret's +// managed fields for its Annotations and Labels, and compare this against the +// SecretTemplate on the given Certificate. Returns false if Annotations and +// Labels match on both the Certificate's SecretTemplate and the Secret's +// managed fields, true otherwise. +// Also returns true if the managed fields or signed certificate were not able +// to be decoded. +func SecretSecretTemplateManagedFieldsMismatch(fieldManager string) Func { + return func(input Input) (string, string, bool) { + managedLabels, managedAnnotations, err := secretLabelsAndAnnotationsManagedFields(input.Secret, fieldManager) + if err != nil { + return ManagedFieldsParseError, fmt.Sprintf("failed to decode managed fields on Secret: %s", err), true } - // Check equal unsorted for SecretTemplate keys, and the managed fields - // equivalents. - for _, smap := range []struct { - specMap map[string]string - managedSet sets.String - }{ - {specMap: input.Certificate.Spec.SecretTemplate.Labels, managedSet: managedLabels}, - {specMap: input.Certificate.Spec.SecretTemplate.Annotations, managedSet: managedAnnotations}, - } { + // Remove the cert-manager annotations from the managed Annotations so we can compare + // 1 to 1 against the SecretTemplate. + for k := range managedAnnotations { + if !strings.HasPrefix(k, "cert-manager.io/") && + !strings.HasPrefix(k, "controller.cert-manager.io/") { + continue + } - specSet := sets.NewString() - for kSpec := range smap.specMap { - specSet.Insert(kSpec) + delete(managedAnnotations, k) + } + + // Remove the cert-manager labels from the managed Labels so we can + // compare 1 to 1 against the SecretTemplate + for k := range managedLabels { + if !strings.HasPrefix(k, "cert-manager.io/") && + !strings.HasPrefix(k, "controller.cert-manager.io/") { + continue + } + + delete(managedLabels, k) + } + + expLabels := sets.New[string]() + expAnnotations := sets.New[string]() + if input.Certificate.Spec.SecretTemplate != nil { + for k := range input.Certificate.Spec.SecretTemplate.Labels { + expLabels.Insert(k) + } + for k := range input.Certificate.Spec.SecretTemplate.Annotations { + expAnnotations.Insert(k) } + } - if !specSet.Equal(smap.managedSet) { - return SecretTemplateMismatch, "Certificate's SecretTemplate doesn't match Secret", true + if !managedLabels.Equal(expLabels) { + missingLabels := expLabels.Difference(managedLabels) + if len(missingLabels) > 0 { + return SecretTemplateMismatch, fmt.Sprintf("Secret is missing these Template Labels: %v", sets.List(missingLabels)), true } + + extraLabels := managedLabels.Difference(expLabels) + return SecretTemplateMismatch, fmt.Sprintf("Secret has these extra Labels: %v", sets.List(extraLabels)), true } + if !managedAnnotations.Equal(expAnnotations) { + missingAnnotations := expAnnotations.Difference(managedAnnotations) + if len(missingAnnotations) > 0 { + return SecretTemplateMismatch, fmt.Sprintf("Secret is missing these Template Annotations: %v", sets.List(missingAnnotations)), true + } + + extraAnnotations := managedAnnotations.Difference(expAnnotations) + return SecretTemplateMismatch, fmt.Sprintf("Secret has these extra Annotations: %v", sets.List(extraAnnotations)), true + } + + return "", "", false + } +} + +// NOTE: The presence of the controller.cert-manager.io/fao label is checked +// by the SecretManagedLabelsAndAnnotationsManagedFieldsMismatch function. +func SecretBaseLabelsMismatch(input Input) (string, string, bool) { + // check if Secret has the base labels. Currently there is only one base label + if input.Secret.Labels == nil { + return "", "", false + } + + value, ok := input.Secret.Labels[cmapi.PartOfCertManagerControllerLabelKey] + if !ok || value == "true" { return "", "", false } + + return SecretManagedMetadataMismatch, fmt.Sprintf("wrong base label %s value %q, expected \"true\"", cmapi.PartOfCertManagerControllerLabelKey, value), true +} + +// SecretCertificateDetailsAnnotationsMismatch returns a validation violation when +// annotations on the Secret do not match the details of the x509 certificate that +// is stored in the Secret. This function will only compare the annotations that +// already exist on the Secret and are also present in the certificate metadata. +// NOTE: Missing and extra annotations are detected by the SecretManagedLabelsAndAnnotationsManagedFieldsMismatch +// function instead. +func SecretCertificateDetailsAnnotationsMismatch(input Input) (string, string, bool) { + dataAnnotations, err := certificateDataAnnotationsForSecret(input.Secret) + if err != nil { + return InvalidCertificate, fmt.Sprintf("Failed getting secret annotations: %v", err), true + } + + if match, key := mapsHaveMatchingValues(dataAnnotations, input.Secret.Annotations); !match { + return SecretTemplateMismatch, fmt.Sprintf("Secret metadata %s does not match certificate metadata %s", input.Secret.Annotations[key], dataAnnotations[key]), true + } + + return "", "", false } -// SecretAdditionalOutputFormatsDataMismatch validates that the Secret has the +// SecretAdditionalOutputFormatsMismatch validates that the Secret has the // expected Certificate AdditionalOutputFormats. // Returns true (violation) if AdditionalOutputFormat(s) are present and any of // the following: // - Secret key is missing // - Secret value is incorrect -func SecretAdditionalOutputFormatsDataMismatch(input Input) (string, string, bool) { +func SecretAdditionalOutputFormatsMismatch(input Input) (string, string, bool) { const message = "Certificate's AdditionalOutputFormats doesn't match Secret Data" for _, format := range input.Certificate.Spec.AdditionalOutputFormats { switch format.Type { @@ -401,7 +627,7 @@ func SecretAdditionalOutputFormatsDataMismatch(input Input) (string, string, boo return "", "", false } -// SecretAdditionalOutputFormatsOwnerMismatch validates that the field manager +// SecretAdditionalOutputFormatsManagedFieldsMismatch validates that the field manager // owns the correct Certificate's AdditionalOutputFormats in the Secret. // Returns true (violation) if: // - missing AdditionalOutputFormat key owned by the field manager @@ -409,7 +635,7 @@ func SecretAdditionalOutputFormatsDataMismatch(input Input) (string, string, boo // // A violation with the reason `ManagedFieldsParseError` should be considered a // non re-triable error. -func SecretAdditionalOutputFormatsOwnerMismatch(fieldManager string) Func { +func SecretAdditionalOutputFormatsManagedFieldsMismatch(fieldManager string) Func { const message = "Certificate's AdditionalOutputFormats doesn't match Secret ManagedFields" return func(input Input) (string, string, bool) { var ( @@ -441,15 +667,15 @@ func SecretAdditionalOutputFormatsOwnerMismatch(fieldManager string) Func { } if fieldset.Has(fieldpath.Path{ - {FieldName: pointer.String("data")}, - {FieldName: pointer.String(cmapi.CertificateOutputFormatCombinedPEMKey)}, + {FieldName: ptr.To("data")}, + {FieldName: ptr.To(cmapi.CertificateOutputFormatCombinedPEMKey)}, }) { secretHasCombinedPEM = true } if fieldset.Has(fieldpath.Path{ - {FieldName: pointer.String("data")}, - {FieldName: pointer.String(cmapi.CertificateOutputFormatDERKey)}, + {FieldName: ptr.To("data")}, + {FieldName: ptr.To(cmapi.CertificateOutputFormatDERKey)}, }) { secretHasDER = true } @@ -486,8 +712,8 @@ func SecretOwnerReferenceManagedFieldMismatch(ownerRefEnabled bool, fieldManager return ManagedFieldsParseError, fmt.Sprintf("failed to decode managed fields on Secret: %s", err), true } if fieldset.Has(fieldpath.Path{ - {FieldName: pointer.String("metadata")}, - {FieldName: pointer.String("ownerReferences")}, + {FieldName: ptr.To("metadata")}, + {FieldName: ptr.To("ownerReferences")}, {Key: &value.FieldList{{Name: "uid", Value: value.NewValueInterface(string(input.Certificate.UID))}}}, }) { hasOwnerRefManagedField = true @@ -506,10 +732,10 @@ func SecretOwnerReferenceManagedFieldMismatch(ownerRefEnabled bool, fieldManager } } -// SecretOwnerReferenceValueMismatch validates that the Secret has the expected +// SecretOwnerReferenceMismatch validates that the Secret has the expected // owner reference if it is enabled. Returns true (violation) if: // * owner reference is enabled, but the reference has an incorrect value -func SecretOwnerReferenceValueMismatch(ownerRefEnabled bool) Func { +func SecretOwnerReferenceMismatch(ownerRefEnabled bool) Func { return func(input Input) (string, string, bool) { // If the Owner Reference is not enabled, we don't need to check the value // and can exit early. @@ -544,3 +770,28 @@ func SecretOwnerReferenceValueMismatch(ownerRefEnabled bool) Func { return "", "", false } } + +// mapsHaveMatchingValues returns true if the two maps have the same values for +// all common keys. Otherwise, the first key for which the values differ is returned. +// This function is stable and will always return the same key if the maps are +// the same. +func mapsHaveMatchingValues[Key cmp.Ordered, Value comparable](a, b map[Key]Value) (bool, Key) { + keys := make([]Key, 0, len(a)) + for k := range a { + if _, ok := b[k]; !ok { + continue + } + + keys = append(keys, k) + } + slices.Sort(keys) + + for _, k := range keys { + if b[k] != a[k] { + return false, k + } + } + + var zero Key + return true, zero +} diff --git a/internal/controller/certificates/policies/checks_test.go b/internal/controller/certificates/policies/checks_test.go index 9ceb1bb8325..75c709be738 100644 --- a/internal/controller/certificates/policies/checks_test.go +++ b/internal/controller/certificates/policies/checks_test.go @@ -17,21 +17,22 @@ limitations under the License. package policies import ( - "encoding/pem" "testing" "time" + "github.com/stretchr/testify/assert" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" fakeclock "k8s.io/utils/clock/testing" - "k8s.io/utils/pointer" + "k8s.io/utils/ptr" + "github.com/cert-manager/cert-manager/internal/pem" cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" + "github.com/cert-manager/cert-manager/pkg/util/pki" testcrypto "github.com/cert-manager/cert-manager/test/unit/crypto" "github.com/cert-manager/cert-manager/test/unit/gen" - "github.com/stretchr/testify/assert" ) // Runs a full set of tests against the trigger 'policy chain' once it is @@ -92,7 +93,7 @@ func Test_NewTriggerPolicyChain(t *testing.T) { }, }, reason: InvalidKeyPair, - message: "Issuing certificate as Secret contains an invalid key-pair: tls: failed to find any PEM data in certificate input", + message: "Issuing certificate as Secret contains invalid private key data: error decoding private key PEM block: no PEM data was found in given input", reissue: true, }, "trigger issuance as Secret contains corrupt certificate data": { @@ -103,8 +104,8 @@ func Test_NewTriggerPolicyChain(t *testing.T) { corev1.TLSCertKey: []byte("test"), }, }, - reason: InvalidKeyPair, - message: "Issuing certificate as Secret contains an invalid key-pair: tls: failed to find any PEM data in certificate input", + reason: InvalidCertificate, + message: "Issuing certificate as Secret contains an invalid certificate: error decoding certificate PEM block: no valid certificates found", reissue: true, }, "trigger issuance as Secret contains corrupt private key data": { @@ -118,7 +119,7 @@ func Test_NewTriggerPolicyChain(t *testing.T) { }, }, reason: InvalidKeyPair, - message: "Issuing certificate as Secret contains an invalid key-pair: tls: failed to find any PEM data in key input", + message: "Issuing certificate as Secret contains invalid private key data: error decoding private key PEM block: no PEM data was found in given input", reissue: true, }, "trigger issuance as Secret contains a non-matching key-pair": { @@ -132,13 +133,13 @@ func Test_NewTriggerPolicyChain(t *testing.T) { }, }, reason: InvalidKeyPair, - message: "Issuing certificate as Secret contains an invalid key-pair: tls: private key does not match public key", + message: "Issuing certificate as Secret contains a private key that does not match the certificate", reissue: true, }, - "trigger issuance as Secret has old/incorrect 'issuer name' annotation": { + "trigger issuance as Secret has old or incorrect 'issuer name' annotation": { certificate: &cmapi.Certificate{Spec: cmapi.CertificateSpec{ SecretName: "something", - IssuerRef: cmmeta.ObjectReference{ + IssuerRef: cmmeta.IssuerReference{ Name: "testissuer", }, }}, @@ -156,13 +157,13 @@ func Test_NewTriggerPolicyChain(t *testing.T) { }, }, reason: IncorrectIssuer, - message: "Issuing certificate as Secret was previously issued by Issuer.cert-manager.io/oldissuer", + message: "Issuing certificate as Secret was previously issued by \"Issuer.cert-manager.io/oldissuer\"", reissue: true, }, - "trigger issuance as Secret has old/incorrect 'issuer kind' annotation": { + "trigger issuance as Secret has old or incorrect 'issuer kind' annotation": { certificate: &cmapi.Certificate{Spec: cmapi.CertificateSpec{ SecretName: "something", - IssuerRef: cmmeta.ObjectReference{ + IssuerRef: cmmeta.IssuerReference{ Name: "testissuer", Kind: "NewIssuerKind", }, @@ -182,13 +183,13 @@ func Test_NewTriggerPolicyChain(t *testing.T) { }, }, reason: IncorrectIssuer, - message: "Issuing certificate as Secret was previously issued by OldIssuerKind.cert-manager.io/testissuer", + message: "Issuing certificate as Secret was previously issued by \"OldIssuerKind.cert-manager.io/testissuer\"", reissue: true, }, - "trigger issuance as Secret has old/incorrect 'issuer group' annotation": { + "trigger issuance as Secret has old or incorrect 'issuer group' annotation": { certificate: &cmapi.Certificate{Spec: cmapi.CertificateSpec{ SecretName: "something", - IssuerRef: cmmeta.ObjectReference{ + IssuerRef: cmmeta.IssuerReference{ Name: "testissuer", Kind: "IssuerKind", Group: "old.example.com", @@ -210,15 +211,69 @@ func Test_NewTriggerPolicyChain(t *testing.T) { }, }, reason: IncorrectIssuer, - message: "Issuing certificate as Secret was previously issued by IssuerKind.new.example.com/testissuer", + message: "Issuing certificate as Secret was previously issued by \"IssuerKind.new.example.com/testissuer\"", + reissue: true, + }, + "trigger issuance as private key properties do not meet the requested properties": { + certificate: &cmapi.Certificate{Spec: cmapi.CertificateSpec{SecretName: "something"}}, + secret: &corev1.Secret{ObjectMeta: metav1.ObjectMeta{Name: "something"}, + Data: func() map[string][]byte { + // generate a 521 bit EC private key, which is not the type of key + // configured in the Certificate resource + pk, err := pki.GenerateECPrivateKey(521) + if err != nil { + t.Fatal(err) + } + + pkData, err := pki.EncodePrivateKey(pk, cmapi.PKCS8) + if err != nil { + t.Fatal(err) + } + + return map[string][]byte{ + corev1.TLSPrivateKeyKey: pkData, + corev1.TLSCertKey: testcrypto.MustCreateCert( + t, pkData, + &cmapi.Certificate{Spec: cmapi.CertificateSpec{CommonName: "example.com"}}, + ), + } + }(), + }, + reason: SecretMismatch, + message: "Existing private key is not up to date for spec: [spec.privateKey.algorithm]", + reissue: true, + }, + "trigger if the Secret contains a different private key than was used to sign the CSR": { + certificate: &cmapi.Certificate{Spec: cmapi.CertificateSpec{SecretName: "something"}}, + secret: &corev1.Secret{ObjectMeta: metav1.ObjectMeta{Name: "something"}, + Data: map[string][]byte{ + corev1.TLSPrivateKeyKey: staticFixedPrivateKey, + corev1.TLSCertKey: testcrypto.MustCreateCert( + t, staticFixedPrivateKey, + &cmapi.Certificate{Spec: cmapi.CertificateSpec{CommonName: "example.com"}}, + ), + }, + }, + request: &cmapi.CertificateRequest{Spec: cmapi.CertificateRequestSpec{ + IssuerRef: cmmeta.IssuerReference{ + Name: "testissuer", + Kind: "IssuerKind", + Group: "group.example.com", + }, + Request: testcrypto.MustGenerateCSRImpl(t, testcrypto.MustCreatePEMPrivateKey(t), &cmapi.Certificate{Spec: cmapi.CertificateSpec{ + CommonName: "example.com", + }}), + }}, + reason: SecretMismatch, + message: "Secret contains a private key that does not match the current CertificateRequest", reissue: true, }, // we only have a basic test here for this as unit tests for the - // `certificates.RequestMatchesSpec` function cover all other cases. + // `pki.RequestMatchesSpec` function cover all other cases. "trigger issuance when CertificateRequest does not match certificate spec": { certificate: &cmapi.Certificate{Spec: cmapi.CertificateSpec{ CommonName: "new.example.com", - IssuerRef: cmmeta.ObjectReference{ + IssuerRef: cmmeta.IssuerReference{ Name: "testissuer", Kind: "IssuerKind", Group: "group.example.com", @@ -243,7 +298,7 @@ func Test_NewTriggerPolicyChain(t *testing.T) { }, }, request: &cmapi.CertificateRequest{Spec: cmapi.CertificateRequestSpec{ - IssuerRef: cmmeta.ObjectReference{ + IssuerRef: cmmeta.IssuerReference{ Name: "testissuer", Kind: "IssuerKind", Group: "group.example.com", @@ -259,7 +314,7 @@ func Test_NewTriggerPolicyChain(t *testing.T) { "do nothing if CertificateRequest matches spec": { certificate: &cmapi.Certificate{Spec: cmapi.CertificateSpec{ CommonName: "example.com", - IssuerRef: cmmeta.ObjectReference{ + IssuerRef: cmmeta.IssuerReference{ Name: "testissuer", Kind: "IssuerKind", Group: "group.example.com", @@ -284,7 +339,7 @@ func Test_NewTriggerPolicyChain(t *testing.T) { }, }, request: &cmapi.CertificateRequest{Spec: cmapi.CertificateRequestSpec{ - IssuerRef: cmmeta.ObjectReference{ + IssuerRef: cmmeta.IssuerReference{ Name: "testissuer", Kind: "IssuerKind", Group: "group.example.com", @@ -297,7 +352,7 @@ func Test_NewTriggerPolicyChain(t *testing.T) { "compare signed x509 certificate in Secret with spec if CertificateRequest does not exist": { certificate: &cmapi.Certificate{Spec: cmapi.CertificateSpec{ CommonName: "new.example.com", - IssuerRef: cmmeta.ObjectReference{ + IssuerRef: cmmeta.IssuerReference{ Name: "testissuer", Kind: "IssuerKind", Group: "group.example.com", @@ -319,13 +374,13 @@ func Test_NewTriggerPolicyChain(t *testing.T) { }, }, reason: SecretMismatch, - message: "Existing issued Secret is not up to date for spec: [spec.commonName]", + message: "Issuing certificate as Existing issued Secret is not up to date for spec: [spec.commonName]", reissue: true, }, "do nothing if signed x509 certificate in Secret matches spec (when request does not exist)": { certificate: &cmapi.Certificate{Spec: cmapi.CertificateSpec{ CommonName: "example.com", - IssuerRef: cmmeta.ObjectReference{ + IssuerRef: cmmeta.IssuerReference{ Name: "testissuer", Kind: "IssuerKind", Group: "group.example.com", @@ -351,7 +406,7 @@ func Test_NewTriggerPolicyChain(t *testing.T) { certificate: &cmapi.Certificate{ Spec: cmapi.CertificateSpec{ CommonName: "example.com", - IssuerRef: cmmeta.ObjectReference{ + IssuerRef: cmmeta.IssuerReference{ Name: "testissuer", Kind: "IssuerKind", Group: "group.example.com", @@ -388,7 +443,7 @@ func Test_NewTriggerPolicyChain(t *testing.T) { certificate: &cmapi.Certificate{ Spec: cmapi.CertificateSpec{ CommonName: "example.com", - IssuerRef: cmmeta.ObjectReference{ + IssuerRef: cmmeta.IssuerReference{ Name: "testissuer", Kind: "IssuerKind", Group: "group.example.com", @@ -425,7 +480,7 @@ func Test_NewTriggerPolicyChain(t *testing.T) { certificate: &cmapi.Certificate{ Spec: cmapi.CertificateSpec{ CommonName: "example.com", - IssuerRef: cmmeta.ObjectReference{ + IssuerRef: cmmeta.IssuerReference{ Name: "testissuer", Kind: "IssuerKind", Group: "group.example.com", @@ -459,7 +514,7 @@ func Test_NewTriggerPolicyChain(t *testing.T) { certificate: &cmapi.Certificate{ Spec: cmapi.CertificateSpec{ CommonName: "example.com", - IssuerRef: cmmeta.ObjectReference{ + IssuerRef: cmmeta.IssuerReference{ Name: "testissuer", Kind: "IssuerKind", Group: "group.example.com", @@ -512,7 +567,127 @@ func Test_NewTriggerPolicyChain(t *testing.T) { } } -func Test_SecretTemplateMismatchesSecret(t *testing.T) { +func Test_SecretManagedLabelsAndAnnotationsManagedFieldsMismatch(t *testing.T) { + const fieldManager = "cert-manager-unit-test" + + var ( + fixedClockStart = time.Now() + fixedClock = fakeclock.NewFakeClock(fixedClockStart) + baseCertBundle = testcrypto.MustCreateCryptoBundle(t, + gen.Certificate("test-certificate", gen.SetCertificateCommonName("cert-manager")), fixedClock) + ) + + tests := map[string]struct { + secretManagedFields []metav1.ManagedFieldsEntry + secretData map[string][]byte + + expReason string + expMessage string + expViolation bool + }{ + "if there are no cert-manager annotations and the certificate data is nil, should return false": { + secretManagedFields: []metav1.ManagedFieldsEntry{ + {Manager: fieldManager, FieldsV1: &metav1.FieldsV1{ + Raw: []byte(`{"f:metadata": { + "f:labels": { + "f:controller.cert-manager.io/fao": {} + } + }}`), + }}, + }, + expReason: "", + expMessage: "", + expViolation: false, + }, + "if optional cert-manager annotations are present with no certificate data, should return false": { + secretManagedFields: []metav1.ManagedFieldsEntry{ + {Manager: fieldManager, FieldsV1: &metav1.FieldsV1{ + Raw: []byte(`{"f:metadata": { + "f:labels": { + "f:controller.cert-manager.io/fao": {} + }, + "f:annotations": { + "f:foo1": {}, + "f:foo2": {}, + "f:cert-manager.io/certificate-name": {}, + "f:cert-manager.io/issuer-name": {}, + "f:cert-manager.io/issuer-kind": {}, + "f:cert-manager.io/issuer-group": {} + } + }}`), + }}, + }, + expReason: "", + expMessage: "", + expViolation: false, + }, + "if cert-manager annotations are present with certificate data, should return false": { + secretManagedFields: []metav1.ManagedFieldsEntry{ + {Manager: fieldManager, FieldsV1: &metav1.FieldsV1{ + Raw: []byte(`{"f:metadata": { + "f:labels": { + "f:controller.cert-manager.io/fao": {} + }, + "f:annotations": { + "f:foo1": {}, + "f:foo2": {}, + "f:cert-manager.io/certificate-name": {}, + "f:cert-manager.io/issuer-name": {}, + "f:cert-manager.io/issuer-kind": {}, + "f:cert-manager.io/issuer-group": {}, + "f:cert-manager.io/common-name": {}, + "f:cert-manager.io/alt-names": {}, + "f:cert-manager.io/ip-sans": {}, + "f:cert-manager.io/uri-sans": {} + } + }}`), + }}, + }, + secretData: map[string][]byte{corev1.TLSCertKey: baseCertBundle.CertBytes}, + expReason: "", + expMessage: "", + expViolation: false, + }, + "if required and optional cert-manager annotations are present with certificate data but certificate data is nil, should return true": { + secretManagedFields: []metav1.ManagedFieldsEntry{ + {Manager: fieldManager, FieldsV1: &metav1.FieldsV1{ + Raw: []byte(`{"f:metadata": { + "f:labels": { + "f:controller.cert-manager.io/fao": {} + }, + "f:annotations": { + "f:foo1": {}, + "f:foo2": {}, + "f:cert-manager.io/certificate-name": {}, + "f:cert-manager.io/issuer-name": {}, + "f:cert-manager.io/issuer-kind": {}, + "f:cert-manager.io/issuer-group": {}, + "f:cert-manager.io/uri-sans": {}, + "f:cert-manager.io/ip-sans": {} + } + }}`), + }}, + }, + expReason: SecretManagedMetadataMismatch, + expMessage: "Secret has these extra Annotations: [cert-manager.io/ip-sans cert-manager.io/uri-sans]", + expViolation: true, + }, + } + + for name, test := range tests { + t.Run(name, func(t *testing.T) { + gotReason, gotMessage, gotViolation := SecretManagedLabelsAndAnnotationsManagedFieldsMismatch(fieldManager)(Input{ + Secret: &corev1.Secret{ObjectMeta: metav1.ObjectMeta{ManagedFields: test.secretManagedFields}, Data: test.secretData}, + }) + + assert.Equal(t, test.expReason, gotReason, "unexpected reason") + assert.Equal(t, test.expMessage, gotMessage, "unexpected message") + assert.Equal(t, test.expViolation, gotViolation, "unexpected violation") + }) + } +} + +func Test_SecretSecretTemplateMismatch(t *testing.T) { tests := map[string]struct { tmpl *cmapi.CertificateSecretTemplate secret *corev1.Secret @@ -551,7 +726,7 @@ func Test_SecretTemplateMismatchesSecret(t *testing.T) { expReason: "", expMessage: "", }, - "if SecretTemplate is non-nil, Secret Annotations match but Labels are nil, return true": { + "if SecretTemplate is non-nil, Secret Annotations match and there are no common Labels, return false": { tmpl: &cmapi.CertificateSecretTemplate{ Annotations: map[string]string{"foo1": "bar1", "foo2": "bar2"}, Labels: map[string]string{"abc": "123", "def": "456"}, @@ -560,11 +735,11 @@ func Test_SecretTemplateMismatchesSecret(t *testing.T) { Annotations: map[string]string{"foo1": "bar1", "foo2": "bar2"}, Labels: nil, }}, - expViolation: true, - expReason: SecretTemplateMismatch, - expMessage: "Certificate's SecretTemplate Labels missing or incorrect value on Secret", + expViolation: false, + expReason: "", + expMessage: "", }, - "if SecretTemplate is non-nil, Secret Labels match but Annotations are nil, return true": { + "if SecretTemplate is non-nil, Secret Labels match and there are no common Annotations, return false": { tmpl: &cmapi.CertificateSecretTemplate{ Annotations: map[string]string{"foo1": "bar1", "foo2": "bar2"}, Labels: map[string]string{"abc": "123", "def": "456"}, @@ -573,35 +748,9 @@ func Test_SecretTemplateMismatchesSecret(t *testing.T) { Annotations: nil, Labels: map[string]string{"abc": "123", "def": "456"}, }}, - expViolation: true, - expReason: SecretTemplateMismatch, - expMessage: "Certificate's SecretTemplate Annotations missing or incorrect value on Secret", - }, - "if SecretTemplate is non-nil, Secret Labels match but Annotations don't match keys, return true": { - tmpl: &cmapi.CertificateSecretTemplate{ - Annotations: map[string]string{"foo1": "bar1", "foo2": "bar2"}, - Labels: map[string]string{"abc": "123", "def": "456"}, - }, - secret: &corev1.Secret{ObjectMeta: metav1.ObjectMeta{ - Annotations: map[string]string{"foo2": "bar1", "foo1": "bar2"}, - Labels: map[string]string{"abc": "123", "def": "456"}, - }}, - expViolation: true, - expReason: SecretTemplateMismatch, - expMessage: "Certificate's SecretTemplate Annotations missing or incorrect value on Secret", - }, - "if SecretTemplate is non-nil, Secret Annoations match but Labels don't match keys, return true": { - tmpl: &cmapi.CertificateSecretTemplate{ - Annotations: map[string]string{"foo1": "bar1", "foo2": "bar2"}, - Labels: map[string]string{"abc": "123", "def": "456"}, - }, - secret: &corev1.Secret{ObjectMeta: metav1.ObjectMeta{ - Annotations: map[string]string{"foo1": "bar1", "foo2": "bar2"}, - Labels: map[string]string{"def": "123", "abc": "456"}, - }}, - expViolation: true, - expReason: SecretTemplateMismatch, - expMessage: "Certificate's SecretTemplate Labels missing or incorrect value on Secret", + expViolation: false, + expReason: "", + expMessage: "", }, "if SecretTemplate is non-nil, Secret Labels match but Annotations don't match values, return true": { tmpl: &cmapi.CertificateSecretTemplate{ @@ -646,7 +795,7 @@ func Test_SecretTemplateMismatchesSecret(t *testing.T) { for name, test := range tests { t.Run(name, func(t *testing.T) { - gotReason, gotMessage, gotViolation := SecretTemplateMismatchesSecret(Input{ + gotReason, gotMessage, gotViolation := SecretSecretTemplateMismatch(Input{ Certificate: &cmapi.Certificate{Spec: cmapi.CertificateSpec{SecretTemplate: test.tmpl}}, Secret: test.secret, }) @@ -658,20 +807,12 @@ func Test_SecretTemplateMismatchesSecret(t *testing.T) { } } -func Test_SecretTemplateMismatchesSecretManagedFields(t *testing.T) { +func Test_SecretSecretTemplateManagedFieldsMismatch(t *testing.T) { const fieldManager = "cert-manager-unit-test" - var ( - fixedClockStart = time.Now() - fixedClock = fakeclock.NewFakeClock(fixedClockStart) - baseCertBundle = testcrypto.MustCreateCryptoBundle(t, - gen.Certificate("test-certificate", gen.SetCertificateCommonName("cert-manager")), fixedClock) - ) - tests := map[string]struct { tmpl *cmapi.CertificateSecretTemplate secretManagedFields []metav1.ManagedFieldsEntry - secretData map[string][]byte expReason string expMessage string @@ -716,7 +857,7 @@ func Test_SecretTemplateMismatchesSecretManagedFields(t *testing.T) { }, secretManagedFields: nil, expReason: SecretTemplateMismatch, - expMessage: "Certificate's SecretTemplate doesn't match Secret", + expMessage: "Secret is missing these Template Labels: [abc]", expViolation: true, }, "if template is nil but managed fields is not nil, should return true": { @@ -734,12 +875,12 @@ func Test_SecretTemplateMismatchesSecretManagedFields(t *testing.T) { }}, }, expReason: SecretTemplateMismatch, - expMessage: "SecretTemplate is nil, but Secret contains extra managed entries", + expMessage: "Secret has these extra Labels: [abc]", expViolation: true, }, "if template annotations do not match managed fields, should return true": { tmpl: &cmapi.CertificateSecretTemplate{ - Annotations: map[string]string{"foo1": "bar1", "foo2": "bar2"}, + Annotations: map[string]string{"foo1": "bar1", "foo2": "bar2", "foo4": "bar4"}, Labels: map[string]string{"abc": "123", "def": "456"}, }, secretManagedFields: []metav1.ManagedFieldsEntry{{ @@ -757,13 +898,13 @@ func Test_SecretTemplateMismatchesSecretManagedFields(t *testing.T) { }}, }, expReason: SecretTemplateMismatch, - expMessage: "Certificate's SecretTemplate doesn't match Secret", + expMessage: "Secret is missing these Template Annotations: [foo2 foo4]", expViolation: true, }, "if template labels do not match managed fields, should return true": { tmpl: &cmapi.CertificateSecretTemplate{ Annotations: map[string]string{"foo1": "bar1", "foo2": "bar2"}, - Labels: map[string]string{"abc": "123", "def": "456"}, + Labels: map[string]string{"abc": "123", "def": "456", "ghi": "789"}, }, secretManagedFields: []metav1.ManagedFieldsEntry{{ Manager: fieldManager, FieldsV1: &metav1.FieldsV1{ @@ -780,7 +921,7 @@ func Test_SecretTemplateMismatchesSecretManagedFields(t *testing.T) { }}, }, expReason: SecretTemplateMismatch, - expMessage: "Certificate's SecretTemplate doesn't match Secret", + expMessage: "Secret is missing these Template Labels: [def ghi]", expViolation: true, }, "if template annotations and labels match managed fields, should return false": { @@ -817,7 +958,8 @@ func Test_SecretTemplateMismatchesSecretManagedFields(t *testing.T) { "f:annotations": { "f:foo1": {}, "f:foo2": {}, - "f:foo3": {} + "f:foo3": {}, + "f:foo4": {} }, "f:labels": { "f:abc": {}, @@ -827,7 +969,7 @@ func Test_SecretTemplateMismatchesSecretManagedFields(t *testing.T) { }}, }, expReason: SecretTemplateMismatch, - expMessage: "Certificate's SecretTemplate doesn't match Secret", + expMessage: "Secret has these extra Annotations: [foo3 foo4]", expViolation: true, }, "if template labels is a subset of managed fields, return true": { @@ -845,13 +987,14 @@ func Test_SecretTemplateMismatchesSecretManagedFields(t *testing.T) { "f:labels": { "f:abc": {}, "f:def": {}, - "f:ghi": {} + "f:ghi": {}, + "f:jkl": {} } }}`), }}, }, expReason: SecretTemplateMismatch, - expMessage: "Certificate's SecretTemplate doesn't match Secret", + expMessage: "Secret has these extra Labels: [ghi jkl]", expViolation: true, }, "if managed fields annotations is a subset of template, return true": { @@ -874,7 +1017,7 @@ func Test_SecretTemplateMismatchesSecretManagedFields(t *testing.T) { }}, }, expReason: SecretTemplateMismatch, - expMessage: "Certificate's SecretTemplate doesn't match Secret", + expMessage: "Secret is missing these Template Annotations: [foo3]", expViolation: true, }, "if managed fields labels is a subset of template, return true": { @@ -897,7 +1040,7 @@ func Test_SecretTemplateMismatchesSecretManagedFields(t *testing.T) { }}, }, expReason: SecretTemplateMismatch, - expMessage: "Certificate's SecretTemplate doesn't match Secret", + expMessage: "Secret is missing these Template Labels: [ghi]", expViolation: true, }, "if managed fields matches template but is split across multiple managed fields, should return false": { @@ -943,7 +1086,7 @@ func Test_SecretTemplateMismatchesSecretManagedFields(t *testing.T) { expMessage: "", expViolation: false, }, - "if managed fields matches template and base cert-manager annotations are present with no certificate data, should return false": { + "if managed fields matches template and cert-manager annotations are present, should return false": { tmpl: &cmapi.CertificateSecretTemplate{ Annotations: map[string]string{"foo1": "bar1", "foo2": "bar2"}, }, @@ -953,10 +1096,8 @@ func Test_SecretTemplateMismatchesSecretManagedFields(t *testing.T) { "f:annotations": { "f:foo1": {}, "f:foo2": {}, - "f:cert-manager.io/certificate-name": {}, - "f:cert-manager.io/issuer-name": {}, - "f:cert-manager.io/issuer-kind": {}, - "f:cert-manager.io/issuer-group": {} + "f:cert-manager.io/foo1": {}, + "f:cert-manager.io/foo2": {} } }}`), }}, @@ -965,64 +1106,13 @@ func Test_SecretTemplateMismatchesSecretManagedFields(t *testing.T) { expMessage: "", expViolation: false, }, - "if managed fields matches template and base cert-manager annotations are present with certificate data, should return false": { - tmpl: &cmapi.CertificateSecretTemplate{ - Annotations: map[string]string{"foo1": "bar1", "foo2": "bar2"}, - }, - secretManagedFields: []metav1.ManagedFieldsEntry{ - {Manager: fieldManager, FieldsV1: &metav1.FieldsV1{ - Raw: []byte(`{"f:metadata": { - "f:annotations": { - "f:foo1": {}, - "f:foo2": {}, - "f:cert-manager.io/certificate-name": {}, - "f:cert-manager.io/issuer-name": {}, - "f:cert-manager.io/issuer-kind": {}, - "f:cert-manager.io/issuer-group": {}, - "f:cert-manager.io/common-name": {}, - "f:cert-manager.io/alt-names": {}, - "f:cert-manager.io/ip-sans": {}, - "f:cert-manager.io/uri-sans": {} - } - }}`), - }}, - }, - secretData: map[string][]byte{corev1.TLSCertKey: baseCertBundle.CertBytes}, - expViolation: false, - }, - "if managed fields matches template and base cert-manager annotations are present with certificate data but certificate data is nil, should return true": { - tmpl: &cmapi.CertificateSecretTemplate{ - Annotations: map[string]string{"foo1": "bar1", "foo2": "bar2"}, - }, - secretManagedFields: []metav1.ManagedFieldsEntry{ - {Manager: fieldManager, FieldsV1: &metav1.FieldsV1{ - Raw: []byte(`{"f:metadata": { - "f:annotations": { - "f:foo1": {}, - "f:foo2": {}, - "f:cert-manager.io/certificate-name": {}, - "f:cert-manager.io/issuer-name": {}, - "f:cert-manager.io/issuer-kind": {}, - "f:cert-manager.io/issuer-group": {}, - "f:cert-manager.io/common-name": {}, - "f:cert-manager.io/alt-names": {}, - "f:cert-manager.io/ip-sans": {}, - "f:cert-manager.io/uri-sans": {} - } - }}`), - }}, - }, - expReason: SecretTemplateMismatch, - expMessage: "Certificate's SecretTemplate doesn't match Secret", - expViolation: true, - }, } for name, test := range tests { t.Run(name, func(t *testing.T) { - gotReason, gotMessage, gotViolation := SecretTemplateMismatchesSecretManagedFields(fieldManager)(Input{ + gotReason, gotMessage, gotViolation := SecretSecretTemplateManagedFieldsMismatch(fieldManager)(Input{ Certificate: &cmapi.Certificate{Spec: cmapi.CertificateSpec{SecretTemplate: test.tmpl}}, - Secret: &corev1.Secret{ObjectMeta: metav1.ObjectMeta{ManagedFields: test.secretManagedFields}, Data: test.secretData}, + Secret: &corev1.Secret{ObjectMeta: metav1.ObjectMeta{ManagedFields: test.secretManagedFields}, Data: map[string][]byte{}}, }) assert.Equal(t, test.expReason, gotReason, "unexpected reason") @@ -1032,10 +1122,14 @@ func Test_SecretTemplateMismatchesSecretManagedFields(t *testing.T) { } } -func Test_SecretAdditionalOutputFormatsDataMismatch(t *testing.T) { +func Test_SecretAdditionalOutputFormatsMismatch(t *testing.T) { cert := []byte("a") pk := testcrypto.MustCreatePEMPrivateKey(t) - block, _ := pem.Decode(pk) + block, _, err := pem.SafeDecodePrivateKey(pk) + if err != nil { + t.Fatalf("got unexpected error decoding PEM: %s", err) + } + pkDER := block.Bytes combinedPEM := append(append(pk, '\n'), cert...) @@ -1289,7 +1383,7 @@ func Test_SecretAdditionalOutputFormatsDataMismatch(t *testing.T) { for name, test := range tests { t.Run(name, func(t *testing.T) { - gotReason, gotMessage, gotViolation := SecretAdditionalOutputFormatsDataMismatch(test.input) + gotReason, gotMessage, gotViolation := SecretAdditionalOutputFormatsMismatch(test.input) assert.Equal(t, test.expReason, gotReason) assert.Equal(t, test.expMessage, gotMessage) assert.Equal(t, test.expViolation, gotViolation) @@ -1297,7 +1391,7 @@ func Test_SecretAdditionalOutputFormatsDataMismatch(t *testing.T) { } } -func Test_SecretAdditionalOutputFormatsOwnerMismatch(t *testing.T) { +func Test_SecretAdditionalOutputFormatsManagedFieldsMismatch(t *testing.T) { const fieldManager = "cert-manager-test" tests := map[string]struct { @@ -1365,9 +1459,9 @@ func Test_SecretAdditionalOutputFormatsOwnerMismatch(t *testing.T) { ManagedFields: []metav1.ManagedFieldsEntry{ {Manager: "not-cert-manager", FieldsV1: &metav1.FieldsV1{ Raw: []byte(` - {"f:data": { - ".": {}, - "f:tls-combined.pem": {} + {"f:data": { + ".": {}, + "f:tls-combined.pem": {} }}`), }}, }, @@ -1388,9 +1482,9 @@ func Test_SecretAdditionalOutputFormatsOwnerMismatch(t *testing.T) { ManagedFields: []metav1.ManagedFieldsEntry{ {Manager: "not-cert-manager", FieldsV1: &metav1.FieldsV1{ Raw: []byte(` - {"f:data": { - ".": {}, - "f:key.der": {} + {"f:data": { + ".": {}, + "f:key.der": {} }}`), }}, }, @@ -1411,10 +1505,10 @@ func Test_SecretAdditionalOutputFormatsOwnerMismatch(t *testing.T) { ManagedFields: []metav1.ManagedFieldsEntry{ {Manager: "not-cert-manager", FieldsV1: &metav1.FieldsV1{ Raw: []byte(` - {"f:data": { - ".": {}, - "f:tls-combined.pem": {}, - "f:key.der": {} + {"f:data": { + ".": {}, + "f:tls-combined.pem": {}, + "f:key.der": {} }}`), }}, }, @@ -1435,9 +1529,9 @@ func Test_SecretAdditionalOutputFormatsOwnerMismatch(t *testing.T) { ManagedFields: []metav1.ManagedFieldsEntry{ {Manager: fieldManager, FieldsV1: &metav1.FieldsV1{ Raw: []byte(` - {"f:data": { - ".": {}, - "f:tls-combined.pem": {} + {"f:data": { + ".": {}, + "f:tls-combined.pem": {} }}`), }}, }, @@ -1458,9 +1552,9 @@ func Test_SecretAdditionalOutputFormatsOwnerMismatch(t *testing.T) { ManagedFields: []metav1.ManagedFieldsEntry{ {Manager: fieldManager, FieldsV1: &metav1.FieldsV1{ Raw: []byte(` - {"f:data": { - ".": {}, - "f:key.der": {} + {"f:data": { + ".": {}, + "f:key.der": {} }}`), }}, }, @@ -1481,10 +1575,10 @@ func Test_SecretAdditionalOutputFormatsOwnerMismatch(t *testing.T) { ManagedFields: []metav1.ManagedFieldsEntry{ {Manager: fieldManager, FieldsV1: &metav1.FieldsV1{ Raw: []byte(` - {"f:data": { - ".": {}, + {"f:data": { + ".": {}, "f:tls-combined.pem": {}, - "f:key.der": {} + "f:key.der": {} }}`), }}, }, @@ -1507,8 +1601,8 @@ func Test_SecretAdditionalOutputFormatsOwnerMismatch(t *testing.T) { ManagedFields: []metav1.ManagedFieldsEntry{ {Manager: "not-cert-manager", FieldsV1: &metav1.FieldsV1{ Raw: []byte(` - {"f:data": { - ".": {}, + {"f:data": { + ".": {}, "f:tls-combined.pem": {} }}`), }}, @@ -1532,8 +1626,8 @@ func Test_SecretAdditionalOutputFormatsOwnerMismatch(t *testing.T) { ManagedFields: []metav1.ManagedFieldsEntry{ {Manager: "not-cert-manager", FieldsV1: &metav1.FieldsV1{ Raw: []byte(` - {"f:data": { - ".": {}, + {"f:data": { + ".": {}, "f:key.der": {} }}`), }}, @@ -1558,8 +1652,8 @@ func Test_SecretAdditionalOutputFormatsOwnerMismatch(t *testing.T) { ManagedFields: []metav1.ManagedFieldsEntry{ {Manager: "not-cert-manager", FieldsV1: &metav1.FieldsV1{ Raw: []byte(` - {"f:data": { - ".": {}, + {"f:data": { + ".": {}, "f:tls-combined.pem": {}, "f:key.der": {} }}`), @@ -1584,8 +1678,8 @@ func Test_SecretAdditionalOutputFormatsOwnerMismatch(t *testing.T) { ManagedFields: []metav1.ManagedFieldsEntry{ {Manager: fieldManager, FieldsV1: &metav1.FieldsV1{ Raw: []byte(` - {"f:data": { - ".": {}, + {"f:data": { + ".": {}, "f:tls-combined.pem": {} }}`), }}, @@ -1609,8 +1703,8 @@ func Test_SecretAdditionalOutputFormatsOwnerMismatch(t *testing.T) { ManagedFields: []metav1.ManagedFieldsEntry{ {Manager: fieldManager, FieldsV1: &metav1.FieldsV1{ Raw: []byte(` - {"f:data": { - ".": {}, + {"f:data": { + ".": {}, "f:key.der": {} }}`), }}, @@ -1635,8 +1729,8 @@ func Test_SecretAdditionalOutputFormatsOwnerMismatch(t *testing.T) { ManagedFields: []metav1.ManagedFieldsEntry{ {Manager: fieldManager, FieldsV1: &metav1.FieldsV1{ Raw: []byte(` - {"f:data": { - ".": {}, + {"f:data": { + ".": {}, "f:key.der": {}, "f:tls-combined.pem": {} }}`), @@ -1662,15 +1756,15 @@ func Test_SecretAdditionalOutputFormatsOwnerMismatch(t *testing.T) { ManagedFields: []metav1.ManagedFieldsEntry{ {Manager: fieldManager, FieldsV1: &metav1.FieldsV1{ Raw: []byte(` - {"f:data": { - ".": {}, + {"f:data": { + ".": {}, "f:key.der": {} }}`), }}, {Manager: fieldManager, FieldsV1: &metav1.FieldsV1{ Raw: []byte(` - {"f:data": { - ".": {}, + {"f:data": { + ".": {}, "f:tls-combined.pem": {} }}`), }}, @@ -1695,16 +1789,16 @@ func Test_SecretAdditionalOutputFormatsOwnerMismatch(t *testing.T) { ManagedFields: []metav1.ManagedFieldsEntry{ {Manager: fieldManager, FieldsV1: &metav1.FieldsV1{ Raw: []byte(` - {"f:data": { - ".": {}, + {"f:data": { + ".": {}, "f:tls-combined.pem": {}, "f:key.der": {} }}`), }}, {Manager: "not-cert-manager", FieldsV1: &metav1.FieldsV1{ Raw: []byte(` - {"f:data": { - ".": {}, + {"f:data": { + ".": {}, "f:key.der": {}, "f:tls-combined.pem": {} }}`), @@ -1721,7 +1815,7 @@ func Test_SecretAdditionalOutputFormatsOwnerMismatch(t *testing.T) { for name, test := range tests { t.Run(name, func(t *testing.T) { - gotReason, gotMessage, gotViolation := SecretAdditionalOutputFormatsOwnerMismatch(fieldManager)(test.input) + gotReason, gotMessage, gotViolation := SecretAdditionalOutputFormatsManagedFieldsMismatch(fieldManager)(test.input) assert.Equal(t, test.expReason, gotReason) assert.Equal(t, test.expMessage, gotMessage) assert.Equal(t, test.expViolation, gotViolation) @@ -1733,7 +1827,7 @@ func Test_SecretOwnerReferenceManagedFieldMismatch(t *testing.T) { const fieldManager = "cert-manager-test" crt := gen.Certificate("test-certificate", - gen.SetCertificateUID(types.UID("uid-123")), + gen.SetCertificateUID("uid-123"), ) tests := map[string]struct { @@ -1762,9 +1856,9 @@ func Test_SecretOwnerReferenceManagedFieldMismatch(t *testing.T) { ManagedFields: []metav1.ManagedFieldsEntry{ {Manager: "cert-manager-test", FieldsV1: &metav1.FieldsV1{ Raw: []byte(` - {"f:metadata": { + {"f:metadata": { "f:ownerReferences": { - "k:{\"uid\":\"4c71e68f-5271-4b8d-9df5-5eb71d130d7d\"}": {} + "k:{\"uid\":\"4c71e68f-5271-4b8d-9df5-5eb71d130d7d\"}": {} }}}`), }}, }, @@ -1784,9 +1878,9 @@ func Test_SecretOwnerReferenceManagedFieldMismatch(t *testing.T) { ManagedFields: []metav1.ManagedFieldsEntry{ {Manager: "cert-manager-test", FieldsV1: &metav1.FieldsV1{ Raw: []byte(` - {"f:metadata": { + {"f:metadata": { "f:ownerReferences": { - "k:{\"uid\":\"uid-123\"}": {} + "k:{\"uid\":\"uid-123\"}": {} }}}`), }}, }, @@ -1806,9 +1900,9 @@ func Test_SecretOwnerReferenceManagedFieldMismatch(t *testing.T) { ManagedFields: []metav1.ManagedFieldsEntry{ {Manager: "not-cert-manager-test", FieldsV1: &metav1.FieldsV1{ Raw: []byte(` - {"f:metadata": { + {"f:metadata": { "f:ownerReferences": { - "k:{\"uid\":\"uid-123\"}": {} + "k:{\"uid\":\"uid-123\"}": {} }}}`), }}, }, @@ -1839,9 +1933,9 @@ func Test_SecretOwnerReferenceManagedFieldMismatch(t *testing.T) { ManagedFields: []metav1.ManagedFieldsEntry{ {Manager: "cert-manager-test", FieldsV1: &metav1.FieldsV1{ Raw: []byte(` - {"f:metadata": { + {"f:metadata": { "f:ownerReferences": { - "k:{\"uid\":\"4c71e68f-5271-4b8d-9df5-5eb71d130d7d\"}": {} + "k:{\"uid\":\"4c71e68f-5271-4b8d-9df5-5eb71d130d7d\"}": {} }}}`), }}, }, @@ -1861,9 +1955,9 @@ func Test_SecretOwnerReferenceManagedFieldMismatch(t *testing.T) { ManagedFields: []metav1.ManagedFieldsEntry{ {Manager: "cert-manager-test", FieldsV1: &metav1.FieldsV1{ Raw: []byte(` - {"f:metadata": { + {"f:metadata": { "f:ownerReferences": { - "k:{\"uid\":\"uid-123\"}": {} + "k:{\"uid\":\"uid-123\"}": {} }}}`), }}, }, @@ -1883,9 +1977,9 @@ func Test_SecretOwnerReferenceManagedFieldMismatch(t *testing.T) { ManagedFields: []metav1.ManagedFieldsEntry{ {Manager: "not-cert-manager-test", FieldsV1: &metav1.FieldsV1{ Raw: []byte(` - {"f:metadata": { + {"f:metadata": { "f:ownerReferences": { - "k:{\"uid\":\"uid-123\"}": {} + "k:{\"uid\":\"uid-123\"}": {} }}}`), }}, }, @@ -1909,9 +2003,9 @@ func Test_SecretOwnerReferenceManagedFieldMismatch(t *testing.T) { } } -func Test_SecretOwnerReferenceValueMismatch(t *testing.T) { +func Test_SecretOwnerReferenceMismatch(t *testing.T) { crt := gen.Certificate("test-certificate", - gen.SetCertificateUID(types.UID("uid-123")), + gen.SetCertificateUID("uid-123"), ) tests := map[string]struct { @@ -1938,7 +2032,7 @@ func Test_SecretOwnerReferenceValueMismatch(t *testing.T) { Secret: &corev1.Secret{ ObjectMeta: metav1.ObjectMeta{ OwnerReferences: []metav1.OwnerReference{ - {APIVersion: "foo.bar/v1", Kind: "Foo", Name: "foo", UID: types.UID("abc"), Controller: pointer.Bool(false), BlockOwnerDeletion: pointer.Bool(false)}, + {APIVersion: "foo.bar/v1", Kind: "Foo", Name: "foo", UID: types.UID("abc"), Controller: ptr.To(false), BlockOwnerDeletion: ptr.To(false)}, }, }, }, @@ -1954,8 +2048,8 @@ func Test_SecretOwnerReferenceValueMismatch(t *testing.T) { Secret: &corev1.Secret{ ObjectMeta: metav1.ObjectMeta{ OwnerReferences: []metav1.OwnerReference{ - {APIVersion: "foo.bar/v1", Kind: "Foo", Name: "foo", UID: types.UID("abc"), Controller: pointer.Bool(false), BlockOwnerDeletion: pointer.Bool(false)}, - {APIVersion: "bar.foo/v1", Kind: "Bar", Name: "bar", UID: types.UID("def"), Controller: pointer.Bool(true), BlockOwnerDeletion: pointer.Bool(true)}, + {APIVersion: "foo.bar/v1", Kind: "Foo", Name: "foo", UID: types.UID("abc"), Controller: ptr.To(false), BlockOwnerDeletion: ptr.To(false)}, + {APIVersion: "bar.foo/v1", Kind: "Bar", Name: "bar", UID: types.UID("def"), Controller: ptr.To(true), BlockOwnerDeletion: ptr.To(true)}, }, }, }, @@ -1971,9 +2065,9 @@ func Test_SecretOwnerReferenceValueMismatch(t *testing.T) { Secret: &corev1.Secret{ ObjectMeta: metav1.ObjectMeta{ OwnerReferences: []metav1.OwnerReference{ - {APIVersion: "foo.bar/v1", Kind: "Foo", Name: "foo", UID: types.UID("abc"), Controller: pointer.Bool(false), BlockOwnerDeletion: pointer.Bool(false)}, - {APIVersion: "bar.foo/v1", Kind: "Bar", Name: "bar", UID: types.UID("def"), Controller: pointer.Bool(true), BlockOwnerDeletion: pointer.Bool(true)}, - {APIVersion: "cert-manager.io/v1", Kind: "Certificate", Name: "test-certificate", UID: types.UID("uid-123"), Controller: pointer.Bool(true), BlockOwnerDeletion: pointer.Bool(true)}, + {APIVersion: "foo.bar/v1", Kind: "Foo", Name: "foo", UID: types.UID("abc"), Controller: ptr.To(false), BlockOwnerDeletion: ptr.To(false)}, + {APIVersion: "bar.foo/v1", Kind: "Bar", Name: "bar", UID: types.UID("def"), Controller: ptr.To(true), BlockOwnerDeletion: ptr.To(true)}, + {APIVersion: "cert-manager.io/v1", Kind: "Certificate", Name: "test-certificate", UID: types.UID("uid-123"), Controller: ptr.To(true), BlockOwnerDeletion: ptr.To(true)}, }, }, }, @@ -1989,9 +2083,9 @@ func Test_SecretOwnerReferenceValueMismatch(t *testing.T) { Secret: &corev1.Secret{ ObjectMeta: metav1.ObjectMeta{ OwnerReferences: []metav1.OwnerReference{ - {APIVersion: "foo.bar/v1", Kind: "Foo", Name: "foo", UID: types.UID("abc"), Controller: pointer.Bool(false), BlockOwnerDeletion: pointer.Bool(false)}, - {APIVersion: "bar.foo/v1", Kind: "Bar", Name: "bar", UID: types.UID("def"), Controller: pointer.Bool(true), BlockOwnerDeletion: pointer.Bool(true)}, - {APIVersion: "cert-manager.io/v1", Kind: "Certificate", Name: "foo", UID: types.UID("uid-123"), Controller: pointer.Bool(true), BlockOwnerDeletion: pointer.Bool(true)}, + {APIVersion: "foo.bar/v1", Kind: "Foo", Name: "foo", UID: types.UID("abc"), Controller: ptr.To(false), BlockOwnerDeletion: ptr.To(false)}, + {APIVersion: "bar.foo/v1", Kind: "Bar", Name: "bar", UID: types.UID("def"), Controller: ptr.To(true), BlockOwnerDeletion: ptr.To(true)}, + {APIVersion: "cert-manager.io/v1", Kind: "Certificate", Name: "foo", UID: types.UID("uid-123"), Controller: ptr.To(true), BlockOwnerDeletion: ptr.To(true)}, }, }, }, @@ -2018,7 +2112,7 @@ func Test_SecretOwnerReferenceValueMismatch(t *testing.T) { Secret: &corev1.Secret{ ObjectMeta: metav1.ObjectMeta{ OwnerReferences: []metav1.OwnerReference{ - {APIVersion: "foo.bar/v1", Kind: "Foo", Name: "foo", UID: types.UID("abc"), Controller: pointer.Bool(false), BlockOwnerDeletion: pointer.Bool(false)}, + {APIVersion: "foo.bar/v1", Kind: "Foo", Name: "foo", UID: types.UID("abc"), Controller: ptr.To(false), BlockOwnerDeletion: ptr.To(false)}, }, }, }, @@ -2034,8 +2128,8 @@ func Test_SecretOwnerReferenceValueMismatch(t *testing.T) { Secret: &corev1.Secret{ ObjectMeta: metav1.ObjectMeta{ OwnerReferences: []metav1.OwnerReference{ - {APIVersion: "foo.bar/v1", Kind: "Foo", Name: "foo", UID: types.UID("abc"), Controller: pointer.Bool(false), BlockOwnerDeletion: pointer.Bool(false)}, - {APIVersion: "bar.foo/v1", Kind: "Bar", Name: "bar", UID: types.UID("def"), Controller: pointer.Bool(true), BlockOwnerDeletion: pointer.Bool(true)}, + {APIVersion: "foo.bar/v1", Kind: "Foo", Name: "foo", UID: types.UID("abc"), Controller: ptr.To(false), BlockOwnerDeletion: ptr.To(false)}, + {APIVersion: "bar.foo/v1", Kind: "Bar", Name: "bar", UID: types.UID("def"), Controller: ptr.To(true), BlockOwnerDeletion: ptr.To(true)}, }, }, }, @@ -2051,9 +2145,9 @@ func Test_SecretOwnerReferenceValueMismatch(t *testing.T) { Secret: &corev1.Secret{ ObjectMeta: metav1.ObjectMeta{ OwnerReferences: []metav1.OwnerReference{ - {APIVersion: "foo.bar/v1", Kind: "Foo", Name: "foo", UID: types.UID("abc"), Controller: pointer.Bool(false), BlockOwnerDeletion: pointer.Bool(false)}, - {APIVersion: "bar.foo/v1", Kind: "Bar", Name: "bar", UID: types.UID("def"), Controller: pointer.Bool(true), BlockOwnerDeletion: pointer.Bool(true)}, - {APIVersion: "cert-manager.io/v1", Kind: "Certificate", Name: "test-certificate", UID: types.UID("uid-123"), Controller: pointer.Bool(true), BlockOwnerDeletion: pointer.Bool(true)}, + {APIVersion: "foo.bar/v1", Kind: "Foo", Name: "foo", UID: types.UID("abc"), Controller: ptr.To(false), BlockOwnerDeletion: ptr.To(false)}, + {APIVersion: "bar.foo/v1", Kind: "Bar", Name: "bar", UID: types.UID("def"), Controller: ptr.To(true), BlockOwnerDeletion: ptr.To(true)}, + {APIVersion: "cert-manager.io/v1", Kind: "Certificate", Name: "test-certificate", UID: types.UID("uid-123"), Controller: ptr.To(true), BlockOwnerDeletion: ptr.To(true)}, }, }, }, @@ -2069,9 +2163,9 @@ func Test_SecretOwnerReferenceValueMismatch(t *testing.T) { Secret: &corev1.Secret{ ObjectMeta: metav1.ObjectMeta{ OwnerReferences: []metav1.OwnerReference{ - {APIVersion: "foo.bar/v1", Kind: "Foo", Name: "foo", UID: types.UID("abc"), Controller: pointer.Bool(false), BlockOwnerDeletion: pointer.Bool(false)}, - {APIVersion: "bar.foo/v1", Kind: "Bar", Name: "bar", UID: types.UID("def"), Controller: pointer.Bool(true), BlockOwnerDeletion: pointer.Bool(true)}, - {APIVersion: "cert-manager.io/v1", Kind: "Certificate", Name: "foo", UID: types.UID("uid-123"), Controller: pointer.Bool(true), BlockOwnerDeletion: pointer.Bool(true)}, + {APIVersion: "foo.bar/v1", Kind: "Foo", Name: "foo", UID: types.UID("abc"), Controller: ptr.To(false), BlockOwnerDeletion: ptr.To(false)}, + {APIVersion: "bar.foo/v1", Kind: "Bar", Name: "bar", UID: types.UID("def"), Controller: ptr.To(true), BlockOwnerDeletion: ptr.To(true)}, + {APIVersion: "cert-manager.io/v1", Kind: "Certificate", Name: "foo", UID: types.UID("uid-123"), Controller: ptr.To(true), BlockOwnerDeletion: ptr.To(true)}, }, }, }, @@ -2087,9 +2181,9 @@ func Test_SecretOwnerReferenceValueMismatch(t *testing.T) { Secret: &corev1.Secret{ ObjectMeta: metav1.ObjectMeta{ OwnerReferences: []metav1.OwnerReference{ - {APIVersion: "foo.bar/v1", Kind: "Foo", Name: "foo", UID: types.UID("abc"), Controller: pointer.Bool(false), BlockOwnerDeletion: pointer.Bool(false)}, - {APIVersion: "bar.foo/v1", Kind: "Bar", Name: "bar", UID: types.UID("def"), Controller: pointer.Bool(true), BlockOwnerDeletion: pointer.Bool(true)}, - {APIVersion: "acme.cert-manager.io/v1", Kind: "Certificate", Name: "test-certificate", UID: types.UID("uid-123"), Controller: pointer.Bool(true), BlockOwnerDeletion: pointer.Bool(true)}, + {APIVersion: "foo.bar/v1", Kind: "Foo", Name: "foo", UID: types.UID("abc"), Controller: ptr.To(false), BlockOwnerDeletion: ptr.To(false)}, + {APIVersion: "bar.foo/v1", Kind: "Bar", Name: "bar", UID: types.UID("def"), Controller: ptr.To(true), BlockOwnerDeletion: ptr.To(true)}, + {APIVersion: "acme.cert-manager.io/v1", Kind: "Certificate", Name: "test-certificate", UID: types.UID("uid-123"), Controller: ptr.To(true), BlockOwnerDeletion: ptr.To(true)}, }, }, }, @@ -2105,9 +2199,9 @@ func Test_SecretOwnerReferenceValueMismatch(t *testing.T) { Secret: &corev1.Secret{ ObjectMeta: metav1.ObjectMeta{ OwnerReferences: []metav1.OwnerReference{ - {APIVersion: "foo.bar/v1", Kind: "Foo", Name: "foo", UID: types.UID("abc"), Controller: pointer.Bool(false), BlockOwnerDeletion: pointer.Bool(false)}, - {APIVersion: "bar.foo/v1", Kind: "Bar", Name: "bar", UID: types.UID("def"), Controller: pointer.Bool(true), BlockOwnerDeletion: pointer.Bool(true)}, - {APIVersion: "cert-manager.io/v1", Kind: "Issuer", Name: "test-certificate", UID: types.UID("uid-123"), Controller: pointer.Bool(true), BlockOwnerDeletion: pointer.Bool(true)}, + {APIVersion: "foo.bar/v1", Kind: "Foo", Name: "foo", UID: types.UID("abc"), Controller: ptr.To(false), BlockOwnerDeletion: ptr.To(false)}, + {APIVersion: "bar.foo/v1", Kind: "Bar", Name: "bar", UID: types.UID("def"), Controller: ptr.To(true), BlockOwnerDeletion: ptr.To(true)}, + {APIVersion: "cert-manager.io/v1", Kind: "Issuer", Name: "test-certificate", UID: types.UID("uid-123"), Controller: ptr.To(true), BlockOwnerDeletion: ptr.To(true)}, }, }, }, @@ -2123,9 +2217,9 @@ func Test_SecretOwnerReferenceValueMismatch(t *testing.T) { Secret: &corev1.Secret{ ObjectMeta: metav1.ObjectMeta{ OwnerReferences: []metav1.OwnerReference{ - {APIVersion: "foo.bar/v1", Kind: "Foo", Name: "foo", UID: types.UID("abc"), Controller: pointer.Bool(false), BlockOwnerDeletion: pointer.Bool(false)}, - {APIVersion: "bar.foo/v1", Kind: "Bar", Name: "bar", UID: types.UID("def"), Controller: pointer.Bool(true), BlockOwnerDeletion: pointer.Bool(true)}, - {APIVersion: "cert-manager.io/v1", Kind: "Certificate", Name: "test-certificate", UID: types.UID("uid-123"), Controller: pointer.Bool(false), BlockOwnerDeletion: pointer.Bool(true)}, + {APIVersion: "foo.bar/v1", Kind: "Foo", Name: "foo", UID: types.UID("abc"), Controller: ptr.To(false), BlockOwnerDeletion: ptr.To(false)}, + {APIVersion: "bar.foo/v1", Kind: "Bar", Name: "bar", UID: types.UID("def"), Controller: ptr.To(true), BlockOwnerDeletion: ptr.To(true)}, + {APIVersion: "cert-manager.io/v1", Kind: "Certificate", Name: "test-certificate", UID: types.UID("uid-123"), Controller: ptr.To(false), BlockOwnerDeletion: ptr.To(true)}, }, }, }, @@ -2141,9 +2235,9 @@ func Test_SecretOwnerReferenceValueMismatch(t *testing.T) { Secret: &corev1.Secret{ ObjectMeta: metav1.ObjectMeta{ OwnerReferences: []metav1.OwnerReference{ - {APIVersion: "foo.bar/v1", Kind: "Foo", Name: "foo", UID: types.UID("abc"), Controller: pointer.Bool(false), BlockOwnerDeletion: pointer.Bool(false)}, - {APIVersion: "bar.foo/v1", Kind: "Bar", Name: "bar", UID: types.UID("def"), Controller: pointer.Bool(true), BlockOwnerDeletion: pointer.Bool(true)}, - {APIVersion: "cert-manager.io/v1", Kind: "Certificate", Name: "test-certificate", UID: types.UID("uid-123"), Controller: pointer.Bool(true), BlockOwnerDeletion: pointer.Bool(false)}, + {APIVersion: "foo.bar/v1", Kind: "Foo", Name: "foo", UID: types.UID("abc"), Controller: ptr.To(false), BlockOwnerDeletion: ptr.To(false)}, + {APIVersion: "bar.foo/v1", Kind: "Bar", Name: "bar", UID: types.UID("def"), Controller: ptr.To(true), BlockOwnerDeletion: ptr.To(true)}, + {APIVersion: "cert-manager.io/v1", Kind: "Certificate", Name: "test-certificate", UID: types.UID("uid-123"), Controller: ptr.To(true), BlockOwnerDeletion: ptr.To(false)}, }, }, }, @@ -2157,7 +2251,68 @@ func Test_SecretOwnerReferenceValueMismatch(t *testing.T) { for name, test := range tests { t.Run(name, func(t *testing.T) { - gotReason, gotMessage, gotViolation := SecretOwnerReferenceValueMismatch(test.ownerRefEnabled)(test.input) + gotReason, gotMessage, gotViolation := SecretOwnerReferenceMismatch(test.ownerRefEnabled)(test.input) + assert.Equal(t, test.expReason, gotReason) + assert.Equal(t, test.expMessage, gotMessage) + assert.Equal(t, test.expViolation, gotViolation) + }) + } +} + +func Test_SecretCertificateNameAnnotationsMismatch(t *testing.T) { + crt := gen.Certificate("test-certificate") + + tests := map[string]struct { + input Input + + expReason string + expMessage string + expViolation bool + }{ + "without a CertificateName annotation, should return false": { + input: Input{ + Certificate: crt, + Secret: &corev1.Secret{}, + }, + expReason: "", + expMessage: "", + expViolation: false, + }, + "with a matching CertificateName annotation, should return false": { + input: Input{ + Certificate: crt, + Secret: &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + cmapi.CertificateNameKey: "test-certificate", + }, + }, + }, + }, + expReason: "", + expMessage: "", + expViolation: false, + }, + "with a non-matching CertificateName annotation, should return true": { + input: Input{ + Certificate: crt, + Secret: &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + cmapi.CertificateNameKey: "foo", + }, + }, + }, + }, + expReason: "IncorrectCertificate", + expMessage: "Secret was issued for \"foo\". If this message is not transient, you might have two conflicting Certificates pointing to the same secret.", + expViolation: true, + }, + } + + for name, test := range tests { + t.Run(name, func(t *testing.T) { + gotReason, gotMessage, gotViolation := SecretCertificateNameAnnotationsMismatch(test.input) assert.Equal(t, test.expReason, gotReason) assert.Equal(t, test.expMessage, gotMessage) assert.Equal(t, test.expViolation, gotViolation) diff --git a/internal/controller/certificates/policies/constants.go b/internal/controller/certificates/policies/constants.go index 7a371ac154b..ba320fdc9a0 100644 --- a/internal/controller/certificates/policies/constants.go +++ b/internal/controller/certificates/policies/constants.go @@ -29,25 +29,38 @@ const ( // InvalidCertificate is a policy violation whereby the signed certificate in // the Input Secret could not be parsed or decoded. InvalidCertificate string = "InvalidCertificate" + // InvalidCertificateRequest is a policy violation whereby the CSR in + // the Input CertificateRequest could not be parsed or decoded. + InvalidCertificateRequest string = "InvalidCertificateRequest" + // SecretMismatch is a policy violation reason for a scenario where Secret's // private key does not match spec. SecretMismatch string = "SecretMismatch" // IncorrectIssuer is a policy violation reason for a scenario where // Certificate has been issued by incorrect Issuer. IncorrectIssuer string = "IncorrectIssuer" + // IncorrectCertificate is a policy violation reason for a scenario where + // the Secret referred to by this Certificate's spec.secretName, + // already has a `cert-manager.io/certificate-name` annotation + // with the name of another Certificate. + IncorrectCertificate string = "IncorrectCertificate" // RequestChanged is a policy violation reason for a scenario where // CertificateRequest not valid for Certificate's spec. RequestChanged string = "RequestChanged" // Renewing is a policy violation reason for a scenario where - // Certificate's renewal time is now or in past. + // Certificate's renewal time is now or in the past. Renewing string = "Renewing" // Expired is a policy violation reason for a scenario where Certificate has // expired. Expired string = "Expired" - // SecretTemplateMisMatch is a policy violation whereby the Certificate's + // SecretTemplateMismatch is a policy violation whereby the Certificate's // SecretTemplate is not reflected on the target Secret, either by having // extra, missing, or wrong Annotations or Labels. SecretTemplateMismatch string = "SecretTemplateMismatch" + // SecretManagedMetadataMismatch is a policy violation whereby the Secret is + // missing labels that should have been added by cert-manager + SecretManagedMetadataMismatch string = "SecretManagedMetadataMismatch" + // AdditionalOutputFormatsMismatch is a policy violation whereby the // Certificate's AdditionalOutputFormats is not reflected on the target // Secret, either by having extra, missing, or wrong values. diff --git a/internal/controller/certificates/policies/gatherer.go b/internal/controller/certificates/policies/gatherer.go index df7ab49559e..982a5537d6b 100644 --- a/internal/controller/certificates/policies/gatherer.go +++ b/internal/controller/certificates/policies/gatherer.go @@ -213,8 +213,8 @@ import ( apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/labels" - corelisters "k8s.io/client-go/listers/core/v1" + internalinformers "github.com/cert-manager/cert-manager/internal/informers" cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" cmlisters "github.com/cert-manager/cert-manager/pkg/client/listers/certmanager/v1" "github.com/cert-manager/cert-manager/pkg/controller/certificates" @@ -226,7 +226,7 @@ import ( // its current readiness/state by applying policy functions to it. type Gatherer struct { CertificateRequestLister cmlisters.CertificateRequestLister - SecretLister corelisters.SecretLister + SecretLister internalinformers.SecretLister } // DataForCertificate returns the secret as well as the "current" and "next" diff --git a/internal/controller/certificates/policies/gatherer_test.go b/internal/controller/certificates/policies/gatherer_test.go index 81882e8e7c7..ede6a873aac 100644 --- a/internal/controller/certificates/policies/gatherer_test.go +++ b/internal/controller/certificates/policies/gatherer_test.go @@ -17,22 +17,19 @@ limitations under the License. package policies import ( - "context" "flag" "testing" "time" - logtesting "github.com/go-logr/logr/testing" + "github.com/go-logr/logr/testr" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/runtime" - kscheme "k8s.io/client-go/kubernetes/scheme" "k8s.io/client-go/tools/cache" "k8s.io/klog/v2" fakeclock "k8s.io/utils/clock/testing" - cmscheme "github.com/cert-manager/cert-manager/pkg/api" cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" testpkg "github.com/cert-manager/cert-manager/pkg/controller/test" logf "github.com/cert-manager/cert-manager/pkg/logs" @@ -40,6 +37,13 @@ import ( ) func TestDataForCertificate(t *testing.T) { + cr := func(crName, ownerCertUID string, annot map[string]string) *cmapi.CertificateRequest { + return gen.CertificateRequest(crName, gen.SetCertificateRequestNamespace("ns-1"), + gen.AddCertificateRequestOwnerReferences(gen.CertificateRef("some-cert-name-that-does-not-matter", ownerCertUID)), + gen.AddCertificateRequestAnnotations(annot), + ) + } + tests := map[string]struct { builder *testpkg.Builder givenCert *cmapi.Certificate @@ -70,8 +74,8 @@ func TestDataForCertificate(t *testing.T) { gen.SetCertificateRevision(1), ), builder: &testpkg.Builder{CertManagerObjects: []runtime.Object{ - cr("cr-unknown-rev1", "ns-1", "unknown-uid", map[string]string{"cert-manager.io/certificate-revision": "1"}), - cr("cr-unknown-rev2", "ns-1", "unknown-uid", map[string]string{"cert-manager.io/certificate-revision": "2"}), + cr("cr-unknown-rev1", "unknown-uid", map[string]string{"cert-manager.io/certificate-revision": "1"}), + cr("cr-unknown-rev2", "unknown-uid", map[string]string{"cert-manager.io/certificate-revision": "2"}), }}, wantCurCR: nil, wantNextCR: nil, @@ -81,17 +85,17 @@ func TestDataForCertificate(t *testing.T) { gen.SetCertificateUID("cert-1-uid"), ), builder: &testpkg.Builder{CertManagerObjects: []runtime.Object{ - cr("cr-1-rev1", "ns-1", "cert-1-uid", map[string]string{"cert-manager.io/certificate-revision": "1"}), - cr("cr-1-rev2", "ns-1", "cert-1-uid", map[string]string{"cert-manager.io/certificate-revision": "2"}), + cr("cr-1-rev1", "cert-1-uid", map[string]string{"cert-manager.io/certificate-revision": "1"}), + cr("cr-1-rev2", "cert-1-uid", map[string]string{"cert-manager.io/certificate-revision": "2"}), // Edge cases. - cr("cr-1-norev", "ns-1", "cert-1-uid", nil), - cr("cr-1-empty", "ns-1", "cert-1-uid", map[string]string{"cert-manager.io/certificate-revision": ""}), - cr("cr-unrelated-rev1", "ns-1", "cert-unrelated-uid", map[string]string{"cert-manager.io/certificate-revision": "1"}), - cr("cr-unrelated-rev2", "ns-1", "cert-unrelated-uid", map[string]string{"cert-manager.io/certificate-revision": "2"}), + cr("cr-1-norev", "cert-1-uid", nil), + cr("cr-1-empty", "cert-1-uid", map[string]string{"cert-manager.io/certificate-revision": ""}), + cr("cr-unrelated-rev1", "cert-unrelated-uid", map[string]string{"cert-manager.io/certificate-revision": "1"}), + cr("cr-unrelated-rev2", "cert-unrelated-uid", map[string]string{"cert-manager.io/certificate-revision": "2"}), }}, wantCurCR: nil, - wantNextCR: cr("cr-1-rev1", "ns-1", "cert-1-uid", map[string]string{"cert-manager.io/certificate-revision": "1"}), + wantNextCR: cr("cr-1-rev1", "cert-1-uid", map[string]string{"cert-manager.io/certificate-revision": "1"}), }, "when cert revision=1, should return the current CR with revision=1 and the next CR with revision=2": { givenCert: gen.Certificate("cert-1", gen.SetCertificateNamespace("ns-1"), @@ -99,20 +103,20 @@ func TestDataForCertificate(t *testing.T) { gen.SetCertificateRevision(1), ), builder: &testpkg.Builder{CertManagerObjects: []runtime.Object{ - cr("cr-1-rev1", "ns-1", "cert-1-uid", map[string]string{"cert-manager.io/certificate-revision": "1"}), - cr("cr-1-rev2", "ns-1", "cert-1-uid", map[string]string{"cert-manager.io/certificate-revision": "2"}), - cr("cr-1-rev3", "ns-1", "cert-1-uid", map[string]string{"cert-manager.io/certificate-revision": "3"}), + cr("cr-1-rev1", "cert-1-uid", map[string]string{"cert-manager.io/certificate-revision": "1"}), + cr("cr-1-rev2", "cert-1-uid", map[string]string{"cert-manager.io/certificate-revision": "2"}), + cr("cr-1-rev3", "cert-1-uid", map[string]string{"cert-manager.io/certificate-revision": "3"}), // Edge cases. - cr("cr-1-no-revision", "ns-1", "cert-1-uid", nil), - cr("cr-1-empty", "ns-1", "cert-1-uid", map[string]string{"cert-manager.io/certificate-revision": ""}), - cr("cr-2-rev1", "ns-1", "cert-2-uid", map[string]string{"cert-manager.io/certificate-revision": "1"}), - cr("cr-unrelated-rev1", "ns-1", "cert-unrelated-uid", map[string]string{"cert-manager.io/certificate-revision": "1"}), - cr("cr-unrelated-rev2", "ns-1", "cert-unrelated-uid", map[string]string{"cert-manager.io/certificate-revision": "2"}), - cr("cr-unrelated-rev3", "ns-1", "cert-unrelated-uid", map[string]string{"cert-manager.io/certificate-revision": "3"}), + cr("cr-1-no-revision", "cert-1-uid", nil), + cr("cr-1-empty", "cert-1-uid", map[string]string{"cert-manager.io/certificate-revision": ""}), + cr("cr-2-rev1", "cert-2-uid", map[string]string{"cert-manager.io/certificate-revision": "1"}), + cr("cr-unrelated-rev1", "cert-unrelated-uid", map[string]string{"cert-manager.io/certificate-revision": "1"}), + cr("cr-unrelated-rev2", "cert-unrelated-uid", map[string]string{"cert-manager.io/certificate-revision": "2"}), + cr("cr-unrelated-rev3", "cert-unrelated-uid", map[string]string{"cert-manager.io/certificate-revision": "3"}), }}, - wantCurCR: cr("cr-1-rev1", "ns-1", "cert-1-uid", map[string]string{"cert-manager.io/certificate-revision": "1"}), - wantNextCR: cr("cr-1-rev2", "ns-1", "cert-1-uid", map[string]string{"cert-manager.io/certificate-revision": "2"}), + wantCurCR: cr("cr-1-rev1", "cert-1-uid", map[string]string{"cert-manager.io/certificate-revision": "1"}), + wantNextCR: cr("cr-1-rev2", "cert-1-uid", map[string]string{"cert-manager.io/certificate-revision": "2"}), }, "should error when duplicate current CRs are found": { givenCert: gen.Certificate("cert-1", gen.SetCertificateNamespace("ns-1"), @@ -120,8 +124,8 @@ func TestDataForCertificate(t *testing.T) { gen.SetCertificateRevision(1), ), builder: &testpkg.Builder{CertManagerObjects: []runtime.Object{ - cr("cr-1-rev1a", "ns-1", "cert-1-uid", map[string]string{"cert-manager.io/certificate-revision": "1"}), - cr("cr-1-rev1b", "ns-1", "cert-1-uid", map[string]string{"cert-manager.io/certificate-revision": "1"}), + cr("cr-1-rev1a", "cert-1-uid", map[string]string{"cert-manager.io/certificate-revision": "1"}), + cr("cr-1-rev1b", "cert-1-uid", map[string]string{"cert-manager.io/certificate-revision": "1"}), }}, wantErr: `multiple CertificateRequests were found for the 'current' revision 1, issuance is skipped until there are no more duplicates`, }, @@ -131,8 +135,8 @@ func TestDataForCertificate(t *testing.T) { gen.SetCertificateRevision(1), ), builder: &testpkg.Builder{CertManagerObjects: []runtime.Object{ - cr("cr-1-rev2a", "ns-1", "cert-1-uid", map[string]string{"cert-manager.io/certificate-revision": "2"}), - cr("cr-1-rev2b", "ns-1", "cert-1-uid", map[string]string{"cert-manager.io/certificate-revision": "2"}), + cr("cr-1-rev2a", "cert-1-uid", map[string]string{"cert-manager.io/certificate-revision": "2"}), + cr("cr-1-rev2b", "cert-1-uid", map[string]string{"cert-manager.io/certificate-revision": "2"}), }}, wantErr: `multiple CertificateRequests were found for the 'next' revision 2, issuance is skipped until there are no more duplicates`, }, @@ -140,21 +144,12 @@ func TestDataForCertificate(t *testing.T) { for name, test := range tests { t.Run(name, func(t *testing.T) { fakeClockStart, _ := time.Parse(time.RFC3339, "2021-01-02T15:04:05Z07:00") - log := logtesting.NewTestLogger(t) - turnOnKlogIfVerboseTest(t) + log := testr.New(t) + turnOnKlogIfVerboseTest() test.builder.T = t test.builder.Clock = fakeclock.NewFakeClock(fakeClockStart) - // In this test, we do not use Register(controller.Context). - // The Register(controller.Context) usually takes care of - // triggering the init() func in ./pkg/api/scheme.go. If we - // forget to have the init() func called, the apiVersion and - // kind fields on cert-manager objects are not automatically - // filled, which breaks the lister cache (i.e., the "indexer"). - _ = cmscheme.Scheme - _ = kscheme.Scheme - test.builder.Init() // One weird behavior in client-go is that listers won't return @@ -167,8 +162,12 @@ func TestDataForCertificate(t *testing.T) { // tests, we "force" the creation of the indexer for the CR // type by registering a fake handler. noop := cache.ResourceEventHandlerFuncs{AddFunc: func(obj interface{}) {}} - test.builder.SharedInformerFactory.Certmanager().V1().CertificateRequests().Informer().AddEventHandler(noop) - test.builder.KubeSharedInformerFactory.Core().V1().Secrets().Informer().AddEventHandler(noop) + if _, err := test.builder.SharedInformerFactory.Certmanager().V1().CertificateRequests().Informer().AddEventHandler(noop); err != nil { + t.Fatalf("failed to add event handler to CertificateRequest informer: %v", err) + } + if _, err := test.builder.KubeSharedInformerFactory.Secrets().Informer().AddEventHandler(noop); err != nil { + t.Fatalf("failed to add event handler to Secret informer: %v", err) + } // Even though we are only relying on listers in this unit test // and do not use the informer event handlers, we still need to @@ -212,10 +211,10 @@ func TestDataForCertificate(t *testing.T) { g := &Gatherer{ CertificateRequestLister: test.builder.SharedInformerFactory.Certmanager().V1().CertificateRequests().Lister(), - SecretLister: test.builder.KubeSharedInformerFactory.Core().V1().Secrets().Lister(), + SecretLister: test.builder.KubeSharedInformerFactory.Secrets().Lister(), } - ctx := logf.NewContext(context.Background(), logf.WithResource(log, test.givenCert)) + ctx := logf.NewContext(t.Context(), logf.WithResource(log, test.givenCert)) got, gotErr := g.DataForCertificate(ctx, test.givenCert) if test.wantErr != "" { @@ -235,7 +234,7 @@ func TestDataForCertificate(t *testing.T) { // The logs are helpful for debugging client-go-related issues (informer // not starting...). This function passes the flag -v=4 to klog when the // tests are being run with -v. Otherwise, the default klog level is used. -func turnOnKlogIfVerboseTest(t *testing.T) { +func turnOnKlogIfVerboseTest() { hasVerboseFlag := flag.Lookup("test.v").Value.String() == "true" if !hasVerboseFlag { return @@ -245,10 +244,3 @@ func turnOnKlogIfVerboseTest(t *testing.T) { klog.InitFlags(klogFlags) _ = klogFlags.Set("v", "4") } - -func cr(crName, crNamespace, ownerCertUID string, annot map[string]string) *cmapi.CertificateRequest { - return gen.CertificateRequest(crName, gen.SetCertificateRequestNamespace(crNamespace), - gen.AddCertificateRequestOwnerReferences(gen.CertificateRef("some-cert-name-that-does-not-matter", ownerCertUID)), - gen.AddCertificateRequestAnnotations(annot), - ) -} diff --git a/internal/controller/certificates/policies/policies.go b/internal/controller/certificates/policies/policies.go index 20e5893f25f..f523d28da78 100644 --- a/internal/controller/certificates/policies/policies.go +++ b/internal/controller/certificates/policies/policies.go @@ -63,29 +63,39 @@ func (c Chain) Evaluate(input Input) (string, string, bool) { return "", "", false } -// NewTriggerPolicyChain includes trigger policy checks, which if return true, +// NewTriggerPolicyChain includes trigger policy checks, which if returns true, // should cause a Certificate to be marked for issuance. func NewTriggerPolicyChain(c clock.Clock) Chain { return Chain{ - SecretDoesNotExist, - SecretIsMissingData, - SecretPublicKeysDiffer, - SecretPrivateKeyMatchesSpec, - SecretIssuerAnnotationsNotUpToDate, - CurrentCertificateRequestNotValidForSpec, - CurrentCertificateNearingExpiry(c), + SecretDoesNotExist, // Make sure the Secret exists + SecretIsMissingData, // Make sure the Secret has the required keys set + SecretPublicKeysDiffer, // Make sure the PrivateKey and PublicKey match in the Secret + + SecretIssuerAnnotationsMismatch, // Make sure the Secret's IssuerRef annotations match the Certificate spec + SecretCertificateNameAnnotationsMismatch, // Make sure the Secret's CertificateName annotation matches the Certificate's name + + SecretPrivateKeyMismatchesSpec, // Make sure the PrivateKey Type and Size match the Certificate spec + SecretPublicKeyDiffersFromCurrentCertificateRequest, // Make sure the Secret's PublicKey matches the current CertificateRequest + CurrentCertificateRequestMismatchesSpec, // Make sure the current CertificateRequest matches the Certificate spec + CurrentCertificateNearingExpiry(c), // Make sure the Certificate in the Secret is not nearing expiry } } -// NewReadinessPolicyChain includes readiness policy checks, which if return +// NewReadinessPolicyChain includes readiness policy checks, which if returns // true, would cause a Certificate to be marked as not ready. func NewReadinessPolicyChain(c clock.Clock) Chain { return Chain{ - SecretDoesNotExist, - SecretIsMissingData, - SecretPublicKeysDiffer, - CurrentCertificateRequestNotValidForSpec, - CurrentCertificateHasExpired(c), + SecretDoesNotExist, // Make sure the Secret exists + SecretIsMissingData, // Make sure the Secret has the required keys set + SecretPublicKeysDiffer, // Make sure the PrivateKey and PublicKey match in the Secret + + SecretIssuerAnnotationsMismatch, // Make sure the Secret's IssuerRef annotations match the Certificate spec + SecretCertificateNameAnnotationsMismatch, // Make sure the Secret's CertificateName annotation matches the Certificate's name + + SecretPrivateKeyMismatchesSpec, // Make sure the PrivateKey Type and Size match the Certificate spec + SecretPublicKeyDiffersFromCurrentCertificateRequest, // Make sure the Secret's PublicKey matches the current CertificateRequest + CurrentCertificateRequestMismatchesSpec, // Make sure the current CertificateRequest matches the Certificate spec + CurrentCertificateHasExpired(c), // Make sure the Certificate in the Secret has not expired } } @@ -94,12 +104,17 @@ func NewReadinessPolicyChain(c clock.Clock) Chain { // correctness of metadata and output formats of Certificate's Secrets. func NewSecretPostIssuancePolicyChain(ownerRefEnabled bool, fieldManager string) Chain { return Chain{ - SecretTemplateMismatchesSecret, - SecretTemplateMismatchesSecretManagedFields(fieldManager), - SecretAdditionalOutputFormatsDataMismatch, - SecretAdditionalOutputFormatsOwnerMismatch(fieldManager), + SecretBaseLabelsMismatch, // Make sure the managed labels have the correct values + SecretCertificateDetailsAnnotationsMismatch, // Make sure the managed certificate details annotations have the correct values + SecretManagedLabelsAndAnnotationsManagedFieldsMismatch(fieldManager), // Make sure only the expected managed labels and annotations exist + SecretSecretTemplateMismatch, // Make sure the template label and annotation values match the secret + SecretSecretTemplateManagedFieldsMismatch(fieldManager), // Make sure only the expected template labels and annotations exist + SecretAdditionalOutputFormatsMismatch, + SecretAdditionalOutputFormatsManagedFieldsMismatch(fieldManager), + SecretOwnerReferenceMismatch(ownerRefEnabled), SecretOwnerReferenceManagedFieldMismatch(ownerRefEnabled, fieldManager), - SecretOwnerReferenceValueMismatch(ownerRefEnabled), + + SecretKeystoreFormatMismatch, } } @@ -107,8 +122,8 @@ func NewSecretPostIssuancePolicyChain(ownerRefEnabled bool, fieldManager string) // temporary certificate is valid. func NewTemporaryCertificatePolicyChain() Chain { return Chain{ - SecretDoesNotExist, - SecretIsMissingData, - SecretPublicKeysDiffer, + SecretDoesNotExist, // Make sure the Secret exists + SecretIsMissingData, // Make sure the Secret has the required keys set + SecretPublicKeysDiffer, // Make sure the PrivateKey and PublicKey match in the Secret } } diff --git a/internal/controller/certificates/secrets.go b/internal/controller/certificates/secrets.go index 0a401c2a508..18bb769482c 100644 --- a/internal/controller/certificates/secrets.go +++ b/internal/controller/certificates/secrets.go @@ -19,42 +19,80 @@ package certificates import ( "bytes" "crypto/x509" - "encoding/pem" - "strings" - apiutil "github.com/cert-manager/cert-manager/pkg/api/util" + "github.com/cert-manager/cert-manager/internal/pem" cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" + cmutil "github.com/cert-manager/cert-manager/pkg/util" utilpki "github.com/cert-manager/cert-manager/pkg/util/pki" ) -// AnnotationsForCertificateSecret returns a map which is set on all +// AnnotationsForCertificate returns a map which is set on all // Certificate Secret's Annotations when issued. These annotations contain -// information about the Issuer and Certificate. -// If the X.509 certificate is not-nil, additional annotations will be added -// relating to its Common Name and Subject Alternative Names. -func AnnotationsForCertificateSecret(crt *cmapi.Certificate, certificate *x509.Certificate) map[string]string { +// information about the Certificate. +// If the X.509 certificate is nil, an empty map will be returned. +func AnnotationsForCertificate(certificate *x509.Certificate) (map[string]string, error) { annotations := make(map[string]string) - annotations[cmapi.CertificateNameKey] = crt.Name - annotations[cmapi.IssuerNameAnnotationKey] = crt.Spec.IssuerRef.Name - annotations[cmapi.IssuerKindAnnotationKey] = apiutil.IssuerKind(crt.Spec.IssuerRef) - annotations[cmapi.IssuerGroupAnnotationKey] = crt.Spec.IssuerRef.Group - - // Only add certificate data if certificate is non-nil. - if certificate != nil { - annotations[cmapi.CommonNameAnnotationKey] = certificate.Subject.CommonName - annotations[cmapi.AltNamesAnnotationKey] = strings.Join(certificate.DNSNames, ",") - annotations[cmapi.IPSANAnnotationKey] = strings.Join(utilpki.IPAddressesToString(certificate.IPAddresses), ",") - annotations[cmapi.URISANAnnotationKey] = strings.Join(utilpki.URLsToString(certificate.URIs), ",") + if certificate == nil { + return annotations, nil } - return annotations + // TODO: the reason that for some annotations we keep empty annotations and we don't for others is not clear. + // The keepEmpty parameter is only used here to maintain this unexplained previous behaviour. + + var encodingErr error + addStringAnnotation := func(keepEmpty bool, key string, value string) { + if len(value) == 0 && !keepEmpty { + return + } + annotations[key] = value + } + addCSVEncodedAnnotation := func(keepEmpty bool, key string, values []string) { + if len(values) == 0 && !keepEmpty { + return + } + + csvString, err := cmutil.JoinWithEscapeCSV(values) + if err != nil { + encodingErr = err + return + } + annotations[key] = csvString + } + + addStringAnnotation(true, cmapi.CommonNameAnnotationKey, certificate.Subject.CommonName) + addStringAnnotation(false, cmapi.SubjectSerialNumberAnnotationKey, certificate.Subject.SerialNumber) + + addCSVEncodedAnnotation(false, cmapi.SubjectOrganizationsAnnotationKey, certificate.Subject.Organization) + addCSVEncodedAnnotation(false, cmapi.SubjectOrganizationalUnitsAnnotationKey, certificate.Subject.OrganizationalUnit) + addCSVEncodedAnnotation(false, cmapi.SubjectCountriesAnnotationKey, certificate.Subject.Country) + addCSVEncodedAnnotation(false, cmapi.SubjectProvincesAnnotationKey, certificate.Subject.Province) + addCSVEncodedAnnotation(false, cmapi.SubjectLocalitiesAnnotationKey, certificate.Subject.Locality) + addCSVEncodedAnnotation(false, cmapi.SubjectPostalCodesAnnotationKey, certificate.Subject.PostalCode) + addCSVEncodedAnnotation(false, cmapi.SubjectStreetAddressesAnnotationKey, certificate.Subject.StreetAddress) + + addCSVEncodedAnnotation(false, cmapi.EmailsAnnotationKey, certificate.EmailAddresses) + addCSVEncodedAnnotation(true, cmapi.AltNamesAnnotationKey, certificate.DNSNames) + addCSVEncodedAnnotation(true, cmapi.IPSANAnnotationKey, utilpki.IPAddressesToString(certificate.IPAddresses)) + addCSVEncodedAnnotation(true, cmapi.URISANAnnotationKey, utilpki.URLsToString(certificate.URIs)) + + if encodingErr != nil { + return nil, encodingErr + } + + return annotations, nil } // OutputFormatDER returns the byte slice of the private key in DER format. To // be used for Certificate's Additional Output Format DER. func OutputFormatDER(privateKey []byte) []byte { - block, _ := pem.Decode(privateKey) + // NOTE: This call to pem.SafeDecodePrivateKey ignores errors. + // This is acceptable here since we're calling this function only on PEM data which we created + // by encoding the private key. As such, we can be fairly confident that: + // 1) The PEM is valid + // 2) The PEM isn't attacker-controlled (and as such unsafe to decode) + + block, _, _ := pem.SafeDecodePrivateKey(privateKey) return block.Bytes } diff --git a/internal/controller/certificates/secrets_test.go b/internal/controller/certificates/secrets_test.go index 7ac3d591977..f0b192a4e88 100644 --- a/internal/controller/certificates/secrets_test.go +++ b/internal/controller/certificates/secrets_test.go @@ -24,10 +24,6 @@ import ( "testing" "github.com/stretchr/testify/assert" - - cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" - cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" - "github.com/cert-manager/cert-manager/test/unit/gen" ) func Test_AnnotationsForCertificateSecret(t *testing.T) { @@ -39,125 +35,100 @@ func Test_AnnotationsForCertificateSecret(t *testing.T) { } tests := map[string]struct { - crt *cmapi.Certificate certificate *x509.Certificate expAnnotations map[string]string }{ "if pass non-nil certificate, expect all Annotations to be present": { - crt: gen.Certificate("test-certificate", - gen.SetCertificateIssuer(cmmeta.ObjectReference{Name: "another-test-issuer", Kind: "GoogleCASIssuer", Group: "my-group.hello.world"}), - ), certificate: &x509.Certificate{ Subject: pkix.Name{ - CommonName: "cert-manager", + CommonName: "cert-manager", + Organization: []string{"Example Organization 1", "Example Organization 2"}, + OrganizationalUnit: []string{"Example Organizational Unit 1", "Example Organizational Unit 2"}, + Country: []string{"Country 1", "Country 2"}, + Province: []string{"Province 1", "Province 2"}, + Locality: []string{"City 1", "City 2"}, + StreetAddress: []string{"1725 Slough Avenue, Suite 200, Scranton Business Park", "123 Example St"}, + PostalCode: []string{"55555", "12345"}, + SerialNumber: "12345678", }, - DNSNames: []string{"example.com", "cert-manager.io"}, - IPAddresses: []net.IP{{1, 1, 1, 1}, {1, 2, 3, 4}}, - URIs: urls, + DNSNames: []string{"example.com", "cert-manager.io"}, + IPAddresses: []net.IP{{1, 1, 1, 1}, {1, 2, 3, 4}}, + URIs: urls, + EmailAddresses: []string{"test1@example.com", "test2@cert-manager.io"}, }, expAnnotations: map[string]string{ - "cert-manager.io/certificate-name": "test-certificate", - "cert-manager.io/issuer-name": "another-test-issuer", - "cert-manager.io/issuer-kind": "GoogleCASIssuer", - "cert-manager.io/issuer-group": "my-group.hello.world", - "cert-manager.io/common-name": "cert-manager", - "cert-manager.io/alt-names": "example.com,cert-manager.io", - "cert-manager.io/ip-sans": "1.1.1.1,1.2.3.4", - "cert-manager.io/uri-sans": "spiffe.io//cert-manager.io/test,spiffe.io//hello.world", + "cert-manager.io/common-name": "cert-manager", + "cert-manager.io/alt-names": "example.com,cert-manager.io", + "cert-manager.io/ip-sans": "1.1.1.1,1.2.3.4", + "cert-manager.io/uri-sans": "spiffe.io//cert-manager.io/test,spiffe.io//hello.world", + "cert-manager.io/email-sans": "test1@example.com,test2@cert-manager.io", + "cert-manager.io/subject-organizations": "Example Organization 1,Example Organization 2", + "cert-manager.io/subject-organizationalunits": "Example Organizational Unit 1,Example Organizational Unit 2", + "cert-manager.io/subject-countries": "Country 1,Country 2", + "cert-manager.io/subject-provinces": "Province 1,Province 2", + "cert-manager.io/subject-localities": "City 1,City 2", + "cert-manager.io/subject-streetaddresses": "\"1725 Slough Avenue, Suite 200, Scranton Business Park\",123 Example St", + "cert-manager.io/subject-postalcodes": "55555,12345", + "cert-manager.io/subject-serialnumber": "12345678", }, }, "if pass non-nil certificate with only CommonName, expect all Annotations to be present": { - crt: gen.Certificate("test-certificate", - gen.SetCertificateIssuer(cmmeta.ObjectReference{Name: "another-test-issuer", Kind: "GoogleCASIssuer", Group: "my-group.hello.world"}), - ), certificate: &x509.Certificate{ Subject: pkix.Name{ CommonName: "cert-manager", }, }, expAnnotations: map[string]string{ - "cert-manager.io/certificate-name": "test-certificate", - "cert-manager.io/issuer-name": "another-test-issuer", - "cert-manager.io/issuer-kind": "GoogleCASIssuer", - "cert-manager.io/issuer-group": "my-group.hello.world", - "cert-manager.io/common-name": "cert-manager", - "cert-manager.io/alt-names": "", - "cert-manager.io/ip-sans": "", - "cert-manager.io/uri-sans": "", + "cert-manager.io/common-name": "cert-manager", + "cert-manager.io/alt-names": "", + "cert-manager.io/ip-sans": "", + "cert-manager.io/uri-sans": "", }, }, "if pass non-nil certificate with only IP Addresses, expect all Annotations to be present": { - crt: gen.Certificate("test-certificate", - gen.SetCertificateIssuer(cmmeta.ObjectReference{Name: "another-test-issuer", Kind: "GoogleCASIssuer", Group: "my-group.hello.world"}), - ), certificate: &x509.Certificate{ IPAddresses: []net.IP{{1, 1, 1, 1}, {1, 2, 3, 4}}, }, expAnnotations: map[string]string{ - "cert-manager.io/certificate-name": "test-certificate", - "cert-manager.io/issuer-name": "another-test-issuer", - "cert-manager.io/issuer-kind": "GoogleCASIssuer", - "cert-manager.io/issuer-group": "my-group.hello.world", - "cert-manager.io/common-name": "", - "cert-manager.io/alt-names": "", - "cert-manager.io/ip-sans": "1.1.1.1,1.2.3.4", - "cert-manager.io/uri-sans": "", + "cert-manager.io/common-name": "", + "cert-manager.io/alt-names": "", + "cert-manager.io/ip-sans": "1.1.1.1,1.2.3.4", + "cert-manager.io/uri-sans": "", }, }, "if pass non-nil certificate with only URI SANs, expect all Annotations to be present": { - crt: gen.Certificate("test-certificate", - gen.SetCertificateIssuer(cmmeta.ObjectReference{Name: "another-test-issuer", Kind: "GoogleCASIssuer", Group: "my-group.hello.world"}), - ), certificate: &x509.Certificate{ URIs: urls, }, expAnnotations: map[string]string{ - "cert-manager.io/certificate-name": "test-certificate", - "cert-manager.io/issuer-name": "another-test-issuer", - "cert-manager.io/issuer-kind": "GoogleCASIssuer", - "cert-manager.io/issuer-group": "my-group.hello.world", - "cert-manager.io/common-name": "", - "cert-manager.io/alt-names": "", - "cert-manager.io/ip-sans": "", - "cert-manager.io/uri-sans": "spiffe.io//cert-manager.io/test,spiffe.io//hello.world", + "cert-manager.io/common-name": "", + "cert-manager.io/alt-names": "", + "cert-manager.io/ip-sans": "", + "cert-manager.io/uri-sans": "spiffe.io//cert-manager.io/test,spiffe.io//hello.world", }, }, "if pass non-nil certificate with only DNS names, expect all Annotations to be present": { - crt: gen.Certificate("test-certificate", - gen.SetCertificateIssuer(cmmeta.ObjectReference{Name: "another-test-issuer", Kind: "GoogleCASIssuer", Group: "my-group.hello.world"}), - ), certificate: &x509.Certificate{ DNSNames: []string{"example.com", "cert-manager.io"}, }, expAnnotations: map[string]string{ - "cert-manager.io/certificate-name": "test-certificate", - "cert-manager.io/issuer-name": "another-test-issuer", - "cert-manager.io/issuer-kind": "GoogleCASIssuer", - "cert-manager.io/issuer-group": "my-group.hello.world", - "cert-manager.io/common-name": "", - "cert-manager.io/alt-names": "example.com,cert-manager.io", - "cert-manager.io/ip-sans": "", - "cert-manager.io/uri-sans": "", + "cert-manager.io/common-name": "", + "cert-manager.io/alt-names": "example.com,cert-manager.io", + "cert-manager.io/ip-sans": "", + "cert-manager.io/uri-sans": "", }, }, "if no certificate data, then expect no X.509 related annotations": { - crt: gen.Certificate("test-certificate", - gen.SetCertificateIssuer(cmmeta.ObjectReference{Name: "test-issuer", Kind: "", Group: "cert-manager.io"}), - ), - certificate: nil, - expAnnotations: map[string]string{ - "cert-manager.io/certificate-name": "test-certificate", - "cert-manager.io/issuer-name": "test-issuer", - "cert-manager.io/issuer-kind": "Issuer", - "cert-manager.io/issuer-group": "cert-manager.io", - }, + certificate: nil, + expAnnotations: map[string]string{}, }, } for name, test := range tests { t.Run(name, func(t *testing.T) { - gotAnnotations := AnnotationsForCertificateSecret(test.crt, test.certificate) + gotAnnotations, err := AnnotationsForCertificate(test.certificate) assert.Equal(t, test.expAnnotations, gotAnnotations) + assert.Equal(t, nil, err) }) } } diff --git a/internal/controller/challenges/apply.go b/internal/controller/challenges/apply.go index 922fd76acdc..8edccd7ab87 100644 --- a/internal/controller/challenges/apply.go +++ b/internal/controller/challenges/apply.go @@ -23,13 +23,13 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" apitypes "k8s.io/apimachinery/pkg/types" - "k8s.io/utils/pointer" + "k8s.io/utils/ptr" cmacme "github.com/cert-manager/cert-manager/pkg/apis/acme/v1" cmclient "github.com/cert-manager/cert-manager/pkg/client/clientset/versioned" ) -// Apply will make a Apply API call with the given client to the challenges +// Apply will make an Apply API call with the given client to the challenges // endpoint. All data in the given Challenges object is dropped; expect for the // name, namespace, and spec object. The given fieldManager is will be used as // the FieldManager in the Apply call. Always sets Force Apply to true. @@ -41,11 +41,11 @@ func Apply(ctx context.Context, cl cmclient.Interface, fieldManager string, chal return cl.AcmeV1().Challenges(challenge.Namespace).Patch( ctx, challenge.Name, apitypes.ApplyPatchType, challengeData, - metav1.PatchOptions{Force: pointer.Bool(true), FieldManager: fieldManager}, + metav1.PatchOptions{Force: ptr.To(true), FieldManager: fieldManager}, ) } -// ApplyStatus will make a Apply API call with the given client to the +// ApplyStatus will make an Apply API call with the given client to the // challenges status sub-resource endpoint. All data in the given Challenges // object is dropped; expect for the name, namespace, and status object. The // given fieldManager is will be used as the FieldManager in the Apply call. @@ -58,12 +58,12 @@ func ApplyStatus(ctx context.Context, cl cmclient.Interface, fieldManager string return cl.AcmeV1().Challenges(challenge.Namespace).Patch( ctx, challenge.Name, apitypes.ApplyPatchType, challengeData, - metav1.PatchOptions{Force: pointer.Bool(true), FieldManager: fieldManager}, "status", + metav1.PatchOptions{Force: ptr.To(true), FieldManager: fieldManager}, "status", ) } // serializeApply converts the given Challenge object in JSON. Only the -// objectmeta, and spec fields will be copied and encoded into the serialized +// ObjectMeta, and Spec fields will be copied and encoded into the serialized // slice. All other fields will be left at their zero value. // TypeMeta will be populated with the Kind "Challenge" and API Version // "acme.cert-manager.io/v1" respectively. diff --git a/internal/controller/challenges/apply_test.go b/internal/controller/challenges/apply_test.go index d8a3d6896c5..387cb9c6841 100644 --- a/internal/controller/challenges/apply_test.go +++ b/internal/controller/challenges/apply_test.go @@ -22,9 +22,9 @@ import ( "sync" "testing" - fuzz "github.com/google/gofuzz" "github.com/stretchr/testify/assert" apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" + "sigs.k8s.io/randfill" cmacme "github.com/cert-manager/cert-manager/pkg/apis/acme/v1" ) @@ -39,19 +39,19 @@ func Test_serializeApply(t *testing.T) { jobs := make(chan int) wg.Add(numJobs) - for i := 0; i < 3; i++ { + for range 3 { go func() { for j := range jobs { t.Run("fuzz_"+strconv.Itoa(j), func(t *testing.T) { var challenge cmacme.Challenge - fuzz.New().NilChance(0.5).Funcs( - func(challenge *cmacme.Challenge, c fuzz.Continue) { + randfill.New().NilChance(0.5).Funcs( + func(challenge *cmacme.Challenge, c randfill.Continue) { if challenge.Spec.Solver.DNS01 != nil && challenge.Spec.Solver.DNS01.Webhook != nil { // Config can only hold data which originates from proper JSON. challenge.Spec.Solver.DNS01.Webhook.Config = &apiextensionsv1.JSON{Raw: []byte(`{"some": {"json": "test"}, "string": 42}`)} } }, - ).Fuzz(&challenge) + ).Fill(&challenge) // Test regex with non-empty status. challengeData, err := serializeApply(&challenge) @@ -69,7 +69,7 @@ func Test_serializeApply(t *testing.T) { }() } - for i := 0; i < numJobs; i++ { + for i := range numJobs { jobs <- i } close(jobs) @@ -78,8 +78,8 @@ func Test_serializeApply(t *testing.T) { func Test_serializeApplyStatus(t *testing.T) { const ( - expReg = `^{"kind":"Challenge","apiVersion":"acme.cert-manager.io/v1","metadata":{"name":"foo","namespace":"bar","creationTimestamp":null},"spec":{"url":"","authorizationURL":"","dnsName":"","wildcard":false,"type":"","token":"","key":"","solver":{},"issuerRef":{"name":""}},"status":{.*}$` - expEmpty = `{"kind":"Challenge","apiVersion":"acme.cert-manager.io/v1","metadata":{"name":"foo","namespace":"bar","creationTimestamp":null},"spec":{"url":"","authorizationURL":"","dnsName":"","wildcard":false,"type":"","token":"","key":"","solver":{},"issuerRef":{"name":""}},"status":{"processing":false,"presented":false}}` + expReg = `^{"kind":"Challenge","apiVersion":"acme.cert-manager.io/v1","metadata":{"name":"foo","namespace":"bar"},"spec":{"url":"","authorizationURL":"","dnsName":"","wildcard":false,"type":"","token":"","key":"","solver":{},"issuerRef":{"name":""}},"status":{.*}$` + expEmpty = `{"kind":"Challenge","apiVersion":"acme.cert-manager.io/v1","metadata":{"name":"foo","namespace":"bar"},"spec":{"url":"","authorizationURL":"","dnsName":"","wildcard":false,"type":"","token":"","key":"","solver":{},"issuerRef":{"name":""}},"status":{"processing":false,"presented":false}}` numJobs = 10000 ) @@ -87,12 +87,12 @@ func Test_serializeApplyStatus(t *testing.T) { jobs := make(chan int) wg.Add(numJobs) - for i := 0; i < 3; i++ { + for range 3 { go func() { for j := range jobs { t.Run("fuzz_"+strconv.Itoa(j), func(t *testing.T) { var challenge cmacme.Challenge - fuzz.New().NilChance(0.5).Fuzz(&challenge) + randfill.New().NilChance(0.5).Fill(&challenge) challenge.Name = "foo" challenge.Namespace = "bar" @@ -118,7 +118,7 @@ func Test_serializeApplyStatus(t *testing.T) { }() } - for i := 0; i < numJobs; i++ { + for i := range numJobs { jobs <- i } close(jobs) diff --git a/internal/controller/feature/features.go b/internal/controller/feature/features.go index db4e65a68bc..059574615ad 100644 --- a/internal/controller/feature/features.go +++ b/internal/controller/feature/features.go @@ -14,6 +14,10 @@ See the License for the specific language governing permissions and limitations under the License. */ +// feature contains controller's feature gate setup functionality. Do not import +// this package into any code that's shared with other components to prevent +// overwriting other component's feature gates, see i.e +// https://github.com/cert-manager/cert-manager/issues/6011 package feature import ( @@ -23,47 +27,167 @@ import ( utilfeature "github.com/cert-manager/cert-manager/pkg/util/feature" ) +// see https://kubernetes.io/docs/reference/command-line-tools-reference/feature-gates/#feature-stages + const ( - // alpha: v0.7.2 + // Copy & paste the following template when you add a new feature gate: + // ========================== START TEMPLATE ========================== + // Owner: @username + // Alpha: vX.Y + // Beta: ... // - // ValidateCAA enables CAA checking when issuing certificates - ValidateCAA featuregate.Feature = "ValidateCAA" + // FeatureName will enable XYZ feature. + // Fill this section out with additional details about the feature. + // FeatureName featuregate.Feature = "FeatureName" + // =========================== END TEMPLATE =========================== - // alpha: v1.4.0 + // Owner: N/A + // Alpha: v1.4 // // ExperimentalCertificateSigningRequestControllers enables all CertificateSigningRequest // controllers that sign Kubernetes CertificateSigningRequest resources ExperimentalCertificateSigningRequestControllers featuregate.Feature = "ExperimentalCertificateSigningRequestControllers" - // alpha: v1.5.0 + // Owner: N/A + // Alpha: v1.5 + // Beta: v1.15 // // ExperimentalGatewayAPISupport enables the gateway-shim controller and adds support for // the Gateway API to the HTTP-01 challenge solver. ExperimentalGatewayAPISupport featuregate.Feature = "ExperimentalGatewayAPISupport" // Owner: @joshvanl - // alpha: v1.7.0 + // Alpha: v1.7 + // Beta: v1.15 + // GA: v1.18 // // AdditionalCertificateOutputFormats enable output additional format AdditionalCertificateOutputFormats featuregate.Feature = "AdditionalCertificateOutputFormats" - // alpha: v1.8.0 + // Owner: @joshvanl + // Alpha: v1.8 // // ServerSideApply enables the use of ServerSideApply in all API calls. ServerSideApply featuregate.Feature = "ServerSideApply" - // Owner (responsible for graduating feature through to GA): @spockz , @irbekrm + // Owner: @spockz , @irbekrm // Alpha: v1.9 + // // LiteralCertificateSubject will enable providing a subject in the Certificate that will be used literally in the CertificateSigningRequest. The subject can be provided via `LiteralSubject` field on `Certificate`'s spec. // This feature gate must be used together with LiteralCertificateSubject webhook feature gate. // See https://github.com/cert-manager/cert-manager/issues/3203 and https://github.com/cert-manager/cert-manager/issues/4424 for context. LiteralCertificateSubject featuregate.Feature = "LiteralCertificateSubject" + // Owner: @inteon // Alpha: v1.10 + // Beta: v1.13 + // // StableCertificateRequestName will enable generation of CertificateRequest resources with a fixed name. The name of the CertificateRequest will be a function of Certificate resource name and its revision // This feature gate will disable auto-generated CertificateRequest name // Github Issue: https://github.com/cert-manager/cert-manager/issues/4956 StableCertificateRequestName featuregate.Feature = "StableCertificateRequestName" + + // Owner: @SgtCoDFish + // Alpha: v1.11 + // + // UseCertificateRequestBasicConstraints will add Basic Constraints section in the Extension Request of the Certificate Signing Request + // This feature will add BasicConstraints section with CA field defaulting to false; CA field will be set true if the Certificate resource spec has isCA as true + // Github Issue: https://github.com/cert-manager/cert-manager/issues/5539 + UseCertificateRequestBasicConstraints featuregate.Feature = "UseCertificateRequestBasicConstraints" + + // Owner: @irbekrm + // Alpha v1.12 + // Beta: v1.13 + // + // SecretsFilteredCaching 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 to access, to speed up issuance. + // See https://github.com/cert-manager/cert-manager/blob/master/design/20221205-memory-management.md + SecretsFilteredCaching featuregate.Feature = "SecretsFilteredCaching" + + // Owner: @inteon + // Beta: v1.13 + // GA: v1.15 + // + // DisallowInsecureCSRUsageDefinition will prevent the webhook from allowing + // CertificateRequest's usages to be only defined in the CSR, while leaving + // the usages field empty. + DisallowInsecureCSRUsageDefinition featuregate.Feature = "DisallowInsecureCSRUsageDefinition" + + // Owner: @tanujd11 + // Alpha: v1.14 + // Beta: v1.17 + // + // NameConstraints adds support for Name Constraints in Certificate resources + // with IsCA=true. + // Github Issue: https://github.com/cert-manager/cert-manager/issues/3655 + NameConstraints featuregate.Feature = "NameConstraints" + + // Owner: @SpectralHiss + // Alpha: v1.14 + // + // OtherNames adds support for OtherName Subject Alternative Name values in + // Certificate resources. + // Github Issue: https://github.com/cert-manager/cert-manager/issues/6393 + OtherNames featuregate.Feature = "OtherNames" + + // Owner: @jsoref + // Alpha: v1.16 + // Beta: v1.17 + // GA: v1.18 + // + // UseDomainQualifiedFinalizer changes the finalizer added to cert-manager created + // resources to acme.cert-manager.io/finalizer instead of finalizer.acme.cert-manager.io. + // GitHub Issue: https://github.com/cert-manager/cert-manager/issues/7266 + UseDomainQualifiedFinalizer featuregate.Feature = "UseDomainQualifiedFinalizer" + + // Owner: N/A + // Alpha: v0.7.2 + // Deprecated: v1.17 + // Removed: v1.18 + // + // ValidateCAA is a now-removed feature gate which enabled CAA checking when issuing certificates + // This was never widely adopted, and without an owner to sponsor it we decided to deprecate + // this feature gate and then remove it. + // The feature gate is still defined here so that users who specify the feature gate aren't + // hit with "unknown feature gate" errors which crash the controller, but this is a no-op + // and only prints a log line if added. + ValidateCAA featuregate.Feature = "ValidateCAA" + + // Owner: @wallrj + // Alpha: v1.18.0 + // Beta: v1.18.0 + // + // DefaultPrivateKeyRotationPolicyAlways change the default value of + // `Certificate.Spec.PrivateKey.RotationPolicy` to `Always`. + // Why? Because the old default (`Never`) was unintuitive and insecure. For + // example, if a private key is exposed, users may (reasonably) assume that + // re-issuing a certificate (e.g. using cmctl renew) will generate a new + // private key, but it won't unless the user has explicitly set + // rotationPolicy: Always on the Certificate resource. + // This feature skipped the Alpha phase and was instead introduced as a Beta + // feature, because it is thought be low-risk feature and because we want to + // accelerate the adoption of this important security feature. + DefaultPrivateKeyRotationPolicyAlways featuregate.Feature = "DefaultPrivateKeyRotationPolicyAlways" + + // Owner: @sspreitzer, @wallrj + // Alpha: v1.18.1 + // Beta: v1.18.1 + // + // ACMEHTTP01IngressPathTypeExact will use Ingress pathType `Exact`. + // `ACMEHTTP01IngressPathTypeExact` changes the default `pathType` for ACME + // HTTP01 Ingress based challenges to `Exact`. This security feature ensures + // that the challenge path (which is an exact path) is not misinterpreted as + // a regular expression or some other Ingress specific (ImplementationSpecific) + // parsing. This allows HTTP01 challenges to be solved when using standards + // compliant Ingress controllers such as Cilium. The old default + // `ImplementationSpecific`` can be reinstated by disabling this feature gate. + // You may need to disable the feature for compatibility with ingress-nginx. + // See: https://cert-manager.io/docs/releases/release-notes/release-notes-1.18 + ACMEHTTP01IngressPathTypeExact featuregate.Feature = "ACMEHTTP01IngressPathTypeExact" ) func init() { @@ -74,11 +198,32 @@ func init() { // To add a new feature, define a key for it above and add it here. The features will be // available on the cert-manager controller binary. var defaultCertManagerFeatureGates = map[featuregate.Feature]featuregate.FeatureSpec{ - ValidateCAA: {Default: false, PreRelease: featuregate.Alpha}, + DisallowInsecureCSRUsageDefinition: {Default: true, PreRelease: featuregate.GA}, + StableCertificateRequestName: {Default: true, PreRelease: featuregate.Beta}, + SecretsFilteredCaching: {Default: true, PreRelease: featuregate.Beta}, + ExperimentalCertificateSigningRequestControllers: {Default: false, PreRelease: featuregate.Alpha}, - ExperimentalGatewayAPISupport: {Default: false, PreRelease: featuregate.Alpha}, - AdditionalCertificateOutputFormats: {Default: false, PreRelease: featuregate.Alpha}, + ExperimentalGatewayAPISupport: {Default: true, PreRelease: featuregate.Beta}, + AdditionalCertificateOutputFormats: {Default: true, PreRelease: featuregate.GA}, ServerSideApply: {Default: false, PreRelease: featuregate.Alpha}, - LiteralCertificateSubject: {Default: false, PreRelease: featuregate.Alpha}, - StableCertificateRequestName: {Default: false, PreRelease: featuregate.Alpha}, + LiteralCertificateSubject: {Default: true, PreRelease: featuregate.Beta}, + UseCertificateRequestBasicConstraints: {Default: false, PreRelease: featuregate.Alpha}, + NameConstraints: {Default: true, PreRelease: featuregate.Beta}, + OtherNames: {Default: false, PreRelease: featuregate.Alpha}, + UseDomainQualifiedFinalizer: {Default: true, PreRelease: featuregate.GA}, + DefaultPrivateKeyRotationPolicyAlways: {Default: true, PreRelease: featuregate.Beta}, + ACMEHTTP01IngressPathTypeExact: {Default: true, PreRelease: featuregate.Beta}, + + // NB: Deprecated + removed feature gates are kept here. + // `featuregate.Deprecated` exists, but will cause the featuregate library + // to emit its own warning when the gate is set: + // > W...] Setting deprecated feature gate ValidateCAA=true. It will be removed in a future release. + // So we have to set to Alpha to avoid that. `PreAlpha` also exists, but + // adds versioning logic we don't want to deal with. + + // If we simply remove the gate from here, then anyone still setting it will + // see an error and the controller will enter CrashLoopBackOff: + // > E...] "error executing command" err="failed to set feature gates from initial flags-based config: unrecognized feature gate: ValidateCAA" logger="cert-manager" + // So we leave it here, set to alpha. + ValidateCAA: {Default: false, PreRelease: featuregate.Alpha}, } diff --git a/internal/controller/issuers/apply.go b/internal/controller/issuers/apply.go index 9ba9533211b..7e31b64ca38 100644 --- a/internal/controller/issuers/apply.go +++ b/internal/controller/issuers/apply.go @@ -23,7 +23,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" apitypes "k8s.io/apimachinery/pkg/types" - "k8s.io/utils/pointer" + "k8s.io/utils/ptr" cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" cmclient "github.com/cert-manager/cert-manager/pkg/client/clientset/versioned" @@ -43,7 +43,7 @@ func ApplyIssuerStatus(ctx context.Context, cl cmclient.Interface, fieldManager _, err = cl.CertmanagerV1().Issuers(issuer.Namespace).Patch( ctx, issuer.Name, apitypes.ApplyPatchType, issuerData, - metav1.PatchOptions{Force: pointer.Bool(true), FieldManager: fieldManager}, "status", + metav1.PatchOptions{Force: ptr.To(true), FieldManager: fieldManager}, "status", ) return err @@ -64,7 +64,7 @@ func ApplyClusterIssuerStatus(ctx context.Context, cl cmclient.Interface, fieldM _, err = cl.CertmanagerV1().ClusterIssuers().Patch( ctx, issuer.Name, apitypes.ApplyPatchType, issuerData, - metav1.PatchOptions{Force: pointer.Bool(true), FieldManager: fieldManager}, "status", + metav1.PatchOptions{Force: ptr.To(true), FieldManager: fieldManager}, "status", ) return err diff --git a/internal/controller/issuers/apply_test.go b/internal/controller/issuers/apply_test.go index 21d37cb8a8a..8fa69517c6d 100644 --- a/internal/controller/issuers/apply_test.go +++ b/internal/controller/issuers/apply_test.go @@ -22,16 +22,16 @@ import ( "sync" "testing" - fuzz "github.com/google/gofuzz" "github.com/stretchr/testify/assert" + "sigs.k8s.io/randfill" cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" ) func Test_serializeApplyIssuerStatus(t *testing.T) { const ( - expReg = `^{"kind":"Issuer","apiVersion":"cert-manager.io/v1","metadata":{"name":"foo","namespace":"bar","creationTimestamp":null},"spec":{},"status":{.*}$` - expEmpty = `{"kind":"Issuer","apiVersion":"cert-manager.io/v1","metadata":{"name":"foo","namespace":"bar","creationTimestamp":null},"spec":{},"status":{}}` + expReg = `^{"kind":"Issuer","apiVersion":"cert-manager.io/v1","metadata":{"name":"foo","namespace":"bar"},"spec":{},"status":{.*}$` + expEmpty = `{"kind":"Issuer","apiVersion":"cert-manager.io/v1","metadata":{"name":"foo","namespace":"bar"},"spec":{},"status":{}}` numJobs = 10000 ) @@ -39,12 +39,12 @@ func Test_serializeApplyIssuerStatus(t *testing.T) { jobs := make(chan int) wg.Add(numJobs) - for i := 0; i < 3; i++ { + for range 3 { go func() { for j := range jobs { t.Run("fuzz_"+strconv.Itoa(j), func(t *testing.T) { var issuer cmapi.Issuer - fuzz.New().NilChance(0.5).Fuzz(&issuer) + randfill.New().NilChance(0.5).Fill(&issuer) issuer.Name = "foo" issuer.Namespace = "bar" @@ -70,7 +70,7 @@ func Test_serializeApplyIssuerStatus(t *testing.T) { }() } - for i := 0; i < numJobs; i++ { + for i := range numJobs { jobs <- i } close(jobs) @@ -79,8 +79,8 @@ func Test_serializeApplyIssuerStatus(t *testing.T) { func Test_serializeApplyClusterIssuerStatus(t *testing.T) { const ( - expReg = `^{"kind":"ClusterIssuer","apiVersion":"cert-manager.io/v1","metadata":{"name":"foo","creationTimestamp":null},"spec":{},"status":{.*}$` - expEmpty = `{"kind":"ClusterIssuer","apiVersion":"cert-manager.io/v1","metadata":{"name":"foo","creationTimestamp":null},"spec":{},"status":{}}` + expReg = `^{"kind":"ClusterIssuer","apiVersion":"cert-manager.io/v1","metadata":{"name":"foo"},"spec":{},"status":{.*}$` + expEmpty = `{"kind":"ClusterIssuer","apiVersion":"cert-manager.io/v1","metadata":{"name":"foo"},"spec":{},"status":{}}` numJobs = 10000 ) @@ -88,12 +88,12 @@ func Test_serializeApplyClusterIssuerStatus(t *testing.T) { jobs := make(chan int) wg.Add(numJobs) - for i := 0; i < 3; i++ { + for range 3 { go func() { for j := range jobs { t.Run("fuzz_"+strconv.Itoa(j), func(t *testing.T) { var issuer cmapi.ClusterIssuer - fuzz.New().NilChance(0.5).Fuzz(&issuer) + randfill.New().NilChance(0.5).Fill(&issuer) issuer.Name = "foo" // Test regex with non-empty status. @@ -118,7 +118,7 @@ func Test_serializeApplyClusterIssuerStatus(t *testing.T) { }() } - for i := 0; i < numJobs; i++ { + for i := range numJobs { jobs <- i } close(jobs) diff --git a/internal/controller/orders/apply.go b/internal/controller/orders/apply.go index 05adb92e38a..bbeab1ede33 100644 --- a/internal/controller/orders/apply.go +++ b/internal/controller/orders/apply.go @@ -23,13 +23,13 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" apitypes "k8s.io/apimachinery/pkg/types" - "k8s.io/utils/pointer" + "k8s.io/utils/ptr" cmacme "github.com/cert-manager/cert-manager/pkg/apis/acme/v1" cmclient "github.com/cert-manager/cert-manager/pkg/client/clientset/versioned" ) -// ApplyStatus will make a Apply API call with the given client to the order's +// ApplyStatus will make an Apply API call with the given client to the order's // status sub-resource endpoint. All data in the given Order object is dropped; // expect for the name, namespace, and status object. The given fieldManager is // will be used as the FieldManager in the Apply call. @@ -42,7 +42,7 @@ func ApplyStatus(ctx context.Context, cl cmclient.Interface, fieldManager string _, err = cl.AcmeV1().Orders(order.Namespace).Patch( ctx, order.Name, apitypes.ApplyPatchType, orderData, - metav1.PatchOptions{Force: pointer.Bool(true), FieldManager: fieldManager}, "status", + metav1.PatchOptions{Force: ptr.To(true), FieldManager: fieldManager}, "status", ) return err diff --git a/internal/controller/orders/apply_test.go b/internal/controller/orders/apply_test.go index f71c07a206f..bbc34034e16 100644 --- a/internal/controller/orders/apply_test.go +++ b/internal/controller/orders/apply_test.go @@ -22,16 +22,16 @@ import ( "sync" "testing" - fuzz "github.com/google/gofuzz" "github.com/stretchr/testify/assert" + "sigs.k8s.io/randfill" cmacme "github.com/cert-manager/cert-manager/pkg/apis/acme/v1" ) func Test_serializeApplyStatus(t *testing.T) { const ( - expReg = `^{"kind":"Order","apiVersion":"acme.cert-manager.io/v1","metadata":{"name":"foo","namespace":"bar","creationTimestamp":null},"spec":{"request":null,"issuerRef":{"name":""}},"status":{.*}$` - expEmpty = `{"kind":"Order","apiVersion":"acme.cert-manager.io/v1","metadata":{"name":"foo","namespace":"bar","creationTimestamp":null},"spec":{"request":null,"issuerRef":{"name":""}},"status":{}}` + expReg = `^{"kind":"Order","apiVersion":"acme.cert-manager.io/v1","metadata":{"name":"foo","namespace":"bar"},"spec":{"request":null,"issuerRef":{"name":""}},"status":{.*}$` + expEmpty = `{"kind":"Order","apiVersion":"acme.cert-manager.io/v1","metadata":{"name":"foo","namespace":"bar"},"spec":{"request":null,"issuerRef":{"name":""}},"status":{}}` numJobs = 10000 ) @@ -39,12 +39,12 @@ func Test_serializeApplyStatus(t *testing.T) { jobs := make(chan int) wg.Add(numJobs) - for i := 0; i < 3; i++ { + for range 3 { go func() { for j := range jobs { t.Run("fuzz_"+strconv.Itoa(j), func(t *testing.T) { var order cmacme.Order - fuzz.New().NilChance(0.5).Fuzz(&order) + randfill.New().NilChance(0.5).Fill(&order) order.Name = "foo" order.Namespace = "bar" @@ -70,7 +70,7 @@ func Test_serializeApplyStatus(t *testing.T) { }() } - for i := 0; i < numJobs; i++ { + for i := range numJobs { jobs <- i } close(jobs) diff --git a/internal/apis/acme/v1alpha3/const.go b/internal/generated/openapi/cmd/models-schema/doc.go similarity index 81% rename from internal/apis/acme/v1alpha3/const.go rename to internal/generated/openapi/cmd/models-schema/doc.go index 6998e44345d..366d0b1e1a2 100644 --- a/internal/apis/acme/v1alpha3/const.go +++ b/internal/generated/openapi/cmd/models-schema/doc.go @@ -1,5 +1,5 @@ /* -Copyright 2020 The cert-manager Authors. +Copyright 2025 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. @@ -14,8 +14,4 @@ See the License for the specific language governing permissions and limitations under the License. */ -package v1alpha3 - -const ( - ACMEFinalizer = "finalizer.acme.cert-manager.io" -) +package main diff --git a/internal/generated/openapi/cmd/models-schema/main.go b/internal/generated/openapi/cmd/models-schema/main.go new file mode 100644 index 00000000000..f67add0d144 --- /dev/null +++ b/internal/generated/openapi/cmd/models-schema/main.go @@ -0,0 +1,92 @@ +/* +Copyright 2025 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. +*/ + +package main + +import ( + "encoding/json" + "fmt" + "os" + "strings" + + "k8s.io/kube-openapi/pkg/common" + "k8s.io/kube-openapi/pkg/validation/spec" + + "github.com/cert-manager/cert-manager/internal/generated/openapi" +) + +// Outputs openAPI schema JSON containing the schema definitions in zz_generated.openapi.go. +func main() { + err := output() + if err != nil { + fmt.Fprintf(os.Stderr, "Failed: %v", err) + os.Exit(1) + } +} + +func output() error { + refFunc := func(name string) spec.Ref { + return spec.MustCreateRef(fmt.Sprintf("#/definitions/%s", friendlyName(name))) + } + defs := openapi.GetOpenAPIDefinitions(refFunc) + schemaDefs := make(map[string]spec.Schema, len(defs)) + for k, v := range defs { + // Replace top-level schema with v2 if a v2 schema is embedded + // so that the output of this program is always in OpenAPI v2. + // This is done by looking up an extension that marks the embedded v2 + // schema, and, if the v2 schema is found, make it the resulting schema for + // the type. + if schema, ok := v.Schema.Extensions[common.ExtensionV2Schema]; ok { + if v2Schema, isOpenAPISchema := schema.(spec.Schema); isOpenAPISchema { + schemaDefs[friendlyName(k)] = v2Schema + continue + } + } + + schemaDefs[friendlyName(k)] = v.Schema + } + data, err := json.Marshal(&spec.Swagger{ + SwaggerProps: spec.SwaggerProps{ + Definitions: schemaDefs, + Info: &spec.Info{ + InfoProps: spec.InfoProps{ + Title: "cert-manager", + Version: "unversioned", + }, + }, + Swagger: "2.0", + }, + }) + if err != nil { + return fmt.Errorf("error serializing api definitions: %w", err) + } + os.Stdout.Write(data) + return nil +} + +// From k8s.io/apiserver/pkg/endpoints/openapi/openapi.go +func friendlyName(name string) string { + nameParts := strings.Split(name, "/") + // Reverse first part. e.g., io.k8s... instead of k8s.io... + if len(nameParts) > 0 && strings.Contains(nameParts[0], ".") { + parts := strings.Split(nameParts[0], ".") + for i, j := 0, len(parts)-1; i < j; i, j = i+1, j-1 { + parts[i], parts[j] = parts[j], parts[i] + } + nameParts[0] = strings.Join(parts, ".") + } + return strings.Join(nameParts, ".") +} diff --git a/internal/apis/acme/v1alpha2/const.go b/internal/generated/openapi/doc.go similarity index 81% rename from internal/apis/acme/v1alpha2/const.go rename to internal/generated/openapi/doc.go index d0704721e7e..90e08ae2c7a 100644 --- a/internal/apis/acme/v1alpha2/const.go +++ b/internal/generated/openapi/doc.go @@ -1,5 +1,5 @@ /* -Copyright 2020 The cert-manager Authors. +Copyright 2025 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. @@ -14,8 +14,4 @@ See the License for the specific language governing permissions and limitations under the License. */ -package v1alpha2 - -const ( - ACMEFinalizer = "finalizer.acme.cert-manager.io" -) +package openapi diff --git a/internal/generated/openapi/openapi_test.go b/internal/generated/openapi/openapi_test.go new file mode 100644 index 00000000000..b3d68861ecd --- /dev/null +++ b/internal/generated/openapi/openapi_test.go @@ -0,0 +1,69 @@ +/* +Copyright 2025 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. +*/ + +package openapi + +import ( + "encoding/json" + "testing" + + "github.com/go-openapi/jsonreference" + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "k8s.io/kube-openapi/pkg/common" + "k8s.io/kube-openapi/pkg/handler" + "k8s.io/kube-openapi/pkg/validation/spec" +) + +func TestOpenAPIRoundtrip(t *testing.T) { + dummyRef := func(name string) spec.Ref { return spec.MustCreateRef("#/definitions/dummy") } + for name, value := range GetOpenAPIDefinitions(dummyRef) { + t.Run(name, func(t *testing.T) { + // TODO(kubernetes/gengo#193): We currently round-trip ints to floats. + value.Schema = *handler.PruneDefaultsSchema(&value.Schema) + data, err := json.Marshal(value.Schema) + if err != nil { + t.Error(err) + return + } + + roundTripped := spec.Schema{} + if err := json.Unmarshal(data, &roundTripped); err != nil { + t.Error(err) + return + } + + // Remove the embedded v2 schema if it presents. + // The v2 schema either become the schema (when serving v2) or get pruned (v3) + // and it is never round-tripped. + delete(roundTripped.Extensions, common.ExtensionV2Schema) + delete(value.Schema.Extensions, common.ExtensionV2Schema) + + opts := []cmp.Option{ + cmpopts.EquateEmpty(), + // jsonreference.Ref contains unexported fields. Compare + // by string representation provides a consistent + cmp.Comparer(func(x, y jsonreference.Ref) bool { + return x.String() == y.String() + }), + } + if !cmp.Equal(value.Schema, roundTripped, opts...) { + t.Errorf("unexpected diff (a=expected,b=roundtripped):\n%s", cmp.Diff(value.Schema, roundTripped, opts...)) + return + } + }) + } +} diff --git a/internal/generated/openapi/zz_generated.openapi.go b/internal/generated/openapi/zz_generated.openapi.go new file mode 100644 index 00000000000..1405d19ebc4 --- /dev/null +++ b/internal/generated/openapi/zz_generated.openapi.go @@ -0,0 +1,26828 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by openapi-gen. DO NOT EDIT. + +package openapi + +import ( + v1 "k8s.io/api/core/v1" + apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" + resource "k8s.io/apimachinery/pkg/api/resource" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + intstr "k8s.io/apimachinery/pkg/util/intstr" + common "k8s.io/kube-openapi/pkg/common" + spec "k8s.io/kube-openapi/pkg/validation/spec" +) + +func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenAPIDefinition { + return map[string]common.OpenAPIDefinition{ + "github.com/cert-manager/cert-manager/pkg/apis/acme/v1.ACMEAuthorization": schema_pkg_apis_acme_v1_ACMEAuthorization(ref), + "github.com/cert-manager/cert-manager/pkg/apis/acme/v1.ACMEChallenge": schema_pkg_apis_acme_v1_ACMEChallenge(ref), + "github.com/cert-manager/cert-manager/pkg/apis/acme/v1.ACMEChallengeSolver": schema_pkg_apis_acme_v1_ACMEChallengeSolver(ref), + "github.com/cert-manager/cert-manager/pkg/apis/acme/v1.ACMEChallengeSolverDNS01": schema_pkg_apis_acme_v1_ACMEChallengeSolverDNS01(ref), + "github.com/cert-manager/cert-manager/pkg/apis/acme/v1.ACMEChallengeSolverHTTP01": schema_pkg_apis_acme_v1_ACMEChallengeSolverHTTP01(ref), + "github.com/cert-manager/cert-manager/pkg/apis/acme/v1.ACMEChallengeSolverHTTP01GatewayHTTPRoute": schema_pkg_apis_acme_v1_ACMEChallengeSolverHTTP01GatewayHTTPRoute(ref), + "github.com/cert-manager/cert-manager/pkg/apis/acme/v1.ACMEChallengeSolverHTTP01Ingress": schema_pkg_apis_acme_v1_ACMEChallengeSolverHTTP01Ingress(ref), + "github.com/cert-manager/cert-manager/pkg/apis/acme/v1.ACMEChallengeSolverHTTP01IngressObjectMeta": schema_pkg_apis_acme_v1_ACMEChallengeSolverHTTP01IngressObjectMeta(ref), + "github.com/cert-manager/cert-manager/pkg/apis/acme/v1.ACMEChallengeSolverHTTP01IngressPodObjectMeta": schema_pkg_apis_acme_v1_ACMEChallengeSolverHTTP01IngressPodObjectMeta(ref), + "github.com/cert-manager/cert-manager/pkg/apis/acme/v1.ACMEChallengeSolverHTTP01IngressPodResources": schema_pkg_apis_acme_v1_ACMEChallengeSolverHTTP01IngressPodResources(ref), + "github.com/cert-manager/cert-manager/pkg/apis/acme/v1.ACMEChallengeSolverHTTP01IngressPodSecurityContext": schema_pkg_apis_acme_v1_ACMEChallengeSolverHTTP01IngressPodSecurityContext(ref), + "github.com/cert-manager/cert-manager/pkg/apis/acme/v1.ACMEChallengeSolverHTTP01IngressPodSpec": schema_pkg_apis_acme_v1_ACMEChallengeSolverHTTP01IngressPodSpec(ref), + "github.com/cert-manager/cert-manager/pkg/apis/acme/v1.ACMEChallengeSolverHTTP01IngressPodTemplate": schema_pkg_apis_acme_v1_ACMEChallengeSolverHTTP01IngressPodTemplate(ref), + "github.com/cert-manager/cert-manager/pkg/apis/acme/v1.ACMEChallengeSolverHTTP01IngressTemplate": schema_pkg_apis_acme_v1_ACMEChallengeSolverHTTP01IngressTemplate(ref), + "github.com/cert-manager/cert-manager/pkg/apis/acme/v1.ACMEExternalAccountBinding": schema_pkg_apis_acme_v1_ACMEExternalAccountBinding(ref), + "github.com/cert-manager/cert-manager/pkg/apis/acme/v1.ACMEIssuer": schema_pkg_apis_acme_v1_ACMEIssuer(ref), + "github.com/cert-manager/cert-manager/pkg/apis/acme/v1.ACMEIssuerDNS01ProviderAcmeDNS": schema_pkg_apis_acme_v1_ACMEIssuerDNS01ProviderAcmeDNS(ref), + "github.com/cert-manager/cert-manager/pkg/apis/acme/v1.ACMEIssuerDNS01ProviderAkamai": schema_pkg_apis_acme_v1_ACMEIssuerDNS01ProviderAkamai(ref), + "github.com/cert-manager/cert-manager/pkg/apis/acme/v1.ACMEIssuerDNS01ProviderAzureDNS": schema_pkg_apis_acme_v1_ACMEIssuerDNS01ProviderAzureDNS(ref), + "github.com/cert-manager/cert-manager/pkg/apis/acme/v1.ACMEIssuerDNS01ProviderCloudDNS": schema_pkg_apis_acme_v1_ACMEIssuerDNS01ProviderCloudDNS(ref), + "github.com/cert-manager/cert-manager/pkg/apis/acme/v1.ACMEIssuerDNS01ProviderCloudflare": schema_pkg_apis_acme_v1_ACMEIssuerDNS01ProviderCloudflare(ref), + "github.com/cert-manager/cert-manager/pkg/apis/acme/v1.ACMEIssuerDNS01ProviderDigitalOcean": schema_pkg_apis_acme_v1_ACMEIssuerDNS01ProviderDigitalOcean(ref), + "github.com/cert-manager/cert-manager/pkg/apis/acme/v1.ACMEIssuerDNS01ProviderRFC2136": schema_pkg_apis_acme_v1_ACMEIssuerDNS01ProviderRFC2136(ref), + "github.com/cert-manager/cert-manager/pkg/apis/acme/v1.ACMEIssuerDNS01ProviderRoute53": schema_pkg_apis_acme_v1_ACMEIssuerDNS01ProviderRoute53(ref), + "github.com/cert-manager/cert-manager/pkg/apis/acme/v1.ACMEIssuerDNS01ProviderWebhook": schema_pkg_apis_acme_v1_ACMEIssuerDNS01ProviderWebhook(ref), + "github.com/cert-manager/cert-manager/pkg/apis/acme/v1.ACMEIssuerStatus": schema_pkg_apis_acme_v1_ACMEIssuerStatus(ref), + "github.com/cert-manager/cert-manager/pkg/apis/acme/v1.AzureManagedIdentity": schema_pkg_apis_acme_v1_AzureManagedIdentity(ref), + "github.com/cert-manager/cert-manager/pkg/apis/acme/v1.CertificateDNSNameSelector": schema_pkg_apis_acme_v1_CertificateDNSNameSelector(ref), + "github.com/cert-manager/cert-manager/pkg/apis/acme/v1.Challenge": schema_pkg_apis_acme_v1_Challenge(ref), + "github.com/cert-manager/cert-manager/pkg/apis/acme/v1.ChallengeList": schema_pkg_apis_acme_v1_ChallengeList(ref), + "github.com/cert-manager/cert-manager/pkg/apis/acme/v1.ChallengeSpec": schema_pkg_apis_acme_v1_ChallengeSpec(ref), + "github.com/cert-manager/cert-manager/pkg/apis/acme/v1.ChallengeStatus": schema_pkg_apis_acme_v1_ChallengeStatus(ref), + "github.com/cert-manager/cert-manager/pkg/apis/acme/v1.Order": schema_pkg_apis_acme_v1_Order(ref), + "github.com/cert-manager/cert-manager/pkg/apis/acme/v1.OrderList": schema_pkg_apis_acme_v1_OrderList(ref), + "github.com/cert-manager/cert-manager/pkg/apis/acme/v1.OrderSpec": schema_pkg_apis_acme_v1_OrderSpec(ref), + "github.com/cert-manager/cert-manager/pkg/apis/acme/v1.OrderStatus": schema_pkg_apis_acme_v1_OrderStatus(ref), + "github.com/cert-manager/cert-manager/pkg/apis/acme/v1.Route53Auth": schema_pkg_apis_acme_v1_Route53Auth(ref), + "github.com/cert-manager/cert-manager/pkg/apis/acme/v1.Route53KubernetesAuth": schema_pkg_apis_acme_v1_Route53KubernetesAuth(ref), + "github.com/cert-manager/cert-manager/pkg/apis/acme/v1.ServiceAccountRef": schema_pkg_apis_acme_v1_ServiceAccountRef(ref), + "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1.CAIssuer": schema_pkg_apis_certmanager_v1_CAIssuer(ref), + "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1.Certificate": schema_pkg_apis_certmanager_v1_Certificate(ref), + "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1.CertificateAdditionalOutputFormat": schema_pkg_apis_certmanager_v1_CertificateAdditionalOutputFormat(ref), + "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1.CertificateCondition": schema_pkg_apis_certmanager_v1_CertificateCondition(ref), + "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1.CertificateKeystores": schema_pkg_apis_certmanager_v1_CertificateKeystores(ref), + "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1.CertificateList": schema_pkg_apis_certmanager_v1_CertificateList(ref), + "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1.CertificatePrivateKey": schema_pkg_apis_certmanager_v1_CertificatePrivateKey(ref), + "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1.CertificateRequest": schema_pkg_apis_certmanager_v1_CertificateRequest(ref), + "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1.CertificateRequestCondition": schema_pkg_apis_certmanager_v1_CertificateRequestCondition(ref), + "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1.CertificateRequestList": schema_pkg_apis_certmanager_v1_CertificateRequestList(ref), + "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1.CertificateRequestSpec": schema_pkg_apis_certmanager_v1_CertificateRequestSpec(ref), + "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1.CertificateRequestStatus": schema_pkg_apis_certmanager_v1_CertificateRequestStatus(ref), + "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1.CertificateSecretTemplate": schema_pkg_apis_certmanager_v1_CertificateSecretTemplate(ref), + "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1.CertificateSpec": schema_pkg_apis_certmanager_v1_CertificateSpec(ref), + "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1.CertificateStatus": schema_pkg_apis_certmanager_v1_CertificateStatus(ref), + "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1.ClusterIssuer": schema_pkg_apis_certmanager_v1_ClusterIssuer(ref), + "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1.ClusterIssuerList": schema_pkg_apis_certmanager_v1_ClusterIssuerList(ref), + "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1.Issuer": schema_pkg_apis_certmanager_v1_Issuer(ref), + "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1.IssuerCondition": schema_pkg_apis_certmanager_v1_IssuerCondition(ref), + "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1.IssuerConfig": schema_pkg_apis_certmanager_v1_IssuerConfig(ref), + "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1.IssuerList": schema_pkg_apis_certmanager_v1_IssuerList(ref), + "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1.IssuerSpec": schema_pkg_apis_certmanager_v1_IssuerSpec(ref), + "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1.IssuerStatus": schema_pkg_apis_certmanager_v1_IssuerStatus(ref), + "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1.JKSKeystore": schema_pkg_apis_certmanager_v1_JKSKeystore(ref), + "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1.NameConstraintItem": schema_pkg_apis_certmanager_v1_NameConstraintItem(ref), + "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1.NameConstraints": schema_pkg_apis_certmanager_v1_NameConstraints(ref), + "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1.OtherName": schema_pkg_apis_certmanager_v1_OtherName(ref), + "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1.PKCS12Keystore": schema_pkg_apis_certmanager_v1_PKCS12Keystore(ref), + "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1.SelfSignedIssuer": schema_pkg_apis_certmanager_v1_SelfSignedIssuer(ref), + "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1.ServiceAccountRef": schema_pkg_apis_certmanager_v1_ServiceAccountRef(ref), + "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1.VaultAppRole": schema_pkg_apis_certmanager_v1_VaultAppRole(ref), + "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1.VaultAuth": schema_pkg_apis_certmanager_v1_VaultAuth(ref), + "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1.VaultClientCertificateAuth": schema_pkg_apis_certmanager_v1_VaultClientCertificateAuth(ref), + "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1.VaultIssuer": schema_pkg_apis_certmanager_v1_VaultIssuer(ref), + "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1.VaultKubernetesAuth": schema_pkg_apis_certmanager_v1_VaultKubernetesAuth(ref), + "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1.VenafiCloud": schema_pkg_apis_certmanager_v1_VenafiCloud(ref), + "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1.VenafiIssuer": schema_pkg_apis_certmanager_v1_VenafiIssuer(ref), + "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1.VenafiTPP": schema_pkg_apis_certmanager_v1_VenafiTPP(ref), + "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1.X509Subject": schema_pkg_apis_certmanager_v1_X509Subject(ref), + "github.com/cert-manager/cert-manager/pkg/apis/meta/v1.IssuerReference": schema_pkg_apis_meta_v1_IssuerReference(ref), + "github.com/cert-manager/cert-manager/pkg/apis/meta/v1.LocalObjectReference": schema_pkg_apis_meta_v1_LocalObjectReference(ref), + "github.com/cert-manager/cert-manager/pkg/apis/meta/v1.SecretKeySelector": schema_pkg_apis_meta_v1_SecretKeySelector(ref), + "k8s.io/api/core/v1.AWSElasticBlockStoreVolumeSource": schema_k8sio_api_core_v1_AWSElasticBlockStoreVolumeSource(ref), + "k8s.io/api/core/v1.Affinity": schema_k8sio_api_core_v1_Affinity(ref), + "k8s.io/api/core/v1.AppArmorProfile": schema_k8sio_api_core_v1_AppArmorProfile(ref), + "k8s.io/api/core/v1.AttachedVolume": schema_k8sio_api_core_v1_AttachedVolume(ref), + "k8s.io/api/core/v1.AvoidPods": schema_k8sio_api_core_v1_AvoidPods(ref), + "k8s.io/api/core/v1.AzureDiskVolumeSource": schema_k8sio_api_core_v1_AzureDiskVolumeSource(ref), + "k8s.io/api/core/v1.AzureFilePersistentVolumeSource": schema_k8sio_api_core_v1_AzureFilePersistentVolumeSource(ref), + "k8s.io/api/core/v1.AzureFileVolumeSource": schema_k8sio_api_core_v1_AzureFileVolumeSource(ref), + "k8s.io/api/core/v1.Binding": schema_k8sio_api_core_v1_Binding(ref), + "k8s.io/api/core/v1.CSIPersistentVolumeSource": schema_k8sio_api_core_v1_CSIPersistentVolumeSource(ref), + "k8s.io/api/core/v1.CSIVolumeSource": schema_k8sio_api_core_v1_CSIVolumeSource(ref), + "k8s.io/api/core/v1.Capabilities": schema_k8sio_api_core_v1_Capabilities(ref), + "k8s.io/api/core/v1.CephFSPersistentVolumeSource": schema_k8sio_api_core_v1_CephFSPersistentVolumeSource(ref), + "k8s.io/api/core/v1.CephFSVolumeSource": schema_k8sio_api_core_v1_CephFSVolumeSource(ref), + "k8s.io/api/core/v1.CinderPersistentVolumeSource": schema_k8sio_api_core_v1_CinderPersistentVolumeSource(ref), + "k8s.io/api/core/v1.CinderVolumeSource": schema_k8sio_api_core_v1_CinderVolumeSource(ref), + "k8s.io/api/core/v1.ClientIPConfig": schema_k8sio_api_core_v1_ClientIPConfig(ref), + "k8s.io/api/core/v1.ClusterTrustBundleProjection": schema_k8sio_api_core_v1_ClusterTrustBundleProjection(ref), + "k8s.io/api/core/v1.ComponentCondition": schema_k8sio_api_core_v1_ComponentCondition(ref), + "k8s.io/api/core/v1.ComponentStatus": schema_k8sio_api_core_v1_ComponentStatus(ref), + "k8s.io/api/core/v1.ComponentStatusList": schema_k8sio_api_core_v1_ComponentStatusList(ref), + "k8s.io/api/core/v1.ConfigMap": schema_k8sio_api_core_v1_ConfigMap(ref), + "k8s.io/api/core/v1.ConfigMapEnvSource": schema_k8sio_api_core_v1_ConfigMapEnvSource(ref), + "k8s.io/api/core/v1.ConfigMapKeySelector": schema_k8sio_api_core_v1_ConfigMapKeySelector(ref), + "k8s.io/api/core/v1.ConfigMapList": schema_k8sio_api_core_v1_ConfigMapList(ref), + "k8s.io/api/core/v1.ConfigMapNodeConfigSource": schema_k8sio_api_core_v1_ConfigMapNodeConfigSource(ref), + "k8s.io/api/core/v1.ConfigMapProjection": schema_k8sio_api_core_v1_ConfigMapProjection(ref), + "k8s.io/api/core/v1.ConfigMapVolumeSource": schema_k8sio_api_core_v1_ConfigMapVolumeSource(ref), + "k8s.io/api/core/v1.Container": schema_k8sio_api_core_v1_Container(ref), + "k8s.io/api/core/v1.ContainerExtendedResourceRequest": schema_k8sio_api_core_v1_ContainerExtendedResourceRequest(ref), + "k8s.io/api/core/v1.ContainerImage": schema_k8sio_api_core_v1_ContainerImage(ref), + "k8s.io/api/core/v1.ContainerPort": schema_k8sio_api_core_v1_ContainerPort(ref), + "k8s.io/api/core/v1.ContainerResizePolicy": schema_k8sio_api_core_v1_ContainerResizePolicy(ref), + "k8s.io/api/core/v1.ContainerRestartRule": schema_k8sio_api_core_v1_ContainerRestartRule(ref), + "k8s.io/api/core/v1.ContainerRestartRuleOnExitCodes": schema_k8sio_api_core_v1_ContainerRestartRuleOnExitCodes(ref), + "k8s.io/api/core/v1.ContainerState": schema_k8sio_api_core_v1_ContainerState(ref), + "k8s.io/api/core/v1.ContainerStateRunning": schema_k8sio_api_core_v1_ContainerStateRunning(ref), + "k8s.io/api/core/v1.ContainerStateTerminated": schema_k8sio_api_core_v1_ContainerStateTerminated(ref), + "k8s.io/api/core/v1.ContainerStateWaiting": schema_k8sio_api_core_v1_ContainerStateWaiting(ref), + "k8s.io/api/core/v1.ContainerStatus": schema_k8sio_api_core_v1_ContainerStatus(ref), + "k8s.io/api/core/v1.ContainerUser": schema_k8sio_api_core_v1_ContainerUser(ref), + "k8s.io/api/core/v1.DaemonEndpoint": schema_k8sio_api_core_v1_DaemonEndpoint(ref), + "k8s.io/api/core/v1.DownwardAPIProjection": schema_k8sio_api_core_v1_DownwardAPIProjection(ref), + "k8s.io/api/core/v1.DownwardAPIVolumeFile": schema_k8sio_api_core_v1_DownwardAPIVolumeFile(ref), + "k8s.io/api/core/v1.DownwardAPIVolumeSource": schema_k8sio_api_core_v1_DownwardAPIVolumeSource(ref), + "k8s.io/api/core/v1.EmptyDirVolumeSource": schema_k8sio_api_core_v1_EmptyDirVolumeSource(ref), + "k8s.io/api/core/v1.EndpointAddress": schema_k8sio_api_core_v1_EndpointAddress(ref), + "k8s.io/api/core/v1.EndpointPort": schema_k8sio_api_core_v1_EndpointPort(ref), + "k8s.io/api/core/v1.EndpointSubset": schema_k8sio_api_core_v1_EndpointSubset(ref), + "k8s.io/api/core/v1.Endpoints": schema_k8sio_api_core_v1_Endpoints(ref), + "k8s.io/api/core/v1.EndpointsList": schema_k8sio_api_core_v1_EndpointsList(ref), + "k8s.io/api/core/v1.EnvFromSource": schema_k8sio_api_core_v1_EnvFromSource(ref), + "k8s.io/api/core/v1.EnvVar": schema_k8sio_api_core_v1_EnvVar(ref), + "k8s.io/api/core/v1.EnvVarSource": schema_k8sio_api_core_v1_EnvVarSource(ref), + "k8s.io/api/core/v1.EphemeralContainer": schema_k8sio_api_core_v1_EphemeralContainer(ref), + "k8s.io/api/core/v1.EphemeralContainerCommon": schema_k8sio_api_core_v1_EphemeralContainerCommon(ref), + "k8s.io/api/core/v1.EphemeralVolumeSource": schema_k8sio_api_core_v1_EphemeralVolumeSource(ref), + "k8s.io/api/core/v1.Event": schema_k8sio_api_core_v1_Event(ref), + "k8s.io/api/core/v1.EventList": schema_k8sio_api_core_v1_EventList(ref), + "k8s.io/api/core/v1.EventSeries": schema_k8sio_api_core_v1_EventSeries(ref), + "k8s.io/api/core/v1.EventSource": schema_k8sio_api_core_v1_EventSource(ref), + "k8s.io/api/core/v1.ExecAction": schema_k8sio_api_core_v1_ExecAction(ref), + "k8s.io/api/core/v1.FCVolumeSource": schema_k8sio_api_core_v1_FCVolumeSource(ref), + "k8s.io/api/core/v1.FileKeySelector": schema_k8sio_api_core_v1_FileKeySelector(ref), + "k8s.io/api/core/v1.FlexPersistentVolumeSource": schema_k8sio_api_core_v1_FlexPersistentVolumeSource(ref), + "k8s.io/api/core/v1.FlexVolumeSource": schema_k8sio_api_core_v1_FlexVolumeSource(ref), + "k8s.io/api/core/v1.FlockerVolumeSource": schema_k8sio_api_core_v1_FlockerVolumeSource(ref), + "k8s.io/api/core/v1.GCEPersistentDiskVolumeSource": schema_k8sio_api_core_v1_GCEPersistentDiskVolumeSource(ref), + "k8s.io/api/core/v1.GRPCAction": schema_k8sio_api_core_v1_GRPCAction(ref), + "k8s.io/api/core/v1.GitRepoVolumeSource": schema_k8sio_api_core_v1_GitRepoVolumeSource(ref), + "k8s.io/api/core/v1.GlusterfsPersistentVolumeSource": schema_k8sio_api_core_v1_GlusterfsPersistentVolumeSource(ref), + "k8s.io/api/core/v1.GlusterfsVolumeSource": schema_k8sio_api_core_v1_GlusterfsVolumeSource(ref), + "k8s.io/api/core/v1.HTTPGetAction": schema_k8sio_api_core_v1_HTTPGetAction(ref), + "k8s.io/api/core/v1.HTTPHeader": schema_k8sio_api_core_v1_HTTPHeader(ref), + "k8s.io/api/core/v1.HostAlias": schema_k8sio_api_core_v1_HostAlias(ref), + "k8s.io/api/core/v1.HostIP": schema_k8sio_api_core_v1_HostIP(ref), + "k8s.io/api/core/v1.HostPathVolumeSource": schema_k8sio_api_core_v1_HostPathVolumeSource(ref), + "k8s.io/api/core/v1.ISCSIPersistentVolumeSource": schema_k8sio_api_core_v1_ISCSIPersistentVolumeSource(ref), + "k8s.io/api/core/v1.ISCSIVolumeSource": schema_k8sio_api_core_v1_ISCSIVolumeSource(ref), + "k8s.io/api/core/v1.ImageVolumeSource": schema_k8sio_api_core_v1_ImageVolumeSource(ref), + "k8s.io/api/core/v1.KeyToPath": schema_k8sio_api_core_v1_KeyToPath(ref), + "k8s.io/api/core/v1.Lifecycle": schema_k8sio_api_core_v1_Lifecycle(ref), + "k8s.io/api/core/v1.LifecycleHandler": schema_k8sio_api_core_v1_LifecycleHandler(ref), + "k8s.io/api/core/v1.LimitRange": schema_k8sio_api_core_v1_LimitRange(ref), + "k8s.io/api/core/v1.LimitRangeItem": schema_k8sio_api_core_v1_LimitRangeItem(ref), + "k8s.io/api/core/v1.LimitRangeList": schema_k8sio_api_core_v1_LimitRangeList(ref), + "k8s.io/api/core/v1.LimitRangeSpec": schema_k8sio_api_core_v1_LimitRangeSpec(ref), + "k8s.io/api/core/v1.LinuxContainerUser": schema_k8sio_api_core_v1_LinuxContainerUser(ref), + "k8s.io/api/core/v1.List": schema_k8sio_api_core_v1_List(ref), + "k8s.io/api/core/v1.LoadBalancerIngress": schema_k8sio_api_core_v1_LoadBalancerIngress(ref), + "k8s.io/api/core/v1.LoadBalancerStatus": schema_k8sio_api_core_v1_LoadBalancerStatus(ref), + "k8s.io/api/core/v1.LocalObjectReference": schema_k8sio_api_core_v1_LocalObjectReference(ref), + "k8s.io/api/core/v1.LocalVolumeSource": schema_k8sio_api_core_v1_LocalVolumeSource(ref), + "k8s.io/api/core/v1.ModifyVolumeStatus": schema_k8sio_api_core_v1_ModifyVolumeStatus(ref), + "k8s.io/api/core/v1.NFSVolumeSource": schema_k8sio_api_core_v1_NFSVolumeSource(ref), + "k8s.io/api/core/v1.Namespace": schema_k8sio_api_core_v1_Namespace(ref), + "k8s.io/api/core/v1.NamespaceCondition": schema_k8sio_api_core_v1_NamespaceCondition(ref), + "k8s.io/api/core/v1.NamespaceList": schema_k8sio_api_core_v1_NamespaceList(ref), + "k8s.io/api/core/v1.NamespaceSpec": schema_k8sio_api_core_v1_NamespaceSpec(ref), + "k8s.io/api/core/v1.NamespaceStatus": schema_k8sio_api_core_v1_NamespaceStatus(ref), + "k8s.io/api/core/v1.Node": schema_k8sio_api_core_v1_Node(ref), + "k8s.io/api/core/v1.NodeAddress": schema_k8sio_api_core_v1_NodeAddress(ref), + "k8s.io/api/core/v1.NodeAffinity": schema_k8sio_api_core_v1_NodeAffinity(ref), + "k8s.io/api/core/v1.NodeCondition": schema_k8sio_api_core_v1_NodeCondition(ref), + "k8s.io/api/core/v1.NodeConfigSource": schema_k8sio_api_core_v1_NodeConfigSource(ref), + "k8s.io/api/core/v1.NodeConfigStatus": schema_k8sio_api_core_v1_NodeConfigStatus(ref), + "k8s.io/api/core/v1.NodeDaemonEndpoints": schema_k8sio_api_core_v1_NodeDaemonEndpoints(ref), + "k8s.io/api/core/v1.NodeFeatures": schema_k8sio_api_core_v1_NodeFeatures(ref), + "k8s.io/api/core/v1.NodeList": schema_k8sio_api_core_v1_NodeList(ref), + "k8s.io/api/core/v1.NodeProxyOptions": schema_k8sio_api_core_v1_NodeProxyOptions(ref), + "k8s.io/api/core/v1.NodeRuntimeHandler": schema_k8sio_api_core_v1_NodeRuntimeHandler(ref), + "k8s.io/api/core/v1.NodeRuntimeHandlerFeatures": schema_k8sio_api_core_v1_NodeRuntimeHandlerFeatures(ref), + "k8s.io/api/core/v1.NodeSelector": schema_k8sio_api_core_v1_NodeSelector(ref), + "k8s.io/api/core/v1.NodeSelectorRequirement": schema_k8sio_api_core_v1_NodeSelectorRequirement(ref), + "k8s.io/api/core/v1.NodeSelectorTerm": schema_k8sio_api_core_v1_NodeSelectorTerm(ref), + "k8s.io/api/core/v1.NodeSpec": schema_k8sio_api_core_v1_NodeSpec(ref), + "k8s.io/api/core/v1.NodeStatus": schema_k8sio_api_core_v1_NodeStatus(ref), + "k8s.io/api/core/v1.NodeSwapStatus": schema_k8sio_api_core_v1_NodeSwapStatus(ref), + "k8s.io/api/core/v1.NodeSystemInfo": schema_k8sio_api_core_v1_NodeSystemInfo(ref), + "k8s.io/api/core/v1.ObjectFieldSelector": schema_k8sio_api_core_v1_ObjectFieldSelector(ref), + "k8s.io/api/core/v1.ObjectReference": schema_k8sio_api_core_v1_ObjectReference(ref), + "k8s.io/api/core/v1.PersistentVolume": schema_k8sio_api_core_v1_PersistentVolume(ref), + "k8s.io/api/core/v1.PersistentVolumeClaim": schema_k8sio_api_core_v1_PersistentVolumeClaim(ref), + "k8s.io/api/core/v1.PersistentVolumeClaimCondition": schema_k8sio_api_core_v1_PersistentVolumeClaimCondition(ref), + "k8s.io/api/core/v1.PersistentVolumeClaimList": schema_k8sio_api_core_v1_PersistentVolumeClaimList(ref), + "k8s.io/api/core/v1.PersistentVolumeClaimSpec": schema_k8sio_api_core_v1_PersistentVolumeClaimSpec(ref), + "k8s.io/api/core/v1.PersistentVolumeClaimStatus": schema_k8sio_api_core_v1_PersistentVolumeClaimStatus(ref), + "k8s.io/api/core/v1.PersistentVolumeClaimTemplate": schema_k8sio_api_core_v1_PersistentVolumeClaimTemplate(ref), + "k8s.io/api/core/v1.PersistentVolumeClaimVolumeSource": schema_k8sio_api_core_v1_PersistentVolumeClaimVolumeSource(ref), + "k8s.io/api/core/v1.PersistentVolumeList": schema_k8sio_api_core_v1_PersistentVolumeList(ref), + "k8s.io/api/core/v1.PersistentVolumeSource": schema_k8sio_api_core_v1_PersistentVolumeSource(ref), + "k8s.io/api/core/v1.PersistentVolumeSpec": schema_k8sio_api_core_v1_PersistentVolumeSpec(ref), + "k8s.io/api/core/v1.PersistentVolumeStatus": schema_k8sio_api_core_v1_PersistentVolumeStatus(ref), + "k8s.io/api/core/v1.PhotonPersistentDiskVolumeSource": schema_k8sio_api_core_v1_PhotonPersistentDiskVolumeSource(ref), + "k8s.io/api/core/v1.Pod": schema_k8sio_api_core_v1_Pod(ref), + "k8s.io/api/core/v1.PodAffinity": schema_k8sio_api_core_v1_PodAffinity(ref), + "k8s.io/api/core/v1.PodAffinityTerm": schema_k8sio_api_core_v1_PodAffinityTerm(ref), + "k8s.io/api/core/v1.PodAntiAffinity": schema_k8sio_api_core_v1_PodAntiAffinity(ref), + "k8s.io/api/core/v1.PodAttachOptions": schema_k8sio_api_core_v1_PodAttachOptions(ref), + "k8s.io/api/core/v1.PodCertificateProjection": schema_k8sio_api_core_v1_PodCertificateProjection(ref), + "k8s.io/api/core/v1.PodCondition": schema_k8sio_api_core_v1_PodCondition(ref), + "k8s.io/api/core/v1.PodDNSConfig": schema_k8sio_api_core_v1_PodDNSConfig(ref), + "k8s.io/api/core/v1.PodDNSConfigOption": schema_k8sio_api_core_v1_PodDNSConfigOption(ref), + "k8s.io/api/core/v1.PodExecOptions": schema_k8sio_api_core_v1_PodExecOptions(ref), + "k8s.io/api/core/v1.PodExtendedResourceClaimStatus": schema_k8sio_api_core_v1_PodExtendedResourceClaimStatus(ref), + "k8s.io/api/core/v1.PodIP": schema_k8sio_api_core_v1_PodIP(ref), + "k8s.io/api/core/v1.PodList": schema_k8sio_api_core_v1_PodList(ref), + "k8s.io/api/core/v1.PodLogOptions": schema_k8sio_api_core_v1_PodLogOptions(ref), + "k8s.io/api/core/v1.PodOS": schema_k8sio_api_core_v1_PodOS(ref), + "k8s.io/api/core/v1.PodPortForwardOptions": schema_k8sio_api_core_v1_PodPortForwardOptions(ref), + "k8s.io/api/core/v1.PodProxyOptions": schema_k8sio_api_core_v1_PodProxyOptions(ref), + "k8s.io/api/core/v1.PodReadinessGate": schema_k8sio_api_core_v1_PodReadinessGate(ref), + "k8s.io/api/core/v1.PodResourceClaim": schema_k8sio_api_core_v1_PodResourceClaim(ref), + "k8s.io/api/core/v1.PodResourceClaimStatus": schema_k8sio_api_core_v1_PodResourceClaimStatus(ref), + "k8s.io/api/core/v1.PodSchedulingGate": schema_k8sio_api_core_v1_PodSchedulingGate(ref), + "k8s.io/api/core/v1.PodSecurityContext": schema_k8sio_api_core_v1_PodSecurityContext(ref), + "k8s.io/api/core/v1.PodSignature": schema_k8sio_api_core_v1_PodSignature(ref), + "k8s.io/api/core/v1.PodSpec": schema_k8sio_api_core_v1_PodSpec(ref), + "k8s.io/api/core/v1.PodStatus": schema_k8sio_api_core_v1_PodStatus(ref), + "k8s.io/api/core/v1.PodStatusResult": schema_k8sio_api_core_v1_PodStatusResult(ref), + "k8s.io/api/core/v1.PodTemplate": schema_k8sio_api_core_v1_PodTemplate(ref), + "k8s.io/api/core/v1.PodTemplateList": schema_k8sio_api_core_v1_PodTemplateList(ref), + "k8s.io/api/core/v1.PodTemplateSpec": schema_k8sio_api_core_v1_PodTemplateSpec(ref), + "k8s.io/api/core/v1.PortStatus": schema_k8sio_api_core_v1_PortStatus(ref), + "k8s.io/api/core/v1.PortworxVolumeSource": schema_k8sio_api_core_v1_PortworxVolumeSource(ref), + "k8s.io/api/core/v1.PreferAvoidPodsEntry": schema_k8sio_api_core_v1_PreferAvoidPodsEntry(ref), + "k8s.io/api/core/v1.PreferredSchedulingTerm": schema_k8sio_api_core_v1_PreferredSchedulingTerm(ref), + "k8s.io/api/core/v1.Probe": schema_k8sio_api_core_v1_Probe(ref), + "k8s.io/api/core/v1.ProbeHandler": schema_k8sio_api_core_v1_ProbeHandler(ref), + "k8s.io/api/core/v1.ProjectedVolumeSource": schema_k8sio_api_core_v1_ProjectedVolumeSource(ref), + "k8s.io/api/core/v1.QuobyteVolumeSource": schema_k8sio_api_core_v1_QuobyteVolumeSource(ref), + "k8s.io/api/core/v1.RBDPersistentVolumeSource": schema_k8sio_api_core_v1_RBDPersistentVolumeSource(ref), + "k8s.io/api/core/v1.RBDVolumeSource": schema_k8sio_api_core_v1_RBDVolumeSource(ref), + "k8s.io/api/core/v1.RangeAllocation": schema_k8sio_api_core_v1_RangeAllocation(ref), + "k8s.io/api/core/v1.ReplicationController": schema_k8sio_api_core_v1_ReplicationController(ref), + "k8s.io/api/core/v1.ReplicationControllerCondition": schema_k8sio_api_core_v1_ReplicationControllerCondition(ref), + "k8s.io/api/core/v1.ReplicationControllerList": schema_k8sio_api_core_v1_ReplicationControllerList(ref), + "k8s.io/api/core/v1.ReplicationControllerSpec": schema_k8sio_api_core_v1_ReplicationControllerSpec(ref), + "k8s.io/api/core/v1.ReplicationControllerStatus": schema_k8sio_api_core_v1_ReplicationControllerStatus(ref), + "k8s.io/api/core/v1.ResourceClaim": schema_k8sio_api_core_v1_ResourceClaim(ref), + "k8s.io/api/core/v1.ResourceFieldSelector": schema_k8sio_api_core_v1_ResourceFieldSelector(ref), + "k8s.io/api/core/v1.ResourceHealth": schema_k8sio_api_core_v1_ResourceHealth(ref), + "k8s.io/api/core/v1.ResourceQuota": schema_k8sio_api_core_v1_ResourceQuota(ref), + "k8s.io/api/core/v1.ResourceQuotaList": schema_k8sio_api_core_v1_ResourceQuotaList(ref), + "k8s.io/api/core/v1.ResourceQuotaSpec": schema_k8sio_api_core_v1_ResourceQuotaSpec(ref), + "k8s.io/api/core/v1.ResourceQuotaStatus": schema_k8sio_api_core_v1_ResourceQuotaStatus(ref), + "k8s.io/api/core/v1.ResourceRequirements": schema_k8sio_api_core_v1_ResourceRequirements(ref), + "k8s.io/api/core/v1.ResourceStatus": schema_k8sio_api_core_v1_ResourceStatus(ref), + "k8s.io/api/core/v1.SELinuxOptions": schema_k8sio_api_core_v1_SELinuxOptions(ref), + "k8s.io/api/core/v1.ScaleIOPersistentVolumeSource": schema_k8sio_api_core_v1_ScaleIOPersistentVolumeSource(ref), + "k8s.io/api/core/v1.ScaleIOVolumeSource": schema_k8sio_api_core_v1_ScaleIOVolumeSource(ref), + "k8s.io/api/core/v1.ScopeSelector": schema_k8sio_api_core_v1_ScopeSelector(ref), + "k8s.io/api/core/v1.ScopedResourceSelectorRequirement": schema_k8sio_api_core_v1_ScopedResourceSelectorRequirement(ref), + "k8s.io/api/core/v1.SeccompProfile": schema_k8sio_api_core_v1_SeccompProfile(ref), + "k8s.io/api/core/v1.Secret": schema_k8sio_api_core_v1_Secret(ref), + "k8s.io/api/core/v1.SecretEnvSource": schema_k8sio_api_core_v1_SecretEnvSource(ref), + "k8s.io/api/core/v1.SecretKeySelector": schema_k8sio_api_core_v1_SecretKeySelector(ref), + "k8s.io/api/core/v1.SecretList": schema_k8sio_api_core_v1_SecretList(ref), + "k8s.io/api/core/v1.SecretProjection": schema_k8sio_api_core_v1_SecretProjection(ref), + "k8s.io/api/core/v1.SecretReference": schema_k8sio_api_core_v1_SecretReference(ref), + "k8s.io/api/core/v1.SecretVolumeSource": schema_k8sio_api_core_v1_SecretVolumeSource(ref), + "k8s.io/api/core/v1.SecurityContext": schema_k8sio_api_core_v1_SecurityContext(ref), + "k8s.io/api/core/v1.SerializedReference": schema_k8sio_api_core_v1_SerializedReference(ref), + "k8s.io/api/core/v1.Service": schema_k8sio_api_core_v1_Service(ref), + "k8s.io/api/core/v1.ServiceAccount": schema_k8sio_api_core_v1_ServiceAccount(ref), + "k8s.io/api/core/v1.ServiceAccountList": schema_k8sio_api_core_v1_ServiceAccountList(ref), + "k8s.io/api/core/v1.ServiceAccountTokenProjection": schema_k8sio_api_core_v1_ServiceAccountTokenProjection(ref), + "k8s.io/api/core/v1.ServiceList": schema_k8sio_api_core_v1_ServiceList(ref), + "k8s.io/api/core/v1.ServicePort": schema_k8sio_api_core_v1_ServicePort(ref), + "k8s.io/api/core/v1.ServiceProxyOptions": schema_k8sio_api_core_v1_ServiceProxyOptions(ref), + "k8s.io/api/core/v1.ServiceSpec": schema_k8sio_api_core_v1_ServiceSpec(ref), + "k8s.io/api/core/v1.ServiceStatus": schema_k8sio_api_core_v1_ServiceStatus(ref), + "k8s.io/api/core/v1.SessionAffinityConfig": schema_k8sio_api_core_v1_SessionAffinityConfig(ref), + "k8s.io/api/core/v1.SleepAction": schema_k8sio_api_core_v1_SleepAction(ref), + "k8s.io/api/core/v1.StorageOSPersistentVolumeSource": schema_k8sio_api_core_v1_StorageOSPersistentVolumeSource(ref), + "k8s.io/api/core/v1.StorageOSVolumeSource": schema_k8sio_api_core_v1_StorageOSVolumeSource(ref), + "k8s.io/api/core/v1.Sysctl": schema_k8sio_api_core_v1_Sysctl(ref), + "k8s.io/api/core/v1.TCPSocketAction": schema_k8sio_api_core_v1_TCPSocketAction(ref), + "k8s.io/api/core/v1.Taint": schema_k8sio_api_core_v1_Taint(ref), + "k8s.io/api/core/v1.Toleration": schema_k8sio_api_core_v1_Toleration(ref), + "k8s.io/api/core/v1.TopologySelectorLabelRequirement": schema_k8sio_api_core_v1_TopologySelectorLabelRequirement(ref), + "k8s.io/api/core/v1.TopologySelectorTerm": schema_k8sio_api_core_v1_TopologySelectorTerm(ref), + "k8s.io/api/core/v1.TopologySpreadConstraint": schema_k8sio_api_core_v1_TopologySpreadConstraint(ref), + "k8s.io/api/core/v1.TypedLocalObjectReference": schema_k8sio_api_core_v1_TypedLocalObjectReference(ref), + "k8s.io/api/core/v1.TypedObjectReference": schema_k8sio_api_core_v1_TypedObjectReference(ref), + "k8s.io/api/core/v1.Volume": schema_k8sio_api_core_v1_Volume(ref), + "k8s.io/api/core/v1.VolumeDevice": schema_k8sio_api_core_v1_VolumeDevice(ref), + "k8s.io/api/core/v1.VolumeMount": schema_k8sio_api_core_v1_VolumeMount(ref), + "k8s.io/api/core/v1.VolumeMountStatus": schema_k8sio_api_core_v1_VolumeMountStatus(ref), + "k8s.io/api/core/v1.VolumeNodeAffinity": schema_k8sio_api_core_v1_VolumeNodeAffinity(ref), + "k8s.io/api/core/v1.VolumeProjection": schema_k8sio_api_core_v1_VolumeProjection(ref), + "k8s.io/api/core/v1.VolumeResourceRequirements": schema_k8sio_api_core_v1_VolumeResourceRequirements(ref), + "k8s.io/api/core/v1.VolumeSource": schema_k8sio_api_core_v1_VolumeSource(ref), + "k8s.io/api/core/v1.VsphereVirtualDiskVolumeSource": schema_k8sio_api_core_v1_VsphereVirtualDiskVolumeSource(ref), + "k8s.io/api/core/v1.WeightedPodAffinityTerm": schema_k8sio_api_core_v1_WeightedPodAffinityTerm(ref), + "k8s.io/api/core/v1.WindowsSecurityContextOptions": schema_k8sio_api_core_v1_WindowsSecurityContextOptions(ref), + "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.ConversionRequest": schema_pkg_apis_apiextensions_v1_ConversionRequest(ref), + "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.ConversionResponse": schema_pkg_apis_apiextensions_v1_ConversionResponse(ref), + "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.ConversionReview": schema_pkg_apis_apiextensions_v1_ConversionReview(ref), + "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.CustomResourceColumnDefinition": schema_pkg_apis_apiextensions_v1_CustomResourceColumnDefinition(ref), + "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.CustomResourceConversion": schema_pkg_apis_apiextensions_v1_CustomResourceConversion(ref), + "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.CustomResourceDefinition": schema_pkg_apis_apiextensions_v1_CustomResourceDefinition(ref), + "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.CustomResourceDefinitionCondition": schema_pkg_apis_apiextensions_v1_CustomResourceDefinitionCondition(ref), + "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.CustomResourceDefinitionList": schema_pkg_apis_apiextensions_v1_CustomResourceDefinitionList(ref), + "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.CustomResourceDefinitionNames": schema_pkg_apis_apiextensions_v1_CustomResourceDefinitionNames(ref), + "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.CustomResourceDefinitionSpec": schema_pkg_apis_apiextensions_v1_CustomResourceDefinitionSpec(ref), + "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.CustomResourceDefinitionStatus": schema_pkg_apis_apiextensions_v1_CustomResourceDefinitionStatus(ref), + "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.CustomResourceDefinitionVersion": schema_pkg_apis_apiextensions_v1_CustomResourceDefinitionVersion(ref), + "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.CustomResourceSubresourceScale": schema_pkg_apis_apiextensions_v1_CustomResourceSubresourceScale(ref), + "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.CustomResourceSubresourceStatus": schema_pkg_apis_apiextensions_v1_CustomResourceSubresourceStatus(ref), + "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.CustomResourceSubresources": schema_pkg_apis_apiextensions_v1_CustomResourceSubresources(ref), + "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.CustomResourceValidation": schema_pkg_apis_apiextensions_v1_CustomResourceValidation(ref), + "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.ExternalDocumentation": schema_pkg_apis_apiextensions_v1_ExternalDocumentation(ref), + "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.JSON": schema_pkg_apis_apiextensions_v1_JSON(ref), + "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.JSONSchemaProps": schema_pkg_apis_apiextensions_v1_JSONSchemaProps(ref), + "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.JSONSchemaPropsOrArray": schema_pkg_apis_apiextensions_v1_JSONSchemaPropsOrArray(ref), + "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.JSONSchemaPropsOrBool": schema_pkg_apis_apiextensions_v1_JSONSchemaPropsOrBool(ref), + "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.JSONSchemaPropsOrStringArray": schema_pkg_apis_apiextensions_v1_JSONSchemaPropsOrStringArray(ref), + "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.SelectableField": schema_pkg_apis_apiextensions_v1_SelectableField(ref), + "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.ServiceReference": schema_pkg_apis_apiextensions_v1_ServiceReference(ref), + "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.ValidationRule": schema_pkg_apis_apiextensions_v1_ValidationRule(ref), + "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.WebhookClientConfig": schema_pkg_apis_apiextensions_v1_WebhookClientConfig(ref), + "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.WebhookConversion": schema_pkg_apis_apiextensions_v1_WebhookConversion(ref), + "k8s.io/apimachinery/pkg/api/resource.Quantity": schema_apimachinery_pkg_api_resource_Quantity(ref), + "k8s.io/apimachinery/pkg/apis/meta/v1.APIGroup": schema_pkg_apis_meta_v1_APIGroup(ref), + "k8s.io/apimachinery/pkg/apis/meta/v1.APIGroupList": schema_pkg_apis_meta_v1_APIGroupList(ref), + "k8s.io/apimachinery/pkg/apis/meta/v1.APIResource": schema_pkg_apis_meta_v1_APIResource(ref), + "k8s.io/apimachinery/pkg/apis/meta/v1.APIResourceList": schema_pkg_apis_meta_v1_APIResourceList(ref), + "k8s.io/apimachinery/pkg/apis/meta/v1.APIVersions": schema_pkg_apis_meta_v1_APIVersions(ref), + "k8s.io/apimachinery/pkg/apis/meta/v1.ApplyOptions": schema_pkg_apis_meta_v1_ApplyOptions(ref), + "k8s.io/apimachinery/pkg/apis/meta/v1.Condition": schema_pkg_apis_meta_v1_Condition(ref), + "k8s.io/apimachinery/pkg/apis/meta/v1.CreateOptions": schema_pkg_apis_meta_v1_CreateOptions(ref), + "k8s.io/apimachinery/pkg/apis/meta/v1.DeleteOptions": schema_pkg_apis_meta_v1_DeleteOptions(ref), + "k8s.io/apimachinery/pkg/apis/meta/v1.Duration": schema_pkg_apis_meta_v1_Duration(ref), + "k8s.io/apimachinery/pkg/apis/meta/v1.FieldSelectorRequirement": schema_pkg_apis_meta_v1_FieldSelectorRequirement(ref), + "k8s.io/apimachinery/pkg/apis/meta/v1.FieldsV1": schema_pkg_apis_meta_v1_FieldsV1(ref), + "k8s.io/apimachinery/pkg/apis/meta/v1.GetOptions": schema_pkg_apis_meta_v1_GetOptions(ref), + "k8s.io/apimachinery/pkg/apis/meta/v1.GroupKind": schema_pkg_apis_meta_v1_GroupKind(ref), + "k8s.io/apimachinery/pkg/apis/meta/v1.GroupResource": schema_pkg_apis_meta_v1_GroupResource(ref), + "k8s.io/apimachinery/pkg/apis/meta/v1.GroupVersion": schema_pkg_apis_meta_v1_GroupVersion(ref), + "k8s.io/apimachinery/pkg/apis/meta/v1.GroupVersionForDiscovery": schema_pkg_apis_meta_v1_GroupVersionForDiscovery(ref), + "k8s.io/apimachinery/pkg/apis/meta/v1.GroupVersionKind": schema_pkg_apis_meta_v1_GroupVersionKind(ref), + "k8s.io/apimachinery/pkg/apis/meta/v1.GroupVersionResource": schema_pkg_apis_meta_v1_GroupVersionResource(ref), + "k8s.io/apimachinery/pkg/apis/meta/v1.InternalEvent": schema_pkg_apis_meta_v1_InternalEvent(ref), + "k8s.io/apimachinery/pkg/apis/meta/v1.LabelSelector": schema_pkg_apis_meta_v1_LabelSelector(ref), + "k8s.io/apimachinery/pkg/apis/meta/v1.LabelSelectorRequirement": schema_pkg_apis_meta_v1_LabelSelectorRequirement(ref), + "k8s.io/apimachinery/pkg/apis/meta/v1.List": schema_pkg_apis_meta_v1_List(ref), + "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta": schema_pkg_apis_meta_v1_ListMeta(ref), + "k8s.io/apimachinery/pkg/apis/meta/v1.ListOptions": schema_pkg_apis_meta_v1_ListOptions(ref), + "k8s.io/apimachinery/pkg/apis/meta/v1.ManagedFieldsEntry": schema_pkg_apis_meta_v1_ManagedFieldsEntry(ref), + "k8s.io/apimachinery/pkg/apis/meta/v1.MicroTime": schema_pkg_apis_meta_v1_MicroTime(ref), + "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta": schema_pkg_apis_meta_v1_ObjectMeta(ref), + "k8s.io/apimachinery/pkg/apis/meta/v1.OwnerReference": schema_pkg_apis_meta_v1_OwnerReference(ref), + "k8s.io/apimachinery/pkg/apis/meta/v1.PartialObjectMetadata": schema_pkg_apis_meta_v1_PartialObjectMetadata(ref), + "k8s.io/apimachinery/pkg/apis/meta/v1.PartialObjectMetadataList": schema_pkg_apis_meta_v1_PartialObjectMetadataList(ref), + "k8s.io/apimachinery/pkg/apis/meta/v1.Patch": schema_pkg_apis_meta_v1_Patch(ref), + "k8s.io/apimachinery/pkg/apis/meta/v1.PatchOptions": schema_pkg_apis_meta_v1_PatchOptions(ref), + "k8s.io/apimachinery/pkg/apis/meta/v1.Preconditions": schema_pkg_apis_meta_v1_Preconditions(ref), + "k8s.io/apimachinery/pkg/apis/meta/v1.RootPaths": schema_pkg_apis_meta_v1_RootPaths(ref), + "k8s.io/apimachinery/pkg/apis/meta/v1.ServerAddressByClientCIDR": schema_pkg_apis_meta_v1_ServerAddressByClientCIDR(ref), + "k8s.io/apimachinery/pkg/apis/meta/v1.Status": schema_pkg_apis_meta_v1_Status(ref), + "k8s.io/apimachinery/pkg/apis/meta/v1.StatusCause": schema_pkg_apis_meta_v1_StatusCause(ref), + "k8s.io/apimachinery/pkg/apis/meta/v1.StatusDetails": schema_pkg_apis_meta_v1_StatusDetails(ref), + "k8s.io/apimachinery/pkg/apis/meta/v1.Table": schema_pkg_apis_meta_v1_Table(ref), + "k8s.io/apimachinery/pkg/apis/meta/v1.TableColumnDefinition": schema_pkg_apis_meta_v1_TableColumnDefinition(ref), + "k8s.io/apimachinery/pkg/apis/meta/v1.TableOptions": schema_pkg_apis_meta_v1_TableOptions(ref), + "k8s.io/apimachinery/pkg/apis/meta/v1.TableRow": schema_pkg_apis_meta_v1_TableRow(ref), + "k8s.io/apimachinery/pkg/apis/meta/v1.TableRowCondition": schema_pkg_apis_meta_v1_TableRowCondition(ref), + "k8s.io/apimachinery/pkg/apis/meta/v1.Time": schema_pkg_apis_meta_v1_Time(ref), + "k8s.io/apimachinery/pkg/apis/meta/v1.Timestamp": schema_pkg_apis_meta_v1_Timestamp(ref), + "k8s.io/apimachinery/pkg/apis/meta/v1.TypeMeta": schema_pkg_apis_meta_v1_TypeMeta(ref), + "k8s.io/apimachinery/pkg/apis/meta/v1.UpdateOptions": schema_pkg_apis_meta_v1_UpdateOptions(ref), + "k8s.io/apimachinery/pkg/apis/meta/v1.WatchEvent": schema_pkg_apis_meta_v1_WatchEvent(ref), + "k8s.io/apimachinery/pkg/runtime.RawExtension": schema_k8sio_apimachinery_pkg_runtime_RawExtension(ref), + "k8s.io/apimachinery/pkg/runtime.TypeMeta": schema_k8sio_apimachinery_pkg_runtime_TypeMeta(ref), + "k8s.io/apimachinery/pkg/runtime.Unknown": schema_k8sio_apimachinery_pkg_runtime_Unknown(ref), + "k8s.io/apimachinery/pkg/util/intstr.IntOrString": schema_apimachinery_pkg_util_intstr_IntOrString(ref), + "k8s.io/apimachinery/pkg/version.Info": schema_k8sio_apimachinery_pkg_version_Info(ref), + "sigs.k8s.io/gateway-api/apis/v1.AllowedListeners": schema_sigsk8sio_gateway_api_apis_v1_AllowedListeners(ref), + "sigs.k8s.io/gateway-api/apis/v1.AllowedRoutes": schema_sigsk8sio_gateway_api_apis_v1_AllowedRoutes(ref), + "sigs.k8s.io/gateway-api/apis/v1.BackendObjectReference": schema_sigsk8sio_gateway_api_apis_v1_BackendObjectReference(ref), + "sigs.k8s.io/gateway-api/apis/v1.BackendRef": schema_sigsk8sio_gateway_api_apis_v1_BackendRef(ref), + "sigs.k8s.io/gateway-api/apis/v1.BackendTLSPolicy": schema_sigsk8sio_gateway_api_apis_v1_BackendTLSPolicy(ref), + "sigs.k8s.io/gateway-api/apis/v1.BackendTLSPolicyList": schema_sigsk8sio_gateway_api_apis_v1_BackendTLSPolicyList(ref), + "sigs.k8s.io/gateway-api/apis/v1.BackendTLSPolicySpec": schema_sigsk8sio_gateway_api_apis_v1_BackendTLSPolicySpec(ref), + "sigs.k8s.io/gateway-api/apis/v1.BackendTLSPolicyValidation": schema_sigsk8sio_gateway_api_apis_v1_BackendTLSPolicyValidation(ref), + "sigs.k8s.io/gateway-api/apis/v1.CommonRouteSpec": schema_sigsk8sio_gateway_api_apis_v1_CommonRouteSpec(ref), + "sigs.k8s.io/gateway-api/apis/v1.CookieConfig": schema_sigsk8sio_gateway_api_apis_v1_CookieConfig(ref), + "sigs.k8s.io/gateway-api/apis/v1.ForwardBodyConfig": schema_sigsk8sio_gateway_api_apis_v1_ForwardBodyConfig(ref), + "sigs.k8s.io/gateway-api/apis/v1.Fraction": schema_sigsk8sio_gateway_api_apis_v1_Fraction(ref), + "sigs.k8s.io/gateway-api/apis/v1.FrontendTLSConfig": schema_sigsk8sio_gateway_api_apis_v1_FrontendTLSConfig(ref), + "sigs.k8s.io/gateway-api/apis/v1.FrontendTLSValidation": schema_sigsk8sio_gateway_api_apis_v1_FrontendTLSValidation(ref), + "sigs.k8s.io/gateway-api/apis/v1.GRPCAuthConfig": schema_sigsk8sio_gateway_api_apis_v1_GRPCAuthConfig(ref), + "sigs.k8s.io/gateway-api/apis/v1.GRPCBackendRef": schema_sigsk8sio_gateway_api_apis_v1_GRPCBackendRef(ref), + "sigs.k8s.io/gateway-api/apis/v1.GRPCHeaderMatch": schema_sigsk8sio_gateway_api_apis_v1_GRPCHeaderMatch(ref), + "sigs.k8s.io/gateway-api/apis/v1.GRPCMethodMatch": schema_sigsk8sio_gateway_api_apis_v1_GRPCMethodMatch(ref), + "sigs.k8s.io/gateway-api/apis/v1.GRPCRoute": schema_sigsk8sio_gateway_api_apis_v1_GRPCRoute(ref), + "sigs.k8s.io/gateway-api/apis/v1.GRPCRouteFilter": schema_sigsk8sio_gateway_api_apis_v1_GRPCRouteFilter(ref), + "sigs.k8s.io/gateway-api/apis/v1.GRPCRouteList": schema_sigsk8sio_gateway_api_apis_v1_GRPCRouteList(ref), + "sigs.k8s.io/gateway-api/apis/v1.GRPCRouteMatch": schema_sigsk8sio_gateway_api_apis_v1_GRPCRouteMatch(ref), + "sigs.k8s.io/gateway-api/apis/v1.GRPCRouteRule": schema_sigsk8sio_gateway_api_apis_v1_GRPCRouteRule(ref), + "sigs.k8s.io/gateway-api/apis/v1.GRPCRouteSpec": schema_sigsk8sio_gateway_api_apis_v1_GRPCRouteSpec(ref), + "sigs.k8s.io/gateway-api/apis/v1.GRPCRouteStatus": schema_sigsk8sio_gateway_api_apis_v1_GRPCRouteStatus(ref), + "sigs.k8s.io/gateway-api/apis/v1.Gateway": schema_sigsk8sio_gateway_api_apis_v1_Gateway(ref), + "sigs.k8s.io/gateway-api/apis/v1.GatewayBackendTLS": schema_sigsk8sio_gateway_api_apis_v1_GatewayBackendTLS(ref), + "sigs.k8s.io/gateway-api/apis/v1.GatewayClass": schema_sigsk8sio_gateway_api_apis_v1_GatewayClass(ref), + "sigs.k8s.io/gateway-api/apis/v1.GatewayClassList": schema_sigsk8sio_gateway_api_apis_v1_GatewayClassList(ref), + "sigs.k8s.io/gateway-api/apis/v1.GatewayClassSpec": schema_sigsk8sio_gateway_api_apis_v1_GatewayClassSpec(ref), + "sigs.k8s.io/gateway-api/apis/v1.GatewayClassStatus": schema_sigsk8sio_gateway_api_apis_v1_GatewayClassStatus(ref), + "sigs.k8s.io/gateway-api/apis/v1.GatewayInfrastructure": schema_sigsk8sio_gateway_api_apis_v1_GatewayInfrastructure(ref), + "sigs.k8s.io/gateway-api/apis/v1.GatewayList": schema_sigsk8sio_gateway_api_apis_v1_GatewayList(ref), + "sigs.k8s.io/gateway-api/apis/v1.GatewaySpec": schema_sigsk8sio_gateway_api_apis_v1_GatewaySpec(ref), + "sigs.k8s.io/gateway-api/apis/v1.GatewaySpecAddress": schema_sigsk8sio_gateway_api_apis_v1_GatewaySpecAddress(ref), + "sigs.k8s.io/gateway-api/apis/v1.GatewayStatus": schema_sigsk8sio_gateway_api_apis_v1_GatewayStatus(ref), + "sigs.k8s.io/gateway-api/apis/v1.GatewayStatusAddress": schema_sigsk8sio_gateway_api_apis_v1_GatewayStatusAddress(ref), + "sigs.k8s.io/gateway-api/apis/v1.GatewayTLSConfig": schema_sigsk8sio_gateway_api_apis_v1_GatewayTLSConfig(ref), + "sigs.k8s.io/gateway-api/apis/v1.HTTPAuthConfig": schema_sigsk8sio_gateway_api_apis_v1_HTTPAuthConfig(ref), + "sigs.k8s.io/gateway-api/apis/v1.HTTPBackendRef": schema_sigsk8sio_gateway_api_apis_v1_HTTPBackendRef(ref), + "sigs.k8s.io/gateway-api/apis/v1.HTTPCORSFilter": schema_sigsk8sio_gateway_api_apis_v1_HTTPCORSFilter(ref), + "sigs.k8s.io/gateway-api/apis/v1.HTTPExternalAuthFilter": schema_sigsk8sio_gateway_api_apis_v1_HTTPExternalAuthFilter(ref), + "sigs.k8s.io/gateway-api/apis/v1.HTTPHeader": schema_sigsk8sio_gateway_api_apis_v1_HTTPHeader(ref), + "sigs.k8s.io/gateway-api/apis/v1.HTTPHeaderFilter": schema_sigsk8sio_gateway_api_apis_v1_HTTPHeaderFilter(ref), + "sigs.k8s.io/gateway-api/apis/v1.HTTPHeaderMatch": schema_sigsk8sio_gateway_api_apis_v1_HTTPHeaderMatch(ref), + "sigs.k8s.io/gateway-api/apis/v1.HTTPPathMatch": schema_sigsk8sio_gateway_api_apis_v1_HTTPPathMatch(ref), + "sigs.k8s.io/gateway-api/apis/v1.HTTPPathModifier": schema_sigsk8sio_gateway_api_apis_v1_HTTPPathModifier(ref), + "sigs.k8s.io/gateway-api/apis/v1.HTTPQueryParamMatch": schema_sigsk8sio_gateway_api_apis_v1_HTTPQueryParamMatch(ref), + "sigs.k8s.io/gateway-api/apis/v1.HTTPRequestMirrorFilter": schema_sigsk8sio_gateway_api_apis_v1_HTTPRequestMirrorFilter(ref), + "sigs.k8s.io/gateway-api/apis/v1.HTTPRequestRedirectFilter": schema_sigsk8sio_gateway_api_apis_v1_HTTPRequestRedirectFilter(ref), + "sigs.k8s.io/gateway-api/apis/v1.HTTPRoute": schema_sigsk8sio_gateway_api_apis_v1_HTTPRoute(ref), + "sigs.k8s.io/gateway-api/apis/v1.HTTPRouteFilter": schema_sigsk8sio_gateway_api_apis_v1_HTTPRouteFilter(ref), + "sigs.k8s.io/gateway-api/apis/v1.HTTPRouteList": schema_sigsk8sio_gateway_api_apis_v1_HTTPRouteList(ref), + "sigs.k8s.io/gateway-api/apis/v1.HTTPRouteMatch": schema_sigsk8sio_gateway_api_apis_v1_HTTPRouteMatch(ref), + "sigs.k8s.io/gateway-api/apis/v1.HTTPRouteRetry": schema_sigsk8sio_gateway_api_apis_v1_HTTPRouteRetry(ref), + "sigs.k8s.io/gateway-api/apis/v1.HTTPRouteRule": schema_sigsk8sio_gateway_api_apis_v1_HTTPRouteRule(ref), + "sigs.k8s.io/gateway-api/apis/v1.HTTPRouteSpec": schema_sigsk8sio_gateway_api_apis_v1_HTTPRouteSpec(ref), + "sigs.k8s.io/gateway-api/apis/v1.HTTPRouteStatus": schema_sigsk8sio_gateway_api_apis_v1_HTTPRouteStatus(ref), + "sigs.k8s.io/gateway-api/apis/v1.HTTPRouteTimeouts": schema_sigsk8sio_gateway_api_apis_v1_HTTPRouteTimeouts(ref), + "sigs.k8s.io/gateway-api/apis/v1.HTTPURLRewriteFilter": schema_sigsk8sio_gateway_api_apis_v1_HTTPURLRewriteFilter(ref), + "sigs.k8s.io/gateway-api/apis/v1.Listener": schema_sigsk8sio_gateway_api_apis_v1_Listener(ref), + "sigs.k8s.io/gateway-api/apis/v1.ListenerNamespaces": schema_sigsk8sio_gateway_api_apis_v1_ListenerNamespaces(ref), + "sigs.k8s.io/gateway-api/apis/v1.ListenerStatus": schema_sigsk8sio_gateway_api_apis_v1_ListenerStatus(ref), + "sigs.k8s.io/gateway-api/apis/v1.ListenerTLSConfig": schema_sigsk8sio_gateway_api_apis_v1_ListenerTLSConfig(ref), + "sigs.k8s.io/gateway-api/apis/v1.LocalObjectReference": schema_sigsk8sio_gateway_api_apis_v1_LocalObjectReference(ref), + "sigs.k8s.io/gateway-api/apis/v1.LocalParametersReference": schema_sigsk8sio_gateway_api_apis_v1_LocalParametersReference(ref), + "sigs.k8s.io/gateway-api/apis/v1.LocalPolicyTargetReference": schema_sigsk8sio_gateway_api_apis_v1_LocalPolicyTargetReference(ref), + "sigs.k8s.io/gateway-api/apis/v1.LocalPolicyTargetReferenceWithSectionName": schema_sigsk8sio_gateway_api_apis_v1_LocalPolicyTargetReferenceWithSectionName(ref), + "sigs.k8s.io/gateway-api/apis/v1.NamespacedPolicyTargetReference": schema_sigsk8sio_gateway_api_apis_v1_NamespacedPolicyTargetReference(ref), + "sigs.k8s.io/gateway-api/apis/v1.ObjectReference": schema_sigsk8sio_gateway_api_apis_v1_ObjectReference(ref), + "sigs.k8s.io/gateway-api/apis/v1.ParametersReference": schema_sigsk8sio_gateway_api_apis_v1_ParametersReference(ref), + "sigs.k8s.io/gateway-api/apis/v1.ParentReference": schema_sigsk8sio_gateway_api_apis_v1_ParentReference(ref), + "sigs.k8s.io/gateway-api/apis/v1.PolicyAncestorStatus": schema_sigsk8sio_gateway_api_apis_v1_PolicyAncestorStatus(ref), + "sigs.k8s.io/gateway-api/apis/v1.PolicyStatus": schema_sigsk8sio_gateway_api_apis_v1_PolicyStatus(ref), + "sigs.k8s.io/gateway-api/apis/v1.RouteGroupKind": schema_sigsk8sio_gateway_api_apis_v1_RouteGroupKind(ref), + "sigs.k8s.io/gateway-api/apis/v1.RouteNamespaces": schema_sigsk8sio_gateway_api_apis_v1_RouteNamespaces(ref), + "sigs.k8s.io/gateway-api/apis/v1.RouteParentStatus": schema_sigsk8sio_gateway_api_apis_v1_RouteParentStatus(ref), + "sigs.k8s.io/gateway-api/apis/v1.RouteStatus": schema_sigsk8sio_gateway_api_apis_v1_RouteStatus(ref), + "sigs.k8s.io/gateway-api/apis/v1.SecretObjectReference": schema_sigsk8sio_gateway_api_apis_v1_SecretObjectReference(ref), + "sigs.k8s.io/gateway-api/apis/v1.SessionPersistence": schema_sigsk8sio_gateway_api_apis_v1_SessionPersistence(ref), + "sigs.k8s.io/gateway-api/apis/v1.SubjectAltName": schema_sigsk8sio_gateway_api_apis_v1_SubjectAltName(ref), + "sigs.k8s.io/gateway-api/apis/v1.SupportedFeature": schema_sigsk8sio_gateway_api_apis_v1_SupportedFeature(ref), + "sigs.k8s.io/gateway-api/apis/v1.TLSConfig": schema_sigsk8sio_gateway_api_apis_v1_TLSConfig(ref), + "sigs.k8s.io/gateway-api/apis/v1.TLSPortConfig": schema_sigsk8sio_gateway_api_apis_v1_TLSPortConfig(ref), + } +} + +func schema_pkg_apis_acme_v1_ACMEAuthorization(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "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.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "url": { + SchemaProps: spec.SchemaProps{ + Description: "URL is the URL of the Authorization that must be completed", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "identifier": { + SchemaProps: spec.SchemaProps{ + Description: "Identifier is the DNS name to be validated as part of this authorization", + Type: []string{"string"}, + Format: "", + }, + }, + "wildcard": { + SchemaProps: spec.SchemaProps{ + Description: "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'.", + Type: []string{"boolean"}, + Format: "", + }, + }, + "initialState": { + SchemaProps: spec.SchemaProps{ + Description: "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.", + Type: []string{"string"}, + Format: "", + }, + }, + "challenges": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "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.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("github.com/cert-manager/cert-manager/pkg/apis/acme/v1.ACMEChallenge"), + }, + }, + }, + }, + }, + }, + Required: []string{"url"}, + }, + }, + Dependencies: []string{ + "github.com/cert-manager/cert-manager/pkg/apis/acme/v1.ACMEChallenge"}, + } +} + +func schema_pkg_apis_acme_v1_ACMEChallenge(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "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.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "url": { + SchemaProps: spec.SchemaProps{ + Description: "URL is the URL of this challenge. It can be used to retrieve additional metadata about the Challenge from the ACME server.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "token": { + SchemaProps: spec.SchemaProps{ + Description: "Token is the token that must be presented for this challenge. This is used to compute the 'key' that must also be presented.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "type": { + SchemaProps: spec.SchemaProps{ + Description: "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.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"url", "token", "type"}, + }, + }, + } +} + +func schema_pkg_apis_acme_v1_ACMEChallengeSolver(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "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.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "selector": { + SchemaProps: spec.SchemaProps{ + Description: "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.", + Ref: ref("github.com/cert-manager/cert-manager/pkg/apis/acme/v1.CertificateDNSNameSelector"), + }, + }, + "http01": { + SchemaProps: spec.SchemaProps{ + Description: "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.", + Ref: ref("github.com/cert-manager/cert-manager/pkg/apis/acme/v1.ACMEChallengeSolverHTTP01"), + }, + }, + "dns01": { + SchemaProps: spec.SchemaProps{ + Description: "Configures cert-manager to attempt to complete authorizations by performing the DNS01 challenge flow.", + Ref: ref("github.com/cert-manager/cert-manager/pkg/apis/acme/v1.ACMEChallengeSolverDNS01"), + }, + }, + }, + }, + }, + Dependencies: []string{ + "github.com/cert-manager/cert-manager/pkg/apis/acme/v1.ACMEChallengeSolverDNS01", "github.com/cert-manager/cert-manager/pkg/apis/acme/v1.ACMEChallengeSolverHTTP01", "github.com/cert-manager/cert-manager/pkg/apis/acme/v1.CertificateDNSNameSelector"}, + } +} + +func schema_pkg_apis_acme_v1_ACMEChallengeSolverDNS01(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "Used to configure a DNS01 challenge provider to be used when solving DNS01 challenges. Only one DNS provider may be configured per solver.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "cnameStrategy": { + SchemaProps: spec.SchemaProps{ + Description: "CNAMEStrategy configures how the DNS01 provider should handle CNAME records when found in DNS zones.", + Type: []string{"string"}, + Format: "", + }, + }, + "akamai": { + SchemaProps: spec.SchemaProps{ + Description: "Use the Akamai DNS zone management API to manage DNS01 challenge records.", + Ref: ref("github.com/cert-manager/cert-manager/pkg/apis/acme/v1.ACMEIssuerDNS01ProviderAkamai"), + }, + }, + "cloudDNS": { + SchemaProps: spec.SchemaProps{ + Description: "Use the Google Cloud DNS API to manage DNS01 challenge records.", + Ref: ref("github.com/cert-manager/cert-manager/pkg/apis/acme/v1.ACMEIssuerDNS01ProviderCloudDNS"), + }, + }, + "cloudflare": { + SchemaProps: spec.SchemaProps{ + Description: "Use the Cloudflare API to manage DNS01 challenge records.", + Ref: ref("github.com/cert-manager/cert-manager/pkg/apis/acme/v1.ACMEIssuerDNS01ProviderCloudflare"), + }, + }, + "route53": { + SchemaProps: spec.SchemaProps{ + Description: "Use the AWS Route53 API to manage DNS01 challenge records.", + Ref: ref("github.com/cert-manager/cert-manager/pkg/apis/acme/v1.ACMEIssuerDNS01ProviderRoute53"), + }, + }, + "azureDNS": { + SchemaProps: spec.SchemaProps{ + Description: "Use the Microsoft Azure DNS API to manage DNS01 challenge records.", + Ref: ref("github.com/cert-manager/cert-manager/pkg/apis/acme/v1.ACMEIssuerDNS01ProviderAzureDNS"), + }, + }, + "digitalocean": { + SchemaProps: spec.SchemaProps{ + Description: "Use the DigitalOcean DNS API to manage DNS01 challenge records.", + Ref: ref("github.com/cert-manager/cert-manager/pkg/apis/acme/v1.ACMEIssuerDNS01ProviderDigitalOcean"), + }, + }, + "acmeDNS": { + SchemaProps: spec.SchemaProps{ + Description: "Use the 'ACME DNS' (https://github.com/joohoi/acme-dns) API to manage DNS01 challenge records.", + Ref: ref("github.com/cert-manager/cert-manager/pkg/apis/acme/v1.ACMEIssuerDNS01ProviderAcmeDNS"), + }, + }, + "rfc2136": { + SchemaProps: spec.SchemaProps{ + Description: "Use RFC2136 (\"Dynamic Updates in the Domain Name System\") (https://datatracker.ietf.org/doc/rfc2136/) to manage DNS01 challenge records.", + Ref: ref("github.com/cert-manager/cert-manager/pkg/apis/acme/v1.ACMEIssuerDNS01ProviderRFC2136"), + }, + }, + "webhook": { + SchemaProps: spec.SchemaProps{ + Description: "Configure an external webhook based DNS01 challenge solver to manage DNS01 challenge records.", + Ref: ref("github.com/cert-manager/cert-manager/pkg/apis/acme/v1.ACMEIssuerDNS01ProviderWebhook"), + }, + }, + }, + }, + }, + Dependencies: []string{ + "github.com/cert-manager/cert-manager/pkg/apis/acme/v1.ACMEIssuerDNS01ProviderAcmeDNS", "github.com/cert-manager/cert-manager/pkg/apis/acme/v1.ACMEIssuerDNS01ProviderAkamai", "github.com/cert-manager/cert-manager/pkg/apis/acme/v1.ACMEIssuerDNS01ProviderAzureDNS", "github.com/cert-manager/cert-manager/pkg/apis/acme/v1.ACMEIssuerDNS01ProviderCloudDNS", "github.com/cert-manager/cert-manager/pkg/apis/acme/v1.ACMEIssuerDNS01ProviderCloudflare", "github.com/cert-manager/cert-manager/pkg/apis/acme/v1.ACMEIssuerDNS01ProviderDigitalOcean", "github.com/cert-manager/cert-manager/pkg/apis/acme/v1.ACMEIssuerDNS01ProviderRFC2136", "github.com/cert-manager/cert-manager/pkg/apis/acme/v1.ACMEIssuerDNS01ProviderRoute53", "github.com/cert-manager/cert-manager/pkg/apis/acme/v1.ACMEIssuerDNS01ProviderWebhook"}, + } +} + +func schema_pkg_apis_acme_v1_ACMEChallengeSolverHTTP01(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "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.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "ingress": { + SchemaProps: spec.SchemaProps{ + Description: "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.", + Ref: ref("github.com/cert-manager/cert-manager/pkg/apis/acme/v1.ACMEChallengeSolverHTTP01Ingress"), + }, + }, + "gatewayHTTPRoute": { + SchemaProps: spec.SchemaProps{ + Description: "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.", + Ref: ref("github.com/cert-manager/cert-manager/pkg/apis/acme/v1.ACMEChallengeSolverHTTP01GatewayHTTPRoute"), + }, + }, + }, + }, + }, + Dependencies: []string{ + "github.com/cert-manager/cert-manager/pkg/apis/acme/v1.ACMEChallengeSolverHTTP01GatewayHTTPRoute", "github.com/cert-manager/cert-manager/pkg/apis/acme/v1.ACMEChallengeSolverHTTP01Ingress"}, + } +} + +func schema_pkg_apis_acme_v1_ACMEChallengeSolverHTTP01GatewayHTTPRoute(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "The ACMEChallengeSolverHTTP01GatewayHTTPRoute solver will create HTTPRoute objects for a Gateway class routing to an ACME challenge solver pod.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "serviceType": { + SchemaProps: spec.SchemaProps{ + Description: "Optional service type for Kubernetes solver service. Supported values are NodePort or ClusterIP. If unset, defaults to NodePort.\n\nPossible enum values:\n - `\"ClusterIP\"` means a service will only be accessible inside the cluster, via the cluster IP.\n - `\"ExternalName\"` means a service consists of only a reference to an external name that kubedns or equivalent will return as a CNAME record, with no exposing or proxying of any pods involved.\n - `\"LoadBalancer\"` means a service will be exposed via an external load balancer (if the cloud provider supports it), in addition to 'NodePort' type.\n - `\"NodePort\"` means a service will be exposed on one port of every node, in addition to 'ClusterIP' type.", + Type: []string{"string"}, + Format: "", + Enum: []interface{}{"ClusterIP", "ExternalName", "LoadBalancer", "NodePort"}, + }, + }, + "labels": { + SchemaProps: spec.SchemaProps{ + Description: "Custom labels that will be applied to HTTPRoutes created by cert-manager while solving HTTP-01 challenges.", + Type: []string{"object"}, + AdditionalProperties: &spec.SchemaOrBool{ + Allows: true, + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + "parentRefs": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "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", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("sigs.k8s.io/gateway-api/apis/v1.ParentReference"), + }, + }, + }, + }, + }, + "podTemplate": { + SchemaProps: spec.SchemaProps{ + Description: "Optional pod template used to configure the ACME challenge solver pods used for HTTP01 challenges.", + Ref: ref("github.com/cert-manager/cert-manager/pkg/apis/acme/v1.ACMEChallengeSolverHTTP01IngressPodTemplate"), + }, + }, + }, + }, + }, + Dependencies: []string{ + "github.com/cert-manager/cert-manager/pkg/apis/acme/v1.ACMEChallengeSolverHTTP01IngressPodTemplate", "sigs.k8s.io/gateway-api/apis/v1.ParentReference"}, + } +} + +func schema_pkg_apis_acme_v1_ACMEChallengeSolverHTTP01Ingress(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "serviceType": { + SchemaProps: spec.SchemaProps{ + Description: "Optional service type for Kubernetes solver service. Supported values are NodePort or ClusterIP. If unset, defaults to NodePort.\n\nPossible enum values:\n - `\"ClusterIP\"` means a service will only be accessible inside the cluster, via the cluster IP.\n - `\"ExternalName\"` means a service consists of only a reference to an external name that kubedns or equivalent will return as a CNAME record, with no exposing or proxying of any pods involved.\n - `\"LoadBalancer\"` means a service will be exposed via an external load balancer (if the cloud provider supports it), in addition to 'NodePort' type.\n - `\"NodePort\"` means a service will be exposed on one port of every node, in addition to 'ClusterIP' type.", + Type: []string{"string"}, + Format: "", + Enum: []interface{}{"ClusterIP", "ExternalName", "LoadBalancer", "NodePort"}, + }, + }, + "ingressClassName": { + SchemaProps: spec.SchemaProps{ + Description: "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.", + Type: []string{"string"}, + Format: "", + }, + }, + "class": { + SchemaProps: spec.SchemaProps{ + Description: "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.", + Type: []string{"string"}, + Format: "", + }, + }, + "name": { + SchemaProps: spec.SchemaProps{ + Description: "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.", + Type: []string{"string"}, + Format: "", + }, + }, + "podTemplate": { + SchemaProps: spec.SchemaProps{ + Description: "Optional pod template used to configure the ACME challenge solver pods used for HTTP01 challenges.", + Ref: ref("github.com/cert-manager/cert-manager/pkg/apis/acme/v1.ACMEChallengeSolverHTTP01IngressPodTemplate"), + }, + }, + "ingressTemplate": { + SchemaProps: spec.SchemaProps{ + Description: "Optional ingress template used to configure the ACME challenge solver ingress used for HTTP01 challenges.", + Ref: ref("github.com/cert-manager/cert-manager/pkg/apis/acme/v1.ACMEChallengeSolverHTTP01IngressTemplate"), + }, + }, + }, + }, + }, + Dependencies: []string{ + "github.com/cert-manager/cert-manager/pkg/apis/acme/v1.ACMEChallengeSolverHTTP01IngressPodTemplate", "github.com/cert-manager/cert-manager/pkg/apis/acme/v1.ACMEChallengeSolverHTTP01IngressTemplate"}, + } +} + +func schema_pkg_apis_acme_v1_ACMEChallengeSolverHTTP01IngressObjectMeta(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "annotations": { + SchemaProps: spec.SchemaProps{ + Description: "Annotations that should be added to the created ACME HTTP01 solver ingress.", + Type: []string{"object"}, + AdditionalProperties: &spec.SchemaOrBool{ + Allows: true, + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + "labels": { + SchemaProps: spec.SchemaProps{ + Description: "Labels that should be added to the created ACME HTTP01 solver ingress.", + Type: []string{"object"}, + AdditionalProperties: &spec.SchemaOrBool{ + Allows: true, + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + }, + }, + }, + } +} + +func schema_pkg_apis_acme_v1_ACMEChallengeSolverHTTP01IngressPodObjectMeta(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "annotations": { + SchemaProps: spec.SchemaProps{ + Description: "Annotations that should be added to the created ACME HTTP01 solver pods.", + Type: []string{"object"}, + AdditionalProperties: &spec.SchemaOrBool{ + Allows: true, + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + "labels": { + SchemaProps: spec.SchemaProps{ + Description: "Labels that should be added to the created ACME HTTP01 solver pods.", + Type: []string{"object"}, + AdditionalProperties: &spec.SchemaOrBool{ + Allows: true, + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + }, + }, + }, + } +} + +func schema_pkg_apis_acme_v1_ACMEChallengeSolverHTTP01IngressPodResources(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "ACMEChallengeSolverHTTP01IngressPodResources defines resource requirements for ACME HTTP01 solver pods. To keep API surface essential, this trims down the 'corev1.ResourceRequirements' type to only include the Requests and Limits fields.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "limits": { + SchemaProps: spec.SchemaProps{ + Description: "Limits describes the maximum amount of compute resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/", + Type: []string{"object"}, + AdditionalProperties: &spec.SchemaOrBool{ + Allows: true, + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Ref: ref("k8s.io/apimachinery/pkg/api/resource.Quantity"), + }, + }, + }, + }, + }, + "requests": { + SchemaProps: spec.SchemaProps{ + Description: "Requests describes the minimum amount of compute resources required. If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, otherwise to the global values configured via controller flags. Requests cannot exceed Limits. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/", + Type: []string{"object"}, + AdditionalProperties: &spec.SchemaOrBool{ + Allows: true, + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Ref: ref("k8s.io/apimachinery/pkg/api/resource.Quantity"), + }, + }, + }, + }, + }, + }, + }, + }, + Dependencies: []string{ + "k8s.io/apimachinery/pkg/api/resource.Quantity"}, + } +} + +func schema_pkg_apis_acme_v1_ACMEChallengeSolverHTTP01IngressPodSecurityContext(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "seLinuxOptions": { + SchemaProps: spec.SchemaProps{ + Description: "The SELinux context to be applied to all containers. If unspecified, the container runtime will allocate a random SELinux context for each container. May also be set in SecurityContext. If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence for that container. Note that this field cannot be set when spec.os.name is windows.", + Ref: ref("k8s.io/api/core/v1.SELinuxOptions"), + }, + }, + "runAsUser": { + SchemaProps: spec.SchemaProps{ + Description: "The UID to run the entrypoint of the container process. Defaults to user specified in image metadata if unspecified. May also be set in SecurityContext. If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence for that container. Note that this field cannot be set when spec.os.name is windows.", + Type: []string{"integer"}, + Format: "int64", + }, + }, + "runAsGroup": { + SchemaProps: spec.SchemaProps{ + Description: "The GID to run the entrypoint of the container process. Uses runtime default if unset. May also be set in SecurityContext. If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence for that container. Note that this field cannot be set when spec.os.name is windows.", + Type: []string{"integer"}, + Format: "int64", + }, + }, + "runAsNonRoot": { + SchemaProps: spec.SchemaProps{ + Description: "Indicates that the container must run as a non-root user. If true, the Kubelet will validate the image at runtime to ensure that it does not run as UID 0 (root) and fail to start the container if it does. If unset or false, no such validation will be performed. May also be set in SecurityContext. If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence.", + Type: []string{"boolean"}, + Format: "", + }, + }, + "supplementalGroups": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "A list of groups applied to the first process run in each container, in addition to the container's primary GID, the fsGroup (if specified), and group memberships defined in the container image for the uid of the container process. If unspecified, no additional groups are added to any container. Note that group memberships defined in the container image for the uid of the container process are still effective, even if they are not included in this list. Note that this field cannot be set when spec.os.name is windows.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: 0, + Type: []string{"integer"}, + Format: "int64", + }, + }, + }, + }, + }, + "fsGroup": { + SchemaProps: spec.SchemaProps{ + Description: "A special supplemental group that applies to all containers in a pod. Some volume types allow the Kubelet to change the ownership of that volume to be owned by the pod:\n\n1. The owning GID will be the FSGroup 2. The setgid bit is set (new files created in the volume will be owned by FSGroup) 3. The permission bits are OR'd with rw-rw----\n\nIf unset, the Kubelet will not modify the ownership and permissions of any volume. Note that this field cannot be set when spec.os.name is windows.", + Type: []string{"integer"}, + Format: "int64", + }, + }, + "sysctls": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "Sysctls hold a list of namespaced sysctls used for the pod. Pods with unsupported sysctls (by the container runtime) might fail to launch. Note that this field cannot be set when spec.os.name is windows.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/api/core/v1.Sysctl"), + }, + }, + }, + }, + }, + "fsGroupChangePolicy": { + SchemaProps: spec.SchemaProps{ + Description: "fsGroupChangePolicy defines behavior of changing ownership and permission of the volume before being exposed inside Pod. This field will only apply to volume types which support fsGroup based ownership(and permissions). It will have no effect on ephemeral volume types such as: secret, configmaps and emptydir. Valid values are \"OnRootMismatch\" and \"Always\". If not specified, \"Always\" is used. Note that this field cannot be set when spec.os.name is windows.\n\nPossible enum values:\n - `\"Always\"` indicates that volume's ownership and permissions should always be changed whenever volume is mounted inside a Pod. This the default behavior.\n - `\"OnRootMismatch\"` indicates that volume's ownership and permissions will be changed only when permission and ownership of root directory does not match with expected permissions on the volume. This can help shorten the time it takes to change ownership and permissions of a volume.", + Type: []string{"string"}, + Format: "", + Enum: []interface{}{"Always", "OnRootMismatch"}, + }, + }, + "seccompProfile": { + SchemaProps: spec.SchemaProps{ + Description: "The seccomp options to use by the containers in this pod. Note that this field cannot be set when spec.os.name is windows.", + Ref: ref("k8s.io/api/core/v1.SeccompProfile"), + }, + }, + }, + }, + }, + Dependencies: []string{ + "k8s.io/api/core/v1.SELinuxOptions", "k8s.io/api/core/v1.SeccompProfile", "k8s.io/api/core/v1.Sysctl"}, + } +} + +func schema_pkg_apis_acme_v1_ACMEChallengeSolverHTTP01IngressPodSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "nodeSelector": { + SchemaProps: spec.SchemaProps{ + Description: "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/", + Type: []string{"object"}, + AdditionalProperties: &spec.SchemaOrBool{ + Allows: true, + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + "affinity": { + SchemaProps: spec.SchemaProps{ + Description: "If specified, the pod's scheduling constraints", + Ref: ref("k8s.io/api/core/v1.Affinity"), + }, + }, + "tolerations": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "If specified, the pod's tolerations.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/api/core/v1.Toleration"), + }, + }, + }, + }, + }, + "priorityClassName": { + SchemaProps: spec.SchemaProps{ + Description: "If specified, the pod's priorityClassName.", + Type: []string{"string"}, + Format: "", + }, + }, + "serviceAccountName": { + SchemaProps: spec.SchemaProps{ + Description: "If specified, the pod's service account", + Type: []string{"string"}, + Format: "", + }, + }, + "imagePullSecrets": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-map-keys": []interface{}{ + "name", + }, + "x-kubernetes-list-type": "map", + "x-kubernetes-patch-merge-key": "name", + "x-kubernetes-patch-strategy": "merge", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "If specified, the pod's imagePullSecrets", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/api/core/v1.LocalObjectReference"), + }, + }, + }, + }, + }, + "securityContext": { + SchemaProps: spec.SchemaProps{ + Description: "If specified, the pod's security context", + Ref: ref("github.com/cert-manager/cert-manager/pkg/apis/acme/v1.ACMEChallengeSolverHTTP01IngressPodSecurityContext"), + }, + }, + "resources": { + SchemaProps: spec.SchemaProps{ + Description: "If specified, the pod's resource requirements. These values override the global resource configuration flags. Note that when only specifying resource limits, ensure they are greater than or equal to the corresponding global resource requests configured via controller flags (--acme-http01-solver-resource-request-cpu, --acme-http01-solver-resource-request-memory). Kubernetes will reject pod creation if limits are lower than requests, causing challenge failures.", + Ref: ref("github.com/cert-manager/cert-manager/pkg/apis/acme/v1.ACMEChallengeSolverHTTP01IngressPodResources"), + }, + }, + }, + }, + }, + Dependencies: []string{ + "github.com/cert-manager/cert-manager/pkg/apis/acme/v1.ACMEChallengeSolverHTTP01IngressPodResources", "github.com/cert-manager/cert-manager/pkg/apis/acme/v1.ACMEChallengeSolverHTTP01IngressPodSecurityContext", "k8s.io/api/core/v1.Affinity", "k8s.io/api/core/v1.LocalObjectReference", "k8s.io/api/core/v1.Toleration"}, + } +} + +func schema_pkg_apis_acme_v1_ACMEChallengeSolverHTTP01IngressPodTemplate(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "metadata": { + SchemaProps: spec.SchemaProps{ + Description: "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.", + Default: map[string]interface{}{}, + Ref: ref("github.com/cert-manager/cert-manager/pkg/apis/acme/v1.ACMEChallengeSolverHTTP01IngressPodObjectMeta"), + }, + }, + "spec": { + SchemaProps: spec.SchemaProps{ + Description: "PodSpec defines overrides for the HTTP01 challenge solver pod. Check ACMEChallengeSolverHTTP01IngressPodSpec to find out currently supported fields. All other fields will be ignored.", + Default: map[string]interface{}{}, + Ref: ref("github.com/cert-manager/cert-manager/pkg/apis/acme/v1.ACMEChallengeSolverHTTP01IngressPodSpec"), + }, + }, + }, + }, + }, + Dependencies: []string{ + "github.com/cert-manager/cert-manager/pkg/apis/acme/v1.ACMEChallengeSolverHTTP01IngressPodObjectMeta", "github.com/cert-manager/cert-manager/pkg/apis/acme/v1.ACMEChallengeSolverHTTP01IngressPodSpec"}, + } +} + +func schema_pkg_apis_acme_v1_ACMEChallengeSolverHTTP01IngressTemplate(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "metadata": { + SchemaProps: spec.SchemaProps{ + Description: "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.", + Default: map[string]interface{}{}, + Ref: ref("github.com/cert-manager/cert-manager/pkg/apis/acme/v1.ACMEChallengeSolverHTTP01IngressObjectMeta"), + }, + }, + }, + }, + }, + Dependencies: []string{ + "github.com/cert-manager/cert-manager/pkg/apis/acme/v1.ACMEChallengeSolverHTTP01IngressObjectMeta"}, + } +} + +func schema_pkg_apis_acme_v1_ACMEExternalAccountBinding(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "ACMEExternalAccountBinding is a reference to a CA external account of the ACME server.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "keyID": { + SchemaProps: spec.SchemaProps{ + Description: "keyID is the ID of the CA key that the External Account is bound to.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "keySecretRef": { + SchemaProps: spec.SchemaProps{ + Description: "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.", + Default: map[string]interface{}{}, + Ref: ref("github.com/cert-manager/cert-manager/pkg/apis/meta/v1.SecretKeySelector"), + }, + }, + "keyAlgorithm": { + SchemaProps: spec.SchemaProps{ + Description: "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.", + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"keyID", "keySecretRef"}, + }, + }, + Dependencies: []string{ + "github.com/cert-manager/cert-manager/pkg/apis/meta/v1.SecretKeySelector"}, + } +} + +func schema_pkg_apis_acme_v1_ACMEIssuer(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "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.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "email": { + SchemaProps: spec.SchemaProps{ + Description: "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.", + Type: []string{"string"}, + Format: "", + }, + }, + "server": { + SchemaProps: spec.SchemaProps{ + Description: "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.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "preferredChain": { + SchemaProps: spec.SchemaProps{ + Description: "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 cross-sign 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 combined set of ACME default and alternative chains that has a root-most certificate with this value as its issuer's commonname.", + Type: []string{"string"}, + Format: "", + }, + }, + "caBundle": { + SchemaProps: spec.SchemaProps{ + Description: "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.", + Type: []string{"string"}, + Format: "byte", + }, + }, + "skipTLSVerify": { + SchemaProps: spec.SchemaProps{ + Description: "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.", + Type: []string{"boolean"}, + Format: "", + }, + }, + "externalAccountBinding": { + SchemaProps: spec.SchemaProps{ + Description: "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.", + Ref: ref("github.com/cert-manager/cert-manager/pkg/apis/acme/v1.ACMEExternalAccountBinding"), + }, + }, + "privateKeySecretRef": { + SchemaProps: spec.SchemaProps{ + Description: "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.", + Default: map[string]interface{}{}, + Ref: ref("github.com/cert-manager/cert-manager/pkg/apis/meta/v1.SecretKeySelector"), + }, + }, + "solvers": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "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/", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("github.com/cert-manager/cert-manager/pkg/apis/acme/v1.ACMEChallengeSolver"), + }, + }, + }, + }, + }, + "disableAccountKeyGeneration": { + SchemaProps: spec.SchemaProps{ + Description: "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.", + Type: []string{"boolean"}, + Format: "", + }, + }, + "enableDurationFeature": { + SchemaProps: spec.SchemaProps{ + Description: "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.", + Type: []string{"boolean"}, + Format: "", + }, + }, + "profile": { + SchemaProps: spec.SchemaProps{ + Description: "Profile allows requesting a certificate profile from the ACME server. Supported profiles are listed by the server's ACME directory URL.", + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"server", "privateKeySecretRef"}, + }, + }, + Dependencies: []string{ + "github.com/cert-manager/cert-manager/pkg/apis/acme/v1.ACMEChallengeSolver", "github.com/cert-manager/cert-manager/pkg/apis/acme/v1.ACMEExternalAccountBinding", "github.com/cert-manager/cert-manager/pkg/apis/meta/v1.SecretKeySelector"}, + } +} + +func schema_pkg_apis_acme_v1_ACMEIssuerDNS01ProviderAcmeDNS(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "ACMEIssuerDNS01ProviderAcmeDNS is a structure containing the configuration for ACME-DNS servers", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "host": { + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "accountSecretRef": { + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("github.com/cert-manager/cert-manager/pkg/apis/meta/v1.SecretKeySelector"), + }, + }, + }, + Required: []string{"host", "accountSecretRef"}, + }, + }, + Dependencies: []string{ + "github.com/cert-manager/cert-manager/pkg/apis/meta/v1.SecretKeySelector"}, + } +} + +func schema_pkg_apis_acme_v1_ACMEIssuerDNS01ProviderAkamai(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "ACMEIssuerDNS01ProviderAkamai is a structure containing the DNS configuration for Akamai DNS—Zone Record Management API", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "serviceConsumerDomain": { + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "clientTokenSecretRef": { + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("github.com/cert-manager/cert-manager/pkg/apis/meta/v1.SecretKeySelector"), + }, + }, + "clientSecretSecretRef": { + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("github.com/cert-manager/cert-manager/pkg/apis/meta/v1.SecretKeySelector"), + }, + }, + "accessTokenSecretRef": { + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("github.com/cert-manager/cert-manager/pkg/apis/meta/v1.SecretKeySelector"), + }, + }, + }, + Required: []string{"serviceConsumerDomain", "clientTokenSecretRef", "clientSecretSecretRef", "accessTokenSecretRef"}, + }, + }, + Dependencies: []string{ + "github.com/cert-manager/cert-manager/pkg/apis/meta/v1.SecretKeySelector"}, + } +} + +func schema_pkg_apis_acme_v1_ACMEIssuerDNS01ProviderAzureDNS(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "ACMEIssuerDNS01ProviderAzureDNS is a structure containing the configuration for Azure DNS", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "clientID": { + SchemaProps: spec.SchemaProps{ + Description: "Auth: Azure Service Principal: The ClientID of the Azure Service Principal used to authenticate with Azure DNS. If set, ClientSecret and TenantID must also be set.", + Type: []string{"string"}, + Format: "", + }, + }, + "clientSecretSecretRef": { + SchemaProps: spec.SchemaProps{ + Description: "Auth: Azure Service Principal: A reference to a Secret containing the password associated with the Service Principal. If set, ClientID and TenantID must also be set.", + Ref: ref("github.com/cert-manager/cert-manager/pkg/apis/meta/v1.SecretKeySelector"), + }, + }, + "subscriptionID": { + SchemaProps: spec.SchemaProps{ + Description: "ID of the Azure subscription", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "tenantID": { + SchemaProps: spec.SchemaProps{ + Description: "Auth: Azure Service Principal: The TenantID of the Azure Service Principal used to authenticate with Azure DNS. If set, ClientID and ClientSecret must also be set.", + Type: []string{"string"}, + Format: "", + }, + }, + "resourceGroupName": { + SchemaProps: spec.SchemaProps{ + Description: "resource group the DNS zone is located in", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "hostedZoneName": { + SchemaProps: spec.SchemaProps{ + Description: "name of the DNS zone that should be used", + Type: []string{"string"}, + Format: "", + }, + }, + "environment": { + SchemaProps: spec.SchemaProps{ + Description: "name of the Azure environment (default AzurePublicCloud)", + Type: []string{"string"}, + Format: "", + }, + }, + "managedIdentity": { + SchemaProps: spec.SchemaProps{ + Description: "Auth: Azure Workload Identity or Azure Managed Service Identity: Settings to enable Azure Workload Identity or Azure Managed Service Identity If set, ClientID, ClientSecret and TenantID must not be set.", + Ref: ref("github.com/cert-manager/cert-manager/pkg/apis/acme/v1.AzureManagedIdentity"), + }, + }, + }, + Required: []string{"subscriptionID", "resourceGroupName"}, + }, + }, + Dependencies: []string{ + "github.com/cert-manager/cert-manager/pkg/apis/acme/v1.AzureManagedIdentity", "github.com/cert-manager/cert-manager/pkg/apis/meta/v1.SecretKeySelector"}, + } +} + +func schema_pkg_apis_acme_v1_ACMEIssuerDNS01ProviderCloudDNS(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "ACMEIssuerDNS01ProviderCloudDNS is a structure containing the DNS configuration for Google Cloud DNS", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "serviceAccountSecretRef": { + SchemaProps: spec.SchemaProps{ + Ref: ref("github.com/cert-manager/cert-manager/pkg/apis/meta/v1.SecretKeySelector"), + }, + }, + "project": { + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "hostedZoneName": { + SchemaProps: spec.SchemaProps{ + Description: "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.", + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"project"}, + }, + }, + Dependencies: []string{ + "github.com/cert-manager/cert-manager/pkg/apis/meta/v1.SecretKeySelector"}, + } +} + +func schema_pkg_apis_acme_v1_ACMEIssuerDNS01ProviderCloudflare(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "ACMEIssuerDNS01ProviderCloudflare is a structure containing the DNS configuration for Cloudflare. One of `apiKeySecretRef` or `apiTokenSecretRef` must be provided.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "email": { + SchemaProps: spec.SchemaProps{ + Description: "Email of the account, only required when using API key based authentication.", + Type: []string{"string"}, + Format: "", + }, + }, + "apiKeySecretRef": { + SchemaProps: spec.SchemaProps{ + Description: "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.", + Ref: ref("github.com/cert-manager/cert-manager/pkg/apis/meta/v1.SecretKeySelector"), + }, + }, + "apiTokenSecretRef": { + SchemaProps: spec.SchemaProps{ + Description: "API token used to authenticate with Cloudflare.", + Ref: ref("github.com/cert-manager/cert-manager/pkg/apis/meta/v1.SecretKeySelector"), + }, + }, + }, + }, + }, + Dependencies: []string{ + "github.com/cert-manager/cert-manager/pkg/apis/meta/v1.SecretKeySelector"}, + } +} + +func schema_pkg_apis_acme_v1_ACMEIssuerDNS01ProviderDigitalOcean(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "ACMEIssuerDNS01ProviderDigitalOcean is a structure containing the DNS configuration for DigitalOcean Domains", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "tokenSecretRef": { + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("github.com/cert-manager/cert-manager/pkg/apis/meta/v1.SecretKeySelector"), + }, + }, + }, + Required: []string{"tokenSecretRef"}, + }, + }, + Dependencies: []string{ + "github.com/cert-manager/cert-manager/pkg/apis/meta/v1.SecretKeySelector"}, + } +} + +func schema_pkg_apis_acme_v1_ACMEIssuerDNS01ProviderRFC2136(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "ACMEIssuerDNS01ProviderRFC2136 is a structure containing the configuration for RFC2136 DNS", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "nameserver": { + SchemaProps: spec.SchemaProps{ + Description: "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])\u00a0; port is optional. This field is required.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "tsigSecretSecretRef": { + SchemaProps: spec.SchemaProps{ + Description: "The name of the secret containing the TSIG value. If ``tsigKeyName`` is defined, this field is required.", + Default: map[string]interface{}{}, + Ref: ref("github.com/cert-manager/cert-manager/pkg/apis/meta/v1.SecretKeySelector"), + }, + }, + "tsigKeyName": { + SchemaProps: spec.SchemaProps{ + Description: "The TSIG Key name configured in the DNS. If ``tsigSecretSecretRef`` is defined, this field is required.", + Type: []string{"string"}, + Format: "", + }, + }, + "tsigAlgorithm": { + SchemaProps: spec.SchemaProps{ + Description: "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``.", + Type: []string{"string"}, + Format: "", + }, + }, + "protocol": { + SchemaProps: spec.SchemaProps{ + Description: "Protocol to use for dynamic DNS update queries. Valid values are (case-sensitive) ``TCP`` and ``UDP``; ``UDP`` (default).", + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"nameserver"}, + }, + }, + Dependencies: []string{ + "github.com/cert-manager/cert-manager/pkg/apis/meta/v1.SecretKeySelector"}, + } +} + +func schema_pkg_apis_acme_v1_ACMEIssuerDNS01ProviderRoute53(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "ACMEIssuerDNS01ProviderRoute53 is a structure containing the Route 53 configuration for AWS", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "auth": { + SchemaProps: spec.SchemaProps{ + Description: "Auth configures how cert-manager authenticates.", + Ref: ref("github.com/cert-manager/cert-manager/pkg/apis/acme/v1.Route53Auth"), + }, + }, + "accessKeyID": { + SchemaProps: spec.SchemaProps{ + Description: "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", + Type: []string{"string"}, + Format: "", + }, + }, + "accessKeyIDSecretRef": { + SchemaProps: spec.SchemaProps{ + Description: "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", + Ref: ref("github.com/cert-manager/cert-manager/pkg/apis/meta/v1.SecretKeySelector"), + }, + }, + "secretAccessKeySecretRef": { + SchemaProps: spec.SchemaProps{ + Description: "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", + Default: map[string]interface{}{}, + Ref: ref("github.com/cert-manager/cert-manager/pkg/apis/meta/v1.SecretKeySelector"), + }, + }, + "role": { + SchemaProps: spec.SchemaProps{ + Description: "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", + Type: []string{"string"}, + Format: "", + }, + }, + "hostedZoneID": { + SchemaProps: spec.SchemaProps{ + Description: "If set, the provider will manage only this zone in Route53 and will not do a lookup using the route53:ListHostedZonesByName api call.", + Type: []string{"string"}, + Format: "", + }, + }, + "region": { + SchemaProps: spec.SchemaProps{ + Description: "Override the AWS region.\n\nRoute53 is a global service and does not have regional endpoints but the region specified here (or via environment variables) is used as a hint to help compute the correct AWS credential scope and partition when it connects to Route53. See: - [Amazon Route 53 endpoints and quotas](https://docs.aws.amazon.com/general/latest/gr/r53.html) - [Global services](https://docs.aws.amazon.com/whitepapers/latest/aws-fault-isolation-boundaries/global-services.html)\n\nIf you omit this region field, cert-manager will use the region from AWS_REGION and AWS_DEFAULT_REGION environment variables, if they are set in the cert-manager controller Pod.\n\nThe `region` field is not needed if you use [IAM Roles for Service Accounts (IRSA)](https://docs.aws.amazon.com/eks/latest/userguide/iam-roles-for-service-accounts.html). Instead an AWS_REGION environment variable is added to the cert-manager controller Pod by: [Amazon EKS Pod Identity Webhook](https://github.com/aws/amazon-eks-pod-identity-webhook). In this case this `region` field value is ignored.\n\nThe `region` field is not needed if you use [EKS Pod Identities](https://docs.aws.amazon.com/eks/latest/userguide/pod-identities.html). Instead an AWS_REGION environment variable is added to the cert-manager controller Pod by: [Amazon EKS Pod Identity Agent](https://github.com/aws/eks-pod-identity-agent), In this case this `region` field value is ignored.", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + Dependencies: []string{ + "github.com/cert-manager/cert-manager/pkg/apis/acme/v1.Route53Auth", "github.com/cert-manager/cert-manager/pkg/apis/meta/v1.SecretKeySelector"}, + } +} + +func schema_pkg_apis_acme_v1_ACMEIssuerDNS01ProviderWebhook(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "ACMEIssuerDNS01ProviderWebhook specifies configuration for a webhook DNS01 provider, including where to POST ChallengePayload resources.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "groupName": { + SchemaProps: spec.SchemaProps{ + Description: "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.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "solverName": { + SchemaProps: spec.SchemaProps{ + Description: "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'.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "config": { + SchemaProps: spec.SchemaProps{ + Description: "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.", + Ref: ref("k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.JSON"), + }, + }, + }, + Required: []string{"groupName", "solverName"}, + }, + }, + Dependencies: []string{ + "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.JSON"}, + } +} + +func schema_pkg_apis_acme_v1_ACMEIssuerStatus(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "uri": { + SchemaProps: spec.SchemaProps{ + Description: "URI is the unique account identifier, which can also be used to retrieve account details from the CA", + Type: []string{"string"}, + Format: "", + }, + }, + "lastRegisteredEmail": { + SchemaProps: spec.SchemaProps{ + 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", + Type: []string{"string"}, + Format: "", + }, + }, + "lastPrivateKeyHash": { + SchemaProps: spec.SchemaProps{ + Description: "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", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + } +} + +func schema_pkg_apis_acme_v1_AzureManagedIdentity(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "AzureManagedIdentity contains the configuration for Azure Workload Identity or Azure Managed Service Identity If the AZURE_FEDERATED_TOKEN_FILE environment variable is set, the Azure Workload Identity will be used. Otherwise, we fall-back to using Azure Managed Service Identity.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "clientID": { + SchemaProps: spec.SchemaProps{ + Description: "client ID of the managed identity, cannot be used at the same time as resourceID", + Type: []string{"string"}, + Format: "", + }, + }, + "resourceID": { + SchemaProps: spec.SchemaProps{ + Description: "resource ID of the managed identity, cannot be used at the same time as clientID Cannot be used for Azure Managed Service Identity", + Type: []string{"string"}, + Format: "", + }, + }, + "tenantID": { + SchemaProps: spec.SchemaProps{ + Description: "tenant ID of the managed identity, cannot be used at the same time as resourceID", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + } +} + +func schema_pkg_apis_acme_v1_CertificateDNSNameSelector(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "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.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "matchLabels": { + SchemaProps: spec.SchemaProps{ + Description: "A label selector that is used to refine the set of certificate's that this challenge solver will apply to.", + Type: []string{"object"}, + AdditionalProperties: &spec.SchemaOrBool{ + Allows: true, + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + "dnsNames": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "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.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + "dnsZones": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "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.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + }, + }, + }, + } +} + +func schema_pkg_apis_acme_v1_Challenge(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "Challenge is a type to represent a Challenge request with an ACME server", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", + }, + }, + "metadata": { + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"), + }, + }, + "spec": { + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("github.com/cert-manager/cert-manager/pkg/apis/acme/v1.ChallengeSpec"), + }, + }, + "status": { + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("github.com/cert-manager/cert-manager/pkg/apis/acme/v1.ChallengeStatus"), + }, + }, + }, + Required: []string{"metadata", "spec"}, + }, + }, + Dependencies: []string{ + "github.com/cert-manager/cert-manager/pkg/apis/acme/v1.ChallengeSpec", "github.com/cert-manager/cert-manager/pkg/apis/acme/v1.ChallengeStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"}, + } +} + +func schema_pkg_apis_acme_v1_ChallengeList(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "ChallengeList is a list of Challenges", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", + }, + }, + "metadata": { + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"), + }, + }, + "items": { + SchemaProps: spec.SchemaProps{ + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("github.com/cert-manager/cert-manager/pkg/apis/acme/v1.Challenge"), + }, + }, + }, + }, + }, + }, + Required: []string{"metadata", "items"}, + }, + }, + Dependencies: []string{ + "github.com/cert-manager/cert-manager/pkg/apis/acme/v1.Challenge", "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"}, + } +} + +func schema_pkg_apis_acme_v1_ChallengeSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "url": { + SchemaProps: spec.SchemaProps{ + Description: "The URL of the ACME Challenge resource for this challenge. This can be used to lookup details about the status of this challenge.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "authorizationURL": { + SchemaProps: spec.SchemaProps{ + Description: "The URL to the ACME Authorization resource that this challenge is a part of.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "dnsName": { + SchemaProps: spec.SchemaProps{ + Description: "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`.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "wildcard": { + SchemaProps: spec.SchemaProps{ + Description: "wildcard will be true if this challenge is for a wildcard identifier, for example '*.example.com'.", + Default: false, + Type: []string{"boolean"}, + Format: "", + }, + }, + "type": { + SchemaProps: spec.SchemaProps{ + Description: "The type of ACME challenge this resource represents. One of \"HTTP-01\" or \"DNS-01\".", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "token": { + SchemaProps: spec.SchemaProps{ + Description: "The ACME challenge token for this challenge. This is the raw value returned from the ACME server.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "key": { + SchemaProps: spec.SchemaProps{ + Description: "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: `.`. For DNS01 challenges, this is the base64 encoded SHA256 sum of the `.` text that must be set as the TXT record content.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "solver": { + SchemaProps: spec.SchemaProps{ + Description: "Contains the domain solving configuration that should be used to solve this challenge resource.", + Default: map[string]interface{}{}, + Ref: ref("github.com/cert-manager/cert-manager/pkg/apis/acme/v1.ACMEChallengeSolver"), + }, + }, + "issuerRef": { + SchemaProps: spec.SchemaProps{ + Description: "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.", + Default: map[string]interface{}{}, + Ref: ref("github.com/cert-manager/cert-manager/pkg/apis/meta/v1.IssuerReference"), + }, + }, + }, + Required: []string{"url", "authorizationURL", "dnsName", "type", "token", "key", "solver", "issuerRef"}, + }, + }, + Dependencies: []string{ + "github.com/cert-manager/cert-manager/pkg/apis/acme/v1.ACMEChallengeSolver", "github.com/cert-manager/cert-manager/pkg/apis/meta/v1.IssuerReference"}, + } +} + +func schema_pkg_apis_acme_v1_ChallengeStatus(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "processing": { + SchemaProps: spec.SchemaProps{ + Description: "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.", + Default: false, + Type: []string{"boolean"}, + Format: "", + }, + }, + "presented": { + SchemaProps: spec.SchemaProps{ + Description: "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).", + Default: false, + Type: []string{"boolean"}, + Format: "", + }, + }, + "reason": { + SchemaProps: spec.SchemaProps{ + Description: "Contains human readable information on why the Challenge is in the current state.", + Type: []string{"string"}, + Format: "", + }, + }, + "state": { + SchemaProps: spec.SchemaProps{ + Description: "Contains the current 'state' of the challenge. If not set, the state of the challenge is unknown.", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + } +} + +func schema_pkg_apis_acme_v1_Order(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "Order is a type to represent an Order with an ACME server", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", + }, + }, + "metadata": { + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"), + }, + }, + "spec": { + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("github.com/cert-manager/cert-manager/pkg/apis/acme/v1.OrderSpec"), + }, + }, + "status": { + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("github.com/cert-manager/cert-manager/pkg/apis/acme/v1.OrderStatus"), + }, + }, + }, + Required: []string{"metadata", "spec"}, + }, + }, + Dependencies: []string{ + "github.com/cert-manager/cert-manager/pkg/apis/acme/v1.OrderSpec", "github.com/cert-manager/cert-manager/pkg/apis/acme/v1.OrderStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"}, + } +} + +func schema_pkg_apis_acme_v1_OrderList(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "OrderList is a list of Orders", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", + }, + }, + "metadata": { + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"), + }, + }, + "items": { + SchemaProps: spec.SchemaProps{ + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("github.com/cert-manager/cert-manager/pkg/apis/acme/v1.Order"), + }, + }, + }, + }, + }, + }, + Required: []string{"metadata", "items"}, + }, + }, + Dependencies: []string{ + "github.com/cert-manager/cert-manager/pkg/apis/acme/v1.Order", "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"}, + } +} + +func schema_pkg_apis_acme_v1_OrderSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "request": { + SchemaProps: spec.SchemaProps{ + Description: "Certificate signing request bytes in DER encoding. This will be used when finalizing the order. This field must be set on the order.", + Type: []string{"string"}, + Format: "byte", + }, + }, + "issuerRef": { + SchemaProps: spec.SchemaProps{ + Description: "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.", + Default: map[string]interface{}{}, + Ref: ref("github.com/cert-manager/cert-manager/pkg/apis/meta/v1.IssuerReference"), + }, + }, + "commonName": { + SchemaProps: spec.SchemaProps{ + Description: "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.", + Type: []string{"string"}, + Format: "", + }, + }, + "dnsNames": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "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.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + "ipAddresses": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "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.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + "duration": { + SchemaProps: spec.SchemaProps{ + Description: "Duration is the duration for the not after date for the requested certificate. this is set on order creation as pe the ACME spec.", + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.Duration"), + }, + }, + "profile": { + SchemaProps: spec.SchemaProps{ + Description: "Profile allows requesting a certificate profile from the ACME server. Supported profiles are listed by the server's ACME directory URL.", + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"request", "issuerRef"}, + }, + }, + Dependencies: []string{ + "github.com/cert-manager/cert-manager/pkg/apis/meta/v1.IssuerReference", "k8s.io/apimachinery/pkg/apis/meta/v1.Duration"}, + } +} + +func schema_pkg_apis_acme_v1_OrderStatus(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "url": { + SchemaProps: spec.SchemaProps{ + Description: "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.", + Type: []string{"string"}, + Format: "", + }, + }, + "finalizeURL": { + SchemaProps: spec.SchemaProps{ + Description: "FinalizeURL of the Order. This is used to obtain certificates for this order once it has been completed.", + Type: []string{"string"}, + Format: "", + }, + }, + "authorizations": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "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.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("github.com/cert-manager/cert-manager/pkg/apis/acme/v1.ACMEAuthorization"), + }, + }, + }, + }, + }, + "certificate": { + SchemaProps: spec.SchemaProps{ + Description: "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.", + Type: []string{"string"}, + Format: "byte", + }, + }, + "state": { + SchemaProps: spec.SchemaProps{ + Description: "State contains the current state of this Order resource. States 'success' and 'expired' are 'final'", + Type: []string{"string"}, + Format: "", + }, + }, + "reason": { + SchemaProps: spec.SchemaProps{ + Description: "Reason optionally provides more information about a why the order is in the current state.", + Type: []string{"string"}, + Format: "", + }, + }, + "failureTime": { + SchemaProps: spec.SchemaProps{ + Description: "FailureTime stores the time that this order failed. This is used to influence garbage collection and back-off.", + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.Time"), + }, + }, + }, + }, + }, + Dependencies: []string{ + "github.com/cert-manager/cert-manager/pkg/apis/acme/v1.ACMEAuthorization", "k8s.io/apimachinery/pkg/apis/meta/v1.Time"}, + } +} + +func schema_pkg_apis_acme_v1_Route53Auth(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "Route53Auth is configuration used to authenticate with a Route53.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kubernetes": { + SchemaProps: spec.SchemaProps{ + Description: "Kubernetes authenticates with Route53 using AssumeRoleWithWebIdentity by passing a bound ServiceAccount token.", + Ref: ref("github.com/cert-manager/cert-manager/pkg/apis/acme/v1.Route53KubernetesAuth"), + }, + }, + }, + Required: []string{"kubernetes"}, + }, + }, + Dependencies: []string{ + "github.com/cert-manager/cert-manager/pkg/apis/acme/v1.Route53KubernetesAuth"}, + } +} + +func schema_pkg_apis_acme_v1_Route53KubernetesAuth(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "Route53KubernetesAuth is a configuration to authenticate against Route53 using a bound Kubernetes ServiceAccount token.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "serviceAccountRef": { + SchemaProps: spec.SchemaProps{ + Description: "A reference to a service account that will be used to request a bound token (also known as \"projected token\"). To use this field, you must configure an RBAC rule to let cert-manager request a token.", + Ref: ref("github.com/cert-manager/cert-manager/pkg/apis/acme/v1.ServiceAccountRef"), + }, + }, + }, + Required: []string{"serviceAccountRef"}, + }, + }, + Dependencies: []string{ + "github.com/cert-manager/cert-manager/pkg/apis/acme/v1.ServiceAccountRef"}, + } +} + +func schema_pkg_apis_acme_v1_ServiceAccountRef(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "ServiceAccountRef is a service account used by cert-manager to request a token. The expiration of the token is also set by cert-manager to 10 minutes.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "name": { + SchemaProps: spec.SchemaProps{ + Description: "Name of the ServiceAccount used to request a token.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "audiences": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "TokenAudiences is an optional list of audiences to include in the token passed to AWS. The default token consisting of the issuer's namespace and name is always included. If unset the audience defaults to `sts.amazonaws.com`.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + }, + Required: []string{"name"}, + }, + }, + } +} + +func schema_pkg_apis_certmanager_v1_CAIssuer(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "secretName": { + SchemaProps: spec.SchemaProps{ + Description: "SecretName is the name of the secret used to sign Certificates issued by this Issuer.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "crlDistributionPoints": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "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.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + "ocspServers": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "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\".", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + "issuingCertificateURLs": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "IssuingCertificateURLs is a list of URLs which this issuer should embed into certificates it creates. See https://www.rfc-editor.org/rfc/rfc5280#section-4.2.2.1 for more details. As an example, such a URL might be \"http://ca.domain.com/ca.crt\".", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + }, + Required: []string{"secretName"}, + }, + }, + } +} + +func schema_pkg_apis_certmanager_v1_Certificate(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "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`.\n\nThe stored certificate will be renewed before it expires (as configured by `spec.renewBefore`).", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", + }, + }, + "metadata": { + SchemaProps: spec.SchemaProps{ + Description: "Standard object's metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata", + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"), + }, + }, + "spec": { + SchemaProps: spec.SchemaProps{ + Description: "Specification of the desired state of the Certificate resource. https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status", + Default: map[string]interface{}{}, + Ref: ref("github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1.CertificateSpec"), + }, + }, + "status": { + SchemaProps: spec.SchemaProps{ + Description: "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", + Default: map[string]interface{}{}, + Ref: ref("github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1.CertificateStatus"), + }, + }, + }, + }, + }, + Dependencies: []string{ + "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1.CertificateSpec", "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1.CertificateStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"}, + } +} + +func schema_pkg_apis_certmanager_v1_CertificateAdditionalOutputFormat(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "CertificateAdditionalOutputFormat defines an additional output format of a Certificate resource. These contain supplementary data formats of the signed certificate chain and paired private key.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "type": { + SchemaProps: spec.SchemaProps{ + Description: "Type is the name of the format type that should be written to the Certificate's target Secret.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"type"}, + }, + }, + } +} + +func schema_pkg_apis_certmanager_v1_CertificateCondition(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "CertificateCondition contains condition information for a Certificate.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "type": { + SchemaProps: spec.SchemaProps{ + Description: "Type of the condition, known values are (`Ready`, `Issuing`).", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "status": { + SchemaProps: spec.SchemaProps{ + Description: "Status of the condition, one of (`True`, `False`, `Unknown`).", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "lastTransitionTime": { + SchemaProps: spec.SchemaProps{ + Description: "LastTransitionTime is the timestamp corresponding to the last status change of this condition.", + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.Time"), + }, + }, + "reason": { + SchemaProps: spec.SchemaProps{ + Description: "Reason is a brief machine readable explanation for the condition's last transition.", + Type: []string{"string"}, + Format: "", + }, + }, + "message": { + SchemaProps: spec.SchemaProps{ + Description: "Message is a human readable description of the details of the last transition, complementing reason.", + Type: []string{"string"}, + Format: "", + }, + }, + "observedGeneration": { + SchemaProps: spec.SchemaProps{ + Description: "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.", + Type: []string{"integer"}, + Format: "int64", + }, + }, + }, + Required: []string{"type", "status"}, + }, + }, + Dependencies: []string{ + "k8s.io/apimachinery/pkg/apis/meta/v1.Time"}, + } +} + +func schema_pkg_apis_certmanager_v1_CertificateKeystores(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "CertificateKeystores configures additional keystore output formats to be created in the Certificate's output Secret.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "jks": { + SchemaProps: spec.SchemaProps{ + Description: "JKS configures options for storing a JKS keystore in the `spec.secretName` Secret resource.", + Ref: ref("github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1.JKSKeystore"), + }, + }, + "pkcs12": { + SchemaProps: spec.SchemaProps{ + Description: "PKCS12 configures options for storing a PKCS12 keystore in the `spec.secretName` Secret resource.", + Ref: ref("github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1.PKCS12Keystore"), + }, + }, + }, + }, + }, + Dependencies: []string{ + "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1.JKSKeystore", "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1.PKCS12Keystore"}, + } +} + +func schema_pkg_apis_certmanager_v1_CertificateList(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "CertificateList is a list of Certificates.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", + }, + }, + "metadata": { + SchemaProps: spec.SchemaProps{ + Description: "Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"), + }, + }, + "items": { + SchemaProps: spec.SchemaProps{ + Description: "List of Certificates", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1.Certificate"), + }, + }, + }, + }, + }, + }, + Required: []string{"items"}, + }, + }, + Dependencies: []string{ + "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1.Certificate", "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"}, + } +} + +func schema_pkg_apis_certmanager_v1_CertificatePrivateKey(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "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.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "rotationPolicy": { + SchemaProps: spec.SchemaProps{ + Description: "RotationPolicy controls how private keys should be regenerated when a re-issuance is being processed.\n\nIf set to `Never`, a private key will only be generated if one does not already exist in the target `spec.secretName`. If one does exist 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 `Always`. The default was changed from `Never` to `Always` in cert-manager >=v1.18.0. The new default can be disabled by setting the `--feature-gates=DefaultPrivateKeyRotationPolicyAlways=false` option on the controller component.", + Type: []string{"string"}, + Format: "", + }, + }, + "encoding": { + SchemaProps: spec.SchemaProps{ + Description: "The private key cryptography standards (PKCS) encoding for this certificate's private key to be encoded in.\n\nIf provided, allowed values are `PKCS1` and `PKCS8` standing for PKCS#1 and PKCS#8, respectively. Defaults to `PKCS1` if not specified.", + Type: []string{"string"}, + Format: "", + }, + }, + "algorithm": { + SchemaProps: spec.SchemaProps{ + Description: "Algorithm is the private key algorithm of the corresponding private key for this certificate.\n\nIf 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.", + Type: []string{"string"}, + Format: "", + }, + }, + "size": { + SchemaProps: spec.SchemaProps{ + Description: "Size is the key bit size of the corresponding private key for this certificate.\n\nIf `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.", + Type: []string{"integer"}, + Format: "int32", + }, + }, + }, + }, + }, + } +} + +func schema_pkg_apis_certmanager_v1_CertificateRequest(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "A CertificateRequest is used to request a signed certificate from one of the configured issuers.\n\nAll 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.\n\nA CertificateRequest is a one-shot resource, meaning it represents a single point in time request for a certificate and cannot be re-used.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", + }, + }, + "metadata": { + SchemaProps: spec.SchemaProps{ + Description: "Standard object's metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata", + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"), + }, + }, + "spec": { + SchemaProps: spec.SchemaProps{ + Description: "Specification of the desired state of the CertificateRequest resource. https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status", + Default: map[string]interface{}{}, + Ref: ref("github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1.CertificateRequestSpec"), + }, + }, + "status": { + SchemaProps: spec.SchemaProps{ + Description: "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", + Default: map[string]interface{}{}, + Ref: ref("github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1.CertificateRequestStatus"), + }, + }, + }, + }, + }, + Dependencies: []string{ + "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1.CertificateRequestSpec", "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1.CertificateRequestStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"}, + } +} + +func schema_pkg_apis_certmanager_v1_CertificateRequestCondition(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "CertificateRequestCondition contains condition information for a CertificateRequest.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "type": { + SchemaProps: spec.SchemaProps{ + Description: "Type of the condition, known values are (`Ready`, `InvalidRequest`, `Approved`, `Denied`).", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "status": { + SchemaProps: spec.SchemaProps{ + Description: "Status of the condition, one of (`True`, `False`, `Unknown`).", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "lastTransitionTime": { + SchemaProps: spec.SchemaProps{ + Description: "LastTransitionTime is the timestamp corresponding to the last status change of this condition.", + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.Time"), + }, + }, + "reason": { + SchemaProps: spec.SchemaProps{ + Description: "Reason is a brief machine readable explanation for the condition's last transition.", + Type: []string{"string"}, + Format: "", + }, + }, + "message": { + SchemaProps: spec.SchemaProps{ + Description: "Message is a human readable description of the details of the last transition, complementing reason.", + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"type", "status"}, + }, + }, + Dependencies: []string{ + "k8s.io/apimachinery/pkg/apis/meta/v1.Time"}, + } +} + +func schema_pkg_apis_certmanager_v1_CertificateRequestList(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "CertificateRequestList is a list of CertificateRequests.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", + }, + }, + "metadata": { + SchemaProps: spec.SchemaProps{ + Description: "Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"), + }, + }, + "items": { + SchemaProps: spec.SchemaProps{ + Description: "List of CertificateRequests", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1.CertificateRequest"), + }, + }, + }, + }, + }, + }, + Required: []string{"items"}, + }, + }, + Dependencies: []string{ + "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1.CertificateRequest", "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"}, + } +} + +func schema_pkg_apis_certmanager_v1_CertificateRequestSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "CertificateRequestSpec defines the desired state of CertificateRequest\n\nNOTE: 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.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "duration": { + SchemaProps: spec.SchemaProps{ + Description: "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.", + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.Duration"), + }, + }, + "issuerRef": { + SchemaProps: spec.SchemaProps{ + Description: "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.\n\nThe `name` field of the reference must always be specified.", + Default: map[string]interface{}{}, + Ref: ref("github.com/cert-manager/cert-manager/pkg/apis/meta/v1.IssuerReference"), + }, + }, + "request": { + SchemaProps: spec.SchemaProps{ + Description: "The PEM-encoded X.509 certificate signing request to be submitted to the issuer for signing.\n\nIf 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.", + Type: []string{"string"}, + Format: "byte", + }, + }, + "isCA": { + SchemaProps: spec.SchemaProps{ + Description: "Requested basic constraints isCA value. Note that the issuer may choose to ignore the requested isCA value, just like any other requested attribute.\n\nNOTE: If the CSR in the `Request` field has a BasicConstraints extension, it must have the same isCA value as specified here.\n\nIf true, this will automatically add the `cert sign` usage to the list of requested `usages`.", + Type: []string{"boolean"}, + Format: "", + }, + }, + "usages": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "Requested key usages and extended key usages.\n\nNOTE: 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.\n\nIf unset, defaults to `digital signature` and `key encipherment`.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + "username": { + SchemaProps: spec.SchemaProps{ + Description: "Username contains the name of the user that created the CertificateRequest. Populated by the cert-manager webhook on creation and immutable.", + Type: []string{"string"}, + Format: "", + }, + }, + "uid": { + SchemaProps: spec.SchemaProps{ + Description: "UID contains the uid of the user that created the CertificateRequest. Populated by the cert-manager webhook on creation and immutable.", + Type: []string{"string"}, + Format: "", + }, + }, + "groups": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "Groups contains group membership of the user that created the CertificateRequest. Populated by the cert-manager webhook on creation and immutable.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + "extra": { + SchemaProps: spec.SchemaProps{ + Description: "Extra contains extra attributes of the user that created the CertificateRequest. Populated by the cert-manager webhook on creation and immutable.", + Type: []string{"object"}, + AdditionalProperties: &spec.SchemaOrBool{ + Allows: true, + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + }, + }, + }, + }, + Required: []string{"issuerRef", "request"}, + }, + }, + Dependencies: []string{ + "github.com/cert-manager/cert-manager/pkg/apis/meta/v1.IssuerReference", "k8s.io/apimachinery/pkg/apis/meta/v1.Duration"}, + } +} + +func schema_pkg_apis_certmanager_v1_CertificateRequestStatus(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "CertificateRequestStatus defines the observed state of CertificateRequest and resulting signed certificate.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "conditions": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-map-keys": []interface{}{ + "type", + }, + "x-kubernetes-list-type": "map", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "List of status conditions to indicate the status of a CertificateRequest. Known condition types are `Ready`, `InvalidRequest`, `Approved` and `Denied`.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1.CertificateRequestCondition"), + }, + }, + }, + }, + }, + "certificate": { + SchemaProps: spec.SchemaProps{ + Description: "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.", + Type: []string{"string"}, + Format: "byte", + }, + }, + "ca": { + SchemaProps: spec.SchemaProps{ + Description: "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.", + Type: []string{"string"}, + Format: "byte", + }, + }, + "failureTime": { + SchemaProps: spec.SchemaProps{ + Description: "FailureTime stores the time that this CertificateRequest failed. This is used to influence garbage collection and back-off.", + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.Time"), + }, + }, + }, + }, + }, + Dependencies: []string{ + "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1.CertificateRequestCondition", "k8s.io/apimachinery/pkg/apis/meta/v1.Time"}, + } +} + +func schema_pkg_apis_certmanager_v1_CertificateSecretTemplate(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "CertificateSecretTemplate defines the default labels and annotations to be copied to the Kubernetes Secret resource named in `CertificateSpec.secretName`.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "annotations": { + SchemaProps: spec.SchemaProps{ + Description: "Annotations is a key value map to be copied to the target Kubernetes Secret.", + Type: []string{"object"}, + AdditionalProperties: &spec.SchemaOrBool{ + Allows: true, + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + "labels": { + SchemaProps: spec.SchemaProps{ + Description: "Labels is a key value map to be copied to the target Kubernetes Secret.", + Type: []string{"object"}, + AdditionalProperties: &spec.SchemaOrBool{ + Allows: true, + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + }, + }, + }, + } +} + +func schema_pkg_apis_certmanager_v1_CertificateSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "CertificateSpec defines the desired state of Certificate.\n\nNOTE: 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.\n\nA valid Certificate requires at least one of a CommonName, LiteralSubject, DNSName, or URI to be valid.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "subject": { + SchemaProps: spec.SchemaProps{ + Description: "Requested set of X509 certificate subject attributes. More info: https://datatracker.ietf.org/doc/html/rfc5280#section-4.1.2.6\n\nThe common name attribute is specified separately in the `commonName` field. Cannot be set if the `literalSubject` field is set.", + Ref: ref("github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1.X509Subject"), + }, + }, + "literalSubject": { + SchemaProps: spec.SchemaProps{ + Description: "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\n\nCannot be set if the `subject` or `commonName` field is set.", + Type: []string{"string"}, + Format: "", + }, + }, + "commonName": { + SchemaProps: spec.SchemaProps{ + Description: "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).\n\nShould have a length of 64 characters or fewer to avoid generating invalid CSRs. Cannot be set if the `literalSubject` field is set.", + Type: []string{"string"}, + Format: "", + }, + }, + "duration": { + SchemaProps: spec.SchemaProps{ + Description: "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.\n\nIf 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.", + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.Duration"), + }, + }, + "renewBefore": { + SchemaProps: spec.SchemaProps{ + Description: "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).\n\nNOTE: 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.\n\nIf unset, this defaults to 1/3 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. Cannot be set if the `renewBeforePercentage` field is set.", + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.Duration"), + }, + }, + "renewBeforePercentage": { + SchemaProps: spec.SchemaProps{ + Description: "`renewBeforePercentage` is like `renewBefore`, except it is a relative percentage rather than an absolute duration. For example, if a certificate is valid for 60 minutes, and `renewBeforePercentage=25`, cert-manager will begin to attempt to renew the certificate 45 minutes after it was issued (i.e. when there are 15 minutes (25%) remaining until the certificate is no longer valid).\n\nNOTE: 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.\n\nValue must be an integer in the range (0,100). The minimum effective `renewBefore` derived from the `renewBeforePercentage` and `duration` fields is 5 minutes. Cannot be set if the `renewBefore` field is set.", + Type: []string{"integer"}, + Format: "int32", + }, + }, + "dnsNames": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "Requested DNS subject alternative names.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + "ipAddresses": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "Requested IP address subject alternative names.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + "uris": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "Requested URI subject alternative names.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + "otherNames": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "`otherNames` is an escape hatch for SAN that allows any type. We currently restrict the support to string like otherNames, cf RFC 5280 p 37 Any UTF8 String valued otherName can be passed with by setting the keys oid: x.x.x.x and UTF8Value: somevalue for `otherName`. Most commonly this would be UPN set with oid: 1.3.6.1.4.1.311.20.2.3 You should ensure that any OID passed is valid for the UTF8String type as we do not explicitly validate this.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1.OtherName"), + }, + }, + }, + }, + }, + "emailAddresses": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "Requested email subject alternative names.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + "secretName": { + SchemaProps: spec.SchemaProps{ + Description: "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.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "secretTemplate": { + SchemaProps: spec.SchemaProps{ + Description: "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.", + Ref: ref("github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1.CertificateSecretTemplate"), + }, + }, + "keystores": { + SchemaProps: spec.SchemaProps{ + Description: "Additional keystore output formats to be stored in the Certificate's Secret.", + Ref: ref("github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1.CertificateKeystores"), + }, + }, + "issuerRef": { + SchemaProps: spec.SchemaProps{ + Description: "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.\n\nThe `name` field of the reference must always be specified.", + Default: map[string]interface{}{}, + Ref: ref("github.com/cert-manager/cert-manager/pkg/apis/meta/v1.IssuerReference"), + }, + }, + "isCA": { + SchemaProps: spec.SchemaProps{ + Description: "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.\n\nIf true, this will automatically add the `cert sign` usage to the list of requested `usages`.", + Type: []string{"boolean"}, + Format: "", + }, + }, + "usages": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "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.\n\nIf unset, defaults to `digital signature` and `key encipherment`.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + "privateKey": { + SchemaProps: spec.SchemaProps{ + Description: "Private key options. These include the key algorithm and size, the used encoding and the rotation policy.", + Ref: ref("github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1.CertificatePrivateKey"), + }, + }, + "signatureAlgorithm": { + SchemaProps: spec.SchemaProps{ + Description: "Signature algorithm to use. Allowed values for RSA keys: SHA256WithRSA, SHA384WithRSA, SHA512WithRSA. Allowed values for ECDSA keys: ECDSAWithSHA256, ECDSAWithSHA384, ECDSAWithSHA512. Allowed values for Ed25519 keys: PureEd25519.", + Type: []string{"string"}, + Format: "", + }, + }, + "encodeUsagesInRequest": { + SchemaProps: spec.SchemaProps{ + Description: "Whether the KeyUsage and ExtKeyUsage extensions should be set in the encoded CSR.\n\nThis option defaults to true, and should only be disabled if the target issuer does not support CSRs with these X509 KeyUsage/ ExtKeyUsage extensions.", + Type: []string{"boolean"}, + Format: "", + }, + }, + "revisionHistoryLimit": { + SchemaProps: spec.SchemaProps{ + Description: "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.\n\nIf set, revisionHistoryLimit must be a value of `1` or greater. Default value is `1`.", + Type: []string{"integer"}, + Format: "int32", + }, + }, + "additionalOutputFormats": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "Defines extra output formats of the private key and signed certificate chain to be written to this Certificate's target Secret.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1.CertificateAdditionalOutputFormat"), + }, + }, + }, + }, + }, + "nameConstraints": { + SchemaProps: spec.SchemaProps{ + Description: "x.509 certificate NameConstraint extension which MUST NOT be used in a non-CA certificate. More Info: https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.10\n\nThis is an Alpha Feature and is only enabled with the `--feature-gates=NameConstraints=true` option set on both the controller and webhook components.", + Ref: ref("github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1.NameConstraints"), + }, + }, + }, + Required: []string{"secretName", "issuerRef"}, + }, + }, + Dependencies: []string{ + "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1.CertificateAdditionalOutputFormat", "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1.CertificateKeystores", "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1.CertificatePrivateKey", "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1.CertificateSecretTemplate", "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1.NameConstraints", "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1.OtherName", "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1.X509Subject", "github.com/cert-manager/cert-manager/pkg/apis/meta/v1.IssuerReference", "k8s.io/apimachinery/pkg/apis/meta/v1.Duration"}, + } +} + +func schema_pkg_apis_certmanager_v1_CertificateStatus(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "CertificateStatus defines the observed state of Certificate", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "conditions": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-map-keys": []interface{}{ + "type", + }, + "x-kubernetes-list-type": "map", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "List of status conditions to indicate the status of certificates. Known condition types are `Ready` and `Issuing`.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1.CertificateCondition"), + }, + }, + }, + }, + }, + "lastFailureTime": { + SchemaProps: spec.SchemaProps{ + Description: "LastFailureTime is set only if the latest 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.", + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.Time"), + }, + }, + "notBefore": { + SchemaProps: spec.SchemaProps{ + Description: "The time after which the certificate stored in the secret named by this resource in `spec.secretName` is valid.", + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.Time"), + }, + }, + "notAfter": { + SchemaProps: spec.SchemaProps{ + Description: "The expiration time of the certificate stored in the secret named by this resource in `spec.secretName`.", + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.Time"), + }, + }, + "renewalTime": { + SchemaProps: spec.SchemaProps{ + Description: "RenewalTime is the time at which the certificate will be next renewed. If not set, no upcoming renewal is scheduled.", + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.Time"), + }, + }, + "revision": { + SchemaProps: spec.SchemaProps{ + Description: "The current 'revision' of the certificate as issued.\n\nWhen 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.\n\nUpon issuance, this field will be set to the value of the annotation on the CertificateRequest resource used to issue the certificate.\n\nPersisting 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.", + Type: []string{"integer"}, + Format: "int32", + }, + }, + "nextPrivateKeySecretName": { + SchemaProps: spec.SchemaProps{ + Description: "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.", + Type: []string{"string"}, + Format: "", + }, + }, + "failedIssuanceAttempts": { + SchemaProps: spec.SchemaProps{ + Description: "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).", + Type: []string{"integer"}, + Format: "int32", + }, + }, + }, + }, + }, + Dependencies: []string{ + "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1.CertificateCondition", "k8s.io/apimachinery/pkg/apis/meta/v1.Time"}, + } +} + +func schema_pkg_apis_certmanager_v1_ClusterIssuer(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "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.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", + }, + }, + "metadata": { + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"), + }, + }, + "spec": { + SchemaProps: spec.SchemaProps{ + Description: "Desired state of the ClusterIssuer resource.", + Default: map[string]interface{}{}, + Ref: ref("github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1.IssuerSpec"), + }, + }, + "status": { + SchemaProps: spec.SchemaProps{ + Description: "Status of the ClusterIssuer. This is set and managed automatically.", + Default: map[string]interface{}{}, + Ref: ref("github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1.IssuerStatus"), + }, + }, + }, + Required: []string{"spec"}, + }, + }, + Dependencies: []string{ + "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1.IssuerSpec", "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1.IssuerStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"}, + } +} + +func schema_pkg_apis_certmanager_v1_ClusterIssuerList(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "ClusterIssuerList is a list of Issuers", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", + }, + }, + "metadata": { + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"), + }, + }, + "items": { + SchemaProps: spec.SchemaProps{ + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1.ClusterIssuer"), + }, + }, + }, + }, + }, + }, + Required: []string{"metadata", "items"}, + }, + }, + Dependencies: []string{ + "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1.ClusterIssuer", "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"}, + } +} + +func schema_pkg_apis_certmanager_v1_Issuer(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "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.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", + }, + }, + "metadata": { + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"), + }, + }, + "spec": { + SchemaProps: spec.SchemaProps{ + Description: "Desired state of the Issuer resource.", + Default: map[string]interface{}{}, + Ref: ref("github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1.IssuerSpec"), + }, + }, + "status": { + SchemaProps: spec.SchemaProps{ + Description: "Status of the Issuer. This is set and managed automatically.", + Default: map[string]interface{}{}, + Ref: ref("github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1.IssuerStatus"), + }, + }, + }, + Required: []string{"spec"}, + }, + }, + Dependencies: []string{ + "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1.IssuerSpec", "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1.IssuerStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"}, + } +} + +func schema_pkg_apis_certmanager_v1_IssuerCondition(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "IssuerCondition contains condition information for an Issuer.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "type": { + SchemaProps: spec.SchemaProps{ + Description: "Type of the condition, known values are (`Ready`).", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "status": { + SchemaProps: spec.SchemaProps{ + Description: "Status of the condition, one of (`True`, `False`, `Unknown`).", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "lastTransitionTime": { + SchemaProps: spec.SchemaProps{ + Description: "LastTransitionTime is the timestamp corresponding to the last status change of this condition.", + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.Time"), + }, + }, + "reason": { + SchemaProps: spec.SchemaProps{ + Description: "Reason is a brief machine readable explanation for the condition's last transition.", + Type: []string{"string"}, + Format: "", + }, + }, + "message": { + SchemaProps: spec.SchemaProps{ + Description: "Message is a human readable description of the details of the last transition, complementing reason.", + Type: []string{"string"}, + Format: "", + }, + }, + "observedGeneration": { + SchemaProps: spec.SchemaProps{ + Description: "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.", + Type: []string{"integer"}, + Format: "int64", + }, + }, + }, + Required: []string{"type", "status"}, + }, + }, + Dependencies: []string{ + "k8s.io/apimachinery/pkg/apis/meta/v1.Time"}, + } +} + +func schema_pkg_apis_certmanager_v1_IssuerConfig(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "The configuration for the issuer. Only one of these can be set.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "acme": { + SchemaProps: spec.SchemaProps{ + Description: "ACME configures this issuer to communicate with a RFC8555 (ACME) server to obtain signed x509 certificates.", + Ref: ref("github.com/cert-manager/cert-manager/pkg/apis/acme/v1.ACMEIssuer"), + }, + }, + "ca": { + SchemaProps: spec.SchemaProps{ + Description: "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.", + Ref: ref("github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1.CAIssuer"), + }, + }, + "vault": { + SchemaProps: spec.SchemaProps{ + Description: "Vault configures this issuer to sign certificates using a HashiCorp Vault PKI backend.", + Ref: ref("github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1.VaultIssuer"), + }, + }, + "selfSigned": { + SchemaProps: spec.SchemaProps{ + Description: "SelfSigned configures this issuer to 'self sign' certificates using the private key used to create the CertificateRequest object.", + Ref: ref("github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1.SelfSignedIssuer"), + }, + }, + "venafi": { + SchemaProps: spec.SchemaProps{ + Description: "Venafi configures this issuer to sign certificates using a Venafi TPP or Venafi Cloud policy zone.", + Ref: ref("github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1.VenafiIssuer"), + }, + }, + }, + }, + }, + Dependencies: []string{ + "github.com/cert-manager/cert-manager/pkg/apis/acme/v1.ACMEIssuer", "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1.CAIssuer", "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1.SelfSignedIssuer", "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1.VaultIssuer", "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1.VenafiIssuer"}, + } +} + +func schema_pkg_apis_certmanager_v1_IssuerList(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "IssuerList is a list of Issuers", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", + }, + }, + "metadata": { + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"), + }, + }, + "items": { + SchemaProps: spec.SchemaProps{ + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1.Issuer"), + }, + }, + }, + }, + }, + }, + Required: []string{"metadata", "items"}, + }, + }, + Dependencies: []string{ + "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1.Issuer", "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"}, + } +} + +func schema_pkg_apis_certmanager_v1_IssuerSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "IssuerSpec is the specification of an Issuer. This includes any configuration required for the issuer.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "acme": { + SchemaProps: spec.SchemaProps{ + Description: "ACME configures this issuer to communicate with a RFC8555 (ACME) server to obtain signed x509 certificates.", + Ref: ref("github.com/cert-manager/cert-manager/pkg/apis/acme/v1.ACMEIssuer"), + }, + }, + "ca": { + SchemaProps: spec.SchemaProps{ + Description: "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.", + Ref: ref("github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1.CAIssuer"), + }, + }, + "vault": { + SchemaProps: spec.SchemaProps{ + Description: "Vault configures this issuer to sign certificates using a HashiCorp Vault PKI backend.", + Ref: ref("github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1.VaultIssuer"), + }, + }, + "selfSigned": { + SchemaProps: spec.SchemaProps{ + Description: "SelfSigned configures this issuer to 'self sign' certificates using the private key used to create the CertificateRequest object.", + Ref: ref("github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1.SelfSignedIssuer"), + }, + }, + "venafi": { + SchemaProps: spec.SchemaProps{ + Description: "Venafi configures this issuer to sign certificates using a Venafi TPP or Venafi Cloud policy zone.", + Ref: ref("github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1.VenafiIssuer"), + }, + }, + }, + }, + }, + Dependencies: []string{ + "github.com/cert-manager/cert-manager/pkg/apis/acme/v1.ACMEIssuer", "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1.CAIssuer", "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1.SelfSignedIssuer", "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1.VaultIssuer", "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1.VenafiIssuer"}, + } +} + +func schema_pkg_apis_certmanager_v1_IssuerStatus(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "IssuerStatus contains status information about an Issuer", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "conditions": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-map-keys": []interface{}{ + "type", + }, + "x-kubernetes-list-type": "map", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "List of status conditions to indicate the status of a CertificateRequest. Known condition types are `Ready`.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1.IssuerCondition"), + }, + }, + }, + }, + }, + "acme": { + SchemaProps: spec.SchemaProps{ + Description: "ACME specific status options. This field should only be set if the Issuer is configured to use an ACME server to issue certificates.", + Ref: ref("github.com/cert-manager/cert-manager/pkg/apis/acme/v1.ACMEIssuerStatus"), + }, + }, + }, + }, + }, + Dependencies: []string{ + "github.com/cert-manager/cert-manager/pkg/apis/acme/v1.ACMEIssuerStatus", "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1.IssuerCondition"}, + } +} + +func schema_pkg_apis_certmanager_v1_JKSKeystore(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "JKS configures options for storing a JKS keystore in the target secret. Either PasswordSecretRef or Password must be provided.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "create": { + SchemaProps: spec.SchemaProps{ + Description: "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` or `password`. 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", + Default: false, + Type: []string{"boolean"}, + Format: "", + }, + }, + "alias": { + SchemaProps: spec.SchemaProps{ + Description: "Alias specifies the alias of the key in the keystore, required by the JKS format. If not provided, the default alias `certificate` will be used.", + Type: []string{"string"}, + Format: "", + }, + }, + "passwordSecretRef": { + SchemaProps: spec.SchemaProps{ + Description: "PasswordSecretRef is a reference to a non-empty key in a Secret resource containing the password used to encrypt the JKS keystore. Mutually exclusive with password. One of password or passwordSecretRef must provide a password with a non-zero length.", + Default: map[string]interface{}{}, + Ref: ref("github.com/cert-manager/cert-manager/pkg/apis/meta/v1.SecretKeySelector"), + }, + }, + "password": { + SchemaProps: spec.SchemaProps{ + Description: "Password provides a literal password used to encrypt the JKS keystore. Mutually exclusive with passwordSecretRef. One of password or passwordSecretRef must provide a password with a non-zero length.", + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"create"}, + }, + }, + Dependencies: []string{ + "github.com/cert-manager/cert-manager/pkg/apis/meta/v1.SecretKeySelector"}, + } +} + +func schema_pkg_apis_certmanager_v1_NameConstraintItem(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "dnsDomains": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "DNSDomains is a list of DNS domains that are permitted or excluded.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + "ipRanges": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "IPRanges is a list of IP Ranges that are permitted or excluded. This should be a valid CIDR notation.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + "emailAddresses": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "EmailAddresses is a list of Email Addresses that are permitted or excluded.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + "uriDomains": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "URIDomains is a list of URI domains that are permitted or excluded.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + }, + }, + }, + } +} + +func schema_pkg_apis_certmanager_v1_NameConstraints(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "NameConstraints is a type to represent x509 NameConstraints", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "critical": { + SchemaProps: spec.SchemaProps{ + Description: "if true then the name constraints are marked critical.", + Type: []string{"boolean"}, + Format: "", + }, + }, + "permitted": { + SchemaProps: spec.SchemaProps{ + Description: "Permitted contains the constraints in which the names must be located.", + Ref: ref("github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1.NameConstraintItem"), + }, + }, + "excluded": { + SchemaProps: spec.SchemaProps{ + Description: "Excluded contains the constraints which must be disallowed. Any name matching a restriction in the excluded field is invalid regardless of information appearing in the permitted", + Ref: ref("github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1.NameConstraintItem"), + }, + }, + }, + }, + }, + Dependencies: []string{ + "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1.NameConstraintItem"}, + } +} + +func schema_pkg_apis_certmanager_v1_OtherName(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "oid": { + SchemaProps: spec.SchemaProps{ + Description: "OID is the object identifier for the otherName SAN. The object identifier must be expressed as a dotted string, for example, \"1.2.840.113556.1.4.221\".", + Type: []string{"string"}, + Format: "", + }, + }, + "utf8Value": { + SchemaProps: spec.SchemaProps{ + Description: "utf8Value is the string value of the otherName SAN. The utf8Value accepts any valid UTF8 string to set as value for the otherName SAN.", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + } +} + +func schema_pkg_apis_certmanager_v1_PKCS12Keystore(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "PKCS12 configures options for storing a PKCS12 keystore in the `spec.secretName` Secret resource.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "create": { + SchemaProps: spec.SchemaProps{ + Description: "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` or in `password`. 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", + Default: false, + Type: []string{"boolean"}, + Format: "", + }, + }, + "profile": { + SchemaProps: spec.SchemaProps{ + Description: "Profile specifies the key and certificate encryption algorithms and the HMAC algorithm used to create the PKCS12 keystore. Default value is `LegacyRC2` for backward compatibility.\n\nIf provided, allowed values are: `LegacyRC2`: Deprecated. Not supported by default in OpenSSL 3 or Java 20. `LegacyDES`: Less secure algorithm. Use this option for maximal compatibility. `Modern2023`: Secure algorithm. Use this option in case you have to always use secure algorithms (e.g., because of company policy). Please note that the security of the algorithm is not that important in reality, because the unencrypted certificate and private key are also stored in the Secret.", + Type: []string{"string"}, + Format: "", + }, + }, + "passwordSecretRef": { + SchemaProps: spec.SchemaProps{ + Description: "PasswordSecretRef is a reference to a non-empty key in a Secret resource containing the password used to encrypt the PKCS#12 keystore. Mutually exclusive with password. One of password or passwordSecretRef must provide a password with a non-zero length.", + Default: map[string]interface{}{}, + Ref: ref("github.com/cert-manager/cert-manager/pkg/apis/meta/v1.SecretKeySelector"), + }, + }, + "password": { + SchemaProps: spec.SchemaProps{ + Description: "Password provides a literal password used to encrypt the PKCS#12 keystore. Mutually exclusive with passwordSecretRef. One of password or passwordSecretRef must provide a password with a non-zero length.", + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"create"}, + }, + }, + Dependencies: []string{ + "github.com/cert-manager/cert-manager/pkg/apis/meta/v1.SecretKeySelector"}, + } +} + +func schema_pkg_apis_certmanager_v1_SelfSignedIssuer(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "Configures an issuer to 'self sign' certificates using the private key used to create the CertificateRequest object.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "crlDistributionPoints": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "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.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + }, + }, + }, + } +} + +func schema_pkg_apis_certmanager_v1_ServiceAccountRef(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "ServiceAccountRef is a service account used by cert-manager to request a token. Default 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.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "name": { + SchemaProps: spec.SchemaProps{ + Description: "Name of the ServiceAccount used to request a token.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "audiences": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "TokenAudiences is an optional list of extra audiences to include in the token passed to Vault. The default token consisting of the issuer's namespace and name is always included.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + }, + Required: []string{"name"}, + }, + }, + } +} + +func schema_pkg_apis_certmanager_v1_VaultAppRole(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "VaultAppRole authenticates with Vault using the App Role auth mechanism, with the role and secret stored in a Kubernetes Secret resource.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "path": { + SchemaProps: spec.SchemaProps{ + Description: "Path where the App Role authentication backend is mounted in Vault, e.g: \"approle\"", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "roleId": { + SchemaProps: spec.SchemaProps{ + Description: "RoleID configured in the App Role authentication backend when setting up the authentication backend in Vault.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "secretRef": { + SchemaProps: spec.SchemaProps{ + Description: "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.", + Default: map[string]interface{}{}, + Ref: ref("github.com/cert-manager/cert-manager/pkg/apis/meta/v1.SecretKeySelector"), + }, + }, + }, + Required: []string{"path", "roleId", "secretRef"}, + }, + }, + Dependencies: []string{ + "github.com/cert-manager/cert-manager/pkg/apis/meta/v1.SecretKeySelector"}, + } +} + +func schema_pkg_apis_certmanager_v1_VaultAuth(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "VaultAuth is configuration used to authenticate with a Vault server. The order of precedence is [`tokenSecretRef`, `appRole`, `clientCertificate` or `kubernetes`].", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "tokenSecretRef": { + SchemaProps: spec.SchemaProps{ + Description: "TokenSecretRef authenticates with Vault by presenting a token.", + Ref: ref("github.com/cert-manager/cert-manager/pkg/apis/meta/v1.SecretKeySelector"), + }, + }, + "appRole": { + SchemaProps: spec.SchemaProps{ + Description: "AppRole authenticates with Vault using the App Role auth mechanism, with the role and secret stored in a Kubernetes Secret resource.", + Ref: ref("github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1.VaultAppRole"), + }, + }, + "clientCertificate": { + SchemaProps: spec.SchemaProps{ + Description: "ClientCertificate authenticates with Vault by presenting a client certificate during the request's TLS handshake. Works only when using HTTPS protocol.", + Ref: ref("github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1.VaultClientCertificateAuth"), + }, + }, + "kubernetes": { + SchemaProps: spec.SchemaProps{ + Description: "Kubernetes authenticates with Vault by passing the ServiceAccount token stored in the named Secret resource to the Vault server.", + Ref: ref("github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1.VaultKubernetesAuth"), + }, + }, + }, + }, + }, + Dependencies: []string{ + "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1.VaultAppRole", "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1.VaultClientCertificateAuth", "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1.VaultKubernetesAuth", "github.com/cert-manager/cert-manager/pkg/apis/meta/v1.SecretKeySelector"}, + } +} + +func schema_pkg_apis_certmanager_v1_VaultClientCertificateAuth(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "VaultKubernetesAuth is used to authenticate against Vault using a client certificate stored in a Secret.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "mountPath": { + SchemaProps: spec.SchemaProps{ + Description: "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/cert\" will be used.", + Type: []string{"string"}, + Format: "", + }, + }, + "secretName": { + SchemaProps: spec.SchemaProps{ + Description: "Reference to Kubernetes Secret of type \"kubernetes.io/tls\" (hence containing tls.crt and tls.key) used to authenticate to Vault using TLS client authentication.", + Type: []string{"string"}, + Format: "", + }, + }, + "name": { + SchemaProps: spec.SchemaProps{ + Description: "Name of the certificate role to authenticate against. If not set, matching any certificate role, if available.", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + } +} + +func schema_pkg_apis_certmanager_v1_VaultIssuer(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "Configures an issuer to sign certificates using a HashiCorp Vault PKI backend.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "auth": { + SchemaProps: spec.SchemaProps{ + Description: "Auth configures how cert-manager authenticates with the Vault server.", + Default: map[string]interface{}{}, + Ref: ref("github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1.VaultAuth"), + }, + }, + "server": { + SchemaProps: spec.SchemaProps{ + Description: "Server is the connection address for the Vault server, e.g: \"https://vault.example.com:8200\".", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "serverName": { + SchemaProps: spec.SchemaProps{ + Description: "ServerName is used to verify the hostname on the returned certificates by the Vault server.", + Type: []string{"string"}, + Format: "", + }, + }, + "path": { + SchemaProps: spec.SchemaProps{ + Description: "Path is the mount path of the Vault PKI backend's `sign` endpoint, e.g: \"my_pki_mount/sign/my-role-name\".", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "namespace": { + SchemaProps: spec.SchemaProps{ + Description: "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", + Type: []string{"string"}, + Format: "", + }, + }, + "caBundle": { + SchemaProps: spec.SchemaProps{ + Description: "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.", + Type: []string{"string"}, + Format: "byte", + }, + }, + "caBundleSecretRef": { + SchemaProps: spec.SchemaProps{ + Description: "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'.", + Ref: ref("github.com/cert-manager/cert-manager/pkg/apis/meta/v1.SecretKeySelector"), + }, + }, + "clientCertSecretRef": { + SchemaProps: spec.SchemaProps{ + Description: "Reference to a Secret containing a PEM-encoded Client Certificate to use when the Vault server requires mTLS.", + Ref: ref("github.com/cert-manager/cert-manager/pkg/apis/meta/v1.SecretKeySelector"), + }, + }, + "clientKeySecretRef": { + SchemaProps: spec.SchemaProps{ + Description: "Reference to a Secret containing a PEM-encoded Client Private Key to use when the Vault server requires mTLS.", + Ref: ref("github.com/cert-manager/cert-manager/pkg/apis/meta/v1.SecretKeySelector"), + }, + }, + }, + Required: []string{"auth", "server", "path"}, + }, + }, + Dependencies: []string{ + "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1.VaultAuth", "github.com/cert-manager/cert-manager/pkg/apis/meta/v1.SecretKeySelector"}, + } +} + +func schema_pkg_apis_certmanager_v1_VaultKubernetesAuth(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "Authenticate against Vault using a Kubernetes ServiceAccount token stored in a Secret.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "mountPath": { + SchemaProps: spec.SchemaProps{ + Description: "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.", + Type: []string{"string"}, + Format: "", + }, + }, + "secretRef": { + SchemaProps: spec.SchemaProps{ + Description: "The required Secret field containing a Kubernetes ServiceAccount JWT used for authenticating with Vault. Use of 'ambient credentials' is not supported.", + Default: map[string]interface{}{}, + Ref: ref("github.com/cert-manager/cert-manager/pkg/apis/meta/v1.SecretKeySelector"), + }, + }, + "serviceAccountRef": { + SchemaProps: spec.SchemaProps{ + Description: "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.", + Ref: ref("github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1.ServiceAccountRef"), + }, + }, + "role": { + SchemaProps: spec.SchemaProps{ + Description: "A required field containing the Vault Role to assume. A Role binds a Kubernetes ServiceAccount with a set of Vault policies.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"role"}, + }, + }, + Dependencies: []string{ + "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1.ServiceAccountRef", "github.com/cert-manager/cert-manager/pkg/apis/meta/v1.SecretKeySelector"}, + } +} + +func schema_pkg_apis_certmanager_v1_VenafiCloud(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "VenafiCloud defines connection configuration details for Venafi Cloud", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "url": { + SchemaProps: spec.SchemaProps{ + Description: "URL is the base URL for Venafi Cloud. Defaults to \"https://api.venafi.cloud/\".", + Type: []string{"string"}, + Format: "", + }, + }, + "apiTokenSecretRef": { + SchemaProps: spec.SchemaProps{ + Description: "APITokenSecretRef is a secret key selector for the Venafi Cloud API token.", + Default: map[string]interface{}{}, + Ref: ref("github.com/cert-manager/cert-manager/pkg/apis/meta/v1.SecretKeySelector"), + }, + }, + }, + Required: []string{"apiTokenSecretRef"}, + }, + }, + Dependencies: []string{ + "github.com/cert-manager/cert-manager/pkg/apis/meta/v1.SecretKeySelector"}, + } +} + +func schema_pkg_apis_certmanager_v1_VenafiIssuer(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "Configures an issuer to sign certificates using a Venafi TPP or Cloud policy zone.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "zone": { + SchemaProps: spec.SchemaProps{ + Description: "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.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "tpp": { + SchemaProps: spec.SchemaProps{ + Description: "TPP specifies Trust Protection Platform configuration settings. Only one of TPP or Cloud may be specified.", + Ref: ref("github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1.VenafiTPP"), + }, + }, + "cloud": { + SchemaProps: spec.SchemaProps{ + Description: "Cloud specifies the Venafi cloud configuration settings. Only one of TPP or Cloud may be specified.", + Ref: ref("github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1.VenafiCloud"), + }, + }, + }, + Required: []string{"zone"}, + }, + }, + Dependencies: []string{ + "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1.VenafiCloud", "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1.VenafiTPP"}, + } +} + +func schema_pkg_apis_certmanager_v1_VenafiTPP(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "VenafiTPP defines connection configuration details for a Venafi TPP instance", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "url": { + SchemaProps: spec.SchemaProps{ + Description: "URL is the base URL for the vedsdk endpoint of the Venafi TPP instance, for example: \"https://tpp.example.com/vedsdk\".", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "credentialsRef": { + SchemaProps: spec.SchemaProps{ + Description: "CredentialsRef is a reference to a Secret containing the Venafi TPP API credentials. The secret must contain the key 'access-token' for the Access Token Authentication, or two keys, 'username' and 'password' for the API Keys Authentication.", + Default: map[string]interface{}{}, + Ref: ref("github.com/cert-manager/cert-manager/pkg/apis/meta/v1.LocalObjectReference"), + }, + }, + "caBundle": { + SchemaProps: spec.SchemaProps{ + Description: "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.", + Type: []string{"string"}, + Format: "byte", + }, + }, + "caBundleSecretRef": { + SchemaProps: spec.SchemaProps{ + Description: "Reference to a Secret containing a 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. Mutually exclusive with CABundle. If neither CABundle nor CABundleSecretRef is defined, the certificate bundle in the cert-manager controller container is used to validate the TLS connection.", + Ref: ref("github.com/cert-manager/cert-manager/pkg/apis/meta/v1.SecretKeySelector"), + }, + }, + }, + Required: []string{"url", "credentialsRef"}, + }, + }, + Dependencies: []string{ + "github.com/cert-manager/cert-manager/pkg/apis/meta/v1.LocalObjectReference", "github.com/cert-manager/cert-manager/pkg/apis/meta/v1.SecretKeySelector"}, + } +} + +func schema_pkg_apis_certmanager_v1_X509Subject(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "X509Subject Full X509 name specification", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "organizations": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "Organizations to be used on the Certificate.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + "countries": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "Countries to be used on the Certificate.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + "organizationalUnits": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "Organizational Units to be used on the Certificate.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + "localities": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "Cities to be used on the Certificate.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + "provinces": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "State/Provinces to be used on the Certificate.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + "streetAddresses": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "Street addresses to be used on the Certificate.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + "postalCodes": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "Postal codes to be used on the Certificate.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + "serialNumber": { + SchemaProps: spec.SchemaProps{ + Description: "Serial number to be used on the Certificate.", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + } +} + +func schema_pkg_apis_meta_v1_IssuerReference(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "ObjectReference is a reference to an object with a given name, kind and group. Deprecated: Use IssuerReference instead.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "name": { + SchemaProps: spec.SchemaProps{ + Description: "Name of the issuer being referred to.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind of the issuer being referred to. Defaults to 'Issuer'.", + Type: []string{"string"}, + Format: "", + }, + }, + "group": { + SchemaProps: spec.SchemaProps{ + Description: "Group of the issuer being referred to. Defaults to 'cert-manager.io'.", + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"name"}, + }, + }, + } +} + +func schema_pkg_apis_meta_v1_LocalObjectReference(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "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).", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "name": { + SchemaProps: spec.SchemaProps{ + Description: "Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"name"}, + }, + }, + } +} + +func schema_pkg_apis_meta_v1_SecretKeySelector(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "A reference to a specific 'key' within a Secret resource. In some instances, `key` is a required field.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "name": { + SchemaProps: spec.SchemaProps{ + Description: "Name of the resource being referred to. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "key": { + SchemaProps: spec.SchemaProps{ + Description: "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.", + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"name"}, + }, + }, + } +} + +func schema_k8sio_api_core_v1_AWSElasticBlockStoreVolumeSource(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "Represents a Persistent Disk resource in AWS.\n\nAn AWS EBS disk must exist before mounting to a container. The disk must also be in the same AWS zone as the kubelet. An AWS EBS disk can only be mounted as read/write once. AWS EBS volumes support ownership management and SELinux relabeling.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "volumeID": { + SchemaProps: spec.SchemaProps{ + Description: "volumeID is unique ID of the persistent disk resource in AWS (Amazon EBS volume). More info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "fsType": { + SchemaProps: spec.SchemaProps{ + Description: "fsType is the filesystem type of the volume that you want to mount. Tip: Ensure that the filesystem type is supported by the host operating system. Examples: \"ext4\", \"xfs\", \"ntfs\". Implicitly inferred to be \"ext4\" if unspecified. More info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore", + Type: []string{"string"}, + Format: "", + }, + }, + "partition": { + SchemaProps: spec.SchemaProps{ + Description: "partition is the partition in the volume that you want to mount. If omitted, the default is to mount by volume name. Examples: For volume /dev/sda1, you specify the partition as \"1\". Similarly, the volume partition for /dev/sda is \"0\" (or you can leave the property empty).", + Type: []string{"integer"}, + Format: "int32", + }, + }, + "readOnly": { + SchemaProps: spec.SchemaProps{ + Description: "readOnly value true will force the readOnly setting in VolumeMounts. More info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore", + Type: []string{"boolean"}, + Format: "", + }, + }, + }, + Required: []string{"volumeID"}, + }, + }, + } +} + +func schema_k8sio_api_core_v1_Affinity(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "Affinity is a group of affinity scheduling rules.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "nodeAffinity": { + SchemaProps: spec.SchemaProps{ + Description: "Describes node affinity scheduling rules for the pod.", + Ref: ref("k8s.io/api/core/v1.NodeAffinity"), + }, + }, + "podAffinity": { + SchemaProps: spec.SchemaProps{ + Description: "Describes pod affinity scheduling rules (e.g. co-locate this pod in the same node, zone, etc. as some other pod(s)).", + Ref: ref("k8s.io/api/core/v1.PodAffinity"), + }, + }, + "podAntiAffinity": { + SchemaProps: spec.SchemaProps{ + Description: "Describes pod anti-affinity scheduling rules (e.g. avoid putting this pod in the same node, zone, etc. as some other pod(s)).", + Ref: ref("k8s.io/api/core/v1.PodAntiAffinity"), + }, + }, + }, + }, + }, + Dependencies: []string{ + "k8s.io/api/core/v1.NodeAffinity", "k8s.io/api/core/v1.PodAffinity", "k8s.io/api/core/v1.PodAntiAffinity"}, + } +} + +func schema_k8sio_api_core_v1_AppArmorProfile(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "AppArmorProfile defines a pod or container's AppArmor settings.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "type": { + SchemaProps: spec.SchemaProps{ + Description: "type indicates which kind of AppArmor profile will be applied. Valid options are:\n Localhost - a profile pre-loaded on the node.\n RuntimeDefault - the container runtime's default profile.\n Unconfined - no AppArmor enforcement.\n\nPossible enum values:\n - `\"Localhost\"` indicates that a profile pre-loaded on the node should be used.\n - `\"RuntimeDefault\"` indicates that the container runtime's default AppArmor profile should be used.\n - `\"Unconfined\"` indicates that no AppArmor profile should be enforced.", + Default: "", + Type: []string{"string"}, + Format: "", + Enum: []interface{}{"Localhost", "RuntimeDefault", "Unconfined"}, + }, + }, + "localhostProfile": { + SchemaProps: spec.SchemaProps{ + Description: "localhostProfile indicates a profile loaded on the node that should be used. The profile must be preconfigured on the node to work. Must match the loaded name of the profile. Must be set if and only if type is \"Localhost\".", + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"type"}, + }, + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-unions": []interface{}{ + map[string]interface{}{ + "discriminator": "type", + "fields-to-discriminateBy": map[string]interface{}{ + "localhostProfile": "LocalhostProfile", + }, + }, + }, + }, + }, + }, + } +} + +func schema_k8sio_api_core_v1_AttachedVolume(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "AttachedVolume describes a volume attached to a node", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "name": { + SchemaProps: spec.SchemaProps{ + Description: "Name of the attached volume", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "devicePath": { + SchemaProps: spec.SchemaProps{ + Description: "DevicePath represents the device path where the volume should be available", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"name", "devicePath"}, + }, + }, + } +} + +func schema_k8sio_api_core_v1_AvoidPods(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "AvoidPods describes pods that should avoid this node. This is the value for a Node annotation with key scheduler.alpha.kubernetes.io/preferAvoidPods and will eventually become a field of NodeStatus.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "preferAvoidPods": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "Bounded-sized list of signatures of pods that should avoid this node, sorted in timestamp order from oldest to newest. Size of the slice is unspecified.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/api/core/v1.PreferAvoidPodsEntry"), + }, + }, + }, + }, + }, + }, + }, + }, + Dependencies: []string{ + "k8s.io/api/core/v1.PreferAvoidPodsEntry"}, + } +} + +func schema_k8sio_api_core_v1_AzureDiskVolumeSource(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "AzureDisk represents an Azure Data Disk mount on the host and bind mount to the pod.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "diskName": { + SchemaProps: spec.SchemaProps{ + Description: "diskName is the Name of the data disk in the blob storage", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "diskURI": { + SchemaProps: spec.SchemaProps{ + Description: "diskURI is the URI of data disk in the blob storage", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "cachingMode": { + SchemaProps: spec.SchemaProps{ + Description: "cachingMode is the Host Caching mode: None, Read Only, Read Write.\n\nPossible enum values:\n - `\"None\"`\n - `\"ReadOnly\"`\n - `\"ReadWrite\"`", + Default: v1.AzureDataDiskCachingReadWrite, + Type: []string{"string"}, + Format: "", + Enum: []interface{}{"None", "ReadOnly", "ReadWrite"}, + }, + }, + "fsType": { + SchemaProps: spec.SchemaProps{ + Description: "fsType is Filesystem type to mount. Must be a filesystem type supported by the host operating system. Ex. \"ext4\", \"xfs\", \"ntfs\". Implicitly inferred to be \"ext4\" if unspecified.", + Default: "ext4", + Type: []string{"string"}, + Format: "", + }, + }, + "readOnly": { + SchemaProps: spec.SchemaProps{ + Description: "readOnly Defaults to false (read/write). ReadOnly here will force the ReadOnly setting in VolumeMounts.", + Default: false, + Type: []string{"boolean"}, + Format: "", + }, + }, + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "kind expected values are Shared: multiple blob disks per storage account Dedicated: single blob disk per storage account Managed: azure managed data disk (only in managed availability set). defaults to shared\n\nPossible enum values:\n - `\"Dedicated\"`\n - `\"Managed\"`\n - `\"Shared\"`", + Default: v1.AzureSharedBlobDisk, + Type: []string{"string"}, + Format: "", + Enum: []interface{}{"Dedicated", "Managed", "Shared"}, + }, + }, + }, + Required: []string{"diskName", "diskURI"}, + }, + }, + } +} + +func schema_k8sio_api_core_v1_AzureFilePersistentVolumeSource(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "AzureFile represents an Azure File Service mount on the host and bind mount to the pod.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "secretName": { + SchemaProps: spec.SchemaProps{ + Description: "secretName is the name of secret that contains Azure Storage Account Name and Key", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "shareName": { + SchemaProps: spec.SchemaProps{ + Description: "shareName is the azure Share Name", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "readOnly": { + SchemaProps: spec.SchemaProps{ + Description: "readOnly defaults to false (read/write). ReadOnly here will force the ReadOnly setting in VolumeMounts.", + Type: []string{"boolean"}, + Format: "", + }, + }, + "secretNamespace": { + SchemaProps: spec.SchemaProps{ + Description: "secretNamespace is the namespace of the secret that contains Azure Storage Account Name and Key default is the same as the Pod", + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"secretName", "shareName"}, + }, + }, + } +} + +func schema_k8sio_api_core_v1_AzureFileVolumeSource(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "AzureFile represents an Azure File Service mount on the host and bind mount to the pod.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "secretName": { + SchemaProps: spec.SchemaProps{ + Description: "secretName is the name of secret that contains Azure Storage Account Name and Key", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "shareName": { + SchemaProps: spec.SchemaProps{ + Description: "shareName is the azure share Name", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "readOnly": { + SchemaProps: spec.SchemaProps{ + Description: "readOnly defaults to false (read/write). ReadOnly here will force the ReadOnly setting in VolumeMounts.", + Type: []string{"boolean"}, + Format: "", + }, + }, + }, + Required: []string{"secretName", "shareName"}, + }, + }, + } +} + +func schema_k8sio_api_core_v1_Binding(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "Binding ties one object to another; for example, a pod is bound to a node by a scheduler.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", + }, + }, + "metadata": { + SchemaProps: spec.SchemaProps{ + Description: "Standard object's metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata", + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"), + }, + }, + "target": { + SchemaProps: spec.SchemaProps{ + Description: "The target object that you want to bind to the standard object.", + Default: map[string]interface{}{}, + Ref: ref("k8s.io/api/core/v1.ObjectReference"), + }, + }, + }, + Required: []string{"target"}, + }, + }, + Dependencies: []string{ + "k8s.io/api/core/v1.ObjectReference", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"}, + } +} + +func schema_k8sio_api_core_v1_CSIPersistentVolumeSource(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "Represents storage that is managed by an external CSI volume driver", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "driver": { + SchemaProps: spec.SchemaProps{ + Description: "driver is the name of the driver to use for this volume. Required.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "volumeHandle": { + SchemaProps: spec.SchemaProps{ + Description: "volumeHandle is the unique volume name returned by the CSI volume plugin’s CreateVolume to refer to the volume on all subsequent calls. Required.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "readOnly": { + SchemaProps: spec.SchemaProps{ + Description: "readOnly value to pass to ControllerPublishVolumeRequest. Defaults to false (read/write).", + Type: []string{"boolean"}, + Format: "", + }, + }, + "fsType": { + SchemaProps: spec.SchemaProps{ + Description: "fsType to mount. Must be a filesystem type supported by the host operating system. Ex. \"ext4\", \"xfs\", \"ntfs\".", + Type: []string{"string"}, + Format: "", + }, + }, + "volumeAttributes": { + SchemaProps: spec.SchemaProps{ + Description: "volumeAttributes of the volume to publish.", + Type: []string{"object"}, + AdditionalProperties: &spec.SchemaOrBool{ + Allows: true, + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + "controllerPublishSecretRef": { + SchemaProps: spec.SchemaProps{ + Description: "controllerPublishSecretRef is a reference to the secret object containing sensitive information to pass to the CSI driver to complete the CSI ControllerPublishVolume and ControllerUnpublishVolume calls. This field is optional, and may be empty if no secret is required. If the secret object contains more than one secret, all secrets are passed.", + Ref: ref("k8s.io/api/core/v1.SecretReference"), + }, + }, + "nodeStageSecretRef": { + SchemaProps: spec.SchemaProps{ + Description: "nodeStageSecretRef is a reference to the secret object containing sensitive information to pass to the CSI driver to complete the CSI NodeStageVolume and NodeStageVolume and NodeUnstageVolume calls. This field is optional, and may be empty if no secret is required. If the secret object contains more than one secret, all secrets are passed.", + Ref: ref("k8s.io/api/core/v1.SecretReference"), + }, + }, + "nodePublishSecretRef": { + SchemaProps: spec.SchemaProps{ + Description: "nodePublishSecretRef is a reference to the secret object containing sensitive information to pass to the CSI driver to complete the CSI NodePublishVolume and NodeUnpublishVolume calls. This field is optional, and may be empty if no secret is required. If the secret object contains more than one secret, all secrets are passed.", + Ref: ref("k8s.io/api/core/v1.SecretReference"), + }, + }, + "controllerExpandSecretRef": { + SchemaProps: spec.SchemaProps{ + Description: "controllerExpandSecretRef is a reference to the secret object containing sensitive information to pass to the CSI driver to complete the CSI ControllerExpandVolume call. This field is optional, and may be empty if no secret is required. If the secret object contains more than one secret, all secrets are passed.", + Ref: ref("k8s.io/api/core/v1.SecretReference"), + }, + }, + "nodeExpandSecretRef": { + SchemaProps: spec.SchemaProps{ + Description: "nodeExpandSecretRef is a reference to the secret object containing sensitive information to pass to the CSI driver to complete the CSI NodeExpandVolume call. This field is optional, may be omitted if no secret is required. If the secret object contains more than one secret, all secrets are passed.", + Ref: ref("k8s.io/api/core/v1.SecretReference"), + }, + }, + }, + Required: []string{"driver", "volumeHandle"}, + }, + }, + Dependencies: []string{ + "k8s.io/api/core/v1.SecretReference"}, + } +} + +func schema_k8sio_api_core_v1_CSIVolumeSource(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "Represents a source location of a volume to mount, managed by an external CSI driver", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "driver": { + SchemaProps: spec.SchemaProps{ + Description: "driver is the name of the CSI driver that handles this volume. Consult with your admin for the correct name as registered in the cluster.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "readOnly": { + SchemaProps: spec.SchemaProps{ + Description: "readOnly specifies a read-only configuration for the volume. Defaults to false (read/write).", + Type: []string{"boolean"}, + Format: "", + }, + }, + "fsType": { + SchemaProps: spec.SchemaProps{ + Description: "fsType to mount. Ex. \"ext4\", \"xfs\", \"ntfs\". If not provided, the empty value is passed to the associated CSI driver which will determine the default filesystem to apply.", + Type: []string{"string"}, + Format: "", + }, + }, + "volumeAttributes": { + SchemaProps: spec.SchemaProps{ + Description: "volumeAttributes stores driver-specific properties that are passed to the CSI driver. Consult your driver's documentation for supported values.", + Type: []string{"object"}, + AdditionalProperties: &spec.SchemaOrBool{ + Allows: true, + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + "nodePublishSecretRef": { + SchemaProps: spec.SchemaProps{ + Description: "nodePublishSecretRef is a reference to the secret object containing sensitive information to pass to the CSI driver to complete the CSI NodePublishVolume and NodeUnpublishVolume calls. This field is optional, and may be empty if no secret is required. If the secret object contains more than one secret, all secret references are passed.", + Ref: ref("k8s.io/api/core/v1.LocalObjectReference"), + }, + }, + }, + Required: []string{"driver"}, + }, + }, + Dependencies: []string{ + "k8s.io/api/core/v1.LocalObjectReference"}, + } +} + +func schema_k8sio_api_core_v1_Capabilities(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "Adds and removes POSIX capabilities from running containers.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "add": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "Added capabilities", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + "drop": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "Removed capabilities", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + }, + }, + }, + } +} + +func schema_k8sio_api_core_v1_CephFSPersistentVolumeSource(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "Represents a Ceph Filesystem mount that lasts the lifetime of a pod Cephfs volumes do not support ownership management or SELinux relabeling.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "monitors": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "monitors is Required: Monitors is a collection of Ceph monitors More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + "path": { + SchemaProps: spec.SchemaProps{ + Description: "path is Optional: Used as the mounted root, rather than the full Ceph tree, default is /", + Type: []string{"string"}, + Format: "", + }, + }, + "user": { + SchemaProps: spec.SchemaProps{ + Description: "user is Optional: User is the rados user name, default is admin More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it", + Type: []string{"string"}, + Format: "", + }, + }, + "secretFile": { + SchemaProps: spec.SchemaProps{ + Description: "secretFile is Optional: SecretFile is the path to key ring for User, default is /etc/ceph/user.secret More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it", + Type: []string{"string"}, + Format: "", + }, + }, + "secretRef": { + SchemaProps: spec.SchemaProps{ + Description: "secretRef is Optional: SecretRef is reference to the authentication secret for User, default is empty. More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it", + Ref: ref("k8s.io/api/core/v1.SecretReference"), + }, + }, + "readOnly": { + SchemaProps: spec.SchemaProps{ + Description: "readOnly is Optional: Defaults to false (read/write). ReadOnly here will force the ReadOnly setting in VolumeMounts. More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it", + Type: []string{"boolean"}, + Format: "", + }, + }, + }, + Required: []string{"monitors"}, + }, + }, + Dependencies: []string{ + "k8s.io/api/core/v1.SecretReference"}, + } +} + +func schema_k8sio_api_core_v1_CephFSVolumeSource(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "Represents a Ceph Filesystem mount that lasts the lifetime of a pod Cephfs volumes do not support ownership management or SELinux relabeling.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "monitors": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "monitors is Required: Monitors is a collection of Ceph monitors More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + "path": { + SchemaProps: spec.SchemaProps{ + Description: "path is Optional: Used as the mounted root, rather than the full Ceph tree, default is /", + Type: []string{"string"}, + Format: "", + }, + }, + "user": { + SchemaProps: spec.SchemaProps{ + Description: "user is optional: User is the rados user name, default is admin More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it", + Type: []string{"string"}, + Format: "", + }, + }, + "secretFile": { + SchemaProps: spec.SchemaProps{ + Description: "secretFile is Optional: SecretFile is the path to key ring for User, default is /etc/ceph/user.secret More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it", + Type: []string{"string"}, + Format: "", + }, + }, + "secretRef": { + SchemaProps: spec.SchemaProps{ + Description: "secretRef is Optional: SecretRef is reference to the authentication secret for User, default is empty. More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it", + Ref: ref("k8s.io/api/core/v1.LocalObjectReference"), + }, + }, + "readOnly": { + SchemaProps: spec.SchemaProps{ + Description: "readOnly is Optional: Defaults to false (read/write). ReadOnly here will force the ReadOnly setting in VolumeMounts. More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it", + Type: []string{"boolean"}, + Format: "", + }, + }, + }, + Required: []string{"monitors"}, + }, + }, + Dependencies: []string{ + "k8s.io/api/core/v1.LocalObjectReference"}, + } +} + +func schema_k8sio_api_core_v1_CinderPersistentVolumeSource(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "Represents a cinder volume resource in Openstack. A Cinder volume must exist before mounting to a container. The volume must also be in the same region as the kubelet. Cinder volumes support ownership management and SELinux relabeling.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "volumeID": { + SchemaProps: spec.SchemaProps{ + Description: "volumeID used to identify the volume in cinder. More info: https://examples.k8s.io/mysql-cinder-pd/README.md", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "fsType": { + SchemaProps: spec.SchemaProps{ + Description: "fsType Filesystem type to mount. Must be a filesystem type supported by the host operating system. Examples: \"ext4\", \"xfs\", \"ntfs\". Implicitly inferred to be \"ext4\" if unspecified. More info: https://examples.k8s.io/mysql-cinder-pd/README.md", + Type: []string{"string"}, + Format: "", + }, + }, + "readOnly": { + SchemaProps: spec.SchemaProps{ + Description: "readOnly is Optional: Defaults to false (read/write). ReadOnly here will force the ReadOnly setting in VolumeMounts. More info: https://examples.k8s.io/mysql-cinder-pd/README.md", + Type: []string{"boolean"}, + Format: "", + }, + }, + "secretRef": { + SchemaProps: spec.SchemaProps{ + Description: "secretRef is Optional: points to a secret object containing parameters used to connect to OpenStack.", + Ref: ref("k8s.io/api/core/v1.SecretReference"), + }, + }, + }, + Required: []string{"volumeID"}, + }, + }, + Dependencies: []string{ + "k8s.io/api/core/v1.SecretReference"}, + } +} + +func schema_k8sio_api_core_v1_CinderVolumeSource(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "Represents a cinder volume resource in Openstack. A Cinder volume must exist before mounting to a container. The volume must also be in the same region as the kubelet. Cinder volumes support ownership management and SELinux relabeling.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "volumeID": { + SchemaProps: spec.SchemaProps{ + Description: "volumeID used to identify the volume in cinder. More info: https://examples.k8s.io/mysql-cinder-pd/README.md", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "fsType": { + SchemaProps: spec.SchemaProps{ + Description: "fsType is the filesystem type to mount. Must be a filesystem type supported by the host operating system. Examples: \"ext4\", \"xfs\", \"ntfs\". Implicitly inferred to be \"ext4\" if unspecified. More info: https://examples.k8s.io/mysql-cinder-pd/README.md", + Type: []string{"string"}, + Format: "", + }, + }, + "readOnly": { + SchemaProps: spec.SchemaProps{ + Description: "readOnly defaults to false (read/write). ReadOnly here will force the ReadOnly setting in VolumeMounts. More info: https://examples.k8s.io/mysql-cinder-pd/README.md", + Type: []string{"boolean"}, + Format: "", + }, + }, + "secretRef": { + SchemaProps: spec.SchemaProps{ + Description: "secretRef is optional: points to a secret object containing parameters used to connect to OpenStack.", + Ref: ref("k8s.io/api/core/v1.LocalObjectReference"), + }, + }, + }, + Required: []string{"volumeID"}, + }, + }, + Dependencies: []string{ + "k8s.io/api/core/v1.LocalObjectReference"}, + } +} + +func schema_k8sio_api_core_v1_ClientIPConfig(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "ClientIPConfig represents the configurations of Client IP based session affinity.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "timeoutSeconds": { + SchemaProps: spec.SchemaProps{ + Description: "timeoutSeconds specifies the seconds of ClientIP type session sticky time. The value must be >0 && <=86400(for 1 day) if ServiceAffinity == \"ClientIP\". Default value is 10800(for 3 hours).", + Type: []string{"integer"}, + Format: "int32", + }, + }, + }, + }, + }, + } +} + +func schema_k8sio_api_core_v1_ClusterTrustBundleProjection(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "ClusterTrustBundleProjection describes how to select a set of ClusterTrustBundle objects and project their contents into the pod filesystem.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "name": { + SchemaProps: spec.SchemaProps{ + Description: "Select a single ClusterTrustBundle by object name. Mutually-exclusive with signerName and labelSelector.", + Type: []string{"string"}, + Format: "", + }, + }, + "signerName": { + SchemaProps: spec.SchemaProps{ + Description: "Select all ClusterTrustBundles that match this signer name. Mutually-exclusive with name. The contents of all selected ClusterTrustBundles will be unified and deduplicated.", + Type: []string{"string"}, + Format: "", + }, + }, + "labelSelector": { + SchemaProps: spec.SchemaProps{ + Description: "Select all ClusterTrustBundles that match this label selector. Only has effect if signerName is set. Mutually-exclusive with name. If unset, interpreted as \"match nothing\". If set but empty, interpreted as \"match everything\".", + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.LabelSelector"), + }, + }, + "optional": { + SchemaProps: spec.SchemaProps{ + Description: "If true, don't block pod startup if the referenced ClusterTrustBundle(s) aren't available. If using name, then the named ClusterTrustBundle is allowed not to exist. If using signerName, then the combination of signerName and labelSelector is allowed to match zero ClusterTrustBundles.", + Type: []string{"boolean"}, + Format: "", + }, + }, + "path": { + SchemaProps: spec.SchemaProps{ + Description: "Relative path from the volume root to write the bundle.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"path"}, + }, + }, + Dependencies: []string{ + "k8s.io/apimachinery/pkg/apis/meta/v1.LabelSelector"}, + } +} + +func schema_k8sio_api_core_v1_ComponentCondition(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "Information about the condition of a component.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "type": { + SchemaProps: spec.SchemaProps{ + Description: "Type of condition for a component. Valid value: \"Healthy\"", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "status": { + SchemaProps: spec.SchemaProps{ + Description: "Status of the condition for a component. Valid values for \"Healthy\": \"True\", \"False\", or \"Unknown\".", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "message": { + SchemaProps: spec.SchemaProps{ + Description: "Message about the condition for a component. For example, information about a health check.", + Type: []string{"string"}, + Format: "", + }, + }, + "error": { + SchemaProps: spec.SchemaProps{ + Description: "Condition error code for a component. For example, a health check error code.", + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"type", "status"}, + }, + }, + } +} + +func schema_k8sio_api_core_v1_ComponentStatus(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "ComponentStatus (and ComponentStatusList) holds the cluster validation info. Deprecated: This API is deprecated in v1.19+", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", + }, + }, + "metadata": { + SchemaProps: spec.SchemaProps{ + Description: "Standard object's metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata", + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"), + }, + }, + "conditions": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-map-keys": []interface{}{ + "type", + }, + "x-kubernetes-list-type": "map", + "x-kubernetes-patch-merge-key": "type", + "x-kubernetes-patch-strategy": "merge", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "List of component conditions observed", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/api/core/v1.ComponentCondition"), + }, + }, + }, + }, + }, + }, + }, + }, + Dependencies: []string{ + "k8s.io/api/core/v1.ComponentCondition", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"}, + } +} + +func schema_k8sio_api_core_v1_ComponentStatusList(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "Status of all the conditions for the component as a list of ComponentStatus objects. Deprecated: This API is deprecated in v1.19+", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", + }, + }, + "metadata": { + SchemaProps: spec.SchemaProps{ + Description: "Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"), + }, + }, + "items": { + SchemaProps: spec.SchemaProps{ + Description: "List of ComponentStatus objects.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/api/core/v1.ComponentStatus"), + }, + }, + }, + }, + }, + }, + Required: []string{"items"}, + }, + }, + Dependencies: []string{ + "k8s.io/api/core/v1.ComponentStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"}, + } +} + +func schema_k8sio_api_core_v1_ConfigMap(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "ConfigMap holds configuration data for pods to consume.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", + }, + }, + "metadata": { + SchemaProps: spec.SchemaProps{ + Description: "Standard object's metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata", + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"), + }, + }, + "immutable": { + SchemaProps: spec.SchemaProps{ + Description: "Immutable, if set to true, ensures that data stored in the ConfigMap cannot be updated (only object metadata can be modified). If not set to true, the field can be modified at any time. Defaulted to nil.", + Type: []string{"boolean"}, + Format: "", + }, + }, + "data": { + SchemaProps: spec.SchemaProps{ + Description: "Data contains the configuration data. Each key must consist of alphanumeric characters, '-', '_' or '.'. Values with non-UTF-8 byte sequences must use the BinaryData field. The keys stored in Data must not overlap with the keys in the BinaryData field, this is enforced during validation process.", + Type: []string{"object"}, + AdditionalProperties: &spec.SchemaOrBool{ + Allows: true, + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + "binaryData": { + SchemaProps: spec.SchemaProps{ + Description: "BinaryData contains the binary data. Each key must consist of alphanumeric characters, '-', '_' or '.'. BinaryData can contain byte sequences that are not in the UTF-8 range. The keys stored in BinaryData must not overlap with the ones in the Data field, this is enforced during validation process. Using this field will require 1.10+ apiserver and kubelet.", + Type: []string{"object"}, + AdditionalProperties: &spec.SchemaOrBool{ + Allows: true, + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"string"}, + Format: "byte", + }, + }, + }, + }, + }, + }, + }, + }, + Dependencies: []string{ + "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"}, + } +} + +func schema_k8sio_api_core_v1_ConfigMapEnvSource(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "ConfigMapEnvSource selects a ConfigMap to populate the environment variables with.\n\nThe contents of the target ConfigMap's Data field will represent the key-value pairs as environment variables.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "name": { + SchemaProps: spec.SchemaProps{ + Description: "Name of the referent. This field is effectively required, but due to backwards compatibility is allowed to be empty. Instances of this type with an empty value here are almost certainly wrong. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "optional": { + SchemaProps: spec.SchemaProps{ + Description: "Specify whether the ConfigMap must be defined", + Type: []string{"boolean"}, + Format: "", + }, + }, + }, + }, + }, + } +} + +func schema_k8sio_api_core_v1_ConfigMapKeySelector(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "Selects a key from a ConfigMap.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "name": { + SchemaProps: spec.SchemaProps{ + Description: "Name of the referent. This field is effectively required, but due to backwards compatibility is allowed to be empty. Instances of this type with an empty value here are almost certainly wrong. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "key": { + SchemaProps: spec.SchemaProps{ + Description: "The key to select.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "optional": { + SchemaProps: spec.SchemaProps{ + Description: "Specify whether the ConfigMap or its key must be defined", + Type: []string{"boolean"}, + Format: "", + }, + }, + }, + Required: []string{"key"}, + }, + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-map-type": "atomic", + }, + }, + }, + } +} + +func schema_k8sio_api_core_v1_ConfigMapList(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "ConfigMapList is a resource containing a list of ConfigMap objects.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", + }, + }, + "metadata": { + SchemaProps: spec.SchemaProps{ + Description: "More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata", + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"), + }, + }, + "items": { + SchemaProps: spec.SchemaProps{ + Description: "Items is the list of ConfigMaps.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/api/core/v1.ConfigMap"), + }, + }, + }, + }, + }, + }, + Required: []string{"items"}, + }, + }, + Dependencies: []string{ + "k8s.io/api/core/v1.ConfigMap", "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"}, + } +} + +func schema_k8sio_api_core_v1_ConfigMapNodeConfigSource(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "ConfigMapNodeConfigSource contains the information to reference a ConfigMap as a config source for the Node. This API is deprecated since 1.22: https://git.k8s.io/enhancements/keps/sig-node/281-dynamic-kubelet-configuration", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "namespace": { + SchemaProps: spec.SchemaProps{ + Description: "Namespace is the metadata.namespace of the referenced ConfigMap. This field is required in all cases.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "name": { + SchemaProps: spec.SchemaProps{ + Description: "Name is the metadata.name of the referenced ConfigMap. This field is required in all cases.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "uid": { + SchemaProps: spec.SchemaProps{ + Description: "UID is the metadata.UID of the referenced ConfigMap. This field is forbidden in Node.Spec, and required in Node.Status.", + Type: []string{"string"}, + Format: "", + }, + }, + "resourceVersion": { + SchemaProps: spec.SchemaProps{ + Description: "ResourceVersion is the metadata.ResourceVersion of the referenced ConfigMap. This field is forbidden in Node.Spec, and required in Node.Status.", + Type: []string{"string"}, + Format: "", + }, + }, + "kubeletConfigKey": { + SchemaProps: spec.SchemaProps{ + Description: "KubeletConfigKey declares which key of the referenced ConfigMap corresponds to the KubeletConfiguration structure This field is required in all cases.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"namespace", "name", "kubeletConfigKey"}, + }, + }, + } +} + +func schema_k8sio_api_core_v1_ConfigMapProjection(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "Adapts a ConfigMap into a projected volume.\n\nThe contents of the target ConfigMap's Data field will be presented in a projected volume as files using the keys in the Data field as the file names, unless the items element is populated with specific mappings of keys to paths. Note that this is identical to a configmap volume source without the default mode.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "name": { + SchemaProps: spec.SchemaProps{ + Description: "Name of the referent. This field is effectively required, but due to backwards compatibility is allowed to be empty. Instances of this type with an empty value here are almost certainly wrong. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "items": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "items if unspecified, each key-value pair in the Data field of the referenced ConfigMap will be projected into the volume as a file whose name is the key and content is the value. If specified, the listed keys will be projected into the specified paths, and unlisted keys will not be present. If a key is specified which is not present in the ConfigMap, the volume setup will error unless it is marked optional. Paths must be relative and may not contain the '..' path or start with '..'.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/api/core/v1.KeyToPath"), + }, + }, + }, + }, + }, + "optional": { + SchemaProps: spec.SchemaProps{ + Description: "optional specify whether the ConfigMap or its keys must be defined", + Type: []string{"boolean"}, + Format: "", + }, + }, + }, + }, + }, + Dependencies: []string{ + "k8s.io/api/core/v1.KeyToPath"}, + } +} + +func schema_k8sio_api_core_v1_ConfigMapVolumeSource(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "Adapts a ConfigMap into a volume.\n\nThe contents of the target ConfigMap's Data field will be presented in a volume as files using the keys in the Data field as the file names, unless the items element is populated with specific mappings of keys to paths. ConfigMap volumes support ownership management and SELinux relabeling.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "name": { + SchemaProps: spec.SchemaProps{ + Description: "Name of the referent. This field is effectively required, but due to backwards compatibility is allowed to be empty. Instances of this type with an empty value here are almost certainly wrong. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "items": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "items if unspecified, each key-value pair in the Data field of the referenced ConfigMap will be projected into the volume as a file whose name is the key and content is the value. If specified, the listed keys will be projected into the specified paths, and unlisted keys will not be present. If a key is specified which is not present in the ConfigMap, the volume setup will error unless it is marked optional. Paths must be relative and may not contain the '..' path or start with '..'.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/api/core/v1.KeyToPath"), + }, + }, + }, + }, + }, + "defaultMode": { + SchemaProps: spec.SchemaProps{ + Description: "defaultMode is optional: mode bits used to set permissions on created files by default. Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. Defaults to 0644. Directories within the path are not affected by this setting. This might be in conflict with other options that affect the file mode, like fsGroup, and the result can be other mode bits set.", + Type: []string{"integer"}, + Format: "int32", + }, + }, + "optional": { + SchemaProps: spec.SchemaProps{ + Description: "optional specify whether the ConfigMap or its keys must be defined", + Type: []string{"boolean"}, + Format: "", + }, + }, + }, + }, + }, + Dependencies: []string{ + "k8s.io/api/core/v1.KeyToPath"}, + } +} + +func schema_k8sio_api_core_v1_Container(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "A single application container that you want to run within a pod.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "name": { + SchemaProps: spec.SchemaProps{ + Description: "Name of the container specified as a DNS_LABEL. Each container in a pod must have a unique name (DNS_LABEL). Cannot be updated.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "image": { + SchemaProps: spec.SchemaProps{ + Description: "Container image name. More info: https://kubernetes.io/docs/concepts/containers/images This field is optional to allow higher level config management to default or override container images in workload controllers like Deployments and StatefulSets.", + Type: []string{"string"}, + Format: "", + }, + }, + "command": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "Entrypoint array. Not executed within a shell. The container image's ENTRYPOINT is used if this is not provided. Variable references $(VAR_NAME) are expanded using the container's environment. If a variable cannot be resolved, the reference in the input string will be unchanged. Double $$ are reduced to a single $, which allows for escaping the $(VAR_NAME) syntax: i.e. \"$$(VAR_NAME)\" will produce the string literal \"$(VAR_NAME)\". Escaped references will never be expanded, regardless of whether the variable exists or not. Cannot be updated. More info: https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + "args": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "Arguments to the entrypoint. The container image's CMD is used if this is not provided. Variable references $(VAR_NAME) are expanded using the container's environment. If a variable cannot be resolved, the reference in the input string will be unchanged. Double $$ are reduced to a single $, which allows for escaping the $(VAR_NAME) syntax: i.e. \"$$(VAR_NAME)\" will produce the string literal \"$(VAR_NAME)\". Escaped references will never be expanded, regardless of whether the variable exists or not. Cannot be updated. More info: https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + "workingDir": { + SchemaProps: spec.SchemaProps{ + Description: "Container's working directory. If not specified, the container runtime's default will be used, which might be configured in the container image. Cannot be updated.", + Type: []string{"string"}, + Format: "", + }, + }, + "ports": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-map-keys": []interface{}{ + "containerPort", + "protocol", + }, + "x-kubernetes-list-type": "map", + "x-kubernetes-patch-merge-key": "containerPort", + "x-kubernetes-patch-strategy": "merge", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "List of ports to expose from the container. Not specifying a port here DOES NOT prevent that port from being exposed. Any port which is listening on the default \"0.0.0.0\" address inside a container will be accessible from the network. Modifying this array with strategic merge patch may corrupt the data. For more information See https://github.com/kubernetes/kubernetes/issues/108255. Cannot be updated.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/api/core/v1.ContainerPort"), + }, + }, + }, + }, + }, + "envFrom": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "List of sources to populate environment variables in the container. The keys defined within a source may consist of any printable ASCII characters except '='. When a key exists in multiple sources, the value associated with the last source will take precedence. Values defined by an Env with a duplicate key will take precedence. Cannot be updated.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/api/core/v1.EnvFromSource"), + }, + }, + }, + }, + }, + "env": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-map-keys": []interface{}{ + "name", + }, + "x-kubernetes-list-type": "map", + "x-kubernetes-patch-merge-key": "name", + "x-kubernetes-patch-strategy": "merge", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "List of environment variables to set in the container. Cannot be updated.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/api/core/v1.EnvVar"), + }, + }, + }, + }, + }, + "resources": { + SchemaProps: spec.SchemaProps{ + Description: "Compute Resources required by this container. Cannot be updated. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/", + Default: map[string]interface{}{}, + Ref: ref("k8s.io/api/core/v1.ResourceRequirements"), + }, + }, + "resizePolicy": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "Resources resize policy for the container.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/api/core/v1.ContainerResizePolicy"), + }, + }, + }, + }, + }, + "restartPolicy": { + SchemaProps: spec.SchemaProps{ + Description: "RestartPolicy defines the restart behavior of individual containers in a pod. This overrides the pod-level restart policy. When this field is not specified, the restart behavior is defined by the Pod's restart policy and the container type. Additionally, setting the RestartPolicy as \"Always\" for the init container will have the following effect: this init container will be continually restarted on exit until all regular containers have terminated. Once all regular containers have completed, all init containers with restartPolicy \"Always\" will be shut down. This lifecycle differs from normal init containers and is often referred to as a \"sidecar\" container. Although this init container still starts in the init container sequence, it does not wait for the container to complete before proceeding to the next init container. Instead, the next init container starts immediately after this init container is started, or after any startupProbe has successfully completed.", + Type: []string{"string"}, + Format: "", + }, + }, + "restartPolicyRules": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "Represents a list of rules to be checked to determine if the container should be restarted on exit. The rules are evaluated in order. Once a rule matches a container exit condition, the remaining rules are ignored. If no rule matches the container exit condition, the Container-level restart policy determines the whether the container is restarted or not. Constraints on the rules: - At most 20 rules are allowed. - Rules can have the same action. - Identical rules are not forbidden in validations. When rules are specified, container MUST set RestartPolicy explicitly even it if matches the Pod's RestartPolicy.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/api/core/v1.ContainerRestartRule"), + }, + }, + }, + }, + }, + "volumeMounts": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-map-keys": []interface{}{ + "mountPath", + }, + "x-kubernetes-list-type": "map", + "x-kubernetes-patch-merge-key": "mountPath", + "x-kubernetes-patch-strategy": "merge", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "Pod volumes to mount into the container's filesystem. Cannot be updated.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/api/core/v1.VolumeMount"), + }, + }, + }, + }, + }, + "volumeDevices": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-map-keys": []interface{}{ + "devicePath", + }, + "x-kubernetes-list-type": "map", + "x-kubernetes-patch-merge-key": "devicePath", + "x-kubernetes-patch-strategy": "merge", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "volumeDevices is the list of block devices to be used by the container.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/api/core/v1.VolumeDevice"), + }, + }, + }, + }, + }, + "livenessProbe": { + SchemaProps: spec.SchemaProps{ + Description: "Periodic probe of container liveness. Container will be restarted if the probe fails. Cannot be updated. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes", + Ref: ref("k8s.io/api/core/v1.Probe"), + }, + }, + "readinessProbe": { + SchemaProps: spec.SchemaProps{ + Description: "Periodic probe of container service readiness. Container will be removed from service endpoints if the probe fails. Cannot be updated. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes", + Ref: ref("k8s.io/api/core/v1.Probe"), + }, + }, + "startupProbe": { + SchemaProps: spec.SchemaProps{ + Description: "StartupProbe indicates that the Pod has successfully initialized. If specified, no other probes are executed until this completes successfully. If this probe fails, the Pod will be restarted, just as if the livenessProbe failed. This can be used to provide different probe parameters at the beginning of a Pod's lifecycle, when it might take a long time to load data or warm a cache, than during steady-state operation. This cannot be updated. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes", + Ref: ref("k8s.io/api/core/v1.Probe"), + }, + }, + "lifecycle": { + SchemaProps: spec.SchemaProps{ + Description: "Actions that the management system should take in response to container lifecycle events. Cannot be updated.", + Ref: ref("k8s.io/api/core/v1.Lifecycle"), + }, + }, + "terminationMessagePath": { + SchemaProps: spec.SchemaProps{ + Description: "Optional: Path at which the file to which the container's termination message will be written is mounted into the container's filesystem. Message written is intended to be brief final status, such as an assertion failure message. Will be truncated by the node if greater than 4096 bytes. The total message length across all containers will be limited to 12kb. Defaults to /dev/termination-log. Cannot be updated.", + Type: []string{"string"}, + Format: "", + }, + }, + "terminationMessagePolicy": { + SchemaProps: spec.SchemaProps{ + Description: "Indicate how the termination message should be populated. File will use the contents of terminationMessagePath to populate the container status message on both success and failure. FallbackToLogsOnError will use the last chunk of container log output if the termination message file is empty and the container exited with an error. The log output is limited to 2048 bytes or 80 lines, whichever is smaller. Defaults to File. Cannot be updated.\n\nPossible enum values:\n - `\"FallbackToLogsOnError\"` will read the most recent contents of the container logs for the container status message when the container exits with an error and the terminationMessagePath has no contents.\n - `\"File\"` is the default behavior and will set the container status message to the contents of the container's terminationMessagePath when the container exits.", + Type: []string{"string"}, + Format: "", + Enum: []interface{}{"FallbackToLogsOnError", "File"}, + }, + }, + "imagePullPolicy": { + SchemaProps: spec.SchemaProps{ + Description: "Image pull policy. One of Always, Never, IfNotPresent. Defaults to Always if :latest tag is specified, or IfNotPresent otherwise. Cannot be updated. More info: https://kubernetes.io/docs/concepts/containers/images#updating-images\n\nPossible enum values:\n - `\"Always\"` means that kubelet always attempts to pull the latest image. Container will fail If the pull fails.\n - `\"IfNotPresent\"` means that kubelet pulls if the image isn't present on disk. Container will fail if the image isn't present and the pull fails.\n - `\"Never\"` means that kubelet never pulls an image, but only uses a local image. Container will fail if the image isn't present", + Type: []string{"string"}, + Format: "", + Enum: []interface{}{"Always", "IfNotPresent", "Never"}, + }, + }, + "securityContext": { + SchemaProps: spec.SchemaProps{ + Description: "SecurityContext defines the security options the container should be run with. If set, the fields of SecurityContext override the equivalent fields of PodSecurityContext. More info: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/", + Ref: ref("k8s.io/api/core/v1.SecurityContext"), + }, + }, + "stdin": { + SchemaProps: spec.SchemaProps{ + Description: "Whether this container should allocate a buffer for stdin in the container runtime. If this is not set, reads from stdin in the container will always result in EOF. Default is false.", + Type: []string{"boolean"}, + Format: "", + }, + }, + "stdinOnce": { + SchemaProps: spec.SchemaProps{ + Description: "Whether the container runtime should close the stdin channel after it has been opened by a single attach. When stdin is true the stdin stream will remain open across multiple attach sessions. If stdinOnce is set to true, stdin is opened on container start, is empty until the first client attaches to stdin, and then remains open and accepts data until the client disconnects, at which time stdin is closed and remains closed until the container is restarted. If this flag is false, a container processes that reads from stdin will never receive an EOF. Default is false", + Type: []string{"boolean"}, + Format: "", + }, + }, + "tty": { + SchemaProps: spec.SchemaProps{ + Description: "Whether this container should allocate a TTY for itself, also requires 'stdin' to be true. Default is false.", + Type: []string{"boolean"}, + Format: "", + }, + }, + }, + Required: []string{"name"}, + }, + }, + Dependencies: []string{ + "k8s.io/api/core/v1.ContainerPort", "k8s.io/api/core/v1.ContainerResizePolicy", "k8s.io/api/core/v1.ContainerRestartRule", "k8s.io/api/core/v1.EnvFromSource", "k8s.io/api/core/v1.EnvVar", "k8s.io/api/core/v1.Lifecycle", "k8s.io/api/core/v1.Probe", "k8s.io/api/core/v1.ResourceRequirements", "k8s.io/api/core/v1.SecurityContext", "k8s.io/api/core/v1.VolumeDevice", "k8s.io/api/core/v1.VolumeMount"}, + } +} + +func schema_k8sio_api_core_v1_ContainerExtendedResourceRequest(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "ContainerExtendedResourceRequest has the mapping of container name, extended resource name to the device request name.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "containerName": { + SchemaProps: spec.SchemaProps{ + Description: "The name of the container requesting resources.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "resourceName": { + SchemaProps: spec.SchemaProps{ + Description: "The name of the extended resource in that container which gets backed by DRA.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "requestName": { + SchemaProps: spec.SchemaProps{ + Description: "The name of the request in the special ResourceClaim which corresponds to the extended resource.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"containerName", "resourceName", "requestName"}, + }, + }, + } +} + +func schema_k8sio_api_core_v1_ContainerImage(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "Describe a container image", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "names": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "Names by which this image is known. e.g. [\"kubernetes.example/hyperkube:v1.0.7\", \"cloud-vendor.registry.example/cloud-vendor/hyperkube:v1.0.7\"]", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + "sizeBytes": { + SchemaProps: spec.SchemaProps{ + Description: "The size of the image in bytes.", + Type: []string{"integer"}, + Format: "int64", + }, + }, + }, + }, + }, + } +} + +func schema_k8sio_api_core_v1_ContainerPort(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "ContainerPort represents a network port in a single container.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "name": { + SchemaProps: spec.SchemaProps{ + Description: "If specified, this must be an IANA_SVC_NAME and unique within the pod. Each named port in a pod must have a unique name. Name for the port that can be referred to by services.", + Type: []string{"string"}, + Format: "", + }, + }, + "hostPort": { + SchemaProps: spec.SchemaProps{ + Description: "Number of port to expose on the host. If specified, this must be a valid port number, 0 < x < 65536. If HostNetwork is specified, this must match ContainerPort. Most containers do not need this.", + Type: []string{"integer"}, + Format: "int32", + }, + }, + "containerPort": { + SchemaProps: spec.SchemaProps{ + Description: "Number of port to expose on the pod's IP address. This must be a valid port number, 0 < x < 65536.", + Default: 0, + Type: []string{"integer"}, + Format: "int32", + }, + }, + "protocol": { + SchemaProps: spec.SchemaProps{ + Description: "Protocol for port. Must be UDP, TCP, or SCTP. Defaults to \"TCP\".\n\nPossible enum values:\n - `\"SCTP\"` is the SCTP protocol.\n - `\"TCP\"` is the TCP protocol.\n - `\"UDP\"` is the UDP protocol.", + Default: "TCP", + Type: []string{"string"}, + Format: "", + Enum: []interface{}{"SCTP", "TCP", "UDP"}, + }, + }, + "hostIP": { + SchemaProps: spec.SchemaProps{ + Description: "What host IP to bind the external port to.", + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"containerPort"}, + }, + }, + } +} + +func schema_k8sio_api_core_v1_ContainerResizePolicy(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "ContainerResizePolicy represents resource resize policy for the container.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "resourceName": { + SchemaProps: spec.SchemaProps{ + Description: "Name of the resource to which this resource resize policy applies. Supported values: cpu, memory.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "restartPolicy": { + SchemaProps: spec.SchemaProps{ + Description: "Restart policy to apply when specified resource is resized. If not specified, it defaults to NotRequired.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"resourceName", "restartPolicy"}, + }, + }, + } +} + +func schema_k8sio_api_core_v1_ContainerRestartRule(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "ContainerRestartRule describes how a container exit is handled.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "action": { + SchemaProps: spec.SchemaProps{ + Description: "Specifies the action taken on a container exit if the requirements are satisfied. The only possible value is \"Restart\" to restart the container.", + Type: []string{"string"}, + Format: "", + }, + }, + "exitCodes": { + SchemaProps: spec.SchemaProps{ + Description: "Represents the exit codes to check on container exits.", + Ref: ref("k8s.io/api/core/v1.ContainerRestartRuleOnExitCodes"), + }, + }, + }, + Required: []string{"action"}, + }, + }, + Dependencies: []string{ + "k8s.io/api/core/v1.ContainerRestartRuleOnExitCodes"}, + } +} + +func schema_k8sio_api_core_v1_ContainerRestartRuleOnExitCodes(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "ContainerRestartRuleOnExitCodes describes the condition for handling an exited container based on its exit codes.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "operator": { + SchemaProps: spec.SchemaProps{ + Description: "Represents the relationship between the container exit code(s) and the specified values. Possible values are: - In: the requirement is satisfied if the container exit code is in the\n set of specified values.\n- NotIn: the requirement is satisfied if the container exit code is\n not in the set of specified values.", + Type: []string{"string"}, + Format: "", + }, + }, + "values": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "set", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "Specifies the set of values to check for container exit codes. At most 255 elements are allowed.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: 0, + Type: []string{"integer"}, + Format: "int32", + }, + }, + }, + }, + }, + }, + Required: []string{"operator"}, + }, + }, + } +} + +func schema_k8sio_api_core_v1_ContainerState(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "ContainerState holds a possible state of container. Only one of its members may be specified. If none of them is specified, the default one is ContainerStateWaiting.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "waiting": { + SchemaProps: spec.SchemaProps{ + Description: "Details about a waiting container", + Ref: ref("k8s.io/api/core/v1.ContainerStateWaiting"), + }, + }, + "running": { + SchemaProps: spec.SchemaProps{ + Description: "Details about a running container", + Ref: ref("k8s.io/api/core/v1.ContainerStateRunning"), + }, + }, + "terminated": { + SchemaProps: spec.SchemaProps{ + Description: "Details about a terminated container", + Ref: ref("k8s.io/api/core/v1.ContainerStateTerminated"), + }, + }, + }, + }, + }, + Dependencies: []string{ + "k8s.io/api/core/v1.ContainerStateRunning", "k8s.io/api/core/v1.ContainerStateTerminated", "k8s.io/api/core/v1.ContainerStateWaiting"}, + } +} + +func schema_k8sio_api_core_v1_ContainerStateRunning(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "ContainerStateRunning is a running state of a container.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "startedAt": { + SchemaProps: spec.SchemaProps{ + Description: "Time at which the container was last (re-)started", + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.Time"), + }, + }, + }, + }, + }, + Dependencies: []string{ + "k8s.io/apimachinery/pkg/apis/meta/v1.Time"}, + } +} + +func schema_k8sio_api_core_v1_ContainerStateTerminated(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "ContainerStateTerminated is a terminated state of a container.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "exitCode": { + SchemaProps: spec.SchemaProps{ + Description: "Exit status from the last termination of the container", + Default: 0, + Type: []string{"integer"}, + Format: "int32", + }, + }, + "signal": { + SchemaProps: spec.SchemaProps{ + Description: "Signal from the last termination of the container", + Type: []string{"integer"}, + Format: "int32", + }, + }, + "reason": { + SchemaProps: spec.SchemaProps{ + Description: "(brief) reason from the last termination of the container", + Type: []string{"string"}, + Format: "", + }, + }, + "message": { + SchemaProps: spec.SchemaProps{ + Description: "Message regarding the last termination of the container", + Type: []string{"string"}, + Format: "", + }, + }, + "startedAt": { + SchemaProps: spec.SchemaProps{ + Description: "Time at which previous execution of the container started", + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.Time"), + }, + }, + "finishedAt": { + SchemaProps: spec.SchemaProps{ + Description: "Time at which the container last terminated", + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.Time"), + }, + }, + "containerID": { + SchemaProps: spec.SchemaProps{ + Description: "Container's ID in the format '://'", + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"exitCode"}, + }, + }, + Dependencies: []string{ + "k8s.io/apimachinery/pkg/apis/meta/v1.Time"}, + } +} + +func schema_k8sio_api_core_v1_ContainerStateWaiting(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "ContainerStateWaiting is a waiting state of a container.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "reason": { + SchemaProps: spec.SchemaProps{ + Description: "(brief) reason the container is not yet running.", + Type: []string{"string"}, + Format: "", + }, + }, + "message": { + SchemaProps: spec.SchemaProps{ + Description: "Message regarding why the container is not yet running.", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + } +} + +func schema_k8sio_api_core_v1_ContainerStatus(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "ContainerStatus contains details for the current status of this container.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "name": { + SchemaProps: spec.SchemaProps{ + Description: "Name is a DNS_LABEL representing the unique name of the container. Each container in a pod must have a unique name across all container types. Cannot be updated.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "state": { + SchemaProps: spec.SchemaProps{ + Description: "State holds details about the container's current condition.", + Default: map[string]interface{}{}, + Ref: ref("k8s.io/api/core/v1.ContainerState"), + }, + }, + "lastState": { + SchemaProps: spec.SchemaProps{ + Description: "LastTerminationState holds the last termination state of the container to help debug container crashes and restarts. This field is not populated if the container is still running and RestartCount is 0.", + Default: map[string]interface{}{}, + Ref: ref("k8s.io/api/core/v1.ContainerState"), + }, + }, + "ready": { + SchemaProps: spec.SchemaProps{ + Description: "Ready specifies whether the container is currently passing its readiness check. The value will change as readiness probes keep executing. If no readiness probes are specified, this field defaults to true once the container is fully started (see Started field).\n\nThe value is typically used to determine whether a container is ready to accept traffic.", + Default: false, + Type: []string{"boolean"}, + Format: "", + }, + }, + "restartCount": { + SchemaProps: spec.SchemaProps{ + Description: "RestartCount holds the number of times the container has been restarted. Kubelet makes an effort to always increment the value, but there are cases when the state may be lost due to node restarts and then the value may be reset to 0. The value is never negative.", + Default: 0, + Type: []string{"integer"}, + Format: "int32", + }, + }, + "image": { + SchemaProps: spec.SchemaProps{ + Description: "Image is the name of container image that the container is running. The container image may not match the image used in the PodSpec, as it may have been resolved by the runtime. More info: https://kubernetes.io/docs/concepts/containers/images.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "imageID": { + SchemaProps: spec.SchemaProps{ + Description: "ImageID is the image ID of the container's image. The image ID may not match the image ID of the image used in the PodSpec, as it may have been resolved by the runtime.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "containerID": { + SchemaProps: spec.SchemaProps{ + Description: "ContainerID is the ID of the container in the format '://'. Where type is a container runtime identifier, returned from Version call of CRI API (for example \"containerd\").", + Type: []string{"string"}, + Format: "", + }, + }, + "started": { + SchemaProps: spec.SchemaProps{ + Description: "Started indicates whether the container has finished its postStart lifecycle hook and passed its startup probe. Initialized as false, becomes true after startupProbe is considered successful. Resets to false when the container is restarted, or if kubelet loses state temporarily. In both cases, startup probes will run again. Is always true when no startupProbe is defined and container is running and has passed the postStart lifecycle hook. The null value must be treated the same as false.", + Type: []string{"boolean"}, + Format: "", + }, + }, + "allocatedResources": { + SchemaProps: spec.SchemaProps{ + Description: "AllocatedResources represents the compute resources allocated for this container by the node. Kubelet sets this value to Container.Resources.Requests upon successful pod admission and after successfully admitting desired pod resize.", + Type: []string{"object"}, + AdditionalProperties: &spec.SchemaOrBool{ + Allows: true, + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Ref: ref("k8s.io/apimachinery/pkg/api/resource.Quantity"), + }, + }, + }, + }, + }, + "resources": { + SchemaProps: spec.SchemaProps{ + Description: "Resources represents the compute resource requests and limits that have been successfully enacted on the running container after it has been started or has been successfully resized.", + Ref: ref("k8s.io/api/core/v1.ResourceRequirements"), + }, + }, + "volumeMounts": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-map-keys": []interface{}{ + "mountPath", + }, + "x-kubernetes-list-type": "map", + "x-kubernetes-patch-merge-key": "mountPath", + "x-kubernetes-patch-strategy": "merge", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "Status of volume mounts.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/api/core/v1.VolumeMountStatus"), + }, + }, + }, + }, + }, + "user": { + SchemaProps: spec.SchemaProps{ + Description: "User represents user identity information initially attached to the first process of the container", + Ref: ref("k8s.io/api/core/v1.ContainerUser"), + }, + }, + "allocatedResourcesStatus": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-map-keys": []interface{}{ + "name", + }, + "x-kubernetes-list-type": "map", + "x-kubernetes-patch-merge-key": "name", + "x-kubernetes-patch-strategy": "merge", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "AllocatedResourcesStatus represents the status of various resources allocated for this Pod.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/api/core/v1.ResourceStatus"), + }, + }, + }, + }, + }, + "stopSignal": { + SchemaProps: spec.SchemaProps{ + Description: "StopSignal reports the effective stop signal for this container\n\nPossible enum values:\n - `\"SIGABRT\"`\n - `\"SIGALRM\"`\n - `\"SIGBUS\"`\n - `\"SIGCHLD\"`\n - `\"SIGCLD\"`\n - `\"SIGCONT\"`\n - `\"SIGFPE\"`\n - `\"SIGHUP\"`\n - `\"SIGILL\"`\n - `\"SIGINT\"`\n - `\"SIGIO\"`\n - `\"SIGIOT\"`\n - `\"SIGKILL\"`\n - `\"SIGPIPE\"`\n - `\"SIGPOLL\"`\n - `\"SIGPROF\"`\n - `\"SIGPWR\"`\n - `\"SIGQUIT\"`\n - `\"SIGRTMAX\"`\n - `\"SIGRTMAX-1\"`\n - `\"SIGRTMAX-10\"`\n - `\"SIGRTMAX-11\"`\n - `\"SIGRTMAX-12\"`\n - `\"SIGRTMAX-13\"`\n - `\"SIGRTMAX-14\"`\n - `\"SIGRTMAX-2\"`\n - `\"SIGRTMAX-3\"`\n - `\"SIGRTMAX-4\"`\n - `\"SIGRTMAX-5\"`\n - `\"SIGRTMAX-6\"`\n - `\"SIGRTMAX-7\"`\n - `\"SIGRTMAX-8\"`\n - `\"SIGRTMAX-9\"`\n - `\"SIGRTMIN\"`\n - `\"SIGRTMIN+1\"`\n - `\"SIGRTMIN+10\"`\n - `\"SIGRTMIN+11\"`\n - `\"SIGRTMIN+12\"`\n - `\"SIGRTMIN+13\"`\n - `\"SIGRTMIN+14\"`\n - `\"SIGRTMIN+15\"`\n - `\"SIGRTMIN+2\"`\n - `\"SIGRTMIN+3\"`\n - `\"SIGRTMIN+4\"`\n - `\"SIGRTMIN+5\"`\n - `\"SIGRTMIN+6\"`\n - `\"SIGRTMIN+7\"`\n - `\"SIGRTMIN+8\"`\n - `\"SIGRTMIN+9\"`\n - `\"SIGSEGV\"`\n - `\"SIGSTKFLT\"`\n - `\"SIGSTOP\"`\n - `\"SIGSYS\"`\n - `\"SIGTERM\"`\n - `\"SIGTRAP\"`\n - `\"SIGTSTP\"`\n - `\"SIGTTIN\"`\n - `\"SIGTTOU\"`\n - `\"SIGURG\"`\n - `\"SIGUSR1\"`\n - `\"SIGUSR2\"`\n - `\"SIGVTALRM\"`\n - `\"SIGWINCH\"`\n - `\"SIGXCPU\"`\n - `\"SIGXFSZ\"`", + Type: []string{"string"}, + Format: "", + Enum: []interface{}{"SIGABRT", "SIGALRM", "SIGBUS", "SIGCHLD", "SIGCLD", "SIGCONT", "SIGFPE", "SIGHUP", "SIGILL", "SIGINT", "SIGIO", "SIGIOT", "SIGKILL", "SIGPIPE", "SIGPOLL", "SIGPROF", "SIGPWR", "SIGQUIT", "SIGRTMAX", "SIGRTMAX-1", "SIGRTMAX-10", "SIGRTMAX-11", "SIGRTMAX-12", "SIGRTMAX-13", "SIGRTMAX-14", "SIGRTMAX-2", "SIGRTMAX-3", "SIGRTMAX-4", "SIGRTMAX-5", "SIGRTMAX-6", "SIGRTMAX-7", "SIGRTMAX-8", "SIGRTMAX-9", "SIGRTMIN", "SIGRTMIN+1", "SIGRTMIN+10", "SIGRTMIN+11", "SIGRTMIN+12", "SIGRTMIN+13", "SIGRTMIN+14", "SIGRTMIN+15", "SIGRTMIN+2", "SIGRTMIN+3", "SIGRTMIN+4", "SIGRTMIN+5", "SIGRTMIN+6", "SIGRTMIN+7", "SIGRTMIN+8", "SIGRTMIN+9", "SIGSEGV", "SIGSTKFLT", "SIGSTOP", "SIGSYS", "SIGTERM", "SIGTRAP", "SIGTSTP", "SIGTTIN", "SIGTTOU", "SIGURG", "SIGUSR1", "SIGUSR2", "SIGVTALRM", "SIGWINCH", "SIGXCPU", "SIGXFSZ"}, + }, + }, + }, + Required: []string{"name", "ready", "restartCount", "image", "imageID"}, + }, + }, + Dependencies: []string{ + "k8s.io/api/core/v1.ContainerState", "k8s.io/api/core/v1.ContainerUser", "k8s.io/api/core/v1.ResourceRequirements", "k8s.io/api/core/v1.ResourceStatus", "k8s.io/api/core/v1.VolumeMountStatus", "k8s.io/apimachinery/pkg/api/resource.Quantity"}, + } +} + +func schema_k8sio_api_core_v1_ContainerUser(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "ContainerUser represents user identity information", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "linux": { + SchemaProps: spec.SchemaProps{ + Description: "Linux holds user identity information initially attached to the first process of the containers in Linux. Note that the actual running identity can be changed if the process has enough privilege to do so.", + Ref: ref("k8s.io/api/core/v1.LinuxContainerUser"), + }, + }, + }, + }, + }, + Dependencies: []string{ + "k8s.io/api/core/v1.LinuxContainerUser"}, + } +} + +func schema_k8sio_api_core_v1_DaemonEndpoint(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "DaemonEndpoint contains information about a single Daemon endpoint.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "Port": { + SchemaProps: spec.SchemaProps{ + Description: "Port number of the given endpoint.", + Default: 0, + Type: []string{"integer"}, + Format: "int32", + }, + }, + }, + Required: []string{"Port"}, + }, + }, + } +} + +func schema_k8sio_api_core_v1_DownwardAPIProjection(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "Represents downward API info for projecting into a projected volume. Note that this is identical to a downwardAPI volume source without the default mode.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "items": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "Items is a list of DownwardAPIVolume file", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/api/core/v1.DownwardAPIVolumeFile"), + }, + }, + }, + }, + }, + }, + }, + }, + Dependencies: []string{ + "k8s.io/api/core/v1.DownwardAPIVolumeFile"}, + } +} + +func schema_k8sio_api_core_v1_DownwardAPIVolumeFile(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "DownwardAPIVolumeFile represents information to create the file containing the pod field", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "path": { + SchemaProps: spec.SchemaProps{ + Description: "Required: Path is the relative path name of the file to be created. Must not be absolute or contain the '..' path. Must be utf-8 encoded. The first item of the relative path must not start with '..'", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "fieldRef": { + SchemaProps: spec.SchemaProps{ + Description: "Required: Selects a field of the pod: only annotations, labels, name, namespace and uid are supported.", + Ref: ref("k8s.io/api/core/v1.ObjectFieldSelector"), + }, + }, + "resourceFieldRef": { + SchemaProps: spec.SchemaProps{ + Description: "Selects a resource of the container: only resources limits and requests (limits.cpu, limits.memory, requests.cpu and requests.memory) are currently supported.", + Ref: ref("k8s.io/api/core/v1.ResourceFieldSelector"), + }, + }, + "mode": { + SchemaProps: spec.SchemaProps{ + Description: "Optional: mode bits used to set permissions on this file, must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. If not specified, the volume defaultMode will be used. This might be in conflict with other options that affect the file mode, like fsGroup, and the result can be other mode bits set.", + Type: []string{"integer"}, + Format: "int32", + }, + }, + }, + Required: []string{"path"}, + }, + }, + Dependencies: []string{ + "k8s.io/api/core/v1.ObjectFieldSelector", "k8s.io/api/core/v1.ResourceFieldSelector"}, + } +} + +func schema_k8sio_api_core_v1_DownwardAPIVolumeSource(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "DownwardAPIVolumeSource represents a volume containing downward API info. Downward API volumes support ownership management and SELinux relabeling.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "items": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "Items is a list of downward API volume file", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/api/core/v1.DownwardAPIVolumeFile"), + }, + }, + }, + }, + }, + "defaultMode": { + SchemaProps: spec.SchemaProps{ + Description: "Optional: mode bits to use on created files by default. Must be a Optional: mode bits used to set permissions on created files by default. Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. Defaults to 0644. Directories within the path are not affected by this setting. This might be in conflict with other options that affect the file mode, like fsGroup, and the result can be other mode bits set.", + Type: []string{"integer"}, + Format: "int32", + }, + }, + }, + }, + }, + Dependencies: []string{ + "k8s.io/api/core/v1.DownwardAPIVolumeFile"}, + } +} + +func schema_k8sio_api_core_v1_EmptyDirVolumeSource(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "Represents an empty directory for a pod. Empty directory volumes support ownership management and SELinux relabeling.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "medium": { + SchemaProps: spec.SchemaProps{ + Description: "medium represents what type of storage medium should back this directory. The default is \"\" which means to use the node's default medium. Must be an empty string (default) or Memory. More info: https://kubernetes.io/docs/concepts/storage/volumes#emptydir", + Type: []string{"string"}, + Format: "", + }, + }, + "sizeLimit": { + SchemaProps: spec.SchemaProps{ + Description: "sizeLimit is the total amount of local storage required for this EmptyDir volume. The size limit is also applicable for memory medium. The maximum usage on memory medium EmptyDir would be the minimum value between the SizeLimit specified here and the sum of memory limits of all containers in a pod. The default is nil which means that the limit is undefined. More info: https://kubernetes.io/docs/concepts/storage/volumes#emptydir", + Ref: ref("k8s.io/apimachinery/pkg/api/resource.Quantity"), + }, + }, + }, + }, + }, + Dependencies: []string{ + "k8s.io/apimachinery/pkg/api/resource.Quantity"}, + } +} + +func schema_k8sio_api_core_v1_EndpointAddress(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "EndpointAddress is a tuple that describes single IP address. Deprecated: This API is deprecated in v1.33+.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "ip": { + SchemaProps: spec.SchemaProps{ + Description: "The IP of this endpoint. May not be loopback (127.0.0.0/8 or ::1), link-local (169.254.0.0/16 or fe80::/10), or link-local multicast (224.0.0.0/24 or ff02::/16).", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "hostname": { + SchemaProps: spec.SchemaProps{ + Description: "The Hostname of this endpoint", + Type: []string{"string"}, + Format: "", + }, + }, + "nodeName": { + SchemaProps: spec.SchemaProps{ + Description: "Optional: Node hosting this endpoint. This can be used to determine endpoints local to a node.", + Type: []string{"string"}, + Format: "", + }, + }, + "targetRef": { + SchemaProps: spec.SchemaProps{ + Description: "Reference to object providing the endpoint.", + Ref: ref("k8s.io/api/core/v1.ObjectReference"), + }, + }, + }, + Required: []string{"ip"}, + }, + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-map-type": "atomic", + }, + }, + }, + Dependencies: []string{ + "k8s.io/api/core/v1.ObjectReference"}, + } +} + +func schema_k8sio_api_core_v1_EndpointPort(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "EndpointPort is a tuple that describes a single port. Deprecated: This API is deprecated in v1.33+.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "name": { + SchemaProps: spec.SchemaProps{ + Description: "The name of this port. This must match the 'name' field in the corresponding ServicePort. Must be a DNS_LABEL. Optional only if one port is defined.", + Type: []string{"string"}, + Format: "", + }, + }, + "port": { + SchemaProps: spec.SchemaProps{ + Description: "The port number of the endpoint.", + Default: 0, + Type: []string{"integer"}, + Format: "int32", + }, + }, + "protocol": { + SchemaProps: spec.SchemaProps{ + Description: "The IP protocol for this port. Must be UDP, TCP, or SCTP. Default is TCP.\n\nPossible enum values:\n - `\"SCTP\"` is the SCTP protocol.\n - `\"TCP\"` is the TCP protocol.\n - `\"UDP\"` is the UDP protocol.", + Type: []string{"string"}, + Format: "", + Enum: []interface{}{"SCTP", "TCP", "UDP"}, + }, + }, + "appProtocol": { + SchemaProps: spec.SchemaProps{ + Description: "The application protocol for this port. This is used as a hint for implementations to offer richer behavior for protocols that they understand. This field follows standard Kubernetes label syntax. Valid values are either:\n\n* Un-prefixed protocol names - reserved for IANA standard service names (as per RFC-6335 and https://www.iana.org/assignments/service-names).\n\n* Kubernetes-defined prefixed names:\n * 'kubernetes.io/h2c' - HTTP/2 prior knowledge over cleartext as described in https://www.rfc-editor.org/rfc/rfc9113.html#name-starting-http-2-with-prior-\n * 'kubernetes.io/ws' - WebSocket over cleartext as described in https://www.rfc-editor.org/rfc/rfc6455\n * 'kubernetes.io/wss' - WebSocket over TLS as described in https://www.rfc-editor.org/rfc/rfc6455\n\n* Other protocols should use implementation-defined prefixed names such as mycompany.com/my-custom-protocol.", + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"port"}, + }, + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-map-type": "atomic", + }, + }, + }, + } +} + +func schema_k8sio_api_core_v1_EndpointSubset(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "EndpointSubset is a group of addresses with a common set of ports. The expanded set of endpoints is the Cartesian product of Addresses x Ports. For example, given:\n\n\t{\n\t Addresses: [{\"ip\": \"10.10.1.1\"}, {\"ip\": \"10.10.2.2\"}],\n\t Ports: [{\"name\": \"a\", \"port\": 8675}, {\"name\": \"b\", \"port\": 309}]\n\t}\n\nThe resulting set of endpoints can be viewed as:\n\n\ta: [ 10.10.1.1:8675, 10.10.2.2:8675 ],\n\tb: [ 10.10.1.1:309, 10.10.2.2:309 ]\n\nDeprecated: This API is deprecated in v1.33+.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "addresses": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "IP addresses which offer the related ports that are marked as ready. These endpoints should be considered safe for load balancers and clients to utilize.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/api/core/v1.EndpointAddress"), + }, + }, + }, + }, + }, + "notReadyAddresses": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "IP addresses which offer the related ports but are not currently marked as ready because they have not yet finished starting, have recently failed a readiness check, or have recently failed a liveness check.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/api/core/v1.EndpointAddress"), + }, + }, + }, + }, + }, + "ports": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "Port numbers available on the related IP addresses.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/api/core/v1.EndpointPort"), + }, + }, + }, + }, + }, + }, + }, + }, + Dependencies: []string{ + "k8s.io/api/core/v1.EndpointAddress", "k8s.io/api/core/v1.EndpointPort"}, + } +} + +func schema_k8sio_api_core_v1_Endpoints(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "Endpoints is a collection of endpoints that implement the actual service. Example:\n\n\t Name: \"mysvc\",\n\t Subsets: [\n\t {\n\t Addresses: [{\"ip\": \"10.10.1.1\"}, {\"ip\": \"10.10.2.2\"}],\n\t Ports: [{\"name\": \"a\", \"port\": 8675}, {\"name\": \"b\", \"port\": 309}]\n\t },\n\t {\n\t Addresses: [{\"ip\": \"10.10.3.3\"}],\n\t Ports: [{\"name\": \"a\", \"port\": 93}, {\"name\": \"b\", \"port\": 76}]\n\t },\n\t]\n\nEndpoints is a legacy API and does not contain information about all Service features. Use discoveryv1.EndpointSlice for complete information about Service endpoints.\n\nDeprecated: This API is deprecated in v1.33+. Use discoveryv1.EndpointSlice.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", + }, + }, + "metadata": { + SchemaProps: spec.SchemaProps{ + Description: "Standard object's metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata", + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"), + }, + }, + "subsets": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "The set of all endpoints is the union of all subsets. Addresses are placed into subsets according to the IPs they share. A single address with multiple ports, some of which are ready and some of which are not (because they come from different containers) will result in the address being displayed in different subsets for the different ports. No address will appear in both Addresses and NotReadyAddresses in the same subset. Sets of addresses and ports that comprise a service.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/api/core/v1.EndpointSubset"), + }, + }, + }, + }, + }, + }, + }, + }, + Dependencies: []string{ + "k8s.io/api/core/v1.EndpointSubset", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"}, + } +} + +func schema_k8sio_api_core_v1_EndpointsList(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "EndpointsList is a list of endpoints. Deprecated: This API is deprecated in v1.33+.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", + }, + }, + "metadata": { + SchemaProps: spec.SchemaProps{ + Description: "Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"), + }, + }, + "items": { + SchemaProps: spec.SchemaProps{ + Description: "List of endpoints.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/api/core/v1.Endpoints"), + }, + }, + }, + }, + }, + }, + Required: []string{"items"}, + }, + }, + Dependencies: []string{ + "k8s.io/api/core/v1.Endpoints", "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"}, + } +} + +func schema_k8sio_api_core_v1_EnvFromSource(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "EnvFromSource represents the source of a set of ConfigMaps or Secrets", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "prefix": { + SchemaProps: spec.SchemaProps{ + Description: "Optional text to prepend to the name of each environment variable. May consist of any printable ASCII characters except '='.", + Type: []string{"string"}, + Format: "", + }, + }, + "configMapRef": { + SchemaProps: spec.SchemaProps{ + Description: "The ConfigMap to select from", + Ref: ref("k8s.io/api/core/v1.ConfigMapEnvSource"), + }, + }, + "secretRef": { + SchemaProps: spec.SchemaProps{ + Description: "The Secret to select from", + Ref: ref("k8s.io/api/core/v1.SecretEnvSource"), + }, + }, + }, + }, + }, + Dependencies: []string{ + "k8s.io/api/core/v1.ConfigMapEnvSource", "k8s.io/api/core/v1.SecretEnvSource"}, + } +} + +func schema_k8sio_api_core_v1_EnvVar(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "EnvVar represents an environment variable present in a Container.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "name": { + SchemaProps: spec.SchemaProps{ + Description: "Name of the environment variable. May consist of any printable ASCII characters except '='.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "value": { + SchemaProps: spec.SchemaProps{ + Description: "Variable references $(VAR_NAME) are expanded using the previously defined environment variables in the container and any service environment variables. If a variable cannot be resolved, the reference in the input string will be unchanged. Double $$ are reduced to a single $, which allows for escaping the $(VAR_NAME) syntax: i.e. \"$$(VAR_NAME)\" will produce the string literal \"$(VAR_NAME)\". Escaped references will never be expanded, regardless of whether the variable exists or not. Defaults to \"\".", + Type: []string{"string"}, + Format: "", + }, + }, + "valueFrom": { + SchemaProps: spec.SchemaProps{ + Description: "Source for the environment variable's value. Cannot be used if value is not empty.", + Ref: ref("k8s.io/api/core/v1.EnvVarSource"), + }, + }, + }, + Required: []string{"name"}, + }, + }, + Dependencies: []string{ + "k8s.io/api/core/v1.EnvVarSource"}, + } +} + +func schema_k8sio_api_core_v1_EnvVarSource(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "EnvVarSource represents a source for the value of an EnvVar.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "fieldRef": { + SchemaProps: spec.SchemaProps{ + Description: "Selects a field of the pod: supports metadata.name, metadata.namespace, `metadata.labels['']`, `metadata.annotations['']`, spec.nodeName, spec.serviceAccountName, status.hostIP, status.podIP, status.podIPs.", + Ref: ref("k8s.io/api/core/v1.ObjectFieldSelector"), + }, + }, + "resourceFieldRef": { + SchemaProps: spec.SchemaProps{ + Description: "Selects a resource of the container: only resources limits and requests (limits.cpu, limits.memory, limits.ephemeral-storage, requests.cpu, requests.memory and requests.ephemeral-storage) are currently supported.", + Ref: ref("k8s.io/api/core/v1.ResourceFieldSelector"), + }, + }, + "configMapKeyRef": { + SchemaProps: spec.SchemaProps{ + Description: "Selects a key of a ConfigMap.", + Ref: ref("k8s.io/api/core/v1.ConfigMapKeySelector"), + }, + }, + "secretKeyRef": { + SchemaProps: spec.SchemaProps{ + Description: "Selects a key of a secret in the pod's namespace", + Ref: ref("k8s.io/api/core/v1.SecretKeySelector"), + }, + }, + "fileKeyRef": { + SchemaProps: spec.SchemaProps{ + Description: "FileKeyRef selects a key of the env file. Requires the EnvFiles feature gate to be enabled.", + Ref: ref("k8s.io/api/core/v1.FileKeySelector"), + }, + }, + }, + }, + }, + Dependencies: []string{ + "k8s.io/api/core/v1.ConfigMapKeySelector", "k8s.io/api/core/v1.FileKeySelector", "k8s.io/api/core/v1.ObjectFieldSelector", "k8s.io/api/core/v1.ResourceFieldSelector", "k8s.io/api/core/v1.SecretKeySelector"}, + } +} + +func schema_k8sio_api_core_v1_EphemeralContainer(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "An EphemeralContainer is a temporary container that you may add to an existing Pod for user-initiated activities such as debugging. Ephemeral containers have no resource or scheduling guarantees, and they will not be restarted when they exit or when a Pod is removed or restarted. The kubelet may evict a Pod if an ephemeral container causes the Pod to exceed its resource allocation.\n\nTo add an ephemeral container, use the ephemeralcontainers subresource of an existing Pod. Ephemeral containers may not be removed or restarted.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "name": { + SchemaProps: spec.SchemaProps{ + Description: "Name of the ephemeral container specified as a DNS_LABEL. This name must be unique among all containers, init containers and ephemeral containers.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "image": { + SchemaProps: spec.SchemaProps{ + Description: "Container image name. More info: https://kubernetes.io/docs/concepts/containers/images", + Type: []string{"string"}, + Format: "", + }, + }, + "command": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "Entrypoint array. Not executed within a shell. The image's ENTRYPOINT is used if this is not provided. Variable references $(VAR_NAME) are expanded using the container's environment. If a variable cannot be resolved, the reference in the input string will be unchanged. Double $$ are reduced to a single $, which allows for escaping the $(VAR_NAME) syntax: i.e. \"$$(VAR_NAME)\" will produce the string literal \"$(VAR_NAME)\". Escaped references will never be expanded, regardless of whether the variable exists or not. Cannot be updated. More info: https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + "args": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "Arguments to the entrypoint. The image's CMD is used if this is not provided. Variable references $(VAR_NAME) are expanded using the container's environment. If a variable cannot be resolved, the reference in the input string will be unchanged. Double $$ are reduced to a single $, which allows for escaping the $(VAR_NAME) syntax: i.e. \"$$(VAR_NAME)\" will produce the string literal \"$(VAR_NAME)\". Escaped references will never be expanded, regardless of whether the variable exists or not. Cannot be updated. More info: https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + "workingDir": { + SchemaProps: spec.SchemaProps{ + Description: "Container's working directory. If not specified, the container runtime's default will be used, which might be configured in the container image. Cannot be updated.", + Type: []string{"string"}, + Format: "", + }, + }, + "ports": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-map-keys": []interface{}{ + "containerPort", + "protocol", + }, + "x-kubernetes-list-type": "map", + "x-kubernetes-patch-merge-key": "containerPort", + "x-kubernetes-patch-strategy": "merge", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "Ports are not allowed for ephemeral containers.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/api/core/v1.ContainerPort"), + }, + }, + }, + }, + }, + "envFrom": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "List of sources to populate environment variables in the container. The keys defined within a source may consist of any printable ASCII characters except '='. When a key exists in multiple sources, the value associated with the last source will take precedence. Values defined by an Env with a duplicate key will take precedence. Cannot be updated.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/api/core/v1.EnvFromSource"), + }, + }, + }, + }, + }, + "env": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-map-keys": []interface{}{ + "name", + }, + "x-kubernetes-list-type": "map", + "x-kubernetes-patch-merge-key": "name", + "x-kubernetes-patch-strategy": "merge", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "List of environment variables to set in the container. Cannot be updated.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/api/core/v1.EnvVar"), + }, + }, + }, + }, + }, + "resources": { + SchemaProps: spec.SchemaProps{ + Description: "Resources are not allowed for ephemeral containers. Ephemeral containers use spare resources already allocated to the pod.", + Default: map[string]interface{}{}, + Ref: ref("k8s.io/api/core/v1.ResourceRequirements"), + }, + }, + "resizePolicy": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "Resources resize policy for the container.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/api/core/v1.ContainerResizePolicy"), + }, + }, + }, + }, + }, + "restartPolicy": { + SchemaProps: spec.SchemaProps{ + Description: "Restart policy for the container to manage the restart behavior of each container within a pod. You cannot set this field on ephemeral containers.", + Type: []string{"string"}, + Format: "", + }, + }, + "restartPolicyRules": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "Represents a list of rules to be checked to determine if the container should be restarted on exit. You cannot set this field on ephemeral containers.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/api/core/v1.ContainerRestartRule"), + }, + }, + }, + }, + }, + "volumeMounts": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-map-keys": []interface{}{ + "mountPath", + }, + "x-kubernetes-list-type": "map", + "x-kubernetes-patch-merge-key": "mountPath", + "x-kubernetes-patch-strategy": "merge", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "Pod volumes to mount into the container's filesystem. Subpath mounts are not allowed for ephemeral containers. Cannot be updated.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/api/core/v1.VolumeMount"), + }, + }, + }, + }, + }, + "volumeDevices": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-map-keys": []interface{}{ + "devicePath", + }, + "x-kubernetes-list-type": "map", + "x-kubernetes-patch-merge-key": "devicePath", + "x-kubernetes-patch-strategy": "merge", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "volumeDevices is the list of block devices to be used by the container.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/api/core/v1.VolumeDevice"), + }, + }, + }, + }, + }, + "livenessProbe": { + SchemaProps: spec.SchemaProps{ + Description: "Probes are not allowed for ephemeral containers.", + Ref: ref("k8s.io/api/core/v1.Probe"), + }, + }, + "readinessProbe": { + SchemaProps: spec.SchemaProps{ + Description: "Probes are not allowed for ephemeral containers.", + Ref: ref("k8s.io/api/core/v1.Probe"), + }, + }, + "startupProbe": { + SchemaProps: spec.SchemaProps{ + Description: "Probes are not allowed for ephemeral containers.", + Ref: ref("k8s.io/api/core/v1.Probe"), + }, + }, + "lifecycle": { + SchemaProps: spec.SchemaProps{ + Description: "Lifecycle is not allowed for ephemeral containers.", + Ref: ref("k8s.io/api/core/v1.Lifecycle"), + }, + }, + "terminationMessagePath": { + SchemaProps: spec.SchemaProps{ + Description: "Optional: Path at which the file to which the container's termination message will be written is mounted into the container's filesystem. Message written is intended to be brief final status, such as an assertion failure message. Will be truncated by the node if greater than 4096 bytes. The total message length across all containers will be limited to 12kb. Defaults to /dev/termination-log. Cannot be updated.", + Type: []string{"string"}, + Format: "", + }, + }, + "terminationMessagePolicy": { + SchemaProps: spec.SchemaProps{ + Description: "Indicate how the termination message should be populated. File will use the contents of terminationMessagePath to populate the container status message on both success and failure. FallbackToLogsOnError will use the last chunk of container log output if the termination message file is empty and the container exited with an error. The log output is limited to 2048 bytes or 80 lines, whichever is smaller. Defaults to File. Cannot be updated.\n\nPossible enum values:\n - `\"FallbackToLogsOnError\"` will read the most recent contents of the container logs for the container status message when the container exits with an error and the terminationMessagePath has no contents.\n - `\"File\"` is the default behavior and will set the container status message to the contents of the container's terminationMessagePath when the container exits.", + Type: []string{"string"}, + Format: "", + Enum: []interface{}{"FallbackToLogsOnError", "File"}, + }, + }, + "imagePullPolicy": { + SchemaProps: spec.SchemaProps{ + Description: "Image pull policy. One of Always, Never, IfNotPresent. Defaults to Always if :latest tag is specified, or IfNotPresent otherwise. Cannot be updated. More info: https://kubernetes.io/docs/concepts/containers/images#updating-images\n\nPossible enum values:\n - `\"Always\"` means that kubelet always attempts to pull the latest image. Container will fail If the pull fails.\n - `\"IfNotPresent\"` means that kubelet pulls if the image isn't present on disk. Container will fail if the image isn't present and the pull fails.\n - `\"Never\"` means that kubelet never pulls an image, but only uses a local image. Container will fail if the image isn't present", + Type: []string{"string"}, + Format: "", + Enum: []interface{}{"Always", "IfNotPresent", "Never"}, + }, + }, + "securityContext": { + SchemaProps: spec.SchemaProps{ + Description: "Optional: SecurityContext defines the security options the ephemeral container should be run with. If set, the fields of SecurityContext override the equivalent fields of PodSecurityContext.", + Ref: ref("k8s.io/api/core/v1.SecurityContext"), + }, + }, + "stdin": { + SchemaProps: spec.SchemaProps{ + Description: "Whether this container should allocate a buffer for stdin in the container runtime. If this is not set, reads from stdin in the container will always result in EOF. Default is false.", + Type: []string{"boolean"}, + Format: "", + }, + }, + "stdinOnce": { + SchemaProps: spec.SchemaProps{ + Description: "Whether the container runtime should close the stdin channel after it has been opened by a single attach. When stdin is true the stdin stream will remain open across multiple attach sessions. If stdinOnce is set to true, stdin is opened on container start, is empty until the first client attaches to stdin, and then remains open and accepts data until the client disconnects, at which time stdin is closed and remains closed until the container is restarted. If this flag is false, a container processes that reads from stdin will never receive an EOF. Default is false", + Type: []string{"boolean"}, + Format: "", + }, + }, + "tty": { + SchemaProps: spec.SchemaProps{ + Description: "Whether this container should allocate a TTY for itself, also requires 'stdin' to be true. Default is false.", + Type: []string{"boolean"}, + Format: "", + }, + }, + "targetContainerName": { + SchemaProps: spec.SchemaProps{ + Description: "If set, the name of the container from PodSpec that this ephemeral container targets. The ephemeral container will be run in the namespaces (IPC, PID, etc) of this container. If not set then the ephemeral container uses the namespaces configured in the Pod spec.\n\nThe container runtime must implement support for this feature. If the runtime does not support namespace targeting then the result of setting this field is undefined.", + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"name"}, + }, + }, + Dependencies: []string{ + "k8s.io/api/core/v1.ContainerPort", "k8s.io/api/core/v1.ContainerResizePolicy", "k8s.io/api/core/v1.ContainerRestartRule", "k8s.io/api/core/v1.EnvFromSource", "k8s.io/api/core/v1.EnvVar", "k8s.io/api/core/v1.Lifecycle", "k8s.io/api/core/v1.Probe", "k8s.io/api/core/v1.ResourceRequirements", "k8s.io/api/core/v1.SecurityContext", "k8s.io/api/core/v1.VolumeDevice", "k8s.io/api/core/v1.VolumeMount"}, + } +} + +func schema_k8sio_api_core_v1_EphemeralContainerCommon(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "EphemeralContainerCommon is a copy of all fields in Container to be inlined in EphemeralContainer. This separate type allows easy conversion from EphemeralContainer to Container and allows separate documentation for the fields of EphemeralContainer. When a new field is added to Container it must be added here as well.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "name": { + SchemaProps: spec.SchemaProps{ + Description: "Name of the ephemeral container specified as a DNS_LABEL. This name must be unique among all containers, init containers and ephemeral containers.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "image": { + SchemaProps: spec.SchemaProps{ + Description: "Container image name. More info: https://kubernetes.io/docs/concepts/containers/images", + Type: []string{"string"}, + Format: "", + }, + }, + "command": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "Entrypoint array. Not executed within a shell. The image's ENTRYPOINT is used if this is not provided. Variable references $(VAR_NAME) are expanded using the container's environment. If a variable cannot be resolved, the reference in the input string will be unchanged. Double $$ are reduced to a single $, which allows for escaping the $(VAR_NAME) syntax: i.e. \"$$(VAR_NAME)\" will produce the string literal \"$(VAR_NAME)\". Escaped references will never be expanded, regardless of whether the variable exists or not. Cannot be updated. More info: https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + "args": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "Arguments to the entrypoint. The image's CMD is used if this is not provided. Variable references $(VAR_NAME) are expanded using the container's environment. If a variable cannot be resolved, the reference in the input string will be unchanged. Double $$ are reduced to a single $, which allows for escaping the $(VAR_NAME) syntax: i.e. \"$$(VAR_NAME)\" will produce the string literal \"$(VAR_NAME)\". Escaped references will never be expanded, regardless of whether the variable exists or not. Cannot be updated. More info: https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + "workingDir": { + SchemaProps: spec.SchemaProps{ + Description: "Container's working directory. If not specified, the container runtime's default will be used, which might be configured in the container image. Cannot be updated.", + Type: []string{"string"}, + Format: "", + }, + }, + "ports": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-map-keys": []interface{}{ + "containerPort", + "protocol", + }, + "x-kubernetes-list-type": "map", + "x-kubernetes-patch-merge-key": "containerPort", + "x-kubernetes-patch-strategy": "merge", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "Ports are not allowed for ephemeral containers.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/api/core/v1.ContainerPort"), + }, + }, + }, + }, + }, + "envFrom": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "List of sources to populate environment variables in the container. The keys defined within a source may consist of any printable ASCII characters except '='. When a key exists in multiple sources, the value associated with the last source will take precedence. Values defined by an Env with a duplicate key will take precedence. Cannot be updated.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/api/core/v1.EnvFromSource"), + }, + }, + }, + }, + }, + "env": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-map-keys": []interface{}{ + "name", + }, + "x-kubernetes-list-type": "map", + "x-kubernetes-patch-merge-key": "name", + "x-kubernetes-patch-strategy": "merge", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "List of environment variables to set in the container. Cannot be updated.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/api/core/v1.EnvVar"), + }, + }, + }, + }, + }, + "resources": { + SchemaProps: spec.SchemaProps{ + Description: "Resources are not allowed for ephemeral containers. Ephemeral containers use spare resources already allocated to the pod.", + Default: map[string]interface{}{}, + Ref: ref("k8s.io/api/core/v1.ResourceRequirements"), + }, + }, + "resizePolicy": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "Resources resize policy for the container.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/api/core/v1.ContainerResizePolicy"), + }, + }, + }, + }, + }, + "restartPolicy": { + SchemaProps: spec.SchemaProps{ + Description: "Restart policy for the container to manage the restart behavior of each container within a pod. You cannot set this field on ephemeral containers.", + Type: []string{"string"}, + Format: "", + }, + }, + "restartPolicyRules": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "Represents a list of rules to be checked to determine if the container should be restarted on exit. You cannot set this field on ephemeral containers.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/api/core/v1.ContainerRestartRule"), + }, + }, + }, + }, + }, + "volumeMounts": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-map-keys": []interface{}{ + "mountPath", + }, + "x-kubernetes-list-type": "map", + "x-kubernetes-patch-merge-key": "mountPath", + "x-kubernetes-patch-strategy": "merge", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "Pod volumes to mount into the container's filesystem. Subpath mounts are not allowed for ephemeral containers. Cannot be updated.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/api/core/v1.VolumeMount"), + }, + }, + }, + }, + }, + "volumeDevices": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-map-keys": []interface{}{ + "devicePath", + }, + "x-kubernetes-list-type": "map", + "x-kubernetes-patch-merge-key": "devicePath", + "x-kubernetes-patch-strategy": "merge", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "volumeDevices is the list of block devices to be used by the container.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/api/core/v1.VolumeDevice"), + }, + }, + }, + }, + }, + "livenessProbe": { + SchemaProps: spec.SchemaProps{ + Description: "Probes are not allowed for ephemeral containers.", + Ref: ref("k8s.io/api/core/v1.Probe"), + }, + }, + "readinessProbe": { + SchemaProps: spec.SchemaProps{ + Description: "Probes are not allowed for ephemeral containers.", + Ref: ref("k8s.io/api/core/v1.Probe"), + }, + }, + "startupProbe": { + SchemaProps: spec.SchemaProps{ + Description: "Probes are not allowed for ephemeral containers.", + Ref: ref("k8s.io/api/core/v1.Probe"), + }, + }, + "lifecycle": { + SchemaProps: spec.SchemaProps{ + Description: "Lifecycle is not allowed for ephemeral containers.", + Ref: ref("k8s.io/api/core/v1.Lifecycle"), + }, + }, + "terminationMessagePath": { + SchemaProps: spec.SchemaProps{ + Description: "Optional: Path at which the file to which the container's termination message will be written is mounted into the container's filesystem. Message written is intended to be brief final status, such as an assertion failure message. Will be truncated by the node if greater than 4096 bytes. The total message length across all containers will be limited to 12kb. Defaults to /dev/termination-log. Cannot be updated.", + Type: []string{"string"}, + Format: "", + }, + }, + "terminationMessagePolicy": { + SchemaProps: spec.SchemaProps{ + Description: "Indicate how the termination message should be populated. File will use the contents of terminationMessagePath to populate the container status message on both success and failure. FallbackToLogsOnError will use the last chunk of container log output if the termination message file is empty and the container exited with an error. The log output is limited to 2048 bytes or 80 lines, whichever is smaller. Defaults to File. Cannot be updated.\n\nPossible enum values:\n - `\"FallbackToLogsOnError\"` will read the most recent contents of the container logs for the container status message when the container exits with an error and the terminationMessagePath has no contents.\n - `\"File\"` is the default behavior and will set the container status message to the contents of the container's terminationMessagePath when the container exits.", + Type: []string{"string"}, + Format: "", + Enum: []interface{}{"FallbackToLogsOnError", "File"}, + }, + }, + "imagePullPolicy": { + SchemaProps: spec.SchemaProps{ + Description: "Image pull policy. One of Always, Never, IfNotPresent. Defaults to Always if :latest tag is specified, or IfNotPresent otherwise. Cannot be updated. More info: https://kubernetes.io/docs/concepts/containers/images#updating-images\n\nPossible enum values:\n - `\"Always\"` means that kubelet always attempts to pull the latest image. Container will fail If the pull fails.\n - `\"IfNotPresent\"` means that kubelet pulls if the image isn't present on disk. Container will fail if the image isn't present and the pull fails.\n - `\"Never\"` means that kubelet never pulls an image, but only uses a local image. Container will fail if the image isn't present", + Type: []string{"string"}, + Format: "", + Enum: []interface{}{"Always", "IfNotPresent", "Never"}, + }, + }, + "securityContext": { + SchemaProps: spec.SchemaProps{ + Description: "Optional: SecurityContext defines the security options the ephemeral container should be run with. If set, the fields of SecurityContext override the equivalent fields of PodSecurityContext.", + Ref: ref("k8s.io/api/core/v1.SecurityContext"), + }, + }, + "stdin": { + SchemaProps: spec.SchemaProps{ + Description: "Whether this container should allocate a buffer for stdin in the container runtime. If this is not set, reads from stdin in the container will always result in EOF. Default is false.", + Type: []string{"boolean"}, + Format: "", + }, + }, + "stdinOnce": { + SchemaProps: spec.SchemaProps{ + Description: "Whether the container runtime should close the stdin channel after it has been opened by a single attach. When stdin is true the stdin stream will remain open across multiple attach sessions. If stdinOnce is set to true, stdin is opened on container start, is empty until the first client attaches to stdin, and then remains open and accepts data until the client disconnects, at which time stdin is closed and remains closed until the container is restarted. If this flag is false, a container processes that reads from stdin will never receive an EOF. Default is false", + Type: []string{"boolean"}, + Format: "", + }, + }, + "tty": { + SchemaProps: spec.SchemaProps{ + Description: "Whether this container should allocate a TTY for itself, also requires 'stdin' to be true. Default is false.", + Type: []string{"boolean"}, + Format: "", + }, + }, + }, + Required: []string{"name"}, + }, + }, + Dependencies: []string{ + "k8s.io/api/core/v1.ContainerPort", "k8s.io/api/core/v1.ContainerResizePolicy", "k8s.io/api/core/v1.ContainerRestartRule", "k8s.io/api/core/v1.EnvFromSource", "k8s.io/api/core/v1.EnvVar", "k8s.io/api/core/v1.Lifecycle", "k8s.io/api/core/v1.Probe", "k8s.io/api/core/v1.ResourceRequirements", "k8s.io/api/core/v1.SecurityContext", "k8s.io/api/core/v1.VolumeDevice", "k8s.io/api/core/v1.VolumeMount"}, + } +} + +func schema_k8sio_api_core_v1_EphemeralVolumeSource(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "Represents an ephemeral volume that is handled by a normal storage driver.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "volumeClaimTemplate": { + SchemaProps: spec.SchemaProps{ + Description: "Will be used to create a stand-alone PVC to provision the volume. The pod in which this EphemeralVolumeSource is embedded will be the owner of the PVC, i.e. the PVC will be deleted together with the pod. The name of the PVC will be `-` where `` is the name from the `PodSpec.Volumes` array entry. Pod validation will reject the pod if the concatenated name is not valid for a PVC (for example, too long).\n\nAn existing PVC with that name that is not owned by the pod will *not* be used for the pod to avoid using an unrelated volume by mistake. Starting the pod is then blocked until the unrelated PVC is removed. If such a pre-created PVC is meant to be used by the pod, the PVC has to updated with an owner reference to the pod once the pod exists. Normally this should not be necessary, but it may be useful when manually reconstructing a broken cluster.\n\nThis field is read-only and no changes will be made by Kubernetes to the PVC after it has been created.\n\nRequired, must not be nil.", + Ref: ref("k8s.io/api/core/v1.PersistentVolumeClaimTemplate"), + }, + }, + }, + }, + }, + Dependencies: []string{ + "k8s.io/api/core/v1.PersistentVolumeClaimTemplate"}, + } +} + +func schema_k8sio_api_core_v1_Event(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "Event is a report of an event somewhere in the cluster. Events have a limited retention time and triggers and messages may evolve with time. Event consumers should not rely on the timing of an event with a given Reason reflecting a consistent underlying trigger, or the continued existence of events with that Reason. Events should be treated as informative, best-effort, supplemental data.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", + }, + }, + "metadata": { + SchemaProps: spec.SchemaProps{ + Description: "Standard object's metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata", + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"), + }, + }, + "involvedObject": { + SchemaProps: spec.SchemaProps{ + Description: "The object that this event is about.", + Default: map[string]interface{}{}, + Ref: ref("k8s.io/api/core/v1.ObjectReference"), + }, + }, + "reason": { + SchemaProps: spec.SchemaProps{ + Description: "This should be a short, machine understandable string that gives the reason for the transition into the object's current status.", + Type: []string{"string"}, + Format: "", + }, + }, + "message": { + SchemaProps: spec.SchemaProps{ + Description: "A human-readable description of the status of this operation.", + Type: []string{"string"}, + Format: "", + }, + }, + "source": { + SchemaProps: spec.SchemaProps{ + Description: "The component reporting this event. Should be a short machine understandable string.", + Default: map[string]interface{}{}, + Ref: ref("k8s.io/api/core/v1.EventSource"), + }, + }, + "firstTimestamp": { + SchemaProps: spec.SchemaProps{ + Description: "The time at which the event was first recorded. (Time of server receipt is in TypeMeta.)", + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.Time"), + }, + }, + "lastTimestamp": { + SchemaProps: spec.SchemaProps{ + Description: "The time at which the most recent occurrence of this event was recorded.", + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.Time"), + }, + }, + "count": { + SchemaProps: spec.SchemaProps{ + Description: "The number of times this event has occurred.", + Type: []string{"integer"}, + Format: "int32", + }, + }, + "type": { + SchemaProps: spec.SchemaProps{ + Description: "Type of this event (Normal, Warning), new types could be added in the future", + Type: []string{"string"}, + Format: "", + }, + }, + "eventTime": { + SchemaProps: spec.SchemaProps{ + Description: "Time when this Event was first observed.", + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.MicroTime"), + }, + }, + "series": { + SchemaProps: spec.SchemaProps{ + Description: "Data about the Event series this event represents or nil if it's a singleton Event.", + Ref: ref("k8s.io/api/core/v1.EventSeries"), + }, + }, + "action": { + SchemaProps: spec.SchemaProps{ + Description: "What action was taken/failed regarding to the Regarding object.", + Type: []string{"string"}, + Format: "", + }, + }, + "related": { + SchemaProps: spec.SchemaProps{ + Description: "Optional secondary object for more complex actions.", + Ref: ref("k8s.io/api/core/v1.ObjectReference"), + }, + }, + "reportingComponent": { + SchemaProps: spec.SchemaProps{ + Description: "Name of the controller that emitted this Event, e.g. `kubernetes.io/kubelet`.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "reportingInstance": { + SchemaProps: spec.SchemaProps{ + Description: "ID of the controller instance, e.g. `kubelet-xyzf`.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"metadata", "involvedObject"}, + }, + }, + Dependencies: []string{ + "k8s.io/api/core/v1.EventSeries", "k8s.io/api/core/v1.EventSource", "k8s.io/api/core/v1.ObjectReference", "k8s.io/apimachinery/pkg/apis/meta/v1.MicroTime", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta", "k8s.io/apimachinery/pkg/apis/meta/v1.Time"}, + } +} + +func schema_k8sio_api_core_v1_EventList(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "EventList is a list of events.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", + }, + }, + "metadata": { + SchemaProps: spec.SchemaProps{ + Description: "Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"), + }, + }, + "items": { + SchemaProps: spec.SchemaProps{ + Description: "List of events", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/api/core/v1.Event"), + }, + }, + }, + }, + }, + }, + Required: []string{"items"}, + }, + }, + Dependencies: []string{ + "k8s.io/api/core/v1.Event", "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"}, + } +} + +func schema_k8sio_api_core_v1_EventSeries(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "EventSeries contain information on series of events, i.e. thing that was/is happening continuously for some time.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "count": { + SchemaProps: spec.SchemaProps{ + Description: "Number of occurrences in this series up to the last heartbeat time", + Type: []string{"integer"}, + Format: "int32", + }, + }, + "lastObservedTime": { + SchemaProps: spec.SchemaProps{ + Description: "Time of the last occurrence observed", + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.MicroTime"), + }, + }, + }, + }, + }, + Dependencies: []string{ + "k8s.io/apimachinery/pkg/apis/meta/v1.MicroTime"}, + } +} + +func schema_k8sio_api_core_v1_EventSource(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "EventSource contains information for an event.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "component": { + SchemaProps: spec.SchemaProps{ + Description: "Component from which the event is generated.", + Type: []string{"string"}, + Format: "", + }, + }, + "host": { + SchemaProps: spec.SchemaProps{ + Description: "Node name on which the event is generated.", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + } +} + +func schema_k8sio_api_core_v1_ExecAction(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "ExecAction describes a \"run in container\" action.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "command": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "Command is the command line to execute inside the container, the working directory for the command is root ('/') in the container's filesystem. The command is simply exec'd, it is not run inside a shell, so traditional shell instructions ('|', etc) won't work. To use a shell, you need to explicitly call out to that shell. Exit status of 0 is treated as live/healthy and non-zero is unhealthy.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + }, + }, + }, + } +} + +func schema_k8sio_api_core_v1_FCVolumeSource(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "Represents a Fibre Channel volume. Fibre Channel volumes can only be mounted as read/write once. Fibre Channel volumes support ownership management and SELinux relabeling.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "targetWWNs": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "targetWWNs is Optional: FC target worldwide names (WWNs)", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + "lun": { + SchemaProps: spec.SchemaProps{ + Description: "lun is Optional: FC target lun number", + Type: []string{"integer"}, + Format: "int32", + }, + }, + "fsType": { + SchemaProps: spec.SchemaProps{ + Description: "fsType is the filesystem type to mount. Must be a filesystem type supported by the host operating system. Ex. \"ext4\", \"xfs\", \"ntfs\". Implicitly inferred to be \"ext4\" if unspecified.", + Type: []string{"string"}, + Format: "", + }, + }, + "readOnly": { + SchemaProps: spec.SchemaProps{ + Description: "readOnly is Optional: Defaults to false (read/write). ReadOnly here will force the ReadOnly setting in VolumeMounts.", + Type: []string{"boolean"}, + Format: "", + }, + }, + "wwids": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "wwids Optional: FC volume world wide identifiers (wwids) Either wwids or combination of targetWWNs and lun must be set, but not both simultaneously.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + }, + }, + }, + } +} + +func schema_k8sio_api_core_v1_FileKeySelector(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "FileKeySelector selects a key of the env file.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "volumeName": { + SchemaProps: spec.SchemaProps{ + Description: "The name of the volume mount containing the env file.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "path": { + SchemaProps: spec.SchemaProps{ + Description: "The path within the volume from which to select the file. Must be relative and may not contain the '..' path or start with '..'.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "key": { + SchemaProps: spec.SchemaProps{ + Description: "The key within the env file. An invalid key will prevent the pod from starting. The keys defined within a source may consist of any printable ASCII characters except '='. During Alpha stage of the EnvFiles feature gate, the key size is limited to 128 characters.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "optional": { + SchemaProps: spec.SchemaProps{ + Description: "Specify whether the file or its key must be defined. If the file or key does not exist, then the env var is not published. If optional is set to true and the specified key does not exist, the environment variable will not be set in the Pod's containers.\n\nIf optional is set to false and the specified key does not exist, an error will be returned during Pod creation.", + Default: false, + Type: []string{"boolean"}, + Format: "", + }, + }, + }, + Required: []string{"volumeName", "path", "key"}, + }, + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-map-type": "atomic", + }, + }, + }, + } +} + +func schema_k8sio_api_core_v1_FlexPersistentVolumeSource(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "FlexPersistentVolumeSource represents a generic persistent volume resource that is provisioned/attached using an exec based plugin.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "driver": { + SchemaProps: spec.SchemaProps{ + Description: "driver is the name of the driver to use for this volume.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "fsType": { + SchemaProps: spec.SchemaProps{ + Description: "fsType is the Filesystem type to mount. Must be a filesystem type supported by the host operating system. Ex. \"ext4\", \"xfs\", \"ntfs\". The default filesystem depends on FlexVolume script.", + Type: []string{"string"}, + Format: "", + }, + }, + "secretRef": { + SchemaProps: spec.SchemaProps{ + Description: "secretRef is Optional: SecretRef is reference to the secret object containing sensitive information to pass to the plugin scripts. This may be empty if no secret object is specified. If the secret object contains more than one secret, all secrets are passed to the plugin scripts.", + Ref: ref("k8s.io/api/core/v1.SecretReference"), + }, + }, + "readOnly": { + SchemaProps: spec.SchemaProps{ + Description: "readOnly is Optional: defaults to false (read/write). ReadOnly here will force the ReadOnly setting in VolumeMounts.", + Type: []string{"boolean"}, + Format: "", + }, + }, + "options": { + SchemaProps: spec.SchemaProps{ + Description: "options is Optional: this field holds extra command options if any.", + Type: []string{"object"}, + AdditionalProperties: &spec.SchemaOrBool{ + Allows: true, + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + }, + Required: []string{"driver"}, + }, + }, + Dependencies: []string{ + "k8s.io/api/core/v1.SecretReference"}, + } +} + +func schema_k8sio_api_core_v1_FlexVolumeSource(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "FlexVolume represents a generic volume resource that is provisioned/attached using an exec based plugin.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "driver": { + SchemaProps: spec.SchemaProps{ + Description: "driver is the name of the driver to use for this volume.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "fsType": { + SchemaProps: spec.SchemaProps{ + Description: "fsType is the filesystem type to mount. Must be a filesystem type supported by the host operating system. Ex. \"ext4\", \"xfs\", \"ntfs\". The default filesystem depends on FlexVolume script.", + Type: []string{"string"}, + Format: "", + }, + }, + "secretRef": { + SchemaProps: spec.SchemaProps{ + Description: "secretRef is Optional: secretRef is reference to the secret object containing sensitive information to pass to the plugin scripts. This may be empty if no secret object is specified. If the secret object contains more than one secret, all secrets are passed to the plugin scripts.", + Ref: ref("k8s.io/api/core/v1.LocalObjectReference"), + }, + }, + "readOnly": { + SchemaProps: spec.SchemaProps{ + Description: "readOnly is Optional: defaults to false (read/write). ReadOnly here will force the ReadOnly setting in VolumeMounts.", + Type: []string{"boolean"}, + Format: "", + }, + }, + "options": { + SchemaProps: spec.SchemaProps{ + Description: "options is Optional: this field holds extra command options if any.", + Type: []string{"object"}, + AdditionalProperties: &spec.SchemaOrBool{ + Allows: true, + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + }, + Required: []string{"driver"}, + }, + }, + Dependencies: []string{ + "k8s.io/api/core/v1.LocalObjectReference"}, + } +} + +func schema_k8sio_api_core_v1_FlockerVolumeSource(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "Represents a Flocker volume mounted by the Flocker agent. One and only one of datasetName and datasetUUID should be set. Flocker volumes do not support ownership management or SELinux relabeling.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "datasetName": { + SchemaProps: spec.SchemaProps{ + Description: "datasetName is Name of the dataset stored as metadata -> name on the dataset for Flocker should be considered as deprecated", + Type: []string{"string"}, + Format: "", + }, + }, + "datasetUUID": { + SchemaProps: spec.SchemaProps{ + Description: "datasetUUID is the UUID of the dataset. This is unique identifier of a Flocker dataset", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + } +} + +func schema_k8sio_api_core_v1_GCEPersistentDiskVolumeSource(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "Represents a Persistent Disk resource in Google Compute Engine.\n\nA GCE PD must exist before mounting to a container. The disk must also be in the same GCE project and zone as the kubelet. A GCE PD can only be mounted as read/write once or read-only many times. GCE PDs support ownership management and SELinux relabeling.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "pdName": { + SchemaProps: spec.SchemaProps{ + Description: "pdName is unique name of the PD resource in GCE. Used to identify the disk in GCE. More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "fsType": { + SchemaProps: spec.SchemaProps{ + Description: "fsType is filesystem type of the volume that you want to mount. Tip: Ensure that the filesystem type is supported by the host operating system. Examples: \"ext4\", \"xfs\", \"ntfs\". Implicitly inferred to be \"ext4\" if unspecified. More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk", + Type: []string{"string"}, + Format: "", + }, + }, + "partition": { + SchemaProps: spec.SchemaProps{ + Description: "partition is the partition in the volume that you want to mount. If omitted, the default is to mount by volume name. Examples: For volume /dev/sda1, you specify the partition as \"1\". Similarly, the volume partition for /dev/sda is \"0\" (or you can leave the property empty). More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk", + Type: []string{"integer"}, + Format: "int32", + }, + }, + "readOnly": { + SchemaProps: spec.SchemaProps{ + Description: "readOnly here will force the ReadOnly setting in VolumeMounts. Defaults to false. More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk", + Type: []string{"boolean"}, + Format: "", + }, + }, + }, + Required: []string{"pdName"}, + }, + }, + } +} + +func schema_k8sio_api_core_v1_GRPCAction(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "GRPCAction specifies an action involving a GRPC service.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "port": { + SchemaProps: spec.SchemaProps{ + Description: "Port number of the gRPC service. Number must be in the range 1 to 65535.", + Default: 0, + Type: []string{"integer"}, + Format: "int32", + }, + }, + "service": { + SchemaProps: spec.SchemaProps{ + Description: "Service is the name of the service to place in the gRPC HealthCheckRequest (see https://github.com/grpc/grpc/blob/master/doc/health-checking.md).\n\nIf this is not specified, the default behavior is defined by gRPC.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"port"}, + }, + }, + } +} + +func schema_k8sio_api_core_v1_GitRepoVolumeSource(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "Represents a volume that is populated with the contents of a git repository. Git repo volumes do not support ownership management. Git repo volumes support SELinux relabeling.\n\nDEPRECATED: GitRepo is deprecated. To provision a container with a git repo, mount an EmptyDir into an InitContainer that clones the repo using git, then mount the EmptyDir into the Pod's container.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "repository": { + SchemaProps: spec.SchemaProps{ + Description: "repository is the URL", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "revision": { + SchemaProps: spec.SchemaProps{ + Description: "revision is the commit hash for the specified revision.", + Type: []string{"string"}, + Format: "", + }, + }, + "directory": { + SchemaProps: spec.SchemaProps{ + Description: "directory is the target directory name. Must not contain or start with '..'. If '.' is supplied, the volume directory will be the git repository. Otherwise, if specified, the volume will contain the git repository in the subdirectory with the given name.", + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"repository"}, + }, + }, + } +} + +func schema_k8sio_api_core_v1_GlusterfsPersistentVolumeSource(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "Represents a Glusterfs mount that lasts the lifetime of a pod. Glusterfs volumes do not support ownership management or SELinux relabeling.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "endpoints": { + SchemaProps: spec.SchemaProps{ + Description: "endpoints is the endpoint name that details Glusterfs topology. More info: https://examples.k8s.io/volumes/glusterfs/README.md#create-a-pod", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "path": { + SchemaProps: spec.SchemaProps{ + Description: "path is the Glusterfs volume path. More info: https://examples.k8s.io/volumes/glusterfs/README.md#create-a-pod", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "readOnly": { + SchemaProps: spec.SchemaProps{ + Description: "readOnly here will force the Glusterfs volume to be mounted with read-only permissions. Defaults to false. More info: https://examples.k8s.io/volumes/glusterfs/README.md#create-a-pod", + Type: []string{"boolean"}, + Format: "", + }, + }, + "endpointsNamespace": { + SchemaProps: spec.SchemaProps{ + Description: "endpointsNamespace is the namespace that contains Glusterfs endpoint. If this field is empty, the EndpointNamespace defaults to the same namespace as the bound PVC. More info: https://examples.k8s.io/volumes/glusterfs/README.md#create-a-pod", + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"endpoints", "path"}, + }, + }, + } +} + +func schema_k8sio_api_core_v1_GlusterfsVolumeSource(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "Represents a Glusterfs mount that lasts the lifetime of a pod. Glusterfs volumes do not support ownership management or SELinux relabeling.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "endpoints": { + SchemaProps: spec.SchemaProps{ + Description: "endpoints is the endpoint name that details Glusterfs topology.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "path": { + SchemaProps: spec.SchemaProps{ + Description: "path is the Glusterfs volume path. More info: https://examples.k8s.io/volumes/glusterfs/README.md#create-a-pod", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "readOnly": { + SchemaProps: spec.SchemaProps{ + Description: "readOnly here will force the Glusterfs volume to be mounted with read-only permissions. Defaults to false. More info: https://examples.k8s.io/volumes/glusterfs/README.md#create-a-pod", + Type: []string{"boolean"}, + Format: "", + }, + }, + }, + Required: []string{"endpoints", "path"}, + }, + }, + } +} + +func schema_k8sio_api_core_v1_HTTPGetAction(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "HTTPGetAction describes an action based on HTTP Get requests.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "path": { + SchemaProps: spec.SchemaProps{ + Description: "Path to access on the HTTP server.", + Type: []string{"string"}, + Format: "", + }, + }, + "port": { + SchemaProps: spec.SchemaProps{ + Description: "Name or number of the port to access on the container. Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME.", + Ref: ref("k8s.io/apimachinery/pkg/util/intstr.IntOrString"), + }, + }, + "host": { + SchemaProps: spec.SchemaProps{ + Description: "Host name to connect to, defaults to the pod IP. You probably want to set \"Host\" in httpHeaders instead.", + Type: []string{"string"}, + Format: "", + }, + }, + "scheme": { + SchemaProps: spec.SchemaProps{ + Description: "Scheme to use for connecting to the host. Defaults to HTTP.\n\nPossible enum values:\n - `\"HTTP\"` means that the scheme used will be http://\n - `\"HTTPS\"` means that the scheme used will be https://", + Type: []string{"string"}, + Format: "", + Enum: []interface{}{"HTTP", "HTTPS"}, + }, + }, + "httpHeaders": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "Custom headers to set in the request. HTTP allows repeated headers.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/api/core/v1.HTTPHeader"), + }, + }, + }, + }, + }, + }, + Required: []string{"port"}, + }, + }, + Dependencies: []string{ + "k8s.io/api/core/v1.HTTPHeader", "k8s.io/apimachinery/pkg/util/intstr.IntOrString"}, + } +} + +func schema_k8sio_api_core_v1_HTTPHeader(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "HTTPHeader describes a custom header to be used in HTTP probes", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "name": { + SchemaProps: spec.SchemaProps{ + Description: "The header field name. This will be canonicalized upon output, so case-variant names will be understood as the same header.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "value": { + SchemaProps: spec.SchemaProps{ + Description: "The header field value", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"name", "value"}, + }, + }, + } +} + +func schema_k8sio_api_core_v1_HostAlias(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "HostAlias holds the mapping between IP and hostnames that will be injected as an entry in the pod's hosts file.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "ip": { + SchemaProps: spec.SchemaProps{ + Description: "IP address of the host file entry.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "hostnames": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "Hostnames for the above IP address.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + }, + Required: []string{"ip"}, + }, + }, + } +} + +func schema_k8sio_api_core_v1_HostIP(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "HostIP represents a single IP address allocated to the host.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "ip": { + SchemaProps: spec.SchemaProps{ + Description: "IP is the IP address assigned to the host", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"ip"}, + }, + }, + } +} + +func schema_k8sio_api_core_v1_HostPathVolumeSource(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "Represents a host path mapped into a pod. Host path volumes do not support ownership management or SELinux relabeling.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "path": { + SchemaProps: spec.SchemaProps{ + Description: "path of the directory on the host. If the path is a symlink, it will follow the link to the real path. More info: https://kubernetes.io/docs/concepts/storage/volumes#hostpath", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "type": { + SchemaProps: spec.SchemaProps{ + Description: "type for HostPath Volume Defaults to \"\" More info: https://kubernetes.io/docs/concepts/storage/volumes#hostpath\n\nPossible enum values:\n - `\"\"` For backwards compatible, leave it empty if unset\n - `\"BlockDevice\"` A block device must exist at the given path\n - `\"CharDevice\"` A character device must exist at the given path\n - `\"Directory\"` A directory must exist at the given path\n - `\"DirectoryOrCreate\"` If nothing exists at the given path, an empty directory will be created there as needed with file mode 0755, having the same group and ownership with Kubelet.\n - `\"File\"` A file must exist at the given path\n - `\"FileOrCreate\"` If nothing exists at the given path, an empty file will be created there as needed with file mode 0644, having the same group and ownership with Kubelet.\n - `\"Socket\"` A UNIX socket must exist at the given path", + Type: []string{"string"}, + Format: "", + Enum: []interface{}{"", "BlockDevice", "CharDevice", "Directory", "DirectoryOrCreate", "File", "FileOrCreate", "Socket"}, + }, + }, + }, + Required: []string{"path"}, + }, + }, + } +} + +func schema_k8sio_api_core_v1_ISCSIPersistentVolumeSource(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "ISCSIPersistentVolumeSource represents an ISCSI disk. ISCSI volumes can only be mounted as read/write once. ISCSI volumes support ownership management and SELinux relabeling.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "targetPortal": { + SchemaProps: spec.SchemaProps{ + Description: "targetPortal is iSCSI Target Portal. The Portal is either an IP or ip_addr:port if the port is other than default (typically TCP ports 860 and 3260).", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "iqn": { + SchemaProps: spec.SchemaProps{ + Description: "iqn is Target iSCSI Qualified Name.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "lun": { + SchemaProps: spec.SchemaProps{ + Description: "lun is iSCSI Target Lun number.", + Default: 0, + Type: []string{"integer"}, + Format: "int32", + }, + }, + "iscsiInterface": { + SchemaProps: spec.SchemaProps{ + Description: "iscsiInterface is the interface Name that uses an iSCSI transport. Defaults to 'default' (tcp).", + Default: "default", + Type: []string{"string"}, + Format: "", + }, + }, + "fsType": { + SchemaProps: spec.SchemaProps{ + Description: "fsType is the filesystem type of the volume that you want to mount. Tip: Ensure that the filesystem type is supported by the host operating system. Examples: \"ext4\", \"xfs\", \"ntfs\". Implicitly inferred to be \"ext4\" if unspecified. More info: https://kubernetes.io/docs/concepts/storage/volumes#iscsi", + Type: []string{"string"}, + Format: "", + }, + }, + "readOnly": { + SchemaProps: spec.SchemaProps{ + Description: "readOnly here will force the ReadOnly setting in VolumeMounts. Defaults to false.", + Type: []string{"boolean"}, + Format: "", + }, + }, + "portals": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "portals is the iSCSI Target Portal List. The Portal is either an IP or ip_addr:port if the port is other than default (typically TCP ports 860 and 3260).", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + "chapAuthDiscovery": { + SchemaProps: spec.SchemaProps{ + Description: "chapAuthDiscovery defines whether support iSCSI Discovery CHAP authentication", + Type: []string{"boolean"}, + Format: "", + }, + }, + "chapAuthSession": { + SchemaProps: spec.SchemaProps{ + Description: "chapAuthSession defines whether support iSCSI Session CHAP authentication", + Type: []string{"boolean"}, + Format: "", + }, + }, + "secretRef": { + SchemaProps: spec.SchemaProps{ + Description: "secretRef is the CHAP Secret for iSCSI target and initiator authentication", + Ref: ref("k8s.io/api/core/v1.SecretReference"), + }, + }, + "initiatorName": { + SchemaProps: spec.SchemaProps{ + Description: "initiatorName is the custom iSCSI Initiator Name. If initiatorName is specified with iscsiInterface simultaneously, new iSCSI interface : will be created for the connection.", + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"targetPortal", "iqn", "lun"}, + }, + }, + Dependencies: []string{ + "k8s.io/api/core/v1.SecretReference"}, + } +} + +func schema_k8sio_api_core_v1_ISCSIVolumeSource(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "Represents an ISCSI disk. ISCSI volumes can only be mounted as read/write once. ISCSI volumes support ownership management and SELinux relabeling.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "targetPortal": { + SchemaProps: spec.SchemaProps{ + Description: "targetPortal is iSCSI Target Portal. The Portal is either an IP or ip_addr:port if the port is other than default (typically TCP ports 860 and 3260).", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "iqn": { + SchemaProps: spec.SchemaProps{ + Description: "iqn is the target iSCSI Qualified Name.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "lun": { + SchemaProps: spec.SchemaProps{ + Description: "lun represents iSCSI Target Lun number.", + Default: 0, + Type: []string{"integer"}, + Format: "int32", + }, + }, + "iscsiInterface": { + SchemaProps: spec.SchemaProps{ + Description: "iscsiInterface is the interface Name that uses an iSCSI transport. Defaults to 'default' (tcp).", + Default: "default", + Type: []string{"string"}, + Format: "", + }, + }, + "fsType": { + SchemaProps: spec.SchemaProps{ + Description: "fsType is the filesystem type of the volume that you want to mount. Tip: Ensure that the filesystem type is supported by the host operating system. Examples: \"ext4\", \"xfs\", \"ntfs\". Implicitly inferred to be \"ext4\" if unspecified. More info: https://kubernetes.io/docs/concepts/storage/volumes#iscsi", + Type: []string{"string"}, + Format: "", + }, + }, + "readOnly": { + SchemaProps: spec.SchemaProps{ + Description: "readOnly here will force the ReadOnly setting in VolumeMounts. Defaults to false.", + Type: []string{"boolean"}, + Format: "", + }, + }, + "portals": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "portals is the iSCSI Target Portal List. The portal is either an IP or ip_addr:port if the port is other than default (typically TCP ports 860 and 3260).", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + "chapAuthDiscovery": { + SchemaProps: spec.SchemaProps{ + Description: "chapAuthDiscovery defines whether support iSCSI Discovery CHAP authentication", + Type: []string{"boolean"}, + Format: "", + }, + }, + "chapAuthSession": { + SchemaProps: spec.SchemaProps{ + Description: "chapAuthSession defines whether support iSCSI Session CHAP authentication", + Type: []string{"boolean"}, + Format: "", + }, + }, + "secretRef": { + SchemaProps: spec.SchemaProps{ + Description: "secretRef is the CHAP Secret for iSCSI target and initiator authentication", + Ref: ref("k8s.io/api/core/v1.LocalObjectReference"), + }, + }, + "initiatorName": { + SchemaProps: spec.SchemaProps{ + Description: "initiatorName is the custom iSCSI Initiator Name. If initiatorName is specified with iscsiInterface simultaneously, new iSCSI interface : will be created for the connection.", + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"targetPortal", "iqn", "lun"}, + }, + }, + Dependencies: []string{ + "k8s.io/api/core/v1.LocalObjectReference"}, + } +} + +func schema_k8sio_api_core_v1_ImageVolumeSource(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "ImageVolumeSource represents a image volume resource.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "reference": { + SchemaProps: spec.SchemaProps{ + Description: "Required: Image or artifact reference to be used. Behaves in the same way as pod.spec.containers[*].image. Pull secrets will be assembled in the same way as for the container image by looking up node credentials, SA image pull secrets, and pod spec image pull secrets. More info: https://kubernetes.io/docs/concepts/containers/images This field is optional to allow higher level config management to default or override container images in workload controllers like Deployments and StatefulSets.", + Type: []string{"string"}, + Format: "", + }, + }, + "pullPolicy": { + SchemaProps: spec.SchemaProps{ + Description: "Policy for pulling OCI objects. Possible values are: Always: the kubelet always attempts to pull the reference. Container creation will fail If the pull fails. Never: the kubelet never pulls the reference and only uses a local image or artifact. Container creation will fail if the reference isn't present. IfNotPresent: the kubelet pulls if the reference isn't already present on disk. Container creation will fail if the reference isn't present and the pull fails. Defaults to Always if :latest tag is specified, or IfNotPresent otherwise.\n\nPossible enum values:\n - `\"Always\"` means that kubelet always attempts to pull the latest image. Container will fail If the pull fails.\n - `\"IfNotPresent\"` means that kubelet pulls if the image isn't present on disk. Container will fail if the image isn't present and the pull fails.\n - `\"Never\"` means that kubelet never pulls an image, but only uses a local image. Container will fail if the image isn't present", + Type: []string{"string"}, + Format: "", + Enum: []interface{}{"Always", "IfNotPresent", "Never"}, + }, + }, + }, + }, + }, + } +} + +func schema_k8sio_api_core_v1_KeyToPath(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "Maps a string key to a path within a volume.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "key": { + SchemaProps: spec.SchemaProps{ + Description: "key is the key to project.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "path": { + SchemaProps: spec.SchemaProps{ + Description: "path is the relative path of the file to map the key to. May not be an absolute path. May not contain the path element '..'. May not start with the string '..'.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "mode": { + SchemaProps: spec.SchemaProps{ + Description: "mode is Optional: mode bits used to set permissions on this file. Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. If not specified, the volume defaultMode will be used. This might be in conflict with other options that affect the file mode, like fsGroup, and the result can be other mode bits set.", + Type: []string{"integer"}, + Format: "int32", + }, + }, + }, + Required: []string{"key", "path"}, + }, + }, + } +} + +func schema_k8sio_api_core_v1_Lifecycle(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "Lifecycle describes actions that the management system should take in response to container lifecycle events. For the PostStart and PreStop lifecycle handlers, management of the container blocks until the action is complete, unless the container process fails, in which case the handler is aborted.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "postStart": { + SchemaProps: spec.SchemaProps{ + Description: "PostStart is called immediately after a container is created. If the handler fails, the container is terminated and restarted according to its restart policy. Other management of the container blocks until the hook completes. More info: https://kubernetes.io/docs/concepts/containers/container-lifecycle-hooks/#container-hooks", + Ref: ref("k8s.io/api/core/v1.LifecycleHandler"), + }, + }, + "preStop": { + SchemaProps: spec.SchemaProps{ + Description: "PreStop is called immediately before a container is terminated due to an API request or management event such as liveness/startup probe failure, preemption, resource contention, etc. The handler is not called if the container crashes or exits. The Pod's termination grace period countdown begins before the PreStop hook is executed. Regardless of the outcome of the handler, the container will eventually terminate within the Pod's termination grace period (unless delayed by finalizers). Other management of the container blocks until the hook completes or until the termination grace period is reached. More info: https://kubernetes.io/docs/concepts/containers/container-lifecycle-hooks/#container-hooks", + Ref: ref("k8s.io/api/core/v1.LifecycleHandler"), + }, + }, + "stopSignal": { + SchemaProps: spec.SchemaProps{ + Description: "StopSignal defines which signal will be sent to a container when it is being stopped. If not specified, the default is defined by the container runtime in use. StopSignal can only be set for Pods with a non-empty .spec.os.name\n\nPossible enum values:\n - `\"SIGABRT\"`\n - `\"SIGALRM\"`\n - `\"SIGBUS\"`\n - `\"SIGCHLD\"`\n - `\"SIGCLD\"`\n - `\"SIGCONT\"`\n - `\"SIGFPE\"`\n - `\"SIGHUP\"`\n - `\"SIGILL\"`\n - `\"SIGINT\"`\n - `\"SIGIO\"`\n - `\"SIGIOT\"`\n - `\"SIGKILL\"`\n - `\"SIGPIPE\"`\n - `\"SIGPOLL\"`\n - `\"SIGPROF\"`\n - `\"SIGPWR\"`\n - `\"SIGQUIT\"`\n - `\"SIGRTMAX\"`\n - `\"SIGRTMAX-1\"`\n - `\"SIGRTMAX-10\"`\n - `\"SIGRTMAX-11\"`\n - `\"SIGRTMAX-12\"`\n - `\"SIGRTMAX-13\"`\n - `\"SIGRTMAX-14\"`\n - `\"SIGRTMAX-2\"`\n - `\"SIGRTMAX-3\"`\n - `\"SIGRTMAX-4\"`\n - `\"SIGRTMAX-5\"`\n - `\"SIGRTMAX-6\"`\n - `\"SIGRTMAX-7\"`\n - `\"SIGRTMAX-8\"`\n - `\"SIGRTMAX-9\"`\n - `\"SIGRTMIN\"`\n - `\"SIGRTMIN+1\"`\n - `\"SIGRTMIN+10\"`\n - `\"SIGRTMIN+11\"`\n - `\"SIGRTMIN+12\"`\n - `\"SIGRTMIN+13\"`\n - `\"SIGRTMIN+14\"`\n - `\"SIGRTMIN+15\"`\n - `\"SIGRTMIN+2\"`\n - `\"SIGRTMIN+3\"`\n - `\"SIGRTMIN+4\"`\n - `\"SIGRTMIN+5\"`\n - `\"SIGRTMIN+6\"`\n - `\"SIGRTMIN+7\"`\n - `\"SIGRTMIN+8\"`\n - `\"SIGRTMIN+9\"`\n - `\"SIGSEGV\"`\n - `\"SIGSTKFLT\"`\n - `\"SIGSTOP\"`\n - `\"SIGSYS\"`\n - `\"SIGTERM\"`\n - `\"SIGTRAP\"`\n - `\"SIGTSTP\"`\n - `\"SIGTTIN\"`\n - `\"SIGTTOU\"`\n - `\"SIGURG\"`\n - `\"SIGUSR1\"`\n - `\"SIGUSR2\"`\n - `\"SIGVTALRM\"`\n - `\"SIGWINCH\"`\n - `\"SIGXCPU\"`\n - `\"SIGXFSZ\"`", + Type: []string{"string"}, + Format: "", + Enum: []interface{}{"SIGABRT", "SIGALRM", "SIGBUS", "SIGCHLD", "SIGCLD", "SIGCONT", "SIGFPE", "SIGHUP", "SIGILL", "SIGINT", "SIGIO", "SIGIOT", "SIGKILL", "SIGPIPE", "SIGPOLL", "SIGPROF", "SIGPWR", "SIGQUIT", "SIGRTMAX", "SIGRTMAX-1", "SIGRTMAX-10", "SIGRTMAX-11", "SIGRTMAX-12", "SIGRTMAX-13", "SIGRTMAX-14", "SIGRTMAX-2", "SIGRTMAX-3", "SIGRTMAX-4", "SIGRTMAX-5", "SIGRTMAX-6", "SIGRTMAX-7", "SIGRTMAX-8", "SIGRTMAX-9", "SIGRTMIN", "SIGRTMIN+1", "SIGRTMIN+10", "SIGRTMIN+11", "SIGRTMIN+12", "SIGRTMIN+13", "SIGRTMIN+14", "SIGRTMIN+15", "SIGRTMIN+2", "SIGRTMIN+3", "SIGRTMIN+4", "SIGRTMIN+5", "SIGRTMIN+6", "SIGRTMIN+7", "SIGRTMIN+8", "SIGRTMIN+9", "SIGSEGV", "SIGSTKFLT", "SIGSTOP", "SIGSYS", "SIGTERM", "SIGTRAP", "SIGTSTP", "SIGTTIN", "SIGTTOU", "SIGURG", "SIGUSR1", "SIGUSR2", "SIGVTALRM", "SIGWINCH", "SIGXCPU", "SIGXFSZ"}, + }, + }, + }, + }, + }, + Dependencies: []string{ + "k8s.io/api/core/v1.LifecycleHandler"}, + } +} + +func schema_k8sio_api_core_v1_LifecycleHandler(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "LifecycleHandler defines a specific action that should be taken in a lifecycle hook. One and only one of the fields, except TCPSocket must be specified.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "exec": { + SchemaProps: spec.SchemaProps{ + Description: "Exec specifies a command to execute in the container.", + Ref: ref("k8s.io/api/core/v1.ExecAction"), + }, + }, + "httpGet": { + SchemaProps: spec.SchemaProps{ + Description: "HTTPGet specifies an HTTP GET request to perform.", + Ref: ref("k8s.io/api/core/v1.HTTPGetAction"), + }, + }, + "tcpSocket": { + SchemaProps: spec.SchemaProps{ + Description: "Deprecated. TCPSocket is NOT supported as a LifecycleHandler and kept for backward compatibility. There is no validation of this field and lifecycle hooks will fail at runtime when it is specified.", + Ref: ref("k8s.io/api/core/v1.TCPSocketAction"), + }, + }, + "sleep": { + SchemaProps: spec.SchemaProps{ + Description: "Sleep represents a duration that the container should sleep.", + Ref: ref("k8s.io/api/core/v1.SleepAction"), + }, + }, + }, + }, + }, + Dependencies: []string{ + "k8s.io/api/core/v1.ExecAction", "k8s.io/api/core/v1.HTTPGetAction", "k8s.io/api/core/v1.SleepAction", "k8s.io/api/core/v1.TCPSocketAction"}, + } +} + +func schema_k8sio_api_core_v1_LimitRange(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "LimitRange sets resource usage limits for each kind of resource in a Namespace.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", + }, + }, + "metadata": { + SchemaProps: spec.SchemaProps{ + Description: "Standard object's metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata", + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"), + }, + }, + "spec": { + SchemaProps: spec.SchemaProps{ + Description: "Spec defines the limits enforced. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status", + Default: map[string]interface{}{}, + Ref: ref("k8s.io/api/core/v1.LimitRangeSpec"), + }, + }, + }, + }, + }, + Dependencies: []string{ + "k8s.io/api/core/v1.LimitRangeSpec", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"}, + } +} + +func schema_k8sio_api_core_v1_LimitRangeItem(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "LimitRangeItem defines a min/max usage limit for any resource that matches on kind.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "type": { + SchemaProps: spec.SchemaProps{ + Description: "Type of resource that this limit applies to.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "max": { + SchemaProps: spec.SchemaProps{ + Description: "Max usage constraints on this kind by resource name.", + Type: []string{"object"}, + AdditionalProperties: &spec.SchemaOrBool{ + Allows: true, + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Ref: ref("k8s.io/apimachinery/pkg/api/resource.Quantity"), + }, + }, + }, + }, + }, + "min": { + SchemaProps: spec.SchemaProps{ + Description: "Min usage constraints on this kind by resource name.", + Type: []string{"object"}, + AdditionalProperties: &spec.SchemaOrBool{ + Allows: true, + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Ref: ref("k8s.io/apimachinery/pkg/api/resource.Quantity"), + }, + }, + }, + }, + }, + "default": { + SchemaProps: spec.SchemaProps{ + Description: "Default resource requirement limit value by resource name if resource limit is omitted.", + Type: []string{"object"}, + AdditionalProperties: &spec.SchemaOrBool{ + Allows: true, + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Ref: ref("k8s.io/apimachinery/pkg/api/resource.Quantity"), + }, + }, + }, + }, + }, + "defaultRequest": { + SchemaProps: spec.SchemaProps{ + Description: "DefaultRequest is the default resource requirement request value by resource name if resource request is omitted.", + Type: []string{"object"}, + AdditionalProperties: &spec.SchemaOrBool{ + Allows: true, + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Ref: ref("k8s.io/apimachinery/pkg/api/resource.Quantity"), + }, + }, + }, + }, + }, + "maxLimitRequestRatio": { + SchemaProps: spec.SchemaProps{ + Description: "MaxLimitRequestRatio if specified, the named resource must have a request and limit that are both non-zero where limit divided by request is less than or equal to the enumerated value; this represents the max burst for the named resource.", + Type: []string{"object"}, + AdditionalProperties: &spec.SchemaOrBool{ + Allows: true, + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Ref: ref("k8s.io/apimachinery/pkg/api/resource.Quantity"), + }, + }, + }, + }, + }, + }, + Required: []string{"type"}, + }, + }, + Dependencies: []string{ + "k8s.io/apimachinery/pkg/api/resource.Quantity"}, + } +} + +func schema_k8sio_api_core_v1_LimitRangeList(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "LimitRangeList is a list of LimitRange items.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", + }, + }, + "metadata": { + SchemaProps: spec.SchemaProps{ + Description: "Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"), + }, + }, + "items": { + SchemaProps: spec.SchemaProps{ + Description: "Items is a list of LimitRange objects. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/api/core/v1.LimitRange"), + }, + }, + }, + }, + }, + }, + Required: []string{"items"}, + }, + }, + Dependencies: []string{ + "k8s.io/api/core/v1.LimitRange", "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"}, + } +} + +func schema_k8sio_api_core_v1_LimitRangeSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "LimitRangeSpec defines a min/max usage limit for resources that match on kind.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "limits": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "Limits is the list of LimitRangeItem objects that are enforced.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/api/core/v1.LimitRangeItem"), + }, + }, + }, + }, + }, + }, + Required: []string{"limits"}, + }, + }, + Dependencies: []string{ + "k8s.io/api/core/v1.LimitRangeItem"}, + } +} + +func schema_k8sio_api_core_v1_LinuxContainerUser(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "LinuxContainerUser represents user identity information in Linux containers", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "uid": { + SchemaProps: spec.SchemaProps{ + Description: "UID is the primary uid initially attached to the first process in the container", + Default: 0, + Type: []string{"integer"}, + Format: "int64", + }, + }, + "gid": { + SchemaProps: spec.SchemaProps{ + Description: "GID is the primary gid initially attached to the first process in the container", + Default: 0, + Type: []string{"integer"}, + Format: "int64", + }, + }, + "supplementalGroups": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "SupplementalGroups are the supplemental groups initially attached to the first process in the container", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: 0, + Type: []string{"integer"}, + Format: "int64", + }, + }, + }, + }, + }, + }, + Required: []string{"uid", "gid"}, + }, + }, + } +} + +func schema_k8sio_api_core_v1_List(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "List holds a list of objects, which may not be known by the server.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", + }, + }, + "metadata": { + SchemaProps: spec.SchemaProps{ + Description: "Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"), + }, + }, + "items": { + SchemaProps: spec.SchemaProps{ + Description: "List of objects", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Ref: ref("k8s.io/apimachinery/pkg/runtime.RawExtension"), + }, + }, + }, + }, + }, + }, + Required: []string{"items"}, + }, + }, + Dependencies: []string{ + "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta", "k8s.io/apimachinery/pkg/runtime.RawExtension"}, + } +} + +func schema_k8sio_api_core_v1_LoadBalancerIngress(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "LoadBalancerIngress represents the status of a load-balancer ingress point: traffic intended for the service should be sent to an ingress point.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "ip": { + SchemaProps: spec.SchemaProps{ + Description: "IP is set for load-balancer ingress points that are IP based (typically GCE or OpenStack load-balancers)", + Type: []string{"string"}, + Format: "", + }, + }, + "hostname": { + SchemaProps: spec.SchemaProps{ + Description: "Hostname is set for load-balancer ingress points that are DNS based (typically AWS load-balancers)", + Type: []string{"string"}, + Format: "", + }, + }, + "ipMode": { + SchemaProps: spec.SchemaProps{ + Description: "IPMode specifies how the load-balancer IP behaves, and may only be specified when the ip field is specified. Setting this to \"VIP\" indicates that traffic is delivered to the node with the destination set to the load-balancer's IP and port. Setting this to \"Proxy\" indicates that traffic is delivered to the node or pod with the destination set to the node's IP and node port or the pod's IP and port. Service implementations may use this information to adjust traffic routing.", + Type: []string{"string"}, + Format: "", + }, + }, + "ports": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "Ports is a list of records of service ports If used, every port defined in the service should have an entry in it", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/api/core/v1.PortStatus"), + }, + }, + }, + }, + }, + }, + }, + }, + Dependencies: []string{ + "k8s.io/api/core/v1.PortStatus"}, + } +} + +func schema_k8sio_api_core_v1_LoadBalancerStatus(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "LoadBalancerStatus represents the status of a load-balancer.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "ingress": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "Ingress is a list containing ingress points for the load-balancer. Traffic intended for the service should be sent to these ingress points.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/api/core/v1.LoadBalancerIngress"), + }, + }, + }, + }, + }, + }, + }, + }, + Dependencies: []string{ + "k8s.io/api/core/v1.LoadBalancerIngress"}, + } +} + +func schema_k8sio_api_core_v1_LocalObjectReference(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "LocalObjectReference contains enough information to let you locate the referenced object inside the same namespace.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "name": { + SchemaProps: spec.SchemaProps{ + Description: "Name of the referent. This field is effectively required, but due to backwards compatibility is allowed to be empty. Instances of this type with an empty value here are almost certainly wrong. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-map-type": "atomic", + }, + }, + }, + } +} + +func schema_k8sio_api_core_v1_LocalVolumeSource(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "Local represents directly-attached storage with node affinity", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "path": { + SchemaProps: spec.SchemaProps{ + Description: "path of the full path to the volume on the node. It can be either a directory or block device (disk, partition, ...).", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "fsType": { + SchemaProps: spec.SchemaProps{ + Description: "fsType is the filesystem type to mount. It applies only when the Path is a block device. Must be a filesystem type supported by the host operating system. Ex. \"ext4\", \"xfs\", \"ntfs\". The default value is to auto-select a filesystem if unspecified.", + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"path"}, + }, + }, + } +} + +func schema_k8sio_api_core_v1_ModifyVolumeStatus(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "ModifyVolumeStatus represents the status object of ControllerModifyVolume operation", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "targetVolumeAttributesClassName": { + SchemaProps: spec.SchemaProps{ + Description: "targetVolumeAttributesClassName is the name of the VolumeAttributesClass the PVC currently being reconciled", + Type: []string{"string"}, + Format: "", + }, + }, + "status": { + SchemaProps: spec.SchemaProps{ + Description: "status is the status of the ControllerModifyVolume operation. It can be in any of following states:\n - Pending\n Pending indicates that the PersistentVolumeClaim cannot be modified due to unmet requirements, such as\n the specified VolumeAttributesClass not existing.\n - InProgress\n InProgress indicates that the volume is being modified.\n - Infeasible\n Infeasible indicates that the request has been rejected as invalid by the CSI driver. To\n\t resolve the error, a valid VolumeAttributesClass needs to be specified.\nNote: New statuses can be added in the future. Consumers should check for unknown statuses and fail appropriately.\n\nPossible enum values:\n - `\"InProgress\"` InProgress indicates that the volume is being modified\n - `\"Infeasible\"` Infeasible indicates that the request has been rejected as invalid by the CSI driver. To resolve the error, a valid VolumeAttributesClass needs to be specified\n - `\"Pending\"` Pending indicates that the PersistentVolumeClaim cannot be modified due to unmet requirements, such as the specified VolumeAttributesClass not existing", + Default: "", + Type: []string{"string"}, + Format: "", + Enum: []interface{}{"InProgress", "Infeasible", "Pending"}, + }, + }, + }, + Required: []string{"status"}, + }, + }, + } +} + +func schema_k8sio_api_core_v1_NFSVolumeSource(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "Represents an NFS mount that lasts the lifetime of a pod. NFS volumes do not support ownership management or SELinux relabeling.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "server": { + SchemaProps: spec.SchemaProps{ + Description: "server is the hostname or IP address of the NFS server. More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "path": { + SchemaProps: spec.SchemaProps{ + Description: "path that is exported by the NFS server. More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "readOnly": { + SchemaProps: spec.SchemaProps{ + Description: "readOnly here will force the NFS export to be mounted with read-only permissions. Defaults to false. More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs", + Type: []string{"boolean"}, + Format: "", + }, + }, + }, + Required: []string{"server", "path"}, + }, + }, + } +} + +func schema_k8sio_api_core_v1_Namespace(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "Namespace provides a scope for Names. Use of multiple namespaces is optional.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", + }, + }, + "metadata": { + SchemaProps: spec.SchemaProps{ + Description: "Standard object's metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata", + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"), + }, + }, + "spec": { + SchemaProps: spec.SchemaProps{ + Description: "Spec defines the behavior of the Namespace. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status", + Default: map[string]interface{}{}, + Ref: ref("k8s.io/api/core/v1.NamespaceSpec"), + }, + }, + "status": { + SchemaProps: spec.SchemaProps{ + Description: "Status describes the current status of a Namespace. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status", + Default: map[string]interface{}{}, + Ref: ref("k8s.io/api/core/v1.NamespaceStatus"), + }, + }, + }, + }, + }, + Dependencies: []string{ + "k8s.io/api/core/v1.NamespaceSpec", "k8s.io/api/core/v1.NamespaceStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"}, + } +} + +func schema_k8sio_api_core_v1_NamespaceCondition(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "NamespaceCondition contains details about state of namespace.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "type": { + SchemaProps: spec.SchemaProps{ + Description: "Type of namespace controller condition.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "status": { + SchemaProps: spec.SchemaProps{ + Description: "Status of the condition, one of True, False, Unknown.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "lastTransitionTime": { + SchemaProps: spec.SchemaProps{ + Description: "Last time the condition transitioned from one status to another.", + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.Time"), + }, + }, + "reason": { + SchemaProps: spec.SchemaProps{ + Description: "Unique, one-word, CamelCase reason for the condition's last transition.", + Type: []string{"string"}, + Format: "", + }, + }, + "message": { + SchemaProps: spec.SchemaProps{ + Description: "Human-readable message indicating details about last transition.", + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"type", "status"}, + }, + }, + Dependencies: []string{ + "k8s.io/apimachinery/pkg/apis/meta/v1.Time"}, + } +} + +func schema_k8sio_api_core_v1_NamespaceList(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "NamespaceList is a list of Namespaces.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", + }, + }, + "metadata": { + SchemaProps: spec.SchemaProps{ + Description: "Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"), + }, + }, + "items": { + SchemaProps: spec.SchemaProps{ + Description: "Items is the list of Namespace objects in the list. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/api/core/v1.Namespace"), + }, + }, + }, + }, + }, + }, + Required: []string{"items"}, + }, + }, + Dependencies: []string{ + "k8s.io/api/core/v1.Namespace", "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"}, + } +} + +func schema_k8sio_api_core_v1_NamespaceSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "NamespaceSpec describes the attributes on a Namespace.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "finalizers": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "Finalizers is an opaque list of values that must be empty to permanently remove object from storage. More info: https://kubernetes.io/docs/tasks/administer-cluster/namespaces/", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + }, + }, + }, + } +} + +func schema_k8sio_api_core_v1_NamespaceStatus(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "NamespaceStatus is information about the current status of a Namespace.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "phase": { + SchemaProps: spec.SchemaProps{ + Description: "Phase is the current lifecycle phase of the namespace. More info: https://kubernetes.io/docs/tasks/administer-cluster/namespaces/\n\nPossible enum values:\n - `\"Active\"` means the namespace is available for use in the system\n - `\"Terminating\"` means the namespace is undergoing graceful termination", + Type: []string{"string"}, + Format: "", + Enum: []interface{}{"Active", "Terminating"}, + }, + }, + "conditions": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-map-keys": []interface{}{ + "type", + }, + "x-kubernetes-list-type": "map", + "x-kubernetes-patch-merge-key": "type", + "x-kubernetes-patch-strategy": "merge", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "Represents the latest available observations of a namespace's current state.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/api/core/v1.NamespaceCondition"), + }, + }, + }, + }, + }, + }, + }, + }, + Dependencies: []string{ + "k8s.io/api/core/v1.NamespaceCondition"}, + } +} + +func schema_k8sio_api_core_v1_Node(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "Node is a worker node in Kubernetes. Each node will have a unique identifier in the cache (i.e. in etcd).", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", + }, + }, + "metadata": { + SchemaProps: spec.SchemaProps{ + Description: "Standard object's metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata", + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"), + }, + }, + "spec": { + SchemaProps: spec.SchemaProps{ + Description: "Spec defines the behavior of a node. https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status", + Default: map[string]interface{}{}, + Ref: ref("k8s.io/api/core/v1.NodeSpec"), + }, + }, + "status": { + SchemaProps: spec.SchemaProps{ + Description: "Most recently observed status of the node. Populated by the system. Read-only. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status", + Default: map[string]interface{}{}, + Ref: ref("k8s.io/api/core/v1.NodeStatus"), + }, + }, + }, + }, + }, + Dependencies: []string{ + "k8s.io/api/core/v1.NodeSpec", "k8s.io/api/core/v1.NodeStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"}, + } +} + +func schema_k8sio_api_core_v1_NodeAddress(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "NodeAddress contains information for the node's address.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "type": { + SchemaProps: spec.SchemaProps{ + Description: "Node address type, one of Hostname, ExternalIP or InternalIP.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "address": { + SchemaProps: spec.SchemaProps{ + Description: "The node address.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"type", "address"}, + }, + }, + } +} + +func schema_k8sio_api_core_v1_NodeAffinity(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "Node affinity is a group of node affinity scheduling rules.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "requiredDuringSchedulingIgnoredDuringExecution": { + SchemaProps: spec.SchemaProps{ + Description: "If the affinity requirements specified by this field are not met at scheduling time, the pod will not be scheduled onto the node. If the affinity requirements specified by this field cease to be met at some point during pod execution (e.g. due to an update), the system may or may not try to eventually evict the pod from its node.", + Ref: ref("k8s.io/api/core/v1.NodeSelector"), + }, + }, + "preferredDuringSchedulingIgnoredDuringExecution": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "The scheduler will prefer to schedule pods to nodes that satisfy the affinity expressions specified by this field, but it may choose a node that violates one or more of the expressions. The node that is most preferred is the one with the greatest sum of weights, i.e. for each node that meets all of the scheduling requirements (resource request, requiredDuringScheduling affinity expressions, etc.), compute a sum by iterating through the elements of this field and adding \"weight\" to the sum if the node matches the corresponding matchExpressions; the node(s) with the highest sum are the most preferred.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/api/core/v1.PreferredSchedulingTerm"), + }, + }, + }, + }, + }, + }, + }, + }, + Dependencies: []string{ + "k8s.io/api/core/v1.NodeSelector", "k8s.io/api/core/v1.PreferredSchedulingTerm"}, + } +} + +func schema_k8sio_api_core_v1_NodeCondition(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "NodeCondition contains condition information for a node.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "type": { + SchemaProps: spec.SchemaProps{ + Description: "Type of node condition.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "status": { + SchemaProps: spec.SchemaProps{ + Description: "Status of the condition, one of True, False, Unknown.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "lastHeartbeatTime": { + SchemaProps: spec.SchemaProps{ + Description: "Last time we got an update on a given condition.", + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.Time"), + }, + }, + "lastTransitionTime": { + SchemaProps: spec.SchemaProps{ + Description: "Last time the condition transit from one status to another.", + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.Time"), + }, + }, + "reason": { + SchemaProps: spec.SchemaProps{ + Description: "(brief) reason for the condition's last transition.", + Type: []string{"string"}, + Format: "", + }, + }, + "message": { + SchemaProps: spec.SchemaProps{ + Description: "Human readable message indicating details about last transition.", + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"type", "status"}, + }, + }, + Dependencies: []string{ + "k8s.io/apimachinery/pkg/apis/meta/v1.Time"}, + } +} + +func schema_k8sio_api_core_v1_NodeConfigSource(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "NodeConfigSource specifies a source of node configuration. Exactly one subfield (excluding metadata) must be non-nil. This API is deprecated since 1.22", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "configMap": { + SchemaProps: spec.SchemaProps{ + Description: "ConfigMap is a reference to a Node's ConfigMap", + Ref: ref("k8s.io/api/core/v1.ConfigMapNodeConfigSource"), + }, + }, + }, + }, + }, + Dependencies: []string{ + "k8s.io/api/core/v1.ConfigMapNodeConfigSource"}, + } +} + +func schema_k8sio_api_core_v1_NodeConfigStatus(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "NodeConfigStatus describes the status of the config assigned by Node.Spec.ConfigSource.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "assigned": { + SchemaProps: spec.SchemaProps{ + Description: "Assigned reports the checkpointed config the node will try to use. When Node.Spec.ConfigSource is updated, the node checkpoints the associated config payload to local disk, along with a record indicating intended config. The node refers to this record to choose its config checkpoint, and reports this record in Assigned. Assigned only updates in the status after the record has been checkpointed to disk. When the Kubelet is restarted, it tries to make the Assigned config the Active config by loading and validating the checkpointed payload identified by Assigned.", + Ref: ref("k8s.io/api/core/v1.NodeConfigSource"), + }, + }, + "active": { + SchemaProps: spec.SchemaProps{ + Description: "Active reports the checkpointed config the node is actively using. Active will represent either the current version of the Assigned config, or the current LastKnownGood config, depending on whether attempting to use the Assigned config results in an error.", + Ref: ref("k8s.io/api/core/v1.NodeConfigSource"), + }, + }, + "lastKnownGood": { + SchemaProps: spec.SchemaProps{ + Description: "LastKnownGood reports the checkpointed config the node will fall back to when it encounters an error attempting to use the Assigned config. The Assigned config becomes the LastKnownGood config when the node determines that the Assigned config is stable and correct. This is currently implemented as a 10-minute soak period starting when the local record of Assigned config is updated. If the Assigned config is Active at the end of this period, it becomes the LastKnownGood. Note that if Spec.ConfigSource is reset to nil (use local defaults), the LastKnownGood is also immediately reset to nil, because the local default config is always assumed good. You should not make assumptions about the node's method of determining config stability and correctness, as this may change or become configurable in the future.", + Ref: ref("k8s.io/api/core/v1.NodeConfigSource"), + }, + }, + "error": { + SchemaProps: spec.SchemaProps{ + Description: "Error describes any problems reconciling the Spec.ConfigSource to the Active config. Errors may occur, for example, attempting to checkpoint Spec.ConfigSource to the local Assigned record, attempting to checkpoint the payload associated with Spec.ConfigSource, attempting to load or validate the Assigned config, etc. Errors may occur at different points while syncing config. Earlier errors (e.g. download or checkpointing errors) will not result in a rollback to LastKnownGood, and may resolve across Kubelet retries. Later errors (e.g. loading or validating a checkpointed config) will result in a rollback to LastKnownGood. In the latter case, it is usually possible to resolve the error by fixing the config assigned in Spec.ConfigSource. You can find additional information for debugging by searching the error message in the Kubelet log. Error is a human-readable description of the error state; machines can check whether or not Error is empty, but should not rely on the stability of the Error text across Kubelet versions.", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + Dependencies: []string{ + "k8s.io/api/core/v1.NodeConfigSource"}, + } +} + +func schema_k8sio_api_core_v1_NodeDaemonEndpoints(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "NodeDaemonEndpoints lists ports opened by daemons running on the Node.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kubeletEndpoint": { + SchemaProps: spec.SchemaProps{ + Description: "Endpoint on which Kubelet is listening.", + Default: map[string]interface{}{}, + Ref: ref("k8s.io/api/core/v1.DaemonEndpoint"), + }, + }, + }, + }, + }, + Dependencies: []string{ + "k8s.io/api/core/v1.DaemonEndpoint"}, + } +} + +func schema_k8sio_api_core_v1_NodeFeatures(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "NodeFeatures describes the set of features implemented by the CRI implementation. The features contained in the NodeFeatures should depend only on the cri implementation independent of runtime handlers.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "supplementalGroupsPolicy": { + SchemaProps: spec.SchemaProps{ + Description: "SupplementalGroupsPolicy is set to true if the runtime supports SupplementalGroupsPolicy and ContainerUser.", + Type: []string{"boolean"}, + Format: "", + }, + }, + }, + }, + }, + } +} + +func schema_k8sio_api_core_v1_NodeList(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "NodeList is the whole list of all Nodes which have been registered with master.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", + }, + }, + "metadata": { + SchemaProps: spec.SchemaProps{ + Description: "Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"), + }, + }, + "items": { + SchemaProps: spec.SchemaProps{ + Description: "List of nodes", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/api/core/v1.Node"), + }, + }, + }, + }, + }, + }, + Required: []string{"items"}, + }, + }, + Dependencies: []string{ + "k8s.io/api/core/v1.Node", "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"}, + } +} + +func schema_k8sio_api_core_v1_NodeProxyOptions(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "NodeProxyOptions is the query options to a Node's proxy call.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", + }, + }, + "path": { + SchemaProps: spec.SchemaProps{ + Description: "Path is the URL path to use for the current proxy request to node.", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + } +} + +func schema_k8sio_api_core_v1_NodeRuntimeHandler(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "NodeRuntimeHandler is a set of runtime handler information.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "name": { + SchemaProps: spec.SchemaProps{ + Description: "Runtime handler name. Empty for the default runtime handler.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "features": { + SchemaProps: spec.SchemaProps{ + Description: "Supported features.", + Ref: ref("k8s.io/api/core/v1.NodeRuntimeHandlerFeatures"), + }, + }, + }, + }, + }, + Dependencies: []string{ + "k8s.io/api/core/v1.NodeRuntimeHandlerFeatures"}, + } +} + +func schema_k8sio_api_core_v1_NodeRuntimeHandlerFeatures(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "NodeRuntimeHandlerFeatures is a set of features implemented by the runtime handler.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "recursiveReadOnlyMounts": { + SchemaProps: spec.SchemaProps{ + Description: "RecursiveReadOnlyMounts is set to true if the runtime handler supports RecursiveReadOnlyMounts.", + Type: []string{"boolean"}, + Format: "", + }, + }, + "userNamespaces": { + SchemaProps: spec.SchemaProps{ + Description: "UserNamespaces is set to true if the runtime handler supports UserNamespaces, including for volumes.", + Type: []string{"boolean"}, + Format: "", + }, + }, + }, + }, + }, + } +} + +func schema_k8sio_api_core_v1_NodeSelector(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "A node selector represents the union of the results of one or more label queries over a set of nodes; that is, it represents the OR of the selectors represented by the node selector terms.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "nodeSelectorTerms": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "Required. A list of node selector terms. The terms are ORed.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/api/core/v1.NodeSelectorTerm"), + }, + }, + }, + }, + }, + }, + Required: []string{"nodeSelectorTerms"}, + }, + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-map-type": "atomic", + }, + }, + }, + Dependencies: []string{ + "k8s.io/api/core/v1.NodeSelectorTerm"}, + } +} + +func schema_k8sio_api_core_v1_NodeSelectorRequirement(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "A node selector requirement is a selector that contains values, a key, and an operator that relates the key and values.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "key": { + SchemaProps: spec.SchemaProps{ + Description: "The label key that the selector applies to.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "operator": { + SchemaProps: spec.SchemaProps{ + Description: "Represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt.\n\nPossible enum values:\n - `\"DoesNotExist\"`\n - `\"Exists\"`\n - `\"Gt\"`\n - `\"In\"`\n - `\"Lt\"`\n - `\"NotIn\"`", + Default: "", + Type: []string{"string"}, + Format: "", + Enum: []interface{}{"DoesNotExist", "Exists", "Gt", "In", "Lt", "NotIn"}, + }, + }, + "values": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "An array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. If the operator is Gt or Lt, the values array must have a single element, which will be interpreted as an integer. This array is replaced during a strategic merge patch.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + }, + Required: []string{"key", "operator"}, + }, + }, + } +} + +func schema_k8sio_api_core_v1_NodeSelectorTerm(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "A null or empty node selector term matches no objects. The requirements of them are ANDed. The TopologySelectorTerm type implements a subset of the NodeSelectorTerm.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "matchExpressions": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "A list of node selector requirements by node's labels.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/api/core/v1.NodeSelectorRequirement"), + }, + }, + }, + }, + }, + "matchFields": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "A list of node selector requirements by node's fields.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/api/core/v1.NodeSelectorRequirement"), + }, + }, + }, + }, + }, + }, + }, + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-map-type": "atomic", + }, + }, + }, + Dependencies: []string{ + "k8s.io/api/core/v1.NodeSelectorRequirement"}, + } +} + +func schema_k8sio_api_core_v1_NodeSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "NodeSpec describes the attributes that a node is created with.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "podCIDR": { + SchemaProps: spec.SchemaProps{ + Description: "PodCIDR represents the pod IP range assigned to the node.", + Type: []string{"string"}, + Format: "", + }, + }, + "podCIDRs": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "set", + "x-kubernetes-patch-strategy": "merge", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "podCIDRs represents the IP ranges assigned to the node for usage by Pods on that node. If this field is specified, the 0th entry must match the podCIDR field. It may contain at most 1 value for each of IPv4 and IPv6.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + "providerID": { + SchemaProps: spec.SchemaProps{ + Description: "ID of the node assigned by the cloud provider in the format: ://", + Type: []string{"string"}, + Format: "", + }, + }, + "unschedulable": { + SchemaProps: spec.SchemaProps{ + Description: "Unschedulable controls node schedulability of new pods. By default, node is schedulable. More info: https://kubernetes.io/docs/concepts/nodes/node/#manual-node-administration", + Type: []string{"boolean"}, + Format: "", + }, + }, + "taints": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "If specified, the node's taints.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/api/core/v1.Taint"), + }, + }, + }, + }, + }, + "configSource": { + SchemaProps: spec.SchemaProps{ + Description: "Deprecated: Previously used to specify the source of the node's configuration for the DynamicKubeletConfig feature. This feature is removed.", + Ref: ref("k8s.io/api/core/v1.NodeConfigSource"), + }, + }, + "externalID": { + SchemaProps: spec.SchemaProps{ + Description: "Deprecated. Not all kubelets will set this field. Remove field after 1.13. see: https://issues.k8s.io/61966", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + Dependencies: []string{ + "k8s.io/api/core/v1.NodeConfigSource", "k8s.io/api/core/v1.Taint"}, + } +} + +func schema_k8sio_api_core_v1_NodeStatus(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "NodeStatus is information about the current status of a node.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "capacity": { + SchemaProps: spec.SchemaProps{ + Description: "Capacity represents the total resources of a node. More info: https://kubernetes.io/docs/reference/node/node-status/#capacity", + Type: []string{"object"}, + AdditionalProperties: &spec.SchemaOrBool{ + Allows: true, + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Ref: ref("k8s.io/apimachinery/pkg/api/resource.Quantity"), + }, + }, + }, + }, + }, + "allocatable": { + SchemaProps: spec.SchemaProps{ + Description: "Allocatable represents the resources of a node that are available for scheduling. Defaults to Capacity.", + Type: []string{"object"}, + AdditionalProperties: &spec.SchemaOrBool{ + Allows: true, + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Ref: ref("k8s.io/apimachinery/pkg/api/resource.Quantity"), + }, + }, + }, + }, + }, + "phase": { + SchemaProps: spec.SchemaProps{ + Description: "NodePhase is the recently observed lifecycle phase of the node. More info: https://kubernetes.io/docs/concepts/nodes/node/#phase The field is never populated, and now is deprecated.\n\nPossible enum values:\n - `\"Pending\"` means the node has been created/added by the system, but not configured.\n - `\"Running\"` means the node has been configured and has Kubernetes components running.\n - `\"Terminated\"` means the node has been removed from the cluster.", + Type: []string{"string"}, + Format: "", + Enum: []interface{}{"Pending", "Running", "Terminated"}, + }, + }, + "conditions": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-map-keys": []interface{}{ + "type", + }, + "x-kubernetes-list-type": "map", + "x-kubernetes-patch-merge-key": "type", + "x-kubernetes-patch-strategy": "merge", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "Conditions is an array of current observed node conditions. More info: https://kubernetes.io/docs/reference/node/node-status/#condition", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/api/core/v1.NodeCondition"), + }, + }, + }, + }, + }, + "addresses": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-map-keys": []interface{}{ + "type", + }, + "x-kubernetes-list-type": "map", + "x-kubernetes-patch-merge-key": "type", + "x-kubernetes-patch-strategy": "merge", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "List of addresses reachable to the node. Queried from cloud provider, if available. More info: https://kubernetes.io/docs/reference/node/node-status/#addresses Note: This field is declared as mergeable, but the merge key is not sufficiently unique, which can cause data corruption when it is merged. Callers should instead use a full-replacement patch. See https://pr.k8s.io/79391 for an example. Consumers should assume that addresses can change during the lifetime of a Node. However, there are some exceptions where this may not be possible, such as Pods that inherit a Node's address in its own status or consumers of the downward API (status.hostIP).", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/api/core/v1.NodeAddress"), + }, + }, + }, + }, + }, + "daemonEndpoints": { + SchemaProps: spec.SchemaProps{ + Description: "Endpoints of daemons running on the Node.", + Default: map[string]interface{}{}, + Ref: ref("k8s.io/api/core/v1.NodeDaemonEndpoints"), + }, + }, + "nodeInfo": { + SchemaProps: spec.SchemaProps{ + Description: "Set of ids/uuids to uniquely identify the node. More info: https://kubernetes.io/docs/reference/node/node-status/#info", + Default: map[string]interface{}{}, + Ref: ref("k8s.io/api/core/v1.NodeSystemInfo"), + }, + }, + "images": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "List of container images on this node", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/api/core/v1.ContainerImage"), + }, + }, + }, + }, + }, + "volumesInUse": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "List of attachable volumes in use (mounted) by the node.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + "volumesAttached": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "List of volumes that are attached to the node.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/api/core/v1.AttachedVolume"), + }, + }, + }, + }, + }, + "config": { + SchemaProps: spec.SchemaProps{ + Description: "Status of the config assigned to the node via the dynamic Kubelet config feature.", + Ref: ref("k8s.io/api/core/v1.NodeConfigStatus"), + }, + }, + "runtimeHandlers": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "The available runtime handlers.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/api/core/v1.NodeRuntimeHandler"), + }, + }, + }, + }, + }, + "features": { + SchemaProps: spec.SchemaProps{ + Description: "Features describes the set of features implemented by the CRI implementation.", + Ref: ref("k8s.io/api/core/v1.NodeFeatures"), + }, + }, + }, + }, + }, + Dependencies: []string{ + "k8s.io/api/core/v1.AttachedVolume", "k8s.io/api/core/v1.ContainerImage", "k8s.io/api/core/v1.NodeAddress", "k8s.io/api/core/v1.NodeCondition", "k8s.io/api/core/v1.NodeConfigStatus", "k8s.io/api/core/v1.NodeDaemonEndpoints", "k8s.io/api/core/v1.NodeFeatures", "k8s.io/api/core/v1.NodeRuntimeHandler", "k8s.io/api/core/v1.NodeSystemInfo", "k8s.io/apimachinery/pkg/api/resource.Quantity"}, + } +} + +func schema_k8sio_api_core_v1_NodeSwapStatus(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "NodeSwapStatus represents swap memory information.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "capacity": { + SchemaProps: spec.SchemaProps{ + Description: "Total amount of swap memory in bytes.", + Type: []string{"integer"}, + Format: "int64", + }, + }, + }, + }, + }, + } +} + +func schema_k8sio_api_core_v1_NodeSystemInfo(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "NodeSystemInfo is a set of ids/uuids to uniquely identify the node.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "machineID": { + SchemaProps: spec.SchemaProps{ + Description: "MachineID reported by the node. For unique machine identification in the cluster this field is preferred. Learn more from man(5) machine-id: http://man7.org/linux/man-pages/man5/machine-id.5.html", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "systemUUID": { + SchemaProps: spec.SchemaProps{ + Description: "SystemUUID reported by the node. For unique machine identification MachineID is preferred. This field is specific to Red Hat hosts https://access.redhat.com/documentation/en-us/red_hat_subscription_management/1/html/rhsm/uuid", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "bootID": { + SchemaProps: spec.SchemaProps{ + Description: "Boot ID reported by the node.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "kernelVersion": { + SchemaProps: spec.SchemaProps{ + Description: "Kernel Version reported by the node from 'uname -r' (e.g. 3.16.0-0.bpo.4-amd64).", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "osImage": { + SchemaProps: spec.SchemaProps{ + Description: "OS Image reported by the node from /etc/os-release (e.g. Debian GNU/Linux 7 (wheezy)).", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "containerRuntimeVersion": { + SchemaProps: spec.SchemaProps{ + Description: "ContainerRuntime Version reported by the node through runtime remote API (e.g. containerd://1.4.2).", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "kubeletVersion": { + SchemaProps: spec.SchemaProps{ + Description: "Kubelet Version reported by the node.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "kubeProxyVersion": { + SchemaProps: spec.SchemaProps{ + Description: "Deprecated: KubeProxy Version reported by the node.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "operatingSystem": { + SchemaProps: spec.SchemaProps{ + Description: "The Operating System reported by the node", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "architecture": { + SchemaProps: spec.SchemaProps{ + Description: "The Architecture reported by the node", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "swap": { + SchemaProps: spec.SchemaProps{ + Description: "Swap Info reported by the node.", + Ref: ref("k8s.io/api/core/v1.NodeSwapStatus"), + }, + }, + }, + Required: []string{"machineID", "systemUUID", "bootID", "kernelVersion", "osImage", "containerRuntimeVersion", "kubeletVersion", "kubeProxyVersion", "operatingSystem", "architecture"}, + }, + }, + Dependencies: []string{ + "k8s.io/api/core/v1.NodeSwapStatus"}, + } +} + +func schema_k8sio_api_core_v1_ObjectFieldSelector(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "ObjectFieldSelector selects an APIVersioned field of an object.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "Version of the schema the FieldPath is written in terms of, defaults to \"v1\".", + Type: []string{"string"}, + Format: "", + }, + }, + "fieldPath": { + SchemaProps: spec.SchemaProps{ + Description: "Path of the field to select in the specified API version.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"fieldPath"}, + }, + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-map-type": "atomic", + }, + }, + }, + } +} + +func schema_k8sio_api_core_v1_ObjectReference(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "ObjectReference contains enough information to let you inspect or modify the referred object.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "namespace": { + SchemaProps: spec.SchemaProps{ + Description: "Namespace of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/", + Type: []string{"string"}, + Format: "", + }, + }, + "name": { + SchemaProps: spec.SchemaProps{ + Description: "Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names", + Type: []string{"string"}, + Format: "", + }, + }, + "uid": { + SchemaProps: spec.SchemaProps{ + Description: "UID of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#uids", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "API version of the referent.", + Type: []string{"string"}, + Format: "", + }, + }, + "resourceVersion": { + SchemaProps: spec.SchemaProps{ + Description: "Specific resourceVersion to which this reference is made, if any. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency", + Type: []string{"string"}, + Format: "", + }, + }, + "fieldPath": { + SchemaProps: spec.SchemaProps{ + Description: "If referring to a piece of an object instead of an entire object, this string should contain a valid JSON/Go field access statement, such as desiredState.manifest.containers[2]. For example, if the object reference is to a container within a pod, this would take on a value like: \"spec.containers{name}\" (where \"name\" refers to the name of the container that triggered the event) or if no container name is specified \"spec.containers[2]\" (container with index 2 in this pod). This syntax is chosen only to have some well-defined way of referencing a part of an object.", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-map-type": "atomic", + }, + }, + }, + } +} + +func schema_k8sio_api_core_v1_PersistentVolume(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "PersistentVolume (PV) is a storage resource provisioned by an administrator. It is analogous to a node. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", + }, + }, + "metadata": { + SchemaProps: spec.SchemaProps{ + Description: "Standard object's metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata", + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"), + }, + }, + "spec": { + SchemaProps: spec.SchemaProps{ + Description: "spec defines a specification of a persistent volume owned by the cluster. Provisioned by an administrator. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#persistent-volumes", + Default: map[string]interface{}{}, + Ref: ref("k8s.io/api/core/v1.PersistentVolumeSpec"), + }, + }, + "status": { + SchemaProps: spec.SchemaProps{ + Description: "status represents the current information/status for the persistent volume. Populated by the system. Read-only. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#persistent-volumes", + Default: map[string]interface{}{}, + Ref: ref("k8s.io/api/core/v1.PersistentVolumeStatus"), + }, + }, + }, + }, + }, + Dependencies: []string{ + "k8s.io/api/core/v1.PersistentVolumeSpec", "k8s.io/api/core/v1.PersistentVolumeStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"}, + } +} + +func schema_k8sio_api_core_v1_PersistentVolumeClaim(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "PersistentVolumeClaim is a user's request for and claim to a persistent volume", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", + }, + }, + "metadata": { + SchemaProps: spec.SchemaProps{ + Description: "Standard object's metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata", + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"), + }, + }, + "spec": { + SchemaProps: spec.SchemaProps{ + Description: "spec defines the desired characteristics of a volume requested by a pod author. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#persistentvolumeclaims", + Default: map[string]interface{}{}, + Ref: ref("k8s.io/api/core/v1.PersistentVolumeClaimSpec"), + }, + }, + "status": { + SchemaProps: spec.SchemaProps{ + Description: "status represents the current information/status of a persistent volume claim. Read-only. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#persistentvolumeclaims", + Default: map[string]interface{}{}, + Ref: ref("k8s.io/api/core/v1.PersistentVolumeClaimStatus"), + }, + }, + }, + }, + }, + Dependencies: []string{ + "k8s.io/api/core/v1.PersistentVolumeClaimSpec", "k8s.io/api/core/v1.PersistentVolumeClaimStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"}, + } +} + +func schema_k8sio_api_core_v1_PersistentVolumeClaimCondition(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "PersistentVolumeClaimCondition contains details about state of pvc", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "type": { + SchemaProps: spec.SchemaProps{ + Description: "Type is the type of the condition. More info: https://kubernetes.io/docs/reference/kubernetes-api/config-and-storage-resources/persistent-volume-claim-v1/#:~:text=set%20to%20%27ResizeStarted%27.-,PersistentVolumeClaimCondition,-contains%20details%20about", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "status": { + SchemaProps: spec.SchemaProps{ + Description: "Status is the status of the condition. Can be True, False, Unknown. More info: https://kubernetes.io/docs/reference/kubernetes-api/config-and-storage-resources/persistent-volume-claim-v1/#:~:text=state%20of%20pvc-,conditions.status,-(string)%2C%20required", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "lastProbeTime": { + SchemaProps: spec.SchemaProps{ + Description: "lastProbeTime is the time we probed the condition.", + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.Time"), + }, + }, + "lastTransitionTime": { + SchemaProps: spec.SchemaProps{ + Description: "lastTransitionTime is the time the condition transitioned from one status to another.", + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.Time"), + }, + }, + "reason": { + SchemaProps: spec.SchemaProps{ + Description: "reason is a unique, this should be a short, machine understandable string that gives the reason for condition's last transition. If it reports \"Resizing\" that means the underlying persistent volume is being resized.", + Type: []string{"string"}, + Format: "", + }, + }, + "message": { + SchemaProps: spec.SchemaProps{ + Description: "message is the human-readable message indicating details about last transition.", + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"type", "status"}, + }, + }, + Dependencies: []string{ + "k8s.io/apimachinery/pkg/apis/meta/v1.Time"}, + } +} + +func schema_k8sio_api_core_v1_PersistentVolumeClaimList(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "PersistentVolumeClaimList is a list of PersistentVolumeClaim items.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", + }, + }, + "metadata": { + SchemaProps: spec.SchemaProps{ + Description: "Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"), + }, + }, + "items": { + SchemaProps: spec.SchemaProps{ + Description: "items is a list of persistent volume claims. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#persistentvolumeclaims", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/api/core/v1.PersistentVolumeClaim"), + }, + }, + }, + }, + }, + }, + Required: []string{"items"}, + }, + }, + Dependencies: []string{ + "k8s.io/api/core/v1.PersistentVolumeClaim", "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"}, + } +} + +func schema_k8sio_api_core_v1_PersistentVolumeClaimSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "PersistentVolumeClaimSpec describes the common attributes of storage devices and allows a Source for provider-specific attributes", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "accessModes": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "accessModes contains the desired access modes the volume should have. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#access-modes-1", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + Enum: []interface{}{"ReadOnlyMany", "ReadWriteMany", "ReadWriteOnce", "ReadWriteOncePod"}, + }, + }, + }, + }, + }, + "selector": { + SchemaProps: spec.SchemaProps{ + Description: "selector is a label query over volumes to consider for binding.", + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.LabelSelector"), + }, + }, + "resources": { + SchemaProps: spec.SchemaProps{ + Description: "resources represents the minimum resources the volume should have. If RecoverVolumeExpansionFailure feature is enabled users are allowed to specify resource requirements that are lower than previous value but must still be higher than capacity recorded in the status field of the claim. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#resources", + Default: map[string]interface{}{}, + Ref: ref("k8s.io/api/core/v1.VolumeResourceRequirements"), + }, + }, + "volumeName": { + SchemaProps: spec.SchemaProps{ + Description: "volumeName is the binding reference to the PersistentVolume backing this claim.", + Type: []string{"string"}, + Format: "", + }, + }, + "storageClassName": { + SchemaProps: spec.SchemaProps{ + Description: "storageClassName is the name of the StorageClass required by the claim. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#class-1", + Type: []string{"string"}, + Format: "", + }, + }, + "volumeMode": { + SchemaProps: spec.SchemaProps{ + Description: "volumeMode defines what type of volume is required by the claim. Value of Filesystem is implied when not included in claim spec.\n\nPossible enum values:\n - `\"Block\"` means the volume will not be formatted with a filesystem and will remain a raw block device.\n - `\"Filesystem\"` means the volume will be or is formatted with a filesystem.", + Type: []string{"string"}, + Format: "", + Enum: []interface{}{"Block", "Filesystem"}, + }, + }, + "dataSource": { + SchemaProps: spec.SchemaProps{ + Description: "dataSource field can be used to specify either: * An existing VolumeSnapshot object (snapshot.storage.k8s.io/VolumeSnapshot) * An existing PVC (PersistentVolumeClaim) If the provisioner or an external controller can support the specified data source, it will create a new volume based on the contents of the specified data source. When the AnyVolumeDataSource feature gate is enabled, dataSource contents will be copied to dataSourceRef, and dataSourceRef contents will be copied to dataSource when dataSourceRef.namespace is not specified. If the namespace is specified, then dataSourceRef will not be copied to dataSource.", + Ref: ref("k8s.io/api/core/v1.TypedLocalObjectReference"), + }, + }, + "dataSourceRef": { + SchemaProps: spec.SchemaProps{ + Description: "dataSourceRef specifies the object from which to populate the volume with data, if a non-empty volume is desired. This may be any object from a non-empty API group (non core object) or a PersistentVolumeClaim object. When this field is specified, volume binding will only succeed if the type of the specified object matches some installed volume populator or dynamic provisioner. This field will replace the functionality of the dataSource field and as such if both fields are non-empty, they must have the same value. For backwards compatibility, when namespace isn't specified in dataSourceRef, both fields (dataSource and dataSourceRef) will be set to the same value automatically if one of them is empty and the other is non-empty. When namespace is specified in dataSourceRef, dataSource isn't set to the same value and must be empty. There are three important differences between dataSource and dataSourceRef: * While dataSource only allows two specific types of objects, dataSourceRef\n allows any non-core object, as well as PersistentVolumeClaim objects.\n* While dataSource ignores disallowed values (dropping them), dataSourceRef\n preserves all values, and generates an error if a disallowed value is\n specified.\n* While dataSource only allows local objects, dataSourceRef allows objects\n in any namespaces.\n(Beta) Using this field requires the AnyVolumeDataSource feature gate to be enabled. (Alpha) Using the namespace field of dataSourceRef requires the CrossNamespaceVolumeDataSource feature gate to be enabled.", + Ref: ref("k8s.io/api/core/v1.TypedObjectReference"), + }, + }, + "volumeAttributesClassName": { + SchemaProps: spec.SchemaProps{ + Description: "volumeAttributesClassName may be used to set the VolumeAttributesClass used by this claim. If specified, the CSI driver will create or update the volume with the attributes defined in the corresponding VolumeAttributesClass. This has a different purpose than storageClassName, it can be changed after the claim is created. An empty string or nil value indicates that no VolumeAttributesClass will be applied to the claim. If the claim enters an Infeasible error state, this field can be reset to its previous value (including nil) to cancel the modification. If the resource referred to by volumeAttributesClass does not exist, this PersistentVolumeClaim will be set to a Pending state, as reflected by the modifyVolumeStatus field, until such as a resource exists. More info: https://kubernetes.io/docs/concepts/storage/volume-attributes-classes/", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + Dependencies: []string{ + "k8s.io/api/core/v1.TypedLocalObjectReference", "k8s.io/api/core/v1.TypedObjectReference", "k8s.io/api/core/v1.VolumeResourceRequirements", "k8s.io/apimachinery/pkg/apis/meta/v1.LabelSelector"}, + } +} + +func schema_k8sio_api_core_v1_PersistentVolumeClaimStatus(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "PersistentVolumeClaimStatus is the current status of a persistent volume claim.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "phase": { + SchemaProps: spec.SchemaProps{ + Description: "phase represents the current phase of PersistentVolumeClaim.\n\nPossible enum values:\n - `\"Bound\"` used for PersistentVolumeClaims that are bound\n - `\"Lost\"` used for PersistentVolumeClaims that lost their underlying PersistentVolume. The claim was bound to a PersistentVolume and this volume does not exist any longer and all data on it was lost.\n - `\"Pending\"` used for PersistentVolumeClaims that are not yet bound", + Type: []string{"string"}, + Format: "", + Enum: []interface{}{"Bound", "Lost", "Pending"}, + }, + }, + "accessModes": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "accessModes contains the actual access modes the volume backing the PVC has. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#access-modes-1", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + Enum: []interface{}{"ReadOnlyMany", "ReadWriteMany", "ReadWriteOnce", "ReadWriteOncePod"}, + }, + }, + }, + }, + }, + "capacity": { + SchemaProps: spec.SchemaProps{ + Description: "capacity represents the actual resources of the underlying volume.", + Type: []string{"object"}, + AdditionalProperties: &spec.SchemaOrBool{ + Allows: true, + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Ref: ref("k8s.io/apimachinery/pkg/api/resource.Quantity"), + }, + }, + }, + }, + }, + "conditions": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-map-keys": []interface{}{ + "type", + }, + "x-kubernetes-list-type": "map", + "x-kubernetes-patch-merge-key": "type", + "x-kubernetes-patch-strategy": "merge", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "conditions is the current Condition of persistent volume claim. If underlying persistent volume is being resized then the Condition will be set to 'Resizing'.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/api/core/v1.PersistentVolumeClaimCondition"), + }, + }, + }, + }, + }, + "allocatedResources": { + SchemaProps: spec.SchemaProps{ + Description: "allocatedResources tracks the resources allocated to a PVC including its capacity. Key names follow standard Kubernetes label syntax. Valid values are either:\n\t* Un-prefixed keys:\n\t\t- storage - the capacity of the volume.\n\t* Custom resources must use implementation-defined prefixed names such as \"example.com/my-custom-resource\"\nApart from above values - keys that are unprefixed or have kubernetes.io prefix are considered reserved and hence may not be used.\n\nCapacity reported here may be larger than the actual capacity when a volume expansion operation is requested. For storage quota, the larger value from allocatedResources and PVC.spec.resources is used. If allocatedResources is not set, PVC.spec.resources alone is used for quota calculation. If a volume expansion capacity request is lowered, allocatedResources is only lowered if there are no expansion operations in progress and if the actual volume capacity is equal or lower than the requested capacity.\n\nA controller that receives PVC update with previously unknown resourceName should ignore the update for the purpose it was designed. For example - a controller that only is responsible for resizing capacity of the volume, should ignore PVC updates that change other valid resources associated with PVC.\n\nThis is an alpha field and requires enabling RecoverVolumeExpansionFailure feature.", + Type: []string{"object"}, + AdditionalProperties: &spec.SchemaOrBool{ + Allows: true, + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Ref: ref("k8s.io/apimachinery/pkg/api/resource.Quantity"), + }, + }, + }, + }, + }, + "allocatedResourceStatuses": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-map-type": "granular", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "allocatedResourceStatuses stores status of resource being resized for the given PVC. Key names follow standard Kubernetes label syntax. Valid values are either:\n\t* Un-prefixed keys:\n\t\t- storage - the capacity of the volume.\n\t* Custom resources must use implementation-defined prefixed names such as \"example.com/my-custom-resource\"\nApart from above values - keys that are unprefixed or have kubernetes.io prefix are considered reserved and hence may not be used.\n\nClaimResourceStatus can be in any of following states:\n\t- ControllerResizeInProgress:\n\t\tState set when resize controller starts resizing the volume in control-plane.\n\t- ControllerResizeFailed:\n\t\tState set when resize has failed in resize controller with a terminal error.\n\t- NodeResizePending:\n\t\tState set when resize controller has finished resizing the volume but further resizing of\n\t\tvolume is needed on the node.\n\t- NodeResizeInProgress:\n\t\tState set when kubelet starts resizing the volume.\n\t- NodeResizeFailed:\n\t\tState set when resizing has failed in kubelet with a terminal error. Transient errors don't set\n\t\tNodeResizeFailed.\nFor example: if expanding a PVC for more capacity - this field can be one of the following states:\n\t- pvc.status.allocatedResourceStatus['storage'] = \"ControllerResizeInProgress\"\n - pvc.status.allocatedResourceStatus['storage'] = \"ControllerResizeFailed\"\n - pvc.status.allocatedResourceStatus['storage'] = \"NodeResizePending\"\n - pvc.status.allocatedResourceStatus['storage'] = \"NodeResizeInProgress\"\n - pvc.status.allocatedResourceStatus['storage'] = \"NodeResizeFailed\"\nWhen this field is not set, it means that no resize operation is in progress for the given PVC.\n\nA controller that receives PVC update with previously unknown resourceName or ClaimResourceStatus should ignore the update for the purpose it was designed. For example - a controller that only is responsible for resizing capacity of the volume, should ignore PVC updates that change other valid resources associated with PVC.\n\nThis is an alpha field and requires enabling RecoverVolumeExpansionFailure feature.", + Type: []string{"object"}, + AdditionalProperties: &spec.SchemaOrBool{ + Allows: true, + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + Enum: []interface{}{"ControllerResizeInProgress", "ControllerResizeInfeasible", "NodeResizeInProgress", "NodeResizeInfeasible", "NodeResizePending"}, + }, + }, + }, + }, + }, + "currentVolumeAttributesClassName": { + SchemaProps: spec.SchemaProps{ + Description: "currentVolumeAttributesClassName is the current name of the VolumeAttributesClass the PVC is using. When unset, there is no VolumeAttributeClass applied to this PersistentVolumeClaim", + Type: []string{"string"}, + Format: "", + }, + }, + "modifyVolumeStatus": { + SchemaProps: spec.SchemaProps{ + Description: "ModifyVolumeStatus represents the status object of ControllerModifyVolume operation. When this is unset, there is no ModifyVolume operation being attempted.", + Ref: ref("k8s.io/api/core/v1.ModifyVolumeStatus"), + }, + }, + }, + }, + }, + Dependencies: []string{ + "k8s.io/api/core/v1.ModifyVolumeStatus", "k8s.io/api/core/v1.PersistentVolumeClaimCondition", "k8s.io/apimachinery/pkg/api/resource.Quantity"}, + } +} + +func schema_k8sio_api_core_v1_PersistentVolumeClaimTemplate(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "PersistentVolumeClaimTemplate is used to produce PersistentVolumeClaim objects as part of an EphemeralVolumeSource.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "metadata": { + SchemaProps: spec.SchemaProps{ + Description: "May contain labels and annotations that will be copied into the PVC when creating it. No other fields are allowed and will be rejected during validation.", + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"), + }, + }, + "spec": { + SchemaProps: spec.SchemaProps{ + Description: "The specification for the PersistentVolumeClaim. The entire content is copied unchanged into the PVC that gets created from this template. The same fields as in a PersistentVolumeClaim are also valid here.", + Default: map[string]interface{}{}, + Ref: ref("k8s.io/api/core/v1.PersistentVolumeClaimSpec"), + }, + }, + }, + Required: []string{"spec"}, + }, + }, + Dependencies: []string{ + "k8s.io/api/core/v1.PersistentVolumeClaimSpec", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"}, + } +} + +func schema_k8sio_api_core_v1_PersistentVolumeClaimVolumeSource(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "PersistentVolumeClaimVolumeSource references the user's PVC in the same namespace. This volume finds the bound PV and mounts that volume for the pod. A PersistentVolumeClaimVolumeSource is, essentially, a wrapper around another type of volume that is owned by someone else (the system).", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "claimName": { + SchemaProps: spec.SchemaProps{ + Description: "claimName is the name of a PersistentVolumeClaim in the same namespace as the pod using this volume. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#persistentvolumeclaims", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "readOnly": { + SchemaProps: spec.SchemaProps{ + Description: "readOnly Will force the ReadOnly setting in VolumeMounts. Default false.", + Type: []string{"boolean"}, + Format: "", + }, + }, + }, + Required: []string{"claimName"}, + }, + }, + } +} + +func schema_k8sio_api_core_v1_PersistentVolumeList(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "PersistentVolumeList is a list of PersistentVolume items.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", + }, + }, + "metadata": { + SchemaProps: spec.SchemaProps{ + Description: "Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"), + }, + }, + "items": { + SchemaProps: spec.SchemaProps{ + Description: "items is a list of persistent volumes. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/api/core/v1.PersistentVolume"), + }, + }, + }, + }, + }, + }, + Required: []string{"items"}, + }, + }, + Dependencies: []string{ + "k8s.io/api/core/v1.PersistentVolume", "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"}, + } +} + +func schema_k8sio_api_core_v1_PersistentVolumeSource(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "PersistentVolumeSource is similar to VolumeSource but meant for the administrator who creates PVs. Exactly one of its members must be set.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "gcePersistentDisk": { + SchemaProps: spec.SchemaProps{ + Description: "gcePersistentDisk represents a GCE Disk resource that is attached to a kubelet's host machine and then exposed to the pod. Provisioned by an admin. Deprecated: GCEPersistentDisk is deprecated. All operations for the in-tree gcePersistentDisk type are redirected to the pd.csi.storage.gke.io CSI driver. More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk", + Ref: ref("k8s.io/api/core/v1.GCEPersistentDiskVolumeSource"), + }, + }, + "awsElasticBlockStore": { + SchemaProps: spec.SchemaProps{ + Description: "awsElasticBlockStore represents an AWS Disk resource that is attached to a kubelet's host machine and then exposed to the pod. Deprecated: AWSElasticBlockStore is deprecated. All operations for the in-tree awsElasticBlockStore type are redirected to the ebs.csi.aws.com CSI driver. More info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore", + Ref: ref("k8s.io/api/core/v1.AWSElasticBlockStoreVolumeSource"), + }, + }, + "hostPath": { + SchemaProps: spec.SchemaProps{ + Description: "hostPath represents a directory on the host. Provisioned by a developer or tester. This is useful for single-node development and testing only! On-host storage is not supported in any way and WILL NOT WORK in a multi-node cluster. More info: https://kubernetes.io/docs/concepts/storage/volumes#hostpath", + Ref: ref("k8s.io/api/core/v1.HostPathVolumeSource"), + }, + }, + "glusterfs": { + SchemaProps: spec.SchemaProps{ + Description: "glusterfs represents a Glusterfs volume that is attached to a host and exposed to the pod. Provisioned by an admin. Deprecated: Glusterfs is deprecated and the in-tree glusterfs type is no longer supported. More info: https://examples.k8s.io/volumes/glusterfs/README.md", + Ref: ref("k8s.io/api/core/v1.GlusterfsPersistentVolumeSource"), + }, + }, + "nfs": { + SchemaProps: spec.SchemaProps{ + Description: "nfs represents an NFS mount on the host. Provisioned by an admin. More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs", + Ref: ref("k8s.io/api/core/v1.NFSVolumeSource"), + }, + }, + "rbd": { + SchemaProps: spec.SchemaProps{ + Description: "rbd represents a Rados Block Device mount on the host that shares a pod's lifetime. Deprecated: RBD is deprecated and the in-tree rbd type is no longer supported. More info: https://examples.k8s.io/volumes/rbd/README.md", + Ref: ref("k8s.io/api/core/v1.RBDPersistentVolumeSource"), + }, + }, + "iscsi": { + SchemaProps: spec.SchemaProps{ + Description: "iscsi represents an ISCSI Disk resource that is attached to a kubelet's host machine and then exposed to the pod. Provisioned by an admin.", + Ref: ref("k8s.io/api/core/v1.ISCSIPersistentVolumeSource"), + }, + }, + "cinder": { + SchemaProps: spec.SchemaProps{ + Description: "cinder represents a cinder volume attached and mounted on kubelets host machine. Deprecated: Cinder is deprecated. All operations for the in-tree cinder type are redirected to the cinder.csi.openstack.org CSI driver. More info: https://examples.k8s.io/mysql-cinder-pd/README.md", + Ref: ref("k8s.io/api/core/v1.CinderPersistentVolumeSource"), + }, + }, + "cephfs": { + SchemaProps: spec.SchemaProps{ + Description: "cephFS represents a Ceph FS mount on the host that shares a pod's lifetime. Deprecated: CephFS is deprecated and the in-tree cephfs type is no longer supported.", + Ref: ref("k8s.io/api/core/v1.CephFSPersistentVolumeSource"), + }, + }, + "fc": { + SchemaProps: spec.SchemaProps{ + Description: "fc represents a Fibre Channel resource that is attached to a kubelet's host machine and then exposed to the pod.", + Ref: ref("k8s.io/api/core/v1.FCVolumeSource"), + }, + }, + "flocker": { + SchemaProps: spec.SchemaProps{ + Description: "flocker represents a Flocker volume attached to a kubelet's host machine and exposed to the pod for its usage. This depends on the Flocker control service being running. Deprecated: Flocker is deprecated and the in-tree flocker type is no longer supported.", + Ref: ref("k8s.io/api/core/v1.FlockerVolumeSource"), + }, + }, + "flexVolume": { + SchemaProps: spec.SchemaProps{ + Description: "flexVolume represents a generic volume resource that is provisioned/attached using an exec based plugin. Deprecated: FlexVolume is deprecated. Consider using a CSIDriver instead.", + Ref: ref("k8s.io/api/core/v1.FlexPersistentVolumeSource"), + }, + }, + "azureFile": { + SchemaProps: spec.SchemaProps{ + Description: "azureFile represents an Azure File Service mount on the host and bind mount to the pod. Deprecated: AzureFile is deprecated. All operations for the in-tree azureFile type are redirected to the file.csi.azure.com CSI driver.", + Ref: ref("k8s.io/api/core/v1.AzureFilePersistentVolumeSource"), + }, + }, + "vsphereVolume": { + SchemaProps: spec.SchemaProps{ + Description: "vsphereVolume represents a vSphere volume attached and mounted on kubelets host machine. Deprecated: VsphereVolume is deprecated. All operations for the in-tree vsphereVolume type are redirected to the csi.vsphere.vmware.com CSI driver.", + Ref: ref("k8s.io/api/core/v1.VsphereVirtualDiskVolumeSource"), + }, + }, + "quobyte": { + SchemaProps: spec.SchemaProps{ + Description: "quobyte represents a Quobyte mount on the host that shares a pod's lifetime. Deprecated: Quobyte is deprecated and the in-tree quobyte type is no longer supported.", + Ref: ref("k8s.io/api/core/v1.QuobyteVolumeSource"), + }, + }, + "azureDisk": { + SchemaProps: spec.SchemaProps{ + Description: "azureDisk represents an Azure Data Disk mount on the host and bind mount to the pod. Deprecated: AzureDisk is deprecated. All operations for the in-tree azureDisk type are redirected to the disk.csi.azure.com CSI driver.", + Ref: ref("k8s.io/api/core/v1.AzureDiskVolumeSource"), + }, + }, + "photonPersistentDisk": { + SchemaProps: spec.SchemaProps{ + Description: "photonPersistentDisk represents a PhotonController persistent disk attached and mounted on kubelets host machine. Deprecated: PhotonPersistentDisk is deprecated and the in-tree photonPersistentDisk type is no longer supported.", + Ref: ref("k8s.io/api/core/v1.PhotonPersistentDiskVolumeSource"), + }, + }, + "portworxVolume": { + SchemaProps: spec.SchemaProps{ + Description: "portworxVolume represents a portworx volume attached and mounted on kubelets host machine. Deprecated: PortworxVolume is deprecated. All operations for the in-tree portworxVolume type are redirected to the pxd.portworx.com CSI driver when the CSIMigrationPortworx feature-gate is on.", + Ref: ref("k8s.io/api/core/v1.PortworxVolumeSource"), + }, + }, + "scaleIO": { + SchemaProps: spec.SchemaProps{ + Description: "scaleIO represents a ScaleIO persistent volume attached and mounted on Kubernetes nodes. Deprecated: ScaleIO is deprecated and the in-tree scaleIO type is no longer supported.", + Ref: ref("k8s.io/api/core/v1.ScaleIOPersistentVolumeSource"), + }, + }, + "local": { + SchemaProps: spec.SchemaProps{ + Description: "local represents directly-attached storage with node affinity", + Ref: ref("k8s.io/api/core/v1.LocalVolumeSource"), + }, + }, + "storageos": { + SchemaProps: spec.SchemaProps{ + Description: "storageOS represents a StorageOS volume that is attached to the kubelet's host machine and mounted into the pod. Deprecated: StorageOS is deprecated and the in-tree storageos type is no longer supported. More info: https://examples.k8s.io/volumes/storageos/README.md", + Ref: ref("k8s.io/api/core/v1.StorageOSPersistentVolumeSource"), + }, + }, + "csi": { + SchemaProps: spec.SchemaProps{ + Description: "csi represents storage that is handled by an external CSI driver.", + Ref: ref("k8s.io/api/core/v1.CSIPersistentVolumeSource"), + }, + }, + }, + }, + }, + Dependencies: []string{ + "k8s.io/api/core/v1.AWSElasticBlockStoreVolumeSource", "k8s.io/api/core/v1.AzureDiskVolumeSource", "k8s.io/api/core/v1.AzureFilePersistentVolumeSource", "k8s.io/api/core/v1.CSIPersistentVolumeSource", "k8s.io/api/core/v1.CephFSPersistentVolumeSource", "k8s.io/api/core/v1.CinderPersistentVolumeSource", "k8s.io/api/core/v1.FCVolumeSource", "k8s.io/api/core/v1.FlexPersistentVolumeSource", "k8s.io/api/core/v1.FlockerVolumeSource", "k8s.io/api/core/v1.GCEPersistentDiskVolumeSource", "k8s.io/api/core/v1.GlusterfsPersistentVolumeSource", "k8s.io/api/core/v1.HostPathVolumeSource", "k8s.io/api/core/v1.ISCSIPersistentVolumeSource", "k8s.io/api/core/v1.LocalVolumeSource", "k8s.io/api/core/v1.NFSVolumeSource", "k8s.io/api/core/v1.PhotonPersistentDiskVolumeSource", "k8s.io/api/core/v1.PortworxVolumeSource", "k8s.io/api/core/v1.QuobyteVolumeSource", "k8s.io/api/core/v1.RBDPersistentVolumeSource", "k8s.io/api/core/v1.ScaleIOPersistentVolumeSource", "k8s.io/api/core/v1.StorageOSPersistentVolumeSource", "k8s.io/api/core/v1.VsphereVirtualDiskVolumeSource"}, + } +} + +func schema_k8sio_api_core_v1_PersistentVolumeSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "PersistentVolumeSpec is the specification of a persistent volume.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "capacity": { + SchemaProps: spec.SchemaProps{ + Description: "capacity is the description of the persistent volume's resources and capacity. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#capacity", + Type: []string{"object"}, + AdditionalProperties: &spec.SchemaOrBool{ + Allows: true, + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Ref: ref("k8s.io/apimachinery/pkg/api/resource.Quantity"), + }, + }, + }, + }, + }, + "gcePersistentDisk": { + SchemaProps: spec.SchemaProps{ + Description: "gcePersistentDisk represents a GCE Disk resource that is attached to a kubelet's host machine and then exposed to the pod. Provisioned by an admin. Deprecated: GCEPersistentDisk is deprecated. All operations for the in-tree gcePersistentDisk type are redirected to the pd.csi.storage.gke.io CSI driver. More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk", + Ref: ref("k8s.io/api/core/v1.GCEPersistentDiskVolumeSource"), + }, + }, + "awsElasticBlockStore": { + SchemaProps: spec.SchemaProps{ + Description: "awsElasticBlockStore represents an AWS Disk resource that is attached to a kubelet's host machine and then exposed to the pod. Deprecated: AWSElasticBlockStore is deprecated. All operations for the in-tree awsElasticBlockStore type are redirected to the ebs.csi.aws.com CSI driver. More info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore", + Ref: ref("k8s.io/api/core/v1.AWSElasticBlockStoreVolumeSource"), + }, + }, + "hostPath": { + SchemaProps: spec.SchemaProps{ + Description: "hostPath represents a directory on the host. Provisioned by a developer or tester. This is useful for single-node development and testing only! On-host storage is not supported in any way and WILL NOT WORK in a multi-node cluster. More info: https://kubernetes.io/docs/concepts/storage/volumes#hostpath", + Ref: ref("k8s.io/api/core/v1.HostPathVolumeSource"), + }, + }, + "glusterfs": { + SchemaProps: spec.SchemaProps{ + Description: "glusterfs represents a Glusterfs volume that is attached to a host and exposed to the pod. Provisioned by an admin. Deprecated: Glusterfs is deprecated and the in-tree glusterfs type is no longer supported. More info: https://examples.k8s.io/volumes/glusterfs/README.md", + Ref: ref("k8s.io/api/core/v1.GlusterfsPersistentVolumeSource"), + }, + }, + "nfs": { + SchemaProps: spec.SchemaProps{ + Description: "nfs represents an NFS mount on the host. Provisioned by an admin. More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs", + Ref: ref("k8s.io/api/core/v1.NFSVolumeSource"), + }, + }, + "rbd": { + SchemaProps: spec.SchemaProps{ + Description: "rbd represents a Rados Block Device mount on the host that shares a pod's lifetime. Deprecated: RBD is deprecated and the in-tree rbd type is no longer supported. More info: https://examples.k8s.io/volumes/rbd/README.md", + Ref: ref("k8s.io/api/core/v1.RBDPersistentVolumeSource"), + }, + }, + "iscsi": { + SchemaProps: spec.SchemaProps{ + Description: "iscsi represents an ISCSI Disk resource that is attached to a kubelet's host machine and then exposed to the pod. Provisioned by an admin.", + Ref: ref("k8s.io/api/core/v1.ISCSIPersistentVolumeSource"), + }, + }, + "cinder": { + SchemaProps: spec.SchemaProps{ + Description: "cinder represents a cinder volume attached and mounted on kubelets host machine. Deprecated: Cinder is deprecated. All operations for the in-tree cinder type are redirected to the cinder.csi.openstack.org CSI driver. More info: https://examples.k8s.io/mysql-cinder-pd/README.md", + Ref: ref("k8s.io/api/core/v1.CinderPersistentVolumeSource"), + }, + }, + "cephfs": { + SchemaProps: spec.SchemaProps{ + Description: "cephFS represents a Ceph FS mount on the host that shares a pod's lifetime. Deprecated: CephFS is deprecated and the in-tree cephfs type is no longer supported.", + Ref: ref("k8s.io/api/core/v1.CephFSPersistentVolumeSource"), + }, + }, + "fc": { + SchemaProps: spec.SchemaProps{ + Description: "fc represents a Fibre Channel resource that is attached to a kubelet's host machine and then exposed to the pod.", + Ref: ref("k8s.io/api/core/v1.FCVolumeSource"), + }, + }, + "flocker": { + SchemaProps: spec.SchemaProps{ + Description: "flocker represents a Flocker volume attached to a kubelet's host machine and exposed to the pod for its usage. This depends on the Flocker control service being running. Deprecated: Flocker is deprecated and the in-tree flocker type is no longer supported.", + Ref: ref("k8s.io/api/core/v1.FlockerVolumeSource"), + }, + }, + "flexVolume": { + SchemaProps: spec.SchemaProps{ + Description: "flexVolume represents a generic volume resource that is provisioned/attached using an exec based plugin. Deprecated: FlexVolume is deprecated. Consider using a CSIDriver instead.", + Ref: ref("k8s.io/api/core/v1.FlexPersistentVolumeSource"), + }, + }, + "azureFile": { + SchemaProps: spec.SchemaProps{ + Description: "azureFile represents an Azure File Service mount on the host and bind mount to the pod. Deprecated: AzureFile is deprecated. All operations for the in-tree azureFile type are redirected to the file.csi.azure.com CSI driver.", + Ref: ref("k8s.io/api/core/v1.AzureFilePersistentVolumeSource"), + }, + }, + "vsphereVolume": { + SchemaProps: spec.SchemaProps{ + Description: "vsphereVolume represents a vSphere volume attached and mounted on kubelets host machine. Deprecated: VsphereVolume is deprecated. All operations for the in-tree vsphereVolume type are redirected to the csi.vsphere.vmware.com CSI driver.", + Ref: ref("k8s.io/api/core/v1.VsphereVirtualDiskVolumeSource"), + }, + }, + "quobyte": { + SchemaProps: spec.SchemaProps{ + Description: "quobyte represents a Quobyte mount on the host that shares a pod's lifetime. Deprecated: Quobyte is deprecated and the in-tree quobyte type is no longer supported.", + Ref: ref("k8s.io/api/core/v1.QuobyteVolumeSource"), + }, + }, + "azureDisk": { + SchemaProps: spec.SchemaProps{ + Description: "azureDisk represents an Azure Data Disk mount on the host and bind mount to the pod. Deprecated: AzureDisk is deprecated. All operations for the in-tree azureDisk type are redirected to the disk.csi.azure.com CSI driver.", + Ref: ref("k8s.io/api/core/v1.AzureDiskVolumeSource"), + }, + }, + "photonPersistentDisk": { + SchemaProps: spec.SchemaProps{ + Description: "photonPersistentDisk represents a PhotonController persistent disk attached and mounted on kubelets host machine. Deprecated: PhotonPersistentDisk is deprecated and the in-tree photonPersistentDisk type is no longer supported.", + Ref: ref("k8s.io/api/core/v1.PhotonPersistentDiskVolumeSource"), + }, + }, + "portworxVolume": { + SchemaProps: spec.SchemaProps{ + Description: "portworxVolume represents a portworx volume attached and mounted on kubelets host machine. Deprecated: PortworxVolume is deprecated. All operations for the in-tree portworxVolume type are redirected to the pxd.portworx.com CSI driver when the CSIMigrationPortworx feature-gate is on.", + Ref: ref("k8s.io/api/core/v1.PortworxVolumeSource"), + }, + }, + "scaleIO": { + SchemaProps: spec.SchemaProps{ + Description: "scaleIO represents a ScaleIO persistent volume attached and mounted on Kubernetes nodes. Deprecated: ScaleIO is deprecated and the in-tree scaleIO type is no longer supported.", + Ref: ref("k8s.io/api/core/v1.ScaleIOPersistentVolumeSource"), + }, + }, + "local": { + SchemaProps: spec.SchemaProps{ + Description: "local represents directly-attached storage with node affinity", + Ref: ref("k8s.io/api/core/v1.LocalVolumeSource"), + }, + }, + "storageos": { + SchemaProps: spec.SchemaProps{ + Description: "storageOS represents a StorageOS volume that is attached to the kubelet's host machine and mounted into the pod. Deprecated: StorageOS is deprecated and the in-tree storageos type is no longer supported. More info: https://examples.k8s.io/volumes/storageos/README.md", + Ref: ref("k8s.io/api/core/v1.StorageOSPersistentVolumeSource"), + }, + }, + "csi": { + SchemaProps: spec.SchemaProps{ + Description: "csi represents storage that is handled by an external CSI driver.", + Ref: ref("k8s.io/api/core/v1.CSIPersistentVolumeSource"), + }, + }, + "accessModes": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "accessModes contains all ways the volume can be mounted. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#access-modes", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + Enum: []interface{}{"ReadOnlyMany", "ReadWriteMany", "ReadWriteOnce", "ReadWriteOncePod"}, + }, + }, + }, + }, + }, + "claimRef": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-map-type": "granular", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "claimRef is part of a bi-directional binding between PersistentVolume and PersistentVolumeClaim. Expected to be non-nil when bound. claim.VolumeName is the authoritative bind between PV and PVC. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#binding", + Ref: ref("k8s.io/api/core/v1.ObjectReference"), + }, + }, + "persistentVolumeReclaimPolicy": { + SchemaProps: spec.SchemaProps{ + Description: "persistentVolumeReclaimPolicy defines what happens to a persistent volume when released from its claim. Valid options are Retain (default for manually created PersistentVolumes), Delete (default for dynamically provisioned PersistentVolumes), and Recycle (deprecated). Recycle must be supported by the volume plugin underlying this PersistentVolume. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#reclaiming\n\nPossible enum values:\n - `\"Delete\"` means the volume will be deleted from Kubernetes on release from its claim. The volume plugin must support Deletion.\n - `\"Recycle\"` means the volume will be recycled back into the pool of unbound persistent volumes on release from its claim. The volume plugin must support Recycling.\n - `\"Retain\"` means the volume will be left in its current phase (Released) for manual reclamation by the administrator. The default policy is Retain.", + Type: []string{"string"}, + Format: "", + Enum: []interface{}{"Delete", "Recycle", "Retain"}, + }, + }, + "storageClassName": { + SchemaProps: spec.SchemaProps{ + Description: "storageClassName is the name of StorageClass to which this persistent volume belongs. Empty value means that this volume does not belong to any StorageClass.", + Type: []string{"string"}, + Format: "", + }, + }, + "mountOptions": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "mountOptions is the list of mount options, e.g. [\"ro\", \"soft\"]. Not validated - mount will simply fail if one is invalid. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes/#mount-options", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + "volumeMode": { + SchemaProps: spec.SchemaProps{ + Description: "volumeMode defines if a volume is intended to be used with a formatted filesystem or to remain in raw block state. Value of Filesystem is implied when not included in spec.\n\nPossible enum values:\n - `\"Block\"` means the volume will not be formatted with a filesystem and will remain a raw block device.\n - `\"Filesystem\"` means the volume will be or is formatted with a filesystem.", + Type: []string{"string"}, + Format: "", + Enum: []interface{}{"Block", "Filesystem"}, + }, + }, + "nodeAffinity": { + SchemaProps: spec.SchemaProps{ + Description: "nodeAffinity defines constraints that limit what nodes this volume can be accessed from. This field influences the scheduling of pods that use this volume.", + Ref: ref("k8s.io/api/core/v1.VolumeNodeAffinity"), + }, + }, + "volumeAttributesClassName": { + SchemaProps: spec.SchemaProps{ + Description: "Name of VolumeAttributesClass to which this persistent volume belongs. Empty value is not allowed. When this field is not set, it indicates that this volume does not belong to any VolumeAttributesClass. This field is mutable and can be changed by the CSI driver after a volume has been updated successfully to a new class. For an unbound PersistentVolume, the volumeAttributesClassName will be matched with unbound PersistentVolumeClaims during the binding process.", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + Dependencies: []string{ + "k8s.io/api/core/v1.AWSElasticBlockStoreVolumeSource", "k8s.io/api/core/v1.AzureDiskVolumeSource", "k8s.io/api/core/v1.AzureFilePersistentVolumeSource", "k8s.io/api/core/v1.CSIPersistentVolumeSource", "k8s.io/api/core/v1.CephFSPersistentVolumeSource", "k8s.io/api/core/v1.CinderPersistentVolumeSource", "k8s.io/api/core/v1.FCVolumeSource", "k8s.io/api/core/v1.FlexPersistentVolumeSource", "k8s.io/api/core/v1.FlockerVolumeSource", "k8s.io/api/core/v1.GCEPersistentDiskVolumeSource", "k8s.io/api/core/v1.GlusterfsPersistentVolumeSource", "k8s.io/api/core/v1.HostPathVolumeSource", "k8s.io/api/core/v1.ISCSIPersistentVolumeSource", "k8s.io/api/core/v1.LocalVolumeSource", "k8s.io/api/core/v1.NFSVolumeSource", "k8s.io/api/core/v1.ObjectReference", "k8s.io/api/core/v1.PhotonPersistentDiskVolumeSource", "k8s.io/api/core/v1.PortworxVolumeSource", "k8s.io/api/core/v1.QuobyteVolumeSource", "k8s.io/api/core/v1.RBDPersistentVolumeSource", "k8s.io/api/core/v1.ScaleIOPersistentVolumeSource", "k8s.io/api/core/v1.StorageOSPersistentVolumeSource", "k8s.io/api/core/v1.VolumeNodeAffinity", "k8s.io/api/core/v1.VsphereVirtualDiskVolumeSource", "k8s.io/apimachinery/pkg/api/resource.Quantity"}, + } +} + +func schema_k8sio_api_core_v1_PersistentVolumeStatus(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "PersistentVolumeStatus is the current status of a persistent volume.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "phase": { + SchemaProps: spec.SchemaProps{ + Description: "phase indicates if a volume is available, bound to a claim, or released by a claim. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#phase\n\nPossible enum values:\n - `\"Available\"` used for PersistentVolumes that are not yet bound Available volumes are held by the binder and matched to PersistentVolumeClaims\n - `\"Bound\"` used for PersistentVolumes that are bound\n - `\"Failed\"` used for PersistentVolumes that failed to be correctly recycled or deleted after being released from a claim\n - `\"Pending\"` used for PersistentVolumes that are not available\n - `\"Released\"` used for PersistentVolumes where the bound PersistentVolumeClaim was deleted released volumes must be recycled before becoming available again this phase is used by the persistent volume claim binder to signal to another process to reclaim the resource", + Type: []string{"string"}, + Format: "", + Enum: []interface{}{"Available", "Bound", "Failed", "Pending", "Released"}, + }, + }, + "message": { + SchemaProps: spec.SchemaProps{ + Description: "message is a human-readable message indicating details about why the volume is in this state.", + Type: []string{"string"}, + Format: "", + }, + }, + "reason": { + SchemaProps: spec.SchemaProps{ + Description: "reason is a brief CamelCase string that describes any failure and is meant for machine parsing and tidy display in the CLI.", + Type: []string{"string"}, + Format: "", + }, + }, + "lastPhaseTransitionTime": { + SchemaProps: spec.SchemaProps{ + Description: "lastPhaseTransitionTime is the time the phase transitioned from one to another and automatically resets to current time everytime a volume phase transitions.", + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.Time"), + }, + }, + }, + }, + }, + Dependencies: []string{ + "k8s.io/apimachinery/pkg/apis/meta/v1.Time"}, + } +} + +func schema_k8sio_api_core_v1_PhotonPersistentDiskVolumeSource(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "Represents a Photon Controller persistent disk resource.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "pdID": { + SchemaProps: spec.SchemaProps{ + Description: "pdID is the ID that identifies Photon Controller persistent disk", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "fsType": { + SchemaProps: spec.SchemaProps{ + Description: "fsType is the filesystem type to mount. Must be a filesystem type supported by the host operating system. Ex. \"ext4\", \"xfs\", \"ntfs\". Implicitly inferred to be \"ext4\" if unspecified.", + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"pdID"}, + }, + }, + } +} + +func schema_k8sio_api_core_v1_Pod(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "Pod is a collection of containers that can run on a host. This resource is created by clients and scheduled onto hosts.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", + }, + }, + "metadata": { + SchemaProps: spec.SchemaProps{ + Description: "Standard object's metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata", + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"), + }, + }, + "spec": { + SchemaProps: spec.SchemaProps{ + Description: "Specification of the desired behavior of the pod. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status", + Default: map[string]interface{}{}, + Ref: ref("k8s.io/api/core/v1.PodSpec"), + }, + }, + "status": { + SchemaProps: spec.SchemaProps{ + Description: "Most recently observed status of the pod. This data may not be up to date. Populated by the system. Read-only. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status", + Default: map[string]interface{}{}, + Ref: ref("k8s.io/api/core/v1.PodStatus"), + }, + }, + }, + }, + }, + Dependencies: []string{ + "k8s.io/api/core/v1.PodSpec", "k8s.io/api/core/v1.PodStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"}, + } +} + +func schema_k8sio_api_core_v1_PodAffinity(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "Pod affinity is a group of inter pod affinity scheduling rules.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "requiredDuringSchedulingIgnoredDuringExecution": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "If the affinity requirements specified by this field are not met at scheduling time, the pod will not be scheduled onto the node. If the affinity requirements specified by this field cease to be met at some point during pod execution (e.g. due to a pod label update), the system may or may not try to eventually evict the pod from its node. When there are multiple elements, the lists of nodes corresponding to each podAffinityTerm are intersected, i.e. all terms must be satisfied.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/api/core/v1.PodAffinityTerm"), + }, + }, + }, + }, + }, + "preferredDuringSchedulingIgnoredDuringExecution": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "The scheduler will prefer to schedule pods to nodes that satisfy the affinity expressions specified by this field, but it may choose a node that violates one or more of the expressions. The node that is most preferred is the one with the greatest sum of weights, i.e. for each node that meets all of the scheduling requirements (resource request, requiredDuringScheduling affinity expressions, etc.), compute a sum by iterating through the elements of this field and adding \"weight\" to the sum if the node has pods which matches the corresponding podAffinityTerm; the node(s) with the highest sum are the most preferred.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/api/core/v1.WeightedPodAffinityTerm"), + }, + }, + }, + }, + }, + }, + }, + }, + Dependencies: []string{ + "k8s.io/api/core/v1.PodAffinityTerm", "k8s.io/api/core/v1.WeightedPodAffinityTerm"}, + } +} + +func schema_k8sio_api_core_v1_PodAffinityTerm(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "Defines a set of pods (namely those matching the labelSelector relative to the given namespace(s)) that this pod should be co-located (affinity) or not co-located (anti-affinity) with, where co-located is defined as running on a node whose value of the label with key matches that of any node on which a pod of the set of pods is running", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "labelSelector": { + SchemaProps: spec.SchemaProps{ + Description: "A label query over a set of resources, in this case pods. If it's null, this PodAffinityTerm matches with no Pods.", + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.LabelSelector"), + }, + }, + "namespaces": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "namespaces specifies a static list of namespace names that the term applies to. The term is applied to the union of the namespaces listed in this field and the ones selected by namespaceSelector. null or empty namespaces list and null namespaceSelector means \"this pod's namespace\".", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + "topologyKey": { + SchemaProps: spec.SchemaProps{ + Description: "This pod should be co-located (affinity) or not co-located (anti-affinity) with the pods matching the labelSelector in the specified namespaces, where co-located is defined as running on a node whose value of the label with key topologyKey matches that of any node on which any of the selected pods is running. Empty topologyKey is not allowed.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "namespaceSelector": { + SchemaProps: spec.SchemaProps{ + Description: "A label query over the set of namespaces that the term applies to. The term is applied to the union of the namespaces selected by this field and the ones listed in the namespaces field. null selector and null or empty namespaces list means \"this pod's namespace\". An empty selector ({}) matches all namespaces.", + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.LabelSelector"), + }, + }, + "matchLabelKeys": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "MatchLabelKeys is a set of pod label keys to select which pods will be taken into consideration. The keys are used to lookup values from the incoming pod labels, those key-value labels are merged with `labelSelector` as `key in (value)` to select the group of existing pods which pods will be taken into consideration for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both matchLabelKeys and labelSelector. Also, matchLabelKeys cannot be set when labelSelector isn't set.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + "mismatchLabelKeys": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "MismatchLabelKeys is a set of pod label keys to select which pods will be taken into consideration. The keys are used to lookup values from the incoming pod labels, those key-value labels are merged with `labelSelector` as `key notin (value)` to select the group of existing pods which pods will be taken into consideration for the incoming pod's pod (anti) affinity. Keys that don't exist in the incoming pod labels will be ignored. The default value is empty. The same key is forbidden to exist in both mismatchLabelKeys and labelSelector. Also, mismatchLabelKeys cannot be set when labelSelector isn't set.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + }, + Required: []string{"topologyKey"}, + }, + }, + Dependencies: []string{ + "k8s.io/apimachinery/pkg/apis/meta/v1.LabelSelector"}, + } +} + +func schema_k8sio_api_core_v1_PodAntiAffinity(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "Pod anti affinity is a group of inter pod anti affinity scheduling rules.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "requiredDuringSchedulingIgnoredDuringExecution": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "If the anti-affinity requirements specified by this field are not met at scheduling time, the pod will not be scheduled onto the node. If the anti-affinity requirements specified by this field cease to be met at some point during pod execution (e.g. due to a pod label update), the system may or may not try to eventually evict the pod from its node. When there are multiple elements, the lists of nodes corresponding to each podAffinityTerm are intersected, i.e. all terms must be satisfied.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/api/core/v1.PodAffinityTerm"), + }, + }, + }, + }, + }, + "preferredDuringSchedulingIgnoredDuringExecution": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "The scheduler will prefer to schedule pods to nodes that satisfy the anti-affinity expressions specified by this field, but it may choose a node that violates one or more of the expressions. The node that is most preferred is the one with the greatest sum of weights, i.e. for each node that meets all of the scheduling requirements (resource request, requiredDuringScheduling anti-affinity expressions, etc.), compute a sum by iterating through the elements of this field and subtracting \"weight\" from the sum if the node has pods which matches the corresponding podAffinityTerm; the node(s) with the highest sum are the most preferred.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/api/core/v1.WeightedPodAffinityTerm"), + }, + }, + }, + }, + }, + }, + }, + }, + Dependencies: []string{ + "k8s.io/api/core/v1.PodAffinityTerm", "k8s.io/api/core/v1.WeightedPodAffinityTerm"}, + } +} + +func schema_k8sio_api_core_v1_PodAttachOptions(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "PodAttachOptions is the query options to a Pod's remote attach call.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", + }, + }, + "stdin": { + SchemaProps: spec.SchemaProps{ + Description: "Stdin if true, redirects the standard input stream of the pod for this call. Defaults to false.", + Type: []string{"boolean"}, + Format: "", + }, + }, + "stdout": { + SchemaProps: spec.SchemaProps{ + Description: "Stdout if true indicates that stdout is to be redirected for the attach call. Defaults to true.", + Type: []string{"boolean"}, + Format: "", + }, + }, + "stderr": { + SchemaProps: spec.SchemaProps{ + Description: "Stderr if true indicates that stderr is to be redirected for the attach call. Defaults to true.", + Type: []string{"boolean"}, + Format: "", + }, + }, + "tty": { + SchemaProps: spec.SchemaProps{ + Description: "TTY if true indicates that a tty will be allocated for the attach call. This is passed through the container runtime so the tty is allocated on the worker node by the container runtime. Defaults to false.", + Type: []string{"boolean"}, + Format: "", + }, + }, + "container": { + SchemaProps: spec.SchemaProps{ + Description: "The container in which to execute the command. Defaults to only container if there is only one container in the pod.", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + } +} + +func schema_k8sio_api_core_v1_PodCertificateProjection(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "PodCertificateProjection provides a private key and X.509 certificate in the pod filesystem.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "signerName": { + SchemaProps: spec.SchemaProps{ + Description: "Kubelet's generated CSRs will be addressed to this signer.", + Type: []string{"string"}, + Format: "", + }, + }, + "keyType": { + SchemaProps: spec.SchemaProps{ + Description: "The type of keypair Kubelet will generate for the pod.\n\nValid values are \"RSA3072\", \"RSA4096\", \"ECDSAP256\", \"ECDSAP384\", \"ECDSAP521\", and \"ED25519\".", + Type: []string{"string"}, + Format: "", + }, + }, + "maxExpirationSeconds": { + SchemaProps: spec.SchemaProps{ + Description: "maxExpirationSeconds is the maximum lifetime permitted for the certificate.\n\nKubelet copies this value verbatim into the PodCertificateRequests it generates for this projection.\n\nIf omitted, kube-apiserver will set it to 86400(24 hours). kube-apiserver will reject values shorter than 3600 (1 hour). The maximum allowable value is 7862400 (91 days).\n\nThe signer implementation is then free to issue a certificate with any lifetime *shorter* than MaxExpirationSeconds, but no shorter than 3600 seconds (1 hour). This constraint is enforced by kube-apiserver. `kubernetes.io` signers will never issue certificates with a lifetime longer than 24 hours.", + Type: []string{"integer"}, + Format: "int32", + }, + }, + "credentialBundlePath": { + SchemaProps: spec.SchemaProps{ + Description: "Write the credential bundle at this path in the projected volume.\n\nThe credential bundle is a single file that contains multiple PEM blocks. The first PEM block is a PRIVATE KEY block, containing a PKCS#8 private key.\n\nThe remaining blocks are CERTIFICATE blocks, containing the issued certificate chain from the signer (leaf and any intermediates).\n\nUsing credentialBundlePath lets your Pod's application code make a single atomic read that retrieves a consistent key and certificate chain. If you project them to separate files, your application code will need to additionally check that the leaf certificate was issued to the key.", + Type: []string{"string"}, + Format: "", + }, + }, + "keyPath": { + SchemaProps: spec.SchemaProps{ + Description: "Write the key at this path in the projected volume.\n\nMost applications should use credentialBundlePath. When using keyPath and certificateChainPath, your application needs to check that the key and leaf certificate are consistent, because it is possible to read the files mid-rotation.", + Type: []string{"string"}, + Format: "", + }, + }, + "certificateChainPath": { + SchemaProps: spec.SchemaProps{ + Description: "Write the certificate chain at this path in the projected volume.\n\nMost applications should use credentialBundlePath. When using keyPath and certificateChainPath, your application needs to check that the key and leaf certificate are consistent, because it is possible to read the files mid-rotation.", + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"signerName", "keyType"}, + }, + }, + } +} + +func schema_k8sio_api_core_v1_PodCondition(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "PodCondition contains details for the current condition of this pod.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "type": { + SchemaProps: spec.SchemaProps{ + Description: "Type is the type of the condition. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#pod-conditions", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "observedGeneration": { + SchemaProps: spec.SchemaProps{ + Description: "If set, this represents the .metadata.generation that the pod condition was set based upon. This is an alpha field. Enable PodObservedGenerationTracking to be able to use this field.", + Type: []string{"integer"}, + Format: "int64", + }, + }, + "status": { + SchemaProps: spec.SchemaProps{ + Description: "Status is the status of the condition. Can be True, False, Unknown. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#pod-conditions", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "lastProbeTime": { + SchemaProps: spec.SchemaProps{ + Description: "Last time we probed the condition.", + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.Time"), + }, + }, + "lastTransitionTime": { + SchemaProps: spec.SchemaProps{ + Description: "Last time the condition transitioned from one status to another.", + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.Time"), + }, + }, + "reason": { + SchemaProps: spec.SchemaProps{ + Description: "Unique, one-word, CamelCase reason for the condition's last transition.", + Type: []string{"string"}, + Format: "", + }, + }, + "message": { + SchemaProps: spec.SchemaProps{ + Description: "Human-readable message indicating details about last transition.", + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"type", "status"}, + }, + }, + Dependencies: []string{ + "k8s.io/apimachinery/pkg/apis/meta/v1.Time"}, + } +} + +func schema_k8sio_api_core_v1_PodDNSConfig(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "PodDNSConfig defines the DNS parameters of a pod in addition to those generated from DNSPolicy.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "nameservers": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "A list of DNS name server IP addresses. This will be appended to the base nameservers generated from DNSPolicy. Duplicated nameservers will be removed.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + "searches": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "A list of DNS search domains for host-name lookup. This will be appended to the base search paths generated from DNSPolicy. Duplicated search paths will be removed.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + "options": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "A list of DNS resolver options. This will be merged with the base options generated from DNSPolicy. Duplicated entries will be removed. Resolution options given in Options will override those that appear in the base DNSPolicy.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/api/core/v1.PodDNSConfigOption"), + }, + }, + }, + }, + }, + }, + }, + }, + Dependencies: []string{ + "k8s.io/api/core/v1.PodDNSConfigOption"}, + } +} + +func schema_k8sio_api_core_v1_PodDNSConfigOption(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "PodDNSConfigOption defines DNS resolver options of a pod.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "name": { + SchemaProps: spec.SchemaProps{ + Description: "Name is this DNS resolver option's name. Required.", + Type: []string{"string"}, + Format: "", + }, + }, + "value": { + SchemaProps: spec.SchemaProps{ + Description: "Value is this DNS resolver option's value.", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + } +} + +func schema_k8sio_api_core_v1_PodExecOptions(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "PodExecOptions is the query options to a Pod's remote exec call.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", + }, + }, + "stdin": { + SchemaProps: spec.SchemaProps{ + Description: "Redirect the standard input stream of the pod for this call. Defaults to false.", + Type: []string{"boolean"}, + Format: "", + }, + }, + "stdout": { + SchemaProps: spec.SchemaProps{ + Description: "Redirect the standard output stream of the pod for this call.", + Type: []string{"boolean"}, + Format: "", + }, + }, + "stderr": { + SchemaProps: spec.SchemaProps{ + Description: "Redirect the standard error stream of the pod for this call.", + Type: []string{"boolean"}, + Format: "", + }, + }, + "tty": { + SchemaProps: spec.SchemaProps{ + Description: "TTY if true indicates that a tty will be allocated for the exec call. Defaults to false.", + Type: []string{"boolean"}, + Format: "", + }, + }, + "container": { + SchemaProps: spec.SchemaProps{ + Description: "Container in which to execute the command. Defaults to only container if there is only one container in the pod.", + Type: []string{"string"}, + Format: "", + }, + }, + "command": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "Command is the remote command to execute. argv array. Not executed within a shell.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + }, + Required: []string{"command"}, + }, + }, + } +} + +func schema_k8sio_api_core_v1_PodExtendedResourceClaimStatus(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "PodExtendedResourceClaimStatus is stored in the PodStatus for the extended resource requests backed by DRA. It stores the generated name for the corresponding special ResourceClaim created by the scheduler.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "requestMappings": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "RequestMappings identifies the mapping of to device request in the generated ResourceClaim.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/api/core/v1.ContainerExtendedResourceRequest"), + }, + }, + }, + }, + }, + "resourceClaimName": { + SchemaProps: spec.SchemaProps{ + Description: "ResourceClaimName is the name of the ResourceClaim that was generated for the Pod in the namespace of the Pod.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"requestMappings", "resourceClaimName"}, + }, + }, + Dependencies: []string{ + "k8s.io/api/core/v1.ContainerExtendedResourceRequest"}, + } +} + +func schema_k8sio_api_core_v1_PodIP(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "PodIP represents a single IP address allocated to the pod.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "ip": { + SchemaProps: spec.SchemaProps{ + Description: "IP is the IP address assigned to the pod", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"ip"}, + }, + }, + } +} + +func schema_k8sio_api_core_v1_PodList(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "PodList is a list of Pods.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", + }, + }, + "metadata": { + SchemaProps: spec.SchemaProps{ + Description: "Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"), + }, + }, + "items": { + SchemaProps: spec.SchemaProps{ + Description: "List of pods. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/api/core/v1.Pod"), + }, + }, + }, + }, + }, + }, + Required: []string{"items"}, + }, + }, + Dependencies: []string{ + "k8s.io/api/core/v1.Pod", "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"}, + } +} + +func schema_k8sio_api_core_v1_PodLogOptions(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "PodLogOptions is the query options for a Pod's logs REST call.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", + }, + }, + "container": { + SchemaProps: spec.SchemaProps{ + Description: "The container for which to stream logs. Defaults to only container if there is one container in the pod.", + Type: []string{"string"}, + Format: "", + }, + }, + "follow": { + SchemaProps: spec.SchemaProps{ + Description: "Follow the log stream of the pod. Defaults to false.", + Type: []string{"boolean"}, + Format: "", + }, + }, + "previous": { + SchemaProps: spec.SchemaProps{ + Description: "Return previous terminated container logs. Defaults to false.", + Type: []string{"boolean"}, + Format: "", + }, + }, + "sinceSeconds": { + SchemaProps: spec.SchemaProps{ + Description: "A relative time in seconds before the current time from which to show logs. If this value precedes the time a pod was started, only logs since the pod start will be returned. If this value is in the future, no logs will be returned. Only one of sinceSeconds or sinceTime may be specified.", + Type: []string{"integer"}, + Format: "int64", + }, + }, + "sinceTime": { + SchemaProps: spec.SchemaProps{ + Description: "An RFC3339 timestamp from which to show logs. If this value precedes the time a pod was started, only logs since the pod start will be returned. If this value is in the future, no logs will be returned. Only one of sinceSeconds or sinceTime may be specified.", + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.Time"), + }, + }, + "timestamps": { + SchemaProps: spec.SchemaProps{ + Description: "If true, add an RFC3339 or RFC3339Nano timestamp at the beginning of every line of log output. Defaults to false.", + Type: []string{"boolean"}, + Format: "", + }, + }, + "tailLines": { + SchemaProps: spec.SchemaProps{ + Description: "If set, the number of lines from the end of the logs to show. If not specified, logs are shown from the creation of the container or sinceSeconds or sinceTime. Note that when \"TailLines\" is specified, \"Stream\" can only be set to nil or \"All\".", + Type: []string{"integer"}, + Format: "int64", + }, + }, + "limitBytes": { + SchemaProps: spec.SchemaProps{ + Description: "If set, the number of bytes to read from the server before terminating the log output. This may not display a complete final line of logging, and may return slightly more or slightly less than the specified limit.", + Type: []string{"integer"}, + Format: "int64", + }, + }, + "insecureSkipTLSVerifyBackend": { + SchemaProps: spec.SchemaProps{ + Description: "insecureSkipTLSVerifyBackend indicates that the apiserver should not confirm the validity of the serving certificate of the backend it is connecting to. This will make the HTTPS connection between the apiserver and the backend insecure. This means the apiserver cannot verify the log data it is receiving came from the real kubelet. If the kubelet is configured to verify the apiserver's TLS credentials, it does not mean the connection to the real kubelet is vulnerable to a man in the middle attack (e.g. an attacker could not intercept the actual log data coming from the real kubelet).", + Type: []string{"boolean"}, + Format: "", + }, + }, + "stream": { + SchemaProps: spec.SchemaProps{ + Description: "Specify which container log stream to return to the client. Acceptable values are \"All\", \"Stdout\" and \"Stderr\". If not specified, \"All\" is used, and both stdout and stderr are returned interleaved. Note that when \"TailLines\" is specified, \"Stream\" can only be set to nil or \"All\".", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + Dependencies: []string{ + "k8s.io/apimachinery/pkg/apis/meta/v1.Time"}, + } +} + +func schema_k8sio_api_core_v1_PodOS(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "PodOS defines the OS parameters of a pod.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "name": { + SchemaProps: spec.SchemaProps{ + Description: "Name is the name of the operating system. The currently supported values are linux and windows. Additional value may be defined in future and can be one of: https://github.com/opencontainers/runtime-spec/blob/master/config.md#platform-specific-configuration Clients should expect to handle additional values and treat unrecognized values in this field as os: null", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"name"}, + }, + }, + } +} + +func schema_k8sio_api_core_v1_PodPortForwardOptions(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "PodPortForwardOptions is the query options to a Pod's port forward call when using WebSockets. The `port` query parameter must specify the port or ports (comma separated) to forward over. Port forwarding over SPDY does not use these options. It requires the port to be passed in the `port` header as part of request.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", + }, + }, + "ports": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "List of ports to forward Required when using WebSockets", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: 0, + Type: []string{"integer"}, + Format: "int32", + }, + }, + }, + }, + }, + }, + }, + }, + } +} + +func schema_k8sio_api_core_v1_PodProxyOptions(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "PodProxyOptions is the query options to a Pod's proxy call.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", + }, + }, + "path": { + SchemaProps: spec.SchemaProps{ + Description: "Path is the URL path to use for the current proxy request to pod.", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + } +} + +func schema_k8sio_api_core_v1_PodReadinessGate(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "PodReadinessGate contains the reference to a pod condition", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "conditionType": { + SchemaProps: spec.SchemaProps{ + Description: "ConditionType refers to a condition in the pod's condition list with matching type.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"conditionType"}, + }, + }, + } +} + +func schema_k8sio_api_core_v1_PodResourceClaim(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "PodResourceClaim references exactly one ResourceClaim, either directly or by naming a ResourceClaimTemplate which is then turned into a ResourceClaim for the pod.\n\nIt adds a name to it that uniquely identifies the ResourceClaim inside the Pod. Containers that need access to the ResourceClaim reference it with this name.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "name": { + SchemaProps: spec.SchemaProps{ + Description: "Name uniquely identifies this resource claim inside the pod. This must be a DNS_LABEL.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "resourceClaimName": { + SchemaProps: spec.SchemaProps{ + Description: "ResourceClaimName is the name of a ResourceClaim object in the same namespace as this pod.\n\nExactly one of ResourceClaimName and ResourceClaimTemplateName must be set.", + Type: []string{"string"}, + Format: "", + }, + }, + "resourceClaimTemplateName": { + SchemaProps: spec.SchemaProps{ + Description: "ResourceClaimTemplateName is the name of a ResourceClaimTemplate object in the same namespace as this pod.\n\nThe template will be used to create a new ResourceClaim, which will be bound to this pod. When this pod is deleted, the ResourceClaim will also be deleted. The pod name and resource name, along with a generated component, will be used to form a unique name for the ResourceClaim, which will be recorded in pod.status.resourceClaimStatuses.\n\nThis field is immutable and no changes will be made to the corresponding ResourceClaim by the control plane after creating the ResourceClaim.\n\nExactly one of ResourceClaimName and ResourceClaimTemplateName must be set.", + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"name"}, + }, + }, + } +} + +func schema_k8sio_api_core_v1_PodResourceClaimStatus(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "PodResourceClaimStatus is stored in the PodStatus for each PodResourceClaim which references a ResourceClaimTemplate. It stores the generated name for the corresponding ResourceClaim.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "name": { + SchemaProps: spec.SchemaProps{ + Description: "Name uniquely identifies this resource claim inside the pod. This must match the name of an entry in pod.spec.resourceClaims, which implies that the string must be a DNS_LABEL.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "resourceClaimName": { + SchemaProps: spec.SchemaProps{ + Description: "ResourceClaimName is the name of the ResourceClaim that was generated for the Pod in the namespace of the Pod. If this is unset, then generating a ResourceClaim was not necessary. The pod.spec.resourceClaims entry can be ignored in this case.", + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"name"}, + }, + }, + } +} + +func schema_k8sio_api_core_v1_PodSchedulingGate(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "PodSchedulingGate is associated to a Pod to guard its scheduling.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "name": { + SchemaProps: spec.SchemaProps{ + Description: "Name of the scheduling gate. Each scheduling gate must have a unique name field.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"name"}, + }, + }, + } +} + +func schema_k8sio_api_core_v1_PodSecurityContext(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "PodSecurityContext holds pod-level security attributes and common container settings. Some fields are also present in container.securityContext. Field values of container.securityContext take precedence over field values of PodSecurityContext.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "seLinuxOptions": { + SchemaProps: spec.SchemaProps{ + Description: "The SELinux context to be applied to all containers. If unspecified, the container runtime will allocate a random SELinux context for each container. May also be set in SecurityContext. If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence for that container. Note that this field cannot be set when spec.os.name is windows.", + Ref: ref("k8s.io/api/core/v1.SELinuxOptions"), + }, + }, + "windowsOptions": { + SchemaProps: spec.SchemaProps{ + Description: "The Windows specific settings applied to all containers. If unspecified, the options within a container's SecurityContext will be used. If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence. Note that this field cannot be set when spec.os.name is linux.", + Ref: ref("k8s.io/api/core/v1.WindowsSecurityContextOptions"), + }, + }, + "runAsUser": { + SchemaProps: spec.SchemaProps{ + Description: "The UID to run the entrypoint of the container process. Defaults to user specified in image metadata if unspecified. May also be set in SecurityContext. If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence for that container. Note that this field cannot be set when spec.os.name is windows.", + Type: []string{"integer"}, + Format: "int64", + }, + }, + "runAsGroup": { + SchemaProps: spec.SchemaProps{ + Description: "The GID to run the entrypoint of the container process. Uses runtime default if unset. May also be set in SecurityContext. If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence for that container. Note that this field cannot be set when spec.os.name is windows.", + Type: []string{"integer"}, + Format: "int64", + }, + }, + "runAsNonRoot": { + SchemaProps: spec.SchemaProps{ + Description: "Indicates that the container must run as a non-root user. If true, the Kubelet will validate the image at runtime to ensure that it does not run as UID 0 (root) and fail to start the container if it does. If unset or false, no such validation will be performed. May also be set in SecurityContext. If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence.", + Type: []string{"boolean"}, + Format: "", + }, + }, + "supplementalGroups": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "A list of groups applied to the first process run in each container, in addition to the container's primary GID and fsGroup (if specified). If the SupplementalGroupsPolicy feature is enabled, the supplementalGroupsPolicy field determines whether these are in addition to or instead of any group memberships defined in the container image. If unspecified, no additional groups are added, though group memberships defined in the container image may still be used, depending on the supplementalGroupsPolicy field. Note that this field cannot be set when spec.os.name is windows.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: 0, + Type: []string{"integer"}, + Format: "int64", + }, + }, + }, + }, + }, + "supplementalGroupsPolicy": { + SchemaProps: spec.SchemaProps{ + Description: "Defines how supplemental groups of the first container processes are calculated. Valid values are \"Merge\" and \"Strict\". If not specified, \"Merge\" is used. (Alpha) Using the field requires the SupplementalGroupsPolicy feature gate to be enabled and the container runtime must implement support for this feature. Note that this field cannot be set when spec.os.name is windows.\n\nPossible enum values:\n - `\"Merge\"` means that the container's provided SupplementalGroups and FsGroup (specified in SecurityContext) will be merged with the primary user's groups as defined in the container image (in /etc/group).\n - `\"Strict\"` means that the container's provided SupplementalGroups and FsGroup (specified in SecurityContext) will be used instead of any groups defined in the container image.", + Type: []string{"string"}, + Format: "", + Enum: []interface{}{"Merge", "Strict"}, + }, + }, + "fsGroup": { + SchemaProps: spec.SchemaProps{ + Description: "A special supplemental group that applies to all containers in a pod. Some volume types allow the Kubelet to change the ownership of that volume to be owned by the pod:\n\n1. The owning GID will be the FSGroup 2. The setgid bit is set (new files created in the volume will be owned by FSGroup) 3. The permission bits are OR'd with rw-rw----\n\nIf unset, the Kubelet will not modify the ownership and permissions of any volume. Note that this field cannot be set when spec.os.name is windows.", + Type: []string{"integer"}, + Format: "int64", + }, + }, + "sysctls": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "Sysctls hold a list of namespaced sysctls used for the pod. Pods with unsupported sysctls (by the container runtime) might fail to launch. Note that this field cannot be set when spec.os.name is windows.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/api/core/v1.Sysctl"), + }, + }, + }, + }, + }, + "fsGroupChangePolicy": { + SchemaProps: spec.SchemaProps{ + Description: "fsGroupChangePolicy defines behavior of changing ownership and permission of the volume before being exposed inside Pod. This field will only apply to volume types which support fsGroup based ownership(and permissions). It will have no effect on ephemeral volume types such as: secret, configmaps and emptydir. Valid values are \"OnRootMismatch\" and \"Always\". If not specified, \"Always\" is used. Note that this field cannot be set when spec.os.name is windows.\n\nPossible enum values:\n - `\"Always\"` indicates that volume's ownership and permissions should always be changed whenever volume is mounted inside a Pod. This the default behavior.\n - `\"OnRootMismatch\"` indicates that volume's ownership and permissions will be changed only when permission and ownership of root directory does not match with expected permissions on the volume. This can help shorten the time it takes to change ownership and permissions of a volume.", + Type: []string{"string"}, + Format: "", + Enum: []interface{}{"Always", "OnRootMismatch"}, + }, + }, + "seccompProfile": { + SchemaProps: spec.SchemaProps{ + Description: "The seccomp options to use by the containers in this pod. Note that this field cannot be set when spec.os.name is windows.", + Ref: ref("k8s.io/api/core/v1.SeccompProfile"), + }, + }, + "appArmorProfile": { + SchemaProps: spec.SchemaProps{ + Description: "appArmorProfile is the AppArmor options to use by the containers in this pod. Note that this field cannot be set when spec.os.name is windows.", + Ref: ref("k8s.io/api/core/v1.AppArmorProfile"), + }, + }, + "seLinuxChangePolicy": { + SchemaProps: spec.SchemaProps{ + Description: "seLinuxChangePolicy defines how the container's SELinux label is applied to all volumes used by the Pod. It has no effect on nodes that do not support SELinux or to volumes does not support SELinux. Valid values are \"MountOption\" and \"Recursive\".\n\n\"Recursive\" means relabeling of all files on all Pod volumes by the container runtime. This may be slow for large volumes, but allows mixing privileged and unprivileged Pods sharing the same volume on the same node.\n\n\"MountOption\" mounts all eligible Pod volumes with `-o context` mount option. This requires all Pods that share the same volume to use the same SELinux label. It is not possible to share the same volume among privileged and unprivileged Pods. Eligible volumes are in-tree FibreChannel and iSCSI volumes, and all CSI volumes whose CSI driver announces SELinux support by setting spec.seLinuxMount: true in their CSIDriver instance. Other volumes are always re-labelled recursively. \"MountOption\" value is allowed only when SELinuxMount feature gate is enabled.\n\nIf not specified and SELinuxMount feature gate is enabled, \"MountOption\" is used. If not specified and SELinuxMount feature gate is disabled, \"MountOption\" is used for ReadWriteOncePod volumes and \"Recursive\" for all other volumes.\n\nThis field affects only Pods that have SELinux label set, either in PodSecurityContext or in SecurityContext of all containers.\n\nAll Pods that use the same volume should use the same seLinuxChangePolicy, otherwise some pods can get stuck in ContainerCreating state. Note that this field cannot be set when spec.os.name is windows.", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + Dependencies: []string{ + "k8s.io/api/core/v1.AppArmorProfile", "k8s.io/api/core/v1.SELinuxOptions", "k8s.io/api/core/v1.SeccompProfile", "k8s.io/api/core/v1.Sysctl", "k8s.io/api/core/v1.WindowsSecurityContextOptions"}, + } +} + +func schema_k8sio_api_core_v1_PodSignature(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "Describes the class of pods that should avoid this node. Exactly one field should be set.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "podController": { + SchemaProps: spec.SchemaProps{ + Description: "Reference to controller whose pods should avoid this node.", + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.OwnerReference"), + }, + }, + }, + }, + }, + Dependencies: []string{ + "k8s.io/apimachinery/pkg/apis/meta/v1.OwnerReference"}, + } +} + +func schema_k8sio_api_core_v1_PodSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "PodSpec is a description of a pod.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "volumes": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-map-keys": []interface{}{ + "name", + }, + "x-kubernetes-list-type": "map", + "x-kubernetes-patch-merge-key": "name", + "x-kubernetes-patch-strategy": "merge,retainKeys", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "List of volumes that can be mounted by containers belonging to the pod. More info: https://kubernetes.io/docs/concepts/storage/volumes", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/api/core/v1.Volume"), + }, + }, + }, + }, + }, + "initContainers": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-map-keys": []interface{}{ + "name", + }, + "x-kubernetes-list-type": "map", + "x-kubernetes-patch-merge-key": "name", + "x-kubernetes-patch-strategy": "merge", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "List of initialization containers belonging to the pod. Init containers are executed in order prior to containers being started. If any init container fails, the pod is considered to have failed and is handled according to its restartPolicy. The name for an init container or normal container must be unique among all containers. Init containers may not have Lifecycle actions, Readiness probes, Liveness probes, or Startup probes. The resourceRequirements of an init container are taken into account during scheduling by finding the highest request/limit for each resource type, and then using the max of that value or the sum of the normal containers. Limits are applied to init containers in a similar fashion. Init containers cannot currently be added or removed. Cannot be updated. More info: https://kubernetes.io/docs/concepts/workloads/pods/init-containers/", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/api/core/v1.Container"), + }, + }, + }, + }, + }, + "containers": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-map-keys": []interface{}{ + "name", + }, + "x-kubernetes-list-type": "map", + "x-kubernetes-patch-merge-key": "name", + "x-kubernetes-patch-strategy": "merge", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "List of containers belonging to the pod. Containers cannot currently be added or removed. There must be at least one container in a Pod. Cannot be updated.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/api/core/v1.Container"), + }, + }, + }, + }, + }, + "ephemeralContainers": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-map-keys": []interface{}{ + "name", + }, + "x-kubernetes-list-type": "map", + "x-kubernetes-patch-merge-key": "name", + "x-kubernetes-patch-strategy": "merge", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "List of ephemeral containers run in this pod. Ephemeral containers may be run in an existing pod to perform user-initiated actions such as debugging. This list cannot be specified when creating a pod, and it cannot be modified by updating the pod spec. In order to add an ephemeral container to an existing pod, use the pod's ephemeralcontainers subresource.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/api/core/v1.EphemeralContainer"), + }, + }, + }, + }, + }, + "restartPolicy": { + SchemaProps: spec.SchemaProps{ + Description: "Restart policy for all containers within the pod. One of Always, OnFailure, Never. In some contexts, only a subset of those values may be permitted. Default to Always. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle/#restart-policy\n\nPossible enum values:\n - `\"Always\"`\n - `\"Never\"`\n - `\"OnFailure\"`", + Type: []string{"string"}, + Format: "", + Enum: []interface{}{"Always", "Never", "OnFailure"}, + }, + }, + "terminationGracePeriodSeconds": { + SchemaProps: spec.SchemaProps{ + Description: "Optional duration in seconds the pod needs to terminate gracefully. May be decreased in delete request. Value must be non-negative integer. The value zero indicates stop immediately via the kill signal (no opportunity to shut down). If this value is nil, the default grace period will be used instead. The grace period is the duration in seconds after the processes running in the pod are sent a termination signal and the time when the processes are forcibly halted with a kill signal. Set this value longer than the expected cleanup time for your process. Defaults to 30 seconds.", + Type: []string{"integer"}, + Format: "int64", + }, + }, + "activeDeadlineSeconds": { + SchemaProps: spec.SchemaProps{ + Description: "Optional duration in seconds the pod may be active on the node relative to StartTime before the system will actively try to mark it failed and kill associated containers. Value must be a positive integer.", + Type: []string{"integer"}, + Format: "int64", + }, + }, + "dnsPolicy": { + SchemaProps: spec.SchemaProps{ + Description: "Set DNS policy for the pod. Defaults to \"ClusterFirst\". Valid values are 'ClusterFirstWithHostNet', 'ClusterFirst', 'Default' or 'None'. DNS parameters given in DNSConfig will be merged with the policy selected with DNSPolicy. To have DNS options set along with hostNetwork, you have to specify DNS policy explicitly to 'ClusterFirstWithHostNet'.\n\nPossible enum values:\n - `\"ClusterFirst\"` indicates that the pod should use cluster DNS first unless hostNetwork is true, if it is available, then fall back on the default (as determined by kubelet) DNS settings.\n - `\"ClusterFirstWithHostNet\"` indicates that the pod should use cluster DNS first, if it is available, then fall back on the default (as determined by kubelet) DNS settings.\n - `\"Default\"` indicates that the pod should use the default (as determined by kubelet) DNS settings.\n - `\"None\"` indicates that the pod should use empty DNS settings. DNS parameters such as nameservers and search paths should be defined via DNSConfig.", + Type: []string{"string"}, + Format: "", + Enum: []interface{}{"ClusterFirst", "ClusterFirstWithHostNet", "Default", "None"}, + }, + }, + "nodeSelector": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-map-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "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/", + Type: []string{"object"}, + AdditionalProperties: &spec.SchemaOrBool{ + Allows: true, + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + "serviceAccountName": { + SchemaProps: spec.SchemaProps{ + Description: "ServiceAccountName is the name of the ServiceAccount to use to run this pod. More info: https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/", + Type: []string{"string"}, + Format: "", + }, + }, + "serviceAccount": { + SchemaProps: spec.SchemaProps{ + Description: "DeprecatedServiceAccount is a deprecated alias for ServiceAccountName. Deprecated: Use serviceAccountName instead.", + Type: []string{"string"}, + Format: "", + }, + }, + "automountServiceAccountToken": { + SchemaProps: spec.SchemaProps{ + Description: "AutomountServiceAccountToken indicates whether a service account token should be automatically mounted.", + Type: []string{"boolean"}, + Format: "", + }, + }, + "nodeName": { + SchemaProps: spec.SchemaProps{ + Description: "NodeName indicates in which node this pod is scheduled. If empty, this pod is a candidate for scheduling by the scheduler defined in schedulerName. Once this field is set, the kubelet for this node becomes responsible for the lifecycle of this pod. This field should not be used to express a desire for the pod to be scheduled on a specific node. https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#nodename", + Type: []string{"string"}, + Format: "", + }, + }, + "hostNetwork": { + SchemaProps: spec.SchemaProps{ + Description: "Host networking requested for this pod. Use the host's network namespace. When using HostNetwork you should specify ports so the scheduler is aware. When `hostNetwork` is true, specified `hostPort` fields in port definitions must match `containerPort`, and unspecified `hostPort` fields in port definitions are defaulted to match `containerPort`. Default to false.", + Type: []string{"boolean"}, + Format: "", + }, + }, + "hostPID": { + SchemaProps: spec.SchemaProps{ + Description: "Use the host's pid namespace. Optional: Default to false.", + Type: []string{"boolean"}, + Format: "", + }, + }, + "hostIPC": { + SchemaProps: spec.SchemaProps{ + Description: "Use the host's ipc namespace. Optional: Default to false.", + Type: []string{"boolean"}, + Format: "", + }, + }, + "shareProcessNamespace": { + SchemaProps: spec.SchemaProps{ + Description: "Share a single process namespace between all of the containers in a pod. When this is set containers will be able to view and signal processes from other containers in the same pod, and the first process in each container will not be assigned PID 1. HostPID and ShareProcessNamespace cannot both be set. Optional: Default to false.", + Type: []string{"boolean"}, + Format: "", + }, + }, + "securityContext": { + SchemaProps: spec.SchemaProps{ + Description: "SecurityContext holds pod-level security attributes and common container settings. Optional: Defaults to empty. See type description for default values of each field.", + Ref: ref("k8s.io/api/core/v1.PodSecurityContext"), + }, + }, + "imagePullSecrets": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-map-keys": []interface{}{ + "name", + }, + "x-kubernetes-list-type": "map", + "x-kubernetes-patch-merge-key": "name", + "x-kubernetes-patch-strategy": "merge", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "ImagePullSecrets is an optional list of references to secrets in the same namespace to use for pulling any of the images used by this PodSpec. If specified, these secrets will be passed to individual puller implementations for them to use. More info: https://kubernetes.io/docs/concepts/containers/images#specifying-imagepullsecrets-on-a-pod", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/api/core/v1.LocalObjectReference"), + }, + }, + }, + }, + }, + "hostname": { + SchemaProps: spec.SchemaProps{ + Description: "Specifies the hostname of the Pod If not specified, the pod's hostname will be set to a system-defined value.", + Type: []string{"string"}, + Format: "", + }, + }, + "subdomain": { + SchemaProps: spec.SchemaProps{ + Description: "If specified, the fully qualified Pod hostname will be \"...svc.\". If not specified, the pod will not have a domainname at all.", + Type: []string{"string"}, + Format: "", + }, + }, + "affinity": { + SchemaProps: spec.SchemaProps{ + Description: "If specified, the pod's scheduling constraints", + Ref: ref("k8s.io/api/core/v1.Affinity"), + }, + }, + "schedulerName": { + SchemaProps: spec.SchemaProps{ + Description: "If specified, the pod will be dispatched by specified scheduler. If not specified, the pod will be dispatched by default scheduler.", + Type: []string{"string"}, + Format: "", + }, + }, + "tolerations": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "If specified, the pod's tolerations.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/api/core/v1.Toleration"), + }, + }, + }, + }, + }, + "hostAliases": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-map-keys": []interface{}{ + "ip", + }, + "x-kubernetes-list-type": "map", + "x-kubernetes-patch-merge-key": "ip", + "x-kubernetes-patch-strategy": "merge", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "HostAliases is an optional list of hosts and IPs that will be injected into the pod's hosts file if specified.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/api/core/v1.HostAlias"), + }, + }, + }, + }, + }, + "priorityClassName": { + SchemaProps: spec.SchemaProps{ + Description: "If specified, indicates the pod's priority. \"system-node-critical\" and \"system-cluster-critical\" are two special keywords which indicate the highest priorities with the former being the highest priority. Any other name must be defined by creating a PriorityClass object with that name. If not specified, the pod priority will be default or zero if there is no default.", + Type: []string{"string"}, + Format: "", + }, + }, + "priority": { + SchemaProps: spec.SchemaProps{ + Description: "The priority value. Various system components use this field to find the priority of the pod. When Priority Admission Controller is enabled, it prevents users from setting this field. The admission controller populates this field from PriorityClassName. The higher the value, the higher the priority.", + Type: []string{"integer"}, + Format: "int32", + }, + }, + "dnsConfig": { + SchemaProps: spec.SchemaProps{ + Description: "Specifies the DNS parameters of a pod. Parameters specified here will be merged to the generated DNS configuration based on DNSPolicy.", + Ref: ref("k8s.io/api/core/v1.PodDNSConfig"), + }, + }, + "readinessGates": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "If specified, all readiness gates will be evaluated for pod readiness. A pod is ready when all its containers are ready AND all conditions specified in the readiness gates have status equal to \"True\" More info: https://git.k8s.io/enhancements/keps/sig-network/580-pod-readiness-gates", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/api/core/v1.PodReadinessGate"), + }, + }, + }, + }, + }, + "runtimeClassName": { + SchemaProps: spec.SchemaProps{ + Description: "RuntimeClassName refers to a RuntimeClass object in the node.k8s.io group, which should be used to run this pod. If no RuntimeClass resource matches the named class, the pod will not be run. If unset or empty, the \"legacy\" RuntimeClass will be used, which is an implicit class with an empty definition that uses the default runtime handler. More info: https://git.k8s.io/enhancements/keps/sig-node/585-runtime-class", + Type: []string{"string"}, + Format: "", + }, + }, + "enableServiceLinks": { + SchemaProps: spec.SchemaProps{ + Description: "EnableServiceLinks indicates whether information about services should be injected into pod's environment variables, matching the syntax of Docker links. Optional: Defaults to true.", + Type: []string{"boolean"}, + Format: "", + }, + }, + "preemptionPolicy": { + SchemaProps: spec.SchemaProps{ + Description: "PreemptionPolicy is the Policy for preempting pods with lower priority. One of Never, PreemptLowerPriority. Defaults to PreemptLowerPriority if unset.\n\nPossible enum values:\n - `\"Never\"` means that pod never preempts other pods with lower priority.\n - `\"PreemptLowerPriority\"` means that pod can preempt other pods with lower priority.", + Type: []string{"string"}, + Format: "", + Enum: []interface{}{"Never", "PreemptLowerPriority"}, + }, + }, + "overhead": { + SchemaProps: spec.SchemaProps{ + Description: "Overhead represents the resource overhead associated with running a pod for a given RuntimeClass. This field will be autopopulated at admission time by the RuntimeClass admission controller. If the RuntimeClass admission controller is enabled, overhead must not be set in Pod create requests. The RuntimeClass admission controller will reject Pod create requests which have the overhead already set. If RuntimeClass is configured and selected in the PodSpec, Overhead will be set to the value defined in the corresponding RuntimeClass, otherwise it will remain unset and treated as zero. More info: https://git.k8s.io/enhancements/keps/sig-node/688-pod-overhead/README.md", + Type: []string{"object"}, + AdditionalProperties: &spec.SchemaOrBool{ + Allows: true, + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Ref: ref("k8s.io/apimachinery/pkg/api/resource.Quantity"), + }, + }, + }, + }, + }, + "topologySpreadConstraints": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-map-keys": []interface{}{ + "topologyKey", + "whenUnsatisfiable", + }, + "x-kubernetes-list-type": "map", + "x-kubernetes-patch-merge-key": "topologyKey", + "x-kubernetes-patch-strategy": "merge", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "TopologySpreadConstraints describes how a group of pods ought to spread across topology domains. Scheduler will schedule pods in a way which abides by the constraints. All topologySpreadConstraints are ANDed.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/api/core/v1.TopologySpreadConstraint"), + }, + }, + }, + }, + }, + "setHostnameAsFQDN": { + SchemaProps: spec.SchemaProps{ + Description: "If true the pod's hostname will be configured as the pod's FQDN, rather than the leaf name (the default). In Linux containers, this means setting the FQDN in the hostname field of the kernel (the nodename field of struct utsname). In Windows containers, this means setting the registry value of hostname for the registry key HKEY_LOCAL_MACHINE\\\\SYSTEM\\\\CurrentControlSet\\\\Services\\\\Tcpip\\\\Parameters to FQDN. If a pod does not have FQDN, this has no effect. Default to false.", + Type: []string{"boolean"}, + Format: "", + }, + }, + "os": { + SchemaProps: spec.SchemaProps{ + Description: "Specifies the OS of the containers in the pod. Some pod and container fields are restricted if this is set.\n\nIf the OS field is set to linux, the following fields must be unset: -securityContext.windowsOptions\n\nIf the OS field is set to windows, following fields must be unset: - spec.hostPID - spec.hostIPC - spec.hostUsers - spec.resources - spec.securityContext.appArmorProfile - spec.securityContext.seLinuxOptions - spec.securityContext.seccompProfile - spec.securityContext.fsGroup - spec.securityContext.fsGroupChangePolicy - spec.securityContext.sysctls - spec.shareProcessNamespace - spec.securityContext.runAsUser - spec.securityContext.runAsGroup - spec.securityContext.supplementalGroups - spec.securityContext.supplementalGroupsPolicy - spec.containers[*].securityContext.appArmorProfile - spec.containers[*].securityContext.seLinuxOptions - spec.containers[*].securityContext.seccompProfile - spec.containers[*].securityContext.capabilities - spec.containers[*].securityContext.readOnlyRootFilesystem - spec.containers[*].securityContext.privileged - spec.containers[*].securityContext.allowPrivilegeEscalation - spec.containers[*].securityContext.procMount - spec.containers[*].securityContext.runAsUser - spec.containers[*].securityContext.runAsGroup", + Ref: ref("k8s.io/api/core/v1.PodOS"), + }, + }, + "hostUsers": { + SchemaProps: spec.SchemaProps{ + Description: "Use the host's user namespace. Optional: Default to true. If set to true or not present, the pod will be run in the host user namespace, useful for when the pod needs a feature only available to the host user namespace, such as loading a kernel module with CAP_SYS_MODULE. When set to false, a new userns is created for the pod. Setting false is useful for mitigating container breakout vulnerabilities even allowing users to run their containers as root without actually having root privileges on the host. This field is alpha-level and is only honored by servers that enable the UserNamespacesSupport feature.", + Type: []string{"boolean"}, + Format: "", + }, + }, + "schedulingGates": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-map-keys": []interface{}{ + "name", + }, + "x-kubernetes-list-type": "map", + "x-kubernetes-patch-merge-key": "name", + "x-kubernetes-patch-strategy": "merge", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "SchedulingGates is an opaque list of values that if specified will block scheduling the pod. If schedulingGates is not empty, the pod will stay in the SchedulingGated state and the scheduler will not attempt to schedule the pod.\n\nSchedulingGates can only be set at pod creation time, and be removed only afterwards.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/api/core/v1.PodSchedulingGate"), + }, + }, + }, + }, + }, + "resourceClaims": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-map-keys": []interface{}{ + "name", + }, + "x-kubernetes-list-type": "map", + "x-kubernetes-patch-merge-key": "name", + "x-kubernetes-patch-strategy": "merge,retainKeys", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "ResourceClaims defines which ResourceClaims must be allocated and reserved before the Pod is allowed to start. The resources will be made available to those containers which consume them by name.\n\nThis is an alpha field and requires enabling the DynamicResourceAllocation feature gate.\n\nThis field is immutable.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/api/core/v1.PodResourceClaim"), + }, + }, + }, + }, + }, + "resources": { + SchemaProps: spec.SchemaProps{ + Description: "Resources is the total amount of CPU and Memory resources required by all containers in the pod. It supports specifying Requests and Limits for \"cpu\", \"memory\" and \"hugepages-\" resource names only. ResourceClaims are not supported.\n\nThis field enables fine-grained control over resource allocation for the entire pod, allowing resource sharing among containers in a pod.\n\nThis is an alpha field and requires enabling the PodLevelResources feature gate.", + Ref: ref("k8s.io/api/core/v1.ResourceRequirements"), + }, + }, + "hostnameOverride": { + SchemaProps: spec.SchemaProps{ + Description: "HostnameOverride specifies an explicit override for the pod's hostname as perceived by the pod. This field only specifies the pod's hostname and does not affect its DNS records. When this field is set to a non-empty string: - It takes precedence over the values set in `hostname` and `subdomain`. - The Pod's hostname will be set to this value. - `setHostnameAsFQDN` must be nil or set to false. - `hostNetwork` must be set to false.\n\nThis field must be a valid DNS subdomain as defined in RFC 1123 and contain at most 64 characters. Requires the HostnameOverride feature gate to be enabled.", + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"containers"}, + }, + }, + Dependencies: []string{ + "k8s.io/api/core/v1.Affinity", "k8s.io/api/core/v1.Container", "k8s.io/api/core/v1.EphemeralContainer", "k8s.io/api/core/v1.HostAlias", "k8s.io/api/core/v1.LocalObjectReference", "k8s.io/api/core/v1.PodDNSConfig", "k8s.io/api/core/v1.PodOS", "k8s.io/api/core/v1.PodReadinessGate", "k8s.io/api/core/v1.PodResourceClaim", "k8s.io/api/core/v1.PodSchedulingGate", "k8s.io/api/core/v1.PodSecurityContext", "k8s.io/api/core/v1.ResourceRequirements", "k8s.io/api/core/v1.Toleration", "k8s.io/api/core/v1.TopologySpreadConstraint", "k8s.io/api/core/v1.Volume", "k8s.io/apimachinery/pkg/api/resource.Quantity"}, + } +} + +func schema_k8sio_api_core_v1_PodStatus(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "PodStatus represents information about the status of a pod. Status may trail the actual state of a system, especially if the node that hosts the pod cannot contact the control plane.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "observedGeneration": { + SchemaProps: spec.SchemaProps{ + Description: "If set, this represents the .metadata.generation that the pod status was set based upon. This is an alpha field. Enable PodObservedGenerationTracking to be able to use this field.", + Type: []string{"integer"}, + Format: "int64", + }, + }, + "phase": { + SchemaProps: spec.SchemaProps{ + Description: "The phase of a Pod is a simple, high-level summary of where the Pod is in its lifecycle. The conditions array, the reason and message fields, and the individual container status arrays contain more detail about the pod's status. There are five possible phase values:\n\nPending: The pod has been accepted by the Kubernetes system, but one or more of the container images has not been created. This includes time before being scheduled as well as time spent downloading images over the network, which could take a while. Running: The pod has been bound to a node, and all of the containers have been created. At least one container is still running, or is in the process of starting or restarting. Succeeded: All containers in the pod have terminated in success, and will not be restarted. Failed: All containers in the pod have terminated, and at least one container has terminated in failure. The container either exited with non-zero status or was terminated by the system. Unknown: For some reason the state of the pod could not be obtained, typically due to an error in communicating with the host of the pod.\n\nMore info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#pod-phase\n\nPossible enum values:\n - `\"Failed\"` means that all containers in the pod have terminated, and at least one container has terminated in a failure (exited with a non-zero exit code or was stopped by the system).\n - `\"Pending\"` means the pod has been accepted by the system, but one or more of the containers has not been started. This includes time before being bound to a node, as well as time spent pulling images onto the host.\n - `\"Running\"` means the pod has been bound to a node and all of the containers have been started. At least one container is still running or is in the process of being restarted.\n - `\"Succeeded\"` means that all containers in the pod have voluntarily terminated with a container exit code of 0, and the system is not going to restart any of these containers.\n - `\"Unknown\"` means that for some reason the state of the pod could not be obtained, typically due to an error in communicating with the host of the pod. Deprecated: It isn't being set since 2015 (74da3b14b0c0f658b3bb8d2def5094686d0e9095)", + Type: []string{"string"}, + Format: "", + Enum: []interface{}{"Failed", "Pending", "Running", "Succeeded", "Unknown"}, + }, + }, + "conditions": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-map-keys": []interface{}{ + "type", + }, + "x-kubernetes-list-type": "map", + "x-kubernetes-patch-merge-key": "type", + "x-kubernetes-patch-strategy": "merge", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "Current service state of pod. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#pod-conditions", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/api/core/v1.PodCondition"), + }, + }, + }, + }, + }, + "message": { + SchemaProps: spec.SchemaProps{ + Description: "A human readable message indicating details about why the pod is in this condition.", + Type: []string{"string"}, + Format: "", + }, + }, + "reason": { + SchemaProps: spec.SchemaProps{ + Description: "A brief CamelCase message indicating details about why the pod is in this state. e.g. 'Evicted'", + Type: []string{"string"}, + Format: "", + }, + }, + "nominatedNodeName": { + SchemaProps: spec.SchemaProps{ + Description: "nominatedNodeName is set only when this pod preempts other pods on the node, but it cannot be scheduled right away as preemption victims receive their graceful termination periods. This field does not guarantee that the pod will be scheduled on this node. Scheduler may decide to place the pod elsewhere if other nodes become available sooner. Scheduler may also decide to give the resources on this node to a higher priority pod that is created after preemption. As a result, this field may be different than PodSpec.nodeName when the pod is scheduled.", + Type: []string{"string"}, + Format: "", + }, + }, + "hostIP": { + SchemaProps: spec.SchemaProps{ + Description: "hostIP holds the IP address of the host to which the pod is assigned. Empty if the pod has not started yet. A pod can be assigned to a node that has a problem in kubelet which in turns mean that HostIP will not be updated even if there is a node is assigned to pod", + Type: []string{"string"}, + Format: "", + }, + }, + "hostIPs": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + "x-kubernetes-patch-merge-key": "ip", + "x-kubernetes-patch-strategy": "merge", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "hostIPs holds the IP addresses allocated to the host. If this field is specified, the first entry must match the hostIP field. This list is empty if the pod has not started yet. A pod can be assigned to a node that has a problem in kubelet which in turns means that HostIPs will not be updated even if there is a node is assigned to this pod.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/api/core/v1.HostIP"), + }, + }, + }, + }, + }, + "podIP": { + SchemaProps: spec.SchemaProps{ + Description: "podIP address allocated to the pod. Routable at least within the cluster. Empty if not yet allocated.", + Type: []string{"string"}, + Format: "", + }, + }, + "podIPs": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-map-keys": []interface{}{ + "ip", + }, + "x-kubernetes-list-type": "map", + "x-kubernetes-patch-merge-key": "ip", + "x-kubernetes-patch-strategy": "merge", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "podIPs holds the IP addresses allocated to the pod. If this field is specified, the 0th entry must match the podIP field. Pods may be allocated at most 1 value for each of IPv4 and IPv6. This list is empty if no IPs have been allocated yet.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/api/core/v1.PodIP"), + }, + }, + }, + }, + }, + "startTime": { + SchemaProps: spec.SchemaProps{ + Description: "RFC 3339 date and time at which the object was acknowledged by the Kubelet. This is before the Kubelet pulled the container image(s) for the pod.", + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.Time"), + }, + }, + "initContainerStatuses": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "Statuses of init containers in this pod. The most recent successful non-restartable init container will have ready = true, the most recently started container will have startTime set. Each init container in the pod should have at most one status in this list, and all statuses should be for containers in the pod. However this is not enforced. If a status for a non-existent container is present in the list, or the list has duplicate names, the behavior of various Kubernetes components is not defined and those statuses might be ignored. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle/#pod-and-container-status", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/api/core/v1.ContainerStatus"), + }, + }, + }, + }, + }, + "containerStatuses": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "Statuses of containers in this pod. Each container in the pod should have at most one status in this list, and all statuses should be for containers in the pod. However this is not enforced. If a status for a non-existent container is present in the list, or the list has duplicate names, the behavior of various Kubernetes components is not defined and those statuses might be ignored. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#pod-and-container-status", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/api/core/v1.ContainerStatus"), + }, + }, + }, + }, + }, + "qosClass": { + SchemaProps: spec.SchemaProps{ + Description: "The Quality of Service (QOS) classification assigned to the pod based on resource requirements See PodQOSClass type for available QOS classes More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-qos/#quality-of-service-classes\n\nPossible enum values:\n - `\"BestEffort\"` is the BestEffort qos class.\n - `\"Burstable\"` is the Burstable qos class.\n - `\"Guaranteed\"` is the Guaranteed qos class.", + Type: []string{"string"}, + Format: "", + Enum: []interface{}{"BestEffort", "Burstable", "Guaranteed"}, + }, + }, + "ephemeralContainerStatuses": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "Statuses for any ephemeral containers that have run in this pod. Each ephemeral container in the pod should have at most one status in this list, and all statuses should be for containers in the pod. However this is not enforced. If a status for a non-existent container is present in the list, or the list has duplicate names, the behavior of various Kubernetes components is not defined and those statuses might be ignored. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#pod-and-container-status", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/api/core/v1.ContainerStatus"), + }, + }, + }, + }, + }, + "resize": { + SchemaProps: spec.SchemaProps{ + Description: "Status of resources resize desired for pod's containers. It is empty if no resources resize is pending. Any changes to container resources will automatically set this to \"Proposed\" Deprecated: Resize status is moved to two pod conditions PodResizePending and PodResizeInProgress. PodResizePending will track states where the spec has been resized, but the Kubelet has not yet allocated the resources. PodResizeInProgress will track in-progress resizes, and should be present whenever allocated resources != acknowledged resources.", + Type: []string{"string"}, + Format: "", + }, + }, + "resourceClaimStatuses": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-map-keys": []interface{}{ + "name", + }, + "x-kubernetes-list-type": "map", + "x-kubernetes-patch-merge-key": "name", + "x-kubernetes-patch-strategy": "merge,retainKeys", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "Status of resource claims.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/api/core/v1.PodResourceClaimStatus"), + }, + }, + }, + }, + }, + "extendedResourceClaimStatus": { + SchemaProps: spec.SchemaProps{ + Description: "Status of extended resource claim backed by DRA.", + Ref: ref("k8s.io/api/core/v1.PodExtendedResourceClaimStatus"), + }, + }, + }, + }, + }, + Dependencies: []string{ + "k8s.io/api/core/v1.ContainerStatus", "k8s.io/api/core/v1.HostIP", "k8s.io/api/core/v1.PodCondition", "k8s.io/api/core/v1.PodExtendedResourceClaimStatus", "k8s.io/api/core/v1.PodIP", "k8s.io/api/core/v1.PodResourceClaimStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.Time"}, + } +} + +func schema_k8sio_api_core_v1_PodStatusResult(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "PodStatusResult is a wrapper for PodStatus returned by kubelet that can be encode/decoded", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", + }, + }, + "metadata": { + SchemaProps: spec.SchemaProps{ + Description: "Standard object's metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata", + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"), + }, + }, + "status": { + SchemaProps: spec.SchemaProps{ + Description: "Most recently observed status of the pod. This data may not be up to date. Populated by the system. Read-only. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status", + Default: map[string]interface{}{}, + Ref: ref("k8s.io/api/core/v1.PodStatus"), + }, + }, + }, + }, + }, + Dependencies: []string{ + "k8s.io/api/core/v1.PodStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"}, + } +} + +func schema_k8sio_api_core_v1_PodTemplate(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "PodTemplate describes a template for creating copies of a predefined pod.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", + }, + }, + "metadata": { + SchemaProps: spec.SchemaProps{ + Description: "Standard object's metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata", + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"), + }, + }, + "template": { + SchemaProps: spec.SchemaProps{ + Description: "Template defines the pods that will be created from this pod template. https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status", + Default: map[string]interface{}{}, + Ref: ref("k8s.io/api/core/v1.PodTemplateSpec"), + }, + }, + }, + }, + }, + Dependencies: []string{ + "k8s.io/api/core/v1.PodTemplateSpec", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"}, + } +} + +func schema_k8sio_api_core_v1_PodTemplateList(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "PodTemplateList is a list of PodTemplates.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", + }, + }, + "metadata": { + SchemaProps: spec.SchemaProps{ + Description: "Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"), + }, + }, + "items": { + SchemaProps: spec.SchemaProps{ + Description: "List of pod templates", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/api/core/v1.PodTemplate"), + }, + }, + }, + }, + }, + }, + Required: []string{"items"}, + }, + }, + Dependencies: []string{ + "k8s.io/api/core/v1.PodTemplate", "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"}, + } +} + +func schema_k8sio_api_core_v1_PodTemplateSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "PodTemplateSpec describes the data a pod should have when created from a template", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "metadata": { + SchemaProps: spec.SchemaProps{ + Description: "Standard object's metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata", + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"), + }, + }, + "spec": { + SchemaProps: spec.SchemaProps{ + Description: "Specification of the desired behavior of the pod. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status", + Default: map[string]interface{}{}, + Ref: ref("k8s.io/api/core/v1.PodSpec"), + }, + }, + }, + }, + }, + Dependencies: []string{ + "k8s.io/api/core/v1.PodSpec", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"}, + } +} + +func schema_k8sio_api_core_v1_PortStatus(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "PortStatus represents the error condition of a service port", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "port": { + SchemaProps: spec.SchemaProps{ + Description: "Port is the port number of the service port of which status is recorded here", + Default: 0, + Type: []string{"integer"}, + Format: "int32", + }, + }, + "protocol": { + SchemaProps: spec.SchemaProps{ + Description: "Protocol is the protocol of the service port of which status is recorded here The supported values are: \"TCP\", \"UDP\", \"SCTP\"\n\nPossible enum values:\n - `\"SCTP\"` is the SCTP protocol.\n - `\"TCP\"` is the TCP protocol.\n - `\"UDP\"` is the UDP protocol.", + Default: "", + Type: []string{"string"}, + Format: "", + Enum: []interface{}{"SCTP", "TCP", "UDP"}, + }, + }, + "error": { + SchemaProps: spec.SchemaProps{ + Description: "Error is to record the problem with the service port The format of the error shall comply with the following rules: - built-in error values shall be specified in this file and those shall use\n CamelCase names\n- cloud provider specific error values must have names that comply with the\n format foo.example.com/CamelCase.", + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"port", "protocol"}, + }, + }, + } +} + +func schema_k8sio_api_core_v1_PortworxVolumeSource(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "PortworxVolumeSource represents a Portworx volume resource.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "volumeID": { + SchemaProps: spec.SchemaProps{ + Description: "volumeID uniquely identifies a Portworx volume", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "fsType": { + SchemaProps: spec.SchemaProps{ + Description: "fSType represents the filesystem type to mount Must be a filesystem type supported by the host operating system. Ex. \"ext4\", \"xfs\". Implicitly inferred to be \"ext4\" if unspecified.", + Type: []string{"string"}, + Format: "", + }, + }, + "readOnly": { + SchemaProps: spec.SchemaProps{ + Description: "readOnly defaults to false (read/write). ReadOnly here will force the ReadOnly setting in VolumeMounts.", + Type: []string{"boolean"}, + Format: "", + }, + }, + }, + Required: []string{"volumeID"}, + }, + }, + } +} + +func schema_k8sio_api_core_v1_PreferAvoidPodsEntry(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "Describes a class of pods that should avoid this node.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "podSignature": { + SchemaProps: spec.SchemaProps{ + Description: "The class of pods.", + Default: map[string]interface{}{}, + Ref: ref("k8s.io/api/core/v1.PodSignature"), + }, + }, + "evictionTime": { + SchemaProps: spec.SchemaProps{ + Description: "Time at which this entry was added to the list.", + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.Time"), + }, + }, + "reason": { + SchemaProps: spec.SchemaProps{ + Description: "(brief) reason why this entry was added to the list.", + Type: []string{"string"}, + Format: "", + }, + }, + "message": { + SchemaProps: spec.SchemaProps{ + Description: "Human readable message indicating why this entry was added to the list.", + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"podSignature"}, + }, + }, + Dependencies: []string{ + "k8s.io/api/core/v1.PodSignature", "k8s.io/apimachinery/pkg/apis/meta/v1.Time"}, + } +} + +func schema_k8sio_api_core_v1_PreferredSchedulingTerm(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "An empty preferred scheduling term matches all objects with implicit weight 0 (i.e. it's a no-op). A null preferred scheduling term matches no objects (i.e. is also a no-op).", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "weight": { + SchemaProps: spec.SchemaProps{ + Description: "Weight associated with matching the corresponding nodeSelectorTerm, in the range 1-100.", + Default: 0, + Type: []string{"integer"}, + Format: "int32", + }, + }, + "preference": { + SchemaProps: spec.SchemaProps{ + Description: "A node selector term, associated with the corresponding weight.", + Default: map[string]interface{}{}, + Ref: ref("k8s.io/api/core/v1.NodeSelectorTerm"), + }, + }, + }, + Required: []string{"weight", "preference"}, + }, + }, + Dependencies: []string{ + "k8s.io/api/core/v1.NodeSelectorTerm"}, + } +} + +func schema_k8sio_api_core_v1_Probe(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "Probe describes a health check to be performed against a container to determine whether it is alive or ready to receive traffic.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "exec": { + SchemaProps: spec.SchemaProps{ + Description: "Exec specifies a command to execute in the container.", + Ref: ref("k8s.io/api/core/v1.ExecAction"), + }, + }, + "httpGet": { + SchemaProps: spec.SchemaProps{ + Description: "HTTPGet specifies an HTTP GET request to perform.", + Ref: ref("k8s.io/api/core/v1.HTTPGetAction"), + }, + }, + "tcpSocket": { + SchemaProps: spec.SchemaProps{ + Description: "TCPSocket specifies a connection to a TCP port.", + Ref: ref("k8s.io/api/core/v1.TCPSocketAction"), + }, + }, + "grpc": { + SchemaProps: spec.SchemaProps{ + Description: "GRPC specifies a GRPC HealthCheckRequest.", + Ref: ref("k8s.io/api/core/v1.GRPCAction"), + }, + }, + "initialDelaySeconds": { + SchemaProps: spec.SchemaProps{ + Description: "Number of seconds after the container has started before liveness probes are initiated. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes", + Type: []string{"integer"}, + Format: "int32", + }, + }, + "timeoutSeconds": { + SchemaProps: spec.SchemaProps{ + Description: "Number of seconds after which the probe times out. Defaults to 1 second. Minimum value is 1. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes", + Type: []string{"integer"}, + Format: "int32", + }, + }, + "periodSeconds": { + SchemaProps: spec.SchemaProps{ + Description: "How often (in seconds) to perform the probe. Default to 10 seconds. Minimum value is 1.", + Type: []string{"integer"}, + Format: "int32", + }, + }, + "successThreshold": { + SchemaProps: spec.SchemaProps{ + Description: "Minimum consecutive successes for the probe to be considered successful after having failed. Defaults to 1. Must be 1 for liveness and startup. Minimum value is 1.", + Type: []string{"integer"}, + Format: "int32", + }, + }, + "failureThreshold": { + SchemaProps: spec.SchemaProps{ + Description: "Minimum consecutive failures for the probe to be considered failed after having succeeded. Defaults to 3. Minimum value is 1.", + Type: []string{"integer"}, + Format: "int32", + }, + }, + "terminationGracePeriodSeconds": { + SchemaProps: spec.SchemaProps{ + Description: "Optional duration in seconds the pod needs to terminate gracefully upon probe failure. The grace period is the duration in seconds after the processes running in the pod are sent a termination signal and the time when the processes are forcibly halted with a kill signal. Set this value longer than the expected cleanup time for your process. If this value is nil, the pod's terminationGracePeriodSeconds will be used. Otherwise, this value overrides the value provided by the pod spec. Value must be non-negative integer. The value zero indicates stop immediately via the kill signal (no opportunity to shut down). This is a beta field and requires enabling ProbeTerminationGracePeriod feature gate. Minimum value is 1. spec.terminationGracePeriodSeconds is used if unset.", + Type: []string{"integer"}, + Format: "int64", + }, + }, + }, + }, + }, + Dependencies: []string{ + "k8s.io/api/core/v1.ExecAction", "k8s.io/api/core/v1.GRPCAction", "k8s.io/api/core/v1.HTTPGetAction", "k8s.io/api/core/v1.TCPSocketAction"}, + } +} + +func schema_k8sio_api_core_v1_ProbeHandler(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "ProbeHandler defines a specific action that should be taken in a probe. One and only one of the fields must be specified.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "exec": { + SchemaProps: spec.SchemaProps{ + Description: "Exec specifies a command to execute in the container.", + Ref: ref("k8s.io/api/core/v1.ExecAction"), + }, + }, + "httpGet": { + SchemaProps: spec.SchemaProps{ + Description: "HTTPGet specifies an HTTP GET request to perform.", + Ref: ref("k8s.io/api/core/v1.HTTPGetAction"), + }, + }, + "tcpSocket": { + SchemaProps: spec.SchemaProps{ + Description: "TCPSocket specifies a connection to a TCP port.", + Ref: ref("k8s.io/api/core/v1.TCPSocketAction"), + }, + }, + "grpc": { + SchemaProps: spec.SchemaProps{ + Description: "GRPC specifies a GRPC HealthCheckRequest.", + Ref: ref("k8s.io/api/core/v1.GRPCAction"), + }, + }, + }, + }, + }, + Dependencies: []string{ + "k8s.io/api/core/v1.ExecAction", "k8s.io/api/core/v1.GRPCAction", "k8s.io/api/core/v1.HTTPGetAction", "k8s.io/api/core/v1.TCPSocketAction"}, + } +} + +func schema_k8sio_api_core_v1_ProjectedVolumeSource(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "Represents a projected volume source", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "sources": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "sources is the list of volume projections. Each entry in this list handles one source.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/api/core/v1.VolumeProjection"), + }, + }, + }, + }, + }, + "defaultMode": { + SchemaProps: spec.SchemaProps{ + Description: "defaultMode are the mode bits used to set permissions on created files by default. Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. Directories within the path are not affected by this setting. This might be in conflict with other options that affect the file mode, like fsGroup, and the result can be other mode bits set.", + Type: []string{"integer"}, + Format: "int32", + }, + }, + }, + }, + }, + Dependencies: []string{ + "k8s.io/api/core/v1.VolumeProjection"}, + } +} + +func schema_k8sio_api_core_v1_QuobyteVolumeSource(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "Represents a Quobyte mount that lasts the lifetime of a pod. Quobyte volumes do not support ownership management or SELinux relabeling.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "registry": { + SchemaProps: spec.SchemaProps{ + Description: "registry represents a single or multiple Quobyte Registry services specified as a string as host:port pair (multiple entries are separated with commas) which acts as the central registry for volumes", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "volume": { + SchemaProps: spec.SchemaProps{ + Description: "volume is a string that references an already created Quobyte volume by name.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "readOnly": { + SchemaProps: spec.SchemaProps{ + Description: "readOnly here will force the Quobyte volume to be mounted with read-only permissions. Defaults to false.", + Type: []string{"boolean"}, + Format: "", + }, + }, + "user": { + SchemaProps: spec.SchemaProps{ + Description: "user to map volume access to Defaults to serivceaccount user", + Type: []string{"string"}, + Format: "", + }, + }, + "group": { + SchemaProps: spec.SchemaProps{ + Description: "group to map volume access to Default is no group", + Type: []string{"string"}, + Format: "", + }, + }, + "tenant": { + SchemaProps: spec.SchemaProps{ + Description: "tenant owning the given Quobyte volume in the Backend Used with dynamically provisioned Quobyte volumes, value is set by the plugin", + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"registry", "volume"}, + }, + }, + } +} + +func schema_k8sio_api_core_v1_RBDPersistentVolumeSource(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "Represents a Rados Block Device mount that lasts the lifetime of a pod. RBD volumes support ownership management and SELinux relabeling.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "monitors": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "monitors is a collection of Ceph monitors. More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + "image": { + SchemaProps: spec.SchemaProps{ + Description: "image is the rados image name. More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "fsType": { + SchemaProps: spec.SchemaProps{ + Description: "fsType is the filesystem type of the volume that you want to mount. Tip: Ensure that the filesystem type is supported by the host operating system. Examples: \"ext4\", \"xfs\", \"ntfs\". Implicitly inferred to be \"ext4\" if unspecified. More info: https://kubernetes.io/docs/concepts/storage/volumes#rbd", + Type: []string{"string"}, + Format: "", + }, + }, + "pool": { + SchemaProps: spec.SchemaProps{ + Description: "pool is the rados pool name. Default is rbd. More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it", + Default: "rbd", + Type: []string{"string"}, + Format: "", + }, + }, + "user": { + SchemaProps: spec.SchemaProps{ + Description: "user is the rados user name. Default is admin. More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it", + Default: "admin", + Type: []string{"string"}, + Format: "", + }, + }, + "keyring": { + SchemaProps: spec.SchemaProps{ + Description: "keyring is the path to key ring for RBDUser. Default is /etc/ceph/keyring. More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it", + Default: "/etc/ceph/keyring", + Type: []string{"string"}, + Format: "", + }, + }, + "secretRef": { + SchemaProps: spec.SchemaProps{ + Description: "secretRef is name of the authentication secret for RBDUser. If provided overrides keyring. Default is nil. More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it", + Ref: ref("k8s.io/api/core/v1.SecretReference"), + }, + }, + "readOnly": { + SchemaProps: spec.SchemaProps{ + Description: "readOnly here will force the ReadOnly setting in VolumeMounts. Defaults to false. More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it", + Type: []string{"boolean"}, + Format: "", + }, + }, + }, + Required: []string{"monitors", "image"}, + }, + }, + Dependencies: []string{ + "k8s.io/api/core/v1.SecretReference"}, + } +} + +func schema_k8sio_api_core_v1_RBDVolumeSource(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "Represents a Rados Block Device mount that lasts the lifetime of a pod. RBD volumes support ownership management and SELinux relabeling.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "monitors": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "monitors is a collection of Ceph monitors. More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + "image": { + SchemaProps: spec.SchemaProps{ + Description: "image is the rados image name. More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "fsType": { + SchemaProps: spec.SchemaProps{ + Description: "fsType is the filesystem type of the volume that you want to mount. Tip: Ensure that the filesystem type is supported by the host operating system. Examples: \"ext4\", \"xfs\", \"ntfs\". Implicitly inferred to be \"ext4\" if unspecified. More info: https://kubernetes.io/docs/concepts/storage/volumes#rbd", + Type: []string{"string"}, + Format: "", + }, + }, + "pool": { + SchemaProps: spec.SchemaProps{ + Description: "pool is the rados pool name. Default is rbd. More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it", + Default: "rbd", + Type: []string{"string"}, + Format: "", + }, + }, + "user": { + SchemaProps: spec.SchemaProps{ + Description: "user is the rados user name. Default is admin. More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it", + Default: "admin", + Type: []string{"string"}, + Format: "", + }, + }, + "keyring": { + SchemaProps: spec.SchemaProps{ + Description: "keyring is the path to key ring for RBDUser. Default is /etc/ceph/keyring. More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it", + Default: "/etc/ceph/keyring", + Type: []string{"string"}, + Format: "", + }, + }, + "secretRef": { + SchemaProps: spec.SchemaProps{ + Description: "secretRef is name of the authentication secret for RBDUser. If provided overrides keyring. Default is nil. More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it", + Ref: ref("k8s.io/api/core/v1.LocalObjectReference"), + }, + }, + "readOnly": { + SchemaProps: spec.SchemaProps{ + Description: "readOnly here will force the ReadOnly setting in VolumeMounts. Defaults to false. More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it", + Type: []string{"boolean"}, + Format: "", + }, + }, + }, + Required: []string{"monitors", "image"}, + }, + }, + Dependencies: []string{ + "k8s.io/api/core/v1.LocalObjectReference"}, + } +} + +func schema_k8sio_api_core_v1_RangeAllocation(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "RangeAllocation is not a public type.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", + }, + }, + "metadata": { + SchemaProps: spec.SchemaProps{ + Description: "Standard object's metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata", + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"), + }, + }, + "range": { + SchemaProps: spec.SchemaProps{ + Description: "Range is string that identifies the range represented by 'data'.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "data": { + SchemaProps: spec.SchemaProps{ + Description: "Data is a bit array containing all allocated addresses in the previous segment.", + Type: []string{"string"}, + Format: "byte", + }, + }, + }, + Required: []string{"range", "data"}, + }, + }, + Dependencies: []string{ + "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"}, + } +} + +func schema_k8sio_api_core_v1_ReplicationController(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "ReplicationController represents the configuration of a replication controller.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", + }, + }, + "metadata": { + SchemaProps: spec.SchemaProps{ + Description: "If the Labels of a ReplicationController are empty, they are defaulted to be the same as the Pod(s) that the replication controller manages. Standard object's metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata", + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"), + }, + }, + "spec": { + SchemaProps: spec.SchemaProps{ + Description: "Spec defines the specification of the desired behavior of the replication controller. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status", + Default: map[string]interface{}{}, + Ref: ref("k8s.io/api/core/v1.ReplicationControllerSpec"), + }, + }, + "status": { + SchemaProps: spec.SchemaProps{ + Description: "Status is the most recently observed status of the replication controller. This data may be out of date by some window of time. Populated by the system. Read-only. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status", + Default: map[string]interface{}{}, + Ref: ref("k8s.io/api/core/v1.ReplicationControllerStatus"), + }, + }, + }, + }, + }, + Dependencies: []string{ + "k8s.io/api/core/v1.ReplicationControllerSpec", "k8s.io/api/core/v1.ReplicationControllerStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"}, + } +} + +func schema_k8sio_api_core_v1_ReplicationControllerCondition(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "ReplicationControllerCondition describes the state of a replication controller at a certain point.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "type": { + SchemaProps: spec.SchemaProps{ + Description: "Type of replication controller condition.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "status": { + SchemaProps: spec.SchemaProps{ + Description: "Status of the condition, one of True, False, Unknown.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "lastTransitionTime": { + SchemaProps: spec.SchemaProps{ + Description: "The last time the condition transitioned from one status to another.", + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.Time"), + }, + }, + "reason": { + SchemaProps: spec.SchemaProps{ + Description: "The reason for the condition's last transition.", + Type: []string{"string"}, + Format: "", + }, + }, + "message": { + SchemaProps: spec.SchemaProps{ + Description: "A human readable message indicating details about the transition.", + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"type", "status"}, + }, + }, + Dependencies: []string{ + "k8s.io/apimachinery/pkg/apis/meta/v1.Time"}, + } +} + +func schema_k8sio_api_core_v1_ReplicationControllerList(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "ReplicationControllerList is a collection of replication controllers.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", + }, + }, + "metadata": { + SchemaProps: spec.SchemaProps{ + Description: "Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"), + }, + }, + "items": { + SchemaProps: spec.SchemaProps{ + Description: "List of replication controllers. More info: https://kubernetes.io/docs/concepts/workloads/controllers/replicationcontroller", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/api/core/v1.ReplicationController"), + }, + }, + }, + }, + }, + }, + Required: []string{"items"}, + }, + }, + Dependencies: []string{ + "k8s.io/api/core/v1.ReplicationController", "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"}, + } +} + +func schema_k8sio_api_core_v1_ReplicationControllerSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "ReplicationControllerSpec is the specification of a replication controller.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "replicas": { + SchemaProps: spec.SchemaProps{ + Description: "Replicas is the number of desired replicas. This is a pointer to distinguish between explicit zero and unspecified. Defaults to 1. More info: https://kubernetes.io/docs/concepts/workloads/controllers/replicationcontroller#what-is-a-replicationcontroller", + Default: 1, + Type: []string{"integer"}, + Format: "int32", + }, + }, + "minReadySeconds": { + SchemaProps: spec.SchemaProps{ + Description: "Minimum number of seconds for which a newly created pod should be ready without any of its container crashing, for it to be considered available. Defaults to 0 (pod will be considered available as soon as it is ready)", + Default: 0, + Type: []string{"integer"}, + Format: "int32", + }, + }, + "selector": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-map-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "Selector is a label query over pods that should match the Replicas count. If Selector is empty, it is defaulted to the labels present on the Pod template. Label keys and values that must match in order to be controlled by this replication controller, if empty defaulted to labels on Pod template. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors", + Type: []string{"object"}, + AdditionalProperties: &spec.SchemaOrBool{ + Allows: true, + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + "template": { + SchemaProps: spec.SchemaProps{ + Description: "Template is the object that describes the pod that will be created if insufficient replicas are detected. This takes precedence over a TemplateRef. The only allowed template.spec.restartPolicy value is \"Always\". More info: https://kubernetes.io/docs/concepts/workloads/controllers/replicationcontroller#pod-template", + Ref: ref("k8s.io/api/core/v1.PodTemplateSpec"), + }, + }, + }, + }, + }, + Dependencies: []string{ + "k8s.io/api/core/v1.PodTemplateSpec"}, + } +} + +func schema_k8sio_api_core_v1_ReplicationControllerStatus(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "ReplicationControllerStatus represents the current status of a replication controller.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "replicas": { + SchemaProps: spec.SchemaProps{ + Description: "Replicas is the most recently observed number of replicas. More info: https://kubernetes.io/docs/concepts/workloads/controllers/replicationcontroller#what-is-a-replicationcontroller", + Default: 0, + Type: []string{"integer"}, + Format: "int32", + }, + }, + "fullyLabeledReplicas": { + SchemaProps: spec.SchemaProps{ + Description: "The number of pods that have labels matching the labels of the pod template of the replication controller.", + Type: []string{"integer"}, + Format: "int32", + }, + }, + "readyReplicas": { + SchemaProps: spec.SchemaProps{ + Description: "The number of ready replicas for this replication controller.", + Type: []string{"integer"}, + Format: "int32", + }, + }, + "availableReplicas": { + SchemaProps: spec.SchemaProps{ + Description: "The number of available replicas (ready for at least minReadySeconds) for this replication controller.", + Type: []string{"integer"}, + Format: "int32", + }, + }, + "observedGeneration": { + SchemaProps: spec.SchemaProps{ + Description: "ObservedGeneration reflects the generation of the most recently observed replication controller.", + Type: []string{"integer"}, + Format: "int64", + }, + }, + "conditions": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-map-keys": []interface{}{ + "type", + }, + "x-kubernetes-list-type": "map", + "x-kubernetes-patch-merge-key": "type", + "x-kubernetes-patch-strategy": "merge", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "Represents the latest available observations of a replication controller's current state.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/api/core/v1.ReplicationControllerCondition"), + }, + }, + }, + }, + }, + }, + Required: []string{"replicas"}, + }, + }, + Dependencies: []string{ + "k8s.io/api/core/v1.ReplicationControllerCondition"}, + } +} + +func schema_k8sio_api_core_v1_ResourceClaim(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "ResourceClaim references one entry in PodSpec.ResourceClaims.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "name": { + SchemaProps: spec.SchemaProps{ + Description: "Name must match the name of one entry in pod.spec.resourceClaims of the Pod where this field is used. It makes that resource available inside a container.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "request": { + SchemaProps: spec.SchemaProps{ + Description: "Request is the name chosen for a request in the referenced claim. If empty, everything from the claim is made available, otherwise only the result of this request.", + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"name"}, + }, + }, + } +} + +func schema_k8sio_api_core_v1_ResourceFieldSelector(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "ResourceFieldSelector represents container resources (cpu, memory) and their output format", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "containerName": { + SchemaProps: spec.SchemaProps{ + Description: "Container name: required for volumes, optional for env vars", + Type: []string{"string"}, + Format: "", + }, + }, + "resource": { + SchemaProps: spec.SchemaProps{ + Description: "Required: resource to select", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "divisor": { + SchemaProps: spec.SchemaProps{ + Description: "Specifies the output format of the exposed resources, defaults to \"1\"", + Ref: ref("k8s.io/apimachinery/pkg/api/resource.Quantity"), + }, + }, + }, + Required: []string{"resource"}, + }, + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-map-type": "atomic", + }, + }, + }, + Dependencies: []string{ + "k8s.io/apimachinery/pkg/api/resource.Quantity"}, + } +} + +func schema_k8sio_api_core_v1_ResourceHealth(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "ResourceHealth represents the health of a resource. It has the latest device health information. This is a part of KEP https://kep.k8s.io/4680.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "resourceID": { + SchemaProps: spec.SchemaProps{ + Description: "ResourceID is the unique identifier of the resource. See the ResourceID type for more information.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "health": { + SchemaProps: spec.SchemaProps{ + Description: "Health of the resource. can be one of:\n - Healthy: operates as normal\n - Unhealthy: reported unhealthy. We consider this a temporary health issue\n since we do not have a mechanism today to distinguish\n temporary and permanent issues.\n - Unknown: The status cannot be determined.\n For example, Device Plugin got unregistered and hasn't been re-registered since.\n\nIn future we may want to introduce the PermanentlyUnhealthy Status.", + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"resourceID"}, + }, + }, + } +} + +func schema_k8sio_api_core_v1_ResourceQuota(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "ResourceQuota sets aggregate quota restrictions enforced per namespace", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", + }, + }, + "metadata": { + SchemaProps: spec.SchemaProps{ + Description: "Standard object's metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata", + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"), + }, + }, + "spec": { + SchemaProps: spec.SchemaProps{ + Description: "Spec defines the desired quota. https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status", + Default: map[string]interface{}{}, + Ref: ref("k8s.io/api/core/v1.ResourceQuotaSpec"), + }, + }, + "status": { + SchemaProps: spec.SchemaProps{ + Description: "Status defines the actual enforced quota and its current usage. https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status", + Default: map[string]interface{}{}, + Ref: ref("k8s.io/api/core/v1.ResourceQuotaStatus"), + }, + }, + }, + }, + }, + Dependencies: []string{ + "k8s.io/api/core/v1.ResourceQuotaSpec", "k8s.io/api/core/v1.ResourceQuotaStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"}, + } +} + +func schema_k8sio_api_core_v1_ResourceQuotaList(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "ResourceQuotaList is a list of ResourceQuota items.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", + }, + }, + "metadata": { + SchemaProps: spec.SchemaProps{ + Description: "Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"), + }, + }, + "items": { + SchemaProps: spec.SchemaProps{ + Description: "Items is a list of ResourceQuota objects. More info: https://kubernetes.io/docs/concepts/policy/resource-quotas/", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/api/core/v1.ResourceQuota"), + }, + }, + }, + }, + }, + }, + Required: []string{"items"}, + }, + }, + Dependencies: []string{ + "k8s.io/api/core/v1.ResourceQuota", "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"}, + } +} + +func schema_k8sio_api_core_v1_ResourceQuotaSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "ResourceQuotaSpec defines the desired hard limits to enforce for Quota.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "hard": { + SchemaProps: spec.SchemaProps{ + Description: "hard is the set of desired hard limits for each named resource. More info: https://kubernetes.io/docs/concepts/policy/resource-quotas/", + Type: []string{"object"}, + AdditionalProperties: &spec.SchemaOrBool{ + Allows: true, + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Ref: ref("k8s.io/apimachinery/pkg/api/resource.Quantity"), + }, + }, + }, + }, + }, + "scopes": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "A collection of filters that must match each object tracked by a quota. If not specified, the quota matches all objects.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + Enum: []interface{}{"BestEffort", "CrossNamespacePodAffinity", "NotBestEffort", "NotTerminating", "PriorityClass", "Terminating", "VolumeAttributesClass"}, + }, + }, + }, + }, + }, + "scopeSelector": { + SchemaProps: spec.SchemaProps{ + Description: "scopeSelector is also a collection of filters like scopes that must match each object tracked by a quota but expressed using ScopeSelectorOperator in combination with possible values. For a resource to match, both scopes AND scopeSelector (if specified in spec), must be matched.", + Ref: ref("k8s.io/api/core/v1.ScopeSelector"), + }, + }, + }, + }, + }, + Dependencies: []string{ + "k8s.io/api/core/v1.ScopeSelector", "k8s.io/apimachinery/pkg/api/resource.Quantity"}, + } +} + +func schema_k8sio_api_core_v1_ResourceQuotaStatus(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "ResourceQuotaStatus defines the enforced hard limits and observed use.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "hard": { + SchemaProps: spec.SchemaProps{ + Description: "Hard is the set of enforced hard limits for each named resource. More info: https://kubernetes.io/docs/concepts/policy/resource-quotas/", + Type: []string{"object"}, + AdditionalProperties: &spec.SchemaOrBool{ + Allows: true, + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Ref: ref("k8s.io/apimachinery/pkg/api/resource.Quantity"), + }, + }, + }, + }, + }, + "used": { + SchemaProps: spec.SchemaProps{ + Description: "Used is the current observed total usage of the resource in the namespace.", + Type: []string{"object"}, + AdditionalProperties: &spec.SchemaOrBool{ + Allows: true, + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Ref: ref("k8s.io/apimachinery/pkg/api/resource.Quantity"), + }, + }, + }, + }, + }, + }, + }, + }, + Dependencies: []string{ + "k8s.io/apimachinery/pkg/api/resource.Quantity"}, + } +} + +func schema_k8sio_api_core_v1_ResourceRequirements(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "ResourceRequirements describes the compute resource requirements.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "limits": { + SchemaProps: spec.SchemaProps{ + Description: "Limits describes the maximum amount of compute resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/", + Type: []string{"object"}, + AdditionalProperties: &spec.SchemaOrBool{ + Allows: true, + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Ref: ref("k8s.io/apimachinery/pkg/api/resource.Quantity"), + }, + }, + }, + }, + }, + "requests": { + SchemaProps: spec.SchemaProps{ + Description: "Requests describes the minimum amount of compute resources required. If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, otherwise to an implementation-defined value. Requests cannot exceed Limits. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/", + Type: []string{"object"}, + AdditionalProperties: &spec.SchemaOrBool{ + Allows: true, + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Ref: ref("k8s.io/apimachinery/pkg/api/resource.Quantity"), + }, + }, + }, + }, + }, + "claims": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-map-keys": []interface{}{ + "name", + }, + "x-kubernetes-list-type": "map", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "Claims lists the names of resources, defined in spec.resourceClaims, that are used by this container.\n\nThis field depends on the DynamicResourceAllocation feature gate.\n\nThis field is immutable. It can only be set for containers.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/api/core/v1.ResourceClaim"), + }, + }, + }, + }, + }, + }, + }, + }, + Dependencies: []string{ + "k8s.io/api/core/v1.ResourceClaim", "k8s.io/apimachinery/pkg/api/resource.Quantity"}, + } +} + +func schema_k8sio_api_core_v1_ResourceStatus(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "ResourceStatus represents the status of a single resource allocated to a Pod.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "name": { + SchemaProps: spec.SchemaProps{ + Description: "Name of the resource. Must be unique within the pod and in case of non-DRA resource, match one of the resources from the pod spec. For DRA resources, the value must be \"claim:/\". When this status is reported about a container, the \"claim_name\" and \"request\" must match one of the claims of this container.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "resources": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-map-keys": []interface{}{ + "resourceID", + }, + "x-kubernetes-list-type": "map", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "List of unique resources health. Each element in the list contains an unique resource ID and its health. At a minimum, for the lifetime of a Pod, resource ID must uniquely identify the resource allocated to the Pod on the Node. If other Pod on the same Node reports the status with the same resource ID, it must be the same resource they share. See ResourceID type definition for a specific format it has in various use cases.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/api/core/v1.ResourceHealth"), + }, + }, + }, + }, + }, + }, + Required: []string{"name"}, + }, + }, + Dependencies: []string{ + "k8s.io/api/core/v1.ResourceHealth"}, + } +} + +func schema_k8sio_api_core_v1_SELinuxOptions(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "SELinuxOptions are the labels to be applied to the container", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "user": { + SchemaProps: spec.SchemaProps{ + Description: "User is a SELinux user label that applies to the container.", + Type: []string{"string"}, + Format: "", + }, + }, + "role": { + SchemaProps: spec.SchemaProps{ + Description: "Role is a SELinux role label that applies to the container.", + Type: []string{"string"}, + Format: "", + }, + }, + "type": { + SchemaProps: spec.SchemaProps{ + Description: "Type is a SELinux type label that applies to the container.", + Type: []string{"string"}, + Format: "", + }, + }, + "level": { + SchemaProps: spec.SchemaProps{ + Description: "Level is SELinux level label that applies to the container.", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + } +} + +func schema_k8sio_api_core_v1_ScaleIOPersistentVolumeSource(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "ScaleIOPersistentVolumeSource represents a persistent ScaleIO volume", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "gateway": { + SchemaProps: spec.SchemaProps{ + Description: "gateway is the host address of the ScaleIO API Gateway.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "system": { + SchemaProps: spec.SchemaProps{ + Description: "system is the name of the storage system as configured in ScaleIO.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "secretRef": { + SchemaProps: spec.SchemaProps{ + Description: "secretRef references to the secret for ScaleIO user and other sensitive information. If this is not provided, Login operation will fail.", + Ref: ref("k8s.io/api/core/v1.SecretReference"), + }, + }, + "sslEnabled": { + SchemaProps: spec.SchemaProps{ + Description: "sslEnabled is the flag to enable/disable SSL communication with Gateway, default false", + Type: []string{"boolean"}, + Format: "", + }, + }, + "protectionDomain": { + SchemaProps: spec.SchemaProps{ + Description: "protectionDomain is the name of the ScaleIO Protection Domain for the configured storage.", + Type: []string{"string"}, + Format: "", + }, + }, + "storagePool": { + SchemaProps: spec.SchemaProps{ + Description: "storagePool is the ScaleIO Storage Pool associated with the protection domain.", + Type: []string{"string"}, + Format: "", + }, + }, + "storageMode": { + SchemaProps: spec.SchemaProps{ + Description: "storageMode indicates whether the storage for a volume should be ThickProvisioned or ThinProvisioned. Default is ThinProvisioned.", + Default: "ThinProvisioned", + Type: []string{"string"}, + Format: "", + }, + }, + "volumeName": { + SchemaProps: spec.SchemaProps{ + Description: "volumeName is the name of a volume already created in the ScaleIO system that is associated with this volume source.", + Type: []string{"string"}, + Format: "", + }, + }, + "fsType": { + SchemaProps: spec.SchemaProps{ + Description: "fsType is the filesystem type to mount. Must be a filesystem type supported by the host operating system. Ex. \"ext4\", \"xfs\", \"ntfs\". Default is \"xfs\"", + Default: "xfs", + Type: []string{"string"}, + Format: "", + }, + }, + "readOnly": { + SchemaProps: spec.SchemaProps{ + Description: "readOnly defaults to false (read/write). ReadOnly here will force the ReadOnly setting in VolumeMounts.", + Type: []string{"boolean"}, + Format: "", + }, + }, + }, + Required: []string{"gateway", "system", "secretRef"}, + }, + }, + Dependencies: []string{ + "k8s.io/api/core/v1.SecretReference"}, + } +} + +func schema_k8sio_api_core_v1_ScaleIOVolumeSource(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "ScaleIOVolumeSource represents a persistent ScaleIO volume", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "gateway": { + SchemaProps: spec.SchemaProps{ + Description: "gateway is the host address of the ScaleIO API Gateway.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "system": { + SchemaProps: spec.SchemaProps{ + Description: "system is the name of the storage system as configured in ScaleIO.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "secretRef": { + SchemaProps: spec.SchemaProps{ + Description: "secretRef references to the secret for ScaleIO user and other sensitive information. If this is not provided, Login operation will fail.", + Ref: ref("k8s.io/api/core/v1.LocalObjectReference"), + }, + }, + "sslEnabled": { + SchemaProps: spec.SchemaProps{ + Description: "sslEnabled Flag enable/disable SSL communication with Gateway, default false", + Type: []string{"boolean"}, + Format: "", + }, + }, + "protectionDomain": { + SchemaProps: spec.SchemaProps{ + Description: "protectionDomain is the name of the ScaleIO Protection Domain for the configured storage.", + Type: []string{"string"}, + Format: "", + }, + }, + "storagePool": { + SchemaProps: spec.SchemaProps{ + Description: "storagePool is the ScaleIO Storage Pool associated with the protection domain.", + Type: []string{"string"}, + Format: "", + }, + }, + "storageMode": { + SchemaProps: spec.SchemaProps{ + Description: "storageMode indicates whether the storage for a volume should be ThickProvisioned or ThinProvisioned. Default is ThinProvisioned.", + Default: "ThinProvisioned", + Type: []string{"string"}, + Format: "", + }, + }, + "volumeName": { + SchemaProps: spec.SchemaProps{ + Description: "volumeName is the name of a volume already created in the ScaleIO system that is associated with this volume source.", + Type: []string{"string"}, + Format: "", + }, + }, + "fsType": { + SchemaProps: spec.SchemaProps{ + Description: "fsType is the filesystem type to mount. Must be a filesystem type supported by the host operating system. Ex. \"ext4\", \"xfs\", \"ntfs\". Default is \"xfs\".", + Default: "xfs", + Type: []string{"string"}, + Format: "", + }, + }, + "readOnly": { + SchemaProps: spec.SchemaProps{ + Description: "readOnly Defaults to false (read/write). ReadOnly here will force the ReadOnly setting in VolumeMounts.", + Type: []string{"boolean"}, + Format: "", + }, + }, + }, + Required: []string{"gateway", "system", "secretRef"}, + }, + }, + Dependencies: []string{ + "k8s.io/api/core/v1.LocalObjectReference"}, + } +} + +func schema_k8sio_api_core_v1_ScopeSelector(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "A scope selector represents the AND of the selectors represented by the scoped-resource selector requirements.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "matchExpressions": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "A list of scope selector requirements by scope of the resources.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/api/core/v1.ScopedResourceSelectorRequirement"), + }, + }, + }, + }, + }, + }, + }, + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-map-type": "atomic", + }, + }, + }, + Dependencies: []string{ + "k8s.io/api/core/v1.ScopedResourceSelectorRequirement"}, + } +} + +func schema_k8sio_api_core_v1_ScopedResourceSelectorRequirement(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "A scoped-resource selector requirement is a selector that contains values, a scope name, and an operator that relates the scope name and values.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "scopeName": { + SchemaProps: spec.SchemaProps{ + Description: "The name of the scope that the selector applies to.\n\nPossible enum values:\n - `\"BestEffort\"` Match all pod objects that have best effort quality of service\n - `\"CrossNamespacePodAffinity\"` Match all pod objects that have cross-namespace pod (anti)affinity mentioned.\n - `\"NotBestEffort\"` Match all pod objects that do not have best effort quality of service\n - `\"NotTerminating\"` Match all pod objects where spec.activeDeadlineSeconds is nil\n - `\"PriorityClass\"` Match all pod objects that have priority class mentioned\n - `\"Terminating\"` Match all pod objects where spec.activeDeadlineSeconds >=0\n - `\"VolumeAttributesClass\"` Match all pvc objects that have volume attributes class mentioned.", + Default: "", + Type: []string{"string"}, + Format: "", + Enum: []interface{}{"BestEffort", "CrossNamespacePodAffinity", "NotBestEffort", "NotTerminating", "PriorityClass", "Terminating", "VolumeAttributesClass"}, + }, + }, + "operator": { + SchemaProps: spec.SchemaProps{ + Description: "Represents a scope's relationship to a set of values. Valid operators are In, NotIn, Exists, DoesNotExist.\n\nPossible enum values:\n - `\"DoesNotExist\"`\n - `\"Exists\"`\n - `\"In\"`\n - `\"NotIn\"`", + Default: "", + Type: []string{"string"}, + Format: "", + Enum: []interface{}{"DoesNotExist", "Exists", "In", "NotIn"}, + }, + }, + "values": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "An array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + }, + Required: []string{"scopeName", "operator"}, + }, + }, + } +} + +func schema_k8sio_api_core_v1_SeccompProfile(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "SeccompProfile defines a pod/container's seccomp profile settings. Only one profile source may be set.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "type": { + SchemaProps: spec.SchemaProps{ + Description: "type indicates which kind of seccomp profile will be applied. Valid options are:\n\nLocalhost - a profile defined in a file on the node should be used. RuntimeDefault - the container runtime default profile should be used. Unconfined - no profile should be applied.\n\nPossible enum values:\n - `\"Localhost\"` indicates a profile defined in a file on the node should be used. The file's location relative to /seccomp.\n - `\"RuntimeDefault\"` represents the default container runtime seccomp profile.\n - `\"Unconfined\"` indicates no seccomp profile is applied (A.K.A. unconfined).", + Default: "", + Type: []string{"string"}, + Format: "", + Enum: []interface{}{"Localhost", "RuntimeDefault", "Unconfined"}, + }, + }, + "localhostProfile": { + SchemaProps: spec.SchemaProps{ + Description: "localhostProfile indicates a profile defined in a file on the node should be used. The profile must be preconfigured on the node to work. Must be a descending path, relative to the kubelet's configured seccomp profile location. Must be set if type is \"Localhost\". Must NOT be set for any other type.", + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"type"}, + }, + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-unions": []interface{}{ + map[string]interface{}{ + "discriminator": "type", + "fields-to-discriminateBy": map[string]interface{}{ + "localhostProfile": "LocalhostProfile", + }, + }, + }, + }, + }, + }, + } +} + +func schema_k8sio_api_core_v1_Secret(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "Secret holds secret data of a certain type. The total bytes of the values in the Data field must be less than MaxSecretSize bytes.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", + }, + }, + "metadata": { + SchemaProps: spec.SchemaProps{ + Description: "Standard object's metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata", + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"), + }, + }, + "immutable": { + SchemaProps: spec.SchemaProps{ + Description: "Immutable, if set to true, ensures that data stored in the Secret cannot be updated (only object metadata can be modified). If not set to true, the field can be modified at any time. Defaulted to nil.", + Type: []string{"boolean"}, + Format: "", + }, + }, + "data": { + SchemaProps: spec.SchemaProps{ + Description: "Data contains the secret data. Each key must consist of alphanumeric characters, '-', '_' or '.'. The serialized form of the secret data is a base64 encoded string, representing the arbitrary (possibly non-string) data value here. Described in https://tools.ietf.org/html/rfc4648#section-4", + Type: []string{"object"}, + AdditionalProperties: &spec.SchemaOrBool{ + Allows: true, + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"string"}, + Format: "byte", + }, + }, + }, + }, + }, + "stringData": { + SchemaProps: spec.SchemaProps{ + Description: "stringData allows specifying non-binary secret data in string form. It is provided as a write-only input field for convenience. All keys and values are merged into the data field on write, overwriting any existing values. The stringData field is never output when reading from the API.", + Type: []string{"object"}, + AdditionalProperties: &spec.SchemaOrBool{ + Allows: true, + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + "type": { + SchemaProps: spec.SchemaProps{ + Description: "Used to facilitate programmatic handling of secret data. More info: https://kubernetes.io/docs/concepts/configuration/secret/#secret-types", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + Dependencies: []string{ + "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"}, + } +} + +func schema_k8sio_api_core_v1_SecretEnvSource(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "SecretEnvSource selects a Secret to populate the environment variables with.\n\nThe contents of the target Secret's Data field will represent the key-value pairs as environment variables.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "name": { + SchemaProps: spec.SchemaProps{ + Description: "Name of the referent. This field is effectively required, but due to backwards compatibility is allowed to be empty. Instances of this type with an empty value here are almost certainly wrong. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "optional": { + SchemaProps: spec.SchemaProps{ + Description: "Specify whether the Secret must be defined", + Type: []string{"boolean"}, + Format: "", + }, + }, + }, + }, + }, + } +} + +func schema_k8sio_api_core_v1_SecretKeySelector(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "SecretKeySelector selects a key of a Secret.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "name": { + SchemaProps: spec.SchemaProps{ + Description: "Name of the referent. This field is effectively required, but due to backwards compatibility is allowed to be empty. Instances of this type with an empty value here are almost certainly wrong. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "key": { + SchemaProps: spec.SchemaProps{ + Description: "The key of the secret to select from. Must be a valid secret key.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "optional": { + SchemaProps: spec.SchemaProps{ + Description: "Specify whether the Secret or its key must be defined", + Type: []string{"boolean"}, + Format: "", + }, + }, + }, + Required: []string{"key"}, + }, + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-map-type": "atomic", + }, + }, + }, + } +} + +func schema_k8sio_api_core_v1_SecretList(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "SecretList is a list of Secret.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", + }, + }, + "metadata": { + SchemaProps: spec.SchemaProps{ + Description: "Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"), + }, + }, + "items": { + SchemaProps: spec.SchemaProps{ + Description: "Items is a list of secret objects. More info: https://kubernetes.io/docs/concepts/configuration/secret", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/api/core/v1.Secret"), + }, + }, + }, + }, + }, + }, + Required: []string{"items"}, + }, + }, + Dependencies: []string{ + "k8s.io/api/core/v1.Secret", "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"}, + } +} + +func schema_k8sio_api_core_v1_SecretProjection(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "Adapts a secret into a projected volume.\n\nThe contents of the target Secret's Data field will be presented in a projected volume as files using the keys in the Data field as the file names. Note that this is identical to a secret volume source without the default mode.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "name": { + SchemaProps: spec.SchemaProps{ + Description: "Name of the referent. This field is effectively required, but due to backwards compatibility is allowed to be empty. Instances of this type with an empty value here are almost certainly wrong. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "items": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "items if unspecified, each key-value pair in the Data field of the referenced Secret will be projected into the volume as a file whose name is the key and content is the value. If specified, the listed keys will be projected into the specified paths, and unlisted keys will not be present. If a key is specified which is not present in the Secret, the volume setup will error unless it is marked optional. Paths must be relative and may not contain the '..' path or start with '..'.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/api/core/v1.KeyToPath"), + }, + }, + }, + }, + }, + "optional": { + SchemaProps: spec.SchemaProps{ + Description: "optional field specify whether the Secret or its key must be defined", + Type: []string{"boolean"}, + Format: "", + }, + }, + }, + }, + }, + Dependencies: []string{ + "k8s.io/api/core/v1.KeyToPath"}, + } +} + +func schema_k8sio_api_core_v1_SecretReference(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "SecretReference represents a Secret Reference. It has enough information to retrieve secret in any namespace", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "name": { + SchemaProps: spec.SchemaProps{ + Description: "name is unique within a namespace to reference a secret resource.", + Type: []string{"string"}, + Format: "", + }, + }, + "namespace": { + SchemaProps: spec.SchemaProps{ + Description: "namespace defines the space within which the secret name must be unique.", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-map-type": "atomic", + }, + }, + }, + } +} + +func schema_k8sio_api_core_v1_SecretVolumeSource(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "Adapts a Secret into a volume.\n\nThe contents of the target Secret's Data field will be presented in a volume as files using the keys in the Data field as the file names. Secret volumes support ownership management and SELinux relabeling.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "secretName": { + SchemaProps: spec.SchemaProps{ + Description: "secretName is the name of the secret in the pod's namespace to use. More info: https://kubernetes.io/docs/concepts/storage/volumes#secret", + Type: []string{"string"}, + Format: "", + }, + }, + "items": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "items If unspecified, each key-value pair in the Data field of the referenced Secret will be projected into the volume as a file whose name is the key and content is the value. If specified, the listed keys will be projected into the specified paths, and unlisted keys will not be present. If a key is specified which is not present in the Secret, the volume setup will error unless it is marked optional. Paths must be relative and may not contain the '..' path or start with '..'.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/api/core/v1.KeyToPath"), + }, + }, + }, + }, + }, + "defaultMode": { + SchemaProps: spec.SchemaProps{ + Description: "defaultMode is Optional: mode bits used to set permissions on created files by default. Must be an octal value between 0000 and 0777 or a decimal value between 0 and 511. YAML accepts both octal and decimal values, JSON requires decimal values for mode bits. Defaults to 0644. Directories within the path are not affected by this setting. This might be in conflict with other options that affect the file mode, like fsGroup, and the result can be other mode bits set.", + Type: []string{"integer"}, + Format: "int32", + }, + }, + "optional": { + SchemaProps: spec.SchemaProps{ + Description: "optional field specify whether the Secret or its keys must be defined", + Type: []string{"boolean"}, + Format: "", + }, + }, + }, + }, + }, + Dependencies: []string{ + "k8s.io/api/core/v1.KeyToPath"}, + } +} + +func schema_k8sio_api_core_v1_SecurityContext(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "SecurityContext holds security configuration that will be applied to a container. Some fields are present in both SecurityContext and PodSecurityContext. When both are set, the values in SecurityContext take precedence.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "capabilities": { + SchemaProps: spec.SchemaProps{ + Description: "The capabilities to add/drop when running containers. Defaults to the default set of capabilities granted by the container runtime. Note that this field cannot be set when spec.os.name is windows.", + Ref: ref("k8s.io/api/core/v1.Capabilities"), + }, + }, + "privileged": { + SchemaProps: spec.SchemaProps{ + Description: "Run container in privileged mode. Processes in privileged containers are essentially equivalent to root on the host. Defaults to false. Note that this field cannot be set when spec.os.name is windows.", + Type: []string{"boolean"}, + Format: "", + }, + }, + "seLinuxOptions": { + SchemaProps: spec.SchemaProps{ + Description: "The SELinux context to be applied to the container. If unspecified, the container runtime will allocate a random SELinux context for each container. May also be set in PodSecurityContext. If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence. Note that this field cannot be set when spec.os.name is windows.", + Ref: ref("k8s.io/api/core/v1.SELinuxOptions"), + }, + }, + "windowsOptions": { + SchemaProps: spec.SchemaProps{ + Description: "The Windows specific settings applied to all containers. If unspecified, the options from the PodSecurityContext will be used. If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence. Note that this field cannot be set when spec.os.name is linux.", + Ref: ref("k8s.io/api/core/v1.WindowsSecurityContextOptions"), + }, + }, + "runAsUser": { + SchemaProps: spec.SchemaProps{ + Description: "The UID to run the entrypoint of the container process. Defaults to user specified in image metadata if unspecified. May also be set in PodSecurityContext. If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence. Note that this field cannot be set when spec.os.name is windows.", + Type: []string{"integer"}, + Format: "int64", + }, + }, + "runAsGroup": { + SchemaProps: spec.SchemaProps{ + Description: "The GID to run the entrypoint of the container process. Uses runtime default if unset. May also be set in PodSecurityContext. If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence. Note that this field cannot be set when spec.os.name is windows.", + Type: []string{"integer"}, + Format: "int64", + }, + }, + "runAsNonRoot": { + SchemaProps: spec.SchemaProps{ + Description: "Indicates that the container must run as a non-root user. If true, the Kubelet will validate the image at runtime to ensure that it does not run as UID 0 (root) and fail to start the container if it does. If unset or false, no such validation will be performed. May also be set in PodSecurityContext. If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence.", + Type: []string{"boolean"}, + Format: "", + }, + }, + "readOnlyRootFilesystem": { + SchemaProps: spec.SchemaProps{ + Description: "Whether this container has a read-only root filesystem. Default is false. Note that this field cannot be set when spec.os.name is windows.", + Type: []string{"boolean"}, + Format: "", + }, + }, + "allowPrivilegeEscalation": { + SchemaProps: spec.SchemaProps{ + Description: "AllowPrivilegeEscalation controls whether a process can gain more privileges than its parent process. This bool directly controls if the no_new_privs flag will be set on the container process. AllowPrivilegeEscalation is true always when the container is: 1) run as Privileged 2) has CAP_SYS_ADMIN Note that this field cannot be set when spec.os.name is windows.", + Type: []string{"boolean"}, + Format: "", + }, + }, + "procMount": { + SchemaProps: spec.SchemaProps{ + Description: "procMount denotes the type of proc mount to use for the containers. The default value is Default which uses the container runtime defaults for readonly paths and masked paths. This requires the ProcMountType feature flag to be enabled. Note that this field cannot be set when spec.os.name is windows.\n\nPossible enum values:\n - `\"Default\"` uses the container runtime defaults for readonly and masked paths for /proc. Most container runtimes mask certain paths in /proc to avoid accidental security exposure of special devices or information.\n - `\"Unmasked\"` bypasses the default masking behavior of the container runtime and ensures the newly created /proc the container stays in tact with no modifications.", + Type: []string{"string"}, + Format: "", + Enum: []interface{}{"Default", "Unmasked"}, + }, + }, + "seccompProfile": { + SchemaProps: spec.SchemaProps{ + Description: "The seccomp options to use by this container. If seccomp options are provided at both the pod & container level, the container options override the pod options. Note that this field cannot be set when spec.os.name is windows.", + Ref: ref("k8s.io/api/core/v1.SeccompProfile"), + }, + }, + "appArmorProfile": { + SchemaProps: spec.SchemaProps{ + Description: "appArmorProfile is the AppArmor options to use by this container. If set, this profile overrides the pod's appArmorProfile. Note that this field cannot be set when spec.os.name is windows.", + Ref: ref("k8s.io/api/core/v1.AppArmorProfile"), + }, + }, + }, + }, + }, + Dependencies: []string{ + "k8s.io/api/core/v1.AppArmorProfile", "k8s.io/api/core/v1.Capabilities", "k8s.io/api/core/v1.SELinuxOptions", "k8s.io/api/core/v1.SeccompProfile", "k8s.io/api/core/v1.WindowsSecurityContextOptions"}, + } +} + +func schema_k8sio_api_core_v1_SerializedReference(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "SerializedReference is a reference to serialized object.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", + }, + }, + "reference": { + SchemaProps: spec.SchemaProps{ + Description: "The reference to an object in the system.", + Default: map[string]interface{}{}, + Ref: ref("k8s.io/api/core/v1.ObjectReference"), + }, + }, + }, + }, + }, + Dependencies: []string{ + "k8s.io/api/core/v1.ObjectReference"}, + } +} + +func schema_k8sio_api_core_v1_Service(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "Service is a named abstraction of software service (for example, mysql) consisting of local port (for example 3306) that the proxy listens on, and the selector that determines which pods will answer requests sent through the proxy.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", + }, + }, + "metadata": { + SchemaProps: spec.SchemaProps{ + Description: "Standard object's metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata", + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"), + }, + }, + "spec": { + SchemaProps: spec.SchemaProps{ + Description: "Spec defines the behavior of a service. https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status", + Default: map[string]interface{}{}, + Ref: ref("k8s.io/api/core/v1.ServiceSpec"), + }, + }, + "status": { + SchemaProps: spec.SchemaProps{ + Description: "Most recently observed status of the service. Populated by the system. Read-only. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status", + Default: map[string]interface{}{}, + Ref: ref("k8s.io/api/core/v1.ServiceStatus"), + }, + }, + }, + }, + }, + Dependencies: []string{ + "k8s.io/api/core/v1.ServiceSpec", "k8s.io/api/core/v1.ServiceStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"}, + } +} + +func schema_k8sio_api_core_v1_ServiceAccount(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "ServiceAccount binds together: * a name, understood by users, and perhaps by peripheral systems, for an identity * a principal that can be authenticated and authorized * a set of secrets", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", + }, + }, + "metadata": { + SchemaProps: spec.SchemaProps{ + Description: "Standard object's metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata", + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"), + }, + }, + "secrets": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-map-keys": []interface{}{ + "name", + }, + "x-kubernetes-list-type": "map", + "x-kubernetes-patch-merge-key": "name", + "x-kubernetes-patch-strategy": "merge", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "Secrets is a list of the secrets in the same namespace that pods running using this ServiceAccount are allowed to use. Pods are only limited to this list if this service account has a \"kubernetes.io/enforce-mountable-secrets\" annotation set to \"true\". The \"kubernetes.io/enforce-mountable-secrets\" annotation is deprecated since v1.32. Prefer separate namespaces to isolate access to mounted secrets. This field should not be used to find auto-generated service account token secrets for use outside of pods. Instead, tokens can be requested directly using the TokenRequest API, or service account token secrets can be manually created. More info: https://kubernetes.io/docs/concepts/configuration/secret", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/api/core/v1.ObjectReference"), + }, + }, + }, + }, + }, + "imagePullSecrets": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "ImagePullSecrets is a list of references to secrets in the same namespace to use for pulling any images in pods that reference this ServiceAccount. ImagePullSecrets are distinct from Secrets because Secrets can be mounted in the pod, but ImagePullSecrets are only accessed by the kubelet. More info: https://kubernetes.io/docs/concepts/containers/images/#specifying-imagepullsecrets-on-a-pod", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/api/core/v1.LocalObjectReference"), + }, + }, + }, + }, + }, + "automountServiceAccountToken": { + SchemaProps: spec.SchemaProps{ + Description: "AutomountServiceAccountToken indicates whether pods running as this service account should have an API token automatically mounted. Can be overridden at the pod level.", + Type: []string{"boolean"}, + Format: "", + }, + }, + }, + }, + }, + Dependencies: []string{ + "k8s.io/api/core/v1.LocalObjectReference", "k8s.io/api/core/v1.ObjectReference", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"}, + } +} + +func schema_k8sio_api_core_v1_ServiceAccountList(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "ServiceAccountList is a list of ServiceAccount objects", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", + }, + }, + "metadata": { + SchemaProps: spec.SchemaProps{ + Description: "Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"), + }, + }, + "items": { + SchemaProps: spec.SchemaProps{ + Description: "List of ServiceAccounts. More info: https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/api/core/v1.ServiceAccount"), + }, + }, + }, + }, + }, + }, + Required: []string{"items"}, + }, + }, + Dependencies: []string{ + "k8s.io/api/core/v1.ServiceAccount", "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"}, + } +} + +func schema_k8sio_api_core_v1_ServiceAccountTokenProjection(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "ServiceAccountTokenProjection represents a projected service account token volume. This projection can be used to insert a service account token into the pods runtime filesystem for use against APIs (Kubernetes API Server or otherwise).", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "audience": { + SchemaProps: spec.SchemaProps{ + Description: "audience is the intended audience of the token. A recipient of a token must identify itself with an identifier specified in the audience of the token, and otherwise should reject the token. The audience defaults to the identifier of the apiserver.", + Type: []string{"string"}, + Format: "", + }, + }, + "expirationSeconds": { + SchemaProps: spec.SchemaProps{ + Description: "expirationSeconds is the requested duration of validity of the service account token. As the token approaches expiration, the kubelet volume plugin will proactively rotate the service account token. The kubelet will start trying to rotate the token if the token is older than 80 percent of its time to live or if the token is older than 24 hours.Defaults to 1 hour and must be at least 10 minutes.", + Type: []string{"integer"}, + Format: "int64", + }, + }, + "path": { + SchemaProps: spec.SchemaProps{ + Description: "path is the path relative to the mount point of the file to project the token into.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"path"}, + }, + }, + } +} + +func schema_k8sio_api_core_v1_ServiceList(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "ServiceList holds a list of services.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", + }, + }, + "metadata": { + SchemaProps: spec.SchemaProps{ + Description: "Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"), + }, + }, + "items": { + SchemaProps: spec.SchemaProps{ + Description: "List of services", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/api/core/v1.Service"), + }, + }, + }, + }, + }, + }, + Required: []string{"items"}, + }, + }, + Dependencies: []string{ + "k8s.io/api/core/v1.Service", "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"}, + } +} + +func schema_k8sio_api_core_v1_ServicePort(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "ServicePort contains information on service's port.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "name": { + SchemaProps: spec.SchemaProps{ + Description: "The name of this port within the service. This must be a DNS_LABEL. All ports within a ServiceSpec must have unique names. When considering the endpoints for a Service, this must match the 'name' field in the EndpointPort. Optional if only one ServicePort is defined on this service.", + Type: []string{"string"}, + Format: "", + }, + }, + "protocol": { + SchemaProps: spec.SchemaProps{ + Description: "The IP protocol for this port. Supports \"TCP\", \"UDP\", and \"SCTP\". Default is TCP.\n\nPossible enum values:\n - `\"SCTP\"` is the SCTP protocol.\n - `\"TCP\"` is the TCP protocol.\n - `\"UDP\"` is the UDP protocol.", + Default: "TCP", + Type: []string{"string"}, + Format: "", + Enum: []interface{}{"SCTP", "TCP", "UDP"}, + }, + }, + "appProtocol": { + SchemaProps: spec.SchemaProps{ + Description: "The application protocol for this port. This is used as a hint for implementations to offer richer behavior for protocols that they understand. This field follows standard Kubernetes label syntax. Valid values are either:\n\n* Un-prefixed protocol names - reserved for IANA standard service names (as per RFC-6335 and https://www.iana.org/assignments/service-names).\n\n* Kubernetes-defined prefixed names:\n * 'kubernetes.io/h2c' - HTTP/2 prior knowledge over cleartext as described in https://www.rfc-editor.org/rfc/rfc9113.html#name-starting-http-2-with-prior-\n * 'kubernetes.io/ws' - WebSocket over cleartext as described in https://www.rfc-editor.org/rfc/rfc6455\n * 'kubernetes.io/wss' - WebSocket over TLS as described in https://www.rfc-editor.org/rfc/rfc6455\n\n* Other protocols should use implementation-defined prefixed names such as mycompany.com/my-custom-protocol.", + Type: []string{"string"}, + Format: "", + }, + }, + "port": { + SchemaProps: spec.SchemaProps{ + Description: "The port that will be exposed by this service.", + Default: 0, + Type: []string{"integer"}, + Format: "int32", + }, + }, + "targetPort": { + SchemaProps: spec.SchemaProps{ + Description: "Number or name of the port to access on the pods targeted by the service. Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME. If this is a string, it will be looked up as a named port in the target Pod's container ports. If this is not specified, the value of the 'port' field is used (an identity map). This field is ignored for services with clusterIP=None, and should be omitted or set equal to the 'port' field. More info: https://kubernetes.io/docs/concepts/services-networking/service/#defining-a-service", + Ref: ref("k8s.io/apimachinery/pkg/util/intstr.IntOrString"), + }, + }, + "nodePort": { + SchemaProps: spec.SchemaProps{ + Description: "The port on each node on which this service is exposed when type is NodePort or LoadBalancer. Usually assigned by the system. If a value is specified, in-range, and not in use it will be used, otherwise the operation will fail. If not specified, a port will be allocated if this Service requires one. If this field is specified when creating a Service which does not need it, creation will fail. This field will be wiped when updating a Service to no longer need it (e.g. changing type from NodePort to ClusterIP). More info: https://kubernetes.io/docs/concepts/services-networking/service/#type-nodeport", + Type: []string{"integer"}, + Format: "int32", + }, + }, + }, + Required: []string{"port"}, + }, + }, + Dependencies: []string{ + "k8s.io/apimachinery/pkg/util/intstr.IntOrString"}, + } +} + +func schema_k8sio_api_core_v1_ServiceProxyOptions(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "ServiceProxyOptions is the query options to a Service's proxy call.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", + }, + }, + "path": { + SchemaProps: spec.SchemaProps{ + Description: "Path is the part of URLs that include service endpoints, suffixes, and parameters to use for the current proxy request to service. For example, the whole request URL is http://localhost/api/v1/namespaces/kube-system/services/elasticsearch-logging/_search?q=user:kimchy. Path is _search?q=user:kimchy.", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + } +} + +func schema_k8sio_api_core_v1_ServiceSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "ServiceSpec describes the attributes that a user creates on a service.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "ports": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-map-keys": []interface{}{ + "port", + "protocol", + }, + "x-kubernetes-list-type": "map", + "x-kubernetes-patch-merge-key": "port", + "x-kubernetes-patch-strategy": "merge", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "The list of ports that are exposed by this service. More info: https://kubernetes.io/docs/concepts/services-networking/service/#virtual-ips-and-service-proxies", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/api/core/v1.ServicePort"), + }, + }, + }, + }, + }, + "selector": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-map-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "Route service traffic to pods with label keys and values matching this selector. If empty or not present, the service is assumed to have an external process managing its endpoints, which Kubernetes will not modify. Only applies to types ClusterIP, NodePort, and LoadBalancer. Ignored if type is ExternalName. More info: https://kubernetes.io/docs/concepts/services-networking/service/", + Type: []string{"object"}, + AdditionalProperties: &spec.SchemaOrBool{ + Allows: true, + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + "clusterIP": { + SchemaProps: spec.SchemaProps{ + Description: "clusterIP is the IP address of the service and is usually assigned randomly. If an address is specified manually, is in-range (as per system configuration), and is not in use, it will be allocated to the service; otherwise creation of the service will fail. This field may not be changed through updates unless the type field is also being changed to ExternalName (which requires this field to be blank) or the type field is being changed from ExternalName (in which case this field may optionally be specified, as describe above). Valid values are \"None\", empty string (\"\"), or a valid IP address. Setting this to \"None\" makes a \"headless service\" (no virtual IP), which is useful when direct endpoint connections are preferred and proxying is not required. Only applies to types ClusterIP, NodePort, and LoadBalancer. If this field is specified when creating a Service of type ExternalName, creation will fail. This field will be wiped when updating a Service to type ExternalName. More info: https://kubernetes.io/docs/concepts/services-networking/service/#virtual-ips-and-service-proxies", + Type: []string{"string"}, + Format: "", + }, + }, + "clusterIPs": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "ClusterIPs is a list of IP addresses assigned to this service, and are usually assigned randomly. If an address is specified manually, is in-range (as per system configuration), and is not in use, it will be allocated to the service; otherwise creation of the service will fail. This field may not be changed through updates unless the type field is also being changed to ExternalName (which requires this field to be empty) or the type field is being changed from ExternalName (in which case this field may optionally be specified, as describe above). Valid values are \"None\", empty string (\"\"), or a valid IP address. Setting this to \"None\" makes a \"headless service\" (no virtual IP), which is useful when direct endpoint connections are preferred and proxying is not required. Only applies to types ClusterIP, NodePort, and LoadBalancer. If this field is specified when creating a Service of type ExternalName, creation will fail. This field will be wiped when updating a Service to type ExternalName. If this field is not specified, it will be initialized from the clusterIP field. If this field is specified, clients must ensure that clusterIPs[0] and clusterIP have the same value.\n\nThis field may hold a maximum of two entries (dual-stack IPs, in either order). These IPs must correspond to the values of the ipFamilies field. Both clusterIPs and ipFamilies are governed by the ipFamilyPolicy field. More info: https://kubernetes.io/docs/concepts/services-networking/service/#virtual-ips-and-service-proxies", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + "type": { + SchemaProps: spec.SchemaProps{ + Description: "type determines how the Service is exposed. Defaults to ClusterIP. Valid options are ExternalName, ClusterIP, NodePort, and LoadBalancer. \"ClusterIP\" allocates a cluster-internal IP address for load-balancing to endpoints. Endpoints are determined by the selector or if that is not specified, by manual construction of an Endpoints object or EndpointSlice objects. If clusterIP is \"None\", no virtual IP is allocated and the endpoints are published as a set of endpoints rather than a virtual IP. \"NodePort\" builds on ClusterIP and allocates a port on every node which routes to the same endpoints as the clusterIP. \"LoadBalancer\" builds on NodePort and creates an external load-balancer (if supported in the current cloud) which routes to the same endpoints as the clusterIP. \"ExternalName\" aliases this service to the specified externalName. Several other fields do not apply to ExternalName services. More info: https://kubernetes.io/docs/concepts/services-networking/service/#publishing-services-service-types\n\nPossible enum values:\n - `\"ClusterIP\"` means a service will only be accessible inside the cluster, via the cluster IP.\n - `\"ExternalName\"` means a service consists of only a reference to an external name that kubedns or equivalent will return as a CNAME record, with no exposing or proxying of any pods involved.\n - `\"LoadBalancer\"` means a service will be exposed via an external load balancer (if the cloud provider supports it), in addition to 'NodePort' type.\n - `\"NodePort\"` means a service will be exposed on one port of every node, in addition to 'ClusterIP' type.", + Type: []string{"string"}, + Format: "", + Enum: []interface{}{"ClusterIP", "ExternalName", "LoadBalancer", "NodePort"}, + }, + }, + "externalIPs": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "externalIPs is a list of IP addresses for which nodes in the cluster will also accept traffic for this service. These IPs are not managed by Kubernetes. The user is responsible for ensuring that traffic arrives at a node with this IP. A common example is external load-balancers that are not part of the Kubernetes system.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + "sessionAffinity": { + SchemaProps: spec.SchemaProps{ + Description: "Supports \"ClientIP\" and \"None\". Used to maintain session affinity. Enable client IP based session affinity. Must be ClientIP or None. Defaults to None. More info: https://kubernetes.io/docs/concepts/services-networking/service/#virtual-ips-and-service-proxies\n\nPossible enum values:\n - `\"ClientIP\"` is the Client IP based.\n - `\"None\"` - no session affinity.", + Type: []string{"string"}, + Format: "", + Enum: []interface{}{"ClientIP", "None"}, + }, + }, + "loadBalancerIP": { + SchemaProps: spec.SchemaProps{ + Description: "Only applies to Service Type: LoadBalancer. This feature depends on whether the underlying cloud-provider supports specifying the loadBalancerIP when a load balancer is created. This field will be ignored if the cloud-provider does not support the feature. Deprecated: This field was under-specified and its meaning varies across implementations. Using it is non-portable and it may not support dual-stack. Users are encouraged to use implementation-specific annotations when available.", + Type: []string{"string"}, + Format: "", + }, + }, + "loadBalancerSourceRanges": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "If specified and supported by the platform, this will restrict traffic through the cloud-provider load-balancer will be restricted to the specified client IPs. This field will be ignored if the cloud-provider does not support the feature.\" More info: https://kubernetes.io/docs/tasks/access-application-cluster/create-external-load-balancer/", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + "externalName": { + SchemaProps: spec.SchemaProps{ + Description: "externalName is the external reference that discovery mechanisms will return as an alias for this service (e.g. a DNS CNAME record). No proxying will be involved. Must be a lowercase RFC-1123 hostname (https://tools.ietf.org/html/rfc1123) and requires `type` to be \"ExternalName\".", + Type: []string{"string"}, + Format: "", + }, + }, + "externalTrafficPolicy": { + SchemaProps: spec.SchemaProps{ + Description: "externalTrafficPolicy describes how nodes distribute service traffic they receive on one of the Service's \"externally-facing\" addresses (NodePorts, ExternalIPs, and LoadBalancer IPs). If set to \"Local\", the proxy will configure the service in a way that assumes that external load balancers will take care of balancing the service traffic between nodes, and so each node will deliver traffic only to the node-local endpoints of the service, without masquerading the client source IP. (Traffic mistakenly sent to a node with no endpoints will be dropped.) The default value, \"Cluster\", uses the standard behavior of routing to all endpoints evenly (possibly modified by topology and other features). Note that traffic sent to an External IP or LoadBalancer IP from within the cluster will always get \"Cluster\" semantics, but clients sending to a NodePort from within the cluster may need to take traffic policy into account when picking a node.\n\nPossible enum values:\n - `\"Cluster\"` routes traffic to all endpoints.\n - `\"Local\"` preserves the source IP of the traffic by routing only to endpoints on the same node as the traffic was received on (dropping the traffic if there are no local endpoints).", + Type: []string{"string"}, + Format: "", + Enum: []interface{}{"Cluster", "Local"}, + }, + }, + "healthCheckNodePort": { + SchemaProps: spec.SchemaProps{ + Description: "healthCheckNodePort specifies the healthcheck nodePort for the service. This only applies when type is set to LoadBalancer and externalTrafficPolicy is set to Local. If a value is specified, is in-range, and is not in use, it will be used. If not specified, a value will be automatically allocated. External systems (e.g. load-balancers) can use this port to determine if a given node holds endpoints for this service or not. If this field is specified when creating a Service which does not need it, creation will fail. This field will be wiped when updating a Service to no longer need it (e.g. changing type). This field cannot be updated once set.", + Type: []string{"integer"}, + Format: "int32", + }, + }, + "publishNotReadyAddresses": { + SchemaProps: spec.SchemaProps{ + Description: "publishNotReadyAddresses indicates that any agent which deals with endpoints for this Service should disregard any indications of ready/not-ready. The primary use case for setting this field is for a StatefulSet's Headless Service to propagate SRV DNS records for its Pods for the purpose of peer discovery. The Kubernetes controllers that generate Endpoints and EndpointSlice resources for Services interpret this to mean that all endpoints are considered \"ready\" even if the Pods themselves are not. Agents which consume only Kubernetes generated endpoints through the Endpoints or EndpointSlice resources can safely assume this behavior.", + Type: []string{"boolean"}, + Format: "", + }, + }, + "sessionAffinityConfig": { + SchemaProps: spec.SchemaProps{ + Description: "sessionAffinityConfig contains the configurations of session affinity.", + Ref: ref("k8s.io/api/core/v1.SessionAffinityConfig"), + }, + }, + "ipFamilies": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "IPFamilies is a list of IP families (e.g. IPv4, IPv6) assigned to this service. This field is usually assigned automatically based on cluster configuration and the ipFamilyPolicy field. If this field is specified manually, the requested family is available in the cluster, and ipFamilyPolicy allows it, it will be used; otherwise creation of the service will fail. This field is conditionally mutable: it allows for adding or removing a secondary IP family, but it does not allow changing the primary IP family of the Service. Valid values are \"IPv4\" and \"IPv6\". This field only applies to Services of types ClusterIP, NodePort, and LoadBalancer, and does apply to \"headless\" services. This field will be wiped when updating a Service to type ExternalName.\n\nThis field may hold a maximum of two entries (dual-stack families, in either order). These families must correspond to the values of the clusterIPs field, if specified. Both clusterIPs and ipFamilies are governed by the ipFamilyPolicy field.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + Enum: []interface{}{"", "IPv4", "IPv6"}, + }, + }, + }, + }, + }, + "ipFamilyPolicy": { + SchemaProps: spec.SchemaProps{ + Description: "IPFamilyPolicy represents the dual-stack-ness requested or required by this Service. If there is no value provided, then this field will be set to SingleStack. Services can be \"SingleStack\" (a single IP family), \"PreferDualStack\" (two IP families on dual-stack configured clusters or a single IP family on single-stack clusters), or \"RequireDualStack\" (two IP families on dual-stack configured clusters, otherwise fail). The ipFamilies and clusterIPs fields depend on the value of this field. This field will be wiped when updating a service to type ExternalName.\n\nPossible enum values:\n - `\"PreferDualStack\"` indicates that this service prefers dual-stack when the cluster is configured for dual-stack. If the cluster is not configured for dual-stack the service will be assigned a single IPFamily. If the IPFamily is not set in service.spec.ipFamilies then the service will be assigned the default IPFamily configured on the cluster\n - `\"RequireDualStack\"` indicates that this service requires dual-stack. Using IPFamilyPolicyRequireDualStack on a single stack cluster will result in validation errors. The IPFamilies (and their order) assigned to this service is based on service.spec.ipFamilies. If service.spec.ipFamilies was not provided then it will be assigned according to how they are configured on the cluster. If service.spec.ipFamilies has only one entry then the alternative IPFamily will be added by apiserver\n - `\"SingleStack\"` indicates that this service is required to have a single IPFamily. The IPFamily assigned is based on the default IPFamily used by the cluster or as identified by service.spec.ipFamilies field", + Type: []string{"string"}, + Format: "", + Enum: []interface{}{"PreferDualStack", "RequireDualStack", "SingleStack"}, + }, + }, + "allocateLoadBalancerNodePorts": { + SchemaProps: spec.SchemaProps{ + Description: "allocateLoadBalancerNodePorts defines if NodePorts will be automatically allocated for services with type LoadBalancer. Default is \"true\". It may be set to \"false\" if the cluster load-balancer does not rely on NodePorts. If the caller requests specific NodePorts (by specifying a value), those requests will be respected, regardless of this field. This field may only be set for services with type LoadBalancer and will be cleared if the type is changed to any other type.", + Type: []string{"boolean"}, + Format: "", + }, + }, + "loadBalancerClass": { + SchemaProps: spec.SchemaProps{ + Description: "loadBalancerClass is the class of the load balancer implementation this Service belongs to. If specified, the value of this field must be a label-style identifier, with an optional prefix, e.g. \"internal-vip\" or \"example.com/internal-vip\". Unprefixed names are reserved for end-users. This field can only be set when the Service type is 'LoadBalancer'. If not set, the default load balancer implementation is used, today this is typically done through the cloud provider integration, but should apply for any default implementation. If set, it is assumed that a load balancer implementation is watching for Services with a matching class. Any default load balancer implementation (e.g. cloud providers) should ignore Services that set this field. This field can only be set when creating or updating a Service to type 'LoadBalancer'. Once set, it can not be changed. This field will be wiped when a service is updated to a non 'LoadBalancer' type.", + Type: []string{"string"}, + Format: "", + }, + }, + "internalTrafficPolicy": { + SchemaProps: spec.SchemaProps{ + Description: "InternalTrafficPolicy describes how nodes distribute service traffic they receive on the ClusterIP. If set to \"Local\", the proxy will assume that pods only want to talk to endpoints of the service on the same node as the pod, dropping the traffic if there are no local endpoints. The default value, \"Cluster\", uses the standard behavior of routing to all endpoints evenly (possibly modified by topology and other features).\n\nPossible enum values:\n - `\"Cluster\"` routes traffic to all endpoints.\n - `\"Local\"` routes traffic only to endpoints on the same node as the client pod (dropping the traffic if there are no local endpoints).", + Type: []string{"string"}, + Format: "", + Enum: []interface{}{"Cluster", "Local"}, + }, + }, + "trafficDistribution": { + SchemaProps: spec.SchemaProps{ + Description: "TrafficDistribution offers a way to express preferences for how traffic is distributed to Service endpoints. Implementations can use this field as a hint, but are not required to guarantee strict adherence. If the field is not set, the implementation will apply its default routing strategy. If set to \"PreferClose\", implementations should prioritize endpoints that are in the same zone.", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + Dependencies: []string{ + "k8s.io/api/core/v1.ServicePort", "k8s.io/api/core/v1.SessionAffinityConfig"}, + } +} + +func schema_k8sio_api_core_v1_ServiceStatus(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "ServiceStatus represents the current status of a service.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "loadBalancer": { + SchemaProps: spec.SchemaProps{ + Description: "LoadBalancer contains the current status of the load-balancer, if one is present.", + Default: map[string]interface{}{}, + Ref: ref("k8s.io/api/core/v1.LoadBalancerStatus"), + }, + }, + "conditions": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-map-keys": []interface{}{ + "type", + }, + "x-kubernetes-list-type": "map", + "x-kubernetes-patch-merge-key": "type", + "x-kubernetes-patch-strategy": "merge", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "Current service state", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.Condition"), + }, + }, + }, + }, + }, + }, + }, + }, + Dependencies: []string{ + "k8s.io/api/core/v1.LoadBalancerStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.Condition"}, + } +} + +func schema_k8sio_api_core_v1_SessionAffinityConfig(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "SessionAffinityConfig represents the configurations of session affinity.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "clientIP": { + SchemaProps: spec.SchemaProps{ + Description: "clientIP contains the configurations of Client IP based session affinity.", + Ref: ref("k8s.io/api/core/v1.ClientIPConfig"), + }, + }, + }, + }, + }, + Dependencies: []string{ + "k8s.io/api/core/v1.ClientIPConfig"}, + } +} + +func schema_k8sio_api_core_v1_SleepAction(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "SleepAction describes a \"sleep\" action.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "seconds": { + SchemaProps: spec.SchemaProps{ + Description: "Seconds is the number of seconds to sleep.", + Default: 0, + Type: []string{"integer"}, + Format: "int64", + }, + }, + }, + Required: []string{"seconds"}, + }, + }, + } +} + +func schema_k8sio_api_core_v1_StorageOSPersistentVolumeSource(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "Represents a StorageOS persistent volume resource.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "volumeName": { + SchemaProps: spec.SchemaProps{ + Description: "volumeName is the human-readable name of the StorageOS volume. Volume names are only unique within a namespace.", + Type: []string{"string"}, + Format: "", + }, + }, + "volumeNamespace": { + SchemaProps: spec.SchemaProps{ + Description: "volumeNamespace specifies the scope of the volume within StorageOS. If no namespace is specified then the Pod's namespace will be used. This allows the Kubernetes name scoping to be mirrored within StorageOS for tighter integration. Set VolumeName to any name to override the default behaviour. Set to \"default\" if you are not using namespaces within StorageOS. Namespaces that do not pre-exist within StorageOS will be created.", + Type: []string{"string"}, + Format: "", + }, + }, + "fsType": { + SchemaProps: spec.SchemaProps{ + Description: "fsType is the filesystem type to mount. Must be a filesystem type supported by the host operating system. Ex. \"ext4\", \"xfs\", \"ntfs\". Implicitly inferred to be \"ext4\" if unspecified.", + Type: []string{"string"}, + Format: "", + }, + }, + "readOnly": { + SchemaProps: spec.SchemaProps{ + Description: "readOnly defaults to false (read/write). ReadOnly here will force the ReadOnly setting in VolumeMounts.", + Type: []string{"boolean"}, + Format: "", + }, + }, + "secretRef": { + SchemaProps: spec.SchemaProps{ + Description: "secretRef specifies the secret to use for obtaining the StorageOS API credentials. If not specified, default values will be attempted.", + Ref: ref("k8s.io/api/core/v1.ObjectReference"), + }, + }, + }, + }, + }, + Dependencies: []string{ + "k8s.io/api/core/v1.ObjectReference"}, + } +} + +func schema_k8sio_api_core_v1_StorageOSVolumeSource(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "Represents a StorageOS persistent volume resource.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "volumeName": { + SchemaProps: spec.SchemaProps{ + Description: "volumeName is the human-readable name of the StorageOS volume. Volume names are only unique within a namespace.", + Type: []string{"string"}, + Format: "", + }, + }, + "volumeNamespace": { + SchemaProps: spec.SchemaProps{ + Description: "volumeNamespace specifies the scope of the volume within StorageOS. If no namespace is specified then the Pod's namespace will be used. This allows the Kubernetes name scoping to be mirrored within StorageOS for tighter integration. Set VolumeName to any name to override the default behaviour. Set to \"default\" if you are not using namespaces within StorageOS. Namespaces that do not pre-exist within StorageOS will be created.", + Type: []string{"string"}, + Format: "", + }, + }, + "fsType": { + SchemaProps: spec.SchemaProps{ + Description: "fsType is the filesystem type to mount. Must be a filesystem type supported by the host operating system. Ex. \"ext4\", \"xfs\", \"ntfs\". Implicitly inferred to be \"ext4\" if unspecified.", + Type: []string{"string"}, + Format: "", + }, + }, + "readOnly": { + SchemaProps: spec.SchemaProps{ + Description: "readOnly defaults to false (read/write). ReadOnly here will force the ReadOnly setting in VolumeMounts.", + Type: []string{"boolean"}, + Format: "", + }, + }, + "secretRef": { + SchemaProps: spec.SchemaProps{ + Description: "secretRef specifies the secret to use for obtaining the StorageOS API credentials. If not specified, default values will be attempted.", + Ref: ref("k8s.io/api/core/v1.LocalObjectReference"), + }, + }, + }, + }, + }, + Dependencies: []string{ + "k8s.io/api/core/v1.LocalObjectReference"}, + } +} + +func schema_k8sio_api_core_v1_Sysctl(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "Sysctl defines a kernel parameter to be set", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "name": { + SchemaProps: spec.SchemaProps{ + Description: "Name of a property to set", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "value": { + SchemaProps: spec.SchemaProps{ + Description: "Value of a property to set", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"name", "value"}, + }, + }, + } +} + +func schema_k8sio_api_core_v1_TCPSocketAction(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "TCPSocketAction describes an action based on opening a socket", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "port": { + SchemaProps: spec.SchemaProps{ + Description: "Number or name of the port to access on the container. Number must be in the range 1 to 65535. Name must be an IANA_SVC_NAME.", + Ref: ref("k8s.io/apimachinery/pkg/util/intstr.IntOrString"), + }, + }, + "host": { + SchemaProps: spec.SchemaProps{ + Description: "Optional: Host name to connect to, defaults to the pod IP.", + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"port"}, + }, + }, + Dependencies: []string{ + "k8s.io/apimachinery/pkg/util/intstr.IntOrString"}, + } +} + +func schema_k8sio_api_core_v1_Taint(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "The node this Taint is attached to has the \"effect\" on any pod that does not tolerate the Taint.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "key": { + SchemaProps: spec.SchemaProps{ + Description: "Required. The taint key to be applied to a node.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "value": { + SchemaProps: spec.SchemaProps{ + Description: "The taint value corresponding to the taint key.", + Type: []string{"string"}, + Format: "", + }, + }, + "effect": { + SchemaProps: spec.SchemaProps{ + Description: "Required. The effect of the taint on pods that do not tolerate the taint. Valid effects are NoSchedule, PreferNoSchedule and NoExecute.\n\nPossible enum values:\n - `\"NoExecute\"` Evict any already-running pods that do not tolerate the taint. Currently enforced by NodeController.\n - `\"NoSchedule\"` Do not allow new pods to schedule onto the node unless they tolerate the taint, but allow all pods submitted to Kubelet without going through the scheduler to start, and allow all already-running pods to continue running. Enforced by the scheduler.\n - `\"PreferNoSchedule\"` Like TaintEffectNoSchedule, but the scheduler tries not to schedule new pods onto the node, rather than prohibiting new pods from scheduling onto the node entirely. Enforced by the scheduler.", + Default: "", + Type: []string{"string"}, + Format: "", + Enum: []interface{}{"NoExecute", "NoSchedule", "PreferNoSchedule"}, + }, + }, + "timeAdded": { + SchemaProps: spec.SchemaProps{ + Description: "TimeAdded represents the time at which the taint was added.", + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.Time"), + }, + }, + }, + Required: []string{"key", "effect"}, + }, + }, + Dependencies: []string{ + "k8s.io/apimachinery/pkg/apis/meta/v1.Time"}, + } +} + +func schema_k8sio_api_core_v1_Toleration(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "The pod this Toleration is attached to tolerates any taint that matches the triple using the matching operator .", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "key": { + SchemaProps: spec.SchemaProps{ + Description: "Key is the taint key that the toleration applies to. Empty means match all taint keys. If the key is empty, operator must be Exists; this combination means to match all values and all keys.", + Type: []string{"string"}, + Format: "", + }, + }, + "operator": { + SchemaProps: spec.SchemaProps{ + Description: "Operator represents a key's relationship to the value. Valid operators are Exists and Equal. Defaults to Equal. Exists is equivalent to wildcard for value, so that a pod can tolerate all taints of a particular category.\n\nPossible enum values:\n - `\"Equal\"`\n - `\"Exists\"`", + Type: []string{"string"}, + Format: "", + Enum: []interface{}{"Equal", "Exists"}, + }, + }, + "value": { + SchemaProps: spec.SchemaProps{ + Description: "Value is the taint value the toleration matches to. If the operator is Exists, the value should be empty, otherwise just a regular string.", + Type: []string{"string"}, + Format: "", + }, + }, + "effect": { + SchemaProps: spec.SchemaProps{ + Description: "Effect indicates the taint effect to match. Empty means match all taint effects. When specified, allowed values are NoSchedule, PreferNoSchedule and NoExecute.\n\nPossible enum values:\n - `\"NoExecute\"` Evict any already-running pods that do not tolerate the taint. Currently enforced by NodeController.\n - `\"NoSchedule\"` Do not allow new pods to schedule onto the node unless they tolerate the taint, but allow all pods submitted to Kubelet without going through the scheduler to start, and allow all already-running pods to continue running. Enforced by the scheduler.\n - `\"PreferNoSchedule\"` Like TaintEffectNoSchedule, but the scheduler tries not to schedule new pods onto the node, rather than prohibiting new pods from scheduling onto the node entirely. Enforced by the scheduler.", + Type: []string{"string"}, + Format: "", + Enum: []interface{}{"NoExecute", "NoSchedule", "PreferNoSchedule"}, + }, + }, + "tolerationSeconds": { + SchemaProps: spec.SchemaProps{ + Description: "TolerationSeconds represents the period of time the toleration (which must be of effect NoExecute, otherwise this field is ignored) tolerates the taint. By default, it is not set, which means tolerate the taint forever (do not evict). Zero and negative values will be treated as 0 (evict immediately) by the system.", + Type: []string{"integer"}, + Format: "int64", + }, + }, + }, + }, + }, + } +} + +func schema_k8sio_api_core_v1_TopologySelectorLabelRequirement(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "A topology selector requirement is a selector that matches given label. This is an alpha feature and may change in the future.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "key": { + SchemaProps: spec.SchemaProps{ + Description: "The label key that the selector applies to.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "values": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "An array of string values. One value must match the label to be selected. Each entry in Values is ORed.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + }, + Required: []string{"key", "values"}, + }, + }, + } +} + +func schema_k8sio_api_core_v1_TopologySelectorTerm(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "A topology selector term represents the result of label queries. A null or empty topology selector term matches no objects. The requirements of them are ANDed. It provides a subset of functionality as NodeSelectorTerm. This is an alpha feature and may change in the future.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "matchLabelExpressions": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "A list of topology selector requirements by labels.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/api/core/v1.TopologySelectorLabelRequirement"), + }, + }, + }, + }, + }, + }, + }, + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-map-type": "atomic", + }, + }, + }, + Dependencies: []string{ + "k8s.io/api/core/v1.TopologySelectorLabelRequirement"}, + } +} + +func schema_k8sio_api_core_v1_TopologySpreadConstraint(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "TopologySpreadConstraint specifies how to spread matching pods among the given topology.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "maxSkew": { + SchemaProps: spec.SchemaProps{ + Description: "MaxSkew describes the degree to which pods may be unevenly distributed. When `whenUnsatisfiable=DoNotSchedule`, it is the maximum permitted difference between the number of matching pods in the target topology and the global minimum. The global minimum is the minimum number of matching pods in an eligible domain or zero if the number of eligible domains is less than MinDomains. For example, in a 3-zone cluster, MaxSkew is set to 1, and pods with the same labelSelector spread as 2/2/1: In this case, the global minimum is 1. | zone1 | zone2 | zone3 | | P P | P P | P | - if MaxSkew is 1, incoming pod can only be scheduled to zone3 to become 2/2/2; scheduling it onto zone1(zone2) would make the ActualSkew(3-1) on zone1(zone2) violate MaxSkew(1). - if MaxSkew is 2, incoming pod can be scheduled onto any zone. When `whenUnsatisfiable=ScheduleAnyway`, it is used to give higher precedence to topologies that satisfy it. It's a required field. Default value is 1 and 0 is not allowed.", + Default: 0, + Type: []string{"integer"}, + Format: "int32", + }, + }, + "topologyKey": { + SchemaProps: spec.SchemaProps{ + Description: "TopologyKey is the key of node labels. Nodes that have a label with this key and identical values are considered to be in the same topology. We consider each as a \"bucket\", and try to put balanced number of pods into each bucket. We define a domain as a particular instance of a topology. Also, we define an eligible domain as a domain whose nodes meet the requirements of nodeAffinityPolicy and nodeTaintsPolicy. e.g. If TopologyKey is \"kubernetes.io/hostname\", each Node is a domain of that topology. And, if TopologyKey is \"topology.kubernetes.io/zone\", each zone is a domain of that topology. It's a required field.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "whenUnsatisfiable": { + SchemaProps: spec.SchemaProps{ + Description: "WhenUnsatisfiable indicates how to deal with a pod if it doesn't satisfy the spread constraint. - DoNotSchedule (default) tells the scheduler not to schedule it. - ScheduleAnyway tells the scheduler to schedule the pod in any location,\n but giving higher precedence to topologies that would help reduce the\n skew.\nA constraint is considered \"Unsatisfiable\" for an incoming pod if and only if every possible node assignment for that pod would violate \"MaxSkew\" on some topology. For example, in a 3-zone cluster, MaxSkew is set to 1, and pods with the same labelSelector spread as 3/1/1: | zone1 | zone2 | zone3 | | P P P | P | P | If WhenUnsatisfiable is set to DoNotSchedule, incoming pod can only be scheduled to zone2(zone3) to become 3/2/1(3/1/2) as ActualSkew(2-1) on zone2(zone3) satisfies MaxSkew(1). In other words, the cluster can still be imbalanced, but scheduler won't make it *more* imbalanced. It's a required field.\n\nPossible enum values:\n - `\"DoNotSchedule\"` instructs the scheduler not to schedule the pod when constraints are not satisfied.\n - `\"ScheduleAnyway\"` instructs the scheduler to schedule the pod even if constraints are not satisfied.", + Default: "", + Type: []string{"string"}, + Format: "", + Enum: []interface{}{"DoNotSchedule", "ScheduleAnyway"}, + }, + }, + "labelSelector": { + SchemaProps: spec.SchemaProps{ + Description: "LabelSelector is used to find matching pods. Pods that match this label selector are counted to determine the number of pods in their corresponding topology domain.", + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.LabelSelector"), + }, + }, + "minDomains": { + SchemaProps: spec.SchemaProps{ + Description: "MinDomains indicates a minimum number of eligible domains. When the number of eligible domains with matching topology keys is less than minDomains, Pod Topology Spread treats \"global minimum\" as 0, and then the calculation of Skew is performed. And when the number of eligible domains with matching topology keys equals or greater than minDomains, this value has no effect on scheduling. As a result, when the number of eligible domains is less than minDomains, scheduler won't schedule more than maxSkew Pods to those domains. If value is nil, the constraint behaves as if MinDomains is equal to 1. Valid values are integers greater than 0. When value is not nil, WhenUnsatisfiable must be DoNotSchedule.\n\nFor example, in a 3-zone cluster, MaxSkew is set to 2, MinDomains is set to 5 and pods with the same labelSelector spread as 2/2/2: | zone1 | zone2 | zone3 | | P P | P P | P P | The number of domains is less than 5(MinDomains), so \"global minimum\" is treated as 0. In this situation, new pod with the same labelSelector cannot be scheduled, because computed skew will be 3(3 - 0) if new Pod is scheduled to any of the three zones, it will violate MaxSkew.", + Type: []string{"integer"}, + Format: "int32", + }, + }, + "nodeAffinityPolicy": { + SchemaProps: spec.SchemaProps{ + Description: "NodeAffinityPolicy indicates how we will treat Pod's nodeAffinity/nodeSelector when calculating pod topology spread skew. Options are: - Honor: only nodes matching nodeAffinity/nodeSelector are included in the calculations. - Ignore: nodeAffinity/nodeSelector are ignored. All nodes are included in the calculations.\n\nIf this value is nil, the behavior is equivalent to the Honor policy.\n\nPossible enum values:\n - `\"Honor\"` means use this scheduling directive when calculating pod topology spread skew.\n - `\"Ignore\"` means ignore this scheduling directive when calculating pod topology spread skew.", + Type: []string{"string"}, + Format: "", + Enum: []interface{}{"Honor", "Ignore"}, + }, + }, + "nodeTaintsPolicy": { + SchemaProps: spec.SchemaProps{ + Description: "NodeTaintsPolicy indicates how we will treat node taints when calculating pod topology spread skew. Options are: - Honor: nodes without taints, along with tainted nodes for which the incoming pod has a toleration, are included. - Ignore: node taints are ignored. All nodes are included.\n\nIf this value is nil, the behavior is equivalent to the Ignore policy.\n\nPossible enum values:\n - `\"Honor\"` means use this scheduling directive when calculating pod topology spread skew.\n - `\"Ignore\"` means ignore this scheduling directive when calculating pod topology spread skew.", + Type: []string{"string"}, + Format: "", + Enum: []interface{}{"Honor", "Ignore"}, + }, + }, + "matchLabelKeys": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "MatchLabelKeys is a set of pod label keys to select the pods over which spreading will be calculated. The keys are used to lookup values from the incoming pod labels, those key-value labels are ANDed with labelSelector to select the group of existing pods over which spreading will be calculated for the incoming pod. The same key is forbidden to exist in both MatchLabelKeys and LabelSelector. MatchLabelKeys cannot be set when LabelSelector isn't set. Keys that don't exist in the incoming pod labels will be ignored. A null or empty list means only match against labelSelector.\n\nThis is a beta field and requires the MatchLabelKeysInPodTopologySpread feature gate to be enabled (enabled by default).", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + }, + Required: []string{"maxSkew", "topologyKey", "whenUnsatisfiable"}, + }, + }, + Dependencies: []string{ + "k8s.io/apimachinery/pkg/apis/meta/v1.LabelSelector"}, + } +} + +func schema_k8sio_api_core_v1_TypedLocalObjectReference(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "TypedLocalObjectReference contains enough information to let you locate the typed referenced object inside the same namespace.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "apiGroup": { + SchemaProps: spec.SchemaProps{ + Description: "APIGroup is the group for the resource being referenced. If APIGroup is not specified, the specified Kind must be in the core API group. For any other third-party types, APIGroup is required.", + Type: []string{"string"}, + Format: "", + }, + }, + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is the type of resource being referenced", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "name": { + SchemaProps: spec.SchemaProps{ + Description: "Name is the name of resource being referenced", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"kind", "name"}, + }, + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-map-type": "atomic", + }, + }, + }, + } +} + +func schema_k8sio_api_core_v1_TypedObjectReference(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "TypedObjectReference contains enough information to let you locate the typed referenced object", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "apiGroup": { + SchemaProps: spec.SchemaProps{ + Description: "APIGroup is the group for the resource being referenced. If APIGroup is not specified, the specified Kind must be in the core API group. For any other third-party types, APIGroup is required.", + Type: []string{"string"}, + Format: "", + }, + }, + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is the type of resource being referenced", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "name": { + SchemaProps: spec.SchemaProps{ + Description: "Name is the name of resource being referenced", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "namespace": { + SchemaProps: spec.SchemaProps{ + Description: "Namespace is the namespace of resource being referenced Note that when a namespace is specified, a gateway.networking.k8s.io/ReferenceGrant object is required in the referent namespace to allow that namespace's owner to accept the reference. See the ReferenceGrant documentation for details. (Alpha) This field requires the CrossNamespaceVolumeDataSource feature gate to be enabled.", + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"kind", "name"}, + }, + }, + } +} + +func schema_k8sio_api_core_v1_Volume(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "Volume represents a named volume in a pod that may be accessed by any container in the pod.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "name": { + SchemaProps: spec.SchemaProps{ + Description: "name of the volume. Must be a DNS_LABEL and unique within the pod. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "hostPath": { + SchemaProps: spec.SchemaProps{ + Description: "hostPath represents a pre-existing file or directory on the host machine that is directly exposed to the container. This is generally used for system agents or other privileged things that are allowed to see the host machine. Most containers will NOT need this. More info: https://kubernetes.io/docs/concepts/storage/volumes#hostpath", + Ref: ref("k8s.io/api/core/v1.HostPathVolumeSource"), + }, + }, + "emptyDir": { + SchemaProps: spec.SchemaProps{ + Description: "emptyDir represents a temporary directory that shares a pod's lifetime. More info: https://kubernetes.io/docs/concepts/storage/volumes#emptydir", + Ref: ref("k8s.io/api/core/v1.EmptyDirVolumeSource"), + }, + }, + "gcePersistentDisk": { + SchemaProps: spec.SchemaProps{ + Description: "gcePersistentDisk represents a GCE Disk resource that is attached to a kubelet's host machine and then exposed to the pod. Deprecated: GCEPersistentDisk is deprecated. All operations for the in-tree gcePersistentDisk type are redirected to the pd.csi.storage.gke.io CSI driver. More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk", + Ref: ref("k8s.io/api/core/v1.GCEPersistentDiskVolumeSource"), + }, + }, + "awsElasticBlockStore": { + SchemaProps: spec.SchemaProps{ + Description: "awsElasticBlockStore represents an AWS Disk resource that is attached to a kubelet's host machine and then exposed to the pod. Deprecated: AWSElasticBlockStore is deprecated. All operations for the in-tree awsElasticBlockStore type are redirected to the ebs.csi.aws.com CSI driver. More info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore", + Ref: ref("k8s.io/api/core/v1.AWSElasticBlockStoreVolumeSource"), + }, + }, + "gitRepo": { + SchemaProps: spec.SchemaProps{ + Description: "gitRepo represents a git repository at a particular revision. Deprecated: GitRepo is deprecated. To provision a container with a git repo, mount an EmptyDir into an InitContainer that clones the repo using git, then mount the EmptyDir into the Pod's container.", + Ref: ref("k8s.io/api/core/v1.GitRepoVolumeSource"), + }, + }, + "secret": { + SchemaProps: spec.SchemaProps{ + Description: "secret represents a secret that should populate this volume. More info: https://kubernetes.io/docs/concepts/storage/volumes#secret", + Ref: ref("k8s.io/api/core/v1.SecretVolumeSource"), + }, + }, + "nfs": { + SchemaProps: spec.SchemaProps{ + Description: "nfs represents an NFS mount on the host that shares a pod's lifetime More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs", + Ref: ref("k8s.io/api/core/v1.NFSVolumeSource"), + }, + }, + "iscsi": { + SchemaProps: spec.SchemaProps{ + Description: "iscsi represents an ISCSI Disk resource that is attached to a kubelet's host machine and then exposed to the pod. More info: https://kubernetes.io/docs/concepts/storage/volumes/#iscsi", + Ref: ref("k8s.io/api/core/v1.ISCSIVolumeSource"), + }, + }, + "glusterfs": { + SchemaProps: spec.SchemaProps{ + Description: "glusterfs represents a Glusterfs mount on the host that shares a pod's lifetime. Deprecated: Glusterfs is deprecated and the in-tree glusterfs type is no longer supported.", + Ref: ref("k8s.io/api/core/v1.GlusterfsVolumeSource"), + }, + }, + "persistentVolumeClaim": { + SchemaProps: spec.SchemaProps{ + Description: "persistentVolumeClaimVolumeSource represents a reference to a PersistentVolumeClaim in the same namespace. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#persistentvolumeclaims", + Ref: ref("k8s.io/api/core/v1.PersistentVolumeClaimVolumeSource"), + }, + }, + "rbd": { + SchemaProps: spec.SchemaProps{ + Description: "rbd represents a Rados Block Device mount on the host that shares a pod's lifetime. Deprecated: RBD is deprecated and the in-tree rbd type is no longer supported.", + Ref: ref("k8s.io/api/core/v1.RBDVolumeSource"), + }, + }, + "flexVolume": { + SchemaProps: spec.SchemaProps{ + Description: "flexVolume represents a generic volume resource that is provisioned/attached using an exec based plugin. Deprecated: FlexVolume is deprecated. Consider using a CSIDriver instead.", + Ref: ref("k8s.io/api/core/v1.FlexVolumeSource"), + }, + }, + "cinder": { + SchemaProps: spec.SchemaProps{ + Description: "cinder represents a cinder volume attached and mounted on kubelets host machine. Deprecated: Cinder is deprecated. All operations for the in-tree cinder type are redirected to the cinder.csi.openstack.org CSI driver. More info: https://examples.k8s.io/mysql-cinder-pd/README.md", + Ref: ref("k8s.io/api/core/v1.CinderVolumeSource"), + }, + }, + "cephfs": { + SchemaProps: spec.SchemaProps{ + Description: "cephFS represents a Ceph FS mount on the host that shares a pod's lifetime. Deprecated: CephFS is deprecated and the in-tree cephfs type is no longer supported.", + Ref: ref("k8s.io/api/core/v1.CephFSVolumeSource"), + }, + }, + "flocker": { + SchemaProps: spec.SchemaProps{ + Description: "flocker represents a Flocker volume attached to a kubelet's host machine. This depends on the Flocker control service being running. Deprecated: Flocker is deprecated and the in-tree flocker type is no longer supported.", + Ref: ref("k8s.io/api/core/v1.FlockerVolumeSource"), + }, + }, + "downwardAPI": { + SchemaProps: spec.SchemaProps{ + Description: "downwardAPI represents downward API about the pod that should populate this volume", + Ref: ref("k8s.io/api/core/v1.DownwardAPIVolumeSource"), + }, + }, + "fc": { + SchemaProps: spec.SchemaProps{ + Description: "fc represents a Fibre Channel resource that is attached to a kubelet's host machine and then exposed to the pod.", + Ref: ref("k8s.io/api/core/v1.FCVolumeSource"), + }, + }, + "azureFile": { + SchemaProps: spec.SchemaProps{ + Description: "azureFile represents an Azure File Service mount on the host and bind mount to the pod. Deprecated: AzureFile is deprecated. All operations for the in-tree azureFile type are redirected to the file.csi.azure.com CSI driver.", + Ref: ref("k8s.io/api/core/v1.AzureFileVolumeSource"), + }, + }, + "configMap": { + SchemaProps: spec.SchemaProps{ + Description: "configMap represents a configMap that should populate this volume", + Ref: ref("k8s.io/api/core/v1.ConfigMapVolumeSource"), + }, + }, + "vsphereVolume": { + SchemaProps: spec.SchemaProps{ + Description: "vsphereVolume represents a vSphere volume attached and mounted on kubelets host machine. Deprecated: VsphereVolume is deprecated. All operations for the in-tree vsphereVolume type are redirected to the csi.vsphere.vmware.com CSI driver.", + Ref: ref("k8s.io/api/core/v1.VsphereVirtualDiskVolumeSource"), + }, + }, + "quobyte": { + SchemaProps: spec.SchemaProps{ + Description: "quobyte represents a Quobyte mount on the host that shares a pod's lifetime. Deprecated: Quobyte is deprecated and the in-tree quobyte type is no longer supported.", + Ref: ref("k8s.io/api/core/v1.QuobyteVolumeSource"), + }, + }, + "azureDisk": { + SchemaProps: spec.SchemaProps{ + Description: "azureDisk represents an Azure Data Disk mount on the host and bind mount to the pod. Deprecated: AzureDisk is deprecated. All operations for the in-tree azureDisk type are redirected to the disk.csi.azure.com CSI driver.", + Ref: ref("k8s.io/api/core/v1.AzureDiskVolumeSource"), + }, + }, + "photonPersistentDisk": { + SchemaProps: spec.SchemaProps{ + Description: "photonPersistentDisk represents a PhotonController persistent disk attached and mounted on kubelets host machine. Deprecated: PhotonPersistentDisk is deprecated and the in-tree photonPersistentDisk type is no longer supported.", + Ref: ref("k8s.io/api/core/v1.PhotonPersistentDiskVolumeSource"), + }, + }, + "projected": { + SchemaProps: spec.SchemaProps{ + Description: "projected items for all in one resources secrets, configmaps, and downward API", + Ref: ref("k8s.io/api/core/v1.ProjectedVolumeSource"), + }, + }, + "portworxVolume": { + SchemaProps: spec.SchemaProps{ + Description: "portworxVolume represents a portworx volume attached and mounted on kubelets host machine. Deprecated: PortworxVolume is deprecated. All operations for the in-tree portworxVolume type are redirected to the pxd.portworx.com CSI driver when the CSIMigrationPortworx feature-gate is on.", + Ref: ref("k8s.io/api/core/v1.PortworxVolumeSource"), + }, + }, + "scaleIO": { + SchemaProps: spec.SchemaProps{ + Description: "scaleIO represents a ScaleIO persistent volume attached and mounted on Kubernetes nodes. Deprecated: ScaleIO is deprecated and the in-tree scaleIO type is no longer supported.", + Ref: ref("k8s.io/api/core/v1.ScaleIOVolumeSource"), + }, + }, + "storageos": { + SchemaProps: spec.SchemaProps{ + Description: "storageOS represents a StorageOS volume attached and mounted on Kubernetes nodes. Deprecated: StorageOS is deprecated and the in-tree storageos type is no longer supported.", + Ref: ref("k8s.io/api/core/v1.StorageOSVolumeSource"), + }, + }, + "csi": { + SchemaProps: spec.SchemaProps{ + Description: "csi (Container Storage Interface) represents ephemeral storage that is handled by certain external CSI drivers.", + Ref: ref("k8s.io/api/core/v1.CSIVolumeSource"), + }, + }, + "ephemeral": { + SchemaProps: spec.SchemaProps{ + Description: "ephemeral represents a volume that is handled by a cluster storage driver. The volume's lifecycle is tied to the pod that defines it - it will be created before the pod starts, and deleted when the pod is removed.\n\nUse this if: a) the volume is only needed while the pod runs, b) features of normal volumes like restoring from snapshot or capacity\n tracking are needed,\nc) the storage driver is specified through a storage class, and d) the storage driver supports dynamic volume provisioning through\n a PersistentVolumeClaim (see EphemeralVolumeSource for more\n information on the connection between this volume type\n and PersistentVolumeClaim).\n\nUse PersistentVolumeClaim or one of the vendor-specific APIs for volumes that persist for longer than the lifecycle of an individual pod.\n\nUse CSI for light-weight local ephemeral volumes if the CSI driver is meant to be used that way - see the documentation of the driver for more information.\n\nA pod can use both types of ephemeral volumes and persistent volumes at the same time.", + Ref: ref("k8s.io/api/core/v1.EphemeralVolumeSource"), + }, + }, + "image": { + SchemaProps: spec.SchemaProps{ + Description: "image represents an OCI object (a container image or artifact) pulled and mounted on the kubelet's host machine. The volume is resolved at pod startup depending on which PullPolicy value is provided:\n\n- Always: the kubelet always attempts to pull the reference. Container creation will fail If the pull fails. - Never: the kubelet never pulls the reference and only uses a local image or artifact. Container creation will fail if the reference isn't present. - IfNotPresent: the kubelet pulls if the reference isn't already present on disk. Container creation will fail if the reference isn't present and the pull fails.\n\nThe volume gets re-resolved if the pod gets deleted and recreated, which means that new remote content will become available on pod recreation. A failure to resolve or pull the image during pod startup will block containers from starting and may add significant latency. Failures will be retried using normal volume backoff and will be reported on the pod reason and message. The types of objects that may be mounted by this volume are defined by the container runtime implementation on a host machine and at minimum must include all valid types supported by the container image field. The OCI object gets mounted in a single directory (spec.containers[*].volumeMounts.mountPath) by merging the manifest layers in the same way as for container images. The volume will be mounted read-only (ro) and non-executable files (noexec). Sub path mounts for containers are not supported (spec.containers[*].volumeMounts.subpath) before 1.33. The field spec.securityContext.fsGroupChangePolicy has no effect on this volume type.", + Ref: ref("k8s.io/api/core/v1.ImageVolumeSource"), + }, + }, + }, + Required: []string{"name"}, + }, + }, + Dependencies: []string{ + "k8s.io/api/core/v1.AWSElasticBlockStoreVolumeSource", "k8s.io/api/core/v1.AzureDiskVolumeSource", "k8s.io/api/core/v1.AzureFileVolumeSource", "k8s.io/api/core/v1.CSIVolumeSource", "k8s.io/api/core/v1.CephFSVolumeSource", "k8s.io/api/core/v1.CinderVolumeSource", "k8s.io/api/core/v1.ConfigMapVolumeSource", "k8s.io/api/core/v1.DownwardAPIVolumeSource", "k8s.io/api/core/v1.EmptyDirVolumeSource", "k8s.io/api/core/v1.EphemeralVolumeSource", "k8s.io/api/core/v1.FCVolumeSource", "k8s.io/api/core/v1.FlexVolumeSource", "k8s.io/api/core/v1.FlockerVolumeSource", "k8s.io/api/core/v1.GCEPersistentDiskVolumeSource", "k8s.io/api/core/v1.GitRepoVolumeSource", "k8s.io/api/core/v1.GlusterfsVolumeSource", "k8s.io/api/core/v1.HostPathVolumeSource", "k8s.io/api/core/v1.ISCSIVolumeSource", "k8s.io/api/core/v1.ImageVolumeSource", "k8s.io/api/core/v1.NFSVolumeSource", "k8s.io/api/core/v1.PersistentVolumeClaimVolumeSource", "k8s.io/api/core/v1.PhotonPersistentDiskVolumeSource", "k8s.io/api/core/v1.PortworxVolumeSource", "k8s.io/api/core/v1.ProjectedVolumeSource", "k8s.io/api/core/v1.QuobyteVolumeSource", "k8s.io/api/core/v1.RBDVolumeSource", "k8s.io/api/core/v1.ScaleIOVolumeSource", "k8s.io/api/core/v1.SecretVolumeSource", "k8s.io/api/core/v1.StorageOSVolumeSource", "k8s.io/api/core/v1.VsphereVirtualDiskVolumeSource"}, + } +} + +func schema_k8sio_api_core_v1_VolumeDevice(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "volumeDevice describes a mapping of a raw block device within a container.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "name": { + SchemaProps: spec.SchemaProps{ + Description: "name must match the name of a persistentVolumeClaim in the pod", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "devicePath": { + SchemaProps: spec.SchemaProps{ + Description: "devicePath is the path inside of the container that the device will be mapped to.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"name", "devicePath"}, + }, + }, + } +} + +func schema_k8sio_api_core_v1_VolumeMount(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "VolumeMount describes a mounting of a Volume within a container.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "name": { + SchemaProps: spec.SchemaProps{ + Description: "This must match the Name of a Volume.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "readOnly": { + SchemaProps: spec.SchemaProps{ + Description: "Mounted read-only if true, read-write otherwise (false or unspecified). Defaults to false.", + Type: []string{"boolean"}, + Format: "", + }, + }, + "recursiveReadOnly": { + SchemaProps: spec.SchemaProps{ + Description: "RecursiveReadOnly specifies whether read-only mounts should be handled recursively.\n\nIf ReadOnly is false, this field has no meaning and must be unspecified.\n\nIf ReadOnly is true, and this field is set to Disabled, the mount is not made recursively read-only. If this field is set to IfPossible, the mount is made recursively read-only, if it is supported by the container runtime. If this field is set to Enabled, the mount is made recursively read-only if it is supported by the container runtime, otherwise the pod will not be started and an error will be generated to indicate the reason.\n\nIf this field is set to IfPossible or Enabled, MountPropagation must be set to None (or be unspecified, which defaults to None).\n\nIf this field is not specified, it is treated as an equivalent of Disabled.", + Type: []string{"string"}, + Format: "", + }, + }, + "mountPath": { + SchemaProps: spec.SchemaProps{ + Description: "Path within the container at which the volume should be mounted. Must not contain ':'.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "subPath": { + SchemaProps: spec.SchemaProps{ + Description: "Path within the volume from which the container's volume should be mounted. Defaults to \"\" (volume's root).", + Type: []string{"string"}, + Format: "", + }, + }, + "mountPropagation": { + SchemaProps: spec.SchemaProps{ + Description: "mountPropagation determines how mounts are propagated from the host to container and the other way around. When not set, MountPropagationNone is used. This field is beta in 1.10. When RecursiveReadOnly is set to IfPossible or to Enabled, MountPropagation must be None or unspecified (which defaults to None).\n\nPossible enum values:\n - `\"Bidirectional\"` means that the volume in a container will receive new mounts from the host or other containers, and its own mounts will be propagated from the container to the host or other containers. Note that this mode is recursively applied to all mounts in the volume (\"rshared\" in Linux terminology).\n - `\"HostToContainer\"` means that the volume in a container will receive new mounts from the host or other containers, but filesystems mounted inside the container won't be propagated to the host or other containers. Note that this mode is recursively applied to all mounts in the volume (\"rslave\" in Linux terminology).\n - `\"None\"` means that the volume in a container will not receive new mounts from the host or other containers, and filesystems mounted inside the container won't be propagated to the host or other containers. Note that this mode corresponds to \"private\" in Linux terminology.", + Type: []string{"string"}, + Format: "", + Enum: []interface{}{"Bidirectional", "HostToContainer", "None"}, + }, + }, + "subPathExpr": { + SchemaProps: spec.SchemaProps{ + Description: "Expanded path within the volume from which the container's volume should be mounted. Behaves similarly to SubPath but environment variable references $(VAR_NAME) are expanded using the container's environment. Defaults to \"\" (volume's root). SubPathExpr and SubPath are mutually exclusive.", + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"name", "mountPath"}, + }, + }, + } +} + +func schema_k8sio_api_core_v1_VolumeMountStatus(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "VolumeMountStatus shows status of volume mounts.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "name": { + SchemaProps: spec.SchemaProps{ + Description: "Name corresponds to the name of the original VolumeMount.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "mountPath": { + SchemaProps: spec.SchemaProps{ + Description: "MountPath corresponds to the original VolumeMount.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "readOnly": { + SchemaProps: spec.SchemaProps{ + Description: "ReadOnly corresponds to the original VolumeMount.", + Type: []string{"boolean"}, + Format: "", + }, + }, + "recursiveReadOnly": { + SchemaProps: spec.SchemaProps{ + Description: "RecursiveReadOnly must be set to Disabled, Enabled, or unspecified (for non-readonly mounts). An IfPossible value in the original VolumeMount must be translated to Disabled or Enabled, depending on the mount result.", + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"name", "mountPath"}, + }, + }, + } +} + +func schema_k8sio_api_core_v1_VolumeNodeAffinity(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "VolumeNodeAffinity defines constraints that limit what nodes this volume can be accessed from.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "required": { + SchemaProps: spec.SchemaProps{ + Description: "required specifies hard node constraints that must be met.", + Ref: ref("k8s.io/api/core/v1.NodeSelector"), + }, + }, + }, + }, + }, + Dependencies: []string{ + "k8s.io/api/core/v1.NodeSelector"}, + } +} + +func schema_k8sio_api_core_v1_VolumeProjection(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "Projection that may be projected along with other supported volume types. Exactly one of these fields must be set.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "secret": { + SchemaProps: spec.SchemaProps{ + Description: "secret information about the secret data to project", + Ref: ref("k8s.io/api/core/v1.SecretProjection"), + }, + }, + "downwardAPI": { + SchemaProps: spec.SchemaProps{ + Description: "downwardAPI information about the downwardAPI data to project", + Ref: ref("k8s.io/api/core/v1.DownwardAPIProjection"), + }, + }, + "configMap": { + SchemaProps: spec.SchemaProps{ + Description: "configMap information about the configMap data to project", + Ref: ref("k8s.io/api/core/v1.ConfigMapProjection"), + }, + }, + "serviceAccountToken": { + SchemaProps: spec.SchemaProps{ + Description: "serviceAccountToken is information about the serviceAccountToken data to project", + Ref: ref("k8s.io/api/core/v1.ServiceAccountTokenProjection"), + }, + }, + "clusterTrustBundle": { + SchemaProps: spec.SchemaProps{ + Description: "ClusterTrustBundle allows a pod to access the `.spec.trustBundle` field of ClusterTrustBundle objects in an auto-updating file.\n\nAlpha, gated by the ClusterTrustBundleProjection feature gate.\n\nClusterTrustBundle objects can either be selected by name, or by the combination of signer name and a label selector.\n\nKubelet performs aggressive normalization of the PEM contents written into the pod filesystem. Esoteric PEM features such as inter-block comments and block headers are stripped. Certificates are deduplicated. The ordering of certificates within the file is arbitrary, and Kubelet may change the order over time.", + Ref: ref("k8s.io/api/core/v1.ClusterTrustBundleProjection"), + }, + }, + "podCertificate": { + SchemaProps: spec.SchemaProps{ + Description: "Projects an auto-rotating credential bundle (private key and certificate chain) that the pod can use either as a TLS client or server.\n\nKubelet generates a private key and uses it to send a PodCertificateRequest to the named signer. Once the signer approves the request and issues a certificate chain, Kubelet writes the key and certificate chain to the pod filesystem. The pod does not start until certificates have been issued for each podCertificate projected volume source in its spec.\n\nKubelet will begin trying to rotate the certificate at the time indicated by the signer using the PodCertificateRequest.Status.BeginRefreshAt timestamp.\n\nKubelet can write a single file, indicated by the credentialBundlePath field, or separate files, indicated by the keyPath and certificateChainPath fields.\n\nThe credential bundle is a single file in PEM format. The first PEM entry is the private key (in PKCS#8 format), and the remaining PEM entries are the certificate chain issued by the signer (typically, signers will return their certificate chain in leaf-to-root order).\n\nPrefer using the credential bundle format, since your application code can read it atomically. If you use keyPath and certificateChainPath, your application must make two separate file reads. If these coincide with a certificate rotation, it is possible that the private key and leaf certificate you read may not correspond to each other. Your application will need to check for this condition, and re-read until they are consistent.\n\nThe named signer controls chooses the format of the certificate it issues; consult the signer implementation's documentation to learn how to use the certificates it issues.", + Ref: ref("k8s.io/api/core/v1.PodCertificateProjection"), + }, + }, + }, + }, + }, + Dependencies: []string{ + "k8s.io/api/core/v1.ClusterTrustBundleProjection", "k8s.io/api/core/v1.ConfigMapProjection", "k8s.io/api/core/v1.DownwardAPIProjection", "k8s.io/api/core/v1.PodCertificateProjection", "k8s.io/api/core/v1.SecretProjection", "k8s.io/api/core/v1.ServiceAccountTokenProjection"}, + } +} + +func schema_k8sio_api_core_v1_VolumeResourceRequirements(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "VolumeResourceRequirements describes the storage resource requirements for a volume.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "limits": { + SchemaProps: spec.SchemaProps{ + Description: "Limits describes the maximum amount of compute resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/", + Type: []string{"object"}, + AdditionalProperties: &spec.SchemaOrBool{ + Allows: true, + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Ref: ref("k8s.io/apimachinery/pkg/api/resource.Quantity"), + }, + }, + }, + }, + }, + "requests": { + SchemaProps: spec.SchemaProps{ + Description: "Requests describes the minimum amount of compute resources required. If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, otherwise to an implementation-defined value. Requests cannot exceed Limits. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/", + Type: []string{"object"}, + AdditionalProperties: &spec.SchemaOrBool{ + Allows: true, + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Ref: ref("k8s.io/apimachinery/pkg/api/resource.Quantity"), + }, + }, + }, + }, + }, + }, + }, + }, + Dependencies: []string{ + "k8s.io/apimachinery/pkg/api/resource.Quantity"}, + } +} + +func schema_k8sio_api_core_v1_VolumeSource(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "Represents the source of a volume to mount. Only one of its members may be specified.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "hostPath": { + SchemaProps: spec.SchemaProps{ + Description: "hostPath represents a pre-existing file or directory on the host machine that is directly exposed to the container. This is generally used for system agents or other privileged things that are allowed to see the host machine. Most containers will NOT need this. More info: https://kubernetes.io/docs/concepts/storage/volumes#hostpath", + Ref: ref("k8s.io/api/core/v1.HostPathVolumeSource"), + }, + }, + "emptyDir": { + SchemaProps: spec.SchemaProps{ + Description: "emptyDir represents a temporary directory that shares a pod's lifetime. More info: https://kubernetes.io/docs/concepts/storage/volumes#emptydir", + Ref: ref("k8s.io/api/core/v1.EmptyDirVolumeSource"), + }, + }, + "gcePersistentDisk": { + SchemaProps: spec.SchemaProps{ + Description: "gcePersistentDisk represents a GCE Disk resource that is attached to a kubelet's host machine and then exposed to the pod. Deprecated: GCEPersistentDisk is deprecated. All operations for the in-tree gcePersistentDisk type are redirected to the pd.csi.storage.gke.io CSI driver. More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk", + Ref: ref("k8s.io/api/core/v1.GCEPersistentDiskVolumeSource"), + }, + }, + "awsElasticBlockStore": { + SchemaProps: spec.SchemaProps{ + Description: "awsElasticBlockStore represents an AWS Disk resource that is attached to a kubelet's host machine and then exposed to the pod. Deprecated: AWSElasticBlockStore is deprecated. All operations for the in-tree awsElasticBlockStore type are redirected to the ebs.csi.aws.com CSI driver. More info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore", + Ref: ref("k8s.io/api/core/v1.AWSElasticBlockStoreVolumeSource"), + }, + }, + "gitRepo": { + SchemaProps: spec.SchemaProps{ + Description: "gitRepo represents a git repository at a particular revision. Deprecated: GitRepo is deprecated. To provision a container with a git repo, mount an EmptyDir into an InitContainer that clones the repo using git, then mount the EmptyDir into the Pod's container.", + Ref: ref("k8s.io/api/core/v1.GitRepoVolumeSource"), + }, + }, + "secret": { + SchemaProps: spec.SchemaProps{ + Description: "secret represents a secret that should populate this volume. More info: https://kubernetes.io/docs/concepts/storage/volumes#secret", + Ref: ref("k8s.io/api/core/v1.SecretVolumeSource"), + }, + }, + "nfs": { + SchemaProps: spec.SchemaProps{ + Description: "nfs represents an NFS mount on the host that shares a pod's lifetime More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs", + Ref: ref("k8s.io/api/core/v1.NFSVolumeSource"), + }, + }, + "iscsi": { + SchemaProps: spec.SchemaProps{ + Description: "iscsi represents an ISCSI Disk resource that is attached to a kubelet's host machine and then exposed to the pod. More info: https://kubernetes.io/docs/concepts/storage/volumes/#iscsi", + Ref: ref("k8s.io/api/core/v1.ISCSIVolumeSource"), + }, + }, + "glusterfs": { + SchemaProps: spec.SchemaProps{ + Description: "glusterfs represents a Glusterfs mount on the host that shares a pod's lifetime. Deprecated: Glusterfs is deprecated and the in-tree glusterfs type is no longer supported.", + Ref: ref("k8s.io/api/core/v1.GlusterfsVolumeSource"), + }, + }, + "persistentVolumeClaim": { + SchemaProps: spec.SchemaProps{ + Description: "persistentVolumeClaimVolumeSource represents a reference to a PersistentVolumeClaim in the same namespace. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#persistentvolumeclaims", + Ref: ref("k8s.io/api/core/v1.PersistentVolumeClaimVolumeSource"), + }, + }, + "rbd": { + SchemaProps: spec.SchemaProps{ + Description: "rbd represents a Rados Block Device mount on the host that shares a pod's lifetime. Deprecated: RBD is deprecated and the in-tree rbd type is no longer supported.", + Ref: ref("k8s.io/api/core/v1.RBDVolumeSource"), + }, + }, + "flexVolume": { + SchemaProps: spec.SchemaProps{ + Description: "flexVolume represents a generic volume resource that is provisioned/attached using an exec based plugin. Deprecated: FlexVolume is deprecated. Consider using a CSIDriver instead.", + Ref: ref("k8s.io/api/core/v1.FlexVolumeSource"), + }, + }, + "cinder": { + SchemaProps: spec.SchemaProps{ + Description: "cinder represents a cinder volume attached and mounted on kubelets host machine. Deprecated: Cinder is deprecated. All operations for the in-tree cinder type are redirected to the cinder.csi.openstack.org CSI driver. More info: https://examples.k8s.io/mysql-cinder-pd/README.md", + Ref: ref("k8s.io/api/core/v1.CinderVolumeSource"), + }, + }, + "cephfs": { + SchemaProps: spec.SchemaProps{ + Description: "cephFS represents a Ceph FS mount on the host that shares a pod's lifetime. Deprecated: CephFS is deprecated and the in-tree cephfs type is no longer supported.", + Ref: ref("k8s.io/api/core/v1.CephFSVolumeSource"), + }, + }, + "flocker": { + SchemaProps: spec.SchemaProps{ + Description: "flocker represents a Flocker volume attached to a kubelet's host machine. This depends on the Flocker control service being running. Deprecated: Flocker is deprecated and the in-tree flocker type is no longer supported.", + Ref: ref("k8s.io/api/core/v1.FlockerVolumeSource"), + }, + }, + "downwardAPI": { + SchemaProps: spec.SchemaProps{ + Description: "downwardAPI represents downward API about the pod that should populate this volume", + Ref: ref("k8s.io/api/core/v1.DownwardAPIVolumeSource"), + }, + }, + "fc": { + SchemaProps: spec.SchemaProps{ + Description: "fc represents a Fibre Channel resource that is attached to a kubelet's host machine and then exposed to the pod.", + Ref: ref("k8s.io/api/core/v1.FCVolumeSource"), + }, + }, + "azureFile": { + SchemaProps: spec.SchemaProps{ + Description: "azureFile represents an Azure File Service mount on the host and bind mount to the pod. Deprecated: AzureFile is deprecated. All operations for the in-tree azureFile type are redirected to the file.csi.azure.com CSI driver.", + Ref: ref("k8s.io/api/core/v1.AzureFileVolumeSource"), + }, + }, + "configMap": { + SchemaProps: spec.SchemaProps{ + Description: "configMap represents a configMap that should populate this volume", + Ref: ref("k8s.io/api/core/v1.ConfigMapVolumeSource"), + }, + }, + "vsphereVolume": { + SchemaProps: spec.SchemaProps{ + Description: "vsphereVolume represents a vSphere volume attached and mounted on kubelets host machine. Deprecated: VsphereVolume is deprecated. All operations for the in-tree vsphereVolume type are redirected to the csi.vsphere.vmware.com CSI driver.", + Ref: ref("k8s.io/api/core/v1.VsphereVirtualDiskVolumeSource"), + }, + }, + "quobyte": { + SchemaProps: spec.SchemaProps{ + Description: "quobyte represents a Quobyte mount on the host that shares a pod's lifetime. Deprecated: Quobyte is deprecated and the in-tree quobyte type is no longer supported.", + Ref: ref("k8s.io/api/core/v1.QuobyteVolumeSource"), + }, + }, + "azureDisk": { + SchemaProps: spec.SchemaProps{ + Description: "azureDisk represents an Azure Data Disk mount on the host and bind mount to the pod. Deprecated: AzureDisk is deprecated. All operations for the in-tree azureDisk type are redirected to the disk.csi.azure.com CSI driver.", + Ref: ref("k8s.io/api/core/v1.AzureDiskVolumeSource"), + }, + }, + "photonPersistentDisk": { + SchemaProps: spec.SchemaProps{ + Description: "photonPersistentDisk represents a PhotonController persistent disk attached and mounted on kubelets host machine. Deprecated: PhotonPersistentDisk is deprecated and the in-tree photonPersistentDisk type is no longer supported.", + Ref: ref("k8s.io/api/core/v1.PhotonPersistentDiskVolumeSource"), + }, + }, + "projected": { + SchemaProps: spec.SchemaProps{ + Description: "projected items for all in one resources secrets, configmaps, and downward API", + Ref: ref("k8s.io/api/core/v1.ProjectedVolumeSource"), + }, + }, + "portworxVolume": { + SchemaProps: spec.SchemaProps{ + Description: "portworxVolume represents a portworx volume attached and mounted on kubelets host machine. Deprecated: PortworxVolume is deprecated. All operations for the in-tree portworxVolume type are redirected to the pxd.portworx.com CSI driver when the CSIMigrationPortworx feature-gate is on.", + Ref: ref("k8s.io/api/core/v1.PortworxVolumeSource"), + }, + }, + "scaleIO": { + SchemaProps: spec.SchemaProps{ + Description: "scaleIO represents a ScaleIO persistent volume attached and mounted on Kubernetes nodes. Deprecated: ScaleIO is deprecated and the in-tree scaleIO type is no longer supported.", + Ref: ref("k8s.io/api/core/v1.ScaleIOVolumeSource"), + }, + }, + "storageos": { + SchemaProps: spec.SchemaProps{ + Description: "storageOS represents a StorageOS volume attached and mounted on Kubernetes nodes. Deprecated: StorageOS is deprecated and the in-tree storageos type is no longer supported.", + Ref: ref("k8s.io/api/core/v1.StorageOSVolumeSource"), + }, + }, + "csi": { + SchemaProps: spec.SchemaProps{ + Description: "csi (Container Storage Interface) represents ephemeral storage that is handled by certain external CSI drivers.", + Ref: ref("k8s.io/api/core/v1.CSIVolumeSource"), + }, + }, + "ephemeral": { + SchemaProps: spec.SchemaProps{ + Description: "ephemeral represents a volume that is handled by a cluster storage driver. The volume's lifecycle is tied to the pod that defines it - it will be created before the pod starts, and deleted when the pod is removed.\n\nUse this if: a) the volume is only needed while the pod runs, b) features of normal volumes like restoring from snapshot or capacity\n tracking are needed,\nc) the storage driver is specified through a storage class, and d) the storage driver supports dynamic volume provisioning through\n a PersistentVolumeClaim (see EphemeralVolumeSource for more\n information on the connection between this volume type\n and PersistentVolumeClaim).\n\nUse PersistentVolumeClaim or one of the vendor-specific APIs for volumes that persist for longer than the lifecycle of an individual pod.\n\nUse CSI for light-weight local ephemeral volumes if the CSI driver is meant to be used that way - see the documentation of the driver for more information.\n\nA pod can use both types of ephemeral volumes and persistent volumes at the same time.", + Ref: ref("k8s.io/api/core/v1.EphemeralVolumeSource"), + }, + }, + "image": { + SchemaProps: spec.SchemaProps{ + Description: "image represents an OCI object (a container image or artifact) pulled and mounted on the kubelet's host machine. The volume is resolved at pod startup depending on which PullPolicy value is provided:\n\n- Always: the kubelet always attempts to pull the reference. Container creation will fail If the pull fails. - Never: the kubelet never pulls the reference and only uses a local image or artifact. Container creation will fail if the reference isn't present. - IfNotPresent: the kubelet pulls if the reference isn't already present on disk. Container creation will fail if the reference isn't present and the pull fails.\n\nThe volume gets re-resolved if the pod gets deleted and recreated, which means that new remote content will become available on pod recreation. A failure to resolve or pull the image during pod startup will block containers from starting and may add significant latency. Failures will be retried using normal volume backoff and will be reported on the pod reason and message. The types of objects that may be mounted by this volume are defined by the container runtime implementation on a host machine and at minimum must include all valid types supported by the container image field. The OCI object gets mounted in a single directory (spec.containers[*].volumeMounts.mountPath) by merging the manifest layers in the same way as for container images. The volume will be mounted read-only (ro) and non-executable files (noexec). Sub path mounts for containers are not supported (spec.containers[*].volumeMounts.subpath) before 1.33. The field spec.securityContext.fsGroupChangePolicy has no effect on this volume type.", + Ref: ref("k8s.io/api/core/v1.ImageVolumeSource"), + }, + }, + }, + }, + }, + Dependencies: []string{ + "k8s.io/api/core/v1.AWSElasticBlockStoreVolumeSource", "k8s.io/api/core/v1.AzureDiskVolumeSource", "k8s.io/api/core/v1.AzureFileVolumeSource", "k8s.io/api/core/v1.CSIVolumeSource", "k8s.io/api/core/v1.CephFSVolumeSource", "k8s.io/api/core/v1.CinderVolumeSource", "k8s.io/api/core/v1.ConfigMapVolumeSource", "k8s.io/api/core/v1.DownwardAPIVolumeSource", "k8s.io/api/core/v1.EmptyDirVolumeSource", "k8s.io/api/core/v1.EphemeralVolumeSource", "k8s.io/api/core/v1.FCVolumeSource", "k8s.io/api/core/v1.FlexVolumeSource", "k8s.io/api/core/v1.FlockerVolumeSource", "k8s.io/api/core/v1.GCEPersistentDiskVolumeSource", "k8s.io/api/core/v1.GitRepoVolumeSource", "k8s.io/api/core/v1.GlusterfsVolumeSource", "k8s.io/api/core/v1.HostPathVolumeSource", "k8s.io/api/core/v1.ISCSIVolumeSource", "k8s.io/api/core/v1.ImageVolumeSource", "k8s.io/api/core/v1.NFSVolumeSource", "k8s.io/api/core/v1.PersistentVolumeClaimVolumeSource", "k8s.io/api/core/v1.PhotonPersistentDiskVolumeSource", "k8s.io/api/core/v1.PortworxVolumeSource", "k8s.io/api/core/v1.ProjectedVolumeSource", "k8s.io/api/core/v1.QuobyteVolumeSource", "k8s.io/api/core/v1.RBDVolumeSource", "k8s.io/api/core/v1.ScaleIOVolumeSource", "k8s.io/api/core/v1.SecretVolumeSource", "k8s.io/api/core/v1.StorageOSVolumeSource", "k8s.io/api/core/v1.VsphereVirtualDiskVolumeSource"}, + } +} + +func schema_k8sio_api_core_v1_VsphereVirtualDiskVolumeSource(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "Represents a vSphere volume resource.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "volumePath": { + SchemaProps: spec.SchemaProps{ + Description: "volumePath is the path that identifies vSphere volume vmdk", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "fsType": { + SchemaProps: spec.SchemaProps{ + Description: "fsType is filesystem type to mount. Must be a filesystem type supported by the host operating system. Ex. \"ext4\", \"xfs\", \"ntfs\". Implicitly inferred to be \"ext4\" if unspecified.", + Type: []string{"string"}, + Format: "", + }, + }, + "storagePolicyName": { + SchemaProps: spec.SchemaProps{ + Description: "storagePolicyName is the storage Policy Based Management (SPBM) profile name.", + Type: []string{"string"}, + Format: "", + }, + }, + "storagePolicyID": { + SchemaProps: spec.SchemaProps{ + Description: "storagePolicyID is the storage Policy Based Management (SPBM) profile ID associated with the StoragePolicyName.", + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"volumePath"}, + }, + }, + } +} + +func schema_k8sio_api_core_v1_WeightedPodAffinityTerm(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "The weights of all of the matched WeightedPodAffinityTerm fields are added per-node to find the most preferred node(s)", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "weight": { + SchemaProps: spec.SchemaProps{ + Description: "weight associated with matching the corresponding podAffinityTerm, in the range 1-100.", + Default: 0, + Type: []string{"integer"}, + Format: "int32", + }, + }, + "podAffinityTerm": { + SchemaProps: spec.SchemaProps{ + Description: "Required. A pod affinity term, associated with the corresponding weight.", + Default: map[string]interface{}{}, + Ref: ref("k8s.io/api/core/v1.PodAffinityTerm"), + }, + }, + }, + Required: []string{"weight", "podAffinityTerm"}, + }, + }, + Dependencies: []string{ + "k8s.io/api/core/v1.PodAffinityTerm"}, + } +} + +func schema_k8sio_api_core_v1_WindowsSecurityContextOptions(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "WindowsSecurityContextOptions contain Windows-specific options and credentials.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "gmsaCredentialSpecName": { + SchemaProps: spec.SchemaProps{ + Description: "GMSACredentialSpecName is the name of the GMSA credential spec to use.", + Type: []string{"string"}, + Format: "", + }, + }, + "gmsaCredentialSpec": { + SchemaProps: spec.SchemaProps{ + Description: "GMSACredentialSpec is where the GMSA admission webhook (https://github.com/kubernetes-sigs/windows-gmsa) inlines the contents of the GMSA credential spec named by the GMSACredentialSpecName field.", + Type: []string{"string"}, + Format: "", + }, + }, + "runAsUserName": { + SchemaProps: spec.SchemaProps{ + Description: "The UserName in Windows to run the entrypoint of the container process. Defaults to the user specified in image metadata if unspecified. May also be set in PodSecurityContext. If set in both SecurityContext and PodSecurityContext, the value specified in SecurityContext takes precedence.", + Type: []string{"string"}, + Format: "", + }, + }, + "hostProcess": { + SchemaProps: spec.SchemaProps{ + Description: "HostProcess determines if a container should be run as a 'Host Process' container. All of a Pod's containers must have the same effective HostProcess value (it is not allowed to have a mix of HostProcess containers and non-HostProcess containers). In addition, if HostProcess is true then HostNetwork must also be set to true.", + Type: []string{"boolean"}, + Format: "", + }, + }, + }, + }, + }, + } +} + +func schema_pkg_apis_apiextensions_v1_ConversionRequest(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "ConversionRequest describes the conversion request parameters.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "uid": { + SchemaProps: spec.SchemaProps{ + Description: "uid is an identifier for the individual request/response. It allows distinguishing instances of requests which are otherwise identical (parallel requests, etc). The UID is meant to track the round trip (request/response) between the Kubernetes API server and the webhook, not the user request. It is suitable for correlating log entries between the webhook and apiserver, for either auditing or debugging.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "desiredAPIVersion": { + SchemaProps: spec.SchemaProps{ + Description: "desiredAPIVersion is the version to convert given objects to. e.g. \"myapi.example.com/v1\"", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "objects": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "objects is the list of custom resource objects to be converted.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Ref: ref("k8s.io/apimachinery/pkg/runtime.RawExtension"), + }, + }, + }, + }, + }, + }, + Required: []string{"uid", "desiredAPIVersion", "objects"}, + }, + }, + Dependencies: []string{ + "k8s.io/apimachinery/pkg/runtime.RawExtension"}, + } +} + +func schema_pkg_apis_apiextensions_v1_ConversionResponse(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "ConversionResponse describes a conversion response.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "uid": { + SchemaProps: spec.SchemaProps{ + Description: "uid is an identifier for the individual request/response. This should be copied over from the corresponding `request.uid`.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "convertedObjects": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "convertedObjects is the list of converted version of `request.objects` if the `result` is successful, otherwise empty. The webhook is expected to set `apiVersion` of these objects to the `request.desiredAPIVersion`. The list must also have the same size as the input list with the same objects in the same order (equal kind, metadata.uid, metadata.name and metadata.namespace). The webhook is allowed to mutate labels and annotations. Any other change to the metadata is silently ignored.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Ref: ref("k8s.io/apimachinery/pkg/runtime.RawExtension"), + }, + }, + }, + }, + }, + "result": { + SchemaProps: spec.SchemaProps{ + Description: "result contains the result of conversion with extra details if the conversion failed. `result.status` determines if the conversion failed or succeeded. The `result.status` field is required and represents the success or failure of the conversion. A successful conversion must set `result.status` to `Success`. A failed conversion must set `result.status` to `Failure` and provide more details in `result.message` and return http status 200. The `result.message` will be used to construct an error message for the end user.", + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.Status"), + }, + }, + }, + Required: []string{"uid", "convertedObjects", "result"}, + }, + }, + Dependencies: []string{ + "k8s.io/apimachinery/pkg/apis/meta/v1.Status", "k8s.io/apimachinery/pkg/runtime.RawExtension"}, + } +} + +func schema_pkg_apis_apiextensions_v1_ConversionReview(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "ConversionReview describes a conversion request/response.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", + }, + }, + "request": { + SchemaProps: spec.SchemaProps{ + Description: "request describes the attributes for the conversion request.", + Ref: ref("k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.ConversionRequest"), + }, + }, + "response": { + SchemaProps: spec.SchemaProps{ + Description: "response describes the attributes for the conversion response.", + Ref: ref("k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.ConversionResponse"), + }, + }, + }, + }, + }, + Dependencies: []string{ + "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.ConversionRequest", "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.ConversionResponse"}, + } +} + +func schema_pkg_apis_apiextensions_v1_CustomResourceColumnDefinition(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "CustomResourceColumnDefinition specifies a column for server side printing.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "name": { + SchemaProps: spec.SchemaProps{ + Description: "name is a human readable name for the column.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "type": { + SchemaProps: spec.SchemaProps{ + Description: "type is an OpenAPI type definition for this column. See https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#data-types for details.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "format": { + SchemaProps: spec.SchemaProps{ + Description: "format is an optional OpenAPI type definition for this column. The 'name' format is applied to the primary identifier column to assist in clients identifying column is the resource name. See https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#data-types for details.", + Type: []string{"string"}, + Format: "", + }, + }, + "description": { + SchemaProps: spec.SchemaProps{ + Description: "description is a human readable description of this column.", + Type: []string{"string"}, + Format: "", + }, + }, + "priority": { + SchemaProps: spec.SchemaProps{ + Description: "priority is an integer defining the relative importance of this column compared to others. Lower numbers are considered higher priority. Columns that may be omitted in limited space scenarios should be given a priority greater than 0.", + Type: []string{"integer"}, + Format: "int32", + }, + }, + "jsonPath": { + SchemaProps: spec.SchemaProps{ + Description: "jsonPath is a simple JSON path (i.e. with array notation) which is evaluated against each custom resource to produce the value for this column.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"name", "type", "jsonPath"}, + }, + }, + } +} + +func schema_pkg_apis_apiextensions_v1_CustomResourceConversion(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "CustomResourceConversion describes how to convert different versions of a CR.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "strategy": { + SchemaProps: spec.SchemaProps{ + Description: "strategy specifies how custom resources are converted between versions. Allowed values are: - `\"None\"`: The converter only change the apiVersion and would not touch any other field in the custom resource. - `\"Webhook\"`: API Server will call to an external webhook to do the conversion. Additional information\n is needed for this option. This requires spec.preserveUnknownFields to be false, and spec.conversion.webhook to be set.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "webhook": { + SchemaProps: spec.SchemaProps{ + Description: "webhook describes how to call the conversion webhook. Required when `strategy` is set to `\"Webhook\"`.", + Ref: ref("k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.WebhookConversion"), + }, + }, + }, + Required: []string{"strategy"}, + }, + }, + Dependencies: []string{ + "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.WebhookConversion"}, + } +} + +func schema_pkg_apis_apiextensions_v1_CustomResourceDefinition(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "CustomResourceDefinition represents a resource that should be exposed on the API server. Its name MUST be in the format <.spec.name>.<.spec.group>.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", + }, + }, + "metadata": { + SchemaProps: spec.SchemaProps{ + Description: "Standard object's metadata More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata", + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"), + }, + }, + "spec": { + SchemaProps: spec.SchemaProps{ + Description: "spec describes how the user wants the resources to appear", + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.CustomResourceDefinitionSpec"), + }, + }, + "status": { + SchemaProps: spec.SchemaProps{ + Description: "status indicates the actual state of the CustomResourceDefinition", + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.CustomResourceDefinitionStatus"), + }, + }, + }, + Required: []string{"spec"}, + }, + }, + Dependencies: []string{ + "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.CustomResourceDefinitionSpec", "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.CustomResourceDefinitionStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"}, + } +} + +func schema_pkg_apis_apiextensions_v1_CustomResourceDefinitionCondition(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "CustomResourceDefinitionCondition contains details for the current condition of this pod.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "type": { + SchemaProps: spec.SchemaProps{ + Description: "type is the type of the condition. Types include Established, NamesAccepted and Terminating.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "status": { + SchemaProps: spec.SchemaProps{ + Description: "status is the status of the condition. Can be True, False, Unknown.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "lastTransitionTime": { + SchemaProps: spec.SchemaProps{ + Description: "lastTransitionTime last time the condition transitioned from one status to another.", + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.Time"), + }, + }, + "reason": { + SchemaProps: spec.SchemaProps{ + Description: "reason is a unique, one-word, CamelCase reason for the condition's last transition.", + Type: []string{"string"}, + Format: "", + }, + }, + "message": { + SchemaProps: spec.SchemaProps{ + Description: "message is a human-readable message indicating details about last transition.", + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"type", "status"}, + }, + }, + Dependencies: []string{ + "k8s.io/apimachinery/pkg/apis/meta/v1.Time"}, + } +} + +func schema_pkg_apis_apiextensions_v1_CustomResourceDefinitionList(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "CustomResourceDefinitionList is a list of CustomResourceDefinition objects.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", + }, + }, + "metadata": { + SchemaProps: spec.SchemaProps{ + Description: "Standard object's metadata More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata", + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"), + }, + }, + "items": { + SchemaProps: spec.SchemaProps{ + Description: "items list individual CustomResourceDefinition objects", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.CustomResourceDefinition"), + }, + }, + }, + }, + }, + }, + Required: []string{"items"}, + }, + }, + Dependencies: []string{ + "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.CustomResourceDefinition", "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"}, + } +} + +func schema_pkg_apis_apiextensions_v1_CustomResourceDefinitionNames(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "CustomResourceDefinitionNames indicates the names to serve this CustomResourceDefinition", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "plural": { + SchemaProps: spec.SchemaProps{ + Description: "plural is the plural name of the resource to serve. The custom resources are served under `/apis///.../`. Must match the name of the CustomResourceDefinition (in the form `.`). Must be all lowercase.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "singular": { + SchemaProps: spec.SchemaProps{ + Description: "singular is the singular name of the resource. It must be all lowercase. Defaults to lowercased `kind`.", + Type: []string{"string"}, + Format: "", + }, + }, + "shortNames": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "shortNames are short names for the resource, exposed in API discovery documents, and used by clients to support invocations like `kubectl get `. It must be all lowercase.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "kind is the serialized kind of the resource. It is normally CamelCase and singular. Custom resource instances will use this value as the `kind` attribute in API calls.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "listKind": { + SchemaProps: spec.SchemaProps{ + Description: "listKind is the serialized kind of the list for this resource. Defaults to \"`kind`List\".", + Type: []string{"string"}, + Format: "", + }, + }, + "categories": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "categories is a list of grouped resources this custom resource belongs to (e.g. 'all'). This is published in API discovery documents, and used by clients to support invocations like `kubectl get all`.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + }, + Required: []string{"plural", "kind"}, + }, + }, + } +} + +func schema_pkg_apis_apiextensions_v1_CustomResourceDefinitionSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "CustomResourceDefinitionSpec describes how a user wants their resource to appear", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "group": { + SchemaProps: spec.SchemaProps{ + Description: "group is the API group of the defined custom resource. The custom resources are served under `/apis//...`. Must match the name of the CustomResourceDefinition (in the form `.`).", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "names": { + SchemaProps: spec.SchemaProps{ + Description: "names specify the resource and kind names for the custom resource.", + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.CustomResourceDefinitionNames"), + }, + }, + "scope": { + SchemaProps: spec.SchemaProps{ + Description: "scope indicates whether the defined custom resource is cluster- or namespace-scoped. Allowed values are `Cluster` and `Namespaced`.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "versions": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "versions is the list of all API versions of the defined custom resource. Version names are used to compute the order in which served versions are listed in API discovery. If the version string is \"kube-like\", it will sort above non \"kube-like\" version strings, which are ordered lexicographically. \"Kube-like\" versions start with a \"v\", then are followed by a number (the major version), then optionally the string \"alpha\" or \"beta\" and another number (the minor version). These are sorted first by GA > beta > alpha (where GA is a version with no suffix such as beta or alpha), and then by comparing major version, then minor version. An example sorted list of versions: v10, v2, v1, v11beta2, v10beta3, v3beta1, v12alpha1, v11alpha2, foo1, foo10.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.CustomResourceDefinitionVersion"), + }, + }, + }, + }, + }, + "conversion": { + SchemaProps: spec.SchemaProps{ + Description: "conversion defines conversion settings for the CRD.", + Ref: ref("k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.CustomResourceConversion"), + }, + }, + "preserveUnknownFields": { + SchemaProps: spec.SchemaProps{ + Description: "preserveUnknownFields indicates that object fields which are not specified in the OpenAPI schema should be preserved when persisting to storage. apiVersion, kind, metadata and known fields inside metadata are always preserved. This field is deprecated in favor of setting `x-preserve-unknown-fields` to true in `spec.versions[*].schema.openAPIV3Schema`. See https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definitions/#field-pruning for details.", + Type: []string{"boolean"}, + Format: "", + }, + }, + }, + Required: []string{"group", "names", "scope", "versions"}, + }, + }, + Dependencies: []string{ + "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.CustomResourceConversion", "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.CustomResourceDefinitionNames", "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.CustomResourceDefinitionVersion"}, + } +} + +func schema_pkg_apis_apiextensions_v1_CustomResourceDefinitionStatus(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "CustomResourceDefinitionStatus indicates the state of the CustomResourceDefinition", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "conditions": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-map-keys": []interface{}{ + "type", + }, + "x-kubernetes-list-type": "map", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "conditions indicate state for particular aspects of a CustomResourceDefinition", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.CustomResourceDefinitionCondition"), + }, + }, + }, + }, + }, + "acceptedNames": { + SchemaProps: spec.SchemaProps{ + Description: "acceptedNames are the names that are actually being used to serve discovery. They may be different than the names in spec.", + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.CustomResourceDefinitionNames"), + }, + }, + "storedVersions": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "storedVersions lists all versions of CustomResources that were ever persisted. Tracking these versions allows a migration path for stored versions in etcd. The field is mutable so a migration controller can finish a migration to another version (ensuring no old objects are left in storage), and then remove the rest of the versions from this list. Versions may not be removed from `spec.versions` while they exist in this list.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + }, + }, + }, + Dependencies: []string{ + "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.CustomResourceDefinitionCondition", "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.CustomResourceDefinitionNames"}, + } +} + +func schema_pkg_apis_apiextensions_v1_CustomResourceDefinitionVersion(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "CustomResourceDefinitionVersion describes a version for CRD.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "name": { + SchemaProps: spec.SchemaProps{ + Description: "name is the version name, e.g. “v1”, “v2beta1”, etc. The custom resources are served under this version at `/apis///...` if `served` is true.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "served": { + SchemaProps: spec.SchemaProps{ + Description: "served is a flag enabling/disabling this version from being served via REST APIs", + Default: false, + Type: []string{"boolean"}, + Format: "", + }, + }, + "storage": { + SchemaProps: spec.SchemaProps{ + Description: "storage indicates this version should be used when persisting custom resources to storage. There must be exactly one version with storage=true.", + Default: false, + Type: []string{"boolean"}, + Format: "", + }, + }, + "deprecated": { + SchemaProps: spec.SchemaProps{ + Description: "deprecated indicates this version of the custom resource API is deprecated. When set to true, API requests to this version receive a warning header in the server response. Defaults to false.", + Type: []string{"boolean"}, + Format: "", + }, + }, + "deprecationWarning": { + SchemaProps: spec.SchemaProps{ + Description: "deprecationWarning overrides the default warning returned to API clients. May only be set when `deprecated` is true. The default warning indicates this version is deprecated and recommends use of the newest served version of equal or greater stability, if one exists.", + Type: []string{"string"}, + Format: "", + }, + }, + "schema": { + SchemaProps: spec.SchemaProps{ + Description: "schema describes the schema used for validation, pruning, and defaulting of this version of the custom resource.", + Ref: ref("k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.CustomResourceValidation"), + }, + }, + "subresources": { + SchemaProps: spec.SchemaProps{ + Description: "subresources specify what subresources this version of the defined custom resource have.", + Ref: ref("k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.CustomResourceSubresources"), + }, + }, + "additionalPrinterColumns": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "additionalPrinterColumns specifies additional columns returned in Table output. See https://kubernetes.io/docs/reference/using-api/api-concepts/#receiving-resources-as-tables for details. If no columns are specified, a single column displaying the age of the custom resource is used.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.CustomResourceColumnDefinition"), + }, + }, + }, + }, + }, + "selectableFields": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "selectableFields specifies paths to fields that may be used as field selectors. A maximum of 8 selectable fields are allowed. See https://kubernetes.io/docs/concepts/overview/working-with-objects/field-selectors", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.SelectableField"), + }, + }, + }, + }, + }, + }, + Required: []string{"name", "served", "storage"}, + }, + }, + Dependencies: []string{ + "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.CustomResourceColumnDefinition", "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.CustomResourceSubresources", "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.CustomResourceValidation", "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.SelectableField"}, + } +} + +func schema_pkg_apis_apiextensions_v1_CustomResourceSubresourceScale(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "CustomResourceSubresourceScale defines how to serve the scale subresource for CustomResources.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "specReplicasPath": { + SchemaProps: spec.SchemaProps{ + Description: "specReplicasPath defines the JSON path inside of a custom resource that corresponds to Scale `spec.replicas`. Only JSON paths without the array notation are allowed. Must be a JSON Path under `.spec`. If there is no value under the given path in the custom resource, the `/scale` subresource will return an error on GET.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "statusReplicasPath": { + SchemaProps: spec.SchemaProps{ + Description: "statusReplicasPath defines the JSON path inside of a custom resource that corresponds to Scale `status.replicas`. Only JSON paths without the array notation are allowed. Must be a JSON Path under `.status`. If there is no value under the given path in the custom resource, the `status.replicas` value in the `/scale` subresource will default to 0.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "labelSelectorPath": { + SchemaProps: spec.SchemaProps{ + Description: "labelSelectorPath defines the JSON path inside of a custom resource that corresponds to Scale `status.selector`. Only JSON paths without the array notation are allowed. Must be a JSON Path under `.status` or `.spec`. Must be set to work with HorizontalPodAutoscaler. The field pointed by this JSON path must be a string field (not a complex selector struct) which contains a serialized label selector in string form. More info: https://kubernetes.io/docs/tasks/access-kubernetes-api/custom-resources/custom-resource-definitions#scale-subresource If there is no value under the given path in the custom resource, the `status.selector` value in the `/scale` subresource will default to the empty string.", + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"specReplicasPath", "statusReplicasPath"}, + }, + }, + } +} + +func schema_pkg_apis_apiextensions_v1_CustomResourceSubresourceStatus(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "CustomResourceSubresourceStatus defines how to serve the status subresource for CustomResources. Status is represented by the `.status` JSON path inside of a CustomResource. When set, * exposes a /status subresource for the custom resource * PUT requests to the /status subresource take a custom resource object, and ignore changes to anything except the status stanza * PUT/POST/PATCH requests to the custom resource ignore changes to the status stanza", + Type: []string{"object"}, + }, + }, + } +} + +func schema_pkg_apis_apiextensions_v1_CustomResourceSubresources(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "CustomResourceSubresources defines the status and scale subresources for CustomResources.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "status": { + SchemaProps: spec.SchemaProps{ + Description: "status indicates the custom resource should serve a `/status` subresource. When enabled: 1. requests to the custom resource primary endpoint ignore changes to the `status` stanza of the object. 2. requests to the custom resource `/status` subresource ignore changes to anything other than the `status` stanza of the object.", + Ref: ref("k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.CustomResourceSubresourceStatus"), + }, + }, + "scale": { + SchemaProps: spec.SchemaProps{ + Description: "scale indicates the custom resource should serve a `/scale` subresource that returns an `autoscaling/v1` Scale object.", + Ref: ref("k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.CustomResourceSubresourceScale"), + }, + }, + }, + }, + }, + Dependencies: []string{ + "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.CustomResourceSubresourceScale", "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.CustomResourceSubresourceStatus"}, + } +} + +func schema_pkg_apis_apiextensions_v1_CustomResourceValidation(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "CustomResourceValidation is a list of validation methods for CustomResources.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "openAPIV3Schema": { + SchemaProps: spec.SchemaProps{ + Description: "openAPIV3Schema is the OpenAPI v3 schema to use for validation and pruning.", + Ref: ref("k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.JSONSchemaProps"), + }, + }, + }, + }, + }, + Dependencies: []string{ + "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.JSONSchemaProps"}, + } +} + +func schema_pkg_apis_apiextensions_v1_ExternalDocumentation(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "ExternalDocumentation allows referencing an external resource for extended documentation.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "description": { + SchemaProps: spec.SchemaProps{ + Type: []string{"string"}, + Format: "", + }, + }, + "url": { + SchemaProps: spec.SchemaProps{ + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + } +} + +func schema_pkg_apis_apiextensions_v1_JSON(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "JSON represents any valid JSON value. These types are supported: bool, int64, float64, string, []interface{}, map[string]interface{} and nil.", + Type: apiextensionsv1.JSON{}.OpenAPISchemaType(), + Format: apiextensionsv1.JSON{}.OpenAPISchemaFormat(), + }, + }, + } +} + +func schema_pkg_apis_apiextensions_v1_JSONSchemaProps(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "JSONSchemaProps is a JSON-Schema following Specification Draft 4 (http://json-schema.org/).", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "id": { + SchemaProps: spec.SchemaProps{ + Type: []string{"string"}, + Format: "", + }, + }, + "$schema": { + SchemaProps: spec.SchemaProps{ + Type: []string{"string"}, + Format: "", + }, + }, + "$ref": { + SchemaProps: spec.SchemaProps{ + Type: []string{"string"}, + Format: "", + }, + }, + "description": { + SchemaProps: spec.SchemaProps{ + Type: []string{"string"}, + Format: "", + }, + }, + "type": { + SchemaProps: spec.SchemaProps{ + Type: []string{"string"}, + Format: "", + }, + }, + "format": { + SchemaProps: spec.SchemaProps{ + Description: "format is an OpenAPI v3 format string. Unknown formats are ignored. The following formats are validated:\n\n- bsonobjectid: a bson object ID, i.e. a 24 characters hex string - uri: an URI as parsed by Golang net/url.ParseRequestURI - email: an email address as parsed by Golang net/mail.ParseAddress - hostname: a valid representation for an Internet host name, as defined by RFC 1034, section 3.1 [RFC1034]. - ipv4: an IPv4 IP as parsed by Golang net.ParseIP - ipv6: an IPv6 IP as parsed by Golang net.ParseIP - cidr: a CIDR as parsed by Golang net.ParseCIDR - mac: a MAC address as parsed by Golang net.ParseMAC - uuid: an UUID that allows uppercase defined by the regex (?i)^[0-9a-f]{8}-?[0-9a-f]{4}-?[0-9a-f]{4}-?[0-9a-f]{4}-?[0-9a-f]{12}$ - uuid3: an UUID3 that allows uppercase defined by the regex (?i)^[0-9a-f]{8}-?[0-9a-f]{4}-?3[0-9a-f]{3}-?[0-9a-f]{4}-?[0-9a-f]{12}$ - uuid4: an UUID4 that allows uppercase defined by the regex (?i)^[0-9a-f]{8}-?[0-9a-f]{4}-?4[0-9a-f]{3}-?[89ab][0-9a-f]{3}-?[0-9a-f]{12}$ - uuid5: an UUID5 that allows uppercase defined by the regex (?i)^[0-9a-f]{8}-?[0-9a-f]{4}-?5[0-9a-f]{3}-?[89ab][0-9a-f]{3}-?[0-9a-f]{12}$ - isbn: an ISBN10 or ISBN13 number string like \"0321751043\" or \"978-0321751041\" - isbn10: an ISBN10 number string like \"0321751043\" - isbn13: an ISBN13 number string like \"978-0321751041\" - creditcard: a credit card number defined by the regex ^(?:4[0-9]{12}(?:[0-9]{3})?|5[1-5][0-9]{14}|6(?:011|5[0-9][0-9])[0-9]{12}|3[47][0-9]{13}|3(?:0[0-5]|[68][0-9])[0-9]{11}|(?:2131|1800|35\\\\d{3})\\\\d{11})$ with any non digit characters mixed in - ssn: a U.S. social security number following the regex ^\\\\d{3}[- ]?\\\\d{2}[- ]?\\\\d{4}$ - hexcolor: an hexadecimal color code like \"#FFFFFF: following the regex ^#?([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$ - rgbcolor: an RGB color code like rgb like \"rgb(255,255,2559\" - byte: base64 encoded binary data - password: any kind of string - date: a date string like \"2006-01-02\" as defined by full-date in RFC3339 - duration: a duration string like \"22 ns\" as parsed by Golang time.ParseDuration or compatible with Scala duration format - datetime: a date time string like \"2014-12-15T19:30:20.000Z\" as defined by date-time in RFC3339.", + Type: []string{"string"}, + Format: "", + }, + }, + "title": { + SchemaProps: spec.SchemaProps{ + Type: []string{"string"}, + Format: "", + }, + }, + "default": { + SchemaProps: spec.SchemaProps{ + Description: "default is a default value for undefined object fields. Defaulting is a beta feature under the CustomResourceDefaulting feature gate. Defaulting requires spec.preserveUnknownFields to be false.", + Ref: ref("k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.JSON"), + }, + }, + "maximum": { + SchemaProps: spec.SchemaProps{ + Type: []string{"number"}, + Format: "double", + }, + }, + "exclusiveMaximum": { + SchemaProps: spec.SchemaProps{ + Type: []string{"boolean"}, + Format: "", + }, + }, + "minimum": { + SchemaProps: spec.SchemaProps{ + Type: []string{"number"}, + Format: "double", + }, + }, + "exclusiveMinimum": { + SchemaProps: spec.SchemaProps{ + Type: []string{"boolean"}, + Format: "", + }, + }, + "maxLength": { + SchemaProps: spec.SchemaProps{ + Type: []string{"integer"}, + Format: "int64", + }, + }, + "minLength": { + SchemaProps: spec.SchemaProps{ + Type: []string{"integer"}, + Format: "int64", + }, + }, + "pattern": { + SchemaProps: spec.SchemaProps{ + Type: []string{"string"}, + Format: "", + }, + }, + "maxItems": { + SchemaProps: spec.SchemaProps{ + Type: []string{"integer"}, + Format: "int64", + }, + }, + "minItems": { + SchemaProps: spec.SchemaProps{ + Type: []string{"integer"}, + Format: "int64", + }, + }, + "uniqueItems": { + SchemaProps: spec.SchemaProps{ + Type: []string{"boolean"}, + Format: "", + }, + }, + "multipleOf": { + SchemaProps: spec.SchemaProps{ + Type: []string{"number"}, + Format: "double", + }, + }, + "enum": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Ref: ref("k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.JSON"), + }, + }, + }, + }, + }, + "maxProperties": { + SchemaProps: spec.SchemaProps{ + Type: []string{"integer"}, + Format: "int64", + }, + }, + "minProperties": { + SchemaProps: spec.SchemaProps{ + Type: []string{"integer"}, + Format: "int64", + }, + }, + "required": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + "items": { + SchemaProps: spec.SchemaProps{ + Ref: ref("k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.JSONSchemaPropsOrArray"), + }, + }, + "allOf": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.JSONSchemaProps"), + }, + }, + }, + }, + }, + "oneOf": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.JSONSchemaProps"), + }, + }, + }, + }, + }, + "anyOf": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.JSONSchemaProps"), + }, + }, + }, + }, + }, + "not": { + SchemaProps: spec.SchemaProps{ + Ref: ref("k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.JSONSchemaProps"), + }, + }, + "properties": { + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + AdditionalProperties: &spec.SchemaOrBool{ + Allows: true, + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.JSONSchemaProps"), + }, + }, + }, + }, + }, + "additionalProperties": { + SchemaProps: spec.SchemaProps{ + Ref: ref("k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.JSONSchemaPropsOrBool"), + }, + }, + "patternProperties": { + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + AdditionalProperties: &spec.SchemaOrBool{ + Allows: true, + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.JSONSchemaProps"), + }, + }, + }, + }, + }, + "dependencies": { + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + AdditionalProperties: &spec.SchemaOrBool{ + Allows: true, + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Ref: ref("k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.JSONSchemaPropsOrStringArray"), + }, + }, + }, + }, + }, + "additionalItems": { + SchemaProps: spec.SchemaProps{ + Ref: ref("k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.JSONSchemaPropsOrBool"), + }, + }, + "definitions": { + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + AdditionalProperties: &spec.SchemaOrBool{ + Allows: true, + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.JSONSchemaProps"), + }, + }, + }, + }, + }, + "externalDocs": { + SchemaProps: spec.SchemaProps{ + Ref: ref("k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.ExternalDocumentation"), + }, + }, + "example": { + SchemaProps: spec.SchemaProps{ + Ref: ref("k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.JSON"), + }, + }, + "nullable": { + SchemaProps: spec.SchemaProps{ + Type: []string{"boolean"}, + Format: "", + }, + }, + "x-kubernetes-preserve-unknown-fields": { + SchemaProps: spec.SchemaProps{ + Description: "x-kubernetes-preserve-unknown-fields stops the API server decoding step from pruning fields which are not specified in the validation schema. This affects fields recursively, but switches back to normal pruning behaviour if nested properties or additionalProperties are specified in the schema. This can either be true or undefined. False is forbidden.", + Type: []string{"boolean"}, + Format: "", + }, + }, + "x-kubernetes-embedded-resource": { + SchemaProps: spec.SchemaProps{ + Description: "x-kubernetes-embedded-resource defines that the value is an embedded Kubernetes runtime.Object, with TypeMeta and ObjectMeta. The type must be object. It is allowed to further restrict the embedded object. kind, apiVersion and metadata are validated automatically. x-kubernetes-preserve-unknown-fields is allowed to be true, but does not have to be if the object is fully specified (up to kind, apiVersion, metadata).", + Type: []string{"boolean"}, + Format: "", + }, + }, + "x-kubernetes-int-or-string": { + SchemaProps: spec.SchemaProps{ + Description: "x-kubernetes-int-or-string specifies that this value is either an integer or a string. If this is true, an empty type is allowed and type as child of anyOf is permitted if following one of the following patterns:\n\n1) anyOf:\n - type: integer\n - type: string\n2) allOf:\n - anyOf:\n - type: integer\n - type: string\n - ... zero or more", + Type: []string{"boolean"}, + Format: "", + }, + }, + "x-kubernetes-list-map-keys": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "x-kubernetes-list-map-keys annotates an array with the x-kubernetes-list-type `map` by specifying the keys used as the index of the map.\n\nThis tag MUST only be used on lists that have the \"x-kubernetes-list-type\" extension set to \"map\". Also, the values specified for this attribute must be a scalar typed field of the child structure (no nesting is supported).\n\nThe properties specified must either be required or have a default value, to ensure those properties are present for all list items.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + "x-kubernetes-list-type": { + SchemaProps: spec.SchemaProps{ + Description: "x-kubernetes-list-type annotates an array to further describe its topology. This extension must only be used on lists and may have 3 possible values:\n\n1) `atomic`: the list is treated as a single entity, like a scalar.\n Atomic lists will be entirely replaced when updated. This extension\n may be used on any type of list (struct, scalar, ...).\n2) `set`:\n Sets are lists that must not have multiple items with the same value. Each\n value must be a scalar, an object with x-kubernetes-map-type `atomic` or an\n array with x-kubernetes-list-type `atomic`.\n3) `map`:\n These lists are like maps in that their elements have a non-index key\n used to identify them. Order is preserved upon merge. The map tag\n must only be used on a list with elements of type object.\nDefaults to atomic for arrays.", + Type: []string{"string"}, + Format: "", + }, + }, + "x-kubernetes-map-type": { + SchemaProps: spec.SchemaProps{ + Description: "x-kubernetes-map-type annotates an object to further describe its topology. This extension must only be used when type is object and may have 2 possible values:\n\n1) `granular`:\n These maps are actual maps (key-value pairs) and each fields are independent\n from each other (they can each be manipulated by separate actors). This is\n the default behaviour for all maps.\n2) `atomic`: the list is treated as a single entity, like a scalar.\n Atomic maps will be entirely replaced when updated.", + Type: []string{"string"}, + Format: "", + }, + }, + "x-kubernetes-validations": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-map-keys": []interface{}{ + "rule", + }, + "x-kubernetes-list-type": "map", + "x-kubernetes-patch-merge-key": "rule", + "x-kubernetes-patch-strategy": "merge", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "x-kubernetes-validations describes a list of validation rules written in the CEL expression language.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.ValidationRule"), + }, + }, + }, + }, + }, + }, + }, + }, + Dependencies: []string{ + "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.ExternalDocumentation", "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.JSON", "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.JSONSchemaProps", "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.JSONSchemaPropsOrArray", "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.JSONSchemaPropsOrBool", "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.JSONSchemaPropsOrStringArray", "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.ValidationRule"}, + } +} + +func schema_pkg_apis_apiextensions_v1_JSONSchemaPropsOrArray(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "JSONSchemaPropsOrArray represents a value that can either be a JSONSchemaProps or an array of JSONSchemaProps. Mainly here for serialization purposes.", + Type: apiextensionsv1.JSONSchemaPropsOrArray{}.OpenAPISchemaType(), + Format: apiextensionsv1.JSONSchemaPropsOrArray{}.OpenAPISchemaFormat(), + }, + }, + } +} + +func schema_pkg_apis_apiextensions_v1_JSONSchemaPropsOrBool(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "JSONSchemaPropsOrBool represents JSONSchemaProps or a boolean value. Defaults to true for the boolean property.", + Type: apiextensionsv1.JSONSchemaPropsOrBool{}.OpenAPISchemaType(), + Format: apiextensionsv1.JSONSchemaPropsOrBool{}.OpenAPISchemaFormat(), + }, + }, + } +} + +func schema_pkg_apis_apiextensions_v1_JSONSchemaPropsOrStringArray(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "JSONSchemaPropsOrStringArray represents a JSONSchemaProps or a string array.", + Type: apiextensionsv1.JSONSchemaPropsOrStringArray{}.OpenAPISchemaType(), + Format: apiextensionsv1.JSONSchemaPropsOrStringArray{}.OpenAPISchemaFormat(), + }, + }, + } +} + +func schema_pkg_apis_apiextensions_v1_SelectableField(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "SelectableField specifies the JSON path of a field that may be used with field selectors.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "jsonPath": { + SchemaProps: spec.SchemaProps{ + Description: "jsonPath is a simple JSON path which is evaluated against each custom resource to produce a field selector value. Only JSON paths without the array notation are allowed. Must point to a field of type string, boolean or integer. Types with enum values and strings with formats are allowed. If jsonPath refers to absent field in a resource, the jsonPath evaluates to an empty string. Must not point to metdata fields. Required.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"jsonPath"}, + }, + }, + } +} + +func schema_pkg_apis_apiextensions_v1_ServiceReference(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "ServiceReference holds a reference to Service.legacy.k8s.io", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "namespace": { + SchemaProps: spec.SchemaProps{ + Description: "namespace is the namespace of the service. Required", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "name": { + SchemaProps: spec.SchemaProps{ + Description: "name is the name of the service. Required", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "path": { + SchemaProps: spec.SchemaProps{ + Description: "path is an optional URL path at which the webhook will be contacted.", + Type: []string{"string"}, + Format: "", + }, + }, + "port": { + SchemaProps: spec.SchemaProps{ + Description: "port is an optional service port at which the webhook will be contacted. `port` should be a valid port number (1-65535, inclusive). Defaults to 443 for backward compatibility.", + Type: []string{"integer"}, + Format: "int32", + }, + }, + }, + Required: []string{"namespace", "name"}, + }, + }, + } +} + +func schema_pkg_apis_apiextensions_v1_ValidationRule(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "ValidationRule describes a validation rule written in the CEL expression language.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "rule": { + SchemaProps: spec.SchemaProps{ + Description: "Rule represents the expression which will be evaluated by CEL. ref: https://github.com/google/cel-spec The Rule is scoped to the location of the x-kubernetes-validations extension in the schema. The `self` variable in the CEL expression is bound to the scoped value. Example: - Rule scoped to the root of a resource with a status subresource: {\"rule\": \"self.status.actual <= self.spec.maxDesired\"}\n\nIf the Rule is scoped to an object with properties, the accessible properties of the object are field selectable via `self.field` and field presence can be checked via `has(self.field)`. Null valued fields are treated as absent fields in CEL expressions. If the Rule is scoped to an object with additionalProperties (i.e. a map) the value of the map are accessible via `self[mapKey]`, map containment can be checked via `mapKey in self` and all entries of the map are accessible via CEL macros and functions such as `self.all(...)`. If the Rule is scoped to an array, the elements of the array are accessible via `self[i]` and also by macros and functions. If the Rule is scoped to a scalar, `self` is bound to the scalar value. Examples: - Rule scoped to a map of objects: {\"rule\": \"self.components['Widget'].priority < 10\"} - Rule scoped to a list of integers: {\"rule\": \"self.values.all(value, value >= 0 && value < 100)\"} - Rule scoped to a string value: {\"rule\": \"self.startsWith('kube')\"}\n\nThe `apiVersion`, `kind`, `metadata.name` and `metadata.generateName` are always accessible from the root of the object and from any x-kubernetes-embedded-resource annotated objects. No other metadata properties are accessible.\n\nUnknown data preserved in custom resources via x-kubernetes-preserve-unknown-fields is not accessible in CEL expressions. This includes: - Unknown field values that are preserved by object schemas with x-kubernetes-preserve-unknown-fields. - Object properties where the property schema is of an \"unknown type\". An \"unknown type\" is recursively defined as:\n - A schema with no type and x-kubernetes-preserve-unknown-fields set to true\n - An array where the items schema is of an \"unknown type\"\n - An object where the additionalProperties schema is of an \"unknown type\"\n\nOnly property names of the form `[a-zA-Z_.-/][a-zA-Z0-9_.-/]*` are accessible. Accessible property names are escaped according to the following rules when accessed in the expression: - '__' escapes to '__underscores__' - '.' escapes to '__dot__' - '-' escapes to '__dash__' - '/' escapes to '__slash__' - Property names that exactly match a CEL RESERVED keyword escape to '__{keyword}__'. The keywords are:\n\t \"true\", \"false\", \"null\", \"in\", \"as\", \"break\", \"const\", \"continue\", \"else\", \"for\", \"function\", \"if\",\n\t \"import\", \"let\", \"loop\", \"package\", \"namespace\", \"return\".\nExamples:\n - Rule accessing a property named \"namespace\": {\"rule\": \"self.__namespace__ > 0\"}\n - Rule accessing a property named \"x-prop\": {\"rule\": \"self.x__dash__prop > 0\"}\n - Rule accessing a property named \"redact__d\": {\"rule\": \"self.redact__underscores__d > 0\"}\n\nEquality on arrays with x-kubernetes-list-type of 'set' or 'map' ignores element order, i.e. [1, 2] == [2, 1]. Concatenation on arrays with x-kubernetes-list-type use the semantics of the list type:\n - 'set': `X + Y` performs a union where the array positions of all elements in `X` are preserved and\n non-intersecting elements in `Y` are appended, retaining their partial order.\n - 'map': `X + Y` performs a merge where the array positions of all keys in `X` are preserved but the values\n are overwritten by values in `Y` when the key sets of `X` and `Y` intersect. Elements in `Y` with\n non-intersecting keys are appended, retaining their partial order.\n\nIf `rule` makes use of the `oldSelf` variable it is implicitly a `transition rule`.\n\nBy default, the `oldSelf` variable is the same type as `self`. When `optionalOldSelf` is true, the `oldSelf` variable is a CEL optional\n variable whose value() is the same type as `self`.\nSee the documentation for the `optionalOldSelf` field for details.\n\nTransition rules by default are applied only on UPDATE requests and are skipped if an old value could not be found. You can opt a transition rule into unconditional evaluation by setting `optionalOldSelf` to true.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "message": { + SchemaProps: spec.SchemaProps{ + Description: "Message represents the message displayed when validation fails. The message is required if the Rule contains line breaks. The message must not contain line breaks. If unset, the message is \"failed rule: {Rule}\". e.g. \"must be a URL with the host matching spec.host\"", + Type: []string{"string"}, + Format: "", + }, + }, + "messageExpression": { + SchemaProps: spec.SchemaProps{ + Description: "MessageExpression declares a CEL expression that evaluates to the validation failure message that is returned when this rule fails. Since messageExpression is used as a failure message, it must evaluate to a string. If both message and messageExpression are present on a rule, then messageExpression will be used if validation fails. If messageExpression results in a runtime error, the runtime error is logged, and the validation failure message is produced as if the messageExpression field were unset. If messageExpression evaluates to an empty string, a string with only spaces, or a string that contains line breaks, then the validation failure message will also be produced as if the messageExpression field were unset, and the fact that messageExpression produced an empty string/string with only spaces/string with line breaks will be logged. messageExpression has access to all the same variables as the rule; the only difference is the return type. Example: \"x must be less than max (\"+string(self.max)+\")\"", + Type: []string{"string"}, + Format: "", + }, + }, + "reason": { + SchemaProps: spec.SchemaProps{ + Description: "reason provides a machine-readable validation failure reason that is returned to the caller when a request fails this validation rule. The HTTP status code returned to the caller will match the reason of the reason of the first failed validation rule. The currently supported reasons are: \"FieldValueInvalid\", \"FieldValueForbidden\", \"FieldValueRequired\", \"FieldValueDuplicate\". If not set, default to use \"FieldValueInvalid\". All future added reasons must be accepted by clients when reading this value and unknown reasons should be treated as FieldValueInvalid.\n\nPossible enum values:\n - `\"FieldValueDuplicate\"` is used to report collisions of values that must be unique (e.g. unique IDs).\n - `\"FieldValueForbidden\"` is used to report valid (as per formatting rules) values which would be accepted under some conditions, but which are not permitted by the current conditions (such as security policy).\n - `\"FieldValueInvalid\"` is used to report malformed values (e.g. failed regex match, too long, out of bounds).\n - `\"FieldValueRequired\"` is used to report required values that are not provided (e.g. empty strings, null values, or empty arrays).", + Type: []string{"string"}, + Format: "", + Enum: []interface{}{"FieldValueDuplicate", "FieldValueForbidden", "FieldValueInvalid", "FieldValueRequired"}, + }, + }, + "fieldPath": { + SchemaProps: spec.SchemaProps{ + Description: "fieldPath represents the field path returned when the validation fails. It must be a relative JSON path (i.e. with array notation) scoped to the location of this x-kubernetes-validations extension in the schema and refer to an existing field. e.g. when validation checks if a specific attribute `foo` under a map `testMap`, the fieldPath could be set to `.testMap.foo` If the validation checks two lists must have unique attributes, the fieldPath could be set to either of the list: e.g. `.testList` It does not support list numeric index. It supports child operation to refer to an existing field currently. Refer to [JSONPath support in Kubernetes](https://kubernetes.io/docs/reference/kubectl/jsonpath/) for more info. Numeric index of array is not supported. For field name which contains special characters, use `['specialName']` to refer the field name. e.g. for attribute `foo.34$` appears in a list `testList`, the fieldPath could be set to `.testList['foo.34$']`", + Type: []string{"string"}, + Format: "", + }, + }, + "optionalOldSelf": { + SchemaProps: spec.SchemaProps{ + Description: "optionalOldSelf is used to opt a transition rule into evaluation even when the object is first created, or if the old object is missing the value.\n\nWhen enabled `oldSelf` will be a CEL optional whose value will be `None` if there is no old value, or when the object is initially created.\n\nYou may check for presence of oldSelf using `oldSelf.hasValue()` and unwrap it after checking using `oldSelf.value()`. Check the CEL documentation for Optional types for more information: https://pkg.go.dev/github.com/google/cel-go/cel#OptionalTypes\n\nMay not be set unless `oldSelf` is used in `rule`.", + Type: []string{"boolean"}, + Format: "", + }, + }, + }, + Required: []string{"rule"}, + }, + }, + } +} + +func schema_pkg_apis_apiextensions_v1_WebhookClientConfig(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "WebhookClientConfig contains the information to make a TLS connection with the webhook.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "url": { + SchemaProps: spec.SchemaProps{ + Description: "url gives the location of the webhook, in standard URL form (`scheme://host:port/path`). Exactly one of `url` or `service` must be specified.\n\nThe `host` should not refer to a service running in the cluster; use the `service` field instead. The host might be resolved via external DNS in some apiservers (e.g., `kube-apiserver` cannot resolve in-cluster DNS as that would be a layering violation). `host` may also be an IP address.\n\nPlease note that using `localhost` or `127.0.0.1` as a `host` is risky unless you take great care to run this webhook on all hosts which run an apiserver which might need to make calls to this webhook. Such installs are likely to be non-portable, i.e., not easy to turn up in a new cluster.\n\nThe scheme must be \"https\"; the URL must begin with \"https://\".\n\nA path is optional, and if present may be any string permissible in a URL. You may use the path to pass an arbitrary string to the webhook, for example, a cluster identifier.\n\nAttempting to use a user or basic auth e.g. \"user:password@\" is not allowed. Fragments (\"#...\") and query parameters (\"?...\") are not allowed, either.", + Type: []string{"string"}, + Format: "", + }, + }, + "service": { + SchemaProps: spec.SchemaProps{ + Description: "service is a reference to the service for this webhook. Either service or url must be specified.\n\nIf the webhook is running within the cluster, then you should use `service`.", + Ref: ref("k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.ServiceReference"), + }, + }, + "caBundle": { + SchemaProps: spec.SchemaProps{ + Description: "caBundle is a PEM encoded CA bundle which will be used to validate the webhook's server certificate. If unspecified, system trust roots on the apiserver are used.", + Type: []string{"string"}, + Format: "byte", + }, + }, + }, + }, + }, + Dependencies: []string{ + "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.ServiceReference"}, + } +} + +func schema_pkg_apis_apiextensions_v1_WebhookConversion(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "WebhookConversion describes how to call a conversion webhook", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "clientConfig": { + SchemaProps: spec.SchemaProps{ + Description: "clientConfig is the instructions for how to call the webhook if strategy is `Webhook`.", + Ref: ref("k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.WebhookClientConfig"), + }, + }, + "conversionReviewVersions": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "conversionReviewVersions is an ordered list of preferred `ConversionReview` versions the Webhook expects. The API server will use the first version in the list which it supports. If none of the versions specified in this list are supported by API server, conversion will fail for the custom resource. If a persisted Webhook configuration specifies allowed versions and does not include any versions known to the API Server, calls to the webhook will fail.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + }, + Required: []string{"conversionReviewVersions"}, + }, + }, + Dependencies: []string{ + "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.WebhookClientConfig"}, + } +} + +func schema_apimachinery_pkg_api_resource_Quantity(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.EmbedOpenAPIDefinitionIntoV2Extension(common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "Quantity is a fixed-point representation of a number. It provides convenient marshaling/unmarshaling in JSON and YAML, in addition to String() and AsInt64() accessors.\n\nThe serialization format is:\n\n``` ::= \n\n\t(Note that may be empty, from the \"\" case in .)\n\n ::= 0 | 1 | ... | 9 ::= | ::= | . | . | . ::= \"+\" | \"-\" ::= | ::= | | ::= Ki | Mi | Gi | Ti | Pi | Ei\n\n\t(International System of units; See: http://physics.nist.gov/cuu/Units/binary.html)\n\n ::= m | \"\" | k | M | G | T | P | E\n\n\t(Note that 1024 = 1Ki but 1000 = 1k; I didn't choose the capitalization.)\n\n ::= \"e\" | \"E\" ```\n\nNo matter which of the three exponent forms is used, no quantity may represent a number greater than 2^63-1 in magnitude, nor may it have more than 3 decimal places. Numbers larger or more precise will be capped or rounded up. (E.g.: 0.1m will rounded up to 1m.) This may be extended in the future if we require larger or smaller quantities.\n\nWhen a Quantity is parsed from a string, it will remember the type of suffix it had, and will use the same type again when it is serialized.\n\nBefore serializing, Quantity will be put in \"canonical form\". This means that Exponent/suffix will be adjusted up or down (with a corresponding increase or decrease in Mantissa) such that:\n\n- No precision is lost - No fractional digits will be emitted - The exponent (or suffix) is as large as possible.\n\nThe sign will be omitted unless the number is negative.\n\nExamples:\n\n- 1.5 will be serialized as \"1500m\" - 1.5Gi will be serialized as \"1536Mi\"\n\nNote that the quantity will NEVER be internally represented by a floating point number. That is the whole point of this exercise.\n\nNon-canonical values will still parse as long as they are well formed, but will be re-emitted in their canonical form. (So always use canonical form, or don't diff.)\n\nThis format is intended to make it difficult to use these numbers without writing some sort of special handling code in the hopes that that will cause implementors to also use a fixed point implementation.", + OneOf: common.GenerateOpenAPIV3OneOfSchema(resource.Quantity{}.OpenAPIV3OneOfTypes()), + Format: resource.Quantity{}.OpenAPISchemaFormat(), + }, + }, + }, common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "Quantity is a fixed-point representation of a number. It provides convenient marshaling/unmarshaling in JSON and YAML, in addition to String() and AsInt64() accessors.\n\nThe serialization format is:\n\n``` ::= \n\n\t(Note that may be empty, from the \"\" case in .)\n\n ::= 0 | 1 | ... | 9 ::= | ::= | . | . | . ::= \"+\" | \"-\" ::= | ::= | | ::= Ki | Mi | Gi | Ti | Pi | Ei\n\n\t(International System of units; See: http://physics.nist.gov/cuu/Units/binary.html)\n\n ::= m | \"\" | k | M | G | T | P | E\n\n\t(Note that 1024 = 1Ki but 1000 = 1k; I didn't choose the capitalization.)\n\n ::= \"e\" | \"E\" ```\n\nNo matter which of the three exponent forms is used, no quantity may represent a number greater than 2^63-1 in magnitude, nor may it have more than 3 decimal places. Numbers larger or more precise will be capped or rounded up. (E.g.: 0.1m will rounded up to 1m.) This may be extended in the future if we require larger or smaller quantities.\n\nWhen a Quantity is parsed from a string, it will remember the type of suffix it had, and will use the same type again when it is serialized.\n\nBefore serializing, Quantity will be put in \"canonical form\". This means that Exponent/suffix will be adjusted up or down (with a corresponding increase or decrease in Mantissa) such that:\n\n- No precision is lost - No fractional digits will be emitted - The exponent (or suffix) is as large as possible.\n\nThe sign will be omitted unless the number is negative.\n\nExamples:\n\n- 1.5 will be serialized as \"1500m\" - 1.5Gi will be serialized as \"1536Mi\"\n\nNote that the quantity will NEVER be internally represented by a floating point number. That is the whole point of this exercise.\n\nNon-canonical values will still parse as long as they are well formed, but will be re-emitted in their canonical form. (So always use canonical form, or don't diff.)\n\nThis format is intended to make it difficult to use these numbers without writing some sort of special handling code in the hopes that that will cause implementors to also use a fixed point implementation.", + Type: resource.Quantity{}.OpenAPISchemaType(), + Format: resource.Quantity{}.OpenAPISchemaFormat(), + }, + }, + }) +} + +func schema_apimachinery_pkg_api_resource_int64Amount(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "int64Amount represents a fixed precision numerator and arbitrary scale exponent. It is faster than operations on inf.Dec for values that can be represented as int64.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "value": { + SchemaProps: spec.SchemaProps{ + Default: 0, + Type: []string{"integer"}, + Format: "int64", + }, + }, + "scale": { + SchemaProps: spec.SchemaProps{ + Default: 0, + Type: []string{"integer"}, + Format: "int32", + }, + }, + }, + Required: []string{"value", "scale"}, + }, + }, + } +} + +func schema_pkg_apis_meta_v1_APIGroup(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "APIGroup contains the name, the supported versions, and the preferred version of a group.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", + }, + }, + "name": { + SchemaProps: spec.SchemaProps{ + Description: "name is the name of the group.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "versions": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "versions are the versions supported in this group.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.GroupVersionForDiscovery"), + }, + }, + }, + }, + }, + "preferredVersion": { + SchemaProps: spec.SchemaProps{ + Description: "preferredVersion is the version preferred by the API server, which probably is the storage version.", + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.GroupVersionForDiscovery"), + }, + }, + "serverAddressByClientCIDRs": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "a map of client CIDR to server address that is serving this group. This is to help clients reach servers in the most network-efficient way possible. Clients can use the appropriate server address as per the CIDR that they match. In case of multiple matches, clients should use the longest matching CIDR. The server returns only those CIDRs that it thinks that the client can match. For example: the master will return an internal IP CIDR only, if the client reaches the server using an internal IP. Server looks at X-Forwarded-For header or X-Real-Ip header or request.RemoteAddr (in that order) to get the client IP.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ServerAddressByClientCIDR"), + }, + }, + }, + }, + }, + }, + Required: []string{"name", "versions"}, + }, + }, + Dependencies: []string{ + "k8s.io/apimachinery/pkg/apis/meta/v1.GroupVersionForDiscovery", "k8s.io/apimachinery/pkg/apis/meta/v1.ServerAddressByClientCIDR"}, + } +} + +func schema_pkg_apis_meta_v1_APIGroupList(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "APIGroupList is a list of APIGroup, to allow clients to discover the API at /apis.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", + }, + }, + "groups": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "groups is a list of APIGroup.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.APIGroup"), + }, + }, + }, + }, + }, + }, + Required: []string{"groups"}, + }, + }, + Dependencies: []string{ + "k8s.io/apimachinery/pkg/apis/meta/v1.APIGroup"}, + } +} + +func schema_pkg_apis_meta_v1_APIResource(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "APIResource specifies the name of a resource and whether it is namespaced.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "name": { + SchemaProps: spec.SchemaProps{ + Description: "name is the plural name of the resource.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "singularName": { + SchemaProps: spec.SchemaProps{ + Description: "singularName is the singular name of the resource. This allows clients to handle plural and singular opaquely. The singularName is more correct for reporting status on a single item and both singular and plural are allowed from the kubectl CLI interface.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "namespaced": { + SchemaProps: spec.SchemaProps{ + Description: "namespaced indicates if a resource is namespaced or not.", + Default: false, + Type: []string{"boolean"}, + Format: "", + }, + }, + "group": { + SchemaProps: spec.SchemaProps{ + Description: "group is the preferred group of the resource. Empty implies the group of the containing resource list. For subresources, this may have a different value, for example: Scale\".", + Type: []string{"string"}, + Format: "", + }, + }, + "version": { + SchemaProps: spec.SchemaProps{ + Description: "version is the preferred version of the resource. Empty implies the version of the containing resource list For subresources, this may have a different value, for example: v1 (while inside a v1beta1 version of the core resource's group)\".", + Type: []string{"string"}, + Format: "", + }, + }, + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "kind is the kind for the resource (e.g. 'Foo' is the kind for a resource 'foo')", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "verbs": { + SchemaProps: spec.SchemaProps{ + Description: "verbs is a list of supported kube verbs (this includes get, list, watch, create, update, patch, delete, deletecollection, and proxy)", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + "shortNames": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "shortNames is a list of suggested short names of the resource.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + "categories": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "categories is a list of the grouped resources this resource belongs to (e.g. 'all')", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + "storageVersionHash": { + SchemaProps: spec.SchemaProps{ + Description: "The hash value of the storage version, the version this resource is converted to when written to the data store. Value must be treated as opaque by clients. Only equality comparison on the value is valid. This is an alpha feature and may change or be removed in the future. The field is populated by the apiserver only if the StorageVersionHash feature gate is enabled. This field will remain optional even if it graduates.", + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"name", "singularName", "namespaced", "kind", "verbs"}, + }, + }, + } +} + +func schema_pkg_apis_meta_v1_APIResourceList(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "APIResourceList is a list of APIResource, it is used to expose the name of the resources supported in a specific group and version, and if the resource is namespaced.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", + }, + }, + "groupVersion": { + SchemaProps: spec.SchemaProps{ + Description: "groupVersion is the group and version this APIResourceList is for.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "resources": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "resources contains the name of the resources and if they are namespaced.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.APIResource"), + }, + }, + }, + }, + }, + }, + Required: []string{"groupVersion", "resources"}, + }, + }, + Dependencies: []string{ + "k8s.io/apimachinery/pkg/apis/meta/v1.APIResource"}, + } +} + +func schema_pkg_apis_meta_v1_APIVersions(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "APIVersions lists the versions that are available, to allow clients to discover the API at /api, which is the root path of the legacy v1 API.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", + }, + }, + "versions": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "versions are the api versions that are available.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + "serverAddressByClientCIDRs": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "a map of client CIDR to server address that is serving this group. This is to help clients reach servers in the most network-efficient way possible. Clients can use the appropriate server address as per the CIDR that they match. In case of multiple matches, clients should use the longest matching CIDR. The server returns only those CIDRs that it thinks that the client can match. For example: the master will return an internal IP CIDR only, if the client reaches the server using an internal IP. Server looks at X-Forwarded-For header or X-Real-Ip header or request.RemoteAddr (in that order) to get the client IP.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ServerAddressByClientCIDR"), + }, + }, + }, + }, + }, + }, + Required: []string{"versions", "serverAddressByClientCIDRs"}, + }, + }, + Dependencies: []string{ + "k8s.io/apimachinery/pkg/apis/meta/v1.ServerAddressByClientCIDR"}, + } +} + +func schema_pkg_apis_meta_v1_ApplyOptions(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "ApplyOptions may be provided when applying an API object. FieldManager is required for apply requests. ApplyOptions is equivalent to PatchOptions. It is provided as a convenience with documentation that speaks specifically to how the options fields relate to apply.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", + }, + }, + "dryRun": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + "force": { + SchemaProps: spec.SchemaProps{ + Description: "Force is going to \"force\" Apply requests. It means user will re-acquire conflicting fields owned by other people.", + Default: false, + Type: []string{"boolean"}, + Format: "", + }, + }, + "fieldManager": { + SchemaProps: spec.SchemaProps{ + Description: "fieldManager is a name associated with the actor or entity that is making these changes. The value must be less than or 128 characters long, and only contain printable characters, as defined by https://golang.org/pkg/unicode/#IsPrint. This field is required.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"force", "fieldManager"}, + }, + }, + } +} + +func schema_pkg_apis_meta_v1_Condition(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "Condition contains details for one aspect of the current state of this API Resource.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "type": { + SchemaProps: spec.SchemaProps{ + Description: "type of condition in CamelCase or in foo.example.com/CamelCase.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "status": { + SchemaProps: spec.SchemaProps{ + Description: "status of the condition, one of True, False, Unknown.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "observedGeneration": { + SchemaProps: spec.SchemaProps{ + Description: "observedGeneration represents the .metadata.generation that the condition was set based upon. For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date with respect to the current state of the instance.", + Type: []string{"integer"}, + Format: "int64", + }, + }, + "lastTransitionTime": { + SchemaProps: spec.SchemaProps{ + Description: "lastTransitionTime is the last time the condition transitioned from one status to another. This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable.", + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.Time"), + }, + }, + "reason": { + SchemaProps: spec.SchemaProps{ + Description: "reason contains a programmatic identifier indicating the reason for the condition's last transition. Producers of specific condition types may define expected values and meanings for this field, and whether the values are considered a guaranteed API. The value should be a CamelCase string. This field may not be empty.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "message": { + SchemaProps: spec.SchemaProps{ + Description: "message is a human readable message indicating details about the transition. This may be an empty string.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"type", "status", "lastTransitionTime", "reason", "message"}, + }, + }, + Dependencies: []string{ + "k8s.io/apimachinery/pkg/apis/meta/v1.Time"}, + } +} + +func schema_pkg_apis_meta_v1_CreateOptions(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "CreateOptions may be provided when creating an API object.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", + }, + }, + "dryRun": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + "fieldManager": { + SchemaProps: spec.SchemaProps{ + Description: "fieldManager is a name associated with the actor or entity that is making these changes. The value must be less than or 128 characters long, and only contain printable characters, as defined by https://golang.org/pkg/unicode/#IsPrint.", + Type: []string{"string"}, + Format: "", + }, + }, + "fieldValidation": { + SchemaProps: spec.SchemaProps{ + Description: "fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default in v1.23+ - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered.", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + } +} + +func schema_pkg_apis_meta_v1_DeleteOptions(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "DeleteOptions may be provided when deleting an API object.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", + }, + }, + "gracePeriodSeconds": { + SchemaProps: spec.SchemaProps{ + Description: "The duration in seconds before the object should be deleted. Value must be non-negative integer. The value zero indicates delete immediately. If this value is nil, the default grace period for the specified type will be used. Defaults to a per object value if not specified. zero means delete immediately.", + Type: []string{"integer"}, + Format: "int64", + }, + }, + "preconditions": { + SchemaProps: spec.SchemaProps{ + Description: "Must be fulfilled before a deletion is carried out. If not possible, a 409 Conflict status will be returned.", + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.Preconditions"), + }, + }, + "orphanDependents": { + SchemaProps: spec.SchemaProps{ + Description: "Deprecated: please use the PropagationPolicy, this field will be deprecated in 1.7. Should the dependent objects be orphaned. If true/false, the \"orphan\" finalizer will be added to/removed from the object's finalizers list. Either this field or PropagationPolicy may be set, but not both.", + Type: []string{"boolean"}, + Format: "", + }, + }, + "propagationPolicy": { + SchemaProps: spec.SchemaProps{ + Description: "Whether and how garbage collection will be performed. Either this field or OrphanDependents may be set, but not both. The default policy is decided by the existing finalizer set in the metadata.finalizers and the resource-specific default policy. Acceptable values are: 'Orphan' - orphan the dependents; 'Background' - allow the garbage collector to delete the dependents in the background; 'Foreground' - a cascading policy that deletes all dependents in the foreground.", + Type: []string{"string"}, + Format: "", + }, + }, + "dryRun": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + "ignoreStoreReadErrorWithClusterBreakingPotential": { + SchemaProps: spec.SchemaProps{ + Description: "if set to true, it will trigger an unsafe deletion of the resource in case the normal deletion flow fails with a corrupt object error. A resource is considered corrupt if it can not be retrieved from the underlying storage successfully because of a) its data can not be transformed e.g. decryption failure, or b) it fails to decode into an object. NOTE: unsafe deletion ignores finalizer constraints, skips precondition checks, and removes the object from the storage. WARNING: This may potentially break the cluster if the workload associated with the resource being unsafe-deleted relies on normal deletion flow. Use only if you REALLY know what you are doing. The default value is false, and the user must opt in to enable it", + Type: []string{"boolean"}, + Format: "", + }, + }, + }, + }, + }, + Dependencies: []string{ + "k8s.io/apimachinery/pkg/apis/meta/v1.Preconditions"}, + } +} + +func schema_pkg_apis_meta_v1_Duration(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "Duration is a wrapper around time.Duration which supports correct marshaling to YAML and JSON. In particular, it marshals into strings, which can be used as map keys in json.", + Type: metav1.Duration{}.OpenAPISchemaType(), + Format: metav1.Duration{}.OpenAPISchemaFormat(), + }, + }, + } +} + +func schema_pkg_apis_meta_v1_FieldSelectorRequirement(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "FieldSelectorRequirement is a selector that contains values, a key, and an operator that relates the key and values.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "key": { + SchemaProps: spec.SchemaProps{ + Description: "key is the field selector key that the requirement applies to.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "operator": { + SchemaProps: spec.SchemaProps{ + Description: "operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists, DoesNotExist. The list of operators may grow in the future.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "values": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + }, + Required: []string{"key", "operator"}, + }, + }, + } +} + +func schema_pkg_apis_meta_v1_FieldsV1(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "FieldsV1 stores a set of fields in a data structure like a Trie, in JSON format.\n\nEach key is either a '.' representing the field itself, and will always map to an empty set, or a string representing a sub-field or item. The string will follow one of these four formats: 'f:', where is the name of a field in a struct, or key in a map 'v:', where is the exact json formatted value of a list item 'i:', where is position of a item in a list 'k:', where is a map of a list item's key fields to their unique values If a key maps to an empty Fields value, the field that key represents is part of the set.\n\nThe exact format is defined in sigs.k8s.io/structured-merge-diff", + Type: []string{"object"}, + }, + }, + } +} + +func schema_pkg_apis_meta_v1_GetOptions(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "GetOptions is the standard query options to the standard REST get call.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", + }, + }, + "resourceVersion": { + SchemaProps: spec.SchemaProps{ + Description: "resourceVersion sets a constraint on what resource versions a request may be served from. See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for details.\n\nDefaults to unset", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + } +} + +func schema_pkg_apis_meta_v1_GroupKind(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "GroupKind specifies a Group and a Kind, but does not force a version. This is useful for identifying concepts during lookup stages without having partially valid types", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "group": { + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "kind": { + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"group", "kind"}, + }, + }, + } +} + +func schema_pkg_apis_meta_v1_GroupResource(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "GroupResource specifies a Group and a Resource, but does not force a version. This is useful for identifying concepts during lookup stages without having partially valid types", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "group": { + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "resource": { + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"group", "resource"}, + }, + }, + } +} + +func schema_pkg_apis_meta_v1_GroupVersion(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "GroupVersion contains the \"group\" and the \"version\", which uniquely identifies the API.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "group": { + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "version": { + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"group", "version"}, + }, + }, + } +} + +func schema_pkg_apis_meta_v1_GroupVersionForDiscovery(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "GroupVersion contains the \"group/version\" and \"version\" string of a version. It is made a struct to keep extensibility.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "groupVersion": { + SchemaProps: spec.SchemaProps{ + Description: "groupVersion specifies the API group and version in the form \"group/version\"", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "version": { + SchemaProps: spec.SchemaProps{ + Description: "version specifies the version in the form of \"version\". This is to save the clients the trouble of splitting the GroupVersion.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"groupVersion", "version"}, + }, + }, + } +} + +func schema_pkg_apis_meta_v1_GroupVersionKind(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "GroupVersionKind unambiguously identifies a kind. It doesn't anonymously include GroupVersion to avoid automatic coercion. It doesn't use a GroupVersion to avoid custom marshalling", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "group": { + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "version": { + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "kind": { + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"group", "version", "kind"}, + }, + }, + } +} + +func schema_pkg_apis_meta_v1_GroupVersionResource(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "GroupVersionResource unambiguously identifies a resource. It doesn't anonymously include GroupVersion to avoid automatic coercion. It doesn't use a GroupVersion to avoid custom marshalling", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "group": { + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "version": { + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "resource": { + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"group", "version", "resource"}, + }, + }, + } +} + +func schema_pkg_apis_meta_v1_InternalEvent(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "InternalEvent makes watch.Event versioned", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "Type": { + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "Object": { + SchemaProps: spec.SchemaProps{ + Description: "Object is:\n * If Type is Added or Modified: the new state of the object.\n * If Type is Deleted: the state of the object immediately before deletion.\n * If Type is Bookmark: the object (instance of a type being watched) where\n only ResourceVersion field is set. On successful restart of watch from a\n bookmark resourceVersion, client is guaranteed to not get repeat event\n nor miss any events.\n * If Type is Error: *api.Status is recommended; other types may make sense\n depending on context.", + }, + }, + }, + Required: []string{"Type", "Object"}, + }, + }, + } +} + +func schema_pkg_apis_meta_v1_LabelSelector(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "A label selector is a label query over a set of resources. The result of matchLabels and matchExpressions are ANDed. An empty label selector matches all objects. A null label selector matches no objects.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "matchLabels": { + SchemaProps: spec.SchemaProps{ + Description: "matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is \"key\", the operator is \"In\", and the values array contains only \"value\". The requirements are ANDed.", + Type: []string{"object"}, + AdditionalProperties: &spec.SchemaOrBool{ + Allows: true, + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + "matchExpressions": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "matchExpressions is a list of label selector requirements. The requirements are ANDed.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.LabelSelectorRequirement"), + }, + }, + }, + }, + }, + }, + }, + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-map-type": "atomic", + }, + }, + }, + Dependencies: []string{ + "k8s.io/apimachinery/pkg/apis/meta/v1.LabelSelectorRequirement"}, + } +} + +func schema_pkg_apis_meta_v1_LabelSelectorRequirement(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "key": { + SchemaProps: spec.SchemaProps{ + Description: "key is the label key that the selector applies to.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "operator": { + SchemaProps: spec.SchemaProps{ + Description: "operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "values": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + }, + Required: []string{"key", "operator"}, + }, + }, + } +} + +func schema_pkg_apis_meta_v1_List(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "List holds a list of objects, which may not be known by the server.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", + }, + }, + "metadata": { + SchemaProps: spec.SchemaProps{ + Description: "Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"), + }, + }, + "items": { + SchemaProps: spec.SchemaProps{ + Description: "List of objects", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Ref: ref("k8s.io/apimachinery/pkg/runtime.RawExtension"), + }, + }, + }, + }, + }, + }, + Required: []string{"items"}, + }, + }, + Dependencies: []string{ + "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta", "k8s.io/apimachinery/pkg/runtime.RawExtension"}, + } +} + +func schema_pkg_apis_meta_v1_ListMeta(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "ListMeta describes metadata that synthetic resources must have, including lists and various status objects. A resource may have only one of {ObjectMeta, ListMeta}.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "selfLink": { + SchemaProps: spec.SchemaProps{ + Description: "Deprecated: selfLink is a legacy read-only field that is no longer populated by the system.", + Type: []string{"string"}, + Format: "", + }, + }, + "resourceVersion": { + SchemaProps: spec.SchemaProps{ + Description: "String that identifies the server's internal version of this object that can be used by clients to determine when objects have changed. Value must be treated as opaque by clients and passed unmodified back to the server. Populated by the system. Read-only. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency", + Type: []string{"string"}, + Format: "", + }, + }, + "continue": { + SchemaProps: spec.SchemaProps{ + Description: "continue may be set if the user set a limit on the number of items returned, and indicates that the server has more data available. The value is opaque and may be used to issue another request to the endpoint that served this list to retrieve the next set of available objects. Continuing a consistent list may not be possible if the server configuration has changed or more than a few minutes have passed. The resourceVersion field returned when using this continue value will be identical to the value in the first response, unless you have received this token from an error message.", + Type: []string{"string"}, + Format: "", + }, + }, + "remainingItemCount": { + SchemaProps: spec.SchemaProps{ + Description: "remainingItemCount is the number of subsequent items in the list which are not included in this list response. If the list request contained label or field selectors, then the number of remaining items is unknown and the field will be left unset and omitted during serialization. If the list is complete (either because it is not chunking or because this is the last chunk), then there are no more remaining items and this field will be left unset and omitted during serialization. Servers older than v1.15 do not set this field. The intended use of the remainingItemCount is *estimating* the size of a collection. Clients should not rely on the remainingItemCount to be set or to be exact.", + Type: []string{"integer"}, + Format: "int64", + }, + }, + }, + }, + }, + } +} + +func schema_pkg_apis_meta_v1_ListOptions(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "ListOptions is the query options to a standard REST list call.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", + }, + }, + "labelSelector": { + SchemaProps: spec.SchemaProps{ + Description: "A selector to restrict the list of returned objects by their labels. Defaults to everything.", + Type: []string{"string"}, + Format: "", + }, + }, + "fieldSelector": { + SchemaProps: spec.SchemaProps{ + Description: "A selector to restrict the list of returned objects by their fields. Defaults to everything.", + Type: []string{"string"}, + Format: "", + }, + }, + "watch": { + SchemaProps: spec.SchemaProps{ + Description: "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", + Type: []string{"boolean"}, + Format: "", + }, + }, + "allowWatchBookmarks": { + SchemaProps: spec.SchemaProps{ + Description: "allowWatchBookmarks requests watch events with type \"BOOKMARK\". Servers that do not implement bookmarks may ignore this flag and bookmarks are sent at the server's discretion. Clients should not assume bookmarks are returned at any specific interval, nor may they assume the server will send any BOOKMARK event during a session. If this is not a watch, this field is ignored.", + Type: []string{"boolean"}, + Format: "", + }, + }, + "resourceVersion": { + SchemaProps: spec.SchemaProps{ + Description: "resourceVersion sets a constraint on what resource versions a request may be served from. See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for details.\n\nDefaults to unset", + Type: []string{"string"}, + Format: "", + }, + }, + "resourceVersionMatch": { + SchemaProps: spec.SchemaProps{ + Description: "resourceVersionMatch determines how resourceVersion is applied to list calls. It is highly recommended that resourceVersionMatch be set for list calls where resourceVersion is set See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for details.\n\nDefaults to unset", + Type: []string{"string"}, + Format: "", + }, + }, + "timeoutSeconds": { + SchemaProps: spec.SchemaProps{ + Description: "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", + Type: []string{"integer"}, + Format: "int64", + }, + }, + "limit": { + SchemaProps: spec.SchemaProps{ + Description: "limit is a maximum number of responses to return for a list call. If more items exist, the server will set the `continue` field on the list metadata to a value that can be used with the same initial query to retrieve the next set of results. Setting a limit may return fewer than the requested amount of items (up to zero items) in the event all requested objects are filtered out and clients should only use the presence of the continue field to determine whether more results are available. Servers may choose not to support the limit argument and will return all of the available results. If limit is specified and the continue field is empty, clients may assume that no more results are available. This field is not supported if watch is true.\n\nThe server guarantees that the objects returned when using continue will be identical to issuing a single list call without a limit - that is, no objects created, modified, or deleted after the first request is issued will be included in any subsequent continued requests. This is sometimes referred to as a consistent snapshot, and ensures that a client that is using limit to receive smaller chunks of a very large result can ensure they see all possible objects. If objects are updated during a chunked list the version of the object that was present at the time the first list result was calculated is returned.", + Type: []string{"integer"}, + Format: "int64", + }, + }, + "continue": { + SchemaProps: spec.SchemaProps{ + Description: "The continue option should be set when retrieving more results from the server. Since this value is server defined, clients may only use the continue value from a previous query result with identical query parameters (except for the value of continue) and the server may reject a continue value it does not recognize. If the specified continue value is no longer valid whether due to expiration (generally five to fifteen minutes) or a configuration change on the server, the server will respond with a 410 ResourceExpired error together with a continue token. If the client needs a consistent list, it must restart their list without the continue field. Otherwise, the client may send another list request with the token received with the 410 error, the server will respond with a list starting from the next key, but from the latest snapshot, which is inconsistent from the previous list results - objects that are created, modified, or deleted after the first list request will be included in the response, as long as their keys are after the \"next key\".\n\nThis field is not supported when watch is true. Clients may start a watch from the last resourceVersion value returned by the server and not miss any modifications.", + Type: []string{"string"}, + Format: "", + }, + }, + "sendInitialEvents": { + SchemaProps: spec.SchemaProps{ + Description: "`sendInitialEvents=true` may be set together with `watch=true`. In that case, the watch stream will begin with synthetic events to produce the current state of objects in the collection. Once all such events have been sent, a synthetic \"Bookmark\" event will be sent. The bookmark will report the ResourceVersion (RV) corresponding to the set of objects, and be marked with `\"k8s.io/initial-events-end\": \"true\"` annotation. Afterwards, the watch stream will proceed as usual, sending watch events corresponding to changes (subsequent to the RV) to objects watched.\n\nWhen `sendInitialEvents` option is set, we require `resourceVersionMatch` option to also be set. The semantic of the watch request is as following: - `resourceVersionMatch` = NotOlderThan\n is interpreted as \"data at least as new as the provided `resourceVersion`\"\n and the bookmark event is send when the state is synced\n to a `resourceVersion` at least as fresh as the one provided by the ListOptions.\n If `resourceVersion` is unset, this is interpreted as \"consistent read\" and the\n bookmark event is send when the state is synced at least to the moment\n when request started being processed.\n- `resourceVersionMatch` set to any other value or unset\n Invalid error is returned.\n\nDefaults to true if `resourceVersion=\"\"` or `resourceVersion=\"0\"` (for backward compatibility reasons) and to false otherwise.", + Type: []string{"boolean"}, + Format: "", + }, + }, + }, + }, + }, + } +} + +func schema_pkg_apis_meta_v1_ManagedFieldsEntry(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "ManagedFieldsEntry is a workflow-id, a FieldSet and the group version of the resource that the fieldset applies to.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "manager": { + SchemaProps: spec.SchemaProps{ + Description: "Manager is an identifier of the workflow managing these fields.", + Type: []string{"string"}, + Format: "", + }, + }, + "operation": { + SchemaProps: spec.SchemaProps{ + Description: "Operation is the type of operation which lead to this ManagedFieldsEntry being created. The only valid values for this field are 'Apply' and 'Update'.", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the version of this resource that this field set applies to. The format is \"group/version\" just like the top-level APIVersion field. It is necessary to track the version of a field set because it cannot be automatically converted.", + Type: []string{"string"}, + Format: "", + }, + }, + "time": { + SchemaProps: spec.SchemaProps{ + Description: "Time is the timestamp of when the ManagedFields entry was added. The timestamp will also be updated if a field is added, the manager changes any of the owned fields value or removes a field. The timestamp does not update when a field is removed from the entry because another manager took it over.", + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.Time"), + }, + }, + "fieldsType": { + SchemaProps: spec.SchemaProps{ + Description: "FieldsType is the discriminator for the different fields format and version. There is currently only one possible value: \"FieldsV1\"", + Type: []string{"string"}, + Format: "", + }, + }, + "fieldsV1": { + SchemaProps: spec.SchemaProps{ + Description: "FieldsV1 holds the first JSON version format as described in the \"FieldsV1\" type.", + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.FieldsV1"), + }, + }, + "subresource": { + SchemaProps: spec.SchemaProps{ + Description: "Subresource is the name of the subresource used to update that object, or empty string if the object was updated through the main resource. The value of this field is used to distinguish between managers, even if they share the same name. For example, a status update will be distinct from a regular update using the same manager name. Note that the APIVersion field is not related to the Subresource field and it always corresponds to the version of the main resource.", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + Dependencies: []string{ + "k8s.io/apimachinery/pkg/apis/meta/v1.FieldsV1", "k8s.io/apimachinery/pkg/apis/meta/v1.Time"}, + } +} + +func schema_pkg_apis_meta_v1_MicroTime(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "MicroTime is version of Time with microsecond level precision.", + Type: metav1.MicroTime{}.OpenAPISchemaType(), + Format: metav1.MicroTime{}.OpenAPISchemaFormat(), + }, + }, + } +} + +func schema_pkg_apis_meta_v1_ObjectMeta(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "ObjectMeta is metadata that all persisted resources must have, which includes all objects users must create.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "name": { + SchemaProps: spec.SchemaProps{ + Description: "Name must be unique within a namespace. Is required when creating resources, although some resources may allow a client to request the generation of an appropriate name automatically. Name is primarily intended for creation idempotence and configuration definition. Cannot be updated. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names#names", + Type: []string{"string"}, + Format: "", + }, + }, + "generateName": { + SchemaProps: spec.SchemaProps{ + Description: "GenerateName is an optional prefix, used by the server, to generate a unique name ONLY IF the Name field has not been provided. If this field is used, the name returned to the client will be different than the name passed. This value will also be combined with a unique suffix. The provided value has the same validation rules as the Name field, and may be truncated by the length of the suffix required to make the value unique on the server.\n\nIf this field is specified and the generated name exists, the server will return a 409.\n\nApplied only if Name is not specified. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#idempotency", + Type: []string{"string"}, + Format: "", + }, + }, + "namespace": { + SchemaProps: spec.SchemaProps{ + Description: "Namespace defines the space within which each name must be unique. An empty namespace is equivalent to the \"default\" namespace, but \"default\" is the canonical representation. Not all objects are required to be scoped to a namespace - the value of this field for those objects will be empty.\n\nMust be a DNS_LABEL. Cannot be updated. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces", + Type: []string{"string"}, + Format: "", + }, + }, + "selfLink": { + SchemaProps: spec.SchemaProps{ + Description: "Deprecated: selfLink is a legacy read-only field that is no longer populated by the system.", + Type: []string{"string"}, + Format: "", + }, + }, + "uid": { + SchemaProps: spec.SchemaProps{ + Description: "UID is the unique in time and space value for this object. It is typically generated by the server on successful creation of a resource and is not allowed to change on PUT operations.\n\nPopulated by the system. Read-only. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names#uids", + Type: []string{"string"}, + Format: "", + }, + }, + "resourceVersion": { + SchemaProps: spec.SchemaProps{ + Description: "An opaque value that represents the internal version of this object that can be used by clients to determine when objects have changed. May be used for optimistic concurrency, change detection, and the watch operation on a resource or set of resources. Clients must treat these values as opaque and passed unmodified back to the server. They may only be valid for a particular resource or set of resources.\n\nPopulated by the system. Read-only. Value must be treated as opaque by clients and . More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency", + Type: []string{"string"}, + Format: "", + }, + }, + "generation": { + SchemaProps: spec.SchemaProps{ + Description: "A sequence number representing a specific generation of the desired state. Populated by the system. Read-only.", + Type: []string{"integer"}, + Format: "int64", + }, + }, + "creationTimestamp": { + SchemaProps: spec.SchemaProps{ + Description: "CreationTimestamp is a timestamp representing the server time when this object was created. It is not guaranteed to be set in happens-before order across separate operations. Clients may not set this value. It is represented in RFC3339 form and is in UTC.\n\nPopulated by the system. Read-only. Null for lists. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata", + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.Time"), + }, + }, + "deletionTimestamp": { + SchemaProps: spec.SchemaProps{ + Description: "DeletionTimestamp is RFC 3339 date and time at which this resource will be deleted. This field is set by the server when a graceful deletion is requested by the user, and is not directly settable by a client. The resource is expected to be deleted (no longer visible from resource lists, and not reachable by name) after the time in this field, once the finalizers list is empty. As long as the finalizers list contains items, deletion is blocked. Once the deletionTimestamp is set, this value may not be unset or be set further into the future, although it may be shortened or the resource may be deleted prior to this time. For example, a user may request that a pod is deleted in 30 seconds. The Kubelet will react by sending a graceful termination signal to the containers in the pod. After that 30 seconds, the Kubelet will send a hard termination signal (SIGKILL) to the container and after cleanup, remove the pod from the API. In the presence of network partitions, this object may still exist after this timestamp, until an administrator or automated process can determine the resource is fully terminated. If not set, graceful deletion of the object has not been requested.\n\nPopulated by the system when a graceful deletion is requested. Read-only. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata", + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.Time"), + }, + }, + "deletionGracePeriodSeconds": { + SchemaProps: spec.SchemaProps{ + Description: "Number of seconds allowed for this object to gracefully terminate before it will be removed from the system. Only set when deletionTimestamp is also set. May only be shortened. Read-only.", + Type: []string{"integer"}, + Format: "int64", + }, + }, + "labels": { + SchemaProps: spec.SchemaProps{ + Description: "Map of string keys and values that can be used to organize and categorize (scope and select) objects. May match selectors of replication controllers and services. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels", + Type: []string{"object"}, + AdditionalProperties: &spec.SchemaOrBool{ + Allows: true, + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + "annotations": { + SchemaProps: spec.SchemaProps{ + Description: "Annotations is an unstructured key value map stored with a resource that may be set by external tools to store and retrieve arbitrary metadata. They are not queryable and should be preserved when modifying objects. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations", + Type: []string{"object"}, + AdditionalProperties: &spec.SchemaOrBool{ + Allows: true, + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + "ownerReferences": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-map-keys": []interface{}{ + "uid", + }, + "x-kubernetes-list-type": "map", + "x-kubernetes-patch-merge-key": "uid", + "x-kubernetes-patch-strategy": "merge", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "List of objects depended by this object. If ALL objects in the list have been deleted, this object will be garbage collected. If this object is managed by a controller, then an entry in this list will point to this controller, with the controller field set to true. There cannot be more than one managing controller.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.OwnerReference"), + }, + }, + }, + }, + }, + "finalizers": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "set", + "x-kubernetes-patch-strategy": "merge", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "Must be empty before the object is deleted from the registry. Each entry is an identifier for the responsible component that will remove the entry from the list. If the deletionTimestamp of the object is non-nil, entries in this list can only be removed. Finalizers may be processed and removed in any order. Order is NOT enforced because it introduces significant risk of stuck finalizers. finalizers is a shared field, any actor with permission can reorder it. If the finalizer list is processed in order, then this can lead to a situation in which the component responsible for the first finalizer in the list is waiting for a signal (field value, external system, or other) produced by a component responsible for a finalizer later in the list, resulting in a deadlock. Without enforced ordering finalizers are free to order amongst themselves and are not vulnerable to ordering changes in the list.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + "managedFields": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "ManagedFields maps workflow-id and version to the set of fields that are managed by that workflow. This is mostly for internal housekeeping, and users typically shouldn't need to set or understand this field. A workflow can be the user's name, a controller's name, or the name of a specific apply path like \"ci-cd\". The set of fields is always in the version that the workflow used when modifying the object.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ManagedFieldsEntry"), + }, + }, + }, + }, + }, + }, + }, + }, + Dependencies: []string{ + "k8s.io/apimachinery/pkg/apis/meta/v1.ManagedFieldsEntry", "k8s.io/apimachinery/pkg/apis/meta/v1.OwnerReference", "k8s.io/apimachinery/pkg/apis/meta/v1.Time"}, + } +} + +func schema_pkg_apis_meta_v1_OwnerReference(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "OwnerReference contains enough information to let you identify an owning object. An owning object must be in the same namespace as the dependent, or be cluster-scoped, so there is no namespace field.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "API version of the referent.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "name": { + SchemaProps: spec.SchemaProps{ + Description: "Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names#names", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "uid": { + SchemaProps: spec.SchemaProps{ + Description: "UID of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names#uids", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "controller": { + SchemaProps: spec.SchemaProps{ + Description: "If true, this reference points to the managing controller.", + Type: []string{"boolean"}, + Format: "", + }, + }, + "blockOwnerDeletion": { + SchemaProps: spec.SchemaProps{ + Description: "If true, AND if the owner has the \"foregroundDeletion\" finalizer, then the owner cannot be deleted from the key-value store until this reference is removed. See https://kubernetes.io/docs/concepts/architecture/garbage-collection/#foreground-deletion for how the garbage collector interacts with this field and enforces the foreground deletion. Defaults to false. To set this field, a user needs \"delete\" permission of the owner, otherwise 422 (Unprocessable Entity) will be returned.", + Type: []string{"boolean"}, + Format: "", + }, + }, + }, + Required: []string{"apiVersion", "kind", "name", "uid"}, + }, + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-map-type": "atomic", + }, + }, + }, + } +} + +func schema_pkg_apis_meta_v1_PartialObjectMetadata(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "PartialObjectMetadata is a generic representation of any object with ObjectMeta. It allows clients to get access to a particular ObjectMeta schema without knowing the details of the version.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", + }, + }, + "metadata": { + SchemaProps: spec.SchemaProps{ + Description: "Standard object's metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata", + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"), + }, + }, + }, + }, + }, + Dependencies: []string{ + "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"}, + } +} + +func schema_pkg_apis_meta_v1_PartialObjectMetadataList(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "PartialObjectMetadataList contains a list of objects containing only their metadata", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", + }, + }, + "metadata": { + SchemaProps: spec.SchemaProps{ + Description: "Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"), + }, + }, + "items": { + SchemaProps: spec.SchemaProps{ + Description: "items contains each of the included items.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.PartialObjectMetadata"), + }, + }, + }, + }, + }, + }, + Required: []string{"items"}, + }, + }, + Dependencies: []string{ + "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta", "k8s.io/apimachinery/pkg/apis/meta/v1.PartialObjectMetadata"}, + } +} + +func schema_pkg_apis_meta_v1_Patch(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "Patch is provided to give a concrete name and type to the Kubernetes PATCH request body.", + Type: []string{"object"}, + }, + }, + } +} + +func schema_pkg_apis_meta_v1_PatchOptions(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "PatchOptions may be provided when patching an API object. PatchOptions is meant to be a superset of UpdateOptions.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", + }, + }, + "dryRun": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + "force": { + SchemaProps: spec.SchemaProps{ + Description: "Force is going to \"force\" Apply requests. It means user will re-acquire conflicting fields owned by other people. Force flag must be unset for non-apply patch requests.", + Type: []string{"boolean"}, + Format: "", + }, + }, + "fieldManager": { + SchemaProps: spec.SchemaProps{ + Description: "fieldManager is a name associated with the actor or entity that is making these changes. The value must be less than or 128 characters long, and only contain printable characters, as defined by https://golang.org/pkg/unicode/#IsPrint. This field is required for apply requests (application/apply-patch) but optional for non-apply patch types (JsonPatch, MergePatch, StrategicMergePatch).", + Type: []string{"string"}, + Format: "", + }, + }, + "fieldValidation": { + SchemaProps: spec.SchemaProps{ + Description: "fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default in v1.23+ - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered.", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + } +} + +func schema_pkg_apis_meta_v1_Preconditions(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "Preconditions must be fulfilled before an operation (update, delete, etc.) is carried out.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "uid": { + SchemaProps: spec.SchemaProps{ + Description: "Specifies the target UID.", + Type: []string{"string"}, + Format: "", + }, + }, + "resourceVersion": { + SchemaProps: spec.SchemaProps{ + Description: "Specifies the target ResourceVersion", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + } +} + +func schema_pkg_apis_meta_v1_RootPaths(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "RootPaths lists the paths available at root. For example: \"/healthz\", \"/apis\".", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "paths": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "paths are the paths available at root.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + }, + Required: []string{"paths"}, + }, + }, + } +} + +func schema_pkg_apis_meta_v1_ServerAddressByClientCIDR(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "ServerAddressByClientCIDR helps the client to determine the server address that they should use, depending on the clientCIDR that they match.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "clientCIDR": { + SchemaProps: spec.SchemaProps{ + Description: "The CIDR with which clients can match their IP to figure out the server address that they should use.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "serverAddress": { + SchemaProps: spec.SchemaProps{ + Description: "Address of this server, suitable for a client that matches the above CIDR. This can be a hostname, hostname:port, IP or IP:port.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"clientCIDR", "serverAddress"}, + }, + }, + } +} + +func schema_pkg_apis_meta_v1_Status(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "Status is a return value for calls that don't return other objects.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", + }, + }, + "metadata": { + SchemaProps: spec.SchemaProps{ + Description: "Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"), + }, + }, + "status": { + SchemaProps: spec.SchemaProps{ + Description: "Status of the operation. One of: \"Success\" or \"Failure\". More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status", + Type: []string{"string"}, + Format: "", + }, + }, + "message": { + SchemaProps: spec.SchemaProps{ + Description: "A human-readable description of the status of this operation.", + Type: []string{"string"}, + Format: "", + }, + }, + "reason": { + SchemaProps: spec.SchemaProps{ + Description: "A machine-readable description of why this operation is in the \"Failure\" status. If this value is empty there is no information available. A Reason clarifies an HTTP status code but does not override it.", + Type: []string{"string"}, + Format: "", + }, + }, + "details": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "Extended data associated with the reason. Each reason may define its own extended details. This field is optional and the data returned is not guaranteed to conform to any schema except that defined by the reason type.", + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.StatusDetails"), + }, + }, + "code": { + SchemaProps: spec.SchemaProps{ + Description: "Suggested HTTP return code for this status, 0 if not set.", + Type: []string{"integer"}, + Format: "int32", + }, + }, + }, + }, + }, + Dependencies: []string{ + "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta", "k8s.io/apimachinery/pkg/apis/meta/v1.StatusDetails"}, + } +} + +func schema_pkg_apis_meta_v1_StatusCause(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "StatusCause provides more information about an api.Status failure, including cases when multiple errors are encountered.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "reason": { + SchemaProps: spec.SchemaProps{ + Description: "A machine-readable description of the cause of the error. If this value is empty there is no information available.", + Type: []string{"string"}, + Format: "", + }, + }, + "message": { + SchemaProps: spec.SchemaProps{ + Description: "A human-readable description of the cause of the error. This field may be presented as-is to a reader.", + Type: []string{"string"}, + Format: "", + }, + }, + "field": { + SchemaProps: spec.SchemaProps{ + Description: "The field of the resource that has caused this error, as named by its JSON serialization. May include dot and postfix notation for nested attributes. Arrays are zero-indexed. Fields may appear more than once in an array of causes due to fields having multiple errors. Optional.\n\nExamples:\n \"name\" - the field \"name\" on the current resource\n \"items[0].name\" - the field \"name\" on the first array entry in \"items\"", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + } +} + +func schema_pkg_apis_meta_v1_StatusDetails(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "StatusDetails is a set of additional properties that MAY be set by the server to provide additional information about a response. The Reason field of a Status object defines what attributes will be set. Clients must ignore fields that do not match the defined type of each attribute, and should assume that any attribute may be empty, invalid, or under defined.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "name": { + SchemaProps: spec.SchemaProps{ + Description: "The name attribute of the resource associated with the status StatusReason (when there is a single name which can be described).", + Type: []string{"string"}, + Format: "", + }, + }, + "group": { + SchemaProps: spec.SchemaProps{ + Description: "The group attribute of the resource associated with the status StatusReason.", + Type: []string{"string"}, + Format: "", + }, + }, + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "The kind attribute of the resource associated with the status StatusReason. On some operations may differ from the requested resource Kind. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "uid": { + SchemaProps: spec.SchemaProps{ + Description: "UID of the resource. (when there is a single resource which can be described). More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names#uids", + Type: []string{"string"}, + Format: "", + }, + }, + "causes": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "The Causes array includes more details associated with the StatusReason failure. Not all StatusReasons may provide detailed causes.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.StatusCause"), + }, + }, + }, + }, + }, + "retryAfterSeconds": { + SchemaProps: spec.SchemaProps{ + Description: "If specified, the time in seconds before the operation should be retried. Some errors may indicate the client must take an alternate action - for those errors this field may indicate how long to wait before taking the alternate action.", + Type: []string{"integer"}, + Format: "int32", + }, + }, + }, + }, + }, + Dependencies: []string{ + "k8s.io/apimachinery/pkg/apis/meta/v1.StatusCause"}, + } +} + +func schema_pkg_apis_meta_v1_Table(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "Table is a tabular representation of a set of API resources. The server transforms the object into a set of preferred columns for quickly reviewing the objects.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", + }, + }, + "metadata": { + SchemaProps: spec.SchemaProps{ + Description: "Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"), + }, + }, + "columnDefinitions": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "columnDefinitions describes each column in the returned items array. The number of cells per row will always match the number of column definitions.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.TableColumnDefinition"), + }, + }, + }, + }, + }, + "rows": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "rows is the list of items in the table.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.TableRow"), + }, + }, + }, + }, + }, + }, + Required: []string{"columnDefinitions", "rows"}, + }, + }, + Dependencies: []string{ + "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta", "k8s.io/apimachinery/pkg/apis/meta/v1.TableColumnDefinition", "k8s.io/apimachinery/pkg/apis/meta/v1.TableRow"}, + } +} + +func schema_pkg_apis_meta_v1_TableColumnDefinition(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "TableColumnDefinition contains information about a column returned in the Table.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "name": { + SchemaProps: spec.SchemaProps{ + Description: "name is a human readable name for the column.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "type": { + SchemaProps: spec.SchemaProps{ + Description: "type is an OpenAPI type definition for this column, such as number, integer, string, or array. See https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#data-types for more.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "format": { + SchemaProps: spec.SchemaProps{ + Description: "format is an optional OpenAPI type modifier for this column. A format modifies the type and imposes additional rules, like date or time formatting for a string. The 'name' format is applied to the primary identifier column which has type 'string' to assist in clients identifying column is the resource name. See https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#data-types for more.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "description": { + SchemaProps: spec.SchemaProps{ + Description: "description is a human readable description of this column.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "priority": { + SchemaProps: spec.SchemaProps{ + Description: "priority is an integer defining the relative importance of this column compared to others. Lower numbers are considered higher priority. Columns that may be omitted in limited space scenarios should be given a higher priority.", + Default: 0, + Type: []string{"integer"}, + Format: "int32", + }, + }, + }, + Required: []string{"name", "type", "format", "description", "priority"}, + }, + }, + } +} + +func schema_pkg_apis_meta_v1_TableOptions(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "TableOptions are used when a Table is requested by the caller.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", + }, + }, + "includeObject": { + SchemaProps: spec.SchemaProps{ + Description: "includeObject decides whether to include each object along with its columnar information. Specifying \"None\" will return no object, specifying \"Object\" will return the full object contents, and specifying \"Metadata\" (the default) will return the object's metadata in the PartialObjectMetadata kind in version v1beta1 of the meta.k8s.io API group.", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + } +} + +func schema_pkg_apis_meta_v1_TableRow(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "TableRow is an individual row in a table.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "cells": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "cells will be as wide as the column definitions array and may contain strings, numbers (float64 or int64), booleans, simple maps, lists, or null. See the type field of the column definition for a more detailed description.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + Format: "", + }, + }, + }, + }, + }, + "conditions": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "conditions describe additional status of a row that are relevant for a human user. These conditions apply to the row, not to the object, and will be specific to table output. The only defined condition type is 'Completed', for a row that indicates a resource that has run to completion and can be given less visual priority.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.TableRowCondition"), + }, + }, + }, + }, + }, + "object": { + SchemaProps: spec.SchemaProps{ + Description: "This field contains the requested additional information about each object based on the includeObject policy when requesting the Table. If \"None\", this field is empty, if \"Object\" this will be the default serialization of the object for the current API version, and if \"Metadata\" (the default) will contain the object metadata. Check the returned kind and apiVersion of the object before parsing. The media type of the object will always match the enclosing list - if this as a JSON table, these will be JSON encoded objects.", + Ref: ref("k8s.io/apimachinery/pkg/runtime.RawExtension"), + }, + }, + }, + Required: []string{"cells"}, + }, + }, + Dependencies: []string{ + "k8s.io/apimachinery/pkg/apis/meta/v1.TableRowCondition", "k8s.io/apimachinery/pkg/runtime.RawExtension"}, + } +} + +func schema_pkg_apis_meta_v1_TableRowCondition(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "TableRowCondition allows a row to be marked with additional information.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "type": { + SchemaProps: spec.SchemaProps{ + Description: "Type of row condition. The only defined value is 'Completed' indicating that the object this row represents has reached a completed state and may be given less visual priority than other rows. Clients are not required to honor any conditions but should be consistent where possible about handling the conditions.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "status": { + SchemaProps: spec.SchemaProps{ + Description: "Status of the condition, one of True, False, Unknown.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "reason": { + SchemaProps: spec.SchemaProps{ + Description: "(brief) machine readable reason for the condition's last transition.", + Type: []string{"string"}, + Format: "", + }, + }, + "message": { + SchemaProps: spec.SchemaProps{ + Description: "Human readable message indicating details about last transition.", + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"type", "status"}, + }, + }, + } +} + +func schema_pkg_apis_meta_v1_Time(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "Time is a wrapper around time.Time which supports correct marshaling to YAML and JSON. Wrappers are provided for many of the factory methods that the time package offers.", + Type: metav1.Time{}.OpenAPISchemaType(), + Format: metav1.Time{}.OpenAPISchemaFormat(), + }, + }, + } +} + +func schema_pkg_apis_meta_v1_Timestamp(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "Timestamp is a struct that is equivalent to Time, but intended for protobuf marshalling/unmarshalling. It is generated into a serialization that matches Time. Do not use in Go structs.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "seconds": { + SchemaProps: spec.SchemaProps{ + Description: "Represents seconds of UTC time since Unix epoch 1970-01-01T00:00:00Z. Must be from 0001-01-01T00:00:00Z to 9999-12-31T23:59:59Z inclusive.", + Default: 0, + Type: []string{"integer"}, + Format: "int64", + }, + }, + "nanos": { + SchemaProps: spec.SchemaProps{ + Description: "Non-negative fractions of a second at nanosecond resolution. Negative second values with fractions must still have non-negative nanos values that count forward in time. Must be from 0 to 999,999,999 inclusive. This field may be limited in precision depending on context.", + Default: 0, + Type: []string{"integer"}, + Format: "int32", + }, + }, + }, + Required: []string{"seconds", "nanos"}, + }, + }, + } +} + +func schema_pkg_apis_meta_v1_TypeMeta(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "TypeMeta describes an individual object in an API response or request with strings representing the type of the object and its API schema version. Structures that are versioned or persisted should inline TypeMeta.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + } +} + +func schema_pkg_apis_meta_v1_UpdateOptions(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "UpdateOptions may be provided when updating an API object. All fields in UpdateOptions should also be present in PatchOptions.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", + }, + }, + "dryRun": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + "fieldManager": { + SchemaProps: spec.SchemaProps{ + Description: "fieldManager is a name associated with the actor or entity that is making these changes. The value must be less than or 128 characters long, and only contain printable characters, as defined by https://golang.org/pkg/unicode/#IsPrint.", + Type: []string{"string"}, + Format: "", + }, + }, + "fieldValidation": { + SchemaProps: spec.SchemaProps{ + Description: "fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default in v1.23+ - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered.", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + } +} + +func schema_pkg_apis_meta_v1_WatchEvent(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "Event represents a single event to a watched resource.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "type": { + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "object": { + SchemaProps: spec.SchemaProps{ + Description: "Object is:\n * If Type is Added or Modified: the new state of the object.\n * If Type is Deleted: the state of the object immediately before deletion.\n * If Type is Error: *Status is recommended; other types may make sense\n depending on context.", + Ref: ref("k8s.io/apimachinery/pkg/runtime.RawExtension"), + }, + }, + }, + Required: []string{"type", "object"}, + }, + }, + Dependencies: []string{ + "k8s.io/apimachinery/pkg/runtime.RawExtension"}, + } +} + +func schema_k8sio_apimachinery_pkg_runtime_RawExtension(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "RawExtension is used to hold extensions in external versions.\n\nTo use this, make a field which has RawExtension as its type in your external, versioned struct, and Object in your internal struct. You also need to register your various plugin types.\n\n// Internal package:\n\n\ttype MyAPIObject struct {\n\t\truntime.TypeMeta `json:\",inline\"`\n\t\tMyPlugin runtime.Object `json:\"myPlugin\"`\n\t}\n\n\ttype PluginA struct {\n\t\tAOption string `json:\"aOption\"`\n\t}\n\n// External package:\n\n\ttype MyAPIObject struct {\n\t\truntime.TypeMeta `json:\",inline\"`\n\t\tMyPlugin runtime.RawExtension `json:\"myPlugin\"`\n\t}\n\n\ttype PluginA struct {\n\t\tAOption string `json:\"aOption\"`\n\t}\n\n// On the wire, the JSON will look something like this:\n\n\t{\n\t\t\"kind\":\"MyAPIObject\",\n\t\t\"apiVersion\":\"v1\",\n\t\t\"myPlugin\": {\n\t\t\t\"kind\":\"PluginA\",\n\t\t\t\"aOption\":\"foo\",\n\t\t},\n\t}\n\nSo what happens? Decode first uses json or yaml to unmarshal the serialized data into your external MyAPIObject. That causes the raw JSON to be stored, but not unpacked. The next step is to copy (using pkg/conversion) into the internal struct. The runtime package's DefaultScheme has conversion functions installed which will unpack the JSON stored in RawExtension, turning it into the correct object type, and storing it in the Object. (TODO: In the case where the object is of an unknown type, a runtime.Unknown object will be created and stored.)", + Type: []string{"object"}, + }, + }, + } +} + +func schema_k8sio_apimachinery_pkg_runtime_TypeMeta(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "TypeMeta is shared by all top level objects. The proper way to use it is to inline it in your type, like this:\n\n\ttype MyAwesomeAPIObject struct {\n\t runtime.TypeMeta `json:\",inline\"`\n\t ... // other fields\n\t}\n\nfunc (obj *MyAwesomeAPIObject) SetGroupVersionKind(gvk *metav1.GroupVersionKind) { metav1.UpdateTypeMeta(obj,gvk) }; GroupVersionKind() *GroupVersionKind\n\nTypeMeta is provided here for convenience. You may use it directly from this package or define your own with the same fields.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Type: []string{"string"}, + Format: "", + }, + }, + "kind": { + SchemaProps: spec.SchemaProps{ + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + } +} + +func schema_k8sio_apimachinery_pkg_runtime_Unknown(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "Unknown allows api objects with unknown types to be passed-through. This can be used to deal with the API objects from a plug-in. Unknown objects still have functioning TypeMeta features-- kind, version, etc. metadata and field mutatation.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Type: []string{"string"}, + Format: "", + }, + }, + "kind": { + SchemaProps: spec.SchemaProps{ + Type: []string{"string"}, + Format: "", + }, + }, + "ContentEncoding": { + SchemaProps: spec.SchemaProps{ + Description: "ContentEncoding is encoding used to encode 'Raw' data. Unspecified means no encoding.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "ContentType": { + SchemaProps: spec.SchemaProps{ + Description: "ContentType is serialization method used to serialize 'Raw'. Unspecified means ContentTypeJSON.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"ContentEncoding", "ContentType"}, + }, + }, + } +} + +func schema_apimachinery_pkg_util_intstr_IntOrString(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.EmbedOpenAPIDefinitionIntoV2Extension(common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "IntOrString is a type that can hold an int32 or a string. When used in JSON or YAML marshalling and unmarshalling, it produces or consumes the inner type. This allows you to have, for example, a JSON field that can accept a name or number.", + OneOf: common.GenerateOpenAPIV3OneOfSchema(intstr.IntOrString{}.OpenAPIV3OneOfTypes()), + Format: intstr.IntOrString{}.OpenAPISchemaFormat(), + }, + }, + }, common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "IntOrString is a type that can hold an int32 or a string. When used in JSON or YAML marshalling and unmarshalling, it produces or consumes the inner type. This allows you to have, for example, a JSON field that can accept a name or number.", + Type: intstr.IntOrString{}.OpenAPISchemaType(), + Format: intstr.IntOrString{}.OpenAPISchemaFormat(), + }, + }, + }) +} + +func schema_k8sio_apimachinery_pkg_version_Info(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "Info contains versioning information. how we'll want to distribute that information.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "major": { + SchemaProps: spec.SchemaProps{ + Description: "Major is the major version of the binary version", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "minor": { + SchemaProps: spec.SchemaProps{ + Description: "Minor is the minor version of the binary version", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "emulationMajor": { + SchemaProps: spec.SchemaProps{ + Description: "EmulationMajor is the major version of the emulation version", + Type: []string{"string"}, + Format: "", + }, + }, + "emulationMinor": { + SchemaProps: spec.SchemaProps{ + Description: "EmulationMinor is the minor version of the emulation version", + Type: []string{"string"}, + Format: "", + }, + }, + "minCompatibilityMajor": { + SchemaProps: spec.SchemaProps{ + Description: "MinCompatibilityMajor is the major version of the minimum compatibility version", + Type: []string{"string"}, + Format: "", + }, + }, + "minCompatibilityMinor": { + SchemaProps: spec.SchemaProps{ + Description: "MinCompatibilityMinor is the minor version of the minimum compatibility version", + Type: []string{"string"}, + Format: "", + }, + }, + "gitVersion": { + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "gitCommit": { + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "gitTreeState": { + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "buildDate": { + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "goVersion": { + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "compiler": { + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "platform": { + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"major", "minor", "gitVersion", "gitCommit", "gitTreeState", "buildDate", "goVersion", "compiler", "platform"}, + }, + }, + } +} + +func schema_sigsk8sio_gateway_api_apis_v1_AllowedListeners(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "AllowedListeners defines which ListenerSets can be attached to this Gateway.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "namespaces": { + SchemaProps: spec.SchemaProps{ + Description: "Namespaces defines which namespaces ListenerSets can be attached to this Gateway. While this feature is experimental, the default value is to allow no ListenerSets.", + Ref: ref("sigs.k8s.io/gateway-api/apis/v1.ListenerNamespaces"), + }, + }, + }, + }, + }, + Dependencies: []string{ + "sigs.k8s.io/gateway-api/apis/v1.ListenerNamespaces"}, + } +} + +func schema_sigsk8sio_gateway_api_apis_v1_AllowedRoutes(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "AllowedRoutes defines which Routes may be attached to this Listener.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "namespaces": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "Namespaces indicates namespaces from which Routes may be attached to this Listener. This is restricted to the namespace of this Gateway by default.\n\nSupport: Core", + Ref: ref("sigs.k8s.io/gateway-api/apis/v1.RouteNamespaces"), + }, + }, + "kinds": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "Kinds specifies the groups and kinds of Routes that are allowed to bind to this Gateway Listener. When unspecified or empty, the kinds of Routes selected are determined using the Listener protocol.\n\nA RouteGroupKind MUST correspond to kinds of Routes that are compatible with the application protocol specified in the Listener's Protocol field. If an implementation does not support or recognize this resource type, it MUST set the \"ResolvedRefs\" condition to False for this Listener with the \"InvalidRouteKinds\" reason.\n\nSupport: Core", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("sigs.k8s.io/gateway-api/apis/v1.RouteGroupKind"), + }, + }, + }, + }, + }, + }, + }, + }, + Dependencies: []string{ + "sigs.k8s.io/gateway-api/apis/v1.RouteGroupKind", "sigs.k8s.io/gateway-api/apis/v1.RouteNamespaces"}, + } +} + +func schema_sigsk8sio_gateway_api_apis_v1_BackendObjectReference(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "BackendObjectReference defines how an ObjectReference that is specific to BackendRef. It includes a few additional fields and features than a regular ObjectReference.\n\nNote that when a namespace different than the local namespace is specified, a ReferenceGrant object is required in the referent namespace to allow that namespace's owner to accept the reference. See the ReferenceGrant documentation for details.\n\nThe API object must be valid in the cluster; the Group and Kind must be registered in the cluster for this reference to be valid.\n\nReferences to objects with invalid Group and Kind are not valid, and must be rejected by the implementation, with appropriate Conditions set on the containing object.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "group": { + SchemaProps: spec.SchemaProps{ + Description: "Group is the group of the referent. For example, \"gateway.networking.k8s.io\". When unspecified or empty string, core API group is inferred.", + Type: []string{"string"}, + Format: "", + }, + }, + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is the Kubernetes resource kind of the referent. For example \"Service\".\n\nDefaults to \"Service\" when not specified.\n\nExternalName services can refer to CNAME DNS records that may live outside of the cluster and as such are difficult to reason about in terms of conformance. They also may not be safe to forward to (see CVE-2021-25740 for more information). Implementations SHOULD NOT support ExternalName Services.\n\nSupport: Core (Services with a type other than ExternalName)\n\nSupport: Implementation-specific (Services with type ExternalName)", + Type: []string{"string"}, + Format: "", + }, + }, + "name": { + SchemaProps: spec.SchemaProps{ + Description: "Name is the name of the referent.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "namespace": { + SchemaProps: spec.SchemaProps{ + Description: "Namespace is the namespace of the backend. When unspecified, the local namespace is inferred.\n\nNote that when a namespace different than the local namespace is specified, a ReferenceGrant object is required in the referent namespace to allow that namespace's owner to accept the reference. See the ReferenceGrant documentation for details.\n\nSupport: Core", + Type: []string{"string"}, + Format: "", + }, + }, + "port": { + SchemaProps: spec.SchemaProps{ + Description: "Port specifies the destination port number to use for this resource. Port is required when the referent is a Kubernetes Service. In this case, the port number is the service port number, not the target port. For other resources, destination port might be derived from the referent resource or this field.", + Type: []string{"integer"}, + Format: "int32", + }, + }, + }, + Required: []string{"name"}, + }, + }, + } +} + +func schema_sigsk8sio_gateway_api_apis_v1_BackendRef(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "BackendRef defines how a Route should forward a request to a Kubernetes resource.\n\nNote that when a namespace different than the local namespace is specified, a ReferenceGrant object is required in the referent namespace to allow that namespace's owner to accept the reference. See the ReferenceGrant documentation for details.\n\n\n\nWhen the BackendRef points to a Kubernetes Service, implementations SHOULD honor the appProtocol field if it is set for the target Service Port.\n\nImplementations supporting appProtocol SHOULD recognize the Kubernetes Standard Application Protocols defined in KEP-3726.\n\nIf a Service appProtocol isn't specified, an implementation MAY infer the backend protocol through its own means. Implementations MAY infer the protocol from the Route type referring to the backend Service.\n\nIf a Route is not able to send traffic to the backend using the specified protocol then the backend is considered invalid. Implementations MUST set the \"ResolvedRefs\" condition to \"False\" with the \"UnsupportedProtocol\" reason.\n\n\n\nNote that when the BackendTLSPolicy object is enabled by the implementation, there are some extra rules about validity to consider here. See the fields where this struct is used for more information about the exact behavior.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "group": { + SchemaProps: spec.SchemaProps{ + Description: "Group is the group of the referent. For example, \"gateway.networking.k8s.io\". When unspecified or empty string, core API group is inferred.", + Type: []string{"string"}, + Format: "", + }, + }, + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is the Kubernetes resource kind of the referent. For example \"Service\".\n\nDefaults to \"Service\" when not specified.\n\nExternalName services can refer to CNAME DNS records that may live outside of the cluster and as such are difficult to reason about in terms of conformance. They also may not be safe to forward to (see CVE-2021-25740 for more information). Implementations SHOULD NOT support ExternalName Services.\n\nSupport: Core (Services with a type other than ExternalName)\n\nSupport: Implementation-specific (Services with type ExternalName)", + Type: []string{"string"}, + Format: "", + }, + }, + "name": { + SchemaProps: spec.SchemaProps{ + Description: "Name is the name of the referent.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "namespace": { + SchemaProps: spec.SchemaProps{ + Description: "Namespace is the namespace of the backend. When unspecified, the local namespace is inferred.\n\nNote that when a namespace different than the local namespace is specified, a ReferenceGrant object is required in the referent namespace to allow that namespace's owner to accept the reference. See the ReferenceGrant documentation for details.\n\nSupport: Core", + Type: []string{"string"}, + Format: "", + }, + }, + "port": { + SchemaProps: spec.SchemaProps{ + Description: "Port specifies the destination port number to use for this resource. Port is required when the referent is a Kubernetes Service. In this case, the port number is the service port number, not the target port. For other resources, destination port might be derived from the referent resource or this field.", + Type: []string{"integer"}, + Format: "int32", + }, + }, + "weight": { + SchemaProps: spec.SchemaProps{ + Description: "Weight specifies the proportion of requests forwarded to the referenced backend. This is computed as weight/(sum of all weights in this BackendRefs list). For non-zero values, there may be some epsilon from the exact proportion defined here depending on the precision an implementation supports. Weight is not a percentage and the sum of weights does not need to equal 100.\n\nIf only one backend is specified and it has a weight greater than 0, 100% of the traffic is forwarded to that backend. If weight is set to 0, no traffic should be forwarded for this entry. If unspecified, weight defaults to 1.\n\nSupport for this field varies based on the context where used.", + Type: []string{"integer"}, + Format: "int32", + }, + }, + }, + Required: []string{"name"}, + }, + }, + } +} + +func schema_sigsk8sio_gateway_api_apis_v1_BackendTLSPolicy(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "BackendTLSPolicy provides a way to configure how a Gateway connects to a Backend via TLS.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", + }, + }, + "metadata": { + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"), + }, + }, + "spec": { + SchemaProps: spec.SchemaProps{ + Description: "Spec defines the desired state of BackendTLSPolicy.", + Default: map[string]interface{}{}, + Ref: ref("sigs.k8s.io/gateway-api/apis/v1.BackendTLSPolicySpec"), + }, + }, + "status": { + SchemaProps: spec.SchemaProps{ + Description: "Status defines the current state of BackendTLSPolicy.", + Default: map[string]interface{}{}, + Ref: ref("sigs.k8s.io/gateway-api/apis/v1.PolicyStatus"), + }, + }, + }, + Required: []string{"spec"}, + }, + }, + Dependencies: []string{ + "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta", "sigs.k8s.io/gateway-api/apis/v1.BackendTLSPolicySpec", "sigs.k8s.io/gateway-api/apis/v1.PolicyStatus"}, + } +} + +func schema_sigsk8sio_gateway_api_apis_v1_BackendTLSPolicyList(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "BackendTLSPolicyList contains a list of BackendTLSPolicies", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", + }, + }, + "metadata": { + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"), + }, + }, + "items": { + SchemaProps: spec.SchemaProps{ + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("sigs.k8s.io/gateway-api/apis/v1.BackendTLSPolicy"), + }, + }, + }, + }, + }, + }, + Required: []string{"items"}, + }, + }, + Dependencies: []string{ + "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta", "sigs.k8s.io/gateway-api/apis/v1.BackendTLSPolicy"}, + } +} + +func schema_sigsk8sio_gateway_api_apis_v1_BackendTLSPolicySpec(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "BackendTLSPolicySpec defines the desired state of BackendTLSPolicy.\n\nSupport: Extended", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "targetRefs": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "TargetRefs identifies an API object to apply the policy to. Only Services have Extended support. Implementations MAY support additional objects, with Implementation Specific support. Note that this config applies to the entire referenced resource by default, but this default may change in the future to provide a more granular application of the policy.\n\nTargetRefs must be _distinct_. This means either that:\n\n* They select different targets. If this is the case, then targetRef\n entries are distinct. In terms of fields, this means that the\n multi-part key defined by `group`, `kind`, and `name` must\n be unique across all targetRef entries in the BackendTLSPolicy.\n* They select different sectionNames in the same target.\n\nWhen more than one BackendTLSPolicy selects the same target and sectionName, implementations MUST determine precedence using the following criteria, continuing on ties:\n\n* The older policy by creation timestamp takes precedence. For\n example, a policy with a creation timestamp of \"2021-07-15\n 01:02:03\" MUST be given precedence over a policy with a\n creation timestamp of \"2021-07-15 01:02:04\".\n* The policy appearing first in alphabetical order by {name}.\n For example, a policy named `bar` is given precedence over a\n policy named `baz`.\n\nFor any BackendTLSPolicy that does not take precedence, the implementation MUST ensure the `Accepted` Condition is set to `status: False`, with Reason `Conflicted`.\n\nSupport: Extended for Kubernetes Service\n\nSupport: Implementation-specific for any other resource", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("sigs.k8s.io/gateway-api/apis/v1.LocalPolicyTargetReferenceWithSectionName"), + }, + }, + }, + }, + }, + "validation": { + SchemaProps: spec.SchemaProps{ + Description: "Validation contains backend TLS validation configuration.", + Default: map[string]interface{}{}, + Ref: ref("sigs.k8s.io/gateway-api/apis/v1.BackendTLSPolicyValidation"), + }, + }, + "options": { + SchemaProps: spec.SchemaProps{ + Description: "Options are a list of key/value pairs to enable extended TLS configuration for each implementation. For example, configuring the minimum TLS version or supported cipher suites.\n\nA set of common keys MAY be defined by the API in the future. To avoid any ambiguity, implementation-specific definitions MUST use domain-prefixed names, such as `example.com/my-custom-option`. Un-prefixed names are reserved for key names defined by Gateway API.\n\nSupport: Implementation-specific", + Type: []string{"object"}, + AdditionalProperties: &spec.SchemaOrBool{ + Allows: true, + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + }, + Required: []string{"targetRefs", "validation"}, + }, + }, + Dependencies: []string{ + "sigs.k8s.io/gateway-api/apis/v1.BackendTLSPolicyValidation", "sigs.k8s.io/gateway-api/apis/v1.LocalPolicyTargetReferenceWithSectionName"}, + } +} + +func schema_sigsk8sio_gateway_api_apis_v1_BackendTLSPolicyValidation(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "BackendTLSPolicyValidation contains backend TLS validation configuration.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "caCertificateRefs": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "CACertificateRefs contains one or more references to Kubernetes objects that contain a PEM-encoded TLS CA certificate bundle, which is used to validate a TLS handshake between the Gateway and backend Pod.\n\nIf CACertificateRefs is empty or unspecified, then WellKnownCACertificates must be specified. Only one of CACertificateRefs or WellKnownCACertificates may be specified, not both. If CACertificateRefs is empty or unspecified, the configuration for WellKnownCACertificates MUST be honored instead if supported by the implementation.\n\nA CACertificateRef is invalid if:\n\n* It refers to a resource that cannot be resolved (e.g., the referenced resource\n does not exist) or is misconfigured (e.g., a ConfigMap does not contain a key\n named `ca.crt`). In this case, the Reason must be set to `InvalidCACertificateRef`\n and the Message of the Condition must indicate which reference is invalid and why.\n\n* It refers to an unknown or unsupported kind of resource. In this case, the Reason\n must be set to `InvalidKind` and the Message of the Condition must explain which\n kind of resource is unknown or unsupported.\n\n* It refers to a resource in another namespace. This may change in future\n spec updates.\n\nImplementations MAY choose to perform further validation of the certificate content (e.g., checking expiry or enforcing specific formats). In such cases, an implementation-specific Reason and Message must be set for the invalid reference.\n\nIn all cases, the implementation MUST ensure the `ResolvedRefs` Condition on the BackendTLSPolicy is set to `status: False`, with a Reason and Message that indicate the cause of the error. Connections using an invalid CACertificateRef MUST fail, and the client MUST receive an HTTP 5xx error response. If ALL CACertificateRefs are invalid, the implementation MUST also ensure the `Accepted` Condition on the BackendTLSPolicy is set to `status: False`, with a Reason `NoValidCACertificate`.\n\nA single CACertificateRef to a Kubernetes ConfigMap kind has \"Core\" support. Implementations MAY choose to support attaching multiple certificates to a backend, but this behavior is implementation-specific.\n\nSupport: Core - An optional single reference to a Kubernetes ConfigMap, with the CA certificate in a key named `ca.crt`.\n\nSupport: Implementation-specific - More than one reference, other kinds of resources, or a single reference that includes multiple certificates.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("sigs.k8s.io/gateway-api/apis/v1.LocalObjectReference"), + }, + }, + }, + }, + }, + "wellKnownCACertificates": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "WellKnownCACertificates specifies whether system CA certificates may be used in the TLS handshake between the gateway and backend pod.\n\nIf WellKnownCACertificates is unspecified or empty (\"\"), then CACertificateRefs must be specified with at least one entry for a valid configuration. Only one of CACertificateRefs or WellKnownCACertificates may be specified, not both. If an implementation does not support the WellKnownCACertificates field, or the supplied value is not recognized, the implementation MUST ensure the `Accepted` Condition on the BackendTLSPolicy is set to `status: False`, with a Reason `Invalid`.\n\nSupport: Implementation-specific", + Type: []string{"string"}, + Format: "", + }, + }, + "hostname": { + SchemaProps: spec.SchemaProps{ + Description: "Hostname is used for two purposes in the connection between Gateways and backends:\n\n1. Hostname MUST be used as the SNI to connect to the backend (RFC 6066). 2. Hostname MUST be used for authentication and MUST match the certificate\n served by the matching backend, unless SubjectAltNames is specified.\n3. If SubjectAltNames are specified, Hostname can be used for certificate selection\n but MUST NOT be used for authentication. If you want to use the value\n of the Hostname field for authentication, you MUST add it to the SubjectAltNames list.\n\nSupport: Core", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "subjectAltNames": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "SubjectAltNames contains one or more Subject Alternative Names. When specified the certificate served from the backend MUST have at least one Subject Alternate Name matching one of the specified SubjectAltNames.\n\nSupport: Extended", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("sigs.k8s.io/gateway-api/apis/v1.SubjectAltName"), + }, + }, + }, + }, + }, + }, + Required: []string{"hostname"}, + }, + }, + Dependencies: []string{ + "sigs.k8s.io/gateway-api/apis/v1.LocalObjectReference", "sigs.k8s.io/gateway-api/apis/v1.SubjectAltName"}, + } +} + +func schema_sigsk8sio_gateway_api_apis_v1_CommonRouteSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "CommonRouteSpec defines the common attributes that all Routes MUST include within their spec.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "parentRefs": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "ParentRefs references the resources (usually Gateways) that a Route wants to be attached to. Note that the referenced parent resource needs to allow this for the attachment to be complete. For Gateways, that means the Gateway needs to allow attachment from Routes of this kind and namespace. For Services, that means the Service must either be in the same namespace for a \"producer\" route, or the mesh implementation must support and allow \"consumer\" routes for the referenced Service. ReferenceGrant is not applicable for governing ParentRefs to Services - it is not possible to create a \"producer\" route for a Service in a different namespace from the Route.\n\nThere are two kinds of parent resources with \"Core\" support:\n\n* Gateway (Gateway conformance profile) * Service (Mesh conformance profile, ClusterIP Services only)\n\nThis API may be extended in the future to support additional kinds of parent resources.\n\nParentRefs must be _distinct_. This means either that:\n\n* They select different objects. If this is the case, then parentRef\n entries are distinct. In terms of fields, this means that the\n multi-part key defined by `group`, `kind`, `namespace`, and `name` must\n be unique across all parentRef entries in the Route.\n* They do not select different objects, but for each optional field used,\n each ParentRef that selects the same object must set the same set of\n optional fields to different values. If one ParentRef sets a\n combination of optional fields, all must set the same combination.\n\nSome examples:\n\n* If one ParentRef sets `sectionName`, all ParentRefs referencing the\n same object must also set `sectionName`.\n* If one ParentRef sets `port`, all ParentRefs referencing the same\n object must also set `port`.\n* If one ParentRef sets `sectionName` and `port`, all ParentRefs\n referencing the same object must also set `sectionName` and `port`.\n\nIt is possible to separately reference multiple distinct objects that may be collapsed by an implementation. For example, some implementations may choose to merge compatible Gateway Listeners together. If that is the case, the list of routes attached to those resources should also be merged.\n\nNote that for ParentRefs that cross namespace boundaries, there are specific rules. Cross-namespace references are only valid if they are explicitly allowed by something in the namespace they are referring to. For example, Gateway has the AllowedRoutes field, and ReferenceGrant provides a generic way to enable other kinds of cross-namespace reference.\n\n ParentRefs from a Route to a Service in the same namespace are \"producer\" routes, which apply default routing rules to inbound connections from any namespace to the Service.\n\nParentRefs from a Route to a Service in a different namespace are \"consumer\" routes, and these routing rules are only applied to outbound connections originating from the same namespace as the Route, for which the intended destination of the connections are a Service targeted as a ParentRef of the Route. \n\n ", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("sigs.k8s.io/gateway-api/apis/v1.ParentReference"), + }, + }, + }, + }, + }, + "useDefaultGateways": { + SchemaProps: spec.SchemaProps{ + Description: "UseDefaultGateways indicates the default Gateway scope to use for this Route. If unset (the default) or set to None, the Route will not be attached to any default Gateway; if set, it will be attached to any default Gateway supporting the named scope, subject to the usual rules about which Routes a Gateway is allowed to claim.\n\nThink carefully before using this functionality! The set of default Gateways supporting the requested scope can change over time without any notice to the Route author, and in many situations it will not be appropriate to request a default Gateway for a given Route -- for example, a Route with specific security requirements should almost certainly not use a default Gateway.\n\n", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + Dependencies: []string{ + "sigs.k8s.io/gateway-api/apis/v1.ParentReference"}, + } +} + +func schema_sigsk8sio_gateway_api_apis_v1_CookieConfig(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "CookieConfig defines the configuration for cookie-based session persistence.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "lifetimeType": { + SchemaProps: spec.SchemaProps{ + Description: "LifetimeType specifies whether the cookie has a permanent or session-based lifetime. A permanent cookie persists until its specified expiry time, defined by the Expires or Max-Age cookie attributes, while a session cookie is deleted when the current session ends.\n\nWhen set to \"Permanent\", AbsoluteTimeout indicates the cookie's lifetime via the Expires or Max-Age cookie attributes and is required.\n\nWhen set to \"Session\", AbsoluteTimeout indicates the absolute lifetime of the cookie tracked by the gateway and is optional.\n\nDefaults to \"Session\".\n\nSupport: Core for \"Session\" type\n\nSupport: Extended for \"Permanent\" type", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + } +} + +func schema_sigsk8sio_gateway_api_apis_v1_ForwardBodyConfig(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "ForwardBody configures if requests to the authorization server should include the body of the client request; and if so, how big that body is allowed to be.\n\nIf empty or unset, do not forward the body.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "maxSize": { + SchemaProps: spec.SchemaProps{ + Description: "MaxSize specifies how large in bytes the largest body that will be buffered and sent to the authorization server. If the body size is larger than `maxSize`, then the body sent to the authorization server must be truncated to `maxSize` bytes.\n\nExperimental note: This behavior needs to be checked against various dataplanes; it may need to be changed. See https://github.com/kubernetes-sigs/gateway-api/pull/4001#discussion_r2291405746 for more.\n\nIf 0, the body will not be sent to the authorization server.", + Type: []string{"integer"}, + Format: "int32", + }, + }, + }, + }, + }, + } +} + +func schema_sigsk8sio_gateway_api_apis_v1_Fraction(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "numerator": { + SchemaProps: spec.SchemaProps{ + Default: 0, + Type: []string{"integer"}, + Format: "int32", + }, + }, + "denominator": { + SchemaProps: spec.SchemaProps{ + Type: []string{"integer"}, + Format: "int32", + }, + }, + }, + Required: []string{"numerator"}, + }, + }, + } +} + +func schema_sigsk8sio_gateway_api_apis_v1_FrontendTLSConfig(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "FrontendTLSConfig specifies frontend tls configuration for gateway.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "default": { + SchemaProps: spec.SchemaProps{ + Description: "Default specifies the default client certificate validation configuration for all Listeners handling HTTPS traffic, unless a per-port configuration is defined.\n\nsupport: Core\n\n", + Default: map[string]interface{}{}, + Ref: ref("sigs.k8s.io/gateway-api/apis/v1.TLSConfig"), + }, + }, + "perPort": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-map-keys": []interface{}{ + "port", + }, + "x-kubernetes-list-type": "map", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "PerPort specifies tls configuration assigned per port. Per port configuration is optional. Once set this configuration overrides the default configuration for all Listeners handling HTTPS traffic that match this port. Each override port requires a unique TLS configuration.\n\nsupport: Core\n\n", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("sigs.k8s.io/gateway-api/apis/v1.TLSPortConfig"), + }, + }, + }, + }, + }, + }, + Required: []string{"default"}, + }, + }, + Dependencies: []string{ + "sigs.k8s.io/gateway-api/apis/v1.TLSConfig", "sigs.k8s.io/gateway-api/apis/v1.TLSPortConfig"}, + } +} + +func schema_sigsk8sio_gateway_api_apis_v1_FrontendTLSValidation(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "FrontendTLSValidation holds configuration information that can be used to validate the frontend initiating the TLS connection", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "caCertificateRefs": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "CACertificateRefs contains one or more references to Kubernetes objects that contain TLS certificates of the Certificate Authorities that can be used as a trust anchor to validate the certificates presented by the client.\n\nA single CA certificate reference to a Kubernetes ConfigMap has \"Core\" support. Implementations MAY choose to support attaching multiple CA certificates to a Listener, but this behavior is implementation-specific.\n\nSupport: Core - A single reference to a Kubernetes ConfigMap with the CA certificate in a key named `ca.crt`.\n\nSupport: Implementation-specific (More than one certificate in a ConfigMap with different keys or more than one reference, or other kinds of resources).\n\nReferences to a resource in a different namespace are invalid UNLESS there is a ReferenceGrant in the target namespace that allows the certificate to be attached. If a ReferenceGrant does not allow this reference, the \"ResolvedRefs\" condition MUST be set to False for this listener with the \"RefNotPermitted\" reason.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("sigs.k8s.io/gateway-api/apis/v1.ObjectReference"), + }, + }, + }, + }, + }, + "mode": { + SchemaProps: spec.SchemaProps{ + Description: "FrontendValidationMode defines the mode for validating the client certificate. There are two possible modes:\n\n- AllowValidOnly: In this mode, the gateway will accept connections only if\n the client presents a valid certificate. This certificate must successfully\n pass validation against the CA certificates specified in `CACertificateRefs`.\n- AllowInsecureFallback: In this mode, the gateway will accept connections\n even if the client certificate is not presented or fails verification.\n\n This approach delegates client authorization to the backend and introduce\n a significant security risk. It should be used in testing environments or\n on a temporary basis in non-testing environments.\n\nDefaults to AllowValidOnly.\n\nSupport: Core", + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"caCertificateRefs"}, + }, + }, + Dependencies: []string{ + "sigs.k8s.io/gateway-api/apis/v1.ObjectReference"}, + } +} + +func schema_sigsk8sio_gateway_api_apis_v1_GRPCAuthConfig(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "GRPCAuthConfig contains configuration for communication with Auth server backends that speak Envoy's ext_authz gRPC protocol.\n\nRequests and responses are defined in the protobufs explained at: https://www.envoyproxy.io/docs/envoy/latest/api-v3/service/auth/v3/external_auth.proto", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "allowedHeaders": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "set", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "AllowedRequestHeaders specifies what headers from the client request will be sent to the authorization server.\n\nIf this list is empty, then all headers must be sent.\n\nIf the list has entries, only those entries must be sent.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + }, + }, + }, + } +} + +func schema_sigsk8sio_gateway_api_apis_v1_GRPCBackendRef(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "GRPCBackendRef defines how a GRPCRoute forwards a gRPC request.\n\nNote that when a namespace different than the local namespace is specified, a ReferenceGrant object is required in the referent namespace to allow that namespace's owner to accept the reference. See the ReferenceGrant documentation for details.\n\n\n\nWhen the BackendRef points to a Kubernetes Service, implementations SHOULD honor the appProtocol field if it is set for the target Service Port.\n\nImplementations supporting appProtocol SHOULD recognize the Kubernetes Standard Application Protocols defined in KEP-3726.\n\nIf a Service appProtocol isn't specified, an implementation MAY infer the backend protocol through its own means. Implementations MAY infer the protocol from the Route type referring to the backend Service.\n\nIf a Route is not able to send traffic to the backend using the specified protocol then the backend is considered invalid. Implementations MUST set the \"ResolvedRefs\" condition to \"False\" with the \"UnsupportedProtocol\" reason.\n\n", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "group": { + SchemaProps: spec.SchemaProps{ + Description: "Group is the group of the referent. For example, \"gateway.networking.k8s.io\". When unspecified or empty string, core API group is inferred.", + Type: []string{"string"}, + Format: "", + }, + }, + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is the Kubernetes resource kind of the referent. For example \"Service\".\n\nDefaults to \"Service\" when not specified.\n\nExternalName services can refer to CNAME DNS records that may live outside of the cluster and as such are difficult to reason about in terms of conformance. They also may not be safe to forward to (see CVE-2021-25740 for more information). Implementations SHOULD NOT support ExternalName Services.\n\nSupport: Core (Services with a type other than ExternalName)\n\nSupport: Implementation-specific (Services with type ExternalName)", + Type: []string{"string"}, + Format: "", + }, + }, + "name": { + SchemaProps: spec.SchemaProps{ + Description: "Name is the name of the referent.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "namespace": { + SchemaProps: spec.SchemaProps{ + Description: "Namespace is the namespace of the backend. When unspecified, the local namespace is inferred.\n\nNote that when a namespace different than the local namespace is specified, a ReferenceGrant object is required in the referent namespace to allow that namespace's owner to accept the reference. See the ReferenceGrant documentation for details.\n\nSupport: Core", + Type: []string{"string"}, + Format: "", + }, + }, + "port": { + SchemaProps: spec.SchemaProps{ + Description: "Port specifies the destination port number to use for this resource. Port is required when the referent is a Kubernetes Service. In this case, the port number is the service port number, not the target port. For other resources, destination port might be derived from the referent resource or this field.", + Type: []string{"integer"}, + Format: "int32", + }, + }, + "weight": { + SchemaProps: spec.SchemaProps{ + Description: "Weight specifies the proportion of requests forwarded to the referenced backend. This is computed as weight/(sum of all weights in this BackendRefs list). For non-zero values, there may be some epsilon from the exact proportion defined here depending on the precision an implementation supports. Weight is not a percentage and the sum of weights does not need to equal 100.\n\nIf only one backend is specified and it has a weight greater than 0, 100% of the traffic is forwarded to that backend. If weight is set to 0, no traffic should be forwarded for this entry. If unspecified, weight defaults to 1.\n\nSupport for this field varies based on the context where used.", + Type: []string{"integer"}, + Format: "int32", + }, + }, + "filters": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "Filters defined at this level MUST be executed if and only if the request is being forwarded to the backend defined here.\n\nSupport: Implementation-specific (For broader support of filters, use the Filters field in GRPCRouteRule.)", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("sigs.k8s.io/gateway-api/apis/v1.GRPCRouteFilter"), + }, + }, + }, + }, + }, + }, + Required: []string{"name"}, + }, + }, + Dependencies: []string{ + "sigs.k8s.io/gateway-api/apis/v1.GRPCRouteFilter"}, + } +} + +func schema_sigsk8sio_gateway_api_apis_v1_GRPCHeaderMatch(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "GRPCHeaderMatch describes how to select a gRPC route by matching gRPC request headers.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "type": { + SchemaProps: spec.SchemaProps{ + Description: "Type specifies how to match against the value of the header.", + Type: []string{"string"}, + Format: "", + }, + }, + "name": { + SchemaProps: spec.SchemaProps{ + Description: "Name is the name of the gRPC Header to be matched.\n\nIf multiple entries specify equivalent header names, only the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "value": { + SchemaProps: spec.SchemaProps{ + Description: "Value is the value of the gRPC Header to be matched.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"name", "value"}, + }, + }, + } +} + +func schema_sigsk8sio_gateway_api_apis_v1_GRPCMethodMatch(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "GRPCMethodMatch describes how to select a gRPC route by matching the gRPC request service and/or method.\n\nAt least one of Service and Method MUST be a non-empty string.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "type": { + SchemaProps: spec.SchemaProps{ + Description: "Type specifies how to match against the service and/or method. Support: Core (Exact with service and method specified)\n\nSupport: Implementation-specific (Exact with method specified but no service specified)\n\nSupport: Implementation-specific (RegularExpression)", + Type: []string{"string"}, + Format: "", + }, + }, + "service": { + SchemaProps: spec.SchemaProps{ + Description: "Value of the service to match against. If left empty or omitted, will match any service.\n\nAt least one of Service and Method MUST be a non-empty string.", + Type: []string{"string"}, + Format: "", + }, + }, + "method": { + SchemaProps: spec.SchemaProps{ + Description: "Value of the method to match against. If left empty or omitted, will match all services.\n\nAt least one of Service and Method MUST be a non-empty string.", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + } +} + +func schema_sigsk8sio_gateway_api_apis_v1_GRPCRoute(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "GRPCRoute provides a way to route gRPC requests. This includes the capability to match requests by hostname, gRPC service, gRPC method, or HTTP/2 header. Filters can be used to specify additional processing steps. Backends specify where matching requests will be routed.\n\nGRPCRoute falls under extended support within the Gateway API. Within the following specification, the word \"MUST\" indicates that an implementation supporting GRPCRoute must conform to the indicated requirement, but an implementation not supporting this route type need not follow the requirement unless explicitly indicated.\n\nImplementations supporting `GRPCRoute` with the `HTTPS` `ProtocolType` MUST accept HTTP/2 connections without an initial upgrade from HTTP/1.1, i.e. via ALPN. If the implementation does not support this, then it MUST set the \"Accepted\" condition to \"False\" for the affected listener with a reason of \"UnsupportedProtocol\". Implementations MAY also accept HTTP/2 connections with an upgrade from HTTP/1.\n\nImplementations supporting `GRPCRoute` with the `HTTP` `ProtocolType` MUST support HTTP/2 over cleartext TCP (h2c, https://www.rfc-editor.org/rfc/rfc7540#section-3.1) without an initial upgrade from HTTP/1.1, i.e. with prior knowledge (https://www.rfc-editor.org/rfc/rfc7540#section-3.4). If the implementation does not support this, then it MUST set the \"Accepted\" condition to \"False\" for the affected listener with a reason of \"UnsupportedProtocol\". Implementations MAY also accept HTTP/2 connections with an upgrade from HTTP/1, i.e. without prior knowledge.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", + }, + }, + "metadata": { + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"), + }, + }, + "spec": { + SchemaProps: spec.SchemaProps{ + Description: "Spec defines the desired state of GRPCRoute.", + Default: map[string]interface{}{}, + Ref: ref("sigs.k8s.io/gateway-api/apis/v1.GRPCRouteSpec"), + }, + }, + "status": { + SchemaProps: spec.SchemaProps{ + Description: "Status defines the current state of GRPCRoute.", + Default: map[string]interface{}{}, + Ref: ref("sigs.k8s.io/gateway-api/apis/v1.GRPCRouteStatus"), + }, + }, + }, + Required: []string{"spec"}, + }, + }, + Dependencies: []string{ + "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta", "sigs.k8s.io/gateway-api/apis/v1.GRPCRouteSpec", "sigs.k8s.io/gateway-api/apis/v1.GRPCRouteStatus"}, + } +} + +func schema_sigsk8sio_gateway_api_apis_v1_GRPCRouteFilter(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "GRPCRouteFilter defines processing steps that must be completed during the request or response lifecycle. GRPCRouteFilters are meant as an extension point to express processing that may be done in Gateway implementations. Some examples include request or response modification, implementing authentication strategies, rate-limiting, and traffic shaping. API guarantee/conformance is defined based on the type of the filter.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "type": { + SchemaProps: spec.SchemaProps{ + Description: "Type identifies the type of filter to apply. As with other API fields, types are classified into three conformance levels:\n\n- Core: Filter types and their corresponding configuration defined by\n \"Support: Core\" in this package, e.g. \"RequestHeaderModifier\". All\n implementations supporting GRPCRoute MUST support core filters.\n\n- Extended: Filter types and their corresponding configuration defined by\n \"Support: Extended\" in this package, e.g. \"RequestMirror\". Implementers\n are encouraged to support extended filters.\n\n- Implementation-specific: Filters that are defined and supported by specific vendors.\n In the future, filters showing convergence in behavior across multiple\n implementations will be considered for inclusion in extended or core\n conformance levels. Filter-specific configuration for such filters\n is specified using the ExtensionRef field. `Type` MUST be set to\n \"ExtensionRef\" for custom filters.\n\nImplementers are encouraged to define custom implementation types to extend the core API with implementation-specific behavior.\n\nIf a reference to a custom filter type cannot be resolved, the filter MUST NOT be skipped. Instead, requests that would have been processed by that filter MUST receive a HTTP error response.\n\n", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "requestHeaderModifier": { + SchemaProps: spec.SchemaProps{ + Description: "RequestHeaderModifier defines a schema for a filter that modifies request headers.\n\nSupport: Core", + Ref: ref("sigs.k8s.io/gateway-api/apis/v1.HTTPHeaderFilter"), + }, + }, + "responseHeaderModifier": { + SchemaProps: spec.SchemaProps{ + Description: "ResponseHeaderModifier defines a schema for a filter that modifies response headers.\n\nSupport: Extended", + Ref: ref("sigs.k8s.io/gateway-api/apis/v1.HTTPHeaderFilter"), + }, + }, + "requestMirror": { + SchemaProps: spec.SchemaProps{ + Description: "RequestMirror defines a schema for a filter that mirrors requests. Requests are sent to the specified destination, but responses from that destination are ignored.\n\nThis filter can be used multiple times within the same rule. Note that not all implementations will be able to support mirroring to multiple backends.\n\nSupport: Extended", + Ref: ref("sigs.k8s.io/gateway-api/apis/v1.HTTPRequestMirrorFilter"), + }, + }, + "extensionRef": { + SchemaProps: spec.SchemaProps{ + Description: "ExtensionRef is an optional, implementation-specific extension to the \"filter\" behavior. For example, resource \"myroutefilter\" in group \"networking.example.net\"). ExtensionRef MUST NOT be used for core and extended filters.\n\nSupport: Implementation-specific\n\nThis filter can be used multiple times within the same rule.", + Ref: ref("sigs.k8s.io/gateway-api/apis/v1.LocalObjectReference"), + }, + }, + }, + Required: []string{"type"}, + }, + }, + Dependencies: []string{ + "sigs.k8s.io/gateway-api/apis/v1.HTTPHeaderFilter", "sigs.k8s.io/gateway-api/apis/v1.HTTPRequestMirrorFilter", "sigs.k8s.io/gateway-api/apis/v1.LocalObjectReference"}, + } +} + +func schema_sigsk8sio_gateway_api_apis_v1_GRPCRouteList(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "GRPCRouteList contains a list of GRPCRoute.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", + }, + }, + "metadata": { + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"), + }, + }, + "items": { + SchemaProps: spec.SchemaProps{ + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("sigs.k8s.io/gateway-api/apis/v1.GRPCRoute"), + }, + }, + }, + }, + }, + }, + Required: []string{"items"}, + }, + }, + Dependencies: []string{ + "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta", "sigs.k8s.io/gateway-api/apis/v1.GRPCRoute"}, + } +} + +func schema_sigsk8sio_gateway_api_apis_v1_GRPCRouteMatch(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "GRPCRouteMatch defines the predicate used to match requests to a given action. Multiple match types are ANDed together, i.e. the match will evaluate to true only if all conditions are satisfied.\n\nFor example, the match below will match a gRPC request only if its service is `foo` AND it contains the `version: v1` header:\n\n``` matches:\n - method:\n type: Exact\n service: \"foo\"\n headers:\n - name: \"version\"\n value \"v1\"\n\n```", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "method": { + SchemaProps: spec.SchemaProps{ + Description: "Method specifies a gRPC request service/method matcher. If this field is not specified, all services and methods will match.", + Ref: ref("sigs.k8s.io/gateway-api/apis/v1.GRPCMethodMatch"), + }, + }, + "headers": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-map-keys": []interface{}{ + "name", + }, + "x-kubernetes-list-type": "map", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "Headers specifies gRPC request header matchers. Multiple match values are ANDed together, meaning, a request MUST match all the specified headers to select the route.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("sigs.k8s.io/gateway-api/apis/v1.GRPCHeaderMatch"), + }, + }, + }, + }, + }, + }, + }, + }, + Dependencies: []string{ + "sigs.k8s.io/gateway-api/apis/v1.GRPCHeaderMatch", "sigs.k8s.io/gateway-api/apis/v1.GRPCMethodMatch"}, + } +} + +func schema_sigsk8sio_gateway_api_apis_v1_GRPCRouteRule(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "GRPCRouteRule defines the semantics for matching a gRPC request based on conditions (matches), processing it (filters), and forwarding the request to an API object (backendRefs).", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "name": { + SchemaProps: spec.SchemaProps{ + Description: "Name is the name of the route rule. This name MUST be unique within a Route if it is set.\n\nSupport: Extended", + Type: []string{"string"}, + Format: "", + }, + }, + "matches": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "Matches define conditions used for matching the rule against incoming gRPC requests. Each match is independent, i.e. this rule will be matched if **any** one of the matches is satisfied.\n\nFor example, take the following matches configuration:\n\n``` matches: - method:\n service: foo.bar\n headers:\n values:\n version: 2\n- method:\n service: foo.bar.v2\n```\n\nFor a request to match against this rule, it MUST satisfy EITHER of the two conditions:\n\n- service of foo.bar AND contains the header `version: 2` - service of foo.bar.v2\n\nSee the documentation for GRPCRouteMatch on how to specify multiple match conditions to be ANDed together.\n\nIf no matches are specified, the implementation MUST match every gRPC request.\n\nProxy or Load Balancer routing configuration generated from GRPCRoutes MUST prioritize rules based on the following criteria, continuing on ties. Merging MUST not be done between GRPCRoutes and HTTPRoutes. Precedence MUST be given to the rule with the largest number of:\n\n* Characters in a matching non-wildcard hostname. * Characters in a matching hostname. * Characters in a matching service. * Characters in a matching method. * Header matches.\n\nIf ties still exist across multiple Routes, matching precedence MUST be determined in order of the following criteria, continuing on ties:\n\n* The oldest Route based on creation timestamp. * The Route appearing first in alphabetical order by\n \"{namespace}/{name}\".\n\nIf ties still exist within the Route that has been given precedence, matching precedence MUST be granted to the first matching rule meeting the above criteria.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("sigs.k8s.io/gateway-api/apis/v1.GRPCRouteMatch"), + }, + }, + }, + }, + }, + "filters": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "Filters define the filters that are applied to requests that match this rule.\n\nThe effects of ordering of multiple behaviors are currently unspecified. This can change in the future based on feedback during the alpha stage.\n\nConformance-levels at this level are defined based on the type of filter:\n\n- ALL core filters MUST be supported by all implementations that support\n GRPCRoute.\n- Implementers are encouraged to support extended filters. - Implementation-specific custom filters have no API guarantees across\n implementations.\n\nSpecifying the same filter multiple times is not supported unless explicitly indicated in the filter.\n\nIf an implementation cannot support a combination of filters, it must clearly document that limitation. In cases where incompatible or unsupported filters are specified and cause the `Accepted` condition to be set to status `False`, implementations may use the `IncompatibleFilters` reason to specify this configuration error.\n\nSupport: Core", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("sigs.k8s.io/gateway-api/apis/v1.GRPCRouteFilter"), + }, + }, + }, + }, + }, + "backendRefs": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "BackendRefs defines the backend(s) where matching requests should be sent.\n\nFailure behavior here depends on how many BackendRefs are specified and how many are invalid.\n\nIf *all* entries in BackendRefs are invalid, and there are also no filters specified in this route rule, *all* traffic which matches this rule MUST receive an `UNAVAILABLE` status.\n\nSee the GRPCBackendRef definition for the rules about what makes a single GRPCBackendRef invalid.\n\nWhen a GRPCBackendRef is invalid, `UNAVAILABLE` statuses MUST be returned for requests that would have otherwise been routed to an invalid backend. If multiple backends are specified, and some are invalid, the proportion of requests that would otherwise have been routed to an invalid backend MUST receive an `UNAVAILABLE` status.\n\nFor example, if two backends are specified with equal weights, and one is invalid, 50 percent of traffic MUST receive an `UNAVAILABLE` status. Implementations may choose how that 50 percent is determined.\n\nSupport: Core for Kubernetes Service\n\nSupport: Implementation-specific for any other resource\n\nSupport for weight: Core", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("sigs.k8s.io/gateway-api/apis/v1.GRPCBackendRef"), + }, + }, + }, + }, + }, + "sessionPersistence": { + SchemaProps: spec.SchemaProps{ + Description: "SessionPersistence defines and configures session persistence for the route rule.\n\nSupport: Extended\n\n", + Ref: ref("sigs.k8s.io/gateway-api/apis/v1.SessionPersistence"), + }, + }, + }, + }, + }, + Dependencies: []string{ + "sigs.k8s.io/gateway-api/apis/v1.GRPCBackendRef", "sigs.k8s.io/gateway-api/apis/v1.GRPCRouteFilter", "sigs.k8s.io/gateway-api/apis/v1.GRPCRouteMatch", "sigs.k8s.io/gateway-api/apis/v1.SessionPersistence"}, + } +} + +func schema_sigsk8sio_gateway_api_apis_v1_GRPCRouteSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "GRPCRouteSpec defines the desired state of GRPCRoute", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "parentRefs": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "ParentRefs references the resources (usually Gateways) that a Route wants to be attached to. Note that the referenced parent resource needs to allow this for the attachment to be complete. For Gateways, that means the Gateway needs to allow attachment from Routes of this kind and namespace. For Services, that means the Service must either be in the same namespace for a \"producer\" route, or the mesh implementation must support and allow \"consumer\" routes for the referenced Service. ReferenceGrant is not applicable for governing ParentRefs to Services - it is not possible to create a \"producer\" route for a Service in a different namespace from the Route.\n\nThere are two kinds of parent resources with \"Core\" support:\n\n* Gateway (Gateway conformance profile) * Service (Mesh conformance profile, ClusterIP Services only)\n\nThis API may be extended in the future to support additional kinds of parent resources.\n\nParentRefs must be _distinct_. This means either that:\n\n* They select different objects. If this is the case, then parentRef\n entries are distinct. In terms of fields, this means that the\n multi-part key defined by `group`, `kind`, `namespace`, and `name` must\n be unique across all parentRef entries in the Route.\n* They do not select different objects, but for each optional field used,\n each ParentRef that selects the same object must set the same set of\n optional fields to different values. If one ParentRef sets a\n combination of optional fields, all must set the same combination.\n\nSome examples:\n\n* If one ParentRef sets `sectionName`, all ParentRefs referencing the\n same object must also set `sectionName`.\n* If one ParentRef sets `port`, all ParentRefs referencing the same\n object must also set `port`.\n* If one ParentRef sets `sectionName` and `port`, all ParentRefs\n referencing the same object must also set `sectionName` and `port`.\n\nIt is possible to separately reference multiple distinct objects that may be collapsed by an implementation. For example, some implementations may choose to merge compatible Gateway Listeners together. If that is the case, the list of routes attached to those resources should also be merged.\n\nNote that for ParentRefs that cross namespace boundaries, there are specific rules. Cross-namespace references are only valid if they are explicitly allowed by something in the namespace they are referring to. For example, Gateway has the AllowedRoutes field, and ReferenceGrant provides a generic way to enable other kinds of cross-namespace reference.\n\n ParentRefs from a Route to a Service in the same namespace are \"producer\" routes, which apply default routing rules to inbound connections from any namespace to the Service.\n\nParentRefs from a Route to a Service in a different namespace are \"consumer\" routes, and these routing rules are only applied to outbound connections originating from the same namespace as the Route, for which the intended destination of the connections are a Service targeted as a ParentRef of the Route. \n\n ", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("sigs.k8s.io/gateway-api/apis/v1.ParentReference"), + }, + }, + }, + }, + }, + "useDefaultGateways": { + SchemaProps: spec.SchemaProps{ + Description: "UseDefaultGateways indicates the default Gateway scope to use for this Route. If unset (the default) or set to None, the Route will not be attached to any default Gateway; if set, it will be attached to any default Gateway supporting the named scope, subject to the usual rules about which Routes a Gateway is allowed to claim.\n\nThink carefully before using this functionality! The set of default Gateways supporting the requested scope can change over time without any notice to the Route author, and in many situations it will not be appropriate to request a default Gateway for a given Route -- for example, a Route with specific security requirements should almost certainly not use a default Gateway.\n\n", + Type: []string{"string"}, + Format: "", + }, + }, + "hostnames": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "Hostnames defines a set of hostnames to match against the GRPC Host header to select a GRPCRoute to process the request. This matches the RFC 1123 definition of a hostname with 2 notable exceptions:\n\n1. IPs are not allowed. 2. A hostname may be prefixed with a wildcard label (`*.`). The wildcard\n label MUST appear by itself as the first label.\n\nIf a hostname is specified by both the Listener and GRPCRoute, there MUST be at least one intersecting hostname for the GRPCRoute to be attached to the Listener. For example:\n\n* A Listener with `test.example.com` as the hostname matches GRPCRoutes\n that have either not specified any hostnames, or have specified at\n least one of `test.example.com` or `*.example.com`.\n* A Listener with `*.example.com` as the hostname matches GRPCRoutes\n that have either not specified any hostnames or have specified at least\n one hostname that matches the Listener hostname. For example,\n `test.example.com` and `*.example.com` would both match. On the other\n hand, `example.com` and `test.example.net` would not match.\n\nHostnames that are prefixed with a wildcard label (`*.`) are interpreted as a suffix match. That means that a match for `*.example.com` would match both `test.example.com`, and `foo.test.example.com`, but not `example.com`.\n\nIf both the Listener and GRPCRoute have specified hostnames, any GRPCRoute hostnames that do not match the Listener hostname MUST be ignored. For example, if a Listener specified `*.example.com`, and the GRPCRoute specified `test.example.com` and `test.example.net`, `test.example.net` MUST NOT be considered for a match.\n\nIf both the Listener and GRPCRoute have specified hostnames, and none match with the criteria above, then the GRPCRoute MUST NOT be accepted by the implementation. The implementation MUST raise an 'Accepted' Condition with a status of `False` in the corresponding RouteParentStatus.\n\nIf a Route (A) of type HTTPRoute or GRPCRoute is attached to a Listener and that listener already has another Route (B) of the other type attached and the intersection of the hostnames of A and B is non-empty, then the implementation MUST accept exactly one of these two routes, determined by the following criteria, in order:\n\n* The oldest Route based on creation timestamp. * The Route appearing first in alphabetical order by\n \"{namespace}/{name}\".\n\nThe rejected Route MUST raise an 'Accepted' condition with a status of 'False' in the corresponding RouteParentStatus.\n\nSupport: Core", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + "rules": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "Rules are a list of GRPC matchers, filters and actions.\n\n", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("sigs.k8s.io/gateway-api/apis/v1.GRPCRouteRule"), + }, + }, + }, + }, + }, + }, + }, + }, + Dependencies: []string{ + "sigs.k8s.io/gateway-api/apis/v1.GRPCRouteRule", "sigs.k8s.io/gateway-api/apis/v1.ParentReference"}, + } +} + +func schema_sigsk8sio_gateway_api_apis_v1_GRPCRouteStatus(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "GRPCRouteStatus defines the observed state of GRPCRoute.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "parents": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "Parents is a list of parent resources (usually Gateways) that are associated with the route, and the status of the route with respect to each parent. When this route attaches to a parent, the controller that manages the parent must add an entry to this list when the controller first sees the route and should update the entry as appropriate when the route or gateway is modified.\n\nNote that parent references that cannot be resolved by an implementation of this API will not be added to this list. Implementations of this API can only populate Route status for the Gateways/parent resources they are responsible for.\n\nA maximum of 32 Gateways will be represented in this list. An empty list means the route has not been attached to any Gateway.\n\n Notes for implementors:\n\nWhile parents is not a listType `map`, this is due to the fact that the list key is not scalar, and Kubernetes is unable to represent this.\n\nParent status MUST be considered to be namespaced by the combination of the parentRef and controllerName fields, and implementations should keep the following rules in mind when updating this status:\n\n* Implementations MUST update only entries that have a matching value of\n `controllerName` for that implementation.\n* Implementations MUST NOT update entries with non-matching `controllerName`\n fields.\n* Implementations MUST treat each `parentRef`` in the Route separately and\n update its status based on the relationship with that parent.\n* Implementations MUST perform a read-modify-write cycle on this field\n before modifying it. That is, when modifying this field, implementations\n must be confident they have fetched the most recent version of this field,\n and ensure that changes they make are on that recent version.\n\n", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("sigs.k8s.io/gateway-api/apis/v1.RouteParentStatus"), + }, + }, + }, + }, + }, + }, + Required: []string{"parents"}, + }, + }, + Dependencies: []string{ + "sigs.k8s.io/gateway-api/apis/v1.RouteParentStatus"}, + } +} + +func schema_sigsk8sio_gateway_api_apis_v1_Gateway(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "Gateway represents an instance of a service-traffic handling infrastructure by binding Listeners to a set of IP addresses.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", + }, + }, + "metadata": { + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"), + }, + }, + "spec": { + SchemaProps: spec.SchemaProps{ + Description: "Spec defines the desired state of Gateway.", + Default: map[string]interface{}{}, + Ref: ref("sigs.k8s.io/gateway-api/apis/v1.GatewaySpec"), + }, + }, + "status": { + SchemaProps: spec.SchemaProps{ + Description: "Status defines the current state of Gateway.", + Default: map[string]interface{}{}, + Ref: ref("sigs.k8s.io/gateway-api/apis/v1.GatewayStatus"), + }, + }, + }, + Required: []string{"spec"}, + }, + }, + Dependencies: []string{ + "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta", "sigs.k8s.io/gateway-api/apis/v1.GatewaySpec", "sigs.k8s.io/gateway-api/apis/v1.GatewayStatus"}, + } +} + +func schema_sigsk8sio_gateway_api_apis_v1_GatewayBackendTLS(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "GatewayBackendTLS describes backend TLS configuration for gateway.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "clientCertificateRef": { + SchemaProps: spec.SchemaProps{ + Description: "ClientCertificateRef is a reference to an object that contains a Client Certificate and the associated private key.\n\nReferences to a resource in different namespace are invalid UNLESS there is a ReferenceGrant in the target namespace that allows the certificate to be attached. If a ReferenceGrant does not allow this reference, the \"ResolvedRefs\" condition MUST be set to False for this listener with the \"RefNotPermitted\" reason.\n\nClientCertificateRef can reference to standard Kubernetes resources, i.e. Secret, or implementation-specific custom resources.\n\nSupport: Core\n\n", + Ref: ref("sigs.k8s.io/gateway-api/apis/v1.SecretObjectReference"), + }, + }, + }, + }, + }, + Dependencies: []string{ + "sigs.k8s.io/gateway-api/apis/v1.SecretObjectReference"}, + } +} + +func schema_sigsk8sio_gateway_api_apis_v1_GatewayClass(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "GatewayClass describes a class of Gateways available to the user for creating Gateway resources.\n\nIt is recommended that this resource be used as a template for Gateways. This means that a Gateway is based on the state of the GatewayClass at the time it was created and changes to the GatewayClass or associated parameters are not propagated down to existing Gateways. This recommendation is intended to limit the blast radius of changes to GatewayClass or associated parameters. If implementations choose to propagate GatewayClass changes to existing Gateways, that MUST be clearly documented by the implementation.\n\nWhenever one or more Gateways are using a GatewayClass, implementations SHOULD add the `gateway-exists-finalizer.gateway.networking.k8s.io` finalizer on the associated GatewayClass. This ensures that a GatewayClass associated with a Gateway is not deleted while in use.\n\nGatewayClass is a Cluster level resource.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", + }, + }, + "metadata": { + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"), + }, + }, + "spec": { + SchemaProps: spec.SchemaProps{ + Description: "Spec defines the desired state of GatewayClass.", + Default: map[string]interface{}{}, + Ref: ref("sigs.k8s.io/gateway-api/apis/v1.GatewayClassSpec"), + }, + }, + "status": { + SchemaProps: spec.SchemaProps{ + Description: "Status defines the current state of GatewayClass.\n\nImplementations MUST populate status on all GatewayClass resources which specify their controller name.", + Default: map[string]interface{}{}, + Ref: ref("sigs.k8s.io/gateway-api/apis/v1.GatewayClassStatus"), + }, + }, + }, + Required: []string{"spec"}, + }, + }, + Dependencies: []string{ + "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta", "sigs.k8s.io/gateway-api/apis/v1.GatewayClassSpec", "sigs.k8s.io/gateway-api/apis/v1.GatewayClassStatus"}, + } +} + +func schema_sigsk8sio_gateway_api_apis_v1_GatewayClassList(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "GatewayClassList contains a list of GatewayClass", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", + }, + }, + "metadata": { + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"), + }, + }, + "items": { + SchemaProps: spec.SchemaProps{ + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("sigs.k8s.io/gateway-api/apis/v1.GatewayClass"), + }, + }, + }, + }, + }, + }, + Required: []string{"items"}, + }, + }, + Dependencies: []string{ + "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta", "sigs.k8s.io/gateway-api/apis/v1.GatewayClass"}, + } +} + +func schema_sigsk8sio_gateway_api_apis_v1_GatewayClassSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "GatewayClassSpec reflects the configuration of a class of Gateways.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "controllerName": { + SchemaProps: spec.SchemaProps{ + Description: "ControllerName is the name of the controller that is managing Gateways of this class. The value of this field MUST be a domain prefixed path.\n\nExample: \"example.net/gateway-controller\".\n\nThis field is not mutable and cannot be empty.\n\nSupport: Core", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "parametersRef": { + SchemaProps: spec.SchemaProps{ + Description: "ParametersRef is a reference to a resource that contains the configuration parameters corresponding to the GatewayClass. This is optional if the controller does not require any additional configuration.\n\nParametersRef can reference a standard Kubernetes resource, i.e. ConfigMap, or an implementation-specific custom resource. The resource can be cluster-scoped or namespace-scoped.\n\nIf the referent cannot be found, refers to an unsupported kind, or when the data within that resource is malformed, the GatewayClass SHOULD be rejected with the \"Accepted\" status condition set to \"False\" and an \"InvalidParameters\" reason.\n\nA Gateway for this GatewayClass may provide its own `parametersRef`. When both are specified, the merging behavior is implementation specific. It is generally recommended that GatewayClass provides defaults that can be overridden by a Gateway.\n\nSupport: Implementation-specific", + Ref: ref("sigs.k8s.io/gateway-api/apis/v1.ParametersReference"), + }, + }, + "description": { + SchemaProps: spec.SchemaProps{ + Description: "Description helps describe a GatewayClass with more details.", + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"controllerName"}, + }, + }, + Dependencies: []string{ + "sigs.k8s.io/gateway-api/apis/v1.ParametersReference"}, + } +} + +func schema_sigsk8sio_gateway_api_apis_v1_GatewayClassStatus(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "GatewayClassStatus is the current status for the GatewayClass.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "conditions": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-map-keys": []interface{}{ + "type", + }, + "x-kubernetes-list-type": "map", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "Conditions is the current status from the controller for this GatewayClass.\n\nControllers should prefer to publish conditions using values of GatewayClassConditionType for the type of each Condition.\n\n Notes for implementors:\n\nConditions are a listType `map`, which means that they function like a map with a key of the `type` field _in the k8s apiserver_.\n\nThis means that implementations must obey some rules when updating this section.\n\n* Implementations MUST perform a read-modify-write cycle on this field\n before modifying it. That is, when modifying this field, implementations\n must be confident they have fetched the most recent version of this field,\n and ensure that changes they make are on that recent version.\n* Implementations MUST NOT remove or reorder Conditions that they are not\n directly responsible for. For example, if an implementation sees a Condition\n with type `special.io/SomeField`, it MUST NOT remove, change or update that\n Condition.\n* Implementations MUST always _merge_ changes into Conditions of the same Type,\n rather than creating more than one Condition of the same Type.\n* Implementations MUST always update the `observedGeneration` field of the\n Condition to the `metadata.generation` of the Gateway at the time of update creation.\n* If the `observedGeneration` of a Condition is _greater than_ the value the\n implementation knows about, then it MUST NOT perform the update on that Condition,\n but must wait for a future reconciliation and status update. (The assumption is that\n the implementation's copy of the object is stale and an update will be re-triggered\n if relevant.)\n\n", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.Condition"), + }, + }, + }, + }, + }, + "supportedFeatures": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-map-keys": []interface{}{ + "name", + }, + "x-kubernetes-list-type": "map", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "SupportedFeatures is the set of features the GatewayClass support. It MUST be sorted in ascending alphabetical order by the Name key.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Ref: ref("sigs.k8s.io/gateway-api/apis/v1.SupportedFeature"), + }, + }, + }, + }, + }, + }, + }, + }, + Dependencies: []string{ + "k8s.io/apimachinery/pkg/apis/meta/v1.Condition", "sigs.k8s.io/gateway-api/apis/v1.SupportedFeature"}, + } +} + +func schema_sigsk8sio_gateway_api_apis_v1_GatewayInfrastructure(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "GatewayInfrastructure defines infrastructure level attributes about a Gateway instance.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "labels": { + SchemaProps: spec.SchemaProps{ + Description: "Labels that SHOULD be applied to any resources created in response to this Gateway.\n\nFor implementations creating other Kubernetes objects, this should be the `metadata.labels` field on resources. For other implementations, this refers to any relevant (implementation specific) \"labels\" concepts.\n\nAn implementation may chose to add additional implementation-specific labels as they see fit.\n\nIf an implementation maps these labels to Pods, or any other resource that would need to be recreated when labels change, it SHOULD clearly warn about this behavior in documentation.\n\nSupport: Extended", + Type: []string{"object"}, + AdditionalProperties: &spec.SchemaOrBool{ + Allows: true, + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + "annotations": { + SchemaProps: spec.SchemaProps{ + Description: "Annotations that SHOULD be applied to any resources created in response to this Gateway.\n\nFor implementations creating other Kubernetes objects, this should be the `metadata.annotations` field on resources. For other implementations, this refers to any relevant (implementation specific) \"annotations\" concepts.\n\nAn implementation may chose to add additional implementation-specific annotations as they see fit.\n\nSupport: Extended", + Type: []string{"object"}, + AdditionalProperties: &spec.SchemaOrBool{ + Allows: true, + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + "parametersRef": { + SchemaProps: spec.SchemaProps{ + Description: "ParametersRef is a reference to a resource that contains the configuration parameters corresponding to the Gateway. This is optional if the controller does not require any additional configuration.\n\nThis follows the same semantics as GatewayClass's `parametersRef`, but on a per-Gateway basis\n\nThe Gateway's GatewayClass may provide its own `parametersRef`. When both are specified, the merging behavior is implementation specific. It is generally recommended that GatewayClass provides defaults that can be overridden by a Gateway.\n\nIf the referent cannot be found, refers to an unsupported kind, or when the data within that resource is malformed, the Gateway SHOULD be rejected with the \"Accepted\" status condition set to \"False\" and an \"InvalidParameters\" reason.\n\nSupport: Implementation-specific", + Ref: ref("sigs.k8s.io/gateway-api/apis/v1.LocalParametersReference"), + }, + }, + }, + }, + }, + Dependencies: []string{ + "sigs.k8s.io/gateway-api/apis/v1.LocalParametersReference"}, + } +} + +func schema_sigsk8sio_gateway_api_apis_v1_GatewayList(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "GatewayList contains a list of Gateways.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", + }, + }, + "metadata": { + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"), + }, + }, + "items": { + SchemaProps: spec.SchemaProps{ + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("sigs.k8s.io/gateway-api/apis/v1.Gateway"), + }, + }, + }, + }, + }, + }, + Required: []string{"items"}, + }, + }, + Dependencies: []string{ + "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta", "sigs.k8s.io/gateway-api/apis/v1.Gateway"}, + } +} + +func schema_sigsk8sio_gateway_api_apis_v1_GatewaySpec(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "GatewaySpec defines the desired state of Gateway.\n\nNot all possible combinations of options specified in the Spec are valid. Some invalid configurations can be caught synchronously via CRD validation, but there are many cases that will require asynchronous signaling via the GatewayStatus block.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "gatewayClassName": { + SchemaProps: spec.SchemaProps{ + Description: "GatewayClassName used for this Gateway. This is the name of a GatewayClass resource.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "listeners": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-map-keys": []interface{}{ + "name", + }, + "x-kubernetes-list-type": "map", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "Listeners associated with this Gateway. Listeners define logical endpoints that are bound on this Gateway's addresses. At least one Listener MUST be specified.\n\n## Distinct Listeners\n\nEach Listener in a set of Listeners (for example, in a single Gateway) MUST be _distinct_, in that a traffic flow MUST be able to be assigned to exactly one listener. (This section uses \"set of Listeners\" rather than \"Listeners in a single Gateway\" because implementations MAY merge configuration from multiple Gateways onto a single data plane, and these rules _also_ apply in that case).\n\nPractically, this means that each listener in a set MUST have a unique combination of Port, Protocol, and, if supported by the protocol, Hostname.\n\nSome combinations of port, protocol, and TLS settings are considered Core support and MUST be supported by implementations based on the objects they support:\n\nHTTPRoute\n\n1. HTTPRoute, Port: 80, Protocol: HTTP 2. HTTPRoute, Port: 443, Protocol: HTTPS, TLS Mode: Terminate, TLS keypair provided\n\nTLSRoute\n\n1. TLSRoute, Port: 443, Protocol: TLS, TLS Mode: Passthrough\n\n\"Distinct\" Listeners have the following property:\n\n**The implementation can match inbound requests to a single distinct Listener**.\n\nWhen multiple Listeners share values for fields (for example, two Listeners with the same Port value), the implementation can match requests to only one of the Listeners using other Listener fields.\n\nWhen multiple listeners have the same value for the Protocol field, then each of the Listeners with matching Protocol values MUST have different values for other fields.\n\nThe set of fields that MUST be different for a Listener differs per protocol. The following rules define the rules for what fields MUST be considered for Listeners to be distinct with each protocol currently defined in the Gateway API spec.\n\nThe set of listeners that all share a protocol value MUST have _different_ values for _at least one_ of these fields to be distinct:\n\n* **HTTP, HTTPS, TLS**: Port, Hostname * **TCP, UDP**: Port\n\nOne **very** important rule to call out involves what happens when an implementation:\n\n* Supports TCP protocol Listeners, as well as HTTP, HTTPS, or TLS protocol\n Listeners, and\n* sees HTTP, HTTPS, or TLS protocols with the same `port` as one with TCP\n Protocol.\n\nIn this case all the Listeners that share a port with the TCP Listener are not distinct and so MUST NOT be accepted.\n\nIf an implementation does not support TCP Protocol Listeners, then the previous rule does not apply, and the TCP Listeners SHOULD NOT be accepted.\n\nNote that the `tls` field is not used for determining if a listener is distinct, because Listeners that _only_ differ on TLS config will still conflict in all cases.\n\n### Listeners that are distinct only by Hostname\n\nWhen the Listeners are distinct based only on Hostname, inbound request hostnames MUST match from the most specific to least specific Hostname values to choose the correct Listener and its associated set of Routes.\n\nExact matches MUST be processed before wildcard matches, and wildcard matches MUST be processed before fallback (empty Hostname value) matches. For example, `\"foo.example.com\"` takes precedence over `\"*.example.com\"`, and `\"*.example.com\"` takes precedence over `\"\"`.\n\nAdditionally, if there are multiple wildcard entries, more specific wildcard entries must be processed before less specific wildcard entries. For example, `\"*.foo.example.com\"` takes precedence over `\"*.example.com\"`.\n\nThe precise definition here is that the higher the number of dots in the hostname to the right of the wildcard character, the higher the precedence.\n\nThe wildcard character will match any number of characters _and dots_ to the left, however, so `\"*.example.com\"` will match both `\"foo.bar.example.com\"` _and_ `\"bar.example.com\"`.\n\n## Handling indistinct Listeners\n\nIf a set of Listeners contains Listeners that are not distinct, then those Listeners are _Conflicted_, and the implementation MUST set the \"Conflicted\" condition in the Listener Status to \"True\".\n\nThe words \"indistinct\" and \"conflicted\" are considered equivalent for the purpose of this documentation.\n\nImplementations MAY choose to accept a Gateway with some Conflicted Listeners only if they only accept the partial Listener set that contains no Conflicted Listeners.\n\nSpecifically, an implementation MAY accept a partial Listener set subject to the following rules:\n\n* The implementation MUST NOT pick one conflicting Listener as the winner.\n ALL indistinct Listeners must not be accepted for processing.\n* At least one distinct Listener MUST be present, or else the Gateway effectively\n contains _no_ Listeners, and must be rejected from processing as a whole.\n\nThe implementation MUST set a \"ListenersNotValid\" condition on the Gateway Status when the Gateway contains Conflicted Listeners whether or not they accept the Gateway. That Condition SHOULD clearly indicate in the Message which Listeners are conflicted, and which are Accepted. Additionally, the Listener status for those listeners SHOULD indicate which Listeners are conflicted and not Accepted.\n\n## General Listener behavior\n\nNote that, for all distinct Listeners, requests SHOULD match at most one Listener. For example, if Listeners are defined for \"foo.example.com\" and \"*.example.com\", a request to \"foo.example.com\" SHOULD only be routed using routes attached to the \"foo.example.com\" Listener (and not the \"*.example.com\" Listener).\n\nThis concept is known as \"Listener Isolation\", and it is an Extended feature of Gateway API. Implementations that do not support Listener Isolation MUST clearly document this, and MUST NOT claim support for the `GatewayHTTPListenerIsolation` feature.\n\nImplementations that _do_ support Listener Isolation SHOULD claim support for the Extended `GatewayHTTPListenerIsolation` feature and pass the associated conformance tests.\n\n## Compatible Listeners\n\nA Gateway's Listeners are considered _compatible_ if:\n\n1. They are distinct. 2. The implementation can serve them in compliance with the Addresses\n requirement that all Listeners are available on all assigned\n addresses.\n\nCompatible combinations in Extended support are expected to vary across implementations. A combination that is compatible for one implementation may not be compatible for another.\n\nFor example, an implementation that cannot serve both TCP and UDP listeners on the same address, or cannot mix HTTPS and generic TLS listens on the same port would not consider those cases compatible, even though they are distinct.\n\nImplementations MAY merge separate Gateways onto a single set of Addresses if all Listeners across all Gateways are compatible.\n\nIn a future release the MinItems=1 requirement MAY be dropped.\n\nSupport: Core", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("sigs.k8s.io/gateway-api/apis/v1.Listener"), + }, + }, + }, + }, + }, + "addresses": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "Addresses requested for this Gateway. This is optional and behavior can depend on the implementation. If a value is set in the spec and the requested address is invalid or unavailable, the implementation MUST indicate this in an associated entry in GatewayStatus.Conditions.\n\nThe Addresses field represents a request for the address(es) on the \"outside of the Gateway\", that traffic bound for this Gateway will use. This could be the IP address or hostname of an external load balancer or other networking infrastructure, or some other address that traffic will be sent to.\n\nIf no Addresses are specified, the implementation MAY schedule the Gateway in an implementation-specific manner, assigning an appropriate set of Addresses.\n\nThe implementation MUST bind all Listeners to every GatewayAddress that it assigns to the Gateway and add a corresponding entry in GatewayStatus.Addresses.\n\nSupport: Extended\n\n", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("sigs.k8s.io/gateway-api/apis/v1.GatewaySpecAddress"), + }, + }, + }, + }, + }, + "infrastructure": { + SchemaProps: spec.SchemaProps{ + Description: "Infrastructure defines infrastructure level attributes about this Gateway instance.\n\nSupport: Extended", + Ref: ref("sigs.k8s.io/gateway-api/apis/v1.GatewayInfrastructure"), + }, + }, + "allowedListeners": { + SchemaProps: spec.SchemaProps{ + Description: "AllowedListeners defines which ListenerSets can be attached to this Gateway. While this feature is experimental, the default value is to allow no ListenerSets.\n\n", + Ref: ref("sigs.k8s.io/gateway-api/apis/v1.AllowedListeners"), + }, + }, + "tls": { + SchemaProps: spec.SchemaProps{ + Description: "TLS specifies frontend and backend tls configuration for entire gateway.\n\nSupport: Extended\n\n", + Ref: ref("sigs.k8s.io/gateway-api/apis/v1.GatewayTLSConfig"), + }, + }, + "defaultScope": { + SchemaProps: spec.SchemaProps{ + Description: "DefaultScope, when set, configures the Gateway as a default Gateway, meaning it will dynamically and implicitly have Routes (e.g. HTTPRoute) attached to it, according to the scope configured here.\n\nIf unset (the default) or set to None, the Gateway will not act as a default Gateway; if set, the Gateway will claim any Route with a matching scope set in its UseDefaultGateway field, subject to the usual rules about which routes the Gateway can attach to.\n\nThink carefully before using this functionality! While the normal rules about which Route can apply are still enforced, it is simply easier for the wrong Route to be accidentally attached to this Gateway in this configuration. If the Gateway operator is not also the operator in control of the scope (e.g. namespace) with tight controls and checks on what kind of workloads and Routes get added in that scope, we strongly recommend not using this just because it seems convenient, and instead stick to direct Route attachment.\n\n", + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"gatewayClassName", "listeners"}, + }, + }, + Dependencies: []string{ + "sigs.k8s.io/gateway-api/apis/v1.AllowedListeners", "sigs.k8s.io/gateway-api/apis/v1.GatewayInfrastructure", "sigs.k8s.io/gateway-api/apis/v1.GatewaySpecAddress", "sigs.k8s.io/gateway-api/apis/v1.GatewayTLSConfig", "sigs.k8s.io/gateway-api/apis/v1.Listener"}, + } +} + +func schema_sigsk8sio_gateway_api_apis_v1_GatewaySpecAddress(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "GatewaySpecAddress describes an address that can be bound to a Gateway.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "type": { + SchemaProps: spec.SchemaProps{ + Description: "Type of the address.", + Type: []string{"string"}, + Format: "", + }, + }, + "value": { + SchemaProps: spec.SchemaProps{ + Description: "When a value is unspecified, an implementation SHOULD automatically assign an address matching the requested type if possible.\n\nIf an implementation does not support an empty value, they MUST set the \"Programmed\" condition in status to False with a reason of \"AddressNotAssigned\".\n\nExamples: `1.2.3.4`, `128::1`, `my-ip-address`.", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + } +} + +func schema_sigsk8sio_gateway_api_apis_v1_GatewayStatus(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "GatewayStatus defines the observed state of Gateway.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "addresses": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "Addresses lists the network addresses that have been bound to the Gateway.\n\nThis list may differ from the addresses provided in the spec under some conditions:\n\n * no addresses are specified, all addresses are dynamically assigned\n * a combination of specified and dynamic addresses are assigned\n * a specified address was unusable (e.g. already in use)\n\n", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("sigs.k8s.io/gateway-api/apis/v1.GatewayStatusAddress"), + }, + }, + }, + }, + }, + "conditions": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-map-keys": []interface{}{ + "type", + }, + "x-kubernetes-list-type": "map", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "Conditions describe the current conditions of the Gateway.\n\nImplementations should prefer to express Gateway conditions using the `GatewayConditionType` and `GatewayConditionReason` constants so that operators and tools can converge on a common vocabulary to describe Gateway state.\n\nKnown condition types are:\n\n* \"Accepted\" * \"Programmed\" * \"Ready\"\n\n Notes for implementors:\n\nConditions are a listType `map`, which means that they function like a map with a key of the `type` field _in the k8s apiserver_.\n\nThis means that implementations must obey some rules when updating this section.\n\n* Implementations MUST perform a read-modify-write cycle on this field\n before modifying it. That is, when modifying this field, implementations\n must be confident they have fetched the most recent version of this field,\n and ensure that changes they make are on that recent version.\n* Implementations MUST NOT remove or reorder Conditions that they are not\n directly responsible for. For example, if an implementation sees a Condition\n with type `special.io/SomeField`, it MUST NOT remove, change or update that\n Condition.\n* Implementations MUST always _merge_ changes into Conditions of the same Type,\n rather than creating more than one Condition of the same Type.\n* Implementations MUST always update the `observedGeneration` field of the\n Condition to the `metadata.generation` of the Gateway at the time of update creation.\n* If the `observedGeneration` of a Condition is _greater than_ the value the\n implementation knows about, then it MUST NOT perform the update on that Condition,\n but must wait for a future reconciliation and status update. (The assumption is that\n the implementation's copy of the object is stale and an update will be re-triggered\n if relevant.)\n", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.Condition"), + }, + }, + }, + }, + }, + "listeners": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-map-keys": []interface{}{ + "name", + }, + "x-kubernetes-list-type": "map", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "Listeners provide status for each unique listener port defined in the Spec.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("sigs.k8s.io/gateway-api/apis/v1.ListenerStatus"), + }, + }, + }, + }, + }, + }, + }, + }, + Dependencies: []string{ + "k8s.io/apimachinery/pkg/apis/meta/v1.Condition", "sigs.k8s.io/gateway-api/apis/v1.GatewayStatusAddress", "sigs.k8s.io/gateway-api/apis/v1.ListenerStatus"}, + } +} + +func schema_sigsk8sio_gateway_api_apis_v1_GatewayStatusAddress(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "GatewayStatusAddress describes a network address that is bound to a Gateway.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "type": { + SchemaProps: spec.SchemaProps{ + Description: "Type of the address.", + Type: []string{"string"}, + Format: "", + }, + }, + "value": { + SchemaProps: spec.SchemaProps{ + Description: "Value of the address. The validity of the values will depend on the type and support by the controller.\n\nExamples: `1.2.3.4`, `128::1`, `my-ip-address`.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"value"}, + }, + }, + } +} + +func schema_sigsk8sio_gateway_api_apis_v1_GatewayTLSConfig(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "GatewayTLSConfig specifies frontend and backend tls configuration for gateway.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "backend": { + SchemaProps: spec.SchemaProps{ + Description: "Backend describes TLS configuration for gateway when connecting to backends.\n\nNote that this contains only details for the Gateway as a TLS client, and does _not_ imply behavior about how to choose which backend should get a TLS connection. That is determined by the presence of a BackendTLSPolicy.\n\nSupport: Core\n\n", + Ref: ref("sigs.k8s.io/gateway-api/apis/v1.GatewayBackendTLS"), + }, + }, + "frontend": { + SchemaProps: spec.SchemaProps{ + Description: "Frontend describes TLS config when client connects to Gateway. Support: Core\n\n", + Ref: ref("sigs.k8s.io/gateway-api/apis/v1.FrontendTLSConfig"), + }, + }, + }, + }, + }, + Dependencies: []string{ + "sigs.k8s.io/gateway-api/apis/v1.FrontendTLSConfig", "sigs.k8s.io/gateway-api/apis/v1.GatewayBackendTLS"}, + } +} + +func schema_sigsk8sio_gateway_api_apis_v1_HTTPAuthConfig(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "HTTPAuthConfig contains configuration for communication with HTTP-speaking backends.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "path": { + SchemaProps: spec.SchemaProps{ + Description: "Path sets the prefix that paths from the client request will have added when forwarded to the authorization server.\n\nWhen empty or unspecified, no prefix is added.\n\nValid values are the same as the \"value\" regex for path values in the `match` stanza, and the validation regex will screen out invalid paths in the same way. Even with the validation, implementations MUST sanitize this input before using it directly.", + Type: []string{"string"}, + Format: "", + }, + }, + "allowedHeaders": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "set", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "AllowedRequestHeaders specifies what additional headers from the client request will be sent to the authorization server.\n\nThe following headers must always be sent to the authorization server, regardless of this setting:\n\n* `Host` * `Method` * `Path` * `Content-Length` * `Authorization`\n\nIf this list is empty, then only those headers must be sent.\n\nNote that `Content-Length` has a special behavior, in that the length sent must be correct for the actual request to the external authorization server - that is, it must reflect the actual number of bytes sent in the body of the request to the authorization server.\n\nSo if the `forwardBody` stanza is unset, or `forwardBody.maxSize` is set to `0`, then `Content-Length` must be `0`. If `forwardBody.maxSize` is set to anything other than `0`, then the `Content-Length` of the authorization request must be set to the actual number of bytes forwarded.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + "allowedResponseHeaders": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "set", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "AllowedResponseHeaders specifies what headers from the authorization response will be copied into the request to the backend.\n\nIf this list is empty, then all headers from the authorization server except Authority or Host must be copied.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + }, + }, + }, + } +} + +func schema_sigsk8sio_gateway_api_apis_v1_HTTPBackendRef(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "HTTPBackendRef defines how a HTTPRoute forwards a HTTP request.\n\nNote that when a namespace different than the local namespace is specified, a ReferenceGrant object is required in the referent namespace to allow that namespace's owner to accept the reference. See the ReferenceGrant documentation for details.\n\n\n\nWhen the BackendRef points to a Kubernetes Service, implementations SHOULD honor the appProtocol field if it is set for the target Service Port.\n\nImplementations supporting appProtocol SHOULD recognize the Kubernetes Standard Application Protocols defined in KEP-3726.\n\nIf a Service appProtocol isn't specified, an implementation MAY infer the backend protocol through its own means. Implementations MAY infer the protocol from the Route type referring to the backend Service.\n\nIf a Route is not able to send traffic to the backend using the specified protocol then the backend is considered invalid. Implementations MUST set the \"ResolvedRefs\" condition to \"False\" with the \"UnsupportedProtocol\" reason.\n\n", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "group": { + SchemaProps: spec.SchemaProps{ + Description: "Group is the group of the referent. For example, \"gateway.networking.k8s.io\". When unspecified or empty string, core API group is inferred.", + Type: []string{"string"}, + Format: "", + }, + }, + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is the Kubernetes resource kind of the referent. For example \"Service\".\n\nDefaults to \"Service\" when not specified.\n\nExternalName services can refer to CNAME DNS records that may live outside of the cluster and as such are difficult to reason about in terms of conformance. They also may not be safe to forward to (see CVE-2021-25740 for more information). Implementations SHOULD NOT support ExternalName Services.\n\nSupport: Core (Services with a type other than ExternalName)\n\nSupport: Implementation-specific (Services with type ExternalName)", + Type: []string{"string"}, + Format: "", + }, + }, + "name": { + SchemaProps: spec.SchemaProps{ + Description: "Name is the name of the referent.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "namespace": { + SchemaProps: spec.SchemaProps{ + Description: "Namespace is the namespace of the backend. When unspecified, the local namespace is inferred.\n\nNote that when a namespace different than the local namespace is specified, a ReferenceGrant object is required in the referent namespace to allow that namespace's owner to accept the reference. See the ReferenceGrant documentation for details.\n\nSupport: Core", + Type: []string{"string"}, + Format: "", + }, + }, + "port": { + SchemaProps: spec.SchemaProps{ + Description: "Port specifies the destination port number to use for this resource. Port is required when the referent is a Kubernetes Service. In this case, the port number is the service port number, not the target port. For other resources, destination port might be derived from the referent resource or this field.", + Type: []string{"integer"}, + Format: "int32", + }, + }, + "weight": { + SchemaProps: spec.SchemaProps{ + Description: "Weight specifies the proportion of requests forwarded to the referenced backend. This is computed as weight/(sum of all weights in this BackendRefs list). For non-zero values, there may be some epsilon from the exact proportion defined here depending on the precision an implementation supports. Weight is not a percentage and the sum of weights does not need to equal 100.\n\nIf only one backend is specified and it has a weight greater than 0, 100% of the traffic is forwarded to that backend. If weight is set to 0, no traffic should be forwarded for this entry. If unspecified, weight defaults to 1.\n\nSupport for this field varies based on the context where used.", + Type: []string{"integer"}, + Format: "int32", + }, + }, + "filters": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "Filters defined at this level should be executed if and only if the request is being forwarded to the backend defined here.\n\nSupport: Implementation-specific (For broader support of filters, use the Filters field in HTTPRouteRule.)", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("sigs.k8s.io/gateway-api/apis/v1.HTTPRouteFilter"), + }, + }, + }, + }, + }, + }, + Required: []string{"name"}, + }, + }, + Dependencies: []string{ + "sigs.k8s.io/gateway-api/apis/v1.HTTPRouteFilter"}, + } +} + +func schema_sigsk8sio_gateway_api_apis_v1_HTTPCORSFilter(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "HTTPCORSFilter defines a filter that that configures Cross-Origin Request Sharing (CORS).", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "allowOrigins": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "set", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "AllowOrigins indicates whether the response can be shared with requested resource from the given `Origin`.\n\nThe `Origin` consists of a scheme and a host, with an optional port, and takes the form `://(:)`.\n\nValid values for scheme are: `http` and `https`.\n\nValid values for port are any integer between 1 and 65535 (the list of available TCP/UDP ports). Note that, if not included, port `80` is assumed for `http` scheme origins, and port `443` is assumed for `https` origins. This may affect origin matching.\n\nThe host part of the origin may contain the wildcard character `*`. These wildcard characters behave as follows:\n\n* `*` is a greedy match to the _left_, including any number of\n DNS labels to the left of its position. This also means that\n `*` will include any number of period `.` characters to the\n left of its position.\n* A wildcard by itself matches all hosts.\n\nAn origin value that includes _only_ the `*` character indicates requests from all `Origin`s are allowed.\n\nWhen the `AllowOrigins` field is configured with multiple origins, it means the server supports clients from multiple origins. If the request `Origin` matches the configured allowed origins, the gateway must return the given `Origin` and sets value of the header `Access-Control-Allow-Origin` same as the `Origin` header provided by the client.\n\nThe status code of a successful response to a \"preflight\" request is always an OK status (i.e., 204 or 200).\n\nIf the request `Origin` does not match the configured allowed origins, the gateway returns 204/200 response but doesn't set the relevant cross-origin response headers. Alternatively, the gateway responds with 403 status to the \"preflight\" request is denied, coupled with omitting the CORS headers. The cross-origin request fails on the client side. Therefore, the client doesn't attempt the actual cross-origin request.\n\nThe `Access-Control-Allow-Origin` response header can only use `*` wildcard as value when the `AllowCredentials` field is false or omitted.\n\nWhen the `AllowCredentials` field is true and `AllowOrigins` field specified with the `*` wildcard, the gateway must return a single origin in the value of the `Access-Control-Allow-Origin` response header, instead of specifying the `*` wildcard. The value of the header `Access-Control-Allow-Origin` is same as the `Origin` header provided by the client.\n\nSupport: Extended", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + "allowCredentials": { + SchemaProps: spec.SchemaProps{ + Description: "AllowCredentials indicates whether the actual cross-origin request allows to include credentials.\n\nWhen set to true, the gateway will include the `Access-Control-Allow-Credentials` response header with value true (case-sensitive).\n\nWhen set to false or omitted the gateway will omit the header `Access-Control-Allow-Credentials` entirely (this is the standard CORS behavior).\n\nSupport: Extended", + Type: []string{"boolean"}, + Format: "", + }, + }, + "allowMethods": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "set", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "AllowMethods indicates which HTTP methods are supported for accessing the requested resource.\n\nValid values are any method defined by RFC9110, along with the special value `*`, which represents all HTTP methods are allowed.\n\nMethod names are case sensitive, so these values are also case-sensitive. (See https://www.rfc-editor.org/rfc/rfc2616#section-5.1.1)\n\nMultiple method names in the value of the `Access-Control-Allow-Methods` response header are separated by a comma (\",\").\n\nA CORS-safelisted method is a method that is `GET`, `HEAD`, or `POST`. (See https://fetch.spec.whatwg.org/#cors-safelisted-method) The CORS-safelisted methods are always allowed, regardless of whether they are specified in the `AllowMethods` field.\n\nWhen the `AllowMethods` field is configured with one or more methods, the gateway must return the `Access-Control-Allow-Methods` response header which value is present in the `AllowMethods` field.\n\nIf the HTTP method of the `Access-Control-Request-Method` request header is not included in the list of methods specified by the response header `Access-Control-Allow-Methods`, it will present an error on the client side.\n\nThe `Access-Control-Allow-Methods` response header can only use `*` wildcard as value when the `AllowCredentials` field is false or omitted.\n\nWhen the `AllowCredentials` field is true and `AllowMethods` field specified with the `*` wildcard, the gateway must specify one HTTP method in the value of the Access-Control-Allow-Methods response header. The value of the header `Access-Control-Allow-Methods` is same as the `Access-Control-Request-Method` header provided by the client. If the header `Access-Control-Request-Method` is not included in the request, the gateway will omit the `Access-Control-Allow-Methods` response header, instead of specifying the `*` wildcard. A Gateway implementation may choose to add implementation-specific default methods.\n\nSupport: Extended", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + "allowHeaders": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "set", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "AllowHeaders indicates which HTTP request headers are supported for accessing the requested resource.\n\nHeader names are not case sensitive.\n\nMultiple header names in the value of the `Access-Control-Allow-Headers` response header are separated by a comma (\",\").\n\nWhen the `AllowHeaders` field is configured with one or more headers, the gateway must return the `Access-Control-Allow-Headers` response header which value is present in the `AllowHeaders` field.\n\nIf any header name in the `Access-Control-Request-Headers` request header is not included in the list of header names specified by the response header `Access-Control-Allow-Headers`, it will present an error on the client side.\n\nIf any header name in the `Access-Control-Allow-Headers` response header does not recognize by the client, it will also occur an error on the client side.\n\nA wildcard indicates that the requests with all HTTP headers are allowed. The `Access-Control-Allow-Headers` response header can only use `*` wildcard as value when the `AllowCredentials` field is false or omitted.\n\nWhen the `AllowCredentials` field is true and `AllowHeaders` field specified with the `*` wildcard, the gateway must specify one or more HTTP headers in the value of the `Access-Control-Allow-Headers` response header. The value of the header `Access-Control-Allow-Headers` is same as the `Access-Control-Request-Headers` header provided by the client. If the header `Access-Control-Request-Headers` is not included in the request, the gateway will omit the `Access-Control-Allow-Headers` response header, instead of specifying the `*` wildcard. A Gateway implementation may choose to add implementation-specific default headers.\n\nSupport: Extended", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + "exposeHeaders": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "set", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "ExposeHeaders indicates which HTTP response headers can be exposed to client-side scripts in response to a cross-origin request.\n\nA CORS-safelisted response header is an HTTP header in a CORS response that it is considered safe to expose to the client scripts. The CORS-safelisted response headers include the following headers: `Cache-Control` `Content-Language` `Content-Length` `Content-Type` `Expires` `Last-Modified` `Pragma` (See https://fetch.spec.whatwg.org/#cors-safelisted-response-header-name) The CORS-safelisted response headers are exposed to client by default.\n\nWhen an HTTP header name is specified using the `ExposeHeaders` field, this additional header will be exposed as part of the response to the client.\n\nHeader names are not case sensitive.\n\nMultiple header names in the value of the `Access-Control-Expose-Headers` response header are separated by a comma (\",\").\n\nA wildcard indicates that the responses with all HTTP headers are exposed to clients. The `Access-Control-Expose-Headers` response header can only use `*` wildcard as value when the `AllowCredentials` field is false or omitted.\n\nSupport: Extended", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + "maxAge": { + SchemaProps: spec.SchemaProps{ + Description: "MaxAge indicates the duration (in seconds) for the client to cache the results of a \"preflight\" request.\n\nThe information provided by the `Access-Control-Allow-Methods` and `Access-Control-Allow-Headers` response headers can be cached by the client until the time specified by `Access-Control-Max-Age` elapses.\n\nThe default value of `Access-Control-Max-Age` response header is 5 (seconds).", + Type: []string{"integer"}, + Format: "int32", + }, + }, + }, + }, + }, + } +} + +func schema_sigsk8sio_gateway_api_apis_v1_HTTPExternalAuthFilter(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "HTTPExternalAuthFilter defines a filter that modifies requests by sending request details to an external authorization server.\n\nSupport: Extended Feature Name: HTTPRouteExternalAuth", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "protocol": { + SchemaProps: spec.SchemaProps{ + Description: "ExternalAuthProtocol describes which protocol to use when communicating with an ext_authz authorization server.\n\nWhen this is set to GRPC, each backend must use the Envoy ext_authz protocol on the port specified in `backendRefs`. Requests and responses are defined in the protobufs explained at: https://www.envoyproxy.io/docs/envoy/latest/api-v3/service/auth/v3/external_auth.proto\n\nWhen this is set to HTTP, each backend must respond with a `200` status code in on a successful authorization. Any other code is considered an authorization failure.\n\nFeature Names: GRPC Support - HTTPRouteExternalAuthGRPC HTTP Support - HTTPRouteExternalAuthHTTP", + Type: []string{"string"}, + Format: "", + }, + }, + "backendRef": { + SchemaProps: spec.SchemaProps{ + Description: "BackendRef is a reference to a backend to send authorization requests to.\n\nThe backend must speak the selected protocol (GRPC or HTTP) on the referenced port.\n\nIf the backend service requires TLS, use BackendTLSPolicy to tell the implementation to supply the TLS details to be used to connect to that backend.", + Default: map[string]interface{}{}, + Ref: ref("sigs.k8s.io/gateway-api/apis/v1.BackendObjectReference"), + }, + }, + "grpc": { + SchemaProps: spec.SchemaProps{ + Description: "GRPCAuthConfig contains configuration for communication with ext_authz protocol-speaking backends.\n\nIf unset, implementations must assume the default behavior for each included field is intended.", + Ref: ref("sigs.k8s.io/gateway-api/apis/v1.GRPCAuthConfig"), + }, + }, + "http": { + SchemaProps: spec.SchemaProps{ + Description: "HTTPAuthConfig contains configuration for communication with HTTP-speaking backends.\n\nIf unset, implementations must assume the default behavior for each included field is intended.", + Ref: ref("sigs.k8s.io/gateway-api/apis/v1.HTTPAuthConfig"), + }, + }, + "forwardBody": { + SchemaProps: spec.SchemaProps{ + Description: "ForwardBody controls if requests to the authorization server should include the body of the client request; and if so, how big that body is allowed to be.\n\nIt is expected that implementations will buffer the request body up to `forwardBody.maxSize` bytes. Bodies over that size must be rejected with a 4xx series error (413 or 403 are common examples), and fail processing of the filter.\n\nIf unset, or `forwardBody.maxSize` is set to `0`, then the body will not be forwarded.\n\nFeature Name: HTTPRouteExternalAuthForwardBody", + Ref: ref("sigs.k8s.io/gateway-api/apis/v1.ForwardBodyConfig"), + }, + }, + }, + Required: []string{"protocol", "backendRef"}, + }, + }, + Dependencies: []string{ + "sigs.k8s.io/gateway-api/apis/v1.BackendObjectReference", "sigs.k8s.io/gateway-api/apis/v1.ForwardBodyConfig", "sigs.k8s.io/gateway-api/apis/v1.GRPCAuthConfig", "sigs.k8s.io/gateway-api/apis/v1.HTTPAuthConfig"}, + } +} + +func schema_sigsk8sio_gateway_api_apis_v1_HTTPHeader(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "HTTPHeader represents an HTTP Header name and value as defined by RFC 7230.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "name": { + SchemaProps: spec.SchemaProps{ + Description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case-insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n\nIf multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "value": { + SchemaProps: spec.SchemaProps{ + Description: "Value is the value of HTTP Header to be matched.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"name", "value"}, + }, + }, + } +} + +func schema_sigsk8sio_gateway_api_apis_v1_HTTPHeaderFilter(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "HTTPHeaderFilter defines a filter that modifies the headers of an HTTP request or response. Only one action for a given header name is permitted. Filters specifying multiple actions of the same or different type for any one header name are invalid. Configuration to set or add multiple values for a header must use RFC 7230 header value formatting, separating each value with a comma.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "set": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-map-keys": []interface{}{ + "name", + }, + "x-kubernetes-list-type": "map", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "Set overwrites the request with the given header (name, value) before the action.\n\nInput:\n GET /foo HTTP/1.1\n my-header: foo\n\nConfig:\n set:\n - name: \"my-header\"\n value: \"bar\"\n\nOutput:\n GET /foo HTTP/1.1\n my-header: bar", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("sigs.k8s.io/gateway-api/apis/v1.HTTPHeader"), + }, + }, + }, + }, + }, + "add": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-map-keys": []interface{}{ + "name", + }, + "x-kubernetes-list-type": "map", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "Add adds the given header(s) (name, value) to the request before the action. It appends to any existing values associated with the header name.\n\nInput:\n GET /foo HTTP/1.1\n my-header: foo\n\nConfig:\n add:\n - name: \"my-header\"\n value: \"bar,baz\"\n\nOutput:\n GET /foo HTTP/1.1\n my-header: foo,bar,baz", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("sigs.k8s.io/gateway-api/apis/v1.HTTPHeader"), + }, + }, + }, + }, + }, + "remove": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "set", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "Remove the given header(s) from the HTTP request before the action. The value of Remove is a list of HTTP header names. Note that the header names are case-insensitive (see https://datatracker.ietf.org/doc/html/rfc2616#section-4.2).\n\nInput:\n GET /foo HTTP/1.1\n my-header1: foo\n my-header2: bar\n my-header3: baz\n\nConfig:\n remove: [\"my-header1\", \"my-header3\"]\n\nOutput:\n GET /foo HTTP/1.1\n my-header2: bar", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + }, + }, + }, + Dependencies: []string{ + "sigs.k8s.io/gateway-api/apis/v1.HTTPHeader"}, + } +} + +func schema_sigsk8sio_gateway_api_apis_v1_HTTPHeaderMatch(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "HTTPHeaderMatch describes how to select a HTTP route by matching HTTP request headers.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "type": { + SchemaProps: spec.SchemaProps{ + Description: "Type specifies how to match against the value of the header.\n\nSupport: Core (Exact)\n\nSupport: Implementation-specific (RegularExpression)\n\nSince RegularExpression HeaderMatchType has implementation-specific conformance, implementations can support POSIX, PCRE or any other dialects of regular expressions. Please read the implementation's documentation to determine the supported dialect.", + Type: []string{"string"}, + Format: "", + }, + }, + "name": { + SchemaProps: spec.SchemaProps{ + Description: "Name is the name of the HTTP Header to be matched. Name matching MUST be case-insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2).\n\nIf multiple entries specify equivalent header names, only the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent header name MUST be ignored. Due to the case-insensitivity of header names, \"foo\" and \"Foo\" are considered equivalent.\n\nWhen a header is repeated in an HTTP request, it is implementation-specific behavior as to how this is represented. Generally, proxies should follow the guidance from the RFC: https://www.rfc-editor.org/rfc/rfc7230.html#section-3.2.2 regarding processing a repeated header, with special handling for \"Set-Cookie\".", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "value": { + SchemaProps: spec.SchemaProps{ + Description: "Value is the value of HTTP Header to be matched.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"name", "value"}, + }, + }, + } +} + +func schema_sigsk8sio_gateway_api_apis_v1_HTTPPathMatch(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "HTTPPathMatch describes how to select a HTTP route by matching the HTTP request path.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "type": { + SchemaProps: spec.SchemaProps{ + Description: "Type specifies how to match against the path Value.\n\nSupport: Core (Exact, PathPrefix)\n\nSupport: Implementation-specific (RegularExpression)", + Type: []string{"string"}, + Format: "", + }, + }, + "value": { + SchemaProps: spec.SchemaProps{ + Description: "Value of the HTTP path to match against.", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + } +} + +func schema_sigsk8sio_gateway_api_apis_v1_HTTPPathModifier(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "HTTPPathModifier defines configuration for path modifiers.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "type": { + SchemaProps: spec.SchemaProps{ + Description: "Type defines the type of path modifier. Additional types may be added in a future release of the API.\n\nNote that values may be added to this enum, implementations must ensure that unknown values will not cause a crash.\n\nUnknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "replaceFullPath": { + SchemaProps: spec.SchemaProps{ + Description: "ReplaceFullPath specifies the value with which to replace the full path of a request during a rewrite or redirect.", + Type: []string{"string"}, + Format: "", + }, + }, + "replacePrefixMatch": { + SchemaProps: spec.SchemaProps{ + Description: "ReplacePrefixMatch specifies the value with which to replace the prefix match of a request during a rewrite or redirect. For example, a request to \"/foo/bar\" with a prefix match of \"/foo\" and a ReplacePrefixMatch of \"/xyz\" would be modified to \"/xyz/bar\".\n\nNote that this matches the behavior of the PathPrefix match type. This matches full path elements. A path element refers to the list of labels in the path split by the `/` separator. When specified, a trailing `/` is ignored. For example, the paths `/abc`, `/abc/`, and `/abc/def` would all match the prefix `/abc`, but the path `/abcd` would not.\n\nReplacePrefixMatch is only compatible with a `PathPrefix` HTTPRouteMatch. Using any other HTTPRouteMatch type on the same HTTPRouteRule will result in the implementation setting the Accepted Condition for the Route to `status: False`.\n\nRequest Path | Prefix Match | Replace Prefix | Modified Path -------------|--------------|----------------|---------- /foo/bar | /foo | /xyz | /xyz/bar /foo/bar | /foo | /xyz/ | /xyz/bar /foo/bar | /foo/ | /xyz | /xyz/bar /foo/bar | /foo/ | /xyz/ | /xyz/bar /foo | /foo | /xyz | /xyz /foo/ | /foo | /xyz | /xyz/ /foo/bar | /foo | | /bar /foo/ | /foo | | / /foo | /foo | | / /foo/ | /foo | / | / /foo | /foo | / | /", + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"type"}, + }, + }, + } +} + +func schema_sigsk8sio_gateway_api_apis_v1_HTTPQueryParamMatch(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "HTTPQueryParamMatch describes how to select a HTTP route by matching HTTP query parameters.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "type": { + SchemaProps: spec.SchemaProps{ + Description: "Type specifies how to match against the value of the query parameter.\n\nSupport: Extended (Exact)\n\nSupport: Implementation-specific (RegularExpression)\n\nSince RegularExpression QueryParamMatchType has Implementation-specific conformance, implementations can support POSIX, PCRE or any other dialects of regular expressions. Please read the implementation's documentation to determine the supported dialect.", + Type: []string{"string"}, + Format: "", + }, + }, + "name": { + SchemaProps: spec.SchemaProps{ + Description: "Name is the name of the HTTP query param to be matched. This must be an exact string match. (See https://tools.ietf.org/html/rfc7230#section-2.7.3).\n\nIf multiple entries specify equivalent query param names, only the first entry with an equivalent name MUST be considered for a match. Subsequent entries with an equivalent query param name MUST be ignored.\n\nIf a query param is repeated in an HTTP request, the behavior is purposely left undefined, since different data planes have different capabilities. However, it is *recommended* that implementations should match against the first value of the param if the data plane supports it, as this behavior is expected in other load balancing contexts outside of the Gateway API.\n\nUsers SHOULD NOT route traffic based on repeated query params to guard themselves against potential differences in the implementations.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "value": { + SchemaProps: spec.SchemaProps{ + Description: "Value is the value of HTTP query param to be matched.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"name", "value"}, + }, + }, + } +} + +func schema_sigsk8sio_gateway_api_apis_v1_HTTPRequestMirrorFilter(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "HTTPRequestMirrorFilter defines configuration for the RequestMirror filter.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "backendRef": { + SchemaProps: spec.SchemaProps{ + Description: "BackendRef references a resource where mirrored requests are sent.\n\nMirrored requests must be sent only to a single destination endpoint within this BackendRef, irrespective of how many endpoints are present within this BackendRef.\n\nIf the referent cannot be found, this BackendRef is invalid and must be dropped from the Gateway. The controller must ensure the \"ResolvedRefs\" condition on the Route status is set to `status: False` and not configure this backend in the underlying implementation.\n\nIf there is a cross-namespace reference to an *existing* object that is not allowed by a ReferenceGrant, the controller must ensure the \"ResolvedRefs\" condition on the Route is set to `status: False`, with the \"RefNotPermitted\" reason and not configure this backend in the underlying implementation.\n\nIn either error case, the Message of the `ResolvedRefs` Condition should be used to provide more detail about the problem.\n\nSupport: Extended for Kubernetes Service\n\nSupport: Implementation-specific for any other resource", + Default: map[string]interface{}{}, + Ref: ref("sigs.k8s.io/gateway-api/apis/v1.BackendObjectReference"), + }, + }, + "percent": { + SchemaProps: spec.SchemaProps{ + Description: "Percent represents the percentage of requests that should be mirrored to BackendRef. Its minimum value is 0 (indicating 0% of requests) and its maximum value is 100 (indicating 100% of requests).\n\nOnly one of Fraction or Percent may be specified. If neither field is specified, 100% of requests will be mirrored.", + Type: []string{"integer"}, + Format: "int32", + }, + }, + "fraction": { + SchemaProps: spec.SchemaProps{ + Description: "Fraction represents the fraction of requests that should be mirrored to BackendRef.\n\nOnly one of Fraction or Percent may be specified. If neither field is specified, 100% of requests will be mirrored.", + Ref: ref("sigs.k8s.io/gateway-api/apis/v1.Fraction"), + }, + }, + }, + Required: []string{"backendRef"}, + }, + }, + Dependencies: []string{ + "sigs.k8s.io/gateway-api/apis/v1.BackendObjectReference", "sigs.k8s.io/gateway-api/apis/v1.Fraction"}, + } +} + +func schema_sigsk8sio_gateway_api_apis_v1_HTTPRequestRedirectFilter(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "HTTPRequestRedirect defines a filter that redirects a request. This filter MUST NOT be used on the same Route rule as a HTTPURLRewrite filter.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "scheme": { + SchemaProps: spec.SchemaProps{ + Description: "Scheme is the scheme to be used in the value of the `Location` header in the response. When empty, the scheme of the request is used.\n\nScheme redirects can affect the port of the redirect, for more information, refer to the documentation for the port field of this filter.\n\nNote that values may be added to this enum, implementations must ensure that unknown values will not cause a crash.\n\nUnknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`.\n\nSupport: Extended", + Type: []string{"string"}, + Format: "", + }, + }, + "hostname": { + SchemaProps: spec.SchemaProps{ + Description: "Hostname is the hostname to be used in the value of the `Location` header in the response. When empty, the hostname in the `Host` header of the request is used.\n\nSupport: Core", + Type: []string{"string"}, + Format: "", + }, + }, + "path": { + SchemaProps: spec.SchemaProps{ + Description: "Path defines parameters used to modify the path of the incoming request. The modified path is then used to construct the `Location` header. When empty, the request path is used as-is.\n\nSupport: Extended", + Ref: ref("sigs.k8s.io/gateway-api/apis/v1.HTTPPathModifier"), + }, + }, + "port": { + SchemaProps: spec.SchemaProps{ + Description: "Port is the port to be used in the value of the `Location` header in the response.\n\nIf no port is specified, the redirect port MUST be derived using the following rules:\n\n* If redirect scheme is not-empty, the redirect port MUST be the well-known\n port associated with the redirect scheme. Specifically \"http\" to port 80\n and \"https\" to port 443. If the redirect scheme does not have a\n well-known port, the listener port of the Gateway SHOULD be used.\n* If redirect scheme is empty, the redirect port MUST be the Gateway\n Listener port.\n\nImplementations SHOULD NOT add the port number in the 'Location' header in the following cases:\n\n* A Location header that will use HTTP (whether that is determined via\n the Listener protocol or the Scheme field) _and_ use port 80.\n* A Location header that will use HTTPS (whether that is determined via\n the Listener protocol or the Scheme field) _and_ use port 443.\n\nSupport: Extended", + Type: []string{"integer"}, + Format: "int32", + }, + }, + "statusCode": { + SchemaProps: spec.SchemaProps{ + Description: "StatusCode is the HTTP status code to be used in response.\n\nNote that values may be added to this enum, implementations must ensure that unknown values will not cause a crash.\n\nUnknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`.\n\nSupport: Core", + Type: []string{"integer"}, + Format: "int32", + }, + }, + }, + }, + }, + Dependencies: []string{ + "sigs.k8s.io/gateway-api/apis/v1.HTTPPathModifier"}, + } +} + +func schema_sigsk8sio_gateway_api_apis_v1_HTTPRoute(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "HTTPRoute provides a way to route HTTP requests. This includes the capability to match requests by hostname, path, header, or query param. Filters can be used to specify additional processing steps. Backends specify where matching requests should be routed.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", + }, + }, + "metadata": { + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"), + }, + }, + "spec": { + SchemaProps: spec.SchemaProps{ + Description: "Spec defines the desired state of HTTPRoute.", + Default: map[string]interface{}{}, + Ref: ref("sigs.k8s.io/gateway-api/apis/v1.HTTPRouteSpec"), + }, + }, + "status": { + SchemaProps: spec.SchemaProps{ + Description: "Status defines the current state of HTTPRoute.", + Default: map[string]interface{}{}, + Ref: ref("sigs.k8s.io/gateway-api/apis/v1.HTTPRouteStatus"), + }, + }, + }, + Required: []string{"spec"}, + }, + }, + Dependencies: []string{ + "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta", "sigs.k8s.io/gateway-api/apis/v1.HTTPRouteSpec", "sigs.k8s.io/gateway-api/apis/v1.HTTPRouteStatus"}, + } +} + +func schema_sigsk8sio_gateway_api_apis_v1_HTTPRouteFilter(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "HTTPRouteFilter defines processing steps that must be completed during the request or response lifecycle. HTTPRouteFilters are meant as an extension point to express processing that may be done in Gateway implementations. Some examples include request or response modification, implementing authentication strategies, rate-limiting, and traffic shaping. API guarantee/conformance is defined based on the type of the filter.\n\n ", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "type": { + SchemaProps: spec.SchemaProps{ + Description: "Type identifies the type of filter to apply. As with other API fields, types are classified into three conformance levels:\n\n- Core: Filter types and their corresponding configuration defined by\n \"Support: Core\" in this package, e.g. \"RequestHeaderModifier\". All\n implementations must support core filters.\n\n- Extended: Filter types and their corresponding configuration defined by\n \"Support: Extended\" in this package, e.g. \"RequestMirror\". Implementers\n are encouraged to support extended filters.\n\n- Implementation-specific: Filters that are defined and supported by\n specific vendors.\n In the future, filters showing convergence in behavior across multiple\n implementations will be considered for inclusion in extended or core\n conformance levels. Filter-specific configuration for such filters\n is specified using the ExtensionRef field. `Type` should be set to\n \"ExtensionRef\" for custom filters.\n\nImplementers are encouraged to define custom implementation types to extend the core API with implementation-specific behavior.\n\nIf a reference to a custom filter type cannot be resolved, the filter MUST NOT be skipped. Instead, requests that would have been processed by that filter MUST receive a HTTP error response.\n\nNote that values may be added to this enum, implementations must ensure that unknown values will not cause a crash.\n\nUnknown values here must result in the implementation setting the Accepted Condition for the Route to `status: False`, with a Reason of `UnsupportedValue`.\n\n", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "requestHeaderModifier": { + SchemaProps: spec.SchemaProps{ + Description: "RequestHeaderModifier defines a schema for a filter that modifies request headers.\n\nSupport: Core", + Ref: ref("sigs.k8s.io/gateway-api/apis/v1.HTTPHeaderFilter"), + }, + }, + "responseHeaderModifier": { + SchemaProps: spec.SchemaProps{ + Description: "ResponseHeaderModifier defines a schema for a filter that modifies response headers.\n\nSupport: Extended", + Ref: ref("sigs.k8s.io/gateway-api/apis/v1.HTTPHeaderFilter"), + }, + }, + "requestMirror": { + SchemaProps: spec.SchemaProps{ + Description: "RequestMirror defines a schema for a filter that mirrors requests. Requests are sent to the specified destination, but responses from that destination are ignored.\n\nThis filter can be used multiple times within the same rule. Note that not all implementations will be able to support mirroring to multiple backends.\n\nSupport: Extended", + Ref: ref("sigs.k8s.io/gateway-api/apis/v1.HTTPRequestMirrorFilter"), + }, + }, + "requestRedirect": { + SchemaProps: spec.SchemaProps{ + Description: "RequestRedirect defines a schema for a filter that responds to the request with an HTTP redirection.\n\nSupport: Core", + Ref: ref("sigs.k8s.io/gateway-api/apis/v1.HTTPRequestRedirectFilter"), + }, + }, + "urlRewrite": { + SchemaProps: spec.SchemaProps{ + Description: "URLRewrite defines a schema for a filter that modifies a request during forwarding.\n\nSupport: Extended", + Ref: ref("sigs.k8s.io/gateway-api/apis/v1.HTTPURLRewriteFilter"), + }, + }, + "cors": { + SchemaProps: spec.SchemaProps{ + Description: "CORS defines a schema for a filter that responds to the cross-origin request based on HTTP response header.\n\nSupport: Extended\n\n", + Ref: ref("sigs.k8s.io/gateway-api/apis/v1.HTTPCORSFilter"), + }, + }, + "externalAuth": { + SchemaProps: spec.SchemaProps{ + Description: "ExternalAuth configures settings related to sending request details to an external auth service. The external service MUST authenticate the request, and MAY authorize the request as well.\n\nIf there is any problem communicating with the external service, this filter MUST fail closed.\n\nSupport: Extended\n\n", + Ref: ref("sigs.k8s.io/gateway-api/apis/v1.HTTPExternalAuthFilter"), + }, + }, + "extensionRef": { + SchemaProps: spec.SchemaProps{ + Description: "ExtensionRef is an optional, implementation-specific extension to the \"filter\" behavior. For example, resource \"myroutefilter\" in group \"networking.example.net\"). ExtensionRef MUST NOT be used for core and extended filters.\n\nThis filter can be used multiple times within the same rule.\n\nSupport: Implementation-specific", + Ref: ref("sigs.k8s.io/gateway-api/apis/v1.LocalObjectReference"), + }, + }, + }, + Required: []string{"type"}, + }, + }, + Dependencies: []string{ + "sigs.k8s.io/gateway-api/apis/v1.HTTPCORSFilter", "sigs.k8s.io/gateway-api/apis/v1.HTTPExternalAuthFilter", "sigs.k8s.io/gateway-api/apis/v1.HTTPHeaderFilter", "sigs.k8s.io/gateway-api/apis/v1.HTTPRequestMirrorFilter", "sigs.k8s.io/gateway-api/apis/v1.HTTPRequestRedirectFilter", "sigs.k8s.io/gateway-api/apis/v1.HTTPURLRewriteFilter", "sigs.k8s.io/gateway-api/apis/v1.LocalObjectReference"}, + } +} + +func schema_sigsk8sio_gateway_api_apis_v1_HTTPRouteList(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "HTTPRouteList contains a list of HTTPRoute.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", + }, + }, + "metadata": { + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"), + }, + }, + "items": { + SchemaProps: spec.SchemaProps{ + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("sigs.k8s.io/gateway-api/apis/v1.HTTPRoute"), + }, + }, + }, + }, + }, + }, + Required: []string{"items"}, + }, + }, + Dependencies: []string{ + "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta", "sigs.k8s.io/gateway-api/apis/v1.HTTPRoute"}, + } +} + +func schema_sigsk8sio_gateway_api_apis_v1_HTTPRouteMatch(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "HTTPRouteMatch defines the predicate used to match requests to a given action. Multiple match types are ANDed together, i.e. the match will evaluate to true only if all conditions are satisfied.\n\nFor example, the match below will match a HTTP request only if its path starts with `/foo` AND it contains the `version: v1` header:\n\n``` match:\n\n\tpath:\n\t value: \"/foo\"\n\theaders:\n\t- name: \"version\"\n\t value \"v1\"\n\n```", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "path": { + SchemaProps: spec.SchemaProps{ + Description: "Path specifies a HTTP request path matcher. If this field is not specified, a default prefix match on the \"/\" path is provided.", + Ref: ref("sigs.k8s.io/gateway-api/apis/v1.HTTPPathMatch"), + }, + }, + "headers": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-map-keys": []interface{}{ + "name", + }, + "x-kubernetes-list-type": "map", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "Headers specifies HTTP request header matchers. Multiple match values are ANDed together, meaning, a request must match all the specified headers to select the route.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("sigs.k8s.io/gateway-api/apis/v1.HTTPHeaderMatch"), + }, + }, + }, + }, + }, + "queryParams": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-map-keys": []interface{}{ + "name", + }, + "x-kubernetes-list-type": "map", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "QueryParams specifies HTTP query parameter matchers. Multiple match values are ANDed together, meaning, a request must match all the specified query parameters to select the route.\n\nSupport: Extended", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("sigs.k8s.io/gateway-api/apis/v1.HTTPQueryParamMatch"), + }, + }, + }, + }, + }, + "method": { + SchemaProps: spec.SchemaProps{ + Description: "Method specifies HTTP method matcher. When specified, this route will be matched only if the request has the specified method.\n\nSupport: Extended", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + Dependencies: []string{ + "sigs.k8s.io/gateway-api/apis/v1.HTTPHeaderMatch", "sigs.k8s.io/gateway-api/apis/v1.HTTPPathMatch", "sigs.k8s.io/gateway-api/apis/v1.HTTPQueryParamMatch"}, + } +} + +func schema_sigsk8sio_gateway_api_apis_v1_HTTPRouteRetry(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "HTTPRouteRetry defines retry configuration for an HTTPRoute.\n\nImplementations SHOULD retry on connection errors (disconnect, reset, timeout, TCP failure) if a retry stanza is configured.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "codes": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "Codes defines the HTTP response status codes for which a backend request should be retried.\n\nSupport: Extended", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: 0, + Type: []string{"integer"}, + Format: "int32", + }, + }, + }, + }, + }, + "attempts": { + SchemaProps: spec.SchemaProps{ + Description: "Attempts specifies the maximum number of times an individual request from the gateway to a backend should be retried.\n\nIf the maximum number of retries has been attempted without a successful response from the backend, the Gateway MUST return an error.\n\nWhen this field is unspecified, the number of times to attempt to retry a backend request is implementation-specific.\n\nSupport: Extended", + Type: []string{"integer"}, + Format: "int32", + }, + }, + "backoff": { + SchemaProps: spec.SchemaProps{ + Description: "Backoff specifies the minimum duration a Gateway should wait between retry attempts and is represented in Gateway API Duration formatting.\n\nFor example, setting the `rules[].retry.backoff` field to the value `100ms` will cause a backend request to first be retried approximately 100 milliseconds after timing out or receiving a response code configured to be retryable.\n\nAn implementation MAY use an exponential or alternative backoff strategy for subsequent retry attempts, MAY cap the maximum backoff duration to some amount greater than the specified minimum, and MAY add arbitrary jitter to stagger requests, as long as unsuccessful backend requests are not retried before the configured minimum duration.\n\nIf a Request timeout (`rules[].timeouts.request`) is configured on the route, the entire duration of the initial request and any retry attempts MUST not exceed the Request timeout duration. If any retry attempts are still in progress when the Request timeout duration has been reached, these SHOULD be canceled if possible and the Gateway MUST immediately return a timeout error.\n\nIf a BackendRequest timeout (`rules[].timeouts.backendRequest`) is configured on the route, any retry attempts which reach the configured BackendRequest timeout duration without a response SHOULD be canceled if possible and the Gateway should wait for at least the specified backoff duration before attempting to retry the backend request again.\n\nIf a BackendRequest timeout is _not_ configured on the route, retry attempts MAY time out after an implementation default duration, or MAY remain pending until a configured Request timeout or implementation default duration for total request time is reached.\n\nWhen this field is unspecified, the time to wait between retry attempts is implementation-specific.\n\nSupport: Extended", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + } +} + +func schema_sigsk8sio_gateway_api_apis_v1_HTTPRouteRule(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "HTTPRouteRule defines semantics for matching an HTTP request based on conditions (matches), processing it (filters), and forwarding the request to an API object (backendRefs).", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "name": { + SchemaProps: spec.SchemaProps{ + Description: "Name is the name of the route rule. This name MUST be unique within a Route if it is set.\n\nSupport: Extended", + Type: []string{"string"}, + Format: "", + }, + }, + "matches": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "Matches define conditions used for matching the rule against incoming HTTP requests. Each match is independent, i.e. this rule will be matched if **any** one of the matches is satisfied.\n\nFor example, take the following matches configuration:\n\n``` matches: - path:\n value: \"/foo\"\n headers:\n - name: \"version\"\n value: \"v2\"\n- path:\n value: \"/v2/foo\"\n```\n\nFor a request to match against this rule, a request must satisfy EITHER of the two conditions:\n\n- path prefixed with `/foo` AND contains the header `version: v2` - path prefix of `/v2/foo`\n\nSee the documentation for HTTPRouteMatch on how to specify multiple match conditions that should be ANDed together.\n\nIf no matches are specified, the default is a prefix path match on \"/\", which has the effect of matching every HTTP request.\n\nProxy or Load Balancer routing configuration generated from HTTPRoutes MUST prioritize matches based on the following criteria, continuing on ties. Across all rules specified on applicable Routes, precedence must be given to the match having:\n\n* \"Exact\" path match. * \"Prefix\" path match with largest number of characters. * Method match. * Largest number of header matches. * Largest number of query param matches.\n\nNote: The precedence of RegularExpression path matches are implementation-specific.\n\nIf ties still exist across multiple Routes, matching precedence MUST be determined in order of the following criteria, continuing on ties:\n\n* The oldest Route based on creation timestamp. * The Route appearing first in alphabetical order by\n \"{namespace}/{name}\".\n\nIf ties still exist within an HTTPRoute, matching precedence MUST be granted to the FIRST matching rule (in list order) with a match meeting the above criteria.\n\nWhen no rules matching a request have been successfully attached to the parent a request is coming from, a HTTP 404 status code MUST be returned.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("sigs.k8s.io/gateway-api/apis/v1.HTTPRouteMatch"), + }, + }, + }, + }, + }, + "filters": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "Filters define the filters that are applied to requests that match this rule.\n\nWherever possible, implementations SHOULD implement filters in the order they are specified.\n\nImplementations MAY choose to implement this ordering strictly, rejecting any combination or order of filters that cannot be supported. If implementations choose a strict interpretation of filter ordering, they MUST clearly document that behavior.\n\nTo reject an invalid combination or order of filters, implementations SHOULD consider the Route Rules with this configuration invalid. If all Route Rules in a Route are invalid, the entire Route would be considered invalid. If only a portion of Route Rules are invalid, implementations MUST set the \"PartiallyInvalid\" condition for the Route.\n\nConformance-levels at this level are defined based on the type of filter:\n\n- ALL core filters MUST be supported by all implementations. - Implementers are encouraged to support extended filters. - Implementation-specific custom filters have no API guarantees across\n implementations.\n\nSpecifying the same filter multiple times is not supported unless explicitly indicated in the filter.\n\nAll filters are expected to be compatible with each other except for the URLRewrite and RequestRedirect filters, which may not be combined. If an implementation cannot support other combinations of filters, they must clearly document that limitation. In cases where incompatible or unsupported filters are specified and cause the `Accepted` condition to be set to status `False`, implementations may use the `IncompatibleFilters` reason to specify this configuration error.\n\nSupport: Core", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("sigs.k8s.io/gateway-api/apis/v1.HTTPRouteFilter"), + }, + }, + }, + }, + }, + "backendRefs": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "BackendRefs defines the backend(s) where matching requests should be sent.\n\nFailure behavior here depends on how many BackendRefs are specified and how many are invalid.\n\nIf *all* entries in BackendRefs are invalid, and there are also no filters specified in this route rule, *all* traffic which matches this rule MUST receive a 500 status code.\n\nSee the HTTPBackendRef definition for the rules about what makes a single HTTPBackendRef invalid.\n\nWhen a HTTPBackendRef is invalid, 500 status codes MUST be returned for requests that would have otherwise been routed to an invalid backend. If multiple backends are specified, and some are invalid, the proportion of requests that would otherwise have been routed to an invalid backend MUST receive a 500 status code.\n\nFor example, if two backends are specified with equal weights, and one is invalid, 50 percent of traffic must receive a 500. Implementations may choose how that 50 percent is determined.\n\nWhen a HTTPBackendRef refers to a Service that has no ready endpoints, implementations SHOULD return a 503 for requests to that backend instead. If an implementation chooses to do this, all of the above rules for 500 responses MUST also apply for responses that return a 503.\n\nSupport: Core for Kubernetes Service\n\nSupport: Extended for Kubernetes ServiceImport\n\nSupport: Implementation-specific for any other resource\n\nSupport for weight: Core", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("sigs.k8s.io/gateway-api/apis/v1.HTTPBackendRef"), + }, + }, + }, + }, + }, + "timeouts": { + SchemaProps: spec.SchemaProps{ + Description: "Timeouts defines the timeouts that can be configured for an HTTP request.\n\nSupport: Extended", + Ref: ref("sigs.k8s.io/gateway-api/apis/v1.HTTPRouteTimeouts"), + }, + }, + "retry": { + SchemaProps: spec.SchemaProps{ + Description: "Retry defines the configuration for when to retry an HTTP request.\n\nSupport: Extended\n\n", + Ref: ref("sigs.k8s.io/gateway-api/apis/v1.HTTPRouteRetry"), + }, + }, + "sessionPersistence": { + SchemaProps: spec.SchemaProps{ + Description: "SessionPersistence defines and configures session persistence for the route rule.\n\nSupport: Extended\n\n", + Ref: ref("sigs.k8s.io/gateway-api/apis/v1.SessionPersistence"), + }, + }, + }, + }, + }, + Dependencies: []string{ + "sigs.k8s.io/gateway-api/apis/v1.HTTPBackendRef", "sigs.k8s.io/gateway-api/apis/v1.HTTPRouteFilter", "sigs.k8s.io/gateway-api/apis/v1.HTTPRouteMatch", "sigs.k8s.io/gateway-api/apis/v1.HTTPRouteRetry", "sigs.k8s.io/gateway-api/apis/v1.HTTPRouteTimeouts", "sigs.k8s.io/gateway-api/apis/v1.SessionPersistence"}, + } +} + +func schema_sigsk8sio_gateway_api_apis_v1_HTTPRouteSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "HTTPRouteSpec defines the desired state of HTTPRoute", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "parentRefs": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "ParentRefs references the resources (usually Gateways) that a Route wants to be attached to. Note that the referenced parent resource needs to allow this for the attachment to be complete. For Gateways, that means the Gateway needs to allow attachment from Routes of this kind and namespace. For Services, that means the Service must either be in the same namespace for a \"producer\" route, or the mesh implementation must support and allow \"consumer\" routes for the referenced Service. ReferenceGrant is not applicable for governing ParentRefs to Services - it is not possible to create a \"producer\" route for a Service in a different namespace from the Route.\n\nThere are two kinds of parent resources with \"Core\" support:\n\n* Gateway (Gateway conformance profile) * Service (Mesh conformance profile, ClusterIP Services only)\n\nThis API may be extended in the future to support additional kinds of parent resources.\n\nParentRefs must be _distinct_. This means either that:\n\n* They select different objects. If this is the case, then parentRef\n entries are distinct. In terms of fields, this means that the\n multi-part key defined by `group`, `kind`, `namespace`, and `name` must\n be unique across all parentRef entries in the Route.\n* They do not select different objects, but for each optional field used,\n each ParentRef that selects the same object must set the same set of\n optional fields to different values. If one ParentRef sets a\n combination of optional fields, all must set the same combination.\n\nSome examples:\n\n* If one ParentRef sets `sectionName`, all ParentRefs referencing the\n same object must also set `sectionName`.\n* If one ParentRef sets `port`, all ParentRefs referencing the same\n object must also set `port`.\n* If one ParentRef sets `sectionName` and `port`, all ParentRefs\n referencing the same object must also set `sectionName` and `port`.\n\nIt is possible to separately reference multiple distinct objects that may be collapsed by an implementation. For example, some implementations may choose to merge compatible Gateway Listeners together. If that is the case, the list of routes attached to those resources should also be merged.\n\nNote that for ParentRefs that cross namespace boundaries, there are specific rules. Cross-namespace references are only valid if they are explicitly allowed by something in the namespace they are referring to. For example, Gateway has the AllowedRoutes field, and ReferenceGrant provides a generic way to enable other kinds of cross-namespace reference.\n\n ParentRefs from a Route to a Service in the same namespace are \"producer\" routes, which apply default routing rules to inbound connections from any namespace to the Service.\n\nParentRefs from a Route to a Service in a different namespace are \"consumer\" routes, and these routing rules are only applied to outbound connections originating from the same namespace as the Route, for which the intended destination of the connections are a Service targeted as a ParentRef of the Route. \n\n ", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("sigs.k8s.io/gateway-api/apis/v1.ParentReference"), + }, + }, + }, + }, + }, + "useDefaultGateways": { + SchemaProps: spec.SchemaProps{ + Description: "UseDefaultGateways indicates the default Gateway scope to use for this Route. If unset (the default) or set to None, the Route will not be attached to any default Gateway; if set, it will be attached to any default Gateway supporting the named scope, subject to the usual rules about which Routes a Gateway is allowed to claim.\n\nThink carefully before using this functionality! The set of default Gateways supporting the requested scope can change over time without any notice to the Route author, and in many situations it will not be appropriate to request a default Gateway for a given Route -- for example, a Route with specific security requirements should almost certainly not use a default Gateway.\n\n", + Type: []string{"string"}, + Format: "", + }, + }, + "hostnames": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "Hostnames defines a set of hostnames that should match against the HTTP Host header to select a HTTPRoute used to process the request. Implementations MUST ignore any port value specified in the HTTP Host header while performing a match and (absent of any applicable header modification configuration) MUST forward this header unmodified to the backend.\n\nValid values for Hostnames are determined by RFC 1123 definition of a hostname with 2 notable exceptions:\n\n1. IPs are not allowed. 2. A hostname may be prefixed with a wildcard label (`*.`). The wildcard\n label must appear by itself as the first label.\n\nIf a hostname is specified by both the Listener and HTTPRoute, there must be at least one intersecting hostname for the HTTPRoute to be attached to the Listener. For example:\n\n* A Listener with `test.example.com` as the hostname matches HTTPRoutes\n that have either not specified any hostnames, or have specified at\n least one of `test.example.com` or `*.example.com`.\n* A Listener with `*.example.com` as the hostname matches HTTPRoutes\n that have either not specified any hostnames or have specified at least\n one hostname that matches the Listener hostname. For example,\n `*.example.com`, `test.example.com`, and `foo.test.example.com` would\n all match. On the other hand, `example.com` and `test.example.net` would\n not match.\n\nHostnames that are prefixed with a wildcard label (`*.`) are interpreted as a suffix match. That means that a match for `*.example.com` would match both `test.example.com`, and `foo.test.example.com`, but not `example.com`.\n\nIf both the Listener and HTTPRoute have specified hostnames, any HTTPRoute hostnames that do not match the Listener hostname MUST be ignored. For example, if a Listener specified `*.example.com`, and the HTTPRoute specified `test.example.com` and `test.example.net`, `test.example.net` must not be considered for a match.\n\nIf both the Listener and HTTPRoute have specified hostnames, and none match with the criteria above, then the HTTPRoute is not accepted. The implementation must raise an 'Accepted' Condition with a status of `False` in the corresponding RouteParentStatus.\n\nIn the event that multiple HTTPRoutes specify intersecting hostnames (e.g. overlapping wildcard matching and exact matching hostnames), precedence must be given to rules from the HTTPRoute with the largest number of:\n\n* Characters in a matching non-wildcard hostname. * Characters in a matching hostname.\n\nIf ties exist across multiple Routes, the matching precedence rules for HTTPRouteMatches takes over.\n\nSupport: Core", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + "rules": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "Rules are a list of HTTP matchers, filters and actions.\n\n", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("sigs.k8s.io/gateway-api/apis/v1.HTTPRouteRule"), + }, + }, + }, + }, + }, + }, + }, + }, + Dependencies: []string{ + "sigs.k8s.io/gateway-api/apis/v1.HTTPRouteRule", "sigs.k8s.io/gateway-api/apis/v1.ParentReference"}, + } +} + +func schema_sigsk8sio_gateway_api_apis_v1_HTTPRouteStatus(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "HTTPRouteStatus defines the observed state of HTTPRoute.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "parents": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "Parents is a list of parent resources (usually Gateways) that are associated with the route, and the status of the route with respect to each parent. When this route attaches to a parent, the controller that manages the parent must add an entry to this list when the controller first sees the route and should update the entry as appropriate when the route or gateway is modified.\n\nNote that parent references that cannot be resolved by an implementation of this API will not be added to this list. Implementations of this API can only populate Route status for the Gateways/parent resources they are responsible for.\n\nA maximum of 32 Gateways will be represented in this list. An empty list means the route has not been attached to any Gateway.\n\n Notes for implementors:\n\nWhile parents is not a listType `map`, this is due to the fact that the list key is not scalar, and Kubernetes is unable to represent this.\n\nParent status MUST be considered to be namespaced by the combination of the parentRef and controllerName fields, and implementations should keep the following rules in mind when updating this status:\n\n* Implementations MUST update only entries that have a matching value of\n `controllerName` for that implementation.\n* Implementations MUST NOT update entries with non-matching `controllerName`\n fields.\n* Implementations MUST treat each `parentRef`` in the Route separately and\n update its status based on the relationship with that parent.\n* Implementations MUST perform a read-modify-write cycle on this field\n before modifying it. That is, when modifying this field, implementations\n must be confident they have fetched the most recent version of this field,\n and ensure that changes they make are on that recent version.\n\n", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("sigs.k8s.io/gateway-api/apis/v1.RouteParentStatus"), + }, + }, + }, + }, + }, + }, + Required: []string{"parents"}, + }, + }, + Dependencies: []string{ + "sigs.k8s.io/gateway-api/apis/v1.RouteParentStatus"}, + } +} + +func schema_sigsk8sio_gateway_api_apis_v1_HTTPRouteTimeouts(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "HTTPRouteTimeouts defines timeouts that can be configured for an HTTPRoute. Timeout values are represented with Gateway API Duration formatting.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "request": { + SchemaProps: spec.SchemaProps{ + Description: "Request specifies the maximum duration for a gateway to respond to an HTTP request. If the gateway has not been able to respond before this deadline is met, the gateway MUST return a timeout error.\n\nFor example, setting the `rules.timeouts.request` field to the value `10s` in an `HTTPRoute` will cause a timeout if a client request is taking longer than 10 seconds to complete.\n\nSetting a timeout to the zero duration (e.g. \"0s\") SHOULD disable the timeout completely. Implementations that cannot completely disable the timeout MUST instead interpret the zero duration as the longest possible value to which the timeout can be set.\n\nThis timeout is intended to cover as close to the whole request-response transaction as possible although an implementation MAY choose to start the timeout after the entire request stream has been received instead of immediately after the transaction is initiated by the client.\n\nThe value of Request is a Gateway API Duration string as defined by GEP-2257. When this field is unspecified, request timeout behavior is implementation-specific.\n\nSupport: Extended", + Type: []string{"string"}, + Format: "", + }, + }, + "backendRequest": { + SchemaProps: spec.SchemaProps{ + Description: "BackendRequest specifies a timeout for an individual request from the gateway to a backend. This covers the time from when the request first starts being sent from the gateway to when the full response has been received from the backend.\n\nSetting a timeout to the zero duration (e.g. \"0s\") SHOULD disable the timeout completely. Implementations that cannot completely disable the timeout MUST instead interpret the zero duration as the longest possible value to which the timeout can be set.\n\nAn entire client HTTP transaction with a gateway, covered by the Request timeout, may result in more than one call from the gateway to the destination backend, for example, if automatic retries are supported.\n\nThe value of BackendRequest must be a Gateway API Duration string as defined by GEP-2257. When this field is unspecified, its behavior is implementation-specific; when specified, the value of BackendRequest must be no more than the value of the Request timeout (since the Request timeout encompasses the BackendRequest timeout).\n\nSupport: Extended", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + } +} + +func schema_sigsk8sio_gateway_api_apis_v1_HTTPURLRewriteFilter(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "HTTPURLRewriteFilter defines a filter that modifies a request during forwarding. At most one of these filters may be used on a Route rule. This MUST NOT be used on the same Route rule as a HTTPRequestRedirect filter.\n\nSupport: Extended", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "hostname": { + SchemaProps: spec.SchemaProps{ + Description: "Hostname is the value to be used to replace the Host header value during forwarding.\n\nSupport: Extended", + Type: []string{"string"}, + Format: "", + }, + }, + "path": { + SchemaProps: spec.SchemaProps{ + Description: "Path defines a path rewrite.\n\nSupport: Extended", + Ref: ref("sigs.k8s.io/gateway-api/apis/v1.HTTPPathModifier"), + }, + }, + }, + }, + }, + Dependencies: []string{ + "sigs.k8s.io/gateway-api/apis/v1.HTTPPathModifier"}, + } +} + +func schema_sigsk8sio_gateway_api_apis_v1_Listener(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "Listener embodies the concept of a logical endpoint where a Gateway accepts network connections.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "name": { + SchemaProps: spec.SchemaProps{ + Description: "Name is the name of the Listener. This name MUST be unique within a Gateway.\n\nSupport: Core", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "hostname": { + SchemaProps: spec.SchemaProps{ + Description: "Hostname specifies the virtual hostname to match for protocol types that define this concept. When unspecified, all hostnames are matched. This field is ignored for protocols that don't require hostname based matching.\n\nImplementations MUST apply Hostname matching appropriately for each of the following protocols:\n\n* TLS: The Listener Hostname MUST match the SNI. * HTTP: The Listener Hostname MUST match the Host header of the request. * HTTPS: The Listener Hostname SHOULD match both the SNI and Host header.\n Note that this does not require the SNI and Host header to be the same.\n The semantics of this are described in more detail below.\n\nTo ensure security, Section 11.1 of RFC-6066 emphasizes that server implementations that rely on SNI hostname matching MUST also verify hostnames within the application protocol.\n\nSection 9.1.2 of RFC-7540 provides a mechanism for servers to reject the reuse of a connection by responding with the HTTP 421 Misdirected Request status code. This indicates that the origin server has rejected the request because it appears to have been misdirected.\n\nTo detect misdirected requests, Gateways SHOULD match the authority of the requests with all the SNI hostname(s) configured across all the Gateway Listeners on the same port and protocol:\n\n* If another Listener has an exact match or more specific wildcard entry,\n the Gateway SHOULD return a 421.\n* If the current Listener (selected by SNI matching during ClientHello)\n does not match the Host:\n * If another Listener does match the Host the Gateway SHOULD return a\n 421.\n * If no other Listener matches the Host, the Gateway MUST return a\n 404.\n\nFor HTTPRoute and TLSRoute resources, there is an interaction with the `spec.hostnames` array. When both listener and route specify hostnames, there MUST be an intersection between the values for a Route to be accepted. For more information, refer to the Route specific Hostnames documentation.\n\nHostnames that are prefixed with a wildcard label (`*.`) are interpreted as a suffix match. That means that a match for `*.example.com` would match both `test.example.com`, and `foo.test.example.com`, but not `example.com`.\n\nSupport: Core", + Type: []string{"string"}, + Format: "", + }, + }, + "port": { + SchemaProps: spec.SchemaProps{ + Description: "Port is the network port. Multiple listeners may use the same port, subject to the Listener compatibility rules.\n\nSupport: Core", + Default: 0, + Type: []string{"integer"}, + Format: "int32", + }, + }, + "protocol": { + SchemaProps: spec.SchemaProps{ + Description: "Protocol specifies the network protocol this listener expects to receive.\n\nSupport: Core", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "tls": { + SchemaProps: spec.SchemaProps{ + Description: "TLS is the TLS configuration for the Listener. This field is required if the Protocol field is \"HTTPS\" or \"TLS\". It is invalid to set this field if the Protocol field is \"HTTP\", \"TCP\", or \"UDP\".\n\nThe association of SNIs to Certificate defined in ListenerTLSConfig is defined based on the Hostname field for this listener.\n\nThe GatewayClass MUST use the longest matching SNI out of all available certificates for any TLS handshake.\n\nSupport: Core", + Ref: ref("sigs.k8s.io/gateway-api/apis/v1.ListenerTLSConfig"), + }, + }, + "allowedRoutes": { + SchemaProps: spec.SchemaProps{ + Description: "AllowedRoutes defines the types of routes that MAY be attached to a Listener and the trusted namespaces where those Route resources MAY be present.\n\nAlthough a client request may match multiple route rules, only one rule may ultimately receive the request. Matching precedence MUST be determined in order of the following criteria:\n\n* The most specific match as defined by the Route type. * The oldest Route based on creation timestamp. For example, a Route with\n a creation timestamp of \"2020-09-08 01:02:03\" is given precedence over\n a Route with a creation timestamp of \"2020-09-08 01:02:04\".\n* If everything else is equivalent, the Route appearing first in\n alphabetical order (namespace/name) should be given precedence. For\n example, foo/bar is given precedence over foo/baz.\n\nAll valid rules within a Route attached to this Listener should be implemented. Invalid Route rules can be ignored (sometimes that will mean the full Route). If a Route rule transitions from valid to invalid, support for that Route rule should be dropped to ensure consistency. For example, even if a filter specified by a Route rule is invalid, the rest of the rules within that Route should still be supported.\n\nSupport: Core", + Ref: ref("sigs.k8s.io/gateway-api/apis/v1.AllowedRoutes"), + }, + }, + }, + Required: []string{"name", "port", "protocol"}, + }, + }, + Dependencies: []string{ + "sigs.k8s.io/gateway-api/apis/v1.AllowedRoutes", "sigs.k8s.io/gateway-api/apis/v1.ListenerTLSConfig"}, + } +} + +func schema_sigsk8sio_gateway_api_apis_v1_ListenerNamespaces(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "ListenerNamespaces indicate which namespaces ListenerSets should be selected from.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "from": { + SchemaProps: spec.SchemaProps{ + Description: "From indicates where ListenerSets can attach to this Gateway. Possible values are:\n\n* Same: Only ListenerSets in the same namespace may be attached to this Gateway. * Selector: ListenerSets in namespaces selected by the selector may be attached to this Gateway. * All: ListenerSets in all namespaces may be attached to this Gateway. * None: Only listeners defined in the Gateway's spec are allowed\n\nWhile this feature is experimental, the default value None", + Type: []string{"string"}, + Format: "", + }, + }, + "selector": { + SchemaProps: spec.SchemaProps{ + Description: "Selector must be specified when From is set to \"Selector\". In that case, only ListenerSets in Namespaces matching this Selector will be selected by this Gateway. This field is ignored for other values of \"From\".", + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.LabelSelector"), + }, + }, + }, + }, + }, + Dependencies: []string{ + "k8s.io/apimachinery/pkg/apis/meta/v1.LabelSelector"}, + } +} + +func schema_sigsk8sio_gateway_api_apis_v1_ListenerStatus(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "ListenerStatus is the status associated with a Listener.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "name": { + SchemaProps: spec.SchemaProps{ + Description: "Name is the name of the Listener that this status corresponds to.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "supportedKinds": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "SupportedKinds is the list indicating the Kinds supported by this listener. This MUST represent the kinds an implementation supports for that Listener configuration.\n\nIf kinds are specified in Spec that are not supported, they MUST NOT appear in this list and an implementation MUST set the \"ResolvedRefs\" condition to \"False\" with the \"InvalidRouteKinds\" reason. If both valid and invalid Route kinds are specified, the implementation MUST reference the valid Route kinds that have been specified.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("sigs.k8s.io/gateway-api/apis/v1.RouteGroupKind"), + }, + }, + }, + }, + }, + "attachedRoutes": { + SchemaProps: spec.SchemaProps{ + Description: "AttachedRoutes represents the total number of Routes that have been successfully attached to this Listener.\n\nSuccessful attachment of a Route to a Listener is based solely on the combination of the AllowedRoutes field on the corresponding Listener and the Route's ParentRefs field. A Route is successfully attached to a Listener when it is selected by the Listener's AllowedRoutes field AND the Route has a valid ParentRef selecting the whole Gateway resource or a specific Listener as a parent resource (more detail on attachment semantics can be found in the documentation on the various Route kinds ParentRefs fields). Listener or Route status does not impact successful attachment, i.e. the AttachedRoutes field count MUST be set for Listeners with condition Accepted: false and MUST count successfully attached Routes that may themselves have Accepted: false conditions.\n\nUses for this field include troubleshooting Route attachment and measuring blast radius/impact of changes to a Listener.", + Default: 0, + Type: []string{"integer"}, + Format: "int32", + }, + }, + "conditions": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-map-keys": []interface{}{ + "type", + }, + "x-kubernetes-list-type": "map", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "Conditions describe the current condition of this listener.\n\n Notes for implementors:\n\nConditions are a listType `map`, which means that they function like a map with a key of the `type` field _in the k8s apiserver_.\n\nThis means that implementations must obey some rules when updating this section.\n\n* Implementations MUST perform a read-modify-write cycle on this field\n before modifying it. That is, when modifying this field, implementations\n must be confident they have fetched the most recent version of this field,\n and ensure that changes they make are on that recent version.\n* Implementations MUST NOT remove or reorder Conditions that they are not\n directly responsible for. For example, if an implementation sees a Condition\n with type `special.io/SomeField`, it MUST NOT remove, change or update that\n Condition.\n* Implementations MUST always _merge_ changes into Conditions of the same Type,\n rather than creating more than one Condition of the same Type.\n* Implementations MUST always update the `observedGeneration` field of the\n Condition to the `metadata.generation` of the Gateway at the time of update creation.\n* If the `observedGeneration` of a Condition is _greater than_ the value the\n implementation knows about, then it MUST NOT perform the update on that Condition,\n but must wait for a future reconciliation and status update. (The assumption is that\n the implementation's copy of the object is stale and an update will be re-triggered\n if relevant.)\n\n", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.Condition"), + }, + }, + }, + }, + }, + }, + Required: []string{"name", "supportedKinds", "attachedRoutes", "conditions"}, + }, + }, + Dependencies: []string{ + "k8s.io/apimachinery/pkg/apis/meta/v1.Condition", "sigs.k8s.io/gateway-api/apis/v1.RouteGroupKind"}, + } +} + +func schema_sigsk8sio_gateway_api_apis_v1_ListenerTLSConfig(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "ListenerTLSConfig describes a TLS configuration for a listener.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "mode": { + SchemaProps: spec.SchemaProps{ + Description: "Mode defines the TLS behavior for the TLS session initiated by the client. There are two possible modes:\n\n- Terminate: The TLS session between the downstream client and the\n Gateway is terminated at the Gateway. This mode requires certificates\n to be specified in some way, such as populating the certificateRefs\n field.\n- Passthrough: The TLS session is NOT terminated by the Gateway. This\n implies that the Gateway can't decipher the TLS stream except for\n the ClientHello message of the TLS protocol. The certificateRefs field\n is ignored in this mode.\n\nSupport: Core", + Type: []string{"string"}, + Format: "", + }, + }, + "certificateRefs": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "CertificateRefs contains a series of references to Kubernetes objects that contains TLS certificates and private keys. These certificates are used to establish a TLS handshake for requests that match the hostname of the associated listener.\n\nA single CertificateRef to a Kubernetes Secret has \"Core\" support. Implementations MAY choose to support attaching multiple certificates to a Listener, but this behavior is implementation-specific.\n\nReferences to a resource in different namespace are invalid UNLESS there is a ReferenceGrant in the target namespace that allows the certificate to be attached. If a ReferenceGrant does not allow this reference, the \"ResolvedRefs\" condition MUST be set to False for this listener with the \"RefNotPermitted\" reason.\n\nThis field is required to have at least one element when the mode is set to \"Terminate\" (default) and is optional otherwise.\n\nCertificateRefs can reference to standard Kubernetes resources, i.e. Secret, or implementation-specific custom resources.\n\nSupport: Core - A single reference to a Kubernetes Secret of type kubernetes.io/tls\n\nSupport: Implementation-specific (More than one reference or other resource types)", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("sigs.k8s.io/gateway-api/apis/v1.SecretObjectReference"), + }, + }, + }, + }, + }, + "options": { + SchemaProps: spec.SchemaProps{ + Description: "Options are a list of key/value pairs to enable extended TLS configuration for each implementation. For example, configuring the minimum TLS version or supported cipher suites.\n\nA set of common keys MAY be defined by the API in the future. To avoid any ambiguity, implementation-specific definitions MUST use domain-prefixed names, such as `example.com/my-custom-option`. Un-prefixed names are reserved for key names defined by Gateway API.\n\nSupport: Implementation-specific", + Type: []string{"object"}, + AdditionalProperties: &spec.SchemaOrBool{ + Allows: true, + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + }, + }, + }, + Dependencies: []string{ + "sigs.k8s.io/gateway-api/apis/v1.SecretObjectReference"}, + } +} + +func schema_sigsk8sio_gateway_api_apis_v1_LocalObjectReference(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "LocalObjectReference identifies an API object within the namespace of the referrer. The API object must be valid in the cluster; the Group and Kind must be registered in the cluster for this reference to be valid.\n\nReferences to objects with invalid Group and Kind are not valid, and must be rejected by the implementation, with appropriate Conditions set on the containing object.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "group": { + SchemaProps: spec.SchemaProps{ + Description: "Group is the group of the referent. For example, \"gateway.networking.k8s.io\". When unspecified or empty string, core API group is inferred.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is kind of the referent. For example \"HTTPRoute\" or \"Service\".", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "name": { + SchemaProps: spec.SchemaProps{ + Description: "Name is the name of the referent.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"group", "kind", "name"}, + }, + }, + } +} + +func schema_sigsk8sio_gateway_api_apis_v1_LocalParametersReference(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "LocalParametersReference identifies an API object containing controller-specific configuration resource within the namespace.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "group": { + SchemaProps: spec.SchemaProps{ + Description: "Group is the group of the referent.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is kind of the referent.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "name": { + SchemaProps: spec.SchemaProps{ + Description: "Name is the name of the referent.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"group", "kind", "name"}, + }, + }, + } +} + +func schema_sigsk8sio_gateway_api_apis_v1_LocalPolicyTargetReference(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "LocalPolicyTargetReference identifies an API object to apply a direct or inherited policy to. This should be used as part of Policy resources that can target Gateway API resources. For more information on how this policy attachment model works, and a sample Policy resource, refer to the policy attachment documentation for Gateway API.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "group": { + SchemaProps: spec.SchemaProps{ + Description: "Group is the group of the target resource.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is kind of the target resource.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "name": { + SchemaProps: spec.SchemaProps{ + Description: "Name is the name of the target resource.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"group", "kind", "name"}, + }, + }, + } +} + +func schema_sigsk8sio_gateway_api_apis_v1_LocalPolicyTargetReferenceWithSectionName(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "LocalPolicyTargetReferenceWithSectionName identifies an API object to apply a direct policy to. This should be used as part of Policy resources that can target single resources. For more information on how this policy attachment mode works, and a sample Policy resource, refer to the policy attachment documentation for Gateway API.\n\nNote: This should only be used for direct policy attachment when references to SectionName are actually needed. In all other cases, LocalPolicyTargetReference should be used.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "group": { + SchemaProps: spec.SchemaProps{ + Description: "Group is the group of the target resource.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is kind of the target resource.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "name": { + SchemaProps: spec.SchemaProps{ + Description: "Name is the name of the target resource.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "sectionName": { + SchemaProps: spec.SchemaProps{ + Description: "SectionName is the name of a section within the target resource. When unspecified, this targetRef targets the entire resource. In the following resources, SectionName is interpreted as the following:\n\n* Gateway: Listener name * HTTPRoute: HTTPRouteRule name * Service: Port name\n\nIf a SectionName is specified, but does not exist on the targeted object, the Policy must fail to attach, and the policy implementation should record a `ResolvedRefs` or similar Condition in the Policy's status.", + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"group", "kind", "name"}, + }, + }, + } +} + +func schema_sigsk8sio_gateway_api_apis_v1_NamespacedPolicyTargetReference(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "NamespacedPolicyTargetReference identifies an API object to apply a direct or inherited policy to, potentially in a different namespace. This should only be used as part of Policy resources that need to be able to target resources in different namespaces. For more information on how this policy attachment model works, and a sample Policy resource, refer to the policy attachment documentation for Gateway API.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "group": { + SchemaProps: spec.SchemaProps{ + Description: "Group is the group of the target resource.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is kind of the target resource.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "name": { + SchemaProps: spec.SchemaProps{ + Description: "Name is the name of the target resource.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "namespace": { + SchemaProps: spec.SchemaProps{ + Description: "Namespace is the namespace of the referent. When unspecified, the local namespace is inferred. Even when policy targets a resource in a different namespace, it MUST only apply to traffic originating from the same namespace as the policy.", + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"group", "kind", "name"}, + }, + }, + } +} + +func schema_sigsk8sio_gateway_api_apis_v1_ObjectReference(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "ObjectReference identifies an API object including its namespace.\n\nThe API object must be valid in the cluster; the Group and Kind must be registered in the cluster for this reference to be valid.\n\nReferences to objects with invalid Group and Kind are not valid, and must be rejected by the implementation, with appropriate Conditions set on the containing object.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "group": { + SchemaProps: spec.SchemaProps{ + Description: "Group is the group of the referent. For example, \"gateway.networking.k8s.io\". When set to the empty string, core API group is inferred.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is kind of the referent. For example \"ConfigMap\" or \"Service\".", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "name": { + SchemaProps: spec.SchemaProps{ + Description: "Name is the name of the referent.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "namespace": { + SchemaProps: spec.SchemaProps{ + Description: "Namespace is the namespace of the referenced object. When unspecified, the local namespace is inferred.\n\nNote that when a namespace different than the local namespace is specified, a ReferenceGrant object is required in the referent namespace to allow that namespace's owner to accept the reference. See the ReferenceGrant documentation for details.\n\nSupport: Core", + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"group", "kind", "name"}, + }, + }, + } +} + +func schema_sigsk8sio_gateway_api_apis_v1_ParametersReference(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "ParametersReference identifies an API object containing controller-specific configuration resource within the cluster.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "group": { + SchemaProps: spec.SchemaProps{ + Description: "Group is the group of the referent.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is kind of the referent.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "name": { + SchemaProps: spec.SchemaProps{ + Description: "Name is the name of the referent.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "namespace": { + SchemaProps: spec.SchemaProps{ + Description: "Namespace is the namespace of the referent. This field is required when referring to a Namespace-scoped resource and MUST be unset when referring to a Cluster-scoped resource.", + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"group", "kind", "name"}, + }, + }, + } +} + +func schema_sigsk8sio_gateway_api_apis_v1_ParentReference(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "ParentReference identifies an API object (usually a Gateway) that can be considered a parent of this resource (usually a route). There are two kinds of parent resources with \"Core\" support:\n\n* Gateway (Gateway conformance profile) * Service (Mesh conformance profile, ClusterIP Services only)\n\nThis API may be extended in the future to support additional kinds of parent resources.\n\nThe API object must be valid in the cluster; the Group and Kind must be registered in the cluster for this reference to be valid.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "group": { + SchemaProps: spec.SchemaProps{ + Description: "Group is the group of the referent. When unspecified, \"gateway.networking.k8s.io\" is inferred. To set the core API group (such as for a \"Service\" kind referent), Group must be explicitly set to \"\" (empty string).\n\nSupport: Core", + Type: []string{"string"}, + Format: "", + }, + }, + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is kind of the referent.\n\nThere are two kinds of parent resources with \"Core\" support:\n\n* Gateway (Gateway conformance profile) * Service (Mesh conformance profile, ClusterIP Services only)\n\nSupport for other resources is Implementation-Specific.", + Type: []string{"string"}, + Format: "", + }, + }, + "namespace": { + SchemaProps: spec.SchemaProps{ + Description: "Namespace is the namespace of the referent. When unspecified, this refers to the local namespace of the Route.\n\nNote that there are specific rules for ParentRefs which cross namespace boundaries. Cross-namespace references are only valid if they are explicitly allowed by something in the namespace they are referring to. For example: Gateway has the AllowedRoutes field, and ReferenceGrant provides a generic way to enable any other kind of cross-namespace reference.\n\n ParentRefs from a Route to a Service in the same namespace are \"producer\" routes, which apply default routing rules to inbound connections from any namespace to the Service.\n\nParentRefs from a Route to a Service in a different namespace are \"consumer\" routes, and these routing rules are only applied to outbound connections originating from the same namespace as the Route, for which the intended destination of the connections are a Service targeted as a ParentRef of the Route. \n\nSupport: Core", + Type: []string{"string"}, + Format: "", + }, + }, + "name": { + SchemaProps: spec.SchemaProps{ + Description: "Name is the name of the referent.\n\nSupport: Core", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "sectionName": { + SchemaProps: spec.SchemaProps{ + Description: "SectionName is the name of a section within the target resource. In the following resources, SectionName is interpreted as the following:\n\n* Gateway: Listener name. When both Port (experimental) and SectionName are specified, the name and port of the selected listener must match both specified values. * Service: Port name. When both Port (experimental) and SectionName are specified, the name and port of the selected listener must match both specified values.\n\nImplementations MAY choose to support attaching Routes to other resources. If that is the case, they MUST clearly document how SectionName is interpreted.\n\nWhen unspecified (empty string), this will reference the entire resource. For the purpose of status, an attachment is considered successful if at least one section in the parent resource accepts it. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway.\n\nSupport: Core", + Type: []string{"string"}, + Format: "", + }, + }, + "port": { + SchemaProps: spec.SchemaProps{ + Description: "Port is the network port this Route targets. It can be interpreted differently based on the type of parent resource.\n\nWhen the parent resource is a Gateway, this targets all listeners listening on the specified port that also support this kind of Route(and select this Route). It's not recommended to set `Port` unless the networking behaviors specified in a Route must apply to a specific port as opposed to a listener(s) whose port(s) may be changed. When both Port and SectionName are specified, the name and port of the selected listener must match both specified values.\n\n When the parent resource is a Service, this targets a specific port in the Service spec. When both Port (experimental) and SectionName are specified, the name and port of the selected port must match both specified values. \n\nImplementations MAY choose to support other parent resources. Implementations supporting other types of parent resources MUST clearly document how/if Port is interpreted.\n\nFor the purpose of status, an attachment is considered successful as long as the parent resource accepts it partially. For example, Gateway listeners can restrict which Routes can attach to them by Route kind, namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from the referencing Route, the Route MUST be considered successfully attached. If no Gateway listeners accept attachment from this Route, the Route MUST be considered detached from the Gateway.\n\nSupport: Extended", + Type: []string{"integer"}, + Format: "int32", + }, + }, + }, + Required: []string{"name"}, + }, + }, + } +} + +func schema_sigsk8sio_gateway_api_apis_v1_PolicyAncestorStatus(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "PolicyAncestorStatus describes the status of a route with respect to an associated Ancestor.\n\nAncestors refer to objects that are either the Target of a policy or above it in terms of object hierarchy. For example, if a policy targets a Service, the Policy's Ancestors are, in order, the Service, the HTTPRoute, the Gateway, and the GatewayClass. Almost always, in this hierarchy, the Gateway will be the most useful object to place Policy status on, so we recommend that implementations SHOULD use Gateway as the PolicyAncestorStatus object unless the designers have a _very_ good reason otherwise.\n\nIn the context of policy attachment, the Ancestor is used to distinguish which resource results in a distinct application of this policy. For example, if a policy targets a Service, it may have a distinct result per attached Gateway.\n\nPolicies targeting the same resource may have different effects depending on the ancestors of those resources. For example, different Gateways targeting the same Service may have different capabilities, especially if they have different underlying implementations.\n\nFor example, in BackendTLSPolicy, the Policy attaches to a Service that is used as a backend in a HTTPRoute that is itself attached to a Gateway. In this case, the relevant object for status is the Gateway, and that is the ancestor object referred to in this status.\n\nNote that a parent is also an ancestor, so for objects where the parent is the relevant object for status, this struct SHOULD still be used.\n\nThis struct is intended to be used in a slice that's effectively a map, with a composite key made up of the AncestorRef and the ControllerName.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "ancestorRef": { + SchemaProps: spec.SchemaProps{ + Description: "AncestorRef corresponds with a ParentRef in the spec that this PolicyAncestorStatus struct describes the status of.", + Default: map[string]interface{}{}, + Ref: ref("sigs.k8s.io/gateway-api/apis/v1.ParentReference"), + }, + }, + "controllerName": { + SchemaProps: spec.SchemaProps{ + Description: "ControllerName is a domain/path string that indicates the name of the controller that wrote this status. This corresponds with the controllerName field on GatewayClass.\n\nExample: \"example.net/gateway-controller\".\n\nThe format of this field is DOMAIN \"/\" PATH, where DOMAIN and PATH are valid Kubernetes names (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names).\n\nControllers MUST populate this field when writing status. Controllers should ensure that entries to status populated with their ControllerName are cleaned up when they are no longer necessary.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "conditions": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-map-keys": []interface{}{ + "type", + }, + "x-kubernetes-list-type": "map", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "Conditions describes the status of the Policy with respect to the given Ancestor.\n\n\n\nNotes for implementors:\n\nConditions are a listType `map`, which means that they function like a map with a key of the `type` field _in the k8s apiserver_.\n\nThis means that implementations must obey some rules when updating this section.\n\n* Implementations MUST perform a read-modify-write cycle on this field\n before modifying it. That is, when modifying this field, implementations\n must be confident they have fetched the most recent version of this field,\n and ensure that changes they make are on that recent version.\n* Implementations MUST NOT remove or reorder Conditions that they are not\n directly responsible for. For example, if an implementation sees a Condition\n with type `special.io/SomeField`, it MUST NOT remove, change or update that\n Condition.\n* Implementations MUST always _merge_ changes into Conditions of the same Type,\n rather than creating more than one Condition of the same Type.\n* Implementations MUST always update the `observedGeneration` field of the\n Condition to the `metadata.generation` of the Gateway at the time of update creation.\n* If the `observedGeneration` of a Condition is _greater than_ the value the\n implementation knows about, then it MUST NOT perform the update on that Condition,\n but must wait for a future reconciliation and status update. (The assumption is that\n the implementation's copy of the object is stale and an update will be re-triggered\n if relevant.)\n\n", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.Condition"), + }, + }, + }, + }, + }, + }, + Required: []string{"ancestorRef", "controllerName", "conditions"}, + }, + }, + Dependencies: []string{ + "k8s.io/apimachinery/pkg/apis/meta/v1.Condition", "sigs.k8s.io/gateway-api/apis/v1.ParentReference"}, + } +} + +func schema_sigsk8sio_gateway_api_apis_v1_PolicyStatus(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "PolicyStatus defines the common attributes that all Policies should include within their status.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "ancestors": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "Ancestors is a list of ancestor resources (usually Gateways) that are associated with the policy, and the status of the policy with respect to each ancestor. When this policy attaches to a parent, the controller that manages the parent and the ancestors MUST add an entry to this list when the controller first sees the policy and SHOULD update the entry as appropriate when the relevant ancestor is modified.\n\nNote that choosing the relevant ancestor is left to the Policy designers; an important part of Policy design is designing the right object level at which to namespace this status.\n\nNote also that implementations MUST ONLY populate ancestor status for the Ancestor resources they are responsible for. Implementations MUST use the ControllerName field to uniquely identify the entries in this list that they are responsible for.\n\nNote that to achieve this, the list of PolicyAncestorStatus structs MUST be treated as a map with a composite key, made up of the AncestorRef and ControllerName fields combined.\n\nA maximum of 16 ancestors will be represented in this list. An empty list means the Policy is not relevant for any ancestors.\n\nIf this slice is full, implementations MUST NOT add further entries. Instead they MUST consider the policy unimplementable and signal that on any related resources such as the ancestor that would be referenced here. For example, if this list was full on BackendTLSPolicy, no additional Gateways would be able to reference the Service targeted by the BackendTLSPolicy.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("sigs.k8s.io/gateway-api/apis/v1.PolicyAncestorStatus"), + }, + }, + }, + }, + }, + }, + Required: []string{"ancestors"}, + }, + }, + Dependencies: []string{ + "sigs.k8s.io/gateway-api/apis/v1.PolicyAncestorStatus"}, + } +} + +func schema_sigsk8sio_gateway_api_apis_v1_RouteGroupKind(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "RouteGroupKind indicates the group and kind of a Route resource.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "group": { + SchemaProps: spec.SchemaProps{ + Description: "Group is the group of the Route.", + Type: []string{"string"}, + Format: "", + }, + }, + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is the kind of the Route.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"kind"}, + }, + }, + } +} + +func schema_sigsk8sio_gateway_api_apis_v1_RouteNamespaces(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "RouteNamespaces indicate which namespaces Routes should be selected from.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "from": { + SchemaProps: spec.SchemaProps{ + Description: "From indicates where Routes will be selected for this Gateway. Possible values are:\n\n* All: Routes in all namespaces may be used by this Gateway. * Selector: Routes in namespaces selected by the selector may be used by\n this Gateway.\n* Same: Only Routes in the same namespace may be used by this Gateway.\n\nSupport: Core", + Type: []string{"string"}, + Format: "", + }, + }, + "selector": { + SchemaProps: spec.SchemaProps{ + Description: "Selector must be specified when From is set to \"Selector\". In that case, only Routes in Namespaces matching this Selector will be selected by this Gateway. This field is ignored for other values of \"From\".\n\nSupport: Core", + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.LabelSelector"), + }, + }, + }, + }, + }, + Dependencies: []string{ + "k8s.io/apimachinery/pkg/apis/meta/v1.LabelSelector"}, + } +} + +func schema_sigsk8sio_gateway_api_apis_v1_RouteParentStatus(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "RouteParentStatus describes the status of a route with respect to an associated Parent.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "parentRef": { + SchemaProps: spec.SchemaProps{ + Description: "ParentRef corresponds with a ParentRef in the spec that this RouteParentStatus struct describes the status of.", + Default: map[string]interface{}{}, + Ref: ref("sigs.k8s.io/gateway-api/apis/v1.ParentReference"), + }, + }, + "controllerName": { + SchemaProps: spec.SchemaProps{ + Description: "ControllerName is a domain/path string that indicates the name of the controller that wrote this status. This corresponds with the controllerName field on GatewayClass.\n\nExample: \"example.net/gateway-controller\".\n\nThe format of this field is DOMAIN \"/\" PATH, where DOMAIN and PATH are valid Kubernetes names (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names).\n\nControllers MUST populate this field when writing status. Controllers should ensure that entries to status populated with their ControllerName are cleaned up when they are no longer necessary.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "conditions": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-map-keys": []interface{}{ + "type", + }, + "x-kubernetes-list-type": "map", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "Conditions describes the status of the route with respect to the Gateway. Note that the route's availability is also subject to the Gateway's own status conditions and listener status.\n\nIf the Route's ParentRef specifies an existing Gateway that supports Routes of this kind AND that Gateway's controller has sufficient access, then that Gateway's controller MUST set the \"Accepted\" condition on the Route, to indicate whether the route has been accepted or rejected by the Gateway, and why.\n\nA Route MUST be considered \"Accepted\" if at least one of the Route's rules is implemented by the Gateway.\n\nThere are a number of cases where the \"Accepted\" condition may not be set due to lack of controller visibility, that includes when:\n\n* The Route refers to a nonexistent parent. * The Route is of a type that the controller does not support. * The Route is in a namespace the controller does not have access to.\n\n\n\nNotes for implementors:\n\nConditions are a listType `map`, which means that they function like a map with a key of the `type` field _in the k8s apiserver_.\n\nThis means that implementations must obey some rules when updating this section.\n\n* Implementations MUST perform a read-modify-write cycle on this field\n before modifying it. That is, when modifying this field, implementations\n must be confident they have fetched the most recent version of this field,\n and ensure that changes they make are on that recent version.\n* Implementations MUST NOT remove or reorder Conditions that they are not\n directly responsible for. For example, if an implementation sees a Condition\n with type `special.io/SomeField`, it MUST NOT remove, change or update that\n Condition.\n* Implementations MUST always _merge_ changes into Conditions of the same Type,\n rather than creating more than one Condition of the same Type.\n* Implementations MUST always update the `observedGeneration` field of the\n Condition to the `metadata.generation` of the Gateway at the time of update creation.\n* If the `observedGeneration` of a Condition is _greater than_ the value the\n implementation knows about, then it MUST NOT perform the update on that Condition,\n but must wait for a future reconciliation and status update. (The assumption is that\n the implementation's copy of the object is stale and an update will be re-triggered\n if relevant.)\n\n", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.Condition"), + }, + }, + }, + }, + }, + }, + Required: []string{"parentRef", "controllerName", "conditions"}, + }, + }, + Dependencies: []string{ + "k8s.io/apimachinery/pkg/apis/meta/v1.Condition", "sigs.k8s.io/gateway-api/apis/v1.ParentReference"}, + } +} + +func schema_sigsk8sio_gateway_api_apis_v1_RouteStatus(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "RouteStatus defines the common attributes that all Routes MUST include within their status.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "parents": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "Parents is a list of parent resources (usually Gateways) that are associated with the route, and the status of the route with respect to each parent. When this route attaches to a parent, the controller that manages the parent must add an entry to this list when the controller first sees the route and should update the entry as appropriate when the route or gateway is modified.\n\nNote that parent references that cannot be resolved by an implementation of this API will not be added to this list. Implementations of this API can only populate Route status for the Gateways/parent resources they are responsible for.\n\nA maximum of 32 Gateways will be represented in this list. An empty list means the route has not been attached to any Gateway.\n\n Notes for implementors:\n\nWhile parents is not a listType `map`, this is due to the fact that the list key is not scalar, and Kubernetes is unable to represent this.\n\nParent status MUST be considered to be namespaced by the combination of the parentRef and controllerName fields, and implementations should keep the following rules in mind when updating this status:\n\n* Implementations MUST update only entries that have a matching value of\n `controllerName` for that implementation.\n* Implementations MUST NOT update entries with non-matching `controllerName`\n fields.\n* Implementations MUST treat each `parentRef`` in the Route separately and\n update its status based on the relationship with that parent.\n* Implementations MUST perform a read-modify-write cycle on this field\n before modifying it. That is, when modifying this field, implementations\n must be confident they have fetched the most recent version of this field,\n and ensure that changes they make are on that recent version.\n\n", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("sigs.k8s.io/gateway-api/apis/v1.RouteParentStatus"), + }, + }, + }, + }, + }, + }, + Required: []string{"parents"}, + }, + }, + Dependencies: []string{ + "sigs.k8s.io/gateway-api/apis/v1.RouteParentStatus"}, + } +} + +func schema_sigsk8sio_gateway_api_apis_v1_SecretObjectReference(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "SecretObjectReference identifies an API object including its namespace, defaulting to Secret.\n\nThe API object must be valid in the cluster; the Group and Kind must be registered in the cluster for this reference to be valid.\n\nReferences to objects with invalid Group and Kind are not valid, and must be rejected by the implementation, with appropriate Conditions set on the containing object.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "group": { + SchemaProps: spec.SchemaProps{ + Description: "Group is the group of the referent. For example, \"gateway.networking.k8s.io\". When unspecified or empty string, core API group is inferred.", + Type: []string{"string"}, + Format: "", + }, + }, + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is kind of the referent. For example \"Secret\".", + Type: []string{"string"}, + Format: "", + }, + }, + "name": { + SchemaProps: spec.SchemaProps{ + Description: "Name is the name of the referent.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "namespace": { + SchemaProps: spec.SchemaProps{ + Description: "Namespace is the namespace of the referenced object. When unspecified, the local namespace is inferred.\n\nNote that when a namespace different than the local namespace is specified, a ReferenceGrant object is required in the referent namespace to allow that namespace's owner to accept the reference. See the ReferenceGrant documentation for details.\n\nSupport: Core", + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"name"}, + }, + }, + } +} + +func schema_sigsk8sio_gateway_api_apis_v1_SessionPersistence(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "SessionPersistence defines the desired state of SessionPersistence.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "sessionName": { + SchemaProps: spec.SchemaProps{ + Description: "SessionName defines the name of the persistent session token which may be reflected in the cookie or the header. Users should avoid reusing session names to prevent unintended consequences, such as rejection or unpredictable behavior.\n\nSupport: Implementation-specific", + Type: []string{"string"}, + Format: "", + }, + }, + "absoluteTimeout": { + SchemaProps: spec.SchemaProps{ + Description: "AbsoluteTimeout defines the absolute timeout of the persistent session. Once the AbsoluteTimeout duration has elapsed, the session becomes invalid.\n\nSupport: Extended", + Type: []string{"string"}, + Format: "", + }, + }, + "idleTimeout": { + SchemaProps: spec.SchemaProps{ + Description: "IdleTimeout defines the idle timeout of the persistent session. Once the session has been idle for more than the specified IdleTimeout duration, the session becomes invalid.\n\nSupport: Extended", + Type: []string{"string"}, + Format: "", + }, + }, + "type": { + SchemaProps: spec.SchemaProps{ + Description: "Type defines the type of session persistence such as through the use a header or cookie. Defaults to cookie based session persistence.\n\nSupport: Core for \"Cookie\" type\n\nSupport: Extended for \"Header\" type", + Type: []string{"string"}, + Format: "", + }, + }, + "cookieConfig": { + SchemaProps: spec.SchemaProps{ + Description: "CookieConfig provides configuration settings that are specific to cookie-based session persistence.\n\nSupport: Core", + Ref: ref("sigs.k8s.io/gateway-api/apis/v1.CookieConfig"), + }, + }, + }, + }, + }, + Dependencies: []string{ + "sigs.k8s.io/gateway-api/apis/v1.CookieConfig"}, + } +} + +func schema_sigsk8sio_gateway_api_apis_v1_SubjectAltName(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "SubjectAltName represents Subject Alternative Name.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "type": { + SchemaProps: spec.SchemaProps{ + Description: "Type determines the format of the Subject Alternative Name. Always required.\n\nSupport: Core", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "hostname": { + SchemaProps: spec.SchemaProps{ + Description: "Hostname contains Subject Alternative Name specified in DNS name format. Required when Type is set to Hostname, ignored otherwise.\n\nSupport: Core", + Type: []string{"string"}, + Format: "", + }, + }, + "uri": { + SchemaProps: spec.SchemaProps{ + Description: "URI contains Subject Alternative Name specified in a full URI format. It MUST include both a scheme (e.g., \"http\" or \"ftp\") and a scheme-specific-part. Common values include SPIFFE IDs like \"spiffe://mycluster.example.com/ns/myns/sa/svc1sa\". Required when Type is set to URI, ignored otherwise.\n\nSupport: Core", + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"type"}, + }, + }, + } +} + +func schema_sigsk8sio_gateway_api_apis_v1_SupportedFeature(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "name": { + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"name"}, + }, + }, + } +} + +func schema_sigsk8sio_gateway_api_apis_v1_TLSConfig(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "TLSConfig describes TLS configuration that can apply to multiple Listeners within this Gateway. Currently, it stores only the client certificate validation configuration, but this may be extended in the future.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "validation": { + SchemaProps: spec.SchemaProps{ + Description: "Validation holds configuration information for validating the frontend (client). Setting this field will result in mutual authentication when connecting to the gateway. In browsers this may result in a dialog appearing that requests a user to specify the client certificate. The maximum depth of a certificate chain accepted in verification is Implementation specific.\n\nSupport: Core\n\n", + Ref: ref("sigs.k8s.io/gateway-api/apis/v1.FrontendTLSValidation"), + }, + }, + }, + }, + }, + Dependencies: []string{ + "sigs.k8s.io/gateway-api/apis/v1.FrontendTLSValidation"}, + } +} + +func schema_sigsk8sio_gateway_api_apis_v1_TLSPortConfig(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "port": { + SchemaProps: spec.SchemaProps{ + Description: "The Port indicates the Port Number to which the TLS configuration will be applied. This configuration will be applied to all Listeners handling HTTPS traffic that match this port.\n\nSupport: Core\n\n", + Default: 0, + Type: []string{"integer"}, + Format: "int32", + }, + }, + "tls": { + SchemaProps: spec.SchemaProps{ + Description: "TLS store the configuration that will be applied to all Listeners handling HTTPS traffic and matching given port.\n\nSupport: Core\n\n", + Default: map[string]interface{}{}, + Ref: ref("sigs.k8s.io/gateway-api/apis/v1.TLSConfig"), + }, + }, + }, + Required: []string{"port", "tls"}, + }, + }, + Dependencies: []string{ + "sigs.k8s.io/gateway-api/apis/v1.TLSConfig"}, + } +} + +func schema_sigsk8sio_gateway_api_apis_v1_supportedFeatureInternal(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "This is solely for the purpose of ensuring backward compatibility and SHOULD NOT be used elsewhere.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "name": { + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"name"}, + }, + }, + } +} diff --git a/internal/informers/core.go b/internal/informers/core.go new file mode 100644 index 00000000000..631d7c66937 --- /dev/null +++ b/internal/informers/core.go @@ -0,0 +1,77 @@ +/* +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. +*/ + +package informers + +import ( + corev1 "k8s.io/api/core/v1" + certificatesv1 "k8s.io/client-go/informers/certificates/v1" + networkingv1informers "k8s.io/client-go/informers/networking/v1" + corev1listers "k8s.io/client-go/listers/core/v1" + "k8s.io/client-go/tools/cache" +) + +// This file contains common informers functionality such as shared interfaces +// The interfaces defined here are mostly a subset of similar interfaces upstream. +// Defining our own instead of reusing the upstream ones allows us to: +// - create smaller interfaces that don't have methods that our control loops don't need (thus avoid defining unnecessary methods in implementations) +// - swap embedded upstream interfaces for our own ones + +var secretsGVR = corev1.SchemeGroupVersion.WithResource("secrets") + +const pleaseOpenIssue = "Please report this by opening an issue with this error and cert-manager controller logs and stack trace https://github.com/cert-manager/cert-manager/issues/new/choose" + +// KubeSharedInformerFactory represents a subset of methods in +// informers.sharedInformerFactory. It allows us to use a wrapper around +// informers.sharedInformerFactory to enforce particular custom informers for +// certain types, for example a filtered informer for Secrets If you need to +// start watching a new core type, add a method that returns an informer for +// that type here. If you don't need special filters, make it return an informer +// from baseFactory +type KubeInformerFactory interface { + Start(<-chan struct{}) + WaitForCacheSync(<-chan struct{}) map[string]bool + Shutdown() + Ingresses() networkingv1informers.IngressInformer + Secrets() SecretInformer + CertificateSigningRequests() certificatesv1.CertificateSigningRequestInformer +} + +// SecretInformer is like client-go SecretInformer +// https://github.com/kubernetes/client-go/blob/release-1.26/informers/core/v1/secret.go#L35-L40 +// but embeds our own interfaces with a smaller subset of methods. +type SecretInformer interface { + // Informer ensures that an Informer has been initialized and returns the initialized informer. + Informer() Informer + // Lister returns a lister for the initialized informer. It will also ensure that the informer exists. + Lister() SecretLister +} + +// SecretLister is a subset of client-go SecretLister functionality https://github.com/kubernetes/client-go/blob/release-1.26/listers/core/v1/secret.go#L28-L37 +type SecretLister interface { + // Secrets returns a namespace secrets getter/lister + Secrets(namespace string) corev1listers.SecretNamespaceLister +} + +// Informer is a subset of client-go SharedIndexInformer https://github.com/kubernetes/client-go/blob/release-1.26/tools/cache/shared_informer.go#L35-L211 +type Informer interface { + // AddEventHandler allows the reconcile loop to register an event handler so + // it gets triggered when the informer has a new event + AddEventHandler(handler cache.ResourceEventHandler) (cache.ResourceEventHandlerRegistration, error) + // HasSynced returns true if the informer's cache has synced (at least + // one LIST has been performed) + HasSynced() bool +} diff --git a/internal/informers/core_basic.go b/internal/informers/core_basic.go new file mode 100644 index 00000000000..343dec9ec9e --- /dev/null +++ b/internal/informers/core_basic.go @@ -0,0 +1,107 @@ +/* +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. +*/ + +package informers + +import ( + "time" + + corev1 "k8s.io/api/core/v1" + kubeinformers "k8s.io/client-go/informers" + certificatesv1 "k8s.io/client-go/informers/certificates/v1" + corev1informers "k8s.io/client-go/informers/core/v1" + networkingv1informers "k8s.io/client-go/informers/networking/v1" + "k8s.io/client-go/kubernetes" + corev1listers "k8s.io/client-go/listers/core/v1" + "k8s.io/client-go/tools/cache" +) + +// This file contains an implementation of core informers that wraps the core +// upstream informers without any custom modifications + +// baseFactory is an implementation of KubeSharedInformerFactory that returns +// standard upstream informer functionality +type baseFactory struct { + f kubeinformers.SharedInformerFactory + // namespace is set if cert-manager controller is scoped to a single + // namespace + namespace string +} + +func NewBaseKubeInformerFactory(client kubernetes.Interface, resync time.Duration, namespace string) KubeInformerFactory { + return &baseFactory{ + f: kubeinformers.NewSharedInformerFactoryWithOptions(client, resync, kubeinformers.WithNamespace(namespace)), + // namespace is set to a non-empty value if cert-manager + // controller is scoped to a single namespace via --namespace + // flag + namespace: namespace, + } +} + +func (bf *baseFactory) Start(stopCh <-chan struct{}) { + bf.f.Start(stopCh) +} + +func (bf *baseFactory) WaitForCacheSync(stopCh <-chan struct{}) map[string]bool { + ret := make(map[string]bool) + cacheSyncs := bf.f.WaitForCacheSync(stopCh) + for key, val := range cacheSyncs { + ret[key.String()] = val + } + return ret +} + +func (bf *baseFactory) Shutdown() { + bf.f.Shutdown() +} + +func (bf *baseFactory) Ingresses() networkingv1informers.IngressInformer { + return bf.f.Networking().V1().Ingresses() +} + +func (bf *baseFactory) Secrets() SecretInformer { + return &baseSecretInformer{ + f: bf.f, + namespace: bf.namespace, + } +} + +func (bf *baseFactory) CertificateSigningRequests() certificatesv1.CertificateSigningRequestInformer { + return bf.f.Certificates().V1().CertificateSigningRequests() +} + +var _ SecretInformer = &baseSecretInformer{} + +// baseSecretInformer is an implementation of SecretInformer that only uses +// upstream client-go functionality +type baseSecretInformer struct { + f kubeinformers.SharedInformerFactory + informer cache.SharedIndexInformer + namespace string +} + +func (bsi *baseSecretInformer) Informer() Informer { + bsi.informer = bsi.f.InformerFor(&corev1.Secret{}, bsi.new) + return bsi.informer +} + +func (bsi *baseSecretInformer) Lister() SecretLister { + return corev1listers.NewSecretLister(bsi.f.InformerFor(&corev1.Secret{}, bsi.new).GetIndexer()) +} + +func (bsi *baseSecretInformer) new(client kubernetes.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { + return corev1informers.NewSecretInformer(client, bsi.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}) +} diff --git a/internal/informers/core_filteredsecrets.go b/internal/informers/core_filteredsecrets.go new file mode 100644 index 00000000000..ffbc9138d74 --- /dev/null +++ b/internal/informers/core_filteredsecrets.go @@ -0,0 +1,340 @@ +/* +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. +*/ + +package informers + +import ( + "context" + "fmt" + "time" + + corev1 "k8s.io/api/core/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/selection" + "k8s.io/apimachinery/pkg/types" + kubeinformers "k8s.io/client-go/informers" + certificatesv1 "k8s.io/client-go/informers/certificates/v1" + corev1informers "k8s.io/client-go/informers/core/v1" + internalinterfaces "k8s.io/client-go/informers/internalinterfaces" + networkingv1informers "k8s.io/client-go/informers/networking/v1" + "k8s.io/client-go/kubernetes" + typedcorev1 "k8s.io/client-go/kubernetes/typed/core/v1" + corev1listers "k8s.io/client-go/listers/core/v1" + "k8s.io/client-go/metadata" + "k8s.io/client-go/metadata/metadatainformer" + "k8s.io/client-go/metadata/metadatalister" + "k8s.io/client-go/tools/cache" + + cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" + logf "github.com/cert-manager/cert-manager/pkg/logs" +) + +// This file contains all the functionality for implementing core informers with a filter for Secrets +// https://github.com/cert-manager/cert-manager/blob/master/design/20221205-memory-management.md +var ( + isCertManageSecretLabelSelector labels.Selector + isNotCertManagerSecretLabelSelector labels.Selector +) + +func init() { + r, err := labels.NewRequirement(cmapi.PartOfCertManagerControllerLabelKey, selection.Equals, []string{"true"}) + if err != nil { + panic(fmt.Errorf("internal error: failed to build label selector to filter cert-manager secrets: %w", err)) + } + isCertManageSecretLabelSelector = labels.NewSelector().Add(*r) + + r, err = labels.NewRequirement(cmapi.PartOfCertManagerControllerLabelKey, selection.DoesNotExist, nil) + if err != nil { + panic(fmt.Errorf("internal error: failed to build label selector to filter non-cert-manager secrets: %w", err)) + } + isNotCertManagerSecretLabelSelector = labels.NewSelector().Add(*r) +} + +type filteredSecretsFactory struct { + typedInformerFactory kubeinformers.SharedInformerFactory + metadataInformerFactory metadatainformer.SharedInformerFactory + client kubernetes.Interface + namespace string + ctx context.Context +} + +func NewFilteredSecretsKubeInformerFactory(ctx context.Context, typedClient kubernetes.Interface, metadataClient metadata.Interface, resync time.Duration, namespace string) KubeInformerFactory { + return &filteredSecretsFactory{ + typedInformerFactory: kubeinformers.NewSharedInformerFactoryWithOptions(typedClient, resync, kubeinformers.WithNamespace(namespace)), + metadataInformerFactory: metadatainformer.NewFilteredSharedInformerFactory(metadataClient, resync, namespace, func(listOptions *metav1.ListOptions) { + listOptions.LabelSelector = isNotCertManagerSecretLabelSelector.String() + + }), + // namespace is set to a non-empty value if cert-manager + // controller is scoped to a single namespace via --namespace + // flag + namespace: namespace, + client: typedClient, + // Go recommends to not store context in + // structs, but here we have no other way as we need to use root context inside + // Get whose signature is defined upstream and does not accept context + ctx: ctx, + } +} + +func (bf *filteredSecretsFactory) Start(stopCh <-chan struct{}) { + bf.typedInformerFactory.Start(stopCh) + bf.metadataInformerFactory.Start(stopCh) +} + +func (bf *filteredSecretsFactory) WaitForCacheSync(stopCh <-chan struct{}) map[string]bool { + caches := make(map[string]bool) + typedCaches := bf.typedInformerFactory.WaitForCacheSync(stopCh) + partialMetaCaches := bf.metadataInformerFactory.WaitForCacheSync(stopCh) + // We have to cast the keys into string type. It is not possible to + // create a generic type here as neither of the types returned by + // WaitForCacheSync are valid map key arguments in generics - they aren't + // comparable types. + for key, val := range typedCaches { + caches[key.String()] = val + } + for key, val := range partialMetaCaches { + caches[key.String()] = val + } + return caches +} + +func (bf *filteredSecretsFactory) Shutdown() { + bf.typedInformerFactory.Shutdown() + bf.metadataInformerFactory.Shutdown() +} + +func (bf *filteredSecretsFactory) Ingresses() networkingv1informers.IngressInformer { + return bf.typedInformerFactory.Networking().V1().Ingresses() +} + +func (bf *filteredSecretsFactory) CertificateSigningRequests() certificatesv1.CertificateSigningRequestInformer { + return bf.typedInformerFactory.Certificates().V1().CertificateSigningRequests() +} + +func (bf *filteredSecretsFactory) Secrets() SecretInformer { + f := func(client kubernetes.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { + return corev1informers.NewFilteredSecretInformer(client, bf.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, func(listOptions *metav1.ListOptions) { + listOptions.LabelSelector = isCertManageSecretLabelSelector.String() + }) + } + return &filteredSecretInformer{ + typedInformerFactory: bf.typedInformerFactory, + metadataInformerFactory: bf.metadataInformerFactory, + namespace: bf.namespace, + typedClient: bf.client.CoreV1(), + newTyped: f, + ctx: bf.ctx, + } +} + +// filteredSecretInformer is an implementation of SecretInformer that uses two +// caches (typed and metadata) to list and watch Secrets +type filteredSecretInformer struct { + typedInformerFactory kubeinformers.SharedInformerFactory + metadataInformerFactory metadatainformer.SharedInformerFactory + typedClient typedcorev1.SecretsGetter + newTyped internalinterfaces.NewInformerFunc + + namespace string + // Go recommends to not store context in + // structs, but here we have no other way as we need to use root context inside + // Get whose signature is defined upstream and does not accept context + ctx context.Context +} + +func (f *filteredSecretInformer) Informer() Informer { + typedInformer := f.typedInformerFactory.InformerFor(&corev1.Secret{}, f.newTyped) + + metadataInformer := f.metadataInformerFactory.ForResource(secretsGVR).Informer() + if err := metadataInformer.SetTransform(partialMetadataRemoveAll); err != nil { + panic(fmt.Sprintf("internal error: error setting transformer on the metadata informer: %v", err)) + } + return &informer{ + typedInformer: typedInformer, + metadataInformer: metadataInformer, + } +} + +func (f *filteredSecretInformer) Lister() SecretLister { + typedLister := corev1listers.NewSecretLister(f.typedInformerFactory.InformerFor(&corev1.Secret{}, f.newTyped).GetIndexer()) + metadataLister := metadatalister.New(f.metadataInformerFactory.ForResource(secretsGVR).Informer().GetIndexer(), secretsGVR) + return &secretLister{ + typedClient: f.typedClient, + namespace: f.namespace, + typedLister: typedLister, + partialMetadataLister: metadataLister, + ctx: f.ctx, + } +} + +// informer is an implementation of Informer interface +type informer struct { + typedInformer cache.SharedIndexInformer + metadataInformer cache.SharedIndexInformer +} + +func (i *informer) HasSynced() bool { + return i.typedInformer.HasSynced() && i.metadataInformer.HasSynced() +} + +func (i *informer) AddEventHandler(handler cache.ResourceEventHandler) (cache.ResourceEventHandlerRegistration, error) { + _, err := i.metadataInformer.AddEventHandler(handler) + if err != nil { + return nil, err + } + _, err = i.typedInformer.AddEventHandler(handler) + return nil, err +} + +// secretLister is an implementation of SecretLister with a namespaced lister +// that knows how to do conditional GET/LIST of Secrets using a combination of +// typed and metadata cache and kube apiserver +type secretLister struct { + namespace string + partialMetadataLister metadatalister.Lister + typedLister corev1listers.SecretLister + typedClient typedcorev1.SecretsGetter + // Go recommends to not store context in + // structs, but here we have no other way as we need to use root context inside + // Get whose signature is defined upstream and does not accept context + ctx context.Context +} + +func (sl *secretLister) Secrets(namespace string) corev1listers.SecretNamespaceLister { + return &secretNamespaceLister{ + namespace: namespace, + partialMetadataLister: sl.partialMetadataLister, + typedLister: sl.typedLister, + typedClient: sl.typedClient, + ctx: sl.ctx, + } +} + +var _ corev1listers.SecretNamespaceLister = &secretNamespaceLister{} + +// secretNamespaceLister is an implementation of +// corelisters.SecretNamespaceLister +// https://github.com/kubernetes/client-go/blob/0382bf0f53b2294d4ac448203718f0ba774a477d/listers/core/v1/secret.go#L62-L72. +// It knows how to get and list Secrets using typed and partial metadata caches +// and kube apiserver. It looks for Secrets in both caches, if the Secret is +// found in metadata cache, it will retrieve it from kube apiserver. +type secretNamespaceLister struct { + namespace string + partialMetadataLister metadatalister.Lister + typedLister corev1listers.SecretLister + typedClient typedcorev1.SecretsGetter + // Go recommends to not store context in + // structs, but here we have no other way as we need to use root context inside + // Get whose signature is defined upstream and does not accept context + ctx context.Context +} + +func (snl *secretNamespaceLister) Get(name string) (*corev1.Secret, error) { + log := logf.FromContext(snl.ctx) + log = log.WithValues("secret", name, "namespace", snl.namespace) + + var secretFoundInTypedCache, secretFoundInMetadataCache bool + secret, typedCacheErr := snl.typedLister.Secrets(snl.namespace).Get(name) + if typedCacheErr == nil { + secretFoundInTypedCache = true + } + + if typedCacheErr != nil && !apierrors.IsNotFound(typedCacheErr) { + log.Error(typedCacheErr, "error getting secret from typed cache") + return nil, fmt.Errorf("error retrieving secret from the typed cache: %w", typedCacheErr) + } + _, partialMetadataGetErr := snl.partialMetadataLister.Namespace(snl.namespace).Get(name) + if partialMetadataGetErr == nil { + secretFoundInMetadataCache = true + } + + if partialMetadataGetErr != nil && !apierrors.IsNotFound(partialMetadataGetErr) { + log.Error(partialMetadataGetErr, "error getting secret from metadata cache") + return nil, fmt.Errorf("error retrieving object from partial object metadata cache: %w", partialMetadataGetErr) + } + + if secretFoundInMetadataCache { + // if secret is found in both caches log an error and return the version from kube apiserver + if secretFoundInTypedCache { + key := types.NamespacedName{Namespace: snl.namespace, Name: name} + log.Info(fmt.Sprintf("warning: possible internal error: stale cache: secret found both in typed cache and in partial cache: %s", pleaseOpenIssue), "secret", key) + } + return snl.typedClient.Secrets(snl.namespace).Get(snl.ctx, name, metav1.GetOptions{}) + } + + if secretFoundInTypedCache { + return secret, nil + } + + // If we get here it is because secret was found neither in typed cache + // nor partial metadata cache + return nil, apierrors.NewNotFound(schema.GroupResource{Group: corev1.GroupName, Resource: corev1.ResourceSecrets.String()}, name) +} + +func (snl *secretNamespaceLister) List(selector labels.Selector) ([]*corev1.Secret, error) { + log := logf.FromContext(snl.ctx) + log = log.WithValues("secrets namespace", snl.namespace, "secrets selector", selector.String()) + matchingSecretsMap := make(map[types.NamespacedName]*corev1.Secret) + typedSecrets, err := snl.typedLister.List(selector) + if err != nil { + log.Error(err, "error listing Secrets from typed cache") + return nil, fmt.Errorf("error listing Secrets from typed cache: %w", err) + } + for _, secret := range typedSecrets { + key := types.NamespacedName{Namespace: secret.Namespace, Name: secret.Name} + matchingSecretsMap[key] = secret + } + metadataSecrets, err := snl.partialMetadataLister.List(selector) + if err != nil { + log.Error(err, "error listing Secrets from metadata only cache") + return nil, fmt.Errorf("error listing Secrets from metadata only cache: %w", err) + } + + if len(metadataSecrets) > 0 { + // We currently do not LIST unlabelled Secrets. This log line is + // here in case we do it sometime in the future at which point + // we can see whether the metadata functionality is performant + // enough. + log.V(logf.InfoLevel).Info("unexpected behaviour: secrets LISTed from metadata cache. Please open an issue") + } + // In practice this section will never be used. The only place + // where we LIST Secrets is in keymanager controller where we list + // temporary Certificate Secrets which are all labelled. + // It is unlikely that we will every list unlabelled Secrets. + for _, secretMeta := range metadataSecrets { + key := types.NamespacedName{Namespace: secretMeta.Namespace, Name: secretMeta.Name} + if _, ok := matchingSecretsMap[key]; ok { + log.Info(fmt.Sprintf("warning: possible internal error: stale cache: secret found both in typed cache and in partial cache: %s", pleaseOpenIssue), "secret name", secretMeta.Name) + // in case of duplicates, return the version from kube apiserver + } + secret, err := snl.typedClient.Secrets(snl.namespace).Get(snl.ctx, secretMeta.Name, metav1.GetOptions{}) + if err != nil { + log.Error(err, "error retrieving secret from kube apiserver", "secret name", secretMeta.Name) + return nil, fmt.Errorf("error retrieving Secret from kube apiserver: %w", err) + } + matchingSecretsMap[key] = secret + } + + matchingSecrets := make([]*corev1.Secret, 0) + for _, val := range matchingSecretsMap { + matchingSecrets = append(matchingSecrets, val) + } + return matchingSecrets, nil +} diff --git a/internal/informers/core_filteredsecrets_test.go b/internal/informers/core_filteredsecrets_test.go new file mode 100644 index 00000000000..85d5fba638f --- /dev/null +++ b/internal/informers/core_filteredsecrets_test.go @@ -0,0 +1,451 @@ +/* +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. +*/ + +package informers + +import ( + "context" + "errors" + "reflect" + "testing" + + "github.com/stretchr/testify/assert" + corev1 "k8s.io/api/core/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/runtime/schema" + typedcorev1 "k8s.io/client-go/kubernetes/typed/core/v1" + corev1listers "k8s.io/client-go/listers/core/v1" + "k8s.io/client-go/metadata/metadatalister" +) + +func Test_secretNamespaceLister_Get(t *testing.T) { + + var ( + data = []byte("foo") + testSecret = &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + Namespace: "foo", + }, + Data: map[string][]byte{"foo": data}, + } + ) + tests := map[string]struct { + namespace string + name string + partialMetadataLister metadatalister.Lister + typedLister corev1listers.SecretLister + typedClient typedcorev1.SecretsGetter + want *corev1.Secret + wantErr bool + }{ + "if querying typed cache returns an error that is not 'not found' error, return the error": { + namespace: "foo", + name: "foo", + typedLister: FakeSecretLister{ + NamespaceLister: FakeSecretNamespaceLister{ + FakeGet: func(string) (*corev1.Secret, error) { + return nil, errors.New("some error") + }, + }, + }, + wantErr: true, + }, + "if querying metadata cache returns an error that is not a 'not found' error, return the error": { + namespace: "foo", + name: "foo", + typedLister: FakeSecretLister{ + NamespaceLister: FakeSecretNamespaceLister{ + FakeGet: func(string) (*corev1.Secret, error) { + return nil, nil + }, + }, + }, + partialMetadataLister: FakeMetadataLister{ + NamespaceLister: FakeMetadataNamespaceLister{ + FakeGet: func(string) (*metav1.PartialObjectMetadata, error) { + return nil, errors.New("some error") + }, + }, + }, + wantErr: true, + }, + "if Secret found in typed cache, return it from there": { + namespace: "foo", + name: "foo", + typedLister: FakeSecretLister{ + NamespaceLister: FakeSecretNamespaceLister{ + FakeGet: func(string) (*corev1.Secret, error) { + return testSecret, nil + }, + }, + }, + partialMetadataLister: FakeMetadataLister{ + NamespaceLister: FakeMetadataNamespaceLister{ + FakeGet: func(string) (*metav1.PartialObjectMetadata, error) { + return nil, apierrors.NewNotFound(schema.GroupResource{}, "foo") + }, + }, + }, + want: testSecret, + }, + "if Secret found in metadata cache, return it from kube apiserver": { + namespace: "foo", + name: "foo", + typedLister: FakeSecretLister{ + NamespaceLister: FakeSecretNamespaceLister{ + FakeGet: func(string) (*corev1.Secret, error) { + return nil, apierrors.NewNotFound(schema.GroupResource{}, "foo") + }, + }, + }, + partialMetadataLister: FakeMetadataLister{ + NamespaceLister: FakeMetadataNamespaceLister{ + FakeGet: func(string) (*metav1.PartialObjectMetadata, error) { + return &metav1.PartialObjectMetadata{}, nil + }, + }, + }, + typedClient: FakeSecretsGetter{ + FakeSecrets: func(string) typedcorev1.SecretInterface { + return FakeSecretInterface{ + FakeGet: func(context.Context, string, metav1.GetOptions) (*corev1.Secret, error) { + return testSecret, nil + }, + } + }, + }, + want: testSecret, + }, + "if Secret found in both caches, return it from kube apiserver": { + namespace: "foo", + name: "foo", + typedLister: FakeSecretLister{ + NamespaceLister: FakeSecretNamespaceLister{ + FakeGet: func(string) (*corev1.Secret, error) { + return &corev1.Secret{}, nil + }, + }, + }, + partialMetadataLister: FakeMetadataLister{ + NamespaceLister: FakeMetadataNamespaceLister{ + FakeGet: func(string) (*metav1.PartialObjectMetadata, error) { + return &metav1.PartialObjectMetadata{}, nil + }, + }, + }, + typedClient: FakeSecretsGetter{ + FakeSecrets: func(string) typedcorev1.SecretInterface { + return FakeSecretInterface{ + FakeGet: func(context.Context, string, metav1.GetOptions) (*corev1.Secret, error) { + return testSecret, nil + }, + } + }, + }, + want: testSecret, + }, + "if Secret found in metadata cache, but querying kube apiserver errors, return the error": { + namespace: "foo", + name: "foo", + typedLister: FakeSecretLister{ + NamespaceLister: FakeSecretNamespaceLister{ + FakeGet: func(string) (*corev1.Secret, error) { + return nil, apierrors.NewNotFound(schema.GroupResource{}, "foo") + }, + }, + }, + partialMetadataLister: FakeMetadataLister{ + NamespaceLister: FakeMetadataNamespaceLister{ + FakeGet: func(string) (*metav1.PartialObjectMetadata, error) { + return &metav1.PartialObjectMetadata{}, nil + }, + }, + }, + typedClient: FakeSecretsGetter{ + FakeSecrets: func(string) typedcorev1.SecretInterface { + return FakeSecretInterface{ + FakeGet: func(context.Context, string, metav1.GetOptions) (*corev1.Secret, error) { + return nil, errors.New("some error") + }, + } + }, + }, + wantErr: true, + }, + "if Secret found not found in either cache return not found error": { + namespace: "foo", + name: "foo", + typedLister: FakeSecretLister{ + NamespaceLister: FakeSecretNamespaceLister{ + FakeGet: func(string) (*corev1.Secret, error) { + return nil, apierrors.NewNotFound(schema.GroupResource{}, "foo") + }, + }, + }, + partialMetadataLister: FakeMetadataLister{ + NamespaceLister: FakeMetadataNamespaceLister{ + FakeGet: func(string) (*metav1.PartialObjectMetadata, error) { + return &metav1.PartialObjectMetadata{}, apierrors.NewNotFound(schema.GroupResource{}, "foo") + }, + }, + }, + typedClient: FakeSecretsGetter{ + FakeSecrets: func(string) typedcorev1.SecretInterface { + return FakeSecretInterface{ + FakeGet: func(context.Context, string, metav1.GetOptions) (*corev1.Secret, error) { + return nil, errors.New("some error") + }, + } + }, + }, + wantErr: true, + }, + } + for name, scenario := range tests { + t.Run(name, func(t *testing.T) { + snl := &secretNamespaceLister{ + namespace: scenario.namespace, + partialMetadataLister: scenario.partialMetadataLister, + typedLister: scenario.typedLister, + typedClient: scenario.typedClient, + ctx: t.Context(), + } + got, err := snl.Get(name) + if (err != nil) != scenario.wantErr { + t.Errorf("secretNamespaceLister.Get() error = %v, wantErr %v", err, scenario.wantErr) + return + } + if !reflect.DeepEqual(got, scenario.want) { + t.Errorf("secretNamespaceLister.Get() = %v, want %v", got, scenario.want) + } + }) + } +} + +func Test_secretNamespaceLister_List(t *testing.T) { + + var ( + someData = []byte("foobar") + someSelector = labels.Everything() + secretFoo = corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + Namespace: "foo", + }, + Data: map[string][]byte{"someKey": someData}, + } + secretFoo2 = corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + Namespace: "foo", + }, + Data: map[string][]byte{"someOtherKey": someData}, + } + secretBar = corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "bar", + Namespace: "bar", + }, + Data: map[string][]byte{"someKey": someData}, + } + secretFooMeta = metav1.PartialObjectMetadata{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + Namespace: "foo", + }, + } + ) + tests := map[string]struct { + namespace string + partialMetadataLister metadatalister.Lister + typedLister corev1listers.SecretLister + typedClient typedcorev1.SecretsGetter + want []*corev1.Secret + wantErr bool + }{ + "if listing Secrets from typed cache errors out then return the error": { + + namespace: "foo", + typedLister: FakeSecretLister{ + FakeList: func(labels.Selector) ([]*corev1.Secret, error) { + return nil, errors.New("some error") + }, + }, + wantErr: true, + }, + "if listing Secrets from metadata cache errors out then return the error": { + + namespace: "foo", + typedLister: FakeSecretLister{ + FakeList: func(labels.Selector) ([]*corev1.Secret, error) { + return nil, nil + }, + }, + partialMetadataLister: FakeMetadataLister{ + FakeList: func(labels.Selector) ([]*metav1.PartialObjectMetadata, error) { + return nil, errors.New("some error") + }, + }, + wantErr: true, + }, + "if no Secrets are found within either cache, don't return any": { + + namespace: "foo", + typedLister: FakeSecretLister{ + FakeList: func(labels.Selector) ([]*corev1.Secret, error) { + return nil, nil + }, + }, + partialMetadataLister: FakeMetadataLister{ + FakeList: func(labels.Selector) ([]*metav1.PartialObjectMetadata, error) { + return nil, nil + }, + }, + want: make([]*corev1.Secret, 0), + }, + "if some Secrets are found in typed cache, return those": { + + namespace: "foo", + typedLister: FakeSecretLister{ + FakeList: func(labels.Selector) ([]*corev1.Secret, error) { + return []*corev1.Secret{&secretBar, &secretFoo}, nil + }, + }, + partialMetadataLister: FakeMetadataLister{ + FakeList: func(labels.Selector) ([]*metav1.PartialObjectMetadata, error) { + return nil, nil + }, + }, + want: []*corev1.Secret{&secretBar, &secretFoo}, + }, + "if a Secret is found in metadata only cache, return it from kube apiserver": { + + namespace: "foo", + typedLister: FakeSecretLister{ + FakeList: func(labels.Selector) ([]*corev1.Secret, error) { + return nil, nil + }, + }, + partialMetadataLister: FakeMetadataLister{ + FakeList: func(labels.Selector) ([]*metav1.PartialObjectMetadata, error) { + return []*metav1.PartialObjectMetadata{&secretFooMeta}, nil + }, + }, + typedClient: FakeSecretsGetter{ + FakeSecrets: func(string) typedcorev1.SecretInterface { + return FakeSecretInterface{ + FakeGet: func(context.Context, string, metav1.GetOptions) (*corev1.Secret, error) { + return &secretFoo, nil + }, + } + }, + }, + want: []*corev1.Secret{&secretFoo}, + }, + "if matching non-duplicate Secrets are found in both caches, return them": { + + namespace: "foo", + typedLister: FakeSecretLister{ + FakeList: func(labels.Selector) ([]*corev1.Secret, error) { + return []*corev1.Secret{&secretBar}, nil + }, + }, + partialMetadataLister: FakeMetadataLister{ + FakeList: func(labels.Selector) ([]*metav1.PartialObjectMetadata, error) { + return []*metav1.PartialObjectMetadata{&secretFooMeta}, nil + }, + }, + typedClient: FakeSecretsGetter{ + FakeSecrets: func(string) typedcorev1.SecretInterface { + return FakeSecretInterface{ + FakeGet: func(context.Context, string, metav1.GetOptions) (*corev1.Secret, error) { + return &secretFoo, nil + }, + } + }, + }, + want: []*corev1.Secret{&secretFoo, &secretBar}, + }, + "if matching Secrets are found in both caches with some duplicates, then returned the duplicates from kube apiserver": { + + namespace: "foo", + typedLister: FakeSecretLister{ + FakeList: func(labels.Selector) ([]*corev1.Secret, error) { + return []*corev1.Secret{&secretFoo2}, nil + }, + }, + partialMetadataLister: FakeMetadataLister{ + FakeList: func(labels.Selector) ([]*metav1.PartialObjectMetadata, error) { + return []*metav1.PartialObjectMetadata{&secretFooMeta}, nil + }, + }, + typedClient: FakeSecretsGetter{ + FakeSecrets: func(string) typedcorev1.SecretInterface { + return FakeSecretInterface{ + FakeGet: func(context.Context, string, metav1.GetOptions) (*corev1.Secret, error) { + return &secretFoo, nil + }, + } + }, + }, + want: []*corev1.Secret{&secretFoo}, + }, + "if a Secret is found in metadata only cache, but querying kube apiserver errors, return the error": { + + namespace: "foo", + typedLister: FakeSecretLister{ + FakeList: func(labels.Selector) ([]*corev1.Secret, error) { + return nil, nil + }, + }, + partialMetadataLister: FakeMetadataLister{ + FakeList: func(labels.Selector) ([]*metav1.PartialObjectMetadata, error) { + return []*metav1.PartialObjectMetadata{&secretFooMeta}, nil + }, + }, + typedClient: FakeSecretsGetter{ + FakeSecrets: func(string) typedcorev1.SecretInterface { + return FakeSecretInterface{ + FakeGet: func(context.Context, string, metav1.GetOptions) (*corev1.Secret, error) { + return nil, errors.New("some error") + }, + } + }, + }, + wantErr: true, + }, + } + for name, scenario := range tests { + t.Run(name, func(t *testing.T) { + snl := &secretNamespaceLister{ + namespace: scenario.namespace, + partialMetadataLister: scenario.partialMetadataLister, + typedLister: scenario.typedLister, + typedClient: scenario.typedClient, + ctx: t.Context(), + } + got, err := snl.List(someSelector) + if (err != nil) != scenario.wantErr { + t.Errorf("secretNamespaceLister.List() error = %v, wantErr %v", err, scenario.wantErr) + return + } + assert.ElementsMatch(t, got, scenario.want) + }) + } +} diff --git a/internal/informers/fakes.go b/internal/informers/fakes.go new file mode 100644 index 00000000000..924aac79621 --- /dev/null +++ b/internal/informers/fakes.go @@ -0,0 +1,143 @@ +/* +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. +*/ + +package informers + +import ( + "context" + + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/watch" + applyconfigcorev1 "k8s.io/client-go/applyconfigurations/core/v1" + typedcorev1 "k8s.io/client-go/kubernetes/typed/core/v1" + corev1listers "k8s.io/client-go/listers/core/v1" + "k8s.io/client-go/metadata/metadatalister" +) + +// FakeSecretLister is a fake of SecretLister +// https://github.com/kubernetes/client-go/blob/0382bf0f53b2294d4ac448203718f0ba774a477d/listers/core/v1/secret.go#L28-L37 +type FakeSecretLister struct { + NamespaceLister FakeSecretNamespaceLister + FakeList func(labels.Selector) ([]*corev1.Secret, error) +} + +func (fsl FakeSecretLister) List(selector labels.Selector) ([]*corev1.Secret, error) { + return fsl.FakeList(selector) +} + +func (fsl FakeSecretLister) Secrets(namespace string) corev1listers.SecretNamespaceLister { + return fsl.NamespaceLister +} + +// FakeSecretNamespaceLister is a fake of SecretNamespaceLister +// https://github.com/kubernetes/client-go/blob/0382bf0f53b2294d4ac448203718f0ba774a477d/listers/core/v1/secret.go#L62-L72. +type FakeSecretNamespaceLister struct { + FakeList func(labels.Selector) ([]*corev1.Secret, error) + FakeGet func(string) (*corev1.Secret, error) +} + +func (fsnl FakeSecretNamespaceLister) List(selector labels.Selector) ([]*corev1.Secret, error) { + return fsnl.FakeList(selector) +} + +func (fsnl FakeSecretNamespaceLister) Get(name string) (*corev1.Secret, error) { + return fsnl.FakeGet(name) +} + +// FakeMetadataLister is a fake of metadata Lister +// https://github.com/kubernetes/client-go/blob/0382bf0f53b2294d4ac448203718f0ba774a477d/metadata/metadatalister/interface.go#L24-L32 +type FakeMetadataLister struct { + FakeList func(labels.Selector) ([]*metav1.PartialObjectMetadata, error) + FakeGet func(string) (*metav1.PartialObjectMetadata, error) + NamespaceLister metadatalister.NamespaceLister +} + +func (fml FakeMetadataLister) List(selector labels.Selector) ([]*metav1.PartialObjectMetadata, error) { + return fml.FakeList(selector) +} + +func (fml FakeMetadataLister) Get(name string) (*metav1.PartialObjectMetadata, error) { + return fml.FakeGet(name) +} + +func (fml FakeMetadataLister) Namespace(string) metadatalister.NamespaceLister { + return fml.NamespaceLister +} + +// FakeMetadataNamespaceLister is a fake of metadata NamespaceLister +// https://github.com/kubernetes/client-go/blob/0382bf0f53b2294d4ac448203718f0ba774a477d/metadata/metadatalister/interface.go#L34-L40 +type FakeMetadataNamespaceLister struct { + FakeList func(labels.Selector) ([]*metav1.PartialObjectMetadata, error) + FakeGet func(string) (*metav1.PartialObjectMetadata, error) +} + +func (fmnl FakeMetadataNamespaceLister) List(selector labels.Selector) ([]*metav1.PartialObjectMetadata, error) { + return fmnl.FakeList(selector) +} + +func (fmnl FakeMetadataNamespaceLister) Get(name string) (*metav1.PartialObjectMetadata, error) { + return fmnl.FakeGet(name) +} + +// FakeSecretsGetter is a fake of corev1 SecretsGetter +// https://github.com/kubernetes/client-go/blob/0382bf0f53b2294d4ac448203718f0ba774a477d/kubernetes/typed/core/v1/secret.go#L33-L37 +type FakeSecretsGetter struct { + FakeSecrets func(string) typedcorev1.SecretInterface +} + +func (fsg FakeSecretsGetter) Secrets(namespace string) typedcorev1.SecretInterface { + return fsg.FakeSecrets(namespace) +} + +// FakeSecretInterface is a fake of corev1 SecretInterface +// https://github.com/kubernetes/client-go/blob/0382bf0f53b2294d4ac448203718f0ba774a477d/kubernetes/typed/core/v1/secret.go#L39-L50 +type FakeSecretInterface struct { + FakeGet func(context.Context, string, metav1.GetOptions) (*corev1.Secret, error) + FakeList func(context.Context, metav1.ListOptions) (*corev1.SecretList, error) +} + +func (fsi FakeSecretInterface) Get(ctx context.Context, name string, opts metav1.GetOptions) (*corev1.Secret, error) { + return fsi.FakeGet(ctx, name, opts) +} +func (fsi FakeSecretInterface) List(ctx context.Context, opts metav1.ListOptions) (*corev1.SecretList, error) { + return fsi.FakeList(ctx, opts) +} + +func (fsi FakeSecretInterface) Create(ctx context.Context, secret *corev1.Secret, opts metav1.CreateOptions) (*corev1.Secret, error) { + panic("not implemented") +} + +func (fsi FakeSecretInterface) Update(ctx context.Context, secret *corev1.Secret, opts metav1.UpdateOptions) (*corev1.Secret, error) { + panic("not implemented") +} +func (fsi FakeSecretInterface) Delete(ctx context.Context, name string, opts metav1.DeleteOptions) error { + panic("not implemented") +} +func (fsi FakeSecretInterface) DeleteCollection(ctx context.Context, opts metav1.DeleteOptions, listOpts metav1.ListOptions) error { + panic("not implemented") +} +func (fsi FakeSecretInterface) Watch(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error) { + panic("not implemented") +} +func (fsi FakeSecretInterface) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts metav1.PatchOptions, subresources ...string) (result *corev1.Secret, err error) { + panic("not implemented") +} +func (fsi FakeSecretInterface) Apply(ctx context.Context, secret *applyconfigcorev1.SecretApplyConfiguration, opts metav1.ApplyOptions) (result *corev1.Secret, err error) { + panic("not implemented") +} diff --git a/internal/informers/transformers.go b/internal/informers/transformers.go new file mode 100644 index 00000000000..779f5c319f8 --- /dev/null +++ b/internal/informers/transformers.go @@ -0,0 +1,40 @@ +/* +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. +*/ + +package informers + +import ( + "fmt" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/tools/cache" +) + +var _ cache.TransformFunc = partialMetadataRemoveAll + +// partialMetadataRemoveAll implements a cache.TransformFunc that removes +// labels, annotations and managed +// fields from PartialObjectMetadata. +func partialMetadataRemoveAll(obj interface{}) (interface{}, error) { + partialMeta, ok := obj.(*metav1.PartialObjectMetadata) + if !ok { + return nil, fmt.Errorf("internal error: cannot cast object %#+v to PartialObjectMetadata", obj) + } + partialMeta.Annotations = nil + partialMeta.ManagedFields = nil + partialMeta.Labels = nil + return partialMeta, nil +} diff --git a/internal/kube/config.go b/internal/kube/config.go new file mode 100644 index 00000000000..cea2167f24e --- /dev/null +++ b/internal/kube/config.go @@ -0,0 +1,51 @@ +/* +Copyright 2021 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. +*/ + +package kube + +import ( + "fmt" + "os" + + "k8s.io/client-go/rest" + "k8s.io/client-go/tools/clientcmd" + clientcmdapi "k8s.io/client-go/tools/clientcmd/api" +) + +func BuildClientConfig(apiServerHost string, kubeConfig string) (*rest.Config, error) { + if apiServerHost == "" && kubeConfig == "" { + return rest.InClusterConfig() + } + return clientcmd.BuildConfigFromKubeconfigGetter(apiServerHost, getKubeConfigGetter(kubeConfig)) +} + +func getKubeConfigGetter(kubeConfig string) clientcmd.KubeconfigGetter { + return func() (*clientcmdapi.Config, error) { + if len(kubeConfig) == 0 { + return clientcmdapi.NewConfig(), nil + } + cfg, err := clientcmd.LoadFromFile(kubeConfig) + if os.IsNotExist(err) { + return clientcmdapi.NewConfig(), err + } + + if err != nil { + return clientcmdapi.NewConfig(), fmt.Errorf("error loading config file \"%s\": %v", kubeConfig, err) + } + + return cfg, nil + } +} diff --git a/internal/pem/decode.go b/internal/pem/decode.go new file mode 100644 index 00000000000..09b01f2c7ff --- /dev/null +++ b/internal/pem/decode.go @@ -0,0 +1,172 @@ +/* +Copyright 2024 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. +*/ + +// Package pem provides utility functions for safely decoding PEM data, placing upper limits on the size +// of data that will be processed. It functions as an extension to the standard library "encoding/pem" functions. +package pem + +import ( + stdpem "encoding/pem" + "errors" + "fmt" +) + +// The constants below are estimates at reasonable upper bounds for sizes of PEM data that cert-manager might encounter. +// cert-manager supports RSA, ECDSA and Ed25519 keys, of which RSA keys are by far the largest. + +// We'll aim to support RSA certs / keys which are larger than the maximum size (defined in pkg/util/pki.MaxRSAKeySize). + +// RSA keys grow proportional to the size of the RSA key used. For example: +// PEM-encoded RSA Keys: 4096-bit is ~3kB, 8192-bit is ~6kB and a 16k-bit key is ~12kB. + +// Certificates have two variables that we can estimate easily; the public key of the cert, and the signature from the signing cert. +// An N-bit key produces an (N/8)-byte signature, so as a worst case for us, a 16kB RSA key will create a 2kB signature. + +// PEM-encoded RSA X.509 certificates: +// Signed with 1k-bit RSA key: 4096-bit is ~1.4kB, 8192-bit is ~2kB, 16k-bit is ~3.5kB +// Signed with 16k-bit RSA key: 4096-bit is ~3.3kB, 8192-bit is ~4kB, 16k-bit is ~5.4kB + +// See https://fm4dd.com/openssl/certexamples.shtm for examples of large RSA certs / keys + +// A further factor more usually for leaf certificates is the number of identities contained within the certificate - usually, DNS names. +// Adding one DNS name to a certificate experimentally adds the length of the DNS name itself, plus about 20 bytes of overhead. +// We've seen[0] some certificates with 250+ DNS names, which could add up to about 30kB of extra size to a certificate given very long DNS names. + +// Generally, issuer certificates will not contain vast amounts of identities, so we can assume a difference in the size of leaf and issuer certificates, +// with issuer certificates being dominated by the size of the public key and signature, while leaf certificates can vary greatly but may be significantly larger +// than issuer certificates due to the number of identities they contain. + +// [0]: https://github.com/cert-manager/cert-manager/pull/7642#issuecomment-3129868718 + +const ( + // maxCACertificatePEMSize is the maximum size in bytes expected for a single PEM-encoded X.509 CA certificate. In practice, this is smaller than the + // maximum size which will be accepted by SafeDecodeSingleCertificate, because CA certificates generally won't contain large numbers of identities. + // The value is based on how large a "realistic" (but still very large) 16k-bit RSA CA certificate might be, plus about a kilobyte of extra data. + // 16k-bit RSA keys are impractical on most on modern hardware due to how slow they can be, + // so we can reasonably assume that no real-world PEM-encoded X.509 cert will be larger than this because of the key size. + maxCACertificatePEMSize = 6500 + + // maxLeafCertificatePEMSize is the maximum size in bytes expected for a single PEM-encoded X.509 certificate which SafeDecodeSingleCertificate will accept. + // The value is based on how large a "realistic" (but still very large) self-signed 16k-bit RSA certificate might be, plus + // a significant amount of extra data for potentially hundreds of DNS names, policy names, etc. + // See the comment for maxCACertificatePEMSize for more details on why we use a 16k-bit RSA key. + // 30000 is a rounded-up estimate for about 250 large DNS names. + maxLeafCertificatePEMSize = 30000 + maxCACertificatePEMSize + + // maxPrivateKeyPEMSize is the maximum size, in bytes, of PEM-encoded private keys which SafeDecodePrivateKey will accept. + // cert-manager supports RSA, ECDSA and Ed25519 keys, of which RSA is by far the largest. + // The value is based on how large a "realistic" (but very large) 16k-bit RSA private key might be. + // Given that 16k-bit RSA keys are so slow to use as to be impractical on modern hardware, + // we can reasonably assume that no real-world PEM-encoded key will be this large. + maxPrivateKeyPEMSize = 13000 + + // maxCertsInChain is the maximum number of 16k-bit RSA certificates signed by 16k-bit RSA CAs we'll allow in a given call to SafeDecodeCertificateChain. + // This is _not_ the maximum number of certificates cert-manager will process in a given chain, which could be much larger. + // This is simply the maximum number of worst-case certificates we'll accept in a chain when parsing PEM data. + maxCertsInChain = 10 + + // maxCertificateChainSize assumes a chain of CA certificates - which we expect to be smaller, generally - + // plus one leaf certificate which can be much larger due to the number of identities it contains. + // See comments for individual constants for more details on the sizes of the certificates. + maxCertificateChainSize = (maxCertsInChain-1)*maxCACertificatePEMSize + maxLeafCertificatePEMSize + + // maxCertsInTrustBundle is an estimated upper-bound for how many large certs might appear in a PEM-encoded trust bundle, + // based on the cert-manager `cert-manager-package-debian` bundle [1] which contains 129 certificates. + // This isn't an upper bound on how many certificates can appear and be parsed; just a reasonable upper bound if using + // exclusively large RSA certs (see estimatedCACertSize) + // In practice, trust stores will contain ECDSA/EdDSA certificates which are smaller than RSA certs, and so will be able to have more certificates + // than maxCertsInTrustBundle if needed. + // [1] quay.io/jetstack/cert-manager-package-debian:20210119.0@sha256:116133f68938ef568aca17a0c691d5b1ef73a9a207029c9a068cf4230053fed5 + maxCertsInTrustBundle = 150 + + // estimatedCACertSize is a guess of how many bytes a large realistic CA cert in a trust bundle cert might be. This is slightly larger + // than a typical self-signed 4096-bit RSA cert (which is just under 2kB). + // For other estimates (i.e. maxCACertificatePEMSize and maxLeafCertificatePEMSize) we use a much larger RSA key, but using such a large RSA key would make + // maxBundleSize's estimate unrealistically large when compared to real-world trust bundles (such as the widely used Mozilla CA trust store) + estimatedCACertSize = 2200 + + // maxBundleSize is an estimate for the max reasonable size for a PEM-encoded TLS trust bundle. + // See also comments for maxCertsInTrustBundle and estimatedCACertSize. + // This estimate is ultimately based on the cert-manager `cert-manager-package-debian` bundle [1] which contains 129 certificates, totalling ~196kB of data. + // [1] quay.io/jetstack/cert-manager-package-debian:20210119.0@sha256:116133f68938ef568aca17a0c691d5b1ef73a9a207029c9a068cf4230053fed5 + maxBundleSize = maxCertsInTrustBundle * estimatedCACertSize +) + +var ( + // ErrNoPEMData is returned when the given data contained no PEM + ErrNoPEMData = errors.New("no PEM data was found in given input") +) + +// ErrPEMDataTooLarge is returned when the given data is larger than the maximum allowed +type ErrPEMDataTooLarge int //nolint:errname + +// Error returns an error string +func (e ErrPEMDataTooLarge) Error() string { + return fmt.Sprintf("provided PEM data was larger than the maximum %dB", int(e)) +} + +func safeDecodeInternal(b []byte, maxSize int) (*stdpem.Block, []byte, error) { + if len(b) > maxSize { + return nil, b, ErrPEMDataTooLarge(maxSize) + } + + block, rest := stdpem.Decode(b) + if block == nil { + return nil, rest, ErrNoPEMData + } + + return block, rest, nil +} + +// SafeDecodePrivateKey calls [encoding/pem.Decode] on the given input as long as it's within a sensible range for +// how large we expect a private key to be. The baseline is a 16k-bit RSA private key, which is larger than the maximum +// supported by cert-manager for key generation. +func SafeDecodePrivateKey(b []byte) (*stdpem.Block, []byte, error) { + return safeDecodeInternal(b, maxPrivateKeyPEMSize) +} + +// SafeDecodeCSR calls [encoding/pem.Decode] on the given input as long as it's within a sensible range for +// how large we expect a single PEM-encoded PKCS#10 CSR to be. +// We assume that a PKCS#12 CSR can be about as large as a leaf certificate, which grows with the size of its public key, signature +// and the number of identities it contains. +func SafeDecodeCSR(b []byte) (*stdpem.Block, []byte, error) { + return safeDecodeInternal(b, maxLeafCertificatePEMSize) +} + +// SafeDecodeSingleCertificate calls [encoding/pem.Decode] on the given input as long as it's within a sensible range for +// how large we expect a single PEM-encoded X.509 _leaf_ certificate to be. +// The baseline is a 16k-bit RSA certificate signed by a different 16k-bit RSA CA, with a very large number of long DNS names. +// The maximum size allowed by this function is significantly larger than the size of most CA certificates, which will usually +// not have a large amount of DNS names or other identities in them. +func SafeDecodeSingleCertificate(b []byte) (*stdpem.Block, []byte, error) { + return safeDecodeInternal(b, maxLeafCertificatePEMSize) +} + +// SafeDecodeCertificateChain calls [encoding/pem.Decode] on the given input as long as it's within a sensible range for +// how large we expect a reasonable-length PEM-encoded X.509 certificate chain to be. +// The baseline is many average sized CA certificates, plus one potentially much larger leaf certificate. +func SafeDecodeCertificateChain(b []byte) (*stdpem.Block, []byte, error) { + return safeDecodeInternal(b, maxCertificateChainSize) +} + +// SafeDecodeCertificateBundle calls [encoding/pem.Decode] on the given input as long as it's within a sensible range for +// how large we expect a reasonable-length PEM-encoded X.509 certificate bundle (such as a TLS trust store) to be. +// The baseline is a bundle of 4k-bit RSA certificates, all self-signed. This is smaller than the 16k-bit RSA keys +// we use in other functions, because using such large keys would make our estimate several times +// too large for a realistic bundle which would be used in practice. +func SafeDecodeCertificateBundle(b []byte) (*stdpem.Block, []byte, error) { + return safeDecodeInternal(b, maxBundleSize) +} diff --git a/internal/pem/decode_test.go b/internal/pem/decode_test.go new file mode 100644 index 00000000000..88102748a3f --- /dev/null +++ b/internal/pem/decode_test.go @@ -0,0 +1,177 @@ +/* +Copyright 2024 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. +*/ + +package pem + +import ( + stdpem "encoding/pem" + "fmt" + "os" + "slices" + "testing" + "time" +) + +// fuzzFile is a fuzz-test-generated file which causes significant slowdown when passed to +// the standard library pem.Decode function. It's used as a test case to ensure that our +// safe PEM decoding functions reject it. +var fuzzFile []byte + +// pathologicalFuzzFile is a copy of fuzzFile trimmed to fit inside our max allowable size +var pathologicalFuzzFile []byte + +// largestLimit is set to maxBundleSize as the maximum size that any of our SafeDecode* functions accepts; we use this +// as an upper bound for the size of pathologicalFuzzFile. +const largestLimit = maxBundleSize + +func init() { + fuzzFilename := "./testdata/issue-ghsa-r4pg-vg54-wxx4.bin" + + var err error + fuzzFile, err = os.ReadFile(fuzzFilename) + if err != nil { + panic(fmt.Errorf("failed to read fuzz file %q: %s", fuzzFilename, err)) + } + + // Assert that largestLimit is actually the largest limit so we're definitely + // testing the worst case with pathologicalFuzzFile. This guards against future changes making these tests invalid; + // e.g. if, maxCertificateChainSize actually became the largest we accept, we'd want to test against that instead. + if largestLimit < maxPrivateKeyPEMSize || largestLimit < maxCertificateChainSize { + panic(fmt.Errorf("invalid test: expected max cert bundle size %d to be larger than maxPrivateKeyPEMSize %d and maxCertificateChainSize %d", maxBundleSize, maxPrivateKeyPEMSize, maxCertificateChainSize)) + } + + pathologicalFuzzFile = fuzzFile[:largestLimit-1] +} + +func TestFuzzData(t *testing.T) { + // The fuzz test data should be rejected by all Safe* functions + + // Ensure fuzz test data is larger than the max we allow + if len(fuzzFile) < maxCertificateChainSize { + t.Fatalf("invalid test; fuzz file data is smaller than the maximum allowed input") + } + + var block *stdpem.Block + var rest []byte + var err error + + expPrivateKeyError := ErrPEMDataTooLarge(maxPrivateKeyPEMSize) + expCSRError := ErrPEMDataTooLarge(maxLeafCertificatePEMSize) + expSingleCertError := ErrPEMDataTooLarge(maxLeafCertificatePEMSize) + expCertChainError := ErrPEMDataTooLarge(maxCertificateChainSize) + expCertBundleError := ErrPEMDataTooLarge(maxBundleSize) + + block, rest, err = SafeDecodePrivateKey(fuzzFile) + if err != expPrivateKeyError { + t.Errorf("SafeDecodePrivateKey: wanted %s but got %v", expPrivateKeyError, err) + } + + if block != nil { + t.Errorf("SafeDecodePrivateKey: expected block to be nil") + } + + if !slices.Equal(rest, fuzzFile) { + t.Errorf("SafeDecodePrivateKey: expected rest to equal input") + } + + block, rest, err = SafeDecodeCSR(fuzzFile) + if err != expCSRError { + t.Errorf("SafeDecodeCSR: wanted %s but got %v", expCSRError, err) + } + + if block != nil { + t.Errorf("SafeDecodeCSR: expected block to be nil") + } + + if !slices.Equal(rest, fuzzFile) { + t.Errorf("SafeDecodeCSR: expected rest to equal input") + } + + block, rest, err = SafeDecodeSingleCertificate(fuzzFile) + if err != expSingleCertError { + t.Errorf("SafeDecodeSingleCertificate: wanted %s but got %v", expSingleCertError, err) + } + + if block != nil { + t.Errorf("SafeDecodeSingleCertificate: expected block to be nil") + } + + if !slices.Equal(rest, fuzzFile) { + t.Errorf("SafeDecodeSingleCertificate: expected rest to equal input") + } + + block, rest, err = SafeDecodeCertificateChain(fuzzFile) + if err != expCertChainError { + t.Errorf("SafeDecodeCertificateChain: wanted %s but got %v", expCertChainError, err) + } + + if block != nil { + t.Errorf("SafeDecodeCertificateChain: expected block to be nil") + } + + if !slices.Equal(rest, fuzzFile) { + t.Errorf("SafeDecodeCertificateChain: expected rest to equal input") + } + + block, rest, err = SafeDecodeCertificateBundle(fuzzFile) + if err != expCertBundleError { + t.Errorf("SafeDecodeCertificateBundle: wanted %s but got %v", expCertBundleError, err) + } + + if block != nil { + t.Errorf("SafeDecodeCertificateBundle: expected block to be nil") + } + + if !slices.Equal(rest, fuzzFile) { + t.Errorf("SafeDecodeCertificateBundle: expected rest to equal input") + } +} + +func testPathologicalInternal(t testing.TB) { + block, rest, err := SafeDecodeCertificateBundle(pathologicalFuzzFile) + + if err != ErrNoPEMData { + t.Errorf("pathological input: expected err %s but got %v", ErrNoPEMData, err) + } + + if block != nil { + t.Errorf("pathological input: expected block to be nil") + } + + if !slices.Equal(rest, pathologicalFuzzFile) { + t.Errorf("pathological input: expected rest to equal input") + } +} + +func TestPathologicalInput(t *testing.T) { + // This test checks the runtime of the worst case scenario, so we can check it's not unacceptably + // slow (indicating that our max sizes might be too permissive) + beforeCall := time.Now() + + testPathologicalInternal(t) + + afterCall := time.Now() + + callDuration := afterCall.Sub(beforeCall) + + t.Logf("pathological input: took %s to execute", callDuration) +} + +func BenchmarkPathologicalInput(b *testing.B) { + for range b.N { + testPathologicalInternal(b) + } +} diff --git a/internal/pem/testdata/issue-ghsa-r4pg-vg54-wxx4.bin b/internal/pem/testdata/issue-ghsa-r4pg-vg54-wxx4.bin new file mode 100644 index 00000000000..f47fb267763 Binary files /dev/null and b/internal/pem/testdata/issue-ghsa-r4pg-vg54-wxx4.bin differ diff --git a/internal/plugin/admission/apideprecation/apideprecation.go b/internal/plugin/admission/apideprecation/apideprecation.go deleted file mode 100644 index ccd22a3ed1a..00000000000 --- a/internal/plugin/admission/apideprecation/apideprecation.go +++ /dev/null @@ -1,64 +0,0 @@ -/* -Copyright 2021 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. -*/ - -package apideprecation - -import ( - "context" - "fmt" - - admissionv1 "k8s.io/api/admission/v1" - "k8s.io/apimachinery/pkg/runtime" - - "github.com/cert-manager/cert-manager/pkg/apis/acme" - "github.com/cert-manager/cert-manager/pkg/apis/certmanager" - "github.com/cert-manager/cert-manager/pkg/webhook/admission" -) - -const PluginName = "APIDeprecation" - -type apiDeprecation struct{} - -// Register registers a plugin -func Register(plugins *admission.Plugins) { - plugins.Register(PluginName, func() (admission.Interface, error) { - return NewPlugin(), nil - }) -} - -var _ admission.ValidationInterface = &apiDeprecation{} - -func (p apiDeprecation) Handles(_ admissionv1.Operation) bool { - return true -} - -func (p apiDeprecation) Validate(ctx context.Context, request admissionv1.AdmissionRequest, oldObj, obj runtime.Object) (warnings []string, err error) { - // Only generate warning messages for cert-manager.io and acme.cert-manager.io APIs - if request.RequestResource.Group != certmanager.GroupName && - request.RequestResource.Group != acme.GroupName { - return nil, nil - } - - // All non-v1 API resources in cert-manager.io and acme.cert-manager.io are now deprecated - if request.RequestResource.Version == "v1" { - return nil, nil - } - return []string{fmt.Sprintf("%s.%s/%s is deprecated in v1.4+, unavailable in v1.6+; use %s.%s/v1", request.RequestResource.Resource, request.RequestResource.Group, request.RequestResource.Version, request.RequestResource.Resource, request.RequestResource.Group)}, nil -} - -func NewPlugin() admission.Interface { - return new(apiDeprecation) -} diff --git a/internal/plugin/admission/apideprecation/apideprecation_test.go b/internal/plugin/admission/apideprecation/apideprecation_test.go deleted file mode 100644 index 427aa3b444c..00000000000 --- a/internal/plugin/admission/apideprecation/apideprecation_test.go +++ /dev/null @@ -1,75 +0,0 @@ -/* -Copyright 2021 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. -*/ - -package apideprecation - -import ( - "context" - "reflect" - "testing" - - admissionv1 "k8s.io/api/admission/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -func TestAPIDeprecation(t *testing.T) { - tests := map[string]struct { - req *admissionv1.AdmissionRequest - warnings []string - }{ - "should print warnings for all non-v1 cert-manager.io types": { - req: &admissionv1.AdmissionRequest{ - RequestResource: &metav1.GroupVersionResource{ - Group: "cert-manager.io", - Version: "something-not-v1", - Resource: "somethings", - }, - }, - warnings: []string{"somethings.cert-manager.io/something-not-v1 is deprecated in v1.4+, unavailable in v1.6+; use somethings.cert-manager.io/v1"}, - }, - "should print warnings for all non-v1 acme.cert-manager.io types": { - req: &admissionv1.AdmissionRequest{ - RequestResource: &metav1.GroupVersionResource{ - Group: "acme.cert-manager.io", - Version: "something-not-v1", - Resource: "somethings", - }, - }, - warnings: []string{"somethings.acme.cert-manager.io/something-not-v1 is deprecated in v1.4+, unavailable in v1.6+; use somethings.acme.cert-manager.io/v1"}, - }, - "should not print warnings for non-v1 types in other groups": { - req: &admissionv1.AdmissionRequest{ - RequestResource: &metav1.GroupVersionResource{ - Group: "some-other-group-name", - Version: "something-not-v1", - Resource: "somethings", - }, - }, - }, - } - for name, test := range tests { - t.Run(name, func(t *testing.T) { - p := NewPlugin().(*apiDeprecation) - warnings, err := p.Validate(context.Background(), *test.req, nil, nil) - if err != nil { - t.Errorf("unexpected error") - } - if !reflect.DeepEqual(warnings, test.warnings) { - t.Errorf("unexpected warnings, exp=%q, got=%q", test.warnings, warnings) - } - }) - } -} diff --git a/internal/plugin/plugins.go b/internal/plugin/plugins.go deleted file mode 100644 index 73f04180ea0..00000000000 --- a/internal/plugin/plugins.go +++ /dev/null @@ -1,54 +0,0 @@ -/* -Copyright 2021 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. -*/ - -package plugin - -import ( - "github.com/cert-manager/cert-manager/internal/plugin/admission/apideprecation" - certificaterequestapproval "github.com/cert-manager/cert-manager/internal/plugin/admission/certificaterequest/approval" - certificaterequestidentity "github.com/cert-manager/cert-manager/internal/plugin/admission/certificaterequest/identity" - "github.com/cert-manager/cert-manager/internal/plugin/admission/resourcevalidation" - "github.com/cert-manager/cert-manager/pkg/webhook/admission" - "k8s.io/apimachinery/pkg/util/sets" -) - -var AllOrderedPlugins = []string{ - apideprecation.PluginName, - resourcevalidation.PluginName, - certificaterequestidentity.PluginName, - certificaterequestapproval.PluginName, -} - -func RegisterAllPlugins(plugins *admission.Plugins) { - apideprecation.Register(plugins) - certificaterequestidentity.Register(plugins) - certificaterequestapproval.Register(plugins) - resourcevalidation.Register(plugins) -} - -func DefaultOnAdmissionPlugins() sets.String { - return sets.NewString( - apideprecation.PluginName, - resourcevalidation.PluginName, - certificaterequestidentity.PluginName, - certificaterequestapproval.PluginName, - ) -} - -// DefaultOffAdmissionPlugins gets admission plugins off by default for the webhook. -func DefaultOffAdmissionPlugins() sets.String { - return sets.NewString(AllOrderedPlugins...).Difference(DefaultOnAdmissionPlugins()) -} diff --git a/internal/test/paths/paths.go b/internal/test/paths/paths.go index 4bbcff48d97..00ead6751be 100644 --- a/internal/test/paths/paths.go +++ b/internal/test/paths/paths.go @@ -36,17 +36,17 @@ var ( // to detect the BINDIR here (`make print-bindir`?) BinDir = filepath.Join(ModuleRootDir, "_bin") - // BinToolsDir is the filesystem path of the bin/tools directory which can for - // example be populated by `make -f make/Makefile integration-test-tools`. + // BinToolsDir is the filesystem path of the _bin/tools directory which can for + // example be populated by `make test`. BinToolsDir = filepath.Join(BinDir, "tools") - // BinCRDDir is the filesystem path of templated CRDs created by Makefile commands - BinCRDDir = filepath.Join(BinDir, "yaml", "templated-crds") + // BinCRDDir is the filesystem path of the CRD yaml + BinCRDDir = filepath.Join(ModuleRootDir, "deploy", "crds") ) // PathForCRD attempts to find a path to the named CRD. // The 'name' is the name of the resource contained within the CRD as denoted -// by the filename, e.g. 'foobar' would find a CRD with a filename containing +// by the filename, e.g., 'foobar' would find a CRD with a filename containing // the word 'foobar'. func PathForCRD(t *testing.T, name string) string { dir, err := CRDDirectory() @@ -54,7 +54,7 @@ func PathForCRD(t *testing.T, name string) string { t.Fatalf("failed to find CRD directory: %s", err) } - path := filepath.Join(dir, fmt.Sprintf("crd-%s.templated.yaml", name)) + path := filepath.Join(dir, fmt.Sprintf("%s.yaml", name)) info, err := os.Stat(path) if err != nil { diff --git a/internal/vault/fake/client.go b/internal/vault/fake/client.go index 64aa0c6aac9..36f169a58d3 100644 --- a/internal/vault/fake/client.go +++ b/internal/vault/fake/client.go @@ -18,18 +18,20 @@ package fake import ( "errors" + "testing" vault "github.com/hashicorp/vault/api" ) -type Client struct { +type FakeClient struct { NewRequestS *vault.Request RawRequestFn func(r *vault.Request) (*vault.Response, error) - token string + GotToken string + T *testing.T } -func NewFakeClient() *Client { - return &Client{ +func NewFakeClient() *FakeClient { + return &FakeClient{ NewRequestS: new(vault.Request), RawRequestFn: func(r *vault.Request) (*vault.Response, error) { return nil, errors.New("unexpected RawRequest call") @@ -37,34 +39,37 @@ func NewFakeClient() *Client { } } -func (c *Client) WithNewRequest(r *vault.Request) *Client { +func (c *FakeClient) CloneConfig() *vault.Config { + return vault.DefaultConfig() +} + +func (c *FakeClient) WithNewRequest(r *vault.Request) *FakeClient { c.NewRequestS = r return c } -func (c *Client) WithRawRequest(resp *vault.Response, err error) *Client { +func (c *FakeClient) WithRawRequest(resp *vault.Response, err error) *FakeClient { c.RawRequestFn = func(r *vault.Request) (*vault.Response, error) { return resp, err } return c } -func (c *Client) NewRequest(method, requestPath string) *vault.Request { - return c.NewRequestS +func (c *FakeClient) WithRawRequestFn(fn func(t *testing.T, r *vault.Request) (*vault.Response, error)) *FakeClient { + c.RawRequestFn = func(req *vault.Request) (*vault.Response, error) { + return fn(c.T, req) + } + return c } -func (c *Client) SetToken(v string) { - c.token = v +func (c *FakeClient) NewRequest(method, requestPath string) *vault.Request { + return c.NewRequestS } -func (c *Client) Token() string { - return c.token +func (c *FakeClient) SetToken(v string) { + c.GotToken = v } -func (c *Client) RawRequest(r *vault.Request) (*vault.Response, error) { +func (c *FakeClient) RawRequest(r *vault.Request) (*vault.Response, error) { return c.RawRequestFn(r) } - -func (c *Client) Sys() *vault.Sys { - return nil -} diff --git a/internal/vault/fake/vault.go b/internal/vault/fake/vault.go index 529a0a54f2a..97014b2388e 100644 --- a/internal/vault/fake/vault.go +++ b/internal/vault/fake/vault.go @@ -20,15 +20,13 @@ package fake import ( "time" - vault "github.com/hashicorp/vault/api" - corelisters "k8s.io/client-go/listers/core/v1" - - v1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" + internalinformers "github.com/cert-manager/cert-manager/internal/informers" + cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" ) // Vault is a mock implementation of the Vault interface type Vault struct { - NewFn func(string, corelisters.SecretLister, v1.GenericIssuer) (*Vault, error) + NewFn func(string, internalinformers.SecretLister, cmapi.GenericIssuer) (*Vault, error) SignFn func([]byte, time.Duration) ([]byte, []byte, error) IsVaultInitializedAndUnsealedFn func() error } @@ -44,7 +42,7 @@ func New() *Vault { }, } - v.NewFn = func(string, corelisters.SecretLister, v1.GenericIssuer) (*Vault, error) { + v.NewFn = func(string, internalinformers.SecretLister, cmapi.GenericIssuer) (*Vault, error) { return v, nil } @@ -65,13 +63,13 @@ func (v *Vault) WithSign(certPEM, caPEM []byte, err error) *Vault { } // WithNew sets the fake Vault's New function. -func (v *Vault) WithNew(f func(string, corelisters.SecretLister, v1.GenericIssuer) (*Vault, error)) *Vault { +func (v *Vault) WithNew(f func(string, internalinformers.SecretLister, cmapi.GenericIssuer) (*Vault, error)) *Vault { v.NewFn = f return v } // New call NewFn and returns a pointer to the fake Vault. -func (v *Vault) New(ns string, sl corelisters.SecretLister, iss v1.GenericIssuer) (*Vault, error) { +func (v *Vault) New(ns string, sl internalinformers.SecretLister, iss cmapi.GenericIssuer) (*Vault, error) { _, err := v.NewFn(ns, sl, iss) if err != nil { return nil, err @@ -80,11 +78,6 @@ func (v *Vault) New(ns string, sl corelisters.SecretLister, iss v1.GenericIssuer return v, nil } -// Sys returns an empty `vault.Sys`. -func (v *Vault) Sys() *vault.Sys { - return new(vault.Sys) -} - // IsVaultInitializedAndUnsealed always returns nil func (v *Vault) IsVaultInitializedAndUnsealed() error { return nil diff --git a/internal/vault/vault.go b/internal/vault/vault.go index 25f9ee9cbcd..a19d6120d0c 100644 --- a/internal/vault/vault.go +++ b/internal/vault/vault.go @@ -17,6 +17,8 @@ limitations under the License. package vault import ( + "context" + "crypto/tls" "crypto/x509" "errors" "fmt" @@ -28,10 +30,15 @@ import ( vault "github.com/hashicorp/vault/api" "github.com/hashicorp/vault/sdk/helper/certutil" - corelisters "k8s.io/client-go/listers/core/v1" + authv1 "k8s.io/api/authentication/v1" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/utils/ptr" + internalinformers "github.com/cert-manager/cert-manager/internal/informers" v1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" + cmerrors "github.com/cert-manager/cert-manager/pkg/util/errors" "github.com/cert-manager/cert-manager/pkg/util/pki" ) @@ -39,16 +46,13 @@ var _ Interface = &Vault{} // ClientBuilder is a function type that returns a new Interface. // Can be used in tests to create a mock signer of Vault certificate requests. -type ClientBuilder func(namespace string, secretsLister corelisters.SecretLister, - issuer v1.GenericIssuer) (Interface, error) +type ClientBuilder func(ctx context.Context, namespace string, _ func(ns string) CreateToken, _ internalinformers.SecretLister, _ v1.GenericIssuer) (Interface, error) // Interface implements various high level functionality related to connecting // with a Vault server, verifying its status and signing certificate request for // Vault's certificate. -// TODO: Sys() is duplicated here and in Client interface type Interface interface { Sign(csrPEM []byte, duration time.Duration) (certPEM []byte, caPEM []byte, err error) - Sys() *vault.Sys IsVaultInitializedAndUnsealed() error } @@ -57,26 +61,45 @@ type Client interface { NewRequest(method, requestPath string) *vault.Request RawRequest(r *vault.Request) (*vault.Response, error) SetToken(v string) - Token() string - Sys() *vault.Sys + CloneConfig() *vault.Config } +// For mocking purposes. +type CreateToken func(ctx context.Context, saName string, req *authv1.TokenRequest, opts metav1.CreateOptions) (*authv1.TokenRequest, error) + // Vault implements Interface and holds a Vault issuer, secrets lister and a // Vault client. type Vault struct { - secretsLister corelisters.SecretLister + createToken CreateToken // Uses the same namespace as below. + secretsLister internalinformers.SecretLister issuer v1.GenericIssuer namespace string + // The pattern below, of namespaced and non-namespaced Vault clients, is copied from Hashicorp Nomad: + // https://github.com/hashicorp/nomad/blob/6e4410a9b13ce167bc7ef53da97c621b5c9dcd12/nomad/vault.go#L180-L190 + + // client is the Vault API client used for Namespace-relative integrations + // with the Vault API (anything except `/v1/sys`). + // The namespace feature is only available in Vault Enterprise. + // The namespace HTTP header (X-Vault-Namespace) is ignored by the open source version of Vault. + // See https://www.vaultproject.io/docs/enterprise/namespaces client Client + + // clientSys is the Vault API client used for non-Namespace-relative integrations + // with the Vault API (anything involving `/v1/sys`). This client is never configured + // with a Vault namespace, because these endpoints may return errors if a namespace + // header is provided + // See https://developer.hashicorp.com/vault/docs/enterprise/namespaces#root-only-api-paths + clientSys Client } // New returns a new Vault instance with the given namespace, issuer and // secrets lister. // Returned errors may be network failures and should be considered for // retrying. -func New(namespace string, secretsLister corelisters.SecretLister, issuer v1.GenericIssuer) (Interface, error) { +func New(ctx context.Context, namespace string, createTokenFn func(ns string) CreateToken, secretsLister internalinformers.SecretLister, issuer v1.GenericIssuer) (Interface, error) { v := &Vault{ + createToken: createTokenFn(namespace), secretsLister: secretsLister, namespace: namespace, issuer: issuer, @@ -92,11 +115,26 @@ func New(namespace string, secretsLister corelisters.SecretLister, issuer v1.Gen return nil, fmt.Errorf("error initializing Vault client: %s", err.Error()) } - if err := v.setToken(client); err != nil { + // Set the Vault namespace. + // An empty namespace string will cause the client to not send the namespace related HTTP headers to Vault. + clientNS := client.WithNamespace(issuer.GetSpec().Vault.Namespace) + + // Use the (maybe) namespaced client to authenticate. + // If a Vault namespace is configured, then the authentication endpoints are + // expected to be in that namespace. + if err := v.setToken(ctx, clientNS); err != nil { return nil, err } - v.client = client + // A client for use with namespaced API paths + v.client = clientNS + + // Create duplicate Vault client without a namespace, for interacting with root-only API paths. + // For backwards compatibility, this client will use the token from the namespaced client, + // although this is probably unnecessary / bad practice, since we only + // interact with the sys/health endpoint which is an unauthenticated endpoint: + // https://github.com/hashicorp/vault/issues/209#issuecomment-102485565. + v.clientSys = clientNS.WithNamespace("") return v, nil } @@ -124,8 +162,6 @@ func (v *Vault) Sign(csrPEM []byte, duration time.Duration) (cert []byte, ca []b request := v.client.NewRequest("POST", url) - v.addVaultNamespaceToRequest(request) - if err := request.SetJSONBody(parameters); err != nil { return nil, nil, fmt.Errorf("failed to build vault request: %s", err) } @@ -146,7 +182,14 @@ func (v *Vault) Sign(csrPEM []byte, duration time.Duration) (cert []byte, ca []b return extractCertificatesFromVaultCertificateSecret(&vaultResult) } -func (v *Vault) setToken(client Client) error { +func (v *Vault) setToken(ctx context.Context, client Client) error { + // IMPORTANT: Because of backwards compatibility with older versions that + // incorrectly allowed multiple authentication methods to be specified at + // the time of validation, we must still allow multiple authentication methods + // to be specified. + // In terms of implementation, we will use the first authentication method. + // The order of precedence is: tokenSecretRef, appRole, clientCertificate, kubernetes + tokenRef := v.issuer.GetSpec().Vault.Auth.TokenSecretRef if tokenRef != nil { token, err := v.tokenRef(tokenRef.Name, v.namespace, tokenRef.Key) @@ -169,17 +212,28 @@ func (v *Vault) setToken(client Client) error { return nil } + clientCert := v.issuer.GetSpec().Vault.Auth.ClientCertificate + if clientCert != nil { + token, err := v.requestTokenWithClientCertificate(client, clientCert) + if err != nil { + return err + } + client.SetToken(token) + + return nil + } + kubernetesAuth := v.issuer.GetSpec().Vault.Auth.Kubernetes if kubernetesAuth != nil { - token, err := v.requestTokenWithKubernetesAuth(client, kubernetesAuth) + token, err := v.requestTokenWithKubernetesAuth(ctx, client, kubernetesAuth) if err != nil { - return fmt.Errorf("error reading Kubernetes service account token from %s: %s", kubernetesAuth.SecretRef.Name, err.Error()) + return fmt.Errorf("while requesting a Vault token using the Kubernetes auth: %w", err) } client.SetToken(token) return nil } - return fmt.Errorf("error initializing Vault client: tokenSecretRef, appRoleSecretRef, or Kubernetes auth role not set") + return cmerrors.NewInvalidData("error initializing Vault client: tokenSecretRef, appRoleSecretRef, clientCertificate, or Kubernetes auth role not set") } func (v *Vault) newConfig() (*vault.Config, error) { @@ -191,20 +245,28 @@ func (v *Vault) newConfig() (*vault.Config, error) { return nil, fmt.Errorf("failed to load vault CA bundle: %w", err) } - // If no CA bundle was loaded, return early and don't modify the vault config - // further. This will cause the vault client to use the system root CA - // bundle. - if len(caBundle) == 0 { - return cfg, nil + if len(caBundle) != 0 { + caCertPool := x509.NewCertPool() + ok := caCertPool.AppendCertsFromPEM(caBundle) + if !ok { + return nil, fmt.Errorf("no Vault CA bundles loaded, check bundle contents") + } + + cfg.HttpClient.Transport.(*http.Transport).TLSClientConfig.RootCAs = caCertPool } - caCertPool := x509.NewCertPool() - ok := caCertPool.AppendCertsFromPEM(caBundle) - if !ok { - return nil, fmt.Errorf("no Vault CA bundles loaded, check bundle contents") + clientCertificate, err := v.clientCertificate() + if err != nil { + return nil, fmt.Errorf("failed to load vault client certificate: %w", err) + } + + if clientCertificate != nil { + cfg.HttpClient.Transport.(*http.Transport).TLSClientConfig.Certificates = []tls.Certificate{*clientCertificate} } - cfg.HttpClient.Transport.(*http.Transport).TLSClientConfig.RootCAs = caCertPool + if serverName := v.issuer.GetSpec().Vault.ServerName; len(serverName) != 0 { + cfg.HttpClient.Transport.(*http.Transport).TLSClientConfig.ServerName = serverName + } return cfg, nil } @@ -245,6 +307,54 @@ func (v *Vault) caBundle() ([]byte, error) { return certBytes, nil } +// clientCertificate returns the Client Certificate for the Vault server. +// Can be used in Vault client configs when the server requires mTLS. +func (v *Vault) clientCertificate() (*tls.Certificate, error) { + refCert := v.issuer.GetSpec().Vault.ClientCertSecretRef + refPrivateKey := v.issuer.GetSpec().Vault.ClientKeySecretRef + if refCert == nil || refPrivateKey == nil { + return nil, nil + } + + secretCert, err := v.secretsLister.Secrets(v.namespace).Get(refCert.Name) + if err != nil { + return nil, fmt.Errorf("could not access Secret '%s/%s': %s", v.namespace, refCert.Name, err) + } + secretPrivateKey, err := v.secretsLister.Secrets(v.namespace).Get(refPrivateKey.Name) + if err != nil { + return nil, fmt.Errorf("could not access Secret '%s/%s': %s", v.namespace, refPrivateKey.Name, err) + } + + var keyCert string + if refCert.Key != "" { + keyCert = refCert.Key + } else { + keyCert = corev1.TLSCertKey + } + + var keyPrivate string + if refPrivateKey.Key != "" { + keyPrivate = refPrivateKey.Key + } else { + keyPrivate = corev1.TLSPrivateKeyKey + } + + certBytes, ok := secretCert.Data[keyCert] + if !ok { + return nil, fmt.Errorf("no data for %q in Secret '%s/%s'", keyCert, v.namespace, refCert.Name) + } + privateKeyBytes, ok := secretPrivateKey.Data[keyPrivate] + if !ok { + return nil, fmt.Errorf("no data for %q in Secret '%s/%s'", keyPrivate, v.namespace, refPrivateKey.Name) + } + + cert, err := tls.X509KeyPair(certBytes, privateKeyBytes) + if err != nil { + return nil, fmt.Errorf("could not parse the TLS certificate from Secrets '%s/%s'(cert) and '%s/%s'(key): %s", v.namespace, refCert.Name, v.namespace, refPrivateKey.Name, err) + } + return &cert, nil +} + func (v *Vault) tokenRef(name, namespace, key string) (string, error) { secret, err := v.secretsLister.Secrets(namespace).Get(name) if err != nil { @@ -312,8 +422,6 @@ func (v *Vault) requestTokenWithAppRoleRef(client Client, appRole *v1.VaultAppRo return "", fmt.Errorf("error encoding Vault parameters: %s", err.Error()) } - v.addVaultNamespaceToRequest(request) - resp, err := client.RawRequest(request) if err != nil { return "", fmt.Errorf("error logging in to Vault server: %s", err.Error()) @@ -338,23 +446,142 @@ func (v *Vault) requestTokenWithAppRoleRef(client Client, appRole *v1.VaultAppRo return token, nil } -func (v *Vault) requestTokenWithKubernetesAuth(client Client, kubernetesAuth *v1.VaultKubernetesAuth) (string, error) { - secret, err := v.secretsLister.Secrets(v.namespace).Get(kubernetesAuth.SecretRef.Name) +func (v *Vault) requestTokenWithClientCertificate(client Client, clientCertificateAuth *v1.VaultClientCertificateAuth) (string, error) { + // If secretName is set, load client certificate from Secret, otherwise assume that a + // fitting client certificate is loaded in the client already. + if len(clientCertificateAuth.SecretName) != 0 { + secret, err := v.secretsLister.Secrets(v.namespace).Get(clientCertificateAuth.SecretName) + if err != nil { + return "", err + } + + cert, ok := secret.Data["tls.crt"] + if !ok { + return "", fmt.Errorf("no data for tls.crt in secret '%s/%s'", v.namespace, clientCertificateAuth.SecretName) + } + key, ok := secret.Data["tls.key"] + if !ok { + return "", fmt.Errorf("no data for tls.key in secret '%s/%s'", v.namespace, clientCertificateAuth.SecretName) + } + + clientCertificate, err := tls.X509KeyPair(cert, key) + if err != nil { + return "", fmt.Errorf("error reading client certificate: %s", err.Error()) + } + + // Setting up a short lived client with a configured client certificate. + // It is only meant to be used for requesting a Vault token. We clone + // http.Client's Transport separately as it has to be adjusted and does + // not seem to be cloned by CloneConfig. + cfg := client.CloneConfig() + tmpTransport := cfg.HttpClient.Transport.(*http.Transport).Clone() + tmpTransport.TLSClientConfig.Certificates = append(tmpTransport.TLSClientConfig.Certificates, clientCertificate) + cfg.HttpClient.Transport = tmpTransport + client, err = vault.NewClient(cfg) + if err != nil { + return "", fmt.Errorf("error initializing intermediary Vault client: %s", err.Error()) + } + } + + parameters := map[string]string{ + "name": clientCertificateAuth.Name, + } + + mountPath := clientCertificateAuth.Path + if mountPath == "" { + mountPath = v1.DefaultVaultClientCertificateAuthMountPath + } + + url := filepath.Join(mountPath, "login") + request := client.NewRequest("POST", url) + err := request.SetJSONBody(parameters) if err != nil { - return "", err + return "", fmt.Errorf("error encoding Vault parameters: %s", err.Error()) } - key := kubernetesAuth.SecretRef.Key - if key == "" { - key = v1.DefaultVaultTokenAuthSecretKey + resp, err := client.RawRequest(request) + if err != nil { + return "", fmt.Errorf("error calling Vault server: %s", err.Error()) } - keyBytes, ok := secret.Data[key] - if !ok { - return "", fmt.Errorf("no data for %q in secret '%s/%s'", key, v.namespace, kubernetesAuth.SecretRef.Name) + defer resp.Body.Close() + vaultResult := vault.Secret{} + err = resp.DecodeJSON(&vaultResult) + if err != nil { + return "", fmt.Errorf("unable to decode JSON payload: %s", err.Error()) } - jwt := string(keyBytes) + token, err := vaultResult.TokenID() + if err != nil { + return "", fmt.Errorf("unable to read token: %s", err.Error()) + } + + return token, nil +} + +func (v *Vault) requestTokenWithKubernetesAuth(ctx context.Context, client Client, kubernetesAuth *v1.VaultKubernetesAuth) (string, error) { + var jwt string + switch { + case kubernetesAuth.SecretRef.Name != "": + secret, err := v.secretsLister.Secrets(v.namespace).Get(kubernetesAuth.SecretRef.Name) + if err != nil { + return "", err + } + + key := kubernetesAuth.SecretRef.Key + if key == "" { + key = v1.DefaultVaultTokenAuthSecretKey + } + + keyBytes, ok := secret.Data[key] + if !ok { + return "", fmt.Errorf("no data for %q in secret '%s/%s'", key, v.namespace, kubernetesAuth.SecretRef.Name) + } + + jwt = string(keyBytes) + + case kubernetesAuth.ServiceAccountRef != nil: + defaultAudience := "vault://" + if v.issuer.GetNamespace() != "" { + defaultAudience += v.issuer.GetNamespace() + "/" + } + defaultAudience += v.issuer.GetName() + + audiences := append([]string(nil), kubernetesAuth.ServiceAccountRef.TokenAudiences...) + audiences = append(audiences, defaultAudience) + + tokenrequest, err := v.createToken(ctx, kubernetesAuth.ServiceAccountRef.Name, &authv1.TokenRequest{ + Spec: authv1.TokenRequestSpec{ + // Default audience is generated by cert-manager. + // This is the most secure configuration as vault role must explicitly mandate the audience. + // The format is: + // "vault:///" (for an Issuer) + // "vault://" (for a ClusterIssuer) + // + // If audiences are specified in the VaultIssuer, they will be appended to the default audience. + // + // Vault backend can bind the kubernetes auth backend role to the service account and specific namespace of the service account. + // Providing additional audiences is not considered a major non-mitigatable security risk + // as if someone creates an Issuer in another namespace/globally with the same audiences + // in attempt to hijack the certificate vault (if role config mandates sa:namespace) won't authorise the connection + // as token subject won't match vault role requirement to have SA originated from the specific namespace. + Audiences: audiences, + + // Since the JWT is only used to authenticate with Vault and is + // immediately discarded, let's use the minimal duration + // possible. 10 minutes is the minimum allowed by the Kubernetes + // API. + ExpirationSeconds: ptr.To(int64(600)), + }, + }, metav1.CreateOptions{}) + if err != nil { + return "", fmt.Errorf("while requesting a token for the service account %s/%s: %s", v.issuer.GetNamespace(), kubernetesAuth.ServiceAccountRef.Name, err.Error()) + } + + jwt = tokenrequest.Status.Token + default: + return "", fmt.Errorf("programmer mistake: both serviceAccountRef and tokenRef.name are empty") + } parameters := map[string]string{ "role": kubernetesAuth.Role, @@ -368,13 +595,11 @@ func (v *Vault) requestTokenWithKubernetesAuth(client Client, kubernetesAuth *v1 url := filepath.Join(mountPath, "login") request := client.NewRequest("POST", url) - err = request.SetJSONBody(parameters) + err := request.SetJSONBody(parameters) if err != nil { return "", fmt.Errorf("error encoding Vault parameters: %s", err.Error()) } - v.addVaultNamespaceToRequest(request) - resp, err := client.RawRequest(request) if err != nil { return "", fmt.Errorf("error calling Vault server: %s", err.Error()) @@ -395,10 +620,6 @@ func (v *Vault) requestTokenWithKubernetesAuth(client Client, kubernetesAuth *v1 return token, nil } -func (v *Vault) Sys() *vault.Sys { - return v.client.Sys() -} - func extractCertificatesFromVaultCertificateSecret(secret *certutil.Secret) ([]byte, []byte, error) { parsedBundle, err := certutil.ParsePKIMap(secret.Data) if err != nil { @@ -425,22 +646,30 @@ func extractCertificatesFromVaultCertificateSecret(secret *certutil.Secret) ([]b func (v *Vault) IsVaultInitializedAndUnsealed() error { healthURL := path.Join("/v1", "sys", "health") - healthRequest := v.client.NewRequest("GET", healthURL) - healthResp, err := v.client.RawRequest(healthRequest) + healthRequest := v.clientSys.NewRequest("GET", healthURL) + healthResp, err := v.clientSys.RawRequest(healthRequest) if healthResp != nil { defer healthResp.Body.Close() } + // 200 = if initialized, unsealed, and active // 429 = if unsealed and standby // 472 = if disaster recovery mode replication secondary and active // 473 = if performance standby + // 501 = if not initialized + // 503 = if sealed + // nolint: usestdlibvars // We use the numeric error codes here that we got from the Vault docs. if err != nil { switch { case healthResp == nil: return err case healthResp.StatusCode == 429, healthResp.StatusCode == 472, healthResp.StatusCode == 473: return nil + case healthResp.StatusCode == 501: + return fmt.Errorf("Vault is not initialized") + case healthResp.StatusCode == 503: + return fmt.Errorf("Vault is sealed") default: return fmt.Errorf("error calling Vault %s: %w", healthURL, err) } @@ -448,16 +677,3 @@ func (v *Vault) IsVaultInitializedAndUnsealed() error { return nil } - -func (v *Vault) addVaultNamespaceToRequest(request *vault.Request) { - vaultIssuer := v.issuer.GetSpec().Vault - if vaultIssuer != nil && vaultIssuer.Namespace != "" { - if request.Headers != nil { - request.Headers.Add("X-VAULT-NAMESPACE", vaultIssuer.Namespace) - } else { - vaultReqHeaders := http.Header{} - vaultReqHeaders.Add("X-VAULT-NAMESPACE", vaultIssuer.Namespace) - request.Headers = vaultReqHeaders - } - } -} diff --git a/internal/vault/vault_test.go b/internal/vault/vault_test.go index 253c0f21ff8..fc1012ba519 100644 --- a/internal/vault/vault_test.go +++ b/internal/vault/vault_test.go @@ -18,17 +18,15 @@ package vault import ( "bytes" + "context" "crypto" - "crypto/rand" "crypto/rsa" "crypto/x509" - "crypto/x509/pkix" - "encoding/asn1" - "encoding/pem" "errors" "fmt" "io" "net/http" + "net/http/httptest" "strings" "testing" "time" @@ -36,11 +34,15 @@ import ( vault "github.com/hashicorp/vault/api" "github.com/hashicorp/vault/sdk/helper/certutil" "github.com/hashicorp/vault/sdk/helper/jsonutil" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + authv1 "k8s.io/api/authentication/v1" corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" clientcorev1 "k8s.io/client-go/listers/core/v1" vaultfake "github.com/cert-manager/cert-manager/internal/vault/fake" - cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" + cmapiv1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" "github.com/cert-manager/cert-manager/pkg/util/pki" "github.com/cert-manager/cert-manager/test/unit/gen" @@ -144,6 +146,58 @@ Pc7TUJiY8gW9SWhPVUPaMkTIBgfN11c6BzLlhzN2r1zaZyghXr8QmcG4kWywkX7k oXeN5eS8iO5fx0EOvIcYQ4yRZLGafZxsLHlsZmt32N/ZZtcl4KDP5LRE7iZEOaE/ UXY5wAUH2A== -----END CERTIFICATE----- +` + testClientCertificate = `-----BEGIN CERTIFICATE----- +MIIDuzCCAqOgAwIBAgIBATANBgkqhkiG9w0BAQsFADBZMQswCQYDVQQGEwJVUzEL +MAkGA1UECAwCQ0ExFjAUBgNVBAcMDVNhbiBGcmFuY2lzY28xFDASBgNVBAoMC0NF +UlRNQU5BR0VSMQ8wDQYDVQQDDAZiYXIuY2EwHhcNMjQwMTA0MTAxMzQ3WhcNNDMx +MjMwMTAxMzQ3WjBgMQswCQYDVQQGEwJVUzELMAkGA1UECAwCQ0ExFjAUBgNVBAcM +DVNhbiBGcmFuY2lzY28xFDASBgNVBAoMC0NFUlRNQU5BR0VSMRYwFAYDVQQDDA1j +bGllbnQuYmFyLmNhMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvFLC +dXqU33bG8TrVQ2mwTZF6UxNgrQCjLYJZWgK7AUvOzdsMUiZfQ51GVRLw7inRYgYn +QuIjPwgXQDYCHfQJEsBghSMNze+QjmGYwT2dy2QZm47Q0lGi57n2rtgRrM2Q+19E +SlEOZhi45ZVaU2NAEMAD8jIj3XznukrZLUZQSt7lN0xS2w1IvhO7Xb1n+wFcjwMt +f4ciRQyKprHQGTcaTzGn4Fjhua3kyFydg7elE1U23UJT42TbZ0WfgNG9KLGLWYpD +pMDnAgkgwMggguQz4izgFyeD2NnvDIviGbwnTC9WAlogwBZx2SOrBGIjoyWNmfIj +9uu5CytBgCdmhzMx1QIDAQABo4GGMIGDMAkGA1UdEwQCMAAwEQYJYIZIAYb4QgEB +BAQDAgeAMB0GA1UdDgQWBBRQlAnVwsjjnb3lu44c2Rt/zz4l3zAfBgNVHSMEGDAW +gBR/PgLMjVGMUxKzqRqcocLfB500ETAOBgNVHQ8BAf8EBAMCBeAwEwYDVR0lBAww +CgYIKwYBBQUHAwIwDQYJKoZIhvcNAQELBQADggEBACXv/vfiuC8VXnvFo+Cvpn1H +eG1qsjOHOnPFhvHaMY55wsFchnZd7t0aqRNwkqLEvqpMIMDiXh7nw5pQZZu5IGBi ++cNDtfadmFi6NMFZNqlgPsYmb6pCI6OOG2r8VkmG+OdIg8QOdH60FQamT3MYKelE +JHxBQYgtiJr+vNTzBdrq9/qDgDJdx0OVo2U8+igFKkrWqgbPeJDLJb1NpVJBIhSG +ntdrtA87wmrLkV09SLUpvTYuTm3NMMrlD3hSBBBm3evb+65tsJg9/M5QjtAb8pQT +gtrc5PnSjjZzCeL94DkWQ+A7oLQStJMVePFvizMTozlnjCpVaJJN25nf+yVm22E= +-----END CERTIFICATE----- +` + testClientCertificatePrivateKey = `-----BEGIN PRIVATE KEY----- +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC8UsJ1epTfdsbx +OtVDabBNkXpTE2CtAKMtgllaArsBS87N2wxSJl9DnUZVEvDuKdFiBidC4iM/CBdA +NgId9AkSwGCFIw3N75COYZjBPZ3LZBmbjtDSUaLnufau2BGszZD7X0RKUQ5mGLjl +lVpTY0AQwAPyMiPdfOe6StktRlBK3uU3TFLbDUi+E7tdvWf7AVyPAy1/hyJFDIqm +sdAZNxpPMafgWOG5reTIXJ2Dt6UTVTbdQlPjZNtnRZ+A0b0osYtZikOkwOcCCSDA +yCCC5DPiLOAXJ4PY2e8Mi+IZvCdML1YCWiDAFnHZI6sEYiOjJY2Z8iP267kLK0GA +J2aHMzHVAgMBAAECggEACAEWfcrHgBX6z675+IMJ+MoJonVM4x2HUfxb0t0R2LTB +pfM8+1LhMq0BG8WR0vWZDisHySp2aAvufQ6umVpRdmgR0ibSw+F+SebxCKmXRtlK +01dHHeFVZLb9OqI5YhhcpKqAaw415/X+CdgGvkuWIgAfStCBwLy51quuvmNiL0Rm +XEq0tUAhJ8Z0G4z1e7PCabjq1eGPfsA6K3V/Bguo9ePIXls7AKn8xtyvQHL67RPy +wiupVB77RuViDYArx0ybMIvSKu8n2GAB0cBOICFe9MU8RiSGHl9MbgfDvW4BU/Fj ++YIrGjBV2MYdvjMCb2wlxyR11Yaaupd4u1e0eYxp4wKBgQDdaGHDgYQvTVOlgHrr +pYJoML50OIi9rh5q5HVRb3iLziSCaDUSBg7HuwC1vGLvZjfX3GVHpvwGaV9ZTk1c +C4jTtDAVibbCeL0/t9OzIE1EZbEwpMg0BXm0Jt97PhEKqxM2QHPGBAXKMxzJHYEO +HUHQpMCeHm5fLxZgv9qcTWisGwKBgQDZvxpCJ2qkRVyEhFYDJKBZ7KTlgH1TDZq6 +XvbVL/Q6c+6/iM+7TvytfnN36yb/p22eaBRf19ZkPHjGUqnCQnudwsK/bdyf0smh +6zZVirTL5/5qWIUPgIgWv5trqksMRm0NtRyo6ZwGWJnKTKPoxZJiW/d8v5bUw5IR +boLBMiCYzwKBgH4FCZAzyb76rl+HD2/M1rri86RHAV2lG18QBc6COgSpIpKvKXXG +yObaA39taIqGjcZphaQQ4WXs1/6G2PVJA2osJyo7JjDudBkuUmqkOhZyIzZitCkX +7LujXJRTMXP3B4pbiQnuBDWgfgPirTARawKMo63b+EppDL2otY89aBR9AoGBAK8q +VcRcEyTdC4UrNEpI/5n3jdt2FttmOU+uL2Dmt9ECDFEWjQ4Ah7JF5DvW9sN4++0P +izxi1HxETWA1hYzZkLojwCjhBzenCT9xiX8dGz5hfcAtP7Vtz4yFTVE6aC8SxI3f +YZPcggB07Bratoz9yznHA/vd4Ed+oJXXUeZ7Hc/vAoGBAKOpdDIzkKIkUIrbIKVI +DjlJQMrnnVpHB+7q2z+EnmVHzfn5zduL77hEBA/tXnkIvOyLm8WXDo/9K4E1xa93 +nUclMMQnJneyw8RRXGvgiy7EsLnRY8EhR/AgjoHY37etpj+v6kcaA+B7Q2oSYr11 +beE8ft41eEFS8AnSJd5hE9Ym +-----END PRIVATE KEY----- ` ) @@ -157,29 +211,20 @@ func generateRSAPrivateKey(t *testing.T) *rsa.PrivateKey { } func generateCSR(t *testing.T, secretKey crypto.Signer) []byte { - asn1Subj, _ := asn1.Marshal(pkix.Name{ - CommonName: "test", - }.ToRDNSequence()) - template := x509.CertificateRequest{ - RawSubject: asn1Subj, - SignatureAlgorithm: x509.SHA256WithRSA, - } - - csrBytes, err := x509.CreateCertificateRequest(rand.Reader, &template, secretKey) + csr, err := gen.CSRWithSigner(secretKey, + gen.SetCSRCommonName("test"), + ) if err != nil { - t.Errorf("failed to create CSR: %s", err) - t.FailNow() + t.Fatal(err) } - csr := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE REQUEST", Bytes: csrBytes}) - return csr } type testSignT struct { - issuer *cmapi.Issuer + issuer *cmapiv1.Issuer fakeLister *listers.FakeSecretLister - fakeClient *vaultfake.Client + fakeClient *vaultfake.FakeClient csrPEM []byte expectedErr error @@ -197,8 +242,8 @@ func signedCertificateSecret(issuingCaPEM string, caPEM ...string) *certutil.Sec secret.Data["issuing_ca"] = issuingCaPEM // Vault returns ca_chain only when a certificate chain is set along with a CA certificate to Vault PKI mount - // See https://github.com/hashicorp/vault/blob/v1.5.0/builtin/logical/pki/path_issue_sign.go#L256 - // See https://github.com/hashicorp/vault/blob/v1.5.5/sdk/helper/certutil/types.go#L627 + // See https://github.com/hashicorp/vault/blob/cmapiv1.5.0/builtin/logical/pki/path_issue_sign.go#L256 + // See https://github.com/hashicorp/vault/blob/cmapiv1.5.5/sdk/helper/certutil/types.go#L627 if len(caPEM) > 0 { chain := []string{issuingCaPEM} chain = append(chain, caPEM...) @@ -240,7 +285,7 @@ func TestSign(t *testing.T) { "a good csr but failed request should error": { csrPEM: csrPEM, issuer: gen.Issuer("vault-issuer", - gen.SetIssuerVault(cmapi.VaultIssuer{}), + gen.SetIssuerVault(cmapiv1.VaultIssuer{}), ), fakeClient: vaultfake.NewFakeClient().WithRawRequest(nil, errors.New("request failed")), expectedErr: errors.New("failed to sign certificate by vault: request failed"), @@ -251,7 +296,7 @@ func TestSign(t *testing.T) { "a good csr and good response with no root should return a certificate with the intermediate in the chain and as the CA": { csrPEM: csrPEM, issuer: gen.Issuer("vault-issuer", - gen.SetIssuerVault(cmapi.VaultIssuer{}), + gen.SetIssuerVault(cmapiv1.VaultIssuer{}), ), fakeClient: vaultfake.NewFakeClient().WithRawRequest(&vault.Response{ Response: &http.Response{ @@ -265,7 +310,7 @@ func TestSign(t *testing.T) { "a good csr and good response with a root should return a certificate without the root in the chain but with the root as the CA": { csrPEM: csrPEM, issuer: gen.Issuer("vault-issuer", - gen.SetIssuerVault(cmapi.VaultIssuer{}), + gen.SetIssuerVault(cmapiv1.VaultIssuer{}), ), fakeClient: vaultfake.NewFakeClient().WithRawRequest(&vault.Response{ Response: &http.Response{ @@ -279,7 +324,7 @@ func TestSign(t *testing.T) { "vault issuer with namespace specified": { csrPEM: csrPEM, issuer: gen.Issuer("vault-issuer", - gen.SetIssuerVault(cmapi.VaultIssuer{Namespace: "test"}), + gen.SetIssuerVault(cmapiv1.VaultIssuer{Namespace: "test"}), ), fakeClient: vaultfake.NewFakeClient().WithRawRequest(&vault.Response{ Response: &http.Response{ @@ -372,15 +417,6 @@ func TestExtractCertificatesFromVaultCertificateSecret(t *testing.T) { } } -type testSetTokenT struct { - expectedToken string - expectedErr error - - issuer *cmapi.Issuer - fakeLister *listers.FakeSecretLister - fakeClient *vaultfake.Client -} - func TestSetToken(t *testing.T) { tokenSecret := &corev1.Secret{ Data: map[string][]byte{ @@ -399,27 +435,35 @@ func TestSetToken(t *testing.T) { "my-kube-key": []byte("my-secret-kube-token"), }, } - tests := map[string]testSetTokenT{ - "if neither token secret ref, app role secret ref, or kube auth then not found then error": { + tests := map[string]struct { + expectedToken string + expectedErr error + + issuer cmapiv1.GenericIssuer + fakeLister *listers.FakeSecretLister + mockCreateToken func(t *testing.T) CreateToken + + fakeClient *vaultfake.FakeClient + }{ + "if neither token secret ref, app role secret ref, clientCertificate auth or kube auth not found then error": { issuer: gen.Issuer("vault-issuer", - gen.SetIssuerVault(cmapi.VaultIssuer{ + gen.SetIssuerVault(cmapiv1.VaultIssuer{ CABundle: []byte(testLeafCertificate), - Auth: cmapi.VaultAuth{}, + Auth: cmapiv1.VaultAuth{}, }), ), fakeLister: listers.FakeSecretListerFrom(listers.NewFakeSecretLister()), - fakeClient: vaultfake.NewFakeClient(), expectedToken: "", expectedErr: errors.New( - "error initializing Vault client: tokenSecretRef, appRoleSecretRef, or Kubernetes auth role not set", + "error initializing Vault client: tokenSecretRef, appRoleSecretRef, clientCertificate, or Kubernetes auth role not set", ), }, "if token secret ref is set but secret doesn't exist should error": { issuer: gen.Issuer("vault-issuer", - gen.SetIssuerVault(cmapi.VaultIssuer{ + gen.SetIssuerVault(cmapiv1.VaultIssuer{ CABundle: []byte(testLeafCertificate), - Auth: cmapi.VaultAuth{ + Auth: cmapiv1.VaultAuth{ TokenSecretRef: &cmmeta.SecretKeySelector{ LocalObjectReference: cmmeta.LocalObjectReference{ Name: "secret-ref-name", @@ -431,16 +475,15 @@ func TestSetToken(t *testing.T) { fakeLister: listers.FakeSecretListerFrom(listers.NewFakeSecretLister(), listers.SetFakeSecretNamespaceListerGet(nil, errors.New("secret does not exists")), ), - fakeClient: vaultfake.NewFakeClient(), expectedToken: "", expectedErr: errors.New("secret does not exists"), }, "if token secret ref set, return client using token stored": { issuer: gen.Issuer("vault-issuer", - gen.SetIssuerVault(cmapi.VaultIssuer{ + gen.SetIssuerVault(cmapiv1.VaultIssuer{ CABundle: []byte(testLeafCertificate), - Auth: cmapi.VaultAuth{ + Auth: cmapiv1.VaultAuth{ TokenSecretRef: &cmmeta.SecretKeySelector{ LocalObjectReference: cmmeta.LocalObjectReference{ Name: "secret-ref-name", @@ -453,17 +496,17 @@ func TestSetToken(t *testing.T) { fakeLister: listers.FakeSecretListerFrom(listers.NewFakeSecretLister(), listers.SetFakeSecretNamespaceListerGet(tokenSecret, nil), ), - fakeClient: vaultfake.NewFakeClient(), + expectedToken: "my-secret-token", expectedErr: nil, }, "if app role set but secret token not but vault fails to return token, error": { issuer: gen.Issuer("vault-issuer", - gen.SetIssuerVault(cmapi.VaultIssuer{ + gen.SetIssuerVault(cmapiv1.VaultIssuer{ CABundle: []byte(testLeafCertificate), - Auth: cmapi.VaultAuth{ - AppRole: &cmapi.VaultAppRole{ + Auth: cmapiv1.VaultAuth{ + AppRole: &cmapiv1.VaultAppRole{ RoleId: "my-role-id", SecretRef: cmmeta.SecretKeySelector{ LocalObjectReference: cmmeta.LocalObjectReference{ @@ -478,17 +521,16 @@ func TestSetToken(t *testing.T) { fakeLister: listers.FakeSecretListerFrom(listers.NewFakeSecretLister(), listers.SetFakeSecretNamespaceListerGet(nil, errors.New("secret not found")), ), - fakeClient: vaultfake.NewFakeClient(), expectedToken: "", expectedErr: errors.New("secret not found"), }, "if app role secret ref set, return client using token stored": { issuer: gen.Issuer("vault-issuer", - gen.SetIssuerVault(cmapi.VaultIssuer{ + gen.SetIssuerVault(cmapiv1.VaultIssuer{ CABundle: []byte(testLeafCertificate), - Auth: cmapi.VaultAuth{ - AppRole: &cmapi.VaultAppRole{ + Auth: cmapiv1.VaultAuth{ + AppRole: &cmapiv1.VaultAppRole{ RoleId: "my-role-id", SecretRef: cmmeta.SecretKeySelector{ LocalObjectReference: cmmeta.LocalObjectReference{ @@ -515,12 +557,98 @@ func TestSetToken(t *testing.T) { expectedErr: nil, }, + "if clientCertificate auth is set but referenced secret doesn't exist return error": { + issuer: gen.Issuer("vault-issuer", + gen.SetIssuerVault(cmapiv1.VaultIssuer{ + CABundle: []byte(testLeafCertificate), + Auth: cmapiv1.VaultAuth{ + ClientCertificate: &cmapiv1.VaultClientCertificateAuth{ + SecretName: "secret-ref-name", + }, + }, + }), + ), + fakeLister: listers.FakeSecretListerFrom(listers.NewFakeSecretLister(), + listers.SetFakeSecretNamespaceListerGet(nil, errors.New("secret does not exist")), + ), + fakeClient: vaultfake.NewFakeClient(), + expectedToken: "", + expectedErr: errors.New("secret does not exist"), + }, + + "if clientCertificate auth set but referenced secret doesn't contain tls.crt return error": { + issuer: gen.Issuer("vault-issuer", + gen.SetIssuerVault(cmapiv1.VaultIssuer{ + CABundle: []byte(testLeafCertificate), + Auth: cmapiv1.VaultAuth{ + ClientCertificate: &cmapiv1.VaultClientCertificateAuth{ + SecretName: "secret-ref-name", + }, + }, + }), + ), + fakeLister: listers.FakeSecretListerFrom(listers.NewFakeSecretLister(), + listers.SetFakeSecretNamespaceListerGet(&corev1.Secret{ + Data: map[string][]byte{ + "tls.key": []byte(testLeafCertificate), + }, + }, nil), + ), + fakeClient: vaultfake.NewFakeClient(), + expectedToken: "", + expectedErr: errors.New("no data for tls.crt in secret 'test-namespace/secret-ref-name'"), + }, + + "if clientCertificate auth set but referenced secret doesn't contain tls.key return error": { + issuer: gen.Issuer("vault-issuer", + gen.SetIssuerVault(cmapiv1.VaultIssuer{ + CABundle: []byte(testLeafCertificate), + Auth: cmapiv1.VaultAuth{ + ClientCertificate: &cmapiv1.VaultClientCertificateAuth{ + SecretName: "secret-ref-name", + }, + }, + }), + ), + fakeLister: listers.FakeSecretListerFrom(listers.NewFakeSecretLister(), + listers.SetFakeSecretNamespaceListerGet(&corev1.Secret{ + Data: map[string][]byte{ + "tls.crt": []byte(testLeafCertificate), + }, + }, nil), + ), + fakeClient: vaultfake.NewFakeClient(), + expectedToken: "", + expectedErr: errors.New("no data for tls.key in secret 'test-namespace/secret-ref-name'"), + }, + + "if clientCertificate auth set but there is no secret referenced, do not return error": { + issuer: gen.Issuer("vault-issuer", + gen.SetIssuerVault(cmapiv1.VaultIssuer{ + CABundle: []byte(testLeafCertificate), + Auth: cmapiv1.VaultAuth{ + ClientCertificate: &cmapiv1.VaultClientCertificateAuth{}, + }, + }), + ), + fakeClient: vaultfake.NewFakeClient().WithRawRequest(&vault.Response{ + Response: &http.Response{ + Body: io.NopCloser( + strings.NewReader( + `{"request_id":"","lease_id":"","lease_duration":0,"renewable":false,"data":null,"warnings":null,"data":{"id":"my-token"}}`), + ), + }, + }, nil), + expectedToken: "my-token", + expectedErr: nil, + }, + "if kubernetes role auth set but reference secret doesn't exist return error": { issuer: gen.Issuer("vault-issuer", - gen.SetIssuerVault(cmapi.VaultIssuer{ + gen.SetIssuerVault(cmapiv1.VaultIssuer{ CABundle: []byte(testLeafCertificate), - Auth: cmapi.VaultAuth{ - Kubernetes: &cmapi.VaultKubernetesAuth{ + Auth: cmapiv1.VaultAuth{ + Kubernetes: &cmapiv1.VaultKubernetesAuth{ Role: "kube-vault-role", SecretRef: cmmeta.SecretKeySelector{ LocalObjectReference: cmmeta.LocalObjectReference{ @@ -535,17 +663,16 @@ func TestSetToken(t *testing.T) { fakeLister: listers.FakeSecretListerFrom(listers.NewFakeSecretLister(), listers.SetFakeSecretNamespaceListerGet(nil, errors.New("secret does not exists")), ), - fakeClient: vaultfake.NewFakeClient(), expectedToken: "", expectedErr: errors.New("error reading Kubernetes service account token from secret-ref-name: secret does not exists"), }, "if kubernetes role auth set but reference secret doesn't contain data at key error": { issuer: gen.Issuer("vault-issuer", - gen.SetIssuerVault(cmapi.VaultIssuer{ + gen.SetIssuerVault(cmapiv1.VaultIssuer{ CABundle: []byte(testLeafCertificate), - Auth: cmapi.VaultAuth{ - Kubernetes: &cmapi.VaultKubernetesAuth{ + Auth: cmapiv1.VaultAuth{ + Kubernetes: &cmapiv1.VaultKubernetesAuth{ Role: "kube-vault-role", SecretRef: cmmeta.SecretKeySelector{ LocalObjectReference: cmmeta.LocalObjectReference{ @@ -560,17 +687,16 @@ func TestSetToken(t *testing.T) { fakeLister: listers.FakeSecretListerFrom(listers.NewFakeSecretLister(), listers.SetFakeSecretNamespaceListerGet(&corev1.Secret{}, nil), ), - fakeClient: vaultfake.NewFakeClient(), expectedToken: "", expectedErr: errors.New(`error reading Kubernetes service account token from secret-ref-name: no data for "my-kube-key" in secret 'test-namespace/secret-ref-name'`), }, "if kubernetes role auth set but errors with a raw request should error": { issuer: gen.Issuer("vault-issuer", - gen.SetIssuerVault(cmapi.VaultIssuer{ + gen.SetIssuerVault(cmapiv1.VaultIssuer{ CABundle: []byte(testLeafCertificate), - Auth: cmapi.VaultAuth{ - Kubernetes: &cmapi.VaultKubernetesAuth{ + Auth: cmapiv1.VaultAuth{ + Kubernetes: &cmapiv1.VaultKubernetesAuth{ Role: "kube-vault-role", SecretRef: cmmeta.SecretKeySelector{ LocalObjectReference: cmmeta.LocalObjectReference{ @@ -592,10 +718,10 @@ func TestSetToken(t *testing.T) { "foo": { issuer: gen.Issuer("vault-issuer", - gen.SetIssuerVault(cmapi.VaultIssuer{ + gen.SetIssuerVault(cmapiv1.VaultIssuer{ CABundle: []byte(testLeafCertificate), - Auth: cmapi.VaultAuth{ - Kubernetes: &cmapi.VaultKubernetesAuth{ + Auth: cmapiv1.VaultAuth{ + Kubernetes: &cmapiv1.VaultKubernetesAuth{ Role: "kube-vault-role", SecretRef: cmmeta.SecretKeySelector{ LocalObjectReference: cmmeta.LocalObjectReference{ @@ -622,12 +748,12 @@ func TestSetToken(t *testing.T) { expectedErr: nil, }, - "if app role secret ref and token secret set, take preference on token secret": { + "if appRole.secretRef, tokenSecretRef set, take preference on tokenSecretRef": { issuer: gen.Issuer("vault-issuer", - gen.SetIssuerVault(cmapi.VaultIssuer{ + gen.SetIssuerVault(cmapiv1.VaultIssuer{ CABundle: []byte(testLeafCertificate), - Auth: cmapi.VaultAuth{ - AppRole: &cmapi.VaultAppRole{ + Auth: cmapiv1.VaultAuth{ + AppRole: &cmapiv1.VaultAppRole{ RoleId: "my-role-id", SecretRef: cmmeta.SecretKeySelector{ LocalObjectReference: cmmeta.LocalObjectReference{ @@ -648,21 +774,189 @@ func TestSetToken(t *testing.T) { fakeLister: listers.FakeSecretListerFrom(listers.NewFakeSecretLister(), listers.SetFakeSecretNamespaceListerGet(tokenSecret, nil), ), - fakeClient: vaultfake.NewFakeClient(), expectedToken: "my-secret-token", expectedErr: nil, }, + + "if kubernetes.serviceAccountRef set, request token and exchange it for a vault token (Issuer)": { + issuer: gen.Issuer("vault-issuer", + gen.SetIssuerVault(cmapiv1.VaultIssuer{ + CABundle: []byte(testLeafCertificate), + Auth: cmapiv1.VaultAuth{ + Kubernetes: &cmapiv1.VaultKubernetesAuth{ + Role: "kube-vault-role", + ServiceAccountRef: &cmapiv1.ServiceAccountRef{ + Name: "my-service-account", + }, + Path: "my-path", + }, + }, + }), + ), + mockCreateToken: func(t *testing.T) CreateToken { + return func(_ context.Context, saName string, req *authv1.TokenRequest, _ metav1.CreateOptions) (*authv1.TokenRequest, error) { + assert.Equal(t, "my-service-account", saName) + assert.Equal(t, "vault://default-unit-test-ns/vault-issuer", req.Spec.Audiences[0]) + assert.Equal(t, int64(600), *req.Spec.ExpirationSeconds) + return &authv1.TokenRequest{Status: authv1.TokenRequestStatus{ + Token: "kube-sa-token", + }}, nil + } + }, + fakeClient: vaultfake.NewFakeClient().WithRawRequestFn(func(t *testing.T, req *vault.Request) (*vault.Response, error) { + // Vault exchanges the Kubernetes token with a Vault token. + assert.Equal(t, "kube-sa-token", req.Obj.(map[string]string)["jwt"]) + assert.Equal(t, "kube-vault-role", req.Obj.(map[string]string)["role"]) + return &vault.Response{Response: &http.Response{Body: io.NopCloser(strings.NewReader( + `{"request_id":"","lease_id":"","lease_duration":0,"renewable":false,"data":null,"warnings":null,"data":{"id":"vault-token"}}`, + ))}}, nil + }), + expectedToken: "vault-token", + expectedErr: nil, + }, + + "if kubernetes.serviceAccountRef set, request token and exchange it for a vault token (ClusterIssuer)": { + issuer: gen.ClusterIssuer("vault-issuer", + gen.SetIssuerVault(cmapiv1.VaultIssuer{ + CABundle: []byte(testLeafCertificate), + Auth: cmapiv1.VaultAuth{ + Kubernetes: &cmapiv1.VaultKubernetesAuth{ + Role: "kube-vault-role", + ServiceAccountRef: &cmapiv1.ServiceAccountRef{ + Name: "my-service-account", + }, + Path: "my-path", + }, + }, + }), + ), + mockCreateToken: func(t *testing.T) CreateToken { + return func(_ context.Context, saName string, req *authv1.TokenRequest, _ metav1.CreateOptions) (*authv1.TokenRequest, error) { + assert.Equal(t, "my-service-account", saName) + assert.Equal(t, "vault://vault-issuer", req.Spec.Audiences[0]) + assert.Equal(t, int64(600), *req.Spec.ExpirationSeconds) + return &authv1.TokenRequest{Status: authv1.TokenRequestStatus{ + Token: "kube-sa-token", + }}, nil + } + }, + fakeClient: vaultfake.NewFakeClient().WithRawRequestFn(func(t *testing.T, req *vault.Request) (*vault.Response, error) { + // Vault exchanges the Kubernetes token with a Vault token. + assert.Equal(t, "kube-sa-token", req.Obj.(map[string]string)["jwt"]) + assert.Equal(t, "kube-vault-role", req.Obj.(map[string]string)["role"]) + return &vault.Response{Response: &http.Response{Body: io.NopCloser(strings.NewReader( + `{"request_id":"","lease_id":"","lease_duration":0,"renewable":false,"data":null,"warnings":null,"data":{"id":"vault-token"}}`, + ))}}, nil + }), + expectedToken: "vault-token", + expectedErr: nil, + }, + + "if kubernetes.serviceAccountRef set and audiences are provided, request token and exchange it for a vault token (Issuer)": { + issuer: gen.Issuer("vault-issuer", + gen.SetIssuerVault(cmapiv1.VaultIssuer{ + CABundle: []byte(testLeafCertificate), + Auth: cmapiv1.VaultAuth{ + Kubernetes: &cmapiv1.VaultKubernetesAuth{ + Role: "kube-vault-role", + ServiceAccountRef: &cmapiv1.ServiceAccountRef{ + Name: "my-service-account", + TokenAudiences: []string{ + "https://custom-audience", + }, + }, + Path: "my-path", + }, + }, + }), + ), + mockCreateToken: func(t *testing.T) CreateToken { + return func(_ context.Context, saName string, req *authv1.TokenRequest, _ metav1.CreateOptions) (*authv1.TokenRequest, error) { + assert.Equal(t, "my-service-account", saName) + assert.Len(t, req.Spec.Audiences, 2) + assert.Contains(t, req.Spec.Audiences, "https://custom-audience") + assert.Contains(t, req.Spec.Audiences, "vault://default-unit-test-ns/vault-issuer") + assert.Equal(t, int64(600), *req.Spec.ExpirationSeconds) + return &authv1.TokenRequest{Status: authv1.TokenRequestStatus{ + Token: "kube-sa-token", + }}, nil + } + }, + fakeClient: vaultfake.NewFakeClient().WithRawRequestFn(func(t *testing.T, req *vault.Request) (*vault.Response, error) { + // Vault exchanges the Kubernetes token with a Vault token. + assert.Equal(t, "kube-sa-token", req.Obj.(map[string]string)["jwt"]) + assert.Equal(t, "kube-vault-role", req.Obj.(map[string]string)["role"]) + return &vault.Response{Response: &http.Response{Body: io.NopCloser(strings.NewReader( + `{"request_id":"","lease_id":"","lease_duration":0,"renewable":false,"data":null,"warnings":null,"data":{"id":"vault-token"}}`, + ))}}, nil + }), + expectedToken: "vault-token", + expectedErr: nil, + }, + + "if kubernetes.serviceAccountRef set and audiences are provided, request token and exchange it for a vault token (ClusterIssuer)": { + issuer: gen.ClusterIssuer("vault-issuer", + gen.SetIssuerVault(cmapiv1.VaultIssuer{ + CABundle: []byte(testLeafCertificate), + Auth: cmapiv1.VaultAuth{ + Kubernetes: &cmapiv1.VaultKubernetesAuth{ + Role: "kube-vault-role", + ServiceAccountRef: &cmapiv1.ServiceAccountRef{ + Name: "my-service-account", + TokenAudiences: []string{ + "https://custom-audience", + }, + }, + Path: "my-path", + }, + }, + }), + ), + mockCreateToken: func(t *testing.T) CreateToken { + return func(_ context.Context, saName string, req *authv1.TokenRequest, _ metav1.CreateOptions) (*authv1.TokenRequest, error) { + assert.Equal(t, "my-service-account", saName) + assert.Len(t, req.Spec.Audiences, 2) + assert.Contains(t, req.Spec.Audiences, "https://custom-audience") + assert.Contains(t, req.Spec.Audiences, "vault://vault-issuer") + assert.Equal(t, int64(600), *req.Spec.ExpirationSeconds) + return &authv1.TokenRequest{Status: authv1.TokenRequestStatus{ + Token: "kube-sa-token", + }}, nil + } + }, + fakeClient: vaultfake.NewFakeClient().WithRawRequestFn(func(t *testing.T, req *vault.Request) (*vault.Response, error) { + // Vault exchanges the Kubernetes token with a Vault token. + assert.Equal(t, "kube-sa-token", req.Obj.(map[string]string)["jwt"]) + assert.Equal(t, "kube-vault-role", req.Obj.(map[string]string)["role"]) + return &vault.Response{Response: &http.Response{Body: io.NopCloser(strings.NewReader( + `{"request_id":"","lease_id":"","lease_duration":0,"renewable":false,"data":null,"warnings":null,"data":{"id":"vault-token"}}`, + ))}}, nil + }), + expectedToken: "vault-token", + expectedErr: nil, + }, } for name, test := range tests { t.Run(name, func(t *testing.T) { + if test.fakeClient == nil { + test.fakeClient = &vaultfake.FakeClient{T: t} + } else { + test.fakeClient.T = t + } + var mockCreateToken CreateToken + if test.mockCreateToken != nil { + mockCreateToken = test.mockCreateToken(t) + } + v := &Vault{ namespace: "test-namespace", secretsLister: test.fakeLister, + createToken: mockCreateToken, issuer: test.issuer, } - err := v.setToken(test.fakeClient) + err := v.setToken(t.Context(), test.fakeClient) if ((test.expectedErr == nil) != (err == nil)) && test.expectedErr != nil && test.expectedErr.Error() != err.Error() { @@ -670,9 +964,9 @@ func TestSetToken(t *testing.T) { test.expectedErr, err) } - if test.fakeClient.Token() != test.expectedToken { + if test.fakeClient.GotToken != test.expectedToken { t.Errorf("got unexpected client token, exp=%s got=%s", - test.expectedToken, test.fakeClient.Token()) + test.expectedToken, test.fakeClient.GotToken) } }) } @@ -683,7 +977,7 @@ type testAppRoleRefT struct { expectedSecretID string expectedErr error - appRole *cmapi.VaultAppRole + appRole *cmapiv1.VaultAppRole fakeLister *listers.FakeSecretLister } @@ -691,7 +985,7 @@ type testAppRoleRefT struct { func TestAppRoleRef(t *testing.T) { errSecretGet := errors.New("no secret found") - basicAppRoleRef := &cmapi.VaultAppRole{ + basicAppRoleRef := &cmapiv1.VaultAppRole{ RoleId: "my-role-id", } @@ -707,7 +1001,7 @@ func TestAppRoleRef(t *testing.T) { }, "no data in key should fail": { - appRole: &cmapi.VaultAppRole{ + appRole: &cmapiv1.VaultAppRole{ RoleId: "", SecretRef: cmmeta.SecretKeySelector{ LocalObjectReference: cmmeta.LocalObjectReference{ @@ -730,7 +1024,7 @@ func TestAppRoleRef(t *testing.T) { }, "should return roleID and secretID with trimmed space": { - appRole: &cmapi.VaultAppRole{ + appRole: &cmapiv1.VaultAppRole{ RoleId: " my-role-id ", SecretRef: cmmeta.SecretKeySelector{ LocalObjectReference: cmmeta.LocalObjectReference{ @@ -878,19 +1172,20 @@ func TestTokenRef(t *testing.T) { type testNewConfigT struct { expectedErr error - issuer *cmapi.Issuer + issuer *cmapiv1.Issuer checkFunc func(cfg *vault.Config, err error) error - fakeLister *listers.FakeSecretLister + fakeLister *listers.FakeSecretLister + fakeCreateToken func(t *testing.T) CreateToken } func TestNewConfig(t *testing.T) { caBundleSecretRefFakeSecretLister := func(namespace, secret, key, cert string) *listers.FakeSecretLister { return listers.FakeSecretListerFrom(listers.NewFakeSecretLister(), func(f *listers.FakeSecretLister) { - f.SecretsFn = func(namespace string) clientcorev1.SecretNamespaceLister { + f.SecretsFn = func(listerNamespace string) clientcorev1.SecretNamespaceLister { return listers.FakeSecretNamespaceListerFrom(listers.NewFakeSecretNamespaceLister(), func(fn *listers.FakeSecretNamespaceLister) { fn.GetFn = func(name string) (*corev1.Secret, error) { - if name == secret && namespace == namespace { + if name == secret && listerNamespace == namespace { return &corev1.Secret{ Data: map[string][]byte{ key: []byte(cert), @@ -902,10 +1197,29 @@ func TestNewConfig(t *testing.T) { } }) } + clientCertificateSecretRefFakeSecretLister := func(namespace, secret, caKey, caCert, clientKey, clientCert, privateKey, privateKeyCert string) *listers.FakeSecretLister { + return listers.FakeSecretListerFrom(listers.NewFakeSecretLister(), func(f *listers.FakeSecretLister) { + f.SecretsFn = func(listerNamespace string) clientcorev1.SecretNamespaceLister { + return listers.FakeSecretNamespaceListerFrom(listers.NewFakeSecretNamespaceLister(), func(fn *listers.FakeSecretNamespaceLister) { + fn.GetFn = func(name string) (*corev1.Secret, error) { + if name == secret && listerNamespace == namespace { + return &corev1.Secret{ + Data: map[string][]byte{ + caKey: []byte(caCert), + clientKey: []byte(clientCert), + privateKey: []byte(privateKeyCert), + }}, nil + } + return nil, errors.New("unexpected secret name or namespace passed to FakeSecretLister") + } + }) + } + }) + } tests := map[string]testNewConfigT{ "no CA bundle set in issuer should return nil": { issuer: gen.Issuer("vault-issuer", - gen.SetIssuerVault(cmapi.VaultIssuer{ + gen.SetIssuerVault(cmapiv1.VaultIssuer{ CABundle: nil, }), ), @@ -914,7 +1228,8 @@ func TestNewConfig(t *testing.T) { "a bad cert bundle should error": { issuer: gen.Issuer("vault-issuer", - gen.SetIssuerVault(cmapi.VaultIssuer{ + gen.SetIssuerVault(cmapiv1.VaultIssuer{ + Server: "https://vault.example.com", CABundle: []byte("a bad cert bundle"), }), ), @@ -923,25 +1238,20 @@ func TestNewConfig(t *testing.T) { "a good cert bundle should be added to the config": { issuer: gen.Issuer("vault-issuer", - gen.SetIssuerVault(cmapi.VaultIssuer{ + gen.SetIssuerVault(cmapiv1.VaultIssuer{ + Server: "https://vault.example.com", CABundle: []byte(testLeafCertificate), }), ), expectedErr: nil, - checkFunc: func(cfg *vault.Config, error error) error { + checkFunc: func(cfg *vault.Config, err error) error { testCA := x509.NewCertPool() testCA.AppendCertsFromPEM([]byte(testLeafCertificate)) - subs := cfg.HttpClient.Transport.(*http.Transport).TLSClientConfig.RootCAs.Subjects() + clientCA := cfg.HttpClient.Transport.(*http.Transport).TLSClientConfig.RootCAs - err := fmt.Errorf("got unexpected root CAs in config, exp=%s got=%s", - testCA.Subjects(), subs) - if len(subs) != len(testCA.Subjects()) { - return err - } - for i := range subs { - if !bytes.Equal(subs[i], testCA.Subjects()[i]) { - return err - } + if !clientCA.Equal(testCA) { + return fmt.Errorf("got unexpected root CAs in config, exp=%v got=%v", + testCA, clientCA) } return nil @@ -950,7 +1260,8 @@ func TestNewConfig(t *testing.T) { "a good bundle from a caBundleSecretRef should be added to the config": { issuer: gen.Issuer("vault-issuer", - gen.SetIssuerVault(cmapi.VaultIssuer{ + gen.SetIssuerVault(cmapiv1.VaultIssuer{ + Server: "https://vault.example.com", CABundleSecretRef: &cmmeta.SecretKeySelector{ Key: "my-bundle.crt", LocalObjectReference: cmmeta.LocalObjectReference{ @@ -959,24 +1270,18 @@ func TestNewConfig(t *testing.T) { }, }, )), - checkFunc: func(cfg *vault.Config, error error) error { - if error != nil { - return error + checkFunc: func(cfg *vault.Config, err error) error { + if err != nil { + return err } testCA := x509.NewCertPool() testCA.AppendCertsFromPEM([]byte(testLeafCertificate)) - subs := cfg.HttpClient.Transport.(*http.Transport).TLSClientConfig.RootCAs.Subjects() + clientCA := cfg.HttpClient.Transport.(*http.Transport).TLSClientConfig.RootCAs - err := fmt.Errorf("got unexpected root CAs in config, exp=%s got=%s", - testCA.Subjects(), subs) - if len(subs) != len(testCA.Subjects()) { - return err - } - for i := range subs { - if !bytes.Equal(subs[i], testCA.Subjects()[i]) { - return err - } + if !clientCA.Equal(testCA) { + return fmt.Errorf("got unexpected root CAs in config, exp=%v got=%v", + testCA, clientCA) } return nil @@ -985,7 +1290,8 @@ func TestNewConfig(t *testing.T) { }, "a good bundle from a caBundleSecretRef with default key should be added to the config": { issuer: gen.Issuer("vault-issuer", - gen.SetIssuerVault(cmapi.VaultIssuer{ + gen.SetIssuerVault(cmapiv1.VaultIssuer{ + Server: "https://vault.example.com", CABundleSecretRef: &cmmeta.SecretKeySelector{ LocalObjectReference: cmmeta.LocalObjectReference{ Name: "bundle", @@ -993,24 +1299,18 @@ func TestNewConfig(t *testing.T) { }, }, )), - checkFunc: func(cfg *vault.Config, error error) error { - if error != nil { - return error + checkFunc: func(cfg *vault.Config, err error) error { + if err != nil { + return err } testCA := x509.NewCertPool() testCA.AppendCertsFromPEM([]byte(testLeafCertificate)) - subs := cfg.HttpClient.Transport.(*http.Transport).TLSClientConfig.RootCAs.Subjects() + clientCA := cfg.HttpClient.Transport.(*http.Transport).TLSClientConfig.RootCAs - err := fmt.Errorf("got unexpected root CAs in config, exp=%s got=%s", - testCA.Subjects(), subs) - if len(subs) != len(testCA.Subjects()) { - return err - } - for i := range subs { - if !bytes.Equal(subs[i], testCA.Subjects()[i]) { - return err - } + if !clientCA.Equal(testCA) { + return fmt.Errorf("got unexpected root CAs in config, exp=%v got=%v", + testCA, clientCA) } return nil @@ -1019,7 +1319,8 @@ func TestNewConfig(t *testing.T) { }, "a bad bundle from a caBundleSecretRef should error": { issuer: gen.Issuer("vault-issuer", - gen.SetIssuerVault(cmapi.VaultIssuer{ + gen.SetIssuerVault(cmapiv1.VaultIssuer{ + Server: "https://vault.example.com", CABundleSecretRef: &cmmeta.SecretKeySelector{ Key: "my-bundle.crt", LocalObjectReference: cmmeta.LocalObjectReference{ @@ -1031,6 +1332,118 @@ func TestNewConfig(t *testing.T) { expectedErr: errors.New("no Vault CA bundles loaded, check bundle contents"), fakeLister: caBundleSecretRefFakeSecretLister("test-namespace", "bundle", "my-bundle.crt", "not a valid certificate"), }, + "the tokenCreate func should be called with the correct namespace": { + issuer: gen.Issuer("vault-issuer", + gen.SetIssuerVault(cmapiv1.VaultIssuer{ + Server: "https://vault.example.com", + Path: "my-path", + Auth: cmapiv1.VaultAuth{ + Kubernetes: &cmapiv1.VaultKubernetesAuth{ + Role: "my-role", + ServiceAccountRef: &cmapiv1.ServiceAccountRef{ + Name: "my-sa", + }, + }, + }})), + fakeCreateToken: func(t *testing.T) CreateToken { + return func(_ context.Context, saName string, req *authv1.TokenRequest, opts metav1.CreateOptions) (*authv1.TokenRequest, error) { + assert.Equal(t, "test-namespace", req.Namespace) + assert.Equal(t, "my-sa", saName) + return &authv1.TokenRequest{Status: authv1.TokenRequestStatus{ + Token: "foo", + }}, nil + } + }, + }, + "a good client certificate with default key should be added to the config": { + issuer: gen.Issuer("vault-issuer", + gen.SetIssuerVault(cmapiv1.VaultIssuer{ + Server: "https://vault.example.com", + CABundleSecretRef: &cmmeta.SecretKeySelector{ + LocalObjectReference: cmmeta.LocalObjectReference{ + Name: "bundle", + }, + }, + ClientCertSecretRef: &cmmeta.SecretKeySelector{ + LocalObjectReference: cmmeta.LocalObjectReference{ + Name: "bundle", + }, + }, + ClientKeySecretRef: &cmmeta.SecretKeySelector{ + LocalObjectReference: cmmeta.LocalObjectReference{ + Name: "bundle", + }, + }, + }, + )), + checkFunc: func(cfg *vault.Config, err error) error { + if err != nil { + return err + } + + certificates := cfg.HttpClient.Transport.(*http.Transport).TLSClientConfig.Certificates + if len(certificates) != 1 { + return fmt.Errorf("got unexpected number of client certificates in config, exp=1 got=%d", len(certificates)) + } + certificate, err := x509.ParseCertificate(certificates[0].Certificate[0]) + if err != nil { + return err + } + if certificate.Subject.CommonName != "client.bar.ca" { + return fmt.Errorf("got unexpected common name from the client certificate in config, exp=client.bar.ca got=%s", certificate.Subject.CommonName) + } + + return nil + }, + fakeLister: clientCertificateSecretRefFakeSecretLister("test-namespace", "bundle", "ca.crt", testLeafCertificate, "tls.crt", testClientCertificate, "tls.key", testClientCertificatePrivateKey), + }, + "a bad client certificate should error": { + issuer: gen.Issuer("vault-issuer", + gen.SetIssuerVault(cmapiv1.VaultIssuer{ + Server: "https://vault.example.com", + CABundleSecretRef: &cmmeta.SecretKeySelector{ + LocalObjectReference: cmmeta.LocalObjectReference{ + Name: "bundle", + }, + }, + ClientCertSecretRef: &cmmeta.SecretKeySelector{ + LocalObjectReference: cmmeta.LocalObjectReference{ + Name: "bundle", + }, + }, + ClientKeySecretRef: &cmmeta.SecretKeySelector{ + LocalObjectReference: cmmeta.LocalObjectReference{ + Name: "bundle", + }, + }, + }, + )), + expectedErr: errors.New("failed to load vault client certificate: could not parse the TLS certificate from Secrets 'test-namespace/bundle'(cert) and 'test-namespace/bundle'(key): tls: failed to find any PEM data in certificate input"), + fakeLister: clientCertificateSecretRefFakeSecretLister("test-namespace", "bundle", "ca.crt", testLeafCertificate, "tls.crt", "not a valid certificate", "tls.key", "not a valid certificate"), + }, + "if server name is set it should be added to the config": { + issuer: gen.Issuer("vault-issuer", + gen.SetIssuerVault(cmapiv1.VaultIssuer{ + Server: "https://vault.example.com", + ServerName: "vault.example.net", + }, + )), + checkFunc: func(cfg *vault.Config, err error) error { + if err != nil { + return err + } + + expectedServerName := "vault.example.net" + clientServerName := cfg.HttpClient.Transport.(*http.Transport).TLSClientConfig.ServerName + + if clientServerName != expectedServerName { + return fmt.Errorf("got unexpected server name in config, exp=%v got=%v", + expectedServerName, clientServerName) + } + + return nil + }, + }, } for name, test := range tests { @@ -1057,7 +1470,7 @@ func TestNewConfig(t *testing.T) { type requestTokenWithAppRoleRefT struct { client Client - appRole *cmapi.VaultAppRole + appRole *cmapiv1.VaultAppRole fakeLister *listers.FakeSecretLister @@ -1066,7 +1479,7 @@ type requestTokenWithAppRoleRefT struct { } func TestRequestTokenWithAppRoleRef(t *testing.T) { - basicAppRoleRef := &cmapi.VaultAppRole{ + basicAppRoleRef := &cmapiv1.VaultAppRole{ RoleId: "test-role-id", SecretRef: cmmeta.SecretKeySelector{ LocalObjectReference: cmmeta.LocalObjectReference{ @@ -1087,7 +1500,6 @@ func TestRequestTokenWithAppRoleRef(t *testing.T) { tests := map[string]requestTokenWithAppRoleRefT{ "a secret reference that does not exist should error": { - client: vaultfake.NewFakeClient(), appRole: basicAppRoleRef, fakeLister: listers.FakeSecretListerFrom(listers.NewFakeSecretLister(), listers.SetFakeSecretNamespaceListerGet(nil, errors.New("secret not found")), @@ -1182,3 +1594,191 @@ func TestRequestTokenWithAppRoleRef(t *testing.T) { }) } } + +// TestNewWithVaultNamespaces demonstrates that New initializes two Vault +// clients, one with a namespace and one without a namespace which is used for +// interacting with root-only APIs. +func TestNewWithVaultNamespaces(t *testing.T) { + type testCase struct { + name string + vaultNS string + } + + tests := []testCase{ + { + name: "without-namespace", + vaultNS: "", + }, + { + name: "with-namespace", + vaultNS: "vault-ns-1", + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + c, err := New( + t.Context(), + "k8s-ns1", + func(ns string) CreateToken { return nil }, + listers.FakeSecretListerFrom(listers.NewFakeSecretLister(), + listers.SetFakeSecretNamespaceListerGet( + &corev1.Secret{ + Data: map[string][]byte{ + "key1": []byte("not-used"), + }, + }, nil), + ), + &cmapiv1.Issuer{ + ObjectMeta: metav1.ObjectMeta{ + Name: "issuer1", + Namespace: "k8s-ns1", + }, + Spec: cmapiv1.IssuerSpec{ + IssuerConfig: cmapiv1.IssuerConfig{ + Vault: &cmapiv1.VaultIssuer{ + Server: "https://vault.example.com", + Namespace: tc.vaultNS, + Auth: cmapiv1.VaultAuth{ + TokenSecretRef: &cmmeta.SecretKeySelector{ + LocalObjectReference: cmmeta.LocalObjectReference{ + Name: "secret1", + }, + Key: "key1", + }, + }, + }, + }, + }, + }) + require.NoError(t, err) + assert.Equal(t, tc.vaultNS, c.(*Vault).client.(*vault.Client).Namespace(), + "The vault client should have the namespace provided in the Issuer resource") + assert.Equal(t, "", c.(*Vault).clientSys.(*vault.Client).Namespace(), + "The vault sys client should never have a namespace") + }) + } +} + +// TestIsVaultInitiatedAndUnsealedIntegration demonstrates that it interacts only with the +// sys/health endpoint and that it supplies the Vault token but not a Vault namespace header. +func TestIsVaultInitiatedAndUnsealedIntegration(t *testing.T) { + + const vaultToken = "token1" + + mux := http.NewServeMux() + mux.HandleFunc("/v1/sys/health", func(response http.ResponseWriter, request *http.Request) { + assert.Empty(t, request.Header.Values("X-Vault-Namespace"), "Unexpected Vault namespace header for root-only API path") + assert.Equal(t, vaultToken, request.Header.Get("X-Vault-Token"), "Expected the Vault token for root-only API path") + }) + server := httptest.NewServer(mux) + defer server.Close() + + v, err := New( + t.Context(), + "k8s-ns1", + func(ns string) CreateToken { return nil }, + listers.FakeSecretListerFrom(listers.NewFakeSecretLister(), + listers.SetFakeSecretNamespaceListerGet( + &corev1.Secret{ + Data: map[string][]byte{ + "key1": []byte(vaultToken), + }, + }, nil), + ), + &cmapiv1.Issuer{ + ObjectMeta: metav1.ObjectMeta{ + Name: "issuer1", + Namespace: "k8s-ns1", + }, + Spec: cmapiv1.IssuerSpec{ + IssuerConfig: cmapiv1.IssuerConfig{ + Vault: &cmapiv1.VaultIssuer{ + Server: server.URL, + Namespace: "ns1", + Auth: cmapiv1.VaultAuth{ + TokenSecretRef: &cmmeta.SecretKeySelector{ + LocalObjectReference: cmmeta.LocalObjectReference{ + Name: "secret1", + }, + Key: "key1", + }, + }, + }, + }, + }, + }) + require.NoError(t, err) + + err = v.IsVaultInitializedAndUnsealed() + require.NoError(t, err) +} + +// TestSignIntegration demonstrates that it interacts only with the API endpoint +// path supplied in the Issuer resource and that it supplies the Vault namespace +// and token to that endpoint. +func TestSignIntegration(t *testing.T) { + const ( + vaultToken = "token1" + vaultNamespace = "vault-ns-1" + vaultPath = "my_pki_mount/sign/my-role-name" + ) + + privatekey := generateRSAPrivateKey(t) + csrPEM := generateCSR(t, privatekey) + + rootBundleData, err := bundlePEM(testIntermediateCa, testRootCa) + require.NoError(t, err) + + mux := http.NewServeMux() + mux.HandleFunc(fmt.Sprintf("/v1/%s", vaultPath), func(response http.ResponseWriter, request *http.Request) { + assert.Equal(t, vaultNamespace, request.Header.Get("X-Vault-Namespace"), "Expected Vault namespace header for namespaced API path") + assert.Equal(t, vaultToken, request.Header.Get("X-Vault-Token"), "Expected the Vault token for root-only API path") + _, err := response.Write(rootBundleData) + require.NoError(t, err) + }) + server := httptest.NewServer(mux) + defer server.Close() + + v, err := New( + t.Context(), + "k8s-ns1", + func(ns string) CreateToken { return nil }, + listers.FakeSecretListerFrom(listers.NewFakeSecretLister(), + listers.SetFakeSecretNamespaceListerGet( + &corev1.Secret{ + Data: map[string][]byte{ + "key1": []byte(vaultToken), + }, + }, nil), + ), + &cmapiv1.Issuer{ + ObjectMeta: metav1.ObjectMeta{ + Name: "issuer1", + Namespace: "k8s-ns1", + }, + Spec: cmapiv1.IssuerSpec{ + IssuerConfig: cmapiv1.IssuerConfig{ + Vault: &cmapiv1.VaultIssuer{ + Server: server.URL, + Path: vaultPath, + Namespace: vaultNamespace, + Auth: cmapiv1.VaultAuth{ + TokenSecretRef: &cmmeta.SecretKeySelector{ + LocalObjectReference: cmmeta.LocalObjectReference{ + Name: "secret1", + }, + Key: "key1", + }, + }, + }, + }, + }, + }) + require.NoError(t, err) + + certPEM, caPEM, err := v.Sign(csrPEM, time.Hour) + require.NoError(t, err) + require.NotEmpty(t, certPEM) + require.NotEmpty(t, caPEM) +} diff --git a/internal/plugin/admission/certificaterequest/approval/certificaterequest_approval.go b/internal/webhook/admission/certificaterequest/approval/certificaterequest_approval.go similarity index 69% rename from internal/plugin/admission/certificaterequest/approval/certificaterequest_approval.go rename to internal/webhook/admission/certificaterequest/approval/certificaterequest_approval.go index 972c6b64681..51ef38037a5 100644 --- a/internal/plugin/admission/certificaterequest/approval/certificaterequest_approval.go +++ b/internal/webhook/admission/certificaterequest/approval/certificaterequest_approval.go @@ -27,53 +27,47 @@ package approval import ( "context" "fmt" - "strings" "sync" admissionv1 "k8s.io/api/admission/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/util/validation/field" "k8s.io/apiserver/pkg/authentication/user" "k8s.io/apiserver/pkg/authorization/authorizer" "k8s.io/client-go/discovery" - "k8s.io/client-go/kubernetes" "github.com/cert-manager/cert-manager/internal/apis/certmanager" "github.com/cert-manager/cert-manager/internal/apis/certmanager/validation/util" "github.com/cert-manager/cert-manager/pkg/webhook/admission" - "github.com/cert-manager/cert-manager/pkg/webhook/admission/initializer" ) -const PluginName = "CertificateRequestApproval" - type certificateRequestApproval struct { *admission.Handler authorizer authorizer.Authorizer discovery discovery.DiscoveryInterface - // resourceCache stores the associated APIResource for a given GroupKind - // to making multiple queries to the API server for every approval. - resourceCache map[schema.GroupKind]metav1.APIResource - mutex sync.RWMutex + // resourceInfo stores the associated resource info for a given GroupKind + // to prevent making multiple queries to the API server for every approval. + resourceInfo map[schema.GroupKind]resourceInfo + mutex sync.RWMutex } -var _ admission.ValidationInterface = &certificateRequestApproval{} -var _ initializer.WantsAuthorizer = &certificateRequestApproval{} -var _ initializer.WantsExternalKubeClientSet = &certificateRequestApproval{} - -func Register(plugins *admission.Plugins) { - plugins.Register(PluginName, func() (admission.Interface, error) { - return NewPlugin(), nil - }) +type resourceInfo struct { + schema.GroupResource + Namespaced bool } -func NewPlugin() admission.Interface { +var _ admission.ValidationInterface = &certificateRequestApproval{} + +func NewPlugin(authz authorizer.Authorizer, discoveryClient discovery.DiscoveryInterface) admission.Interface { return &certificateRequestApproval{ - Handler: admission.NewHandler(admissionv1.Update), - resourceCache: map[schema.GroupKind]metav1.APIResource{}, + Handler: admission.NewHandler(admissionv1.Update), + resourceInfo: map[schema.GroupKind]resourceInfo{}, + + authorizer: authz, + discovery: discoveryClient, } } @@ -99,6 +93,8 @@ func (c *certificateRequestApproval) Validate(ctx context.Context, request admis if kind == "" { kind = "Issuer" } + + // We got the GroupKind, now we need to get the Resource name. apiResource, err := c.apiResourceForGroupKind(schema.GroupKind{Group: group, Kind: kind}) switch { case err == errNoResourceExists: @@ -108,8 +104,8 @@ func (c *certificateRequestApproval) Validate(ctx context.Context, request admis return nil, err } - signerName := signerNameForAPIResource(cr.Spec.IssuerRef.Name, cr.Namespace, *apiResource) - if !isAuthorizedForSignerName(ctx, c.authorizer, userInfoForRequest(request), signerName) { + signerNames := signerNamesForAPIResource(cr.Spec.IssuerRef.Name, cr.Namespace, *apiResource) + if !isAuthorizedForSignerNames(ctx, c.authorizer, userInfoForRequest(request), signerNames) { return nil, field.Forbidden(field.NewPath("status.conditions"), fmt.Sprintf("user %q does not have permissions to set approved/denied conditions for issuer %v", request.UserInfo.Username, cr.Spec.IssuerRef)) } @@ -132,7 +128,7 @@ func approvalConditionsHaveChanged(oldCR, cr *certmanager.CertificateRequest) bo // requests that approve or deny the CertificateRequest. // namespaced will be true if the resource is namespaced. // 'resource' may be nil even if err is also nil. -func (c *certificateRequestApproval) apiResourceForGroupKind(groupKind schema.GroupKind) (resource *metav1.APIResource, err error) { +func (c *certificateRequestApproval) apiResourceForGroupKind(groupKind schema.GroupKind) (info *resourceInfo, err error) { // fast path if resource is in the cache already if resource := c.readAPIResourceFromCache(groupKind); resource != nil { return resource, nil @@ -163,11 +159,7 @@ func (c *certificateRequestApproval) apiResourceForGroupKind(groupKind schema.Gr continue } - r := resource.DeepCopy() - // the Group field is not always populated in responses, so explicitly set it - r.Group = apiGroup.Name - c.cacheAPIResource(groupKind, *r) - return r, nil + return c.cacheAPIResource(groupKind, resource.Name, resource.Namespaced), nil } } } @@ -175,30 +167,48 @@ func (c *certificateRequestApproval) apiResourceForGroupKind(groupKind schema.Gr return nil, errNoResourceExists } -func (c *certificateRequestApproval) readAPIResourceFromCache(groupKind schema.GroupKind) *metav1.APIResource { +func (c *certificateRequestApproval) readAPIResourceFromCache(groupKind schema.GroupKind) *resourceInfo { c.mutex.RLock() defer c.mutex.RUnlock() - if resource, ok := c.resourceCache[groupKind]; ok { - return &resource + if info, ok := c.resourceInfo[groupKind]; ok { + return &info } return nil } -func (c *certificateRequestApproval) cacheAPIResource(groupKind schema.GroupKind, resource metav1.APIResource) { +func (c *certificateRequestApproval) cacheAPIResource(groupKind schema.GroupKind, resourceName string, namespaced bool) *resourceInfo { c.mutex.Lock() defer c.mutex.Unlock() - c.resourceCache[groupKind] = resource + + info := resourceInfo{ + GroupResource: schema.GroupResource{ + Group: groupKind.Group, + Resource: resourceName, + }, + Namespaced: namespaced, + } + + c.resourceInfo[groupKind] = info + + return &info } var errNoResourceExists = fmt.Errorf("no resource registered") -// signerNameForAPIResource returns the computed signerName for a given API resource +// signerNamesForAPIResource returns the computed signerName for a given API resource // referenced by a CertificateRequest in a namespace. -func signerNameForAPIResource(name, namespace string, apiResource metav1.APIResource) string { - if apiResource.Namespaced { - return fmt.Sprintf("%s.%s/%s.%s", apiResource.Name, apiResource.Group, namespace, name) +func signerNamesForAPIResource(name, namespace string, info resourceInfo) []string { + signerNames := make([]string, 0, 2) + + signerNames = append(signerNames, fmt.Sprintf("%s.%s/*", info.Resource, info.Group)) + + if info.Namespaced { + signerNames = append(signerNames, fmt.Sprintf("%s.%s/%s.%s", info.Resource, info.Group, namespace, name)) + } else { + signerNames = append(signerNames, fmt.Sprintf("%s.%s/%s", info.Resource, info.Group, name)) } - return fmt.Sprintf("%s.%s/%s", apiResource.Name, apiResource.Group, name) + + return signerNames } // userInfoForRequest constructs a user.Info suitable for using with the authorizer interface @@ -216,32 +226,23 @@ func userInfoForRequest(req admissionv1.AdmissionRequest) user.Info { } } -// isAuthorizedForSignerName checks whether an entity is authorized to 'approve' certificaterequests -// for a given signerName. +// isAuthorizedForSignerNames checks whether an entity is authorized to 'approve' certificaterequests +// for a given set of signerNames. // We absorb errors from the authorizer because they are already retried by the underlying authorization // client, so we shouldn't ever see them unless the context webhook doesn't have the ability to submit // SARs or the context is cancelled (in which case, the AdmissionResponse won't ever be returned to the apiserver). -func isAuthorizedForSignerName(ctx context.Context, authz authorizer.Authorizer, info user.Info, signerName string) bool { +func isAuthorizedForSignerNames(ctx context.Context, authz authorizer.Authorizer, info user.Info, signerNames []string) bool { verb := "approve" - // First check if the user has explicit permission to 'approve' for the given signerName. - attr := buildAttributes(info, verb, signerName) - decision, _, err := authz.Authorize(ctx, attr) - switch { - case err != nil: - return false - case decision == authorizer.DecisionAllow: - return true - } - // If not, check if the user has wildcard permissions to 'approve' for the domain portion of the signerName, e.g. - // 'issuers.cert-manager.io/*'. - attr = buildWildcardAttributes(info, verb, signerName) - decision, _, err = authz.Authorize(ctx, attr) - switch { - case err != nil: - return false - case decision == authorizer.DecisionAllow: - return true + for _, signerName := range signerNames { + attr := buildAttributes(info, verb, signerName) + decision, _, err := authz.Authorize(ctx, attr) + switch { + case err != nil: + return false + case decision == authorizer.DecisionAllow: + return true + } } return false @@ -259,20 +260,6 @@ func buildAttributes(info user.Info, verb, signerName string) authorizer.Attribu } } -func buildWildcardAttributes(info user.Info, verb, signerName string) authorizer.Attributes { - parts := strings.Split(signerName, "/") - domain := parts[0] - return buildAttributes(info, verb, domain+"/*") -} - -func (c *certificateRequestApproval) SetAuthorizer(a authorizer.Authorizer) { - c.authorizer = a -} - -func (c *certificateRequestApproval) SetExternalKubeClientSet(client kubernetes.Interface) { - c.discovery = client.Discovery() -} - func (c *certificateRequestApproval) ValidateInitialization() error { if c.authorizer == nil { return fmt.Errorf("authorizer not set") diff --git a/internal/plugin/admission/certificaterequest/approval/certificaterequest_approval_test.go b/internal/webhook/admission/certificaterequest/approval/certificaterequest_approval_test.go similarity index 97% rename from internal/plugin/admission/certificaterequest/approval/certificaterequest_approval_test.go rename to internal/webhook/admission/certificaterequest/approval/certificaterequest_approval_test.go index 6bc88d47e73..2dec03faa01 100644 --- a/internal/plugin/admission/certificaterequest/approval/certificaterequest_approval_test.go +++ b/internal/webhook/admission/certificaterequest/approval/certificaterequest_approval_test.go @@ -41,7 +41,7 @@ func TestValidate(t *testing.T) { baseCR := &certmanager.CertificateRequest{ ObjectMeta: metav1.ObjectMeta{Namespace: "testns"}, Spec: certmanager.CertificateRequestSpec{ - IssuerRef: meta.ObjectReference{ + IssuerRef: meta.IssuerReference{ Name: "my-issuer", Kind: "Issuer", Group: "example.io", @@ -297,14 +297,12 @@ func TestValidate(t *testing.T) { for name, test := range tests { t.Run(name, func(t *testing.T) { - a := NewPlugin().(*certificateRequestApproval) - a.discovery = test.discoverclient + a := NewPlugin(test.authorizer, test.discoverclient).(*certificateRequestApproval) if test.authorizer != nil { test.authorizer.t = t } - a.authorizer = test.authorizer - warnings, err := a.Validate(context.TODO(), *test.req, test.oldCR, test.newCR) + warnings, err := a.Validate(t.Context(), *test.req, test.oldCR, test.newCR) if len(warnings) > 0 { t.Errorf("expected no warnings but got: %v", warnings) } diff --git a/internal/webhook/admission/certificaterequest/approval/fuzz_test.go b/internal/webhook/admission/certificaterequest/approval/fuzz_test.go new file mode 100644 index 00000000000..e5614ebcc62 --- /dev/null +++ b/internal/webhook/admission/certificaterequest/approval/fuzz_test.go @@ -0,0 +1,90 @@ +/* +Copyright 2024 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. +*/ + +package approval + +import ( + "testing" + + gfh "github.com/AdaLogics/go-fuzz-headers" + admissionv1 "k8s.io/api/admission/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apiserver/pkg/authorization/authorizer" + + "github.com/cert-manager/cert-manager/internal/apis/certmanager" + discoveryfake "github.com/cert-manager/cert-manager/test/unit/discovery" +) + +// FuzzValidate tests the approval validation with +// random CRs. It can be run with `go test -fuzz=FuzzValidate`. +// It tests for panics, OOMs and stackoverflow-related bugs in +// the authorizer validation. +func FuzzValidate(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte, verb, allowedName string, decision uint8) { + fdp := gfh.NewConsumer(data) + req := &admissionv1.AdmissionRequest{} + err := fdp.GenerateStruct(req) + if err != nil { + return + } + req.RequestResource.Group = "cert-manager.io" + req.RequestResource.Resource = "certificaterequests" + req.RequestSubResource = "status" + + // Add random values to the CR + cr := &certmanager.CertificateRequest{} + err = fdp.GenerateStruct(cr) + if err != nil { + return + } + approvedCR := &certmanager.CertificateRequest{} + err = fdp.GenerateStruct(approvedCR) + if err != nil { + return + } + // Add random values to the Group List + apiGroupList := &metav1.APIGroupList{} + err = fdp.GenerateStruct(apiGroupList) + if err != nil { + return + } + // Add random values to the Resource List + apiResourceList := &metav1.APIResourceList{} + err = fdp.GenerateStruct(apiResourceList) + if err != nil { + return + } + // Create an authorizer with random verb and allowedName + auth := &fakeAuthorizer{ + verb: verb, + allowedName: allowedName, + decision: authorizer.DecisionAllow, + } + // Add a discovery client that returns the Group List + // and Resource List we created above. + discoverclient := discoveryfake.NewDiscovery(). + WithServerGroups(func() (*metav1.APIGroupList, error) { + return apiGroupList, nil + }). + WithServerResourcesForGroupVersion(func(groupVersion string) (*metav1.APIResourceList, error) { + return apiResourceList, nil + }) + // Create the approval plugin + a := NewPlugin(auth, discoverclient).(*certificateRequestApproval) + // Validate + _, _ = a.Validate(t.Context(), *req, cr, approvedCR) + }) +} diff --git a/internal/plugin/admission/certificaterequest/identity/certificaterequest_identity.go b/internal/webhook/admission/certificaterequest/identity/certificaterequest_identity.go similarity index 80% rename from internal/plugin/admission/certificaterequest/identity/certificaterequest_identity.go rename to internal/webhook/admission/certificaterequest/identity/certificaterequest_identity.go index f19752b01c1..61402f2e57f 100644 --- a/internal/plugin/admission/certificaterequest/identity/certificaterequest_identity.go +++ b/internal/webhook/admission/certificaterequest/identity/certificaterequest_identity.go @@ -23,6 +23,7 @@ import ( admissionv1 "k8s.io/api/admission/v1" authenticationv1 "k8s.io/api/authentication/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/util/validation/field" @@ -31,19 +32,10 @@ import ( "github.com/cert-manager/cert-manager/pkg/webhook/admission" ) -const PluginName = "CertificateRequestIdentity" - type certificateRequestIdentity struct { *admission.Handler } -// Register registers a plugin -func Register(plugins *admission.Plugins) { - plugins.Register(PluginName, func() (admission.Interface, error) { - return NewPlugin(), nil - }) -} - var _ admission.ValidationInterface = &certificateRequestIdentity{} var _ admission.MutationInterface = &certificateRequestIdentity{} @@ -53,7 +45,7 @@ func NewPlugin() admission.Interface { } } -func (p *certificateRequestIdentity) Mutate(ctx context.Context, request admissionv1.AdmissionRequest, obj runtime.Object) error { +func (p *certificateRequestIdentity) Mutate(ctx context.Context, request admissionv1.AdmissionRequest, obj *unstructured.Unstructured) error { // Only run this admission plugin for the certificaterequests/status sub-resource if request.RequestResource.Group != "cert-manager.io" || request.RequestResource.Resource != "certificaterequests" || @@ -61,13 +53,28 @@ func (p *certificateRequestIdentity) Mutate(ctx context.Context, request admissi return nil } - cr := obj.(*certmanager.CertificateRequest) - cr.Spec.UID = request.UserInfo.UID - cr.Spec.Username = request.UserInfo.Username - cr.Spec.Groups = request.UserInfo.Groups - cr.Spec.Extra = make(map[string][]string) - for k, v := range request.UserInfo.Extra { - cr.Spec.Extra[k] = v + for _, err := range []error{ + unstructured.SetNestedField(obj.Object, request.UserInfo.UID, "spec", "uid"), + unstructured.SetNestedField(obj.Object, request.UserInfo.Username, "spec", "username"), + unstructured.SetNestedStringSlice(obj.Object, request.UserInfo.Groups, "spec", "groups"), + } { + if err != nil { + return err + } + } + + // Overwrite the 'spec.extra' field with the request.UserInfo.Extra field. + // If the request.UserInfo.Extra field is empty, remove the 'spec.extra' field. + unstructured.RemoveNestedField(obj.Object, "spec", "extra") + if len(request.UserInfo.Extra) > 0 { + unstructuredExtra, err := runtime.DefaultUnstructuredConverter.ToUnstructured(&request.UserInfo.Extra) + if err != nil { + return err + } + + if err := unstructured.SetNestedMap(obj.Object, unstructuredExtra, "spec", "extra"); err != nil { + return err + } } return nil @@ -95,9 +102,9 @@ func (p *certificateRequestIdentity) Validate(ctx context.Context, request admis return nil, fmt.Errorf("internal error: oldObject in admission request is not of type *certmanager.CertificateRequest") } return nil, validateUpdate(oldCR, cr) + default: + return nil, fmt.Errorf("internal error: request operation has changed - this should never be possible") } - - return nil, fmt.Errorf("internal error: request operation has changed - this should never be possible") } func validateUpdate(oldCR *certmanager.CertificateRequest, cr *certmanager.CertificateRequest) error { diff --git a/internal/plugin/admission/certificaterequest/identity/certificaterequest_identity_test.go b/internal/webhook/admission/certificaterequest/identity/certificaterequest_identity_test.go similarity index 81% rename from internal/plugin/admission/certificaterequest/identity/certificaterequest_identity_test.go rename to internal/webhook/admission/certificaterequest/identity/certificaterequest_identity_test.go index e91d956e19b..6d949bf346d 100644 --- a/internal/plugin/admission/certificaterequest/identity/certificaterequest_identity_test.go +++ b/internal/webhook/admission/certificaterequest/identity/certificaterequest_identity_test.go @@ -17,16 +17,18 @@ limitations under the License. package identity import ( - "context" "reflect" "testing" admissionv1 "k8s.io/api/admission/v1" authenticationv1 "k8s.io/api/authentication/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/util/validation/field" "github.com/cert-manager/cert-manager/internal/apis/certmanager" + cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" ) var correctRequestResource = &metav1.GroupVersionResource{ @@ -35,10 +37,36 @@ var correctRequestResource = &metav1.GroupVersionResource{ Resource: "certificaterequests", } +func toUnstructured(t *testing.T, obj runtime.Object) *unstructured.Unstructured { + scheme := runtime.NewScheme() + if err := cmapi.AddToScheme(scheme); err != nil { + t.Fatalf("unexpected error: %v", err) + } + + unstr := unstructured.Unstructured{} + if err := scheme.Convert(obj, &unstr, nil); err != nil { + t.Fatalf("unexpected error: %v", err) + } + + return &unstr +} + +func fromUnstructured(t *testing.T, obj *unstructured.Unstructured, into runtime.Object) { + scheme := runtime.NewScheme() + if err := cmapi.AddToScheme(scheme); err != nil { + t.Fatalf("unexpected error: %v", err) + } + + if err := scheme.Convert(obj, into, nil); err != nil { + t.Fatalf("unexpected error: %v", err) + } +} + func TestMutate(t *testing.T) { plugin := NewPlugin().(*certificateRequestIdentity) - cr := &certmanager.CertificateRequest{} - err := plugin.Mutate(context.Background(), admissionv1.AdmissionRequest{ + cr := &cmapi.CertificateRequest{} + crUnstr := toUnstructured(t, cr) + err := plugin.Mutate(t.Context(), admissionv1.AdmissionRequest{ Operation: admissionv1.Create, RequestResource: &metav1.GroupVersionResource{ Group: "cert-manager.io", @@ -52,10 +80,11 @@ func TestMutate(t *testing.T) { Extra: map[string]authenticationv1.ExtraValue{ "testkey": []string{"testvalue"}, }, - }}, cr) + }}, crUnstr) if err != nil { t.Errorf("unexpected error: %v", err) } + fromUnstructured(t, crUnstr, cr) if cr.Spec.Username != "testuser" { t.Errorf("unexpected username. got: %q, expected %q", cr.Spec.UID, "testuser") @@ -104,8 +133,9 @@ func TestMutate_Ignores(t *testing.T) { } for name, test := range tests { t.Run(name, func(t *testing.T) { - cr := &certmanager.CertificateRequest{} - err := plugin.Mutate(context.Background(), admissionv1.AdmissionRequest{ + cr := &cmapi.CertificateRequest{} + crUnstr := toUnstructured(t, cr) + err := plugin.Mutate(t.Context(), admissionv1.AdmissionRequest{ Operation: test.op, RequestResource: test.gvr, UserInfo: authenticationv1.UserInfo{ @@ -115,7 +145,8 @@ func TestMutate_Ignores(t *testing.T) { Extra: map[string]authenticationv1.ExtraValue{ "testkey": []string{"testvalue"}, }, - }}, cr) + }}, crUnstr) + fromUnstructured(t, crUnstr, cr) if err != nil { t.Errorf("unexpected error: %v", err) } @@ -199,7 +230,7 @@ func TestValidateCreate(t *testing.T) { for name, test := range tests { t.Run(name, func(t *testing.T) { p := NewPlugin().(*certificateRequestIdentity) - gotW, gotE := p.Validate(context.Background(), *test.req, nil, test.cr) + gotW, gotE := p.Validate(t.Context(), *test.req, nil, test.cr) compareErrors(t, test.wantE, gotE) if !reflect.DeepEqual(gotW, test.wantW) { t.Errorf("warnings from ValidateCreate() = %v, want %v", gotW, test.wantW) @@ -314,7 +345,7 @@ func TestValidateUpdate(t *testing.T) { for name, test := range tests { t.Run(name, func(t *testing.T) { p := NewPlugin().(*certificateRequestIdentity) - gotW, gotE := p.Validate(context.Background(), *test.req, test.oldCR, test.newCR) + gotW, gotE := p.Validate(t.Context(), *test.req, test.oldCR, test.newCR) compareErrors(t, test.wantE, gotE) if !reflect.DeepEqual(gotW, test.wantW) { t.Errorf("warnings from ValidateUpdate() = %v, want %v", gotW, test.wantW) @@ -326,7 +357,7 @@ func TestValidateUpdate(t *testing.T) { func TestMutateCreate(t *testing.T) { tests := map[string]struct { req *admissionv1.AdmissionRequest - existingCR, expectedCR *certmanager.CertificateRequest + existingCR, expectedCR *cmapi.CertificateRequest }{ "should set the identity of CertificateRequest to that of the requester": { req: &admissionv1.AdmissionRequest{ @@ -342,9 +373,9 @@ func TestMutateCreate(t *testing.T) { }, }, }, - existingCR: new(certmanager.CertificateRequest), - expectedCR: &certmanager.CertificateRequest{ - Spec: certmanager.CertificateRequestSpec{ + existingCR: new(cmapi.CertificateRequest), + expectedCR: &cmapi.CertificateRequest{ + Spec: cmapi.CertificateRequestSpec{ UID: "abc", Username: "user-1", Groups: []string{"group-1", "group-2"}, @@ -369,8 +400,8 @@ func TestMutateCreate(t *testing.T) { }, }, }, - existingCR: &certmanager.CertificateRequest{ - Spec: certmanager.CertificateRequestSpec{ + existingCR: &cmapi.CertificateRequest{ + Spec: cmapi.CertificateRequestSpec{ UID: "1234", Username: "user-2", Groups: []string{"group-3", "group-4"}, @@ -380,8 +411,8 @@ func TestMutateCreate(t *testing.T) { }, }, }, - expectedCR: &certmanager.CertificateRequest{ - Spec: certmanager.CertificateRequestSpec{ + expectedCR: &cmapi.CertificateRequest{ + Spec: cmapi.CertificateRequestSpec{ UID: "abc", Username: "user-1", Groups: []string{"group-1", "group-2"}, @@ -392,15 +423,46 @@ func TestMutateCreate(t *testing.T) { }, }, }, + "should handle nil Extra values": { + req: &admissionv1.AdmissionRequest{ + Operation: admissionv1.Create, + RequestResource: correctRequestResource, + UserInfo: authenticationv1.UserInfo{ + UID: "abc", + Username: "user-1", + Groups: []string{"group-1", "group-2"}, + }, + }, + existingCR: &cmapi.CertificateRequest{ + Spec: cmapi.CertificateRequestSpec{ + UID: "1234", + Username: "user-2", + Groups: []string{"group-3", "group-4"}, + Extra: map[string][]string{ + "3": {"abc", "efg"}, + "4": {"efg", "abc"}, + }, + }, + }, + expectedCR: &cmapi.CertificateRequest{ + Spec: cmapi.CertificateRequestSpec{ + UID: "abc", + Username: "user-1", + Groups: []string{"group-1", "group-2"}, + }, + }, + }, } for name, test := range tests { t.Run(name, func(t *testing.T) { cr := test.existingCR.DeepCopy() p := NewPlugin().(*certificateRequestIdentity) - if err := p.Mutate(context.Background(), *test.req, cr); err != nil { + crUnstr := toUnstructured(t, cr) + if err := p.Mutate(t.Context(), *test.req, crUnstr); err != nil { t.Errorf("unexpected error: %v", err) } + fromUnstructured(t, crUnstr, cr) if !reflect.DeepEqual(test.expectedCR, cr) { t.Errorf("MutateCreate() = %v, want %v", cr, test.expectedCR) } @@ -411,7 +473,7 @@ func TestMutateCreate(t *testing.T) { func TestMutateUpdate(t *testing.T) { tests := map[string]struct { req *admissionv1.AdmissionRequest - existingCR, expectedCR *certmanager.CertificateRequest + existingCR, expectedCR *cmapi.CertificateRequest }{ "should not overwrite user info fields during an Update operation": { req: &admissionv1.AdmissionRequest{ @@ -427,8 +489,8 @@ func TestMutateUpdate(t *testing.T) { }, }, }, - existingCR: &certmanager.CertificateRequest{ - Spec: certmanager.CertificateRequestSpec{ + existingCR: &cmapi.CertificateRequest{ + Spec: cmapi.CertificateRequestSpec{ UID: "1234", Username: "user-2", Groups: []string{"group-3", "group-4"}, @@ -438,8 +500,8 @@ func TestMutateUpdate(t *testing.T) { }, }, }, - expectedCR: &certmanager.CertificateRequest{ - Spec: certmanager.CertificateRequestSpec{ + expectedCR: &cmapi.CertificateRequest{ + Spec: cmapi.CertificateRequestSpec{ UID: "1234", Username: "user-2", Groups: []string{"group-3", "group-4"}, @@ -456,9 +518,11 @@ func TestMutateUpdate(t *testing.T) { t.Run(name, func(t *testing.T) { cr := test.existingCR.DeepCopy() p := NewPlugin().(*certificateRequestIdentity) - if err := p.Mutate(context.Background(), *test.req, cr); err != nil { + crUnstr := toUnstructured(t, cr) + if err := p.Mutate(t.Context(), *test.req, crUnstr); err != nil { t.Errorf("unexpected error: %v", err) } + fromUnstructured(t, crUnstr, cr) if !reflect.DeepEqual(test.expectedCR, cr) { t.Errorf("MutateCreate() = %v, want %v", cr, test.expectedCR) } diff --git a/internal/plugin/admission/resourcevalidation/resourcevalidation.go b/internal/webhook/admission/resourcevalidation/resourcevalidation.go similarity index 94% rename from internal/plugin/admission/resourcevalidation/resourcevalidation.go rename to internal/webhook/admission/resourcevalidation/resourcevalidation.go index 801e3cf38fb..2d44f843f0f 100644 --- a/internal/plugin/admission/resourcevalidation/resourcevalidation.go +++ b/internal/webhook/admission/resourcevalidation/resourcevalidation.go @@ -31,21 +31,12 @@ import ( admission "github.com/cert-manager/cert-manager/pkg/webhook/admission" ) -const PluginName = "ResourceValidation" - type resourceValidation struct { *admission.Handler validationMappings map[schema.GroupVersionResource]validationPair } -// Register registers a plugin -func Register(plugins *admission.Plugins) { - plugins.Register(PluginName, func() (admission.Interface, error) { - return NewPlugin(), nil - }) -} - var _ admission.ValidationInterface = &resourceValidation{} var certificateGVR = certmanagerv1.SchemeGroupVersion.WithResource("certificates") @@ -107,7 +98,7 @@ func (p resourceValidation) Validate(_ context.Context, request admissionv1.Admi } errs, warnings := pair.update(&request, oldObj, obj) return warnings, errs.ToAggregate() + default: + return nil, nil } - - return nil, nil } diff --git a/internal/plugin/admission/resourcevalidation/resourcevalidation_test.go b/internal/webhook/admission/resourcevalidation/resourcevalidation_test.go similarity index 96% rename from internal/plugin/admission/resourcevalidation/resourcevalidation_test.go rename to internal/webhook/admission/resourcevalidation/resourcevalidation_test.go index 0c8cbedfa0e..a9e0d796271 100644 --- a/internal/plugin/admission/resourcevalidation/resourcevalidation_test.go +++ b/internal/webhook/admission/resourcevalidation/resourcevalidation_test.go @@ -17,7 +17,6 @@ limitations under the License. package resourcevalidation import ( - "context" "reflect" "testing" @@ -83,7 +82,7 @@ func TestResourceValidation(t *testing.T) { t.Run(name, func(t *testing.T) { p := NewPlugin().(*resourceValidation) p.validationMappings = test.mapping - warnings, err := p.Validate(context.Background(), test.req, test.oldObj, test.obj) + warnings, err := p.Validate(t.Context(), test.req, test.oldObj, test.obj) compareErrors(t, test.expectedError, err) if !reflect.DeepEqual(test.expectedWarnings, warnings) { t.Errorf("unexpected warnings. exp=%v, got=%v", test.expectedWarnings, warnings) diff --git a/internal/webhook/feature/features.go b/internal/webhook/feature/features.go index a096cb2744f..5a7bad9c250 100644 --- a/internal/webhook/feature/features.go +++ b/internal/webhook/feature/features.go @@ -14,39 +14,78 @@ See the License for the specific language governing permissions and limitations under the License. */ +// feature contains webhook's feature gate setup functionality. Do not import +// this package into any code that's shared with other components to prevent +// overwriting other component's feature gates, see i.e +// https://github.com/cert-manager/cert-manager/issues/6011 package feature import ( + utilruntime "k8s.io/apimachinery/pkg/util/runtime" "k8s.io/component-base/featuregate" utilfeature "github.com/cert-manager/cert-manager/pkg/util/feature" ) +// see https://kubernetes.io/docs/reference/command-line-tools-reference/feature-gates/#feature-stages + const ( - // FeatureName will enable XYZ feature. - // Fill this section out with additional details about the feature. - // - // Owner (responsible for graduating feature through to GA): @username + // Copy & paste the following template when you add a new feature gate: + // ========================== START TEMPLATE ========================== + // Owner: @username // Alpha: vX.Y // Beta: ... - //FeatureName featuregate.Feature = "FeatureName" + // + // FeatureName will enable XYZ feature. + // Fill this section out with additional details about the feature. + // FeatureName featuregate.Feature = "FeatureName" + // =========================== END TEMPLATE =========================== // Owner: @joshvanl - // alpha: v1.7.1 + // Alpha: v1.7.1 + // Beta: v1.15 + // GA: v1.18 // // AdditionalCertificateOutputFormats enable output additional format AdditionalCertificateOutputFormats featuregate.Feature = "AdditionalCertificateOutputFormats" - // Owner (responsible for graduating feature through to GA): @spockz , @irbekrm + // Owner: @spockz, @irbekrm // Alpha: v1.9 + // // LiteralCertificateSubject will enable providing a subject in the Certificate that will be used literally in the CertificateSigningRequest. The subject can be provided via `LiteralSubject` field on `Certificate`'s spec. // This feature gate must be used together with LiteralCertificateSubject webhook feature gate. // See https://github.com/cert-manager/cert-manager/issues/3203 and https://github.com/cert-manager/cert-manager/issues/4424 for context. LiteralCertificateSubject featuregate.Feature = "LiteralCertificateSubject" + + // Owner: @inteon + // Beta: v1.13 + // GA: v1.15 + // + // DisallowInsecureCSRUsageDefinition will prevent the webhook from allowing + // CertificateRequest's usages to be only defined in the CSR, while leaving + // the usages field empty. + DisallowInsecureCSRUsageDefinition featuregate.Feature = "DisallowInsecureCSRUsageDefinition" + + // Owner: @tanujd11 + // Alpha: v1.14 + // Beta: v1.17 + // + // NameConstraints adds support for Name Constraints in Certificate resources + // with IsCA=true. + // Github Issue: https://github.com/cert-manager/cert-manager/issues/3655 + NameConstraints featuregate.Feature = "NameConstraints" + + // Owner: @SpectralHiss + // Alpha: v1.14 + // + // OtherNames adds support for OtherName Subject Alternative Name values in + // Certificate resources. + // Github Issue: https://github.com/cert-manager/cert-manager/issues/6393 + OtherNames featuregate.Feature = "OtherNames" ) func init() { - utilfeature.DefaultMutableFeatureGate.Add(webhookFeatureGates) + utilruntime.Must(utilfeature.DefaultMutableFeatureGate.Add(webhookFeatureGates)) } // webhookFeatureGates defines all feature gates for the webhook component. @@ -57,6 +96,10 @@ func init() { // // Where utilfeature is github.com/cert-manager/cert-manager/pkg/util/feature. var webhookFeatureGates = map[featuregate.Feature]featuregate.FeatureSpec{ - AdditionalCertificateOutputFormats: {Default: false, PreRelease: featuregate.Alpha}, - LiteralCertificateSubject: {Default: false, PreRelease: featuregate.Alpha}, + DisallowInsecureCSRUsageDefinition: {Default: true, PreRelease: featuregate.GA}, + + AdditionalCertificateOutputFormats: {Default: true, PreRelease: featuregate.GA}, + LiteralCertificateSubject: {Default: true, PreRelease: featuregate.Beta}, + NameConstraints: {Default: true, PreRelease: featuregate.Beta}, + OtherNames: {Default: false, PreRelease: featuregate.Alpha}, } diff --git a/internal/webhook/webhook.go b/internal/webhook/webhook.go index e3ec30acff1..3813e8fa7a4 100644 --- a/internal/webhook/webhook.go +++ b/internal/webhook/webhook.go @@ -21,41 +21,35 @@ import ( "time" "github.com/go-logr/logr" + "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/util/wait" "k8s.io/apiserver/pkg/authorization/authorizerfactory" "k8s.io/client-go/kubernetes" "k8s.io/client-go/rest" - "k8s.io/client-go/tools/clientcmd" + "k8s.io/utils/ptr" + crlog "sigs.k8s.io/controller-runtime/pkg/log" - "github.com/cert-manager/cert-manager/cmd/webhook/app/options" acmeinstall "github.com/cert-manager/cert-manager/internal/apis/acme/install" cminstall "github.com/cert-manager/cert-manager/internal/apis/certmanager/install" + "github.com/cert-manager/cert-manager/internal/apis/config/shared" config "github.com/cert-manager/cert-manager/internal/apis/config/webhook" metainstall "github.com/cert-manager/cert-manager/internal/apis/meta/install" - "github.com/cert-manager/cert-manager/internal/plugin" + "github.com/cert-manager/cert-manager/internal/kube" + crapproval "github.com/cert-manager/cert-manager/internal/webhook/admission/certificaterequest/approval" + cridentity "github.com/cert-manager/cert-manager/internal/webhook/admission/certificaterequest/identity" + "github.com/cert-manager/cert-manager/internal/webhook/admission/resourcevalidation" logf "github.com/cert-manager/cert-manager/pkg/logs" + "github.com/cert-manager/cert-manager/pkg/server/tls" + "github.com/cert-manager/cert-manager/pkg/server/tls/authority" "github.com/cert-manager/cert-manager/pkg/webhook/admission" - "github.com/cert-manager/cert-manager/pkg/webhook/admission/initializer" - "github.com/cert-manager/cert-manager/pkg/webhook/authority" - "github.com/cert-manager/cert-manager/pkg/webhook/handlers" "github.com/cert-manager/cert-manager/pkg/webhook/server" - "github.com/cert-manager/cert-manager/pkg/webhook/server/tls" ) -var conversionHook handlers.ConversionHook = handlers.NewSchemeBackedConverter(logf.Log, Scheme) - -// WithConversionHandler allows you to override the handler for the `/convert` -// endpoint in tests. -func WithConversionHandler(handler handlers.ConversionHook) func(*server.Server) { - return func(s *server.Server) { - s.ConversionWebhook = handler - } -} - // NewCertManagerWebhookServer creates a new webhook server configured with all cert-manager // resource types, validation, defaulting and conversion functions. -func NewCertManagerWebhookServer(log logr.Logger, _ options.WebhookFlags, opts config.WebhookConfiguration, optionFunctions ...func(*server.Server)) (*server.Server, error) { - restcfg, err := clientcmd.BuildConfigFromFlags(opts.APIServerHost, opts.KubeConfig) +func NewCertManagerWebhookServer(log logr.Logger, opts config.WebhookConfiguration, optionFunctions ...func(*server.Server)) (*server.Server, error) { + crlog.SetLogger(log) + restcfg, err := kube.BuildClientConfig(opts.APIServerHost, opts.KubeConfig) if err != nil { return nil, err } @@ -71,17 +65,26 @@ func NewCertManagerWebhookServer(log logr.Logger, _ options.WebhookFlags, opts c return nil, err } + scheme := runtime.NewScheme() + cminstall.Install(scheme) + acmeinstall.Install(scheme) + metainstall.Install(scheme) + s := &server.Server{ - ListenAddr: fmt.Sprintf(":%d", *opts.SecurePort), - HealthzAddr: fmt.Sprintf(":%d", *opts.HealthzPort), - EnablePprof: opts.EnablePprof, - PprofAddr: opts.PprofAddress, - CertificateSource: buildCertificateSource(log, opts.TLSConfig, restcfg), - CipherSuites: opts.TLSConfig.CipherSuites, - MinTLSVersion: opts.TLSConfig.MinTLSVersion, - ValidationWebhook: admissionHandler, - MutationWebhook: admissionHandler, - ConversionWebhook: conversionHook, + ResourceScheme: scheme, + ListenAddr: int(opts.SecurePort), + HealthzAddr: ptr.To(int(opts.HealthzPort)), + EnablePprof: opts.EnablePprof, + PprofAddress: opts.PprofAddress, + CertificateSource: buildCertificateSource(log, opts.TLSConfig, restcfg), + CipherSuites: opts.TLSConfig.CipherSuites, + MinTLSVersion: opts.TLSConfig.MinTLSVersion, + ValidationWebhook: admissionHandler, + MutationWebhook: admissionHandler, + MetricsListenAddress: opts.MetricsListenAddress, + MetricsCertificateSource: buildCertificateSource(log, opts.MetricsTLSConfig, restcfg), + MetricsCipherSuites: opts.MetricsTLSConfig.CipherSuites, + MetricsMinTLSVersion: opts.MetricsTLSConfig.MinTLSVersion, } for _, fn := range optionFunctions { fn(s) @@ -89,10 +92,7 @@ func NewCertManagerWebhookServer(log logr.Logger, _ options.WebhookFlags, opts c return s, nil } -func buildAdmissionChain(client kubernetes.Interface) (*admission.RequestHandler, error) { - // Set up the admission chain - pluginHandler := admission.NewPlugins(Scheme) - plugin.RegisterAllPlugins(pluginHandler) +func buildAdmissionChain(client kubernetes.Interface) (admission.PluginChain, error) { authorizer, err := authorizerfactory.DelegatingAuthorizerConfig{ SubjectAccessReviewClient: client.AuthorizationV1(), // cache responses for 1 second @@ -109,15 +109,17 @@ func buildAdmissionChain(client kubernetes.Interface) (*admission.RequestHandler if err != nil { return nil, fmt.Errorf("error creating authorization handler: %v", err) } - pluginInitializer := initializer.New(client, nil, authorizer, nil) - pluginChain, err := pluginHandler.NewFromPlugins(plugin.DefaultOnAdmissionPlugins().List(), pluginInitializer) - if err != nil { - return nil, fmt.Errorf("error building admission chain: %v", err) - } - return admission.NewRequestHandler(Scheme, pluginChain.(admission.ValidationInterface), pluginChain.(admission.MutationInterface)), nil + + pluginChain := admission.PluginChain([]admission.Interface{ + cridentity.NewPlugin(), + crapproval.NewPlugin(authorizer, client.Discovery()), + resourcevalidation.NewPlugin(), + }) + + return pluginChain, nil } -func buildCertificateSource(log logr.Logger, tlsConfig config.TLSConfig, restCfg *rest.Config) tls.CertificateSource { +func buildCertificateSource(log logr.Logger, tlsConfig shared.TLSConfig, restCfg *rest.Config) tls.CertificateSource { switch { case tlsConfig.FilesystemConfigProvided(): log.V(logf.InfoLevel).Info("using TLS certificate from local filesystem", "private_key_path", tlsConfig.Filesystem.KeyFile, "certificate", tlsConfig.Filesystem.CertFile) @@ -125,6 +127,7 @@ func buildCertificateSource(log logr.Logger, tlsConfig config.TLSConfig, restCfg CertPath: tlsConfig.Filesystem.CertFile, KeyPath: tlsConfig.Filesystem.KeyFile, } + case tlsConfig.DynamicConfigProvided(): log.V(logf.InfoLevel).Info("using dynamic certificate generating using CA stored in Secret resource", "secret_namespace", tlsConfig.Dynamic.SecretNamespace, "secret_name", tlsConfig.Dynamic.SecretName) return &tls.DynamicSource{ @@ -132,6 +135,8 @@ func buildCertificateSource(log logr.Logger, tlsConfig config.TLSConfig, restCfg Authority: &authority.DynamicAuthority{ SecretNamespace: tlsConfig.Dynamic.SecretNamespace, SecretName: tlsConfig.Dynamic.SecretName, + SecretLabels: map[string]string{"app.kubernetes.io/managed-by": "cert-manager-webhook"}, + LeafDuration: tlsConfig.Dynamic.LeafDuration, RESTConfig: restCfg, }, } @@ -140,9 +145,3 @@ func buildCertificateSource(log logr.Logger, tlsConfig config.TLSConfig, restCfg } return nil } - -func init() { - cminstall.Install(Scheme) - acmeinstall.Install(Scheme) - metainstall.Install(Scheme) -} diff --git a/klone.yaml b/klone.yaml new file mode 100644 index 00000000000..556af76af00 --- /dev/null +++ b/klone.yaml @@ -0,0 +1,55 @@ +# This klone.yaml file describes the Makefile modules and versions that are +# cloned into the "make/_shared" folder. These modules are dynamically imported +# by the root Makefile. The "make upgrade-klone" target can be used to pull +# the latest version from the upstream repositories (using the repo_ref value). +# +# More info can be found here: https://github.com/cert-manager/makefile-modules + +targets: + make/_shared: + - folder_name: boilerplate + repo_url: https://github.com/cert-manager/makefile-modules.git + repo_ref: main + repo_hash: 737c51c1bf36dea15a7ef2bc5c070d09845530a2 + repo_path: modules/boilerplate + - folder_name: generate-verify + repo_url: https://github.com/cert-manager/makefile-modules.git + repo_ref: main + repo_hash: 737c51c1bf36dea15a7ef2bc5c070d09845530a2 + repo_path: modules/generate-verify + - folder_name: go + repo_url: https://github.com/cert-manager/makefile-modules.git + repo_ref: main + repo_hash: 737c51c1bf36dea15a7ef2bc5c070d09845530a2 + repo_path: modules/go + - folder_name: help + repo_url: https://github.com/cert-manager/makefile-modules.git + repo_ref: main + repo_hash: 737c51c1bf36dea15a7ef2bc5c070d09845530a2 + repo_path: modules/help + - folder_name: klone + repo_url: https://github.com/cert-manager/makefile-modules.git + repo_ref: main + repo_hash: 737c51c1bf36dea15a7ef2bc5c070d09845530a2 + repo_path: modules/klone + - folder_name: licenses + repo_url: https://github.com/cert-manager/makefile-modules.git + repo_ref: main + repo_hash: 737c51c1bf36dea15a7ef2bc5c070d09845530a2 + repo_path: modules/licenses + - folder_name: repository-base + repo_url: https://github.com/cert-manager/makefile-modules.git + repo_ref: main + repo_hash: 737c51c1bf36dea15a7ef2bc5c070d09845530a2 + repo_path: modules/repository-base + - folder_name: tools + repo_url: https://github.com/cert-manager/makefile-modules.git + repo_ref: main + repo_hash: 737c51c1bf36dea15a7ef2bc5c070d09845530a2 + repo_path: modules/tools + make/_shared_new: + - folder_name: helm + repo_url: https://github.com/cert-manager/makefile-modules.git + repo_ref: main + repo_hash: 737c51c1bf36dea15a7ef2bc5c070d09845530a2 + repo_path: modules/helm diff --git a/make/00_mod.mk b/make/00_mod.mk new file mode 100644 index 00000000000..aafce0e4b67 --- /dev/null +++ b/make/00_mod.mk @@ -0,0 +1,73 @@ +# 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. + +repo_name := github.com/cert-manager/cert-manager + +include make/util.mk + +# SOURCES contains all go files except those in $(bin_dir), the old bindir `bin`, or in +# the make dir. +# NB: we skip `bin/` since users might have a `bin` directory left over in repos they were +# using before the bin dir was renamed +SOURCES := $(call get-sources,cat -) go.mod go.sum + +# SOURCE_DIRS contains all the directories that contain go files +SOURCE_DIRS := $(call get-sources,cut -d'/' -f2 | sort | uniq | tr '\n' ' ') + +## GOBUILDPROCS is passed to GOMAXPROCS when running go build; if you're running +## make in parallel using "-jN" then you'll probably want to reduce the value +## of GOBUILDPROCS or else you could end up running N parallel invocations of +## go build, each of which will spin up as many threads as are available on your +## system. +## @category Build +GOBUILDPROCS ?= + +include make/git.mk + +## By default, we don't link Go binaries to the libc. In some case, you might +## want to build libc-linked binaries, in which case you can set this to "1". +## @category Build +CGO_ENABLED ?= 0 + +## This flag is passed to `go build` to enable Go experiments. It's empty by default +## @category Build +GOEXPERIMENT ?= # empty by default + +## Extra flags passed to 'go' when building. For example, use GOFLAGS=-v to turn on the +## verbose output. +## @category Build +GOFLAGS := -trimpath + +## Extra linking flags passed to 'go' via '-ldflags' when building. +## @category Build +GOLDFLAGS := -w -s \ + -X github.com/cert-manager/cert-manager/pkg/util.AppVersion=$(VERSION) \ + -X github.com/cert-manager/cert-manager/pkg/util.AppGitCommit=$(GITCOMMIT) + +golangci_lint_config := .golangci.yaml + +GINKGO_VERSION ?= $(shell awk '/ginkgo\/v2/ {print $$2}' test/e2e/go.mod) + +build_names := controller acmesolver webhook cainjector startupapicheck + +go_controller_mod_dir := ./cmd/controller +go_acmesolver_mod_dir := ./cmd/acmesolver +go_webhook_mod_dir := ./cmd/webhook +go_cainjector_mod_dir := ./cmd/cainjector +go_startupapicheck_mod_dir := ./cmd/startupapicheck + +crds_expression := or .Values.crds.enabled .Values.installCRDs + +helm_chart_source_dir := deploy/charts/cert-manager +helm_labels_template_name := cert-manager.crd-labels diff --git a/make/02_mod.mk b/make/02_mod.mk new file mode 100644 index 00000000000..e69488bc435 --- /dev/null +++ b/make/02_mod.mk @@ -0,0 +1,54 @@ +# 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. + +GOBUILD := CGO_ENABLED=$(CGO_ENABLED) GOEXPERIMENT=$(GOEXPERIMENT) GOMAXPROCS=$(GOBUILDPROCS) $(GO) build +GOTEST := CGO_ENABLED=$(CGO_ENABLED) GOEXPERIMENT=$(GOEXPERIMENT) $(GO) test + +# overwrite $(GOTESTSUM) and add relevant environment variables +GOTESTSUM := CGO_ENABLED=$(CGO_ENABLED) GOEXPERIMENT=$(GOEXPERIMENT) $(GOTESTSUM) + +# Version of Gateway API install bundle https://gateway-api.sigs.k8s.io/v1alpha2/guides/#installing-gateway-api +GATEWAY_API_VERSION=v1.0.0 + +$(bin_dir)/scratch/gateway-api-$(GATEWAY_API_VERSION).yaml: | $(bin_dir)/scratch + $(CURL) https://github.com/kubernetes-sigs/gateway-api/releases/download/$(GATEWAY_API_VERSION)/experimental-install.yaml -o $@ + +include make/ci.mk +include make/test.mk +include make/base_images.mk +include make/server.mk +include make/containers.mk +include make/release.mk +include make/manifests.mk +include make/licenses.mk +include make/e2e-setup.mk +include make/scan.mk +include make/ko.mk +include make/third_party.mk +include make/_shared_new/helm/crds.mk + +generate-licenses: generate-go-licenses + +.PHONY: tidy +tidy: generate-go-mod-tidy + +.PHONY: update-base-images +update-base-images: | $(NEEDS_CRANE) + CRANE=$(CRANE) ./hack/latest-base-images.sh + +.PHONY: update-licenses +update-licenses: generate-licenses + +.PHONY: verify-licenses +verify-licenses: verify-generate-licenses diff --git a/make/README.md b/make/README.md index ae0545f4809..c0df6e2a7dc 100644 --- a/make/README.md +++ b/make/README.md @@ -84,14 +84,14 @@ Otherwise, your dependency should be normal. For example: ```make -$(BINDIR)/awesome-stuff/my-file: README.md | $(BINDIR)/awesome-stuff $(NEEDS_KIND) - # write the kind version to $(BINDIR)/awesome-stuff/my-file +$(bin_dir)/awesome-stuff/my-file: README.md | $(bin_dir)/awesome-stuff $(NEEDS_KIND) + # write the kind version to $(bin_dir)/awesome-stuff/my-file $(KIND) --version > $@ # append README.md cat README.md >> $@ ``` -This target will be rebuilt if `README.md` changes, but not if the installed version of kind changes or the `$(BINDIR)/awesome-stuff` folder changes. +This target will be rebuilt if `README.md` changes, but not if the installed version of kind changes or the `$(bin_dir)/awesome-stuff` folder changes. The dependencies you'll need will inevitably depend on the target you're writing. If in doubt, feel free to ask! diff --git a/hack/boilerplate/boilerplate.sh.txt b/make/_shared/boilerplate/00_mod.mk similarity index 75% rename from hack/boilerplate/boilerplate.sh.txt rename to make/_shared/boilerplate/00_mod.mk index 0a45273f9af..46f32fc5c42 100644 --- a/hack/boilerplate/boilerplate.sh.txt +++ b/make/_shared/boilerplate/00_mod.mk @@ -1,4 +1,4 @@ -# Copyright YEAR The cert-manager Authors. +# 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. @@ -12,3 +12,6 @@ # See the License for the specific language governing permissions and # limitations under the License. +default_go_header_file := $(dir $(lastword $(MAKEFILE_LIST)))/template/boilerplate.go.txt + +go_header_file ?= $(default_go_header_file) diff --git a/hack/update-deps-licenses.sh b/make/_shared/boilerplate/01_mod.mk old mode 100755 new mode 100644 similarity index 50% rename from hack/update-deps-licenses.sh rename to make/_shared/boilerplate/01_mod.mk index 07cdb8d0540..f7cf2d857cd --- a/hack/update-deps-licenses.sh +++ b/make/_shared/boilerplate/01_mod.mk @@ -1,6 +1,4 @@ -#!/usr/bin/env bash - -# Copyright 2022 The cert-manager Authors. +# 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. @@ -14,15 +12,20 @@ # See the License for the specific language governing permissions and # limitations under the License. -set -o errexit -set -o nounset -set -o pipefail +license_base_dir := $(dir $(lastword $(MAKEFILE_LIST)))/base/ -# This file is kept as backwards-compatibility for people with muscle memory who -# type "./hack/update-deps-licenses.sh" and expect it to work, or for third party CI pipelines. +.PHONY: verify-boilerplate +## Verify that all files have the correct boilerplate. +## @category [shared] Generate/ Verify +verify-boilerplate: | $(NEEDS_BOILERSUITE) + $(BOILERSUITE) . -# The replacement make target handles only licenses and doesn't touch anything relating to bazel +shared_verify_targets += verify-boilerplate -# This script may be removed in the future. Prefer using `make` directly. +.PHONY: generate-license +## Generate LICENSE file in the repository +## @category [shared] Generate/ Verify +generate-license: + cp -r $(license_base_dir)/. ./ -make update-licenses +shared_generate_targets += generate-license diff --git a/make/_shared/boilerplate/base/LICENSE b/make/_shared/boilerplate/base/LICENSE new file mode 100644 index 00000000000..261eeb9e9f8 --- /dev/null +++ b/make/_shared/boilerplate/base/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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. diff --git a/hack/boilerplate/boilerplate.go.txt b/make/_shared/boilerplate/template/boilerplate.go.txt similarity index 92% rename from hack/boilerplate/boilerplate.go.txt rename to make/_shared/boilerplate/template/boilerplate.go.txt index b2bca057ebe..f0214588363 100644 --- a/hack/boilerplate/boilerplate.go.txt +++ b/make/_shared/boilerplate/template/boilerplate.go.txt @@ -1,5 +1,5 @@ /* -Copyright YEAR The cert-manager Authors. +Copyright 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. @@ -12,5 +12,4 @@ 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. -*/ - +*/ \ No newline at end of file diff --git a/hack/boilerplate/boilerplate.bzl.txt b/make/_shared/generate-verify/00_mod.mk similarity index 77% rename from hack/boilerplate/boilerplate.bzl.txt rename to make/_shared/generate-verify/00_mod.mk index 0a45273f9af..435551388ad 100644 --- a/hack/boilerplate/boilerplate.bzl.txt +++ b/make/_shared/generate-verify/00_mod.mk @@ -1,4 +1,4 @@ -# Copyright YEAR The cert-manager Authors. +# 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. @@ -12,3 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. +shared_generate_targets ?= +shared_generate_targets_dirty ?= +shared_verify_targets ?= +shared_verify_targets_dirty ?= diff --git a/make/_shared/generate-verify/02_mod.mk b/make/_shared/generate-verify/02_mod.mk new file mode 100644 index 00000000000..f0677298aaf --- /dev/null +++ b/make/_shared/generate-verify/02_mod.mk @@ -0,0 +1,39 @@ +# 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. + +.PHONY: generate +## Generate all generate targets. +## @category [shared] Generate/ Verify +generate: $$(shared_generate_targets) + @echo "The following targets cannot be run simultaneously with each other or other generate scripts:" + $(foreach TARGET,$(shared_generate_targets_dirty), $(MAKE) $(TARGET)) + +verify_script := $(dir $(lastword $(MAKEFILE_LIST)))/util/verify.sh + +# Run the supplied make target argument in a temporary workspace and diff the results. +verify-%: FORCE + +$(verify_script) $(MAKE) $* + +verify_generated_targets = $(shared_generate_targets:%=verify-%) +verify_generated_targets_dirty = $(shared_generate_targets_dirty:%=verify-%) + +verify_targets = $(sort $(verify_generated_targets) $(shared_verify_targets)) +verify_targets_dirty = $(sort $(verify_generated_targets_dirty) $(shared_verify_targets_dirty)) + +.PHONY: verify +## Verify code and generate targets. +## @category [shared] Generate/ Verify +verify: $$(verify_targets) + @echo "The following targets create temporary files in the current directory, that is why they have to be run last:" + $(foreach TARGET,$(verify_targets_dirty), $(MAKE) $(TARGET)) diff --git a/make/_shared/generate-verify/util/verify.sh b/make/_shared/generate-verify/util/verify.sh new file mode 100755 index 00000000000..feb53bfeab8 --- /dev/null +++ b/make/_shared/generate-verify/util/verify.sh @@ -0,0 +1,73 @@ +#!/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. + +# Verify that the supplied command does not make any changes to the repository. +# +# This is called from the Makefile to verify that all code generation scripts +# have been run and that their changes have been committed to the repository. +# +# Runs any of the scripts or Make targets in this repository, after making a +# copy of the repository, then reports any changes to the files in the copy. + +# For example: +# +# make verify-helm-chart-update || \ +# make helm-chart-update +# +set -o errexit +set -o nounset +set -o pipefail + +projectdir="$( cd "$( dirname "${BASH_SOURCE[0]}" )/../../../.." && pwd )" + +cd "${projectdir}" + +# Use short form arguments here to support BSD/macOS. `-d` instructs +# it to make a directory, `-t` provides a prefix to use for the directory name. +tmp="$(mktemp -d /tmp/verify.sh.XXXXXXXX)" + +cleanup() { + rm -rf "${tmp}" +} +trap "cleanup" EXIT SIGINT + +# Why not just "cp" to the tmp dir? +# A dumb "cp" will fail sometimes since _bin can get changed while it's being copied if targets are run in parallel, +# and cp doesn't have some universal "exclude" option to ignore "_bin" +# +# We previously used "rsync" here, but: +# 1. That's another tool we need to depend on +# 2. rsync on macOS 15.4 and newer is actually openrsync, which has different permissions and throws errors when copying git objects +# +# So, we use find to list all files except _bin, and then copy each in turn +find . -maxdepth 1 -not \( -path "./_bin" \) -not \( -path "." \) | xargs -I% cp -af "${projectdir}/%" "${tmp}/" + +pushd "${tmp}" >/dev/null + +"$@" + +popd >/dev/null + +if ! diff \ + --exclude=".git" \ + --exclude="_bin" \ + --new-file --unified --show-c-function --recursive "${projectdir}" "${tmp}" +then + echo + echo "Project '${projectdir}' is out of date." + echo "Please run '${*}' or apply the above diffs" + exit 1 +fi diff --git a/make/_shared/go/.golangci.override.yaml b/make/_shared/go/.golangci.override.yaml new file mode 100644 index 00000000000..5b209d8761c --- /dev/null +++ b/make/_shared/go/.golangci.override.yaml @@ -0,0 +1,80 @@ +version: "2" +linters: + default: none + exclusions: + generated: lax + presets: [ comments, common-false-positives, legacy, std-error-handling ] + paths: [ third_party, builtin$, examples$ ] + warn-unused: true + settings: + staticcheck: + checks: [ "all", "-ST1000", "-ST1001", "-ST1003", "-ST1005", "-ST1012", "-ST1016", "-ST1020", "-ST1021", "-ST1022", "-QF1001", "-QF1003", "-QF1008" ] + enable: + - asasalint + - asciicheck + - bidichk + - bodyclose + - canonicalheader + - contextcheck + - copyloopvar + - decorder + - dogsled + - dupword + - durationcheck + - errcheck + - errchkjson + - errname + - exhaustive + - exptostd + - forbidigo + - ginkgolinter + - gocheckcompilerdirectives + - gochecksumtype + - gocritic + - goheader + - goprintffuncname + - gosec + - gosmopolitan + - govet + - grouper + - importas + - ineffassign + - interfacebloat + - intrange + - loggercheck + - makezero + - mirror + - misspell + - musttag + - nakedret + - nilerr + - nilnil + - noctx + - nosprintfhostport + - predeclared + - promlinter + - protogetter + - reassign + - sloglint + - staticcheck + - tagalign + - testableexamples + - unconvert + - unparam + - unused + - usestdlibvars + - usetesting + - wastedassign +formatters: + enable: [ gci, gofmt ] + settings: + gci: + sections: + - standard # Standard section: captures all standard packages. + - default # Default section: contains all imports that could not be matched to another section type. + - prefix({{REPO-NAME}}) # Custom section: groups all imports with the specified Prefix. + - blank # Blank section: contains all blank imports. This section is not present unless explicitly enabled. + - dot # Dot section: contains all dot imports. This section is not present unless explicitly enabled. + exclusions: + generated: lax + paths: [ third_party, builtin$, examples$ ] diff --git a/make/_shared/go/01_mod.mk b/make/_shared/go/01_mod.mk new file mode 100644 index 00000000000..2f053f69cf5 --- /dev/null +++ b/make/_shared/go/01_mod.mk @@ -0,0 +1,155 @@ +# 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. + +ifndef bin_dir +$(error bin_dir is not set) +endif + +ifndef repo_name +$(error repo_name is not set) +endif + +ifndef golangci_lint_config +$(error golangci_lint_config is not set) +endif + +golangci_lint_override := $(dir $(lastword $(MAKEFILE_LIST)))/.golangci.override.yaml + +.PHONY: go-workspace +go-workspace: export GOWORK?=$(abspath go.work) +## Create a go.work file in the repository root (or GOWORK) +## +## @category Development +go-workspace: | $(NEEDS_GO) + @rm -f $(GOWORK) + $(GO) work init + @find . -name go.mod -not \( -path "./$(bin_dir)/*" -or -path "./make/_shared/*" \) \ + | while read d; do \ + target=$$(dirname $${d}); \ + $(GO) work use "$${target}"; \ + done + +.PHONY: go-tidy +## Alias for `make generate-go-mod-tidy` +## @category [shared] Generate/ Verify +go-tidy: generate-go-mod-tidy + +.PHONY: generate-go-mod-tidy +## Run `go mod tidy` on all Go modules +## @category [shared] Generate/ Verify +generate-go-mod-tidy: | $(NEEDS_GO) + @find . -name go.mod -not \( -path "./$(bin_dir)/*" -or -path "./make/_shared/*" \) \ + | while read d; do \ + target=$$(dirname $${d}); \ + echo "Running 'go mod tidy' in directory '$${target}'"; \ + pushd "$${target}" >/dev/null; \ + $(GO) mod tidy || exit; \ + $(GO) get toolchain@none || exit; \ + popd >/dev/null; \ + echo ""; \ + done + +shared_generate_targets := generate-go-mod-tidy $(shared_generate_targets) + +ifndef dont_generate_govulncheck + +govulncheck_base_dir := $(dir $(lastword $(MAKEFILE_LIST)))/base/ + +.PHONY: generate-govulncheck +## Generate base files in the repository +## @category [shared] Generate/ Verify +generate-govulncheck: + cp -r $(govulncheck_base_dir)/. ./ + cd $(govulncheck_base_dir) && \ + find . -type f | while read file; do \ + sed "s|{{REPLACE:GH-REPOSITORY}}|$(repo_name:github.com/%=%)|g" "$$file" > "$(CURDIR)/$$file"; \ + done + +shared_generate_targets += generate-govulncheck + +endif # dont_generate_govulncheck + +.PHONY: verify-govulncheck +## Verify all Go modules for vulnerabilities using govulncheck +## @category [shared] Generate/ Verify +# +# Runs `govulncheck` on all Go modules related to the project. +# Ignores Go modules among the temporary build artifacts in _bin, to avoid +# scanning the code of the vendored Go, after running make vendor-go. +# Ignores Go modules in make/_shared, because those will be checked in centrally +# in the makefile_modules repository. +# +# `verify-govulncheck` not added to the `shared_verify_targets` variable and is +# not run by `make verify`, because `make verify` is run for each PR, and we do +# not want new vulnerabilities in existing code to block the merging of PRs. +# Instead `make verify-govulncheck` is intended to be run periodically by a CI job. +verify-govulncheck: | $(NEEDS_GOVULNCHECK) + @find . -name go.mod -not \( -path "./$(bin_dir)/*" -or -path "./make/_shared/*" \) \ + | while read d; do \ + target=$$(dirname $${d}); \ + echo "Running 'GOTOOLCHAIN=go$(VENDORED_GO_VERSION) $(bin_dir)/tools/govulncheck ./...' in directory '$${target}'"; \ + pushd "$${target}" >/dev/null; \ + GOTOOLCHAIN=go$(VENDORED_GO_VERSION) $(GOVULNCHECK) ./... || exit; \ + popd >/dev/null; \ + echo ""; \ + done + +.PHONY: generate-golangci-lint-config +## Generate a golangci-lint configuration file +## @category [shared] Generate/ Verify +generate-golangci-lint-config: | $(NEEDS_GOLANGCI-LINT) $(NEEDS_YQ) $(bin_dir)/scratch + if [ "$$($(YQ) eval 'has("version") | not' $(golangci_lint_config))" == "true" ]; then \ + $(GOLANGCI-LINT) migrate -c $(golangci_lint_config); \ + rm $(basename $(golangci_lint_config)).bck$(suffix $(golangci_lint_config)); \ + fi + + cp $(golangci_lint_config) $(bin_dir)/scratch/golangci-lint.yaml.tmp + $(YQ) -i 'del(.linters.enable)' $(bin_dir)/scratch/golangci-lint.yaml.tmp + $(YQ) eval-all -i '. as $$item ireduce ({}; . * $$item)' $(bin_dir)/scratch/golangci-lint.yaml.tmp $(golangci_lint_override) + $(YQ) -i '(.. | select(tag == "!!str")) |= sub("{{REPO-NAME}}", "$(repo_name)")' $(bin_dir)/scratch/golangci-lint.yaml.tmp + mv $(bin_dir)/scratch/golangci-lint.yaml.tmp $(golangci_lint_config) + +shared_generate_targets += generate-golangci-lint-config + +golangci_lint_timeout ?= 10m + +.PHONY: verify-golangci-lint +## Verify all Go modules using golangci-lint +## @category [shared] Generate/ Verify +verify-golangci-lint: | $(NEEDS_GO) $(NEEDS_GOLANGCI-LINT) $(NEEDS_YQ) $(bin_dir)/scratch + @find . -name go.mod -not \( -path "./$(bin_dir)/*" -or -path "./make/_shared/*" \) \ + | while read d; do \ + target=$$(dirname $${d}); \ + echo "Running 'GOVERSION=$(VENDORED_GO_VERSION) $(bin_dir)/tools/golangci-lint run -c $(CURDIR)/$(golangci_lint_config) --timeout $(golangci_lint_timeout)' in directory '$${target}'"; \ + pushd "$${target}" >/dev/null; \ + GOVERSION=$(VENDORED_GO_VERSION) $(GOLANGCI-LINT) run -c $(CURDIR)/$(golangci_lint_config) --timeout $(golangci_lint_timeout) || exit; \ + popd >/dev/null; \ + echo ""; \ + done + +shared_verify_targets_dirty += verify-golangci-lint + +.PHONY: fix-golangci-lint +## Fix all Go modules using golangci-lint +## @category [shared] Generate/ Verify +fix-golangci-lint: | $(NEEDS_GOLANGCI-LINT) $(NEEDS_YQ) $(NEEDS_GCI) $(bin_dir)/scratch + @find . -name go.mod -not \( -path "./$(bin_dir)/*" -or -path "./make/_shared/*" \) \ + | while read d; do \ + target=$$(dirname $${d}); \ + echo "Running 'GOVERSION=$(VENDORED_GO_VERSION) $(bin_dir)/tools/golangci-lint fmt -c $(CURDIR)/$(golangci_lint_config)' in directory '$${target}'"; \ + pushd "$${target}" >/dev/null; \ + GOVERSION=$(VENDORED_GO_VERSION) $(GOLANGCI-LINT) fmt -c $(CURDIR)/$(golangci_lint_config) || exit; \ + popd >/dev/null; \ + echo ""; \ + done diff --git a/make/_shared/go/README.md b/make/_shared/go/README.md new file mode 100644 index 00000000000..ad1962ba1dc --- /dev/null +++ b/make/_shared/go/README.md @@ -0,0 +1,3 @@ +# README + +A module for various Go static checks. diff --git a/make/_shared/go/base/.github/workflows/govulncheck.yaml b/make/_shared/go/base/.github/workflows/govulncheck.yaml new file mode 100644 index 00000000000..938da2e371b --- /dev/null +++ b/make/_shared/go/base/.github/workflows/govulncheck.yaml @@ -0,0 +1,37 @@ +# THIS FILE IS AUTOMATICALLY GENERATED. DO NOT EDIT. +# Edit https://github.com/cert-manager/makefile-modules/blob/main/modules/go/base/.github/workflows/govulncheck.yaml instead. + +# Run govulncheck at midnight every night on the main branch, +# to alert us to recent vulnerabilities which affect the Go code in this +# project. +name: govulncheck +on: + workflow_dispatch: {} + schedule: + - cron: '0 0 * * *' + +permissions: + contents: read + +jobs: + govulncheck: + runs-on: ubuntu-latest + + if: github.repository == '{{REPLACE:GH-REPOSITORY}}' + + steps: + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + # Adding `fetch-depth: 0` makes sure tags are also fetched. We need + # the tags so `git describe` returns a valid version. + # see https://github.com/actions/checkout/issues/701 for extra info about this option + with: { fetch-depth: 0 } + + - id: go-version + run: | + make print-go-version >> "$GITHUB_OUTPUT" + + - uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6.0.0 + with: + go-version: ${{ steps.go-version.outputs.result }} + + - run: make verify-govulncheck diff --git a/make/_shared/help/01_mod.mk b/make/_shared/help/01_mod.mk new file mode 100644 index 00000000000..1a6a3b48b24 --- /dev/null +++ b/make/_shared/help/01_mod.mk @@ -0,0 +1,22 @@ +# 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. + + +help_sh := $(dir $(lastword $(MAKEFILE_LIST)))/help.sh + +.PHONY: help +help: + @MAKEFILE_LIST="$(MAKEFILE_LIST)" \ + MAKE="$(MAKE)" \ + $(help_sh) diff --git a/make/_shared/help/help.sh b/make/_shared/help/help.sh new file mode 100755 index 00000000000..400aab3d5fe --- /dev/null +++ b/make/_shared/help/help.sh @@ -0,0 +1,115 @@ +#!/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 + +## 1. Build set of extracted line items + +EMPTYLINE_REGEX="^[[:space:]]*$" +DOCBLOCK_REGEX="^##[[:space:]]*(.*)$" +CATEGORY_REGEX="^##[[:space:]]*@category[[:space:]]*(.*)$" +TARGET_REGEX="^(([a-zA-Z0-9\_\/\%\$\(\)]|-)+):.*$" + +EMPTY_ITEM="" + +# shellcheck disable=SC2086 +raw_lines=$(cat ${MAKEFILE_LIST} | tr '\t' ' ' | grep -E "($TARGET_REGEX|$DOCBLOCK_REGEX|$EMPTYLINE_REGEX)") +extracted_lines="" +extracted_current="$EMPTY_ITEM" +max_target_length=0 + +## Extract all the commented targets from the Makefile +while read -r line; do + if [[ $line =~ $EMPTYLINE_REGEX ]]; then + # Reset current item. + extracted_current="$EMPTY_ITEM" + elif [[ $line =~ $CATEGORY_REGEX ]]; then + extracted_current=${extracted_current///${BASH_REMATCH[1]}} + elif [[ $line =~ $TARGET_REGEX ]]; then + # only keep the target if there is a comment + if [[ $extracted_current != *""* ]]; then + max_target_length=$(( ${#BASH_REMATCH[1]} > max_target_length ? ${#BASH_REMATCH[1]} : max_target_length )) + extracted_current=${extracted_current///${BASH_REMATCH[1]}} + extracted_lines="$extracted_lines\n$extracted_current" + fi + + extracted_current="$EMPTY_ITEM" + elif [[ $line =~ $DOCBLOCK_REGEX ]]; then + extracted_current=${extracted_current///${BASH_REMATCH[1]}} + fi +done <<< "$raw_lines" + +## 2. Build mapping for expanding targets + +ASSIGNMENT_REGEX="^(([a-zA-Z0-9\_\/\%\$\(\)]|-)+)[[:space:]]*:=[[:space:]]*(.*)$" + +raw_expansions=$(${MAKE} --dry-run --print-data-base noop | tr '\t' ' ' | grep -E "$ASSIGNMENT_REGEX") +extracted_expansions="" + +while read -r line; do + if [[ $line =~ $ASSIGNMENT_REGEX ]]; then + target=${BASH_REMATCH[1]} + expansion=${BASH_REMATCH[3]// /, } + extracted_expansions="$extracted_expansions\n$target$expansion" + fi +done <<< "$raw_expansions" + +## 3. Sort and print the extracted line items + +RULE_COLOR="$(TERM=xterm tput setaf 6)" +CATEGORY_COLOR="$(TERM=xterm tput setaf 3)" +CLEAR_STYLE="$(TERM=xterm tput sgr0)" +PURPLE=$(TERM=xterm tput setaf 125) + +extracted_lines=$(echo -e "$extracted_lines" | LC_ALL=C sort -r) +current_category="" + +## Print the help +echo "Usage: make [target1] [target2] ..." + +IFS=$'\n'; for line in $extracted_lines; do + category=$([[ $line =~ \(.*)\ ]] && echo "${BASH_REMATCH[1]}") + target=$([[ $line =~ \(.*)\ ]] && echo "${BASH_REMATCH[1]}") + comment=$([[ $line =~ \(.*)\ ]] && echo -e "${BASH_REMATCH[1]///\\n}") + + # Print the category header if it's changed + if [[ "$current_category" != "$category" ]]; then + current_category=$category + echo -e "\n${CATEGORY_COLOR}${current_category}${CLEAR_STYLE}" + fi + + # replace any $(...) with the actual value + if [[ $target =~ \$\((.*)\) ]]; then + new_target=$(echo -e "$extracted_expansions" | grep "${BASH_REMATCH[1]}" || true) + if [[ -n "$new_target" ]]; then + target=$([[ $new_target =~ \(.*)\ ]] && echo -e "${BASH_REMATCH[1]}") + fi + fi + + # Print the target and its multiline comment + is_first_line=true + while read -r comment_line; do + if [[ "$is_first_line" == true ]]; then + is_first_line=false + padding=$(( max_target_length - ${#target} )) + printf " %s%${padding}s ${PURPLE}>${CLEAR_STYLE} %s\n" "${RULE_COLOR}${target}${CLEAR_STYLE}" "" "${comment_line}" + else + printf " %${max_target_length}s %s\n" "" "${comment_line}" + fi + done <<< "$comment" +done diff --git a/hack/verify-codegen.sh b/make/_shared/klone/01_mod.mk old mode 100755 new mode 100644 similarity index 58% rename from hack/verify-codegen.sh rename to make/_shared/klone/01_mod.mk index cc505cf970d..a3d07dd2778 --- a/hack/verify-codegen.sh +++ b/make/_shared/klone/01_mod.mk @@ -1,6 +1,4 @@ -#!/usr/bin/env bash - -# Copyright 2022 The cert-manager Authors. +# 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. @@ -14,13 +12,16 @@ # See the License for the specific language governing permissions and # limitations under the License. -set -o nounset -set -o errexit -set -o pipefail - -# This file is kept as backwards-compatibility for people with muscle memory who -# type "./hack/verify-codegen.sh" and expect it to work, or for third party CI pipelines. +.PHONY: generate-klone +## Generate klone shared Makefiles +## @category [shared] Generate/ Verify +generate-klone: | $(NEEDS_KLONE) + $(KLONE) sync -# This script may be removed in the future. Prefer using `make` directly. +shared_generate_targets += generate-klone -make verify-codegen +.PHONY: upgrade-klone +## Upgrade klone Makefile modules to latest version +## @category [shared] Self-upgrade +upgrade-klone: | $(NEEDS_KLONE) + $(KLONE) upgrade diff --git a/hack/boilerplate/boilerplate.py.txt b/make/_shared/licenses/00_mod.mk similarity index 83% rename from hack/boilerplate/boilerplate.py.txt rename to make/_shared/licenses/00_mod.mk index 0a45273f9af..fb72a199541 100644 --- a/hack/boilerplate/boilerplate.py.txt +++ b/make/_shared/licenses/00_mod.mk @@ -1,4 +1,4 @@ -# Copyright YEAR The cert-manager Authors. +# Copyright 2024 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. @@ -12,3 +12,5 @@ # See the License for the specific language governing permissions and # limitations under the License. +# Define default config for generating licenses +license_ignore ?= diff --git a/make/_shared/licenses/01_mod.mk b/make/_shared/licenses/01_mod.mk new file mode 100644 index 00000000000..e9e748c9e6a --- /dev/null +++ b/make/_shared/licenses/01_mod.mk @@ -0,0 +1,75 @@ +# Copyright 2024 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. + +###################### Generate LICENSES files ###################### + +# _module_dir is the directory containing this Makefile, used to retrieve the path of the licenses.tmpl file +_module_dir := $(abspath $(dir $(lastword $(MAKEFILE_LIST)))) + +# Create a go.work file so that go-licenses can discover the LICENSE file of the +# other modules in the repo. +# +# Without this, go-licenses *guesses* the wrong LICENSE for local dependencies and +# links to the wrong versions of LICENSES for transitive dependencies. +licenses_go_work := $(bin_dir)/scratch/LICENSES.go.work +$(licenses_go_work): $(bin_dir)/scratch + GOWORK=$(abspath $@) \ + $(MAKE) go-workspace + +## Generate licenses for the golang dependencies +## @category [shared] Generate/ Verify +generate-go-licenses: # +shared_generate_targets += generate-go-licenses + +define licenses_target +$1/LICENSES: $1/go.mod $(licenses_go_work) $(_module_dir)/licenses.tmpl | $(NEEDS_GO-LICENSES) + cd $$(dir $$@) && \ + GOWORK=$(abspath $(licenses_go_work)) \ + GOOS=linux GOARCH=amd64 \ + $(GO-LICENSES) report --ignore "$$(license_ignore)" --template $(_module_dir)/licenses.tmpl ./... > LICENSES + +generate-go-licenses: $1/LICENSES +# The /LICENSE targets make sure these files exist. +# Otherwise, make will error. +generate-go-licenses: $1/LICENSE +endef + +# Calculate all the go.mod directories, build targets may share go.mod dirs so +# we use $(sort) to de-duplicate. +go_mod_dirs := $(foreach build_name,$(build_names),$(go_$(build_name)_mod_dir)) +ifneq ("$(wildcard go.mod)","") + go_mod_dirs += . +endif +go_mod_dirs := $(sort $(go_mod_dirs)) +$(foreach go_mod_dir,$(go_mod_dirs),$(eval $(call licenses_target,$(go_mod_dir)))) + +###################### Include LICENSES in OCI image ###################### + +define license_layer +license_layer_path_$1 := $$(abspath $(bin_dir)/scratch/licenses-$1) + +# Target to generate image layer containing license information +.PHONY: oci-license-layer-$1 +oci-license-layer-$1: | $(bin_dir)/scratch $(NEEDS_GO-LICENSES) + rm -rf $$(license_layer_path_$1) + mkdir -p $$(license_layer_path_$1)/licenses + cp $$(go_$1_mod_dir)/LICENSE $$(license_layer_path_$1)/licenses/LICENSE + cp $$(go_$1_mod_dir)/LICENSES $$(license_layer_path_$1)/licenses/LICENSES + +oci-build-$1: oci-license-layer-$1 +oci-build-$1__local: oci-license-layer-$1 +oci_$1_additional_layers += $$(license_layer_path_$1) +endef + +$(foreach build_name,$(build_names),$(eval $(call license_layer,$(build_name)))) diff --git a/make/_shared/licenses/licenses.tmpl b/make/_shared/licenses/licenses.tmpl new file mode 100644 index 00000000000..16d90b0c32f --- /dev/null +++ b/make/_shared/licenses/licenses.tmpl @@ -0,0 +1,41 @@ +This LICENSES file is generated by the `licenses` module in makefile-modules[0]. + +The licenses below the "---" are determined by the go-licenses tool[1]. + +The aim of this file is to collect the licenses of all dependencies, and provide +a single source of truth for licenses used by this project. + +## For Developers + +If CI reports that this file is out of date, you should be careful to check that the +new licenses are acceptable for this project before running `make generate-go-licenses` +to update this file. + +Acceptable licenses are those allowlisted by the CNCF[2]. + +You MUST NOT add any new dependencies whose licenses are not allowlisted by the CNCF, +or which do not have an explicit license exception[3]. + +## For Users + +If this file was included in a release artifact, it is a snapshot of the licenses of all dependencies at the time of the release. + +You can retrieve the actual license text by following these steps: + +1. Find the dependency name in this file +2. Go to the source code repository of this project, and go to the tag corresponding to this release. +3. Find the exact version of the dependency in the `go.mod` file +4. Search for the dependency at the correct version in the [Go package index](https://pkg.go.dev/). + +## Links + +[0]: https://github.com/cert-manager/makefile-modules/ +[1]: https://github.com/google/go-licenses +[2]: https://github.com/cncf/foundation/blob/db4179134ebe7fa00b140a050c19147db808b6fa/policies-guidance/allowed-third-party-license-policy.md#cncf-allowlist-license-policy +[3]: https://github.com/cncf/foundation/blob/db4179134ebe7fa00b140a050c19147db808b6fa/license-exceptions/README.md + +--- + +{{ range . -}} +{{ .Name }},{{ .LicenseName }} +{{ end -}} diff --git a/make/_shared/repository-base/01_mod.mk b/make/_shared/repository-base/01_mod.mk new file mode 100644 index 00000000000..5b7831e36d7 --- /dev/null +++ b/make/_shared/repository-base/01_mod.mk @@ -0,0 +1,36 @@ +# 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. + +ifndef repo_name +$(error repo_name is not set) +endif + +_repository_base_module_dir := $(dir $(lastword $(MAKEFILE_LIST))) +repository_base_dir := $(_repository_base_module_dir)base/ + +.PHONY: generate-base +## Generate base files in the repository +## @category [shared] Generate/ Verify +generate-base: + cp -r $(repository_base_dir)/. ./ + cd $(repository_base_dir) && \ + find . -type f | while read file; do \ + sed "s|{{REPLACE:GH-REPOSITORY}}|$(repo_name:github.com/%=%)|g" "$$file" > "$(CURDIR)/$$file"; \ + done + if [ ! -e ./.github/renovate.json5 ]; then \ + mkdir -p ./.github; \ + cp $(_repository_base_module_dir)/renovate-bootstrap-config.json5 ./.github/renovate.json5; \ + fi + +shared_generate_targets += generate-base diff --git a/make/_shared/repository-base/base/.github/chainguard/make-self-upgrade.sts.yaml b/make/_shared/repository-base/base/.github/chainguard/make-self-upgrade.sts.yaml new file mode 100644 index 00000000000..310ca5ca55d --- /dev/null +++ b/make/_shared/repository-base/base/.github/chainguard/make-self-upgrade.sts.yaml @@ -0,0 +1,10 @@ +# THIS FILE IS AUTOMATICALLY GENERATED. DO NOT EDIT. +# Edit https://github.com/cert-manager/makefile-modules/blob/main/modules/repository-base/base/.github/chainguard/make-self-upgrade.sts.yaml instead. + +issuer: https://token.actions.githubusercontent.com +subject_pattern: ^repo:{{REPLACE:GH-REPOSITORY}}:ref:refs/heads/(main|master)$ + +permissions: + contents: write + pull_requests: write + workflows: write diff --git a/make/_shared/repository-base/base/.github/chainguard/renovate.sts.yaml b/make/_shared/repository-base/base/.github/chainguard/renovate.sts.yaml new file mode 100644 index 00000000000..cb082a2cdaa --- /dev/null +++ b/make/_shared/repository-base/base/.github/chainguard/renovate.sts.yaml @@ -0,0 +1,14 @@ +# THIS FILE IS AUTOMATICALLY GENERATED. DO NOT EDIT. +# Edit https://github.com/cert-manager/makefile-modules/blob/main/modules/repository-base/base/.github/chainguard/renovate.sts.yaml instead. + +issuer: https://token.actions.githubusercontent.com +subject_pattern: ^repo:{{REPLACE:GH-REPOSITORY}}:ref:refs/heads/(main|master)$ + +permissions: + administration: read + contents: write + issues: write + pull_requests: write + security_events: read + statuses: write + workflows: write diff --git a/make/_shared/repository-base/base/.github/workflows/make-self-upgrade.yaml b/make/_shared/repository-base/base/.github/workflows/make-self-upgrade.yaml new file mode 100644 index 00000000000..1850dbc7a64 --- /dev/null +++ b/make/_shared/repository-base/base/.github/workflows/make-self-upgrade.yaml @@ -0,0 +1,114 @@ +# THIS FILE IS AUTOMATICALLY GENERATED. DO NOT EDIT. +# Edit https://github.com/cert-manager/makefile-modules/blob/main/modules/repository-base/base/.github/workflows/make-self-upgrade.yaml instead. + +name: make-self-upgrade +concurrency: make-self-upgrade +on: + workflow_dispatch: {} + schedule: + - cron: '0 0 * * *' + +permissions: + contents: read + +jobs: + self_upgrade: + runs-on: ubuntu-latest + + if: github.repository == '{{REPLACE:GH-REPOSITORY}}' + + permissions: + id-token: write + + env: + SOURCE_BRANCH: "${{ github.ref_name }}" + SELF_UPGRADE_BRANCH: "self-upgrade-${{ github.ref_name }}" + + steps: + - name: Fail if branch is not head of branch. + if: ${{ !startsWith(github.ref, 'refs/heads/') && env.SOURCE_BRANCH != '' && env.SELF_UPGRADE_BRANCH != '' }} + run: | + echo "This workflow should not be run on a non-branch-head." + exit 1 + + - name: Octo STS Token Exchange + uses: octo-sts/action@d6c70ad3b9ac85df6da6b9749014d7283987cfec # v1.0.3 + id: octo-sts + with: + scope: '{{REPLACE:GH-REPOSITORY}}' + identity: make-self-upgrade + + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + # Adding `fetch-depth: 0` makes sure tags are also fetched. We need + # the tags so `git describe` returns a valid version. + # see https://github.com/actions/checkout/issues/701 for extra info about this option + with: + fetch-depth: 0 + token: ${{ steps.octo-sts.outputs.token }} + + - id: go-version + run: | + make print-go-version >> "$GITHUB_OUTPUT" + + - uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6.0.0 + with: + go-version: ${{ steps.go-version.outputs.result }} + + - run: | + git checkout -B "$SELF_UPGRADE_BRANCH" + + - run: | + make -j upgrade-klone + make -j generate + + - id: is-up-to-date + shell: bash + run: | + git_status=$(git status -s) + is_up_to_date="true" + if [ -n "$git_status" ]; then + is_up_to_date="false" + echo "The following changes will be committed:" + echo "$git_status" + fi + echo "result=$is_up_to_date" >> "$GITHUB_OUTPUT" + + - if: ${{ steps.is-up-to-date.outputs.result != 'true' }} + run: | + git config --global user.name "cert-manager-bot" + git config --global user.email "cert-manager-bot@users.noreply.github.com" + git add -A && git commit -m "BOT: run 'make upgrade-klone' and 'make generate'" --signoff + git push -f origin "$SELF_UPGRADE_BRANCH" + + - if: ${{ steps.is-up-to-date.outputs.result != 'true' }} + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 + with: + github-token: ${{ steps.octo-sts.outputs.token }} + script: | + const { repo, owner } = context.repo; + const pulls = await github.rest.pulls.list({ + owner: owner, + repo: repo, + head: owner + ':' + process.env.SELF_UPGRADE_BRANCH, + base: process.env.SOURCE_BRANCH, + state: 'open', + }); + + if (pulls.data.length < 1) { + const result = await github.rest.pulls.create({ + title: '[CI] Merge ' + process.env.SELF_UPGRADE_BRANCH + ' into ' + process.env.SOURCE_BRANCH, + owner: owner, + repo: repo, + head: process.env.SELF_UPGRADE_BRANCH, + base: process.env.SOURCE_BRANCH, + body: [ + 'This PR is auto-generated to bump the Makefile modules.', + ].join('\n'), + }); + await github.rest.issues.addLabels({ + owner, + repo, + issue_number: result.data.number, + labels: ['ok-to-test', 'skip-review', 'release-note-none', 'kind/cleanup'] + }); + } diff --git a/make/_shared/repository-base/base/.github/workflows/renovate.yaml b/make/_shared/repository-base/base/.github/workflows/renovate.yaml new file mode 100644 index 00000000000..b0ccae1cb8e --- /dev/null +++ b/make/_shared/repository-base/base/.github/workflows/renovate.yaml @@ -0,0 +1,62 @@ +# THIS FILE IS AUTOMATICALLY GENERATED. DO NOT EDIT. +# Edit https://github.com/cert-manager/makefile-modules/blob/main/modules/repository-base/base/.github/workflows/renovate.yaml instead. + +name: Renovate +on: + workflow_dispatch: {} + schedule: + - cron: '0 2 * * *' + +permissions: + contents: read + +jobs: + renovate: + runs-on: ubuntu-latest + + if: github.repository == '{{REPLACE:GH-REPOSITORY}}' + + permissions: + id-token: write + + steps: + - name: Fail if branch is not head of branch. + if: ${{ !startsWith(github.ref, 'refs/heads/') && env.SOURCE_BRANCH != '' && env.SELF_UPGRADE_BRANCH != '' }} + run: | + echo "This workflow should not be run on a non-branch-head." + exit 1 + + - name: Octo STS Token Exchange + uses: octo-sts/action@d6c70ad3b9ac85df6da6b9749014d7283987cfec # v1.0.3 + id: octo-sts + with: + scope: '{{REPLACE:GH-REPOSITORY}}' + identity: renovate + + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + # Adding `fetch-depth: 0` makes sure tags are also fetched. We need + # the tags so `git describe` returns a valid version. + # see https://github.com/actions/checkout/issues/701 for extra info about this option + with: + fetch-depth: 0 + token: ${{ steps.octo-sts.outputs.token }} + + - id: go-version + run: | + make print-go-version >> "$GITHUB_OUTPUT" + + - uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6.0.0 + with: + go-version: ${{ steps.go-version.outputs.result }} + + - name: Self-hosted Renovate + uses: renovatebot/github-action@aec779d4f7845f8431ddf403cf9659d4702ddde0 # v43.0.18 + with: + configurationFile: .github/renovate.json5 + token: ${{ steps.octo-sts.outputs.token }} + env: + RENOVATE_REPOSITORIES: '["${{ github.repository }}"]' + RENOVATE_ONBOARDING: "false" + RENOVATE_PLATFORM: "github" + LOG_LEVEL: "debug" + RENOVATE_ALLOWED_COMMANDS: '[".*"]' diff --git a/make/_shared/repository-base/base/Makefile b/make/_shared/repository-base/base/Makefile new file mode 100644 index 00000000000..9a7b7033b7b --- /dev/null +++ b/make/_shared/repository-base/base/Makefile @@ -0,0 +1,116 @@ +# 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. + +# THIS FILE IS AUTOMATICALLY GENERATED. DO NOT EDIT. +# Edit https://github.com/cert-manager/makefile-modules/blob/main/modules/repository-base/base/Makefile instead. + +# NOTE FOR DEVELOPERS: "How do the Makefiles work and how can I extend them?" +# +# Shared Makefile logic lives in the make/_shared/ directory. The source of truth for these files +# lies outside of this repository, eg. in the cert-manager/makefile-modules repository. +# +# Logic specific to this repository must be defined in the make/00_mod.mk and make/02_mod.mk files: +# - The make/00_mod.mk file is included first and contains variable definitions needed by +# the shared Makefile logic. +# - The make/02_mod.mk file is included later, it can make use of most of the shared targets +# defined in the make/_shared/ directory (all targets defined in 00_mod.mk and 01_mod.mk). +# This file should be used to define targets specific to this repository. + +################################## + +# Some modules build their dependencies from variables, we want these to be +# evaluated at the last possible moment. For this we use second expansion to +# re-evaluate the generate and verify targets a second time. +# +# See https://www.gnu.org/software/make/manual/html_node/Secondary-Expansion.html +.SECONDEXPANSION: + +# For details on some of these "prelude" settings, see: +# https://clarkgrubb.com/makefile-style-guide +MAKEFLAGS += --warn-undefined-variables --no-builtin-rules +SHELL := /usr/bin/env PS1="" bash +.SHELLFLAGS := -uo pipefail -c +.DEFAULT_GOAL := help +.DELETE_ON_ERROR: +.SUFFIXES: +FORCE: + +noop: # do nothing + +# Set empty value for MAKECMDGOALS to prevent the "warning: undefined variable 'MAKECMDGOALS'" +# warning from happening when running make without arguments +MAKECMDGOALS ?= + +################################## +# Host OS and architecture setup # +################################## + +# The reason we don't use "go env GOOS" or "go env GOARCH" is that the "go" +# binary may not be available in the PATH yet when the Makefiles are +# evaluated. HOST_OS and HOST_ARCH only support Linux, *BSD and macOS (M1 +# and Intel). +host_os := $(shell uname -s | tr A-Z a-z) +host_arch := $(shell uname -m) +HOST_OS ?= $(host_os) +HOST_ARCH ?= $(host_arch) + +ifeq (x86_64, $(HOST_ARCH)) + HOST_ARCH = amd64 +else ifeq (aarch64, $(HOST_ARCH)) + # linux reports the arm64 arch as aarch64 + HOST_ARCH = arm64 +endif + +################################## +# Git and versioning information # +################################## + +git_version := $(shell git describe --tags --always --match='v*' --abbrev=14 --dirty) +VERSION ?= $(git_version) +IS_PRERELEASE := $(shell git describe --tags --always --match='v*' --abbrev=0 | grep -q '-' && echo true || echo false) +GITCOMMIT := $(shell git rev-parse HEAD) +GITEPOCH := $(shell git show -s --format=%ct HEAD) + +################################## +# Global variables and dirs # +################################## + +bin_dir := _bin + +# The ARTIFACTS environment variable is set by the CI system to a directory +# where artifacts should be placed. These artifacts are then uploaded to a +# storage bucket by the CI system (https://docs.prow.k8s.io/docs/components/pod-utilities/). +# An example of such an artifact is a jUnit XML file containing test results. +# If the ARTIFACTS environment variable is not set, we default to a local +# directory in the _bin directory. +ARTIFACTS ?= $(bin_dir)/artifacts + +$(bin_dir) $(ARTIFACTS) $(bin_dir)/scratch: + mkdir -p $@ + +.PHONY: clean +## Clean all temporary files +## @category [shared] Tools +clean: + rm -rf $(bin_dir) + +################################## +# Include all the Makefiles # +################################## + +-include make/00_mod.mk +-include make/_shared/*/00_mod.mk +-include make/_shared/*/01_mod.mk +-include make/02_mod.mk +-include make/_shared/*/02_mod.mk diff --git a/make/_shared/repository-base/base/OWNERS_ALIASES b/make/_shared/repository-base/base/OWNERS_ALIASES new file mode 100644 index 00000000000..672704c9709 --- /dev/null +++ b/make/_shared/repository-base/base/OWNERS_ALIASES @@ -0,0 +1,14 @@ +# THIS FILE IS AUTOMATICALLY GENERATED. DO NOT EDIT. +# Edit https://github.com/cert-manager/makefile-modules/blob/main/modules/repository-base/base/OWNERS_ALIASES instead. + +aliases: + cm-maintainers: + - munnerz + - joshvanl + - wallrj + - jakexks + - maelvls + - sgtcodfish + - inteon + - thatsmrtalbot + - erikgb diff --git a/make/_shared/repository-base/renovate-bootstrap-config.json5 b/make/_shared/repository-base/renovate-bootstrap-config.json5 new file mode 100644 index 00000000000..ce9d622fb8e --- /dev/null +++ b/make/_shared/repository-base/renovate-bootstrap-config.json5 @@ -0,0 +1,6 @@ +{ + $schema: 'https://docs.renovatebot.com/renovate-schema.json', + extends: [ + 'github>cert-manager/renovate-config:default.json5', + ], +} diff --git a/make/_shared/tools/00_mod.mk b/make/_shared/tools/00_mod.mk new file mode 100644 index 00000000000..f95b5fe51ae --- /dev/null +++ b/make/_shared/tools/00_mod.mk @@ -0,0 +1,725 @@ +# 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. + +ifndef bin_dir +$(error bin_dir is not set) +endif + +########################################## + +default_shared_dir := $(CURDIR)/$(bin_dir) +# If $(HOME) is set and $(CI) is not, use the $(HOME)/.cache +# folder to store downloaded binaries. +ifneq ($(shell printenv HOME),) +ifeq ($(shell printenv CI),) +default_shared_dir := $(HOME)/.cache/makefile-modules +endif +endif + +export DOWNLOAD_DIR ?= $(default_shared_dir)/downloaded +export GOVENDOR_DIR ?= $(default_shared_dir)/go_vendor + +$(bin_dir)/tools $(DOWNLOAD_DIR)/tools: + @mkdir -p $@ + +checkhash_script := $(dir $(lastword $(MAKEFILE_LIST)))/util/checkhash.sh +lock_script := $(dir $(lastword $(MAKEFILE_LIST)))/util/lock.sh + +# $outfile is a variable in the lock script +outfile := $$outfile + +for_each_kv = $(foreach item,$2,$(eval $(call $1,$(word 1,$(subst =, ,$(item))),$(word 2,$(subst =, ,$(item)))))) + +# To make sure we use the right version of each tool, we put symlink in +# $(bin_dir)/tools, and the actual binaries are in $(bin_dir)/downloaded. When bumping +# the version of the tools, this symlink gets updated. + +# Let's have $(bin_dir)/tools in front of the PATH so that we don't inadvertently +# pick up the wrong binary somewhere. Watch out, $(shell echo $$PATH) will +# still print the original PATH, since GNU make does not honor exported +# variables: https://stackoverflow.com/questions/54726457 +export PATH := $(CURDIR)/$(bin_dir)/tools:$(PATH) + +CTR ?= docker +.PHONY: __require-ctr +ifneq ($(shell command -v $(CTR) >/dev/null || echo notfound),) +__require-ctr: + @:$(error "$(CTR) (or set CTR to a docker-compatible tool)") +endif +NEEDS_CTR = __require-ctr + +tools := +# https://github.com/helm/helm/releases +# renovate: datasource=github-releases packageName=helm/helm +tools += helm=v3.19.0 +# https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl +# renovate: datasource=github-releases packageName=kubernetes/kubernetes +tools += kubectl=v1.34.1 +# https://github.com/kubernetes-sigs/kind/releases +# renovate: datasource=github-releases packageName=kubernetes-sigs/kind +tools += kind=v0.30.0 +# https://www.vaultproject.io/downloads +# renovate: datasource=github-releases packageName=hashicorp/vault +tools += vault=v1.21.0 +# https://github.com/Azure/azure-workload-identity/releases +# renovate: datasource=github-releases packageName=Azure/azure-workload-identity +tools += azwi=v1.5.1 +# https://github.com/kyverno/kyverno/releases +# renovate: datasource=github-releases packageName=kyverno/kyverno +tools += kyverno=v1.15.2 +# https://github.com/mikefarah/yq/releases +# renovate: datasource=github-releases packageName=mikefarah/yq +tools += yq=v4.48.1 +# https://github.com/ko-build/ko/releases +# renovate: datasource=github-releases packageName=ko-build/ko +tools += ko=0.18.0 +# https://github.com/protocolbuffers/protobuf/releases +# renovate: datasource=github-releases packageName=protocolbuffers/protobuf +tools += protoc=v32.1 +# https://github.com/aquasecurity/trivy/releases +# renovate: datasource=github-releases packageName=aquasecurity/trivy +tools += trivy=v0.67.2 +# https://github.com/vmware-tanzu/carvel-ytt/releases +# renovate: datasource=github-releases packageName=vmware-tanzu/carvel-ytt +tools += ytt=v0.52.1 +# https://github.com/rclone/rclone/releases +# renovate: datasource=github-releases packageName=rclone/rclone +tools += rclone=v1.71.2 +# https://github.com/istio/istio/releases +# renovate: datasource=github-releases packageName=istio/istio +tools += istioctl=1.27.3 + +### go packages +# https://pkg.go.dev/sigs.k8s.io/controller-tools/cmd/controller-gen?tab=versions +# renovate: datasource=go packageName=sigs.k8s.io/controller-tools +tools += controller-gen=v0.19.0 +# https://pkg.go.dev/golang.org/x/tools/cmd/goimports?tab=versions +# renovate: datasource=go packageName=golang.org/x/tools +tools += goimports=v0.38.0 +# https://pkg.go.dev/github.com/google/go-licenses/v2?tab=versions +# renovate: datasource=go packageName=github.com/inteon/go-licenses/v2 +tools += go-licenses=v2.0.0-20250821024731-e4be79958780 +# https://pkg.go.dev/gotest.tools/gotestsum?tab=versions +# renovate: datasource=github-releases packageName=gotestyourself/gotestsum +tools += gotestsum=v1.13.0 +# https://pkg.go.dev/sigs.k8s.io/kustomize/kustomize/v5?tab=versions +# renovate: datasource=go packageName=sigs.k8s.io/kustomize/kustomize/v5 +tools += kustomize=v5.7.1 +# https://pkg.go.dev/github.com/itchyny/gojq?tab=versions +# renovate: datasource=go packageName=github.com/itchyny/gojq +tools += gojq=v0.12.17 +# https://pkg.go.dev/github.com/google/go-containerregistry/pkg/crane?tab=versions +# renovate: datasource=go packageName=github.com/google/go-containerregistry +tools += crane=v0.20.6 +# https://pkg.go.dev/google.golang.org/protobuf/cmd/protoc-gen-go?tab=versions +# renovate: datasource=go packageName=google.golang.org/protobuf +tools += protoc-gen-go=v1.36.10 +# https://pkg.go.dev/github.com/sigstore/cosign/v2/cmd/cosign?tab=versions +# renovate: datasource=go packageName=github.com/sigstore/cosign/v2 +tools += cosign=v2.6.1 +# https://pkg.go.dev/github.com/cert-manager/boilersuite?tab=versions +# renovate: datasource=go packageName=github.com/cert-manager/boilersuite +tools += boilersuite=v0.1.0 +# https://pkg.go.dev/github.com/princjef/gomarkdoc/cmd/gomarkdoc?tab=versions +# renovate: datasource=go packageName=github.com/princjef/gomarkdoc +tools += gomarkdoc=v1.1.0 +# https://pkg.go.dev/oras.land/oras/cmd/oras?tab=versions +# renovate: datasource=go packageName=oras.land/oras +tools += oras=v1.3.0 +# https://pkg.go.dev/github.com/onsi/ginkgo/v2/ginkgo?tab=versions +# The gingko version should be kept in sync with the version used in code. +# If there is no go.mod file (which is only the case for the makefile-modules +# repo), then we default to a version that we know exists. We have to do this +# because otherwise the awk failure renders the whole makefile unusable. +detected_ginkgo_version := $(shell [[ -f go.mod ]] && awk '/ginkgo\/v2/ {print $$2}' go.mod || echo "v2.23.4") +tools += ginkgo=$(detected_ginkgo_version) +# https://pkg.go.dev/github.com/cert-manager/klone?tab=versions +# renovate: datasource=go packageName=github.com/cert-manager/klone +tools += klone=v0.2.0 +# https://pkg.go.dev/github.com/goreleaser/goreleaser/v2?tab=versions +# renovate: datasource=go packageName=github.com/goreleaser/goreleaser/v2 +tools += goreleaser=v2.12.7 +# https://pkg.go.dev/github.com/anchore/syft/cmd/syft?tab=versions +# renovate: datasource=go packageName=github.com/anchore/syft +tools += syft=v1.36.0 +# https://github.com/cert-manager/helm-tool/releases +# renovate: datasource=github-releases packageName=cert-manager/helm-tool +tools += helm-tool=v0.5.3 +# https://github.com/cert-manager/image-tool/releases +# renovate: datasource=github-releases packageName=cert-manager/image-tool +tools += image-tool=v0.1.0 +# https://github.com/cert-manager/cmctl/releases +# renovate: datasource=github-releases packageName=cert-manager/cmctl +tools += cmctl=v2.3.0 +# https://pkg.go.dev/github.com/cert-manager/release/cmd/cmrel?tab=versions +# renovate: datasource=go packageName=github.com/cert-manager/release +tools += cmrel=v1.12.15-0.20241121151736-e3cbe5171488 +# https://pkg.go.dev/github.com/golangci/golangci-lint/v2/cmd/golangci-lint?tab=versions +# renovate: datasource=go packageName=github.com/golangci/golangci-lint/v2 +tools += golangci-lint=v2.5.0 +# https://pkg.go.dev/golang.org/x/vuln?tab=versions +# renovate: datasource=go packageName=golang.org/x/vuln +tools += govulncheck=v1.1.4 +# https://github.com/operator-framework/operator-sdk/releases +# renovate: datasource=github-releases packageName=operator-framework/operator-sdk +tools += operator-sdk=v1.41.1 +# https://pkg.go.dev/github.com/cli/cli/v2?tab=versions +# renovate: datasource=go packageName=github.com/cli/cli/v2 +tools += gh=v2.82.1 +# https://github.com/redhat-openshift-ecosystem/openshift-preflight/releases +# renovate: datasource=github-releases packageName=redhat-openshift-ecosystem/openshift-preflight +tools += preflight=1.14.1 +# https://github.com/daixiang0/gci/releases +# renovate: datasource=github-releases packageName=daixiang0/gci +tools += gci=v0.13.7 +# https://github.com/google/yamlfmt/releases +# renovate: datasource=github-releases packageName=google/yamlfmt +tools += yamlfmt=v0.20.0 +# https://github.com/yannh/kubeconform/releases +# renovate: datasource=github-releases packageName=yannh/kubeconform +tools += kubeconform=v0.7.0 + +# FIXME(erikgb): cert-manager needs the ability to override the version set here +# https://pkg.go.dev/k8s.io/code-generator/cmd?tab=versions +# renovate: datasource=go packageName=k8s.io/code-generator +K8S_CODEGEN_VERSION ?= v0.34.1 +tools += client-gen=$(K8S_CODEGEN_VERSION) +tools += deepcopy-gen=$(K8S_CODEGEN_VERSION) +tools += informer-gen=$(K8S_CODEGEN_VERSION) +tools += lister-gen=$(K8S_CODEGEN_VERSION) +tools += applyconfiguration-gen=$(K8S_CODEGEN_VERSION) +tools += defaulter-gen=$(K8S_CODEGEN_VERSION) +tools += conversion-gen=$(K8S_CODEGEN_VERSION) +# https://github.com/kubernetes/kube-openapi +# renovate: datasource=go packageName=k8s.io/kube-openapi +tools += openapi-gen=v0.0.0-20250910181357-589584f1c912 + +# https://raw.githubusercontent.com/kubernetes-sigs/controller-tools/master/envtest-releases.yaml +# FIXME: Find a way to configure Renovate to suggest upgrades +KUBEBUILDER_ASSETS_VERSION := v1.34.1 +tools += etcd=$(KUBEBUILDER_ASSETS_VERSION) +tools += kube-apiserver=$(KUBEBUILDER_ASSETS_VERSION) + +# Additional tools can be defined to reuse the tooling in this file +ADDITIONAL_TOOLS ?= +tools += $(ADDITIONAL_TOOLS) + +# https://go.dev/dl/ +# renovate: datasource=golang-version packageName=go +VENDORED_GO_VERSION := 1.25.3 + +# Print the go version which can be used in GH actions +.PHONY: print-go-version +print-go-version: + @echo result=$(VENDORED_GO_VERSION) + +# When switching branches which use different versions of the tools, we +# need a way to re-trigger the symlinking from $(bin_dir)/downloaded to $(bin_dir)/tools. +$(bin_dir)/scratch/%_VERSION: FORCE | $(bin_dir)/scratch + @test "$($*_VERSION)" == "$(shell cat $@ 2>/dev/null)" || echo $($*_VERSION) > $@ + +# --silent = don't print output like progress meters +# --show-error = but do print errors when they happen +# --fail = exit with a nonzero error code without the response from the server when there's an HTTP error +# --location = follow redirects from the server +# --retry = the number of times to retry a failed attempt to connect +# --retry-connrefused = retry even if the initial connection was refused +CURL := curl --silent --show-error --fail --location --retry 10 --retry-connrefused + +# LN is expected to be an atomic action, meaning that two Make processes +# can run the "link $(DOWNLOAD_DIR)/tools/xxx@$(XXX_VERSION)_$(HOST_OS)_$(HOST_ARCH) +# to $(bin_dir)/tools/xxx" operation simultaneously without issues (both +# will perform the action and the second time the link will be overwritten). +# +# -s = Create a symbolic link +# -f = Force the creation of the link (replace existing links) +# -n = If destination already exists, replace it, don't use it as a directory to create a new link inside +LN := ln -fsn + +upper_map := a:A b:B c:C d:D e:E f:F g:G h:H i:I j:J k:K l:L m:M n:N o:O p:P q:Q r:R s:S t:T u:U v:V w:W x:X y:Y z:Z +uc = $(strip \ + $(eval __upper := $1) \ + $(foreach p,$(upper_map), \ + $(eval __upper := $(subst $(word 1,$(subst :, ,$p)),$(word 2,$(subst :, ,$p)),$(__upper))) \ + ) \ + )$(__upper) + +tool_names := + +# for each item `xxx` in the tools variable: +# - a $(XXX_VERSION) variable is generated +# -> this variable contains the version of the tool +# - a $(NEEDS_XXX) variable is generated +# -> this variable contains the target name for the tool, +# which is the relative path of the binary, this target +# should be used when adding the tool as a dependency to +# your target, you can't use $(XXX) as a dependency because +# make does not support an absolute path as a dependency +# - a $(XXX) variable is generated +# -> this variable contains the absolute path of the binary, +# the absolute path should be used when executing the binary +# in targets or in scripts, because it is agnostic to the +# working directory +# - an unversioned target $(bin_dir)/tools/xxx is generated that +# creates a link to the corresponding versioned target: +# $(DOWNLOAD_DIR)/tools/xxx@$(XXX_VERSION)_$(HOST_OS)_$(HOST_ARCH) +define tool_defs +tool_names += $1 + +$(call uc,$1)_VERSION ?= $2 +NEEDS_$(call uc,$1) := $$(bin_dir)/tools/$1 +$(call uc,$1) := $$(CURDIR)/$$(bin_dir)/tools/$1 + +$$(bin_dir)/tools/$1: $$(bin_dir)/scratch/$(call uc,$1)_VERSION | $$(DOWNLOAD_DIR)/tools/$1@$$($(call uc,$1)_VERSION)_$$(HOST_OS)_$$(HOST_ARCH) $$(bin_dir)/tools + @cd $$(dir $$@) && $$(LN) $$(patsubst $$(bin_dir)/%,../%,$$(word 1,$$|)) $$(notdir $$@) + @touch $$@ # making sure the target of the symlink is newer than *_VERSION +endef + +$(foreach tool,$(tools),$(eval $(call tool_defs,$(word 1,$(subst =, ,$(tool))),$(word 2,$(subst =, ,$(tool)))))) + +###### +# Go # +###### + +# $(NEEDS_GO) is a target that is set as an order-only prerequisite in +# any target that calls $(GO), e.g.: +# +# $(bin_dir)/tools/crane: $(NEEDS_GO) +# $(GO) build -o $(bin_dir)/tools/crane +# +# $(NEEDS_GO) is empty most of the time, except when running "make vendor-go" +# or when "make vendor-go" was previously run, in which case $(NEEDS_GO) is set +# to $(bin_dir)/tools/go, since $(bin_dir)/tools/go is a prerequisite of +# any target depending on Go when "make vendor-go" was run. + +detected_vendoring := $(findstring vendor-go,$(MAKECMDGOALS))$(shell [ -f $(bin_dir)/tools/go ] && echo yes) +export VENDOR_GO ?= $(detected_vendoring) + +ifeq ($(VENDOR_GO),) +.PHONY: __require-go +ifneq ($(shell command -v go >/dev/null || echo notfound),) +__require-go: + @:$(error "$(GO) (or run 'make vendor-go')") +endif +GO := go +NEEDS_GO = __require-go +else +export GOROOT := $(CURDIR)/$(bin_dir)/tools/goroot +export PATH := $(CURDIR)/$(bin_dir)/tools/goroot/bin:$(PATH) +GO := $(CURDIR)/$(bin_dir)/tools/go +NEEDS_GO := $(bin_dir)/tools/go +MAKE := $(MAKE) vendor-go +endif + +.PHONY: vendor-go +## By default, this Makefile uses the system's Go. You can use a "vendored" +## version of Go that will get downloaded by running this command once. To +## disable vendoring, run "make unvendor-go". When vendoring is enabled, +## you will want to set the following: +## +## export PATH="$PWD/$(bin_dir)/tools:$PATH" +## export GOROOT="$PWD/$(bin_dir)/tools/goroot" +## @category [shared] Tools +vendor-go: $(bin_dir)/tools/go + +.PHONY: unvendor-go +unvendor-go: $(bin_dir)/tools/go + rm -rf $(bin_dir)/tools/go $(bin_dir)/tools/goroot + +.PHONY: which-go +## Print the version and path of go which will be used for building and +## testing in Makefile commands. Vendored go will have a path in ./bin +## @category [shared] Tools +which-go: | $(NEEDS_GO) + @$(GO) version + @echo "go binary used for above version information: $(GO)" + +$(bin_dir)/tools/go: $(bin_dir)/scratch/VENDORED_GO_VERSION | $(bin_dir)/tools/goroot $(bin_dir)/tools + @cd $(dir $@) && $(LN) ./goroot/bin/go $(notdir $@) + @touch $@ # making sure the target of the symlink is newer than *_VERSION + +# The "_" in "_bin" prevents "go mod tidy" from trying to tidy the vendored goroot. +$(bin_dir)/tools/goroot: $(bin_dir)/scratch/VENDORED_GO_VERSION | $(GOVENDOR_DIR)/go@$(VENDORED_GO_VERSION)_$(HOST_OS)_$(HOST_ARCH)/goroot $(bin_dir)/tools + @cd $(dir $@) && $(LN) $(patsubst $(bin_dir)/%,../%,$(word 1,$|)) $(notdir $@) + @touch $@ # making sure the target of the symlink is newer than *_VERSION + +# Extract the tar to the $(GOVENDOR_DIR) directory, this directory is not cached across CI runs. +$(GOVENDOR_DIR)/go@$(VENDORED_GO_VERSION)_$(HOST_OS)_$(HOST_ARCH)/goroot: | $(DOWNLOAD_DIR)/tools/go@$(VENDORED_GO_VERSION)_$(HOST_OS)_$(HOST_ARCH).tar.gz + @source $(lock_script) $@; \ + mkdir -p $(outfile).dir; \ + tar xzf $| -C $(outfile).dir; \ + mv $(outfile).dir/go $(outfile); \ + rm -rf $(outfile).dir + +################### +# go dependencies # +################### + +go_dependencies := +go_dependencies += ginkgo=github.com/onsi/ginkgo/v2/ginkgo +go_dependencies += controller-gen=sigs.k8s.io/controller-tools/cmd/controller-gen +go_dependencies += goimports=golang.org/x/tools/cmd/goimports +# FIXME: Switch back to github.com/google/go-licenses once +# https://github.com/google/go-licenses/pull/327 is merged. +# Remember to also update the Go package in the Renovate marker over the version (above). +go_dependencies += go-licenses=github.com/inteon/go-licenses/v2 +go_dependencies += gotestsum=gotest.tools/gotestsum +go_dependencies += kustomize=sigs.k8s.io/kustomize/kustomize/v5 +go_dependencies += gojq=github.com/itchyny/gojq/cmd/gojq +go_dependencies += crane=github.com/google/go-containerregistry/cmd/crane +go_dependencies += protoc-gen-go=google.golang.org/protobuf/cmd/protoc-gen-go +go_dependencies += cosign=github.com/sigstore/cosign/v2/cmd/cosign +go_dependencies += boilersuite=github.com/cert-manager/boilersuite +go_dependencies += gomarkdoc=github.com/princjef/gomarkdoc/cmd/gomarkdoc +go_dependencies += oras=oras.land/oras/cmd/oras +go_dependencies += klone=github.com/cert-manager/klone +go_dependencies += goreleaser=github.com/goreleaser/goreleaser/v2 +go_dependencies += syft=github.com/anchore/syft/cmd/syft +go_dependencies += client-gen=k8s.io/code-generator/cmd/client-gen +go_dependencies += deepcopy-gen=k8s.io/code-generator/cmd/deepcopy-gen +go_dependencies += informer-gen=k8s.io/code-generator/cmd/informer-gen +go_dependencies += lister-gen=k8s.io/code-generator/cmd/lister-gen +go_dependencies += applyconfiguration-gen=k8s.io/code-generator/cmd/applyconfiguration-gen +go_dependencies += defaulter-gen=k8s.io/code-generator/cmd/defaulter-gen +go_dependencies += conversion-gen=k8s.io/code-generator/cmd/conversion-gen +go_dependencies += openapi-gen=k8s.io/kube-openapi/cmd/openapi-gen +go_dependencies += helm-tool=github.com/cert-manager/helm-tool +go_dependencies += image-tool=github.com/cert-manager/image-tool +go_dependencies += cmctl=github.com/cert-manager/cmctl/v2 +go_dependencies += cmrel=github.com/cert-manager/release/cmd/cmrel +go_dependencies += golangci-lint=github.com/golangci/golangci-lint/v2/cmd/golangci-lint +go_dependencies += govulncheck=golang.org/x/vuln/cmd/govulncheck +go_dependencies += gh=github.com/cli/cli/v2/cmd/gh +go_dependencies += gci=github.com/daixiang0/gci +go_dependencies += yamlfmt=github.com/google/yamlfmt/cmd/yamlfmt +go_dependencies += kubeconform=github.com/yannh/kubeconform/cmd/kubeconform + +################# +# go build tags # +################# + +go_tags := + +# Additional Go dependencies can be defined to re-use the tooling in this file +ADDITIONAL_GO_DEPENDENCIES ?= +ADDITIONAL_GO_TAGS ?= +go_dependencies += $(ADDITIONAL_GO_DEPENDENCIES) +go_tags += $(ADDITIONAL_GO_TAGS) + +go_tags_init = go_tags_$1 := +$(call for_each_kv,go_tags_init,$(go_dependencies)) + +go_tags_defs = go_tags_$1 += $2 +$(call for_each_kv,go_tags_defs,$(go_tags)) + +go_tool_names := + +define go_dependency +go_tool_names += $1 +$$(DOWNLOAD_DIR)/tools/$1@$($(call uc,$1)_VERSION)_$(HOST_OS)_$(HOST_ARCH): | $$(NEEDS_GO) $$(DOWNLOAD_DIR)/tools + @source $$(lock_script) $$@; \ + mkdir -p $$(outfile).dir; \ + GOWORK=off GOBIN=$$(outfile).dir $$(GO) install --tags "$(strip $(go_tags_$1))" $2@$($(call uc,$1)_VERSION); \ + mv $$(outfile).dir/$1 $$(outfile); \ + rm -rf $$(outfile).dir +endef +$(call for_each_kv,go_dependency,$(go_dependencies)) + +################## +# File downloads # +################## + +go_linux_amd64_SHA256SUM=0335f314b6e7bfe08c3d0cfaa7c19db961b7b99fb20be62b0a826c992ad14e0f +go_linux_arm64_SHA256SUM=1d42ebc84999b5e2069f5e31b67d6fc5d67308adad3e178d5a2ee2c9ff2001f5 +go_darwin_amd64_SHA256SUM=1641050b422b80dfd6299f8aa7eb8798d1cd23eac7e79f445728926e881b7bcd +go_darwin_arm64_SHA256SUM=7c083e3d2c00debfeb2f77d9a4c00a1aac97113b89b9ccc42a90487af3437382 + +.PRECIOUS: $(DOWNLOAD_DIR)/tools/go@$(VENDORED_GO_VERSION)_$(HOST_OS)_$(HOST_ARCH).tar.gz +$(DOWNLOAD_DIR)/tools/go@$(VENDORED_GO_VERSION)_$(HOST_OS)_$(HOST_ARCH).tar.gz: | $(DOWNLOAD_DIR)/tools + @source $(lock_script) $@; \ + $(CURL) https://go.dev/dl/go$(VENDORED_GO_VERSION).$(HOST_OS)-$(HOST_ARCH).tar.gz -o $(outfile); \ + $(checkhash_script) $(outfile) $(go_$(HOST_OS)_$(HOST_ARCH)_SHA256SUM) + +helm_linux_amd64_SHA256SUM=a7f81ce08007091b86d8bd696eb4d86b8d0f2e1b9f6c714be62f82f96a594496 +helm_linux_arm64_SHA256SUM=440cf7add0aee27ebc93fada965523c1dc2e0ab340d4348da2215737fc0d76ad +helm_darwin_amd64_SHA256SUM=09a108c0abda42e45af172be65c49125354bf7cd178dbe10435e94540e49c7b9 +helm_darwin_arm64_SHA256SUM=31513e1193da4eb4ae042eb5f98ef9aca7890cfa136f4707c8d4f70e2115bef6 + +.PRECIOUS: $(DOWNLOAD_DIR)/tools/helm@$(HELM_VERSION)_$(HOST_OS)_$(HOST_ARCH) +$(DOWNLOAD_DIR)/tools/helm@$(HELM_VERSION)_$(HOST_OS)_$(HOST_ARCH): | $(DOWNLOAD_DIR)/tools + @source $(lock_script) $@; \ + $(CURL) https://get.helm.sh/helm-$(HELM_VERSION)-$(HOST_OS)-$(HOST_ARCH).tar.gz -o $(outfile).tar.gz; \ + $(checkhash_script) $(outfile).tar.gz $(helm_$(HOST_OS)_$(HOST_ARCH)_SHA256SUM); \ + tar xfO $(outfile).tar.gz $(HOST_OS)-$(HOST_ARCH)/helm > $(outfile); \ + chmod +x $(outfile); \ + rm -f $(outfile).tar.gz + +kubectl_linux_amd64_SHA256SUM=7721f265e18709862655affba5343e85e1980639395d5754473dafaadcaa69e3 +kubectl_linux_arm64_SHA256SUM=420e6110e3ba7ee5a3927b5af868d18df17aae36b720529ffa4e9e945aa95450 +kubectl_darwin_amd64_SHA256SUM=bb211f2b31f2b3bc60562b44cc1e3b712a16a98e9072968ba255beb04cefcfdf +kubectl_darwin_arm64_SHA256SUM=d80e5fa36f2b14005e5bb35d3a72818acb1aea9a081af05340a000e5fbdb2f76 + +.PRECIOUS: $(DOWNLOAD_DIR)/tools/kubectl@$(KUBECTL_VERSION)_$(HOST_OS)_$(HOST_ARCH) +$(DOWNLOAD_DIR)/tools/kubectl@$(KUBECTL_VERSION)_$(HOST_OS)_$(HOST_ARCH): | $(DOWNLOAD_DIR)/tools + @source $(lock_script) $@; \ + $(CURL) https://dl.k8s.io/release/$(KUBECTL_VERSION)/bin/$(HOST_OS)/$(HOST_ARCH)/kubectl -o $(outfile); \ + $(checkhash_script) $(outfile) $(kubectl_$(HOST_OS)_$(HOST_ARCH)_SHA256SUM); \ + chmod +x $(outfile) + +kind_linux_amd64_SHA256SUM=517ab7fc89ddeed5fa65abf71530d90648d9638ef0c4cde22c2c11f8097b8889 +kind_linux_arm64_SHA256SUM=7ea2de9d2d190022ed4a8a4e3ac0636c8a455e460b9a13ccf19f15d07f4f00eb +kind_darwin_amd64_SHA256SUM=4f0b6e3b88bdc66d922c08469f05ef507d4903dd236e6319199bb9c868eed274 +kind_darwin_arm64_SHA256SUM=ceaf40df1d1551c481fb50e3deb5c3deecad5fd599df5469626b70ddf52a1518 + +.PRECIOUS: $(DOWNLOAD_DIR)/tools/kind@$(KIND_VERSION)_$(HOST_OS)_$(HOST_ARCH) +$(DOWNLOAD_DIR)/tools/kind@$(KIND_VERSION)_$(HOST_OS)_$(HOST_ARCH): | $(DOWNLOAD_DIR)/tools + @source $(lock_script) $@; \ + $(CURL) https://github.com/kubernetes-sigs/kind/releases/download/$(KIND_VERSION)/kind-$(HOST_OS)-$(HOST_ARCH) -o $(outfile); \ + $(checkhash_script) $(outfile) $(kind_$(HOST_OS)_$(HOST_ARCH)_SHA256SUM); \ + chmod +x $(outfile) + +vault_linux_amd64_SHA256SUM=5a91c93a9949ed8863ee4b91cfc30640bc49ab04225f0b1c5a0650c4d6e10171 +vault_linux_arm64_SHA256SUM=0083b02005ad89f6a01773866c6a892194ba27867b5f26ee374a0dfbbfb84c07 +vault_darwin_amd64_SHA256SUM=2e00e327be8141751f7bcc840aad93c8a5428908a4131f17d02d22eab444bcf2 +vault_darwin_arm64_SHA256SUM=fd1b26fcbc78c04c2d76d35a13a9564d450074f2547871b2046ddb95bbd7ea9c + +.PRECIOUS: $(DOWNLOAD_DIR)/tools/vault@$(VAULT_VERSION)_$(HOST_OS)_$(HOST_ARCH) +$(DOWNLOAD_DIR)/tools/vault@$(VAULT_VERSION)_$(HOST_OS)_$(HOST_ARCH): | $(DOWNLOAD_DIR)/tools + @source $(lock_script) $@; \ + $(CURL) https://releases.hashicorp.com/vault/$(VAULT_VERSION:v%=%)/vault_$(VAULT_VERSION:v%=%)_$(HOST_OS)_$(HOST_ARCH).zip -o $(outfile).zip; \ + $(checkhash_script) $(outfile).zip $(vault_$(HOST_OS)_$(HOST_ARCH)_SHA256SUM); \ + unzip -qq -c $(outfile).zip > $(outfile); \ + chmod +x $(outfile); \ + rm -f $(outfile).zip + +azwi_linux_amd64_SHA256SUM=d816d24c865d86ca101219197b493e399d3f669e8e20e0aaffc5a09f0f4c0aaf +azwi_linux_arm64_SHA256SUM=f74799439ec3d33d6f69dcaa237fbdde8501390f06ee6d6fb1edfb36f64e1fa6 +azwi_darwin_amd64_SHA256SUM=50dec4f29819a68827d695950a36b296aff501e81420787c16603d6394503c97 +azwi_darwin_arm64_SHA256SUM=f267f5fad691cb60d1983a3df5c9a67d83cba0ca0d87aa707a713d2ba4f47776 + +.PRECIOUS: $(DOWNLOAD_DIR)/tools/azwi@$(AZWI_VERSION)_$(HOST_OS)_$(HOST_ARCH) +$(DOWNLOAD_DIR)/tools/azwi@$(AZWI_VERSION)_$(HOST_OS)_$(HOST_ARCH): | $(DOWNLOAD_DIR)/tools + @source $(lock_script) $@; \ + $(CURL) https://github.com/Azure/azure-workload-identity/releases/download/$(AZWI_VERSION)/azwi-$(AZWI_VERSION)-$(HOST_OS)-$(HOST_ARCH).tar.gz -o $(outfile).tar.gz; \ + $(checkhash_script) $(outfile).tar.gz $(azwi_$(HOST_OS)_$(HOST_ARCH)_SHA256SUM); \ + tar xfO $(outfile).tar.gz azwi > $(outfile) && chmod 775 $(outfile); \ + rm -f $(outfile).tar.gz + +kubebuilder_tools_linux_amd64_SHA256SUM=c8500090806ed5ce4064eeeb2a5666476a5168c1f4ff0eadd54fe59b22c4baa7 +kubebuilder_tools_linux_arm64_SHA256SUM=cb56759108ea15933abf79d8573bbf66cca8c13e20425d7bc9f95941a060649d +kubebuilder_tools_darwin_amd64_SHA256SUM=84d47d6c3a2fa4d14571249b4cccfafad1eb77087bb680693553b438b8ec8c43 +kubebuilder_tools_darwin_arm64_SHA256SUM=f25c213bc88582750935b370fa2c6108f0259b9c8f59ece5a82345d48858fc7d + +.PRECIOUS: $(DOWNLOAD_DIR)/tools/kubebuilder_tools_$(KUBEBUILDER_ASSETS_VERSION)_$(HOST_OS)_$(HOST_ARCH).tar.gz +$(DOWNLOAD_DIR)/tools/kubebuilder_tools_$(KUBEBUILDER_ASSETS_VERSION)_$(HOST_OS)_$(HOST_ARCH).tar.gz: | $(DOWNLOAD_DIR)/tools + @source $(lock_script) $@; \ + $(CURL) https://github.com/kubernetes-sigs/controller-tools/releases/download/envtest-$(KUBEBUILDER_ASSETS_VERSION)/envtest-$(KUBEBUILDER_ASSETS_VERSION)-$(HOST_OS)-$(HOST_ARCH).tar.gz -o $(outfile); \ + $(checkhash_script) $(outfile) $(kubebuilder_tools_$(HOST_OS)_$(HOST_ARCH)_SHA256SUM) + +$(DOWNLOAD_DIR)/tools/etcd@$(KUBEBUILDER_ASSETS_VERSION)_$(HOST_OS)_$(HOST_ARCH): $(DOWNLOAD_DIR)/tools/kubebuilder_tools_$(KUBEBUILDER_ASSETS_VERSION)_$(HOST_OS)_$(HOST_ARCH).tar.gz | $(DOWNLOAD_DIR)/tools + @source $(lock_script) $@; \ + tar xfO $< controller-tools/envtest/etcd > $(outfile) && chmod 775 $(outfile) + +$(DOWNLOAD_DIR)/tools/kube-apiserver@$(KUBEBUILDER_ASSETS_VERSION)_$(HOST_OS)_$(HOST_ARCH): $(DOWNLOAD_DIR)/tools/kubebuilder_tools_$(KUBEBUILDER_ASSETS_VERSION)_$(HOST_OS)_$(HOST_ARCH).tar.gz | $(DOWNLOAD_DIR)/tools + @source $(lock_script) $@; \ + tar xfO $< controller-tools/envtest/kube-apiserver > $(outfile) && chmod 775 $(outfile) + +kyverno_linux_amd64_SHA256SUM=c90520ba24fb8b8df003ec22d6d2621e4a3d3c7497665fdcf84e9eab4ff1dfe0 +kyverno_linux_arm64_SHA256SUM=3d9b2465d09d2d251b42a8de92531cf00ecef4afc1e74ea6af01498f6a8b8c80 +kyverno_darwin_amd64_SHA256SUM=bf6348d84ef0ee487b3476db03217d24e6e980ceaea35248932f6e96ffb6d0c8 +kyverno_darwin_arm64_SHA256SUM=217af6bc2fc21006dd243101db64a48436c01a63092feabb3d994e286d64d4b1 + +.PRECIOUS: $(DOWNLOAD_DIR)/tools/kyverno@$(KYVERNO_VERSION)_$(HOST_OS)_$(HOST_ARCH) +$(DOWNLOAD_DIR)/tools/kyverno@$(KYVERNO_VERSION)_$(HOST_OS)_$(HOST_ARCH): | $(DOWNLOAD_DIR)/tools + $(eval ARCH := $(subst amd64,x86_64,$(HOST_ARCH))) + + @source $(lock_script) $@; \ + $(CURL) https://github.com/kyverno/kyverno/releases/download/$(KYVERNO_VERSION)/kyverno-cli_$(KYVERNO_VERSION)_$(HOST_OS)_$(ARCH).tar.gz -o $(outfile).tar.gz; \ + $(checkhash_script) $(outfile).tar.gz $(kyverno_$(HOST_OS)_$(HOST_ARCH)_SHA256SUM); \ + tar xfO $(outfile).tar.gz kyverno > $(outfile); \ + chmod +x $(outfile); \ + rm -f $(outfile).tar.gz + +yq_linux_amd64_SHA256SUM=99df6047f5b577a9d25f969f7c3823ada3488de2e2115b30a0abb10d9324fd9f +yq_linux_arm64_SHA256SUM=0e46b5b926a9e57c526fa2bd8f8e38b7e17fbf6e2403ff1741f3b268e3363a9e +yq_darwin_amd64_SHA256SUM=c93d5e5880c78e22aec4efc1d719751b60f9adc49b2735a8009916581b8457c2 +yq_darwin_arm64_SHA256SUM=05e19db817704d945f28f73763cc2b3c5142ef114a991f57b83bd034c2b86646 + +.PRECIOUS: $(DOWNLOAD_DIR)/tools/yq@$(YQ_VERSION)_$(HOST_OS)_$(HOST_ARCH) +$(DOWNLOAD_DIR)/tools/yq@$(YQ_VERSION)_$(HOST_OS)_$(HOST_ARCH): | $(DOWNLOAD_DIR)/tools + @source $(lock_script) $@; \ + $(CURL) https://github.com/mikefarah/yq/releases/download/$(YQ_VERSION)/yq_$(HOST_OS)_$(HOST_ARCH) -o $(outfile); \ + $(checkhash_script) $(outfile) $(yq_$(HOST_OS)_$(HOST_ARCH)_SHA256SUM); \ + chmod +x $(outfile) + +ko_linux_amd64_SHA256SUM=ce8c8776b243357e0a822c279b06c34302460221e834765dee5f4e9e2c0b7b38 +ko_linux_arm64_SHA256SUM=cf9abbdcc4fb7cf85f5e5ba029eba257ee98ef9410bcef94fae17056ec32bab5 +ko_darwin_amd64_SHA256SUM=066013c67e6e4b7c5f7c1a6b3c93ba66989e47de435558ff7edb875608028668 +ko_darwin_arm64_SHA256SUM=2efa5796986e38994a3a233641b98404fa071a76456e3c99b3c00df0436d5833 + +.PRECIOUS: $(DOWNLOAD_DIR)/tools/ko@$(KO_VERSION)_$(HOST_OS)_$(HOST_ARCH) +$(DOWNLOAD_DIR)/tools/ko@$(KO_VERSION)_$(HOST_OS)_$(HOST_ARCH): | $(DOWNLOAD_DIR)/tools + $(eval OS := $(subst linux,Linux,$(subst darwin,Darwin,$(HOST_OS)))) + $(eval ARCH := $(subst amd64,x86_64,$(HOST_ARCH))) + + @source $(lock_script) $@; \ + $(CURL) https://github.com/ko-build/ko/releases/download/v$(KO_VERSION)/ko_$(KO_VERSION)_$(OS)_$(ARCH).tar.gz -o $(outfile).tar.gz; \ + $(checkhash_script) $(outfile).tar.gz $(ko_$(HOST_OS)_$(HOST_ARCH)_SHA256SUM); \ + tar xfO $(outfile).tar.gz ko > $(outfile); \ + chmod +x $(outfile); \ + rm -f $(outfile).tar.gz + +protoc_linux_amd64_SHA256SUM=e9c129c176bb7df02546c4cd6185126ca53c89e7d2f09511e209319704b5dd7e +protoc_linux_arm64_SHA256SUM=4a802ed23d70f7bad7eb19e5a3e724b3aa967250d572cadfd537c1ba939aee6a +protoc_darwin_amd64_SHA256SUM=f9caa5b4d0b537acffb0ffd7d53225511a5574ef903fca550ea9e7600987f13b +protoc_darwin_arm64_SHA256SUM=a7b51b2113862690fa52c62f8891a6037bafb9db88d4f9924c486de9d9bb89d5 + +.PRECIOUS: $(DOWNLOAD_DIR)/tools/protoc@$(PROTOC_VERSION)_$(HOST_OS)_$(HOST_ARCH) +$(DOWNLOAD_DIR)/tools/protoc@$(PROTOC_VERSION)_$(HOST_OS)_$(HOST_ARCH): | $(DOWNLOAD_DIR)/tools + $(eval OS := $(subst darwin,osx,$(HOST_OS))) + $(eval ARCH := $(subst arm64,aarch_64,$(subst amd64,x86_64,$(HOST_ARCH)))) + + @source $(lock_script) $@; \ + $(CURL) https://github.com/protocolbuffers/protobuf/releases/download/$(PROTOC_VERSION)/protoc-$(PROTOC_VERSION:v%=%)-$(OS)-$(ARCH).zip -o $(outfile).zip; \ + $(checkhash_script) $(outfile).zip $(protoc_$(HOST_OS)_$(HOST_ARCH)_SHA256SUM); \ + unzip -qq -c $(outfile).zip bin/protoc > $(outfile); \ + chmod +x $(outfile); \ + rm -f $(outfile).zip + +trivy_linux_amd64_SHA256SUM=546511a5514afc813c0b72e4abeea2c16a32228a13a1e5114d927c190e76b1f9 +trivy_linux_arm64_SHA256SUM=e4f28390b06cdaaed94f8c49cce2c4c847938b5188aefdeb82453f2e933e57cb +trivy_darwin_amd64_SHA256SUM=4a5b936a8d89b508ecdc6edd65933b6fe3e9a368796cbdf917fd0df393f26542 +trivy_darwin_arm64_SHA256SUM=6b3163667f29fc608a2ed647c1bd42023af5779349286148190a168c5b3f28f1 + +.PRECIOUS: $(DOWNLOAD_DIR)/tools/trivy@$(TRIVY_VERSION)_$(HOST_OS)_$(HOST_ARCH) +$(DOWNLOAD_DIR)/tools/trivy@$(TRIVY_VERSION)_$(HOST_OS)_$(HOST_ARCH): | $(DOWNLOAD_DIR)/tools + $(eval OS := $(subst linux,Linux,$(subst darwin,macOS,$(HOST_OS)))) + $(eval ARCH := $(subst amd64,64bit,$(subst arm64,ARM64,$(HOST_ARCH)))) + + @source $(lock_script) $@; \ + $(CURL) https://github.com/aquasecurity/trivy/releases/download/$(TRIVY_VERSION)/trivy_$(patsubst v%,%,$(TRIVY_VERSION))_$(OS)-$(ARCH).tar.gz -o $(outfile).tar.gz; \ + $(checkhash_script) $(outfile).tar.gz $(trivy_$(HOST_OS)_$(HOST_ARCH)_SHA256SUM); \ + tar xfO $(outfile).tar.gz trivy > $(outfile); \ + chmod +x $(outfile); \ + rm $(outfile).tar.gz + +ytt_linux_amd64_SHA256SUM=490f138ae5b6864071d3c20a5a231e378cee7487cd4aeffc79dbf66718e65408 +ytt_linux_arm64_SHA256SUM=7d86bd3299e43d1455201fc213d698bae7482cd88f3e05de2f935e6eab842db9 +ytt_darwin_amd64_SHA256SUM=1975e52b3b97bd9be72f4efb714562da6a80cf181f036ae1f86eec215e208498 +ytt_darwin_arm64_SHA256SUM=a205f49267a44cd495e4c8b245754d8a216931a28ef29c78ae161c370a9b6117 + +.PRECIOUS: $(DOWNLOAD_DIR)/tools/ytt@$(YTT_VERSION)_$(HOST_OS)_$(HOST_ARCH) +$(DOWNLOAD_DIR)/tools/ytt@$(YTT_VERSION)_$(HOST_OS)_$(HOST_ARCH): | $(DOWNLOAD_DIR)/tools + @source $(lock_script) $@; \ + $(CURL) -sSfL https://github.com/vmware-tanzu/carvel-ytt/releases/download/$(YTT_VERSION)/ytt-$(HOST_OS)-$(HOST_ARCH) -o $(outfile); \ + $(checkhash_script) $(outfile) $(ytt_$(HOST_OS)_$(HOST_ARCH)_SHA256SUM); \ + chmod +x $(outfile) + +rclone_linux_amd64_SHA256SUM=ab9fa5877cee91c64fdfd61a27028a458cf618b39259e5c371dc2ec34a12e415 +rclone_linux_arm64_SHA256SUM=e2e2efc7ed143026352d60216ef0d46d3fa4fe9d647eff1bd929e6fea498e6f1 +rclone_darwin_amd64_SHA256SUM=37e50641cd736de296b8aca8149e607b9923b357d79abb902e89c4cdb1fcc790 +rclone_darwin_arm64_SHA256SUM=d1cea838b618f9b4f15984748502232684e92ff0b90e3c4c8bd91ac21f4d8695 + +.PRECIOUS: $(DOWNLOAD_DIR)/tools/rclone@$(RCLONE_VERSION)_$(HOST_OS)_$(HOST_ARCH) +$(DOWNLOAD_DIR)/tools/rclone@$(RCLONE_VERSION)_$(HOST_OS)_$(HOST_ARCH): | $(DOWNLOAD_DIR)/tools + $(eval OS := $(subst darwin,osx,$(HOST_OS))) + + @source $(lock_script) $@; \ + $(CURL) https://github.com/rclone/rclone/releases/download/$(RCLONE_VERSION)/rclone-$(RCLONE_VERSION)-$(OS)-$(HOST_ARCH).zip -o $(outfile).zip; \ + $(checkhash_script) $(outfile).zip $(rclone_$(HOST_OS)_$(HOST_ARCH)_SHA256SUM); \ + unzip -p $(outfile).zip rclone-$(RCLONE_VERSION)-$(OS)-$(HOST_ARCH)/rclone > $(outfile); \ + chmod +x $(outfile); \ + rm -f $(outfile).zip + +istioctl_linux_amd64_SHA256SUM=55670d7472548b71e495ebc1184e8d90bcc34f7897d7e570c57a33fa5e6eb25d +istioctl_linux_arm64_SHA256SUM=1ff44e1b90e3fa432bada81e566fd3282878be8f1dd88f82c0221a5b56480d63 +istioctl_darwin_amd64_SHA256SUM=ec1064b244f1ff8601053545469fd2bfecdda7de65ec0fa04e0e760c4c40fbe0 +istioctl_darwin_arm64_SHA256SUM=6b51382defc02ad460c12052e3214e1f9763ff3a7bb73694ec032f5260842dd3 + +.PRECIOUS: $(DOWNLOAD_DIR)/tools/istioctl@$(ISTIOCTL_VERSION)_$(HOST_OS)_$(HOST_ARCH) +$(DOWNLOAD_DIR)/tools/istioctl@$(ISTIOCTL_VERSION)_$(HOST_OS)_$(HOST_ARCH): | $(DOWNLOAD_DIR)/tools + $(eval OS := $(subst darwin,osx,$(HOST_OS))) + + @source $(lock_script) $@; \ + $(CURL) https://github.com/istio/istio/releases/download/$(ISTIOCTL_VERSION)/istio-$(ISTIOCTL_VERSION)-$(OS)-$(HOST_ARCH).tar.gz -o $(outfile).tar.gz; \ + $(checkhash_script) $(outfile).tar.gz $(istioctl_$(HOST_OS)_$(HOST_ARCH)_SHA256SUM); \ + tar xfO $(outfile).tar.gz istio-$(ISTIOCTL_VERSION)/bin/istioctl > $(outfile); \ + chmod +x $(outfile); \ + rm $(outfile).tar.gz + +preflight_linux_amd64_SHA256SUM=cd1b6143fb511433d07f29075b4840b712933d7d4d4fc6353b079b59c1cb06cd +preflight_linux_arm64_SHA256SUM=cd29e198bd54cec46b219fc151b1b9c8fe71c33e7fdab7814862736a309a2a7c +preflight_darwin_amd64_SHA256SUM=7e03a564cfb1697a6a3179c5d2f6f0a861a14bf4443f553d946f92ac06376b98 +preflight_darwin_arm64_SHA256SUM=216b5f8846b6d3292bb798765a63f935627c36285fcba649ddab535973e70914 + +.PRECIOUS: $(DOWNLOAD_DIR)/tools/preflight@$(PREFLIGHT_VERSION)_$(HOST_OS)_$(HOST_ARCH) +$(DOWNLOAD_DIR)/tools/preflight@$(PREFLIGHT_VERSION)_$(HOST_OS)_$(HOST_ARCH): | $(DOWNLOAD_DIR)/tools + @source $(lock_script) $@; \ + $(CURL) https://github.com/redhat-openshift-ecosystem/openshift-preflight/releases/download/$(PREFLIGHT_VERSION)/preflight-$(HOST_OS)-$(HOST_ARCH) -o $(outfile); \ + $(checkhash_script) $(outfile) $(preflight_$(HOST_OS)_$(HOST_ARCH)_SHA256SUM); \ + chmod +x $(outfile) + +operator-sdk_linux_amd64_SHA256SUM=348284cbd5298f70e2b0a01f9f86820a3149aa6e7e19272e886a9d5769c7fb69 +operator-sdk_linux_arm64_SHA256SUM=719e5565cb11895995284d236e94bc14af0c9e7c96954ce4f30f450d8c86995e +operator-sdk_darwin_amd64_SHA256SUM=d1d55418a37f142913b7155cfdd16416aeaa657eb25e27644bd37a91451f7751 +operator-sdk_darwin_arm64_SHA256SUM=e9f3bdc229697a30f725ffa5bbb15ee59ca7eba6e6f58b3028bf940903ed0df6 + +.PRECIOUS: $(DOWNLOAD_DIR)/tools/operator-sdk@$(OPERATOR-SDK_VERSION)_$(HOST_OS)_$(HOST_ARCH) +$(DOWNLOAD_DIR)/tools/operator-sdk@$(OPERATOR-SDK_VERSION)_$(HOST_OS)_$(HOST_ARCH): | $(DOWNLOAD_DIR)/tools + @source $(lock_script) $@; \ + $(CURL) https://github.com/operator-framework/operator-sdk/releases/download/$(OPERATOR-SDK_VERSION)/operator-sdk_$(HOST_OS)_$(HOST_ARCH) -o $(outfile); \ + $(checkhash_script) $(outfile) $(operator-sdk_$(HOST_OS)_$(HOST_ARCH)_SHA256SUM); \ + chmod +x $(outfile) + +################# +# Other Targets # +################# + +# Although we "vendor" most tools in $(bin_dir)/tools, we still require some binaries +# to be available on the system. The vendor-go MAKECMDGOALS trick prevents the +# check for the presence of Go when 'make vendor-go' is run. + +# Gotcha warning: MAKECMDGOALS only contains what the _top level_ make invocation used, and doesn't look at target dependencies +# i.e. if we have a target "abc: vendor-go test" and run "make abc", we'll get an error +# about go being missing even though abc itself depends on vendor-go! +# That means we need to pass vendor-go at the top level if go is not installed (i.e. "make vendor-go abc") + +missing=$(shell (command -v curl >/dev/null || echo curl) \ + && (command -v sha256sum >/dev/null || command -v shasum >/dev/null || echo sha256sum) \ + && (command -v git >/dev/null || echo git) \ + && (command -v xargs >/dev/null || echo xargs) \ + && (command -v bash >/dev/null || echo bash)) +ifneq ($(missing),) +$(error Missing required tools: $(missing)) +endif + +non_go_tool_names := $(filter-out $(go_tool_names),$(tool_names)) + +.PHONY: non-go-tools +## Download and setup all Non-Go tools +## @category [shared] Tools +non-go-tools: $(non_go_tool_names:%=$(bin_dir)/tools/%) + +.PHONY: go-tools +## Download and setup all Go tools +## NOTE: this target is also used to learn the shas of +## these tools (see scripts/learn_tools_shas.sh in the +## Makefile modules repo) +## @category [shared] Tools +go-tools: $(go_tool_names:%=$(bin_dir)/tools/%) + +.PHONY: tools +## Download and setup all tools +## @category [shared] Tools +tools: non-go-tools go-tools diff --git a/make/_shared/tools/util/checkhash.sh b/make/_shared/tools/util/checkhash.sh new file mode 100755 index 00000000000..62e5489bad4 --- /dev/null +++ b/make/_shared/tools/util/checkhash.sh @@ -0,0 +1,58 @@ +#!/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 + +SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" + +# This script takes the hash of its first argument and verifies it against the +# hex hash given in its second argument + +function usage_and_exit() { + echo "usage: $0 " + echo "or: LEARN_FILE= $0 " + exit 1 +} + +HASH_TARGET=${1:-} +EXPECTED_HASH=${2:-} + +if [[ -z $HASH_TARGET ]]; then + usage_and_exit +fi + +if [[ -z $EXPECTED_HASH ]]; then + usage_and_exit +fi + +SHASUM=$("${SCRIPT_DIR}/hash.sh" "$HASH_TARGET") + +if [[ "$SHASUM" == "$EXPECTED_HASH" ]]; then + exit 0 +fi + +# When running 'make learn-sha-tools', we don't want this script to fail. +# Instead we log what sha values are wrong, so the make.mk file can be updated. + +if [ "${LEARN_FILE:-}" != "" ]; then + echo "s/$EXPECTED_HASH/$SHASUM/g" >> "${LEARN_FILE:-}" + exit 0 +fi + +echo "invalid checksum for \"$HASH_TARGET\": wanted \"$EXPECTED_HASH\" but got \"$SHASUM\"" +exit 1 diff --git a/hack/verify-crds.sh b/make/_shared/tools/util/hash.sh similarity index 67% rename from hack/verify-crds.sh rename to make/_shared/tools/util/hash.sh index 49f020adfed..21d006fc8fa 100755 --- a/hack/verify-crds.sh +++ b/make/_shared/tools/util/hash.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash -# Copyright 2022 The cert-manager Authors. +# 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. @@ -14,13 +14,14 @@ # See the License for the specific language governing permissions and # limitations under the License. -set -o nounset set -o errexit +set -o nounset set -o pipefail -# This file is kept as backwards-compatibility for people with muscle memory who -# type "./hack/verify-crds.sh" and expect it to work, or for third party CI pipelines. - -# This script may be removed in the future. Prefer using `make` directly. +# This script is a wrapper for outputting purely the sha256 hash of the input file, +# ideally in a portable way. -make verify-crds +case "$(uname -s)" in + Darwin*) shasum -a 256 "$1";; + *) sha256sum "$1" +esac | cut -d" " -f1 \ No newline at end of file diff --git a/make/_shared/tools/util/lock.sh b/make/_shared/tools/util/lock.sh new file mode 100755 index 00000000000..0b89fda7da4 --- /dev/null +++ b/make/_shared/tools/util/lock.sh @@ -0,0 +1,70 @@ +#!/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 + +# This script is used to lock a file while it is being downloaded. It prevents +# multiple processes from downloading the same file at the same time or from reading +# a half-downloaded file. +# We need this solution because we have recursive $(MAKE) calls in our makefile +# which each will try to download a set of tools. To prevent them from all downloading +# the same files, we re-use the same downloads folder for all $(MAKE) invocations and +# use this script to deduplicate the download processes. + +finalfile="$1" +lockfile="$finalfile.lock" + +# On macOS, flock is not installed, we just skip locking in that case, +# this means that running verify in parallel without downloading all +# tools first will not work. +flock_installed=$(command -v flock >/dev/null && echo "yes" || echo "no") + +if [[ "$flock_installed" == "yes" ]]; then + mkdir -p "$(dirname "$lockfile")" + touch "$lockfile" + exec {FD}<>"$lockfile" + + # wait for the file to be unlocked + if ! flock -x $FD; then + echo "Failed to obtain a lock for $lockfile" + exit 1 + fi +fi + +# now that we have the lock, check if file is already there +if [[ -e "$finalfile" ]]; then + exit 0 +fi + +# use a temporary file to prevent Make from thinking the file is ready +# while in reality is is only a partial download +# shellcheck disable=SC2034 +outfile="$finalfile.tmp" + +finish() { + rv=$? + if [[ $rv -eq 0 ]]; then + mv "$outfile" "$finalfile" + echo "[info]: downloaded $finalfile" + else + rm -rf "$outfile" || true + rm -rf "$finalfile" || true + fi + rm -rf "$lockfile" || true +} +trap finish EXIT SIGINT diff --git a/hack/update-all.sh b/make/_shared_new/helm/01_mod.mk old mode 100755 new mode 100644 similarity index 69% rename from hack/update-all.sh rename to make/_shared_new/helm/01_mod.mk index ca636b294ea..45ed301badb --- a/hack/update-all.sh +++ b/make/_shared_new/helm/01_mod.mk @@ -1,6 +1,4 @@ -#!/usr/bin/env bash - -# Copyright 2022 The cert-manager Authors. +# 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. @@ -14,10 +12,9 @@ # See the License for the specific language governing permissions and # limitations under the License. -set -o errexit -set -o nounset -set -o pipefail - -# This script is kept only to preserve muscle memory. Prefer using make directly. +ifndef helm_dont_include_crds +include $(dir $(lastword $(MAKEFILE_LIST)))/crds.mk +endif -make update-all +include $(dir $(lastword $(MAKEFILE_LIST)))/helm.mk +include $(dir $(lastword $(MAKEFILE_LIST)))/deploy.mk diff --git a/make/_shared_new/helm/crd.template.footer.yaml b/make/_shared_new/helm/crd.template.footer.yaml new file mode 100644 index 00000000000..2a42faa9ba0 --- /dev/null +++ b/make/_shared_new/helm/crd.template.footer.yaml @@ -0,0 +1 @@ +{{- end }} diff --git a/make/_shared_new/helm/crd.template.header.yaml b/make/_shared_new/helm/crd.template.header.yaml new file mode 100644 index 00000000000..e1fbd33fa4b --- /dev/null +++ b/make/_shared_new/helm/crd.template.header.yaml @@ -0,0 +1,11 @@ +{{- if REPLACE_CRD_EXPRESSION }} +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: "REPLACE_CRD_NAME" + {{- if .Values.crds.keep }} + annotations: + helm.sh/resource-policy: keep + {{- end }} + labels: + {{- include "REPLACE_LABELS_TEMPLATE" . | nindent 4 }} diff --git a/make/_shared_new/helm/crds.mk b/make/_shared_new/helm/crds.mk new file mode 100644 index 00000000000..124a15d114c --- /dev/null +++ b/make/_shared_new/helm/crds.mk @@ -0,0 +1,90 @@ +# 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. + +################ +# Check Inputs # +################ + +ifndef helm_chart_source_dir +$(error helm_chart_source_dir is not set) +endif + +ifndef helm_labels_template_name +$(error helm_labels_template_name is not set) +endif + +################ +# Add targets # +################ + +crd_template_header := $(dir $(lastword $(MAKEFILE_LIST)))/crd.template.header.yaml +crd_template_footer := $(dir $(lastword $(MAKEFILE_LIST)))/crd.template.footer.yaml + +# see https://stackoverflow.com/a/53408233 +sed_inplace := sed -i'' +ifeq ($(HOST_OS),darwin) + sed_inplace := sed -i '' +endif + +crds_dir ?= deploy/crds +crds_dir_readme := $(dir $(lastword $(MAKEFILE_LIST)))/crds_dir.README.md +crds_expression ?= .Values.crds.enabled +crds_template_include_pattern := *.yaml +# Space-separated list of basenames to exclude (e.g. foo.yaml *_test.yaml) +crds_template_exclude_pattern ?= + +define filter-out-basenames + $(if $(strip $(2)), \ + $(foreach f,$(1),$(if $(filter $(2),$(notdir $(f))),,$(f))), \ + $(1)) +endef + +.PHONY: generate-crds +## Generate CRD manifests. +## @category [shared] Generate/ Verify +generate-crds: | $(NEEDS_CONTROLLER-GEN) $(NEEDS_YQ) + $(eval crds_gen_temp := $(bin_dir)/scratch/crds) + $(eval directories := $(shell ls -d */ | grep -v -e 'make' $(shell git check-ignore -- * | sed 's/^/-e /'))) + + rm -rf $(crds_gen_temp) + mkdir -p $(crds_gen_temp) + + $(CONTROLLER-GEN) crd \ + $(directories:%=paths=./%...) \ + output:crd:artifacts:config=$(crds_gen_temp) + + @echo "Updating CRDs with helm templating, writing to $(helm_chart_source_dir)/templates" + + $(eval crds_gen_temp_all_files := $(wildcard $(crds_gen_temp)/$(crds_template_include_pattern))) + $(eval crds_gen_temp_files := $(if $(crds_template_exclude_pattern), \ + $(call filter-out-basenames,$(crds_gen_temp_all_files),$(crds_template_exclude_pattern)), \ + $(crds_gen_temp_all_files))) + + @for f in $(crds_gen_temp_files); do \ + crd_name=$$($(YQ) eval '.metadata.name' $$f); \ + crd_template_file="$(helm_chart_source_dir)/templates/crd-$$(basename $$f)"; \ + cat $(crd_template_header) > $$crd_template_file; \ + $(sed_inplace) "s/REPLACE_CRD_EXPRESSION/$(crds_expression)/g" $$crd_template_file; \ + $(sed_inplace) "s/REPLACE_CRD_NAME/$$crd_name/g" $$crd_template_file; \ + $(sed_inplace) "s/REPLACE_LABELS_TEMPLATE/$(helm_labels_template_name)/g" $$crd_template_file; \ + $(YQ) -I2 '{"spec": .spec}' $$f >> $$crd_template_file; \ + cat $(crd_template_footer) >> $$crd_template_file; \ + done + + @if [ -n "$$(ls $(crds_gen_temp) 2>/dev/null)" ]; then \ + cp $(crds_gen_temp)/* $(crds_dir)/ ; \ + cp $(crds_dir_readme) $(crds_dir)/README.md ; \ + fi + +shared_generate_targets += generate-crds diff --git a/make/_shared_new/helm/crds_dir.README.md b/make/_shared_new/helm/crds_dir.README.md new file mode 100644 index 00000000000..fba79fed242 --- /dev/null +++ b/make/_shared_new/helm/crds_dir.README.md @@ -0,0 +1,8 @@ +# CRDs source directory + +> **WARNING**: if you are an end-user, you probably should NOT need to use the +> files in this directory. These files are for **reference, development and testing purposes only**. + +This directory contains 'source code' used to build our CustomResourceDefinition +resources consumed by our officially supported deployment methods (e.g. the Helm chart). +The CRDs in this directory might be incomplete, and should **NOT** be used to provision the operator. \ No newline at end of file diff --git a/make/_shared_new/helm/deploy.mk b/make/_shared_new/helm/deploy.mk new file mode 100644 index 00000000000..8bc6ebb4773 --- /dev/null +++ b/make/_shared_new/helm/deploy.mk @@ -0,0 +1,54 @@ +# 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. + +ifndef deploy_name +$(error deploy_name is not set) +endif + +ifndef deploy_namespace +$(error deploy_namespace is not set) +endif + +# Install options allows the user configuration of extra flags +INSTALL_OPTIONS ?= + +########################################## + +.PHONY: install +## Install controller helm chart on the current active K8S cluster. +## @category [shared] Deployment +install: $(helm_chart_archive) | $(NEEDS_HELM) + $(HELM) upgrade $(deploy_name) $(helm_chart_archive) \ + --wait \ + --install \ + --create-namespace \ + $(INSTALL_OPTIONS) \ + --namespace $(deploy_namespace) + +.PHONY: uninstall +## Uninstall controller helm chart from the current active K8S cluster. +## @category [shared] Deployment +uninstall: | $(NEEDS_HELM) + $(HELM) uninstall $(deploy_name) \ + --wait \ + --namespace $(deploy_namespace) + +.PHONY: template +## Template the helm chart. +## @category [shared] Deployment +template: $(helm_chart_archive) | $(NEEDS_HELM) + @$(HELM) template $(deploy_name) $(helm_chart_archive) \ + --create-namespace \ + $(INSTALL_OPTIONS) \ + --namespace $(deploy_namespace) diff --git a/make/_shared_new/helm/helm.mk b/make/_shared_new/helm/helm.mk new file mode 100644 index 00000000000..6c84d1f74e5 --- /dev/null +++ b/make/_shared_new/helm/helm.mk @@ -0,0 +1,189 @@ +# 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. + +ifndef bin_dir +$(error bin_dir is not set) +endif + +ifndef helm_chart_source_dir +$(error helm_chart_source_dir is not set) +endif + +ifndef helm_chart_image_name +$(error helm_chart_image_name is not set) +endif + +ifndef helm_chart_version +$(error helm_chart_version is not set) +endif +ifneq ($(helm_chart_version:v%=v),v) +$(error helm_chart_version "$(helm_chart_version)" should start with a "v" - did you forget to pull tags from the remote repository?) +endif + +ifndef helm_values_mutation_function +$(error helm_values_mutation_function is not set) +endif + +########################################## + +helm_chart_name := $(notdir $(helm_chart_image_name)) +helm_chart_image_registry := $(dir $(helm_chart_image_name)) +helm_chart_image_tag := $(helm_chart_version) +helm_chart_sources := $(shell find $(helm_chart_source_dir) -maxdepth 1 -type f) $(shell find $(helm_chart_source_dir)/templates -type f) +helm_chart_archive := $(bin_dir)/scratch/helm/$(helm_chart_name)-$(helm_chart_version).tgz +helm_digest_path := $(bin_dir)/scratch/helm/$(helm_chart_name)-$(helm_chart_version).digests +helm_digest = $(shell head -1 $(helm_digest_path) 2> /dev/null) + +$(bin_dir)/scratch/helm: + @mkdir -p $@ + +$(helm_chart_archive): $(helm_chart_sources) | $(NEEDS_HELM) $(NEEDS_YQ) $(bin_dir)/scratch/helm + $(eval helm_chart_source_dir_versioned := $@.tmp) + rm -rf $(helm_chart_source_dir_versioned) + mkdir -p $(dir $(helm_chart_source_dir_versioned)) + cp -a $(helm_chart_source_dir) $(helm_chart_source_dir_versioned) + + $(call helm_values_mutation_function,$(helm_chart_source_dir_versioned)/values.yaml) + + @if ! $(YQ) -oy '.name' $(helm_chart_source_dir_versioned)/Chart.yaml | grep -q '^$(helm_chart_name)$$'; then \ + echo "Chart name does not match the name in the helm_chart_name variable"; \ + exit 1; \ + fi + + $(YQ) '.annotations."artifacthub.io/prerelease" = "$(IS_PRERELEASE)"' \ + --inplace $(helm_chart_source_dir_versioned)/Chart.yaml + + mkdir -p $(dir $@) + $(HELM) package $(helm_chart_source_dir_versioned) \ + --app-version $(helm_chart_version) \ + --version $(helm_chart_version) \ + --destination $(dir $@) + +.PHONY: helm-chart-oci-push +## Create and push Helm chart to OCI registry. +## Will also create a non-v-prefixed tag for the OCI image. +## @category [shared] Publish +helm-chart-oci-push: $(helm_chart_archive) | $(NEEDS_HELM) $(NEEDS_CRANE) + $(HELM) push "$(helm_chart_archive)" "oci://$(helm_chart_image_registry)" 2>&1 \ + | tee >(grep -o "sha256:.\+" | tee $(helm_digest_path)) + + @# $(helm_chart_image_tag:v%=%) removes the v prefix from the value stored in helm_chart_image_tag. + @# See https://www.gnu.org/software/make/manual/html_node/Substitution-Refs.html for the manual on the syntax. + helm_digest=$$(cat $(helm_digest_path)) && \ + $(CRANE) copy "$(helm_chart_image_name)@$$helm_digest" "$(helm_chart_image_name):$(helm_chart_image_tag:v%=%)" + +.PHONY: helm-chart +## Create a helm chart +## @category [shared] Helm Chart +helm-chart: $(helm_chart_archive) + +helm_tool_header_search ?= ^ +helm_tool_footer_search ?= ^ + +.PHONY: generate-helm-docs +## Generate Helm chart documentation. +## @category [shared] Generate/ Verify +generate-helm-docs: | $(NEEDS_HELM-TOOL) + $(HELM-TOOL) inject -i $(helm_chart_source_dir)/values.yaml -o $(helm_chart_source_dir)/README.md --header-search "$(helm_tool_header_search)" --footer-search "$(helm_tool_footer_search)" + +shared_generate_targets += generate-helm-docs + +.PHONY: generate-helm-schema +## Generate Helm chart schema. +## @category [shared] Generate/ Verify +generate-helm-schema: | $(NEEDS_HELM-TOOL) $(NEEDS_GOJQ) + $(HELM-TOOL) schema -i $(helm_chart_source_dir)/values.yaml | $(GOJQ) > $(helm_chart_source_dir)/values.schema.json + +shared_generate_targets += generate-helm-schema + +.PHONY: verify-helm-values +## Verify Helm chart values using helm-tool. +## @category [shared] Generate/ Verify +verify-helm-values: | $(NEEDS_HELM-TOOL) $(NEEDS_GOJQ) + $(HELM-TOOL) lint -i $(helm_chart_source_dir)/values.yaml -d $(helm_chart_source_dir)/templates -e $(helm_chart_source_dir)/values.linter.exceptions + +shared_verify_targets += verify-helm-values + +$(bin_dir)/scratch/kyverno: + @mkdir -p $@ + +$(bin_dir)/scratch/kyverno/pod-security-policy.yaml: | $(NEEDS_KUSTOMIZE) $(bin_dir)/scratch/kyverno + @$(KUSTOMIZE) build https://github.com/kyverno/policies/pod-security/enforce > $@ + +# Extra arguments for kyverno apply. +kyverno_apply_extra_args := +# Allows known policy violations to be skipped by supplying Kyverno policy +# exceptions as a Kyverno YAML resource, e.g.: +# apiVersion: kyverno.io/v2 +# kind: PolicyException +# metadata: +# name: pod-security-exceptions +# spec: +# exceptions: +# - policyName: disallow-privilege-escalation +# ruleNames: +# - autogen-privilege-escalation +# - policyName: restrict-seccomp-strict +# ruleNames: +# - autogen-check-seccomp-strict +# match: +# any: +# - resources: +# kinds: +# - Deployment +# namespaces: +# - mynamespace +# names: +# - my-deployment +ifneq ("$(wildcard make/verify-pod-security-standards-exceptions.yaml)","") + kyverno_apply_extra_args += --exceptions make/verify-pod-security-standards-exceptions.yaml +endif + +.PHONY: verify-pod-security-standards +## Verify that the Helm chart complies with the pod security standards. +## +## You can add Kyverno policy exceptions to +## `make/verify-pod-security-standards-exceptions.yaml`, to skip some of the pod +## security policy rules. +## +## @category [shared] Generate/ Verify +verify-pod-security-standards: $(helm_chart_archive) $(bin_dir)/scratch/kyverno/pod-security-policy.yaml | $(NEEDS_KYVERNO) $(NEEDS_HELM) + @$(HELM) template $(helm_chart_archive) $(INSTALL_OPTIONS) \ + | $(KYVERNO) apply $(bin_dir)/scratch/kyverno/pod-security-policy.yaml \ + $(kyverno_apply_extra_args) \ + --resource - \ + --table + +shared_verify_targets_dirty += verify-pod-security-standards + +.PHONY: verify-helm-lint +## Verify that the Helm chart is linted. +## @category [shared] Generate/ Verify +verify-helm-lint: $(helm_chart_archive) | $(NEEDS_HELM) + $(HELM) lint $(helm_chart_archive) + +shared_verify_targets_dirty += verify-helm-lint + +.PHONY: verify-helm-kubeconform +## Verify that the Helm chart passes a strict check using kubeconform +## @category [shared] Generate/ Verify +verify-helm-kubeconform: $(helm_chart_archive) | $(NEEDS_KUBECONFORM) + @$(HELM) template $(helm_chart_archive) $(INSTALL_OPTIONS) \ + | $(KUBECONFORM) \ + -schema-location default \ + -schema-location "https://raw.githubusercontent.com/yannh/kubernetes-json-schema/master/{{.NormalizedKubernetesVersion}}/{{.ResourceKind}}.json" \ + -schema-location "https://raw.githubusercontent.com/datreeio/CRDs-catalog/main/{{.Group}}/{{.ResourceKind}}_{{.ResourceAPIVersion}}.json" \ + -strict + +shared_verify_targets_dirty += verify-helm-kubeconform diff --git a/make/base_images.mk b/make/base_images.mk index 45e94148717..8f50194a9e3 100644 --- a/make/base_images.mk +++ b/make/base_images.mk @@ -1,11 +1,11 @@ -# autogenerated by hack/latest-base-images.sh -STATIC_BASE_IMAGE_amd64 := gcr.io/distroless/static@sha256:f6ba6e4b2b5881fb94a99113de3c886c5f72e589946ece055dee2aade9486b8f -STATIC_BASE_IMAGE_arm64 := gcr.io/distroless/static@sha256:7f7f3b90d455ef2c1dfbe7bdfd2c3a33749d8cb91544e9676146636da775ce50 -STATIC_BASE_IMAGE_s390x := gcr.io/distroless/static@sha256:44787810ec7ff81a7659bed7daed722b640ba92b1217dbf86c5666f2024dfc09 -STATIC_BASE_IMAGE_arm := gcr.io/distroless/static@sha256:5b13d2ab3cff934fc44996b33818aa149001f8aead240d68208c81e0f359bfd0 -STATIC_BASE_IMAGE_ppc64le := gcr.io/distroless/static@sha256:c6c0c8c93600faa416e472b9c95e1e20eb8a85171680ce4bc872887781dda36c -DYNAMIC_BASE_IMAGE_amd64 := gcr.io/distroless/base@sha256:bf37ce66c1c295ef3e965ef273141e41c28866bdb28f54edb99b8596efd07564 -DYNAMIC_BASE_IMAGE_arm64 := gcr.io/distroless/base@sha256:11e438e3d4f7652cb64b16b0e2bbff2271f701f7b93bd61c7eb922503e4f44ab -DYNAMIC_BASE_IMAGE_s390x := gcr.io/distroless/base@sha256:1f3318430844e5fa43843beeec96d1070e45cdf41158d8687cc81f640bb077ab -DYNAMIC_BASE_IMAGE_arm := gcr.io/distroless/base@sha256:ace1935c55c879c8999acfd0ab55cd831b0a2a7f353f5c9b6141ea4afc6873c9 -DYNAMIC_BASE_IMAGE_ppc64le := gcr.io/distroless/base@sha256:087ba40e7c8cd82f702dc53178df9e872b41fe335520a51a320b578d282576ba +# +skip_license_check +STATIC_BASE_IMAGE_amd64 := gcr.io/distroless/static-debian12@sha256:6ceafbc2a9c566d66448fb1d5381dede2b29200d1916e03f5238a1c437e7d9ea +STATIC_BASE_IMAGE_arm64 := gcr.io/distroless/static-debian12@sha256:ed92139a33080a51ac2e0607c781a67fb3facf2e6b3b04a2238703d8bcf39c40 +STATIC_BASE_IMAGE_s390x := gcr.io/distroless/static-debian12@sha256:0f30716c69ea9a9f62484fe3b284300ae67d136135312ee6d0f794c470b4fa27 +STATIC_BASE_IMAGE_arm := gcr.io/distroless/static-debian12@sha256:9b9ebe0472d908cc5f8ca03e437dd82f0984cc254eee59effd52aa539fe8a3d8 +STATIC_BASE_IMAGE_ppc64le := gcr.io/distroless/static-debian12@sha256:17274770d835d14eddc4070a12bdbcf746991125b70acffbd65935d9d88ab2ac +DYNAMIC_BASE_IMAGE_amd64 := gcr.io/distroless/base-debian12@sha256:689fe8afc6077bec0fa17e674cce6525f9a230e01680d4112eda88e36e711012 +DYNAMIC_BASE_IMAGE_arm64 := gcr.io/distroless/base-debian12@sha256:d1fa441d279fb67b1d48ae7e36a9d2be56f6b55a835ffe741240e23ca7573ae7 +DYNAMIC_BASE_IMAGE_s390x := gcr.io/distroless/base-debian12@sha256:459ca2c7b6100bfed200811efaaed2964ebf4710918d31e71756d5d74d8b0759 +DYNAMIC_BASE_IMAGE_arm := gcr.io/distroless/base-debian12@sha256:f0f38e9d78351ba31073e23f70070acfdeaad7b36d07aa8ecea650aa26d5804b +DYNAMIC_BASE_IMAGE_ppc64le := gcr.io/distroless/base-debian12@sha256:3d994ef3b6f81e73d15865d539ae0b0fb6c4b2899f588d429c496011a69da7d1 diff --git a/make/ci.mk b/make/ci.mk index 7981bbe552a..a972c20030c 100644 --- a/make/ci.mk +++ b/make/ci.mk @@ -1,84 +1,84 @@ -.PHONY: ci-presubmit -## Run all checks (but not Go tests) which should pass before any given pull -## request or change is merged. -## -## @category CI -ci-presubmit: verify-imports verify-errexit verify-boilerplate verify-codegen verify-crds verify-licenses +# 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. + +.PHONY: verify-modules +verify-modules: | $(NEEDS_CMREL) + $(CMREL) validate-gomod --path $(shell pwd) --no-dummy-modules github.com/cert-manager/cert-manager/integration-tests -.PHONY: verify-imports -verify-imports: | $(NEEDS_GOIMPORTS) - ./hack/verify-goimports.sh $(GOIMPORTS) +shared_verify_targets += verify-modules .PHONY: verify-chart -verify-chart: $(BINDIR)/cert-manager-$(RELEASE_VERSION).tgz +verify-chart: $(bin_dir)/cert-manager-$(VERSION).tgz | $(NEEDS_CTR) DOCKER=$(CTR) ./hack/verify-chart-version.sh $< .PHONY: verify-errexit verify-errexit: ./hack/verify-errexit.sh -__PYTHON := python3 +shared_verify_targets += verify-errexit -.PHONY: verify-boilerplate -verify-boilerplate: - @command -v $(__PYTHON) >/dev/null || (echo "couldn't find python3 at '$(__PYTHON)', required for $@. Install python3 or set '__PYTHON'" && exit 1) - $(__PYTHON) hack/verify_boilerplate.py +.PHONY: generate-codegen +generate-codegen: | $(NEEDS_CLIENT-GEN) $(NEEDS_DEEPCOPY-GEN) $(NEEDS_INFORMER-GEN) $(NEEDS_LISTER-GEN) $(NEEDS_DEFAULTER-GEN) $(NEEDS_CONVERSION-GEN) $(NEEDS_OPENAPI-GEN) $(NEEDS_APPLYCONFIGURATION-GEN) + ./hack/k8s-codegen.sh \ + $(CLIENT-GEN) \ + $(DEEPCOPY-GEN) \ + $(INFORMER-GEN) \ + $(LISTER-GEN) \ + $(DEFAULTER-GEN) \ + $(CONVERSION-GEN) \ + $(OPENAPI-GEN) \ + $(APPLYCONFIGURATION-GEN) -.PHONY: verify-licenses -verify-licenses: $(BINDIR)/scratch/LATEST-LICENSES - @diff $(BINDIR)/scratch/LATEST-LICENSES LICENSES >/dev/null || (echo -e "\033[0;33mLICENSES seem to be out of date; update with 'make update-licenses'\033[0m" && exit 1) +shared_generate_targets_dirty += generate-codegen -.PHONY: verify-crds -verify-crds: | $(NEEDS_GO) $(NEEDS_CONTROLLER-GEN) $(NEEDS_YQ) - ./hack/check-crds.sh $(GO) $(CONTROLLER-GEN) $(YQ) +.PHONY: generate-helm-docs +generate-helm-docs: deploy/charts/cert-manager/README.template.md deploy/charts/cert-manager/values.yaml | $(NEEDS_HELM-TOOL) + $(HELM-TOOL) inject \ + --header-search '^' \ + --footer-search '^' \ + -i deploy/charts/cert-manager/values.yaml \ + -o deploy/charts/cert-manager/README.template.md -.PHONY: update-licenses -update-licenses: LICENSES +shared_generate_targets += generate-helm-docs -.PHONY: update-crds -update-crds: generate-test-crds patch-crds +.PHONY: generate-helm-schema +## Generate Helm chart schema. +## @category [shared] Generate/ Verify +generate-helm-schema: | $(NEEDS_HELM-TOOL) $(NEEDS_GOJQ) + $(HELM-TOOL) schema -i deploy/charts/cert-manager/values.yaml | $(GOJQ) > deploy/charts/cert-manager/values.schema.json -.PHONY: generate-test-crds -generate-test-crds: | $(NEEDS_CONTROLLER-GEN) - $(CONTROLLER-GEN) \ - crd \ - paths=./pkg/webhook/handlers/testdata/apis/testgroup/v{1,2}/... \ - output:crd:dir=./pkg/webhook/handlers/testdata/apis/testgroup/crds +shared_generate_targets += generate-helm-schema -PATCH_CRD_OUTPUT_DIR=./deploy/crds -.PHONY: patch-crds -patch-crds: | $(NEEDS_CONTROLLER-GEN) - $(CONTROLLER-GEN) \ - schemapatch:manifests=./deploy/crds \ - output:dir=$(PATCH_CRD_OUTPUT_DIR) \ - paths=./pkg/apis/... +.PHONY: verify-helm-values +## Verify Helm chart values using helm-tool. +## @category [shared] Generate/ Verify +verify-helm-values: | $(NEEDS_HELM-TOOL) $(NEEDS_GOJQ) + $(HELM-TOOL) lint -i deploy/charts/cert-manager/values.yaml -d deploy/charts/cert-manager/templates -e deploy/charts/cert-manager/values.linter.exceptions -.PHONY: verify-codegen -verify-codegen: | k8s-codegen-tools $(NEEDS_GO) - VERIFY_ONLY="true" ./hack/k8s-codegen.sh \ - $(GO) \ - ./$(BINDIR)/tools/client-gen \ - ./$(BINDIR)/tools/deepcopy-gen \ - ./$(BINDIR)/tools/informer-gen \ - ./$(BINDIR)/tools/lister-gen \ - ./$(BINDIR)/tools/defaulter-gen \ - ./$(BINDIR)/tools/conversion-gen +shared_verify_targets += verify-helm-values -.PHONY: update-codegen -update-codegen: | k8s-codegen-tools $(NEEDS_GO) - ./hack/k8s-codegen.sh \ - $(GO) \ - ./$(BINDIR)/tools/client-gen \ - ./$(BINDIR)/tools/deepcopy-gen \ - ./$(BINDIR)/tools/informer-gen \ - ./$(BINDIR)/tools/lister-gen \ - ./$(BINDIR)/tools/defaulter-gen \ - ./$(BINDIR)/tools/conversion-gen +.PHONY: ci-presubmit +## Run all checks (but not Go tests) which should pass before any given pull +## request or change is merged. +## +## @category CI +ci-presubmit: verify -.PHONY: update-all +.PHONY: generate-all ## Update CRDs, code generation and licenses to the latest versions. ## This is provided as a convenience to run locally before creating a PR, to ensure ## that everything is up-to-date. ## ## @category Development -update-all: update-crds update-codegen update-licenses +generate-all: generate diff --git a/make/cluster.sh b/make/cluster.sh index 5383d135640..2435ec4b3ed 100755 --- a/make/cluster.sh +++ b/make/cluster.sh @@ -25,8 +25,8 @@ set -e source ./make/kind_images.sh mode=kind -k8s_version=1.25 -kind_cluster_name=kind +k8s_version=1.34 +name=kind help() { cat <&2 @@ -94,22 +98,17 @@ while [ $# -ne 0 ]; do shift done +kind_cluster_name=${name} + if printenv K8S_VERSION >/dev/null && [ -n "$K8S_VERSION" ]; then k8s_version="$K8S_VERSION" fi -# TODO(SgtCoDFish): Using "KIND_IMAGE_FULL_*" here breaks podman, which doesn't support using both tags and digests -# when referring to an image. We should avoid using FULL where possible. - case "$k8s_version" in -1.18*) image=$KIND_IMAGE_FULL_K8S_118 ;; -1.19*) image=$KIND_IMAGE_FULL_K8S_119 ;; -1.20*) image=$KIND_IMAGE_FULL_K8S_120 ;; -1.21*) image=$KIND_IMAGE_FULL_K8S_121 ;; -1.22*) image=$KIND_IMAGE_FULL_K8S_122 ;; -1.23*) image=$KIND_IMAGE_FULL_K8S_123 ;; -1.24*) image=$KIND_IMAGE_FULL_K8S_124 ;; -1.25*) image=$KIND_IMAGE_FULL_K8S_125 ;; +1.31*) image=$KIND_IMAGE_K8S_131 ;; +1.32*) image=$KIND_IMAGE_K8S_132 ;; +1.33*) image=$KIND_IMAGE_K8S_133 ;; +1.34*) image=$KIND_IMAGE_K8S_134 ;; v*) printf "${red}${redcross}Error${end}: Kubernetes version must be given without the leading 'v'\n" >&2 && exit 1 ;; *) printf "${red}${redcross}Error${end}: unsupported Kubernetes version ${yel}${k8s_version}${end}\n" >&2 && exit 1 ;; esac @@ -120,26 +119,6 @@ if [ -n "$show_image" ]; then fi setup_kind() { - # When running in our CI environment the Docker network's subnet choice will - # cause issues with routing, which can manifest in errors such as this one: - # - # dial tcp: lookup charts.jetstack.io on 10.8.240.10:53: read udp 10.8.0.2:54823->10.8.240.10:53: i/o timeout - # - # as seen in the build [1]. We create this custom network as a workaround - # until we have a way to properly patch this. - # - # [1]: https://prow.build-infra.jetstack.net/view/gs/jetstack-logs/pr-logs/pull/cert-manager_approver-policy/36/pull-cert-manager-approver-policy-smoke/1447565895923666944#1:build-log.txt%3A222 - if printenv CI >/dev/null; then - if ! docker network inspect kind >/dev/null 2>&1; then - docker network create --driver=bridge --subnet=192.168.0.0/16 --gateway 192.168.0.1 kind - fi - - # Wait for the network to be created so kind does not overwrite it. - while ! docker network inspect kind >/dev/null; do - sleep 100ms - done - fi - # (1) Does the kind cluster already exist? if ! kind get clusters -q | grep -q "^$kind_cluster_name\$"; then trace kind create cluster --config "make/config/kind/cluster.yaml" \ diff --git a/make/cmctl.mk b/make/cmctl.mk deleted file mode 100644 index b80a5ddb62c..00000000000 --- a/make/cmctl.mk +++ /dev/null @@ -1,227 +0,0 @@ -CMCTL_GOLDFLAGS := $(GOLDFLAGS) -X "github.com/cert-manager/cert-manager/cmd/ctl/pkg/build.name=cmctl" -X "github.com/cert-manager/cert-manager/cmd/ctl/pkg/build/commands.registerCompletion=true" - -KUBECTL_PLUGIN_GOLDFLAGS := $(GOLDFLAGS) -X "github.com/cert-manager/cert-manager/cmd/ctl/pkg/build.name=kubectl cert-manager" -X "github.com/cert-manager/cert-manager/cmd/ctl/pkg/build/commands.registerCompletion=false" - -$(BINDIR)/cmctl: - @mkdir -p $@ - -$(BINDIR)/kubectl-cert_manager: - @mkdir -p $@ - -.PHONY: cmctl -cmctl: cmctl-linux cmctl-linux-tarballs cmctl-linux-metadata cmctl-darwin cmctl-darwin-tarballs cmctl-darwin-metadata cmctl-windows cmctl-windows-tarballs cmctl-windows-metadata | $(BINDIR)/cmctl - -.PHONY: cmctl-linux -cmctl-linux: $(BINDIR)/cmctl/cmctl-linux-amd64 $(BINDIR)/cmctl/cmctl-linux-arm64 $(BINDIR)/cmctl/cmctl-linux-s390x $(BINDIR)/cmctl/cmctl-linux-ppc64le $(BINDIR)/cmctl/cmctl-linux-arm | $(BINDIR)/cmctl - -.PHONY: cmctl-linux-tarballs -cmctl-linux-tarballs: $(BINDIR)/release/cert-manager-cmctl-linux-amd64.tar.gz $(BINDIR)/release/cert-manager-cmctl-linux-arm64.tar.gz $(BINDIR)/release/cert-manager-cmctl-linux-s390x.tar.gz $(BINDIR)/release/cert-manager-cmctl-linux-ppc64le.tar.gz $(BINDIR)/release/cert-manager-cmctl-linux-arm.tar.gz | $(BINDIR)/release - -.PHONY: cmctl-linux-metadata -cmctl-linux-metadata: $(BINDIR)/metadata/cert-manager-cmctl-linux-amd64.tar.gz.metadata.json $(BINDIR)/metadata/cert-manager-cmctl-linux-arm64.tar.gz.metadata.json $(BINDIR)/metadata/cert-manager-cmctl-linux-s390x.tar.gz.metadata.json $(BINDIR)/metadata/cert-manager-cmctl-linux-ppc64le.tar.gz.metadata.json $(BINDIR)/metadata/cert-manager-cmctl-linux-arm.tar.gz.metadata.json | $(BINDIR)/metadata - -$(BINDIR)/cmctl/cmctl-linux-amd64 $(BINDIR)/cmctl/cmctl-linux-arm64 $(BINDIR)/cmctl/cmctl-linux-s390x $(BINDIR)/cmctl/cmctl-linux-ppc64le: $(BINDIR)/cmctl/cmctl-linux-%: $(SOURCES) | $(NEEDS_GO) $(BINDIR)/cmctl - GOOS=linux GOARCH=$* $(GOBUILD) -o $@ $(GOFLAGS) -ldflags '$(CMCTL_GOLDFLAGS)' cmd/ctl/main.go - -$(BINDIR)/cmctl/cmctl-linux-arm: $(SOURCES) | $(NEEDS_GO) $(BINDIR)/cmctl - GOOS=linux GOARCH=arm GOARM=7 $(GOBUILD) -o $@ $(GOFLAGS) -ldflags '$(CMCTL_GOLDFLAGS)' cmd/ctl/main.go - -$(BINDIR)/release/cert-manager-cmctl-linux-amd64.tar.gz $(BINDIR)/release/cert-manager-cmctl-linux-arm64.tar.gz $(BINDIR)/release/cert-manager-cmctl-linux-s390x.tar.gz $(BINDIR)/release/cert-manager-cmctl-linux-ppc64le.tar.gz $(BINDIR)/release/cert-manager-cmctl-linux-arm.tar.gz: $(BINDIR)/release/cert-manager-cmctl-linux-%.tar.gz: $(BINDIR)/cmctl/cmctl-linux-% $(BINDIR)/scratch/cert-manager.license | $(BINDIR)/scratch $(BINDIR)/release - @$(eval TARDIR := $(BINDIR)/scratch/$(notdir $@)) - mkdir -p $(TARDIR) - cp $< $(TARDIR)/cmctl - cp $(BINDIR)/scratch/cert-manager.license $(TARDIR)/LICENSE - # removes leading ./ from archived paths - find $(TARDIR) -maxdepth 1 -mindepth 1 | sed 's|.*/||' | tar czf $@ -C $(TARDIR) -T - - rm -rf $(TARDIR) - -$(BINDIR)/metadata/cert-manager-cmctl-linux-amd64.tar.gz.metadata.json $(BINDIR)/metadata/cert-manager-cmctl-linux-arm64.tar.gz.metadata.json $(BINDIR)/metadata/cert-manager-cmctl-linux-s390x.tar.gz.metadata.json $(BINDIR)/metadata/cert-manager-cmctl-linux-ppc64le.tar.gz.metadata.json $(BINDIR)/metadata/cert-manager-cmctl-linux-arm.tar.gz.metadata.json: $(BINDIR)/metadata/cert-manager-cmctl-linux-%.tar.gz.metadata.json: $(BINDIR)/release/cert-manager-cmctl-linux-%.tar.gz hack/artifact-metadata.template.json | $(BINDIR)/metadata - jq --arg name "$(notdir $<)" \ - --arg sha256 "$(shell ./hack/util/hash.sh $<)" \ - --arg os "linux" \ - --arg architecture "$*" \ - '.name = $$name | .sha256 = $$sha256 | .os = $$os | .architecture = $$architecture' \ - hack/artifact-metadata.template.json > $@ - -.PHONY: cmctl-darwin -cmctl-darwin: $(BINDIR)/cmctl/cmctl-darwin-amd64 $(BINDIR)/cmctl/cmctl-darwin-arm64 | $(BINDIR)/cmctl - -.PHONY: cmctl-darwin-tarballs -cmctl-darwin-tarballs: $(BINDIR)/release/cert-manager-cmctl-darwin-amd64.tar.gz $(BINDIR)/release/cert-manager-cmctl-darwin-arm64.tar.gz | $(BINDIR)/release - -.PHONY: cmctl-darwin-metadata -cmctl-darwin-metadata: $(BINDIR)/metadata/cert-manager-cmctl-darwin-amd64.tar.gz.metadata.json $(BINDIR)/metadata/cert-manager-cmctl-darwin-arm64.tar.gz.metadata.json | $(BINDIR)/metadata - -$(BINDIR)/cmctl/cmctl-darwin-amd64 $(BINDIR)/cmctl/cmctl-darwin-arm64: $(BINDIR)/cmctl/cmctl-darwin-%: $(SOURCES) | $(NEEDS_GO) $(BINDIR)/cmctl - GOOS=darwin GOARCH=$* $(GOBUILD) -o $@ $(GOFLAGS) -ldflags '$(CMCTL_GOLDFLAGS)' cmd/ctl/main.go - -$(BINDIR)/release/cert-manager-cmctl-darwin-amd64.tar.gz $(BINDIR)/release/cert-manager-cmctl-darwin-arm64.tar.gz: $(BINDIR)/release/cert-manager-cmctl-darwin-%.tar.gz: $(BINDIR)/cmctl/cmctl-darwin-% $(BINDIR)/scratch/cert-manager.license | $(BINDIR)/scratch $(BINDIR)/release - @$(eval TARDIR := $(BINDIR)/scratch/$(notdir $@)) - mkdir -p $(TARDIR) - cp $< $(TARDIR)/cmctl - cp $(BINDIR)/scratch/cert-manager.license $(TARDIR)/LICENSE - # removes leading ./ from archived paths - find $(TARDIR) -maxdepth 1 -mindepth 1 | sed 's|.*/||' | tar czf $@ -C $(TARDIR) -T - - rm -rf $(TARDIR) - -$(BINDIR)/metadata/cert-manager-cmctl-darwin-amd64.tar.gz.metadata.json $(BINDIR)/metadata/cert-manager-cmctl-darwin-arm64.tar.gz.metadata.json: $(BINDIR)/metadata/cert-manager-cmctl-darwin-%.tar.gz.metadata.json: $(BINDIR)/release/cert-manager-cmctl-darwin-%.tar.gz hack/artifact-metadata.template.json | $(BINDIR)/metadata - jq --arg name "$(notdir $<)" \ - --arg sha256 "$(shell ./hack/util/hash.sh $<)" \ - --arg os "darwin" \ - --arg architecture "$*" \ - '.name = $$name | .sha256 = $$sha256 | .os = $$os | .architecture = $$architecture' \ - hack/artifact-metadata.template.json > $@ - -.PHONY: cmctl-windows -cmctl-windows: $(BINDIR)/cmctl/cmctl-windows-amd64.exe | $(BINDIR)/cmctl - -.PHONY: cmctl-windows-tarballs -cmctl-windows-tarballs: $(BINDIR)/release/cert-manager-cmctl-windows-amd64.tar.gz $(BINDIR)/release/cert-manager-cmctl-windows-amd64.zip | $(BINDIR)/release - -.PHONY: cmctl-windows-metadata -cmctl-windows-metadata: $(BINDIR)/metadata/cert-manager-cmctl-windows-amd64.tar.gz.metadata.json $(BINDIR)/metadata/cert-manager-cmctl-windows-amd64.zip.metadata.json | $(BINDIR)/release - -$(BINDIR)/cmctl/cmctl-windows-amd64.exe: $(SOURCES) | $(NEEDS_GO) $(BINDIR)/cmctl - GOOS=windows GOARCH=amd64 $(GOBUILD) -o $@ $(GOFLAGS) -ldflags '$(CMCTL_GOLDFLAGS)' cmd/ctl/main.go - -$(BINDIR)/release/cert-manager-cmctl-windows-amd64.zip: $(BINDIR)/cmctl/cmctl-windows-amd64.exe $(BINDIR)/scratch/cert-manager.license | $(BINDIR)/scratch $(BINDIR)/release - @$(eval TARDIR := $(BINDIR)/scratch/$(notdir $@)) - mkdir -p $(TARDIR) - cp $< $(TARDIR)/cmctl.exe - cp $(BINDIR)/scratch/cert-manager.license $(TARDIR)/LICENSE - pushd $(TARDIR) && zip -r $(notdir $@) . && popd && mv $(TARDIR)/$(notdir $@) $@ - rm -rf $(TARDIR) - -$(BINDIR)/release/cert-manager-cmctl-windows-amd64.tar.gz: $(BINDIR)/cmctl/cmctl-windows-amd64.exe $(BINDIR)/scratch/cert-manager.license | $(BINDIR)/scratch $(BINDIR)/release - @$(eval TARDIR := $(BINDIR)/scratch/$(notdir $@)) - mkdir -p $(TARDIR) - cp $< $(TARDIR)/cmctl.exe - cp $(BINDIR)/scratch/cert-manager.license $(TARDIR)/LICENSE - # removes leading ./ from archived paths - find $(TARDIR) -maxdepth 1 -mindepth 1 | sed 's|.*/||' | tar czf $@ -C $(TARDIR) -T - - rm -rf $(TARDIR) - -$(BINDIR)/metadata/cert-manager-cmctl-windows-amd64.tar.gz.metadata.json: $(BINDIR)/release/cert-manager-cmctl-windows-amd64.tar.gz hack/artifact-metadata.template.json | $(BINDIR)/metadata - jq --arg name "$(notdir $<)" \ - --arg sha256 "$(shell ./hack/util/hash.sh $<)" \ - --arg os "windows" \ - --arg architecture "amd64" \ - '.name = $$name | .sha256 = $$sha256 | .os = $$os | .architecture = $$architecture' \ - hack/artifact-metadata.template.json > $@ - -$(BINDIR)/metadata/cert-manager-cmctl-windows-amd64.zip.metadata.json: $(BINDIR)/release/cert-manager-cmctl-windows-amd64.zip hack/artifact-metadata.template.json | $(BINDIR)/metadata - jq --arg name "$(notdir $<)" \ - --arg sha256 "$(shell ./hack/util/hash.sh $<)" \ - --arg os "windows" \ - --arg architecture "amd64" \ - '.name = $$name | .sha256 = $$sha256 | .os = $$os | .architecture = $$architecture' \ - hack/artifact-metadata.template.json > $@ - -.PHONY: kubectl-cert_manager -kubectl-cert_manager: kubectl-cert_manager-linux kubectl-cert_manager-linux-tarballs kubectl-cert_manager-linux-metadata kubectl-cert_manager-darwin kubectl-cert_manager-darwin-tarballs kubectl-cert_manager-darwin-metadata kubectl-cert_manager-windows kubectl-cert_manager-windows-tarballs kubectl-cert_manager-windows-metadata | $(BINDIR)/kubectl-cert_manager - -.PHONY: kubectl-cert_manager-linux -kubectl-cert_manager-linux: $(BINDIR)/kubectl-cert_manager/kubectl-cert_manager-linux-amd64 $(BINDIR)/kubectl-cert_manager/kubectl-cert_manager-linux-arm64 $(BINDIR)/kubectl-cert_manager/kubectl-cert_manager-linux-s390x $(BINDIR)/kubectl-cert_manager/kubectl-cert_manager-linux-ppc64le $(BINDIR)/kubectl-cert_manager/kubectl-cert_manager-linux-arm | $(BINDIR)/kubectl-cert_manager - -.PHONY: kubectl-cert_manager-linux-tarballs -kubectl-cert_manager-linux-tarballs: $(BINDIR)/release/cert-manager-kubectl-cert_manager-linux-amd64.tar.gz $(BINDIR)/release/cert-manager-kubectl-cert_manager-linux-arm64.tar.gz $(BINDIR)/release/cert-manager-kubectl-cert_manager-linux-s390x.tar.gz $(BINDIR)/release/cert-manager-kubectl-cert_manager-linux-ppc64le.tar.gz $(BINDIR)/release/cert-manager-kubectl-cert_manager-linux-arm.tar.gz | $(BINDIR)/release - -.PHONY: kubectl-cert_manager-linux-metadata -kubectl-cert_manager-linux-metadata: $(BINDIR)/metadata/cert-manager-kubectl-cert_manager-linux-amd64.tar.gz.metadata.json $(BINDIR)/metadata/cert-manager-kubectl-cert_manager-linux-arm64.tar.gz.metadata.json $(BINDIR)/metadata/cert-manager-kubectl-cert_manager-linux-s390x.tar.gz.metadata.json $(BINDIR)/metadata/cert-manager-kubectl-cert_manager-linux-ppc64le.tar.gz.metadata.json $(BINDIR)/metadata/cert-manager-kubectl-cert_manager-linux-arm.tar.gz.metadata.json | $(BINDIR)/metadata - -$(BINDIR)/kubectl-cert_manager/kubectl-cert_manager-linux-amd64 $(BINDIR)/kubectl-cert_manager/kubectl-cert_manager-linux-arm64 $(BINDIR)/kubectl-cert_manager/kubectl-cert_manager-linux-s390x $(BINDIR)/kubectl-cert_manager/kubectl-cert_manager-linux-ppc64le: $(BINDIR)/kubectl-cert_manager/kubectl-cert_manager-linux-%: $(SOURCES) | $(NEEDS_GO) $(BINDIR)/kubectl-cert_manager - GOOS=linux GOARCH=$* $(GOBUILD) -o $@ $(GOFLAGS) -ldflags '$(KUBECTL_PLUGIN_GOLDFLAGS)' cmd/ctl/main.go - -$(BINDIR)/kubectl-cert_manager/kubectl-cert_manager-linux-arm: $(SOURCES) | $(NEEDS_GO) $(BINDIR)/kubectl-cert_manager - GOOS=linux GOARCH=arm GOARM=7 $(GOBUILD) -o $@ $(GOFLAGS) -ldflags '$(KUBECTL_PLUGIN_GOLDFLAGS)' cmd/ctl/main.go - -$(BINDIR)/release/cert-manager-kubectl-cert_manager-linux-amd64.tar.gz $(BINDIR)/release/cert-manager-kubectl-cert_manager-linux-arm64.tar.gz $(BINDIR)/release/cert-manager-kubectl-cert_manager-linux-s390x.tar.gz $(BINDIR)/release/cert-manager-kubectl-cert_manager-linux-ppc64le.tar.gz $(BINDIR)/release/cert-manager-kubectl-cert_manager-linux-arm.tar.gz: $(BINDIR)/release/cert-manager-kubectl-cert_manager-linux-%.tar.gz: $(BINDIR)/kubectl-cert_manager/kubectl-cert_manager-linux-% $(BINDIR)/scratch/cert-manager.license | $(BINDIR)/scratch $(BINDIR)/release - @$(eval TARDIR := $(BINDIR)/scratch/$(notdir $@)) - mkdir -p $(TARDIR) - cp $< $(TARDIR)/kubectl-cert_manager - cp $(BINDIR)/scratch/cert-manager.license $(TARDIR)/LICENSE - # removes leading ./ from archived paths - find $(TARDIR) -maxdepth 1 -mindepth 1 | sed 's|.*/||' | tar czf $@ -C $(TARDIR) -T - - rm -rf $(TARDIR) - -$(BINDIR)/metadata/cert-manager-kubectl-cert_manager-linux-amd64.tar.gz.metadata.json $(BINDIR)/metadata/cert-manager-kubectl-cert_manager-linux-arm64.tar.gz.metadata.json $(BINDIR)/metadata/cert-manager-kubectl-cert_manager-linux-s390x.tar.gz.metadata.json $(BINDIR)/metadata/cert-manager-kubectl-cert_manager-linux-ppc64le.tar.gz.metadata.json $(BINDIR)/metadata/cert-manager-kubectl-cert_manager-linux-arm.tar.gz.metadata.json: $(BINDIR)/metadata/cert-manager-kubectl-cert_manager-linux-%.tar.gz.metadata.json: $(BINDIR)/release/cert-manager-kubectl-cert_manager-linux-%.tar.gz hack/artifact-metadata.template.json | $(BINDIR)/metadata - jq --arg name "$(notdir $<)" \ - --arg sha256 "$(shell ./hack/util/hash.sh $<)" \ - --arg os "linux" \ - --arg architecture "$*" \ - '.name = $$name | .sha256 = $$sha256 | .os = $$os | .architecture = $$architecture' \ - hack/artifact-metadata.template.json > $@ - -.PHONY: kubectl-cert_manager-darwin -kubectl-cert_manager-darwin: $(BINDIR)/kubectl-cert_manager/kubectl-cert_manager-darwin-amd64 $(BINDIR)/kubectl-cert_manager/kubectl-cert_manager-darwin-arm64 | $(BINDIR)/kubectl-cert_manager - -.PHONY: kubectl-cert_manager-darwin-tarballs -kubectl-cert_manager-darwin-tarballs: $(BINDIR)/release/cert-manager-kubectl-cert_manager-darwin-amd64.tar.gz $(BINDIR)/release/cert-manager-kubectl-cert_manager-darwin-arm64.tar.gz | $(BINDIR)/release - -.PHONY: kubectl-cert_manager-darwin-metadata -kubectl-cert_manager-darwin-metadata: $(BINDIR)/metadata/cert-manager-kubectl-cert_manager-darwin-amd64.tar.gz.metadata.json $(BINDIR)/metadata/cert-manager-kubectl-cert_manager-darwin-arm64.tar.gz.metadata.json | $(BINDIR)/metadata - -$(BINDIR)/kubectl-cert_manager/kubectl-cert_manager-darwin-amd64 $(BINDIR)/kubectl-cert_manager/kubectl-cert_manager-darwin-arm64: $(BINDIR)/kubectl-cert_manager/kubectl-cert_manager-darwin-%: $(SOURCES) | $(NEEDS_GO) $(BINDIR)/kubectl-cert_manager - GOOS=darwin GOARCH=$* $(GOBUILD) -o $@ $(GOFLAGS) -ldflags '$(KUBECTL_PLUGIN_GOLDFLAGS)' cmd/ctl/main.go - -$(BINDIR)/release/cert-manager-kubectl-cert_manager-darwin-amd64.tar.gz $(BINDIR)/release/cert-manager-kubectl-cert_manager-darwin-arm64.tar.gz: $(BINDIR)/release/cert-manager-kubectl-cert_manager-darwin-%.tar.gz: $(BINDIR)/kubectl-cert_manager/kubectl-cert_manager-darwin-% $(BINDIR)/scratch/cert-manager.license | $(BINDIR)/scratch $(BINDIR)/release - @$(eval TARDIR := $(BINDIR)/scratch/$(notdir $@)) - mkdir -p $(TARDIR) - cp $< $(TARDIR)/kubectl-cert_manager - cp $(BINDIR)/scratch/cert-manager.license $(TARDIR)/LICENSE - # removes leading ./ from archived paths - find $(TARDIR) -maxdepth 1 -mindepth 1 | sed 's|.*/||' | tar czf $@ -C $(TARDIR) -T - - rm -rf $(TARDIR) - -$(BINDIR)/metadata/cert-manager-kubectl-cert_manager-darwin-amd64.tar.gz.metadata.json $(BINDIR)/metadata/cert-manager-kubectl-cert_manager-darwin-arm64.tar.gz.metadata.json: $(BINDIR)/metadata/cert-manager-kubectl-cert_manager-darwin-%.tar.gz.metadata.json: $(BINDIR)/release/cert-manager-kubectl-cert_manager-darwin-%.tar.gz hack/artifact-metadata.template.json | $(BINDIR)/metadata - jq --arg name "$(notdir $<)" \ - --arg sha256 "$(shell ./hack/util/hash.sh $<)" \ - --arg os "darwin" \ - --arg architecture "$*" \ - '.name = $$name | .sha256 = $$sha256 | .os = $$os | .architecture = $$architecture' \ - hack/artifact-metadata.template.json > $@ - -.PHONY: kubectl-cert_manager-windows -kubectl-cert_manager-windows: $(BINDIR)/kubectl-cert_manager/kubectl-cert_manager-windows-amd64.exe | $(BINDIR)/kubectl-cert_manager - -.PHONY: kubectl-cert_manager-windows-tarballs -kubectl-cert_manager-windows-tarballs: $(BINDIR)/release/cert-manager-kubectl-cert_manager-windows-amd64.tar.gz $(BINDIR)/release/cert-manager-kubectl-cert_manager-windows-amd64.zip | $(BINDIR)/release - -.PHONY: kubectl-cert_manager-windows-metadata -kubectl-cert_manager-windows-metadata: $(BINDIR)/metadata/cert-manager-kubectl-cert_manager-windows-amd64.tar.gz.metadata.json $(BINDIR)/metadata/cert-manager-kubectl-cert_manager-windows-amd64.zip.metadata.json | $(BINDIR)/release - -$(BINDIR)/kubectl-cert_manager/kubectl-cert_manager-windows-amd64.exe: $(SOURCES) | $(NEEDS_GO) $(BINDIR)/kubectl-cert_manager - GOOS=windows GOARCH=amd64 $(GOBUILD) -o $@ $(GOFLAGS) -ldflags '$(KUBECTL_PLUGIN_GOLDFLAGS)' cmd/ctl/main.go - -$(BINDIR)/release/cert-manager-kubectl-cert_manager-windows-amd64.zip: $(BINDIR)/kubectl-cert_manager/kubectl-cert_manager-windows-amd64.exe $(BINDIR)/scratch/cert-manager.license | $(BINDIR)/scratch $(BINDIR)/release - @$(eval TARDIR := $(BINDIR)/scratch/$(notdir $@)) - mkdir -p $(TARDIR) - cp $< $(TARDIR)/kubectl-cert_manager.exe - cp $(BINDIR)/scratch/cert-manager.license $(TARDIR)/LICENSE - pushd $(TARDIR) && zip -r $(notdir $@) . && popd && mv $(TARDIR)/$(notdir $@) $@ - rm -rf $(TARDIR) - -$(BINDIR)/release/cert-manager-kubectl-cert_manager-windows-amd64.tar.gz: $(BINDIR)/kubectl-cert_manager/kubectl-cert_manager-windows-amd64.exe $(BINDIR)/scratch/cert-manager.license | $(BINDIR)/scratch $(BINDIR)/release - @$(eval TARDIR := $(BINDIR)/scratch/$(notdir $@)) - mkdir -p $(TARDIR) - cp $< $(TARDIR)/kubectl-cert_manager.exe - cp $(BINDIR)/scratch/cert-manager.license $(TARDIR)/LICENSE - # removes leading ./ from archived paths - find $(TARDIR) -maxdepth 1 -mindepth 1 | sed 's|.*/||' | tar czf $@ -C $(TARDIR) -T - - rm -rf $(TARDIR) - -$(BINDIR)/metadata/cert-manager-kubectl-cert_manager-windows-amd64.tar.gz.metadata.json: $(BINDIR)/release/cert-manager-kubectl-cert_manager-windows-amd64.tar.gz hack/artifact-metadata.template.json | $(BINDIR)/metadata - jq --arg name "$(notdir $<)" \ - --arg sha256 "$(shell ./hack/util/hash.sh $<)" \ - --arg os "windows" \ - --arg architecture "amd64" \ - '.name = $$name | .sha256 = $$sha256 | .os = $$os | .architecture = $$architecture' \ - hack/artifact-metadata.template.json > $@ - -$(BINDIR)/metadata/cert-manager-kubectl-cert_manager-windows-amd64.zip.metadata.json: $(BINDIR)/release/cert-manager-kubectl-cert_manager-windows-amd64.zip hack/artifact-metadata.template.json | $(BINDIR)/metadata - jq --arg name "$(notdir $<)" \ - --arg sha256 "$(shell ./hack/util/hash.sh $<)" \ - --arg os "windows" \ - --arg architecture "amd64" \ - '.name = $$name | .sha256 = $$sha256 | .os = $$os | .architecture = $$architecture' \ - hack/artifact-metadata.template.json > $@ diff --git a/make/config/bind/configmap.yaml b/make/config/bind/configmap.yaml index fef8330413d..6480daaa0a3 100644 --- a/make/config/bind/configmap.yaml +++ b/make/config/bind/configmap.yaml @@ -9,8 +9,11 @@ data: directory "/var/cache/bind"; dnssec-validation auto; auth-nxdomain no; # conform to RFC1035 - listen-on { any; }; + listen-on port 8053 { any; }; + max-cache-size 192m; }; + + controls { }; zone "http01.example.com" { type master; diff --git a/make/config/bind/deployment.yaml b/make/config/bind/deployment.yaml index eeef15a19d4..3f8cb592953 100644 --- a/make/config/bind/deployment.yaml +++ b/make/config/bind/deployment.yaml @@ -14,37 +14,42 @@ spec: labels: app: bind spec: + securityContext: + # 101 is the user id of the bind user + runAsUser: 101 + runAsGroup: 101 + fsGroup: 101 containers: - name: bind image: "{IMAGE}" imagePullPolicy: Never - # TODO(wallrj): I couldn't figure out how to run Bind as a non-root user, using this Docker image. - # I think bind expects to start as root and then chown to a non-root BIND user. - # securityContext: - # runAsNonRoot: true command: - /bin/bash - -c - | - rm -rf /etc/bind - mkdir -p /etc/bind - ls -lah /config/ cp -Lr /config/* /etc/bind/ - chown -R "${BIND_USER}:${BIND_USER}" /etc/bind - exec $(which named) -u ${BIND_USER} -g - env: - - name: WEBMIN_ENABLED - value: "false" + exec $(which named) -u bind -g -4 ports: - - containerPort: 53 + - containerPort: 8053 protocol: UDP volumeMounts: - mountPath: /config - name: data + name: config + readOnly: true + - name: tmpfs-volume + mountPath: /etc/bind/ + resources: + requests: + cpu: 10m + memory: 256Mi + limits: + memory: 256Mi volumes: - - name: data + - name: config configMap: name: bind + - name: tmpfs-volume + emptyDir: {} dnsConfig: options: - name: ndots diff --git a/make/config/bind/service.yaml b/make/config/bind/service.yaml index 063dfb3fba3..379eda6168e 100644 --- a/make/config/bind/service.yaml +++ b/make/config/bind/service.yaml @@ -11,7 +11,7 @@ spec: clusterIP: {SERVICE_IP_PREFIX}.16 ports: - port: 53 - targetPort: 53 + targetPort: 8053 protocol: UDP selector: app: bind diff --git a/make/config/kind/cluster.yaml b/make/config/kind/cluster.yaml index 992b7c8de4e..11283925a99 100644 --- a/make/config/kind/cluster.yaml +++ b/make/config/kind/cluster.yaml @@ -15,6 +15,14 @@ # various addon Services such as ingress-nginx, Gateway etc. # TODO: parameterize the service subnet range instead of hardcoding it so that it is defined in one place only # It could be interpolated with ytt like for addons i.e https://github.com/cert-manager/cert-manager/blob/134398e939bb2b1401697eaf589405ad469cd609/make/e2e-setup.mk#L379 +# +# TIP: If you are running kind on a computer with corporate MITM VPN, you can add +# the MITM certs to the kind trust store by adding these extra mounts to the control-plane node: +# nodes: +# - role: control-plane +# extraMounts: +# - hostPath: /etc/ssl/certs +# containerPath: /etc/ssl/certs apiVersion: kind.x-k8s.io/v1alpha4 kind: Cluster diff --git a/make/config/kyverno/kustomization.yaml b/make/config/kyverno/kustomization.yaml index b38b9ac4eb4..32ed09d4601 100644 --- a/make/config/kyverno/kustomization.yaml +++ b/make/config/kyverno/kustomization.yaml @@ -1,17 +1,41 @@ -# This Kustomization is used to adapt the upstream Pod security policy for use -# specifically in the cert-manager namespace. -# Changes ClusterPolicy resources to namespaced Policy. +# This Kustomization is used to adapt the Kyverno policies downloaded from +# https://kyverno.io/policies/, for use in the cert-manager +# namespace and in the E2E test namespaces. +# +# * Changes the failure action of all ClusterPolicy resources from Audit to Enforce. +# * Adds exclude` fields to all ClusterPolicy resources to allow the +# installation of non-compliant E2E test components such as ingress-nginx and +# pebble. +# The method used is a bit of a hack, because it is difficult to get Kustomize +# to patch **all** the rules in the Kyverno ClusterPolicy custom resource. +# See https://github.com/kyverno/kyverno/issues/2408#issuecomment-1125926525 +# # Use as follows: # kustomize build . > policy.yaml -bases: +# +resources: - https://github.com/kyverno/policies/pod-security/enforce + - https://github.com/kyverno/policies/raw/main/other/restrict-automount-sa-token/restrict-automount-sa-token.yaml + - https://github.com/kyverno/policies/raw/main/best-practices/require-ro-rootfs/require-ro-rootfs.yaml + patches: - - patch: |- + - target: + kind: ClusterPolicy + patch: |- - op: replace - path: /kind - value: Policy + path: /spec/validationFailureAction + value: Enforce - op: add - path: /metadata/namespace - value: cert-manager - target: - kind: ClusterPolicy + path: /spec/rules/0/exclude + value: + resources: + namespaces: + - bind + - e2e-vault + - e2e-vault-mtls + - gateway-system + - ingress-nginx + - pebble + - projectcontour + - sample-external-issuer-system + - samplewebhook diff --git a/make/config/kyverno/policy.yaml b/make/config/kyverno/policy.yaml index 7d3727e2c66..fb53096e56b 100644 --- a/make/config/kyverno/policy.yaml +++ b/make/config/kyverno/policy.yaml @@ -1,5 +1,5 @@ apiVersion: kyverno.io/v1 -kind: Policy +kind: ClusterPolicy metadata: annotations: kyverno.io/kubernetes-version: 1.22-1.23 @@ -12,16 +12,32 @@ metadata: policies.kyverno.io/subject: Pod policies.kyverno.io/title: Disallow Capabilities name: disallow-capabilities - namespace: cert-manager spec: background: true rules: - - match: + - exclude: + resources: + namespaces: + - bind + - e2e-vault + - e2e-vault-mtls + - gateway-system + - ingress-nginx + - pebble + - projectcontour + - sample-external-issuer-system + - samplewebhook + match: any: - resources: kinds: - Pod name: adding-capabilities + preconditions: + all: + - key: '{{ request.operation || ''BACKGROUND'' }}' + operator: NotEquals + value: DELETE validate: deny: conditions: @@ -46,10 +62,10 @@ spec: message: Any capabilities added beyond the allowed list (AUDIT_WRITE, CHOWN, DAC_OVERRIDE, FOWNER, FSETID, KILL, MKNOD, NET_BIND_SERVICE, SETFCAP, SETGID, SETPCAP, SETUID, SYS_CHROOT) are disallowed. - validationFailureAction: enforce + validationFailureAction: Enforce --- apiVersion: kyverno.io/v1 -kind: Policy +kind: ClusterPolicy metadata: annotations: kyverno.io/kubernetes-version: 1.22-1.23 @@ -62,11 +78,22 @@ metadata: policies.kyverno.io/subject: Pod policies.kyverno.io/title: Disallow Capabilities (Strict) name: disallow-capabilities-strict - namespace: cert-manager spec: background: true rules: - - match: + - exclude: + resources: + namespaces: + - bind + - e2e-vault + - e2e-vault-mtls + - gateway-system + - ingress-nginx + - pebble + - projectcontour + - sample-external-issuer-system + - samplewebhook + match: any: - resources: kinds: @@ -74,7 +101,7 @@ spec: name: require-drop-all preconditions: all: - - key: '{{ request.operation }}' + - key: '{{ request.operation || ''BACKGROUND'' }}' operator: NotEquals value: DELETE validate: @@ -84,7 +111,7 @@ spec: all: - key: ALL operator: AnyNotIn - value: '{{ element.securityContext.capabilities.drop || '''' }}' + value: '{{ element.securityContext.capabilities.drop[] || `[]` }}' list: request.object.spec.[ephemeralContainers, initContainers, containers][] message: Containers must drop `ALL` capabilities. - match: @@ -95,7 +122,7 @@ spec: name: adding-capabilities-strict preconditions: all: - - key: '{{ request.operation }}' + - key: '{{ request.operation || ''BACKGROUND'' }}' operator: NotEquals value: DELETE validate: @@ -103,17 +130,17 @@ spec: - deny: conditions: all: - - key: '{{ element.securityContext.capabilities.add[] || '''' }}' + - key: '{{ element.securityContext.capabilities.add[] || `[]` }}' operator: AnyNotIn value: - NET_BIND_SERVICE - "" list: request.object.spec.[ephemeralContainers, initContainers, containers][] message: Any capabilities added other than NET_BIND_SERVICE are disallowed. - validationFailureAction: enforce + validationFailureAction: Enforce --- apiVersion: kyverno.io/v1 -kind: Policy +kind: ClusterPolicy metadata: annotations: kyverno.io/kubernetes-version: 1.22-1.23 @@ -128,11 +155,22 @@ metadata: policies.kyverno.io/subject: Pod policies.kyverno.io/title: Disallow Host Namespaces name: disallow-host-namespaces - namespace: cert-manager spec: background: true rules: - - match: + - exclude: + resources: + namespaces: + - bind + - e2e-vault + - e2e-vault-mtls + - gateway-system + - ingress-nginx + - pebble + - projectcontour + - sample-external-issuer-system + - samplewebhook + match: any: - resources: kinds: @@ -146,10 +184,10 @@ spec: =(hostIPC): "false" =(hostNetwork): "false" =(hostPID): "false" - validationFailureAction: enforce + validationFailureAction: Enforce --- apiVersion: kyverno.io/v1 -kind: Policy +kind: ClusterPolicy metadata: annotations: kyverno.io/kubernetes-version: 1.22-1.23 @@ -163,11 +201,22 @@ metadata: policies.kyverno.io/subject: Pod,Volume policies.kyverno.io/title: Disallow hostPath name: disallow-host-path - namespace: cert-manager spec: background: true rules: - - match: + - exclude: + resources: + namespaces: + - bind + - e2e-vault + - e2e-vault-mtls + - gateway-system + - ingress-nginx + - pebble + - projectcontour + - sample-external-issuer-system + - samplewebhook + match: any: - resources: kinds: @@ -180,10 +229,10 @@ spec: spec: =(volumes): - X(hostPath): "null" - validationFailureAction: enforce + validationFailureAction: Enforce --- apiVersion: kyverno.io/v1 -kind: Policy +kind: ClusterPolicy metadata: annotations: kyverno.io/kubernetes-version: 1.22-1.23 @@ -196,11 +245,22 @@ metadata: policies.kyverno.io/subject: Pod policies.kyverno.io/title: Disallow hostPorts name: disallow-host-ports - namespace: cert-manager spec: background: true rules: - - match: + - exclude: + resources: + namespaces: + - bind + - e2e-vault + - e2e-vault-mtls + - gateway-system + - ingress-nginx + - pebble + - projectcontour + - sample-external-issuer-system + - samplewebhook + match: any: - resources: kinds: @@ -221,10 +281,10 @@ spec: containers: - =(ports): - =(hostPort): 0 - validationFailureAction: enforce + validationFailureAction: Enforce --- apiVersion: kyverno.io/v1 -kind: Policy +kind: ClusterPolicy metadata: annotations: kyverno.io/kubernetes-version: 1.22-1.23 @@ -239,11 +299,22 @@ metadata: policies.kyverno.io/subject: Pod policies.kyverno.io/title: Disallow hostProcess name: disallow-host-process - namespace: cert-manager spec: background: true rules: - - match: + - exclude: + resources: + namespaces: + - bind + - e2e-vault + - e2e-vault-mtls + - gateway-system + - ingress-nginx + - pebble + - projectcontour + - sample-external-issuer-system + - samplewebhook + match: any: - resources: kinds: @@ -268,10 +339,10 @@ spec: - =(securityContext): =(windowsOptions): =(hostProcess): "false" - validationFailureAction: enforce + validationFailureAction: Enforce --- apiVersion: kyverno.io/v1 -kind: Policy +kind: ClusterPolicy metadata: annotations: kyverno.io/kubernetes-version: 1.22-1.23 @@ -284,11 +355,22 @@ metadata: policies.kyverno.io/subject: Pod policies.kyverno.io/title: Disallow Privilege Escalation name: disallow-privilege-escalation - namespace: cert-manager spec: background: true rules: - - match: + - exclude: + resources: + namespaces: + - bind + - e2e-vault + - e2e-vault-mtls + - gateway-system + - ingress-nginx + - pebble + - projectcontour + - sample-external-issuer-system + - samplewebhook + match: any: - resources: kinds: @@ -309,10 +391,10 @@ spec: containers: - securityContext: allowPrivilegeEscalation: "false" - validationFailureAction: enforce + validationFailureAction: Enforce --- apiVersion: kyverno.io/v1 -kind: Policy +kind: ClusterPolicy metadata: annotations: kyverno.io/kubernetes-version: 1.22-1.23 @@ -325,11 +407,22 @@ metadata: policies.kyverno.io/subject: Pod policies.kyverno.io/title: Disallow Privileged Containers name: disallow-privileged-containers - namespace: cert-manager spec: background: true rules: - - match: + - exclude: + resources: + namespaces: + - bind + - e2e-vault + - e2e-vault-mtls + - gateway-system + - ingress-nginx + - pebble + - projectcontour + - sample-external-issuer-system + - samplewebhook + match: any: - resources: kinds: @@ -350,10 +443,10 @@ spec: containers: - =(securityContext): =(privileged): "false" - validationFailureAction: enforce + validationFailureAction: Enforce --- apiVersion: kyverno.io/v1 -kind: Policy +kind: ClusterPolicy metadata: annotations: kyverno.io/kubernetes-version: 1.22-1.23 @@ -367,11 +460,22 @@ metadata: policies.kyverno.io/subject: Pod policies.kyverno.io/title: Disallow procMount name: disallow-proc-mount - namespace: cert-manager spec: background: true rules: - - match: + - exclude: + resources: + namespaces: + - bind + - e2e-vault + - e2e-vault-mtls + - gateway-system + - ingress-nginx + - pebble + - projectcontour + - sample-external-issuer-system + - samplewebhook + match: any: - resources: kinds: @@ -393,10 +497,10 @@ spec: containers: - =(securityContext): =(procMount): Default - validationFailureAction: enforce + validationFailureAction: Enforce --- apiVersion: kyverno.io/v1 -kind: Policy +kind: ClusterPolicy metadata: annotations: kyverno.io/kubernetes-version: 1.22-1.23 @@ -409,11 +513,22 @@ metadata: policies.kyverno.io/subject: Pod policies.kyverno.io/title: Disallow SELinux name: disallow-selinux - namespace: cert-manager spec: background: true rules: - - match: + - exclude: + resources: + namespaces: + - bind + - e2e-vault + - e2e-vault-mtls + - gateway-system + - ingress-nginx + - pebble + - projectcontour + - sample-external-issuer-system + - samplewebhook + match: any: - resources: kinds: @@ -476,10 +591,55 @@ spec: =(seLinuxOptions): X(role): "null" X(user): "null" - validationFailureAction: enforce + validationFailureAction: Enforce --- apiVersion: kyverno.io/v1 -kind: Policy +kind: ClusterPolicy +metadata: + annotations: + policies.kyverno.io/category: Best Practices, EKS Best Practices, PSP Migration + policies.kyverno.io/description: 'A read-only root file system helps to enforce + an immutable infrastructure strategy; the container only needs to write on the + mounted volume that persists the state. An immutable root filesystem can also + prevent malicious binaries from writing to the host system. This policy validates + that containers define a securityContext with `readOnlyRootFilesystem: true`.' + policies.kyverno.io/minversion: 1.6.0 + policies.kyverno.io/severity: medium + policies.kyverno.io/subject: Pod + policies.kyverno.io/title: Require Read-Only Root Filesystem + name: require-ro-rootfs +spec: + background: true + rules: + - exclude: + resources: + namespaces: + - bind + - e2e-vault + - e2e-vault-mtls + - gateway-system + - ingress-nginx + - pebble + - projectcontour + - sample-external-issuer-system + - samplewebhook + match: + any: + - resources: + kinds: + - Pod + name: validate-readOnlyRootFilesystem + validate: + message: Root filesystem must be read-only. + pattern: + spec: + containers: + - securityContext: + readOnlyRootFilesystem: true + validationFailureAction: Enforce +--- +apiVersion: kyverno.io/v1 +kind: ClusterPolicy metadata: annotations: kyverno.io/kubernetes-version: 1.22-1.23 @@ -492,11 +652,22 @@ metadata: policies.kyverno.io/subject: Pod policies.kyverno.io/title: Require Run As Non-Root User name: require-run-as-non-root-user - namespace: cert-manager spec: background: true rules: - - match: + - exclude: + resources: + namespaces: + - bind + - e2e-vault + - e2e-vault-mtls + - gateway-system + - ingress-nginx + - pebble + - projectcontour + - sample-external-issuer-system + - samplewebhook + match: any: - resources: kinds: @@ -520,10 +691,10 @@ spec: containers: - =(securityContext): =(runAsUser): '>0' - validationFailureAction: enforce + validationFailureAction: Enforce --- apiVersion: kyverno.io/v1 -kind: Policy +kind: ClusterPolicy metadata: annotations: kyverno.io/kubernetes-version: 1.22-1.23 @@ -537,11 +708,22 @@ metadata: policies.kyverno.io/subject: Pod policies.kyverno.io/title: Require runAsNonRoot name: require-run-as-nonroot - namespace: cert-manager spec: background: true rules: - - match: + - exclude: + resources: + namespaces: + - bind + - e2e-vault + - e2e-vault-mtls + - gateway-system + - ingress-nginx + - pebble + - projectcontour + - sample-external-issuer-system + - samplewebhook + match: any: - resources: kinds: @@ -552,33 +734,33 @@ spec: - spec: =(ephemeralContainers): - =(securityContext): - =(runAsNonRoot): true + =(runAsNonRoot): "true" =(initContainers): - =(securityContext): - =(runAsNonRoot): true + =(runAsNonRoot): "true" containers: - =(securityContext): - =(runAsNonRoot): true + =(runAsNonRoot): "true" securityContext: - runAsNonRoot: true + runAsNonRoot: "true" - spec: =(ephemeralContainers): - securityContext: - runAsNonRoot: true + runAsNonRoot: "true" =(initContainers): - securityContext: - runAsNonRoot: true + runAsNonRoot: "true" containers: - securityContext: - runAsNonRoot: true + runAsNonRoot: "true" message: Running as root is not allowed. Either the field spec.securityContext.runAsNonRoot must be set to `true`, or the fields spec.containers[*].securityContext.runAsNonRoot, spec.initContainers[*].securityContext.runAsNonRoot, and spec.ephemeralContainers[*].securityContext.runAsNonRoot must be set to `true`. - validationFailureAction: enforce + validationFailureAction: Enforce --- apiVersion: kyverno.io/v1 -kind: Policy +kind: ClusterPolicy metadata: annotations: kyverno.io/kubernetes-version: 1.22-1.23 @@ -594,11 +776,22 @@ metadata: policies.kyverno.io/subject: Pod, Annotation policies.kyverno.io/title: Restrict AppArmor name: restrict-apparmor-profiles - namespace: cert-manager spec: background: true rules: - - match: + - exclude: + resources: + namespaces: + - bind + - e2e-vault + - e2e-vault-mtls + - gateway-system + - ingress-nginx + - pebble + - projectcontour + - sample-external-issuer-system + - samplewebhook + match: any: - resources: kinds: @@ -612,10 +805,60 @@ spec: =(annotations): =(container.apparmor.security.beta.kubernetes.io/*): runtime/default | localhost/* - validationFailureAction: enforce + validationFailureAction: Enforce --- apiVersion: kyverno.io/v1 -kind: Policy +kind: ClusterPolicy +metadata: + annotations: + policies.kyverno.io/category: Sample, EKS Best Practices + policies.kyverno.io/description: 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. + policies.kyverno.io/minversion: 1.6.0 + policies.kyverno.io/severity: medium + policies.kyverno.io/subject: Pod,ServiceAccount + policies.kyverno.io/title: Restrict Auto-Mount of Service Account Tokens + name: restrict-automount-sa-token +spec: + background: true + rules: + - exclude: + resources: + namespaces: + - bind + - e2e-vault + - e2e-vault-mtls + - gateway-system + - ingress-nginx + - pebble + - projectcontour + - sample-external-issuer-system + - samplewebhook + match: + any: + - resources: + kinds: + - Pod + name: validate-automountServiceAccountToken + preconditions: + all: + - key: '{{ request."object".metadata.labels."app.kubernetes.io/part-of" || '''' + }}' + operator: NotEquals + value: policy-reporter + validate: + message: Auto-mounting of Service Account tokens is not allowed. + pattern: + spec: + automountServiceAccountToken: "false" + validationFailureAction: Enforce +--- +apiVersion: kyverno.io/v1 +kind: ClusterPolicy metadata: annotations: kyverno.io/kubernetes-version: 1.22-1.23 @@ -628,11 +871,22 @@ metadata: policies.kyverno.io/subject: Pod policies.kyverno.io/title: Restrict Seccomp name: restrict-seccomp - namespace: cert-manager spec: background: true rules: - - match: + - exclude: + resources: + namespaces: + - bind + - e2e-vault + - e2e-vault-mtls + - gateway-system + - ingress-nginx + - pebble + - projectcontour + - sample-external-issuer-system + - samplewebhook + match: any: - resources: kinds: @@ -660,10 +914,10 @@ spec: - =(securityContext): =(seccompProfile): =(type): RuntimeDefault | Localhost - validationFailureAction: enforce + validationFailureAction: Enforce --- apiVersion: kyverno.io/v1 -kind: Policy +kind: ClusterPolicy metadata: annotations: kyverno.io/kubernetes-version: 1.22-1.23 @@ -679,11 +933,22 @@ metadata: policies.kyverno.io/subject: Pod policies.kyverno.io/title: Restrict Seccomp (Strict) name: restrict-seccomp-strict - namespace: cert-manager spec: background: true rules: - - match: + - exclude: + resources: + namespaces: + - bind + - e2e-vault + - e2e-vault-mtls + - gateway-system + - ingress-nginx + - pebble + - projectcontour + - sample-external-issuer-system + - samplewebhook + match: any: - resources: kinds: @@ -724,10 +989,10 @@ spec: spec.containers[*].securityContext.seccompProfile.type, spec.initContainers[*].securityContext.seccompProfile.type, and spec.ephemeralContainers[*].securityContext.seccompProfile.type must be set to `RuntimeDefault` or `Localhost`. - validationFailureAction: enforce + validationFailureAction: Enforce --- apiVersion: kyverno.io/v1 -kind: Policy +kind: ClusterPolicy metadata: annotations: kyverno.io/kubernetes-version: 1.22-1.23 @@ -742,11 +1007,22 @@ metadata: policies.kyverno.io/subject: Pod policies.kyverno.io/title: Restrict sysctls name: restrict-sysctls - namespace: cert-manager spec: background: true rules: - - match: + - exclude: + resources: + namespaces: + - bind + - e2e-vault + - e2e-vault-mtls + - gateway-system + - ingress-nginx + - pebble + - projectcontour + - sample-external-issuer-system + - samplewebhook + match: any: - resources: kinds: @@ -763,10 +1039,10 @@ spec: =(sysctls): - =(name): kernel.shm_rmid_forced | net.ipv4.ip_local_port_range | net.ipv4.ip_unprivileged_port_start | net.ipv4.tcp_syncookies | net.ipv4.ping_group_range - validationFailureAction: enforce + validationFailureAction: Enforce --- apiVersion: kyverno.io/v1 -kind: Policy +kind: ClusterPolicy metadata: annotations: kyverno.io/kubernetes-version: 1.22-1.23 @@ -781,16 +1057,32 @@ metadata: policies.kyverno.io/subject: Pod,Volume policies.kyverno.io/title: Restrict Volume Types name: restrict-volume-types - namespace: cert-manager spec: background: true rules: - - match: + - exclude: + resources: + namespaces: + - bind + - e2e-vault + - e2e-vault-mtls + - gateway-system + - ingress-nginx + - pebble + - projectcontour + - sample-external-issuer-system + - samplewebhook + match: any: - resources: kinds: - Pod name: restricted-volumes + preconditions: + all: + - key: '{{ request.operation || ''BACKGROUND'' }}' + operator: NotEquals + value: DELETE validate: deny: conditions: @@ -810,4 +1102,4 @@ spec: - "" message: 'Only the following types of volumes may be used: configMap, csi, downwardAPI, emptyDir, ephemeral, persistentVolumeClaim, projected, and secret.' - validationFailureAction: enforce + validationFailureAction: Enforce diff --git a/make/config/lib.sh b/make/config/lib.sh index 79b856dde8b..dc245dda14c 100644 --- a/make/config/lib.sh +++ b/make/config/lib.sh @@ -36,7 +36,18 @@ warn= wait= greencheck= redcross= -if ! printenv NO_COLOR >/dev/null; then + +should_color() { + if [[ "${CI:-}" == "true" ]]; then + return 1 + elif [[ "${NO_COLOR:-}" ]]; then + return 1 + fi + + return 0 +} + +if should_color >/dev/null; then red="\033[0;31m" green="\033[0;32m" yel="\033[0;33m" @@ -84,7 +95,7 @@ color() { trace() { # This mysterious awk expression makes sure to double-quote the arguments # that have special characters in them, such as spaces, curly braces (since - # zsh interprets curly braces), interogation marks, simple braces, and "*". + # zsh interprets curly braces), interrogation marks, simple braces, and "*". for arg in "$@"; do echo "$arg"; done \ | awk '{if (NR==1) printf "'"$yel"'%s '"$bold"'",$0; else if ($0 ~ / |\}|\{|\(|\)|\\|\*|\?/) printf "\"%s\" ",$0; else printf "%s ",$0} END {printf "\n"}' diff --git a/make/config/pebble/Containerfile.pebble b/make/config/pebble/Containerfile.pebble index 1c07c9b3f24..11daff63e41 100644 --- a/make/config/pebble/Containerfile.pebble +++ b/make/config/pebble/Containerfile.pebble @@ -1,3 +1,5 @@ +# +skip_license_check + ARG BASE_IMAGE FROM $BASE_IMAGE diff --git a/make/config/pebble/chart/templates/configmap.yaml b/make/config/pebble/chart/templates/configmap.yaml index 6417ff75183..f883d4d7724 100644 --- a/make/config/pebble/chart/templates/configmap.yaml +++ b/make/config/pebble/chart/templates/configmap.yaml @@ -8,6 +8,8 @@ metadata: release: {{ .Release.Name }} heritage: {{ .Release.Service }} data: + # NOTE: The externalAccountMACKeys values must match those used in `tests/e2e`. + # The key is copied from https://github.com/letsencrypt/pebble/blob/main/test/config/pebble-config-external-account-bindings.json config.json: | { "pebble": { @@ -18,10 +20,19 @@ data: "tlsPort": 443, "externalAccountBindingRequired": false, "externalAccountMACKeys": { - "kid-1": "a2lkLXNlY3JldC0x", - "kid-2": "a2lkLXNlY3JldC0y" + "kid-1": "zWNDZM6eQGHWpSRTPal5eIUYFTu7EajVIoguysqZ9wG44nMEtx3MUAsUDkMTQ12W" }, - "domainBlocklist": ["google.com"] + "domainBlocklist": ["google.com"], + "profiles": { + "123h": { + "description": "Certificates with duration 123h", + "validityPeriod": {{ mul 60 60 123 }} + }, + "456h": { + "description": "Certificates with duration 456h", + "validityPeriod": {{ mul 60 60 456 }} + } + } } } cert.pem: | diff --git a/make/config/pebble/chart/templates/deployment.yaml b/make/config/pebble/chart/templates/deployment.yaml index 73f97831f21..7af5e5ef229 100644 --- a/make/config/pebble/chart/templates/deployment.yaml +++ b/make/config/pebble/chart/templates/deployment.yaml @@ -33,10 +33,12 @@ spec: mountPath: /config readOnly: true readinessProbe: - tcpSocket: + httpGet: + path: /dir port: 14000 + scheme: HTTPS initialDelaySeconds: 1 - periodSeconds: 1 + periodSeconds: 2 failureThreshold: 10 successThreshold: 1 resources: diff --git a/make/config/projectcontour/contour.yaml b/make/config/projectcontour/contour.yaml index 533b419195e..93b04a6b714 100644 --- a/make/config/projectcontour/contour.yaml +++ b/make/config/projectcontour/contour.yaml @@ -6,7 +6,9 @@ # # Specify the Gateway API configuration. gateway: - controllerName: projectcontour.io/projectcontour/contour + gatewayRef: + name: acmesolver + namespace: projectcontour # # should contour expect to be running inside a k8s cluster # incluster: true @@ -15,7 +17,7 @@ gateway: # kubeconfig: /path/to/.kube/config # # Disable RFC-compliant behavior to strip "Content-Length" header if -# "Tranfer-Encoding: chunked" is also set. +# "Transfer-Encoding: chunked" is also set. # disableAllowChunkedLength: false # # Disable Envoy's non-standard merge_slashes path transformation option @@ -63,7 +65,7 @@ accesslog-format: envoy # accesslog-format: json # accesslog-level: info # The default fields that will be logged are specified below. -# To customise this list, just add or remove entries. +# To customize this list, just add or remove entries. # The canonical list is available at # https://godoc.org/github.com/projectcontour/contour/internal/envoy#JSONFields # json-fields: diff --git a/make/config/projectcontour/gateway.yaml b/make/config/projectcontour/gateway.yaml index a4ccab95098..40f6d83f80e 100644 --- a/make/config/projectcontour/gateway.yaml +++ b/make/config/projectcontour/gateway.yaml @@ -1,13 +1,13 @@ kind: GatewayClass -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1beta1 metadata: name: acmesolver spec: - controllerName: projectcontour.io/projectcontour/contour + controllerName: projectcontour.io/gateway-controller --- kind: Gateway -apiVersion: gateway.networking.k8s.io/v1alpha2 +apiVersion: gateway.networking.k8s.io/v1beta1 metadata: name: acmesolver namespace: projectcontour diff --git a/make/config/samplewebhook/Containerfile.samplewebhook b/make/config/samplewebhook/Containerfile.samplewebhook index 48c260d9c99..c84c8b4a9db 100644 --- a/make/config/samplewebhook/Containerfile.samplewebhook +++ b/make/config/samplewebhook/Containerfile.samplewebhook @@ -1,3 +1,17 @@ +# 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. + ARG BASE_IMAGE FROM $BASE_IMAGE diff --git a/make/config/samplewebhook/chart/templates/_helpers.tpl b/make/config/samplewebhook/chart/templates/_helpers.tpl index d3c474b64f1..12d866b5267 100644 --- a/make/config/samplewebhook/chart/templates/_helpers.tpl +++ b/make/config/samplewebhook/chart/templates/_helpers.tpl @@ -32,7 +32,7 @@ Create chart name and version as used by the chart label. {{- end -}} {{- define "example-webhook.selfSignedIssuer" -}} -{{ printf "%s-selfsign" (include "example-webhook.fullname" .) }} +{{ printf "%s-self-sign" (include "example-webhook.fullname" .) }} {{- end -}} {{- define "example-webhook.rootCAIssuer" -}} diff --git a/make/config/samplewebhook/chart/templates/deployment.yaml b/make/config/samplewebhook/chart/templates/deployment.yaml index a124b380e74..a3981b0a197 100644 --- a/make/config/samplewebhook/chart/templates/deployment.yaml +++ b/make/config/samplewebhook/chart/templates/deployment.yaml @@ -61,7 +61,9 @@ spec: secretName: {{ include "example-webhook.servingCertificate" . }} {{- with .Values.nodeSelector }} nodeSelector: - {{- toYaml . | nindent 8 }} + {{- range $key, $value := . }} + {{ $key }}: {{ $value | quote }} + {{- end }} {{- end }} {{- with .Values.affinity }} affinity: diff --git a/make/config/samplewebhook/sample/main.go b/make/config/samplewebhook/sample/main.go index 2bbbcea8def..5f6a741f97b 100644 --- a/make/config/samplewebhook/sample/main.go +++ b/make/config/samplewebhook/sample/main.go @@ -56,7 +56,7 @@ type customDNSProviderSolver struct { // 3. uncomment the relevant code in the Initialize method below // 4. ensure your webhook's service account has the required RBAC role // assigned to it for interacting with the Kubernetes APIs you need. - //client kubernetes.Clientset + // client kubernetes.Clientset } // customDNSProviderConfig is a structure that is used to decode into when @@ -79,8 +79,8 @@ type customDNSProviderConfig struct { // These fields will be set by users in the // `issuer.spec.acme.dns01.providers.webhook.config` field. - //Email string `json:"email"` - //APIKeySecretRef cmmeta.SecretKeySelector `json:"apiKeySecretRef"` + // Email string `json:"email"` + // APIKeySecretRef cmmeta.SecretKeySelector `json:"apiKeySecretRef"` } // Name is used as the name for this DNS solver when referencing it on the ACME @@ -105,7 +105,7 @@ func (c *customDNSProviderSolver) Present(ch *v1alpha1.ChallengeRequest) error { } // TODO: do something more useful with the decoded configuration - fmt.Printf("Decoded configuration %v", cfg) + fmt.Fprintf(os.Stdout, "Decoded configuration %v", cfg) // TODO: add code that sets a record in the DNS provider's console return nil @@ -123,7 +123,7 @@ func (c *customDNSProviderSolver) CleanUp(ch *v1alpha1.ChallengeRequest) error { } // Initialize will be called when the webhook first starts. -// This method can be used to instantiate the webhook, i.e. initialising +// This method can be used to instantiate the webhook, i.e. initializing // connections or warming up caches. // Typically, the kubeClientConfig parameter is used to build a Kubernetes // client that can be used to fetch resources from the Kubernetes API, e.g. @@ -135,12 +135,12 @@ func (c *customDNSProviderSolver) Initialize(kubeClientConfig *rest.Config, stop ///// UNCOMMENT THE BELOW CODE TO MAKE A KUBERNETES CLIENTSET AVAILABLE TO ///// YOUR CUSTOM DNS PROVIDER - //cl, err := kubernetes.NewForConfig(kubeClientConfig) - //if err != nil { - // return err - //} + // cl, err := kubernetes.NewForConfig(kubeClientConfig) + // if err != nil { + // return err + // } // - //c.client = cl + // c.client = cl ///// END OF CODE TO MAKE KUBERNETES CLIENTSET AVAILABLE return nil diff --git a/make/containers.mk b/make/containers.mk index a1c6af6eaf3..820c4c50f3f 100644 --- a/make/containers.mk +++ b/make/containers.mk @@ -1,8 +1,22 @@ +# 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 to "DYNAMIC" to use a dynamic base image BASE_IMAGE_TYPE:=STATIC ARCHS = amd64 arm64 s390x ppc64le arm -BINS = controller acmesolver cainjector webhook ctl +BINS = controller acmesolver cainjector webhook startupapicheck BASE_IMAGE_controller-linux-amd64:=$($(BASE_IMAGE_TYPE)_BASE_IMAGE_amd64) BASE_IMAGE_controller-linux-arm64:=$($(BASE_IMAGE_TYPE)_BASE_IMAGE_arm64) @@ -28,20 +42,20 @@ BASE_IMAGE_cainjector-linux-s390x:=$($(BASE_IMAGE_TYPE)_BASE_IMAGE_s390x) BASE_IMAGE_cainjector-linux-ppc64le:=$($(BASE_IMAGE_TYPE)_BASE_IMAGE_ppc64le) BASE_IMAGE_cainjector-linux-arm:=$($(BASE_IMAGE_TYPE)_BASE_IMAGE_arm) -BASE_IMAGE_cmctl-linux-amd64:=$($(BASE_IMAGE_TYPE)_BASE_IMAGE_amd64) -BASE_IMAGE_cmctl-linux-arm64:=$($(BASE_IMAGE_TYPE)_BASE_IMAGE_arm64) -BASE_IMAGE_cmctl-linux-s390x:=$($(BASE_IMAGE_TYPE)_BASE_IMAGE_s390x) -BASE_IMAGE_cmctl-linux-ppc64le:=$($(BASE_IMAGE_TYPE)_BASE_IMAGE_ppc64le) -BASE_IMAGE_cmctl-linux-arm:=$($(BASE_IMAGE_TYPE)_BASE_IMAGE_arm) +BASE_IMAGE_startupapicheck-linux-amd64:=$($(BASE_IMAGE_TYPE)_BASE_IMAGE_amd64) +BASE_IMAGE_startupapicheck-linux-arm64:=$($(BASE_IMAGE_TYPE)_BASE_IMAGE_arm64) +BASE_IMAGE_startupapicheck-linux-s390x:=$($(BASE_IMAGE_TYPE)_BASE_IMAGE_s390x) +BASE_IMAGE_startupapicheck-linux-ppc64le:=$($(BASE_IMAGE_TYPE)_BASE_IMAGE_ppc64le) +BASE_IMAGE_startupapicheck-linux-arm:=$($(BASE_IMAGE_TYPE)_BASE_IMAGE_arm) .PHONY: all-containers -all-containers: cert-manager-controller-linux cert-manager-webhook-linux cert-manager-acmesolver-linux cert-manager-cainjector-linux cert-manager-ctl-linux +all-containers: cert-manager-controller-linux cert-manager-webhook-linux cert-manager-acmesolver-linux cert-manager-cainjector-linux cert-manager-startupapicheck-linux .PHONY: cert-manager-controller-linux -cert-manager-controller-linux: $(BINDIR)/containers/cert-manager-controller-linux-amd64.tar.gz $(BINDIR)/containers/cert-manager-controller-linux-arm64.tar.gz $(BINDIR)/containers/cert-manager-controller-linux-s390x.tar.gz $(BINDIR)/containers/cert-manager-controller-linux-ppc64le.tar.gz $(BINDIR)/containers/cert-manager-controller-linux-arm.tar.gz +cert-manager-controller-linux: $(bin_dir)/containers/cert-manager-controller-linux-amd64.tar.gz $(bin_dir)/containers/cert-manager-controller-linux-arm64.tar.gz $(bin_dir)/containers/cert-manager-controller-linux-s390x.tar.gz $(bin_dir)/containers/cert-manager-controller-linux-ppc64le.tar.gz $(bin_dir)/containers/cert-manager-controller-linux-arm.tar.gz -$(BINDIR)/containers/cert-manager-controller-linux-amd64.tar $(BINDIR)/containers/cert-manager-controller-linux-arm64.tar $(BINDIR)/containers/cert-manager-controller-linux-s390x.tar $(BINDIR)/containers/cert-manager-controller-linux-ppc64le.tar $(BINDIR)/containers/cert-manager-controller-linux-arm.tar: $(BINDIR)/containers/cert-manager-controller-linux-%.tar: $(BINDIR)/scratch/build-context/cert-manager-controller-linux-%/controller hack/containers/Containerfile.controller $(BINDIR)/scratch/build-context/cert-manager-controller-linux-%/cert-manager.license $(BINDIR)/scratch/build-context/cert-manager-controller-linux-%/cert-manager.licenses_notice $(BINDIR)/release-version | $(BINDIR)/containers - @$(eval TAG := cert-manager-controller-$*:$(RELEASE_VERSION)) +$(bin_dir)/containers/cert-manager-controller-linux-amd64.tar $(bin_dir)/containers/cert-manager-controller-linux-arm64.tar $(bin_dir)/containers/cert-manager-controller-linux-s390x.tar $(bin_dir)/containers/cert-manager-controller-linux-ppc64le.tar $(bin_dir)/containers/cert-manager-controller-linux-arm.tar: $(bin_dir)/containers/cert-manager-controller-linux-%.tar: $(bin_dir)/scratch/build-context/cert-manager-controller-linux-%/controller hack/containers/Containerfile.controller $(bin_dir)/scratch/build-context/cert-manager-controller-linux-%/cert-manager.license $(bin_dir)/scratch/build-context/cert-manager-controller-linux-%/cert-manager.licenses_notice $(bin_dir)/release-version | $(bin_dir)/containers $(NEEDS_CTR) + @$(eval TAG := cert-manager-controller-$*:$(VERSION)) @$(eval BASE := BASE_IMAGE_controller-linux-$*) $(CTR) build --quiet \ -f hack/containers/Containerfile.controller \ @@ -51,10 +65,10 @@ $(BINDIR)/containers/cert-manager-controller-linux-amd64.tar $(BINDIR)/container $(CTR) save $(TAG) -o $@ >/dev/null .PHONY: cert-manager-webhook-linux -cert-manager-webhook-linux: $(BINDIR)/containers/cert-manager-webhook-linux-amd64.tar.gz $(BINDIR)/containers/cert-manager-webhook-linux-arm64.tar.gz $(BINDIR)/containers/cert-manager-webhook-linux-s390x.tar.gz $(BINDIR)/containers/cert-manager-webhook-linux-ppc64le.tar.gz $(BINDIR)/containers/cert-manager-webhook-linux-arm.tar.gz +cert-manager-webhook-linux: $(bin_dir)/containers/cert-manager-webhook-linux-amd64.tar.gz $(bin_dir)/containers/cert-manager-webhook-linux-arm64.tar.gz $(bin_dir)/containers/cert-manager-webhook-linux-s390x.tar.gz $(bin_dir)/containers/cert-manager-webhook-linux-ppc64le.tar.gz $(bin_dir)/containers/cert-manager-webhook-linux-arm.tar.gz -$(BINDIR)/containers/cert-manager-webhook-linux-amd64.tar $(BINDIR)/containers/cert-manager-webhook-linux-arm64.tar $(BINDIR)/containers/cert-manager-webhook-linux-s390x.tar $(BINDIR)/containers/cert-manager-webhook-linux-ppc64le.tar $(BINDIR)/containers/cert-manager-webhook-linux-arm.tar: $(BINDIR)/containers/cert-manager-webhook-linux-%.tar: $(BINDIR)/scratch/build-context/cert-manager-webhook-linux-%/webhook hack/containers/Containerfile.webhook $(BINDIR)/scratch/build-context/cert-manager-webhook-linux-%/cert-manager.license $(BINDIR)/scratch/build-context/cert-manager-webhook-linux-%/cert-manager.licenses_notice $(BINDIR)/release-version | $(BINDIR)/containers - @$(eval TAG := cert-manager-webhook-$*:$(RELEASE_VERSION)) +$(bin_dir)/containers/cert-manager-webhook-linux-amd64.tar $(bin_dir)/containers/cert-manager-webhook-linux-arm64.tar $(bin_dir)/containers/cert-manager-webhook-linux-s390x.tar $(bin_dir)/containers/cert-manager-webhook-linux-ppc64le.tar $(bin_dir)/containers/cert-manager-webhook-linux-arm.tar: $(bin_dir)/containers/cert-manager-webhook-linux-%.tar: $(bin_dir)/scratch/build-context/cert-manager-webhook-linux-%/webhook hack/containers/Containerfile.webhook $(bin_dir)/scratch/build-context/cert-manager-webhook-linux-%/cert-manager.license $(bin_dir)/scratch/build-context/cert-manager-webhook-linux-%/cert-manager.licenses_notice $(bin_dir)/release-version | $(bin_dir)/containers $(NEEDS_CTR) + @$(eval TAG := cert-manager-webhook-$*:$(VERSION)) @$(eval BASE := BASE_IMAGE_webhook-linux-$*) $(CTR) build --quiet \ -f hack/containers/Containerfile.webhook \ @@ -64,10 +78,10 @@ $(BINDIR)/containers/cert-manager-webhook-linux-amd64.tar $(BINDIR)/containers/c $(CTR) save $(TAG) -o $@ >/dev/null .PHONY: cert-manager-cainjector-linux -cert-manager-cainjector-linux: $(BINDIR)/containers/cert-manager-cainjector-linux-amd64.tar.gz $(BINDIR)/containers/cert-manager-cainjector-linux-arm64.tar.gz $(BINDIR)/containers/cert-manager-cainjector-linux-s390x.tar.gz $(BINDIR)/containers/cert-manager-cainjector-linux-ppc64le.tar.gz $(BINDIR)/containers/cert-manager-cainjector-linux-arm.tar.gz +cert-manager-cainjector-linux: $(bin_dir)/containers/cert-manager-cainjector-linux-amd64.tar.gz $(bin_dir)/containers/cert-manager-cainjector-linux-arm64.tar.gz $(bin_dir)/containers/cert-manager-cainjector-linux-s390x.tar.gz $(bin_dir)/containers/cert-manager-cainjector-linux-ppc64le.tar.gz $(bin_dir)/containers/cert-manager-cainjector-linux-arm.tar.gz -$(BINDIR)/containers/cert-manager-cainjector-linux-amd64.tar $(BINDIR)/containers/cert-manager-cainjector-linux-arm64.tar $(BINDIR)/containers/cert-manager-cainjector-linux-s390x.tar $(BINDIR)/containers/cert-manager-cainjector-linux-ppc64le.tar $(BINDIR)/containers/cert-manager-cainjector-linux-arm.tar: $(BINDIR)/containers/cert-manager-cainjector-linux-%.tar: $(BINDIR)/scratch/build-context/cert-manager-cainjector-linux-%/cainjector hack/containers/Containerfile.cainjector $(BINDIR)/scratch/build-context/cert-manager-cainjector-linux-%/cert-manager.license $(BINDIR)/scratch/build-context/cert-manager-cainjector-linux-%/cert-manager.licenses_notice $(BINDIR)/release-version | $(BINDIR)/containers - @$(eval TAG := cert-manager-cainjector-$*:$(RELEASE_VERSION)) +$(bin_dir)/containers/cert-manager-cainjector-linux-amd64.tar $(bin_dir)/containers/cert-manager-cainjector-linux-arm64.tar $(bin_dir)/containers/cert-manager-cainjector-linux-s390x.tar $(bin_dir)/containers/cert-manager-cainjector-linux-ppc64le.tar $(bin_dir)/containers/cert-manager-cainjector-linux-arm.tar: $(bin_dir)/containers/cert-manager-cainjector-linux-%.tar: $(bin_dir)/scratch/build-context/cert-manager-cainjector-linux-%/cainjector hack/containers/Containerfile.cainjector $(bin_dir)/scratch/build-context/cert-manager-cainjector-linux-%/cert-manager.license $(bin_dir)/scratch/build-context/cert-manager-cainjector-linux-%/cert-manager.licenses_notice $(bin_dir)/release-version | $(bin_dir)/containers $(NEEDS_CTR) + @$(eval TAG := cert-manager-cainjector-$*:$(VERSION)) @$(eval BASE := BASE_IMAGE_cainjector-linux-$*) $(CTR) build --quiet \ -f hack/containers/Containerfile.cainjector \ @@ -77,10 +91,10 @@ $(BINDIR)/containers/cert-manager-cainjector-linux-amd64.tar $(BINDIR)/container $(CTR) save $(TAG) -o $@ >/dev/null .PHONY: cert-manager-acmesolver-linux -cert-manager-acmesolver-linux: $(BINDIR)/containers/cert-manager-acmesolver-linux-amd64.tar.gz $(BINDIR)/containers/cert-manager-acmesolver-linux-arm64.tar.gz $(BINDIR)/containers/cert-manager-acmesolver-linux-s390x.tar.gz $(BINDIR)/containers/cert-manager-acmesolver-linux-ppc64le.tar.gz $(BINDIR)/containers/cert-manager-acmesolver-linux-arm.tar.gz +cert-manager-acmesolver-linux: $(bin_dir)/containers/cert-manager-acmesolver-linux-amd64.tar.gz $(bin_dir)/containers/cert-manager-acmesolver-linux-arm64.tar.gz $(bin_dir)/containers/cert-manager-acmesolver-linux-s390x.tar.gz $(bin_dir)/containers/cert-manager-acmesolver-linux-ppc64le.tar.gz $(bin_dir)/containers/cert-manager-acmesolver-linux-arm.tar.gz -$(BINDIR)/containers/cert-manager-acmesolver-linux-amd64.tar $(BINDIR)/containers/cert-manager-acmesolver-linux-arm64.tar $(BINDIR)/containers/cert-manager-acmesolver-linux-s390x.tar $(BINDIR)/containers/cert-manager-acmesolver-linux-ppc64le.tar $(BINDIR)/containers/cert-manager-acmesolver-linux-arm.tar: $(BINDIR)/containers/cert-manager-acmesolver-linux-%.tar: $(BINDIR)/scratch/build-context/cert-manager-acmesolver-linux-%/acmesolver hack/containers/Containerfile.acmesolver $(BINDIR)/scratch/build-context/cert-manager-acmesolver-linux-%/cert-manager.license $(BINDIR)/scratch/build-context/cert-manager-acmesolver-linux-%/cert-manager.licenses_notice $(BINDIR)/release-version | $(BINDIR)/containers - @$(eval TAG := cert-manager-acmesolver-$*:$(RELEASE_VERSION)) +$(bin_dir)/containers/cert-manager-acmesolver-linux-amd64.tar $(bin_dir)/containers/cert-manager-acmesolver-linux-arm64.tar $(bin_dir)/containers/cert-manager-acmesolver-linux-s390x.tar $(bin_dir)/containers/cert-manager-acmesolver-linux-ppc64le.tar $(bin_dir)/containers/cert-manager-acmesolver-linux-arm.tar: $(bin_dir)/containers/cert-manager-acmesolver-linux-%.tar: $(bin_dir)/scratch/build-context/cert-manager-acmesolver-linux-%/acmesolver hack/containers/Containerfile.acmesolver $(bin_dir)/scratch/build-context/cert-manager-acmesolver-linux-%/cert-manager.license $(bin_dir)/scratch/build-context/cert-manager-acmesolver-linux-%/cert-manager.licenses_notice $(bin_dir)/release-version | $(bin_dir)/containers $(NEEDS_CTR) + @$(eval TAG := cert-manager-acmesolver-$*:$(VERSION)) @$(eval BASE := BASE_IMAGE_acmesolver-linux-$*) $(CTR) build --quiet \ -f hack/containers/Containerfile.acmesolver \ @@ -89,14 +103,14 @@ $(BINDIR)/containers/cert-manager-acmesolver-linux-amd64.tar $(BINDIR)/container $(dir $<) >/dev/null $(CTR) save $(TAG) -o $@ >/dev/null -.PHONY: cert-manager-ctl-linux -cert-manager-ctl-linux: $(BINDIR)/containers/cert-manager-ctl-linux-amd64.tar.gz $(BINDIR)/containers/cert-manager-ctl-linux-arm64.tar.gz $(BINDIR)/containers/cert-manager-ctl-linux-s390x.tar.gz $(BINDIR)/containers/cert-manager-ctl-linux-ppc64le.tar.gz $(BINDIR)/containers/cert-manager-ctl-linux-arm.tar.gz +.PHONY: cert-manager-startupapicheck-linux +cert-manager-startupapicheck-linux: $(bin_dir)/containers/cert-manager-startupapicheck-linux-amd64.tar.gz $(bin_dir)/containers/cert-manager-startupapicheck-linux-arm64.tar.gz $(bin_dir)/containers/cert-manager-startupapicheck-linux-s390x.tar.gz $(bin_dir)/containers/cert-manager-startupapicheck-linux-ppc64le.tar.gz $(bin_dir)/containers/cert-manager-startupapicheck-linux-arm.tar.gz -$(foreach arch,$(ARCHS),$(BINDIR)/containers/cert-manager-ctl-linux-$(arch).tar): $(BINDIR)/containers/cert-manager-ctl-linux-%.tar: $(BINDIR)/scratch/build-context/cert-manager-ctl-linux-%/ctl hack/containers/Containerfile.ctl $(BINDIR)/scratch/build-context/cert-manager-ctl-linux-%/cert-manager.license $(BINDIR)/scratch/build-context/cert-manager-ctl-linux-%/cert-manager.licenses_notice $(BINDIR)/release-version | $(BINDIR)/containers - @$(eval TAG := cert-manager-ctl-$*:$(RELEASE_VERSION)) - @$(eval BASE := BASE_IMAGE_cmctl-linux-$*) +$(bin_dir)/containers/cert-manager-startupapicheck-linux-amd64.tar $(bin_dir)/containers/cert-manager-startupapicheck-linux-arm64.tar $(bin_dir)/containers/cert-manager-startupapicheck-linux-s390x.tar $(bin_dir)/containers/cert-manager-startupapicheck-linux-ppc64le.tar $(bin_dir)/containers/cert-manager-startupapicheck-linux-arm.tar: $(bin_dir)/containers/cert-manager-startupapicheck-linux-%.tar: $(bin_dir)/scratch/build-context/cert-manager-startupapicheck-linux-%/startupapicheck hack/containers/Containerfile.startupapicheck $(bin_dir)/scratch/build-context/cert-manager-startupapicheck-linux-%/cert-manager.license $(bin_dir)/scratch/build-context/cert-manager-startupapicheck-linux-%/cert-manager.licenses_notice $(bin_dir)/release-version | $(bin_dir)/containers $(NEEDS_CTR) + @$(eval TAG := cert-manager-startupapicheck-$*:$(VERSION)) + @$(eval BASE := BASE_IMAGE_startupapicheck-linux-$*) $(CTR) build --quiet \ - -f hack/containers/Containerfile.ctl \ + -f hack/containers/Containerfile.startupapicheck \ --build-arg BASE_IMAGE=$($(BASE)) \ -t $(TAG) \ $(dir $<) >/dev/null @@ -105,10 +119,10 @@ $(foreach arch,$(ARCHS),$(BINDIR)/containers/cert-manager-ctl-linux-$(arch).tar) # At first, we used .INTERMEDIATE to remove the intermediate .tar files. # But it meant "make install" would always have to rebuild # the tar files. -$(BINDIR)/containers/cert-manager-%.tar.gz: $(BINDIR)/containers/cert-manager-%.tar +$(bin_dir)/containers/cert-manager-%.tar.gz: $(bin_dir)/containers/cert-manager-%.tar gzip -c $< > $@ -$(BINDIR)/containers: +$(bin_dir)/containers: @mkdir -p $@ # When running "docker build .", the "build context" was getting too big (1.1 GB @@ -120,19 +134,16 @@ $(BINDIR)/containers: # # Note that we can't use symlinks in the build context. In order to avoid the # cost of multiple copies of the same binary, we use hard links which shouldn't -# be a problem since the $(BINDIR)/ folder is entirely managed by make. +# be a problem since the $(bin_dir)/ folder is entirely managed by make. -$(foreach arch,$(ARCHS),$(foreach bin,$(BINS), $(BINDIR)/scratch/build-context/cert-manager-$(bin)-linux-$(arch))): +$(foreach arch,$(ARCHS),$(foreach bin,$(BINS), $(bin_dir)/scratch/build-context/cert-manager-$(bin)-linux-$(arch))): @mkdir -p $@ -$(BINDIR)/scratch/build-context/cert-manager-%/cert-manager.license: $(BINDIR)/scratch/cert-manager.license | $(BINDIR)/scratch/build-context/cert-manager-% - @ln -f $< $@ - -$(BINDIR)/scratch/build-context/cert-manager-%/cert-manager.licenses_notice: $(BINDIR)/scratch/cert-manager.licenses_notice | $(BINDIR)/scratch/build-context/cert-manager-% +$(bin_dir)/scratch/build-context/cert-manager-%/cert-manager.license: $(bin_dir)/scratch/cert-manager.license | $(bin_dir)/scratch/build-context/cert-manager-% @ln -f $< $@ -$(BINDIR)/scratch/build-context/cert-manager-%/controller $(BINDIR)/scratch/build-context/cert-manager-%/acmesolver $(BINDIR)/scratch/build-context/cert-manager-%/cainjector $(BINDIR)/scratch/build-context/cert-manager-%/webhook: $(BINDIR)/server/% | $(BINDIR)/scratch/build-context/cert-manager-% +$(bin_dir)/scratch/build-context/cert-manager-%/cert-manager.licenses_notice: $(bin_dir)/scratch/cert-manager.licenses_notice | $(bin_dir)/scratch/build-context/cert-manager-% @ln -f $< $@ -$(BINDIR)/scratch/build-context/cert-manager-ctl-%/ctl: $(BINDIR)/cmctl/cmctl-% | $(BINDIR)/scratch/build-context/cert-manager-ctl-% +$(bin_dir)/scratch/build-context/cert-manager-%/controller $(bin_dir)/scratch/build-context/cert-manager-%/acmesolver $(bin_dir)/scratch/build-context/cert-manager-%/cainjector $(bin_dir)/scratch/build-context/cert-manager-%/webhook $(bin_dir)/scratch/build-context/cert-manager-%/startupapicheck: $(bin_dir)/server/% | $(bin_dir)/scratch/build-context/cert-manager-% @ln -f $< $@ diff --git a/make/e2e-ci.sh b/make/e2e-ci.sh index f757ae7a1e7..b7104785b4c 100755 --- a/make/e2e-ci.sh +++ b/make/e2e-ci.sh @@ -15,5 +15,13 @@ # limitations under the License. set -o errexit + trap 'make kind-logs' EXIT -make --no-print-directory e2e FLAKE_ATTEMPTS=2 K8S_VERSION="$(K8S_VERSION)" + +# Note: We set CI here, even though it should be set by Prow, which is the cert-manager CI test runner +# See the list of defined variables here: https://docs.prow.k8s.io/docs/jobs/#job-environment-variables +# We explicitly set CI here because it helps with local testing +# (i.e. "I want to run the exact same e2e test that will be run in CI") +# and because it allows us to be explicit about where it's getting set when we call "make e2e-ci" + +make --no-print-directory e2e FLAKE_ATTEMPTS=2 CI=true K8S_VERSION="$K8S_VERSION" diff --git a/make/e2e-setup.mk b/make/e2e-setup.mk index 67b88545b1b..ddadb7e50f8 100644 --- a/make/e2e-setup.mk +++ b/make/e2e-setup.mk @@ -1,3 +1,17 @@ +# 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. + # CRI_ARCH is meant for M1 users. By default, the images loaded into the local # cluster when running 'make -j e2e-setup' will match the architecture detected # by "uname -m" (e.g., arm64). Note that images that don't have an arm64 @@ -10,37 +24,55 @@ CRI_ARCH := $(HOST_ARCH) # TODO: this version is also defaulted in ./make/cluster.sh. Make it so that it # is set in one place only. -K8S_VERSION := 1.24 - -IMAGE_ingressnginx_amd64 := k8s.gcr.io/ingress-nginx/controller:v1.1.0@sha256:7464dc90abfaa084204176bcc0728f182b0611849395787143f6854dc6c38c85 -IMAGE_kyverno_amd64 := ghcr.io/kyverno/kyverno:v1.7.1@sha256:aec4b029660d47aea025336150fdc2822c991f592d5170d754b6acaf158b513e -IMAGE_kyvernopre_amd64 := ghcr.io/kyverno/kyvernopre:v1.7.1@sha256:1bcec6bc854720e22f439c6dcea02fcf689f31976babcf03a449d750c2b1f34a -IMAGE_vault_amd64 := index.docker.io/library/vault:1.2.3@sha256:b1c86c9e173f15bb4a926e4144a63f7779531c30554ac7aee9b2a408b22b2c01 -IMAGE_bind_amd64 := docker.io/eafxx/bind:latest-9f74179f@sha256:0b8c766f5bedbcbe559c7970c8e923aa0c4ca771e62fcf8dba64ffab980c9a51 -IMAGE_sampleexternalissuer_amd64 := ghcr.io/cert-manager/sample-external-issuer/controller:v0.1.1@sha256:7dafe98c73d229bbac08067fccf9b2884c63c8e1412fe18f9986f59232cf3cb5 -IMAGE_projectcontour_amd64 := ghcr.io/projectcontour/contour:v1.22.0@sha256:c8ee1e566340c1bfd11fc9a1a90d758bde562faecb722540207084330b300497 -IMAGE_pebble_amd64 := local/pebble:local -IMAGE_vaultretagged_amd64 := local/vault:local - -IMAGE_ingressnginx_arm64 := k8s.gcr.io/ingress-nginx/controller:v1.1.0@sha256:86be28e506653cbe29214cb272d60e7c8841ddaf530da29aa22b1b1017faa956 -IMAGE_kyverno_arm64 := ghcr.io/kyverno/kyverno:v1.7.1@sha256:4355f1f65ea5e952886e929a15628f0c6704905035b4741c6f560378871c9335 -IMAGE_kyvernopre_arm64 := ghcr.io/kyverno/kyvernopre:v1.7.1@sha256:141234fb74242155c7b843180b90ee5fb6a20c9e77598bd9c138c687059cdafd -IMAGE_vault_arm64 := index.docker.io/library/vault:1.2.3@sha256:226a269b83c4b28ff8a512e76f1e7b707eccea012e4c3ab4c7af7fff1777ca2d -IMAGE_bind_arm64 := docker.io/eafxx/bind:latest-9f74179f@sha256:85de273f24762c0445035d36290a440e8c5a6a64e9ae6227d92e8b0b0dc7dd6d -IMAGE_sampleexternalissuer_arm64 := # 🚧 NOT AVAILABLE FOR arm64 🚧 -IMAGE_projectcontour_arm64 := ghcr.io/projectcontour/contour:v1.22.0@sha256:ca37e86e284e72b3a969c7845a56a1cfcd348f4cb75bf6312d5b11067efdd667 -IMAGE_pebble_arm64 := local/pebble:local -IMAGE_vaultretagged_arm64 := local/vault:local +K8S_VERSION := 1.34 + +IMAGE_ingressnginx_amd64 := registry.k8s.io/ingress-nginx/controller:v1.12.3@sha256:aadad8e26329d345dea3a69b8deb9f3c52899a97cbaf7e702b8dfbeae3082c15 +IMAGE_kyverno_amd64 := ghcr.io/kyverno/kyverno:v1.12.3@sha256:127def0e41f49fea6e260abf7b1662fe7bdfb9f33e8f9047fb74d0162a5697bb +IMAGE_kyvernopre_amd64 := ghcr.io/kyverno/kyvernopre:v1.12.3@sha256:d388cd67b38fb4f55eb5e38107dbbce9e06208b8e3839f0b63f8631f286181be +IMAGE_vault_amd64 := docker.io/hashicorp/vault:1.14.1@sha256:436d056e8e2a96c7356720069c29229970466f4f686886289dcc94dfa21d3155 +IMAGE_bind_amd64 := europe-west1-docker.pkg.dev/cert-manager-tests-trusted/cert-manager-infra-images/bind9:9.18-22.04_beta@sha256:8c45ba363b2921950161451cf3ff58dff1816fa46b16fb8fa601d5500cdc2ffc +IMAGE_sampleexternalissuer_amd64 := ghcr.io/cert-manager/sample-external-issuer/controller:v0.4.0@sha256:964b378fe0dda7fc38ce3f211c3b24c780e44cef13c39d3206de985bad67f294 +IMAGE_projectcontour_amd64 := ghcr.io/projectcontour/contour:v1.29.1@sha256:bb7af851ac5832c315e0863d12ed583cee54c495d58a206f1d0897647505ed70 +# We use the bitnamilegacy image because Bitnami are deprecating support for +# non-hardened, Debian-based software images in its free tier. See +# https://github.com/bitnami/containers/issues/83267 +IMAGE_projectcontourenvoy_amd64 := docker.io/bitnamilegacy/envoy:1.29.5-debian-12-r0@sha256:34be30978b7765699c4548a393374a5fea64613352078ec49581be26c2024dec + +IMAGE_ingressnginx_arm64 := registry.k8s.io/ingress-nginx/controller:v1.12.3@sha256:800048a4cdf4ad487a17f56d22ec6be7a34248fc18900d945bc869fee4ccb2f7 +IMAGE_kyverno_arm64 := ghcr.io/kyverno/kyverno:v1.12.3@sha256:c076a1ba9e0fb33d8eca3e7499caddfa3bb4f5e52e9dee589d8476ae1688cd34 +IMAGE_kyvernopre_arm64 := ghcr.io/kyverno/kyvernopre:v1.12.3@sha256:d8d750012ed4bb46fd41d8892e92af6fb9fd212317bc23e68a2a47199646b04a +IMAGE_vault_arm64 := docker.io/hashicorp/vault:1.14.1@sha256:27dd264f3813c71a66792191db5382f0cf9eeaf1ae91770634911facfcfe4837 +IMAGE_bind_arm64 := europe-west1-docker.pkg.dev/cert-manager-tests-trusted/cert-manager-infra-images/bind9:9.18-22.04_beta@sha256:7fcfebdfacf52fa0dee2b1ae37ebe235fe169cbc404974c396937599ca69da6f +IMAGE_sampleexternalissuer_arm64 := ghcr.io/cert-manager/sample-external-issuer/controller:v0.4.0@sha256:bdff00089ec7581c0d12414ce5ad1c6ccf5b6cacbfb0b0804fefe5043a1cb849 +IMAGE_projectcontour_arm64 := ghcr.io/projectcontour/contour:v1.29.1@sha256:dbfec77951e123bf383a09412a51df218b716aaf3fe7b2778bb2f208ac495dc5 +# We use the bitnamilegacy image because Bitnami are deprecating support for +# non-hardened, Debian-based software images in its free tier. See +# https://github.com/bitnami/containers/issues/83267 +IMAGE_projectcontourenvoy_arm64 := docker.io/bitnamilegacy/envoy:1.29.5-debian-12-r0@sha256:0862aad6a034e822ef6cc0e2f2af697ec924d58b8e9acffba48be5b29a9d9776 + +# We are using @inteon's fork of Pebble, which adds support for signing CSRs with +# Ed25519 keys: +# - https://github.com/letsencrypt/pebble/pull/468 +# - https://github.com/inteon/pebble/tree/add_Ed25519_support +PEBBLE_COMMIT = 8318667fcd32f96579c45ee64c747d52519f0cdc + +LOCALIMAGE_pebble := local/pebble:local +LOCALIMAGE_vaultretagged := local/vault:local +LOCALIMAGE_samplewebhook := local/samplewebhook:local IMAGE_kind_amd64 := $(shell make/cluster.sh --show-image) IMAGE_kind_arm64 := $(IMAGE_kind_amd64) -PEBBLE_COMMIT = ba5f81dd80fa870cbc19326f2d5a46f45f0b5ee3 - # TODO: considering moving the installation commands in this file to separate scripts for readability # Once that is done, we can consume this variable from ./make/config/lib.sh SERVICE_IP_PREFIX = 10.0.0 +# This variable is exported so that the Vault add-on in the E2E tests can set +# the image reference of the locally loaded Docker image when it installs the +# Vault Helm chart. +# The Vault Docker image is loaded into kind by `make e2e-setup`. +export E2E_VAULT_IMAGE := $(LOCALIMAGE_vaultretagged) + .PHONY: e2e-setup-kind ## Create a Kubernetes cluster using Kind, which is required for `make e2e`. ## The Kind image is pre-pulled to avoid 'kind create' from blocking other make @@ -55,7 +87,7 @@ e2e-setup-kind: kind-exists # This is the actual target that creates the kind cluster. # -# The presence of the file $(BINDIR)/scratch/kind-exists indicates that your kube +# The presence of the file $(bin_dir)/scratch/kind-exists indicates that your kube # config's current context points to a kind cluster. The file contains the # name of the kind cluster. # @@ -63,13 +95,13 @@ e2e-setup-kind: kind-exists # used as a prerequisite. If we were to use .PHONY, then the file's # timestamp would not be used to check whether targets should be rebuilt, # and they would get constantly rebuilt. -$(BINDIR)/scratch/kind-exists: make/config/kind/cluster.yaml preload-kind-image make/cluster.sh FORCE | $(BINDIR)/scratch $(NEEDS_KIND) $(NEEDS_KUBECTL) $(NEEDS_YQ) +$(bin_dir)/scratch/kind-exists: make/config/kind/cluster.yaml preload-kind-image make/cluster.sh FORCE | $(bin_dir)/scratch $(NEEDS_KIND) $(NEEDS_KUBECTL) $(NEEDS_YQ) @$(eval KIND_CLUSTER_NAME ?= kind) @make/cluster.sh --name $(KIND_CLUSTER_NAME) - @if [ "$(shell cat $@ 2>/dev/null)" != kind ]; then echo kind > $@; else touch $@; fi + @if [ "$(shell cat $@ 2>/dev/null)" != $(KIND_CLUSTER_NAME) ]; then echo $(KIND_CLUSTER_NAME) > $@; else touch $@; fi .PHONY: kind-exists -kind-exists: $(BINDIR)/scratch/kind-exists +kind-exists: $(bin_dir)/scratch/kind-exists # Component Used in IP A record in bind # --------- ------- -- ---------------- @@ -82,7 +114,7 @@ kind-exists: $(BINDIR)/scratch/kind-exists ## created. ## ## @category Development -e2e-setup: e2e-setup-gatewayapi e2e-setup-certmanager e2e-setup-kyverno e2e-setup-vault e2e-setup-bind e2e-setup-sampleexternalissuer e2e-setup-samplewebhook e2e-setup-pebble e2e-setup-ingressnginx e2e-setup-projectcontour +e2e-setup: e2e-setup-gatewayapi e2e-setup-certmanager e2e-setup-vault e2e-setup-bind e2e-setup-sampleexternalissuer e2e-setup-samplewebhook e2e-setup-pebble e2e-setup-ingressnginx e2e-setup-projectcontour # The function "image-tar" returns the path to the image tarball for a given # image name. For example: @@ -91,7 +123,7 @@ e2e-setup: e2e-setup-gatewayapi e2e-setup-certmanager e2e-setup-kyverno e2e-setu # # returns the following path: # -# $(BINDIR)/downloaded/containers/amd64/docker.io/traefik+2.4.9@sha256+bfba204252.tar +# $(bin_dir)/downloaded/containers/amd64/docker.io/traefik+2.4.9@sha256+bfba204252.tar # <---> <---------------------------------------> # CRI_ARCH IMAGE_kyverno_amd64 # (with ":" replaced with "+") @@ -101,56 +133,99 @@ e2e-setup: e2e-setup-gatewayapi e2e-setup-certmanager e2e-setup-kyverno e2e-setu # in image names. # # When an image isn't available, i.e., IMAGE_imagename_arm64 is empty, we still -# return a string of the form "$(BINDIR)/downloaded/containers/amd64/missing-imagename.tar". +# return a string of the form "$(bin_dir)/downloaded/containers/amd64/missing-imagename.tar". define image-tar -$(BINDIR)/downloaded/containers/$(CRI_ARCH)/$(if $(IMAGE_$(1)_$(CRI_ARCH)),$(subst :,+,$(IMAGE_$(1)_$(CRI_ARCH))),missing-$(1)).tar +$(bin_dir)/downloaded/containers/$(CRI_ARCH)/$(if $(IMAGE_$(1)_$(CRI_ARCH)),$(subst :,+,$(IMAGE_$(1)_$(CRI_ARCH))),missing-$(1)).tar +endef + +# The function "local-image-tar" returns the path to the image tarball for a given local +# image name. For example: +# +# $(call local-image-tar, samplewebhook) +# +# returns the following path: +# +# $(bin_dir)/containers/samplewebhook+local.tar +# <---------------------> +# LOCALIMAGE_samplewebhook +# (with ":" replaced with "+") +# +# Note the "+" signs. We replace all the "+" with ":" because ":" can't be used +# in make targets. The "+" replacement is safe since it isn't a valid character +# in image names. +# +# When an image isn't available, i.e., IMAGE_imagename is empty, we still +# return a string of the form "$(bin_dir)/containers/missing-imagename.tar". +define local-image-tar +$(bin_dir)/containers/$(if $(LOCALIMAGE_$(1)),$(subst :,+,$(LOCALIMAGE_$(1))),missing-$(1)).tar endef # Let's separate the pulling of the Kind image so that more tasks can be # run in parallel when running "make -j e2e-setup". In CI, the Docker # engine being stripped on every job, we save the kind image to -# "$(BINDIR)/downloads". Side note: we don't use "$(CI)" directly since we would +# "$(bin_dir)/downloads". Side note: we don't use "$(CI)" directly since we would # get the message "warning: undefined variable 'CI'". .PHONY: preload-kind-image ifeq ($(shell printenv CI),) -preload-kind-image: | $(NEEDS_CRANE) +preload-kind-image: | $(NEEDS_CTR) @$(CTR) inspect $(IMAGE_kind_$(CRI_ARCH)) 2>/dev/null >&2 || (set -x; $(CTR) pull $(IMAGE_kind_$(CRI_ARCH))) else -preload-kind-image: $(call image-tar,kind) | $(NEEDS_CRANE) +preload-kind-image: $(call image-tar,kind) | $(NEEDS_CTR) $(CTR) inspect $(IMAGE_kind_$(CRI_ARCH)) 2>/dev/null >&2 || $(CTR) load -i $< endif -LOAD_TARGETS=load-$(call image-tar,ingressnginx) load-$(call image-tar,kyverno) load-$(call image-tar,kyvernopre) load-$(call image-tar,vault) load-$(call image-tar,bind) load-$(call image-tar,projectcontour) load-$(call image-tar,sampleexternalissuer) load-$(call image-tar,vaultretagged) load-$(BINDIR)/downloaded/containers/$(CRI_ARCH)/pebble.tar load-$(BINDIR)/downloaded/containers/$(CRI_ARCH)/samplewebhook.tar load-$(BINDIR)/containers/cert-manager-controller-linux-$(CRI_ARCH).tar load-$(BINDIR)/containers/cert-manager-acmesolver-linux-$(CRI_ARCH).tar load-$(BINDIR)/containers/cert-manager-cainjector-linux-$(CRI_ARCH).tar load-$(BINDIR)/containers/cert-manager-webhook-linux-$(CRI_ARCH).tar load-$(BINDIR)/containers/cert-manager-ctl-linux-$(CRI_ARCH).tar +LOAD_TARGETS=load-$(call image-tar,ingressnginx) load-$(call image-tar,kyverno) load-$(call image-tar,kyvernopre) load-$(call image-tar,bind) load-$(call image-tar,projectcontour) load-$(call image-tar,projectcontourenvoy) load-$(call image-tar,sampleexternalissuer) load-$(call local-image-tar,vaultretagged) load-$(call local-image-tar,pebble) load-$(call local-image-tar,samplewebhook) load-$(bin_dir)/containers/cert-manager-controller-linux-$(CRI_ARCH).tar load-$(bin_dir)/containers/cert-manager-acmesolver-linux-$(CRI_ARCH).tar load-$(bin_dir)/containers/cert-manager-cainjector-linux-$(CRI_ARCH).tar load-$(bin_dir)/containers/cert-manager-webhook-linux-$(CRI_ARCH).tar load-$(bin_dir)/containers/cert-manager-startupapicheck-linux-$(CRI_ARCH).tar .PHONY: $(LOAD_TARGETS) -$(LOAD_TARGETS): load-%: % $(BINDIR)/scratch/kind-exists | $(NEEDS_KIND) - $(KIND) load image-archive --name=$(shell cat $(BINDIR)/scratch/kind-exists) $* +$(LOAD_TARGETS): load-%: % $(bin_dir)/scratch/kind-exists | $(NEEDS_KIND) + $(KIND) load image-archive --name=$(shell cat $(bin_dir)/scratch/kind-exists) $* +# Download a single-arch image +# +# The input variable IMAGE_example_ARCH must contain the digest of the single-arch image manifest, +# NOT the multi-arch manifest. +# # We use crane instead of docker when pulling images, which saves some time # since we don't care about having the image available to docker. # # We don't pull using both the digest and tag because crane replaces the # tag with "i-was-a-digest". We still check that the downloaded image # matches the digest. -$(call image-tar,kyverno) $(call image-tar,kyvernopre) $(call image-tar,bind) $(call image-tar,projectcontour) $(call image-tar,sampleexternalissuer) $(call image-tar,vault) $(call image-tar,ingressnginx): $(BINDIR)/downloaded/containers/$(CRI_ARCH)/%.tar: | $(NEEDS_CRANE) +# +# We check that the remote image tag and digest still match what is pinned in +# the `IMAGE_example_arch` variables (above). +# This is useful because: +# 1. It tells us if the image maintainers have deliberately or maliciously +# pushed a different image and re-used an existing tag. +# 2. It makes it easy to learn the new digest when updating the pinned image +# tag. The rule will fail and the new digest will be printed out. +# 3. It prevents us accidentally using the wrong digest when we pin the images +# in the variables above. +$(call image-tar,vault) $(call image-tar,kyverno) $(call image-tar,kyvernopre) $(call image-tar,bind) $(call image-tar,projectcontour) $(call image-tar,projectcontourenvoy) $(call image-tar,sampleexternalissuer) $(call image-tar,ingressnginx): $(bin_dir)/downloaded/containers/$(CRI_ARCH)/%.tar: | $(NEEDS_CRANE) @$(eval IMAGE=$(subst +,:,$*)) @$(eval IMAGE_WITHOUT_DIGEST=$(shell cut -d@ -f1 <<<"$(IMAGE)")) @$(eval DIGEST=$(subst $(IMAGE_WITHOUT_DIGEST)@,,$(IMAGE))) @mkdir -p $(dir $@) - diff <(echo "$(DIGEST) -" | cut -d: -f2) <($(CRANE) manifest --platform=linux/$(CRI_ARCH) $(IMAGE) | sha256sum) + diff <(echo "$(DIGEST) -" | cut -d: -f2) <($(CRANE) manifest --platform=linux/$(CRI_ARCH) $(IMAGE_WITHOUT_DIGEST) | sha256sum) $(CRANE) pull $(IMAGE_WITHOUT_DIGEST) $@ --platform=linux/$(CRI_ARCH) -# Same as above, except it supports multiarch images. -$(call image-tar,kind): $(BINDIR)/downloaded/containers/$(CRI_ARCH)/%.tar: | $(NEEDS_CRANE) - @$(eval IMAGE=$(subst +,:,$*)) - @$(eval IMAGE_WITHOUT_DIGEST=$(shell cut -d@ -f1 <<<"$(IMAGE)")) - @$(eval DIGEST=$(subst $(IMAGE_WITHOUT_DIGEST)@,,$(IMAGE))) +# Download the Kind node image +# +# This is handled differently from the other image downloads, because: +# 1. The pinned Kind image references are automatically generated using +# `hack/latest-kind-images.sh`. +# 2. It uses digests that point to the multi-arch manifest, rather than the +# actual image. +# 3. The Kind image tags DO change; each new Kind release has a set of Kind node +# images tagged using the Kubernetes version. Subsequent Kind releases may +# have an incompatible Kind node image format, but re-use the same Kubernetes +# version tags. +$(call image-tar,kind): $(NEEDS_CRANE) @mkdir -p $(dir $@) - diff <(echo "$(DIGEST) -" | cut -d: -f2) <($(CRANE) manifest $(IMAGE) | sha256sum) - $(CRANE) pull $(IMAGE_WITHOUT_DIGEST) $@ --platform=linux/$(CRI_ARCH) + $(CRANE) pull $(IMAGE_kind_$(CRI_ARCH)) $@ --platform linux/$(CRI_ARCH) # Since we dynamically install Vault via Helm during the end-to-end tests, # we need its image to be retagged to a well-known tag "local/vault:local". -$(call image-tar,vaultretagged): $(call image-tar,vault) +$(call local-image-tar,vaultretagged): $(call image-tar,vault) @mkdir -p /tmp/vault $(dir $@) tar xf $< -C /tmp/vault cat /tmp/vault/manifest.json | jq '.[0].RepoTags |= ["local/vault:local"]' -r > /tmp/vault/temp @@ -158,7 +233,38 @@ $(call image-tar,vaultretagged): $(call image-tar,vault) tar cf $@ -C /tmp/vault . @rm -rf /tmp/vault -FEATURE_GATES ?= AdditionalCertificateOutputFormats=true,ExperimentalCertificateSigningRequestControllers=true,ExperimentalGatewayAPISupport=true,ServerSideApply=true,LiteralCertificateSubject=true +FEATURE_GATES ?= ExperimentalCertificateSigningRequestControllers=true,ExperimentalGatewayAPISupport=true,ServerSideApply=true,LiteralCertificateSubject=true,UseCertificateRequestBasicConstraints=true,NameConstraints=true,OtherNames=true + +## Set this environment variable to a non empty string to cause cert-manager to +## be installed using best-practice configuration settings, and to install +## Kyverno with a policy that will cause cert-manager installation to fail +## unless it conforms to the documented best-practices. +## See https://cert-manager.io/docs/installation/best-practice/ for context. +## +## make E2E_SETUP_OPTION_BESTPRACTICE=true e2e-setup +## +## @category Development +E2E_SETUP_OPTION_BESTPRACTICE ?= +## The URL of the Helm values file containing best-practice configuration values +## which will allow cert-manager to be installed and used in a cluster where +## Kyverno and the policies in make/config/kyverno have been applied. +## +## @category Development +E2E_SETUP_OPTION_BESTPRACTICE_HELM_VALUES_URL ?= https://raw.githubusercontent.com/cert-manager/website/ea5db62772e6b9d1430b9d63f581e74d5c18b627/public/docs/installation/best-practice/values.best-practice.yaml +E2E_SETUP_OPTION_BESTPRACTICE_HELM_VALUES_URL_SUM := $(shell sha256sum <<<$(E2E_SETUP_OPTION_BESTPRACTICE_HELM_VALUES_URL) | cut -d ' ' -f 1) + +## A local Helm values file containing best-practice configuration values. +## It will be downloaded from E2E_SETUP_OPTION_BESTPRACTICE_HELM_VALUES_URL if +## it does not exist. +## +## @category Development +E2E_SETUP_OPTION_BESTPRACTICE_HELM_VALUES_FILE ?= $(bin_dir)/scratch/values-bestpractice-$(E2E_SETUP_OPTION_BESTPRACTICE_HELM_VALUES_URL_SUM).yaml +$(E2E_SETUP_OPTION_BESTPRACTICE_HELM_VALUES_FILE): | $(bin_dir)/scratch + $(CURL) $(E2E_SETUP_OPTION_BESTPRACTICE_HELM_VALUES_URL) -o $@ + +# Dependencies which will be added to e2e-setup-certmanager depending on the +# supplied E2E_SETUP_OPTION_ variables. +E2E_SETUP_OPTION_DEPENDENCIES := $(if $(E2E_SETUP_OPTION_BESTPRACTICE),e2e-setup-kyverno $(E2E_SETUP_OPTION_BESTPRACTICE_HELM_VALUES_FILE)) # In make, there is no way to escape commas or spaces. So we use the # variables $(space) and $(comma) instead. @@ -168,56 +274,114 @@ comma = , # Helm's "--set" interprets commas, which means we want to escape commas # for "--set featureGates". That's why we have "\$(comma)". -feature_gates_controller := $(subst $(space),\$(comma),$(filter AllAlpha=% AllBeta=% AdditionalCertificateOutputFormats=% ValidateCAA=% ExperimentalCertificateSigningRequestControllers=% ExperimentalGatewayAPISupport=% ServerSideApply=% LiteralCertificateSubject=%, $(subst $(comma),$(space),$(FEATURE_GATES)))) -feature_gates_webhook := $(subst $(space),\$(comma),$(filter AllAlpha=% AllBeta=% AdditionalCertificateOutputFormats=% LiteralCertificateSubject=%, $(subst $(comma),$(space),$(FEATURE_GATES)))) -feature_gates_cainjector := $(subst $(space),\$(comma),$(filter AllAlpha=% AllBeta=%, $(subst $(comma),$(space),$(FEATURE_GATES)))) +feature_gates_controller := $(subst $(space),\$(comma),$(filter AllAlpha=% AllBeta=% ExperimentalCertificateSigningRequestControllers=% ExperimentalGatewayAPISupport=% ServerSideApply=% LiteralCertificateSubject=% UseCertificateRequestBasicConstraints=% NameConstraints=% SecretsFilteredCaching=% OtherNames=%, $(subst $(comma),$(space),$(FEATURE_GATES)))) +feature_gates_webhook := $(subst $(space),\$(comma),$(filter AllAlpha=% AllBeta=% LiteralCertificateSubject=% NameConstraints=% OtherNames=%, $(subst $(comma),$(space),$(FEATURE_GATES)))) +feature_gates_cainjector := $(subst $(space),\$(comma),$(filter AllAlpha=% AllBeta=% ServerSideApply=% CAInjectorMerging=%, $(subst $(comma),$(space),$(FEATURE_GATES)))) + +# When testing an published chart the repo can be configured using +# E2E_CERT_MANAGER_REPO +E2E_CERT_MANAGER_REPO ?= https://charts.jetstack.io +# When testing an published chart the chart name can be configured using +# E2E_CERT_MANAGER_CHART. This can also be set to a local path to test a +# downloaded chart +E2E_CERT_MANAGER_CHART ?= cert-manager +# When testing an published chart, default to the latest release +E2E_CERT_MANAGER_VERSION ?= + +# Example running E2E tests against a downloaded chart: +# E2E_EXISTING_CHART=true E2E_CERT_MANAGER_CHART=./cert-manager-v1.14.2.tgz make e2e-setup +# make e2e +# +# Example running E2E test against published version of a chart: +# E2E_EXISTING_CHART=true E2E_CERT_MANAGER_VERSION=1.14.2 make e2e-setup +# make e2e +# Install cert-manager with E2E specific images and deployment settings. +# The values.best-practice.yaml file is applied for compliance with the +# Kyverno policy which has been installed in a prerequisite target. +# # TODO: move these commands to separate scripts for readability # # ⚠ The following components are installed *before* cert-manager: # * GatewayAPI: so that cert-manager can watch those CRs. -# * Kyverno: so that it can check the cert-manager manifests against the policy in `config/kyverno/`. +# * Kyverno: so that it can check the cert-manager manifests against the policy in `config/kyverno/` +# (only installed if E2E_SETUP_OPTION_BESTPRACTICE is set). +ifdef E2E_EXISTING_CHART +.PHONY: e2e-setup-certmanager +e2e-setup-certmanager: e2e-setup-gatewayapi $(E2E_SETUP_OPTION_DEPENDENCIES) $(bin_dir)/scratch/kind-exists | $(NEEDS_KUBECTL) $(NEEDS_KIND) $(NEEDS_HELM) + $(HELM) upgrade \ + --install \ + --create-namespace \ + --wait \ + --namespace cert-manager \ + --repo $(E2E_CERT_MANAGER_REPO) \ + $(addprefix --version=,$(E2E_CERT_MANAGER_VERSION)) \ + --set crds.enabled=true \ + --set featureGates="$(feature_gates_controller)" \ + --set "extraArgs={--kube-api-qps=9000,--kube-api-burst=9000,--concurrent-workers=200,--enable-gateway-api}" \ + --set webhook.featureGates="$(feature_gates_webhook)" \ + --set "cainjector.extraArgs={--feature-gates=$(feature_gates_cainjector)}" \ + --set "dns01RecursiveNameservers=$(SERVICE_IP_PREFIX).16:53" \ + --set "dns01RecursiveNameserversOnly=true" \ + $(if $(E2E_SETUP_OPTION_BESTPRACTICE),--values=$(E2E_SETUP_OPTION_BESTPRACTICE_HELM_VALUES_FILE)) \ + cert-manager $(E2E_CERT_MANAGER_CHART) >/dev/null +else .PHONY: e2e-setup-certmanager -e2e-setup-certmanager: $(BINDIR)/cert-manager.tgz $(foreach binaryname,controller acmesolver cainjector webhook ctl,$(BINDIR)/containers/cert-manager-$(binaryname)-linux-$(CRI_ARCH).tar) $(foreach binaryname,controller acmesolver cainjector webhook ctl,load-$(BINDIR)/containers/cert-manager-$(binaryname)-linux-$(CRI_ARCH).tar) e2e-setup-gatewayapi e2e-setup-kyverno $(BINDIR)/scratch/kind-exists | $(NEEDS_KUBECTL) $(NEEDS_KIND) $(NEEDS_HELM) - @$(eval TAG = $(shell tar xfO $(BINDIR)/containers/cert-manager-controller-linux-$(CRI_ARCH).tar manifest.json | jq '.[0].RepoTags[0]' -r | cut -d: -f2)) +e2e-setup-certmanager: $(bin_dir)/cert-manager.tgz $(foreach binaryname,controller acmesolver cainjector webhook startupapicheck,$(bin_dir)/containers/cert-manager-$(binaryname)-linux-$(CRI_ARCH).tar) $(foreach binaryname,controller acmesolver cainjector webhook startupapicheck,load-$(bin_dir)/containers/cert-manager-$(binaryname)-linux-$(CRI_ARCH).tar) e2e-setup-gatewayapi $(E2E_SETUP_OPTION_DEPENDENCIES) $(bin_dir)/scratch/kind-exists | $(NEEDS_KUBECTL) $(NEEDS_KIND) $(NEEDS_HELM) + @$(eval TAG = $(shell tar xfO $(bin_dir)/containers/cert-manager-controller-linux-$(CRI_ARCH).tar manifest.json | jq '.[0].RepoTags[0]' -r | cut -d: -f2)) $(HELM) upgrade \ --install \ --create-namespace \ --wait \ --namespace cert-manager \ - --set image.repository="$(shell tar xfO $(BINDIR)/containers/cert-manager-controller-linux-$(CRI_ARCH).tar manifest.json | jq '.[0].RepoTags[0]' -r | cut -d: -f1)" \ - --set cainjector.image.repository="$(shell tar xfO $(BINDIR)/containers/cert-manager-cainjector-linux-$(CRI_ARCH).tar manifest.json | jq '.[0].RepoTags[0]' -r | cut -d: -f1)" \ - --set webhook.image.repository="$(shell tar xfO $(BINDIR)/containers/cert-manager-webhook-linux-$(CRI_ARCH).tar manifest.json | jq '.[0].RepoTags[0]' -r | cut -d: -f1)" \ - --set startupapicheck.image.repository="$(shell tar xfO $(BINDIR)/containers/cert-manager-ctl-linux-$(CRI_ARCH).tar manifest.json | jq '.[0].RepoTags[0]' -r | cut -d: -f1)" \ + --set image.repository="$(shell tar xfO $(bin_dir)/containers/cert-manager-controller-linux-$(CRI_ARCH).tar manifest.json | jq '.[0].RepoTags[0]' -r | cut -d: -f1)" \ + --set cainjector.image.repository="$(shell tar xfO $(bin_dir)/containers/cert-manager-cainjector-linux-$(CRI_ARCH).tar manifest.json | jq '.[0].RepoTags[0]' -r | cut -d: -f1)" \ + --set webhook.image.repository="$(shell tar xfO $(bin_dir)/containers/cert-manager-webhook-linux-$(CRI_ARCH).tar manifest.json | jq '.[0].RepoTags[0]' -r | cut -d: -f1)" \ + --set acmesolver.image.repository="$(shell tar xfO $(bin_dir)/containers/cert-manager-acmesolver-linux-$(CRI_ARCH).tar manifest.json | jq '.[0].RepoTags[0]' -r | cut -d: -f1)" \ + --set startupapicheck.image.repository="$(shell tar xfO $(bin_dir)/containers/cert-manager-startupapicheck-linux-$(CRI_ARCH).tar manifest.json | jq '.[0].RepoTags[0]' -r | cut -d: -f1)" \ --set image.tag="$(TAG)" \ --set cainjector.image.tag="$(TAG)" \ --set webhook.image.tag="$(TAG)" \ + --set acmesolver.image.tag="$(TAG)" \ --set startupapicheck.image.tag="$(TAG)" \ - --set installCRDs=true \ + --set crds.enabled=true \ --set featureGates="$(feature_gates_controller)" \ - --set "webhook.extraArgs={--feature-gates=$(feature_gates_webhook)}" \ + --set "extraArgs={--kube-api-qps=9000,--kube-api-burst=9000,--concurrent-workers=200,--enable-gateway-api}" \ + --set webhook.featureGates="$(feature_gates_webhook)" \ --set "cainjector.extraArgs={--feature-gates=$(feature_gates_cainjector)}" \ - --set "extraArgs={--dns01-recursive-nameservers=$(SERVICE_IP_PREFIX).16:53,--dns01-recursive-nameservers-only=true,--acme-http01-solver-image=cert-manager-acmesolver-$(CRI_ARCH):$(TAG)}" \ + --set "dns01RecursiveNameservers=$(SERVICE_IP_PREFIX).16:53" \ + --set "dns01RecursiveNameserversOnly=true" \ + $(if $(E2E_SETUP_OPTION_BESTPRACTICE),--values=$(E2E_SETUP_OPTION_BESTPRACTICE_HELM_VALUES_FILE)) \ cert-manager $< >/dev/null +endif .PHONY: e2e-setup-bind -e2e-setup-bind: $(call image-tar,bind) load-$(call image-tar,bind) $(wildcard make/config/bind/*.yaml) $(BINDIR)/scratch/kind-exists | $(NEEDS_KUBECTL) +e2e-setup-bind: $(call image-tar,bind) load-$(call image-tar,bind) $(wildcard make/config/bind/*.yaml) $(bin_dir)/scratch/kind-exists | $(NEEDS_KUBECTL) @$(eval IMAGE = $(shell tar xfO $< manifest.json | jq '.[0].RepoTags[0]' -r)) $(KUBECTL) get ns bind 2>/dev/null >&2 || $(KUBECTL) create ns bind sed -e "s|{SERVICE_IP_PREFIX}|$(SERVICE_IP_PREFIX)|g" -e "s|{IMAGE}|$(IMAGE)|g" make/config/bind/*.yaml | $(KUBECTL) apply -n bind -f - >/dev/null .PHONY: e2e-setup-gatewayapi -e2e-setup-gatewayapi: $(BINDIR)/downloaded/gateway-api@$(GATEWAY_API_VERSION) $(BINDIR)/scratch/kind-exists $(NEEDS_KUBECTL) - $(KUBECTL) kustomize $/dev/null +e2e-setup-gatewayapi: $(bin_dir)/scratch/gateway-api-$(GATEWAY_API_VERSION).yaml $(bin_dir)/scratch/kind-exists $(NEEDS_KUBECTL) + $(KUBECTL) apply --server-side -f $(bin_dir)/scratch/gateway-api-$(GATEWAY_API_VERSION).yaml > /dev/null # v1 NGINX-Ingress by default only watches Ingresses with Ingress class -# defined. When configuring solver block for ACME HTTTP01 challenge on an +# defined. When configuring solver block for ACME HTTP01 challenge on an # ACME issuer, cert-manager users can currently specify either an Ingress # name or a class. We also e2e test these two ways of creating Ingresses # with ingress-shim. For the ingress controller to watch our Ingresses that # don't have a class, we pass a --watch-ingress-without-class flag: # https://github.com/kubernetes/ingress-nginx/blob/main/charts/ingress-nginx/values.yaml#L64-L67 +# +# Versions of ingress-nginx >=1.8.0 support a strict-validate-path-type +# configuration option which, when enabled, disallows . (dot) in the path value. +# This is a bug which makes it impossible to use various legitimate URL paths, +# including the http:///.well-known/acme-challenge/ URLs +# used for ACME HTTP01. To make matters worse, the buggy validation is enabled +# by default in ingress-nginx >= 1.12.0. +# We disable it by passing a `--set-string controller.config.strict-validate-path-type=false` flag. +# https://github.com/kubernetes/ingress-nginx/issues/11176 .PHONY: e2e-setup-ingressnginx e2e-setup-ingressnginx: $(call image-tar,ingressnginx) load-$(call image-tar,ingressnginx) | $(NEEDS_HELM) @$(eval TAG=$(shell tar xfO $< manifest.json | jq '.[0].RepoTags[0]' -r | cut -d: -f2)) @@ -225,22 +389,24 @@ e2e-setup-ingressnginx: $(call image-tar,ingressnginx) load-$(call image-tar,ing $(HELM) upgrade \ --install \ --wait \ - --version 4.0.10 \ + --version 4.12.3 \ --namespace ingress-nginx \ --create-namespace \ --set controller.image.tag=$(TAG) \ + --set controller.image.registry=registry.k8s.io \ --set controller.image.digest= \ --set controller.image.pullPolicy=Never \ --set controller.service.clusterIP=${SERVICE_IP_PREFIX}.15 \ --set controller.service.type=ClusterIP \ --set controller.config.no-tls-redirect-locations= \ - --set admissionWebhooks.enabled=false \ + --set-string controller.config.strict-validate-path-type=false \ + --set admissionWebhooks.enabled=true \ --set controller.admissionWebhooks.enabled=true \ --set controller.watchIngressWithoutClass=true \ ingress-nginx ingress-nginx/ingress-nginx >/dev/null .PHONY: e2e-setup-kyverno -e2e-setup-kyverno: $(call image-tar,kyverno) $(call image-tar,kyvernopre) load-$(call image-tar,kyverno) load-$(call image-tar,kyvernopre) make/config/kyverno/policy.yaml $(BINDIR)/scratch/kind-exists | $(NEEDS_KUBECTL) $(NEEDS_HELM) +e2e-setup-kyverno: $(call image-tar,kyverno) $(call image-tar,kyvernopre) load-$(call image-tar,kyverno) load-$(call image-tar,kyvernopre) make/config/kyverno/policy.yaml $(bin_dir)/scratch/kind-exists | $(NEEDS_KUBECTL) $(NEEDS_HELM) @$(eval TAG=$(shell tar xfO $< manifest.json | jq '.[0].RepoTags[0]' -r | cut -d: -f2)) $(HELM) repo add kyverno --force-update https://kyverno.github.io/kyverno/ >/dev/null $(HELM) upgrade \ @@ -248,27 +414,35 @@ e2e-setup-kyverno: $(call image-tar,kyverno) $(call image-tar,kyvernopre) load-$ --wait \ --namespace kyverno \ --create-namespace \ - --version v2.5.1 \ - --set image.tag=v1.7.1 \ - --set initImage.tag=v1.7.1 \ - --set image.pullPolicy=Never \ - --set initImage.pullPolicy=Never \ + --version 3.2.4 \ + --set webhooksCleanup.enabled=false \ + --set reportsController.enabled=false \ + --set cleanupController.enabled=false \ + --set backgroundController.enabled=false \ + --set admissionController.container.image.tag=$(TAG) \ + --set admissionController.container.image.pullPolicy=Never \ + --set admissionController.initContainer.image.tag=$(TAG) \ + --set admissionController.initContainer.image.pullPolicy=Never \ kyverno kyverno/kyverno >/dev/null @$(KUBECTL) create ns cert-manager >/dev/null 2>&1 || true - $(KUBECTL) apply -f make/config/kyverno/policy.yaml >/dev/null + $(KUBECTL) apply --server-side -f make/config/kyverno/policy.yaml >/dev/null -$(BINDIR)/downloaded/pebble-$(PEBBLE_COMMIT).tar.gz: | $(BINDIR)/downloaded - $(CURL) https://github.com/letsencrypt/pebble/archive/$(PEBBLE_COMMIT).tar.gz -o $@ +# We are using @inteon's fork of Pebble, which adds support for signing CSRs with +# Ed25519 keys: +# - https://github.com/letsencrypt/pebble/pull/468 +# - https://github.com/inteon/pebble/tree/add_Ed25519_support +$(bin_dir)/downloaded/pebble-$(PEBBLE_COMMIT).tar.gz: | $(bin_dir)/downloaded + $(CURL) https://github.com/inteon/pebble/archive/$(PEBBLE_COMMIT).tar.gz -o $@ # We can't use GOBIN with "go install" because cross-compilation is not # possible with go install. That's a problem when cross-compiling for # linux/arm64 when running on darwin/arm64. -$(BINDIR)/downloaded/containers/$(CRI_ARCH)/pebble/pebble: $(BINDIR)/downloaded/pebble-$(PEBBLE_COMMIT).tar.gz | $(NEEDS_GO) +$(call local-image-tar,pebble).dir/pebble: $(bin_dir)/downloaded/pebble-$(PEBBLE_COMMIT).tar.gz | $(NEEDS_GO) @mkdir -p $(dir $@) - tar xzf $< -C $(dir $@) - cd $(dir $@)pebble-$(PEBBLE_COMMIT) && GOOS=linux GOARCH=$(CRI_ARCH) CGO_ENABLED=$(CGO_ENABLED) GOMAXPROCS=$(GOBUILDPROCS) $(GOBUILD) $(GOFLAGS) -o $(CURDIR)/$@ ./cmd/pebble + tar xzf $< -C /tmp + cd /tmp/pebble-$(PEBBLE_COMMIT) && GOOS=linux GOARCH=$(CRI_ARCH) CGO_ENABLED=$(CGO_ENABLED) GOMAXPROCS=$(GOBUILDPROCS) $(GOBUILD) $(GOFLAGS) -o $(CURDIR)/$@ ./cmd/pebble -$(BINDIR)/downloaded/containers/$(CRI_ARCH)/pebble.tar: $(BINDIR)/downloaded/containers/$(CRI_ARCH)/pebble/pebble make/config/pebble/Containerfile.pebble +$(call local-image-tar,pebble): $(call local-image-tar,pebble).dir/pebble make/config/pebble/Containerfile.pebble | $(NEEDS_CTR) @$(eval BASE := BASE_IMAGE_controller-linux-$(CRI_ARCH)) $(CTR) build --quiet \ -f make/config/pebble/Containerfile.pebble \ @@ -278,7 +452,7 @@ $(BINDIR)/downloaded/containers/$(CRI_ARCH)/pebble.tar: $(BINDIR)/downloaded/con $(CTR) save local/pebble:local -o $@ >/dev/null .PHONY: e2e-setup-pebble -e2e-setup-pebble: load-$(BINDIR)/downloaded/containers/$(CRI_ARCH)/pebble.tar $(BINDIR)/scratch/kind-exists | $(NEEDS_HELM) +e2e-setup-pebble: load-$(call local-image-tar,pebble) $(bin_dir)/scratch/kind-exists | $(NEEDS_HELM) $(HELM) upgrade \ --install \ --wait \ @@ -286,11 +460,11 @@ e2e-setup-pebble: load-$(BINDIR)/downloaded/containers/$(CRI_ARCH)/pebble.tar $( --create-namespace \ pebble make/config/pebble/chart >/dev/null -$(BINDIR)/downloaded/containers/$(CRI_ARCH)/samplewebhook/samplewebhook: make/config/samplewebhook/sample/main.go | $(NEEDS_GO) +$(call local-image-tar,samplewebhook).dir/samplewebhook: make/config/samplewebhook/sample/main.go | $(NEEDS_GO) @mkdir -p $(dir $@) GOOS=linux GOARCH=$(CRI_ARCH) $(GOBUILD) -o $@ $(GOFLAGS) make/config/samplewebhook/sample/main.go -$(BINDIR)/downloaded/containers/$(CRI_ARCH)/samplewebhook.tar: $(BINDIR)/downloaded/containers/$(CRI_ARCH)/samplewebhook/samplewebhook make/config/samplewebhook/Containerfile.samplewebhook +$(call local-image-tar,samplewebhook): $(call local-image-tar,samplewebhook).dir/samplewebhook make/config/samplewebhook/Containerfile.samplewebhook | $(NEEDS_CTR) @$(eval BASE := BASE_IMAGE_controller-linux-$(CRI_ARCH)) $(CTR) build --quiet \ -f make/config/samplewebhook/Containerfile.samplewebhook \ @@ -300,7 +474,7 @@ $(BINDIR)/downloaded/containers/$(CRI_ARCH)/samplewebhook.tar: $(BINDIR)/downloa $(CTR) save local/samplewebhook:local -o $@ >/dev/null .PHONY: e2e-setup-samplewebhook -e2e-setup-samplewebhook: load-$(BINDIR)/downloaded/containers/$(CRI_ARCH)/samplewebhook.tar e2e-setup-certmanager $(BINDIR)/scratch/kind-exists | $(NEEDS_HELM) +e2e-setup-samplewebhook: load-$(call local-image-tar,samplewebhook) e2e-setup-certmanager $(bin_dir)/scratch/kind-exists | $(NEEDS_HELM) $(HELM) upgrade \ --install \ --wait \ @@ -309,55 +483,58 @@ e2e-setup-samplewebhook: load-$(BINDIR)/downloaded/containers/$(CRI_ARCH)/sample samplewebhook make/config/samplewebhook/chart >/dev/null .PHONY: e2e-setup-projectcontour -e2e-setup-projectcontour: $(call image-tar,projectcontour) load-$(call image-tar,projectcontour) make/config/projectcontour/gateway.yaml make/config/projectcontour/contour.yaml $(BINDIR)/scratch/kind-exists | $(NEEDS_HELM) $(NEEDS_KUBECTL) - @$(eval TAG=$(shell tar xfO $< manifest.json | jq '.[0].RepoTags[0]' -r | cut -d: -f2)) +e2e-setup-projectcontour: $(call image-tar,projectcontour) load-$(call image-tar,projectcontour) $(call image-tar,projectcontourenvoy) load-$(call image-tar,projectcontourenvoy) make/config/projectcontour/gateway.yaml make/config/projectcontour/contour.yaml $(bin_dir)/scratch/kind-exists | $(NEEDS_HELM) $(NEEDS_KUBECTL) + @$(eval CONTOUR_TAG=$(shell tar xfO $< manifest.json | jq '.[0].RepoTags[0]' -r | cut -d: -f2)) + @$(eval ENVOY_TAG=$(shell tar xfO $(call image-tar,projectcontourenvoy) manifest.json | jq '.[0].RepoTags[0]' -r | cut -d: -f2)) $(HELM) repo add bitnami --force-update https://charts.bitnami.com/bitnami >/dev/null + # Warning: When upgrading the version of this helm chart, bear in mind that the IMAGE_projectcontour_* images above might need to be updated, too. + # Each helm chart version in the bitnami repo corresponds to an underlying application version. Check application versions and chart versions with: + # $$ helm search repo bitnami -l | grep -E "contour[^-]" + # + # TODO(wallrj): The free version of the Bitnami contour chart and the + # associated images are deprecated. We are using the docker.io/bitnamilegacy + # registry as a stop gap measure until we can move to a different chart. See: + # https://github.com/bitnami/charts/blob/main/bitnami/contour/README.md#%EF%B8%8F-important-notice-upcoming-changes-to-the-bitnami-catalog $(HELM) upgrade \ --install \ --wait \ - --version 7.8.1 \ + --version 18.2.4 \ --namespace projectcontour \ --create-namespace \ --set contour.ingressClass.create=false \ --set contour.ingressClass.default=false \ - --set image.tag=$(TAG) \ - --set image.pullPolicy=Never \ + --set contour.image.registry=ghcr.io \ + --set contour.image.repository=projectcontour/contour \ + --set contour.image.tag=$(CONTOUR_TAG) \ + --set contour.image.pullPolicy=Never \ --set contour.service.type=ClusterIP \ --set contour.service.externalTrafficPolicy="" \ --set envoy.service.type=ClusterIP \ --set envoy.service.externalTrafficPolicy="" \ --set envoy.service.clusterIP=${SERVICE_IP_PREFIX}.14 \ + --set envoy.image.registry=docker.io/bitnamilegacy \ + --set envoy.image.repository=envoy \ + --set envoy.image.tag=$(ENVOY_TAG) \ + --set envoy.image.pullPolicy=Never \ --set-file configInline=make/config/projectcontour/contour.yaml \ projectcontour bitnami/contour >/dev/null - $(KUBECTL) apply -f make/config/projectcontour/gateway.yaml + $(KUBECTL) apply --server-side -f make/config/projectcontour/gateway.yaml .PHONY: e2e-setup-sampleexternalissuer -ifeq ($(CRI_ARCH),amd64) -e2e-setup-sampleexternalissuer: load-$(call image-tar,sampleexternalissuer) $(BINDIR)/scratch/kind-exists | $(NEEDS_KUBECTL) - $(KUBECTL) apply -n sample-external-issuer-system -f https://github.com/cert-manager/sample-external-issuer/releases/download/v0.1.1/install.yaml >/dev/null +e2e-setup-sampleexternalissuer: load-$(call image-tar,sampleexternalissuer) $(bin_dir)/scratch/kind-exists | $(NEEDS_KUBECTL) + $(KUBECTL) apply -n sample-external-issuer-system -f https://github.com/cert-manager/sample-external-issuer/releases/download/v0.4.0/install.yaml >/dev/null $(KUBECTL) patch -n sample-external-issuer-system deployments.apps sample-external-issuer-controller-manager --type=json -p='[{"op": "add", "path": "/spec/template/spec/containers/1/imagePullPolicy", "value": "Never"}]' >/dev/null -else -e2e-setup-sampleexternalissuer: - @printf "\033[0;33mWarning\033[0;0m: skipping the target \033[0;31m$@\033[0;0m because there exists no image for $(CRI_ARCH).\n" >&2 - @printf "The end-to-end tests that rely on sampleexternalissuer will fail. If you are using Docker Desktop,\n" >&2 - @printf "you can force using the amd64 image anyways by running:\n" >&2 - @printf " \033[0;36mmake $@ CRI_ARCH=amd64\033[0;0m\n" >&2 - @printf "Note that this won't if you are using Colima, or Rancher Desktop, or minikube.\n" >&2 -endif # Note that the end-to-end tests are dealing with the Helm installation. We # do not need to Helm install here. .PHONY: e2e-setup-vault -e2e-setup-vault: load-$(call image-tar,vaultretagged) $(BINDIR)/scratch/kind-exists | $(NEEDS_HELM) +e2e-setup-vault: load-$(call local-image-tar,vaultretagged) $(bin_dir)/scratch/kind-exists | $(NEEDS_HELM) # Exported because it needs to flow down to make/e2e.sh. -export ARTIFACTS ?= $(shell pwd)/$(BINDIR)/artifacts +export ARTIFACTS ?= $(shell pwd)/$(bin_dir)/artifacts .PHONY: kind-logs -kind-logs: $(BINDIR)/scratch/kind-exists | $(NEEDS_KIND) +kind-logs: $(bin_dir)/scratch/kind-exists | $(NEEDS_KIND) rm -rf $(ARTIFACTS)/cert-manager-e2e-logs mkdir -p $(ARTIFACTS)/cert-manager-e2e-logs - $(KIND) export logs $(ARTIFACTS)/cert-manager-e2e-logs --name=$(shell cat $(BINDIR)/scratch/kind-exists) - -$(BINDIR)/scratch: - @mkdir -p $@ + $(KIND) export logs $(ARTIFACTS)/cert-manager-e2e-logs --name=$(shell cat $(bin_dir)/scratch/kind-exists) diff --git a/make/e2e.sh b/make/e2e.sh index e3d167ba52d..edfe76f2e46 100755 --- a/make/e2e.sh +++ b/make/e2e.sh @@ -52,7 +52,7 @@ BINDIR=${BINDIR:-$_default_bindir} # | 40 | 26m 26s | 26 | 29m 29s | 3m 3s (hot) | [6][] | # | 50 | interrupted (*) | | | (hot) | [7][] | # -# The startup time is calculated by substracting the "started time" visible +# The startup time is calculated by subtracting the "started time" visible # on the Prow UI with the first line that has a timestamp. This time # depends on whether this Kubernetes node already has a cache or not. # @@ -69,12 +69,18 @@ BINDIR=${BINDIR:-$_default_bindir} # [5]: https://prow.build-infra.jetstack.net/view/gs/jetstack-logs/pr-logs/pull/cert-manager_cert-manager/4968/pull-cert-manager-make-e2e-v1-23/1507011895024947200 # [6]: https://prow.build-infra.jetstack.net/view/gs/jetstack-logs/pr-logs/pull/cert-manager_cert-manager/4968/pull-cert-manager-make-e2e-v1-23/1507019887451574272 # [7]: https://prow.build-infra.jetstack.net/view/gs/jetstack-logs/pr-logs/pull/cert-manager_cert-manager/4968/pull-cert-manager-make-e2e-v1-23/1507040653668782080 -nodes=20 + +nodes=40 + flake_attempts=1 + ginkgo_skip= ginkgo_focus= -feature_gates=AdditionalCertificateOutputFormats=true,ExperimentalCertificateSigningRequestControllers=true,ExperimentalGatewayAPISupport=true + +feature_gates=ExperimentalCertificateSigningRequestControllers=true,ExperimentalGatewayAPISupport=true,LiteralCertificateSubject=true,OtherNames=true + artifacts="./$BINDIR/artifacts" + help() { cat <&2 - - if [[ -z "$ginkgo_skip" ]]; then - ginkgo_skip="Gateway" - else - # duplicates are ok - ginkgo_skip="${ginkgo_skip}|Gateway" - fi - ;; -esac - ginkgo_args=("$@") if [[ -n "$ginkgo_focus" ]]; then ginkgo_args+=(--ginkgo.focus="${ginkgo_focus}"); fi @@ -185,22 +176,28 @@ if [[ "${ginkgo_args[*]}" =~ ginkgo.focus ]]; then ginkgo_args+=(--ginkgo.v --test.v) fi +ginkgo_color= + +if ! should_color; then + ginkgo_color="--no-color" +fi + mkdir -p "$artifacts" export CGO_ENABLED=0 trace ginkgo \ - -tags=e2e_test \ - -procs="$nodes" \ - -output-dir="$artifacts" \ - -junit-report="junit__01.xml" \ - -flake-attempts="$flake_attempts" \ - -timeout="24h" \ + --tags=e2e_test \ + --procs="$nodes" \ + --output-dir="$artifacts" \ + --junit-report="junit__01.xml" \ + --flake-attempts="$flake_attempts" \ + --timeout="1h" \ + $ginkgo_color \ -v \ - -randomize-all \ - -progress \ - -trace \ - -slow-spec-threshold="${GINKGO_SLOW_SPEC_THRESHOLD:-300s}" \ + --randomize-all \ + --trace \ + --poll-progress-after=60s \ ./test/e2e/ \ -- \ --repo-root="$PWD" \ diff --git a/make/git.mk b/make/git.mk index 50f2803c9be..51d55e91cd4 100644 --- a/make/git.mk +++ b/make/git.mk @@ -1,48 +1,43 @@ -RELEASE_VERSION := $(shell git describe --tags --match='v*' --abbrev=14) - -GITCOMMIT := $(shell git rev-parse HEAD) +# 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. IS_TAGGED_RELEASE := $(shell git describe --exact-match HEAD >/dev/null 2>&1 && echo "true" || echo "false") -IS_PRERELEASE := $(shell echo $(RELEASE_VERSION) | grep -qE '^v[0-9]+\.[0-9]+\.[0-9]+$$' - && echo "false" || echo "true") - .PHONY: gitver gitver: - @echo "Release version: \"$(RELEASE_VERSION)\"" + @echo "Release version: \"$(VERSION)\"" @echo "Is tagged release: \"$(IS_TAGGED_RELEASE)\"" @echo "Is prerelease: \"$(IS_PRERELEASE)\"" @echo "Git commit hash: \"$(GITCOMMIT)\"" .PHONY: release-version release-version: - @echo "$(RELEASE_VERSION)" - -# Lists all remote tags on the upstream, which gives tags in format: -# " ref/tags/". Strips commit + tag prefix, filters out tags for v1+, -# and manually removes v1.2.0-alpha.1, since that version's manifest contains -# duplicate CRD resources (2 CRDs with the same name) which in turn can cause problems -# with the versionchecker test. -# Open question: how do we decide when to refresh this target? -$(BINDIR)/scratch/git/upstream-tags.txt: | $(BINDIR)/scratch/git - git ls-remote --tags --refs https://github.com/cert-manager/cert-manager.git | \ - awk '{print $$2;}' | \ - sed 's/refs\/tags\///' | \ - sed -n '/v1.0.0/,$$p' | \ - grep -v "v1.2.0-alpha.1" > $@ + @echo "$(VERSION)" # The file "release-version" gets updated whenever git describe --tags changes. -# This is used by the $(BINDIR)/containers/*.tar.gz targets to make sure that the +# This is used by the $(bin_dir)/containers/*.tar.gz targets to make sure that the # containers, which use the output of "git describe --tags" as their tag, get # rebuilt whenever you check out a different commit. If we didn't do this, the -# Helm chart $(BINDIR)/cert-manager-*.tgz would refer to an image tag that doesn't -# exist in $(BINDIR)/containers/*.tar.gz. +# Helm chart $(bin_dir)/cert-manager-*.tgz would refer to an image tag that doesn't +# exist in $(bin_dir)/containers/*.tar.gz. # # We use FORCE instead of .PHONY because this is a real file that can be used as # a prerequisite. If we were to use .PHONY, then the file's timestamp would not # be used to check whether targets should be rebuilt, and they would get # constantly rebuilt. -$(BINDIR)/release-version: FORCE | $(BINDIR) - @test "$(RELEASE_VERSION)" == "$(shell cat $@ 2>/dev/null)" || echo $(RELEASE_VERSION) > $@ +$(bin_dir)/release-version: FORCE | $(bin_dir) + @test "$(VERSION)" == "$(shell cat $@ 2>/dev/null)" || echo $(VERSION) > $@ -$(BINDIR)/scratch/git: +$(bin_dir)/scratch/git: @mkdir -p $@ diff --git a/make/help.mk b/make/help.mk deleted file mode 100644 index ad5521fd8e6..00000000000 --- a/make/help.mk +++ /dev/null @@ -1,104 +0,0 @@ -# Inspired from -# https://github.com/Mischback/django-calingen/blob/3f0e6db6/Makefile -# and https://gist.github.com/klmr/575726c7e05d8780505a - -# fancy colors -cyan := "$$(tput setaf 6)" -green := "$$(tput setaf 2)" -red := "$$(tput setaf 1)" -yel := "$$(tput setaf 3)" -gray := "$$(tput setaf 8)" -grayb := "$$(printf "\033[1m"; tput setaf 8)" -end := "$$(tput sgr0)" -TARGET_STYLED_HELP_NAME = "$(cyan)TARGET$(end)" -ARGUMENTS_HELP_NAME = "$(green)ARGUMENT$(end)=$(red)VALUE$(end)" - -# This mountrous sed is compatible with both GNU sed and BSD sed (for macOS). -# That's why "-E", "|", "+", "\s", "?", and "\t" aren't used. See the details -# about BSD sed vs. GNU sed: https://riptutorial.com/sed/topic/9436 - -target_regex := [a-zA-Z0-9%_\/%-][a-zA-Z0-9%_\/%-]* -variable_regex := [^:= ][^:= ]* -variable_assignment_regex := [ ]*:*[+:!\?]*= * -value_regex := .* -category_annotation_regex := @category * -category_regex := [^<][^<]* - -# We first parse and markup with these ad-hoc tags, and then we turn the markup -# into a colorful output. -target_tag_start := -target_tag_end := -target_variable_tag_start := -target_variable_tag_end := -variable_tag_start := -variable_tag_end := -global_variable_tag_start := -global_variable_tag_end := -value_tag_start := -value_tag_end := -prerequisites_tag_start := -prerequisites_tag_end := -doc_tag_start := -doc_tag_indented_start := -doc_tag_indented_end := -doc_tag_end := -category_tag_start := -category_tag_end := -default_category_tag_start := -default_category_tag_end := - -DEFAULT_CATEGORY = General - -.PHONY: help -help: - @echo "Usage: make [$(TARGET_STYLED_HELP_NAME) [$(TARGET_STYLED_HELP_NAME) ...]] [$(ARGUMENTS_HELP_NAME) [$(ARGUMENTS_HELP_NAME) ...]]" - @cat ${MAKEFILE_LIST} \ - | tr '\t' ' ' \ - | sed -n -e "/^## / { \ - h; \ - s/.*/##/; \ - :doc" \ - -e "H; \ - n; \ - s|^## *\(.*\)|$(doc_tag_start)$(doc_tag_indented_start)\1$(doc_tag_indented_end)$(doc_tag_end)|; \ - s|^## *\(.*\)|$(doc_tag_start)\1$(doc_tag_end)|; \ - t doc" \ - -e "s| *#[^#].*||; " \ - -e "s|^\(define *\)\($(variable_regex)\)$(variable_assignment_regex)\($(value_regex)\)|$(global_variable_tag_start)\2$(global_variable_tag_end)$(value_tag_start)\3$(value_tag_end)|;" \ - -e "s|^\($(variable_regex)\)$(variable_assignment_regex)\($(value_regex)\)|$(global_variable_tag_start)\1$(global_variable_tag_end)$(value_tag_start)\2$(value_tag_end)|;" \ - -e "s|^\($(target_regex)\) *: *\(\($(variable_regex)\)$(variable_assignment_regex)\($(value_regex)\)\)|$(target_variable_tag_start)\1$(target_variable_tag_end)$(variable_tag_start)\3$(variable_tag_end)$(value_tag_start)\4$(value_tag_end)|;" \ - -e "s|^\($(target_regex)\) *: *\($(target_regex)\( *$(target_regex)\)*\) *\(\| *\( *$(target_regex)\)*\)|$(target_tag_start)\1$(target_tag_end)$(prerequisites_tag_start)\2$(prerequisites_tag_end)|;" \ - -e "s|^\($(target_regex)\) *: *\($(target_regex)\( *$(target_regex)\)*\)|$(target_tag_start)\1$(target_tag_end)$(prerequisites_tag_start)\2$(prerequisites_tag_end)|;" \ - -e "s|^\($(target_regex)\) *: *\(\| *\( *$(target_regex)\)*\)|$(target_tag_start)\1$(target_tag_end)|;" \ - -e "s|^\($(target_regex)\) *: *|$(target_tag_start)\1$(target_tag_end)|;" \ - -e " \ - G; \ - s|## *\(.*\) *##|$(doc_tag_start)\1$(doc_tag_end)|; \ - s|\\n||g;" \ - -e "/$(category_annotation_regex)/!s|.*|$(default_category_tag_start)$(DEFAULT_CATEGORY)$(default_category_tag_end)&|" \ - -e "s|^\(.*\)$(doc_tag_start)$(category_annotation_regex)\($(category_regex)\)$(doc_tag_end)|$(category_tag_start)\2$(category_tag_end)\1|" \ - -e "p; \ - }" \ - | sort \ - | sed -n \ - -e "s|$(default_category_tag_start)|$(category_tag_start)|" \ - -e "s|$(default_category_tag_end)|$(category_tag_end)|" \ - -e "{G; s|\($(category_tag_start)$(category_regex)$(category_tag_end)\)\(.*\)\n\1|\2|; s|\n.*||; H; }" \ - -e "s|$(category_tag_start)||" \ - -e "s|$(category_tag_end)|:\n|" \ - -e "s|$(target_variable_tag_start)|$(target_tag_start)|" \ - -e "s|$(target_variable_tag_end)|$(target_tag_end)|" \ - -e "s|$(target_tag_start)| $(cyan)|" \ - -e "s|$(target_tag_end)|$(end) |" \ - -e "s|$(prerequisites_tag_start).*$(prerequisites_tag_end)||" \ - -e "s|$(variable_tag_start)|$(green)|g" \ - -e "s|$(variable_tag_end)|$(end)|" \ - -e "s|$(global_variable_tag_start)| $(green)|g" \ - -e "s|$(global_variable_tag_end)|$(end)|" \ - -e "s|$(value_tag_start)| (default: $(red)|" \ - -e "s|$(value_tag_end)|$(end))|" \ - -e "s|$(doc_tag_indented_start)|$(grayb)|g" \ - -e "s|$(doc_tag_indented_end)|$(end)|g" \ - -e "s|$(doc_tag_start)|\n |g" \ - -e "s|$(doc_tag_end)||g" \ - -e "p" diff --git a/make/kind_images.sh b/make/kind_images.sh old mode 100644 new mode 100755 index 19eb3c3c6b8..9a8a9c47278 --- a/make/kind_images.sh +++ b/make/kind_images.sh @@ -12,39 +12,9 @@ # See the License for the specific language governing permissions and # limitations under the License. -# generated by ./hack/latest-kind-images.sh - -KIND_IMAGE_K8S_120=docker.io/kindest/node@sha256:45d0194a8069c46483a0e509088ab9249302af561ebee76a1281a1f08ecb4ed3 -KIND_IMAGE_K8S_121=docker.io/kindest/node@sha256:ad5b7446dd8332439f22a1efdac73670f0da158c00f0a70b45716e7ef3fae20b -KIND_IMAGE_K8S_122=docker.io/kindest/node@sha256:bfd5eaae36849bfb3c1e3b9442f3da17d730718248939d9d547e86bbac5da586 -KIND_IMAGE_K8S_123=docker.io/kindest/node@sha256:9402cf1330bbd3a0d097d2033fa489b2abe40d479cc5ef47d0b6a6960613148a -KIND_IMAGE_K8S_124=docker.io/kindest/node@sha256:97e8d00bc37a7598a0b32d1fabd155a96355c49fa0d4d4790aab0f161bf31be1 -KIND_IMAGE_K8S_125=docker.io/kindest/node@sha256:9be91e9e9cdf116809841fc77ebdb8845443c4c72fe5218f3ae9eb57fdb4bace - -# docker.io/kindest/node:v1.20.15 -KIND_IMAGE_SHA_K8S_120=sha256:45d0194a8069c46483a0e509088ab9249302af561ebee76a1281a1f08ecb4ed3 - -# docker.io/kindest/node:v1.21.14 -KIND_IMAGE_SHA_K8S_121=sha256:ad5b7446dd8332439f22a1efdac73670f0da158c00f0a70b45716e7ef3fae20b - -# docker.io/kindest/node:v1.22.15 -KIND_IMAGE_SHA_K8S_122=sha256:bfd5eaae36849bfb3c1e3b9442f3da17d730718248939d9d547e86bbac5da586 - -# docker.io/kindest/node:v1.23.12 -KIND_IMAGE_SHA_K8S_123=sha256:9402cf1330bbd3a0d097d2033fa489b2abe40d479cc5ef47d0b6a6960613148a - -# docker.io/kindest/node:v1.24.6 -KIND_IMAGE_SHA_K8S_124=sha256:97e8d00bc37a7598a0b32d1fabd155a96355c49fa0d4d4790aab0f161bf31be1 - -# docker.io/kindest/node:v1.25.2 -KIND_IMAGE_SHA_K8S_125=sha256:9be91e9e9cdf116809841fc77ebdb8845443c4c72fe5218f3ae9eb57fdb4bace - -# note that these 'full' digests should be avoided since not all tools support them -# prefer KIND_IMAGE_K8S_*** instead -KIND_IMAGE_FULL_K8S_120=docker.io/kindest/node:v1.20.15@sha256:45d0194a8069c46483a0e509088ab9249302af561ebee76a1281a1f08ecb4ed3 -KIND_IMAGE_FULL_K8S_121=docker.io/kindest/node:v1.21.14@sha256:ad5b7446dd8332439f22a1efdac73670f0da158c00f0a70b45716e7ef3fae20b -KIND_IMAGE_FULL_K8S_122=docker.io/kindest/node:v1.22.15@sha256:bfd5eaae36849bfb3c1e3b9442f3da17d730718248939d9d547e86bbac5da586 -KIND_IMAGE_FULL_K8S_123=docker.io/kindest/node:v1.23.12@sha256:9402cf1330bbd3a0d097d2033fa489b2abe40d479cc5ef47d0b6a6960613148a -KIND_IMAGE_FULL_K8S_124=docker.io/kindest/node:v1.24.6@sha256:97e8d00bc37a7598a0b32d1fabd155a96355c49fa0d4d4790aab0f161bf31be1 -KIND_IMAGE_FULL_K8S_125=docker.io/kindest/node:v1.25.2@sha256:9be91e9e9cdf116809841fc77ebdb8845443c4c72fe5218f3ae9eb57fdb4bace +# generated by hack/latest-kind-images.sh from kind GH release v0.30.0 +KIND_IMAGE_K8S_131=docker.io/kindest/node@sha256:0f5cc49c5e73c0c2bb6e2df56e7df189240d83cf94edfa30946482eb08ec57d2 +KIND_IMAGE_K8S_132=docker.io/kindest/node@sha256:abd489f042d2b644e2d033f5c2d900bc707798d075e8186cb65e3f1367a9d5a1 +KIND_IMAGE_K8S_133=docker.io/kindest/node@sha256:25a6018e48dfcaee478f4a59af81157a437f15e6e140bf103f85a2e7cd0cbbf2 +KIND_IMAGE_K8S_134=docker.io/kindest/node@sha256:7416a61b42b1662ca6ca89f02028ac133a309a2a30ba309614e8ec94d976dc5a diff --git a/make/ko.mk b/make/ko.mk new file mode 100644 index 00000000000..bddc068f88d --- /dev/null +++ b/make/ko.mk @@ -0,0 +1,91 @@ +# 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. + +## Experimental tools for building and deploying cert-manager using ko to build and push Docker images. +## You need to have go workspaces set up to use the ko make targets. +## https://go.dev/blog/get-familiar-with-workspaces. +## Run make go-workspaces to set up a Go workspace for this repo. +## +## Examples: +## +## # Build and Push all images to an OCI registry +## make ko-images-push KO_REGISTRY= +## +## # Build and Push images to an OCI registry and deploy cert-manager to the current cluster in KUBECONFIG +## make ko-deploy-certmanager KO_REGISTRY= [KO_HELM_VALUES_FILES=path/to/values.yaml] +## +## @category Experimental/ko + +## (required) The OCI registry prefix to which images will be pushed by ko. +## @category Experimental/ko +KO_REGISTRY ?= $(error "KO_REGISTRY is a required environment variable") + +## (optional) The SBOM media type to use (none will disable SBOM synthesis and +## upload, also supports: spdx, cyclonedx, go.version-m). +## @category Experimental/ko +KO_SBOM ?= none + +## (optional) Which platforms to include in the multi-arch image. +## Format: all | [/[/]][,platform]* +## @category Experimental/ko +KO_PLATFORM ?= linux/amd64 + +## (optional) Which cert-manager images to build. +## @category Experimental/ko +KO_BINS ?= controller acmesolver cainjector webhook startupapicheck + +## (optional) Paths of Helm values files which will be supplied to `helm install +## --values` flag by make ko-deploy-certmanager. +## @category Experimental/ko +KO_HELM_VALUES_FILES ?= + +export KOCACHE = $(bin_dir)/scratch/ko/cache + +KO_IMAGE_REFS = $(foreach bin,$(KO_BINS),_bin/scratch/ko/$(bin).yaml) +$(KO_IMAGE_REFS): _bin/scratch/ko/%.yaml: FORCE | $(NEEDS_KO) $(NEEDS_YQ) + @mkdir -p $(dir $@) + @$(eval export KO_DOCKER_REPO=$(KO_REGISTRY)/cert-manager-$*) + $(KO) build ./cmd/$* \ + --bare \ + --sbom=$(KO_SBOM) \ + --platform=$(KO_PLATFORM) \ + --tags=$(VERSION) \ + | $(YQ) 'capture("(?P(?P[^:]+):(?P[^@]+)@(?P.*))")' > $@ + +.PHONY: ko-images-push +## Build and push docker images to an OCI registry using ko. +## @category Experimental/ko +ko-images-push: $(KO_IMAGE_REFS) + +.PHONY: ko-deploy-certmanager +## Deploy cert-manager after pushing docker images to an OCI registry using ko. +## @category Experimental/ko +ko-deploy-certmanager: $(bin_dir)/cert-manager.tgz $(KO_IMAGE_REFS) + @$(eval ACME_HTTP01_SOLVER_IMAGE = $(shell $(YQ) '.repository + "@" + .digest' $(bin_dir)/scratch/ko/acmesolver.yaml)) + $(HELM) upgrade cert-manager $< \ + --install \ + --create-namespace \ + --wait \ + --namespace cert-manager \ + $(and $(KO_HELM_VALUES_FILES),--values $(KO_HELM_VALUES_FILES)) \ + --set image.repository="$(shell $(YQ) .repository $(bin_dir)/scratch/ko/controller.yaml)" \ + --set image.digest="$(shell $(YQ) .digest $(bin_dir)/scratch/ko/controller.yaml)" \ + --set cainjector.image.repository="$(shell $(YQ) .repository $(bin_dir)/scratch/ko/cainjector.yaml)" \ + --set cainjector.image.digest="$(shell $(YQ) .digest $(bin_dir)/scratch/ko/cainjector.yaml)" \ + --set webhook.image.repository="$(shell $(YQ) .repository $(bin_dir)/scratch/ko/webhook.yaml)" \ + --set webhook.image.digest="$(shell $(YQ) .digest $(bin_dir)/scratch/ko/webhook.yaml)" \ + --set startupapicheck.image.repository="$(shell $(YQ) .repository $(bin_dir)/scratch/ko/startupapicheck.yaml)" \ + --set startupapicheck.image.digest="$(shell $(YQ) .digest $(bin_dir)/scratch/ko/startupapicheck.yaml)" \ + --set crds.enabled=true \ + --set "extraArgs={--acme-http01-solver-image=$(ACME_HTTP01_SOLVER_IMAGE)}" diff --git a/make/legacy.mk b/make/legacy.mk deleted file mode 100644 index bdaddef27e0..00000000000 --- a/make/legacy.mk +++ /dev/null @@ -1,23 +0,0 @@ -# Targets in this file are legacy holdovers from before the migration to make. -# They're preserved here in case they're used in some third party CI system or script, -# but are liable to being removed or broken without warning. - -.PHONY: verify -verify: ci-presubmit test - $(warning "The '$@' target is deprecated and may be removed. Use 'make $^' instead.") - -.PHONY: verify_deps -verify_deps: - $(warning "The '$@' target is deprecated and may be removed. This target is a no-op with the new make flow.") - -.PHONY: cluster -cluster: e2e-setup-kind - $(warning "The '$@' target is deprecated and may be removed. Use 'make $^' instead.") - -.PHONY: verify_chart -verify_chart: verify-chart - $(warning "The '$@' target is deprecated and may be removed. Use 'make $^' instead.") - -.PHONY: verify_upgrade -verify_upgrade: test-upgrade - $(warning "The '$@' target is deprecated and may be removed. Use 'make $^' instead.") diff --git a/make/licenses.mk b/make/licenses.mk index ee88eb63278..9519c0ba34a 100644 --- a/make/licenses.mk +++ b/make/licenses.mk @@ -1,26 +1,37 @@ +# 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. + # LICENSE_YEAR is the value which will be substituted into licenses when they're generated # It would be possible to make this more dynamic, but there's seemingly no need: # https://stackoverflow.com/a/2391555/1615417 -# As such, this is hardcoded to avoid needless complexity +# As such, this is hardcoded to avoid needless complexity. There's generally no need to update +# this and create regular diffs which do nothing but update the license year. LICENSE_YEAR=2022 -# Creates the boilerplate header for YAML files, assumed to be the same as the one in -# shell scripts (hence the use of boilerplate.sh.txt) -$(BINDIR)/scratch/license.yaml: hack/boilerplate/boilerplate.sh.txt | $(BINDIR)/scratch +# Creates the boilerplate header for YAML files from the template in hack/ +$(bin_dir)/scratch/license.yaml: hack/boilerplate-yaml.txt | $(bin_dir)/scratch sed -e "s/YEAR/$(LICENSE_YEAR)/g" < $< > $@ # The references LICENSES file is 1.4MB at the time of writing. Bundling it into every container image # seems wasteful in terms of bytes stored and bytes transferred on the wire just to add a file # which presumably nobody will ever read or care about. Instead, just add a little footnote pointing # to the cert-manager repo in case anybody actually decides that they care. -$(BINDIR)/scratch/license-footnote.yaml: | $(BINDIR)/scratch +$(bin_dir)/scratch/license-footnote.yaml: | $(bin_dir)/scratch @echo -e "# To view licenses for cert-manager dependencies, see the LICENSES file in the\n# cert-manager repo: https://github.com/cert-manager/cert-manager/blob/$(GITCOMMIT)/LICENSES" > $@ -$(BINDIR)/scratch/cert-manager.license: $(BINDIR)/scratch/license.yaml $(BINDIR)/scratch/license-footnote.yaml | $(BINDIR)/scratch +$(bin_dir)/scratch/cert-manager.license: $(bin_dir)/scratch/license.yaml $(bin_dir)/scratch/license-footnote.yaml | $(bin_dir)/scratch cat $^ > $@ -$(BINDIR)/scratch/cert-manager.licenses_notice: $(BINDIR)/scratch/license-footnote.yaml | $(BINDIR)/scratch +$(bin_dir)/scratch/cert-manager.licenses_notice: $(bin_dir)/scratch/license-footnote.yaml | $(bin_dir)/scratch cp $< $@ - -LICENSES $(BINDIR)/scratch/LATEST-LICENSES: go.mod go.sum | $(NEEDS_GO-LICENSES) - $(GO-LICENSES) csv ./... > $@ diff --git a/make/manifests.mk b/make/manifests.mk index 1f82fc21e5d..8fd05b05056 100644 --- a/make/manifests.mk +++ b/make/manifests.mk @@ -1,8 +1,19 @@ -CRDS_SOURCES=$(wildcard deploy/crds/*.yaml) -CRDS_TEMPLATED=$(CRDS_SOURCES:deploy/crds/%.yaml=$(BINDIR)/yaml/templated-crds/%.templated.yaml) +# 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. HELM_TEMPLATE_SOURCES=$(wildcard deploy/charts/cert-manager/templates/*.yaml) -HELM_TEMPLATE_TARGETS=$(patsubst deploy/charts/cert-manager/templates/%,$(BINDIR)/helm/cert-manager/templates/%,$(HELM_TEMPLATE_SOURCES)) +HELM_TEMPLATE_TARGETS=$(patsubst deploy/charts/cert-manager/templates/%,$(bin_dir)/helm/cert-manager/templates/%,$(HELM_TEMPLATE_SOURCES)) #################### # Friendly Targets # @@ -11,16 +22,16 @@ HELM_TEMPLATE_TARGETS=$(patsubst deploy/charts/cert-manager/templates/%,$(BINDIR # These targets provide friendly names for the various manifests / charts we build .PHONY: helm-chart -helm-chart: $(BINDIR)/cert-manager-$(RELEASE_VERSION).tgz +helm-chart: $(bin_dir)/cert-manager-$(VERSION).tgz -$(BINDIR)/cert-manager.tgz: $(BINDIR)/cert-manager-$(RELEASE_VERSION).tgz +$(bin_dir)/cert-manager.tgz: $(bin_dir)/cert-manager-$(VERSION).tgz @ln -s -f $(notdir $<) $@ .PHONY: helm-chart-signature -helm-chart-signature: $(BINDIR)/cert-manager-$(RELEASE_VERSION).tgz.prov +helm-chart-signature: $(bin_dir)/cert-manager-$(VERSION).tgz.prov .PHONY: static-manifests -static-manifests: $(BINDIR)/yaml/cert-manager.crds.yaml $(BINDIR)/yaml/cert-manager.yaml +static-manifests: $(bin_dir)/yaml/cert-manager.crds.yaml $(bin_dir)/yaml/cert-manager.yaml ################### # Release Targets # @@ -30,7 +41,7 @@ static-manifests: $(BINDIR)/yaml/cert-manager.crds.yaml $(BINDIR)/yaml/cert-mana ## Build YAML manifests and helm charts (but not the helm chart signature) ## ## @category Release -release-manifests: $(BINDIR)/scratch/cert-manager-manifests-unsigned.tar.gz +release-manifests: $(bin_dir)/scratch/cert-manager-manifests-unsigned.tar.gz .PHONY: release-manifests-signed ## Build YAML manifests and helm charts including the helm chart signature @@ -39,29 +50,29 @@ release-manifests: $(BINDIR)/scratch/cert-manager-manifests-unsigned.tar.gz ## Prefer `make release-manifests` locally. ## ## @category Release -release-manifests-signed: $(BINDIR)/release/cert-manager-manifests.tar.gz $(BINDIR)/metadata/cert-manager-manifests.tar.gz.metadata.json +release-manifests-signed: $(bin_dir)/release/cert-manager-manifests.tar.gz $(bin_dir)/metadata/cert-manager-manifests.tar.gz.metadata.json -$(BINDIR)/release/cert-manager-manifests.tar.gz: $(BINDIR)/cert-manager-$(RELEASE_VERSION).tgz $(BINDIR)/yaml/cert-manager.crds.yaml $(BINDIR)/yaml/cert-manager.yaml $(BINDIR)/cert-manager-$(RELEASE_VERSION).tgz.prov | $(BINDIR)/scratch/manifests $(BINDIR)/release - mkdir -p $(BINDIR)/scratch/manifests/deploy/chart/ - mkdir -p $(BINDIR)/scratch/manifests/deploy/manifests/ - cp $(BINDIR)/cert-manager-$(RELEASE_VERSION).tgz $(BINDIR)/cert-manager-$(RELEASE_VERSION).tgz.prov $(BINDIR)/scratch/manifests/deploy/chart/ - cp $(BINDIR)/yaml/cert-manager.crds.yaml $(BINDIR)/yaml/cert-manager.yaml $(BINDIR)/scratch/manifests/deploy/manifests/ +$(bin_dir)/release/cert-manager-manifests.tar.gz: $(bin_dir)/cert-manager-$(VERSION).tgz $(bin_dir)/yaml/cert-manager.crds.yaml $(bin_dir)/yaml/cert-manager.yaml $(bin_dir)/cert-manager-$(VERSION).tgz.prov | $(bin_dir)/scratch/manifests-signed $(bin_dir)/release + mkdir -p $(bin_dir)/scratch/manifests-signed/deploy/chart/ + mkdir -p $(bin_dir)/scratch/manifests-signed/deploy/manifests/ + cp $(bin_dir)/cert-manager-$(VERSION).tgz $(bin_dir)/cert-manager-$(VERSION).tgz.prov $(bin_dir)/scratch/manifests-signed/deploy/chart/ + cp $(bin_dir)/yaml/cert-manager.crds.yaml $(bin_dir)/yaml/cert-manager.yaml $(bin_dir)/scratch/manifests-signed/deploy/manifests/ # removes leading ./ from archived paths - find $(BINDIR)/scratch/manifests -maxdepth 1 -mindepth 1 | sed 's|.*/||' | tar czf $@ -C $(BINDIR)/scratch/manifests -T - - rm -rf $(BINDIR)/scratch/manifests - -$(BINDIR)/scratch/cert-manager-manifests-unsigned.tar.gz: $(BINDIR)/cert-manager-$(RELEASE_VERSION).tgz $(BINDIR)/yaml/cert-manager.crds.yaml $(BINDIR)/yaml/cert-manager.yaml | $(BINDIR)/scratch/manifests - mkdir -p $(BINDIR)/scratch/manifests/deploy/chart/ - mkdir -p $(BINDIR)/scratch/manifests/deploy/manifests/ - cp $(BINDIR)/cert-manager-$(RELEASE_VERSION).tgz $(BINDIR)/scratch/manifests/deploy/chart/ - cp $(BINDIR)/yaml/cert-manager.crds.yaml $(BINDIR)/yaml/cert-manager.yaml $(BINDIR)/scratch/manifests/deploy/manifests/ + find $(bin_dir)/scratch/manifests-signed -maxdepth 1 -mindepth 1 | sed 's|.*/||' | tar czf $@ -C $(bin_dir)/scratch/manifests-signed -T - + rm -rf $(bin_dir)/scratch/manifests-signed + +$(bin_dir)/scratch/cert-manager-manifests-unsigned.tar.gz: $(bin_dir)/cert-manager-$(VERSION).tgz $(bin_dir)/yaml/cert-manager.crds.yaml $(bin_dir)/yaml/cert-manager.yaml | $(bin_dir)/scratch/manifests-unsigned + mkdir -p $(bin_dir)/scratch/manifests-unsigned/deploy/chart/ + mkdir -p $(bin_dir)/scratch/manifests-unsigned/deploy/manifests/ + cp $(bin_dir)/cert-manager-$(VERSION).tgz $(bin_dir)/scratch/manifests-unsigned/deploy/chart/ + cp $(bin_dir)/yaml/cert-manager.crds.yaml $(bin_dir)/yaml/cert-manager.yaml $(bin_dir)/scratch/manifests-unsigned/deploy/manifests/ # removes leading ./ from archived paths - find $(BINDIR)/scratch/manifests -maxdepth 1 -mindepth 1 | sed 's|.*/||' | tar czf $@ -C $(BINDIR)/scratch/manifests -T - - rm -rf $(BINDIR)/scratch/manifests + find $(bin_dir)/scratch/manifests-unsigned -maxdepth 1 -mindepth 1 | sed 's|.*/||' | tar czf $@ -C $(bin_dir)/scratch/manifests-unsigned -T - + rm -rf $(bin_dir)/scratch/manifests-unsigned # This metadata blob is constructed slightly differently and doesn't use hack/artifact-metadata.template.json directly; # this is because the bazel staged releases didn't include an "os" or "architecture" field for this artifact -$(BINDIR)/metadata/cert-manager-manifests.tar.gz.metadata.json: $(BINDIR)/release/cert-manager-manifests.tar.gz hack/artifact-metadata.template.json | $(BINDIR)/metadata +$(bin_dir)/metadata/cert-manager-manifests.tar.gz.metadata.json: $(bin_dir)/release/cert-manager-manifests.tar.gz hack/artifact-metadata.template.json | $(bin_dir)/metadata jq -n --arg name "$(notdir $<)" \ --arg sha256 "$(shell ./hack/util/hash.sh $<)" \ '.name = $$name | .sha256 = $$sha256' > $@ @@ -72,42 +83,40 @@ $(BINDIR)/metadata/cert-manager-manifests.tar.gz.metadata.json: $(BINDIR)/releas # These targets provide for building and signing the cert-manager helm chart. -$(BINDIR)/cert-manager-$(RELEASE_VERSION).tgz: $(BINDIR)/helm/cert-manager/README.md $(BINDIR)/helm/cert-manager/Chart.yaml $(BINDIR)/helm/cert-manager/values.yaml $(HELM_TEMPLATE_TARGETS) $(BINDIR)/helm/cert-manager/templates/NOTES.txt $(BINDIR)/helm/cert-manager/templates/_helpers.tpl $(BINDIR)/helm/cert-manager/templates/crds.yaml | $(NEEDS_HELM) $(BINDIR)/helm/cert-manager - $(HELM) package --app-version=$(RELEASE_VERSION) --version=$(RELEASE_VERSION) --destination "$(dir $@)" ./$(BINDIR)/helm/cert-manager +$(bin_dir)/cert-manager-$(VERSION).tgz: $(bin_dir)/helm/cert-manager/README.md $(bin_dir)/helm/cert-manager/Chart.yaml $(bin_dir)/helm/cert-manager/values.yaml $(bin_dir)/helm/cert-manager/values.schema.json $(HELM_TEMPLATE_TARGETS) $(bin_dir)/helm/cert-manager/templates/NOTES.txt $(bin_dir)/helm/cert-manager/templates/_helpers.tpl | $(NEEDS_HELM) $(bin_dir)/helm/cert-manager + $(HELM) package --app-version=$(VERSION) --version=$(VERSION) --destination "$(dir $@)" ./$(bin_dir)/helm/cert-manager -$(BINDIR)/cert-manager-$(RELEASE_VERSION).tgz.prov: $(BINDIR)/cert-manager-$(RELEASE_VERSION).tgz | $(NEEDS_CMREL) $(BINDIR)/helm/cert-manager +$(bin_dir)/cert-manager-$(VERSION).tgz.prov: $(bin_dir)/cert-manager-$(VERSION).tgz | $(NEEDS_CMREL) $(bin_dir)/helm/cert-manager ifeq ($(strip $(CMREL_KEY)),) $(error Trying to sign helm chart but CMREL_KEY is empty) endif cd $(dir $<) && $(CMREL) sign helm --chart-path "$(notdir $<)" --key "$(CMREL_KEY)" -$(BINDIR)/helm/cert-manager/templates/%.yaml: deploy/charts/cert-manager/templates/%.yaml | $(BINDIR)/helm/cert-manager/templates +$(bin_dir)/helm/cert-manager/templates/%.yaml: deploy/charts/cert-manager/templates/%.yaml | $(bin_dir)/helm/cert-manager/templates cp -f $^ $@ -$(BINDIR)/helm/cert-manager/templates/_helpers.tpl: deploy/charts/cert-manager/templates/_helpers.tpl | $(BINDIR)/helm/cert-manager/templates +$(bin_dir)/helm/cert-manager/templates/_helpers.tpl: deploy/charts/cert-manager/templates/_helpers.tpl | $(bin_dir)/helm/cert-manager/templates cp $< $@ -$(BINDIR)/helm/cert-manager/templates/NOTES.txt: deploy/charts/cert-manager/templates/NOTES.txt | $(BINDIR)/helm/cert-manager/templates +$(bin_dir)/helm/cert-manager/templates/NOTES.txt: deploy/charts/cert-manager/templates/NOTES.txt | $(bin_dir)/helm/cert-manager/templates cp $< $@ -$(BINDIR)/helm/cert-manager/templates/crds.yaml: $(CRDS_SOURCES) | $(BINDIR)/helm/cert-manager/templates - echo '{{- if .Values.installCRDs }}' > $@ - ./hack/concat-yaml.sh $^ >> $@ - echo '{{- end }}' >> $@ +$(bin_dir)/helm/cert-manager/values.yaml: deploy/charts/cert-manager/values.yaml | $(bin_dir)/helm/cert-manager + cp $< $@ -$(BINDIR)/helm/cert-manager/values.yaml: deploy/charts/cert-manager/values.yaml | $(BINDIR)/helm/cert-manager +$(bin_dir)/helm/cert-manager/values.schema.json: deploy/charts/cert-manager/values.schema.json | $(bin_dir)/helm/cert-manager cp $< $@ -$(BINDIR)/helm/cert-manager/README.md: deploy/charts/cert-manager/README.template.md | $(BINDIR)/helm/cert-manager - sed -e "s:{{RELEASE_VERSION}}:$(RELEASE_VERSION):g" < $< > $@ +$(bin_dir)/helm/cert-manager/README.md: deploy/charts/cert-manager/README.template.md | $(bin_dir)/helm/cert-manager + sed -e "s:{{RELEASE_VERSION}}:$(VERSION):g" < $< > $@ -$(BINDIR)/helm/cert-manager/Chart.yaml: deploy/charts/cert-manager/Chart.template.yaml deploy/charts/cert-manager/signkey_annotation.txt | $(NEEDS_YQ) $(BINDIR)/helm/cert-manager +$(bin_dir)/helm/cert-manager/Chart.yaml: deploy/charts/cert-manager/Chart.template.yaml deploy/charts/cert-manager/signkey_annotation.txt | $(NEEDS_YQ) $(bin_dir)/helm/cert-manager @# this horrible mess is taken from the YQ manual's example of multiline string blocks from a file: @# https://mikefarah.gitbook.io/yq/operators/string-operators#string-blocks-bash-and-newlines @# we set a bash variable called SIGNKEY_ANNOTATION using read, and then use that bash variable in yq IFS= read -rd '' SIGNKEY_ANNOTATION < <(cat deploy/charts/cert-manager/signkey_annotation.txt) ; \ SIGNKEY_ANNOTATION=$$SIGNKEY_ANNOTATION $(YQ) eval \ - '.annotations."artifacthub.io/signKey" = strenv(SIGNKEY_ANNOTATION) | .annotations."artifacthub.io/prerelease" = "$(IS_PRERELEASE)" | .version = "$(RELEASE_VERSION)" | .appVersion = "$(RELEASE_VERSION)"' \ + '.annotations."artifacthub.io/signKey" = strenv(SIGNKEY_ANNOTATION) | .annotations."artifacthub.io/prerelease" = "$(IS_PRERELEASE)" | .version = "$(VERSION)" | .appVersion = "$(VERSION)"' \ $< > $@ ############################################################ @@ -119,53 +128,46 @@ $(BINDIR)/helm/cert-manager/Chart.yaml: deploy/charts/cert-manager/Chart.templat # with templating completed, and then concatenate with the cert-manager namespace and the CRDs. # Renders all resources except the namespace and the CRDs -$(BINDIR)/scratch/yaml/cert-manager.noncrd.unlicensed.yaml: $(BINDIR)/cert-manager-$(RELEASE_VERSION).tgz | $(NEEDS_HELM) $(BINDIR)/scratch/yaml +$(bin_dir)/scratch/yaml/cert-manager.noncrd.unlicensed.yaml: $(bin_dir)/cert-manager-$(VERSION).tgz | $(NEEDS_HELM) $(bin_dir)/scratch/yaml @# The sed command removes the first line but only if it matches "---", which helm adds $(HELM) template --api-versions="" --namespace=cert-manager --set="creator=static" --set="startupapicheck.enabled=false" cert-manager $< | \ sed -e "1{/^---$$/d;}" > $@ -$(BINDIR)/scratch/yaml/cert-manager.all.unlicensed.yaml: $(BINDIR)/cert-manager-$(RELEASE_VERSION).tgz | $(NEEDS_HELM) $(BINDIR)/scratch/yaml +$(bin_dir)/scratch/yaml/cert-manager.all.unlicensed.yaml: $(bin_dir)/cert-manager-$(VERSION).tgz | $(NEEDS_HELM) $(bin_dir)/scratch/yaml @# The sed command removes the first line but only if it matches "---", which helm adds - $(HELM) template --api-versions="" --namespace=cert-manager --set="installCRDs=true" --set="creator=static" --set="startupapicheck.enabled=false" cert-manager $< | \ + $(HELM) template --api-versions="" --namespace=cert-manager --set="crds.enabled=true" --set="creator=static" --set="startupapicheck.enabled=false" cert-manager $< | \ sed -e "1{/^---$$/d;}" > $@ -$(BINDIR)/scratch/yaml/cert-manager.crds.unlicensed.yaml: $(BINDIR)/scratch/yaml/cert-manager.all.unlicensed.yaml | $(NEEDS_GO) $(BINDIR)/scratch/yaml +$(bin_dir)/scratch/yaml/cert-manager.crds.unlicensed.yaml: $(bin_dir)/scratch/yaml/cert-manager.all.unlicensed.yaml | $(NEEDS_GO) $(bin_dir)/scratch/yaml $(GO) run hack/extractcrd/main.go $< > $@ -$(BINDIR)/yaml/cert-manager.yaml: $(BINDIR)/scratch/license.yaml deploy/manifests/namespace.yaml $(BINDIR)/scratch/yaml/cert-manager.crds.unlicensed.yaml $(BINDIR)/scratch/yaml/cert-manager.noncrd.unlicensed.yaml | $(BINDIR)/yaml +$(bin_dir)/yaml/cert-manager.yaml: $(bin_dir)/scratch/license.yaml deploy/manifests/namespace.yaml $(bin_dir)/scratch/yaml/cert-manager.crds.unlicensed.yaml $(bin_dir)/scratch/yaml/cert-manager.noncrd.unlicensed.yaml | $(bin_dir)/yaml @# NB: filter-out removes the license (the first dependency, $<) from the YAML concatenation ./hack/concat-yaml.sh $(filter-out $<, $^) | cat $< - > $@ -$(BINDIR)/yaml/cert-manager.crds.yaml: $(BINDIR)/scratch/license.yaml $(BINDIR)/scratch/yaml/cert-manager.crds.unlicensed.yaml | $(BINDIR)/yaml +$(bin_dir)/yaml/cert-manager.crds.yaml: $(bin_dir)/scratch/license.yaml $(bin_dir)/scratch/yaml/cert-manager.crds.unlicensed.yaml | $(bin_dir)/yaml cat $^ > $@ -$(CRDS_TEMPLATED): $(BINDIR)/yaml/templated-crds/crd-%.templated.yaml: $(BINDIR)/scratch/license.yaml $(BINDIR)/scratch/yaml/cert-manager.crds.unlicensed.yaml | $(NEEDS_GO) $(BINDIR)/yaml/templated-crds - cat $< > $@ - $(GO) run hack/extractcrd/main.go $(word 2,$^) $* >> $@ - -.PHONY: templated-crds -templated-crds: $(CRDS_TEMPLATED) - ############### # Dir targets # ############### # These targets are trivial, to ensure that dirs exist -$(BINDIR)/yaml: +$(bin_dir)/yaml: @mkdir -p $@ -$(BINDIR)/helm/cert-manager: +$(bin_dir)/helm/cert-manager: @mkdir -p $@ -$(BINDIR)/helm/cert-manager/templates: +$(bin_dir)/helm/cert-manager/templates: @mkdir -p $@ -$(BINDIR)/scratch/yaml: +$(bin_dir)/scratch/yaml: @mkdir -p $@ -$(BINDIR)/scratch/manifests: +$(bin_dir)/scratch/manifests-unsigned: @mkdir -p $@ -$(BINDIR)/yaml/templated-crds: +$(bin_dir)/scratch/manifests-signed: @mkdir -p $@ diff --git a/make/release.mk b/make/release.mk index 335c19fcc14..bc38921d745 100644 --- a/make/release.mk +++ b/make/release.mk @@ -1,3 +1,17 @@ +# 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 this as an environment variable to enable signing commands using cmrel. ## Format should be: ## projects//locations//keyRings//cryptoKeys//cryptoKeyVersions/ @@ -21,7 +35,7 @@ RELEASE_TARGET_BUCKET ?= ## built without errors. Not useful for an actual release - instead, use `make release` for that. ## ## @category Release -release-artifacts: server-binaries cmctl kubectl-cert_manager helm-chart release-containers release-manifests +release-artifacts: server-binaries helm-chart release-containers release-manifests .PHONY: release-artifacts-signed # Same as `release-artifacts`, except also signs the Helm chart. Requires CMREL_KEY @@ -39,7 +53,7 @@ release-artifacts-signed: release-artifacts release-manifests-signed ## ## @category Release release: release-artifacts-signed - $(MAKE) --no-print-directory $(BINDIR)/release/metadata.json + $(MAKE) --no-print-directory $(bin_dir)/release/metadata.json .PHONY: upload-release ## Create a complete release and then upload it to a target GCS bucket specified by @@ -50,13 +64,13 @@ upload-release: release | $(NEEDS_RCLONE) ifeq ($(strip $(RELEASE_TARGET_BUCKET)),) $(error Trying to upload-release but RELEASE_TARGET_BUCKET is empty) endif - $(RCLONE) copyto ./$(BINDIR)/release :gcs:$(RELEASE_TARGET_BUCKET)/stage/gcb/release/$(RELEASE_VERSION) + $(RCLONE) --gcs-bucket-policy-only copyto ./$(bin_dir)/release :gcs:$(RELEASE_TARGET_BUCKET)/stage/gcb/release/$(VERSION) -# Takes all metadata files in $(BINDIR)/metadata and combines them into one. +# Takes all metadata files in $(bin_dir)/metadata and combines them into one. -$(BINDIR)/release/metadata.json: $(wildcard $(BINDIR)/metadata/*.json) | $(BINDIR)/release +$(bin_dir)/release/metadata.json: $(wildcard $(bin_dir)/metadata/*.json) | $(bin_dir)/release jq -n \ - --arg releaseVersion "$(RELEASE_VERSION)" \ + --arg releaseVersion "$(VERSION)" \ --arg buildSource "make" \ --arg gitCommitRef "$(GITCOMMIT)" \ '.releaseVersion = $$releaseVersion | .gitCommitRef = $$gitCommitRef | .buildSource = $$buildSource | .artifacts += [inputs]' $^ > $@ @@ -65,33 +79,33 @@ $(BINDIR)/release/metadata.json: $(wildcard $(BINDIR)/metadata/*.json) | $(BINDI release-containers: release-container-bundles release-container-metadata .PHONY: release-container-bundles -release-container-bundles: $(BINDIR)/release/cert-manager-server-linux-amd64.tar.gz $(BINDIR)/release/cert-manager-server-linux-arm64.tar.gz $(BINDIR)/release/cert-manager-server-linux-s390x.tar.gz $(BINDIR)/release/cert-manager-server-linux-ppc64le.tar.gz $(BINDIR)/release/cert-manager-server-linux-arm.tar.gz +release-container-bundles: $(bin_dir)/release/cert-manager-server-linux-amd64.tar.gz $(bin_dir)/release/cert-manager-server-linux-arm64.tar.gz $(bin_dir)/release/cert-manager-server-linux-s390x.tar.gz $(bin_dir)/release/cert-manager-server-linux-ppc64le.tar.gz $(bin_dir)/release/cert-manager-server-linux-arm.tar.gz -$(BINDIR)/release/cert-manager-server-linux-amd64.tar.gz $(BINDIR)/release/cert-manager-server-linux-arm64.tar.gz $(BINDIR)/release/cert-manager-server-linux-s390x.tar.gz $(BINDIR)/release/cert-manager-server-linux-ppc64le.tar.gz $(BINDIR)/release/cert-manager-server-linux-arm.tar.gz: $(BINDIR)/release/cert-manager-server-linux-%.tar.gz: $(BINDIR)/containers/cert-manager-acmesolver-linux-%.tar.gz $(BINDIR)/containers/cert-manager-cainjector-linux-%.tar.gz $(BINDIR)/containers/cert-manager-controller-linux-%.tar.gz $(BINDIR)/containers/cert-manager-webhook-linux-%.tar.gz $(BINDIR)/containers/cert-manager-ctl-linux-%.tar.gz $(BINDIR)/scratch/cert-manager.license | $(BINDIR)/release $(BINDIR)/scratch +$(bin_dir)/release/cert-manager-server-linux-amd64.tar.gz $(bin_dir)/release/cert-manager-server-linux-arm64.tar.gz $(bin_dir)/release/cert-manager-server-linux-s390x.tar.gz $(bin_dir)/release/cert-manager-server-linux-ppc64le.tar.gz $(bin_dir)/release/cert-manager-server-linux-arm.tar.gz: $(bin_dir)/release/cert-manager-server-linux-%.tar.gz: $(bin_dir)/containers/cert-manager-acmesolver-linux-%.tar.gz $(bin_dir)/containers/cert-manager-cainjector-linux-%.tar.gz $(bin_dir)/containers/cert-manager-controller-linux-%.tar.gz $(bin_dir)/containers/cert-manager-webhook-linux-%.tar.gz $(bin_dir)/containers/cert-manager-startupapicheck-linux-%.tar.gz $(bin_dir)/scratch/cert-manager.license | $(bin_dir)/release $(bin_dir)/scratch @# use basename twice to strip both "tar" and "gz" @$(eval CTR_BASENAME := $(basename $(basename $(notdir $@)))) - @$(eval CTR_SCRATCHDIR := $(BINDIR)/scratch/release-container-bundle/$(CTR_BASENAME)) + @$(eval CTR_SCRATCHDIR := $(bin_dir)/scratch/release-container-bundle/$(CTR_BASENAME)) mkdir -p $(CTR_SCRATCHDIR)/server/images - echo "$(RELEASE_VERSION)" > $(CTR_SCRATCHDIR)/version - echo "$(RELEASE_VERSION)" > $(CTR_SCRATCHDIR)/server/images/acmesolver.docker_tag - echo "$(RELEASE_VERSION)" > $(CTR_SCRATCHDIR)/server/images/cainjector.docker_tag - echo "$(RELEASE_VERSION)" > $(CTR_SCRATCHDIR)/server/images/controller.docker_tag - echo "$(RELEASE_VERSION)" > $(CTR_SCRATCHDIR)/server/images/webhook.docker_tag - echo "$(RELEASE_VERSION)" > $(CTR_SCRATCHDIR)/server/images/ctl.docker_tag - cp $(BINDIR)/scratch/cert-manager.license $(CTR_SCRATCHDIR)/LICENSES - gunzip -c $(BINDIR)/containers/cert-manager-acmesolver-linux-$*.tar.gz >$(CTR_SCRATCHDIR)/server/images/acmesolver.tar - gunzip -c $(BINDIR)/containers/cert-manager-cainjector-linux-$*.tar.gz >$(CTR_SCRATCHDIR)/server/images/cainjector.tar - gunzip -c $(BINDIR)/containers/cert-manager-controller-linux-$*.tar.gz >$(CTR_SCRATCHDIR)/server/images/controller.tar - gunzip -c $(BINDIR)/containers/cert-manager-webhook-linux-$*.tar.gz >$(CTR_SCRATCHDIR)/server/images/webhook.tar - gunzip -c $(BINDIR)/containers/cert-manager-ctl-linux-$*.tar.gz >$(CTR_SCRATCHDIR)/server/images/ctl.tar + echo "$(VERSION)" > $(CTR_SCRATCHDIR)/version + echo "$(VERSION)" > $(CTR_SCRATCHDIR)/server/images/acmesolver.docker_tag + echo "$(VERSION)" > $(CTR_SCRATCHDIR)/server/images/cainjector.docker_tag + echo "$(VERSION)" > $(CTR_SCRATCHDIR)/server/images/controller.docker_tag + echo "$(VERSION)" > $(CTR_SCRATCHDIR)/server/images/webhook.docker_tag + echo "$(VERSION)" > $(CTR_SCRATCHDIR)/server/images/startupapicheck.docker_tag + cp $(bin_dir)/scratch/cert-manager.license $(CTR_SCRATCHDIR)/LICENSES + gunzip -c $(bin_dir)/containers/cert-manager-acmesolver-linux-$*.tar.gz >$(CTR_SCRATCHDIR)/server/images/acmesolver.tar + gunzip -c $(bin_dir)/containers/cert-manager-cainjector-linux-$*.tar.gz >$(CTR_SCRATCHDIR)/server/images/cainjector.tar + gunzip -c $(bin_dir)/containers/cert-manager-controller-linux-$*.tar.gz >$(CTR_SCRATCHDIR)/server/images/controller.tar + gunzip -c $(bin_dir)/containers/cert-manager-webhook-linux-$*.tar.gz >$(CTR_SCRATCHDIR)/server/images/webhook.tar + gunzip -c $(bin_dir)/containers/cert-manager-startupapicheck-linux-$*.tar.gz >$(CTR_SCRATCHDIR)/server/images/startupapicheck.tar chmod -R 755 $(CTR_SCRATCHDIR)/server/images/* - tar czf $@ -C $(BINDIR)/scratch/release-container-bundle $(CTR_BASENAME) + tar czf $@ -C $(bin_dir)/scratch/release-container-bundle $(CTR_BASENAME) rm -rf $(CTR_SCRATCHDIR) .PHONY: release-container-metadata -release-container-metadata: $(BINDIR)/metadata/cert-manager-server-linux-amd64.tar.gz.metadata.json $(BINDIR)/metadata/cert-manager-server-linux-arm64.tar.gz.metadata.json $(BINDIR)/metadata/cert-manager-server-linux-s390x.tar.gz.metadata.json $(BINDIR)/metadata/cert-manager-server-linux-ppc64le.tar.gz.metadata.json $(BINDIR)/metadata/cert-manager-server-linux-arm.tar.gz.metadata.json +release-container-metadata: $(bin_dir)/metadata/cert-manager-server-linux-amd64.tar.gz.metadata.json $(bin_dir)/metadata/cert-manager-server-linux-arm64.tar.gz.metadata.json $(bin_dir)/metadata/cert-manager-server-linux-s390x.tar.gz.metadata.json $(bin_dir)/metadata/cert-manager-server-linux-ppc64le.tar.gz.metadata.json $(bin_dir)/metadata/cert-manager-server-linux-arm.tar.gz.metadata.json -$(BINDIR)/metadata/cert-manager-server-linux-amd64.tar.gz.metadata.json $(BINDIR)/metadata/cert-manager-server-linux-arm64.tar.gz.metadata.json $(BINDIR)/metadata/cert-manager-server-linux-s390x.tar.gz.metadata.json $(BINDIR)/metadata/cert-manager-server-linux-ppc64le.tar.gz.metadata.json $(BINDIR)/metadata/cert-manager-server-linux-arm.tar.gz.metadata.json: $(BINDIR)/metadata/cert-manager-server-linux-%.tar.gz.metadata.json: $(BINDIR)/release/cert-manager-server-linux-%.tar.gz hack/artifact-metadata.template.json | $(BINDIR)/metadata +$(bin_dir)/metadata/cert-manager-server-linux-amd64.tar.gz.metadata.json $(bin_dir)/metadata/cert-manager-server-linux-arm64.tar.gz.metadata.json $(bin_dir)/metadata/cert-manager-server-linux-s390x.tar.gz.metadata.json $(bin_dir)/metadata/cert-manager-server-linux-ppc64le.tar.gz.metadata.json $(bin_dir)/metadata/cert-manager-server-linux-arm.tar.gz.metadata.json: $(bin_dir)/metadata/cert-manager-server-linux-%.tar.gz.metadata.json: $(bin_dir)/release/cert-manager-server-linux-%.tar.gz hack/artifact-metadata.template.json | $(bin_dir)/metadata jq --arg name "$(notdir $<)" \ --arg sha256 "$(shell ./hack/util/hash.sh $<)" \ --arg os "linux" \ @@ -102,17 +116,17 @@ $(BINDIR)/metadata/cert-manager-server-linux-amd64.tar.gz.metadata.json $(BINDIR # This target allows us to set all the modified times for all files in bin to the same time, which # is similar to what bazel does. We might not want this, and it's not currently used. .PHONY: forcetime -forcetime: | $(BINDIR) - find $(BINDIR) | xargs touch -d "2000-01-01 00:00:00" - +forcetime: | $(bin_dir) + find $(bin_dir) | xargs touch -d "2000-01-01 00:00:00" - -$(BINDIR)/release $(BINDIR)/metadata: +$(bin_dir)/release $(bin_dir)/metadata: @mkdir -p $@ # Example of how we can generate a SHA256SUMS file and sign it using cosign -#$(BINDIR)/SHA256SUMS: $(wildcard ...) -# @# The patsubst means "all dependencies, but with "$(BINDIR)/" trimmed off the beginning +#$(bin_dir)/SHA256SUMS: $(wildcard ...) +# @# The patsubst means "all dependencies, but with "$(bin_dir)/" trimmed off the beginning # @# We cd into bin so that SHA256SUMS file doesn't have a prefix of `bin` on everything -# cd $(dir $@) && sha256sum $(patsubst $(BINDIR)/%,%,$^) > $(notdir $@) +# cd $(dir $@) && sha256sum $(patsubst $(bin_dir)/%,%,$^) > $(notdir $@) -#$(BINDIR)/SHA256SUMS.sig: $(BINDIR)/SHA256SUMS | $(NEEDS_COSIGN) +#$(bin_dir)/SHA256SUMS.sig: $(bin_dir)/SHA256SUMS | $(NEEDS_COSIGN) # $(COSIGN) sign-blob --key $(COSIGN_KEY) $< > $@ diff --git a/make/scan.mk b/make/scan.mk index 916efddfb1f..162016d7b15 100644 --- a/make/scan.mk +++ b/make/scan.mk @@ -1,28 +1,42 @@ +# 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. + .PHONY: trivy-scan-all ## trivy-scan-all runs a scan using Trivy (https://github.com/aquasecurity/trivy) ## against all containers that cert-manager builds. If one of the containers ## fails a scan, then all scans will be aborted; if you need to check a specific -## container, use "trivy-scan-", e.g. "make trivy-scan-controller" +## container, use "trivy-scan-", e.g., "make trivy-scan-controller" ## ## @category Development -trivy-scan-all: trivy-scan-controller trivy-scan-acmesolver trivy-scan-webhook trivy-scan-cainjector trivy-scan-ctl +trivy-scan-all: trivy-scan-controller trivy-scan-acmesolver trivy-scan-webhook trivy-scan-cainjector trivy-scan-startupapicheck .PHONY: trivy-scan-controller -trivy-scan-controller: $(BINDIR)/containers/cert-manager-controller-linux-amd64.tar | $(NEEDS_TRIVY) +trivy-scan-controller: $(bin_dir)/containers/cert-manager-controller-linux-amd64.tar | $(NEEDS_TRIVY) $(TRIVY) image --input $< --format json --exit-code 1 .PHONY: trivy-scan-acmesolver -trivy-scan-acmesolver: $(BINDIR)/containers/cert-manager-acmesolver-linux-amd64.tar | $(NEEDS_TRIVY) +trivy-scan-acmesolver: $(bin_dir)/containers/cert-manager-acmesolver-linux-amd64.tar | $(NEEDS_TRIVY) $(TRIVY) image --input $< --format json --exit-code 1 .PHONY: trivy-scan-webhook -trivy-scan-webhook: $(BINDIR)/containers/cert-manager-webhook-linux-amd64.tar | $(NEEDS_TRIVY) +trivy-scan-webhook: $(bin_dir)/containers/cert-manager-webhook-linux-amd64.tar | $(NEEDS_TRIVY) $(TRIVY) image --input $< --format json --exit-code 1 .PHONY: trivy-scan-cainjector -trivy-scan-cainjector: $(BINDIR)/containers/cert-manager-cainjector-linux-amd64.tar | $(NEEDS_TRIVY) +trivy-scan-cainjector: $(bin_dir)/containers/cert-manager-cainjector-linux-amd64.tar | $(NEEDS_TRIVY) $(TRIVY) image --input $< --format json --exit-code 1 -.PHONY: trivy-scan-ctl -trivy-scan-ctl: $(BINDIR)/containers/cert-manager-ctl-linux-amd64.tar | $(NEEDS_TRIVY) +.PHONY: trivy-scan-startupapicheck +trivy-scan-startupapicheck: $(bin_dir)/containers/cert-manager-startupapicheck-linux-amd64.tar | $(NEEDS_TRIVY) $(TRIVY) image --input $< --format json --exit-code 1 diff --git a/make/server.mk b/make/server.mk index 3de963f5605..79dbb4d9694 100644 --- a/make/server.mk +++ b/make/server.mk @@ -1,77 +1,109 @@ +# 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. + .PHONY: server-binaries server-binaries: controller acmesolver webhook cainjector -$(BINDIR)/server: +$(bin_dir)/server: @mkdir -p $@ .PHONY: controller -controller: $(BINDIR)/server/controller-linux-amd64 $(BINDIR)/server/controller-linux-arm64 $(BINDIR)/server/controller-linux-s390x $(BINDIR)/server/controller-linux-ppc64le $(BINDIR)/server/controller-linux-arm | $(NEEDS_GO) $(BINDIR)/server +controller: $(bin_dir)/server/controller-linux-amd64 $(bin_dir)/server/controller-linux-arm64 $(bin_dir)/server/controller-linux-s390x $(bin_dir)/server/controller-linux-ppc64le $(bin_dir)/server/controller-linux-arm | $(NEEDS_GO) $(bin_dir)/server -$(BINDIR)/server/controller-linux-amd64: $(SOURCES) | $(NEEDS_GO) $(BINDIR)/server - GOOS=linux GOARCH=amd64 $(GOBUILD) -o $@ $(GOFLAGS) -ldflags '$(GOLDFLAGS)' cmd/controller/main.go +$(bin_dir)/server/controller-linux-amd64: $(SOURCES) | $(NEEDS_GO) $(bin_dir)/server + cd cmd/controller && GOOS=linux GOARCH=amd64 $(GOBUILD) -o ../../$@ $(GOFLAGS) -ldflags '$(GOLDFLAGS)' main.go -$(BINDIR)/server/controller-linux-arm64: $(SOURCES) | $(NEEDS_GO) $(BINDIR)/server - GOOS=linux GOARCH=arm64 $(GOBUILD) -o $@ $(GOFLAGS) -ldflags '$(GOLDFLAGS)' cmd/controller/main.go +$(bin_dir)/server/controller-linux-arm64: $(SOURCES) | $(NEEDS_GO) $(bin_dir)/server + cd cmd/controller && GOOS=linux GOARCH=arm64 $(GOBUILD) -o ../../$@ $(GOFLAGS) -ldflags '$(GOLDFLAGS)' main.go -$(BINDIR)/server/controller-linux-s390x: $(SOURCES) | $(NEEDS_GO) $(BINDIR)/server - GOOS=linux GOARCH=s390x $(GOBUILD) -o $@ $(GOFLAGS) -ldflags '$(GOLDFLAGS)' cmd/controller/main.go +$(bin_dir)/server/controller-linux-s390x: $(SOURCES) | $(NEEDS_GO) $(bin_dir)/server + cd cmd/controller && GOOS=linux GOARCH=s390x $(GOBUILD) -o ../../$@ $(GOFLAGS) -ldflags '$(GOLDFLAGS)' main.go -$(BINDIR)/server/controller-linux-ppc64le: $(SOURCES) | $(NEEDS_GO) $(BINDIR)/server - GOOS=linux GOARCH=ppc64le $(GOBUILD) -o $@ $(GOFLAGS) -ldflags '$(GOLDFLAGS)' cmd/controller/main.go +$(bin_dir)/server/controller-linux-ppc64le: $(SOURCES) | $(NEEDS_GO) $(bin_dir)/server + cd cmd/controller && GOOS=linux GOARCH=ppc64le $(GOBUILD) -o ../../$@ $(GOFLAGS) -ldflags '$(GOLDFLAGS)' main.go -$(BINDIR)/server/controller-linux-arm: $(SOURCES) | $(NEEDS_GO) $(BINDIR)/server - GOOS=linux GOARCH=arm GOARM=7 $(GOBUILD) -o $@ $(GOFLAGS) -ldflags '$(GOLDFLAGS)' cmd/controller/main.go +$(bin_dir)/server/controller-linux-arm: $(SOURCES) | $(NEEDS_GO) $(bin_dir)/server + cd cmd/controller && GOOS=linux GOARCH=arm GOARM=7 $(GOBUILD) -o ../../$@ $(GOFLAGS) -ldflags '$(GOLDFLAGS)' main.go .PHONY: acmesolver -acmesolver: $(BINDIR)/server/acmesolver-linux-amd64 $(BINDIR)/server/acmesolver-linux-arm64 $(BINDIR)/server/acmesolver-linux-s390x $(BINDIR)/server/acmesolver-linux-ppc64le $(BINDIR)/server/acmesolver-linux-arm | $(NEEDS_GO) $(BINDIR)/server +acmesolver: $(bin_dir)/server/acmesolver-linux-amd64 $(bin_dir)/server/acmesolver-linux-arm64 $(bin_dir)/server/acmesolver-linux-s390x $(bin_dir)/server/acmesolver-linux-ppc64le $(bin_dir)/server/acmesolver-linux-arm | $(NEEDS_GO) $(bin_dir)/server -$(BINDIR)/server/acmesolver-linux-amd64: $(SOURCES) | $(NEEDS_GO) $(BINDIR)/server - GOOS=linux GOARCH=amd64 $(GOBUILD) -o $@ $(GOFLAGS) -ldflags '$(GOLDFLAGS)' cmd/acmesolver/main.go +$(bin_dir)/server/acmesolver-linux-amd64: $(SOURCES) | $(NEEDS_GO) $(bin_dir)/server + cd cmd/acmesolver && GOOS=linux GOARCH=amd64 $(GOBUILD) -o ../../$@ $(GOFLAGS) -ldflags '$(GOLDFLAGS)' main.go -$(BINDIR)/server/acmesolver-linux-arm64: $(SOURCES) | $(NEEDS_GO) $(BINDIR)/server - GOOS=linux GOARCH=arm64 $(GOBUILD) -o $@ $(GOFLAGS) -ldflags '$(GOLDFLAGS)' cmd/acmesolver/main.go +$(bin_dir)/server/acmesolver-linux-arm64: $(SOURCES) | $(NEEDS_GO) $(bin_dir)/server + cd cmd/acmesolver && GOOS=linux GOARCH=arm64 $(GOBUILD) -o ../../$@ $(GOFLAGS) -ldflags '$(GOLDFLAGS)' main.go -$(BINDIR)/server/acmesolver-linux-s390x: $(SOURCES) | $(NEEDS_GO) $(BINDIR)/server - GOOS=linux GOARCH=s390x $(GOBUILD) -o $@ $(GOFLAGS) -ldflags '$(GOLDFLAGS)' cmd/acmesolver/main.go +$(bin_dir)/server/acmesolver-linux-s390x: $(SOURCES) | $(NEEDS_GO) $(bin_dir)/server + cd cmd/acmesolver && GOOS=linux GOARCH=s390x $(GOBUILD) -o ../../$@ $(GOFLAGS) -ldflags '$(GOLDFLAGS)' main.go -$(BINDIR)/server/acmesolver-linux-ppc64le: $(SOURCES) | $(NEEDS_GO) $(BINDIR)/server - GOOS=linux GOARCH=ppc64le $(GOBUILD) -o $@ $(GOFLAGS) -ldflags '$(GOLDFLAGS)' cmd/acmesolver/main.go +$(bin_dir)/server/acmesolver-linux-ppc64le: $(SOURCES) | $(NEEDS_GO) $(bin_dir)/server + cd cmd/acmesolver && GOOS=linux GOARCH=ppc64le $(GOBUILD) -o ../../$@ $(GOFLAGS) -ldflags '$(GOLDFLAGS)' main.go -$(BINDIR)/server/acmesolver-linux-arm: $(SOURCES) | $(NEEDS_GO) $(BINDIR)/server - GOOS=linux GOARCH=arm GOARM=7 $(GOBUILD) -o $@ $(GOFLAGS) -ldflags '$(GOLDFLAGS)' cmd/acmesolver/main.go +$(bin_dir)/server/acmesolver-linux-arm: $(SOURCES) | $(NEEDS_GO) $(bin_dir)/server + cd cmd/acmesolver && GOOS=linux GOARCH=arm GOARM=7 $(GOBUILD) -o ../../$@ $(GOFLAGS) -ldflags '$(GOLDFLAGS)' main.go .PHONY: webhook -webhook: $(BINDIR)/server/webhook-linux-amd64 $(BINDIR)/server/webhook-linux-arm64 $(BINDIR)/server/webhook-linux-s390x $(BINDIR)/server/webhook-linux-ppc64le $(BINDIR)/server/webhook-linux-arm | $(NEEDS_GO) $(BINDIR)/server +webhook: $(bin_dir)/server/webhook-linux-amd64 $(bin_dir)/server/webhook-linux-arm64 $(bin_dir)/server/webhook-linux-s390x $(bin_dir)/server/webhook-linux-ppc64le $(bin_dir)/server/webhook-linux-arm | $(NEEDS_GO) $(bin_dir)/server -$(BINDIR)/server/webhook-linux-amd64: $(SOURCES) | $(NEEDS_GO) $(BINDIR)/server - GOOS=linux GOARCH=amd64 $(GOBUILD) -o $@ $(GOFLAGS) -ldflags '$(GOLDFLAGS)' cmd/webhook/main.go +$(bin_dir)/server/webhook-linux-amd64: $(SOURCES) | $(NEEDS_GO) $(bin_dir)/server + cd cmd/webhook && GOOS=linux GOARCH=amd64 $(GOBUILD) -o ../../$@ $(GOFLAGS) -ldflags '$(GOLDFLAGS)' main.go -$(BINDIR)/server/webhook-linux-arm64: $(SOURCES) | $(NEEDS_GO) $(BINDIR)/server - GOOS=linux GOARCH=arm64 $(GOBUILD) -o $@ $(GOFLAGS) -ldflags '$(GOLDFLAGS)' cmd/webhook/main.go +$(bin_dir)/server/webhook-linux-arm64: $(SOURCES) | $(NEEDS_GO) $(bin_dir)/server + cd cmd/webhook && GOOS=linux GOARCH=arm64 $(GOBUILD) -o ../../$@ $(GOFLAGS) -ldflags '$(GOLDFLAGS)' main.go -$(BINDIR)/server/webhook-linux-s390x: $(SOURCES) | $(NEEDS_GO) $(BINDIR)/server - GOOS=linux GOARCH=s390x $(GOBUILD) -o $@ $(GOFLAGS) -ldflags '$(GOLDFLAGS)' cmd/webhook/main.go +$(bin_dir)/server/webhook-linux-s390x: $(SOURCES) | $(NEEDS_GO) $(bin_dir)/server + cd cmd/webhook && GOOS=linux GOARCH=s390x $(GOBUILD) -o ../../$@ $(GOFLAGS) -ldflags '$(GOLDFLAGS)' main.go -$(BINDIR)/server/webhook-linux-ppc64le: $(SOURCES) | $(NEEDS_GO) $(BINDIR)/server - GOOS=linux GOARCH=ppc64le $(GOBUILD) -o $@ $(GOFLAGS) -ldflags '$(GOLDFLAGS)' cmd/webhook/main.go +$(bin_dir)/server/webhook-linux-ppc64le: $(SOURCES) | $(NEEDS_GO) $(bin_dir)/server + cd cmd/webhook && GOOS=linux GOARCH=ppc64le $(GOBUILD) -o ../../$@ $(GOFLAGS) -ldflags '$(GOLDFLAGS)' main.go -$(BINDIR)/server/webhook-linux-arm: $(SOURCES) | $(NEEDS_GO) $(BINDIR)/server - GOOS=linux GOARCH=arm GOARM=7 $(GOBUILD) -o $@ $(GOFLAGS) -ldflags '$(GOLDFLAGS)' cmd/webhook/main.go +$(bin_dir)/server/webhook-linux-arm: $(SOURCES) | $(NEEDS_GO) $(bin_dir)/server + cd cmd/webhook && GOOS=linux GOARCH=arm GOARM=7 $(GOBUILD) -o ../../$@ $(GOFLAGS) -ldflags '$(GOLDFLAGS)' main.go .PHONY: cainjector -cainjector: $(BINDIR)/server/cainjector-linux-amd64 $(BINDIR)/server/cainjector-linux-arm64 $(BINDIR)/server/cainjector-linux-s390x $(BINDIR)/server/cainjector-linux-ppc64le $(BINDIR)/server/cainjector-linux-arm | $(NEEDS_GO) $(BINDIR)/server +cainjector: $(bin_dir)/server/cainjector-linux-amd64 $(bin_dir)/server/cainjector-linux-arm64 $(bin_dir)/server/cainjector-linux-s390x $(bin_dir)/server/cainjector-linux-ppc64le $(bin_dir)/server/cainjector-linux-arm | $(NEEDS_GO) $(bin_dir)/server + +$(bin_dir)/server/cainjector-linux-amd64: $(SOURCES) | $(NEEDS_GO) $(bin_dir)/server + cd cmd/cainjector && GOOS=linux GOARCH=amd64 $(GOBUILD) -o ../../$@ $(GOFLAGS) -ldflags '$(GOLDFLAGS)' main.go + +$(bin_dir)/server/cainjector-linux-arm64: $(SOURCES) | $(NEEDS_GO) $(bin_dir)/server + cd cmd/cainjector && GOOS=linux GOARCH=arm64 $(GOBUILD) -o ../../$@ $(GOFLAGS) -ldflags '$(GOLDFLAGS)' main.go + +$(bin_dir)/server/cainjector-linux-s390x: $(SOURCES) | $(NEEDS_GO) $(bin_dir)/server + cd cmd/cainjector && GOOS=linux GOARCH=s390x $(GOBUILD) -o ../../$@ $(GOFLAGS) -ldflags '$(GOLDFLAGS)' main.go + +$(bin_dir)/server/cainjector-linux-ppc64le: $(SOURCES) | $(NEEDS_GO) $(bin_dir)/server + cd cmd/cainjector && GOOS=linux GOARCH=ppc64le $(GOBUILD) -o ../../$@ $(GOFLAGS) -ldflags '$(GOLDFLAGS)' main.go + +$(bin_dir)/server/cainjector-linux-arm: $(SOURCES) | $(NEEDS_GO) $(bin_dir)/server + cd cmd/cainjector && GOOS=linux GOARCH=arm GOARM=7 $(GOBUILD) -o ../../$@ $(GOFLAGS) -ldflags '$(GOLDFLAGS)' main.go + +.PHONY: startupapicheck +cainjector: $(bin_dir)/server/startupapicheck-linux-amd64 $(bin_dir)/server/startupapicheck-linux-arm64 $(bin_dir)/server/startupapicheck-linux-s390x $(bin_dir)/server/startupapicheck-linux-ppc64le $(bin_dir)/server/startupapicheck-linux-arm | $(NEEDS_GO) $(bin_dir)/server -$(BINDIR)/server/cainjector-linux-amd64: $(SOURCES) | $(NEEDS_GO) $(BINDIR)/server - GOOS=linux GOARCH=amd64 $(GOBUILD) -o $@ $(GOFLAGS) -ldflags '$(GOLDFLAGS)' cmd/cainjector/main.go +$(bin_dir)/server/startupapicheck-linux-amd64: $(SOURCES) | $(NEEDS_GO) $(bin_dir)/server + cd cmd/startupapicheck && GOOS=linux GOARCH=amd64 $(GOBUILD) -o ../../$@ $(GOFLAGS) -ldflags '$(GOLDFLAGS)' main.go -$(BINDIR)/server/cainjector-linux-arm64: $(SOURCES) | $(NEEDS_GO) $(BINDIR)/server - GOOS=linux GOARCH=arm64 $(GOBUILD) -o $@ $(GOFLAGS) -ldflags '$(GOLDFLAGS)' cmd/cainjector/main.go +$(bin_dir)/server/startupapicheck-linux-arm64: $(SOURCES) | $(NEEDS_GO) $(bin_dir)/server + cd cmd/startupapicheck && GOOS=linux GOARCH=arm64 $(GOBUILD) -o ../../$@ $(GOFLAGS) -ldflags '$(GOLDFLAGS)' main.go -$(BINDIR)/server/cainjector-linux-s390x: $(SOURCES) | $(NEEDS_GO) $(BINDIR)/server - GOOS=linux GOARCH=s390x $(GOBUILD) -o $@ $(GOFLAGS) -ldflags '$(GOLDFLAGS)' cmd/cainjector/main.go +$(bin_dir)/server/startupapicheck-linux-s390x: $(SOURCES) | $(NEEDS_GO) $(bin_dir)/server + cd cmd/startupapicheck && GOOS=linux GOARCH=s390x $(GOBUILD) -o ../../$@ $(GOFLAGS) -ldflags '$(GOLDFLAGS)' main.go -$(BINDIR)/server/cainjector-linux-ppc64le: $(SOURCES) | $(NEEDS_GO) $(BINDIR)/server - GOOS=linux GOARCH=ppc64le $(GOBUILD) -o $@ $(GOFLAGS) -ldflags '$(GOLDFLAGS)' cmd/cainjector/main.go +$(bin_dir)/server/startupapicheck-linux-ppc64le: $(SOURCES) | $(NEEDS_GO) $(bin_dir)/server + cd cmd/startupapicheck && GOOS=linux GOARCH=ppc64le $(GOBUILD) -o ../../$@ $(GOFLAGS) -ldflags '$(GOLDFLAGS)' main.go -$(BINDIR)/server/cainjector-linux-arm: $(SOURCES) | $(NEEDS_GO) $(BINDIR)/server - GOOS=linux GOARCH=arm GOARM=7 $(GOBUILD) -o $@ $(GOFLAGS) -ldflags '$(GOLDFLAGS)' cmd/cainjector/main.go +$(bin_dir)/server/startupapicheck-linux-arm: $(SOURCES) | $(NEEDS_GO) $(bin_dir)/server + cd cmd/startupapicheck && GOOS=linux GOARCH=arm GOARM=7 $(GOBUILD) -o ../../$@ $(GOFLAGS) -ldflags '$(GOLDFLAGS)' main.go diff --git a/make/test.mk b/make/test.mk index c8489a88afc..eb03af62c66 100644 --- a/make/test.mk +++ b/make/test.mk @@ -1,9 +1,26 @@ -export KUBEBUILDER_ASSETS=$(PWD)/$(BINDIR)/tools +# 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. + +export KUBEBUILDER_ASSETS=$(PWD)/$(bin_dir)/tools + +# GOTESTSUM_CI_FLAGS contains flags which are common to invocations of gotestsum in CI environments +GOTESTSUM_CI_FLAGS := --junitfile-testsuite-name short --junitfile-testcase-classname relative # WHAT can be used to control which unit tests are run by "make test"; defaults to running all # tests except e2e tests (which require more significant setup) # For example: make WHAT=./pkg/util/pki test-pretty to only run the PKI utils tests -WHAT ?= ./pkg/... ./cmd/... ./internal/... ./test/... ./hack/prune-junit-xml/... +WHAT ?= ./pkg/... ./internal/... ./test/... ./hack/prune-junit-xml/... .PHONY: test ## Test is the workhorse test command which by default runs all unit and @@ -11,14 +28,18 @@ WHAT ?= ./pkg/... ./cmd/... ./internal/... ./test/... ./hack/prune-junit-xml/... ## ## make test WHAT=./pkg/... ## +## Note that some tests and binaries are separated into different modules, and +## as such won't be testable from the root directory or using this command. +## There are separate make targets - such as "make unit-test" which should be +## used to test everything at once. +## ## @category Development test: setup-integration-tests | $(NEEDS_GOTESTSUM) $(NEEDS_ETCD) $(NEEDS_KUBECTL) $(NEEDS_KUBE-APISERVER) $(NEEDS_GO) $(GOTESTSUM) -- $(WHAT) .PHONY: test-ci -## test-ci runs all unit and integration tests and writes a JUnit report of -## the results. WHAT can be used to limit which tests are run; see help for -## `make test` for more details. +## test-ci runs all unit and integration tests and writes JUnit reports of +## the results. ## ## Fuzz tests are hidden from JUnit output, because they're noisy and can cause ## issues with dashboards and UIs. @@ -27,12 +48,17 @@ test: setup-integration-tests | $(NEEDS_GOTESTSUM) $(NEEDS_ETCD) $(NEEDS_KUBECTL test-ci: setup-integration-tests | $(NEEDS_GOTESTSUM) $(NEEDS_ETCD) $(NEEDS_KUBECTL) $(NEEDS_KUBE-APISERVER) $(NEEDS_GO) @mkdir -p $(ARTIFACTS) $(GOTESTSUM) \ - --junitfile $(ARTIFACTS)/junit_make-test-ci.xml \ - --junitfile-testsuite-name short \ - --junitfile-testcase-classname relative \ + --junitfile $(ARTIFACTS)/junit_make-test-ci-core.xml \ + $(GOTESTSUM_CI_FLAGS) \ --post-run-command $$'bash -c "$(GO) run hack/prune-junit-xml/prunexml.go $$GOTESTSUM_JUNITFILE"' \ -- \ $(WHAT) + cd cmd/acmesolver && $(GOTESTSUM) --junitfile $(ARTIFACTS)/junit_make-test-ci-acmesolver.xml $(GOTESTSUM_CI_FLAGS) --post-run-command $$'bash -c "$(GO) run ../../hack/prune-junit-xml/prunexml.go $$GOTESTSUM_JUNITFILE"' -- ./... + cd cmd/cainjector && $(GOTESTSUM) --junitfile $(ARTIFACTS)/junit_make-test-ci-cainjector.xml $(GOTESTSUM_CI_FLAGS) --post-run-command $$'bash -c "$(GO) run ../../hack/prune-junit-xml/prunexml.go $$GOTESTSUM_JUNITFILE"' -- ./... + cd cmd/controller && $(GOTESTSUM) --junitfile $(ARTIFACTS)/junit_make-test-ci-controller.xml $(GOTESTSUM_CI_FLAGS) --post-run-command $$'bash -c "$(GO) run ../../hack/prune-junit-xml/prunexml.go $$GOTESTSUM_JUNITFILE"' -- ./... + cd cmd/webhook && $(GOTESTSUM) --junitfile $(ARTIFACTS)/junit_make-test-ci-webhook.xml $(GOTESTSUM_CI_FLAGS) --post-run-command $$'bash -c "$(GO) run ../../hack/prune-junit-xml/prunexml.go $$GOTESTSUM_JUNITFILE"' -- ./... + cd test/integration && $(GOTESTSUM) --junitfile $(ARTIFACTS)/junit_make-test-ci-integration.xml $(GOTESTSUM_CI_FLAGS) --post-run-command $$'bash -c "$(GO) run ../../hack/prune-junit-xml/prunexml.go $$GOTESTSUM_JUNITFILE"' -- ./... + $(GOTESTSUM) --junitfile $(ARTIFACTS)/junit_make-test-ci-livedns.xml $(GOTESTSUM_CI_FLAGS) --post-run-command $$'bash -c "$(GO) run ./hack/prune-junit-xml/prunexml.go $$GOTESTSUM_JUNITFILE"' -- --tags=livedns_test ./pkg/issuer/acme/dns/util/... .PHONY: unit-test ## Same as `test` but only runs the unit tests. By "unit tests", we mean tests @@ -40,13 +66,40 @@ test-ci: setup-integration-tests | $(NEEDS_GOTESTSUM) $(NEEDS_ETCD) $(NEEDS_KUBE ## or an apiserver. ## ## @category Development -unit-test: | $(NEEDS_GOTESTSUM) - $(GOTESTSUM) ./cmd/... ./pkg/... ./internal/... +unit-test: unit-test-core-module unit-test-acmesolver unit-test-cainjector unit-test-controller unit-test-webhook unit-test-thirdparty | $(NEEDS_GOTESTSUM) + +.PHONY: unit-test-core-module +unit-test-core-module: | $(NEEDS_GOTESTSUM) + $(GOTESTSUM) ./pkg/... ./internal/... + +.PHONY: unit-test-acmesolver +unit-test-acmesolver: | $(NEEDS_GOTESTSUM) + cd cmd/acmesolver && $(GOTESTSUM) ./... + +.PHONY: unit-test-cainjector +unit-test-cainjector: | $(NEEDS_GOTESTSUM) + cd cmd/cainjector && $(GOTESTSUM) ./... + +.PHONY: unit-test-controller +unit-test-controller: | $(NEEDS_GOTESTSUM) + cd cmd/controller && $(GOTESTSUM) ./... + +.PHONY: unit-test-webhook +unit-test-webhook: | $(NEEDS_GOTESTSUM) + cd cmd/webhook && $(GOTESTSUM) ./... + +.PHONY: unit-test-thirdparty +unit-test-thirdparty: | $(NEEDS_GOTESTSUM) + $(GOTESTSUM) ./third_party/... + +.PHONY: update-config-api-defaults +update-config-api-defaults: | $(NEEDS_GO) + cd internal/apis/config/cainjector/v1alpha1/ && UPDATE_DEFAULTS=true $(GO) test . && echo "cainjector config api defaults updated" + cd internal/apis/config/controller/v1alpha1/ && UPDATE_DEFAULTS=true $(GO) test . && echo "controller config api defaults updated" + cd internal/apis/config/webhook/v1alpha1/ && UPDATE_DEFAULTS=true $(GO) test . && echo "webhook config api defaults updated" .PHONY: setup-integration-tests -setup-integration-tests: test/integration/versionchecker/testdata/test_manifests.tar templated-crds - @$(eval GIT_TAGS_FILE := $(BINDIR)/scratch/git/upstream-tags.txt) - @echo -e "\033[0;33mLatest known tag for integration tests is $(shell tail -1 $(GIT_TAGS_FILE)); if that seems out-of-date,\npull latest tags, run 'rm $(GIT_TAGS_FILE)' and retest\033[0m" +setup-integration-tests: # No dependencies .PHONY: integration-test ## Same as `test` but only run the integration tests. By "integration tests", @@ -55,7 +108,22 @@ setup-integration-tests: test/integration/versionchecker/testdata/test_manifests ## ## @category Development integration-test: setup-integration-tests | $(NEEDS_GOTESTSUM) $(NEEDS_ETCD) $(NEEDS_KUBECTL) $(NEEDS_KUBE-APISERVER) $(NEEDS_GO) - $(GOTESTSUM) ./test/... + cd test/integration && $(GOTESTSUM) ./... + +.PHONY: livedns-test +## "Live DNS" tests rely on external DNS records and are separated from other tests +## for various reasons detailed in the files tested by this target. +## +## @category Development +livedns-test: | $(NEEDS_GOTESTSUM) $(NEEDS_GO) + $(GOTESTSUM) -- --tags=livedns_test ./pkg/issuer/acme/dns/util/... + +## (optional) Set this to true to run the E2E tests against an OpenShift cluster. +## When set to true, the Hashicorp Vault Helm chart will be installed with +## settings appropriate for OpenShift. +## +## @category Development +E2E_OPENSHIFT ?= false .PHONY: e2e ## Run the end-to-end tests. Before running this, you need to run: @@ -69,40 +137,60 @@ integration-test: setup-integration-tests | $(NEEDS_GOTESTSUM) $(NEEDS_ETCD) $(N ## For more information about GINKGO_FOCUS, see "make/e2e.sh --help". ## ## @category Development -e2e: $(BINDIR)/scratch/kind-exists | $(NEEDS_KUBECTL) $(NEEDS_GINKGO) +e2e: $(bin_dir)/scratch/kind-exists | $(NEEDS_KUBECTL) $(NEEDS_GINKGO) make/e2e.sh .PHONY: e2e-ci -e2e-ci: e2e-setup-kind e2e-setup +e2e-ci: | $(NEEDS_GO) + $(shell export HELM_BURST_LIMIT=-1) + $(MAKE) e2e-setup-kind e2e-setup make/e2e-ci.sh +$(bin_dir)/test/e2e.test: FORCE | $(NEEDS_GINKGO) $(bin_dir)/test + CGO_ENABLED=0 $(GINKGO) build --ldflags="-w -s" --trimpath --tags e2e_test test/e2e + mv test/e2e/e2e.test $(bin_dir)/test/e2e.test + +.PHONY: e2e-build +## Build an end-to-end test binary +## +## The resulting binary can be used to execute the end-to-end tests on a +## computer without the test source files and without Go or Ginkgo installed. +## +## For example, the binary can be copied to an OpenShift CRC virtual machine and +## used to run end-to-end tests against cert-manager that has been installed +## using OperatorHub. +## +## Most of the tests require some other dependencies such as an ingress controller or an ACME server, +## so you will need to use --ginkgo.skip and / or --ginkgo.focus to select a subset of the tests. +## +## The tests will use the current context in your KUBECONFIG file +## and create namespaces and resources in that cluster. +## +## Here's an example of how you might run a subset of the end-to-end tests +## which only require cert-manager to be installed: +## +## ./_bin/test/e2e.test --repo-root=/dev/null --ginkgo.focus="CA\ Issuer" --ginkgo.skip="Gateway" +## +## @category Development +e2e-build: $(bin_dir)/test/e2e.test + +## Sets the search prefix for finding the "latest" release in test-upgrade +## To find the latest release for, e.g., cert-manager v1.12, use "v1.12*" +UPGRADE_TEST_INITIAL_RELEASE_PREFIX ?= + +## Can be set to choose a different starting point for the upgrade test, +## which defaults to using the latest published release and upgrading from +## there to master +UPGRADE_TEST_INITIAL_RELEASE ?= + .PHONY: test-upgrade -test-upgrade: | $(NEEDS_HELM) $(NEEDS_KIND) $(NEEDS_YTT) $(NEEDS_KUBECTL) $(BINDIR)/cmctl/cmctl-$(HOST_OS)-$(HOST_ARCH) - ./hack/verify-upgrade.sh $(HELM) $(KIND) $(YTT) $(KUBECTL) $(BINDIR)/cmctl/cmctl-$(HOST_OS)-$(HOST_ARCH) - -test/integration/versionchecker/testdata/test_manifests.tar: $(BINDIR)/scratch/oldcrds.tar $(BINDIR)/yaml/cert-manager.yaml - @# Remove the temp files if they exist - rm -f $(BINDIR)/scratch/versionchecker-test-manifests.tar $(BINDIR)/scratch/$(RELEASE_VERSION).yaml - @# Copy the old CRDs tar and append the currentl release version to it - cp $(BINDIR)/scratch/oldcrds.tar $(BINDIR)/scratch/versionchecker-test-manifests.tar - cp $(BINDIR)/yaml/cert-manager.yaml $(BINDIR)/scratch/$(RELEASE_VERSION).yaml - tar --append -f $(BINDIR)/scratch/versionchecker-test-manifests.tar -C $(BINDIR)/scratch ./$(RELEASE_VERSION).yaml - cp $(BINDIR)/scratch/versionchecker-test-manifests.tar $@ - -$(BINDIR)/scratch/oldcrds.tar: $(BINDIR)/scratch/git/upstream-tags.txt | $(BINDIR)/scratch/oldcrds - @# First, download the CRDs for all releases listed in upstream-tags.txt - <$(BINDIR)/scratch/git/upstream-tags.txt xargs -I% -P5 \ - ./hack/fetch-old-crd.sh \ - "https://github.com/cert-manager/cert-manager/releases/download/%/cert-manager.yaml" \ - $(BINDIR)/scratch/oldcrds/%.yaml - @# Next, tar up the old CRDs together - tar cf $@ -C $(BINDIR)/scratch/oldcrds . - -$(BINDIR)/scratch/oldcrds: - @mkdir -p $@ +test-upgrade: | $(NEEDS_HELM) $(NEEDS_KIND) $(NEEDS_YTT) $(NEEDS_KUBECTL) $(NEEDS_CMCTL) + INITIAL_RELEASE=$(UPGRADE_TEST_INITIAL_RELEASE) \ + INITIAL_RELEASE_PREFIX=$(UPGRADE_TEST_INITIAL_RELEASE_PREFIX) \ + ./hack/verify-upgrade.sh $(HELM) $(KIND) $(YTT) $(KUBECTL) $(CMCTL) $(HOST_ARCH) -$(BINDIR)/test: +$(bin_dir)/test: @mkdir -p $@ -$(BINDIR)/testlogs: +$(bin_dir)/testlogs: @mkdir -p $@ diff --git a/make/third_party.mk b/make/third_party.mk new file mode 100644 index 00000000000..628b1fa35eb --- /dev/null +++ b/make/third_party.mk @@ -0,0 +1,28 @@ +# 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. + +.PHONY: update-third-party +## Update the code in the `third_party/` directory. +## +## @category Development +update-third-party: | $(NEEDS_KLONE) + @pushd third_party && $(KLONE) sync + @echo acme: Updating import statements + @find third_party/forked/acme -iname '*.go' \ + | xargs sed -e 's%golang\.org/x/crypto/acme%github.com/cert-manager/cert-manager/third_party/forked/acme%g' -i + @echo acme: Updating the package version in the user-agent string + @sed -e 's%golang\.org/x/crypto%github.com/cert-manager/cert-manager%' -i third_party/forked/acme/http.go + @pushd third_party/forked/acme && curl -fsSL \ + -O https://raw.githubusercontent.com/golang/crypto/refs/heads/master/LICENSE \ + -O https://raw.githubusercontent.com/golang/crypto/refs/heads/master/PATENTS diff --git a/make/tools.mk b/make/tools.mk deleted file mode 100644 index 5d44d6323a8..00000000000 --- a/make/tools.mk +++ /dev/null @@ -1,419 +0,0 @@ -# To make sure we use the right version of each tool, we put symlink in -# $(BINDIR)/tools, and the actual binaries are in $(BINDIR)/downloaded. When bumping -# the version of the tools, this symlink gets updated. - -# Let's have $(BINDIR)/tools in front of the PATH so that we don't inavertedly -# pick up the wrong binary somewhere. Watch out, $(shell echo $$PATH) will -# still print the original PATH, since GNU make does not honor exported -# variables: https://stackoverflow.com/questions/54726457 -export PATH := $(PWD)/$(BINDIR)/tools:$(PATH) - -CTR=docker - -TOOLS := -TOOLS += helm=v3.10.0 -TOOLS += kubectl=v1.25.2 -TOOLS += kind=v0.16.0 -TOOLS += controller-gen=v0.10.0 -TOOLS += cosign=v1.12.1 -TOOLS += cmrel=a1e2bad95be9688794fd0571c4c40e88cccf9173 -TOOLS += release-notes=v0.14.0 -TOOLS += goimports=v0.1.12 -TOOLS += go-licenses=v1.3.1 -TOOLS += gotestsum=v1.8.2 -TOOLS += rclone=v1.59.2 -TOOLS += trivy=v0.32.0 -TOOLS += ytt=v0.43.0 -TOOLS += yq=v4.27.5 -TOOLS += crane=v0.11.0 -TOOLS += ginkgo=$(shell awk '/ginkgo\/v2/ {print $$2}' go.mod) - -GATEWAY_API_VERSION=v0.5.0 - -K8S_CODEGEN_VERSION=v0.25.2 - -KUBEBUILDER_ASSETS_VERSION=1.25.0 -TOOLS += etcd=$(KUBEBUILDER_ASSETS_VERSION) -TOOLS += kube-apiserver=$(KUBEBUILDER_ASSETS_VERSION) - -VENDORED_GO_VERSION := 1.19.1 - -# When switching branches which use different versions of the tools, we -# need a way to re-trigger the symlinking from $(BINDIR)/downloaded to $(BINDIR)/tools. -$(BINDIR)/scratch/%_VERSION: FORCE | $(BINDIR)/scratch - @test "$($*_VERSION)" == "$(shell cat $@ 2>/dev/null)" || echo $($*_VERSION) > $@ - -# The reason we don't use "go env GOOS" or "go env GOARCH" is that the "go" -# binary may not be available in the PATH yet when the Makefiles are -# evaluated. HOST_OS and HOST_ARCH only support Linux, *BSD and macOS (M1 -# and Intel). -HOST_OS := $(shell uname -s | tr A-Z a-z) -HOST_ARCH = $(shell uname -m) -ifeq (x86_64, $(HOST_ARCH)) - HOST_ARCH = amd64 -endif - -# --silent = don't print output like progress meters -# --show-error = but do print errors when they happen -# --fail = exit with a nonzero error code without the response from the server when there's an HTTP error -# --location = follow redirects from the server -# --retry = the number of times to retry a failed attempt to connect -# --retry-connrefused = retry even if the initial connection was refused -CURL = curl --silent --show-error --fail --location --retry 10 --retry-connrefused - -# In Prow, the pod has the folder "$(BINDIR)/downloaded" mounted into the -# container. For some reason, even though the permissions are correct, -# binaries that are mounted with hostPath can't be executed. When in CI, we -# copy the binaries to work around that. Using $(LN) is only required when -# dealing with binaries. Other files and folders can be symlinked. -# -# Details on how "$(BINDIR)/downloaded" gets cached are available in the -# description of the PR https://github.com/jetstack/testing/pull/651. -# -# We use "printenv CI" instead of just "ifeq ($(CI),)" because otherwise we -# would get "warning: undefined variable 'CI'". -ifeq ($(shell printenv CI),) -LN := ln -f -s -else -LN := cp -f -r -endif - -UC = $(shell echo '$1' | tr a-z A-Z) -LC = $(shell echo '$1' | tr A-Z a-z) - -TOOL_NAMES := - -# for each item `xxx` in the TOOLS variable: -# - a $(XXX_VERSION) variable is generated -# -> this variable contains the version of the tool -# - a $(NEEDS_XXX) variable is generated -# -> this variable contains the target name for the tool, -# which is the relative path of the binary, this target -# should be used when adding the tool as a dependency to -# your target, you can't use $(XXX) as a dependency because -# make does not support an absolute path as a dependency -# - a $(XXX) variable is generated -# -> this variable contains the absolute path of the binary, -# the absolute path should be used when executing the binary -# in targets or in scripts, because it is agnostic to the -# working directory -# - an unversioned target $(BINDIR)/tools/xxx is generated that -# creates a copy/ link to the corresponding versioned target: -# $(BINDIR)/tools/xxx@$(XXX_VERSION)_$(HOST_OS)_$(HOST_ARCH) -define tool_defs -TOOL_NAMES += $1 - -$(call UC,$1)_VERSION ?= $2 -NEEDS_$(call UC,$1) := $$(BINDIR)/tools/$1 -$(call UC,$1) := $$(PWD)/$$(BINDIR)/tools/$1 - -$$(BINDIR)/tools/$1: $$(BINDIR)/scratch/$(call UC,$1)_VERSION | $$(BINDIR)/downloaded/tools/$1@$$($(call UC,$1)_VERSION)_$$(HOST_OS)_$$(HOST_ARCH) $$(BINDIR)/tools - cd $$(dir $$@) && $$(LN) $$(patsubst $$(BINDIR)/%,../%,$$(word 1,$$|)) $$(notdir $$@) -endef - -$(foreach TOOL,$(TOOLS),$(eval $(call tool_defs,$(word 1,$(subst =, ,$(TOOL))),$(word 2,$(subst =, ,$(TOOL)))))) - -TOOLS_PATHS := $(TOOL_NAMES:%=$(BINDIR)/tools/%) - -###### -# Go # -###### - -# $(NEEDS_GO) is a target that is set as an order-only prerequisite in -# any target that calls $(GO), e.g.: -# -# $(BINDIR)/tools/crane: $(NEEDS_GO) -# $(GO) build -o $(BINDIR)/tools/crane -# -# $(NEEDS_GO) is empty most of the time, except when running "make vendor-go" -# or when "make vendor-go" was previously run, in which case $(NEEDS_GO) is set -# to $(BINDIR)/tools/go, since $(BINDIR)/tools/go is a prerequisite of -# any target depending on Go when "make vendor-go" was run. -NEEDS_GO := $(if $(findstring vendor-go,$(MAKECMDGOALS))$(shell [ -f $(BINDIR)/tools/go ] && echo yes), $(BINDIR)/tools/go,) -ifeq ($(NEEDS_GO),) -GO := go -else -export GOROOT := $(PWD)/$(BINDIR)/tools/goroot -export PATH := $(PWD)/$(BINDIR)/tools/goroot/bin:$(PATH) -GO := $(PWD)/$(BINDIR)/tools/go -endif - -GOBUILD := CGO_ENABLED=$(CGO_ENABLED) GOMAXPROCS=$(GOBUILDPROCS) $(GO) build -GOTEST := CGO_ENABLED=$(CGO_ENABLED) $(GO) test - -# overwrite $(GOTESTSUM) and add CGO_ENABLED variable -GOTESTSUM := CGO_ENABLED=$(CGO_ENABLED) $(GOTESTSUM) - -.PHONY: vendor-go -## By default, this Makefile uses the system's Go. You can use a "vendored" -## version of Go that will get downloaded by running this command once. To -## disable vendoring, run "make unvendor-go". When vendoring is enabled, -## you will want to set the following: -## -## export PATH="$PWD/$(BINDIR)/tools:$PATH" -## export GOROOT="$PWD/$(BINDIR)/tools/goroot" -vendor-go: $(BINDIR)/tools/go - -.PHONY: unvendor-go -unvendor-go: $(BINDIR)/tools/go - rm -rf $(BINDIR)/tools/go $(BINDIR)/tools/goroot - -.PHONY: which-go -## Print the version and path of go which will be used for building and -## testing in Makefile commands. Vendored go will have a path in ./bin -which-go: | $(NEEDS_GO) - @$(GO) version - @echo "go binary used for above version information: $(GO)" - -# The "_" in "_go "prevents "go mod tidy" from trying to tidy the vendored -# goroot. -$(BINDIR)/tools/go: $(BINDIR)/downloaded/tools/_go-$(VENDORED_GO_VERSION)-$(HOST_OS)-$(HOST_ARCH)/goroot/bin/go $(BINDIR)/tools/goroot $(BINDIR)/scratch/VENDORED_GO_VERSION | $(BINDIR)/tools - cd $(dir $@) && $(LN) $(patsubst $(BINDIR)/%,../%,$<) . - @touch $@ - -$(BINDIR)/tools/goroot: $(BINDIR)/downloaded/tools/_go-$(VENDORED_GO_VERSION)-$(HOST_OS)-$(HOST_ARCH)/goroot $(BINDIR)/scratch/VENDORED_GO_VERSION | $(BINDIR)/tools - @rm -rf $(BINDIR)/tools/goroot - cd $(dir $@) && $(LN) $(patsubst $(BINDIR)/%,../%,$<) . - @touch $@ - -$(BINDIR)/downloaded/tools/_go-$(VENDORED_GO_VERSION)-%/goroot $(BINDIR)/downloaded/tools/_go-$(VENDORED_GO_VERSION)-%/goroot/bin/go: $(BINDIR)/downloaded/tools/go-$(VENDORED_GO_VERSION)-%.tar.gz - @mkdir -p $(dir $@) - rm -rf $(BINDIR)/downloaded/tools/_go-$(VENDORED_GO_VERSION)-$*/goroot - tar xzf $< -C $(BINDIR)/downloaded/tools/_go-$(VENDORED_GO_VERSION)-$* - mv $(BINDIR)/downloaded/tools/_go-$(VENDORED_GO_VERSION)-$*/go $(BINDIR)/downloaded/tools/_go-$(VENDORED_GO_VERSION)-$*/goroot - -$(BINDIR)/downloaded/tools/go-$(VENDORED_GO_VERSION)-%.tar.gz: | $(BINDIR)/downloaded/tools - $(CURL) https://go.dev/dl/go$(VENDORED_GO_VERSION).$*.tar.gz -o $@ - -################### -# go dependencies # -################### - -GO_DEPENDENCIES := -GO_DEPENDENCIES += ginkgo=github.com/onsi/ginkgo/v2/ginkgo -GO_DEPENDENCIES += cmrel=github.com/cert-manager/release/cmd/cmrel -GO_DEPENDENCIES += release-notes=k8s.io/release/cmd/release-notes -GO_DEPENDENCIES += controller-gen=sigs.k8s.io/controller-tools/cmd/controller-gen -GO_DEPENDENCIES += goimports=golang.org/x/tools/cmd/goimports -GO_DEPENDENCIES += go-licenses=github.com/google/go-licenses -GO_DEPENDENCIES += gotestsum=gotest.tools/gotestsum -GO_DEPENDENCIES += crane=github.com/google/go-containerregistry/cmd/crane - -define go_dependency -$$(BINDIR)/downloaded/tools/$1@$($(call UC,$1)_VERSION)_%: | $$(NEEDS_GO) $$(BINDIR)/downloaded/tools - GOBIN=$$(PWD)/$$(dir $$@) $$(GO) install $2@$($(call UC,$1)_VERSION) - @mv $$(PWD)/$$(dir $$@)/$1 $$@ -endef - -$(foreach GO_DEPENDENCY,$(GO_DEPENDENCIES),$(eval $(call go_dependency,$(word 1,$(subst =, ,$(GO_DEPENDENCY))),$(word 2,$(subst =, ,$(GO_DEPENDENCY)))))) - -######## -# Helm # -######## - -HELM_linux_amd64_SHA256SUM=bf56beb418bb529b5e0d6d43d56654c5a03f89c98400b409d1013a33d9586474 -HELM_darwin_amd64_SHA256SUM=1e7fd528482ac2ef2d79fe300724b3e07ff6f846a2a9b0b0fe6f5fa05691786b -HELM_darwin_arm64_SHA256SUM=f7f6558ebc8211824032a7fdcf0d55ad064cb33ec1eeec3d18057b9fe2e04dbe - -$(BINDIR)/downloaded/tools/helm@$(HELM_VERSION)_%: | $(BINDIR)/downloaded/tools - $(CURL) https://get.helm.sh/helm-$(HELM_VERSION)-$(subst _,-,$*).tar.gz -o $@.tar.gz - ./hack/util/checkhash.sh $@.tar.gz $(HELM_$*_SHA256SUM) - @# O writes the specified file to stdout - tar xfO $@.tar.gz $(subst _,-,$*)/helm > $@ - chmod +x $@ - rm -f $@.tar.gz - -########### -# kubectl # -########### - -KUBECTL_linux_amd64_SHA256SUM=8639f2b9c33d38910d706171ce3d25be9b19fc139d0e3d4627f38ce84f9040eb -KUBECTL_darwin_amd64_SHA256SUM=b859766d7b47267af5cc1ee01a2d0c3c137dbfc53cd5be066181beed11ec7d34 -KUBECTL_darwin_arm64_SHA256SUM=1c37f9b7c0c92532f52c572476fd26a9349574abae8faf265fd4f8bca25b3d77 - -$(BINDIR)/downloaded/tools/kubectl@$(KUBECTL_VERSION)_%: | $(BINDIR)/downloaded/tools - $(CURL) https://storage.googleapis.com/kubernetes-release/release/$(KUBECTL_VERSION)/bin/$(subst _,/,$*)/kubectl -o $@ - ./hack/util/checkhash.sh $@ $(KUBECTL_$*_SHA256SUM) - chmod +x $@ - -######## -# kind # -######## - -KIND_linux_amd64_SHA256SUM=a9438c56776bde1637ec763f3e450078258b791aaa631b8211b7ed3e4f50d089 -KIND_darwin_amd64_SHA256SUM=9936eafdcc4e34dfa3c9ad0e57162e19575c6581ab28f6780dc434bcb9245ecd -KIND_darwin_arm64_SHA256SUM=3e8ac912f24066f8de8fbaed471b76307484afa8165193ee797b622beba54d0a - -$(BINDIR)/downloaded/tools/kind@$(KIND_VERSION)_%: | $(BINDIR)/downloaded/tools $(BINDIR)/tools - $(CURL) -sSfL https://github.com/kubernetes-sigs/kind/releases/download/$(KIND_VERSION)/kind-$(subst _,-,$*) -o $@ - ./hack/util/checkhash.sh $@ $(KIND_$*_SHA256SUM) - chmod +x $@ - -########## -# cosign # -########## - -COSIGN_linux_amd64_SHA256SUM=b30fdc7d9aab246bc2f6a760ed8eff063bd37935389302c963c07018e5d48a12 -COSIGN_darwin_amd64_SHA256SUM=87a7e93b1539d988fefe0d00fd5a5a0e02ef43f5f977c2a701170c502a17980d -COSIGN_darwin_arm64_SHA256SUM=41bc69dae9f06f58e8e61446907b7e53a4db41ef341b235172d3745c937f1777 - -# TODO: cosign also provides signatures on all of its binaries, but they can't be validated without already having cosign -# available! We could do something like "if system cosign is available, verify using that", but for now we'll skip -$(BINDIR)/downloaded/tools/cosign@$(COSIGN_VERSION)_%: | $(BINDIR)/downloaded/tools - $(CURL) https://github.com/sigstore/cosign/releases/download/$(COSIGN_VERSION)/cosign-$(subst _,-,$*) -o $@ - ./hack/util/checkhash.sh $@ $(COSIGN_$*_SHA256SUM) - chmod +x $@ - -########## -# rclone # -########## - -RCLONE_linux_amd64_SHA256SUM=81e7be456369f5957713463e3624023e9159c1cae756e807937046ebc9394383 -RCLONE_darwin_amd64_SHA256SUM=d0a70241212198566028cd3154c418e35cbe73a6cd22c2d851341e88cb650cb7 -RCLONE_darwin_arm64_SHA256SUM=8b98893fa34aa790ae23dd2417e8c9a200326c05feb26101dff09cda479aeb1f - -$(BINDIR)/downloaded/tools/rclone@$(RCLONE_VERSION)_%: | $(BINDIR)/downloaded/tools - $(eval OS_AND_ARCH := $(subst darwin,osx,$*)) - $(CURL) https://github.com/rclone/rclone/releases/download/$(RCLONE_VERSION)/rclone-$(RCLONE_VERSION)-$(subst _,-,$(OS_AND_ARCH)).zip -o $@.zip - ./hack/util/checkhash.sh $@.zip $(RCLONE_$*_SHA256SUM) - @# -p writes to stdout, the second file arg specifies the sole file we - @# want to extract - unzip -p $@.zip rclone-$(RCLONE_VERSION)-$(subst _,-,$(OS_AND_ARCH))/rclone > $@ - chmod +x $@ - rm -f $@.zip - -######### -# trivy # -######### - -TRIVY_linux_amd64_SHA256SUM=e6e1c4767881ab1e40da5f3bb499b1c9176892021c7cb209405078fc096d94d8 -TRIVY_darwin_amd64_SHA256SUM=1cc8b2301f696b71c488d99c917a21a191ab26e1c093287c20112e8bb517ac4c -TRIVY_darwin_arm64_SHA256SUM=41a3d4c12cd227cf95db6b30144b85e571541f587837f2f3814e2339dd81a21a - -$(BINDIR)/downloaded/tools/trivy@$(TRIVY_VERSION)_%: | $(BINDIR)/downloaded/tools - $(eval OS_AND_ARCH := $(subst darwin,macOS,$*)) - $(eval OS_AND_ARCH := $(subst linux,Linux,$(OS_AND_ARCH))) - $(eval OS_AND_ARCH := $(subst arm64,ARM64,$(OS_AND_ARCH))) - $(eval OS_AND_ARCH := $(subst amd64,64bit,$(OS_AND_ARCH))) - - $(CURL) https://github.com/aquasecurity/trivy/releases/download/$(TRIVY_VERSION)/trivy_$(patsubst v%,%,$(TRIVY_VERSION))_$(subst _,-,$(OS_AND_ARCH)).tar.gz -o $@.tar.gz - ./hack/util/checkhash.sh $@.tar.gz $(TRIVY_$*_SHA256SUM) - tar xfO $@.tar.gz trivy > $@ - chmod +x $@ - rm $@.tar.gz - -####### -# ytt # -####### - -YTT_linux_amd64_SHA256SUM=29e647beeacbcc2be5f2f481e405c73bcd6d7563bd229ff924a7997b6f2edd5f -YTT_darwin_amd64_SHA256SUM=579012ac80cc0d55c3a6dde2dfc0ff5bf8a4f74c775295be99faf691cc18595e -YTT_darwin_arm64_SHA256SUM=bd8781e76e833c848ecc80580b3588b4ce8f38d8697802ec83c07aae7cf7a66f - -$(BINDIR)/downloaded/tools/ytt@$(YTT_VERSION)_%: | $(BINDIR)/downloaded/tools - $(CURL) -sSfL https://github.com/vmware-tanzu/carvel-ytt/releases/download/$(YTT_VERSION)/ytt-$(subst _,-,$*) -o $@ - ./hack/util/checkhash.sh $@ $(YTT_$*_SHA256SUM) - chmod +x $@ - -###### -# yq # -###### - -YQ_linux_amd64_SHA256SUM=9a54846e81720ae22814941905cd3b056ebdffb76bf09acffa30f5e90b22d615 -YQ_darwin_amd64_SHA256SUM=79a55533b683c5eabdc35b00336aa4c107d7d719db0639a31892fc35d1436cdc -YQ_darwin_arm64_SHA256SUM=40547a5049f15a1103268fd871baaa34a31ad30136ee27a829cf697737f392be - -$(BINDIR)/downloaded/tools/yq@$(YQ_VERSION)_%: | $(BINDIR)/downloaded/tools - $(CURL) https://github.com/mikefarah/yq/releases/download/$(YQ_VERSION)/yq_$* -o $@ - ./hack/util/checkhash.sh $@ $(YQ_$*_SHA256SUM) - chmod +x $@ - -##################### -# k8s codegen tools # -##################### - -K8S_CODEGEN_TOOLS := client-gen conversion-gen deepcopy-gen defaulter-gen informer-gen lister-gen -K8S_CODEGEN_TOOLS_PATHS := $(K8S_CODEGEN_TOOLS:%=$(BINDIR)/tools/%) -K8S_CODEGEN_TOOLS_DOWNLOADS := $(K8S_CODEGEN_TOOLS:%=$(BINDIR)/downloaded/tools/%@$(K8S_CODEGEN_VERSION)) - -.PHONY: k8s-codegen-tools -k8s-codegen-tools: $(K8S_CODEGEN_TOOLS_PATHS) - -$(K8S_CODEGEN_TOOLS_PATHS): $(BINDIR)/tools/%-gen: $(BINDIR)/scratch/K8S_CODEGEN_VERSION | $(BINDIR)/downloaded/tools/%-gen@$(K8S_CODEGEN_VERSION) $(BINDIR)/tools - cd $(dir $@) && $(LN) $(patsubst $(BINDIR)/%,../%,$(word 1,$|)) $(notdir $@) - -$(K8S_CODEGEN_TOOLS_DOWNLOADS): $(BINDIR)/downloaded/tools/%-gen@$(K8S_CODEGEN_VERSION): $(NEEDS_GO) | $(BINDIR)/downloaded/tools - GOBIN=$(PWD)/$(dir $@) $(GO) install k8s.io/code-generator/cmd/$(notdir $@) - @mv $(subst @$(K8S_CODEGEN_VERSION),,$@) $@ - -############################ -# kubebuilder-tools assets # -# kube-apiserver / etcd # -# The SHAs for the same version of kubebuilder tools can change as new versions are published for changes merged to https://github.com/kubernetes-sigs/kubebuilder/tree/tools-releases # -# You can use ./hack/latest-kubebuilder-shas.sh to get latest SHAs for a particular version of kubebuilder tools # -############################ - -KUBEBUILDER_TOOLS_linux_amd64_SHA256SUM=c9796a0a13ccb79b77e3d64b8d3bb85a14fc850800724c63b85bf5bacbe0b4ba -KUBEBUILDER_TOOLS_darwin_amd64_SHA256SUM=a232faf4551ffb1185660c5a2eb9eaaf7eb02136fa71e7ead84ee940a205d9bf -KUBEBUILDER_TOOLS_darwin_arm64_SHA256SUM=9a8c8526965f46256ff947303342e73499217df5c53680a03ac950d331191ffc - -$(BINDIR)/downloaded/tools/etcd@$(KUBEBUILDER_ASSETS_VERSION)_%: $(BINDIR)/downloaded/tools/kubebuilder_tools_$(KUBEBUILDER_ASSETS_VERSION)_%.tar.gz | $(BINDIR)/downloaded/tools - ./hack/util/checkhash.sh $< $(KUBEBUILDER_TOOLS_$*_SHA256SUM) - @# O writes the specified file to stdout - tar xfO $< kubebuilder/bin/etcd > $@ && chmod 775 $@ - -$(BINDIR)/downloaded/tools/kube-apiserver@$(KUBEBUILDER_ASSETS_VERSION)_%: $(BINDIR)/downloaded/tools/kubebuilder_tools_$(KUBEBUILDER_ASSETS_VERSION)_%.tar.gz | $(BINDIR)/downloaded/tools - ./hack/util/checkhash.sh $< $(KUBEBUILDER_TOOLS_$*_SHA256SUM) - @# O writes the specified file to stdout - tar xfO $< kubebuilder/bin/kube-apiserver > $@ && chmod 775 $@ - -$(BINDIR)/downloaded/tools/kubebuilder_tools_$(KUBEBUILDER_ASSETS_VERSION)_$(HOST_OS)_$(HOST_ARCH).tar.gz: | $(BINDIR)/downloaded/tools - $(CURL) https://storage.googleapis.com/kubebuilder-tools/kubebuilder-tools-$(KUBEBUILDER_ASSETS_VERSION)-$(HOST_OS)-$(HOST_ARCH).tar.gz -o $@ - -############## -# gatewayapi # -############## - -GATEWAY_API_SHA256SUM=c45f8806883014f7f75a2084c612fc62eb00d5c1915a906f8ca5ecda5450b163 - -$(BINDIR)/downloaded/gateway-api@$(GATEWAY_API_VERSION): $(BINDIR)/downloaded/gateway-api@$(GATEWAY_API_VERSION).tar.gz | $(BINDIR)/downloaded - ./hack/util/checkhash.sh $< $(GATEWAY_API_SHA256SUM) - @mkdir -p $@ - tar xz -C $@ -f $< - -$(BINDIR)/downloaded/gateway-api@$(GATEWAY_API_VERSION).tar.gz: | $(BINDIR)/downloaded - $(CURL) https://github.com/kubernetes-sigs/gateway-api/archive/refs/tags/$(GATEWAY_API_VERSION).tar.gz -o $@ - -################# -# Other Targets # -################# - -$(BINDIR) $(BINDIR)/tools $(BINDIR)/downloaded $(BINDIR)/downloaded/tools: - @mkdir -p $@ - -# Although we "vendor" most tools in $(BINDIR)/tools, we still require some binaries -# to be available on the system. The vendor-go MAKECMDGOALS trick prevents the -# check for the presence of Go when 'make vendor-go' is run. - -# Gotcha warning: MAKECMDGOALS only contains what the _top level_ make invocation used, and doesn't look at target dependencies -# i.e. if we have a target "abc: vendor-go test" and run "make abc", we'll get an error -# about go being missing even though abc itself depends on vendor-go! -# That means we need to pass vendor-go at the top level if go is not installed (i.e. "make vendor-go abc") - -MISSING=$(shell (command -v curl >/dev/null || echo curl) \ - && (command -v jq >/dev/null || echo jq) \ - && (command -v sha256sum >/dev/null || echo sha256sum) \ - && (command -v git >/dev/null || echo git) \ - && ([ -n "$(findstring vendor-go,$(MAKECMDGOALS),)" ] \ - || command -v $(GO) >/dev/null || echo "$(GO) (or run 'make vendor-go')") \ - && (command -v $(CTR) >/dev/null || echo "$(CTR) (or set CTR to a docker-compatible tool)")) -ifneq ($(MISSING),) -$(error Missing required tools: $(MISSING)) -endif - -.PHONY: tools -tools: $(TOOLS_PATHS) $(K8S_CODEGEN_TOOLS_PATHS) ## install all tools - -.PHONY: update-kind-images -update-kind-images: $(BINDIR)/tools/crane - CRANE=./$(BINDIR)/tools/crane ./hack/latest-kind-images.sh diff --git a/make/util.mk b/make/util.mk index d8d02739546..a33003fb6dc 100644 --- a/make/util.mk +++ b/make/util.mk @@ -1,15 +1,29 @@ +# 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. + # Utility helper function to "get every go file in all except binary / make dirs" # The first argument $(1) defines the commands which the find output are piped into # Note that the "-not \( ... -prune \)" syntax is important here, if a little trickier to # understand. It causes find to prune entire search branches and not search inside the path. # If we used "-not -path X" instead, find would _still look inside X_. define get-sources -$(shell find . -not \( -path "./$(BINDIR)/*" -prune \) -not \( -path "./bin/*" -prune \) -not \( -path "./make/*" -prune \) -name "*.go" | $(1)) +$(shell find . -not \( -path "./$(bin_dir)/*" -prune \) -not \( -path "./bin/*" -prune \) -not \( -path "./make/*" -prune \) -name "*.go" | $(1)) endef .PHONY: print-bindir print-bindir: - @echo $(BINDIR) + @echo $(bin_dir) .PHONY: print-sources print-sources: @@ -17,4 +31,4 @@ print-sources: .PHONY: print-source-dirs print-source-dirs: - @echo $(call get-sources,cut -d'/' -f2 | sort | uniq | tr '\n' ' ') + @echo $(SOURCE_DIRS) diff --git a/pkg/acme/accounts/client.go b/pkg/acme/accounts/client.go index b11f87b048e..57f1498cfa2 100644 --- a/pkg/acme/accounts/client.go +++ b/pkg/acme/accounts/client.go @@ -19,51 +19,79 @@ package accounts import ( "crypto/rsa" "crypto/tls" + "crypto/x509" "net" "net/http" "time" - acmeapi "golang.org/x/crypto/acme" - acmecl "github.com/cert-manager/cert-manager/pkg/acme/client" "github.com/cert-manager/cert-manager/pkg/acme/client/middleware" 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" + acmeapi "github.com/cert-manager/cert-manager/third_party/forked/acme" ) const ( // defaultACMEHTTPTimeout sets the default maximum time that an individual HTTP request can take when doing ACME operations. - // Note that there may be other timeouts - e.g. dial timeouts or TLS handshake timeouts - which will be smaller than this. This + // Note that there may be other timeouts - e.g., dial timeouts or TLS handshake timeouts - which will be smaller than this. This // timeout is the overall timeout for the entire request. defaultACMEHTTPTimeout = time.Second * 90 ) -// NewClientFunc is a function type for building a new ACME client. -type NewClientFunc func(*http.Client, cmacme.ACMEIssuer, *rsa.PrivateKey, string) acmecl.Interface +type NewClientOptions struct { + SkipTLSVerify bool + CABundle []byte + Server string + PrivateKey *rsa.PrivateKey +} -var _ NewClientFunc = NewClient +// NewClientFunc is a function type for building a new ACME client. +type NewClientFunc func(options NewClientOptions) acmecl.Interface // NewClient is an implementation of NewClientFunc that returns a real ACME client. -func NewClient(client *http.Client, config cmacme.ACMEIssuer, privateKey *rsa.PrivateKey, userAgent string) acmecl.Interface { +func NewClient( + metrics *metrics.Metrics, + userAgent string, +) NewClientFunc { + return func(options NewClientOptions) acmecl.Interface { + httpClient := buildHTTPClientWithCABundle(metrics, options.SkipTLSVerify, options.CABundle) + return newClientFromHTTPClient(httpClient, userAgent, options) + } +} + +func newClientFromHTTPClient(httpClient *http.Client, userAgent string, options NewClientOptions) acmecl.Interface { return middleware.NewLogger(&acmeapi.Client{ - Key: privateKey, - HTTPClient: client, - DirectoryURL: config.Server, + Key: options.PrivateKey, + HTTPClient: httpClient, + DirectoryURL: options.Server, UserAgent: userAgent, RetryBackoff: acmeutil.RetryBackoff, }) } -// BuildHTTPClient returns a instrumented HTTP client to be used by the ACME -// client. -// For the time being, we construct a new HTTP client on each invocation. -// This is because we need to set the 'skipTLSVerify' flag on the HTTP client -// itself. -// In future, we may change to having two global HTTP clients - one that ignores -// TLS connection errors, and the other that does not. -func BuildHTTPClient(metrics *metrics.Metrics, skipTLSVerify bool) *http.Client { - return acmecl.NewInstrumentedClient(metrics, +// buildHTTPClientWithCABundle returns an instrumented HTTP client to be used by an ACME +// client, with an optional custom CA bundle set. +// For the time being, we construct a new HTTP client on each invocation, because we need +// to set the 'skipTLSVerify' flag and the CA bundle on the HTTP client itself, distinct +// from the ACME client +func buildHTTPClientWithCABundle(metrics *metrics.Metrics, skipTLSVerify bool, caBundle []byte) *http.Client { + tlsConfig := &tls.Config{ + InsecureSkipVerify: skipTLSVerify, // #nosec G402 -- false positive + } + + // len also checks if the bundle is nil + if len(caBundle) > 0 { + pool := x509.NewCertPool() + + // We only want tlsConfig.RootCAs to be non-nil if we added at least one custom + // CA to "pool". + if ok := pool.AppendCertsFromPEM(caBundle); ok { + tlsConfig.RootCAs = pool + } + } + + return acmecl.NewInstrumentedClient( + metrics, &http.Client{ Transport: &http.Transport{ Proxy: http.ProxyFromEnvironment, @@ -71,12 +99,13 @@ func BuildHTTPClient(metrics *metrics.Metrics, skipTLSVerify bool) *http.Client Timeout: 30 * time.Second, KeepAlive: 30 * time.Second, }).DialContext, - TLSClientConfig: &tls.Config{InsecureSkipVerify: skipTLSVerify}, + TLSClientConfig: tlsConfig, MaxIdleConns: 100, IdleConnTimeout: 90 * time.Second, TLSHandshakeTimeout: 10 * time.Second, ExpectContinueTimeout: 1 * time.Second, }, Timeout: defaultACMEHTTPTimeout, - }) + }, + ) } diff --git a/pkg/acme/accounts/registry.go b/pkg/acme/accounts/registry.go index 44dfa5471ce..6a8ecf384e1 100644 --- a/pkg/acme/accounts/registry.go +++ b/pkg/acme/accounts/registry.go @@ -18,12 +18,13 @@ package accounts import ( "crypto/rsa" + "crypto/sha256" + "crypto/x509" + "encoding/base64" "errors" - "net/http" "sync" acmecl "github.com/cert-manager/cert-manager/pkg/acme/client" - cmacme "github.com/cert-manager/cert-manager/pkg/apis/acme/v1" ) // ErrNotFound is returned by GetClient if there is no ACME client registered. @@ -35,12 +36,16 @@ var ErrNotFound = errors.New("ACME client for issuer not initialised/available") type Registry interface { // AddClient will ensure the registry has a stored ACME client for the Issuer // object with the given UID, configuration and private key. - AddClient(client *http.Client, uid string, config cmacme.ACMEIssuer, privateKey *rsa.PrivateKey, userAgent string) + AddClient(uid string, options NewClientOptions) // RemoveClient will remove a registered client using the UID of the Issuer // resource that constructed it. RemoveClient(uid string) + // IsKeyCheckSumCached checks if the private key checksum is cached with registered client. + // If not cached, the account is re-verified for the private key. + IsKeyCheckSumCached(lastPrivateKeyHash string, privateKey *rsa.PrivateKey) bool + Getter } @@ -53,15 +58,16 @@ type Getter interface { // ListClients will return a full list of all ACME clients by their UIDs. // This can be used to enumerate all registered clients and call RemoveClient - // on any clients that should no longer be registered, e.g. because their + // on any clients that should no longer be registered, e.g., because their // corresponding Issuer resource has been deleted. ListClients() map[string]acmecl.Interface } // NewDefaultRegistry returns a new default instantiation of a client registry. -func NewDefaultRegistry() Registry { +func NewDefaultRegistry(newClientFunc NewClientFunc) Registry { return ®istry{ - clients: make(map[string]clientWithMeta), + newClientFunc: newClientFunc, + clients: make(map[string]clientWithMeta), } } @@ -69,6 +75,8 @@ func NewDefaultRegistry() Registry { type registry struct { lock sync.RWMutex + newClientFunc NewClientFunc + // a map of an issuer's 'uid' to an ACME client with metadata clients map[string]clientWithMeta } @@ -82,21 +90,27 @@ type stableOptions struct { issuerUID string publicKey string exponent int + caBundle string + keyChecksum [sha256.Size]byte } func (c stableOptions) equalTo(c2 stableOptions) bool { return c == c2 } -func newStableOptions(uid string, config cmacme.ACMEIssuer, privateKey *rsa.PrivateKey) stableOptions { +func newStableOptions(uid string, options NewClientOptions) stableOptions { // Encoding a big.Int cannot fail - publicNBytes, _ := privateKey.PublicKey.N.GobEncode() + publicNBytes, _ := options.PrivateKey.PublicKey.N.GobEncode() + checksum := sha256.Sum256(x509.MarshalPKCS1PrivateKey(options.PrivateKey)) + return stableOptions{ - serverURL: config.Server, - skipVerifyTLS: config.SkipTLSVerify, + serverURL: options.Server, + skipVerifyTLS: options.SkipTLSVerify, issuerUID: uid, publicKey: string(publicNBytes), - exponent: privateKey.PublicKey.E, + exponent: options.PrivateKey.PublicKey.E, + caBundle: string(options.CABundle), + keyChecksum: checksum, } } @@ -110,9 +124,9 @@ type clientWithMeta struct { // AddClient will ensure the registry has a stored ACME client for the Issuer // object with the given UID, configuration and private key. -func (r *registry) AddClient(client *http.Client, uid string, config cmacme.ACMEIssuer, privateKey *rsa.PrivateKey, userAgent string) { +func (r *registry) AddClient(uid string, options NewClientOptions) { // ensure the client is up to date for the current configuration - r.ensureClient(client, uid, config, privateKey, userAgent) + r.ensureClient(uid, options) } // ensureClient will ensure an ACME client with the given parameters is registered. @@ -120,21 +134,23 @@ func (r *registry) AddClient(client *http.Client, uid string, config cmacme.ACME // the client will NOT be mutated or replaced, allowing this method to be called // even if the client does not need replacing/updating without causing issues for // consumers of the registry. -func (r *registry) ensureClient(client *http.Client, uid string, config cmacme.ACMEIssuer, privateKey *rsa.PrivateKey, userAgent string) { +func (r *registry) ensureClient(uid string, options NewClientOptions) { // acquire a read-write lock even if we hit the fast-path where the client // is already present to avoid having to RLock, RUnlock and Lock again, // which could itself cause a race r.lock.Lock() defer r.lock.Unlock() - newOpts := newStableOptions(uid, config, privateKey) + + newOpts := newStableOptions(uid, options) // fast-path if there is nothing to do if meta, ok := r.clients[uid]; ok && meta.equalTo(newOpts) { return } + // create a new client if one is not registered or if the // 'metadata' does not match r.clients[uid] = clientWithMeta{ - Interface: NewClient(client, config, privateKey, userAgent), + Interface: r.newClientFunc(options), stableOptions: newOpts, } } @@ -165,7 +181,7 @@ func (r *registry) RemoveClient(uid string) { // ListClients will return a full list of all ACME clients by their UIDs. // This can be used to enumerate all registered clients and call RemoveClient -// on any clients that should no longer be registered, e.g. because their +// on any clients that should no longer be registered, e.g., because their // corresponding Issuer resource has been deleted. func (r *registry) ListClients() map[string]acmecl.Interface { r.lock.RLock() @@ -177,3 +193,26 @@ func (r *registry) ListClients() map[string]acmecl.Interface { } return out } + +// IsKeyCheckSumCached returns true when there is no difference in private key checksum. +// This can be used to identify if the private key has changed for the existing +// registered client. +func (r *registry) IsKeyCheckSumCached(lastPrivateKeyHash string, privateKey *rsa.PrivateKey) bool { + r.lock.RLock() + defer r.lock.RUnlock() + + if privateKey != nil && lastPrivateKeyHash != "" { + privateKeyBytes := x509.MarshalPKCS1PrivateKey(privateKey) + checksum := sha256.Sum256(privateKeyBytes) + checksumString := base64.StdEncoding.EncodeToString(checksum[:]) + + if lastPrivateKeyHash == checksumString { + return true + } + + } + + // Either there is no entry found in client cache for uid + // or private key checksum does not match with cached entry + return false +} diff --git a/pkg/acme/accounts/registry_test.go b/pkg/acme/accounts/registry_test.go index 5590d10b12b..3516ee64e50 100644 --- a/pkg/acme/accounts/registry_test.go +++ b/pkg/acme/accounts/registry_test.go @@ -17,22 +17,29 @@ limitations under the License. package accounts import ( + "crypto/sha256" + "crypto/x509" + "encoding/base64" "net/http" "testing" - cmacme "github.com/cert-manager/cert-manager/pkg/apis/acme/v1" + "github.com/cert-manager/cert-manager/pkg/acme/client" "github.com/cert-manager/cert-manager/pkg/util/pki" ) func TestRegistry_AddClient(t *testing.T) { - r := NewDefaultRegistry() + r := NewDefaultRegistry(func(options NewClientOptions) client.Interface { + return newClientFromHTTPClient(http.DefaultClient, "cert-manager-test", options) + }) pk, err := pki.GenerateRSAPrivateKey(2048) if err != nil { t.Fatal(err) } // Register a new client - r.AddClient(http.DefaultClient, "abc", cmacme.ACMEIssuer{}, pk, "cert-manager-test") + r.AddClient("abc", NewClientOptions{ + PrivateKey: pk, + }) c, err := r.GetClient("abc") if err != nil { @@ -44,14 +51,18 @@ func TestRegistry_AddClient(t *testing.T) { } func TestRegistry_RemoveClient(t *testing.T) { - r := NewDefaultRegistry() + r := NewDefaultRegistry(func(options NewClientOptions) client.Interface { + return newClientFromHTTPClient(http.DefaultClient, "cert-manager-test", options) + }) pk, err := pki.GenerateRSAPrivateKey(2048) if err != nil { t.Fatal(err) } // Register a new client - r.AddClient(http.DefaultClient, "abc", cmacme.ACMEIssuer{}, pk, "cert-manager-test") + r.AddClient("abc", NewClientOptions{ + PrivateKey: pk, + }) c, err := r.GetClient("abc") if err != nil { @@ -72,7 +83,7 @@ func TestRegistry_RemoveClient(t *testing.T) { } func TestRegistry_RemoveClient_EmptyRegistry(t *testing.T) { - r := NewDefaultRegistry() + r := NewDefaultRegistry(nil) r.RemoveClient("abc") c, err := r.GetClient("abc") if err != ErrNotFound { @@ -84,21 +95,27 @@ func TestRegistry_RemoveClient_EmptyRegistry(t *testing.T) { } func TestRegistry_ListClients(t *testing.T) { - r := NewDefaultRegistry() + r := NewDefaultRegistry(func(options NewClientOptions) client.Interface { + return newClientFromHTTPClient(http.DefaultClient, "cert-manager-test", options) + }) pk, err := pki.GenerateRSAPrivateKey(2048) if err != nil { t.Fatal(err) } // Register a new client - r.AddClient(http.DefaultClient, "abc", cmacme.ACMEIssuer{}, pk, "cert-manager-test") + r.AddClient("abc", NewClientOptions{ + PrivateKey: pk, + }) l := r.ListClients() if len(l) != 1 { t.Errorf("expected ListClients to have 1 item but it has %d", len(l)) } // Register a second client - r.AddClient(http.DefaultClient, "abc2", cmacme.ACMEIssuer{}, pk, "cert-manager-test") + r.AddClient("abc2", NewClientOptions{ + PrivateKey: pk, + }) l = r.ListClients() if len(l) != 2 { t.Errorf("expected ListClients to have 2 items but it has %d", len(l)) @@ -106,14 +123,19 @@ func TestRegistry_ListClients(t *testing.T) { // Register a third client with the same options as the second, meaning // it should be de-duplicated - r.AddClient(http.DefaultClient, "abc2", cmacme.ACMEIssuer{}, pk, "cert-manager-test") + r.AddClient("abc2", NewClientOptions{ + PrivateKey: pk, + }) l = r.ListClients() if len(l) != 2 { t.Errorf("expected ListClients to have 2 items but it has %d", len(l)) } // Update the second client with a new server URL - r.AddClient(http.DefaultClient, "abc2", cmacme.ACMEIssuer{Server: "abc.com"}, pk, "cert-manager-test") + r.AddClient("abc2", NewClientOptions{ + Server: "abc.com", + PrivateKey: pk, + }) l = r.ListClients() if len(l) != 2 { t.Errorf("expected ListClients to have 2 items but it has %d", len(l)) @@ -121,7 +143,9 @@ func TestRegistry_ListClients(t *testing.T) { } func TestRegistry_AddClient_UpdatesExistingWhenPrivateKeyChanges(t *testing.T) { - r := NewDefaultRegistry() + r := NewDefaultRegistry(func(options NewClientOptions) client.Interface { + return newClientFromHTTPClient(http.DefaultClient, "cert-manager-test", options) + }) pk, err := pki.GenerateRSAPrivateKey(2048) if err != nil { t.Fatal(err) @@ -132,16 +156,57 @@ func TestRegistry_AddClient_UpdatesExistingWhenPrivateKeyChanges(t *testing.T) { } // Register a new client - r.AddClient(http.DefaultClient, "abc", cmacme.ACMEIssuer{}, pk, "cert-manager-test") + r.AddClient("abc", NewClientOptions{ + PrivateKey: pk, + }) l := r.ListClients() if len(l) != 1 { t.Errorf("expected ListClients to have 1 item but it has %d", len(l)) } // Update the client with a new private key - r.AddClient(http.DefaultClient, "abc", cmacme.ACMEIssuer{}, pk2, "cert-manager-test") + r.AddClient("abc", NewClientOptions{ + PrivateKey: pk2, + }) l = r.ListClients() if len(l) != 1 { t.Errorf("expected ListClients to have 1 item but it has %d", len(l)) } } + +func TestRegistry_AddClient_UpdatesClientPKChecksum(t *testing.T) { + r := NewDefaultRegistry(func(options NewClientOptions) client.Interface { + return newClientFromHTTPClient(http.DefaultClient, "cert-manager-test", options) + }) + pk, err := pki.GenerateRSAPrivateKey(2048) + if err != nil { + t.Fatal(err) + } + pk2, err := pki.GenerateRSAPrivateKey(2048) + if err != nil { + t.Fatal(err) + } + + pkBytes := x509.MarshalPKCS1PrivateKey(pk) + pkChecksum := sha256.Sum256(pkBytes) + pkChecksumString := base64.StdEncoding.EncodeToString(pkChecksum[:]) + + // Register a new client + r.AddClient("abc", NewClientOptions{ + PrivateKey: pk, + }) + l := r.ListClients() + if len(l) != 1 { + t.Errorf("expected ListClients to have 1 item but it has %d", len(l)) + } + + isCached := r.IsKeyCheckSumCached(pkChecksumString, pk) + if isCached == false { + t.Fatal("checksum failed for same key") + } + + isCached = r.IsKeyCheckSumCached(pkChecksumString, pk2) + if isCached == true { + t.Fatal("checksum reported same for different keys") + } +} diff --git a/pkg/acme/accounts/test/registry.go b/pkg/acme/accounts/test/registry.go index db40f492d82..9dc22d98c90 100644 --- a/pkg/acme/accounts/test/registry.go +++ b/pkg/acme/accounts/test/registry.go @@ -18,26 +18,24 @@ package test import ( "crypto/rsa" - "net/http" - - acmecl "github.com/cert-manager/cert-manager/pkg/acme/client" - cmacme "github.com/cert-manager/cert-manager/pkg/apis/acme/v1" "github.com/cert-manager/cert-manager/pkg/acme/accounts" + acmecl "github.com/cert-manager/cert-manager/pkg/acme/client" ) var _ accounts.Registry = &FakeRegistry{} // FakeRegistry implements the accounts.Registry interface using stub functions type FakeRegistry struct { - AddClientFunc func(uid string, config cmacme.ACMEIssuer, privateKey *rsa.PrivateKey, userAgent string) - RemoveClientFunc func(uid string) - GetClientFunc func(uid string) (acmecl.Interface, error) - ListClientsFunc func() map[string]acmecl.Interface + AddClientFunc func(uid string, options accounts.NewClientOptions) + RemoveClientFunc func(uid string) + GetClientFunc func(uid string) (acmecl.Interface, error) + ListClientsFunc func() map[string]acmecl.Interface + IsKeyCheckSumCachedFunc func(lastPrivateKeyHash string, privateKey *rsa.PrivateKey) bool } -func (f *FakeRegistry) AddClient(client *http.Client, uid string, config cmacme.ACMEIssuer, privateKey *rsa.PrivateKey, userAgent string) { - f.AddClientFunc(uid, config, privateKey, userAgent) +func (f *FakeRegistry) AddClient(uid string, options accounts.NewClientOptions) { + f.AddClientFunc(uid, options) } func (f *FakeRegistry) RemoveClient(uid string) { @@ -51,3 +49,7 @@ func (f *FakeRegistry) GetClient(uid string) (acmecl.Interface, error) { func (f *FakeRegistry) ListClients() map[string]acmecl.Interface { return f.ListClientsFunc() } + +func (f *FakeRegistry) IsKeyCheckSumCached(lastPrivateKeyHash string, privateKey *rsa.PrivateKey) bool { + return f.IsKeyCheckSumCachedFunc(lastPrivateKeyHash, privateKey) +} diff --git a/pkg/acme/client/fake.go b/pkg/acme/client/fake.go index a989bfca020..f5cb263f23a 100644 --- a/pkg/acme/client/fake.go +++ b/pkg/acme/client/fake.go @@ -20,7 +20,7 @@ import ( "context" "fmt" - "golang.org/x/crypto/acme" + "github.com/cert-manager/cert-manager/third_party/forked/acme" ) // TODO: expand this out one day to be backed by the pebble wfe package diff --git a/pkg/acme/client/http.go b/pkg/acme/client/http.go index 8a67ae01d03..0e02d017d64 100644 --- a/pkg/acme/client/http.go +++ b/pkg/acme/client/http.go @@ -19,7 +19,6 @@ package client import ( "fmt" "net/http" - "strings" "time" "github.com/cert-manager/cert-manager/pkg/metrics" @@ -32,6 +31,13 @@ import ( // calls made to the ACME server caused by retries in the underlying ACME // library. +// MetricsContextKey is the type used for context keys in the metrics package. +// Using a custom type prevents key collisions with other packages. +type MetricsContextKey string + +// AcmeActionLabel is the context key for storing the logical ACME operation name. +const AcmeActionLabel = MetricsContextKey("acme_action") + // Transport is a http.RoundTripper that collects Prometheus metrics of every // request it processes. It allows to be configured with callbacks that process // request path and query into a suitable label value. @@ -74,11 +80,17 @@ func (it *Transport) RoundTrip(req *http.Request) (*http.Response, error) { if resp != nil { statusCode = resp.StatusCode } - + var action string + if op, ok := req.Context().Value(AcmeActionLabel).(string); ok { + action = op + } else { + // Fallback for any requests where the context was not set. + action = "unnamed_action" + } labels := []string{ req.URL.Scheme, req.URL.Host, - pathProcessor(req.URL.Path), + action, req.Method, fmt.Sprintf("%d", statusCode), } @@ -89,14 +101,3 @@ func (it *Transport) RoundTrip(req *http.Request) (*http.Response, error) { // return the response and error reported from the next RoundTripper. return resp, err } - -// pathProcessor will trim the provided path to only include the first 2 -// segments in order to reduce the number of prometheus labels generated -func pathProcessor(path string) string { - p := strings.Split(path, "/") - // only record the first two path segments as a prometheus label value - if len(p) > 3 { - p = p[:3] - } - return strings.Join(p, "/") -} diff --git a/pkg/acme/client/http_test.go b/pkg/acme/client/http_test.go new file mode 100644 index 00000000000..2e3696d144f --- /dev/null +++ b/pkg/acme/client/http_test.go @@ -0,0 +1,138 @@ +/* +Copyright 2024 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. +*/ + +package client + +import ( + "context" + "fmt" + "net/http" + "net/http/httptest" + "net/url" + "strings" + "testing" + "time" + + "github.com/go-logr/logr/testr" + "github.com/prometheus/client_golang/prometheus/testutil" + fakeclock "k8s.io/utils/clock/testing" + + metricspkg "github.com/cert-manager/cert-manager/pkg/metrics" +) + +func TestInstrumentedRoundTripper_LabelsAndAccumulation(t *testing.T) { + testCases := []struct { + name string + ctx context.Context + method string + statusToReturn int + useTLS bool + requestsToMake int + expectedAction string + }{ + { + name: "GET 200 OK with action", + ctx: context.WithValue(context.Background(), AcmeActionLabel, "get_directory"), + method: "GET", + statusToReturn: http.StatusOK, + useTLS: false, + requestsToMake: 1, + expectedAction: "get_directory", + }, + { + name: "POST 500 Error without action", + ctx: context.Background(), + method: "POST", + statusToReturn: http.StatusInternalServerError, + useTLS: false, + requestsToMake: 1, + expectedAction: "unnamed_action", + }, + { + name: "GET 200 OK over HTTPS", + ctx: context.WithValue(context.Background(), AcmeActionLabel, "get_cert"), + method: "GET", + statusToReturn: http.StatusOK, + useTLS: true, + requestsToMake: 1, + expectedAction: "get_cert", + }, + { + name: "Multiple requests accumulate", + ctx: context.WithValue(context.Background(), AcmeActionLabel, "finalize_order"), + method: "POST", + statusToReturn: http.StatusOK, + useTLS: false, + requestsToMake: 3, + expectedAction: "finalize_order", + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + fixedClock := fakeclock.NewFakeClock(time.Now()) + metrics := metricspkg.New(testr.New(t), fixedClock) + + handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(tc.statusToReturn) + }) + + var server *httptest.Server + var scheme string + if tc.useTLS { + server = httptest.NewTLSServer(handler) + scheme = "https" + } else { + server = httptest.NewServer(handler) + scheme = "http" + } + defer server.Close() + + httpClient := server.Client() + instrumentedTransport := &Transport{ + wrappedRT: httpClient.Transport, + metrics: metrics, + } + httpClient.Transport = instrumentedTransport + + for range tc.requestsToMake { + req, err := http.NewRequestWithContext(tc.ctx, tc.method, server.URL, nil) + if err != nil { + t.Fatalf("Failed to create request: %v", err) + } + + resp, err := httpClient.Do(req) + if err != nil { + t.Fatalf("failed to make request: %v", err) + } + resp.Body.Close() + } + parsedURL, err := url.Parse(server.URL) + if err != nil { + t.Fatalf("Failed to parse server URL: %v", err) + } + expectedCounter := fmt.Sprintf(` + # HELP certmanager_http_acme_client_request_count Total number of outbound ACME HTTP requests. Labels: scheme (http/https), host (ACME host), action (logical ACME operation), method (HTTP verb), status (HTTP status code). + # TYPE certmanager_http_acme_client_request_count counter + certmanager_http_acme_client_request_count{action="%s",host="%s",method="%s",scheme="%s",status="%d"} %d + `, tc.expectedAction, parsedURL.Host, tc.method, scheme, tc.statusToReturn, tc.requestsToMake) + err = testutil.CollectAndCompare(metrics.ACMERequestCounter(), strings.NewReader(expectedCounter)) + if err != nil { + t.Errorf("unexpected counter metric result:\n%v", err) + } + }) + } +} diff --git a/pkg/acme/client/interfaces.go b/pkg/acme/client/interfaces.go index 5a3f87e5421..f297d5f84a9 100644 --- a/pkg/acme/client/interfaces.go +++ b/pkg/acme/client/interfaces.go @@ -20,8 +20,7 @@ import ( "context" acmeutil "github.com/cert-manager/cert-manager/pkg/acme/util" - - "golang.org/x/crypto/acme" + "github.com/cert-manager/cert-manager/third_party/forked/acme" ) // Interface is an Automatic Certificate Management Environment (ACME) client @@ -29,20 +28,31 @@ import ( // // For more information see https://pkg.go.dev/golang.org/x/crypto/acme#Client // and RFC 8555 (https://tools.ietf.org/html/rfc8555). -type Interface interface { +type Interface interface { //nolint:interfacebloat AuthorizeOrder(ctx context.Context, id []acme.AuthzID, opt ...acme.OrderOption) (*acme.Order, error) GetOrder(ctx context.Context, url string) (*acme.Order, error) FetchCert(ctx context.Context, url string, bundle bool) ([][]byte, error) ListCertAlternates(ctx context.Context, url string) ([]string, error) WaitOrder(ctx context.Context, url string) (*acme.Order, error) CreateOrderCert(ctx context.Context, finalizeURL string, csr []byte, bundle bool) (der [][]byte, certURL string, err error) + // Accept will (in success cases) be called once per a Challenge once it + // has passed self-check and is ready to be verified by the ACME server. Accept(ctx context.Context, chal *acme.Challenge) (*acme.Challenge, error) GetChallenge(ctx context.Context, url string) (*acme.Challenge, error) + // GetAuthorization will be called once for each required authorization + // for an Order. Additionally it will be called most likely once when a + // Challenge has been scheduled for processing to retrieve its status. GetAuthorization(ctx context.Context, url string) (*acme.Authorization, error) + // WaitAuthorization will, in success cases, be called once per + // Challenge after it has been accepted. WaitAuthorization(ctx context.Context, url string) (*acme.Authorization, error) Register(ctx context.Context, acct *acme.Account, prompt func(tosURL string) bool) (*acme.Account, error) GetReg(ctx context.Context, url string) (*acme.Account, error) + // HTTP01ChallengeResponse will be called once when a cert-manager.io + // Challenge for an http-01 challenge type is being created. HTTP01ChallengeResponse(token string) (string, error) + // DNS01ChallengeResponse will be called once when a cert-manager.io + // Challenge for an http-01 challenge type is being created. DNS01ChallengeRecord(token string) (string, error) Discover(ctx context.Context) (acme.Directory, error) UpdateReg(ctx context.Context, a *acme.Account) (*acme.Account, error) diff --git a/pkg/acme/client/middleware/logger.go b/pkg/acme/client/middleware/logger.go index 73c8dbfd1f0..4addad1c0dd 100644 --- a/pkg/acme/client/middleware/logger.go +++ b/pkg/acme/client/middleware/logger.go @@ -20,10 +20,10 @@ import ( "context" "github.com/go-logr/logr" - "golang.org/x/crypto/acme" "github.com/cert-manager/cert-manager/pkg/acme/client" logf "github.com/cert-manager/cert-manager/pkg/logs" + "github.com/cert-manager/cert-manager/third_party/forked/acme" ) func NewLogger(baseCl client.Interface) client.Interface { @@ -33,7 +33,9 @@ func NewLogger(baseCl client.Interface) client.Interface { } } -// Logger is a glog based logging middleware for an ACME client +// Logger is a glog based logging middleware for an ACME client. +// Also used to attach an AcmeActionLabel to the request's context to be used downstream in as a metric label. +// TODO: Maybe rename to something more generic like "ACMEObservabilityMiddleware" since it does more than just logging. type Logger struct { baseCl client.Interface log logr.Logger @@ -43,72 +45,84 @@ var _ client.Interface = &Logger{} func (l *Logger) AuthorizeOrder(ctx context.Context, id []acme.AuthzID, opt ...acme.OrderOption) (*acme.Order, error) { l.log.V(logf.TraceLevel).Info("Calling AuthorizeOrder") + ctx = context.WithValue(ctx, client.AcmeActionLabel, "authorize_order") return l.baseCl.AuthorizeOrder(ctx, id, opt...) } func (l *Logger) GetOrder(ctx context.Context, url string) (*acme.Order, error) { l.log.V(logf.TraceLevel).Info("Calling GetOrder") + ctx = context.WithValue(ctx, client.AcmeActionLabel, "get_order") return l.baseCl.GetOrder(ctx, url) } func (l *Logger) FetchCert(ctx context.Context, url string, bundle bool) ([][]byte, error) { l.log.V(logf.TraceLevel).Info("Calling FetchCert") + ctx = context.WithValue(ctx, client.AcmeActionLabel, "fetch_cert") return l.baseCl.FetchCert(ctx, url, bundle) } func (l *Logger) ListCertAlternates(ctx context.Context, url string) ([]string, error) { l.log.V(logf.TraceLevel).Info("Calling ListCertAlternates") + ctx = context.WithValue(ctx, client.AcmeActionLabel, "list_cert_alternates") return l.baseCl.ListCertAlternates(ctx, url) } func (l *Logger) WaitOrder(ctx context.Context, url string) (*acme.Order, error) { l.log.V(logf.TraceLevel).Info("Calling WaitOrder") + ctx = context.WithValue(ctx, client.AcmeActionLabel, "wait_order") return l.baseCl.WaitOrder(ctx, url) } func (l *Logger) CreateOrderCert(ctx context.Context, finalizeURL string, csr []byte, bundle bool) (der [][]byte, certURL string, err error) { l.log.V(logf.TraceLevel).Info("Calling CreateOrderCert") + ctx = context.WithValue(ctx, client.AcmeActionLabel, "create_order_cert") return l.baseCl.CreateOrderCert(ctx, finalizeURL, csr, bundle) } func (l *Logger) Accept(ctx context.Context, chal *acme.Challenge) (*acme.Challenge, error) { l.log.V(logf.TraceLevel).Info("Calling Accept") + ctx = context.WithValue(ctx, client.AcmeActionLabel, "accept_challenge") return l.baseCl.Accept(ctx, chal) } func (l *Logger) GetChallenge(ctx context.Context, url string) (*acme.Challenge, error) { l.log.V(logf.TraceLevel).Info("Calling GetChallenge") + ctx = context.WithValue(ctx, client.AcmeActionLabel, "get_challenge") return l.baseCl.GetChallenge(ctx, url) } func (l *Logger) GetAuthorization(ctx context.Context, url string) (*acme.Authorization, error) { l.log.V(logf.TraceLevel).Info("Calling GetAuthorization") + ctx = context.WithValue(ctx, client.AcmeActionLabel, "get_authorization") return l.baseCl.GetAuthorization(ctx, url) } func (l *Logger) WaitAuthorization(ctx context.Context, url string) (*acme.Authorization, error) { l.log.V(logf.TraceLevel).Info("Calling WaitAuthorization") + ctx = context.WithValue(ctx, client.AcmeActionLabel, "wait_authorization") return l.baseCl.WaitAuthorization(ctx, url) } func (l *Logger) Register(ctx context.Context, a *acme.Account, prompt func(tosURL string) bool) (*acme.Account, error) { l.log.V(logf.TraceLevel).Info("Calling Register") + ctx = context.WithValue(ctx, client.AcmeActionLabel, "register_account") return l.baseCl.Register(ctx, a, prompt) } func (l *Logger) GetReg(ctx context.Context, url string) (*acme.Account, error) { l.log.V(logf.TraceLevel).Info("Calling GetReg") + ctx = context.WithValue(ctx, client.AcmeActionLabel, "get_registration") return l.baseCl.GetReg(ctx, url) } @@ -127,12 +141,14 @@ func (l *Logger) DNS01ChallengeRecord(token string) (string, error) { func (l *Logger) Discover(ctx context.Context) (acme.Directory, error) { l.log.V(logf.TraceLevel).Info("Calling Discover") + ctx = context.WithValue(ctx, client.AcmeActionLabel, "discover") return l.baseCl.Discover(ctx) } func (l *Logger) UpdateReg(ctx context.Context, a *acme.Account) (*acme.Account, error) { l.log.V(logf.TraceLevel).Info("Calling UpdateReg") + ctx = context.WithValue(ctx, client.AcmeActionLabel, "update_registration") return l.baseCl.UpdateReg(ctx, a) } diff --git a/pkg/acme/util.go b/pkg/acme/util.go index 42e05aae421..95b441c274a 100644 --- a/pkg/acme/util.go +++ b/pkg/acme/util.go @@ -28,8 +28,7 @@ import ( // The 'valid' state is a special case, as it is a final state for Challenges but // not for Orders. func IsFinalState(s cmacme.State) bool { - switch s { - case cmacme.Valid: + if s == cmacme.Valid { return true } return IsFailureState(s) @@ -39,8 +38,9 @@ func IsFailureState(s cmacme.State) bool { switch s { case cmacme.Invalid, cmacme.Expired, cmacme.Errored: return true + default: + return false } - return false } // PrivateKeySelector will default the SecretKeySelector with a default secret key diff --git a/pkg/acme/util/util.go b/pkg/acme/util/util.go index 0f2c76f6702..4094ec32279 100644 --- a/pkg/acme/util/util.go +++ b/pkg/acme/util/util.go @@ -17,8 +17,7 @@ limitations under the License. package util import ( - "crypto/rand" - "math/big" + "math/rand/v2" "net/http" "time" ) @@ -34,7 +33,7 @@ const ( func RetryBackoff(n int, r *http.Request, resp *http.Response) time.Duration { // According to the spec badNonce is urn:ietf:params:acme:error:badNonce. - // However, we can not use the request body in here as it is closed already. + // However, we cannot use the request body in here as it is closed already. // So we're using its status code instead: 400 if resp.StatusCode != http.StatusBadRequest { return -1 @@ -43,17 +42,20 @@ func RetryBackoff(n int, r *http.Request, resp *http.Response) time.Duration { // don't retry more than 6 times, if we get 6 nonce mismatches something is quite wrong if n > maxRetries { return -1 - } else if n < 1 { - // n is used for the backoff time below - n = 1 } - var jitter time.Duration - if x, err := rand.Int(rand.Reader, big.NewInt(1000)); err == nil { - jitter = (1 + time.Duration(x.Int64())) * time.Millisecond + // No need for a cryptographically secure RNG here + jitter := 1 + time.Millisecond*time.Duration(rand.Int64N(1000)) // #nosec G404 + + // the exponent is calculated slightly contrived to allow the gosec:G115 + // linter to recognise the safe type conversion. + // simple formula: exponent = max(0, n-1) + exponent := uint(0) + if temp := n - 1; temp >= 0 { + exponent = uint(temp) } - d := time.Duration(1< maxDelay { return maxDelay } diff --git a/pkg/acme/util/util_test.go b/pkg/acme/util/util_test.go index b1b41cc7710..e1f2adea4bd 100644 --- a/pkg/acme/util/util_test.go +++ b/pkg/acme/util/util_test.go @@ -56,7 +56,7 @@ func TestRetryBackoff(t *testing.T) { }, }, { - name: "Retry a 400 error when when less than 6 times", + name: "Retry a 400 error when less than 6 times", args: args{ n: 5, r: &http.Request{}, diff --git a/pkg/acme/webhook/apis/acme/v1alpha1/doc.go b/pkg/acme/webhook/apis/acme/v1alpha1/doc.go index b7980ddcc69..0fa9b1b1a70 100644 --- a/pkg/acme/webhook/apis/acme/v1alpha1/doc.go +++ b/pkg/acme/webhook/apis/acme/v1alpha1/doc.go @@ -16,6 +16,7 @@ limitations under the License. // +k8s:deepcopy-gen=package,register // +k8s:defaulter-gen=TypeMeta +// +k8s:openapi-gen=true // Package v1alpha1 is the v1alpha1 version of the API. // +groupName=webhook.acme.cert-manager.io diff --git a/pkg/acme/webhook/apis/acme/v1alpha1/types.go b/pkg/acme/webhook/apis/acme/v1alpha1/types.go index d362127c007..7df58ae864b 100644 --- a/pkg/acme/webhook/apis/acme/v1alpha1/types.go +++ b/pkg/acme/webhook/apis/acme/v1alpha1/types.go @@ -112,7 +112,7 @@ type ChallengeRequest struct { } // ChallengeAction represents an action associated with a challenge such as -// 'present' or cleanup'. +// 'present' or 'cleanup'. type ChallengeAction string const ( diff --git a/pkg/acme/webhook/apiserver/apiserver.go b/pkg/acme/webhook/apiserver/apiserver.go index acfc12b21ea..256a016a08a 100644 --- a/pkg/acme/webhook/apiserver/apiserver.go +++ b/pkg/acme/webhook/apiserver/apiserver.go @@ -24,13 +24,16 @@ import ( "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/serializer" - "k8s.io/apimachinery/pkg/version" + utilruntime "k8s.io/apimachinery/pkg/util/runtime" + "k8s.io/apiserver/pkg/endpoints/openapi" "k8s.io/apiserver/pkg/registry/rest" genericapiserver "k8s.io/apiserver/pkg/server" restclient "k8s.io/client-go/rest" + basecompatibility "k8s.io/component-base/compatibility" "github.com/cert-manager/cert-manager/pkg/acme/webhook" whapi "github.com/cert-manager/cert-manager/pkg/acme/webhook/apis/acme/v1alpha1" + cmopenapi "github.com/cert-manager/cert-manager/pkg/acme/webhook/openapi" "github.com/cert-manager/cert-manager/pkg/acme/webhook/registry/challengepayload" ) @@ -40,7 +43,7 @@ var ( ) func init() { - whapi.AddToScheme(Scheme) + utilruntime.Must(whapi.AddToScheme(Scheme)) // we need to add the options to empty v1 // TODO fix the server code to avoid this @@ -54,20 +57,12 @@ func init() { &metav1.APIGroupList{}, &metav1.APIGroup{}, &metav1.APIResourceList{}, - &metav1.ListOptions{}, - &metav1.GetOptions{}, - &metav1.PatchOptions{}, - &metav1.DeleteOptions{}, - &metav1.CreateOptions{}, - &metav1.UpdateOptions{}, ) } type Config struct { GenericConfig *genericapiserver.RecommendedConfig ExtraConfig ExtraConfig - - restConfig *restclient.Config } type ExtraConfig struct { @@ -98,21 +93,21 @@ type CompletedConfig struct { // Complete fills in any fields not set that are required to have valid data. It's mutating the receiver. func (c *Config) Complete() CompletedConfig { - completedCfg := completedConfig{ + c.GenericConfig.EffectiveVersion = basecompatibility.NewEffectiveVersionFromString("1.1", "", "") + c.GenericConfig.OpenAPIConfig = genericapiserver.DefaultOpenAPIConfig(cmopenapi.GetOpenAPIDefinitions, openapi.NewDefinitionNamer(Scheme)) + c.GenericConfig.OpenAPIV3Config = genericapiserver.DefaultOpenAPIV3Config(cmopenapi.GetOpenAPIDefinitions, openapi.NewDefinitionNamer(Scheme)) + + return CompletedConfig{&completedConfig{ c.GenericConfig.Complete(), &c.ExtraConfig, - c.restConfig, - } - - completedCfg.GenericConfig.Version = &version.Info{ - Major: "1", - Minor: "1", - } - - return CompletedConfig{&completedCfg} + c.GenericConfig.ClientConfig, + }} } -// New returns a new instance of AdmissionServer from the given config. +// New returns a new instance of apiserver from the given config. Each of the +// configured solvers will have an API GroupVersion registered with the new +// apiserver and will have its Initialize function passed as post-start hook +// with the server. func (c completedConfig) New() (*ChallengeServer, error) { genericServer, err := c.GenericConfig.New("challenge-server", genericapiserver.NewEmptyDelegate()) // completion is done in Complete, no need for a second time if err != nil { @@ -123,47 +118,40 @@ func (c completedConfig) New() (*ChallengeServer, error) { GenericAPIServer: genericServer, } - if c.restConfig == nil { - c.restConfig, err = restclient.InClusterConfig() - if err != nil { - return nil, err - } - } - // TODO we're going to need a later k8s.io/apiserver so that we can get discovery to list a different group version for // our endpoint which we'll use to back some custom storage which will consume the AdmissionReview type and give back the correct response apiGroupInfo := genericapiserver.APIGroupInfo{ VersionedResourcesStorageMap: map[string]map[string]rest.Storage{}, - // TODO unhardcode this. It was hardcoded before, but we need to re-evaluate - OptionsExternalVersion: &schema.GroupVersion{Version: "v1alpha1"}, + // TODO unhardcode this. It was hardcoded before, but we need to re-evaluate + OptionsExternalVersion: &schema.GroupVersion{Version: "v1"}, Scheme: Scheme, ParameterCodec: metav1.ParameterCodec, NegotiatedSerializer: Codecs, } for _, solver := range solversByName(c.ExtraConfig.Solvers...) { - challengeHandler := challengepayload.NewREST(solver) - v1alpha1storage, ok := apiGroupInfo.VersionedResourcesStorageMap["v1alpha1"] - if !ok { - v1alpha1storage = map[string]rest.Storage{} - } - gvr := metav1.GroupVersionResource{ Group: c.ExtraConfig.SolverGroup, Version: "v1alpha1", Resource: solver.Name(), } + challengeHandler := challengepayload.NewREST(solver) + apiGroupInfo.PrioritizedVersions = appendUniqueGroupVersion(apiGroupInfo.PrioritizedVersions, schema.GroupVersion{ Group: gvr.Group, Version: gvr.Version, }) + v1alpha1storage, ok := apiGroupInfo.VersionedResourcesStorageMap[gvr.Version] + if !ok { + v1alpha1storage = map[string]rest.Storage{} + } v1alpha1storage[gvr.Resource] = challengeHandler apiGroupInfo.VersionedResourcesStorageMap[gvr.Version] = v1alpha1storage } if err := s.GenericAPIServer.InstallAPIGroup(&apiGroupInfo); err != nil { - return nil, err + return nil, fmt.Errorf("error installing APIGroup for solvers: %w", err) } for i := range c.ExtraConfig.Solvers { @@ -174,7 +162,7 @@ func (c completedConfig) New() (*ChallengeServer, error) { } s.GenericAPIServer.AddPostStartHookOrDie(postStartName, func(context genericapiserver.PostStartHookContext) error { - return solver.Initialize(c.restConfig, context.StopCh) + return solver.Initialize(c.restConfig, context.Done()) }, ) } diff --git a/pkg/acme/webhook/apiserver/apiserver_test.go b/pkg/acme/webhook/apiserver/apiserver_test.go index 31018d294c6..d350dd432db 100644 --- a/pkg/acme/webhook/apiserver/apiserver_test.go +++ b/pkg/acme/webhook/apiserver/apiserver_test.go @@ -75,7 +75,6 @@ func TestNewChallengeServer(t *testing.T) { noOpSolver{name: "solver-1"}, }, }, - restConfig: &rest.Config{}, }, expErr: false, }, @@ -89,7 +88,6 @@ func TestNewChallengeServer(t *testing.T) { noOpSolver{name: "solver-2"}, }, }, - restConfig: &rest.Config{}, }, expErr: false, }, diff --git a/pkg/acme/webhook/cmd/cmd.go b/pkg/acme/webhook/cmd/cmd.go index 5e6c0ea1817..89b93ab61a1 100644 --- a/pkg/acme/webhook/cmd/cmd.go +++ b/pkg/acme/webhook/cmd/cmd.go @@ -17,32 +17,40 @@ limitations under the License. package cmd import ( - "flag" + "context" "os" "runtime" "k8s.io/component-base/logs" + ctrl "sigs.k8s.io/controller-runtime" - "github.com/cert-manager/cert-manager/cmd/util" + "github.com/cert-manager/cert-manager/internal/cmd/util" "github.com/cert-manager/cert-manager/pkg/acme/webhook" "github.com/cert-manager/cert-manager/pkg/acme/webhook/cmd/server" logf "github.com/cert-manager/cert-manager/pkg/logs" ) +// RunWebhookServer creates and starts a new apiserver that acts as an external +// webhook server for solving DNS challenges using the provided solver +// implementations. This can be used as an entry point by external webhook +// implementations, see +// https://github.com/cert-manager/webhook-example/blob/899c408751425f8d0842b61c0e62fd8035d00316/main.go#L23-L31 func RunWebhookServer(groupName string, hooks ...webhook.Solver) { - stopCh, exit := util.SetupExitHandler(util.GracefulShutdown) + ctx, exit := util.SetupExitHandler(context.Background(), util.GracefulShutdown) defer exit() // This function might call os.Exit, so defer last logs.InitLogs() defer logs.FlushLogs() + ctrl.SetLogger(logf.Log) + ctx = logf.NewContext(ctx, logf.Log, "acme-dns-webhook") if len(os.Getenv("GOMAXPROCS")) == 0 { runtime.GOMAXPROCS(runtime.NumCPU()) } - cmd := server.NewCommandStartWebhookServer(os.Stdout, os.Stderr, stopCh, groupName, hooks...) - cmd.Flags().AddGoFlagSet(flag.CommandLine) - if err := cmd.Execute(); err != nil { + cmd := server.NewCommandStartWebhookServer(ctx, groupName, hooks...) + + if err := cmd.ExecuteContext(ctx); err != nil { logf.Log.Error(err, "error executing command") util.SetExitCode(err) } diff --git a/pkg/acme/webhook/cmd/server/start.go b/pkg/acme/webhook/cmd/server/start.go index 46e44d70641..4e1e3e66c7b 100644 --- a/pkg/acme/webhook/cmd/server/start.go +++ b/pkg/acme/webhook/cmd/server/start.go @@ -17,66 +17,66 @@ limitations under the License. package server import ( + "context" "fmt" - "io" "net" "github.com/spf13/cobra" - genericapiserver "k8s.io/apiserver/pkg/server" genericoptions "k8s.io/apiserver/pkg/server/options" + "k8s.io/component-base/logs" "github.com/cert-manager/cert-manager/pkg/acme/webhook" whapi "github.com/cert-manager/cert-manager/pkg/acme/webhook/apis/acme/v1alpha1" "github.com/cert-manager/cert-manager/pkg/acme/webhook/apiserver" + logf "github.com/cert-manager/cert-manager/pkg/logs" ) -const defaultEtcdPathPrefix = "/registry/acme.cert-manager.io" - type WebhookServerOptions struct { + Logging *logs.Options + RecommendedOptions *genericoptions.RecommendedOptions SolverGroup string Solvers []webhook.Solver - - StdOut io.Writer - StdErr io.Writer } -func NewWebhookServerOptions(out, errOut io.Writer, groupName string, solvers ...webhook.Solver) *WebhookServerOptions { +func NewWebhookServerOptions(groupName string, solvers ...webhook.Solver) *WebhookServerOptions { o := &WebhookServerOptions{ - // TODO we will nil out the etcd storage options. This requires a later level of k8s.io/apiserver + Logging: logs.NewOptions(), + RecommendedOptions: genericoptions.NewRecommendedOptions( - defaultEtcdPathPrefix, + "", apiserver.Codecs.LegacyCodec(whapi.SchemeGroupVersion), ), SolverGroup: groupName, Solvers: solvers, - - StdOut: out, - StdErr: errOut, } o.RecommendedOptions.Etcd = nil o.RecommendedOptions.Admission = nil + o.RecommendedOptions.Features.EnablePriorityAndFairness = false return o } -func NewCommandStartWebhookServer(out, errOut io.Writer, stopCh <-chan struct{}, groupName string, solvers ...webhook.Solver) *cobra.Command { - o := NewWebhookServerOptions(out, errOut, groupName, solvers...) +func NewCommandStartWebhookServer(_ context.Context, groupName string, solvers ...webhook.Solver) *cobra.Command { + o := NewWebhookServerOptions(groupName, solvers...) cmd := &cobra.Command{ Short: "Launch an ACME solver API server", Long: "Launch an ACME solver API server", + // nolint:contextcheck // False positive RunE: func(c *cobra.Command, args []string) error { + runCtx := c.Context() + if err := o.Complete(); err != nil { return err } if err := o.Validate(args); err != nil { return err } - if err := o.RunWebhookServer(stopCh); err != nil { + if err := o.RunWebhookServer(runCtx); err != nil { return err } return nil @@ -84,12 +84,21 @@ func NewCommandStartWebhookServer(out, errOut io.Writer, stopCh <-chan struct{}, } flags := cmd.Flags() + logf.AddFlags(o.Logging, flags) o.RecommendedOptions.AddFlags(flags) return cmd } func (o WebhookServerOptions) Validate(args []string) error { + if err := logf.ValidateAndApply(o.Logging); err != nil { + return err + } + + if errs := o.RecommendedOptions.Validate(); len(errs) > 0 { + return fmt.Errorf("error validating recommended options: %v", errs) + } + return nil } @@ -97,6 +106,9 @@ func (o *WebhookServerOptions) Complete() error { return nil } +// Config creates a new webhook server config that includes generic upstream +// apiserver options, rest client config and the Solvers configured for this +// webhook server func (o WebhookServerOptions) Config() (*apiserver.Config, error) { // TODO have a "real" external address if err := o.RecommendedOptions.SecureServing.MaybeDefaultWithSelfSignedCerts("localhost", nil, []net.IP{net.ParseIP("127.0.0.1")}); err != nil { @@ -118,7 +130,9 @@ func (o WebhookServerOptions) Config() (*apiserver.Config, error) { return config, nil } -func (o WebhookServerOptions) RunWebhookServer(stopCh <-chan struct{}) error { +// RunWebhookServer creates a new apiserver, registers an API Group for each of +// the configured solvers and runs the new apiserver. +func (o WebhookServerOptions) RunWebhookServer(ctx context.Context) error { config, err := o.Config() if err != nil { return err @@ -128,5 +142,5 @@ func (o WebhookServerOptions) RunWebhookServer(stopCh <-chan struct{}) error { if err != nil { return err } - return server.GenericAPIServer.PrepareRun().Run(stopCh) + return server.GenericAPIServer.PrepareRun().RunWithContext(ctx) } diff --git a/internal/apis/acme/v1beta1/const.go b/pkg/acme/webhook/openapi/doc.go similarity index 88% rename from internal/apis/acme/v1beta1/const.go rename to pkg/acme/webhook/openapi/doc.go index ec9ad4dc222..512fa0f23dc 100644 --- a/internal/apis/acme/v1beta1/const.go +++ b/pkg/acme/webhook/openapi/doc.go @@ -14,8 +14,4 @@ See the License for the specific language governing permissions and limitations under the License. */ -package v1beta1 - -const ( - ACMEFinalizer = "finalizer.acme.cert-manager.io" -) +package openapi diff --git a/pkg/acme/webhook/openapi/zz_generated.openapi.go b/pkg/acme/webhook/openapi/zz_generated.openapi.go new file mode 100644 index 00000000000..1054784b217 --- /dev/null +++ b/pkg/acme/webhook/openapi/zz_generated.openapi.go @@ -0,0 +1,4389 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by openapi-gen. DO NOT EDIT. + +package openapi + +import ( + v1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + common "k8s.io/kube-openapi/pkg/common" + spec "k8s.io/kube-openapi/pkg/validation/spec" +) + +func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenAPIDefinition { + return map[string]common.OpenAPIDefinition{ + "github.com/cert-manager/cert-manager/pkg/acme/webhook/apis/acme/v1alpha1.ChallengePayload": schema_webhook_apis_acme_v1alpha1_ChallengePayload(ref), + "github.com/cert-manager/cert-manager/pkg/acme/webhook/apis/acme/v1alpha1.ChallengeRequest": schema_webhook_apis_acme_v1alpha1_ChallengeRequest(ref), + "github.com/cert-manager/cert-manager/pkg/acme/webhook/apis/acme/v1alpha1.ChallengeResponse": schema_webhook_apis_acme_v1alpha1_ChallengeResponse(ref), + "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.ConversionRequest": schema_pkg_apis_apiextensions_v1_ConversionRequest(ref), + "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.ConversionResponse": schema_pkg_apis_apiextensions_v1_ConversionResponse(ref), + "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.ConversionReview": schema_pkg_apis_apiextensions_v1_ConversionReview(ref), + "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.CustomResourceColumnDefinition": schema_pkg_apis_apiextensions_v1_CustomResourceColumnDefinition(ref), + "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.CustomResourceConversion": schema_pkg_apis_apiextensions_v1_CustomResourceConversion(ref), + "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.CustomResourceDefinition": schema_pkg_apis_apiextensions_v1_CustomResourceDefinition(ref), + "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.CustomResourceDefinitionCondition": schema_pkg_apis_apiextensions_v1_CustomResourceDefinitionCondition(ref), + "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.CustomResourceDefinitionList": schema_pkg_apis_apiextensions_v1_CustomResourceDefinitionList(ref), + "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.CustomResourceDefinitionNames": schema_pkg_apis_apiextensions_v1_CustomResourceDefinitionNames(ref), + "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.CustomResourceDefinitionSpec": schema_pkg_apis_apiextensions_v1_CustomResourceDefinitionSpec(ref), + "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.CustomResourceDefinitionStatus": schema_pkg_apis_apiextensions_v1_CustomResourceDefinitionStatus(ref), + "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.CustomResourceDefinitionVersion": schema_pkg_apis_apiextensions_v1_CustomResourceDefinitionVersion(ref), + "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.CustomResourceSubresourceScale": schema_pkg_apis_apiextensions_v1_CustomResourceSubresourceScale(ref), + "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.CustomResourceSubresourceStatus": schema_pkg_apis_apiextensions_v1_CustomResourceSubresourceStatus(ref), + "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.CustomResourceSubresources": schema_pkg_apis_apiextensions_v1_CustomResourceSubresources(ref), + "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.CustomResourceValidation": schema_pkg_apis_apiextensions_v1_CustomResourceValidation(ref), + "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.ExternalDocumentation": schema_pkg_apis_apiextensions_v1_ExternalDocumentation(ref), + "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.JSON": schema_pkg_apis_apiextensions_v1_JSON(ref), + "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.JSONSchemaProps": schema_pkg_apis_apiextensions_v1_JSONSchemaProps(ref), + "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.JSONSchemaPropsOrArray": schema_pkg_apis_apiextensions_v1_JSONSchemaPropsOrArray(ref), + "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.JSONSchemaPropsOrBool": schema_pkg_apis_apiextensions_v1_JSONSchemaPropsOrBool(ref), + "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.JSONSchemaPropsOrStringArray": schema_pkg_apis_apiextensions_v1_JSONSchemaPropsOrStringArray(ref), + "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.SelectableField": schema_pkg_apis_apiextensions_v1_SelectableField(ref), + "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.ServiceReference": schema_pkg_apis_apiextensions_v1_ServiceReference(ref), + "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.ValidationRule": schema_pkg_apis_apiextensions_v1_ValidationRule(ref), + "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.WebhookClientConfig": schema_pkg_apis_apiextensions_v1_WebhookClientConfig(ref), + "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.WebhookConversion": schema_pkg_apis_apiextensions_v1_WebhookConversion(ref), + "k8s.io/apimachinery/pkg/apis/meta/v1.APIGroup": schema_pkg_apis_meta_v1_APIGroup(ref), + "k8s.io/apimachinery/pkg/apis/meta/v1.APIGroupList": schema_pkg_apis_meta_v1_APIGroupList(ref), + "k8s.io/apimachinery/pkg/apis/meta/v1.APIResource": schema_pkg_apis_meta_v1_APIResource(ref), + "k8s.io/apimachinery/pkg/apis/meta/v1.APIResourceList": schema_pkg_apis_meta_v1_APIResourceList(ref), + "k8s.io/apimachinery/pkg/apis/meta/v1.APIVersions": schema_pkg_apis_meta_v1_APIVersions(ref), + "k8s.io/apimachinery/pkg/apis/meta/v1.ApplyOptions": schema_pkg_apis_meta_v1_ApplyOptions(ref), + "k8s.io/apimachinery/pkg/apis/meta/v1.Condition": schema_pkg_apis_meta_v1_Condition(ref), + "k8s.io/apimachinery/pkg/apis/meta/v1.CreateOptions": schema_pkg_apis_meta_v1_CreateOptions(ref), + "k8s.io/apimachinery/pkg/apis/meta/v1.DeleteOptions": schema_pkg_apis_meta_v1_DeleteOptions(ref), + "k8s.io/apimachinery/pkg/apis/meta/v1.Duration": schema_pkg_apis_meta_v1_Duration(ref), + "k8s.io/apimachinery/pkg/apis/meta/v1.FieldSelectorRequirement": schema_pkg_apis_meta_v1_FieldSelectorRequirement(ref), + "k8s.io/apimachinery/pkg/apis/meta/v1.FieldsV1": schema_pkg_apis_meta_v1_FieldsV1(ref), + "k8s.io/apimachinery/pkg/apis/meta/v1.GetOptions": schema_pkg_apis_meta_v1_GetOptions(ref), + "k8s.io/apimachinery/pkg/apis/meta/v1.GroupKind": schema_pkg_apis_meta_v1_GroupKind(ref), + "k8s.io/apimachinery/pkg/apis/meta/v1.GroupResource": schema_pkg_apis_meta_v1_GroupResource(ref), + "k8s.io/apimachinery/pkg/apis/meta/v1.GroupVersion": schema_pkg_apis_meta_v1_GroupVersion(ref), + "k8s.io/apimachinery/pkg/apis/meta/v1.GroupVersionForDiscovery": schema_pkg_apis_meta_v1_GroupVersionForDiscovery(ref), + "k8s.io/apimachinery/pkg/apis/meta/v1.GroupVersionKind": schema_pkg_apis_meta_v1_GroupVersionKind(ref), + "k8s.io/apimachinery/pkg/apis/meta/v1.GroupVersionResource": schema_pkg_apis_meta_v1_GroupVersionResource(ref), + "k8s.io/apimachinery/pkg/apis/meta/v1.InternalEvent": schema_pkg_apis_meta_v1_InternalEvent(ref), + "k8s.io/apimachinery/pkg/apis/meta/v1.LabelSelector": schema_pkg_apis_meta_v1_LabelSelector(ref), + "k8s.io/apimachinery/pkg/apis/meta/v1.LabelSelectorRequirement": schema_pkg_apis_meta_v1_LabelSelectorRequirement(ref), + "k8s.io/apimachinery/pkg/apis/meta/v1.List": schema_pkg_apis_meta_v1_List(ref), + "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta": schema_pkg_apis_meta_v1_ListMeta(ref), + "k8s.io/apimachinery/pkg/apis/meta/v1.ListOptions": schema_pkg_apis_meta_v1_ListOptions(ref), + "k8s.io/apimachinery/pkg/apis/meta/v1.ManagedFieldsEntry": schema_pkg_apis_meta_v1_ManagedFieldsEntry(ref), + "k8s.io/apimachinery/pkg/apis/meta/v1.MicroTime": schema_pkg_apis_meta_v1_MicroTime(ref), + "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta": schema_pkg_apis_meta_v1_ObjectMeta(ref), + "k8s.io/apimachinery/pkg/apis/meta/v1.OwnerReference": schema_pkg_apis_meta_v1_OwnerReference(ref), + "k8s.io/apimachinery/pkg/apis/meta/v1.PartialObjectMetadata": schema_pkg_apis_meta_v1_PartialObjectMetadata(ref), + "k8s.io/apimachinery/pkg/apis/meta/v1.PartialObjectMetadataList": schema_pkg_apis_meta_v1_PartialObjectMetadataList(ref), + "k8s.io/apimachinery/pkg/apis/meta/v1.Patch": schema_pkg_apis_meta_v1_Patch(ref), + "k8s.io/apimachinery/pkg/apis/meta/v1.PatchOptions": schema_pkg_apis_meta_v1_PatchOptions(ref), + "k8s.io/apimachinery/pkg/apis/meta/v1.Preconditions": schema_pkg_apis_meta_v1_Preconditions(ref), + "k8s.io/apimachinery/pkg/apis/meta/v1.RootPaths": schema_pkg_apis_meta_v1_RootPaths(ref), + "k8s.io/apimachinery/pkg/apis/meta/v1.ServerAddressByClientCIDR": schema_pkg_apis_meta_v1_ServerAddressByClientCIDR(ref), + "k8s.io/apimachinery/pkg/apis/meta/v1.Status": schema_pkg_apis_meta_v1_Status(ref), + "k8s.io/apimachinery/pkg/apis/meta/v1.StatusCause": schema_pkg_apis_meta_v1_StatusCause(ref), + "k8s.io/apimachinery/pkg/apis/meta/v1.StatusDetails": schema_pkg_apis_meta_v1_StatusDetails(ref), + "k8s.io/apimachinery/pkg/apis/meta/v1.Table": schema_pkg_apis_meta_v1_Table(ref), + "k8s.io/apimachinery/pkg/apis/meta/v1.TableColumnDefinition": schema_pkg_apis_meta_v1_TableColumnDefinition(ref), + "k8s.io/apimachinery/pkg/apis/meta/v1.TableOptions": schema_pkg_apis_meta_v1_TableOptions(ref), + "k8s.io/apimachinery/pkg/apis/meta/v1.TableRow": schema_pkg_apis_meta_v1_TableRow(ref), + "k8s.io/apimachinery/pkg/apis/meta/v1.TableRowCondition": schema_pkg_apis_meta_v1_TableRowCondition(ref), + "k8s.io/apimachinery/pkg/apis/meta/v1.Time": schema_pkg_apis_meta_v1_Time(ref), + "k8s.io/apimachinery/pkg/apis/meta/v1.Timestamp": schema_pkg_apis_meta_v1_Timestamp(ref), + "k8s.io/apimachinery/pkg/apis/meta/v1.TypeMeta": schema_pkg_apis_meta_v1_TypeMeta(ref), + "k8s.io/apimachinery/pkg/apis/meta/v1.UpdateOptions": schema_pkg_apis_meta_v1_UpdateOptions(ref), + "k8s.io/apimachinery/pkg/apis/meta/v1.WatchEvent": schema_pkg_apis_meta_v1_WatchEvent(ref), + "k8s.io/apimachinery/pkg/runtime.RawExtension": schema_k8sio_apimachinery_pkg_runtime_RawExtension(ref), + "k8s.io/apimachinery/pkg/runtime.TypeMeta": schema_k8sio_apimachinery_pkg_runtime_TypeMeta(ref), + "k8s.io/apimachinery/pkg/runtime.Unknown": schema_k8sio_apimachinery_pkg_runtime_Unknown(ref), + "k8s.io/apimachinery/pkg/version.Info": schema_k8sio_apimachinery_pkg_version_Info(ref), + } +} + +func schema_webhook_apis_acme_v1alpha1_ChallengePayload(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "ChallengePayload describes a request/response for presenting or cleaning up an ACME challenge resource", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", + }, + }, + "request": { + SchemaProps: spec.SchemaProps{ + Description: "Request describes the attributes for the ACME solver request", + Ref: ref("github.com/cert-manager/cert-manager/pkg/acme/webhook/apis/acme/v1alpha1.ChallengeRequest"), + }, + }, + "response": { + SchemaProps: spec.SchemaProps{ + Description: "Response describes the attributes for the ACME solver response", + Ref: ref("github.com/cert-manager/cert-manager/pkg/acme/webhook/apis/acme/v1alpha1.ChallengeResponse"), + }, + }, + }, + }, + }, + Dependencies: []string{ + "github.com/cert-manager/cert-manager/pkg/acme/webhook/apis/acme/v1alpha1.ChallengeRequest", "github.com/cert-manager/cert-manager/pkg/acme/webhook/apis/acme/v1alpha1.ChallengeResponse"}, + } +} + +func schema_webhook_apis_acme_v1alpha1_ChallengeRequest(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "ChallengeRequest is a payload that can be sent to external ACME webhook solvers in order to 'Present' or 'CleanUp' a challenge with an ACME server.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "uid": { + SchemaProps: spec.SchemaProps{ + Description: "UID is an identifier for the individual request/response. It allows us to distinguish instances of requests which are otherwise identical (parallel requests, requests when earlier requests did not modify etc) The UID is meant to track the round trip (request/response) between the KAS and the WebHook, not the user request. It is suitable for correlating log entries between the webhook and apiserver, for either auditing or debugging.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "action": { + SchemaProps: spec.SchemaProps{ + Description: "Action is one of 'present' or 'cleanup'. If the action is 'present', the record will be presented with the solving service. If the action is 'cleanup', the record will be cleaned up with the solving service.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "type": { + SchemaProps: spec.SchemaProps{ + Description: "Type is the type of ACME challenge. Only dns-01 is currently supported.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "dnsName": { + SchemaProps: spec.SchemaProps{ + Description: "DNSName is the name of the domain that is actually being validated, as requested by the user on the Certificate resource. This will be of the form 'example.com' from normal hostnames, and '*.example.com' for wildcards.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "key": { + SchemaProps: spec.SchemaProps{ + Description: "Key is the key that should be presented. This key will already be signed by the account that owns the challenge. For DNS01, this is the key that should be set for the TXT record for ResolveFQDN.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "resourceNamespace": { + SchemaProps: spec.SchemaProps{ + Description: "ResourceNamespace is the namespace containing resources that are referenced in the providers config. If this request is solving for an Issuer resource, this will be the namespace of the Issuer. If this request is solving for a ClusterIssuer resource, this will be the configured 'cluster resource namespace'", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "resolvedFQDN": { + SchemaProps: spec.SchemaProps{ + Description: "ResolvedFQDN is the fully-qualified domain name that should be updated/presented after resolving all CNAMEs. This should be honoured when using the DNS01 solver type. This will be of the form '_acme-challenge.example.com.'.", + Type: []string{"string"}, + Format: "", + }, + }, + "resolvedZone": { + SchemaProps: spec.SchemaProps{ + Description: "ResolvedZone is the zone encompassing the ResolvedFQDN. This is included as part of the ChallengeRequest so that webhook implementers do not need to implement their own SOA recursion logic. This indicates the zone that the provided FQDN is encompassed within, determined by performing SOA record queries for each part of the FQDN until an authoritative zone is found. This will be of the form 'example.com.'.", + Type: []string{"string"}, + Format: "", + }, + }, + "allowAmbientCredentials": { + SchemaProps: spec.SchemaProps{ + Description: "AllowAmbientCredentials advises webhook implementations that they can use 'ambient credentials' for authenticating with their respective DNS provider services. This field SHOULD be honoured by all DNS webhook implementations, but in certain instances where it does not make sense to honour this option, an implementation may ignore it.", + Default: false, + Type: []string{"boolean"}, + Format: "", + }, + }, + "config": { + SchemaProps: spec.SchemaProps{ + Description: "Config contains unstructured JSON configuration data that the webhook implementation can unmarshal in order to fetch secrets or configure connection details etc. Secret values should not be passed in this field, in favour of references to Kubernetes Secret resources that the webhook can fetch.", + Ref: ref("k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.JSON"), + }, + }, + }, + Required: []string{"uid", "action", "type", "dnsName", "key", "resourceNamespace", "allowAmbientCredentials"}, + }, + }, + Dependencies: []string{ + "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.JSON"}, + } +} + +func schema_webhook_apis_acme_v1alpha1_ChallengeResponse(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "ChallengeResponse represents a response from an ACME challenge.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "uid": { + SchemaProps: spec.SchemaProps{ + Description: "UID is an identifier for the individual request/response. This should be copied over from the corresponding ChallengeRequest.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "success": { + SchemaProps: spec.SchemaProps{ + Description: "Success will be set to true if the request action (i.e. presenting or cleaning up) was successful.", + Default: false, + Type: []string{"boolean"}, + Format: "", + }, + }, + "status": { + SchemaProps: spec.SchemaProps{ + Description: "Result contains extra details into why a challenge request failed. This field will be completely ignored if 'success' is true.", + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.Status"), + }, + }, + }, + Required: []string{"uid", "success"}, + }, + }, + Dependencies: []string{ + "k8s.io/apimachinery/pkg/apis/meta/v1.Status"}, + } +} + +func schema_pkg_apis_apiextensions_v1_ConversionRequest(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "ConversionRequest describes the conversion request parameters.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "uid": { + SchemaProps: spec.SchemaProps{ + Description: "uid is an identifier for the individual request/response. It allows distinguishing instances of requests which are otherwise identical (parallel requests, etc). The UID is meant to track the round trip (request/response) between the Kubernetes API server and the webhook, not the user request. It is suitable for correlating log entries between the webhook and apiserver, for either auditing or debugging.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "desiredAPIVersion": { + SchemaProps: spec.SchemaProps{ + Description: "desiredAPIVersion is the version to convert given objects to. e.g. \"myapi.example.com/v1\"", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "objects": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "objects is the list of custom resource objects to be converted.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Ref: ref("k8s.io/apimachinery/pkg/runtime.RawExtension"), + }, + }, + }, + }, + }, + }, + Required: []string{"uid", "desiredAPIVersion", "objects"}, + }, + }, + Dependencies: []string{ + "k8s.io/apimachinery/pkg/runtime.RawExtension"}, + } +} + +func schema_pkg_apis_apiextensions_v1_ConversionResponse(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "ConversionResponse describes a conversion response.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "uid": { + SchemaProps: spec.SchemaProps{ + Description: "uid is an identifier for the individual request/response. This should be copied over from the corresponding `request.uid`.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "convertedObjects": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "convertedObjects is the list of converted version of `request.objects` if the `result` is successful, otherwise empty. The webhook is expected to set `apiVersion` of these objects to the `request.desiredAPIVersion`. The list must also have the same size as the input list with the same objects in the same order (equal kind, metadata.uid, metadata.name and metadata.namespace). The webhook is allowed to mutate labels and annotations. Any other change to the metadata is silently ignored.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Ref: ref("k8s.io/apimachinery/pkg/runtime.RawExtension"), + }, + }, + }, + }, + }, + "result": { + SchemaProps: spec.SchemaProps{ + Description: "result contains the result of conversion with extra details if the conversion failed. `result.status` determines if the conversion failed or succeeded. The `result.status` field is required and represents the success or failure of the conversion. A successful conversion must set `result.status` to `Success`. A failed conversion must set `result.status` to `Failure` and provide more details in `result.message` and return http status 200. The `result.message` will be used to construct an error message for the end user.", + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.Status"), + }, + }, + }, + Required: []string{"uid", "convertedObjects", "result"}, + }, + }, + Dependencies: []string{ + "k8s.io/apimachinery/pkg/apis/meta/v1.Status", "k8s.io/apimachinery/pkg/runtime.RawExtension"}, + } +} + +func schema_pkg_apis_apiextensions_v1_ConversionReview(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "ConversionReview describes a conversion request/response.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", + }, + }, + "request": { + SchemaProps: spec.SchemaProps{ + Description: "request describes the attributes for the conversion request.", + Ref: ref("k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.ConversionRequest"), + }, + }, + "response": { + SchemaProps: spec.SchemaProps{ + Description: "response describes the attributes for the conversion response.", + Ref: ref("k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.ConversionResponse"), + }, + }, + }, + }, + }, + Dependencies: []string{ + "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.ConversionRequest", "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.ConversionResponse"}, + } +} + +func schema_pkg_apis_apiextensions_v1_CustomResourceColumnDefinition(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "CustomResourceColumnDefinition specifies a column for server side printing.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "name": { + SchemaProps: spec.SchemaProps{ + Description: "name is a human readable name for the column.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "type": { + SchemaProps: spec.SchemaProps{ + Description: "type is an OpenAPI type definition for this column. See https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#data-types for details.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "format": { + SchemaProps: spec.SchemaProps{ + Description: "format is an optional OpenAPI type definition for this column. The 'name' format is applied to the primary identifier column to assist in clients identifying column is the resource name. See https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#data-types for details.", + Type: []string{"string"}, + Format: "", + }, + }, + "description": { + SchemaProps: spec.SchemaProps{ + Description: "description is a human readable description of this column.", + Type: []string{"string"}, + Format: "", + }, + }, + "priority": { + SchemaProps: spec.SchemaProps{ + Description: "priority is an integer defining the relative importance of this column compared to others. Lower numbers are considered higher priority. Columns that may be omitted in limited space scenarios should be given a priority greater than 0.", + Type: []string{"integer"}, + Format: "int32", + }, + }, + "jsonPath": { + SchemaProps: spec.SchemaProps{ + Description: "jsonPath is a simple JSON path (i.e. with array notation) which is evaluated against each custom resource to produce the value for this column.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"name", "type", "jsonPath"}, + }, + }, + } +} + +func schema_pkg_apis_apiextensions_v1_CustomResourceConversion(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "CustomResourceConversion describes how to convert different versions of a CR.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "strategy": { + SchemaProps: spec.SchemaProps{ + Description: "strategy specifies how custom resources are converted between versions. Allowed values are: - `\"None\"`: The converter only change the apiVersion and would not touch any other field in the custom resource. - `\"Webhook\"`: API Server will call to an external webhook to do the conversion. Additional information\n is needed for this option. This requires spec.preserveUnknownFields to be false, and spec.conversion.webhook to be set.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "webhook": { + SchemaProps: spec.SchemaProps{ + Description: "webhook describes how to call the conversion webhook. Required when `strategy` is set to `\"Webhook\"`.", + Ref: ref("k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.WebhookConversion"), + }, + }, + }, + Required: []string{"strategy"}, + }, + }, + Dependencies: []string{ + "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.WebhookConversion"}, + } +} + +func schema_pkg_apis_apiextensions_v1_CustomResourceDefinition(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "CustomResourceDefinition represents a resource that should be exposed on the API server. Its name MUST be in the format <.spec.name>.<.spec.group>.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", + }, + }, + "metadata": { + SchemaProps: spec.SchemaProps{ + Description: "Standard object's metadata More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata", + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"), + }, + }, + "spec": { + SchemaProps: spec.SchemaProps{ + Description: "spec describes how the user wants the resources to appear", + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.CustomResourceDefinitionSpec"), + }, + }, + "status": { + SchemaProps: spec.SchemaProps{ + Description: "status indicates the actual state of the CustomResourceDefinition", + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.CustomResourceDefinitionStatus"), + }, + }, + }, + Required: []string{"spec"}, + }, + }, + Dependencies: []string{ + "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.CustomResourceDefinitionSpec", "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.CustomResourceDefinitionStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"}, + } +} + +func schema_pkg_apis_apiextensions_v1_CustomResourceDefinitionCondition(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "CustomResourceDefinitionCondition contains details for the current condition of this pod.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "type": { + SchemaProps: spec.SchemaProps{ + Description: "type is the type of the condition. Types include Established, NamesAccepted and Terminating.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "status": { + SchemaProps: spec.SchemaProps{ + Description: "status is the status of the condition. Can be True, False, Unknown.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "lastTransitionTime": { + SchemaProps: spec.SchemaProps{ + Description: "lastTransitionTime last time the condition transitioned from one status to another.", + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.Time"), + }, + }, + "reason": { + SchemaProps: spec.SchemaProps{ + Description: "reason is a unique, one-word, CamelCase reason for the condition's last transition.", + Type: []string{"string"}, + Format: "", + }, + }, + "message": { + SchemaProps: spec.SchemaProps{ + Description: "message is a human-readable message indicating details about last transition.", + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"type", "status"}, + }, + }, + Dependencies: []string{ + "k8s.io/apimachinery/pkg/apis/meta/v1.Time"}, + } +} + +func schema_pkg_apis_apiextensions_v1_CustomResourceDefinitionList(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "CustomResourceDefinitionList is a list of CustomResourceDefinition objects.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", + }, + }, + "metadata": { + SchemaProps: spec.SchemaProps{ + Description: "Standard object's metadata More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata", + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"), + }, + }, + "items": { + SchemaProps: spec.SchemaProps{ + Description: "items list individual CustomResourceDefinition objects", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.CustomResourceDefinition"), + }, + }, + }, + }, + }, + }, + Required: []string{"items"}, + }, + }, + Dependencies: []string{ + "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.CustomResourceDefinition", "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"}, + } +} + +func schema_pkg_apis_apiextensions_v1_CustomResourceDefinitionNames(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "CustomResourceDefinitionNames indicates the names to serve this CustomResourceDefinition", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "plural": { + SchemaProps: spec.SchemaProps{ + Description: "plural is the plural name of the resource to serve. The custom resources are served under `/apis///.../`. Must match the name of the CustomResourceDefinition (in the form `.`). Must be all lowercase.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "singular": { + SchemaProps: spec.SchemaProps{ + Description: "singular is the singular name of the resource. It must be all lowercase. Defaults to lowercased `kind`.", + Type: []string{"string"}, + Format: "", + }, + }, + "shortNames": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "shortNames are short names for the resource, exposed in API discovery documents, and used by clients to support invocations like `kubectl get `. It must be all lowercase.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "kind is the serialized kind of the resource. It is normally CamelCase and singular. Custom resource instances will use this value as the `kind` attribute in API calls.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "listKind": { + SchemaProps: spec.SchemaProps{ + Description: "listKind is the serialized kind of the list for this resource. Defaults to \"`kind`List\".", + Type: []string{"string"}, + Format: "", + }, + }, + "categories": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "categories is a list of grouped resources this custom resource belongs to (e.g. 'all'). This is published in API discovery documents, and used by clients to support invocations like `kubectl get all`.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + }, + Required: []string{"plural", "kind"}, + }, + }, + } +} + +func schema_pkg_apis_apiextensions_v1_CustomResourceDefinitionSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "CustomResourceDefinitionSpec describes how a user wants their resource to appear", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "group": { + SchemaProps: spec.SchemaProps{ + Description: "group is the API group of the defined custom resource. The custom resources are served under `/apis//...`. Must match the name of the CustomResourceDefinition (in the form `.`).", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "names": { + SchemaProps: spec.SchemaProps{ + Description: "names specify the resource and kind names for the custom resource.", + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.CustomResourceDefinitionNames"), + }, + }, + "scope": { + SchemaProps: spec.SchemaProps{ + Description: "scope indicates whether the defined custom resource is cluster- or namespace-scoped. Allowed values are `Cluster` and `Namespaced`.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "versions": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "versions is the list of all API versions of the defined custom resource. Version names are used to compute the order in which served versions are listed in API discovery. If the version string is \"kube-like\", it will sort above non \"kube-like\" version strings, which are ordered lexicographically. \"Kube-like\" versions start with a \"v\", then are followed by a number (the major version), then optionally the string \"alpha\" or \"beta\" and another number (the minor version). These are sorted first by GA > beta > alpha (where GA is a version with no suffix such as beta or alpha), and then by comparing major version, then minor version. An example sorted list of versions: v10, v2, v1, v11beta2, v10beta3, v3beta1, v12alpha1, v11alpha2, foo1, foo10.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.CustomResourceDefinitionVersion"), + }, + }, + }, + }, + }, + "conversion": { + SchemaProps: spec.SchemaProps{ + Description: "conversion defines conversion settings for the CRD.", + Ref: ref("k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.CustomResourceConversion"), + }, + }, + "preserveUnknownFields": { + SchemaProps: spec.SchemaProps{ + Description: "preserveUnknownFields indicates that object fields which are not specified in the OpenAPI schema should be preserved when persisting to storage. apiVersion, kind, metadata and known fields inside metadata are always preserved. This field is deprecated in favor of setting `x-preserve-unknown-fields` to true in `spec.versions[*].schema.openAPIV3Schema`. See https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definitions/#field-pruning for details.", + Type: []string{"boolean"}, + Format: "", + }, + }, + }, + Required: []string{"group", "names", "scope", "versions"}, + }, + }, + Dependencies: []string{ + "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.CustomResourceConversion", "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.CustomResourceDefinitionNames", "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.CustomResourceDefinitionVersion"}, + } +} + +func schema_pkg_apis_apiextensions_v1_CustomResourceDefinitionStatus(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "CustomResourceDefinitionStatus indicates the state of the CustomResourceDefinition", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "conditions": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-map-keys": []interface{}{ + "type", + }, + "x-kubernetes-list-type": "map", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "conditions indicate state for particular aspects of a CustomResourceDefinition", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.CustomResourceDefinitionCondition"), + }, + }, + }, + }, + }, + "acceptedNames": { + SchemaProps: spec.SchemaProps{ + Description: "acceptedNames are the names that are actually being used to serve discovery. They may be different than the names in spec.", + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.CustomResourceDefinitionNames"), + }, + }, + "storedVersions": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "storedVersions lists all versions of CustomResources that were ever persisted. Tracking these versions allows a migration path for stored versions in etcd. The field is mutable so a migration controller can finish a migration to another version (ensuring no old objects are left in storage), and then remove the rest of the versions from this list. Versions may not be removed from `spec.versions` while they exist in this list.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + }, + }, + }, + Dependencies: []string{ + "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.CustomResourceDefinitionCondition", "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.CustomResourceDefinitionNames"}, + } +} + +func schema_pkg_apis_apiextensions_v1_CustomResourceDefinitionVersion(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "CustomResourceDefinitionVersion describes a version for CRD.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "name": { + SchemaProps: spec.SchemaProps{ + Description: "name is the version name, e.g. “v1”, “v2beta1”, etc. The custom resources are served under this version at `/apis///...` if `served` is true.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "served": { + SchemaProps: spec.SchemaProps{ + Description: "served is a flag enabling/disabling this version from being served via REST APIs", + Default: false, + Type: []string{"boolean"}, + Format: "", + }, + }, + "storage": { + SchemaProps: spec.SchemaProps{ + Description: "storage indicates this version should be used when persisting custom resources to storage. There must be exactly one version with storage=true.", + Default: false, + Type: []string{"boolean"}, + Format: "", + }, + }, + "deprecated": { + SchemaProps: spec.SchemaProps{ + Description: "deprecated indicates this version of the custom resource API is deprecated. When set to true, API requests to this version receive a warning header in the server response. Defaults to false.", + Type: []string{"boolean"}, + Format: "", + }, + }, + "deprecationWarning": { + SchemaProps: spec.SchemaProps{ + Description: "deprecationWarning overrides the default warning returned to API clients. May only be set when `deprecated` is true. The default warning indicates this version is deprecated and recommends use of the newest served version of equal or greater stability, if one exists.", + Type: []string{"string"}, + Format: "", + }, + }, + "schema": { + SchemaProps: spec.SchemaProps{ + Description: "schema describes the schema used for validation, pruning, and defaulting of this version of the custom resource.", + Ref: ref("k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.CustomResourceValidation"), + }, + }, + "subresources": { + SchemaProps: spec.SchemaProps{ + Description: "subresources specify what subresources this version of the defined custom resource have.", + Ref: ref("k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.CustomResourceSubresources"), + }, + }, + "additionalPrinterColumns": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "additionalPrinterColumns specifies additional columns returned in Table output. See https://kubernetes.io/docs/reference/using-api/api-concepts/#receiving-resources-as-tables for details. If no columns are specified, a single column displaying the age of the custom resource is used.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.CustomResourceColumnDefinition"), + }, + }, + }, + }, + }, + "selectableFields": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "selectableFields specifies paths to fields that may be used as field selectors. A maximum of 8 selectable fields are allowed. See https://kubernetes.io/docs/concepts/overview/working-with-objects/field-selectors", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.SelectableField"), + }, + }, + }, + }, + }, + }, + Required: []string{"name", "served", "storage"}, + }, + }, + Dependencies: []string{ + "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.CustomResourceColumnDefinition", "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.CustomResourceSubresources", "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.CustomResourceValidation", "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.SelectableField"}, + } +} + +func schema_pkg_apis_apiextensions_v1_CustomResourceSubresourceScale(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "CustomResourceSubresourceScale defines how to serve the scale subresource for CustomResources.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "specReplicasPath": { + SchemaProps: spec.SchemaProps{ + Description: "specReplicasPath defines the JSON path inside of a custom resource that corresponds to Scale `spec.replicas`. Only JSON paths without the array notation are allowed. Must be a JSON Path under `.spec`. If there is no value under the given path in the custom resource, the `/scale` subresource will return an error on GET.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "statusReplicasPath": { + SchemaProps: spec.SchemaProps{ + Description: "statusReplicasPath defines the JSON path inside of a custom resource that corresponds to Scale `status.replicas`. Only JSON paths without the array notation are allowed. Must be a JSON Path under `.status`. If there is no value under the given path in the custom resource, the `status.replicas` value in the `/scale` subresource will default to 0.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "labelSelectorPath": { + SchemaProps: spec.SchemaProps{ + Description: "labelSelectorPath defines the JSON path inside of a custom resource that corresponds to Scale `status.selector`. Only JSON paths without the array notation are allowed. Must be a JSON Path under `.status` or `.spec`. Must be set to work with HorizontalPodAutoscaler. The field pointed by this JSON path must be a string field (not a complex selector struct) which contains a serialized label selector in string form. More info: https://kubernetes.io/docs/tasks/access-kubernetes-api/custom-resources/custom-resource-definitions#scale-subresource If there is no value under the given path in the custom resource, the `status.selector` value in the `/scale` subresource will default to the empty string.", + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"specReplicasPath", "statusReplicasPath"}, + }, + }, + } +} + +func schema_pkg_apis_apiextensions_v1_CustomResourceSubresourceStatus(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "CustomResourceSubresourceStatus defines how to serve the status subresource for CustomResources. Status is represented by the `.status` JSON path inside of a CustomResource. When set, * exposes a /status subresource for the custom resource * PUT requests to the /status subresource take a custom resource object, and ignore changes to anything except the status stanza * PUT/POST/PATCH requests to the custom resource ignore changes to the status stanza", + Type: []string{"object"}, + }, + }, + } +} + +func schema_pkg_apis_apiextensions_v1_CustomResourceSubresources(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "CustomResourceSubresources defines the status and scale subresources for CustomResources.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "status": { + SchemaProps: spec.SchemaProps{ + Description: "status indicates the custom resource should serve a `/status` subresource. When enabled: 1. requests to the custom resource primary endpoint ignore changes to the `status` stanza of the object. 2. requests to the custom resource `/status` subresource ignore changes to anything other than the `status` stanza of the object.", + Ref: ref("k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.CustomResourceSubresourceStatus"), + }, + }, + "scale": { + SchemaProps: spec.SchemaProps{ + Description: "scale indicates the custom resource should serve a `/scale` subresource that returns an `autoscaling/v1` Scale object.", + Ref: ref("k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.CustomResourceSubresourceScale"), + }, + }, + }, + }, + }, + Dependencies: []string{ + "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.CustomResourceSubresourceScale", "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.CustomResourceSubresourceStatus"}, + } +} + +func schema_pkg_apis_apiextensions_v1_CustomResourceValidation(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "CustomResourceValidation is a list of validation methods for CustomResources.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "openAPIV3Schema": { + SchemaProps: spec.SchemaProps{ + Description: "openAPIV3Schema is the OpenAPI v3 schema to use for validation and pruning.", + Ref: ref("k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.JSONSchemaProps"), + }, + }, + }, + }, + }, + Dependencies: []string{ + "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.JSONSchemaProps"}, + } +} + +func schema_pkg_apis_apiextensions_v1_ExternalDocumentation(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "ExternalDocumentation allows referencing an external resource for extended documentation.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "description": { + SchemaProps: spec.SchemaProps{ + Type: []string{"string"}, + Format: "", + }, + }, + "url": { + SchemaProps: spec.SchemaProps{ + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + } +} + +func schema_pkg_apis_apiextensions_v1_JSON(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "JSON represents any valid JSON value. These types are supported: bool, int64, float64, string, []interface{}, map[string]interface{} and nil.", + Type: v1.JSON{}.OpenAPISchemaType(), + Format: v1.JSON{}.OpenAPISchemaFormat(), + }, + }, + } +} + +func schema_pkg_apis_apiextensions_v1_JSONSchemaProps(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "JSONSchemaProps is a JSON-Schema following Specification Draft 4 (http://json-schema.org/).", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "id": { + SchemaProps: spec.SchemaProps{ + Type: []string{"string"}, + Format: "", + }, + }, + "$schema": { + SchemaProps: spec.SchemaProps{ + Type: []string{"string"}, + Format: "", + }, + }, + "$ref": { + SchemaProps: spec.SchemaProps{ + Type: []string{"string"}, + Format: "", + }, + }, + "description": { + SchemaProps: spec.SchemaProps{ + Type: []string{"string"}, + Format: "", + }, + }, + "type": { + SchemaProps: spec.SchemaProps{ + Type: []string{"string"}, + Format: "", + }, + }, + "format": { + SchemaProps: spec.SchemaProps{ + Description: "format is an OpenAPI v3 format string. Unknown formats are ignored. The following formats are validated:\n\n- bsonobjectid: a bson object ID, i.e. a 24 characters hex string - uri: an URI as parsed by Golang net/url.ParseRequestURI - email: an email address as parsed by Golang net/mail.ParseAddress - hostname: a valid representation for an Internet host name, as defined by RFC 1034, section 3.1 [RFC1034]. - ipv4: an IPv4 IP as parsed by Golang net.ParseIP - ipv6: an IPv6 IP as parsed by Golang net.ParseIP - cidr: a CIDR as parsed by Golang net.ParseCIDR - mac: a MAC address as parsed by Golang net.ParseMAC - uuid: an UUID that allows uppercase defined by the regex (?i)^[0-9a-f]{8}-?[0-9a-f]{4}-?[0-9a-f]{4}-?[0-9a-f]{4}-?[0-9a-f]{12}$ - uuid3: an UUID3 that allows uppercase defined by the regex (?i)^[0-9a-f]{8}-?[0-9a-f]{4}-?3[0-9a-f]{3}-?[0-9a-f]{4}-?[0-9a-f]{12}$ - uuid4: an UUID4 that allows uppercase defined by the regex (?i)^[0-9a-f]{8}-?[0-9a-f]{4}-?4[0-9a-f]{3}-?[89ab][0-9a-f]{3}-?[0-9a-f]{12}$ - uuid5: an UUID5 that allows uppercase defined by the regex (?i)^[0-9a-f]{8}-?[0-9a-f]{4}-?5[0-9a-f]{3}-?[89ab][0-9a-f]{3}-?[0-9a-f]{12}$ - isbn: an ISBN10 or ISBN13 number string like \"0321751043\" or \"978-0321751041\" - isbn10: an ISBN10 number string like \"0321751043\" - isbn13: an ISBN13 number string like \"978-0321751041\" - creditcard: a credit card number defined by the regex ^(?:4[0-9]{12}(?:[0-9]{3})?|5[1-5][0-9]{14}|6(?:011|5[0-9][0-9])[0-9]{12}|3[47][0-9]{13}|3(?:0[0-5]|[68][0-9])[0-9]{11}|(?:2131|1800|35\\\\d{3})\\\\d{11})$ with any non digit characters mixed in - ssn: a U.S. social security number following the regex ^\\\\d{3}[- ]?\\\\d{2}[- ]?\\\\d{4}$ - hexcolor: an hexadecimal color code like \"#FFFFFF: following the regex ^#?([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$ - rgbcolor: an RGB color code like rgb like \"rgb(255,255,2559\" - byte: base64 encoded binary data - password: any kind of string - date: a date string like \"2006-01-02\" as defined by full-date in RFC3339 - duration: a duration string like \"22 ns\" as parsed by Golang time.ParseDuration or compatible with Scala duration format - datetime: a date time string like \"2014-12-15T19:30:20.000Z\" as defined by date-time in RFC3339.", + Type: []string{"string"}, + Format: "", + }, + }, + "title": { + SchemaProps: spec.SchemaProps{ + Type: []string{"string"}, + Format: "", + }, + }, + "default": { + SchemaProps: spec.SchemaProps{ + Description: "default is a default value for undefined object fields. Defaulting is a beta feature under the CustomResourceDefaulting feature gate. Defaulting requires spec.preserveUnknownFields to be false.", + Ref: ref("k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.JSON"), + }, + }, + "maximum": { + SchemaProps: spec.SchemaProps{ + Type: []string{"number"}, + Format: "double", + }, + }, + "exclusiveMaximum": { + SchemaProps: spec.SchemaProps{ + Type: []string{"boolean"}, + Format: "", + }, + }, + "minimum": { + SchemaProps: spec.SchemaProps{ + Type: []string{"number"}, + Format: "double", + }, + }, + "exclusiveMinimum": { + SchemaProps: spec.SchemaProps{ + Type: []string{"boolean"}, + Format: "", + }, + }, + "maxLength": { + SchemaProps: spec.SchemaProps{ + Type: []string{"integer"}, + Format: "int64", + }, + }, + "minLength": { + SchemaProps: spec.SchemaProps{ + Type: []string{"integer"}, + Format: "int64", + }, + }, + "pattern": { + SchemaProps: spec.SchemaProps{ + Type: []string{"string"}, + Format: "", + }, + }, + "maxItems": { + SchemaProps: spec.SchemaProps{ + Type: []string{"integer"}, + Format: "int64", + }, + }, + "minItems": { + SchemaProps: spec.SchemaProps{ + Type: []string{"integer"}, + Format: "int64", + }, + }, + "uniqueItems": { + SchemaProps: spec.SchemaProps{ + Type: []string{"boolean"}, + Format: "", + }, + }, + "multipleOf": { + SchemaProps: spec.SchemaProps{ + Type: []string{"number"}, + Format: "double", + }, + }, + "enum": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Ref: ref("k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.JSON"), + }, + }, + }, + }, + }, + "maxProperties": { + SchemaProps: spec.SchemaProps{ + Type: []string{"integer"}, + Format: "int64", + }, + }, + "minProperties": { + SchemaProps: spec.SchemaProps{ + Type: []string{"integer"}, + Format: "int64", + }, + }, + "required": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + "items": { + SchemaProps: spec.SchemaProps{ + Ref: ref("k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.JSONSchemaPropsOrArray"), + }, + }, + "allOf": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.JSONSchemaProps"), + }, + }, + }, + }, + }, + "oneOf": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.JSONSchemaProps"), + }, + }, + }, + }, + }, + "anyOf": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.JSONSchemaProps"), + }, + }, + }, + }, + }, + "not": { + SchemaProps: spec.SchemaProps{ + Ref: ref("k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.JSONSchemaProps"), + }, + }, + "properties": { + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + AdditionalProperties: &spec.SchemaOrBool{ + Allows: true, + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.JSONSchemaProps"), + }, + }, + }, + }, + }, + "additionalProperties": { + SchemaProps: spec.SchemaProps{ + Ref: ref("k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.JSONSchemaPropsOrBool"), + }, + }, + "patternProperties": { + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + AdditionalProperties: &spec.SchemaOrBool{ + Allows: true, + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.JSONSchemaProps"), + }, + }, + }, + }, + }, + "dependencies": { + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + AdditionalProperties: &spec.SchemaOrBool{ + Allows: true, + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Ref: ref("k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.JSONSchemaPropsOrStringArray"), + }, + }, + }, + }, + }, + "additionalItems": { + SchemaProps: spec.SchemaProps{ + Ref: ref("k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.JSONSchemaPropsOrBool"), + }, + }, + "definitions": { + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + AdditionalProperties: &spec.SchemaOrBool{ + Allows: true, + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.JSONSchemaProps"), + }, + }, + }, + }, + }, + "externalDocs": { + SchemaProps: spec.SchemaProps{ + Ref: ref("k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.ExternalDocumentation"), + }, + }, + "example": { + SchemaProps: spec.SchemaProps{ + Ref: ref("k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.JSON"), + }, + }, + "nullable": { + SchemaProps: spec.SchemaProps{ + Type: []string{"boolean"}, + Format: "", + }, + }, + "x-kubernetes-preserve-unknown-fields": { + SchemaProps: spec.SchemaProps{ + Description: "x-kubernetes-preserve-unknown-fields stops the API server decoding step from pruning fields which are not specified in the validation schema. This affects fields recursively, but switches back to normal pruning behaviour if nested properties or additionalProperties are specified in the schema. This can either be true or undefined. False is forbidden.", + Type: []string{"boolean"}, + Format: "", + }, + }, + "x-kubernetes-embedded-resource": { + SchemaProps: spec.SchemaProps{ + Description: "x-kubernetes-embedded-resource defines that the value is an embedded Kubernetes runtime.Object, with TypeMeta and ObjectMeta. The type must be object. It is allowed to further restrict the embedded object. kind, apiVersion and metadata are validated automatically. x-kubernetes-preserve-unknown-fields is allowed to be true, but does not have to be if the object is fully specified (up to kind, apiVersion, metadata).", + Type: []string{"boolean"}, + Format: "", + }, + }, + "x-kubernetes-int-or-string": { + SchemaProps: spec.SchemaProps{ + Description: "x-kubernetes-int-or-string specifies that this value is either an integer or a string. If this is true, an empty type is allowed and type as child of anyOf is permitted if following one of the following patterns:\n\n1) anyOf:\n - type: integer\n - type: string\n2) allOf:\n - anyOf:\n - type: integer\n - type: string\n - ... zero or more", + Type: []string{"boolean"}, + Format: "", + }, + }, + "x-kubernetes-list-map-keys": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "x-kubernetes-list-map-keys annotates an array with the x-kubernetes-list-type `map` by specifying the keys used as the index of the map.\n\nThis tag MUST only be used on lists that have the \"x-kubernetes-list-type\" extension set to \"map\". Also, the values specified for this attribute must be a scalar typed field of the child structure (no nesting is supported).\n\nThe properties specified must either be required or have a default value, to ensure those properties are present for all list items.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + "x-kubernetes-list-type": { + SchemaProps: spec.SchemaProps{ + Description: "x-kubernetes-list-type annotates an array to further describe its topology. This extension must only be used on lists and may have 3 possible values:\n\n1) `atomic`: the list is treated as a single entity, like a scalar.\n Atomic lists will be entirely replaced when updated. This extension\n may be used on any type of list (struct, scalar, ...).\n2) `set`:\n Sets are lists that must not have multiple items with the same value. Each\n value must be a scalar, an object with x-kubernetes-map-type `atomic` or an\n array with x-kubernetes-list-type `atomic`.\n3) `map`:\n These lists are like maps in that their elements have a non-index key\n used to identify them. Order is preserved upon merge. The map tag\n must only be used on a list with elements of type object.\nDefaults to atomic for arrays.", + Type: []string{"string"}, + Format: "", + }, + }, + "x-kubernetes-map-type": { + SchemaProps: spec.SchemaProps{ + Description: "x-kubernetes-map-type annotates an object to further describe its topology. This extension must only be used when type is object and may have 2 possible values:\n\n1) `granular`:\n These maps are actual maps (key-value pairs) and each fields are independent\n from each other (they can each be manipulated by separate actors). This is\n the default behaviour for all maps.\n2) `atomic`: the list is treated as a single entity, like a scalar.\n Atomic maps will be entirely replaced when updated.", + Type: []string{"string"}, + Format: "", + }, + }, + "x-kubernetes-validations": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-map-keys": []interface{}{ + "rule", + }, + "x-kubernetes-list-type": "map", + "x-kubernetes-patch-merge-key": "rule", + "x-kubernetes-patch-strategy": "merge", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "x-kubernetes-validations describes a list of validation rules written in the CEL expression language.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.ValidationRule"), + }, + }, + }, + }, + }, + }, + }, + }, + Dependencies: []string{ + "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.ExternalDocumentation", "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.JSON", "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.JSONSchemaProps", "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.JSONSchemaPropsOrArray", "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.JSONSchemaPropsOrBool", "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.JSONSchemaPropsOrStringArray", "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.ValidationRule"}, + } +} + +func schema_pkg_apis_apiextensions_v1_JSONSchemaPropsOrArray(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "JSONSchemaPropsOrArray represents a value that can either be a JSONSchemaProps or an array of JSONSchemaProps. Mainly here for serialization purposes.", + Type: v1.JSONSchemaPropsOrArray{}.OpenAPISchemaType(), + Format: v1.JSONSchemaPropsOrArray{}.OpenAPISchemaFormat(), + }, + }, + } +} + +func schema_pkg_apis_apiextensions_v1_JSONSchemaPropsOrBool(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "JSONSchemaPropsOrBool represents JSONSchemaProps or a boolean value. Defaults to true for the boolean property.", + Type: v1.JSONSchemaPropsOrBool{}.OpenAPISchemaType(), + Format: v1.JSONSchemaPropsOrBool{}.OpenAPISchemaFormat(), + }, + }, + } +} + +func schema_pkg_apis_apiextensions_v1_JSONSchemaPropsOrStringArray(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "JSONSchemaPropsOrStringArray represents a JSONSchemaProps or a string array.", + Type: v1.JSONSchemaPropsOrStringArray{}.OpenAPISchemaType(), + Format: v1.JSONSchemaPropsOrStringArray{}.OpenAPISchemaFormat(), + }, + }, + } +} + +func schema_pkg_apis_apiextensions_v1_SelectableField(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "SelectableField specifies the JSON path of a field that may be used with field selectors.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "jsonPath": { + SchemaProps: spec.SchemaProps{ + Description: "jsonPath is a simple JSON path which is evaluated against each custom resource to produce a field selector value. Only JSON paths without the array notation are allowed. Must point to a field of type string, boolean or integer. Types with enum values and strings with formats are allowed. If jsonPath refers to absent field in a resource, the jsonPath evaluates to an empty string. Must not point to metdata fields. Required.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"jsonPath"}, + }, + }, + } +} + +func schema_pkg_apis_apiextensions_v1_ServiceReference(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "ServiceReference holds a reference to Service.legacy.k8s.io", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "namespace": { + SchemaProps: spec.SchemaProps{ + Description: "namespace is the namespace of the service. Required", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "name": { + SchemaProps: spec.SchemaProps{ + Description: "name is the name of the service. Required", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "path": { + SchemaProps: spec.SchemaProps{ + Description: "path is an optional URL path at which the webhook will be contacted.", + Type: []string{"string"}, + Format: "", + }, + }, + "port": { + SchemaProps: spec.SchemaProps{ + Description: "port is an optional service port at which the webhook will be contacted. `port` should be a valid port number (1-65535, inclusive). Defaults to 443 for backward compatibility.", + Type: []string{"integer"}, + Format: "int32", + }, + }, + }, + Required: []string{"namespace", "name"}, + }, + }, + } +} + +func schema_pkg_apis_apiextensions_v1_ValidationRule(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "ValidationRule describes a validation rule written in the CEL expression language.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "rule": { + SchemaProps: spec.SchemaProps{ + Description: "Rule represents the expression which will be evaluated by CEL. ref: https://github.com/google/cel-spec The Rule is scoped to the location of the x-kubernetes-validations extension in the schema. The `self` variable in the CEL expression is bound to the scoped value. Example: - Rule scoped to the root of a resource with a status subresource: {\"rule\": \"self.status.actual <= self.spec.maxDesired\"}\n\nIf the Rule is scoped to an object with properties, the accessible properties of the object are field selectable via `self.field` and field presence can be checked via `has(self.field)`. Null valued fields are treated as absent fields in CEL expressions. If the Rule is scoped to an object with additionalProperties (i.e. a map) the value of the map are accessible via `self[mapKey]`, map containment can be checked via `mapKey in self` and all entries of the map are accessible via CEL macros and functions such as `self.all(...)`. If the Rule is scoped to an array, the elements of the array are accessible via `self[i]` and also by macros and functions. If the Rule is scoped to a scalar, `self` is bound to the scalar value. Examples: - Rule scoped to a map of objects: {\"rule\": \"self.components['Widget'].priority < 10\"} - Rule scoped to a list of integers: {\"rule\": \"self.values.all(value, value >= 0 && value < 100)\"} - Rule scoped to a string value: {\"rule\": \"self.startsWith('kube')\"}\n\nThe `apiVersion`, `kind`, `metadata.name` and `metadata.generateName` are always accessible from the root of the object and from any x-kubernetes-embedded-resource annotated objects. No other metadata properties are accessible.\n\nUnknown data preserved in custom resources via x-kubernetes-preserve-unknown-fields is not accessible in CEL expressions. This includes: - Unknown field values that are preserved by object schemas with x-kubernetes-preserve-unknown-fields. - Object properties where the property schema is of an \"unknown type\". An \"unknown type\" is recursively defined as:\n - A schema with no type and x-kubernetes-preserve-unknown-fields set to true\n - An array where the items schema is of an \"unknown type\"\n - An object where the additionalProperties schema is of an \"unknown type\"\n\nOnly property names of the form `[a-zA-Z_.-/][a-zA-Z0-9_.-/]*` are accessible. Accessible property names are escaped according to the following rules when accessed in the expression: - '__' escapes to '__underscores__' - '.' escapes to '__dot__' - '-' escapes to '__dash__' - '/' escapes to '__slash__' - Property names that exactly match a CEL RESERVED keyword escape to '__{keyword}__'. The keywords are:\n\t \"true\", \"false\", \"null\", \"in\", \"as\", \"break\", \"const\", \"continue\", \"else\", \"for\", \"function\", \"if\",\n\t \"import\", \"let\", \"loop\", \"package\", \"namespace\", \"return\".\nExamples:\n - Rule accessing a property named \"namespace\": {\"rule\": \"self.__namespace__ > 0\"}\n - Rule accessing a property named \"x-prop\": {\"rule\": \"self.x__dash__prop > 0\"}\n - Rule accessing a property named \"redact__d\": {\"rule\": \"self.redact__underscores__d > 0\"}\n\nEquality on arrays with x-kubernetes-list-type of 'set' or 'map' ignores element order, i.e. [1, 2] == [2, 1]. Concatenation on arrays with x-kubernetes-list-type use the semantics of the list type:\n - 'set': `X + Y` performs a union where the array positions of all elements in `X` are preserved and\n non-intersecting elements in `Y` are appended, retaining their partial order.\n - 'map': `X + Y` performs a merge where the array positions of all keys in `X` are preserved but the values\n are overwritten by values in `Y` when the key sets of `X` and `Y` intersect. Elements in `Y` with\n non-intersecting keys are appended, retaining their partial order.\n\nIf `rule` makes use of the `oldSelf` variable it is implicitly a `transition rule`.\n\nBy default, the `oldSelf` variable is the same type as `self`. When `optionalOldSelf` is true, the `oldSelf` variable is a CEL optional\n variable whose value() is the same type as `self`.\nSee the documentation for the `optionalOldSelf` field for details.\n\nTransition rules by default are applied only on UPDATE requests and are skipped if an old value could not be found. You can opt a transition rule into unconditional evaluation by setting `optionalOldSelf` to true.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "message": { + SchemaProps: spec.SchemaProps{ + Description: "Message represents the message displayed when validation fails. The message is required if the Rule contains line breaks. The message must not contain line breaks. If unset, the message is \"failed rule: {Rule}\". e.g. \"must be a URL with the host matching spec.host\"", + Type: []string{"string"}, + Format: "", + }, + }, + "messageExpression": { + SchemaProps: spec.SchemaProps{ + Description: "MessageExpression declares a CEL expression that evaluates to the validation failure message that is returned when this rule fails. Since messageExpression is used as a failure message, it must evaluate to a string. If both message and messageExpression are present on a rule, then messageExpression will be used if validation fails. If messageExpression results in a runtime error, the runtime error is logged, and the validation failure message is produced as if the messageExpression field were unset. If messageExpression evaluates to an empty string, a string with only spaces, or a string that contains line breaks, then the validation failure message will also be produced as if the messageExpression field were unset, and the fact that messageExpression produced an empty string/string with only spaces/string with line breaks will be logged. messageExpression has access to all the same variables as the rule; the only difference is the return type. Example: \"x must be less than max (\"+string(self.max)+\")\"", + Type: []string{"string"}, + Format: "", + }, + }, + "reason": { + SchemaProps: spec.SchemaProps{ + Description: "reason provides a machine-readable validation failure reason that is returned to the caller when a request fails this validation rule. The HTTP status code returned to the caller will match the reason of the reason of the first failed validation rule. The currently supported reasons are: \"FieldValueInvalid\", \"FieldValueForbidden\", \"FieldValueRequired\", \"FieldValueDuplicate\". If not set, default to use \"FieldValueInvalid\". All future added reasons must be accepted by clients when reading this value and unknown reasons should be treated as FieldValueInvalid.\n\nPossible enum values:\n - `\"FieldValueDuplicate\"` is used to report collisions of values that must be unique (e.g. unique IDs).\n - `\"FieldValueForbidden\"` is used to report valid (as per formatting rules) values which would be accepted under some conditions, but which are not permitted by the current conditions (such as security policy).\n - `\"FieldValueInvalid\"` is used to report malformed values (e.g. failed regex match, too long, out of bounds).\n - `\"FieldValueRequired\"` is used to report required values that are not provided (e.g. empty strings, null values, or empty arrays).", + Type: []string{"string"}, + Format: "", + Enum: []interface{}{"FieldValueDuplicate", "FieldValueForbidden", "FieldValueInvalid", "FieldValueRequired"}, + }, + }, + "fieldPath": { + SchemaProps: spec.SchemaProps{ + Description: "fieldPath represents the field path returned when the validation fails. It must be a relative JSON path (i.e. with array notation) scoped to the location of this x-kubernetes-validations extension in the schema and refer to an existing field. e.g. when validation checks if a specific attribute `foo` under a map `testMap`, the fieldPath could be set to `.testMap.foo` If the validation checks two lists must have unique attributes, the fieldPath could be set to either of the list: e.g. `.testList` It does not support list numeric index. It supports child operation to refer to an existing field currently. Refer to [JSONPath support in Kubernetes](https://kubernetes.io/docs/reference/kubectl/jsonpath/) for more info. Numeric index of array is not supported. For field name which contains special characters, use `['specialName']` to refer the field name. e.g. for attribute `foo.34$` appears in a list `testList`, the fieldPath could be set to `.testList['foo.34$']`", + Type: []string{"string"}, + Format: "", + }, + }, + "optionalOldSelf": { + SchemaProps: spec.SchemaProps{ + Description: "optionalOldSelf is used to opt a transition rule into evaluation even when the object is first created, or if the old object is missing the value.\n\nWhen enabled `oldSelf` will be a CEL optional whose value will be `None` if there is no old value, or when the object is initially created.\n\nYou may check for presence of oldSelf using `oldSelf.hasValue()` and unwrap it after checking using `oldSelf.value()`. Check the CEL documentation for Optional types for more information: https://pkg.go.dev/github.com/google/cel-go/cel#OptionalTypes\n\nMay not be set unless `oldSelf` is used in `rule`.", + Type: []string{"boolean"}, + Format: "", + }, + }, + }, + Required: []string{"rule"}, + }, + }, + } +} + +func schema_pkg_apis_apiextensions_v1_WebhookClientConfig(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "WebhookClientConfig contains the information to make a TLS connection with the webhook.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "url": { + SchemaProps: spec.SchemaProps{ + Description: "url gives the location of the webhook, in standard URL form (`scheme://host:port/path`). Exactly one of `url` or `service` must be specified.\n\nThe `host` should not refer to a service running in the cluster; use the `service` field instead. The host might be resolved via external DNS in some apiservers (e.g., `kube-apiserver` cannot resolve in-cluster DNS as that would be a layering violation). `host` may also be an IP address.\n\nPlease note that using `localhost` or `127.0.0.1` as a `host` is risky unless you take great care to run this webhook on all hosts which run an apiserver which might need to make calls to this webhook. Such installs are likely to be non-portable, i.e., not easy to turn up in a new cluster.\n\nThe scheme must be \"https\"; the URL must begin with \"https://\".\n\nA path is optional, and if present may be any string permissible in a URL. You may use the path to pass an arbitrary string to the webhook, for example, a cluster identifier.\n\nAttempting to use a user or basic auth e.g. \"user:password@\" is not allowed. Fragments (\"#...\") and query parameters (\"?...\") are not allowed, either.", + Type: []string{"string"}, + Format: "", + }, + }, + "service": { + SchemaProps: spec.SchemaProps{ + Description: "service is a reference to the service for this webhook. Either service or url must be specified.\n\nIf the webhook is running within the cluster, then you should use `service`.", + Ref: ref("k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.ServiceReference"), + }, + }, + "caBundle": { + SchemaProps: spec.SchemaProps{ + Description: "caBundle is a PEM encoded CA bundle which will be used to validate the webhook's server certificate. If unspecified, system trust roots on the apiserver are used.", + Type: []string{"string"}, + Format: "byte", + }, + }, + }, + }, + }, + Dependencies: []string{ + "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.ServiceReference"}, + } +} + +func schema_pkg_apis_apiextensions_v1_WebhookConversion(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "WebhookConversion describes how to call a conversion webhook", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "clientConfig": { + SchemaProps: spec.SchemaProps{ + Description: "clientConfig is the instructions for how to call the webhook if strategy is `Webhook`.", + Ref: ref("k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.WebhookClientConfig"), + }, + }, + "conversionReviewVersions": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "conversionReviewVersions is an ordered list of preferred `ConversionReview` versions the Webhook expects. The API server will use the first version in the list which it supports. If none of the versions specified in this list are supported by API server, conversion will fail for the custom resource. If a persisted Webhook configuration specifies allowed versions and does not include any versions known to the API Server, calls to the webhook will fail.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + }, + Required: []string{"conversionReviewVersions"}, + }, + }, + Dependencies: []string{ + "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1.WebhookClientConfig"}, + } +} + +func schema_pkg_apis_meta_v1_APIGroup(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "APIGroup contains the name, the supported versions, and the preferred version of a group.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", + }, + }, + "name": { + SchemaProps: spec.SchemaProps{ + Description: "name is the name of the group.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "versions": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "versions are the versions supported in this group.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.GroupVersionForDiscovery"), + }, + }, + }, + }, + }, + "preferredVersion": { + SchemaProps: spec.SchemaProps{ + Description: "preferredVersion is the version preferred by the API server, which probably is the storage version.", + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.GroupVersionForDiscovery"), + }, + }, + "serverAddressByClientCIDRs": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "a map of client CIDR to server address that is serving this group. This is to help clients reach servers in the most network-efficient way possible. Clients can use the appropriate server address as per the CIDR that they match. In case of multiple matches, clients should use the longest matching CIDR. The server returns only those CIDRs that it thinks that the client can match. For example: the master will return an internal IP CIDR only, if the client reaches the server using an internal IP. Server looks at X-Forwarded-For header or X-Real-Ip header or request.RemoteAddr (in that order) to get the client IP.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ServerAddressByClientCIDR"), + }, + }, + }, + }, + }, + }, + Required: []string{"name", "versions"}, + }, + }, + Dependencies: []string{ + "k8s.io/apimachinery/pkg/apis/meta/v1.GroupVersionForDiscovery", "k8s.io/apimachinery/pkg/apis/meta/v1.ServerAddressByClientCIDR"}, + } +} + +func schema_pkg_apis_meta_v1_APIGroupList(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "APIGroupList is a list of APIGroup, to allow clients to discover the API at /apis.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", + }, + }, + "groups": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "groups is a list of APIGroup.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.APIGroup"), + }, + }, + }, + }, + }, + }, + Required: []string{"groups"}, + }, + }, + Dependencies: []string{ + "k8s.io/apimachinery/pkg/apis/meta/v1.APIGroup"}, + } +} + +func schema_pkg_apis_meta_v1_APIResource(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "APIResource specifies the name of a resource and whether it is namespaced.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "name": { + SchemaProps: spec.SchemaProps{ + Description: "name is the plural name of the resource.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "singularName": { + SchemaProps: spec.SchemaProps{ + Description: "singularName is the singular name of the resource. This allows clients to handle plural and singular opaquely. The singularName is more correct for reporting status on a single item and both singular and plural are allowed from the kubectl CLI interface.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "namespaced": { + SchemaProps: spec.SchemaProps{ + Description: "namespaced indicates if a resource is namespaced or not.", + Default: false, + Type: []string{"boolean"}, + Format: "", + }, + }, + "group": { + SchemaProps: spec.SchemaProps{ + Description: "group is the preferred group of the resource. Empty implies the group of the containing resource list. For subresources, this may have a different value, for example: Scale\".", + Type: []string{"string"}, + Format: "", + }, + }, + "version": { + SchemaProps: spec.SchemaProps{ + Description: "version is the preferred version of the resource. Empty implies the version of the containing resource list For subresources, this may have a different value, for example: v1 (while inside a v1beta1 version of the core resource's group)\".", + Type: []string{"string"}, + Format: "", + }, + }, + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "kind is the kind for the resource (e.g. 'Foo' is the kind for a resource 'foo')", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "verbs": { + SchemaProps: spec.SchemaProps{ + Description: "verbs is a list of supported kube verbs (this includes get, list, watch, create, update, patch, delete, deletecollection, and proxy)", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + "shortNames": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "shortNames is a list of suggested short names of the resource.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + "categories": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "categories is a list of the grouped resources this resource belongs to (e.g. 'all')", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + "storageVersionHash": { + SchemaProps: spec.SchemaProps{ + Description: "The hash value of the storage version, the version this resource is converted to when written to the data store. Value must be treated as opaque by clients. Only equality comparison on the value is valid. This is an alpha feature and may change or be removed in the future. The field is populated by the apiserver only if the StorageVersionHash feature gate is enabled. This field will remain optional even if it graduates.", + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"name", "singularName", "namespaced", "kind", "verbs"}, + }, + }, + } +} + +func schema_pkg_apis_meta_v1_APIResourceList(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "APIResourceList is a list of APIResource, it is used to expose the name of the resources supported in a specific group and version, and if the resource is namespaced.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", + }, + }, + "groupVersion": { + SchemaProps: spec.SchemaProps{ + Description: "groupVersion is the group and version this APIResourceList is for.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "resources": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "resources contains the name of the resources and if they are namespaced.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.APIResource"), + }, + }, + }, + }, + }, + }, + Required: []string{"groupVersion", "resources"}, + }, + }, + Dependencies: []string{ + "k8s.io/apimachinery/pkg/apis/meta/v1.APIResource"}, + } +} + +func schema_pkg_apis_meta_v1_APIVersions(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "APIVersions lists the versions that are available, to allow clients to discover the API at /api, which is the root path of the legacy v1 API.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", + }, + }, + "versions": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "versions are the api versions that are available.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + "serverAddressByClientCIDRs": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "a map of client CIDR to server address that is serving this group. This is to help clients reach servers in the most network-efficient way possible. Clients can use the appropriate server address as per the CIDR that they match. In case of multiple matches, clients should use the longest matching CIDR. The server returns only those CIDRs that it thinks that the client can match. For example: the master will return an internal IP CIDR only, if the client reaches the server using an internal IP. Server looks at X-Forwarded-For header or X-Real-Ip header or request.RemoteAddr (in that order) to get the client IP.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ServerAddressByClientCIDR"), + }, + }, + }, + }, + }, + }, + Required: []string{"versions", "serverAddressByClientCIDRs"}, + }, + }, + Dependencies: []string{ + "k8s.io/apimachinery/pkg/apis/meta/v1.ServerAddressByClientCIDR"}, + } +} + +func schema_pkg_apis_meta_v1_ApplyOptions(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "ApplyOptions may be provided when applying an API object. FieldManager is required for apply requests. ApplyOptions is equivalent to PatchOptions. It is provided as a convenience with documentation that speaks specifically to how the options fields relate to apply.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", + }, + }, + "dryRun": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + "force": { + SchemaProps: spec.SchemaProps{ + Description: "Force is going to \"force\" Apply requests. It means user will re-acquire conflicting fields owned by other people.", + Default: false, + Type: []string{"boolean"}, + Format: "", + }, + }, + "fieldManager": { + SchemaProps: spec.SchemaProps{ + Description: "fieldManager is a name associated with the actor or entity that is making these changes. The value must be less than or 128 characters long, and only contain printable characters, as defined by https://golang.org/pkg/unicode/#IsPrint. This field is required.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"force", "fieldManager"}, + }, + }, + } +} + +func schema_pkg_apis_meta_v1_Condition(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "Condition contains details for one aspect of the current state of this API Resource.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "type": { + SchemaProps: spec.SchemaProps{ + Description: "type of condition in CamelCase or in foo.example.com/CamelCase.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "status": { + SchemaProps: spec.SchemaProps{ + Description: "status of the condition, one of True, False, Unknown.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "observedGeneration": { + SchemaProps: spec.SchemaProps{ + Description: "observedGeneration represents the .metadata.generation that the condition was set based upon. For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date with respect to the current state of the instance.", + Type: []string{"integer"}, + Format: "int64", + }, + }, + "lastTransitionTime": { + SchemaProps: spec.SchemaProps{ + Description: "lastTransitionTime is the last time the condition transitioned from one status to another. This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable.", + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.Time"), + }, + }, + "reason": { + SchemaProps: spec.SchemaProps{ + Description: "reason contains a programmatic identifier indicating the reason for the condition's last transition. Producers of specific condition types may define expected values and meanings for this field, and whether the values are considered a guaranteed API. The value should be a CamelCase string. This field may not be empty.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "message": { + SchemaProps: spec.SchemaProps{ + Description: "message is a human readable message indicating details about the transition. This may be an empty string.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"type", "status", "lastTransitionTime", "reason", "message"}, + }, + }, + Dependencies: []string{ + "k8s.io/apimachinery/pkg/apis/meta/v1.Time"}, + } +} + +func schema_pkg_apis_meta_v1_CreateOptions(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "CreateOptions may be provided when creating an API object.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", + }, + }, + "dryRun": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + "fieldManager": { + SchemaProps: spec.SchemaProps{ + Description: "fieldManager is a name associated with the actor or entity that is making these changes. The value must be less than or 128 characters long, and only contain printable characters, as defined by https://golang.org/pkg/unicode/#IsPrint.", + Type: []string{"string"}, + Format: "", + }, + }, + "fieldValidation": { + SchemaProps: spec.SchemaProps{ + Description: "fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default in v1.23+ - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered.", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + } +} + +func schema_pkg_apis_meta_v1_DeleteOptions(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "DeleteOptions may be provided when deleting an API object.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", + }, + }, + "gracePeriodSeconds": { + SchemaProps: spec.SchemaProps{ + Description: "The duration in seconds before the object should be deleted. Value must be non-negative integer. The value zero indicates delete immediately. If this value is nil, the default grace period for the specified type will be used. Defaults to a per object value if not specified. zero means delete immediately.", + Type: []string{"integer"}, + Format: "int64", + }, + }, + "preconditions": { + SchemaProps: spec.SchemaProps{ + Description: "Must be fulfilled before a deletion is carried out. If not possible, a 409 Conflict status will be returned.", + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.Preconditions"), + }, + }, + "orphanDependents": { + SchemaProps: spec.SchemaProps{ + Description: "Deprecated: please use the PropagationPolicy, this field will be deprecated in 1.7. Should the dependent objects be orphaned. If true/false, the \"orphan\" finalizer will be added to/removed from the object's finalizers list. Either this field or PropagationPolicy may be set, but not both.", + Type: []string{"boolean"}, + Format: "", + }, + }, + "propagationPolicy": { + SchemaProps: spec.SchemaProps{ + Description: "Whether and how garbage collection will be performed. Either this field or OrphanDependents may be set, but not both. The default policy is decided by the existing finalizer set in the metadata.finalizers and the resource-specific default policy. Acceptable values are: 'Orphan' - orphan the dependents; 'Background' - allow the garbage collector to delete the dependents in the background; 'Foreground' - a cascading policy that deletes all dependents in the foreground.", + Type: []string{"string"}, + Format: "", + }, + }, + "dryRun": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + "ignoreStoreReadErrorWithClusterBreakingPotential": { + SchemaProps: spec.SchemaProps{ + Description: "if set to true, it will trigger an unsafe deletion of the resource in case the normal deletion flow fails with a corrupt object error. A resource is considered corrupt if it can not be retrieved from the underlying storage successfully because of a) its data can not be transformed e.g. decryption failure, or b) it fails to decode into an object. NOTE: unsafe deletion ignores finalizer constraints, skips precondition checks, and removes the object from the storage. WARNING: This may potentially break the cluster if the workload associated with the resource being unsafe-deleted relies on normal deletion flow. Use only if you REALLY know what you are doing. The default value is false, and the user must opt in to enable it", + Type: []string{"boolean"}, + Format: "", + }, + }, + }, + }, + }, + Dependencies: []string{ + "k8s.io/apimachinery/pkg/apis/meta/v1.Preconditions"}, + } +} + +func schema_pkg_apis_meta_v1_Duration(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "Duration is a wrapper around time.Duration which supports correct marshaling to YAML and JSON. In particular, it marshals into strings, which can be used as map keys in json.", + Type: metav1.Duration{}.OpenAPISchemaType(), + Format: metav1.Duration{}.OpenAPISchemaFormat(), + }, + }, + } +} + +func schema_pkg_apis_meta_v1_FieldSelectorRequirement(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "FieldSelectorRequirement is a selector that contains values, a key, and an operator that relates the key and values.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "key": { + SchemaProps: spec.SchemaProps{ + Description: "key is the field selector key that the requirement applies to.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "operator": { + SchemaProps: spec.SchemaProps{ + Description: "operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists, DoesNotExist. The list of operators may grow in the future.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "values": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + }, + Required: []string{"key", "operator"}, + }, + }, + } +} + +func schema_pkg_apis_meta_v1_FieldsV1(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "FieldsV1 stores a set of fields in a data structure like a Trie, in JSON format.\n\nEach key is either a '.' representing the field itself, and will always map to an empty set, or a string representing a sub-field or item. The string will follow one of these four formats: 'f:', where is the name of a field in a struct, or key in a map 'v:', where is the exact json formatted value of a list item 'i:', where is position of a item in a list 'k:', where is a map of a list item's key fields to their unique values If a key maps to an empty Fields value, the field that key represents is part of the set.\n\nThe exact format is defined in sigs.k8s.io/structured-merge-diff", + Type: []string{"object"}, + }, + }, + } +} + +func schema_pkg_apis_meta_v1_GetOptions(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "GetOptions is the standard query options to the standard REST get call.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", + }, + }, + "resourceVersion": { + SchemaProps: spec.SchemaProps{ + Description: "resourceVersion sets a constraint on what resource versions a request may be served from. See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for details.\n\nDefaults to unset", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + } +} + +func schema_pkg_apis_meta_v1_GroupKind(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "GroupKind specifies a Group and a Kind, but does not force a version. This is useful for identifying concepts during lookup stages without having partially valid types", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "group": { + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "kind": { + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"group", "kind"}, + }, + }, + } +} + +func schema_pkg_apis_meta_v1_GroupResource(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "GroupResource specifies a Group and a Resource, but does not force a version. This is useful for identifying concepts during lookup stages without having partially valid types", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "group": { + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "resource": { + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"group", "resource"}, + }, + }, + } +} + +func schema_pkg_apis_meta_v1_GroupVersion(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "GroupVersion contains the \"group\" and the \"version\", which uniquely identifies the API.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "group": { + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "version": { + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"group", "version"}, + }, + }, + } +} + +func schema_pkg_apis_meta_v1_GroupVersionForDiscovery(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "GroupVersion contains the \"group/version\" and \"version\" string of a version. It is made a struct to keep extensibility.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "groupVersion": { + SchemaProps: spec.SchemaProps{ + Description: "groupVersion specifies the API group and version in the form \"group/version\"", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "version": { + SchemaProps: spec.SchemaProps{ + Description: "version specifies the version in the form of \"version\". This is to save the clients the trouble of splitting the GroupVersion.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"groupVersion", "version"}, + }, + }, + } +} + +func schema_pkg_apis_meta_v1_GroupVersionKind(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "GroupVersionKind unambiguously identifies a kind. It doesn't anonymously include GroupVersion to avoid automatic coercion. It doesn't use a GroupVersion to avoid custom marshalling", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "group": { + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "version": { + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "kind": { + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"group", "version", "kind"}, + }, + }, + } +} + +func schema_pkg_apis_meta_v1_GroupVersionResource(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "GroupVersionResource unambiguously identifies a resource. It doesn't anonymously include GroupVersion to avoid automatic coercion. It doesn't use a GroupVersion to avoid custom marshalling", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "group": { + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "version": { + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "resource": { + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"group", "version", "resource"}, + }, + }, + } +} + +func schema_pkg_apis_meta_v1_InternalEvent(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "InternalEvent makes watch.Event versioned", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "Type": { + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "Object": { + SchemaProps: spec.SchemaProps{ + Description: "Object is:\n * If Type is Added or Modified: the new state of the object.\n * If Type is Deleted: the state of the object immediately before deletion.\n * If Type is Bookmark: the object (instance of a type being watched) where\n only ResourceVersion field is set. On successful restart of watch from a\n bookmark resourceVersion, client is guaranteed to not get repeat event\n nor miss any events.\n * If Type is Error: *api.Status is recommended; other types may make sense\n depending on context.", + }, + }, + }, + Required: []string{"Type", "Object"}, + }, + }, + } +} + +func schema_pkg_apis_meta_v1_LabelSelector(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "A label selector is a label query over a set of resources. The result of matchLabels and matchExpressions are ANDed. An empty label selector matches all objects. A null label selector matches no objects.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "matchLabels": { + SchemaProps: spec.SchemaProps{ + Description: "matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is \"key\", the operator is \"In\", and the values array contains only \"value\". The requirements are ANDed.", + Type: []string{"object"}, + AdditionalProperties: &spec.SchemaOrBool{ + Allows: true, + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + "matchExpressions": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "matchExpressions is a list of label selector requirements. The requirements are ANDed.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.LabelSelectorRequirement"), + }, + }, + }, + }, + }, + }, + }, + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-map-type": "atomic", + }, + }, + }, + Dependencies: []string{ + "k8s.io/apimachinery/pkg/apis/meta/v1.LabelSelectorRequirement"}, + } +} + +func schema_pkg_apis_meta_v1_LabelSelectorRequirement(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "key": { + SchemaProps: spec.SchemaProps{ + Description: "key is the label key that the selector applies to.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "operator": { + SchemaProps: spec.SchemaProps{ + Description: "operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "values": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + }, + Required: []string{"key", "operator"}, + }, + }, + } +} + +func schema_pkg_apis_meta_v1_List(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "List holds a list of objects, which may not be known by the server.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", + }, + }, + "metadata": { + SchemaProps: spec.SchemaProps{ + Description: "Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"), + }, + }, + "items": { + SchemaProps: spec.SchemaProps{ + Description: "List of objects", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Ref: ref("k8s.io/apimachinery/pkg/runtime.RawExtension"), + }, + }, + }, + }, + }, + }, + Required: []string{"items"}, + }, + }, + Dependencies: []string{ + "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta", "k8s.io/apimachinery/pkg/runtime.RawExtension"}, + } +} + +func schema_pkg_apis_meta_v1_ListMeta(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "ListMeta describes metadata that synthetic resources must have, including lists and various status objects. A resource may have only one of {ObjectMeta, ListMeta}.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "selfLink": { + SchemaProps: spec.SchemaProps{ + Description: "Deprecated: selfLink is a legacy read-only field that is no longer populated by the system.", + Type: []string{"string"}, + Format: "", + }, + }, + "resourceVersion": { + SchemaProps: spec.SchemaProps{ + Description: "String that identifies the server's internal version of this object that can be used by clients to determine when objects have changed. Value must be treated as opaque by clients and passed unmodified back to the server. Populated by the system. Read-only. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency", + Type: []string{"string"}, + Format: "", + }, + }, + "continue": { + SchemaProps: spec.SchemaProps{ + Description: "continue may be set if the user set a limit on the number of items returned, and indicates that the server has more data available. The value is opaque and may be used to issue another request to the endpoint that served this list to retrieve the next set of available objects. Continuing a consistent list may not be possible if the server configuration has changed or more than a few minutes have passed. The resourceVersion field returned when using this continue value will be identical to the value in the first response, unless you have received this token from an error message.", + Type: []string{"string"}, + Format: "", + }, + }, + "remainingItemCount": { + SchemaProps: spec.SchemaProps{ + Description: "remainingItemCount is the number of subsequent items in the list which are not included in this list response. If the list request contained label or field selectors, then the number of remaining items is unknown and the field will be left unset and omitted during serialization. If the list is complete (either because it is not chunking or because this is the last chunk), then there are no more remaining items and this field will be left unset and omitted during serialization. Servers older than v1.15 do not set this field. The intended use of the remainingItemCount is *estimating* the size of a collection. Clients should not rely on the remainingItemCount to be set or to be exact.", + Type: []string{"integer"}, + Format: "int64", + }, + }, + }, + }, + }, + } +} + +func schema_pkg_apis_meta_v1_ListOptions(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "ListOptions is the query options to a standard REST list call.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", + }, + }, + "labelSelector": { + SchemaProps: spec.SchemaProps{ + Description: "A selector to restrict the list of returned objects by their labels. Defaults to everything.", + Type: []string{"string"}, + Format: "", + }, + }, + "fieldSelector": { + SchemaProps: spec.SchemaProps{ + Description: "A selector to restrict the list of returned objects by their fields. Defaults to everything.", + Type: []string{"string"}, + Format: "", + }, + }, + "watch": { + SchemaProps: spec.SchemaProps{ + Description: "Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion.", + Type: []string{"boolean"}, + Format: "", + }, + }, + "allowWatchBookmarks": { + SchemaProps: spec.SchemaProps{ + Description: "allowWatchBookmarks requests watch events with type \"BOOKMARK\". Servers that do not implement bookmarks may ignore this flag and bookmarks are sent at the server's discretion. Clients should not assume bookmarks are returned at any specific interval, nor may they assume the server will send any BOOKMARK event during a session. If this is not a watch, this field is ignored.", + Type: []string{"boolean"}, + Format: "", + }, + }, + "resourceVersion": { + SchemaProps: spec.SchemaProps{ + Description: "resourceVersion sets a constraint on what resource versions a request may be served from. See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for details.\n\nDefaults to unset", + Type: []string{"string"}, + Format: "", + }, + }, + "resourceVersionMatch": { + SchemaProps: spec.SchemaProps{ + Description: "resourceVersionMatch determines how resourceVersion is applied to list calls. It is highly recommended that resourceVersionMatch be set for list calls where resourceVersion is set See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for details.\n\nDefaults to unset", + Type: []string{"string"}, + Format: "", + }, + }, + "timeoutSeconds": { + SchemaProps: spec.SchemaProps{ + Description: "Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity.", + Type: []string{"integer"}, + Format: "int64", + }, + }, + "limit": { + SchemaProps: spec.SchemaProps{ + Description: "limit is a maximum number of responses to return for a list call. If more items exist, the server will set the `continue` field on the list metadata to a value that can be used with the same initial query to retrieve the next set of results. Setting a limit may return fewer than the requested amount of items (up to zero items) in the event all requested objects are filtered out and clients should only use the presence of the continue field to determine whether more results are available. Servers may choose not to support the limit argument and will return all of the available results. If limit is specified and the continue field is empty, clients may assume that no more results are available. This field is not supported if watch is true.\n\nThe server guarantees that the objects returned when using continue will be identical to issuing a single list call without a limit - that is, no objects created, modified, or deleted after the first request is issued will be included in any subsequent continued requests. This is sometimes referred to as a consistent snapshot, and ensures that a client that is using limit to receive smaller chunks of a very large result can ensure they see all possible objects. If objects are updated during a chunked list the version of the object that was present at the time the first list result was calculated is returned.", + Type: []string{"integer"}, + Format: "int64", + }, + }, + "continue": { + SchemaProps: spec.SchemaProps{ + Description: "The continue option should be set when retrieving more results from the server. Since this value is server defined, clients may only use the continue value from a previous query result with identical query parameters (except for the value of continue) and the server may reject a continue value it does not recognize. If the specified continue value is no longer valid whether due to expiration (generally five to fifteen minutes) or a configuration change on the server, the server will respond with a 410 ResourceExpired error together with a continue token. If the client needs a consistent list, it must restart their list without the continue field. Otherwise, the client may send another list request with the token received with the 410 error, the server will respond with a list starting from the next key, but from the latest snapshot, which is inconsistent from the previous list results - objects that are created, modified, or deleted after the first list request will be included in the response, as long as their keys are after the \"next key\".\n\nThis field is not supported when watch is true. Clients may start a watch from the last resourceVersion value returned by the server and not miss any modifications.", + Type: []string{"string"}, + Format: "", + }, + }, + "sendInitialEvents": { + SchemaProps: spec.SchemaProps{ + Description: "`sendInitialEvents=true` may be set together with `watch=true`. In that case, the watch stream will begin with synthetic events to produce the current state of objects in the collection. Once all such events have been sent, a synthetic \"Bookmark\" event will be sent. The bookmark will report the ResourceVersion (RV) corresponding to the set of objects, and be marked with `\"k8s.io/initial-events-end\": \"true\"` annotation. Afterwards, the watch stream will proceed as usual, sending watch events corresponding to changes (subsequent to the RV) to objects watched.\n\nWhen `sendInitialEvents` option is set, we require `resourceVersionMatch` option to also be set. The semantic of the watch request is as following: - `resourceVersionMatch` = NotOlderThan\n is interpreted as \"data at least as new as the provided `resourceVersion`\"\n and the bookmark event is send when the state is synced\n to a `resourceVersion` at least as fresh as the one provided by the ListOptions.\n If `resourceVersion` is unset, this is interpreted as \"consistent read\" and the\n bookmark event is send when the state is synced at least to the moment\n when request started being processed.\n- `resourceVersionMatch` set to any other value or unset\n Invalid error is returned.\n\nDefaults to true if `resourceVersion=\"\"` or `resourceVersion=\"0\"` (for backward compatibility reasons) and to false otherwise.", + Type: []string{"boolean"}, + Format: "", + }, + }, + }, + }, + }, + } +} + +func schema_pkg_apis_meta_v1_ManagedFieldsEntry(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "ManagedFieldsEntry is a workflow-id, a FieldSet and the group version of the resource that the fieldset applies to.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "manager": { + SchemaProps: spec.SchemaProps{ + Description: "Manager is an identifier of the workflow managing these fields.", + Type: []string{"string"}, + Format: "", + }, + }, + "operation": { + SchemaProps: spec.SchemaProps{ + Description: "Operation is the type of operation which lead to this ManagedFieldsEntry being created. The only valid values for this field are 'Apply' and 'Update'.", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the version of this resource that this field set applies to. The format is \"group/version\" just like the top-level APIVersion field. It is necessary to track the version of a field set because it cannot be automatically converted.", + Type: []string{"string"}, + Format: "", + }, + }, + "time": { + SchemaProps: spec.SchemaProps{ + Description: "Time is the timestamp of when the ManagedFields entry was added. The timestamp will also be updated if a field is added, the manager changes any of the owned fields value or removes a field. The timestamp does not update when a field is removed from the entry because another manager took it over.", + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.Time"), + }, + }, + "fieldsType": { + SchemaProps: spec.SchemaProps{ + Description: "FieldsType is the discriminator for the different fields format and version. There is currently only one possible value: \"FieldsV1\"", + Type: []string{"string"}, + Format: "", + }, + }, + "fieldsV1": { + SchemaProps: spec.SchemaProps{ + Description: "FieldsV1 holds the first JSON version format as described in the \"FieldsV1\" type.", + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.FieldsV1"), + }, + }, + "subresource": { + SchemaProps: spec.SchemaProps{ + Description: "Subresource is the name of the subresource used to update that object, or empty string if the object was updated through the main resource. The value of this field is used to distinguish between managers, even if they share the same name. For example, a status update will be distinct from a regular update using the same manager name. Note that the APIVersion field is not related to the Subresource field and it always corresponds to the version of the main resource.", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + Dependencies: []string{ + "k8s.io/apimachinery/pkg/apis/meta/v1.FieldsV1", "k8s.io/apimachinery/pkg/apis/meta/v1.Time"}, + } +} + +func schema_pkg_apis_meta_v1_MicroTime(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "MicroTime is version of Time with microsecond level precision.", + Type: metav1.MicroTime{}.OpenAPISchemaType(), + Format: metav1.MicroTime{}.OpenAPISchemaFormat(), + }, + }, + } +} + +func schema_pkg_apis_meta_v1_ObjectMeta(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "ObjectMeta is metadata that all persisted resources must have, which includes all objects users must create.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "name": { + SchemaProps: spec.SchemaProps{ + Description: "Name must be unique within a namespace. Is required when creating resources, although some resources may allow a client to request the generation of an appropriate name automatically. Name is primarily intended for creation idempotence and configuration definition. Cannot be updated. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names#names", + Type: []string{"string"}, + Format: "", + }, + }, + "generateName": { + SchemaProps: spec.SchemaProps{ + Description: "GenerateName is an optional prefix, used by the server, to generate a unique name ONLY IF the Name field has not been provided. If this field is used, the name returned to the client will be different than the name passed. This value will also be combined with a unique suffix. The provided value has the same validation rules as the Name field, and may be truncated by the length of the suffix required to make the value unique on the server.\n\nIf this field is specified and the generated name exists, the server will return a 409.\n\nApplied only if Name is not specified. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#idempotency", + Type: []string{"string"}, + Format: "", + }, + }, + "namespace": { + SchemaProps: spec.SchemaProps{ + Description: "Namespace defines the space within which each name must be unique. An empty namespace is equivalent to the \"default\" namespace, but \"default\" is the canonical representation. Not all objects are required to be scoped to a namespace - the value of this field for those objects will be empty.\n\nMust be a DNS_LABEL. Cannot be updated. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces", + Type: []string{"string"}, + Format: "", + }, + }, + "selfLink": { + SchemaProps: spec.SchemaProps{ + Description: "Deprecated: selfLink is a legacy read-only field that is no longer populated by the system.", + Type: []string{"string"}, + Format: "", + }, + }, + "uid": { + SchemaProps: spec.SchemaProps{ + Description: "UID is the unique in time and space value for this object. It is typically generated by the server on successful creation of a resource and is not allowed to change on PUT operations.\n\nPopulated by the system. Read-only. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names#uids", + Type: []string{"string"}, + Format: "", + }, + }, + "resourceVersion": { + SchemaProps: spec.SchemaProps{ + Description: "An opaque value that represents the internal version of this object that can be used by clients to determine when objects have changed. May be used for optimistic concurrency, change detection, and the watch operation on a resource or set of resources. Clients must treat these values as opaque and passed unmodified back to the server. They may only be valid for a particular resource or set of resources.\n\nPopulated by the system. Read-only. Value must be treated as opaque by clients and . More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency", + Type: []string{"string"}, + Format: "", + }, + }, + "generation": { + SchemaProps: spec.SchemaProps{ + Description: "A sequence number representing a specific generation of the desired state. Populated by the system. Read-only.", + Type: []string{"integer"}, + Format: "int64", + }, + }, + "creationTimestamp": { + SchemaProps: spec.SchemaProps{ + Description: "CreationTimestamp is a timestamp representing the server time when this object was created. It is not guaranteed to be set in happens-before order across separate operations. Clients may not set this value. It is represented in RFC3339 form and is in UTC.\n\nPopulated by the system. Read-only. Null for lists. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata", + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.Time"), + }, + }, + "deletionTimestamp": { + SchemaProps: spec.SchemaProps{ + Description: "DeletionTimestamp is RFC 3339 date and time at which this resource will be deleted. This field is set by the server when a graceful deletion is requested by the user, and is not directly settable by a client. The resource is expected to be deleted (no longer visible from resource lists, and not reachable by name) after the time in this field, once the finalizers list is empty. As long as the finalizers list contains items, deletion is blocked. Once the deletionTimestamp is set, this value may not be unset or be set further into the future, although it may be shortened or the resource may be deleted prior to this time. For example, a user may request that a pod is deleted in 30 seconds. The Kubelet will react by sending a graceful termination signal to the containers in the pod. After that 30 seconds, the Kubelet will send a hard termination signal (SIGKILL) to the container and after cleanup, remove the pod from the API. In the presence of network partitions, this object may still exist after this timestamp, until an administrator or automated process can determine the resource is fully terminated. If not set, graceful deletion of the object has not been requested.\n\nPopulated by the system when a graceful deletion is requested. Read-only. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata", + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.Time"), + }, + }, + "deletionGracePeriodSeconds": { + SchemaProps: spec.SchemaProps{ + Description: "Number of seconds allowed for this object to gracefully terminate before it will be removed from the system. Only set when deletionTimestamp is also set. May only be shortened. Read-only.", + Type: []string{"integer"}, + Format: "int64", + }, + }, + "labels": { + SchemaProps: spec.SchemaProps{ + Description: "Map of string keys and values that can be used to organize and categorize (scope and select) objects. May match selectors of replication controllers and services. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels", + Type: []string{"object"}, + AdditionalProperties: &spec.SchemaOrBool{ + Allows: true, + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + "annotations": { + SchemaProps: spec.SchemaProps{ + Description: "Annotations is an unstructured key value map stored with a resource that may be set by external tools to store and retrieve arbitrary metadata. They are not queryable and should be preserved when modifying objects. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations", + Type: []string{"object"}, + AdditionalProperties: &spec.SchemaOrBool{ + Allows: true, + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + "ownerReferences": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-map-keys": []interface{}{ + "uid", + }, + "x-kubernetes-list-type": "map", + "x-kubernetes-patch-merge-key": "uid", + "x-kubernetes-patch-strategy": "merge", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "List of objects depended by this object. If ALL objects in the list have been deleted, this object will be garbage collected. If this object is managed by a controller, then an entry in this list will point to this controller, with the controller field set to true. There cannot be more than one managing controller.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.OwnerReference"), + }, + }, + }, + }, + }, + "finalizers": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "set", + "x-kubernetes-patch-strategy": "merge", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "Must be empty before the object is deleted from the registry. Each entry is an identifier for the responsible component that will remove the entry from the list. If the deletionTimestamp of the object is non-nil, entries in this list can only be removed. Finalizers may be processed and removed in any order. Order is NOT enforced because it introduces significant risk of stuck finalizers. finalizers is a shared field, any actor with permission can reorder it. If the finalizer list is processed in order, then this can lead to a situation in which the component responsible for the first finalizer in the list is waiting for a signal (field value, external system, or other) produced by a component responsible for a finalizer later in the list, resulting in a deadlock. Without enforced ordering finalizers are free to order amongst themselves and are not vulnerable to ordering changes in the list.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + "managedFields": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "ManagedFields maps workflow-id and version to the set of fields that are managed by that workflow. This is mostly for internal housekeeping, and users typically shouldn't need to set or understand this field. A workflow can be the user's name, a controller's name, or the name of a specific apply path like \"ci-cd\". The set of fields is always in the version that the workflow used when modifying the object.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ManagedFieldsEntry"), + }, + }, + }, + }, + }, + }, + }, + }, + Dependencies: []string{ + "k8s.io/apimachinery/pkg/apis/meta/v1.ManagedFieldsEntry", "k8s.io/apimachinery/pkg/apis/meta/v1.OwnerReference", "k8s.io/apimachinery/pkg/apis/meta/v1.Time"}, + } +} + +func schema_pkg_apis_meta_v1_OwnerReference(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "OwnerReference contains enough information to let you identify an owning object. An owning object must be in the same namespace as the dependent, or be cluster-scoped, so there is no namespace field.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "API version of the referent.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "name": { + SchemaProps: spec.SchemaProps{ + Description: "Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names#names", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "uid": { + SchemaProps: spec.SchemaProps{ + Description: "UID of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names#uids", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "controller": { + SchemaProps: spec.SchemaProps{ + Description: "If true, this reference points to the managing controller.", + Type: []string{"boolean"}, + Format: "", + }, + }, + "blockOwnerDeletion": { + SchemaProps: spec.SchemaProps{ + Description: "If true, AND if the owner has the \"foregroundDeletion\" finalizer, then the owner cannot be deleted from the key-value store until this reference is removed. See https://kubernetes.io/docs/concepts/architecture/garbage-collection/#foreground-deletion for how the garbage collector interacts with this field and enforces the foreground deletion. Defaults to false. To set this field, a user needs \"delete\" permission of the owner, otherwise 422 (Unprocessable Entity) will be returned.", + Type: []string{"boolean"}, + Format: "", + }, + }, + }, + Required: []string{"apiVersion", "kind", "name", "uid"}, + }, + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-map-type": "atomic", + }, + }, + }, + } +} + +func schema_pkg_apis_meta_v1_PartialObjectMetadata(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "PartialObjectMetadata is a generic representation of any object with ObjectMeta. It allows clients to get access to a particular ObjectMeta schema without knowing the details of the version.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", + }, + }, + "metadata": { + SchemaProps: spec.SchemaProps{ + Description: "Standard object's metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata", + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"), + }, + }, + }, + }, + }, + Dependencies: []string{ + "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"}, + } +} + +func schema_pkg_apis_meta_v1_PartialObjectMetadataList(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "PartialObjectMetadataList contains a list of objects containing only their metadata", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", + }, + }, + "metadata": { + SchemaProps: spec.SchemaProps{ + Description: "Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"), + }, + }, + "items": { + SchemaProps: spec.SchemaProps{ + Description: "items contains each of the included items.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.PartialObjectMetadata"), + }, + }, + }, + }, + }, + }, + Required: []string{"items"}, + }, + }, + Dependencies: []string{ + "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta", "k8s.io/apimachinery/pkg/apis/meta/v1.PartialObjectMetadata"}, + } +} + +func schema_pkg_apis_meta_v1_Patch(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "Patch is provided to give a concrete name and type to the Kubernetes PATCH request body.", + Type: []string{"object"}, + }, + }, + } +} + +func schema_pkg_apis_meta_v1_PatchOptions(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "PatchOptions may be provided when patching an API object. PatchOptions is meant to be a superset of UpdateOptions.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", + }, + }, + "dryRun": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + "force": { + SchemaProps: spec.SchemaProps{ + Description: "Force is going to \"force\" Apply requests. It means user will re-acquire conflicting fields owned by other people. Force flag must be unset for non-apply patch requests.", + Type: []string{"boolean"}, + Format: "", + }, + }, + "fieldManager": { + SchemaProps: spec.SchemaProps{ + Description: "fieldManager is a name associated with the actor or entity that is making these changes. The value must be less than or 128 characters long, and only contain printable characters, as defined by https://golang.org/pkg/unicode/#IsPrint. This field is required for apply requests (application/apply-patch) but optional for non-apply patch types (JsonPatch, MergePatch, StrategicMergePatch).", + Type: []string{"string"}, + Format: "", + }, + }, + "fieldValidation": { + SchemaProps: spec.SchemaProps{ + Description: "fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default in v1.23+ - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered.", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + } +} + +func schema_pkg_apis_meta_v1_Preconditions(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "Preconditions must be fulfilled before an operation (update, delete, etc.) is carried out.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "uid": { + SchemaProps: spec.SchemaProps{ + Description: "Specifies the target UID.", + Type: []string{"string"}, + Format: "", + }, + }, + "resourceVersion": { + SchemaProps: spec.SchemaProps{ + Description: "Specifies the target ResourceVersion", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + } +} + +func schema_pkg_apis_meta_v1_RootPaths(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "RootPaths lists the paths available at root. For example: \"/healthz\", \"/apis\".", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "paths": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "paths are the paths available at root.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + }, + Required: []string{"paths"}, + }, + }, + } +} + +func schema_pkg_apis_meta_v1_ServerAddressByClientCIDR(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "ServerAddressByClientCIDR helps the client to determine the server address that they should use, depending on the clientCIDR that they match.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "clientCIDR": { + SchemaProps: spec.SchemaProps{ + Description: "The CIDR with which clients can match their IP to figure out the server address that they should use.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "serverAddress": { + SchemaProps: spec.SchemaProps{ + Description: "Address of this server, suitable for a client that matches the above CIDR. This can be a hostname, hostname:port, IP or IP:port.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"clientCIDR", "serverAddress"}, + }, + }, + } +} + +func schema_pkg_apis_meta_v1_Status(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "Status is a return value for calls that don't return other objects.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", + }, + }, + "metadata": { + SchemaProps: spec.SchemaProps{ + Description: "Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"), + }, + }, + "status": { + SchemaProps: spec.SchemaProps{ + Description: "Status of the operation. One of: \"Success\" or \"Failure\". More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status", + Type: []string{"string"}, + Format: "", + }, + }, + "message": { + SchemaProps: spec.SchemaProps{ + Description: "A human-readable description of the status of this operation.", + Type: []string{"string"}, + Format: "", + }, + }, + "reason": { + SchemaProps: spec.SchemaProps{ + Description: "A machine-readable description of why this operation is in the \"Failure\" status. If this value is empty there is no information available. A Reason clarifies an HTTP status code but does not override it.", + Type: []string{"string"}, + Format: "", + }, + }, + "details": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "Extended data associated with the reason. Each reason may define its own extended details. This field is optional and the data returned is not guaranteed to conform to any schema except that defined by the reason type.", + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.StatusDetails"), + }, + }, + "code": { + SchemaProps: spec.SchemaProps{ + Description: "Suggested HTTP return code for this status, 0 if not set.", + Type: []string{"integer"}, + Format: "int32", + }, + }, + }, + }, + }, + Dependencies: []string{ + "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta", "k8s.io/apimachinery/pkg/apis/meta/v1.StatusDetails"}, + } +} + +func schema_pkg_apis_meta_v1_StatusCause(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "StatusCause provides more information about an api.Status failure, including cases when multiple errors are encountered.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "reason": { + SchemaProps: spec.SchemaProps{ + Description: "A machine-readable description of the cause of the error. If this value is empty there is no information available.", + Type: []string{"string"}, + Format: "", + }, + }, + "message": { + SchemaProps: spec.SchemaProps{ + Description: "A human-readable description of the cause of the error. This field may be presented as-is to a reader.", + Type: []string{"string"}, + Format: "", + }, + }, + "field": { + SchemaProps: spec.SchemaProps{ + Description: "The field of the resource that has caused this error, as named by its JSON serialization. May include dot and postfix notation for nested attributes. Arrays are zero-indexed. Fields may appear more than once in an array of causes due to fields having multiple errors. Optional.\n\nExamples:\n \"name\" - the field \"name\" on the current resource\n \"items[0].name\" - the field \"name\" on the first array entry in \"items\"", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + } +} + +func schema_pkg_apis_meta_v1_StatusDetails(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "StatusDetails is a set of additional properties that MAY be set by the server to provide additional information about a response. The Reason field of a Status object defines what attributes will be set. Clients must ignore fields that do not match the defined type of each attribute, and should assume that any attribute may be empty, invalid, or under defined.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "name": { + SchemaProps: spec.SchemaProps{ + Description: "The name attribute of the resource associated with the status StatusReason (when there is a single name which can be described).", + Type: []string{"string"}, + Format: "", + }, + }, + "group": { + SchemaProps: spec.SchemaProps{ + Description: "The group attribute of the resource associated with the status StatusReason.", + Type: []string{"string"}, + Format: "", + }, + }, + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "The kind attribute of the resource associated with the status StatusReason. On some operations may differ from the requested resource Kind. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "uid": { + SchemaProps: spec.SchemaProps{ + Description: "UID of the resource. (when there is a single resource which can be described). More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names#uids", + Type: []string{"string"}, + Format: "", + }, + }, + "causes": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "The Causes array includes more details associated with the StatusReason failure. Not all StatusReasons may provide detailed causes.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.StatusCause"), + }, + }, + }, + }, + }, + "retryAfterSeconds": { + SchemaProps: spec.SchemaProps{ + Description: "If specified, the time in seconds before the operation should be retried. Some errors may indicate the client must take an alternate action - for those errors this field may indicate how long to wait before taking the alternate action.", + Type: []string{"integer"}, + Format: "int32", + }, + }, + }, + }, + }, + Dependencies: []string{ + "k8s.io/apimachinery/pkg/apis/meta/v1.StatusCause"}, + } +} + +func schema_pkg_apis_meta_v1_Table(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "Table is a tabular representation of a set of API resources. The server transforms the object into a set of preferred columns for quickly reviewing the objects.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", + }, + }, + "metadata": { + SchemaProps: spec.SchemaProps{ + Description: "Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"), + }, + }, + "columnDefinitions": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "columnDefinitions describes each column in the returned items array. The number of cells per row will always match the number of column definitions.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.TableColumnDefinition"), + }, + }, + }, + }, + }, + "rows": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "rows is the list of items in the table.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.TableRow"), + }, + }, + }, + }, + }, + }, + Required: []string{"columnDefinitions", "rows"}, + }, + }, + Dependencies: []string{ + "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta", "k8s.io/apimachinery/pkg/apis/meta/v1.TableColumnDefinition", "k8s.io/apimachinery/pkg/apis/meta/v1.TableRow"}, + } +} + +func schema_pkg_apis_meta_v1_TableColumnDefinition(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "TableColumnDefinition contains information about a column returned in the Table.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "name": { + SchemaProps: spec.SchemaProps{ + Description: "name is a human readable name for the column.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "type": { + SchemaProps: spec.SchemaProps{ + Description: "type is an OpenAPI type definition for this column, such as number, integer, string, or array. See https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#data-types for more.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "format": { + SchemaProps: spec.SchemaProps{ + Description: "format is an optional OpenAPI type modifier for this column. A format modifies the type and imposes additional rules, like date or time formatting for a string. The 'name' format is applied to the primary identifier column which has type 'string' to assist in clients identifying column is the resource name. See https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#data-types for more.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "description": { + SchemaProps: spec.SchemaProps{ + Description: "description is a human readable description of this column.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "priority": { + SchemaProps: spec.SchemaProps{ + Description: "priority is an integer defining the relative importance of this column compared to others. Lower numbers are considered higher priority. Columns that may be omitted in limited space scenarios should be given a higher priority.", + Default: 0, + Type: []string{"integer"}, + Format: "int32", + }, + }, + }, + Required: []string{"name", "type", "format", "description", "priority"}, + }, + }, + } +} + +func schema_pkg_apis_meta_v1_TableOptions(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "TableOptions are used when a Table is requested by the caller.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", + }, + }, + "includeObject": { + SchemaProps: spec.SchemaProps{ + Description: "includeObject decides whether to include each object along with its columnar information. Specifying \"None\" will return no object, specifying \"Object\" will return the full object contents, and specifying \"Metadata\" (the default) will return the object's metadata in the PartialObjectMetadata kind in version v1beta1 of the meta.k8s.io API group.", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + } +} + +func schema_pkg_apis_meta_v1_TableRow(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "TableRow is an individual row in a table.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "cells": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "cells will be as wide as the column definitions array and may contain strings, numbers (float64 or int64), booleans, simple maps, lists, or null. See the type field of the column definition for a more detailed description.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + Format: "", + }, + }, + }, + }, + }, + "conditions": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "conditions describe additional status of a row that are relevant for a human user. These conditions apply to the row, not to the object, and will be specific to table output. The only defined condition type is 'Completed', for a row that indicates a resource that has run to completion and can be given less visual priority.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.TableRowCondition"), + }, + }, + }, + }, + }, + "object": { + SchemaProps: spec.SchemaProps{ + Description: "This field contains the requested additional information about each object based on the includeObject policy when requesting the Table. If \"None\", this field is empty, if \"Object\" this will be the default serialization of the object for the current API version, and if \"Metadata\" (the default) will contain the object metadata. Check the returned kind and apiVersion of the object before parsing. The media type of the object will always match the enclosing list - if this as a JSON table, these will be JSON encoded objects.", + Ref: ref("k8s.io/apimachinery/pkg/runtime.RawExtension"), + }, + }, + }, + Required: []string{"cells"}, + }, + }, + Dependencies: []string{ + "k8s.io/apimachinery/pkg/apis/meta/v1.TableRowCondition", "k8s.io/apimachinery/pkg/runtime.RawExtension"}, + } +} + +func schema_pkg_apis_meta_v1_TableRowCondition(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "TableRowCondition allows a row to be marked with additional information.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "type": { + SchemaProps: spec.SchemaProps{ + Description: "Type of row condition. The only defined value is 'Completed' indicating that the object this row represents has reached a completed state and may be given less visual priority than other rows. Clients are not required to honor any conditions but should be consistent where possible about handling the conditions.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "status": { + SchemaProps: spec.SchemaProps{ + Description: "Status of the condition, one of True, False, Unknown.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "reason": { + SchemaProps: spec.SchemaProps{ + Description: "(brief) machine readable reason for the condition's last transition.", + Type: []string{"string"}, + Format: "", + }, + }, + "message": { + SchemaProps: spec.SchemaProps{ + Description: "Human readable message indicating details about last transition.", + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"type", "status"}, + }, + }, + } +} + +func schema_pkg_apis_meta_v1_Time(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "Time is a wrapper around time.Time which supports correct marshaling to YAML and JSON. Wrappers are provided for many of the factory methods that the time package offers.", + Type: metav1.Time{}.OpenAPISchemaType(), + Format: metav1.Time{}.OpenAPISchemaFormat(), + }, + }, + } +} + +func schema_pkg_apis_meta_v1_Timestamp(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "Timestamp is a struct that is equivalent to Time, but intended for protobuf marshalling/unmarshalling. It is generated into a serialization that matches Time. Do not use in Go structs.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "seconds": { + SchemaProps: spec.SchemaProps{ + Description: "Represents seconds of UTC time since Unix epoch 1970-01-01T00:00:00Z. Must be from 0001-01-01T00:00:00Z to 9999-12-31T23:59:59Z inclusive.", + Default: 0, + Type: []string{"integer"}, + Format: "int64", + }, + }, + "nanos": { + SchemaProps: spec.SchemaProps{ + Description: "Non-negative fractions of a second at nanosecond resolution. Negative second values with fractions must still have non-negative nanos values that count forward in time. Must be from 0 to 999,999,999 inclusive. This field may be limited in precision depending on context.", + Default: 0, + Type: []string{"integer"}, + Format: "int32", + }, + }, + }, + Required: []string{"seconds", "nanos"}, + }, + }, + } +} + +func schema_pkg_apis_meta_v1_TypeMeta(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "TypeMeta describes an individual object in an API response or request with strings representing the type of the object and its API schema version. Structures that are versioned or persisted should inline TypeMeta.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + } +} + +func schema_pkg_apis_meta_v1_UpdateOptions(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "UpdateOptions may be provided when updating an API object. All fields in UpdateOptions should also be present in PatchOptions.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", + }, + }, + "dryRun": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + "fieldManager": { + SchemaProps: spec.SchemaProps{ + Description: "fieldManager is a name associated with the actor or entity that is making these changes. The value must be less than or 128 characters long, and only contain printable characters, as defined by https://golang.org/pkg/unicode/#IsPrint.", + Type: []string{"string"}, + Format: "", + }, + }, + "fieldValidation": { + SchemaProps: spec.SchemaProps{ + Description: "fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default in v1.23+ - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered.", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + } +} + +func schema_pkg_apis_meta_v1_WatchEvent(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "Event represents a single event to a watched resource.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "type": { + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "object": { + SchemaProps: spec.SchemaProps{ + Description: "Object is:\n * If Type is Added or Modified: the new state of the object.\n * If Type is Deleted: the state of the object immediately before deletion.\n * If Type is Error: *Status is recommended; other types may make sense\n depending on context.", + Ref: ref("k8s.io/apimachinery/pkg/runtime.RawExtension"), + }, + }, + }, + Required: []string{"type", "object"}, + }, + }, + Dependencies: []string{ + "k8s.io/apimachinery/pkg/runtime.RawExtension"}, + } +} + +func schema_k8sio_apimachinery_pkg_runtime_RawExtension(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "RawExtension is used to hold extensions in external versions.\n\nTo use this, make a field which has RawExtension as its type in your external, versioned struct, and Object in your internal struct. You also need to register your various plugin types.\n\n// Internal package:\n\n\ttype MyAPIObject struct {\n\t\truntime.TypeMeta `json:\",inline\"`\n\t\tMyPlugin runtime.Object `json:\"myPlugin\"`\n\t}\n\n\ttype PluginA struct {\n\t\tAOption string `json:\"aOption\"`\n\t}\n\n// External package:\n\n\ttype MyAPIObject struct {\n\t\truntime.TypeMeta `json:\",inline\"`\n\t\tMyPlugin runtime.RawExtension `json:\"myPlugin\"`\n\t}\n\n\ttype PluginA struct {\n\t\tAOption string `json:\"aOption\"`\n\t}\n\n// On the wire, the JSON will look something like this:\n\n\t{\n\t\t\"kind\":\"MyAPIObject\",\n\t\t\"apiVersion\":\"v1\",\n\t\t\"myPlugin\": {\n\t\t\t\"kind\":\"PluginA\",\n\t\t\t\"aOption\":\"foo\",\n\t\t},\n\t}\n\nSo what happens? Decode first uses json or yaml to unmarshal the serialized data into your external MyAPIObject. That causes the raw JSON to be stored, but not unpacked. The next step is to copy (using pkg/conversion) into the internal struct. The runtime package's DefaultScheme has conversion functions installed which will unpack the JSON stored in RawExtension, turning it into the correct object type, and storing it in the Object. (TODO: In the case where the object is of an unknown type, a runtime.Unknown object will be created and stored.)", + Type: []string{"object"}, + }, + }, + } +} + +func schema_k8sio_apimachinery_pkg_runtime_TypeMeta(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "TypeMeta is shared by all top level objects. The proper way to use it is to inline it in your type, like this:\n\n\ttype MyAwesomeAPIObject struct {\n\t runtime.TypeMeta `json:\",inline\"`\n\t ... // other fields\n\t}\n\nfunc (obj *MyAwesomeAPIObject) SetGroupVersionKind(gvk *metav1.GroupVersionKind) { metav1.UpdateTypeMeta(obj,gvk) }; GroupVersionKind() *GroupVersionKind\n\nTypeMeta is provided here for convenience. You may use it directly from this package or define your own with the same fields.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Type: []string{"string"}, + Format: "", + }, + }, + "kind": { + SchemaProps: spec.SchemaProps{ + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + } +} + +func schema_k8sio_apimachinery_pkg_runtime_Unknown(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "Unknown allows api objects with unknown types to be passed-through. This can be used to deal with the API objects from a plug-in. Unknown objects still have functioning TypeMeta features-- kind, version, etc. metadata and field mutatation.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Type: []string{"string"}, + Format: "", + }, + }, + "kind": { + SchemaProps: spec.SchemaProps{ + Type: []string{"string"}, + Format: "", + }, + }, + "ContentEncoding": { + SchemaProps: spec.SchemaProps{ + Description: "ContentEncoding is encoding used to encode 'Raw' data. Unspecified means no encoding.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "ContentType": { + SchemaProps: spec.SchemaProps{ + Description: "ContentType is serialization method used to serialize 'Raw'. Unspecified means ContentTypeJSON.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"ContentEncoding", "ContentType"}, + }, + }, + } +} + +func schema_k8sio_apimachinery_pkg_version_Info(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "Info contains versioning information. how we'll want to distribute that information.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "major": { + SchemaProps: spec.SchemaProps{ + Description: "Major is the major version of the binary version", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "minor": { + SchemaProps: spec.SchemaProps{ + Description: "Minor is the minor version of the binary version", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "emulationMajor": { + SchemaProps: spec.SchemaProps{ + Description: "EmulationMajor is the major version of the emulation version", + Type: []string{"string"}, + Format: "", + }, + }, + "emulationMinor": { + SchemaProps: spec.SchemaProps{ + Description: "EmulationMinor is the minor version of the emulation version", + Type: []string{"string"}, + Format: "", + }, + }, + "minCompatibilityMajor": { + SchemaProps: spec.SchemaProps{ + Description: "MinCompatibilityMajor is the major version of the minimum compatibility version", + Type: []string{"string"}, + Format: "", + }, + }, + "minCompatibilityMinor": { + SchemaProps: spec.SchemaProps{ + Description: "MinCompatibilityMinor is the minor version of the minimum compatibility version", + Type: []string{"string"}, + Format: "", + }, + }, + "gitVersion": { + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "gitCommit": { + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "gitTreeState": { + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "buildDate": { + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "goVersion": { + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "compiler": { + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "platform": { + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"major", "minor", "gitVersion", "gitCommit", "gitTreeState", "buildDate", "goVersion", "compiler", "platform"}, + }, + }, + } +} diff --git a/pkg/acme/webhook/registry/challengepayload/challenge_payload.go b/pkg/acme/webhook/registry/challengepayload/challenge_payload.go index f202b8603c4..de58eba834e 100644 --- a/pkg/acme/webhook/registry/challengepayload/challenge_payload.go +++ b/pkg/acme/webhook/registry/challengepayload/challenge_payload.go @@ -33,9 +33,10 @@ type REST struct { hookFn webhook.Solver } -var _ rest.Creater = &REST{} +var _ rest.Creater = &REST{} // nolint:misspell var _ rest.Scoper = &REST{} var _ rest.GroupVersionKindProvider = &REST{} +var _ rest.SingularNameProvider = &REST{} func NewREST(hookFn webhook.Solver) *REST { return &REST{ @@ -46,6 +47,9 @@ func NewREST(hookFn webhook.Solver) *REST { func (r *REST) New() runtime.Object { return &v1alpha1.ChallengePayload{} } +func (r *REST) GetSingularName() string { + return "ChallengePayload" +} func (r *REST) GroupVersionKind(containingGV schema.GroupVersion) schema.GroupVersionKind { return v1alpha1.SchemeGroupVersion.WithKind("ChallengePayload") diff --git a/pkg/acme/webhook/webhook.go b/pkg/acme/webhook/webhook.go index dfccbc265bb..c1480a13bef 100644 --- a/pkg/acme/webhook/webhook.go +++ b/pkg/acme/webhook/webhook.go @@ -24,7 +24,9 @@ import ( whapi "github.com/cert-manager/cert-manager/pkg/acme/webhook/apis/acme/v1alpha1" ) -// Solver has the functionality to solve ACME challenges. +// Solver has the functionality to solve ACME challenges. This interface is +// implemented internally by RFC2136 DNS provider and by external webhook solver +// implementations see https://github.com/cert-manager/webhook-example type Solver interface { // Name is the name of this ACME solver as part of the API group. // This must match what you configure in the ACME Issuer's DNS01 config. @@ -41,5 +43,6 @@ type Solver interface { CleanUp(ch *whapi.ChallengeRequest) error // Initialize is called as a post-start hook when the apiserver starts. + // https://github.com/kubernetes/apiserver/blob/release-1.26/pkg/server/hooks.go#L32-L42 Initialize(kubeClientConfig *restclient.Config, stopCh <-chan struct{}) error } diff --git a/pkg/api/scheme.go b/pkg/api/scheme.go index ecdb1fbb7be..75e791611dc 100644 --- a/pkg/api/scheme.go +++ b/pkg/api/scheme.go @@ -17,21 +17,12 @@ limitations under the License. package api import ( - apiext "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/serializer" utilruntime "k8s.io/apimachinery/pkg/util/runtime" - kscheme "k8s.io/client-go/kubernetes/scheme" - apireg "k8s.io/kube-aggregator/pkg/apis/apiregistration/v1" - cmacmev1alpha2 "github.com/cert-manager/cert-manager/internal/apis/acme/v1alpha2" - cmacmev1alpha3 "github.com/cert-manager/cert-manager/internal/apis/acme/v1alpha3" - cmacmev1beta1 "github.com/cert-manager/cert-manager/internal/apis/acme/v1beta1" - cmapiv1alpha2 "github.com/cert-manager/cert-manager/internal/apis/certmanager/v1alpha2" - cmapiv1alpha3 "github.com/cert-manager/cert-manager/internal/apis/certmanager/v1alpha3" - cmapiv1beta1 "github.com/cert-manager/cert-manager/internal/apis/certmanager/v1beta1" whapi "github.com/cert-manager/cert-manager/pkg/acme/webhook/apis/acme/v1alpha1" cmacmev1 "github.com/cert-manager/cert-manager/pkg/apis/acme/v1" cmapiv1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" @@ -51,19 +42,10 @@ var Scheme = runtime.NewScheme() var Codecs = serializer.NewCodecFactory(Scheme) var ParameterCodec = runtime.NewParameterCodec(Scheme) var localSchemeBuilder = runtime.SchemeBuilder{ - cmapiv1alpha2.AddToScheme, - cmapiv1alpha3.AddToScheme, - cmapiv1beta1.AddToScheme, cmapiv1.AddToScheme, - cmacmev1alpha2.AddToScheme, - cmacmev1alpha3.AddToScheme, - cmacmev1beta1.AddToScheme, cmacmev1.AddToScheme, cmmeta.AddToScheme, whapi.AddToScheme, - kscheme.AddToScheme, - apireg.AddToScheme, - apiext.AddToScheme, } // AddToScheme adds all types of this clientset into the given scheme. This allows composition diff --git a/pkg/api/util/conditions.go b/pkg/api/util/conditions.go index d1876753684..df247f29cce 100644 --- a/pkg/api/util/conditions.go +++ b/pkg/api/util/conditions.go @@ -18,6 +18,7 @@ package util import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/klog/v2" "k8s.io/utils/clock" cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" @@ -82,7 +83,12 @@ func SetIssuerCondition(i cmapi.GenericIssuer, observedGeneration int64, conditi if cond.Status == status { newCondition.LastTransitionTime = cond.LastTransitionTime } else { - logf.V(logf.InfoLevel).Infof("Found status change for Issuer %q condition %q: %q -> %q; setting lastTransitionTime to %v", i.GetObjectMeta().Name, conditionType, cond.Status, status, nowTime.Time) + logf.Log.V(logf.InfoLevel).Info("Found status change for Issuer condition; setting lastTransitionTime", + "issuer", klog.KObj(i), + "condition", conditionType, + "oldStatus", cond.Status, + "status", status, + "lastTransitionTime", nowTime.Time) } // Overwrite the existing condition @@ -93,7 +99,10 @@ func SetIssuerCondition(i cmapi.GenericIssuer, observedGeneration int64, conditi // If we've not found an existing condition of this type, we simply insert // the new condition into the slice. i.GetStatus().Conditions = append(i.GetStatus().Conditions, newCondition) - logf.V(logf.InfoLevel).Infof("Setting lastTransitionTime for Issuer %q condition %q to %v", i.GetObjectMeta().Name, conditionType, nowTime.Time) + logf.Log.V(logf.InfoLevel).Info("Setting lastTransitionTime for Issuer condition", + "issuer", klog.KObj(i), + "condition", conditionType, + "lastTransitionTime", nowTime.Time) } // CertificateHasCondition will return true if the given Certificate has a @@ -134,18 +143,18 @@ func CertificateHasConditionWithObservedGeneration(crt *cmapi.Certificate, c cma } func GetCertificateCondition(crt *cmapi.Certificate, conditionType cmapi.CertificateConditionType) *cmapi.CertificateCondition { - for _, cond := range crt.Status.Conditions { + for i, cond := range crt.Status.Conditions { if cond.Type == conditionType { - return &cond + return &crt.Status.Conditions[i] } } return nil } func GetCertificateRequestCondition(req *cmapi.CertificateRequest, conditionType cmapi.CertificateRequestConditionType) *cmapi.CertificateRequestCondition { - for _, cond := range req.Status.Conditions { + for i, cond := range req.Status.Conditions { if cond.Type == conditionType { - return &cond + return &req.Status.Conditions[i] } } return nil @@ -189,7 +198,12 @@ func SetCertificateCondition(crt *cmapi.Certificate, observedGeneration int64, c if cond.Status == status { newCondition.LastTransitionTime = cond.LastTransitionTime } else { - logf.V(logf.InfoLevel).Infof("Found status change for Certificate %q condition %q: %q -> %q; setting lastTransitionTime to %v", crt.Name, conditionType, cond.Status, status, nowTime.Time) + logf.Log.V(logf.InfoLevel).Info("Found status change for Certificate condition; setting lastTransitionTime", + "certificate", klog.KObj(crt), + "condition", conditionType, + "oldStatus", cond.Status, + "status", status, + "lastTransitionTime", nowTime.Time) } // Overwrite the existing condition @@ -200,7 +214,10 @@ func SetCertificateCondition(crt *cmapi.Certificate, observedGeneration int64, c // If we've not found an existing condition of this type, we simply insert // the new condition into the slice. crt.Status.Conditions = append(crt.Status.Conditions, newCondition) - logf.V(logf.InfoLevel).Infof("Setting lastTransitionTime for Certificate %q condition %q to %v", crt.Name, conditionType, nowTime.Time) + logf.Log.V(logf.InfoLevel).Info("Setting lastTransitionTime for Certificate condition", + "certificate", klog.KObj(crt), + "condition", conditionType, + "lastTransitionTime", nowTime.Time) } // RemoveCertificateCondition will remove any condition with this condition type @@ -249,7 +266,12 @@ func SetCertificateRequestCondition(cr *cmapi.CertificateRequest, conditionType if cond.Status == status { newCondition.LastTransitionTime = cond.LastTransitionTime } else { - logf.V(logf.InfoLevel).Infof("Found status change for CertificateRequest %q condition %q: %q -> %q; setting lastTransitionTime to %v", cr.Name, conditionType, cond.Status, status, nowTime.Time) + logf.Log.V(logf.InfoLevel).Info("Found status change for CertificateRequest condition; setting lastTransitionTime", + "certificateRequest", klog.KObj(cr), + "condition", conditionType, + "oldStatus", cond.Status, + "status", status, + "lastTransitionTime", nowTime.Time) } // Overwrite the existing condition @@ -260,7 +282,10 @@ func SetCertificateRequestCondition(cr *cmapi.CertificateRequest, conditionType // If we've not found an existing condition of this type, we simply insert // the new condition into the slice. cr.Status.Conditions = append(cr.Status.Conditions, newCondition) - logf.V(logf.InfoLevel).Infof("Setting lastTransitionTime for CertificateRequest %q condition %q to %v", cr.Name, conditionType, nowTime.Time) + logf.Log.V(logf.InfoLevel).Info("Setting lastTransitionTime for CertificateRequest condition", + "certificateRequest", klog.KObj(cr), + "condition", conditionType, + "lastTransitionTime", nowTime.Time) } // CertificateRequestHasCondition will return true if the given diff --git a/pkg/api/util/issuers.go b/pkg/api/util/issuers.go index b802b18ead9..54eccbad874 100644 --- a/pkg/api/util/issuers.go +++ b/pkg/api/util/issuers.go @@ -55,7 +55,7 @@ func NameForIssuer(i cmapi.GenericIssuer) (string, error) { } // IssuerKind returns the kind of issuer for a certificate. -func IssuerKind(ref cmmeta.ObjectReference) string { +func IssuerKind(ref cmmeta.IssuerReference) string { if ref.Kind == "" { return cmapi.IssuerKind } diff --git a/pkg/api/util/kube.go b/pkg/api/util/kube.go index abab8f23450..f68ed318c1b 100644 --- a/pkg/api/util/kube.go +++ b/pkg/api/util/kube.go @@ -69,8 +69,8 @@ func ExtKeyUsageTypeKube(usage certificatesv1.KeyUsage) (x509.ExtKeyUsage, bool) func KubeKeyUsageStrings(usage x509.KeyUsage) []certificatesv1.KeyUsage { var usageStr []certificatesv1.KeyUsage - for i := 0; i < bits.UintSize; i++ { - if v := usage & (1 << uint(i)); v != 0 { + for i := range bits.UintSize { + if v := usage & (1 << i); v != 0 { usageStr = append(usageStr, kubeKeyUsageString(v)) } } @@ -91,6 +91,10 @@ func KubeExtKeyUsageStrings(usage []x509.ExtKeyUsage) []certificatesv1.KeyUsage // kubeKeyUsageString returns the cmapi.KeyUsage and "unknown" if not found func kubeKeyUsageString(usage x509.KeyUsage) certificatesv1.KeyUsage { + if usage == x509.KeyUsageDigitalSignature { + return certificatesv1.UsageDigitalSignature // we have two keys that map to KeyUsageDigitalSignature in our map, we should be consistent when parsing + } + for k, v := range keyUsagesKube { if usage == v { return k @@ -102,6 +106,10 @@ func kubeKeyUsageString(usage x509.KeyUsage) certificatesv1.KeyUsage { // kubeExtKeyUsageString returns the cmapi.ExtKeyUsage and "unknown" if not found func kubeExtKeyUsageString(usage x509.ExtKeyUsage) certificatesv1.KeyUsage { + if usage == x509.ExtKeyUsageEmailProtection { + return certificatesv1.UsageEmailProtection // we have two keys that map to ExtKeyUsageEmailProtection in our map, we should be consistent when parsing + } + for k, v := range extKeyUsagesKube { if usage == v { return k diff --git a/pkg/api/util/names.go b/pkg/api/util/names.go index fdeb82758aa..9c4c8165929 100644 --- a/pkg/api/util/names.go +++ b/pkg/api/util/names.go @@ -17,10 +17,10 @@ limitations under the License. package util import ( + "crypto/sha256" "encoding/json" "fmt" "hash/fnv" - "regexp" ) @@ -44,15 +44,70 @@ func ComputeName(prefix string, obj interface{}) (string, error) { // and pods down the road for ACME resources. prefix = DNSSafeShortenTo52Characters(prefix) + // the prefix is <= 52 characters, the decimal representation of + // the hash is <= 10 characters, and the hyphen is 1 character. + // 52 + 10 + 1 = 63, so we're good. return fmt.Sprintf("%s-%d", prefix, hashF.Sum32()), nil } -// DNSSafeShortenTo52Characters shortens the input string to 52 chars and ensures the last char is an alpha-numeric character. -func DNSSafeShortenTo52Characters(in string) string { - if len(in) >= 52 { - validCharIndexes := regexp.MustCompile(`[a-zA-Z\d]`).FindAllStringIndex(fmt.Sprintf("%.52s", in), -1) - in = in[:validCharIndexes[len(validCharIndexes)-1][1]] +// ComputeSecureUniqueDeterministicNameFromData computes a deterministic name from the given data. +// The algorithm in use is SHA256 and is cryptographically secure. +// The output is a string that is safe to use as a DNS label. +// The output is guaranteed to be unique for the given input. +// The output will be at least 64 characters long. +func ComputeSecureUniqueDeterministicNameFromData(fullName string, maxNameLength int) (string, error) { + const hashLength = 64 + if maxNameLength < hashLength { + return "", fmt.Errorf("maxNameLength must be at least %d", hashLength) + } + + if len(fullName) <= maxNameLength { + return fullName, nil + } + + hash := sha256.New() + + _, err := hash.Write([]byte(fullName)) + if err != nil { + return "", err + } + + // Although fullName is already a DNS subdomain, we can't just cut it + // at N characters and expect another DNS subdomain. That's because + // we might cut it right after a ".", which would give an invalid DNS + // subdomain (e.g., test.-). So we make sure the last character + // is an alpha-numeric character. + prefix := DNSSafeShortenToNCharacters(fullName, maxNameLength-hashLength-1) + hashResult := hash.Sum(nil) + + if len(prefix) == 0 { + return fmt.Sprintf("%08x", hashResult), nil } - return in + return fmt.Sprintf("%s-%08x", prefix, hashResult), nil +} + +// DNSSafeShortenToNCharacters shortens the input string to N chars and ensures the last char is an alpha-numeric character. +func DNSSafeShortenToNCharacters(in string, maxLength int) string { + var alphaNumeric = regexp.MustCompile(`[a-zA-Z\d]`) + + if len(in) < maxLength { + return in + } + + if maxLength <= 0 { + return "" + } + + validCharIndexes := alphaNumeric.FindAllStringIndex(in[:maxLength], -1) + if len(validCharIndexes) == 0 { + return "" + } + + return in[:validCharIndexes[len(validCharIndexes)-1][1]] +} + +// DNSSafeShortenTo52Characters shortens the input string to 52 chars and ensures the last char is an alpha-numeric character. +func DNSSafeShortenTo52Characters(in string) string { + return DNSSafeShortenToNCharacters(in, 52) } diff --git a/pkg/api/util/names_test.go b/pkg/api/util/names_test.go index 392026253c7..e1dfcb4c5bd 100644 --- a/pkg/api/util/names_test.go +++ b/pkg/api/util/names_test.go @@ -17,11 +17,14 @@ limitations under the License. package util import ( + "fmt" "testing" - cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/rand" "k8s.io/apimachinery/pkg/util/validation" + + cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" ) func TestComputeName(t *testing.T) { @@ -111,3 +114,200 @@ func TestComputeName(t *testing.T) { }) } } + +func TestDNSSafeShortenToNCharacters(t *testing.T) { + type testcase struct { + in string + maxLength int + expOut string + } + + tests := []testcase{ + { + in: "aaaaaaaaaaaaaaa", + maxLength: 0, + expOut: "", + }, + { + in: "aa-----aaaa", + maxLength: 5, + expOut: "aa", + }, + { + in: "aa11111aaaa", + maxLength: 5, + expOut: "aa111", + }, + { + in: "aaAAAAAaaaa", + maxLength: 5, + expOut: "aaAAA", + }, + { + in: "aaaaaaaaaaaaaaa", + maxLength: 3, + expOut: "aaa", + }, + { + in: ".....", + maxLength: 3, + expOut: "", + }, + { + in: "aa.....", + maxLength: 3, + expOut: "aa", + }, + { + in: "aaa.....", + maxLength: 3, + expOut: "aaa", + }, + { + in: "a*aa.....", + maxLength: 3, + expOut: "a*a", + }, + { + in: "a**aa.....", + maxLength: 3, + expOut: "a", + }, + } + + for i, test := range tests { + t.Run(fmt.Sprintf("test-%d", i), func(t *testing.T) { + out := DNSSafeShortenToNCharacters(test.in, test.maxLength) + if out != test.expOut { + t.Errorf("expected %q, got %q", test.expOut, out) + } + }) + } +} + +func TestComputeSecureUniqueDeterministicNameFromData(t *testing.T) { + type testcase struct { + in string + maxLength int + expOut string + expErr bool + } + + aString64 := "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + randomString64 := rand.String(64) + + tests := []testcase{ + { + in: "aaaa", + maxLength: 3, // must be at least 64 + expOut: "", + expErr: true, + }, + { + in: aString64, + maxLength: 64, + expOut: aString64, + }, + { + in: aString64[:10], + maxLength: 64, + expOut: aString64[:10], + }, + { + in: "b" + aString64, + maxLength: 64, + expOut: "08ba353c3a64d6186cac33ae87b2bd29700803754b34f77dc4d3a45e66316745", + }, + { + in: "b" + aString64, + maxLength: 65, + expOut: "b" + aString64, + }, + { + in: "bb" + aString64, + maxLength: 65, + expOut: "824cc1084d15d9bff4dda12c92066ff5d15ef2f9847c47347836cee174138ca0", + }, + { + in: "bbb" + aString64, + maxLength: 66, + expOut: "b-9a956f515497faf6c2e733e5c2a0e35700ff0b9457e6fd163f30bfe5ec81d13c", + }, + { + in: ".bb" + aString64, + maxLength: 66, + expOut: "efd1f8e9b2f02af94b0d00c03eaddbde3a510b626eb92022f1f25bcc74eedb5b", + }, + { + in: "b.b" + aString64, + maxLength: 66, + expOut: "b-f0673c1af88891be1ecfe74876e460de28e073a0bb78d3308fb41617db4c2ca5", + }, + { + in: "bbbbbbbbbbbbbc............." + aString64, + maxLength: 79, + expOut: "bbbbbbbbbbbbbc-d1b69a0803d97526b868335f95a8bc6fcf02e8e08644264c470faded0ca42033", + }, + { + in: "bbbbbbbbbbbbbc............." + aString64, + maxLength: 80, + expOut: "bbbbbbbbbbbbbc-d1b69a0803d97526b868335f95a8bc6fcf02e8e08644264c470faded0ca42033", + }, + { + in: "bbbbbbbbbbbbbc............." + aString64, + maxLength: 90, + expOut: "bbbbbbbbbbbbbc-d1b69a0803d97526b868335f95a8bc6fcf02e8e08644264c470faded0ca42033", + }, + { + in: randomString64, + maxLength: 64, + expOut: randomString64, + }, + } + + for i, test := range tests { + t.Run(fmt.Sprintf("test-%d", i), func(t *testing.T) { + out, err := ComputeSecureUniqueDeterministicNameFromData(test.in, test.maxLength) + if (err != nil) != test.expErr { + t.Errorf("expected err %v, got %v", test.expErr, err) + } + if len(out) > test.maxLength { + t.Errorf("expected output to be at most %d characters, got %d", test.maxLength, len(out)) + } + if out != test.expOut { + t.Errorf("expected %q, got %q", test.expOut, out) + } + }) + } + + aString70 := "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + randomString70 := rand.String(70) + + // Test that the output is unique for different inputs + inputs := []string{ + aString70, + aString70 + "a", + aString70 + "b", + aString70 + ".", + "." + aString70, + "...................." + aString70, + "...................a" + aString70, + "a..................." + aString70, + randomString70, + randomString70 + "a", + randomString70 + "b", + randomString70 + "c", + } + + outputs := make(map[string]struct{}) + for _, in := range inputs { + out, err := ComputeSecureUniqueDeterministicNameFromData(in, 80) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + if _, ok := outputs[out]; ok { + t.Errorf("output %q already seen", out) + } + outputs[out] = struct{}{} + } +} diff --git a/pkg/api/util/usages.go b/pkg/api/util/usages.go index 82e13a060b8..bfc8fcb5a07 100644 --- a/pkg/api/util/usages.go +++ b/pkg/api/util/usages.go @@ -68,8 +68,8 @@ func ExtKeyUsageType(usage cmapi.KeyUsage) (x509.ExtKeyUsage, bool) { func KeyUsageStrings(usage x509.KeyUsage) []cmapi.KeyUsage { var usageStr []cmapi.KeyUsage - for i := 0; i < bits.UintSize; i++ { - if v := usage & (1 << uint(i)); v != 0 { + for i := range bits.UintSize { + if v := usage & (1 << i); v != 0 { usageStr = append(usageStr, keyUsageString(v)) } } @@ -90,10 +90,11 @@ func ExtKeyUsageStrings(usage []x509.ExtKeyUsage) []cmapi.KeyUsage { // keyUsageString returns the cmapi.KeyUsage and "unknown" if not found func keyUsageString(usage x509.KeyUsage) cmapi.KeyUsage { + if usage == x509.KeyUsageDigitalSignature { + return cmapi.UsageDigitalSignature // we have two keys that map to KeyUsageDigitalSignature in our map, we should be consistent when parsing + } + for k, v := range keyUsages { - if usage == x509.KeyUsageDigitalSignature { - return cmapi.UsageDigitalSignature // we have KeyUsageDigitalSignature twice in our array, we should be consistent when parsing - } if usage == v { return k } @@ -104,6 +105,10 @@ func keyUsageString(usage x509.KeyUsage) cmapi.KeyUsage { // extKeyUsageString returns the cmapi.ExtKeyUsage and "unknown" if not found func extKeyUsageString(usage x509.ExtKeyUsage) cmapi.KeyUsage { + if usage == x509.ExtKeyUsageEmailProtection { + return cmapi.UsageEmailProtection // we have two keys that map to ExtKeyUsageEmailProtection in our map, we should be consistent when parsing + } + for k, v := range extKeyUsages { if usage == v { return k diff --git a/pkg/apis/acme/v1/const.go b/pkg/apis/acme/v1/const.go index 287ed2f6d7d..ffc76533ac1 100644 --- a/pkg/apis/acme/v1/const.go +++ b/pkg/apis/acme/v1/const.go @@ -17,5 +17,6 @@ limitations under the License. package v1 const ( - ACMEFinalizer = "finalizer.acme.cert-manager.io" + ACMELegacyFinalizer = "finalizer.acme.cert-manager.io" + ACMEDomainQualifiedFinalizer = "acme.cert-manager.io/finalizer" ) diff --git a/pkg/apis/acme/v1/doc.go b/pkg/apis/acme/v1/doc.go index 92b6583d629..5ba5e8f1c45 100644 --- a/pkg/apis/acme/v1/doc.go +++ b/pkg/apis/acme/v1/doc.go @@ -16,5 +16,6 @@ limitations under the License. // Package v1 is the v1 version of the API. // +k8s:deepcopy-gen=package,register +// +k8s:openapi-gen=true // +groupName=acme.cert-manager.io package v1 diff --git a/pkg/apis/acme/v1/types_challenge.go b/pkg/apis/acme/v1/types_challenge.go index cfc4f241429..dc3bb1b37fe 100644 --- a/pkg/apis/acme/v1/types_challenge.go +++ b/pkg/apis/acme/v1/types_challenge.go @@ -25,15 +25,14 @@ import ( // +genclient // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object // +kubebuilder:storageversion - -// Challenge is a type to represent a Challenge request with an ACME server -// +k8s:openapi-gen=true // +kubebuilder:printcolumn:name="State",type="string",JSONPath=".status.state" // +kubebuilder:printcolumn:name="Domain",type="string",JSONPath=".spec.dnsName" // +kubebuilder:printcolumn:name="Reason",type="string",JSONPath=".status.reason",description="",priority=1 // +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp",description="CreationTimestamp is a timestamp representing the server time when this object was created. It is not guaranteed to be set in happens-before order across separate operations. Clients may not set this value. It is represented in RFC3339 form and is in UTC." +// +kubebuilder:resource:scope=Namespaced,categories={cert-manager,cert-manager-acme} // +kubebuilder:subresource:status -// +kubebuilder:resource:path=challenges + +// Challenge is a type to represent a Challenge request with an ACME server type Challenge struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata"` @@ -62,9 +61,9 @@ type ChallengeSpec struct { // challenge is a part of. AuthorizationURL string `json:"authorizationURL"` - // dnsName is the identifier that this challenge is for, e.g. example.com. + // 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`. + // non-wildcard domain, e.g., for `*.example.com`, it must be `example.com`. DNSName string `json:"dnsName"` // wildcard will be true if this challenge is for a wildcard identifier, @@ -98,7 +97,7 @@ type ChallengeSpec struct { // 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. - IssuerRef cmmeta.ObjectReference `json:"issuerRef"` + IssuerRef cmmeta.IssuerReference `json:"issuerRef"` } // The type of ACME challenge. Only HTTP-01 and DNS-01 are supported. diff --git a/pkg/apis/acme/v1/types_issuer.go b/pkg/apis/acme/v1/types_issuer.go index 967ba864fe5..009b1abe84a 100644 --- a/pkg/apis/acme/v1/types_issuer.go +++ b/pkg/apis/acme/v1/types_issuer.go @@ -19,7 +19,7 @@ package v1 import ( corev1 "k8s.io/api/core/v1" apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" - gwapi "sigs.k8s.io/gateway-api/apis/v1alpha2" + gwapi "sigs.k8s.io/gateway-api/apis/v1" cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" ) @@ -46,20 +46,32 @@ type ACMEIssuer struct { // 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: + // For example, for Let's Encrypt's DST cross-sign 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 + // This value picks the first certificate bundle in the combined set of + // ACME default and alternative chains that has a root-most certificate with + // this value as its issuer's commonname. // +optional // +kubebuilder:validation:MaxLength=64 - PreferredChain string `json:"preferredChain"` - - // Enables or disables validation of the ACME server TLS certificate. - // If true, requests to the ACME server will not have their TLS certificate - // validated (i.e. insecure connections will be allowed). + PreferredChain string `json:"preferredChain,omitempty"` + + // 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. + // +optional + CABundle []byte `json:"caBundle,omitempty"` + + // 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. - // The cert-manager system installed roots will be used to verify connections - // to the ACME server if this is false. + // If CABundle and SkipTLSVerify are unset, the system certificate bundle inside + // the container is used to validate the TLS connection. // Defaults to false. // +optional SkipTLSVerify bool `json:"skipTLSVerify,omitempty"` @@ -84,6 +96,7 @@ type ACMEIssuer struct { // from an ACME server. // For more information, see: https://cert-manager.io/docs/configuration/acme/ // +optional + // +listType=atomic Solvers []ACMEChallengeSolver `json:"solvers,omitempty"` // Enables or disables generating a new ACME account key. @@ -98,10 +111,15 @@ type ACMEIssuer struct { // 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. + // it, it will create an error on the Order. // Defaults to false. // +optional EnableDurationFeature bool `json:"enableDurationFeature,omitempty"` + + // Profile allows requesting a certificate profile from the ACME server. + // Supported profiles are listed by the server's ACME directory URL. + // +optional + Profile string `json:"profile,omitempty"` } // ACMEExternalAccountBinding is a reference to a CA external account of the ACME @@ -151,7 +169,7 @@ type ACMEChallengeSolver struct { // 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. + // (e.g., `*.example.com`) using the HTTP01 challenge mechanism. // +optional HTTP01 *ACMEChallengeSolverHTTP01 `json:"http01,omitempty"` @@ -179,6 +197,7 @@ type CertificateDNSNameSelector struct { // If neither has more matches, the solver defined earlier in the list // will be selected. // +optional + // +listType=atomic DNSNames []string `json:"dnsNames,omitempty"` // List of DNSZones that this solver will be used to solve. @@ -191,6 +210,7 @@ type CertificateDNSNameSelector struct { // If neither has more matches, the solver defined earlier in the list // will be selected. // +optional + // +listType=atomic DNSZones []string `json:"dnsZones,omitempty"` } @@ -222,9 +242,17 @@ type ACMEChallengeSolverHTTP01Ingress struct { // +optional ServiceType corev1.ServiceType `json:"serviceType,omitempty"` - // 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 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. + // +optional + IngressClassName *string `json:"ingressClassName,omitempty"` + + // 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. // +optional Class *string `json:"class,omitempty"` @@ -232,7 +260,8 @@ type ACMEChallengeSolverHTTP01Ingress struct { // 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. + // ingress resources. Only one of `class`, `name` or `ingressClassName` may + // be specified. // +optional Name string `json:"name,omitempty"` @@ -263,8 +292,15 @@ type ACMEChallengeSolverHTTP01GatewayHTTPRoute struct { // 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/v1alpha2/api-types/httproute/#attaching-to-gateways + // https://gateway-api.sigs.k8s.io/api-types/httproute/#attaching-to-gateways + // +optional + // +listType=atomic ParentRefs []gwapi.ParentReference `json:"parentRefs,omitempty"` + + // Optional pod template used to configure the ACME challenge solver pods + // used for HTTP01 challenges. + // +optional + PodTemplate *ACMEChallengeSolverHTTP01IngressPodTemplate `json:"podTemplate,omitempty"` } type ACMEChallengeSolverHTTP01IngressPodTemplate struct { @@ -276,15 +312,14 @@ type ACMEChallengeSolverHTTP01IngressPodTemplate struct { ACMEChallengeSolverHTTP01IngressPodObjectMeta `json:"metadata"` // PodSpec defines overrides for the HTTP01 challenge solver pod. - // Only the 'priorityClassName', 'nodeSelector', 'affinity', - // 'serviceAccountName' and 'tolerations' fields are supported currently. + // Check ACMEChallengeSolverHTTP01IngressPodSpec to find out currently supported fields. // All other fields will be ignored. // +optional Spec ACMEChallengeSolverHTTP01IngressPodSpec `json:"spec"` } type ACMEChallengeSolverHTTP01IngressPodObjectMeta struct { - // Annotations that should be added to the create ACME HTTP01 solver pods. + // Annotations that should be added to the created ACME HTTP01 solver pods. // +optional Annotations map[string]string `json:"annotations,omitempty"` @@ -306,6 +341,7 @@ type ACMEChallengeSolverHTTP01IngressPodSpec struct { // If specified, the pod's tolerations. // +optional + // +listType=atomic Tolerations []corev1.Toleration `json:"tolerations,omitempty"` // If specified, the pod's priorityClassName. @@ -315,6 +351,27 @@ type ACMEChallengeSolverHTTP01IngressPodSpec struct { // If specified, the pod's service account // +optional ServiceAccountName string `json:"serviceAccountName,omitempty"` + + // If specified, the pod's imagePullSecrets + // +optional + // +patchMergeKey=name + // +patchStrategy=merge + // +listType=map + // +listMapKey=name + ImagePullSecrets []corev1.LocalObjectReference `json:"imagePullSecrets,omitempty" patchMergeKey:"name" patchStrategy:"merge"` + + // If specified, the pod's security context + // +optional + SecurityContext *ACMEChallengeSolverHTTP01IngressPodSecurityContext `json:"securityContext,omitempty"` + + // If specified, the pod's resource requirements. + // These values override the global resource configuration flags. + // Note that when only specifying resource limits, ensure they are greater than or equal + // to the corresponding global resource requests configured via controller flags + // (--acme-http01-solver-resource-request-cpu, --acme-http01-solver-resource-request-memory). + // Kubernetes will reject pod creation if limits are lower than requests, causing challenge failures. + // +optional + Resources *ACMEChallengeSolverHTTP01IngressPodResources `json:"resources,omitempty"` } type ACMEChallengeSolverHTTP01IngressTemplate struct { @@ -385,6 +442,97 @@ type ACMEChallengeSolverDNS01 struct { Webhook *ACMEIssuerDNS01ProviderWebhook `json:"webhook,omitempty"` } +type ACMEChallengeSolverHTTP01IngressPodSecurityContext struct { + // The SELinux context to be applied to all containers. + // If unspecified, the container runtime will allocate a random SELinux context for each + // container. May also be set in SecurityContext. If set in + // both SecurityContext and PodSecurityContext, the value specified in SecurityContext + // takes precedence for that container. + // Note that this field cannot be set when spec.os.name is windows. + // +optional + SELinuxOptions *corev1.SELinuxOptions `json:"seLinuxOptions,omitempty"` + // The UID to run the entrypoint of the container process. + // Defaults to user specified in image metadata if unspecified. + // May also be set in SecurityContext. If set in both SecurityContext and + // PodSecurityContext, the value specified in SecurityContext takes precedence + // for that container. + // Note that this field cannot be set when spec.os.name is windows. + // +optional + RunAsUser *int64 `json:"runAsUser,omitempty"` + // The GID to run the entrypoint of the container process. + // Uses runtime default if unset. + // May also be set in SecurityContext. If set in both SecurityContext and + // PodSecurityContext, the value specified in SecurityContext takes precedence + // for that container. + // Note that this field cannot be set when spec.os.name is windows. + // +optional + RunAsGroup *int64 `json:"runAsGroup,omitempty"` + // Indicates that the container must run as a non-root user. + // If true, the Kubelet will validate the image at runtime to ensure that it + // does not run as UID 0 (root) and fail to start the container if it does. + // If unset or false, no such validation will be performed. + // May also be set in SecurityContext. If set in both SecurityContext and + // PodSecurityContext, the value specified in SecurityContext takes precedence. + // +optional + RunAsNonRoot *bool `json:"runAsNonRoot,omitempty"` + // A list of groups applied to the first process run in each container, in addition + // to the container's primary GID, the fsGroup (if specified), and group memberships + // defined in the container image for the uid of the container process. If unspecified, + // no additional groups are added to any container. Note that group memberships + // defined in the container image for the uid of the container process are still effective, + // even if they are not included in this list. + // Note that this field cannot be set when spec.os.name is windows. + // +optional + // +listType=atomic + SupplementalGroups []int64 `json:"supplementalGroups,omitempty"` + // A special supplemental group that applies to all containers in a pod. + // Some volume types allow the Kubelet to change the ownership of that volume + // to be owned by the pod: + // + // 1. The owning GID will be the FSGroup + // 2. The setgid bit is set (new files created in the volume will be owned by FSGroup) + // 3. The permission bits are OR'd with rw-rw---- + // + // If unset, the Kubelet will not modify the ownership and permissions of any volume. + // Note that this field cannot be set when spec.os.name is windows. + // +optional + FSGroup *int64 `json:"fsGroup,omitempty"` + // Sysctls hold a list of namespaced sysctls used for the pod. Pods with unsupported + // sysctls (by the container runtime) might fail to launch. + // Note that this field cannot be set when spec.os.name is windows. + // +optional + // +listType=atomic + Sysctls []corev1.Sysctl `json:"sysctls,omitempty"` + // fsGroupChangePolicy defines behavior of changing ownership and permission of the volume + // before being exposed inside Pod. This field will only apply to + // volume types which support fsGroup based ownership(and permissions). + // It will have no effect on ephemeral volume types such as: secret, configmaps + // and emptydir. + // Valid values are "OnRootMismatch" and "Always". If not specified, "Always" is used. + // Note that this field cannot be set when spec.os.name is windows. + // +optional + FSGroupChangePolicy *corev1.PodFSGroupChangePolicy `json:"fsGroupChangePolicy,omitempty"` + // The seccomp options to use by the containers in this pod. + // Note that this field cannot be set when spec.os.name is windows. + // +optional + SeccompProfile *corev1.SeccompProfile `json:"seccompProfile,omitempty"` +} + +// ACMEChallengeSolverHTTP01IngressPodResources defines resource requirements for ACME HTTP01 solver pods. +// To keep API surface essential, this trims down the 'corev1.ResourceRequirements' type to only include the Requests and Limits fields. +type ACMEChallengeSolverHTTP01IngressPodResources struct { + // Limits describes the maximum amount of compute resources allowed. + // More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + // +optional + Limits corev1.ResourceList `json:"limits,omitempty"` + // Requests describes the minimum amount of compute resources required. + // If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, + // otherwise to the global values configured via controller flags. Requests cannot exceed Limits. + // More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + // +optional + Requests corev1.ResourceList `json:"requests,omitempty"` +} + // 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). @@ -455,6 +603,10 @@ type ACMEIssuerDNS01ProviderDigitalOcean struct { // ACMEIssuerDNS01ProviderRoute53 is a structure containing the Route 53 // configuration for AWS type ACMEIssuerDNS01ProviderRoute53 struct { + // Auth configures how cert-manager authenticates. + // +optional + Auth *Route53Auth `json:"auth,omitempty"` + // 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 @@ -484,29 +636,89 @@ type ACMEIssuerDNS01ProviderRoute53 struct { // +optional Role string `json:"role,omitempty"` - // If set, the provider will manage only this zone in Route53 and will not do an lookup using the route53:ListHostedZonesByName api call. + // If set, the provider will manage only this zone in Route53 and will not do a lookup using the route53:ListHostedZonesByName api call. // +optional HostedZoneID string `json:"hostedZoneID,omitempty"` - // Always set the region when using AccessKeyID and SecretAccessKey - Region string `json:"region"` + // Override the AWS region. + // + // Route53 is a global service and does not have regional endpoints but the + // region specified here (or via environment variables) is used as a hint to + // help compute the correct AWS credential scope and partition when it + // connects to Route53. See: + // - [Amazon Route 53 endpoints and quotas](https://docs.aws.amazon.com/general/latest/gr/r53.html) + // - [Global services](https://docs.aws.amazon.com/whitepapers/latest/aws-fault-isolation-boundaries/global-services.html) + // + // If you omit this region field, cert-manager will use the region from + // AWS_REGION and AWS_DEFAULT_REGION environment variables, if they are set + // in the cert-manager controller Pod. + // + // The `region` field is not needed if you use [IAM Roles for Service Accounts (IRSA)](https://docs.aws.amazon.com/eks/latest/userguide/iam-roles-for-service-accounts.html). + // Instead an AWS_REGION environment variable is added to the cert-manager controller Pod by: + // [Amazon EKS Pod Identity Webhook](https://github.com/aws/amazon-eks-pod-identity-webhook). + // In this case this `region` field value is ignored. + // + // The `region` field is not needed if you use [EKS Pod Identities](https://docs.aws.amazon.com/eks/latest/userguide/pod-identities.html). + // Instead an AWS_REGION environment variable is added to the cert-manager controller Pod by: + // [Amazon EKS Pod Identity Agent](https://github.com/aws/eks-pod-identity-agent), + // In this case this `region` field value is ignored. + // + // +optional + Region string `json:"region,omitempty"` +} + +// Route53Auth is configuration used to authenticate with a Route53. +type Route53Auth struct { + // Kubernetes authenticates with Route53 using AssumeRoleWithWebIdentity + // by passing a bound ServiceAccount token. + Kubernetes *Route53KubernetesAuth `json:"kubernetes"` +} + +// Route53KubernetesAuth is a configuration to authenticate against Route53 +// using a bound Kubernetes ServiceAccount token. +type Route53KubernetesAuth struct { + // A reference to a service account that will be used to request a bound + // token (also known as "projected token"). To use this field, you must + // configure an RBAC rule to let cert-manager request a token. + ServiceAccountRef *ServiceAccountRef `json:"serviceAccountRef"` +} + +// ServiceAccountRef is a service account used by cert-manager to request a +// token. The expiration of the token is also set by cert-manager to 10 minutes. +type ServiceAccountRef struct { + // Name of the ServiceAccount used to request a token. + Name string `json:"name"` + + // TokenAudiences is an optional list of audiences to include in the + // token passed to AWS. The default token consisting of the issuer's namespace + // and name is always included. + // If unset the audience defaults to `sts.amazonaws.com`. + // +optional + // +listType=atomic + TokenAudiences []string `json:"audiences,omitempty"` } // ACMEIssuerDNS01ProviderAzureDNS is a structure containing the // configuration for Azure DNS type ACMEIssuerDNS01ProviderAzureDNS struct { - // if both this and ClientSecret are left unset MSI will be used + // Auth: Azure Service Principal: + // The ClientID of the Azure Service Principal used to authenticate with Azure DNS. + // If set, ClientSecret and TenantID must also be set. // +optional ClientID string `json:"clientID,omitempty"` - // if both this and ClientID are left unset MSI will be used + // Auth: Azure Service Principal: + // A reference to a Secret containing the password associated with the Service Principal. + // If set, ClientID and TenantID must also be set. // +optional ClientSecret *cmmeta.SecretKeySelector `json:"clientSecretSecretRef,omitempty"` // ID of the Azure subscription SubscriptionID string `json:"subscriptionID"` - // when specifying ClientID and ClientSecret then this field is also needed + // Auth: Azure Service Principal: + // The TenantID of the Azure Service Principal used to authenticate with Azure DNS. + // If set, ClientID and ClientSecret must also be set. // +optional TenantID string `json:"tenantID,omitempty"` @@ -521,19 +733,29 @@ type ACMEIssuerDNS01ProviderAzureDNS struct { // +optional Environment AzureDNSEnvironment `json:"environment,omitempty"` - // managed identity configuration, can not be used at the same time as clientID, clientSecretSecretRef or tenantID + // Auth: Azure Workload Identity or Azure Managed Service Identity: + // Settings to enable Azure Workload Identity or Azure Managed Service Identity + // If set, ClientID, ClientSecret and TenantID must not be set. // +optional ManagedIdentity *AzureManagedIdentity `json:"managedIdentity,omitempty"` } +// AzureManagedIdentity contains the configuration for Azure Workload Identity or Azure Managed Service Identity +// If the AZURE_FEDERATED_TOKEN_FILE environment variable is set, the Azure Workload Identity will be used. +// Otherwise, we fall-back to using Azure Managed Service Identity. type AzureManagedIdentity struct { - // client ID of the managed identity, can not be used at the same time as resourceID + // client ID of the managed identity, cannot be used at the same time as resourceID // +optional ClientID string `json:"clientID,omitempty"` - // resource ID of the managed identity, can not be used at the same time as clientID + // resource ID of the managed identity, cannot be used at the same time as clientID + // Cannot be used for Azure Managed Service Identity // +optional ResourceID string `json:"resourceID,omitempty"` + + // tenant ID of the managed identity, cannot be used at the same time as resourceID + // +optional + TenantID string `json:"tenantID,omitempty"` } // +kubebuilder:validation:Enum=AzurePublicCloud;AzureChinaCloud;AzureGermanCloud;AzureUSGovernmentCloud @@ -579,8 +801,22 @@ type ACMEIssuerDNS01ProviderRFC2136 struct { // ``HMACSHA1``, ``HMACSHA256`` or ``HMACSHA512``. // +optional TSIGAlgorithm string `json:"tsigAlgorithm,omitempty"` + + // Protocol to use for dynamic DNS update queries. Valid values are (case-sensitive) ``TCP`` and ``UDP``; ``UDP`` (default). + // +optional + Protocol RFC2136UpdateProtocol `json:"protocol,omitempty"` } +// +kubebuilder:validation:Enum=TCP;UDP +type RFC2136UpdateProtocol string + +const ( + // RFC2136UpdateProtocolTCP utilizes TCP to update queries. + RFC2136UpdateProtocolTCP RFC2136UpdateProtocol = "TCP" + // RFC2136UpdateProtocolUDP utilizes UDP to update queries. + RFC2136UpdateProtocolUDP RFC2136UpdateProtocol = "UDP" +) + // ACMEIssuerDNS01ProviderWebhook specifies configuration for a webhook DNS01 // provider, including where to POST ChallengePayload resources. type ACMEIssuerDNS01ProviderWebhook struct { @@ -592,14 +828,14 @@ type ACMEIssuerDNS01ProviderWebhook struct { // 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'. + // This will typically be the name of the provider, e.g., 'cloudflare'. SolverName string `json:"solverName"` // 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 + // 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. @@ -618,4 +854,10 @@ type ACMEIssuerStatus struct { // associated with the Issuer // +optional LastRegisteredEmail string `json:"lastRegisteredEmail,omitempty"` + + // 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 + // +optional + LastPrivateKeyHash string `json:"lastPrivateKeyHash,omitempty"` } diff --git a/pkg/apis/acme/v1/types_order.go b/pkg/apis/acme/v1/types_order.go index e9a50a30134..e7e199c31b7 100644 --- a/pkg/apis/acme/v1/types_order.go +++ b/pkg/apis/acme/v1/types_order.go @@ -25,9 +25,14 @@ import ( // +genclient // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object // +kubebuilder:storageversion +// +kubebuilder:printcolumn:name="State",type="string",JSONPath=".status.state" +// +kubebuilder:printcolumn:name="Issuer",type="string",JSONPath=".spec.issuerRef.name",priority=1 +// +kubebuilder:printcolumn:name="Reason",type="string",JSONPath=".status.reason",description="",priority=1 +// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp",description="CreationTimestamp is a timestamp representing the server time when this object was created. It is not guaranteed to be set in happens-before order across separate operations. Clients may not set this value. It is represented in RFC3339 form and is in UTC." +// +kubebuilder:resource:scope=Namespaced,categories={cert-manager,cert-manager-acme} +// +kubebuilder:subresource:status // Order is a type to represent an Order with an ACME server -// +k8s:openapi-gen=true type Order struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata"` @@ -58,7 +63,7 @@ type OrderSpec struct { // 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. - IssuerRef cmmeta.ObjectReference `json:"issuerRef"` + IssuerRef cmmeta.IssuerReference `json:"issuerRef"` // CommonName is the common name as specified on the DER encoded CSR. // If specified, this value must also be present in `dnsNames` or `ipAddresses`. @@ -69,19 +74,26 @@ type OrderSpec struct { // 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. - //+optional + // +optional + // +listType=atomic DNSNames []string `json:"dnsNames,omitempty"` // 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. // +optional + // +listType=atomic IPAddresses []string `json:"ipAddresses,omitempty"` // Duration is the duration for the not after date for the requested certificate. // this is set on order creation as pe the ACME spec. // +optional Duration *metav1.Duration `json:"duration,omitempty"` + + // Profile allows requesting a certificate profile from the ACME server. + // Supported profiles are listed by the server's ACME directory URL. + // +optional + Profile string `json:"profile,omitempty"` } type OrderStatus struct { @@ -101,6 +113,7 @@ type OrderStatus struct { // authorizations must be completed in order to validate the DNS names // specified on the Order. // +optional + // +listType=atomic Authorizations []ACMEAuthorization `json:"authorizations,omitempty"` // Certificate is a copy of the PEM encoded certificate for this Order. @@ -161,6 +174,7 @@ type ACMEAuthorization struct { // name and an appropriate Challenge resource will be created to perform // the ACME challenge process. // +optional + // +listType=atomic Challenges []ACMEChallenge `json:"challenges,omitempty"` } @@ -176,7 +190,7 @@ type ACMEChallenge struct { // This is used to compute the 'key' that must also be presented. Token string `json:"token"` - // Type is the type of challenge being offered, e.g. 'http-01', 'dns-01', + // 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 @@ -223,7 +237,7 @@ const ( Processing State = "processing" // 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. + // If an Order is marked 'invalid', one of its validations must be invalid for some reason. // This is a final state. Invalid State = "invalid" diff --git a/pkg/apis/acme/v1/zz_generated.deepcopy.go b/pkg/apis/acme/v1/zz_generated.deepcopy.go index c584ec88ad6..e1b4500daab 100644 --- a/pkg/apis/acme/v1/zz_generated.deepcopy.go +++ b/pkg/apis/acme/v1/zz_generated.deepcopy.go @@ -27,7 +27,7 @@ import ( apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" apismetav1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" - v1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" + apisv1 "sigs.k8s.io/gateway-api/apis/v1" ) // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. @@ -202,11 +202,16 @@ func (in *ACMEChallengeSolverHTTP01GatewayHTTPRoute) DeepCopyInto(out *ACMEChall } if in.ParentRefs != nil { in, out := &in.ParentRefs, &out.ParentRefs - *out = make([]v1alpha2.ParentReference, len(*in)) + *out = make([]apisv1.ParentReference, len(*in)) for i := range *in { (*in)[i].DeepCopyInto(&(*out)[i]) } } + if in.PodTemplate != nil { + in, out := &in.PodTemplate, &out.PodTemplate + *out = new(ACMEChallengeSolverHTTP01IngressPodTemplate) + (*in).DeepCopyInto(*out) + } return } @@ -223,6 +228,11 @@ func (in *ACMEChallengeSolverHTTP01GatewayHTTPRoute) DeepCopy() *ACMEChallengeSo // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ACMEChallengeSolverHTTP01Ingress) DeepCopyInto(out *ACMEChallengeSolverHTTP01Ingress) { *out = *in + if in.IngressClassName != nil { + in, out := &in.IngressClassName, &out.IngressClassName + *out = new(string) + **out = **in + } if in.Class != nil { in, out := &in.Class, &out.Class *out = new(string) @@ -311,6 +321,97 @@ func (in *ACMEChallengeSolverHTTP01IngressPodObjectMeta) DeepCopy() *ACMEChallen return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ACMEChallengeSolverHTTP01IngressPodResources) DeepCopyInto(out *ACMEChallengeSolverHTTP01IngressPodResources) { + *out = *in + if in.Limits != nil { + in, out := &in.Limits, &out.Limits + *out = make(corev1.ResourceList, len(*in)) + for key, val := range *in { + (*out)[key] = val.DeepCopy() + } + } + if in.Requests != nil { + in, out := &in.Requests, &out.Requests + *out = make(corev1.ResourceList, len(*in)) + for key, val := range *in { + (*out)[key] = val.DeepCopy() + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ACMEChallengeSolverHTTP01IngressPodResources. +func (in *ACMEChallengeSolverHTTP01IngressPodResources) DeepCopy() *ACMEChallengeSolverHTTP01IngressPodResources { + if in == nil { + return nil + } + out := new(ACMEChallengeSolverHTTP01IngressPodResources) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ACMEChallengeSolverHTTP01IngressPodSecurityContext) DeepCopyInto(out *ACMEChallengeSolverHTTP01IngressPodSecurityContext) { + *out = *in + if in.SELinuxOptions != nil { + in, out := &in.SELinuxOptions, &out.SELinuxOptions + *out = new(corev1.SELinuxOptions) + **out = **in + } + if in.RunAsUser != nil { + in, out := &in.RunAsUser, &out.RunAsUser + *out = new(int64) + **out = **in + } + if in.RunAsGroup != nil { + in, out := &in.RunAsGroup, &out.RunAsGroup + *out = new(int64) + **out = **in + } + if in.RunAsNonRoot != nil { + in, out := &in.RunAsNonRoot, &out.RunAsNonRoot + *out = new(bool) + **out = **in + } + if in.SupplementalGroups != nil { + in, out := &in.SupplementalGroups, &out.SupplementalGroups + *out = make([]int64, len(*in)) + copy(*out, *in) + } + if in.FSGroup != nil { + in, out := &in.FSGroup, &out.FSGroup + *out = new(int64) + **out = **in + } + if in.Sysctls != nil { + in, out := &in.Sysctls, &out.Sysctls + *out = make([]corev1.Sysctl, len(*in)) + copy(*out, *in) + } + if in.FSGroupChangePolicy != nil { + in, out := &in.FSGroupChangePolicy, &out.FSGroupChangePolicy + *out = new(corev1.PodFSGroupChangePolicy) + **out = **in + } + if in.SeccompProfile != nil { + in, out := &in.SeccompProfile, &out.SeccompProfile + *out = new(corev1.SeccompProfile) + (*in).DeepCopyInto(*out) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ACMEChallengeSolverHTTP01IngressPodSecurityContext. +func (in *ACMEChallengeSolverHTTP01IngressPodSecurityContext) DeepCopy() *ACMEChallengeSolverHTTP01IngressPodSecurityContext { + if in == nil { + return nil + } + out := new(ACMEChallengeSolverHTTP01IngressPodSecurityContext) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ACMEChallengeSolverHTTP01IngressPodSpec) DeepCopyInto(out *ACMEChallengeSolverHTTP01IngressPodSpec) { *out = *in @@ -333,6 +434,21 @@ func (in *ACMEChallengeSolverHTTP01IngressPodSpec) DeepCopyInto(out *ACMEChallen (*in)[i].DeepCopyInto(&(*out)[i]) } } + if in.ImagePullSecrets != nil { + in, out := &in.ImagePullSecrets, &out.ImagePullSecrets + *out = make([]corev1.LocalObjectReference, len(*in)) + copy(*out, *in) + } + if in.SecurityContext != nil { + in, out := &in.SecurityContext, &out.SecurityContext + *out = new(ACMEChallengeSolverHTTP01IngressPodSecurityContext) + (*in).DeepCopyInto(*out) + } + if in.Resources != nil { + in, out := &in.Resources, &out.Resources + *out = new(ACMEChallengeSolverHTTP01IngressPodResources) + (*in).DeepCopyInto(*out) + } return } @@ -401,6 +517,11 @@ func (in *ACMEExternalAccountBinding) DeepCopy() *ACMEExternalAccountBinding { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ACMEIssuer) DeepCopyInto(out *ACMEIssuer) { *out = *in + if in.CABundle != nil { + in, out := &in.CABundle, &out.CABundle + *out = make([]byte, len(*in)) + copy(*out, *in) + } if in.ExternalAccountBinding != nil { in, out := &in.ExternalAccountBinding, &out.ExternalAccountBinding *out = new(ACMEExternalAccountBinding) @@ -573,6 +694,11 @@ func (in *ACMEIssuerDNS01ProviderRFC2136) DeepCopy() *ACMEIssuerDNS01ProviderRFC // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ACMEIssuerDNS01ProviderRoute53) DeepCopyInto(out *ACMEIssuerDNS01ProviderRoute53) { *out = *in + if in.Auth != nil { + in, out := &in.Auth, &out.Auth + *out = new(Route53Auth) + (*in).DeepCopyInto(*out) + } if in.SecretAccessKeyID != nil { in, out := &in.SecretAccessKeyID, &out.SecretAccessKeyID *out = new(metav1.SecretKeySelector) @@ -902,3 +1028,66 @@ func (in *OrderStatus) DeepCopy() *OrderStatus { in.DeepCopyInto(out) return out } + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Route53Auth) DeepCopyInto(out *Route53Auth) { + *out = *in + if in.Kubernetes != nil { + in, out := &in.Kubernetes, &out.Kubernetes + *out = new(Route53KubernetesAuth) + (*in).DeepCopyInto(*out) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Route53Auth. +func (in *Route53Auth) DeepCopy() *Route53Auth { + if in == nil { + return nil + } + out := new(Route53Auth) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Route53KubernetesAuth) DeepCopyInto(out *Route53KubernetesAuth) { + *out = *in + if in.ServiceAccountRef != nil { + in, out := &in.ServiceAccountRef, &out.ServiceAccountRef + *out = new(ServiceAccountRef) + (*in).DeepCopyInto(*out) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Route53KubernetesAuth. +func (in *Route53KubernetesAuth) DeepCopy() *Route53KubernetesAuth { + if in == nil { + return nil + } + out := new(Route53KubernetesAuth) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ServiceAccountRef) DeepCopyInto(out *ServiceAccountRef) { + *out = *in + if in.TokenAudiences != nil { + in, out := &in.TokenAudiences, &out.TokenAudiences + *out = make([]string, len(*in)) + copy(*out, *in) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServiceAccountRef. +func (in *ServiceAccountRef) DeepCopy() *ServiceAccountRef { + if in == nil { + return nil + } + out := new(ServiceAccountRef) + in.DeepCopyInto(out) + return out +} diff --git a/pkg/apis/certmanager/v1/const.go b/pkg/apis/certmanager/v1/const.go index 7b8a8b0b678..9d61baebaf2 100644 --- a/pkg/apis/certmanager/v1/const.go +++ b/pkg/apis/certmanager/v1/const.go @@ -40,4 +40,9 @@ const ( // (/v1/auth/kubernetes). The endpoint will then be called at `/login`, so // left as the default, `/v1/auth/kubernetes/login` will be called. DefaultVaultKubernetesAuthMountPath = "/v1/auth/kubernetes" + + // Default mount path location for client certificate authentication + // (/v1/auth/cert). The endpoint will then be called at `/login`, so + // left as the default, `/v1/auth/cert/login` will be called. + DefaultVaultClientCertificateAuthMountPath = "/v1/auth/cert" ) diff --git a/pkg/apis/certmanager/v1/doc.go b/pkg/apis/certmanager/v1/doc.go index 348211c6859..3830f7af353 100644 --- a/pkg/apis/certmanager/v1/doc.go +++ b/pkg/apis/certmanager/v1/doc.go @@ -16,6 +16,7 @@ limitations under the License. // Package v1 is the v1 version of the API. // +k8s:deepcopy-gen=package,register +// +k8s:openapi-gen=true // +groupName=cert-manager.io // +groupGoName=Certmanager package v1 diff --git a/pkg/apis/certmanager/v1/types.go b/pkg/apis/certmanager/v1/types.go index a3fa3ae35e2..4b0c35a785e 100644 --- a/pkg/apis/certmanager/v1/types.go +++ b/pkg/apis/certmanager/v1/types.go @@ -16,8 +16,22 @@ limitations under the License. package v1 -// Common annotation keys added to resources. const ( + + // Common label keys added to resources + // Label key that indicates that a resource is of interest to + // cert-manager controller By default this is set on + // certificate.spec.secretName secret as well as on the temporary + // private key Secret. If using SecretsFilteredCaching feature, you + // might want to set this (with a value of 'true') to any other Secrets + // that cert-manager controller needs to read, such as issuer + // credentials Secrets. + // fao = 'for attention of' + // See https://github.com/cert-manager/cert-manager/blob/master/design/20221205-memory-management.md#risks-and-mitigations + PartOfCertManagerControllerLabelKey = "controller.cert-manager.io/fao" + + // Common annotation keys added to resources + // Annotation key for DNS subjectAltNames. AltNamesAnnotationKey = "cert-manager.io/alt-names" @@ -36,6 +50,36 @@ const ( // Annotation key for certificate renewBefore. RenewBeforeAnnotationKey = "cert-manager.io/renew-before" + // Annotation key for certificate renewBeforePercentage. + RenewBeforePercentageAnnotationKey = "cert-manager.io/renew-before-percentage" + + // Annotation key for emails subjectAltNames. + EmailsAnnotationKey = "cert-manager.io/email-sans" + + // Annotation key for subject organization. + SubjectOrganizationsAnnotationKey = "cert-manager.io/subject-organizations" + + // Annotation key for subject organizational units. + SubjectOrganizationalUnitsAnnotationKey = "cert-manager.io/subject-organizationalunits" + + // Annotation key for subject organizational units. + SubjectCountriesAnnotationKey = "cert-manager.io/subject-countries" + + // Annotation key for subject provinces. + SubjectProvincesAnnotationKey = "cert-manager.io/subject-provinces" + + // Annotation key for subject localities. + SubjectLocalitiesAnnotationKey = "cert-manager.io/subject-localities" + + // Annotation key for subject provinces. + SubjectStreetAddressesAnnotationKey = "cert-manager.io/subject-streetaddresses" + + // Annotation key for subject postal codes. + SubjectPostalCodesAnnotationKey = "cert-manager.io/subject-postalcodes" + + // Annotation key for subject serial number. + SubjectSerialNumberAnnotationKey = "cert-manager.io/subject-serialnumber" + // Annotation key for certificate key usages. UsagesAnnotationKey = "cert-manager.io/usages" @@ -104,6 +148,10 @@ const ( // controller only processes Ingresses with this annotation either unset, or // set to either the configured value or the empty string. IngressClassAnnotationKey = "kubernetes.io/ingress.class" + + // IngressSecretTemplate can be used to set the secretTemplate field in the generated Certificate. + // The value is a JSON representation of secretTemplate and must not have any unknown fields. + IngressSecretTemplate = "cert-manager.io/secret-template" ) // Annotation names for CertificateRequests @@ -233,6 +281,24 @@ const ( UsageNetscapeSGC KeyUsage = "netscape sgc" ) +// Keystore specific secret keys +const ( + // PKCS12SecretKey is the name of the data entry in the Secret resource + // used to store the p12 file. + PKCS12SecretKey = "keystore.p12" + // Data Entry Name in the Secret resource for PKCS12 containing Certificate Authority + PKCS12TruststoreKey = "truststore.p12" + + // JKSSecretKey is the name of the data entry in the Secret resource + // used to store the jks file. + JKSSecretKey = "keystore.jks" + // Data Entry Name in the Secret resource for JKS containing Certificate Authority + JKSTruststoreKey = "truststore.jks" + + // The password used to encrypt the keystore and truststore + KeystorePassword = "keystorePassword" +) + // DefaultKeyUsages contains the default list of key usages func DefaultKeyUsages() []KeyUsage { // The serverAuth EKU is required as of Mac OS Catalina: https://support.apple.com/en-us/HT210176 diff --git a/pkg/apis/certmanager/v1/types_certificate.go b/pkg/apis/certmanager/v1/types_certificate.go index a831d6a5b63..bc5475a32dc 100644 --- a/pkg/apis/certmanager/v1/types_certificate.go +++ b/pkg/apis/certmanager/v1/types_certificate.go @@ -22,34 +22,54 @@ import ( cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" ) +// NOTE: Be mindful of adding OpenAPI validation - see https://github.com/cert-manager/cert-manager/issues/3644 + // +genclient // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object // +kubebuilder:storageversion +// +kubebuilder:printcolumn:name="Ready",type="string",JSONPath=`.status.conditions[?(@.type == "Ready")].status` +// +kubebuilder:printcolumn:name="Secret",type="string",JSONPath=`.spec.secretName` +// +kubebuilder:printcolumn:name="Issuer",type="string",JSONPath=`.spec.issuerRef.name`,priority=1 +// +kubebuilder:printcolumn:name="Status",type="string",JSONPath=`.status.conditions[?(@.type == "Ready")].message`,priority=1 +// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=`.metadata.creationTimestamp`,description="CreationTimestamp is a timestamp representing the server time when this object was created. It is not guaranteed to be set in happens-before order across separate operations. Clients may not set this value. It is represented in RFC3339 form and is in UTC." +// +kubebuilder:resource:scope=Namespaced,shortName={cert,certs},categories=cert-manager +// +kubebuilder:subresource:status // 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`. +// 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`). -// +k8s:openapi-gen=true type Certificate struct { - metav1.TypeMeta `json:",inline"` + metav1.TypeMeta `json:",inline"` + // Standard object's metadata. + // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata + // +optional metav1.ObjectMeta `json:"metadata,omitempty"` - // Desired state of the Certificate resource. + // Specification of the desired state of the Certificate resource. + // https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status + // +optional Spec CertificateSpec `json:"spec"` - // 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 // +optional Status CertificateStatus `json:"status"` } // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object -// CertificateList is a list of Certificates +// CertificateList is a list of Certificates. type CertificateList struct { metav1.TypeMeta `json:",inline"` - metav1.ListMeta `json:"metadata"` + // Standard list metadata. + // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + // +optional + metav1.ListMeta `json:"metadata,omitempty"` + // List of Certificates Items []Certificate `json:"items"` } @@ -57,13 +77,13 @@ type CertificateList struct { type PrivateKeyAlgorithm string const ( - // Denotes the RSA private key type. + // RSA private key algorithm. RSAKeyAlgorithm PrivateKeyAlgorithm = "RSA" - // Denotes the ECDSA private key type. + // ECDSA private key algorithm. ECDSAKeyAlgorithm PrivateKeyAlgorithm = "ECDSA" - // Denotes the Ed25519 private key type. + // Ed25519 private key algorithm. Ed25519KeyAlgorithm PrivateKeyAlgorithm = "Ed25519" ) @@ -71,158 +91,276 @@ const ( type PrivateKeyEncoding string const ( - // 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. PKCS1 PrivateKeyEncoding = "PKCS1" - // 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. PKCS8 PrivateKeyEncoding = "PKCS8" ) +// +kubebuilder:validation:Enum=SHA256WithRSA;SHA384WithRSA;SHA512WithRSA;ECDSAWithSHA256;ECDSAWithSHA384;ECDSAWithSHA512;PureEd25519 +type SignatureAlgorithm string + +const ( + SHA256WithRSA SignatureAlgorithm = "SHA256WithRSA" + SHA384WithRSA SignatureAlgorithm = "SHA384WithRSA" + SHA512WithRSA SignatureAlgorithm = "SHA512WithRSA" + ECDSAWithSHA256 SignatureAlgorithm = "ECDSAWithSHA256" + ECDSAWithSHA384 SignatureAlgorithm = "ECDSAWithSHA384" + ECDSAWithSHA512 SignatureAlgorithm = "ECDSAWithSHA512" + PureEd25519 SignatureAlgorithm = "PureEd25519" +) + // CertificateSpec defines the desired state of Certificate. -// A valid Certificate requires at least one of a CommonName, DNSName, or -// URISAN to be valid. +// +// 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. type CertificateSpec struct { - // 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 Subject *X509Subject `json:"subject,omitempty"` - // LiteralSubject is an LDAP formatted string that represents the [X.509 Subject field](https://datatracker.ietf.org/doc/html/rfc5280#section-4.1.2.6). - // 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. // +optional LiteralSubject string `json:"literalSubject,omitempty"` - // 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 CommonName string `json:"commonName,omitempty"` - // 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 2/3 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 Duration *metav1.Duration `json:"duration,omitempty"` - // How long before the currently issued certificate's expiry - // cert-manager should renew the certificate. The default is 2/3 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 1/3 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. + // Cannot be set if the `renewBeforePercentage` field is set. // +optional RenewBefore *metav1.Duration `json:"renewBefore,omitempty"` - // DNSNames is a list of DNS subjectAltNames to be set on the Certificate. + // `renewBeforePercentage` is like `renewBefore`, except it is a relative percentage + // rather than an absolute duration. For example, if a certificate is valid for 60 + // minutes, and `renewBeforePercentage=25`, cert-manager will begin to attempt to + // renew the certificate 45 minutes after it was issued (i.e. when there are 15 + // minutes (25%) 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. + // + // Value must be an integer in the range (0,100). The minimum effective + // `renewBefore` derived from the `renewBeforePercentage` and `duration` fields is 5 + // minutes. + // Cannot be set if the `renewBefore` field is set. + // +optional + RenewBeforePercentage *int32 `json:"renewBeforePercentage,omitempty"` + + // Requested DNS subject alternative names. // +optional + // +listType=atomic DNSNames []string `json:"dnsNames,omitempty"` - // IPAddresses is a list of IP address subjectAltNames to be set on the Certificate. + // Requested IP address subject alternative names. // +optional + // +listType=atomic IPAddresses []string `json:"ipAddresses,omitempty"` - // URIs is a list of URI subjectAltNames to be set on the Certificate. + // Requested URI subject alternative names. // +optional + // +listType=atomic URIs []string `json:"uris,omitempty"` - // EmailAddresses is a list of email subjectAltNames to be set on the Certificate. + // `otherNames` is an escape hatch for SAN that allows any type. We currently restrict the support to string like otherNames, cf RFC 5280 p 37 + // Any UTF8 String valued otherName can be passed with by setting the keys oid: x.x.x.x and UTF8Value: somevalue for `otherName`. + // Most commonly this would be UPN set with oid: 1.3.6.1.4.1.311.20.2.3 + // You should ensure that any OID passed is valid for the UTF8String type as we do not explicitly validate this. // +optional + // +listType=atomic + OtherNames []OtherName `json:"otherNames,omitempty"` + + // Requested email subject alternative names. + // +optional + // +listType=atomic EmailAddresses []string `json:"emailAddresses,omitempty"` - // 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. SecretName string `json:"secretName"` - // 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 SecretTemplate *CertificateSecretTemplate `json:"secretTemplate,omitempty"` - // Keystores configures additional keystore output formats stored in the - // `secretName` Secret resource. + // Additional keystore output formats to be stored in the Certificate's Secret. // +optional Keystores *CertificateKeystores `json:"keystores,omitempty"` - // 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. - IssuerRef cmmeta.ObjectReference `json:"issuerRef"` + // 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. + IssuerRef cmmeta.IssuerReference `json:"issuerRef"` - // 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 IsCA bool `json:"isCA,omitempty"` - // 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 + // +listType=atomic Usages []KeyUsage `json:"usages,omitempty"` - // 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 PrivateKey *CertificatePrivateKey `json:"privateKey,omitempty"` - // EncodeUsagesInRequest controls whether key usages should be present - // in the CertificateRequest + // Signature algorithm to use. + // Allowed values for RSA keys: SHA256WithRSA, SHA384WithRSA, SHA512WithRSA. + // Allowed values for ECDSA keys: ECDSAWithSHA256, ECDSAWithSHA384, ECDSAWithSHA512. + // Allowed values for Ed25519 keys: PureEd25519. + // +optional + SignatureAlgorithm SignatureAlgorithm `json:"signatureAlgorithm,omitempty"` + + // 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 EncodeUsagesInRequest *bool `json:"encodeUsagesInRequest,omitempty"` - // 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`. - // +kubebuilder:validation:ExclusiveMaximum=false - // +optional - RevisionHistoryLimit *int32 `json:"revisionHistoryLimit,omitempty"` // Validated by the validating webhook. - - // 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. + // 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. + // Default value is `1`. // +optional + RevisionHistoryLimit *int32 `json:"revisionHistoryLimit,omitempty"` + + // Defines extra output formats of the private key and signed certificate chain + // to be written to this Certificate's target Secret. + // +optional + // +listType=atomic AdditionalOutputFormats []CertificateAdditionalOutputFormat `json:"additionalOutputFormats,omitempty"` + + // x.509 certificate NameConstraint extension which MUST NOT be used in a non-CA certificate. + // More Info: https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.10 + // + // This is an Alpha Feature and is only enabled with the + // `--feature-gates=NameConstraints=true` option set on both + // the controller and webhook components. + // +optional + NameConstraints *NameConstraints `json:"nameConstraints,omitempty"` +} + +type OtherName struct { + // OID is the object identifier for the otherName SAN. + // The object identifier must be expressed as a dotted string, for + // example, "1.2.840.113556.1.4.221". + OID string `json:"oid,omitempty"` + + // utf8Value is the string value of the otherName SAN. + // The utf8Value accepts any valid UTF8 string to set as value for the otherName SAN. + UTF8Value string `json:"utf8Value,omitempty"` } // CertificatePrivateKey contains configuration options for private keys // used by the Certificate controller. -// This allows control of how private keys are rotated. +// These include the key algorithm and size, the used encoding and the +// rotation policy. type CertificatePrivateKey struct { // 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 + // + // 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 exist 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 + // 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. + // Default is `Always`. + // The default was changed from `Never` to `Always` in cert-manager >=v1.18.0. + // The new default can be disabled by setting the + // `--feature-gates=DefaultPrivateKeyRotationPolicyAlways=false` option on + // the controller component. // +optional - // +kubebuilder:validation:Enum=Never;Always RotationPolicy PrivateKeyRotationPolicy `json:"rotationPolicy,omitempty"` // 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. @@ -230,15 +368,18 @@ type CertificatePrivateKey struct { Encoding PrivateKeyEncoding `json:"encoding,omitempty"` // Algorithm is the private key algorithm of the corresponding private key - // for this certificate. If provided, allowed values are either `RSA`,`Ed25519` or `ECDSA` + // 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 256 will be used for `ECDSA` key algorithm and - // key size of 2048 will be used for `RSA` key algorithm. + // 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 Algorithm PrivateKeyAlgorithm `json:"algorithm,omitempty"` // 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`, @@ -246,17 +387,18 @@ type CertificatePrivateKey struct { // If `algorithm` is set to `Ed25519`, Size is ignored. // No other values are allowed. // +optional - Size int `json:"size,omitempty"` // Validated by webhook. Be mindful of adding OpenAPI validation- see https://github.com/cert-manager/cert-manager/issues/3644 + Size int `json:"size,omitempty"` } // Denotes how private keys should be generated or sourced when a Certificate // is being issued. +// +kubebuilder:validation:Enum=Never;Always type PrivateKeyRotationPolicy string var ( // RotationPolicyNever means 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, + // If one does exist but it does not have the correct algorithm or size, // a warning will be raised to await user intervention. RotationPolicyNever PrivateKeyRotationPolicy = "Never" @@ -311,24 +453,31 @@ type CertificateAdditionalOutputFormat struct { type X509Subject struct { // Organizations to be used on the Certificate. // +optional + // +listType=atomic Organizations []string `json:"organizations,omitempty"` // Countries to be used on the Certificate. // +optional + // +listType=atomic Countries []string `json:"countries,omitempty"` // Organizational Units to be used on the Certificate. // +optional + // +listType=atomic OrganizationalUnits []string `json:"organizationalUnits,omitempty"` // Cities to be used on the Certificate. // +optional + // +listType=atomic Localities []string `json:"localities,omitempty"` // State/Provinces to be used on the Certificate. // +optional + // +listType=atomic Provinces []string `json:"provinces,omitempty"` // Street addresses to be used on the Certificate. // +optional + // +listType=atomic StreetAddresses []string `json:"streetAddresses,omitempty"` // Postal codes to be used on the Certificate. // +optional + // +listType=atomic PostalCodes []string `json:"postalCodes,omitempty"` // Serial number to be used on the Certificate. // +optional @@ -349,22 +498,37 @@ type CertificateKeystores struct { PKCS12 *PKCS12Keystore `json:"pkcs12,omitempty"` } -// JKS configures options for storing a JKS keystore in the `spec.secretName` -// Secret resource. +// JKS configures options for storing a JKS keystore in the target secret. +// Either PasswordSecretRef or Password must be provided. type JKSKeystore struct { // 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 only be updated upon re-issuance. - // 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` or `password`. + // 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 Create bool `json:"create"` - // PasswordSecretRef is a reference to a key in a Secret resource + // Alias specifies the alias of the key in the keystore, required by the JKS format. + // If not provided, the default alias `certificate` will be used. + // +optional + Alias *string `json:"alias,omitempty"` + + // PasswordSecretRef is a reference to a non-empty key in a Secret resource // containing the password used to encrypt the JKS keystore. - PasswordSecretRef cmmeta.SecretKeySelector `json:"passwordSecretRef"` + // Mutually exclusive with password. + // One of password or passwordSecretRef must provide a password with a non-zero length. + // +optional + PasswordSecretRef cmmeta.SecretKeySelector `json:"passwordSecretRef,omitempty"` + + // Password provides a literal password used to encrypt the JKS keystore. + // Mutually exclusive with passwordSecretRef. + // One of password or passwordSecretRef must provide a password with a non-zero length. + // +optional + Password *string `json:"password,omitempty"` } // PKCS12 configures options for storing a PKCS12 keystore in the @@ -373,37 +537,73 @@ type PKCS12Keystore struct { // 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 only be updated upon re-issuance. - // 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` or in `password`. + // 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 Create bool `json:"create"` - // PasswordSecretRef is a reference to a key in a Secret resource - // containing the password used to encrypt the PKCS12 keystore. - PasswordSecretRef cmmeta.SecretKeySelector `json:"passwordSecretRef"` + // Profile specifies the key and certificate encryption algorithms and the HMAC algorithm + // used to create the PKCS12 keystore. Default value is `LegacyRC2` for backward compatibility. + // + // If provided, allowed values are: + // `LegacyRC2`: Deprecated. Not supported by default in OpenSSL 3 or Java 20. + // `LegacyDES`: Less secure algorithm. Use this option for maximal compatibility. + // `Modern2023`: Secure algorithm. Use this option in case you have to always use secure algorithms + // (e.g., because of company policy). Please note that the security of the algorithm is not that important + // in reality, because the unencrypted certificate and private key are also stored in the Secret. + // +optional + Profile PKCS12Profile `json:"profile,omitempty"` + + // PasswordSecretRef is a reference to a non-empty key in a Secret resource + // containing the password used to encrypt the PKCS#12 keystore. + // Mutually exclusive with password. + // One of password or passwordSecretRef must provide a password with a non-zero length. + // +optional + PasswordSecretRef cmmeta.SecretKeySelector `json:"passwordSecretRef,omitempty"` + + // Password provides a literal password used to encrypt the PKCS#12 keystore. + // Mutually exclusive with passwordSecretRef. + // One of password or passwordSecretRef must provide a password with a non-zero length. + // +optional + Password *string `json:"password,omitempty"` } +// +kubebuilder:validation:Enum=LegacyRC2;LegacyDES;Modern2023 +type PKCS12Profile string + +const ( + // see: https://pkg.go.dev/software.sslmate.com/src/go-pkcs12#LegacyRC2 + LegacyRC2PKCS12Profile PKCS12Profile = "LegacyRC2" + + // see: https://pkg.go.dev/software.sslmate.com/src/go-pkcs12#LegacyDES + LegacyDESPKCS12Profile PKCS12Profile = "LegacyDES" + + // see: https://pkg.go.dev/software.sslmate.com/src/go-pkcs12#Modern2023 + Modern2023PKCS12Profile PKCS12Profile = "Modern2023" +) + // CertificateStatus defines the observed state of Certificate type CertificateStatus struct { // List of status conditions to indicate the status of certificates. // Known condition types are `Ready` and `Issuing`. + // +optional // +listType=map // +listMapKey=type - // +optional Conditions []CertificateCondition `json:"conditions,omitempty"` - // 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 latest 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. // +optional LastFailureTime *metav1.Time `json:"lastFailureTime,omitempty"` // The time after which the certificate stored in the secret named - // by this resource in spec.secretName is valid. + // by this resource in `spec.secretName` is valid. // +optional NotBefore *metav1.Time `json:"notBefore,omitempty"` @@ -453,7 +653,7 @@ type CertificateStatus struct { FailedIssuanceAttempts *int `json:"failedIssuanceAttempts,omitempty"` } -// CertificateCondition contains condition information for an Certificate. +// CertificateCondition contains condition information for a Certificate. type CertificateCondition struct { // Type of the condition, known values are (`Ready`, `Issuing`). Type CertificateConditionType `json:"type"` @@ -485,7 +685,7 @@ type CertificateCondition struct { ObservedGeneration int64 `json:"observedGeneration,omitempty"` } -// CertificateConditionType represents an Certificate condition value. +// CertificateConditionType represents a Certificate condition value. type CertificateConditionType string const ( @@ -507,7 +707,7 @@ const ( // `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. + // 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. @@ -527,3 +727,45 @@ type CertificateSecretTemplate struct { // +optional Labels map[string]string `json:"labels,omitempty"` } + +// NameConstraints is a type to represent x509 NameConstraints +type NameConstraints struct { + // if true then the name constraints are marked critical. + // + // +optional + Critical bool `json:"critical,omitempty"` + // Permitted contains the constraints in which the names must be located. + // + // +optional + Permitted *NameConstraintItem `json:"permitted,omitempty"` + // Excluded contains the constraints which must be disallowed. Any name matching a + // restriction in the excluded field is invalid regardless + // of information appearing in the permitted + // + // +optional + Excluded *NameConstraintItem `json:"excluded,omitempty"` +} + +type NameConstraintItem struct { + // DNSDomains is a list of DNS domains that are permitted or excluded. + // + // +optional + // +listType=atomic + DNSDomains []string `json:"dnsDomains,omitempty"` + // IPRanges is a list of IP Ranges that are permitted or excluded. + // This should be a valid CIDR notation. + // + // +optional + // +listType=atomic + IPRanges []string `json:"ipRanges,omitempty"` + // EmailAddresses is a list of Email Addresses that are permitted or excluded. + // + // +optional + // +listType=atomic + EmailAddresses []string `json:"emailAddresses,omitempty"` + // URIDomains is a list of URI domains that are permitted or excluded. + // + // +optional + // +listType=atomic + URIDomains []string `json:"uriDomains,omitempty"` +} diff --git a/pkg/apis/certmanager/v1/types_certificaterequest.go b/pkg/apis/certmanager/v1/types_certificaterequest.go index 6c8d857d3e1..a948f112916 100644 --- a/pkg/apis/certmanager/v1/types_certificaterequest.go +++ b/pkg/apis/certmanager/v1/types_certificaterequest.go @@ -26,8 +26,9 @@ const ( // Pending indicates that a CertificateRequest is still in progress. CertificateRequestReasonPending = "Pending" - // Failed indicates that a CertificateRequest has failed, either due to - // timing out or some other critical failure. + // Failed indicates that a CertificateRequest has failed permanently, + // either due to timing out or some other critical failure. + // The `status.failureTime` field should be set in this case. CertificateRequestReasonFailed = "Failed" // Issued indicates that a CertificateRequest has been completed, and that @@ -37,75 +38,120 @@ const ( // Denied is a Ready condition reason that indicates that a // CertificateRequest has been denied, and the CertificateRequest will never // be issued. + // The `status.failureTime` field should be set in this case. CertificateRequestReasonDenied = "Denied" ) // +genclient // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object // +kubebuilder:storageversion +// +kubebuilder:printcolumn:name="Approved",type="string",JSONPath=`.status.conditions[?(@.type == "Approved")].status` +// +kubebuilder:printcolumn:name="Denied",type="string",JSONPath=`.status.conditions[?(@.type == "Denied")].status` +// +kubebuilder:printcolumn:name="Ready",type="string",JSONPath=`.status.conditions[?(@.type == "Ready")].status` +// +kubebuilder:printcolumn:name="Issuer",type="string",JSONPath=`.spec.issuerRef.name` +// +kubebuilder:printcolumn:name="Requester",type="string",JSONPath=`.spec.username` +// +kubebuilder:printcolumn:name="Status",type="string",JSONPath=`.status.conditions[?(@.type == "Ready")].message`,priority=1 +// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=`.metadata.creationTimestamp`,description="CreationTimestamp is a timestamp representing the server time when this object was created. It is not guaranteed to be set in happens-before order across separate operations. Clients may not set this value. It is represented in RFC3339 form and is in UTC." +// +kubebuilder:resource:scope=Namespaced,shortName={cr,crs},categories=cert-manager +// +kubebuilder:subresource:status // 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 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. -// +k8s:openapi-gen=true type CertificateRequest struct { - metav1.TypeMeta `json:",inline"` + metav1.TypeMeta `json:",inline"` + // Standard object's metadata. + // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata + // +optional metav1.ObjectMeta `json:"metadata,omitempty"` - // Desired state of the CertificateRequest resource. + // Specification of the desired state of the CertificateRequest resource. + // https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status + // +optional Spec CertificateRequestSpec `json:"spec"` - // 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 // +optional Status CertificateRequestStatus `json:"status"` } // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object -// CertificateRequestList is a list of Certificates +// CertificateRequestList is a list of CertificateRequests. type CertificateRequestList struct { metav1.TypeMeta `json:",inline"` - metav1.ListMeta `json:"metadata"` + // Standard list metadata. + // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + // +optional + metav1.ListMeta `json:"metadata,omitempty"` + // List of CertificateRequests Items []CertificateRequest `json:"items"` } // 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. type CertificateRequestSpec struct { - // 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. // +optional Duration *metav1.Duration `json:"duration,omitempty"` - // 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. - IssuerRef cmmeta.ObjectReference `json:"issuerRef"` - - // The PEM-encoded x509 certificate signing request to be submitted to the - // CA for signing. + // 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. + IssuerRef cmmeta.IssuerReference `json:"issuerRef"` + + // 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. Request []byte `json:"request"` - // 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 IsCA bool `json:"isCA,omitempty"` - // 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 + // +listType=atomic Usages []KeyUsage `json:"usages,omitempty"` // Username contains the name of the user that created the CertificateRequest. @@ -118,8 +164,8 @@ type CertificateRequestSpec struct { UID string `json:"uid,omitempty"` // Groups contains group membership of the user that created the CertificateRequest. // Populated by the cert-manager webhook on creation and immutable. - // +listType=atomic // +optional + // +listType=atomic Groups []string `json:"groups,omitempty"` // Extra contains extra attributes of the user that created the CertificateRequest. // Populated by the cert-manager webhook on creation and immutable. @@ -131,13 +177,13 @@ type CertificateRequestSpec struct { // resulting signed certificate. type CertificateRequestStatus struct { // List of status conditions to indicate the status of a CertificateRequest. - // Known condition types are `Ready` and `InvalidRequest`. + // Known condition types are `Ready`, `InvalidRequest`, `Approved` and `Denied`. + // +optional // +listType=map // +listMapKey=type - // +optional Conditions []CertificateRequestCondition `json:"conditions,omitempty"` - // The PEM encoded x509 certificate resulting from the certificate + // 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 @@ -145,7 +191,7 @@ type CertificateRequestStatus struct { // +optional Certificate []byte `json:"certificate,omitempty"` - // The PEM encoded x509 certificate of the signer, also known as the CA + // 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. @@ -183,7 +229,7 @@ type CertificateRequestCondition struct { Message string `json:"message,omitempty"` } -// CertificateRequestConditionType represents an Certificate condition value. +// CertificateRequestConditionType represents a Certificate condition value. type CertificateRequestConditionType string const ( diff --git a/pkg/apis/certmanager/v1/types_issuer.go b/pkg/apis/certmanager/v1/types_issuer.go index 363d66920a2..1cbd93f9515 100644 --- a/pkg/apis/certmanager/v1/types_issuer.go +++ b/pkg/apis/certmanager/v1/types_issuer.go @@ -25,9 +25,13 @@ import ( // +genclient // +genclient:nonNamespaced -// +k8s:openapi-gen=true // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object // +kubebuilder:storageversion +// +kubebuilder:printcolumn:name="Ready",type="string",JSONPath=`.status.conditions[?(@.type == "Ready")].status` +// +kubebuilder:printcolumn:name="Status",type="string",JSONPath=`.status.conditions[?(@.type == "Ready")].message`,priority=1 +// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=`.metadata.creationTimestamp`,description="CreationTimestamp is a timestamp representing the server time when this object was created. It is not guaranteed to be set in happens-before order across separate operations. Clients may not set this value. It is represented in RFC3339 form and is in UTC." +// +kubebuilder:resource:scope=Cluster,shortName=ciss,categories=cert-manager +// +kubebuilder:subresource:status // A ClusterIssuer represents a certificate issuing authority which can be // referenced as part of `issuerRef` fields. @@ -57,9 +61,13 @@ type ClusterIssuerList struct { } // +genclient -// +k8s:openapi-gen=true // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object // +kubebuilder:storageversion +// +kubebuilder:printcolumn:name="Ready",type="string",JSONPath=`.status.conditions[?(@.type == "Ready")].status` +// +kubebuilder:printcolumn:name="Status",type="string",JSONPath=`.status.conditions[?(@.type == "Ready")].message`,priority=1 +// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=`.metadata.creationTimestamp`,description="CreationTimestamp is a timestamp representing the server time when this object was created. It is not guaranteed to be set in happens-before order across separate operations. Clients may not set this value. It is represented in RFC3339 form and is in UTC." +// +kubebuilder:resource:scope=Namespaced,shortName=iss,categories=cert-manager +// +kubebuilder:subresource:status // An Issuer represents a certificate issuing authority which can be // referenced as part of `issuerRef` fields. @@ -149,25 +157,31 @@ type VenafiTPP struct { // for example: "https://tpp.example.com/vedsdk". URL string `json:"url"` - // 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'. + // CredentialsRef is a reference to a Secret containing the Venafi TPP API credentials. + // The secret must contain the key 'access-token' for the Access Token Authentication, + // or two keys, 'username' and 'password' for the API Keys Authentication. CredentialsRef cmmeta.LocalObjectReference `json:"credentialsRef"` - // CABundle is a PEM encoded TLS certificate to use to verify connections to - // the TPP instance. - // If specified, system roots will not be used and the issuing CA for the - // TPP instance must be verifiable using the provided root. - // If not specified, the connection will be verified using the cert-manager - // system root certificates. + // 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. // +optional CABundle []byte `json:"caBundle,omitempty"` + + // Reference to a Secret containing a 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. Mutually exclusive with CABundle. + // If neither CABundle nor CABundleSecretRef is defined, the certificate bundle in + // the cert-manager controller container is used to validate the TLS connection. + // +optional + CABundleSecretRef *cmmeta.SecretKeySelector `json:"caBundleSecretRef,omitempty"` } // VenafiCloud defines connection configuration details for Venafi Cloud type VenafiCloud struct { // URL is the base URL for Venafi Cloud. - // Defaults to "https://api.venafi.cloud/v1". + // Defaults to "https://api.venafi.cloud/". // +optional URL string `json:"url,omitempty"` @@ -182,6 +196,7 @@ type SelfSignedIssuer struct { // 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. // +optional + // +listType=atomic CRLDistributionPoints []string `json:"crlDistributionPoints,omitempty"` } @@ -194,6 +209,11 @@ type VaultIssuer struct { // Server is the connection address for the Vault server, e.g: "https://vault.example.com:8200". Server string `json:"server"` + // ServerName is used to verify the hostname on the returned certificates + // by the Vault server. + // +optional + ServerName string `json:"serverName,omitempty"` + // Path is the mount path of the Vault PKI backend's `sign` endpoint, e.g: // "my_pki_mount/sign/my-role-name". Path string `json:"path"` @@ -203,26 +223,37 @@ type VaultIssuer struct { // +optional Namespace string `json:"namespace,omitempty"` - // PEM-encoded CA bundle (base64-encoded) used to validate Vault server - // certificate. Only used if the Server URL is using HTTPS protocol. This - // parameter is ignored for plain HTTP protocol connection. If not set the - // system root certificates are used to validate the TLS connection. - // Mutually exclusive with CABundleSecretRef. If neither CABundle nor CABundleSecretRef are defined, - // the cert-manager controller system root certificates are used to validate the TLS connection. + // 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. // +optional CABundle []byte `json:"caBundle,omitempty"` - // CABundleSecretRef is a reference to a Secret which contains the CABundle which will be used when - // connecting to Vault when using HTTPS. - // Mutually exclusive with CABundle. If neither CABundleSecretRef nor CABundle are defined, the cert-manager - // controller system root certificates are used to validate the TLS connection. + // 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'. // +optional CABundleSecretRef *cmmeta.SecretKeySelector `json:"caBundleSecretRef,omitempty"` + + // Reference to a Secret containing a PEM-encoded Client Certificate to use when the + // Vault server requires mTLS. + // +optional + ClientCertSecretRef *cmmeta.SecretKeySelector `json:"clientCertSecretRef,omitempty"` + + // Reference to a Secret containing a PEM-encoded Client Private Key to use when the + // Vault server requires mTLS. + // +optional + ClientKeySecretRef *cmmeta.SecretKeySelector `json:"clientKeySecretRef,omitempty"` } -// 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`, `clientCertificate` or `kubernetes`]. type VaultAuth struct { // TokenSecretRef authenticates with Vault by presenting a token. // +optional @@ -233,6 +264,12 @@ type VaultAuth struct { // +optional AppRole *VaultAppRole `json:"appRole,omitempty"` + // ClientCertificate authenticates with Vault by presenting a client + // certificate during the request's TLS handshake. + // Works only when using HTTPS protocol. + // +optional + ClientCertificate *VaultClientCertificateAuth `json:"clientCertificate,omitempty"` + // Kubernetes authenticates with Vault by passing the ServiceAccount // token stored in the named Secret resource to the Vault server. // +optional @@ -257,6 +294,28 @@ type VaultAppRole struct { SecretRef cmmeta.SecretKeySelector `json:"secretRef"` } +// VaultKubernetesAuth is used to authenticate against Vault using a client +// certificate stored in a Secret. +type VaultClientCertificateAuth struct { + // 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/cert" will be used. + // +optional + Path string `json:"mountPath,omitempty"` + + // Reference to Kubernetes Secret of type "kubernetes.io/tls" (hence containing + // tls.crt and tls.key) used to authenticate to Vault using TLS client + // authentication. + // +optional + SecretName string `json:"secretName,omitempty"` + + // Name of the certificate role to authenticate against. + // If not set, matching any certificate role, if available. + // +optional + Name string `json:"name,omitempty"` +} + // Authenticate against Vault using a Kubernetes ServiceAccount token stored in // a Secret. type VaultKubernetesAuth struct { @@ -270,13 +329,38 @@ type VaultKubernetesAuth struct { // The required Secret field containing a Kubernetes ServiceAccount JWT used // for authenticating with Vault. Use of 'ambient credentials' is not // supported. - SecretRef cmmeta.SecretKeySelector `json:"secretRef"` + // +optional + SecretRef cmmeta.SecretKeySelector `json:"secretRef,omitempty"` + // Note: we don't use a pointer here for backwards compatibility. + + // 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. + // +optional + ServiceAccountRef *ServiceAccountRef `json:"serviceAccountRef,omitempty"` // A required field containing the Vault Role to assume. A Role binds a // Kubernetes ServiceAccount with a set of Vault policies. Role string `json:"role"` } +// ServiceAccountRef is a service account used by cert-manager to request a +// token. Default 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. +type ServiceAccountRef struct { + // Name of the ServiceAccount used to request a token. + Name string `json:"name"` + // TokenAudiences is an optional list of extra audiences to include in the token passed to Vault. The default token + // consisting of the issuer's namespace and name is always included. + // +optional + // +listType=atomic + TokenAudiences []string `json:"audiences,omitempty"` +} + type CAIssuer struct { // SecretName is the name of the secret used to sign Certificates issued // by this Issuer. @@ -286,6 +370,7 @@ type CAIssuer struct { // 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. // +optional + // +listType=atomic CRLDistributionPoints []string `json:"crlDistributionPoints,omitempty"` // The OCSP server list is an X.509 v3 extension that defines a list of @@ -294,16 +379,24 @@ type CAIssuer struct { // certificate will be issued with no OCSP servers set. For example, an // OCSP server URL could be "http://ocsp.int-x3.letsencrypt.org". // +optional + // +listType=atomic OCSPServers []string `json:"ocspServers,omitempty"` + + // IssuingCertificateURLs is a list of URLs which this issuer should embed into certificates + // it creates. See https://www.rfc-editor.org/rfc/rfc5280#section-4.2.2.1 for more details. + // As an example, such a URL might be "http://ca.domain.com/ca.crt". + // +optional + // +listType=atomic + IssuingCertificateURLs []string `json:"issuingCertificateURLs,omitempty"` } // IssuerStatus contains status information about an Issuer type IssuerStatus struct { // List of status conditions to indicate the status of a CertificateRequest. // Known condition types are `Ready`. + // +optional // +listType=map // +listMapKey=type - // +optional Conditions []IssuerCondition `json:"conditions,omitempty"` // ACME specific status options. diff --git a/pkg/apis/certmanager/v1/zz_generated.deepcopy.go b/pkg/apis/certmanager/v1/zz_generated.deepcopy.go index 3a9f68f807c..e889ecf1a9a 100644 --- a/pkg/apis/certmanager/v1/zz_generated.deepcopy.go +++ b/pkg/apis/certmanager/v1/zz_generated.deepcopy.go @@ -41,6 +41,11 @@ func (in *CAIssuer) DeepCopyInto(out *CAIssuer) { *out = make([]string, len(*in)) copy(*out, *in) } + if in.IssuingCertificateURLs != nil { + in, out := &in.IssuingCertificateURLs, &out.IssuingCertificateURLs + *out = make([]string, len(*in)) + copy(*out, *in) + } return } @@ -124,12 +129,12 @@ func (in *CertificateKeystores) DeepCopyInto(out *CertificateKeystores) { if in.JKS != nil { in, out := &in.JKS, &out.JKS *out = new(JKSKeystore) - **out = **in + (*in).DeepCopyInto(*out) } if in.PKCS12 != nil { in, out := &in.PKCS12, &out.PKCS12 *out = new(PKCS12Keystore) - **out = **in + (*in).DeepCopyInto(*out) } return } @@ -411,6 +416,11 @@ func (in *CertificateSpec) DeepCopyInto(out *CertificateSpec) { *out = new(metav1.Duration) **out = **in } + if in.RenewBeforePercentage != nil { + in, out := &in.RenewBeforePercentage, &out.RenewBeforePercentage + *out = new(int32) + **out = **in + } if in.DNSNames != nil { in, out := &in.DNSNames, &out.DNSNames *out = make([]string, len(*in)) @@ -426,6 +436,11 @@ func (in *CertificateSpec) DeepCopyInto(out *CertificateSpec) { *out = make([]string, len(*in)) copy(*out, *in) } + if in.OtherNames != nil { + in, out := &in.OtherNames, &out.OtherNames + *out = make([]OtherName, len(*in)) + copy(*out, *in) + } if in.EmailAddresses != nil { in, out := &in.EmailAddresses, &out.EmailAddresses *out = make([]string, len(*in)) @@ -467,6 +482,11 @@ func (in *CertificateSpec) DeepCopyInto(out *CertificateSpec) { *out = make([]CertificateAdditionalOutputFormat, len(*in)) copy(*out, *in) } + if in.NameConstraints != nil { + in, out := &in.NameConstraints, &out.NameConstraints + *out = new(NameConstraints) + (*in).DeepCopyInto(*out) + } return } @@ -765,7 +785,17 @@ func (in *IssuerStatus) DeepCopy() *IssuerStatus { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *JKSKeystore) DeepCopyInto(out *JKSKeystore) { *out = *in + if in.Alias != nil { + in, out := &in.Alias, &out.Alias + *out = new(string) + **out = **in + } out.PasswordSecretRef = in.PasswordSecretRef + if in.Password != nil { + in, out := &in.Password, &out.Password + *out = new(string) + **out = **in + } return } @@ -779,10 +809,93 @@ func (in *JKSKeystore) DeepCopy() *JKSKeystore { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NameConstraintItem) DeepCopyInto(out *NameConstraintItem) { + *out = *in + if in.DNSDomains != nil { + in, out := &in.DNSDomains, &out.DNSDomains + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.IPRanges != nil { + in, out := &in.IPRanges, &out.IPRanges + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.EmailAddresses != nil { + in, out := &in.EmailAddresses, &out.EmailAddresses + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.URIDomains != nil { + in, out := &in.URIDomains, &out.URIDomains + *out = make([]string, len(*in)) + copy(*out, *in) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NameConstraintItem. +func (in *NameConstraintItem) DeepCopy() *NameConstraintItem { + if in == nil { + return nil + } + out := new(NameConstraintItem) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NameConstraints) DeepCopyInto(out *NameConstraints) { + *out = *in + if in.Permitted != nil { + in, out := &in.Permitted, &out.Permitted + *out = new(NameConstraintItem) + (*in).DeepCopyInto(*out) + } + if in.Excluded != nil { + in, out := &in.Excluded, &out.Excluded + *out = new(NameConstraintItem) + (*in).DeepCopyInto(*out) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NameConstraints. +func (in *NameConstraints) DeepCopy() *NameConstraints { + if in == nil { + return nil + } + out := new(NameConstraints) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *OtherName) DeepCopyInto(out *OtherName) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OtherName. +func (in *OtherName) DeepCopy() *OtherName { + if in == nil { + return nil + } + out := new(OtherName) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *PKCS12Keystore) DeepCopyInto(out *PKCS12Keystore) { *out = *in out.PasswordSecretRef = in.PasswordSecretRef + if in.Password != nil { + in, out := &in.Password, &out.Password + *out = new(string) + **out = **in + } return } @@ -817,6 +930,27 @@ func (in *SelfSignedIssuer) DeepCopy() *SelfSignedIssuer { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ServiceAccountRef) DeepCopyInto(out *ServiceAccountRef) { + *out = *in + if in.TokenAudiences != nil { + in, out := &in.TokenAudiences, &out.TokenAudiences + *out = make([]string, len(*in)) + copy(*out, *in) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServiceAccountRef. +func (in *ServiceAccountRef) DeepCopy() *ServiceAccountRef { + if in == nil { + return nil + } + out := new(ServiceAccountRef) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *VaultAppRole) DeepCopyInto(out *VaultAppRole) { *out = *in @@ -847,10 +981,15 @@ func (in *VaultAuth) DeepCopyInto(out *VaultAuth) { *out = new(VaultAppRole) **out = **in } + if in.ClientCertificate != nil { + in, out := &in.ClientCertificate, &out.ClientCertificate + *out = new(VaultClientCertificateAuth) + **out = **in + } if in.Kubernetes != nil { in, out := &in.Kubernetes, &out.Kubernetes *out = new(VaultKubernetesAuth) - **out = **in + (*in).DeepCopyInto(*out) } return } @@ -865,6 +1004,22 @@ func (in *VaultAuth) DeepCopy() *VaultAuth { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *VaultClientCertificateAuth) DeepCopyInto(out *VaultClientCertificateAuth) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VaultClientCertificateAuth. +func (in *VaultClientCertificateAuth) DeepCopy() *VaultClientCertificateAuth { + if in == nil { + return nil + } + out := new(VaultClientCertificateAuth) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *VaultIssuer) DeepCopyInto(out *VaultIssuer) { *out = *in @@ -879,6 +1034,16 @@ func (in *VaultIssuer) DeepCopyInto(out *VaultIssuer) { *out = new(apismetav1.SecretKeySelector) **out = **in } + if in.ClientCertSecretRef != nil { + in, out := &in.ClientCertSecretRef, &out.ClientCertSecretRef + *out = new(apismetav1.SecretKeySelector) + **out = **in + } + if in.ClientKeySecretRef != nil { + in, out := &in.ClientKeySecretRef, &out.ClientKeySecretRef + *out = new(apismetav1.SecretKeySelector) + **out = **in + } return } @@ -896,6 +1061,11 @@ func (in *VaultIssuer) DeepCopy() *VaultIssuer { func (in *VaultKubernetesAuth) DeepCopyInto(out *VaultKubernetesAuth) { *out = *in out.SecretRef = in.SecretRef + if in.ServiceAccountRef != nil { + in, out := &in.ServiceAccountRef, &out.ServiceAccountRef + *out = new(ServiceAccountRef) + (*in).DeepCopyInto(*out) + } return } @@ -961,6 +1131,11 @@ func (in *VenafiTPP) DeepCopyInto(out *VenafiTPP) { *out = make([]byte, len(*in)) copy(*out, *in) } + if in.CABundleSecretRef != nil { + in, out := &in.CABundleSecretRef, &out.CABundleSecretRef + *out = new(apismetav1.SecretKeySelector) + **out = **in + } return } diff --git a/internal/apis/certmanager/v1alpha2/defaults.go b/pkg/apis/config/cainjector/doc.go similarity index 69% rename from internal/apis/certmanager/v1alpha2/defaults.go rename to pkg/apis/config/cainjector/doc.go index 93ea5ff4f97..b1b973a52bd 100644 --- a/internal/apis/certmanager/v1alpha2/defaults.go +++ b/pkg/apis/config/cainjector/doc.go @@ -1,5 +1,5 @@ /* -Copyright 2020 The cert-manager Authors. +Copyright 2021 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. @@ -14,12 +14,9 @@ See the License for the specific language governing permissions and limitations under the License. */ -package v1alpha2 +// +groupName=cainjector.config.cert-manager.io -import ( - "k8s.io/apimachinery/pkg/runtime" -) +// Package cainjector contains types used to configure the cainjector +package cainjector -func addDefaultingFuncs(scheme *runtime.Scheme) error { - return RegisterDefaults(scheme) -} +const GroupName = "cainjector.config.cert-manager.io" diff --git a/pkg/client/clientset/versioned/doc.go b/pkg/apis/config/cainjector/v1alpha1/doc.go similarity index 70% rename from pkg/client/clientset/versioned/doc.go rename to pkg/apis/config/cainjector/v1alpha1/doc.go index 74f45ce2651..d7c955f9360 100644 --- a/pkg/client/clientset/versioned/doc.go +++ b/pkg/apis/config/cainjector/v1alpha1/doc.go @@ -1,5 +1,5 @@ /* -Copyright The cert-manager Authors. +Copyright 2021 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. @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -// Code generated by client-gen. DO NOT EDIT. - -// This package has the automatically generated clientset. -package versioned +// Package v1alpha1 is the v1alpha1 version of the cainjector config API. +// +k8s:deepcopy-gen=package,register +// +groupName=cainjector.config.cert-manager.io +package v1alpha1 diff --git a/pkg/webhook/handlers/testdata/apis/testgroup/v2/register.go b/pkg/apis/config/cainjector/v1alpha1/register.go similarity index 82% rename from pkg/webhook/handlers/testdata/apis/testgroup/v2/register.go rename to pkg/apis/config/cainjector/v1alpha1/register.go index a41b8b77421..83a26e89a51 100644 --- a/pkg/webhook/handlers/testdata/apis/testgroup/v2/register.go +++ b/pkg/apis/config/cainjector/v1alpha1/register.go @@ -1,5 +1,5 @@ /* -Copyright 2020 The cert-manager Authors. +Copyright 2021 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. @@ -14,18 +14,18 @@ See the License for the specific language governing permissions and limitations under the License. */ -package v2 +package v1alpha1 import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" - "github.com/cert-manager/cert-manager/pkg/webhook/handlers/testdata/apis/testgroup" + "github.com/cert-manager/cert-manager/pkg/apis/config/cainjector" ) // SchemeGroupVersion is group version used to register these objects -var SchemeGroupVersion = schema.GroupVersion{Group: testgroup.GroupName, Version: "v2"} +var SchemeGroupVersion = schema.GroupVersion{Group: cainjector.GroupName, Version: "v1alpha1"} // Resource takes an unqualified resource and returns a Group qualified GroupResource func Resource(resource string) schema.GroupResource { @@ -42,13 +42,14 @@ func init() { // We only register manually written functions here. The registration of the // generated functions takes place in the generated files. The separation // makes the code compile even when the generated files are missing. - localSchemeBuilder.Register(addKnownTypes, addDefaultingFuncs) + localSchemeBuilder.Register(addKnownTypes) } // Adds the list of known types to api.Scheme. func addKnownTypes(scheme *runtime.Scheme) error { scheme.AddKnownTypes(SchemeGroupVersion, - &TestType{}, + &CAInjectorConfiguration{}, + // Add new kinds to be registered here ) metav1.AddToGroupVersion(scheme, SchemeGroupVersion) return nil diff --git a/pkg/apis/config/cainjector/v1alpha1/types.go b/pkg/apis/config/cainjector/v1alpha1/types.go new file mode 100644 index 00000000000..c1c7303de05 --- /dev/null +++ b/pkg/apis/config/cainjector/v1alpha1/types.go @@ -0,0 +1,108 @@ +/* +Copyright 2021 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. +*/ + +package v1alpha1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + logsapi "k8s.io/component-base/logs/api/v1" + + sharedv1alpha1 "github.com/cert-manager/cert-manager/pkg/apis/config/shared/v1alpha1" +) + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +type CAInjectorConfiguration struct { + metav1.TypeMeta `json:",inline"` + + // kubeConfig is the kubeconfig file used to connect to the Kubernetes apiserver. + // If not specified, the cainjector will attempt to load the in-cluster-config. + KubeConfig string `json:"kubeConfig,omitempty"` + + // 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. + Namespace string `json:"namespace,omitempty"` + + // LeaderElectionConfig configures the behaviour of the leader election + LeaderElectionConfig sharedv1alpha1.LeaderElectionConfig `json:"leaderElectionConfig"` + + // EnableDataSourceConfig determines whether cainjector's control loops will watch + // cert-manager resources as potential sources of CA data. + EnableDataSourceConfig EnableDataSourceConfig `json:"enableDataSourceConfig"` + + // EnableInjectableConfig determines whether cainjector's control loops will watch + // cert-manager resources as potential targets for CA data injection. + EnableInjectableConfig EnableInjectableConfig `json:"enableInjectableConfig"` + + // Enable profiling for cainjector. + EnablePprof bool `json:"enablePprof"` + + // 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. + PprofAddress string `json:"pprofAddress,omitempty"` + + // logging configures the logging behaviour of the cainjector. + // https://pkg.go.dev/k8s.io/component-base@v0.27.3/logs/api/v1#LoggingConfiguration + Logging logsapi.LoggingConfiguration `json:"logging"` + + // featureGates is a map of feature names to bools that enable or disable experimental + // features. + // +optional + FeatureGates map[string]bool `json:"featureGates,omitempty"` + + // The host and port that the metrics endpoint should listen on. + // The value "0" disables the metrics server. + // Defaults to '0.0.0.0:9402'. + MetricsListenAddress string `json:"metricsListenAddress,omitempty"` + + // metricsTLSConfig is used to configure the metrics server TLS settings. + MetricsTLSConfig sharedv1alpha1.TLSConfig `json:"metricsTLSConfig"` +} + +type EnableDataSourceConfig struct { + // Certificates determines whether cainjector's control loops will watch + // cert-manager Certificate resources as potential sources of CA data. + // If not set, defaults to true. + Certificates *bool `json:"certificates"` +} + +type EnableInjectableConfig struct { + // ValidatingWebhookConfigurations determines whether cainjector + // will spin up a control loop to inject CA data to annotated + // ValidatingWebhookConfigurations + // If not set, defaults to true. + ValidatingWebhookConfigurations *bool `json:"validatingWebhookConfigurations"` + + // MutatingWebhookConfigurations determines whether cainjector + // will spin up a control loop to inject CA data to annotated + // MutatingWebhookConfigurations + // If not set, defaults to true. + MutatingWebhookConfigurations *bool `json:"mutatingWebhookConfigurations"` + + // CustomResourceDefinitions determines whether cainjector + // will spin up a control loop to inject CA data to annotated + // CustomResourceDefinitions + // If not set, defaults to true. + CustomResourceDefinitions *bool `json:"customResourceDefinitions"` + + // APIServices determines whether cainjector + // will spin up a control loop to inject CA data to annotated + // APIServices + // If not set, defaults to true. + APIServices *bool `json:"apiServices"` +} diff --git a/pkg/apis/config/cainjector/v1alpha1/zz_generated.deepcopy.go b/pkg/apis/config/cainjector/v1alpha1/zz_generated.deepcopy.go new file mode 100644 index 00000000000..65191496966 --- /dev/null +++ b/pkg/apis/config/cainjector/v1alpha1/zz_generated.deepcopy.go @@ -0,0 +1,120 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by deepcopy-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + runtime "k8s.io/apimachinery/pkg/runtime" +) + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *CAInjectorConfiguration) DeepCopyInto(out *CAInjectorConfiguration) { + *out = *in + out.TypeMeta = in.TypeMeta + in.LeaderElectionConfig.DeepCopyInto(&out.LeaderElectionConfig) + in.EnableDataSourceConfig.DeepCopyInto(&out.EnableDataSourceConfig) + in.EnableInjectableConfig.DeepCopyInto(&out.EnableInjectableConfig) + in.Logging.DeepCopyInto(&out.Logging) + if in.FeatureGates != nil { + in, out := &in.FeatureGates, &out.FeatureGates + *out = make(map[string]bool, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + in.MetricsTLSConfig.DeepCopyInto(&out.MetricsTLSConfig) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CAInjectorConfiguration. +func (in *CAInjectorConfiguration) DeepCopy() *CAInjectorConfiguration { + if in == nil { + return nil + } + out := new(CAInjectorConfiguration) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *CAInjectorConfiguration) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *EnableDataSourceConfig) DeepCopyInto(out *EnableDataSourceConfig) { + *out = *in + if in.Certificates != nil { + in, out := &in.Certificates, &out.Certificates + *out = new(bool) + **out = **in + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new EnableDataSourceConfig. +func (in *EnableDataSourceConfig) DeepCopy() *EnableDataSourceConfig { + if in == nil { + return nil + } + out := new(EnableDataSourceConfig) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *EnableInjectableConfig) DeepCopyInto(out *EnableInjectableConfig) { + *out = *in + if in.ValidatingWebhookConfigurations != nil { + in, out := &in.ValidatingWebhookConfigurations, &out.ValidatingWebhookConfigurations + *out = new(bool) + **out = **in + } + if in.MutatingWebhookConfigurations != nil { + in, out := &in.MutatingWebhookConfigurations, &out.MutatingWebhookConfigurations + *out = new(bool) + **out = **in + } + if in.CustomResourceDefinitions != nil { + in, out := &in.CustomResourceDefinitions, &out.CustomResourceDefinitions + *out = new(bool) + **out = **in + } + if in.APIServices != nil { + in, out := &in.APIServices, &out.APIServices + *out = new(bool) + **out = **in + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new EnableInjectableConfig. +func (in *EnableInjectableConfig) DeepCopy() *EnableInjectableConfig { + if in == nil { + return nil + } + out := new(EnableInjectableConfig) + in.DeepCopyInto(out) + return out +} diff --git a/pkg/apis/config/controller/doc.go b/pkg/apis/config/controller/doc.go new file mode 100644 index 00000000000..80f0a61e463 --- /dev/null +++ b/pkg/apis/config/controller/doc.go @@ -0,0 +1,22 @@ +/* +Copyright 2021 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. +*/ + +// +groupName=controller.config.cert-manager.io + +// Package controller contains types used to configure the controller +package controller + +const GroupName = "controller.config.cert-manager.io" diff --git a/internal/apis/acme/v1alpha2/defaults.go b/pkg/apis/config/controller/v1alpha1/doc.go similarity index 70% rename from internal/apis/acme/v1alpha2/defaults.go rename to pkg/apis/config/controller/v1alpha1/doc.go index 93ea5ff4f97..1188713651d 100644 --- a/internal/apis/acme/v1alpha2/defaults.go +++ b/pkg/apis/config/controller/v1alpha1/doc.go @@ -1,5 +1,5 @@ /* -Copyright 2020 The cert-manager Authors. +Copyright 2021 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. @@ -14,12 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package v1alpha2 - -import ( - "k8s.io/apimachinery/pkg/runtime" -) - -func addDefaultingFuncs(scheme *runtime.Scheme) error { - return RegisterDefaults(scheme) -} +// Package v1alpha1 is the v1alpha1 version of the controller config API. +// +k8s:deepcopy-gen=package,register +// +groupName=controller.config.cert-manager.io +package v1alpha1 diff --git a/pkg/webhook/handlers/testdata/apis/testgroup/v1/register.go b/pkg/apis/config/controller/v1alpha1/register.go similarity index 82% rename from pkg/webhook/handlers/testdata/apis/testgroup/v1/register.go rename to pkg/apis/config/controller/v1alpha1/register.go index 5bb86bc4c6d..5f50b785ffc 100644 --- a/pkg/webhook/handlers/testdata/apis/testgroup/v1/register.go +++ b/pkg/apis/config/controller/v1alpha1/register.go @@ -1,5 +1,5 @@ /* -Copyright 2020 The cert-manager Authors. +Copyright 2021 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. @@ -14,18 +14,18 @@ See the License for the specific language governing permissions and limitations under the License. */ -package v1 +package v1alpha1 import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" - "github.com/cert-manager/cert-manager/pkg/webhook/handlers/testdata/apis/testgroup" + "github.com/cert-manager/cert-manager/pkg/apis/config/controller" ) // SchemeGroupVersion is group version used to register these objects -var SchemeGroupVersion = schema.GroupVersion{Group: testgroup.GroupName, Version: "v1"} +var SchemeGroupVersion = schema.GroupVersion{Group: controller.GroupName, Version: "v1alpha1"} // Resource takes an unqualified resource and returns a Group qualified GroupResource func Resource(resource string) schema.GroupResource { @@ -42,13 +42,14 @@ func init() { // We only register manually written functions here. The registration of the // generated functions takes place in the generated files. The separation // makes the code compile even when the generated files are missing. - localSchemeBuilder.Register(addKnownTypes, addDefaultingFuncs) + localSchemeBuilder.Register(addKnownTypes) } // Adds the list of known types to api.Scheme. func addKnownTypes(scheme *runtime.Scheme) error { scheme.AddKnownTypes(SchemeGroupVersion, - &TestType{}, + &ControllerConfiguration{}, + // Add new kinds to be registered here ) metav1.AddToGroupVersion(scheme, SchemeGroupVersion) return nil diff --git a/pkg/apis/config/controller/v1alpha1/types.go b/pkg/apis/config/controller/v1alpha1/types.go new file mode 100644 index 00000000000..7cbbdd23fa0 --- /dev/null +++ b/pkg/apis/config/controller/v1alpha1/types.go @@ -0,0 +1,227 @@ +/* +Copyright 2021 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. +*/ + +package v1alpha1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + logsapi "k8s.io/component-base/logs/api/v1" + + sharedv1alpha1 "github.com/cert-manager/cert-manager/pkg/apis/config/shared/v1alpha1" +) + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +type ControllerConfiguration struct { + metav1.TypeMeta `json:",inline"` + + // kubeConfig is the kubeconfig file used to connect to the Kubernetes apiserver. + // If not specified, the controller will attempt to load the in-cluster-config. + KubeConfig string `json:"kubeConfig,omitempty"` + + // apiServerHost is used to override the API server connection address. + // Deprecated: use `kubeConfig` instead. + APIServerHost string `json:"apiServerHost,omitempty"` + + // 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/ + KubernetesAPIQPS *float32 `json:"kubernetesAPIQPS,omitempty"` + + // The maximum burst queries-per-second of requests sent to the Kubernetes apiserver + KubernetesAPIBurst *int32 `json:"kubernetesAPIBurst,omitempty"` + + // 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 + Namespace string `json:"namespace,omitempty"` + + // Namespace to store resources owned by cluster scoped resources such as ClusterIssuer in. + ClusterResourceNamespace string `json:"clusterResourceNamespace,omitempty"` + + // LeaderElectionConfig configures the behaviour of the leader election + LeaderElectionConfig LeaderElectionConfig `json:"leaderElectionConfig"` + + // A list of controllers to enable. + // ['*'] enables all controllers, + // ['foo'] enables only the foo controller + // ['*', '-foo'] disables the controller named foo. + Controllers []string `json:"controllers,omitempty"` + + // 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. + IssuerAmbientCredentials *bool `json:"issuerAmbientCredentials,omitempty"` + + // 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. + ClusterIssuerAmbientCredentials *bool `json:"clusterIssuerAmbientCredentials,omitempty"` + + // 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. + EnableCertificateOwnerRef *bool `json:"enableCertificateOwnerRef,omitempty"` + + // Whether gateway API integration is enabled within cert-manager. The + // ExperimentalGatewayAPISupport feature gate must also be enabled (default + // as of 1.15). + EnableGatewayAPI *bool `json:"enableGatewayAPI,omitempty"` + + // 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.kubernetes.io/'- all annotations will be copied apart from the + // ones where the key is prefixed with 'kubectl.kubernetes.io/'. + CopiedAnnotationPrefixes []string `json:"copiedAnnotationPrefixes,omitempty"` + + // The number of concurrent workers for each controller. + NumberOfConcurrentWorkers *int32 `json:"numberOfConcurrentWorkers,omitempty"` + + // The maximum number of challenges that can be scheduled as 'processing' at once. + MaxConcurrentChallenges *int32 `json:"maxConcurrentChallenges,omitempty"` + + // The host and port that the metrics endpoint should listen on. + MetricsListenAddress string `json:"metricsListenAddress,omitempty"` + + // TLS config for the metrics endpoint + MetricsTLSConfig sharedv1alpha1.TLSConfig `json:"metricsTLSConfig"` + + // The host and port address, separated by a ':', that the healthz server + // should listen on. + HealthzListenAddress string `json:"healthzListenAddress,omitempty"` + + // Enable profiling for controller. + EnablePprof *bool `json:"enablePprof"` + + // 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. + PprofAddress string `json:"pprofAddress,omitempty"` + + // logging configures the logging behaviour of the controller. + // https://pkg.go.dev/k8s.io/component-base@v0.27.3/logs/api/v1#LoggingConfiguration + Logging logsapi.LoggingConfiguration `json:"logging"` + + // featureGates is a map of feature names to bools that enable or disable experimental + // features. + // +optional + FeatureGates map[string]bool `json:"featureGates,omitempty"` + + // ingressShimConfig configures the behaviour of the ingress-shim controller + IngressShimConfig IngressShimConfig `json:"ingressShimConfig,omitempty"` + + // acmeHTTP01Config configures the behaviour of the ACME HTTP01 challenge solver + ACMEHTTP01Config ACMEHTTP01Config `json:"acmeHTTP01Config,omitempty"` + + // acmeDNS01Config configures the behaviour of the ACME DNS01 challenge solver + ACMEDNS01Config ACMEDNS01Config `json:"acmeDNS01Config,omitempty"` +} + +type LeaderElectionConfig struct { + sharedv1alpha1.LeaderElectionConfig `json:",inline"` + + // Leader election healthz checks within this timeout period after the lease + // expires will still return healthy. + HealthzTimeout *sharedv1alpha1.Duration `json:"healthzTimeout,omitempty"` +} + +type IngressShimConfig struct { + // 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. + DefaultIssuerName string `json:"defaultIssuerName,omitempty"` + + // Kind of the Issuer to use when the TLS is requested but issuer kind is not + // specified on the ingress resource. + DefaultIssuerKind string `json:"defaultIssuerKind,omitempty"` + + // Group of the Issuer to use when the TLS is requested but issuer group is + // not specified on the ingress resource. + DefaultIssuerGroup string `json:"defaultIssuerGroup,omitempty"` + + // The annotation consumed by the ingress-shim controller to indicate an ingress + // is requesting a certificate + DefaultAutoCertificateAnnotations []string `json:"defaultAutoCertificateAnnotations,omitempty"` + + // ExtraCertificateAnnotations is a list of annotations which should be copied from + // and ingress-like object to a Certificate. + ExtraCertificateAnnotations []string `json:"extraCertificateAnnotations,omitempty"` +} + +type ACMEHTTP01Config struct { + // 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. + SolverImage string `json:"solverImage,omitempty"` + + // Defines the resource request CPU size when spawning new ACME HTTP01 + // challenge solver pods. + SolverResourceRequestCPU string `json:"solverResourceRequestCPU,omitempty"` + + // Defines the resource request Memory size when spawning new ACME HTTP01 + // challenge solver pods. + SolverResourceRequestMemory string `json:"solverResourceRequestMemory,omitempty"` + + // Defines the resource limits CPU size when spawning new ACME HTTP01 + // challenge solver pods. + SolverResourceLimitsCPU string `json:"solverResourceLimitsCPU,omitempty"` + + // Defines the resource limits Memory size when spawning new ACME HTTP01 + // challenge solver pods. + SolverResourceLimitsMemory string `json:"solverResourceLimitsMemory,omitempty"` + + // Defines the ability to run the http01 solver as root for troubleshooting + // issues + SolverRunAsNonRoot *bool `json:"solverRunAsNonRoot,omitempty"` + + // 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. + SolverNameservers []string `json:"solverNameservers,omitempty"` +} + +type ACMEDNS01Config struct { + // 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) + RecursiveNameservers []string `json:"recursiveNameservers,omitempty"` + + // 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. + RecursiveNameserversOnly *bool `json:"recursiveNameserversOnly,omitempty"` + + // 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 + CheckRetryPeriod *sharedv1alpha1.Duration `json:"checkRetryPeriod,omitempty"` +} diff --git a/pkg/apis/config/controller/v1alpha1/zz_generated.deepcopy.go b/pkg/apis/config/controller/v1alpha1/zz_generated.deepcopy.go new file mode 100644 index 00000000000..a558c67b60a --- /dev/null +++ b/pkg/apis/config/controller/v1alpha1/zz_generated.deepcopy.go @@ -0,0 +1,225 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by deepcopy-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + sharedv1alpha1 "github.com/cert-manager/cert-manager/pkg/apis/config/shared/v1alpha1" + runtime "k8s.io/apimachinery/pkg/runtime" +) + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ACMEDNS01Config) DeepCopyInto(out *ACMEDNS01Config) { + *out = *in + if in.RecursiveNameservers != nil { + in, out := &in.RecursiveNameservers, &out.RecursiveNameservers + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.RecursiveNameserversOnly != nil { + in, out := &in.RecursiveNameserversOnly, &out.RecursiveNameserversOnly + *out = new(bool) + **out = **in + } + if in.CheckRetryPeriod != nil { + in, out := &in.CheckRetryPeriod, &out.CheckRetryPeriod + *out = new(sharedv1alpha1.Duration) + **out = **in + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ACMEDNS01Config. +func (in *ACMEDNS01Config) DeepCopy() *ACMEDNS01Config { + if in == nil { + return nil + } + out := new(ACMEDNS01Config) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ACMEHTTP01Config) DeepCopyInto(out *ACMEHTTP01Config) { + *out = *in + if in.SolverRunAsNonRoot != nil { + in, out := &in.SolverRunAsNonRoot, &out.SolverRunAsNonRoot + *out = new(bool) + **out = **in + } + if in.SolverNameservers != nil { + in, out := &in.SolverNameservers, &out.SolverNameservers + *out = make([]string, len(*in)) + copy(*out, *in) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ACMEHTTP01Config. +func (in *ACMEHTTP01Config) DeepCopy() *ACMEHTTP01Config { + if in == nil { + return nil + } + out := new(ACMEHTTP01Config) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ControllerConfiguration) DeepCopyInto(out *ControllerConfiguration) { + *out = *in + out.TypeMeta = in.TypeMeta + if in.KubernetesAPIQPS != nil { + in, out := &in.KubernetesAPIQPS, &out.KubernetesAPIQPS + *out = new(float32) + **out = **in + } + if in.KubernetesAPIBurst != nil { + in, out := &in.KubernetesAPIBurst, &out.KubernetesAPIBurst + *out = new(int32) + **out = **in + } + in.LeaderElectionConfig.DeepCopyInto(&out.LeaderElectionConfig) + if in.Controllers != nil { + in, out := &in.Controllers, &out.Controllers + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.IssuerAmbientCredentials != nil { + in, out := &in.IssuerAmbientCredentials, &out.IssuerAmbientCredentials + *out = new(bool) + **out = **in + } + if in.ClusterIssuerAmbientCredentials != nil { + in, out := &in.ClusterIssuerAmbientCredentials, &out.ClusterIssuerAmbientCredentials + *out = new(bool) + **out = **in + } + if in.EnableCertificateOwnerRef != nil { + in, out := &in.EnableCertificateOwnerRef, &out.EnableCertificateOwnerRef + *out = new(bool) + **out = **in + } + if in.EnableGatewayAPI != nil { + in, out := &in.EnableGatewayAPI, &out.EnableGatewayAPI + *out = new(bool) + **out = **in + } + if in.CopiedAnnotationPrefixes != nil { + in, out := &in.CopiedAnnotationPrefixes, &out.CopiedAnnotationPrefixes + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.NumberOfConcurrentWorkers != nil { + in, out := &in.NumberOfConcurrentWorkers, &out.NumberOfConcurrentWorkers + *out = new(int32) + **out = **in + } + if in.MaxConcurrentChallenges != nil { + in, out := &in.MaxConcurrentChallenges, &out.MaxConcurrentChallenges + *out = new(int32) + **out = **in + } + in.MetricsTLSConfig.DeepCopyInto(&out.MetricsTLSConfig) + if in.EnablePprof != nil { + in, out := &in.EnablePprof, &out.EnablePprof + *out = new(bool) + **out = **in + } + in.Logging.DeepCopyInto(&out.Logging) + if in.FeatureGates != nil { + in, out := &in.FeatureGates, &out.FeatureGates + *out = make(map[string]bool, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + in.IngressShimConfig.DeepCopyInto(&out.IngressShimConfig) + in.ACMEHTTP01Config.DeepCopyInto(&out.ACMEHTTP01Config) + in.ACMEDNS01Config.DeepCopyInto(&out.ACMEDNS01Config) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ControllerConfiguration. +func (in *ControllerConfiguration) DeepCopy() *ControllerConfiguration { + if in == nil { + return nil + } + out := new(ControllerConfiguration) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ControllerConfiguration) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *IngressShimConfig) DeepCopyInto(out *IngressShimConfig) { + *out = *in + if in.DefaultAutoCertificateAnnotations != nil { + in, out := &in.DefaultAutoCertificateAnnotations, &out.DefaultAutoCertificateAnnotations + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.ExtraCertificateAnnotations != nil { + in, out := &in.ExtraCertificateAnnotations, &out.ExtraCertificateAnnotations + *out = make([]string, len(*in)) + copy(*out, *in) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new IngressShimConfig. +func (in *IngressShimConfig) DeepCopy() *IngressShimConfig { + if in == nil { + return nil + } + out := new(IngressShimConfig) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *LeaderElectionConfig) DeepCopyInto(out *LeaderElectionConfig) { + *out = *in + in.LeaderElectionConfig.DeepCopyInto(&out.LeaderElectionConfig) + if in.HealthzTimeout != nil { + in, out := &in.HealthzTimeout, &out.HealthzTimeout + *out = new(sharedv1alpha1.Duration) + **out = **in + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LeaderElectionConfig. +func (in *LeaderElectionConfig) DeepCopy() *LeaderElectionConfig { + if in == nil { + return nil + } + out := new(LeaderElectionConfig) + in.DeepCopyInto(out) + return out +} diff --git a/pkg/apis/config/shared/doc.go b/pkg/apis/config/shared/doc.go new file mode 100644 index 00000000000..f598e21362f --- /dev/null +++ b/pkg/apis/config/shared/doc.go @@ -0,0 +1,18 @@ +/* +Copyright 2021 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. +*/ + +// Package shared contains shared types for the cert-manager configuration API +package shared diff --git a/internal/apis/config/webhook/v1alpha1/conversion.go b/pkg/apis/config/shared/v1alpha1/doc.go similarity index 93% rename from internal/apis/config/webhook/v1alpha1/conversion.go rename to pkg/apis/config/shared/v1alpha1/doc.go index 335956697c5..ab45229806e 100644 --- a/internal/apis/config/webhook/v1alpha1/conversion.go +++ b/pkg/apis/config/shared/v1alpha1/doc.go @@ -14,4 +14,5 @@ See the License for the specific language governing permissions and limitations under the License. */ +// +k8s:deepcopy-gen=package,register package v1alpha1 diff --git a/pkg/apis/config/shared/v1alpha1/types_duration.go b/pkg/apis/config/shared/v1alpha1/types_duration.go new file mode 100644 index 00000000000..7d222281488 --- /dev/null +++ b/pkg/apis/config/shared/v1alpha1/types_duration.go @@ -0,0 +1,65 @@ +/* +Copyright 2021 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. +*/ + +package v1alpha1 + +import ( + "encoding/json" + "fmt" + "time" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// Duration is present for backwards compatibility for fields that +// previously used time.Duration. +// +k8s:conversion-gen=false +// +kubebuilder:validation:XIntOrString +type Duration struct { + // Duration holds the duration + Duration metav1.Duration +} + +func DurationFromMetav1(d metav1.Duration) *Duration { + return &Duration{Duration: d} +} + +func DurationFromTime(d time.Duration) *Duration { + return DurationFromMetav1(metav1.Duration{Duration: d}) +} + +func (t *Duration) MarshalJSON() ([]byte, error) { + return t.Duration.MarshalJSON() +} + +func (t *Duration) UnmarshalJSON(b []byte) error { + if len(b) > 0 && b[0] == '"' { + // string values unmarshal as metav1.Duration + return json.Unmarshal(b, &t.Duration) + } + if err := json.Unmarshal(b, &t.Duration.Duration); err != nil { + return fmt.Errorf("invalid duration %q: %w", string(b), err) + } + return nil +} + +func (t *Duration) IsZero() bool { + if t == nil { + return true + } + + return t.Duration.Duration == 0 +} diff --git a/pkg/apis/config/shared/v1alpha1/types_leaderelection.go b/pkg/apis/config/shared/v1alpha1/types_leaderelection.go new file mode 100644 index 00000000000..d5f06f224b9 --- /dev/null +++ b/pkg/apis/config/shared/v1alpha1/types_leaderelection.go @@ -0,0 +1,42 @@ +/* +Copyright 2021 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. +*/ + +package v1alpha1 + +type LeaderElectionConfig struct { + // If true, cert-manager will perform leader election between instances to + // ensure no more than one instance of cert-manager operates at a time + Enabled *bool `json:"enabled,omitempty"` + + // Namespace used to perform leader election. Only used if leader election is enabled + Namespace string `json:"namespace,omitempty"` + + // 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. + LeaseDuration *Duration `json:"leaseDuration,omitempty"` + + // 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. + RenewDeadline *Duration `json:"renewDeadline,omitempty"` + + // The duration the clients should wait between attempting acquisition and renewal + // of a leadership. This is only applicable if leader election is enabled. + RetryPeriod *Duration `json:"retryPeriod,omitempty"` +} diff --git a/pkg/apis/config/shared/v1alpha1/types_tlsconfig.go b/pkg/apis/config/shared/v1alpha1/types_tlsconfig.go new file mode 100644 index 00000000000..1ff7a35d8de --- /dev/null +++ b/pkg/apis/config/shared/v1alpha1/types_tlsconfig.go @@ -0,0 +1,69 @@ +/* +Copyright 2021 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. +*/ + +package v1alpha1 + +// TLSConfig configures how TLS certificates are sourced for serving. +// Only one of 'filesystem' or 'dynamic' may be specified. +type TLSConfig struct { + // 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. + CipherSuites []string `json:"cipherSuites,omitempty"` + + // 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. + MinTLSVersion string `json:"minTLSVersion,omitempty"` + + // 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. + Filesystem FilesystemServingConfig `json:"filesystem"` + + // When Dynamic serving is enabled, the controller will generate a CA used to sign + // certificates and persist it into a Kubernetes Secret resource (for other replicas of the + // controller to consume). + // It will then generate a certificate in-memory for itself using this CA to serve with. + Dynamic DynamicServingConfig `json:"dynamic"` +} + +// DynamicServingConfig makes the controller generate a CA and persist it into Secret resources. +// This CA will be used by all instances of the controller for signing serving certificates. +type DynamicServingConfig struct { + // Namespace of the Kubernetes Secret resource containing the TLS certificate + // used as a CA to sign dynamic serving certificates. + SecretNamespace string `json:"secretNamespace,omitempty"` + + // Secret resource name containing the TLS certificate + // used as a CA to sign dynamic serving certificates. + SecretName string `json:"secretName,omitempty"` + + // DNSNames that must be present on serving certificates signed by the CA. + DNSNames []string `json:"dnsNames,omitempty"` + + // LeafDuration is a customizable duration on serving certificates signed by the CA. + LeafDuration *Duration `json:"leafDuration,omitempty"` +} + +// 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. +type FilesystemServingConfig struct { + // Path to a file containing TLS certificate & chain to serve with + CertFile string `json:"certFile,omitempty"` + + // Path to a file containing a TLS private key to serve with + KeyFile string `json:"keyFile,omitempty"` +} diff --git a/pkg/apis/config/shared/v1alpha1/zz_generated.deepcopy.go b/pkg/apis/config/shared/v1alpha1/zz_generated.deepcopy.go new file mode 100644 index 00000000000..cec22bf41a4 --- /dev/null +++ b/pkg/apis/config/shared/v1alpha1/zz_generated.deepcopy.go @@ -0,0 +1,140 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +/* +Copyright 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. +*/ + +// Code generated by deepcopy-gen. DO NOT EDIT. + +package v1alpha1 + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Duration) DeepCopyInto(out *Duration) { + *out = *in + out.Duration = in.Duration + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Duration. +func (in *Duration) DeepCopy() *Duration { + if in == nil { + return nil + } + out := new(Duration) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *DynamicServingConfig) DeepCopyInto(out *DynamicServingConfig) { + *out = *in + if in.DNSNames != nil { + in, out := &in.DNSNames, &out.DNSNames + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.LeafDuration != nil { + in, out := &in.LeafDuration, &out.LeafDuration + *out = new(Duration) + **out = **in + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DynamicServingConfig. +func (in *DynamicServingConfig) DeepCopy() *DynamicServingConfig { + if in == nil { + return nil + } + out := new(DynamicServingConfig) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *FilesystemServingConfig) DeepCopyInto(out *FilesystemServingConfig) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FilesystemServingConfig. +func (in *FilesystemServingConfig) DeepCopy() *FilesystemServingConfig { + if in == nil { + return nil + } + out := new(FilesystemServingConfig) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *LeaderElectionConfig) DeepCopyInto(out *LeaderElectionConfig) { + *out = *in + if in.Enabled != nil { + in, out := &in.Enabled, &out.Enabled + *out = new(bool) + **out = **in + } + if in.LeaseDuration != nil { + in, out := &in.LeaseDuration, &out.LeaseDuration + *out = new(Duration) + **out = **in + } + if in.RenewDeadline != nil { + in, out := &in.RenewDeadline, &out.RenewDeadline + *out = new(Duration) + **out = **in + } + if in.RetryPeriod != nil { + in, out := &in.RetryPeriod, &out.RetryPeriod + *out = new(Duration) + **out = **in + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LeaderElectionConfig. +func (in *LeaderElectionConfig) DeepCopy() *LeaderElectionConfig { + if in == nil { + return nil + } + out := new(LeaderElectionConfig) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TLSConfig) DeepCopyInto(out *TLSConfig) { + *out = *in + if in.CipherSuites != nil { + in, out := &in.CipherSuites, &out.CipherSuites + *out = make([]string, len(*in)) + copy(*out, *in) + } + out.Filesystem = in.Filesystem + in.Dynamic.DeepCopyInto(&out.Dynamic) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TLSConfig. +func (in *TLSConfig) DeepCopy() *TLSConfig { + if in == nil { + return nil + } + out := new(TLSConfig) + in.DeepCopyInto(out) + return out +} diff --git a/pkg/apis/config/webhook/v1alpha1/types.go b/pkg/apis/config/webhook/v1alpha1/types.go index 13b01a84766..38cecb76157 100644 --- a/pkg/apis/config/webhook/v1alpha1/types.go +++ b/pkg/apis/config/webhook/v1alpha1/types.go @@ -16,7 +16,12 @@ limitations under the License. package v1alpha1 -import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + logsapi "k8s.io/component-base/logs/api/v1" + + sharedv1alpha1 "github.com/cert-manager/cert-manager/pkg/apis/config/shared/v1alpha1" +) // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object @@ -24,15 +29,17 @@ type WebhookConfiguration struct { metav1.TypeMeta `json:",inline"` // 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. - SecurePort *int `json:"securePort,omitempty"` + SecurePort *int32 `json:"securePort,omitempty"` // 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. - HealthzPort *int `json:"healthzPort,omitempty"` + HealthzPort *int32 `json:"healthzPort,omitempty"` // tlsConfig is used to configure the secure listener's TLS settings. - TLSConfig TLSConfig `json:"tlsConfig"` + TLSConfig sharedv1alpha1.TLSConfig `json:"tlsConfig"` // 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. @@ -49,60 +56,20 @@ type WebhookConfiguration struct { // Defaults to 'localhost:6060'. PprofAddress string `json:"pprofAddress,omitempty"` + // logging configures the logging behaviour of the webhook. + // https://pkg.go.dev/k8s.io/component-base@v0.27.3/logs/api/v1#LoggingConfiguration + Logging logsapi.LoggingConfiguration `json:"logging"` + // featureGates is a map of feature names to bools that enable or disable experimental // features. - // Default: nil // +optional FeatureGates map[string]bool `json:"featureGates,omitempty"` -} - -// TLSConfig configures how TLS certificates are sourced for serving. -// Only one of 'filesystem' or 'dynamic' may be specified. -type TLSConfig struct { - // 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. - CipherSuites []string `json:"cipherSuites,omitempty"` - - // 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. - MinTLSVersion string `json:"minTLSVersion,omitempty"` - - // 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. - Filesystem FilesystemServingConfig `json:"filesystem"` - - // 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). - Dynamic DynamicServingConfig `json:"dynamic"` -} - -// 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. -type DynamicServingConfig struct { - // Namespace of the Kubernetes Secret resource containing the TLS certificate - // used as a CA to sign dynamic serving certificates. - SecretNamespace string `json:"secretNamespace,omitempty"` - - // Namespace of the Kubernetes Secret resource containing the TLS certificate - // used as a CA to sign dynamic serving certificates. - SecretName string `json:"secretName,omitempty"` - - // DNSNames that must be present on serving certificates signed by the CA. - DNSNames []string `json:"dnsNames,omitempty"` -} -// 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. -type FilesystemServingConfig struct { - // Path to a file containing TLS certificate & chain to serve with - CertFile string `json:"certFile,omitempty"` + // The host and port that the metrics endpoint should listen on. + // The value "0" disables the metrics server. + // Defaults to '0.0.0.0:9402'. + MetricsListenAddress string `json:"metricsListenAddress,omitempty"` - // Path to a file containing a TLS private key to server with - KeyFile string `json:"keyFile,omitempty"` + // metricsTLSConfig is used to configure the metrics server TLS settings. + MetricsTLSConfig sharedv1alpha1.TLSConfig `json:"metricsTLSConfig"` } diff --git a/pkg/apis/config/webhook/v1alpha1/zz_generated.deepcopy.go b/pkg/apis/config/webhook/v1alpha1/zz_generated.deepcopy.go index d5674181ee9..43f50d78d05 100644 --- a/pkg/apis/config/webhook/v1alpha1/zz_generated.deepcopy.go +++ b/pkg/apis/config/webhook/v1alpha1/zz_generated.deepcopy.go @@ -25,81 +25,22 @@ import ( runtime "k8s.io/apimachinery/pkg/runtime" ) -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *DynamicServingConfig) DeepCopyInto(out *DynamicServingConfig) { - *out = *in - if in.DNSNames != nil { - in, out := &in.DNSNames, &out.DNSNames - *out = make([]string, len(*in)) - copy(*out, *in) - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DynamicServingConfig. -func (in *DynamicServingConfig) DeepCopy() *DynamicServingConfig { - if in == nil { - return nil - } - out := new(DynamicServingConfig) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *FilesystemServingConfig) DeepCopyInto(out *FilesystemServingConfig) { - *out = *in - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FilesystemServingConfig. -func (in *FilesystemServingConfig) DeepCopy() *FilesystemServingConfig { - if in == nil { - return nil - } - out := new(FilesystemServingConfig) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *TLSConfig) DeepCopyInto(out *TLSConfig) { - *out = *in - if in.CipherSuites != nil { - in, out := &in.CipherSuites, &out.CipherSuites - *out = make([]string, len(*in)) - copy(*out, *in) - } - out.Filesystem = in.Filesystem - in.Dynamic.DeepCopyInto(&out.Dynamic) - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TLSConfig. -func (in *TLSConfig) DeepCopy() *TLSConfig { - if in == nil { - return nil - } - out := new(TLSConfig) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *WebhookConfiguration) DeepCopyInto(out *WebhookConfiguration) { *out = *in out.TypeMeta = in.TypeMeta if in.SecurePort != nil { in, out := &in.SecurePort, &out.SecurePort - *out = new(int) + *out = new(int32) **out = **in } if in.HealthzPort != nil { in, out := &in.HealthzPort, &out.HealthzPort - *out = new(int) + *out = new(int32) **out = **in } in.TLSConfig.DeepCopyInto(&out.TLSConfig) + in.Logging.DeepCopyInto(&out.Logging) if in.FeatureGates != nil { in, out := &in.FeatureGates, &out.FeatureGates *out = make(map[string]bool, len(*in)) @@ -107,6 +48,7 @@ func (in *WebhookConfiguration) DeepCopyInto(out *WebhookConfiguration) { (*out)[key] = val } } + in.MetricsTLSConfig.DeepCopyInto(&out.MetricsTLSConfig) return } diff --git a/pkg/apis/experimental/v1alpha1/types.go b/pkg/apis/experimental/v1alpha1/types.go index 3a6a54d7a04..7e66b9a2be1 100644 --- a/pkg/apis/experimental/v1alpha1/types.go +++ b/pkg/apis/experimental/v1alpha1/types.go @@ -34,7 +34,7 @@ const ( // the experimental.cert-manager.io/request-duration annotation. This // has to be the same as the minimum allowed value for // spec.expirationSeconds of a CertificateSigningRequest - CertificateSigningRequestMinimumDuration = time.Duration(time.Second * 600) + CertificateSigningRequestMinimumDuration = time.Second * 600 ) // SelfSigned Issuer specific Annotations diff --git a/pkg/apis/meta/doc.go b/pkg/apis/meta/doc.go index f391663af4e..378fecfb23c 100644 --- a/pkg/apis/meta/doc.go +++ b/pkg/apis/meta/doc.go @@ -14,8 +14,6 @@ See the License for the specific language governing permissions and limitations under the License. */ -// +groupName=meta.cert-manager.io - // Package meta contains meta types for cert-manager APIs package meta diff --git a/pkg/apis/meta/v1/doc.go b/pkg/apis/meta/v1/doc.go index 9a673685d6e..7b5d3d1fb38 100644 --- a/pkg/apis/meta/v1/doc.go +++ b/pkg/apis/meta/v1/doc.go @@ -16,6 +16,6 @@ limitations under the License. // Package v1 contains meta types for cert-manager APIs // +k8s:deepcopy-gen=package +// +k8s:openapi-gen=true // +gencrdrefdocs:force -// +groupName=meta.cert-manager.io package v1 diff --git a/pkg/apis/meta/v1/types.go b/pkg/apis/meta/v1/types.go index 24e72d15ffd..2b294e1a922 100644 --- a/pkg/apis/meta/v1/types.go +++ b/pkg/apis/meta/v1/types.go @@ -24,7 +24,7 @@ type ConditionStatus string // the condition; "ConditionFalse" means a resource is not in the condition; // "ConditionUnknown" means kubernetes can't decide if a resource is in the // condition or not. In the future, we could add other intermediate -// conditions, e.g. ConditionDegraded. +// conditions, e.g., ConditionDegraded. const ( // ConditionTrue represents the fact that a given condition is true ConditionTrue ConditionStatus = "True" @@ -37,7 +37,7 @@ const ( ) // A reference to an object in the same namespace as the referent. -// If the referent is a cluster-scoped resource (e.g. a ClusterIssuer), +// 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 @@ -48,18 +48,24 @@ type LocalObjectReference struct { Name string `json:"name"` } -// ObjectReference is a reference to an object with a given name, kind and group. -type ObjectReference struct { - // Name of the resource being referred to. +// IssuerReference is a reference to a certificate issuer object with a given name, kind and group. +type IssuerReference struct { + // Name of the issuer being referred to. Name string `json:"name"` - // Kind of the resource being referred to. + // Kind of the issuer being referred to. + // Defaults to 'Issuer'. // +optional Kind string `json:"kind,omitempty"` - // Group of the resource being referred to. + // Group of the issuer being referred to. + // Defaults to 'cert-manager.io'. // +optional Group string `json:"group,omitempty"` } +// ObjectReference is a reference to an object with a given name, kind and group. +// Deprecated: Use IssuerReference instead. +type ObjectReference = IssuerReference + // A reference to a specific 'key' within a Secret resource. // In some instances, `key` is a required field. type SecretKeySelector struct { diff --git a/pkg/apis/meta/v1/zz_generated.deepcopy.go b/pkg/apis/meta/v1/zz_generated.deepcopy.go index 9fa10e5e665..0d4af070867 100644 --- a/pkg/apis/meta/v1/zz_generated.deepcopy.go +++ b/pkg/apis/meta/v1/zz_generated.deepcopy.go @@ -22,33 +22,33 @@ limitations under the License. package v1 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *LocalObjectReference) DeepCopyInto(out *LocalObjectReference) { +func (in *IssuerReference) DeepCopyInto(out *IssuerReference) { *out = *in return } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LocalObjectReference. -func (in *LocalObjectReference) DeepCopy() *LocalObjectReference { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new IssuerReference. +func (in *IssuerReference) DeepCopy() *IssuerReference { if in == nil { return nil } - out := new(LocalObjectReference) + out := new(IssuerReference) in.DeepCopyInto(out) return out } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ObjectReference) DeepCopyInto(out *ObjectReference) { +func (in *LocalObjectReference) DeepCopyInto(out *LocalObjectReference) { *out = *in return } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ObjectReference. -func (in *ObjectReference) DeepCopy() *ObjectReference { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LocalObjectReference. +func (in *LocalObjectReference) DeepCopy() *LocalObjectReference { if in == nil { return nil } - out := new(ObjectReference) + out := new(LocalObjectReference) in.DeepCopyInto(out) return out } diff --git a/pkg/cainjector/configfile/configfile.go b/pkg/cainjector/configfile/configfile.go new file mode 100644 index 00000000000..29c26523eab --- /dev/null +++ b/pkg/cainjector/configfile/configfile.go @@ -0,0 +1,86 @@ +/* +Copyright 2021 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. +*/ + +package configfile + +import ( + "fmt" + + "k8s.io/apimachinery/pkg/runtime/serializer" + + config "github.com/cert-manager/cert-manager/internal/apis/config/cainjector" + "github.com/cert-manager/cert-manager/internal/apis/config/cainjector/scheme" +) + +type CAInjectorConfigFile struct { + Config *config.CAInjectorConfiguration +} + +func New() *CAInjectorConfigFile { + return &CAInjectorConfigFile{ + Config: &config.CAInjectorConfiguration{}, + } +} + +func decodeConfiguration(data []byte) (*config.CAInjectorConfiguration, error) { + _, codec, err := scheme.NewSchemeAndCodecs(serializer.EnableStrict) + if err != nil { + return nil, err + } + + obj, _, err := codec.UniversalDecoder().Decode(data, nil, nil) + if err != nil { + return nil, fmt.Errorf("failed to decode: %w", err) + } + + c, ok := obj.(*config.CAInjectorConfiguration) + if !ok { + return nil, fmt.Errorf("failed to cast object to ControllerConfiguration, unexpected type") + } + + return c, nil + +} + +func (cfg *CAInjectorConfigFile) DecodeAndConfigure(data []byte) error { + config, err := decodeConfiguration(data) + if err != nil { + return err + } + cfg.Config = config + + return nil +} + +func (cfg *CAInjectorConfigFile) GetPathRefs() ([]*string, error) { + paths, err := CAInjectorConfigurationPathRefs(cfg.Config) + if err != nil { + return nil, err + } + return paths, err + +} + +// CAInjectorConfigurationPathRefs returns pointers to all the CAInjectorConfiguration fields that contain filepaths. +// You might use this, for example, to resolve all relative paths against some common root before +// passing the configuration to the application. This method must be kept up to date as new fields are added. +func CAInjectorConfigurationPathRefs(cfg *config.CAInjectorConfiguration) ([]*string, error) { + return []*string{ + &cfg.MetricsTLSConfig.Filesystem.KeyFile, + &cfg.MetricsTLSConfig.Filesystem.CertFile, + &cfg.KubeConfig, + }, nil +} diff --git a/pkg/cainjector/configfile/configfile_test.go b/pkg/cainjector/configfile/configfile_test.go new file mode 100644 index 00000000000..d180be8ea15 --- /dev/null +++ b/pkg/cainjector/configfile/configfile_test.go @@ -0,0 +1,54 @@ +/* +Copyright 2021 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. +*/ + +package configfile + +import ( + "fmt" + "testing" + + "github.com/cert-manager/cert-manager/pkg/util/configfile" +) + +func TestFSLoader_Load(t *testing.T) { + const expectedFilename = "/path/to/config/file" + const kubeConfigPath = "path/to/kubeconfig/file" + + cainjectorConfig := New() + + loader, err := configfile.NewConfigurationFSLoader(func(filename string) ([]byte, error) { + if filename != expectedFilename { + t.Fatalf("unexpected filename %q passed to ReadFile", filename) + return nil, fmt.Errorf("unexpected filename %q", filename) + } + return []byte(fmt.Sprintf(`apiVersion: cainjector.config.cert-manager.io/v1alpha1 +kind: CAInjectorConfiguration +kubeConfig: %s`, kubeConfigPath)), nil + }, expectedFilename) + if err != nil { + t.Fatal(err) + } + + if err := loader.Load(cainjectorConfig); err != nil { + t.Fatal(err) + } + + // the config loader will force paths to be 'absolute' if they are provided as relative. + absKubeConfigPath := "/path/to/config/path/to/kubeconfig/file" + if cainjectorConfig.Config.KubeConfig != absKubeConfigPath { + t.Errorf("expected kubeConfig to be set to %q but got %q", absKubeConfigPath, cainjectorConfig.Config.KubeConfig) + } +} diff --git a/pkg/client/applyconfigurations/acme/v1/acmeauthorization.go b/pkg/client/applyconfigurations/acme/v1/acmeauthorization.go new file mode 100644 index 00000000000..f4b4575b36f --- /dev/null +++ b/pkg/client/applyconfigurations/acme/v1/acmeauthorization.go @@ -0,0 +1,84 @@ +/* +Copyright 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. +*/ + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1 + +import ( + acmev1 "github.com/cert-manager/cert-manager/pkg/apis/acme/v1" +) + +// ACMEAuthorizationApplyConfiguration represents a declarative configuration of the ACMEAuthorization type for use +// with apply. +type ACMEAuthorizationApplyConfiguration struct { + URL *string `json:"url,omitempty"` + Identifier *string `json:"identifier,omitempty"` + Wildcard *bool `json:"wildcard,omitempty"` + InitialState *acmev1.State `json:"initialState,omitempty"` + Challenges []ACMEChallengeApplyConfiguration `json:"challenges,omitempty"` +} + +// ACMEAuthorizationApplyConfiguration constructs a declarative configuration of the ACMEAuthorization type for use with +// apply. +func ACMEAuthorization() *ACMEAuthorizationApplyConfiguration { + return &ACMEAuthorizationApplyConfiguration{} +} + +// WithURL sets the URL field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the URL field is set to the value of the last call. +func (b *ACMEAuthorizationApplyConfiguration) WithURL(value string) *ACMEAuthorizationApplyConfiguration { + b.URL = &value + return b +} + +// WithIdentifier sets the Identifier field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Identifier field is set to the value of the last call. +func (b *ACMEAuthorizationApplyConfiguration) WithIdentifier(value string) *ACMEAuthorizationApplyConfiguration { + b.Identifier = &value + return b +} + +// WithWildcard sets the Wildcard field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Wildcard field is set to the value of the last call. +func (b *ACMEAuthorizationApplyConfiguration) WithWildcard(value bool) *ACMEAuthorizationApplyConfiguration { + b.Wildcard = &value + return b +} + +// WithInitialState sets the InitialState field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the InitialState field is set to the value of the last call. +func (b *ACMEAuthorizationApplyConfiguration) WithInitialState(value acmev1.State) *ACMEAuthorizationApplyConfiguration { + b.InitialState = &value + return b +} + +// WithChallenges adds the given value to the Challenges field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the Challenges field. +func (b *ACMEAuthorizationApplyConfiguration) WithChallenges(values ...*ACMEChallengeApplyConfiguration) *ACMEAuthorizationApplyConfiguration { + for i := range values { + if values[i] == nil { + panic("nil value passed to WithChallenges") + } + b.Challenges = append(b.Challenges, *values[i]) + } + return b +} diff --git a/pkg/client/applyconfigurations/acme/v1/acmechallenge.go b/pkg/client/applyconfigurations/acme/v1/acmechallenge.go new file mode 100644 index 00000000000..746948ceb83 --- /dev/null +++ b/pkg/client/applyconfigurations/acme/v1/acmechallenge.go @@ -0,0 +1,57 @@ +/* +Copyright 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. +*/ + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1 + +// ACMEChallengeApplyConfiguration represents a declarative configuration of the ACMEChallenge type for use +// with apply. +type ACMEChallengeApplyConfiguration struct { + URL *string `json:"url,omitempty"` + Token *string `json:"token,omitempty"` + Type *string `json:"type,omitempty"` +} + +// ACMEChallengeApplyConfiguration constructs a declarative configuration of the ACMEChallenge type for use with +// apply. +func ACMEChallenge() *ACMEChallengeApplyConfiguration { + return &ACMEChallengeApplyConfiguration{} +} + +// WithURL sets the URL field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the URL field is set to the value of the last call. +func (b *ACMEChallengeApplyConfiguration) WithURL(value string) *ACMEChallengeApplyConfiguration { + b.URL = &value + return b +} + +// WithToken sets the Token field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Token field is set to the value of the last call. +func (b *ACMEChallengeApplyConfiguration) WithToken(value string) *ACMEChallengeApplyConfiguration { + b.Token = &value + return b +} + +// WithType sets the Type field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Type field is set to the value of the last call. +func (b *ACMEChallengeApplyConfiguration) WithType(value string) *ACMEChallengeApplyConfiguration { + b.Type = &value + return b +} diff --git a/pkg/client/applyconfigurations/acme/v1/acmechallengesolver.go b/pkg/client/applyconfigurations/acme/v1/acmechallengesolver.go new file mode 100644 index 00000000000..c52eb1f7523 --- /dev/null +++ b/pkg/client/applyconfigurations/acme/v1/acmechallengesolver.go @@ -0,0 +1,57 @@ +/* +Copyright 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. +*/ + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1 + +// ACMEChallengeSolverApplyConfiguration represents a declarative configuration of the ACMEChallengeSolver type for use +// with apply. +type ACMEChallengeSolverApplyConfiguration struct { + Selector *CertificateDNSNameSelectorApplyConfiguration `json:"selector,omitempty"` + HTTP01 *ACMEChallengeSolverHTTP01ApplyConfiguration `json:"http01,omitempty"` + DNS01 *ACMEChallengeSolverDNS01ApplyConfiguration `json:"dns01,omitempty"` +} + +// ACMEChallengeSolverApplyConfiguration constructs a declarative configuration of the ACMEChallengeSolver type for use with +// apply. +func ACMEChallengeSolver() *ACMEChallengeSolverApplyConfiguration { + return &ACMEChallengeSolverApplyConfiguration{} +} + +// WithSelector sets the Selector field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Selector field is set to the value of the last call. +func (b *ACMEChallengeSolverApplyConfiguration) WithSelector(value *CertificateDNSNameSelectorApplyConfiguration) *ACMEChallengeSolverApplyConfiguration { + b.Selector = value + return b +} + +// WithHTTP01 sets the HTTP01 field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the HTTP01 field is set to the value of the last call. +func (b *ACMEChallengeSolverApplyConfiguration) WithHTTP01(value *ACMEChallengeSolverHTTP01ApplyConfiguration) *ACMEChallengeSolverApplyConfiguration { + b.HTTP01 = value + return b +} + +// WithDNS01 sets the DNS01 field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the DNS01 field is set to the value of the last call. +func (b *ACMEChallengeSolverApplyConfiguration) WithDNS01(value *ACMEChallengeSolverDNS01ApplyConfiguration) *ACMEChallengeSolverApplyConfiguration { + b.DNS01 = value + return b +} diff --git a/pkg/client/applyconfigurations/acme/v1/acmechallengesolverdns01.go b/pkg/client/applyconfigurations/acme/v1/acmechallengesolverdns01.go new file mode 100644 index 00000000000..beedfce393f --- /dev/null +++ b/pkg/client/applyconfigurations/acme/v1/acmechallengesolverdns01.go @@ -0,0 +1,124 @@ +/* +Copyright 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. +*/ + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1 + +import ( + acmev1 "github.com/cert-manager/cert-manager/pkg/apis/acme/v1" +) + +// ACMEChallengeSolverDNS01ApplyConfiguration represents a declarative configuration of the ACMEChallengeSolverDNS01 type for use +// with apply. +type ACMEChallengeSolverDNS01ApplyConfiguration struct { + CNAMEStrategy *acmev1.CNAMEStrategy `json:"cnameStrategy,omitempty"` + Akamai *ACMEIssuerDNS01ProviderAkamaiApplyConfiguration `json:"akamai,omitempty"` + CloudDNS *ACMEIssuerDNS01ProviderCloudDNSApplyConfiguration `json:"cloudDNS,omitempty"` + Cloudflare *ACMEIssuerDNS01ProviderCloudflareApplyConfiguration `json:"cloudflare,omitempty"` + Route53 *ACMEIssuerDNS01ProviderRoute53ApplyConfiguration `json:"route53,omitempty"` + AzureDNS *ACMEIssuerDNS01ProviderAzureDNSApplyConfiguration `json:"azureDNS,omitempty"` + DigitalOcean *ACMEIssuerDNS01ProviderDigitalOceanApplyConfiguration `json:"digitalocean,omitempty"` + AcmeDNS *ACMEIssuerDNS01ProviderAcmeDNSApplyConfiguration `json:"acmeDNS,omitempty"` + RFC2136 *ACMEIssuerDNS01ProviderRFC2136ApplyConfiguration `json:"rfc2136,omitempty"` + Webhook *ACMEIssuerDNS01ProviderWebhookApplyConfiguration `json:"webhook,omitempty"` +} + +// ACMEChallengeSolverDNS01ApplyConfiguration constructs a declarative configuration of the ACMEChallengeSolverDNS01 type for use with +// apply. +func ACMEChallengeSolverDNS01() *ACMEChallengeSolverDNS01ApplyConfiguration { + return &ACMEChallengeSolverDNS01ApplyConfiguration{} +} + +// WithCNAMEStrategy sets the CNAMEStrategy field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the CNAMEStrategy field is set to the value of the last call. +func (b *ACMEChallengeSolverDNS01ApplyConfiguration) WithCNAMEStrategy(value acmev1.CNAMEStrategy) *ACMEChallengeSolverDNS01ApplyConfiguration { + b.CNAMEStrategy = &value + return b +} + +// WithAkamai sets the Akamai field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Akamai field is set to the value of the last call. +func (b *ACMEChallengeSolverDNS01ApplyConfiguration) WithAkamai(value *ACMEIssuerDNS01ProviderAkamaiApplyConfiguration) *ACMEChallengeSolverDNS01ApplyConfiguration { + b.Akamai = value + return b +} + +// WithCloudDNS sets the CloudDNS field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the CloudDNS field is set to the value of the last call. +func (b *ACMEChallengeSolverDNS01ApplyConfiguration) WithCloudDNS(value *ACMEIssuerDNS01ProviderCloudDNSApplyConfiguration) *ACMEChallengeSolverDNS01ApplyConfiguration { + b.CloudDNS = value + return b +} + +// WithCloudflare sets the Cloudflare field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Cloudflare field is set to the value of the last call. +func (b *ACMEChallengeSolverDNS01ApplyConfiguration) WithCloudflare(value *ACMEIssuerDNS01ProviderCloudflareApplyConfiguration) *ACMEChallengeSolverDNS01ApplyConfiguration { + b.Cloudflare = value + return b +} + +// WithRoute53 sets the Route53 field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Route53 field is set to the value of the last call. +func (b *ACMEChallengeSolverDNS01ApplyConfiguration) WithRoute53(value *ACMEIssuerDNS01ProviderRoute53ApplyConfiguration) *ACMEChallengeSolverDNS01ApplyConfiguration { + b.Route53 = value + return b +} + +// WithAzureDNS sets the AzureDNS field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the AzureDNS field is set to the value of the last call. +func (b *ACMEChallengeSolverDNS01ApplyConfiguration) WithAzureDNS(value *ACMEIssuerDNS01ProviderAzureDNSApplyConfiguration) *ACMEChallengeSolverDNS01ApplyConfiguration { + b.AzureDNS = value + return b +} + +// WithDigitalOcean sets the DigitalOcean field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the DigitalOcean field is set to the value of the last call. +func (b *ACMEChallengeSolverDNS01ApplyConfiguration) WithDigitalOcean(value *ACMEIssuerDNS01ProviderDigitalOceanApplyConfiguration) *ACMEChallengeSolverDNS01ApplyConfiguration { + b.DigitalOcean = value + return b +} + +// WithAcmeDNS sets the AcmeDNS field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the AcmeDNS field is set to the value of the last call. +func (b *ACMEChallengeSolverDNS01ApplyConfiguration) WithAcmeDNS(value *ACMEIssuerDNS01ProviderAcmeDNSApplyConfiguration) *ACMEChallengeSolverDNS01ApplyConfiguration { + b.AcmeDNS = value + return b +} + +// WithRFC2136 sets the RFC2136 field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the RFC2136 field is set to the value of the last call. +func (b *ACMEChallengeSolverDNS01ApplyConfiguration) WithRFC2136(value *ACMEIssuerDNS01ProviderRFC2136ApplyConfiguration) *ACMEChallengeSolverDNS01ApplyConfiguration { + b.RFC2136 = value + return b +} + +// WithWebhook sets the Webhook field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Webhook field is set to the value of the last call. +func (b *ACMEChallengeSolverDNS01ApplyConfiguration) WithWebhook(value *ACMEIssuerDNS01ProviderWebhookApplyConfiguration) *ACMEChallengeSolverDNS01ApplyConfiguration { + b.Webhook = value + return b +} diff --git a/pkg/client/applyconfigurations/acme/v1/acmechallengesolverhttp01.go b/pkg/client/applyconfigurations/acme/v1/acmechallengesolverhttp01.go new file mode 100644 index 00000000000..7c95caa1099 --- /dev/null +++ b/pkg/client/applyconfigurations/acme/v1/acmechallengesolverhttp01.go @@ -0,0 +1,48 @@ +/* +Copyright 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. +*/ + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1 + +// ACMEChallengeSolverHTTP01ApplyConfiguration represents a declarative configuration of the ACMEChallengeSolverHTTP01 type for use +// with apply. +type ACMEChallengeSolverHTTP01ApplyConfiguration struct { + Ingress *ACMEChallengeSolverHTTP01IngressApplyConfiguration `json:"ingress,omitempty"` + GatewayHTTPRoute *ACMEChallengeSolverHTTP01GatewayHTTPRouteApplyConfiguration `json:"gatewayHTTPRoute,omitempty"` +} + +// ACMEChallengeSolverHTTP01ApplyConfiguration constructs a declarative configuration of the ACMEChallengeSolverHTTP01 type for use with +// apply. +func ACMEChallengeSolverHTTP01() *ACMEChallengeSolverHTTP01ApplyConfiguration { + return &ACMEChallengeSolverHTTP01ApplyConfiguration{} +} + +// WithIngress sets the Ingress field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Ingress field is set to the value of the last call. +func (b *ACMEChallengeSolverHTTP01ApplyConfiguration) WithIngress(value *ACMEChallengeSolverHTTP01IngressApplyConfiguration) *ACMEChallengeSolverHTTP01ApplyConfiguration { + b.Ingress = value + return b +} + +// WithGatewayHTTPRoute sets the GatewayHTTPRoute field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the GatewayHTTPRoute field is set to the value of the last call. +func (b *ACMEChallengeSolverHTTP01ApplyConfiguration) WithGatewayHTTPRoute(value *ACMEChallengeSolverHTTP01GatewayHTTPRouteApplyConfiguration) *ACMEChallengeSolverHTTP01ApplyConfiguration { + b.GatewayHTTPRoute = value + return b +} diff --git a/pkg/client/applyconfigurations/acme/v1/acmechallengesolverhttp01gatewayhttproute.go b/pkg/client/applyconfigurations/acme/v1/acmechallengesolverhttp01gatewayhttproute.go new file mode 100644 index 00000000000..0c5f0ad59c4 --- /dev/null +++ b/pkg/client/applyconfigurations/acme/v1/acmechallengesolverhttp01gatewayhttproute.go @@ -0,0 +1,79 @@ +/* +Copyright 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. +*/ + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1 + +import ( + corev1 "k8s.io/api/core/v1" + apisv1 "sigs.k8s.io/gateway-api/apis/v1" +) + +// ACMEChallengeSolverHTTP01GatewayHTTPRouteApplyConfiguration represents a declarative configuration of the ACMEChallengeSolverHTTP01GatewayHTTPRoute type for use +// with apply. +type ACMEChallengeSolverHTTP01GatewayHTTPRouteApplyConfiguration struct { + ServiceType *corev1.ServiceType `json:"serviceType,omitempty"` + Labels map[string]string `json:"labels,omitempty"` + ParentRefs []apisv1.ParentReference `json:"parentRefs,omitempty"` + PodTemplate *ACMEChallengeSolverHTTP01IngressPodTemplateApplyConfiguration `json:"podTemplate,omitempty"` +} + +// ACMEChallengeSolverHTTP01GatewayHTTPRouteApplyConfiguration constructs a declarative configuration of the ACMEChallengeSolverHTTP01GatewayHTTPRoute type for use with +// apply. +func ACMEChallengeSolverHTTP01GatewayHTTPRoute() *ACMEChallengeSolverHTTP01GatewayHTTPRouteApplyConfiguration { + return &ACMEChallengeSolverHTTP01GatewayHTTPRouteApplyConfiguration{} +} + +// WithServiceType sets the ServiceType field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the ServiceType field is set to the value of the last call. +func (b *ACMEChallengeSolverHTTP01GatewayHTTPRouteApplyConfiguration) WithServiceType(value corev1.ServiceType) *ACMEChallengeSolverHTTP01GatewayHTTPRouteApplyConfiguration { + b.ServiceType = &value + return b +} + +// WithLabels puts the entries into the Labels field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, the entries provided by each call will be put on the Labels field, +// overwriting an existing map entries in Labels field with the same key. +func (b *ACMEChallengeSolverHTTP01GatewayHTTPRouteApplyConfiguration) WithLabels(entries map[string]string) *ACMEChallengeSolverHTTP01GatewayHTTPRouteApplyConfiguration { + if b.Labels == nil && len(entries) > 0 { + b.Labels = make(map[string]string, len(entries)) + } + for k, v := range entries { + b.Labels[k] = v + } + return b +} + +// WithParentRefs adds the given value to the ParentRefs field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the ParentRefs field. +func (b *ACMEChallengeSolverHTTP01GatewayHTTPRouteApplyConfiguration) WithParentRefs(values ...apisv1.ParentReference) *ACMEChallengeSolverHTTP01GatewayHTTPRouteApplyConfiguration { + for i := range values { + b.ParentRefs = append(b.ParentRefs, values[i]) + } + return b +} + +// WithPodTemplate sets the PodTemplate field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the PodTemplate field is set to the value of the last call. +func (b *ACMEChallengeSolverHTTP01GatewayHTTPRouteApplyConfiguration) WithPodTemplate(value *ACMEChallengeSolverHTTP01IngressPodTemplateApplyConfiguration) *ACMEChallengeSolverHTTP01GatewayHTTPRouteApplyConfiguration { + b.PodTemplate = value + return b +} diff --git a/pkg/client/applyconfigurations/acme/v1/acmechallengesolverhttp01ingress.go b/pkg/client/applyconfigurations/acme/v1/acmechallengesolverhttp01ingress.go new file mode 100644 index 00000000000..01ac4d7f7fd --- /dev/null +++ b/pkg/client/applyconfigurations/acme/v1/acmechallengesolverhttp01ingress.go @@ -0,0 +1,88 @@ +/* +Copyright 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. +*/ + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1 + +import ( + corev1 "k8s.io/api/core/v1" +) + +// ACMEChallengeSolverHTTP01IngressApplyConfiguration represents a declarative configuration of the ACMEChallengeSolverHTTP01Ingress type for use +// with apply. +type ACMEChallengeSolverHTTP01IngressApplyConfiguration struct { + ServiceType *corev1.ServiceType `json:"serviceType,omitempty"` + IngressClassName *string `json:"ingressClassName,omitempty"` + Class *string `json:"class,omitempty"` + Name *string `json:"name,omitempty"` + PodTemplate *ACMEChallengeSolverHTTP01IngressPodTemplateApplyConfiguration `json:"podTemplate,omitempty"` + IngressTemplate *ACMEChallengeSolverHTTP01IngressTemplateApplyConfiguration `json:"ingressTemplate,omitempty"` +} + +// ACMEChallengeSolverHTTP01IngressApplyConfiguration constructs a declarative configuration of the ACMEChallengeSolverHTTP01Ingress type for use with +// apply. +func ACMEChallengeSolverHTTP01Ingress() *ACMEChallengeSolverHTTP01IngressApplyConfiguration { + return &ACMEChallengeSolverHTTP01IngressApplyConfiguration{} +} + +// WithServiceType sets the ServiceType field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the ServiceType field is set to the value of the last call. +func (b *ACMEChallengeSolverHTTP01IngressApplyConfiguration) WithServiceType(value corev1.ServiceType) *ACMEChallengeSolverHTTP01IngressApplyConfiguration { + b.ServiceType = &value + return b +} + +// WithIngressClassName sets the IngressClassName field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the IngressClassName field is set to the value of the last call. +func (b *ACMEChallengeSolverHTTP01IngressApplyConfiguration) WithIngressClassName(value string) *ACMEChallengeSolverHTTP01IngressApplyConfiguration { + b.IngressClassName = &value + return b +} + +// WithClass sets the Class field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Class field is set to the value of the last call. +func (b *ACMEChallengeSolverHTTP01IngressApplyConfiguration) WithClass(value string) *ACMEChallengeSolverHTTP01IngressApplyConfiguration { + b.Class = &value + return b +} + +// WithName sets the Name field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Name field is set to the value of the last call. +func (b *ACMEChallengeSolverHTTP01IngressApplyConfiguration) WithName(value string) *ACMEChallengeSolverHTTP01IngressApplyConfiguration { + b.Name = &value + return b +} + +// WithPodTemplate sets the PodTemplate field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the PodTemplate field is set to the value of the last call. +func (b *ACMEChallengeSolverHTTP01IngressApplyConfiguration) WithPodTemplate(value *ACMEChallengeSolverHTTP01IngressPodTemplateApplyConfiguration) *ACMEChallengeSolverHTTP01IngressApplyConfiguration { + b.PodTemplate = value + return b +} + +// WithIngressTemplate sets the IngressTemplate field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the IngressTemplate field is set to the value of the last call. +func (b *ACMEChallengeSolverHTTP01IngressApplyConfiguration) WithIngressTemplate(value *ACMEChallengeSolverHTTP01IngressTemplateApplyConfiguration) *ACMEChallengeSolverHTTP01IngressApplyConfiguration { + b.IngressTemplate = value + return b +} diff --git a/pkg/client/applyconfigurations/acme/v1/acmechallengesolverhttp01ingressobjectmeta.go b/pkg/client/applyconfigurations/acme/v1/acmechallengesolverhttp01ingressobjectmeta.go new file mode 100644 index 00000000000..3afc2e230e9 --- /dev/null +++ b/pkg/client/applyconfigurations/acme/v1/acmechallengesolverhttp01ingressobjectmeta.go @@ -0,0 +1,60 @@ +/* +Copyright 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. +*/ + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1 + +// ACMEChallengeSolverHTTP01IngressObjectMetaApplyConfiguration represents a declarative configuration of the ACMEChallengeSolverHTTP01IngressObjectMeta type for use +// with apply. +type ACMEChallengeSolverHTTP01IngressObjectMetaApplyConfiguration struct { + Annotations map[string]string `json:"annotations,omitempty"` + Labels map[string]string `json:"labels,omitempty"` +} + +// ACMEChallengeSolverHTTP01IngressObjectMetaApplyConfiguration constructs a declarative configuration of the ACMEChallengeSolverHTTP01IngressObjectMeta type for use with +// apply. +func ACMEChallengeSolverHTTP01IngressObjectMeta() *ACMEChallengeSolverHTTP01IngressObjectMetaApplyConfiguration { + return &ACMEChallengeSolverHTTP01IngressObjectMetaApplyConfiguration{} +} + +// WithAnnotations puts the entries into the Annotations field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, the entries provided by each call will be put on the Annotations field, +// overwriting an existing map entries in Annotations field with the same key. +func (b *ACMEChallengeSolverHTTP01IngressObjectMetaApplyConfiguration) WithAnnotations(entries map[string]string) *ACMEChallengeSolverHTTP01IngressObjectMetaApplyConfiguration { + if b.Annotations == nil && len(entries) > 0 { + b.Annotations = make(map[string]string, len(entries)) + } + for k, v := range entries { + b.Annotations[k] = v + } + return b +} + +// WithLabels puts the entries into the Labels field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, the entries provided by each call will be put on the Labels field, +// overwriting an existing map entries in Labels field with the same key. +func (b *ACMEChallengeSolverHTTP01IngressObjectMetaApplyConfiguration) WithLabels(entries map[string]string) *ACMEChallengeSolverHTTP01IngressObjectMetaApplyConfiguration { + if b.Labels == nil && len(entries) > 0 { + b.Labels = make(map[string]string, len(entries)) + } + for k, v := range entries { + b.Labels[k] = v + } + return b +} diff --git a/pkg/client/applyconfigurations/acme/v1/acmechallengesolverhttp01ingresspodobjectmeta.go b/pkg/client/applyconfigurations/acme/v1/acmechallengesolverhttp01ingresspodobjectmeta.go new file mode 100644 index 00000000000..895bdc108b5 --- /dev/null +++ b/pkg/client/applyconfigurations/acme/v1/acmechallengesolverhttp01ingresspodobjectmeta.go @@ -0,0 +1,60 @@ +/* +Copyright 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. +*/ + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1 + +// ACMEChallengeSolverHTTP01IngressPodObjectMetaApplyConfiguration represents a declarative configuration of the ACMEChallengeSolverHTTP01IngressPodObjectMeta type for use +// with apply. +type ACMEChallengeSolverHTTP01IngressPodObjectMetaApplyConfiguration struct { + Annotations map[string]string `json:"annotations,omitempty"` + Labels map[string]string `json:"labels,omitempty"` +} + +// ACMEChallengeSolverHTTP01IngressPodObjectMetaApplyConfiguration constructs a declarative configuration of the ACMEChallengeSolverHTTP01IngressPodObjectMeta type for use with +// apply. +func ACMEChallengeSolverHTTP01IngressPodObjectMeta() *ACMEChallengeSolverHTTP01IngressPodObjectMetaApplyConfiguration { + return &ACMEChallengeSolverHTTP01IngressPodObjectMetaApplyConfiguration{} +} + +// WithAnnotations puts the entries into the Annotations field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, the entries provided by each call will be put on the Annotations field, +// overwriting an existing map entries in Annotations field with the same key. +func (b *ACMEChallengeSolverHTTP01IngressPodObjectMetaApplyConfiguration) WithAnnotations(entries map[string]string) *ACMEChallengeSolverHTTP01IngressPodObjectMetaApplyConfiguration { + if b.Annotations == nil && len(entries) > 0 { + b.Annotations = make(map[string]string, len(entries)) + } + for k, v := range entries { + b.Annotations[k] = v + } + return b +} + +// WithLabels puts the entries into the Labels field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, the entries provided by each call will be put on the Labels field, +// overwriting an existing map entries in Labels field with the same key. +func (b *ACMEChallengeSolverHTTP01IngressPodObjectMetaApplyConfiguration) WithLabels(entries map[string]string) *ACMEChallengeSolverHTTP01IngressPodObjectMetaApplyConfiguration { + if b.Labels == nil && len(entries) > 0 { + b.Labels = make(map[string]string, len(entries)) + } + for k, v := range entries { + b.Labels[k] = v + } + return b +} diff --git a/pkg/client/applyconfigurations/acme/v1/acmechallengesolverhttp01ingresspodresources.go b/pkg/client/applyconfigurations/acme/v1/acmechallengesolverhttp01ingresspodresources.go new file mode 100644 index 00000000000..f3967e817ca --- /dev/null +++ b/pkg/client/applyconfigurations/acme/v1/acmechallengesolverhttp01ingresspodresources.go @@ -0,0 +1,52 @@ +/* +Copyright 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. +*/ + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1 + +import ( + corev1 "k8s.io/api/core/v1" +) + +// ACMEChallengeSolverHTTP01IngressPodResourcesApplyConfiguration represents a declarative configuration of the ACMEChallengeSolverHTTP01IngressPodResources type for use +// with apply. +type ACMEChallengeSolverHTTP01IngressPodResourcesApplyConfiguration struct { + Limits *corev1.ResourceList `json:"limits,omitempty"` + Requests *corev1.ResourceList `json:"requests,omitempty"` +} + +// ACMEChallengeSolverHTTP01IngressPodResourcesApplyConfiguration constructs a declarative configuration of the ACMEChallengeSolverHTTP01IngressPodResources type for use with +// apply. +func ACMEChallengeSolverHTTP01IngressPodResources() *ACMEChallengeSolverHTTP01IngressPodResourcesApplyConfiguration { + return &ACMEChallengeSolverHTTP01IngressPodResourcesApplyConfiguration{} +} + +// WithLimits sets the Limits field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Limits field is set to the value of the last call. +func (b *ACMEChallengeSolverHTTP01IngressPodResourcesApplyConfiguration) WithLimits(value corev1.ResourceList) *ACMEChallengeSolverHTTP01IngressPodResourcesApplyConfiguration { + b.Limits = &value + return b +} + +// WithRequests sets the Requests field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Requests field is set to the value of the last call. +func (b *ACMEChallengeSolverHTTP01IngressPodResourcesApplyConfiguration) WithRequests(value corev1.ResourceList) *ACMEChallengeSolverHTTP01IngressPodResourcesApplyConfiguration { + b.Requests = &value + return b +} diff --git a/pkg/client/applyconfigurations/acme/v1/acmechallengesolverhttp01ingresspodsecuritycontext.go b/pkg/client/applyconfigurations/acme/v1/acmechallengesolverhttp01ingresspodsecuritycontext.go new file mode 100644 index 00000000000..c8cc30366d7 --- /dev/null +++ b/pkg/client/applyconfigurations/acme/v1/acmechallengesolverhttp01ingresspodsecuritycontext.go @@ -0,0 +1,119 @@ +/* +Copyright 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. +*/ + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1 + +import ( + corev1 "k8s.io/api/core/v1" +) + +// ACMEChallengeSolverHTTP01IngressPodSecurityContextApplyConfiguration represents a declarative configuration of the ACMEChallengeSolverHTTP01IngressPodSecurityContext type for use +// with apply. +type ACMEChallengeSolverHTTP01IngressPodSecurityContextApplyConfiguration struct { + SELinuxOptions *corev1.SELinuxOptions `json:"seLinuxOptions,omitempty"` + RunAsUser *int64 `json:"runAsUser,omitempty"` + RunAsGroup *int64 `json:"runAsGroup,omitempty"` + RunAsNonRoot *bool `json:"runAsNonRoot,omitempty"` + SupplementalGroups []int64 `json:"supplementalGroups,omitempty"` + FSGroup *int64 `json:"fsGroup,omitempty"` + Sysctls []corev1.Sysctl `json:"sysctls,omitempty"` + FSGroupChangePolicy *corev1.PodFSGroupChangePolicy `json:"fsGroupChangePolicy,omitempty"` + SeccompProfile *corev1.SeccompProfile `json:"seccompProfile,omitempty"` +} + +// ACMEChallengeSolverHTTP01IngressPodSecurityContextApplyConfiguration constructs a declarative configuration of the ACMEChallengeSolverHTTP01IngressPodSecurityContext type for use with +// apply. +func ACMEChallengeSolverHTTP01IngressPodSecurityContext() *ACMEChallengeSolverHTTP01IngressPodSecurityContextApplyConfiguration { + return &ACMEChallengeSolverHTTP01IngressPodSecurityContextApplyConfiguration{} +} + +// WithSELinuxOptions sets the SELinuxOptions field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the SELinuxOptions field is set to the value of the last call. +func (b *ACMEChallengeSolverHTTP01IngressPodSecurityContextApplyConfiguration) WithSELinuxOptions(value corev1.SELinuxOptions) *ACMEChallengeSolverHTTP01IngressPodSecurityContextApplyConfiguration { + b.SELinuxOptions = &value + return b +} + +// WithRunAsUser sets the RunAsUser field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the RunAsUser field is set to the value of the last call. +func (b *ACMEChallengeSolverHTTP01IngressPodSecurityContextApplyConfiguration) WithRunAsUser(value int64) *ACMEChallengeSolverHTTP01IngressPodSecurityContextApplyConfiguration { + b.RunAsUser = &value + return b +} + +// WithRunAsGroup sets the RunAsGroup field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the RunAsGroup field is set to the value of the last call. +func (b *ACMEChallengeSolverHTTP01IngressPodSecurityContextApplyConfiguration) WithRunAsGroup(value int64) *ACMEChallengeSolverHTTP01IngressPodSecurityContextApplyConfiguration { + b.RunAsGroup = &value + return b +} + +// WithRunAsNonRoot sets the RunAsNonRoot field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the RunAsNonRoot field is set to the value of the last call. +func (b *ACMEChallengeSolverHTTP01IngressPodSecurityContextApplyConfiguration) WithRunAsNonRoot(value bool) *ACMEChallengeSolverHTTP01IngressPodSecurityContextApplyConfiguration { + b.RunAsNonRoot = &value + return b +} + +// WithSupplementalGroups adds the given value to the SupplementalGroups field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the SupplementalGroups field. +func (b *ACMEChallengeSolverHTTP01IngressPodSecurityContextApplyConfiguration) WithSupplementalGroups(values ...int64) *ACMEChallengeSolverHTTP01IngressPodSecurityContextApplyConfiguration { + for i := range values { + b.SupplementalGroups = append(b.SupplementalGroups, values[i]) + } + return b +} + +// WithFSGroup sets the FSGroup field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the FSGroup field is set to the value of the last call. +func (b *ACMEChallengeSolverHTTP01IngressPodSecurityContextApplyConfiguration) WithFSGroup(value int64) *ACMEChallengeSolverHTTP01IngressPodSecurityContextApplyConfiguration { + b.FSGroup = &value + return b +} + +// WithSysctls adds the given value to the Sysctls field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the Sysctls field. +func (b *ACMEChallengeSolverHTTP01IngressPodSecurityContextApplyConfiguration) WithSysctls(values ...corev1.Sysctl) *ACMEChallengeSolverHTTP01IngressPodSecurityContextApplyConfiguration { + for i := range values { + b.Sysctls = append(b.Sysctls, values[i]) + } + return b +} + +// WithFSGroupChangePolicy sets the FSGroupChangePolicy field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the FSGroupChangePolicy field is set to the value of the last call. +func (b *ACMEChallengeSolverHTTP01IngressPodSecurityContextApplyConfiguration) WithFSGroupChangePolicy(value corev1.PodFSGroupChangePolicy) *ACMEChallengeSolverHTTP01IngressPodSecurityContextApplyConfiguration { + b.FSGroupChangePolicy = &value + return b +} + +// WithSeccompProfile sets the SeccompProfile field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the SeccompProfile field is set to the value of the last call. +func (b *ACMEChallengeSolverHTTP01IngressPodSecurityContextApplyConfiguration) WithSeccompProfile(value corev1.SeccompProfile) *ACMEChallengeSolverHTTP01IngressPodSecurityContextApplyConfiguration { + b.SeccompProfile = &value + return b +} diff --git a/pkg/client/applyconfigurations/acme/v1/acmechallengesolverhttp01ingresspodspec.go b/pkg/client/applyconfigurations/acme/v1/acmechallengesolverhttp01ingresspodspec.go new file mode 100644 index 00000000000..471b6ae27c1 --- /dev/null +++ b/pkg/client/applyconfigurations/acme/v1/acmechallengesolverhttp01ingresspodspec.go @@ -0,0 +1,116 @@ +/* +Copyright 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. +*/ + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1 + +import ( + corev1 "k8s.io/api/core/v1" +) + +// ACMEChallengeSolverHTTP01IngressPodSpecApplyConfiguration represents a declarative configuration of the ACMEChallengeSolverHTTP01IngressPodSpec type for use +// with apply. +type ACMEChallengeSolverHTTP01IngressPodSpecApplyConfiguration struct { + NodeSelector map[string]string `json:"nodeSelector,omitempty"` + Affinity *corev1.Affinity `json:"affinity,omitempty"` + Tolerations []corev1.Toleration `json:"tolerations,omitempty"` + PriorityClassName *string `json:"priorityClassName,omitempty"` + ServiceAccountName *string `json:"serviceAccountName,omitempty"` + ImagePullSecrets []corev1.LocalObjectReference `json:"imagePullSecrets,omitempty"` + SecurityContext *ACMEChallengeSolverHTTP01IngressPodSecurityContextApplyConfiguration `json:"securityContext,omitempty"` + Resources *ACMEChallengeSolverHTTP01IngressPodResourcesApplyConfiguration `json:"resources,omitempty"` +} + +// ACMEChallengeSolverHTTP01IngressPodSpecApplyConfiguration constructs a declarative configuration of the ACMEChallengeSolverHTTP01IngressPodSpec type for use with +// apply. +func ACMEChallengeSolverHTTP01IngressPodSpec() *ACMEChallengeSolverHTTP01IngressPodSpecApplyConfiguration { + return &ACMEChallengeSolverHTTP01IngressPodSpecApplyConfiguration{} +} + +// WithNodeSelector puts the entries into the NodeSelector field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, the entries provided by each call will be put on the NodeSelector field, +// overwriting an existing map entries in NodeSelector field with the same key. +func (b *ACMEChallengeSolverHTTP01IngressPodSpecApplyConfiguration) WithNodeSelector(entries map[string]string) *ACMEChallengeSolverHTTP01IngressPodSpecApplyConfiguration { + if b.NodeSelector == nil && len(entries) > 0 { + b.NodeSelector = make(map[string]string, len(entries)) + } + for k, v := range entries { + b.NodeSelector[k] = v + } + return b +} + +// WithAffinity sets the Affinity field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Affinity field is set to the value of the last call. +func (b *ACMEChallengeSolverHTTP01IngressPodSpecApplyConfiguration) WithAffinity(value corev1.Affinity) *ACMEChallengeSolverHTTP01IngressPodSpecApplyConfiguration { + b.Affinity = &value + return b +} + +// WithTolerations adds the given value to the Tolerations field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the Tolerations field. +func (b *ACMEChallengeSolverHTTP01IngressPodSpecApplyConfiguration) WithTolerations(values ...corev1.Toleration) *ACMEChallengeSolverHTTP01IngressPodSpecApplyConfiguration { + for i := range values { + b.Tolerations = append(b.Tolerations, values[i]) + } + return b +} + +// WithPriorityClassName sets the PriorityClassName field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the PriorityClassName field is set to the value of the last call. +func (b *ACMEChallengeSolverHTTP01IngressPodSpecApplyConfiguration) WithPriorityClassName(value string) *ACMEChallengeSolverHTTP01IngressPodSpecApplyConfiguration { + b.PriorityClassName = &value + return b +} + +// WithServiceAccountName sets the ServiceAccountName field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the ServiceAccountName field is set to the value of the last call. +func (b *ACMEChallengeSolverHTTP01IngressPodSpecApplyConfiguration) WithServiceAccountName(value string) *ACMEChallengeSolverHTTP01IngressPodSpecApplyConfiguration { + b.ServiceAccountName = &value + return b +} + +// WithImagePullSecrets adds the given value to the ImagePullSecrets field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the ImagePullSecrets field. +func (b *ACMEChallengeSolverHTTP01IngressPodSpecApplyConfiguration) WithImagePullSecrets(values ...corev1.LocalObjectReference) *ACMEChallengeSolverHTTP01IngressPodSpecApplyConfiguration { + for i := range values { + b.ImagePullSecrets = append(b.ImagePullSecrets, values[i]) + } + return b +} + +// WithSecurityContext sets the SecurityContext field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the SecurityContext field is set to the value of the last call. +func (b *ACMEChallengeSolverHTTP01IngressPodSpecApplyConfiguration) WithSecurityContext(value *ACMEChallengeSolverHTTP01IngressPodSecurityContextApplyConfiguration) *ACMEChallengeSolverHTTP01IngressPodSpecApplyConfiguration { + b.SecurityContext = value + return b +} + +// WithResources sets the Resources field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Resources field is set to the value of the last call. +func (b *ACMEChallengeSolverHTTP01IngressPodSpecApplyConfiguration) WithResources(value *ACMEChallengeSolverHTTP01IngressPodResourcesApplyConfiguration) *ACMEChallengeSolverHTTP01IngressPodSpecApplyConfiguration { + b.Resources = value + return b +} diff --git a/pkg/client/applyconfigurations/acme/v1/acmechallengesolverhttp01ingresspodtemplate.go b/pkg/client/applyconfigurations/acme/v1/acmechallengesolverhttp01ingresspodtemplate.go new file mode 100644 index 00000000000..a954fa0f615 --- /dev/null +++ b/pkg/client/applyconfigurations/acme/v1/acmechallengesolverhttp01ingresspodtemplate.go @@ -0,0 +1,76 @@ +/* +Copyright 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. +*/ + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1 + +// ACMEChallengeSolverHTTP01IngressPodTemplateApplyConfiguration represents a declarative configuration of the ACMEChallengeSolverHTTP01IngressPodTemplate type for use +// with apply. +type ACMEChallengeSolverHTTP01IngressPodTemplateApplyConfiguration struct { + *ACMEChallengeSolverHTTP01IngressPodObjectMetaApplyConfiguration `json:"metadata,omitempty"` + Spec *ACMEChallengeSolverHTTP01IngressPodSpecApplyConfiguration `json:"spec,omitempty"` +} + +// ACMEChallengeSolverHTTP01IngressPodTemplateApplyConfiguration constructs a declarative configuration of the ACMEChallengeSolverHTTP01IngressPodTemplate type for use with +// apply. +func ACMEChallengeSolverHTTP01IngressPodTemplate() *ACMEChallengeSolverHTTP01IngressPodTemplateApplyConfiguration { + return &ACMEChallengeSolverHTTP01IngressPodTemplateApplyConfiguration{} +} + +// WithAnnotations puts the entries into the Annotations field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, the entries provided by each call will be put on the Annotations field, +// overwriting an existing map entries in Annotations field with the same key. +func (b *ACMEChallengeSolverHTTP01IngressPodTemplateApplyConfiguration) WithAnnotations(entries map[string]string) *ACMEChallengeSolverHTTP01IngressPodTemplateApplyConfiguration { + b.ensureACMEChallengeSolverHTTP01IngressPodObjectMetaApplyConfigurationExists() + if b.ACMEChallengeSolverHTTP01IngressPodObjectMetaApplyConfiguration.Annotations == nil && len(entries) > 0 { + b.ACMEChallengeSolverHTTP01IngressPodObjectMetaApplyConfiguration.Annotations = make(map[string]string, len(entries)) + } + for k, v := range entries { + b.ACMEChallengeSolverHTTP01IngressPodObjectMetaApplyConfiguration.Annotations[k] = v + } + return b +} + +// WithLabels puts the entries into the Labels field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, the entries provided by each call will be put on the Labels field, +// overwriting an existing map entries in Labels field with the same key. +func (b *ACMEChallengeSolverHTTP01IngressPodTemplateApplyConfiguration) WithLabels(entries map[string]string) *ACMEChallengeSolverHTTP01IngressPodTemplateApplyConfiguration { + b.ensureACMEChallengeSolverHTTP01IngressPodObjectMetaApplyConfigurationExists() + if b.ACMEChallengeSolverHTTP01IngressPodObjectMetaApplyConfiguration.Labels == nil && len(entries) > 0 { + b.ACMEChallengeSolverHTTP01IngressPodObjectMetaApplyConfiguration.Labels = make(map[string]string, len(entries)) + } + for k, v := range entries { + b.ACMEChallengeSolverHTTP01IngressPodObjectMetaApplyConfiguration.Labels[k] = v + } + return b +} + +func (b *ACMEChallengeSolverHTTP01IngressPodTemplateApplyConfiguration) ensureACMEChallengeSolverHTTP01IngressPodObjectMetaApplyConfigurationExists() { + if b.ACMEChallengeSolverHTTP01IngressPodObjectMetaApplyConfiguration == nil { + b.ACMEChallengeSolverHTTP01IngressPodObjectMetaApplyConfiguration = &ACMEChallengeSolverHTTP01IngressPodObjectMetaApplyConfiguration{} + } +} + +// WithSpec sets the Spec field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Spec field is set to the value of the last call. +func (b *ACMEChallengeSolverHTTP01IngressPodTemplateApplyConfiguration) WithSpec(value *ACMEChallengeSolverHTTP01IngressPodSpecApplyConfiguration) *ACMEChallengeSolverHTTP01IngressPodTemplateApplyConfiguration { + b.Spec = value + return b +} diff --git a/pkg/client/applyconfigurations/acme/v1/acmechallengesolverhttp01ingresstemplate.go b/pkg/client/applyconfigurations/acme/v1/acmechallengesolverhttp01ingresstemplate.go new file mode 100644 index 00000000000..334b269e5f1 --- /dev/null +++ b/pkg/client/applyconfigurations/acme/v1/acmechallengesolverhttp01ingresstemplate.go @@ -0,0 +1,67 @@ +/* +Copyright 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. +*/ + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1 + +// ACMEChallengeSolverHTTP01IngressTemplateApplyConfiguration represents a declarative configuration of the ACMEChallengeSolverHTTP01IngressTemplate type for use +// with apply. +type ACMEChallengeSolverHTTP01IngressTemplateApplyConfiguration struct { + *ACMEChallengeSolverHTTP01IngressObjectMetaApplyConfiguration `json:"metadata,omitempty"` +} + +// ACMEChallengeSolverHTTP01IngressTemplateApplyConfiguration constructs a declarative configuration of the ACMEChallengeSolverHTTP01IngressTemplate type for use with +// apply. +func ACMEChallengeSolverHTTP01IngressTemplate() *ACMEChallengeSolverHTTP01IngressTemplateApplyConfiguration { + return &ACMEChallengeSolverHTTP01IngressTemplateApplyConfiguration{} +} + +// WithAnnotations puts the entries into the Annotations field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, the entries provided by each call will be put on the Annotations field, +// overwriting an existing map entries in Annotations field with the same key. +func (b *ACMEChallengeSolverHTTP01IngressTemplateApplyConfiguration) WithAnnotations(entries map[string]string) *ACMEChallengeSolverHTTP01IngressTemplateApplyConfiguration { + b.ensureACMEChallengeSolverHTTP01IngressObjectMetaApplyConfigurationExists() + if b.ACMEChallengeSolverHTTP01IngressObjectMetaApplyConfiguration.Annotations == nil && len(entries) > 0 { + b.ACMEChallengeSolverHTTP01IngressObjectMetaApplyConfiguration.Annotations = make(map[string]string, len(entries)) + } + for k, v := range entries { + b.ACMEChallengeSolverHTTP01IngressObjectMetaApplyConfiguration.Annotations[k] = v + } + return b +} + +// WithLabels puts the entries into the Labels field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, the entries provided by each call will be put on the Labels field, +// overwriting an existing map entries in Labels field with the same key. +func (b *ACMEChallengeSolverHTTP01IngressTemplateApplyConfiguration) WithLabels(entries map[string]string) *ACMEChallengeSolverHTTP01IngressTemplateApplyConfiguration { + b.ensureACMEChallengeSolverHTTP01IngressObjectMetaApplyConfigurationExists() + if b.ACMEChallengeSolverHTTP01IngressObjectMetaApplyConfiguration.Labels == nil && len(entries) > 0 { + b.ACMEChallengeSolverHTTP01IngressObjectMetaApplyConfiguration.Labels = make(map[string]string, len(entries)) + } + for k, v := range entries { + b.ACMEChallengeSolverHTTP01IngressObjectMetaApplyConfiguration.Labels[k] = v + } + return b +} + +func (b *ACMEChallengeSolverHTTP01IngressTemplateApplyConfiguration) ensureACMEChallengeSolverHTTP01IngressObjectMetaApplyConfigurationExists() { + if b.ACMEChallengeSolverHTTP01IngressObjectMetaApplyConfiguration == nil { + b.ACMEChallengeSolverHTTP01IngressObjectMetaApplyConfiguration = &ACMEChallengeSolverHTTP01IngressObjectMetaApplyConfiguration{} + } +} diff --git a/pkg/client/applyconfigurations/acme/v1/acmeexternalaccountbinding.go b/pkg/client/applyconfigurations/acme/v1/acmeexternalaccountbinding.go new file mode 100644 index 00000000000..28416356cac --- /dev/null +++ b/pkg/client/applyconfigurations/acme/v1/acmeexternalaccountbinding.go @@ -0,0 +1,62 @@ +/* +Copyright 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. +*/ + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1 + +import ( + acmev1 "github.com/cert-manager/cert-manager/pkg/apis/acme/v1" + metav1 "github.com/cert-manager/cert-manager/pkg/client/applyconfigurations/meta/v1" +) + +// ACMEExternalAccountBindingApplyConfiguration represents a declarative configuration of the ACMEExternalAccountBinding type for use +// with apply. +type ACMEExternalAccountBindingApplyConfiguration struct { + KeyID *string `json:"keyID,omitempty"` + Key *metav1.SecretKeySelectorApplyConfiguration `json:"keySecretRef,omitempty"` + KeyAlgorithm *acmev1.HMACKeyAlgorithm `json:"keyAlgorithm,omitempty"` +} + +// ACMEExternalAccountBindingApplyConfiguration constructs a declarative configuration of the ACMEExternalAccountBinding type for use with +// apply. +func ACMEExternalAccountBinding() *ACMEExternalAccountBindingApplyConfiguration { + return &ACMEExternalAccountBindingApplyConfiguration{} +} + +// WithKeyID sets the KeyID field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the KeyID field is set to the value of the last call. +func (b *ACMEExternalAccountBindingApplyConfiguration) WithKeyID(value string) *ACMEExternalAccountBindingApplyConfiguration { + b.KeyID = &value + return b +} + +// WithKey sets the Key field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Key field is set to the value of the last call. +func (b *ACMEExternalAccountBindingApplyConfiguration) WithKey(value *metav1.SecretKeySelectorApplyConfiguration) *ACMEExternalAccountBindingApplyConfiguration { + b.Key = value + return b +} + +// WithKeyAlgorithm sets the KeyAlgorithm field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the KeyAlgorithm field is set to the value of the last call. +func (b *ACMEExternalAccountBindingApplyConfiguration) WithKeyAlgorithm(value acmev1.HMACKeyAlgorithm) *ACMEExternalAccountBindingApplyConfiguration { + b.KeyAlgorithm = &value + return b +} diff --git a/pkg/client/applyconfigurations/acme/v1/acmeissuer.go b/pkg/client/applyconfigurations/acme/v1/acmeissuer.go new file mode 100644 index 00000000000..96d026411c2 --- /dev/null +++ b/pkg/client/applyconfigurations/acme/v1/acmeissuer.go @@ -0,0 +1,140 @@ +/* +Copyright 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. +*/ + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1 + +import ( + metav1 "github.com/cert-manager/cert-manager/pkg/client/applyconfigurations/meta/v1" +) + +// ACMEIssuerApplyConfiguration represents a declarative configuration of the ACMEIssuer type for use +// with apply. +type ACMEIssuerApplyConfiguration struct { + Email *string `json:"email,omitempty"` + Server *string `json:"server,omitempty"` + PreferredChain *string `json:"preferredChain,omitempty"` + CABundle []byte `json:"caBundle,omitempty"` + SkipTLSVerify *bool `json:"skipTLSVerify,omitempty"` + ExternalAccountBinding *ACMEExternalAccountBindingApplyConfiguration `json:"externalAccountBinding,omitempty"` + PrivateKey *metav1.SecretKeySelectorApplyConfiguration `json:"privateKeySecretRef,omitempty"` + Solvers []ACMEChallengeSolverApplyConfiguration `json:"solvers,omitempty"` + DisableAccountKeyGeneration *bool `json:"disableAccountKeyGeneration,omitempty"` + EnableDurationFeature *bool `json:"enableDurationFeature,omitempty"` + Profile *string `json:"profile,omitempty"` +} + +// ACMEIssuerApplyConfiguration constructs a declarative configuration of the ACMEIssuer type for use with +// apply. +func ACMEIssuer() *ACMEIssuerApplyConfiguration { + return &ACMEIssuerApplyConfiguration{} +} + +// WithEmail sets the Email field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Email field is set to the value of the last call. +func (b *ACMEIssuerApplyConfiguration) WithEmail(value string) *ACMEIssuerApplyConfiguration { + b.Email = &value + return b +} + +// WithServer sets the Server field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Server field is set to the value of the last call. +func (b *ACMEIssuerApplyConfiguration) WithServer(value string) *ACMEIssuerApplyConfiguration { + b.Server = &value + return b +} + +// WithPreferredChain sets the PreferredChain field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the PreferredChain field is set to the value of the last call. +func (b *ACMEIssuerApplyConfiguration) WithPreferredChain(value string) *ACMEIssuerApplyConfiguration { + b.PreferredChain = &value + return b +} + +// WithCABundle adds the given value to the CABundle field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the CABundle field. +func (b *ACMEIssuerApplyConfiguration) WithCABundle(values ...byte) *ACMEIssuerApplyConfiguration { + for i := range values { + b.CABundle = append(b.CABundle, values[i]) + } + return b +} + +// WithSkipTLSVerify sets the SkipTLSVerify field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the SkipTLSVerify field is set to the value of the last call. +func (b *ACMEIssuerApplyConfiguration) WithSkipTLSVerify(value bool) *ACMEIssuerApplyConfiguration { + b.SkipTLSVerify = &value + return b +} + +// WithExternalAccountBinding sets the ExternalAccountBinding field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the ExternalAccountBinding field is set to the value of the last call. +func (b *ACMEIssuerApplyConfiguration) WithExternalAccountBinding(value *ACMEExternalAccountBindingApplyConfiguration) *ACMEIssuerApplyConfiguration { + b.ExternalAccountBinding = value + return b +} + +// WithPrivateKey sets the PrivateKey field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the PrivateKey field is set to the value of the last call. +func (b *ACMEIssuerApplyConfiguration) WithPrivateKey(value *metav1.SecretKeySelectorApplyConfiguration) *ACMEIssuerApplyConfiguration { + b.PrivateKey = value + return b +} + +// WithSolvers adds the given value to the Solvers field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the Solvers field. +func (b *ACMEIssuerApplyConfiguration) WithSolvers(values ...*ACMEChallengeSolverApplyConfiguration) *ACMEIssuerApplyConfiguration { + for i := range values { + if values[i] == nil { + panic("nil value passed to WithSolvers") + } + b.Solvers = append(b.Solvers, *values[i]) + } + return b +} + +// WithDisableAccountKeyGeneration sets the DisableAccountKeyGeneration field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the DisableAccountKeyGeneration field is set to the value of the last call. +func (b *ACMEIssuerApplyConfiguration) WithDisableAccountKeyGeneration(value bool) *ACMEIssuerApplyConfiguration { + b.DisableAccountKeyGeneration = &value + return b +} + +// WithEnableDurationFeature sets the EnableDurationFeature field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the EnableDurationFeature field is set to the value of the last call. +func (b *ACMEIssuerApplyConfiguration) WithEnableDurationFeature(value bool) *ACMEIssuerApplyConfiguration { + b.EnableDurationFeature = &value + return b +} + +// WithProfile sets the Profile field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Profile field is set to the value of the last call. +func (b *ACMEIssuerApplyConfiguration) WithProfile(value string) *ACMEIssuerApplyConfiguration { + b.Profile = &value + return b +} diff --git a/pkg/client/applyconfigurations/acme/v1/acmeissuerdns01provideracmedns.go b/pkg/client/applyconfigurations/acme/v1/acmeissuerdns01provideracmedns.go new file mode 100644 index 00000000000..abb6b3b37fd --- /dev/null +++ b/pkg/client/applyconfigurations/acme/v1/acmeissuerdns01provideracmedns.go @@ -0,0 +1,52 @@ +/* +Copyright 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. +*/ + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1 + +import ( + metav1 "github.com/cert-manager/cert-manager/pkg/client/applyconfigurations/meta/v1" +) + +// ACMEIssuerDNS01ProviderAcmeDNSApplyConfiguration represents a declarative configuration of the ACMEIssuerDNS01ProviderAcmeDNS type for use +// with apply. +type ACMEIssuerDNS01ProviderAcmeDNSApplyConfiguration struct { + Host *string `json:"host,omitempty"` + AccountSecret *metav1.SecretKeySelectorApplyConfiguration `json:"accountSecretRef,omitempty"` +} + +// ACMEIssuerDNS01ProviderAcmeDNSApplyConfiguration constructs a declarative configuration of the ACMEIssuerDNS01ProviderAcmeDNS type for use with +// apply. +func ACMEIssuerDNS01ProviderAcmeDNS() *ACMEIssuerDNS01ProviderAcmeDNSApplyConfiguration { + return &ACMEIssuerDNS01ProviderAcmeDNSApplyConfiguration{} +} + +// WithHost sets the Host field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Host field is set to the value of the last call. +func (b *ACMEIssuerDNS01ProviderAcmeDNSApplyConfiguration) WithHost(value string) *ACMEIssuerDNS01ProviderAcmeDNSApplyConfiguration { + b.Host = &value + return b +} + +// WithAccountSecret sets the AccountSecret field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the AccountSecret field is set to the value of the last call. +func (b *ACMEIssuerDNS01ProviderAcmeDNSApplyConfiguration) WithAccountSecret(value *metav1.SecretKeySelectorApplyConfiguration) *ACMEIssuerDNS01ProviderAcmeDNSApplyConfiguration { + b.AccountSecret = value + return b +} diff --git a/pkg/client/applyconfigurations/acme/v1/acmeissuerdns01providerakamai.go b/pkg/client/applyconfigurations/acme/v1/acmeissuerdns01providerakamai.go new file mode 100644 index 00000000000..16406223cff --- /dev/null +++ b/pkg/client/applyconfigurations/acme/v1/acmeissuerdns01providerakamai.go @@ -0,0 +1,70 @@ +/* +Copyright 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. +*/ + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1 + +import ( + metav1 "github.com/cert-manager/cert-manager/pkg/client/applyconfigurations/meta/v1" +) + +// ACMEIssuerDNS01ProviderAkamaiApplyConfiguration represents a declarative configuration of the ACMEIssuerDNS01ProviderAkamai type for use +// with apply. +type ACMEIssuerDNS01ProviderAkamaiApplyConfiguration struct { + ServiceConsumerDomain *string `json:"serviceConsumerDomain,omitempty"` + ClientToken *metav1.SecretKeySelectorApplyConfiguration `json:"clientTokenSecretRef,omitempty"` + ClientSecret *metav1.SecretKeySelectorApplyConfiguration `json:"clientSecretSecretRef,omitempty"` + AccessToken *metav1.SecretKeySelectorApplyConfiguration `json:"accessTokenSecretRef,omitempty"` +} + +// ACMEIssuerDNS01ProviderAkamaiApplyConfiguration constructs a declarative configuration of the ACMEIssuerDNS01ProviderAkamai type for use with +// apply. +func ACMEIssuerDNS01ProviderAkamai() *ACMEIssuerDNS01ProviderAkamaiApplyConfiguration { + return &ACMEIssuerDNS01ProviderAkamaiApplyConfiguration{} +} + +// WithServiceConsumerDomain sets the ServiceConsumerDomain field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the ServiceConsumerDomain field is set to the value of the last call. +func (b *ACMEIssuerDNS01ProviderAkamaiApplyConfiguration) WithServiceConsumerDomain(value string) *ACMEIssuerDNS01ProviderAkamaiApplyConfiguration { + b.ServiceConsumerDomain = &value + return b +} + +// WithClientToken sets the ClientToken field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the ClientToken field is set to the value of the last call. +func (b *ACMEIssuerDNS01ProviderAkamaiApplyConfiguration) WithClientToken(value *metav1.SecretKeySelectorApplyConfiguration) *ACMEIssuerDNS01ProviderAkamaiApplyConfiguration { + b.ClientToken = value + return b +} + +// WithClientSecret sets the ClientSecret field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the ClientSecret field is set to the value of the last call. +func (b *ACMEIssuerDNS01ProviderAkamaiApplyConfiguration) WithClientSecret(value *metav1.SecretKeySelectorApplyConfiguration) *ACMEIssuerDNS01ProviderAkamaiApplyConfiguration { + b.ClientSecret = value + return b +} + +// WithAccessToken sets the AccessToken field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the AccessToken field is set to the value of the last call. +func (b *ACMEIssuerDNS01ProviderAkamaiApplyConfiguration) WithAccessToken(value *metav1.SecretKeySelectorApplyConfiguration) *ACMEIssuerDNS01ProviderAkamaiApplyConfiguration { + b.AccessToken = value + return b +} diff --git a/pkg/client/applyconfigurations/acme/v1/acmeissuerdns01providerazuredns.go b/pkg/client/applyconfigurations/acme/v1/acmeissuerdns01providerazuredns.go new file mode 100644 index 00000000000..a78a96f807d --- /dev/null +++ b/pkg/client/applyconfigurations/acme/v1/acmeissuerdns01providerazuredns.go @@ -0,0 +1,107 @@ +/* +Copyright 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. +*/ + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1 + +import ( + acmev1 "github.com/cert-manager/cert-manager/pkg/apis/acme/v1" + metav1 "github.com/cert-manager/cert-manager/pkg/client/applyconfigurations/meta/v1" +) + +// ACMEIssuerDNS01ProviderAzureDNSApplyConfiguration represents a declarative configuration of the ACMEIssuerDNS01ProviderAzureDNS type for use +// with apply. +type ACMEIssuerDNS01ProviderAzureDNSApplyConfiguration struct { + ClientID *string `json:"clientID,omitempty"` + ClientSecret *metav1.SecretKeySelectorApplyConfiguration `json:"clientSecretSecretRef,omitempty"` + SubscriptionID *string `json:"subscriptionID,omitempty"` + TenantID *string `json:"tenantID,omitempty"` + ResourceGroupName *string `json:"resourceGroupName,omitempty"` + HostedZoneName *string `json:"hostedZoneName,omitempty"` + Environment *acmev1.AzureDNSEnvironment `json:"environment,omitempty"` + ManagedIdentity *AzureManagedIdentityApplyConfiguration `json:"managedIdentity,omitempty"` +} + +// ACMEIssuerDNS01ProviderAzureDNSApplyConfiguration constructs a declarative configuration of the ACMEIssuerDNS01ProviderAzureDNS type for use with +// apply. +func ACMEIssuerDNS01ProviderAzureDNS() *ACMEIssuerDNS01ProviderAzureDNSApplyConfiguration { + return &ACMEIssuerDNS01ProviderAzureDNSApplyConfiguration{} +} + +// WithClientID sets the ClientID field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the ClientID field is set to the value of the last call. +func (b *ACMEIssuerDNS01ProviderAzureDNSApplyConfiguration) WithClientID(value string) *ACMEIssuerDNS01ProviderAzureDNSApplyConfiguration { + b.ClientID = &value + return b +} + +// WithClientSecret sets the ClientSecret field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the ClientSecret field is set to the value of the last call. +func (b *ACMEIssuerDNS01ProviderAzureDNSApplyConfiguration) WithClientSecret(value *metav1.SecretKeySelectorApplyConfiguration) *ACMEIssuerDNS01ProviderAzureDNSApplyConfiguration { + b.ClientSecret = value + return b +} + +// WithSubscriptionID sets the SubscriptionID field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the SubscriptionID field is set to the value of the last call. +func (b *ACMEIssuerDNS01ProviderAzureDNSApplyConfiguration) WithSubscriptionID(value string) *ACMEIssuerDNS01ProviderAzureDNSApplyConfiguration { + b.SubscriptionID = &value + return b +} + +// WithTenantID sets the TenantID field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the TenantID field is set to the value of the last call. +func (b *ACMEIssuerDNS01ProviderAzureDNSApplyConfiguration) WithTenantID(value string) *ACMEIssuerDNS01ProviderAzureDNSApplyConfiguration { + b.TenantID = &value + return b +} + +// WithResourceGroupName sets the ResourceGroupName field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the ResourceGroupName field is set to the value of the last call. +func (b *ACMEIssuerDNS01ProviderAzureDNSApplyConfiguration) WithResourceGroupName(value string) *ACMEIssuerDNS01ProviderAzureDNSApplyConfiguration { + b.ResourceGroupName = &value + return b +} + +// WithHostedZoneName sets the HostedZoneName field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the HostedZoneName field is set to the value of the last call. +func (b *ACMEIssuerDNS01ProviderAzureDNSApplyConfiguration) WithHostedZoneName(value string) *ACMEIssuerDNS01ProviderAzureDNSApplyConfiguration { + b.HostedZoneName = &value + return b +} + +// WithEnvironment sets the Environment field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Environment field is set to the value of the last call. +func (b *ACMEIssuerDNS01ProviderAzureDNSApplyConfiguration) WithEnvironment(value acmev1.AzureDNSEnvironment) *ACMEIssuerDNS01ProviderAzureDNSApplyConfiguration { + b.Environment = &value + return b +} + +// WithManagedIdentity sets the ManagedIdentity field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the ManagedIdentity field is set to the value of the last call. +func (b *ACMEIssuerDNS01ProviderAzureDNSApplyConfiguration) WithManagedIdentity(value *AzureManagedIdentityApplyConfiguration) *ACMEIssuerDNS01ProviderAzureDNSApplyConfiguration { + b.ManagedIdentity = value + return b +} diff --git a/pkg/client/applyconfigurations/acme/v1/acmeissuerdns01providerclouddns.go b/pkg/client/applyconfigurations/acme/v1/acmeissuerdns01providerclouddns.go new file mode 100644 index 00000000000..100c703f290 --- /dev/null +++ b/pkg/client/applyconfigurations/acme/v1/acmeissuerdns01providerclouddns.go @@ -0,0 +1,61 @@ +/* +Copyright 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. +*/ + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1 + +import ( + metav1 "github.com/cert-manager/cert-manager/pkg/client/applyconfigurations/meta/v1" +) + +// ACMEIssuerDNS01ProviderCloudDNSApplyConfiguration represents a declarative configuration of the ACMEIssuerDNS01ProviderCloudDNS type for use +// with apply. +type ACMEIssuerDNS01ProviderCloudDNSApplyConfiguration struct { + ServiceAccount *metav1.SecretKeySelectorApplyConfiguration `json:"serviceAccountSecretRef,omitempty"` + Project *string `json:"project,omitempty"` + HostedZoneName *string `json:"hostedZoneName,omitempty"` +} + +// ACMEIssuerDNS01ProviderCloudDNSApplyConfiguration constructs a declarative configuration of the ACMEIssuerDNS01ProviderCloudDNS type for use with +// apply. +func ACMEIssuerDNS01ProviderCloudDNS() *ACMEIssuerDNS01ProviderCloudDNSApplyConfiguration { + return &ACMEIssuerDNS01ProviderCloudDNSApplyConfiguration{} +} + +// WithServiceAccount sets the ServiceAccount field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the ServiceAccount field is set to the value of the last call. +func (b *ACMEIssuerDNS01ProviderCloudDNSApplyConfiguration) WithServiceAccount(value *metav1.SecretKeySelectorApplyConfiguration) *ACMEIssuerDNS01ProviderCloudDNSApplyConfiguration { + b.ServiceAccount = value + return b +} + +// WithProject sets the Project field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Project field is set to the value of the last call. +func (b *ACMEIssuerDNS01ProviderCloudDNSApplyConfiguration) WithProject(value string) *ACMEIssuerDNS01ProviderCloudDNSApplyConfiguration { + b.Project = &value + return b +} + +// WithHostedZoneName sets the HostedZoneName field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the HostedZoneName field is set to the value of the last call. +func (b *ACMEIssuerDNS01ProviderCloudDNSApplyConfiguration) WithHostedZoneName(value string) *ACMEIssuerDNS01ProviderCloudDNSApplyConfiguration { + b.HostedZoneName = &value + return b +} diff --git a/pkg/client/applyconfigurations/acme/v1/acmeissuerdns01providercloudflare.go b/pkg/client/applyconfigurations/acme/v1/acmeissuerdns01providercloudflare.go new file mode 100644 index 00000000000..477cb968900 --- /dev/null +++ b/pkg/client/applyconfigurations/acme/v1/acmeissuerdns01providercloudflare.go @@ -0,0 +1,61 @@ +/* +Copyright 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. +*/ + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1 + +import ( + metav1 "github.com/cert-manager/cert-manager/pkg/client/applyconfigurations/meta/v1" +) + +// ACMEIssuerDNS01ProviderCloudflareApplyConfiguration represents a declarative configuration of the ACMEIssuerDNS01ProviderCloudflare type for use +// with apply. +type ACMEIssuerDNS01ProviderCloudflareApplyConfiguration struct { + Email *string `json:"email,omitempty"` + APIKey *metav1.SecretKeySelectorApplyConfiguration `json:"apiKeySecretRef,omitempty"` + APIToken *metav1.SecretKeySelectorApplyConfiguration `json:"apiTokenSecretRef,omitempty"` +} + +// ACMEIssuerDNS01ProviderCloudflareApplyConfiguration constructs a declarative configuration of the ACMEIssuerDNS01ProviderCloudflare type for use with +// apply. +func ACMEIssuerDNS01ProviderCloudflare() *ACMEIssuerDNS01ProviderCloudflareApplyConfiguration { + return &ACMEIssuerDNS01ProviderCloudflareApplyConfiguration{} +} + +// WithEmail sets the Email field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Email field is set to the value of the last call. +func (b *ACMEIssuerDNS01ProviderCloudflareApplyConfiguration) WithEmail(value string) *ACMEIssuerDNS01ProviderCloudflareApplyConfiguration { + b.Email = &value + return b +} + +// WithAPIKey sets the APIKey field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the APIKey field is set to the value of the last call. +func (b *ACMEIssuerDNS01ProviderCloudflareApplyConfiguration) WithAPIKey(value *metav1.SecretKeySelectorApplyConfiguration) *ACMEIssuerDNS01ProviderCloudflareApplyConfiguration { + b.APIKey = value + return b +} + +// WithAPIToken sets the APIToken field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the APIToken field is set to the value of the last call. +func (b *ACMEIssuerDNS01ProviderCloudflareApplyConfiguration) WithAPIToken(value *metav1.SecretKeySelectorApplyConfiguration) *ACMEIssuerDNS01ProviderCloudflareApplyConfiguration { + b.APIToken = value + return b +} diff --git a/pkg/client/applyconfigurations/acme/v1/acmeissuerdns01providerdigitalocean.go b/pkg/client/applyconfigurations/acme/v1/acmeissuerdns01providerdigitalocean.go new file mode 100644 index 00000000000..89cc2ca28f7 --- /dev/null +++ b/pkg/client/applyconfigurations/acme/v1/acmeissuerdns01providerdigitalocean.go @@ -0,0 +1,43 @@ +/* +Copyright 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. +*/ + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1 + +import ( + metav1 "github.com/cert-manager/cert-manager/pkg/client/applyconfigurations/meta/v1" +) + +// ACMEIssuerDNS01ProviderDigitalOceanApplyConfiguration represents a declarative configuration of the ACMEIssuerDNS01ProviderDigitalOcean type for use +// with apply. +type ACMEIssuerDNS01ProviderDigitalOceanApplyConfiguration struct { + Token *metav1.SecretKeySelectorApplyConfiguration `json:"tokenSecretRef,omitempty"` +} + +// ACMEIssuerDNS01ProviderDigitalOceanApplyConfiguration constructs a declarative configuration of the ACMEIssuerDNS01ProviderDigitalOcean type for use with +// apply. +func ACMEIssuerDNS01ProviderDigitalOcean() *ACMEIssuerDNS01ProviderDigitalOceanApplyConfiguration { + return &ACMEIssuerDNS01ProviderDigitalOceanApplyConfiguration{} +} + +// WithToken sets the Token field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Token field is set to the value of the last call. +func (b *ACMEIssuerDNS01ProviderDigitalOceanApplyConfiguration) WithToken(value *metav1.SecretKeySelectorApplyConfiguration) *ACMEIssuerDNS01ProviderDigitalOceanApplyConfiguration { + b.Token = value + return b +} diff --git a/pkg/client/applyconfigurations/acme/v1/acmeissuerdns01providerrfc2136.go b/pkg/client/applyconfigurations/acme/v1/acmeissuerdns01providerrfc2136.go new file mode 100644 index 00000000000..b211f2c74f1 --- /dev/null +++ b/pkg/client/applyconfigurations/acme/v1/acmeissuerdns01providerrfc2136.go @@ -0,0 +1,80 @@ +/* +Copyright 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. +*/ + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1 + +import ( + acmev1 "github.com/cert-manager/cert-manager/pkg/apis/acme/v1" + metav1 "github.com/cert-manager/cert-manager/pkg/client/applyconfigurations/meta/v1" +) + +// ACMEIssuerDNS01ProviderRFC2136ApplyConfiguration represents a declarative configuration of the ACMEIssuerDNS01ProviderRFC2136 type for use +// with apply. +type ACMEIssuerDNS01ProviderRFC2136ApplyConfiguration struct { + Nameserver *string `json:"nameserver,omitempty"` + TSIGSecret *metav1.SecretKeySelectorApplyConfiguration `json:"tsigSecretSecretRef,omitempty"` + TSIGKeyName *string `json:"tsigKeyName,omitempty"` + TSIGAlgorithm *string `json:"tsigAlgorithm,omitempty"` + Protocol *acmev1.RFC2136UpdateProtocol `json:"protocol,omitempty"` +} + +// ACMEIssuerDNS01ProviderRFC2136ApplyConfiguration constructs a declarative configuration of the ACMEIssuerDNS01ProviderRFC2136 type for use with +// apply. +func ACMEIssuerDNS01ProviderRFC2136() *ACMEIssuerDNS01ProviderRFC2136ApplyConfiguration { + return &ACMEIssuerDNS01ProviderRFC2136ApplyConfiguration{} +} + +// WithNameserver sets the Nameserver field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Nameserver field is set to the value of the last call. +func (b *ACMEIssuerDNS01ProviderRFC2136ApplyConfiguration) WithNameserver(value string) *ACMEIssuerDNS01ProviderRFC2136ApplyConfiguration { + b.Nameserver = &value + return b +} + +// WithTSIGSecret sets the TSIGSecret field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the TSIGSecret field is set to the value of the last call. +func (b *ACMEIssuerDNS01ProviderRFC2136ApplyConfiguration) WithTSIGSecret(value *metav1.SecretKeySelectorApplyConfiguration) *ACMEIssuerDNS01ProviderRFC2136ApplyConfiguration { + b.TSIGSecret = value + return b +} + +// WithTSIGKeyName sets the TSIGKeyName field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the TSIGKeyName field is set to the value of the last call. +func (b *ACMEIssuerDNS01ProviderRFC2136ApplyConfiguration) WithTSIGKeyName(value string) *ACMEIssuerDNS01ProviderRFC2136ApplyConfiguration { + b.TSIGKeyName = &value + return b +} + +// WithTSIGAlgorithm sets the TSIGAlgorithm field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the TSIGAlgorithm field is set to the value of the last call. +func (b *ACMEIssuerDNS01ProviderRFC2136ApplyConfiguration) WithTSIGAlgorithm(value string) *ACMEIssuerDNS01ProviderRFC2136ApplyConfiguration { + b.TSIGAlgorithm = &value + return b +} + +// WithProtocol sets the Protocol field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Protocol field is set to the value of the last call. +func (b *ACMEIssuerDNS01ProviderRFC2136ApplyConfiguration) WithProtocol(value acmev1.RFC2136UpdateProtocol) *ACMEIssuerDNS01ProviderRFC2136ApplyConfiguration { + b.Protocol = &value + return b +} diff --git a/pkg/client/applyconfigurations/acme/v1/acmeissuerdns01providerroute53.go b/pkg/client/applyconfigurations/acme/v1/acmeissuerdns01providerroute53.go new file mode 100644 index 00000000000..f573d84f86e --- /dev/null +++ b/pkg/client/applyconfigurations/acme/v1/acmeissuerdns01providerroute53.go @@ -0,0 +1,97 @@ +/* +Copyright 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. +*/ + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1 + +import ( + metav1 "github.com/cert-manager/cert-manager/pkg/client/applyconfigurations/meta/v1" +) + +// ACMEIssuerDNS01ProviderRoute53ApplyConfiguration represents a declarative configuration of the ACMEIssuerDNS01ProviderRoute53 type for use +// with apply. +type ACMEIssuerDNS01ProviderRoute53ApplyConfiguration struct { + Auth *Route53AuthApplyConfiguration `json:"auth,omitempty"` + AccessKeyID *string `json:"accessKeyID,omitempty"` + SecretAccessKeyID *metav1.SecretKeySelectorApplyConfiguration `json:"accessKeyIDSecretRef,omitempty"` + SecretAccessKey *metav1.SecretKeySelectorApplyConfiguration `json:"secretAccessKeySecretRef,omitempty"` + Role *string `json:"role,omitempty"` + HostedZoneID *string `json:"hostedZoneID,omitempty"` + Region *string `json:"region,omitempty"` +} + +// ACMEIssuerDNS01ProviderRoute53ApplyConfiguration constructs a declarative configuration of the ACMEIssuerDNS01ProviderRoute53 type for use with +// apply. +func ACMEIssuerDNS01ProviderRoute53() *ACMEIssuerDNS01ProviderRoute53ApplyConfiguration { + return &ACMEIssuerDNS01ProviderRoute53ApplyConfiguration{} +} + +// WithAuth sets the Auth field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Auth field is set to the value of the last call. +func (b *ACMEIssuerDNS01ProviderRoute53ApplyConfiguration) WithAuth(value *Route53AuthApplyConfiguration) *ACMEIssuerDNS01ProviderRoute53ApplyConfiguration { + b.Auth = value + return b +} + +// WithAccessKeyID sets the AccessKeyID field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the AccessKeyID field is set to the value of the last call. +func (b *ACMEIssuerDNS01ProviderRoute53ApplyConfiguration) WithAccessKeyID(value string) *ACMEIssuerDNS01ProviderRoute53ApplyConfiguration { + b.AccessKeyID = &value + return b +} + +// WithSecretAccessKeyID sets the SecretAccessKeyID field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the SecretAccessKeyID field is set to the value of the last call. +func (b *ACMEIssuerDNS01ProviderRoute53ApplyConfiguration) WithSecretAccessKeyID(value *metav1.SecretKeySelectorApplyConfiguration) *ACMEIssuerDNS01ProviderRoute53ApplyConfiguration { + b.SecretAccessKeyID = value + return b +} + +// WithSecretAccessKey sets the SecretAccessKey field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the SecretAccessKey field is set to the value of the last call. +func (b *ACMEIssuerDNS01ProviderRoute53ApplyConfiguration) WithSecretAccessKey(value *metav1.SecretKeySelectorApplyConfiguration) *ACMEIssuerDNS01ProviderRoute53ApplyConfiguration { + b.SecretAccessKey = value + return b +} + +// WithRole sets the Role field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Role field is set to the value of the last call. +func (b *ACMEIssuerDNS01ProviderRoute53ApplyConfiguration) WithRole(value string) *ACMEIssuerDNS01ProviderRoute53ApplyConfiguration { + b.Role = &value + return b +} + +// WithHostedZoneID sets the HostedZoneID field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the HostedZoneID field is set to the value of the last call. +func (b *ACMEIssuerDNS01ProviderRoute53ApplyConfiguration) WithHostedZoneID(value string) *ACMEIssuerDNS01ProviderRoute53ApplyConfiguration { + b.HostedZoneID = &value + return b +} + +// WithRegion sets the Region field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Region field is set to the value of the last call. +func (b *ACMEIssuerDNS01ProviderRoute53ApplyConfiguration) WithRegion(value string) *ACMEIssuerDNS01ProviderRoute53ApplyConfiguration { + b.Region = &value + return b +} diff --git a/pkg/client/applyconfigurations/acme/v1/acmeissuerdns01providerwebhook.go b/pkg/client/applyconfigurations/acme/v1/acmeissuerdns01providerwebhook.go new file mode 100644 index 00000000000..56a85531321 --- /dev/null +++ b/pkg/client/applyconfigurations/acme/v1/acmeissuerdns01providerwebhook.go @@ -0,0 +1,61 @@ +/* +Copyright 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. +*/ + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1 + +import ( + apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" +) + +// ACMEIssuerDNS01ProviderWebhookApplyConfiguration represents a declarative configuration of the ACMEIssuerDNS01ProviderWebhook type for use +// with apply. +type ACMEIssuerDNS01ProviderWebhookApplyConfiguration struct { + GroupName *string `json:"groupName,omitempty"` + SolverName *string `json:"solverName,omitempty"` + Config *apiextensionsv1.JSON `json:"config,omitempty"` +} + +// ACMEIssuerDNS01ProviderWebhookApplyConfiguration constructs a declarative configuration of the ACMEIssuerDNS01ProviderWebhook type for use with +// apply. +func ACMEIssuerDNS01ProviderWebhook() *ACMEIssuerDNS01ProviderWebhookApplyConfiguration { + return &ACMEIssuerDNS01ProviderWebhookApplyConfiguration{} +} + +// WithGroupName sets the GroupName field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the GroupName field is set to the value of the last call. +func (b *ACMEIssuerDNS01ProviderWebhookApplyConfiguration) WithGroupName(value string) *ACMEIssuerDNS01ProviderWebhookApplyConfiguration { + b.GroupName = &value + return b +} + +// WithSolverName sets the SolverName field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the SolverName field is set to the value of the last call. +func (b *ACMEIssuerDNS01ProviderWebhookApplyConfiguration) WithSolverName(value string) *ACMEIssuerDNS01ProviderWebhookApplyConfiguration { + b.SolverName = &value + return b +} + +// WithConfig sets the Config field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Config field is set to the value of the last call. +func (b *ACMEIssuerDNS01ProviderWebhookApplyConfiguration) WithConfig(value apiextensionsv1.JSON) *ACMEIssuerDNS01ProviderWebhookApplyConfiguration { + b.Config = &value + return b +} diff --git a/pkg/client/applyconfigurations/acme/v1/acmeissuerstatus.go b/pkg/client/applyconfigurations/acme/v1/acmeissuerstatus.go new file mode 100644 index 00000000000..832739f38ee --- /dev/null +++ b/pkg/client/applyconfigurations/acme/v1/acmeissuerstatus.go @@ -0,0 +1,57 @@ +/* +Copyright 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. +*/ + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1 + +// ACMEIssuerStatusApplyConfiguration represents a declarative configuration of the ACMEIssuerStatus type for use +// with apply. +type ACMEIssuerStatusApplyConfiguration struct { + URI *string `json:"uri,omitempty"` + LastRegisteredEmail *string `json:"lastRegisteredEmail,omitempty"` + LastPrivateKeyHash *string `json:"lastPrivateKeyHash,omitempty"` +} + +// ACMEIssuerStatusApplyConfiguration constructs a declarative configuration of the ACMEIssuerStatus type for use with +// apply. +func ACMEIssuerStatus() *ACMEIssuerStatusApplyConfiguration { + return &ACMEIssuerStatusApplyConfiguration{} +} + +// WithURI sets the URI field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the URI field is set to the value of the last call. +func (b *ACMEIssuerStatusApplyConfiguration) WithURI(value string) *ACMEIssuerStatusApplyConfiguration { + b.URI = &value + return b +} + +// WithLastRegisteredEmail sets the LastRegisteredEmail field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the LastRegisteredEmail field is set to the value of the last call. +func (b *ACMEIssuerStatusApplyConfiguration) WithLastRegisteredEmail(value string) *ACMEIssuerStatusApplyConfiguration { + b.LastRegisteredEmail = &value + return b +} + +// WithLastPrivateKeyHash sets the LastPrivateKeyHash field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the LastPrivateKeyHash field is set to the value of the last call. +func (b *ACMEIssuerStatusApplyConfiguration) WithLastPrivateKeyHash(value string) *ACMEIssuerStatusApplyConfiguration { + b.LastPrivateKeyHash = &value + return b +} diff --git a/pkg/client/applyconfigurations/acme/v1/azuremanagedidentity.go b/pkg/client/applyconfigurations/acme/v1/azuremanagedidentity.go new file mode 100644 index 00000000000..155d3292ef7 --- /dev/null +++ b/pkg/client/applyconfigurations/acme/v1/azuremanagedidentity.go @@ -0,0 +1,57 @@ +/* +Copyright 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. +*/ + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1 + +// AzureManagedIdentityApplyConfiguration represents a declarative configuration of the AzureManagedIdentity type for use +// with apply. +type AzureManagedIdentityApplyConfiguration struct { + ClientID *string `json:"clientID,omitempty"` + ResourceID *string `json:"resourceID,omitempty"` + TenantID *string `json:"tenantID,omitempty"` +} + +// AzureManagedIdentityApplyConfiguration constructs a declarative configuration of the AzureManagedIdentity type for use with +// apply. +func AzureManagedIdentity() *AzureManagedIdentityApplyConfiguration { + return &AzureManagedIdentityApplyConfiguration{} +} + +// WithClientID sets the ClientID field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the ClientID field is set to the value of the last call. +func (b *AzureManagedIdentityApplyConfiguration) WithClientID(value string) *AzureManagedIdentityApplyConfiguration { + b.ClientID = &value + return b +} + +// WithResourceID sets the ResourceID field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the ResourceID field is set to the value of the last call. +func (b *AzureManagedIdentityApplyConfiguration) WithResourceID(value string) *AzureManagedIdentityApplyConfiguration { + b.ResourceID = &value + return b +} + +// WithTenantID sets the TenantID field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the TenantID field is set to the value of the last call. +func (b *AzureManagedIdentityApplyConfiguration) WithTenantID(value string) *AzureManagedIdentityApplyConfiguration { + b.TenantID = &value + return b +} diff --git a/pkg/client/applyconfigurations/acme/v1/certificatednsnameselector.go b/pkg/client/applyconfigurations/acme/v1/certificatednsnameselector.go new file mode 100644 index 00000000000..82b194bceac --- /dev/null +++ b/pkg/client/applyconfigurations/acme/v1/certificatednsnameselector.go @@ -0,0 +1,67 @@ +/* +Copyright 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. +*/ + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1 + +// CertificateDNSNameSelectorApplyConfiguration represents a declarative configuration of the CertificateDNSNameSelector type for use +// with apply. +type CertificateDNSNameSelectorApplyConfiguration struct { + MatchLabels map[string]string `json:"matchLabels,omitempty"` + DNSNames []string `json:"dnsNames,omitempty"` + DNSZones []string `json:"dnsZones,omitempty"` +} + +// CertificateDNSNameSelectorApplyConfiguration constructs a declarative configuration of the CertificateDNSNameSelector type for use with +// apply. +func CertificateDNSNameSelector() *CertificateDNSNameSelectorApplyConfiguration { + return &CertificateDNSNameSelectorApplyConfiguration{} +} + +// WithMatchLabels puts the entries into the MatchLabels field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, the entries provided by each call will be put on the MatchLabels field, +// overwriting an existing map entries in MatchLabels field with the same key. +func (b *CertificateDNSNameSelectorApplyConfiguration) WithMatchLabels(entries map[string]string) *CertificateDNSNameSelectorApplyConfiguration { + if b.MatchLabels == nil && len(entries) > 0 { + b.MatchLabels = make(map[string]string, len(entries)) + } + for k, v := range entries { + b.MatchLabels[k] = v + } + return b +} + +// WithDNSNames adds the given value to the DNSNames field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the DNSNames field. +func (b *CertificateDNSNameSelectorApplyConfiguration) WithDNSNames(values ...string) *CertificateDNSNameSelectorApplyConfiguration { + for i := range values { + b.DNSNames = append(b.DNSNames, values[i]) + } + return b +} + +// WithDNSZones adds the given value to the DNSZones field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the DNSZones field. +func (b *CertificateDNSNameSelectorApplyConfiguration) WithDNSZones(values ...string) *CertificateDNSNameSelectorApplyConfiguration { + for i := range values { + b.DNSZones = append(b.DNSZones, values[i]) + } + return b +} diff --git a/pkg/client/applyconfigurations/acme/v1/challenge.go b/pkg/client/applyconfigurations/acme/v1/challenge.go new file mode 100644 index 00000000000..d26c143d61d --- /dev/null +++ b/pkg/client/applyconfigurations/acme/v1/challenge.go @@ -0,0 +1,281 @@ +/* +Copyright 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. +*/ + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1 + +import ( + acmev1 "github.com/cert-manager/cert-manager/pkg/apis/acme/v1" + internal "github.com/cert-manager/cert-manager/pkg/client/applyconfigurations/internal" + apismetav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + types "k8s.io/apimachinery/pkg/types" + managedfields "k8s.io/apimachinery/pkg/util/managedfields" + metav1 "k8s.io/client-go/applyconfigurations/meta/v1" +) + +// ChallengeApplyConfiguration represents a declarative configuration of the Challenge type for use +// with apply. +type ChallengeApplyConfiguration struct { + metav1.TypeMetaApplyConfiguration `json:",inline"` + *metav1.ObjectMetaApplyConfiguration `json:"metadata,omitempty"` + Spec *ChallengeSpecApplyConfiguration `json:"spec,omitempty"` + Status *ChallengeStatusApplyConfiguration `json:"status,omitempty"` +} + +// Challenge constructs a declarative configuration of the Challenge type for use with +// apply. +func Challenge(name, namespace string) *ChallengeApplyConfiguration { + b := &ChallengeApplyConfiguration{} + b.WithName(name) + b.WithNamespace(namespace) + b.WithKind("Challenge") + b.WithAPIVersion("acme.cert-manager.io/v1") + return b +} + +// ExtractChallenge extracts the applied configuration owned by fieldManager from +// challenge. If no managedFields are found in challenge for fieldManager, a +// ChallengeApplyConfiguration is returned with only the Name, Namespace (if applicable), +// APIVersion and Kind populated. It is possible that no managed fields were found for because other +// field managers have taken ownership of all the fields previously owned by fieldManager, or because +// the fieldManager never owned fields any fields. +// challenge must be a unmodified Challenge API object that was retrieved from the Kubernetes API. +// ExtractChallenge provides a way to perform a extract/modify-in-place/apply workflow. +// Note that an extracted apply configuration will contain fewer fields than what the fieldManager previously +// applied if another fieldManager has updated or force applied any of the previously applied fields. +// Experimental! +func ExtractChallenge(challenge *acmev1.Challenge, fieldManager string) (*ChallengeApplyConfiguration, error) { + return extractChallenge(challenge, fieldManager, "") +} + +// ExtractChallengeStatus is the same as ExtractChallenge except +// that it extracts the status subresource applied configuration. +// Experimental! +func ExtractChallengeStatus(challenge *acmev1.Challenge, fieldManager string) (*ChallengeApplyConfiguration, error) { + return extractChallenge(challenge, fieldManager, "status") +} + +func extractChallenge(challenge *acmev1.Challenge, fieldManager string, subresource string) (*ChallengeApplyConfiguration, error) { + b := &ChallengeApplyConfiguration{} + err := managedfields.ExtractInto(challenge, internal.Parser().Type("com.github.cert-manager.cert-manager.pkg.apis.acme.v1.Challenge"), fieldManager, b, subresource) + if err != nil { + return nil, err + } + b.WithName(challenge.Name) + b.WithNamespace(challenge.Namespace) + + b.WithKind("Challenge") + b.WithAPIVersion("acme.cert-manager.io/v1") + return b, nil +} +func (b ChallengeApplyConfiguration) IsApplyConfiguration() {} + +// WithKind sets the Kind field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Kind field is set to the value of the last call. +func (b *ChallengeApplyConfiguration) WithKind(value string) *ChallengeApplyConfiguration { + b.TypeMetaApplyConfiguration.Kind = &value + return b +} + +// WithAPIVersion sets the APIVersion field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the APIVersion field is set to the value of the last call. +func (b *ChallengeApplyConfiguration) WithAPIVersion(value string) *ChallengeApplyConfiguration { + b.TypeMetaApplyConfiguration.APIVersion = &value + return b +} + +// WithName sets the Name field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Name field is set to the value of the last call. +func (b *ChallengeApplyConfiguration) WithName(value string) *ChallengeApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.Name = &value + return b +} + +// WithGenerateName sets the GenerateName field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the GenerateName field is set to the value of the last call. +func (b *ChallengeApplyConfiguration) WithGenerateName(value string) *ChallengeApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.GenerateName = &value + return b +} + +// WithNamespace sets the Namespace field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Namespace field is set to the value of the last call. +func (b *ChallengeApplyConfiguration) WithNamespace(value string) *ChallengeApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.Namespace = &value + return b +} + +// WithUID sets the UID field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the UID field is set to the value of the last call. +func (b *ChallengeApplyConfiguration) WithUID(value types.UID) *ChallengeApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.UID = &value + return b +} + +// WithResourceVersion sets the ResourceVersion field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the ResourceVersion field is set to the value of the last call. +func (b *ChallengeApplyConfiguration) WithResourceVersion(value string) *ChallengeApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.ResourceVersion = &value + return b +} + +// WithGeneration sets the Generation field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Generation field is set to the value of the last call. +func (b *ChallengeApplyConfiguration) WithGeneration(value int64) *ChallengeApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.Generation = &value + return b +} + +// WithCreationTimestamp sets the CreationTimestamp field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the CreationTimestamp field is set to the value of the last call. +func (b *ChallengeApplyConfiguration) WithCreationTimestamp(value apismetav1.Time) *ChallengeApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.CreationTimestamp = &value + return b +} + +// WithDeletionTimestamp sets the DeletionTimestamp field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the DeletionTimestamp field is set to the value of the last call. +func (b *ChallengeApplyConfiguration) WithDeletionTimestamp(value apismetav1.Time) *ChallengeApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.DeletionTimestamp = &value + return b +} + +// WithDeletionGracePeriodSeconds sets the DeletionGracePeriodSeconds field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the DeletionGracePeriodSeconds field is set to the value of the last call. +func (b *ChallengeApplyConfiguration) WithDeletionGracePeriodSeconds(value int64) *ChallengeApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.DeletionGracePeriodSeconds = &value + return b +} + +// WithLabels puts the entries into the Labels field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, the entries provided by each call will be put on the Labels field, +// overwriting an existing map entries in Labels field with the same key. +func (b *ChallengeApplyConfiguration) WithLabels(entries map[string]string) *ChallengeApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + if b.ObjectMetaApplyConfiguration.Labels == nil && len(entries) > 0 { + b.ObjectMetaApplyConfiguration.Labels = make(map[string]string, len(entries)) + } + for k, v := range entries { + b.ObjectMetaApplyConfiguration.Labels[k] = v + } + return b +} + +// WithAnnotations puts the entries into the Annotations field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, the entries provided by each call will be put on the Annotations field, +// overwriting an existing map entries in Annotations field with the same key. +func (b *ChallengeApplyConfiguration) WithAnnotations(entries map[string]string) *ChallengeApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + if b.ObjectMetaApplyConfiguration.Annotations == nil && len(entries) > 0 { + b.ObjectMetaApplyConfiguration.Annotations = make(map[string]string, len(entries)) + } + for k, v := range entries { + b.ObjectMetaApplyConfiguration.Annotations[k] = v + } + return b +} + +// WithOwnerReferences adds the given value to the OwnerReferences field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the OwnerReferences field. +func (b *ChallengeApplyConfiguration) WithOwnerReferences(values ...*metav1.OwnerReferenceApplyConfiguration) *ChallengeApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + for i := range values { + if values[i] == nil { + panic("nil value passed to WithOwnerReferences") + } + b.ObjectMetaApplyConfiguration.OwnerReferences = append(b.ObjectMetaApplyConfiguration.OwnerReferences, *values[i]) + } + return b +} + +// WithFinalizers adds the given value to the Finalizers field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the Finalizers field. +func (b *ChallengeApplyConfiguration) WithFinalizers(values ...string) *ChallengeApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + for i := range values { + b.ObjectMetaApplyConfiguration.Finalizers = append(b.ObjectMetaApplyConfiguration.Finalizers, values[i]) + } + return b +} + +func (b *ChallengeApplyConfiguration) ensureObjectMetaApplyConfigurationExists() { + if b.ObjectMetaApplyConfiguration == nil { + b.ObjectMetaApplyConfiguration = &metav1.ObjectMetaApplyConfiguration{} + } +} + +// WithSpec sets the Spec field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Spec field is set to the value of the last call. +func (b *ChallengeApplyConfiguration) WithSpec(value *ChallengeSpecApplyConfiguration) *ChallengeApplyConfiguration { + b.Spec = value + return b +} + +// WithStatus sets the Status field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Status field is set to the value of the last call. +func (b *ChallengeApplyConfiguration) WithStatus(value *ChallengeStatusApplyConfiguration) *ChallengeApplyConfiguration { + b.Status = value + return b +} + +// GetKind retrieves the value of the Kind field in the declarative configuration. +func (b *ChallengeApplyConfiguration) GetKind() *string { + return b.TypeMetaApplyConfiguration.Kind +} + +// GetAPIVersion retrieves the value of the APIVersion field in the declarative configuration. +func (b *ChallengeApplyConfiguration) GetAPIVersion() *string { + return b.TypeMetaApplyConfiguration.APIVersion +} + +// GetName retrieves the value of the Name field in the declarative configuration. +func (b *ChallengeApplyConfiguration) GetName() *string { + b.ensureObjectMetaApplyConfigurationExists() + return b.ObjectMetaApplyConfiguration.Name +} + +// GetNamespace retrieves the value of the Namespace field in the declarative configuration. +func (b *ChallengeApplyConfiguration) GetNamespace() *string { + b.ensureObjectMetaApplyConfigurationExists() + return b.ObjectMetaApplyConfiguration.Namespace +} diff --git a/pkg/client/applyconfigurations/acme/v1/challengespec.go b/pkg/client/applyconfigurations/acme/v1/challengespec.go new file mode 100644 index 00000000000..af629b6f0d1 --- /dev/null +++ b/pkg/client/applyconfigurations/acme/v1/challengespec.go @@ -0,0 +1,116 @@ +/* +Copyright 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. +*/ + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1 + +import ( + acmev1 "github.com/cert-manager/cert-manager/pkg/apis/acme/v1" + metav1 "github.com/cert-manager/cert-manager/pkg/client/applyconfigurations/meta/v1" +) + +// ChallengeSpecApplyConfiguration represents a declarative configuration of the ChallengeSpec type for use +// with apply. +type ChallengeSpecApplyConfiguration struct { + URL *string `json:"url,omitempty"` + AuthorizationURL *string `json:"authorizationURL,omitempty"` + DNSName *string `json:"dnsName,omitempty"` + Wildcard *bool `json:"wildcard,omitempty"` + Type *acmev1.ACMEChallengeType `json:"type,omitempty"` + Token *string `json:"token,omitempty"` + Key *string `json:"key,omitempty"` + Solver *ACMEChallengeSolverApplyConfiguration `json:"solver,omitempty"` + IssuerRef *metav1.IssuerReferenceApplyConfiguration `json:"issuerRef,omitempty"` +} + +// ChallengeSpecApplyConfiguration constructs a declarative configuration of the ChallengeSpec type for use with +// apply. +func ChallengeSpec() *ChallengeSpecApplyConfiguration { + return &ChallengeSpecApplyConfiguration{} +} + +// WithURL sets the URL field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the URL field is set to the value of the last call. +func (b *ChallengeSpecApplyConfiguration) WithURL(value string) *ChallengeSpecApplyConfiguration { + b.URL = &value + return b +} + +// WithAuthorizationURL sets the AuthorizationURL field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the AuthorizationURL field is set to the value of the last call. +func (b *ChallengeSpecApplyConfiguration) WithAuthorizationURL(value string) *ChallengeSpecApplyConfiguration { + b.AuthorizationURL = &value + return b +} + +// WithDNSName sets the DNSName field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the DNSName field is set to the value of the last call. +func (b *ChallengeSpecApplyConfiguration) WithDNSName(value string) *ChallengeSpecApplyConfiguration { + b.DNSName = &value + return b +} + +// WithWildcard sets the Wildcard field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Wildcard field is set to the value of the last call. +func (b *ChallengeSpecApplyConfiguration) WithWildcard(value bool) *ChallengeSpecApplyConfiguration { + b.Wildcard = &value + return b +} + +// WithType sets the Type field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Type field is set to the value of the last call. +func (b *ChallengeSpecApplyConfiguration) WithType(value acmev1.ACMEChallengeType) *ChallengeSpecApplyConfiguration { + b.Type = &value + return b +} + +// WithToken sets the Token field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Token field is set to the value of the last call. +func (b *ChallengeSpecApplyConfiguration) WithToken(value string) *ChallengeSpecApplyConfiguration { + b.Token = &value + return b +} + +// WithKey sets the Key field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Key field is set to the value of the last call. +func (b *ChallengeSpecApplyConfiguration) WithKey(value string) *ChallengeSpecApplyConfiguration { + b.Key = &value + return b +} + +// WithSolver sets the Solver field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Solver field is set to the value of the last call. +func (b *ChallengeSpecApplyConfiguration) WithSolver(value *ACMEChallengeSolverApplyConfiguration) *ChallengeSpecApplyConfiguration { + b.Solver = value + return b +} + +// WithIssuerRef sets the IssuerRef field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the IssuerRef field is set to the value of the last call. +func (b *ChallengeSpecApplyConfiguration) WithIssuerRef(value *metav1.IssuerReferenceApplyConfiguration) *ChallengeSpecApplyConfiguration { + b.IssuerRef = value + return b +} diff --git a/pkg/client/applyconfigurations/acme/v1/challengestatus.go b/pkg/client/applyconfigurations/acme/v1/challengestatus.go new file mode 100644 index 00000000000..12864143547 --- /dev/null +++ b/pkg/client/applyconfigurations/acme/v1/challengestatus.go @@ -0,0 +1,70 @@ +/* +Copyright 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. +*/ + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1 + +import ( + acmev1 "github.com/cert-manager/cert-manager/pkg/apis/acme/v1" +) + +// ChallengeStatusApplyConfiguration represents a declarative configuration of the ChallengeStatus type for use +// with apply. +type ChallengeStatusApplyConfiguration struct { + Processing *bool `json:"processing,omitempty"` + Presented *bool `json:"presented,omitempty"` + Reason *string `json:"reason,omitempty"` + State *acmev1.State `json:"state,omitempty"` +} + +// ChallengeStatusApplyConfiguration constructs a declarative configuration of the ChallengeStatus type for use with +// apply. +func ChallengeStatus() *ChallengeStatusApplyConfiguration { + return &ChallengeStatusApplyConfiguration{} +} + +// WithProcessing sets the Processing field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Processing field is set to the value of the last call. +func (b *ChallengeStatusApplyConfiguration) WithProcessing(value bool) *ChallengeStatusApplyConfiguration { + b.Processing = &value + return b +} + +// WithPresented sets the Presented field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Presented field is set to the value of the last call. +func (b *ChallengeStatusApplyConfiguration) WithPresented(value bool) *ChallengeStatusApplyConfiguration { + b.Presented = &value + return b +} + +// WithReason sets the Reason field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Reason field is set to the value of the last call. +func (b *ChallengeStatusApplyConfiguration) WithReason(value string) *ChallengeStatusApplyConfiguration { + b.Reason = &value + return b +} + +// WithState sets the State field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the State field is set to the value of the last call. +func (b *ChallengeStatusApplyConfiguration) WithState(value acmev1.State) *ChallengeStatusApplyConfiguration { + b.State = &value + return b +} diff --git a/pkg/client/applyconfigurations/acme/v1/order.go b/pkg/client/applyconfigurations/acme/v1/order.go new file mode 100644 index 00000000000..cf45d0330e7 --- /dev/null +++ b/pkg/client/applyconfigurations/acme/v1/order.go @@ -0,0 +1,281 @@ +/* +Copyright 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. +*/ + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1 + +import ( + acmev1 "github.com/cert-manager/cert-manager/pkg/apis/acme/v1" + internal "github.com/cert-manager/cert-manager/pkg/client/applyconfigurations/internal" + apismetav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + types "k8s.io/apimachinery/pkg/types" + managedfields "k8s.io/apimachinery/pkg/util/managedfields" + metav1 "k8s.io/client-go/applyconfigurations/meta/v1" +) + +// OrderApplyConfiguration represents a declarative configuration of the Order type for use +// with apply. +type OrderApplyConfiguration struct { + metav1.TypeMetaApplyConfiguration `json:",inline"` + *metav1.ObjectMetaApplyConfiguration `json:"metadata,omitempty"` + Spec *OrderSpecApplyConfiguration `json:"spec,omitempty"` + Status *OrderStatusApplyConfiguration `json:"status,omitempty"` +} + +// Order constructs a declarative configuration of the Order type for use with +// apply. +func Order(name, namespace string) *OrderApplyConfiguration { + b := &OrderApplyConfiguration{} + b.WithName(name) + b.WithNamespace(namespace) + b.WithKind("Order") + b.WithAPIVersion("acme.cert-manager.io/v1") + return b +} + +// ExtractOrder extracts the applied configuration owned by fieldManager from +// order. If no managedFields are found in order for fieldManager, a +// OrderApplyConfiguration is returned with only the Name, Namespace (if applicable), +// APIVersion and Kind populated. It is possible that no managed fields were found for because other +// field managers have taken ownership of all the fields previously owned by fieldManager, or because +// the fieldManager never owned fields any fields. +// order must be a unmodified Order API object that was retrieved from the Kubernetes API. +// ExtractOrder provides a way to perform a extract/modify-in-place/apply workflow. +// Note that an extracted apply configuration will contain fewer fields than what the fieldManager previously +// applied if another fieldManager has updated or force applied any of the previously applied fields. +// Experimental! +func ExtractOrder(order *acmev1.Order, fieldManager string) (*OrderApplyConfiguration, error) { + return extractOrder(order, fieldManager, "") +} + +// ExtractOrderStatus is the same as ExtractOrder except +// that it extracts the status subresource applied configuration. +// Experimental! +func ExtractOrderStatus(order *acmev1.Order, fieldManager string) (*OrderApplyConfiguration, error) { + return extractOrder(order, fieldManager, "status") +} + +func extractOrder(order *acmev1.Order, fieldManager string, subresource string) (*OrderApplyConfiguration, error) { + b := &OrderApplyConfiguration{} + err := managedfields.ExtractInto(order, internal.Parser().Type("com.github.cert-manager.cert-manager.pkg.apis.acme.v1.Order"), fieldManager, b, subresource) + if err != nil { + return nil, err + } + b.WithName(order.Name) + b.WithNamespace(order.Namespace) + + b.WithKind("Order") + b.WithAPIVersion("acme.cert-manager.io/v1") + return b, nil +} +func (b OrderApplyConfiguration) IsApplyConfiguration() {} + +// WithKind sets the Kind field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Kind field is set to the value of the last call. +func (b *OrderApplyConfiguration) WithKind(value string) *OrderApplyConfiguration { + b.TypeMetaApplyConfiguration.Kind = &value + return b +} + +// WithAPIVersion sets the APIVersion field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the APIVersion field is set to the value of the last call. +func (b *OrderApplyConfiguration) WithAPIVersion(value string) *OrderApplyConfiguration { + b.TypeMetaApplyConfiguration.APIVersion = &value + return b +} + +// WithName sets the Name field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Name field is set to the value of the last call. +func (b *OrderApplyConfiguration) WithName(value string) *OrderApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.Name = &value + return b +} + +// WithGenerateName sets the GenerateName field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the GenerateName field is set to the value of the last call. +func (b *OrderApplyConfiguration) WithGenerateName(value string) *OrderApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.GenerateName = &value + return b +} + +// WithNamespace sets the Namespace field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Namespace field is set to the value of the last call. +func (b *OrderApplyConfiguration) WithNamespace(value string) *OrderApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.Namespace = &value + return b +} + +// WithUID sets the UID field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the UID field is set to the value of the last call. +func (b *OrderApplyConfiguration) WithUID(value types.UID) *OrderApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.UID = &value + return b +} + +// WithResourceVersion sets the ResourceVersion field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the ResourceVersion field is set to the value of the last call. +func (b *OrderApplyConfiguration) WithResourceVersion(value string) *OrderApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.ResourceVersion = &value + return b +} + +// WithGeneration sets the Generation field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Generation field is set to the value of the last call. +func (b *OrderApplyConfiguration) WithGeneration(value int64) *OrderApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.Generation = &value + return b +} + +// WithCreationTimestamp sets the CreationTimestamp field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the CreationTimestamp field is set to the value of the last call. +func (b *OrderApplyConfiguration) WithCreationTimestamp(value apismetav1.Time) *OrderApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.CreationTimestamp = &value + return b +} + +// WithDeletionTimestamp sets the DeletionTimestamp field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the DeletionTimestamp field is set to the value of the last call. +func (b *OrderApplyConfiguration) WithDeletionTimestamp(value apismetav1.Time) *OrderApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.DeletionTimestamp = &value + return b +} + +// WithDeletionGracePeriodSeconds sets the DeletionGracePeriodSeconds field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the DeletionGracePeriodSeconds field is set to the value of the last call. +func (b *OrderApplyConfiguration) WithDeletionGracePeriodSeconds(value int64) *OrderApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.DeletionGracePeriodSeconds = &value + return b +} + +// WithLabels puts the entries into the Labels field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, the entries provided by each call will be put on the Labels field, +// overwriting an existing map entries in Labels field with the same key. +func (b *OrderApplyConfiguration) WithLabels(entries map[string]string) *OrderApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + if b.ObjectMetaApplyConfiguration.Labels == nil && len(entries) > 0 { + b.ObjectMetaApplyConfiguration.Labels = make(map[string]string, len(entries)) + } + for k, v := range entries { + b.ObjectMetaApplyConfiguration.Labels[k] = v + } + return b +} + +// WithAnnotations puts the entries into the Annotations field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, the entries provided by each call will be put on the Annotations field, +// overwriting an existing map entries in Annotations field with the same key. +func (b *OrderApplyConfiguration) WithAnnotations(entries map[string]string) *OrderApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + if b.ObjectMetaApplyConfiguration.Annotations == nil && len(entries) > 0 { + b.ObjectMetaApplyConfiguration.Annotations = make(map[string]string, len(entries)) + } + for k, v := range entries { + b.ObjectMetaApplyConfiguration.Annotations[k] = v + } + return b +} + +// WithOwnerReferences adds the given value to the OwnerReferences field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the OwnerReferences field. +func (b *OrderApplyConfiguration) WithOwnerReferences(values ...*metav1.OwnerReferenceApplyConfiguration) *OrderApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + for i := range values { + if values[i] == nil { + panic("nil value passed to WithOwnerReferences") + } + b.ObjectMetaApplyConfiguration.OwnerReferences = append(b.ObjectMetaApplyConfiguration.OwnerReferences, *values[i]) + } + return b +} + +// WithFinalizers adds the given value to the Finalizers field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the Finalizers field. +func (b *OrderApplyConfiguration) WithFinalizers(values ...string) *OrderApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + for i := range values { + b.ObjectMetaApplyConfiguration.Finalizers = append(b.ObjectMetaApplyConfiguration.Finalizers, values[i]) + } + return b +} + +func (b *OrderApplyConfiguration) ensureObjectMetaApplyConfigurationExists() { + if b.ObjectMetaApplyConfiguration == nil { + b.ObjectMetaApplyConfiguration = &metav1.ObjectMetaApplyConfiguration{} + } +} + +// WithSpec sets the Spec field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Spec field is set to the value of the last call. +func (b *OrderApplyConfiguration) WithSpec(value *OrderSpecApplyConfiguration) *OrderApplyConfiguration { + b.Spec = value + return b +} + +// WithStatus sets the Status field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Status field is set to the value of the last call. +func (b *OrderApplyConfiguration) WithStatus(value *OrderStatusApplyConfiguration) *OrderApplyConfiguration { + b.Status = value + return b +} + +// GetKind retrieves the value of the Kind field in the declarative configuration. +func (b *OrderApplyConfiguration) GetKind() *string { + return b.TypeMetaApplyConfiguration.Kind +} + +// GetAPIVersion retrieves the value of the APIVersion field in the declarative configuration. +func (b *OrderApplyConfiguration) GetAPIVersion() *string { + return b.TypeMetaApplyConfiguration.APIVersion +} + +// GetName retrieves the value of the Name field in the declarative configuration. +func (b *OrderApplyConfiguration) GetName() *string { + b.ensureObjectMetaApplyConfigurationExists() + return b.ObjectMetaApplyConfiguration.Name +} + +// GetNamespace retrieves the value of the Namespace field in the declarative configuration. +func (b *OrderApplyConfiguration) GetNamespace() *string { + b.ensureObjectMetaApplyConfigurationExists() + return b.ObjectMetaApplyConfiguration.Namespace +} diff --git a/pkg/client/applyconfigurations/acme/v1/orderspec.go b/pkg/client/applyconfigurations/acme/v1/orderspec.go new file mode 100644 index 00000000000..6a26038ac63 --- /dev/null +++ b/pkg/client/applyconfigurations/acme/v1/orderspec.go @@ -0,0 +1,104 @@ +/* +Copyright 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. +*/ + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1 + +import ( + metav1 "github.com/cert-manager/cert-manager/pkg/client/applyconfigurations/meta/v1" + apismetav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// OrderSpecApplyConfiguration represents a declarative configuration of the OrderSpec type for use +// with apply. +type OrderSpecApplyConfiguration struct { + Request []byte `json:"request,omitempty"` + IssuerRef *metav1.IssuerReferenceApplyConfiguration `json:"issuerRef,omitempty"` + CommonName *string `json:"commonName,omitempty"` + DNSNames []string `json:"dnsNames,omitempty"` + IPAddresses []string `json:"ipAddresses,omitempty"` + Duration *apismetav1.Duration `json:"duration,omitempty"` + Profile *string `json:"profile,omitempty"` +} + +// OrderSpecApplyConfiguration constructs a declarative configuration of the OrderSpec type for use with +// apply. +func OrderSpec() *OrderSpecApplyConfiguration { + return &OrderSpecApplyConfiguration{} +} + +// WithRequest adds the given value to the Request field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the Request field. +func (b *OrderSpecApplyConfiguration) WithRequest(values ...byte) *OrderSpecApplyConfiguration { + for i := range values { + b.Request = append(b.Request, values[i]) + } + return b +} + +// WithIssuerRef sets the IssuerRef field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the IssuerRef field is set to the value of the last call. +func (b *OrderSpecApplyConfiguration) WithIssuerRef(value *metav1.IssuerReferenceApplyConfiguration) *OrderSpecApplyConfiguration { + b.IssuerRef = value + return b +} + +// WithCommonName sets the CommonName field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the CommonName field is set to the value of the last call. +func (b *OrderSpecApplyConfiguration) WithCommonName(value string) *OrderSpecApplyConfiguration { + b.CommonName = &value + return b +} + +// WithDNSNames adds the given value to the DNSNames field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the DNSNames field. +func (b *OrderSpecApplyConfiguration) WithDNSNames(values ...string) *OrderSpecApplyConfiguration { + for i := range values { + b.DNSNames = append(b.DNSNames, values[i]) + } + return b +} + +// WithIPAddresses adds the given value to the IPAddresses field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the IPAddresses field. +func (b *OrderSpecApplyConfiguration) WithIPAddresses(values ...string) *OrderSpecApplyConfiguration { + for i := range values { + b.IPAddresses = append(b.IPAddresses, values[i]) + } + return b +} + +// WithDuration sets the Duration field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Duration field is set to the value of the last call. +func (b *OrderSpecApplyConfiguration) WithDuration(value apismetav1.Duration) *OrderSpecApplyConfiguration { + b.Duration = &value + return b +} + +// WithProfile sets the Profile field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Profile field is set to the value of the last call. +func (b *OrderSpecApplyConfiguration) WithProfile(value string) *OrderSpecApplyConfiguration { + b.Profile = &value + return b +} diff --git a/pkg/client/applyconfigurations/acme/v1/orderstatus.go b/pkg/client/applyconfigurations/acme/v1/orderstatus.go new file mode 100644 index 00000000000..e889a33c564 --- /dev/null +++ b/pkg/client/applyconfigurations/acme/v1/orderstatus.go @@ -0,0 +1,105 @@ +/* +Copyright 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. +*/ + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1 + +import ( + acmev1 "github.com/cert-manager/cert-manager/pkg/apis/acme/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// OrderStatusApplyConfiguration represents a declarative configuration of the OrderStatus type for use +// with apply. +type OrderStatusApplyConfiguration struct { + URL *string `json:"url,omitempty"` + FinalizeURL *string `json:"finalizeURL,omitempty"` + Authorizations []ACMEAuthorizationApplyConfiguration `json:"authorizations,omitempty"` + Certificate []byte `json:"certificate,omitempty"` + State *acmev1.State `json:"state,omitempty"` + Reason *string `json:"reason,omitempty"` + FailureTime *metav1.Time `json:"failureTime,omitempty"` +} + +// OrderStatusApplyConfiguration constructs a declarative configuration of the OrderStatus type for use with +// apply. +func OrderStatus() *OrderStatusApplyConfiguration { + return &OrderStatusApplyConfiguration{} +} + +// WithURL sets the URL field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the URL field is set to the value of the last call. +func (b *OrderStatusApplyConfiguration) WithURL(value string) *OrderStatusApplyConfiguration { + b.URL = &value + return b +} + +// WithFinalizeURL sets the FinalizeURL field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the FinalizeURL field is set to the value of the last call. +func (b *OrderStatusApplyConfiguration) WithFinalizeURL(value string) *OrderStatusApplyConfiguration { + b.FinalizeURL = &value + return b +} + +// WithAuthorizations adds the given value to the Authorizations field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the Authorizations field. +func (b *OrderStatusApplyConfiguration) WithAuthorizations(values ...*ACMEAuthorizationApplyConfiguration) *OrderStatusApplyConfiguration { + for i := range values { + if values[i] == nil { + panic("nil value passed to WithAuthorizations") + } + b.Authorizations = append(b.Authorizations, *values[i]) + } + return b +} + +// WithCertificate adds the given value to the Certificate field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the Certificate field. +func (b *OrderStatusApplyConfiguration) WithCertificate(values ...byte) *OrderStatusApplyConfiguration { + for i := range values { + b.Certificate = append(b.Certificate, values[i]) + } + return b +} + +// WithState sets the State field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the State field is set to the value of the last call. +func (b *OrderStatusApplyConfiguration) WithState(value acmev1.State) *OrderStatusApplyConfiguration { + b.State = &value + return b +} + +// WithReason sets the Reason field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Reason field is set to the value of the last call. +func (b *OrderStatusApplyConfiguration) WithReason(value string) *OrderStatusApplyConfiguration { + b.Reason = &value + return b +} + +// WithFailureTime sets the FailureTime field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the FailureTime field is set to the value of the last call. +func (b *OrderStatusApplyConfiguration) WithFailureTime(value metav1.Time) *OrderStatusApplyConfiguration { + b.FailureTime = &value + return b +} diff --git a/pkg/client/applyconfigurations/acme/v1/route53auth.go b/pkg/client/applyconfigurations/acme/v1/route53auth.go new file mode 100644 index 00000000000..d3c8d719d98 --- /dev/null +++ b/pkg/client/applyconfigurations/acme/v1/route53auth.go @@ -0,0 +1,39 @@ +/* +Copyright 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. +*/ + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1 + +// Route53AuthApplyConfiguration represents a declarative configuration of the Route53Auth type for use +// with apply. +type Route53AuthApplyConfiguration struct { + Kubernetes *Route53KubernetesAuthApplyConfiguration `json:"kubernetes,omitempty"` +} + +// Route53AuthApplyConfiguration constructs a declarative configuration of the Route53Auth type for use with +// apply. +func Route53Auth() *Route53AuthApplyConfiguration { + return &Route53AuthApplyConfiguration{} +} + +// WithKubernetes sets the Kubernetes field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Kubernetes field is set to the value of the last call. +func (b *Route53AuthApplyConfiguration) WithKubernetes(value *Route53KubernetesAuthApplyConfiguration) *Route53AuthApplyConfiguration { + b.Kubernetes = value + return b +} diff --git a/pkg/client/applyconfigurations/acme/v1/route53kubernetesauth.go b/pkg/client/applyconfigurations/acme/v1/route53kubernetesauth.go new file mode 100644 index 00000000000..ff8c96bec82 --- /dev/null +++ b/pkg/client/applyconfigurations/acme/v1/route53kubernetesauth.go @@ -0,0 +1,39 @@ +/* +Copyright 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. +*/ + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1 + +// Route53KubernetesAuthApplyConfiguration represents a declarative configuration of the Route53KubernetesAuth type for use +// with apply. +type Route53KubernetesAuthApplyConfiguration struct { + ServiceAccountRef *ServiceAccountRefApplyConfiguration `json:"serviceAccountRef,omitempty"` +} + +// Route53KubernetesAuthApplyConfiguration constructs a declarative configuration of the Route53KubernetesAuth type for use with +// apply. +func Route53KubernetesAuth() *Route53KubernetesAuthApplyConfiguration { + return &Route53KubernetesAuthApplyConfiguration{} +} + +// WithServiceAccountRef sets the ServiceAccountRef field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the ServiceAccountRef field is set to the value of the last call. +func (b *Route53KubernetesAuthApplyConfiguration) WithServiceAccountRef(value *ServiceAccountRefApplyConfiguration) *Route53KubernetesAuthApplyConfiguration { + b.ServiceAccountRef = value + return b +} diff --git a/pkg/client/applyconfigurations/acme/v1/serviceaccountref.go b/pkg/client/applyconfigurations/acme/v1/serviceaccountref.go new file mode 100644 index 00000000000..b1fd4e84cb5 --- /dev/null +++ b/pkg/client/applyconfigurations/acme/v1/serviceaccountref.go @@ -0,0 +1,50 @@ +/* +Copyright 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. +*/ + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1 + +// ServiceAccountRefApplyConfiguration represents a declarative configuration of the ServiceAccountRef type for use +// with apply. +type ServiceAccountRefApplyConfiguration struct { + Name *string `json:"name,omitempty"` + TokenAudiences []string `json:"audiences,omitempty"` +} + +// ServiceAccountRefApplyConfiguration constructs a declarative configuration of the ServiceAccountRef type for use with +// apply. +func ServiceAccountRef() *ServiceAccountRefApplyConfiguration { + return &ServiceAccountRefApplyConfiguration{} +} + +// WithName sets the Name field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Name field is set to the value of the last call. +func (b *ServiceAccountRefApplyConfiguration) WithName(value string) *ServiceAccountRefApplyConfiguration { + b.Name = &value + return b +} + +// WithTokenAudiences adds the given value to the TokenAudiences field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the TokenAudiences field. +func (b *ServiceAccountRefApplyConfiguration) WithTokenAudiences(values ...string) *ServiceAccountRefApplyConfiguration { + for i := range values { + b.TokenAudiences = append(b.TokenAudiences, values[i]) + } + return b +} diff --git a/pkg/client/applyconfigurations/certmanager/v1/caissuer.go b/pkg/client/applyconfigurations/certmanager/v1/caissuer.go new file mode 100644 index 00000000000..f8f1c8723d5 --- /dev/null +++ b/pkg/client/applyconfigurations/certmanager/v1/caissuer.go @@ -0,0 +1,72 @@ +/* +Copyright 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. +*/ + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1 + +// CAIssuerApplyConfiguration represents a declarative configuration of the CAIssuer type for use +// with apply. +type CAIssuerApplyConfiguration struct { + SecretName *string `json:"secretName,omitempty"` + CRLDistributionPoints []string `json:"crlDistributionPoints,omitempty"` + OCSPServers []string `json:"ocspServers,omitempty"` + IssuingCertificateURLs []string `json:"issuingCertificateURLs,omitempty"` +} + +// CAIssuerApplyConfiguration constructs a declarative configuration of the CAIssuer type for use with +// apply. +func CAIssuer() *CAIssuerApplyConfiguration { + return &CAIssuerApplyConfiguration{} +} + +// WithSecretName sets the SecretName field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the SecretName field is set to the value of the last call. +func (b *CAIssuerApplyConfiguration) WithSecretName(value string) *CAIssuerApplyConfiguration { + b.SecretName = &value + return b +} + +// WithCRLDistributionPoints adds the given value to the CRLDistributionPoints field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the CRLDistributionPoints field. +func (b *CAIssuerApplyConfiguration) WithCRLDistributionPoints(values ...string) *CAIssuerApplyConfiguration { + for i := range values { + b.CRLDistributionPoints = append(b.CRLDistributionPoints, values[i]) + } + return b +} + +// WithOCSPServers adds the given value to the OCSPServers field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the OCSPServers field. +func (b *CAIssuerApplyConfiguration) WithOCSPServers(values ...string) *CAIssuerApplyConfiguration { + for i := range values { + b.OCSPServers = append(b.OCSPServers, values[i]) + } + return b +} + +// WithIssuingCertificateURLs adds the given value to the IssuingCertificateURLs field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the IssuingCertificateURLs field. +func (b *CAIssuerApplyConfiguration) WithIssuingCertificateURLs(values ...string) *CAIssuerApplyConfiguration { + for i := range values { + b.IssuingCertificateURLs = append(b.IssuingCertificateURLs, values[i]) + } + return b +} diff --git a/pkg/client/applyconfigurations/certmanager/v1/certificate.go b/pkg/client/applyconfigurations/certmanager/v1/certificate.go new file mode 100644 index 00000000000..aae457eca31 --- /dev/null +++ b/pkg/client/applyconfigurations/certmanager/v1/certificate.go @@ -0,0 +1,281 @@ +/* +Copyright 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. +*/ + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1 + +import ( + certmanagerv1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" + internal "github.com/cert-manager/cert-manager/pkg/client/applyconfigurations/internal" + apismetav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + types "k8s.io/apimachinery/pkg/types" + managedfields "k8s.io/apimachinery/pkg/util/managedfields" + metav1 "k8s.io/client-go/applyconfigurations/meta/v1" +) + +// CertificateApplyConfiguration represents a declarative configuration of the Certificate type for use +// with apply. +type CertificateApplyConfiguration struct { + metav1.TypeMetaApplyConfiguration `json:",inline"` + *metav1.ObjectMetaApplyConfiguration `json:"metadata,omitempty"` + Spec *CertificateSpecApplyConfiguration `json:"spec,omitempty"` + Status *CertificateStatusApplyConfiguration `json:"status,omitempty"` +} + +// Certificate constructs a declarative configuration of the Certificate type for use with +// apply. +func Certificate(name, namespace string) *CertificateApplyConfiguration { + b := &CertificateApplyConfiguration{} + b.WithName(name) + b.WithNamespace(namespace) + b.WithKind("Certificate") + b.WithAPIVersion("cert-manager.io/v1") + return b +} + +// ExtractCertificate extracts the applied configuration owned by fieldManager from +// certificate. If no managedFields are found in certificate for fieldManager, a +// CertificateApplyConfiguration is returned with only the Name, Namespace (if applicable), +// APIVersion and Kind populated. It is possible that no managed fields were found for because other +// field managers have taken ownership of all the fields previously owned by fieldManager, or because +// the fieldManager never owned fields any fields. +// certificate must be a unmodified Certificate API object that was retrieved from the Kubernetes API. +// ExtractCertificate provides a way to perform a extract/modify-in-place/apply workflow. +// Note that an extracted apply configuration will contain fewer fields than what the fieldManager previously +// applied if another fieldManager has updated or force applied any of the previously applied fields. +// Experimental! +func ExtractCertificate(certificate *certmanagerv1.Certificate, fieldManager string) (*CertificateApplyConfiguration, error) { + return extractCertificate(certificate, fieldManager, "") +} + +// ExtractCertificateStatus is the same as ExtractCertificate except +// that it extracts the status subresource applied configuration. +// Experimental! +func ExtractCertificateStatus(certificate *certmanagerv1.Certificate, fieldManager string) (*CertificateApplyConfiguration, error) { + return extractCertificate(certificate, fieldManager, "status") +} + +func extractCertificate(certificate *certmanagerv1.Certificate, fieldManager string, subresource string) (*CertificateApplyConfiguration, error) { + b := &CertificateApplyConfiguration{} + err := managedfields.ExtractInto(certificate, internal.Parser().Type("com.github.cert-manager.cert-manager.pkg.apis.certmanager.v1.Certificate"), fieldManager, b, subresource) + if err != nil { + return nil, err + } + b.WithName(certificate.Name) + b.WithNamespace(certificate.Namespace) + + b.WithKind("Certificate") + b.WithAPIVersion("cert-manager.io/v1") + return b, nil +} +func (b CertificateApplyConfiguration) IsApplyConfiguration() {} + +// WithKind sets the Kind field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Kind field is set to the value of the last call. +func (b *CertificateApplyConfiguration) WithKind(value string) *CertificateApplyConfiguration { + b.TypeMetaApplyConfiguration.Kind = &value + return b +} + +// WithAPIVersion sets the APIVersion field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the APIVersion field is set to the value of the last call. +func (b *CertificateApplyConfiguration) WithAPIVersion(value string) *CertificateApplyConfiguration { + b.TypeMetaApplyConfiguration.APIVersion = &value + return b +} + +// WithName sets the Name field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Name field is set to the value of the last call. +func (b *CertificateApplyConfiguration) WithName(value string) *CertificateApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.Name = &value + return b +} + +// WithGenerateName sets the GenerateName field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the GenerateName field is set to the value of the last call. +func (b *CertificateApplyConfiguration) WithGenerateName(value string) *CertificateApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.GenerateName = &value + return b +} + +// WithNamespace sets the Namespace field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Namespace field is set to the value of the last call. +func (b *CertificateApplyConfiguration) WithNamespace(value string) *CertificateApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.Namespace = &value + return b +} + +// WithUID sets the UID field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the UID field is set to the value of the last call. +func (b *CertificateApplyConfiguration) WithUID(value types.UID) *CertificateApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.UID = &value + return b +} + +// WithResourceVersion sets the ResourceVersion field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the ResourceVersion field is set to the value of the last call. +func (b *CertificateApplyConfiguration) WithResourceVersion(value string) *CertificateApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.ResourceVersion = &value + return b +} + +// WithGeneration sets the Generation field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Generation field is set to the value of the last call. +func (b *CertificateApplyConfiguration) WithGeneration(value int64) *CertificateApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.Generation = &value + return b +} + +// WithCreationTimestamp sets the CreationTimestamp field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the CreationTimestamp field is set to the value of the last call. +func (b *CertificateApplyConfiguration) WithCreationTimestamp(value apismetav1.Time) *CertificateApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.CreationTimestamp = &value + return b +} + +// WithDeletionTimestamp sets the DeletionTimestamp field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the DeletionTimestamp field is set to the value of the last call. +func (b *CertificateApplyConfiguration) WithDeletionTimestamp(value apismetav1.Time) *CertificateApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.DeletionTimestamp = &value + return b +} + +// WithDeletionGracePeriodSeconds sets the DeletionGracePeriodSeconds field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the DeletionGracePeriodSeconds field is set to the value of the last call. +func (b *CertificateApplyConfiguration) WithDeletionGracePeriodSeconds(value int64) *CertificateApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.DeletionGracePeriodSeconds = &value + return b +} + +// WithLabels puts the entries into the Labels field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, the entries provided by each call will be put on the Labels field, +// overwriting an existing map entries in Labels field with the same key. +func (b *CertificateApplyConfiguration) WithLabels(entries map[string]string) *CertificateApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + if b.ObjectMetaApplyConfiguration.Labels == nil && len(entries) > 0 { + b.ObjectMetaApplyConfiguration.Labels = make(map[string]string, len(entries)) + } + for k, v := range entries { + b.ObjectMetaApplyConfiguration.Labels[k] = v + } + return b +} + +// WithAnnotations puts the entries into the Annotations field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, the entries provided by each call will be put on the Annotations field, +// overwriting an existing map entries in Annotations field with the same key. +func (b *CertificateApplyConfiguration) WithAnnotations(entries map[string]string) *CertificateApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + if b.ObjectMetaApplyConfiguration.Annotations == nil && len(entries) > 0 { + b.ObjectMetaApplyConfiguration.Annotations = make(map[string]string, len(entries)) + } + for k, v := range entries { + b.ObjectMetaApplyConfiguration.Annotations[k] = v + } + return b +} + +// WithOwnerReferences adds the given value to the OwnerReferences field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the OwnerReferences field. +func (b *CertificateApplyConfiguration) WithOwnerReferences(values ...*metav1.OwnerReferenceApplyConfiguration) *CertificateApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + for i := range values { + if values[i] == nil { + panic("nil value passed to WithOwnerReferences") + } + b.ObjectMetaApplyConfiguration.OwnerReferences = append(b.ObjectMetaApplyConfiguration.OwnerReferences, *values[i]) + } + return b +} + +// WithFinalizers adds the given value to the Finalizers field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the Finalizers field. +func (b *CertificateApplyConfiguration) WithFinalizers(values ...string) *CertificateApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + for i := range values { + b.ObjectMetaApplyConfiguration.Finalizers = append(b.ObjectMetaApplyConfiguration.Finalizers, values[i]) + } + return b +} + +func (b *CertificateApplyConfiguration) ensureObjectMetaApplyConfigurationExists() { + if b.ObjectMetaApplyConfiguration == nil { + b.ObjectMetaApplyConfiguration = &metav1.ObjectMetaApplyConfiguration{} + } +} + +// WithSpec sets the Spec field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Spec field is set to the value of the last call. +func (b *CertificateApplyConfiguration) WithSpec(value *CertificateSpecApplyConfiguration) *CertificateApplyConfiguration { + b.Spec = value + return b +} + +// WithStatus sets the Status field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Status field is set to the value of the last call. +func (b *CertificateApplyConfiguration) WithStatus(value *CertificateStatusApplyConfiguration) *CertificateApplyConfiguration { + b.Status = value + return b +} + +// GetKind retrieves the value of the Kind field in the declarative configuration. +func (b *CertificateApplyConfiguration) GetKind() *string { + return b.TypeMetaApplyConfiguration.Kind +} + +// GetAPIVersion retrieves the value of the APIVersion field in the declarative configuration. +func (b *CertificateApplyConfiguration) GetAPIVersion() *string { + return b.TypeMetaApplyConfiguration.APIVersion +} + +// GetName retrieves the value of the Name field in the declarative configuration. +func (b *CertificateApplyConfiguration) GetName() *string { + b.ensureObjectMetaApplyConfigurationExists() + return b.ObjectMetaApplyConfiguration.Name +} + +// GetNamespace retrieves the value of the Namespace field in the declarative configuration. +func (b *CertificateApplyConfiguration) GetNamespace() *string { + b.ensureObjectMetaApplyConfigurationExists() + return b.ObjectMetaApplyConfiguration.Namespace +} diff --git a/pkg/client/applyconfigurations/certmanager/v1/certificateadditionaloutputformat.go b/pkg/client/applyconfigurations/certmanager/v1/certificateadditionaloutputformat.go new file mode 100644 index 00000000000..e6890a8dc07 --- /dev/null +++ b/pkg/client/applyconfigurations/certmanager/v1/certificateadditionaloutputformat.go @@ -0,0 +1,43 @@ +/* +Copyright 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. +*/ + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1 + +import ( + certmanagerv1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" +) + +// CertificateAdditionalOutputFormatApplyConfiguration represents a declarative configuration of the CertificateAdditionalOutputFormat type for use +// with apply. +type CertificateAdditionalOutputFormatApplyConfiguration struct { + Type *certmanagerv1.CertificateOutputFormatType `json:"type,omitempty"` +} + +// CertificateAdditionalOutputFormatApplyConfiguration constructs a declarative configuration of the CertificateAdditionalOutputFormat type for use with +// apply. +func CertificateAdditionalOutputFormat() *CertificateAdditionalOutputFormatApplyConfiguration { + return &CertificateAdditionalOutputFormatApplyConfiguration{} +} + +// WithType sets the Type field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Type field is set to the value of the last call. +func (b *CertificateAdditionalOutputFormatApplyConfiguration) WithType(value certmanagerv1.CertificateOutputFormatType) *CertificateAdditionalOutputFormatApplyConfiguration { + b.Type = &value + return b +} diff --git a/pkg/client/applyconfigurations/certmanager/v1/certificatecondition.go b/pkg/client/applyconfigurations/certmanager/v1/certificatecondition.go new file mode 100644 index 00000000000..ceb1161a122 --- /dev/null +++ b/pkg/client/applyconfigurations/certmanager/v1/certificatecondition.go @@ -0,0 +1,90 @@ +/* +Copyright 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. +*/ + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1 + +import ( + certmanagerv1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" + metav1 "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" + apismetav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// CertificateConditionApplyConfiguration represents a declarative configuration of the CertificateCondition type for use +// with apply. +type CertificateConditionApplyConfiguration struct { + Type *certmanagerv1.CertificateConditionType `json:"type,omitempty"` + Status *metav1.ConditionStatus `json:"status,omitempty"` + LastTransitionTime *apismetav1.Time `json:"lastTransitionTime,omitempty"` + Reason *string `json:"reason,omitempty"` + Message *string `json:"message,omitempty"` + ObservedGeneration *int64 `json:"observedGeneration,omitempty"` +} + +// CertificateConditionApplyConfiguration constructs a declarative configuration of the CertificateCondition type for use with +// apply. +func CertificateCondition() *CertificateConditionApplyConfiguration { + return &CertificateConditionApplyConfiguration{} +} + +// WithType sets the Type field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Type field is set to the value of the last call. +func (b *CertificateConditionApplyConfiguration) WithType(value certmanagerv1.CertificateConditionType) *CertificateConditionApplyConfiguration { + b.Type = &value + return b +} + +// WithStatus sets the Status field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Status field is set to the value of the last call. +func (b *CertificateConditionApplyConfiguration) WithStatus(value metav1.ConditionStatus) *CertificateConditionApplyConfiguration { + b.Status = &value + return b +} + +// WithLastTransitionTime sets the LastTransitionTime field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the LastTransitionTime field is set to the value of the last call. +func (b *CertificateConditionApplyConfiguration) WithLastTransitionTime(value apismetav1.Time) *CertificateConditionApplyConfiguration { + b.LastTransitionTime = &value + return b +} + +// WithReason sets the Reason field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Reason field is set to the value of the last call. +func (b *CertificateConditionApplyConfiguration) WithReason(value string) *CertificateConditionApplyConfiguration { + b.Reason = &value + return b +} + +// WithMessage sets the Message field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Message field is set to the value of the last call. +func (b *CertificateConditionApplyConfiguration) WithMessage(value string) *CertificateConditionApplyConfiguration { + b.Message = &value + return b +} + +// WithObservedGeneration sets the ObservedGeneration field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the ObservedGeneration field is set to the value of the last call. +func (b *CertificateConditionApplyConfiguration) WithObservedGeneration(value int64) *CertificateConditionApplyConfiguration { + b.ObservedGeneration = &value + return b +} diff --git a/pkg/client/applyconfigurations/certmanager/v1/certificatekeystores.go b/pkg/client/applyconfigurations/certmanager/v1/certificatekeystores.go new file mode 100644 index 00000000000..5c9c6b3ffc5 --- /dev/null +++ b/pkg/client/applyconfigurations/certmanager/v1/certificatekeystores.go @@ -0,0 +1,48 @@ +/* +Copyright 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. +*/ + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1 + +// CertificateKeystoresApplyConfiguration represents a declarative configuration of the CertificateKeystores type for use +// with apply. +type CertificateKeystoresApplyConfiguration struct { + JKS *JKSKeystoreApplyConfiguration `json:"jks,omitempty"` + PKCS12 *PKCS12KeystoreApplyConfiguration `json:"pkcs12,omitempty"` +} + +// CertificateKeystoresApplyConfiguration constructs a declarative configuration of the CertificateKeystores type for use with +// apply. +func CertificateKeystores() *CertificateKeystoresApplyConfiguration { + return &CertificateKeystoresApplyConfiguration{} +} + +// WithJKS sets the JKS field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the JKS field is set to the value of the last call. +func (b *CertificateKeystoresApplyConfiguration) WithJKS(value *JKSKeystoreApplyConfiguration) *CertificateKeystoresApplyConfiguration { + b.JKS = value + return b +} + +// WithPKCS12 sets the PKCS12 field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the PKCS12 field is set to the value of the last call. +func (b *CertificateKeystoresApplyConfiguration) WithPKCS12(value *PKCS12KeystoreApplyConfiguration) *CertificateKeystoresApplyConfiguration { + b.PKCS12 = value + return b +} diff --git a/pkg/client/applyconfigurations/certmanager/v1/certificateprivatekey.go b/pkg/client/applyconfigurations/certmanager/v1/certificateprivatekey.go new file mode 100644 index 00000000000..ae94a2dc1c6 --- /dev/null +++ b/pkg/client/applyconfigurations/certmanager/v1/certificateprivatekey.go @@ -0,0 +1,70 @@ +/* +Copyright 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. +*/ + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1 + +import ( + certmanagerv1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" +) + +// CertificatePrivateKeyApplyConfiguration represents a declarative configuration of the CertificatePrivateKey type for use +// with apply. +type CertificatePrivateKeyApplyConfiguration struct { + RotationPolicy *certmanagerv1.PrivateKeyRotationPolicy `json:"rotationPolicy,omitempty"` + Encoding *certmanagerv1.PrivateKeyEncoding `json:"encoding,omitempty"` + Algorithm *certmanagerv1.PrivateKeyAlgorithm `json:"algorithm,omitempty"` + Size *int `json:"size,omitempty"` +} + +// CertificatePrivateKeyApplyConfiguration constructs a declarative configuration of the CertificatePrivateKey type for use with +// apply. +func CertificatePrivateKey() *CertificatePrivateKeyApplyConfiguration { + return &CertificatePrivateKeyApplyConfiguration{} +} + +// WithRotationPolicy sets the RotationPolicy field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the RotationPolicy field is set to the value of the last call. +func (b *CertificatePrivateKeyApplyConfiguration) WithRotationPolicy(value certmanagerv1.PrivateKeyRotationPolicy) *CertificatePrivateKeyApplyConfiguration { + b.RotationPolicy = &value + return b +} + +// WithEncoding sets the Encoding field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Encoding field is set to the value of the last call. +func (b *CertificatePrivateKeyApplyConfiguration) WithEncoding(value certmanagerv1.PrivateKeyEncoding) *CertificatePrivateKeyApplyConfiguration { + b.Encoding = &value + return b +} + +// WithAlgorithm sets the Algorithm field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Algorithm field is set to the value of the last call. +func (b *CertificatePrivateKeyApplyConfiguration) WithAlgorithm(value certmanagerv1.PrivateKeyAlgorithm) *CertificatePrivateKeyApplyConfiguration { + b.Algorithm = &value + return b +} + +// WithSize sets the Size field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Size field is set to the value of the last call. +func (b *CertificatePrivateKeyApplyConfiguration) WithSize(value int) *CertificatePrivateKeyApplyConfiguration { + b.Size = &value + return b +} diff --git a/pkg/client/applyconfigurations/certmanager/v1/certificaterequest.go b/pkg/client/applyconfigurations/certmanager/v1/certificaterequest.go new file mode 100644 index 00000000000..c5950884d22 --- /dev/null +++ b/pkg/client/applyconfigurations/certmanager/v1/certificaterequest.go @@ -0,0 +1,281 @@ +/* +Copyright 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. +*/ + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1 + +import ( + certmanagerv1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" + internal "github.com/cert-manager/cert-manager/pkg/client/applyconfigurations/internal" + apismetav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + types "k8s.io/apimachinery/pkg/types" + managedfields "k8s.io/apimachinery/pkg/util/managedfields" + metav1 "k8s.io/client-go/applyconfigurations/meta/v1" +) + +// CertificateRequestApplyConfiguration represents a declarative configuration of the CertificateRequest type for use +// with apply. +type CertificateRequestApplyConfiguration struct { + metav1.TypeMetaApplyConfiguration `json:",inline"` + *metav1.ObjectMetaApplyConfiguration `json:"metadata,omitempty"` + Spec *CertificateRequestSpecApplyConfiguration `json:"spec,omitempty"` + Status *CertificateRequestStatusApplyConfiguration `json:"status,omitempty"` +} + +// CertificateRequest constructs a declarative configuration of the CertificateRequest type for use with +// apply. +func CertificateRequest(name, namespace string) *CertificateRequestApplyConfiguration { + b := &CertificateRequestApplyConfiguration{} + b.WithName(name) + b.WithNamespace(namespace) + b.WithKind("CertificateRequest") + b.WithAPIVersion("cert-manager.io/v1") + return b +} + +// ExtractCertificateRequest extracts the applied configuration owned by fieldManager from +// certificateRequest. If no managedFields are found in certificateRequest for fieldManager, a +// CertificateRequestApplyConfiguration is returned with only the Name, Namespace (if applicable), +// APIVersion and Kind populated. It is possible that no managed fields were found for because other +// field managers have taken ownership of all the fields previously owned by fieldManager, or because +// the fieldManager never owned fields any fields. +// certificateRequest must be a unmodified CertificateRequest API object that was retrieved from the Kubernetes API. +// ExtractCertificateRequest provides a way to perform a extract/modify-in-place/apply workflow. +// Note that an extracted apply configuration will contain fewer fields than what the fieldManager previously +// applied if another fieldManager has updated or force applied any of the previously applied fields. +// Experimental! +func ExtractCertificateRequest(certificateRequest *certmanagerv1.CertificateRequest, fieldManager string) (*CertificateRequestApplyConfiguration, error) { + return extractCertificateRequest(certificateRequest, fieldManager, "") +} + +// ExtractCertificateRequestStatus is the same as ExtractCertificateRequest except +// that it extracts the status subresource applied configuration. +// Experimental! +func ExtractCertificateRequestStatus(certificateRequest *certmanagerv1.CertificateRequest, fieldManager string) (*CertificateRequestApplyConfiguration, error) { + return extractCertificateRequest(certificateRequest, fieldManager, "status") +} + +func extractCertificateRequest(certificateRequest *certmanagerv1.CertificateRequest, fieldManager string, subresource string) (*CertificateRequestApplyConfiguration, error) { + b := &CertificateRequestApplyConfiguration{} + err := managedfields.ExtractInto(certificateRequest, internal.Parser().Type("com.github.cert-manager.cert-manager.pkg.apis.certmanager.v1.CertificateRequest"), fieldManager, b, subresource) + if err != nil { + return nil, err + } + b.WithName(certificateRequest.Name) + b.WithNamespace(certificateRequest.Namespace) + + b.WithKind("CertificateRequest") + b.WithAPIVersion("cert-manager.io/v1") + return b, nil +} +func (b CertificateRequestApplyConfiguration) IsApplyConfiguration() {} + +// WithKind sets the Kind field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Kind field is set to the value of the last call. +func (b *CertificateRequestApplyConfiguration) WithKind(value string) *CertificateRequestApplyConfiguration { + b.TypeMetaApplyConfiguration.Kind = &value + return b +} + +// WithAPIVersion sets the APIVersion field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the APIVersion field is set to the value of the last call. +func (b *CertificateRequestApplyConfiguration) WithAPIVersion(value string) *CertificateRequestApplyConfiguration { + b.TypeMetaApplyConfiguration.APIVersion = &value + return b +} + +// WithName sets the Name field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Name field is set to the value of the last call. +func (b *CertificateRequestApplyConfiguration) WithName(value string) *CertificateRequestApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.Name = &value + return b +} + +// WithGenerateName sets the GenerateName field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the GenerateName field is set to the value of the last call. +func (b *CertificateRequestApplyConfiguration) WithGenerateName(value string) *CertificateRequestApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.GenerateName = &value + return b +} + +// WithNamespace sets the Namespace field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Namespace field is set to the value of the last call. +func (b *CertificateRequestApplyConfiguration) WithNamespace(value string) *CertificateRequestApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.Namespace = &value + return b +} + +// WithUID sets the UID field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the UID field is set to the value of the last call. +func (b *CertificateRequestApplyConfiguration) WithUID(value types.UID) *CertificateRequestApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.UID = &value + return b +} + +// WithResourceVersion sets the ResourceVersion field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the ResourceVersion field is set to the value of the last call. +func (b *CertificateRequestApplyConfiguration) WithResourceVersion(value string) *CertificateRequestApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.ResourceVersion = &value + return b +} + +// WithGeneration sets the Generation field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Generation field is set to the value of the last call. +func (b *CertificateRequestApplyConfiguration) WithGeneration(value int64) *CertificateRequestApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.Generation = &value + return b +} + +// WithCreationTimestamp sets the CreationTimestamp field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the CreationTimestamp field is set to the value of the last call. +func (b *CertificateRequestApplyConfiguration) WithCreationTimestamp(value apismetav1.Time) *CertificateRequestApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.CreationTimestamp = &value + return b +} + +// WithDeletionTimestamp sets the DeletionTimestamp field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the DeletionTimestamp field is set to the value of the last call. +func (b *CertificateRequestApplyConfiguration) WithDeletionTimestamp(value apismetav1.Time) *CertificateRequestApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.DeletionTimestamp = &value + return b +} + +// WithDeletionGracePeriodSeconds sets the DeletionGracePeriodSeconds field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the DeletionGracePeriodSeconds field is set to the value of the last call. +func (b *CertificateRequestApplyConfiguration) WithDeletionGracePeriodSeconds(value int64) *CertificateRequestApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.DeletionGracePeriodSeconds = &value + return b +} + +// WithLabels puts the entries into the Labels field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, the entries provided by each call will be put on the Labels field, +// overwriting an existing map entries in Labels field with the same key. +func (b *CertificateRequestApplyConfiguration) WithLabels(entries map[string]string) *CertificateRequestApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + if b.ObjectMetaApplyConfiguration.Labels == nil && len(entries) > 0 { + b.ObjectMetaApplyConfiguration.Labels = make(map[string]string, len(entries)) + } + for k, v := range entries { + b.ObjectMetaApplyConfiguration.Labels[k] = v + } + return b +} + +// WithAnnotations puts the entries into the Annotations field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, the entries provided by each call will be put on the Annotations field, +// overwriting an existing map entries in Annotations field with the same key. +func (b *CertificateRequestApplyConfiguration) WithAnnotations(entries map[string]string) *CertificateRequestApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + if b.ObjectMetaApplyConfiguration.Annotations == nil && len(entries) > 0 { + b.ObjectMetaApplyConfiguration.Annotations = make(map[string]string, len(entries)) + } + for k, v := range entries { + b.ObjectMetaApplyConfiguration.Annotations[k] = v + } + return b +} + +// WithOwnerReferences adds the given value to the OwnerReferences field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the OwnerReferences field. +func (b *CertificateRequestApplyConfiguration) WithOwnerReferences(values ...*metav1.OwnerReferenceApplyConfiguration) *CertificateRequestApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + for i := range values { + if values[i] == nil { + panic("nil value passed to WithOwnerReferences") + } + b.ObjectMetaApplyConfiguration.OwnerReferences = append(b.ObjectMetaApplyConfiguration.OwnerReferences, *values[i]) + } + return b +} + +// WithFinalizers adds the given value to the Finalizers field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the Finalizers field. +func (b *CertificateRequestApplyConfiguration) WithFinalizers(values ...string) *CertificateRequestApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + for i := range values { + b.ObjectMetaApplyConfiguration.Finalizers = append(b.ObjectMetaApplyConfiguration.Finalizers, values[i]) + } + return b +} + +func (b *CertificateRequestApplyConfiguration) ensureObjectMetaApplyConfigurationExists() { + if b.ObjectMetaApplyConfiguration == nil { + b.ObjectMetaApplyConfiguration = &metav1.ObjectMetaApplyConfiguration{} + } +} + +// WithSpec sets the Spec field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Spec field is set to the value of the last call. +func (b *CertificateRequestApplyConfiguration) WithSpec(value *CertificateRequestSpecApplyConfiguration) *CertificateRequestApplyConfiguration { + b.Spec = value + return b +} + +// WithStatus sets the Status field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Status field is set to the value of the last call. +func (b *CertificateRequestApplyConfiguration) WithStatus(value *CertificateRequestStatusApplyConfiguration) *CertificateRequestApplyConfiguration { + b.Status = value + return b +} + +// GetKind retrieves the value of the Kind field in the declarative configuration. +func (b *CertificateRequestApplyConfiguration) GetKind() *string { + return b.TypeMetaApplyConfiguration.Kind +} + +// GetAPIVersion retrieves the value of the APIVersion field in the declarative configuration. +func (b *CertificateRequestApplyConfiguration) GetAPIVersion() *string { + return b.TypeMetaApplyConfiguration.APIVersion +} + +// GetName retrieves the value of the Name field in the declarative configuration. +func (b *CertificateRequestApplyConfiguration) GetName() *string { + b.ensureObjectMetaApplyConfigurationExists() + return b.ObjectMetaApplyConfiguration.Name +} + +// GetNamespace retrieves the value of the Namespace field in the declarative configuration. +func (b *CertificateRequestApplyConfiguration) GetNamespace() *string { + b.ensureObjectMetaApplyConfigurationExists() + return b.ObjectMetaApplyConfiguration.Namespace +} diff --git a/pkg/client/applyconfigurations/certmanager/v1/certificaterequestcondition.go b/pkg/client/applyconfigurations/certmanager/v1/certificaterequestcondition.go new file mode 100644 index 00000000000..24ec85dbbb9 --- /dev/null +++ b/pkg/client/applyconfigurations/certmanager/v1/certificaterequestcondition.go @@ -0,0 +1,81 @@ +/* +Copyright 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. +*/ + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1 + +import ( + certmanagerv1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" + metav1 "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" + apismetav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// CertificateRequestConditionApplyConfiguration represents a declarative configuration of the CertificateRequestCondition type for use +// with apply. +type CertificateRequestConditionApplyConfiguration struct { + Type *certmanagerv1.CertificateRequestConditionType `json:"type,omitempty"` + Status *metav1.ConditionStatus `json:"status,omitempty"` + LastTransitionTime *apismetav1.Time `json:"lastTransitionTime,omitempty"` + Reason *string `json:"reason,omitempty"` + Message *string `json:"message,omitempty"` +} + +// CertificateRequestConditionApplyConfiguration constructs a declarative configuration of the CertificateRequestCondition type for use with +// apply. +func CertificateRequestCondition() *CertificateRequestConditionApplyConfiguration { + return &CertificateRequestConditionApplyConfiguration{} +} + +// WithType sets the Type field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Type field is set to the value of the last call. +func (b *CertificateRequestConditionApplyConfiguration) WithType(value certmanagerv1.CertificateRequestConditionType) *CertificateRequestConditionApplyConfiguration { + b.Type = &value + return b +} + +// WithStatus sets the Status field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Status field is set to the value of the last call. +func (b *CertificateRequestConditionApplyConfiguration) WithStatus(value metav1.ConditionStatus) *CertificateRequestConditionApplyConfiguration { + b.Status = &value + return b +} + +// WithLastTransitionTime sets the LastTransitionTime field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the LastTransitionTime field is set to the value of the last call. +func (b *CertificateRequestConditionApplyConfiguration) WithLastTransitionTime(value apismetav1.Time) *CertificateRequestConditionApplyConfiguration { + b.LastTransitionTime = &value + return b +} + +// WithReason sets the Reason field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Reason field is set to the value of the last call. +func (b *CertificateRequestConditionApplyConfiguration) WithReason(value string) *CertificateRequestConditionApplyConfiguration { + b.Reason = &value + return b +} + +// WithMessage sets the Message field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Message field is set to the value of the last call. +func (b *CertificateRequestConditionApplyConfiguration) WithMessage(value string) *CertificateRequestConditionApplyConfiguration { + b.Message = &value + return b +} diff --git a/pkg/client/applyconfigurations/certmanager/v1/certificaterequestspec.go b/pkg/client/applyconfigurations/certmanager/v1/certificaterequestspec.go new file mode 100644 index 00000000000..3ffc6f3fc54 --- /dev/null +++ b/pkg/client/applyconfigurations/certmanager/v1/certificaterequestspec.go @@ -0,0 +1,129 @@ +/* +Copyright 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. +*/ + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1 + +import ( + certmanagerv1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" + applyconfigurationsmetav1 "github.com/cert-manager/cert-manager/pkg/client/applyconfigurations/meta/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// CertificateRequestSpecApplyConfiguration represents a declarative configuration of the CertificateRequestSpec type for use +// with apply. +type CertificateRequestSpecApplyConfiguration struct { + Duration *metav1.Duration `json:"duration,omitempty"` + IssuerRef *applyconfigurationsmetav1.IssuerReferenceApplyConfiguration `json:"issuerRef,omitempty"` + Request []byte `json:"request,omitempty"` + IsCA *bool `json:"isCA,omitempty"` + Usages []certmanagerv1.KeyUsage `json:"usages,omitempty"` + Username *string `json:"username,omitempty"` + UID *string `json:"uid,omitempty"` + Groups []string `json:"groups,omitempty"` + Extra map[string][]string `json:"extra,omitempty"` +} + +// CertificateRequestSpecApplyConfiguration constructs a declarative configuration of the CertificateRequestSpec type for use with +// apply. +func CertificateRequestSpec() *CertificateRequestSpecApplyConfiguration { + return &CertificateRequestSpecApplyConfiguration{} +} + +// WithDuration sets the Duration field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Duration field is set to the value of the last call. +func (b *CertificateRequestSpecApplyConfiguration) WithDuration(value metav1.Duration) *CertificateRequestSpecApplyConfiguration { + b.Duration = &value + return b +} + +// WithIssuerRef sets the IssuerRef field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the IssuerRef field is set to the value of the last call. +func (b *CertificateRequestSpecApplyConfiguration) WithIssuerRef(value *applyconfigurationsmetav1.IssuerReferenceApplyConfiguration) *CertificateRequestSpecApplyConfiguration { + b.IssuerRef = value + return b +} + +// WithRequest adds the given value to the Request field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the Request field. +func (b *CertificateRequestSpecApplyConfiguration) WithRequest(values ...byte) *CertificateRequestSpecApplyConfiguration { + for i := range values { + b.Request = append(b.Request, values[i]) + } + return b +} + +// WithIsCA sets the IsCA field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the IsCA field is set to the value of the last call. +func (b *CertificateRequestSpecApplyConfiguration) WithIsCA(value bool) *CertificateRequestSpecApplyConfiguration { + b.IsCA = &value + return b +} + +// WithUsages adds the given value to the Usages field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the Usages field. +func (b *CertificateRequestSpecApplyConfiguration) WithUsages(values ...certmanagerv1.KeyUsage) *CertificateRequestSpecApplyConfiguration { + for i := range values { + b.Usages = append(b.Usages, values[i]) + } + return b +} + +// WithUsername sets the Username field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Username field is set to the value of the last call. +func (b *CertificateRequestSpecApplyConfiguration) WithUsername(value string) *CertificateRequestSpecApplyConfiguration { + b.Username = &value + return b +} + +// WithUID sets the UID field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the UID field is set to the value of the last call. +func (b *CertificateRequestSpecApplyConfiguration) WithUID(value string) *CertificateRequestSpecApplyConfiguration { + b.UID = &value + return b +} + +// WithGroups adds the given value to the Groups field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the Groups field. +func (b *CertificateRequestSpecApplyConfiguration) WithGroups(values ...string) *CertificateRequestSpecApplyConfiguration { + for i := range values { + b.Groups = append(b.Groups, values[i]) + } + return b +} + +// WithExtra puts the entries into the Extra field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, the entries provided by each call will be put on the Extra field, +// overwriting an existing map entries in Extra field with the same key. +func (b *CertificateRequestSpecApplyConfiguration) WithExtra(entries map[string][]string) *CertificateRequestSpecApplyConfiguration { + if b.Extra == nil && len(entries) > 0 { + b.Extra = make(map[string][]string, len(entries)) + } + for k, v := range entries { + b.Extra[k] = v + } + return b +} diff --git a/pkg/client/applyconfigurations/certmanager/v1/certificaterequeststatus.go b/pkg/client/applyconfigurations/certmanager/v1/certificaterequeststatus.go new file mode 100644 index 00000000000..47e2fa66626 --- /dev/null +++ b/pkg/client/applyconfigurations/certmanager/v1/certificaterequeststatus.go @@ -0,0 +1,79 @@ +/* +Copyright 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. +*/ + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// CertificateRequestStatusApplyConfiguration represents a declarative configuration of the CertificateRequestStatus type for use +// with apply. +type CertificateRequestStatusApplyConfiguration struct { + Conditions []CertificateRequestConditionApplyConfiguration `json:"conditions,omitempty"` + Certificate []byte `json:"certificate,omitempty"` + CA []byte `json:"ca,omitempty"` + FailureTime *metav1.Time `json:"failureTime,omitempty"` +} + +// CertificateRequestStatusApplyConfiguration constructs a declarative configuration of the CertificateRequestStatus type for use with +// apply. +func CertificateRequestStatus() *CertificateRequestStatusApplyConfiguration { + return &CertificateRequestStatusApplyConfiguration{} +} + +// WithConditions adds the given value to the Conditions field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the Conditions field. +func (b *CertificateRequestStatusApplyConfiguration) WithConditions(values ...*CertificateRequestConditionApplyConfiguration) *CertificateRequestStatusApplyConfiguration { + for i := range values { + if values[i] == nil { + panic("nil value passed to WithConditions") + } + b.Conditions = append(b.Conditions, *values[i]) + } + return b +} + +// WithCertificate adds the given value to the Certificate field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the Certificate field. +func (b *CertificateRequestStatusApplyConfiguration) WithCertificate(values ...byte) *CertificateRequestStatusApplyConfiguration { + for i := range values { + b.Certificate = append(b.Certificate, values[i]) + } + return b +} + +// WithCA adds the given value to the CA field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the CA field. +func (b *CertificateRequestStatusApplyConfiguration) WithCA(values ...byte) *CertificateRequestStatusApplyConfiguration { + for i := range values { + b.CA = append(b.CA, values[i]) + } + return b +} + +// WithFailureTime sets the FailureTime field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the FailureTime field is set to the value of the last call. +func (b *CertificateRequestStatusApplyConfiguration) WithFailureTime(value metav1.Time) *CertificateRequestStatusApplyConfiguration { + b.FailureTime = &value + return b +} diff --git a/pkg/client/applyconfigurations/certmanager/v1/certificatesecrettemplate.go b/pkg/client/applyconfigurations/certmanager/v1/certificatesecrettemplate.go new file mode 100644 index 00000000000..6ae4eee9186 --- /dev/null +++ b/pkg/client/applyconfigurations/certmanager/v1/certificatesecrettemplate.go @@ -0,0 +1,60 @@ +/* +Copyright 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. +*/ + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1 + +// CertificateSecretTemplateApplyConfiguration represents a declarative configuration of the CertificateSecretTemplate type for use +// with apply. +type CertificateSecretTemplateApplyConfiguration struct { + Annotations map[string]string `json:"annotations,omitempty"` + Labels map[string]string `json:"labels,omitempty"` +} + +// CertificateSecretTemplateApplyConfiguration constructs a declarative configuration of the CertificateSecretTemplate type for use with +// apply. +func CertificateSecretTemplate() *CertificateSecretTemplateApplyConfiguration { + return &CertificateSecretTemplateApplyConfiguration{} +} + +// WithAnnotations puts the entries into the Annotations field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, the entries provided by each call will be put on the Annotations field, +// overwriting an existing map entries in Annotations field with the same key. +func (b *CertificateSecretTemplateApplyConfiguration) WithAnnotations(entries map[string]string) *CertificateSecretTemplateApplyConfiguration { + if b.Annotations == nil && len(entries) > 0 { + b.Annotations = make(map[string]string, len(entries)) + } + for k, v := range entries { + b.Annotations[k] = v + } + return b +} + +// WithLabels puts the entries into the Labels field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, the entries provided by each call will be put on the Labels field, +// overwriting an existing map entries in Labels field with the same key. +func (b *CertificateSecretTemplateApplyConfiguration) WithLabels(entries map[string]string) *CertificateSecretTemplateApplyConfiguration { + if b.Labels == nil && len(entries) > 0 { + b.Labels = make(map[string]string, len(entries)) + } + for k, v := range entries { + b.Labels[k] = v + } + return b +} diff --git a/pkg/client/applyconfigurations/certmanager/v1/certificatespec.go b/pkg/client/applyconfigurations/certmanager/v1/certificatespec.go new file mode 100644 index 00000000000..093660a0a74 --- /dev/null +++ b/pkg/client/applyconfigurations/certmanager/v1/certificatespec.go @@ -0,0 +1,263 @@ +/* +Copyright 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. +*/ + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1 + +import ( + certmanagerv1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" + applyconfigurationsmetav1 "github.com/cert-manager/cert-manager/pkg/client/applyconfigurations/meta/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// CertificateSpecApplyConfiguration represents a declarative configuration of the CertificateSpec type for use +// with apply. +type CertificateSpecApplyConfiguration struct { + Subject *X509SubjectApplyConfiguration `json:"subject,omitempty"` + LiteralSubject *string `json:"literalSubject,omitempty"` + CommonName *string `json:"commonName,omitempty"` + Duration *metav1.Duration `json:"duration,omitempty"` + RenewBefore *metav1.Duration `json:"renewBefore,omitempty"` + RenewBeforePercentage *int32 `json:"renewBeforePercentage,omitempty"` + DNSNames []string `json:"dnsNames,omitempty"` + IPAddresses []string `json:"ipAddresses,omitempty"` + URIs []string `json:"uris,omitempty"` + OtherNames []OtherNameApplyConfiguration `json:"otherNames,omitempty"` + EmailAddresses []string `json:"emailAddresses,omitempty"` + SecretName *string `json:"secretName,omitempty"` + SecretTemplate *CertificateSecretTemplateApplyConfiguration `json:"secretTemplate,omitempty"` + Keystores *CertificateKeystoresApplyConfiguration `json:"keystores,omitempty"` + IssuerRef *applyconfigurationsmetav1.IssuerReferenceApplyConfiguration `json:"issuerRef,omitempty"` + IsCA *bool `json:"isCA,omitempty"` + Usages []certmanagerv1.KeyUsage `json:"usages,omitempty"` + PrivateKey *CertificatePrivateKeyApplyConfiguration `json:"privateKey,omitempty"` + SignatureAlgorithm *certmanagerv1.SignatureAlgorithm `json:"signatureAlgorithm,omitempty"` + EncodeUsagesInRequest *bool `json:"encodeUsagesInRequest,omitempty"` + RevisionHistoryLimit *int32 `json:"revisionHistoryLimit,omitempty"` + AdditionalOutputFormats []CertificateAdditionalOutputFormatApplyConfiguration `json:"additionalOutputFormats,omitempty"` + NameConstraints *NameConstraintsApplyConfiguration `json:"nameConstraints,omitempty"` +} + +// CertificateSpecApplyConfiguration constructs a declarative configuration of the CertificateSpec type for use with +// apply. +func CertificateSpec() *CertificateSpecApplyConfiguration { + return &CertificateSpecApplyConfiguration{} +} + +// WithSubject sets the Subject field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Subject field is set to the value of the last call. +func (b *CertificateSpecApplyConfiguration) WithSubject(value *X509SubjectApplyConfiguration) *CertificateSpecApplyConfiguration { + b.Subject = value + return b +} + +// WithLiteralSubject sets the LiteralSubject field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the LiteralSubject field is set to the value of the last call. +func (b *CertificateSpecApplyConfiguration) WithLiteralSubject(value string) *CertificateSpecApplyConfiguration { + b.LiteralSubject = &value + return b +} + +// WithCommonName sets the CommonName field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the CommonName field is set to the value of the last call. +func (b *CertificateSpecApplyConfiguration) WithCommonName(value string) *CertificateSpecApplyConfiguration { + b.CommonName = &value + return b +} + +// WithDuration sets the Duration field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Duration field is set to the value of the last call. +func (b *CertificateSpecApplyConfiguration) WithDuration(value metav1.Duration) *CertificateSpecApplyConfiguration { + b.Duration = &value + return b +} + +// WithRenewBefore sets the RenewBefore field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the RenewBefore field is set to the value of the last call. +func (b *CertificateSpecApplyConfiguration) WithRenewBefore(value metav1.Duration) *CertificateSpecApplyConfiguration { + b.RenewBefore = &value + return b +} + +// WithRenewBeforePercentage sets the RenewBeforePercentage field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the RenewBeforePercentage field is set to the value of the last call. +func (b *CertificateSpecApplyConfiguration) WithRenewBeforePercentage(value int32) *CertificateSpecApplyConfiguration { + b.RenewBeforePercentage = &value + return b +} + +// WithDNSNames adds the given value to the DNSNames field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the DNSNames field. +func (b *CertificateSpecApplyConfiguration) WithDNSNames(values ...string) *CertificateSpecApplyConfiguration { + for i := range values { + b.DNSNames = append(b.DNSNames, values[i]) + } + return b +} + +// WithIPAddresses adds the given value to the IPAddresses field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the IPAddresses field. +func (b *CertificateSpecApplyConfiguration) WithIPAddresses(values ...string) *CertificateSpecApplyConfiguration { + for i := range values { + b.IPAddresses = append(b.IPAddresses, values[i]) + } + return b +} + +// WithURIs adds the given value to the URIs field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the URIs field. +func (b *CertificateSpecApplyConfiguration) WithURIs(values ...string) *CertificateSpecApplyConfiguration { + for i := range values { + b.URIs = append(b.URIs, values[i]) + } + return b +} + +// WithOtherNames adds the given value to the OtherNames field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the OtherNames field. +func (b *CertificateSpecApplyConfiguration) WithOtherNames(values ...*OtherNameApplyConfiguration) *CertificateSpecApplyConfiguration { + for i := range values { + if values[i] == nil { + panic("nil value passed to WithOtherNames") + } + b.OtherNames = append(b.OtherNames, *values[i]) + } + return b +} + +// WithEmailAddresses adds the given value to the EmailAddresses field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the EmailAddresses field. +func (b *CertificateSpecApplyConfiguration) WithEmailAddresses(values ...string) *CertificateSpecApplyConfiguration { + for i := range values { + b.EmailAddresses = append(b.EmailAddresses, values[i]) + } + return b +} + +// WithSecretName sets the SecretName field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the SecretName field is set to the value of the last call. +func (b *CertificateSpecApplyConfiguration) WithSecretName(value string) *CertificateSpecApplyConfiguration { + b.SecretName = &value + return b +} + +// WithSecretTemplate sets the SecretTemplate field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the SecretTemplate field is set to the value of the last call. +func (b *CertificateSpecApplyConfiguration) WithSecretTemplate(value *CertificateSecretTemplateApplyConfiguration) *CertificateSpecApplyConfiguration { + b.SecretTemplate = value + return b +} + +// WithKeystores sets the Keystores field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Keystores field is set to the value of the last call. +func (b *CertificateSpecApplyConfiguration) WithKeystores(value *CertificateKeystoresApplyConfiguration) *CertificateSpecApplyConfiguration { + b.Keystores = value + return b +} + +// WithIssuerRef sets the IssuerRef field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the IssuerRef field is set to the value of the last call. +func (b *CertificateSpecApplyConfiguration) WithIssuerRef(value *applyconfigurationsmetav1.IssuerReferenceApplyConfiguration) *CertificateSpecApplyConfiguration { + b.IssuerRef = value + return b +} + +// WithIsCA sets the IsCA field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the IsCA field is set to the value of the last call. +func (b *CertificateSpecApplyConfiguration) WithIsCA(value bool) *CertificateSpecApplyConfiguration { + b.IsCA = &value + return b +} + +// WithUsages adds the given value to the Usages field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the Usages field. +func (b *CertificateSpecApplyConfiguration) WithUsages(values ...certmanagerv1.KeyUsage) *CertificateSpecApplyConfiguration { + for i := range values { + b.Usages = append(b.Usages, values[i]) + } + return b +} + +// WithPrivateKey sets the PrivateKey field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the PrivateKey field is set to the value of the last call. +func (b *CertificateSpecApplyConfiguration) WithPrivateKey(value *CertificatePrivateKeyApplyConfiguration) *CertificateSpecApplyConfiguration { + b.PrivateKey = value + return b +} + +// WithSignatureAlgorithm sets the SignatureAlgorithm field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the SignatureAlgorithm field is set to the value of the last call. +func (b *CertificateSpecApplyConfiguration) WithSignatureAlgorithm(value certmanagerv1.SignatureAlgorithm) *CertificateSpecApplyConfiguration { + b.SignatureAlgorithm = &value + return b +} + +// WithEncodeUsagesInRequest sets the EncodeUsagesInRequest field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the EncodeUsagesInRequest field is set to the value of the last call. +func (b *CertificateSpecApplyConfiguration) WithEncodeUsagesInRequest(value bool) *CertificateSpecApplyConfiguration { + b.EncodeUsagesInRequest = &value + return b +} + +// WithRevisionHistoryLimit sets the RevisionHistoryLimit field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the RevisionHistoryLimit field is set to the value of the last call. +func (b *CertificateSpecApplyConfiguration) WithRevisionHistoryLimit(value int32) *CertificateSpecApplyConfiguration { + b.RevisionHistoryLimit = &value + return b +} + +// WithAdditionalOutputFormats adds the given value to the AdditionalOutputFormats field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the AdditionalOutputFormats field. +func (b *CertificateSpecApplyConfiguration) WithAdditionalOutputFormats(values ...*CertificateAdditionalOutputFormatApplyConfiguration) *CertificateSpecApplyConfiguration { + for i := range values { + if values[i] == nil { + panic("nil value passed to WithAdditionalOutputFormats") + } + b.AdditionalOutputFormats = append(b.AdditionalOutputFormats, *values[i]) + } + return b +} + +// WithNameConstraints sets the NameConstraints field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the NameConstraints field is set to the value of the last call. +func (b *CertificateSpecApplyConfiguration) WithNameConstraints(value *NameConstraintsApplyConfiguration) *CertificateSpecApplyConfiguration { + b.NameConstraints = value + return b +} diff --git a/pkg/client/applyconfigurations/certmanager/v1/certificatestatus.go b/pkg/client/applyconfigurations/certmanager/v1/certificatestatus.go new file mode 100644 index 00000000000..06919f17d11 --- /dev/null +++ b/pkg/client/applyconfigurations/certmanager/v1/certificatestatus.go @@ -0,0 +1,111 @@ +/* +Copyright 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. +*/ + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// CertificateStatusApplyConfiguration represents a declarative configuration of the CertificateStatus type for use +// with apply. +type CertificateStatusApplyConfiguration struct { + Conditions []CertificateConditionApplyConfiguration `json:"conditions,omitempty"` + LastFailureTime *metav1.Time `json:"lastFailureTime,omitempty"` + NotBefore *metav1.Time `json:"notBefore,omitempty"` + NotAfter *metav1.Time `json:"notAfter,omitempty"` + RenewalTime *metav1.Time `json:"renewalTime,omitempty"` + Revision *int `json:"revision,omitempty"` + NextPrivateKeySecretName *string `json:"nextPrivateKeySecretName,omitempty"` + FailedIssuanceAttempts *int `json:"failedIssuanceAttempts,omitempty"` +} + +// CertificateStatusApplyConfiguration constructs a declarative configuration of the CertificateStatus type for use with +// apply. +func CertificateStatus() *CertificateStatusApplyConfiguration { + return &CertificateStatusApplyConfiguration{} +} + +// WithConditions adds the given value to the Conditions field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the Conditions field. +func (b *CertificateStatusApplyConfiguration) WithConditions(values ...*CertificateConditionApplyConfiguration) *CertificateStatusApplyConfiguration { + for i := range values { + if values[i] == nil { + panic("nil value passed to WithConditions") + } + b.Conditions = append(b.Conditions, *values[i]) + } + return b +} + +// WithLastFailureTime sets the LastFailureTime field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the LastFailureTime field is set to the value of the last call. +func (b *CertificateStatusApplyConfiguration) WithLastFailureTime(value metav1.Time) *CertificateStatusApplyConfiguration { + b.LastFailureTime = &value + return b +} + +// WithNotBefore sets the NotBefore field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the NotBefore field is set to the value of the last call. +func (b *CertificateStatusApplyConfiguration) WithNotBefore(value metav1.Time) *CertificateStatusApplyConfiguration { + b.NotBefore = &value + return b +} + +// WithNotAfter sets the NotAfter field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the NotAfter field is set to the value of the last call. +func (b *CertificateStatusApplyConfiguration) WithNotAfter(value metav1.Time) *CertificateStatusApplyConfiguration { + b.NotAfter = &value + return b +} + +// WithRenewalTime sets the RenewalTime field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the RenewalTime field is set to the value of the last call. +func (b *CertificateStatusApplyConfiguration) WithRenewalTime(value metav1.Time) *CertificateStatusApplyConfiguration { + b.RenewalTime = &value + return b +} + +// WithRevision sets the Revision field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Revision field is set to the value of the last call. +func (b *CertificateStatusApplyConfiguration) WithRevision(value int) *CertificateStatusApplyConfiguration { + b.Revision = &value + return b +} + +// WithNextPrivateKeySecretName sets the NextPrivateKeySecretName field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the NextPrivateKeySecretName field is set to the value of the last call. +func (b *CertificateStatusApplyConfiguration) WithNextPrivateKeySecretName(value string) *CertificateStatusApplyConfiguration { + b.NextPrivateKeySecretName = &value + return b +} + +// WithFailedIssuanceAttempts sets the FailedIssuanceAttempts field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the FailedIssuanceAttempts field is set to the value of the last call. +func (b *CertificateStatusApplyConfiguration) WithFailedIssuanceAttempts(value int) *CertificateStatusApplyConfiguration { + b.FailedIssuanceAttempts = &value + return b +} diff --git a/pkg/client/applyconfigurations/certmanager/v1/clusterissuer.go b/pkg/client/applyconfigurations/certmanager/v1/clusterissuer.go new file mode 100644 index 00000000000..04f1d7fbc14 --- /dev/null +++ b/pkg/client/applyconfigurations/certmanager/v1/clusterissuer.go @@ -0,0 +1,279 @@ +/* +Copyright 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. +*/ + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1 + +import ( + certmanagerv1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" + internal "github.com/cert-manager/cert-manager/pkg/client/applyconfigurations/internal" + apismetav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + types "k8s.io/apimachinery/pkg/types" + managedfields "k8s.io/apimachinery/pkg/util/managedfields" + metav1 "k8s.io/client-go/applyconfigurations/meta/v1" +) + +// ClusterIssuerApplyConfiguration represents a declarative configuration of the ClusterIssuer type for use +// with apply. +type ClusterIssuerApplyConfiguration struct { + metav1.TypeMetaApplyConfiguration `json:",inline"` + *metav1.ObjectMetaApplyConfiguration `json:"metadata,omitempty"` + Spec *IssuerSpecApplyConfiguration `json:"spec,omitempty"` + Status *IssuerStatusApplyConfiguration `json:"status,omitempty"` +} + +// ClusterIssuer constructs a declarative configuration of the ClusterIssuer type for use with +// apply. +func ClusterIssuer(name string) *ClusterIssuerApplyConfiguration { + b := &ClusterIssuerApplyConfiguration{} + b.WithName(name) + b.WithKind("ClusterIssuer") + b.WithAPIVersion("cert-manager.io/v1") + return b +} + +// ExtractClusterIssuer extracts the applied configuration owned by fieldManager from +// clusterIssuer. If no managedFields are found in clusterIssuer for fieldManager, a +// ClusterIssuerApplyConfiguration is returned with only the Name, Namespace (if applicable), +// APIVersion and Kind populated. It is possible that no managed fields were found for because other +// field managers have taken ownership of all the fields previously owned by fieldManager, or because +// the fieldManager never owned fields any fields. +// clusterIssuer must be a unmodified ClusterIssuer API object that was retrieved from the Kubernetes API. +// ExtractClusterIssuer provides a way to perform a extract/modify-in-place/apply workflow. +// Note that an extracted apply configuration will contain fewer fields than what the fieldManager previously +// applied if another fieldManager has updated or force applied any of the previously applied fields. +// Experimental! +func ExtractClusterIssuer(clusterIssuer *certmanagerv1.ClusterIssuer, fieldManager string) (*ClusterIssuerApplyConfiguration, error) { + return extractClusterIssuer(clusterIssuer, fieldManager, "") +} + +// ExtractClusterIssuerStatus is the same as ExtractClusterIssuer except +// that it extracts the status subresource applied configuration. +// Experimental! +func ExtractClusterIssuerStatus(clusterIssuer *certmanagerv1.ClusterIssuer, fieldManager string) (*ClusterIssuerApplyConfiguration, error) { + return extractClusterIssuer(clusterIssuer, fieldManager, "status") +} + +func extractClusterIssuer(clusterIssuer *certmanagerv1.ClusterIssuer, fieldManager string, subresource string) (*ClusterIssuerApplyConfiguration, error) { + b := &ClusterIssuerApplyConfiguration{} + err := managedfields.ExtractInto(clusterIssuer, internal.Parser().Type("com.github.cert-manager.cert-manager.pkg.apis.certmanager.v1.ClusterIssuer"), fieldManager, b, subresource) + if err != nil { + return nil, err + } + b.WithName(clusterIssuer.Name) + + b.WithKind("ClusterIssuer") + b.WithAPIVersion("cert-manager.io/v1") + return b, nil +} +func (b ClusterIssuerApplyConfiguration) IsApplyConfiguration() {} + +// WithKind sets the Kind field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Kind field is set to the value of the last call. +func (b *ClusterIssuerApplyConfiguration) WithKind(value string) *ClusterIssuerApplyConfiguration { + b.TypeMetaApplyConfiguration.Kind = &value + return b +} + +// WithAPIVersion sets the APIVersion field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the APIVersion field is set to the value of the last call. +func (b *ClusterIssuerApplyConfiguration) WithAPIVersion(value string) *ClusterIssuerApplyConfiguration { + b.TypeMetaApplyConfiguration.APIVersion = &value + return b +} + +// WithName sets the Name field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Name field is set to the value of the last call. +func (b *ClusterIssuerApplyConfiguration) WithName(value string) *ClusterIssuerApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.Name = &value + return b +} + +// WithGenerateName sets the GenerateName field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the GenerateName field is set to the value of the last call. +func (b *ClusterIssuerApplyConfiguration) WithGenerateName(value string) *ClusterIssuerApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.GenerateName = &value + return b +} + +// WithNamespace sets the Namespace field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Namespace field is set to the value of the last call. +func (b *ClusterIssuerApplyConfiguration) WithNamespace(value string) *ClusterIssuerApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.Namespace = &value + return b +} + +// WithUID sets the UID field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the UID field is set to the value of the last call. +func (b *ClusterIssuerApplyConfiguration) WithUID(value types.UID) *ClusterIssuerApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.UID = &value + return b +} + +// WithResourceVersion sets the ResourceVersion field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the ResourceVersion field is set to the value of the last call. +func (b *ClusterIssuerApplyConfiguration) WithResourceVersion(value string) *ClusterIssuerApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.ResourceVersion = &value + return b +} + +// WithGeneration sets the Generation field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Generation field is set to the value of the last call. +func (b *ClusterIssuerApplyConfiguration) WithGeneration(value int64) *ClusterIssuerApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.Generation = &value + return b +} + +// WithCreationTimestamp sets the CreationTimestamp field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the CreationTimestamp field is set to the value of the last call. +func (b *ClusterIssuerApplyConfiguration) WithCreationTimestamp(value apismetav1.Time) *ClusterIssuerApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.CreationTimestamp = &value + return b +} + +// WithDeletionTimestamp sets the DeletionTimestamp field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the DeletionTimestamp field is set to the value of the last call. +func (b *ClusterIssuerApplyConfiguration) WithDeletionTimestamp(value apismetav1.Time) *ClusterIssuerApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.DeletionTimestamp = &value + return b +} + +// WithDeletionGracePeriodSeconds sets the DeletionGracePeriodSeconds field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the DeletionGracePeriodSeconds field is set to the value of the last call. +func (b *ClusterIssuerApplyConfiguration) WithDeletionGracePeriodSeconds(value int64) *ClusterIssuerApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.DeletionGracePeriodSeconds = &value + return b +} + +// WithLabels puts the entries into the Labels field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, the entries provided by each call will be put on the Labels field, +// overwriting an existing map entries in Labels field with the same key. +func (b *ClusterIssuerApplyConfiguration) WithLabels(entries map[string]string) *ClusterIssuerApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + if b.ObjectMetaApplyConfiguration.Labels == nil && len(entries) > 0 { + b.ObjectMetaApplyConfiguration.Labels = make(map[string]string, len(entries)) + } + for k, v := range entries { + b.ObjectMetaApplyConfiguration.Labels[k] = v + } + return b +} + +// WithAnnotations puts the entries into the Annotations field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, the entries provided by each call will be put on the Annotations field, +// overwriting an existing map entries in Annotations field with the same key. +func (b *ClusterIssuerApplyConfiguration) WithAnnotations(entries map[string]string) *ClusterIssuerApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + if b.ObjectMetaApplyConfiguration.Annotations == nil && len(entries) > 0 { + b.ObjectMetaApplyConfiguration.Annotations = make(map[string]string, len(entries)) + } + for k, v := range entries { + b.ObjectMetaApplyConfiguration.Annotations[k] = v + } + return b +} + +// WithOwnerReferences adds the given value to the OwnerReferences field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the OwnerReferences field. +func (b *ClusterIssuerApplyConfiguration) WithOwnerReferences(values ...*metav1.OwnerReferenceApplyConfiguration) *ClusterIssuerApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + for i := range values { + if values[i] == nil { + panic("nil value passed to WithOwnerReferences") + } + b.ObjectMetaApplyConfiguration.OwnerReferences = append(b.ObjectMetaApplyConfiguration.OwnerReferences, *values[i]) + } + return b +} + +// WithFinalizers adds the given value to the Finalizers field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the Finalizers field. +func (b *ClusterIssuerApplyConfiguration) WithFinalizers(values ...string) *ClusterIssuerApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + for i := range values { + b.ObjectMetaApplyConfiguration.Finalizers = append(b.ObjectMetaApplyConfiguration.Finalizers, values[i]) + } + return b +} + +func (b *ClusterIssuerApplyConfiguration) ensureObjectMetaApplyConfigurationExists() { + if b.ObjectMetaApplyConfiguration == nil { + b.ObjectMetaApplyConfiguration = &metav1.ObjectMetaApplyConfiguration{} + } +} + +// WithSpec sets the Spec field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Spec field is set to the value of the last call. +func (b *ClusterIssuerApplyConfiguration) WithSpec(value *IssuerSpecApplyConfiguration) *ClusterIssuerApplyConfiguration { + b.Spec = value + return b +} + +// WithStatus sets the Status field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Status field is set to the value of the last call. +func (b *ClusterIssuerApplyConfiguration) WithStatus(value *IssuerStatusApplyConfiguration) *ClusterIssuerApplyConfiguration { + b.Status = value + return b +} + +// GetKind retrieves the value of the Kind field in the declarative configuration. +func (b *ClusterIssuerApplyConfiguration) GetKind() *string { + return b.TypeMetaApplyConfiguration.Kind +} + +// GetAPIVersion retrieves the value of the APIVersion field in the declarative configuration. +func (b *ClusterIssuerApplyConfiguration) GetAPIVersion() *string { + return b.TypeMetaApplyConfiguration.APIVersion +} + +// GetName retrieves the value of the Name field in the declarative configuration. +func (b *ClusterIssuerApplyConfiguration) GetName() *string { + b.ensureObjectMetaApplyConfigurationExists() + return b.ObjectMetaApplyConfiguration.Name +} + +// GetNamespace retrieves the value of the Namespace field in the declarative configuration. +func (b *ClusterIssuerApplyConfiguration) GetNamespace() *string { + b.ensureObjectMetaApplyConfigurationExists() + return b.ObjectMetaApplyConfiguration.Namespace +} diff --git a/pkg/client/applyconfigurations/certmanager/v1/issuer.go b/pkg/client/applyconfigurations/certmanager/v1/issuer.go new file mode 100644 index 00000000000..17a926e8e55 --- /dev/null +++ b/pkg/client/applyconfigurations/certmanager/v1/issuer.go @@ -0,0 +1,281 @@ +/* +Copyright 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. +*/ + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1 + +import ( + certmanagerv1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" + internal "github.com/cert-manager/cert-manager/pkg/client/applyconfigurations/internal" + apismetav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + types "k8s.io/apimachinery/pkg/types" + managedfields "k8s.io/apimachinery/pkg/util/managedfields" + metav1 "k8s.io/client-go/applyconfigurations/meta/v1" +) + +// IssuerApplyConfiguration represents a declarative configuration of the Issuer type for use +// with apply. +type IssuerApplyConfiguration struct { + metav1.TypeMetaApplyConfiguration `json:",inline"` + *metav1.ObjectMetaApplyConfiguration `json:"metadata,omitempty"` + Spec *IssuerSpecApplyConfiguration `json:"spec,omitempty"` + Status *IssuerStatusApplyConfiguration `json:"status,omitempty"` +} + +// Issuer constructs a declarative configuration of the Issuer type for use with +// apply. +func Issuer(name, namespace string) *IssuerApplyConfiguration { + b := &IssuerApplyConfiguration{} + b.WithName(name) + b.WithNamespace(namespace) + b.WithKind("Issuer") + b.WithAPIVersion("cert-manager.io/v1") + return b +} + +// ExtractIssuer extracts the applied configuration owned by fieldManager from +// issuer. If no managedFields are found in issuer for fieldManager, a +// IssuerApplyConfiguration is returned with only the Name, Namespace (if applicable), +// APIVersion and Kind populated. It is possible that no managed fields were found for because other +// field managers have taken ownership of all the fields previously owned by fieldManager, or because +// the fieldManager never owned fields any fields. +// issuer must be a unmodified Issuer API object that was retrieved from the Kubernetes API. +// ExtractIssuer provides a way to perform a extract/modify-in-place/apply workflow. +// Note that an extracted apply configuration will contain fewer fields than what the fieldManager previously +// applied if another fieldManager has updated or force applied any of the previously applied fields. +// Experimental! +func ExtractIssuer(issuer *certmanagerv1.Issuer, fieldManager string) (*IssuerApplyConfiguration, error) { + return extractIssuer(issuer, fieldManager, "") +} + +// ExtractIssuerStatus is the same as ExtractIssuer except +// that it extracts the status subresource applied configuration. +// Experimental! +func ExtractIssuerStatus(issuer *certmanagerv1.Issuer, fieldManager string) (*IssuerApplyConfiguration, error) { + return extractIssuer(issuer, fieldManager, "status") +} + +func extractIssuer(issuer *certmanagerv1.Issuer, fieldManager string, subresource string) (*IssuerApplyConfiguration, error) { + b := &IssuerApplyConfiguration{} + err := managedfields.ExtractInto(issuer, internal.Parser().Type("com.github.cert-manager.cert-manager.pkg.apis.certmanager.v1.Issuer"), fieldManager, b, subresource) + if err != nil { + return nil, err + } + b.WithName(issuer.Name) + b.WithNamespace(issuer.Namespace) + + b.WithKind("Issuer") + b.WithAPIVersion("cert-manager.io/v1") + return b, nil +} +func (b IssuerApplyConfiguration) IsApplyConfiguration() {} + +// WithKind sets the Kind field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Kind field is set to the value of the last call. +func (b *IssuerApplyConfiguration) WithKind(value string) *IssuerApplyConfiguration { + b.TypeMetaApplyConfiguration.Kind = &value + return b +} + +// WithAPIVersion sets the APIVersion field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the APIVersion field is set to the value of the last call. +func (b *IssuerApplyConfiguration) WithAPIVersion(value string) *IssuerApplyConfiguration { + b.TypeMetaApplyConfiguration.APIVersion = &value + return b +} + +// WithName sets the Name field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Name field is set to the value of the last call. +func (b *IssuerApplyConfiguration) WithName(value string) *IssuerApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.Name = &value + return b +} + +// WithGenerateName sets the GenerateName field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the GenerateName field is set to the value of the last call. +func (b *IssuerApplyConfiguration) WithGenerateName(value string) *IssuerApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.GenerateName = &value + return b +} + +// WithNamespace sets the Namespace field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Namespace field is set to the value of the last call. +func (b *IssuerApplyConfiguration) WithNamespace(value string) *IssuerApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.Namespace = &value + return b +} + +// WithUID sets the UID field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the UID field is set to the value of the last call. +func (b *IssuerApplyConfiguration) WithUID(value types.UID) *IssuerApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.UID = &value + return b +} + +// WithResourceVersion sets the ResourceVersion field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the ResourceVersion field is set to the value of the last call. +func (b *IssuerApplyConfiguration) WithResourceVersion(value string) *IssuerApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.ResourceVersion = &value + return b +} + +// WithGeneration sets the Generation field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Generation field is set to the value of the last call. +func (b *IssuerApplyConfiguration) WithGeneration(value int64) *IssuerApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.Generation = &value + return b +} + +// WithCreationTimestamp sets the CreationTimestamp field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the CreationTimestamp field is set to the value of the last call. +func (b *IssuerApplyConfiguration) WithCreationTimestamp(value apismetav1.Time) *IssuerApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.CreationTimestamp = &value + return b +} + +// WithDeletionTimestamp sets the DeletionTimestamp field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the DeletionTimestamp field is set to the value of the last call. +func (b *IssuerApplyConfiguration) WithDeletionTimestamp(value apismetav1.Time) *IssuerApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.DeletionTimestamp = &value + return b +} + +// WithDeletionGracePeriodSeconds sets the DeletionGracePeriodSeconds field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the DeletionGracePeriodSeconds field is set to the value of the last call. +func (b *IssuerApplyConfiguration) WithDeletionGracePeriodSeconds(value int64) *IssuerApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + b.ObjectMetaApplyConfiguration.DeletionGracePeriodSeconds = &value + return b +} + +// WithLabels puts the entries into the Labels field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, the entries provided by each call will be put on the Labels field, +// overwriting an existing map entries in Labels field with the same key. +func (b *IssuerApplyConfiguration) WithLabels(entries map[string]string) *IssuerApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + if b.ObjectMetaApplyConfiguration.Labels == nil && len(entries) > 0 { + b.ObjectMetaApplyConfiguration.Labels = make(map[string]string, len(entries)) + } + for k, v := range entries { + b.ObjectMetaApplyConfiguration.Labels[k] = v + } + return b +} + +// WithAnnotations puts the entries into the Annotations field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, the entries provided by each call will be put on the Annotations field, +// overwriting an existing map entries in Annotations field with the same key. +func (b *IssuerApplyConfiguration) WithAnnotations(entries map[string]string) *IssuerApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + if b.ObjectMetaApplyConfiguration.Annotations == nil && len(entries) > 0 { + b.ObjectMetaApplyConfiguration.Annotations = make(map[string]string, len(entries)) + } + for k, v := range entries { + b.ObjectMetaApplyConfiguration.Annotations[k] = v + } + return b +} + +// WithOwnerReferences adds the given value to the OwnerReferences field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the OwnerReferences field. +func (b *IssuerApplyConfiguration) WithOwnerReferences(values ...*metav1.OwnerReferenceApplyConfiguration) *IssuerApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + for i := range values { + if values[i] == nil { + panic("nil value passed to WithOwnerReferences") + } + b.ObjectMetaApplyConfiguration.OwnerReferences = append(b.ObjectMetaApplyConfiguration.OwnerReferences, *values[i]) + } + return b +} + +// WithFinalizers adds the given value to the Finalizers field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the Finalizers field. +func (b *IssuerApplyConfiguration) WithFinalizers(values ...string) *IssuerApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + for i := range values { + b.ObjectMetaApplyConfiguration.Finalizers = append(b.ObjectMetaApplyConfiguration.Finalizers, values[i]) + } + return b +} + +func (b *IssuerApplyConfiguration) ensureObjectMetaApplyConfigurationExists() { + if b.ObjectMetaApplyConfiguration == nil { + b.ObjectMetaApplyConfiguration = &metav1.ObjectMetaApplyConfiguration{} + } +} + +// WithSpec sets the Spec field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Spec field is set to the value of the last call. +func (b *IssuerApplyConfiguration) WithSpec(value *IssuerSpecApplyConfiguration) *IssuerApplyConfiguration { + b.Spec = value + return b +} + +// WithStatus sets the Status field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Status field is set to the value of the last call. +func (b *IssuerApplyConfiguration) WithStatus(value *IssuerStatusApplyConfiguration) *IssuerApplyConfiguration { + b.Status = value + return b +} + +// GetKind retrieves the value of the Kind field in the declarative configuration. +func (b *IssuerApplyConfiguration) GetKind() *string { + return b.TypeMetaApplyConfiguration.Kind +} + +// GetAPIVersion retrieves the value of the APIVersion field in the declarative configuration. +func (b *IssuerApplyConfiguration) GetAPIVersion() *string { + return b.TypeMetaApplyConfiguration.APIVersion +} + +// GetName retrieves the value of the Name field in the declarative configuration. +func (b *IssuerApplyConfiguration) GetName() *string { + b.ensureObjectMetaApplyConfigurationExists() + return b.ObjectMetaApplyConfiguration.Name +} + +// GetNamespace retrieves the value of the Namespace field in the declarative configuration. +func (b *IssuerApplyConfiguration) GetNamespace() *string { + b.ensureObjectMetaApplyConfigurationExists() + return b.ObjectMetaApplyConfiguration.Namespace +} diff --git a/pkg/client/applyconfigurations/certmanager/v1/issuercondition.go b/pkg/client/applyconfigurations/certmanager/v1/issuercondition.go new file mode 100644 index 00000000000..15791ab3f8a --- /dev/null +++ b/pkg/client/applyconfigurations/certmanager/v1/issuercondition.go @@ -0,0 +1,90 @@ +/* +Copyright 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. +*/ + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1 + +import ( + certmanagerv1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" + metav1 "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" + apismetav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// IssuerConditionApplyConfiguration represents a declarative configuration of the IssuerCondition type for use +// with apply. +type IssuerConditionApplyConfiguration struct { + Type *certmanagerv1.IssuerConditionType `json:"type,omitempty"` + Status *metav1.ConditionStatus `json:"status,omitempty"` + LastTransitionTime *apismetav1.Time `json:"lastTransitionTime,omitempty"` + Reason *string `json:"reason,omitempty"` + Message *string `json:"message,omitempty"` + ObservedGeneration *int64 `json:"observedGeneration,omitempty"` +} + +// IssuerConditionApplyConfiguration constructs a declarative configuration of the IssuerCondition type for use with +// apply. +func IssuerCondition() *IssuerConditionApplyConfiguration { + return &IssuerConditionApplyConfiguration{} +} + +// WithType sets the Type field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Type field is set to the value of the last call. +func (b *IssuerConditionApplyConfiguration) WithType(value certmanagerv1.IssuerConditionType) *IssuerConditionApplyConfiguration { + b.Type = &value + return b +} + +// WithStatus sets the Status field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Status field is set to the value of the last call. +func (b *IssuerConditionApplyConfiguration) WithStatus(value metav1.ConditionStatus) *IssuerConditionApplyConfiguration { + b.Status = &value + return b +} + +// WithLastTransitionTime sets the LastTransitionTime field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the LastTransitionTime field is set to the value of the last call. +func (b *IssuerConditionApplyConfiguration) WithLastTransitionTime(value apismetav1.Time) *IssuerConditionApplyConfiguration { + b.LastTransitionTime = &value + return b +} + +// WithReason sets the Reason field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Reason field is set to the value of the last call. +func (b *IssuerConditionApplyConfiguration) WithReason(value string) *IssuerConditionApplyConfiguration { + b.Reason = &value + return b +} + +// WithMessage sets the Message field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Message field is set to the value of the last call. +func (b *IssuerConditionApplyConfiguration) WithMessage(value string) *IssuerConditionApplyConfiguration { + b.Message = &value + return b +} + +// WithObservedGeneration sets the ObservedGeneration field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the ObservedGeneration field is set to the value of the last call. +func (b *IssuerConditionApplyConfiguration) WithObservedGeneration(value int64) *IssuerConditionApplyConfiguration { + b.ObservedGeneration = &value + return b +} diff --git a/pkg/client/applyconfigurations/certmanager/v1/issuerconfig.go b/pkg/client/applyconfigurations/certmanager/v1/issuerconfig.go new file mode 100644 index 00000000000..85ddf8f3fd2 --- /dev/null +++ b/pkg/client/applyconfigurations/certmanager/v1/issuerconfig.go @@ -0,0 +1,79 @@ +/* +Copyright 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. +*/ + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1 + +import ( + acmev1 "github.com/cert-manager/cert-manager/pkg/client/applyconfigurations/acme/v1" +) + +// IssuerConfigApplyConfiguration represents a declarative configuration of the IssuerConfig type for use +// with apply. +type IssuerConfigApplyConfiguration struct { + ACME *acmev1.ACMEIssuerApplyConfiguration `json:"acme,omitempty"` + CA *CAIssuerApplyConfiguration `json:"ca,omitempty"` + Vault *VaultIssuerApplyConfiguration `json:"vault,omitempty"` + SelfSigned *SelfSignedIssuerApplyConfiguration `json:"selfSigned,omitempty"` + Venafi *VenafiIssuerApplyConfiguration `json:"venafi,omitempty"` +} + +// IssuerConfigApplyConfiguration constructs a declarative configuration of the IssuerConfig type for use with +// apply. +func IssuerConfig() *IssuerConfigApplyConfiguration { + return &IssuerConfigApplyConfiguration{} +} + +// WithACME sets the ACME field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the ACME field is set to the value of the last call. +func (b *IssuerConfigApplyConfiguration) WithACME(value *acmev1.ACMEIssuerApplyConfiguration) *IssuerConfigApplyConfiguration { + b.ACME = value + return b +} + +// WithCA sets the CA field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the CA field is set to the value of the last call. +func (b *IssuerConfigApplyConfiguration) WithCA(value *CAIssuerApplyConfiguration) *IssuerConfigApplyConfiguration { + b.CA = value + return b +} + +// WithVault sets the Vault field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Vault field is set to the value of the last call. +func (b *IssuerConfigApplyConfiguration) WithVault(value *VaultIssuerApplyConfiguration) *IssuerConfigApplyConfiguration { + b.Vault = value + return b +} + +// WithSelfSigned sets the SelfSigned field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the SelfSigned field is set to the value of the last call. +func (b *IssuerConfigApplyConfiguration) WithSelfSigned(value *SelfSignedIssuerApplyConfiguration) *IssuerConfigApplyConfiguration { + b.SelfSigned = value + return b +} + +// WithVenafi sets the Venafi field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Venafi field is set to the value of the last call. +func (b *IssuerConfigApplyConfiguration) WithVenafi(value *VenafiIssuerApplyConfiguration) *IssuerConfigApplyConfiguration { + b.Venafi = value + return b +} diff --git a/pkg/client/applyconfigurations/certmanager/v1/issuerspec.go b/pkg/client/applyconfigurations/certmanager/v1/issuerspec.go new file mode 100644 index 00000000000..d9d1393be31 --- /dev/null +++ b/pkg/client/applyconfigurations/certmanager/v1/issuerspec.go @@ -0,0 +1,75 @@ +/* +Copyright 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. +*/ + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1 + +import ( + acmev1 "github.com/cert-manager/cert-manager/pkg/client/applyconfigurations/acme/v1" +) + +// IssuerSpecApplyConfiguration represents a declarative configuration of the IssuerSpec type for use +// with apply. +type IssuerSpecApplyConfiguration struct { + IssuerConfigApplyConfiguration `json:",inline"` +} + +// IssuerSpecApplyConfiguration constructs a declarative configuration of the IssuerSpec type for use with +// apply. +func IssuerSpec() *IssuerSpecApplyConfiguration { + return &IssuerSpecApplyConfiguration{} +} + +// WithACME sets the ACME field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the ACME field is set to the value of the last call. +func (b *IssuerSpecApplyConfiguration) WithACME(value *acmev1.ACMEIssuerApplyConfiguration) *IssuerSpecApplyConfiguration { + b.IssuerConfigApplyConfiguration.ACME = value + return b +} + +// WithCA sets the CA field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the CA field is set to the value of the last call. +func (b *IssuerSpecApplyConfiguration) WithCA(value *CAIssuerApplyConfiguration) *IssuerSpecApplyConfiguration { + b.IssuerConfigApplyConfiguration.CA = value + return b +} + +// WithVault sets the Vault field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Vault field is set to the value of the last call. +func (b *IssuerSpecApplyConfiguration) WithVault(value *VaultIssuerApplyConfiguration) *IssuerSpecApplyConfiguration { + b.IssuerConfigApplyConfiguration.Vault = value + return b +} + +// WithSelfSigned sets the SelfSigned field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the SelfSigned field is set to the value of the last call. +func (b *IssuerSpecApplyConfiguration) WithSelfSigned(value *SelfSignedIssuerApplyConfiguration) *IssuerSpecApplyConfiguration { + b.IssuerConfigApplyConfiguration.SelfSigned = value + return b +} + +// WithVenafi sets the Venafi field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Venafi field is set to the value of the last call. +func (b *IssuerSpecApplyConfiguration) WithVenafi(value *VenafiIssuerApplyConfiguration) *IssuerSpecApplyConfiguration { + b.IssuerConfigApplyConfiguration.Venafi = value + return b +} diff --git a/pkg/client/applyconfigurations/certmanager/v1/issuerstatus.go b/pkg/client/applyconfigurations/certmanager/v1/issuerstatus.go new file mode 100644 index 00000000000..77a03fe8f3a --- /dev/null +++ b/pkg/client/applyconfigurations/certmanager/v1/issuerstatus.go @@ -0,0 +1,57 @@ +/* +Copyright 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. +*/ + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1 + +import ( + acmev1 "github.com/cert-manager/cert-manager/pkg/client/applyconfigurations/acme/v1" +) + +// IssuerStatusApplyConfiguration represents a declarative configuration of the IssuerStatus type for use +// with apply. +type IssuerStatusApplyConfiguration struct { + Conditions []IssuerConditionApplyConfiguration `json:"conditions,omitempty"` + ACME *acmev1.ACMEIssuerStatusApplyConfiguration `json:"acme,omitempty"` +} + +// IssuerStatusApplyConfiguration constructs a declarative configuration of the IssuerStatus type for use with +// apply. +func IssuerStatus() *IssuerStatusApplyConfiguration { + return &IssuerStatusApplyConfiguration{} +} + +// WithConditions adds the given value to the Conditions field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the Conditions field. +func (b *IssuerStatusApplyConfiguration) WithConditions(values ...*IssuerConditionApplyConfiguration) *IssuerStatusApplyConfiguration { + for i := range values { + if values[i] == nil { + panic("nil value passed to WithConditions") + } + b.Conditions = append(b.Conditions, *values[i]) + } + return b +} + +// WithACME sets the ACME field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the ACME field is set to the value of the last call. +func (b *IssuerStatusApplyConfiguration) WithACME(value *acmev1.ACMEIssuerStatusApplyConfiguration) *IssuerStatusApplyConfiguration { + b.ACME = value + return b +} diff --git a/pkg/client/applyconfigurations/certmanager/v1/jkskeystore.go b/pkg/client/applyconfigurations/certmanager/v1/jkskeystore.go new file mode 100644 index 00000000000..3c3e646327d --- /dev/null +++ b/pkg/client/applyconfigurations/certmanager/v1/jkskeystore.go @@ -0,0 +1,70 @@ +/* +Copyright 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. +*/ + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1 + +import ( + metav1 "github.com/cert-manager/cert-manager/pkg/client/applyconfigurations/meta/v1" +) + +// JKSKeystoreApplyConfiguration represents a declarative configuration of the JKSKeystore type for use +// with apply. +type JKSKeystoreApplyConfiguration struct { + Create *bool `json:"create,omitempty"` + Alias *string `json:"alias,omitempty"` + PasswordSecretRef *metav1.SecretKeySelectorApplyConfiguration `json:"passwordSecretRef,omitempty"` + Password *string `json:"password,omitempty"` +} + +// JKSKeystoreApplyConfiguration constructs a declarative configuration of the JKSKeystore type for use with +// apply. +func JKSKeystore() *JKSKeystoreApplyConfiguration { + return &JKSKeystoreApplyConfiguration{} +} + +// WithCreate sets the Create field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Create field is set to the value of the last call. +func (b *JKSKeystoreApplyConfiguration) WithCreate(value bool) *JKSKeystoreApplyConfiguration { + b.Create = &value + return b +} + +// WithAlias sets the Alias field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Alias field is set to the value of the last call. +func (b *JKSKeystoreApplyConfiguration) WithAlias(value string) *JKSKeystoreApplyConfiguration { + b.Alias = &value + return b +} + +// WithPasswordSecretRef sets the PasswordSecretRef field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the PasswordSecretRef field is set to the value of the last call. +func (b *JKSKeystoreApplyConfiguration) WithPasswordSecretRef(value *metav1.SecretKeySelectorApplyConfiguration) *JKSKeystoreApplyConfiguration { + b.PasswordSecretRef = value + return b +} + +// WithPassword sets the Password field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Password field is set to the value of the last call. +func (b *JKSKeystoreApplyConfiguration) WithPassword(value string) *JKSKeystoreApplyConfiguration { + b.Password = &value + return b +} diff --git a/pkg/client/applyconfigurations/certmanager/v1/nameconstraintitem.go b/pkg/client/applyconfigurations/certmanager/v1/nameconstraintitem.go new file mode 100644 index 00000000000..f25f6beb071 --- /dev/null +++ b/pkg/client/applyconfigurations/certmanager/v1/nameconstraintitem.go @@ -0,0 +1,74 @@ +/* +Copyright 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. +*/ + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1 + +// NameConstraintItemApplyConfiguration represents a declarative configuration of the NameConstraintItem type for use +// with apply. +type NameConstraintItemApplyConfiguration struct { + DNSDomains []string `json:"dnsDomains,omitempty"` + IPRanges []string `json:"ipRanges,omitempty"` + EmailAddresses []string `json:"emailAddresses,omitempty"` + URIDomains []string `json:"uriDomains,omitempty"` +} + +// NameConstraintItemApplyConfiguration constructs a declarative configuration of the NameConstraintItem type for use with +// apply. +func NameConstraintItem() *NameConstraintItemApplyConfiguration { + return &NameConstraintItemApplyConfiguration{} +} + +// WithDNSDomains adds the given value to the DNSDomains field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the DNSDomains field. +func (b *NameConstraintItemApplyConfiguration) WithDNSDomains(values ...string) *NameConstraintItemApplyConfiguration { + for i := range values { + b.DNSDomains = append(b.DNSDomains, values[i]) + } + return b +} + +// WithIPRanges adds the given value to the IPRanges field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the IPRanges field. +func (b *NameConstraintItemApplyConfiguration) WithIPRanges(values ...string) *NameConstraintItemApplyConfiguration { + for i := range values { + b.IPRanges = append(b.IPRanges, values[i]) + } + return b +} + +// WithEmailAddresses adds the given value to the EmailAddresses field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the EmailAddresses field. +func (b *NameConstraintItemApplyConfiguration) WithEmailAddresses(values ...string) *NameConstraintItemApplyConfiguration { + for i := range values { + b.EmailAddresses = append(b.EmailAddresses, values[i]) + } + return b +} + +// WithURIDomains adds the given value to the URIDomains field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the URIDomains field. +func (b *NameConstraintItemApplyConfiguration) WithURIDomains(values ...string) *NameConstraintItemApplyConfiguration { + for i := range values { + b.URIDomains = append(b.URIDomains, values[i]) + } + return b +} diff --git a/pkg/client/applyconfigurations/certmanager/v1/nameconstraints.go b/pkg/client/applyconfigurations/certmanager/v1/nameconstraints.go new file mode 100644 index 00000000000..224676dcceb --- /dev/null +++ b/pkg/client/applyconfigurations/certmanager/v1/nameconstraints.go @@ -0,0 +1,57 @@ +/* +Copyright 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. +*/ + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1 + +// NameConstraintsApplyConfiguration represents a declarative configuration of the NameConstraints type for use +// with apply. +type NameConstraintsApplyConfiguration struct { + Critical *bool `json:"critical,omitempty"` + Permitted *NameConstraintItemApplyConfiguration `json:"permitted,omitempty"` + Excluded *NameConstraintItemApplyConfiguration `json:"excluded,omitempty"` +} + +// NameConstraintsApplyConfiguration constructs a declarative configuration of the NameConstraints type for use with +// apply. +func NameConstraints() *NameConstraintsApplyConfiguration { + return &NameConstraintsApplyConfiguration{} +} + +// WithCritical sets the Critical field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Critical field is set to the value of the last call. +func (b *NameConstraintsApplyConfiguration) WithCritical(value bool) *NameConstraintsApplyConfiguration { + b.Critical = &value + return b +} + +// WithPermitted sets the Permitted field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Permitted field is set to the value of the last call. +func (b *NameConstraintsApplyConfiguration) WithPermitted(value *NameConstraintItemApplyConfiguration) *NameConstraintsApplyConfiguration { + b.Permitted = value + return b +} + +// WithExcluded sets the Excluded field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Excluded field is set to the value of the last call. +func (b *NameConstraintsApplyConfiguration) WithExcluded(value *NameConstraintItemApplyConfiguration) *NameConstraintsApplyConfiguration { + b.Excluded = value + return b +} diff --git a/pkg/client/applyconfigurations/certmanager/v1/othername.go b/pkg/client/applyconfigurations/certmanager/v1/othername.go new file mode 100644 index 00000000000..144422df052 --- /dev/null +++ b/pkg/client/applyconfigurations/certmanager/v1/othername.go @@ -0,0 +1,48 @@ +/* +Copyright 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. +*/ + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1 + +// OtherNameApplyConfiguration represents a declarative configuration of the OtherName type for use +// with apply. +type OtherNameApplyConfiguration struct { + OID *string `json:"oid,omitempty"` + UTF8Value *string `json:"utf8Value,omitempty"` +} + +// OtherNameApplyConfiguration constructs a declarative configuration of the OtherName type for use with +// apply. +func OtherName() *OtherNameApplyConfiguration { + return &OtherNameApplyConfiguration{} +} + +// WithOID sets the OID field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the OID field is set to the value of the last call. +func (b *OtherNameApplyConfiguration) WithOID(value string) *OtherNameApplyConfiguration { + b.OID = &value + return b +} + +// WithUTF8Value sets the UTF8Value field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the UTF8Value field is set to the value of the last call. +func (b *OtherNameApplyConfiguration) WithUTF8Value(value string) *OtherNameApplyConfiguration { + b.UTF8Value = &value + return b +} diff --git a/pkg/client/applyconfigurations/certmanager/v1/pkcs12keystore.go b/pkg/client/applyconfigurations/certmanager/v1/pkcs12keystore.go new file mode 100644 index 00000000000..385986bcb09 --- /dev/null +++ b/pkg/client/applyconfigurations/certmanager/v1/pkcs12keystore.go @@ -0,0 +1,71 @@ +/* +Copyright 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. +*/ + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1 + +import ( + certmanagerv1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" + metav1 "github.com/cert-manager/cert-manager/pkg/client/applyconfigurations/meta/v1" +) + +// PKCS12KeystoreApplyConfiguration represents a declarative configuration of the PKCS12Keystore type for use +// with apply. +type PKCS12KeystoreApplyConfiguration struct { + Create *bool `json:"create,omitempty"` + Profile *certmanagerv1.PKCS12Profile `json:"profile,omitempty"` + PasswordSecretRef *metav1.SecretKeySelectorApplyConfiguration `json:"passwordSecretRef,omitempty"` + Password *string `json:"password,omitempty"` +} + +// PKCS12KeystoreApplyConfiguration constructs a declarative configuration of the PKCS12Keystore type for use with +// apply. +func PKCS12Keystore() *PKCS12KeystoreApplyConfiguration { + return &PKCS12KeystoreApplyConfiguration{} +} + +// WithCreate sets the Create field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Create field is set to the value of the last call. +func (b *PKCS12KeystoreApplyConfiguration) WithCreate(value bool) *PKCS12KeystoreApplyConfiguration { + b.Create = &value + return b +} + +// WithProfile sets the Profile field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Profile field is set to the value of the last call. +func (b *PKCS12KeystoreApplyConfiguration) WithProfile(value certmanagerv1.PKCS12Profile) *PKCS12KeystoreApplyConfiguration { + b.Profile = &value + return b +} + +// WithPasswordSecretRef sets the PasswordSecretRef field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the PasswordSecretRef field is set to the value of the last call. +func (b *PKCS12KeystoreApplyConfiguration) WithPasswordSecretRef(value *metav1.SecretKeySelectorApplyConfiguration) *PKCS12KeystoreApplyConfiguration { + b.PasswordSecretRef = value + return b +} + +// WithPassword sets the Password field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Password field is set to the value of the last call. +func (b *PKCS12KeystoreApplyConfiguration) WithPassword(value string) *PKCS12KeystoreApplyConfiguration { + b.Password = &value + return b +} diff --git a/pkg/client/applyconfigurations/certmanager/v1/selfsignedissuer.go b/pkg/client/applyconfigurations/certmanager/v1/selfsignedissuer.go new file mode 100644 index 00000000000..936cb6c03db --- /dev/null +++ b/pkg/client/applyconfigurations/certmanager/v1/selfsignedissuer.go @@ -0,0 +1,41 @@ +/* +Copyright 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. +*/ + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1 + +// SelfSignedIssuerApplyConfiguration represents a declarative configuration of the SelfSignedIssuer type for use +// with apply. +type SelfSignedIssuerApplyConfiguration struct { + CRLDistributionPoints []string `json:"crlDistributionPoints,omitempty"` +} + +// SelfSignedIssuerApplyConfiguration constructs a declarative configuration of the SelfSignedIssuer type for use with +// apply. +func SelfSignedIssuer() *SelfSignedIssuerApplyConfiguration { + return &SelfSignedIssuerApplyConfiguration{} +} + +// WithCRLDistributionPoints adds the given value to the CRLDistributionPoints field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the CRLDistributionPoints field. +func (b *SelfSignedIssuerApplyConfiguration) WithCRLDistributionPoints(values ...string) *SelfSignedIssuerApplyConfiguration { + for i := range values { + b.CRLDistributionPoints = append(b.CRLDistributionPoints, values[i]) + } + return b +} diff --git a/pkg/client/applyconfigurations/certmanager/v1/serviceaccountref.go b/pkg/client/applyconfigurations/certmanager/v1/serviceaccountref.go new file mode 100644 index 00000000000..b1fd4e84cb5 --- /dev/null +++ b/pkg/client/applyconfigurations/certmanager/v1/serviceaccountref.go @@ -0,0 +1,50 @@ +/* +Copyright 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. +*/ + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1 + +// ServiceAccountRefApplyConfiguration represents a declarative configuration of the ServiceAccountRef type for use +// with apply. +type ServiceAccountRefApplyConfiguration struct { + Name *string `json:"name,omitempty"` + TokenAudiences []string `json:"audiences,omitempty"` +} + +// ServiceAccountRefApplyConfiguration constructs a declarative configuration of the ServiceAccountRef type for use with +// apply. +func ServiceAccountRef() *ServiceAccountRefApplyConfiguration { + return &ServiceAccountRefApplyConfiguration{} +} + +// WithName sets the Name field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Name field is set to the value of the last call. +func (b *ServiceAccountRefApplyConfiguration) WithName(value string) *ServiceAccountRefApplyConfiguration { + b.Name = &value + return b +} + +// WithTokenAudiences adds the given value to the TokenAudiences field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the TokenAudiences field. +func (b *ServiceAccountRefApplyConfiguration) WithTokenAudiences(values ...string) *ServiceAccountRefApplyConfiguration { + for i := range values { + b.TokenAudiences = append(b.TokenAudiences, values[i]) + } + return b +} diff --git a/pkg/client/applyconfigurations/certmanager/v1/vaultapprole.go b/pkg/client/applyconfigurations/certmanager/v1/vaultapprole.go new file mode 100644 index 00000000000..de1242cd35c --- /dev/null +++ b/pkg/client/applyconfigurations/certmanager/v1/vaultapprole.go @@ -0,0 +1,61 @@ +/* +Copyright 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. +*/ + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1 + +import ( + metav1 "github.com/cert-manager/cert-manager/pkg/client/applyconfigurations/meta/v1" +) + +// VaultAppRoleApplyConfiguration represents a declarative configuration of the VaultAppRole type for use +// with apply. +type VaultAppRoleApplyConfiguration struct { + Path *string `json:"path,omitempty"` + RoleId *string `json:"roleId,omitempty"` + SecretRef *metav1.SecretKeySelectorApplyConfiguration `json:"secretRef,omitempty"` +} + +// VaultAppRoleApplyConfiguration constructs a declarative configuration of the VaultAppRole type for use with +// apply. +func VaultAppRole() *VaultAppRoleApplyConfiguration { + return &VaultAppRoleApplyConfiguration{} +} + +// WithPath sets the Path field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Path field is set to the value of the last call. +func (b *VaultAppRoleApplyConfiguration) WithPath(value string) *VaultAppRoleApplyConfiguration { + b.Path = &value + return b +} + +// WithRoleId sets the RoleId field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the RoleId field is set to the value of the last call. +func (b *VaultAppRoleApplyConfiguration) WithRoleId(value string) *VaultAppRoleApplyConfiguration { + b.RoleId = &value + return b +} + +// WithSecretRef sets the SecretRef field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the SecretRef field is set to the value of the last call. +func (b *VaultAppRoleApplyConfiguration) WithSecretRef(value *metav1.SecretKeySelectorApplyConfiguration) *VaultAppRoleApplyConfiguration { + b.SecretRef = value + return b +} diff --git a/pkg/client/applyconfigurations/certmanager/v1/vaultauth.go b/pkg/client/applyconfigurations/certmanager/v1/vaultauth.go new file mode 100644 index 00000000000..f96c96a2af2 --- /dev/null +++ b/pkg/client/applyconfigurations/certmanager/v1/vaultauth.go @@ -0,0 +1,70 @@ +/* +Copyright 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. +*/ + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1 + +import ( + metav1 "github.com/cert-manager/cert-manager/pkg/client/applyconfigurations/meta/v1" +) + +// VaultAuthApplyConfiguration represents a declarative configuration of the VaultAuth type for use +// with apply. +type VaultAuthApplyConfiguration struct { + TokenSecretRef *metav1.SecretKeySelectorApplyConfiguration `json:"tokenSecretRef,omitempty"` + AppRole *VaultAppRoleApplyConfiguration `json:"appRole,omitempty"` + ClientCertificate *VaultClientCertificateAuthApplyConfiguration `json:"clientCertificate,omitempty"` + Kubernetes *VaultKubernetesAuthApplyConfiguration `json:"kubernetes,omitempty"` +} + +// VaultAuthApplyConfiguration constructs a declarative configuration of the VaultAuth type for use with +// apply. +func VaultAuth() *VaultAuthApplyConfiguration { + return &VaultAuthApplyConfiguration{} +} + +// WithTokenSecretRef sets the TokenSecretRef field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the TokenSecretRef field is set to the value of the last call. +func (b *VaultAuthApplyConfiguration) WithTokenSecretRef(value *metav1.SecretKeySelectorApplyConfiguration) *VaultAuthApplyConfiguration { + b.TokenSecretRef = value + return b +} + +// WithAppRole sets the AppRole field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the AppRole field is set to the value of the last call. +func (b *VaultAuthApplyConfiguration) WithAppRole(value *VaultAppRoleApplyConfiguration) *VaultAuthApplyConfiguration { + b.AppRole = value + return b +} + +// WithClientCertificate sets the ClientCertificate field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the ClientCertificate field is set to the value of the last call. +func (b *VaultAuthApplyConfiguration) WithClientCertificate(value *VaultClientCertificateAuthApplyConfiguration) *VaultAuthApplyConfiguration { + b.ClientCertificate = value + return b +} + +// WithKubernetes sets the Kubernetes field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Kubernetes field is set to the value of the last call. +func (b *VaultAuthApplyConfiguration) WithKubernetes(value *VaultKubernetesAuthApplyConfiguration) *VaultAuthApplyConfiguration { + b.Kubernetes = value + return b +} diff --git a/pkg/client/applyconfigurations/certmanager/v1/vaultclientcertificateauth.go b/pkg/client/applyconfigurations/certmanager/v1/vaultclientcertificateauth.go new file mode 100644 index 00000000000..93e9ff9baee --- /dev/null +++ b/pkg/client/applyconfigurations/certmanager/v1/vaultclientcertificateauth.go @@ -0,0 +1,57 @@ +/* +Copyright 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. +*/ + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1 + +// VaultClientCertificateAuthApplyConfiguration represents a declarative configuration of the VaultClientCertificateAuth type for use +// with apply. +type VaultClientCertificateAuthApplyConfiguration struct { + Path *string `json:"mountPath,omitempty"` + SecretName *string `json:"secretName,omitempty"` + Name *string `json:"name,omitempty"` +} + +// VaultClientCertificateAuthApplyConfiguration constructs a declarative configuration of the VaultClientCertificateAuth type for use with +// apply. +func VaultClientCertificateAuth() *VaultClientCertificateAuthApplyConfiguration { + return &VaultClientCertificateAuthApplyConfiguration{} +} + +// WithPath sets the Path field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Path field is set to the value of the last call. +func (b *VaultClientCertificateAuthApplyConfiguration) WithPath(value string) *VaultClientCertificateAuthApplyConfiguration { + b.Path = &value + return b +} + +// WithSecretName sets the SecretName field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the SecretName field is set to the value of the last call. +func (b *VaultClientCertificateAuthApplyConfiguration) WithSecretName(value string) *VaultClientCertificateAuthApplyConfiguration { + b.SecretName = &value + return b +} + +// WithName sets the Name field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Name field is set to the value of the last call. +func (b *VaultClientCertificateAuthApplyConfiguration) WithName(value string) *VaultClientCertificateAuthApplyConfiguration { + b.Name = &value + return b +} diff --git a/pkg/client/applyconfigurations/certmanager/v1/vaultissuer.go b/pkg/client/applyconfigurations/certmanager/v1/vaultissuer.go new file mode 100644 index 00000000000..ae16928e2de --- /dev/null +++ b/pkg/client/applyconfigurations/certmanager/v1/vaultissuer.go @@ -0,0 +1,117 @@ +/* +Copyright 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. +*/ + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1 + +import ( + metav1 "github.com/cert-manager/cert-manager/pkg/client/applyconfigurations/meta/v1" +) + +// VaultIssuerApplyConfiguration represents a declarative configuration of the VaultIssuer type for use +// with apply. +type VaultIssuerApplyConfiguration struct { + Auth *VaultAuthApplyConfiguration `json:"auth,omitempty"` + Server *string `json:"server,omitempty"` + ServerName *string `json:"serverName,omitempty"` + Path *string `json:"path,omitempty"` + Namespace *string `json:"namespace,omitempty"` + CABundle []byte `json:"caBundle,omitempty"` + CABundleSecretRef *metav1.SecretKeySelectorApplyConfiguration `json:"caBundleSecretRef,omitempty"` + ClientCertSecretRef *metav1.SecretKeySelectorApplyConfiguration `json:"clientCertSecretRef,omitempty"` + ClientKeySecretRef *metav1.SecretKeySelectorApplyConfiguration `json:"clientKeySecretRef,omitempty"` +} + +// VaultIssuerApplyConfiguration constructs a declarative configuration of the VaultIssuer type for use with +// apply. +func VaultIssuer() *VaultIssuerApplyConfiguration { + return &VaultIssuerApplyConfiguration{} +} + +// WithAuth sets the Auth field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Auth field is set to the value of the last call. +func (b *VaultIssuerApplyConfiguration) WithAuth(value *VaultAuthApplyConfiguration) *VaultIssuerApplyConfiguration { + b.Auth = value + return b +} + +// WithServer sets the Server field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Server field is set to the value of the last call. +func (b *VaultIssuerApplyConfiguration) WithServer(value string) *VaultIssuerApplyConfiguration { + b.Server = &value + return b +} + +// WithServerName sets the ServerName field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the ServerName field is set to the value of the last call. +func (b *VaultIssuerApplyConfiguration) WithServerName(value string) *VaultIssuerApplyConfiguration { + b.ServerName = &value + return b +} + +// WithPath sets the Path field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Path field is set to the value of the last call. +func (b *VaultIssuerApplyConfiguration) WithPath(value string) *VaultIssuerApplyConfiguration { + b.Path = &value + return b +} + +// WithNamespace sets the Namespace field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Namespace field is set to the value of the last call. +func (b *VaultIssuerApplyConfiguration) WithNamespace(value string) *VaultIssuerApplyConfiguration { + b.Namespace = &value + return b +} + +// WithCABundle adds the given value to the CABundle field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the CABundle field. +func (b *VaultIssuerApplyConfiguration) WithCABundle(values ...byte) *VaultIssuerApplyConfiguration { + for i := range values { + b.CABundle = append(b.CABundle, values[i]) + } + return b +} + +// WithCABundleSecretRef sets the CABundleSecretRef field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the CABundleSecretRef field is set to the value of the last call. +func (b *VaultIssuerApplyConfiguration) WithCABundleSecretRef(value *metav1.SecretKeySelectorApplyConfiguration) *VaultIssuerApplyConfiguration { + b.CABundleSecretRef = value + return b +} + +// WithClientCertSecretRef sets the ClientCertSecretRef field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the ClientCertSecretRef field is set to the value of the last call. +func (b *VaultIssuerApplyConfiguration) WithClientCertSecretRef(value *metav1.SecretKeySelectorApplyConfiguration) *VaultIssuerApplyConfiguration { + b.ClientCertSecretRef = value + return b +} + +// WithClientKeySecretRef sets the ClientKeySecretRef field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the ClientKeySecretRef field is set to the value of the last call. +func (b *VaultIssuerApplyConfiguration) WithClientKeySecretRef(value *metav1.SecretKeySelectorApplyConfiguration) *VaultIssuerApplyConfiguration { + b.ClientKeySecretRef = value + return b +} diff --git a/pkg/client/applyconfigurations/certmanager/v1/vaultkubernetesauth.go b/pkg/client/applyconfigurations/certmanager/v1/vaultkubernetesauth.go new file mode 100644 index 00000000000..5b6eb222a82 --- /dev/null +++ b/pkg/client/applyconfigurations/certmanager/v1/vaultkubernetesauth.go @@ -0,0 +1,70 @@ +/* +Copyright 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. +*/ + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1 + +import ( + metav1 "github.com/cert-manager/cert-manager/pkg/client/applyconfigurations/meta/v1" +) + +// VaultKubernetesAuthApplyConfiguration represents a declarative configuration of the VaultKubernetesAuth type for use +// with apply. +type VaultKubernetesAuthApplyConfiguration struct { + Path *string `json:"mountPath,omitempty"` + SecretRef *metav1.SecretKeySelectorApplyConfiguration `json:"secretRef,omitempty"` + ServiceAccountRef *ServiceAccountRefApplyConfiguration `json:"serviceAccountRef,omitempty"` + Role *string `json:"role,omitempty"` +} + +// VaultKubernetesAuthApplyConfiguration constructs a declarative configuration of the VaultKubernetesAuth type for use with +// apply. +func VaultKubernetesAuth() *VaultKubernetesAuthApplyConfiguration { + return &VaultKubernetesAuthApplyConfiguration{} +} + +// WithPath sets the Path field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Path field is set to the value of the last call. +func (b *VaultKubernetesAuthApplyConfiguration) WithPath(value string) *VaultKubernetesAuthApplyConfiguration { + b.Path = &value + return b +} + +// WithSecretRef sets the SecretRef field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the SecretRef field is set to the value of the last call. +func (b *VaultKubernetesAuthApplyConfiguration) WithSecretRef(value *metav1.SecretKeySelectorApplyConfiguration) *VaultKubernetesAuthApplyConfiguration { + b.SecretRef = value + return b +} + +// WithServiceAccountRef sets the ServiceAccountRef field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the ServiceAccountRef field is set to the value of the last call. +func (b *VaultKubernetesAuthApplyConfiguration) WithServiceAccountRef(value *ServiceAccountRefApplyConfiguration) *VaultKubernetesAuthApplyConfiguration { + b.ServiceAccountRef = value + return b +} + +// WithRole sets the Role field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Role field is set to the value of the last call. +func (b *VaultKubernetesAuthApplyConfiguration) WithRole(value string) *VaultKubernetesAuthApplyConfiguration { + b.Role = &value + return b +} diff --git a/pkg/client/applyconfigurations/certmanager/v1/venaficloud.go b/pkg/client/applyconfigurations/certmanager/v1/venaficloud.go new file mode 100644 index 00000000000..7f31792f30f --- /dev/null +++ b/pkg/client/applyconfigurations/certmanager/v1/venaficloud.go @@ -0,0 +1,52 @@ +/* +Copyright 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. +*/ + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1 + +import ( + metav1 "github.com/cert-manager/cert-manager/pkg/client/applyconfigurations/meta/v1" +) + +// VenafiCloudApplyConfiguration represents a declarative configuration of the VenafiCloud type for use +// with apply. +type VenafiCloudApplyConfiguration struct { + URL *string `json:"url,omitempty"` + APITokenSecretRef *metav1.SecretKeySelectorApplyConfiguration `json:"apiTokenSecretRef,omitempty"` +} + +// VenafiCloudApplyConfiguration constructs a declarative configuration of the VenafiCloud type for use with +// apply. +func VenafiCloud() *VenafiCloudApplyConfiguration { + return &VenafiCloudApplyConfiguration{} +} + +// WithURL sets the URL field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the URL field is set to the value of the last call. +func (b *VenafiCloudApplyConfiguration) WithURL(value string) *VenafiCloudApplyConfiguration { + b.URL = &value + return b +} + +// WithAPITokenSecretRef sets the APITokenSecretRef field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the APITokenSecretRef field is set to the value of the last call. +func (b *VenafiCloudApplyConfiguration) WithAPITokenSecretRef(value *metav1.SecretKeySelectorApplyConfiguration) *VenafiCloudApplyConfiguration { + b.APITokenSecretRef = value + return b +} diff --git a/pkg/client/applyconfigurations/certmanager/v1/venafiissuer.go b/pkg/client/applyconfigurations/certmanager/v1/venafiissuer.go new file mode 100644 index 00000000000..90132ae636c --- /dev/null +++ b/pkg/client/applyconfigurations/certmanager/v1/venafiissuer.go @@ -0,0 +1,57 @@ +/* +Copyright 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. +*/ + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1 + +// VenafiIssuerApplyConfiguration represents a declarative configuration of the VenafiIssuer type for use +// with apply. +type VenafiIssuerApplyConfiguration struct { + Zone *string `json:"zone,omitempty"` + TPP *VenafiTPPApplyConfiguration `json:"tpp,omitempty"` + Cloud *VenafiCloudApplyConfiguration `json:"cloud,omitempty"` +} + +// VenafiIssuerApplyConfiguration constructs a declarative configuration of the VenafiIssuer type for use with +// apply. +func VenafiIssuer() *VenafiIssuerApplyConfiguration { + return &VenafiIssuerApplyConfiguration{} +} + +// WithZone sets the Zone field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Zone field is set to the value of the last call. +func (b *VenafiIssuerApplyConfiguration) WithZone(value string) *VenafiIssuerApplyConfiguration { + b.Zone = &value + return b +} + +// WithTPP sets the TPP field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the TPP field is set to the value of the last call. +func (b *VenafiIssuerApplyConfiguration) WithTPP(value *VenafiTPPApplyConfiguration) *VenafiIssuerApplyConfiguration { + b.TPP = value + return b +} + +// WithCloud sets the Cloud field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Cloud field is set to the value of the last call. +func (b *VenafiIssuerApplyConfiguration) WithCloud(value *VenafiCloudApplyConfiguration) *VenafiIssuerApplyConfiguration { + b.Cloud = value + return b +} diff --git a/pkg/client/applyconfigurations/certmanager/v1/venafitpp.go b/pkg/client/applyconfigurations/certmanager/v1/venafitpp.go new file mode 100644 index 00000000000..706e45e405d --- /dev/null +++ b/pkg/client/applyconfigurations/certmanager/v1/venafitpp.go @@ -0,0 +1,72 @@ +/* +Copyright 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. +*/ + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1 + +import ( + metav1 "github.com/cert-manager/cert-manager/pkg/client/applyconfigurations/meta/v1" +) + +// VenafiTPPApplyConfiguration represents a declarative configuration of the VenafiTPP type for use +// with apply. +type VenafiTPPApplyConfiguration struct { + URL *string `json:"url,omitempty"` + CredentialsRef *metav1.LocalObjectReferenceApplyConfiguration `json:"credentialsRef,omitempty"` + CABundle []byte `json:"caBundle,omitempty"` + CABundleSecretRef *metav1.SecretKeySelectorApplyConfiguration `json:"caBundleSecretRef,omitempty"` +} + +// VenafiTPPApplyConfiguration constructs a declarative configuration of the VenafiTPP type for use with +// apply. +func VenafiTPP() *VenafiTPPApplyConfiguration { + return &VenafiTPPApplyConfiguration{} +} + +// WithURL sets the URL field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the URL field is set to the value of the last call. +func (b *VenafiTPPApplyConfiguration) WithURL(value string) *VenafiTPPApplyConfiguration { + b.URL = &value + return b +} + +// WithCredentialsRef sets the CredentialsRef field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the CredentialsRef field is set to the value of the last call. +func (b *VenafiTPPApplyConfiguration) WithCredentialsRef(value *metav1.LocalObjectReferenceApplyConfiguration) *VenafiTPPApplyConfiguration { + b.CredentialsRef = value + return b +} + +// WithCABundle adds the given value to the CABundle field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the CABundle field. +func (b *VenafiTPPApplyConfiguration) WithCABundle(values ...byte) *VenafiTPPApplyConfiguration { + for i := range values { + b.CABundle = append(b.CABundle, values[i]) + } + return b +} + +// WithCABundleSecretRef sets the CABundleSecretRef field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the CABundleSecretRef field is set to the value of the last call. +func (b *VenafiTPPApplyConfiguration) WithCABundleSecretRef(value *metav1.SecretKeySelectorApplyConfiguration) *VenafiTPPApplyConfiguration { + b.CABundleSecretRef = value + return b +} diff --git a/pkg/client/applyconfigurations/certmanager/v1/x509subject.go b/pkg/client/applyconfigurations/certmanager/v1/x509subject.go new file mode 100644 index 00000000000..9cae41f8468 --- /dev/null +++ b/pkg/client/applyconfigurations/certmanager/v1/x509subject.go @@ -0,0 +1,116 @@ +/* +Copyright 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. +*/ + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1 + +// X509SubjectApplyConfiguration represents a declarative configuration of the X509Subject type for use +// with apply. +type X509SubjectApplyConfiguration struct { + Organizations []string `json:"organizations,omitempty"` + Countries []string `json:"countries,omitempty"` + OrganizationalUnits []string `json:"organizationalUnits,omitempty"` + Localities []string `json:"localities,omitempty"` + Provinces []string `json:"provinces,omitempty"` + StreetAddresses []string `json:"streetAddresses,omitempty"` + PostalCodes []string `json:"postalCodes,omitempty"` + SerialNumber *string `json:"serialNumber,omitempty"` +} + +// X509SubjectApplyConfiguration constructs a declarative configuration of the X509Subject type for use with +// apply. +func X509Subject() *X509SubjectApplyConfiguration { + return &X509SubjectApplyConfiguration{} +} + +// WithOrganizations adds the given value to the Organizations field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the Organizations field. +func (b *X509SubjectApplyConfiguration) WithOrganizations(values ...string) *X509SubjectApplyConfiguration { + for i := range values { + b.Organizations = append(b.Organizations, values[i]) + } + return b +} + +// WithCountries adds the given value to the Countries field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the Countries field. +func (b *X509SubjectApplyConfiguration) WithCountries(values ...string) *X509SubjectApplyConfiguration { + for i := range values { + b.Countries = append(b.Countries, values[i]) + } + return b +} + +// WithOrganizationalUnits adds the given value to the OrganizationalUnits field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the OrganizationalUnits field. +func (b *X509SubjectApplyConfiguration) WithOrganizationalUnits(values ...string) *X509SubjectApplyConfiguration { + for i := range values { + b.OrganizationalUnits = append(b.OrganizationalUnits, values[i]) + } + return b +} + +// WithLocalities adds the given value to the Localities field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the Localities field. +func (b *X509SubjectApplyConfiguration) WithLocalities(values ...string) *X509SubjectApplyConfiguration { + for i := range values { + b.Localities = append(b.Localities, values[i]) + } + return b +} + +// WithProvinces adds the given value to the Provinces field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the Provinces field. +func (b *X509SubjectApplyConfiguration) WithProvinces(values ...string) *X509SubjectApplyConfiguration { + for i := range values { + b.Provinces = append(b.Provinces, values[i]) + } + return b +} + +// WithStreetAddresses adds the given value to the StreetAddresses field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the StreetAddresses field. +func (b *X509SubjectApplyConfiguration) WithStreetAddresses(values ...string) *X509SubjectApplyConfiguration { + for i := range values { + b.StreetAddresses = append(b.StreetAddresses, values[i]) + } + return b +} + +// WithPostalCodes adds the given value to the PostalCodes field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the PostalCodes field. +func (b *X509SubjectApplyConfiguration) WithPostalCodes(values ...string) *X509SubjectApplyConfiguration { + for i := range values { + b.PostalCodes = append(b.PostalCodes, values[i]) + } + return b +} + +// WithSerialNumber sets the SerialNumber field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the SerialNumber field is set to the value of the last call. +func (b *X509SubjectApplyConfiguration) WithSerialNumber(value string) *X509SubjectApplyConfiguration { + b.SerialNumber = &value + return b +} diff --git a/pkg/client/applyconfigurations/internal/internal.go b/pkg/client/applyconfigurations/internal/internal.go new file mode 100644 index 00000000000..75563d69194 --- /dev/null +++ b/pkg/client/applyconfigurations/internal/internal.go @@ -0,0 +1,1920 @@ +/* +Copyright 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. +*/ + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package internal + +import ( + fmt "fmt" + sync "sync" + + typed "sigs.k8s.io/structured-merge-diff/v6/typed" +) + +func Parser() *typed.Parser { + parserOnce.Do(func() { + var err error + parser, err = typed.NewParser(schemaYAML) + if err != nil { + panic(fmt.Sprintf("Failed to parse schema: %v", err)) + } + }) + return parser +} + +var parserOnce sync.Once +var parser *typed.Parser +var schemaYAML = typed.YAMLObject(`types: +- name: com.github.cert-manager.cert-manager.pkg.apis.acme.v1.ACMEAuthorization + map: + fields: + - name: challenges + type: + list: + elementType: + namedType: com.github.cert-manager.cert-manager.pkg.apis.acme.v1.ACMEChallenge + elementRelationship: atomic + - name: identifier + type: + scalar: string + - name: initialState + type: + scalar: string + - name: url + type: + scalar: string + default: "" + - name: wildcard + type: + scalar: boolean +- name: com.github.cert-manager.cert-manager.pkg.apis.acme.v1.ACMEChallenge + map: + fields: + - name: token + type: + scalar: string + default: "" + - name: type + type: + scalar: string + default: "" + - name: url + type: + scalar: string + default: "" +- name: com.github.cert-manager.cert-manager.pkg.apis.acme.v1.ACMEChallengeSolver + map: + fields: + - name: dns01 + type: + namedType: com.github.cert-manager.cert-manager.pkg.apis.acme.v1.ACMEChallengeSolverDNS01 + - name: http01 + type: + namedType: com.github.cert-manager.cert-manager.pkg.apis.acme.v1.ACMEChallengeSolverHTTP01 + - name: selector + type: + namedType: com.github.cert-manager.cert-manager.pkg.apis.acme.v1.CertificateDNSNameSelector +- name: com.github.cert-manager.cert-manager.pkg.apis.acme.v1.ACMEChallengeSolverDNS01 + map: + fields: + - name: acmeDNS + type: + namedType: com.github.cert-manager.cert-manager.pkg.apis.acme.v1.ACMEIssuerDNS01ProviderAcmeDNS + - name: akamai + type: + namedType: com.github.cert-manager.cert-manager.pkg.apis.acme.v1.ACMEIssuerDNS01ProviderAkamai + - name: azureDNS + type: + namedType: com.github.cert-manager.cert-manager.pkg.apis.acme.v1.ACMEIssuerDNS01ProviderAzureDNS + - name: cloudDNS + type: + namedType: com.github.cert-manager.cert-manager.pkg.apis.acme.v1.ACMEIssuerDNS01ProviderCloudDNS + - name: cloudflare + type: + namedType: com.github.cert-manager.cert-manager.pkg.apis.acme.v1.ACMEIssuerDNS01ProviderCloudflare + - name: cnameStrategy + type: + scalar: string + - name: digitalocean + type: + namedType: com.github.cert-manager.cert-manager.pkg.apis.acme.v1.ACMEIssuerDNS01ProviderDigitalOcean + - name: rfc2136 + type: + namedType: com.github.cert-manager.cert-manager.pkg.apis.acme.v1.ACMEIssuerDNS01ProviderRFC2136 + - name: route53 + type: + namedType: com.github.cert-manager.cert-manager.pkg.apis.acme.v1.ACMEIssuerDNS01ProviderRoute53 + - name: webhook + type: + namedType: com.github.cert-manager.cert-manager.pkg.apis.acme.v1.ACMEIssuerDNS01ProviderWebhook +- name: com.github.cert-manager.cert-manager.pkg.apis.acme.v1.ACMEChallengeSolverHTTP01 + map: + fields: + - name: gatewayHTTPRoute + type: + namedType: com.github.cert-manager.cert-manager.pkg.apis.acme.v1.ACMEChallengeSolverHTTP01GatewayHTTPRoute + - name: ingress + type: + namedType: com.github.cert-manager.cert-manager.pkg.apis.acme.v1.ACMEChallengeSolverHTTP01Ingress +- name: com.github.cert-manager.cert-manager.pkg.apis.acme.v1.ACMEChallengeSolverHTTP01GatewayHTTPRoute + map: + fields: + - name: labels + type: + map: + elementType: + scalar: string + - name: parentRefs + type: + list: + elementType: + namedType: io.k8s.sigs.gateway-api.apis.v1.ParentReference + elementRelationship: atomic + - name: podTemplate + type: + namedType: com.github.cert-manager.cert-manager.pkg.apis.acme.v1.ACMEChallengeSolverHTTP01IngressPodTemplate + - name: serviceType + type: + scalar: string +- name: com.github.cert-manager.cert-manager.pkg.apis.acme.v1.ACMEChallengeSolverHTTP01Ingress + map: + fields: + - name: class + type: + scalar: string + - name: ingressClassName + type: + scalar: string + - name: ingressTemplate + type: + namedType: com.github.cert-manager.cert-manager.pkg.apis.acme.v1.ACMEChallengeSolverHTTP01IngressTemplate + - name: name + type: + scalar: string + - name: podTemplate + type: + namedType: com.github.cert-manager.cert-manager.pkg.apis.acme.v1.ACMEChallengeSolverHTTP01IngressPodTemplate + - name: serviceType + type: + scalar: string +- name: com.github.cert-manager.cert-manager.pkg.apis.acme.v1.ACMEChallengeSolverHTTP01IngressObjectMeta + map: + fields: + - name: annotations + type: + map: + elementType: + scalar: string + - name: labels + type: + map: + elementType: + scalar: string +- name: com.github.cert-manager.cert-manager.pkg.apis.acme.v1.ACMEChallengeSolverHTTP01IngressPodObjectMeta + map: + fields: + - name: annotations + type: + map: + elementType: + scalar: string + - name: labels + type: + map: + elementType: + scalar: string +- name: com.github.cert-manager.cert-manager.pkg.apis.acme.v1.ACMEChallengeSolverHTTP01IngressPodResources + map: + fields: + - name: limits + type: + map: + elementType: + namedType: io.k8s.apimachinery.pkg.api.resource.Quantity + - name: requests + type: + map: + elementType: + namedType: io.k8s.apimachinery.pkg.api.resource.Quantity +- name: com.github.cert-manager.cert-manager.pkg.apis.acme.v1.ACMEChallengeSolverHTTP01IngressPodSecurityContext + map: + fields: + - name: fsGroup + type: + scalar: numeric + - name: fsGroupChangePolicy + type: + scalar: string + - name: runAsGroup + type: + scalar: numeric + - name: runAsNonRoot + type: + scalar: boolean + - name: runAsUser + type: + scalar: numeric + - name: seLinuxOptions + type: + namedType: io.k8s.api.core.v1.SELinuxOptions + - name: seccompProfile + type: + namedType: io.k8s.api.core.v1.SeccompProfile + - name: supplementalGroups + type: + list: + elementType: + scalar: numeric + elementRelationship: atomic + - name: sysctls + type: + list: + elementType: + namedType: io.k8s.api.core.v1.Sysctl + elementRelationship: atomic +- name: com.github.cert-manager.cert-manager.pkg.apis.acme.v1.ACMEChallengeSolverHTTP01IngressPodSpec + map: + fields: + - name: affinity + type: + namedType: io.k8s.api.core.v1.Affinity + - name: imagePullSecrets + type: + list: + elementType: + namedType: io.k8s.api.core.v1.LocalObjectReference + elementRelationship: associative + keys: + - name + - name: nodeSelector + type: + map: + elementType: + scalar: string + - name: priorityClassName + type: + scalar: string + - name: resources + type: + namedType: com.github.cert-manager.cert-manager.pkg.apis.acme.v1.ACMEChallengeSolverHTTP01IngressPodResources + - name: securityContext + type: + namedType: com.github.cert-manager.cert-manager.pkg.apis.acme.v1.ACMEChallengeSolverHTTP01IngressPodSecurityContext + - name: serviceAccountName + type: + scalar: string + - name: tolerations + type: + list: + elementType: + namedType: io.k8s.api.core.v1.Toleration + elementRelationship: atomic +- name: com.github.cert-manager.cert-manager.pkg.apis.acme.v1.ACMEChallengeSolverHTTP01IngressPodTemplate + map: + fields: + - name: metadata + type: + namedType: com.github.cert-manager.cert-manager.pkg.apis.acme.v1.ACMEChallengeSolverHTTP01IngressPodObjectMeta + default: {} + - name: spec + type: + namedType: com.github.cert-manager.cert-manager.pkg.apis.acme.v1.ACMEChallengeSolverHTTP01IngressPodSpec + default: {} +- name: com.github.cert-manager.cert-manager.pkg.apis.acme.v1.ACMEChallengeSolverHTTP01IngressTemplate + map: + fields: + - name: metadata + type: + namedType: com.github.cert-manager.cert-manager.pkg.apis.acme.v1.ACMEChallengeSolverHTTP01IngressObjectMeta + default: {} +- name: com.github.cert-manager.cert-manager.pkg.apis.acme.v1.ACMEExternalAccountBinding + map: + fields: + - name: keyAlgorithm + type: + scalar: string + - name: keyID + type: + scalar: string + default: "" + - name: keySecretRef + type: + namedType: com.github.cert-manager.cert-manager.pkg.apis.meta.v1.SecretKeySelector + default: {} +- name: com.github.cert-manager.cert-manager.pkg.apis.acme.v1.ACMEIssuer + map: + fields: + - name: caBundle + type: + scalar: string + - name: disableAccountKeyGeneration + type: + scalar: boolean + - name: email + type: + scalar: string + - name: enableDurationFeature + type: + scalar: boolean + - name: externalAccountBinding + type: + namedType: com.github.cert-manager.cert-manager.pkg.apis.acme.v1.ACMEExternalAccountBinding + - name: preferredChain + type: + scalar: string + - name: privateKeySecretRef + type: + namedType: com.github.cert-manager.cert-manager.pkg.apis.meta.v1.SecretKeySelector + default: {} + - name: profile + type: + scalar: string + - name: server + type: + scalar: string + default: "" + - name: skipTLSVerify + type: + scalar: boolean + - name: solvers + type: + list: + elementType: + namedType: com.github.cert-manager.cert-manager.pkg.apis.acme.v1.ACMEChallengeSolver + elementRelationship: atomic +- name: com.github.cert-manager.cert-manager.pkg.apis.acme.v1.ACMEIssuerDNS01ProviderAcmeDNS + map: + fields: + - name: accountSecretRef + type: + namedType: com.github.cert-manager.cert-manager.pkg.apis.meta.v1.SecretKeySelector + default: {} + - name: host + type: + scalar: string + default: "" +- name: com.github.cert-manager.cert-manager.pkg.apis.acme.v1.ACMEIssuerDNS01ProviderAkamai + map: + fields: + - name: accessTokenSecretRef + type: + namedType: com.github.cert-manager.cert-manager.pkg.apis.meta.v1.SecretKeySelector + default: {} + - name: clientSecretSecretRef + type: + namedType: com.github.cert-manager.cert-manager.pkg.apis.meta.v1.SecretKeySelector + default: {} + - name: clientTokenSecretRef + type: + namedType: com.github.cert-manager.cert-manager.pkg.apis.meta.v1.SecretKeySelector + default: {} + - name: serviceConsumerDomain + type: + scalar: string + default: "" +- name: com.github.cert-manager.cert-manager.pkg.apis.acme.v1.ACMEIssuerDNS01ProviderAzureDNS + map: + fields: + - name: clientID + type: + scalar: string + - name: clientSecretSecretRef + type: + namedType: com.github.cert-manager.cert-manager.pkg.apis.meta.v1.SecretKeySelector + - name: environment + type: + scalar: string + - name: hostedZoneName + type: + scalar: string + - name: managedIdentity + type: + namedType: com.github.cert-manager.cert-manager.pkg.apis.acme.v1.AzureManagedIdentity + - name: resourceGroupName + type: + scalar: string + default: "" + - name: subscriptionID + type: + scalar: string + default: "" + - name: tenantID + type: + scalar: string +- name: com.github.cert-manager.cert-manager.pkg.apis.acme.v1.ACMEIssuerDNS01ProviderCloudDNS + map: + fields: + - name: hostedZoneName + type: + scalar: string + - name: project + type: + scalar: string + default: "" + - name: serviceAccountSecretRef + type: + namedType: com.github.cert-manager.cert-manager.pkg.apis.meta.v1.SecretKeySelector +- name: com.github.cert-manager.cert-manager.pkg.apis.acme.v1.ACMEIssuerDNS01ProviderCloudflare + map: + fields: + - name: apiKeySecretRef + type: + namedType: com.github.cert-manager.cert-manager.pkg.apis.meta.v1.SecretKeySelector + - name: apiTokenSecretRef + type: + namedType: com.github.cert-manager.cert-manager.pkg.apis.meta.v1.SecretKeySelector + - name: email + type: + scalar: string +- name: com.github.cert-manager.cert-manager.pkg.apis.acme.v1.ACMEIssuerDNS01ProviderDigitalOcean + map: + fields: + - name: tokenSecretRef + type: + namedType: com.github.cert-manager.cert-manager.pkg.apis.meta.v1.SecretKeySelector + default: {} +- name: com.github.cert-manager.cert-manager.pkg.apis.acme.v1.ACMEIssuerDNS01ProviderRFC2136 + map: + fields: + - name: nameserver + type: + scalar: string + default: "" + - name: protocol + type: + scalar: string + - name: tsigAlgorithm + type: + scalar: string + - name: tsigKeyName + type: + scalar: string + - name: tsigSecretSecretRef + type: + namedType: com.github.cert-manager.cert-manager.pkg.apis.meta.v1.SecretKeySelector + default: {} +- name: com.github.cert-manager.cert-manager.pkg.apis.acme.v1.ACMEIssuerDNS01ProviderRoute53 + map: + fields: + - name: accessKeyID + type: + scalar: string + - name: accessKeyIDSecretRef + type: + namedType: com.github.cert-manager.cert-manager.pkg.apis.meta.v1.SecretKeySelector + - name: auth + type: + namedType: com.github.cert-manager.cert-manager.pkg.apis.acme.v1.Route53Auth + - name: hostedZoneID + type: + scalar: string + - name: region + type: + scalar: string + - name: role + type: + scalar: string + - name: secretAccessKeySecretRef + type: + namedType: com.github.cert-manager.cert-manager.pkg.apis.meta.v1.SecretKeySelector + default: {} +- name: com.github.cert-manager.cert-manager.pkg.apis.acme.v1.ACMEIssuerDNS01ProviderWebhook + map: + fields: + - name: config + type: + namedType: io.k8s.apiextensions-apiserver.pkg.apis.apiextensions.v1.JSON + - name: groupName + type: + scalar: string + default: "" + - name: solverName + type: + scalar: string + default: "" +- name: com.github.cert-manager.cert-manager.pkg.apis.acme.v1.ACMEIssuerStatus + map: + fields: + - name: lastPrivateKeyHash + type: + scalar: string + - name: lastRegisteredEmail + type: + scalar: string + - name: uri + type: + scalar: string +- name: com.github.cert-manager.cert-manager.pkg.apis.acme.v1.AzureManagedIdentity + map: + fields: + - name: clientID + type: + scalar: string + - name: resourceID + type: + scalar: string + - name: tenantID + type: + scalar: string +- name: com.github.cert-manager.cert-manager.pkg.apis.acme.v1.CertificateDNSNameSelector + map: + fields: + - name: dnsNames + type: + list: + elementType: + scalar: string + elementRelationship: atomic + - name: dnsZones + type: + list: + elementType: + scalar: string + elementRelationship: atomic + - name: matchLabels + type: + map: + elementType: + scalar: string +- name: com.github.cert-manager.cert-manager.pkg.apis.acme.v1.Challenge + map: + fields: + - name: apiVersion + type: + scalar: string + - name: kind + type: + scalar: string + - name: metadata + type: + namedType: io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta + default: {} + - name: spec + type: + namedType: com.github.cert-manager.cert-manager.pkg.apis.acme.v1.ChallengeSpec + default: {} + - name: status + type: + namedType: com.github.cert-manager.cert-manager.pkg.apis.acme.v1.ChallengeStatus + default: {} +- name: com.github.cert-manager.cert-manager.pkg.apis.acme.v1.ChallengeSpec + map: + fields: + - name: authorizationURL + type: + scalar: string + default: "" + - name: dnsName + type: + scalar: string + default: "" + - name: issuerRef + type: + namedType: com.github.cert-manager.cert-manager.pkg.apis.meta.v1.IssuerReference + default: {} + - name: key + type: + scalar: string + default: "" + - name: solver + type: + namedType: com.github.cert-manager.cert-manager.pkg.apis.acme.v1.ACMEChallengeSolver + default: {} + - name: token + type: + scalar: string + default: "" + - name: type + type: + scalar: string + default: "" + - name: url + type: + scalar: string + default: "" + - name: wildcard + type: + scalar: boolean + default: false +- name: com.github.cert-manager.cert-manager.pkg.apis.acme.v1.ChallengeStatus + map: + fields: + - name: presented + type: + scalar: boolean + default: false + - name: processing + type: + scalar: boolean + default: false + - name: reason + type: + scalar: string + - name: state + type: + scalar: string +- name: com.github.cert-manager.cert-manager.pkg.apis.acme.v1.Order + map: + fields: + - name: apiVersion + type: + scalar: string + - name: kind + type: + scalar: string + - name: metadata + type: + namedType: io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta + default: {} + - name: spec + type: + namedType: com.github.cert-manager.cert-manager.pkg.apis.acme.v1.OrderSpec + default: {} + - name: status + type: + namedType: com.github.cert-manager.cert-manager.pkg.apis.acme.v1.OrderStatus + default: {} +- name: com.github.cert-manager.cert-manager.pkg.apis.acme.v1.OrderSpec + map: + fields: + - name: commonName + type: + scalar: string + - name: dnsNames + type: + list: + elementType: + scalar: string + elementRelationship: atomic + - name: duration + type: + namedType: io.k8s.apimachinery.pkg.apis.meta.v1.Duration + - name: ipAddresses + type: + list: + elementType: + scalar: string + elementRelationship: atomic + - name: issuerRef + type: + namedType: com.github.cert-manager.cert-manager.pkg.apis.meta.v1.IssuerReference + default: {} + - name: profile + type: + scalar: string + - name: request + type: + scalar: string +- name: com.github.cert-manager.cert-manager.pkg.apis.acme.v1.OrderStatus + map: + fields: + - name: authorizations + type: + list: + elementType: + namedType: com.github.cert-manager.cert-manager.pkg.apis.acme.v1.ACMEAuthorization + elementRelationship: atomic + - name: certificate + type: + scalar: string + - name: failureTime + type: + namedType: io.k8s.apimachinery.pkg.apis.meta.v1.Time + - name: finalizeURL + type: + scalar: string + - name: reason + type: + scalar: string + - name: state + type: + scalar: string + - name: url + type: + scalar: string +- name: com.github.cert-manager.cert-manager.pkg.apis.acme.v1.Route53Auth + map: + fields: + - name: kubernetes + type: + namedType: com.github.cert-manager.cert-manager.pkg.apis.acme.v1.Route53KubernetesAuth +- name: com.github.cert-manager.cert-manager.pkg.apis.acme.v1.Route53KubernetesAuth + map: + fields: + - name: serviceAccountRef + type: + namedType: com.github.cert-manager.cert-manager.pkg.apis.acme.v1.ServiceAccountRef +- name: com.github.cert-manager.cert-manager.pkg.apis.acme.v1.ServiceAccountRef + map: + fields: + - name: audiences + type: + list: + elementType: + scalar: string + elementRelationship: atomic + - name: name + type: + scalar: string + default: "" +- name: com.github.cert-manager.cert-manager.pkg.apis.certmanager.v1.CAIssuer + map: + fields: + - name: crlDistributionPoints + type: + list: + elementType: + scalar: string + elementRelationship: atomic + - name: issuingCertificateURLs + type: + list: + elementType: + scalar: string + elementRelationship: atomic + - name: ocspServers + type: + list: + elementType: + scalar: string + elementRelationship: atomic + - name: secretName + type: + scalar: string + default: "" +- name: com.github.cert-manager.cert-manager.pkg.apis.certmanager.v1.Certificate + map: + fields: + - name: apiVersion + type: + scalar: string + - name: kind + type: + scalar: string + - name: metadata + type: + namedType: io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta + default: {} + - name: spec + type: + namedType: com.github.cert-manager.cert-manager.pkg.apis.certmanager.v1.CertificateSpec + default: {} + - name: status + type: + namedType: com.github.cert-manager.cert-manager.pkg.apis.certmanager.v1.CertificateStatus + default: {} +- name: com.github.cert-manager.cert-manager.pkg.apis.certmanager.v1.CertificateAdditionalOutputFormat + map: + fields: + - name: type + type: + scalar: string + default: "" +- name: com.github.cert-manager.cert-manager.pkg.apis.certmanager.v1.CertificateCondition + map: + fields: + - name: lastTransitionTime + type: + namedType: io.k8s.apimachinery.pkg.apis.meta.v1.Time + - name: message + type: + scalar: string + - name: observedGeneration + type: + scalar: numeric + - name: reason + type: + scalar: string + - name: status + type: + scalar: string + default: "" + - name: type + type: + scalar: string + default: "" +- name: com.github.cert-manager.cert-manager.pkg.apis.certmanager.v1.CertificateKeystores + map: + fields: + - name: jks + type: + namedType: com.github.cert-manager.cert-manager.pkg.apis.certmanager.v1.JKSKeystore + - name: pkcs12 + type: + namedType: com.github.cert-manager.cert-manager.pkg.apis.certmanager.v1.PKCS12Keystore +- name: com.github.cert-manager.cert-manager.pkg.apis.certmanager.v1.CertificatePrivateKey + map: + fields: + - name: algorithm + type: + scalar: string + - name: encoding + type: + scalar: string + - name: rotationPolicy + type: + scalar: string + - name: size + type: + scalar: numeric +- name: com.github.cert-manager.cert-manager.pkg.apis.certmanager.v1.CertificateRequest + map: + fields: + - name: apiVersion + type: + scalar: string + - name: kind + type: + scalar: string + - name: metadata + type: + namedType: io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta + default: {} + - name: spec + type: + namedType: com.github.cert-manager.cert-manager.pkg.apis.certmanager.v1.CertificateRequestSpec + default: {} + - name: status + type: + namedType: com.github.cert-manager.cert-manager.pkg.apis.certmanager.v1.CertificateRequestStatus + default: {} +- name: com.github.cert-manager.cert-manager.pkg.apis.certmanager.v1.CertificateRequestCondition + map: + fields: + - name: lastTransitionTime + type: + namedType: io.k8s.apimachinery.pkg.apis.meta.v1.Time + - name: message + type: + scalar: string + - name: reason + type: + scalar: string + - name: status + type: + scalar: string + default: "" + - name: type + type: + scalar: string + default: "" +- name: com.github.cert-manager.cert-manager.pkg.apis.certmanager.v1.CertificateRequestSpec + map: + fields: + - name: duration + type: + namedType: io.k8s.apimachinery.pkg.apis.meta.v1.Duration + - name: extra + type: + map: + elementType: + list: + elementType: + scalar: string + elementRelationship: atomic + - name: groups + type: + list: + elementType: + scalar: string + elementRelationship: atomic + - name: isCA + type: + scalar: boolean + - name: issuerRef + type: + namedType: com.github.cert-manager.cert-manager.pkg.apis.meta.v1.IssuerReference + default: {} + - name: request + type: + scalar: string + - name: uid + type: + scalar: string + - name: usages + type: + list: + elementType: + scalar: string + elementRelationship: atomic + - name: username + type: + scalar: string +- name: com.github.cert-manager.cert-manager.pkg.apis.certmanager.v1.CertificateRequestStatus + map: + fields: + - name: ca + type: + scalar: string + - name: certificate + type: + scalar: string + - name: conditions + type: + list: + elementType: + namedType: com.github.cert-manager.cert-manager.pkg.apis.certmanager.v1.CertificateRequestCondition + elementRelationship: associative + keys: + - type + - name: failureTime + type: + namedType: io.k8s.apimachinery.pkg.apis.meta.v1.Time +- name: com.github.cert-manager.cert-manager.pkg.apis.certmanager.v1.CertificateSecretTemplate + map: + fields: + - name: annotations + type: + map: + elementType: + scalar: string + - name: labels + type: + map: + elementType: + scalar: string +- name: com.github.cert-manager.cert-manager.pkg.apis.certmanager.v1.CertificateSpec + map: + fields: + - name: additionalOutputFormats + type: + list: + elementType: + namedType: com.github.cert-manager.cert-manager.pkg.apis.certmanager.v1.CertificateAdditionalOutputFormat + elementRelationship: atomic + - name: commonName + type: + scalar: string + - name: dnsNames + type: + list: + elementType: + scalar: string + elementRelationship: atomic + - name: duration + type: + namedType: io.k8s.apimachinery.pkg.apis.meta.v1.Duration + - name: emailAddresses + type: + list: + elementType: + scalar: string + elementRelationship: atomic + - name: encodeUsagesInRequest + type: + scalar: boolean + - name: ipAddresses + type: + list: + elementType: + scalar: string + elementRelationship: atomic + - name: isCA + type: + scalar: boolean + - name: issuerRef + type: + namedType: com.github.cert-manager.cert-manager.pkg.apis.meta.v1.IssuerReference + default: {} + - name: keystores + type: + namedType: com.github.cert-manager.cert-manager.pkg.apis.certmanager.v1.CertificateKeystores + - name: literalSubject + type: + scalar: string + - name: nameConstraints + type: + namedType: com.github.cert-manager.cert-manager.pkg.apis.certmanager.v1.NameConstraints + - name: otherNames + type: + list: + elementType: + namedType: com.github.cert-manager.cert-manager.pkg.apis.certmanager.v1.OtherName + elementRelationship: atomic + - name: privateKey + type: + namedType: com.github.cert-manager.cert-manager.pkg.apis.certmanager.v1.CertificatePrivateKey + - name: renewBefore + type: + namedType: io.k8s.apimachinery.pkg.apis.meta.v1.Duration + - name: renewBeforePercentage + type: + scalar: numeric + - name: revisionHistoryLimit + type: + scalar: numeric + - name: secretName + type: + scalar: string + default: "" + - name: secretTemplate + type: + namedType: com.github.cert-manager.cert-manager.pkg.apis.certmanager.v1.CertificateSecretTemplate + - name: signatureAlgorithm + type: + scalar: string + - name: subject + type: + namedType: com.github.cert-manager.cert-manager.pkg.apis.certmanager.v1.X509Subject + - name: uris + type: + list: + elementType: + scalar: string + elementRelationship: atomic + - name: usages + type: + list: + elementType: + scalar: string + elementRelationship: atomic +- name: com.github.cert-manager.cert-manager.pkg.apis.certmanager.v1.CertificateStatus + map: + fields: + - name: conditions + type: + list: + elementType: + namedType: com.github.cert-manager.cert-manager.pkg.apis.certmanager.v1.CertificateCondition + elementRelationship: associative + keys: + - type + - name: failedIssuanceAttempts + type: + scalar: numeric + - name: lastFailureTime + type: + namedType: io.k8s.apimachinery.pkg.apis.meta.v1.Time + - name: nextPrivateKeySecretName + type: + scalar: string + - name: notAfter + type: + namedType: io.k8s.apimachinery.pkg.apis.meta.v1.Time + - name: notBefore + type: + namedType: io.k8s.apimachinery.pkg.apis.meta.v1.Time + - name: renewalTime + type: + namedType: io.k8s.apimachinery.pkg.apis.meta.v1.Time + - name: revision + type: + scalar: numeric +- name: com.github.cert-manager.cert-manager.pkg.apis.certmanager.v1.ClusterIssuer + map: + fields: + - name: apiVersion + type: + scalar: string + - name: kind + type: + scalar: string + - name: metadata + type: + namedType: io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta + default: {} + - name: spec + type: + namedType: com.github.cert-manager.cert-manager.pkg.apis.certmanager.v1.IssuerSpec + default: {} + - name: status + type: + namedType: com.github.cert-manager.cert-manager.pkg.apis.certmanager.v1.IssuerStatus + default: {} +- name: com.github.cert-manager.cert-manager.pkg.apis.certmanager.v1.Issuer + map: + fields: + - name: apiVersion + type: + scalar: string + - name: kind + type: + scalar: string + - name: metadata + type: + namedType: io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta + default: {} + - name: spec + type: + namedType: com.github.cert-manager.cert-manager.pkg.apis.certmanager.v1.IssuerSpec + default: {} + - name: status + type: + namedType: com.github.cert-manager.cert-manager.pkg.apis.certmanager.v1.IssuerStatus + default: {} +- name: com.github.cert-manager.cert-manager.pkg.apis.certmanager.v1.IssuerCondition + map: + fields: + - name: lastTransitionTime + type: + namedType: io.k8s.apimachinery.pkg.apis.meta.v1.Time + - name: message + type: + scalar: string + - name: observedGeneration + type: + scalar: numeric + - name: reason + type: + scalar: string + - name: status + type: + scalar: string + default: "" + - name: type + type: + scalar: string + default: "" +- name: com.github.cert-manager.cert-manager.pkg.apis.certmanager.v1.IssuerSpec + map: + fields: + - name: acme + type: + namedType: com.github.cert-manager.cert-manager.pkg.apis.acme.v1.ACMEIssuer + - name: ca + type: + namedType: com.github.cert-manager.cert-manager.pkg.apis.certmanager.v1.CAIssuer + - name: selfSigned + type: + namedType: com.github.cert-manager.cert-manager.pkg.apis.certmanager.v1.SelfSignedIssuer + - name: vault + type: + namedType: com.github.cert-manager.cert-manager.pkg.apis.certmanager.v1.VaultIssuer + - name: venafi + type: + namedType: com.github.cert-manager.cert-manager.pkg.apis.certmanager.v1.VenafiIssuer +- name: com.github.cert-manager.cert-manager.pkg.apis.certmanager.v1.IssuerStatus + map: + fields: + - name: acme + type: + namedType: com.github.cert-manager.cert-manager.pkg.apis.acme.v1.ACMEIssuerStatus + - name: conditions + type: + list: + elementType: + namedType: com.github.cert-manager.cert-manager.pkg.apis.certmanager.v1.IssuerCondition + elementRelationship: associative + keys: + - type +- name: com.github.cert-manager.cert-manager.pkg.apis.certmanager.v1.JKSKeystore + map: + fields: + - name: alias + type: + scalar: string + - name: create + type: + scalar: boolean + default: false + - name: password + type: + scalar: string + - name: passwordSecretRef + type: + namedType: com.github.cert-manager.cert-manager.pkg.apis.meta.v1.SecretKeySelector + default: {} +- name: com.github.cert-manager.cert-manager.pkg.apis.certmanager.v1.NameConstraintItem + map: + fields: + - name: dnsDomains + type: + list: + elementType: + scalar: string + elementRelationship: atomic + - name: emailAddresses + type: + list: + elementType: + scalar: string + elementRelationship: atomic + - name: ipRanges + type: + list: + elementType: + scalar: string + elementRelationship: atomic + - name: uriDomains + type: + list: + elementType: + scalar: string + elementRelationship: atomic +- name: com.github.cert-manager.cert-manager.pkg.apis.certmanager.v1.NameConstraints + map: + fields: + - name: critical + type: + scalar: boolean + - name: excluded + type: + namedType: com.github.cert-manager.cert-manager.pkg.apis.certmanager.v1.NameConstraintItem + - name: permitted + type: + namedType: com.github.cert-manager.cert-manager.pkg.apis.certmanager.v1.NameConstraintItem +- name: com.github.cert-manager.cert-manager.pkg.apis.certmanager.v1.OtherName + map: + fields: + - name: oid + type: + scalar: string + - name: utf8Value + type: + scalar: string +- name: com.github.cert-manager.cert-manager.pkg.apis.certmanager.v1.PKCS12Keystore + map: + fields: + - name: create + type: + scalar: boolean + default: false + - name: password + type: + scalar: string + - name: passwordSecretRef + type: + namedType: com.github.cert-manager.cert-manager.pkg.apis.meta.v1.SecretKeySelector + default: {} + - name: profile + type: + scalar: string +- name: com.github.cert-manager.cert-manager.pkg.apis.certmanager.v1.SelfSignedIssuer + map: + fields: + - name: crlDistributionPoints + type: + list: + elementType: + scalar: string + elementRelationship: atomic +- name: com.github.cert-manager.cert-manager.pkg.apis.certmanager.v1.ServiceAccountRef + map: + fields: + - name: audiences + type: + list: + elementType: + scalar: string + elementRelationship: atomic + - name: name + type: + scalar: string + default: "" +- name: com.github.cert-manager.cert-manager.pkg.apis.certmanager.v1.VaultAppRole + map: + fields: + - name: path + type: + scalar: string + default: "" + - name: roleId + type: + scalar: string + default: "" + - name: secretRef + type: + namedType: com.github.cert-manager.cert-manager.pkg.apis.meta.v1.SecretKeySelector + default: {} +- name: com.github.cert-manager.cert-manager.pkg.apis.certmanager.v1.VaultAuth + map: + fields: + - name: appRole + type: + namedType: com.github.cert-manager.cert-manager.pkg.apis.certmanager.v1.VaultAppRole + - name: clientCertificate + type: + namedType: com.github.cert-manager.cert-manager.pkg.apis.certmanager.v1.VaultClientCertificateAuth + - name: kubernetes + type: + namedType: com.github.cert-manager.cert-manager.pkg.apis.certmanager.v1.VaultKubernetesAuth + - name: tokenSecretRef + type: + namedType: com.github.cert-manager.cert-manager.pkg.apis.meta.v1.SecretKeySelector +- name: com.github.cert-manager.cert-manager.pkg.apis.certmanager.v1.VaultClientCertificateAuth + map: + fields: + - name: mountPath + type: + scalar: string + - name: name + type: + scalar: string + - name: secretName + type: + scalar: string +- name: com.github.cert-manager.cert-manager.pkg.apis.certmanager.v1.VaultIssuer + map: + fields: + - name: auth + type: + namedType: com.github.cert-manager.cert-manager.pkg.apis.certmanager.v1.VaultAuth + default: {} + - name: caBundle + type: + scalar: string + - name: caBundleSecretRef + type: + namedType: com.github.cert-manager.cert-manager.pkg.apis.meta.v1.SecretKeySelector + - name: clientCertSecretRef + type: + namedType: com.github.cert-manager.cert-manager.pkg.apis.meta.v1.SecretKeySelector + - name: clientKeySecretRef + type: + namedType: com.github.cert-manager.cert-manager.pkg.apis.meta.v1.SecretKeySelector + - name: namespace + type: + scalar: string + - name: path + type: + scalar: string + default: "" + - name: server + type: + scalar: string + default: "" + - name: serverName + type: + scalar: string +- name: com.github.cert-manager.cert-manager.pkg.apis.certmanager.v1.VaultKubernetesAuth + map: + fields: + - name: mountPath + type: + scalar: string + - name: role + type: + scalar: string + default: "" + - name: secretRef + type: + namedType: com.github.cert-manager.cert-manager.pkg.apis.meta.v1.SecretKeySelector + default: {} + - name: serviceAccountRef + type: + namedType: com.github.cert-manager.cert-manager.pkg.apis.certmanager.v1.ServiceAccountRef +- name: com.github.cert-manager.cert-manager.pkg.apis.certmanager.v1.VenafiCloud + map: + fields: + - name: apiTokenSecretRef + type: + namedType: com.github.cert-manager.cert-manager.pkg.apis.meta.v1.SecretKeySelector + default: {} + - name: url + type: + scalar: string +- name: com.github.cert-manager.cert-manager.pkg.apis.certmanager.v1.VenafiIssuer + map: + fields: + - name: cloud + type: + namedType: com.github.cert-manager.cert-manager.pkg.apis.certmanager.v1.VenafiCloud + - name: tpp + type: + namedType: com.github.cert-manager.cert-manager.pkg.apis.certmanager.v1.VenafiTPP + - name: zone + type: + scalar: string + default: "" +- name: com.github.cert-manager.cert-manager.pkg.apis.certmanager.v1.VenafiTPP + map: + fields: + - name: caBundle + type: + scalar: string + - name: caBundleSecretRef + type: + namedType: com.github.cert-manager.cert-manager.pkg.apis.meta.v1.SecretKeySelector + - name: credentialsRef + type: + namedType: com.github.cert-manager.cert-manager.pkg.apis.meta.v1.LocalObjectReference + default: {} + - name: url + type: + scalar: string + default: "" +- name: com.github.cert-manager.cert-manager.pkg.apis.certmanager.v1.X509Subject + map: + fields: + - name: countries + type: + list: + elementType: + scalar: string + elementRelationship: atomic + - name: localities + type: + list: + elementType: + scalar: string + elementRelationship: atomic + - name: organizationalUnits + type: + list: + elementType: + scalar: string + elementRelationship: atomic + - name: organizations + type: + list: + elementType: + scalar: string + elementRelationship: atomic + - name: postalCodes + type: + list: + elementType: + scalar: string + elementRelationship: atomic + - name: provinces + type: + list: + elementType: + scalar: string + elementRelationship: atomic + - name: serialNumber + type: + scalar: string + - name: streetAddresses + type: + list: + elementType: + scalar: string + elementRelationship: atomic +- name: com.github.cert-manager.cert-manager.pkg.apis.meta.v1.IssuerReference + map: + fields: + - name: group + type: + scalar: string + - name: kind + type: + scalar: string + - name: name + type: + scalar: string + default: "" +- name: com.github.cert-manager.cert-manager.pkg.apis.meta.v1.LocalObjectReference + map: + fields: + - name: name + type: + scalar: string + default: "" +- name: com.github.cert-manager.cert-manager.pkg.apis.meta.v1.SecretKeySelector + map: + fields: + - name: key + type: + scalar: string + - name: name + type: + scalar: string + default: "" +- name: io.k8s.api.core.v1.Affinity + map: + fields: + - name: nodeAffinity + type: + namedType: io.k8s.api.core.v1.NodeAffinity + - name: podAffinity + type: + namedType: io.k8s.api.core.v1.PodAffinity + - name: podAntiAffinity + type: + namedType: io.k8s.api.core.v1.PodAntiAffinity +- name: io.k8s.api.core.v1.LocalObjectReference + map: + fields: + - name: name + type: + scalar: string + default: "" + elementRelationship: atomic +- name: io.k8s.api.core.v1.NodeAffinity + map: + fields: + - name: preferredDuringSchedulingIgnoredDuringExecution + type: + list: + elementType: + namedType: io.k8s.api.core.v1.PreferredSchedulingTerm + elementRelationship: atomic + - name: requiredDuringSchedulingIgnoredDuringExecution + type: + namedType: io.k8s.api.core.v1.NodeSelector +- name: io.k8s.api.core.v1.NodeSelector + map: + fields: + - name: nodeSelectorTerms + type: + list: + elementType: + namedType: io.k8s.api.core.v1.NodeSelectorTerm + elementRelationship: atomic + elementRelationship: atomic +- name: io.k8s.api.core.v1.NodeSelectorRequirement + map: + fields: + - name: key + type: + scalar: string + default: "" + - name: operator + type: + scalar: string + default: "" + - name: values + type: + list: + elementType: + scalar: string + elementRelationship: atomic +- name: io.k8s.api.core.v1.NodeSelectorTerm + map: + fields: + - name: matchExpressions + type: + list: + elementType: + namedType: io.k8s.api.core.v1.NodeSelectorRequirement + elementRelationship: atomic + - name: matchFields + type: + list: + elementType: + namedType: io.k8s.api.core.v1.NodeSelectorRequirement + elementRelationship: atomic + elementRelationship: atomic +- name: io.k8s.api.core.v1.PodAffinity + map: + fields: + - name: preferredDuringSchedulingIgnoredDuringExecution + type: + list: + elementType: + namedType: io.k8s.api.core.v1.WeightedPodAffinityTerm + elementRelationship: atomic + - name: requiredDuringSchedulingIgnoredDuringExecution + type: + list: + elementType: + namedType: io.k8s.api.core.v1.PodAffinityTerm + elementRelationship: atomic +- name: io.k8s.api.core.v1.PodAffinityTerm + map: + fields: + - name: labelSelector + type: + namedType: io.k8s.apimachinery.pkg.apis.meta.v1.LabelSelector + - name: matchLabelKeys + type: + list: + elementType: + scalar: string + elementRelationship: atomic + - name: mismatchLabelKeys + type: + list: + elementType: + scalar: string + elementRelationship: atomic + - name: namespaceSelector + type: + namedType: io.k8s.apimachinery.pkg.apis.meta.v1.LabelSelector + - name: namespaces + type: + list: + elementType: + scalar: string + elementRelationship: atomic + - name: topologyKey + type: + scalar: string + default: "" +- name: io.k8s.api.core.v1.PodAntiAffinity + map: + fields: + - name: preferredDuringSchedulingIgnoredDuringExecution + type: + list: + elementType: + namedType: io.k8s.api.core.v1.WeightedPodAffinityTerm + elementRelationship: atomic + - name: requiredDuringSchedulingIgnoredDuringExecution + type: + list: + elementType: + namedType: io.k8s.api.core.v1.PodAffinityTerm + elementRelationship: atomic +- name: io.k8s.api.core.v1.PreferredSchedulingTerm + map: + fields: + - name: preference + type: + namedType: io.k8s.api.core.v1.NodeSelectorTerm + default: {} + - name: weight + type: + scalar: numeric + default: 0 +- name: io.k8s.api.core.v1.SELinuxOptions + map: + fields: + - name: level + type: + scalar: string + - name: role + type: + scalar: string + - name: type + type: + scalar: string + - name: user + type: + scalar: string +- name: io.k8s.api.core.v1.SeccompProfile + map: + fields: + - name: localhostProfile + type: + scalar: string + - name: type + type: + scalar: string + default: "" + unions: + - discriminator: type + fields: + - fieldName: localhostProfile + discriminatorValue: LocalhostProfile +- name: io.k8s.api.core.v1.Sysctl + map: + fields: + - name: name + type: + scalar: string + default: "" + - name: value + type: + scalar: string + default: "" +- name: io.k8s.api.core.v1.Toleration + map: + fields: + - name: effect + type: + scalar: string + - name: key + type: + scalar: string + - name: operator + type: + scalar: string + - name: tolerationSeconds + type: + scalar: numeric + - name: value + type: + scalar: string +- name: io.k8s.api.core.v1.WeightedPodAffinityTerm + map: + fields: + - name: podAffinityTerm + type: + namedType: io.k8s.api.core.v1.PodAffinityTerm + default: {} + - name: weight + type: + scalar: numeric + default: 0 +- name: io.k8s.apiextensions-apiserver.pkg.apis.apiextensions.v1.JSON + scalar: untyped + list: + elementType: + namedType: __untyped_atomic_ + elementRelationship: atomic + map: + elementType: + namedType: __untyped_deduced_ + elementRelationship: separable +- name: io.k8s.apimachinery.pkg.api.resource.Quantity + scalar: untyped +- name: io.k8s.apimachinery.pkg.apis.meta.v1.Duration + scalar: string +- name: io.k8s.apimachinery.pkg.apis.meta.v1.FieldsV1 + map: + elementType: + scalar: untyped + list: + elementType: + namedType: __untyped_atomic_ + elementRelationship: atomic + map: + elementType: + namedType: __untyped_deduced_ + elementRelationship: separable +- name: io.k8s.apimachinery.pkg.apis.meta.v1.LabelSelector + map: + fields: + - name: matchExpressions + type: + list: + elementType: + namedType: io.k8s.apimachinery.pkg.apis.meta.v1.LabelSelectorRequirement + elementRelationship: atomic + - name: matchLabels + type: + map: + elementType: + scalar: string + elementRelationship: atomic +- name: io.k8s.apimachinery.pkg.apis.meta.v1.LabelSelectorRequirement + map: + fields: + - name: key + type: + scalar: string + default: "" + - name: operator + type: + scalar: string + default: "" + - name: values + type: + list: + elementType: + scalar: string + elementRelationship: atomic +- name: io.k8s.apimachinery.pkg.apis.meta.v1.ManagedFieldsEntry + map: + fields: + - name: apiVersion + type: + scalar: string + - name: fieldsType + type: + scalar: string + - name: fieldsV1 + type: + namedType: io.k8s.apimachinery.pkg.apis.meta.v1.FieldsV1 + - name: manager + type: + scalar: string + - name: operation + type: + scalar: string + - name: subresource + type: + scalar: string + - name: time + type: + namedType: io.k8s.apimachinery.pkg.apis.meta.v1.Time +- name: io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta + map: + fields: + - name: annotations + type: + map: + elementType: + scalar: string + - name: creationTimestamp + type: + namedType: io.k8s.apimachinery.pkg.apis.meta.v1.Time + - name: deletionGracePeriodSeconds + type: + scalar: numeric + - name: deletionTimestamp + type: + namedType: io.k8s.apimachinery.pkg.apis.meta.v1.Time + - name: finalizers + type: + list: + elementType: + scalar: string + elementRelationship: associative + - name: generateName + type: + scalar: string + - name: generation + type: + scalar: numeric + - name: labels + type: + map: + elementType: + scalar: string + - name: managedFields + type: + list: + elementType: + namedType: io.k8s.apimachinery.pkg.apis.meta.v1.ManagedFieldsEntry + elementRelationship: atomic + - name: name + type: + scalar: string + - name: namespace + type: + scalar: string + - name: ownerReferences + type: + list: + elementType: + namedType: io.k8s.apimachinery.pkg.apis.meta.v1.OwnerReference + elementRelationship: associative + keys: + - uid + - name: resourceVersion + type: + scalar: string + - name: selfLink + type: + scalar: string + - name: uid + type: + scalar: string +- name: io.k8s.apimachinery.pkg.apis.meta.v1.OwnerReference + map: + fields: + - name: apiVersion + type: + scalar: string + default: "" + - name: blockOwnerDeletion + type: + scalar: boolean + - name: controller + type: + scalar: boolean + - name: kind + type: + scalar: string + default: "" + - name: name + type: + scalar: string + default: "" + - name: uid + type: + scalar: string + default: "" + elementRelationship: atomic +- name: io.k8s.apimachinery.pkg.apis.meta.v1.Time + scalar: untyped +- name: io.k8s.sigs.gateway-api.apis.v1.ParentReference + map: + fields: + - name: group + type: + scalar: string + - name: kind + type: + scalar: string + - name: name + type: + scalar: string + default: "" + - name: namespace + type: + scalar: string + - name: port + type: + scalar: numeric + - name: sectionName + type: + scalar: string +- name: __untyped_atomic_ + scalar: untyped + list: + elementType: + namedType: __untyped_atomic_ + elementRelationship: atomic + map: + elementType: + namedType: __untyped_atomic_ + elementRelationship: atomic +- name: __untyped_deduced_ + scalar: untyped + list: + elementType: + namedType: __untyped_atomic_ + elementRelationship: atomic + map: + elementType: + namedType: __untyped_deduced_ + elementRelationship: separable +`) diff --git a/pkg/client/applyconfigurations/meta/v1/issuerreference.go b/pkg/client/applyconfigurations/meta/v1/issuerreference.go new file mode 100644 index 00000000000..3291e5358ac --- /dev/null +++ b/pkg/client/applyconfigurations/meta/v1/issuerreference.go @@ -0,0 +1,57 @@ +/* +Copyright 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. +*/ + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1 + +// IssuerReferenceApplyConfiguration represents a declarative configuration of the IssuerReference type for use +// with apply. +type IssuerReferenceApplyConfiguration struct { + Name *string `json:"name,omitempty"` + Kind *string `json:"kind,omitempty"` + Group *string `json:"group,omitempty"` +} + +// IssuerReferenceApplyConfiguration constructs a declarative configuration of the IssuerReference type for use with +// apply. +func IssuerReference() *IssuerReferenceApplyConfiguration { + return &IssuerReferenceApplyConfiguration{} +} + +// WithName sets the Name field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Name field is set to the value of the last call. +func (b *IssuerReferenceApplyConfiguration) WithName(value string) *IssuerReferenceApplyConfiguration { + b.Name = &value + return b +} + +// WithKind sets the Kind field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Kind field is set to the value of the last call. +func (b *IssuerReferenceApplyConfiguration) WithKind(value string) *IssuerReferenceApplyConfiguration { + b.Kind = &value + return b +} + +// WithGroup sets the Group field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Group field is set to the value of the last call. +func (b *IssuerReferenceApplyConfiguration) WithGroup(value string) *IssuerReferenceApplyConfiguration { + b.Group = &value + return b +} diff --git a/pkg/client/applyconfigurations/meta/v1/localobjectreference.go b/pkg/client/applyconfigurations/meta/v1/localobjectreference.go new file mode 100644 index 00000000000..a4829c7194b --- /dev/null +++ b/pkg/client/applyconfigurations/meta/v1/localobjectreference.go @@ -0,0 +1,39 @@ +/* +Copyright 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. +*/ + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1 + +// LocalObjectReferenceApplyConfiguration represents a declarative configuration of the LocalObjectReference type for use +// with apply. +type LocalObjectReferenceApplyConfiguration struct { + Name *string `json:"name,omitempty"` +} + +// LocalObjectReferenceApplyConfiguration constructs a declarative configuration of the LocalObjectReference type for use with +// apply. +func LocalObjectReference() *LocalObjectReferenceApplyConfiguration { + return &LocalObjectReferenceApplyConfiguration{} +} + +// WithName sets the Name field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Name field is set to the value of the last call. +func (b *LocalObjectReferenceApplyConfiguration) WithName(value string) *LocalObjectReferenceApplyConfiguration { + b.Name = &value + return b +} diff --git a/pkg/client/applyconfigurations/meta/v1/secretkeyselector.go b/pkg/client/applyconfigurations/meta/v1/secretkeyselector.go new file mode 100644 index 00000000000..9e9cb6d2dfa --- /dev/null +++ b/pkg/client/applyconfigurations/meta/v1/secretkeyselector.go @@ -0,0 +1,48 @@ +/* +Copyright 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. +*/ + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1 + +// SecretKeySelectorApplyConfiguration represents a declarative configuration of the SecretKeySelector type for use +// with apply. +type SecretKeySelectorApplyConfiguration struct { + LocalObjectReferenceApplyConfiguration `json:",inline"` + Key *string `json:"key,omitempty"` +} + +// SecretKeySelectorApplyConfiguration constructs a declarative configuration of the SecretKeySelector type for use with +// apply. +func SecretKeySelector() *SecretKeySelectorApplyConfiguration { + return &SecretKeySelectorApplyConfiguration{} +} + +// WithName sets the Name field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Name field is set to the value of the last call. +func (b *SecretKeySelectorApplyConfiguration) WithName(value string) *SecretKeySelectorApplyConfiguration { + b.LocalObjectReferenceApplyConfiguration.Name = &value + return b +} + +// WithKey sets the Key field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Key field is set to the value of the last call. +func (b *SecretKeySelectorApplyConfiguration) WithKey(value string) *SecretKeySelectorApplyConfiguration { + b.Key = &value + return b +} diff --git a/pkg/client/applyconfigurations/utils.go b/pkg/client/applyconfigurations/utils.go new file mode 100644 index 00000000000..0729e915ae9 --- /dev/null +++ b/pkg/client/applyconfigurations/utils.go @@ -0,0 +1,200 @@ +/* +Copyright 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. +*/ + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package applyconfigurations + +import ( + v1 "github.com/cert-manager/cert-manager/pkg/apis/acme/v1" + certmanagerv1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" + metav1 "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" + acmev1 "github.com/cert-manager/cert-manager/pkg/client/applyconfigurations/acme/v1" + applyconfigurationscertmanagerv1 "github.com/cert-manager/cert-manager/pkg/client/applyconfigurations/certmanager/v1" + internal "github.com/cert-manager/cert-manager/pkg/client/applyconfigurations/internal" + applyconfigurationsmetav1 "github.com/cert-manager/cert-manager/pkg/client/applyconfigurations/meta/v1" + runtime "k8s.io/apimachinery/pkg/runtime" + schema "k8s.io/apimachinery/pkg/runtime/schema" + managedfields "k8s.io/apimachinery/pkg/util/managedfields" +) + +// ForKind returns an apply configuration type for the given GroupVersionKind, or nil if no +// apply configuration type exists for the given GroupVersionKind. +func ForKind(kind schema.GroupVersionKind) interface{} { + switch kind { + // Group=acme.cert-manager.io, Version=v1 + case v1.SchemeGroupVersion.WithKind("ACMEAuthorization"): + return &acmev1.ACMEAuthorizationApplyConfiguration{} + case v1.SchemeGroupVersion.WithKind("ACMEChallenge"): + return &acmev1.ACMEChallengeApplyConfiguration{} + case v1.SchemeGroupVersion.WithKind("ACMEChallengeSolver"): + return &acmev1.ACMEChallengeSolverApplyConfiguration{} + case v1.SchemeGroupVersion.WithKind("ACMEChallengeSolverDNS01"): + return &acmev1.ACMEChallengeSolverDNS01ApplyConfiguration{} + case v1.SchemeGroupVersion.WithKind("ACMEChallengeSolverHTTP01"): + return &acmev1.ACMEChallengeSolverHTTP01ApplyConfiguration{} + case v1.SchemeGroupVersion.WithKind("ACMEChallengeSolverHTTP01GatewayHTTPRoute"): + return &acmev1.ACMEChallengeSolverHTTP01GatewayHTTPRouteApplyConfiguration{} + case v1.SchemeGroupVersion.WithKind("ACMEChallengeSolverHTTP01Ingress"): + return &acmev1.ACMEChallengeSolverHTTP01IngressApplyConfiguration{} + case v1.SchemeGroupVersion.WithKind("ACMEChallengeSolverHTTP01IngressObjectMeta"): + return &acmev1.ACMEChallengeSolverHTTP01IngressObjectMetaApplyConfiguration{} + case v1.SchemeGroupVersion.WithKind("ACMEChallengeSolverHTTP01IngressPodObjectMeta"): + return &acmev1.ACMEChallengeSolverHTTP01IngressPodObjectMetaApplyConfiguration{} + case v1.SchemeGroupVersion.WithKind("ACMEChallengeSolverHTTP01IngressPodResources"): + return &acmev1.ACMEChallengeSolverHTTP01IngressPodResourcesApplyConfiguration{} + case v1.SchemeGroupVersion.WithKind("ACMEChallengeSolverHTTP01IngressPodSecurityContext"): + return &acmev1.ACMEChallengeSolverHTTP01IngressPodSecurityContextApplyConfiguration{} + case v1.SchemeGroupVersion.WithKind("ACMEChallengeSolverHTTP01IngressPodSpec"): + return &acmev1.ACMEChallengeSolverHTTP01IngressPodSpecApplyConfiguration{} + case v1.SchemeGroupVersion.WithKind("ACMEChallengeSolverHTTP01IngressPodTemplate"): + return &acmev1.ACMEChallengeSolverHTTP01IngressPodTemplateApplyConfiguration{} + case v1.SchemeGroupVersion.WithKind("ACMEChallengeSolverHTTP01IngressTemplate"): + return &acmev1.ACMEChallengeSolverHTTP01IngressTemplateApplyConfiguration{} + case v1.SchemeGroupVersion.WithKind("ACMEExternalAccountBinding"): + return &acmev1.ACMEExternalAccountBindingApplyConfiguration{} + case v1.SchemeGroupVersion.WithKind("ACMEIssuer"): + return &acmev1.ACMEIssuerApplyConfiguration{} + case v1.SchemeGroupVersion.WithKind("ACMEIssuerDNS01ProviderAcmeDNS"): + return &acmev1.ACMEIssuerDNS01ProviderAcmeDNSApplyConfiguration{} + case v1.SchemeGroupVersion.WithKind("ACMEIssuerDNS01ProviderAkamai"): + return &acmev1.ACMEIssuerDNS01ProviderAkamaiApplyConfiguration{} + case v1.SchemeGroupVersion.WithKind("ACMEIssuerDNS01ProviderAzureDNS"): + return &acmev1.ACMEIssuerDNS01ProviderAzureDNSApplyConfiguration{} + case v1.SchemeGroupVersion.WithKind("ACMEIssuerDNS01ProviderCloudDNS"): + return &acmev1.ACMEIssuerDNS01ProviderCloudDNSApplyConfiguration{} + case v1.SchemeGroupVersion.WithKind("ACMEIssuerDNS01ProviderCloudflare"): + return &acmev1.ACMEIssuerDNS01ProviderCloudflareApplyConfiguration{} + case v1.SchemeGroupVersion.WithKind("ACMEIssuerDNS01ProviderDigitalOcean"): + return &acmev1.ACMEIssuerDNS01ProviderDigitalOceanApplyConfiguration{} + case v1.SchemeGroupVersion.WithKind("ACMEIssuerDNS01ProviderRFC2136"): + return &acmev1.ACMEIssuerDNS01ProviderRFC2136ApplyConfiguration{} + case v1.SchemeGroupVersion.WithKind("ACMEIssuerDNS01ProviderRoute53"): + return &acmev1.ACMEIssuerDNS01ProviderRoute53ApplyConfiguration{} + case v1.SchemeGroupVersion.WithKind("ACMEIssuerDNS01ProviderWebhook"): + return &acmev1.ACMEIssuerDNS01ProviderWebhookApplyConfiguration{} + case v1.SchemeGroupVersion.WithKind("ACMEIssuerStatus"): + return &acmev1.ACMEIssuerStatusApplyConfiguration{} + case v1.SchemeGroupVersion.WithKind("AzureManagedIdentity"): + return &acmev1.AzureManagedIdentityApplyConfiguration{} + case v1.SchemeGroupVersion.WithKind("CertificateDNSNameSelector"): + return &acmev1.CertificateDNSNameSelectorApplyConfiguration{} + case v1.SchemeGroupVersion.WithKind("Challenge"): + return &acmev1.ChallengeApplyConfiguration{} + case v1.SchemeGroupVersion.WithKind("ChallengeSpec"): + return &acmev1.ChallengeSpecApplyConfiguration{} + case v1.SchemeGroupVersion.WithKind("ChallengeStatus"): + return &acmev1.ChallengeStatusApplyConfiguration{} + case v1.SchemeGroupVersion.WithKind("Order"): + return &acmev1.OrderApplyConfiguration{} + case v1.SchemeGroupVersion.WithKind("OrderSpec"): + return &acmev1.OrderSpecApplyConfiguration{} + case v1.SchemeGroupVersion.WithKind("OrderStatus"): + return &acmev1.OrderStatusApplyConfiguration{} + case v1.SchemeGroupVersion.WithKind("Route53Auth"): + return &acmev1.Route53AuthApplyConfiguration{} + case v1.SchemeGroupVersion.WithKind("Route53KubernetesAuth"): + return &acmev1.Route53KubernetesAuthApplyConfiguration{} + case v1.SchemeGroupVersion.WithKind("ServiceAccountRef"): + return &acmev1.ServiceAccountRefApplyConfiguration{} + + // Group=cert-manager.io, Version=v1 + case certmanagerv1.SchemeGroupVersion.WithKind("CAIssuer"): + return &applyconfigurationscertmanagerv1.CAIssuerApplyConfiguration{} + case certmanagerv1.SchemeGroupVersion.WithKind("Certificate"): + return &applyconfigurationscertmanagerv1.CertificateApplyConfiguration{} + case certmanagerv1.SchemeGroupVersion.WithKind("CertificateAdditionalOutputFormat"): + return &applyconfigurationscertmanagerv1.CertificateAdditionalOutputFormatApplyConfiguration{} + case certmanagerv1.SchemeGroupVersion.WithKind("CertificateCondition"): + return &applyconfigurationscertmanagerv1.CertificateConditionApplyConfiguration{} + case certmanagerv1.SchemeGroupVersion.WithKind("CertificateKeystores"): + return &applyconfigurationscertmanagerv1.CertificateKeystoresApplyConfiguration{} + case certmanagerv1.SchemeGroupVersion.WithKind("CertificatePrivateKey"): + return &applyconfigurationscertmanagerv1.CertificatePrivateKeyApplyConfiguration{} + case certmanagerv1.SchemeGroupVersion.WithKind("CertificateRequest"): + return &applyconfigurationscertmanagerv1.CertificateRequestApplyConfiguration{} + case certmanagerv1.SchemeGroupVersion.WithKind("CertificateRequestCondition"): + return &applyconfigurationscertmanagerv1.CertificateRequestConditionApplyConfiguration{} + case certmanagerv1.SchemeGroupVersion.WithKind("CertificateRequestSpec"): + return &applyconfigurationscertmanagerv1.CertificateRequestSpecApplyConfiguration{} + case certmanagerv1.SchemeGroupVersion.WithKind("CertificateRequestStatus"): + return &applyconfigurationscertmanagerv1.CertificateRequestStatusApplyConfiguration{} + case certmanagerv1.SchemeGroupVersion.WithKind("CertificateSecretTemplate"): + return &applyconfigurationscertmanagerv1.CertificateSecretTemplateApplyConfiguration{} + case certmanagerv1.SchemeGroupVersion.WithKind("CertificateSpec"): + return &applyconfigurationscertmanagerv1.CertificateSpecApplyConfiguration{} + case certmanagerv1.SchemeGroupVersion.WithKind("CertificateStatus"): + return &applyconfigurationscertmanagerv1.CertificateStatusApplyConfiguration{} + case certmanagerv1.SchemeGroupVersion.WithKind("ClusterIssuer"): + return &applyconfigurationscertmanagerv1.ClusterIssuerApplyConfiguration{} + case certmanagerv1.SchemeGroupVersion.WithKind("Issuer"): + return &applyconfigurationscertmanagerv1.IssuerApplyConfiguration{} + case certmanagerv1.SchemeGroupVersion.WithKind("IssuerCondition"): + return &applyconfigurationscertmanagerv1.IssuerConditionApplyConfiguration{} + case certmanagerv1.SchemeGroupVersion.WithKind("IssuerConfig"): + return &applyconfigurationscertmanagerv1.IssuerConfigApplyConfiguration{} + case certmanagerv1.SchemeGroupVersion.WithKind("IssuerSpec"): + return &applyconfigurationscertmanagerv1.IssuerSpecApplyConfiguration{} + case certmanagerv1.SchemeGroupVersion.WithKind("IssuerStatus"): + return &applyconfigurationscertmanagerv1.IssuerStatusApplyConfiguration{} + case certmanagerv1.SchemeGroupVersion.WithKind("JKSKeystore"): + return &applyconfigurationscertmanagerv1.JKSKeystoreApplyConfiguration{} + case certmanagerv1.SchemeGroupVersion.WithKind("NameConstraintItem"): + return &applyconfigurationscertmanagerv1.NameConstraintItemApplyConfiguration{} + case certmanagerv1.SchemeGroupVersion.WithKind("NameConstraints"): + return &applyconfigurationscertmanagerv1.NameConstraintsApplyConfiguration{} + case certmanagerv1.SchemeGroupVersion.WithKind("OtherName"): + return &applyconfigurationscertmanagerv1.OtherNameApplyConfiguration{} + case certmanagerv1.SchemeGroupVersion.WithKind("PKCS12Keystore"): + return &applyconfigurationscertmanagerv1.PKCS12KeystoreApplyConfiguration{} + case certmanagerv1.SchemeGroupVersion.WithKind("SelfSignedIssuer"): + return &applyconfigurationscertmanagerv1.SelfSignedIssuerApplyConfiguration{} + case certmanagerv1.SchemeGroupVersion.WithKind("ServiceAccountRef"): + return &applyconfigurationscertmanagerv1.ServiceAccountRefApplyConfiguration{} + case certmanagerv1.SchemeGroupVersion.WithKind("VaultAppRole"): + return &applyconfigurationscertmanagerv1.VaultAppRoleApplyConfiguration{} + case certmanagerv1.SchemeGroupVersion.WithKind("VaultAuth"): + return &applyconfigurationscertmanagerv1.VaultAuthApplyConfiguration{} + case certmanagerv1.SchemeGroupVersion.WithKind("VaultClientCertificateAuth"): + return &applyconfigurationscertmanagerv1.VaultClientCertificateAuthApplyConfiguration{} + case certmanagerv1.SchemeGroupVersion.WithKind("VaultIssuer"): + return &applyconfigurationscertmanagerv1.VaultIssuerApplyConfiguration{} + case certmanagerv1.SchemeGroupVersion.WithKind("VaultKubernetesAuth"): + return &applyconfigurationscertmanagerv1.VaultKubernetesAuthApplyConfiguration{} + case certmanagerv1.SchemeGroupVersion.WithKind("VenafiCloud"): + return &applyconfigurationscertmanagerv1.VenafiCloudApplyConfiguration{} + case certmanagerv1.SchemeGroupVersion.WithKind("VenafiIssuer"): + return &applyconfigurationscertmanagerv1.VenafiIssuerApplyConfiguration{} + case certmanagerv1.SchemeGroupVersion.WithKind("VenafiTPP"): + return &applyconfigurationscertmanagerv1.VenafiTPPApplyConfiguration{} + case certmanagerv1.SchemeGroupVersion.WithKind("X509Subject"): + return &applyconfigurationscertmanagerv1.X509SubjectApplyConfiguration{} + + // Group=meta, Version=v1 + case metav1.SchemeGroupVersion.WithKind("IssuerReference"): + return &applyconfigurationsmetav1.IssuerReferenceApplyConfiguration{} + case metav1.SchemeGroupVersion.WithKind("LocalObjectReference"): + return &applyconfigurationsmetav1.LocalObjectReferenceApplyConfiguration{} + case metav1.SchemeGroupVersion.WithKind("SecretKeySelector"): + return &applyconfigurationsmetav1.SecretKeySelectorApplyConfiguration{} + + } + return nil +} + +func NewTypeConverter(scheme *runtime.Scheme) managedfields.TypeConverter { + return managedfields.NewSchemeTypeConverter(scheme, internal.Parser()) +} diff --git a/pkg/client/clientset/versioned/clientset.go b/pkg/client/clientset/versioned/clientset.go index 519ef01d296..1ad7245c7f5 100644 --- a/pkg/client/clientset/versioned/clientset.go +++ b/pkg/client/clientset/versioned/clientset.go @@ -19,8 +19,8 @@ limitations under the License. package versioned import ( - "fmt" - "net/http" + fmt "fmt" + http "net/http" acmev1 "github.com/cert-manager/cert-manager/pkg/client/clientset/versioned/typed/acme/v1" certmanagerv1 "github.com/cert-manager/cert-manager/pkg/client/clientset/versioned/typed/certmanager/v1" @@ -35,8 +35,7 @@ type Interface interface { CertmanagerV1() certmanagerv1.CertmanagerV1Interface } -// Clientset contains the clients for groups. Each group has exactly one -// version included in a Clientset. +// Clientset contains the clients for groups. type Clientset struct { *discovery.DiscoveryClient acmeV1 *acmev1.AcmeV1Client diff --git a/pkg/client/clientset/versioned/fake/clientset_generated.go b/pkg/client/clientset/versioned/fake/clientset_generated.go index ecb72c06d76..74a609551d8 100644 --- a/pkg/client/clientset/versioned/fake/clientset_generated.go +++ b/pkg/client/clientset/versioned/fake/clientset_generated.go @@ -19,11 +19,13 @@ limitations under the License. package fake import ( + applyconfigurations "github.com/cert-manager/cert-manager/pkg/client/applyconfigurations" clientset "github.com/cert-manager/cert-manager/pkg/client/clientset/versioned" acmev1 "github.com/cert-manager/cert-manager/pkg/client/clientset/versioned/typed/acme/v1" fakeacmev1 "github.com/cert-manager/cert-manager/pkg/client/clientset/versioned/typed/acme/v1/fake" certmanagerv1 "github.com/cert-manager/cert-manager/pkg/client/clientset/versioned/typed/certmanager/v1" fakecertmanagerv1 "github.com/cert-manager/cert-manager/pkg/client/clientset/versioned/typed/certmanager/v1/fake" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/watch" "k8s.io/client-go/discovery" @@ -33,8 +35,12 @@ import ( // NewSimpleClientset returns a clientset that will respond with the provided objects. // It's backed by a very simple object tracker that processes creates, updates and deletions as-is, -// without applying any validations and/or defaults. It shouldn't be considered a replacement +// without applying any field management, validations and/or defaults. It shouldn't be considered a replacement // for a real clientset and is mostly useful in simple unit tests. +// +// DEPRECATED: NewClientset replaces this with support for field management, which significantly improves +// server side apply testing. NewClientset is only available when apply configurations are generated (e.g. +// via --with-applyconfig). func NewSimpleClientset(objects ...runtime.Object) *Clientset { o := testing.NewObjectTracker(scheme, codecs.UniversalDecoder()) for _, obj := range objects { @@ -47,9 +53,13 @@ func NewSimpleClientset(objects ...runtime.Object) *Clientset { cs.discovery = &fakediscovery.FakeDiscovery{Fake: &cs.Fake} cs.AddReactor("*", "*", testing.ObjectReaction(o)) cs.AddWatchReactor("*", func(action testing.Action) (handled bool, ret watch.Interface, err error) { + var opts metav1.ListOptions + if watchActcion, ok := action.(testing.WatchActionImpl); ok { + opts = watchActcion.ListOptions + } gvr := action.GetResource() ns := action.GetNamespace() - watch, err := o.Watch(gvr, ns) + watch, err := o.Watch(gvr, ns, opts) if err != nil { return false, nil, err } @@ -76,6 +86,42 @@ func (c *Clientset) Tracker() testing.ObjectTracker { return c.tracker } +// NewClientset returns a clientset that will respond with the provided objects. +// It's backed by a very simple object tracker that processes creates, updates and deletions as-is, +// without applying any validations and/or defaults. It shouldn't be considered a replacement +// for a real clientset and is mostly useful in simple unit tests. +func NewClientset(objects ...runtime.Object) *Clientset { + o := testing.NewFieldManagedObjectTracker( + scheme, + codecs.UniversalDecoder(), + applyconfigurations.NewTypeConverter(scheme), + ) + for _, obj := range objects { + if err := o.Add(obj); err != nil { + panic(err) + } + } + + cs := &Clientset{tracker: o} + cs.discovery = &fakediscovery.FakeDiscovery{Fake: &cs.Fake} + cs.AddReactor("*", "*", testing.ObjectReaction(o)) + cs.AddWatchReactor("*", func(action testing.Action) (handled bool, ret watch.Interface, err error) { + var opts metav1.ListOptions + if watchAction, ok := action.(testing.WatchActionImpl); ok { + opts = watchAction.ListOptions + } + gvr := action.GetResource() + ns := action.GetNamespace() + watch, err := o.Watch(gvr, ns, opts) + if err != nil { + return false, nil, err + } + return true, watch, nil + }) + + return cs +} + var ( _ clientset.Interface = &Clientset{} _ testing.FakeClient = &Clientset{} diff --git a/pkg/client/clientset/versioned/typed/acme/v1/acme_client.go b/pkg/client/clientset/versioned/typed/acme/v1/acme_client.go index ff077b66046..bec0af70c79 100644 --- a/pkg/client/clientset/versioned/typed/acme/v1/acme_client.go +++ b/pkg/client/clientset/versioned/typed/acme/v1/acme_client.go @@ -19,10 +19,10 @@ limitations under the License. package v1 import ( - "net/http" + http "net/http" - v1 "github.com/cert-manager/cert-manager/pkg/apis/acme/v1" - "github.com/cert-manager/cert-manager/pkg/client/clientset/versioned/scheme" + acmev1 "github.com/cert-manager/cert-manager/pkg/apis/acme/v1" + scheme "github.com/cert-manager/cert-manager/pkg/client/clientset/versioned/scheme" rest "k8s.io/client-go/rest" ) @@ -50,9 +50,7 @@ func (c *AcmeV1Client) Orders(namespace string) OrderInterface { // where httpClient was generated with rest.HTTPClientFor(c). func NewForConfig(c *rest.Config) (*AcmeV1Client, error) { config := *c - if err := setConfigDefaults(&config); err != nil { - return nil, err - } + setConfigDefaults(&config) httpClient, err := rest.HTTPClientFor(&config) if err != nil { return nil, err @@ -64,9 +62,7 @@ func NewForConfig(c *rest.Config) (*AcmeV1Client, error) { // Note the http client provided takes precedence over the configured transport values. func NewForConfigAndClient(c *rest.Config, h *http.Client) (*AcmeV1Client, error) { config := *c - if err := setConfigDefaults(&config); err != nil { - return nil, err - } + setConfigDefaults(&config) client, err := rest.RESTClientForConfigAndClient(&config, h) if err != nil { return nil, err @@ -89,17 +85,15 @@ func New(c rest.Interface) *AcmeV1Client { return &AcmeV1Client{c} } -func setConfigDefaults(config *rest.Config) error { - gv := v1.SchemeGroupVersion +func setConfigDefaults(config *rest.Config) { + gv := acmev1.SchemeGroupVersion config.GroupVersion = &gv config.APIPath = "/apis" - config.NegotiatedSerializer = scheme.Codecs.WithoutConversion() + config.NegotiatedSerializer = rest.CodecFactoryForGeneratedClient(scheme.Scheme, scheme.Codecs).WithoutConversion() if config.UserAgent == "" { config.UserAgent = rest.DefaultKubernetesUserAgent() } - - return nil } // RESTClient returns a RESTClient that is used to communicate diff --git a/pkg/client/clientset/versioned/typed/acme/v1/challenge.go b/pkg/client/clientset/versioned/typed/acme/v1/challenge.go index 225f3983fbb..82d89913920 100644 --- a/pkg/client/clientset/versioned/typed/acme/v1/challenge.go +++ b/pkg/client/clientset/versioned/typed/acme/v1/challenge.go @@ -19,15 +19,15 @@ limitations under the License. package v1 import ( - "context" - "time" + context "context" - v1 "github.com/cert-manager/cert-manager/pkg/apis/acme/v1" + acmev1 "github.com/cert-manager/cert-manager/pkg/apis/acme/v1" + applyconfigurationsacmev1 "github.com/cert-manager/cert-manager/pkg/client/applyconfigurations/acme/v1" scheme "github.com/cert-manager/cert-manager/pkg/client/clientset/versioned/scheme" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" types "k8s.io/apimachinery/pkg/types" watch "k8s.io/apimachinery/pkg/watch" - rest "k8s.io/client-go/rest" + gentype "k8s.io/client-go/gentype" ) // ChallengesGetter has a method to return a ChallengeInterface. @@ -38,158 +38,37 @@ type ChallengesGetter interface { // ChallengeInterface has methods to work with Challenge resources. type ChallengeInterface interface { - Create(ctx context.Context, challenge *v1.Challenge, opts metav1.CreateOptions) (*v1.Challenge, error) - Update(ctx context.Context, challenge *v1.Challenge, opts metav1.UpdateOptions) (*v1.Challenge, error) - UpdateStatus(ctx context.Context, challenge *v1.Challenge, opts metav1.UpdateOptions) (*v1.Challenge, error) + Create(ctx context.Context, challenge *acmev1.Challenge, opts metav1.CreateOptions) (*acmev1.Challenge, error) + Update(ctx context.Context, challenge *acmev1.Challenge, opts metav1.UpdateOptions) (*acmev1.Challenge, error) + // Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). + UpdateStatus(ctx context.Context, challenge *acmev1.Challenge, opts metav1.UpdateOptions) (*acmev1.Challenge, error) Delete(ctx context.Context, name string, opts metav1.DeleteOptions) error DeleteCollection(ctx context.Context, opts metav1.DeleteOptions, listOpts metav1.ListOptions) error - Get(ctx context.Context, name string, opts metav1.GetOptions) (*v1.Challenge, error) - List(ctx context.Context, opts metav1.ListOptions) (*v1.ChallengeList, error) + Get(ctx context.Context, name string, opts metav1.GetOptions) (*acmev1.Challenge, error) + List(ctx context.Context, opts metav1.ListOptions) (*acmev1.ChallengeList, error) Watch(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error) - Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts metav1.PatchOptions, subresources ...string) (result *v1.Challenge, err error) + Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts metav1.PatchOptions, subresources ...string) (result *acmev1.Challenge, err error) + Apply(ctx context.Context, challenge *applyconfigurationsacmev1.ChallengeApplyConfiguration, opts metav1.ApplyOptions) (result *acmev1.Challenge, err error) + // Add a +genclient:noStatus comment above the type to avoid generating ApplyStatus(). + ApplyStatus(ctx context.Context, challenge *applyconfigurationsacmev1.ChallengeApplyConfiguration, opts metav1.ApplyOptions) (result *acmev1.Challenge, err error) ChallengeExpansion } // challenges implements ChallengeInterface type challenges struct { - client rest.Interface - ns string + *gentype.ClientWithListAndApply[*acmev1.Challenge, *acmev1.ChallengeList, *applyconfigurationsacmev1.ChallengeApplyConfiguration] } // newChallenges returns a Challenges func newChallenges(c *AcmeV1Client, namespace string) *challenges { return &challenges{ - client: c.RESTClient(), - ns: namespace, + gentype.NewClientWithListAndApply[*acmev1.Challenge, *acmev1.ChallengeList, *applyconfigurationsacmev1.ChallengeApplyConfiguration]( + "challenges", + c.RESTClient(), + scheme.ParameterCodec, + namespace, + func() *acmev1.Challenge { return &acmev1.Challenge{} }, + func() *acmev1.ChallengeList { return &acmev1.ChallengeList{} }, + ), } } - -// Get takes name of the challenge, and returns the corresponding challenge object, and an error if there is any. -func (c *challenges) Get(ctx context.Context, name string, options metav1.GetOptions) (result *v1.Challenge, err error) { - result = &v1.Challenge{} - err = c.client.Get(). - Namespace(c.ns). - Resource("challenges"). - Name(name). - VersionedParams(&options, scheme.ParameterCodec). - Do(ctx). - Into(result) - return -} - -// List takes label and field selectors, and returns the list of Challenges that match those selectors. -func (c *challenges) List(ctx context.Context, opts metav1.ListOptions) (result *v1.ChallengeList, err error) { - var timeout time.Duration - if opts.TimeoutSeconds != nil { - timeout = time.Duration(*opts.TimeoutSeconds) * time.Second - } - result = &v1.ChallengeList{} - err = c.client.Get(). - Namespace(c.ns). - Resource("challenges"). - VersionedParams(&opts, scheme.ParameterCodec). - Timeout(timeout). - Do(ctx). - Into(result) - return -} - -// Watch returns a watch.Interface that watches the requested challenges. -func (c *challenges) Watch(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error) { - var timeout time.Duration - if opts.TimeoutSeconds != nil { - timeout = time.Duration(*opts.TimeoutSeconds) * time.Second - } - opts.Watch = true - return c.client.Get(). - Namespace(c.ns). - Resource("challenges"). - VersionedParams(&opts, scheme.ParameterCodec). - Timeout(timeout). - Watch(ctx) -} - -// Create takes the representation of a challenge and creates it. Returns the server's representation of the challenge, and an error, if there is any. -func (c *challenges) Create(ctx context.Context, challenge *v1.Challenge, opts metav1.CreateOptions) (result *v1.Challenge, err error) { - result = &v1.Challenge{} - err = c.client.Post(). - Namespace(c.ns). - Resource("challenges"). - VersionedParams(&opts, scheme.ParameterCodec). - Body(challenge). - Do(ctx). - Into(result) - return -} - -// Update takes the representation of a challenge and updates it. Returns the server's representation of the challenge, and an error, if there is any. -func (c *challenges) Update(ctx context.Context, challenge *v1.Challenge, opts metav1.UpdateOptions) (result *v1.Challenge, err error) { - result = &v1.Challenge{} - err = c.client.Put(). - Namespace(c.ns). - Resource("challenges"). - Name(challenge.Name). - VersionedParams(&opts, scheme.ParameterCodec). - Body(challenge). - Do(ctx). - Into(result) - return -} - -// UpdateStatus was generated because the type contains a Status member. -// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). -func (c *challenges) UpdateStatus(ctx context.Context, challenge *v1.Challenge, opts metav1.UpdateOptions) (result *v1.Challenge, err error) { - result = &v1.Challenge{} - err = c.client.Put(). - Namespace(c.ns). - Resource("challenges"). - Name(challenge.Name). - SubResource("status"). - VersionedParams(&opts, scheme.ParameterCodec). - Body(challenge). - Do(ctx). - Into(result) - return -} - -// Delete takes name of the challenge and deletes it. Returns an error if one occurs. -func (c *challenges) Delete(ctx context.Context, name string, opts metav1.DeleteOptions) error { - return c.client.Delete(). - Namespace(c.ns). - Resource("challenges"). - Name(name). - Body(&opts). - Do(ctx). - Error() -} - -// DeleteCollection deletes a collection of objects. -func (c *challenges) DeleteCollection(ctx context.Context, opts metav1.DeleteOptions, listOpts metav1.ListOptions) error { - var timeout time.Duration - if listOpts.TimeoutSeconds != nil { - timeout = time.Duration(*listOpts.TimeoutSeconds) * time.Second - } - return c.client.Delete(). - Namespace(c.ns). - Resource("challenges"). - VersionedParams(&listOpts, scheme.ParameterCodec). - Timeout(timeout). - Body(&opts). - Do(ctx). - Error() -} - -// Patch applies the patch and returns the patched challenge. -func (c *challenges) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts metav1.PatchOptions, subresources ...string) (result *v1.Challenge, err error) { - result = &v1.Challenge{} - err = c.client.Patch(pt). - Namespace(c.ns). - Resource("challenges"). - Name(name). - SubResource(subresources...). - VersionedParams(&opts, scheme.ParameterCodec). - Body(data). - Do(ctx). - Into(result) - return -} diff --git a/pkg/client/clientset/versioned/typed/acme/v1/fake/fake_acme_client.go b/pkg/client/clientset/versioned/typed/acme/v1/fake/fake_acme_client.go index 938de918f83..be8d81f6d66 100644 --- a/pkg/client/clientset/versioned/typed/acme/v1/fake/fake_acme_client.go +++ b/pkg/client/clientset/versioned/typed/acme/v1/fake/fake_acme_client.go @@ -29,11 +29,11 @@ type FakeAcmeV1 struct { } func (c *FakeAcmeV1) Challenges(namespace string) v1.ChallengeInterface { - return &FakeChallenges{c, namespace} + return newFakeChallenges(c, namespace) } func (c *FakeAcmeV1) Orders(namespace string) v1.OrderInterface { - return &FakeOrders{c, namespace} + return newFakeOrders(c, namespace) } // RESTClient returns a RESTClient that is used to communicate diff --git a/pkg/client/clientset/versioned/typed/acme/v1/fake/fake_challenge.go b/pkg/client/clientset/versioned/typed/acme/v1/fake/fake_challenge.go index e551dc742b1..2313e566ba5 100644 --- a/pkg/client/clientset/versioned/typed/acme/v1/fake/fake_challenge.go +++ b/pkg/client/clientset/versioned/typed/acme/v1/fake/fake_challenge.go @@ -19,124 +19,31 @@ limitations under the License. package fake import ( - "context" - - acmev1 "github.com/cert-manager/cert-manager/pkg/apis/acme/v1" - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" - labels "k8s.io/apimachinery/pkg/labels" - schema "k8s.io/apimachinery/pkg/runtime/schema" - types "k8s.io/apimachinery/pkg/types" - watch "k8s.io/apimachinery/pkg/watch" - testing "k8s.io/client-go/testing" + v1 "github.com/cert-manager/cert-manager/pkg/apis/acme/v1" + acmev1 "github.com/cert-manager/cert-manager/pkg/client/applyconfigurations/acme/v1" + typedacmev1 "github.com/cert-manager/cert-manager/pkg/client/clientset/versioned/typed/acme/v1" + gentype "k8s.io/client-go/gentype" ) -// FakeChallenges implements ChallengeInterface -type FakeChallenges struct { +// fakeChallenges implements ChallengeInterface +type fakeChallenges struct { + *gentype.FakeClientWithListAndApply[*v1.Challenge, *v1.ChallengeList, *acmev1.ChallengeApplyConfiguration] Fake *FakeAcmeV1 - ns string -} - -var challengesResource = schema.GroupVersionResource{Group: "acme.cert-manager.io", Version: "v1", Resource: "challenges"} - -var challengesKind = schema.GroupVersionKind{Group: "acme.cert-manager.io", Version: "v1", Kind: "Challenge"} - -// Get takes name of the challenge, and returns the corresponding challenge object, and an error if there is any. -func (c *FakeChallenges) Get(ctx context.Context, name string, options v1.GetOptions) (result *acmev1.Challenge, err error) { - obj, err := c.Fake. - Invokes(testing.NewGetAction(challengesResource, c.ns, name), &acmev1.Challenge{}) - - if obj == nil { - return nil, err - } - return obj.(*acmev1.Challenge), err -} - -// List takes label and field selectors, and returns the list of Challenges that match those selectors. -func (c *FakeChallenges) List(ctx context.Context, opts v1.ListOptions) (result *acmev1.ChallengeList, err error) { - obj, err := c.Fake. - Invokes(testing.NewListAction(challengesResource, challengesKind, c.ns, opts), &acmev1.ChallengeList{}) - - if obj == nil { - return nil, err - } - - label, _, _ := testing.ExtractFromListOptions(opts) - if label == nil { - label = labels.Everything() - } - list := &acmev1.ChallengeList{ListMeta: obj.(*acmev1.ChallengeList).ListMeta} - for _, item := range obj.(*acmev1.ChallengeList).Items { - if label.Matches(labels.Set(item.Labels)) { - list.Items = append(list.Items, item) - } - } - return list, err -} - -// Watch returns a watch.Interface that watches the requested challenges. -func (c *FakeChallenges) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { - return c.Fake. - InvokesWatch(testing.NewWatchAction(challengesResource, c.ns, opts)) - -} - -// Create takes the representation of a challenge and creates it. Returns the server's representation of the challenge, and an error, if there is any. -func (c *FakeChallenges) Create(ctx context.Context, challenge *acmev1.Challenge, opts v1.CreateOptions) (result *acmev1.Challenge, err error) { - obj, err := c.Fake. - Invokes(testing.NewCreateAction(challengesResource, c.ns, challenge), &acmev1.Challenge{}) - - if obj == nil { - return nil, err - } - return obj.(*acmev1.Challenge), err -} - -// Update takes the representation of a challenge and updates it. Returns the server's representation of the challenge, and an error, if there is any. -func (c *FakeChallenges) Update(ctx context.Context, challenge *acmev1.Challenge, opts v1.UpdateOptions) (result *acmev1.Challenge, err error) { - obj, err := c.Fake. - Invokes(testing.NewUpdateAction(challengesResource, c.ns, challenge), &acmev1.Challenge{}) - - if obj == nil { - return nil, err - } - return obj.(*acmev1.Challenge), err -} - -// UpdateStatus was generated because the type contains a Status member. -// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). -func (c *FakeChallenges) UpdateStatus(ctx context.Context, challenge *acmev1.Challenge, opts v1.UpdateOptions) (*acmev1.Challenge, error) { - obj, err := c.Fake. - Invokes(testing.NewUpdateSubresourceAction(challengesResource, "status", c.ns, challenge), &acmev1.Challenge{}) - - if obj == nil { - return nil, err - } - return obj.(*acmev1.Challenge), err -} - -// Delete takes name of the challenge and deletes it. Returns an error if one occurs. -func (c *FakeChallenges) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { - _, err := c.Fake. - Invokes(testing.NewDeleteActionWithOptions(challengesResource, c.ns, name, opts), &acmev1.Challenge{}) - - return err } -// DeleteCollection deletes a collection of objects. -func (c *FakeChallenges) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error { - action := testing.NewDeleteCollectionAction(challengesResource, c.ns, listOpts) - - _, err := c.Fake.Invokes(action, &acmev1.ChallengeList{}) - return err -} - -// Patch applies the patch and returns the patched challenge. -func (c *FakeChallenges) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *acmev1.Challenge, err error) { - obj, err := c.Fake. - Invokes(testing.NewPatchSubresourceAction(challengesResource, c.ns, name, pt, data, subresources...), &acmev1.Challenge{}) - - if obj == nil { - return nil, err +func newFakeChallenges(fake *FakeAcmeV1, namespace string) typedacmev1.ChallengeInterface { + return &fakeChallenges{ + gentype.NewFakeClientWithListAndApply[*v1.Challenge, *v1.ChallengeList, *acmev1.ChallengeApplyConfiguration]( + fake.Fake, + namespace, + v1.SchemeGroupVersion.WithResource("challenges"), + v1.SchemeGroupVersion.WithKind("Challenge"), + func() *v1.Challenge { return &v1.Challenge{} }, + func() *v1.ChallengeList { return &v1.ChallengeList{} }, + func(dst, src *v1.ChallengeList) { dst.ListMeta = src.ListMeta }, + func(list *v1.ChallengeList) []*v1.Challenge { return gentype.ToPointerSlice(list.Items) }, + func(list *v1.ChallengeList, items []*v1.Challenge) { list.Items = gentype.FromPointerSlice(items) }, + ), + fake, } - return obj.(*acmev1.Challenge), err } diff --git a/pkg/client/clientset/versioned/typed/acme/v1/fake/fake_order.go b/pkg/client/clientset/versioned/typed/acme/v1/fake/fake_order.go index afad27140a3..2cfe1be0993 100644 --- a/pkg/client/clientset/versioned/typed/acme/v1/fake/fake_order.go +++ b/pkg/client/clientset/versioned/typed/acme/v1/fake/fake_order.go @@ -19,124 +19,31 @@ limitations under the License. package fake import ( - "context" - - acmev1 "github.com/cert-manager/cert-manager/pkg/apis/acme/v1" - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" - labels "k8s.io/apimachinery/pkg/labels" - schema "k8s.io/apimachinery/pkg/runtime/schema" - types "k8s.io/apimachinery/pkg/types" - watch "k8s.io/apimachinery/pkg/watch" - testing "k8s.io/client-go/testing" + v1 "github.com/cert-manager/cert-manager/pkg/apis/acme/v1" + acmev1 "github.com/cert-manager/cert-manager/pkg/client/applyconfigurations/acme/v1" + typedacmev1 "github.com/cert-manager/cert-manager/pkg/client/clientset/versioned/typed/acme/v1" + gentype "k8s.io/client-go/gentype" ) -// FakeOrders implements OrderInterface -type FakeOrders struct { +// fakeOrders implements OrderInterface +type fakeOrders struct { + *gentype.FakeClientWithListAndApply[*v1.Order, *v1.OrderList, *acmev1.OrderApplyConfiguration] Fake *FakeAcmeV1 - ns string -} - -var ordersResource = schema.GroupVersionResource{Group: "acme.cert-manager.io", Version: "v1", Resource: "orders"} - -var ordersKind = schema.GroupVersionKind{Group: "acme.cert-manager.io", Version: "v1", Kind: "Order"} - -// Get takes name of the order, and returns the corresponding order object, and an error if there is any. -func (c *FakeOrders) Get(ctx context.Context, name string, options v1.GetOptions) (result *acmev1.Order, err error) { - obj, err := c.Fake. - Invokes(testing.NewGetAction(ordersResource, c.ns, name), &acmev1.Order{}) - - if obj == nil { - return nil, err - } - return obj.(*acmev1.Order), err -} - -// List takes label and field selectors, and returns the list of Orders that match those selectors. -func (c *FakeOrders) List(ctx context.Context, opts v1.ListOptions) (result *acmev1.OrderList, err error) { - obj, err := c.Fake. - Invokes(testing.NewListAction(ordersResource, ordersKind, c.ns, opts), &acmev1.OrderList{}) - - if obj == nil { - return nil, err - } - - label, _, _ := testing.ExtractFromListOptions(opts) - if label == nil { - label = labels.Everything() - } - list := &acmev1.OrderList{ListMeta: obj.(*acmev1.OrderList).ListMeta} - for _, item := range obj.(*acmev1.OrderList).Items { - if label.Matches(labels.Set(item.Labels)) { - list.Items = append(list.Items, item) - } - } - return list, err -} - -// Watch returns a watch.Interface that watches the requested orders. -func (c *FakeOrders) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { - return c.Fake. - InvokesWatch(testing.NewWatchAction(ordersResource, c.ns, opts)) - -} - -// Create takes the representation of a order and creates it. Returns the server's representation of the order, and an error, if there is any. -func (c *FakeOrders) Create(ctx context.Context, order *acmev1.Order, opts v1.CreateOptions) (result *acmev1.Order, err error) { - obj, err := c.Fake. - Invokes(testing.NewCreateAction(ordersResource, c.ns, order), &acmev1.Order{}) - - if obj == nil { - return nil, err - } - return obj.(*acmev1.Order), err -} - -// Update takes the representation of a order and updates it. Returns the server's representation of the order, and an error, if there is any. -func (c *FakeOrders) Update(ctx context.Context, order *acmev1.Order, opts v1.UpdateOptions) (result *acmev1.Order, err error) { - obj, err := c.Fake. - Invokes(testing.NewUpdateAction(ordersResource, c.ns, order), &acmev1.Order{}) - - if obj == nil { - return nil, err - } - return obj.(*acmev1.Order), err -} - -// UpdateStatus was generated because the type contains a Status member. -// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). -func (c *FakeOrders) UpdateStatus(ctx context.Context, order *acmev1.Order, opts v1.UpdateOptions) (*acmev1.Order, error) { - obj, err := c.Fake. - Invokes(testing.NewUpdateSubresourceAction(ordersResource, "status", c.ns, order), &acmev1.Order{}) - - if obj == nil { - return nil, err - } - return obj.(*acmev1.Order), err -} - -// Delete takes name of the order and deletes it. Returns an error if one occurs. -func (c *FakeOrders) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { - _, err := c.Fake. - Invokes(testing.NewDeleteActionWithOptions(ordersResource, c.ns, name, opts), &acmev1.Order{}) - - return err } -// DeleteCollection deletes a collection of objects. -func (c *FakeOrders) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error { - action := testing.NewDeleteCollectionAction(ordersResource, c.ns, listOpts) - - _, err := c.Fake.Invokes(action, &acmev1.OrderList{}) - return err -} - -// Patch applies the patch and returns the patched order. -func (c *FakeOrders) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *acmev1.Order, err error) { - obj, err := c.Fake. - Invokes(testing.NewPatchSubresourceAction(ordersResource, c.ns, name, pt, data, subresources...), &acmev1.Order{}) - - if obj == nil { - return nil, err +func newFakeOrders(fake *FakeAcmeV1, namespace string) typedacmev1.OrderInterface { + return &fakeOrders{ + gentype.NewFakeClientWithListAndApply[*v1.Order, *v1.OrderList, *acmev1.OrderApplyConfiguration]( + fake.Fake, + namespace, + v1.SchemeGroupVersion.WithResource("orders"), + v1.SchemeGroupVersion.WithKind("Order"), + func() *v1.Order { return &v1.Order{} }, + func() *v1.OrderList { return &v1.OrderList{} }, + func(dst, src *v1.OrderList) { dst.ListMeta = src.ListMeta }, + func(list *v1.OrderList) []*v1.Order { return gentype.ToPointerSlice(list.Items) }, + func(list *v1.OrderList, items []*v1.Order) { list.Items = gentype.FromPointerSlice(items) }, + ), + fake, } - return obj.(*acmev1.Order), err } diff --git a/pkg/client/clientset/versioned/typed/acme/v1/order.go b/pkg/client/clientset/versioned/typed/acme/v1/order.go index 35f3f138b26..cd5c1fcbe8b 100644 --- a/pkg/client/clientset/versioned/typed/acme/v1/order.go +++ b/pkg/client/clientset/versioned/typed/acme/v1/order.go @@ -19,15 +19,15 @@ limitations under the License. package v1 import ( - "context" - "time" + context "context" - v1 "github.com/cert-manager/cert-manager/pkg/apis/acme/v1" + acmev1 "github.com/cert-manager/cert-manager/pkg/apis/acme/v1" + applyconfigurationsacmev1 "github.com/cert-manager/cert-manager/pkg/client/applyconfigurations/acme/v1" scheme "github.com/cert-manager/cert-manager/pkg/client/clientset/versioned/scheme" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" types "k8s.io/apimachinery/pkg/types" watch "k8s.io/apimachinery/pkg/watch" - rest "k8s.io/client-go/rest" + gentype "k8s.io/client-go/gentype" ) // OrdersGetter has a method to return a OrderInterface. @@ -38,158 +38,37 @@ type OrdersGetter interface { // OrderInterface has methods to work with Order resources. type OrderInterface interface { - Create(ctx context.Context, order *v1.Order, opts metav1.CreateOptions) (*v1.Order, error) - Update(ctx context.Context, order *v1.Order, opts metav1.UpdateOptions) (*v1.Order, error) - UpdateStatus(ctx context.Context, order *v1.Order, opts metav1.UpdateOptions) (*v1.Order, error) + Create(ctx context.Context, order *acmev1.Order, opts metav1.CreateOptions) (*acmev1.Order, error) + Update(ctx context.Context, order *acmev1.Order, opts metav1.UpdateOptions) (*acmev1.Order, error) + // Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). + UpdateStatus(ctx context.Context, order *acmev1.Order, opts metav1.UpdateOptions) (*acmev1.Order, error) Delete(ctx context.Context, name string, opts metav1.DeleteOptions) error DeleteCollection(ctx context.Context, opts metav1.DeleteOptions, listOpts metav1.ListOptions) error - Get(ctx context.Context, name string, opts metav1.GetOptions) (*v1.Order, error) - List(ctx context.Context, opts metav1.ListOptions) (*v1.OrderList, error) + Get(ctx context.Context, name string, opts metav1.GetOptions) (*acmev1.Order, error) + List(ctx context.Context, opts metav1.ListOptions) (*acmev1.OrderList, error) Watch(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error) - Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts metav1.PatchOptions, subresources ...string) (result *v1.Order, err error) + Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts metav1.PatchOptions, subresources ...string) (result *acmev1.Order, err error) + Apply(ctx context.Context, order *applyconfigurationsacmev1.OrderApplyConfiguration, opts metav1.ApplyOptions) (result *acmev1.Order, err error) + // Add a +genclient:noStatus comment above the type to avoid generating ApplyStatus(). + ApplyStatus(ctx context.Context, order *applyconfigurationsacmev1.OrderApplyConfiguration, opts metav1.ApplyOptions) (result *acmev1.Order, err error) OrderExpansion } // orders implements OrderInterface type orders struct { - client rest.Interface - ns string + *gentype.ClientWithListAndApply[*acmev1.Order, *acmev1.OrderList, *applyconfigurationsacmev1.OrderApplyConfiguration] } // newOrders returns a Orders func newOrders(c *AcmeV1Client, namespace string) *orders { return &orders{ - client: c.RESTClient(), - ns: namespace, + gentype.NewClientWithListAndApply[*acmev1.Order, *acmev1.OrderList, *applyconfigurationsacmev1.OrderApplyConfiguration]( + "orders", + c.RESTClient(), + scheme.ParameterCodec, + namespace, + func() *acmev1.Order { return &acmev1.Order{} }, + func() *acmev1.OrderList { return &acmev1.OrderList{} }, + ), } } - -// Get takes name of the order, and returns the corresponding order object, and an error if there is any. -func (c *orders) Get(ctx context.Context, name string, options metav1.GetOptions) (result *v1.Order, err error) { - result = &v1.Order{} - err = c.client.Get(). - Namespace(c.ns). - Resource("orders"). - Name(name). - VersionedParams(&options, scheme.ParameterCodec). - Do(ctx). - Into(result) - return -} - -// List takes label and field selectors, and returns the list of Orders that match those selectors. -func (c *orders) List(ctx context.Context, opts metav1.ListOptions) (result *v1.OrderList, err error) { - var timeout time.Duration - if opts.TimeoutSeconds != nil { - timeout = time.Duration(*opts.TimeoutSeconds) * time.Second - } - result = &v1.OrderList{} - err = c.client.Get(). - Namespace(c.ns). - Resource("orders"). - VersionedParams(&opts, scheme.ParameterCodec). - Timeout(timeout). - Do(ctx). - Into(result) - return -} - -// Watch returns a watch.Interface that watches the requested orders. -func (c *orders) Watch(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error) { - var timeout time.Duration - if opts.TimeoutSeconds != nil { - timeout = time.Duration(*opts.TimeoutSeconds) * time.Second - } - opts.Watch = true - return c.client.Get(). - Namespace(c.ns). - Resource("orders"). - VersionedParams(&opts, scheme.ParameterCodec). - Timeout(timeout). - Watch(ctx) -} - -// Create takes the representation of a order and creates it. Returns the server's representation of the order, and an error, if there is any. -func (c *orders) Create(ctx context.Context, order *v1.Order, opts metav1.CreateOptions) (result *v1.Order, err error) { - result = &v1.Order{} - err = c.client.Post(). - Namespace(c.ns). - Resource("orders"). - VersionedParams(&opts, scheme.ParameterCodec). - Body(order). - Do(ctx). - Into(result) - return -} - -// Update takes the representation of a order and updates it. Returns the server's representation of the order, and an error, if there is any. -func (c *orders) Update(ctx context.Context, order *v1.Order, opts metav1.UpdateOptions) (result *v1.Order, err error) { - result = &v1.Order{} - err = c.client.Put(). - Namespace(c.ns). - Resource("orders"). - Name(order.Name). - VersionedParams(&opts, scheme.ParameterCodec). - Body(order). - Do(ctx). - Into(result) - return -} - -// UpdateStatus was generated because the type contains a Status member. -// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). -func (c *orders) UpdateStatus(ctx context.Context, order *v1.Order, opts metav1.UpdateOptions) (result *v1.Order, err error) { - result = &v1.Order{} - err = c.client.Put(). - Namespace(c.ns). - Resource("orders"). - Name(order.Name). - SubResource("status"). - VersionedParams(&opts, scheme.ParameterCodec). - Body(order). - Do(ctx). - Into(result) - return -} - -// Delete takes name of the order and deletes it. Returns an error if one occurs. -func (c *orders) Delete(ctx context.Context, name string, opts metav1.DeleteOptions) error { - return c.client.Delete(). - Namespace(c.ns). - Resource("orders"). - Name(name). - Body(&opts). - Do(ctx). - Error() -} - -// DeleteCollection deletes a collection of objects. -func (c *orders) DeleteCollection(ctx context.Context, opts metav1.DeleteOptions, listOpts metav1.ListOptions) error { - var timeout time.Duration - if listOpts.TimeoutSeconds != nil { - timeout = time.Duration(*listOpts.TimeoutSeconds) * time.Second - } - return c.client.Delete(). - Namespace(c.ns). - Resource("orders"). - VersionedParams(&listOpts, scheme.ParameterCodec). - Timeout(timeout). - Body(&opts). - Do(ctx). - Error() -} - -// Patch applies the patch and returns the patched order. -func (c *orders) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts metav1.PatchOptions, subresources ...string) (result *v1.Order, err error) { - result = &v1.Order{} - err = c.client.Patch(pt). - Namespace(c.ns). - Resource("orders"). - Name(name). - SubResource(subresources...). - VersionedParams(&opts, scheme.ParameterCodec). - Body(data). - Do(ctx). - Into(result) - return -} diff --git a/pkg/client/clientset/versioned/typed/certmanager/v1/certificate.go b/pkg/client/clientset/versioned/typed/certmanager/v1/certificate.go index f7e396186ff..7760408e4e1 100644 --- a/pkg/client/clientset/versioned/typed/certmanager/v1/certificate.go +++ b/pkg/client/clientset/versioned/typed/certmanager/v1/certificate.go @@ -19,15 +19,15 @@ limitations under the License. package v1 import ( - "context" - "time" + context "context" - v1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" + certmanagerv1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" + applyconfigurationscertmanagerv1 "github.com/cert-manager/cert-manager/pkg/client/applyconfigurations/certmanager/v1" scheme "github.com/cert-manager/cert-manager/pkg/client/clientset/versioned/scheme" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" types "k8s.io/apimachinery/pkg/types" watch "k8s.io/apimachinery/pkg/watch" - rest "k8s.io/client-go/rest" + gentype "k8s.io/client-go/gentype" ) // CertificatesGetter has a method to return a CertificateInterface. @@ -38,158 +38,37 @@ type CertificatesGetter interface { // CertificateInterface has methods to work with Certificate resources. type CertificateInterface interface { - Create(ctx context.Context, certificate *v1.Certificate, opts metav1.CreateOptions) (*v1.Certificate, error) - Update(ctx context.Context, certificate *v1.Certificate, opts metav1.UpdateOptions) (*v1.Certificate, error) - UpdateStatus(ctx context.Context, certificate *v1.Certificate, opts metav1.UpdateOptions) (*v1.Certificate, error) + Create(ctx context.Context, certificate *certmanagerv1.Certificate, opts metav1.CreateOptions) (*certmanagerv1.Certificate, error) + Update(ctx context.Context, certificate *certmanagerv1.Certificate, opts metav1.UpdateOptions) (*certmanagerv1.Certificate, error) + // Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). + UpdateStatus(ctx context.Context, certificate *certmanagerv1.Certificate, opts metav1.UpdateOptions) (*certmanagerv1.Certificate, error) Delete(ctx context.Context, name string, opts metav1.DeleteOptions) error DeleteCollection(ctx context.Context, opts metav1.DeleteOptions, listOpts metav1.ListOptions) error - Get(ctx context.Context, name string, opts metav1.GetOptions) (*v1.Certificate, error) - List(ctx context.Context, opts metav1.ListOptions) (*v1.CertificateList, error) + Get(ctx context.Context, name string, opts metav1.GetOptions) (*certmanagerv1.Certificate, error) + List(ctx context.Context, opts metav1.ListOptions) (*certmanagerv1.CertificateList, error) Watch(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error) - Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts metav1.PatchOptions, subresources ...string) (result *v1.Certificate, err error) + Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts metav1.PatchOptions, subresources ...string) (result *certmanagerv1.Certificate, err error) + Apply(ctx context.Context, certificate *applyconfigurationscertmanagerv1.CertificateApplyConfiguration, opts metav1.ApplyOptions) (result *certmanagerv1.Certificate, err error) + // Add a +genclient:noStatus comment above the type to avoid generating ApplyStatus(). + ApplyStatus(ctx context.Context, certificate *applyconfigurationscertmanagerv1.CertificateApplyConfiguration, opts metav1.ApplyOptions) (result *certmanagerv1.Certificate, err error) CertificateExpansion } // certificates implements CertificateInterface type certificates struct { - client rest.Interface - ns string + *gentype.ClientWithListAndApply[*certmanagerv1.Certificate, *certmanagerv1.CertificateList, *applyconfigurationscertmanagerv1.CertificateApplyConfiguration] } // newCertificates returns a Certificates func newCertificates(c *CertmanagerV1Client, namespace string) *certificates { return &certificates{ - client: c.RESTClient(), - ns: namespace, + gentype.NewClientWithListAndApply[*certmanagerv1.Certificate, *certmanagerv1.CertificateList, *applyconfigurationscertmanagerv1.CertificateApplyConfiguration]( + "certificates", + c.RESTClient(), + scheme.ParameterCodec, + namespace, + func() *certmanagerv1.Certificate { return &certmanagerv1.Certificate{} }, + func() *certmanagerv1.CertificateList { return &certmanagerv1.CertificateList{} }, + ), } } - -// Get takes name of the certificate, and returns the corresponding certificate object, and an error if there is any. -func (c *certificates) Get(ctx context.Context, name string, options metav1.GetOptions) (result *v1.Certificate, err error) { - result = &v1.Certificate{} - err = c.client.Get(). - Namespace(c.ns). - Resource("certificates"). - Name(name). - VersionedParams(&options, scheme.ParameterCodec). - Do(ctx). - Into(result) - return -} - -// List takes label and field selectors, and returns the list of Certificates that match those selectors. -func (c *certificates) List(ctx context.Context, opts metav1.ListOptions) (result *v1.CertificateList, err error) { - var timeout time.Duration - if opts.TimeoutSeconds != nil { - timeout = time.Duration(*opts.TimeoutSeconds) * time.Second - } - result = &v1.CertificateList{} - err = c.client.Get(). - Namespace(c.ns). - Resource("certificates"). - VersionedParams(&opts, scheme.ParameterCodec). - Timeout(timeout). - Do(ctx). - Into(result) - return -} - -// Watch returns a watch.Interface that watches the requested certificates. -func (c *certificates) Watch(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error) { - var timeout time.Duration - if opts.TimeoutSeconds != nil { - timeout = time.Duration(*opts.TimeoutSeconds) * time.Second - } - opts.Watch = true - return c.client.Get(). - Namespace(c.ns). - Resource("certificates"). - VersionedParams(&opts, scheme.ParameterCodec). - Timeout(timeout). - Watch(ctx) -} - -// Create takes the representation of a certificate and creates it. Returns the server's representation of the certificate, and an error, if there is any. -func (c *certificates) Create(ctx context.Context, certificate *v1.Certificate, opts metav1.CreateOptions) (result *v1.Certificate, err error) { - result = &v1.Certificate{} - err = c.client.Post(). - Namespace(c.ns). - Resource("certificates"). - VersionedParams(&opts, scheme.ParameterCodec). - Body(certificate). - Do(ctx). - Into(result) - return -} - -// Update takes the representation of a certificate and updates it. Returns the server's representation of the certificate, and an error, if there is any. -func (c *certificates) Update(ctx context.Context, certificate *v1.Certificate, opts metav1.UpdateOptions) (result *v1.Certificate, err error) { - result = &v1.Certificate{} - err = c.client.Put(). - Namespace(c.ns). - Resource("certificates"). - Name(certificate.Name). - VersionedParams(&opts, scheme.ParameterCodec). - Body(certificate). - Do(ctx). - Into(result) - return -} - -// UpdateStatus was generated because the type contains a Status member. -// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). -func (c *certificates) UpdateStatus(ctx context.Context, certificate *v1.Certificate, opts metav1.UpdateOptions) (result *v1.Certificate, err error) { - result = &v1.Certificate{} - err = c.client.Put(). - Namespace(c.ns). - Resource("certificates"). - Name(certificate.Name). - SubResource("status"). - VersionedParams(&opts, scheme.ParameterCodec). - Body(certificate). - Do(ctx). - Into(result) - return -} - -// Delete takes name of the certificate and deletes it. Returns an error if one occurs. -func (c *certificates) Delete(ctx context.Context, name string, opts metav1.DeleteOptions) error { - return c.client.Delete(). - Namespace(c.ns). - Resource("certificates"). - Name(name). - Body(&opts). - Do(ctx). - Error() -} - -// DeleteCollection deletes a collection of objects. -func (c *certificates) DeleteCollection(ctx context.Context, opts metav1.DeleteOptions, listOpts metav1.ListOptions) error { - var timeout time.Duration - if listOpts.TimeoutSeconds != nil { - timeout = time.Duration(*listOpts.TimeoutSeconds) * time.Second - } - return c.client.Delete(). - Namespace(c.ns). - Resource("certificates"). - VersionedParams(&listOpts, scheme.ParameterCodec). - Timeout(timeout). - Body(&opts). - Do(ctx). - Error() -} - -// Patch applies the patch and returns the patched certificate. -func (c *certificates) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts metav1.PatchOptions, subresources ...string) (result *v1.Certificate, err error) { - result = &v1.Certificate{} - err = c.client.Patch(pt). - Namespace(c.ns). - Resource("certificates"). - Name(name). - SubResource(subresources...). - VersionedParams(&opts, scheme.ParameterCodec). - Body(data). - Do(ctx). - Into(result) - return -} diff --git a/pkg/client/clientset/versioned/typed/certmanager/v1/certificaterequest.go b/pkg/client/clientset/versioned/typed/certmanager/v1/certificaterequest.go index 99633aad070..f8a1877640f 100644 --- a/pkg/client/clientset/versioned/typed/certmanager/v1/certificaterequest.go +++ b/pkg/client/clientset/versioned/typed/certmanager/v1/certificaterequest.go @@ -19,15 +19,15 @@ limitations under the License. package v1 import ( - "context" - "time" + context "context" - v1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" + certmanagerv1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" + applyconfigurationscertmanagerv1 "github.com/cert-manager/cert-manager/pkg/client/applyconfigurations/certmanager/v1" scheme "github.com/cert-manager/cert-manager/pkg/client/clientset/versioned/scheme" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" types "k8s.io/apimachinery/pkg/types" watch "k8s.io/apimachinery/pkg/watch" - rest "k8s.io/client-go/rest" + gentype "k8s.io/client-go/gentype" ) // CertificateRequestsGetter has a method to return a CertificateRequestInterface. @@ -38,158 +38,37 @@ type CertificateRequestsGetter interface { // CertificateRequestInterface has methods to work with CertificateRequest resources. type CertificateRequestInterface interface { - Create(ctx context.Context, certificateRequest *v1.CertificateRequest, opts metav1.CreateOptions) (*v1.CertificateRequest, error) - Update(ctx context.Context, certificateRequest *v1.CertificateRequest, opts metav1.UpdateOptions) (*v1.CertificateRequest, error) - UpdateStatus(ctx context.Context, certificateRequest *v1.CertificateRequest, opts metav1.UpdateOptions) (*v1.CertificateRequest, error) + Create(ctx context.Context, certificateRequest *certmanagerv1.CertificateRequest, opts metav1.CreateOptions) (*certmanagerv1.CertificateRequest, error) + Update(ctx context.Context, certificateRequest *certmanagerv1.CertificateRequest, opts metav1.UpdateOptions) (*certmanagerv1.CertificateRequest, error) + // Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). + UpdateStatus(ctx context.Context, certificateRequest *certmanagerv1.CertificateRequest, opts metav1.UpdateOptions) (*certmanagerv1.CertificateRequest, error) Delete(ctx context.Context, name string, opts metav1.DeleteOptions) error DeleteCollection(ctx context.Context, opts metav1.DeleteOptions, listOpts metav1.ListOptions) error - Get(ctx context.Context, name string, opts metav1.GetOptions) (*v1.CertificateRequest, error) - List(ctx context.Context, opts metav1.ListOptions) (*v1.CertificateRequestList, error) + Get(ctx context.Context, name string, opts metav1.GetOptions) (*certmanagerv1.CertificateRequest, error) + List(ctx context.Context, opts metav1.ListOptions) (*certmanagerv1.CertificateRequestList, error) Watch(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error) - Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts metav1.PatchOptions, subresources ...string) (result *v1.CertificateRequest, err error) + Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts metav1.PatchOptions, subresources ...string) (result *certmanagerv1.CertificateRequest, err error) + Apply(ctx context.Context, certificateRequest *applyconfigurationscertmanagerv1.CertificateRequestApplyConfiguration, opts metav1.ApplyOptions) (result *certmanagerv1.CertificateRequest, err error) + // Add a +genclient:noStatus comment above the type to avoid generating ApplyStatus(). + ApplyStatus(ctx context.Context, certificateRequest *applyconfigurationscertmanagerv1.CertificateRequestApplyConfiguration, opts metav1.ApplyOptions) (result *certmanagerv1.CertificateRequest, err error) CertificateRequestExpansion } // certificateRequests implements CertificateRequestInterface type certificateRequests struct { - client rest.Interface - ns string + *gentype.ClientWithListAndApply[*certmanagerv1.CertificateRequest, *certmanagerv1.CertificateRequestList, *applyconfigurationscertmanagerv1.CertificateRequestApplyConfiguration] } // newCertificateRequests returns a CertificateRequests func newCertificateRequests(c *CertmanagerV1Client, namespace string) *certificateRequests { return &certificateRequests{ - client: c.RESTClient(), - ns: namespace, + gentype.NewClientWithListAndApply[*certmanagerv1.CertificateRequest, *certmanagerv1.CertificateRequestList, *applyconfigurationscertmanagerv1.CertificateRequestApplyConfiguration]( + "certificaterequests", + c.RESTClient(), + scheme.ParameterCodec, + namespace, + func() *certmanagerv1.CertificateRequest { return &certmanagerv1.CertificateRequest{} }, + func() *certmanagerv1.CertificateRequestList { return &certmanagerv1.CertificateRequestList{} }, + ), } } - -// Get takes name of the certificateRequest, and returns the corresponding certificateRequest object, and an error if there is any. -func (c *certificateRequests) Get(ctx context.Context, name string, options metav1.GetOptions) (result *v1.CertificateRequest, err error) { - result = &v1.CertificateRequest{} - err = c.client.Get(). - Namespace(c.ns). - Resource("certificaterequests"). - Name(name). - VersionedParams(&options, scheme.ParameterCodec). - Do(ctx). - Into(result) - return -} - -// List takes label and field selectors, and returns the list of CertificateRequests that match those selectors. -func (c *certificateRequests) List(ctx context.Context, opts metav1.ListOptions) (result *v1.CertificateRequestList, err error) { - var timeout time.Duration - if opts.TimeoutSeconds != nil { - timeout = time.Duration(*opts.TimeoutSeconds) * time.Second - } - result = &v1.CertificateRequestList{} - err = c.client.Get(). - Namespace(c.ns). - Resource("certificaterequests"). - VersionedParams(&opts, scheme.ParameterCodec). - Timeout(timeout). - Do(ctx). - Into(result) - return -} - -// Watch returns a watch.Interface that watches the requested certificateRequests. -func (c *certificateRequests) Watch(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error) { - var timeout time.Duration - if opts.TimeoutSeconds != nil { - timeout = time.Duration(*opts.TimeoutSeconds) * time.Second - } - opts.Watch = true - return c.client.Get(). - Namespace(c.ns). - Resource("certificaterequests"). - VersionedParams(&opts, scheme.ParameterCodec). - Timeout(timeout). - Watch(ctx) -} - -// Create takes the representation of a certificateRequest and creates it. Returns the server's representation of the certificateRequest, and an error, if there is any. -func (c *certificateRequests) Create(ctx context.Context, certificateRequest *v1.CertificateRequest, opts metav1.CreateOptions) (result *v1.CertificateRequest, err error) { - result = &v1.CertificateRequest{} - err = c.client.Post(). - Namespace(c.ns). - Resource("certificaterequests"). - VersionedParams(&opts, scheme.ParameterCodec). - Body(certificateRequest). - Do(ctx). - Into(result) - return -} - -// Update takes the representation of a certificateRequest and updates it. Returns the server's representation of the certificateRequest, and an error, if there is any. -func (c *certificateRequests) Update(ctx context.Context, certificateRequest *v1.CertificateRequest, opts metav1.UpdateOptions) (result *v1.CertificateRequest, err error) { - result = &v1.CertificateRequest{} - err = c.client.Put(). - Namespace(c.ns). - Resource("certificaterequests"). - Name(certificateRequest.Name). - VersionedParams(&opts, scheme.ParameterCodec). - Body(certificateRequest). - Do(ctx). - Into(result) - return -} - -// UpdateStatus was generated because the type contains a Status member. -// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). -func (c *certificateRequests) UpdateStatus(ctx context.Context, certificateRequest *v1.CertificateRequest, opts metav1.UpdateOptions) (result *v1.CertificateRequest, err error) { - result = &v1.CertificateRequest{} - err = c.client.Put(). - Namespace(c.ns). - Resource("certificaterequests"). - Name(certificateRequest.Name). - SubResource("status"). - VersionedParams(&opts, scheme.ParameterCodec). - Body(certificateRequest). - Do(ctx). - Into(result) - return -} - -// Delete takes name of the certificateRequest and deletes it. Returns an error if one occurs. -func (c *certificateRequests) Delete(ctx context.Context, name string, opts metav1.DeleteOptions) error { - return c.client.Delete(). - Namespace(c.ns). - Resource("certificaterequests"). - Name(name). - Body(&opts). - Do(ctx). - Error() -} - -// DeleteCollection deletes a collection of objects. -func (c *certificateRequests) DeleteCollection(ctx context.Context, opts metav1.DeleteOptions, listOpts metav1.ListOptions) error { - var timeout time.Duration - if listOpts.TimeoutSeconds != nil { - timeout = time.Duration(*listOpts.TimeoutSeconds) * time.Second - } - return c.client.Delete(). - Namespace(c.ns). - Resource("certificaterequests"). - VersionedParams(&listOpts, scheme.ParameterCodec). - Timeout(timeout). - Body(&opts). - Do(ctx). - Error() -} - -// Patch applies the patch and returns the patched certificateRequest. -func (c *certificateRequests) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts metav1.PatchOptions, subresources ...string) (result *v1.CertificateRequest, err error) { - result = &v1.CertificateRequest{} - err = c.client.Patch(pt). - Namespace(c.ns). - Resource("certificaterequests"). - Name(name). - SubResource(subresources...). - VersionedParams(&opts, scheme.ParameterCodec). - Body(data). - Do(ctx). - Into(result) - return -} diff --git a/pkg/client/clientset/versioned/typed/certmanager/v1/certmanager_client.go b/pkg/client/clientset/versioned/typed/certmanager/v1/certmanager_client.go index d4c9c38007e..f47ceaac6dd 100644 --- a/pkg/client/clientset/versioned/typed/certmanager/v1/certmanager_client.go +++ b/pkg/client/clientset/versioned/typed/certmanager/v1/certmanager_client.go @@ -19,10 +19,10 @@ limitations under the License. package v1 import ( - "net/http" + http "net/http" - v1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" - "github.com/cert-manager/cert-manager/pkg/client/clientset/versioned/scheme" + certmanagerv1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" + scheme "github.com/cert-manager/cert-manager/pkg/client/clientset/versioned/scheme" rest "k8s.io/client-go/rest" ) @@ -60,9 +60,7 @@ func (c *CertmanagerV1Client) Issuers(namespace string) IssuerInterface { // where httpClient was generated with rest.HTTPClientFor(c). func NewForConfig(c *rest.Config) (*CertmanagerV1Client, error) { config := *c - if err := setConfigDefaults(&config); err != nil { - return nil, err - } + setConfigDefaults(&config) httpClient, err := rest.HTTPClientFor(&config) if err != nil { return nil, err @@ -74,9 +72,7 @@ func NewForConfig(c *rest.Config) (*CertmanagerV1Client, error) { // Note the http client provided takes precedence over the configured transport values. func NewForConfigAndClient(c *rest.Config, h *http.Client) (*CertmanagerV1Client, error) { config := *c - if err := setConfigDefaults(&config); err != nil { - return nil, err - } + setConfigDefaults(&config) client, err := rest.RESTClientForConfigAndClient(&config, h) if err != nil { return nil, err @@ -99,17 +95,15 @@ func New(c rest.Interface) *CertmanagerV1Client { return &CertmanagerV1Client{c} } -func setConfigDefaults(config *rest.Config) error { - gv := v1.SchemeGroupVersion +func setConfigDefaults(config *rest.Config) { + gv := certmanagerv1.SchemeGroupVersion config.GroupVersion = &gv config.APIPath = "/apis" - config.NegotiatedSerializer = scheme.Codecs.WithoutConversion() + config.NegotiatedSerializer = rest.CodecFactoryForGeneratedClient(scheme.Scheme, scheme.Codecs).WithoutConversion() if config.UserAgent == "" { config.UserAgent = rest.DefaultKubernetesUserAgent() } - - return nil } // RESTClient returns a RESTClient that is used to communicate diff --git a/pkg/client/clientset/versioned/typed/certmanager/v1/clusterissuer.go b/pkg/client/clientset/versioned/typed/certmanager/v1/clusterissuer.go index 3e8c33984ad..de1ac6f330b 100644 --- a/pkg/client/clientset/versioned/typed/certmanager/v1/clusterissuer.go +++ b/pkg/client/clientset/versioned/typed/certmanager/v1/clusterissuer.go @@ -19,15 +19,15 @@ limitations under the License. package v1 import ( - "context" - "time" + context "context" - v1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" + certmanagerv1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" + applyconfigurationscertmanagerv1 "github.com/cert-manager/cert-manager/pkg/client/applyconfigurations/certmanager/v1" scheme "github.com/cert-manager/cert-manager/pkg/client/clientset/versioned/scheme" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" types "k8s.io/apimachinery/pkg/types" watch "k8s.io/apimachinery/pkg/watch" - rest "k8s.io/client-go/rest" + gentype "k8s.io/client-go/gentype" ) // ClusterIssuersGetter has a method to return a ClusterIssuerInterface. @@ -38,147 +38,37 @@ type ClusterIssuersGetter interface { // ClusterIssuerInterface has methods to work with ClusterIssuer resources. type ClusterIssuerInterface interface { - Create(ctx context.Context, clusterIssuer *v1.ClusterIssuer, opts metav1.CreateOptions) (*v1.ClusterIssuer, error) - Update(ctx context.Context, clusterIssuer *v1.ClusterIssuer, opts metav1.UpdateOptions) (*v1.ClusterIssuer, error) - UpdateStatus(ctx context.Context, clusterIssuer *v1.ClusterIssuer, opts metav1.UpdateOptions) (*v1.ClusterIssuer, error) + Create(ctx context.Context, clusterIssuer *certmanagerv1.ClusterIssuer, opts metav1.CreateOptions) (*certmanagerv1.ClusterIssuer, error) + Update(ctx context.Context, clusterIssuer *certmanagerv1.ClusterIssuer, opts metav1.UpdateOptions) (*certmanagerv1.ClusterIssuer, error) + // Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). + UpdateStatus(ctx context.Context, clusterIssuer *certmanagerv1.ClusterIssuer, opts metav1.UpdateOptions) (*certmanagerv1.ClusterIssuer, error) Delete(ctx context.Context, name string, opts metav1.DeleteOptions) error DeleteCollection(ctx context.Context, opts metav1.DeleteOptions, listOpts metav1.ListOptions) error - Get(ctx context.Context, name string, opts metav1.GetOptions) (*v1.ClusterIssuer, error) - List(ctx context.Context, opts metav1.ListOptions) (*v1.ClusterIssuerList, error) + Get(ctx context.Context, name string, opts metav1.GetOptions) (*certmanagerv1.ClusterIssuer, error) + List(ctx context.Context, opts metav1.ListOptions) (*certmanagerv1.ClusterIssuerList, error) Watch(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error) - Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts metav1.PatchOptions, subresources ...string) (result *v1.ClusterIssuer, err error) + Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts metav1.PatchOptions, subresources ...string) (result *certmanagerv1.ClusterIssuer, err error) + Apply(ctx context.Context, clusterIssuer *applyconfigurationscertmanagerv1.ClusterIssuerApplyConfiguration, opts metav1.ApplyOptions) (result *certmanagerv1.ClusterIssuer, err error) + // Add a +genclient:noStatus comment above the type to avoid generating ApplyStatus(). + ApplyStatus(ctx context.Context, clusterIssuer *applyconfigurationscertmanagerv1.ClusterIssuerApplyConfiguration, opts metav1.ApplyOptions) (result *certmanagerv1.ClusterIssuer, err error) ClusterIssuerExpansion } // clusterIssuers implements ClusterIssuerInterface type clusterIssuers struct { - client rest.Interface + *gentype.ClientWithListAndApply[*certmanagerv1.ClusterIssuer, *certmanagerv1.ClusterIssuerList, *applyconfigurationscertmanagerv1.ClusterIssuerApplyConfiguration] } // newClusterIssuers returns a ClusterIssuers func newClusterIssuers(c *CertmanagerV1Client) *clusterIssuers { return &clusterIssuers{ - client: c.RESTClient(), + gentype.NewClientWithListAndApply[*certmanagerv1.ClusterIssuer, *certmanagerv1.ClusterIssuerList, *applyconfigurationscertmanagerv1.ClusterIssuerApplyConfiguration]( + "clusterissuers", + c.RESTClient(), + scheme.ParameterCodec, + "", + func() *certmanagerv1.ClusterIssuer { return &certmanagerv1.ClusterIssuer{} }, + func() *certmanagerv1.ClusterIssuerList { return &certmanagerv1.ClusterIssuerList{} }, + ), } } - -// Get takes name of the clusterIssuer, and returns the corresponding clusterIssuer object, and an error if there is any. -func (c *clusterIssuers) Get(ctx context.Context, name string, options metav1.GetOptions) (result *v1.ClusterIssuer, err error) { - result = &v1.ClusterIssuer{} - err = c.client.Get(). - Resource("clusterissuers"). - Name(name). - VersionedParams(&options, scheme.ParameterCodec). - Do(ctx). - Into(result) - return -} - -// List takes label and field selectors, and returns the list of ClusterIssuers that match those selectors. -func (c *clusterIssuers) List(ctx context.Context, opts metav1.ListOptions) (result *v1.ClusterIssuerList, err error) { - var timeout time.Duration - if opts.TimeoutSeconds != nil { - timeout = time.Duration(*opts.TimeoutSeconds) * time.Second - } - result = &v1.ClusterIssuerList{} - err = c.client.Get(). - Resource("clusterissuers"). - VersionedParams(&opts, scheme.ParameterCodec). - Timeout(timeout). - Do(ctx). - Into(result) - return -} - -// Watch returns a watch.Interface that watches the requested clusterIssuers. -func (c *clusterIssuers) Watch(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error) { - var timeout time.Duration - if opts.TimeoutSeconds != nil { - timeout = time.Duration(*opts.TimeoutSeconds) * time.Second - } - opts.Watch = true - return c.client.Get(). - Resource("clusterissuers"). - VersionedParams(&opts, scheme.ParameterCodec). - Timeout(timeout). - Watch(ctx) -} - -// Create takes the representation of a clusterIssuer and creates it. Returns the server's representation of the clusterIssuer, and an error, if there is any. -func (c *clusterIssuers) Create(ctx context.Context, clusterIssuer *v1.ClusterIssuer, opts metav1.CreateOptions) (result *v1.ClusterIssuer, err error) { - result = &v1.ClusterIssuer{} - err = c.client.Post(). - Resource("clusterissuers"). - VersionedParams(&opts, scheme.ParameterCodec). - Body(clusterIssuer). - Do(ctx). - Into(result) - return -} - -// Update takes the representation of a clusterIssuer and updates it. Returns the server's representation of the clusterIssuer, and an error, if there is any. -func (c *clusterIssuers) Update(ctx context.Context, clusterIssuer *v1.ClusterIssuer, opts metav1.UpdateOptions) (result *v1.ClusterIssuer, err error) { - result = &v1.ClusterIssuer{} - err = c.client.Put(). - Resource("clusterissuers"). - Name(clusterIssuer.Name). - VersionedParams(&opts, scheme.ParameterCodec). - Body(clusterIssuer). - Do(ctx). - Into(result) - return -} - -// UpdateStatus was generated because the type contains a Status member. -// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). -func (c *clusterIssuers) UpdateStatus(ctx context.Context, clusterIssuer *v1.ClusterIssuer, opts metav1.UpdateOptions) (result *v1.ClusterIssuer, err error) { - result = &v1.ClusterIssuer{} - err = c.client.Put(). - Resource("clusterissuers"). - Name(clusterIssuer.Name). - SubResource("status"). - VersionedParams(&opts, scheme.ParameterCodec). - Body(clusterIssuer). - Do(ctx). - Into(result) - return -} - -// Delete takes name of the clusterIssuer and deletes it. Returns an error if one occurs. -func (c *clusterIssuers) Delete(ctx context.Context, name string, opts metav1.DeleteOptions) error { - return c.client.Delete(). - Resource("clusterissuers"). - Name(name). - Body(&opts). - Do(ctx). - Error() -} - -// DeleteCollection deletes a collection of objects. -func (c *clusterIssuers) DeleteCollection(ctx context.Context, opts metav1.DeleteOptions, listOpts metav1.ListOptions) error { - var timeout time.Duration - if listOpts.TimeoutSeconds != nil { - timeout = time.Duration(*listOpts.TimeoutSeconds) * time.Second - } - return c.client.Delete(). - Resource("clusterissuers"). - VersionedParams(&listOpts, scheme.ParameterCodec). - Timeout(timeout). - Body(&opts). - Do(ctx). - Error() -} - -// Patch applies the patch and returns the patched clusterIssuer. -func (c *clusterIssuers) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts metav1.PatchOptions, subresources ...string) (result *v1.ClusterIssuer, err error) { - result = &v1.ClusterIssuer{} - err = c.client.Patch(pt). - Resource("clusterissuers"). - Name(name). - SubResource(subresources...). - VersionedParams(&opts, scheme.ParameterCodec). - Body(data). - Do(ctx). - Into(result) - return -} diff --git a/pkg/client/clientset/versioned/typed/certmanager/v1/fake/fake_certificate.go b/pkg/client/clientset/versioned/typed/certmanager/v1/fake/fake_certificate.go index 0496f056213..a9618252b44 100644 --- a/pkg/client/clientset/versioned/typed/certmanager/v1/fake/fake_certificate.go +++ b/pkg/client/clientset/versioned/typed/certmanager/v1/fake/fake_certificate.go @@ -19,124 +19,31 @@ limitations under the License. package fake import ( - "context" - - certmanagerv1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" - labels "k8s.io/apimachinery/pkg/labels" - schema "k8s.io/apimachinery/pkg/runtime/schema" - types "k8s.io/apimachinery/pkg/types" - watch "k8s.io/apimachinery/pkg/watch" - testing "k8s.io/client-go/testing" + v1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" + certmanagerv1 "github.com/cert-manager/cert-manager/pkg/client/applyconfigurations/certmanager/v1" + typedcertmanagerv1 "github.com/cert-manager/cert-manager/pkg/client/clientset/versioned/typed/certmanager/v1" + gentype "k8s.io/client-go/gentype" ) -// FakeCertificates implements CertificateInterface -type FakeCertificates struct { +// fakeCertificates implements CertificateInterface +type fakeCertificates struct { + *gentype.FakeClientWithListAndApply[*v1.Certificate, *v1.CertificateList, *certmanagerv1.CertificateApplyConfiguration] Fake *FakeCertmanagerV1 - ns string -} - -var certificatesResource = schema.GroupVersionResource{Group: "cert-manager.io", Version: "v1", Resource: "certificates"} - -var certificatesKind = schema.GroupVersionKind{Group: "cert-manager.io", Version: "v1", Kind: "Certificate"} - -// Get takes name of the certificate, and returns the corresponding certificate object, and an error if there is any. -func (c *FakeCertificates) Get(ctx context.Context, name string, options v1.GetOptions) (result *certmanagerv1.Certificate, err error) { - obj, err := c.Fake. - Invokes(testing.NewGetAction(certificatesResource, c.ns, name), &certmanagerv1.Certificate{}) - - if obj == nil { - return nil, err - } - return obj.(*certmanagerv1.Certificate), err -} - -// List takes label and field selectors, and returns the list of Certificates that match those selectors. -func (c *FakeCertificates) List(ctx context.Context, opts v1.ListOptions) (result *certmanagerv1.CertificateList, err error) { - obj, err := c.Fake. - Invokes(testing.NewListAction(certificatesResource, certificatesKind, c.ns, opts), &certmanagerv1.CertificateList{}) - - if obj == nil { - return nil, err - } - - label, _, _ := testing.ExtractFromListOptions(opts) - if label == nil { - label = labels.Everything() - } - list := &certmanagerv1.CertificateList{ListMeta: obj.(*certmanagerv1.CertificateList).ListMeta} - for _, item := range obj.(*certmanagerv1.CertificateList).Items { - if label.Matches(labels.Set(item.Labels)) { - list.Items = append(list.Items, item) - } - } - return list, err -} - -// Watch returns a watch.Interface that watches the requested certificates. -func (c *FakeCertificates) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { - return c.Fake. - InvokesWatch(testing.NewWatchAction(certificatesResource, c.ns, opts)) - -} - -// Create takes the representation of a certificate and creates it. Returns the server's representation of the certificate, and an error, if there is any. -func (c *FakeCertificates) Create(ctx context.Context, certificate *certmanagerv1.Certificate, opts v1.CreateOptions) (result *certmanagerv1.Certificate, err error) { - obj, err := c.Fake. - Invokes(testing.NewCreateAction(certificatesResource, c.ns, certificate), &certmanagerv1.Certificate{}) - - if obj == nil { - return nil, err - } - return obj.(*certmanagerv1.Certificate), err -} - -// Update takes the representation of a certificate and updates it. Returns the server's representation of the certificate, and an error, if there is any. -func (c *FakeCertificates) Update(ctx context.Context, certificate *certmanagerv1.Certificate, opts v1.UpdateOptions) (result *certmanagerv1.Certificate, err error) { - obj, err := c.Fake. - Invokes(testing.NewUpdateAction(certificatesResource, c.ns, certificate), &certmanagerv1.Certificate{}) - - if obj == nil { - return nil, err - } - return obj.(*certmanagerv1.Certificate), err -} - -// UpdateStatus was generated because the type contains a Status member. -// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). -func (c *FakeCertificates) UpdateStatus(ctx context.Context, certificate *certmanagerv1.Certificate, opts v1.UpdateOptions) (*certmanagerv1.Certificate, error) { - obj, err := c.Fake. - Invokes(testing.NewUpdateSubresourceAction(certificatesResource, "status", c.ns, certificate), &certmanagerv1.Certificate{}) - - if obj == nil { - return nil, err - } - return obj.(*certmanagerv1.Certificate), err -} - -// Delete takes name of the certificate and deletes it. Returns an error if one occurs. -func (c *FakeCertificates) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { - _, err := c.Fake. - Invokes(testing.NewDeleteActionWithOptions(certificatesResource, c.ns, name, opts), &certmanagerv1.Certificate{}) - - return err } -// DeleteCollection deletes a collection of objects. -func (c *FakeCertificates) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error { - action := testing.NewDeleteCollectionAction(certificatesResource, c.ns, listOpts) - - _, err := c.Fake.Invokes(action, &certmanagerv1.CertificateList{}) - return err -} - -// Patch applies the patch and returns the patched certificate. -func (c *FakeCertificates) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *certmanagerv1.Certificate, err error) { - obj, err := c.Fake. - Invokes(testing.NewPatchSubresourceAction(certificatesResource, c.ns, name, pt, data, subresources...), &certmanagerv1.Certificate{}) - - if obj == nil { - return nil, err +func newFakeCertificates(fake *FakeCertmanagerV1, namespace string) typedcertmanagerv1.CertificateInterface { + return &fakeCertificates{ + gentype.NewFakeClientWithListAndApply[*v1.Certificate, *v1.CertificateList, *certmanagerv1.CertificateApplyConfiguration]( + fake.Fake, + namespace, + v1.SchemeGroupVersion.WithResource("certificates"), + v1.SchemeGroupVersion.WithKind("Certificate"), + func() *v1.Certificate { return &v1.Certificate{} }, + func() *v1.CertificateList { return &v1.CertificateList{} }, + func(dst, src *v1.CertificateList) { dst.ListMeta = src.ListMeta }, + func(list *v1.CertificateList) []*v1.Certificate { return gentype.ToPointerSlice(list.Items) }, + func(list *v1.CertificateList, items []*v1.Certificate) { list.Items = gentype.FromPointerSlice(items) }, + ), + fake, } - return obj.(*certmanagerv1.Certificate), err } diff --git a/pkg/client/clientset/versioned/typed/certmanager/v1/fake/fake_certificaterequest.go b/pkg/client/clientset/versioned/typed/certmanager/v1/fake/fake_certificaterequest.go index b2a3169f676..0cc93b342a2 100644 --- a/pkg/client/clientset/versioned/typed/certmanager/v1/fake/fake_certificaterequest.go +++ b/pkg/client/clientset/versioned/typed/certmanager/v1/fake/fake_certificaterequest.go @@ -19,124 +19,35 @@ limitations under the License. package fake import ( - "context" - - certmanagerv1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" - labels "k8s.io/apimachinery/pkg/labels" - schema "k8s.io/apimachinery/pkg/runtime/schema" - types "k8s.io/apimachinery/pkg/types" - watch "k8s.io/apimachinery/pkg/watch" - testing "k8s.io/client-go/testing" + v1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" + certmanagerv1 "github.com/cert-manager/cert-manager/pkg/client/applyconfigurations/certmanager/v1" + typedcertmanagerv1 "github.com/cert-manager/cert-manager/pkg/client/clientset/versioned/typed/certmanager/v1" + gentype "k8s.io/client-go/gentype" ) -// FakeCertificateRequests implements CertificateRequestInterface -type FakeCertificateRequests struct { +// fakeCertificateRequests implements CertificateRequestInterface +type fakeCertificateRequests struct { + *gentype.FakeClientWithListAndApply[*v1.CertificateRequest, *v1.CertificateRequestList, *certmanagerv1.CertificateRequestApplyConfiguration] Fake *FakeCertmanagerV1 - ns string -} - -var certificaterequestsResource = schema.GroupVersionResource{Group: "cert-manager.io", Version: "v1", Resource: "certificaterequests"} - -var certificaterequestsKind = schema.GroupVersionKind{Group: "cert-manager.io", Version: "v1", Kind: "CertificateRequest"} - -// Get takes name of the certificateRequest, and returns the corresponding certificateRequest object, and an error if there is any. -func (c *FakeCertificateRequests) Get(ctx context.Context, name string, options v1.GetOptions) (result *certmanagerv1.CertificateRequest, err error) { - obj, err := c.Fake. - Invokes(testing.NewGetAction(certificaterequestsResource, c.ns, name), &certmanagerv1.CertificateRequest{}) - - if obj == nil { - return nil, err - } - return obj.(*certmanagerv1.CertificateRequest), err -} - -// List takes label and field selectors, and returns the list of CertificateRequests that match those selectors. -func (c *FakeCertificateRequests) List(ctx context.Context, opts v1.ListOptions) (result *certmanagerv1.CertificateRequestList, err error) { - obj, err := c.Fake. - Invokes(testing.NewListAction(certificaterequestsResource, certificaterequestsKind, c.ns, opts), &certmanagerv1.CertificateRequestList{}) - - if obj == nil { - return nil, err - } - - label, _, _ := testing.ExtractFromListOptions(opts) - if label == nil { - label = labels.Everything() - } - list := &certmanagerv1.CertificateRequestList{ListMeta: obj.(*certmanagerv1.CertificateRequestList).ListMeta} - for _, item := range obj.(*certmanagerv1.CertificateRequestList).Items { - if label.Matches(labels.Set(item.Labels)) { - list.Items = append(list.Items, item) - } - } - return list, err -} - -// Watch returns a watch.Interface that watches the requested certificateRequests. -func (c *FakeCertificateRequests) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { - return c.Fake. - InvokesWatch(testing.NewWatchAction(certificaterequestsResource, c.ns, opts)) - -} - -// Create takes the representation of a certificateRequest and creates it. Returns the server's representation of the certificateRequest, and an error, if there is any. -func (c *FakeCertificateRequests) Create(ctx context.Context, certificateRequest *certmanagerv1.CertificateRequest, opts v1.CreateOptions) (result *certmanagerv1.CertificateRequest, err error) { - obj, err := c.Fake. - Invokes(testing.NewCreateAction(certificaterequestsResource, c.ns, certificateRequest), &certmanagerv1.CertificateRequest{}) - - if obj == nil { - return nil, err - } - return obj.(*certmanagerv1.CertificateRequest), err -} - -// Update takes the representation of a certificateRequest and updates it. Returns the server's representation of the certificateRequest, and an error, if there is any. -func (c *FakeCertificateRequests) Update(ctx context.Context, certificateRequest *certmanagerv1.CertificateRequest, opts v1.UpdateOptions) (result *certmanagerv1.CertificateRequest, err error) { - obj, err := c.Fake. - Invokes(testing.NewUpdateAction(certificaterequestsResource, c.ns, certificateRequest), &certmanagerv1.CertificateRequest{}) - - if obj == nil { - return nil, err - } - return obj.(*certmanagerv1.CertificateRequest), err -} - -// UpdateStatus was generated because the type contains a Status member. -// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). -func (c *FakeCertificateRequests) UpdateStatus(ctx context.Context, certificateRequest *certmanagerv1.CertificateRequest, opts v1.UpdateOptions) (*certmanagerv1.CertificateRequest, error) { - obj, err := c.Fake. - Invokes(testing.NewUpdateSubresourceAction(certificaterequestsResource, "status", c.ns, certificateRequest), &certmanagerv1.CertificateRequest{}) - - if obj == nil { - return nil, err - } - return obj.(*certmanagerv1.CertificateRequest), err -} - -// Delete takes name of the certificateRequest and deletes it. Returns an error if one occurs. -func (c *FakeCertificateRequests) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { - _, err := c.Fake. - Invokes(testing.NewDeleteActionWithOptions(certificaterequestsResource, c.ns, name, opts), &certmanagerv1.CertificateRequest{}) - - return err } -// DeleteCollection deletes a collection of objects. -func (c *FakeCertificateRequests) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error { - action := testing.NewDeleteCollectionAction(certificaterequestsResource, c.ns, listOpts) - - _, err := c.Fake.Invokes(action, &certmanagerv1.CertificateRequestList{}) - return err -} - -// Patch applies the patch and returns the patched certificateRequest. -func (c *FakeCertificateRequests) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *certmanagerv1.CertificateRequest, err error) { - obj, err := c.Fake. - Invokes(testing.NewPatchSubresourceAction(certificaterequestsResource, c.ns, name, pt, data, subresources...), &certmanagerv1.CertificateRequest{}) - - if obj == nil { - return nil, err +func newFakeCertificateRequests(fake *FakeCertmanagerV1, namespace string) typedcertmanagerv1.CertificateRequestInterface { + return &fakeCertificateRequests{ + gentype.NewFakeClientWithListAndApply[*v1.CertificateRequest, *v1.CertificateRequestList, *certmanagerv1.CertificateRequestApplyConfiguration]( + fake.Fake, + namespace, + v1.SchemeGroupVersion.WithResource("certificaterequests"), + v1.SchemeGroupVersion.WithKind("CertificateRequest"), + func() *v1.CertificateRequest { return &v1.CertificateRequest{} }, + func() *v1.CertificateRequestList { return &v1.CertificateRequestList{} }, + func(dst, src *v1.CertificateRequestList) { dst.ListMeta = src.ListMeta }, + func(list *v1.CertificateRequestList) []*v1.CertificateRequest { + return gentype.ToPointerSlice(list.Items) + }, + func(list *v1.CertificateRequestList, items []*v1.CertificateRequest) { + list.Items = gentype.FromPointerSlice(items) + }, + ), + fake, } - return obj.(*certmanagerv1.CertificateRequest), err } diff --git a/pkg/client/clientset/versioned/typed/certmanager/v1/fake/fake_certmanager_client.go b/pkg/client/clientset/versioned/typed/certmanager/v1/fake/fake_certmanager_client.go index b56ece834a5..24961b1e857 100644 --- a/pkg/client/clientset/versioned/typed/certmanager/v1/fake/fake_certmanager_client.go +++ b/pkg/client/clientset/versioned/typed/certmanager/v1/fake/fake_certmanager_client.go @@ -29,19 +29,19 @@ type FakeCertmanagerV1 struct { } func (c *FakeCertmanagerV1) Certificates(namespace string) v1.CertificateInterface { - return &FakeCertificates{c, namespace} + return newFakeCertificates(c, namespace) } func (c *FakeCertmanagerV1) CertificateRequests(namespace string) v1.CertificateRequestInterface { - return &FakeCertificateRequests{c, namespace} + return newFakeCertificateRequests(c, namespace) } func (c *FakeCertmanagerV1) ClusterIssuers() v1.ClusterIssuerInterface { - return &FakeClusterIssuers{c} + return newFakeClusterIssuers(c) } func (c *FakeCertmanagerV1) Issuers(namespace string) v1.IssuerInterface { - return &FakeIssuers{c, namespace} + return newFakeIssuers(c, namespace) } // RESTClient returns a RESTClient that is used to communicate diff --git a/pkg/client/clientset/versioned/typed/certmanager/v1/fake/fake_clusterissuer.go b/pkg/client/clientset/versioned/typed/certmanager/v1/fake/fake_clusterissuer.go index ef685424709..097f9f4c220 100644 --- a/pkg/client/clientset/versioned/typed/certmanager/v1/fake/fake_clusterissuer.go +++ b/pkg/client/clientset/versioned/typed/certmanager/v1/fake/fake_clusterissuer.go @@ -19,115 +19,33 @@ limitations under the License. package fake import ( - "context" - - certmanagerv1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" - labels "k8s.io/apimachinery/pkg/labels" - schema "k8s.io/apimachinery/pkg/runtime/schema" - types "k8s.io/apimachinery/pkg/types" - watch "k8s.io/apimachinery/pkg/watch" - testing "k8s.io/client-go/testing" + v1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" + certmanagerv1 "github.com/cert-manager/cert-manager/pkg/client/applyconfigurations/certmanager/v1" + typedcertmanagerv1 "github.com/cert-manager/cert-manager/pkg/client/clientset/versioned/typed/certmanager/v1" + gentype "k8s.io/client-go/gentype" ) -// FakeClusterIssuers implements ClusterIssuerInterface -type FakeClusterIssuers struct { +// fakeClusterIssuers implements ClusterIssuerInterface +type fakeClusterIssuers struct { + *gentype.FakeClientWithListAndApply[*v1.ClusterIssuer, *v1.ClusterIssuerList, *certmanagerv1.ClusterIssuerApplyConfiguration] Fake *FakeCertmanagerV1 } -var clusterissuersResource = schema.GroupVersionResource{Group: "cert-manager.io", Version: "v1", Resource: "clusterissuers"} - -var clusterissuersKind = schema.GroupVersionKind{Group: "cert-manager.io", Version: "v1", Kind: "ClusterIssuer"} - -// Get takes name of the clusterIssuer, and returns the corresponding clusterIssuer object, and an error if there is any. -func (c *FakeClusterIssuers) Get(ctx context.Context, name string, options v1.GetOptions) (result *certmanagerv1.ClusterIssuer, err error) { - obj, err := c.Fake. - Invokes(testing.NewRootGetAction(clusterissuersResource, name), &certmanagerv1.ClusterIssuer{}) - if obj == nil { - return nil, err - } - return obj.(*certmanagerv1.ClusterIssuer), err -} - -// List takes label and field selectors, and returns the list of ClusterIssuers that match those selectors. -func (c *FakeClusterIssuers) List(ctx context.Context, opts v1.ListOptions) (result *certmanagerv1.ClusterIssuerList, err error) { - obj, err := c.Fake. - Invokes(testing.NewRootListAction(clusterissuersResource, clusterissuersKind, opts), &certmanagerv1.ClusterIssuerList{}) - if obj == nil { - return nil, err - } - - label, _, _ := testing.ExtractFromListOptions(opts) - if label == nil { - label = labels.Everything() - } - list := &certmanagerv1.ClusterIssuerList{ListMeta: obj.(*certmanagerv1.ClusterIssuerList).ListMeta} - for _, item := range obj.(*certmanagerv1.ClusterIssuerList).Items { - if label.Matches(labels.Set(item.Labels)) { - list.Items = append(list.Items, item) - } - } - return list, err -} - -// Watch returns a watch.Interface that watches the requested clusterIssuers. -func (c *FakeClusterIssuers) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { - return c.Fake. - InvokesWatch(testing.NewRootWatchAction(clusterissuersResource, opts)) -} - -// Create takes the representation of a clusterIssuer and creates it. Returns the server's representation of the clusterIssuer, and an error, if there is any. -func (c *FakeClusterIssuers) Create(ctx context.Context, clusterIssuer *certmanagerv1.ClusterIssuer, opts v1.CreateOptions) (result *certmanagerv1.ClusterIssuer, err error) { - obj, err := c.Fake. - Invokes(testing.NewRootCreateAction(clusterissuersResource, clusterIssuer), &certmanagerv1.ClusterIssuer{}) - if obj == nil { - return nil, err - } - return obj.(*certmanagerv1.ClusterIssuer), err -} - -// Update takes the representation of a clusterIssuer and updates it. Returns the server's representation of the clusterIssuer, and an error, if there is any. -func (c *FakeClusterIssuers) Update(ctx context.Context, clusterIssuer *certmanagerv1.ClusterIssuer, opts v1.UpdateOptions) (result *certmanagerv1.ClusterIssuer, err error) { - obj, err := c.Fake. - Invokes(testing.NewRootUpdateAction(clusterissuersResource, clusterIssuer), &certmanagerv1.ClusterIssuer{}) - if obj == nil { - return nil, err - } - return obj.(*certmanagerv1.ClusterIssuer), err -} - -// UpdateStatus was generated because the type contains a Status member. -// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). -func (c *FakeClusterIssuers) UpdateStatus(ctx context.Context, clusterIssuer *certmanagerv1.ClusterIssuer, opts v1.UpdateOptions) (*certmanagerv1.ClusterIssuer, error) { - obj, err := c.Fake. - Invokes(testing.NewRootUpdateSubresourceAction(clusterissuersResource, "status", clusterIssuer), &certmanagerv1.ClusterIssuer{}) - if obj == nil { - return nil, err - } - return obj.(*certmanagerv1.ClusterIssuer), err -} - -// Delete takes name of the clusterIssuer and deletes it. Returns an error if one occurs. -func (c *FakeClusterIssuers) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { - _, err := c.Fake. - Invokes(testing.NewRootDeleteActionWithOptions(clusterissuersResource, name, opts), &certmanagerv1.ClusterIssuer{}) - return err -} - -// DeleteCollection deletes a collection of objects. -func (c *FakeClusterIssuers) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error { - action := testing.NewRootDeleteCollectionAction(clusterissuersResource, listOpts) - - _, err := c.Fake.Invokes(action, &certmanagerv1.ClusterIssuerList{}) - return err -} - -// Patch applies the patch and returns the patched clusterIssuer. -func (c *FakeClusterIssuers) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *certmanagerv1.ClusterIssuer, err error) { - obj, err := c.Fake. - Invokes(testing.NewRootPatchSubresourceAction(clusterissuersResource, name, pt, data, subresources...), &certmanagerv1.ClusterIssuer{}) - if obj == nil { - return nil, err +func newFakeClusterIssuers(fake *FakeCertmanagerV1) typedcertmanagerv1.ClusterIssuerInterface { + return &fakeClusterIssuers{ + gentype.NewFakeClientWithListAndApply[*v1.ClusterIssuer, *v1.ClusterIssuerList, *certmanagerv1.ClusterIssuerApplyConfiguration]( + fake.Fake, + "", + v1.SchemeGroupVersion.WithResource("clusterissuers"), + v1.SchemeGroupVersion.WithKind("ClusterIssuer"), + func() *v1.ClusterIssuer { return &v1.ClusterIssuer{} }, + func() *v1.ClusterIssuerList { return &v1.ClusterIssuerList{} }, + func(dst, src *v1.ClusterIssuerList) { dst.ListMeta = src.ListMeta }, + func(list *v1.ClusterIssuerList) []*v1.ClusterIssuer { return gentype.ToPointerSlice(list.Items) }, + func(list *v1.ClusterIssuerList, items []*v1.ClusterIssuer) { + list.Items = gentype.FromPointerSlice(items) + }, + ), + fake, } - return obj.(*certmanagerv1.ClusterIssuer), err } diff --git a/pkg/client/clientset/versioned/typed/certmanager/v1/fake/fake_issuer.go b/pkg/client/clientset/versioned/typed/certmanager/v1/fake/fake_issuer.go index 42f24b4f3e2..1c3bec77577 100644 --- a/pkg/client/clientset/versioned/typed/certmanager/v1/fake/fake_issuer.go +++ b/pkg/client/clientset/versioned/typed/certmanager/v1/fake/fake_issuer.go @@ -19,124 +19,31 @@ limitations under the License. package fake import ( - "context" - - certmanagerv1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" - labels "k8s.io/apimachinery/pkg/labels" - schema "k8s.io/apimachinery/pkg/runtime/schema" - types "k8s.io/apimachinery/pkg/types" - watch "k8s.io/apimachinery/pkg/watch" - testing "k8s.io/client-go/testing" + v1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" + certmanagerv1 "github.com/cert-manager/cert-manager/pkg/client/applyconfigurations/certmanager/v1" + typedcertmanagerv1 "github.com/cert-manager/cert-manager/pkg/client/clientset/versioned/typed/certmanager/v1" + gentype "k8s.io/client-go/gentype" ) -// FakeIssuers implements IssuerInterface -type FakeIssuers struct { +// fakeIssuers implements IssuerInterface +type fakeIssuers struct { + *gentype.FakeClientWithListAndApply[*v1.Issuer, *v1.IssuerList, *certmanagerv1.IssuerApplyConfiguration] Fake *FakeCertmanagerV1 - ns string -} - -var issuersResource = schema.GroupVersionResource{Group: "cert-manager.io", Version: "v1", Resource: "issuers"} - -var issuersKind = schema.GroupVersionKind{Group: "cert-manager.io", Version: "v1", Kind: "Issuer"} - -// Get takes name of the issuer, and returns the corresponding issuer object, and an error if there is any. -func (c *FakeIssuers) Get(ctx context.Context, name string, options v1.GetOptions) (result *certmanagerv1.Issuer, err error) { - obj, err := c.Fake. - Invokes(testing.NewGetAction(issuersResource, c.ns, name), &certmanagerv1.Issuer{}) - - if obj == nil { - return nil, err - } - return obj.(*certmanagerv1.Issuer), err -} - -// List takes label and field selectors, and returns the list of Issuers that match those selectors. -func (c *FakeIssuers) List(ctx context.Context, opts v1.ListOptions) (result *certmanagerv1.IssuerList, err error) { - obj, err := c.Fake. - Invokes(testing.NewListAction(issuersResource, issuersKind, c.ns, opts), &certmanagerv1.IssuerList{}) - - if obj == nil { - return nil, err - } - - label, _, _ := testing.ExtractFromListOptions(opts) - if label == nil { - label = labels.Everything() - } - list := &certmanagerv1.IssuerList{ListMeta: obj.(*certmanagerv1.IssuerList).ListMeta} - for _, item := range obj.(*certmanagerv1.IssuerList).Items { - if label.Matches(labels.Set(item.Labels)) { - list.Items = append(list.Items, item) - } - } - return list, err -} - -// Watch returns a watch.Interface that watches the requested issuers. -func (c *FakeIssuers) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { - return c.Fake. - InvokesWatch(testing.NewWatchAction(issuersResource, c.ns, opts)) - -} - -// Create takes the representation of a issuer and creates it. Returns the server's representation of the issuer, and an error, if there is any. -func (c *FakeIssuers) Create(ctx context.Context, issuer *certmanagerv1.Issuer, opts v1.CreateOptions) (result *certmanagerv1.Issuer, err error) { - obj, err := c.Fake. - Invokes(testing.NewCreateAction(issuersResource, c.ns, issuer), &certmanagerv1.Issuer{}) - - if obj == nil { - return nil, err - } - return obj.(*certmanagerv1.Issuer), err -} - -// Update takes the representation of a issuer and updates it. Returns the server's representation of the issuer, and an error, if there is any. -func (c *FakeIssuers) Update(ctx context.Context, issuer *certmanagerv1.Issuer, opts v1.UpdateOptions) (result *certmanagerv1.Issuer, err error) { - obj, err := c.Fake. - Invokes(testing.NewUpdateAction(issuersResource, c.ns, issuer), &certmanagerv1.Issuer{}) - - if obj == nil { - return nil, err - } - return obj.(*certmanagerv1.Issuer), err -} - -// UpdateStatus was generated because the type contains a Status member. -// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). -func (c *FakeIssuers) UpdateStatus(ctx context.Context, issuer *certmanagerv1.Issuer, opts v1.UpdateOptions) (*certmanagerv1.Issuer, error) { - obj, err := c.Fake. - Invokes(testing.NewUpdateSubresourceAction(issuersResource, "status", c.ns, issuer), &certmanagerv1.Issuer{}) - - if obj == nil { - return nil, err - } - return obj.(*certmanagerv1.Issuer), err -} - -// Delete takes name of the issuer and deletes it. Returns an error if one occurs. -func (c *FakeIssuers) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { - _, err := c.Fake. - Invokes(testing.NewDeleteActionWithOptions(issuersResource, c.ns, name, opts), &certmanagerv1.Issuer{}) - - return err } -// DeleteCollection deletes a collection of objects. -func (c *FakeIssuers) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error { - action := testing.NewDeleteCollectionAction(issuersResource, c.ns, listOpts) - - _, err := c.Fake.Invokes(action, &certmanagerv1.IssuerList{}) - return err -} - -// Patch applies the patch and returns the patched issuer. -func (c *FakeIssuers) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *certmanagerv1.Issuer, err error) { - obj, err := c.Fake. - Invokes(testing.NewPatchSubresourceAction(issuersResource, c.ns, name, pt, data, subresources...), &certmanagerv1.Issuer{}) - - if obj == nil { - return nil, err +func newFakeIssuers(fake *FakeCertmanagerV1, namespace string) typedcertmanagerv1.IssuerInterface { + return &fakeIssuers{ + gentype.NewFakeClientWithListAndApply[*v1.Issuer, *v1.IssuerList, *certmanagerv1.IssuerApplyConfiguration]( + fake.Fake, + namespace, + v1.SchemeGroupVersion.WithResource("issuers"), + v1.SchemeGroupVersion.WithKind("Issuer"), + func() *v1.Issuer { return &v1.Issuer{} }, + func() *v1.IssuerList { return &v1.IssuerList{} }, + func(dst, src *v1.IssuerList) { dst.ListMeta = src.ListMeta }, + func(list *v1.IssuerList) []*v1.Issuer { return gentype.ToPointerSlice(list.Items) }, + func(list *v1.IssuerList, items []*v1.Issuer) { list.Items = gentype.FromPointerSlice(items) }, + ), + fake, } - return obj.(*certmanagerv1.Issuer), err } diff --git a/pkg/client/clientset/versioned/typed/certmanager/v1/issuer.go b/pkg/client/clientset/versioned/typed/certmanager/v1/issuer.go index 2baeb107b63..b915a9d65eb 100644 --- a/pkg/client/clientset/versioned/typed/certmanager/v1/issuer.go +++ b/pkg/client/clientset/versioned/typed/certmanager/v1/issuer.go @@ -19,15 +19,15 @@ limitations under the License. package v1 import ( - "context" - "time" + context "context" - v1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" + certmanagerv1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" + applyconfigurationscertmanagerv1 "github.com/cert-manager/cert-manager/pkg/client/applyconfigurations/certmanager/v1" scheme "github.com/cert-manager/cert-manager/pkg/client/clientset/versioned/scheme" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" types "k8s.io/apimachinery/pkg/types" watch "k8s.io/apimachinery/pkg/watch" - rest "k8s.io/client-go/rest" + gentype "k8s.io/client-go/gentype" ) // IssuersGetter has a method to return a IssuerInterface. @@ -38,158 +38,37 @@ type IssuersGetter interface { // IssuerInterface has methods to work with Issuer resources. type IssuerInterface interface { - Create(ctx context.Context, issuer *v1.Issuer, opts metav1.CreateOptions) (*v1.Issuer, error) - Update(ctx context.Context, issuer *v1.Issuer, opts metav1.UpdateOptions) (*v1.Issuer, error) - UpdateStatus(ctx context.Context, issuer *v1.Issuer, opts metav1.UpdateOptions) (*v1.Issuer, error) + Create(ctx context.Context, issuer *certmanagerv1.Issuer, opts metav1.CreateOptions) (*certmanagerv1.Issuer, error) + Update(ctx context.Context, issuer *certmanagerv1.Issuer, opts metav1.UpdateOptions) (*certmanagerv1.Issuer, error) + // Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). + UpdateStatus(ctx context.Context, issuer *certmanagerv1.Issuer, opts metav1.UpdateOptions) (*certmanagerv1.Issuer, error) Delete(ctx context.Context, name string, opts metav1.DeleteOptions) error DeleteCollection(ctx context.Context, opts metav1.DeleteOptions, listOpts metav1.ListOptions) error - Get(ctx context.Context, name string, opts metav1.GetOptions) (*v1.Issuer, error) - List(ctx context.Context, opts metav1.ListOptions) (*v1.IssuerList, error) + Get(ctx context.Context, name string, opts metav1.GetOptions) (*certmanagerv1.Issuer, error) + List(ctx context.Context, opts metav1.ListOptions) (*certmanagerv1.IssuerList, error) Watch(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error) - Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts metav1.PatchOptions, subresources ...string) (result *v1.Issuer, err error) + Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts metav1.PatchOptions, subresources ...string) (result *certmanagerv1.Issuer, err error) + Apply(ctx context.Context, issuer *applyconfigurationscertmanagerv1.IssuerApplyConfiguration, opts metav1.ApplyOptions) (result *certmanagerv1.Issuer, err error) + // Add a +genclient:noStatus comment above the type to avoid generating ApplyStatus(). + ApplyStatus(ctx context.Context, issuer *applyconfigurationscertmanagerv1.IssuerApplyConfiguration, opts metav1.ApplyOptions) (result *certmanagerv1.Issuer, err error) IssuerExpansion } // issuers implements IssuerInterface type issuers struct { - client rest.Interface - ns string + *gentype.ClientWithListAndApply[*certmanagerv1.Issuer, *certmanagerv1.IssuerList, *applyconfigurationscertmanagerv1.IssuerApplyConfiguration] } // newIssuers returns a Issuers func newIssuers(c *CertmanagerV1Client, namespace string) *issuers { return &issuers{ - client: c.RESTClient(), - ns: namespace, + gentype.NewClientWithListAndApply[*certmanagerv1.Issuer, *certmanagerv1.IssuerList, *applyconfigurationscertmanagerv1.IssuerApplyConfiguration]( + "issuers", + c.RESTClient(), + scheme.ParameterCodec, + namespace, + func() *certmanagerv1.Issuer { return &certmanagerv1.Issuer{} }, + func() *certmanagerv1.IssuerList { return &certmanagerv1.IssuerList{} }, + ), } } - -// Get takes name of the issuer, and returns the corresponding issuer object, and an error if there is any. -func (c *issuers) Get(ctx context.Context, name string, options metav1.GetOptions) (result *v1.Issuer, err error) { - result = &v1.Issuer{} - err = c.client.Get(). - Namespace(c.ns). - Resource("issuers"). - Name(name). - VersionedParams(&options, scheme.ParameterCodec). - Do(ctx). - Into(result) - return -} - -// List takes label and field selectors, and returns the list of Issuers that match those selectors. -func (c *issuers) List(ctx context.Context, opts metav1.ListOptions) (result *v1.IssuerList, err error) { - var timeout time.Duration - if opts.TimeoutSeconds != nil { - timeout = time.Duration(*opts.TimeoutSeconds) * time.Second - } - result = &v1.IssuerList{} - err = c.client.Get(). - Namespace(c.ns). - Resource("issuers"). - VersionedParams(&opts, scheme.ParameterCodec). - Timeout(timeout). - Do(ctx). - Into(result) - return -} - -// Watch returns a watch.Interface that watches the requested issuers. -func (c *issuers) Watch(ctx context.Context, opts metav1.ListOptions) (watch.Interface, error) { - var timeout time.Duration - if opts.TimeoutSeconds != nil { - timeout = time.Duration(*opts.TimeoutSeconds) * time.Second - } - opts.Watch = true - return c.client.Get(). - Namespace(c.ns). - Resource("issuers"). - VersionedParams(&opts, scheme.ParameterCodec). - Timeout(timeout). - Watch(ctx) -} - -// Create takes the representation of a issuer and creates it. Returns the server's representation of the issuer, and an error, if there is any. -func (c *issuers) Create(ctx context.Context, issuer *v1.Issuer, opts metav1.CreateOptions) (result *v1.Issuer, err error) { - result = &v1.Issuer{} - err = c.client.Post(). - Namespace(c.ns). - Resource("issuers"). - VersionedParams(&opts, scheme.ParameterCodec). - Body(issuer). - Do(ctx). - Into(result) - return -} - -// Update takes the representation of a issuer and updates it. Returns the server's representation of the issuer, and an error, if there is any. -func (c *issuers) Update(ctx context.Context, issuer *v1.Issuer, opts metav1.UpdateOptions) (result *v1.Issuer, err error) { - result = &v1.Issuer{} - err = c.client.Put(). - Namespace(c.ns). - Resource("issuers"). - Name(issuer.Name). - VersionedParams(&opts, scheme.ParameterCodec). - Body(issuer). - Do(ctx). - Into(result) - return -} - -// UpdateStatus was generated because the type contains a Status member. -// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus(). -func (c *issuers) UpdateStatus(ctx context.Context, issuer *v1.Issuer, opts metav1.UpdateOptions) (result *v1.Issuer, err error) { - result = &v1.Issuer{} - err = c.client.Put(). - Namespace(c.ns). - Resource("issuers"). - Name(issuer.Name). - SubResource("status"). - VersionedParams(&opts, scheme.ParameterCodec). - Body(issuer). - Do(ctx). - Into(result) - return -} - -// Delete takes name of the issuer and deletes it. Returns an error if one occurs. -func (c *issuers) Delete(ctx context.Context, name string, opts metav1.DeleteOptions) error { - return c.client.Delete(). - Namespace(c.ns). - Resource("issuers"). - Name(name). - Body(&opts). - Do(ctx). - Error() -} - -// DeleteCollection deletes a collection of objects. -func (c *issuers) DeleteCollection(ctx context.Context, opts metav1.DeleteOptions, listOpts metav1.ListOptions) error { - var timeout time.Duration - if listOpts.TimeoutSeconds != nil { - timeout = time.Duration(*listOpts.TimeoutSeconds) * time.Second - } - return c.client.Delete(). - Namespace(c.ns). - Resource("issuers"). - VersionedParams(&listOpts, scheme.ParameterCodec). - Timeout(timeout). - Body(&opts). - Do(ctx). - Error() -} - -// Patch applies the patch and returns the patched issuer. -func (c *issuers) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts metav1.PatchOptions, subresources ...string) (result *v1.Issuer, err error) { - result = &v1.Issuer{} - err = c.client.Patch(pt). - Namespace(c.ns). - Resource("issuers"). - Name(name). - SubResource(subresources...). - VersionedParams(&opts, scheme.ParameterCodec). - Body(data). - Do(ctx). - Into(result) - return -} diff --git a/pkg/client/informers/externalversions/acme/v1/challenge.go b/pkg/client/informers/externalversions/acme/v1/challenge.go index 18e0d07ace0..7be5fe12a49 100644 --- a/pkg/client/informers/externalversions/acme/v1/challenge.go +++ b/pkg/client/informers/externalversions/acme/v1/challenge.go @@ -19,13 +19,13 @@ limitations under the License. package v1 import ( - "context" + context "context" time "time" - acmev1 "github.com/cert-manager/cert-manager/pkg/apis/acme/v1" + apisacmev1 "github.com/cert-manager/cert-manager/pkg/apis/acme/v1" versioned "github.com/cert-manager/cert-manager/pkg/client/clientset/versioned" internalinterfaces "github.com/cert-manager/cert-manager/pkg/client/informers/externalversions/internalinterfaces" - v1 "github.com/cert-manager/cert-manager/pkg/client/listers/acme/v1" + acmev1 "github.com/cert-manager/cert-manager/pkg/client/listers/acme/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" watch "k8s.io/apimachinery/pkg/watch" @@ -36,7 +36,7 @@ import ( // Challenges. type ChallengeInformer interface { Informer() cache.SharedIndexInformer - Lister() v1.ChallengeLister + Lister() acmev1.ChallengeLister } type challengeInformer struct { @@ -62,16 +62,28 @@ func NewFilteredChallengeInformer(client versioned.Interface, namespace string, if tweakListOptions != nil { tweakListOptions(&options) } - return client.AcmeV1().Challenges(namespace).List(context.TODO(), options) + return client.AcmeV1().Challenges(namespace).List(context.Background(), options) }, WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { tweakListOptions(&options) } - return client.AcmeV1().Challenges(namespace).Watch(context.TODO(), options) + return client.AcmeV1().Challenges(namespace).Watch(context.Background(), options) + }, + ListWithContextFunc: func(ctx context.Context, options metav1.ListOptions) (runtime.Object, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.AcmeV1().Challenges(namespace).List(ctx, options) + }, + WatchFuncWithContext: func(ctx context.Context, options metav1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.AcmeV1().Challenges(namespace).Watch(ctx, options) }, }, - &acmev1.Challenge{}, + &apisacmev1.Challenge{}, resyncPeriod, indexers, ) @@ -82,9 +94,9 @@ func (f *challengeInformer) defaultInformer(client versioned.Interface, resyncPe } func (f *challengeInformer) Informer() cache.SharedIndexInformer { - return f.factory.InformerFor(&acmev1.Challenge{}, f.defaultInformer) + return f.factory.InformerFor(&apisacmev1.Challenge{}, f.defaultInformer) } -func (f *challengeInformer) Lister() v1.ChallengeLister { - return v1.NewChallengeLister(f.Informer().GetIndexer()) +func (f *challengeInformer) Lister() acmev1.ChallengeLister { + return acmev1.NewChallengeLister(f.Informer().GetIndexer()) } diff --git a/pkg/client/informers/externalversions/acme/v1/order.go b/pkg/client/informers/externalversions/acme/v1/order.go index 83fc4b61770..1fbf2eb8c63 100644 --- a/pkg/client/informers/externalversions/acme/v1/order.go +++ b/pkg/client/informers/externalversions/acme/v1/order.go @@ -19,13 +19,13 @@ limitations under the License. package v1 import ( - "context" + context "context" time "time" - acmev1 "github.com/cert-manager/cert-manager/pkg/apis/acme/v1" + apisacmev1 "github.com/cert-manager/cert-manager/pkg/apis/acme/v1" versioned "github.com/cert-manager/cert-manager/pkg/client/clientset/versioned" internalinterfaces "github.com/cert-manager/cert-manager/pkg/client/informers/externalversions/internalinterfaces" - v1 "github.com/cert-manager/cert-manager/pkg/client/listers/acme/v1" + acmev1 "github.com/cert-manager/cert-manager/pkg/client/listers/acme/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" watch "k8s.io/apimachinery/pkg/watch" @@ -36,7 +36,7 @@ import ( // Orders. type OrderInformer interface { Informer() cache.SharedIndexInformer - Lister() v1.OrderLister + Lister() acmev1.OrderLister } type orderInformer struct { @@ -62,16 +62,28 @@ func NewFilteredOrderInformer(client versioned.Interface, namespace string, resy if tweakListOptions != nil { tweakListOptions(&options) } - return client.AcmeV1().Orders(namespace).List(context.TODO(), options) + return client.AcmeV1().Orders(namespace).List(context.Background(), options) }, WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { tweakListOptions(&options) } - return client.AcmeV1().Orders(namespace).Watch(context.TODO(), options) + return client.AcmeV1().Orders(namespace).Watch(context.Background(), options) + }, + ListWithContextFunc: func(ctx context.Context, options metav1.ListOptions) (runtime.Object, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.AcmeV1().Orders(namespace).List(ctx, options) + }, + WatchFuncWithContext: func(ctx context.Context, options metav1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.AcmeV1().Orders(namespace).Watch(ctx, options) }, }, - &acmev1.Order{}, + &apisacmev1.Order{}, resyncPeriod, indexers, ) @@ -82,9 +94,9 @@ func (f *orderInformer) defaultInformer(client versioned.Interface, resyncPeriod } func (f *orderInformer) Informer() cache.SharedIndexInformer { - return f.factory.InformerFor(&acmev1.Order{}, f.defaultInformer) + return f.factory.InformerFor(&apisacmev1.Order{}, f.defaultInformer) } -func (f *orderInformer) Lister() v1.OrderLister { - return v1.NewOrderLister(f.Informer().GetIndexer()) +func (f *orderInformer) Lister() acmev1.OrderLister { + return acmev1.NewOrderLister(f.Informer().GetIndexer()) } diff --git a/pkg/client/informers/externalversions/certmanager/v1/certificate.go b/pkg/client/informers/externalversions/certmanager/v1/certificate.go index eb89488b4b9..869811f64f7 100644 --- a/pkg/client/informers/externalversions/certmanager/v1/certificate.go +++ b/pkg/client/informers/externalversions/certmanager/v1/certificate.go @@ -19,13 +19,13 @@ limitations under the License. package v1 import ( - "context" + context "context" time "time" - certmanagerv1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" + apiscertmanagerv1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" versioned "github.com/cert-manager/cert-manager/pkg/client/clientset/versioned" internalinterfaces "github.com/cert-manager/cert-manager/pkg/client/informers/externalversions/internalinterfaces" - v1 "github.com/cert-manager/cert-manager/pkg/client/listers/certmanager/v1" + certmanagerv1 "github.com/cert-manager/cert-manager/pkg/client/listers/certmanager/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" watch "k8s.io/apimachinery/pkg/watch" @@ -36,7 +36,7 @@ import ( // Certificates. type CertificateInformer interface { Informer() cache.SharedIndexInformer - Lister() v1.CertificateLister + Lister() certmanagerv1.CertificateLister } type certificateInformer struct { @@ -62,16 +62,28 @@ func NewFilteredCertificateInformer(client versioned.Interface, namespace string if tweakListOptions != nil { tweakListOptions(&options) } - return client.CertmanagerV1().Certificates(namespace).List(context.TODO(), options) + return client.CertmanagerV1().Certificates(namespace).List(context.Background(), options) }, WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { tweakListOptions(&options) } - return client.CertmanagerV1().Certificates(namespace).Watch(context.TODO(), options) + return client.CertmanagerV1().Certificates(namespace).Watch(context.Background(), options) + }, + ListWithContextFunc: func(ctx context.Context, options metav1.ListOptions) (runtime.Object, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.CertmanagerV1().Certificates(namespace).List(ctx, options) + }, + WatchFuncWithContext: func(ctx context.Context, options metav1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.CertmanagerV1().Certificates(namespace).Watch(ctx, options) }, }, - &certmanagerv1.Certificate{}, + &apiscertmanagerv1.Certificate{}, resyncPeriod, indexers, ) @@ -82,9 +94,9 @@ func (f *certificateInformer) defaultInformer(client versioned.Interface, resync } func (f *certificateInformer) Informer() cache.SharedIndexInformer { - return f.factory.InformerFor(&certmanagerv1.Certificate{}, f.defaultInformer) + return f.factory.InformerFor(&apiscertmanagerv1.Certificate{}, f.defaultInformer) } -func (f *certificateInformer) Lister() v1.CertificateLister { - return v1.NewCertificateLister(f.Informer().GetIndexer()) +func (f *certificateInformer) Lister() certmanagerv1.CertificateLister { + return certmanagerv1.NewCertificateLister(f.Informer().GetIndexer()) } diff --git a/pkg/client/informers/externalversions/certmanager/v1/certificaterequest.go b/pkg/client/informers/externalversions/certmanager/v1/certificaterequest.go index d07cb2a2c81..e558ba8a6d8 100644 --- a/pkg/client/informers/externalversions/certmanager/v1/certificaterequest.go +++ b/pkg/client/informers/externalversions/certmanager/v1/certificaterequest.go @@ -19,13 +19,13 @@ limitations under the License. package v1 import ( - "context" + context "context" time "time" - certmanagerv1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" + apiscertmanagerv1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" versioned "github.com/cert-manager/cert-manager/pkg/client/clientset/versioned" internalinterfaces "github.com/cert-manager/cert-manager/pkg/client/informers/externalversions/internalinterfaces" - v1 "github.com/cert-manager/cert-manager/pkg/client/listers/certmanager/v1" + certmanagerv1 "github.com/cert-manager/cert-manager/pkg/client/listers/certmanager/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" watch "k8s.io/apimachinery/pkg/watch" @@ -36,7 +36,7 @@ import ( // CertificateRequests. type CertificateRequestInformer interface { Informer() cache.SharedIndexInformer - Lister() v1.CertificateRequestLister + Lister() certmanagerv1.CertificateRequestLister } type certificateRequestInformer struct { @@ -62,16 +62,28 @@ func NewFilteredCertificateRequestInformer(client versioned.Interface, namespace if tweakListOptions != nil { tweakListOptions(&options) } - return client.CertmanagerV1().CertificateRequests(namespace).List(context.TODO(), options) + return client.CertmanagerV1().CertificateRequests(namespace).List(context.Background(), options) }, WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { tweakListOptions(&options) } - return client.CertmanagerV1().CertificateRequests(namespace).Watch(context.TODO(), options) + return client.CertmanagerV1().CertificateRequests(namespace).Watch(context.Background(), options) + }, + ListWithContextFunc: func(ctx context.Context, options metav1.ListOptions) (runtime.Object, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.CertmanagerV1().CertificateRequests(namespace).List(ctx, options) + }, + WatchFuncWithContext: func(ctx context.Context, options metav1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.CertmanagerV1().CertificateRequests(namespace).Watch(ctx, options) }, }, - &certmanagerv1.CertificateRequest{}, + &apiscertmanagerv1.CertificateRequest{}, resyncPeriod, indexers, ) @@ -82,9 +94,9 @@ func (f *certificateRequestInformer) defaultInformer(client versioned.Interface, } func (f *certificateRequestInformer) Informer() cache.SharedIndexInformer { - return f.factory.InformerFor(&certmanagerv1.CertificateRequest{}, f.defaultInformer) + return f.factory.InformerFor(&apiscertmanagerv1.CertificateRequest{}, f.defaultInformer) } -func (f *certificateRequestInformer) Lister() v1.CertificateRequestLister { - return v1.NewCertificateRequestLister(f.Informer().GetIndexer()) +func (f *certificateRequestInformer) Lister() certmanagerv1.CertificateRequestLister { + return certmanagerv1.NewCertificateRequestLister(f.Informer().GetIndexer()) } diff --git a/pkg/client/informers/externalversions/certmanager/v1/clusterissuer.go b/pkg/client/informers/externalversions/certmanager/v1/clusterissuer.go index 6f5e6d62111..1d8e4bd5a39 100644 --- a/pkg/client/informers/externalversions/certmanager/v1/clusterissuer.go +++ b/pkg/client/informers/externalversions/certmanager/v1/clusterissuer.go @@ -19,13 +19,13 @@ limitations under the License. package v1 import ( - "context" + context "context" time "time" - certmanagerv1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" + apiscertmanagerv1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" versioned "github.com/cert-manager/cert-manager/pkg/client/clientset/versioned" internalinterfaces "github.com/cert-manager/cert-manager/pkg/client/informers/externalversions/internalinterfaces" - v1 "github.com/cert-manager/cert-manager/pkg/client/listers/certmanager/v1" + certmanagerv1 "github.com/cert-manager/cert-manager/pkg/client/listers/certmanager/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" watch "k8s.io/apimachinery/pkg/watch" @@ -36,7 +36,7 @@ import ( // ClusterIssuers. type ClusterIssuerInformer interface { Informer() cache.SharedIndexInformer - Lister() v1.ClusterIssuerLister + Lister() certmanagerv1.ClusterIssuerLister } type clusterIssuerInformer struct { @@ -61,16 +61,28 @@ func NewFilteredClusterIssuerInformer(client versioned.Interface, resyncPeriod t if tweakListOptions != nil { tweakListOptions(&options) } - return client.CertmanagerV1().ClusterIssuers().List(context.TODO(), options) + return client.CertmanagerV1().ClusterIssuers().List(context.Background(), options) }, WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { tweakListOptions(&options) } - return client.CertmanagerV1().ClusterIssuers().Watch(context.TODO(), options) + return client.CertmanagerV1().ClusterIssuers().Watch(context.Background(), options) + }, + ListWithContextFunc: func(ctx context.Context, options metav1.ListOptions) (runtime.Object, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.CertmanagerV1().ClusterIssuers().List(ctx, options) + }, + WatchFuncWithContext: func(ctx context.Context, options metav1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.CertmanagerV1().ClusterIssuers().Watch(ctx, options) }, }, - &certmanagerv1.ClusterIssuer{}, + &apiscertmanagerv1.ClusterIssuer{}, resyncPeriod, indexers, ) @@ -81,9 +93,9 @@ func (f *clusterIssuerInformer) defaultInformer(client versioned.Interface, resy } func (f *clusterIssuerInformer) Informer() cache.SharedIndexInformer { - return f.factory.InformerFor(&certmanagerv1.ClusterIssuer{}, f.defaultInformer) + return f.factory.InformerFor(&apiscertmanagerv1.ClusterIssuer{}, f.defaultInformer) } -func (f *clusterIssuerInformer) Lister() v1.ClusterIssuerLister { - return v1.NewClusterIssuerLister(f.Informer().GetIndexer()) +func (f *clusterIssuerInformer) Lister() certmanagerv1.ClusterIssuerLister { + return certmanagerv1.NewClusterIssuerLister(f.Informer().GetIndexer()) } diff --git a/pkg/client/informers/externalversions/certmanager/v1/issuer.go b/pkg/client/informers/externalversions/certmanager/v1/issuer.go index 14fbf0ae1ef..664608dd482 100644 --- a/pkg/client/informers/externalversions/certmanager/v1/issuer.go +++ b/pkg/client/informers/externalversions/certmanager/v1/issuer.go @@ -19,13 +19,13 @@ limitations under the License. package v1 import ( - "context" + context "context" time "time" - certmanagerv1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" + apiscertmanagerv1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" versioned "github.com/cert-manager/cert-manager/pkg/client/clientset/versioned" internalinterfaces "github.com/cert-manager/cert-manager/pkg/client/informers/externalversions/internalinterfaces" - v1 "github.com/cert-manager/cert-manager/pkg/client/listers/certmanager/v1" + certmanagerv1 "github.com/cert-manager/cert-manager/pkg/client/listers/certmanager/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" watch "k8s.io/apimachinery/pkg/watch" @@ -36,7 +36,7 @@ import ( // Issuers. type IssuerInformer interface { Informer() cache.SharedIndexInformer - Lister() v1.IssuerLister + Lister() certmanagerv1.IssuerLister } type issuerInformer struct { @@ -62,16 +62,28 @@ func NewFilteredIssuerInformer(client versioned.Interface, namespace string, res if tweakListOptions != nil { tweakListOptions(&options) } - return client.CertmanagerV1().Issuers(namespace).List(context.TODO(), options) + return client.CertmanagerV1().Issuers(namespace).List(context.Background(), options) }, WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { if tweakListOptions != nil { tweakListOptions(&options) } - return client.CertmanagerV1().Issuers(namespace).Watch(context.TODO(), options) + return client.CertmanagerV1().Issuers(namespace).Watch(context.Background(), options) + }, + ListWithContextFunc: func(ctx context.Context, options metav1.ListOptions) (runtime.Object, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.CertmanagerV1().Issuers(namespace).List(ctx, options) + }, + WatchFuncWithContext: func(ctx context.Context, options metav1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.CertmanagerV1().Issuers(namespace).Watch(ctx, options) }, }, - &certmanagerv1.Issuer{}, + &apiscertmanagerv1.Issuer{}, resyncPeriod, indexers, ) @@ -82,9 +94,9 @@ func (f *issuerInformer) defaultInformer(client versioned.Interface, resyncPerio } func (f *issuerInformer) Informer() cache.SharedIndexInformer { - return f.factory.InformerFor(&certmanagerv1.Issuer{}, f.defaultInformer) + return f.factory.InformerFor(&apiscertmanagerv1.Issuer{}, f.defaultInformer) } -func (f *issuerInformer) Lister() v1.IssuerLister { - return v1.NewIssuerLister(f.Informer().GetIndexer()) +func (f *issuerInformer) Lister() certmanagerv1.IssuerLister { + return certmanagerv1.NewIssuerLister(f.Informer().GetIndexer()) } diff --git a/pkg/client/informers/externalversions/factory.go b/pkg/client/informers/externalversions/factory.go index bd544cee97f..c59bddf337f 100644 --- a/pkg/client/informers/externalversions/factory.go +++ b/pkg/client/informers/externalversions/factory.go @@ -43,11 +43,17 @@ type sharedInformerFactory struct { lock sync.Mutex defaultResync time.Duration customResync map[reflect.Type]time.Duration + transform cache.TransformFunc informers map[reflect.Type]cache.SharedIndexInformer // startedInformers is used for tracking which informers have been started. // This allows Start() to be called multiple times safely. startedInformers map[reflect.Type]bool + // wg tracks how many goroutines were started. + wg sync.WaitGroup + // shuttingDown is true when Shutdown has been called. It may still be running + // because it needs to wait for goroutines. + shuttingDown bool } // WithCustomResyncConfig sets a custom resync period for the specified informer types. @@ -76,6 +82,14 @@ func WithNamespace(namespace string) SharedInformerOption { } } +// WithTransform sets a transform on all informers. +func WithTransform(transform cache.TransformFunc) SharedInformerOption { + return func(factory *sharedInformerFactory) *sharedInformerFactory { + factory.transform = transform + return factory + } +} + // NewSharedInformerFactory constructs a new instance of sharedInformerFactory for all namespaces. func NewSharedInformerFactory(client versioned.Interface, defaultResync time.Duration) SharedInformerFactory { return NewSharedInformerFactoryWithOptions(client, defaultResync) @@ -108,20 +122,39 @@ func NewSharedInformerFactoryWithOptions(client versioned.Interface, defaultResy return factory } -// Start initializes all requested informers. func (f *sharedInformerFactory) Start(stopCh <-chan struct{}) { f.lock.Lock() defer f.lock.Unlock() + if f.shuttingDown { + return + } + for informerType, informer := range f.informers { if !f.startedInformers[informerType] { - go informer.Run(stopCh) + f.wg.Add(1) + // We need a new variable in each loop iteration, + // otherwise the goroutine would use the loop variable + // and that keeps changing. + informer := informer + go func() { + defer f.wg.Done() + informer.Run(stopCh) + }() f.startedInformers[informerType] = true } } } -// WaitForCacheSync waits for all started informers' cache were synced. +func (f *sharedInformerFactory) Shutdown() { + f.lock.Lock() + f.shuttingDown = true + f.lock.Unlock() + + // Will return immediately if there is nothing to wait for. + f.wg.Wait() +} + func (f *sharedInformerFactory) WaitForCacheSync(stopCh <-chan struct{}) map[reflect.Type]bool { informers := func() map[reflect.Type]cache.SharedIndexInformer { f.lock.Lock() @@ -143,7 +176,7 @@ func (f *sharedInformerFactory) WaitForCacheSync(stopCh <-chan struct{}) map[ref return res } -// InternalInformerFor returns the SharedIndexInformer for obj using an internal +// InformerFor returns the SharedIndexInformer for obj using an internal // client. func (f *sharedInformerFactory) InformerFor(obj runtime.Object, newFunc internalinterfaces.NewInformerFunc) cache.SharedIndexInformer { f.lock.Lock() @@ -161,6 +194,7 @@ func (f *sharedInformerFactory) InformerFor(obj runtime.Object, newFunc internal } informer = newFunc(f.client, resyncPeriod) + informer.SetTransform(f.transform) f.informers[informerType] = informer return informer @@ -168,11 +202,59 @@ func (f *sharedInformerFactory) InformerFor(obj runtime.Object, newFunc internal // SharedInformerFactory provides shared informers for resources in all known // API group versions. +// +// It is typically used like this: +// +// ctx, cancel := context.Background() +// defer cancel() +// factory := NewSharedInformerFactory(client, resyncPeriod) +// defer factory.WaitForStop() // Returns immediately if nothing was started. +// genericInformer := factory.ForResource(resource) +// typedInformer := factory.SomeAPIGroup().V1().SomeType() +// factory.Start(ctx.Done()) // Start processing these informers. +// synced := factory.WaitForCacheSync(ctx.Done()) +// for v, ok := range synced { +// if !ok { +// fmt.Fprintf(os.Stderr, "caches failed to sync: %v", v) +// return +// } +// } +// +// // Creating informers can also be created after Start, but then +// // Start must be called again: +// anotherGenericInformer := factory.ForResource(resource) +// factory.Start(ctx.Done()) type SharedInformerFactory interface { internalinterfaces.SharedInformerFactory - ForResource(resource schema.GroupVersionResource) (GenericInformer, error) + + // Start initializes all requested informers. They are handled in goroutines + // which run until the stop channel gets closed. + // Warning: Start does not block. When run in a go-routine, it will race with a later WaitForCacheSync. + Start(stopCh <-chan struct{}) + + // Shutdown marks a factory as shutting down. At that point no new + // informers can be started anymore and Start will return without + // doing anything. + // + // In addition, Shutdown blocks until all goroutines have terminated. For that + // to happen, the close channel(s) that they were started with must be closed, + // either before Shutdown gets called or while it is waiting. + // + // Shutdown may be called multiple times, even concurrently. All such calls will + // block until all goroutines have terminated. + Shutdown() + + // WaitForCacheSync blocks until all started informers' caches were synced + // or the stop channel gets closed. WaitForCacheSync(stopCh <-chan struct{}) map[reflect.Type]bool + // ForResource gives generic access to a shared informer of the matching type. + ForResource(resource schema.GroupVersionResource) (GenericInformer, error) + + // InformerFor returns the SharedIndexInformer for obj using an internal + // client. + InformerFor(obj runtime.Object, newFunc internalinterfaces.NewInformerFunc) cache.SharedIndexInformer + Acme() acme.Interface Certmanager() certmanager.Interface } diff --git a/pkg/client/informers/externalversions/generic.go b/pkg/client/informers/externalversions/generic.go index 837635bb205..e8475fc2464 100644 --- a/pkg/client/informers/externalversions/generic.go +++ b/pkg/client/informers/externalversions/generic.go @@ -19,7 +19,7 @@ limitations under the License. package externalversions import ( - "fmt" + fmt "fmt" v1 "github.com/cert-manager/cert-manager/pkg/apis/acme/v1" certmanagerv1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" diff --git a/pkg/client/listers/acme/v1/challenge.go b/pkg/client/listers/acme/v1/challenge.go index 80e8eae7196..7a83cb813a0 100644 --- a/pkg/client/listers/acme/v1/challenge.go +++ b/pkg/client/listers/acme/v1/challenge.go @@ -19,10 +19,10 @@ limitations under the License. package v1 import ( - v1 "github.com/cert-manager/cert-manager/pkg/apis/acme/v1" - "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/labels" - "k8s.io/client-go/tools/cache" + acmev1 "github.com/cert-manager/cert-manager/pkg/apis/acme/v1" + labels "k8s.io/apimachinery/pkg/labels" + listers "k8s.io/client-go/listers" + cache "k8s.io/client-go/tools/cache" ) // ChallengeLister helps list Challenges. @@ -30,7 +30,7 @@ import ( type ChallengeLister interface { // List lists all Challenges in the indexer. // Objects returned here must be treated as read-only. - List(selector labels.Selector) (ret []*v1.Challenge, err error) + List(selector labels.Selector) (ret []*acmev1.Challenge, err error) // Challenges returns an object that can list and get Challenges. Challenges(namespace string) ChallengeNamespaceLister ChallengeListerExpansion @@ -38,25 +38,17 @@ type ChallengeLister interface { // challengeLister implements the ChallengeLister interface. type challengeLister struct { - indexer cache.Indexer + listers.ResourceIndexer[*acmev1.Challenge] } // NewChallengeLister returns a new ChallengeLister. func NewChallengeLister(indexer cache.Indexer) ChallengeLister { - return &challengeLister{indexer: indexer} -} - -// List lists all Challenges in the indexer. -func (s *challengeLister) List(selector labels.Selector) (ret []*v1.Challenge, err error) { - err = cache.ListAll(s.indexer, selector, func(m interface{}) { - ret = append(ret, m.(*v1.Challenge)) - }) - return ret, err + return &challengeLister{listers.New[*acmev1.Challenge](indexer, acmev1.Resource("challenge"))} } // Challenges returns an object that can list and get Challenges. func (s *challengeLister) Challenges(namespace string) ChallengeNamespaceLister { - return challengeNamespaceLister{indexer: s.indexer, namespace: namespace} + return challengeNamespaceLister{listers.NewNamespaced[*acmev1.Challenge](s.ResourceIndexer, namespace)} } // ChallengeNamespaceLister helps list and get Challenges. @@ -64,36 +56,15 @@ func (s *challengeLister) Challenges(namespace string) ChallengeNamespaceLister type ChallengeNamespaceLister interface { // List lists all Challenges in the indexer for a given namespace. // Objects returned here must be treated as read-only. - List(selector labels.Selector) (ret []*v1.Challenge, err error) + List(selector labels.Selector) (ret []*acmev1.Challenge, err error) // Get retrieves the Challenge from the indexer for a given namespace and name. // Objects returned here must be treated as read-only. - Get(name string) (*v1.Challenge, error) + Get(name string) (*acmev1.Challenge, error) ChallengeNamespaceListerExpansion } // challengeNamespaceLister implements the ChallengeNamespaceLister // interface. type challengeNamespaceLister struct { - indexer cache.Indexer - namespace string -} - -// List lists all Challenges in the indexer for a given namespace. -func (s challengeNamespaceLister) List(selector labels.Selector) (ret []*v1.Challenge, err error) { - err = cache.ListAllByNamespace(s.indexer, s.namespace, selector, func(m interface{}) { - ret = append(ret, m.(*v1.Challenge)) - }) - return ret, err -} - -// Get retrieves the Challenge from the indexer for a given namespace and name. -func (s challengeNamespaceLister) Get(name string) (*v1.Challenge, error) { - obj, exists, err := s.indexer.GetByKey(s.namespace + "/" + name) - if err != nil { - return nil, err - } - if !exists { - return nil, errors.NewNotFound(v1.Resource("challenge"), name) - } - return obj.(*v1.Challenge), nil + listers.ResourceIndexer[*acmev1.Challenge] } diff --git a/pkg/client/listers/acme/v1/order.go b/pkg/client/listers/acme/v1/order.go index c3e32355974..7536b0b23c4 100644 --- a/pkg/client/listers/acme/v1/order.go +++ b/pkg/client/listers/acme/v1/order.go @@ -19,10 +19,10 @@ limitations under the License. package v1 import ( - v1 "github.com/cert-manager/cert-manager/pkg/apis/acme/v1" - "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/labels" - "k8s.io/client-go/tools/cache" + acmev1 "github.com/cert-manager/cert-manager/pkg/apis/acme/v1" + labels "k8s.io/apimachinery/pkg/labels" + listers "k8s.io/client-go/listers" + cache "k8s.io/client-go/tools/cache" ) // OrderLister helps list Orders. @@ -30,7 +30,7 @@ import ( type OrderLister interface { // List lists all Orders in the indexer. // Objects returned here must be treated as read-only. - List(selector labels.Selector) (ret []*v1.Order, err error) + List(selector labels.Selector) (ret []*acmev1.Order, err error) // Orders returns an object that can list and get Orders. Orders(namespace string) OrderNamespaceLister OrderListerExpansion @@ -38,25 +38,17 @@ type OrderLister interface { // orderLister implements the OrderLister interface. type orderLister struct { - indexer cache.Indexer + listers.ResourceIndexer[*acmev1.Order] } // NewOrderLister returns a new OrderLister. func NewOrderLister(indexer cache.Indexer) OrderLister { - return &orderLister{indexer: indexer} -} - -// List lists all Orders in the indexer. -func (s *orderLister) List(selector labels.Selector) (ret []*v1.Order, err error) { - err = cache.ListAll(s.indexer, selector, func(m interface{}) { - ret = append(ret, m.(*v1.Order)) - }) - return ret, err + return &orderLister{listers.New[*acmev1.Order](indexer, acmev1.Resource("order"))} } // Orders returns an object that can list and get Orders. func (s *orderLister) Orders(namespace string) OrderNamespaceLister { - return orderNamespaceLister{indexer: s.indexer, namespace: namespace} + return orderNamespaceLister{listers.NewNamespaced[*acmev1.Order](s.ResourceIndexer, namespace)} } // OrderNamespaceLister helps list and get Orders. @@ -64,36 +56,15 @@ func (s *orderLister) Orders(namespace string) OrderNamespaceLister { type OrderNamespaceLister interface { // List lists all Orders in the indexer for a given namespace. // Objects returned here must be treated as read-only. - List(selector labels.Selector) (ret []*v1.Order, err error) + List(selector labels.Selector) (ret []*acmev1.Order, err error) // Get retrieves the Order from the indexer for a given namespace and name. // Objects returned here must be treated as read-only. - Get(name string) (*v1.Order, error) + Get(name string) (*acmev1.Order, error) OrderNamespaceListerExpansion } // orderNamespaceLister implements the OrderNamespaceLister // interface. type orderNamespaceLister struct { - indexer cache.Indexer - namespace string -} - -// List lists all Orders in the indexer for a given namespace. -func (s orderNamespaceLister) List(selector labels.Selector) (ret []*v1.Order, err error) { - err = cache.ListAllByNamespace(s.indexer, s.namespace, selector, func(m interface{}) { - ret = append(ret, m.(*v1.Order)) - }) - return ret, err -} - -// Get retrieves the Order from the indexer for a given namespace and name. -func (s orderNamespaceLister) Get(name string) (*v1.Order, error) { - obj, exists, err := s.indexer.GetByKey(s.namespace + "/" + name) - if err != nil { - return nil, err - } - if !exists { - return nil, errors.NewNotFound(v1.Resource("order"), name) - } - return obj.(*v1.Order), nil + listers.ResourceIndexer[*acmev1.Order] } diff --git a/pkg/client/listers/certmanager/v1/certificate.go b/pkg/client/listers/certmanager/v1/certificate.go index 9990d56dc1a..8c0c011ba75 100644 --- a/pkg/client/listers/certmanager/v1/certificate.go +++ b/pkg/client/listers/certmanager/v1/certificate.go @@ -19,10 +19,10 @@ limitations under the License. package v1 import ( - v1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" - "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/labels" - "k8s.io/client-go/tools/cache" + certmanagerv1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" + labels "k8s.io/apimachinery/pkg/labels" + listers "k8s.io/client-go/listers" + cache "k8s.io/client-go/tools/cache" ) // CertificateLister helps list Certificates. @@ -30,7 +30,7 @@ import ( type CertificateLister interface { // List lists all Certificates in the indexer. // Objects returned here must be treated as read-only. - List(selector labels.Selector) (ret []*v1.Certificate, err error) + List(selector labels.Selector) (ret []*certmanagerv1.Certificate, err error) // Certificates returns an object that can list and get Certificates. Certificates(namespace string) CertificateNamespaceLister CertificateListerExpansion @@ -38,25 +38,17 @@ type CertificateLister interface { // certificateLister implements the CertificateLister interface. type certificateLister struct { - indexer cache.Indexer + listers.ResourceIndexer[*certmanagerv1.Certificate] } // NewCertificateLister returns a new CertificateLister. func NewCertificateLister(indexer cache.Indexer) CertificateLister { - return &certificateLister{indexer: indexer} -} - -// List lists all Certificates in the indexer. -func (s *certificateLister) List(selector labels.Selector) (ret []*v1.Certificate, err error) { - err = cache.ListAll(s.indexer, selector, func(m interface{}) { - ret = append(ret, m.(*v1.Certificate)) - }) - return ret, err + return &certificateLister{listers.New[*certmanagerv1.Certificate](indexer, certmanagerv1.Resource("certificate"))} } // Certificates returns an object that can list and get Certificates. func (s *certificateLister) Certificates(namespace string) CertificateNamespaceLister { - return certificateNamespaceLister{indexer: s.indexer, namespace: namespace} + return certificateNamespaceLister{listers.NewNamespaced[*certmanagerv1.Certificate](s.ResourceIndexer, namespace)} } // CertificateNamespaceLister helps list and get Certificates. @@ -64,36 +56,15 @@ func (s *certificateLister) Certificates(namespace string) CertificateNamespaceL type CertificateNamespaceLister interface { // List lists all Certificates in the indexer for a given namespace. // Objects returned here must be treated as read-only. - List(selector labels.Selector) (ret []*v1.Certificate, err error) + List(selector labels.Selector) (ret []*certmanagerv1.Certificate, err error) // Get retrieves the Certificate from the indexer for a given namespace and name. // Objects returned here must be treated as read-only. - Get(name string) (*v1.Certificate, error) + Get(name string) (*certmanagerv1.Certificate, error) CertificateNamespaceListerExpansion } // certificateNamespaceLister implements the CertificateNamespaceLister // interface. type certificateNamespaceLister struct { - indexer cache.Indexer - namespace string -} - -// List lists all Certificates in the indexer for a given namespace. -func (s certificateNamespaceLister) List(selector labels.Selector) (ret []*v1.Certificate, err error) { - err = cache.ListAllByNamespace(s.indexer, s.namespace, selector, func(m interface{}) { - ret = append(ret, m.(*v1.Certificate)) - }) - return ret, err -} - -// Get retrieves the Certificate from the indexer for a given namespace and name. -func (s certificateNamespaceLister) Get(name string) (*v1.Certificate, error) { - obj, exists, err := s.indexer.GetByKey(s.namespace + "/" + name) - if err != nil { - return nil, err - } - if !exists { - return nil, errors.NewNotFound(v1.Resource("certificate"), name) - } - return obj.(*v1.Certificate), nil + listers.ResourceIndexer[*certmanagerv1.Certificate] } diff --git a/pkg/client/listers/certmanager/v1/certificaterequest.go b/pkg/client/listers/certmanager/v1/certificaterequest.go index 649c71152eb..1733f7adebf 100644 --- a/pkg/client/listers/certmanager/v1/certificaterequest.go +++ b/pkg/client/listers/certmanager/v1/certificaterequest.go @@ -19,10 +19,10 @@ limitations under the License. package v1 import ( - v1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" - "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/labels" - "k8s.io/client-go/tools/cache" + certmanagerv1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" + labels "k8s.io/apimachinery/pkg/labels" + listers "k8s.io/client-go/listers" + cache "k8s.io/client-go/tools/cache" ) // CertificateRequestLister helps list CertificateRequests. @@ -30,7 +30,7 @@ import ( type CertificateRequestLister interface { // List lists all CertificateRequests in the indexer. // Objects returned here must be treated as read-only. - List(selector labels.Selector) (ret []*v1.CertificateRequest, err error) + List(selector labels.Selector) (ret []*certmanagerv1.CertificateRequest, err error) // CertificateRequests returns an object that can list and get CertificateRequests. CertificateRequests(namespace string) CertificateRequestNamespaceLister CertificateRequestListerExpansion @@ -38,25 +38,17 @@ type CertificateRequestLister interface { // certificateRequestLister implements the CertificateRequestLister interface. type certificateRequestLister struct { - indexer cache.Indexer + listers.ResourceIndexer[*certmanagerv1.CertificateRequest] } // NewCertificateRequestLister returns a new CertificateRequestLister. func NewCertificateRequestLister(indexer cache.Indexer) CertificateRequestLister { - return &certificateRequestLister{indexer: indexer} -} - -// List lists all CertificateRequests in the indexer. -func (s *certificateRequestLister) List(selector labels.Selector) (ret []*v1.CertificateRequest, err error) { - err = cache.ListAll(s.indexer, selector, func(m interface{}) { - ret = append(ret, m.(*v1.CertificateRequest)) - }) - return ret, err + return &certificateRequestLister{listers.New[*certmanagerv1.CertificateRequest](indexer, certmanagerv1.Resource("certificaterequest"))} } // CertificateRequests returns an object that can list and get CertificateRequests. func (s *certificateRequestLister) CertificateRequests(namespace string) CertificateRequestNamespaceLister { - return certificateRequestNamespaceLister{indexer: s.indexer, namespace: namespace} + return certificateRequestNamespaceLister{listers.NewNamespaced[*certmanagerv1.CertificateRequest](s.ResourceIndexer, namespace)} } // CertificateRequestNamespaceLister helps list and get CertificateRequests. @@ -64,36 +56,15 @@ func (s *certificateRequestLister) CertificateRequests(namespace string) Certifi type CertificateRequestNamespaceLister interface { // List lists all CertificateRequests in the indexer for a given namespace. // Objects returned here must be treated as read-only. - List(selector labels.Selector) (ret []*v1.CertificateRequest, err error) + List(selector labels.Selector) (ret []*certmanagerv1.CertificateRequest, err error) // Get retrieves the CertificateRequest from the indexer for a given namespace and name. // Objects returned here must be treated as read-only. - Get(name string) (*v1.CertificateRequest, error) + Get(name string) (*certmanagerv1.CertificateRequest, error) CertificateRequestNamespaceListerExpansion } // certificateRequestNamespaceLister implements the CertificateRequestNamespaceLister // interface. type certificateRequestNamespaceLister struct { - indexer cache.Indexer - namespace string -} - -// List lists all CertificateRequests in the indexer for a given namespace. -func (s certificateRequestNamespaceLister) List(selector labels.Selector) (ret []*v1.CertificateRequest, err error) { - err = cache.ListAllByNamespace(s.indexer, s.namespace, selector, func(m interface{}) { - ret = append(ret, m.(*v1.CertificateRequest)) - }) - return ret, err -} - -// Get retrieves the CertificateRequest from the indexer for a given namespace and name. -func (s certificateRequestNamespaceLister) Get(name string) (*v1.CertificateRequest, error) { - obj, exists, err := s.indexer.GetByKey(s.namespace + "/" + name) - if err != nil { - return nil, err - } - if !exists { - return nil, errors.NewNotFound(v1.Resource("certificaterequest"), name) - } - return obj.(*v1.CertificateRequest), nil + listers.ResourceIndexer[*certmanagerv1.CertificateRequest] } diff --git a/pkg/client/listers/certmanager/v1/clusterissuer.go b/pkg/client/listers/certmanager/v1/clusterissuer.go index a362980b8d6..d284de60cbd 100644 --- a/pkg/client/listers/certmanager/v1/clusterissuer.go +++ b/pkg/client/listers/certmanager/v1/clusterissuer.go @@ -19,10 +19,10 @@ limitations under the License. package v1 import ( - v1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" - "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/labels" - "k8s.io/client-go/tools/cache" + certmanagerv1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" + labels "k8s.io/apimachinery/pkg/labels" + listers "k8s.io/client-go/listers" + cache "k8s.io/client-go/tools/cache" ) // ClusterIssuerLister helps list ClusterIssuers. @@ -30,39 +30,19 @@ import ( type ClusterIssuerLister interface { // List lists all ClusterIssuers in the indexer. // Objects returned here must be treated as read-only. - List(selector labels.Selector) (ret []*v1.ClusterIssuer, err error) + List(selector labels.Selector) (ret []*certmanagerv1.ClusterIssuer, err error) // Get retrieves the ClusterIssuer from the index for a given name. // Objects returned here must be treated as read-only. - Get(name string) (*v1.ClusterIssuer, error) + Get(name string) (*certmanagerv1.ClusterIssuer, error) ClusterIssuerListerExpansion } // clusterIssuerLister implements the ClusterIssuerLister interface. type clusterIssuerLister struct { - indexer cache.Indexer + listers.ResourceIndexer[*certmanagerv1.ClusterIssuer] } // NewClusterIssuerLister returns a new ClusterIssuerLister. func NewClusterIssuerLister(indexer cache.Indexer) ClusterIssuerLister { - return &clusterIssuerLister{indexer: indexer} -} - -// List lists all ClusterIssuers in the indexer. -func (s *clusterIssuerLister) List(selector labels.Selector) (ret []*v1.ClusterIssuer, err error) { - err = cache.ListAll(s.indexer, selector, func(m interface{}) { - ret = append(ret, m.(*v1.ClusterIssuer)) - }) - return ret, err -} - -// Get retrieves the ClusterIssuer from the index for a given name. -func (s *clusterIssuerLister) Get(name string) (*v1.ClusterIssuer, error) { - obj, exists, err := s.indexer.GetByKey(name) - if err != nil { - return nil, err - } - if !exists { - return nil, errors.NewNotFound(v1.Resource("clusterissuer"), name) - } - return obj.(*v1.ClusterIssuer), nil + return &clusterIssuerLister{listers.New[*certmanagerv1.ClusterIssuer](indexer, certmanagerv1.Resource("clusterissuer"))} } diff --git a/pkg/client/listers/certmanager/v1/issuer.go b/pkg/client/listers/certmanager/v1/issuer.go index 83e9458515e..53fe5a882be 100644 --- a/pkg/client/listers/certmanager/v1/issuer.go +++ b/pkg/client/listers/certmanager/v1/issuer.go @@ -19,10 +19,10 @@ limitations under the License. package v1 import ( - v1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" - "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/labels" - "k8s.io/client-go/tools/cache" + certmanagerv1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" + labels "k8s.io/apimachinery/pkg/labels" + listers "k8s.io/client-go/listers" + cache "k8s.io/client-go/tools/cache" ) // IssuerLister helps list Issuers. @@ -30,7 +30,7 @@ import ( type IssuerLister interface { // List lists all Issuers in the indexer. // Objects returned here must be treated as read-only. - List(selector labels.Selector) (ret []*v1.Issuer, err error) + List(selector labels.Selector) (ret []*certmanagerv1.Issuer, err error) // Issuers returns an object that can list and get Issuers. Issuers(namespace string) IssuerNamespaceLister IssuerListerExpansion @@ -38,25 +38,17 @@ type IssuerLister interface { // issuerLister implements the IssuerLister interface. type issuerLister struct { - indexer cache.Indexer + listers.ResourceIndexer[*certmanagerv1.Issuer] } // NewIssuerLister returns a new IssuerLister. func NewIssuerLister(indexer cache.Indexer) IssuerLister { - return &issuerLister{indexer: indexer} -} - -// List lists all Issuers in the indexer. -func (s *issuerLister) List(selector labels.Selector) (ret []*v1.Issuer, err error) { - err = cache.ListAll(s.indexer, selector, func(m interface{}) { - ret = append(ret, m.(*v1.Issuer)) - }) - return ret, err + return &issuerLister{listers.New[*certmanagerv1.Issuer](indexer, certmanagerv1.Resource("issuer"))} } // Issuers returns an object that can list and get Issuers. func (s *issuerLister) Issuers(namespace string) IssuerNamespaceLister { - return issuerNamespaceLister{indexer: s.indexer, namespace: namespace} + return issuerNamespaceLister{listers.NewNamespaced[*certmanagerv1.Issuer](s.ResourceIndexer, namespace)} } // IssuerNamespaceLister helps list and get Issuers. @@ -64,36 +56,15 @@ func (s *issuerLister) Issuers(namespace string) IssuerNamespaceLister { type IssuerNamespaceLister interface { // List lists all Issuers in the indexer for a given namespace. // Objects returned here must be treated as read-only. - List(selector labels.Selector) (ret []*v1.Issuer, err error) + List(selector labels.Selector) (ret []*certmanagerv1.Issuer, err error) // Get retrieves the Issuer from the indexer for a given namespace and name. // Objects returned here must be treated as read-only. - Get(name string) (*v1.Issuer, error) + Get(name string) (*certmanagerv1.Issuer, error) IssuerNamespaceListerExpansion } // issuerNamespaceLister implements the IssuerNamespaceLister // interface. type issuerNamespaceLister struct { - indexer cache.Indexer - namespace string -} - -// List lists all Issuers in the indexer for a given namespace. -func (s issuerNamespaceLister) List(selector labels.Selector) (ret []*v1.Issuer, err error) { - err = cache.ListAllByNamespace(s.indexer, s.namespace, selector, func(m interface{}) { - ret = append(ret, m.(*v1.Issuer)) - }) - return ret, err -} - -// Get retrieves the Issuer from the indexer for a given namespace and name. -func (s issuerNamespaceLister) Get(name string) (*v1.Issuer, error) { - obj, exists, err := s.indexer.GetByKey(s.namespace + "/" + name) - if err != nil { - return nil, err - } - if !exists { - return nil, errors.NewNotFound(v1.Resource("issuer"), name) - } - return obj.(*v1.Issuer), nil + listers.ResourceIndexer[*certmanagerv1.Issuer] } diff --git a/pkg/controller/acmechallenges/controller.go b/pkg/controller/acmechallenges/controller.go index e556d8829bd..d5fd8c2cd98 100644 --- a/pkg/controller/acmechallenges/controller.go +++ b/pkg/controller/acmechallenges/controller.go @@ -18,16 +18,18 @@ package acmechallenges import ( "context" + "fmt" "time" "github.com/go-logr/logr" corev1 "k8s.io/api/core/v1" k8sErrors "k8s.io/apimachinery/pkg/api/errors" - corelisters "k8s.io/client-go/listers/core/v1" + "k8s.io/apimachinery/pkg/types" "k8s.io/client-go/tools/cache" "k8s.io/client-go/tools/record" "k8s.io/client-go/util/workqueue" + internalinformers "github.com/cert-manager/cert-manager/internal/informers" "github.com/cert-manager/cert-manager/pkg/acme/accounts" cmacmelisters "github.com/cert-manager/cert-manager/pkg/client/listers/acme/v1" cmlisters "github.com/cert-manager/cert-manager/pkg/client/listers/certmanager/v1" @@ -50,7 +52,7 @@ type controller struct { challengeLister cmacmelisters.ChallengeLister issuerLister cmlisters.IssuerLister clusterIssuerLister cmlisters.ClusterIssuerLister - secretLister corelisters.SecretLister + secretLister internalinformers.SecretLister // ACME challenge solvers are instantiated once at the time of controller // construction. @@ -67,7 +69,7 @@ type controller struct { // maintain a reference to the workqueue for this controller // so the handleOwnedResource method can enqueue resources - queue workqueue.RateLimitingInterface + queue workqueue.TypedRateLimitingInterface[types.NamespacedName] // logger to be used by this controller log logr.Logger @@ -81,22 +83,27 @@ type controller struct { objectUpdater } -func (c *controller) Register(ctx *controllerpkg.Context) (workqueue.RateLimitingInterface, []cache.InformerSynced, error) { +func (c *controller) Register(ctx *controllerpkg.Context) (workqueue.TypedRateLimitingInterface[types.NamespacedName], []cache.InformerSynced, error) { // construct a new named logger to be reused throughout the controller c.log = logf.FromContext(ctx.RootContext, ControllerName) // create a queue used to queue up items to be processed - c.queue = workqueue.NewNamedRateLimitingQueue(workqueue.NewItemExponentialFailureRateLimiter(time.Second*5, time.Minute*30), ControllerName) + c.queue = workqueue.NewTypedRateLimitingQueueWithConfig( + controllerpkg.DefaultACMERateLimiter(), + workqueue.TypedRateLimitingQueueConfig[types.NamespacedName]{ + Name: ControllerName, + }, + ) // obtain references to all the informers used by this controller challengeInformer := ctx.SharedInformerFactory.Acme().V1().Challenges() issuerInformer := ctx.SharedInformerFactory.Certmanager().V1().Issuers() - secretInformer := ctx.KubeSharedInformerFactory.Core().V1().Secrets() + secretInformer := ctx.KubeSharedInformerFactory.Secrets() // we register these informers here so the HTTP01 solver has a synced // cache when managing pod/service/ingress resources - podInformer := ctx.KubeSharedInformerFactory.Core().V1().Pods() - serviceInformer := ctx.KubeSharedInformerFactory.Core().V1().Services() - ingressInformer := ctx.KubeSharedInformerFactory.Networking().V1().Ingresses() + podInformer := ctx.HTTP01ResourceMetadataInformersFactory.ForResource(corev1.SchemeGroupVersion.WithResource("pods")) + serviceInformer := ctx.HTTP01ResourceMetadataInformersFactory.ForResource(corev1.SchemeGroupVersion.WithResource("services")) + ingressInformer := ctx.KubeSharedInformerFactory.Ingresses() // build a list of InformerSynced functions that will be returned by the Register method. // the controller will only begin processing items once all of these informers have synced. @@ -110,7 +117,7 @@ func (c *controller) Register(ctx *controllerpkg.Context) (workqueue.RateLimitin } if ctx.GatewaySolverEnabled { - gwAPIHTTPRouteInformer := ctx.GWShared.Gateway().V1alpha2().HTTPRoutes() + gwAPIHTTPRouteInformer := ctx.GWShared.Gateway().V1().HTTPRoutes() mustSync = append(mustSync, gwAPIHTTPRouteInformer.Informer().HasSynced) } @@ -128,12 +135,14 @@ func (c *controller) Register(ctx *controllerpkg.Context) (workqueue.RateLimitin } // register handler functions - challengeInformer.Informer().AddEventHandler(&controllerpkg.QueuingEventHandler{Queue: c.queue}) + if _, err := challengeInformer.Informer().AddEventHandler(&controllerpkg.QueuingEventHandler{Queue: c.queue}); err != nil { + return nil, nil, fmt.Errorf("error setting up event handler: %v", err) + } c.helper = issuer.NewHelper(c.issuerLister, c.clusterIssuerLister) c.scheduler = scheduler.New(logf.NewContext(ctx.RootContext, c.log), c.challengeLister, ctx.SchedulerOptions.MaxConcurrentChallenges) c.recorder = ctx.Recorder - c.accountRegistry = ctx.ACMEOptions.AccountRegistry + c.accountRegistry = ctx.ACMEAccountRegistry var err error c.httpSolver, err = http.NewSolver(ctx) @@ -193,24 +202,20 @@ func (c *controller) runScheduler(ctx context.Context) { } } -func (c *controller) ProcessItem(ctx context.Context, key string) error { +func (c *controller) ProcessItem(ctx context.Context, key types.NamespacedName) error { log := logf.FromContext(ctx) - namespace, name, err := cache.SplitMetaNamespaceKey(key) - if err != nil { - log.Error(err, "invalid resource key") - return nil - } + namespace, name := key.Namespace, key.Name ch, err := c.challengeLister.Challenges(namespace).Get(name) - - if err != nil { - if k8sErrors.IsNotFound(err) { - log.Error(err, "challenge in work queue no longer exists") - return nil - } - + if err != nil && !k8sErrors.IsNotFound(err) { return err } + // WARNING: we do want to call Sync when ch.DeletionTimestamp != nil, such that + // we can perform the required cleanup and remove the finalizer. + if ch == nil { + // Challenge object no longer exists + return nil + } ctx = logf.NewContext(ctx, logf.WithResource(log, ch)) return c.Sync(ctx, ch) diff --git a/pkg/controller/acmechallenges/controller_test.go b/pkg/controller/acmechallenges/controller_test.go index 066e7c693a1..7b5eb0a8313 100644 --- a/pkg/controller/acmechallenges/controller_test.go +++ b/pkg/controller/acmechallenges/controller_test.go @@ -17,21 +17,15 @@ limitations under the License. package acmechallenges import ( - "context" "testing" + "github.com/stretchr/testify/require" "k8s.io/apimachinery/pkg/runtime" coretesting "k8s.io/client-go/testing" cmacme "github.com/cert-manager/cert-manager/pkg/apis/acme/v1" testpkg "github.com/cert-manager/cert-manager/pkg/controller/test" "github.com/cert-manager/cert-manager/test/unit/gen" - "github.com/stretchr/testify/require" -) - -const ( - randomFinalizer = "random.acme.cert-manager.io" - maxConcurrentChallenges = 60 ) func TestRunScheduler(t *testing.T) { @@ -137,7 +131,7 @@ func TestRunScheduler(t *testing.T) { _, _, err := c.Register(test.builder.Context) require.NoError(t, err) test.builder.Start() - c.runScheduler(context.Background()) + c.runScheduler(t.Context()) test.builder.CheckAndFinish() }) } diff --git a/pkg/controller/acmechallenges/finalizer.go b/pkg/controller/acmechallenges/finalizer.go index 86d80b5ff23..dd22070f52d 100644 --- a/pkg/controller/acmechallenges/finalizer.go +++ b/pkg/controller/acmechallenges/finalizer.go @@ -31,5 +31,11 @@ import ( // finalizerRequired returns true if the finalizer is not found on the challenge. func finalizerRequired(ch *cmacme.Challenge) bool { - return !sets.NewString(ch.Finalizers...).Has(cmacme.ACMEFinalizer) + return !sets.NewString(ch.Finalizers...).Has(cmacme.ACMEDomainQualifiedFinalizer) +} + +func otherFinalizerPresent(ch *cmacme.Challenge) bool { + finalizers := sets.NewString(ch.Finalizers...). + Delete(cmacme.ACMEDomainQualifiedFinalizer, cmacme.ACMELegacyFinalizer) + return len(finalizers) > 0 } diff --git a/pkg/controller/acmechallenges/finalizer_test.go b/pkg/controller/acmechallenges/finalizer_test.go index c6fae054ca7..3224ed02d99 100644 --- a/pkg/controller/acmechallenges/finalizer_test.go +++ b/pkg/controller/acmechallenges/finalizer_test.go @@ -37,13 +37,33 @@ func Test_finalizerRequired(t *testing.T) { want: true, }, { - name: "only-native-finalizer", - finalizers: []string{cmacme.ACMEFinalizer}, + name: "only-native-legacy-finalizer", + finalizers: []string{cmacme.ACMELegacyFinalizer}, + want: true, + }, + { + name: "only-native-domain-qualified-finalizer", + finalizers: []string{cmacme.ACMEDomainQualifiedFinalizer}, + want: false, + }, + { + name: "both-native-finalizers", + finalizers: []string{cmacme.ACMELegacyFinalizer, cmacme.ACMEDomainQualifiedFinalizer}, + want: false, + }, + { + name: "some-foreign-and-legacy-finalizer", + finalizers: []string{"f1", "f2", cmacme.ACMELegacyFinalizer, "f3"}, + want: true, + }, + { + name: "some-foreign-and-domain-qualified-finalizer", + finalizers: []string{"f1", "f2", cmacme.ACMEDomainQualifiedFinalizer, "f3"}, want: false, }, { - name: "some-foreign-finalizers", - finalizers: []string{"f1", "f2", cmacme.ACMEFinalizer, "f3"}, + name: "some-foreign-and-legacy-finalizer-and-domain-qualified-finalizer", + finalizers: []string{"f1", "f2", cmacme.ACMELegacyFinalizer, cmacme.ACMEDomainQualifiedFinalizer, "f3"}, want: false, }, { diff --git a/pkg/controller/acmechallenges/scheduler/scheduler.go b/pkg/controller/acmechallenges/scheduler/scheduler.go index 94509844438..73499bfcc1c 100644 --- a/pkg/controller/acmechallenges/scheduler/scheduler.go +++ b/pkg/controller/acmechallenges/scheduler/scheduler.go @@ -55,17 +55,14 @@ func (s *Scheduler) ScheduleN(n int) ([]*cmacme.Challenge, error) { return nil, err } - return s.scheduleN(n, allChallenges) + return s.scheduleN(n, allChallenges), nil } -func (s *Scheduler) scheduleN(n int, allChallenges []*cmacme.Challenge) ([]*cmacme.Challenge, error) { +func (s *Scheduler) scheduleN(n int, allChallenges []*cmacme.Challenge) []*cmacme.Challenge { // Determine the list of challenges that could feasibly be scheduled on // this pass of the scheduler. // This function returns a list of candidates sorted by creation timestamp. - candidates, inProgressChallengeCount, err := s.determineChallengeCandidates(allChallenges) - if err != nil { - return nil, err - } + candidates, inProgressChallengeCount := s.determineChallengeCandidates(allChallenges) numberToSelect := n remainingNumberAllowedChallenges := s.maxConcurrentChallenges - inProgressChallengeCount @@ -76,23 +73,18 @@ func (s *Scheduler) scheduleN(n int, allChallenges []*cmacme.Challenge) ([]*cmac numberToSelect = remainingNumberAllowedChallenges } - candidates, err = s.selectChallengesToSchedule(candidates, numberToSelect) - if err != nil { - return nil, err - } - - return candidates, nil + return s.selectChallengesToSchedule(candidates, numberToSelect) } // selectChallengesToSchedule will apply some sorting heuristic to the allowed // challenge candidates and return a maximum of N challenges that should be // scheduled for processing. -func (s *Scheduler) selectChallengesToSchedule(candidates []*cmacme.Challenge, n int) ([]*cmacme.Challenge, error) { +func (s *Scheduler) selectChallengesToSchedule(candidates []*cmacme.Challenge, n int) []*cmacme.Challenge { // Trim the candidates returned to 'n' if len(candidates) > n { candidates = candidates[:n] } - return candidates, nil + return candidates } // determineChallengeCandidates will determine which, if any, challenges can @@ -100,7 +92,7 @@ func (s *Scheduler) selectChallengesToSchedule(candidates []*cmacme.Challenge, n // processing. // The returned challenges will be sorted in ascending order based on timestamp // (i.e. the oldest challenge will be element zero). -func (s *Scheduler) determineChallengeCandidates(allChallenges []*cmacme.Challenge) ([]*cmacme.Challenge, int, error) { +func (s *Scheduler) determineChallengeCandidates(allChallenges []*cmacme.Challenge) ([]*cmacme.Challenge, int) { // consider the entire set of challenges for 'in progress', in case a challenge // has processing=true whilst still being in a 'final' state inProgress := processingChallenges(allChallenges) @@ -111,7 +103,7 @@ func (s *Scheduler) determineChallengeCandidates(allChallenges []*cmacme.Challen // hit the maximum number of challenges. if inProgressChallengeCount >= s.maxConcurrentChallenges { s.log.V(logs.DebugLevel).Info("hit maximum concurrent challenge limit. refusing to schedule more challenges.", "in_progress", len(inProgress), "max_concurrent", s.maxConcurrentChallenges) - return []*cmacme.Challenge{}, inProgressChallengeCount, nil + return []*cmacme.Challenge{}, inProgressChallengeCount } // Calculate incomplete challenges @@ -139,7 +131,7 @@ func (s *Scheduler) determineChallengeCandidates(allChallenges []*cmacme.Challen // Finally, sorted the challenges by timestamp to ensure a stable output sortChallengesByTimestamp(candidates) - return candidates, inProgressChallengeCount, nil + return candidates, inProgressChallengeCount } func sortChallengesByTimestamp(chs []*cmacme.Challenge) { diff --git a/pkg/controller/acmechallenges/scheduler/scheduler_test.go b/pkg/controller/acmechallenges/scheduler/scheduler_test.go index d2b64d7bc6c..dc65fe47007 100644 --- a/pkg/controller/acmechallenges/scheduler/scheduler_test.go +++ b/pkg/controller/acmechallenges/scheduler/scheduler_test.go @@ -17,31 +17,29 @@ limitations under the License. package scheduler import ( - "context" "fmt" - "reflect" "testing" "time" + "github.com/google/go-cmp/cmp" "github.com/stretchr/testify/require" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/util/diff" + "k8s.io/apimachinery/pkg/util/rand" cmacme "github.com/cert-manager/cert-manager/pkg/apis/acme/v1" "github.com/cert-manager/cert-manager/pkg/client/clientset/versioned/fake" cminformers "github.com/cert-manager/cert-manager/pkg/client/informers/externalversions" - "github.com/cert-manager/cert-manager/pkg/util" "github.com/cert-manager/cert-manager/test/unit/gen" ) const maxConcurrentChallenges = 60 -func randomChallenge(rand int) *cmacme.Challenge { - if rand == 0 { - rand = 10 +func randomChallenge(dnsNamelength int) *cmacme.Challenge { + if dnsNamelength == 0 { + dnsNamelength = 10 } - return gen.Challenge("test-"+util.RandStringRunes(10), - gen.SetChallengeDNSName(util.RandStringRunes(rand)), + return gen.Challenge("test-"+rand.String(10), + gen.SetChallengeDNSName(rand.String(dnsNamelength)), gen.SetChallengeType(cmacme.ACMEChallengeTypeHTTP01)) } @@ -81,9 +79,8 @@ func BenchmarkScheduleAscending(b *testing.B) { chs := ascendingChallengeN(c) s := &Scheduler{} b.ResetTimer() - for n := 0; n < b.N; n++ { - _, err := s.scheduleN(30, chs) - require.NoError(b, err) + for range b.N { + _ = s.scheduleN(30, chs) } }) } @@ -96,9 +93,8 @@ func BenchmarkScheduleRandom(b *testing.B) { chs := randomChallengeN(c, 0) s := &Scheduler{} b.ResetTimer() - for n := 0; n < b.N; n++ { - _, err := s.scheduleN(30, chs) - require.NoError(b, err) + for range b.N { + _ = s.scheduleN(30, chs) } }) } @@ -111,9 +107,8 @@ func BenchmarkScheduleDuplicates(b *testing.B) { chs := randomChallengeN(c, 3) s := &Scheduler{} b.ResetTimer() - for n := 0; n < b.N; n++ { - _, err := s.scheduleN(30, chs) - require.NoError(b, err) + for range b.N { + _ = s.scheduleN(30, chs) } }) } @@ -146,7 +141,7 @@ func TestScheduleN(t *testing.T) { expected: ascendingChallengeN(maxConcurrentChallenges), }, { - name: "schedule no new if current number is higher than MaxConcurrentChallenges", + name: "schedule no new if current number is greater than MaxConcurrentChallenges", n: maxConcurrentChallenges, challenges: ascendingChallengeN(maxConcurrentChallenges * 4), expected: ascendingChallengeN(maxConcurrentChallenges), @@ -311,7 +306,7 @@ func TestScheduleN(t *testing.T) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { - cl := fake.NewSimpleClientset() + cl := fake.NewClientset() factory := cminformers.NewSharedInformerFactory(cl, 0) challengesInformer := factory.Acme().V1().Challenges() for _, ch := range test.challenges { @@ -319,7 +314,7 @@ func TestScheduleN(t *testing.T) { require.NoError(t, err) } - s := New(context.Background(), challengesInformer.Lister(), maxConcurrentChallenges) + s := New(t.Context(), challengesInformer.Lister(), maxConcurrentChallenges) if test.expected == nil { test.expected = []*cmacme.Challenge{} @@ -331,8 +326,8 @@ func TestScheduleN(t *testing.T) { if err == nil && test.err { t.Errorf("expected to get an error, but got none") } - if !reflect.DeepEqual(chs, test.expected) { - t.Errorf("expected did not match actual: %v", diff.ObjectDiff(test.expected, chs)) + if diff := cmp.Diff(test.expected, chs); diff != "" { + t.Errorf("expected did not match actual (-want +got):\n%s", diff) } }) } diff --git a/pkg/controller/acmechallenges/sync.go b/pkg/controller/acmechallenges/sync.go index 695ade4f012..170a3560e83 100644 --- a/pkg/controller/acmechallenges/sync.go +++ b/pkg/controller/acmechallenges/sync.go @@ -20,20 +20,19 @@ import ( "context" "errors" "fmt" + "slices" + "time" - acmeapi "golang.org/x/crypto/acme" corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/types" utilerrors "k8s.io/apimachinery/pkg/util/errors" - "github.com/cert-manager/cert-manager/internal/controller/feature" "github.com/cert-manager/cert-manager/pkg/acme" acmecl "github.com/cert-manager/cert-manager/pkg/acme/client" cmacme "github.com/cert-manager/cert-manager/pkg/apis/acme/v1" cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" - controllerpkg "github.com/cert-manager/cert-manager/pkg/controller" - dnsutil "github.com/cert-manager/cert-manager/pkg/issuer/acme/dns/util" logf "github.com/cert-manager/cert-manager/pkg/logs" - utilfeature "github.com/cert-manager/cert-manager/pkg/util/feature" + acmeapi "github.com/cert-manager/cert-manager/third_party/forked/acme" ) const ( @@ -42,6 +41,10 @@ const ( reasonPresentError = "PresentError" reasonPresented = "Presented" reasonFailed = "Failed" + + // How long to wait for an authorization response from the ACME server in acceptChallenge() + // before giving up + authorizationTimeout = 2 * time.Minute ) // solver solves ACME challenges by presenting the given token and key in an @@ -53,8 +56,8 @@ type solver interface { Check(ctx context.Context, issuer cmapi.GenericIssuer, ch *cmacme.Challenge) error // CleanUp will remove challenge records for a given solver. // This may involve deleting resources in the Kubernetes API Server, or - // communicating with other external components (e.g. DNS providers). - CleanUp(ctx context.Context, issuer cmapi.GenericIssuer, ch *cmacme.Challenge) error + // communicating with other external components (e.g., DNS providers). + CleanUp(ctx context.Context, ch *cmacme.Challenge) error } // Sync will process this ACME Challenge. @@ -66,7 +69,7 @@ func (c *controller) Sync(ctx context.Context, chOriginal *cmacme.Challenge) (er defer func() { if updateError := c.updateObject(ctx, chOriginal, ch); updateError != nil { - if errors.Is(updateError, argumentError) { + if errors.Is(updateError, errArgument) { log.Error(updateError, "If this error occurs there is a bug in cert-manager. Please report it. Not retrying.") return } @@ -84,11 +87,16 @@ func (c *controller) Sync(ctx context.Context, chOriginal *cmacme.Challenge) (er return nil } + // Remove legacy finalizer + ch.Finalizers = slices.DeleteFunc(ch.Finalizers, func(finalizer string) bool { + return finalizer == cmacme.ACMELegacyFinalizer + }) + // This finalizer ensures that the challenge is not garbage collected before // cert-manager has a chance to clean up resources created for the // challenge. if finalizerRequired(ch) { - ch.Finalizers = append(ch.Finalizers, cmacme.ACMEFinalizer) + ch.Finalizers = append(ch.Finalizers, cmacme.ACMEDomainQualifiedFinalizer) return nil } @@ -107,7 +115,7 @@ func (c *controller) Sync(ctx context.Context, chOriginal *cmacme.Challenge) (er return err } - err = solver.CleanUp(ctx, genericIssuer, ch) + err = solver.CleanUp(ctx, ch) if err != nil { c.recorder.Eventf(ch, corev1.EventTypeWarning, reasonCleanUpError, "Error cleaning up challenge: %v", err) ch.Status.Reason = err.Error() @@ -131,7 +139,7 @@ func (c *controller) Sync(ctx context.Context, chOriginal *cmacme.Challenge) (er if ch.Status.State == "" { err := c.syncChallengeStatus(ctx, cl, ch) if err != nil { - return handleError(ch, err) + return handleError(ctx, ch, err) } // if the state has not changed, return an error @@ -146,28 +154,6 @@ func (c *controller) Sync(ctx context.Context, chOriginal *cmacme.Challenge) (er return nil } - if utilfeature.DefaultFeatureGate.Enabled(feature.ValidateCAA) { - // check for CAA records. - // CAA records are static, so we don't have to present anything - // before we check for them. - - // Find out which identity the ACME server says it will use. - dir, err := cl.Discover(ctx) - if err != nil { - return handleError(ch, err) - } - // TODO(dmo): figure out if missing CAA identity in directory - // means no CAA check is performed by ACME server or if any valid - // CAA would stop issuance (strongly suspect the former) - if len(dir.CAA) != 0 { - err := dnsutil.ValidateCAA(ch.Spec.DNSName, dir.CAA, ch.Spec.Wildcard, c.dns01Nameservers) - if err != nil { - ch.Status.Reason = fmt.Sprintf("CAA self-check failed: %s", err) - return err - } - } - } - solver, err := c.solverFor(ch.Spec.Type) if err != nil { return err @@ -190,13 +176,10 @@ func (c *controller) Sync(ctx context.Context, chOriginal *cmacme.Challenge) (er log.Error(err, "propagation check failed") ch.Status.Reason = fmt.Sprintf("Waiting for %s challenge propagation: %s", ch.Spec.Type, err) - key, err := controllerpkg.KeyFunc(ch) - // This is an unexpected edge case and should never occur - if err != nil { - return err - } - - c.queue.AddAfter(key, c.DNS01CheckRetryPeriod) + c.queue.AddAfter(types.NamespacedName{ + Namespace: ch.Namespace, + Name: ch.Name, + }, c.DNS01CheckRetryPeriod) return nil } @@ -212,7 +195,7 @@ func (c *controller) Sync(ctx context.Context, chOriginal *cmacme.Challenge) (er // handleError will handle ACME error types, updating the challenge resource // with any new information found whilst inspecting the error response. // This may include marking the challenge as expired. -func handleError(ch *cmacme.Challenge, err error) error { +func handleError(ctx context.Context, ch *cmacme.Challenge, err error) error { if err == nil { return nil } @@ -220,20 +203,24 @@ func handleError(ch *cmacme.Challenge, err error) error { var acmeErr *acmeapi.Error var ok bool if acmeErr, ok = err.(*acmeapi.Error); !ok { + ch.Status.State = cmacme.Errored + ch.Status.Reason = fmt.Sprintf("unexpected non-ACME API error: %v", err) + logf.FromContext(ctx).V(logf.ErrorLevel).Error(err, "unexpected non-ACME API error") return err } - switch acmeErr.ProblemType { + // This response type is returned when an authorization has expired or the // request is in some way malformed. // In this case, we should mark the challenge as expired so that the order // can be retried. // TODO: don't mark *all* malformed errors as expired, we may be able to be // more informative to the user by further inspecting the Error response. - case "urn:ietf:params:acme:error:malformed": + if acmeErr.ProblemType == "urn:ietf:params:acme:error:malformed" { ch.Status.State = cmacme.Expired // absorb the error as updating the challenge's status will trigger a sync return nil } + if acmeErr.StatusCode >= 400 && acmeErr.StatusCode < 500 { ch.Status.State = cmacme.Errored ch.Status.Reason = fmt.Sprintf("Failed to retrieve Order resource: %v", err) @@ -250,32 +237,29 @@ func (c *controller) handleFinalizer(ctx context.Context, ch *cmacme.Challenge) if len(ch.Finalizers) == 0 { return nil } - if ch.Finalizers[0] != cmacme.ACMEFinalizer { + if otherFinalizerPresent(ch) { log.V(logf.DebugLevel).Info("waiting to run challenge finalization...") return nil } defer func() { // call Update to remove the metadata.finalizers entry - ch.Finalizers = ch.Finalizers[1:] + ch.Finalizers = slices.DeleteFunc(ch.Finalizers, func(finalizer string) bool { + return finalizer == cmacme.ACMEDomainQualifiedFinalizer || finalizer == cmacme.ACMELegacyFinalizer + }) }() if !ch.Status.Processing { return nil } - genericIssuer, err := c.helper.GetGenericIssuer(ch.Spec.IssuerRef, ch.Namespace) - if err != nil { - return fmt.Errorf("error reading (cluster)issuer %q: %v", ch.Spec.IssuerRef.Name, err) - } - solver, err := c.solverFor(ch.Spec.Type) if err != nil { log.Error(err, "error getting solver for challenge") return nil } - err = solver.CleanUp(ctx, genericIssuer, ch) + err = solver.CleanUp(ctx, ch) if err != nil { c.recorder.Eventf(ch, corev1.EventTypeWarning, reasonCleanUpError, "Error cleaning up challenge: %v", err) ch.Status.Reason = err.Error() @@ -370,14 +354,22 @@ func (c *controller) acceptChallenge(ctx context.Context, cl acmecl.Interface, c if err != nil { log.Error(err, "error accepting challenge") ch.Status.Reason = fmt.Sprintf("Error accepting challenge: %v", err) - return handleError(ch, err) + return handleError(ctx, ch, err) } log.V(logf.DebugLevel).Info("waiting for authorization for domain") - authorization, err := cl.WaitAuthorization(ctx, ch.Spec.AuthorizationURL) + // The underlying ACME implementation from golang.org/x/crypto of WaitAuthorization retries on + // response parsing errors. In the event that an ACME server is not returning expected JSON + // responses, the call to WaitAuthorization can and has been seen to not return and loop forever, + // blocking the challenge's processing. Here, we defensively add a timeout for this exchange + // with the ACME server and a "context deadline reached" error will be returned by WaitAuthorization + // in the err variable. + ctxTimeout, cancelAuthorization := context.WithTimeout(ctx, authorizationTimeout) + defer cancelAuthorization() + authorization, err := cl.WaitAuthorization(ctxTimeout, ch.Spec.AuthorizationURL) if err != nil { log.Error(err, "error waiting for authorization") - return c.handleAuthorizationError(ch, err) + return c.handleAuthorizationError(ctxTimeout, ch, err) } ch.Status.State = cmacme.State(authorization.Status) @@ -387,10 +379,10 @@ func (c *controller) acceptChallenge(ctx context.Context, cl acmecl.Interface, c return nil } -func (c *controller) handleAuthorizationError(ch *cmacme.Challenge, err error) error { +func (c *controller) handleAuthorizationError(ctx context.Context, ch *cmacme.Challenge, err error) error { authErr, ok := err.(*acmeapi.AuthorizationError) if !ok { - return handleError(ch, err) + return handleError(ctx, ch, err) } // TODO: the AuthorizationError above could technically contain the final diff --git a/pkg/controller/acmechallenges/sync_test.go b/pkg/controller/acmechallenges/sync_test.go index 1a84feccd61..7ef47a4245a 100644 --- a/pkg/controller/acmechallenges/sync_test.go +++ b/pkg/controller/acmechallenges/sync_test.go @@ -22,7 +22,6 @@ import ( "fmt" "testing" - acmeapi "golang.org/x/crypto/acme" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" coretesting "k8s.io/client-go/testing" @@ -35,6 +34,7 @@ import ( testpkg "github.com/cert-manager/cert-manager/pkg/controller/test" "github.com/cert-manager/cert-manager/pkg/issuer" "github.com/cert-manager/cert-manager/test/unit/gen" + acmeapi "github.com/cert-manager/cert-manager/third_party/forked/acme" ) // Present the challenge value with the given solver. @@ -43,7 +43,7 @@ func (f *fakeSolver) Present(ctx context.Context, issuer v1.GenericIssuer, ch *c } // Check should return Error only if propagation check cannot be performed. -// It MUST return `false, nil` if can contact all relevant services and all is +// It MUST return `false, nil` if it can contact all relevant services and all it is // doing is waiting for propagation func (f *fakeSolver) Check(ctx context.Context, issuer v1.GenericIssuer, ch *cmacme.Challenge) error { return f.fakeCheck(ctx, issuer, ch) @@ -51,15 +51,15 @@ func (f *fakeSolver) Check(ctx context.Context, issuer v1.GenericIssuer, ch *cma // CleanUp will remove challenge records for a given solver. // This may involve deleting resources in the Kubernetes API Server, or -// communicating with other external components (e.g. DNS providers). -func (f *fakeSolver) CleanUp(ctx context.Context, issuer v1.GenericIssuer, ch *cmacme.Challenge) error { - return f.fakeCleanUp(ctx, issuer, ch) +// communicating with other external components (e.g., DNS providers). +func (f *fakeSolver) CleanUp(ctx context.Context, ch *cmacme.Challenge) error { + return f.fakeCleanUp(ctx, ch) } type fakeSolver struct { fakePresent func(ctx context.Context, issuer v1.GenericIssuer, ch *cmacme.Challenge) error fakeCheck func(ctx context.Context, issuer v1.GenericIssuer, ch *cmacme.Challenge) error - fakeCleanUp func(ctx context.Context, issuer v1.GenericIssuer, ch *cmacme.Challenge) error + fakeCleanUp func(ctx context.Context, ch *cmacme.Challenge) error } type testT struct { @@ -82,10 +82,10 @@ func TestSyncHappyPath(t *testing.T) { }, })) baseChallenge := gen.Challenge("testchal", - gen.SetChallengeIssuer(cmmeta.ObjectReference{ + gen.SetChallengeIssuer(cmmeta.IssuerReference{ Name: "testissuer", }), - gen.SetChallengeFinalizers([]string{cmacme.ACMEFinalizer}), + gen.SetChallengeFinalizers([]string{cmacme.ACMEDomainQualifiedFinalizer}), ) deletedChallenge := gen.ChallengeFrom(baseChallenge, gen.SetChallengeDeletionTimestamp(metav1.Now())) @@ -99,7 +99,7 @@ func TestSyncHappyPath(t *testing.T) { gen.SetChallengeType(cmacme.ACMEChallengeTypeHTTP01), ), httpSolver: &fakeSolver{ - fakeCleanUp: func(ctx context.Context, issuer v1.GenericIssuer, ch *cmacme.Challenge) error { + fakeCleanUp: func(ctx context.Context, ch *cmacme.Challenge) error { return nil }, }, @@ -131,7 +131,7 @@ func TestSyncHappyPath(t *testing.T) { gen.SetChallengeType(cmacme.ACMEChallengeTypeHTTP01), ), httpSolver: &fakeSolver{ - fakeCleanUp: func(ctx context.Context, issuer v1.GenericIssuer, ch *cmacme.Challenge) error { + fakeCleanUp: func(context.Context, *cmacme.Challenge) error { return simulatedCleanupError }, }, @@ -188,7 +188,7 @@ func TestSyncHappyPath(t *testing.T) { gen.DefaultTestNamespace, gen.ChallengeFrom(baseChallenge, gen.SetChallengeProcessing(true), - gen.SetChallengeFinalizers([]string{cmacme.ACMEFinalizer})))), + gen.SetChallengeFinalizers([]string{cmacme.ACMEDomainQualifiedFinalizer})))), }, }, expectErr: false, @@ -203,7 +203,17 @@ func TestSyncHappyPath(t *testing.T) { gen.SetChallengeProcessing(true), gen.SetChallengeURL("testurl"), ), testIssuerHTTP01Enabled}, - ExpectedActions: []testpkg.Action{}, + ExpectedActions: []testpkg.Action{ + testpkg.NewAction( + coretesting.NewUpdateSubresourceAction(cmacme.SchemeGroupVersion.WithResource("challenges"), + "status", + gen.DefaultTestNamespace, + gen.ChallengeFrom(baseChallenge, + gen.SetChallengeURL("testurl"), + gen.SetChallengeProcessing(true), + gen.SetChallengeReason("unexpected non-ACME API error: challenge was not present in authorization"), + gen.SetChallengeState(cmacme.Errored)))), + }, }, expectErr: true, acmeClient: &acmecl.FakeACME{ @@ -316,6 +326,7 @@ func TestSyncHappyPath(t *testing.T) { ))), }, ExpectedEvents: []string{ + //nolint: dupword "Normal Presented Presented challenge using HTTP-01 challenge mechanism", }, }, @@ -333,7 +344,7 @@ func TestSyncHappyPath(t *testing.T) { fakeCheck: func(ctx context.Context, issuer v1.GenericIssuer, ch *cmacme.Challenge) error { return nil }, - fakeCleanUp: func(context.Context, v1.GenericIssuer, *cmacme.Challenge) error { + fakeCleanUp: func(context.Context, *cmacme.Challenge) error { return nil }, }, @@ -388,7 +399,7 @@ func TestSyncHappyPath(t *testing.T) { fakeCheck: func(ctx context.Context, issuer v1.GenericIssuer, ch *cmacme.Challenge) error { return nil }, - fakeCleanUp: func(context.Context, v1.GenericIssuer, *cmacme.Challenge) error { + fakeCleanUp: func(context.Context, *cmacme.Challenge) error { return nil }, }, @@ -447,7 +458,7 @@ func TestSyncHappyPath(t *testing.T) { fakeCheck: func(ctx context.Context, issuer v1.GenericIssuer, ch *cmacme.Challenge) error { return nil }, - fakeCleanUp: func(context.Context, v1.GenericIssuer, *cmacme.Challenge) error { + fakeCleanUp: func(context.Context, *cmacme.Challenge) error { return nil }, }, @@ -507,7 +518,7 @@ func TestSyncHappyPath(t *testing.T) { gen.SetChallengePresented(true), ), httpSolver: &fakeSolver{ - fakeCleanUp: func(ctx context.Context, issuer v1.GenericIssuer, ch *cmacme.Challenge) error { + fakeCleanUp: func(context.Context, *cmacme.Challenge) error { return nil }, }, @@ -542,7 +553,7 @@ func TestSyncHappyPath(t *testing.T) { gen.SetChallengePresented(true), ), httpSolver: &fakeSolver{ - fakeCleanUp: func(ctx context.Context, issuer v1.GenericIssuer, ch *cmacme.Challenge) error { + fakeCleanUp: func(context.Context, *cmacme.Challenge) error { return nil }, }, @@ -583,7 +594,9 @@ func runTest(t *testing.T, test testT) { defer test.builder.Stop() c := &controller{} - c.Register(test.builder.Context) + if _, _, err := c.Register(test.builder.Context); err != nil { + t.Fatal(err) + } c.helper = issuer.NewHelper( test.builder.SharedInformerFactory.Certmanager().V1().Issuers().Lister(), test.builder.SharedInformerFactory.Certmanager().V1().ClusterIssuers().Lister(), @@ -597,7 +610,7 @@ func runTest(t *testing.T, test testT) { c.dnsSolver = test.dnsSolver test.builder.Start() - err := c.Sync(context.Background(), test.challenge) + err := c.Sync(t.Context(), test.challenge) if err != nil && !test.expectErr { t.Errorf("Expected function to not error, but got: %v", err) } diff --git a/pkg/controller/acmechallenges/update.go b/pkg/controller/acmechallenges/update.go index a8ce4519445..3190d8fcb4d 100644 --- a/pkg/controller/acmechallenges/update.go +++ b/pkg/controller/acmechallenges/update.go @@ -18,10 +18,9 @@ package acmechallenges import ( "context" - stderrors "errors" + "errors" "fmt" - "github.com/pkg/errors" apiequality "k8s.io/apimachinery/pkg/api/equality" k8sErrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -35,7 +34,7 @@ import ( "github.com/cert-manager/cert-manager/test/unit/gen" ) -var argumentError = stderrors.New("invalid arguments") +var errArgument = errors.New("invalid arguments") type objectUpdateClient interface { update(context.Context, *cmacme.Challenge) (*cmacme.Challenge, error) @@ -68,40 +67,44 @@ func newObjectUpdater(cl versioned.Interface, fieldManager string) objectUpdater // the UpdateStatus method. // Both updates will be attempted, even if one fails, except in the case where // one of the updates fails with a Not Found error. -// If the any of the API operations results in a Not Found error, updateObject +// If any of the API operations results in a Not Found error, updateObject // will exit without error and the remaining operations will be skipped. // Only the Finalizers and Status fields may be modified. If there are any // modifications to new object, outside of the Finalizers and Status fields, // this function return an error. -func (o *defaultObjectUpdater) updateObject(ctx context.Context, old, new *cmacme.Challenge) error { +func (o *defaultObjectUpdater) updateObject(ctx context.Context, oldChallenge, newChallenge *cmacme.Challenge) error { if !apiequality.Semantic.DeepEqual( - gen.ChallengeFrom(old, gen.SetChallengeFinalizers(nil), gen.ResetChallengeStatus()), - gen.ChallengeFrom(new, gen.SetChallengeFinalizers(nil), gen.ResetChallengeStatus()), + gen.ChallengeFrom(oldChallenge, gen.SetChallengeFinalizers(nil), gen.ResetChallengeStatus()), + gen.ChallengeFrom(newChallenge, gen.SetChallengeFinalizers(nil), gen.ResetChallengeStatus()), ) { - return errors.WithStack( - fmt.Errorf( - "%w: in updateObject: unexpected differences between old and new: only the finalizers and status fields may be modified", - argumentError, - ), + return fmt.Errorf( + "%w: in updateObject: unexpected differences between old and new: only the finalizers and status fields may be modified", + errArgument, ) } var updateFunctions []func() (*cmacme.Challenge, error) - if !apiequality.Semantic.DeepEqual(old.Status, new.Status) { + if !apiequality.Semantic.DeepEqual(oldChallenge.Status, newChallenge.Status) { updateFunctions = append( updateFunctions, func() (*cmacme.Challenge, error) { - obj, err := o.updateStatus(ctx, new) - return obj, errors.Wrap(err, "when updating the status") + if obj, err := o.updateStatus(ctx, newChallenge); err != nil { + return obj, fmt.Errorf("when updating the status: %w", err) + } else { + return obj, nil + } }, ) } - if !apiequality.Semantic.DeepEqual(old.Finalizers, new.Finalizers) { + if !apiequality.Semantic.DeepEqual(oldChallenge.Finalizers, newChallenge.Finalizers) { updateFunctions = append( updateFunctions, func() (*cmacme.Challenge, error) { - obj, err := o.update(ctx, new) - return obj, errors.Wrap(err, "when updating the finalizers") + if obj, err := o.update(ctx, newChallenge); err != nil { + return obj, fmt.Errorf("when updating the finalizers: %w", err) + } else { + return obj, nil + } }, ) } @@ -113,7 +116,7 @@ func (o *defaultObjectUpdater) updateObject(ctx context.Context, old, new *cmacm return nil } } else { - new = o + newChallenge = o } } return utilerrors.NewAggregate(errors) @@ -123,12 +126,12 @@ type objectUpdateClientDefault struct { cl versioned.Interface } -func (o *objectUpdateClientDefault) update(ctx context.Context, new *cmacme.Challenge) (*cmacme.Challenge, error) { - return o.cl.AcmeV1().Challenges(new.Namespace).Update(ctx, new, metav1.UpdateOptions{}) +func (o *objectUpdateClientDefault) update(ctx context.Context, challenge *cmacme.Challenge) (*cmacme.Challenge, error) { + return o.cl.AcmeV1().Challenges(challenge.Namespace).Update(ctx, challenge, metav1.UpdateOptions{}) } -func (o *objectUpdateClientDefault) updateStatus(ctx context.Context, new *cmacme.Challenge) (*cmacme.Challenge, error) { - return o.cl.AcmeV1().Challenges(new.Namespace).UpdateStatus(ctx, new, metav1.UpdateOptions{}) +func (o *objectUpdateClientDefault) updateStatus(ctx context.Context, challenge *cmacme.Challenge) (*cmacme.Challenge, error) { + return o.cl.AcmeV1().Challenges(challenge.Namespace).UpdateStatus(ctx, challenge, metav1.UpdateOptions{}) } type objectUpdateClientSSA struct { @@ -136,10 +139,10 @@ type objectUpdateClientSSA struct { fieldManager string } -func (o *objectUpdateClientSSA) update(ctx context.Context, new *cmacme.Challenge) (*cmacme.Challenge, error) { - return internalchallenges.Apply(ctx, o.cl, o.fieldManager, new) +func (o *objectUpdateClientSSA) update(ctx context.Context, challenge *cmacme.Challenge) (*cmacme.Challenge, error) { + return internalchallenges.Apply(ctx, o.cl, o.fieldManager, challenge) } -func (o *objectUpdateClientSSA) updateStatus(ctx context.Context, new *cmacme.Challenge) (*cmacme.Challenge, error) { - return internalchallenges.ApplyStatus(ctx, o.cl, o.fieldManager, new) +func (o *objectUpdateClientSSA) updateStatus(ctx context.Context, challenge *cmacme.Challenge) (*cmacme.Challenge, error) { + return internalchallenges.ApplyStatus(ctx, o.cl, o.fieldManager, challenge) } diff --git a/pkg/controller/acmechallenges/update_test.go b/pkg/controller/acmechallenges/update_test.go index a41f640c27a..14c6e9cb00b 100644 --- a/pkg/controller/acmechallenges/update_test.go +++ b/pkg/controller/acmechallenges/update_test.go @@ -17,7 +17,6 @@ limitations under the License. package acmechallenges import ( - "context" "errors" "fmt" "testing" @@ -36,25 +35,30 @@ import ( ) func TestUpdateObjectStandard(t *testing.T) { - runUpdateObjectTests(t) + runUpdateObjectTests(t, "update") } func TestUpdateObjectSSA(t *testing.T) { - t.Skip( - "Server Side Apply cannot be tested because PatchType is not supported by the fake versioned client. See:", - "https://github.com/kubernetes/client-go/issues/970", - "https://github.com/kubernetes/client-go/issues/992", - ) - defer featuretesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, feature.ServerSideApply, true)() - runUpdateObjectTests(t) + featuretesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, feature.ServerSideApply, true) + runUpdateObjectTests(t, "patch") } -func runUpdateObjectTests(t *testing.T) { +func runUpdateObjectTests(t *testing.T, verb string) { simulatedUpdateError := errors.New("simulated-update-error") simulatedUpdateStatusError := errors.New("simulated-update-status-error") + // NOTE: We cannot test updates to both the main resource and status + // subresource in the same test using APPLY as the fake.NewClientset used + // in these tests uses a very simple object tracker that does not track main and + // subresource fields separately, which is required for SSA to function. + // For reference see these comments in upstream K8s code: + // - https://github.com/kubernetes/kubernetes/blob/790393ae92e97262827d4f1fba24e8ae65bbada0/staging/src/k8s.io/client-go/testing/fixture.go#L97-L99 + // - https://github.com/kubernetes/kubernetes/blob/790393ae92e97262827d4f1fba24e8ae65bbada0/staging/src/k8s.io/client-go/testing/fixture.go#L167-L169 + // The fake client works for UPDATE, but since the K8s ecosystem is moving towards SSA, + // we should try to improve our code (and/or test strategy) long-term. tests := []struct { name string + skip bool mods []gen.ChallengeModifier notFound bool updateError error @@ -65,6 +69,8 @@ func runUpdateObjectTests(t *testing.T) { // finalizers and status being updated. { name: "success", + // FIXME: Skip test case when using SSA. See description above. + skip: verb == "patch", mods: []gen.ChallengeModifier{ gen.SetChallengeFinalizers([]string{"example.com/another-finalizer"}), gen.SetChallengePresented(true), @@ -89,7 +95,7 @@ func runUpdateObjectTests(t *testing.T) { }, errorMessage: fmt.Sprintf( "%s: in updateObject: unexpected differences between old and new: only the finalizers and status fields may be modified", - argumentError, + errArgument, ), }, // If the Update API call fails, that error is returned. @@ -128,30 +134,33 @@ func runUpdateObjectTests(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - ctx := context.TODO() - old := gen.Challenge("c1") - new := gen.ChallengeFrom(old, tt.mods...) - objects := []runtime.Object{old} + if tt.skip { + t.Skip("The fake client doesn't support K8s subresources like status when using SSA.") + } + + oldChallenge := gen.Challenge("c1") + newChallenge := gen.ChallengeFrom(oldChallenge, tt.mods...) + objects := []runtime.Object{oldChallenge} if tt.notFound { t.Log("Simulating a situation where the target object has been deleted") objects = nil } - cl := fake.NewSimpleClientset(objects...) + cl := fake.NewClientset(objects...) if tt.updateError != nil { - cl.PrependReactor("update", "challenges", func(action clienttesting.Action) (handled bool, ret runtime.Object, err error) { + cl.PrependReactor(verb, "challenges", func(action clienttesting.Action) (handled bool, ret runtime.Object, err error) { t.Log("Simulating a challenge update error") return true, nil, tt.updateError }) } if tt.updateStatusError != nil { - cl.PrependReactor("update", "challenges/status", func(action clienttesting.Action) (handled bool, ret runtime.Object, err error) { + cl.PrependReactor(verb, "challenges/status", func(action clienttesting.Action) (handled bool, ret runtime.Object, err error) { t.Log("Simulating a challenge/status update error") return true, nil, tt.updateStatusError }) } updater := newObjectUpdater(cl, "test-fieldmanager") t.Log("Calling updateObject") - updateObjectErr := updater.updateObject(ctx, old, new) + updateObjectErr := updater.updateObject(t.Context(), oldChallenge, newChallenge) if tt.errorMessage == "" { assert.NoError(t, updateObjectErr) } else { @@ -164,16 +173,22 @@ func runUpdateObjectTests(t *testing.T) { if !tt.notFound { t.Log("Checking whether the object was updated") - actual, err := cl.AcmeV1().Challenges(old.Namespace).Get(ctx, old.Name, metav1.GetOptions{}) + actual, err := cl.AcmeV1().Challenges(oldChallenge.Namespace).Get(t.Context(), oldChallenge.Name, metav1.GetOptions{}) require.NoError(t, err) if updateObjectErr == nil { - assert.Equal(t, new, actual, "updateObject did not return an error so the object in the API should have been updated") + expected := newChallenge + expected.APIVersion = actual.APIVersion + expected.Kind = actual.Kind + // We ignore differences in .ManagedFields since the expected object does not have them. + // FIXME: don't ignore this field + expected.ManagedFields = actual.ManagedFields + assert.Equal(t, expected, actual, "updateObject did not return an error so the object in the API should have been updated") } else { if !errors.Is(updateObjectErr, simulatedUpdateError) { - assert.Equal(t, new.Finalizers, actual.Finalizers, "The Update did not fail so the Finalizers of the API object should have been updated") + assert.Equal(t, newChallenge.Finalizers, actual.Finalizers, "The Update did not fail so the Finalizers of the API object should have been updated") } if !errors.Is(updateObjectErr, simulatedUpdateStatusError) { - assert.Equal(t, new.Status, actual.Status, "The UpdateStatus did not fail so the Status of the API object should have been updated") + assert.Equal(t, newChallenge.Status, actual.Status, "The UpdateStatus did not fail so the Status of the API object should have been updated") } } } diff --git a/pkg/controller/acmeorders/checks.go b/pkg/controller/acmeorders/checks.go index 379faeef250..755517f4b01 100644 --- a/pkg/controller/acmeorders/checks.go +++ b/pkg/controller/acmeorders/checks.go @@ -20,6 +20,7 @@ import ( "fmt" "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/runtime" "k8s.io/client-go/util/workqueue" @@ -29,7 +30,7 @@ import ( ) func handleGenericIssuerFunc( - queue workqueue.RateLimitingInterface, + queue workqueue.TypedRateLimitingInterface[types.NamespacedName], orderLister cmacmelisters.OrderLister, ) func(interface{}) { return func(obj interface{}) { @@ -45,12 +46,10 @@ func handleGenericIssuerFunc( return } for _, crt := range certs { - key, err := keyFunc(crt) - if err != nil { - runtime.HandleError(err) - continue - } - queue.Add(key) + queue.Add(types.NamespacedName{ + Namespace: crt.Namespace, + Name: crt.Name, + }) } } } diff --git a/pkg/controller/acmeorders/controller.go b/pkg/controller/acmeorders/controller.go index 4b0a10ff28b..9cfcbd01b50 100644 --- a/pkg/controller/acmeorders/controller.go +++ b/pkg/controller/acmeorders/controller.go @@ -18,20 +18,20 @@ package acmeorders import ( "context" - "time" + "fmt" "github.com/go-logr/logr" k8sErrors "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/client-go/informers" - corelisters "k8s.io/client-go/listers/core/v1" + "k8s.io/apimachinery/pkg/types" "k8s.io/client-go/tools/cache" "k8s.io/client-go/tools/record" "k8s.io/client-go/util/workqueue" "k8s.io/utils/clock" + internalinformers "github.com/cert-manager/cert-manager/internal/informers" "github.com/cert-manager/cert-manager/pkg/acme/accounts" + cmacme "github.com/cert-manager/cert-manager/pkg/apis/acme/v1" cmclient "github.com/cert-manager/cert-manager/pkg/client/clientset/versioned" - cminformers "github.com/cert-manager/cert-manager/pkg/client/informers/externalversions" cmacmelisters "github.com/cert-manager/cert-manager/pkg/client/listers/acme/v1" cmlisters "github.com/cert-manager/cert-manager/pkg/client/listers/certmanager/v1" controllerpkg "github.com/cert-manager/cert-manager/pkg/controller" @@ -40,8 +40,6 @@ import ( "github.com/cert-manager/cert-manager/pkg/scheduler" ) -var keyFunc = controllerpkg.KeyFunc - type controller struct { // issuer helper is used to obtain references to issuers, used by Sync() helper issuer.Helper @@ -54,7 +52,7 @@ type controller struct { challengeLister cmacmelisters.ChallengeLister issuerLister cmlisters.IssuerLister clusterIssuerLister cmlisters.ClusterIssuerLister - secretLister corelisters.SecretLister + secretLister internalinformers.SecretLister // used for testing clock clock.Clock @@ -68,42 +66,35 @@ type controller struct { // maintain a reference to the workqueue for this controller // so the handleOwnedResource method can enqueue resources - queue workqueue.RateLimitingInterface + queue workqueue.TypedRateLimitingInterface[types.NamespacedName] // scheduledWorkQueue holds items to be re-queued after a period of time. - scheduledWorkQueue scheduler.ScheduledWorkQueue - - // logger to be used by this controller - log logr.Logger + scheduledWorkQueue scheduler.ScheduledWorkQueue[types.NamespacedName] } // NewController constructs an orders controller using the provided options. func NewController( log logr.Logger, - cmClient cmclient.Interface, - kubeInformerFactory informers.SharedInformerFactory, - cmInformerFactory cminformers.SharedInformerFactory, - accountRegistry accounts.Getter, - recorder record.EventRecorder, - clock clock.Clock, + ctx *controllerpkg.Context, isNamespaced bool, - fieldManager string, -) (*controller, workqueue.RateLimitingInterface, []cache.InformerSynced) { +) (*controller, workqueue.TypedRateLimitingInterface[types.NamespacedName], []cache.InformerSynced, error) { // Create a queue used to queue up Orders to be processed. - queue := workqueue.NewNamedRateLimitingQueue( - workqueue.NewItemExponentialFailureRateLimiter(time.Second*5, time.Minute*30), - ControllerName, + queue := workqueue.NewTypedRateLimitingQueueWithConfig( + controllerpkg.DefaultACMERateLimiter(), + workqueue.TypedRateLimitingQueueConfig[types.NamespacedName]{ + Name: ControllerName, + }, ) // Create a scheduledWorkQueue to schedule Orders for re-processing. - scheduledWorkQueue := scheduler.NewScheduledWorkQueue(clock, queue.Add) + scheduledWorkQueue := scheduler.NewScheduledWorkQueue(ctx.Clock, queue.Add) // Obtain references to all the informers used by this controller. - orderInformer := cmInformerFactory.Acme().V1().Orders() - issuerInformer := cmInformerFactory.Certmanager().V1().Issuers() - challengeInformer := cmInformerFactory.Acme().V1().Challenges() - secretInformer := kubeInformerFactory.Core().V1().Secrets() + orderInformer := ctx.SharedInformerFactory.Acme().V1().Orders() + issuerInformer := ctx.SharedInformerFactory.Certmanager().V1().Issuers() + challengeInformer := ctx.SharedInformerFactory.Acme().V1().Challenges() + secretInformer := ctx.KubeSharedInformerFactory.Secrets() // Build a list of InformerSynced functions. The controller will only begin // processing items once all of these informers have synced. @@ -124,28 +115,36 @@ func NewController( // register event handlers and obtain a lister for ClusterIssuers. var clusterIssuerLister cmlisters.ClusterIssuerLister if !isNamespaced { - clusterIssuerInformer := cmInformerFactory.Certmanager().V1().ClusterIssuers() + clusterIssuerInformer := ctx.SharedInformerFactory.Certmanager().V1().ClusterIssuers() mustSync = append(mustSync, clusterIssuerInformer.Informer().HasSynced) clusterIssuerLister = clusterIssuerInformer.Lister() // register handler function for clusterissuer resources - clusterIssuerInformer.Informer().AddEventHandler( + if _, err := clusterIssuerInformer.Informer().AddEventHandler( &controllerpkg.BlockingEventHandler{WorkFunc: handleGenericIssuerFunc(queue, orderLister)}, - ) + ); err != nil { + return nil, nil, nil, fmt.Errorf("error setting up event handler: %v", err) + } } // register handler functions - orderInformer.Informer().AddEventHandler( + if _, err := orderInformer.Informer().AddEventHandler( &controllerpkg.QueuingEventHandler{Queue: queue}, - ) - issuerInformer.Informer().AddEventHandler( + ); err != nil { + return nil, nil, nil, fmt.Errorf("error setting up event handler: %v", err) + } + if _, err := issuerInformer.Informer().AddEventHandler( &controllerpkg.BlockingEventHandler{WorkFunc: handleGenericIssuerFunc(queue, orderLister)}, - ) - challengeInformer.Informer().AddEventHandler(&controllerpkg.BlockingEventHandler{ + ); err != nil { + return nil, nil, nil, fmt.Errorf("error setting up event handler: %v", err) + } + if _, err := challengeInformer.Informer().AddEventHandler(&controllerpkg.BlockingEventHandler{ WorkFunc: controllerpkg.HandleOwnedResourceNamespacedFunc(log, queue, orderGvk, orderGetterFunc(orderLister)), - }) + }); err != nil { + return nil, nil, nil, fmt.Errorf("error setting up event handler: %v", err) + } return &controller{ - clock: clock, + clock: ctx.Clock, queue: queue, scheduledWorkQueue: scheduledWorkQueue, orderLister: orderLister, @@ -154,39 +153,35 @@ func NewController( secretLister: secretLister, clusterIssuerLister: clusterIssuerLister, helper: issuer.NewHelper(issuerLister, clusterIssuerLister), - recorder: recorder, - cmClient: cmClient, - accountRegistry: accountRegistry, - fieldManager: fieldManager, - }, queue, mustSync + recorder: ctx.Recorder, + cmClient: ctx.CMClient, + accountRegistry: ctx.ACMEAccountRegistry, + fieldManager: ctx.FieldManager, + }, queue, mustSync, nil } -func (c *controller) ProcessItem(ctx context.Context, key string) error { +func (c *controller) ProcessItem(ctx context.Context, key types.NamespacedName) error { log := logf.FromContext(ctx) - namespace, name, err := cache.SplitMetaNamespaceKey(key) - if err != nil { - log.Error(err, "invalid resource key") - return nil - } + namespace, name := key.Namespace, key.Name order, err := c.orderLister.Orders(namespace).Get(name) - if err != nil { - if k8sErrors.IsNotFound(err) { - log.Error(err, "order in work queue no longer exists") - return nil - } - + if err != nil && !k8sErrors.IsNotFound(err) { return err } + if order == nil || order.DeletionTimestamp != nil { + // If the Order object was/ is being deleted, we don't want to update its status or + // create new Challenges. + return nil + } ctx = logf.NewContext(ctx, logf.WithResource(log, order)) return c.Sync(ctx, order) } // Returns a function that finds a named Order in a particular namespace. -func orderGetterFunc(orderLister cmacmelisters.OrderLister) func(string, string) (interface{}, error) { - return func(namespace, name string) (interface{}, error) { +func orderGetterFunc(orderLister cmacmelisters.OrderLister) func(string, string) (*cmacme.Order, error) { + return func(namespace, name string) (*cmacme.Order, error) { return orderLister.Orders(namespace).Get(name) } } @@ -206,27 +201,21 @@ type controllerWrapper struct { // Register registers a controller, created using the provided context. // It returns the workqueue to be used to enqueue items, a list of // InformerSynced functions that must be synced, or an error. -func (c *controllerWrapper) Register(ctx *controllerpkg.Context) (workqueue.RateLimitingInterface, []cache.InformerSynced, error) { +func (c *controllerWrapper) Register(ctx *controllerpkg.Context) (workqueue.TypedRateLimitingInterface[types.NamespacedName], []cache.InformerSynced, error) { // Construct a new named logger to be reused throughout the controller. log := logf.FromContext(ctx.RootContext, ControllerName) // If --namespace flag was set thus limiting cert-manager to a single namespace. isNamespaced := ctx.Namespace != "" - ctrl, queue, mustSync := NewController( + ctrl, queue, mustSync, err := NewController( log, - ctx.CMClient, - ctx.KubeSharedInformerFactory, - ctx.SharedInformerFactory, - ctx.ACMEOptions.AccountRegistry, - ctx.Recorder, - ctx.Clock, + ctx, isNamespaced, - ctx.FieldManager, ) c.controller = ctrl - return queue, mustSync, nil + return queue, mustSync, err } func init() { diff --git a/pkg/controller/acmeorders/selectors/dns_names.go b/pkg/controller/acmeorders/selectors/dns_names.go index fcfe17ea6d9..54f71b77808 100644 --- a/pkg/controller/acmeorders/selectors/dns_names.go +++ b/pkg/controller/acmeorders/selectors/dns_names.go @@ -17,6 +17,8 @@ limitations under the License. package selectors import ( + "slices" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" cmacme "github.com/cert-manager/cert-manager/pkg/apis/acme/v1" @@ -37,10 +39,8 @@ func (s *dnsNamesSelector) Matches(meta metav1.ObjectMeta, dnsName string) (bool return true, 0 } - for _, d := range s.allowedDNSNames { - if dnsName == d { - return true, 1 - } + if slices.Contains(s.allowedDNSNames, dnsName) { + return true, 1 } return false, 0 diff --git a/pkg/controller/acmeorders/sync.go b/pkg/controller/acmeorders/sync.go index 6cde88af708..c974ff75ac0 100644 --- a/pkg/controller/acmeorders/sync.go +++ b/pkg/controller/acmeorders/sync.go @@ -21,27 +21,32 @@ import ( "context" "crypto/x509" "encoding/pem" + "errors" "fmt" + "net" + "net/http" "time" - acmeapi "golang.org/x/crypto/acme" corev1 "k8s.io/api/core/v1" apiequality "k8s.io/apimachinery/pkg/api/equality" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/types" utilerrors "k8s.io/apimachinery/pkg/util/errors" "k8s.io/apimachinery/pkg/util/sets" - "k8s.io/client-go/tools/cache" + "k8s.io/apimachinery/pkg/util/validation" "github.com/cert-manager/cert-manager/internal/controller/feature" internalorders "github.com/cert-manager/cert-manager/internal/controller/orders" + safepem "github.com/cert-manager/cert-manager/internal/pem" "github.com/cert-manager/cert-manager/pkg/acme" acmecl "github.com/cert-manager/cert-manager/pkg/acme/client" cmacme "github.com/cert-manager/cert-manager/pkg/apis/acme/v1" cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" logf "github.com/cert-manager/cert-manager/pkg/logs" utilfeature "github.com/cert-manager/cert-manager/pkg/util/feature" + acmeapi "github.com/cert-manager/cert-manager/third_party/forked/acme" ) const ( @@ -51,8 +56,8 @@ const ( var ( // RequeuePeriod is the default period after which an Order should be re-queued. - // It can be overriden in tests. - RequeuePeriod time.Duration = time.Second * 5 + // It can be overridden in tests. + RequeuePeriod = time.Second * 5 ) func (c *controller) Sync(ctx context.Context, o *cmacme.Order) (err error) { @@ -121,7 +126,7 @@ func (c *controller) Sync(ctx context.Context, o *cmacme.Order) (err error) { } dbg.Info("Computing list of Challenge resources that need to exist to complete this Order") - requiredChallenges, err := buildRequiredChallenges(ctx, cl, genericIssuer, o) + requiredChallenges, err := buildPartialRequiredChallenges(ctx, genericIssuer, o) if err != nil { log.Error(err, "Failed to determine the list of Challenge resources needed for the Order") c.recorder.Eventf(o, corev1.EventTypeWarning, reasonSolver, "Failed to determine a valid solver configuration for the set of domains on the Order: %v", err) @@ -142,6 +147,10 @@ func (c *controller) Sync(ctx context.Context, o *cmacme.Order) (err error) { switch { case needToCreateChallenges: log.V(logf.DebugLevel).Info("Creating additional Challenge resources to complete Order") + requiredChallenges, err = ensureKeysForChallenges(cl, requiredChallenges) + if err != nil { + return err + } return c.createRequiredChallenges(ctx, o, requiredChallenges) case needToDeleteChallenges: log.V(logf.DebugLevel).Info("Deleting leftover Challenge resources no longer required by Order") @@ -161,6 +170,14 @@ func (c *controller) Sync(ctx context.Context, o *cmacme.Order) (err error) { return c.finalizeOrder(ctx, cl, o, genericIssuer) } + // At this point, if no Challenges have failed or reached a final state, + // we can return without taking any action. This controller will resync + // the Order on any owned Challenge events. + if !anyChallengesFailed(challenges) && !allChallengesFinal(challenges) { + log.V(logf.DebugLevel).Info("No action taken") + return nil + } + // Note: each of the following code paths uses the ACME Order retrieved // here. Be mindful when adding new code below this call to ACME server- // if the new code does not need this ACME order, try to place it above @@ -181,11 +198,16 @@ func (c *controller) Sync(ctx context.Context, o *cmacme.Order) (err error) { switch { case anyChallengesFailed(challenges): - // TODO (@munnerz): instead of waiting for the ACME server to mark this - // Order as failed, we could just mark the Order as failed as there is - // no way that we will attempt and continue the order anyway. + // TODO (@munnerz): instead of waiting for the ACME server to + // mark this Order as failed, we could just mark the Order as + // failed as there is no way that we will attempt and continue + // the order anyway. This might, however, be a breaking change + // in edge cases where the status of the order resource in ACME + // server cannot be determined from challenge resource statuses + // correctly. Do not change this unless there is a real need for + // it. log.V(logf.DebugLevel).Info("Update Order status as at least one Challenge has failed") - _, err := c.updateOrderStatusFromACMEOrder(ctx, cl, o, acmeOrder) + _, err := c.updateOrderStatusFromACMEOrder(o, acmeOrder) if acmeErr, ok := err.(*acmeapi.Error); ok { if acmeErr.StatusCode >= 400 && acmeErr.StatusCode < 500 { log.Error(err, "failed to update Order status due to a 4xx error, marking Order as failed") @@ -209,23 +231,18 @@ func (c *controller) Sync(ctx context.Context, o *cmacme.Order) (err error) { // This is probably not needed as at this point the Order's status // should already be Pending, but set it anyway to be explicit. c.setOrderState(&o.Status, string(cmacme.Pending)) - key, err := cache.MetaNamespaceKeyFunc(o) - if err != nil { - log.Error(err, "failed to construct key for pending Order") - // We should never end up here as this error would have been - // encountered in informers callback already. This probably cannot - // be fixed by re-queueing. If we do start encountering this - // scenario, we should consider whether the Order should be marked - // as failed here. - return nil - } + // Re-queue the Order to be processed again after 5 seconds. - c.scheduledWorkQueue.Add(key, RequeuePeriod) + c.scheduledWorkQueue.Add(types.NamespacedName{ + Name: o.Name, + Namespace: o.Namespace, + }, RequeuePeriod) + return nil case !anyChallengesFailed(challenges) && allChallengesFinal(challenges): log.V(logf.DebugLevel).Info("All challenges are in a final state, updating order state") - _, err := c.updateOrderStatusFromACMEOrder(ctx, cl, o, acmeOrder) + _, err := c.updateOrderStatusFromACMEOrder(o, acmeOrder) if acmeErr, ok := err.(*acmeapi.Error); ok { if acmeErr.StatusCode >= 400 && acmeErr.StatusCode < 500 { log.Error(err, "failed to update Order status due to a 4xx error, marking Order as failed") @@ -242,6 +259,24 @@ func (c *controller) Sync(ctx context.Context, o *cmacme.Order) (err error) { return nil } +func isRetryableError(err error) bool { + for _, targetErr := range []error{ + acmeapi.ErrCADoesNotSupportProfiles, + acmeapi.ErrProfileNotInSetOfSupportedProfiles, + } { + if errors.Is(err, targetErr) { + return false + } + } + var acmeErr *acmeapi.Error + if errors.As(err, &acmeErr) { + if acmeErr.StatusCode >= 400 && acmeErr.StatusCode < 500 { + return false + } + } + return true +} + func (c *controller) createOrder(ctx context.Context, cl acmecl.Interface, o *cmacme.Order) error { log := logf.FromContext(ctx) @@ -250,33 +285,38 @@ func (c *controller) createOrder(ctx context.Context, cl acmecl.Interface, o *cm } log.V(logf.DebugLevel).Info("order URL not set, submitting Order to ACME server") - dnsIdentifierSet := sets.NewString(o.Spec.DNSNames...) - if o.Spec.CommonName != "" { + dnsIdentifierSet := sets.New[string](o.Spec.DNSNames...) + ipIdentifierSet := sets.New[string](o.Spec.IPAddresses...) + switch { + case o.Spec.CommonName == "": + case net.ParseIP(o.Spec.CommonName) != nil: + ipIdentifierSet.Insert(o.Spec.CommonName) + case len(validation.IsFullyQualifiedDomainName(nil, o.Spec.CommonName)) == 0: dnsIdentifierSet.Insert(o.Spec.CommonName) } - log.V(logf.DebugLevel).Info("build set of domains for Order", "domains", dnsIdentifierSet.List()) + log.V(logf.DebugLevel).Info("built set of identifiers for Order", "domains", sets.List(dnsIdentifierSet), "ipAddresses", sets.List(ipIdentifierSet)) - ipIdentifierSet := sets.NewString(o.Spec.IPAddresses...) - log.V(logf.DebugLevel).Info("build set of IPs for Order", "domains", dnsIdentifierSet.List()) - - authzIDs := acmeapi.DomainIDs(dnsIdentifierSet.List()...) - authzIDs = append(authzIDs, acmeapi.IPIDs(ipIdentifierSet.List()...)...) + authzIDs := acmeapi.DomainIDs(sets.List(dnsIdentifierSet)...) + authzIDs = append(authzIDs, acmeapi.IPIDs(sets.List(ipIdentifierSet)...)...) // create a new order with the acme server var options []acmeapi.OrderOption if o.Spec.Duration != nil { options = append(options, acmeapi.WithOrderNotAfter(c.clock.Now().Add(o.Spec.Duration.Duration))) } + + if o.Spec.Profile != "" { + options = append(options, acmeapi.WithOrderProfile(o.Spec.Profile)) + } + acmeOrder, err := cl.AuthorizeOrder(ctx, authzIDs, options...) - if acmeErr, ok := err.(*acmeapi.Error); ok { - if acmeErr.StatusCode >= 400 && acmeErr.StatusCode < 500 { + if err != nil { + if !isRetryableError(err) { log.Error(err, "failed to create Order resource due to bad request, marking Order as failed") c.setOrderState(&o.Status, string(cmacme.Errored)) o.Status.Reason = fmt.Sprintf("Failed to create Order: %v", err) return nil } - } - if err != nil { return fmt.Errorf("error creating new order: %v", err) } log.V(logf.DebugLevel).Info("submitted Order to ACME server") @@ -295,13 +335,16 @@ func (c *controller) updateOrderStatus(ctx context.Context, cl acmecl.Interface, return nil, err } - return c.updateOrderStatusFromACMEOrder(ctx, cl, o, acmeOrder) + return c.updateOrderStatusFromACMEOrder(o, acmeOrder) } -func (c *controller) updateOrderStatusFromACMEOrder(ctx context.Context, cl acmecl.Interface, o *cmacme.Order, acmeOrder *acmeapi.Order) (*acmeapi.Order, error) { +func (c *controller) updateOrderStatusFromACMEOrder(o *cmacme.Order, acmeOrder *acmeapi.Order) (*acmeapi.Order, error) { // Workaround bug in golang.org/x/crypto/acme implementation whereby the // order's URI field will be empty when calling GetOrder due to the // 'Location' header not being set on the response from the ACME server. + // + // TODO(wallrj): We have vendored golang.org/x/crypto/acme so there's + // nothing stopping us fixing this bug. if acmeOrder.URI != "" { o.Status.URL = acmeOrder.URI } @@ -386,7 +429,7 @@ func (c *controller) fetchMetadataForAuthorizations(ctx context.Context, o *cmac return nil } -func (c *controller) anyRequiredChallengesDoNotExist(requiredChallenges []cmacme.Challenge) (bool, error) { +func (c *controller) anyRequiredChallengesDoNotExist(requiredChallenges []*cmacme.Challenge) (bool, error) { for _, ch := range requiredChallenges { _, err := c.challengeLister.Challenges(ch.Namespace).Get(ch.Name) if apierrors.IsNotFound(err) { @@ -399,9 +442,9 @@ func (c *controller) anyRequiredChallengesDoNotExist(requiredChallenges []cmacme return false, nil } -func (c *controller) createRequiredChallenges(ctx context.Context, o *cmacme.Order, requiredChallenges []cmacme.Challenge) error { +func (c *controller) createRequiredChallenges(ctx context.Context, o *cmacme.Order, requiredChallenges []*cmacme.Challenge) error { for _, ch := range requiredChallenges { - _, err := c.cmClient.AcmeV1().Challenges(ch.Namespace).Create(ctx, &ch, metav1.CreateOptions{}) + _, err := c.cmClient.AcmeV1().Challenges(ch.Namespace).Create(ctx, ch, metav1.CreateOptions{}) if apierrors.IsAlreadyExists(err) { continue } @@ -413,7 +456,7 @@ func (c *controller) createRequiredChallenges(ctx context.Context, o *cmacme.Ord return nil } -func (c *controller) anyLeftoverChallengesExist(o *cmacme.Order, requiredChallenges []cmacme.Challenge) (bool, error) { +func (c *controller) anyLeftoverChallengesExist(o *cmacme.Order, requiredChallenges []*cmacme.Challenge) (bool, error) { leftoverChallenges, err := c.determineLeftoverChallenges(o, requiredChallenges) if err != nil { return false, err @@ -422,7 +465,7 @@ func (c *controller) anyLeftoverChallengesExist(o *cmacme.Order, requiredChallen return len(leftoverChallenges) > 0, nil } -func (c *controller) deleteLeftoverChallenges(ctx context.Context, o *cmacme.Order, requiredChallenges []cmacme.Challenge) error { +func (c *controller) deleteLeftoverChallenges(ctx context.Context, o *cmacme.Order, requiredChallenges []*cmacme.Challenge) error { leftover, err := c.determineLeftoverChallenges(o, requiredChallenges) if err != nil { return err @@ -452,7 +495,7 @@ func (c *controller) deleteAllChallenges(ctx context.Context, o *cmacme.Order) e return nil } -func (c *controller) determineLeftoverChallenges(o *cmacme.Order, requiredChallenges []cmacme.Challenge) ([]*cmacme.Challenge, error) { +func (c *controller) determineLeftoverChallenges(o *cmacme.Order, requiredChallenges []*cmacme.Challenge) ([]*cmacme.Challenge, error) { requiredNames := map[string]struct{}{} for _, ch := range requiredChallenges { requiredNames[ch.Name] = struct{}{} @@ -498,13 +541,17 @@ func (c *controller) finalizeOrder(ctx context.Context, cl acmecl.Interface, o * // only supported DER encoded CSRs and not PEM encoded as they are intended // to be as part of our API. // To work around this, we first attempt to decode the Request into DER bytes - // by running pem.Decode. If the PEM block is empty, we assume that the Request + // by running pem.SafeDecodeCSR. If the PEM block is empty, we assume that the Request // is DER encoded and continue to call FinalizeOrder. var derBytes []byte - block, _ := pem.Decode(o.Spec.Request) - if block == nil { - log.V(logf.WarnLevel).Info("failed to parse Request as PEM data, attempting to treat Request as DER encoded for compatibility reasons") - derBytes = o.Spec.Request + block, _, err := safepem.SafeDecodeCSR(o.Spec.Request) + if err != nil { + if err == safepem.ErrNoPEMData { + log.V(logf.WarnLevel).Info("failed to parse Request as PEM data, attempting to treat Request as DER encoded for compatibility reasons") + derBytes = o.Spec.Request + } else { + return err + } } else { derBytes = block.Bytes } @@ -519,7 +566,7 @@ func (c *controller) finalizeOrder(ctx context.Context, cl acmecl.Interface, o * // finalized in an earlier reconcile, but the reconciler failed // to update the status of the Order CR. // https://datatracker.ietf.org/doc/html/rfc8555#:~:text=A%20request%20to%20finalize%20an%20order%20will%20result%20in%20error,will%20indicate%20what%20action%20the%20client%20should%20take%20(see%20below). - if ok && acmeErr.StatusCode == 403 { + if ok && acmeErr.StatusCode == http.StatusForbidden { acmeOrder, getOrderErr := getACMEOrder(ctx, cl, o) acmeGetOrderErr, ok := getOrderErr.(*acmeapi.Error) @@ -548,6 +595,10 @@ func (c *controller) finalizeOrder(ctx context.Context, cl acmecl.Interface, o * return nil } + if ok && acmeErr.StatusCode >= 500 { + log.Error(acmeErr, "acme server fatal error", "errorCode", acmeErr.StatusCode, "dnsNames", o.Spec.DNSNames, "ipAddresses", o.Spec.IPAddresses, "responseHeaders", acmeErr.Header) + } + // Before checking whether the call to CreateOrderCert returned a // non-4xx error, ensure the order status is up-to-date. _, errUpdate := c.updateOrderStatus(ctx, cl, o) @@ -558,7 +609,11 @@ func (c *controller) finalizeOrder(ctx context.Context, cl acmecl.Interface, o * o.Status.Reason = fmt.Sprintf("Failed to retrieve Order resource: %v", errUpdate) return nil } + if acmeErr.StatusCode >= 500 { + log.Error(acmeErr, "acme server fatal error", "errorCode", acmeErr.StatusCode, "dnsNames", o.Spec.DNSNames, "ipAddresses", o.Spec.IPAddresses, "responseHeaders", acmeErr.Header) + } } + if errUpdate != nil { return fmt.Errorf("error syncing order status: %v", errUpdate) } @@ -568,17 +623,17 @@ func (c *controller) finalizeOrder(ctx context.Context, cl acmecl.Interface, o * } if issuer.GetSpec().ACME != nil && issuer.GetSpec().ACME.PreferredChain != "" { - preferredChain := issuer.GetSpec().ACME.PreferredChain - found, altChain, err := getAltCertChain(ctx, cl, certURL, preferredChain) + preferredChainName := issuer.GetSpec().ACME.PreferredChain + found, preferredCertChain, err := getPreferredCertChain(ctx, cl, certURL, certSlice, preferredChainName) if err != nil { - return fmt.Errorf("error retrieving alternate chain: %w", err) + return fmt.Errorf("error retrieving preferred chain: %w", err) } if found { - return c.storeCertificateOnStatus(ctx, o, altChain) + return c.storeCertificateOnStatus(ctx, o, preferredCertChain) } // if no match is found we return to the actual cert // it is a *preferred* chain after all - log.V(logf.DebugLevel).Info(fmt.Sprintf("Preferred chain %s not found, fall back to the default cert", preferredChain)) + log.V(logf.DebugLevel).Info(fmt.Sprintf("Preferred chain %s not found, fall back to the default cert", preferredChainName)) } return c.storeCertificateOnStatus(ctx, o, certSlice) @@ -635,16 +690,6 @@ func (c *controller) syncCertificateDataWithOrder(ctx context.Context, cl acmecl return nil } - if issuer.GetSpec().ACME != nil && issuer.GetSpec().ACME.PreferredChain != "" { - found, altCerts, err := getAltCertChain(ctx, cl, acmeOrder.CertURL, issuer.GetSpec().ACME.PreferredChain) - if err != nil { - return err - } - if found { - return c.storeCertificateOnStatus(ctx, o, altCerts) - } - - } certs, err := cl.FetchCert(ctx, acmeOrder.CertURL, true) if acmeErr, ok := err.(*acmeapi.Error); ok { if acmeErr.StatusCode >= 400 && acmeErr.StatusCode < 500 { @@ -658,6 +703,16 @@ func (c *controller) syncCertificateDataWithOrder(ctx context.Context, cl acmecl return err } + if issuer.GetSpec().ACME != nil && issuer.GetSpec().ACME.PreferredChain != "" { + found, preferredCertChain, err := getPreferredCertChain(ctx, cl, acmeOrder.CertURL, certs, issuer.GetSpec().ACME.PreferredChain) + if err != nil { + return err + } + if found { + return c.storeCertificateOnStatus(ctx, o, preferredCertChain) + } + } + err = c.storeCertificateOnStatus(ctx, o, certs) if err != nil { return err @@ -683,35 +738,75 @@ func getACMEOrder(ctx context.Context, cl acmecl.Interface, o *cmacme.Order) (*a return acmeOrder, nil } -func getAltCertChain(ctx context.Context, cl acmecl.Interface, certURL string, preferredChain string) (bool, [][]byte, error) { +func getPreferredCertChain( + ctx context.Context, + cl acmecl.Interface, + certURL string, + certBundle [][]byte, + preferredChain string, +) (bool, [][]byte, error) { log := logf.FromContext(ctx) - altURLs, err := cl.ListCertAlternates(ctx, certURL) - if err != nil { - return false, nil, fmt.Errorf("error listing alternate certificate URLs: %w", err) + + isMatch := func(name string, chain [][]byte) (bool, error) { + if len(chain) == 0 { + return false, nil + } + + // Check topmost certificate + cert, err := x509.ParseCertificate(chain[len(chain)-1]) + if err != nil { + return false, fmt.Errorf("error parsing certificate chain: %w", err) + } + + log.V(logf.DebugLevel).WithValues("Issuer CN", cert.Issuer.CommonName).Info("Found ACME bundle") + if cert.Issuer.CommonName == preferredChain { + // if the issuer's CN matched the preferred chain it means this bundle is + // signed by the requested chain + log.V(logf.DebugLevel). + WithValues("Issuer CN", cert.Issuer.CommonName). + Info("Selecting preferred ACME bundle with a matching Common Name from chain", "chainName", name) + return true, nil + } + + return false, nil + } + + // Check if the default chain matches the preferred chain + { + match, err := isMatch("default", certBundle) + if err != nil { + return false, nil, err + } + if match { + return true, certBundle, nil + } } - // Loop over all alternative chains - for _, altURL := range altURLs { - altChain, err := cl.FetchCert(ctx, altURL, true) + + // Check if any alternate chain matches the preferred chain + { + altURLs, err := cl.ListCertAlternates(ctx, certURL) if err != nil { - return false, nil, fmt.Errorf("error fetching alternate certificate chain from %s: %w", altURL, err) + return false, nil, fmt.Errorf("error listing alternate certificate URLs: %w", err) } - // Loop over each cert in this alternative chain - for _, altCert := range altChain { - cert, err := x509.ParseCertificate(altCert) + + for _, chainURL := range altURLs { + certChain, err := cl.FetchCert(ctx, chainURL, true) if err != nil { - return false, nil, fmt.Errorf("error parsing alternate certificate chain: %w", err) + return false, nil, fmt.Errorf("error fetching certificate chain from %s: %w", chainURL, err) } - log.V(logf.DebugLevel).WithValues("Issuer CN", cert.Issuer.CommonName).Info("Found alternative ACME bundle") - if cert.Issuer.CommonName == preferredChain { - // if the issuer's CN matched the preferred chain it means this bundle is - // signed by the requested chain - log.V(logf.DebugLevel).WithValues("Issuer CN", cert.Issuer.CommonName).Info("Selecting alternative ACME bundle with a matching Common Name from %s", altURL) - return true, altChain, nil + + match, err := isMatch(chainURL, certChain) + if err != nil { + return false, nil, err + } + + if match { + return true, certChain, nil } } } - return false, nil, nil + return false, nil, nil } // updateOrApplyStatus will update the order status. diff --git a/pkg/controller/acmeorders/sync_test.go b/pkg/controller/acmeorders/sync_test.go index a04fe252173..5f1792c302a 100644 --- a/pkg/controller/acmeorders/sync_test.go +++ b/pkg/controller/acmeorders/sync_test.go @@ -18,25 +18,32 @@ package acmeorders import ( "context" - "encoding/pem" "errors" "fmt" + "net/http" + "strings" "testing" "time" - acmeapi "golang.org/x/crypto/acme" + "github.com/go-logr/logr" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" coretesting "k8s.io/client-go/testing" fakeclock "k8s.io/utils/clock/testing" + "github.com/cert-manager/cert-manager/internal/pem" accountstest "github.com/cert-manager/cert-manager/pkg/acme/accounts/test" acmecl "github.com/cert-manager/cert-manager/pkg/acme/client" cmacme "github.com/cert-manager/cert-manager/pkg/apis/acme/v1" + v1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" testpkg "github.com/cert-manager/cert-manager/pkg/controller/test" + logf "github.com/cert-manager/cert-manager/pkg/logs" schedulertest "github.com/cert-manager/cert-manager/pkg/scheduler/test" + "github.com/cert-manager/cert-manager/pkg/util/pki" "github.com/cert-manager/cert-manager/test/unit/gen" + acmeapi "github.com/cert-manager/cert-manager/third_party/forked/acme" ) func TestSync(t *testing.T) { @@ -68,7 +75,7 @@ func TestSync(t *testing.T) { })) testIssuerHTTP01TestComPreferredChain := gen.Issuer("testissuer", gen.SetIssuerACME(cmacme.ACMEIssuer{ - PreferredChain: "ISRG Root X1", + PreferredChain: "DST Root CA X3", // This is the common name of the root certificate in the testAltCert Solvers: []cmacme.ACMEChallengeSolver{ { Selector: &cmacme.CertificateDNSNameSelector{ @@ -83,12 +90,27 @@ func TestSync(t *testing.T) { testOrder := gen.Order("testorder", gen.SetOrderCommonName("test.com"), - gen.SetOrderIssuer(cmmeta.ObjectReference{ + gen.SetOrderIssuer(cmmeta.IssuerReference{ Name: testIssuerHTTP01TestCom.Name, }), ) - testOrderIP := gen.Order("testorder", gen.SetOrderIssuer(cmmeta.ObjectReference{Name: testIssuerHTTP01.Name}), gen.SetOrderIPAddresses("10.0.0.1")) + testOrderIP := gen.Order("testorder", + gen.SetOrderCommonName("10.0.0.2"), + gen.SetOrderIssuer(cmmeta.IssuerReference{ + Name: testIssuerHTTP01.Name, + }), + gen.SetOrderIPAddresses("10.0.0.1")) + + const ipv6AddressOne = "2001:4860:4860::8888" + const ipv6AddressTwo = "2001:4860:4860::8844" + + testOrderIPV6 := gen.Order("testorder", + gen.SetOrderCommonName(ipv6AddressOne), + gen.SetOrderIssuer(cmmeta.IssuerReference{ + Name: testIssuerHTTP01.Name, + }), + gen.SetOrderIPAddresses(ipv6AddressTwo)) pendingStatus := cmacme.OrderStatus{ State: cmacme.Pending, @@ -144,6 +166,125 @@ func TestSync(t *testing.T) { Detail: "some error", } + // testCert is using the following Let's Encrypt chain (X1 is not included): + // leaf -> R3 -> ISRG Root X1 + testCert := `-----BEGIN CERTIFICATE----- +MIIEZjCCA06gAwIBAgISAx0TG3o1EufZi/OTOnR9vqt/MA0GCSqGSIb3DQEBCwUA +MDIxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MQswCQYDVQQD +EwJSMzAeFw0yMzEyMTkxNjIwMjFaFw0yNDAzMTgxNjIwMjBaMBoxGDAWBgNVBAMT +D2NlcnQtbWFuYWdlci5pbzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABCRoMZW8 +FQpb9R2fNhLps2Jms1e058hkiz9PzfyVZT4n0ONmV2OlnNXg7Y3F8v47yc5tq5W6 +8oum8TN+Y2v3u2CjggJXMIICUzAOBgNVHQ8BAf8EBAMCB4AwHQYDVR0lBBYwFAYI +KwYBBQUHAwEGCCsGAQUFBwMCMAwGA1UdEwEB/wQCMAAwHQYDVR0OBBYEFBljJ3oq +zwwTlkvYYFNit4ol03klMB8GA1UdIwQYMBaAFBQusxe3WFbLrlAJQOYfr52LFMLG +MFUGCCsGAQUFBwEBBEkwRzAhBggrBgEFBQcwAYYVaHR0cDovL3IzLm8ubGVuY3Iu +b3JnMCIGCCsGAQUFBzAChhZodHRwOi8vcjMuaS5sZW5jci5vcmcvMF4GA1UdEQRX +MFWCD2NlcnQtbWFuYWdlci5pb4IUZG9jcy5jZXJ0LW1hbmFnZXIuaW+CF25ldGxp +ZnkuY2VydC1tYW5hZ2VyLmlvghN3d3cuY2VydC1tYW5hZ2VyLmlvMBMGA1UdIAQM +MAowCAYGZ4EMAQIBMIIBBgYKKwYBBAHWeQIEAgSB9wSB9ADyAHcASLDja9qmRzQP +5WoC+p0w6xxSActW3SyB2bu/qznYhHMAAAGMgxfC6wAABAMASDBGAiEA1Ac3K8oT +EGY509sNj0/hZ4x5Td6aA3HsElojcF0DOMwCIQDxXgjEDmg0vS4u5BHEndIecmHe +2cMTnTIRM8c9IW0ZTgB3AKLiv9Ye3i8vB6DWTm03p9xlQ7DGtS6i2reK+Jpt9RfY +AAABjIMXwyAAAAQDAEgwRgIhAI9E0vDiqkNXYqtVmQBxM1Nk6eOmeMtZSGoojfBW +IsHBAiEA4S+mvJMqrVQ78UtAT+SGJ9Mr6fb/T45rDmID0PDhuXEwDQYJKoZIhvcN +AQELBQADggEBAJuZ66ArEUG/98Aaz+xYPbpRAfNmllyk9o6exmmZWrAvTBgzCF+D +T+UN8XtOwVW4lTJGHBsXmY9mGtP4lPehGwSD26fJsHTGZYTUGHFwStHrhbu1tyKc +hQg/wgviBN0oRsPWcBqMp0jZkHDNUZPq6fmVGXWeX+sx6Cu+iC8BrdQiEzD8DtrJ +11n6zDjy3mW64D/8MNCGzESbJ9F9N5162Yd3JWHO2eA9FXvcDg6lY2lBitQECqz1 +m8/A7QoPnC9uk/LvEnaqmLbZy7+yK/5+wDbW1y6AbIeo7On1UAXymn5zBTKlEkVm +Q+Rh9actCRTGKaeLO4ar2i59xZ9OnqZhx9c= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFFjCCAv6gAwIBAgIRAJErCErPDBinU/bWLiWnX1owDQYJKoZIhvcNAQELBQAw +TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh +cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMjAwOTA0MDAwMDAw +WhcNMjUwOTE1MTYwMDAwWjAyMQswCQYDVQQGEwJVUzEWMBQGA1UEChMNTGV0J3Mg +RW5jcnlwdDELMAkGA1UEAxMCUjMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK +AoIBAQC7AhUozPaglNMPEuyNVZLD+ILxmaZ6QoinXSaqtSu5xUyxr45r+XXIo9cP +R5QUVTVXjJ6oojkZ9YI8QqlObvU7wy7bjcCwXPNZOOftz2nwWgsbvsCUJCWH+jdx +sxPnHKzhm+/b5DtFUkWWqcFTzjTIUu61ru2P3mBw4qVUq7ZtDpelQDRrK9O8Zutm +NHz6a4uPVymZ+DAXXbpyb/uBxa3Shlg9F8fnCbvxK/eG3MHacV3URuPMrSXBiLxg +Z3Vms/EY96Jc5lP/Ooi2R6X/ExjqmAl3P51T+c8B5fWmcBcUr2Ok/5mzk53cU6cG +/kiFHaFpriV1uxPMUgP17VGhi9sVAgMBAAGjggEIMIIBBDAOBgNVHQ8BAf8EBAMC +AYYwHQYDVR0lBBYwFAYIKwYBBQUHAwIGCCsGAQUFBwMBMBIGA1UdEwEB/wQIMAYB +Af8CAQAwHQYDVR0OBBYEFBQusxe3WFbLrlAJQOYfr52LFMLGMB8GA1UdIwQYMBaA +FHm0WeZ7tuXkAXOACIjIGlj26ZtuMDIGCCsGAQUFBwEBBCYwJDAiBggrBgEFBQcw +AoYWaHR0cDovL3gxLmkubGVuY3Iub3JnLzAnBgNVHR8EIDAeMBygGqAYhhZodHRw +Oi8veDEuYy5sZW5jci5vcmcvMCIGA1UdIAQbMBkwCAYGZ4EMAQIBMA0GCysGAQQB +gt8TAQEBMA0GCSqGSIb3DQEBCwUAA4ICAQCFyk5HPqP3hUSFvNVneLKYY611TR6W +PTNlclQtgaDqw+34IL9fzLdwALduO/ZelN7kIJ+m74uyA+eitRY8kc607TkC53wl +ikfmZW4/RvTZ8M6UK+5UzhK8jCdLuMGYL6KvzXGRSgi3yLgjewQtCPkIVz6D2QQz +CkcheAmCJ8MqyJu5zlzyZMjAvnnAT45tRAxekrsu94sQ4egdRCnbWSDtY7kh+BIm +lJNXoB1lBMEKIq4QDUOXoRgffuDghje1WrG9ML+Hbisq/yFOGwXD9RiX8F6sw6W4 +avAuvDszue5L3sz85K+EC4Y/wFVDNvZo4TYXao6Z0f+lQKc0t8DQYzk1OXVu8rp2 +yJMC6alLbBfODALZvYH7n7do1AZls4I9d1P4jnkDrQoxB3UqQ9hVl3LEKQ73xF1O +yK5GhDDX8oVfGKF5u+decIsH4YaTw7mP3GFxJSqv3+0lUFJoi5Lc5da149p90Ids +hCExroL1+7mryIkXPeFM5TgO9r0rvZaBFOvV2z0gp35Z0+L4WPlbuEjN/lxPFin+ +HlUjr8gRsI3qfJOQFy/9rKIJR0Y/8Omwt/8oTWgy1mdeHmmjk7j1nYsvC9JSQ6Zv +MldlTTKB3zhThV1+XWYp6rjd5JW1zbVWEkLNxE7GJThEUG3szgBVGP7pSWTUTsqX +nLRbwHOoq7hHwg== +-----END CERTIFICATE----- +` + + // testCert is using the following Let's Encrypt chain (DST Root CA X3 is not included): + // leaf -> R3 -> ISRG Root X1 -> DST Root CA X3 + testAltCert := testCert + `-----BEGIN CERTIFICATE----- +MIIFYDCCBEigAwIBAgIQQAF3ITfU6UK47naqPGQKtzANBgkqhkiG9w0BAQsFADA/ +MSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMT +DkRTVCBSb290IENBIFgzMB4XDTIxMDEyMDE5MTQwM1oXDTI0MDkzMDE4MTQwM1ow +TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh +cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwggIiMA0GCSqGSIb3DQEB +AQUAA4ICDwAwggIKAoICAQCt6CRz9BQ385ueK1coHIe+3LffOJCMbjzmV6B493XC +ov71am72AE8o295ohmxEk7axY/0UEmu/H9LqMZshftEzPLpI9d1537O4/xLxIZpL +wYqGcWlKZmZsj348cL+tKSIG8+TA5oCu4kuPt5l+lAOf00eXfJlII1PoOK5PCm+D +LtFJV4yAdLbaL9A4jXsDcCEbdfIwPPqPrt3aY6vrFk/CjhFLfs8L6P+1dy70sntK +4EwSJQxwjQMpoOFTJOwT2e4ZvxCzSow/iaNhUd6shweU9GNx7C7ib1uYgeGJXDR5 +bHbvO5BieebbpJovJsXQEOEO3tkQjhb7t/eo98flAgeYjzYIlefiN5YNNnWe+w5y +sR2bvAP5SQXYgd0FtCrWQemsAXaVCg/Y39W9Eh81LygXbNKYwagJZHduRze6zqxZ +Xmidf3LWicUGQSk+WT7dJvUkyRGnWqNMQB9GoZm1pzpRboY7nn1ypxIFeFntPlF4 +FQsDj43QLwWyPntKHEtzBRL8xurgUBN8Q5N0s8p0544fAQjQMNRbcTa0B7rBMDBc +SLeCO5imfWCKoqMpgsy6vYMEG6KDA0Gh1gXxG8K28Kh8hjtGqEgqiNx2mna/H2ql +PRmP6zjzZN7IKw0KKP/32+IVQtQi0Cdd4Xn+GOdwiK1O5tmLOsbdJ1Fu/7xk9TND +TwIDAQABo4IBRjCCAUIwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYw +SwYIKwYBBQUHAQEEPzA9MDsGCCsGAQUFBzAChi9odHRwOi8vYXBwcy5pZGVudHJ1 +c3QuY29tL3Jvb3RzL2RzdHJvb3RjYXgzLnA3YzAfBgNVHSMEGDAWgBTEp7Gkeyxx ++tvhS5B1/8QVYIWJEDBUBgNVHSAETTBLMAgGBmeBDAECATA/BgsrBgEEAYLfEwEB +ATAwMC4GCCsGAQUFBwIBFiJodHRwOi8vY3BzLnJvb3QteDEubGV0c2VuY3J5cHQu +b3JnMDwGA1UdHwQ1MDMwMaAvoC2GK2h0dHA6Ly9jcmwuaWRlbnRydXN0LmNvbS9E +U1RST09UQ0FYM0NSTC5jcmwwHQYDVR0OBBYEFHm0WeZ7tuXkAXOACIjIGlj26Ztu +MA0GCSqGSIb3DQEBCwUAA4IBAQAKcwBslm7/DlLQrt2M51oGrS+o44+/yQoDFVDC +5WxCu2+b9LRPwkSICHXM6webFGJueN7sJ7o5XPWioW5WlHAQU7G75K/QosMrAdSW +9MUgNTP52GE24HGNtLi1qoJFlcDyqSMo59ahy2cI2qBDLKobkx/J3vWraV0T9VuG +WCLKTVXkcGdtwlfFRjlBz4pYg1htmf5X6DYO8A4jqv2Il9DjXA6USbW1FzXSLr9O +he8Y4IWS6wY7bCkjCWDcRQJMEhg76fsO3txE+FiYruq9RUWhiF1myv4Q6W+CyBFC +Dfvp7OOGAN6dEOM4+qR9sdjoSYKEBpsr6GtPAQw4dy753ec5 +-----END CERTIFICATE----- +` + + decodeAll := func(pemBytes []byte) [][]byte { + var blocks [][]byte + for { + block, rest, _ := pem.SafeDecodeCertificateBundle(pemBytes) + if block == nil { + break + } + + blocks = append(blocks, block.Bytes) + pemBytes = rest + } + return blocks + } + + rawTestCert := decodeAll([]byte(testCert)) + if _, err := pki.ParseSingleCertificateChainPEM([]byte(testCert)); err != nil { + t.Fatalf("error parsing test certificate: %v", err) + } + + rawTestAltCert := decodeAll([]byte(testAltCert)) + if _, err := pki.ParseSingleCertificateChainPEM([]byte(testAltCert)); err != nil { + t.Fatalf("error parsing test certificate: %v", err) + } + testOrderPending := gen.OrderFrom(testOrder, gen.SetOrderStatus(pendingStatus)) testOrderInvalid := testOrderPending.DeepCopy() testOrderInvalid.Status.State = cmacme.Invalid @@ -154,51 +295,13 @@ func TestSync(t *testing.T) { testOrderValid := testOrderPending.DeepCopy() testOrderValid.Status.State = cmacme.Valid // pem encoded word 'test' - testOrderValid.Status.Certificate = []byte(`-----BEGIN CERTIFICATE----- -dGVzdA== ------END CERTIFICATE----- -`) + testOrderValid.Status.Certificate = []byte(testCert) testOrderReady := testOrderPending.DeepCopy() testOrderReady.Status.State = cmacme.Ready - testCert := []byte(`-----BEGIN CERTIFICATE----- -MIIFjTCCA3WgAwIBAgIRANOxciY0IzLc9AUoUSrsnGowDQYJKoZIhvcNAQELBQAw -TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh -cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTYxMDA2MTU0MzU1 -WhcNMjExMDA2MTU0MzU1WjBKMQswCQYDVQQGEwJVUzEWMBQGA1UEChMNTGV0J3Mg -RW5jcnlwdDEjMCEGA1UEAxMaTGV0J3MgRW5jcnlwdCBBdXRob3JpdHkgWDMwggEi -MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCc0wzwWuUuR7dyXTeDs2hjMOrX -NSYZJeG9vjXxcJIvt7hLQQWrqZ41CFjssSrEaIcLo+N15Obzp2JxunmBYB/XkZqf -89B4Z3HIaQ6Vkc/+5pnpYDxIzH7KTXcSJJ1HG1rrueweNwAcnKx7pwXqzkrrvUHl -Npi5y/1tPJZo3yMqQpAMhnRnyH+lmrhSYRQTP2XpgofL2/oOVvaGifOFP5eGr7Dc -Gu9rDZUWfcQroGWymQQ2dYBrrErzG5BJeC+ilk8qICUpBMZ0wNAxzY8xOJUWuqgz -uEPxsR/DMH+ieTETPS02+OP88jNquTkxxa/EjQ0dZBYzqvqEKbbUC8DYfcOTAgMB -AAGjggFnMIIBYzAOBgNVHQ8BAf8EBAMCAYYwEgYDVR0TAQH/BAgwBgEB/wIBADBU -BgNVHSAETTBLMAgGBmeBDAECATA/BgsrBgEEAYLfEwEBATAwMC4GCCsGAQUFBwIB -FiJodHRwOi8vY3BzLnJvb3QteDEubGV0c2VuY3J5cHQub3JnMB0GA1UdDgQWBBSo -SmpjBH3duubRObemRWXv86jsoTAzBgNVHR8ELDAqMCigJqAkhiJodHRwOi8vY3Js -LnJvb3QteDEubGV0c2VuY3J5cHQub3JnMHIGCCsGAQUFBwEBBGYwZDAwBggrBgEF -BQcwAYYkaHR0cDovL29jc3Aucm9vdC14MS5sZXRzZW5jcnlwdC5vcmcvMDAGCCsG -AQUFBzAChiRodHRwOi8vY2VydC5yb290LXgxLmxldHNlbmNyeXB0Lm9yZy8wHwYD -VR0jBBgwFoAUebRZ5nu25eQBc4AIiMgaWPbpm24wDQYJKoZIhvcNAQELBQADggIB -ABnPdSA0LTqmRf/Q1eaM2jLonG4bQdEnqOJQ8nCqxOeTRrToEKtwT++36gTSlBGx -A/5dut82jJQ2jxN8RI8L9QFXrWi4xXnA2EqA10yjHiR6H9cj6MFiOnb5In1eWsRM -UM2v3e9tNsCAgBukPHAg1lQh07rvFKm/Bz9BCjaxorALINUfZ9DD64j2igLIxle2 -DPxW8dI/F2loHMjXZjqG8RkqZUdoxtID5+90FgsGIfkMpqgRS05f4zPbCEHqCXl1 -eO5HyELTgcVlLXXQDgAWnRzut1hFJeczY1tjQQno6f6s+nMydLN26WuU4s3UYvOu -OsUxRlJu7TSRHqDC3lSE5XggVkzdaPkuKGQbGpny+01/47hfXXNB7HntWNZ6N2Vw -p7G6OfY+YQrZwIaQmhrIqJZuigsrbe3W+gdn5ykE9+Ky0VgVUsfxo52mwFYs1JKY -2PGDuWx8M6DlS6qQkvHaRUo0FMd8TsSlbF0/v965qGFKhSDeQoMpYnwcmQilRh/0 -ayLThlHLN81gSkJjVrPI0Y8xCVPB4twb1PFUd2fPM3sA1tJ83sZ5v8vgFv2yofKR -PB0t6JzUA81mSqM3kxl5e+IZwhYAyO0OTg3/fs8HqGTNKd9BqoUwSRBzp06JMg5b -rUCGwbCUDI0mxadJ3Bz4WxR6fyNpBK2yAinWEsikxqEt ------END CERTIFICATE----- -`) - rawTestCert, _ := pem.Decode(testCert) - testOrderValidAltCert := gen.OrderFrom(testOrder, gen.SetOrderStatus(pendingStatus)) testOrderValidAltCert.Status.State = cmacme.Valid - testOrderValidAltCert.Status.Certificate = testCert + testOrderValidAltCert.Status.Certificate = []byte(testAltCert) fakeHTTP01ACMECl := &acmecl.FakeACME{ FakeHTTP01ChallengeResponse: func(s string) (string, error) { @@ -206,10 +309,16 @@ rUCGwbCUDI0mxadJ3Bz4WxR6fyNpBK2yAinWEsikxqEt return "key", nil }, } - testAuthorizationChallenge, err := buildChallenge(context.TODO(), fakeHTTP01ACMECl, testIssuerHTTP01TestCom, testOrderPending, testOrderPending.Status.Authorizations[0]) + testAuthorizationChallenge, err := buildPartialChallenge(t.Context(), testIssuerHTTP01TestCom, testOrderPending, testOrderPending.Status.Authorizations[0]) + + if err != nil { + t.Fatalf("error building Challenge resource test fixture: %v", err) + } + key, err := fakeHTTP01ACMECl.FakeHTTP01ChallengeResponse(testAuthorizationChallenge.Spec.Token) if err != nil { t.Fatalf("error building Challenge resource test fixture: %v", err) } + testAuthorizationChallenge.Spec.Key = key testAuthorizationChallengeValid := testAuthorizationChallenge.DeepCopy() testAuthorizationChallengeValid.Status.State = cmacme.Valid testAuthorizationChallengeInvalid := testAuthorizationChallenge.DeepCopy() @@ -245,6 +354,7 @@ rUCGwbCUDI0mxadJ3Bz4WxR6fyNpBK2yAinWEsikxqEt testACMEOrderValid := &acmeapi.Order{} *testACMEOrderValid = *testACMEOrderPending testACMEOrderValid.Status = acmeapi.StatusValid + testACMEOrderValid.CertURL = "http://testurl" // shallow copy testACMEOrderReady := &acmeapi.Order{} *testACMEOrderReady = *testACMEOrderPending @@ -291,7 +401,7 @@ rUCGwbCUDI0mxadJ3Bz4WxR6fyNpBK2yAinWEsikxqEt }, }, }, - "create a new order with the acme server with an IP address": { + "create a new order with the acme server with an IPv4 address": { order: testOrderIP, builder: &testpkg.Builder{ CertManagerObjects: []runtime.Object{testIssuerHTTP01, testOrderIP}, @@ -316,6 +426,53 @@ rUCGwbCUDI0mxadJ3Bz4WxR6fyNpBK2yAinWEsikxqEt if id[0].Value != "10.0.0.1" || id[0].Type != "ip" { return nil, errors.New("AuthzID needs to be the IP") } + if id[1].Value != "10.0.0.2" || id[1].Type != "ip" { + return nil, errors.New("AuthzID needs to be the IP") + } + return testACMEOrderPending, nil + }, + FakeGetAuthorization: func(ctx context.Context, url string) (*acmeapi.Authorization, error) { + if url != "http://authzurl" { + return nil, fmt.Errorf("Invalid URL: expected http://authzurl got %q", url) + } + return testACMEAuthorizationPending, nil + }, + FakeHTTP01ChallengeResponse: func(s string) (string, error) { + // TODO: assert s = "token" + return "key", nil + }, + }, + }, + "create a new order with the acme server with an IPv6 address": { + order: testOrderIPV6, + builder: &testpkg.Builder{ + CertManagerObjects: []runtime.Object{testIssuerHTTP01, testOrderIPV6}, + ExpectedActions: []testpkg.Action{ + testpkg.NewAction(coretesting.NewUpdateSubresourceAction(cmacme.SchemeGroupVersion.WithResource("orders"), + "status", + testOrderPending.Namespace, + gen.OrderFrom(testOrderIPV6, gen.SetOrderStatus(cmacme.OrderStatus{ + State: cmacme.Pending, + URL: "http://testurl.com/abcde", + FinalizeURL: "http://testurl.com/abcde/finalize", + Authorizations: []cmacme.ACMEAuthorization{ + { + URL: "http://authzurl", + }, + }, + })))), + }, + }, + acmeClient: &acmecl.FakeACME{ + FakeAuthorizeOrder: func(ctx context.Context, id []acmeapi.AuthzID, opt ...acmeapi.OrderOption) (*acmeapi.Order, error) { + if id[0].Value != ipv6AddressTwo || id[0].Type != "ip" { + return nil, fmt.Errorf("AuthzID 1 needs to be expected IPv6 address: wanted value=%s but got %s", ipv6AddressTwo, id[0].Value) + } + + if id[1].Value != ipv6AddressOne || id[1].Type != "ip" { + return nil, fmt.Errorf("AuthzID 2 needs to be expected IPv6 address: wanted value=%s but got %s", ipv6AddressOne, id[1].Value) + } + return testACMEOrderPending, nil }, FakeGetAuthorization: func(ctx context.Context, url string) (*acmeapi.Authorization, error) { @@ -338,7 +495,8 @@ rUCGwbCUDI0mxadJ3Bz4WxR6fyNpBK2yAinWEsikxqEt testpkg.NewAction(coretesting.NewCreateAction(cmacme.SchemeGroupVersion.WithResource("challenges"), testAuthorizationChallenge.Namespace, testAuthorizationChallenge)), }, ExpectedEvents: []string{ - `Normal Created Created Challenge resource "testorder-2179654896" for domain "test.com"`, + //nolint: dupword + `Normal Created Created Challenge resource "testorder-2580184217" for domain "test.com"`, }, }, acmeClient: &acmecl.FakeACME{ @@ -490,9 +648,6 @@ rUCGwbCUDI0mxadJ3Bz4WxR6fyNpBK2yAinWEsikxqEt ExpectedActions: []testpkg.Action{}, }, acmeClient: &acmecl.FakeACME{ - FakeGetOrder: func(_ context.Context, url string) (*acmeapi.Order, error) { - return testACMEOrderPending, nil - }, FakeHTTP01ChallengeResponse: func(s string) (string, error) { // TODO: assert s = "token" return "key", nil @@ -537,8 +692,7 @@ rUCGwbCUDI0mxadJ3Bz4WxR6fyNpBK2yAinWEsikxqEt return testACMEOrderValid, nil }, FakeCreateOrderCert: func(_ context.Context, url string, csr []byte, bundle bool) ([][]byte, string, error) { - testData := []byte("test") - return [][]byte{testData}, "http://testurl", nil + return rawTestCert, testACMEOrderValid.CertURL, nil }, FakeHTTP01ChallengeResponse: func(s string) (string, error) { // TODO: assert s = "token" @@ -614,7 +768,13 @@ rUCGwbCUDI0mxadJ3Bz4WxR6fyNpBK2yAinWEsikxqEt return "key", nil }, FakeFetchCert: func(_ context.Context, url string, bundle bool) ([][]byte, error) { - return [][]byte{[]byte("test")}, nil + if url != testACMEOrderValid.CertURL { + return nil, errors.New("Cert URL is incorrect") + } + if !bundle { + return nil, errors.New("Expecting to be called with bundle=true") + } + return rawTestCert, nil }, }, expectErr: false, @@ -644,24 +804,22 @@ rUCGwbCUDI0mxadJ3Bz4WxR6fyNpBK2yAinWEsikxqEt return "key", nil }, FakeListCertAlternates: func(_ context.Context, url string) ([]string, error) { + if url != testACMEOrderValid.CertURL { + return nil, errors.New("Cert URL is incorrect") + } return []string{"http://alturl"}, nil - }, FakeFetchCert: func(_ context.Context, url string, bundle bool) ([][]byte, error) { - if url != "http://alturl" { - // This bit just ensures that we - // call it from the correct - // place. This is the same URL - // that is returned from - // FakeCertAlternates that - // should have been called - // before this. + if url != testACMEOrderValid.CertURL && url != "http://alturl" { return nil, errors.New("Cert URL is incorrect") } if !bundle { return nil, errors.New("Expecting to be called with bundle=true") } - return [][]byte{rawTestCert.Bytes}, nil + if url == testACMEOrderValid.CertURL { + return rawTestCert, nil + } + return rawTestAltCert, nil }, }, expectErr: false, @@ -684,11 +842,10 @@ rUCGwbCUDI0mxadJ3Bz4WxR6fyNpBK2yAinWEsikxqEt return testACMEOrderValid, nil }, FakeCreateOrderCert: func(_ context.Context, url string, csr []byte, bundle bool) ([][]byte, string, error) { - testData := []byte("test") - return [][]byte{testData}, "http://testurl", nil + return rawTestCert, testACMEOrderValid.CertURL, nil }, FakeListCertAlternates: func(_ context.Context, url string) ([]string, error) { - if url != "http://testurl" { + if url != testACMEOrderValid.CertURL { return nil, errors.New("Cert URL is incorrect") } return []string{"http://alturl"}, nil @@ -696,19 +853,47 @@ rUCGwbCUDI0mxadJ3Bz4WxR6fyNpBK2yAinWEsikxqEt }, FakeFetchCert: func(_ context.Context, url string, bundle bool) ([][]byte, error) { if url != "http://alturl" { - // This bit just ensures that we - // call it from the correct - // place. This is the same URL - // that is returned from - // FakeCertAlternates that - // should have been called - // before this. - return nil, errors.New("Cert URL is incorrect") + return nil, errors.New("Cert URL is incorrect: expected http://alturl got " + url) } if !bundle { return nil, errors.New("Expecting to be called with bundle=true") } - return [][]byte{rawTestCert.Bytes}, nil + return rawTestAltCert, nil + }, + FakeHTTP01ChallengeResponse: func(s string) (string, error) { + // TODO: assert s = "token" + return "key", nil + }, + }, + }, + "preferred chain is default cert chain": { + order: testOrderReady.DeepCopy(), + builder: &testpkg.Builder{ + CertManagerObjects: []runtime.Object{ + gen.IssuerFrom(testIssuerHTTP01TestComPreferredChain, gen.SetIssuerACMEPreferredChain("ISRG Root X1")), + testOrderReady, testAuthorizationChallengeValid, + }, + ExpectedActions: []testpkg.Action{ + testpkg.NewAction(coretesting.NewUpdateSubresourceAction(cmacme.SchemeGroupVersion.WithResource("orders"), + "status", + testOrderValid.Namespace, testOrderValid)), + }, + ExpectedEvents: []string{ + "Normal Complete Order completed successfully", + }, + }, + acmeClient: &acmecl.FakeACME{ + FakeGetOrder: func(_ context.Context, url string) (*acmeapi.Order, error) { + return testACMEOrderValid, nil + }, + FakeCreateOrderCert: func(_ context.Context, url string, csr []byte, bundle bool) ([][]byte, string, error) { + return rawTestCert, testACMEOrderValid.CertURL, nil + }, + FakeListCertAlternates: func(_ context.Context, url string) ([]string, error) { + return nil, errors.New("should not be called") + }, + FakeFetchCert: func(_ context.Context, url string, bundle bool) ([][]byte, error) { + return nil, errors.New("should not be called") }, FakeHTTP01ChallengeResponse: func(s string) (string, error) { // TODO: assert s = "token" @@ -775,6 +960,56 @@ rUCGwbCUDI0mxadJ3Bz4WxR6fyNpBK2yAinWEsikxqEt }, acmeClient: &acmecl.FakeACME{}, }, + "acme-profiles:profiles-not-implemented": { + // Simulate an attempt to create an order with a profile on an ACME + // server which does not support profiles. + order: testOrder, + builder: &testpkg.Builder{ + CertManagerObjects: []runtime.Object{testIssuerHTTP01, testOrderPending}, + ExpectedActions: []testpkg.Action{ + testpkg.NewAction( + coretesting.NewUpdateSubresourceAction( + cmacme.SchemeGroupVersion.WithResource("orders"), + "status", + testOrderPending.Namespace, + gen.OrderFrom( + testOrderErrored, + gen.SetOrderReason("Failed to create Order: acme: certificate authority does not support profiles"), + ), + )), + }, + }, + acmeClient: &acmecl.FakeACME{ + FakeAuthorizeOrder: func(ctx context.Context, id []acmeapi.AuthzID, opt ...acmeapi.OrderOption) (*acmeapi.Order, error) { + return nil, acmeapi.ErrCADoesNotSupportProfiles + }, + }, + }, + "acme-profiles:profile-not-supported": { + // Simulate an attempt to create an order with a profile which the + // ACME server does not provide. + order: testOrder, + builder: &testpkg.Builder{ + CertManagerObjects: []runtime.Object{testIssuerHTTP01, testOrderPending}, + ExpectedActions: []testpkg.Action{ + testpkg.NewAction( + coretesting.NewUpdateSubresourceAction( + cmacme.SchemeGroupVersion.WithResource("orders"), + "status", + testOrderPending.Namespace, + gen.OrderFrom( + testOrderErrored, + gen.SetOrderReason("Failed to create Order: acme: certificate authority does not advertise a profile with name"), + ), + )), + }, + }, + acmeClient: &acmecl.FakeACME{ + FakeAuthorizeOrder: func(ctx context.Context, id []acmeapi.AuthzID, opt ...acmeapi.OrderOption) (*acmeapi.Order, error) { + return nil, acmeapi.ErrProfileNotInSetOfSupportedProfiles + }, + }, + }, } for name, test := range tests { @@ -817,7 +1052,7 @@ func runTest(t *testing.T, test testT) { } gotScheduled := false fakeScheduler := schedulertest.FakeScheduler{ - AddFunc: func(obj interface{}, duration time.Duration) { + AddFunc: func(obj types.NamespacedName, duration time.Duration) { gotScheduled = true }, } @@ -825,7 +1060,7 @@ func runTest(t *testing.T, test testT) { test.builder.Start() - err = cw.Sync(context.Background(), test.order) + err = cw.Sync(t.Context(), test.order) if err != nil && !test.expectErr { t.Errorf("Expected function to not error, but got: %v", err) } @@ -838,3 +1073,161 @@ func runTest(t *testing.T, test testT) { test.builder.CheckAndFinish(err) } + +func TestFinalizeOrder(t *testing.T) { + + tests := map[string]struct { + cl acmecl.Interface + o *cmacme.Order + createRequestLog string + getOrderLog string + logCount int + }{ + "CreateOrderRequest - Succeed, UpdateOrderStatus - Succeed": { + cl: &acmecl.FakeACME{ + FakeCreateOrderCert: func(ctx context.Context, finalizeURL string, csr []byte, bundle bool) (der [][]byte, certURL string, err error) { + return nil, "", nil + }, + FakeGetOrder: func(ctx context.Context, url string) (*acmeapi.Order, error) { + return nil, nil + }, + }, + o: &cmacme.Order{ + Spec: cmacme.OrderSpec{ + DNSNames: []string{ + "example.com", + }, + IPAddresses: []string{ + "1.2.3.4", + }, + }, + }, + logCount: 0, + }, + "CreateOrderRequest - Fail, UpdateOrderStatus - Succeed": { + cl: &acmecl.FakeACME{ + FakeCreateOrderCert: func(ctx context.Context, finalizeURL string, csr []byte, bundle bool) (der [][]byte, certURL string, err error) { + return nil, "", &acmeapi.Error{ + StatusCode: 500, + Header: http.Header{ + "header1": []string{"header2"}, + }, + } + }, + }, + o: &cmacme.Order{ + Spec: cmacme.OrderSpec{ + DNSNames: []string{ + "example.com", + }, + IPAddresses: []string{ + "1.2.3.4", + }, + }, + }, + createRequestLog: "acme server fatal error err=\"500 : \" [errorCode 500 dnsNames [example.com] ipAddresses [1.2.3.4] responseHeaders map[header1:[header2]]]", + logCount: 1, + }, + "CreateOrderRequest - Fail, UpdateOrderStatus - Fail": { + cl: &acmecl.FakeACME{ + FakeCreateOrderCert: func(ctx context.Context, finalizeURL string, csr []byte, bundle bool) (der [][]byte, certURL string, err error) { + return nil, "", &acmeapi.Error{ + StatusCode: 501, + Header: http.Header{ + "header1": []string{"header2"}, + }, + } + }, + FakeGetOrder: func(ctx context.Context, url string) (*acmeapi.Order, error) { + return nil, &acmeapi.Error{ + StatusCode: 502, + Header: http.Header{ + "header3": []string{"header4"}, + }, + } + }, + }, + o: &cmacme.Order{ + Spec: cmacme.OrderSpec{ + DNSNames: []string{ + "example.com", + }, + IPAddresses: []string{ + "1.2.3.4", + }, + }, + Status: cmacme.OrderStatus{ + URL: "example@example.com", + }, + }, + createRequestLog: "acme server fatal error err=\"501 : \" [errorCode 501 dnsNames [example.com] ipAddresses [1.2.3.4] responseHeaders map[header1:[header2]]]", + getOrderLog: "acme server fatal error err=\"502 : \" [errorCode 502 dnsNames [example.com] ipAddresses [1.2.3.4] responseHeaders map[header3:[header4]]]", + logCount: 2, + }, + } + + for name, test := range tests { + t.Run(name, func(t *testing.T) { + s := newFakeLogSink() + logger := logr.New(s) + ctx := context.Background() + ctx = logf.NewContext(ctx, logger) + + i := &v1.Issuer{} + cw := &controllerWrapper{} + + _ = cw.finalizeOrder(ctx, test.cl, test.o, i) + if len(s.errMessages) != test.logCount { + t.Errorf("Unexpected error message count. Expected %d, got %d", test.logCount, len(s.errMessages)) + } + + if test.createRequestLog != "" && s.errMessages[0] != test.createRequestLog { + t.Errorf("Incorrect log message got %q, expected %q", s.errMessages[0], test.createRequestLog) + } + if test.getOrderLog != "" && s.errMessages[1] != test.getOrderLog { + t.Errorf("Incorrect log message got %q, expected %q", s.errMessages[1], test.getOrderLog) + } + }) + } + +} + +type fakeLogSink struct { + messages []string + errMessages []string +} + +func (l *fakeLogSink) Init(info logr.RuntimeInfo) { +} + +func (l *fakeLogSink) Enabled(level int) bool { + return true +} + +func (l *fakeLogSink) WithValues(keysAndValues ...any) logr.LogSink { + return l +} + +func (l *fakeLogSink) WithName(name string) logr.LogSink { + return l +} + +func newFakeLogSink() *fakeLogSink { + return &fakeLogSink{} +} + +func (l *fakeLogSink) Info(level int, msg string, keysAndValues ...interface{}) { + l.messages = append(l.messages, fmt.Sprintf("%s %s", msg, keysAndValues)) +} + +func (l *fakeLogSink) Error(err error, msg string, keysAndValues ...interface{}) { + l.errMessages = append(l.errMessages, fmt.Sprintf("%s err=%q %v", msg, err, keysAndValues)) +} + +func (l *fakeLogSink) String() string { + out := make([]string, len(l.messages)) + for i := range l.messages { + out[i] = "\t-" + l.messages[i] + } + return strings.Join(out, "\n") +} diff --git a/pkg/controller/acmeorders/util.go b/pkg/controller/acmeorders/util.go index 4952c3be035..36dadf12315 100644 --- a/pkg/controller/acmeorders/util.go +++ b/pkg/controller/acmeorders/util.go @@ -27,16 +27,19 @@ import ( "github.com/cert-manager/cert-manager/pkg/api/util" cmacme "github.com/cert-manager/cert-manager/pkg/apis/acme/v1" cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" - "github.com/cert-manager/cert-manager/pkg/controller/acmeorders/selectors" logf "github.com/cert-manager/cert-manager/pkg/logs" + "github.com/cert-manager/cert-manager/pkg/util/solverpicker" ) var ( orderGvk = cmacme.SchemeGroupVersion.WithKind("Order") ) -func buildRequiredChallenges(ctx context.Context, cl acmecl.Interface, issuer cmapi.GenericIssuer, o *cmacme.Order) ([]cmacme.Challenge, error) { - chs := make([]cmacme.Challenge, 0) +// buildPartialRequiredChallenges builds partial required ACME challenges by +// looking at authorization on order spec and related issuer. It does not call +// ACME. ensureKeysForChallenge must be called before creating the Challenge. +func buildPartialRequiredChallenges(ctx context.Context, issuer cmapi.GenericIssuer, o *cmacme.Order) ([]*cmacme.Challenge, error) { + chs := make([]*cmacme.Challenge, 0) for _, a := range o.Status.Authorizations { if a.InitialState == cmacme.Valid { wc := false @@ -46,17 +49,20 @@ func buildRequiredChallenges(ctx context.Context, cl acmecl.Interface, issuer cm logf.FromContext(ctx).V(logf.DebugLevel).Info("Authorization already valid, not creating Challenge resource", "identifier", a.Identifier, "is_wildcard", wc) continue } - ch, err := buildChallenge(ctx, cl, issuer, o, a) + ch, err := buildPartialChallenge(ctx, issuer, o, a) if err != nil { return nil, err } - chs = append(chs, *ch) + chs = append(chs, ch) } return chs, nil } -func buildChallenge(ctx context.Context, cl acmecl.Interface, issuer cmapi.GenericIssuer, o *cmacme.Order, authz cmacme.ACMEAuthorization) (*cmacme.Challenge, error) { - chSpec, err := challengeSpecForAuthorization(ctx, cl, issuer, o, authz) +// buildPartialChallenge builds a challenge for the required ACME Authorization. +// The spec will be populated with fields that can be determined by looking at +// the ACME Authorization object returned in Order. +func buildPartialChallenge(ctx context.Context, issuer cmapi.GenericIssuer, o *cmacme.Order, authz cmacme.ACMEAuthorization) (*cmacme.Challenge, error) { + chSpec, err := partialChallengeSpecForAuthorization(ctx, issuer, o, authz) if err != nil { // TODO: in this case, we should probably not return the error as it's // unlikely we can make it succeed by retrying. @@ -78,10 +84,10 @@ func buildChallenge(ctx context.Context, cl acmecl.Interface, issuer cmapi.Gener }, nil } -func challengeSpecForAuthorization(ctx context.Context, cl acmecl.Interface, issuer cmapi.GenericIssuer, o *cmacme.Order, authz cmacme.ACMEAuthorization) (*cmacme.ChallengeSpec, error) { - log := logf.FromContext(ctx, "challengeSpecForAuthorization") - dbg := log.V(logf.DebugLevel) - +// partialChallengeSpecForAuthorization builds a partial challenge spec by +// looking at the ACME authorization object and issuer. It does not make any +// ACME calls. +func partialChallengeSpecForAuthorization(ctx context.Context, issuer cmapi.GenericIssuer, o *cmacme.Order, authz cmacme.ACMEAuthorization) (*cmacme.ChallengeSpec, error) { // 1. fetch solvers from issuer solvers := issuer.GetSpec().ACME.Solvers @@ -94,158 +100,7 @@ func challengeSpecForAuthorization(ctx context.Context, cl acmecl.Interface, iss domainToFind = "*." + domainToFind } - var selectedSolver *cmacme.ACMEChallengeSolver - var selectedChallenge *cmacme.ACMEChallenge - selectedNumLabelsMatch := 0 - selectedNumDNSNamesMatch := 0 - selectedNumDNSZonesMatch := 0 - - challengeForSolver := func(solver *cmacme.ACMEChallengeSolver) *cmacme.ACMEChallenge { - for _, ch := range authz.Challenges { - switch { - case ch.Type == "http-01" && solver.HTTP01 != nil: - return &ch - case ch.Type == "dns-01" && solver.DNS01 != nil: - return &ch - } - } - return nil - } - - // 2. filter solvers to only those that matchLabels - for _, cfg := range solvers { - acmech := challengeForSolver(&cfg) - if acmech == nil { - dbg.Info("cannot use solver as the ACME authorization does not allow solvers of this type") - continue - } - - if cfg.Selector == nil { - if selectedSolver != nil { - dbg.Info("not selecting solver as previously selected solver has a just as or more specific selector") - continue - } - dbg.Info("selecting solver due to match all selector and no previously selected solver") - selectedSolver = cfg.DeepCopy() - selectedChallenge = acmech - continue - } - - labelsMatch, numLabelsMatch := selectors.Labels(*cfg.Selector).Matches(o.ObjectMeta, domainToFind) - dnsNamesMatch, numDNSNamesMatch := selectors.DNSNames(*cfg.Selector).Matches(o.ObjectMeta, domainToFind) - dnsZonesMatch, numDNSZonesMatch := selectors.DNSZones(*cfg.Selector).Matches(o.ObjectMeta, domainToFind) - - if !labelsMatch || !dnsNamesMatch || !dnsZonesMatch { - dbg.Info("not selecting solver", "labels_match", labelsMatch, "dnsnames_match", dnsNamesMatch, "dnszones_match", dnsZonesMatch) - continue - } - - dbg.Info("selector matches") - - selectSolver := func() { - selectedSolver = cfg.DeepCopy() - selectedChallenge = acmech - selectedNumLabelsMatch = numLabelsMatch - selectedNumDNSNamesMatch = numDNSNamesMatch - selectedNumDNSZonesMatch = numDNSZonesMatch - } - - if selectedSolver == nil { - dbg.Info("selecting solver as there is no previously selected solver") - selectSolver() - continue - } - - dbg.Info("determining whether this match is more significant than last") - - // because we don't count multiple dnsName matches as extra 'weight' - // in the selection process, we normalize the numDNSNamesMatch vars - // to be either 1 or 0 (i.e. true or false) - selectedHasMatchingDNSNames := selectedNumDNSNamesMatch > 0 - hasMatchingDNSNames := numDNSNamesMatch > 0 - - // dnsName selectors have the highest precedence, so check them first - switch { - case !selectedHasMatchingDNSNames && hasMatchingDNSNames: - dbg.Info("selecting solver as this solver has matching DNS names and the previous one does not") - selectSolver() - continue - case selectedHasMatchingDNSNames && !hasMatchingDNSNames: - dbg.Info("not selecting solver as the previous one has matching DNS names and this one does not") - continue - case !selectedHasMatchingDNSNames && !hasMatchingDNSNames: - dbg.Info("solver does not have any matching DNS names, checking dnsZones") - // check zones - case selectedHasMatchingDNSNames && hasMatchingDNSNames: - dbg.Info("both this solver and the previously selected one matches dnsNames, comparing zones") - if numDNSZonesMatch > selectedNumDNSZonesMatch { - dbg.Info("selecting solver as this one has a more specific dnsZone match than the previously selected one") - selectSolver() - continue - } - if selectedNumDNSZonesMatch > numDNSZonesMatch { - dbg.Info("not selecting this solver as the previously selected one has a more specific dnsZone match") - continue - } - dbg.Info("both this solver and the previously selected one match dnsZones, comparing labels") - // choose the one with the most labels - if numLabelsMatch > selectedNumLabelsMatch { - dbg.Info("selecting solver as this one has more labels than the previously selected one") - selectSolver() - continue - } - dbg.Info("not selecting this solver as previous one has either the same number of or more labels") - continue - } - - selectedHasMatchingDNSZones := selectedNumDNSZonesMatch > 0 - hasMatchingDNSZones := numDNSZonesMatch > 0 - - switch { - case !selectedHasMatchingDNSZones && hasMatchingDNSZones: - dbg.Info("selecting solver as this solver has matching DNS zones and the previous one does not") - selectSolver() - continue - case selectedHasMatchingDNSZones && !hasMatchingDNSZones: - dbg.Info("not selecting solver as the previous one has matching DNS zones and this one does not") - continue - case !selectedHasMatchingDNSZones && !hasMatchingDNSZones: - dbg.Info("solver does not have any matching DNS zones, checking labels") - // check labels - case selectedHasMatchingDNSZones && hasMatchingDNSZones: - dbg.Info("both this solver and the previously selected one matches dnsZones") - dbg.Info("comparing number of matching domain segments") - // choose the one with the most matching DNS zone segments - if numDNSZonesMatch > selectedNumDNSZonesMatch { - dbg.Info("selecting solver because this one has more matching DNS zone segments") - selectSolver() - continue - } - if selectedNumDNSZonesMatch > numDNSZonesMatch { - dbg.Info("not selecting solver because previous one has more matching DNS zone segments") - continue - } - // choose the one with the most labels - if numLabelsMatch > selectedNumLabelsMatch { - dbg.Info("selecting solver because this one has more labels than the previous one") - selectSolver() - continue - } - dbg.Info("not selecting solver as this one's number of matching labels is equal to or less than the last one") - continue - } - - if numLabelsMatch > selectedNumLabelsMatch { - dbg.Info("selecting solver as this one has more labels than the last one") - selectSolver() - continue - } - - dbg.Info("not selecting solver as this one's number of matching labels is equal to or less than the last one (reached end of loop)") - // if we get here, the number of matches is less than or equal so we - // fallback to choosing the first in the list - } - + selectedSolver, selectedChallenge := solverpicker.Pick(ctx, domainToFind, authz.Challenges, solvers, o) if selectedSolver == nil || selectedChallenge == nil { return nil, fmt.Errorf("no configured challenge solvers can be used for this challenge") } @@ -258,11 +113,6 @@ func challengeSpecForAuthorization(ctx context.Context, cl acmecl.Interface, iss return nil, err } - key, err := keyForChallenge(cl, selectedChallenge.Token, chType) - if err != nil { - return nil, err - } - // 4. handle overriding the HTTP01 ingress class and name fields using the // ACMECertificateHTTP01IngressNameOverride & Class annotations if err := applyIngressParameterAnnotationOverrides(o, selectedSolver); err != nil { @@ -276,7 +126,6 @@ func challengeSpecForAuthorization(ctx context.Context, cl acmecl.Interface, iss URL: selectedChallenge.URL, DNSName: authz.Identifier, Token: selectedChallenge.Token, - Key: key, // selectedSolver cannot be nil due to the check above. Solver: *selectedSolver, Wildcard: wc, @@ -321,15 +170,26 @@ func applyIngressParameterAnnotationOverrides(o *cmacme.Order, s *cmacme.ACMECha return nil } -func keyForChallenge(cl acmecl.Interface, token string, chType cmacme.ACMEChallengeType) (string, error) { - switch chType { - case cmacme.ACMEChallengeTypeHTTP01: - return cl.HTTP01ChallengeResponse(token) - case cmacme.ACMEChallengeTypeDNS01: - return cl.DNS01ChallengeRecord(token) - default: - return "", fmt.Errorf("unsupported challenge type: %v", chType) +func ensureKeysForChallenges(cl acmecl.Interface, challenges []*cmacme.Challenge) ([]*cmacme.Challenge, error) { + for _, ch := range challenges { + var ( + key string + err error + ) + switch ch.Spec.Type { + case cmacme.ACMEChallengeTypeHTTP01: + key, err = cl.HTTP01ChallengeResponse(ch.Spec.Token) + case cmacme.ACMEChallengeTypeDNS01: + key, err = cl.DNS01ChallengeRecord(ch.Spec.Token) + default: + return nil, fmt.Errorf("challenge %s has unsupported challenge type: %s", ch.Name, ch.Spec.Type) + } + if err != nil { + return nil, err + } + ch.Spec.Key = key } + return challenges, nil } func anyChallengesFailed(chs []*cmacme.Challenge) bool { diff --git a/pkg/controller/acmeorders/util_test.go b/pkg/controller/acmeorders/util_test.go index eda53bbcfa4..6cfa47469b1 100644 --- a/pkg/controller/acmeorders/util_test.go +++ b/pkg/controller/acmeorders/util_test.go @@ -17,17 +17,18 @@ limitations under the License. package acmeorders import ( - "context" + "fmt" "reflect" "testing" - "github.com/kr/pretty" + "github.com/google/go-cmp/cmp" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/utils/pointer" + "k8s.io/utils/ptr" acmecl "github.com/cert-manager/cert-manager/pkg/acme/client" cmacme "github.com/cert-manager/cert-manager/pkg/apis/acme/v1" - v1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" + cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" + "github.com/cert-manager/cert-manager/test/unit/gen" ) func TestChallengeSpecForAuthorization(t *testing.T) { @@ -56,29 +57,6 @@ func TestChallengeSpecForAuthorization(t *testing.T) { }, }, } - nonMatchingSelectorSolver := cmacme.ACMEChallengeSolver{ - Selector: &cmacme.CertificateDNSNameSelector{ - MatchLabels: map[string]string{ - "label": "does-not-exist", - "does-not": "match", - }, - }, - HTTP01: &cmacme.ACMEChallengeSolverHTTP01{ - Ingress: &cmacme.ACMEChallengeSolverHTTP01Ingress{ - Name: "non-matching-selector-solver", - }, - }, - } - exampleComDNSNameSelectorSolver := cmacme.ACMEChallengeSolver{ - Selector: &cmacme.CertificateDNSNameSelector{ - DNSNames: []string{"example.com"}, - }, - HTTP01: &cmacme.ACMEChallengeSolverHTTP01{ - Ingress: &cmacme.ACMEChallengeSolverHTTP01Ingress{ - Name: "example-com-dns-name-selector-solver", - }, - }, - } // define ACME challenges that are used during tests acmeChallengeHTTP01 := &cmacme.ACMEChallenge{ Type: "http-01", @@ -91,7 +69,7 @@ func TestChallengeSpecForAuthorization(t *testing.T) { tests := map[string]struct { acmeClient acmecl.Interface - issuer v1.GenericIssuer + issuer cmapi.GenericIssuer order *cmacme.Order authz *cmacme.ACMEAuthorization @@ -100,9 +78,9 @@ func TestChallengeSpecForAuthorization(t *testing.T) { }{ "should override the ingress name to edit if override annotation is specified": { acmeClient: basicACMEClient, - issuer: &v1.Issuer{ - Spec: v1.IssuerSpec{ - IssuerConfig: v1.IssuerConfig{ + issuer: &cmapi.Issuer{ + Spec: cmapi.IssuerSpec{ + IssuerConfig: cmapi.IssuerConfig{ ACME: &cmacme.ACMEIssuer{ Solvers: []cmacme.ACMEChallengeSolver{emptySelectorSolverHTTP01}, }, @@ -127,7 +105,6 @@ func TestChallengeSpecForAuthorization(t *testing.T) { Type: cmacme.ACMEChallengeTypeHTTP01, DNSName: "example.com", Token: acmeChallengeHTTP01.Token, - Key: "http01", Solver: cmacme.ACMEChallengeSolver{ HTTP01: &cmacme.ACMEChallengeSolverHTTP01{ Ingress: &cmacme.ACMEChallengeSolverHTTP01Ingress{ @@ -139,9 +116,9 @@ func TestChallengeSpecForAuthorization(t *testing.T) { }, "should override the ingress class to edit if override annotation is specified": { acmeClient: basicACMEClient, - issuer: &v1.Issuer{ - Spec: v1.IssuerSpec{ - IssuerConfig: v1.IssuerConfig{ + issuer: &cmapi.Issuer{ + Spec: cmapi.IssuerSpec{ + IssuerConfig: cmapi.IssuerConfig{ ACME: &cmacme.ACMEIssuer{ Solvers: []cmacme.ACMEChallengeSolver{emptySelectorSolverHTTP01}, }, @@ -166,11 +143,10 @@ func TestChallengeSpecForAuthorization(t *testing.T) { Type: cmacme.ACMEChallengeTypeHTTP01, DNSName: "example.com", Token: acmeChallengeHTTP01.Token, - Key: "http01", Solver: cmacme.ACMEChallengeSolver{ HTTP01: &cmacme.ACMEChallengeSolverHTTP01{ Ingress: &cmacme.ACMEChallengeSolverHTTP01Ingress{ - Class: pointer.StringPtr("test-class-to-override"), + Class: ptr.To("test-class-to-override"), }, }, }, @@ -178,9 +154,9 @@ func TestChallengeSpecForAuthorization(t *testing.T) { }, "should return an error if both ingress class and name override annotations are set": { acmeClient: basicACMEClient, - issuer: &v1.Issuer{ - Spec: v1.IssuerSpec{ - IssuerConfig: v1.IssuerConfig{ + issuer: &cmapi.Issuer{ + Spec: cmapi.IssuerSpec{ + IssuerConfig: cmapi.IssuerConfig{ ACME: &cmacme.ACMEIssuer{ Solvers: []cmacme.ACMEChallengeSolver{emptySelectorSolverHTTP01}, }, @@ -206,9 +182,9 @@ func TestChallengeSpecForAuthorization(t *testing.T) { }, "should ignore HTTP01 override annotations if DNS01 solver is chosen": { acmeClient: basicACMEClient, - issuer: &v1.Issuer{ - Spec: v1.IssuerSpec{ - IssuerConfig: v1.IssuerConfig{ + issuer: &cmapi.Issuer{ + Spec: cmapi.IssuerSpec{ + IssuerConfig: cmapi.IssuerConfig{ ACME: &cmacme.ACMEIssuer{ Solvers: []cmacme.ACMEChallengeSolver{emptySelectorSolverDNS01}, }, @@ -233,248 +209,25 @@ func TestChallengeSpecForAuthorization(t *testing.T) { Type: cmacme.ACMEChallengeTypeDNS01, DNSName: "example.com", Token: acmeChallengeDNS01.Token, - Key: "dns01", - Solver: emptySelectorSolverDNS01, - }, - }, - "should use configured default solver when no others are present": { - acmeClient: basicACMEClient, - issuer: &v1.Issuer{ - Spec: v1.IssuerSpec{ - IssuerConfig: v1.IssuerConfig{ - ACME: &cmacme.ACMEIssuer{ - Solvers: []cmacme.ACMEChallengeSolver{emptySelectorSolverHTTP01}, - }, - }, - }, - }, - order: &cmacme.Order{ - Spec: cmacme.OrderSpec{ - DNSNames: []string{"example.com"}, - }, - }, - authz: &cmacme.ACMEAuthorization{ - Identifier: "example.com", - Challenges: []cmacme.ACMEChallenge{*acmeChallengeHTTP01}, - }, - expectedChallengeSpec: &cmacme.ChallengeSpec{ - Type: cmacme.ACMEChallengeTypeHTTP01, - DNSName: "example.com", - Token: acmeChallengeHTTP01.Token, - Key: "http01", - Solver: emptySelectorSolverHTTP01, - }, - }, - "should use configured default solver when no others are present but selector is non-nil": { - acmeClient: basicACMEClient, - issuer: &v1.Issuer{ - Spec: v1.IssuerSpec{ - IssuerConfig: v1.IssuerConfig{ - ACME: &cmacme.ACMEIssuer{ - Solvers: []cmacme.ACMEChallengeSolver{ - { - Selector: &cmacme.CertificateDNSNameSelector{}, - HTTP01: &cmacme.ACMEChallengeSolverHTTP01{ - Ingress: &cmacme.ACMEChallengeSolverHTTP01Ingress{ - Name: "empty-selector-solver", - }, - }, - }, - }, - }, - }, - }, - }, - order: &cmacme.Order{ - Spec: cmacme.OrderSpec{ - DNSNames: []string{"example.com"}, - }, - }, - authz: &cmacme.ACMEAuthorization{ - Identifier: "example.com", - Challenges: []cmacme.ACMEChallenge{*acmeChallengeHTTP01}, - }, - expectedChallengeSpec: &cmacme.ChallengeSpec{ - Type: cmacme.ACMEChallengeTypeHTTP01, - DNSName: "example.com", - Token: acmeChallengeHTTP01.Token, - Key: "http01", - Solver: cmacme.ACMEChallengeSolver{ - Selector: &cmacme.CertificateDNSNameSelector{}, - HTTP01: &cmacme.ACMEChallengeSolverHTTP01{ - Ingress: &cmacme.ACMEChallengeSolverHTTP01Ingress{ - Name: "empty-selector-solver", - }, - }, - }, - }, - }, - "should use configured default solver when others do not match": { - acmeClient: basicACMEClient, - issuer: &v1.Issuer{ - Spec: v1.IssuerSpec{ - IssuerConfig: v1.IssuerConfig{ - ACME: &cmacme.ACMEIssuer{ - Solvers: []cmacme.ACMEChallengeSolver{ - emptySelectorSolverHTTP01, - nonMatchingSelectorSolver, - }, - }, - }, - }, - }, - order: &cmacme.Order{ - Spec: cmacme.OrderSpec{ - DNSNames: []string{"example.com"}, - }, - }, - authz: &cmacme.ACMEAuthorization{ - Identifier: "example.com", - Challenges: []cmacme.ACMEChallenge{*acmeChallengeHTTP01}, - }, - expectedChallengeSpec: &cmacme.ChallengeSpec{ - Type: cmacme.ACMEChallengeTypeHTTP01, - DNSName: "example.com", - Token: acmeChallengeHTTP01.Token, - Key: "http01", - Solver: emptySelectorSolverHTTP01, - }, - }, - "should use DNS01 solver over HTTP01 if challenge is of type DNS01": { - acmeClient: basicACMEClient, - issuer: &v1.Issuer{ - Spec: v1.IssuerSpec{ - IssuerConfig: v1.IssuerConfig{ - ACME: &cmacme.ACMEIssuer{ - Solvers: []cmacme.ACMEChallengeSolver{ - emptySelectorSolverHTTP01, - emptySelectorSolverDNS01, - }, - }, - }, - }, - }, - order: &cmacme.Order{ - Spec: cmacme.OrderSpec{ - DNSNames: []string{"example.com"}, - }, - }, - authz: &cmacme.ACMEAuthorization{ - Identifier: "example.com", - Challenges: []cmacme.ACMEChallenge{*acmeChallengeDNS01}, - }, - expectedChallengeSpec: &cmacme.ChallengeSpec{ - Type: cmacme.ACMEChallengeTypeDNS01, - DNSName: "example.com", - Token: acmeChallengeDNS01.Token, - Key: "dns01", Solver: emptySelectorSolverDNS01, }, }, "should return an error if none match": { - acmeClient: basicACMEClient, - issuer: &v1.Issuer{ - Spec: v1.IssuerSpec{ - IssuerConfig: v1.IssuerConfig{ - ACME: &cmacme.ACMEIssuer{ - Solvers: []cmacme.ACMEChallengeSolver{ - nonMatchingSelectorSolver, - }, - }, - }, - }, - }, - order: &cmacme.Order{ - Spec: cmacme.OrderSpec{ - DNSNames: []string{"example.com"}, - }, - }, - authz: &cmacme.ACMEAuthorization{ - Identifier: "example.com", - Challenges: []cmacme.ACMEChallenge{*acmeChallengeHTTP01}, - }, - expectedError: true, - }, - "uses correct solver when selector explicitly names dnsName": { - acmeClient: basicACMEClient, - issuer: &v1.Issuer{ - Spec: v1.IssuerSpec{ - IssuerConfig: v1.IssuerConfig{ - ACME: &cmacme.ACMEIssuer{ - Solvers: []cmacme.ACMEChallengeSolver{ - emptySelectorSolverHTTP01, - exampleComDNSNameSelectorSolver, - }, - }, - }, - }, - }, - order: &cmacme.Order{ - Spec: cmacme.OrderSpec{ - DNSNames: []string{"example.com"}, - }, - }, - authz: &cmacme.ACMEAuthorization{ - Identifier: "example.com", - Challenges: []cmacme.ACMEChallenge{*acmeChallengeHTTP01}, - }, - expectedChallengeSpec: &cmacme.ChallengeSpec{ - Type: cmacme.ACMEChallengeTypeHTTP01, - DNSName: "example.com", - Token: acmeChallengeHTTP01.Token, - Key: "http01", - Solver: exampleComDNSNameSelectorSolver, - }, - }, - "uses default solver if dnsName does not match": { - acmeClient: basicACMEClient, - issuer: &v1.Issuer{ - Spec: v1.IssuerSpec{ - IssuerConfig: v1.IssuerConfig{ + issuer: &cmapi.Issuer{ + Spec: cmapi.IssuerSpec{ + IssuerConfig: cmapi.IssuerConfig{ ACME: &cmacme.ACMEIssuer{ Solvers: []cmacme.ACMEChallengeSolver{ - emptySelectorSolverHTTP01, - exampleComDNSNameSelectorSolver, - }, - }, - }, - }, - }, - order: &cmacme.Order{ - Spec: cmacme.OrderSpec{ - DNSNames: []string{"notexample.com"}, - }, - }, - authz: &cmacme.ACMEAuthorization{ - Identifier: "notexample.com", - Challenges: []cmacme.ACMEChallenge{*acmeChallengeHTTP01}, - }, - expectedChallengeSpec: &cmacme.ChallengeSpec{ - Type: cmacme.ACMEChallengeTypeHTTP01, - DNSName: "notexample.com", - Token: acmeChallengeHTTP01.Token, - Key: "http01", - Solver: emptySelectorSolverHTTP01, - }, - }, - "if two solvers specify the same dnsName, the one with the most labels should be chosen": { - acmeClient: basicACMEClient, - issuer: &v1.Issuer{ - Spec: v1.IssuerSpec{ - IssuerConfig: v1.IssuerConfig{ - ACME: &cmacme.ACMEIssuer{ - Solvers: []cmacme.ACMEChallengeSolver{ - exampleComDNSNameSelectorSolver, { Selector: &cmacme.CertificateDNSNameSelector{ MatchLabels: map[string]string{ - "label": "exists", + "label": "does-not-exist", + "does-not": "match", }, - DNSNames: []string{"example.com"}, }, HTTP01: &cmacme.ACMEChallengeSolverHTTP01{ Ingress: &cmacme.ACMEChallengeSolverHTTP01Ingress{ - Name: "example-com-dns-name-labels-selector-solver", + Name: "non-matching-selector-solver", }, }, }, @@ -484,11 +237,6 @@ func TestChallengeSpecForAuthorization(t *testing.T) { }, }, order: &cmacme.Order{ - ObjectMeta: metav1.ObjectMeta{ - Labels: map[string]string{ - "label": "exists", - }, - }, Spec: cmacme.OrderSpec{ DNSNames: []string{"example.com"}, }, @@ -497,779 +245,92 @@ func TestChallengeSpecForAuthorization(t *testing.T) { Identifier: "example.com", Challenges: []cmacme.ACMEChallenge{*acmeChallengeHTTP01}, }, - expectedChallengeSpec: &cmacme.ChallengeSpec{ - Type: cmacme.ACMEChallengeTypeHTTP01, - DNSName: "example.com", - Token: acmeChallengeHTTP01.Token, - Key: "http01", - Solver: cmacme.ACMEChallengeSolver{ - Selector: &cmacme.CertificateDNSNameSelector{ - MatchLabels: map[string]string{ - "label": "exists", - }, - DNSNames: []string{"example.com"}, - }, - HTTP01: &cmacme.ACMEChallengeSolverHTTP01{ - Ingress: &cmacme.ACMEChallengeSolverHTTP01Ingress{ - Name: "example-com-dns-name-labels-selector-solver", - }, - }, - }, - }, + expectedError: true, }, - "if one solver matches with dnsNames, and the other solver matches with labels, the dnsName solver should be chosen": { - acmeClient: basicACMEClient, - issuer: &v1.Issuer{ - Spec: v1.IssuerSpec{ - IssuerConfig: v1.IssuerConfig{ - ACME: &cmacme.ACMEIssuer{ - Solvers: []cmacme.ACMEChallengeSolver{ - exampleComDNSNameSelectorSolver, - { - Selector: &cmacme.CertificateDNSNameSelector{ - MatchLabels: map[string]string{ - "label": "exists", - }, - }, - HTTP01: &cmacme.ACMEChallengeSolverHTTP01{ - Ingress: &cmacme.ACMEChallengeSolverHTTP01Ingress{ - Name: "example-com-labels-selector-solver", - }, - }, - }, - }, - }, - }, - }, - }, - order: &cmacme.Order{ - ObjectMeta: metav1.ObjectMeta{ - Labels: map[string]string{ - "label": "exists", - }, - }, - Spec: cmacme.OrderSpec{ - DNSNames: []string{"example.com"}, - }, - }, - authz: &cmacme.ACMEAuthorization{ - Identifier: "example.com", - Challenges: []cmacme.ACMEChallenge{*acmeChallengeHTTP01}, - }, - expectedChallengeSpec: &cmacme.ChallengeSpec{ - Type: cmacme.ACMEChallengeTypeHTTP01, - DNSName: "example.com", - Token: acmeChallengeHTTP01.Token, - Key: "http01", - Solver: exampleComDNSNameSelectorSolver, - }, + } + for name, test := range tests { + t.Run(name, func(t *testing.T) { + cs, err := partialChallengeSpecForAuthorization(t.Context(), test.issuer, test.order, *test.authz) + if err != nil && !test.expectedError { + t.Errorf("expected to not get an error, but got: %v", err) + t.Fail() + } + if err == nil && test.expectedError { + t.Errorf("expected to get an error, but got none") + } + if !reflect.DeepEqual(cs, test.expectedChallengeSpec) { + t.Errorf("returned challenge spec was not as expected (-want +got):\n%s", cmp.Diff(test.expectedChallengeSpec, cs)) + } + }) + } +} + +func Test_ensureKeysForChallenges(t *testing.T) { + basicACMEClient := &acmecl.FakeACME{ + FakeHTTP01ChallengeResponse: func(token string) (string, error) { + switch token { + case "fooToken": + return "fooKeyHTTP01", nil + case "barToken": + return "barKeyHTTP01", nil + } + return "", fmt.Errorf("internal error: unexpected token value %s", token) + }, + FakeDNS01ChallengeRecord: func(token string) (string, error) { + switch token { + case "fooToken": + return "fooKeyDNS01", nil + case "barToken": + return "barKeyDNS01", nil + } + return "", fmt.Errorf("internal error: unexpected token value %s", token) }, - // identical to the test above, but the solvers are listed in reverse - // order to ensure that this behaviour isn't just incidental - "if one solver matches with dnsNames, and the other solver matches with labels, the dnsName solver should be chosen (solvers listed in reverse order)": { + } + fooChallenge := gen.Challenge("foo", gen.SetChallengeToken("fooToken")) + barChallenge := gen.Challenge("bar", gen.SetChallengeToken("barToken")) + tests := map[string]struct { + acmeClient acmecl.Interface + partialChallenges []*cmacme.Challenge + want []*cmacme.Challenge + wantErr bool + }{ + "happy path with some http-01 challenges": { acmeClient: basicACMEClient, - issuer: &v1.Issuer{ - Spec: v1.IssuerSpec{ - IssuerConfig: v1.IssuerConfig{ - ACME: &cmacme.ACMEIssuer{ - Solvers: []cmacme.ACMEChallengeSolver{ - { - Selector: &cmacme.CertificateDNSNameSelector{ - MatchLabels: map[string]string{ - "label": "exists", - }, - }, - HTTP01: &cmacme.ACMEChallengeSolverHTTP01{ - Ingress: &cmacme.ACMEChallengeSolverHTTP01Ingress{ - Name: "example-com-labels-selector-solver", - }, - }, - }, - exampleComDNSNameSelectorSolver, - }, - }, - }, - }, - }, - order: &cmacme.Order{ - ObjectMeta: metav1.ObjectMeta{ - Labels: map[string]string{ - "label": "exists", - }, - }, - Spec: cmacme.OrderSpec{ - DNSNames: []string{"example.com"}, - }, - }, - authz: &cmacme.ACMEAuthorization{ - Identifier: "example.com", - Challenges: []cmacme.ACMEChallenge{*acmeChallengeHTTP01}, - }, - expectedChallengeSpec: &cmacme.ChallengeSpec{ - Type: cmacme.ACMEChallengeTypeHTTP01, - DNSName: "example.com", - Token: acmeChallengeHTTP01.Token, - Key: "http01", - Solver: exampleComDNSNameSelectorSolver, - }, - }, - "if one solver matches with dnsNames, and the other solver matches with 2 labels, the dnsName solver should be chosen": { - acmeClient: basicACMEClient, - issuer: &v1.Issuer{ - Spec: v1.IssuerSpec{ - IssuerConfig: v1.IssuerConfig{ - ACME: &cmacme.ACMEIssuer{ - Solvers: []cmacme.ACMEChallengeSolver{ - exampleComDNSNameSelectorSolver, - { - Selector: &cmacme.CertificateDNSNameSelector{ - MatchLabels: map[string]string{ - "label": "exists", - "another": "label", - }, - }, - HTTP01: &cmacme.ACMEChallengeSolverHTTP01{ - Ingress: &cmacme.ACMEChallengeSolverHTTP01Ingress{ - Name: "example-com-labels-selector-solver", - }, - }, - }, - }, - }, - }, - }, - }, - order: &cmacme.Order{ - ObjectMeta: metav1.ObjectMeta{ - Labels: map[string]string{ - "label": "exists", - "another": "label", - }, - }, - Spec: cmacme.OrderSpec{ - DNSNames: []string{"example.com"}, - }, - }, - authz: &cmacme.ACMEAuthorization{ - Identifier: "example.com", - Challenges: []cmacme.ACMEChallenge{*acmeChallengeHTTP01}, - }, - expectedChallengeSpec: &cmacme.ChallengeSpec{ - Type: cmacme.ACMEChallengeTypeHTTP01, - DNSName: "example.com", - Token: acmeChallengeHTTP01.Token, - Key: "http01", - Solver: exampleComDNSNameSelectorSolver, - }, - }, - "should choose the solver with the most labels matching if multiple match": { - acmeClient: basicACMEClient, - issuer: &v1.Issuer{ - Spec: v1.IssuerSpec{ - IssuerConfig: v1.IssuerConfig{ - ACME: &cmacme.ACMEIssuer{ - Solvers: []cmacme.ACMEChallengeSolver{ - { - Selector: &cmacme.CertificateDNSNameSelector{ - MatchLabels: map[string]string{ - "label": "exists", - }, - }, - HTTP01: &cmacme.ACMEChallengeSolverHTTP01{ - Ingress: &cmacme.ACMEChallengeSolverHTTP01Ingress{ - Name: "example-com-labels-selector-solver", - }, - }, - }, - { - Selector: &cmacme.CertificateDNSNameSelector{ - MatchLabels: map[string]string{ - "label": "exists", - "another": "matches", - }, - }, - HTTP01: &cmacme.ACMEChallengeSolverHTTP01{ - Ingress: &cmacme.ACMEChallengeSolverHTTP01Ingress{ - Name: "example-com-multiple-labels-selector-solver", - }, - }, - }, - }, - }, - }, - }, - }, - order: &cmacme.Order{ - ObjectMeta: metav1.ObjectMeta{ - Labels: map[string]string{ - "label": "exists", - "another": "matches", - }, - }, - Spec: cmacme.OrderSpec{ - DNSNames: []string{"example.com"}, - }, - }, - authz: &cmacme.ACMEAuthorization{ - Identifier: "example.com", - Challenges: []cmacme.ACMEChallenge{*acmeChallengeHTTP01}, - }, - expectedChallengeSpec: &cmacme.ChallengeSpec{ - Type: cmacme.ACMEChallengeTypeHTTP01, - DNSName: "example.com", - Token: acmeChallengeHTTP01.Token, - Key: "http01", - Solver: cmacme.ACMEChallengeSolver{ - Selector: &cmacme.CertificateDNSNameSelector{ - MatchLabels: map[string]string{ - "label": "exists", - "another": "matches", - }, - }, - HTTP01: &cmacme.ACMEChallengeSolverHTTP01{ - Ingress: &cmacme.ACMEChallengeSolverHTTP01Ingress{ - Name: "example-com-multiple-labels-selector-solver", - }, - }, - }, - }, - }, - "should match wildcard dnsName solver if authorization has Wildcard=true": { - acmeClient: basicACMEClient, - issuer: &v1.Issuer{ - Spec: v1.IssuerSpec{ - IssuerConfig: v1.IssuerConfig{ - ACME: &cmacme.ACMEIssuer{ - Solvers: []cmacme.ACMEChallengeSolver{ - emptySelectorSolverDNS01, - { - Selector: &cmacme.CertificateDNSNameSelector{ - DNSNames: []string{"*.example.com"}, - }, - DNS01: &cmacme.ACMEChallengeSolverDNS01{ - Cloudflare: &cmacme.ACMEIssuerDNS01ProviderCloudflare{ - Email: "example-com-wc-dnsname-selector-solver", - }, - }, - }, - }, - }, - }, - }, - }, - order: &cmacme.Order{ - Spec: cmacme.OrderSpec{ - DNSNames: []string{"*.example.com"}, - }, - }, - authz: &cmacme.ACMEAuthorization{ - Identifier: "example.com", - Wildcard: pointer.BoolPtr(true), - Challenges: []cmacme.ACMEChallenge{*acmeChallengeDNS01}, - }, - expectedChallengeSpec: &cmacme.ChallengeSpec{ - Type: cmacme.ACMEChallengeTypeDNS01, - DNSName: "example.com", - Wildcard: true, - Token: acmeChallengeDNS01.Token, - Key: "dns01", - Solver: cmacme.ACMEChallengeSolver{ - Selector: &cmacme.CertificateDNSNameSelector{ - DNSNames: []string{"*.example.com"}, - }, - DNS01: &cmacme.ACMEChallengeSolverDNS01{ - Cloudflare: &cmacme.ACMEIssuerDNS01ProviderCloudflare{ - Email: "example-com-wc-dnsname-selector-solver", - }, - }, - }, - }, - }, - "dnsName selectors should take precedence over dnsZone selectors": { - acmeClient: basicACMEClient, - issuer: &v1.Issuer{ - Spec: v1.IssuerSpec{ - IssuerConfig: v1.IssuerConfig{ - ACME: &cmacme.ACMEIssuer{ - Solvers: []cmacme.ACMEChallengeSolver{ - exampleComDNSNameSelectorSolver, - { - Selector: &cmacme.CertificateDNSNameSelector{ - DNSZones: []string{"com"}, - }, - HTTP01: &cmacme.ACMEChallengeSolverHTTP01{ - Ingress: &cmacme.ACMEChallengeSolverHTTP01Ingress{ - Name: "com-dnszone-selector-solver", - }, - }, - }, - }, - }, - }, - }, - }, - order: &cmacme.Order{ - Spec: cmacme.OrderSpec{ - DNSNames: []string{"example.com"}, - }, - }, - authz: &cmacme.ACMEAuthorization{ - Identifier: "example.com", - Challenges: []cmacme.ACMEChallenge{*acmeChallengeHTTP01}, - }, - expectedChallengeSpec: &cmacme.ChallengeSpec{ - Type: cmacme.ACMEChallengeTypeHTTP01, - DNSName: "example.com", - Token: acmeChallengeHTTP01.Token, - Key: "http01", - Solver: exampleComDNSNameSelectorSolver, - }, - }, - "dnsName selectors should take precedence over dnsZone selectors (reversed order)": { - acmeClient: basicACMEClient, - issuer: &v1.Issuer{ - Spec: v1.IssuerSpec{ - IssuerConfig: v1.IssuerConfig{ - ACME: &cmacme.ACMEIssuer{ - Solvers: []cmacme.ACMEChallengeSolver{ - { - Selector: &cmacme.CertificateDNSNameSelector{ - DNSZones: []string{"com"}, - }, - DNS01: &cmacme.ACMEChallengeSolverDNS01{ - Cloudflare: &cmacme.ACMEIssuerDNS01ProviderCloudflare{ - Email: "com-dnszone-selector-solver", - }, - }, - }, - exampleComDNSNameSelectorSolver, - }, - }, - }, - }, - }, - order: &cmacme.Order{ - Spec: cmacme.OrderSpec{ - DNSNames: []string{"example.com"}, - }, - }, - authz: &cmacme.ACMEAuthorization{ - Identifier: "example.com", - Challenges: []cmacme.ACMEChallenge{*acmeChallengeHTTP01}, - }, - expectedChallengeSpec: &cmacme.ChallengeSpec{ - Type: cmacme.ACMEChallengeTypeHTTP01, - DNSName: "example.com", - Token: acmeChallengeHTTP01.Token, - Key: "http01", - Solver: exampleComDNSNameSelectorSolver, - }, - }, - "should allow matching with dnsZones": { - acmeClient: basicACMEClient, - issuer: &v1.Issuer{ - Spec: v1.IssuerSpec{ - IssuerConfig: v1.IssuerConfig{ - ACME: &cmacme.ACMEIssuer{ - Solvers: []cmacme.ACMEChallengeSolver{ - emptySelectorSolverDNS01, - { - Selector: &cmacme.CertificateDNSNameSelector{ - DNSZones: []string{"example.com"}, - }, - DNS01: &cmacme.ACMEChallengeSolverDNS01{ - Cloudflare: &cmacme.ACMEIssuerDNS01ProviderCloudflare{ - Email: "example-com-dnszone-selector-solver", - }, - }, - }, - }, - }, - }, - }, - }, - order: &cmacme.Order{ - Spec: cmacme.OrderSpec{ - DNSNames: []string{"www.example.com"}, - }, - }, - authz: &cmacme.ACMEAuthorization{ - Identifier: "www.example.com", - Wildcard: pointer.BoolPtr(true), - Challenges: []cmacme.ACMEChallenge{*acmeChallengeDNS01}, - }, - expectedChallengeSpec: &cmacme.ChallengeSpec{ - Type: cmacme.ACMEChallengeTypeDNS01, - DNSName: "www.example.com", - Wildcard: true, - Token: acmeChallengeDNS01.Token, - Key: "dns01", - Solver: cmacme.ACMEChallengeSolver{ - Selector: &cmacme.CertificateDNSNameSelector{ - DNSZones: []string{"example.com"}, - }, - DNS01: &cmacme.ACMEChallengeSolverDNS01{ - Cloudflare: &cmacme.ACMEIssuerDNS01ProviderCloudflare{ - Email: "example-com-dnszone-selector-solver", - }, - }, - }, - }, - }, - "most specific dnsZone should be selected if multiple match": { - acmeClient: basicACMEClient, - issuer: &v1.Issuer{ - Spec: v1.IssuerSpec{ - IssuerConfig: v1.IssuerConfig{ - ACME: &cmacme.ACMEIssuer{ - Solvers: []cmacme.ACMEChallengeSolver{ - { - Selector: &cmacme.CertificateDNSNameSelector{ - DNSZones: []string{"example.com"}, - }, - DNS01: &cmacme.ACMEChallengeSolverDNS01{ - Cloudflare: &cmacme.ACMEIssuerDNS01ProviderCloudflare{ - Email: "example-com-dnszone-selector-solver", - }, - }, - }, - { - Selector: &cmacme.CertificateDNSNameSelector{ - DNSZones: []string{"prod.example.com"}, - }, - DNS01: &cmacme.ACMEChallengeSolverDNS01{ - Cloudflare: &cmacme.ACMEIssuerDNS01ProviderCloudflare{ - Email: "prod-example-com-dnszone-selector-solver", - }, - }, - }, - }, - }, - }, - }, - }, - order: &cmacme.Order{ - Spec: cmacme.OrderSpec{ - DNSNames: []string{"www.prod.example.com"}, - }, - }, - authz: &cmacme.ACMEAuthorization{ - Identifier: "www.prod.example.com", - Wildcard: pointer.BoolPtr(true), - Challenges: []cmacme.ACMEChallenge{*acmeChallengeDNS01}, - }, - expectedChallengeSpec: &cmacme.ChallengeSpec{ - Type: cmacme.ACMEChallengeTypeDNS01, - DNSName: "www.prod.example.com", - Wildcard: true, - Token: acmeChallengeDNS01.Token, - Key: "dns01", - Solver: cmacme.ACMEChallengeSolver{ - Selector: &cmacme.CertificateDNSNameSelector{ - DNSZones: []string{"prod.example.com"}, - }, - DNS01: &cmacme.ACMEChallengeSolverDNS01{ - Cloudflare: &cmacme.ACMEIssuerDNS01ProviderCloudflare{ - Email: "prod-example-com-dnszone-selector-solver", - }, - }, - }, - }, - }, - "most specific dnsZone should be selected if multiple match (reversed)": { - acmeClient: basicACMEClient, - issuer: &v1.Issuer{ - Spec: v1.IssuerSpec{ - IssuerConfig: v1.IssuerConfig{ - ACME: &cmacme.ACMEIssuer{ - Solvers: []cmacme.ACMEChallengeSolver{ - { - Selector: &cmacme.CertificateDNSNameSelector{ - DNSZones: []string{"prod.example.com"}, - }, - DNS01: &cmacme.ACMEChallengeSolverDNS01{ - Cloudflare: &cmacme.ACMEIssuerDNS01ProviderCloudflare{ - Email: "prod-example-com-dnszone-selector-solver", - }, - }, - }, - { - Selector: &cmacme.CertificateDNSNameSelector{ - DNSZones: []string{"example.com"}, - }, - DNS01: &cmacme.ACMEChallengeSolverDNS01{ - Cloudflare: &cmacme.ACMEIssuerDNS01ProviderCloudflare{ - Email: "example-com-dnszone-selector-solver", - }, - }, - }, - }, - }, - }, - }, - }, - order: &cmacme.Order{ - Spec: cmacme.OrderSpec{ - DNSNames: []string{"www.prod.example.com"}, - }, - }, - authz: &cmacme.ACMEAuthorization{ - Identifier: "www.prod.example.com", - Wildcard: pointer.BoolPtr(true), - Challenges: []cmacme.ACMEChallenge{*acmeChallengeDNS01}, - }, - expectedChallengeSpec: &cmacme.ChallengeSpec{ - Type: cmacme.ACMEChallengeTypeDNS01, - DNSName: "www.prod.example.com", - Wildcard: true, - Token: acmeChallengeDNS01.Token, - Key: "dns01", - Solver: cmacme.ACMEChallengeSolver{ - Selector: &cmacme.CertificateDNSNameSelector{ - DNSZones: []string{"prod.example.com"}, - }, - DNS01: &cmacme.ACMEChallengeSolverDNS01{ - Cloudflare: &cmacme.ACMEIssuerDNS01ProviderCloudflare{ - Email: "prod-example-com-dnszone-selector-solver", - }, - }, - }, - }, - }, - "if two solvers specify the same dnsZone, the one with the most labels should be chosen": { - acmeClient: basicACMEClient, - issuer: &v1.Issuer{ - Spec: v1.IssuerSpec{ - IssuerConfig: v1.IssuerConfig{ - ACME: &cmacme.ACMEIssuer{ - Solvers: []cmacme.ACMEChallengeSolver{ - { - Selector: &cmacme.CertificateDNSNameSelector{ - DNSZones: []string{"example.com"}, - }, - HTTP01: &cmacme.ACMEChallengeSolverHTTP01{ - Ingress: &cmacme.ACMEChallengeSolverHTTP01Ingress{ - Name: "example-com-dnszone-selector-solver", - }, - }, - }, - { - Selector: &cmacme.CertificateDNSNameSelector{ - MatchLabels: map[string]string{ - "label": "exists", - }, - DNSZones: []string{"example.com"}, - }, - HTTP01: &cmacme.ACMEChallengeSolverHTTP01{ - Ingress: &cmacme.ACMEChallengeSolverHTTP01Ingress{ - Name: "example-com-dnszone-labels-selector-solver", - }, - }, - }, - }, - }, - }, - }, - }, - order: &cmacme.Order{ - ObjectMeta: metav1.ObjectMeta{ - Labels: map[string]string{ - "label": "exists", - }, - }, - Spec: cmacme.OrderSpec{ - DNSNames: []string{"www.example.com"}, - }, - }, - authz: &cmacme.ACMEAuthorization{ - Identifier: "www.example.com", - Challenges: []cmacme.ACMEChallenge{*acmeChallengeHTTP01}, - }, - expectedChallengeSpec: &cmacme.ChallengeSpec{ - Type: cmacme.ACMEChallengeTypeHTTP01, - DNSName: "www.example.com", - Token: acmeChallengeHTTP01.Token, - Key: "http01", - Solver: cmacme.ACMEChallengeSolver{ - Selector: &cmacme.CertificateDNSNameSelector{ - MatchLabels: map[string]string{ - "label": "exists", - }, - DNSZones: []string{"example.com"}, - }, - HTTP01: &cmacme.ACMEChallengeSolverHTTP01{ - Ingress: &cmacme.ACMEChallengeSolverHTTP01Ingress{ - Name: "example-com-dnszone-labels-selector-solver", - }, - }, - }, - }, - }, - "if both solvers match dnsNames, and one also matches dnsZones, choose the one that matches dnsZones": { + partialChallenges: []*cmacme.Challenge{ + gen.ChallengeFrom(fooChallenge, gen.SetChallengeType(cmacme.ACMEChallengeTypeHTTP01)), + gen.ChallengeFrom(barChallenge, gen.SetChallengeType(cmacme.ACMEChallengeTypeHTTP01))}, + want: []*cmacme.Challenge{ + gen.ChallengeFrom(fooChallenge, gen.SetChallengeType(cmacme.ACMEChallengeTypeHTTP01), + gen.SetChallengeKey("fooKeyHTTP01")), + gen.ChallengeFrom(barChallenge, gen.SetChallengeType(cmacme.ACMEChallengeTypeHTTP01), + gen.SetChallengeKey("barKeyHTTP01"))}, + }, + "happy path with some dns-01 challenges": { acmeClient: basicACMEClient, - issuer: &v1.Issuer{ - Spec: v1.IssuerSpec{ - IssuerConfig: v1.IssuerConfig{ - ACME: &cmacme.ACMEIssuer{ - Solvers: []cmacme.ACMEChallengeSolver{ - { - Selector: &cmacme.CertificateDNSNameSelector{ - DNSNames: []string{"www.example.com"}, - }, - HTTP01: &cmacme.ACMEChallengeSolverHTTP01{ - Ingress: &cmacme.ACMEChallengeSolverHTTP01Ingress{ - Name: "example-com-dnsname-selector-solver", - }, - }, - }, - { - Selector: &cmacme.CertificateDNSNameSelector{ - DNSZones: []string{"example.com"}, - DNSNames: []string{"www.example.com"}, - }, - HTTP01: &cmacme.ACMEChallengeSolverHTTP01{ - Ingress: &cmacme.ACMEChallengeSolverHTTP01Ingress{ - Name: "example-com-dnsname-dnszone-selector-solver", - }, - }, - }, - }, - }, - }, - }, - }, - order: &cmacme.Order{ - Spec: cmacme.OrderSpec{ - DNSNames: []string{"www.example.com"}, - }, - }, - authz: &cmacme.ACMEAuthorization{ - Identifier: "www.example.com", - Challenges: []cmacme.ACMEChallenge{*acmeChallengeHTTP01}, - }, - expectedChallengeSpec: &cmacme.ChallengeSpec{ - Type: cmacme.ACMEChallengeTypeHTTP01, - DNSName: "www.example.com", - Token: acmeChallengeHTTP01.Token, - Key: "http01", - Solver: cmacme.ACMEChallengeSolver{ - Selector: &cmacme.CertificateDNSNameSelector{ - DNSZones: []string{"example.com"}, - DNSNames: []string{"www.example.com"}, - }, - HTTP01: &cmacme.ACMEChallengeSolverHTTP01{ - Ingress: &cmacme.ACMEChallengeSolverHTTP01Ingress{ - Name: "example-com-dnsname-dnszone-selector-solver", - }, - }, - }, - }, - }, - "if both solvers match dnsNames, and one also matches dnsZones, choose the one that matches dnsZones (reversed)": { - acmeClient: basicACMEClient, - issuer: &v1.Issuer{ - Spec: v1.IssuerSpec{ - IssuerConfig: v1.IssuerConfig{ - ACME: &cmacme.ACMEIssuer{ - Solvers: []cmacme.ACMEChallengeSolver{ - { - Selector: &cmacme.CertificateDNSNameSelector{ - DNSZones: []string{"example.com"}, - DNSNames: []string{"www.example.com"}, - }, - HTTP01: &cmacme.ACMEChallengeSolverHTTP01{ - Ingress: &cmacme.ACMEChallengeSolverHTTP01Ingress{ - Name: "example-com-dnsname-dnszone-selector-solver", - }, - }, - }, - { - Selector: &cmacme.CertificateDNSNameSelector{ - DNSNames: []string{"www.example.com"}, - }, - HTTP01: &cmacme.ACMEChallengeSolverHTTP01{ - Ingress: &cmacme.ACMEChallengeSolverHTTP01Ingress{ - Name: "example-com-dnsname-selector-solver", - }, - }, - }, - }, - }, - }, - }, - }, - order: &cmacme.Order{ - Spec: cmacme.OrderSpec{ - DNSNames: []string{"www.example.com"}, - }, - }, - authz: &cmacme.ACMEAuthorization{ - Identifier: "www.example.com", - Challenges: []cmacme.ACMEChallenge{*acmeChallengeHTTP01}, - }, - expectedChallengeSpec: &cmacme.ChallengeSpec{ - Type: cmacme.ACMEChallengeTypeHTTP01, - DNSName: "www.example.com", - Token: acmeChallengeHTTP01.Token, - Key: "http01", - Solver: cmacme.ACMEChallengeSolver{ - Selector: &cmacme.CertificateDNSNameSelector{ - DNSZones: []string{"example.com"}, - DNSNames: []string{"www.example.com"}, - }, - HTTP01: &cmacme.ACMEChallengeSolverHTTP01{ - Ingress: &cmacme.ACMEChallengeSolverHTTP01Ingress{ - Name: "example-com-dnsname-dnszone-selector-solver", - }, - }, - }, - }, - }, - "uses correct solver when selector explicitly names dnsName (reversed)": { - acmeClient: basicACMEClient, - issuer: &v1.Issuer{ - Spec: v1.IssuerSpec{ - IssuerConfig: v1.IssuerConfig{ - ACME: &cmacme.ACMEIssuer{ - Solvers: []cmacme.ACMEChallengeSolver{ - exampleComDNSNameSelectorSolver, - emptySelectorSolverHTTP01, - }, - }, - }, - }, - }, - order: &cmacme.Order{ - Spec: cmacme.OrderSpec{ - DNSNames: []string{"example.com"}, - }, - }, - authz: &cmacme.ACMEAuthorization{ - Identifier: "example.com", - Challenges: []cmacme.ACMEChallenge{*acmeChallengeHTTP01}, - }, - expectedChallengeSpec: &cmacme.ChallengeSpec{ - Type: cmacme.ACMEChallengeTypeHTTP01, - DNSName: "example.com", - Token: acmeChallengeHTTP01.Token, - Key: "http01", - Solver: exampleComDNSNameSelectorSolver, - }, + partialChallenges: []*cmacme.Challenge{ + gen.ChallengeFrom(fooChallenge, gen.SetChallengeType(cmacme.ACMEChallengeTypeDNS01)), + gen.ChallengeFrom(barChallenge, gen.SetChallengeType(cmacme.ACMEChallengeTypeDNS01))}, + want: []*cmacme.Challenge{ + gen.ChallengeFrom(fooChallenge, gen.SetChallengeType(cmacme.ACMEChallengeTypeDNS01), + gen.SetChallengeKey("fooKeyDNS01")), + gen.ChallengeFrom(barChallenge, gen.SetChallengeType(cmacme.ACMEChallengeTypeDNS01), + gen.SetChallengeKey("barKeyDNS01"))}, + }, + "unhappy path with an unknown challenge type": { + acmeClient: basicACMEClient, + partialChallenges: []*cmacme.Challenge{gen.ChallengeFrom(fooChallenge, gen.SetChallengeType(cmacme.ACMEChallengeType("foo")))}, + wantErr: true, }, } - for name, test := range tests { + for name, scenario := range tests { t.Run(name, func(t *testing.T) { - ctx := context.Background() - cs, err := challengeSpecForAuthorization(ctx, test.acmeClient, test.issuer, test.order, *test.authz) - if err != nil && !test.expectedError { - t.Errorf("expected to not get an error, but got: %v", err) - t.Fail() + got, err := ensureKeysForChallenges(scenario.acmeClient, scenario.partialChallenges) + if (err != nil) != scenario.wantErr { + t.Errorf("ensureKeysForChallenges() error = %v, wantErr %v", err, scenario.wantErr) + return } - if err == nil && test.expectedError { - t.Errorf("expected to get an error, but got none") - } - if !reflect.DeepEqual(cs, test.expectedChallengeSpec) { - t.Errorf("returned challenge spec was not as expected: %v", pretty.Diff(test.expectedChallengeSpec, cs)) + if !reflect.DeepEqual(got, scenario.want) { + t.Errorf("ensureKeysForChallenges() = %v, want %v", got, scenario.want) } }) } diff --git a/pkg/controller/builder.go b/pkg/controller/builder.go index 0aed8c6888f..a9f8f227593 100644 --- a/pkg/controller/builder.go +++ b/pkg/controller/builder.go @@ -20,8 +20,6 @@ import ( "context" "fmt" "time" - - logf "github.com/cert-manager/cert-manager/pkg/logs" ) // Builder is used to build controllers that implement the queuingController @@ -37,11 +35,6 @@ type Builder struct { // the actual controller implementation impl queueingController - // runFirstFuncs are a list of functions that will be called immediately - // after the controller has been initialised, once. They are run in queue, sequentially, - // and block runDurationFuncs until complete. - runFirstFuncs []runFunc - // runDurationFuncs are a list of functions that will be called every // 'duration' runDurationFuncs []runDurationFunc @@ -71,22 +64,12 @@ func (b *Builder) With(function func(context.Context), duration time.Duration) * return b } -// First will register a function that will be called once, after the -// controller has been initialised. They are queued, run sequentially, and -// block "With" runDurationFuncs from running until all are complete. -func (b *Builder) First(function func(context.Context)) *Builder { - b.runFirstFuncs = append(b.runFirstFuncs, function) - return b -} - func (b *Builder) Complete() (Interface, error) { controllerctx, err := b.contextFactory.Build(b.name) if err != nil { return nil, err } - ctx := logf.NewContext(controllerctx.RootContext, logf.Log, b.name) - if b.impl == nil { return nil, fmt.Errorf("controller implementation must be non-nil") } @@ -95,5 +78,5 @@ func (b *Builder) Complete() (Interface, error) { return nil, fmt.Errorf("error registering controller: %v", err) } - return NewController(ctx, b.name, controllerctx.Metrics, b.impl.ProcessItem, mustSync, b.runDurationFuncs, queue), nil + return NewController(b.name, controllerctx.Metrics, b.impl.ProcessItem, mustSync, b.runDurationFuncs, queue), nil } diff --git a/pkg/controller/cainjector/indexers.go b/pkg/controller/cainjector/indexers.go index 0d872be2a93..d2a29cb929b 100644 --- a/pkg/controller/cainjector/indexers.go +++ b/pkg/controller/cainjector/indexers.go @@ -20,28 +20,50 @@ import ( "context" "github.com/go-logr/logr" - corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/handler" cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" ) -// setup for indexers used to trigger reconciliation on injected CA data. +const ( + // injectFromPath is the index key used to look up the value of inject-ca-from on targeted objects + injectFromPath = ".metadata.annotations.inject-ca-from" -// certificateToInjectableFunc converts a given certificate to the reconcile requests for the corresponding injectables -// (webhooks, api services, etc) that reference it. -type certificateToInjectableFunc func(log logr.Logger, cl client.Reader, certName types.NamespacedName) []ctrl.Request + // injectFromSecretPath is the index key used to look up the value of + // inject-ca-from-secret on targeted objects + injectFromSecretPath = ".metadata.annotations.inject-ca-from-secret" +) -// buildCertToInjectableFunc creates a certificateToInjectableFunc that maps from certificates to the given type of injectable. -func buildCertToInjectableFunc(listTyp runtime.Object, resourceName string) certificateToInjectableFunc { - return func(log logr.Logger, cl client.Reader, certName types.NamespacedName) []ctrl.Request { - log = log.WithValues("type", resourceName) - objs := listTyp.DeepCopyObject().(client.ObjectList) - if err := cl.List(context.Background(), objs, client.MatchingFields{injectFromPath: certName.String()}); err != nil { +// certFromSecretToInjectableMapFuncBuilder returns a handler.MapFunc that, for +// a Secret change, ensures that if this Secret is a Certificate Secret of +// Certificate that is configured as a CA source for an injectable via +// inject-ca-from annotation, a reconcile loop will be triggered for this +// injectable +func certFromSecretToInjectableMapFuncBuilder(cl client.Reader, log logr.Logger, config setup) handler.MapFunc { + return func(ctx context.Context, obj client.Object) []ctrl.Request { + secretName := types.NamespacedName{Name: obj.GetName(), Namespace: obj.GetNamespace()} + certName := owningCertForSecret(obj.(*metav1.PartialObjectMetadata)) + if certName == nil { + return nil + } + log := log.WithValues("type", config.resourceName, "secret", secretName, "certificate", *certName) + + // confirm that a service owns this cert + var cert cmapi.Certificate + if err := cl.Get(ctx, *certName, &cert); err != nil { + // TODO(directxman12): check for not found error? + log.Error(err, "unable to fetch certificate that owns the secret") + return nil + } + + objs := config.listType.DeepCopyObject().(client.ObjectList) + if err := cl.List(ctx, objs, client.MatchingFields{injectFromPath: certName.String()}); err != nil { log.Error(err, "unable to fetch injectables associated with certificate") return nil } @@ -68,83 +90,54 @@ func buildCertToInjectableFunc(listTyp runtime.Object, resourceName string) cert } } -// secretForCertificateMapper is a Mapper that converts secrets up to injectables, through certificates. -type secretForCertificateMapper struct { - Client client.Reader - log logr.Logger - certificateToInjectable certificateToInjectableFunc -} - -func (m *secretForCertificateMapper) Map(obj client.Object) []ctrl.Request { - // grab the certificate, if it exists - certName := OwningCertForSecret(obj.(*corev1.Secret)) - if certName == nil { - return nil - } - - secretName := types.NamespacedName{Name: obj.GetName(), Namespace: obj.GetNamespace()} - log := m.log.WithValues("secret", secretName, "certificate", *certName) - - var cert cmapi.Certificate - // confirm that a service owns this cert - if err := m.Client.Get(context.Background(), *certName, &cert); err != nil { - // TODO(directxman12): check for not found error? - log.Error(err, "unable to fetch certificate that owns the secret") - return nil - } - - return m.certificateToInjectable(log, m.Client, *certName) -} - -// certMapper is a mapper that converts Certificates up to injectables -type certMapper struct { - Client client.Reader - log logr.Logger - toInjectable certificateToInjectableFunc -} - -func (m *certMapper) Map(obj client.Object) []ctrl.Request { - certName := types.NamespacedName{Name: obj.GetName(), Namespace: obj.GetNamespace()} - log := m.log.WithValues("certificate", certName) - return m.toInjectable(log, m.Client, certName) -} - -var ( - // injectFromPath is the index key used to look up the value of inject-ca-from on targeted objects - injectFromPath = ".metadata.annotations.inject-ca-from" -) +// certToInjectableMapFuncBuilder returns a handler.MapFunc that, for +// a Certificate change, ensures that if this Certificate that is configured as +// a CA source for an injectable via inject-ca-from annotation, a reconcile loop +// will be triggered for this injectable +func certToInjectableMapFuncBuilder(cl client.Reader, log logr.Logger, config setup) handler.MapFunc { + return func(ctx context.Context, obj client.Object) []ctrl.Request { + certName := types.NamespacedName{Namespace: obj.GetNamespace(), Name: obj.GetName()} + log := log.WithValues("type", config.resourceName, "certificate", certName) + objs := config.listType.DeepCopyObject().(client.ObjectList) + if err := cl.List(ctx, objs, client.MatchingFields{injectFromPath: certName.String()}); err != nil { + log.Error(err, "unable to fetch injectables associated with certificate") + return nil + } -// injectableCAFromIndexer is an IndexerFunc indexing on certificates -// referenced by injectables. -func injectableCAFromIndexer(rawObj client.Object) []string { - metaInfo, err := meta.Accessor(rawObj) - if err != nil { - return nil - } + var reqs []ctrl.Request + if err := meta.EachListItem(objs, func(obj runtime.Object) error { + metaInfo, err := meta.Accessor(obj) + if err != nil { + log.Error(err, "unable to get metadata from list item") + // continue on error + return nil + } + reqs = append(reqs, ctrl.Request{NamespacedName: types.NamespacedName{ + Name: metaInfo.GetName(), + Namespace: metaInfo.GetNamespace(), + }}) + return nil + }); err != nil { + log.Error(err, "unable get items from list") + return nil + } - // skip invalid certificate names - certNameRaw := metaInfo.GetAnnotations()[cmapi.WantInjectAnnotation] - if certNameRaw == "" { - return nil - } - certName := splitNamespacedName(certNameRaw) - if certName.Namespace == "" { - return nil + return reqs } - - return []string{certNameRaw} } -// secretToInjectableFunc converts a given certificate to the reconcile requests for the corresponding injectables -// (webhooks, api services, etc) that reference it. -type secretToInjectableFunc func(log logr.Logger, cl client.Reader, certName types.NamespacedName) []ctrl.Request - -// buildSecretToInjectableFunc creates a certificateToInjectableFunc that maps from certificates to the given type of injectable. -func buildSecretToInjectableFunc(listTyp runtime.Object, resourceName string) secretToInjectableFunc { - return func(log logr.Logger, cl client.Reader, secretName types.NamespacedName) []ctrl.Request { - log = log.WithValues("type", resourceName) - objs := listTyp.DeepCopyObject().(client.ObjectList) - if err := cl.List(context.Background(), objs, client.MatchingFields{injectFromSecretPath: secretName.String()}); err != nil { +// secretForInjectableMapFuncBuilder returns a handler.MapFunc that, for a +// config for particular injectable type (i.e CRD, APIService) and a Secret, +// returns all injectables that have the inject-ca-from-secret annotation with the +// given secret name. This will be used in an event handler to ensure that +// changes to a Secret triggers a reconcile loop for the relevant injectable. +func secretForInjectableMapFuncBuilder(cl client.Reader, log logr.Logger, config setup) handler.MapFunc { + return func(ctx context.Context, obj client.Object) []ctrl.Request { + secretName := types.NamespacedName{Namespace: obj.GetNamespace(), Name: obj.GetName()} + log := log.WithValues("type", config.resourceName, "secret", secretName) + objs := config.listType.DeepCopyObject().(client.ObjectList) + // TODO: ensure that this is cache lister, not a direct client + if err := cl.List(ctx, objs, client.MatchingFields{injectFromSecretPath: secretName.String()}); err != nil { log.Error(err, "unable to fetch injectables associated with secret") return nil } @@ -171,25 +164,26 @@ func buildSecretToInjectableFunc(listTyp runtime.Object, resourceName string) se } } -// secretForInjectableMapper is a Mapper that converts secrets to injectables -// via the 'inject-ca-from-secret' annotation -type secretForInjectableMapper struct { - Client client.Reader - log logr.Logger - secretToInjectable secretToInjectableFunc -} +// injectableCAFromIndexer is an IndexerFunc indexing on certificates +// referenced by injectables. +func injectableCAFromIndexer(rawObj client.Object) []string { + metaInfo, err := meta.Accessor(rawObj) + if err != nil { + return nil + } -func (m *secretForInjectableMapper) Map(obj client.Object) []ctrl.Request { - secretName := types.NamespacedName{Namespace: obj.GetNamespace(), Name: obj.GetName()} - log := m.log.WithValues("secret", secretName) - return m.secretToInjectable(log, m.Client, secretName) -} + // skip invalid certificate names + certNameRaw := metaInfo.GetAnnotations()[cmapi.WantInjectAnnotation] + if certNameRaw == "" { + return nil + } + certName := splitNamespacedName(certNameRaw) + if certName.Namespace == "" { + return nil + } -var ( - // injectFromSecretPath is the index key used to look up the value of - // inject-ca-from-secret on targeted objects - injectFromSecretPath = ".metadata.annotations.inject-ca-from-secret" -) + return []string{certNameRaw} +} // injectableCAFromSecretIndexer is an IndexerFunc indexing on secrets // referenced by injectables. @@ -211,3 +205,20 @@ func injectableCAFromSecretIndexer(rawObj client.Object) []string { return []string{secretNameRaw} } + +// hasInjectableAnnotation returns predicates that determine whether an object is a +// cainjector injectable by looking at whether it has one of the three +// annotations used to mark injectables. +func hasInjectableAnnotation(o client.Object) bool { + annots := o.GetAnnotations() + if _, ok := annots[cmapi.WantInjectAPIServerCAAnnotation]; ok { + return true + } + if _, ok := annots[cmapi.WantInjectAnnotation]; ok { + return true + } + if _, ok := annots[cmapi.WantInjectFromSecretAnnotation]; ok { + return true + } + return false +} diff --git a/pkg/controller/cainjector/injectables.go b/pkg/controller/cainjector/injectables.go new file mode 100644 index 00000000000..9e7fb7eac69 --- /dev/null +++ b/pkg/controller/cainjector/injectables.go @@ -0,0 +1,338 @@ +/* +Copyright 2020 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. +*/ + +package cainjector + +import ( + "encoding/json" + + admissionreg "k8s.io/api/admissionregistration/v1" + apiext "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" + "k8s.io/apimachinery/pkg/types" + applyadmissionreg "k8s.io/client-go/applyconfigurations/admissionregistration/v1" + applymetav1 "k8s.io/client-go/applyconfigurations/meta/v1" + apireg "k8s.io/kube-aggregator/pkg/apis/apiregistration/v1" + "sigs.k8s.io/controller-runtime/pkg/client" + + cainjectorbundle "github.com/cert-manager/cert-manager/internal/cainjector/bundle" + "github.com/cert-manager/cert-manager/internal/cainjector/feature" + utilfeature "github.com/cert-manager/cert-manager/pkg/util/feature" +) + +// This file contains logic for dealing with injectables, such as injecting CA +// data to an instance of an injectable. + +// NewInjectableTarget knows how to create InjectTarget for a particular type of +// injectable. +type NewInjectableTarget func() InjectTarget + +var _ NewInjectableTarget = newMutatingWebhookInjectable + +func newMutatingWebhookInjectable() InjectTarget { + return &mutatingWebhookTarget{} +} + +var _ NewInjectableTarget = newValidatingWebhookInjectable + +func newValidatingWebhookInjectable() InjectTarget { + return &validatingWebhookTarget{} +} + +var _ NewInjectableTarget = newAPIServiceInjectable + +func newAPIServiceInjectable() InjectTarget { + return &apiServiceTarget{} +} + +var _ NewInjectableTarget = newCRDConversionInjectable + +func newCRDConversionInjectable() InjectTarget { + return &crdConversionTarget{} +} + +// InjectTarget knows how to set CA data to a particular instance of injectable, +// for example an instance of ValidatingWebhookConfiguration. +type InjectTarget interface { + // AsObject returns this injectable as an object. + // It should be a pointer suitable for mutation. + AsObject() client.Object + + // AsApplyObject returns this injectable as an object that only contains + // fields which are managed by the cainjector (CA Data) and immutable fields + // that must be present in Apply calls; intended for use for Apply Patch + // calls. + AsApplyObject() (client.Object, client.Patch) + + // SetCA sets the CA of this target to the given certificate data (in the standard + // PEM format used across Kubernetes). In cases where multiple CA fields exist per + // target (like admission webhook configs), all CAs are set to the given value. + SetCA(data []byte) +} + +type ssaPatch struct { + patch []byte + err error +} + +func newSSAPatch(patch interface{}) *ssaPatch { + jsonPatch, err := json.Marshal(patch) + return &ssaPatch{patch: jsonPatch, err: err} +} + +func (p *ssaPatch) Type() types.PatchType { + return types.ApplyPatchType +} + +func (p *ssaPatch) Data(obj client.Object) ([]byte, error) { + return p.patch, nil +} + +var _ client.Patch = &ssaPatch{} + +// mutatingWebhookTarget knows how to set CA data for all the webhooks +// in a mutatingWebhookConfiguration. +type mutatingWebhookTarget struct { + obj admissionreg.MutatingWebhookConfiguration +} + +func (t *mutatingWebhookTarget) AsObject() client.Object { + return &t.obj +} + +func (t *mutatingWebhookTarget) SetCA(data []byte) { + for ind := range t.obj.Webhooks { + if utilfeature.DefaultFeatureGate.Enabled(feature.CAInjectorMerging) { + // If we for any reason cannot merge the certificate in, we replace it with + // the new certificate. + // + // This mirrors the old behavior of this function so is a reasonable + // fallback + bundle, err := cainjectorbundle.AppendCertificatesToBundle(t.obj.Webhooks[ind].ClientConfig.CABundle, data) + if err != nil { + bundle = data + } + + t.obj.Webhooks[ind].ClientConfig.CABundle = bundle + } else { + t.obj.Webhooks[ind].ClientConfig.CABundle = data + } + } +} + +func (t *mutatingWebhookTarget) AsApplyObject() (client.Object, client.Patch) { + patch := applyadmissionreg.MutatingWebhookConfiguration(t.obj.Name) + + for i := range t.obj.Webhooks { + patch = patch.WithWebhooks( + applyadmissionreg. + MutatingWebhook(). + WithName(t.obj.Webhooks[i].Name). // Name is used as slice key. + WithClientConfig( + &applyadmissionreg.WebhookClientConfigApplyConfiguration{ + CABundle: t.obj.Webhooks[i].ClientConfig.CABundle, + }, + ), + ) + } + + return &t.obj, newSSAPatch(patch) +} + +// validatingWebhookTarget knows how to set CA data for all the webhooks +// in a validatingWebhookConfiguration. +type validatingWebhookTarget struct { + obj admissionreg.ValidatingWebhookConfiguration +} + +func (t *validatingWebhookTarget) AsObject() client.Object { + return &t.obj +} + +func (t *validatingWebhookTarget) SetCA(data []byte) { + for ind := range t.obj.Webhooks { + if utilfeature.DefaultFeatureGate.Enabled(feature.CAInjectorMerging) { + // If we for any reason cannot merge the certificate in, we replace it with + // the new certificate. + // + // This mirrors the old behavior of this function so is a reasonable + // fallback + bundle, err := cainjectorbundle.AppendCertificatesToBundle(t.obj.Webhooks[ind].ClientConfig.CABundle, data) + if err != nil { + bundle = data + } + + t.obj.Webhooks[ind].ClientConfig.CABundle = bundle + } else { + t.obj.Webhooks[ind].ClientConfig.CABundle = data + } + } +} + +func (t *validatingWebhookTarget) AsApplyObject() (client.Object, client.Patch) { + patch := applyadmissionreg.ValidatingWebhookConfiguration(t.obj.Name) + + for i := range t.obj.Webhooks { + patch = patch.WithWebhooks( + applyadmissionreg. + ValidatingWebhook(). + WithName(t.obj.Webhooks[i].Name). // Name is used as slice key. + WithClientConfig( + &applyadmissionreg.WebhookClientConfigApplyConfiguration{ + CABundle: t.obj.Webhooks[i].ClientConfig.CABundle, + }, + ), + ) + } + + return &t.obj, newSSAPatch(patch) +} + +// apiServiceTarget knows how to set CA data for the CA bundle in +// the APIService spec. +type apiServiceTarget struct { + obj apireg.APIService +} + +func (t *apiServiceTarget) AsObject() client.Object { + return &t.obj +} + +func (t *apiServiceTarget) SetCA(data []byte) { + if utilfeature.DefaultFeatureGate.Enabled(feature.CAInjectorMerging) { + // If we for any reason cannot merge the certificate in, we replace it with + // the new certificate. + // + // This mirrors the old behavior of this function so is a reasonable + // fallback + bundle, err := cainjectorbundle.AppendCertificatesToBundle(t.obj.Spec.CABundle, data) + if err != nil { + bundle = data + } + + t.obj.Spec.CABundle = bundle + } else { + t.obj.Spec.CABundle = data + } +} + +type apiServiceTargetPatch struct { + applymetav1.TypeMetaApplyConfiguration `json:",inline"` + *applymetav1.ObjectMetaApplyConfiguration `json:"metadata,omitempty"` + Spec *apiServiceTargetSpecPatch `json:"spec,omitempty"` +} + +type apiServiceTargetSpecPatch struct { + CABundle []byte `json:"caBundle,omitempty"` +} + +func (t *apiServiceTarget) AsApplyObject() (client.Object, client.Patch) { + return &t.obj, newSSAPatch(&apiServiceTargetPatch{ + TypeMetaApplyConfiguration: *applymetav1. + TypeMeta(). + WithAPIVersion(apireg.SchemeGroupVersion.String()). + WithKind("APIService"), + ObjectMetaApplyConfiguration: applymetav1. + ObjectMeta(). + WithName(t.obj.Name), + Spec: &apiServiceTargetSpecPatch{ + CABundle: t.obj.Spec.CABundle, + }, + }) +} + +// crdConversionTarget knows how to set CA data for the conversion webhook in CRDs +type crdConversionTarget struct { + obj apiext.CustomResourceDefinition +} + +func (t *crdConversionTarget) AsObject() client.Object { + return &t.obj +} + +func (t *crdConversionTarget) SetCA(data []byte) { + if t.obj.Spec.Conversion == nil || t.obj.Spec.Conversion.Strategy != apiext.WebhookConverter { + return + } + if t.obj.Spec.Conversion.Webhook == nil { + t.obj.Spec.Conversion.Webhook = &apiext.WebhookConversion{} + } + if t.obj.Spec.Conversion.Webhook.ClientConfig == nil { + t.obj.Spec.Conversion.Webhook.ClientConfig = &apiext.WebhookClientConfig{} + } + + if utilfeature.DefaultFeatureGate.Enabled(feature.CAInjectorMerging) { + // If we for any reason cannot merge the certificate in, we replace it with + // the new certificate. + // + // This mirrors the old behavior of this function so is a reasonable + // fallback + bundle, err := cainjectorbundle.AppendCertificatesToBundle(t.obj.Spec.Conversion.Webhook.ClientConfig.CABundle, data) + if err != nil { + bundle = data + } + + t.obj.Spec.Conversion.Webhook.ClientConfig.CABundle = bundle + } else { + t.obj.Spec.Conversion.Webhook.ClientConfig.CABundle = data + } +} + +type customResourceDefinitionPatch struct { + applymetav1.TypeMetaApplyConfiguration `json:",inline"` + *applymetav1.ObjectMetaApplyConfiguration `json:"metadata,omitempty"` + Spec *customResourceDefinitionSpecPatch `json:"spec,omitempty"` +} + +type customResourceDefinitionSpecPatch struct { + Conversion *customResourceConversionPatch `json:"conversion,omitempty"` +} + +type customResourceConversionPatch struct { + Webhook *customResourceWebhookConversionPatch `json:"webhook,omitempty"` +} + +type customResourceWebhookConversionPatch struct { + ClientConfig *customResourceWebhookClientConfigPatch `json:"clientConfig,omitempty"` +} + +type customResourceWebhookClientConfigPatch struct { + CABundle []byte `json:"caBundle,omitempty"` +} + +func (t *crdConversionTarget) AsApplyObject() (client.Object, client.Patch) { + if t.obj.Spec.Conversion == nil || t.obj.Spec.Conversion.Webhook == nil || t.obj.Spec.Conversion.Webhook.ClientConfig == nil { + return &t.obj, nil + } + + return &t.obj, newSSAPatch(&customResourceDefinitionPatch{ + TypeMetaApplyConfiguration: *applymetav1. + TypeMeta(). + WithAPIVersion(apiext.SchemeGroupVersion.String()). + WithKind("CustomResourceDefinition"), + ObjectMetaApplyConfiguration: applymetav1. + ObjectMeta(). + WithName(t.obj.Name), + Spec: &customResourceDefinitionSpecPatch{ + Conversion: &customResourceConversionPatch{ + Webhook: &customResourceWebhookConversionPatch{ + ClientConfig: &customResourceWebhookClientConfigPatch{ + CABundle: t.obj.Spec.Conversion.Webhook.ClientConfig.CABundle, + }, + }, + }, + }, + }) +} diff --git a/pkg/controller/cainjector/injectors.go b/pkg/controller/cainjector/injectors.go deleted file mode 100644 index d1b97c0fc8c..00000000000 --- a/pkg/controller/cainjector/injectors.go +++ /dev/null @@ -1,142 +0,0 @@ -/* -Copyright 2020 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. -*/ - -package cainjector - -import ( - admissionreg "k8s.io/api/admissionregistration/v1" - apiext "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" - apireg "k8s.io/kube-aggregator/pkg/apis/apiregistration/v1" - "sigs.k8s.io/controller-runtime/pkg/client" -) - -// this contains implementations of CertInjector (and dependents) -// for various Kubernetes types that contain CA bundles. -// This allows us to build a generic "injection" controller, and parameterize -// it with these. -// Ideally, we'd have some generic way to express this as well. - -// mutatingWebhookInjector knows how to create an InjectTarget a MutatingWebhookConfiguration. -type mutatingWebhookInjector struct{} - -func (i mutatingWebhookInjector) IsAlpha() bool { - return false -} - -func (i mutatingWebhookInjector) NewTarget() InjectTarget { - return &mutatingWebhookTarget{} -} - -// mutatingWebhookTarget knows how to set CA data for all the webhooks -// in a mutatingWebhookConfiguration. -type mutatingWebhookTarget struct { - obj admissionreg.MutatingWebhookConfiguration -} - -func (t *mutatingWebhookTarget) AsObject() client.Object { - return &t.obj -} -func (t *mutatingWebhookTarget) SetCA(data []byte) { - for ind := range t.obj.Webhooks { - t.obj.Webhooks[ind].ClientConfig.CABundle = data - } -} - -// validatingWebhookInjector knows how to create an InjectTarget a ValidatingWebhookConfiguration. -type validatingWebhookInjector struct{} - -func (i validatingWebhookInjector) NewTarget() InjectTarget { - return &validatingWebhookTarget{} -} - -func (i validatingWebhookInjector) IsAlpha() bool { - return false -} - -// validatingWebhookTarget knows how to set CA data for all the webhooks -// in a validatingWebhookConfiguration. -type validatingWebhookTarget struct { - obj admissionreg.ValidatingWebhookConfiguration -} - -func (t *validatingWebhookTarget) AsObject() client.Object { - return &t.obj -} - -func (t *validatingWebhookTarget) SetCA(data []byte) { - for ind := range t.obj.Webhooks { - t.obj.Webhooks[ind].ClientConfig.CABundle = data - } -} - -// apiServiceInjector knows how to create an InjectTarget for APICAReferences -type apiServiceInjector struct{} - -func (i apiServiceInjector) NewTarget() InjectTarget { - return &apiServiceTarget{} -} - -func (i apiServiceInjector) IsAlpha() bool { - return false -} - -// apiServiceTarget knows how to set CA data for the CA bundle in -// the APIService. -type apiServiceTarget struct { - obj apireg.APIService -} - -func (t *apiServiceTarget) AsObject() client.Object { - return &t.obj -} - -func (t *apiServiceTarget) SetCA(data []byte) { - t.obj.Spec.CABundle = data -} - -// TODO(directxman12): conversion webhooks -// crdConversionInjector knows how to create an InjectTarget for CRD conversion webhooks -type crdConversionInjector struct{} - -func (i crdConversionInjector) NewTarget() InjectTarget { - return &crdConversionTarget{} -} - -func (i crdConversionInjector) IsAlpha() bool { - return false -} - -// crdConversionTarget knows how to set CA data for the conversion webhook in CRDs -type crdConversionTarget struct { - obj apiext.CustomResourceDefinition -} - -func (t *crdConversionTarget) AsObject() client.Object { - return &t.obj -} - -func (t *crdConversionTarget) SetCA(data []byte) { - if t.obj.Spec.Conversion == nil || t.obj.Spec.Conversion.Strategy != apiext.WebhookConverter { - return - } - if t.obj.Spec.Conversion.Webhook == nil { - t.obj.Spec.Conversion.Webhook = &apiext.WebhookConversion{} - } - if t.obj.Spec.Conversion.Webhook.ClientConfig == nil { - t.obj.Spec.Conversion.Webhook.ClientConfig = &apiext.WebhookClientConfig{} - } - t.obj.Spec.Conversion.Webhook.ClientConfig.CABundle = data -} diff --git a/pkg/controller/cainjector/controller.go b/pkg/controller/cainjector/reconciler.go similarity index 56% rename from pkg/controller/cainjector/controller.go rename to pkg/controller/cainjector/reconciler.go index 1bcfc4ec350..2aaf618dd47 100644 --- a/pkg/controller/cainjector/controller.go +++ b/pkg/controller/cainjector/reconciler.go @@ -22,80 +22,30 @@ import ( "strings" "github.com/go-logr/logr" - corev1 "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" + "k8s.io/utils/ptr" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" + "github.com/cert-manager/cert-manager/internal/cainjector/feature" certmanager "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" logf "github.com/cert-manager/cert-manager/pkg/logs" + utilfeature "github.com/cert-manager/cert-manager/pkg/util/feature" ) -// dropNotFound ignores the given error if it's a not-found error, -// but otherwise just returns the argument. -func dropNotFound(err error) error { - if apierrors.IsNotFound(err) { - return nil - } - return err -} - -// OwningCertForSecret gets the name of the owning certificate for a -// given secret, returning nil if no such object exists. -// Right now, this actually uses a label instead of owner refs, -// since certmanager doesn't set owner refs on secrets. -func OwningCertForSecret(secret *corev1.Secret) *types.NamespacedName { - lblVal, hasLbl := secret.Annotations[certmanager.CertificateNameKey] - if !hasLbl { - return nil - } - return &types.NamespacedName{ - Name: lblVal, - Namespace: secret.Namespace, - } -} - -// InjectTarget is a Kubernetes API object that has one or more references to Kubernetes -// Services with corresponding fields for CA bundles. -type InjectTarget interface { - // AsObject returns this injectable as an object. - // It should be a pointer suitable for mutation. - AsObject() client.Object - - // SetCA sets the CA of this target to the given certificate data (in the standard - // PEM format used across Kubernetes). In cases where multiple CA fields exist per - // target (like admission webhook configs), all CAs are set to the given value. - SetCA(data []byte) -} - -// Injectable is a point in a Kubernetes API object that represents a Kubernetes Service -// reference with a corresponding spot for a CA bundle. -type Injectable interface { -} +// This file contains logic to create reconcilers. By default a +// reconciler is created for each of the injectables - CustomResourceDefinition, +// Validating/MutatingWebhookConfiguration, APIService and gets triggered for +// events on those resources as well as on Secrets and Certificates. -// CertInjector knows how to create an instance of an InjectTarget for some particular type -// of inject target. For instance, an implementation might create a InjectTarget -// containing an empty MutatingWebhookConfiguration. The underlying API object can -// be populated (via AsObject) using client.Client#Get, and then CAs can be injected with -// Injectables (representing the various individual webhooks in the config) retrieved with -// Services. -type CertInjector interface { - // NewTarget creates a new InjectTarget containing an empty underlying object. - NewTarget() InjectTarget - // IsAlpha tells the client to disregard "no matching kind" type of errors - IsAlpha() bool -} - -// genericInjectReconciler is a reconciler that knows how to check if a given object is -// marked as requiring a CA, chase down the corresponding Service, Certificate, Secret, and -// inject that into the object. -type genericInjectReconciler struct { - // injector is responsible for the logic of actually setting a CA -- it's the component - // that contains type-specific logic. - injector CertInjector +// reconciler syncs CA data from source to injectable. +type reconciler struct { + // newInjectableTarget knows how to create a new injectable target for + // the injectable being reconciled. + newInjectableTarget NewInjectableTarget // sources is a list of available 'data sources' that can be used to extract // caBundles from various source. // This is defined as a variable to allow an instance of the secret-based @@ -106,27 +56,24 @@ type genericInjectReconciler struct { log logr.Logger client.Client - resourceName string // just used for logging -} + // if set, the reconciler is namespace scoped + namespace string -// splitNamespacedName turns the string form of a namespaced name -// (/) back into a types.NamespacedName. -func splitNamespacedName(nameStr string) types.NamespacedName { - splitPoint := strings.IndexRune(nameStr, types.Separator) - if splitPoint == -1 { - return types.NamespacedName{Name: nameStr} - } - return types.NamespacedName{Namespace: nameStr[:splitPoint], Name: nameStr[splitPoint+1:]} + // fieldManager is the manager name used for the Apply operations. + fieldManager string + + resourceName string // just used for logging } -// Reconcile attempts to ensure that a particular object has all the CAs injected that +// Reconcile attempts to ensure that a particular injectable has all the CAs injected that // it has requested. -func (r *genericInjectReconciler) Reconcile(_ context.Context, req ctrl.Request) (ctrl.Result, error) { - ctx := context.Background() - log := r.log.WithValues(r.resourceName, req.NamespacedName) - +func (r *reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { // fetch the target object - target := r.injector.NewTarget() + target := r.newInjectableTarget() + + log := r.log.WithValues("kind", r.resourceName, "name", req.Name) + log.V(logf.DebugLevel).Info("Parsing injectable") + if err := r.Client.Get(ctx, req.NamespacedName, target.AsObject()); err != nil { if dropNotFound(err) == nil { // don't requeue on deletions, which yield a non-found object @@ -142,7 +89,6 @@ func (r *genericInjectReconciler) Reconcile(_ context.Context, req ctrl.Request) log.Error(err, "unable to get metadata for object") return ctrl.Result{}, err } - log = logf.WithResource(r.log, metaObj) // ignore resources that are being deleted if !metaObj.GetDeletionTimestamp().IsZero() { @@ -154,14 +100,19 @@ func (r *genericInjectReconciler) Reconcile(_ context.Context, req ctrl.Request) dataSource, err := r.caDataSourceFor(log, metaObj) if err != nil { log.V(logf.DebugLevel).Info("failed to determine ca data source for injectable") - return ctrl.Result{}, nil + return ctrl.Result{}, nil //nolint:nilerr } - caData, err := dataSource.ReadCA(ctx, log, metaObj) + caData, err := dataSource.ReadCA(ctx, log, metaObj, r.namespace) + if apierrors.IsForbidden(err) { + log.V(logf.InfoLevel).Info("cainjector was forbidden to retrieve the ca data source") + return ctrl.Result{}, nil + } if err != nil { log.Error(err, "failed to read CA from data source") return ctrl.Result{}, err } + if caData == nil { log.V(logf.InfoLevel).Info("could not find any ca data in data source for target") return ctrl.Result{}, nil @@ -171,16 +122,28 @@ func (r *genericInjectReconciler) Reconcile(_ context.Context, req ctrl.Request) target.SetCA(caData) // actually update with injected CA data - if err := r.Client.Update(ctx, target.AsObject()); err != nil { + if utilfeature.DefaultFeatureGate.Enabled(feature.ServerSideApply) { + obj, patch := target.AsApplyObject() + if patch != nil { + err = r.Client.Patch(ctx, obj, patch, &client.PatchOptions{ + Force: ptr.To(true), FieldManager: r.fieldManager, + }) + } + } else { + err = r.Client.Update(ctx, target.AsObject()) + } + + if err != nil { log.Error(err, "unable to update target object with new CA data") return ctrl.Result{}, err } - log.V(logf.InfoLevel).Info("updated object") + + log.V(logf.InfoLevel).Info("Updated object") return ctrl.Result{}, nil } -func (r *genericInjectReconciler) caDataSourceFor(log logr.Logger, metaObj metav1.Object) (caDataSource, error) { +func (r *reconciler) caDataSourceFor(log logr.Logger, metaObj metav1.Object) (caDataSource, error) { for _, s := range r.sources { if s.Configured(log, metaObj) { return s, nil @@ -188,3 +151,45 @@ func (r *genericInjectReconciler) caDataSourceFor(log logr.Logger, metaObj metav } return nil, fmt.Errorf("could not determine ca data source for resource") } + +// dropNotFound ignores the given error if it's a not-found error, +// but otherwise just returns the argument. +// TODO: we don't use this pattern anywhere else in this project so probably doesn't make sense here either +func dropNotFound(err error) error { + if apierrors.IsNotFound(err) { + return nil + } + return err +} + +// owningCertForSecret gets the name of the owning certificate for a given +// secret, returning nil if the supplied secret does not have a +// `cert-manager.io/certificate-name` annotation. +// The secret may be a v1.Secret or a v1.PartialObjectMetadata. +// +// NOTE: "owning" here does not mean [ownerReference][1], because +// cert-manager does not set the ownerReference of the Secret, +// unless the [`--enable-certificate-owner-ref` flag is true][2]. +// +// [1]: https://kubernetes.io/docs/concepts/overview/working-with-objects/owners-dependents/ +// [2]: https://cert-manager.io/docs/cli/controller/ +func owningCertForSecret(secret client.Object) *types.NamespacedName { + val, ok := secret.GetAnnotations()[certmanager.CertificateNameKey] + if !ok { + return nil + } + return &types.NamespacedName{ + Name: val, + Namespace: secret.GetNamespace(), + } +} + +// splitNamespacedName turns the string form of a namespaced name +// (/) back into a types.NamespacedName. +func splitNamespacedName(nameStr string) types.NamespacedName { + splitPoint := strings.IndexRune(nameStr, types.Separator) + if splitPoint == -1 { + return types.NamespacedName{Name: nameStr} + } + return types.NamespacedName{Namespace: nameStr[:splitPoint], Name: nameStr[splitPoint+1:]} +} diff --git a/pkg/controller/cainjector/setup.go b/pkg/controller/cainjector/setup.go index acb7743f8db..bf23396bf22 100644 --- a/pkg/controller/cainjector/setup.go +++ b/pkg/controller/cainjector/setup.go @@ -21,142 +21,189 @@ import ( "fmt" "os" - logf "github.com/cert-manager/cert-manager/pkg/logs" - "github.com/go-logr/logr" - "golang.org/x/sync/errgroup" admissionreg "k8s.io/api/admissionregistration/v1" + corev1 "k8s.io/api/core/v1" apiext "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" - "k8s.io/apimachinery/pkg/api/meta" "k8s.io/apimachinery/pkg/runtime" apireg "k8s.io/kube-aggregator/pkg/apis/apiregistration/v1" ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/cache" + "sigs.k8s.io/controller-runtime/pkg/builder" "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/cluster" - "sigs.k8s.io/controller-runtime/pkg/controller" + "sigs.k8s.io/controller-runtime/pkg/event" "sigs.k8s.io/controller-runtime/pkg/handler" - "sigs.k8s.io/controller-runtime/pkg/reconcile" - "sigs.k8s.io/controller-runtime/pkg/source" + "sigs.k8s.io/controller-runtime/pkg/predicate" + + cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" + "github.com/cert-manager/cert-manager/pkg/util" +) + +// this file contains the logic to set up the different reconcile loops run by cainjector +// each reconciler corresponds to a type of injectable + +const ( + MutatingWebhookConfigurationName = "mutatingwebhookconfiguration" + ValidatingWebhookConfigurationName = "validatingwebhookconfiguration" + APIServiceName = "apiservice" + CustomResourceDefinitionName = "customresourcedefinition" ) -// injectorSet describes a particular setup of the injector controller -type injectorSetup struct { +// setup is setup for a reconciler for a particular injectable type +type setup struct { resourceName string - injector CertInjector - listType runtime.Object + // newInjectableTarget knows how to create an InjectableTarget for a particular injectable type + newInjectableTarget NewInjectableTarget + listType runtime.Object + objType client.Object +} + +type SetupOptions struct { + Namespace string + EnableCertificatesDataSource bool + EnabledReconcilersFor map[string]bool } var ( - MutatingWebhookSetup = injectorSetup{ - resourceName: "mutatingwebhookconfiguration", - injector: mutatingWebhookInjector{}, - listType: &admissionreg.MutatingWebhookConfigurationList{}, + MutatingWebhookSetup = setup{ + resourceName: "mutatingwebhookconfiguration", + newInjectableTarget: newMutatingWebhookInjectable, + listType: &admissionreg.MutatingWebhookConfigurationList{}, + objType: &admissionreg.MutatingWebhookConfiguration{}, } - ValidatingWebhookSetup = injectorSetup{ - resourceName: "validatingwebhookconfiguration", - injector: validatingWebhookInjector{}, - listType: &admissionreg.ValidatingWebhookConfigurationList{}, + ValidatingWebhookSetup = setup{ + resourceName: "validatingwebhookconfiguration", + newInjectableTarget: newValidatingWebhookInjectable, + listType: &admissionreg.ValidatingWebhookConfigurationList{}, + objType: &admissionreg.ValidatingWebhookConfiguration{}, } - APIServiceSetup = injectorSetup{ - resourceName: "apiservice", - injector: apiServiceInjector{}, - listType: &apireg.APIServiceList{}, + APIServiceSetup = setup{ + resourceName: "apiservice", + newInjectableTarget: newAPIServiceInjectable, + listType: &apireg.APIServiceList{}, + objType: &apireg.APIService{}, } - CRDSetup = injectorSetup{ - resourceName: "customresourcedefinition", - injector: crdConversionInjector{}, - listType: &apiext.CustomResourceDefinitionList{}, + CRDSetup = setup{ + resourceName: "customresourcedefinition", + newInjectableTarget: newCRDConversionInjectable, + listType: &apiext.CustomResourceDefinitionList{}, + objType: &apiext.CustomResourceDefinition{}, } - - injectorSetups = []injectorSetup{MutatingWebhookSetup, ValidatingWebhookSetup, APIServiceSetup, CRDSetup} - ControllerNames []string ) -// registerAllInjectors registers all injectors and based on the -// graduation state of the injector decides how to log no kind/resource match errors -func registerAllInjectors(ctx context.Context, groupName string, mgr ctrl.Manager, sources []caDataSource, client client.Client, ca cache.Cache) error { - controllers := make([]controller.Controller, len(injectorSetups)) - for i, setup := range injectorSetups { - controller, err := newGenericInjectionController(ctx, groupName, mgr, setup, sources, ca, client) - if err != nil { - if !meta.IsNoMatchError(err) || !setup.injector.IsAlpha() { - return err - } - ctrl.Log.V(logf.WarnLevel).Info("unable to register injector which is still in an alpha phase."+ - " Enable the feature on the API server in order to use this injector", - "injector", setup.resourceName) - } - controllers[i] = controller +// RegisterAllInjectors sets up watches for all injectable and injector types that cainjector should watch +func RegisterAllInjectors(ctx context.Context, mgr ctrl.Manager, opts SetupOptions) error { + sds := &secretDataSource{ + client: mgr.GetClient(), } - g, gctx := errgroup.WithContext(ctx) - - g.Go(func() (err error) { - if err = ca.Start(gctx); err != nil { - return err - } - return nil - }) - if ca.WaitForCacheSync(gctx) { - for _, controller := range controllers { - if gctx.Err() != nil { - break - } - controller := controller - g.Go(func() (err error) { - return controller.Start(gctx) - }) - } - } else { - // I assume that if the cache sync fails, then the already-started cache - // will exit with a meaningful error which will be returned by the errgroup - ctrl.Log.Error(nil, "timed out or failed while waiting for cache") + cds := &certificateDataSource{ + client: mgr.GetClient(), } - return g.Wait() -} - -// newGenericInjectionController creates a controller and adds relevant watches -// and indexers to the supplied cache. -// TODO: We can't use the controller-runtime controller.Builder mechanism here -// because it doesn't allow us to specify the cache to which we link watches, -// indexes and event sources. Keep checking new controller-runtime releases for -// improvements which might make this easier: -// * https://github.com/kubernetes-sigs/controller-runtime/issues/764 -func newGenericInjectionController(ctx context.Context, groupName string, mgr ctrl.Manager, - setup injectorSetup, sources []caDataSource, ca cache.Cache, - client client.Client) (controller.Controller, error) { - log := ctrl.Log.WithName(groupName).WithName(setup.resourceName) - typ := setup.injector.NewTarget().AsObject() - - c, err := controller.NewUnmanaged( - fmt.Sprintf("controller-for-%s-%s", groupName, setup.resourceName), - mgr, - controller.Options{ - Reconciler: &genericInjectReconciler{ - Client: client, - sources: sources, - log: log.WithName("generic-inject-reconciler"), - resourceName: setup.resourceName, - injector: setup.injector, - }, - LogConstructor: func(request *reconcile.Request) logr.Logger { return log }, - }) + cfg := mgr.GetConfig() + caBundle, err := dataFromSliceOrFile(cfg.CAData, cfg.CAFile) if err != nil { - return nil, err + return err } - if err := c.Watch(source.NewKindWithCache(typ, ca), &handler.EnqueueRequestForObject{}); err != nil { - return nil, err + kds := &kubeconfigDataSource{ + apiserverCABundle: caBundle, } + injectorSetups := []setup{MutatingWebhookSetup, ValidatingWebhookSetup, APIServiceSetup, CRDSetup} + // Registers a c/r controller for each of APIService, CustomResourceDefinition, Mutating/ValidatingWebhookConfiguration + for _, setup := range injectorSetups { + log := ctrl.Log.WithValues("kind", setup.resourceName) + if !opts.EnabledReconcilersFor[setup.resourceName] { + log.Info("Not registering a reconcile for injectable kind as it's disabled") + continue + } + log.Info("Registering a reconciler for injectable") + r := &reconciler{ + namespace: opts.Namespace, + resourceName: setup.resourceName, + newInjectableTarget: setup.newInjectableTarget, + log: log, + Client: mgr.GetClient(), + // TODO: refactor + sources: []caDataSource{ + sds, + cds, + kds, + }, + fieldManager: util.PrefixFromUserAgent(mgr.GetConfig().UserAgent), + } - for _, s := range sources { - if err := s.ApplyTo(ctx, mgr, setup, c, ca); err != nil { - return nil, err + // Index injectable with a new field. If the injectable's CA is + // to be sourced from a Secret, the field's value will be the + // namespaced name of the Secret. + // This field can then be used as a field selector when listing injectables of this type. + secretTyp := setup.newInjectableTarget().AsObject() + if err := mgr.GetFieldIndexer().IndexField(ctx, secretTyp, injectFromSecretPath, injectableCAFromSecretIndexer); err != nil { + err := fmt.Errorf("error making injectable indexable by inject-ca-from-secret annotation: %w", err) + return err + } + predicates := predicate.Funcs{ + UpdateFunc: func(e event.UpdateEvent) bool { + return hasInjectableAnnotation(e.ObjectNew) + }, + CreateFunc: func(e event.CreateEvent) bool { + return hasInjectableAnnotation(e.Object) + }, + DeleteFunc: func(e event.DeleteEvent) bool { + return hasInjectableAnnotation(e.Object) + }, } - } - return c, nil + b := ctrl.NewControllerManagedBy(mgr). + For(setup.objType, + // We watch all CRDs, + // Validating/MutatingWebhookConfigurations, + // APIServices because the only way how to tell + // if an object is an injectable is from + // annotation value and this cannot be used to + // filter List/Watch. The earliest point where + // we can use the annotation to filter + // injectables is here where we define which + // objects' events should trigger a reconcile. + builder.WithPredicates(predicates)). + Watches( + new(corev1.Secret), + handler.EnqueueRequestsFromMapFunc(secretForInjectableMapFuncBuilder(mgr.GetClient(), log, setup)), + // Why do we use builder.OnlyMetadata? + // + // 1. To reduce memory use of cainjector, by only caching the + // metadata of Secrets, not the data. + // 2. To reduce the load on the K8S API server, by only listing + // the metadata of the Secrets when cainjector starts up. + // + // This configuration works in conjunction with the `DisableFor` + // option in the manager client.Options and client.CacheOptions. + builder.OnlyMetadata, + ) + if opts.EnableCertificatesDataSource { + // Index injectable with a new field. If the injectable's CA is + // to be sourced from a Certificate's Secret, the field's value will be the + // namespaced name of the Certificate. + // This field can then be used as a field selector when listing injectables of this type. + certTyp := setup.newInjectableTarget().AsObject() + if err := mgr.GetFieldIndexer().IndexField(ctx, certTyp, injectFromPath, injectableCAFromIndexer); err != nil { + err := fmt.Errorf("error making injectable indexable by inject-ca-from path: %w", err) + return err + } + b.Watches( + new(corev1.Secret), + handler.EnqueueRequestsFromMapFunc(certFromSecretToInjectableMapFuncBuilder(mgr.GetClient(), log, setup)), + // See "Why do we use builder.OnlyMetadata?" above. + builder.OnlyMetadata, + ).Watches( + new(cmapi.Certificate), + handler.EnqueueRequestsFromMapFunc(certToInjectableMapFuncBuilder(mgr.GetClient(), log, setup)), + ) + } + if err := b.Complete(r); err != nil { + return fmt.Errorf("error registering controller for %s: %w", setup.objType.GetName(), err) + } + } + return nil } // dataFromSliceOrFile returns data from the slice (if non-empty), or from the file, @@ -174,76 +221,3 @@ func dataFromSliceOrFile(data []byte, file string) ([]byte, error) { } return nil, nil } - -// RegisterCertificateBased registers all known injection controllers that -// target Certificate resources with the given manager, and adds relevant -// indices. -// The registered controllers require the cert-manager API to be available -// in order to run. -func RegisterCertificateBased(ctx context.Context, mgr ctrl.Manager) error { - cache, client, err := newIndependentCacheAndDelegatingClient(mgr) - if err != nil { - return err - } - return registerAllInjectors( - ctx, - "certificate", - mgr, - []caDataSource{ - &certificateDataSource{client: cache}, - }, - client, - cache, - ) -} - -// RegisterSecretBased registers all known injection controllers that -// target Secret resources with the given manager, and adds relevant -// indices. -// The registered controllers only require the corev1 APi to be available in -// order to run. -func RegisterSecretBased(ctx context.Context, mgr ctrl.Manager) error { - cache, client, err := newIndependentCacheAndDelegatingClient(mgr) - if err != nil { - return err - } - return registerAllInjectors( - ctx, - "secret", - mgr, - []caDataSource{ - &secretDataSource{client: cache}, - &kubeconfigDataSource{}, - }, - client, - cache, - ) -} - -// newIndependentCacheAndDelegatingClient creates a cache and a delegating -// client which are independent of the cache of the manager. -// This allows us to start the manager and secrets based injectors before the -// cert-manager Certificates CRDs have been installed and before the CA bundles -// have been injected into the cert-manager CRDs, by the secrets based injector, -// which is running in a separate goroutine. -func newIndependentCacheAndDelegatingClient(mgr ctrl.Manager) (cache.Cache, client.Client, error) { - cacheOptions := cache.Options{ - Scheme: mgr.GetScheme(), - Mapper: mgr.GetRESTMapper(), - } - ca, err := cache.New(mgr.GetConfig(), cacheOptions) - if err != nil { - return nil, nil, err - } - - clientOptions := client.Options{ - Scheme: mgr.GetScheme(), - Mapper: mgr.GetRESTMapper(), - } - - client, err := cluster.DefaultNewClient(ca, mgr.GetConfig(), clientOptions) - if err != nil { - return nil, nil, err - } - return ca, client, nil -} diff --git a/pkg/controller/cainjector/sources.go b/pkg/controller/cainjector/sources.go index 044d667afd0..5d7bdfe22cd 100644 --- a/pkg/controller/cainjector/sources.go +++ b/pkg/controller/cainjector/sources.go @@ -18,27 +18,24 @@ package cainjector import ( "context" - - logf "github.com/cert-manager/cert-manager/pkg/logs" + "errors" + "fmt" "github.com/go-logr/logr" corev1 "k8s.io/api/core/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/cache" "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/controller" - "sigs.k8s.io/controller-runtime/pkg/handler" - "sigs.k8s.io/controller-runtime/pkg/source" cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" + logf "github.com/cert-manager/cert-manager/pkg/logs" ) // caDataSource knows how to extract CA data given a provided InjectTarget. // This allows adaptable implementations of fetching CA data based on -// configuration given on the injection target (e.g. annotations). +// configuration given on the injection target (e.g., annotations). type caDataSource interface { // Configured returns true if this data source should be used for the given @@ -52,10 +49,7 @@ type caDataSource interface { // In this case, the caller should not retry the operation. // It is up to the ReadCA implementation to inform the user why the CA // failed to read. - ReadCA(ctx context.Context, log logr.Logger, metaObj metav1.Object) (ca []byte, err error) - - // ApplyTo applies any required watchers to the given controller. - ApplyTo(ctx context.Context, mgr ctrl.Manager, setup injectorSetup, controller controller.Controller, ca cache.Cache) error + ReadCA(ctx context.Context, log logr.Logger, metaObj metav1.Object, namespace string) (ca []byte, err error) } // kubeconfigDataSource reads the ca bundle provided as part of the struct @@ -69,20 +63,10 @@ func (c *kubeconfigDataSource) Configured(log logr.Logger, metaObj metav1.Object return metaObj.GetAnnotations()[cmapi.WantInjectAPIServerCAAnnotation] == "true" } -func (c *kubeconfigDataSource) ReadCA(ctx context.Context, log logr.Logger, metaObj metav1.Object) (ca []byte, err error) { +func (c *kubeconfigDataSource) ReadCA(ctx context.Context, log logr.Logger, metaObj metav1.Object, namespace string) (ca []byte, err error) { return c.apiserverCABundle, nil } -func (c *kubeconfigDataSource) ApplyTo(ctx context.Context, mgr ctrl.Manager, setup injectorSetup, _ controller.Controller, _ cache.Cache) error { - cfg := mgr.GetConfig() - caBundle, err := dataFromSliceOrFile(cfg.CAData, cfg.CAFile) - if err != nil { - return err - } - c.apiserverCABundle = caBundle - return nil -} - // certificateDataSource reads a CA bundle by fetching the Certificate named in // the 'cert-manager.io/inject-ca-from' annotation in the form // 'namespace/name'. @@ -99,15 +83,23 @@ func (c *certificateDataSource) Configured(log logr.Logger, metaObj metav1.Objec return true } -func (c *certificateDataSource) ReadCA(ctx context.Context, log logr.Logger, metaObj metav1.Object) (ca []byte, err error) { +func (c *certificateDataSource) ReadCA(ctx context.Context, log logr.Logger, metaObj metav1.Object, namespace string) (ca []byte, err error) { certNameRaw := metaObj.GetAnnotations()[cmapi.WantInjectAnnotation] certName := splitNamespacedName(certNameRaw) log = log.WithValues("certificate", certName) if certName.Namespace == "" { - log.Error(nil, "invalid certificate name; needs a namespace/ prefix") + err := errors.New("invalid annotation") + log.Error(err, "invalid certificate name: needs a namespace/ prefix") + // TODO: should an error be returned here to prevent the caller from proceeding? // don't return an error, requeuing won't help till this is changed return nil, nil } + if namespace != "" && certName.Namespace != namespace { + err := fmt.Errorf("cannot read CA data from Certificate in namespace %s, cainjector is scoped to namespace %s", certName.Namespace, namespace) + forbiddenErr := apierrors.NewForbidden(cmapi.Resource("certificates"), certName.Name, err) + log.Error(forbiddenErr, "cannot read data source") + return nil, forbiddenErr + } var cert cmapi.Certificate if err := c.client.Get(ctx, certName, &cert); err != nil { @@ -125,7 +117,14 @@ func (c *certificateDataSource) ReadCA(ctx context.Context, log logr.Logger, met // don't requeue if we're just not found, we'll get called when the secret gets created return nil, dropNotFound(err) } - owner := OwningCertForSecret(&secret) + // Only use Secrets that have been created by this Certificate. + // The Secret must have a `cert-manager.io/certificate-name` annotation + // value matching the name of this Certificate.. + // NOTE: "owner" is not the `ownerReference`, because cert-manager does not + // usually set the ownerReference of the Secret. + // TODO: The logged warning below is misleading because it contains the + // ownerReference, which is not the reason for ignoring the Secret. + owner := owningCertForSecret(&secret) if owner == nil || *owner != certName { log.V(logf.WarnLevel).Info("refusing to target secret not owned by certificate", "owner", metav1.GetControllerOf(&secret)) return nil, nil @@ -134,7 +133,8 @@ func (c *certificateDataSource) ReadCA(ctx context.Context, log logr.Logger, met // inject the CA data caData, hasCAData := secret.Data[cmmeta.TLSCAKey] if !hasCAData { - log.Error(nil, "certificate has no CA data") + err := errors.New("invalid CA source") + log.Error(err, "certificate has no CA data") // don't requeue, we'll get called when the secret gets updated return nil, nil } @@ -142,33 +142,6 @@ func (c *certificateDataSource) ReadCA(ctx context.Context, log logr.Logger, met return caData, nil } -func (c *certificateDataSource) ApplyTo(ctx context.Context, mgr ctrl.Manager, setup injectorSetup, controller controller.Controller, ca cache.Cache) error { - typ := setup.injector.NewTarget().AsObject() - if err := ca.IndexField(ctx, typ, injectFromPath, injectableCAFromIndexer); err != nil { - return err - } - - if err := controller.Watch(source.NewKindWithCache(&cmapi.Certificate{}, ca), - handler.EnqueueRequestsFromMapFunc((&certMapper{ - Client: ca, - log: ctrl.Log.WithName("cert-mapper"), - toInjectable: buildCertToInjectableFunc(setup.listType, setup.resourceName), - }).Map), - ); err != nil { - return err - } - if err := controller.Watch(source.NewKindWithCache(&corev1.Secret{}, ca), - handler.EnqueueRequestsFromMapFunc((&secretForCertificateMapper{ - Client: ca, - log: ctrl.Log.WithName("secret-for-certificate-mapper"), - certificateToInjectable: buildCertToInjectableFunc(setup.listType, setup.resourceName), - }).Map), - ); err != nil { - return err - } - return nil -} - // secretDataSource reads a CA bundle from a Secret resource named using the // 'cert-manager.io/inject-ca-from-secret' annotation in the form // 'namespace/name'. @@ -185,16 +158,25 @@ func (c *secretDataSource) Configured(log logr.Logger, metaObj metav1.Object) bo return true } -func (c *secretDataSource) ReadCA(ctx context.Context, log logr.Logger, metaObj metav1.Object) ([]byte, error) { +func (c *secretDataSource) ReadCA(ctx context.Context, log logr.Logger, metaObj metav1.Object, namespace string) ([]byte, error) { secretNameRaw := metaObj.GetAnnotations()[cmapi.WantInjectFromSecretAnnotation] secretName := splitNamespacedName(secretNameRaw) log = log.WithValues("secret", secretName) if secretName.Namespace == "" { - log.Error(nil, "invalid certificate name") + err := errors.New("invalid annotation") + log.Error(err, "invalid secret source: missing namespace/ prefix") + // TODO: should we return error here to prevent the caller from proceeding? // don't return an error, requeuing won't help till this is changed return nil, nil } + if namespace != "" && secretName.Namespace != namespace { + err := fmt.Errorf("cannot read CA data from Secret in namespace %s, cainjector is scoped to namespace %s", secretName.Namespace, namespace) + forbiddenErr := apierrors.NewForbidden(cmapi.Resource("certificates"), secretName.Name, err) + log.Error(forbiddenErr, "cannot read data source") + return nil, forbiddenErr + } + // grab the associated secret var secret corev1.Secret if err := c.client.Get(ctx, secretName, &secret); err != nil { @@ -211,27 +193,11 @@ func (c *secretDataSource) ReadCA(ctx context.Context, log logr.Logger, metaObj // inject the CA data caData, hasCAData := secret.Data[cmmeta.TLSCAKey] if !hasCAData { - log.Error(nil, "certificate has no CA data") + err := errors.New("invalid CA source") + log.Error(err, "secret contains no CA data") // don't requeue, we'll get called when the secret gets updated return nil, nil } return caData, nil } - -func (c *secretDataSource) ApplyTo(ctx context.Context, mgr ctrl.Manager, setup injectorSetup, controller controller.Controller, ca cache.Cache) error { - typ := setup.injector.NewTarget().AsObject() - if err := ca.IndexField(ctx, typ, injectFromSecretPath, injectableCAFromSecretIndexer); err != nil { - return err - } - if err := controller.Watch(source.NewKindWithCache(&corev1.Secret{}, ca), - handler.EnqueueRequestsFromMapFunc((&secretForInjectableMapper{ - Client: ca, - log: ctrl.Log.WithName("secret-mapper"), - secretToInjectable: buildSecretToInjectableFunc(setup.listType, setup.resourceName), - }).Map), - ); err != nil { - return err - } - return nil -} diff --git a/pkg/controller/certificate-shim/gateways/controller.go b/pkg/controller/certificate-shim/gateways/controller.go index 2b3e0ad87f9..6182cc6dbf6 100644 --- a/pkg/controller/certificate-shim/gateways/controller.go +++ b/pkg/controller/certificate-shim/gateways/controller.go @@ -19,14 +19,14 @@ package controller import ( "context" "fmt" - "time" k8sErrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/runtime" "k8s.io/client-go/tools/cache" "k8s.io/client-go/util/workqueue" - gwlisters "sigs.k8s.io/gateway-api/pkg/client/listers/apis/v1alpha2" + gwlisters "sigs.k8s.io/gateway-api/pkg/client/listers/apis/v1" cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" controllerpkg "github.com/cert-manager/cert-manager/pkg/controller" @@ -36,12 +36,6 @@ import ( const ( ControllerName = "gateway-shim" - - // resyncPeriod is set to 10 hours across cert-manager. These 10 hours come - // from a discussion on the controller-runtime project that boils down to: - // never change this without an explicit reason. - // https://github.com/kubernetes-sigs/controller-runtime/pull/88#issuecomment-408500629 - resyncPeriod = 10 * time.Hour ) type controller struct { @@ -49,20 +43,22 @@ type controller struct { sync shimhelper.SyncFn // For testing purposes. - queue workqueue.RateLimitingInterface + queue workqueue.TypedRateLimitingInterface[types.NamespacedName] } -func (c *controller) Register(ctx *controllerpkg.Context) (workqueue.RateLimitingInterface, []cache.InformerSynced, error) { - c.gatewayLister = ctx.GWShared.Gateway().V1alpha2().Gateways().Lister() +func (c *controller) Register(ctx *controllerpkg.Context) (workqueue.TypedRateLimitingInterface[types.NamespacedName], []cache.InformerSynced, error) { + c.gatewayLister = ctx.GWShared.Gateway().V1().Gateways().Lister() log := logf.FromContext(ctx.RootContext, ControllerName) c.sync = shimhelper.SyncFnFor(ctx.Recorder, log, ctx.CMClient, ctx.SharedInformerFactory.Certmanager().V1().Certificates().Lister(), ctx.IngressShimOptions, ctx.FieldManager) // We don't need to requeue Gateways on "Deleted" events, since our Sync // function does nothing when the Gateway lister returns "not found". But we // still do it for consistency with the rest of the controllers. - ctx.GWShared.Gateway().V1alpha2().Gateways().Informer().AddEventHandler(&controllerpkg.QueuingEventHandler{ + if _, err := ctx.GWShared.Gateway().V1().Gateways().Informer().AddEventHandler(&controllerpkg.QueuingEventHandler{ Queue: c.queue, - }) + }); err != nil { + return nil, nil, fmt.Errorf("error setting up event handler: %v", err) + } // Even thought the Gateway controller already re-queues the Gateway after // creating a child Certificate, we still re-queue the Gateway when we @@ -74,35 +70,31 @@ func (c *controller) Register(ctx *controllerpkg.Context) (workqueue.RateLimitin // // Regarding "Deleted" events on Certificates, we requeue the parent Gateway // to immediately recreate the Certificate when the Certificate is deleted. - ctx.SharedInformerFactory.Certmanager().V1().Certificates().Informer().AddEventHandler(&controllerpkg.BlockingEventHandler{ + if _, err := ctx.SharedInformerFactory.Certmanager().V1().Certificates().Informer().AddEventHandler(&controllerpkg.BlockingEventHandler{ WorkFunc: certificateHandler(c.queue), - }) + }); err != nil { + return nil, nil, fmt.Errorf("error setting up event handler: %v", err) + } mustSync := []cache.InformerSynced{ - ctx.GWShared.Gateway().V1alpha2().Gateways().Informer().HasSynced, + ctx.GWShared.Gateway().V1().Gateways().Informer().HasSynced, ctx.SharedInformerFactory.Certmanager().V1().Certificates().Informer().HasSynced, } return c.queue, mustSync, nil } -func (c *controller) ProcessItem(ctx context.Context, key string) error { - namespace, name, err := cache.SplitMetaNamespaceKey(key) - if err != nil { - runtime.HandleError(fmt.Errorf("invalid resource key: %s", key)) - return nil - } +func (c *controller) ProcessItem(ctx context.Context, key types.NamespacedName) error { + namespace, name := key.Namespace, key.Name gateway, err := c.gatewayLister.Gateways(namespace).Get(name) - - if err != nil { - if k8sErrors.IsNotFound(err) { - runtime.HandleError(fmt.Errorf("Gateway '%s' in work queue no longer exists", key)) - return nil - } - + if err != nil && !k8sErrors.IsNotFound(err) { return err } + if gateway == nil || gateway.DeletionTimestamp != nil { + // If the Gateway object was/ is being deleted, we don't want to start creating Certificates. + return nil + } return c.sync(ctx, gateway) } @@ -122,7 +114,7 @@ func (c *controller) ProcessItem(ctx context.Context, key string) error { // name: gateway-1 // blockOwnerDeletion: true // uid: 7d3897c2-ce27-4144-883a-e1b5f89bd65a -func certificateHandler(queue workqueue.RateLimitingInterface) func(obj interface{}) { +func certificateHandler(queue workqueue.TypedRateLimitingInterface[types.NamespacedName]) func(obj interface{}) { return func(obj interface{}) { crt, ok := obj.(*cmapi.Certificate) if !ok { @@ -137,21 +129,29 @@ func certificateHandler(queue workqueue.RateLimitingInterface) func(obj interfac return } - // We don't check the apiVersion e.g. "networking.x-k8s.io/v1alpha1" + // We don't check the apiVersion, e.g., "networking.x-k8s.io/v1alpha1" // because there is no chance that another object called "Gateway" be // the controller of a Certificate. if ref.Kind != "Gateway" { return } - queue.Add(crt.Namespace + "/" + ref.Name) + queue.Add(types.NamespacedName{ + Namespace: crt.Namespace, + Name: ref.Name, + }) } } func init() { controllerpkg.Register(ControllerName, func(ctx *controllerpkg.ContextFactory) (controllerpkg.Interface, error) { return controllerpkg.NewBuilder(ctx, ControllerName). - For(&controller{queue: workqueue.NewNamedRateLimitingQueue(controllerpkg.DefaultItemBasedRateLimiter(), ControllerName)}). + For(&controller{queue: workqueue.NewTypedRateLimitingQueueWithConfig( + controllerpkg.DefaultItemBasedRateLimiter(), + workqueue.TypedRateLimitingQueueConfig[types.NamespacedName]{ + Name: ControllerName, + }, + )}). Complete() }) } diff --git a/pkg/controller/certificate-shim/gateways/controller_test.go b/pkg/controller/certificate-shim/gateways/controller_test.go index 397427641b5..dae0e0e89bb 100644 --- a/pkg/controller/certificate-shim/gateways/controller_test.go +++ b/pkg/controller/certificate-shim/gateways/controller_test.go @@ -17,21 +17,21 @@ limitations under the License. package controller import ( - "context" "testing" "time" - testpkg "github.com/cert-manager/cert-manager/pkg/controller/test" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" "k8s.io/client-go/util/workqueue" - gwapi "sigs.k8s.io/gateway-api/apis/v1alpha2" + gwapi "sigs.k8s.io/gateway-api/apis/v1" gwclient "sigs.k8s.io/gateway-api/pkg/client/clientset/versioned" cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" cmclient "github.com/cert-manager/cert-manager/pkg/client/clientset/versioned" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" + testpkg "github.com/cert-manager/cert-manager/pkg/controller/test" ) var gatewayGVK = gwapi.SchemeGroupVersion.WithKind("Gateway") @@ -41,17 +41,22 @@ func Test_controller_Register(t *testing.T) { name string existingCert *cmapi.Certificate givenCall func(*testing.T, cmclient.Interface, gwclient.Interface) - expectAddCalls []interface{} + expectAddCalls []types.NamespacedName }{ { name: "gateway is re-queued when an 'Added' event is received for this gateway", givenCall: func(t *testing.T, _ cmclient.Interface, c gwclient.Interface) { - _, err := c.GatewayV1alpha2().Gateways("namespace-1").Create(context.Background(), &gwapi.Gateway{ObjectMeta: metav1.ObjectMeta{ + _, err := c.GatewayV1().Gateways("namespace-1").Create(t.Context(), &gwapi.Gateway{ObjectMeta: metav1.ObjectMeta{ Namespace: "namespace-1", Name: "gateway-1", }}, metav1.CreateOptions{}) require.NoError(t, err) }, - expectAddCalls: []interface{}{"namespace-1/gateway-1"}, + expectAddCalls: []types.NamespacedName{ + { + Namespace: "namespace-1", + Name: "gateway-1", + }, + }, }, { name: "gateway is re-queued when an 'Updated' event is received for this gateway", @@ -59,37 +64,57 @@ func Test_controller_Register(t *testing.T) { // We can't use the gateway-api fake.NewSimpleClientset due to // Gateway being pluralized as "gatewaies" instead of // "gateways". The trick is thus to use Create instead. - _, err := c.GatewayV1alpha2().Gateways("namespace-1").Create(context.Background(), &gwapi.Gateway{ObjectMeta: metav1.ObjectMeta{ + _, err := c.GatewayV1().Gateways("namespace-1").Create(t.Context(), &gwapi.Gateway{ObjectMeta: metav1.ObjectMeta{ Namespace: "namespace-1", Name: "gateway-1", }}, metav1.CreateOptions{}) require.NoError(t, err) - _, err = c.GatewayV1alpha2().Gateways("namespace-1").Update(context.Background(), &gwapi.Gateway{ObjectMeta: metav1.ObjectMeta{ + _, err = c.GatewayV1().Gateways("namespace-1").Update(t.Context(), &gwapi.Gateway{ObjectMeta: metav1.ObjectMeta{ Namespace: "namespace-1", Name: "gateway-1", Labels: map[string]string{"foo": "bar"}, }}, metav1.UpdateOptions{}) require.NoError(t, err) }, - expectAddCalls: []interface{}{"namespace-1/gateway-1", "namespace-1/gateway-1"}, - // <----- Create ------> <------ Update -----> + expectAddCalls: []types.NamespacedName{ + // Create + { + Namespace: "namespace-1", + Name: "gateway-1", + }, + // Update + { + Namespace: "namespace-1", + Name: "gateway-1", + }, + }, }, { name: "gateway is re-queued when a 'Deleted' event is received for this gateway", givenCall: func(t *testing.T, _ cmclient.Interface, c gwclient.Interface) { - _, err := c.GatewayV1alpha2().Gateways("namespace-1").Create(context.Background(), &gwapi.Gateway{ObjectMeta: metav1.ObjectMeta{ + _, err := c.GatewayV1().Gateways("namespace-1").Create(t.Context(), &gwapi.Gateway{ObjectMeta: metav1.ObjectMeta{ Namespace: "namespace-1", Name: "gateway-1", }}, metav1.CreateOptions{}) require.NoError(t, err) - err = c.GatewayV1alpha2().Gateways("namespace-1").Delete(context.Background(), "gateway-1", metav1.DeleteOptions{}) + err = c.GatewayV1().Gateways("namespace-1").Delete(t.Context(), "gateway-1", metav1.DeleteOptions{}) require.NoError(t, err) }, - expectAddCalls: []interface{}{"namespace-1/gateway-1", "namespace-1/gateway-1"}, - // <----- Create ------> <------ Delete -----> + expectAddCalls: []types.NamespacedName{ + // Create + { + Namespace: "namespace-1", + Name: "gateway-1", + }, + // Delete + { + Namespace: "namespace-1", + Name: "gateway-1", + }, + }, }, { name: "gateway is re-queued when an 'Added' event is received for its child Certificate", givenCall: func(t *testing.T, c cmclient.Interface, _ gwclient.Interface) { - _, err := c.CertmanagerV1().Certificates("namespace-1").Create(context.Background(), &cmapi.Certificate{ObjectMeta: metav1.ObjectMeta{ + _, err := c.CertmanagerV1().Certificates("namespace-1").Create(t.Context(), &cmapi.Certificate{ObjectMeta: metav1.ObjectMeta{ Namespace: "namespace-1", Name: "cert-1", OwnerReferences: []metav1.OwnerReference{*metav1.NewControllerRef(&gwapi.Gateway{ObjectMeta: metav1.ObjectMeta{ Namespace: "namespace-1", Name: "gateway-2", @@ -97,7 +122,12 @@ func Test_controller_Register(t *testing.T) { }}, metav1.CreateOptions{}) require.NoError(t, err) }, - expectAddCalls: []interface{}{"namespace-1/gateway-2"}, + expectAddCalls: []types.NamespacedName{ + { + Namespace: "namespace-1", + Name: "gateway-2", + }, + }, }, { name: "gateway is re-queued when an 'Updated' event is received for its child Certificate", @@ -108,7 +138,7 @@ func Test_controller_Register(t *testing.T) { }}, gatewayGVK)}, }}, givenCall: func(t *testing.T, c cmclient.Interface, _ gwclient.Interface) { - _, err := c.CertmanagerV1().Certificates("namespace-1").Update(context.Background(), &cmapi.Certificate{ObjectMeta: metav1.ObjectMeta{ + _, err := c.CertmanagerV1().Certificates("namespace-1").Update(t.Context(), &cmapi.Certificate{ObjectMeta: metav1.ObjectMeta{ Namespace: "namespace-1", Name: "cert-1", OwnerReferences: []metav1.OwnerReference{*metav1.NewControllerRef(&gwapi.Gateway{ObjectMeta: metav1.ObjectMeta{ Namespace: "namespace-1", Name: "gateway-2", @@ -116,7 +146,12 @@ func Test_controller_Register(t *testing.T) { }}, metav1.UpdateOptions{}) require.NoError(t, err) }, - expectAddCalls: []interface{}{"namespace-1/gateway-2"}, + expectAddCalls: []types.NamespacedName{ + { + Namespace: "namespace-1", + Name: "gateway-2", + }, + }, }, { name: "gateway is re-queued when a 'Deleted' event is received for its child Certificate", @@ -127,10 +162,15 @@ func Test_controller_Register(t *testing.T) { }}, gatewayGVK)}, }}, givenCall: func(t *testing.T, c cmclient.Interface, _ gwclient.Interface) { - // err := c.CertmanagerV1().Certificates("namespace-1").Delete(context.Background(), "cert-1", metav1.DeleteOptions{}) + // err := c.CertmanagerV1().Certificates("namespace-1").Delete(t.Context(), "cert-1", metav1.DeleteOptions{}) // require.NoError(t, err) }, - expectAddCalls: []interface{}{"namespace-1/gateway-2"}, + expectAddCalls: []types.NamespacedName{ + { + Namespace: "namespace-1", + Name: "gateway-2", + }, + }, }, } @@ -181,34 +221,34 @@ func Test_controller_Register(t *testing.T) { type mockWorkqueue struct { t *testing.T - callsToAdd []interface{} + callsToAdd []types.NamespacedName } -var _ workqueue.Interface = &mockWorkqueue{} +var _ workqueue.TypedInterface[types.NamespacedName] = &mockWorkqueue{} -func (m *mockWorkqueue) Add(arg0 interface{}) { +func (m *mockWorkqueue) Add(arg0 types.NamespacedName) { m.callsToAdd = append(m.callsToAdd, arg0) } -func (m *mockWorkqueue) AddAfter(arg0 interface{}, arg1 time.Duration) { +func (m *mockWorkqueue) AddAfter(arg0 types.NamespacedName, arg1 time.Duration) { m.t.Error("workqueue.AddAfter was called but was not expected to be called") } -func (m *mockWorkqueue) AddRateLimited(arg0 interface{}) { +func (m *mockWorkqueue) AddRateLimited(arg0 types.NamespacedName) { m.t.Error("workqueue.AddRateLimited was called but was not expected to be called") } -func (m *mockWorkqueue) Done(arg0 interface{}) { +func (m *mockWorkqueue) Done(arg0 types.NamespacedName) { m.t.Error("workqueue.Done was called but was not expected to be called") } -func (m *mockWorkqueue) Forget(arg0 interface{}) { +func (m *mockWorkqueue) Forget(arg0 types.NamespacedName) { m.t.Error("workqueue.Forget was called but was not expected to be called") } -func (m *mockWorkqueue) Get() (interface{}, bool) { +func (m *mockWorkqueue) Get() (types.NamespacedName, bool) { m.t.Error("workqueue.Get was called but was not expected to be called") - return nil, false + return types.NamespacedName{}, false } func (m *mockWorkqueue) Len() int { @@ -216,7 +256,7 @@ func (m *mockWorkqueue) Len() int { return 0 } -func (m *mockWorkqueue) NumRequeues(arg0 interface{}) int { +func (m *mockWorkqueue) NumRequeues(arg0 types.NamespacedName) int { m.t.Error("workqueue.NumRequeues was called but was not expected to be called") return 0 } diff --git a/pkg/controller/certificate-shim/helper.go b/pkg/controller/certificate-shim/helper.go index 994cadd457b..b2e1ee05e1b 100644 --- a/pkg/controller/certificate-shim/helper.go +++ b/pkg/controller/certificate-shim/helper.go @@ -17,17 +17,21 @@ limitations under the License. package shimhelper import ( + "encoding/json" "errors" "fmt" + "reflect" "strconv" "strings" "time" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/utils/pointer" + "k8s.io/utils/ptr" apiutil "github.com/cert-manager/cert-manager/pkg/api/util" cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" + "github.com/cert-manager/cert-manager/pkg/util" + "github.com/cert-manager/cert-manager/pkg/util/pki" ) var ( @@ -67,6 +71,83 @@ func translateAnnotations(crt *cmapi.Certificate, ingLikeAnnotations map[string] crt.Spec.CommonName = commonName } + if emailAddresses, found := ingLikeAnnotations[cmapi.EmailsAnnotationKey]; found { + crt.Spec.EmailAddresses = strings.Split(emailAddresses, ",") + } + + subject := &cmapi.X509Subject{} + if organizations, found := ingLikeAnnotations[cmapi.SubjectOrganizationsAnnotationKey]; found { + organizations, err := util.SplitWithEscapeCSV(organizations) + subject.Organizations = organizations + + if err != nil { + return fmt.Errorf("%w %q: %v", errInvalidIngressAnnotation, cmapi.SubjectOrganizationsAnnotationKey, err) + } + } + + if organizationalUnits, found := ingLikeAnnotations[cmapi.SubjectOrganizationalUnitsAnnotationKey]; found { + organizationalUnits, err := util.SplitWithEscapeCSV(organizationalUnits) + subject.OrganizationalUnits = organizationalUnits + + if err != nil { + return fmt.Errorf("%w %q: %v", errInvalidIngressAnnotation, cmapi.SubjectOrganizationsAnnotationKey, err) + } + } + + if countries, found := ingLikeAnnotations[cmapi.SubjectCountriesAnnotationKey]; found { + countries, err := util.SplitWithEscapeCSV(countries) + subject.Countries = countries + + if err != nil { + return fmt.Errorf("%w %q: %v", errInvalidIngressAnnotation, cmapi.SubjectCountriesAnnotationKey, err) + } + } + + if provinces, found := ingLikeAnnotations[cmapi.SubjectProvincesAnnotationKey]; found { + provinces, err := util.SplitWithEscapeCSV(provinces) + subject.Provinces = provinces + + if err != nil { + return fmt.Errorf("%w %q: %v", errInvalidIngressAnnotation, cmapi.SubjectProvincesAnnotationKey, err) + } + } + + if localities, found := ingLikeAnnotations[cmapi.SubjectLocalitiesAnnotationKey]; found { + localities, err := util.SplitWithEscapeCSV(localities) + subject.Localities = localities + + if err != nil { + return fmt.Errorf("%w %q: %v", errInvalidIngressAnnotation, cmapi.SubjectLocalitiesAnnotationKey, err) + } + } + + if postalCodes, found := ingLikeAnnotations[cmapi.SubjectPostalCodesAnnotationKey]; found { + postalCodes, err := util.SplitWithEscapeCSV(postalCodes) + subject.PostalCodes = postalCodes + + if err != nil { + return fmt.Errorf("%w %q: %v", errInvalidIngressAnnotation, cmapi.SubjectPostalCodesAnnotationKey, err) + } + } + + if streetAddresses, found := ingLikeAnnotations[cmapi.SubjectStreetAddressesAnnotationKey]; found { + streetAddresses, err := util.SplitWithEscapeCSV(streetAddresses) + subject.StreetAddresses = streetAddresses + + if err != nil { + return fmt.Errorf("%w %q: %v", errInvalidIngressAnnotation, cmapi.SubjectStreetAddressesAnnotationKey, err) + } + } + + if serialNumber, found := ingLikeAnnotations[cmapi.SubjectSerialNumberAnnotationKey]; found { + subject.SerialNumber = serialNumber + } + + emptySubject := &cmapi.X509Subject{} + if !reflect.DeepEqual(emptySubject, subject) { + crt.Spec.Subject = subject + } + if duration, found := ingLikeAnnotations[cmapi.DurationAnnotationKey]; found { duration, err := time.ParseDuration(duration) if err != nil { @@ -83,6 +164,14 @@ func translateAnnotations(crt *cmapi.Certificate, ingLikeAnnotations map[string] crt.Spec.RenewBefore = &metav1.Duration{Duration: duration} } + if renewBeforePercentage, found := ingLikeAnnotations[cmapi.RenewBeforePercentageAnnotationKey]; found { + pct, err := strconv.ParseInt(renewBeforePercentage, 10, 32) + if err != nil { + return fmt.Errorf("%w %q: %v", errInvalidIngressAnnotation, cmapi.RenewBeforePercentageAnnotationKey, err) + } + crt.Spec.RenewBeforePercentage = ptr.To(int32(pct)) + } + if usages, found := ingLikeAnnotations[cmapi.UsagesAnnotationKey]; found { var newUsages []cmapi.KeyUsage for _, usageName := range strings.Split(usages, ",") { @@ -107,7 +196,7 @@ func translateAnnotations(crt *cmapi.Certificate, ingLikeAnnotations map[string] return fmt.Errorf("%w %q: revision history limit must be a positive number %q", errInvalidIngressAnnotation, cmapi.RevisionHistoryLimitAnnotationKey, revisionHistoryLimit) } - crt.Spec.RevisionHistoryLimit = pointer.Int32(int32(limit)) + crt.Spec.RevisionHistoryLimit = ptr.To(int32(limit)) } if privateKeyAlgorithm, found := ingLikeAnnotations[cmapi.PrivateKeyAlgorithmAnnotationKey]; found { @@ -156,7 +245,7 @@ func translateAnnotations(crt *cmapi.Certificate, ingLikeAnnotations map[string] switch algorithm { case cmapi.RSAKeyAlgorithm: - if size < 2048 || size > 8192 { + if size < pki.MinRSAKeySize || size > pki.MaxRSAKeySize { return fmt.Errorf("%w %q: invalid private key size for RSA algorithm %q", errInvalidIngressAnnotation, cmapi.PrivateKeySizeAnnotationKey, privateKeySize) } case cmapi.ECDSAKeyAlgorithm: @@ -166,6 +255,8 @@ func translateAnnotations(crt *cmapi.Certificate, ingLikeAnnotations map[string] default: return fmt.Errorf("%w %q: invalid private key size for ECDSA algorithm %q", errInvalidIngressAnnotation, cmapi.PrivateKeySizeAnnotationKey, privateKeySize) } + default: + // ok } if crt.Spec.PrivateKey == nil { @@ -189,5 +280,23 @@ func translateAnnotations(crt *cmapi.Certificate, ingLikeAnnotations map[string] } } + if secretTemplateJson, found := ingLikeAnnotations[cmapi.IngressSecretTemplate]; found { + decoder := json.NewDecoder(strings.NewReader(secretTemplateJson)) + decoder.DisallowUnknownFields() + + var secretTemplate = new(cmapi.CertificateSecretTemplate) + if err := decoder.Decode(secretTemplate); err != nil { + return fmt.Errorf("%w %q: error parsing secret template JSON: %v", errInvalidIngressAnnotation, cmapi.IngressSecretTemplate, err) + } + for annotationKey := range secretTemplate.Annotations { + if strings.HasPrefix(annotationKey, "cert-manager.io/") { + return fmt.Errorf("%w %q: secretTemplate must not have cert-manager.io/ annotations: %q", errInvalidIngressAnnotation, cmapi.IngressSecretTemplate, annotationKey) + } + } + if len(secretTemplate.Annotations) > 0 || len(secretTemplate.Labels) > 0 { + crt.Spec.SecretTemplate = secretTemplate + } + } + return nil } diff --git a/pkg/controller/certificate-shim/helper_test.go b/pkg/controller/certificate-shim/helper_test.go index fdf28bd0a3c..f42b8f8cb9b 100644 --- a/pkg/controller/certificate-shim/helper_test.go +++ b/pkg/controller/certificate-shim/helper_test.go @@ -23,9 +23,10 @@ import ( "github.com/stretchr/testify/assert" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/utils/pointer" + "k8s.io/utils/ptr" cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" + cmutil "github.com/cert-manager/cert-manager/pkg/util" "github.com/cert-manager/cert-manager/test/unit/gen" ) @@ -40,11 +41,20 @@ func Test_translateAnnotations(t *testing.T) { validAnnotations := func() map[string]string { return map[string]string{ - cmapi.CommonNameAnnotationKey: "www.example.com", - cmapi.DurationAnnotationKey: "168h", // 1 week - cmapi.RenewBeforeAnnotationKey: "24h", - cmapi.UsagesAnnotationKey: "server auth,signing", - cmapi.RevisionHistoryLimitAnnotationKey: "7", + cmapi.CommonNameAnnotationKey: "www.example.com", + cmapi.DurationAnnotationKey: "168h", // 1 week + cmapi.RenewBeforeAnnotationKey: "24h", + cmapi.UsagesAnnotationKey: "server auth,signing", + cmapi.RevisionHistoryLimitAnnotationKey: "7", + cmapi.EmailsAnnotationKey: "test@example.com", + cmapi.SubjectOrganizationsAnnotationKey: "Test Organization", + cmapi.SubjectOrganizationalUnitsAnnotationKey: "Test Organizational Unit", + cmapi.SubjectCountriesAnnotationKey: "Country", + cmapi.SubjectProvincesAnnotationKey: "Province", + cmapi.SubjectLocalitiesAnnotationKey: "City", + cmapi.SubjectStreetAddressesAnnotationKey: `"1725 Slough Avenue, Suite 200, Scranton Business Park","1800 Slough Avenue, Suite 200, Scranton Business Park"`, + cmapi.SubjectPostalCodesAnnotationKey: "ABC123", + cmapi.SubjectSerialNumberAnnotationKey: "123456", } } @@ -57,7 +67,39 @@ func Test_translateAnnotations(t *testing.T) { a.Equal(&metav1.Duration{Duration: time.Hour * 24 * 7}, crt.Spec.Duration) a.Equal(&metav1.Duration{Duration: time.Hour * 24}, crt.Spec.RenewBefore) a.Equal([]cmapi.KeyUsage{cmapi.UsageServerAuth, cmapi.UsageSigning}, crt.Spec.Usages) - a.Equal(pointer.Int32(7), crt.Spec.RevisionHistoryLimit) + a.Equal(ptr.To(int32(7)), crt.Spec.RevisionHistoryLimit) + a.Equal("123456", crt.Spec.Subject.SerialNumber) + + splitAddresses, splitErr := cmutil.SplitWithEscapeCSV(`"1725 Slough Avenue, Suite 200, Scranton Business Park","1800 Slough Avenue, Suite 200, Scranton Business Park"`) + a.Equal(nil, splitErr) + a.Equal(splitAddresses, crt.Spec.Subject.StreetAddresses) + + joinedAddresses, joinErr := cmutil.JoinWithEscapeCSV(crt.Spec.Subject.StreetAddresses) + a.Equal(nil, joinErr) + a.Equal(`"1725 Slough Avenue, Suite 200, Scranton Business Park","1800 Slough Avenue, Suite 200, Scranton Business Park"`, joinedAddresses) + }, + }, + "success renew before pct": { + crt: gen.Certificate("example-cert"), + annotations: map[string]string{ + cmapi.CommonNameAnnotationKey: "www.example.com", + cmapi.DurationAnnotationKey: "168h", // 1 week + cmapi.RenewBeforePercentageAnnotationKey: "50", + cmapi.UsagesAnnotationKey: "server auth,signing", + cmapi.PrivateKeyAlgorithmAnnotationKey: "RSA", + cmapi.PrivateKeyEncodingAnnotationKey: "PKCS1", + cmapi.PrivateKeySizeAnnotationKey: "2048", + cmapi.PrivateKeyRotationPolicyAnnotationKey: "Always", + }, + check: func(a *assert.Assertions, crt *cmapi.Certificate) { + a.Equal("www.example.com", crt.Spec.CommonName) + a.Equal(&metav1.Duration{Duration: time.Hour * 24 * 7}, crt.Spec.Duration) + a.Equal(ptr.To(int32(50)), crt.Spec.RenewBeforePercentage) + a.Equal([]cmapi.KeyUsage{cmapi.UsageServerAuth, cmapi.UsageSigning}, crt.Spec.Usages) + a.Equal(cmapi.RSAKeyAlgorithm, crt.Spec.PrivateKey.Algorithm) + a.Equal(cmapi.PKCS1, crt.Spec.PrivateKey.Encoding) + a.Equal(2048, crt.Spec.PrivateKey.Size) + a.Equal(cmapi.RotationPolicyAlways, crt.Spec.PrivateKey.RotationPolicy) }, }, "success rsa private key algorithm": { @@ -156,6 +198,14 @@ func Test_translateAnnotations(t *testing.T) { }, expectedError: errInvalidIngressAnnotation, }, + "bad renewBeforePercentage": { + crt: gen.Certificate("example-cert"), + annotations: validAnnotations(), + mutate: func(tc *testCase) { + tc.annotations[cmapi.RenewBeforePercentageAnnotationKey] = "an un-parsable integer" + }, + expectedError: errInvalidIngressAnnotation, + }, "bad usages": { crt: gen.Certificate("example-cert"), annotations: validAnnotations(), @@ -246,6 +296,14 @@ func Test_translateAnnotations(t *testing.T) { }, expectedError: errInvalidIngressAnnotation, }, + "bad street addresses": { + crt: gen.Certificate("example-cert"), + annotations: validAnnotations(), + mutate: func(tc *testCase) { + tc.annotations[cmapi.SubjectStreetAddressesAnnotationKey] = "invalid csv\"," + }, + expectedError: errInvalidIngressAnnotation, + }, } for name, tc := range tests { t.Run(name, func(t *testing.T) { diff --git a/pkg/controller/certificate-shim/ingresses/controller.go b/pkg/controller/certificate-shim/ingresses/controller.go index 728227cb188..a369ab02246 100644 --- a/pkg/controller/certificate-shim/ingresses/controller.go +++ b/pkg/controller/certificate-shim/ingresses/controller.go @@ -22,6 +22,7 @@ import ( k8sErrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/runtime" networkingv1listers "k8s.io/client-go/listers/networking/v1" "k8s.io/client-go/tools/cache" @@ -42,16 +43,21 @@ type controller struct { sync shimhelper.SyncFn } -func (c *controller) Register(ctx *controllerpkg.Context) (workqueue.RateLimitingInterface, []cache.InformerSynced, error) { +func (c *controller) Register(ctx *controllerpkg.Context) (workqueue.TypedRateLimitingInterface[types.NamespacedName], []cache.InformerSynced, error) { cmShared := ctx.SharedInformerFactory - ingressInformer := ctx.KubeSharedInformerFactory.Networking().V1().Ingresses() + ingressInformer := ctx.KubeSharedInformerFactory.Ingresses() c.ingressLister = ingressInformer.Lister() log := logf.FromContext(ctx.RootContext, ControllerName) c.sync = shimhelper.SyncFnFor(ctx.Recorder, log, ctx.CMClient, cmShared.Certmanager().V1().Certificates().Lister(), ctx.IngressShimOptions, ctx.FieldManager) - queue := workqueue.NewNamedRateLimitingQueue(controllerpkg.DefaultItemBasedRateLimiter(), ControllerName) + queue := workqueue.NewTypedRateLimitingQueueWithConfig( + controllerpkg.DefaultItemBasedRateLimiter(), + workqueue.TypedRateLimitingQueueConfig[types.NamespacedName]{ + Name: ControllerName, + }, + ) mustSync := []cache.InformerSynced{ ingressInformer.Informer().HasSynced, @@ -64,9 +70,11 @@ func (c *controller) Register(ctx *controllerpkg.Context) (workqueue.RateLimitin // to do some cleanup, we would use a finalizer, and the cleanup logic would // be triggered by the "Updated" event when the object gets marked for // deletion. - ingressInformer.Informer().AddEventHandler(&controllerpkg.QueuingEventHandler{ + if _, err := ingressInformer.Informer().AddEventHandler(&controllerpkg.QueuingEventHandler{ Queue: queue, - }) + }); err != nil { + return nil, nil, fmt.Errorf("error setting up event handler: %v", err) + } // We still re-queue on "Add" because the workqueue will remove any // duplicate key, although the Ingress controller already re-queues the @@ -77,30 +85,26 @@ func (c *controller) Register(ctx *controllerpkg.Context) (workqueue.RateLimitin // // We want to immediately recreate a Certificate when the Certificate is // deleted. - cmShared.Certmanager().V1().Certificates().Informer().AddEventHandler(&controllerpkg.BlockingEventHandler{ + if _, err := cmShared.Certmanager().V1().Certificates().Informer().AddEventHandler(&controllerpkg.BlockingEventHandler{ WorkFunc: certificateHandler(queue), - }) + }); err != nil { + return nil, nil, fmt.Errorf("error setting up event handler: %v", err) + } return queue, mustSync, nil } -func (c *controller) ProcessItem(ctx context.Context, key string) error { - namespace, name, err := cache.SplitMetaNamespaceKey(key) - if err != nil { - runtime.HandleError(fmt.Errorf("invalid resource key: %s", key)) - return nil - } +func (c *controller) ProcessItem(ctx context.Context, key types.NamespacedName) error { + namespace, name := key.Namespace, key.Name ingress, err := c.ingressLister.Ingresses(namespace).Get(name) - - if err != nil { - if k8sErrors.IsNotFound(err) { - runtime.HandleError(fmt.Errorf("ingress '%s' in work queue no longer exists", key)) - return nil - } - + if err != nil && !k8sErrors.IsNotFound(err) { return err } + if ingress == nil || ingress.DeletionTimestamp != nil { + // If the Ingress object was/ is being deleted, we don't want to start creating Certificates. + return nil + } return c.sync(ctx, ingress) } @@ -120,7 +124,7 @@ func (c *controller) ProcessItem(ctx context.Context, key string) error { // name: ingress-1 // blockOwnerDeletion: true // uid: 7d3897c2-ce27-4144-883a-e1b5f89bd65a -func certificateHandler(queue workqueue.RateLimitingInterface) func(obj interface{}) { +func certificateHandler(queue workqueue.TypedRateLimitingInterface[types.NamespacedName]) func(obj interface{}) { return func(obj interface{}) { cert, ok := obj.(*cmapi.Certificate) if !ok { @@ -139,7 +143,10 @@ func certificateHandler(queue workqueue.RateLimitingInterface) func(obj interfac return } - queue.Add(cert.Namespace + "/" + ingress.Name) + queue.Add(types.NamespacedName{ + Namespace: cert.Namespace, + Name: ingress.Name, + }) } } diff --git a/pkg/controller/certificate-shim/ingresses/controller_test.go b/pkg/controller/certificate-shim/ingresses/controller_test.go index eff4a59f62b..627086bdce7 100644 --- a/pkg/controller/certificate-shim/ingresses/controller_test.go +++ b/pkg/controller/certificate-shim/ingresses/controller_test.go @@ -17,20 +17,20 @@ limitations under the License. package controller import ( - "context" "testing" "time" - testpkg "github.com/cert-manager/cert-manager/pkg/controller/test" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" networkingv1 "k8s.io/api/networking/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" kclient "k8s.io/client-go/kubernetes" cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" cmclient "github.com/cert-manager/cert-manager/pkg/client/clientset/versioned" + testpkg "github.com/cert-manager/cert-manager/pkg/controller/test" ) var ingressGVK = networkingv1.SchemeGroupVersion.WithKind("Ingress") @@ -41,17 +41,20 @@ func Test_controller_Register(t *testing.T) { existingKObjects []runtime.Object existingCMObjects []runtime.Object givenCall func(*testing.T, cmclient.Interface, kclient.Interface) - expectRequeueKey string + expectRequeueKey types.NamespacedName }{ { name: "ingress is re-queued when an 'Added' event is received for this ingress", givenCall: func(t *testing.T, _ cmclient.Interface, c kclient.Interface) { - _, err := c.NetworkingV1().Ingresses("namespace-1").Create(context.Background(), &networkingv1.Ingress{ObjectMeta: metav1.ObjectMeta{ + _, err := c.NetworkingV1().Ingresses("namespace-1").Create(t.Context(), &networkingv1.Ingress{ObjectMeta: metav1.ObjectMeta{ Namespace: "namespace-1", Name: "ingress-1", }}, metav1.CreateOptions{}) require.NoError(t, err) }, - expectRequeueKey: "namespace-1/ingress-1", + expectRequeueKey: types.NamespacedName{ + Namespace: "namespace-1", + Name: "ingress-1", + }, }, { name: "ingress is re-queued when an 'Updated' event is received for this ingress", @@ -59,12 +62,15 @@ func Test_controller_Register(t *testing.T) { Namespace: "namespace-1", Name: "ingress-1", }}}, givenCall: func(t *testing.T, _ cmclient.Interface, c kclient.Interface) { - _, err := c.NetworkingV1().Ingresses("namespace-1").Update(context.Background(), &networkingv1.Ingress{ObjectMeta: metav1.ObjectMeta{ + _, err := c.NetworkingV1().Ingresses("namespace-1").Update(t.Context(), &networkingv1.Ingress{ObjectMeta: metav1.ObjectMeta{ Namespace: "namespace-1", Name: "ingress-1", }}, metav1.UpdateOptions{}) require.NoError(t, err) }, - expectRequeueKey: "namespace-1/ingress-1", + expectRequeueKey: types.NamespacedName{ + Namespace: "namespace-1", + Name: "ingress-1", + }, }, { name: "ingress is re-queued when a 'Deleted' event is received for this ingress", @@ -72,15 +78,18 @@ func Test_controller_Register(t *testing.T) { Namespace: "namespace-1", Name: "ingress-1", }}}, givenCall: func(t *testing.T, _ cmclient.Interface, c kclient.Interface) { - err := c.NetworkingV1().Ingresses("namespace-1").Delete(context.Background(), "ingress-1", metav1.DeleteOptions{}) + err := c.NetworkingV1().Ingresses("namespace-1").Delete(t.Context(), "ingress-1", metav1.DeleteOptions{}) require.NoError(t, err) }, - expectRequeueKey: "namespace-1/ingress-1", + expectRequeueKey: types.NamespacedName{ + Namespace: "namespace-1", + Name: "ingress-1", + }, }, { name: "ingress is re-queued when an 'Added' event is received for its child Certificate", givenCall: func(t *testing.T, c cmclient.Interface, _ kclient.Interface) { - _, err := c.CertmanagerV1().Certificates("namespace-1").Create(context.Background(), &cmapi.Certificate{ObjectMeta: metav1.ObjectMeta{ + _, err := c.CertmanagerV1().Certificates("namespace-1").Create(t.Context(), &cmapi.Certificate{ObjectMeta: metav1.ObjectMeta{ Namespace: "namespace-1", Name: "cert-1", OwnerReferences: []metav1.OwnerReference{*metav1.NewControllerRef(&networkingv1.Ingress{ObjectMeta: metav1.ObjectMeta{ Namespace: "namespace-1", Name: "ingress-2", @@ -88,7 +97,10 @@ func Test_controller_Register(t *testing.T) { }}, metav1.CreateOptions{}) require.NoError(t, err) }, - expectRequeueKey: "namespace-1/ingress-2", + expectRequeueKey: types.NamespacedName{ + Namespace: "namespace-1", + Name: "ingress-2", + }, }, { name: "ingress is re-queued when an 'Updated' event is received for its child Certificate", @@ -99,7 +111,7 @@ func Test_controller_Register(t *testing.T) { }}, ingressGVK)}, }}}, givenCall: func(t *testing.T, c cmclient.Interface, _ kclient.Interface) { - _, err := c.CertmanagerV1().Certificates("namespace-1").Update(context.Background(), &cmapi.Certificate{ObjectMeta: metav1.ObjectMeta{ + _, err := c.CertmanagerV1().Certificates("namespace-1").Update(t.Context(), &cmapi.Certificate{ObjectMeta: metav1.ObjectMeta{ Namespace: "namespace-1", Name: "cert-1", OwnerReferences: []metav1.OwnerReference{*metav1.NewControllerRef(&networkingv1.Ingress{ObjectMeta: metav1.ObjectMeta{ Namespace: "namespace-1", Name: "ingress-2", @@ -107,7 +119,10 @@ func Test_controller_Register(t *testing.T) { }}, metav1.UpdateOptions{}) require.NoError(t, err) }, - expectRequeueKey: "namespace-1/ingress-2", + expectRequeueKey: types.NamespacedName{ + Namespace: "namespace-1", + Name: "ingress-2", + }, }, { name: "ingress is re-queued when a 'Deleted' event is received for its child Certificate", @@ -118,10 +133,13 @@ func Test_controller_Register(t *testing.T) { }}, ingressGVK)}, }}}, givenCall: func(t *testing.T, c cmclient.Interface, _ kclient.Interface) { - err := c.CertmanagerV1().Certificates("namespace-1").Delete(context.Background(), "cert-1", metav1.DeleteOptions{}) + err := c.CertmanagerV1().Certificates("namespace-1").Delete(t.Context(), "cert-1", metav1.DeleteOptions{}) require.NoError(t, err) }, - expectRequeueKey: "namespace-1/ingress-2", + expectRequeueKey: types.NamespacedName{ + Namespace: "namespace-1", + Name: "ingress-2", + }, }, } @@ -149,7 +167,7 @@ func Test_controller_Register(t *testing.T) { // to be nil. time.AfterFunc(50*time.Millisecond, queue.ShutDown) - var gotKeys []string + var gotKeys []types.NamespacedName for { // Get blocks until either (1) a key is returned, or (2) the // queue is shut down. @@ -157,13 +175,13 @@ func Test_controller_Register(t *testing.T) { if done { break } - gotKeys = append(gotKeys, gotKey.(string)) + gotKeys = append(gotKeys, gotKey) } assert.Equal(t, 0, queue.Len(), "queue should be empty") // We only expect 0 or 1 keys received in the queue. - if test.expectRequeueKey != "" { - assert.Equal(t, []string{test.expectRequeueKey}, gotKeys) + if test.expectRequeueKey != (types.NamespacedName{}) { + assert.Equal(t, []types.NamespacedName{test.expectRequeueKey}, gotKeys) } else { assert.Nil(t, gotKeys) } diff --git a/pkg/controller/certificate-shim/sync.go b/pkg/controller/certificate-shim/sync.go index 112b7d8f463..408ffba3921 100644 --- a/pkg/controller/certificate-shim/sync.go +++ b/pkg/controller/certificate-shim/sync.go @@ -20,6 +20,8 @@ import ( "context" "errors" "fmt" + "maps" + "net" "reflect" "strconv" "strings" @@ -34,7 +36,7 @@ import ( "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/util/validation/field" "k8s.io/client-go/tools/record" - gwapi "sigs.k8s.io/gateway-api/apis/v1alpha2" + gwapi "sigs.k8s.io/gateway-api/apis/v1" internalcertificates "github.com/cert-manager/cert-manager/internal/controller/certificates" "github.com/cert-manager/cert-manager/internal/controller/feature" @@ -55,6 +57,8 @@ const ( reasonDeleteCertificate = "DeleteCertificate" ) +const applysetLabel = "applyset.kubernetes.io/part-of" + var ingressV1GVK = networkingv1.SchemeGroupVersion.WithKind("Ingress") var gatewayGVK = gwapi.SchemeGroupVersion.WithKind("Gateway") @@ -91,14 +95,18 @@ func SyncFnFor( // "kubernetes.io/tls-acme" annotation are only enabled for the Ingress // resource. var autoAnnotations []string - switch ingLike.(type) { - case *networkingv1.Ingress: + if _, ok := ingLike.(*networkingv1.Ingress); ok { autoAnnotations = defaults.DefaultAutoCertificateAnnotations } if !hasShimAnnotation(ingLike, autoAnnotations) { - logf.V(logf.DebugLevel).Infof("not syncing ingress resource as it does not contain a %q or %q annotation", - cmapi.IngressIssuerNameAnnotationKey, cmapi.IngressClusterIssuerNameAnnotationKey) + log.V(logf.DebugLevel).Info("not syncing ingress resource", + "reason", fmt.Sprintf("it does not contain a %q or %q annotation", cmapi.IngressIssuerNameAnnotationKey, cmapi.IngressClusterIssuerNameAnnotationKey)) + return nil + } + + if isDeletedInForeground(ingLike) { + log.V(logf.DebugLevel).Info("not syncing ingress resource", "reason", "it is being deleted via foreground cascading") return nil } @@ -116,7 +124,8 @@ func SyncFnFor( return nil } - newCrts, updateCrts, err := buildCertificates(rec, log, cmLister, ingLike, issuerName, issuerKind, issuerGroup) + extraAnnotations := extractExtraAnnotations(ingLike, defaults.ExtraCertificateAnnotations) + newCrts, updateCrts, err := buildCertificates(rec, log, cmLister, ingLike, issuerName, issuerKind, issuerGroup, extraAnnotations) if err != nil { return err } @@ -138,12 +147,14 @@ func SyncFnFor( Namespace: crt.Namespace, Labels: crt.Labels, OwnerReferences: crt.OwnerReferences, + Annotations: extraAnnotations, }, Spec: cmapi.CertificateSpec{ - DNSNames: crt.Spec.DNSNames, - SecretName: crt.Spec.SecretName, - IssuerRef: crt.Spec.IssuerRef, - Usages: crt.Spec.Usages, + DNSNames: crt.Spec.DNSNames, + IPAddresses: crt.Spec.IPAddresses, + SecretName: crt.Spec.SecretName, + IssuerRef: crt.Spec.IssuerRef, + Usages: crt.Spec.Usages, }, }) } else { @@ -239,7 +250,7 @@ func validateIngressTLSBlock(path *field.Path, tlsBlock networkingv1.IngressTLS) return errs } -func validateGatewayListenerBlock(path *field.Path, l gwapi.Listener) field.ErrorList { +func validateGatewayListenerBlock(path *field.Path, l gwapi.Listener, ingLike metav1.Object) field.ErrorList { var errs field.ErrorList if l.Hostname == nil || *l.Hostname == "" { @@ -266,17 +277,20 @@ func validateGatewayListenerBlock(path *field.Path, l gwapi.Listener) field.Erro errs = append(errs, field.NotSupported(path.Child("tls").Child("certificateRef").Index(i).Child("kind"), *secretRef.Kind, []string{"Secret", ""})) } + + if secretRef.Namespace != nil && string(*secretRef.Namespace) != ingLike.GetNamespace() { + errs = append(errs, field.Invalid(path.Child("tls").Child("certificateRef").Index(i).Child("namespace"), + *secretRef.Namespace, "cross-namespace secret references are not allowed in listeners")) + } } } if l.TLS.Mode == nil { errs = append(errs, field.Required(path.Child("tls").Child("mode"), "the mode field is required")) - } else { - if *l.TLS.Mode != gwapi.TLSModeTerminate { - errs = append(errs, field.NotSupported(path.Child("tls").Child("mode"), - *l.TLS.Mode, []string{string(gwapi.TLSModeTerminate)})) - } + } else if *l.TLS.Mode != gwapi.TLSModeTerminate { + errs = append(errs, field.NotSupported(path.Child("tls").Child("mode"), + *l.TLS.Mode, []string{string(gwapi.TLSModeTerminate)})) } return errs @@ -288,11 +302,8 @@ func buildCertificates( cmLister cmlisters.CertificateLister, ingLike metav1.Object, issuerName, issuerKind, issuerGroup string, -) (new, update []*cmapi.Certificate, _ error) { - - var newCrts []*cmapi.Certificate - var updateCrts []*cmapi.Certificate - + annotations map[string]string, +) (newCrts, updateCrts []*cmapi.Certificate, _ error) { tlsHosts := make(map[corev1.ObjectReference][]string) switch ingLike := ingLike.(type) { case *networkingv1.Ingress: @@ -310,7 +321,17 @@ func buildCertificates( } case *gwapi.Gateway: for i, l := range ingLike.Spec.Listeners { - err := validateGatewayListenerBlock(field.NewPath("spec", "listeners").Index(i), l).ToAggregate() + // TLS is only supported for a limited set of protocol types: https://gateway-api.sigs.k8s.io/guides/tls/#listeners-and-tls + if l.Protocol != gwapi.HTTPSProtocolType && l.Protocol != gwapi.TLSProtocolType { + continue + } + + // We never issue certificates for TLS Passthrough mode + if l.TLS != nil && l.TLS.Mode != nil && *l.TLS.Mode == gwapi.TLSModePassthrough { + continue + } + + err := validateGatewayListenerBlock(field.NewPath("spec", "listeners").Index(i), l, ingLike).ToAggregate() if err != nil { rec.Eventf(ingLike, corev1.EventTypeWarning, reasonBadConfig, "Skipped a listener block: "+err.Error()) continue @@ -327,7 +348,7 @@ func buildCertificates( } // Gateway API hostname explicitly disallows IP addresses, so this // should be OK. - tlsHosts[secretRef] = append(tlsHosts[secretRef], fmt.Sprintf("%s", *l.Hostname)) + tlsHosts[secretRef] = append(tlsHosts[secretRef], string(*l.Hostname)) } } default: @@ -348,17 +369,48 @@ func buildCertificates( controllerGVK = gatewayGVK } + var ( + ipAddress, dnsNames []string + ) + for _, h := range hosts { + if ip := net.ParseIP(h); ip != nil { + ipAddress = append(ipAddress, h) + } else { + dnsNames = append(dnsNames, h) + } + } + + labels := ingLike.GetLabels() + + // Remove applyset labels, as they cause certificates to be + // incorrectly pruned. + // + // See https://github.com/cert-manager/cert-manager/issues/7306 + // + // TODO: Use a more generic annotation on ingress-like resources + // to exclude labels. Something like: + // + // cert-manager.io/exclude-labels: "applyset.kubernetes.io/*,abc.com/efg" + // + // Or, something like: + // + // cert-manager.io/propagate-labels: "false" + // + delete(labels, applysetLabel) + crt := &cmapi.Certificate{ ObjectMeta: metav1.ObjectMeta{ Name: secretRef.Name, Namespace: secretRef.Namespace, - Labels: ingLike.GetLabels(), + Labels: labels, + Annotations: annotations, OwnerReferences: []metav1.OwnerReference{*metav1.NewControllerRef(ingLike, controllerGVK)}, }, Spec: cmapi.CertificateSpec{ - DNSNames: hosts, - SecretName: secretRef.Name, - IssuerRef: cmmeta.ObjectReference{ + DNSNames: dnsNames, + IPAddresses: ipAddress, + SecretName: secretRef.Name, + IssuerRef: cmmeta.IssuerReference{ Name: issuerName, Kind: issuerKind, Group: issuerGroup, @@ -405,6 +457,15 @@ func buildCertificates( updateCrt.Spec = crt.Spec updateCrt.Labels = crt.Labels + // extra annotation wanted but none set + if len(updateCrt.GetAnnotations()) == 0 && len(annotations) > 0 { + updateCrt.SetAnnotations(annotations) + } + // update append extra annotations + if len(updateCrt.GetAnnotations()) > 0 && len(annotations) > 0 { + updateCrt.SetAnnotations(mergeAnnotations(updateCrt.GetAnnotations(), annotations)) + } + setIssuerSpecificConfig(crt, ingLike) updateCrts = append(updateCrts, updateCrt) @@ -429,6 +490,17 @@ func findCertificatesToBeRemoved(certs []*cmapi.Certificate, ingLike metav1.Obje return toBeRemoved } +func mergeAnnotations(a, b map[string]string) map[string]string { + merged := make(map[string]string) + + // Copy annotations from the first map + maps.Copy(merged, a) + + // Copy annotations from the second map (overwriting if key exists) + maps.Copy(merged, b) + + return merged +} func secretNameUsedIn(secretName string, ingLike metav1.Object) bool { switch o := ingLike.(type) { case *networkingv1.Ingress: @@ -561,7 +633,7 @@ func certNeedsUpdate(a, b *cmapi.Certificate) bool { // (1) // The edit-in-place Ingress annotation allows the use of Ingress // controllers that map a single IP address to a single Ingress -// resource, such as the GCE ingress controller. The the following +// resource, such as the GCE ingress controller. The following // annotation on an Ingress named "my-ingress": // // acme.cert-manager.io/http01-edit-in-place: "true" @@ -643,6 +715,46 @@ func hasShimAnnotation(ingLike metav1.Object, autoCertificateAnnotations []strin return false } +func extractExtraAnnotations(ingLike metav1.Object, e []string) map[string]string { + annotations := ingLike.GetAnnotations() + if annotations == nil { + annotations = map[string]string{} + } + extraAnnotations := make(map[string]string) + for _, x := range e { + if s, ok := annotations[x]; ok { + extraAnnotations[x] = s + } + } + if len(extraAnnotations) == 0 { + return nil + } + + return extraAnnotations +} + +// isDeletedInForeground returns true if the given ingressLike resource +// contains either +// +// metadata.deletionTimestamp, or +// metadata.finalizers having one of the values as foregroundDeletion +// +// which indicates that the resource is being deleted via foreground cascading. +// Ref: https://kubernetes.io/docs/concepts/architecture/garbage-collection/#foreground-deletion +func isDeletedInForeground(ingLike metav1.Object) bool { + deletionTimestamp := ingLike.GetDeletionTimestamp() + finalizers := ingLike.GetFinalizers() + foregroundDeletion := false + + for _, v := range finalizers { + if v == metav1.FinalizerDeleteDependents { + foregroundDeletion = true + } + } + + return deletionTimestamp != nil || foregroundDeletion +} + // issuerForIngressLike determines the Issuer that should be specified on a // Certificate created for the given ingress-like resource. If one is not set, // the default issuer given to the controller is used. We look up the following diff --git a/pkg/controller/certificate-shim/sync_test.go b/pkg/controller/certificate-shim/sync_test.go index 5b4783cbf9b..eb89bf6ab50 100644 --- a/pkg/controller/certificate-shim/sync_test.go +++ b/pkg/controller/certificate-shim/sync_test.go @@ -17,9 +17,8 @@ limitations under the License. package shimhelper import ( - "context" "errors" - "fmt" + "reflect" "testing" "github.com/go-logr/logr" @@ -30,13 +29,12 @@ import ( "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/validation/field" coretesting "k8s.io/client-go/testing" - "k8s.io/utils/pointer" - gwapi "sigs.k8s.io/gateway-api/apis/v1alpha2" + "k8s.io/utils/ptr" + gwapi "sigs.k8s.io/gateway-api/apis/v1" cmacme "github.com/cert-manager/cert-manager/pkg/apis/acme/v1" cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" - "github.com/cert-manager/cert-manager/pkg/controller" controllerpkg "github.com/cert-manager/cert-manager/pkg/controller" testpkg "github.com/cert-manager/cert-manager/pkg/controller/test" "github.com/cert-manager/cert-manager/test/unit/gen" @@ -130,13 +128,166 @@ func TestSync(t *testing.T) { Labels: map[string]string{ "my-test-label": "should be copied", }, - OwnerReferences: buildIngressOwnerReferences("ingress-name", gen.DefaultTestNamespace), + OwnerReferences: buildIngressOwnerReferences("ingress-name"), }, Spec: cmapi.CertificateSpec{ DNSNames: []string{"example.com", "www.example.com"}, CommonName: "my-cn", SecretName: "example-com-tls", - IssuerRef: cmmeta.ObjectReference{ + IssuerRef: cmmeta.IssuerReference{ + Name: "issuer-name", + Kind: "ClusterIssuer", + }, + Usages: cmapi.DefaultKeyUsages(), + }, + }, + }, + }, + { + Name: "return a single Certificate for an ingress with dnsNames and ipv4 addresses", + Issuer: acmeClusterIssuer, + IngressLike: &networkingv1.Ingress{ + ObjectMeta: metav1.ObjectMeta{ + Name: "ingress-name", + Namespace: gen.DefaultTestNamespace, + Labels: map[string]string{ + "my-test-label": "should be copied", + }, + Annotations: map[string]string{ + cmapi.IngressClusterIssuerNameAnnotationKey: "issuer-name", + cmapi.CommonNameAnnotationKey: "my-cn", + }, + UID: types.UID("ingress-name"), + }, + Spec: networkingv1.IngressSpec{ + TLS: []networkingv1.IngressTLS{ + { + Hosts: []string{"example.com", "www.example.com", "10.112.234.34", "1.1.1.1"}, + SecretName: "example-com-tls", + }, + }, + }, + }, + ClusterIssuerLister: []runtime.Object{acmeClusterIssuer}, + ExpectedEvents: []string{`Normal CreateCertificate Successfully created Certificate "example-com-tls"`}, + ExpectedCreate: []*cmapi.Certificate{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "example-com-tls", + Namespace: gen.DefaultTestNamespace, + Labels: map[string]string{ + "my-test-label": "should be copied", + }, + OwnerReferences: buildIngressOwnerReferences("ingress-name"), + }, + Spec: cmapi.CertificateSpec{ + DNSNames: []string{"example.com", "www.example.com"}, + IPAddresses: []string{"10.112.234.34", "1.1.1.1"}, + CommonName: "my-cn", + SecretName: "example-com-tls", + IssuerRef: cmmeta.IssuerReference{ + Name: "issuer-name", + Kind: "ClusterIssuer", + }, + Usages: cmapi.DefaultKeyUsages(), + }, + }, + }, + }, + { + Name: "return a single Certificate for an ingress with dnsNames and ipv6 addresses", + Issuer: acmeClusterIssuer, + IngressLike: &networkingv1.Ingress{ + ObjectMeta: metav1.ObjectMeta{ + Name: "ingress-name", + Namespace: gen.DefaultTestNamespace, + Labels: map[string]string{ + "my-test-label": "should be copied", + }, + Annotations: map[string]string{ + cmapi.IngressClusterIssuerNameAnnotationKey: "issuer-name", + cmapi.CommonNameAnnotationKey: "my-cn", + }, + UID: types.UID("ingress-name"), + }, + Spec: networkingv1.IngressSpec{ + TLS: []networkingv1.IngressTLS{ + { + Hosts: []string{"example.com", "www.example.com", "2a00:1450:4009:819::aaaa", "2a00:1450:4009:819::eeee"}, + SecretName: "example-com-tls", + }, + }, + }, + }, + ClusterIssuerLister: []runtime.Object{acmeClusterIssuer}, + ExpectedEvents: []string{`Normal CreateCertificate Successfully created Certificate "example-com-tls"`}, + ExpectedCreate: []*cmapi.Certificate{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "example-com-tls", + Namespace: gen.DefaultTestNamespace, + Labels: map[string]string{ + "my-test-label": "should be copied", + }, + OwnerReferences: buildIngressOwnerReferences("ingress-name"), + }, + Spec: cmapi.CertificateSpec{ + DNSNames: []string{"example.com", "www.example.com"}, + IPAddresses: []string{"2a00:1450:4009:819::aaaa", "2a00:1450:4009:819::eeee"}, + CommonName: "my-cn", + SecretName: "example-com-tls", + IssuerRef: cmmeta.IssuerReference{ + Name: "issuer-name", + Kind: "ClusterIssuer", + }, + Usages: cmapi.DefaultKeyUsages(), + }, + }, + }, + }, + { + Name: "return a single Certificate for an ingress with dnsNames and ipv4 and ipv6 addresses", + Issuer: acmeClusterIssuer, + IngressLike: &networkingv1.Ingress{ + ObjectMeta: metav1.ObjectMeta{ + Name: "ingress-name", + Namespace: gen.DefaultTestNamespace, + Labels: map[string]string{ + "my-test-label": "should be copied", + }, + Annotations: map[string]string{ + cmapi.IngressClusterIssuerNameAnnotationKey: "issuer-name", + cmapi.CommonNameAnnotationKey: "my-cn", + }, + UID: types.UID("ingress-name"), + }, + Spec: networkingv1.IngressSpec{ + TLS: []networkingv1.IngressTLS{ + { + Hosts: []string{"example.com", "www.example.com", "1.1.1.1", "2a00:1450:4009:819::eeee"}, + SecretName: "example-com-tls", + }, + }, + }, + }, + ClusterIssuerLister: []runtime.Object{acmeClusterIssuer}, + ExpectedEvents: []string{`Normal CreateCertificate Successfully created Certificate "example-com-tls"`}, + ExpectedCreate: []*cmapi.Certificate{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "example-com-tls", + Namespace: gen.DefaultTestNamespace, + Labels: map[string]string{ + "my-test-label": "should be copied", + }, + OwnerReferences: buildIngressOwnerReferences("ingress-name"), + }, + Spec: cmapi.CertificateSpec{ + DNSNames: []string{"example.com", "www.example.com"}, + IPAddresses: []string{"1.1.1.1", "2a00:1450:4009:819::eeee"}, + CommonName: "my-cn", + SecretName: "example-com-tls", + IssuerRef: cmmeta.IssuerReference{ Name: "issuer-name", Kind: "ClusterIssuer", }, @@ -184,12 +335,12 @@ func TestSync(t *testing.T) { cmacme.ACMECertificateHTTP01IngressNameOverride: "ingress-name", cmapi.IssueTemporaryCertificateAnnotation: "true", }, - OwnerReferences: buildIngressOwnerReferences("ingress-name", gen.DefaultTestNamespace), + OwnerReferences: buildIngressOwnerReferences("ingress-name"), }, Spec: cmapi.CertificateSpec{ DNSNames: []string{"example.com", "www.example.com"}, SecretName: "example-com-tls", - IssuerRef: cmmeta.ObjectReference{ + IssuerRef: cmmeta.IssuerReference{ Name: "issuer-name", Kind: "ClusterIssuer", }, @@ -237,12 +388,12 @@ func TestSync(t *testing.T) { cmacme.ACMECertificateHTTP01IngressNameOverride: "ingress-name", cmapi.IssueTemporaryCertificateAnnotation: "true", }, - OwnerReferences: buildIngressOwnerReferences("ingress-name", gen.DefaultTestNamespace), + OwnerReferences: buildIngressOwnerReferences("ingress-name"), }, Spec: cmapi.CertificateSpec{ DNSNames: []string{"example.com", "www.example.com"}, SecretName: "example-com-tls", - IssuerRef: cmmeta.ObjectReference{ + IssuerRef: cmmeta.IssuerReference{ Name: "issuer-name", Kind: "ClusterIssuer", }, @@ -279,12 +430,12 @@ func TestSync(t *testing.T) { ObjectMeta: metav1.ObjectMeta{ Name: "example-com-tls", Namespace: gen.DefaultTestNamespace, - OwnerReferences: buildIngressOwnerReferences("ingress-name", gen.DefaultTestNamespace), + OwnerReferences: buildIngressOwnerReferences("ingress-name"), }, Spec: cmapi.CertificateSpec{ DNSNames: []string{"example.com", "www.example.com"}, SecretName: "example-com-tls", - IssuerRef: cmmeta.ObjectReference{ + IssuerRef: cmmeta.IssuerReference{ Name: "issuer-name", Kind: "ClusterIssuer", }, @@ -322,12 +473,12 @@ func TestSync(t *testing.T) { ObjectMeta: metav1.ObjectMeta{ Name: "example-com-tls", Namespace: gen.DefaultTestNamespace, - OwnerReferences: buildIngressOwnerReferences("ingress-name", gen.DefaultTestNamespace), + OwnerReferences: buildIngressOwnerReferences("ingress-name"), }, Spec: cmapi.CertificateSpec{ DNSNames: []string{"example.com", "www.example.com"}, SecretName: "example-com-tls", - IssuerRef: cmmeta.ObjectReference{ + IssuerRef: cmmeta.IssuerReference{ Name: "issuer-name", Kind: "ClusterIssuer", }, @@ -366,7 +517,7 @@ func TestSync(t *testing.T) { ObjectMeta: metav1.ObjectMeta{ Name: "example-com-tls", Namespace: gen.DefaultTestNamespace, - OwnerReferences: buildIngressOwnerReferences("ingress-name", gen.DefaultTestNamespace), + OwnerReferences: buildIngressOwnerReferences("ingress-name"), Annotations: map[string]string{ cmacme.ACMECertificateHTTP01IngressClassOverride: "cert-ing", }, @@ -374,7 +525,58 @@ func TestSync(t *testing.T) { Spec: cmapi.CertificateSpec{ DNSNames: []string{"example.com", "www.example.com"}, SecretName: "example-com-tls", - IssuerRef: cmmeta.ObjectReference{ + IssuerRef: cmmeta.IssuerReference{ + Name: "issuer-name", + Kind: "ClusterIssuer", + }, + Usages: cmapi.DefaultKeyUsages(), + }, + }, + }, + }, + { + Name: "return a single HTTP01 Certificate for an ingress with a single valid TLS entry and valid secret template annotation", + Issuer: acmeClusterIssuer, + IngressLike: &networkingv1.Ingress{ + ObjectMeta: metav1.ObjectMeta{ + Name: "ingress-name", + Namespace: gen.DefaultTestNamespace, + Annotations: map[string]string{ + cmapi.IngressClusterIssuerNameAnnotationKey: "issuer-name", + cmapi.IngressSecretTemplate: `{ "annotations": { "example-annotation" : "dummy-value" }, "labels": { "example-label" : "dummy-value" } }`, + }, + UID: types.UID("ingress-name"), + }, + Spec: networkingv1.IngressSpec{ + TLS: []networkingv1.IngressTLS{ + { + Hosts: []string{"example.com", "www.example.com"}, + SecretName: "example-com-tls", + }, + }, + }, + }, + ClusterIssuerLister: []runtime.Object{acmeClusterIssuer}, + ExpectedEvents: []string{`Normal CreateCertificate Successfully created Certificate "example-com-tls"`}, + ExpectedCreate: []*cmapi.Certificate{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "example-com-tls", + Namespace: gen.DefaultTestNamespace, + OwnerReferences: buildIngressOwnerReferences("ingress-name"), + }, + Spec: cmapi.CertificateSpec{ + DNSNames: []string{"example.com", "www.example.com"}, + SecretName: "example-com-tls", + SecretTemplate: &cmapi.CertificateSecretTemplate{ + Annotations: map[string]string{ + "example-annotation": "dummy-value", + }, + Labels: map[string]string{ + "example-label": "dummy-value", + }, + }, + IssuerRef: cmmeta.IssuerReference{ Name: "issuer-name", Kind: "ClusterIssuer", }, @@ -383,6 +585,54 @@ func TestSync(t *testing.T) { }, }, }, + { + Name: "secret template annotation should not allow cert-manager.io/ annotations", + Issuer: acmeClusterIssuer, + IngressLike: &networkingv1.Ingress{ + ObjectMeta: metav1.ObjectMeta{ + Name: "ingress-name", + Namespace: gen.DefaultTestNamespace, + Annotations: map[string]string{ + cmapi.IngressClusterIssuerNameAnnotationKey: "issuer-name", + cmapi.IngressSecretTemplate: `{ "annotations": { "cert-manager.io/disallowed-annotation" : "dummy-value" } }`, + }, + UID: types.UID("ingress-name"), + }, + Spec: networkingv1.IngressSpec{ + TLS: []networkingv1.IngressTLS{ + { + Hosts: []string{"example.com", "www.example.com"}, + SecretName: "example-com-tls", + }, + }, + }, + }, + Err: true, + }, + { + Name: "secret template annotation should not allow unknown fields", + Issuer: acmeClusterIssuer, + IngressLike: &networkingv1.Ingress{ + ObjectMeta: metav1.ObjectMeta{ + Name: "ingress-name", + Namespace: gen.DefaultTestNamespace, + Annotations: map[string]string{ + cmapi.IngressClusterIssuerNameAnnotationKey: "issuer-name", + cmapi.IngressSecretTemplate: `{ "unknown-field": "true" }`, + }, + UID: types.UID("ingress-name"), + }, + Spec: networkingv1.IngressSpec{ + TLS: []networkingv1.IngressTLS{ + { + Hosts: []string{"example.com", "www.example.com"}, + SecretName: "example-com-tls", + }, + }, + }, + }, + Err: true, + }, { Name: "edit-in-place set to false should not trigger editing the ingress in-place", Issuer: acmeClusterIssuer, @@ -413,12 +663,12 @@ func TestSync(t *testing.T) { ObjectMeta: metav1.ObjectMeta{ Name: "example-com-tls", Namespace: gen.DefaultTestNamespace, - OwnerReferences: buildIngressOwnerReferences("ingress-name", gen.DefaultTestNamespace), + OwnerReferences: buildIngressOwnerReferences("ingress-name"), }, Spec: cmapi.CertificateSpec{ DNSNames: []string{"example.com", "www.example.com"}, SecretName: "example-com-tls", - IssuerRef: cmmeta.ObjectReference{ + IssuerRef: cmmeta.IssuerReference{ Name: "issuer-name", Kind: "ClusterIssuer", }, @@ -455,12 +705,12 @@ func TestSync(t *testing.T) { ObjectMeta: metav1.ObjectMeta{ Name: "example-com-tls", Namespace: gen.DefaultTestNamespace, - OwnerReferences: buildIngressOwnerReferences("ingress-name", gen.DefaultTestNamespace), + OwnerReferences: buildIngressOwnerReferences("ingress-name"), }, Spec: cmapi.CertificateSpec{ DNSNames: []string{"example.com", "www.example.com"}, SecretName: "example-com-tls", - IssuerRef: cmmeta.ObjectReference{ + IssuerRef: cmmeta.IssuerReference{ Name: "issuer-name", Kind: "ClusterIssuer", }, @@ -500,12 +750,12 @@ func TestSync(t *testing.T) { ObjectMeta: metav1.ObjectMeta{ Name: "example-com-tls", Namespace: gen.DefaultTestNamespace, - OwnerReferences: buildIngressOwnerReferences("ingress-name", gen.DefaultTestNamespace), + OwnerReferences: buildIngressOwnerReferences("ingress-name"), }, Spec: cmapi.CertificateSpec{ DNSNames: []string{"example.com", "www.example.com"}, SecretName: "example-com-tls", - IssuerRef: cmmeta.ObjectReference{ + IssuerRef: cmmeta.IssuerReference{ Name: "issuer-name", Kind: "ClusterIssuer", Group: "cert-manager.io", @@ -549,13 +799,13 @@ func TestSync(t *testing.T) { ObjectMeta: metav1.ObjectMeta{ Name: "example-com-tls", Namespace: gen.DefaultTestNamespace, - OwnerReferences: buildIngressOwnerReferences("ingress-name", gen.DefaultTestNamespace), + OwnerReferences: buildIngressOwnerReferences("ingress-name"), }, Spec: cmapi.CertificateSpec{ DNSNames: []string{"example.com", "www.example.com"}, SecretName: "example-com-tls", Usages: cmapi.DefaultKeyUsages(), - IssuerRef: cmmeta.ObjectReference{ + IssuerRef: cmmeta.IssuerReference{ Name: "issuer-name", Kind: "Issuer", }, @@ -598,13 +848,13 @@ func TestSync(t *testing.T) { ObjectMeta: metav1.ObjectMeta{ Name: "example-com-tls", Namespace: gen.DefaultTestNamespace, - OwnerReferences: buildIngressOwnerReferences("ingress-name", gen.DefaultTestNamespace), + OwnerReferences: buildIngressOwnerReferences("ingress-name"), }, Spec: cmapi.CertificateSpec{ DNSNames: []string{"example.com", "www.example.com"}, SecretName: "example-com-tls", Usages: cmapi.DefaultKeyUsages(), - IssuerRef: cmmeta.ObjectReference{ + IssuerRef: cmmeta.IssuerReference{ Name: "issuer-name", Kind: "Issuer", }, @@ -654,12 +904,12 @@ func TestSync(t *testing.T) { ObjectMeta: metav1.ObjectMeta{ Name: "existing-crt", Namespace: gen.DefaultTestNamespace, - OwnerReferences: buildIngressOwnerReferences("ingress-name", gen.DefaultTestNamespace), + OwnerReferences: buildIngressOwnerReferences("ingress-name"), }, Spec: cmapi.CertificateSpec{ DNSNames: []string{"example.com"}, SecretName: "existing-crt", - IssuerRef: cmmeta.ObjectReference{ + IssuerRef: cmmeta.IssuerReference{ Name: "issuer-name", Kind: "Issuer", Group: "cert-manager.io", @@ -694,7 +944,7 @@ func TestSync(t *testing.T) { CertificateLister: []runtime.Object{ buildCertificate("existing-crt", gen.DefaultTestNamespace, - buildIngressOwnerReferences("ingress-name", gen.DefaultTestNamespace), + buildIngressOwnerReferences("ingress-name"), ), }, DefaultIssuerKind: "Issuer", @@ -704,12 +954,12 @@ func TestSync(t *testing.T) { ObjectMeta: metav1.ObjectMeta{ Name: "existing-crt", Namespace: gen.DefaultTestNamespace, - OwnerReferences: buildIngressOwnerReferences("ingress-name", gen.DefaultTestNamespace), + OwnerReferences: buildIngressOwnerReferences("ingress-name"), }, Spec: cmapi.CertificateSpec{ DNSNames: []string{"example.com"}, SecretName: "existing-crt", - IssuerRef: cmmeta.ObjectReference{ + IssuerRef: cmmeta.IssuerReference{ Name: "issuer-name", Kind: "Issuer", }, @@ -752,12 +1002,12 @@ func TestSync(t *testing.T) { Labels: map[string]string{ "a-different-value": "should be removed", }, - OwnerReferences: buildIngressOwnerReferences("ingress-name", gen.DefaultTestNamespace), + OwnerReferences: buildIngressOwnerReferences("ingress-name"), }, Spec: cmapi.CertificateSpec{ DNSNames: []string{"example.com"}, SecretName: "cert-secret-name", - IssuerRef: cmmeta.ObjectReference{ + IssuerRef: cmmeta.IssuerReference{ Name: "issuer-name", Kind: "Issuer", }, @@ -774,12 +1024,12 @@ func TestSync(t *testing.T) { Labels: map[string]string{ "my-test-label": "should be copied", }, - OwnerReferences: buildIngressOwnerReferences("ingress-name", gen.DefaultTestNamespace), + OwnerReferences: buildIngressOwnerReferences("ingress-name"), }, Spec: cmapi.CertificateSpec{ DNSNames: []string{"example.com"}, SecretName: "cert-secret-name", - IssuerRef: cmmeta.ObjectReference{ + IssuerRef: cmmeta.IssuerReference{ Name: "issuer-name", Kind: "Issuer", }, @@ -817,17 +1067,17 @@ func TestSync(t *testing.T) { ObjectMeta: metav1.ObjectMeta{ Name: "cert-secret-name", Namespace: gen.DefaultTestNamespace, - OwnerReferences: buildIngressOwnerReferences("ingress-name", gen.DefaultTestNamespace), + OwnerReferences: buildIngressOwnerReferences("ingress-name"), }, Spec: cmapi.CertificateSpec{ DNSNames: []string{"example.com"}, SecretName: "cert-secret-name", - IssuerRef: cmmeta.ObjectReference{ + IssuerRef: cmmeta.IssuerReference{ Name: "issuer-name", Kind: "Issuer", }, Usages: cmapi.DefaultKeyUsages(), - RevisionHistoryLimit: pointer.Int32(7), + RevisionHistoryLimit: ptr.To(int32(7)), }, }, }, @@ -837,17 +1087,17 @@ func TestSync(t *testing.T) { ObjectMeta: metav1.ObjectMeta{ Name: "cert-secret-name", Namespace: gen.DefaultTestNamespace, - OwnerReferences: buildIngressOwnerReferences("ingress-name", gen.DefaultTestNamespace), + OwnerReferences: buildIngressOwnerReferences("ingress-name"), }, Spec: cmapi.CertificateSpec{ DNSNames: []string{"example.com"}, SecretName: "cert-secret-name", - IssuerRef: cmmeta.ObjectReference{ + IssuerRef: cmmeta.IssuerReference{ Name: "issuer-name", Kind: "Issuer", }, Usages: cmapi.DefaultKeyUsages(), - RevisionHistoryLimit: pointer.Int32(1), + RevisionHistoryLimit: ptr.To(int32(1)), }, }, }, @@ -882,12 +1132,12 @@ func TestSync(t *testing.T) { ObjectMeta: metav1.ObjectMeta{ Name: "cert-secret-name", Namespace: gen.DefaultTestNamespace, - OwnerReferences: buildIngressOwnerReferences("ingress-name", gen.DefaultTestNamespace), + OwnerReferences: buildIngressOwnerReferences("ingress-name"), }, Spec: cmapi.CertificateSpec{ DNSNames: []string{"example.com"}, SecretName: "cert-secret-name", - IssuerRef: cmmeta.ObjectReference{ + IssuerRef: cmmeta.IssuerReference{ Name: "issuer-name", Kind: "Issuer", }, @@ -905,12 +1155,12 @@ func TestSync(t *testing.T) { ObjectMeta: metav1.ObjectMeta{ Name: "cert-secret-name", Namespace: gen.DefaultTestNamespace, - OwnerReferences: buildIngressOwnerReferences("ingress-name", gen.DefaultTestNamespace), + OwnerReferences: buildIngressOwnerReferences("ingress-name"), }, Spec: cmapi.CertificateSpec{ DNSNames: []string{"example.com"}, SecretName: "cert-secret-name", - IssuerRef: cmmeta.ObjectReference{ + IssuerRef: cmmeta.IssuerReference{ Name: "issuer-name", Kind: "Issuer", }, @@ -953,12 +1203,12 @@ func TestSync(t *testing.T) { ObjectMeta: metav1.ObjectMeta{ Name: "cert-secret-name", Namespace: gen.DefaultTestNamespace, - OwnerReferences: buildIngressOwnerReferences("ingress-name", gen.DefaultTestNamespace), + OwnerReferences: buildIngressOwnerReferences("ingress-name"), }, Spec: cmapi.CertificateSpec{ DNSNames: []string{"example.com"}, SecretName: "cert-secret-name", - IssuerRef: cmmeta.ObjectReference{ + IssuerRef: cmmeta.IssuerReference{ Name: "issuer-name", Kind: "Issuer", }, @@ -976,12 +1226,12 @@ func TestSync(t *testing.T) { ObjectMeta: metav1.ObjectMeta{ Name: "cert-secret-name", Namespace: gen.DefaultTestNamespace, - OwnerReferences: buildIngressOwnerReferences("ingress-name", gen.DefaultTestNamespace), + OwnerReferences: buildIngressOwnerReferences("ingress-name"), }, Spec: cmapi.CertificateSpec{ DNSNames: []string{"example.com"}, SecretName: "cert-secret-name", - IssuerRef: cmmeta.ObjectReference{ + IssuerRef: cmmeta.IssuerReference{ Name: "issuer-name", Kind: "Issuer", }, @@ -1025,12 +1275,12 @@ func TestSync(t *testing.T) { ObjectMeta: metav1.ObjectMeta{ Name: "cert-secret-name", Namespace: gen.DefaultTestNamespace, - OwnerReferences: buildIngressOwnerReferences("ingress-name", gen.DefaultTestNamespace), + OwnerReferences: buildIngressOwnerReferences("ingress-name"), }, Spec: cmapi.CertificateSpec{ DNSNames: []string{"example.com"}, SecretName: "cert-secret-name", - IssuerRef: cmmeta.ObjectReference{ + IssuerRef: cmmeta.IssuerReference{ Name: "issuer-name", Kind: "Issuer", }, @@ -1048,12 +1298,12 @@ func TestSync(t *testing.T) { ObjectMeta: metav1.ObjectMeta{ Name: "cert-secret-name", Namespace: gen.DefaultTestNamespace, - OwnerReferences: buildIngressOwnerReferences("ingress-name", gen.DefaultTestNamespace), + OwnerReferences: buildIngressOwnerReferences("ingress-name"), }, Spec: cmapi.CertificateSpec{ DNSNames: []string{"example.com"}, SecretName: "cert-secret-name", - IssuerRef: cmmeta.ObjectReference{ + IssuerRef: cmmeta.IssuerReference{ Name: "issuer-name", Kind: "Issuer", }, @@ -1099,12 +1349,12 @@ func TestSync(t *testing.T) { ObjectMeta: metav1.ObjectMeta{ Name: "cert-secret-name", Namespace: gen.DefaultTestNamespace, - OwnerReferences: buildIngressOwnerReferences("ingress-name", gen.DefaultTestNamespace), + OwnerReferences: buildIngressOwnerReferences("ingress-name"), }, Spec: cmapi.CertificateSpec{ DNSNames: []string{"example.com"}, SecretName: "cert-secret-name", - IssuerRef: cmmeta.ObjectReference{ + IssuerRef: cmmeta.IssuerReference{ Name: "issuer-name", Kind: "Issuer", }, @@ -1122,12 +1372,12 @@ func TestSync(t *testing.T) { ObjectMeta: metav1.ObjectMeta{ Name: "cert-secret-name", Namespace: gen.DefaultTestNamespace, - OwnerReferences: buildIngressOwnerReferences("ingress-name", gen.DefaultTestNamespace), + OwnerReferences: buildIngressOwnerReferences("ingress-name"), }, Spec: cmapi.CertificateSpec{ DNSNames: []string{"example.com"}, SecretName: "cert-secret-name", - IssuerRef: cmmeta.ObjectReference{ + IssuerRef: cmmeta.IssuerReference{ Name: "issuer-name", Kind: "Issuer", }, @@ -1175,7 +1425,7 @@ func TestSync(t *testing.T) { Spec: cmapi.CertificateSpec{ DNSNames: []string{"example.com"}, SecretName: "existing-crt", - IssuerRef: cmmeta.ObjectReference{ + IssuerRef: cmmeta.IssuerReference{ Name: "issuer-name", Kind: "Issuer", }, @@ -1212,12 +1462,12 @@ func TestSync(t *testing.T) { ObjectMeta: metav1.ObjectMeta{ Name: "existing-crt", Namespace: gen.DefaultTestNamespace, - OwnerReferences: buildIngressOwnerReferences("not-ingress-name", gen.DefaultTestNamespace), + OwnerReferences: buildIngressOwnerReferences("not-ingress-name"), }, Spec: cmapi.CertificateSpec{ DNSNames: []string{"example.com"}, SecretName: "existing-crt", - IssuerRef: cmmeta.ObjectReference{ + IssuerRef: cmmeta.IssuerReference{ Name: "issuer-name", Kind: "Issuer", }, @@ -1245,12 +1495,12 @@ func TestSync(t *testing.T) { ObjectMeta: metav1.ObjectMeta{ Name: "existing-crt", Namespace: gen.DefaultTestNamespace, - OwnerReferences: buildIngressOwnerReferences("ingress-name", gen.DefaultTestNamespace), + OwnerReferences: buildIngressOwnerReferences("ingress-name"), }, Spec: cmapi.CertificateSpec{ DNSNames: []string{"example.com"}, SecretName: "existing-crt", - IssuerRef: cmmeta.ObjectReference{ + IssuerRef: cmmeta.IssuerReference{ Name: "issuer-name", Kind: "Issuer", }, @@ -1264,12 +1514,12 @@ func TestSync(t *testing.T) { ObjectMeta: metav1.ObjectMeta{ Name: "existing-crt", Namespace: gen.DefaultTestNamespace, - OwnerReferences: buildIngressOwnerReferences("ingress-name", gen.DefaultTestNamespace), + OwnerReferences: buildIngressOwnerReferences("ingress-name"), }, Spec: cmapi.CertificateSpec{ DNSNames: []string{"example.com"}, SecretName: "existing-crt", - IssuerRef: cmmeta.ObjectReference{ + IssuerRef: cmmeta.IssuerReference{ Name: "issuer-name", Kind: "Issuer", }, @@ -1306,13 +1556,13 @@ func TestSync(t *testing.T) { ObjectMeta: metav1.ObjectMeta{ Name: "example-com-tls", Namespace: gen.DefaultTestNamespace, - OwnerReferences: buildIngressOwnerReferences("ingress-name", gen.DefaultTestNamespace), + OwnerReferences: buildIngressOwnerReferences("ingress-name"), }, Spec: cmapi.CertificateSpec{ DNSNames: []string{"example.com"}, SecretName: "example-com-tls", CommonName: "example-common-name", - IssuerRef: cmmeta.ObjectReference{ + IssuerRef: cmmeta.IssuerReference{ Name: "issuer-name", Kind: "Issuer", Group: "cert-manager.io", @@ -1327,12 +1577,12 @@ func TestSync(t *testing.T) { ObjectMeta: metav1.ObjectMeta{ Name: "example-com-tls", Namespace: gen.DefaultTestNamespace, - OwnerReferences: buildIngressOwnerReferences("ingress-name", gen.DefaultTestNamespace), + OwnerReferences: buildIngressOwnerReferences("ingress-name"), }, Spec: cmapi.CertificateSpec{ DNSNames: []string{"example.com"}, SecretName: "example-com-tls", - IssuerRef: cmmeta.ObjectReference{ + IssuerRef: cmmeta.IssuerReference{ Name: "issuer-name", Kind: "Issuer", Group: "cert-manager.io", @@ -1441,13 +1691,13 @@ func TestSync(t *testing.T) { Labels: map[string]string{ "my-test-label": "should be copied", }, - OwnerReferences: buildIngressOwnerReferences("ingress-name", gen.DefaultTestNamespace), + OwnerReferences: buildIngressOwnerReferences("ingress-name"), }, Spec: cmapi.CertificateSpec{ DNSNames: []string{"example.com", "www.example.com"}, CommonName: "my-cn", SecretName: "example-com-tls", - IssuerRef: cmmeta.ObjectReference{ + IssuerRef: cmmeta.IssuerReference{ Name: "issuer-name", Kind: "ClusterIssuer", }, @@ -1460,15 +1710,12 @@ func TestSync(t *testing.T) { }, }, }, - } - - testGatewayShim := []testT{ { - Name: "return a single Certificate for a Gateway with a single valid TLS entry and common-name annotation", + Name: "return a single Certificate for an ingress with a single valid TLS entry with common-name and subject street addresses annotation", Issuer: acmeClusterIssuer, - IngressLike: &gwapi.Gateway{ + IngressLike: &networkingv1.Ingress{ ObjectMeta: metav1.ObjectMeta{ - Name: "gateway-name", + Name: "ingress-name", Namespace: gen.DefaultTestNamespace, Labels: map[string]string{ "my-test-label": "should be copied", @@ -1476,26 +1723,15 @@ func TestSync(t *testing.T) { Annotations: map[string]string{ cmapi.IngressClusterIssuerNameAnnotationKey: "issuer-name", cmapi.CommonNameAnnotationKey: "my-cn", + cmapi.SubjectStreetAddressesAnnotationKey: `"1725 Slough Avenue, Suite 200, Scranton Business Park"`, }, - UID: types.UID("gateway-name"), + UID: types.UID("ingress-name"), }, - Spec: gwapi.GatewaySpec{ - GatewayClassName: "test-gateway", - Listeners: []gwapi.Listener{ + Spec: networkingv1.IngressSpec{ + TLS: []networkingv1.IngressTLS{ { - Hostname: ptrHostname("example.com"), - Port: 443, - Protocol: "HTTPS", - TLS: &gwapi.GatewayTLSConfig{ - Mode: ptrMode(gwapi.TLSModeTerminate), - CertificateRefs: []gwapi.SecretObjectReference{ - { - Group: func() *gwapi.Group { g := gwapi.Group("core"); return &g }(), - Kind: func() *gwapi.Kind { k := gwapi.Kind("Secret"); return &k }(), - Name: "example-com-tls", - }, - }, - }, + Hosts: []string{"example.com", "www.example.com"}, + SecretName: "example-com-tls", }, }, }, @@ -1510,25 +1746,409 @@ func TestSync(t *testing.T) { Labels: map[string]string{ "my-test-label": "should be copied", }, - OwnerReferences: buildGatewayOwnerReferences("gateway-name", gen.DefaultTestNamespace), + OwnerReferences: buildIngressOwnerReferences("ingress-name"), }, Spec: cmapi.CertificateSpec{ - DNSNames: []string{"example.com"}, + DNSNames: []string{"example.com", "www.example.com"}, CommonName: "my-cn", SecretName: "example-com-tls", - IssuerRef: cmmeta.ObjectReference{ + IssuerRef: cmmeta.IssuerReference{ Name: "issuer-name", Kind: "ClusterIssuer", }, + Subject: &cmapi.X509Subject{ + StreetAddresses: []string{"1725 Slough Avenue, Suite 200, Scranton Business Park"}, + }, Usages: cmapi.DefaultKeyUsages(), }, }, }, }, { - Name: "return a single HTTP01 Certificate for a Gateway with a single valid TLS entry and HTTP01 annotations using edit-in-place", + Name: "should not trigger an ingress sync if deleted in foreground", + Issuer: clusterIssuer, + DefaultIssuerName: "issuer-name", + DefaultIssuerKind: "ClusterIssuer", + DefaultIssuerGroup: "cert-manager.io", + ClusterIssuerLister: []runtime.Object{clusterIssuer}, + IngressLike: buildIngressInDeletion(buildIngress("", "", map[string]string{cmapi.IngressIssuerNameAnnotationKey: ""}), &metav1.Time{}, []string{metav1.FinalizerDeleteDependents}), + }, + + { + Name: "should not propagate the applyset label", Issuer: acmeClusterIssuer, - IngressLike: &gwapi.Gateway{ + IngressLike: &networkingv1.Ingress{ + ObjectMeta: metav1.ObjectMeta{ + Name: "ingress-name", + Namespace: gen.DefaultTestNamespace, + Labels: map[string]string{ + "my-test-label": "should be copied", + applysetLabel: "should not be propagated", + }, + Annotations: map[string]string{ + cmapi.IngressClusterIssuerNameAnnotationKey: "issuer-name", + cmapi.CommonNameAnnotationKey: "my-cn", + }, + UID: types.UID("ingress-name"), + }, + Spec: networkingv1.IngressSpec{ + TLS: []networkingv1.IngressTLS{ + { + Hosts: []string{"example.com", "www.example.com"}, + SecretName: "example-com-tls", + }, + }, + }, + }, + ClusterIssuerLister: []runtime.Object{acmeClusterIssuer}, + ExpectedEvents: []string{`Normal CreateCertificate Successfully created Certificate "example-com-tls"`}, + ExpectedCreate: []*cmapi.Certificate{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "example-com-tls", + Namespace: gen.DefaultTestNamespace, + Labels: map[string]string{ + "my-test-label": "should be copied", + // note that the applyset label should not be here + }, + OwnerReferences: buildIngressOwnerReferences("ingress-name"), + }, + Spec: cmapi.CertificateSpec{ + DNSNames: []string{"example.com", "www.example.com"}, + CommonName: "my-cn", + SecretName: "example-com-tls", + IssuerRef: cmmeta.IssuerReference{ + Name: "issuer-name", + Kind: "ClusterIssuer", + }, + Usages: cmapi.DefaultKeyUsages(), + }, + }, + }, + }, + { + Name: "extra Ingress annotation is copied to Certificate object", + Issuer: acmeClusterIssuer, + IngressLike: &networkingv1.Ingress{ + ObjectMeta: metav1.ObjectMeta{ + Name: "ingress-name", + Namespace: gen.DefaultTestNamespace, + Labels: map[string]string{}, + Annotations: map[string]string{ + cmapi.IngressClusterIssuerNameAnnotationKey: "issuer-name", + cmapi.CommonNameAnnotationKey: "my-cn", + "venafi.cert-manager.io/custom-fields": "foo", + "venafi.cert-manager.io/do-not-copy": "bar", + }, + UID: types.UID("ingress-name"), + }, + Spec: networkingv1.IngressSpec{ + TLS: []networkingv1.IngressTLS{ + { + Hosts: []string{"example.com", "www.example.com"}, + SecretName: "example-com-tls", + }, + }, + }, + }, + ClusterIssuerLister: []runtime.Object{acmeClusterIssuer}, + ExpectedEvents: []string{`Normal CreateCertificate Successfully created Certificate "example-com-tls"`}, + ExpectedCreate: []*cmapi.Certificate{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "example-com-tls", + Namespace: gen.DefaultTestNamespace, + Labels: map[string]string{}, + Annotations: map[string]string{ + "venafi.cert-manager.io/custom-fields": "foo", + }, + OwnerReferences: buildIngressOwnerReferences("ingress-name"), + }, + Spec: cmapi.CertificateSpec{ + DNSNames: []string{"example.com", "www.example.com"}, + CommonName: "my-cn", + SecretName: "example-com-tls", + IssuerRef: cmmeta.IssuerReference{ + Name: "issuer-name", + Kind: "ClusterIssuer", + }, + Usages: cmapi.DefaultKeyUsages(), + }, + }, + }, + }, + { + Name: "should update a Certificate annotations if custom annotation is needed", + Issuer: acmeIssuer, + IssuerLister: []runtime.Object{acmeIssuer}, + IngressLike: &networkingv1.Ingress{ + ObjectMeta: metav1.ObjectMeta{ + Name: "ingress-name", + Namespace: gen.DefaultTestNamespace, + Annotations: map[string]string{ + cmapi.IngressIssuerNameAnnotationKey: "issuer-name", + cmapi.IssuerKindAnnotationKey: "Issuer", + cmapi.IssuerGroupAnnotationKey: "cert-manager.io", + "venafi.cert-manager.io/custom-fields": "foo", + }, + UID: types.UID("ingress-name"), + }, + Spec: networkingv1.IngressSpec{ + TLS: []networkingv1.IngressTLS{ + { + Hosts: []string{"example.com"}, + SecretName: "example-com-tls", + }, + }, + }, + }, + CertificateLister: []runtime.Object{ + &cmapi.Certificate{ + ObjectMeta: metav1.ObjectMeta{ + Name: "example-com-tls", + Namespace: gen.DefaultTestNamespace, + OwnerReferences: buildIngressOwnerReferences("ingress-name"), + }, + Spec: cmapi.CertificateSpec{ + DNSNames: []string{"example.com"}, + SecretName: "example-com-tls", + CommonName: "example-common-name", + IssuerRef: cmmeta.IssuerReference{ + Name: "issuer-name", + Kind: "Issuer", + Group: "cert-manager.io", + }, + Usages: cmapi.DefaultKeyUsages(), + }, + }, + }, + ExpectedEvents: []string{`Normal UpdateCertificate Successfully updated Certificate "example-com-tls"`}, + ExpectedUpdate: []*cmapi.Certificate{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "example-com-tls", + Namespace: gen.DefaultTestNamespace, + OwnerReferences: buildIngressOwnerReferences("ingress-name"), + Annotations: map[string]string{ + "venafi.cert-manager.io/custom-fields": "foo", + }, + }, + Spec: cmapi.CertificateSpec{ + DNSNames: []string{"example.com"}, + SecretName: "example-com-tls", + IssuerRef: cmmeta.IssuerReference{ + Name: "issuer-name", + Kind: "Issuer", + Group: "cert-manager.io", + }, + Usages: cmapi.DefaultKeyUsages(), + }, + }, + }, + }, + { + Name: "should append a Certificate annotations if annotation is set", + Issuer: acmeIssuer, + IssuerLister: []runtime.Object{acmeIssuer}, + IngressLike: &networkingv1.Ingress{ + ObjectMeta: metav1.ObjectMeta{ + Name: "ingress-name", + Namespace: gen.DefaultTestNamespace, + Annotations: map[string]string{ + cmapi.IngressIssuerNameAnnotationKey: "issuer-name", + cmapi.IssuerKindAnnotationKey: "Issuer", + cmapi.IssuerGroupAnnotationKey: "cert-manager.io", + "venafi.cert-manager.io/custom-fields": "foo", + }, + UID: types.UID("ingress-name"), + }, + Spec: networkingv1.IngressSpec{ + TLS: []networkingv1.IngressTLS{ + { + Hosts: []string{"example.com"}, + SecretName: "example-com-tls", + }, + }, + }, + }, + CertificateLister: []runtime.Object{ + &cmapi.Certificate{ + ObjectMeta: metav1.ObjectMeta{ + Name: "example-com-tls", + Namespace: gen.DefaultTestNamespace, + OwnerReferences: buildIngressOwnerReferences("ingress-name"), + Annotations: map[string]string{ + "venafi.cert-manager.io/custom-fields": "bar", + }, + }, + Spec: cmapi.CertificateSpec{ + DNSNames: []string{"example.com"}, + SecretName: "example-com-tls", + CommonName: "example-common-name", + IssuerRef: cmmeta.IssuerReference{ + Name: "issuer-name", + Kind: "Issuer", + Group: "cert-manager.io", + }, + Usages: cmapi.DefaultKeyUsages(), + }, + }, + }, + ExpectedEvents: []string{`Normal UpdateCertificate Successfully updated Certificate "example-com-tls"`}, + ExpectedUpdate: []*cmapi.Certificate{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "example-com-tls", + Namespace: gen.DefaultTestNamespace, + OwnerReferences: buildIngressOwnerReferences("ingress-name"), + Annotations: map[string]string{ + "venafi.cert-manager.io/custom-fields": "foo", + }, + }, + Spec: cmapi.CertificateSpec{ + DNSNames: []string{"example.com"}, + SecretName: "example-com-tls", + IssuerRef: cmmeta.IssuerReference{ + Name: "issuer-name", + Kind: "Issuer", + Group: "cert-manager.io", + }, + Usages: cmapi.DefaultKeyUsages(), + }, + }, + }, + }, + } + + testGatewayShim := []testT{ + { + Name: "return a single Certificate for a Gateway with a single valid TLS entry and common-name annotation (HTTPS)", + Issuer: acmeClusterIssuer, + IngressLike: &gwapi.Gateway{ + ObjectMeta: metav1.ObjectMeta{ + Name: "gateway-name", + Namespace: gen.DefaultTestNamespace, + Labels: map[string]string{ + "my-test-label": "should be copied", + }, + Annotations: map[string]string{ + cmapi.IngressClusterIssuerNameAnnotationKey: "issuer-name", + cmapi.CommonNameAnnotationKey: "my-cn", + }, + UID: types.UID("gateway-name"), + }, + Spec: gwapi.GatewaySpec{ + GatewayClassName: "test-gateway", + Listeners: []gwapi.Listener{ + { + Hostname: ptrHostname("example.com"), + Port: 443, + Protocol: gwapi.HTTPSProtocolType, + TLS: &gwapi.ListenerTLSConfig{ + Mode: ptrMode(gwapi.TLSModeTerminate), + CertificateRefs: []gwapi.SecretObjectReference{ + { + Group: func() *gwapi.Group { g := gwapi.Group("core"); return &g }(), + Kind: func() *gwapi.Kind { k := gwapi.Kind("Secret"); return &k }(), + Name: "example-com-tls", + }, + }, + }, + }, + }, + }, + }, + ClusterIssuerLister: []runtime.Object{acmeClusterIssuer}, + ExpectedEvents: []string{`Normal CreateCertificate Successfully created Certificate "example-com-tls"`}, + ExpectedCreate: []*cmapi.Certificate{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "example-com-tls", + Namespace: gen.DefaultTestNamespace, + Labels: map[string]string{ + "my-test-label": "should be copied", + }, + OwnerReferences: buildGatewayOwnerReferences("gateway-name"), + }, + Spec: cmapi.CertificateSpec{ + DNSNames: []string{"example.com"}, + CommonName: "my-cn", + SecretName: "example-com-tls", + IssuerRef: cmmeta.IssuerReference{ + Name: "issuer-name", + Kind: "ClusterIssuer", + }, + Usages: cmapi.DefaultKeyUsages(), + }, + }, + }, + }, + { + Name: "return a single Certificate for a Gateway with a single valid TLS entry and common-name annotation (TLS)", + Issuer: acmeClusterIssuer, + IngressLike: &gwapi.Gateway{ + ObjectMeta: metav1.ObjectMeta{ + Name: "gateway-name", + Namespace: gen.DefaultTestNamespace, + Labels: map[string]string{ + "my-test-label": "should be copied", + }, + Annotations: map[string]string{ + cmapi.IngressClusterIssuerNameAnnotationKey: "issuer-name", + cmapi.CommonNameAnnotationKey: "my-cn", + }, + UID: types.UID("gateway-name"), + }, + Spec: gwapi.GatewaySpec{ + GatewayClassName: "test-gateway", + Listeners: []gwapi.Listener{ + { + Hostname: ptrHostname("example.com"), + Port: 443, + Protocol: gwapi.TLSProtocolType, + TLS: &gwapi.ListenerTLSConfig{ + Mode: ptrMode(gwapi.TLSModeTerminate), + CertificateRefs: []gwapi.SecretObjectReference{ + { + Group: func() *gwapi.Group { g := gwapi.Group("core"); return &g }(), + Kind: func() *gwapi.Kind { k := gwapi.Kind("Secret"); return &k }(), + Name: "example-com-tls", + }, + }, + }, + }, + }, + }, + }, + ClusterIssuerLister: []runtime.Object{acmeClusterIssuer}, + ExpectedEvents: []string{`Normal CreateCertificate Successfully created Certificate "example-com-tls"`}, + ExpectedCreate: []*cmapi.Certificate{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "example-com-tls", + Namespace: gen.DefaultTestNamespace, + Labels: map[string]string{ + "my-test-label": "should be copied", + }, + OwnerReferences: buildGatewayOwnerReferences("gateway-name"), + }, + Spec: cmapi.CertificateSpec{ + DNSNames: []string{"example.com"}, + CommonName: "my-cn", + SecretName: "example-com-tls", + IssuerRef: cmmeta.IssuerReference{ + Name: "issuer-name", + Kind: "ClusterIssuer", + }, + Usages: cmapi.DefaultKeyUsages(), + }, + }, + }, + }, + { + Name: "return a single HTTP01 Certificate for a Gateway with a single valid TLS entry and HTTP01 annotations using edit-in-place", + Issuer: acmeClusterIssuer, + IngressLike: &gwapi.Gateway{ ObjectMeta: metav1.ObjectMeta{ Name: "gateway-name", Namespace: gen.DefaultTestNamespace, @@ -1546,8 +2166,8 @@ func TestSync(t *testing.T) { Listeners: []gwapi.Listener{{ Hostname: ptrHostname("example.com"), Port: 443, - Protocol: "HTTPS", - TLS: &gwapi.GatewayTLSConfig{ + Protocol: gwapi.HTTPSProtocolType, + TLS: &gwapi.ListenerTLSConfig{ Mode: ptrMode(gwapi.TLSModeTerminate), CertificateRefs: []gwapi.SecretObjectReference{ { @@ -1574,12 +2194,12 @@ func TestSync(t *testing.T) { cmacme.ACMECertificateHTTP01IngressNameOverride: "gateway-name", cmapi.IssueTemporaryCertificateAnnotation: "true", }, - OwnerReferences: buildGatewayOwnerReferences("gateway-name", gen.DefaultTestNamespace), + OwnerReferences: buildGatewayOwnerReferences("gateway-name"), }, Spec: cmapi.CertificateSpec{ DNSNames: []string{"example.com"}, SecretName: "example-com-tls", - IssuerRef: cmmeta.ObjectReference{ + IssuerRef: cmmeta.IssuerReference{ Name: "issuer-name", Kind: "ClusterIssuer", }, @@ -1609,8 +2229,8 @@ func TestSync(t *testing.T) { Listeners: []gwapi.Listener{{ Hostname: ptrHostname("example.com"), Port: 443, - Protocol: "HTTPS", - TLS: &gwapi.GatewayTLSConfig{ + Protocol: gwapi.HTTPSProtocolType, + TLS: &gwapi.ListenerTLSConfig{ Mode: ptrMode(gwapi.TLSModeTerminate), CertificateRefs: []gwapi.SecretObjectReference{ { @@ -1637,12 +2257,12 @@ func TestSync(t *testing.T) { cmacme.ACMECertificateHTTP01IngressNameOverride: "gateway-name", cmapi.IssueTemporaryCertificateAnnotation: "true", }, - OwnerReferences: buildGatewayOwnerReferences("gateway-name", gen.DefaultTestNamespace), + OwnerReferences: buildGatewayOwnerReferences("gateway-name"), }, Spec: cmapi.CertificateSpec{ DNSNames: []string{"example.com"}, SecretName: "example-com-tls", - IssuerRef: cmmeta.ObjectReference{ + IssuerRef: cmmeta.IssuerReference{ Name: "issuer-name", Kind: "ClusterIssuer", }, @@ -1668,8 +2288,8 @@ func TestSync(t *testing.T) { Listeners: []gwapi.Listener{{ Hostname: ptrHostname("example.com"), Port: 443, - Protocol: "HTTPS", - TLS: &gwapi.GatewayTLSConfig{ + Protocol: gwapi.HTTPSProtocolType, + TLS: &gwapi.ListenerTLSConfig{ Mode: ptrMode(gwapi.TLSModeTerminate), CertificateRefs: []gwapi.SecretObjectReference{ { @@ -1689,12 +2309,12 @@ func TestSync(t *testing.T) { ObjectMeta: metav1.ObjectMeta{ Name: "example-com-tls", Namespace: gen.DefaultTestNamespace, - OwnerReferences: buildGatewayOwnerReferences("gateway-name", gen.DefaultTestNamespace), + OwnerReferences: buildGatewayOwnerReferences("gateway-name"), }, Spec: cmapi.CertificateSpec{ DNSNames: []string{"example.com"}, SecretName: "example-com-tls", - IssuerRef: cmmeta.ObjectReference{ + IssuerRef: cmmeta.IssuerReference{ Name: "issuer-name", Kind: "ClusterIssuer", }, @@ -1721,8 +2341,8 @@ func TestSync(t *testing.T) { Listeners: []gwapi.Listener{{ Hostname: ptrHostname("example.com"), Port: 443, - Protocol: "HTTPS", - TLS: &gwapi.GatewayTLSConfig{ + Protocol: gwapi.HTTPSProtocolType, + TLS: &gwapi.ListenerTLSConfig{ Mode: ptrMode(gwapi.TLSModeTerminate), CertificateRefs: []gwapi.SecretObjectReference{ { @@ -1742,12 +2362,12 @@ func TestSync(t *testing.T) { ObjectMeta: metav1.ObjectMeta{ Name: "example-com-tls", Namespace: gen.DefaultTestNamespace, - OwnerReferences: buildGatewayOwnerReferences("gateway-name", gen.DefaultTestNamespace), + OwnerReferences: buildGatewayOwnerReferences("gateway-name"), }, Spec: cmapi.CertificateSpec{ DNSNames: []string{"example.com"}, SecretName: "example-com-tls", - IssuerRef: cmmeta.ObjectReference{ + IssuerRef: cmmeta.IssuerReference{ Name: "issuer-name", Kind: "ClusterIssuer", }, @@ -1775,8 +2395,8 @@ func TestSync(t *testing.T) { Listeners: []gwapi.Listener{{ Hostname: ptrHostname("example.com"), Port: 443, - Protocol: "HTTPS", - TLS: &gwapi.GatewayTLSConfig{ + Protocol: gwapi.HTTPSProtocolType, + TLS: &gwapi.ListenerTLSConfig{ Mode: ptrMode(gwapi.TLSModeTerminate), CertificateRefs: []gwapi.SecretObjectReference{ { @@ -1796,7 +2416,7 @@ func TestSync(t *testing.T) { ObjectMeta: metav1.ObjectMeta{ Name: "example-com-tls", Namespace: gen.DefaultTestNamespace, - OwnerReferences: buildGatewayOwnerReferences("gateway-name", gen.DefaultTestNamespace), + OwnerReferences: buildGatewayOwnerReferences("gateway-name"), Annotations: map[string]string{ cmacme.ACMECertificateHTTP01IngressClassOverride: "cert-ing", }, @@ -1804,7 +2424,7 @@ func TestSync(t *testing.T) { Spec: cmapi.CertificateSpec{ DNSNames: []string{"example.com"}, SecretName: "example-com-tls", - IssuerRef: cmmeta.ObjectReference{ + IssuerRef: cmmeta.IssuerReference{ Name: "issuer-name", Kind: "ClusterIssuer", }, @@ -1832,8 +2452,8 @@ func TestSync(t *testing.T) { Listeners: []gwapi.Listener{{ Hostname: ptrHostname("example.com"), Port: 443, - Protocol: "HTTPS", - TLS: &gwapi.GatewayTLSConfig{ + Protocol: gwapi.HTTPSProtocolType, + TLS: &gwapi.ListenerTLSConfig{ Mode: ptrMode(gwapi.TLSModeTerminate), CertificateRefs: []gwapi.SecretObjectReference{ { @@ -1853,12 +2473,12 @@ func TestSync(t *testing.T) { ObjectMeta: metav1.ObjectMeta{ Name: "example-com-tls", Namespace: gen.DefaultTestNamespace, - OwnerReferences: buildGatewayOwnerReferences("gateway-name", gen.DefaultTestNamespace), + OwnerReferences: buildGatewayOwnerReferences("gateway-name"), }, Spec: cmapi.CertificateSpec{ DNSNames: []string{"example.com"}, SecretName: "example-com-tls", - IssuerRef: cmmeta.ObjectReference{ + IssuerRef: cmmeta.IssuerReference{ Name: "issuer-name", Kind: "ClusterIssuer", }, @@ -1884,8 +2504,8 @@ func TestSync(t *testing.T) { Listeners: []gwapi.Listener{{ Hostname: ptrHostname("example.com"), Port: 443, - Protocol: "HTTPS", - TLS: &gwapi.GatewayTLSConfig{ + Protocol: gwapi.HTTPSProtocolType, + TLS: &gwapi.ListenerTLSConfig{ Mode: ptrMode(gwapi.TLSModeTerminate), CertificateRefs: []gwapi.SecretObjectReference{ { @@ -1905,12 +2525,12 @@ func TestSync(t *testing.T) { ObjectMeta: metav1.ObjectMeta{ Name: "example-com-tls", Namespace: gen.DefaultTestNamespace, - OwnerReferences: buildGatewayOwnerReferences("gateway-name", gen.DefaultTestNamespace), + OwnerReferences: buildGatewayOwnerReferences("gateway-name"), }, Spec: cmapi.CertificateSpec{ DNSNames: []string{"example.com"}, SecretName: "example-com-tls", - IssuerRef: cmmeta.ObjectReference{ + IssuerRef: cmmeta.IssuerReference{ Name: "issuer-name", Kind: "ClusterIssuer", }, @@ -1940,8 +2560,8 @@ func TestSync(t *testing.T) { Listeners: []gwapi.Listener{{ Hostname: ptrHostname("example.com"), Port: 443, - Protocol: "HTTPS", - TLS: &gwapi.GatewayTLSConfig{ + Protocol: gwapi.HTTPSProtocolType, + TLS: &gwapi.ListenerTLSConfig{ Mode: ptrMode(gwapi.TLSModeTerminate), CertificateRefs: []gwapi.SecretObjectReference{ { @@ -1977,8 +2597,8 @@ func TestSync(t *testing.T) { Listeners: []gwapi.Listener{{ Hostname: ptrHostname("example.com"), Port: 443, - Protocol: "HTTPS", - TLS: &gwapi.GatewayTLSConfig{ + Protocol: gwapi.HTTPSProtocolType, + TLS: &gwapi.ListenerTLSConfig{ Mode: ptrMode(gwapi.TLSModeTerminate), CertificateRefs: []gwapi.SecretObjectReference{ { @@ -1991,8 +2611,8 @@ func TestSync(t *testing.T) { }, { Hostname: nil, // 🔥 Port: 443, - Protocol: "HTTPS", - TLS: &gwapi.GatewayTLSConfig{ + Protocol: gwapi.HTTPSProtocolType, + TLS: &gwapi.ListenerTLSConfig{ Mode: ptrMode(gwapi.TLSModeTerminate), CertificateRefs: []gwapi.SecretObjectReference{ { @@ -2010,13 +2630,13 @@ func TestSync(t *testing.T) { ObjectMeta: metav1.ObjectMeta{ Name: "example-com-tls", Namespace: gen.DefaultTestNamespace, - OwnerReferences: buildGatewayOwnerReferences("gateway-name", gen.DefaultTestNamespace), + OwnerReferences: buildGatewayOwnerReferences("gateway-name"), }, Spec: cmapi.CertificateSpec{ DNSNames: []string{"example.com"}, SecretName: "example-com-tls", Usages: cmapi.DefaultKeyUsages(), - IssuerRef: cmmeta.ObjectReference{ + IssuerRef: cmmeta.IssuerReference{ Name: "issuer-name", Kind: "Issuer", }, @@ -2046,16 +2666,16 @@ func TestSync(t *testing.T) { Listeners: []gwapi.Listener{{ Hostname: ptrHostname("example.com"), Port: 443, - Protocol: "HTTPS", - TLS: &gwapi.GatewayTLSConfig{ + Protocol: gwapi.HTTPSProtocolType, + TLS: &gwapi.ListenerTLSConfig{ Mode: ptrMode(gwapi.TLSModeTerminate), CertificateRefs: []gwapi.SecretObjectReference{}, }, }, { Hostname: ptrHostname("www.example.com"), Port: 443, - Protocol: "HTTPS", - TLS: &gwapi.GatewayTLSConfig{ + Protocol: gwapi.HTTPSProtocolType, + TLS: &gwapi.ListenerTLSConfig{ Mode: ptrMode(gwapi.TLSModeTerminate), CertificateRefs: []gwapi.SecretObjectReference{ { @@ -2073,13 +2693,75 @@ func TestSync(t *testing.T) { ObjectMeta: metav1.ObjectMeta{ Name: "example-com-tls", Namespace: gen.DefaultTestNamespace, - OwnerReferences: buildGatewayOwnerReferences("gateway-name", gen.DefaultTestNamespace), + OwnerReferences: buildGatewayOwnerReferences("gateway-name"), + }, + Spec: cmapi.CertificateSpec{ + DNSNames: []string{"www.example.com"}, + SecretName: "example-com-tls", + Usages: cmapi.DefaultKeyUsages(), + IssuerRef: cmmeta.IssuerReference{ + Name: "issuer-name", + Kind: "Issuer", + }, + }, + }, + }, + }, + { + Name: "should skip TLS protocol listener in TLS passthrough mode", + Issuer: acmeIssuer, + IssuerLister: []runtime.Object{acmeIssuer}, + ExpectedEvents: []string{ + `Normal CreateCertificate Successfully created Certificate "example-com-tls"`, + }, + IngressLike: &gwapi.Gateway{ + ObjectMeta: metav1.ObjectMeta{ + Name: "gateway-name", + Namespace: gen.DefaultTestNamespace, + Annotations: map[string]string{ + cmapi.IngressIssuerNameAnnotationKey: "issuer-name", + }, + UID: types.UID("gateway-name"), + }, + Spec: gwapi.GatewaySpec{ + GatewayClassName: "test-gateway", + Listeners: []gwapi.Listener{{ + Hostname: ptrHostname("example.com"), + Port: 443, + Protocol: gwapi.HTTPSProtocolType, + TLS: &gwapi.ListenerTLSConfig{ + Mode: ptrMode(gwapi.TLSModeTerminate), + CertificateRefs: []gwapi.SecretObjectReference{ + { + Group: func() *gwapi.Group { g := gwapi.Group("core"); return &g }(), + Kind: func() *gwapi.Kind { k := gwapi.Kind("Secret"); return &k }(), + Name: "example-com-tls", + }, + }, + }, + }, { + Hostname: ptrHostname("subdomain.example.com"), + Port: 443, + Protocol: gwapi.TLSProtocolType, + TLS: &gwapi.ListenerTLSConfig{ + Mode: ptrMode(gwapi.TLSModePassthrough), + CertificateRefs: []gwapi.SecretObjectReference{}, + }, + }}, + }, + }, + ExpectedCreate: []*cmapi.Certificate{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "example-com-tls", + Namespace: gen.DefaultTestNamespace, + OwnerReferences: buildGatewayOwnerReferences("gateway-name"), }, Spec: cmapi.CertificateSpec{ - DNSNames: []string{"www.example.com"}, + DNSNames: []string{"example.com"}, SecretName: "example-com-tls", Usages: cmapi.DefaultKeyUsages(), - IssuerRef: cmmeta.ObjectReference{ + IssuerRef: cmmeta.IssuerReference{ Name: "issuer-name", Kind: "Issuer", }, @@ -2118,8 +2800,8 @@ func TestSync(t *testing.T) { Listeners: []gwapi.Listener{{ Hostname: ptrHostname("example.com"), Port: 443, - Protocol: "HTTPS", - TLS: &gwapi.GatewayTLSConfig{ + Protocol: gwapi.HTTPSProtocolType, + TLS: &gwapi.ListenerTLSConfig{ Mode: ptrMode(gwapi.TLSModeTerminate), CertificateRefs: []gwapi.SecretObjectReference{ { @@ -2139,12 +2821,12 @@ func TestSync(t *testing.T) { ObjectMeta: metav1.ObjectMeta{ Name: "existing-crt", Namespace: gen.DefaultTestNamespace, - OwnerReferences: buildGatewayOwnerReferences("gateway-name", gen.DefaultTestNamespace), + OwnerReferences: buildGatewayOwnerReferences("gateway-name"), }, Spec: cmapi.CertificateSpec{ DNSNames: []string{"example.com"}, SecretName: "existing-crt", - IssuerRef: cmmeta.ObjectReference{ + IssuerRef: cmmeta.IssuerReference{ Name: "issuer-name", Kind: "Issuer", Group: "cert-manager.io", @@ -2172,8 +2854,8 @@ func TestSync(t *testing.T) { Listeners: []gwapi.Listener{{ Hostname: ptrHostname("example.com"), Port: 443, - Protocol: "HTTPS", - TLS: &gwapi.GatewayTLSConfig{ + Protocol: gwapi.HTTPSProtocolType, + TLS: &gwapi.ListenerTLSConfig{ Mode: ptrMode(gwapi.TLSModeTerminate), CertificateRefs: []gwapi.SecretObjectReference{ { @@ -2189,7 +2871,7 @@ func TestSync(t *testing.T) { CertificateLister: []runtime.Object{ buildCertificate("existing-crt", gen.DefaultTestNamespace, - buildGatewayOwnerReferences("gateway-name", gen.DefaultTestNamespace), + buildGatewayOwnerReferences("gateway-name"), ), }, DefaultIssuerKind: "Issuer", @@ -2199,12 +2881,12 @@ func TestSync(t *testing.T) { ObjectMeta: metav1.ObjectMeta{ Name: "existing-crt", Namespace: gen.DefaultTestNamespace, - OwnerReferences: buildGatewayOwnerReferences("gateway-name", gen.DefaultTestNamespace), + OwnerReferences: buildGatewayOwnerReferences("gateway-name"), }, Spec: cmapi.CertificateSpec{ DNSNames: []string{"example.com"}, SecretName: "existing-crt", - IssuerRef: cmmeta.ObjectReference{ + IssuerRef: cmmeta.IssuerReference{ Name: "issuer-name", Kind: "Issuer", }, @@ -2234,8 +2916,8 @@ func TestSync(t *testing.T) { Listeners: []gwapi.Listener{{ Hostname: ptrHostname("example.com"), Port: 443, - Protocol: "HTTPS", - TLS: &gwapi.GatewayTLSConfig{ + Protocol: gwapi.HTTPSProtocolType, + TLS: &gwapi.ListenerTLSConfig{ Mode: ptrMode(gwapi.TLSModeTerminate), CertificateRefs: []gwapi.SecretObjectReference{ { @@ -2257,12 +2939,12 @@ func TestSync(t *testing.T) { Labels: map[string]string{ "a-different-value": "should be removed", }, - OwnerReferences: buildGatewayOwnerReferences("gateway-name", gen.DefaultTestNamespace), + OwnerReferences: buildGatewayOwnerReferences("gateway-name"), }, Spec: cmapi.CertificateSpec{ DNSNames: []string{"example.com"}, SecretName: "cert-secret-name", - IssuerRef: cmmeta.ObjectReference{ + IssuerRef: cmmeta.IssuerReference{ Name: "issuer-name", Kind: "Issuer", }, @@ -2279,12 +2961,12 @@ func TestSync(t *testing.T) { Labels: map[string]string{ "my-test-label": "should be copied", }, - OwnerReferences: buildGatewayOwnerReferences("gateway-name", gen.DefaultTestNamespace), + OwnerReferences: buildGatewayOwnerReferences("gateway-name"), }, Spec: cmapi.CertificateSpec{ DNSNames: []string{"example.com"}, SecretName: "cert-secret-name", - IssuerRef: cmmeta.ObjectReference{ + IssuerRef: cmmeta.IssuerReference{ Name: "issuer-name", Kind: "Issuer", }, @@ -2312,8 +2994,8 @@ func TestSync(t *testing.T) { Listeners: []gwapi.Listener{{ Hostname: ptrHostname("example.com"), Port: 443, - Protocol: "HTTPS", - TLS: &gwapi.GatewayTLSConfig{ + Protocol: gwapi.HTTPSProtocolType, + TLS: &gwapi.ListenerTLSConfig{ Mode: ptrMode(gwapi.TLSModeTerminate), CertificateRefs: []gwapi.SecretObjectReference{ { @@ -2336,7 +3018,7 @@ func TestSync(t *testing.T) { Spec: cmapi.CertificateSpec{ DNSNames: []string{"example.com"}, SecretName: "existing-crt", - IssuerRef: cmmeta.ObjectReference{ + IssuerRef: cmmeta.IssuerReference{ Name: "issuer-name", Kind: "Issuer", }, @@ -2364,8 +3046,8 @@ func TestSync(t *testing.T) { Listeners: []gwapi.Listener{{ Hostname: ptrHostname("example.com"), Port: 443, - Protocol: "HTTPS", - TLS: &gwapi.GatewayTLSConfig{ + Protocol: gwapi.HTTPSProtocolType, + TLS: &gwapi.ListenerTLSConfig{ Mode: ptrMode(gwapi.TLSModeTerminate), CertificateRefs: []gwapi.SecretObjectReference{ { @@ -2383,12 +3065,12 @@ func TestSync(t *testing.T) { ObjectMeta: metav1.ObjectMeta{ Name: "existing-crt", Namespace: gen.DefaultTestNamespace, - OwnerReferences: buildIngressOwnerReferences("not-gateway-name", gen.DefaultTestNamespace), + OwnerReferences: buildIngressOwnerReferences("not-gateway-name"), }, Spec: cmapi.CertificateSpec{ DNSNames: []string{"example.com"}, SecretName: "existing-crt", - IssuerRef: cmmeta.ObjectReference{ + IssuerRef: cmmeta.IssuerReference{ Name: "issuer-name", Kind: "Issuer", }, @@ -2416,12 +3098,12 @@ func TestSync(t *testing.T) { ObjectMeta: metav1.ObjectMeta{ Name: "existing-crt", Namespace: gen.DefaultTestNamespace, - OwnerReferences: buildGatewayOwnerReferences("gateway-name", gen.DefaultTestNamespace), + OwnerReferences: buildGatewayOwnerReferences("gateway-name"), }, Spec: cmapi.CertificateSpec{ DNSNames: []string{"example.com"}, SecretName: "existing-crt", - IssuerRef: cmmeta.ObjectReference{ + IssuerRef: cmmeta.IssuerReference{ Name: "issuer-name", Kind: "Issuer", }, @@ -2435,12 +3117,12 @@ func TestSync(t *testing.T) { ObjectMeta: metav1.ObjectMeta{ Name: "existing-crt", Namespace: gen.DefaultTestNamespace, - OwnerReferences: buildGatewayOwnerReferences("gateway-name", gen.DefaultTestNamespace), + OwnerReferences: buildGatewayOwnerReferences("gateway-name"), }, Spec: cmapi.CertificateSpec{ DNSNames: []string{"example.com"}, SecretName: "existing-crt", - IssuerRef: cmmeta.ObjectReference{ + IssuerRef: cmmeta.IssuerReference{ Name: "issuer-name", Kind: "Issuer", }, @@ -2468,8 +3150,8 @@ func TestSync(t *testing.T) { Listeners: []gwapi.Listener{{ Hostname: ptrHostname("example.com"), Port: 443, - Protocol: "HTTPS", - TLS: &gwapi.GatewayTLSConfig{ + Protocol: gwapi.HTTPSProtocolType, + TLS: &gwapi.ListenerTLSConfig{ Mode: ptrMode(gwapi.TLSModeTerminate), CertificateRefs: []gwapi.SecretObjectReference{ { @@ -2487,13 +3169,13 @@ func TestSync(t *testing.T) { ObjectMeta: metav1.ObjectMeta{ Name: "example-com-tls", Namespace: gen.DefaultTestNamespace, - OwnerReferences: buildGatewayOwnerReferences("gateway-name", gen.DefaultTestNamespace), + OwnerReferences: buildGatewayOwnerReferences("gateway-name"), }, Spec: cmapi.CertificateSpec{ DNSNames: []string{"example.com"}, SecretName: "example-com-tls", CommonName: "example-common-name", - IssuerRef: cmmeta.ObjectReference{ + IssuerRef: cmmeta.IssuerReference{ Name: "issuer-name", Kind: "Issuer", Group: "cert-manager.io", @@ -2508,12 +3190,12 @@ func TestSync(t *testing.T) { ObjectMeta: metav1.ObjectMeta{ Name: "example-com-tls", Namespace: gen.DefaultTestNamespace, - OwnerReferences: buildGatewayOwnerReferences("gateway-name", gen.DefaultTestNamespace), + OwnerReferences: buildGatewayOwnerReferences("gateway-name"), }, Spec: cmapi.CertificateSpec{ DNSNames: []string{"example.com"}, SecretName: "example-com-tls", - IssuerRef: cmmeta.ObjectReference{ + IssuerRef: cmmeta.IssuerReference{ Name: "issuer-name", Kind: "Issuer", Group: "cert-manager.io", @@ -2543,8 +3225,8 @@ func TestSync(t *testing.T) { Listeners: []gwapi.Listener{{ Hostname: ptrHostname("example.com"), Port: 443, - Protocol: "HTTPS", - TLS: &gwapi.GatewayTLSConfig{ + Protocol: gwapi.HTTPSProtocolType, + TLS: &gwapi.ListenerTLSConfig{ Mode: ptrMode(gwapi.TLSModeTerminate), CertificateRefs: []gwapi.SecretObjectReference{ { @@ -2557,8 +3239,8 @@ func TestSync(t *testing.T) { }, { Hostname: ptrHostname("www.example.com"), Port: 443, - Protocol: "HTTPS", - TLS: &gwapi.GatewayTLSConfig{ + Protocol: gwapi.HTTPSProtocolType, + TLS: &gwapi.ListenerTLSConfig{ Mode: ptrMode(gwapi.TLSModeTerminate), CertificateRefs: []gwapi.SecretObjectReference{ { @@ -2571,8 +3253,8 @@ func TestSync(t *testing.T) { }, { Hostname: ptrHostname("foo.example.com"), Port: 443, - Protocol: "HTTPS", - TLS: &gwapi.GatewayTLSConfig{ + Protocol: gwapi.HTTPSProtocolType, + TLS: &gwapi.ListenerTLSConfig{ Mode: ptrMode(gwapi.TLSModeTerminate), CertificateRefs: []gwapi.SecretObjectReference{ { @@ -2593,12 +3275,12 @@ func TestSync(t *testing.T) { ObjectMeta: metav1.ObjectMeta{ Name: "example-com-tls", Namespace: gen.DefaultTestNamespace, - OwnerReferences: buildGatewayOwnerReferences("gateway-name", gen.DefaultTestNamespace), + OwnerReferences: buildGatewayOwnerReferences("gateway-name"), }, Spec: cmapi.CertificateSpec{ DNSNames: []string{"example.com", "www.example.com", "foo.example.com"}, SecretName: "example-com-tls", - IssuerRef: cmmeta.ObjectReference{ + IssuerRef: cmmeta.IssuerReference{ Name: "issuer-name", Kind: "Issuer", Group: "cert-manager.io", @@ -2628,8 +3310,8 @@ func TestSync(t *testing.T) { Listeners: []gwapi.Listener{{ Hostname: ptrHostname("foo.example.com"), Port: 443, - Protocol: "HTTPS", - TLS: &gwapi.GatewayTLSConfig{ + Protocol: gwapi.HTTPSProtocolType, + TLS: &gwapi.ListenerTLSConfig{ Mode: ptrMode(gwapi.TLSModeTerminate), CertificateRefs: []gwapi.SecretObjectReference{ { @@ -2642,8 +3324,8 @@ func TestSync(t *testing.T) { }, { Hostname: ptrHostname("bar.example.com"), Port: 443, - Protocol: "HTTPS", - TLS: &gwapi.GatewayTLSConfig{ + Protocol: gwapi.HTTPSProtocolType, + TLS: &gwapi.ListenerTLSConfig{ Mode: ptrMode(gwapi.TLSModeTerminate), CertificateRefs: []gwapi.SecretObjectReference{ { @@ -2665,12 +3347,12 @@ func TestSync(t *testing.T) { ObjectMeta: metav1.ObjectMeta{ Name: "foo-example-com-tls", Namespace: gen.DefaultTestNamespace, - OwnerReferences: buildGatewayOwnerReferences("gateway-name", gen.DefaultTestNamespace), + OwnerReferences: buildGatewayOwnerReferences("gateway-name"), }, Spec: cmapi.CertificateSpec{ DNSNames: []string{"foo.example.com"}, SecretName: "foo-example-com-tls", - IssuerRef: cmmeta.ObjectReference{ + IssuerRef: cmmeta.IssuerReference{ Name: "issuer-name", Kind: "Issuer", Group: "cert-manager.io", @@ -2682,12 +3364,12 @@ func TestSync(t *testing.T) { ObjectMeta: metav1.ObjectMeta{ Name: "bar-example-com-tls", Namespace: gen.DefaultTestNamespace, - OwnerReferences: buildGatewayOwnerReferences("gateway-name", gen.DefaultTestNamespace), + OwnerReferences: buildGatewayOwnerReferences("gateway-name"), }, Spec: cmapi.CertificateSpec{ DNSNames: []string{"bar.example.com"}, SecretName: "bar-example-com-tls", - IssuerRef: cmmeta.ObjectReference{ + IssuerRef: cmmeta.IssuerReference{ Name: "issuer-name", Kind: "Issuer", Group: "cert-manager.io", @@ -2717,8 +3399,8 @@ func TestSync(t *testing.T) { Listeners: []gwapi.Listener{{ Hostname: ptrHostname("example.com"), Port: 443, - Protocol: "HTTPS", - TLS: &gwapi.GatewayTLSConfig{ + Protocol: gwapi.HTTPSProtocolType, + TLS: &gwapi.ListenerTLSConfig{ Mode: ptrMode(gwapi.TLSModeTerminate), CertificateRefs: []gwapi.SecretObjectReference{ { @@ -2755,8 +3437,8 @@ func TestSync(t *testing.T) { Listeners: []gwapi.Listener{{ Hostname: ptrHostname("example.com"), Port: 443, - Protocol: "HTTPS", - TLS: &gwapi.GatewayTLSConfig{ + Protocol: gwapi.HTTPSProtocolType, + TLS: &gwapi.ListenerTLSConfig{ Mode: ptrMode(gwapi.TLSModeTerminate), CertificateRefs: []gwapi.SecretObjectReference{ { @@ -2779,13 +3461,13 @@ func TestSync(t *testing.T) { Labels: map[string]string{ "my-test-label": "should be copied", }, - OwnerReferences: buildGatewayOwnerReferences("gateway-name", gen.DefaultTestNamespace), + OwnerReferences: buildGatewayOwnerReferences("gateway-name"), }, Spec: cmapi.CertificateSpec{ DNSNames: []string{"example.com"}, CommonName: "my-cn", SecretName: "example-com-tls", - IssuerRef: cmmeta.ObjectReference{ + IssuerRef: cmmeta.IssuerReference{ Name: "issuer-name", Kind: "ClusterIssuer", }, @@ -2798,6 +3480,142 @@ func TestSync(t *testing.T) { }, }, }, + { + Name: "should not trigger a gateway sync if deleted in foreground", + Issuer: clusterIssuer, + DefaultIssuerName: "issuer-name", + DefaultIssuerKind: "ClusterIssuer", + DefaultIssuerGroup: "cert-manager.io", + ClusterIssuerLister: []runtime.Object{clusterIssuer}, + IngressLike: buildGatewayInDeletion(buildGateway("", "", map[string]string{cmapi.IngressIssuerNameAnnotationKey: ""}), &metav1.Time{}, []string{metav1.FinalizerDeleteDependents}), + }, + { + Name: "return a single Certificate for a Gateway with a single valid TLS entry and common-name annotation (HTTPS)", + Issuer: acmeClusterIssuer, + IngressLike: &gwapi.Gateway{ + ObjectMeta: metav1.ObjectMeta{ + Name: "gateway-name", + Namespace: gen.DefaultTestNamespace, + Labels: map[string]string{ + "my-test-label": "should be copied", + applysetLabel: "should not be propagated", + }, + Annotations: map[string]string{ + cmapi.IngressClusterIssuerNameAnnotationKey: "issuer-name", + cmapi.CommonNameAnnotationKey: "my-cn", + }, + UID: types.UID("gateway-name"), + }, + Spec: gwapi.GatewaySpec{ + GatewayClassName: "test-gateway", + Listeners: []gwapi.Listener{ + { + Hostname: ptrHostname("example.com"), + Port: 443, + Protocol: gwapi.HTTPSProtocolType, + TLS: &gwapi.ListenerTLSConfig{ + Mode: ptrMode(gwapi.TLSModeTerminate), + CertificateRefs: []gwapi.SecretObjectReference{ + { + Group: func() *gwapi.Group { g := gwapi.Group("core"); return &g }(), + Kind: func() *gwapi.Kind { k := gwapi.Kind("Secret"); return &k }(), + Name: "example-com-tls", + }, + }, + }, + }, + }, + }, + }, + ClusterIssuerLister: []runtime.Object{acmeClusterIssuer}, + ExpectedEvents: []string{`Normal CreateCertificate Successfully created Certificate "example-com-tls"`}, + ExpectedCreate: []*cmapi.Certificate{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "example-com-tls", + Namespace: gen.DefaultTestNamespace, + Labels: map[string]string{ + "my-test-label": "should be copied", + // note that the applyset label should not be here + }, + OwnerReferences: buildGatewayOwnerReferences("gateway-name"), + }, + Spec: cmapi.CertificateSpec{ + DNSNames: []string{"example.com"}, + CommonName: "my-cn", + SecretName: "example-com-tls", + IssuerRef: cmmeta.IssuerReference{ + Name: "issuer-name", + Kind: "ClusterIssuer", + }, + Usages: cmapi.DefaultKeyUsages(), + }, + }, + }, + }, + { + Name: "extra Gateway annotation is copied to Certificate object", + Issuer: acmeClusterIssuer, + IngressLike: &gwapi.Gateway{ + ObjectMeta: metav1.ObjectMeta{ + Name: "gateway-name", + Namespace: gen.DefaultTestNamespace, + Labels: map[string]string{}, + Annotations: map[string]string{ + cmapi.IngressClusterIssuerNameAnnotationKey: "issuer-name", + cmapi.CommonNameAnnotationKey: "my-cn", + "venafi.cert-manager.io/custom-fields": "foo", + "venafi.cert-manager.io/do-not-copy": "bar", + }, + UID: types.UID("gateway-name"), + }, + Spec: gwapi.GatewaySpec{ + GatewayClassName: "test-gateway", + Listeners: []gwapi.Listener{ + { + Hostname: ptrHostname("example.com"), + Port: 443, + Protocol: gwapi.HTTPSProtocolType, + TLS: &gwapi.ListenerTLSConfig{ + Mode: ptrMode(gwapi.TLSModeTerminate), + CertificateRefs: []gwapi.SecretObjectReference{ + { + Group: func() *gwapi.Group { g := gwapi.Group("core"); return &g }(), + Kind: func() *gwapi.Kind { k := gwapi.Kind("Secret"); return &k }(), + Name: "example-com-tls", + }, + }, + }, + }, + }, + }, + }, + ClusterIssuerLister: []runtime.Object{acmeClusterIssuer}, + ExpectedEvents: []string{`Normal CreateCertificate Successfully created Certificate "example-com-tls"`}, + ExpectedCreate: []*cmapi.Certificate{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "example-com-tls", + Namespace: gen.DefaultTestNamespace, + Labels: map[string]string{}, + Annotations: map[string]string{ + "venafi.cert-manager.io/custom-fields": "foo", + }, + OwnerReferences: buildGatewayOwnerReferences("gateway-name"), + }, + Spec: cmapi.CertificateSpec{ + DNSNames: []string{"example.com"}, + CommonName: "my-cn", + SecretName: "example-com-tls", + IssuerRef: cmmeta.IssuerReference{ + Name: "issuer-name", + Kind: "ClusterIssuer", + }, + Usages: cmapi.DefaultKeyUsages(), + }, + }, + }, + }, } testFn := func(test testT) func(t *testing.T) { @@ -2809,28 +3627,37 @@ func TestSync(t *testing.T) { var expectedActions []testpkg.Action for _, cr := range test.ExpectedCreate { expectedActions = append(expectedActions, - testpkg.NewAction(coretesting.NewCreateAction( + testpkg.NewAction(coretesting.NewCreateActionWithOptions( cmapi.SchemeGroupVersion.WithResource("certificates"), cr.Namespace, cr, + metav1.CreateOptions{ + FieldManager: "cert-manager-test", + }, )), ) } for _, cr := range test.ExpectedUpdate { expectedActions = append(expectedActions, - testpkg.NewAction(coretesting.NewUpdateAction( + testpkg.NewAction(coretesting.NewUpdateActionWithOptions( cmapi.SchemeGroupVersion.WithResource("certificates"), cr.Namespace, cr, + metav1.UpdateOptions{ + // TODO: set field manager here too + }, )), ) } for _, cr := range test.ExpectedDelete { expectedActions = append(expectedActions, - testpkg.NewAction(coretesting.NewDeleteAction( + testpkg.NewAction(coretesting.NewDeleteActionWithOptions( cmapi.SchemeGroupVersion.WithResource("certificates"), cr.Namespace, cr.Name, + metav1.DeleteOptions{ + // TODO: set field manager here too + }, ))) } b := &testpkg.Builder{ @@ -2841,15 +3668,16 @@ func TestSync(t *testing.T) { } b.Init() defer b.Stop() - sync := SyncFnFor(b.Recorder, logr.Discard(), b.CMClient, b.SharedInformerFactory.Certmanager().V1().Certificates().Lister(), controller.IngressShimOptions{ + sync := SyncFnFor(b.Recorder, logr.Discard(), b.CMClient, b.SharedInformerFactory.Certmanager().V1().Certificates().Lister(), controllerpkg.IngressShimOptions{ DefaultIssuerName: test.DefaultIssuerName, DefaultIssuerKind: test.DefaultIssuerKind, DefaultIssuerGroup: test.DefaultIssuerGroup, DefaultAutoCertificateAnnotations: []string{"kubernetes.io/tls-acme"}, + ExtraCertificateAnnotations: []string{"venafi.cert-manager.io/custom-fields"}, }, "cert-manager-test") b.Start() - err := sync(context.Background(), test.IngressLike) + err := sync(t.Context(), test.IngressLike) // If test.Err == true, err should not be nil and vice versa if test.Err == (err == nil) { @@ -2859,11 +3687,8 @@ func TestSync(t *testing.T) { if err := b.AllEventsCalled(); err != nil { t.Error(err) } - if err := b.AllReactorsCalled(); err != nil { - t.Errorf("Not all expected reactors were called: %v", err) - } if err := b.AllActionsExecuted(); err != nil { - t.Errorf(err.Error()) + t.Error(err) } } } @@ -2878,18 +3703,6 @@ func TestSync(t *testing.T) { t.Run(test.Name, testFn(test)) } }) - -} - -type fakeHelper struct { - issuer cmapi.GenericIssuer -} - -func (f *fakeHelper) GetGenericIssuer(ref cmmeta.ObjectReference, ns string) (cmapi.GenericIssuer, error) { - if f.issuer == nil { - return nil, fmt.Errorf("no issuer specified on fake helper") - } - return f.issuer, nil } func TestIssuerForIngress(t *testing.T) { @@ -2980,6 +3793,34 @@ func TestIssuerForIngress(t *testing.T) { } } +func TestExtractAnnotations(t *testing.T) { + type testT struct { + IngressLike *networkingv1.Ingress + Expected map[string]string + } + tests := []testT{ + { + IngressLike: buildIngress("ing1", "namespace1", map[string]string{ + "key1": "value1", + "key2": "value2", + }), + Expected: map[string]string{ + "key1": "value1", + }, + }, + { + IngressLike: buildIngress("name", "namespace", nil), + Expected: nil, + }, + } + for _, test := range tests { + annotations := extractExtraAnnotations(test.IngressLike, []string{"key1"}) + if !reflect.DeepEqual(annotations, test.Expected) { + t.Errorf("expected annotations to be %v but got %v", test.Expected, annotations) + } + } +} + func buildCertificate(name, namespace string, ownerReferences []metav1.OwnerReference) *cmapi.Certificate { return &cmapi.Certificate{ ObjectMeta: metav1.ObjectMeta{ @@ -3010,20 +3851,21 @@ func buildGateway(name, namespace string, annotations map[string]string) *gwapi. Name: name, Namespace: namespace, Annotations: annotations, + UID: types.UID(name), }, } } -func buildIngressOwnerReferences(name, namespace string) []metav1.OwnerReference { +func buildIngressOwnerReferences(name string) []metav1.OwnerReference { return []metav1.OwnerReference{ - *metav1.NewControllerRef(buildIngress(name, namespace, nil), ingressV1GVK), + *metav1.NewControllerRef(buildIngress(name, gen.DefaultTestNamespace, nil), ingressV1GVK), } } // The Gateway name and UID are set to the same. -func buildGatewayOwnerReferences(name, namespace string) []metav1.OwnerReference { +func buildGatewayOwnerReferences(name string) []metav1.OwnerReference { return []metav1.OwnerReference{ - *metav1.NewControllerRef(buildIngress(name, namespace, nil), gatewayGVK), + *metav1.NewControllerRef(buildGateway(name, gen.DefaultTestNamespace, nil), gatewayGVK), } } @@ -3039,11 +3881,18 @@ func ptrMode(mode gwapi.TLSModeType) *gwapi.TLSModeType { func Test_validateGatewayListenerBlock(t *testing.T) { tests := []struct { name string + ingLike metav1.Object listener gwapi.Listener wantErr string }{ { name: "empty TLS block", + ingLike: &gwapi.Gateway{ + ObjectMeta: metav1.ObjectMeta{ + Name: "gateway", + Namespace: gen.DefaultTestNamespace, + }, + }, listener: gwapi.Listener{ Hostname: ptrHostname("example.com"), Port: gwapi.PortNumber(443), @@ -3053,11 +3902,17 @@ func Test_validateGatewayListenerBlock(t *testing.T) { }, { name: "empty hostname", + ingLike: &gwapi.Gateway{ + ObjectMeta: metav1.ObjectMeta{ + Name: "gateway", + Namespace: gen.DefaultTestNamespace, + }, + }, listener: gwapi.Listener{ Hostname: ptrHostname(""), Port: gwapi.PortNumber(443), Protocol: gwapi.HTTPSProtocolType, - TLS: &gwapi.GatewayTLSConfig{ + TLS: &gwapi.ListenerTLSConfig{ Mode: ptrMode(gwapi.TLSModeTerminate), CertificateRefs: []gwapi.SecretObjectReference{ { @@ -3072,11 +3927,17 @@ func Test_validateGatewayListenerBlock(t *testing.T) { }, { name: "empty group", + ingLike: &gwapi.Gateway{ + ObjectMeta: metav1.ObjectMeta{ + Name: "example", + Namespace: gen.DefaultTestNamespace, + }, + }, listener: gwapi.Listener{ Hostname: ptrHostname("example.com"), Port: gwapi.PortNumber(443), Protocol: gwapi.HTTPSProtocolType, - TLS: &gwapi.GatewayTLSConfig{ + TLS: &gwapi.ListenerTLSConfig{ Mode: ptrMode(gwapi.TLSModeTerminate), CertificateRefs: []gwapi.SecretObjectReference{ { @@ -3096,7 +3957,7 @@ func Test_validateGatewayListenerBlock(t *testing.T) { Hostname: ptrHostname("example.com"), Port: gwapi.PortNumber(443), Protocol: gwapi.HTTPSProtocolType, - TLS: &gwapi.GatewayTLSConfig{ + TLS: &gwapi.ListenerTLSConfig{ Mode: ptrMode(gwapi.TLSModeTerminate), CertificateRefs: []gwapi.SecretObjectReference{ { @@ -3115,7 +3976,7 @@ func Test_validateGatewayListenerBlock(t *testing.T) { Hostname: ptrHostname("example.com"), Port: gwapi.PortNumber(443), Protocol: gwapi.HTTPSProtocolType, - TLS: &gwapi.GatewayTLSConfig{ + TLS: &gwapi.ListenerTLSConfig{ Mode: ptrMode(gwapi.TLSModeTerminate), CertificateRefs: []gwapi.SecretObjectReference{ { @@ -3128,10 +3989,62 @@ func Test_validateGatewayListenerBlock(t *testing.T) { }, wantErr: "spec.listeners[0].tls.certificateRef[0].kind: Unsupported value: \"SomeOtherKind\": supported values: \"Secret\", \"\"", }, + { + name: "cross-namespace secret ref", + ingLike: &gwapi.Gateway{ + ObjectMeta: metav1.ObjectMeta{ + Name: "example", + Namespace: gen.DefaultTestNamespace, + }, + }, + listener: gwapi.Listener{ + Hostname: ptrHostname("example.com"), + Port: gwapi.PortNumber(443), + Protocol: gwapi.HTTPSProtocolType, + TLS: &gwapi.ListenerTLSConfig{ + Mode: ptrMode(gwapi.TLSModeTerminate), + CertificateRefs: []gwapi.SecretObjectReference{ + { + Group: func() *gwapi.Group { g := gwapi.Group(""); return &g }(), + Kind: func() *gwapi.Kind { k := gwapi.Kind("Secret"); return &k }(), + Name: "example-com", + Namespace: func() *gwapi.Namespace { n := gwapi.Namespace("another-namespace"); return &n }(), + }, + }, + }, + }, + wantErr: "spec.listeners[0].tls.certificateRef[0].namespace: Invalid value: \"another-namespace\": cross-namespace secret references are not allowed in listeners", + }, + { + name: "same namespace secret ref", + ingLike: &gwapi.Gateway{ + ObjectMeta: metav1.ObjectMeta{ + Name: "example", + Namespace: "another-namespace", + }, + }, + listener: gwapi.Listener{ + Hostname: ptrHostname("example.com"), + Port: gwapi.PortNumber(443), + Protocol: gwapi.HTTPSProtocolType, + TLS: &gwapi.ListenerTLSConfig{ + Mode: ptrMode(gwapi.TLSModeTerminate), + CertificateRefs: []gwapi.SecretObjectReference{ + { + Group: func() *gwapi.Group { g := gwapi.Group(""); return &g }(), + Kind: func() *gwapi.Kind { k := gwapi.Kind("Secret"); return &k }(), + Name: "example-com", + Namespace: func() *gwapi.Namespace { n := gwapi.Namespace("another-namespace"); return &n }(), + }, + }, + }, + }, + wantErr: "", + }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { - gotErr := validateGatewayListenerBlock(field.NewPath("spec", "listeners").Index(0), test.listener).ToAggregate() + gotErr := validateGatewayListenerBlock(field.NewPath("spec", "listeners").Index(0), test.listener, test.ingLike).ToAggregate() if test.wantErr == "" { assert.NoError(t, gotErr) } else { @@ -3153,14 +4066,14 @@ func Test_findCertificatesToBeRemoved(t *testing.T) { givenCerts: []*cmapi.Certificate{{ ObjectMeta: metav1.ObjectMeta{ Name: "cert-1", - Namespace: "default", - OwnerReferences: buildGatewayOwnerReferences("ingress-1", "default"), + Namespace: gen.DefaultTestNamespace, + OwnerReferences: buildGatewayOwnerReferences("ingress-1"), }, Spec: cmapi.CertificateSpec{ SecretName: "secret-name", }}, }, ingLike: &networkingv1.Ingress{ - ObjectMeta: metav1.ObjectMeta{Name: "ingress-2", Namespace: "default", UID: "ingress-2"}, + ObjectMeta: metav1.ObjectMeta{Name: "ingress-2", Namespace: gen.DefaultTestNamespace, UID: "ingress-2"}, Spec: networkingv1.IngressSpec{TLS: []networkingv1.IngressTLS{{SecretName: "secret-name"}}}, }, wantToBeRemoved: nil, @@ -3170,14 +4083,14 @@ func Test_findCertificatesToBeRemoved(t *testing.T) { givenCerts: []*cmapi.Certificate{{ ObjectMeta: metav1.ObjectMeta{ Name: "cert-1", - Namespace: "default", - OwnerReferences: buildGatewayOwnerReferences("ingress-1", "default"), + Namespace: gen.DefaultTestNamespace, + OwnerReferences: buildGatewayOwnerReferences("ingress-1"), }, Spec: cmapi.CertificateSpec{ SecretName: "secret-name", }}, }, ingLike: &networkingv1.Ingress{ - ObjectMeta: metav1.ObjectMeta{Name: "ingress-1", Namespace: "default", UID: "ingress-1"}, + ObjectMeta: metav1.ObjectMeta{Name: "ingress-1", Namespace: gen.DefaultTestNamespace, UID: "ingress-1"}, Spec: networkingv1.IngressSpec{TLS: []networkingv1.IngressTLS{{SecretName: "secret-name"}}}, }, wantToBeRemoved: nil, @@ -3187,14 +4100,14 @@ func Test_findCertificatesToBeRemoved(t *testing.T) { givenCerts: []*cmapi.Certificate{{ ObjectMeta: metav1.ObjectMeta{ Name: "cert-1", - Namespace: "default", - OwnerReferences: buildGatewayOwnerReferences("ingress-1", "default"), + Namespace: gen.DefaultTestNamespace, + OwnerReferences: buildGatewayOwnerReferences("ingress-1"), }, Spec: cmapi.CertificateSpec{ SecretName: "secret-name", }}, }, ingLike: &networkingv1.Ingress{ - ObjectMeta: metav1.ObjectMeta{Name: "ingress-1", Namespace: "default", UID: "ingress-1"}, + ObjectMeta: metav1.ObjectMeta{Name: "ingress-1", Namespace: gen.DefaultTestNamespace, UID: "ingress-1"}, }, wantToBeRemoved: []string{"cert-1"}, }, @@ -3203,16 +4116,16 @@ func Test_findCertificatesToBeRemoved(t *testing.T) { givenCerts: []*cmapi.Certificate{{ ObjectMeta: metav1.ObjectMeta{ Name: "cert-1", - Namespace: "default", - OwnerReferences: buildGatewayOwnerReferences("gw-1", "default"), + Namespace: gen.DefaultTestNamespace, + OwnerReferences: buildGatewayOwnerReferences("gw-1"), }, Spec: cmapi.CertificateSpec{ SecretName: "secret-name", }}, }, ingLike: &gwapi.Gateway{ - ObjectMeta: metav1.ObjectMeta{Name: "gw-2", Namespace: "default", UID: "gw-2"}, + ObjectMeta: metav1.ObjectMeta{Name: "gw-2", Namespace: gen.DefaultTestNamespace, UID: "gw-2"}, Spec: gwapi.GatewaySpec{Listeners: []gwapi.Listener{{ - TLS: &gwapi.GatewayTLSConfig{CertificateRefs: []gwapi.SecretObjectReference{ + TLS: &gwapi.ListenerTLSConfig{CertificateRefs: []gwapi.SecretObjectReference{ { Name: "secret-name", }, @@ -3226,16 +4139,16 @@ func Test_findCertificatesToBeRemoved(t *testing.T) { givenCerts: []*cmapi.Certificate{{ ObjectMeta: metav1.ObjectMeta{ Name: "cert-1", - Namespace: "default", - OwnerReferences: buildGatewayOwnerReferences("gw-1", "default"), + Namespace: gen.DefaultTestNamespace, + OwnerReferences: buildGatewayOwnerReferences("gw-1"), }, Spec: cmapi.CertificateSpec{ SecretName: "secret-name", }}, }, ingLike: &gwapi.Gateway{ - ObjectMeta: metav1.ObjectMeta{Name: "gw-1", Namespace: "default", UID: "gw-1"}, + ObjectMeta: metav1.ObjectMeta{Name: "gw-1", Namespace: gen.DefaultTestNamespace, UID: "gw-1"}, Spec: gwapi.GatewaySpec{Listeners: []gwapi.Listener{ - {TLS: &gwapi.GatewayTLSConfig{CertificateRefs: []gwapi.SecretObjectReference{{Name: "not-secret-name"}}}}, + {TLS: &gwapi.ListenerTLSConfig{CertificateRefs: []gwapi.SecretObjectReference{{Name: "not-secret-name"}}}}, }}, }, wantToBeRemoved: []string{"cert-1"}, @@ -3245,16 +4158,16 @@ func Test_findCertificatesToBeRemoved(t *testing.T) { givenCerts: []*cmapi.Certificate{{ ObjectMeta: metav1.ObjectMeta{ Name: "cert-1", - Namespace: "default", - OwnerReferences: buildGatewayOwnerReferences("gw-1", "default"), + Namespace: gen.DefaultTestNamespace, + OwnerReferences: buildGatewayOwnerReferences("gw-1"), }, Spec: cmapi.CertificateSpec{ SecretName: "secret-name", }}, }, ingLike: &gwapi.Gateway{ - ObjectMeta: metav1.ObjectMeta{Name: "gw-1", Namespace: "default", UID: "gw-1"}, + ObjectMeta: metav1.ObjectMeta{Name: "gw-1", Namespace: gen.DefaultTestNamespace, UID: "gw-1"}, Spec: gwapi.GatewaySpec{Listeners: []gwapi.Listener{ - {TLS: &gwapi.GatewayTLSConfig{CertificateRefs: []gwapi.SecretObjectReference{{Name: "secret-name"}}}}, + {TLS: &gwapi.ListenerTLSConfig{CertificateRefs: []gwapi.SecretObjectReference{{Name: "secret-name"}}}}, }}, }, wantToBeRemoved: nil, @@ -3270,21 +4183,156 @@ func Test_findCertificatesToBeRemoved(t *testing.T) { func Test_secretNameUsedIn_nilPointerGateway(t *testing.T) { got := secretNameUsedIn("secret-name", &gwapi.Gateway{ - ObjectMeta: metav1.ObjectMeta{Name: "gw-1", Namespace: "default", UID: "gw-1"}, + ObjectMeta: metav1.ObjectMeta{Name: "gw-1", Namespace: gen.DefaultTestNamespace, UID: "gw-1"}, Spec: gwapi.GatewaySpec{Listeners: []gwapi.Listener{ {TLS: nil}, - {TLS: &gwapi.GatewayTLSConfig{CertificateRefs: nil}}, - {TLS: &gwapi.GatewayTLSConfig{CertificateRefs: []gwapi.SecretObjectReference{{Name: "secret-name"}}}}, + {TLS: &gwapi.ListenerTLSConfig{CertificateRefs: nil}}, + {TLS: &gwapi.ListenerTLSConfig{CertificateRefs: []gwapi.SecretObjectReference{{Name: "secret-name"}}}}, }}, }) assert.Equal(t, true, got) got = secretNameUsedIn("secret-name", &gwapi.Gateway{ - ObjectMeta: metav1.ObjectMeta{Name: "gw-1", Namespace: "default", UID: "gw-1"}, + ObjectMeta: metav1.ObjectMeta{Name: "gw-1", Namespace: gen.DefaultTestNamespace, UID: "gw-1"}, Spec: gwapi.GatewaySpec{Listeners: []gwapi.Listener{ {TLS: nil}, - {TLS: &gwapi.GatewayTLSConfig{CertificateRefs: nil}}, + {TLS: &gwapi.ListenerTLSConfig{CertificateRefs: nil}}, }}, }) assert.Equal(t, false, got) } + +func buildIngressInDeletion(ingress *networkingv1.Ingress, deletionTimestamp *metav1.Time, finalizers []string) *networkingv1.Ingress { + if ingress == nil { + ingress = buildIngress("test-ingress", gen.DefaultTestNamespace, nil) + } + + ingress.SetDeletionTimestamp(deletionTimestamp) + ingress.SetFinalizers(finalizers) + return ingress +} + +func buildGatewayInDeletion(gateway *gwapi.Gateway, deletionTimestamp *metav1.Time, finalizers []string) *gwapi.Gateway { + if gateway == nil { + gateway = buildGateway("test-gw", gen.DefaultTestNamespace, nil) + } + + gateway.SetDeletionTimestamp(deletionTimestamp) + gateway.SetFinalizers(finalizers) + return gateway +} + +func Test_isDeletedInForeground(t *testing.T) { + type testT struct { + DeletionTimestamp *metav1.Time + Finalizers []string + SkipSync bool + } + + tests := []testT{ + {DeletionTimestamp: nil, Finalizers: nil, SkipSync: false}, + {DeletionTimestamp: nil, Finalizers: []string{}, SkipSync: false}, + {DeletionTimestamp: nil, Finalizers: []string{"cert-lock"}, SkipSync: false}, + {DeletionTimestamp: &metav1.Time{}, Finalizers: []string{"cert-lock"}, SkipSync: true}, + {DeletionTimestamp: &metav1.Time{}, Finalizers: nil, SkipSync: true}, + {DeletionTimestamp: &metav1.Time{}, Finalizers: []string{}, SkipSync: true}, + {DeletionTimestamp: nil, Finalizers: []string{metav1.FinalizerDeleteDependents}, SkipSync: true}, + {DeletionTimestamp: &metav1.Time{}, Finalizers: []string{"cert-lock", metav1.FinalizerDeleteDependents}, SkipSync: true}, + } + + t.Run("should skip ingress sync if being deleted in foreground", func(t *testing.T) { + for _, test := range tests { + skipIngressSync := isDeletedInForeground(buildIngressInDeletion(nil, test.DeletionTimestamp, test.Finalizers)) + if skipIngressSync != test.SkipSync { + t.Errorf("Expected skipIngressSync=%v for deletionTimestamp %#v, finalizers %#v", test.SkipSync, test.DeletionTimestamp, test.Finalizers) + } + } + }) + + t.Run("should skip gateway sync if being deleted in foreground", func(t *testing.T) { + for _, test := range tests { + skipGatewaySync := isDeletedInForeground(buildGatewayInDeletion(nil, test.DeletionTimestamp, test.Finalizers)) + if skipGatewaySync != test.SkipSync { + t.Errorf("Expected skipGatewaySync=%v for deletionTimestamp %#v, finalizers %#v", test.SkipSync, test.DeletionTimestamp, test.Finalizers) + } + } + }) +} + +func TestMergeAnnotations(t *testing.T) { + tests := []struct { + name string + existing map[string]string + update map[string]string + expected map[string]string + }{ + { + name: "No conflicts, simple merge", + existing: map[string]string{ + "env": "prod", + "team": "alpha", + }, + update: map[string]string{ + "version": "1.0.0", + }, + expected: map[string]string{ + "env": "prod", + "team": "alpha", + "version": "1.0.0", + }, + }, + { + name: "Overwriting existing keys", + existing: map[string]string{ + "owner": "team-a", + }, + update: map[string]string{ + "owner": "team-b", + }, + expected: map[string]string{ + "owner": "team-b", + }, + }, + { + name: "Merging empty maps", + existing: map[string]string{}, + update: map[string]string{}, + expected: map[string]string{}, + }, + { + name: "One empty map", + existing: map[string]string{ + "key1": "value1", + }, + update: map[string]string{}, + expected: map[string]string{ + "key1": "value1", + }, + }, + { + name: "Overlapping and new keys", + existing: map[string]string{ + "key1": "value1", + "key2": "value2", + }, + update: map[string]string{ + "key2": "new-value2", + "key3": "value3", + }, + expected: map[string]string{ + "key1": "value1", + "key2": "new-value2", // Overwritten + "key3": "value3", + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := mergeAnnotations(tt.existing, tt.update) + if !reflect.DeepEqual(result, tt.expected) { + t.Errorf("mergeAnnotations() = %v, expected %v", result, tt.expected) + } + }) + } +} diff --git a/pkg/controller/certificaterequests/acme/acme.go b/pkg/controller/certificaterequests/acme/acme.go index 95be29c29cb..91983ecab03 100644 --- a/pkg/controller/certificaterequests/acme/acme.go +++ b/pkg/controller/certificaterequests/acme/acme.go @@ -20,9 +20,12 @@ import ( "context" "crypto/x509" "fmt" + "slices" + "github.com/go-logr/logr" k8sErrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" "k8s.io/client-go/tools/cache" "k8s.io/client-go/tools/record" "k8s.io/client-go/util/workqueue" @@ -38,9 +41,7 @@ import ( crutil "github.com/cert-manager/cert-manager/pkg/controller/certificaterequests/util" issuerpkg "github.com/cert-manager/cert-manager/pkg/issuer" logf "github.com/cert-manager/cert-manager/pkg/logs" - "github.com/cert-manager/cert-manager/pkg/util" "github.com/cert-manager/cert-manager/pkg/util/pki" - "github.com/go-logr/logr" ) const ( @@ -74,19 +75,21 @@ func init() { For(certificaterequests.New( apiutil.IssuerACME, NewACME, - func(ctx *controllerpkg.Context, log logr.Logger, queue workqueue.RateLimitingInterface) ([]cache.InformerSynced, error) { + func(ctx *controllerpkg.Context, log logr.Logger, queue workqueue.TypedRateLimitingInterface[types.NamespacedName]) ([]cache.InformerSynced, error) { orderInformer := ctx.SharedInformerFactory.Acme().V1().Orders().Informer() certificateRequestLister := ctx.SharedInformerFactory.Certmanager().V1().CertificateRequests().Lister() - orderInformer.AddEventHandler(&controllerpkg.BlockingEventHandler{ + if _, err := orderInformer.AddEventHandler(&controllerpkg.BlockingEventHandler{ WorkFunc: controllerpkg.HandleOwnedResourceNamespacedFunc( log, queue, cmapi.SchemeGroupVersion.WithKind(cmapi.CertificateRequestKind), - func(namespace, name string) (interface{}, error) { + func(namespace, name string) (*cmapi.CertificateRequest, error) { return certificateRequestLister.CertificateRequests(namespace).Get(name) }, ), - }) + }); err != nil { + return nil, fmt.Errorf("error setting up event handler: %v", err) + } return []cache.InformerSynced{orderInformer.HasSynced}, nil }, )). @@ -127,7 +130,7 @@ func (a *ACME) Sign(ctx context.Context, cr *cmapi.CertificateRequest, issuer cm } // If the CommonName is also not present in the DNS names or IP Addresses of the Request then hard fail. - if len(csr.Subject.CommonName) > 0 && !util.Contains(csr.DNSNames, csr.Subject.CommonName) && !util.Contains(pki.IPAddressesToString(csr.IPAddresses), csr.Subject.CommonName) { + if len(csr.Subject.CommonName) > 0 && !slices.Contains(csr.DNSNames, csr.Subject.CommonName) && !slices.Contains(pki.IPAddressesToString(csr.IPAddresses), csr.Subject.CommonName) { err = fmt.Errorf("%q does not exist in %s or %s", csr.Subject.CommonName, csr.DNSNames, pki.IPAddressesToString(csr.IPAddresses)) message := "The CSR PEM requests a commonName that is not present in the list of dnsNames or ipAddresses. If a commonName is set, ACME requires that the value is also present in the list of dnsNames or ipAddresses" @@ -139,7 +142,7 @@ func (a *ACME) Sign(ctx context.Context, cr *cmapi.CertificateRequest, issuer cm } // If we fail to build the order we have to hard fail. - expectedOrder, err := buildOrder(cr, csr, issuer.GetSpec().ACME.EnableDurationFeature) + expectedOrder, err := buildOrder(cr, csr, issuer.GetSpec().ACME.EnableDurationFeature, issuer.GetSpec().ACME.Profile) if err != nil { message := "Failed to build order" @@ -151,6 +154,7 @@ func (a *ACME) Sign(ctx context.Context, cr *cmapi.CertificateRequest, issuer cm order, err := a.orderLister.Orders(expectedOrder.Namespace).Get(expectedOrder.Name) if k8sErrors.IsNotFound(err) { + log.V(logf.DebugLevel).Info("creating order", "profile", expectedOrder.Spec.Profile) // Failing to create the order here is most likely network related. // We should backoff and keep trying. _, err = a.acmeClientV.Orders(expectedOrder.Namespace).Create(ctx, expectedOrder, metav1.CreateOptions{FieldManager: a.fieldManager}) @@ -232,14 +236,14 @@ func (a *ACME) Sign(ctx context.Context, cr *cmapi.CertificateRequest, issuer cm log.V(logf.InfoLevel).Info("certificate issued") - // Order valid, return cert. The calling controller will update with ready if its happy with the cert. + // Order valid, return cert. The calling controller will update with ready if it's happy with the cert. return &issuerpkg.IssueResponse{ Certificate: order.Status.Certificate, }, nil } // Build order. If we error here it is a terminating failure. -func buildOrder(cr *cmapi.CertificateRequest, csr *x509.CertificateRequest, enableDurationFeature bool) (*cmacme.Order, error) { +func buildOrder(cr *cmapi.CertificateRequest, csr *x509.CertificateRequest, enableDurationFeature bool, profile string) (*cmacme.Order, error) { var ipAddresses []string for _, ip := range csr.IPAddresses { ipAddresses = append(ipAddresses, ip.String()) @@ -256,6 +260,7 @@ func buildOrder(cr *cmapi.CertificateRequest, csr *x509.CertificateRequest, enab CommonName: csr.Subject.CommonName, DNSNames: dnsNames, IPAddresses: ipAddresses, + Profile: profile, } if enableDurationFeature { diff --git a/pkg/controller/certificaterequests/acme/acme_test.go b/pkg/controller/certificaterequests/acme/acme_test.go index 1b7906ed084..3f5ef630b8a 100644 --- a/pkg/controller/certificaterequests/acme/acme_test.go +++ b/pkg/controller/certificaterequests/acme/acme_test.go @@ -17,19 +17,15 @@ limitations under the License. package acme import ( - "context" "crypto" - "crypto/rand" "crypto/x509" "crypto/x509/pkix" - "encoding/pem" "errors" "math/big" - "net" - "reflect" "testing" "time" + "github.com/stretchr/testify/assert" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" coretesting "k8s.io/client-go/testing" @@ -38,8 +34,7 @@ import ( apiutil "github.com/cert-manager/cert-manager/pkg/api/util" cmacme "github.com/cert-manager/cert-manager/pkg/apis/acme/v1" "github.com/cert-manager/cert-manager/pkg/apis/certmanager" - cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" - v1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" + cmapiv1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" cmacmelisters "github.com/cert-manager/cert-manager/pkg/client/listers/acme/v1" "github.com/cert-manager/cert-manager/pkg/controller" @@ -56,50 +51,27 @@ var ( ) func generateCSR(t *testing.T, secretKey crypto.Signer, commonName string, dnsNames ...string) []byte { - // The CommonName of the certificate request must also be present in the DNS - // Names. - template := x509.CertificateRequest{ - Subject: pkix.Name{ - CommonName: commonName, - }, - SignatureAlgorithm: x509.SHA256WithRSA, - DNSNames: dnsNames, - } - - csrBytes, err := x509.CreateCertificateRequest(rand.Reader, &template, secretKey) + csr, err := gen.CSRWithSigner(secretKey, + gen.SetCSRCommonName(commonName), + gen.SetCSRDNSNames(dnsNames...), + ) if err != nil { t.Fatal(err) } - csr := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE REQUEST", Bytes: csrBytes}) - return csr } func generateCSRWithIPs(t *testing.T, secretKey crypto.Signer, commonName string, dnsNames []string, ips []string) []byte { - // The CommonName of the certificate request must also be present in the DNS - // Names. - - var certIPs []net.IP - for _, ip := range ips { - certIPs = append(certIPs, net.ParseIP(ip)) - } - template := x509.CertificateRequest{ - Subject: pkix.Name{ - CommonName: commonName, - }, - SignatureAlgorithm: x509.SHA256WithRSA, - DNSNames: dnsNames, - IPAddresses: certIPs, - } - - csrBytes, err := x509.CreateCertificateRequest(rand.Reader, &template, secretKey) + csr, err := gen.CSRWithSigner(secretKey, + gen.SetCSRCommonName(commonName), + gen.SetCSRDNSNames(dnsNames...), + gen.SetCSRIPAddressesFromStrings(ips...), + ) if err != nil { t.Fatal(err) } - csr := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE REQUEST", Bytes: csrBytes}) - return csr } @@ -107,8 +79,8 @@ func TestSign(t *testing.T) { metaFixedClockStart := metav1.NewTime(fixedClockStart) baseIssuer := gen.Issuer("test-issuer", gen.SetIssuerACME(cmacme.ACMEIssuer{}), - gen.AddIssuerCondition(cmapi.IssuerCondition{ - Type: cmapi.IssuerConditionReady, + gen.AddIssuerCondition(cmapiv1.IssuerCondition{ + Type: cmapiv1.IssuerConditionReady, Status: cmmeta.ConditionTrue, }), ) @@ -119,7 +91,6 @@ func TestSign(t *testing.T) { } rootTmpl := &x509.Certificate{ - Version: 2, BasicConstraintsValid: true, SerialNumber: big.NewInt(0), Subject: pkix.Name{ @@ -149,15 +120,15 @@ func TestSign(t *testing.T) { gen.SetCertificateRequestCSR(csrPEM), gen.SetCertificateRequestIsCA(false), gen.SetCertificateRequestDuration(&metav1.Duration{Duration: time.Hour * 24 * 60}), - gen.SetCertificateRequestIssuer(cmmeta.ObjectReference{ + gen.SetCertificateRequestIssuer(cmmeta.IssuerReference{ Name: baseIssuer.Name, Group: certmanager.GroupName, Kind: "Issuer", }), ) baseCRDenied := gen.CertificateRequestFrom(baseCRNotApproved, - gen.SetCertificateRequestStatusCondition(cmapi.CertificateRequestCondition{ - Type: cmapi.CertificateRequestConditionDenied, + gen.SetCertificateRequestStatusCondition(cmapiv1.CertificateRequestCondition{ + Type: cmapiv1.CertificateRequestConditionDenied, Status: cmmeta.ConditionTrue, Reason: "Foo", Message: "Certificate request has been denied by cert-manager.io", @@ -165,8 +136,8 @@ func TestSign(t *testing.T) { }), ) baseCR := gen.CertificateRequestFrom(baseCRNotApproved, - gen.SetCertificateRequestStatusCondition(cmapi.CertificateRequestCondition{ - Type: cmapi.CertificateRequestConditionApproved, + gen.SetCertificateRequestStatusCondition(cmapiv1.CertificateRequestCondition{ + Type: cmapiv1.CertificateRequestConditionApproved, Status: cmmeta.ConditionTrue, Reason: "cert-manager.io", Message: "Certificate request has been approved by cert-manager.io", @@ -179,7 +150,7 @@ func TestSign(t *testing.T) { t.Fatal(err) } - template, err := pki.GenerateTemplateFromCertificateRequest(baseCR) + template, err := pki.CertificateTemplateFromCertificateRequest(baseCR) if err != nil { t.Errorf("error generating template: %v", err) } @@ -195,7 +166,12 @@ func TestSign(t *testing.T) { if err != nil { t.Fatal(err) } - template2, err := pki.GenerateTemplateFromCSRPEM(generateCSR(t, sk2, "example.com", "example.com", "foo.com"), time.Hour, false) + template2, err := pki.CertificateTemplateFromCSRPEM( + generateCSR(t, sk2, "example.com", "example.com", "foo.com"), + pki.CertificateTemplateOverrideDuration(time.Hour), + pki.CertificateTemplateValidateAndOverrideBasicConstraints(false, nil), + pki.CertificateTemplateValidateAndOverrideKeyUsages(0, nil), + ) if err != nil { t.Fatal(err) } @@ -210,12 +186,12 @@ func TestSign(t *testing.T) { t.Fatal(err) } ipBaseCR := gen.CertificateRequestFrom(baseCR, gen.SetCertificateRequestCSR(ipCSRPEM)) - ipBaseOrder, err := buildOrder(ipBaseCR, ipCSR, baseIssuer.GetSpec().ACME.EnableDurationFeature) + ipBaseOrder, err := buildOrder(ipBaseCR, ipCSR, baseIssuer.GetSpec().ACME.EnableDurationFeature, "") if err != nil { t.Fatalf("failed to build order during testing: %s", err) } - baseOrder, err := buildOrder(baseCR, csr, baseIssuer.GetSpec().ACME.EnableDurationFeature) + baseOrder, err := buildOrder(baseCR, csr, baseIssuer.GetSpec().ACME.EnableDurationFeature, "") if err != nil { t.Fatalf("failed to build order during testing: %s", err) } @@ -239,12 +215,12 @@ func TestSign(t *testing.T) { ExpectedEvents: []string{}, ExpectedActions: []testpkg.Action{ testpkg.NewAction(coretesting.NewUpdateSubresourceAction( - cmapi.SchemeGroupVersion.WithResource("certificaterequests"), + cmapiv1.SchemeGroupVersion.WithResource("certificaterequests"), "status", gen.DefaultTestNamespace, gen.CertificateRequestFrom(baseCRDenied, - gen.SetCertificateRequestStatusCondition(cmapi.CertificateRequestCondition{ - Type: cmapi.CertificateRequestConditionReady, + gen.SetCertificateRequestStatusCondition(cmapiv1.CertificateRequestCondition{ + Type: cmapiv1.CertificateRequestConditionReady, Status: cmmeta.ConditionFalse, Reason: "Denied", Message: "The CertificateRequest was denied by an approval controller", @@ -264,20 +240,20 @@ func TestSign(t *testing.T) { KubeObjects: []runtime.Object{}, CertManagerObjects: []runtime.Object{baseCR.DeepCopy(), baseIssuer.DeepCopy()}, ExpectedEvents: []string{ - "Warning RequestParsingError Failed to decode CSR in spec.request: error decoding certificate request PEM block", + "Warning RequestParsingError Failed to decode CSR in spec.request: error decoding certificate request PEM block: no PEM data was found in given input", }, ExpectedActions: []testpkg.Action{ testpkg.NewAction(coretesting.NewUpdateSubresourceAction( - cmapi.SchemeGroupVersion.WithResource("certificaterequests"), + cmapiv1.SchemeGroupVersion.WithResource("certificaterequests"), "status", gen.DefaultTestNamespace, gen.CertificateRequestFrom(baseCR, gen.SetCertificateRequestCSR([]byte("a bad csr")), - gen.SetCertificateRequestStatusCondition(cmapi.CertificateRequestCondition{ - Type: cmapi.CertificateRequestConditionReady, + gen.SetCertificateRequestStatusCondition(cmapiv1.CertificateRequestCondition{ + Type: cmapiv1.CertificateRequestConditionReady, Status: cmmeta.ConditionFalse, - Reason: cmapi.CertificateRequestReasonFailed, - Message: "Failed to decode CSR in spec.request: error decoding certificate request PEM block", + Reason: cmapiv1.CertificateRequestReasonFailed, + Message: "Failed to decode CSR in spec.request: error decoding certificate request PEM block: no PEM data was found in given input", LastTransitionTime: &metaFixedClockStart, }), gen.SetCertificateRequestFailureTime(metaFixedClockStart), @@ -297,15 +273,15 @@ func TestSign(t *testing.T) { }, ExpectedActions: []testpkg.Action{ testpkg.NewAction(coretesting.NewUpdateSubresourceAction( - cmapi.SchemeGroupVersion.WithResource("certificaterequests"), + cmapiv1.SchemeGroupVersion.WithResource("certificaterequests"), "status", gen.DefaultTestNamespace, gen.CertificateRequestFrom(baseCR, gen.SetCertificateRequestCSR(csrPEMExampleNotPresent), - gen.SetCertificateRequestStatusCondition(cmapi.CertificateRequestCondition{ - Type: cmapi.CertificateRequestConditionReady, + gen.SetCertificateRequestStatusCondition(cmapiv1.CertificateRequestCondition{ + Type: cmapiv1.CertificateRequestConditionReady, Status: cmmeta.ConditionFalse, - Reason: cmapi.CertificateRequestReasonFailed, + Reason: cmapiv1.CertificateRequestReasonFailed, Message: `The CSR PEM requests a commonName that is not present in the list of dnsNames or ipAddresses. If a commonName is set, ACME requires that the value is also present in the list of dnsNames or ipAddresses: "example.com" does not exist in [foo.com] or []`, LastTransitionTime: &metaFixedClockStart, }), @@ -327,15 +303,15 @@ func TestSign(t *testing.T) { }, ExpectedActions: []testpkg.Action{ testpkg.NewAction(coretesting.NewUpdateSubresourceAction( - cmapi.SchemeGroupVersion.WithResource("certificaterequests"), + cmapiv1.SchemeGroupVersion.WithResource("certificaterequests"), "status", gen.DefaultTestNamespace, gen.CertificateRequestFrom(baseCR, gen.SetCertificateRequestCSR(generateCSR(t, sk, "10.0.0.1", "example.com")), - gen.SetCertificateRequestStatusCondition(cmapi.CertificateRequestCondition{ - Type: cmapi.CertificateRequestConditionReady, + gen.SetCertificateRequestStatusCondition(cmapiv1.CertificateRequestCondition{ + Type: cmapiv1.CertificateRequestConditionReady, Status: cmmeta.ConditionFalse, - Reason: cmapi.CertificateRequestReasonFailed, + Reason: cmapiv1.CertificateRequestReasonFailed, Message: `The CSR PEM requests a commonName that is not present in the list of dnsNames or ipAddresses. If a commonName is set, ACME requires that the value is also present in the list of dnsNames or ipAddresses: "10.0.0.1" does not exist in [example.com] or []`, LastTransitionTime: &metaFixedClockStart, }), @@ -362,15 +338,15 @@ func TestSign(t *testing.T) { ipBaseOrder, )), testpkg.NewAction(coretesting.NewUpdateSubresourceAction( - cmapi.SchemeGroupVersion.WithResource("certificaterequests"), + cmapiv1.SchemeGroupVersion.WithResource("certificaterequests"), "status", gen.DefaultTestNamespace, gen.CertificateRequestFrom(ipBaseCR, gen.SetCertificateRequestCSR(ipCSRPEM), - gen.SetCertificateRequestStatusCondition(cmapi.CertificateRequestCondition{ - Type: cmapi.CertificateRequestConditionReady, + gen.SetCertificateRequestStatusCondition(cmapiv1.CertificateRequestCondition{ + Type: cmapiv1.CertificateRequestConditionReady, Status: cmmeta.ConditionFalse, - Reason: cmapi.CertificateRequestReasonPending, + Reason: cmapiv1.CertificateRequestReasonPending, Message: "Created Order resource default-unit-test-ns/test-cr-3104426127", LastTransitionTime: &metaFixedClockStart, }), @@ -396,14 +372,14 @@ func TestSign(t *testing.T) { baseOrder, )), testpkg.NewAction(coretesting.NewUpdateSubresourceAction( - cmapi.SchemeGroupVersion.WithResource("certificaterequests"), + cmapiv1.SchemeGroupVersion.WithResource("certificaterequests"), "status", gen.DefaultTestNamespace, gen.CertificateRequestFrom(baseCR, - gen.SetCertificateRequestStatusCondition(cmapi.CertificateRequestCondition{ - Type: cmapi.CertificateRequestConditionReady, + gen.SetCertificateRequestStatusCondition(cmapiv1.CertificateRequestCondition{ + Type: cmapiv1.CertificateRequestConditionReady, Status: cmmeta.ConditionFalse, - Reason: cmapi.CertificateRequestReasonPending, + Reason: cmapiv1.CertificateRequestReasonPending, Message: "Created Order resource default-unit-test-ns/test-cr-1733622556", LastTransitionTime: &metaFixedClockStart, }), @@ -425,12 +401,12 @@ func TestSign(t *testing.T) { }, ExpectedActions: []testpkg.Action{ testpkg.NewAction(coretesting.NewUpdateSubresourceAction( - cmapi.SchemeGroupVersion.WithResource("certificaterequests"), + cmapiv1.SchemeGroupVersion.WithResource("certificaterequests"), "status", gen.DefaultTestNamespace, gen.CertificateRequestFrom(baseCR, - gen.SetCertificateRequestStatusCondition(cmapi.CertificateRequestCondition{ - Type: cmapi.CertificateRequestConditionReady, + gen.SetCertificateRequestStatusCondition(cmapiv1.CertificateRequestCondition{ + Type: cmapiv1.CertificateRequestConditionReady, Status: cmmeta.ConditionFalse, Reason: "Pending", Message: "Referenced issuer does not have a Ready status condition", @@ -451,14 +427,14 @@ func TestSign(t *testing.T) { CertManagerObjects: []runtime.Object{baseCR.DeepCopy(), baseIssuer.DeepCopy()}, ExpectedActions: []testpkg.Action{ testpkg.NewAction(coretesting.NewUpdateSubresourceAction( - cmapi.SchemeGroupVersion.WithResource("certificaterequests"), + cmapiv1.SchemeGroupVersion.WithResource("certificaterequests"), "status", gen.DefaultTestNamespace, gen.CertificateRequestFrom(baseCR, - gen.SetCertificateRequestStatusCondition(cmapi.CertificateRequestCondition{ - Type: cmapi.CertificateRequestConditionReady, + gen.SetCertificateRequestStatusCondition(cmapiv1.CertificateRequestCondition{ + Type: cmapiv1.CertificateRequestConditionReady, Status: cmmeta.ConditionFalse, - Reason: cmapi.CertificateRequestReasonPending, + Reason: cmapiv1.CertificateRequestReasonPending, Message: "Failed to get order resource default-unit-test-ns/test-cr-1733622556: this is a network error", LastTransitionTime: &metaFixedClockStart, }), @@ -492,14 +468,14 @@ func TestSign(t *testing.T) { }, ExpectedActions: []testpkg.Action{ testpkg.NewAction(coretesting.NewUpdateSubresourceAction( - cmapi.SchemeGroupVersion.WithResource("certificaterequests"), + cmapiv1.SchemeGroupVersion.WithResource("certificaterequests"), "status", gen.DefaultTestNamespace, gen.CertificateRequestFrom(baseCR, - gen.SetCertificateRequestStatusCondition(cmapi.CertificateRequestCondition{ - Type: cmapi.CertificateRequestConditionReady, + gen.SetCertificateRequestStatusCondition(cmapiv1.CertificateRequestCondition{ + Type: cmapiv1.CertificateRequestConditionReady, Status: cmmeta.ConditionFalse, - Reason: cmapi.CertificateRequestReasonFailed, + Reason: cmapiv1.CertificateRequestReasonFailed, Message: `Failed to wait for order resource "test-cr-1733622556" to become ready: order is in "invalid" state: simulated failure`, LastTransitionTime: &metaFixedClockStart, }), @@ -523,14 +499,14 @@ func TestSign(t *testing.T) { }, ExpectedActions: []testpkg.Action{ testpkg.NewAction(coretesting.NewUpdateSubresourceAction( - cmapi.SchemeGroupVersion.WithResource("certificaterequests"), + cmapiv1.SchemeGroupVersion.WithResource("certificaterequests"), "status", gen.DefaultTestNamespace, gen.CertificateRequestFrom(baseCR, - gen.SetCertificateRequestStatusCondition(cmapi.CertificateRequestCondition{ - Type: cmapi.CertificateRequestConditionReady, + gen.SetCertificateRequestStatusCondition(cmapiv1.CertificateRequestCondition{ + Type: cmapiv1.CertificateRequestConditionReady, Status: cmmeta.ConditionFalse, - Reason: cmapi.CertificateRequestReasonPending, + Reason: cmapiv1.CertificateRequestReasonPending, Message: `Waiting on certificate issuance from order default-unit-test-ns/test-cr-1733622556: "pending"`, LastTransitionTime: &metaFixedClockStart, }), @@ -551,14 +527,14 @@ func TestSign(t *testing.T) { ), baseCR.DeepCopy(), baseIssuer.DeepCopy()}, ExpectedActions: []testpkg.Action{ testpkg.NewAction(coretesting.NewUpdateSubresourceAction( - cmapi.SchemeGroupVersion.WithResource("certificaterequests"), + cmapiv1.SchemeGroupVersion.WithResource("certificaterequests"), "status", gen.DefaultTestNamespace, gen.CertificateRequestFrom(baseCR, - gen.SetCertificateRequestStatusCondition(cmapi.CertificateRequestCondition{ - Type: cmapi.CertificateRequestConditionReady, + gen.SetCertificateRequestStatusCondition(cmapiv1.CertificateRequestCondition{ + Type: cmapiv1.CertificateRequestConditionReady, Status: cmmeta.ConditionFalse, - Reason: cmapi.CertificateRequestReasonPending, + Reason: cmapiv1.CertificateRequestReasonPending, Message: "Waiting for order-controller to add certificate data to Order default-unit-test-ns/test-cr-1733622556", LastTransitionTime: &metaFixedClockStart, }), @@ -614,14 +590,14 @@ func TestSign(t *testing.T) { ), baseCR.DeepCopy(), baseIssuer.DeepCopy()}, ExpectedActions: []testpkg.Action{ testpkg.NewAction(coretesting.NewUpdateSubresourceAction( - cmapi.SchemeGroupVersion.WithResource("certificaterequests"), + cmapiv1.SchemeGroupVersion.WithResource("certificaterequests"), "status", gen.DefaultTestNamespace, gen.CertificateRequestFrom(baseCR, - gen.SetCertificateRequestStatusCondition(cmapi.CertificateRequestCondition{ - Type: cmapi.CertificateRequestConditionReady, + gen.SetCertificateRequestStatusCondition(cmapiv1.CertificateRequestCondition{ + Type: cmapiv1.CertificateRequestConditionReady, Status: cmmeta.ConditionTrue, - Reason: cmapi.CertificateRequestReasonIssued, + Reason: cmapiv1.CertificateRequestReasonIssued, Message: "Certificate fetched from issuer successfully", LastTransitionTime: &metaFixedClockStart, }), @@ -644,7 +620,7 @@ func TestSign(t *testing.T) { type testT struct { builder *testpkg.Builder - certificateRequest *cmapi.CertificateRequest + certificateRequest *cmapiv1.CertificateRequest expectedErr bool @@ -671,7 +647,7 @@ func runTest(t *testing.T, test testT) { } test.builder.Start() - err = controller.Sync(context.Background(), test.certificateRequest) + err = controller.Sync(t.Context(), test.certificateRequest) if err != nil && !test.expectedErr { t.Errorf("expected to not get an error, but got: %v", err) } @@ -694,11 +670,21 @@ func Test_buildOrder(t *testing.T) { t.Fatal(err) } - cr := gen.CertificateRequest("test", gen.SetCertificateRequestDuration(&metav1.Duration{Duration: time.Hour}), gen.SetCertificateRequestCSR(csrPEM)) + cr := gen.CertificateRequest("test", + gen.SetCertificateRequestIssuer(cmmeta.IssuerReference{Name: "test-issuer"}), + gen.SetCertificateRequestDuration(&metav1.Duration{Duration: time.Hour}), + gen.SetCertificateRequestCSR(csrPEM)) + baseOrder := gen.Order("", + gen.SetOrderCsr(csrPEM), + gen.SetOrderCommonName("example.com"), + gen.SetOrderDNSNames("example.com"), + gen.SetOrderIssuer(cmmeta.IssuerReference{Name: "test-issuer"})) + type args struct { - cr *v1.CertificateRequest + cr *cmapiv1.CertificateRequest csr *x509.CertificateRequest enableDurationFeature bool + profile string } tests := []struct { name string @@ -713,13 +699,7 @@ func Test_buildOrder(t *testing.T) { csr: csr, enableDurationFeature: false, }, - want: &cmacme.Order{ - Spec: cmacme.OrderSpec{ - Request: csrPEM, - CommonName: "example.com", - DNSNames: []string{"example.com"}, - }, - }, + want: baseOrder, wantErr: false, }, { @@ -729,29 +709,32 @@ func Test_buildOrder(t *testing.T) { csr: csr, enableDurationFeature: true, }, - want: &cmacme.Order{ - Spec: cmacme.OrderSpec{ - Request: csrPEM, - CommonName: "example.com", - DNSNames: []string{"example.com"}, - Duration: &metav1.Duration{Duration: time.Hour}, - }, + want: gen.OrderFrom(baseOrder, + gen.SetOrderDuration(time.Hour)), + wantErr: false, + }, + { + name: "Building with profile", + args: args{ + cr: cr, + csr: csr, + profile: "shortlived", }, + want: gen.OrderFrom(baseOrder, + gen.SetOrderProfile("shortlived")), wantErr: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got, err := buildOrder(tt.args.cr, tt.args.csr, tt.args.enableDurationFeature) + got, err := buildOrder(tt.args.cr, tt.args.csr, tt.args.enableDurationFeature, tt.args.profile) if (err != nil) != tt.wantErr { t.Errorf("buildOrder() error = %v, wantErr %v", err, tt.wantErr) return } // for the current purpose we only test the spec - if !reflect.DeepEqual(got.Spec, tt.want.Spec) { - t.Errorf("buildOrder() got = %v, want %v", got.Spec, tt.want.Spec) - } + assert.Equal(t, tt.want.Spec, got.Spec) }) } @@ -759,7 +742,7 @@ func Test_buildOrder(t *testing.T) { "test-comparison-that-is-at-the-fifty-two-character-l", gen.SetCertificateRequestDuration(&metav1.Duration{Duration: time.Hour}), gen.SetCertificateRequestCSR(csrPEM)) - orderOne, err := buildOrder(longCrOne, csr, false) + orderOne, err := buildOrder(longCrOne, csr, false, "") if err != nil { t.Errorf("buildOrder() received error %v", err) return @@ -771,7 +754,7 @@ func Test_buildOrder(t *testing.T) { gen.SetCertificateRequestDuration(&metav1.Duration{Duration: time.Hour}), gen.SetCertificateRequestCSR(csrPEM)) - orderTwo, err := buildOrder(longCrTwo, csr, false) + orderTwo, err := buildOrder(longCrTwo, csr, false, "") if err != nil { t.Errorf("buildOrder() received error %v", err) return @@ -786,13 +769,13 @@ func Test_buildOrder(t *testing.T) { }) t.Run("Builds two orders from the same long CRs to guarantee same name", func(t *testing.T) { - orderOne, err := buildOrder(longCrOne, csr, false) + orderOne, err := buildOrder(longCrOne, csr, false, "") if err != nil { t.Errorf("buildOrder() received error %v", err) return } - orderTwo, err := buildOrder(longCrOne, csr, false) + orderTwo, err := buildOrder(longCrOne, csr, false, "") if err != nil { t.Errorf("buildOrder() received error %v", err) return diff --git a/pkg/controller/certificaterequests/approver/approver.go b/pkg/controller/certificaterequests/approver/approver.go index bc1f165c194..d9f88e3733e 100644 --- a/pkg/controller/certificaterequests/approver/approver.go +++ b/pkg/controller/certificaterequests/approver/approver.go @@ -22,6 +22,7 @@ import ( "github.com/go-logr/logr" apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/types" "k8s.io/client-go/tools/cache" "k8s.io/client-go/tools/record" "k8s.io/client-go/util/workqueue" @@ -51,7 +52,7 @@ type Controller struct { recorder record.EventRecorder - queue workqueue.RateLimitingInterface + queue workqueue.TypedRateLimitingInterface[types.NamespacedName] } func init() { @@ -65,13 +66,20 @@ func init() { // Register registers and constructs the controller using the provided context. // It returns the workqueue to be used to enqueue items, a list of // InformerSynced functions that must be synced, or an error. -func (c *Controller) Register(ctx *controllerpkg.Context) (workqueue.RateLimitingInterface, []cache.InformerSynced, error) { +func (c *Controller) Register(ctx *controllerpkg.Context) (workqueue.TypedRateLimitingInterface[types.NamespacedName], []cache.InformerSynced, error) { c.log = logf.FromContext(ctx.RootContext, ControllerName) - c.queue = workqueue.NewNamedRateLimitingQueue(controllerpkg.DefaultItemBasedRateLimiter(), ControllerName) + c.queue = workqueue.NewTypedRateLimitingQueueWithConfig( + controllerpkg.DefaultItemBasedRateLimiter(), + workqueue.TypedRateLimitingQueueConfig[types.NamespacedName]{ + Name: ControllerName, + }, + ) certificateRequestInformer := ctx.SharedInformerFactory.Certmanager().V1().CertificateRequests() mustSync := []cache.InformerSynced{certificateRequestInformer.Informer().HasSynced} - certificateRequestInformer.Informer().AddEventHandler(&controllerpkg.QueuingEventHandler{Queue: c.queue}) + if _, err := certificateRequestInformer.Informer().AddEventHandler(&controllerpkg.QueuingEventHandler{Queue: c.queue}); err != nil { + return nil, nil, fmt.Errorf("error setting up event handler: %v", err) + } c.certificateRequestLister = certificateRequestInformer.Lister() c.cmClient = ctx.CMClient @@ -83,15 +91,11 @@ func (c *Controller) Register(ctx *controllerpkg.Context) (workqueue.RateLimitin return c.queue, mustSync, nil } -func (c *Controller) ProcessItem(ctx context.Context, key string) error { +func (c *Controller) ProcessItem(ctx context.Context, key types.NamespacedName) error { log := logf.FromContext(ctx) dbg := log.V(logf.DebugLevel) - namespace, name, err := cache.SplitMetaNamespaceKey(key) - if err != nil { - log.Error(err, "invalid resource key") - return nil - } + namespace, name := key.Namespace, key.Name cr, err := c.certificateRequestLister.CertificateRequests(namespace).Get(name) if apierrors.IsNotFound(err) { diff --git a/pkg/controller/certificaterequests/approver/approver_test.go b/pkg/controller/certificaterequests/approver/approver_test.go index 2c898c00921..68aa614a5d1 100644 --- a/pkg/controller/certificaterequests/approver/approver_test.go +++ b/pkg/controller/certificaterequests/approver/approver_test.go @@ -17,17 +17,16 @@ limitations under the License. package approver import ( - "context" "testing" "time" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" coretesting "k8s.io/client-go/testing" fakeclock "k8s.io/utils/clock/testing" cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" - controllerpkg "github.com/cert-manager/cert-manager/pkg/controller" testpkg "github.com/cert-manager/cert-manager/pkg/controller/test" ) @@ -39,7 +38,7 @@ func TestProcessItem(t *testing.T) { // key that should be passed to ProcessItem. // if not set, the 'namespace/name' of the 'CertificateRequest' field will be used. // if neither is set, the key will be "" - key string + key types.NamespacedName // CertificateRequest to be synced for the test. // if not set, the 'key' will be passed to ProcessItem instead. @@ -59,10 +58,16 @@ func TestProcessItem(t *testing.T) { }{ "do nothing if an empty 'key' is used": {}, "do nothing if an invalid 'key' is used": { - key: "abc/def/ghi", + key: types.NamespacedName{ + Namespace: "abc", + Name: "def/ghi", + }, }, "do nothing if a key references a Certificate that does not exist": { - key: "namespace/name", + key: types.NamespacedName{ + Namespace: "namespace", + Name: "name", + }, }, "do nothing if CertificateRequest already has 'Approved' True condition": { request: &cmapi.CertificateRequest{ @@ -206,15 +211,15 @@ func TestProcessItem(t *testing.T) { defer builder.Stop() key := test.key - if key == "" && test.request != nil { - key, err = controllerpkg.KeyFunc(test.request) - if err != nil { - t.Fatal(err) + if key == (types.NamespacedName{}) && test.request != nil { + key = types.NamespacedName{ + Name: test.request.Name, + Namespace: test.request.Namespace, } } // Call ProcessItem - err = c.ProcessItem(context.Background(), key) + err = c.ProcessItem(t.Context(), key) switch { case err != nil: if test.err != err.Error() { @@ -232,9 +237,6 @@ func TestProcessItem(t *testing.T) { if err := builder.AllActionsExecuted(); err != nil { builder.T.Error(err) } - if err := builder.AllReactorsCalled(); err != nil { - builder.T.Error(err) - } }) } } diff --git a/pkg/controller/certificaterequests/ca/ca.go b/pkg/controller/certificaterequests/ca/ca.go index 754ff5c31d8..ad676bb23c2 100644 --- a/pkg/controller/certificaterequests/ca/ca.go +++ b/pkg/controller/certificaterequests/ca/ca.go @@ -23,8 +23,8 @@ import ( "fmt" k8sErrors "k8s.io/apimachinery/pkg/api/errors" - corelisters "k8s.io/client-go/listers/core/v1" + internalinformers "github.com/cert-manager/cert-manager/internal/informers" apiutil "github.com/cert-manager/cert-manager/pkg/api/util" cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" controllerpkg "github.com/cert-manager/cert-manager/pkg/controller" @@ -46,30 +46,25 @@ type signingFn func([]*x509.Certificate, crypto.Signer, *x509.Certificate) (pki. type CA struct { issuerOptions controllerpkg.IssuerOptions - secretsLister corelisters.SecretLister + secretsLister internalinformers.SecretLister reporter *crutil.Reporter - // Used for testing to get reproducible resulting certificates + // templateGenerator is used to generate templates to pass to the Go stdlib for signing. + // It's a member of the struct so it can be mocked for testing. templateGenerator templateGenerator - signingFn signingFn -} -func init() { - // create certificate request controller for ca issuer - controllerpkg.Register(CRControllerName, func(ctx *controllerpkg.ContextFactory) (controllerpkg.Interface, error) { - return controllerpkg.NewBuilder(ctx, CRControllerName). - For(certificaterequests.New(apiutil.IssuerCA, NewCA)). - Complete() - }) + // signingFn is the function called to actually sign certificates. + // It's a member of the struct so it can be mocked for testing. + signingFn signingFn } func NewCA(ctx *controllerpkg.Context) certificaterequests.Issuer { return &CA{ issuerOptions: ctx.IssuerOptions, - secretsLister: ctx.KubeSharedInformerFactory.Core().V1().Secrets().Lister(), + secretsLister: ctx.KubeSharedInformerFactory.Secrets().Lister(), reporter: crutil.NewReporter(ctx.Clock, ctx.Recorder), - templateGenerator: pki.GenerateTemplateFromCertificateRequest, + templateGenerator: pki.CertificateTemplateFromCertificateRequest, signingFn: pki.SignCSRTemplate, } } @@ -120,6 +115,7 @@ func (c *CA) Sign(ctx context.Context, cr *cmapi.CertificateRequest, issuerObj c template.CRLDistributionPoints = issuerObj.GetSpec().CA.CRLDistributionPoints template.OCSPServer = issuerObj.GetSpec().CA.OCSPServers + template.IssuingCertificateURL = issuerObj.GetSpec().CA.IssuingCertificateURLs bundle, err := c.signingFn(caCerts, caKey, template) if err != nil { @@ -136,3 +132,12 @@ func (c *CA) Sign(ctx context.Context, cr *cmapi.CertificateRequest, issuerObj c CA: bundle.CAPEM, }, nil } + +func init() { + // create certificate request controller for ca issuer + controllerpkg.Register(CRControllerName, func(ctx *controllerpkg.ContextFactory) (controllerpkg.Interface, error) { + return controllerpkg.NewBuilder(ctx, CRControllerName). + For(certificaterequests.New(apiutil.IssuerCA, NewCA)). + Complete() + }) +} diff --git a/pkg/controller/certificaterequests/ca/ca_test.go b/pkg/controller/certificaterequests/ca/ca_test.go index 40c03d2b4d0..6785681199a 100644 --- a/pkg/controller/certificaterequests/ca/ca_test.go +++ b/pkg/controller/certificaterequests/ca/ca_test.go @@ -17,14 +17,11 @@ limitations under the License. package ca import ( - "context" "crypto" "crypto/ecdsa" "crypto/rand" "crypto/x509" "crypto/x509/pkix" - "encoding/asn1" - "encoding/pem" "errors" "math" "math/big" @@ -58,28 +55,19 @@ var ( fixedClock = fakeclock.NewFakeClock(fixedClockStart) ) -func generateCSR(t *testing.T, secretKey crypto.Signer, sigAlg x509.SignatureAlgorithm) []byte { - asn1Subj, _ := asn1.Marshal(pkix.Name{ - CommonName: "test", - }.ToRDNSequence()) - template := x509.CertificateRequest{ - RawSubject: asn1Subj, - SignatureAlgorithm: sigAlg, - } - - csrBytes, err := x509.CreateCertificateRequest(rand.Reader, &template, secretKey) +func generateCSR(t *testing.T, secretKey crypto.Signer) []byte { + csr, err := gen.CSRWithSigner(secretKey, + gen.SetCSRCommonName("test"), + ) if err != nil { t.Fatal(err) } - csr := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE REQUEST", Bytes: csrBytes}) - return csr } func generateSelfSignedCACert(t *testing.T, key crypto.Signer, name string) (*x509.Certificate, []byte) { tmpl := &x509.Certificate{ - Version: 2, BasicConstraintsValid: true, SerialNumber: big.NewInt(0), Subject: pkix.Name{ @@ -124,12 +112,12 @@ func TestSign(t *testing.T) { if err != nil { t.Fatal(err) } - testCSR := generateCSR(t, testpk, x509.ECDSAWithSHA256) + testCSR := generateCSR(t, testpk) baseCRNotApproved := gen.CertificateRequest("test-cr", gen.SetCertificateRequestIsCA(true), gen.SetCertificateRequestCSR(testCSR), - gen.SetCertificateRequestIssuer(cmmeta.ObjectReference{ + gen.SetCertificateRequestIssuer(cmmeta.IssuerReference{ Name: baseIssuer.DeepCopy().Name, Group: certmanager.GroupName, Kind: "Issuer", @@ -155,7 +143,7 @@ func TestSign(t *testing.T) { }), ) - // generate a self signed root ca valid for 60d + // generate a self-signed root ca valid for 60d rootCert, rootCertPEM := generateSelfSignedCACert(t, rootPK, "root") rsaCASecret := &corev1.Secret{ ObjectMeta: metav1.ObjectMeta{ @@ -171,7 +159,7 @@ func TestSign(t *testing.T) { badDataSecret := rsaCASecret.DeepCopy() badDataSecret.Data[corev1.TLSPrivateKeyKey] = []byte("bad key") - template, err := pki.GenerateTemplateFromCertificateRequest(baseCR) + template, err := pki.CertificateTemplateFromCertificateRequest(baseCR) if err != nil { t.Fatal(err) } @@ -252,7 +240,7 @@ func TestSign(t *testing.T) { ), }, ExpectedEvents: []string{ - "Normal SecretInvalidData Failed to parse signing CA keypair from secret default-unit-test-ns/root-ca-secret: error decoding private key PEM block", + "Normal SecretInvalidData Failed to parse signing CA keypair from secret default-unit-test-ns/root-ca-secret: error decoding private key PEM block: no PEM data was found in given input", }, ExpectedActions: []testpkg.Action{ testpkg.NewAction(coretesting.NewUpdateSubresourceAction( @@ -264,7 +252,7 @@ func TestSign(t *testing.T) { Type: cmapi.CertificateRequestConditionReady, Status: cmmeta.ConditionFalse, Reason: cmapi.CertificateRequestReasonPending, - Message: "Failed to parse signing CA keypair from secret default-unit-test-ns/root-ca-secret: error decoding private key PEM block", + Message: "Failed to parse signing CA keypair from secret default-unit-test-ns/root-ca-secret: error decoding private key PEM block: no PEM data was found in given input", LastTransitionTime: &metaFixedClockStart, }), ), @@ -370,7 +358,7 @@ func TestSign(t *testing.T) { "a successful signing should set condition to Ready": { certificateRequest: baseCR.DeepCopy(), templateGenerator: func(cr *cmapi.CertificateRequest) (*x509.Certificate, error) { - _, err := pki.GenerateTemplateFromCertificateRequest(cr) + _, err := pki.CertificateTemplateFromCertificateRequest(cr) if err != nil { return nil, err } @@ -450,10 +438,12 @@ func runTest(t *testing.T, test testT) { apiutil.IssuerCA, func(*controller.Context) certificaterequests.Issuer { return ca }, ) - controller.Register(test.builder.Context) + if _, _, err := controller.Register(test.builder.Context); err != nil { + t.Fatal(err) + } test.builder.Start() - err := controller.Sync(context.Background(), test.certificateRequest) + err := controller.Sync(t.Context(), test.certificateRequest) if err != nil && !test.expectedErr { t.Errorf("expected to not get an error, but got: %v", err) } @@ -476,7 +466,7 @@ func TestCA_Sign(t *testing.T) { if err != nil { t.Fatal(err) } - testCSR := generateCSR(t, testpk, x509.ECDSAWithSHA256) + testCSR := generateCSR(t, testpk) tests := map[string]struct { givenCASecret *corev1.Secret @@ -492,7 +482,7 @@ func TestCA_Sign(t *testing.T) { })), givenCR: gen.CertificateRequest("cr-1", gen.SetCertificateRequestCSR(testCSR), - gen.SetCertificateRequestIssuer(cmmeta.ObjectReference{ + gen.SetCertificateRequestIssuer(cmmeta.IssuerReference{ Name: "issuer-1", Group: certmanager.GroupName, Kind: "Issuer", @@ -533,7 +523,7 @@ func TestCA_Sign(t *testing.T) { })), givenCR: gen.CertificateRequest("cr-1", gen.SetCertificateRequestCSR(testCSR), - gen.SetCertificateRequestIssuer(cmmeta.ObjectReference{ + gen.SetCertificateRequestIssuer(cmmeta.IssuerReference{ Name: "issuer-1", Group: certmanager.GroupName, Kind: "Issuer", @@ -552,7 +542,7 @@ func TestCA_Sign(t *testing.T) { })), givenCR: gen.CertificateRequest("cr-1", gen.SetCertificateRequestCSR(testCSR), - gen.SetCertificateRequestIssuer(cmmeta.ObjectReference{ + gen.SetCertificateRequestIssuer(cmmeta.IssuerReference{ Name: "issuer-1", Group: certmanager.GroupName, Kind: "Issuer", @@ -562,6 +552,24 @@ func TestCA_Sign(t *testing.T) { assert.Equal(t, []string{"http://ocsp-v3.example.org"}, got.OCSPServer) }, }, + "when the Issuer has IssuingCertificateURL set, it should appear on the signed ca": { + givenCASecret: gen.SecretFrom(gen.Secret("secret-1"), gen.SetSecretNamespace("default"), gen.SetSecretData(secretDataFor(t, rootPK, rootCert))), + givenCAIssuer: gen.Issuer("issuer-1", gen.SetIssuerCA(cmapi.CAIssuer{ + SecretName: "secret-1", + IssuingCertificateURLs: []string{"http://ca.letsencrypt.org/ca.crt"}, + })), + givenCR: gen.CertificateRequest("cr-1", + gen.SetCertificateRequestCSR(testCSR), + gen.SetCertificateRequestIssuer(cmmeta.IssuerReference{ + Name: "issuer-1", + Group: certmanager.GroupName, + Kind: "Issuer", + }), + ), + assertSignedCert: func(t *testing.T, got *x509.Certificate) { + assert.Equal(t, []string{"http://ca.letsencrypt.org/ca.crt"}, got.IssuingCertificateURL) + }, + }, "when the Issuer has crlDistributionPoints set, it should appear on the signed ca ": { givenCASecret: gen.SecretFrom(gen.Secret("secret-1"), gen.SetSecretNamespace("default"), gen.SetSecretData(secretDataFor(t, rootPK, rootCert))), givenCAIssuer: gen.Issuer("issuer-1", gen.SetIssuerCA(cmapi.CAIssuer{ @@ -571,7 +579,7 @@ func TestCA_Sign(t *testing.T) { givenCR: gen.CertificateRequest("cr-1", gen.SetCertificateRequestIsCA(true), gen.SetCertificateRequestCSR(testCSR), - gen.SetCertificateRequestIssuer(cmmeta.ObjectReference{ + gen.SetCertificateRequestIssuer(cmmeta.IssuerReference{ Name: "issuer-1", Group: certmanager.GroupName, Kind: "Issuer", @@ -596,11 +604,11 @@ func TestCA_Sign(t *testing.T) { secretsLister: testlisters.FakeSecretListerFrom(testlisters.NewFakeSecretLister(), testlisters.SetFakeSecretNamespaceListerGet(test.givenCASecret, nil), ), - templateGenerator: pki.GenerateTemplateFromCertificateRequest, + templateGenerator: pki.CertificateTemplateFromCertificateRequest, signingFn: pki.SignCSRTemplate, } - gotIssueResp, gotErr := c.Sign(context.Background(), test.givenCR, test.givenCAIssuer) + gotIssueResp, gotErr := c.Sign(t.Context(), test.givenCR, test.givenCAIssuer) if test.wantErr != "" { require.EqualError(t, gotErr, test.wantErr) } else { diff --git a/pkg/controller/certificaterequests/checks.go b/pkg/controller/certificaterequests/checks.go index 322b34cd80c..effdc299068 100644 --- a/pkg/controller/certificaterequests/checks.go +++ b/pkg/controller/certificaterequests/checks.go @@ -20,6 +20,7 @@ import ( "fmt" "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/types" cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" logf "github.com/cert-manager/cert-manager/pkg/logs" @@ -41,13 +42,10 @@ func (c *Controller) handleGenericIssuer(obj interface{}) { return } for _, cr := range crs { - log := logf.WithRelatedResource(log, cr) - key, err := keyFunc(cr) - if err != nil { - log.Error(err, "error computing key for resource") - continue - } - c.queue.Add(key) + c.queue.Add(types.NamespacedName{ + Name: cr.Name, + Namespace: cr.Namespace, + }) } } diff --git a/pkg/controller/certificaterequests/controller.go b/pkg/controller/certificaterequests/controller.go index 424e1a8cfe3..6b63c4c581f 100644 --- a/pkg/controller/certificaterequests/controller.go +++ b/pkg/controller/certificaterequests/controller.go @@ -22,12 +22,13 @@ import ( "github.com/go-logr/logr" k8sErrors "k8s.io/apimachinery/pkg/api/errors" - corelisters "k8s.io/client-go/listers/core/v1" + "k8s.io/apimachinery/pkg/types" "k8s.io/client-go/tools/cache" "k8s.io/client-go/tools/record" "k8s.io/client-go/util/workqueue" "k8s.io/utils/clock" + internalinformers "github.com/cert-manager/cert-manager/internal/informers" v1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" cmclient "github.com/cert-manager/cert-manager/pkg/client/clientset/versioned" cmlisters "github.com/cert-manager/cert-manager/pkg/client/listers/certmanager/v1" @@ -37,15 +38,13 @@ import ( logf "github.com/cert-manager/cert-manager/pkg/logs" ) -var keyFunc = controllerpkg.KeyFunc - // Issuer implements the functionality to sign a certificate request for a // particular issuer type. type Issuer interface { Sign(context.Context, *v1.CertificateRequest, v1.GenericIssuer) (*issuer.IssueResponse, error) } -// Issuer Contractor builds a Issuer instance using the given controller +// Issuer Contractor builds an Issuer instance using the given controller // context. type IssuerConstructor func(*controllerpkg.Context) Issuer @@ -54,7 +53,7 @@ type IssuerConstructor func(*controllerpkg.Context) Issuer // covered in the main shared controller implementation. // The returned set of InformerSyncs will be waited on when the controller // starts. -type RegisterExtraInformerFn func(*controllerpkg.Context, logr.Logger, workqueue.RateLimitingInterface) ([]cache.InformerSynced, error) +type RegisterExtraInformerFn func(*controllerpkg.Context, logr.Logger, workqueue.TypedRateLimitingInterface[types.NamespacedName]) ([]cache.InformerSynced, error) // Controller is an implementation of the queueingController for // certificate requests. @@ -72,9 +71,9 @@ type Controller struct { // we need to wait for Secrets to be synced to avoid a situation where CA issuer's Secret // is not yet in cached at a time when issuance is attempted, // more details at https://github.com/cert-manager/cert-manager/issues/5216 - secretLister corelisters.SecretLister + secretLister internalinformers.SecretLister - queue workqueue.RateLimitingInterface + queue workqueue.TypedRateLimitingInterface[types.NamespacedName] // logger to be used by this controller log logr.Logger @@ -88,8 +87,8 @@ type Controller struct { issuerLister cmlisters.IssuerLister clusterIssuerLister cmlisters.ClusterIssuerLister - //registerExtraInformers is a list of functions that CertificateRequest - //controllers can use to register custom informers. + // registerExtraInformers is a list of functions that CertificateRequest + // controllers can use to register custom informers. registerExtraInformers []RegisterExtraInformerFn // Issuer to call sign function @@ -104,7 +103,7 @@ type Controller struct { // New will construct a new certificaterequest controller using the given // Issuer implementation. -// Note: the registerExtraInfromers passed here will be 'waited' for when +// Note: the registerExtraInformers passed here will be 'waited' for when // starting to ensure their corresponding listers have synced. // The caller is responsible for ensuring the informer work functions are setup // correctly on any informer. @@ -123,16 +122,21 @@ func New(issuerType string, issuerConstructor IssuerConstructor, registerExtraIn // Register registers and constructs the controller using the provided context. // It returns the workqueue to be used to enqueue items, a list of // InformerSynced functions that must be synced, or an error. -func (c *Controller) Register(ctx *controllerpkg.Context) (workqueue.RateLimitingInterface, []cache.InformerSynced, error) { +func (c *Controller) Register(ctx *controllerpkg.Context) (workqueue.TypedRateLimitingInterface[types.NamespacedName], []cache.InformerSynced, error) { componentName := "certificaterequests-issuer-" + c.issuerType // construct a new named logger to be reused throughout the controller c.log = logf.FromContext(ctx.RootContext, componentName) // create a queue used to queue up items to be processed - c.queue = workqueue.NewNamedRateLimitingQueue(controllerpkg.DefaultItemBasedRateLimiter(), componentName) - - secretsInformer := ctx.KubeSharedInformerFactory.Core().V1().Secrets() + c.queue = workqueue.NewTypedRateLimitingQueueWithConfig( + controllerpkg.DefaultItemBasedRateLimiter(), + workqueue.TypedRateLimitingQueueConfig[types.NamespacedName]{ + Name: componentName, + }, + ) + + secretsInformer := ctx.KubeSharedInformerFactory.Secrets() issuerInformer := ctx.SharedInformerFactory.Certmanager().V1().Issuers() c.issuerLister = issuerInformer.Lister() c.secretLister = secretsInformer.Lister() @@ -164,7 +168,9 @@ func (c *Controller) Register(ctx *controllerpkg.Context) (workqueue.RateLimitin clusterIssuerInformer := ctx.SharedInformerFactory.Certmanager().V1().ClusterIssuers() c.clusterIssuerLister = clusterIssuerInformer.Lister() // register handler function for clusterissuer resources - clusterIssuerInformer.Informer().AddEventHandler(&controllerpkg.BlockingEventHandler{WorkFunc: c.handleGenericIssuer}) + if _, err := clusterIssuerInformer.Informer().AddEventHandler(&controllerpkg.BlockingEventHandler{WorkFunc: c.handleGenericIssuer}); err != nil { + return nil, nil, fmt.Errorf("error setting up event handler: %v", err) + } mustSync = append(mustSync, clusterIssuerInformer.Informer().HasSynced) } @@ -172,8 +178,12 @@ func (c *Controller) Register(ctx *controllerpkg.Context) (workqueue.RateLimitin c.certificateRequestLister = certificateRequestInformer.Lister() // register handler functions - certificateRequestInformer.Informer().AddEventHandler(&controllerpkg.QueuingEventHandler{Queue: c.queue}) - issuerInformer.Informer().AddEventHandler(&controllerpkg.BlockingEventHandler{WorkFunc: c.handleGenericIssuer}) + if _, err := certificateRequestInformer.Informer().AddEventHandler(&controllerpkg.QueuingEventHandler{Queue: c.queue}); err != nil { + return nil, nil, fmt.Errorf("error setting up event handler: %v", err) + } + if _, err := issuerInformer.Informer().AddEventHandler(&controllerpkg.BlockingEventHandler{WorkFunc: c.handleGenericIssuer}); err != nil { + return nil, nil, fmt.Errorf("error setting up event handler: %v", err) + } // create an issuer helper for reading generic issuers c.helper = issuer.NewHelper(c.issuerLister, c.clusterIssuerLister) @@ -196,25 +206,19 @@ func (c *Controller) Register(ctx *controllerpkg.Context) (workqueue.RateLimitin // ProcessItem is the worker function that will be called with a new key from // the workqueue. A key corresponds to a certificate request object. -func (c *Controller) ProcessItem(ctx context.Context, key string) error { +func (c *Controller) ProcessItem(ctx context.Context, key types.NamespacedName) error { log := logf.FromContext(ctx) - dbg := log.V(logf.DebugLevel) - namespace, name, err := cache.SplitMetaNamespaceKey(key) - if err != nil { - log.Error(err, "invalid resource key") - return nil - } + namespace, name := key.Namespace, key.Name cr, err := c.certificateRequestLister.CertificateRequests(namespace).Get(name) - if err != nil { - if k8sErrors.IsNotFound(err) { - dbg.Info(fmt.Sprintf("certificate request in work queue no longer exists: %s", err)) - return nil - } - + if err != nil && !k8sErrors.IsNotFound(err) { return err } + if cr == nil || cr.DeletionTimestamp != nil { + // If the CertificateRequest object was/ is being deleted, we don't want to start signing. + return nil + } ctx = logf.NewContext(ctx, logf.WithResource(log, cr)) return c.Sync(ctx, cr) diff --git a/pkg/controller/certificaterequests/selfsigned/checks.go b/pkg/controller/certificaterequests/selfsigned/checks.go index cd133e1a20e..3c859655039 100644 --- a/pkg/controller/certificaterequests/selfsigned/checks.go +++ b/pkg/controller/certificaterequests/selfsigned/checks.go @@ -23,9 +23,11 @@ import ( corev1 "k8s.io/api/core/v1" k8sErrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/types" "k8s.io/client-go/util/workqueue" apiutil "github.com/cert-manager/cert-manager/pkg/api/util" + cmdoc "github.com/cert-manager/cert-manager/pkg/apis/certmanager" cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" clientv1 "github.com/cert-manager/cert-manager/pkg/client/listers/certmanager/v1" controllerpkg "github.com/cert-manager/cert-manager/pkg/controller" @@ -40,13 +42,13 @@ import ( func handleSecretReferenceWorkFunc(log logr.Logger, lister clientv1.CertificateRequestLister, helper issuer.Helper, - queue workqueue.RateLimitingInterface, + queue workqueue.TypedRateLimitingInterface[types.NamespacedName], ) func(obj any) { return func(obj any) { log := log.WithName("handleSecretReference") - secret, ok := obj.(*corev1.Secret) + secret, ok := controllerpkg.ToSecret(obj) if !ok { - log.Error(nil, "object is not a secret") + log.Error(nil, "object is not a secret", "object", obj) return } log = logf.WithResource(log, secret) @@ -56,13 +58,10 @@ func handleSecretReferenceWorkFunc(log logr.Logger, return } for _, request := range requests { - log := logf.WithRelatedResource(log, request) - key, err := controllerpkg.KeyFunc(request) - if err != nil { - log.Error(err, "error computing key for resource") - continue - } - queue.Add(key) + queue.Add(types.NamespacedName{ + Name: request.Name, + Namespace: request.Namespace, + }) } } } @@ -82,9 +81,14 @@ func certificateRequestsForSecret(log logr.Logger, return nil, fmt.Errorf("failed to list certificate requests: %w", err) } - dbg.Info("checking if self signed certificate requests reference secret") + dbg.Info("checking if self-signed certificate requests reference secret") var affected []*cmapi.CertificateRequest for _, request := range requests { + if request.Spec.IssuerRef.Group != cmdoc.GroupName { + dbg.Info("skipping SelfSigned secret reference checks since issuer has external group", "group", request.Spec.IssuerRef.Group) + continue + } + issuerObj, err := helper.GetGenericIssuer(request.Spec.IssuerRef, request.Namespace) if k8sErrors.IsNotFound(err) { dbg.Info("issuer not found, skipping") diff --git a/pkg/controller/certificaterequests/selfsigned/checks_test.go b/pkg/controller/certificaterequests/selfsigned/checks_test.go index 09bd3026e35..93ccce86e68 100644 --- a/pkg/controller/certificaterequests/selfsigned/checks_test.go +++ b/pkg/controller/certificaterequests/selfsigned/checks_test.go @@ -22,8 +22,9 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" "k8s.io/client-go/util/workqueue" - "k8s.io/klog/v2/klogr" + "k8s.io/klog/v2/ktesting" cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" @@ -37,7 +38,7 @@ func Test_handleSecretReferenceWorkFunc(t *testing.T) { secret runtime.Object existingCRs []runtime.Object existingIssuers []runtime.Object - expectedQueue []string + expectedQueue []types.NamespacedName }{ "if given object is not secret, expect empty queue": { secret: gen.Certificate("not-a-secret"), @@ -46,7 +47,7 @@ func Test_handleSecretReferenceWorkFunc(t *testing.T) { gen.SetCertificateRequestNamespace("test-namespace"), gen.SetCertificateRequestAnnotations(map[string]string{ "cert-manager.io/private-key-secret-name": "test-secret", - }), gen.SetCertificateRequestIssuer(cmmeta.ObjectReference{ + }), gen.SetCertificateRequestIssuer(cmmeta.IssuerReference{ Name: "a", Kind: "Issuer", Group: "cert-manager.io", }), ), @@ -54,7 +55,7 @@ func Test_handleSecretReferenceWorkFunc(t *testing.T) { gen.SetCertificateRequestNamespace("test-namespace"), gen.SetCertificateRequestAnnotations(map[string]string{ "cert-manager.io/private-key-secret-name": "test-secret", - }), gen.SetCertificateRequestIssuer(cmmeta.ObjectReference{ + }), gen.SetCertificateRequestIssuer(cmmeta.IssuerReference{ Name: "b", Kind: "ClusterIssuer", Group: "cert-manager.io", }), ), @@ -68,7 +69,7 @@ func Test_handleSecretReferenceWorkFunc(t *testing.T) { gen.SetIssuerSelfSigned(cmapi.SelfSignedIssuer{}), ), }, - expectedQueue: []string{}, + expectedQueue: []types.NamespacedName{}, }, "if no requests then expect empty queue": { secret: gen.Secret("test-secret", gen.SetSecretNamespace("test-namespace")), @@ -82,7 +83,7 @@ func Test_handleSecretReferenceWorkFunc(t *testing.T) { gen.SetIssuerSelfSigned(cmapi.SelfSignedIssuer{}), ), }, - expectedQueue: []string{}, + expectedQueue: []types.NamespacedName{}, }, "referenced requests should be added to the queue": { secret: gen.Secret("test-secret", gen.SetSecretNamespace("test-namespace")), @@ -91,7 +92,7 @@ func Test_handleSecretReferenceWorkFunc(t *testing.T) { gen.SetCertificateRequestNamespace("test-namespace"), gen.SetCertificateRequestAnnotations(map[string]string{ "cert-manager.io/private-key-secret-name": "test-secret", - }), gen.SetCertificateRequestIssuer(cmmeta.ObjectReference{ + }), gen.SetCertificateRequestIssuer(cmmeta.IssuerReference{ Name: "a", Kind: "Issuer", Group: "cert-manager.io", }), ), @@ -99,7 +100,7 @@ func Test_handleSecretReferenceWorkFunc(t *testing.T) { gen.SetCertificateRequestNamespace("test-namespace"), gen.SetCertificateRequestAnnotations(map[string]string{ "cert-manager.io/private-key-secret-name": "test-secret", - }), gen.SetCertificateRequestIssuer(cmmeta.ObjectReference{ + }), gen.SetCertificateRequestIssuer(cmmeta.IssuerReference{ Name: "b", Kind: "ClusterIssuer", Group: "cert-manager.io", }), ), @@ -113,7 +114,16 @@ func Test_handleSecretReferenceWorkFunc(t *testing.T) { gen.SetIssuerSelfSigned(cmapi.SelfSignedIssuer{}), ), }, - expectedQueue: []string{"test-namespace/a", "test-namespace/b"}, + expectedQueue: []types.NamespacedName{ + { + Namespace: "test-namespace", + Name: "a", + }, + { + Namespace: "test-namespace", + Name: "b", + }, + }, }, } @@ -134,13 +144,13 @@ func Test_handleSecretReferenceWorkFunc(t *testing.T) { builder.Start() - queue := workqueue.NewRateLimitingQueue(workqueue.DefaultControllerRateLimiter()) - handleSecretReferenceWorkFunc(klogr.New(), lister, helper, queue)(test.secret) + queue := workqueue.NewTypedRateLimitingQueue(workqueue.DefaultTypedControllerRateLimiter[types.NamespacedName]()) + handleSecretReferenceWorkFunc(ktesting.NewLogger(t, ktesting.NewConfig()), lister, helper, queue)(test.secret) require.Equal(t, len(test.expectedQueue), queue.Len()) - var actualQueue []string + var actualQueue []types.NamespacedName for range test.expectedQueue { i, _ := queue.Get() - actualQueue = append(actualQueue, i.(string)) + actualQueue = append(actualQueue, i) } assert.ElementsMatch(t, test.expectedQueue, actualQueue) }) @@ -179,7 +189,7 @@ func Test_certificatesRequestsForSecret(t *testing.T) { gen.SetCertificateRequestNamespace("test-namespace"), gen.SetCertificateRequestAnnotations(map[string]string{ "cert-manager.io/private-key-secret-name": "test-secret", - }), gen.SetCertificateRequestIssuer(cmmeta.ObjectReference{ + }), gen.SetCertificateRequestIssuer(cmmeta.IssuerReference{ Name: "a", Kind: "Issuer", Group: "cert-manager.io", }), ), @@ -187,7 +197,7 @@ func Test_certificatesRequestsForSecret(t *testing.T) { gen.SetCertificateRequestNamespace("test-namespace"), gen.SetCertificateRequestAnnotations(map[string]string{ "cert-manager.io/private-key-secret-name": "test-secret", - }), gen.SetCertificateRequestIssuer(cmmeta.ObjectReference{ + }), gen.SetCertificateRequestIssuer(cmmeta.IssuerReference{ Name: "b", Kind: "ClusterIssuer", Group: "cert-manager.io", }), ), @@ -195,13 +205,13 @@ func Test_certificatesRequestsForSecret(t *testing.T) { existingIssuers: []runtime.Object{}, expectedAffected: []*cmapi.CertificateRequest{}, }, - "if issuers are not self signed then don't return requests": { + "if issuers are not self-signed then don't return requests": { existingCRs: []runtime.Object{ gen.CertificateRequest("a", gen.SetCertificateRequestNamespace("test-namespace"), gen.SetCertificateRequestAnnotations(map[string]string{ "cert-manager.io/private-key-secret-name": "test-secret", - }), gen.SetCertificateRequestIssuer(cmmeta.ObjectReference{ + }), gen.SetCertificateRequestIssuer(cmmeta.IssuerReference{ Name: "a", Kind: "Issuer", Group: "cert-manager.io", }), ), @@ -209,7 +219,7 @@ func Test_certificatesRequestsForSecret(t *testing.T) { gen.SetCertificateRequestNamespace("test-namespace"), gen.SetCertificateRequestAnnotations(map[string]string{ "cert-manager.io/private-key-secret-name": "test-secret", - }), gen.SetCertificateRequestIssuer(cmmeta.ObjectReference{ + }), gen.SetCertificateRequestIssuer(cmmeta.IssuerReference{ Name: "b", Kind: "ClusterIssuer", Group: "cert-manager.io", }), ), @@ -225,13 +235,27 @@ func Test_certificatesRequestsForSecret(t *testing.T) { }, expectedAffected: []*cmapi.CertificateRequest{}, }, + "if issuer has different group, do nothing": { + existingCRs: []runtime.Object{ + gen.CertificateRequest("a", + gen.SetCertificateRequestNamespace("test-namespace"), + gen.SetCertificateRequestAnnotations(map[string]string{ + "cert-manager.io/private-key-secret-name": "test-secret", + }), gen.SetCertificateRequestIssuer(cmmeta.IssuerReference{ + Name: "a", Kind: "Keith", Group: "not-cert-manager.io", + }), + ), + }, + existingIssuers: []runtime.Object{}, + expectedAffected: []*cmapi.CertificateRequest{}, + }, "should not return requests which are in a different namespace": { existingCRs: []runtime.Object{ gen.CertificateRequest("a", gen.SetCertificateRequestNamespace("another-namespace"), gen.SetCertificateRequestAnnotations(map[string]string{ "cert-manager.io/private-key-secret-name": "test-secret", - }), gen.SetCertificateRequestIssuer(cmmeta.ObjectReference{ + }), gen.SetCertificateRequestIssuer(cmmeta.IssuerReference{ Name: "a", Kind: "Issuer", Group: "cert-manager.io", }), ), @@ -239,7 +263,7 @@ func Test_certificatesRequestsForSecret(t *testing.T) { gen.SetCertificateRequestNamespace("another-namespace"), gen.SetCertificateRequestAnnotations(map[string]string{ "cert-manager.io/private-key-secret-name": "test-secret", - }), gen.SetCertificateRequestIssuer(cmmeta.ObjectReference{ + }), gen.SetCertificateRequestIssuer(cmmeta.IssuerReference{ Name: "b", Kind: "ClusterIssuer", Group: "cert-manager.io", }), ), @@ -261,7 +285,7 @@ func Test_certificatesRequestsForSecret(t *testing.T) { gen.SetCertificateRequestNamespace("test-namespace"), gen.SetCertificateRequestAnnotations(map[string]string{ "cert-manager.io/private-key-secret-name": "test-secret", - }), gen.SetCertificateRequestIssuer(cmmeta.ObjectReference{ + }), gen.SetCertificateRequestIssuer(cmmeta.IssuerReference{ Name: "a", Kind: "Issuer", Group: "cert-manager.io", }), ), @@ -269,7 +293,7 @@ func Test_certificatesRequestsForSecret(t *testing.T) { gen.SetCertificateRequestNamespace("test-namespace"), gen.SetCertificateRequestAnnotations(map[string]string{ "cert-manager.io/private-key-secret-name": "test-secret", - }), gen.SetCertificateRequestIssuer(cmmeta.ObjectReference{ + }), gen.SetCertificateRequestIssuer(cmmeta.IssuerReference{ Name: "b", Kind: "ClusterIssuer", Group: "cert-manager.io", }), ), @@ -288,7 +312,7 @@ func Test_certificatesRequestsForSecret(t *testing.T) { gen.SetCertificateRequestNamespace("test-namespace"), gen.SetCertificateRequestAnnotations(map[string]string{ "cert-manager.io/private-key-secret-name": "test-secret", - }), gen.SetCertificateRequestIssuer(cmmeta.ObjectReference{ + }), gen.SetCertificateRequestIssuer(cmmeta.IssuerReference{ Name: "a", Kind: "Issuer", Group: "cert-manager.io", }), ), @@ -296,7 +320,7 @@ func Test_certificatesRequestsForSecret(t *testing.T) { gen.SetCertificateRequestNamespace("test-namespace"), gen.SetCertificateRequestAnnotations(map[string]string{ "cert-manager.io/private-key-secret-name": "test-secret", - }), gen.SetCertificateRequestIssuer(cmmeta.ObjectReference{ + }), gen.SetCertificateRequestIssuer(cmmeta.IssuerReference{ Name: "b", Kind: "ClusterIssuer", Group: "cert-manager.io", }), ), @@ -321,7 +345,7 @@ func Test_certificatesRequestsForSecret(t *testing.T) { builder.Start() - affected, err := certificateRequestsForSecret(klogr.New(), lister, helper, secret.DeepCopy()) + affected, err := certificateRequestsForSecret(ktesting.NewLogger(t, ktesting.NewConfig()), lister, helper, secret.DeepCopy()) assert.NoError(t, err) assert.ElementsMatch(t, test.expectedAffected, affected) }) diff --git a/pkg/controller/certificaterequests/selfsigned/selfsigned.go b/pkg/controller/certificaterequests/selfsigned/selfsigned.go index e55aa52033d..0515e868252 100644 --- a/pkg/controller/certificaterequests/selfsigned/selfsigned.go +++ b/pkg/controller/certificaterequests/selfsigned/selfsigned.go @@ -23,15 +23,18 @@ import ( "errors" "fmt" + "github.com/go-logr/logr" corev1 "k8s.io/api/core/v1" k8sErrors "k8s.io/apimachinery/pkg/api/errors" - corelisters "k8s.io/client-go/listers/core/v1" + "k8s.io/apimachinery/pkg/types" "k8s.io/client-go/tools/cache" "k8s.io/client-go/tools/record" "k8s.io/client-go/util/workqueue" + internalinformers "github.com/cert-manager/cert-manager/internal/informers" apiutil "github.com/cert-manager/cert-manager/pkg/api/util" cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" + cmlisters "github.com/cert-manager/cert-manager/pkg/client/listers/certmanager/v1" controllerpkg "github.com/cert-manager/cert-manager/pkg/controller" "github.com/cert-manager/cert-manager/pkg/controller/certificaterequests" crutil "github.com/cert-manager/cert-manager/pkg/controller/certificaterequests/util" @@ -40,7 +43,6 @@ import ( cmerrors "github.com/cert-manager/cert-manager/pkg/util/errors" "github.com/cert-manager/cert-manager/pkg/util/kube" "github.com/cert-manager/cert-manager/pkg/util/pki" - "github.com/go-logr/logr" ) const ( @@ -52,12 +54,12 @@ type signingFn func(*x509.Certificate, *x509.Certificate, crypto.PublicKey, inte type SelfSigned struct { issuerOptions controllerpkg.IssuerOptions - secretsLister corelisters.SecretLister + secretsLister internalinformers.SecretLister reporter *crutil.Reporter recorder record.EventRecorder - // Used for testing to get reproducible resulting certificates + // signingFn is the function called to actually sign certificates. It's a member of the struct so it can be mocked for testing. signingFn signingFn } @@ -71,21 +73,32 @@ func init() { // Handle informed Secrets which may be referenced by the // "cert-manager.io/private-key-secret-name" annotation. - func(ctx *controllerpkg.Context, log logr.Logger, queue workqueue.RateLimitingInterface) ([]cache.InformerSynced, error) { - secretInformer := ctx.KubeSharedInformerFactory.Core().V1().Secrets().Informer() + func(ctx *controllerpkg.Context, log logr.Logger, queue workqueue.TypedRateLimitingInterface[types.NamespacedName]) ([]cache.InformerSynced, error) { + secretInformer := ctx.KubeSharedInformerFactory.Secrets().Informer() certificateRequestLister := ctx.SharedInformerFactory.Certmanager().V1().CertificateRequests().Lister() + + isNamespaced := ctx.Namespace != "" + + mustSync := []cache.InformerSynced{ + secretInformer.HasSynced, + ctx.SharedInformerFactory.Certmanager().V1().Issuers().Informer().HasSynced, + } + + var clusterIssuerLister cmlisters.ClusterIssuerLister + if !isNamespaced { + clusterIssuerLister = ctx.SharedInformerFactory.Certmanager().V1().ClusterIssuers().Lister() + mustSync = append(mustSync, ctx.SharedInformerFactory.Certmanager().V1().ClusterIssuers().Informer().HasSynced) + } helper := issuer.NewHelper( ctx.SharedInformerFactory.Certmanager().V1().Issuers().Lister(), - ctx.SharedInformerFactory.Certmanager().V1().ClusterIssuers().Lister(), + clusterIssuerLister, ) - secretInformer.AddEventHandler(&controllerpkg.BlockingEventHandler{ + if _, err := secretInformer.AddEventHandler(&controllerpkg.BlockingEventHandler{ WorkFunc: handleSecretReferenceWorkFunc(log, certificateRequestLister, helper, queue), - }) - return []cache.InformerSynced{ - secretInformer.HasSynced, - ctx.SharedInformerFactory.Certmanager().V1().Issuers().Informer().HasSynced, - ctx.SharedInformerFactory.Certmanager().V1().ClusterIssuers().Informer().HasSynced, - }, nil + }); err != nil { + return nil, fmt.Errorf("error setting up event handler: %v", err) + } + return mustSync, nil }, )). Complete() @@ -95,7 +108,7 @@ func init() { func NewSelfSigned(ctx *controllerpkg.Context) certificaterequests.Issuer { return &SelfSigned{ issuerOptions: ctx.IssuerOptions, - secretsLister: ctx.KubeSharedInformerFactory.Core().V1().Secrets().Lister(), + secretsLister: ctx.KubeSharedInformerFactory.Secrets().Lister(), reporter: crutil.NewReporter(ctx.Clock, ctx.Recorder), recorder: ctx.Recorder, signingFn: pki.SignCertificate, @@ -147,7 +160,8 @@ func (s *SelfSigned) Sign(ctx context.Context, cr *cmapi.CertificateRequest, iss return nil, err } - template, err := pki.GenerateTemplateFromCertificateRequest(cr) + var template *x509.Certificate + template, err = pki.CertificateTemplateFromCertificateRequest(cr) if err != nil { message := "Error generating certificate template" s.reporter.Failed(cr, err, "ErrorGenerating", message) @@ -198,9 +212,9 @@ func (s *SelfSigned) Sign(ctx context.Context, cr *cmapi.CertificateRequest, iss return nil, nil } - log.V(logf.DebugLevel).Info("self signed certificate issued") + log.V(logf.DebugLevel).Info("self-signed certificate issued") - // We set the CA to the returned certificate here since this is self signed. + // We set the CA to the returned certificate here since this is self-signed. return &issuer.IssueResponse{ Certificate: certPem, CA: certPem, diff --git a/pkg/controller/certificaterequests/selfsigned/selfsigned_test.go b/pkg/controller/certificaterequests/selfsigned/selfsigned_test.go index 36daeb3ca42..f4d969d5f28 100644 --- a/pkg/controller/certificaterequests/selfsigned/selfsigned_test.go +++ b/pkg/controller/certificaterequests/selfsigned/selfsigned_test.go @@ -17,13 +17,8 @@ limitations under the License. package selfsigned import ( - "context" "crypto" - "crypto/rand" "crypto/x509" - "crypto/x509/pkix" - "encoding/asn1" - "encoding/pem" "errors" "fmt" "testing" @@ -53,23 +48,14 @@ var ( fixedClock = fakeclock.NewFakeClock(fixedClockStart) ) -func generateCSR(t *testing.T, secretKey crypto.Signer, alg x509.SignatureAlgorithm, commonName string) []byte { - asn1Subj, _ := asn1.Marshal(pkix.Name{ - CommonName: commonName, - }.ToRDNSequence()) - template := x509.CertificateRequest{ - RawSubject: asn1Subj, - SignatureAlgorithm: alg, - } - - csrBytes, err := x509.CreateCertificateRequest(rand.Reader, &template, secretKey) +func generateCSR(t *testing.T, secretKey crypto.Signer, commonName string) []byte { + csr, err := gen.CSRWithSigner(secretKey, + gen.SetCSRCommonName(commonName), + ) if err != nil { - t.Error(err) - t.FailNow() + t.Fatal(err) } - csr := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE REQUEST", Bytes: csrBytes}) - return csr } @@ -108,11 +94,11 @@ func TestSign(t *testing.T) { corev1.TLSPrivateKeyKey: []byte("this is a bad key"), }, } - csrRSAPEM := generateCSR(t, skRSA, x509.SHA256WithRSA, "test-rsa") + csrRSAPEM := generateCSR(t, skRSA, "test-rsa") skEC, err := pki.GenerateECPrivateKey(256) if err != nil { - t.Errorf("failed to generate ECDA private key: %s", err) + t.Errorf("failed to generate EC private key: %s", err) t.FailNow() } skECPEM, err := pki.EncodeECPrivateKey(skEC) @@ -129,9 +115,9 @@ func TestSign(t *testing.T) { corev1.TLSPrivateKeyKey: skECPEM, }, } - csrECPEM := generateCSR(t, skEC, x509.ECDSAWithSHA256, "test-ec") + csrECPEM := generateCSR(t, skEC, "test-ec") - csrEmptyCertPEM := generateCSR(t, skEC, x509.ECDSAWithSHA256, "") + csrEmptyCertPEM := generateCSR(t, skEC, "") baseCRNotApproved := gen.CertificateRequest("test-cr", gen.SetCertificateRequestAnnotations( @@ -140,7 +126,7 @@ func TestSign(t *testing.T) { }, ), gen.SetCertificateRequestCSR(csrRSAPEM), - gen.SetCertificateRequestIssuer(cmmeta.ObjectReference{ + gen.SetCertificateRequestIssuer(cmmeta.IssuerReference{ Name: baseIssuer.Name, Group: certmanager.GroupName, Kind: "Issuer", @@ -171,7 +157,7 @@ func TestSign(t *testing.T) { gen.SetCertificateRequestCSR(csrEmptyCertPEM), ) - templateRSA, err := pki.GenerateTemplateFromCertificateRequest(baseCR) + templateRSA, err := pki.CertificateTemplateFromCertificateRequest(baseCR) if err != nil { t.Error(err) t.FailNow() @@ -182,7 +168,7 @@ func TestSign(t *testing.T) { t.FailNow() } - templateEC, err := pki.GenerateTemplateFromCertificateRequest(ecCR) + templateEC, err := pki.CertificateTemplateFromCertificateRequest(ecCR) if err != nil { t.Error(err) t.FailNow() @@ -193,7 +179,7 @@ func TestSign(t *testing.T) { t.FailNow() } - templateEmptyCert, err := pki.GenerateTemplateFromCertificateRequest(emptyCR) + templateEmptyCert, err := pki.CertificateTemplateFromCertificateRequest(emptyCR) if err != nil { t.Error(err) t.FailNow() @@ -341,7 +327,7 @@ func TestSign(t *testing.T) { KubeObjects: []runtime.Object{invalidKeySecret}, CertManagerObjects: []runtime.Object{baseCR.DeepCopy(), baseIssuer}, ExpectedEvents: []string{ - `Normal ErrorParsingKey Failed to get key "test-rsa-key" referenced in annotation "cert-manager.io/private-key-secret-name": error decoding private key PEM block`, + `Normal ErrorParsingKey Failed to get key "test-rsa-key" referenced in annotation "cert-manager.io/private-key-secret-name": error decoding private key PEM block: no PEM data was found in given input`, }, ExpectedActions: []testpkg.Action{ testpkg.NewAction(coretesting.NewUpdateSubresourceAction( @@ -353,7 +339,7 @@ func TestSign(t *testing.T) { Type: cmapi.CertificateRequestConditionReady, Status: cmmeta.ConditionFalse, Reason: cmapi.CertificateRequestReasonPending, - Message: `Failed to get key "test-rsa-key" referenced in annotation "cert-manager.io/private-key-secret-name": error decoding private key PEM block`, + Message: `Failed to get key "test-rsa-key" referenced in annotation "cert-manager.io/private-key-secret-name": error decoding private key PEM block: no PEM data was found in given input`, LastTransitionTime: &metaFixedClockStart, }), ), @@ -643,10 +629,12 @@ func runTest(t *testing.T, test testT) { apiutil.IssuerSelfSigned, func(*controller.Context) certificaterequests.Issuer { return self }, ) - controller.Register(test.builder.Context) + if _, _, err := controller.Register(test.builder.Context); err != nil { + t.Fatal(err) + } test.builder.Start() - err := controller.Sync(context.Background(), test.certificateRequest) + err := controller.Sync(t.Context(), test.certificateRequest) if err != nil && !test.expectedErr { t.Errorf("expected to not get an error, but got: %v", err) } diff --git a/pkg/controller/certificaterequests/sync.go b/pkg/controller/certificaterequests/sync.go index 5ba01635102..3e0c388156c 100644 --- a/pkg/controller/certificaterequests/sync.go +++ b/pkg/controller/certificaterequests/sync.go @@ -21,7 +21,7 @@ import ( "fmt" "reflect" - "github.com/kr/pretty" + "github.com/google/go-cmp/cmp" corev1 "k8s.io/api/core/v1" apiequality "k8s.io/apimachinery/pkg/api/equality" k8sErrors "k8s.io/apimachinery/pkg/api/errors" @@ -63,6 +63,12 @@ func (c *Controller) Sync(ctx context.Context, cr *cmapi.CertificateRequest) (er return nil } + // If CertificateRequest is invalid, do not process it + if apiutil.CertificateRequestHasInvalidRequest(cr) { + dbg.Info("certificate request is invalid and will not be further processed") + return nil + } + // If CertificateRequest has not been approved, exit early. if !apiutil.CertificateRequestIsApproved(cr) { dbg.Info("certificate request has not been approved") @@ -162,21 +168,21 @@ func (c *Controller) Sync(ctx context.Context, cr *cmapi.CertificateRequest) (er return nil } -func (c *Controller) updateCertificateRequestStatusAndAnnotations(ctx context.Context, old, new *cmapi.CertificateRequest) error { +func (c *Controller) updateCertificateRequestStatusAndAnnotations(ctx context.Context, oldCR, newCR *cmapi.CertificateRequest) error { log := logf.FromContext(ctx, "updateStatus") // if annotations changed we have to call .Update() and not .UpdateStatus() - if !reflect.DeepEqual(old.Annotations, new.Annotations) { - log.V(logf.DebugLevel).Info("updating resource due to change in annotations", "diff", pretty.Diff(old.Annotations, new.Annotations)) - return c.updateOrApply(ctx, new) + if !reflect.DeepEqual(oldCR.Annotations, newCR.Annotations) { + log.V(logf.DebugLevel).Info("updating resource due to change in annotations", "diff", cmp.Diff(oldCR.Annotations, newCR.Annotations)) + return c.updateOrApply(ctx, newCR) } - if apiequality.Semantic.DeepEqual(old.Status, new.Status) { + if apiequality.Semantic.DeepEqual(oldCR.Status, newCR.Status) { return nil } - log.V(logf.DebugLevel).Info("updating resource due to change in status", "diff", pretty.Diff(old.Status, new.Status)) - return c.updateStatusOrApply(ctx, new) + log.V(logf.DebugLevel).Info("updating resource due to change in status", "diff", cmp.Diff(oldCR.Status, newCR.Status)) + return c.updateStatusOrApply(ctx, newCR) } func (c *Controller) updateOrApply(ctx context.Context, cr *cmapi.CertificateRequest) error { diff --git a/pkg/controller/certificaterequests/sync_test.go b/pkg/controller/certificaterequests/sync_test.go index 74b1b279418..db2ee7145ac 100644 --- a/pkg/controller/certificaterequests/sync_test.go +++ b/pkg/controller/certificaterequests/sync_test.go @@ -22,8 +22,6 @@ import ( "crypto" "crypto/rand" "crypto/x509" - "crypto/x509/pkix" - "encoding/asn1" "encoding/pem" "errors" "testing" @@ -42,9 +40,10 @@ import ( testpkg "github.com/cert-manager/cert-manager/pkg/controller/test" "github.com/cert-manager/cert-manager/pkg/issuer" issuerfake "github.com/cert-manager/cert-manager/pkg/issuer/fake" - _ "github.com/cert-manager/cert-manager/pkg/issuer/selfsigned" "github.com/cert-manager/cert-manager/pkg/util/pki" "github.com/cert-manager/cert-manager/test/unit/gen" + + _ "github.com/cert-manager/cert-manager/pkg/issuer/selfsigned" ) var ( @@ -52,30 +51,20 @@ var ( fixedClock = fakeclock.NewFakeClock(fixedClockStart) ) -func generateCSR(t *testing.T, secretKey crypto.Signer, alg x509.SignatureAlgorithm) []byte { - t.Helper() - asn1Subj, _ := asn1.Marshal(pkix.Name{ - CommonName: "test", - }.ToRDNSequence()) - template := x509.CertificateRequest{ - RawSubject: asn1Subj, - SignatureAlgorithm: alg, - } - - csrBytes, err := x509.CreateCertificateRequest(rand.Reader, &template, secretKey) +func generateCSR(t *testing.T, secretKey crypto.Signer) []byte { + csr, err := gen.CSRWithSigner(secretKey, + gen.SetCSRCommonName("test"), + ) if err != nil { - t.Error(err) - t.FailNow() + t.Fatal(err) } - csr := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE REQUEST", Bytes: csrBytes}) - return csr } func generateSelfSignedCert(t *testing.T, cr *cmapi.CertificateRequest, key crypto.Signer, notBefore, notAfter time.Time) []byte { t.Helper() - template, err := pki.GenerateTemplateFromCertificateRequest(cr) + template, err := pki.CertificateTemplateFromCertificateRequest(cr) if err != nil { t.Errorf("failed to generate cert template from CSR: %v", err) t.FailNow() @@ -115,8 +104,8 @@ func TestSync(t *testing.T) { t.FailNow() } - csrRSAPEM := generateCSR(t, skRSA, x509.SHA256WithRSA) - csrECPEM := generateCSR(t, skEC, x509.ECDSAWithSHA256) + csrRSAPEM := generateCSR(t, skRSA) + csrECPEM := generateCSR(t, skEC) baseIssuer := gen.Issuer("test-issuer", gen.SetIssuerSelfSigned(cmapi.SelfSignedIssuer{}), @@ -129,7 +118,7 @@ func TestSync(t *testing.T) { baseCRNotApproved := gen.CertificateRequest("test-cr", gen.SetCertificateRequestIsCA(false), gen.SetCertificateRequestCSR(csrRSAPEM), - gen.SetCertificateRequestIssuer(cmmeta.ObjectReference{ + gen.SetCertificateRequestIssuer(cmmeta.IssuerReference{ Kind: baseIssuer.Kind, Name: baseIssuer.Name, }), @@ -147,7 +136,7 @@ func TestSync(t *testing.T) { baseCRNotApprovedEC := gen.CertificateRequest("test-cr", gen.SetCertificateRequestIsCA(false), gen.SetCertificateRequestCSR(csrECPEM), - gen.SetCertificateRequestIssuer(cmmeta.ObjectReference{ + gen.SetCertificateRequestIssuer(cmmeta.IssuerReference{ Kind: baseIssuer.Kind, Name: baseIssuer.Name, }), @@ -172,7 +161,7 @@ func TestSync(t *testing.T) { tests := map[string]testT{ "should return nil (no action) if group name if not 'cert-manager.io' or ''": { certificateRequest: gen.CertificateRequestFrom(baseCR, - gen.SetCertificateRequestIssuer(cmmeta.ObjectReference{ + gen.SetCertificateRequestIssuer(cmmeta.IssuerReference{ Group: "not-cert-manager.io", }), ), @@ -384,6 +373,21 @@ func TestSync(t *testing.T) { ExpectedActions: []testpkg.Action{}, }, }, + "should return nil (no action) if certificate request invalidrequest is set to true": { + certificateRequest: gen.CertificateRequestFrom(baseCRNotApproved, + gen.SetCertificateRequestStatusCondition(cmapi.CertificateRequestCondition{ + Type: cmapi.CertificateRequestConditionInvalidRequest, + Status: cmmeta.ConditionTrue, + Reason: "InvalidRequest", + Message: "Certificate request is invalid", + LastTransitionTime: &nowMetaTime, + }), + ), + builder: &testpkg.Builder{ + CertManagerObjects: []runtime.Object{baseIssuer, baseCR}, + ExpectedActions: []testpkg.Action{}, + }, + }, "should return nil (no action) if certificate request is ready and reason Issued": { certificateRequest: gen.CertificateRequestFrom(baseCR, gen.SetCertificateRequestStatusCondition(cmapi.CertificateRequestCondition{ @@ -441,10 +445,10 @@ func TestSync(t *testing.T) { }, }, }, - "should return error to try again if there was a error getting issuer wasn't a not found error": { + "should return error to try again if there was an error getting issuer wasn't a not found error": { certificateRequest: baseCR.DeepCopy(), helper: &issuerfake.Helper{ - GetGenericIssuerFunc: func(cmmeta.ObjectReference, string) (cmapi.GenericIssuer, error) { + GetGenericIssuerFunc: func(cmmeta.IssuerReference, string) (cmapi.GenericIssuer, error) { return nil, errors.New("this is a network error") }, }, @@ -581,7 +585,7 @@ func TestSync(t *testing.T) { gen.SetCertificateRequestCertificate([]byte("a bad certificate")), )}, ExpectedEvents: []string{ - "Warning DecodeError Failed to decode returned certificate: error decoding certificate PEM block", + "Warning DecodeError Failed to decode returned certificate: error decoding certificate PEM block: no valid certificates found", }, ExpectedActions: []testpkg.Action{ testpkg.NewAction(coretesting.NewUpdateSubresourceAction( @@ -594,7 +598,7 @@ func TestSync(t *testing.T) { Type: cmapi.CertificateRequestConditionReady, Status: cmmeta.ConditionFalse, Reason: "Failed", - Message: "Failed to decode returned certificate: error decoding certificate PEM block", + Message: "Failed to decode returned certificate: error decoding certificate PEM block: no valid certificates found", LastTransitionTime: &nowMetaTime, }), gen.SetCertificateRequestFailureTime(nowMetaTime), @@ -769,7 +773,9 @@ func runTest(t *testing.T, test testT) { } c := New(util.IssuerSelfSigned, func(*controller.Context) Issuer { return test.issuerImpl }) - c.Register(test.builder.Context) + if _, _, err := c.Register(test.builder.Context); err != nil { + t.Fatal(err) + } if test.helper != nil { c.helper = test.helper @@ -777,7 +783,7 @@ func runTest(t *testing.T, test testT) { test.builder.Start() - err := c.Sync(context.Background(), test.certificateRequest) + err := c.Sync(t.Context(), test.certificateRequest) if err != nil && !test.expectedErr { t.Errorf("expected to not get an error, but got: %v", err) } diff --git a/pkg/controller/certificaterequests/util/reporter_test.go b/pkg/controller/certificaterequests/util/reporter_test.go index 41f0d86c95f..a8b1e953c2e 100644 --- a/pkg/controller/certificaterequests/util/reporter_test.go +++ b/pkg/controller/certificaterequests/util/reporter_test.go @@ -19,6 +19,7 @@ package util import ( "errors" "fmt" + "slices" "testing" "time" @@ -28,7 +29,6 @@ import ( apiutil "github.com/cert-manager/cert-manager/pkg/api/util" cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" controllertest "github.com/cert-manager/cert-manager/pkg/controller/test" - "github.com/cert-manager/cert-manager/pkg/util" "github.com/cert-manager/cert-manager/test/unit/gen" ) @@ -270,7 +270,7 @@ func (tt *reporterT) runTest(t *testing.T) { expConditions, gotConditions) } - if !util.EqualSorted(tt.expectedEvents, recorder.Events) { + if !slices.Equal(tt.expectedEvents, recorder.Events) { t.Errorf("got unexpected events, exp=%+v got=%+v", tt.expectedEvents, recorder.Events) } diff --git a/pkg/controller/certificaterequests/vault/fuzz_test.go b/pkg/controller/certificaterequests/vault/fuzz_test.go new file mode 100644 index 00000000000..0b8176e717c --- /dev/null +++ b/pkg/controller/certificaterequests/vault/fuzz_test.go @@ -0,0 +1,192 @@ +/* +Copyright 2024 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. +*/ + +package vault + +import ( + "context" + "crypto/rsa" + "testing" + "time" + + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + + internalinformers "github.com/cert-manager/cert-manager/internal/informers" + internalvault "github.com/cert-manager/cert-manager/internal/vault" + fakevault "github.com/cert-manager/cert-manager/internal/vault/fake" + apiutil "github.com/cert-manager/cert-manager/pkg/api/util" + "github.com/cert-manager/cert-manager/pkg/apis/certmanager" + cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" + cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" + controllerpkg "github.com/cert-manager/cert-manager/pkg/controller" + "github.com/cert-manager/cert-manager/pkg/controller/certificaterequests" + testpkg "github.com/cert-manager/cert-manager/pkg/controller/test" + "github.com/cert-manager/cert-manager/pkg/util/pki" + "github.com/cert-manager/cert-manager/test/unit/gen" +) + +var ( + rsaSKFuzz *rsa.PrivateKey + err error +) + +func init() { + rsaSKFuzz, err = pki.GenerateRSAPrivateKey(2048) + if err != nil { + panic(err) + } +} + +/* + FuzzVaultCRController is a fuzz test that can be run by way of + +go test -fuzz=FuzzVaultCRController. It tests for panics, OOMs +and stackoverflow-related bugs in the Vault reconciliation. +*/ +func FuzzVaultCRController(f *testing.F) { + f.Fuzz(func(t *testing.T, + secretTokenData, + customCsrPEM, + customRsaPEMCert []byte, + certDuration string, + addToken, + addCustomCsrPEM, + isCA bool, + baseCRCondition int) { + tm, err := time.ParseDuration(certDuration) + if err != nil { + return + } + + // Add possibly invalid csrPEM or generate valid + var csrPEM []byte + if addCustomCsrPEM { + csrPEM = customCsrPEM + } else { + csrPEM = generateCSR(t, rsaSKFuzz) + } + + fixedClockStart = time.Now() + metaFixedClockStart := metav1.NewTime(fixedClockStart) + baseIssuer := gen.Issuer("vault-issuer", + gen.SetIssuerVault(cmapi.VaultIssuer{ + Server: "https://example.vault.com", + }), + gen.AddIssuerCondition(cmapi.IssuerCondition{ + Type: cmapi.IssuerConditionReady, + Status: cmmeta.ConditionTrue, + }), + ) + + baseCRNotApproved := gen.CertificateRequest("test-cr", + gen.SetCertificateRequestIsCA(isCA), + gen.SetCertificateRequestCSR(csrPEM), + gen.SetCertificateRequestDuration(&metav1.Duration{Duration: tm}), + gen.SetCertificateRequestIssuer(cmmeta.IssuerReference{ + Name: baseIssuer.Name, + Group: certmanager.GroupName, + Kind: baseIssuer.Kind, + }), + ) + var condition cmapi.CertificateRequestConditionType + switch baseCRCondition % 4 { + case 0: + condition = cmapi.CertificateRequestConditionReady + case 1: + condition = cmapi.CertificateRequestConditionInvalidRequest + case 2: + condition = cmapi.CertificateRequestConditionApproved + case 3: + condition = cmapi.CertificateRequestConditionDenied + } + baseCR := gen.CertificateRequestFrom(baseCRNotApproved, + gen.SetCertificateRequestStatusCondition(cmapi.CertificateRequestCondition{ + Type: condition, + Status: cmmeta.ConditionTrue, + Reason: "cert-manager.io", + Message: "[test-message]", + LastTransitionTime: &metaFixedClockStart, + }), + ) + + kubeObjects := []runtime.Object{} + certManagerObjects := []runtime.Object{} + certManagerObjects = append(certManagerObjects, baseCR.DeepCopy()) + + // Add token if the fuzzer decides to. + if addToken { + tokenSecret := &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: gen.DefaultTestNamespace, + Name: "token-secret", + }, + Data: map[string][]byte{ + "my-token-key": secretTokenData, + }, + } + kubeObjects = append(kubeObjects, tokenSecret) + certManagerObjects = append(certManagerObjects, gen.IssuerFrom(baseIssuer, + gen.SetIssuerVault(cmapi.VaultIssuer{ + Auth: cmapi.VaultAuth{ + TokenSecretRef: &cmmeta.SecretKeySelector{ + Key: "my-token-key", + LocalObjectReference: cmmeta.LocalObjectReference{ + Name: "token-secret", + }, + }, + }, + }), + )) + } else { + certManagerObjects = append(certManagerObjects, baseIssuer.DeepCopy()) + } + + builder := &testpkg.Builder{ + T: t, + KubeObjects: kubeObjects, + CertManagerObjects: certManagerObjects, + } + builder.Init() + defer builder.Stop() + vault := NewVault(builder.Context).(*Vault) + + if !addCustomCsrPEM { + rsaPEMCert, err := generateSelfSignedCertFromCR(baseCR, rsaSKFuzz) + if err != nil { + return + } + + fakeVault := fakevault.New().WithSign(rsaPEMCert, rsaPEMCert, nil) + vault.vaultClientBuilder = func(_ context.Context, ns string, _ func(ns string) internalvault.CreateToken, sl internalinformers.SecretLister, + iss cmapi.GenericIssuer) (internalvault.Interface, error) { + return fakeVault.New(ns, sl, iss) + } + } + + controller := certificaterequests.New( + apiutil.IssuerVault, + func(*controllerpkg.Context) certificaterequests.Issuer { return vault }, + ) + if _, _, err := controller.Register(builder.Context); err != nil { + // Make it explicit if this fails + panic(err) + } + builder.Start() + _ = controller.Sync(t.Context(), baseCR) + }) +} diff --git a/pkg/controller/certificaterequests/vault/vault.go b/pkg/controller/certificaterequests/vault/vault.go index 758b4420b3a..419b1cdd7c5 100644 --- a/pkg/controller/certificaterequests/vault/vault.go +++ b/pkg/controller/certificaterequests/vault/vault.go @@ -20,8 +20,8 @@ import ( "context" k8sErrors "k8s.io/apimachinery/pkg/api/errors" - corelisters "k8s.io/client-go/listers/core/v1" + internalinformers "github.com/cert-manager/cert-manager/internal/informers" vaultinternal "github.com/cert-manager/cert-manager/internal/vault" apiutil "github.com/cert-manager/cert-manager/pkg/api/util" v1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" @@ -30,6 +30,7 @@ import ( crutil "github.com/cert-manager/cert-manager/pkg/controller/certificaterequests/util" "github.com/cert-manager/cert-manager/pkg/issuer" logf "github.com/cert-manager/cert-manager/pkg/logs" + cmerrors "github.com/cert-manager/cert-manager/pkg/util/errors" ) const ( @@ -41,7 +42,8 @@ const ( // pkg/controller/certificaterequests.Issuer interface. type Vault struct { issuerOptions controllerpkg.IssuerOptions - secretsLister corelisters.SecretLister + createTokenFn func(ns string) vaultinternal.CreateToken + secretsLister internalinformers.SecretLister reporter *crutil.Reporter vaultClientBuilder vaultinternal.ClientBuilder @@ -59,8 +61,11 @@ func init() { // NewVault returns a new Vault instance with the given controller context. func NewVault(ctx *controllerpkg.Context) certificaterequests.Issuer { return &Vault{ - issuerOptions: ctx.IssuerOptions, - secretsLister: ctx.KubeSharedInformerFactory.Core().V1().Secrets().Lister(), + issuerOptions: ctx.IssuerOptions, + createTokenFn: func(ns string) vaultinternal.CreateToken { + return ctx.Client.CoreV1().ServiceAccounts(ns).CreateToken + }, + secretsLister: ctx.KubeSharedInformerFactory.Secrets().Lister(), reporter: crutil.NewReporter(ctx.Clock, ctx.Recorder), vaultClientBuilder: vaultinternal.New, } @@ -74,7 +79,7 @@ func (v *Vault) Sign(ctx context.Context, cr *v1.CertificateRequest, issuerObj v resourceNamespace := v.issuerOptions.ResourceNamespace(issuerObj) - client, err := v.vaultClientBuilder(resourceNamespace, v.secretsLister, issuerObj) + client, err := v.vaultClientBuilder(ctx, resourceNamespace, v.createTokenFn, v.secretsLister, issuerObj) if k8sErrors.IsNotFound(err) { message := "Required secret resource not found" @@ -83,12 +88,16 @@ func (v *Vault) Sign(ctx context.Context, cr *v1.CertificateRequest, issuerObj v return nil, nil } - // TODO: distinguish between network errors and other which might warrant a failure. if err != nil { message := "Failed to initialise vault client for signing" v.reporter.Pending(cr, err, "VaultInitError", message) log.Error(err, message) - return nil, nil + + if cmerrors.IsInvalidData(err) { + return nil, nil // Don't retry, wait for the issuer to be updated + } + + return nil, err // Return error to requeue and retry } certDuration := apiutil.DefaultCertDuration(cr.Spec.Duration) diff --git a/pkg/controller/certificaterequests/vault/vault_test.go b/pkg/controller/certificaterequests/vault/vault_test.go index ee11a87aa9c..2738782e89f 100644 --- a/pkg/controller/certificaterequests/vault/vault_test.go +++ b/pkg/controller/certificaterequests/vault/vault_test.go @@ -22,8 +22,6 @@ import ( "crypto" "crypto/rand" "crypto/x509" - "crypto/x509/pkix" - "encoding/asn1" "encoding/pem" "errors" "fmt" @@ -33,17 +31,17 @@ import ( corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" - corelisters "k8s.io/client-go/listers/core/v1" coretesting "k8s.io/client-go/testing" fakeclock "k8s.io/utils/clock/testing" + internalinformers "github.com/cert-manager/cert-manager/internal/informers" internalvault "github.com/cert-manager/cert-manager/internal/vault" fakevault "github.com/cert-manager/cert-manager/internal/vault/fake" apiutil "github.com/cert-manager/cert-manager/pkg/api/util" "github.com/cert-manager/cert-manager/pkg/apis/certmanager" cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" - "github.com/cert-manager/cert-manager/pkg/controller" + controllerpkg "github.com/cert-manager/cert-manager/pkg/controller" "github.com/cert-manager/cert-manager/pkg/controller/certificaterequests" testpkg "github.com/cert-manager/cert-manager/pkg/controller/test" "github.com/cert-manager/cert-manager/pkg/util/pki" @@ -56,28 +54,18 @@ var ( ) func generateCSR(t *testing.T, secretKey crypto.Signer) []byte { - asn1Subj, _ := asn1.Marshal(pkix.Name{ - CommonName: "test", - }.ToRDNSequence()) - template := x509.CertificateRequest{ - RawSubject: asn1Subj, - SignatureAlgorithm: x509.SHA256WithRSA, - } - - csrBytes, err := x509.CreateCertificateRequest(rand.Reader, &template, secretKey) + csr, err := gen.CSRWithSigner(secretKey, + gen.SetCSRCommonName("test"), + ) if err != nil { - t.Error(err) - t.FailNow() + t.Fatal(err) } - csr := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE REQUEST", Bytes: csrBytes}) - return csr } -func generateSelfSignedCertFromCR(cr *cmapi.CertificateRequest, key crypto.Signer, - duration time.Duration) ([]byte, error) { - template, err := pki.GenerateTemplateFromCertificateRequest(cr) +func generateSelfSignedCertFromCR(cr *cmapi.CertificateRequest, key crypto.Signer) ([]byte, error) { + template, err := pki.CertificateTemplateFromCertificateRequest(cr) if err != nil { return nil, fmt.Errorf("error generating template: %v", err) } @@ -99,7 +87,9 @@ func generateSelfSignedCertFromCR(cr *cmapi.CertificateRequest, key crypto.Signe func TestSign(t *testing.T) { metaFixedClockStart := metav1.NewTime(fixedClockStart) baseIssuer := gen.Issuer("vault-issuer", - gen.SetIssuerVault(cmapi.VaultIssuer{}), + gen.SetIssuerVault(cmapi.VaultIssuer{ + Server: "https://example.vault.com", + }), gen.AddIssuerCondition(cmapi.IssuerCondition{ Type: cmapi.IssuerConditionReady, Status: cmmeta.ConditionTrue, @@ -118,7 +108,7 @@ func TestSign(t *testing.T) { gen.SetCertificateRequestIsCA(true), gen.SetCertificateRequestCSR(csrPEM), gen.SetCertificateRequestDuration(&metav1.Duration{Duration: time.Hour * 24 * 60}), - gen.SetCertificateRequestIssuer(cmmeta.ObjectReference{ + gen.SetCertificateRequestIssuer(cmmeta.IssuerReference{ Name: baseIssuer.Name, Group: certmanager.GroupName, Kind: baseIssuer.Kind, @@ -143,7 +133,7 @@ func TestSign(t *testing.T) { }), ) - rsaPEMCert, err := generateSelfSignedCertFromCR(baseCR, rsaSK, time.Hour*24*60) + rsaPEMCert, err := generateSelfSignedCertFromCR(baseCR, rsaSK) if err != nil { t.Error(err) t.FailNow() @@ -211,7 +201,7 @@ func TestSign(t *testing.T) { KubeObjects: []runtime.Object{}, CertManagerObjects: []runtime.Object{baseCR.DeepCopy(), baseIssuer.DeepCopy()}, ExpectedEvents: []string{ - "Normal VaultInitError Failed to initialise vault client for signing: error initializing Vault client: tokenSecretRef, appRoleSecretRef, or Kubernetes auth role not set", + "Normal VaultInitError Failed to initialise vault client for signing: error initializing Vault client: tokenSecretRef, appRoleSecretRef, clientCertificate, or Kubernetes auth role not set", }, ExpectedActions: []testpkg.Action{ testpkg.NewAction(coretesting.NewUpdateSubresourceAction( @@ -223,7 +213,7 @@ func TestSign(t *testing.T) { Type: cmapi.CertificateRequestConditionReady, Status: cmmeta.ConditionFalse, Reason: cmapi.CertificateRequestReasonPending, - Message: "Failed to initialise vault client for signing: error initializing Vault client: tokenSecretRef, appRoleSecretRef, or Kubernetes auth role not set", + Message: "Failed to initialise vault client for signing: error initializing Vault client: tokenSecretRef, appRoleSecretRef, clientCertificate, or Kubernetes auth role not set", LastTransitionTime: &metaFixedClockStart, }), ), @@ -245,6 +235,7 @@ func TestSign(t *testing.T) { }, }, }, + Server: "https://example.vault.com", })), }, ExpectedEvents: []string{ @@ -285,6 +276,7 @@ func TestSign(t *testing.T) { }, }, }, + Server: "https://example.vault.com", }), )}, ExpectedEvents: []string{ @@ -337,6 +329,47 @@ func TestSign(t *testing.T) { }, }, }, + "a client with a token secret referenced with token but temporary failed to authenticate should report pending and return error": { + certificateRequest: baseCR.DeepCopy(), + builder: &testpkg.Builder{ + KubeObjects: []runtime.Object{tokenSecret}, + CertManagerObjects: []runtime.Object{baseCR.DeepCopy(), gen.IssuerFrom(baseIssuer, + gen.SetIssuerVault(cmapi.VaultIssuer{ + Auth: cmapi.VaultAuth{ + TokenSecretRef: &cmmeta.SecretKeySelector{ + Key: "my-token-key", + LocalObjectReference: cmmeta.LocalObjectReference{ + Name: "token-secret", + }, + }, + }, + }), + )}, + ExpectedEvents: []string{ + "Normal VaultInitError Failed to initialise vault client for signing: failed to create vault client, temporary auth failure", + }, + ExpectedActions: []testpkg.Action{ + testpkg.NewAction(coretesting.NewUpdateSubresourceAction( + cmapi.SchemeGroupVersion.WithResource("certificaterequests"), + "status", + gen.DefaultTestNamespace, + gen.CertificateRequestFrom(baseCR, + gen.SetCertificateRequestStatusCondition(cmapi.CertificateRequestCondition{ + Type: cmapi.CertificateRequestConditionReady, + Status: cmmeta.ConditionFalse, + Reason: cmapi.CertificateRequestReasonPending, + Message: "Failed to initialise vault client for signing: failed to create vault client, temporary auth failure", + LastTransitionTime: &metaFixedClockStart, + }), + ), + )), + }, + }, + fakeVault: fakevault.New().WithNew(func(string, internalinformers.SecretLister, cmapi.GenericIssuer) (*fakevault.Vault, error) { + return nil, errors.New("failed to create vault client, temporary auth failure") + }), + expectedErr: true, + }, "a client with a token secret referenced with token but failed to sign should report fail": { certificateRequest: baseCR.DeepCopy(), builder: &testpkg.Builder{ @@ -529,7 +562,7 @@ func runTest(t *testing.T, test testT) { vault := NewVault(test.builder.Context).(*Vault) if test.fakeVault != nil { - vault.vaultClientBuilder = func(ns string, sl corelisters.SecretLister, + vault.vaultClientBuilder = func(_ context.Context, ns string, _ func(ns string) internalvault.CreateToken, sl internalinformers.SecretLister, iss cmapi.GenericIssuer) (internalvault.Interface, error) { return test.fakeVault.New(ns, sl, iss) } @@ -537,7 +570,7 @@ func runTest(t *testing.T, test testT) { controller := certificaterequests.New( apiutil.IssuerVault, - func(*controller.Context) certificaterequests.Issuer { return vault }, + func(*controllerpkg.Context) certificaterequests.Issuer { return vault }, ) if _, _, err := controller.Register(test.builder.Context); err != nil { @@ -546,7 +579,7 @@ func runTest(t *testing.T, test testT) { test.builder.Start() - err := controller.Sync(context.Background(), test.certificateRequest) + err := controller.Sync(t.Context(), test.certificateRequest) if err != nil && !test.expectedErr { t.Errorf("expected to not get an error, but got: %v", err) } diff --git a/pkg/controller/certificaterequests/venafi/fuzz_test.go b/pkg/controller/certificaterequests/venafi/fuzz_test.go new file mode 100644 index 00000000000..c3137654579 --- /dev/null +++ b/pkg/controller/certificaterequests/venafi/fuzz_test.go @@ -0,0 +1,165 @@ +/* +Copyright 2024 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. +*/ + +package venafi + +import ( + "crypto/rsa" + "testing" + "time" + + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + + apiutil "github.com/cert-manager/cert-manager/pkg/api/util" + "github.com/cert-manager/cert-manager/pkg/apis/certmanager" + cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" + cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" + controllerpkg "github.com/cert-manager/cert-manager/pkg/controller" + "github.com/cert-manager/cert-manager/pkg/controller/certificaterequests" + testpkg "github.com/cert-manager/cert-manager/pkg/controller/test" + "github.com/cert-manager/cert-manager/pkg/util/pki" + "github.com/cert-manager/cert-manager/test/unit/gen" +) + +var ( + rsaSKFuzz *rsa.PrivateKey + err error +) + +func init() { + rsaSKFuzz, err = pki.GenerateRSAPrivateKey(2048) + if err != nil { + panic(err) + } +} + +/* + FuzzVenafiCRController is a fuzz test that can be run by way of + +go test -fuzz=FuzzVenafiCRController. It tests for panics, OOMs +and stackoverflow-related bugs in the Venafi reconciliation. +*/ +func FuzzVenafiCRController(f *testing.F) { + f.Fuzz(func(t *testing.T, + secretTokenData, + customCsrPEM, + customRsaPEMCert []byte, + certDuration string, + addToken, + addCustomCsrPEM, + isCA bool, + baseCRCondition int) { + tm, err := time.ParseDuration(certDuration) + if err != nil { + return + } + + // Add possibly invalid csrPEM or generate valid + var csrPEM []byte + if addCustomCsrPEM { + csrPEM = customCsrPEM + } else { + csrPEM = generateCSR(t, rsaSKFuzz) + } + + fixedClockStart = time.Now() + metaFixedClockStart := metav1.NewTime(fixedClockStart) + + baseIssuer := gen.Issuer("fuzz-issuer", + gen.SetIssuerVenafi(cmapi.VenafiIssuer{}), + gen.AddIssuerCondition(cmapi.IssuerCondition{ + Type: cmapi.IssuerConditionReady, + Status: cmmeta.ConditionTrue, + }), + ) + + baseCRNotApproved := gen.CertificateRequest("test-cr", + gen.SetCertificateRequestIsCA(isCA), + gen.SetCertificateRequestCSR(csrPEM), + gen.SetCertificateRequestDuration(&metav1.Duration{Duration: tm}), + gen.SetCertificateRequestIssuer(cmmeta.IssuerReference{ + Name: baseIssuer.Name, + Group: certmanager.GroupName, + Kind: baseIssuer.Kind, + }), + ) + var condition cmapi.CertificateRequestConditionType + switch baseCRCondition % 4 { + case 0: + condition = cmapi.CertificateRequestConditionReady + case 1: + condition = cmapi.CertificateRequestConditionInvalidRequest + case 2: + condition = cmapi.CertificateRequestConditionApproved + case 3: + condition = cmapi.CertificateRequestConditionDenied + } + baseCR := gen.CertificateRequestFrom(baseCRNotApproved, + gen.SetCertificateRequestStatusCondition(cmapi.CertificateRequestCondition{ + Type: condition, + Status: cmmeta.ConditionTrue, + Reason: "cert-manager.io", + Message: "[test-message]", + LastTransitionTime: &metaFixedClockStart, + }), + ) + + kubeObjects := []runtime.Object{} + certManagerObjects := []runtime.Object{} + certManagerObjects = append(certManagerObjects, baseCR.DeepCopy()) + + // Add token if the fuzzer decides to. + if addToken { + tokenSecret := &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: gen.DefaultTestNamespace, + Name: "token-secret", + }, + Data: map[string][]byte{ + "my-token-key": secretTokenData, + }, + } + kubeObjects = append(kubeObjects, tokenSecret) + certManagerObjects = append(certManagerObjects, gen.IssuerFrom(baseIssuer, + gen.SetIssuerVenafi(cmapi.VenafiIssuer{}), + )) + + } else { + certManagerObjects = append(certManagerObjects, baseIssuer.DeepCopy()) + } + + builder := &testpkg.Builder{ + T: t, + KubeObjects: kubeObjects, + CertManagerObjects: certManagerObjects, + } + defer builder.Stop() + builder.InitWithRESTConfig() + v := NewVenafi(builder.Context).(*Venafi) + controller := certificaterequests.New( + apiutil.IssuerVenafi, + func(*controllerpkg.Context) certificaterequests.Issuer { return v }, + ) + if _, _, err := controller.Register(builder.Context); err != nil { + // Make it explicit if this fails + panic(err) + } + _ = controller.Sync(t.Context(), baseCR) + + }) +} diff --git a/pkg/controller/certificaterequests/venafi/venafi.go b/pkg/controller/certificaterequests/venafi/venafi.go index 6aaf4a4509b..77ee5334f49 100644 --- a/pkg/controller/certificaterequests/venafi/venafi.go +++ b/pkg/controller/certificaterequests/venafi/venafi.go @@ -21,12 +21,11 @@ import ( "encoding/json" "fmt" + "github.com/Venafi/vcert/v5/pkg/endpoint" k8sErrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - corelisters "k8s.io/client-go/listers/core/v1" - - "github.com/Venafi/vcert/v4/pkg/endpoint" + internalinformers "github.com/cert-manager/cert-manager/internal/informers" apiutil "github.com/cert-manager/cert-manager/pkg/api/util" cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" clientset "github.com/cert-manager/cert-manager/pkg/client/clientset/versioned" @@ -47,13 +46,16 @@ const ( type Venafi struct { issuerOptions controllerpkg.IssuerOptions - secretsLister corelisters.SecretLister + secretsLister internalinformers.SecretLister reporter *crutil.Reporter cmClient clientset.Interface clientBuilder venaficlient.VenafiClientBuilder metrics *metrics.Metrics + + // userAgent is the string used as the UserAgent when making HTTP calls. + userAgent string } func init() { @@ -68,11 +70,12 @@ func init() { func NewVenafi(ctx *controllerpkg.Context) certificaterequests.Issuer { return &Venafi{ issuerOptions: ctx.IssuerOptions, - secretsLister: ctx.KubeSharedInformerFactory.Core().V1().Secrets().Lister(), + secretsLister: ctx.KubeSharedInformerFactory.Secrets().Lister(), reporter: crutil.NewReporter(ctx.Clock, ctx.Recorder), clientBuilder: venaficlient.New, metrics: ctx.Metrics, cmClient: ctx.CMClient, + userAgent: ctx.RESTConfig.UserAgent, } } @@ -80,7 +83,7 @@ func (v *Venafi) Sign(ctx context.Context, cr *cmapi.CertificateRequest, issuerO log := logf.FromContext(ctx, "sign") log = logf.WithRelatedResource(log, issuerObj) - client, err := v.clientBuilder(v.issuerOptions.ResourceNamespace(issuerObj), v.secretsLister, issuerObj, v.metrics, log) + client, err := v.clientBuilder(v.issuerOptions.ResourceNamespace(issuerObj), v.secretsLister, issuerObj, v.metrics, log, v.userAgent) if k8sErrors.IsNotFound(err) { message := "Required secret resource not found" diff --git a/pkg/controller/certificaterequests/venafi/venafi_test.go b/pkg/controller/certificaterequests/venafi/venafi_test.go index dc6d446a93e..c779b971bf8 100644 --- a/pkg/controller/certificaterequests/venafi/venafi_test.go +++ b/pkg/controller/certificaterequests/venafi/venafi_test.go @@ -17,18 +17,14 @@ limitations under the License. package venafi import ( - "context" "crypto" - "crypto/rand" "crypto/x509" "crypto/x509/pkix" - "encoding/pem" "errors" - "math/big" "testing" "time" - "github.com/Venafi/vcert/v4/pkg/endpoint" + "github.com/Venafi/vcert/v5/pkg/endpoint" "github.com/go-logr/logr" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -37,11 +33,12 @@ import ( coretesting "k8s.io/client-go/testing" fakeclock "k8s.io/utils/clock/testing" + internalinformers "github.com/cert-manager/cert-manager/internal/informers" apiutil "github.com/cert-manager/cert-manager/pkg/api/util" "github.com/cert-manager/cert-manager/pkg/apis/certmanager" cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" - "github.com/cert-manager/cert-manager/pkg/controller" + controllerpkg "github.com/cert-manager/cert-manager/pkg/controller" "github.com/cert-manager/cert-manager/pkg/controller/certificaterequests" controllertest "github.com/cert-manager/cert-manager/pkg/controller/test" "github.com/cert-manager/cert-manager/pkg/issuer/venafi/client" @@ -58,25 +55,15 @@ var ( fixedClock = fakeclock.NewFakeClock(fixedClockStart) ) -func generateCSR(t *testing.T, secretKey crypto.Signer, alg x509.SignatureAlgorithm) []byte { - template := x509.CertificateRequest{ - Subject: pkix.Name{ - CommonName: "test-common-name", - }, - DNSNames: []string{ - "foo.example.com", "bar.example.com", - }, - SignatureAlgorithm: alg, - PublicKey: secretKey.Public(), - } - - csrBytes, err := x509.CreateCertificateRequest(rand.Reader, &template, secretKey) +func generateCSR(t *testing.T, secretKey crypto.Signer) []byte { + csr, err := gen.CSRWithSigner(secretKey, + gen.SetCSRCommonName("test-common-name"), + gen.SetCSRDNSNames("foo.example.com", "bar.example.com"), + ) if err != nil { t.Fatal(err) } - csr := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE REQUEST", Bytes: csrBytes}) - return csr } @@ -87,15 +74,8 @@ func TestSign(t *testing.T) { t.Fatal(err) } - serialNumber, err := rand.Int(rand.Reader, new(big.Int).Lsh(big.NewInt(1), 128)) - if err != nil { - t.Fatal(err) - } - rootTmpl := &x509.Certificate{ - Version: 2, BasicConstraintsValid: true, - SerialNumber: serialNumber, PublicKeyAlgorithm: x509.ECDSA, PublicKey: rootPK.Public(), IsCA: true, @@ -116,7 +96,7 @@ func TestSign(t *testing.T) { t.Fatal(err) } - csrPEM := generateCSR(t, testPK, x509.ECDSAWithSHA256) + csrPEM := generateCSR(t, testPK) tppSecret := &corev1.Secret{ ObjectMeta: metav1.ObjectMeta{ @@ -190,7 +170,7 @@ func TestSign(t *testing.T) { ) tppCR := gen.CertificateRequestFrom(baseCR, - gen.SetCertificateRequestIssuer(cmmeta.ObjectReference{ + gen.SetCertificateRequestIssuer(cmmeta.IssuerReference{ Group: certmanager.GroupName, Name: tppIssuer.Name, Kind: tppIssuer.Kind, @@ -204,7 +184,7 @@ func TestSign(t *testing.T) { tppCRWithInvalidCustomFieldType := gen.CertificateRequestFrom(tppCR, gen.SetCertificateRequestAnnotations(map[string]string{"venafi.cert-manager.io/custom-fields": `[{"name": "cert-manager-test", "value": "test ok", "type": "Bool"}]`})) cloudCR := gen.CertificateRequestFrom(baseCR, - gen.SetCertificateRequestIssuer(cmmeta.ObjectReference{ + gen.SetCertificateRequestIssuer(cmmeta.IssuerReference{ Group: certmanager.GroupName, Name: cloudIssuer.Name, Kind: cloudIssuer.Kind, @@ -221,7 +201,7 @@ func TestSign(t *testing.T) { }, } - template, err := pki.GenerateTemplateFromCertificateRequest(baseCR) + template, err := pki.CertificateTemplateFromCertificateRequest(baseCR) if err != nil { t.Fatal(err) } @@ -823,7 +803,7 @@ type testT struct { func runTest(t *testing.T, test testT) { test.builder.T = t - test.builder.Init() + test.builder.InitWithRESTConfig() defer test.builder.Stop() v := NewVenafi(test.builder.Context).(*Venafi) @@ -833,26 +813,28 @@ func runTest(t *testing.T, test testT) { } if test.fakeClient != nil { - v.clientBuilder = func(namespace string, secretsLister corelisters.SecretLister, - issuer cmapi.GenericIssuer, _ *metrics.Metrics, _ logr.Logger) (client.Interface, error) { + v.clientBuilder = func(namespace string, secretsLister internalinformers.SecretLister, + issuer cmapi.GenericIssuer, _ *metrics.Metrics, _ logr.Logger, _ string) (client.Interface, error) { return test.fakeClient, nil } } controller := certificaterequests.New( apiutil.IssuerVenafi, - func(*controller.Context) certificaterequests.Issuer { return v }, + func(*controllerpkg.Context) certificaterequests.Issuer { return v }, ) - controller.Register(test.builder.Context) + if _, _, err := controller.Register(test.builder.Context); err != nil { + t.Fatal(err) + } test.builder.Start() // Deep copy the certificate request to prevent pulling condition state across tests - err := controller.Sync(context.Background(), test.certificateRequest) + err := controller.Sync(t.Context(), test.certificateRequest) if err == nil && test.fakeClient != nil && test.fakeClient.RetrieveCertificateFn != nil && !test.skipSecondSignCall { // request state is ok! simulating a 2nd sync to fetch the cert metav1.SetMetaDataAnnotation(&test.certificateRequest.ObjectMeta, cmapi.VenafiPickupIDAnnotationKey, "test") - err = controller.Sync(context.Background(), test.certificateRequest) + err = controller.Sync(t.Context(), test.certificateRequest) } if err != nil && !test.expectedErr { diff --git a/pkg/controller/certificates/informers.go b/pkg/controller/certificates/informers.go index 27f6d9b85cb..ac56c110fe3 100644 --- a/pkg/controller/certificates/informers.go +++ b/pkg/controller/certificates/informers.go @@ -21,10 +21,10 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" "k8s.io/client-go/util/workqueue" cmlisters "github.com/cert-manager/cert-manager/pkg/client/listers/certmanager/v1" - controllerpkg "github.com/cert-manager/cert-manager/pkg/controller" logf "github.com/cert-manager/cert-manager/pkg/logs" "github.com/cert-manager/cert-manager/pkg/util/predicate" ) @@ -37,7 +37,7 @@ import ( // call when enqueuing Certificate resources. // If no predicate constructors are given, all Certificate resources will be // enqueued on every invocation. -func EnqueueCertificatesForResourceUsingPredicates(log logr.Logger, queue workqueue.Interface, lister cmlisters.CertificateLister, selector labels.Selector, predicateBuilders ...predicate.ExtractorFunc) func(obj interface{}) { +func EnqueueCertificatesForResourceUsingPredicates(log logr.Logger, queue workqueue.TypedInterface[types.NamespacedName], lister cmlisters.CertificateLister, selector labels.Selector, predicateBuilders ...predicate.ExtractorFunc) func(obj interface{}) { return func(obj interface{}) { s, ok := obj.(metav1.Object) if !ok { @@ -58,12 +58,10 @@ func EnqueueCertificatesForResourceUsingPredicates(log logr.Logger, queue workqu } for _, cert := range certs { - key, err := controllerpkg.KeyFunc(cert) - if err != nil { - log.Error(err, "Error determining 'key' for resource") - continue - } - queue.Add(key) + queue.Add(types.NamespacedName{ + Name: cert.Name, + Namespace: cert.Namespace, + }) } } } diff --git a/pkg/controller/certificates/issuing/fuzz_test.go b/pkg/controller/certificates/issuing/fuzz_test.go new file mode 100644 index 00000000000..0bb1c2952b6 --- /dev/null +++ b/pkg/controller/certificates/issuing/fuzz_test.go @@ -0,0 +1,149 @@ +/* +Copyright 2024 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. +*/ + +package issuing + +import ( + "testing" + "time" + + gfh "github.com/AdaLogics/go-fuzz-headers" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + + cmapiv1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" + cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" + testpkg "github.com/cert-manager/cert-manager/pkg/controller/test" + testcrypto "github.com/cert-manager/cert-manager/test/unit/crypto" + "github.com/cert-manager/cert-manager/test/unit/gen" +) + +var ( + fuzzBundle testcrypto.CryptoBundle + baseCert *cmapiv1.Certificate +) + +func init() { + nextPrivateKeySecretName := "next-private-key" + baseCert = gen.Certificate("test", + gen.SetCertificateIssuer(cmmeta.IssuerReference{Name: "ca-issuer", Kind: "Issuer", Group: "foo.io"}), + gen.SetCertificateGeneration(3), + gen.SetCertificateSecretName("output"), + gen.SetCertificateRenewBefore(&metav1.Duration{Duration: time.Hour * 36}), + gen.SetCertificateDNSNames("example.com"), + gen.SetCertificateRevision(1), + gen.SetCertificateNextPrivateKeySecretName(nextPrivateKeySecretName), + ) + fuzzBundle = testcrypto.MustCreateCryptoBundle(&testing.T{}, baseCert.DeepCopy(), fixedClock) +} + +// FuzzProcessItem tests the issuing controllers ProcessItem() method. +// It creates a random certificate, a random secret and a random +// issuing certificate and adds these to the builder. All of these objects +// might be invalid and as such the fuzzer overapproximates which can +// result in false positives. +// The fuzzer does not verify how Cert-Manager behaves. It tests for panics +// or unrecoverable issues such as stack overflows, excessive memory usage, +// deadlocks, inifinite loops and other similar issues. +func FuzzProcessItem(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte, + randomizeCert, + randomizeIssuingCert, + addIssuingCert bool) { + + fdp := gfh.NewConsumer(data) + + // Create the random certificate + var certificate *cmapiv1.Certificate + if randomizeCert { + certificate = &cmapiv1.Certificate{} + err := fdp.GenerateStruct(certificate) + if err != nil { + return + } + } else { + certificate = fuzzBundle.Certificate + certificate.Name = "test2" + } + certManagerObjects := make([]runtime.Object, 0) + certManagerObjects = append(certManagerObjects, certificate) + + // Create the random secret + secret := &corev1.Secret{} + err := fdp.GenerateStruct(secret) + if err != nil { + return + } + kubeObjects := make([]runtime.Object, 0) + kubeObjects = append(kubeObjects, secret) + + // Create the random issuing cert + // The fuzzer itself chooses whether to add this. + // As such, there may be invocations that do not + // include an issuing certificate. + // The fuzzer can randomize this entirely or use + // a template certificate. + if addIssuingCert { + var issuingCert *cmapiv1.Certificate + if randomizeIssuingCert { + issuingCert = &cmapiv1.Certificate{} + err := fdp.GenerateStruct(issuingCert) + if err != nil { + return + } + } else { + metaFixedClockStart := metav1.NewTime(fixedClockStart) + + issCert := gen.CertificateFrom(baseCert.DeepCopy(), + gen.SetCertificateStatusCondition(cmapiv1.CertificateCondition{ + Type: cmapiv1.CertificateConditionIssuing, + Status: cmmeta.ConditionTrue, + ObservedGeneration: 3, + LastTransitionTime: &metaFixedClockStart, + }), + ) + issuingCert = gen.CertificateFrom(issCert) + } + certManagerObjects = append(certManagerObjects, issuingCert) + } + + // Create the builder + builder := &testpkg.Builder{} + builder.CertManagerObjects = certManagerObjects + builder.KubeObjects = kubeObjects + fixedClock.SetTime(fixedClockStart) + builder.Clock = fixedClock + builder.T = t + builder.InitWithRESTConfig() + builder.Start() + defer builder.Stop() + + w := controllerWrapper{} + _, _, err = w.Register(builder.Context) + if err != nil { + panic(err) + } + w.controller.localTemporarySigner = testLocalTemporarySignerFn(fuzzBundle.LocalTemporaryCertificateBytes) + + // Invoke ProcessItem(). This is the method that this fuzzers tests. + _ = w.controller.ProcessItem(t.Context(), types.NamespacedName{ + Namespace: certificate.Namespace, + Name: certificate.Name, + }) + }) +} diff --git a/pkg/controller/certificates/issuing/internal/keystore.go b/pkg/controller/certificates/issuing/internal/keystore.go index 2c73ac1276b..1ad79c44bb1 100644 --- a/pkg/controller/certificates/issuing/internal/keystore.go +++ b/pkg/controller/certificates/issuing/internal/keystore.go @@ -24,36 +24,23 @@ package internal import ( "bytes" - "crypto/rand" "crypto/x509" + "fmt" "time" jks "github.com/pavlo-v-chernykh/keystore-go/v4" "software.sslmate.com/src/go-pkcs12" + cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" "github.com/cert-manager/cert-manager/pkg/util/pki" ) -const ( - // pkcs12SecretKey is the name of the data entry in the Secret resource - // used to store the p12 file. - pkcs12SecretKey = "keystore.p12" - // Data Entry Name in the Secret resource for PKCS12 containing Certificate Authority - pkcs12TruststoreKey = "truststore.p12" - - // jksSecretKey is the name of the data entry in the Secret resource - // used to store the jks file. - jksSecretKey = "keystore.jks" - // Data Entry Name in the Secret resource for JKS containing Certificate Authority - jksTruststoreKey = "truststore.jks" -) - // encodePKCS12Keystore will encode a PKCS12 keystore using the password provided. // The key, certificate and CA data must be provided in PKCS1 or PKCS8 PEM format. // If the certificate data contains multiple certificates, the first will be used // as the keystores 'certificate' and the remaining certificates will be prepended // to the list of CAs in the resulting keystore. -func encodePKCS12Keystore(password string, rawKey []byte, certPem []byte, caPem []byte) ([]byte, error) { +func encodePKCS12Keystore(profile cmapi.PKCS12Profile, password string, rawKey []byte, certPem []byte, caPem []byte) ([]byte, error) { key, err := pki.DecodePrivateKeyBytes(rawKey) if err != nil { return nil, err @@ -64,7 +51,7 @@ func encodePKCS12Keystore(password string, rawKey []byte, certPem []byte, caPem } var cas []*x509.Certificate if len(caPem) > 0 { - cas, err = pki.DecodeX509CertificateChainBytes(caPem) + cas, err = pki.DecodeX509CertificateSetBytes(caPem) if err != nil { return nil, err } @@ -74,20 +61,38 @@ func encodePKCS12Keystore(password string, rawKey []byte, certPem []byte, caPem if len(certs) > 1 { cas = append(certs[1:], cas...) } - return pkcs12.Encode(rand.Reader, key, certs[0], cas, password) + + switch profile { + case cmapi.Modern2023PKCS12Profile: + return pkcs12.Modern2023.Encode(key, certs[0], cas, password) + case cmapi.LegacyDESPKCS12Profile: + return pkcs12.LegacyDES.Encode(key, certs[0], cas, password) + case cmapi.LegacyRC2PKCS12Profile: + return pkcs12.LegacyRC2.Encode(key, certs[0], cas, password) + default: + return pkcs12.LegacyRC2.Encode(key, certs[0], cas, password) + } } -func encodePKCS12Truststore(password string, caPem []byte) ([]byte, error) { - ca, err := pki.DecodeX509CertificateBytes(caPem) +func encodePKCS12Truststore(profile cmapi.PKCS12Profile, password string, caPem []byte) ([]byte, error) { + cas, err := pki.DecodeX509CertificateSetBytes(caPem) if err != nil { return nil, err } - var cas = []*x509.Certificate{ca} - return pkcs12.EncodeTrustStore(rand.Reader, cas, password) + switch profile { + case cmapi.Modern2023PKCS12Profile: + return pkcs12.Modern2023.EncodeTrustStore(cas, password) + case cmapi.LegacyDESPKCS12Profile: + return pkcs12.LegacyDES.EncodeTrustStore(cas, password) + case cmapi.LegacyRC2PKCS12Profile: + return pkcs12.LegacyRC2.EncodeTrustStore(cas, password) + default: + return pkcs12.LegacyRC2.EncodeTrustStore(cas, password) + } } -func encodeJKSKeystore(password []byte, rawKey []byte, certPem []byte, caPem []byte) ([]byte, error) { +func encodeJKSKeystore(password []byte, keyAlias string, rawKey []byte, certPem []byte, caPem []byte) ([]byte, error) { // encode the private key to PKCS8 key, err := pki.DecodePrivateKeyBytes(rawKey) if err != nil { @@ -112,25 +117,19 @@ func encodeJKSKeystore(password []byte, rawKey []byte, certPem []byte, caPem []b } ks := jks.New() - ks.SetPrivateKeyEntry("certificate", jks.PrivateKeyEntry{ + if err = ks.SetPrivateKeyEntry(keyAlias, jks.PrivateKeyEntry{ CreationTime: time.Now(), PrivateKey: keyDER, CertificateChain: certs, - }, password) + }, password); err != nil { + return nil, err + } // add the CA certificate, if set if len(caPem) > 0 { - ca, err := pki.DecodeX509CertificateBytes(caPem) - if err != nil { + if err := addCAsToJKSStore(&ks, caPem); err != nil { return nil, err } - ks.SetTrustedCertificateEntry("ca", jks.TrustedCertificateEntry{ - CreationTime: time.Now(), - Certificate: jks.Certificate{ - Type: "X509", - Content: ca.Raw, - }}, - ) } buf := &bytes.Buffer{} @@ -141,23 +140,38 @@ func encodeJKSKeystore(password []byte, rawKey []byte, certPem []byte, caPem []b } func encodeJKSTruststore(password []byte, caPem []byte) ([]byte, error) { - ca, err := pki.DecodeX509CertificateBytes(caPem) - if err != nil { + ks := jks.New() + if err := addCAsToJKSStore(&ks, caPem); err != nil { return nil, err } - - ks := jks.New() - ks.SetTrustedCertificateEntry("ca", jks.TrustedCertificateEntry{ - CreationTime: time.Now(), - Certificate: jks.Certificate{ - Type: "X509", - Content: ca.Raw, - }}, - ) - buf := &bytes.Buffer{} if err := ks.Store(buf, password); err != nil { return nil, err } return buf.Bytes(), nil } + +func addCAsToJKSStore(ks *jks.KeyStore, caPem []byte) error { + cas, err := pki.DecodeX509CertificateSetBytes(caPem) + if err != nil { + return err + } + + creationTime := time.Now() + for i, ca := range cas { + alias := fmt.Sprintf("ca-%d", i) + if i == 0 { + alias = "ca" + } + if err = ks.SetTrustedCertificateEntry(alias, jks.TrustedCertificateEntry{ + CreationTime: creationTime, + Certificate: jks.Certificate{ + Type: "X509", + Content: ca.Raw, + }}, + ); err != nil { + return err + } + } + return nil +} diff --git a/pkg/controller/certificates/issuing/internal/keystore_test.go b/pkg/controller/certificates/issuing/internal/keystore_test.go index 740670e9cb2..0d8ae42c489 100644 --- a/pkg/controller/certificates/issuing/internal/keystore_test.go +++ b/pkg/controller/certificates/issuing/internal/keystore_test.go @@ -18,18 +18,17 @@ package internal import ( "bytes" - "context" "crypto" "crypto/x509" "fmt" "testing" - fuzz "github.com/google/gofuzz" jks "github.com/pavlo-v-chernykh/keystore-go/v4" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "golang.org/x/sync/errgroup" "golang.org/x/sync/semaphore" + "sigs.k8s.io/randfill" "software.sslmate.com/src/go-pkcs12" cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" @@ -48,15 +47,13 @@ func mustGeneratePrivateKey(t *testing.T, encoding cmapi.PrivateKeyEncoding) []b return pkBytes } -func mustSelfSignCertificate(t *testing.T, pkBytes []byte) []byte { - if pkBytes == nil { - pkBytes = mustGeneratePrivateKey(t, cmapi.PKCS8) - } +func mustSelfSignCertificate(t *testing.T) []byte { + pkBytes := mustGeneratePrivateKey(t, cmapi.PKCS8) pk, err := pki.DecodePrivateKeyBytes(pkBytes) if err != nil { t.Fatal(err) } - x509Crt, err := pki.GenerateTemplate(&cmapi.Certificate{ + x509Crt, err := pki.CertificateTemplateFromCertificate(&cmapi.Certificate{ Spec: cmapi.CertificateSpec{ DNSNames: []string{"example.com"}, }, @@ -71,6 +68,14 @@ func mustSelfSignCertificate(t *testing.T, pkBytes []byte) []byte { return certBytes } +func mustSelfSignCertificates(t *testing.T, count int) []byte { + var buf bytes.Buffer + for range count { + buf.Write(mustSelfSignCertificate(t)) + } + return buf.Bytes() +} + type keyAndCert struct { key crypto.Signer keyPEM []byte @@ -84,7 +89,7 @@ func mustCert(t *testing.T, commonName string, isCA bool) *keyAndCert { keyPEM, err := pki.EncodePrivateKey(key, cmapi.PKCS8) require.NoError(t, err) - cert, err := pki.GenerateTemplate(&cmapi.Certificate{ + cert, err := pki.CertificateTemplateFromCertificate(&cmapi.Certificate{ Spec: cmapi.CertificateSpec{ CommonName: commonName, IsCA: isCA, @@ -149,13 +154,15 @@ func mustLeafWithChain(t *testing.T) leafWithChain { func TestEncodeJKSKeystore(t *testing.T) { tests := map[string]struct { password string + alias string rawKey, certPEM, caPEM []byte verify func(t *testing.T, out []byte, err error) }{ "encode a JKS bundle for a PKCS1 key and certificate only": { password: "password", + alias: "alias", rawKey: mustGeneratePrivateKey(t, cmapi.PKCS1), - certPEM: mustSelfSignCertificate(t, nil), + certPEM: mustSelfSignCertificate(t), verify: func(t *testing.T, out []byte, err error) { if err != nil { t.Errorf("expected no error but got: %v", err) @@ -169,7 +176,7 @@ func TestEncodeJKSKeystore(t *testing.T) { return } - if !ks.IsPrivateKeyEntry("certificate") { + if !ks.IsPrivateKeyEntry("alias") { t.Errorf("no certificate data found in keystore") } @@ -180,8 +187,9 @@ func TestEncodeJKSKeystore(t *testing.T) { }, "encode a JKS bundle for a PKCS8 key and certificate only": { password: "password", + alias: "alias", rawKey: mustGeneratePrivateKey(t, cmapi.PKCS8), - certPEM: mustSelfSignCertificate(t, nil), + certPEM: mustSelfSignCertificate(t), verify: func(t *testing.T, out []byte, err error) { if err != nil { t.Errorf("expected no error but got: %v", err) @@ -193,7 +201,7 @@ func TestEncodeJKSKeystore(t *testing.T) { t.Errorf("error decoding keystore: %v", err) return } - if !ks.IsPrivateKeyEntry("certificate") { + if !ks.IsPrivateKeyEntry("alias") { t.Errorf("no certificate data found in keystore") } @@ -204,9 +212,10 @@ func TestEncodeJKSKeystore(t *testing.T) { }, "encode a JKS bundle for a key, certificate and ca": { password: "password", + alias: "alias", rawKey: mustGeneratePrivateKey(t, cmapi.PKCS8), - certPEM: mustSelfSignCertificate(t, nil), - caPEM: mustSelfSignCertificate(t, nil), + certPEM: mustSelfSignCertificate(t), + caPEM: mustSelfSignCertificate(t), verify: func(t *testing.T, out []byte, err error) { if err != nil { t.Errorf("expected no error but got: %v", err) @@ -218,7 +227,7 @@ func TestEncodeJKSKeystore(t *testing.T) { t.Errorf("error decoding keystore: %v", err) return } - if !ks.IsPrivateKeyEntry("certificate") { + if !ks.IsPrivateKeyEntry("alias") { t.Errorf("no certificate data found in keystore") } if !ks.IsTrustedCertificateEntry("ca") { @@ -226,10 +235,109 @@ func TestEncodeJKSKeystore(t *testing.T) { } }, }, + "encode a JKS bundle for a key, certificate and multiple cas": { + password: "password", + alias: "alias", + rawKey: mustGeneratePrivateKey(t, cmapi.PKCS8), + certPEM: mustSelfSignCertificate(t), + caPEM: mustSelfSignCertificates(t, 3), + verify: func(t *testing.T, out []byte, err error) { + if err != nil { + t.Errorf("expected no error but got: %v", err) + } + buf := bytes.NewBuffer(out) + ks := jks.New() + err = ks.Load(buf, []byte("password")) + if err != nil { + t.Errorf("error decoding keystore: %v", err) + return + } + if !ks.IsPrivateKeyEntry("alias") { + t.Errorf("no certificate data found in keystore") + } + if !ks.IsTrustedCertificateEntry("ca") { + t.Errorf("no ca data found in truststore") + } + if !ks.IsTrustedCertificateEntry("ca-1") { + t.Errorf("no ca data found in truststore") + } + if !ks.IsTrustedCertificateEntry("ca-2") { + t.Errorf("no ca data found in truststore") + } + if len(ks.Aliases()) != 4 { + t.Errorf("expected 4 aliases in keystore, got %d", len(ks.Aliases())) + } + }, + }, } for name, test := range tests { t.Run(name, func(t *testing.T) { - out, err := encodeJKSKeystore([]byte(test.password), test.rawKey, test.certPEM, test.caPEM) + out, err := encodeJKSKeystore([]byte(test.password), test.alias, test.rawKey, test.certPEM, test.caPEM) + test.verify(t, out, err) + }) + } +} + +func TestEncodeJKSTruststore(t *testing.T) { + tests := map[string]struct { + password string + caCount int + verify func(t *testing.T, out []byte, err error) + }{ + "encode a JKS truststore for a single ca": { + password: "password", + caCount: 1, + verify: func(t *testing.T, out []byte, err error) { + if err != nil { + t.Errorf("expected no error but got: %v", err) + } + buf := bytes.NewBuffer(out) + ks := jks.New() + err = ks.Load(buf, []byte("password")) + if err != nil { + t.Errorf("error decoding keystore: %v", err) + return + } + if !ks.IsTrustedCertificateEntry("ca") { + t.Errorf("no ca data found in truststore") + } + if len(ks.Aliases()) != 1 { + t.Errorf("expected 1 alias in keystore, got %d", len(ks.Aliases())) + } + }, + }, + "encode a JKS truststore for multiple cas": { + password: "password", + caCount: 3, + verify: func(t *testing.T, out []byte, err error) { + if err != nil { + t.Errorf("expected no error but got: %v", err) + } + buf := bytes.NewBuffer(out) + ks := jks.New() + err = ks.Load(buf, []byte("password")) + if err != nil { + t.Errorf("error decoding keystore: %v", err) + return + } + if !ks.IsTrustedCertificateEntry("ca") { + t.Errorf("no ca data found in truststore") + } + if !ks.IsTrustedCertificateEntry("ca-1") { + t.Errorf("no ca data found in truststore") + } + if !ks.IsTrustedCertificateEntry("ca-2") { + t.Errorf("no ca data found in truststore") + } + if len(ks.Aliases()) != 3 { + t.Errorf("expected 3 aliases in keystore, got %d", len(ks.Aliases())) + } + }, + }, + } + for name, test := range tests { + t.Run(name, func(t *testing.T) { + out, err := encodeJKSTruststore([]byte(test.password), mustSelfSignCertificates(t, test.caCount)) test.verify(t, out, err) }) } @@ -245,7 +353,7 @@ func TestEncodePKCS12Keystore(t *testing.T) { "encode a JKS bundle for a PKCS1 key and certificate only": { password: "password", rawKey: mustGeneratePrivateKey(t, cmapi.PKCS1), - certPEM: mustSelfSignCertificate(t, nil), + certPEM: mustSelfSignCertificate(t), verify: func(t *testing.T, out []byte, err error) { if err != nil { t.Errorf("expected no error but got: %v", err) @@ -266,7 +374,7 @@ func TestEncodePKCS12Keystore(t *testing.T) { "encode a JKS bundle for a PKCS8 key and certificate only": { password: "password", rawKey: mustGeneratePrivateKey(t, cmapi.PKCS8), - certPEM: mustSelfSignCertificate(t, nil), + certPEM: mustSelfSignCertificate(t), verify: func(t *testing.T, out []byte, err error) { if err != nil { t.Errorf("expected no error but got: %v", err) @@ -287,8 +395,8 @@ func TestEncodePKCS12Keystore(t *testing.T) { "encode a JKS bundle for a key, certificate and ca": { password: "password", rawKey: mustGeneratePrivateKey(t, cmapi.PKCS8), - certPEM: mustSelfSignCertificate(t, nil), - caPEM: mustSelfSignCertificate(t, nil), + certPEM: mustSelfSignCertificate(t), + caPEM: mustSelfSignCertificate(t), verify: func(t *testing.T, out []byte, err error) { if err != nil { t.Errorf("expected no error but got: %v", err) @@ -312,8 +420,10 @@ func TestEncodePKCS12Keystore(t *testing.T) { } for name, test := range tests { t.Run(name, func(t *testing.T) { - out, err := encodePKCS12Keystore(test.password, test.rawKey, test.certPEM, test.caPEM) - test.verify(t, out, err) + for _, profile := range []cmapi.PKCS12Profile{"", cmapi.LegacyRC2PKCS12Profile, cmapi.LegacyDESPKCS12Profile, cmapi.Modern2023PKCS12Profile} { + out, err := encodePKCS12Keystore(profile, test.password, test.rawKey, test.certPEM, test.caPEM) + test.verify(t, out, err) + } }) } t.Run("encodePKCS12Keystore encodes non-leaf certificates to the CA certificate chain, even when the supplied CA chain is empty", func(t *testing.T) { @@ -321,36 +431,40 @@ func TestEncodePKCS12Keystore(t *testing.T) { var emptyCAChain []byte = nil chain := mustLeafWithChain(t) - out, err := encodePKCS12Keystore(password, chain.leaf.keyPEM, chain.all.certsToPEM(), emptyCAChain) - require.NoError(t, err) - - pkOut, certOut, caChain, err := pkcs12.DecodeChain(out, password) - require.NoError(t, err) - assert.NotNil(t, pkOut) - assert.Equal(t, chain.leaf.cert.Signature, certOut.Signature, "leaf certificate signature does not match") - if assert.Len(t, caChain, 2, "caChain should contain 2 items: intermediate certificate and top-level certificate") { - assert.Equal(t, chain.cas[0].cert.Signature, caChain[0].Signature, "intermediate certificate signature does not match") - assert.Equal(t, chain.cas[1].cert.Signature, caChain[1].Signature, "top-level certificate signature does not match") + for _, profile := range []cmapi.PKCS12Profile{"", cmapi.LegacyRC2PKCS12Profile, cmapi.LegacyDESPKCS12Profile, cmapi.Modern2023PKCS12Profile} { + out, err := encodePKCS12Keystore(profile, password, chain.leaf.keyPEM, chain.all.certsToPEM(), emptyCAChain) + require.NoError(t, err) + + pkOut, certOut, caChain, err := pkcs12.DecodeChain(out, password) + require.NoError(t, err) + assert.NotNil(t, pkOut) + assert.Equal(t, chain.leaf.cert.Signature, certOut.Signature, "leaf certificate signature does not match") + if assert.Len(t, caChain, 2, "caChain should contain 2 items: intermediate certificate and top-level certificate") { + assert.Equal(t, chain.cas[0].cert.Signature, caChain[0].Signature, "intermediate certificate signature does not match") + assert.Equal(t, chain.cas[1].cert.Signature, caChain[1].Signature, "top-level certificate signature does not match") + } } }) t.Run("encodePKCS12Keystore *prepends* non-leaf certificates to the supplied CA certificate chain", func(t *testing.T) { const password = "password" - var caChainInPEM []byte = mustSelfSignCertificate(t, nil) + caChainInPEM := mustSelfSignCertificate(t) caChainIn, err := pki.DecodeX509CertificateChainBytes(caChainInPEM) require.NoError(t, err) chain := mustLeafWithChain(t) - out, err := encodePKCS12Keystore(password, chain.leaf.keyPEM, chain.all.certsToPEM(), caChainInPEM) - require.NoError(t, err) - - pkOut, certOut, caChainOut, err := pkcs12.DecodeChain(out, password) - require.NoError(t, err) - assert.NotNil(t, pkOut) - assert.Equal(t, chain.leaf.cert.Signature, certOut.Signature, "leaf certificate signature does not match") - if assert.Len(t, caChainOut, 3, "caChain should contain 3 items: intermediate certificate and top-level certificate and supplied CA") { - assert.Equal(t, chain.cas[0].cert.Signature, caChainOut[0].Signature, "intermediate certificate signature does not match") - assert.Equal(t, chain.cas[1].cert.Signature, caChainOut[1].Signature, "top-level certificate signature does not match") - assert.Equal(t, caChainIn, caChainOut[2:], "supplied certificate chain is not at the end of the chain") + for _, profile := range []cmapi.PKCS12Profile{"", cmapi.LegacyRC2PKCS12Profile, cmapi.LegacyDESPKCS12Profile, cmapi.Modern2023PKCS12Profile} { + out, err := encodePKCS12Keystore(profile, password, chain.leaf.keyPEM, chain.all.certsToPEM(), caChainInPEM) + require.NoError(t, err) + + pkOut, certOut, caChainOut, err := pkcs12.DecodeChain(out, password) + require.NoError(t, err) + assert.NotNil(t, pkOut) + assert.Equal(t, chain.leaf.cert.Signature, certOut.Signature, "leaf certificate signature does not match") + if assert.Len(t, caChainOut, 3, "caChain should contain 3 items: intermediate certificate and top-level certificate and supplied CA") { + assert.Equal(t, chain.cas[0].cert.Signature, caChainOut[0].Signature, "intermediate certificate signature does not match") + assert.Equal(t, chain.cas[1].cert.Signature, caChainOut[1].Signature, "top-level certificate signature does not match") + assert.Equal(t, caChainIn, caChainOut[2:], "supplied certificate chain is not at the end of the chain") + } } }) } @@ -364,7 +478,7 @@ func TestEncodePKCS12Truststore(t *testing.T) { }{ "encode a PKCS12 bundle for a CA": { password: "password", - caPEM: mustSelfSignCertificate(t, nil), + caPEM: mustSelfSignCertificates(t, 1), verify: func(t *testing.T, caPEM []byte, out []byte, err error) { if err != nil { t.Errorf("expected no error but got: %v", err) @@ -384,36 +498,58 @@ func TestEncodePKCS12Truststore(t *testing.T) { } }, }, + "encode a PKCS12 bundle for multiple CAs": { + password: "password", + caPEM: mustSelfSignCertificates(t, 3), + verify: func(t *testing.T, caPEM []byte, out []byte, err error) { + if err != nil { + t.Errorf("expected no error but got: %v", err) + } + certs, err := pkcs12.DecodeTrustStore(out, "password") + if err != nil { + t.Errorf("error decoding truststore: %v", err) + return + } + if certs == nil { + t.Errorf("no certificates found in truststore") + } + if len(certs) != 3 { + t.Errorf("Trusted CA certificates should include 3 entries, got %d", len(certs)) + } + }, + }, } for name, test := range tests { t.Run(name, func(t *testing.T) { - out, err := encodePKCS12Truststore(test.password, test.caPEM) - test.verify(t, test.caPEM, out, err) + for _, profile := range []cmapi.PKCS12Profile{"", cmapi.LegacyRC2PKCS12Profile, cmapi.LegacyDESPKCS12Profile, cmapi.Modern2023PKCS12Profile} { + out, err := encodePKCS12Truststore(profile, test.password, test.caPEM) + test.verify(t, test.caPEM, out, err) + } }) } } func TestManyPasswordLengths(t *testing.T) { rawKey := mustGeneratePrivateKey(t, cmapi.PKCS8) - certPEM := mustSelfSignCertificate(t, nil) - caPEM := mustSelfSignCertificate(t, nil) + certPEM := mustSelfSignCertificate(t) + caPEM := mustSelfSignCertificate(t) const testN = 10000 // We will test random password lengths between 0 and 128 character lengths - f := fuzz.New().NilChance(0).NumElements(0, 128) + f := randfill.New().NilChance(0).NumElements(0, 128) // Pre-create password test cases. This cannot be done during the test itself // since the fuzzer cannot be used concurrently. var passwords [testN]string - for testi := 0; testi < testN; testi++ { + for testi := range testN { // fill the password with random characters - f.Fuzz(&passwords[testi]) + f.Fill(&passwords[testi]) } // Run these tests in parallel s := semaphore.NewWeighted(32) - g, ctx := errgroup.WithContext(context.Background()) - for tests := 0; tests < testN; tests++ { + g, ctx := errgroup.WithContext(t.Context()) + for tests := range testN { testi := tests if ctx.Err() != nil { t.Errorf("internal error while testing JKS Keystore password lengths: %s", ctx.Err()) @@ -425,7 +561,7 @@ func TestManyPasswordLengths(t *testing.T) { } g.Go(func() error { defer s.Release(1) - keystore, err := encodeJKSKeystore([]byte(passwords[testi]), rawKey, certPEM, caPEM) + keystore, err := encodeJKSKeystore([]byte(passwords[testi]), "alias", rawKey, certPEM, caPEM) if err != nil { t.Errorf("couldn't encode JKS Keystore with password %s (length %d): %s", passwords[testi], len(passwords[testi]), err.Error()) return err @@ -438,7 +574,7 @@ func TestManyPasswordLengths(t *testing.T) { t.Errorf("error decoding keystore with password %s (length %d): %v", passwords[testi], len(passwords[testi]), err) return err } - if !ks.IsPrivateKeyEntry("certificate") { + if !ks.IsPrivateKeyEntry("alias") { t.Errorf("no certificate data found in keystore") } if !ks.IsTrustedCertificateEntry("ca") { diff --git a/pkg/controller/certificates/issuing/internal/secret.go b/pkg/controller/certificates/issuing/internal/secret.go index c145dcb072d..341eef73039 100644 --- a/pkg/controller/certificates/issuing/internal/secret.go +++ b/pkg/controller/certificates/issuing/internal/secret.go @@ -20,6 +20,7 @@ import ( "context" "crypto/x509" "fmt" + "maps" corev1 "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" @@ -27,17 +28,18 @@ import ( applycorev1 "k8s.io/client-go/applyconfigurations/core/v1" applymetav1 "k8s.io/client-go/applyconfigurations/meta/v1" coreclient "k8s.io/client-go/kubernetes/typed/core/v1" - corelisters "k8s.io/client-go/listers/core/v1" "github.com/cert-manager/cert-manager/internal/controller/certificates" - "github.com/cert-manager/cert-manager/internal/controller/feature" + internalinformers "github.com/cert-manager/cert-manager/internal/informers" cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" logf "github.com/cert-manager/cert-manager/pkg/logs" - utilfeature "github.com/cert-manager/cert-manager/pkg/util/feature" utilpki "github.com/cert-manager/cert-manager/pkg/util/pki" ) +// DefaultPassword is the string "changeit", a commonly-used password for keystore files. +const DefaultKeystorePassword = "changeit" + var ( certificateGvk = cmapi.SchemeGroupVersion.WithKind("Certificate") ) @@ -45,7 +47,7 @@ var ( // SecretsManager creates and updates secrets with certificate and key data. type SecretsManager struct { secretClient coreclient.SecretsGetter - secretLister corelisters.SecretLister + secretLister internalinformers.SecretLister // fieldManager is the manager name used for the Apply operations on Secrets. fieldManager string @@ -59,7 +61,9 @@ type SecretsManager struct { // SecretData is a structure wrapping private key, Certificate and CA data type SecretData struct { - PrivateKey, Certificate, CA []byte + PrivateKey, Certificate, CA []byte + CertificateName string + IssuerName, IssuerKind, IssuerGroup string } // NewSecretsManager returns a new SecretsManager. Setting @@ -67,7 +71,7 @@ type SecretData struct { // when the corresponding Certificate is deleted. func NewSecretsManager( secretClient coreclient.SecretsGetter, - secretLister corelisters.SecretLister, + secretLister internalinformers.SecretLister, fieldManager string, enableSecretOwnerReferences bool, ) *SecretsManager { @@ -84,7 +88,7 @@ func NewSecretsManager( // If the Secret resource does not exist, it will be created on Apply. // UpdateData will also update deprecated annotations if they exist. func (s *SecretsManager) UpdateData(ctx context.Context, crt *cmapi.Certificate, data SecretData) error { - secret, err := s.getCertificateSecret(ctx, crt) + secret, err := s.getCertificateSecret(crt) if err != nil { return err } @@ -137,11 +141,9 @@ func (s *SecretsManager) setValues(crt *cmapi.Certificate, secret *corev1.Secret return fmt.Errorf("failed to add keystores to Secret: %w", err) } - // Add additional output formats if feature enabled. - if utilfeature.DefaultFeatureGate.Enabled(feature.AdditionalCertificateOutputFormats) { - if err := setAdditionalOutputFormats(crt, secret, data); err != nil { - return fmt.Errorf("failed to add additional output formats to Secret: %w", err) - } + // Add additional output formats if enabled. + if err := setAdditionalOutputFormats(crt, secret, data); err != nil { + return fmt.Errorf("failed to add additional output formats to Secret: %w", err) } secret.Data[corev1.TLSPrivateKeyKey] = data.PrivateKey @@ -150,36 +152,56 @@ func (s *SecretsManager) setValues(crt *cmapi.Certificate, secret *corev1.Secret secret.Data[cmmeta.TLSCAKey] = data.CA } + if secret.Annotations == nil { + secret.Annotations = make(map[string]string) + } + + if secret.Labels == nil { + secret.Labels = make(map[string]string) + } + + if crt.Spec.SecretTemplate != nil { + maps.Copy(secret.Labels, crt.Spec.SecretTemplate.Labels) + maps.Copy(secret.Annotations, crt.Spec.SecretTemplate.Annotations) + } + var certificate *x509.Certificate if len(data.Certificate) > 0 { var err error certificate, err = utilpki.DecodeX509CertificateBytes(data.Certificate) - // TODO: handle InvalidData here? + // TODO: handle InvalidData here? Maybe we should still patch the secret + // when we detect that the certificate bytes are invalid. if err != nil { return err } } - secret.Annotations = certificates.AnnotationsForCertificateSecret(crt, certificate) - if secret.Labels == nil { - secret.Labels = make(map[string]string) + certificateDetailsAnnotations, err := certificates.AnnotationsForCertificate(certificate) + if err != nil { + return err } + maps.Copy(secret.Annotations, certificateDetailsAnnotations) - if crt.Spec.SecretTemplate != nil { - for k, v := range crt.Spec.SecretTemplate.Labels { - secret.Labels[k] = v - } - for k, v := range crt.Spec.SecretTemplate.Annotations { - secret.Annotations[k] = v - } + // Add the certificate name and issuer details to the secret annotations. + // If the annotations are not set/ empty, we do not use them to determine + // if the secret needs to be updated. + if data.CertificateName != "" { + secret.Annotations[cmapi.CertificateNameKey] = data.CertificateName } + if data.IssuerName != "" || data.IssuerKind != "" || data.IssuerGroup != "" { + secret.Annotations[cmapi.IssuerNameAnnotationKey] = data.IssuerName + secret.Annotations[cmapi.IssuerKindAnnotationKey] = data.IssuerKind + secret.Annotations[cmapi.IssuerGroupAnnotationKey] = data.IssuerGroup + } + + secret.Labels[cmapi.PartOfCertManagerControllerLabelKey] = "true" return nil } // getCertificateSecret will return a secret which is ready for fields to be // applied. Only the Secret Type will be persisted from the original Secret. -func (s *SecretsManager) getCertificateSecret(ctx context.Context, crt *cmapi.Certificate) (*corev1.Secret, error) { +func (s *SecretsManager) getCertificateSecret(crt *cmapi.Certificate) (*corev1.Secret, error) { // Get existing secret if it exists. existingSecret, err := s.secretLister.Secrets(crt.Namespace).Get(crt.Spec.SecretName) @@ -219,51 +241,102 @@ func (s *SecretsManager) getCertificateSecret(ctx context.Context, crt *cmapi.Ce // setKeystores will set extra Secret Data keys according to any Keystores // which have been configured. func (s *SecretsManager) setKeystores(crt *cmapi.Certificate, secret *corev1.Secret, data SecretData) error { - // Handle the experimental PKCS12 support - if crt.Spec.Keystores != nil && crt.Spec.Keystores.PKCS12 != nil && crt.Spec.Keystores.PKCS12.Create { + if crt.Spec.Keystores == nil { + return nil + } + + // Handle PKCS#12 keystores + if crt.Spec.Keystores.PKCS12 != nil && crt.Spec.Keystores.PKCS12.Create { + var pw []byte + ref := crt.Spec.Keystores.PKCS12.PasswordSecretRef - pwSecret, err := s.secretLister.Secrets(crt.Namespace).Get(ref.Name) - if err != nil { - return fmt.Errorf("fetching PKCS12 keystore password from Secret: %v", err) - } - if pwSecret.Data == nil || len(pwSecret.Data[ref.Key]) == 0 { - return fmt.Errorf("PKCS12 keystore password Secret contains no data for key %q", ref.Key) + + switch { + case ref.Name != "": + pwSecret, err := s.secretLister.Secrets(crt.Namespace).Get(ref.Name) + if err != nil { + return fmt.Errorf("fetching PKCS12 keystore password from Secret: %v", err) + } + + if pwSecret.Data == nil || len(pwSecret.Data[ref.Key]) == 0 { + return fmt.Errorf("PKCS12 keystore password Secret contains no data for key %q", ref.Key) + } + + pw = pwSecret.Data[ref.Key] + + case crt.Spec.Keystores.PKCS12.Password != nil: + if len(*crt.Spec.Keystores.PKCS12.Password) == 0 { + return fmt.Errorf("PKCS12 literal password cannot be empty") + } + + pw = []byte(*crt.Spec.Keystores.PKCS12.Password) + + default: + return fmt.Errorf("either passwordSecretRef or password must be set for PKCS#12 keystore") } - pw := pwSecret.Data[ref.Key] - keystoreData, err := encodePKCS12Keystore(string(pw), data.PrivateKey, data.Certificate, data.CA) + + profile := crt.Spec.Keystores.PKCS12.Profile + + keystoreData, err := encodePKCS12Keystore(profile, string(pw), data.PrivateKey, data.Certificate, data.CA) if err != nil { return fmt.Errorf("error encoding PKCS12 bundle: %w", err) } + // always overwrite the keystore entry for now - secret.Data[pkcs12SecretKey] = keystoreData + secret.Data[cmapi.PKCS12SecretKey] = keystoreData if len(data.CA) > 0 { - truststoreData, err := encodePKCS12Truststore(string(pw), data.CA) + truststoreData, err := encodePKCS12Truststore(profile, string(pw), data.CA) if err != nil { return fmt.Errorf("error encoding PKCS12 trust store bundle: %w", err) } // always overwrite the truststore entry - secret.Data[pkcs12TruststoreKey] = truststoreData + secret.Data[cmapi.PKCS12TruststoreKey] = truststoreData } } - // Handle the experimental JKS support - if crt.Spec.Keystores != nil && crt.Spec.Keystores.JKS != nil && crt.Spec.Keystores.JKS.Create { + // Handle JKS keystores + if crt.Spec.Keystores.JKS != nil && crt.Spec.Keystores.JKS.Create { + var pw []byte + ref := crt.Spec.Keystores.JKS.PasswordSecretRef - pwSecret, err := s.secretLister.Secrets(crt.Namespace).Get(ref.Name) - if err != nil { - return fmt.Errorf("fetching JKS keystore password from Secret: %v", err) + + switch { + case ref.Name != "": + pwSecret, err := s.secretLister.Secrets(crt.Namespace).Get(ref.Name) + if err != nil { + return fmt.Errorf("fetching JKS keystore password from Secret: %v", err) + } + + if pwSecret.Data == nil || len(pwSecret.Data[ref.Key]) == 0 { + return fmt.Errorf("JKS keystore password Secret contains no data for key %q", ref.Key) + } + + pw = pwSecret.Data[ref.Key] + + case crt.Spec.Keystores.JKS.Password != nil: + if len(*crt.Spec.Keystores.JKS.Password) == 0 { + return fmt.Errorf("JKS literal password cannot be empty") + } + + pw = []byte(*crt.Spec.Keystores.JKS.Password) + + default: + return fmt.Errorf("either passwordSecretRef or password must be set for JKS keystore") } - if pwSecret.Data == nil || len(pwSecret.Data[ref.Key]) == 0 { - return fmt.Errorf("JKS keystore password Secret contains no data for key %q", ref.Key) + + alias := "certificate" + if crt.Spec.Keystores.JKS.Alias != nil { + alias = *crt.Spec.Keystores.JKS.Alias } - pw := pwSecret.Data[ref.Key] - keystoreData, err := encodeJKSKeystore(pw, data.PrivateKey, data.Certificate, data.CA) + + keystoreData, err := encodeJKSKeystore(pw, alias, data.PrivateKey, data.Certificate, data.CA) if err != nil { return fmt.Errorf("error encoding JKS bundle: %w", err) } + // always overwrite the keystore entry - secret.Data[jksSecretKey] = keystoreData + secret.Data[cmapi.JKSSecretKey] = keystoreData if len(data.CA) > 0 { truststoreData, err := encodeJKSTruststore(pw, data.CA) @@ -271,7 +344,7 @@ func (s *SecretsManager) setKeystores(crt *cmapi.Certificate, secret *corev1.Sec return fmt.Errorf("error encoding JKS trust store bundle: %w", err) } // always overwrite the keystore entry - secret.Data[jksTruststoreKey] = truststoreData + secret.Data[cmapi.JKSTruststoreKey] = truststoreData } } diff --git a/pkg/controller/certificates/issuing/internal/secret_test.go b/pkg/controller/certificates/issuing/internal/secret_test.go index 56caf0def30..041bd7b048c 100644 --- a/pkg/controller/certificates/issuing/internal/secret_test.go +++ b/pkg/controller/certificates/issuing/internal/secret_test.go @@ -18,15 +18,11 @@ package internal import ( "context" - "encoding/pem" "errors" "strings" "testing" "time" - "github.com/cert-manager/cert-manager/internal/controller/feature" - testpkg "github.com/cert-manager/cert-manager/pkg/controller/test" - utilfeature "github.com/cert-manager/cert-manager/pkg/util/feature" "github.com/stretchr/testify/assert" corev1 "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" @@ -34,13 +30,14 @@ import ( apitypes "k8s.io/apimachinery/pkg/types" applycorev1 "k8s.io/client-go/applyconfigurations/core/v1" applymetav1 "k8s.io/client-go/applyconfigurations/meta/v1" - featuregatetesting "k8s.io/component-base/featuregate/testing" fakeclock "k8s.io/utils/clock/testing" - "k8s.io/utils/pointer" + "k8s.io/utils/ptr" + "github.com/cert-manager/cert-manager/internal/pem" cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" controllerpkg "github.com/cert-manager/cert-manager/pkg/controller" + testpkg "github.com/cert-manager/cert-manager/pkg/controller/test" utilpki "github.com/cert-manager/cert-manager/pkg/util/pki" testcoreclients "github.com/cert-manager/cert-manager/test/unit/coreclients" testcrypto "github.com/cert-manager/cert-manager/test/unit/crypto" @@ -58,13 +55,10 @@ var ( // SecretsManager. // See: https://github.com/kubernetes/client-go/issues/970 func Test_SecretsManager(t *testing.T) { - // Enable feature gate additional private key for this test - defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultMutableFeatureGate, feature.AdditionalCertificateOutputFormats, true)() - baseCert := gen.Certificate("test", - gen.SetCertificateIssuer(cmmeta.ObjectReference{Name: "ca-issuer", Kind: "Issuer", Group: "foo.io"}), + gen.SetCertificateIssuer(cmmeta.IssuerReference{Name: "ca-issuer", Kind: "Issuer", Group: "foo.io"}), gen.SetCertificateSecretName("output"), - gen.SetCertificateRenewBefore(time.Hour*36), + gen.SetCertificateRenewBefore(&metav1.Duration{Duration: time.Hour * 36}), gen.SetCertificateDNSNames("example.com"), gen.SetCertificateUID(apitypes.UID("test-uid")), ) @@ -84,16 +78,27 @@ func Test_SecretsManager(t *testing.T) { baseCertWithAdditionalOutputFormatDER := gen.CertificateFrom(baseCertBundle.Certificate, gen.SetCertificateAdditionalOutputFormats(cmapi.CertificateAdditionalOutputFormat{Type: "DER"}), ) + baseCertWithAdditionalOutputFormatCombinedPEM := gen.CertificateFrom(baseCertBundle.Certificate, gen.SetCertificateAdditionalOutputFormats(cmapi.CertificateAdditionalOutputFormat{Type: "CombinedPEM"}), ) + baseCertWithAdditionalOutputFormats := gen.CertificateFrom(baseCertBundle.Certificate, gen.SetCertificateAdditionalOutputFormats( cmapi.CertificateAdditionalOutputFormat{Type: "DER"}, cmapi.CertificateAdditionalOutputFormat{Type: "CombinedPEM"}, ), ) - block, _ := pem.Decode(baseCertBundle.PrivateKeyBytes) + keystorePassword := "something" + baseCertWithJKSKeystore := gen.CertificateFrom(baseCertBundle.Certificate, + gen.SetCertificateKeystore(&cmapi.CertificateKeystores{JKS: &cmapi.JKSKeystore{Create: true, Password: &keystorePassword}}), + ) + + baseCertWithPKCS12Keystore := gen.CertificateFrom(baseCertBundle.Certificate, + gen.SetCertificateKeystore(&cmapi.CertificateKeystores{PKCS12: &cmapi.PKCS12Keystore{Create: true, Password: &keystorePassword}}), + ) + + block, _, _ := pem.SafeDecodePrivateKey(baseCertBundle.PrivateKeyBytes) tlsDerContent := block.Bytes tests := map[string]struct { @@ -110,7 +115,10 @@ func Test_SecretsManager(t *testing.T) { certificateOptions: controllerpkg.CertificateOptions{EnableOwnerRef: false}, certificate: baseCertBundle.Certificate, existingSecret: nil, - secretData: SecretData{Certificate: []byte("test-cert"), CA: []byte("test-ca"), PrivateKey: []byte("test-key")}, + secretData: SecretData{ + Certificate: []byte("test-cert"), CA: []byte("test-ca"), PrivateKey: []byte("test-key"), + CertificateName: "test", IssuerName: "ca-issuer", IssuerKind: "Issuer", IssuerGroup: "foo.io", + }, applyFn: func(t *testing.T) testcoreclients.ApplyFn { return func(context.Context, *applycorev1.SecretApplyConfiguration, metav1.ApplyOptions) (*corev1.Secret, error) { t.Error("unexpected apply call") @@ -124,7 +132,10 @@ func Test_SecretsManager(t *testing.T) { certificateOptions: controllerpkg.CertificateOptions{EnableOwnerRef: false}, certificate: baseCertBundle.Certificate, existingSecret: nil, - secretData: SecretData{Certificate: baseCertBundle.CertBytes, CA: []byte("test-ca"), PrivateKey: []byte("test-key")}, + secretData: SecretData{ + Certificate: baseCertBundle.CertBytes, CA: []byte("test-ca"), PrivateKey: []byte("test-key"), + CertificateName: "test", IssuerName: "ca-issuer", IssuerKind: "Issuer", IssuerGroup: "foo.io", + }, applyFn: func(t *testing.T) testcoreclients.ApplyFn { return func(_ context.Context, gotCnf *applycorev1.SecretApplyConfiguration, gotOpts metav1.ApplyOptions) (*corev1.Secret, error) { expCnf := applycorev1.Secret("output", gen.DefaultTestNamespace). @@ -137,7 +148,7 @@ func Test_SecretsManager(t *testing.T) { cmapi.IPSANAnnotationKey: strings.Join(utilpki.IPAddressesToString(baseCertBundle.Cert.IPAddresses), ","), cmapi.URISANAnnotationKey: strings.Join(utilpki.URLsToString(baseCertBundle.Cert.URIs), ","), }). - WithLabels(make(map[string]string)). + WithLabels(map[string]string{cmapi.PartOfCertManagerControllerLabelKey: "true"}). WithData(map[string][]byte{ corev1.TLSCertKey: baseCertBundle.CertBytes, corev1.TLSPrivateKeyKey: []byte("test-key"), @@ -159,7 +170,10 @@ func Test_SecretsManager(t *testing.T) { certificateOptions: controllerpkg.CertificateOptions{EnableOwnerRef: true}, certificate: baseCertBundle.Certificate, existingSecret: nil, - secretData: SecretData{Certificate: baseCertBundle.CertBytes, CA: []byte("test-ca"), PrivateKey: []byte("test-key")}, + secretData: SecretData{ + Certificate: baseCertBundle.CertBytes, CA: []byte("test-ca"), PrivateKey: []byte("test-key"), + CertificateName: "test", IssuerName: "ca-issuer", IssuerKind: "Issuer", IssuerGroup: "foo.io", + }, applyFn: func(t *testing.T) testcoreclients.ApplyFn { return func(_ context.Context, gotCnf *applycorev1.SecretApplyConfiguration, gotOpts metav1.ApplyOptions) (*corev1.Secret, error) { expUID := apitypes.UID("test-uid") @@ -172,13 +186,13 @@ func Test_SecretsManager(t *testing.T) { cmapi.AltNamesAnnotationKey: strings.Join(baseCertBundle.Cert.DNSNames, ","), cmapi.IPSANAnnotationKey: strings.Join(utilpki.IPAddressesToString(baseCertBundle.Cert.IPAddresses), ","), cmapi.URISANAnnotationKey: strings.Join(utilpki.URLsToString(baseCertBundle.Cert.URIs), ","), }). - WithLabels(make(map[string]string)). + WithLabels(map[string]string{cmapi.PartOfCertManagerControllerLabelKey: "true"}). WithData(map[string][]byte{corev1.TLSCertKey: baseCertBundle.CertBytes, corev1.TLSPrivateKeyKey: []byte("test-key"), cmmeta.TLSCAKey: []byte("test-ca")}). WithType(corev1.SecretTypeTLS). WithOwnerReferences(&applymetav1.OwnerReferenceApplyConfiguration{ - APIVersion: pointer.String("cert-manager.io/v1"), Kind: pointer.String("Certificate"), - Name: pointer.String("test"), UID: &expUID, - Controller: pointer.Bool(true), BlockOwnerDeletion: pointer.Bool(true), + APIVersion: ptr.To("cert-manager.io/v1"), Kind: ptr.To("Certificate"), + Name: ptr.To("test"), UID: &expUID, + Controller: ptr.To(true), BlockOwnerDeletion: ptr.To(true), }) assert.Equal(t, expCnf, gotCnf) @@ -191,7 +205,7 @@ func Test_SecretsManager(t *testing.T) { expectedErr: false, }, - "if secret does exist, update existing Secret and leave custom annotations, with owner disabled": { + "if secret does exist, update existing Secret and leave custom annotations and labels, with owner disabled": { certificateOptions: controllerpkg.CertificateOptions{EnableOwnerRef: false}, certificate: baseCertBundle.Certificate, existingSecret: &corev1.Secret{ @@ -199,12 +213,15 @@ func Test_SecretsManager(t *testing.T) { Namespace: gen.DefaultTestNamespace, Name: "output", Annotations: map[string]string{"my-custom": "annotation"}, - Labels: map[string]string{}, + Labels: map[string]string{"my-custom": "label"}, }, Data: map[string][]byte{corev1.TLSCertKey: []byte("foo"), corev1.TLSPrivateKeyKey: []byte("foo"), cmmeta.TLSCAKey: []byte("foo")}, Type: corev1.SecretTypeTLS, }, - secretData: SecretData{Certificate: baseCertBundle.CertBytes, CA: []byte("test-ca"), PrivateKey: []byte("test-key")}, + secretData: SecretData{ + Certificate: baseCertBundle.CertBytes, CA: []byte("test-ca"), PrivateKey: []byte("test-key"), + CertificateName: "test", IssuerName: "ca-issuer", IssuerKind: "Issuer", IssuerGroup: "foo.io", + }, applyFn: func(t *testing.T) testcoreclients.ApplyFn { return func(_ context.Context, gotCnf *applycorev1.SecretApplyConfiguration, gotOpts metav1.ApplyOptions) (*corev1.Secret, error) { expCnf := applycorev1.Secret("output", gen.DefaultTestNamespace). @@ -218,7 +235,7 @@ func Test_SecretsManager(t *testing.T) { cmapi.IPSANAnnotationKey: strings.Join(utilpki.IPAddressesToString(baseCertBundle.Cert.IPAddresses), ","), cmapi.URISANAnnotationKey: strings.Join(utilpki.URLsToString(baseCertBundle.Cert.URIs), ","), }). - WithLabels(make(map[string]string)). + WithLabels(map[string]string{cmapi.PartOfCertManagerControllerLabelKey: "true"}). WithData(map[string][]byte{ corev1.TLSCertKey: baseCertBundle.CertBytes, corev1.TLSPrivateKeyKey: []byte("test-key"), @@ -235,7 +252,7 @@ func Test_SecretsManager(t *testing.T) { }, expectedErr: false, }, - "if secret does exist, update existing Secret and leave custom annotations, with owner enabled": { + "if secret does exist, update existing Secret and leave custom annotations and labels, with owner enabled": { certificateOptions: controllerpkg.CertificateOptions{EnableOwnerRef: true}, certificate: baseCertBundle.Certificate, existingSecret: &corev1.Secret{ @@ -243,12 +260,15 @@ func Test_SecretsManager(t *testing.T) { Namespace: gen.DefaultTestNamespace, Name: "output", Annotations: map[string]string{"my-custom": "annotation"}, - Labels: map[string]string{}, + Labels: map[string]string{"my-custom": "label"}, }, Data: map[string][]byte{corev1.TLSCertKey: []byte("foo"), corev1.TLSPrivateKeyKey: []byte("foo"), cmmeta.TLSCAKey: []byte("foo")}, Type: corev1.SecretTypeTLS, }, - secretData: SecretData{Certificate: baseCertBundle.CertBytes, CA: []byte("test-ca"), PrivateKey: []byte("test-key")}, + secretData: SecretData{ + Certificate: baseCertBundle.CertBytes, CA: []byte("test-ca"), PrivateKey: []byte("test-key"), + CertificateName: "test", IssuerName: "ca-issuer", IssuerKind: "Issuer", IssuerGroup: "foo.io", + }, applyFn: func(t *testing.T) testcoreclients.ApplyFn { return func(_ context.Context, gotCnf *applycorev1.SecretApplyConfiguration, gotOpts metav1.ApplyOptions) (*corev1.Secret, error) { expUID := apitypes.UID("test-uid") @@ -263,7 +283,7 @@ func Test_SecretsManager(t *testing.T) { cmapi.IPSANAnnotationKey: strings.Join(utilpki.IPAddressesToString(baseCertBundle.Cert.IPAddresses), ","), cmapi.URISANAnnotationKey: strings.Join(utilpki.URLsToString(baseCertBundle.Cert.URIs), ","), }). - WithLabels(make(map[string]string)). + WithLabels(map[string]string{cmapi.PartOfCertManagerControllerLabelKey: "true"}). WithData(map[string][]byte{ corev1.TLSCertKey: baseCertBundle.CertBytes, corev1.TLSPrivateKeyKey: []byte("test-key"), @@ -271,9 +291,9 @@ func Test_SecretsManager(t *testing.T) { }). WithType(corev1.SecretTypeTLS). WithOwnerReferences(&applymetav1.OwnerReferenceApplyConfiguration{ - APIVersion: pointer.String("cert-manager.io/v1"), Kind: pointer.String("Certificate"), - Name: pointer.String("test"), UID: &expUID, - Controller: pointer.Bool(true), BlockOwnerDeletion: pointer.Bool(true), + APIVersion: ptr.To("cert-manager.io/v1"), Kind: ptr.To("Certificate"), + Name: ptr.To("test"), UID: &expUID, + Controller: ptr.To(true), BlockOwnerDeletion: ptr.To(true), }) assert.Equal(t, expCnf, gotCnf) @@ -286,7 +306,7 @@ func Test_SecretsManager(t *testing.T) { expectedErr: false, }, - "if secret does not exist, create new Secret using the secret template": { + "if secret does exist, update existing Secret and add annotations set in secretTemplate": { certificateOptions: controllerpkg.CertificateOptions{EnableOwnerRef: false}, certificate: baseCertWithSecretTemplate, existingSecret: &corev1.Secret{ @@ -294,12 +314,15 @@ func Test_SecretsManager(t *testing.T) { Namespace: gen.DefaultTestNamespace, Name: "output", Annotations: map[string]string{"my-custom": "annotation"}, - Labels: map[string]string{}, + Labels: map[string]string{"my-custom": "label"}, }, Data: map[string][]byte{corev1.TLSCertKey: []byte("foo"), corev1.TLSPrivateKeyKey: []byte("foo"), cmmeta.TLSCAKey: []byte("foo")}, Type: corev1.SecretTypeTLS, }, - secretData: SecretData{Certificate: baseCertBundle.CertBytes, CA: []byte("test-ca"), PrivateKey: []byte("test-key")}, + secretData: SecretData{ + Certificate: baseCertBundle.CertBytes, CA: []byte("test-ca"), PrivateKey: []byte("test-key"), + CertificateName: "test", IssuerName: "ca-issuer", IssuerKind: "Issuer", IssuerGroup: "foo.io", + }, applyFn: func(t *testing.T) testcoreclients.ApplyFn { return func(_ context.Context, gotCnf *applycorev1.SecretApplyConfiguration, gotOpts metav1.ApplyOptions) (*corev1.Secret, error) { expCnf := applycorev1.Secret("output", gen.DefaultTestNamespace). @@ -315,7 +338,7 @@ func Test_SecretsManager(t *testing.T) { cmapi.IPSANAnnotationKey: strings.Join(utilpki.IPAddressesToString(baseCertBundle.Cert.IPAddresses), ","), cmapi.URISANAnnotationKey: strings.Join(utilpki.URLsToString(baseCertBundle.Cert.URIs), ","), }). - WithLabels(map[string]string{"template": "label"}). + WithLabels(map[string]string{"template": "label", cmapi.PartOfCertManagerControllerLabelKey: "true"}). WithData(map[string][]byte{ corev1.TLSCertKey: baseCertBundle.CertBytes, corev1.TLSPrivateKeyKey: []byte("test-key"), @@ -333,11 +356,64 @@ func Test_SecretsManager(t *testing.T) { expectedErr: false, }, - "if secret does exist, update existing Secret and add annotations set in secretTemplate": { + "if secret does exist, ensure that any missing base labels and annotations are added": { + certificateOptions: controllerpkg.CertificateOptions{EnableOwnerRef: false}, + certificate: baseCertWithSecretTemplate, + existingSecret: &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: gen.DefaultTestNamespace, + Name: "output", + Annotations: map[string]string{"my-custom": "annotation"}, + Labels: map[string]string{"my-custom": "label"}, + }, + Data: map[string][]byte{corev1.TLSCertKey: []byte("foo"), corev1.TLSPrivateKeyKey: []byte("foo"), cmmeta.TLSCAKey: []byte("foo")}, + Type: corev1.SecretTypeTLS, + }, + secretData: SecretData{ + Certificate: baseCertBundle.CertBytes, CA: []byte("test-ca"), PrivateKey: []byte("test-key"), + CertificateName: "test", IssuerName: "ca-issuer", IssuerKind: "Issuer", IssuerGroup: "foo.io", + }, + applyFn: func(t *testing.T) testcoreclients.ApplyFn { + return func(_ context.Context, gotCnf *applycorev1.SecretApplyConfiguration, gotOpts metav1.ApplyOptions) (*corev1.Secret, error) { + expCnf := applycorev1.Secret("output", gen.DefaultTestNamespace). + WithAnnotations( + map[string]string{ + "template": "annotation", + "my-custom": "annotation-from-secret", + cmapi.CertificateNameKey: "test", cmapi.IssuerGroupAnnotationKey: "foo.io", + cmapi.IssuerKindAnnotationKey: "Issuer", cmapi.IssuerNameAnnotationKey: "ca-issuer", + + cmapi.CommonNameAnnotationKey: baseCertBundle.Cert.Subject.CommonName, + cmapi.AltNamesAnnotationKey: strings.Join(baseCertBundle.Cert.DNSNames, ","), + cmapi.IPSANAnnotationKey: strings.Join(utilpki.IPAddressesToString(baseCertBundle.Cert.IPAddresses), ","), + cmapi.URISANAnnotationKey: strings.Join(utilpki.URLsToString(baseCertBundle.Cert.URIs), ","), + }). + WithLabels(map[string]string{"template": "label", cmapi.PartOfCertManagerControllerLabelKey: "true"}). + WithData(map[string][]byte{ + corev1.TLSCertKey: baseCertBundle.CertBytes, + corev1.TLSPrivateKeyKey: []byte("test-key"), + cmmeta.TLSCAKey: []byte("test-ca"), + }). + WithType(corev1.SecretTypeTLS) + assert.Equal(t, expCnf, gotCnf) + + expOpts := metav1.ApplyOptions{FieldManager: "cert-manager-test", Force: true} + assert.Equal(t, expOpts, gotOpts) + + return nil, nil + } + }, + expectedErr: false, + }, + + "if secret does not exist, create new Secret using the secret template": { certificateOptions: controllerpkg.CertificateOptions{EnableOwnerRef: true}, certificate: baseCertWithSecretTemplate, existingSecret: nil, - secretData: SecretData{Certificate: baseCertBundle.CertBytes, CA: []byte("test-ca"), PrivateKey: []byte("test-key")}, + secretData: SecretData{ + Certificate: baseCertBundle.CertBytes, CA: []byte("test-ca"), PrivateKey: []byte("test-key"), + CertificateName: "test", IssuerName: "ca-issuer", IssuerKind: "Issuer", IssuerGroup: "foo.io", + }, applyFn: func(t *testing.T) testcoreclients.ApplyFn { return func(_ context.Context, gotCnf *applycorev1.SecretApplyConfiguration, gotOpts metav1.ApplyOptions) (*corev1.Secret, error) { expUID := apitypes.UID("test-uid") @@ -354,7 +430,7 @@ func Test_SecretsManager(t *testing.T) { cmapi.IPSANAnnotationKey: strings.Join(utilpki.IPAddressesToString(baseCertBundle.Cert.IPAddresses), ","), cmapi.URISANAnnotationKey: strings.Join(utilpki.URLsToString(baseCertBundle.Cert.URIs), ","), }). - WithLabels(map[string]string{"template": "label"}). + WithLabels(map[string]string{cmapi.PartOfCertManagerControllerLabelKey: "true", "template": "label"}). WithData(map[string][]byte{ corev1.TLSCertKey: baseCertBundle.CertBytes, corev1.TLSPrivateKeyKey: []byte("test-key"), @@ -362,9 +438,9 @@ func Test_SecretsManager(t *testing.T) { }). WithType(corev1.SecretTypeTLS). WithOwnerReferences(&applymetav1.OwnerReferenceApplyConfiguration{ - APIVersion: pointer.String("cert-manager.io/v1"), Kind: pointer.String("Certificate"), - Name: pointer.String("test"), UID: &expUID, - Controller: pointer.Bool(true), BlockOwnerDeletion: pointer.Bool(true), + APIVersion: ptr.To("cert-manager.io/v1"), Kind: ptr.To("Certificate"), + Name: ptr.To("test"), UID: &expUID, + Controller: ptr.To(true), BlockOwnerDeletion: ptr.To(true), }) assert.Equal(t, expCnf, gotCnf) @@ -381,7 +457,10 @@ func Test_SecretsManager(t *testing.T) { certificateOptions: controllerpkg.CertificateOptions{EnableOwnerRef: false}, certificate: baseCertWithAdditionalOutputFormatDER, existingSecret: nil, - secretData: SecretData{Certificate: baseCertBundle.CertBytes, CA: []byte("test-ca"), PrivateKey: baseCertBundle.PrivateKeyBytes}, + secretData: SecretData{ + Certificate: baseCertBundle.CertBytes, CA: []byte("test-ca"), PrivateKey: baseCertBundle.PrivateKeyBytes, + CertificateName: "test", IssuerName: "ca-issuer", IssuerKind: "Issuer", IssuerGroup: "foo.io", + }, applyFn: func(t *testing.T) testcoreclients.ApplyFn { return func(_ context.Context, gotCnf *applycorev1.SecretApplyConfiguration, gotOpts metav1.ApplyOptions) (*corev1.Secret, error) { expCnf := applycorev1.Secret("output", gen.DefaultTestNamespace). @@ -395,7 +474,7 @@ func Test_SecretsManager(t *testing.T) { cmapi.IPSANAnnotationKey: strings.Join(utilpki.IPAddressesToString(baseCertBundle.Cert.IPAddresses), ","), cmapi.URISANAnnotationKey: strings.Join(utilpki.URLsToString(baseCertBundle.Cert.URIs), ","), }). - WithLabels(make(map[string]string)). + WithLabels(map[string]string{cmapi.PartOfCertManagerControllerLabelKey: "true"}). WithData(map[string][]byte{ corev1.TLSCertKey: baseCertBundle.CertBytes, corev1.TLSPrivateKeyKey: baseCertBundle.PrivateKeyBytes, @@ -418,7 +497,10 @@ func Test_SecretsManager(t *testing.T) { certificateOptions: controllerpkg.CertificateOptions{EnableOwnerRef: false}, certificate: baseCertWithAdditionalOutputFormatCombinedPEM, existingSecret: nil, - secretData: SecretData{Certificate: baseCertBundle.CertBytes, CA: []byte("test-ca"), PrivateKey: baseCertBundle.PrivateKeyBytes}, + secretData: SecretData{ + Certificate: baseCertBundle.CertBytes, CA: []byte("test-ca"), PrivateKey: baseCertBundle.PrivateKeyBytes, + CertificateName: "test", IssuerName: "ca-issuer", IssuerKind: "Issuer", IssuerGroup: "foo.io", + }, applyFn: func(t *testing.T) testcoreclients.ApplyFn { return func(_ context.Context, gotCnf *applycorev1.SecretApplyConfiguration, gotOpts metav1.ApplyOptions) (*corev1.Secret, error) { expCnf := applycorev1.Secret("output", gen.DefaultTestNamespace). @@ -432,7 +514,7 @@ func Test_SecretsManager(t *testing.T) { cmapi.IPSANAnnotationKey: strings.Join(utilpki.IPAddressesToString(baseCertBundle.Cert.IPAddresses), ","), cmapi.URISANAnnotationKey: strings.Join(utilpki.URLsToString(baseCertBundle.Cert.URIs), ","), }). - WithLabels(make(map[string]string)). + WithLabels(map[string]string{cmapi.PartOfCertManagerControllerLabelKey: "true"}). WithData(map[string][]byte{ corev1.TLSCertKey: baseCertBundle.CertBytes, corev1.TLSPrivateKeyKey: baseCertBundle.PrivateKeyBytes, @@ -455,7 +537,10 @@ func Test_SecretsManager(t *testing.T) { certificateOptions: controllerpkg.CertificateOptions{EnableOwnerRef: false}, certificate: baseCertWithAdditionalOutputFormats, existingSecret: nil, - secretData: SecretData{Certificate: baseCertBundle.CertBytes, CA: []byte("test-ca"), PrivateKey: baseCertBundle.PrivateKeyBytes}, + secretData: SecretData{ + Certificate: baseCertBundle.CertBytes, CA: []byte("test-ca"), PrivateKey: baseCertBundle.PrivateKeyBytes, + CertificateName: "test", IssuerName: "ca-issuer", IssuerKind: "Issuer", IssuerGroup: "foo.io", + }, applyFn: func(t *testing.T) testcoreclients.ApplyFn { return func(_ context.Context, gotCnf *applycorev1.SecretApplyConfiguration, gotOpts metav1.ApplyOptions) (*corev1.Secret, error) { expCnf := applycorev1.Secret("output", gen.DefaultTestNamespace). @@ -469,7 +554,7 @@ func Test_SecretsManager(t *testing.T) { cmapi.IPSANAnnotationKey: strings.Join(utilpki.IPAddressesToString(baseCertBundle.Cert.IPAddresses), ","), cmapi.URISANAnnotationKey: strings.Join(utilpki.URLsToString(baseCertBundle.Cert.URIs), ","), }). - WithLabels(make(map[string]string)). + WithLabels(map[string]string{cmapi.PartOfCertManagerControllerLabelKey: "true"}). WithData(map[string][]byte{ corev1.TLSCertKey: baseCertBundle.CertBytes, corev1.TLSPrivateKeyKey: baseCertBundle.PrivateKeyBytes, @@ -492,7 +577,10 @@ func Test_SecretsManager(t *testing.T) { "if secret exists, with tls-combined.pem and key.der but no additional formats specified": { certificateOptions: controllerpkg.CertificateOptions{EnableOwnerRef: false}, certificate: baseCertBundle.Certificate, - secretData: SecretData{Certificate: baseCertBundle.CertBytes, CA: []byte("test-ca"), PrivateKey: baseCertBundle.PrivateKeyBytes}, + secretData: SecretData{ + Certificate: baseCertBundle.CertBytes, CA: []byte("test-ca"), PrivateKey: baseCertBundle.PrivateKeyBytes, + CertificateName: "test", IssuerName: "ca-issuer", IssuerKind: "Issuer", IssuerGroup: "foo.io", + }, existingSecret: &corev1.Secret{ ObjectMeta: metav1.ObjectMeta{ Namespace: gen.DefaultTestNamespace, @@ -500,6 +588,9 @@ func Test_SecretsManager(t *testing.T) { Annotations: map[string]string{ "my-custom": "annotation", }, + Labels: map[string]string{ + "my-custom": "label", + }, }, Data: map[string][]byte{ corev1.TLSCertKey: []byte("foo"), @@ -524,7 +615,7 @@ func Test_SecretsManager(t *testing.T) { cmapi.IPSANAnnotationKey: strings.Join(utilpki.IPAddressesToString(baseCertBundle.Cert.IPAddresses), ","), cmapi.URISANAnnotationKey: strings.Join(utilpki.URLsToString(baseCertBundle.Cert.URIs), ","), }). - WithLabels(make(map[string]string)). + WithLabels(map[string]string{cmapi.PartOfCertManagerControllerLabelKey: "true"}). WithData(map[string][]byte{ corev1.TLSCertKey: baseCertBundle.CertBytes, corev1.TLSPrivateKeyKey: baseCertBundle.PrivateKeyBytes, @@ -545,7 +636,10 @@ func Test_SecretsManager(t *testing.T) { "if secret exists, with tls-combined.pem and key.der but only DER Format specified": { certificateOptions: controllerpkg.CertificateOptions{EnableOwnerRef: false}, certificate: baseCertWithAdditionalOutputFormatDER, - secretData: SecretData{Certificate: baseCertBundle.CertBytes, CA: []byte("test-ca"), PrivateKey: baseCertBundle.PrivateKeyBytes}, + secretData: SecretData{ + Certificate: baseCertBundle.CertBytes, CA: []byte("test-ca"), PrivateKey: baseCertBundle.PrivateKeyBytes, + CertificateName: "test", IssuerName: "ca-issuer", IssuerKind: "Issuer", IssuerGroup: "foo.io", + }, existingSecret: &corev1.Secret{ ObjectMeta: metav1.ObjectMeta{ Namespace: gen.DefaultTestNamespace, @@ -553,6 +647,9 @@ func Test_SecretsManager(t *testing.T) { Annotations: map[string]string{ "my-custom": "annotation", }, + Labels: map[string]string{ + "my-custom": "label", + }, }, Data: map[string][]byte{ corev1.TLSCertKey: []byte("foo"), @@ -577,7 +674,7 @@ func Test_SecretsManager(t *testing.T) { cmapi.IPSANAnnotationKey: strings.Join(utilpki.IPAddressesToString(baseCertBundle.Cert.IPAddresses), ","), cmapi.URISANAnnotationKey: strings.Join(utilpki.URLsToString(baseCertBundle.Cert.URIs), ","), }). - WithLabels(make(map[string]string)). + WithLabels(map[string]string{cmapi.PartOfCertManagerControllerLabelKey: "true"}). WithData(map[string][]byte{ corev1.TLSCertKey: baseCertBundle.CertBytes, corev1.TLSPrivateKeyKey: baseCertBundle.PrivateKeyBytes, @@ -599,7 +696,10 @@ func Test_SecretsManager(t *testing.T) { "if secret exists, with tls-combined.pem and key.der but only Combined PEM Format specified": { certificateOptions: controllerpkg.CertificateOptions{EnableOwnerRef: false}, certificate: baseCertWithAdditionalOutputFormatCombinedPEM, - secretData: SecretData{Certificate: baseCertBundle.CertBytes, CA: []byte("test-ca"), PrivateKey: baseCertBundle.PrivateKeyBytes}, + secretData: SecretData{ + Certificate: baseCertBundle.CertBytes, CA: []byte("test-ca"), PrivateKey: baseCertBundle.PrivateKeyBytes, + CertificateName: "test", IssuerName: "ca-issuer", IssuerKind: "Issuer", IssuerGroup: "foo.io", + }, existingSecret: &corev1.Secret{ ObjectMeta: metav1.ObjectMeta{ Namespace: gen.DefaultTestNamespace, @@ -607,6 +707,9 @@ func Test_SecretsManager(t *testing.T) { Annotations: map[string]string{ "my-custom": "annotation", }, + Labels: map[string]string{ + "my-custom": "label", + }, }, Data: map[string][]byte{ corev1.TLSCertKey: []byte("foo"), @@ -631,7 +734,7 @@ func Test_SecretsManager(t *testing.T) { cmapi.IPSANAnnotationKey: strings.Join(utilpki.IPAddressesToString(baseCertBundle.Cert.IPAddresses), ","), cmapi.URISANAnnotationKey: strings.Join(utilpki.URLsToString(baseCertBundle.Cert.URIs), ","), }). - WithLabels(make(map[string]string)). + WithLabels(map[string]string{cmapi.PartOfCertManagerControllerLabelKey: "true"}). WithData(map[string][]byte{ corev1.TLSCertKey: baseCertBundle.CertBytes, corev1.TLSPrivateKeyKey: baseCertBundle.PrivateKeyBytes, @@ -653,7 +756,10 @@ func Test_SecretsManager(t *testing.T) { certificateOptions: controllerpkg.CertificateOptions{EnableOwnerRef: true}, certificate: baseCertWithSecretTemplate, existingSecret: nil, - secretData: SecretData{Certificate: baseCertBundle.CertBytes, CA: []byte("test-ca"), PrivateKey: []byte("test-key")}, + secretData: SecretData{ + Certificate: baseCertBundle.CertBytes, CA: []byte("test-ca"), PrivateKey: []byte("test-key"), + CertificateName: "test", IssuerName: "ca-issuer", IssuerKind: "Issuer", IssuerGroup: "foo.io", + }, applyFn: func(t *testing.T) testcoreclients.ApplyFn { return func(_ context.Context, gotCnf *applycorev1.SecretApplyConfiguration, gotOpts metav1.ApplyOptions) (*corev1.Secret, error) { return nil, errors.New("this is an error") @@ -661,9 +767,41 @@ func Test_SecretsManager(t *testing.T) { }, expectedErr: true, }, - } - // TODO: add to these tests once the JKS/PKCS12 support is updated + "if secret does not exist, create new Secret with JKS keystore": { + certificateOptions: controllerpkg.CertificateOptions{EnableOwnerRef: false}, + certificate: baseCertWithJKSKeystore, + existingSecret: nil, + secretData: SecretData{ + Certificate: baseCertBundle.CertBytes, PrivateKey: baseCertBundle.PrivateKeyBytes, + CertificateName: "test", IssuerName: "ca-issuer", IssuerKind: "Issuer", IssuerGroup: "foo.io", + }, + applyFn: func(t *testing.T) testcoreclients.ApplyFn { + return func(_ context.Context, gotCnf *applycorev1.SecretApplyConfiguration, gotOpts metav1.ApplyOptions) (*corev1.Secret, error) { + assert.NotNil(t, gotCnf.Data[cmapi.JKSSecretKey]) + return nil, nil + } + }, + expectedErr: false, + }, + + "if secret does not exist, create new Secret with PKCS12 keystore": { + certificateOptions: controllerpkg.CertificateOptions{EnableOwnerRef: false}, + certificate: baseCertWithPKCS12Keystore, + existingSecret: nil, + secretData: SecretData{ + Certificate: baseCertBundle.CertBytes, PrivateKey: baseCertBundle.PrivateKeyBytes, + CertificateName: "test", IssuerName: "ca-issuer", IssuerKind: "Issuer", IssuerGroup: "foo.io", + }, + applyFn: func(t *testing.T) testcoreclients.ApplyFn { + return func(_ context.Context, gotCnf *applycorev1.SecretApplyConfiguration, gotOpts metav1.ApplyOptions) (*corev1.Secret, error) { + assert.NotNil(t, gotCnf.Data[cmapi.PKCS12SecretKey]) + return nil, nil + } + }, + expectedErr: false, + }, + } for name, test := range tests { t.Run(name, func(t *testing.T) { @@ -683,7 +821,7 @@ func Test_SecretsManager(t *testing.T) { test.certificateOptions.EnableOwnerRef, ) - err := testManager.UpdateData(context.Background(), test.certificate, test.secretData) + err := testManager.UpdateData(t.Context(), test.certificate, test.secretData) if err != nil && !test.expectedErr { t.Errorf("expected to not get an error, but got: %v", err) } @@ -712,7 +850,7 @@ func Test_getCertificateSecret(t *testing.T) { Type: corev1.SecretTypeTLS, }, }, - "if secret exists, expect onlt basic metadata to be retuned, but the Type set to tls": { + "if secret exists, expect only basic metadata to be retuned, but the Type set to tls": { existingSecret: &corev1.Secret{ ObjectMeta: metav1.ObjectMeta{ Namespace: "test-namespace", Name: "test-secret", @@ -763,14 +901,14 @@ func Test_getCertificateSecret(t *testing.T) { s := SecretsManager{ secretClient: builder.Client.CoreV1(), - secretLister: builder.KubeSharedInformerFactory.Core().V1().Secrets().Lister(), + secretLister: builder.KubeSharedInformerFactory.Secrets().Lister(), fieldManager: "cert-manager-test", } builder.Start() defer builder.Stop() - gotSecret, err := s.getCertificateSecret(context.Background(), crt) + gotSecret, err := s.getCertificateSecret(crt) assert.NoError(t, err) assert.Equal(t, test.expSecret, gotSecret, "unexpected returned secret") diff --git a/pkg/controller/certificates/issuing/issuing_controller.go b/pkg/controller/certificates/issuing/issuing_controller.go index 1276ee2c51d..53033fb81cb 100644 --- a/pkg/controller/certificates/issuing/issuing_controller.go +++ b/pkg/controller/certificates/issuing/issuing_controller.go @@ -27,9 +27,7 @@ import ( apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" - "k8s.io/client-go/informers" - "k8s.io/client-go/kubernetes" - corelisters "k8s.io/client-go/listers/core/v1" + "k8s.io/apimachinery/pkg/types" "k8s.io/client-go/tools/cache" "k8s.io/client-go/tools/record" "k8s.io/client-go/util/workqueue" @@ -38,11 +36,11 @@ import ( internalcertificates "github.com/cert-manager/cert-manager/internal/controller/certificates" "github.com/cert-manager/cert-manager/internal/controller/certificates/policies" "github.com/cert-manager/cert-manager/internal/controller/feature" + internalinformers "github.com/cert-manager/cert-manager/internal/informers" apiutil "github.com/cert-manager/cert-manager/pkg/api/util" cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" cmclient "github.com/cert-manager/cert-manager/pkg/client/clientset/versioned" - cminformers "github.com/cert-manager/cert-manager/pkg/client/informers/externalversions" cmlisters "github.com/cert-manager/cert-manager/pkg/client/listers/certmanager/v1" controllerpkg "github.com/cert-manager/cert-manager/pkg/controller" "github.com/cert-manager/cert-manager/pkg/controller/certificates" @@ -66,7 +64,7 @@ type localTemporarySignerFn func(crt *cmapi.Certificate, pk []byte) ([]byte, err type controller struct { certificateLister cmlisters.CertificateLister certificateRequestLister cmlisters.CertificateRequestLister - secretLister corelisters.SecretLister + secretLister internalinformers.SecretLister recorder record.EventRecorder clock clock.Clock @@ -92,39 +90,45 @@ type controller struct { func NewController( log logr.Logger, - kubeClient kubernetes.Interface, - client cmclient.Interface, - factory informers.SharedInformerFactory, - cmFactory cminformers.SharedInformerFactory, - recorder record.EventRecorder, - clock clock.Clock, - certificateControllerOptions controllerpkg.CertificateOptions, - fieldManager string, -) (*controller, workqueue.RateLimitingInterface, []cache.InformerSynced) { + ctx *controllerpkg.Context, +) (*controller, workqueue.TypedRateLimitingInterface[types.NamespacedName], []cache.InformerSynced, error) { // create a queue used to queue up items to be processed - queue := workqueue.NewNamedRateLimitingQueue(workqueue.NewItemExponentialFailureRateLimiter(time.Second*1, time.Second*30), ControllerName) + queue := workqueue.NewTypedRateLimitingQueueWithConfig( + controllerpkg.DefaultCertificateRateLimiter(), + workqueue.TypedRateLimitingQueueConfig[types.NamespacedName]{ + Name: ControllerName, + }, + ) // obtain references to all the informers used by this controller - certificateInformer := cmFactory.Certmanager().V1().Certificates() - certificateRequestInformer := cmFactory.Certmanager().V1().CertificateRequests() - secretsInformer := factory.Core().V1().Secrets() + certificateInformer := ctx.SharedInformerFactory.Certmanager().V1().Certificates() + certificateRequestInformer := ctx.SharedInformerFactory.Certmanager().V1().CertificateRequests() + secretsInformer := ctx.KubeSharedInformerFactory.Secrets() - certificateInformer.Informer().AddEventHandler(&controllerpkg.QueuingEventHandler{Queue: queue}) - certificateRequestInformer.Informer().AddEventHandler(&controllerpkg.BlockingEventHandler{ + if _, err := certificateInformer.Informer().AddEventHandler(&controllerpkg.QueuingEventHandler{Queue: queue}); err != nil { + return nil, nil, nil, fmt.Errorf("error setting up event handler: %v", err) + } + if _, err := certificateRequestInformer.Informer().AddEventHandler(&controllerpkg.BlockingEventHandler{ WorkFunc: certificates.EnqueueCertificatesForResourceUsingPredicates(log, queue, certificateInformer.Lister(), labels.Everything(), predicate.ResourceOwnerOf), - }) - secretsInformer.Informer().AddEventHandler(&controllerpkg.BlockingEventHandler{ + }); err != nil { + return nil, nil, nil, fmt.Errorf("error setting up event handler: %v", err) + } + if _, err := secretsInformer.Informer().AddEventHandler(&controllerpkg.BlockingEventHandler{ // Issuer reconciles on changes to the Secret named `spec.nextPrivateKeySecretName` WorkFunc: certificates.EnqueueCertificatesForResourceUsingPredicates(log, queue, certificateInformer.Lister(), labels.Everything(), predicate.ResourceOwnerOf, predicate.ExtractResourceName(predicate.CertificateNextPrivateKeySecretName)), - }) - secretsInformer.Informer().AddEventHandler(&controllerpkg.BlockingEventHandler{ + }); err != nil { + return nil, nil, nil, fmt.Errorf("error setting up event handler: %v", err) + } + if _, err := secretsInformer.Informer().AddEventHandler(&controllerpkg.BlockingEventHandler{ // Issuer reconciles on changes to the Secret named `spec.secretName` WorkFunc: certificates.EnqueueCertificatesForResourceUsingPredicates(log, queue, certificateInformer.Lister(), labels.Everything(), predicate.ExtractResourceName(predicate.CertificateSecretName)), - }) + }); err != nil { + return nil, nil, nil, fmt.Errorf("error setting up event handler: %v", err) + } // build a list of InformerSynced functions that will be returned by the Register method. // the controller will only begin processing items once all of these informers have synced. @@ -135,28 +139,28 @@ func NewController( } secretsManager := internal.NewSecretsManager( - kubeClient.CoreV1(), secretsInformer.Lister(), - fieldManager, certificateControllerOptions.EnableOwnerRef, + ctx.Client.CoreV1(), secretsInformer.Lister(), + ctx.FieldManager, ctx.CertificateOptions.EnableOwnerRef, ) return &controller{ certificateLister: certificateInformer.Lister(), certificateRequestLister: certificateRequestInformer.Lister(), secretLister: secretsInformer.Lister(), - client: client, - recorder: recorder, - clock: clock, + client: ctx.CMClient, + recorder: ctx.Recorder, + clock: ctx.Clock, secretsUpdateData: secretsManager.UpdateData, postIssuancePolicyChain: policies.NewSecretPostIssuancePolicyChain( - certificateControllerOptions.EnableOwnerRef, - fieldManager, + ctx.CertificateOptions.EnableOwnerRef, + ctx.FieldManager, ), - fieldManager: fieldManager, - localTemporarySigner: certificates.GenerateLocallySignedTemporaryCertificate, - }, queue, mustSync + fieldManager: ctx.FieldManager, + localTemporarySigner: utilpki.GenerateLocallySignedTemporaryCertificate, + }, queue, mustSync, nil } -func (c *controller) ProcessItem(ctx context.Context, key string) error { +func (c *controller) ProcessItem(ctx context.Context, key types.NamespacedName) error { // TODO: Change to globals.DefaultControllerContextTimeout as part of a wider effort to ensure we have // failsafe timeouts in every controller ctx, cancel := context.WithTimeout(ctx, time.Second*10) @@ -164,19 +168,17 @@ func (c *controller) ProcessItem(ctx context.Context, key string) error { log := logf.FromContext(ctx).WithValues("key", key) - namespace, name, err := cache.SplitMetaNamespaceKey(key) - if err != nil { - return nil - } + namespace, name := key.Namespace, key.Name crt, err := c.certificateLister.Certificates(namespace).Get(name) - if apierrors.IsNotFound(err) { - log.V(logf.DebugLevel).Info("certificate not found for key", "error", err.Error()) - return nil - } - if err != nil { + if err != nil && !apierrors.IsNotFound(err) { return err } + if crt == nil || crt.DeletionTimestamp != nil { + // If the Certificate object was/ is being deleted, we don't want to update its status or + // create/ update any Secret resources. + return nil + } log = logf.WithResource(log, crt) ctx = logf.NewContext(ctx, log) @@ -217,10 +219,7 @@ func (c *controller) ProcessItem(ctx context.Context, key string) error { logf.WithResource(log, nextPrivateKeySecret).Error(err, "failed to parse next private key, waiting for keymanager controller") return nil } - pkViolations, err := certificates.PrivateKeyMatchesSpec(pk, crt.Spec) - if err != nil { - return err - } + pkViolations := utilpki.PrivateKeyMatchesSpec(pk, crt.Spec) if len(pkViolations) > 0 { logf.WithResource(log, nextPrivateKeySecret).Info("stored next private key does not match requirements on Certificate resource, waiting for keymanager controller", "violations", pkViolations) return nil @@ -251,7 +250,7 @@ func (c *controller) ProcessItem(ctx context.Context, key string) error { // Verify the CSR options match what is requested in certificate.spec. // If there are violations in the spec, then the requestmanager will handle this. - requestViolations, err := certificates.RequestMatchesSpec(req, crt.Spec) + requestViolations, err := utilpki.RequestMatchesSpec(req, crt.Spec) if err != nil { return err } @@ -278,25 +277,37 @@ func (c *controller) ProcessItem(ctx context.Context, key string) error { return nil } - // Some issuers won't honor the "Denied=True" condition, and we don't want - // to break these issuers. To avoid breaking these issuers, we skip bubbling - // up the "Denied=True" condition from the certificate request object to the - // certificate object when the issuer ignores the "Denied" state. - // - // To know whether or not an issuer ignores the "Denied" state, we pay - // attention to the "Ready" condition on the certificate request. If a - // certificate request is "Denied=True" and that the issuer still proceeds - // to adding the "Ready" condition (to either true or false), then we - // consider that this issuer has ignored the "Denied" state. - if crReadyCond == nil { - if apiutil.CertificateRequestIsDenied(req) { - return c.failIssueCertificate(ctx, log, crt, apiutil.GetCertificateRequestCondition(req, cmapi.CertificateRequestConditionDenied)) - } + // Now check if CertificateRequest is in any of the final states so that + // this issuance can be completed as either succeeded or failed. Failed + // issuance will be retried with a delay (the logic for that lives in + // certificates-trigger controller). Final states are: Denied condition + // with status True => fail issuance InvalidRequest condition with + // status True => fail issuance Ready condition with reason Failed => + // fail issuance Ready condition with reason Issued => finalize issuance + // as succeeded. - if apiutil.CertificateRequestHasInvalidRequest(req) { - return c.failIssueCertificate(ctx, log, crt, apiutil.GetCertificateRequestCondition(req, cmapi.CertificateRequestConditionInvalidRequest)) - } + // In case of a non-compliant issuer, a CertificateRequest can have both + // Denied status True (set by an approver) and Ready condition with + // reason Issued (set by the issuer). In this case, we prioritize the + // Denied condition and fail the issuance. This is done for consistency + // and also to avoid race conditions between the non-compliant issuer + // and this control loop. + + // If the certificate request was denied, set the last failure time to + // now, bump the issuance attempts and set the Issuing status condition + // to False. + if apiutil.CertificateRequestIsDenied(req) { + return c.failIssueCertificate(ctx, log, crt, apiutil.GetCertificateRequestCondition(req, cmapi.CertificateRequestConditionDenied)) + } + + // If the certificate request is invalid, set the last failure time to + // now, bump the issuance attempts and set the Issuing status condition + // to False. + if apiutil.CertificateRequestHasInvalidRequest(req) { + return c.failIssueCertificate(ctx, log, crt, apiutil.GetCertificateRequestCondition(req, cmapi.CertificateRequestConditionInvalidRequest)) + } + if crReadyCond == nil { log.V(logf.DebugLevel).Info("CertificateRequest does not have Ready condition, waiting...") return nil } @@ -387,16 +398,20 @@ func (c *controller) issueCertificate(ctx context.Context, nextRevision int, crt return err } secretData := internal.SecretData{ - PrivateKey: pkData, - Certificate: req.Status.Certificate, - CA: req.Status.CA, + PrivateKey: pkData, + Certificate: req.Status.Certificate, + CA: req.Status.CA, + CertificateName: crt.Name, + IssuerName: req.Spec.IssuerRef.Name, + IssuerKind: req.Spec.IssuerRef.Kind, + IssuerGroup: req.Spec.IssuerRef.Group, } if err := c.secretsUpdateData(ctx, crt, secretData); err != nil { return err } - //Set status.revision to revision of the CertificateRequest + // Set status.revision to revision of the CertificateRequest crt.Status.Revision = &nextRevision // Remove Issuing status condition @@ -463,23 +478,14 @@ type controllerWrapper struct { *controller } -func (c *controllerWrapper) Register(ctx *controllerpkg.Context) (workqueue.RateLimitingInterface, []cache.InformerSynced, error) { +func (c *controllerWrapper) Register(ctx *controllerpkg.Context) (workqueue.TypedRateLimitingInterface[types.NamespacedName], []cache.InformerSynced, error) { // construct a new named logger to be reused throughout the controller log := logf.FromContext(ctx.RootContext, ControllerName) - ctrl, queue, mustSync := NewController(log, - ctx.Client, - ctx.CMClient, - ctx.KubeSharedInformerFactory, - ctx.SharedInformerFactory, - ctx.Recorder, - ctx.Clock, - ctx.CertificateOptions, - ctx.FieldManager, - ) + ctrl, queue, mustSync, err := NewController(log, ctx) c.controller = ctrl - return queue, mustSync, nil + return queue, mustSync, err } func init() { diff --git a/pkg/controller/certificates/issuing/issuing_controller_test.go b/pkg/controller/certificates/issuing/issuing_controller_test.go index 2b911fed04c..d98fa6b45da 100644 --- a/pkg/controller/certificates/issuing/issuing_controller_test.go +++ b/pkg/controller/certificates/issuing/issuing_controller_test.go @@ -27,10 +27,10 @@ import ( corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" coretesting "k8s.io/client-go/testing" - "k8s.io/client-go/tools/cache" fakeclock "k8s.io/utils/clock/testing" - "k8s.io/utils/pointer" + "k8s.io/utils/ptr" cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" @@ -64,10 +64,10 @@ func TestIssuingController(t *testing.T) { nextPrivateKeySecretName := "next-private-key" baseCert := gen.Certificate("test", - gen.SetCertificateIssuer(cmmeta.ObjectReference{Name: "ca-issuer", Kind: "Issuer", Group: "foo.io"}), + gen.SetCertificateIssuer(cmmeta.IssuerReference{Name: "ca-issuer", Kind: "Issuer", Group: "foo.io"}), gen.SetCertificateGeneration(3), gen.SetCertificateSecretName("output"), - gen.SetCertificateRenewBefore(time.Hour*36), + gen.SetCertificateRenewBefore(&metav1.Duration{Duration: time.Hour * 36}), gen.SetCertificateDNSNames("example.com"), gen.SetCertificateRevision(1), gen.SetCertificateNextPrivateKeySecretName(nextPrivateKeySecretName), @@ -291,7 +291,7 @@ func TestIssuingController(t *testing.T) { ObservedGeneration: 3, }), gen.SetCertificateLastFailureTime(metaFixedClockStart), - gen.SetCertificateIssuanceAttempts(pointer.Int(1)), + gen.SetCertificateIssuanceAttempts(ptr.To(1)), ), )), }, @@ -305,7 +305,7 @@ func TestIssuingController(t *testing.T) { certificate: exampleBundle.Certificate, builder: &testpkg.Builder{ CertManagerObjects: []runtime.Object{ - gen.CertificateFrom(issuingCert, gen.SetCertificateIssuanceAttempts(pointer.Int(4))), + gen.CertificateFrom(issuingCert, gen.SetCertificateIssuanceAttempts(ptr.To(4))), gen.CertificateRequestFrom(exampleBundle.CertificateRequestFailed, gen.AddCertificateRequestAnnotations(map[string]string{ cmapi.CertificateRequestRevisionAnnotationKey: "2", // Current Certificate revision=1 @@ -343,7 +343,7 @@ func TestIssuingController(t *testing.T) { ObservedGeneration: 3, }), gen.SetCertificateLastFailureTime(metaFixedClockStart), - gen.SetCertificateIssuanceAttempts(pointer.Int(5)), + gen.SetCertificateIssuanceAttempts(ptr.To(5)), ), )), }, @@ -507,9 +507,13 @@ func TestIssuingController(t *testing.T) { }, }, expSecretUpdateDataCall: &internal.SecretData{ - Certificate: exampleBundle.CertificateRequestReady.Status.Certificate, - PrivateKey: exampleBundle.PrivateKeyBytes, - CA: nil, + Certificate: exampleBundle.CertificateRequestReady.Status.Certificate, + PrivateKey: exampleBundle.PrivateKeyBytes, + CA: nil, + CertificateName: "test", + IssuerName: "ca-issuer", + IssuerKind: "Issuer", + IssuerGroup: "foo.io", }, expectedErr: false, }, @@ -561,9 +565,13 @@ func TestIssuingController(t *testing.T) { }, }, expSecretUpdateDataCall: &internal.SecretData{ - Certificate: exampleBundle.CertificateRequestReady.Status.Certificate, - PrivateKey: exampleBundle.PrivateKeyBytes, - CA: nil, + Certificate: exampleBundle.CertificateRequestReady.Status.Certificate, + PrivateKey: exampleBundle.PrivateKeyBytes, + CA: nil, + CertificateName: "test", + IssuerName: "ca-issuer", + IssuerKind: "Issuer", + IssuerGroup: "foo.io", }, expectedErr: false, }, @@ -614,9 +622,13 @@ func TestIssuingController(t *testing.T) { }, }, expSecretUpdateDataCall: &internal.SecretData{ - Certificate: exampleBundle.CertificateRequestReady.Status.Certificate, - PrivateKey: exampleBundle.PrivateKeyBytes, - CA: nil, + Certificate: exampleBundle.CertificateRequestReady.Status.Certificate, + PrivateKey: exampleBundle.PrivateKeyBytes, + CA: nil, + CertificateName: "test", + IssuerName: "ca-issuer", + IssuerKind: "Issuer", + IssuerGroup: "foo.io", }, expectedErr: false, }, @@ -625,7 +637,7 @@ func TestIssuingController(t *testing.T) { builder: &testpkg.Builder{ CertManagerObjects: []runtime.Object{ gen.CertificateFrom(issuingCert, gen.SetCertificateLastFailureTime(metaFixedClockStart), - gen.SetCertificateIssuanceAttempts(pointer.Int(4))), + gen.SetCertificateIssuanceAttempts(ptr.To(4))), gen.CertificateRequestFrom(exampleBundle.CertificateRequestReady, gen.AddCertificateRequestAnnotations(map[string]string{ cmapi.CertificateRequestRevisionAnnotationKey: "2", // Current Certificate revision=1 @@ -668,9 +680,13 @@ func TestIssuingController(t *testing.T) { }, }, expSecretUpdateDataCall: &internal.SecretData{ - Certificate: exampleBundle.CertificateRequestReady.Status.Certificate, - PrivateKey: exampleBundle.PrivateKeyBytes, - CA: nil, + Certificate: exampleBundle.CertificateRequestReady.Status.Certificate, + PrivateKey: exampleBundle.PrivateKeyBytes, + CA: nil, + CertificateName: "test", + IssuerName: "ca-issuer", + IssuerKind: "Issuer", + IssuerGroup: "foo.io", }, expectedErr: false, }, @@ -705,9 +721,10 @@ func TestIssuingController(t *testing.T) { }, }, expSecretUpdateDataCall: &internal.SecretData{ - Certificate: exampleBundle.LocalTemporaryCertificateBytes, - PrivateKey: exampleBundle.PrivateKeyBytes, - CA: nil, + Certificate: exampleBundle.LocalTemporaryCertificateBytes, + PrivateKey: exampleBundle.PrivateKeyBytes, + CA: nil, + CertificateName: "test", }, expectedErr: false, }, @@ -753,9 +770,10 @@ func TestIssuingController(t *testing.T) { }, }, expSecretUpdateDataCall: &internal.SecretData{ - Certificate: exampleBundle.LocalTemporaryCertificateBytes, - PrivateKey: exampleBundle.PrivateKeyBytes, - CA: nil, + Certificate: exampleBundle.LocalTemporaryCertificateBytes, + PrivateKey: exampleBundle.PrivateKeyBytes, + CA: nil, + CertificateName: "test", }, expectedErr: false, }, @@ -850,9 +868,10 @@ func TestIssuingController(t *testing.T) { }, }, expSecretUpdateDataCall: &internal.SecretData{ - Certificate: exampleBundle.LocalTemporaryCertificateBytes, - PrivateKey: exampleBundle.PrivateKeyBytes, - CA: nil, + Certificate: exampleBundle.LocalTemporaryCertificateBytes, + PrivateKey: exampleBundle.PrivateKeyBytes, + CA: nil, + CertificateName: "test", }, expectedErr: false, }, @@ -945,9 +964,13 @@ func TestIssuingController(t *testing.T) { }, }, expSecretUpdateDataCall: &internal.SecretData{ - Certificate: exampleBundle.CertificateRequestReady.Status.Certificate, - PrivateKey: exampleBundle.PrivateKeyBytes, - CA: nil, + Certificate: exampleBundle.CertificateRequestReady.Status.Certificate, + PrivateKey: exampleBundle.PrivateKeyBytes, + CA: nil, + CertificateName: "test", + IssuerName: "ca-issuer", + IssuerKind: "Issuer", + IssuerGroup: "foo.io", }, expectedErr: false, }, @@ -1000,7 +1023,7 @@ func TestIssuingController(t *testing.T) { ObservedGeneration: 3, }), gen.SetCertificateLastFailureTime(metaFixedClockStart), - gen.SetCertificateIssuanceAttempts(pointer.Int(1)), + gen.SetCertificateIssuanceAttempts(ptr.To(1)), ), )), }, @@ -1010,7 +1033,7 @@ func TestIssuingController(t *testing.T) { }, expectedErr: false, }, - "if certificate is in Issuing state, one CertificateRequest, but has been denied, report denial and set last failed time and issuance attempts": { + "if certificate is in Issuing state, one CertificateRequest without Ready condition, but with Denied condition, report denial and set last failed time and issuance attempts": { certificate: exampleBundle.Certificate, builder: &testpkg.Builder{ CertManagerObjects: []runtime.Object{ @@ -1052,7 +1075,7 @@ func TestIssuingController(t *testing.T) { ObservedGeneration: 3, }), gen.SetCertificateLastFailureTime(metaFixedClockStart), - gen.SetCertificateIssuanceAttempts(pointer.Int(1)), + gen.SetCertificateIssuanceAttempts(ptr.To(1)), ), )), }, @@ -1062,6 +1085,290 @@ func TestIssuingController(t *testing.T) { }, expectedErr: false, }, + "if certificate is in Issuing state, one CertificateRequest with a pending Ready condition and a Denied condition, report denial and set last failed time and issuance attempts": { + certificate: exampleBundle.Certificate, + builder: &testpkg.Builder{ + CertManagerObjects: []runtime.Object{ + gen.CertificateFrom(issuingCert), + gen.CertificateRequestFrom(exampleBundle.CertificateRequest, + gen.AddCertificateRequestAnnotations(map[string]string{ + cmapi.CertificateRequestRevisionAnnotationKey: "2", // Current Certificate revision=1 + }), + gen.SetCertificateRequestStatusCondition(cmapi.CertificateRequestCondition{ + Type: cmapi.CertificateRequestConditionDenied, + Status: cmmeta.ConditionTrue, + Reason: "DeniedReason", + Message: "The certificate request has been denied", + }), + gen.SetCertificateRequestStatusCondition(cmapi.CertificateRequestCondition{ + Type: cmapi.CertificateRequestConditionReady, + Status: cmmeta.ConditionFalse, + Reason: "Pending", + Message: "The certificate request is pending", + }), + )}, + KubeObjects: []runtime.Object{ + &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: nextPrivateKeySecretName, + Namespace: exampleBundle.Certificate.Namespace, + }, + Data: map[string][]byte{ + corev1.TLSPrivateKeyKey: exampleBundle.PrivateKeyBytes, + }, + }, + }, + ExpectedActions: []testpkg.Action{ + testpkg.NewAction(coretesting.NewUpdateSubresourceAction( + cmapi.SchemeGroupVersion.WithResource("certificates"), + "status", + exampleBundle.Certificate.Namespace, + gen.CertificateFrom(exampleBundle.Certificate, + gen.SetCertificateStatusCondition(cmapi.CertificateCondition{ + Type: cmapi.CertificateConditionIssuing, + Status: cmmeta.ConditionFalse, + Reason: "DeniedReason", + Message: "The certificate request has failed to complete and will be retried: The certificate request has been denied", + LastTransitionTime: &metaFixedClockStart, + ObservedGeneration: 3, + }), + gen.SetCertificateLastFailureTime(metaFixedClockStart), + gen.SetCertificateIssuanceAttempts(ptr.To(1)), + ), + )), + }, + ExpectedEvents: []string{ + "Warning DeniedReason The certificate request has failed to complete and will be retried: The certificate request has been denied", + }, + }, + expectedErr: false, + }, + "if certificate is in Issuing state, one CertificateRequest that has been issued, but also has a Denied condition, report denial and set last failed time and issuance attempts": { + certificate: exampleBundle.Certificate, + builder: &testpkg.Builder{ + CertManagerObjects: []runtime.Object{ + gen.CertificateFrom(issuingCert), + gen.CertificateRequestFrom(exampleBundle.CertificateRequest, + gen.AddCertificateRequestAnnotations(map[string]string{ + cmapi.CertificateRequestRevisionAnnotationKey: "2", // Current Certificate revision=1 + }), + gen.SetCertificateRequestStatusCondition(cmapi.CertificateRequestCondition{ + Type: cmapi.CertificateRequestConditionDenied, + Status: cmmeta.ConditionTrue, + Reason: "DeniedReason", + Message: "The certificate request has been denied", + }), + gen.SetCertificateRequestStatusCondition(cmapi.CertificateRequestCondition{ + Type: cmapi.CertificateRequestConditionReady, + Status: cmmeta.ConditionTrue, + Reason: "Issued", + Message: "The certificate request has been issued", + }), + )}, + KubeObjects: []runtime.Object{ + &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: nextPrivateKeySecretName, + Namespace: exampleBundle.Certificate.Namespace, + }, + Data: map[string][]byte{ + corev1.TLSPrivateKeyKey: exampleBundle.PrivateKeyBytes, + }, + }, + }, + ExpectedActions: []testpkg.Action{ + testpkg.NewAction(coretesting.NewUpdateSubresourceAction( + cmapi.SchemeGroupVersion.WithResource("certificates"), + "status", + exampleBundle.Certificate.Namespace, + gen.CertificateFrom(exampleBundle.Certificate, + gen.SetCertificateStatusCondition(cmapi.CertificateCondition{ + Type: cmapi.CertificateConditionIssuing, + Status: cmmeta.ConditionFalse, + Reason: "DeniedReason", + Message: "The certificate request has failed to complete and will be retried: The certificate request has been denied", + LastTransitionTime: &metaFixedClockStart, + ObservedGeneration: 3, + }), + gen.SetCertificateLastFailureTime(metaFixedClockStart), + gen.SetCertificateIssuanceAttempts(ptr.To(1)), + ), + )), + }, + ExpectedEvents: []string{ + "Warning DeniedReason The certificate request has failed to complete and will be retried: The certificate request has been denied", + }, + }, + expectedErr: false, + }, + "if certificate is in Issuing state, one CertificateRequest that has no ready condition and has been marked as invalid, report error and set last failed time and issuance attempts": { + certificate: exampleBundle.Certificate, + builder: &testpkg.Builder{ + CertManagerObjects: []runtime.Object{ + gen.CertificateFrom(issuingCert), + gen.CertificateRequestFrom(exampleBundle.CertificateRequest, + gen.AddCertificateRequestAnnotations(map[string]string{ + cmapi.CertificateRequestRevisionAnnotationKey: "2", // Current Certificate revision=1 + }), + gen.SetCertificateRequestStatusCondition(cmapi.CertificateRequestCondition{ + Type: cmapi.CertificateRequestConditionInvalidRequest, + Status: cmmeta.ConditionTrue, + Reason: "InvalidRequest", + Message: "The certificate request is invalid", + }), + )}, + KubeObjects: []runtime.Object{ + &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: nextPrivateKeySecretName, + Namespace: exampleBundle.Certificate.Namespace, + }, + Data: map[string][]byte{ + corev1.TLSPrivateKeyKey: exampleBundle.PrivateKeyBytes, + }, + }, + }, + ExpectedActions: []testpkg.Action{ + testpkg.NewAction(coretesting.NewUpdateSubresourceAction( + cmapi.SchemeGroupVersion.WithResource("certificates"), + "status", + exampleBundle.Certificate.Namespace, + gen.CertificateFrom(exampleBundle.Certificate, + gen.SetCertificateStatusCondition(cmapi.CertificateCondition{ + Type: cmapi.CertificateConditionIssuing, + Status: cmmeta.ConditionFalse, + Reason: "InvalidRequest", + Message: "The certificate request has failed to complete and will be retried: The certificate request is invalid", + LastTransitionTime: &metaFixedClockStart, + ObservedGeneration: 3, + }), + gen.SetCertificateLastFailureTime(metaFixedClockStart), + gen.SetCertificateIssuanceAttempts(ptr.To(1)), + ), + )), + }, + ExpectedEvents: []string{ + "Warning InvalidRequest The certificate request has failed to complete and will be retried: The certificate request is invalid", + }, + }, + expectedErr: false, + }, + "if certificate is in Issuing state, one CertificateRequest that has a pending ready condition and has been marked as invalid, report error and set last failed time and issuance attempts": { + certificate: exampleBundle.Certificate, + builder: &testpkg.Builder{ + CertManagerObjects: []runtime.Object{ + gen.CertificateFrom(issuingCert), + gen.CertificateRequestFrom(exampleBundle.CertificateRequest, + gen.AddCertificateRequestAnnotations(map[string]string{ + cmapi.CertificateRequestRevisionAnnotationKey: "2", // Current Certificate revision=1 + }), + gen.SetCertificateRequestStatusCondition(cmapi.CertificateRequestCondition{ + Type: cmapi.CertificateRequestConditionInvalidRequest, + Status: cmmeta.ConditionTrue, + Reason: "InvalidRequest", + Message: "The certificate request is invalid", + }), + gen.SetCertificateRequestStatusCondition(cmapi.CertificateRequestCondition{ + Type: cmapi.CertificateRequestConditionReady, + Status: cmmeta.ConditionFalse, + Reason: "Pending", + Message: "The certificate request is being issued", + }), + )}, + KubeObjects: []runtime.Object{ + &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: nextPrivateKeySecretName, + Namespace: exampleBundle.Certificate.Namespace, + }, + Data: map[string][]byte{ + corev1.TLSPrivateKeyKey: exampleBundle.PrivateKeyBytes, + }, + }, + }, + ExpectedActions: []testpkg.Action{ + testpkg.NewAction(coretesting.NewUpdateSubresourceAction( + cmapi.SchemeGroupVersion.WithResource("certificates"), + "status", + exampleBundle.Certificate.Namespace, + gen.CertificateFrom(exampleBundle.Certificate, + gen.SetCertificateStatusCondition(cmapi.CertificateCondition{ + Type: cmapi.CertificateConditionIssuing, + Status: cmmeta.ConditionFalse, + Reason: "InvalidRequest", + Message: "The certificate request has failed to complete and will be retried: The certificate request is invalid", + LastTransitionTime: &metaFixedClockStart, + ObservedGeneration: 3, + }), + gen.SetCertificateLastFailureTime(metaFixedClockStart), + gen.SetCertificateIssuanceAttempts(ptr.To(1)), + ), + )), + }, + ExpectedEvents: []string{ + "Warning InvalidRequest The certificate request has failed to complete and will be retried: The certificate request is invalid", + }, + }, + expectedErr: false, + }, + "if certificate is in Issuing state, one CertificateRequest that has been issued, but has also been marked as invalid, report error and set last failed time and issuance attempts": { + certificate: exampleBundle.Certificate, + builder: &testpkg.Builder{ + CertManagerObjects: []runtime.Object{ + gen.CertificateFrom(issuingCert), + gen.CertificateRequestFrom(exampleBundle.CertificateRequest, + gen.AddCertificateRequestAnnotations(map[string]string{ + cmapi.CertificateRequestRevisionAnnotationKey: "2", // Current Certificate revision=1 + }), + gen.SetCertificateRequestStatusCondition(cmapi.CertificateRequestCondition{ + Type: cmapi.CertificateRequestConditionInvalidRequest, + Status: cmmeta.ConditionTrue, + Reason: "InvalidRequest", + Message: "The certificate request is invalid", + }), + gen.SetCertificateRequestStatusCondition(cmapi.CertificateRequestCondition{ + Type: cmapi.CertificateRequestConditionReady, + Status: cmmeta.ConditionTrue, + Reason: "Issued", + Message: "The certificate request has been issued", + }), + )}, + KubeObjects: []runtime.Object{ + &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: nextPrivateKeySecretName, + Namespace: exampleBundle.Certificate.Namespace, + }, + Data: map[string][]byte{ + corev1.TLSPrivateKeyKey: exampleBundle.PrivateKeyBytes, + }, + }, + }, + ExpectedActions: []testpkg.Action{ + testpkg.NewAction(coretesting.NewUpdateSubresourceAction( + cmapi.SchemeGroupVersion.WithResource("certificates"), + "status", + exampleBundle.Certificate.Namespace, + gen.CertificateFrom(exampleBundle.Certificate, + gen.SetCertificateStatusCondition(cmapi.CertificateCondition{ + Type: cmapi.CertificateConditionIssuing, + Status: cmmeta.ConditionFalse, + Reason: "InvalidRequest", + Message: "The certificate request has failed to complete and will be retried: The certificate request is invalid", + LastTransitionTime: &metaFixedClockStart, + ObservedGeneration: 3, + }), + gen.SetCertificateLastFailureTime(metaFixedClockStart), + gen.SetCertificateIssuanceAttempts(ptr.To(1)), + ), + )), + }, + ExpectedEvents: []string{ + "Warning InvalidRequest The certificate request has failed to complete and will be retried: The certificate request is invalid", + }, + }, + expectedErr: false, + }, } for name, test := range tests { @@ -1090,13 +1397,10 @@ func TestIssuingController(t *testing.T) { test.builder.Start() - key, err := cache.MetaNamespaceKeyFunc(test.certificate) - if err != nil { - t.Errorf("failed to build meta namespace key from certificate: %s", err) - t.FailNow() - } - - err = w.controller.ProcessItem(context.Background(), key) + err = w.controller.ProcessItem(t.Context(), types.NamespacedName{ + Namespace: test.certificate.Namespace, + Name: test.certificate.Name, + }) if err != nil && !test.expectedErr { t.Errorf("expected to not get an error, but got: %v", err) } diff --git a/pkg/controller/certificates/issuing/secret_manager.go b/pkg/controller/certificates/issuing/secret_manager.go index 319bac9932d..7a0fc98113f 100644 --- a/pkg/controller/certificates/issuing/secret_manager.go +++ b/pkg/controller/certificates/issuing/secret_manager.go @@ -55,7 +55,7 @@ func (c *controller) ensureSecretData(ctx context.Context, log logr.Logger, crt log = log.WithValues("secret", secret.Name) // If there is no certificate or private key data available at the target - // Secret then exit early. The absense of these keys should cause an issuance + // Secret then exit early. The absence of these keys should cause an issuance // of the Certificate, so there is no need to run post issuance checks. if secret.Data == nil || len(secret.Data[corev1.TLSCertKey]) == 0 || @@ -66,9 +66,13 @@ func (c *controller) ensureSecretData(ctx context.Context, log logr.Logger, crt } data := internal.SecretData{ - PrivateKey: secret.Data[corev1.TLSPrivateKeyKey], - Certificate: secret.Data[corev1.TLSCertKey], - CA: secret.Data[cmmeta.TLSCAKey], + PrivateKey: secret.Data[corev1.TLSPrivateKeyKey], + Certificate: secret.Data[corev1.TLSCertKey], + CA: secret.Data[cmmeta.TLSCAKey], + CertificateName: secret.Annotations[cmapi.CertificateNameKey], + IssuerName: secret.Annotations[cmapi.IssuerNameAnnotationKey], + IssuerKind: secret.Annotations[cmapi.IssuerKindAnnotationKey], + IssuerGroup: secret.Annotations[cmapi.IssuerGroupAnnotationKey], } // Check whether the Certificate's Secret has correct output format and @@ -81,11 +85,11 @@ func (c *controller) ensureSecretData(ctx context.Context, log logr.Logger, crt if isViolation { switch reason { case policies.InvalidCertificate, policies.ManagedFieldsParseError: - //An error here indicates that the managed fields are malformed and the - //decoder doesn't understand the managed fields on the Secret, or the - //signed certificate data could not be decoded. There is nothing more the - //controller can do here, so we exit nil so this controller doesn't end in - //an infinite loop. + // An error here indicates that the managed fields are malformed and the + // decoder doesn't understand the managed fields on the Secret, or the + // signed certificate data could not be decoded. There is nothing more the + // controller can do here, so we exit nil so this controller doesn't end in + // an infinite loop. log.Error(errors.New(message), "failed to determine whether the SecretTemplate matches Secret") return nil default: diff --git a/pkg/controller/certificates/issuing/secret_manager_test.go b/pkg/controller/certificates/issuing/secret_manager_test.go index 449c3d2984c..9944a0ab472 100644 --- a/pkg/controller/certificates/issuing/secret_manager_test.go +++ b/pkg/controller/certificates/issuing/secret_manager_test.go @@ -18,16 +18,16 @@ package issuing import ( "context" - "encoding/pem" "testing" "github.com/stretchr/testify/assert" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" - "k8s.io/utils/pointer" + "k8s.io/utils/ptr" "github.com/cert-manager/cert-manager/internal/controller/certificates/policies" + "github.com/cert-manager/cert-manager/internal/pem" cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" "github.com/cert-manager/cert-manager/pkg/controller/certificates/issuing/internal" @@ -40,7 +40,9 @@ func Test_ensureSecretData(t *testing.T) { pk := testcrypto.MustCreatePEMPrivateKey(t) cert := testcrypto.MustCreateCert(t, pk, &cmapi.Certificate{Spec: cmapi.CertificateSpec{CommonName: "test"}}) - block, _ := pem.Decode(pk) + + block, _, _ := pem.SafeDecodePrivateKey(pk) + pkDER := block.Bytes combinedPEM := append(append(pk, '\n'), cert...) @@ -48,7 +50,7 @@ func Test_ensureSecretData(t *testing.T) { // key that should be passed to ProcessItem. // if not set, the 'namespace/name' of the 'Certificate' field will be used. // if neither is set, the key will be "". - key string + key types.NamespacedName // cert is the optional cert to be loaded to fake clientset. cert *cmapi.Certificate @@ -67,15 +69,20 @@ func Test_ensureSecretData(t *testing.T) { expectedAction: false, }, "if 'key' is an invalid value, should do nothing and not error": { - key: "abc/def/ghi", + key: types.NamespacedName{ + Namespace: "abc", + Name: "def/ghi", + }, expectedAction: false, }, "if 'key' references a Certificate that doesn't exist, should do nothing and not error": { - key: "random-namespace/random-certificate", + key: types.NamespacedName{ + Namespace: "random-namespace", + Name: "random-certificate", + }, expectedAction: false, }, "if Certificate and Secret exists, but the Secret contains no certificate or private key data, do nothing": { - key: "test-namespace/test-name", cert: &cmapi.Certificate{ ObjectMeta: metav1.ObjectMeta{Namespace: "test-namespace", Name: "test-name"}, Spec: cmapi.CertificateSpec{ @@ -90,7 +97,6 @@ func Test_ensureSecretData(t *testing.T) { expectedAction: false, }, "if Certificate and Secret exists, but the Secret contains no certificate data, do nothing": { - key: "test-namespace/test-name", cert: &cmapi.Certificate{ ObjectMeta: metav1.ObjectMeta{Namespace: "test-namespace", Name: "test-name"}, Spec: cmapi.CertificateSpec{ @@ -107,7 +113,6 @@ func Test_ensureSecretData(t *testing.T) { expectedAction: false, }, "if Certificate and Secret exists, but the Secret contains no private key data, do nothing": { - key: "test-namespace/test-name", cert: &cmapi.Certificate{ ObjectMeta: metav1.ObjectMeta{Namespace: "test-namespace", Name: "test-name"}, Spec: cmapi.CertificateSpec{ @@ -124,7 +129,6 @@ func Test_ensureSecretData(t *testing.T) { expectedAction: false, }, "if Certificate and Secret exists, but the Certificate has a True Issuing condition, do nothing": { - key: "test-namespace/test-name", cert: &cmapi.Certificate{ ObjectMeta: metav1.ObjectMeta{Namespace: "test-namespace", Name: "test-name"}, Spec: cmapi.CertificateSpec{ @@ -145,7 +149,6 @@ func Test_ensureSecretData(t *testing.T) { expectedAction: false, }, "if Certificate exists without a Issuing condition, but Secret does not exist, do nothing": { - key: "test-namespace/test-name", cert: &cmapi.Certificate{ ObjectMeta: metav1.ObjectMeta{Namespace: "test-namespace", Name: "test-name"}, Spec: cmapi.CertificateSpec{ @@ -158,7 +161,6 @@ func Test_ensureSecretData(t *testing.T) { expectedAction: false, }, "if Certificate exists in a false Issuing condition, Secret exists and matches the SecretTemplate but no managed fields, should reconcile Secret": { - key: "test-namespace/test-name", cert: &cmapi.Certificate{ ObjectMeta: metav1.ObjectMeta{Namespace: "test-namespace", Name: "test-name"}, Spec: cmapi.CertificateSpec{ @@ -182,7 +184,6 @@ func Test_ensureSecretData(t *testing.T) { expectedAction: true, }, "if Certificate exists in a false Issuing condition, Secret exists and matches the SecretTemplate but the managed fields contains more than what is in the SecretTemplate, should reconcile Secret": { - key: "test-namespace/test-name", cert: &cmapi.Certificate{ ObjectMeta: metav1.ObjectMeta{Namespace: "test-namespace", Name: "test-name"}, Spec: cmapi.CertificateSpec{ @@ -202,10 +203,15 @@ func Test_ensureSecretData(t *testing.T) { FieldsV1: &metav1.FieldsV1{ Raw: []byte(`{"f:metadata": { "f:annotations": { + "f:cert-manager.io/common-name": {}, + "f:cert-manager.io/alt-names": {}, + "f:cert-manager.io/ip-sans": {}, + "f:cert-manager.io/uri-sans": {}, "f:foo": {}, "f:another-annotation": {} }, "f:labels": { + "f:controller.cert-manager.io/fao": {}, "f:abc": {}, "f:another-label": {} } @@ -221,7 +227,6 @@ func Test_ensureSecretData(t *testing.T) { expectedAction: true, }, "if Certificate exists in a false Issuing condition, Secret exists and matches the SecretTemplate but the managed fields are managed by another manager, should reconcile Secret": { - key: "test-namespace/test-name", cert: &cmapi.Certificate{ ObjectMeta: metav1.ObjectMeta{Namespace: "test-namespace", Name: "test-name"}, Spec: cmapi.CertificateSpec{ @@ -241,9 +246,14 @@ func Test_ensureSecretData(t *testing.T) { FieldsV1: &metav1.FieldsV1{ Raw: []byte(`{"f:metadata": { "f:annotations": { + "f:cert-manager.io/common-name": {}, + "f:cert-manager.io/alt-names": {}, + "f:cert-manager.io/ip-sans": {}, + "f:cert-manager.io/uri-sans": {}, "f:foo": {} }, "f:labels": { + "f:controller.cert-manager.io/fao": {}, "f:abc": {} } }}`), @@ -257,13 +267,13 @@ func Test_ensureSecretData(t *testing.T) { }, expectedAction: true, }, - "if Certificate exists in a false Issuing condition, Secret exists and matches the SecretTemplate with the correct managed fields, should do nothing": { - key: "test-namespace/test-name", + "if Certificate exists in a false Issuing condition, Secret exists and matches the SecretTemplate with the correct managed fields and base labels, should do nothing": { cert: &cmapi.Certificate{ ObjectMeta: metav1.ObjectMeta{Namespace: "test-namespace", Name: "test-name"}, Spec: cmapi.CertificateSpec{ - SecretName: "test-secret", - SecretTemplate: &cmapi.CertificateSecretTemplate{Annotations: map[string]string{"foo": "bar"}, Labels: map[string]string{"abc": "123"}}, + SecretName: "test-secret", + SecretTemplate: &cmapi.CertificateSecretTemplate{Annotations: map[string]string{"foo": "bar"}, + Labels: map[string]string{"abc": "123"}}, }, Status: cmapi.CertificateStatus{ Conditions: []cmapi.CertificateCondition{{Type: cmapi.CertificateConditionIssuing, Status: cmmeta.ConditionFalse}}, @@ -272,15 +282,21 @@ func Test_ensureSecretData(t *testing.T) { secret: &corev1.Secret{ ObjectMeta: metav1.ObjectMeta{ Namespace: "test-namespace", Name: "test-secret", - Annotations: map[string]string{"foo": "bar"}, Labels: map[string]string{"abc": "123"}, + Annotations: map[string]string{"foo": "bar"}, + Labels: map[string]string{"abc": "123", cmapi.PartOfCertManagerControllerLabelKey: "true"}, ManagedFields: []metav1.ManagedFieldsEntry{{ Manager: fieldManager, FieldsV1: &metav1.FieldsV1{ Raw: []byte(`{"f:metadata": { "f:annotations": { + "f:cert-manager.io/common-name": {}, + "f:cert-manager.io/alt-names": {}, + "f:cert-manager.io/ip-sans": {}, + "f:cert-manager.io/uri-sans": {}, "f:foo": {} }, "f:labels": { + "f:controller.cert-manager.io/fao": {}, "f:abc": {} } }}`), @@ -295,7 +311,54 @@ func Test_ensureSecretData(t *testing.T) { expectedAction: false, }, "if Certificate exists in a false Issuing condition, Secret exists but does not match SecretTemplate, should apply the Labels and Annotations": { - key: "test-namespace/test-name", + cert: &cmapi.Certificate{ + ObjectMeta: metav1.ObjectMeta{Namespace: "test-namespace", Name: "test-name"}, + Spec: cmapi.CertificateSpec{ + SecretName: "test-secret", + SecretTemplate: &cmapi.CertificateSecretTemplate{Annotations: map[string]string{"foo": "bar"}, Labels: map[string]string{"abc": "123"}}, + }, + Status: cmapi.CertificateStatus{ + Conditions: []cmapi.CertificateCondition{ + {Type: cmapi.CertificateConditionIssuing, Status: cmmeta.ConditionFalse}, + {Type: cmapi.CertificateConditionIssuing, Status: cmmeta.ConditionFalse}, + }, + }, + }, + secret: &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{Namespace: "test-namespace", Name: "test-secret", + Labels: map[string]string{cmapi.PartOfCertManagerControllerLabelKey: "true"}}, + Data: map[string][]byte{ + "tls.crt": cert, + "tls.key": pk, + }, + }, + expectedAction: true, + }, + "if Certificate exists in a false Issuing condition, Secret exists but is missing the required label, apply the label": { + cert: &cmapi.Certificate{ + ObjectMeta: metav1.ObjectMeta{Namespace: "test-namespace", Name: "test-name"}, + Spec: cmapi.CertificateSpec{ + SecretName: "test-secret", + SecretTemplate: &cmapi.CertificateSecretTemplate{Annotations: map[string]string{"foo": "bar"}, Labels: map[string]string{"abc": "123"}}, + }, + Status: cmapi.CertificateStatus{ + Conditions: []cmapi.CertificateCondition{ + {Type: cmapi.CertificateConditionIssuing, Status: cmmeta.ConditionFalse}, + {Type: cmapi.CertificateConditionIssuing, Status: cmmeta.ConditionFalse}, + }, + }, + }, + secret: &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{Namespace: "test-namespace", Name: "test-secret", + Labels: map[string]string{"foo": "bar"}}, + Data: map[string][]byte{ + "tls.crt": cert, + "tls.key": pk, + }, + }, + expectedAction: true, + }, + "if Certificate exists in a false Issuing condition, Secret exists with some labels, but is missing the required label, apply the label": { cert: &cmapi.Certificate{ ObjectMeta: metav1.ObjectMeta{Namespace: "test-namespace", Name: "test-name"}, Spec: cmapi.CertificateSpec{ @@ -319,7 +382,6 @@ func Test_ensureSecretData(t *testing.T) { expectedAction: true, }, "if Certificate with combined pem and Secret exists, but the Secret doesn't have combined pem, should apply the combined pem": { - key: "test-namespace/test-name", cert: &cmapi.Certificate{ ObjectMeta: metav1.ObjectMeta{Namespace: "test-namespace", Name: "test-name"}, Spec: cmapi.CertificateSpec{ @@ -330,7 +392,8 @@ func Test_ensureSecretData(t *testing.T) { }, }, secret: &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{Namespace: "test-namespace", Name: "test-secret"}, + ObjectMeta: metav1.ObjectMeta{Namespace: "test-namespace", Name: "test-secret", + Labels: map[string]string{cmapi.PartOfCertManagerControllerLabelKey: "true"}}, Data: map[string][]byte{ "tls.crt": cert, "tls.key": pk, @@ -339,7 +402,6 @@ func Test_ensureSecretData(t *testing.T) { expectedAction: true, }, "if Certificate with der and Secret exists, but the Secret doesn't have der, should apply the der": { - key: "test-namespace/test-name", cert: &cmapi.Certificate{ ObjectMeta: metav1.ObjectMeta{Namespace: "test-namespace", Name: "test-name"}, Spec: cmapi.CertificateSpec{ @@ -350,7 +412,8 @@ func Test_ensureSecretData(t *testing.T) { }, }, secret: &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{Namespace: "test-namespace", Name: "test-secret"}, + ObjectMeta: metav1.ObjectMeta{Namespace: "test-namespace", Name: "test-secret", + Labels: map[string]string{cmapi.PartOfCertManagerControllerLabelKey: "true"}}, Data: map[string][]byte{ "tls.crt": cert, "tls.key": pk, @@ -359,7 +422,6 @@ func Test_ensureSecretData(t *testing.T) { expectedAction: true, }, "if Certificate with combined pem and der, and Secret exists, but the Secret doesn't have combined pem or der, should apply the combined pem and der": { - key: "test-namespace/test-name", cert: &cmapi.Certificate{ ObjectMeta: metav1.ObjectMeta{Namespace: "test-namespace", Name: "test-name"}, Spec: cmapi.CertificateSpec{ @@ -371,7 +433,8 @@ func Test_ensureSecretData(t *testing.T) { }, }, secret: &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{Namespace: "test-namespace", Name: "test-secret"}, + ObjectMeta: metav1.ObjectMeta{Namespace: "test-namespace", Name: "test-secret", + Labels: map[string]string{cmapi.PartOfCertManagerControllerLabelKey: "true"}}, Data: map[string][]byte{ "tls.crt": cert, "tls.key": pk, @@ -380,7 +443,6 @@ func Test_ensureSecretData(t *testing.T) { expectedAction: true, }, "if Certificate with combined pem and der, and Secret exists with combined pem and der with managed fields, should do nothing": { - key: "test-namespace/test-name", cert: &cmapi.Certificate{ ObjectMeta: metav1.ObjectMeta{Namespace: "test-namespace", Name: "test-name"}, Spec: cmapi.CertificateSpec{ @@ -393,13 +455,31 @@ func Test_ensureSecretData(t *testing.T) { }, secret: &corev1.Secret{ ObjectMeta: metav1.ObjectMeta{Namespace: "test-namespace", Name: "test-secret", + Labels: map[string]string{cmapi.PartOfCertManagerControllerLabelKey: "true"}, ManagedFields: []metav1.ManagedFieldsEntry{{ Manager: fieldManager, FieldsV1: &metav1.FieldsV1{ - Raw: []byte(`{"f:data": { - "f:tls-combined.pem": {}, - "f:key.der": {} - }}`), + Raw: []byte(` + { + "f:metadata": { + "f:labels": { + "f:controller.cert-manager.io/fao": {} + }, + "f:annotations": { + "f:cert-manager.io/common-name": {}, + "f:cert-manager.io/alt-names": {}, + "f:cert-manager.io/ip-sans": {}, + "f:cert-manager.io/uri-sans": {} + }, + "f:ownerReferences": { + "k:{\"uid\":\"uid-123\"}": {} + } + }, + "f:data": { + "f:tls-combined.pem": {}, + "f:key.der": {} + } + }`), }, }}, }, @@ -413,7 +493,6 @@ func Test_ensureSecretData(t *testing.T) { expectedAction: false, }, "if Certificate with no combined pem or der, and Secret exists with combined pem and der managed by field manager, should apply to remove them": { - key: "test-namespace/test-name", cert: &cmapi.Certificate{ ObjectMeta: metav1.ObjectMeta{Namespace: "test-namespace", Name: "test-name"}, Spec: cmapi.CertificateSpec{ @@ -423,13 +502,31 @@ func Test_ensureSecretData(t *testing.T) { }, secret: &corev1.Secret{ ObjectMeta: metav1.ObjectMeta{Namespace: "test-namespace", Name: "test-secret", + Labels: map[string]string{cmapi.PartOfCertManagerControllerLabelKey: "true"}, ManagedFields: []metav1.ManagedFieldsEntry{{ Manager: fieldManager, FieldsV1: &metav1.FieldsV1{ - Raw: []byte(`{"f:data": { - "f:tls-combined.pem": {}, - "f:key.der": {} - }}`), + Raw: []byte(` + { + "f:metadata": { + "f:labels": { + "f:controller.cert-manager.io/fao": {} + }, + "f:annotations": { + "f:cert-manager.io/common-name": {}, + "f:cert-manager.io/alt-names": {}, + "f:cert-manager.io/ip-sans": {}, + "f:cert-manager.io/uri-sans": {} + }, + "f:ownerReferences": { + "k:{\"uid\":\"uid-123\"}": {} + } + }, + "f:data": { + "f:tls-combined.pem": {}, + "f:key.der": {} + } + }`), }, }}, }, @@ -444,7 +541,6 @@ func Test_ensureSecretData(t *testing.T) { }, "enabledOwnerRef=false if Secret has owner reference to Certificate owned by field manager, expect action": { - key: "test-namespace/test-name", enableOwnerRef: false, cert: &cmapi.Certificate{ ObjectMeta: metav1.ObjectMeta{Namespace: "test-namespace", Name: "test-name", UID: types.UID("uid-123")}, @@ -452,13 +548,24 @@ func Test_ensureSecretData(t *testing.T) { }, secret: &corev1.Secret{ ObjectMeta: metav1.ObjectMeta{Namespace: "test-namespace", Name: "test-secret", + Labels: map[string]string{cmapi.PartOfCertManagerControllerLabelKey: "true"}, ManagedFields: []metav1.ManagedFieldsEntry{ {Manager: fieldManager, FieldsV1: &metav1.FieldsV1{ Raw: []byte(` - {"f:metadata": { + {"f:metadata": { + "f:labels": { + "f:controller.cert-manager.io/fao": {} + }, + "f:annotations": { + "f:cert-manager.io/common-name": {}, + "f:cert-manager.io/alt-names": {}, + "f:cert-manager.io/ip-sans": {}, + "f:cert-manager.io/uri-sans": {} + }, "f:ownerReferences": { - "k:{\"uid\":\"uid-123\"}": {} - }}}`), + "k:{\"uid\":\"uid-123\"}": {} + } + }}`), }}, }, }, @@ -467,7 +574,6 @@ func Test_ensureSecretData(t *testing.T) { expectedAction: true, }, "enabledOwnerRef=true if Secret has owner reference to Certificate owned by field manager, expect no action": { - key: "test-namespace/test-name", enableOwnerRef: true, cert: &cmapi.Certificate{ ObjectMeta: metav1.ObjectMeta{Namespace: "test-namespace", Name: "test-name", UID: types.UID("uid-123")}, @@ -475,16 +581,27 @@ func Test_ensureSecretData(t *testing.T) { }, secret: &corev1.Secret{ ObjectMeta: metav1.ObjectMeta{Namespace: "test-namespace", Name: "test-secret", + Labels: map[string]string{cmapi.PartOfCertManagerControllerLabelKey: "true"}, OwnerReferences: []metav1.OwnerReference{ - {APIVersion: "cert-manager.io/v1", Kind: "Certificate", Name: "test-name", UID: types.UID("uid-123"), Controller: pointer.Bool(true), BlockOwnerDeletion: pointer.Bool(true)}, + {APIVersion: "cert-manager.io/v1", Kind: "Certificate", Name: "test-name", UID: types.UID("uid-123"), Controller: ptr.To(true), BlockOwnerDeletion: ptr.To(true)}, }, ManagedFields: []metav1.ManagedFieldsEntry{ {Manager: fieldManager, FieldsV1: &metav1.FieldsV1{ Raw: []byte(` - {"f:metadata": { + {"f:metadata": { + "f:labels": { + "f:controller.cert-manager.io/fao": {} + }, + "f:annotations": { + "f:cert-manager.io/common-name": {}, + "f:cert-manager.io/alt-names": {}, + "f:cert-manager.io/ip-sans": {}, + "f:cert-manager.io/uri-sans": {} + }, "f:ownerReferences": { - "k:{\"uid\":\"uid-123\"}": {} - }}}`), + "k:{\"uid\":\"uid-123\"}": {} + } + }}`), }}, }, }, @@ -492,6 +609,524 @@ func Test_ensureSecretData(t *testing.T) { }, expectedAction: false, }, + "refresh secrets when keystore is not defined and the secret has keystore/truststore fields": { + enableOwnerRef: true, + cert: &cmapi.Certificate{ + ObjectMeta: metav1.ObjectMeta{Namespace: "test-namespace", Name: "test-name", UID: types.UID("uid-234")}, + Spec: cmapi.CertificateSpec{ + CommonName: "example.com", + IssuerRef: cmmeta.IssuerReference{ + Name: "testissuer", + Kind: "IssuerKind", + Group: "group.example.com", + }, + SecretName: "test-secret", + }}, + secret: &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{Name: "test-secret", Namespace: "test-namespace", + Annotations: map[string]string{ + cmapi.IssuerNameAnnotationKey: "testissuer", + cmapi.IssuerKindAnnotationKey: "IssuerKind", + cmapi.IssuerGroupAnnotationKey: "group.example.com", + }, + Labels: map[string]string{cmapi.PartOfCertManagerControllerLabelKey: "true"}, + OwnerReferences: []metav1.OwnerReference{ + {APIVersion: "cert-manager.io/v1", Kind: "Certificate", Name: "test-name", UID: types.UID("uid-234"), Controller: ptr.To(true), BlockOwnerDeletion: ptr.To(true)}, + }, + ManagedFields: []metav1.ManagedFieldsEntry{ + {Manager: fieldManager, FieldsV1: &metav1.FieldsV1{ + Raw: []byte(` + {"f:metadata": { + "f:labels": { + "f:controller.cert-manager.io/fao": {} + }, + "f:annotations": { + "f:cert-manager.io/common-name": {}, + "f:cert-manager.io/alt-names": {}, + "f:cert-manager.io/ip-sans": {}, + "f:cert-manager.io/uri-sans": {} + }, + "f:ownerReferences": { + "k:{\"uid\":\"uid-123\"}": {} + } + }}`), + }}, + }, + }, + Data: map[string][]byte{ + corev1.TLSPrivateKeyKey: pk, + corev1.TLSCertKey: testcrypto.MustCreateCert(t, pk, + &cmapi.Certificate{Spec: cmapi.CertificateSpec{CommonName: "example.com"}}, + ), + cmapi.PKCS12TruststoreKey: []byte("SomeData"), + }, + }, + expectedAction: true, + }, + "refresh secrets when JKS keystore is defined and the secret does not have keystore/truststore fields": { + enableOwnerRef: true, + cert: &cmapi.Certificate{ + ObjectMeta: metav1.ObjectMeta{Namespace: "test-namespace", Name: "test-name", UID: types.UID("uid-123")}, + Spec: cmapi.CertificateSpec{ + CommonName: "example.com", + IssuerRef: cmmeta.IssuerReference{ + Name: "testissuer", + Kind: "IssuerKind", + Group: "group.example.com", + }, + SecretName: "something", + Keystores: &cmapi.CertificateKeystores{ + JKS: &cmapi.JKSKeystore{ + Create: true, + }, + }, + }}, + secret: &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{Name: "something", Namespace: "test-namespace", + Annotations: map[string]string{ + cmapi.IssuerNameAnnotationKey: "testissuer", + cmapi.IssuerKindAnnotationKey: "IssuerKind", + cmapi.IssuerGroupAnnotationKey: "group.example.com", + }, + Labels: map[string]string{cmapi.PartOfCertManagerControllerLabelKey: "true"}, + OwnerReferences: []metav1.OwnerReference{ + {APIVersion: "cert-manager.io/v1", Kind: "Certificate", Name: "test-name", UID: types.UID("uid-123"), Controller: ptr.To(true), BlockOwnerDeletion: ptr.To(true)}, + }, + ManagedFields: []metav1.ManagedFieldsEntry{ + {Manager: fieldManager, FieldsV1: &metav1.FieldsV1{ + Raw: []byte(` + {"f:metadata": { + "f:labels": { + "f:controller.cert-manager.io/fao": {} + }, + "f:annotations": { + "f:cert-manager.io/common-name": {}, + "f:cert-manager.io/alt-names": {}, + "f:cert-manager.io/ip-sans": {}, + "f:cert-manager.io/uri-sans": {} + }, + "f:ownerReferences": { + "k:{\"uid\":\"uid-123\"}": {} + } + }}`), + }}, + }, + }, + Data: map[string][]byte{ + corev1.TLSPrivateKeyKey: pk, + corev1.TLSCertKey: testcrypto.MustCreateCert(t, pk, + &cmapi.Certificate{Spec: cmapi.CertificateSpec{CommonName: "example.com"}}, + ), + }, + }, + expectedAction: true, + }, + "refresh secrets when JKS keystore is defined, create is disabled and the secret has keystore/truststore fields": { + enableOwnerRef: true, + cert: &cmapi.Certificate{ + ObjectMeta: metav1.ObjectMeta{Namespace: "test-namespace", Name: "test-name", UID: types.UID("uid-123")}, + Spec: cmapi.CertificateSpec{ + CommonName: "example.com", + IssuerRef: cmmeta.IssuerReference{ + Name: "testissuer", + Kind: "IssuerKind", + Group: "group.example.com", + }, + SecretName: "something", + Keystores: &cmapi.CertificateKeystores{ + JKS: &cmapi.JKSKeystore{ + Create: false, + }, + }, + }}, + secret: &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{Name: "something", Namespace: "test-namespace", + Annotations: map[string]string{ + cmapi.IssuerNameAnnotationKey: "testissuer", + cmapi.IssuerKindAnnotationKey: "IssuerKind", + cmapi.IssuerGroupAnnotationKey: "group.example.com", + }, + Labels: map[string]string{cmapi.PartOfCertManagerControllerLabelKey: "true"}, + OwnerReferences: []metav1.OwnerReference{ + {APIVersion: "cert-manager.io/v1", Kind: "Certificate", Name: "test-name", UID: types.UID("uid-123"), Controller: ptr.To(true), BlockOwnerDeletion: ptr.To(true)}, + }, + ManagedFields: []metav1.ManagedFieldsEntry{ + {Manager: fieldManager, FieldsV1: &metav1.FieldsV1{ + Raw: []byte(` + {"f:metadata": { + "f:labels": { + "f:controller.cert-manager.io/fao": {} + }, + "f:annotations": { + "f:cert-manager.io/common-name": {}, + "f:cert-manager.io/alt-names": {}, + "f:cert-manager.io/ip-sans": {}, + "f:cert-manager.io/uri-sans": {} + }, + "f:ownerReferences": { + "k:{\"uid\":\"uid-123\"}": {} + } + }}`), + }}, + }, + }, + Data: map[string][]byte{ + corev1.TLSPrivateKeyKey: pk, + corev1.TLSCertKey: testcrypto.MustCreateCert(t, pk, + &cmapi.Certificate{Spec: cmapi.CertificateSpec{CommonName: "example.com"}}, + ), + cmapi.JKSTruststoreKey: []byte("SomeData"), + }, + }, + expectedAction: true, + }, + "refresh secrets when JKS keystore is null and the secret has keystore/truststore fields": { + enableOwnerRef: true, + cert: &cmapi.Certificate{ + ObjectMeta: metav1.ObjectMeta{Namespace: "test-namespace", Name: "test-name", UID: types.UID("uid-123")}, + Spec: cmapi.CertificateSpec{ + CommonName: "example.com", + IssuerRef: cmmeta.IssuerReference{ + Name: "testissuer", + Kind: "IssuerKind", + Group: "group.example.com", + }, + SecretName: "something", + Keystores: &cmapi.CertificateKeystores{ + JKS: nil, + }, + }}, + secret: &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{Name: "something", Namespace: "test-namespace", + Annotations: map[string]string{ + cmapi.IssuerNameAnnotationKey: "testissuer", + cmapi.IssuerKindAnnotationKey: "IssuerKind", + cmapi.IssuerGroupAnnotationKey: "group.example.com", + }, + Labels: map[string]string{cmapi.PartOfCertManagerControllerLabelKey: "true"}, + OwnerReferences: []metav1.OwnerReference{ + {APIVersion: "cert-manager.io/v1", Kind: "Certificate", Name: "test-name", UID: types.UID("uid-123"), Controller: ptr.To(true), BlockOwnerDeletion: ptr.To(true)}, + }, + ManagedFields: []metav1.ManagedFieldsEntry{ + {Manager: fieldManager, FieldsV1: &metav1.FieldsV1{ + Raw: []byte(` + {"f:metadata": { + "f:labels": { + "f:controller.cert-manager.io/fao": {} + }, + "f:annotations": { + "f:cert-manager.io/common-name": {}, + "f:cert-manager.io/alt-names": {}, + "f:cert-manager.io/ip-sans": {}, + "f:cert-manager.io/uri-sans": {} + }, + "f:ownerReferences": { + "k:{\"uid\":\"uid-123\"}": {} + } + }}`), + }}, + }, + }, + Data: map[string][]byte{ + corev1.TLSPrivateKeyKey: pk, + corev1.TLSCertKey: testcrypto.MustCreateCert(t, pk, + &cmapi.Certificate{Spec: cmapi.CertificateSpec{CommonName: "example.com"}}, + ), + cmapi.JKSTruststoreKey: []byte("SomeData"), + }, + }, + expectedAction: true, + }, + "do nothing when JKS keystore is defined and create field is set to false": { + enableOwnerRef: true, + cert: &cmapi.Certificate{ + ObjectMeta: metav1.ObjectMeta{Namespace: "test-namespace", Name: "test-name", UID: types.UID("uid-123")}, + Spec: cmapi.CertificateSpec{ + CommonName: "example.com", + IssuerRef: cmmeta.IssuerReference{ + Name: "testissuer", + Kind: "IssuerKind", + Group: "group.example.com", + }, + SecretName: "something", + Keystores: &cmapi.CertificateKeystores{ + JKS: &cmapi.JKSKeystore{ + Create: false, + }, + }, + }}, + secret: &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{Name: "something", Namespace: "test-namespace", + Annotations: map[string]string{ + cmapi.IssuerNameAnnotationKey: "testissuer", + cmapi.IssuerKindAnnotationKey: "IssuerKind", + cmapi.IssuerGroupAnnotationKey: "group.example.com", + }, + Labels: map[string]string{cmapi.PartOfCertManagerControllerLabelKey: "true"}, + OwnerReferences: []metav1.OwnerReference{ + {APIVersion: "cert-manager.io/v1", Kind: "Certificate", Name: "test-name", UID: types.UID("uid-123"), Controller: ptr.To(true), BlockOwnerDeletion: ptr.To(true)}, + }, + ManagedFields: []metav1.ManagedFieldsEntry{ + {Manager: fieldManager, FieldsV1: &metav1.FieldsV1{ + Raw: []byte(` + {"f:metadata": { + "f:labels": { + "f:controller.cert-manager.io/fao": {} + }, + "f:annotations": { + "f:cert-manager.io/common-name": {}, + "f:cert-manager.io/alt-names": {}, + "f:cert-manager.io/ip-sans": {}, + "f:cert-manager.io/uri-sans": {} + }, + "f:ownerReferences": { + "k:{\"uid\":\"uid-123\"}": {} + } + }}`), + }}, + }, + }, + Data: map[string][]byte{ + corev1.TLSPrivateKeyKey: pk, + corev1.TLSCertKey: testcrypto.MustCreateCert(t, pk, + &cmapi.Certificate{Spec: cmapi.CertificateSpec{CommonName: "example.com"}}, + ), + }, + }, + expectedAction: false, + }, + "refresh secret when PKCS12 keystore is defined and the secret does not have keystore/truststore fields": { + enableOwnerRef: true, + cert: &cmapi.Certificate{ + ObjectMeta: metav1.ObjectMeta{Namespace: "test-namespace", Name: "test-name", UID: types.UID("uid-123")}, + Spec: cmapi.CertificateSpec{ + CommonName: "example.com", + IssuerRef: cmmeta.IssuerReference{ + Name: "testissuer", + Kind: "IssuerKind", + Group: "group.example.com", + }, + SecretName: "something", + Keystores: &cmapi.CertificateKeystores{ + PKCS12: &cmapi.PKCS12Keystore{ + Create: true, + }, + }, + }}, + secret: &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{Name: "something", Namespace: "test-namespace", + Annotations: map[string]string{ + cmapi.IssuerNameAnnotationKey: "testissuer", + cmapi.IssuerKindAnnotationKey: "IssuerKind", + cmapi.IssuerGroupAnnotationKey: "group.example.com", + }, + Labels: map[string]string{cmapi.PartOfCertManagerControllerLabelKey: "true"}, + OwnerReferences: []metav1.OwnerReference{ + {APIVersion: "cert-manager.io/v1", Kind: "Certificate", Name: "test-name", UID: types.UID("uid-123"), Controller: ptr.To(true), BlockOwnerDeletion: ptr.To(true)}, + }, + ManagedFields: []metav1.ManagedFieldsEntry{ + {Manager: fieldManager, FieldsV1: &metav1.FieldsV1{ + Raw: []byte(` + {"f:metadata": { + "f:labels": { + "f:controller.cert-manager.io/fao": {} + }, + "f:annotations": { + "f:cert-manager.io/common-name": {}, + "f:cert-manager.io/alt-names": {}, + "f:cert-manager.io/ip-sans": {}, + "f:cert-manager.io/uri-sans": {} + }, + "f:ownerReferences": { + "k:{\"uid\":\"uid-123\"}": {} + } + }}`), + }}, + }, + }, + Data: map[string][]byte{ + corev1.TLSPrivateKeyKey: pk, + corev1.TLSCertKey: testcrypto.MustCreateCert(t, pk, + &cmapi.Certificate{Spec: cmapi.CertificateSpec{CommonName: "example.com"}}, + ), + }, + }, + expectedAction: true, + }, + "refresh secret when PKCS12 keystore is defined, create is disabled and the secret has keystore/truststore fields": { + enableOwnerRef: true, + cert: &cmapi.Certificate{ + ObjectMeta: metav1.ObjectMeta{Namespace: "test-namespace", Name: "test-name", UID: types.UID("uid-123")}, + Spec: cmapi.CertificateSpec{ + CommonName: "example.com", + IssuerRef: cmmeta.IssuerReference{ + Name: "testissuer", + Kind: "IssuerKind", + Group: "group.example.com", + }, + SecretName: "something", + Keystores: &cmapi.CertificateKeystores{ + PKCS12: &cmapi.PKCS12Keystore{ + Create: false, + }, + }, + }}, + secret: &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{Name: "something", Namespace: "test-namespace", + Annotations: map[string]string{ + cmapi.IssuerNameAnnotationKey: "testissuer", + cmapi.IssuerKindAnnotationKey: "IssuerKind", + cmapi.IssuerGroupAnnotationKey: "group.example.com", + }, + Labels: map[string]string{cmapi.PartOfCertManagerControllerLabelKey: "true"}, + OwnerReferences: []metav1.OwnerReference{ + {APIVersion: "cert-manager.io/v1", Kind: "Certificate", Name: "test-name", UID: types.UID("uid-123"), Controller: ptr.To(true), BlockOwnerDeletion: ptr.To(true)}, + }, + ManagedFields: []metav1.ManagedFieldsEntry{ + {Manager: fieldManager, FieldsV1: &metav1.FieldsV1{ + Raw: []byte(` + {"f:metadata": { + "f:labels": { + "f:controller.cert-manager.io/fao": {} + }, + "f:annotations": { + "f:cert-manager.io/common-name": {}, + "f:cert-manager.io/alt-names": {}, + "f:cert-manager.io/ip-sans": {}, + "f:cert-manager.io/uri-sans": {} + }, + "f:ownerReferences": { + "k:{\"uid\":\"uid-123\"}": {} + } + }}`), + }}, + }, + }, + Data: map[string][]byte{ + corev1.TLSPrivateKeyKey: pk, + corev1.TLSCertKey: testcrypto.MustCreateCert(t, pk, + &cmapi.Certificate{Spec: cmapi.CertificateSpec{CommonName: "example.com"}}, + ), + cmapi.PKCS12TruststoreKey: []byte("SomeData"), + }, + }, + expectedAction: true, + }, + "refresh secret when PKCS12 keystore is null and the secret has keystore/truststore fields": { + enableOwnerRef: true, + cert: &cmapi.Certificate{ + ObjectMeta: metav1.ObjectMeta{Namespace: "test-namespace", Name: "test-name", UID: types.UID("uid-123")}, + Spec: cmapi.CertificateSpec{ + CommonName: "example.com", + IssuerRef: cmmeta.IssuerReference{ + Name: "testissuer", + Kind: "IssuerKind", + Group: "group.example.com", + }, + SecretName: "something", + Keystores: &cmapi.CertificateKeystores{ + PKCS12: nil, + }, + }}, + secret: &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{Name: "something", Namespace: "test-namespace", + Annotations: map[string]string{ + cmapi.IssuerNameAnnotationKey: "testissuer", + cmapi.IssuerKindAnnotationKey: "IssuerKind", + cmapi.IssuerGroupAnnotationKey: "group.example.com", + }, + Labels: map[string]string{cmapi.PartOfCertManagerControllerLabelKey: "true"}, + OwnerReferences: []metav1.OwnerReference{ + {APIVersion: "cert-manager.io/v1", Kind: "Certificate", Name: "test-name", UID: types.UID("uid-123"), Controller: ptr.To(true), BlockOwnerDeletion: ptr.To(true)}, + }, + ManagedFields: []metav1.ManagedFieldsEntry{ + {Manager: fieldManager, FieldsV1: &metav1.FieldsV1{ + Raw: []byte(` + {"f:metadata": { + "f:labels": { + "f:controller.cert-manager.io/fao": {} + }, + "f:annotations": { + "f:cert-manager.io/common-name": {}, + "f:cert-manager.io/alt-names": {}, + "f:cert-manager.io/ip-sans": {}, + "f:cert-manager.io/uri-sans": {} + }, + "f:ownerReferences": { + "k:{\"uid\":\"uid-123\"}": {} + } + }}`), + }}, + }, + }, + Data: map[string][]byte{ + corev1.TLSPrivateKeyKey: pk, + corev1.TLSCertKey: testcrypto.MustCreateCert(t, pk, + &cmapi.Certificate{Spec: cmapi.CertificateSpec{CommonName: "example.com"}}, + ), + cmapi.PKCS12TruststoreKey: []byte("SomeData"), + }, + }, + expectedAction: true, + }, + "do nothing when PKCS12 keystore is defined and the create is set to false": { + enableOwnerRef: true, + cert: &cmapi.Certificate{ + ObjectMeta: metav1.ObjectMeta{Namespace: "test-namespace", Name: "test-name", UID: types.UID("uid-123")}, + Spec: cmapi.CertificateSpec{ + CommonName: "example.com", + IssuerRef: cmmeta.IssuerReference{ + Name: "testissuer", + Kind: "IssuerKind", + Group: "group.example.com", + }, + SecretName: "something", + Keystores: &cmapi.CertificateKeystores{ + PKCS12: &cmapi.PKCS12Keystore{ + Create: false, + }, + }, + }}, + secret: &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{Name: "something", Namespace: "test-namespace", + Annotations: map[string]string{ + cmapi.IssuerNameAnnotationKey: "testissuer", + cmapi.IssuerKindAnnotationKey: "IssuerKind", + cmapi.IssuerGroupAnnotationKey: "group.example.com", + }, + Labels: map[string]string{cmapi.PartOfCertManagerControllerLabelKey: "true"}, + OwnerReferences: []metav1.OwnerReference{ + {APIVersion: "cert-manager.io/v1", Kind: "Certificate", Name: "test-name", UID: types.UID("uid-123"), Controller: ptr.To(true), BlockOwnerDeletion: ptr.To(true)}, + }, + ManagedFields: []metav1.ManagedFieldsEntry{ + {Manager: fieldManager, FieldsV1: &metav1.FieldsV1{ + Raw: []byte(` + {"f:metadata": { + "f:labels": { + "f:controller.cert-manager.io/fao": {} + }, + "f:annotations": { + "f:cert-manager.io/common-name": {}, + "f:cert-manager.io/alt-names": {}, + "f:cert-manager.io/ip-sans": {}, + "f:cert-manager.io/uri-sans": {} + }, + "f:ownerReferences": { + "k:{\"uid\":\"uid-123\"}": {} + } + }}`), + }}, + }, + }, + Data: map[string][]byte{ + corev1.TLSPrivateKeyKey: pk, + corev1.TLSCertKey: testcrypto.MustCreateCert(t, pk, + &cmapi.Certificate{Spec: cmapi.CertificateSpec{CommonName: "example.com"}}, + ), + }, + }, + expectedAction: false, + }, } for name, test := range tests { @@ -530,17 +1165,20 @@ func Test_ensureSecretData(t *testing.T) { defer builder.Stop() key := test.key + if key == (types.NamespacedName{}) && test.cert != nil { + key = types.NamespacedName{ + Name: test.cert.Name, + Namespace: test.cert.Namespace, + } + } // Call ProcessItem - err = w.controller.ProcessItem(context.Background(), key) + err = w.controller.ProcessItem(t.Context(), key) assert.NoError(t, err) if err := builder.AllActionsExecuted(); err != nil { builder.T.Error(err) } - if err := builder.AllReactorsCalled(); err != nil { - builder.T.Error(err) - } assert.Equal(t, test.expectedAction, actionCalled, "unexpected Secret reconcile called") }) diff --git a/pkg/controller/certificates/issuing/temporary.go b/pkg/controller/certificates/issuing/temporary.go index a1898e6df03..dacc264d90e 100644 --- a/pkg/controller/certificates/issuing/temporary.go +++ b/pkg/controller/certificates/issuing/temporary.go @@ -68,8 +68,9 @@ func (c *controller) ensureTemporaryCertificate(ctx context.Context, crt *cmapi. return false, err } secretData := internal.SecretData{ - Certificate: certData, - PrivateKey: pkData, + Certificate: certData, + PrivateKey: pkData, + CertificateName: crt.Name, } if err := c.secretsUpdateData(ctx, crt, secretData); err != nil { return false, err diff --git a/pkg/controller/certificates/keymanager/fuzz_test.go b/pkg/controller/certificates/keymanager/fuzz_test.go new file mode 100644 index 00000000000..715302bc5fd --- /dev/null +++ b/pkg/controller/certificates/keymanager/fuzz_test.go @@ -0,0 +1,99 @@ +/* +Copyright 2024 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. +*/ + +package keymanager + +import ( + "testing" + + gfh "github.com/AdaLogics/go-fuzz-headers" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/types" + + cmapiv1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" + testpkg "github.com/cert-manager/cert-manager/pkg/controller/test" +) + +// FuzzProcessItem tests the keymanager controllers ProcessItem() +// method. It creates a fully-randomized certificate, secret and +// multiple random requests and adds all of these to the builder. +// These objects may be invalid compared to a real-world use case +// and as such the fuzzer overapproximates. Depending on the +// number of false positives, this case be adjusted over time. +// +// The fuzzer does not verify how Cert-Manager behaves. It tests for panics +// or unrecoverable issues such as stack overflows, excessive memory usage, +// deadlocks, inifinite loops and other similar issues. +func FuzzProcessItem(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte, numberOfRequests int) { + fdp := gfh.NewConsumer(data) + + // Create a fully random certificate + // This may be invalid. + certificate := &cmapiv1.Certificate{} + err := fdp.GenerateStruct(certificate) + if err != nil { + return + } + // Create a fully random secret + // This may be invalid. + secret := &corev1.Secret{} + err = fdp.GenerateStruct(secret) + if err != nil { + return + } + + // Create fully random requests. these may be invalid. + requests := make([]*cmapiv1.CertificateRequest, 0) + for range numberOfRequests % 10 { + request := &cmapiv1.CertificateRequest{} + err = fdp.GenerateStruct(request) + if err != nil { + if len(requests) == 0 { + return + } + } + requests = append(requests, request) + } + + // Create the builder + builder := &testpkg.Builder{ + T: t, + StringGenerator: func(i int) string { return "notrandom" }, + } + builder.CertManagerObjects = append(builder.CertManagerObjects, certificate) + builder.KubeObjects = append(builder.KubeObjects, secret) + for _, req := range requests { + builder.CertManagerObjects = append(builder.CertManagerObjects, req) + } + builder.Init() + + w := &controllerWrapper{} + _, _, err = w.Register(builder.Context) + if err != nil { + t.Fatal(err) + } + builder.Start() + defer builder.Stop() + + key := types.NamespacedName{ + Name: certificate.Name, + Namespace: certificate.Namespace, + } + // Call ProcessItem. This is the API that the fuzzer tests. + _ = w.controller.ProcessItem(t.Context(), key) + }) +} diff --git a/pkg/controller/certificates/keymanager/keymanager_controller.go b/pkg/controller/certificates/keymanager/keymanager_controller.go index a6aa2788abb..d6a7183f193 100644 --- a/pkg/controller/certificates/keymanager/keymanager_controller.go +++ b/pkg/controller/certificates/keymanager/keymanager_controller.go @@ -20,7 +20,6 @@ import ( "context" "crypto" "fmt" - "time" "github.com/go-logr/logr" corev1 "k8s.io/api/core/v1" @@ -28,20 +27,20 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/selection" - "k8s.io/client-go/informers" + "k8s.io/apimachinery/pkg/types" "k8s.io/client-go/kubernetes" - corelisters "k8s.io/client-go/listers/core/v1" "k8s.io/client-go/tools/cache" "k8s.io/client-go/tools/record" "k8s.io/client-go/util/workqueue" + cminternal "github.com/cert-manager/cert-manager/internal/apis/certmanager/v1" internalcertificates "github.com/cert-manager/cert-manager/internal/controller/certificates" "github.com/cert-manager/cert-manager/internal/controller/feature" + internalinformers "github.com/cert-manager/cert-manager/internal/informers" apiutil "github.com/cert-manager/cert-manager/pkg/api/util" cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" cmclient "github.com/cert-manager/cert-manager/pkg/client/clientset/versioned" - cminformers "github.com/cert-manager/cert-manager/pkg/client/informers/externalversions" cmlisters "github.com/cert-manager/cert-manager/pkg/client/listers/certmanager/v1" controllerpkg "github.com/cert-manager/cert-manager/pkg/controller" "github.com/cert-manager/cert-manager/pkg/controller/certificates" @@ -64,7 +63,7 @@ var ( type controller struct { certificateLister cmlisters.CertificateLister - secretLister corelisters.SecretLister + secretLister internalinformers.SecretLister client cmclient.Interface coreClient kubernetes.Interface recorder record.EventRecorder @@ -76,35 +75,40 @@ type controller struct { } func NewController( - log logr.Logger, - client cmclient.Interface, - coreClient kubernetes.Interface, - factory informers.SharedInformerFactory, - cmFactory cminformers.SharedInformerFactory, - recorder record.EventRecorder, - fieldManager string, -) (*controller, workqueue.RateLimitingInterface, []cache.InformerSynced) { + log logr.Logger, ctx *controllerpkg.Context, +) (*controller, workqueue.TypedRateLimitingInterface[types.NamespacedName], []cache.InformerSynced, error) { // create a queue used to queue up items to be processed - queue := workqueue.NewNamedRateLimitingQueue(workqueue.NewItemExponentialFailureRateLimiter(time.Second*1, time.Second*30), ControllerName) + queue := workqueue.NewTypedRateLimitingQueueWithConfig( + controllerpkg.DefaultCertificateRateLimiter(), + workqueue.TypedRateLimitingQueueConfig[types.NamespacedName]{ + Name: ControllerName, + }, + ) // obtain references to all the informers used by this controller - certificateInformer := cmFactory.Certmanager().V1().Certificates() - secretsInformer := factory.Core().V1().Secrets() + certificateInformer := ctx.SharedInformerFactory.Certmanager().V1().Certificates() + secretsInformer := ctx.KubeSharedInformerFactory.Secrets() - certificateInformer.Informer().AddEventHandler(&controllerpkg.QueuingEventHandler{Queue: queue}) + if _, err := certificateInformer.Informer().AddEventHandler(&controllerpkg.QueuingEventHandler{Queue: queue}); err != nil { + return nil, nil, nil, fmt.Errorf("error setting up event handler: %v", err) + } - secretsInformer.Informer().AddEventHandler(&controllerpkg.BlockingEventHandler{ + if _, err := secretsInformer.Informer().AddEventHandler(&controllerpkg.BlockingEventHandler{ // Trigger reconciles on changes to any 'owned' secret resources WorkFunc: certificates.EnqueueCertificatesForResourceUsingPredicates(log, queue, certificateInformer.Lister(), labels.Everything(), predicate.ResourceOwnerOf, ), - }) - secretsInformer.Informer().AddEventHandler(&controllerpkg.BlockingEventHandler{ + }); err != nil { + return nil, nil, nil, fmt.Errorf("error setting up event handler: %v", err) + } + if _, err := secretsInformer.Informer().AddEventHandler(&controllerpkg.BlockingEventHandler{ // Trigger reconciles on changes to certificates named as spec.secretName WorkFunc: certificates.EnqueueCertificatesForResourceUsingPredicates(log, queue, certificateInformer.Lister(), labels.Everything(), predicate.ExtractResourceName(predicate.CertificateSecretName), ), - }) + }); err != nil { + return nil, nil, nil, fmt.Errorf("error setting up event handler: %v", err) + } // build a list of InformerSynced functions that will be returned by the Register method. // the controller will only begin processing items once all of these informers have synced. @@ -116,11 +120,11 @@ func NewController( return &controller{ certificateLister: certificateInformer.Lister(), secretLister: secretsInformer.Lister(), - client: client, - coreClient: coreClient, - recorder: recorder, - fieldManager: fieldManager, - }, queue, mustSync + client: ctx.CMClient, + coreClient: ctx.Client, + recorder: ctx.Recorder, + fieldManager: ctx.FieldManager, + }, queue, mustSync, nil } // isNextPrivateKeyLabelSelector is a label selector used to match Secret @@ -128,31 +132,34 @@ func NewController( var isNextPrivateKeyLabelSelector labels.Selector func init() { - r, err := labels.NewRequirement("cert-manager.io/next-private-key", selection.Equals, []string{"true"}) + r, err := labels.NewRequirement(cmapi.IsNextPrivateKeySecretLabelKey, selection.Equals, []string{"true"}) if err != nil { panic(err) } isNextPrivateKeyLabelSelector = labels.NewSelector().Add(*r) } -func (c *controller) ProcessItem(ctx context.Context, key string) error { +func (c *controller) ProcessItem(ctx context.Context, key types.NamespacedName) error { log := logf.FromContext(ctx).WithValues("key", key) ctx = logf.NewContext(ctx, log) - namespace, name, err := cache.SplitMetaNamespaceKey(key) - if err != nil { - log.Error(err, "invalid resource key passed to ProcessItem") - return nil - } + namespace, name := key.Namespace, key.Name crt, err := c.certificateLister.Certificates(namespace).Get(name) - if apierrors.IsNotFound(err) { - log.V(logf.DebugLevel).Info("certificate not found for key", "error", err.Error()) - return nil - } - if err != nil { + if err != nil && !apierrors.IsNotFound(err) { return err } + if crt == nil || crt.DeletionTimestamp != nil { + // If the Certificate object was/ is being deleted, we don't want to create any + // new Secret resources. + return nil + } + + // Apply runtime defaults to apply default values that are governed by + // controller feature gates, such as DefaultPrivateKeyRotationPolicyAlways. + // We deep copy the object to avoid mutating the client-go cache. + crt = crt.DeepCopy() + cminternal.SetRuntimeDefaults_Certificate(crt) // Discover all 'owned' secrets that have the `next-private-key` label secrets, err := certificates.ListSecretsMatchingPredicates(c.secretLister.Secrets(crt.Namespace), isNextPrivateKeyLabelSelector, predicate.ResourceOwnedBy(crt)) @@ -173,10 +180,9 @@ func (c *controller) ProcessItem(ctx context.Context, key string) error { // if there is no existing Secret resource, create a new one if len(secrets) == 0 { - rotationPolicy := cmapi.RotationPolicyNever - if crt.Spec.PrivateKey != nil && crt.Spec.PrivateKey.RotationPolicy != "" { - rotationPolicy = crt.Spec.PrivateKey.RotationPolicy - } + // PrivateKey is a pointer, but it will never be nil because we called + // the SetRuntimeDefaults function at the start of this function. + rotationPolicy := crt.Spec.PrivateKey.RotationPolicy switch rotationPolicy { case cmapi.RotationPolicyNever: return c.createNextPrivateKeyRotationPolicyNever(ctx, crt) @@ -220,11 +226,7 @@ func (c *controller) ProcessItem(ctx context.Context, key string) error { return c.deleteSecretResources(ctx, secrets) } - violations, err := certificates.PrivateKeyMatchesSpec(pk, crt.Spec) - if err != nil { - log.Error(err, "Internal error verifying if private key matches spec - please open an issue.") - return nil - } + violations := pki.PrivateKeyMatchesSpec(pk, crt.Spec) if len(violations) > 0 { log.V(logf.DebugLevel).Info("Regenerating private key due to change in fields", "violations", violations) c.recorder.Eventf(crt, corev1.EventTypeNormal, reasonDeleted, "Regenerating private key due to change in fields: %v", violations) @@ -254,11 +256,7 @@ func (c *controller) createNextPrivateKeyRotationPolicyNever(ctx context.Context c.recorder.Eventf(crt, corev1.EventTypeWarning, reasonDecodeFailed, "Failed to decode private key stored in Secret %q - generating new key", crt.Spec.SecretName) return c.createAndSetNextPrivateKey(ctx, crt) } - violations, err := certificates.PrivateKeyMatchesSpec(pk, crt.Spec) - if err != nil { - c.recorder.Eventf(crt, corev1.EventTypeWarning, reasonDecodeFailed, "Failed to check if private key stored in Secret %q is up to date - generating new key", crt.Spec.SecretName) - return c.createAndSetNextPrivateKey(ctx, crt) - } + violations := pki.PrivateKeyMatchesSpec(pk, crt.Spec) if len(violations) > 0 { c.recorder.Eventf(crt, corev1.EventTypeWarning, reasonCannotRegenerateKey, "User intervention required: existing private key in Secret %q does not match requirements on Certificate resource, mismatching fields: %v, but cert-manager cannot create new private key as the Certificate's .spec.privateKey.rotationPolicy is unset or set to Never. To allow cert-manager to create a new private key you can set .spec.privateKey.rotationPolicy to 'Always' (this will result in the private key being regenerated every time a cert is renewed) ", crt.Spec.SecretName, violations) return nil @@ -327,7 +325,10 @@ func (c *controller) updateOrApplyStatus(ctx context.Context, crt *cmapi.Certifi Status: cmapi.CertificateStatus{NextPrivateKeySecretName: crt.Status.NextPrivateKeySecretName}, }) } else { - _, err := c.client.CertmanagerV1().Certificates(crt.Namespace).UpdateStatus(ctx, crt, metav1.UpdateOptions{}) + _, err := c.client.CertmanagerV1().Certificates(crt.Namespace).UpdateStatus(ctx, &cmapi.Certificate{ + ObjectMeta: crt.ObjectMeta, + Status: crt.Status, + }, metav1.UpdateOptions{}) return err } } @@ -351,7 +352,8 @@ func (c *controller) createNewPrivateKeySecret(ctx context.Context, crt *cmapi.C Name: name, OwnerReferences: []metav1.OwnerReference{*metav1.NewControllerRef(crt, certificateGvk)}, Labels: map[string]string{ - "cert-manager.io/next-private-key": "true", + cmapi.IsNextPrivateKeySecretLabelKey: "true", + cmapi.PartOfCertManagerControllerLabelKey: "true", }, }, Data: map[string][]byte{ @@ -375,21 +377,14 @@ type controllerWrapper struct { *controller } -func (c *controllerWrapper) Register(ctx *controllerpkg.Context) (workqueue.RateLimitingInterface, []cache.InformerSynced, error) { +func (c *controllerWrapper) Register(ctx *controllerpkg.Context) (workqueue.TypedRateLimitingInterface[types.NamespacedName], []cache.InformerSynced, error) { // construct a new named logger to be reused throughout the controller log := logf.FromContext(ctx.RootContext, ControllerName) - ctrl, queue, mustSync := NewController(log, - ctx.CMClient, - ctx.Client, - ctx.KubeSharedInformerFactory, - ctx.SharedInformerFactory, - ctx.Recorder, - ctx.FieldManager, - ) + ctrl, queue, mustSync, err := NewController(log, ctx) c.controller = ctrl - return queue, mustSync, nil + return queue, mustSync, err } func init() { diff --git a/pkg/controller/certificates/keymanager/keymanager_controller_test.go b/pkg/controller/certificates/keymanager/keymanager_controller_test.go index c417932cfbc..9d6c0467a3c 100644 --- a/pkg/controller/certificates/keymanager/keymanager_controller_test.go +++ b/pkg/controller/certificates/keymanager/keymanager_controller_test.go @@ -17,22 +17,20 @@ limitations under the License. package keymanager import ( - "context" "fmt" "reflect" "testing" - "github.com/kr/pretty" + "github.com/google/go-cmp/cmp" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" coretesting "k8s.io/client-go/testing" - "k8s.io/utils/pointer" + "k8s.io/utils/ptr" cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" - controllerpkg "github.com/cert-manager/cert-manager/pkg/controller" testpkg "github.com/cert-manager/cert-manager/pkg/controller/test" "github.com/cert-manager/cert-manager/pkg/util/pki" ) @@ -71,7 +69,7 @@ func relaxedSecretMatcher(l coretesting.Action, r coretesting.Action) error { objR.Data[k] = []byte("something") } if !reflect.DeepEqual(objL, objR) { - return fmt.Errorf("unexpected difference between actions: %s", pretty.Diff(objL, objR)) + return fmt.Errorf("unexpected difference between actions (-want +got):\n%s", cmp.Diff(objL, objR)) } return nil } @@ -82,7 +80,8 @@ func TestProcessItem(t *testing.T) { Namespace: namespace, Name: name, Labels: map[string]string{ - cmapi.IsNextPrivateKeySecretLabelKey: "true", + cmapi.IsNextPrivateKeySecretLabelKey: "true", + cmapi.PartOfCertManagerControllerLabelKey: "true", }, OwnerReferences: []metav1.OwnerReference{ *metav1.NewControllerRef(&cmapi.Certificate{ @@ -97,7 +96,7 @@ func TestProcessItem(t *testing.T) { // key that should be passed to ProcessItem. // if not set, the 'namespace/name' of the 'Certificate' field will be used. // if neither is set, the key will be "" - key string + key types.NamespacedName // Certificate to be synced for the test. // if not set, the 'key' will be passed to ProcessItem instead. @@ -117,10 +116,16 @@ func TestProcessItem(t *testing.T) { }{ "do nothing if an empty 'key' is used": {}, "do nothing if an invalid 'key' is used": { - key: "abc/def/ghi", + key: types.NamespacedName{ + Namespace: "abc", + Name: "def/ghi", + }, }, "do nothing if a key references a Certificate that does not exist": { - key: "namespace/name", + key: types.NamespacedName{ + Namespace: "namespace", + Name: "name", + }, }, "do nothing if Certificate has 'Issuing' condition set to 'false'": { certificate: &cmapi.Certificate{ @@ -164,7 +169,7 @@ func TestProcessItem(t *testing.T) { &cmapi.Certificate{ ObjectMeta: metav1.ObjectMeta{Namespace: "testns", Name: "test"}, Status: cmapi.CertificateStatus{ - NextPrivateKeySecretName: pointer.StringPtr("test-notrandom"), + NextPrivateKeySecretName: ptr.To("test-notrandom"), Conditions: []cmapi.CertificateCondition{ { Type: cmapi.CertificateConditionIssuing, @@ -181,7 +186,7 @@ func TestProcessItem(t *testing.T) { ObjectMeta: metav1.ObjectMeta{ Namespace: "testns", GenerateName: "test-", - Labels: map[string]string{cmapi.IsNextPrivateKeySecretLabelKey: "true"}, + Labels: map[string]string{cmapi.IsNextPrivateKeySecretLabelKey: "true", cmapi.PartOfCertManagerControllerLabelKey: "true"}, OwnerReferences: []metav1.OwnerReference{*metav1.NewControllerRef(&cmapi.Certificate{ObjectMeta: metav1.ObjectMeta{Namespace: "testns", Name: "test"}}, certificateGvk)}, }, Data: map[string][]byte{"tls.key": nil}, @@ -193,7 +198,7 @@ func TestProcessItem(t *testing.T) { certificate: &cmapi.Certificate{ ObjectMeta: metav1.ObjectMeta{Namespace: "testns", Name: "test"}, Status: cmapi.CertificateStatus{ - NextPrivateKeySecretName: pointer.StringPtr("fixed-name"), + NextPrivateKeySecretName: ptr.To("fixed-name"), Conditions: []cmapi.CertificateCondition{ { Type: cmapi.CertificateConditionIssuing, @@ -211,7 +216,7 @@ func TestProcessItem(t *testing.T) { ObjectMeta: metav1.ObjectMeta{ Namespace: "testns", Name: "fixed-name", - Labels: map[string]string{cmapi.IsNextPrivateKeySecretLabelKey: "true"}, + Labels: map[string]string{cmapi.IsNextPrivateKeySecretLabelKey: "true", cmapi.PartOfCertManagerControllerLabelKey: "true"}, OwnerReferences: []metav1.OwnerReference{*metav1.NewControllerRef(&cmapi.Certificate{ObjectMeta: metav1.ObjectMeta{Namespace: "testns", Name: "test"}}, certificateGvk)}, }, Data: map[string][]byte{"tls.key": nil}, @@ -225,7 +230,7 @@ func TestProcessItem(t *testing.T) { certificate: &cmapi.Certificate{ ObjectMeta: metav1.ObjectMeta{Namespace: "testns", Name: "test"}, Status: cmapi.CertificateStatus{ - NextPrivateKeySecretName: pointer.StringPtr("fixed-name"), + NextPrivateKeySecretName: ptr.To("fixed-name"), Conditions: []cmapi.CertificateCondition{ { Type: cmapi.CertificateConditionIssuing, @@ -243,7 +248,7 @@ func TestProcessItem(t *testing.T) { ObjectMeta: metav1.ObjectMeta{ Namespace: "testns", Name: "fixed-name", - Labels: map[string]string{cmapi.IsNextPrivateKeySecretLabelKey: "true"}, + Labels: map[string]string{cmapi.IsNextPrivateKeySecretLabelKey: "true", cmapi.PartOfCertManagerControllerLabelKey: "true"}, OwnerReferences: []metav1.OwnerReference{*metav1.NewControllerRef(&cmapi.Certificate{ObjectMeta: metav1.ObjectMeta{Namespace: "testns", Name: "test"}}, certificateGvk)}, }, Data: map[string][]byte{"tls.key": nil}, @@ -286,7 +291,7 @@ func TestProcessItem(t *testing.T) { certificate: &cmapi.Certificate{ ObjectMeta: metav1.ObjectMeta{Namespace: "testns", Name: "test", UID: types.UID("test")}, Status: cmapi.CertificateStatus{ - NextPrivateKeySecretName: pointer.StringPtr("fixed-name"), + NextPrivateKeySecretName: ptr.To("fixed-name"), Conditions: []cmapi.CertificateCondition{ { Type: cmapi.CertificateConditionIssuing, @@ -316,7 +321,7 @@ func TestProcessItem(t *testing.T) { certificate: &cmapi.Certificate{ ObjectMeta: metav1.ObjectMeta{Namespace: "testns", Name: "test", UID: types.UID("test")}, Status: cmapi.CertificateStatus{ - NextPrivateKeySecretName: pointer.StringPtr("fixed-name"), + NextPrivateKeySecretName: ptr.To("fixed-name"), Conditions: []cmapi.CertificateCondition{ { Type: cmapi.CertificateConditionIssuing, @@ -359,7 +364,7 @@ func TestProcessItem(t *testing.T) { &cmapi.Certificate{ ObjectMeta: metav1.ObjectMeta{Namespace: "testns", Name: "test", UID: types.UID("test")}, Status: cmapi.CertificateStatus{ - NextPrivateKeySecretName: pointer.StringPtr("fixed-name"), + NextPrivateKeySecretName: ptr.To("fixed-name"), Conditions: []cmapi.CertificateCondition{ { Type: cmapi.CertificateConditionIssuing, @@ -375,7 +380,7 @@ func TestProcessItem(t *testing.T) { certificate: &cmapi.Certificate{ ObjectMeta: metav1.ObjectMeta{Namespace: "testns", Name: "test", UID: types.UID("test")}, Status: cmapi.CertificateStatus{ - NextPrivateKeySecretName: pointer.StringPtr("fixed-name-2"), + NextPrivateKeySecretName: ptr.To("fixed-name-2"), Conditions: []cmapi.CertificateCondition{ { Type: cmapi.CertificateConditionIssuing, @@ -399,7 +404,7 @@ func TestProcessItem(t *testing.T) { certificate: &cmapi.Certificate{ ObjectMeta: metav1.ObjectMeta{Namespace: "testns", Name: "test", UID: types.UID("test")}, Status: cmapi.CertificateStatus{ - NextPrivateKeySecretName: pointer.StringPtr("fixed-name"), + NextPrivateKeySecretName: ptr.To("fixed-name"), Conditions: []cmapi.CertificateCondition{ { Type: cmapi.CertificateConditionIssuing, @@ -423,7 +428,7 @@ func TestProcessItem(t *testing.T) { certificate: &cmapi.Certificate{ ObjectMeta: metav1.ObjectMeta{Namespace: "testns", Name: "test", UID: types.UID("test")}, Status: cmapi.CertificateStatus{ - NextPrivateKeySecretName: pointer.StringPtr("fixed-name"), + NextPrivateKeySecretName: ptr.To("fixed-name"), Conditions: []cmapi.CertificateCondition{ { Type: cmapi.CertificateConditionIssuing, @@ -448,7 +453,7 @@ func TestProcessItem(t *testing.T) { certificate: &cmapi.Certificate{ ObjectMeta: metav1.ObjectMeta{Namespace: "testns", Name: "test", UID: types.UID("test")}, Status: cmapi.CertificateStatus{ - NextPrivateKeySecretName: pointer.StringPtr("fixed-name"), + NextPrivateKeySecretName: ptr.To("fixed-name"), Conditions: []cmapi.CertificateCondition{ { Type: cmapi.CertificateConditionIssuing, @@ -493,15 +498,15 @@ func TestProcessItem(t *testing.T) { defer builder.Stop() key := test.key - if key == "" && test.certificate != nil { - key, err = controllerpkg.KeyFunc(test.certificate) - if err != nil { - t.Fatal(err) + if key == (types.NamespacedName{}) && test.certificate != nil { + key = types.NamespacedName{ + Name: test.certificate.Name, + Namespace: test.certificate.Namespace, } } // Call ProcessItem - err = w.controller.ProcessItem(context.Background(), key) + err = w.controller.ProcessItem(t.Context(), key) switch { case err != nil: if test.err != err.Error() { @@ -519,9 +524,6 @@ func TestProcessItem(t *testing.T) { if err := builder.AllActionsExecuted(); err != nil { builder.T.Error(err) } - if err := builder.AllReactorsCalled(); err != nil { - builder.T.Error(err) - } }) } } diff --git a/pkg/controller/certificates/metrics/controller.go b/pkg/controller/certificates/metrics/certificates.go similarity index 55% rename from pkg/controller/certificates/metrics/controller.go rename to pkg/controller/certificates/metrics/certificates.go index 94060e46b52..627e77eecae 100644 --- a/pkg/controller/certificates/metrics/controller.go +++ b/pkg/controller/certificates/metrics/certificates.go @@ -18,17 +18,14 @@ package metrics import ( "context" - "time" + "fmt" - apierrors "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/client-go/informers" + "k8s.io/apimachinery/pkg/types" "k8s.io/client-go/tools/cache" "k8s.io/client-go/util/workqueue" - cminformers "github.com/cert-manager/cert-manager/pkg/client/informers/externalversions" cmlisters "github.com/cert-manager/cert-manager/pkg/client/listers/certmanager/v1" controllerpkg "github.com/cert-manager/cert-manager/pkg/controller" - "github.com/cert-manager/cert-manager/pkg/metrics" ) const ( @@ -43,29 +40,30 @@ type controllerWrapper struct { *controller } -// This controller is synced on all Certificate 'create', 'update', and -// 'delete' events which will update the metrics for that Certificate. +// This controller is a no-op controller for certificate metrics that is kept for backwards compatibility. Certificate metrics are migrated +// to the collector approach and this controller will be remove in a future release. type controller struct { certificateLister cmlisters.CertificateLister - - metrics *metrics.Metrics } -func NewController( - factory informers.SharedInformerFactory, - cmFactory cminformers.SharedInformerFactory, - metrics *metrics.Metrics, -) (*controller, workqueue.RateLimitingInterface, []cache.InformerSynced) { +func NewController(ctx *controllerpkg.Context) (*controller, workqueue.TypedRateLimitingInterface[types.NamespacedName], []cache.InformerSynced, error) { // create a queue used to queue up items to be processed - queue := workqueue.NewNamedRateLimitingQueue(workqueue.NewItemExponentialFailureRateLimiter(time.Second*1, time.Second*30), ControllerName) + queue := workqueue.NewTypedRateLimitingQueueWithConfig( + controllerpkg.DefaultCertificateRateLimiter(), + workqueue.TypedRateLimitingQueueConfig[types.NamespacedName]{ + Name: ControllerName, + }, + ) // obtain references to all the informers used by this controller - certificateInformer := cmFactory.Certmanager().V1().Certificates() + certificateInformer := ctx.SharedInformerFactory.Certmanager().V1().Certificates() // Reconcile over all Certificate events. We do _not_ reconcile on Secret // events that are related to Certificates. It is the responsibility of the // Certificates controllers to update accordingly. - certificateInformer.Informer().AddEventHandler(&controllerpkg.QueuingEventHandler{Queue: queue}) + if _, err := certificateInformer.Informer().AddEventHandler(&controllerpkg.QueuingEventHandler{Queue: queue}); err != nil { + return nil, nil, nil, fmt.Errorf("error setting up event handler: %v", err) + } // build a list of InformerSynced functions that will be returned by the // Register method. the controller will only begin processing items once all @@ -76,45 +74,17 @@ func NewController( return &controller{ certificateLister: certificateInformer.Lister(), - metrics: metrics, - }, queue, mustSync + }, queue, mustSync, nil } -func (c *controller) ProcessItem(ctx context.Context, key string) error { - // Set context deadline for full sync in 10 seconds - ctx, cancel := context.WithTimeout(ctx, time.Second*10) - defer cancel() - - namespace, name, err := cache.SplitMetaNamespaceKey(key) - if err != nil { - return nil - } - - crt, err := c.certificateLister.Certificates(namespace).Get(name) - if apierrors.IsNotFound(err) { - // If the Certificate no longer exists, remove it's metrics from being exposed. - c.metrics.RemoveCertificate(key) - return nil - } - if err != nil { - return err - } - - // Update that Certificates metrics - c.metrics.UpdateCertificate(ctx, crt) - +func (c *controller) ProcessItem(ctx context.Context, key types.NamespacedName) error { return nil } -func (c *controllerWrapper) Register(ctx *controllerpkg.Context) (workqueue.RateLimitingInterface, []cache.InformerSynced, error) { - ctrl, queue, mustSync := NewController( - ctx.KubeSharedInformerFactory, - ctx.SharedInformerFactory, - ctx.Metrics, - ) +func (c *controllerWrapper) Register(ctx *controllerpkg.Context) (workqueue.TypedRateLimitingInterface[types.NamespacedName], []cache.InformerSynced, error) { + ctrl, queue, mustSync, err := NewController(ctx) c.controller = ctrl - - return queue, mustSync, nil + return queue, mustSync, err } func init() { diff --git a/pkg/controller/certificates/readiness/fuzz_test.go b/pkg/controller/certificates/readiness/fuzz_test.go new file mode 100644 index 00000000000..a72597d88bd --- /dev/null +++ b/pkg/controller/certificates/readiness/fuzz_test.go @@ -0,0 +1,150 @@ +/* +Copyright 2024 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. +*/ + +package readiness + +import ( + "testing" + "time" + + gfh "github.com/AdaLogics/go-fuzz-headers" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/types" + fakeclock "k8s.io/utils/clock/testing" + + cmapiv1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" + testpkg "github.com/cert-manager/cert-manager/pkg/controller/test" + "github.com/cert-manager/cert-manager/pkg/util/pki" + "github.com/cert-manager/cert-manager/test/unit/gen" +) + +var ( + privKey []byte +) + +func init() { + privKey = createPEMPrivateKey() +} + +// FuzzProcessItem tests the readiness controllers ProcessItem() method. +// It creates a random certificate and a random secret and adds these to +// the builder. All of these objects might be invalid and as such the +// fuzzer overapproximates which can result in false positives. +// The fuzzer does not verify how Cert-Manager behaves. It tests for panics +// or unrecoverable issues such as stack overflows, excessive memory usage, +// deadlocks, inifinite loops and other similar issues. +func FuzzProcessItem(f *testing.F) { + f.Fuzz(func(t *testing.T, data, randomX509Bytes, pkData []byte, randomizeX509Bytes bool) { + fdp := gfh.NewConsumer(data) + + // Create the certificate + cert := &cmapiv1.Certificate{} + err := fdp.GenerateStruct(cert) + if err != nil { + return + } + + // Create the secret + secret := &corev1.Secret{} + err = fdp.GenerateStruct(secret) + if err != nil { + return + } + + now := time.Now().UTC() + builder := &testpkg.Builder{ + T: t, + Clock: fakeclock.NewFakeClock(now), + } + + // Here the fuzzer can choose to create valid x509 bytes + // based on the global private key and add these to the + // secret, or it can use the secret as-is. At this point + // the fuzzer should already have x509 bytes specified, + // although they can be invalid if considering a real- + // world usecase. For example, they can contain non- + // alphanumeric characters. + var x509Bytes []byte + mods := make([]gen.SecretModifier, 0) + if !randomizeX509Bytes { + newX509Bytes, err := createSignedCertificate(privKey, cert) + if err != nil { + x509Bytes = randomX509Bytes + } else { + x509Bytes = newX509Bytes + } + mods = append(mods, + gen.SetSecretData(map[string][]byte{ + "tls.crt": x509Bytes, + })) + builder.KubeObjects = append(builder.KubeObjects, + gen.SecretFrom(secret, mods...)) + } else { + builder.KubeObjects = append(builder.KubeObjects, secret) + } + + builder.CertManagerObjects = append(builder.CertManagerObjects, cert) + builder.Init() + w := &controllerWrapper{} + _, _, err = w.Register(builder.Context) + if err != nil { + panic(err) + } + builder.Start() + defer builder.Stop() + + key := types.NamespacedName{ + Name: cert.Name, + Namespace: cert.Namespace, + } + // Call ProcessItem. This is the API that the fuzzer tests. + _ = w.controller.ProcessItem(t.Context(), key) + }) +} + +// MustCreatePEMPrivateKey returns a PEM encoded 2048 bit RSA private key +func createPEMPrivateKey() []byte { + pk, err := pki.GenerateRSAPrivateKey(2048) + if err != nil { + panic(err) + } + pkData, err := pki.EncodePrivateKey(pk, cmapiv1.PKCS8) + if err != nil { + panic(err) + } + return pkData +} + +func createSignedCertificate(pkData []byte, spec *cmapiv1.Certificate) ([]byte, error) { + pk, err := pki.DecodePrivateKeyBytes(pkData) + if err != nil { + return []byte(""), err + } + template, err := pki.CertificateTemplateFromCertificate(spec) + if err != nil { + return []byte(""), err + } + clock := &fakeclock.FakeClock{} + template.NotBefore = clock.Now() + template.NotAfter = clock.Now().Add(time.Hour * 3) + + certData, _, err := pki.SignCertificate(template, template, pk.Public(), pk) + if err != nil { + return []byte(""), err + } + + return certData, nil +} diff --git a/pkg/controller/certificates/readiness/readiness_controller.go b/pkg/controller/certificates/readiness/readiness_controller.go index 0c8e1341ae1..aa5d531763f 100644 --- a/pkg/controller/certificates/readiness/readiness_controller.go +++ b/pkg/controller/certificates/readiness/readiness_controller.go @@ -18,7 +18,7 @@ package readiness import ( "context" - "time" + "fmt" "github.com/go-logr/logr" corev1 "k8s.io/api/core/v1" @@ -26,19 +26,18 @@ import ( apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" - "k8s.io/client-go/informers" - corelisters "k8s.io/client-go/listers/core/v1" + "k8s.io/apimachinery/pkg/types" "k8s.io/client-go/tools/cache" "k8s.io/client-go/util/workqueue" internalcertificates "github.com/cert-manager/cert-manager/internal/controller/certificates" "github.com/cert-manager/cert-manager/internal/controller/certificates/policies" "github.com/cert-manager/cert-manager/internal/controller/feature" + internalinformers "github.com/cert-manager/cert-manager/internal/informers" apiutil "github.com/cert-manager/cert-manager/pkg/api/util" cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" cmclient "github.com/cert-manager/cert-manager/pkg/client/clientset/versioned" - cminformers "github.com/cert-manager/cert-manager/pkg/client/informers/externalversions" cmlisters "github.com/cert-manager/cert-manager/pkg/client/listers/certmanager/v1" controllerpkg "github.com/cert-manager/cert-manager/pkg/controller" "github.com/cert-manager/cert-manager/pkg/controller/certificates" @@ -60,13 +59,13 @@ type controller struct { policyChain policies.Chain certificateLister cmlisters.CertificateLister certificateRequestLister cmlisters.CertificateRequestLister - secretLister corelisters.SecretLister + secretLister internalinformers.SecretLister client cmclient.Interface gatherer *policies.Gatherer // policyEvaluator builds Ready condition of a Certificate based on policy evaluation policyEvaluator policyEvaluatorFunc // renewalTimeCalculator calculates renewal time of a certificate - renewalTimeCalculator certificates.RenewalTimeFunc + renewalTimeCalculator pki.RenewalTimeFunc // fieldManager is the string which will be used as the Field Manager on // fields created or edited by the cert-manager Kubernetes client during @@ -80,34 +79,42 @@ type policyEvaluatorFunc func(policies.Chain, policies.Input) cmapi.CertificateC // NewController returns a new certificate readiness controller. func NewController( log logr.Logger, - client cmclient.Interface, - factory informers.SharedInformerFactory, - cmFactory cminformers.SharedInformerFactory, + ctx *controllerpkg.Context, chain policies.Chain, - renewalTimeCalculator certificates.RenewalTimeFunc, + renewalTimeCalculator pki.RenewalTimeFunc, policyEvaluator policyEvaluatorFunc, - fieldManager string, -) (*controller, workqueue.RateLimitingInterface, []cache.InformerSynced) { +) (*controller, workqueue.TypedRateLimitingInterface[types.NamespacedName], []cache.InformerSynced, error) { // create a queue used to queue up items to be processed - queue := workqueue.NewNamedRateLimitingQueue(workqueue.NewItemExponentialFailureRateLimiter(time.Second*1, time.Second*30), ControllerName) + queue := workqueue.NewTypedRateLimitingQueueWithConfig( + controllerpkg.DefaultCertificateRateLimiter(), + workqueue.TypedRateLimitingQueueConfig[types.NamespacedName]{ + Name: ControllerName, + }, + ) // obtain references to all the informers used by this controller - certificateInformer := cmFactory.Certmanager().V1().Certificates() - certificateRequestInformer := cmFactory.Certmanager().V1().CertificateRequests() - secretsInformer := factory.Core().V1().Secrets() + certificateInformer := ctx.SharedInformerFactory.Certmanager().V1().Certificates() + certificateRequestInformer := ctx.SharedInformerFactory.Certmanager().V1().CertificateRequests() + secretsInformer := ctx.KubeSharedInformerFactory.Secrets() - certificateInformer.Informer().AddEventHandler(&controllerpkg.QueuingEventHandler{Queue: queue}) + if _, err := certificateInformer.Informer().AddEventHandler(&controllerpkg.QueuingEventHandler{Queue: queue}); err != nil { + return nil, nil, nil, fmt.Errorf("error setting up event handler: %v", err) + } // When a CertificateRequest resource changes, enqueue the Certificate resource that owns it. - certificateRequestInformer.Informer().AddEventHandler(&controllerpkg.BlockingEventHandler{ + if _, err := certificateRequestInformer.Informer().AddEventHandler(&controllerpkg.BlockingEventHandler{ WorkFunc: certificates.EnqueueCertificatesForResourceUsingPredicates(log, queue, certificateInformer.Lister(), labels.Everything(), predicate.ResourceOwnerOf), - }) + }); err != nil { + return nil, nil, nil, fmt.Errorf("error setting up event handler: %v", err) + } // When a Secret resource changes, enqueue any Certificate resources that name it as spec.secretName. - secretsInformer.Informer().AddEventHandler(&controllerpkg.BlockingEventHandler{ + if _, err := secretsInformer.Informer().AddEventHandler(&controllerpkg.BlockingEventHandler{ // Trigger reconciles on changes to the Secret named `spec.secretName` WorkFunc: certificates.EnqueueCertificatesForResourceUsingPredicates(log, queue, certificateInformer.Lister(), labels.Everything(), predicate.ExtractResourceName(predicate.CertificateSecretName)), - }) + }); err != nil { + return nil, nil, nil, fmt.Errorf("error setting up event handler: %v", err) + } // build a list of InformerSynced functions that will be returned by the Register method. // the controller will only begin processing items once all of these informers have synced. @@ -122,38 +129,34 @@ func NewController( certificateLister: certificateInformer.Lister(), certificateRequestLister: certificateRequestInformer.Lister(), secretLister: secretsInformer.Lister(), - client: client, + client: ctx.CMClient, gatherer: &policies.Gatherer{ CertificateRequestLister: certificateRequestInformer.Lister(), SecretLister: secretsInformer.Lister(), }, policyEvaluator: policyEvaluator, renewalTimeCalculator: renewalTimeCalculator, - fieldManager: fieldManager, - }, queue, mustSync + fieldManager: ctx.FieldManager, + }, queue, mustSync, nil } // ProcessItem is a worker function that will be called when a new key // corresponding to a Certificate to be re-synced is pulled from the workqueue. // ProcessItem will update the Ready condition of a Certificate. -func (c *controller) ProcessItem(ctx context.Context, key string) error { +func (c *controller) ProcessItem(ctx context.Context, key types.NamespacedName) error { log := logf.FromContext(ctx).WithValues("key", key) ctx = logf.NewContext(ctx, log) - namespace, name, err := cache.SplitMetaNamespaceKey(key) - if err != nil { - log.Error(err, "invalid resource key passed to ProcessItem") - return nil - } + namespace, name := key.Namespace, key.Name crt, err := c.certificateLister.Certificates(namespace).Get(name) - if apierrors.IsNotFound(err) { - log.V(logf.DebugLevel).Info("certificate not found for key", "error", err.Error()) - return nil - } - if err != nil { + if err != nil && !apierrors.IsNotFound(err) { return err } + if crt == nil || crt.DeletionTimestamp != nil { + // If the Certificate object was/ is being deleted, we don't want to update its status. + return nil + } input, err := c.gatherer.DataForCertificate(ctx, crt) if err != nil { @@ -178,10 +181,9 @@ func (c *controller) ProcessItem(ctx context.Context, key string) error { notBefore := metav1.NewTime(x509cert.NotBefore) notAfter := metav1.NewTime(x509cert.NotAfter) - renewBeforeHint := crt.Spec.RenewBefore - renewalTime := c.renewalTimeCalculator(x509cert.NotBefore, x509cert.NotAfter, renewBeforeHint) + renewalTime := c.renewalTimeCalculator(x509cert.NotBefore, x509cert.NotAfter, crt.Spec.RenewBefore, crt.Spec.RenewBeforePercentage) - //update Certificate's Status + // update Certificate's Status crt.Status.NotBefore = ¬Before crt.Status.NotAfter = ¬After crt.Status.RenewalTime = renewalTime @@ -250,22 +252,19 @@ type controllerWrapper struct { *controller } -func (c *controllerWrapper) Register(ctx *controllerpkg.Context) (workqueue.RateLimitingInterface, []cache.InformerSynced, error) { +func (c *controllerWrapper) Register(ctx *controllerpkg.Context) (workqueue.TypedRateLimitingInterface[types.NamespacedName], []cache.InformerSynced, error) { // construct a new named logger to be reused throughout the controller log := logf.FromContext(ctx.RootContext, ControllerName) - ctrl, queue, mustSync := NewController(log, - ctx.CMClient, - ctx.KubeSharedInformerFactory, - ctx.SharedInformerFactory, + ctrl, queue, mustSync, err := NewController(log, + ctx, policies.NewReadinessPolicyChain(ctx.Clock), - certificates.RenewalTime, + pki.RenewalTime, BuildReadyConditionFromChain, - ctx.FieldManager, ) c.controller = ctrl - return queue, mustSync, nil + return queue, mustSync, err } func init() { diff --git a/pkg/controller/certificates/readiness/readiness_controller_test.go b/pkg/controller/certificates/readiness/readiness_controller_test.go index 0913bf2f618..6fa353d7006 100644 --- a/pkg/controller/certificates/readiness/readiness_controller_test.go +++ b/pkg/controller/certificates/readiness/readiness_controller_test.go @@ -17,21 +17,20 @@ limitations under the License. package readiness import ( - "context" "testing" "time" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" coretesting "k8s.io/client-go/testing" fakeclock "k8s.io/utils/clock/testing" "github.com/cert-manager/cert-manager/internal/controller/certificates/policies" cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" - controllerpkg "github.com/cert-manager/cert-manager/pkg/controller" - "github.com/cert-manager/cert-manager/pkg/controller/certificates" testpkg "github.com/cert-manager/cert-manager/pkg/controller/test" + "github.com/cert-manager/cert-manager/pkg/util/pki" testcrypto "github.com/cert-manager/cert-manager/test/unit/crypto" "github.com/cert-manager/cert-manager/test/unit/gen" ) @@ -44,8 +43,8 @@ func policyEvaluatorBuilder(c cmapi.CertificateCondition) policyEvaluatorFunc { } // renewalTimeBuilder returns a fake renewalTimeFunc for ReadinessController. -func renewalTimeBuilder(rt *metav1.Time) certificates.RenewalTimeFunc { - return func(notBefore, notAfter time.Time, cert *metav1.Duration) *metav1.Time { +func renewalTimeBuilder(rt *metav1.Time) pki.RenewalTimeFunc { + return func(notBefore, notAfter time.Time, renewBefore *metav1.Duration, renewBeforePercentage *int32) *metav1.Time { return rt } } @@ -74,7 +73,7 @@ func TestProcessItem(t *testing.T) { // key that should be passed to ProcessItem. // if not set, the 'namespace/name' of the 'Certificate' field will be used. // if neither is set, the key will be "". - key string + key types.NamespacedName // cert to be loaded to fake clientset cert *cmapi.Certificate @@ -105,10 +104,16 @@ func TestProcessItem(t *testing.T) { }{ "do nothing if an empty 'key' is used": {}, "do nothing if an invalid 'key' is used": { - key: "abc/def/ghi", + key: types.NamespacedName{ + Namespace: "abc", + Name: "def/ghi", + }, }, "do nothing if a key references a Certificate that does not exist": { - key: "namespace/name", + key: types.NamespacedName{ + Namespace: "namespace", + Name: "name", + }, }, "update status for a Certificate that is evaluated as Ready and whose spec.secretName secret contains a valid X509 cert": { condition: cmapi.CertificateCondition{ @@ -216,7 +221,7 @@ func TestProcessItem(t *testing.T) { Message: "ready message", })), }, - "update status for a Certificate that has a Ready conditon and the policy evaluates to True- should remain True": { + "update status for a Certificate that has a Ready condition and the policy evaluates to True - should remain True": { condition: cmapi.CertificateCondition{ Type: cmapi.CertificateConditionReady, Status: cmmeta.ConditionTrue, @@ -285,7 +290,7 @@ func TestProcessItem(t *testing.T) { c := gen.CertificateFrom(test.cert, gen.SetCertificateStatusCondition(test.condition)) - // gen package functions don't accept pointers- we need to test setting these values to nil in some scenarios. + // gen package functions don't accept pointers - we need to test setting these values to nil in some scenarios. c.Status.NotAfter = test.notAfter c.Status.NotBefore = test.notBefore c.Status.RenewalTime = test.renewalTime @@ -303,15 +308,15 @@ func TestProcessItem(t *testing.T) { defer builder.Stop() key := test.key - if key == "" && cert != nil { - key, err = controllerpkg.KeyFunc(cert) - if err != nil { - t.Fatal(err) + if key == (types.NamespacedName{}) && cert != nil { + key = types.NamespacedName{ + Name: cert.Name, + Namespace: cert.Namespace, } } // Call ProcessItem - err = w.controller.ProcessItem(context.Background(), key) + err = w.controller.ProcessItem(t.Context(), key) if test.wantsErr != (err != nil) { t.Errorf("expected error: %v, got : %v", test.wantsErr, err) } @@ -319,9 +324,6 @@ func TestProcessItem(t *testing.T) { if err := builder.AllActionsExecuted(); err != nil { builder.T.Error(err) } - if err := builder.AllReactorsCalled(); err != nil { - builder.T.Error(err) - } }) } } @@ -375,7 +377,7 @@ func TestNewReadinessPolicyChain(t *testing.T) { corev1.TLSCertKey: []byte("test"), })), reason: policies.InvalidKeyPair, - message: "Issuing certificate as Secret contains an invalid key-pair: tls: failed to find any PEM data in certificate input", + message: "Issuing certificate as Secret contains invalid private key data: error decoding private key PEM block: no PEM data was found in given input", violationFound: true, }, "Certificate not Ready as Secret contains corrupt certificate data": { @@ -385,8 +387,8 @@ func TestNewReadinessPolicyChain(t *testing.T) { corev1.TLSPrivateKeyKey: privKey, corev1.TLSCertKey: []byte("test"), })), - reason: policies.InvalidKeyPair, - message: "Issuing certificate as Secret contains an invalid key-pair: tls: failed to find any PEM data in certificate input", + reason: policies.InvalidCertificate, + message: "Issuing certificate as Secret contains an invalid certificate: error decoding certificate PEM block: no valid certificates found", violationFound: true, }, "Certificate not Ready as Secret contains a non-matching key-pair": { @@ -399,14 +401,14 @@ func TestNewReadinessPolicyChain(t *testing.T) { gen.Certificate("something else", gen.SetCertificateCommonName("example.com"))), })), reason: policies.InvalidKeyPair, - message: "Issuing certificate as Secret contains an invalid key-pair: tls: private key does not match public key", + message: "Issuing certificate as Secret contains a private key that does not match the certificate", violationFound: true, }, "Certificate not Ready when CertificateRequest does not match certificate spec": { cert: gen.Certificate("something", gen.SetCertificateCommonName("new.example.com"), gen.SetCertificateIssuer( - cmmeta.ObjectReference{Name: "testissuer", Kind: "IssuerKind", Group: "group.example.com"})), + cmmeta.IssuerReference{Name: "testissuer", Kind: "IssuerKind", Group: "group.example.com"})), secret: gen.Secret("something", gen.SetSecretAnnotations( map[string]string{ @@ -425,7 +427,7 @@ func TestNewReadinessPolicyChain(t *testing.T) { ), cr: gen.CertificateRequest("something", gen.SetCertificateRequestIssuer( - cmmeta.ObjectReference{ + cmmeta.IssuerReference{ Name: "testissuer", Kind: "IssuerKind", Group: "group.example.com", @@ -442,7 +444,7 @@ func TestNewReadinessPolicyChain(t *testing.T) { "Certificate is not Ready when it has expired": { cert: gen.Certificate("something", gen.SetCertificateCommonName("new.example.com"), - gen.SetCertificateIssuer(cmmeta.ObjectReference{ + gen.SetCertificateIssuer(cmmeta.IssuerReference{ Name: "testissuer", Kind: "IssuerKind", Group: "group.example.com", @@ -463,11 +465,15 @@ func TestNewReadinessPolicyChain(t *testing.T) { }, )), cr: gen.CertificateRequest("something", - gen.SetCertificateRequestIssuer(cmmeta.ObjectReference{ + gen.SetCertificateRequestIssuer(cmmeta.IssuerReference{ Name: "testissuer", Kind: "IssuerKind", Group: "group.example.com", - })), + }), + gen.SetCertificateRequestCSR(testcrypto.MustGenerateCSRImpl(t, privKey, + gen.Certificate("something", + gen.SetCertificateCommonName("new.example.com")))), + ), reason: policies.Expired, message: "Certificate expired on Sun, 31 Dec 0000 23:00:00 UTC", violationFound: true, @@ -475,7 +481,7 @@ func TestNewReadinessPolicyChain(t *testing.T) { "Certificate is Ready, no policy violations found": { cert: gen.Certificate("something", gen.SetCertificateCommonName("new.example.com"), - gen.SetCertificateIssuer(cmmeta.ObjectReference{ + gen.SetCertificateIssuer(cmmeta.IssuerReference{ Name: "testissuer", Kind: "IssuerKind", Group: "group.example.com", @@ -498,7 +504,7 @@ func TestNewReadinessPolicyChain(t *testing.T) { )), cr: gen.CertificateRequest("something", gen.SetCertificateRequestIssuer( - cmmeta.ObjectReference{ + cmmeta.IssuerReference{ Name: "testissuer", Kind: "IssuerKind", Group: "group.example.com", diff --git a/pkg/controller/certificates/requestmanager/fuzz_test.go b/pkg/controller/certificates/requestmanager/fuzz_test.go new file mode 100644 index 00000000000..336dcafb7fd --- /dev/null +++ b/pkg/controller/certificates/requestmanager/fuzz_test.go @@ -0,0 +1,165 @@ +/* +Copyright 2024 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. +*/ + +package requestmanager + +import ( + "testing" + "time" + + gfh "github.com/AdaLogics/go-fuzz-headers" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + featuregatetesting "k8s.io/component-base/featuregate/testing" + fakeclock "k8s.io/utils/clock/testing" + + "github.com/cert-manager/cert-manager/internal/controller/feature" + cmapiv1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" + cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" + testpkg "github.com/cert-manager/cert-manager/pkg/controller/test" + utilfeature "github.com/cert-manager/cert-manager/pkg/util/feature" + "github.com/cert-manager/cert-manager/test/unit/gen" +) + +var ( + globalBundle *cryptoBundle +) + +func init() { + var err error + globalBundle, err = createCryptoBundle(&cmapiv1.Certificate{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "testns", + Name: "test", + UID: "test", + }, + Spec: cmapiv1.CertificateSpec{CommonName: "test-bundle-1"}}, + ) + if err != nil { + panic(err) + } +} + +// FuzzProcessItem tests the requestmanager controllers ProcessItem() method. +// It creates up to 10 random certificate requests, 1 certificate, a secret +// and adds these to the builder. All of these objects might be invalid and +// as such the fuzzer overapproximates which can result in false positives. +// The fuzzer does not verify how Cert-Manager behaves. It tests for panics +// or unrecoverable issues such as stack overflows, excessive memory usage, +// deadlocks, inifinite loops and other similar issues. +func FuzzProcessItem(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte, + numberOfRequests int, + useStableCertificateRequestName, + randomizeCertificate, + randomizeSecret bool) { + fdp := gfh.NewConsumer(data) + + // Create up to 10 random certificate requests + requests := make([]runtime.Object, 0) + for range numberOfRequests % 10 { + request := &cmapiv1.CertificateRequest{} + err := fdp.GenerateStruct(request) + if err != nil { + if len(requests) == 0 { + return + } + break + } + requests = append(requests, request) + } + + // Create a certificate and a secret + // The certificate can be entirely random, or the fuzzer + // can generate one from the global bundle. + var certificate *cmapiv1.Certificate + var secret *corev1.Secret + if randomizeCertificate { + certificate = &cmapiv1.Certificate{} + err := fdp.GenerateStruct(certificate) + if err != nil { + return + } + secret = &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{Namespace: certificate.Namespace, Name: "secret"}, + Data: map[string][]byte{corev1.TLSPrivateKeyKey: globalBundle.privateKeyBytes}, + } + } else { + certificate = gen.CertificateFrom(globalBundle.certificate, + gen.SetCertificateNextPrivateKeySecretName("secret"), + gen.SetCertificateStatusCondition(cmapiv1.CertificateCondition{Type: cmapiv1.CertificateConditionIssuing, Status: cmmeta.ConditionTrue})) + secret = &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{Namespace: globalBundle.certificate.Namespace, Name: "secret"}, + Data: map[string][]byte{corev1.TLSPrivateKeyKey: globalBundle.privateKeyBytes}, + } + } + + // At this point, the fuzzer has created a valid secret. + // To allow it to test for invalid edge cases too, we + // give it the option to create a new, possibly invalid + // secret which is entirely random. If the fuzzer fails + // to randomize the secret here, it uses the valid secret + // instead. + if randomizeSecret { + randomSecret := &corev1.Secret{} + err := fdp.GenerateStruct(randomSecret) + if err == nil { + secret = randomSecret + } + } + secrets := []runtime.Object{secret} + fixedNow := metav1.NewTime(time.Now()) + fixedClock := fakeclock.NewFakeClock(fixedNow.Time) + + // Create the builder + builder := &testpkg.Builder{ + T: t, + StringGenerator: func(i int) string { return "notrandom" }, + Clock: fixedClock, + } + + // Add the created objects to the builder + builder.CertManagerObjects = append(builder.CertManagerObjects, certificate) + builder.KubeObjects = append(builder.KubeObjects, secrets...) + builder.CertManagerObjects = append(builder.CertManagerObjects, requests...) + + builder.Init() + w := &controllerWrapper{} + _, _, err := w.Register(builder.Context) + if err != nil { + panic(err) + } + key := types.NamespacedName{ + Name: certificate.Name, + Namespace: certificate.Namespace, + } + + // Enable feature settings that will otherwise + // block the fuzzer. + featuregatetesting.SetFeatureGateDuringTest(t, + utilfeature.DefaultFeatureGate, + feature.StableCertificateRequestName, + useStableCertificateRequestName) + + builder.Start() + defer builder.Stop() + + // Call ProcessItem. This is the API that the fuzzer tests. + _ = w.controller.ProcessItem(t.Context(), key) + }) +} diff --git a/pkg/controller/certificates/requestmanager/requestmanager_controller.go b/pkg/controller/certificates/requestmanager/requestmanager_controller.go index d367caadea3..e7df6be1eda 100644 --- a/pkg/controller/certificates/requestmanager/requestmanager_controller.go +++ b/pkg/controller/certificates/requestmanager/requestmanager_controller.go @@ -30,20 +30,19 @@ import ( apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/wait" - "k8s.io/client-go/informers" - corelisters "k8s.io/client-go/listers/core/v1" "k8s.io/client-go/tools/cache" "k8s.io/client-go/tools/record" "k8s.io/client-go/util/workqueue" "k8s.io/utils/clock" "github.com/cert-manager/cert-manager/internal/controller/feature" + internalinformers "github.com/cert-manager/cert-manager/internal/informers" apiutil "github.com/cert-manager/cert-manager/pkg/api/util" cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" cmclient "github.com/cert-manager/cert-manager/pkg/client/clientset/versioned" - cminformers "github.com/cert-manager/cert-manager/pkg/client/informers/externalversions" cmlisters "github.com/cert-manager/cert-manager/pkg/client/listers/certmanager/v1" controllerpkg "github.com/cert-manager/cert-manager/pkg/controller" "github.com/cert-manager/cert-manager/pkg/controller/certificates" @@ -66,7 +65,7 @@ var ( type controller struct { certificateLister cmlisters.CertificateLister certificateRequestLister cmlisters.CertificateRequestLister - secretLister corelisters.SecretLister + secretLister internalinformers.SecretLister client cmclient.Interface recorder record.EventRecorder clock clock.Clock @@ -79,36 +78,39 @@ type controller struct { } func NewController( - log logr.Logger, - client cmclient.Interface, - factory informers.SharedInformerFactory, - cmFactory cminformers.SharedInformerFactory, - recorder record.EventRecorder, - clock clock.Clock, - certificateControllerOptions controllerpkg.CertificateOptions, - fieldManager string, -) (*controller, workqueue.RateLimitingInterface, []cache.InformerSynced) { + log logr.Logger, ctx *controllerpkg.Context) (*controller, workqueue.TypedRateLimitingInterface[types.NamespacedName], []cache.InformerSynced, error) { // create a queue used to queue up items to be processed - queue := workqueue.NewNamedRateLimitingQueue(workqueue.NewItemExponentialFailureRateLimiter(time.Second*1, time.Second*30), ControllerName) + queue := workqueue.NewTypedRateLimitingQueueWithConfig( + controllerpkg.DefaultCertificateRateLimiter(), + workqueue.TypedRateLimitingQueueConfig[types.NamespacedName]{ + Name: ControllerName, + }, + ) // obtain references to all the informers used by this controller - certificateInformer := cmFactory.Certmanager().V1().Certificates() - certificateRequestInformer := cmFactory.Certmanager().V1().CertificateRequests() - secretsInformer := factory.Core().V1().Secrets() + certificateInformer := ctx.SharedInformerFactory.Certmanager().V1().Certificates() + certificateRequestInformer := ctx.SharedInformerFactory.Certmanager().V1().CertificateRequests() + secretsInformer := ctx.KubeSharedInformerFactory.Secrets() - certificateInformer.Informer().AddEventHandler(&controllerpkg.QueuingEventHandler{Queue: queue}) - certificateRequestInformer.Informer().AddEventHandler(&controllerpkg.BlockingEventHandler{ + if _, err := certificateInformer.Informer().AddEventHandler(&controllerpkg.QueuingEventHandler{Queue: queue}); err != nil { + return nil, nil, nil, fmt.Errorf("error setting up event handler: %v", err) + } + if _, err := certificateRequestInformer.Informer().AddEventHandler(&controllerpkg.BlockingEventHandler{ // Trigger reconciles on changes to any 'owned' CertificateRequest resources WorkFunc: certificates.EnqueueCertificatesForResourceUsingPredicates(log, queue, certificateInformer.Lister(), labels.Everything(), predicate.ResourceOwnerOf, ), - }) - secretsInformer.Informer().AddEventHandler(&controllerpkg.BlockingEventHandler{ + }); err != nil { + return nil, nil, nil, fmt.Errorf("error setting up event handler: %v", err) + } + if _, err := secretsInformer.Informer().AddEventHandler(&controllerpkg.BlockingEventHandler{ // Trigger reconciles on changes to any 'owned' secret resources WorkFunc: certificates.EnqueueCertificatesForResourceUsingPredicates(log, queue, certificateInformer.Lister(), labels.Everything(), predicate.ResourceOwnerOf, ), - }) + }); err != nil { + return nil, nil, nil, fmt.Errorf("error setting up event handler: %v", err) + } // build a list of InformerSynced functions that will be returned by the Register method. // the controller will only begin processing items once all of these informers have synced. @@ -122,32 +124,29 @@ func NewController( certificateLister: certificateInformer.Lister(), certificateRequestLister: certificateRequestInformer.Lister(), secretLister: secretsInformer.Lister(), - client: client, - recorder: recorder, - clock: clock, - copiedAnnotationPrefixes: certificateControllerOptions.CopiedAnnotationPrefixes, - fieldManager: fieldManager, - }, queue, mustSync + client: ctx.CMClient, + recorder: ctx.Recorder, + clock: ctx.Clock, + copiedAnnotationPrefixes: ctx.CertificateOptions.CopiedAnnotationPrefixes, + fieldManager: ctx.FieldManager, + }, queue, mustSync, nil } -func (c *controller) ProcessItem(ctx context.Context, key string) error { +func (c *controller) ProcessItem(ctx context.Context, key types.NamespacedName) error { log := logf.FromContext(ctx).WithValues("key", key) ctx = logf.NewContext(ctx, log) - namespace, name, err := cache.SplitMetaNamespaceKey(key) - if err != nil { - log.Error(err, "invalid resource key passed to ProcessItem") - return nil - } + namespace, name := key.Namespace, key.Name crt, err := c.certificateLister.Certificates(namespace).Get(name) - if apierrors.IsNotFound(err) { - log.V(logf.DebugLevel).Info("certificate not found for key", "error", err.Error()) - return nil - } - if err != nil { + if err != nil && !apierrors.IsNotFound(err) { return err } + if crt == nil || crt.DeletionTimestamp != nil { + // If the Certificate object was/ is being deleted, we don't want to create any + // new CertificateRequests objects + return nil + } if !apiutil.CertificateHasCondition(crt, cmapi.CertificateCondition{ Type: cmapi.CertificateConditionIssuing, @@ -321,7 +320,7 @@ func (c *controller) deleteRequestsNotMatchingSpec(ctx context.Context, crt *cma var remaining []*cmapi.CertificateRequest for _, req := range reqs { log := logf.WithRelatedResource(log, req) - violations, err := certificates.RequestMatchesSpec(req, crt.Spec) + violations, err := pki.RequestMatchesSpec(req, crt.Spec) if err != nil { log.Error(err, "Failed to check if CertificateRequest matches spec, deleting CertificateRequest") if err := c.client.CertmanagerV1().CertificateRequests(req.Namespace).Delete(ctx, req.Name, metav1.DeleteOptions{}); err != nil { @@ -359,7 +358,14 @@ func (c *controller) deleteRequestsNotMatchingSpec(ctx context.Context, crt *cma func (c *controller) createNewCertificateRequest(ctx context.Context, crt *cmapi.Certificate, pk crypto.Signer, nextRevision int, nextPrivateKeySecretName string) error { log := logf.FromContext(ctx) - x509CSR, err := pki.GenerateCSR(crt) + + x509CSR, err := pki.GenerateCSR( + crt, + pki.WithUseLiteralSubject(utilfeature.DefaultMutableFeatureGate.Enabled(feature.LiteralCertificateSubject)), + pki.WithEncodeBasicConstraintsInRequest(utilfeature.DefaultMutableFeatureGate.Enabled(feature.UseCertificateRequestBasicConstraints)), + pki.WithNameConstraints(utilfeature.DefaultMutableFeatureGate.Enabled(feature.NameConstraints)), + pki.WithOtherNames(utilfeature.DefaultMutableFeatureGate.Enabled(feature.OtherNames)), + ) if err != nil { log.Error(err, "Failed to generate CSR - will not retry") return nil @@ -382,7 +388,10 @@ func (c *controller) createNewCertificateRequest(ctx context.Context, crt *cmapi cr := &cmapi.CertificateRequest{ ObjectMeta: metav1.ObjectMeta{ - Namespace: crt.Namespace, + Namespace: crt.Namespace, + // We limit the GenerateName to 52 + 1 characters to stay within the 63 - 5 character limit that + // is used in Kubernetes when generating names. + // see https://github.com/kubernetes/apiserver/blob/696768606f546f71a1e90546613be37d1aa37f64/pkg/storage/names/generate.go GenerateName: apiutil.DNSSafeShortenTo52Characters(crt.Name) + "-", Annotations: annotations, Labels: crt.Labels, @@ -399,7 +408,20 @@ func (c *controller) createNewCertificateRequest(ctx context.Context, crt *cmapi if utilfeature.DefaultFeatureGate.Enabled(feature.StableCertificateRequestName) { cr.ObjectMeta.GenerateName = "" - cr.ObjectMeta.Name = apiutil.DNSSafeShortenTo52Characters(crt.Name) + "-" + fmt.Sprintf("%d", nextRevision) + + // The CertificateRequest name is limited to 253 characters, assuming the nextRevision and hyphen + // can be represented using 20 characters, we can directly accept certificate names up to 233 + // characters. Certificate names that are longer than this will be hashed to a shorter name. We want + // to make crafting two Certificates with the same truncated name as difficult as possible, so we + // use a cryptographic hash function to hash the full certificate name to 64 characters. + // Finally, for Certificates with a name longer than 233 characters, we build the CertificateRequest + // name as follows: -<64-char-hash>-<19-char-nextRevision> + crName, err := apiutil.ComputeSecureUniqueDeterministicNameFromData(crt.Name, 233) + if err != nil { + return err + } + + cr.ObjectMeta.Name = fmt.Sprintf("%s-%d", crName, nextRevision) } cr, err = c.client.CertmanagerV1().CertificateRequests(cr.Namespace).Create(ctx, cr, metav1.CreateOptions{FieldManager: c.fieldManager}) @@ -417,14 +439,14 @@ func (c *controller) createNewCertificateRequest(ctx context.Context, crt *cmapi return nil } - if err := c.waitForCertificateRequestToExist(cr.Namespace, cr.Name); err != nil { + if err := c.waitForCertificateRequestToExist(ctx, cr.Namespace, cr.Name); err != nil { return fmt.Errorf("failed whilst waiting for CertificateRequest to exist - this may indicate an apiserver running slowly. Request will be retried. %w", err) } return nil } -func (c *controller) waitForCertificateRequestToExist(namespace, name string) error { - return wait.Poll(time.Millisecond*100, time.Second*5, func() (bool, error) { +func (c *controller) waitForCertificateRequestToExist(ctx context.Context, namespace, name string) error { + return wait.PollUntilContextTimeout(ctx, time.Millisecond*100, time.Second*5, false, func(_ context.Context) (bool, error) { _, err := c.certificateRequestLister.CertificateRequests(namespace).Get(name) if apierrors.IsNotFound(err) { return false, nil @@ -442,22 +464,14 @@ type controllerWrapper struct { *controller } -func (c *controllerWrapper) Register(ctx *controllerpkg.Context) (workqueue.RateLimitingInterface, []cache.InformerSynced, error) { +func (c *controllerWrapper) Register(ctx *controllerpkg.Context) (workqueue.TypedRateLimitingInterface[types.NamespacedName], []cache.InformerSynced, error) { // construct a new named logger to be reused throughout the controller log := logf.FromContext(ctx.RootContext, ControllerName) - ctrl, queue, mustSync := NewController(log, - ctx.CMClient, - ctx.KubeSharedInformerFactory, - ctx.SharedInformerFactory, - ctx.Recorder, - ctx.Clock, - ctx.CertificateOptions, - ctx.FieldManager, - ) + ctrl, queue, mustSync, err := NewController(log, ctx) c.controller = ctrl - return queue, mustSync, nil + return queue, mustSync, err } func init() { diff --git a/pkg/controller/certificates/requestmanager/requestmanager_controller_test.go b/pkg/controller/certificates/requestmanager/requestmanager_controller_test.go index 2d50b839ec6..c10593cb2d7 100644 --- a/pkg/controller/certificates/requestmanager/requestmanager_controller_test.go +++ b/pkg/controller/certificates/requestmanager/requestmanager_controller_test.go @@ -17,16 +17,17 @@ limitations under the License. package requestmanager import ( - "context" "fmt" "reflect" + "strings" "testing" "time" - "github.com/kr/pretty" + "github.com/google/go-cmp/cmp" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" coretesting "k8s.io/client-go/testing" "k8s.io/component-base/featuregate" featuregatetesting "k8s.io/component-base/featuregate/testing" @@ -35,15 +36,14 @@ import ( "github.com/cert-manager/cert-manager/internal/controller/feature" cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" - controllerpkg "github.com/cert-manager/cert-manager/pkg/controller" testpkg "github.com/cert-manager/cert-manager/pkg/controller/test" utilfeature "github.com/cert-manager/cert-manager/pkg/util/feature" "github.com/cert-manager/cert-manager/pkg/util/pki" "github.com/cert-manager/cert-manager/test/unit/gen" ) -func mustGenerateRSA(t *testing.T, keySize int) []byte { - pk, err := pki.GenerateRSAPrivateKey(keySize) +func mustGenerateRSA(t *testing.T) []byte { + pk, err := pki.GenerateRSAPrivateKey(2048) if err != nil { t.Fatal(err) } @@ -60,7 +60,7 @@ func relaxedCertificateRequestMatcher(l coretesting.Action, r coretesting.Action objL.Spec.Request = nil objR.Spec.Request = nil if !reflect.DeepEqual(objL, objR) { - return fmt.Errorf("unexpected difference between actions: %s", pretty.Diff(objL, objR)) + return fmt.Errorf("unexpected difference between actions (-want +got):\n%s", cmp.Diff(objL, objR)) } return nil } @@ -90,6 +90,14 @@ func TestProcessItem(t *testing.T) { }, Spec: cmapi.CertificateSpec{CommonName: "test-bundle-3"}}, ) + bundle4 := mustCreateCryptoBundle(t, &cmapi.Certificate{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "testns", + Name: strings.Repeat("a", 167) + "b" + strings.Repeat("c", 85), + UID: "test", + }, + Spec: cmapi.CertificateSpec{CommonName: "test-bundle-4"}}, + ) fixedNow := metav1.NewTime(time.Now()) fixedClock := fakeclock.NewFakeClock(fixedNow.Time) failedCRConditionPreviousIssuance := cmapi.CertificateRequestCondition{ @@ -108,10 +116,10 @@ func TestProcessItem(t *testing.T) { // key that should be passed to ProcessItem. // if not set, the 'namespace/name' of the 'Certificate' field will be used. // if neither is set, the key will be "" - key string + key types.NamespacedName // Featuregates to set for a particular test. - featuresToEnable []featuregate.Feature + featuresFlags map[featuregate.Feature]bool // Certificate to be synced for the test. // if not set, the 'key' will be passed to ProcessItem instead. @@ -131,10 +139,16 @@ func TestProcessItem(t *testing.T) { }{ "do nothing if an empty 'key' is used": {}, "do nothing if an invalid 'key' is used": { - key: "abc/def/ghi", + key: types.NamespacedName{ + Namespace: "abc", + Name: "def/ghi", + }, }, "do nothing if a key references a Certificate that does not exist": { - key: "namespace/name", + key: types.NamespacedName{ + Namespace: "namespace", + Name: "name", + }, }, "do nothing if Certificate has 'Issuing' condition set to 'false'": { certificate: gen.CertificateFrom(bundle1.certificate, @@ -178,7 +192,10 @@ func TestProcessItem(t *testing.T) { gen.SetCertificateStatusCondition(cmapi.CertificateCondition{Type: cmapi.CertificateConditionIssuing, Status: cmmeta.ConditionTrue}), ), }, - "create a CertificateRequest if none exists": { + "create a CertificateRequest if none exists and StableCertificateRequestName disabled": { + featuresFlags: map[featuregate.Feature]bool{ + feature.StableCertificateRequestName: false, + }, secrets: []runtime.Object{ &corev1.Secret{ ObjectMeta: metav1.ObjectMeta{Namespace: bundle1.certificate.Namespace, Name: "exists"}, @@ -193,6 +210,8 @@ func TestProcessItem(t *testing.T) { expectedActions: []testpkg.Action{ testpkg.NewCustomMatch(coretesting.NewCreateAction(cmapi.SchemeGroupVersion.WithResource("certificaterequests"), "testns", gen.CertificateRequestFrom(bundle1.certificateRequest, + gen.SetCertificateRequestName(""), + gen.SetCertificateRequestGenerateName("test-"), gen.SetCertificateRequestAnnotations(map[string]string{ cmapi.CertificateRequestPrivateKeyAnnotationKey: "exists", cmapi.CertificateRequestRevisionAnnotationKey: "1", @@ -200,8 +219,7 @@ func TestProcessItem(t *testing.T) { )), relaxedCertificateRequestMatcher), }, }, - "create a CertificateRequest if none exists and StableCertificateRequestName enabled": { - featuresToEnable: []featuregate.Feature{feature.StableCertificateRequestName}, + "create a CertificateRequest if none exists": { secrets: []runtime.Object{ &corev1.Secret{ ObjectMeta: metav1.ObjectMeta{Namespace: bundle3.certificate.Namespace, Name: "exists"}, @@ -217,7 +235,6 @@ func TestProcessItem(t *testing.T) { testpkg.NewCustomMatch(coretesting.NewCreateAction(cmapi.SchemeGroupVersion.WithResource("certificaterequests"), "testns", gen.CertificateRequestFrom(bundle3.certificateRequest, gen.SetCertificateRequestName("test-1"), - gen.SetCertificateRequestGenerateName(""), gen.SetCertificateRequestAnnotations(map[string]string{ cmapi.CertificateRequestPrivateKeyAnnotationKey: "exists", cmapi.CertificateRequestRevisionAnnotationKey: "1", @@ -225,11 +242,63 @@ func TestProcessItem(t *testing.T) { )), relaxedCertificateRequestMatcher), }, }, + "create a CertificateRequest if none exists (with long name)": { + secrets: []runtime.Object{ + &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{Namespace: bundle3.certificate.Namespace, Name: "exists"}, + Data: map[string][]byte{corev1.TLSPrivateKeyKey: bundle3.privateKeyBytes}, + }, + }, + certificate: gen.CertificateFrom(bundle4.certificate, + gen.SetCertificateNextPrivateKeySecretName("exists"), + gen.SetCertificateStatusCondition(cmapi.CertificateCondition{Type: cmapi.CertificateConditionIssuing, Status: cmmeta.ConditionTrue}), + gen.SetCertificateRevision(19), + ), + expectedEvents: []string{ + fmt.Sprintf(`Normal Requested Created new CertificateRequest resource "%s"`, strings.Repeat("a", 167)+"b-d3f4fc40a686edfd404adf1d3fb1530653988c878e6c9c07b2e2fa4001a21269-20"), + }, + expectedActions: []testpkg.Action{ + testpkg.NewCustomMatch(coretesting.NewCreateAction(cmapi.SchemeGroupVersion.WithResource("certificaterequests"), "testns", + gen.CertificateRequestFrom(bundle4.certificateRequest, + gen.SetCertificateRequestName(strings.Repeat("a", 167)+"b-d3f4fc40a686edfd404adf1d3fb1530653988c878e6c9c07b2e2fa4001a21269-20"), + gen.SetCertificateRequestAnnotations(map[string]string{ + cmapi.CertificateRequestPrivateKeyAnnotationKey: "exists", + cmapi.CertificateRequestRevisionAnnotationKey: "20", + }), + )), relaxedCertificateRequestMatcher), + }, + }, + "create a CertificateRequest if none exists (with long name and very large revision)": { + secrets: []runtime.Object{ + &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{Namespace: bundle3.certificate.Namespace, Name: "exists"}, + Data: map[string][]byte{corev1.TLSPrivateKeyKey: bundle3.privateKeyBytes}, + }, + }, + certificate: gen.CertificateFrom(bundle4.certificate, + gen.SetCertificateNextPrivateKeySecretName("exists"), + gen.SetCertificateStatusCondition(cmapi.CertificateCondition{Type: cmapi.CertificateConditionIssuing, Status: cmmeta.ConditionTrue}), + gen.SetCertificateRevision(999999999), + ), + expectedEvents: []string{ + fmt.Sprintf(`Normal Requested Created new CertificateRequest resource "%s"`, strings.Repeat("a", 167)+"b-d3f4fc40a686edfd404adf1d3fb1530653988c878e6c9c07b2e2fa4001a21269-1000000000"), + }, + expectedActions: []testpkg.Action{ + testpkg.NewCustomMatch(coretesting.NewCreateAction(cmapi.SchemeGroupVersion.WithResource("certificaterequests"), "testns", + gen.CertificateRequestFrom(bundle4.certificateRequest, + gen.SetCertificateRequestName(strings.Repeat("a", 167)+"b-d3f4fc40a686edfd404adf1d3fb1530653988c878e6c9c07b2e2fa4001a21269-1000000000"), + gen.SetCertificateRequestAnnotations(map[string]string{ + cmapi.CertificateRequestPrivateKeyAnnotationKey: "exists", + cmapi.CertificateRequestRevisionAnnotationKey: "1000000000", + }), + )), relaxedCertificateRequestMatcher), + }, + }, "delete the owned CertificateRequest and create a new one if existing one does not have the annotation": { secrets: []runtime.Object{ &corev1.Secret{ ObjectMeta: metav1.ObjectMeta{Namespace: "testns", Name: "exists"}, - Data: map[string][]byte{corev1.TLSPrivateKeyKey: mustGenerateRSA(t, 2048)}, + Data: map[string][]byte{corev1.TLSPrivateKeyKey: mustGenerateRSA(t)}, }, }, certificate: gen.CertificateFrom(bundle1.certificate, @@ -238,17 +307,19 @@ func TestProcessItem(t *testing.T) { ), requests: []runtime.Object{ gen.CertificateRequestFrom(bundle1.certificateRequest, + gen.SetCertificateRequestName("random-value"), gen.SetCertificateRequestAnnotations(map[string]string{ cmapi.CertificateRequestPrivateKeyAnnotationKey: "exists", cmapi.CertificateRequestRevisionAnnotationKey: "", }), ), }, - expectedEvents: []string{`Normal Requested Created new CertificateRequest resource "test-notrandom"`}, + expectedEvents: []string{`Normal Requested Created new CertificateRequest resource "test-1"`}, expectedActions: []testpkg.Action{ - testpkg.NewAction(coretesting.NewDeleteAction(cmapi.SchemeGroupVersion.WithResource("certificaterequests"), "testns", "test")), + testpkg.NewAction(coretesting.NewDeleteAction(cmapi.SchemeGroupVersion.WithResource("certificaterequests"), "testns", "random-value")), testpkg.NewCustomMatch(coretesting.NewCreateAction(cmapi.SchemeGroupVersion.WithResource("certificaterequests"), "testns", gen.CertificateRequestFrom(bundle1.certificateRequest, + gen.SetCertificateRequestName("test-1"), gen.SetCertificateRequestAnnotations(map[string]string{ cmapi.CertificateRequestPrivateKeyAnnotationKey: "exists", cmapi.CertificateRequestRevisionAnnotationKey: "1", @@ -260,7 +331,7 @@ func TestProcessItem(t *testing.T) { secrets: []runtime.Object{ &corev1.Secret{ ObjectMeta: metav1.ObjectMeta{Namespace: "testns", Name: "exists"}, - Data: map[string][]byte{corev1.TLSPrivateKeyKey: mustGenerateRSA(t, 2048)}, + Data: map[string][]byte{corev1.TLSPrivateKeyKey: mustGenerateRSA(t)}, }, }, certificate: gen.CertificateFrom(bundle1.certificate, @@ -269,17 +340,19 @@ func TestProcessItem(t *testing.T) { ), requests: []runtime.Object{ gen.CertificateRequestFrom(bundle1.certificateRequest, + gen.SetCertificateRequestName("random-value"), gen.SetCertificateRequestAnnotations(map[string]string{ cmapi.CertificateRequestPrivateKeyAnnotationKey: "exists", cmapi.CertificateRequestRevisionAnnotationKey: "invalid", }), ), }, - expectedEvents: []string{`Normal Requested Created new CertificateRequest resource "test-notrandom"`}, + expectedEvents: []string{`Normal Requested Created new CertificateRequest resource "test-1"`}, expectedActions: []testpkg.Action{ - testpkg.NewAction(coretesting.NewDeleteAction(cmapi.SchemeGroupVersion.WithResource("certificaterequests"), "testns", "test")), + testpkg.NewAction(coretesting.NewDeleteAction(cmapi.SchemeGroupVersion.WithResource("certificaterequests"), "testns", "random-value")), testpkg.NewCustomMatch(coretesting.NewCreateAction(cmapi.SchemeGroupVersion.WithResource("certificaterequests"), "testns", gen.CertificateRequestFrom(bundle1.certificateRequest, + gen.SetCertificateRequestName("test-1"), gen.SetCertificateRequestAnnotations(map[string]string{ cmapi.CertificateRequestPrivateKeyAnnotationKey: "exists", cmapi.CertificateRequestRevisionAnnotationKey: "1", @@ -300,6 +373,7 @@ func TestProcessItem(t *testing.T) { ), requests: []runtime.Object{ gen.CertificateRequestFrom(bundle1.certificateRequest, + gen.SetCertificateRequestName("random-value"), gen.SetCertificateRequestAnnotations(map[string]string{ cmapi.CertificateRequestPrivateKeyAnnotationKey: "exists", cmapi.CertificateRequestRevisionAnnotationKey: "1", @@ -320,6 +394,7 @@ func TestProcessItem(t *testing.T) { ), requests: []runtime.Object{ gen.CertificateRequestFrom(bundle1.certificateRequest, + gen.SetCertificateRequestName("random-value"), gen.SetCertificateRequestAnnotations(map[string]string{ cmapi.CertificateRequestPrivateKeyAnnotationKey: "exists", cmapi.CertificateRequestRevisionAnnotationKey: "1", @@ -327,11 +402,12 @@ func TestProcessItem(t *testing.T) { gen.SetCertificateRequestCSR([]byte("invalid")), ), }, - expectedEvents: []string{`Normal Requested Created new CertificateRequest resource "test-notrandom"`}, + expectedEvents: []string{`Normal Requested Created new CertificateRequest resource "test-1"`}, expectedActions: []testpkg.Action{ - testpkg.NewAction(coretesting.NewDeleteAction(cmapi.SchemeGroupVersion.WithResource("certificaterequests"), "testns", "test")), + testpkg.NewAction(coretesting.NewDeleteAction(cmapi.SchemeGroupVersion.WithResource("certificaterequests"), "testns", "random-value")), testpkg.NewCustomMatch(coretesting.NewCreateAction(cmapi.SchemeGroupVersion.WithResource("certificaterequests"), "testns", gen.CertificateRequestFrom(bundle1.certificateRequest, + gen.SetCertificateRequestName("test-1"), gen.SetCertificateRequestAnnotations(map[string]string{ cmapi.CertificateRequestPrivateKeyAnnotationKey: "exists", cmapi.CertificateRequestRevisionAnnotationKey: "1", @@ -343,7 +419,7 @@ func TestProcessItem(t *testing.T) { secrets: []runtime.Object{ &corev1.Secret{ ObjectMeta: metav1.ObjectMeta{Namespace: "testns", Name: "exists"}, - Data: map[string][]byte{corev1.TLSPrivateKeyKey: mustGenerateRSA(t, 2048)}, + Data: map[string][]byte{corev1.TLSPrivateKeyKey: mustGenerateRSA(t)}, }, }, certificate: gen.CertificateFrom(bundle1.certificate, @@ -352,23 +428,25 @@ func TestProcessItem(t *testing.T) { ), requests: []runtime.Object{ gen.CertificateRequestFrom(bundle1.certificateRequest, + gen.SetCertificateRequestName("test-3"), gen.SetCertificateRequestAnnotations(map[string]string{ cmapi.CertificateRequestPrivateKeyAnnotationKey: "exists", cmapi.CertificateRequestRevisionAnnotationKey: "3", }), ), gen.CertificateRequestFrom(bundle1.certificateRequest, - gen.SetCertificateRequestName("testing-number-2"), + gen.SetCertificateRequestName("test-4"), gen.SetCertificateRequestAnnotations(map[string]string{ cmapi.CertificateRequestPrivateKeyAnnotationKey: "exists", cmapi.CertificateRequestRevisionAnnotationKey: "4", }), ), }, - expectedEvents: []string{`Normal Requested Created new CertificateRequest resource "test-notrandom"`}, + expectedEvents: []string{`Normal Requested Created new CertificateRequest resource "test-1"`}, expectedActions: []testpkg.Action{ testpkg.NewCustomMatch(coretesting.NewCreateAction(cmapi.SchemeGroupVersion.WithResource("certificaterequests"), "testns", gen.CertificateRequestFrom(bundle1.certificateRequest, + gen.SetCertificateRequestName("test-1"), gen.SetCertificateRequestAnnotations(map[string]string{ cmapi.CertificateRequestPrivateKeyAnnotationKey: "exists", cmapi.CertificateRequestRevisionAnnotationKey: "1", @@ -380,7 +458,7 @@ func TestProcessItem(t *testing.T) { secrets: []runtime.Object{ &corev1.Secret{ ObjectMeta: metav1.ObjectMeta{Namespace: "testns", Name: "exists"}, - Data: map[string][]byte{corev1.TLSPrivateKeyKey: mustGenerateRSA(t, 2048)}, + Data: map[string][]byte{corev1.TLSPrivateKeyKey: mustGenerateRSA(t)}, }, }, certificate: gen.CertificateFrom(bundle1.certificate, @@ -389,6 +467,7 @@ func TestProcessItem(t *testing.T) { ), requests: []runtime.Object{ gen.CertificateRequestFrom(bundle1.certificateRequest, + gen.SetCertificateRequestName("test-1"), gen.SetCertificateRequestAnnotations(map[string]string{ cmapi.CertificateRequestPrivateKeyAnnotationKey: "exists", cmapi.CertificateRequestRevisionAnnotationKey: "1", @@ -397,18 +476,19 @@ func TestProcessItem(t *testing.T) { // included here just to ensure it does not get deleted as it is not for the // 'next' revision that is being requested gen.CertificateRequestFrom(bundle1.certificateRequest, - gen.SetCertificateRequestName("testing-number-2"), + gen.SetCertificateRequestName("test-4"), gen.SetCertificateRequestAnnotations(map[string]string{ cmapi.CertificateRequestPrivateKeyAnnotationKey: "exists", cmapi.CertificateRequestRevisionAnnotationKey: "4", }), ), }, - expectedEvents: []string{`Normal Requested Created new CertificateRequest resource "test-notrandom"`}, + expectedEvents: []string{`Normal Requested Created new CertificateRequest resource "test-1"`}, expectedActions: []testpkg.Action{ - testpkg.NewAction(coretesting.NewDeleteAction(cmapi.SchemeGroupVersion.WithResource("certificaterequests"), "testns", "test")), + testpkg.NewAction(coretesting.NewDeleteAction(cmapi.SchemeGroupVersion.WithResource("certificaterequests"), "testns", "test-1")), testpkg.NewCustomMatch(coretesting.NewCreateAction(cmapi.SchemeGroupVersion.WithResource("certificaterequests"), "testns", gen.CertificateRequestFrom(bundle1.certificateRequest, + gen.SetCertificateRequestName("test-1"), gen.SetCertificateRequestAnnotations(map[string]string{ cmapi.CertificateRequestPrivateKeyAnnotationKey: "exists", cmapi.CertificateRequestRevisionAnnotationKey: "1", @@ -430,6 +510,7 @@ func TestProcessItem(t *testing.T) { ), requests: []runtime.Object{ gen.CertificateRequestFrom(bundle1.certificateRequest, + gen.SetCertificateRequestName("test-6"), gen.SetCertificateRequestAnnotations(map[string]string{ cmapi.CertificateRequestPrivateKeyAnnotationKey: "exists", cmapi.CertificateRequestRevisionAnnotationKey: "6", @@ -438,18 +519,19 @@ func TestProcessItem(t *testing.T) { // included here just to ensure it does not get deleted as it is not for the // 'next' revision that is being requested gen.CertificateRequestFrom(bundle1.certificateRequest, - gen.SetCertificateRequestName("testing-number-2"), + gen.SetCertificateRequestName("test-5"), gen.SetCertificateRequestAnnotations(map[string]string{ cmapi.CertificateRequestPrivateKeyAnnotationKey: "exists", cmapi.CertificateRequestRevisionAnnotationKey: "5", }), ), }, - expectedEvents: []string{`Normal Requested Created new CertificateRequest resource "test-notrandom"`}, + expectedEvents: []string{`Normal Requested Created new CertificateRequest resource "test-6"`}, expectedActions: []testpkg.Action{ - testpkg.NewAction(coretesting.NewDeleteAction(cmapi.SchemeGroupVersion.WithResource("certificaterequests"), "testns", "test")), + testpkg.NewAction(coretesting.NewDeleteAction(cmapi.SchemeGroupVersion.WithResource("certificaterequests"), "testns", "test-6")), testpkg.NewCustomMatch(coretesting.NewCreateAction(cmapi.SchemeGroupVersion.WithResource("certificaterequests"), "testns", gen.CertificateRequestFrom(bundle2.certificateRequest, + gen.SetCertificateRequestName("test-6"), gen.SetCertificateRequestAnnotations(map[string]string{ cmapi.CertificateRequestPrivateKeyAnnotationKey: "exists", cmapi.CertificateRequestRevisionAnnotationKey: "6", @@ -461,7 +543,7 @@ func TestProcessItem(t *testing.T) { secrets: []runtime.Object{ &corev1.Secret{ ObjectMeta: metav1.ObjectMeta{Namespace: "testns", Name: "exists"}, - Data: map[string][]byte{corev1.TLSPrivateKeyKey: mustGenerateRSA(t, 2048)}, + Data: map[string][]byte{corev1.TLSPrivateKeyKey: mustGenerateRSA(t)}, }, }, certificate: gen.CertificateFrom(bundle1.certificate, @@ -471,17 +553,19 @@ func TestProcessItem(t *testing.T) { ), requests: []runtime.Object{ gen.CertificateRequestFrom(bundle1.certificateRequest, + gen.SetCertificateRequestName("test-6"), gen.SetCertificateRequestAnnotations(map[string]string{ cmapi.CertificateRequestPrivateKeyAnnotationKey: "exists", cmapi.CertificateRequestRevisionAnnotationKey: "6", }), ), }, - expectedEvents: []string{`Normal Requested Created new CertificateRequest resource "test-notrandom"`}, + expectedEvents: []string{`Normal Requested Created new CertificateRequest resource "test-6"`}, expectedActions: []testpkg.Action{ - testpkg.NewAction(coretesting.NewDeleteAction(cmapi.SchemeGroupVersion.WithResource("certificaterequests"), "testns", "test")), + testpkg.NewAction(coretesting.NewDeleteAction(cmapi.SchemeGroupVersion.WithResource("certificaterequests"), "testns", "test-6")), testpkg.NewCustomMatch(coretesting.NewCreateAction(cmapi.SchemeGroupVersion.WithResource("certificaterequests"), "testns", gen.CertificateRequestFrom(bundle1.certificateRequest, + gen.SetCertificateRequestName("test-6"), gen.SetCertificateRequestAnnotations(map[string]string{ cmapi.CertificateRequestPrivateKeyAnnotationKey: "exists", cmapi.CertificateRequestRevisionAnnotationKey: "6", @@ -504,17 +588,19 @@ func TestProcessItem(t *testing.T) { ), requests: []runtime.Object{ gen.CertificateRequestFrom(bundle1.certificateRequest, + gen.SetCertificateRequestName("test-6"), gen.SetCertificateRequestAnnotations(map[string]string{ cmapi.CertificateRequestPrivateKeyAnnotationKey: "exists", cmapi.CertificateRequestRevisionAnnotationKey: "6", }), ), }, - expectedEvents: []string{`Normal Requested Created new CertificateRequest resource "test-notrandom"`}, + expectedEvents: []string{`Normal Requested Created new CertificateRequest resource "test-6"`}, expectedActions: []testpkg.Action{ - testpkg.NewAction(coretesting.NewDeleteAction(cmapi.SchemeGroupVersion.WithResource("certificaterequests"), "testns", "test")), + testpkg.NewAction(coretesting.NewDeleteAction(cmapi.SchemeGroupVersion.WithResource("certificaterequests"), "testns", "test-6")), testpkg.NewCustomMatch(coretesting.NewCreateAction(cmapi.SchemeGroupVersion.WithResource("certificaterequests"), "testns", gen.CertificateRequestFrom(bundle1.certificateRequest, + gen.SetCertificateRequestName("test-6"), gen.SetCertificateRequestAnnotations(map[string]string{ cmapi.CertificateRequestPrivateKeyAnnotationKey: "exists", cmapi.CertificateRequestRevisionAnnotationKey: "6", @@ -536,6 +622,7 @@ func TestProcessItem(t *testing.T) { ), requests: []runtime.Object{ gen.CertificateRequestFrom(bundle1.certificateRequest, + gen.SetCertificateRequestName("random-value"), gen.SetCertificateRequestAnnotations(map[string]string{ cmapi.CertificateRequestPrivateKeyAnnotationKey: "exists", cmapi.CertificateRequestRevisionAnnotationKey: "6", @@ -557,13 +644,14 @@ func TestProcessItem(t *testing.T) { ), requests: []runtime.Object{ gen.CertificateRequestFrom(bundle1.certificateRequest, + gen.SetCertificateRequestName("random-value-1"), gen.SetCertificateRequestAnnotations(map[string]string{ cmapi.CertificateRequestPrivateKeyAnnotationKey: "exists", cmapi.CertificateRequestRevisionAnnotationKey: "6", }), ), gen.CertificateRequestFrom(bundle1.certificateRequest, - gen.SetCertificateRequestName("another-name-2"), + gen.SetCertificateRequestName("random-value-2"), gen.SetCertificateRequestAnnotations(map[string]string{ cmapi.CertificateRequestPrivateKeyAnnotationKey: "exists", cmapi.CertificateRequestRevisionAnnotationKey: "6", @@ -585,6 +673,7 @@ func TestProcessItem(t *testing.T) { ), requests: []runtime.Object{ gen.CertificateRequestFrom(bundle1.certificateRequest, + gen.SetCertificateRequestName("test-6"), gen.SetCertificateRequestAnnotations(map[string]string{ cmapi.CertificateRequestPrivateKeyAnnotationKey: "exists", cmapi.CertificateRequestRevisionAnnotationKey: "6", @@ -593,11 +682,12 @@ func TestProcessItem(t *testing.T) { gen.SetCertificateRequestFailureTime(metav1.Time{Time: fixedNow.Time.Add(time.Hour * -1)}), ), }, - expectedEvents: []string{`Normal Requested Created new CertificateRequest resource "test-notrandom"`}, + expectedEvents: []string{`Normal Requested Created new CertificateRequest resource "test-6"`}, expectedActions: []testpkg.Action{ - testpkg.NewAction(coretesting.NewDeleteAction(cmapi.SchemeGroupVersion.WithResource("certificaterequests"), "testns", "test")), + testpkg.NewAction(coretesting.NewDeleteAction(cmapi.SchemeGroupVersion.WithResource("certificaterequests"), "testns", "test-6")), testpkg.NewCustomMatch(coretesting.NewCreateAction(cmapi.SchemeGroupVersion.WithResource("certificaterequests"), "testns", gen.CertificateRequestFrom(bundle1.certificateRequest, + gen.SetCertificateRequestName("test-6"), gen.SetCertificateRequestAnnotations(map[string]string{ cmapi.CertificateRequestPrivateKeyAnnotationKey: "exists", cmapi.CertificateRequestRevisionAnnotationKey: "6", @@ -619,6 +709,7 @@ func TestProcessItem(t *testing.T) { ), requests: []runtime.Object{ gen.CertificateRequestFrom(bundle1.certificateRequest, + gen.SetCertificateRequestName("random-value"), gen.SetCertificateRequestAnnotations(map[string]string{ cmapi.CertificateRequestPrivateKeyAnnotationKey: "exists", cmapi.CertificateRequestRevisionAnnotationKey: "6", @@ -656,8 +747,8 @@ func TestProcessItem(t *testing.T) { } // Enable any features for a particular test - for _, feature := range test.featuresToEnable { - defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, feature, true)() + for feature, value := range test.featuresFlags { + featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, feature, value) } // Start the informers and begin processing updates @@ -665,15 +756,15 @@ func TestProcessItem(t *testing.T) { defer builder.Stop() key := test.key - if key == "" && test.certificate != nil { - key, err = controllerpkg.KeyFunc(test.certificate) - if err != nil { - t.Fatal(err) + if key == (types.NamespacedName{}) && test.certificate != nil { + key = types.NamespacedName{ + Name: test.certificate.Name, + Namespace: test.certificate.Namespace, } } // Call ProcessItem - err = w.controller.ProcessItem(context.Background(), key) + err = w.controller.ProcessItem(t.Context(), key) switch { case err != nil: if test.err != err.Error() { @@ -691,9 +782,6 @@ func TestProcessItem(t *testing.T) { if err := builder.AllActionsExecuted(); err != nil { builder.T.Error(err) } - if err := builder.AllReactorsCalled(); err != nil { - builder.T.Error(err) - } }) } } diff --git a/pkg/controller/certificates/requestmanager/util_test.go b/pkg/controller/certificates/requestmanager/util_test.go index f2403110833..a5bfed2e63a 100644 --- a/pkg/controller/certificates/requestmanager/util_test.go +++ b/pkg/controller/certificates/requestmanager/util_test.go @@ -19,8 +19,7 @@ package requestmanager import ( "crypto" "crypto/x509" - "encoding/pem" - "fmt" + "maps" "testing" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -78,7 +77,7 @@ func createCryptoBundle(originalCert *cmapi.Certificate) (*cryptoBundle, error) return nil, err } - privateKey, err := pki.GeneratePrivateKeyForCertificate(crt) + csrPEM, privateKey, err := gen.CSRForCertificate(crt) if err != nil { return nil, err } @@ -88,26 +87,15 @@ func createCryptoBundle(originalCert *cmapi.Certificate) (*cryptoBundle, error) return nil, err } - csrPEM, err := generateCSRImpl(crt, privateKeyBytes) - if err != nil { - return nil, err - } - csr, err := pki.DecodeX509CertificateRequestBytes(csrPEM) if err != nil { return nil, err } annotations := make(map[string]string) - for k, v := range crt.Annotations { - annotations[k] = v - } - if crt.Status.Revision != nil { - annotations[cmapi.CertificateRequestRevisionAnnotationKey] = fmt.Sprintf("%d", crt.Status.Revision) - } else { - annotations[cmapi.CertificateRequestRevisionAnnotationKey] = "1" - } + maps.Copy(annotations, crt.Annotations) + annotations[cmapi.CertificateRequestRevisionAnnotationKey] = "NOT SET" annotations[cmapi.CertificateRequestPrivateKeyAnnotationKey] = crt.Spec.SecretName annotations[cmapi.CertificateNameKey] = crt.Name if crt.Status.NextPrivateKeySecretName != nil { @@ -115,7 +103,7 @@ func createCryptoBundle(originalCert *cmapi.Certificate) (*cryptoBundle, error) } certificateRequest := &cmapi.CertificateRequest{ ObjectMeta: metav1.ObjectMeta{ - GenerateName: crt.Name + "-", + Name: "NOT SET", Namespace: crt.Namespace, OwnerReferences: []metav1.OwnerReference{*metav1.NewControllerRef(crt, certificateGvk)}, Annotations: annotations, @@ -128,7 +116,7 @@ func createCryptoBundle(originalCert *cmapi.Certificate) (*cryptoBundle, error) }, } - unsignedCert, err := pki.GenerateTemplateFromCertificateRequest(certificateRequest) + unsignedCert, err := pki.CertificateTemplateFromCertificateRequest(certificateRequest) if err != nil { return nil, err } @@ -178,26 +166,3 @@ func createCryptoBundle(originalCert *cmapi.Certificate) (*cryptoBundle, error) certBytes: certBytes, }, nil } - -func generateCSRImpl(crt *cmapi.Certificate, pk []byte) ([]byte, error) { - csr, err := pki.GenerateCSR(crt) - if err != nil { - return nil, err - } - - signer, err := pki.DecodePrivateKeyBytes(pk) - if err != nil { - return nil, err - } - - csrDER, err := pki.EncodeCSR(csr, signer) - if err != nil { - return nil, err - } - - csrPEM := pem.EncodeToMemory(&pem.Block{ - Type: "CERTIFICATE REQUEST", Bytes: csrDER, - }) - - return csrPEM, nil -} diff --git a/pkg/controller/certificates/revisionmanager/fuzz_test.go b/pkg/controller/certificates/revisionmanager/fuzz_test.go new file mode 100644 index 00000000000..93ff1fa838b --- /dev/null +++ b/pkg/controller/certificates/revisionmanager/fuzz_test.go @@ -0,0 +1,115 @@ +/* +Copyright 2024 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. +*/ + +package revisionmanager + +import ( + "testing" + + gfh "github.com/AdaLogics/go-fuzz-headers" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + + v1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" + testpkg "github.com/cert-manager/cert-manager/pkg/controller/test" +) + +// FuzzProcessItem tests the revisionmanager controllers ProcessItem() method. +// It creates up to 10 random certificate requests and up to 10 certificates. +// All of these objects might be invalid and as such the fuzzer +// overapproximates which can result in false positives. +// The fuzzer does not verify how Cert-Manager behaves. It tests for panics +// or unrecoverable issues such as stack overflows, excessive memory usage, +// deadlocks, inifinite loops and other similar issues. +func FuzzProcessItem(f *testing.F) { + f.Fuzz(func(t *testing.T, + data []byte, + numberOfCerts, + numberOfRequests int, + ) { + var certificateName, certificateNamespace string + fdp := gfh.NewConsumer(data) + + // Create up to 10 random certificate requests + requests := make([]runtime.Object, 0) + for range numberOfRequests % 10 { + request := &v1.CertificateRequest{} + err := fdp.GenerateStruct(request) + if err != nil { + if len(requests) == 0 { + return + } + break + } + requests = append(requests, request) + } + if len(requests) == 0 { + return + } + + // Create up to 10 random certificates + existingCertManagerObjects := make([]runtime.Object, 0) + for i := range numberOfCerts % 10 { + cert := &v1.Certificate{} + err := fdp.GenerateStruct(cert) + if err != nil { + // If the fuzzer fails to create a certificate + // here, we return if it has not created + // any certificates. If it has created one or + // more certificates, the fuzzer proceeds with + // that. + if len(existingCertManagerObjects) == 0 { + return + } + break + } + if i == 0 { + certificateName = cert.Name + certificateNamespace = cert.Namespace + } + existingCertManagerObjects = append(existingCertManagerObjects, cert) + } + + // Create the builder + builder := &testpkg.Builder{ + T: t, + StringGenerator: func(i int) string { return "notrandom" }, + } + // Add created objects to builder + builder.CertManagerObjects = append(builder.CertManagerObjects, existingCertManagerObjects...) + builder.CertManagerObjects = append(builder.CertManagerObjects, requests...) + + builder.Init() + + // Register informers used by the controller using the registration wrapper + w := &controllerWrapper{} + _, _, err := w.Register(builder.Context) + if err != nil { + t.Fatal(err) + } + + builder.Start() + defer builder.Stop() + + key := types.NamespacedName{ + Name: certificateName, + Namespace: certificateNamespace, + } + + // Call ProcessItem. This is the API that the fuzzer tests. + _ = w.controller.ProcessItem(t.Context(), key) + }) +} diff --git a/pkg/controller/certificates/revisionmanager/revisionmanager_controller.go b/pkg/controller/certificates/revisionmanager/revisionmanager_controller.go index 290b1957645..c8a041868ba 100644 --- a/pkg/controller/certificates/revisionmanager/revisionmanager_controller.go +++ b/pkg/controller/certificates/revisionmanager/revisionmanager_controller.go @@ -19,9 +19,9 @@ package revisionmanager import ( "context" "errors" + "fmt" "sort" "strconv" - "time" "github.com/go-logr/logr" apierrors "k8s.io/apimachinery/pkg/api/errors" @@ -35,7 +35,6 @@ import ( cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" cmclient "github.com/cert-manager/cert-manager/pkg/client/clientset/versioned" - cminformers "github.com/cert-manager/cert-manager/pkg/client/informers/externalversions" cmlisters "github.com/cert-manager/cert-manager/pkg/client/listers/certmanager/v1" controllerpkg "github.com/cert-manager/cert-manager/pkg/controller" "github.com/cert-manager/cert-manager/pkg/controller/certificates" @@ -58,21 +57,30 @@ type revision struct { types.NamespacedName } -func NewController(log logr.Logger, client cmclient.Interface, cmFactory cminformers.SharedInformerFactory) (*controller, workqueue.RateLimitingInterface, []cache.InformerSynced) { +func NewController(log logr.Logger, ctx *controllerpkg.Context) (*controller, workqueue.TypedRateLimitingInterface[types.NamespacedName], []cache.InformerSynced, error) { // create a queue used to queue up items to be processed - queue := workqueue.NewNamedRateLimitingQueue(workqueue.NewItemExponentialFailureRateLimiter(time.Second*1, time.Second*30), ControllerName) + queue := workqueue.NewTypedRateLimitingQueueWithConfig( + controllerpkg.DefaultCertificateRateLimiter(), + workqueue.TypedRateLimitingQueueConfig[types.NamespacedName]{ + Name: ControllerName, + }, + ) // obtain references to all the informers used by this controller - certificateInformer := cmFactory.Certmanager().V1().Certificates() - certificateRequestInformer := cmFactory.Certmanager().V1().CertificateRequests() + certificateInformer := ctx.SharedInformerFactory.Certmanager().V1().Certificates() + certificateRequestInformer := ctx.SharedInformerFactory.Certmanager().V1().CertificateRequests() - certificateInformer.Informer().AddEventHandler(&controllerpkg.QueuingEventHandler{Queue: queue}) - certificateRequestInformer.Informer().AddEventHandler(&controllerpkg.BlockingEventHandler{ + if _, err := certificateInformer.Informer().AddEventHandler(&controllerpkg.QueuingEventHandler{Queue: queue}); err != nil { + return nil, nil, nil, fmt.Errorf("error setting up event handler: %v", err) + } + if _, err := certificateRequestInformer.Informer().AddEventHandler(&controllerpkg.BlockingEventHandler{ // Trigger reconciles on changes to any 'owned' CertificateRequest resources WorkFunc: certificates.EnqueueCertificatesForResourceUsingPredicates(log, queue, certificateInformer.Lister(), labels.Everything(), predicate.ResourceOwnerOf, ), - }) + }); err != nil { + return nil, nil, nil, fmt.Errorf("error setting up event handler: %v", err) + } // build a list of InformerSynced functions that will be returned by the Register method. // the controller will only begin processing items once all of these informers have synced. @@ -84,40 +92,31 @@ func NewController(log logr.Logger, client cmclient.Interface, cmFactory cminfor return &controller{ certificateLister: certificateInformer.Lister(), certificateRequestLister: certificateRequestInformer.Lister(), - client: client, - }, queue, mustSync + client: ctx.CMClient, + }, queue, mustSync, nil } // ProcessItem will attempt to garbage collect old CertificateRequests based // upon `spec.revisionHistoryLimit`. This controller will only act on // Certificates which are in a Ready state and this value is set. -func (c *controller) ProcessItem(ctx context.Context, key string) error { +func (c *controller) ProcessItem(ctx context.Context, key types.NamespacedName) error { log := logf.FromContext(ctx).WithValues("key", key) ctx = logf.NewContext(ctx, log) - namespace, name, err := cache.SplitMetaNamespaceKey(key) - if err != nil { - log.Error(err, "invalid resource key passed to ProcessItem") - return nil - } + namespace, name := key.Namespace, key.Name crt, err := c.certificateLister.Certificates(namespace).Get(name) - if apierrors.IsNotFound(err) { - log.V(logf.DebugLevel).Info("certificate not found for key", "error", err.Error()) - return nil - } - if err != nil { + if err != nil && !apierrors.IsNotFound(err) { return err } - - log = logf.WithResource(log, crt) - - // If RevisionHistoryLimit is nil, don't attempt to garbage collect old - // CertificateRequests - if crt.Spec.RevisionHistoryLimit == nil { + if crt == nil || crt.DeletionTimestamp != nil { + // If the Certificate object was/ is being deleted, we don't want to start deleting + // CertificateRequests last minute in the same namespace. return nil } + log = logf.WithResource(log, crt) + // Only garbage collect over Certificates that are in a Ready=True condition. if !apiutil.CertificateHasCondition(crt, cmapi.CertificateCondition{ Type: cmapi.CertificateConditionReady, @@ -134,12 +133,19 @@ func (c *controller) ProcessItem(ctx context.Context, key string) error { } // Fetch and delete all CertificateRequests that need to be deleted - limit := int(*crt.Spec.RevisionHistoryLimit) + // If RevisionHistoryLimit is nil, then default to 1 + var limit int + if crt.Spec.RevisionHistoryLimit == nil { + limit = 1 + } else { + limit = int(*crt.Spec.RevisionHistoryLimit) + } + toDelete := certificateRequestsToDelete(log, limit, requests) for _, req := range toDelete { logf.WithRelatedResourceName(log, req.Name, req.Namespace, cmapi.CertificateRequestKind). - WithValues("revision", req.rev).Info("garbage collecting old certificate request revsion") + WithValues("revision", req.rev).Info("garbage collecting old certificate request revision") err = c.client.CertmanagerV1().CertificateRequests(req.Namespace).Delete(ctx, req.Name, metav1.DeleteOptions{}) if apierrors.IsNotFound(err) { continue @@ -169,13 +175,13 @@ func certificateRequestsToDelete(log logr.Logger, limit int, requests []*cmapi.C log = logf.WithRelatedResource(log, req) if req.Annotations == nil || req.Annotations[cmapi.CertificateRequestRevisionAnnotationKey] == "" { - log.Error(errors.New("skipping processing request with missing revsion"), "") + log.Error(errors.New("skipping processing request with missing revision"), "") continue } rn, err := strconv.Atoi(req.Annotations[cmapi.CertificateRequestRevisionAnnotationKey]) if err != nil { - log.Error(err, "failed to parse request revsion") + log.Error(err, "failed to parse request revision") continue } @@ -186,7 +192,7 @@ func certificateRequestsToDelete(log logr.Logger, limit int, requests []*cmapi.C return revisions[i].rev < revisions[j].rev }) - // Return the oldest revsions which are over the limit + // Return the oldest revisions which are over the limit remaining := len(revisions) - limit if remaining < 0 { return nil @@ -201,14 +207,14 @@ type controllerWrapper struct { *controller } -func (c *controllerWrapper) Register(ctx *controllerpkg.Context) (workqueue.RateLimitingInterface, []cache.InformerSynced, error) { +func (c *controllerWrapper) Register(ctx *controllerpkg.Context) (workqueue.TypedRateLimitingInterface[types.NamespacedName], []cache.InformerSynced, error) { // construct a new named logger to be reused throughout the controller log := logf.FromContext(ctx.RootContext, ControllerName) - ctrl, queue, mustSync := NewController(log, ctx.CMClient, ctx.SharedInformerFactory) + ctrl, queue, mustSync, err := NewController(log, ctx) c.controller = ctrl - return queue, mustSync, nil + return queue, mustSync, err } func init() { diff --git a/pkg/controller/certificates/revisionmanager/revisionmanager_controller_test.go b/pkg/controller/certificates/revisionmanager/revisionmanager_controller_test.go index a4286287a01..a7ac5584afe 100644 --- a/pkg/controller/certificates/revisionmanager/revisionmanager_controller_test.go +++ b/pkg/controller/certificates/revisionmanager/revisionmanager_controller_test.go @@ -17,11 +17,10 @@ limitations under the License. package revisionmanager import ( - "context" "reflect" "testing" - logtesting "github.com/go-logr/logr/testing" + "github.com/go-logr/logr/testr" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" @@ -29,7 +28,6 @@ import ( cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" - controllerpkg "github.com/cert-manager/cert-manager/pkg/controller" testpkg "github.com/cert-manager/cert-manager/pkg/controller/test" "github.com/cert-manager/cert-manager/test/unit/gen" ) @@ -52,7 +50,7 @@ func TestProcessItem(t *testing.T) { // key that should be passed to ProcessItem. // if not set, the 'namespace/name' of the 'Certificate' field will be used. // if neither is set, the key will be "" - key string + key types.NamespacedName // Certificate to be synced for the test. // if not set, the 'key' will be passed to ProcessItem instead. @@ -68,15 +66,20 @@ func TestProcessItem(t *testing.T) { }{ "do nothing if an empty 'key' is used": {}, "do nothing if an invalid 'key' is used": { - key: "abc/def/ghi", + key: types.NamespacedName{ + Namespace: "abc", + Name: "def/ghi", + }, }, "do nothing if a key references a Certificate that does not exist": { - key: "namespace/name", + key: types.NamespacedName{ + Namespace: "namespace", + Name: "name", + }, }, "do nothing if Certificate is not in a Ready=True state": { certificate: gen.CertificateFrom(baseCrt, gen.SetCertificateStatusCondition(cmapi.CertificateCondition{Type: cmapi.CertificateConditionIssuing, Status: cmmeta.ConditionFalse}), - gen.SetCertificateRevisionHistoryLimit(1), ), requests: []runtime.Object{ gen.CertificateRequestFrom(baseCR, @@ -91,13 +94,11 @@ func TestProcessItem(t *testing.T) { "do nothing if no requests exist": { certificate: gen.CertificateFrom(baseCrt, gen.SetCertificateStatusCondition(cmapi.CertificateCondition{Type: cmapi.CertificateConditionReady, Status: cmmeta.ConditionTrue}), - gen.SetCertificateRevisionHistoryLimit(1), ), }, "do nothing if requests don't have or bad revisions set": { certificate: gen.CertificateFrom(baseCrt, gen.SetCertificateStatusCondition(cmapi.CertificateCondition{Type: cmapi.CertificateConditionReady, Status: cmmeta.ConditionTrue}), - gen.SetCertificateRevisionHistoryLimit(1), ), requests: []runtime.Object{ gen.CertificateRequestFrom(baseCR, @@ -112,7 +113,6 @@ func TestProcessItem(t *testing.T) { "do nothing if requests aren't owned by this Certificate": { certificate: gen.CertificateFrom(baseCrt, gen.SetCertificateStatusCondition(cmapi.CertificateCondition{Type: cmapi.CertificateConditionReady, Status: cmmeta.ConditionTrue}), - gen.SetCertificateRevisionHistoryLimit(1), ), requests: []runtime.Object{ gen.CertificateRequestFrom(baseCRNoOwner, @@ -141,25 +141,9 @@ func TestProcessItem(t *testing.T) { ), }, }, - "do nothing if revision limit is not set": { - certificate: gen.CertificateFrom(baseCrt, - gen.SetCertificateStatusCondition(cmapi.CertificateCondition{Type: cmapi.CertificateConditionReady, Status: cmmeta.ConditionTrue}), - ), - requests: []runtime.Object{ - gen.CertificateRequestFrom(baseCR, - gen.SetCertificateRequestName("cr-1"), - gen.SetCertificateRequestRevision("1"), - ), - gen.CertificateRequestFrom(baseCR, - gen.SetCertificateRequestName("cr-2"), - gen.SetCertificateRequestRevision("2"), - ), - }, - }, - "delete 1 request if limit is 1 and 2 requests exist": { + "delete 1 request if 2 requests exist since the default limit is 1": { certificate: gen.CertificateFrom(baseCrt, gen.SetCertificateStatusCondition(cmapi.CertificateCondition{Type: cmapi.CertificateConditionReady, Status: cmmeta.ConditionTrue}), - gen.SetCertificateRevisionHistoryLimit(1), ), requests: []runtime.Object{ gen.CertificateRequestFrom(baseCR, @@ -239,15 +223,15 @@ func TestProcessItem(t *testing.T) { defer builder.Stop() key := test.key - if key == "" && test.certificate != nil { - key, err = controllerpkg.KeyFunc(test.certificate) - if err != nil { - t.Fatal(err) + if key == (types.NamespacedName{}) && test.certificate != nil { + key = types.NamespacedName{ + Name: test.certificate.Name, + Namespace: test.certificate.Namespace, } } // Call ProcessItem - err = w.controller.ProcessItem(context.Background(), key) + err = w.controller.ProcessItem(t.Context(), key) switch { case err != nil: if test.err != err.Error() { @@ -265,9 +249,6 @@ func TestProcessItem(t *testing.T) { if err := builder.AllActionsExecuted(); err != nil { builder.T.Error(err) } - if err := builder.AllReactorsCalled(); err != nil { - builder.T.Error(err) - } }) } } @@ -315,7 +296,7 @@ func TestCertificateRequestsToDelete(t *testing.T) { limit: 1, exp: []revision{}, }, - "multiple requests with some with good revsions should return list in order": { + "multiple requests with some with good revisions should return list in order": { input: []*cmapi.CertificateRequest{ gen.CertificateRequestFrom(baseCR, gen.SetCertificateRequestName("cr-1"), @@ -367,7 +348,7 @@ func TestCertificateRequestsToDelete(t *testing.T) { }, }, }, - "multiple requests with some with good revsions but less than the limit, should return list in order under limit": { + "multiple requests with some with good revisions but less than the limit, should return list in order under limit": { input: []*cmapi.CertificateRequest{ gen.CertificateRequestFrom(baseCR, gen.SetCertificateRequestName("cr-1"), @@ -409,7 +390,7 @@ func TestCertificateRequestsToDelete(t *testing.T) { for name, test := range tests { t.Run(name, func(t *testing.T) { - log := logtesting.NewTestLogger(t) + log := testr.New(t) output := certificateRequestsToDelete(log, test.limit, test.input) if !reflect.DeepEqual(test.exp, output) { t.Errorf("unexpected prune sort response, exp=%v got=%v", diff --git a/pkg/controller/certificates/trigger/fuzz_test.go b/pkg/controller/certificates/trigger/fuzz_test.go new file mode 100644 index 00000000000..249cf410167 --- /dev/null +++ b/pkg/controller/certificates/trigger/fuzz_test.go @@ -0,0 +1,152 @@ +/* +Copyright 2024 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. +*/ + +package trigger + +import ( + "context" + "fmt" + "testing" + "time" + + gfh "github.com/AdaLogics/go-fuzz-headers" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + fakeclock "k8s.io/utils/clock/testing" + + "github.com/cert-manager/cert-manager/internal/controller/certificates/policies" + cmapiv1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" + testpkg "github.com/cert-manager/cert-manager/pkg/controller/test" +) + +func mockShouldReissue() policies.Func { + return func(policies.Input) (string, string, bool) { + return "ForceTriggered", "Re-issuance forced by unit test case", true + } +} + +// FuzzProcessItem tests the trigger controllers ProcessItem() method. +// It creates up to 10 random certificate requests and up to 10 certificates. +// All of these objects might be invalid and as such the fuzzer +// overapproximates which can result in false positives. +// The fuzzer does not verify how Cert-Manager behaves. It tests for panics +// or unrecoverable issues such as stack overflows, excessive memory usage, +// deadlocks, inifinite loops and other similar issues. +func FuzzProcessItem(f *testing.F) { + f.Fuzz(func(t *testing.T, data []byte, + returnErr bool, + numberOfCerts, + numberOfsecrets int) { + + fdp := gfh.NewConsumer(data) + existingCertificate := &cmapiv1.Certificate{} + err := fdp.GenerateStruct(existingCertificate) + if err != nil { + return + } + + var certificateName, certificateNamespace string + + // Create up to 10 certificates + existingCertManagerObjects := make([]runtime.Object, 0) + for i := range numberOfCerts % 10 { + cert := &cmapiv1.Certificate{} + err := fdp.GenerateStruct(cert) + if err != nil { + if len(existingCertManagerObjects) == 0 { + return + } + break + } + if i == 0 { + // Save the name and namespace of the first + // certificate for later. + certificateName = cert.Name + certificateNamespace = cert.Namespace + } + existingCertManagerObjects = append(existingCertManagerObjects, cert) + } + if len(existingCertManagerObjects) == 0 { + return + } + + // Create up to 10 secrets + existingKubeObjects := make([]runtime.Object, 0) + for range numberOfsecrets % 10 { + secret := &corev1.Secret{} + err := fdp.GenerateStruct(secret) + if err != nil { + if len(existingKubeObjects) == 0 { + return + } + break + } + existingKubeObjects = append(existingKubeObjects, secret) + } + if len(existingKubeObjects) == 0 { + return + } + + fixedNow := metav1.NewTime(time.Now()) + fixedClock := fakeclock.NewFakeClock(fixedNow.Time) + + // Create the builder + builder := &testpkg.Builder{ + T: t, + Clock: fixedClock, + } + builder.CertManagerObjects = append(builder.CertManagerObjects, existingCertificate) + builder.CertManagerObjects = append(builder.CertManagerObjects, existingCertManagerObjects...) + builder.KubeObjects = append(builder.KubeObjects, existingKubeObjects...) + builder.Init() + + w := &controllerWrapper{} + _, _, err = w.Register(builder.Context) + if err != nil { + panic(err) + } + + w.shouldReissue = func(i policies.Input) (string, string, bool) { + return mockShouldReissue()(i) + } + + mockDataForCertificateReturn := policies.Input{} + mockDataForCertificateReturn.Certificate = existingCertificate + + var mockDataForCertificateReturnErr error + if returnErr { + mockDataForCertificateReturnErr = fmt.Errorf("fuzz err") + } else { + mockDataForCertificateReturnErr = nil + } + w.dataForCertificate = func(context.Context, *cmapiv1.Certificate) (policies.Input, error) { + return mockDataForCertificateReturn, mockDataForCertificateReturnErr + } + + builder.Start() + defer builder.Stop() + + key := types.NamespacedName{ + Name: certificateName, + Namespace: certificateNamespace, + } + + // Call ProcessItem. This is the API that the fuzzer tests. + _ = w.controller.ProcessItem(t.Context(), key) + }) +} diff --git a/pkg/controller/certificates/trigger/trigger_controller.go b/pkg/controller/certificates/trigger/trigger_controller.go index c2bc970bb17..a3cd1456076 100644 --- a/pkg/controller/certificates/trigger/trigger_controller.go +++ b/pkg/controller/certificates/trigger/trigger_controller.go @@ -24,11 +24,10 @@ import ( "github.com/go-logr/logr" corev1 "k8s.io/api/core/v1" - apierrors "k8s.io/apimachinery/pkg/api/errors" + k8sErrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" - "k8s.io/client-go/informers" - corelisters "k8s.io/client-go/listers/core/v1" + "k8s.io/apimachinery/pkg/types" "k8s.io/client-go/tools/cache" "k8s.io/client-go/tools/record" "k8s.io/client-go/util/workqueue" @@ -37,17 +36,18 @@ import ( internalcertificates "github.com/cert-manager/cert-manager/internal/controller/certificates" "github.com/cert-manager/cert-manager/internal/controller/certificates/policies" "github.com/cert-manager/cert-manager/internal/controller/feature" + internalinformers "github.com/cert-manager/cert-manager/internal/informers" apiutil "github.com/cert-manager/cert-manager/pkg/api/util" cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" cmclient "github.com/cert-manager/cert-manager/pkg/client/clientset/versioned" - cminformers "github.com/cert-manager/cert-manager/pkg/client/informers/externalversions" cmlisters "github.com/cert-manager/cert-manager/pkg/client/listers/certmanager/v1" controllerpkg "github.com/cert-manager/cert-manager/pkg/controller" "github.com/cert-manager/cert-manager/pkg/controller/certificates" logf "github.com/cert-manager/cert-manager/pkg/logs" "github.com/cert-manager/cert-manager/pkg/scheduler" utilfeature "github.com/cert-manager/cert-manager/pkg/util/feature" + "github.com/cert-manager/cert-manager/pkg/util/pki" "github.com/cert-manager/cert-manager/pkg/util/predicate" ) @@ -67,10 +67,10 @@ const ( type controller struct { certificateLister cmlisters.CertificateLister certificateRequestLister cmlisters.CertificateRequestLister - secretLister corelisters.SecretLister + secretLister internalinformers.SecretLister client cmclient.Interface recorder record.EventRecorder - scheduledWorkQueue scheduler.ScheduledWorkQueue + scheduledWorkQueue scheduler.ScheduledWorkQueue[types.NamespacedName] // fieldManager is the string which will be used as the Field Manager on // fields created or edited by the cert-manager Kubernetes client during @@ -85,34 +85,40 @@ type controller struct { func NewController( log logr.Logger, - client cmclient.Interface, - factory informers.SharedInformerFactory, - cmFactory cminformers.SharedInformerFactory, - recorder record.EventRecorder, - clock clock.Clock, + ctx *controllerpkg.Context, shouldReissue policies.Func, - fieldManager string, -) (*controller, workqueue.RateLimitingInterface, []cache.InformerSynced) { +) (*controller, workqueue.TypedRateLimitingInterface[types.NamespacedName], []cache.InformerSynced, error) { // create a queue used to queue up items to be processed - queue := workqueue.NewNamedRateLimitingQueue(workqueue.NewItemExponentialFailureRateLimiter(time.Second*1, time.Second*30), ControllerName) + queue := workqueue.NewTypedRateLimitingQueueWithConfig( + controllerpkg.DefaultCertificateRateLimiter(), + workqueue.TypedRateLimitingQueueConfig[types.NamespacedName]{ + Name: ControllerName, + }, + ) // obtain references to all the informers used by this controller - certificateInformer := cmFactory.Certmanager().V1().Certificates() - certificateRequestInformer := cmFactory.Certmanager().V1().CertificateRequests() - secretsInformer := factory.Core().V1().Secrets() + certificateInformer := ctx.SharedInformerFactory.Certmanager().V1().Certificates() + certificateRequestInformer := ctx.SharedInformerFactory.Certmanager().V1().CertificateRequests() + secretsInformer := ctx.KubeSharedInformerFactory.Secrets() - certificateInformer.Informer().AddEventHandler(&controllerpkg.QueuingEventHandler{Queue: queue}) + if _, err := certificateInformer.Informer().AddEventHandler(&controllerpkg.QueuingEventHandler{Queue: queue}); err != nil { + return nil, nil, nil, fmt.Errorf("error setting up event handler: %v", err) + } // When a CertificateRequest resource changes, enqueue the Certificate resource that owns it. - certificateRequestInformer.Informer().AddEventHandler(&controllerpkg.BlockingEventHandler{ + if _, err := certificateRequestInformer.Informer().AddEventHandler(&controllerpkg.BlockingEventHandler{ WorkFunc: certificates.EnqueueCertificatesForResourceUsingPredicates(log, queue, certificateInformer.Lister(), labels.Everything(), predicate.ResourceOwnerOf), - }) + }); err != nil { + return nil, nil, nil, fmt.Errorf("error setting up event handler: %v", err) + } // When a Secret resource changes, enqueue any Certificate resources that name it as spec.secretName. - secretsInformer.Informer().AddEventHandler(&controllerpkg.BlockingEventHandler{ + if _, err := secretsInformer.Informer().AddEventHandler(&controllerpkg.BlockingEventHandler{ // Trigger reconciles on changes to the Secret named `spec.secretName` WorkFunc: certificates.EnqueueCertificatesForResourceUsingPredicates(log, queue, certificateInformer.Lister(), labels.Everything(), predicate.ExtractResourceName(predicate.CertificateSecretName)), - }) + }); err != nil { + return nil, nil, nil, fmt.Errorf("error setting up event handler: %v", err) + } // build a list of InformerSynced functions that will be returned by the Register method. // the controller will only begin processing items once all of these informers have synced. @@ -126,38 +132,36 @@ func NewController( certificateLister: certificateInformer.Lister(), certificateRequestLister: certificateRequestInformer.Lister(), secretLister: secretsInformer.Lister(), - client: client, - recorder: recorder, - scheduledWorkQueue: scheduler.NewScheduledWorkQueue(clock, queue.Add), - fieldManager: fieldManager, + client: ctx.CMClient, + recorder: ctx.Recorder, + scheduledWorkQueue: scheduler.NewScheduledWorkQueue(ctx.Clock, queue.Add), + fieldManager: ctx.FieldManager, // The following are used for testing purposes. - clock: clock, + clock: ctx.Clock, shouldReissue: shouldReissue, dataForCertificate: (&policies.Gatherer{ CertificateRequestLister: certificateRequestInformer.Lister(), SecretLister: secretsInformer.Lister(), }).DataForCertificate, - }, queue, mustSync + }, queue, mustSync, nil } -func (c *controller) ProcessItem(ctx context.Context, key string) error { +func (c *controller) ProcessItem(ctx context.Context, key types.NamespacedName) error { log := logf.FromContext(ctx).WithValues("key", key) ctx = logf.NewContext(ctx, log) - namespace, name, err := cache.SplitMetaNamespaceKey(key) - if err != nil { - log.Error(err, "invalid resource key passed to ProcessItem") - return nil - } + namespace, name := key.Namespace, key.Name crt, err := c.certificateLister.Certificates(namespace).Get(name) - if apierrors.IsNotFound(err) { - log.V(logf.DebugLevel).Info("certificate not found for key", "error", err.Error()) - return nil - } - if err != nil { + if err != nil && !k8sErrors.IsNotFound(err) { return err } + if crt == nil || crt.DeletionTimestamp != nil { + // If the Certificate object was/ is being deleted, we don't want to start scheduling + // renewals. + return nil + } + if apiutil.CertificateHasCondition(crt, cmapi.CertificateCondition{ Type: cmapi.CertificateConditionIssuing, Status: cmmeta.ConditionTrue, @@ -166,6 +170,27 @@ func (c *controller) ProcessItem(ctx context.Context, key string) error { return nil } + // It is possible for multiple Certificates to reference the same Secret. In that case, without this check, + // the duplicate Certificates would each be issued and store their version of the X.509 certificate in the + // target Secret, triggering the re-issuance of the other Certificate resources whose spec no longer matches + // what is in the Secret. This would cause a flood of re-issuance attempts and overloads the Kubernetes API + // and the API server of the issuing CA. + isOwner, duplicates, err := internalcertificates.CertificateOwnsSecret(ctx, c.certificateLister, c.secretLister, crt) + if err != nil { + return err + } + if !isOwner { + log.V(logf.DebugLevel).Info("Certificate.Spec.SecretName refers to the same Secret as other Certificates in the same namespace, skipping trigger.", "duplicates", duplicates) + + // If the Certificate is not the owner of the Secret, we requeue the Certificate and wait for the + // Certificate to become the owner of the Secret. This can happen if the Certificate is updated to + // reference a different Secret, or if the conflicting Certificate is deleted or updated to no longer + // reference the Secret. + c.scheduledWorkQueue.Add(key, 3*time.Minute) + + return nil + } + input, err := c.dataForCertificate(ctx, crt) if err != nil { return err @@ -231,7 +256,7 @@ func (c *controller) updateOrApplyStatus(ctx context.Context, crt *cmapi.Certifi // shouldBackOffReissuingOnFailure returns true if an issuance needs to be // delayed and the required delay after calculating the exponential backoff. // The backoff periods are 1h, 2h, 4h, 8h, 16h and 32h counting from when the last -// failure occured, +// failure occurred, // so the returned delay will be backoff_period - (current_time - last_failure_time) // // Notably, it returns no back-off when the certificate doesn't @@ -258,7 +283,7 @@ func shouldBackoffReissuingOnFailure(log logr.Logger, c clock.Clock, crt *cmapi. if nextCR == nil { log.V(logf.InfoLevel).Info("next CertificateRequest not available, skipping checking if Certificate matches the CertificateRequest") } else { - mismatches, err := certificates.RequestMatchesSpec(nextCR, crt.Spec) + mismatches, err := pki.RequestMatchesSpec(nextCR, crt.Spec) if err != nil { log.V(logf.InfoLevel).Info("next CertificateRequest cannot be decoded, skipping checking if Certificate matches the CertificateRequest") return false, 0 @@ -312,7 +337,7 @@ func shouldBackoffReissuingOnFailure(log logr.Logger, c clock.Clock, crt *cmapi. // has elapsed. // If the 'durationUntilRenewalTime' is less than zero, it will not be // queued again. -func (c *controller) scheduleRecheckOfCertificateIfRequired(log logr.Logger, key string, durationUntilRenewalTime time.Duration) { +func (c *controller) scheduleRecheckOfCertificateIfRequired(log logr.Logger, key types.NamespacedName, durationUntilRenewalTime time.Duration) { // don't schedule a re-queue if the time is in the past. // if it is in the past, the resource will be triggered during the // current call to the ProcessItem method. If we added the item to the @@ -334,22 +359,17 @@ type controllerWrapper struct { *controller } -func (c *controllerWrapper) Register(ctx *controllerpkg.Context) (workqueue.RateLimitingInterface, []cache.InformerSynced, error) { +func (c *controllerWrapper) Register(ctx *controllerpkg.Context) (workqueue.TypedRateLimitingInterface[types.NamespacedName], []cache.InformerSynced, error) { // construct a new named logger to be reused throughout the controller log := logf.FromContext(ctx.RootContext, ControllerName) - ctrl, queue, mustSync := NewController(log, - ctx.CMClient, - ctx.KubeSharedInformerFactory, - ctx.SharedInformerFactory, - ctx.Recorder, - ctx.Clock, + ctrl, queue, mustSync, err := NewController(log, + ctx, policies.NewTriggerPolicyChain(ctx.Clock).Evaluate, - ctx.FieldManager, ) c.controller = ctrl - return queue, mustSync, nil + return queue, mustSync, err } func init() { diff --git a/pkg/controller/certificates/trigger/trigger_controller_test.go b/pkg/controller/certificates/trigger/trigger_controller_test.go index 202e6794a09..b8cd4dbe6d7 100644 --- a/pkg/controller/certificates/trigger/trigger_controller_test.go +++ b/pkg/controller/certificates/trigger/trigger_controller_test.go @@ -22,16 +22,18 @@ import ( "testing" "time" - logtesting "github.com/go-logr/logr/testing" + "github.com/go-logr/logr/testr" "github.com/stretchr/testify/assert" + corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" coretesting "k8s.io/client-go/testing" fakeclock "k8s.io/utils/clock/testing" - "k8s.io/utils/pointer" + "k8s.io/utils/ptr" "github.com/cert-manager/cert-manager/internal/controller/certificates/policies" cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" - controllerpkg "github.com/cert-manager/cert-manager/pkg/controller" testpkg "github.com/cert-manager/cert-manager/pkg/controller/test" testcrypto "github.com/cert-manager/cert-manager/test/unit/crypto" "github.com/cert-manager/cert-manager/test/unit/gen" @@ -50,11 +52,13 @@ func Test_controller_ProcessItem(t *testing.T) { // key that should be passed to ProcessItem. If not set, the // 'namespace/name' of the 'Certificate' field will be used. If neither // is set, the key will be "". - key string + key types.NamespacedName // Certificate to be synced for the test. If not set, the 'key' will be // passed to ProcessItem instead. - existingCertificate *cmapi.Certificate + existingCertificate *cmapi.Certificate + existingCertManagerObjects []runtime.Object + existingKubeObjects []runtime.Object mockDataForCertificateReturn policies.Input mockDataForCertificateReturnErr error @@ -80,10 +84,16 @@ func Test_controller_ProcessItem(t *testing.T) { }{ "do nothing if an empty 'key' is used": {}, "do nothing if an invalid 'key' is used": { - key: "abc/def/ghi", + key: types.NamespacedName{ + Namespace: "abc", + Name: "def/ghi", + }, }, "do nothing if a key references a Certificate that does not exist": { - key: "namespace/name", + key: types.NamespacedName{ + Namespace: "namespace", + Name: "name", + }, }, "should do nothing if Certificate already has 'Issuing' condition": { existingCertificate: gen.Certificate("cert-1", gen.SetCertificateNamespace("testns"), @@ -168,7 +178,7 @@ func Test_controller_ProcessItem(t *testing.T) { gen.SetCertificateRevision(1), gen.SetCertificateDNSNames("example.com"), gen.SetCertificateLastFailureTime(metav1.NewTime(fixedNow.Add(-59*time.Minute))), - gen.SetCertificateIssuanceAttempts(pointer.Int(1)), + gen.SetCertificateIssuanceAttempts(ptr.To(1)), ), wantDataForCertificateCalled: true, mockDataForCertificateReturn: policies.Input{ @@ -187,7 +197,7 @@ func Test_controller_ProcessItem(t *testing.T) { gen.SetCertificateRevision(1), gen.SetCertificateDNSNames("example-that-was-updated-by-user.com"), gen.SetCertificateLastFailureTime(metav1.NewTime(fixedNow.Add(-59*time.Minute))), - gen.SetCertificateIssuanceAttempts(pointer.Int(1)), + gen.SetCertificateIssuanceAttempts(ptr.To(1)), ), wantDataForCertificateCalled: true, mockDataForCertificateReturn: policies.Input{ @@ -217,8 +227,8 @@ func Test_controller_ProcessItem(t *testing.T) { existingCertificate: gen.Certificate("cert-1", gen.SetCertificateNamespace("testns"), gen.SetCertificateGeneration(42), gen.SetCertificateLastFailureTime(metav1.NewTime(fixedNow.Add(-61*time.Minute))), - gen.SetCertificateIssuanceAttempts(pointer.Int(1)), - gen.SetCertificateIssuanceAttempts(pointer.Int(1)), + gen.SetCertificateIssuanceAttempts(ptr.To(1)), + gen.SetCertificateIssuanceAttempts(ptr.To(1)), ), wantDataForCertificateCalled: true, mockDataForCertificateReturn: policies.Input{}, @@ -238,6 +248,136 @@ func Test_controller_ProcessItem(t *testing.T) { ObservedGeneration: 42, }}, }, + "should not set Issuing=True when other Certificates with the same secret name are found, the secret does not exist and the certificate is not the first": { + existingCertificate: gen.Certificate("cert-2", + gen.SetCertificateCreationTimestamp(metav1.NewTime(fixedNow.Add(1*time.Minute))), + gen.SetCertificateNamespace("testns"), + gen.SetCertificateRevision(1), + gen.SetCertificateDNSNames("example.com"), + gen.SetCertificateSecretName("secret-1"), + ), + existingCertManagerObjects: []runtime.Object{ + gen.Certificate("cert-1", + gen.SetCertificateCreationTimestamp(fixedNow), + gen.SetCertificateNamespace("testns"), + gen.SetCertificateRevision(1), + gen.SetCertificateDNSNames("example.com"), + gen.SetCertificateSecretName("secret-1"), + ), + }, + wantDataForCertificateCalled: false, + wantShouldReissueCalled: false, + }, + "should set Issuing=True when other Certificates with the same secret name are found, the secret does not exist and the certificate is the first": { + existingCertificate: gen.Certificate("cert-1", + gen.SetCertificateCreationTimestamp(fixedNow), + gen.SetCertificateNamespace("testns"), + gen.SetCertificateRevision(1), + gen.SetCertificateDNSNames("example.com"), + gen.SetCertificateSecretName("secret-1"), + ), + existingCertManagerObjects: []runtime.Object{ + gen.Certificate("cert-2", + gen.SetCertificateCreationTimestamp(metav1.NewTime(fixedNow.Add(1*time.Minute))), + gen.SetCertificateNamespace("testns"), + gen.SetCertificateRevision(1), + gen.SetCertificateDNSNames("example.com"), + gen.SetCertificateSecretName("secret-1"), + ), + }, + wantDataForCertificateCalled: true, + mockDataForCertificateReturn: policies.Input{}, + wantShouldReissueCalled: true, + mockShouldReissue: func(*testing.T) policies.Func { + return func(policies.Input) (string, string, bool) { + return "ForceTriggered", "Re-issuance forced by unit test case", true + } + }, + wantEvent: "Normal Issuing Re-issuance forced by unit test case", + wantConditions: []cmapi.CertificateCondition{{ + Type: "Issuing", + Status: "True", + Reason: "ForceTriggered", + Message: "Re-issuance forced by unit test case", + LastTransitionTime: &fixedNow, + }}, + }, + "should set Issuing=True when other Certificates with the same secret name are found, the secret does exist and the certificate is the owner": { + existingCertificate: gen.Certificate("cert-2", + gen.SetCertificateCreationTimestamp(metav1.NewTime(fixedNow.Add(1*time.Minute))), + gen.SetCertificateNamespace("testns"), + gen.SetCertificateRevision(1), + gen.SetCertificateDNSNames("example.com"), + gen.SetCertificateSecretName("secret-1"), + ), + existingCertManagerObjects: []runtime.Object{ + gen.Certificate("cert-1", + gen.SetCertificateCreationTimestamp(fixedNow), + gen.SetCertificateNamespace("testns"), + gen.SetCertificateRevision(1), + gen.SetCertificateDNSNames("example.com"), + gen.SetCertificateSecretName("secret-1"), + ), + }, + existingKubeObjects: []runtime.Object{ + &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "secret-1", + Namespace: "testns", + Annotations: map[string]string{ + cmapi.CertificateNameKey: "cert-2", + }, + }, + }, + }, + wantDataForCertificateCalled: true, + mockDataForCertificateReturn: policies.Input{}, + wantShouldReissueCalled: true, + mockShouldReissue: func(*testing.T) policies.Func { + return func(policies.Input) (string, string, bool) { + return "ForceTriggered", "Re-issuance forced by unit test case", true + } + }, + wantEvent: "Normal Issuing Re-issuance forced by unit test case", + wantConditions: []cmapi.CertificateCondition{{ + Type: "Issuing", + Status: "True", + Reason: "ForceTriggered", + Message: "Re-issuance forced by unit test case", + LastTransitionTime: &fixedNow, + }}, + }, + "should not set Issuing=True when other Certificates with the same secret name are found, the secret does exist and the certificate is first but not the owner": { + existingCertificate: gen.Certificate("cert-1", + gen.SetCertificateCreationTimestamp(fixedNow), + gen.SetCertificateNamespace("testns"), + gen.SetCertificateRevision(1), + gen.SetCertificateDNSNames("example.com"), + gen.SetCertificateSecretName("secret-1"), + ), + existingCertManagerObjects: []runtime.Object{ + gen.Certificate("cert-2", + gen.SetCertificateCreationTimestamp(metav1.NewTime(fixedNow.Add(1*time.Minute))), + gen.SetCertificateNamespace("testns"), + gen.SetCertificateRevision(1), + gen.SetCertificateDNSNames("example.com"), + gen.SetCertificateSecretName("secret-1"), + ), + }, + existingKubeObjects: []runtime.Object{ + &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "secret-1", + Namespace: "testns", + Annotations: map[string]string{ + cmapi.CertificateNameKey: "cert-2", + }, + }, + }, + }, + wantDataForCertificateCalled: false, + wantShouldReissueCalled: false, + }, } for name, test := range tests { t.Run(name, func(t *testing.T) { @@ -248,6 +388,12 @@ func Test_controller_ProcessItem(t *testing.T) { if test.existingCertificate != nil { builder.CertManagerObjects = append(builder.CertManagerObjects, test.existingCertificate) } + if test.existingCertManagerObjects != nil { + builder.CertManagerObjects = append(builder.CertManagerObjects, test.existingCertManagerObjects...) + } + if test.existingKubeObjects != nil { + builder.KubeObjects = append(builder.KubeObjects, test.existingKubeObjects...) + } builder.Init() w := &controllerWrapper{} @@ -268,7 +414,7 @@ func Test_controller_ProcessItem(t *testing.T) { // TODO(mael): we should really remove the Certificate field from // DataForCertificate since the input certificate is always expected - // to be the same as the output certiticate. + // to be the same as the output certificate. test.mockDataForCertificateReturn.Certificate = test.existingCertificate gotDataForCertificateCalled := false @@ -300,14 +446,14 @@ func Test_controller_ProcessItem(t *testing.T) { defer builder.Stop() key := test.key - if key == "" && test.existingCertificate != nil { - key, err = controllerpkg.KeyFunc(test.existingCertificate) - if err != nil { - t.Fatal(err) + if key == (types.NamespacedName{}) && test.existingCertificate != nil { + key = types.NamespacedName{ + Name: test.existingCertificate.Name, + Namespace: test.existingCertificate.Namespace, } } - gotErr := w.controller.ProcessItem(context.Background(), key) + gotErr := w.controller.ProcessItem(t.Context(), key) switch { case gotErr != nil: if test.wantErr != gotErr.Error() { @@ -328,7 +474,7 @@ func Test_controller_ProcessItem(t *testing.T) { } func Test_shouldBackoffReissuingOnFailure(t *testing.T) { - clock := fakeclock.NewFakeClock(time.Date(2020, 11, 20, 16, 05, 00, 0000, time.Local)) + clock := fakeclock.NewFakeClock(time.Date(2020, 11, 20, 16, 05, 00, 0000, time.UTC)) // We don't need to full bundle, just a simple CertificateRequest. createCertificateRequestOrPanic := func(crt *cmapi.Certificate) *cmapi.CertificateRequest { @@ -365,7 +511,7 @@ func Test_shouldBackoffReissuingOnFailure(t *testing.T) { gen.SetCertificateRevision(1), gen.SetCertificateDNSNames("example.com"), gen.SetCertificateLastFailureTime(metav1.NewTime(clock.Now().Add(-59*time.Minute))), - gen.SetCertificateIssuanceAttempts(pointer.Int(1)), + gen.SetCertificateIssuanceAttempts(ptr.To(1)), ), givenNextCR: createCertificateRequestOrPanic(gen.Certificate("cert-1", gen.SetCertificateNamespace("testns"), gen.SetCertificateUID("cert-1-uid"), @@ -381,7 +527,7 @@ func Test_shouldBackoffReissuingOnFailure(t *testing.T) { gen.SetCertificateRevision(1), gen.SetCertificateDNSNames("example.com"), gen.SetCertificateLastFailureTime(metav1.NewTime(clock.Now())), - gen.SetCertificateIssuanceAttempts(pointer.Int(1)), + gen.SetCertificateIssuanceAttempts(ptr.To(1)), ), givenNextCR: createCertificateRequestOrPanic(gen.Certificate("cert-1", gen.SetCertificateNamespace("testns"), gen.SetCertificateUID("cert-1-uid"), @@ -397,7 +543,7 @@ func Test_shouldBackoffReissuingOnFailure(t *testing.T) { gen.SetCertificateRevision(1), gen.SetCertificateDNSNames("example.com"), gen.SetCertificateLastFailureTime(metav1.NewTime(clock.Now().Add(-61*time.Minute))), - gen.SetCertificateIssuanceAttempts(pointer.Int(1)), + gen.SetCertificateIssuanceAttempts(ptr.To(1)), ), givenNextCR: createCertificateRequestOrPanic(gen.Certificate("cert-1", gen.SetCertificateNamespace("testns"), gen.SetCertificateUID("cert-1-uid"), @@ -412,7 +558,7 @@ func Test_shouldBackoffReissuingOnFailure(t *testing.T) { gen.SetCertificateRevision(1), gen.SetCertificateDNSNames("example.com"), gen.SetCertificateLastFailureTime(metav1.NewTime(clock.Now())), - gen.SetCertificateIssuanceAttempts(pointer.Int(2)), + gen.SetCertificateIssuanceAttempts(ptr.To(2)), ), givenNextCR: createCertificateRequestOrPanic(gen.Certificate("cert-1", gen.SetCertificateNamespace("testns"), gen.SetCertificateUID("cert-1-uid"), @@ -428,7 +574,7 @@ func Test_shouldBackoffReissuingOnFailure(t *testing.T) { gen.SetCertificateRevision(1), gen.SetCertificateDNSNames("example.com"), gen.SetCertificateLastFailureTime(metav1.NewTime(clock.Now().Add(-121*time.Minute))), - gen.SetCertificateIssuanceAttempts(pointer.Int(2)), + gen.SetCertificateIssuanceAttempts(ptr.To(2)), ), givenNextCR: createCertificateRequestOrPanic(gen.Certificate("cert-1", gen.SetCertificateNamespace("testns"), gen.SetCertificateUID("cert-1-uid"), @@ -443,7 +589,7 @@ func Test_shouldBackoffReissuingOnFailure(t *testing.T) { gen.SetCertificateRevision(1), gen.SetCertificateDNSNames("example.com"), gen.SetCertificateLastFailureTime(metav1.NewTime(clock.Now())), - gen.SetCertificateIssuanceAttempts(pointer.Int(3)), + gen.SetCertificateIssuanceAttempts(ptr.To(3)), ), givenNextCR: createCertificateRequestOrPanic(gen.Certificate("cert-1", gen.SetCertificateNamespace("testns"), gen.SetCertificateUID("cert-1-uid"), @@ -459,7 +605,7 @@ func Test_shouldBackoffReissuingOnFailure(t *testing.T) { gen.SetCertificateRevision(1), gen.SetCertificateDNSNames("example.com"), gen.SetCertificateLastFailureTime(metav1.NewTime(clock.Now().Add(-245*time.Minute))), - gen.SetCertificateIssuanceAttempts(pointer.Int(3)), + gen.SetCertificateIssuanceAttempts(ptr.To(3)), ), givenNextCR: createCertificateRequestOrPanic(gen.Certificate("cert-1", gen.SetCertificateNamespace("testns"), gen.SetCertificateUID("cert-1-uid"), @@ -474,7 +620,7 @@ func Test_shouldBackoffReissuingOnFailure(t *testing.T) { gen.SetCertificateRevision(1), gen.SetCertificateDNSNames("example.com"), gen.SetCertificateLastFailureTime(metav1.NewTime(clock.Now())), - gen.SetCertificateIssuanceAttempts(pointer.Int(4)), + gen.SetCertificateIssuanceAttempts(ptr.To(4)), ), givenNextCR: createCertificateRequestOrPanic(gen.Certificate("cert-1", gen.SetCertificateNamespace("testns"), gen.SetCertificateUID("cert-1-uid"), @@ -490,7 +636,7 @@ func Test_shouldBackoffReissuingOnFailure(t *testing.T) { gen.SetCertificateRevision(1), gen.SetCertificateDNSNames("example.com"), gen.SetCertificateLastFailureTime(metav1.NewTime(clock.Now().Add(-10*time.Hour))), - gen.SetCertificateIssuanceAttempts(pointer.Int(4)), + gen.SetCertificateIssuanceAttempts(ptr.To(4)), ), givenNextCR: createCertificateRequestOrPanic(gen.Certificate("cert-1", gen.SetCertificateNamespace("testns"), gen.SetCertificateUID("cert-1-uid"), @@ -505,7 +651,7 @@ func Test_shouldBackoffReissuingOnFailure(t *testing.T) { gen.SetCertificateRevision(1), gen.SetCertificateDNSNames("example.com"), gen.SetCertificateLastFailureTime(metav1.NewTime(clock.Now())), - gen.SetCertificateIssuanceAttempts(pointer.Int(5)), + gen.SetCertificateIssuanceAttempts(ptr.To(5)), ), givenNextCR: createCertificateRequestOrPanic(gen.Certificate("cert-1", gen.SetCertificateNamespace("testns"), gen.SetCertificateUID("cert-1-uid"), @@ -521,7 +667,7 @@ func Test_shouldBackoffReissuingOnFailure(t *testing.T) { gen.SetCertificateRevision(1), gen.SetCertificateDNSNames("example.com"), gen.SetCertificateLastFailureTime(metav1.NewTime(clock.Now().Add(-1021*time.Minute))), - gen.SetCertificateIssuanceAttempts(pointer.Int(5)), + gen.SetCertificateIssuanceAttempts(ptr.To(5)), ), givenNextCR: createCertificateRequestOrPanic(gen.Certificate("cert-1", gen.SetCertificateNamespace("testns"), gen.SetCertificateUID("cert-1-uid"), @@ -536,7 +682,7 @@ func Test_shouldBackoffReissuingOnFailure(t *testing.T) { gen.SetCertificateRevision(1), gen.SetCertificateDNSNames("example.com"), gen.SetCertificateLastFailureTime(metav1.NewTime(clock.Now())), - gen.SetCertificateIssuanceAttempts(pointer.Int(6)), + gen.SetCertificateIssuanceAttempts(ptr.To(6)), ), givenNextCR: createCertificateRequestOrPanic(gen.Certificate("cert-1", gen.SetCertificateNamespace("testns"), gen.SetCertificateUID("cert-1-uid"), @@ -552,7 +698,7 @@ func Test_shouldBackoffReissuingOnFailure(t *testing.T) { gen.SetCertificateRevision(1), gen.SetCertificateDNSNames("example.com"), gen.SetCertificateLastFailureTime(metav1.NewTime(clock.Now().Add(-32*time.Hour))), - gen.SetCertificateIssuanceAttempts(pointer.Int(6)), + gen.SetCertificateIssuanceAttempts(ptr.To(6)), ), givenNextCR: createCertificateRequestOrPanic(gen.Certificate("cert-1", gen.SetCertificateNamespace("testns"), gen.SetCertificateUID("cert-1-uid"), @@ -567,7 +713,7 @@ func Test_shouldBackoffReissuingOnFailure(t *testing.T) { gen.SetCertificateRevision(1), gen.SetCertificateDNSNames("example.com"), gen.SetCertificateLastFailureTime(metav1.NewTime(clock.Now())), - gen.SetCertificateIssuanceAttempts(pointer.Int(100)), + gen.SetCertificateIssuanceAttempts(ptr.To(100)), ), givenNextCR: createCertificateRequestOrPanic(gen.Certificate("cert-1", gen.SetCertificateNamespace("testns"), gen.SetCertificateUID("cert-1-uid"), @@ -583,7 +729,7 @@ func Test_shouldBackoffReissuingOnFailure(t *testing.T) { gen.SetCertificateRevision(1), gen.SetCertificateDNSNames("example.com"), gen.SetCertificateLastFailureTime(metav1.NewTime(clock.Now().Add(-32*time.Hour))), - gen.SetCertificateIssuanceAttempts(pointer.Int(100)), + gen.SetCertificateIssuanceAttempts(ptr.To(100)), ), givenNextCR: createCertificateRequestOrPanic(gen.Certificate("cert-1", gen.SetCertificateNamespace("testns"), gen.SetCertificateUID("cert-1-uid"), @@ -645,7 +791,7 @@ func Test_shouldBackoffReissuingOnFailure(t *testing.T) { gen.SetCertificateRevision(1), gen.SetCertificateDNSNames("example-was-changed-by-user.com"), gen.SetCertificateLastFailureTime(metav1.NewTime(clock.Now())), - gen.SetCertificateIssuanceAttempts(pointer.Int(1)), + gen.SetCertificateIssuanceAttempts(ptr.To(1)), ), givenNextCR: createCertificateRequestOrPanic(gen.Certificate("cert-1", gen.SetCertificateNamespace("testns"), gen.SetCertificateUID("cert-1-uid"), @@ -660,7 +806,7 @@ func Test_shouldBackoffReissuingOnFailure(t *testing.T) { gen.SetCertificateRevision(1), gen.SetCertificateDNSNames("example-was-updated-by-user.com"), gen.SetCertificateLastFailureTime(metav1.NewTime(clock.Now().Add(-1*time.Minute))), - gen.SetCertificateIssuanceAttempts(pointer.Int(1)), + gen.SetCertificateIssuanceAttempts(ptr.To(1)), ), givenNextCR: createCertificateRequestOrPanic(gen.Certificate("cert-1", gen.SetCertificateNamespace("testns"), gen.SetCertificateUID("cert-1-uid"), @@ -672,7 +818,7 @@ func Test_shouldBackoffReissuingOnFailure(t *testing.T) { } for name, test := range tests { t.Run(name, func(t *testing.T) { - gotBackoff, gotDelay := shouldBackoffReissuingOnFailure(logtesting.NewTestLogger(t), clock, test.givenCert, test.givenNextCR) + gotBackoff, gotDelay := shouldBackoffReissuingOnFailure(testr.New(t), clock, test.givenCert, test.givenNextCR) assert.Equal(t, test.wantBackoff, gotBackoff) assert.Equal(t, test.wantDelay, gotDelay) }) diff --git a/pkg/controller/certificates/util.go b/pkg/controller/certificates/util.go deleted file mode 100644 index 3bc0eda2ca2..00000000000 --- a/pkg/controller/certificates/util.go +++ /dev/null @@ -1,339 +0,0 @@ -/* -Copyright 2020 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. -*/ - -package certificates - -import ( - "crypto" - "crypto/ecdsa" - "crypto/ed25519" - "crypto/rsa" - "crypto/x509/pkix" - "encoding/asn1" - - "fmt" - "reflect" - "time" - - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/util/sets" - - cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" - "github.com/cert-manager/cert-manager/pkg/util" - "github.com/cert-manager/cert-manager/pkg/util/pki" -) - -// PrivateKeyMatchesSpec returns an error if the private key bit size -// doesn't match the provided spec. RSA, Ed25519 and ECDSA are supported. -// If any error is returned, a list of violations will also be returned. -func PrivateKeyMatchesSpec(pk crypto.PrivateKey, spec cmapi.CertificateSpec) ([]string, error) { - spec = *spec.DeepCopy() - if spec.PrivateKey == nil { - spec.PrivateKey = &cmapi.CertificatePrivateKey{} - } - switch spec.PrivateKey.Algorithm { - case "", cmapi.RSAKeyAlgorithm: - return rsaPrivateKeyMatchesSpec(pk, spec) - case cmapi.Ed25519KeyAlgorithm: - return ed25519PrivateKeyMatchesSpec(pk, spec) - case cmapi.ECDSAKeyAlgorithm: - return ecdsaPrivateKeyMatchesSpec(pk, spec) - default: - return nil, fmt.Errorf("unrecognised key algorithm type %q", spec.PrivateKey.Algorithm) - } -} - -func rsaPrivateKeyMatchesSpec(pk crypto.PrivateKey, spec cmapi.CertificateSpec) ([]string, error) { - rsaPk, ok := pk.(*rsa.PrivateKey) - if !ok { - return []string{"spec.privateKey.algorithm"}, nil - } - var violations []string - // TODO: we should not use implicit defaulting here, and instead rely on - // defaulting performed within the Kubernetes apiserver here. - // This requires careful handling in order to not interrupt users upgrading - // from older versions. - // The default RSA keySize is set to 2048. - keySize := pki.MinRSAKeySize - if spec.PrivateKey.Size > 0 { - keySize = spec.PrivateKey.Size - } - if rsaPk.N.BitLen() != keySize { - violations = append(violations, "spec.privateKey.size") - } - return violations, nil -} - -func ecdsaPrivateKeyMatchesSpec(pk crypto.PrivateKey, spec cmapi.CertificateSpec) ([]string, error) { - ecdsaPk, ok := pk.(*ecdsa.PrivateKey) - if !ok { - return []string{"spec.privateKey.algorithm"}, nil - } - var violations []string - // TODO: we should not use implicit defaulting here, and instead rely on - // defaulting performed within the Kubernetes apiserver here. - // This requires careful handling in order to not interrupt users upgrading - // from older versions. - // The default EC curve type is EC256 - expectedKeySize := pki.ECCurve256 - if spec.PrivateKey.Size > 0 { - expectedKeySize = spec.PrivateKey.Size - } - if expectedKeySize != ecdsaPk.Curve.Params().BitSize { - violations = append(violations, "spec.privateKey.size") - } - return violations, nil -} - -func ed25519PrivateKeyMatchesSpec(pk crypto.PrivateKey, spec cmapi.CertificateSpec) ([]string, error) { - _, ok := pk.(ed25519.PrivateKey) - if !ok { - return []string{"spec.privateKey.algorithm"}, nil - } - - return nil, nil -} - -// RequestMatchesSpec compares a CertificateRequest with a CertificateSpec -// and returns a list of field names on the Certificate that do not match their -// counterpart fields on the CertificateRequest. -// If decoding the x509 certificate request fails, an error will be returned. -func RequestMatchesSpec(req *cmapi.CertificateRequest, spec cmapi.CertificateSpec) ([]string, error) { - x509req, err := pki.DecodeX509CertificateRequestBytes(req.Spec.Request) - if err != nil { - return nil, err - } - - // It is safe to mutate top-level fields in `spec` as it is not a pointer - // meaning changes will not effect the caller. - if spec.Subject == nil { - spec.Subject = &cmapi.X509Subject{} - } - - var violations []string - if spec.LiteralSubject == "" { - if x509req.Subject.CommonName != spec.CommonName { - violations = append(violations, "spec.commonName") - } - if !util.EqualUnsorted(x509req.DNSNames, spec.DNSNames) { - violations = append(violations, "spec.dnsNames") - } - if !util.EqualUnsorted(pki.IPAddressesToString(x509req.IPAddresses), spec.IPAddresses) { - violations = append(violations, "spec.ipAddresses") - } - if !util.EqualUnsorted(pki.URLsToString(x509req.URIs), spec.URIs) { - violations = append(violations, "spec.uris") - } - if !util.EqualUnsorted(x509req.EmailAddresses, spec.EmailAddresses) { - violations = append(violations, "spec.emailAddresses") - } - if x509req.Subject.SerialNumber != spec.Subject.SerialNumber { - violations = append(violations, "spec.subject.serialNumber") - } - if !util.EqualUnsorted(x509req.Subject.Organization, spec.Subject.Organizations) { - violations = append(violations, "spec.subject.organizations") - } - if !util.EqualUnsorted(x509req.Subject.Country, spec.Subject.Countries) { - violations = append(violations, "spec.subject.countries") - } - if !util.EqualUnsorted(x509req.Subject.Locality, spec.Subject.Localities) { - violations = append(violations, "spec.subject.localities") - } - if !util.EqualUnsorted(x509req.Subject.OrganizationalUnit, spec.Subject.OrganizationalUnits) { - violations = append(violations, "spec.subject.organizationalUnits") - } - if !util.EqualUnsorted(x509req.Subject.PostalCode, spec.Subject.PostalCodes) { - violations = append(violations, "spec.subject.postCodes") - } - if !util.EqualUnsorted(x509req.Subject.Province, spec.Subject.Provinces) { - violations = append(violations, "spec.subject.postCodes") - } - if !util.EqualUnsorted(x509req.Subject.StreetAddress, spec.Subject.StreetAddresses) { - violations = append(violations, "spec.subject.streetAddresses") - } - if req.Spec.IsCA != spec.IsCA { - violations = append(violations, "spec.isCA") - } - if !util.EqualKeyUsagesUnsorted(req.Spec.Usages, spec.Usages) { - violations = append(violations, "spec.usages") - } - if spec.Duration != nil && req.Spec.Duration != nil && - spec.Duration.Duration != req.Spec.Duration.Duration { - violations = append(violations, "spec.duration") - } - if !reflect.DeepEqual(spec.IssuerRef, req.Spec.IssuerRef) { - violations = append(violations, "spec.issuerRef") - } - } else { - // we have a LiteralSubject - // parse the subject of the csr in the same way as we parse LiteralSubject and see whether the RDN Sequences match - - var rdnSequenceFromCertificateRequest pkix.RDNSequence - _, err2 := asn1.Unmarshal(x509req.RawSubject, &rdnSequenceFromCertificateRequest) - if err2 != nil { - return nil, err2 - } - - rdnSequenceFromCertificate, err := pki.ParseSubjectStringToRdnSequence(spec.LiteralSubject) - if err != nil { - return nil, err - } - - if !reflect.DeepEqual(rdnSequenceFromCertificate, rdnSequenceFromCertificateRequest) { - violations = append(violations, "spec.literalSubject") - } - } - - return violations, nil -} - -// SecretDataAltNamesMatchSpec will compare a Secret resource containing certificate -// data to a CertificateSpec and return a list of 'violations' for any fields that -// do not match their counterparts. -// This is a purposely less comprehensive check than RequestMatchesSpec as some -// issuers override/force certain fields. -func SecretDataAltNamesMatchSpec(secret *corev1.Secret, spec cmapi.CertificateSpec) ([]string, error) { - x509cert, err := pki.DecodeX509CertificateBytes(secret.Data[corev1.TLSCertKey]) - if err != nil { - return nil, err - } - - var violations []string - - // Perform a 'loose' check on the x509 certificate to determine if the - // commonName and dnsNames fields are up to date. - // This check allows names to move between the DNSNames and CommonName - // field freely in order to account for CAs behaviour of promoting DNSNames - // to be CommonNames or vice-versa. - expectedDNSNames := sets.NewString(spec.DNSNames...) - if spec.CommonName != "" { - expectedDNSNames.Insert(spec.CommonName) - } - allDNSNames := sets.NewString(x509cert.DNSNames...) - if x509cert.Subject.CommonName != "" { - allDNSNames.Insert(x509cert.Subject.CommonName) - } - if !allDNSNames.Equal(expectedDNSNames) { - // We know a mismatch occurred, so now determine which fields mismatched. - if (spec.CommonName != "" && !allDNSNames.Has(spec.CommonName)) || (x509cert.Subject.CommonName != "" && !expectedDNSNames.Has(x509cert.Subject.CommonName)) { - violations = append(violations, "spec.commonName") - } - - if !allDNSNames.HasAll(spec.DNSNames...) || !expectedDNSNames.HasAll(x509cert.DNSNames...) { - violations = append(violations, "spec.dnsNames") - } - } - - if !util.EqualUnsorted(pki.IPAddressesToString(x509cert.IPAddresses), spec.IPAddresses) { - violations = append(violations, "spec.ipAddresses") - } - if !util.EqualUnsorted(pki.URLsToString(x509cert.URIs), spec.URIs) { - violations = append(violations, "spec.uris") - } - if !util.EqualUnsorted(x509cert.EmailAddresses, spec.EmailAddresses) { - violations = append(violations, "spec.emailAddresses") - } - - return violations, nil -} - -// staticTemporarySerialNumber is a fixed serial number we use for temporary certificates -const staticTemporarySerialNumber = "1234567890" - -// GenerateLocallySignedTemporaryCertificate signs a temporary certificate for -// the given certificate resource using a one-use temporary CA that is then -// discarded afterwards. -// This is to mitigate a potential attack against x509 certificates that use a -// predictable serial number and weak MD5 hashing algorithms. -// In practice, this shouldn't really be a concern anyway. -func GenerateLocallySignedTemporaryCertificate(crt *cmapi.Certificate, pkData []byte) ([]byte, error) { - // generate a throwaway self-signed root CA - caPk, err := pki.GenerateECPrivateKey(pki.ECCurve521) - if err != nil { - return nil, err - } - caCertTemplate, err := pki.GenerateTemplate(&cmapi.Certificate{ - Spec: cmapi.CertificateSpec{ - CommonName: "cert-manager.local", - IsCA: true, - }, - }) - if err != nil { - return nil, err - } - _, caCert, err := pki.SignCertificate(caCertTemplate, caCertTemplate, caPk.Public(), caPk) - if err != nil { - return nil, err - } - - // sign a temporary certificate using the root CA - template, err := pki.GenerateTemplate(crt) - if err != nil { - return nil, err - } - template.Subject.SerialNumber = staticTemporarySerialNumber - - signeeKey, err := pki.DecodePrivateKeyBytes(pkData) - if err != nil { - return nil, err - } - - b, _, err := pki.SignCertificate(template, caCert, signeeKey.Public(), caPk) - if err != nil { - return nil, err - } - - return b, nil -} - -// RenewalTimeFunc is a custom function type for calculating renewal time of a certificate. -type RenewalTimeFunc func(time.Time, time.Time, *metav1.Duration) *metav1.Time - -// RenewalTime calculates renewal time for a certificate. Default renewal time -// is 2/3 through certificate's lifetime. If user has configured -// spec.renewBefore, renewal time will be renewBefore period before expiry -// (unless that is after the expiry). -func RenewalTime(notBefore, notAfter time.Time, renewBeforeOverride *metav1.Duration) *metav1.Time { - - // 1. Calculate how long before expiry a cert should be renewed - - actualDuration := notAfter.Sub(notBefore) - - renewBefore := actualDuration / 3 - - // If spec.renewBefore was set (and is less than duration) - // respect that. We don't want to prevent users from renewing - // longer lived certs more frequently. - if renewBeforeOverride != nil && renewBeforeOverride.Duration < actualDuration { - renewBefore = renewBeforeOverride.Duration - } - - // 2. Calculate when a cert should be renewed - - // Truncate the renewal time to nearest second. This is important - // because the renewal time also gets stored on Certificate's status - // where it is truncated to the nearest second. We use the renewal time - // from Certificate's status to determine when the Certificate will be - // added to the queue to be renewed, but then re-calculate whether it - // needs to be renewed _now_ using this function- so returning a - // non-truncated value here would potentially cause Certificates to be - // re-queued for renewal earlier than the calculated renewal time thus - // causing Certificates to not be automatically renewed. See - // https://github.com/cert-manager/cert-manager/pull/4399. - rt := metav1.NewTime(notAfter.Add(-1 * renewBefore).Truncate(time.Second)) - return &rt -} diff --git a/pkg/controller/certificates/util_test.go b/pkg/controller/certificates/util_test.go deleted file mode 100644 index 1446fc2e060..00000000000 --- a/pkg/controller/certificates/util_test.go +++ /dev/null @@ -1,363 +0,0 @@ -/* -Copyright 2020 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. -*/ - -package certificates - -import ( - "crypto" - "fmt" - "reflect" - "testing" - "time" - - "github.com/stretchr/testify/assert" - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - - cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" - "github.com/cert-manager/cert-manager/pkg/util/pki" -) - -func mustGenerateRSA(t *testing.T, keySize int) crypto.PrivateKey { - pk, err := pki.GenerateRSAPrivateKey(keySize) - if err != nil { - t.Fatal(err) - } - return pk -} - -func mustGenerateECDSA(t *testing.T, keySize int) crypto.PrivateKey { - pk, err := pki.GenerateECPrivateKey(keySize) - if err != nil { - t.Fatal(err) - } - return pk -} - -func mustGenerateEd25519(t *testing.T) crypto.PrivateKey { - pk, err := pki.GenerateEd25519PrivateKey() - if err != nil { - t.Fatal(err) - } - return pk -} - -func TestPrivateKeyMatchesSpec(t *testing.T) { - tests := map[string]struct { - key crypto.PrivateKey - expectedAlgo cmapi.PrivateKeyAlgorithm - expectedSize int - violations []string - err string - }{ - "should match if keySize and algorithm are correct (RSA)": { - key: mustGenerateRSA(t, 2048), - expectedAlgo: cmapi.RSAKeyAlgorithm, - expectedSize: 2048, - }, - "should not match if RSA keySize is incorrect": { - key: mustGenerateRSA(t, 2048), - expectedAlgo: cmapi.RSAKeyAlgorithm, - expectedSize: 4096, - violations: []string{"spec.privateKey.size"}, - }, - "should match if keySize and algorithm are correct (ECDSA)": { - key: mustGenerateECDSA(t, pki.ECCurve256), - expectedAlgo: cmapi.ECDSAKeyAlgorithm, - expectedSize: 256, - }, - "should not match if ECDSA keySize is incorrect": { - key: mustGenerateECDSA(t, pki.ECCurve256), - expectedAlgo: cmapi.ECDSAKeyAlgorithm, - expectedSize: pki.ECCurve521, - violations: []string{"spec.privateKey.size"}, - }, - "should not match if keyAlgorithm is incorrect": { - key: mustGenerateECDSA(t, pki.ECCurve256), - expectedAlgo: cmapi.RSAKeyAlgorithm, - expectedSize: 2048, - violations: []string{"spec.privateKey.algorithm"}, - }, - "should match if keySize and algorithm are correct (Ed25519)": { - key: mustGenerateEd25519(t), - expectedAlgo: cmapi.Ed25519KeyAlgorithm, - }, - } - for name, test := range tests { - t.Run(name, func(t *testing.T) { - violations, err := PrivateKeyMatchesSpec( - test.key, - cmapi.CertificateSpec{ - PrivateKey: &cmapi.CertificatePrivateKey{ - Algorithm: test.expectedAlgo, - Size: test.expectedSize, - }, - }, - ) - switch { - case err != nil: - if test.err != err.Error() { - t.Errorf("error text did not match, got=%s, exp=%s", err.Error(), test.err) - } - default: - if test.err != "" { - t.Errorf("got no error but expected: %s", test.err) - } - } - if !reflect.DeepEqual(violations, test.violations) { - t.Errorf("violations did not match, got=%s, exp=%s", violations, test.violations) - } - }) - } -} - -func TestSecretDataAltNamesMatchSpec(t *testing.T) { - tests := map[string]struct { - data []byte - spec cmapi.CertificateSpec - err string - violations []string - }{ - "should match if common name and dns names exactly equal": { - spec: cmapi.CertificateSpec{ - CommonName: "cn", - DNSNames: []string{"at", "least", "one"}, - }, - data: selfSignCertificate(t, cmapi.CertificateSpec{ - CommonName: "cn", - DNSNames: []string{"at", "least", "one"}, - }), - }, - "should match if commonName is missing but is present in dnsNames": { - spec: cmapi.CertificateSpec{ - CommonName: "cn", - DNSNames: []string{"at", "least", "one"}, - }, - data: selfSignCertificate(t, cmapi.CertificateSpec{ - DNSNames: []string{"cn", "at", "least", "one"}, - }), - }, - "should match if commonName is missing but is present in dnsNames (not first)": { - spec: cmapi.CertificateSpec{ - CommonName: "cn", - DNSNames: []string{"at", "least", "one"}, - }, - data: selfSignCertificate(t, cmapi.CertificateSpec{ - DNSNames: []string{"at", "least", "one", "cn"}, - }), - }, - "should match if commonName is one of the requested requested dnsNames": { - spec: cmapi.CertificateSpec{ - DNSNames: []string{"at", "least", "one"}, - }, - data: selfSignCertificate(t, cmapi.CertificateSpec{ - CommonName: "at", - DNSNames: []string{"least", "one"}, - }), - }, - "should not match if commonName is not present on certificate": { - spec: cmapi.CertificateSpec{ - CommonName: "cn", - DNSNames: []string{"at", "least", "one"}, - }, - data: selfSignCertificate(t, cmapi.CertificateSpec{ - DNSNames: []string{"at", "least", "one"}, - }), - violations: []string{"spec.commonName"}, - }, - "should report violation for both commonName and dnsNames if both are missing": { - spec: cmapi.CertificateSpec{ - CommonName: "cn", - DNSNames: []string{"at", "least", "one", "other"}, - }, - data: selfSignCertificate(t, cmapi.CertificateSpec{ - DNSNames: []string{"at", "least", "one"}, - }), - violations: []string{"spec.commonName", "spec.dnsNames"}, - }, - "should report violation for both commonName and dnsNames if not requested": { - spec: cmapi.CertificateSpec{ - DNSNames: []string{"at", "least", "one"}, - }, - data: selfSignCertificate(t, cmapi.CertificateSpec{ - CommonName: "cn", - DNSNames: []string{"at", "least", "one", "other"}, - }), - violations: []string{"spec.commonName", "spec.dnsNames"}, - }, - "should not match if certificate has more dnsNames than spec": { - spec: cmapi.CertificateSpec{ - CommonName: "cn", - DNSNames: []string{"at", "least", "one"}, - }, - data: selfSignCertificate(t, cmapi.CertificateSpec{ - CommonName: "cn", - DNSNames: []string{"at", "least", "one", "other"}, - }), - violations: []string{"spec.dnsNames"}, - }, - "should match if commonName is a duplicated dnsName (but not requested)": { - spec: cmapi.CertificateSpec{ - DNSNames: []string{"at", "least", "one"}, - }, - data: selfSignCertificate(t, cmapi.CertificateSpec{ - CommonName: "at", - DNSNames: []string{"at", "least", "one"}, - }), - }, - "should match if commonName is a duplicated dnsName": { - spec: cmapi.CertificateSpec{ - CommonName: "cn", - DNSNames: []string{"at", "least", "one"}, - }, - data: selfSignCertificate(t, cmapi.CertificateSpec{ - CommonName: "at", - DNSNames: []string{"at", "least", "one", "cn"}, - }), - }, - "should match if ipAddresses are equal": { - spec: cmapi.CertificateSpec{ - IPAddresses: []string{"127.0.0.1"}, - }, - data: selfSignCertificate(t, cmapi.CertificateSpec{ - IPAddresses: []string{"127.0.0.1"}, - }), - }, - "should not match if ipAddresses are not equal": { - spec: cmapi.CertificateSpec{ - IPAddresses: []string{"127.0.0.1"}, - }, - data: selfSignCertificate(t, cmapi.CertificateSpec{ - IPAddresses: []string{"127.0.2.1"}, - }), - violations: []string{"spec.ipAddresses"}, - }, - "should not match if ipAddresses has been made the commonName": { - spec: cmapi.CertificateSpec{ - IPAddresses: []string{"127.0.0.1"}, - }, - data: selfSignCertificate(t, cmapi.CertificateSpec{ - CommonName: "127.0.0.1", - IPAddresses: []string{"127.0.0.1"}, - }), - violations: []string{"spec.commonName"}, - }, - } - for name, test := range tests { - t.Run(name, func(t *testing.T) { - violations, err := SecretDataAltNamesMatchSpec(&corev1.Secret{Data: map[string][]byte{corev1.TLSCertKey: test.data}}, test.spec) - switch { - case err != nil: - if test.err != err.Error() { - t.Errorf("error text did not match, got=%s, exp=%s", err.Error(), test.err) - } - default: - if test.err != "" { - t.Errorf("got no error but expected: %s", test.err) - } - } - if !reflect.DeepEqual(violations, test.violations) { - t.Errorf("violations did not match, got=%s, exp=%s", violations, test.violations) - } - }) - } -} - -func selfSignCertificate(t *testing.T, spec cmapi.CertificateSpec) []byte { - pk, err := pki.GenerateRSAPrivateKey(2048) - if err != nil { - t.Fatal(err) - } - - template, err := pki.GenerateTemplate(&cmapi.Certificate{Spec: spec}) - if err != nil { - t.Fatal(err) - } - - pemData, _, err := pki.SignCertificate(template, template, pk.Public(), pk) - if err != nil { - t.Fatal(err) - } - - return pemData -} - -func TestRenewalTime(t *testing.T) { - type scenario struct { - notBefore time.Time - notAfter time.Time - renewBeforeOverride *metav1.Duration - expectedRenewalTime *metav1.Time - } - now := time.Now().Truncate(time.Second) - tests := map[string]scenario{ - "short lived cert, spec.renewBefore is not set": { - notBefore: now, - notAfter: now.Add(time.Hour * 3), - renewBeforeOverride: nil, - expectedRenewalTime: &metav1.Time{Time: now.Add(time.Hour * 2)}, - }, - "long lived cert, spec.renewBefore is not set": { - notBefore: now, - notAfter: now.Add(time.Hour * 4380), // 6 months - renewBeforeOverride: nil, - expectedRenewalTime: &metav1.Time{Time: now.Add(time.Hour * 2920)}, // renew in 4 months - }, - "spec.renewBefore is set": { - notBefore: now, - notAfter: now.Add(time.Hour * 24), - renewBeforeOverride: &metav1.Duration{Duration: time.Hour * 20}, - expectedRenewalTime: &metav1.Time{Time: now.Add(time.Hour * 4)}, - }, - "long lived cert, spec.renewBefore is set to renew every day": { - notBefore: now, - notAfter: now.Add(time.Hour * 730), // 1 month - renewBeforeOverride: &metav1.Duration{Duration: time.Hour * 706}, // 1 month - 1 day - expectedRenewalTime: &metav1.Time{Time: now.Add(time.Hour * 24)}, - }, - "spec.renewBefore is set, but would result in renewal time after expiry": { - notBefore: now, - notAfter: now.Add(time.Hour * 24), - renewBeforeOverride: &metav1.Duration{Duration: time.Hour * 25}, - expectedRenewalTime: &metav1.Time{Time: now.Add(time.Hour * 16)}, - }, - // This test case is here to show the scenario where users set - // renewBefore to very slightly less than actual duration. This - // will result in cert being renewed 'continuously'. - "spec.renewBefore is set to a value slightly less than cert's duration": { - notBefore: now, - notAfter: now.Add(time.Hour*24 + time.Minute*3), - renewBeforeOverride: &metav1.Duration{Duration: time.Hour * 24}, - expectedRenewalTime: &metav1.Time{Time: now.Add(time.Minute * 3)}, // renew in 3 minutes - }, - // This test case is here to guard against an earlier bug where - // a non-truncated renewal time returned from this function - // caused certs to not be renewed. - // See https://github.com/cert-manager/cert-manager/pull/4399 - "certificate's duration is skewed by a second": { - notBefore: now, - notAfter: now.Add(time.Hour * 24).Add(time.Second * -1), - expectedRenewalTime: &metav1.Time{Time: now.Add(time.Hour * 16).Add(time.Second * -1)}, - }, - } - for n, s := range tests { - t.Run(n, func(t *testing.T) { - renewalTime := RenewalTime(s.notBefore, s.notAfter, s.renewBeforeOverride) - assert.Equal(t, s.expectedRenewalTime, renewalTime, fmt.Sprintf("Expected renewal time: %v got: %v", s.expectedRenewalTime, renewalTime)) - - }) - } -} diff --git a/pkg/controller/certificatesigningrequests/acme/acme.go b/pkg/controller/certificatesigningrequests/acme/acme.go index f9461ec8eae..939308b6385 100644 --- a/pkg/controller/certificatesigningrequests/acme/acme.go +++ b/pkg/controller/certificatesigningrequests/acme/acme.go @@ -21,6 +21,7 @@ import ( "crypto/x509" "errors" "fmt" + "slices" "github.com/go-logr/logr" certificatesv1 "k8s.io/api/certificates/v1" @@ -28,6 +29,7 @@ import ( apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/types" certificatesclient "k8s.io/client-go/kubernetes/typed/certificates/v1" "k8s.io/client-go/tools/cache" "k8s.io/client-go/tools/record" @@ -44,7 +46,6 @@ import ( "github.com/cert-manager/cert-manager/pkg/controller/certificatesigningrequests" ctrlutil "github.com/cert-manager/cert-manager/pkg/controller/certificatesigningrequests/util" logf "github.com/cert-manager/cert-manager/pkg/logs" - "github.com/cert-manager/cert-manager/pkg/util" "github.com/cert-manager/cert-manager/pkg/util/pki" ) @@ -80,19 +81,21 @@ func init() { func controllerBuilder() *certificatesigningrequests.Controller { return certificatesigningrequests.New(apiutil.IssuerACME, NewACME, - func(ctx *controllerpkg.Context, log logr.Logger, queue workqueue.RateLimitingInterface) ([]cache.InformerSynced, error) { + func(ctx *controllerpkg.Context, log logr.Logger, queue workqueue.TypedRateLimitingInterface[types.NamespacedName]) ([]cache.InformerSynced, error) { orderInformer := ctx.SharedInformerFactory.Acme().V1().Orders().Informer() - csrLister := ctx.KubeSharedInformerFactory.Certificates().V1().CertificateSigningRequests().Lister() + csrLister := ctx.KubeSharedInformerFactory.CertificateSigningRequests().Lister() - orderInformer.AddEventHandler(&controllerpkg.BlockingEventHandler{ + if _, err := orderInformer.AddEventHandler(&controllerpkg.BlockingEventHandler{ WorkFunc: controllerpkg.HandleOwnedResourceNamespacedFunc( log, queue, certificatesv1.SchemeGroupVersion.WithKind("CertificateSigningRequest"), - func(_, name string) (interface{}, error) { + func(_, name string) (*certificatesv1.CertificateSigningRequest, error) { return csrLister.Get(name) }, ), - }) + }); err != nil { + return nil, fmt.Errorf("error setting up event handler: %v", err) + } return []cache.InformerSynced{orderInformer.HasSynced}, nil }, ) @@ -133,7 +136,7 @@ func (a *ACME) Sign(ctx context.Context, csr *certificatesv1.CertificateSigningR // If the CommonName is also not present in the DNS names or IP Addresses of // the Request then hard fail. - if len(req.Subject.CommonName) > 0 && !util.Contains(req.DNSNames, req.Subject.CommonName) && !util.Contains(pki.IPAddressesToString(req.IPAddresses), req.Subject.CommonName) { + if len(req.Subject.CommonName) > 0 && !slices.Contains(req.DNSNames, req.Subject.CommonName) && !slices.Contains(pki.IPAddressesToString(req.IPAddresses), req.Subject.CommonName) { err = fmt.Errorf("%q does not exist in %s or %s", req.Subject.CommonName, req.DNSNames, pki.IPAddressesToString(req.IPAddresses)) message := fmt.Sprintf("The CSR PEM requests a commonName that is not present in the list of dnsNames or ipAddresses. If a commonName is set, ACME requires that the value is also present in the list of dnsNames or ipAddresses: %s", err) @@ -275,7 +278,7 @@ func (a *ACME) buildOrder(csr *certificatesv1.CertificateSigningRequest, req *x5 spec := cmacme.OrderSpec{ Request: csr.Spec.Request, - IssuerRef: cmmeta.ObjectReference{ + IssuerRef: cmmeta.IssuerReference{ Name: ref.Name, Kind: kind, Group: ref.Group, diff --git a/pkg/controller/certificatesigningrequests/acme/acme_test.go b/pkg/controller/certificatesigningrequests/acme/acme_test.go index c9729f50645..fbeeecfeedd 100644 --- a/pkg/controller/certificatesigningrequests/acme/acme_test.go +++ b/pkg/controller/certificatesigningrequests/acme/acme_test.go @@ -17,7 +17,6 @@ limitations under the License. package acme import ( - "context" "crypto/x509" "reflect" "testing" @@ -31,6 +30,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/types" "k8s.io/client-go/kubernetes" coretesting "k8s.io/client-go/testing" fakeclock "k8s.io/utils/clock/testing" @@ -66,19 +66,19 @@ func Test_controllerBuilder(t *testing.T) { existingCSR runtime.Object existingCMObjects []runtime.Object givenCall func(*testing.T, cmclient.Interface, kubernetes.Interface) - expectRequeueKey string + expectRequeueKey types.NamespacedName }{ "if no request then no request should sync": { existingCSR: nil, existingCMObjects: []runtime.Object{baseOrder}, givenCall: func(t *testing.T, _ cmclient.Interface, _ kubernetes.Interface) {}, - expectRequeueKey: "", + expectRequeueKey: types.NamespacedName{}, }, "if no changes to request or order, then no request should sync": { existingCSR: baseCSR, existingCMObjects: []runtime.Object{baseOrder}, givenCall: func(t *testing.T, _ cmclient.Interface, _ kubernetes.Interface) {}, - expectRequeueKey: "", + expectRequeueKey: types.NamespacedName{}, }, "request should be synced if an owned order is updated": { existingCSR: baseCSR, @@ -92,10 +92,12 @@ func Test_controllerBuilder(t *testing.T) { gen.SetOrderOwnerReference(*metav1.NewControllerRef(baseCSR, certificatesigningrequestGVK)), gen.SetOrderURL("update"), ) - _, err := cmclient.AcmeV1().Orders("test-namespace").Update(context.TODO(), order, metav1.UpdateOptions{}) + _, err := cmclient.AcmeV1().Orders("test-namespace").Update(t.Context(), order, metav1.UpdateOptions{}) require.NoError(t, err) }, - expectRequeueKey: "test-csr", + expectRequeueKey: types.NamespacedName{ + Name: "test-csr", + }, }, "request should not be synced if updated order is not owned": { existingCSR: baseCSR, @@ -106,10 +108,10 @@ func Test_controllerBuilder(t *testing.T) { order := gen.OrderFrom(baseOrder, gen.SetOrderURL("update"), ) - _, err := cmclient.AcmeV1().Orders("test-namespace").Update(context.TODO(), order, metav1.UpdateOptions{}) + _, err := cmclient.AcmeV1().Orders("test-namespace").Update(t.Context(), order, metav1.UpdateOptions{}) require.NoError(t, err) }, - expectRequeueKey: "", + expectRequeueKey: types.NamespacedName{}, }, "request should be synced if request is updated": { existingCSR: baseCSR, @@ -118,10 +120,12 @@ func Test_controllerBuilder(t *testing.T) { csr := gen.CertificateSigningRequestFrom(baseCSR, gen.SetCertificateSigningRequestCertificate([]byte("update")), ) - _, err := kubeclient.CertificatesV1().CertificateSigningRequests().UpdateStatus(context.TODO(), csr, metav1.UpdateOptions{}) + _, err := kubeclient.CertificatesV1().CertificateSigningRequests().UpdateStatus(t.Context(), csr, metav1.UpdateOptions{}) require.NoError(t, err) }, - expectRequeueKey: "test-csr", + expectRequeueKey: types.NamespacedName{ + Name: "test-csr", + }, }, } @@ -158,7 +162,7 @@ func Test_controllerBuilder(t *testing.T) { // to be nil. time.AfterFunc(50*time.Millisecond, queue.ShutDown) - var gotKeys []string + var gotKeys []types.NamespacedName for { // Get blocks until either (1) a key is returned, or (2) the // queue is shut down. @@ -166,13 +170,13 @@ func Test_controllerBuilder(t *testing.T) { if done { break } - gotKeys = append(gotKeys, gotKey.(string)) + gotKeys = append(gotKeys, gotKey) } assert.Equal(t, 0, queue.Len(), "queue should be empty") // We only expect 0 or 1 keys received in the queue. - if test.expectRequeueKey != "" { - assert.Equal(t, []string{test.expectRequeueKey}, gotKeys) + if test.expectRequeueKey != (types.NamespacedName{}) { + assert.Equal(t, []types.NamespacedName{test.expectRequeueKey}, gotKeys) } else { assert.Nil(t, gotKeys) } @@ -225,7 +229,7 @@ func Test_ProcessItem(t *testing.T) { }), ) - tmpl, err := pki.GenerateTemplateFromCertificateSigningRequest(baseCSR) + tmpl, err := pki.CertificateTemplateFromCertificateSigningRequest(baseCSR) if err != nil { t.Fatal(err) } @@ -234,7 +238,7 @@ func Test_ProcessItem(t *testing.T) { t.Fatal(err) } - tmpl, err = pki.GenerateTemplateFromCertificateSigningRequest(gen.CertificateSigningRequestFrom(baseCSR, + tmpl, err = pki.CertificateTemplateFromCertificateSigningRequest(gen.CertificateSigningRequestFrom(baseCSR, gen.SetCertificateSigningRequestRequest(csrPEMExampleNotPresent), )) if err != nil { @@ -288,7 +292,7 @@ func Test_ProcessItem(t *testing.T) { builder: &testpkg.Builder{ CertManagerObjects: []runtime.Object{baseIssuer.DeepCopy()}, ExpectedEvents: []string{ - "Warning RequestParsingError Failed to decode CSR in spec.request: error decoding certificate request PEM block", + "Warning RequestParsingError Failed to decode CSR in spec.request: error decoding certificate request PEM block: no PEM data was found in given input", }, ExpectedActions: []testpkg.Action{ testpkg.NewAction(coretesting.NewCreateAction( @@ -328,7 +332,7 @@ func Test_ProcessItem(t *testing.T) { Type: certificatesv1.CertificateFailed, Status: corev1.ConditionTrue, Reason: "RequestParsingError", - Message: "Failed to decode CSR in spec.request: error decoding certificate request PEM block", + Message: "Failed to decode CSR in spec.request: error decoding certificate request PEM block: no PEM data was found in given input", LastTransitionTime: metaFixedClockStart, LastUpdateTime: metaFixedClockStart, }), @@ -738,7 +742,7 @@ func Test_ProcessItem(t *testing.T) { ), }, ExpectedEvents: []string{ - "Warning OrderBadCertificate Deleting Order with bad certificate: error decoding certificate PEM block", + "Warning OrderBadCertificate Deleting Order with bad certificate: error decoding certificate PEM block: no valid certificates found", }, ExpectedActions: []testpkg.Action{ testpkg.NewAction(coretesting.NewCreateAction( @@ -916,11 +920,15 @@ func Test_ProcessItem(t *testing.T) { defer test.builder.Stop() controller := controllerBuilder() - controller.Register(test.builder.Context) + if _, _, err := controller.Register(test.builder.Context); err != nil { + t.Fatal(err) + } test.builder.Start() - err := controller.ProcessItem(context.Background(), test.csr.Name) + err := controller.ProcessItem(t.Context(), types.NamespacedName{ + Name: test.csr.Name, + }) if (err != nil) != test.expectedErr { t.Errorf("unexpected error, exp=%t got=%v", test.expectedErr, err) } @@ -963,7 +971,7 @@ func Test_buildOrder(t *testing.T) { Request: csrPEM, CommonName: "example.com", DNSNames: []string{"example.com"}, - IssuerRef: cmmeta.ObjectReference{ + IssuerRef: cmmeta.IssuerReference{ Name: "test-name", Kind: "Issuer", Group: "cert-manager.io", @@ -980,7 +988,7 @@ func Test_buildOrder(t *testing.T) { CommonName: "example.com", DNSNames: []string{"example.com"}, Duration: &metav1.Duration{Duration: time.Hour}, - IssuerRef: cmmeta.ObjectReference{ + IssuerRef: cmmeta.IssuerReference{ Name: "test-name", Kind: "Issuer", Group: "cert-manager.io", diff --git a/pkg/controller/certificatesigningrequests/ca/ca.go b/pkg/controller/certificatesigningrequests/ca/ca.go index 4ea5f5d8ee8..a7aebf0a709 100644 --- a/pkg/controller/certificatesigningrequests/ca/ca.go +++ b/pkg/controller/certificatesigningrequests/ca/ca.go @@ -26,9 +26,9 @@ import ( corev1 "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" certificatesclient "k8s.io/client-go/kubernetes/typed/certificates/v1" - corelisters "k8s.io/client-go/listers/core/v1" "k8s.io/client-go/tools/record" + internalinformers "github.com/cert-manager/cert-manager/internal/informers" apiutil "github.com/cert-manager/cert-manager/pkg/api/util" cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" controllerpkg "github.com/cert-manager/cert-manager/pkg/controller" @@ -52,7 +52,7 @@ type signingFn func([]*x509.Certificate, crypto.Signer, *x509.Certificate) (pki. // or ClusterIssuer type CA struct { issuerOptions controllerpkg.IssuerOptions - secretsLister corelisters.SecretLister + secretsLister internalinformers.SecretLister certClient certificatesclient.CertificateSigningRequestInterface @@ -78,11 +78,11 @@ func init() { func NewCA(ctx *controllerpkg.Context) certificatesigningrequests.Signer { return &CA{ issuerOptions: ctx.IssuerOptions, - secretsLister: ctx.KubeSharedInformerFactory.Core().V1().Secrets().Lister(), + secretsLister: ctx.KubeSharedInformerFactory.Secrets().Lister(), certClient: ctx.Client.CertificatesV1().CertificateSigningRequests(), fieldManager: ctx.FieldManager, recorder: ctx.Recorder, - templateGenerator: pki.GenerateTemplateFromCertificateSigningRequest, + templateGenerator: pki.CertificateTemplateFromCertificateSigningRequest, signingFn: pki.SignCSRTemplate, } } @@ -129,6 +129,7 @@ func (c *CA) Sign(ctx context.Context, csr *certificatesv1.CertificateSigningReq template.CRLDistributionPoints = issuerObj.GetSpec().CA.CRLDistributionPoints template.OCSPServer = issuerObj.GetSpec().CA.OCSPServers + template.IssuingCertificateURL = issuerObj.GetSpec().CA.IssuingCertificateURLs bundle, err := c.signingFn(caCerts, caKey, template) if err != nil { diff --git a/pkg/controller/certificatesigningrequests/ca/ca_test.go b/pkg/controller/certificatesigningrequests/ca/ca_test.go index e72eb98ef2f..6fb77d1bbf1 100644 --- a/pkg/controller/certificatesigningrequests/ca/ca_test.go +++ b/pkg/controller/certificatesigningrequests/ca/ca_test.go @@ -17,13 +17,11 @@ limitations under the License. package ca import ( - "context" "crypto" "crypto/ecdsa" "crypto/rand" "crypto/x509" "crypto/x509/pkix" - "encoding/pem" "errors" "math" "math/big" @@ -37,6 +35,7 @@ import ( corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" clientcorev1 "k8s.io/client-go/listers/core/v1" coretesting "k8s.io/client-go/testing" fakeclock "k8s.io/utils/clock/testing" @@ -59,26 +58,19 @@ var ( fixedClock = fakeclock.NewFakeClock(fixedClockStart) ) -func generateCSR(t *testing.T, secretKey crypto.Signer, sigAlg x509.SignatureAlgorithm) []byte { - template := x509.CertificateRequest{ - Subject: pkix.Name{ - CommonName: "test", - }, - SignatureAlgorithm: sigAlg, - } - - csrBytes, err := x509.CreateCertificateRequest(rand.Reader, &template, secretKey) +func generateCSR(t *testing.T, secretKey crypto.Signer) []byte { + csr, err := gen.CSRWithSigner(secretKey, + gen.SetCSRCommonName("test"), + ) if err != nil { t.Fatal(err) } - csr := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE REQUEST", Bytes: csrBytes}) return csr } func generateSelfSignedCACert(t *testing.T, key crypto.Signer, name string) (*x509.Certificate, []byte) { tmpl := &x509.Certificate{ - Version: 2, BasicConstraintsValid: true, SerialNumber: big.NewInt(0), Subject: pkix.Name{ @@ -124,7 +116,7 @@ func TestSign(t *testing.T) { if err != nil { t.Fatal(err) } - testCSR := generateCSR(t, testpk, x509.ECDSAWithSHA256) + testCSR := generateCSR(t, testpk) baseCSRNotApproved := gen.CertificateSigningRequest("test-cr", gen.SetCertificateSigningRequestIsCA(true), @@ -157,7 +149,7 @@ func TestSign(t *testing.T) { }), ) - // generate a self signed root ca valid for 60d + // generate a self-signed root ca valid for 60d rootCert, rootCertPEM := generateSelfSignedCACert(t, rootPK, "root") ecCASecret := &corev1.Secret{ ObjectMeta: metav1.ObjectMeta{ @@ -173,7 +165,7 @@ func TestSign(t *testing.T) { badDataSecret := ecCASecret.DeepCopy() badDataSecret.Data[corev1.TLSPrivateKeyKey] = []byte("bad key") - template, err := pki.GenerateTemplateFromCertificateSigningRequest(baseCSR) + template, err := pki.CertificateTemplateFromCertificateSigningRequest(baseCSR) if err != nil { t.Fatal(err) } @@ -247,7 +239,7 @@ func TestSign(t *testing.T) { ), }, ExpectedEvents: []string{ - "Warning SecretInvalidData Failed to parse signing CA keypair from secret default-unit-test-ns/root-ca-secret: error decoding private key PEM block", + "Warning SecretInvalidData Failed to parse signing CA keypair from secret default-unit-test-ns/root-ca-secret: error decoding private key PEM block: no PEM data was found in given input", }, ExpectedActions: []testpkg.Action{ @@ -472,7 +464,7 @@ func TestSign(t *testing.T) { templateGenerator: func(csr *certificatesv1.CertificateSigningRequest) (*x509.Certificate, error) { // Pass the given CSR to a "real" template generator to ensure that it // doesn't err. Return the pre-generated template. - _, err := pki.GenerateTemplateFromCertificateSigningRequest(csr) + _, err := pki.CertificateTemplateFromCertificateSigningRequest(csr) if err != nil { return nil, err } @@ -580,10 +572,14 @@ func runTest(t *testing.T, test testT) { apiutil.IssuerCA, func(*controller.Context) certificatesigningrequests.Signer { return ca }, ) - controller.Register(test.builder.Context) + if _, _, err := controller.Register(test.builder.Context); err != nil { + t.Fatal(err) + } test.builder.Start() - err := controller.ProcessItem(context.Background(), test.csr.Name) + err := controller.ProcessItem(t.Context(), types.NamespacedName{ + Name: test.csr.Name, + }) if err != nil && !test.expectedErr { t.Errorf("expected to not get an error, but got: %v", err) } @@ -606,7 +602,7 @@ func TestCA_Sign(t *testing.T) { if err != nil { t.Fatal(err) } - testCSR := generateCSR(t, testpk, x509.ECDSAWithSHA256) + testCSR := generateCSR(t, testpk) tests := map[string]struct { givenCASecret *corev1.Secret @@ -621,7 +617,7 @@ func TestCA_Sign(t *testing.T) { })), givenCSR: gen.CertificateSigningRequest("csr-1", gen.SetCertificateSigningRequestRequest(testCSR), - gen.SetCertificateSigningRequestSignerName("issers.cert-manager.io/"+gen.DefaultTestNamespace+".issuer-1"), + gen.SetCertificateSigningRequestSignerName("issuers.cert-manager.io/"+gen.DefaultTestNamespace+".issuer-1"), gen.SetCertificateSigningRequestDuration("30m"), ), assertSignedCert: func(t *testing.T, got *x509.Certificate) { @@ -656,7 +652,7 @@ func TestCA_Sign(t *testing.T) { })), givenCSR: gen.CertificateSigningRequest("csr-1", gen.SetCertificateSigningRequestRequest(testCSR), - gen.SetCertificateSigningRequestSignerName("issers.cert-manager.io/"+gen.DefaultTestNamespace+".issuer-1"), + gen.SetCertificateSigningRequestSignerName("issuers.cert-manager.io/"+gen.DefaultTestNamespace+".issuer-1"), gen.SetCertificateSigningRequestExpirationSeconds(654), ), assertSignedCert: func(t *testing.T, got *x509.Certificate) { @@ -712,6 +708,20 @@ func TestCA_Sign(t *testing.T) { assert.Equal(t, []string{"http://ocsp-v3.example.org"}, got.OCSPServer) }, }, + "when the Issuer has issuingCertificateURLs set, it should appear on the signed ca": { + givenCASecret: gen.SecretFrom(gen.Secret("secret-1"), gen.SetSecretNamespace("default"), gen.SetSecretData(secretDataFor(t, rootPK, rootCert))), + givenCAIssuer: gen.Issuer("issuer-1", gen.SetIssuerCA(cmapi.CAIssuer{ + SecretName: "secret-1", + IssuingCertificateURLs: []string{"http://ca.example.com/ca.crt"}, + })), + givenCSR: gen.CertificateSigningRequest("cr-1", + gen.SetCertificateSigningRequestRequest(testCSR), + gen.SetCertificateSigningRequestSignerName("issuers.cert-manager.io/"+gen.DefaultTestNamespace+".issuer-1"), + ), + assertSignedCert: func(t *testing.T, got *x509.Certificate) { + assert.Equal(t, []string{"http://ca.example.com/ca.crt"}, got.IssuingCertificateURL) + }, + }, "when the Issuer has crlDistributionPoints set, it should appear on the signed ca ": { givenCASecret: gen.SecretFrom(gen.Secret("secret-1"), gen.SetSecretNamespace("default"), gen.SetSecretData(secretDataFor(t, rootPK, rootCert))), givenCAIssuer: gen.Issuer("issuer-1", gen.SetIssuerCA(cmapi.CAIssuer{ @@ -750,15 +760,15 @@ func TestCA_Sign(t *testing.T) { secretsLister: testlisters.FakeSecretListerFrom(testlisters.NewFakeSecretLister(), testlisters.SetFakeSecretNamespaceListerGet(test.givenCASecret, nil), ), - templateGenerator: pki.GenerateTemplateFromCertificateSigningRequest, + templateGenerator: pki.CertificateTemplateFromCertificateSigningRequest, signingFn: pki.SignCSRTemplate, } - gotErr := c.Sign(context.Background(), test.givenCSR, test.givenCAIssuer) + gotErr := c.Sign(t.Context(), test.givenCSR, test.givenCAIssuer) require.NoError(t, gotErr) builder.Sync() - csr, err := builder.Client.CertificatesV1().CertificateSigningRequests().Get(context.TODO(), test.givenCSR.Name, metav1.GetOptions{}) + csr, err := builder.Client.CertificatesV1().CertificateSigningRequests().Get(t.Context(), test.givenCSR.Name, metav1.GetOptions{}) require.NoError(t, err) require.NotEmpty(t, csr.Status.Certificate) diff --git a/pkg/controller/certificatesigningrequests/checks.go b/pkg/controller/certificatesigningrequests/checks.go index 5673da55ed7..5e112a3c7f9 100644 --- a/pkg/controller/certificatesigningrequests/checks.go +++ b/pkg/controller/certificatesigningrequests/checks.go @@ -21,6 +21,7 @@ import ( certificatesv1 "k8s.io/api/certificates/v1" "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/types" "github.com/cert-manager/cert-manager/pkg/apis/certmanager" cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" @@ -44,13 +45,10 @@ func (c *Controller) handleGenericIssuer(obj interface{}) { return } for _, cr := range crs { - log := logf.WithRelatedResource(log, cr) - key, err := keyFunc(cr) - if err != nil { - log.Error(err, "error computing key for resource") - continue - } - c.queue.Add(key) + c.queue.Add(types.NamespacedName{ + Name: cr.Name, + Namespace: cr.Namespace, + }) } } diff --git a/pkg/controller/certificatesigningrequests/controller.go b/pkg/controller/certificatesigningrequests/controller.go index ea9c6bc871b..a3012b1b4ac 100644 --- a/pkg/controller/certificatesigningrequests/controller.go +++ b/pkg/controller/certificatesigningrequests/controller.go @@ -22,7 +22,8 @@ import ( "github.com/go-logr/logr" certificatesv1 "k8s.io/api/certificates/v1" - apierrors "k8s.io/apimachinery/pkg/api/errors" + k8sErrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/types" authzclient "k8s.io/client-go/kubernetes/typed/authorization/v1" certificatesclient "k8s.io/client-go/kubernetes/typed/certificates/v1" certificateslisters "k8s.io/client-go/listers/certificates/v1" @@ -37,8 +38,6 @@ import ( logf "github.com/cert-manager/cert-manager/pkg/logs" ) -var keyFunc = controllerpkg.KeyFunc - // Signer is an implementation of a Kubernetes CertificateSigningRequest // signer, backed by a cert-manager Issuer. type Signer interface { @@ -54,7 +53,7 @@ type SignerConstructor func(*controllerpkg.Context) Signer // informers not covered in the main shared controller implementation. // The returned set of InformerSyncs will be waited on when the controller // starts. -type RegisterExtraInformerFn func(*controllerpkg.Context, logr.Logger, workqueue.RateLimitingInterface) ([]cache.InformerSynced, error) +type RegisterExtraInformerFn func(*controllerpkg.Context, logr.Logger, workqueue.TypedRateLimitingInterface[types.NamespacedName]) ([]cache.InformerSynced, error) // Controller is a base Kubernetes CertificateSigningRequest controller. It is // responsible for orchestrating and performing shared operations that all @@ -72,7 +71,7 @@ type Controller struct { // fieldManager is the manager name used for the Apply operations. fieldManager string - queue workqueue.RateLimitingInterface + queue workqueue.TypedRateLimitingInterface[types.NamespacedName] // logger to be used by this controller log logr.Logger @@ -87,8 +86,8 @@ type Controller struct { // the signer kind to react to when a certificate signing request is synced signerType string - //registerExtraInformers is a list of functions that - //CertificateSigningRequest controllers can use to register custom informers. + // registerExtraInformers is a list of functions that + // CertificateSigningRequest controllers can use to register custom informers. registerExtraInformers []RegisterExtraInformerFn // used for testing @@ -97,7 +96,7 @@ type Controller struct { // New will construct a new certificatesigningrequest controller using the // given Signer implementation. -// Note: the registerExtraInfromers passed here will be 'waited' for when +// Note: the registerExtraInformers passed here will be 'waited' for when // starting to ensure their corresponding listers have synced. // The caller is responsible for ensuring the informer work functions are setup // correctly on any informer. @@ -113,14 +112,19 @@ func New(signerType string, signerConstructor SignerConstructor, registerExtraIn } } -func (c *Controller) Register(ctx *controllerpkg.Context) (workqueue.RateLimitingInterface, []cache.InformerSynced, error) { +func (c *Controller) Register(ctx *controllerpkg.Context) (workqueue.TypedRateLimitingInterface[types.NamespacedName], []cache.InformerSynced, error) { componentName := "certificatesigningrequests-" + c.signerType // construct a new named logger to be reused throughout the controller c.log = logf.FromContext(ctx.RootContext, componentName) // create a queue used to queue up items to be processed - c.queue = workqueue.NewNamedRateLimitingQueue(controllerpkg.DefaultItemBasedRateLimiter(), componentName) + c.queue = workqueue.NewTypedRateLimitingQueueWithConfig( + controllerpkg.DefaultItemBasedRateLimiter(), + workqueue.TypedRateLimitingQueueConfig[types.NamespacedName]{ + Name: componentName, + }, + ) kubeClient := ctx.Client c.sarClient = kubeClient.AuthorizationV1().SubjectAccessReviews() @@ -128,7 +132,7 @@ func (c *Controller) Register(ctx *controllerpkg.Context) (workqueue.RateLimitin issuerInformer := ctx.SharedInformerFactory.Certmanager().V1().Issuers() // obtain references to all the informers used by this controller - csrInformer := ctx.KubeSharedInformerFactory.Certificates().V1().CertificateSigningRequests() + csrInformer := ctx.KubeSharedInformerFactory.CertificateSigningRequests() // build a list of InformerSynced functions that will be returned by the // Register method. The controller will only begin processing items once all @@ -153,7 +157,9 @@ func (c *Controller) Register(ctx *controllerpkg.Context) (workqueue.RateLimitin clusterIssuerInformer := ctx.SharedInformerFactory.Certmanager().V1().ClusterIssuers() if ctx.Namespace == "" { // register handler function for clusterissuer resources - clusterIssuerInformer.Informer().AddEventHandler(&controllerpkg.BlockingEventHandler{WorkFunc: c.handleGenericIssuer}) + if _, err := clusterIssuerInformer.Informer().AddEventHandler(&controllerpkg.BlockingEventHandler{WorkFunc: c.handleGenericIssuer}); err != nil { + return nil, nil, fmt.Errorf("error setting up event handler: %v", err) + } mustSync = append(mustSync, clusterIssuerInformer.Informer().HasSynced) } @@ -161,8 +167,12 @@ func (c *Controller) Register(ctx *controllerpkg.Context) (workqueue.RateLimitin c.csrLister = csrInformer.Lister() // register handler functions - csrInformer.Informer().AddEventHandler(&controllerpkg.QueuingEventHandler{Queue: c.queue}) - issuerInformer.Informer().AddEventHandler(&controllerpkg.BlockingEventHandler{WorkFunc: c.handleGenericIssuer}) + if _, err := csrInformer.Informer().AddEventHandler(&controllerpkg.QueuingEventHandler{Queue: c.queue}); err != nil { + return nil, nil, fmt.Errorf("error setting up event handler: %v", err) + } + if _, err := issuerInformer.Informer().AddEventHandler(&controllerpkg.BlockingEventHandler{WorkFunc: c.handleGenericIssuer}); err != nil { + return nil, nil, fmt.Errorf("error setting up event handler: %v", err) + } // create an issuer helper for reading generic issuers c.helper = issuer.NewHelper(issuerInformer.Lister(), clusterIssuerInformer.Lister()) @@ -182,25 +192,19 @@ func (c *Controller) Register(ctx *controllerpkg.Context) (workqueue.RateLimitin return c.queue, mustSync, nil } -func (c *Controller) ProcessItem(ctx context.Context, key string) error { +func (c *Controller) ProcessItem(ctx context.Context, key types.NamespacedName) error { log := logf.FromContext(ctx) - dbg := log.V(logf.DebugLevel) - _, name, err := cache.SplitMetaNamespaceKey(key) - if err != nil { - log.Error(err, "invalid resource key") - return nil - } + name := key.Name csr, err := c.csrLister.Get(name) - if apierrors.IsNotFound(err) { - dbg.Info("certificate signing request in work queue no longer exists", "error", err.Error()) - return nil - } - - if err != nil { + if err != nil && !k8sErrors.IsNotFound(err) { return err } + if csr == nil || csr.DeletionTimestamp != nil { + // If the CertificateSigningRequest object was/ is being deleted, we don't want to start signing. + return nil + } ctx = logf.NewContext(ctx, logf.WithResource(log, csr)) return c.Sync(ctx, csr) diff --git a/pkg/controller/certificatesigningrequests/controller_test.go b/pkg/controller/certificatesigningrequests/controller_test.go index db7b7c043a1..72ef50791ad 100644 --- a/pkg/controller/certificatesigningrequests/controller_test.go +++ b/pkg/controller/certificatesigningrequests/controller_test.go @@ -27,6 +27,7 @@ import ( corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" coretesting "k8s.io/client-go/testing" fakeclock "k8s.io/utils/clock/testing" @@ -34,7 +35,6 @@ import ( cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" "github.com/cert-manager/cert-manager/pkg/controller" - controllerpkg "github.com/cert-manager/cert-manager/pkg/controller" "github.com/cert-manager/cert-manager/pkg/controller/certificatesigningrequests/fake" "github.com/cert-manager/cert-manager/pkg/controller/certificatesigningrequests/util" testpkg "github.com/cert-manager/cert-manager/pkg/controller/test" @@ -75,7 +75,7 @@ func TestController(t *testing.T) { // key that should be passed to ProcessItem. If not set, the // 'namespace/name' of the 'CertificateSigningRequest' field will be used. // If neither is set, the key will be "". - key string + key types.NamespacedName // CertificateSigningRequest to be synced for the test. If not set, the // 'key' will be passed to ProcessItem instead. @@ -107,13 +107,19 @@ func TestController(t *testing.T) { sarReaction: sarReactionExpectNoCall, }, "do nothing if an invalid 'key' is used": { - key: "abc/def/ghi", + key: types.NamespacedName{ + Namespace: "abc", + Name: "def/ghi", + }, signerType: apiutil.IssuerCA, signerImpl: signerExpectNoCall, sarReaction: sarReactionExpectNoCall, }, "do nothing if a key references a CertificateSigningRequest that does not exist": { - key: "namespace/name", + key: types.NamespacedName{ + Namespace: "namespace", + Name: "name", + }, signerType: apiutil.IssuerCA, signerImpl: signerExpectNoCall, sarReaction: sarReactionExpectNoCall, @@ -621,14 +627,14 @@ func TestController(t *testing.T) { defer builder.Stop() key := test.key - if key == "" && test.existingCSR != nil { - key, err = controllerpkg.KeyFunc(test.existingCSR) - if err != nil { - t.Fatal(err) + if key == (types.NamespacedName{}) && test.existingCSR != nil { + key = types.NamespacedName{ + Name: test.existingCSR.Name, + Namespace: test.existingCSR.Namespace, } } - gotErr := controller.ProcessItem(context.Background(), key) + gotErr := controller.ProcessItem(t.Context(), key) if test.wantErr != (gotErr != nil) { t.Errorf("got unexpected error, exp=%t got=%v", test.wantErr, gotErr) diff --git a/pkg/controller/certificatesigningrequests/selfsigned/checks.go b/pkg/controller/certificatesigningrequests/selfsigned/checks.go index c6d2073c5fe..06fc99de638 100644 --- a/pkg/controller/certificatesigningrequests/selfsigned/checks.go +++ b/pkg/controller/certificatesigningrequests/selfsigned/checks.go @@ -24,6 +24,7 @@ import ( corev1 "k8s.io/api/core/v1" k8sErrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/types" clientv1 "k8s.io/client-go/listers/certificates/v1" "k8s.io/client-go/util/workqueue" @@ -43,14 +44,14 @@ import ( func handleSecretReferenceWorkFunc(log logr.Logger, lister clientv1.CertificateSigningRequestLister, helper issuer.Helper, - queue workqueue.RateLimitingInterface, + queue workqueue.TypedRateLimitingInterface[types.NamespacedName], issuerOptions controllerpkg.IssuerOptions, ) func(obj any) { return func(obj any) { log := log.WithName("handleSecretReference") - secret, ok := obj.(*corev1.Secret) + secret, ok := controllerpkg.ToSecret(obj) if !ok { - log.Error(nil, "object is not a secret") + log.Error(nil, "object is not a secret", "object", obj) return } log = logf.WithResource(log, secret) @@ -60,13 +61,10 @@ func handleSecretReferenceWorkFunc(log logr.Logger, return } for _, request := range requests { - log := logf.WithRelatedResource(log, request) - key, err := controllerpkg.KeyFunc(request) - if err != nil { - log.Error(err, "error computing key for resource") - continue - } - queue.Add(key) + queue.Add(types.NamespacedName{ + Name: request.Name, + Namespace: request.Namespace, + }) } } } @@ -88,7 +86,7 @@ func certificateSigningRequestsForSecret(log logr.Logger, return nil, fmt.Errorf("failed to list certificate requests: %w", err) } - dbg.Info("checking if self signed certificate signing requests reference secret") + dbg.Info("checking if self-signed certificate signing requests reference secret") var affected []*certificatesv1.CertificateSigningRequest for _, request := range requests { ref, ok := util.SignerIssuerRefFromSignerName(request.Spec.SignerName) @@ -103,7 +101,7 @@ func certificateSigningRequestsForSecret(log logr.Logger, continue } - issuerObj, err := helper.GetGenericIssuer(cmmeta.ObjectReference{ + issuerObj, err := helper.GetGenericIssuer(cmmeta.IssuerReference{ Name: ref.Name, Kind: kind, Group: ref.Group, diff --git a/pkg/controller/certificatesigningrequests/selfsigned/checks_test.go b/pkg/controller/certificatesigningrequests/selfsigned/checks_test.go index fdb9f3a32e4..ee46854201d 100644 --- a/pkg/controller/certificatesigningrequests/selfsigned/checks_test.go +++ b/pkg/controller/certificatesigningrequests/selfsigned/checks_test.go @@ -23,8 +23,9 @@ import ( "github.com/stretchr/testify/require" certificatesv1 "k8s.io/api/certificates/v1" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" "k8s.io/client-go/util/workqueue" - "k8s.io/klog/v2/klogr" + "k8s.io/klog/v2/ktesting" cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" controllerpkg "github.com/cert-manager/cert-manager/pkg/controller" @@ -38,7 +39,7 @@ func Test_handleSecretReferenceWorkFunc(t *testing.T) { secret runtime.Object existingCSRs []runtime.Object existingIssuers []runtime.Object - expectedQueue []string + expectedQueue []types.NamespacedName }{ "if given object is not secret, expect empty queue": { secret: gen.Certificate("not-a-secret"), @@ -65,7 +66,7 @@ func Test_handleSecretReferenceWorkFunc(t *testing.T) { gen.SetIssuerSelfSigned(cmapi.SelfSignedIssuer{}), ), }, - expectedQueue: []string{}, + expectedQueue: []types.NamespacedName{}, }, "if no requests then expect empty queue": { secret: gen.Secret("test-secret", gen.SetSecretNamespace("test-namespace")), @@ -79,7 +80,7 @@ func Test_handleSecretReferenceWorkFunc(t *testing.T) { gen.SetIssuerSelfSigned(cmapi.SelfSignedIssuer{}), ), }, - expectedQueue: []string{}, + expectedQueue: []types.NamespacedName{}, }, "referenced requests should be added to the queue": { secret: gen.Secret("test-secret", gen.SetSecretNamespace("test-namespace")), @@ -106,7 +107,10 @@ func Test_handleSecretReferenceWorkFunc(t *testing.T) { gen.SetIssuerSelfSigned(cmapi.SelfSignedIssuer{}), ), }, - expectedQueue: []string{"a", "b"}, + expectedQueue: []types.NamespacedName{ + {Name: "a"}, + {Name: "b"}, + }, }, } @@ -120,7 +124,7 @@ func Test_handleSecretReferenceWorkFunc(t *testing.T) { defer builder.Stop() builder.Init() - lister := builder.Context.KubeSharedInformerFactory.Certificates().V1().CertificateSigningRequests().Lister() + lister := builder.Context.KubeSharedInformerFactory.CertificateSigningRequests().Lister() helper := issuer.NewHelper( builder.Context.SharedInformerFactory.Certmanager().V1().Issuers().Lister(), builder.Context.SharedInformerFactory.Certmanager().V1().ClusterIssuers().Lister(), @@ -128,15 +132,15 @@ func Test_handleSecretReferenceWorkFunc(t *testing.T) { builder.Start() - queue := workqueue.NewRateLimitingQueue(workqueue.DefaultControllerRateLimiter()) - handleSecretReferenceWorkFunc(klogr.New(), lister, helper, queue, + queue := workqueue.NewTypedRateLimitingQueueWithConfig(workqueue.DefaultTypedControllerRateLimiter[types.NamespacedName](), workqueue.TypedRateLimitingQueueConfig[types.NamespacedName]{}) + handleSecretReferenceWorkFunc(ktesting.NewLogger(t, ktesting.NewConfig()), lister, helper, queue, controllerpkg.IssuerOptions{ClusterResourceNamespace: "test-namespace"}, )(test.secret) require.Equal(t, len(test.expectedQueue), queue.Len()) - var actualQueue []string + var actualQueue []types.NamespacedName for range test.expectedQueue { i, _ := queue.Get() - actualQueue = append(actualQueue, i.(string)) + actualQueue = append(actualQueue, i) } assert.ElementsMatch(t, test.expectedQueue, actualQueue) }) @@ -190,7 +194,7 @@ func Test_certificatesRequestsForSecret(t *testing.T) { clusterResourceNamespace: "test-namespace", expectedAffected: []*certificatesv1.CertificateSigningRequest{}, }, - "if issuers are not self signed then don't return requests": { + "if issuers are not self-signed then don't return requests": { existingCSRs: []runtime.Object{ gen.CertificateSigningRequest("a", gen.AddCertificateSigningRequestAnnotations(map[string]string{ @@ -330,7 +334,7 @@ func Test_certificatesRequestsForSecret(t *testing.T) { defer builder.Stop() builder.Init() - lister := builder.Context.KubeSharedInformerFactory.Certificates().V1().CertificateSigningRequests().Lister() + lister := builder.Context.KubeSharedInformerFactory.CertificateSigningRequests().Lister() helper := issuer.NewHelper( builder.Context.SharedInformerFactory.Certmanager().V1().Issuers().Lister(), builder.Context.SharedInformerFactory.Certmanager().V1().ClusterIssuers().Lister(), @@ -338,7 +342,7 @@ func Test_certificatesRequestsForSecret(t *testing.T) { builder.Start() - affected, err := certificateSigningRequestsForSecret(klogr.New(), lister, helper, secret.DeepCopy(), controllerpkg.IssuerOptions{ + affected, err := certificateSigningRequestsForSecret(ktesting.NewLogger(t, ktesting.NewConfig()), lister, helper, secret.DeepCopy(), controllerpkg.IssuerOptions{ ClusterResourceNamespace: test.clusterResourceNamespace, }) diff --git a/pkg/controller/certificatesigningrequests/selfsigned/selfsigned.go b/pkg/controller/certificatesigningrequests/selfsigned/selfsigned.go index c3b577e84b5..331ce12f3df 100644 --- a/pkg/controller/certificatesigningrequests/selfsigned/selfsigned.go +++ b/pkg/controller/certificatesigningrequests/selfsigned/selfsigned.go @@ -23,15 +23,17 @@ import ( "errors" "fmt" + "github.com/go-logr/logr" certificatesv1 "k8s.io/api/certificates/v1" corev1 "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/types" certificatesclient "k8s.io/client-go/kubernetes/typed/certificates/v1" - corelisters "k8s.io/client-go/listers/core/v1" "k8s.io/client-go/tools/cache" "k8s.io/client-go/tools/record" "k8s.io/client-go/util/workqueue" + internalinformers "github.com/cert-manager/cert-manager/internal/informers" apiutil "github.com/cert-manager/cert-manager/pkg/api/util" cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" experimentalapi "github.com/cert-manager/cert-manager/pkg/apis/experimental/v1alpha1" @@ -43,7 +45,6 @@ import ( cmerrors "github.com/cert-manager/cert-manager/pkg/util/errors" "github.com/cert-manager/cert-manager/pkg/util/kube" "github.com/cert-manager/cert-manager/pkg/util/pki" - "github.com/go-logr/logr" ) const ( @@ -57,7 +58,7 @@ type signingFn func(*x509.Certificate, *x509.Certificate, crypto.PublicKey, inte // using SelfSigning Issuers. type SelfSigned struct { issuerOptions controllerpkg.IssuerOptions - secretsLister corelisters.SecretLister + secretsLister internalinformers.SecretLister certClient certificatesclient.CertificateSigningRequestInterface @@ -79,16 +80,18 @@ func init() { // Handle informed Secrets which may be referenced by the // "experimental.cert-manager.io/private-key-secret-name" annotation. - func(ctx *controllerpkg.Context, log logr.Logger, queue workqueue.RateLimitingInterface) ([]cache.InformerSynced, error) { - secretInformer := ctx.KubeSharedInformerFactory.Core().V1().Secrets().Informer() - certificateSigningRequestLister := ctx.KubeSharedInformerFactory.Certificates().V1().CertificateSigningRequests().Lister() + func(ctx *controllerpkg.Context, log logr.Logger, queue workqueue.TypedRateLimitingInterface[types.NamespacedName]) ([]cache.InformerSynced, error) { + secretInformer := ctx.KubeSharedInformerFactory.Secrets().Informer() + certificateSigningRequestLister := ctx.KubeSharedInformerFactory.CertificateSigningRequests().Lister() helper := issuer.NewHelper( ctx.SharedInformerFactory.Certmanager().V1().Issuers().Lister(), ctx.SharedInformerFactory.Certmanager().V1().ClusterIssuers().Lister(), ) - secretInformer.AddEventHandler(&controllerpkg.BlockingEventHandler{ + if _, err := secretInformer.AddEventHandler(&controllerpkg.BlockingEventHandler{ WorkFunc: handleSecretReferenceWorkFunc(log, certificateSigningRequestLister, helper, queue, ctx.IssuerOptions), - }) + }); err != nil { + return nil, fmt.Errorf("error setting up event handler: %v", err) + } return []cache.InformerSynced{ secretInformer.HasSynced, ctx.SharedInformerFactory.Certmanager().V1().Issuers().Informer().HasSynced, @@ -104,7 +107,7 @@ func init() { func NewSelfSigned(ctx *controllerpkg.Context) certificatesigningrequests.Signer { return &SelfSigned{ issuerOptions: ctx.IssuerOptions, - secretsLister: ctx.KubeSharedInformerFactory.Core().V1().Secrets().Lister(), + secretsLister: ctx.KubeSharedInformerFactory.Secrets().Lister(), certClient: ctx.Client.CertificatesV1().CertificateSigningRequests(), fieldManager: ctx.FieldManager, recorder: ctx.Recorder, @@ -160,7 +163,7 @@ func (s *SelfSigned) Sign(ctx context.Context, csr *certificatesv1.CertificateSi return err } - template, err := pki.GenerateTemplateFromCertificateSigningRequest(csr) + template, err := pki.CertificateTemplateFromCertificateSigningRequest(csr) if err != nil { message := fmt.Sprintf("Error generating certificate template: %s", err) log.Error(err, message) @@ -214,8 +217,8 @@ func (s *SelfSigned) Sign(ctx context.Context, csr *certificatesv1.CertificateSi return err } - log.V(logf.DebugLevel).Info("self signed certificate issued") - s.recorder.Event(csr, corev1.EventTypeNormal, "CertificateIssued", "Certificate self signed successfully") + log.V(logf.DebugLevel).Info("self-signed certificate issued") + s.recorder.Event(csr, corev1.EventTypeNormal, "CertificateIssued", "Certificate self-signed successfully") return nil } diff --git a/pkg/controller/certificatesigningrequests/selfsigned/selfsigned_test.go b/pkg/controller/certificatesigningrequests/selfsigned/selfsigned_test.go index e33d2692c88..94c7d4f637c 100644 --- a/pkg/controller/certificatesigningrequests/selfsigned/selfsigned_test.go +++ b/pkg/controller/certificatesigningrequests/selfsigned/selfsigned_test.go @@ -17,12 +17,8 @@ limitations under the License. package selfsigned import ( - "context" "crypto" - "crypto/rand" "crypto/x509" - "crypto/x509/pkix" - "encoding/pem" "errors" "math" "testing" @@ -35,6 +31,7 @@ import ( corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" coretesting "k8s.io/client-go/testing" fakeclock "k8s.io/utils/clock/testing" @@ -69,18 +66,10 @@ func mustCryptoBundle(t *testing.T) cryptoBundle { t.Fatal(err) } - template := x509.CertificateRequest{ - Subject: pkix.Name{ - CommonName: "test", - }, - SignatureAlgorithm: x509.ECDSAWithSHA256, - } - - csrBytes, err := x509.CreateCertificateRequest(rand.Reader, &template, key) + csrPEM, err := gen.CSRWithSigner(key, gen.SetCSRCommonName("test")) if err != nil { t.Fatal(err) } - csrPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE REQUEST", Bytes: csrBytes}) keyPEM, err := pki.EncodePKCS8PrivateKey(key) if err != nil { @@ -286,7 +275,7 @@ func TestProcessItem(t *testing.T) { }, }, ExpectedEvents: []string{ - `Warning ErrorParsingKey Failed to parse signing key from secret default-unit-test-ns/test-secret: error decoding private key PEM block`, + `Warning ErrorParsingKey Failed to parse signing key from secret default-unit-test-ns/test-secret: error decoding private key PEM block: no PEM data was found in given input`, }, ExpectedActions: []testpkg.Action{ @@ -331,7 +320,7 @@ func TestProcessItem(t *testing.T) { CertManagerObjects: []runtime.Object{baseIssuer.DeepCopy()}, KubeObjects: []runtime.Object{csrBundle.secret}, ExpectedEvents: []string{ - "Warning ErrorGenerating Error generating certificate template: failed to decode csr", + "Warning ErrorGenerating Error generating certificate template: error decoding certificate request PEM block: no PEM data was found in given input", }, ExpectedActions: []testpkg.Action{ @@ -375,7 +364,7 @@ func TestProcessItem(t *testing.T) { Type: certificatesv1.CertificateFailed, Status: corev1.ConditionTrue, Reason: "ErrorGenerating", - Message: "Error generating certificate template: failed to decode csr", + Message: "Error generating certificate template: error decoding certificate request PEM block: no PEM data was found in given input", LastTransitionTime: metaFixedClockStart, LastUpdateTime: metaFixedClockStart, }), @@ -541,7 +530,7 @@ func TestProcessItem(t *testing.T) { CertManagerObjects: []runtime.Object{baseIssuer.DeepCopy()}, KubeObjects: []runtime.Object{csrBundle.secret}, ExpectedEvents: []string{ - "Normal CertificateIssued Certificate self signed successfully", + "Normal CertificateIssued Certificate self-signed successfully", }, ExpectedActions: []testpkg.Action{ testpkg.NewAction(coretesting.NewCreateAction( @@ -627,10 +616,14 @@ func TestProcessItem(t *testing.T) { apiutil.IssuerSelfSigned, func(*controller.Context) certificatesigningrequests.Signer { return selfsigned }, ) - controller.Register(test.builder.Context) + if _, _, err := controller.Register(test.builder.Context); err != nil { + t.Fatal(err) + } test.builder.Start() - err := controller.ProcessItem(context.Background(), test.csr.Name) + err := controller.ProcessItem(t.Context(), types.NamespacedName{ + Name: test.csr.Name, + }) if err != nil && !test.expectedErr { t.Errorf("expected to not get an error, but got: %v", err) } @@ -776,11 +769,11 @@ func TestSign(t *testing.T) { signingFn: pki.SignCertificate, } - gotErr := selfsigned.Sign(context.Background(), test.csr, test.issuer) + gotErr := selfsigned.Sign(t.Context(), test.csr, test.issuer) require.NoError(t, gotErr) builder.Sync() - csr, err := builder.Client.CertificatesV1().CertificateSigningRequests().Get(context.TODO(), test.csr.Name, metav1.GetOptions{}) + csr, err := builder.Client.CertificatesV1().CertificateSigningRequests().Get(t.Context(), test.csr.Name, metav1.GetOptions{}) require.NoError(t, err) require.NotEmpty(t, csr.Status.Certificate) diff --git a/pkg/controller/certificatesigningrequests/sync.go b/pkg/controller/certificatesigningrequests/sync.go index 9e5ed0150b3..387147a2b50 100644 --- a/pkg/controller/certificatesigningrequests/sync.go +++ b/pkg/controller/certificatesigningrequests/sync.go @@ -81,7 +81,7 @@ func (c *Controller) Sync(ctx context.Context, csr *certificatesv1.CertificateSi return nil } - issuerObj, err := c.helper.GetGenericIssuer(cmmeta.ObjectReference{ + issuerObj, err := c.helper.GetGenericIssuer(cmmeta.IssuerReference{ Name: ref.Name, Kind: kind, Group: ref.Group, @@ -102,7 +102,7 @@ func (c *Controller) Sync(ctx context.Context, csr *certificatesv1.CertificateSi signerType, err := apiutil.NameForIssuer(issuerObj) if err != nil { c.recorder.Eventf(csr, corev1.EventTypeWarning, "IssuerTypeMissing", "Referenced %s %s/%s is missing type", kind, ref.Namespace, ref.Name) - return nil + return nil //nolint:nilerr } // This CertificateSigningRequest is not meant for us, ignore diff --git a/pkg/controller/certificatesigningrequests/sync_test.go b/pkg/controller/certificatesigningrequests/sync_test.go index 9c813d31687..429045731fb 100644 --- a/pkg/controller/certificatesigningrequests/sync_test.go +++ b/pkg/controller/certificatesigningrequests/sync_test.go @@ -24,6 +24,7 @@ import ( certificatesv1 "k8s.io/api/certificates/v1" corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" coretesting "k8s.io/client-go/testing" fakeclock "k8s.io/utils/clock/testing" @@ -36,7 +37,6 @@ import ( csrutil "github.com/cert-manager/cert-manager/pkg/controller/certificatesigningrequests/util" testpkg "github.com/cert-manager/cert-manager/pkg/controller/test" "github.com/cert-manager/cert-manager/test/unit/gen" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) var ( @@ -330,11 +330,13 @@ func TestController_Sync(t *testing.T) { } c := New(util.IssuerSelfSigned, func(*controller.Context) Signer { return scenario.signerImpl }) - c.Register(scenario.builder.Context) + if _, _, err := c.Register(scenario.builder.Context); err != nil { + t.Fatal(err) + } scenario.builder.Start() - err := c.Sync(context.Background(), scenario.csr) + err := c.Sync(t.Context(), scenario.csr) if (err == nil) == scenario.wantErr { t.Errorf("expected error: %v, but got: %v", scenario.wantErr, err) } diff --git a/pkg/controller/certificatesigningrequests/util/conditions.go b/pkg/controller/certificatesigningrequests/util/conditions.go index 1db85f4d741..47a76de829a 100644 --- a/pkg/controller/certificatesigningrequests/util/conditions.go +++ b/pkg/controller/certificatesigningrequests/util/conditions.go @@ -20,6 +20,7 @@ import ( certificatesv1 "k8s.io/api/certificates/v1" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/klog/v2" "k8s.io/utils/clock" logf "github.com/cert-manager/cert-manager/pkg/logs" @@ -69,8 +70,10 @@ func CertificateSigningRequestSetFailed(csr *certificatesv1.CertificateSigningRe LastUpdateTime: nowTime, }) - logf.V(logf.InfoLevel).Infof("Setting lastTransitionTime for CertificateSigningRequest %s/%s condition Failed to %v", - csr.Namespace, csr.Name, nowTime.Time) + logf.Log.V(logf.InfoLevel).Info("Setting lastTransitionTime for CertificateSigningRequest condition", + "certificateSigningRequest", klog.KObj(csr), + "condition", certificatesv1.CertificateFailed, + "lastTransitionTime", nowTime.Time) } func certificateSigningRequestGetCondition(csr *certificatesv1.CertificateSigningRequest, condType certificatesv1.RequestConditionType) *certificatesv1.CertificateSigningRequestCondition { diff --git a/pkg/controller/certificatesigningrequests/vault/vault.go b/pkg/controller/certificatesigningrequests/vault/vault.go index 53a0dd03f06..2ffff36205c 100644 --- a/pkg/controller/certificatesigningrequests/vault/vault.go +++ b/pkg/controller/certificatesigningrequests/vault/vault.go @@ -18,17 +18,16 @@ package vault import ( "context" - "crypto" - "crypto/x509" "fmt" certificatesv1 "k8s.io/api/certificates/v1" corev1 "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/client-go/kubernetes" certificatesclient "k8s.io/client-go/kubernetes/typed/certificates/v1" - corelisters "k8s.io/client-go/listers/core/v1" "k8s.io/client-go/tools/record" + internalinformers "github.com/cert-manager/cert-manager/internal/informers" internalvault "github.com/cert-manager/cert-manager/internal/vault" apiutil "github.com/cert-manager/cert-manager/pkg/api/util" cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" @@ -43,13 +42,12 @@ const ( CSRControllerName = "certificatesigningrequests-issuer-vault" ) -type signingFn func(*x509.Certificate, *x509.Certificate, crypto.PublicKey, interface{}) ([]byte, *x509.Certificate, error) - // Vault is a controller for signing Kubernetes CertificateSigningRequest // using Vault Issuers. type Vault struct { issuerOptions controllerpkg.IssuerOptions - secretsLister corelisters.SecretLister + kclient kubernetes.Interface + secretsLister internalinformers.SecretLister recorder record.EventRecorder @@ -71,7 +69,8 @@ func init() { func NewVault(ctx *controllerpkg.Context) certificatesigningrequests.Signer { return &Vault{ issuerOptions: ctx.IssuerOptions, - secretsLister: ctx.KubeSharedInformerFactory.Core().V1().Secrets().Lister(), + kclient: ctx.Client, + secretsLister: ctx.KubeSharedInformerFactory.Secrets().Lister(), recorder: ctx.Recorder, certClient: ctx.Client.CertificatesV1().CertificateSigningRequests(), clientBuilder: internalvault.New, @@ -89,7 +88,8 @@ func (v *Vault) Sign(ctx context.Context, csr *certificatesv1.CertificateSigning resourceNamespace := v.issuerOptions.ResourceNamespace(issuerObj) - client, err := v.clientBuilder(resourceNamespace, v.secretsLister, issuerObj) + createTokenFn := func(ns string) internalvault.CreateToken { return v.kclient.CoreV1().ServiceAccounts(ns).CreateToken } + client, err := v.clientBuilder(ctx, resourceNamespace, createTokenFn, v.secretsLister, issuerObj) if apierrors.IsNotFound(err) { message := "Required secret resource not found" log.Error(err, message) diff --git a/pkg/controller/certificatesigningrequests/vault/vault_test.go b/pkg/controller/certificatesigningrequests/vault/vault_test.go index 7cd9ebc33be..6971018508c 100644 --- a/pkg/controller/certificatesigningrequests/vault/vault_test.go +++ b/pkg/controller/certificatesigningrequests/vault/vault_test.go @@ -30,17 +30,18 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" - corelisters "k8s.io/client-go/listers/core/v1" + "k8s.io/apimachinery/pkg/types" coretesting "k8s.io/client-go/testing" fakeclock "k8s.io/utils/clock/testing" + internalinformers "github.com/cert-manager/cert-manager/internal/informers" internalvault "github.com/cert-manager/cert-manager/internal/vault" fakevault "github.com/cert-manager/cert-manager/internal/vault/fake" apiutil "github.com/cert-manager/cert-manager/pkg/api/util" "github.com/cert-manager/cert-manager/pkg/apis/certmanager" cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" - "github.com/cert-manager/cert-manager/pkg/controller" + controllerpkg "github.com/cert-manager/cert-manager/pkg/controller" "github.com/cert-manager/cert-manager/pkg/controller/certificatesigningrequests" "github.com/cert-manager/cert-manager/pkg/controller/certificatesigningrequests/util" testpkg "github.com/cert-manager/cert-manager/pkg/controller/test" @@ -70,6 +71,7 @@ func TestProcessItem(t *testing.T) { }, }, }, + Server: "https://example.vault.com", }), gen.AddIssuerCondition(cmapi.IssuerCondition{ Type: cmapi.IssuerConditionReady, @@ -129,7 +131,7 @@ func TestProcessItem(t *testing.T) { Status: corev1.ConditionTrue, }), ), - clientBuilder: func(_ string, _ corelisters.SecretLister, _ cmapi.GenericIssuer) (internalvault.Interface, error) { + clientBuilder: func(_ context.Context, _ string, _ func(ns string) internalvault.CreateToken, _ internalinformers.SecretLister, _ cmapi.GenericIssuer) (internalvault.Interface, error) { return nil, apierrors.NewNotFound(schema.GroupResource{}, "test-secret") }, builder: &testpkg.Builder{ @@ -190,7 +192,7 @@ func TestProcessItem(t *testing.T) { Status: corev1.ConditionTrue, }), ), - clientBuilder: func(_ string, _ corelisters.SecretLister, _ cmapi.GenericIssuer) (internalvault.Interface, error) { + clientBuilder: func(_ context.Context, _ string, _ func(ns string) internalvault.CreateToken, _ internalinformers.SecretLister, _ cmapi.GenericIssuer) (internalvault.Interface, error) { return nil, errors.New("generic error") }, expectedErr: true, @@ -234,7 +236,7 @@ func TestProcessItem(t *testing.T) { Status: corev1.ConditionTrue, }), ), - clientBuilder: func(_ string, _ corelisters.SecretLister, _ cmapi.GenericIssuer) (internalvault.Interface, error) { + clientBuilder: func(_ context.Context, _ string, _ func(ns string) internalvault.CreateToken, _ internalinformers.SecretLister, _ cmapi.GenericIssuer) (internalvault.Interface, error) { return fakevault.New(), nil }, builder: &testpkg.Builder{ @@ -296,7 +298,7 @@ func TestProcessItem(t *testing.T) { Status: corev1.ConditionTrue, }), ), - clientBuilder: func(_ string, _ corelisters.SecretLister, _ cmapi.GenericIssuer) (internalvault.Interface, error) { + clientBuilder: func(_ context.Context, _ string, _ func(ns string) internalvault.CreateToken, _ internalinformers.SecretLister, _ cmapi.GenericIssuer) (internalvault.Interface, error) { return fakevault.New().WithSign(nil, nil, errors.New("sign error")), nil }, builder: &testpkg.Builder{ @@ -357,7 +359,7 @@ func TestProcessItem(t *testing.T) { Status: corev1.ConditionTrue, }), ), - clientBuilder: func(_ string, _ corelisters.SecretLister, _ cmapi.GenericIssuer) (internalvault.Interface, error) { + clientBuilder: func(_ context.Context, _ string, _ func(ns string) internalvault.CreateToken, _ internalinformers.SecretLister, _ cmapi.GenericIssuer) (internalvault.Interface, error) { return fakevault.New().WithSign([]byte("signed-cert"), []byte("signing-ca"), nil), nil }, builder: &testpkg.Builder{ @@ -436,12 +438,16 @@ func TestProcessItem(t *testing.T) { controller := certificatesigningrequests.New( apiutil.IssuerVault, - func(*controller.Context) certificatesigningrequests.Signer { return vault }, + func(*controllerpkg.Context) certificatesigningrequests.Signer { return vault }, ) - controller.Register(test.builder.Context) + if _, _, err := controller.Register(test.builder.Context); err != nil { + t.Fatal(err) + } test.builder.Start() - err := controller.ProcessItem(context.Background(), test.csr.Name) + err := controller.ProcessItem(t.Context(), types.NamespacedName{ + Name: test.csr.Name, + }) if err != nil && !test.expectedErr { t.Errorf("expected to not get an error, but got: %v", err) } diff --git a/pkg/controller/certificatesigningrequests/venafi/venafi.go b/pkg/controller/certificatesigningrequests/venafi/venafi.go index f5d21db4df7..d1d906ff6cf 100644 --- a/pkg/controller/certificatesigningrequests/venafi/venafi.go +++ b/pkg/controller/certificatesigningrequests/venafi/venafi.go @@ -21,15 +21,15 @@ import ( "encoding/json" "fmt" - "github.com/Venafi/vcert/v4/pkg/endpoint" + "github.com/Venafi/vcert/v5/pkg/endpoint" certificatesv1 "k8s.io/api/certificates/v1" corev1 "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" certificatesclient "k8s.io/client-go/kubernetes/typed/certificates/v1" - corelisters "k8s.io/client-go/listers/core/v1" "k8s.io/client-go/tools/record" + internalinformers "github.com/cert-manager/cert-manager/internal/informers" apiutil "github.com/cert-manager/cert-manager/pkg/api/util" cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" experimentalapi "github.com/cert-manager/cert-manager/pkg/apis/experimental/v1alpha1" @@ -40,7 +40,6 @@ import ( venafiapi "github.com/cert-manager/cert-manager/pkg/issuer/venafi/client/api" logf "github.com/cert-manager/cert-manager/pkg/logs" "github.com/cert-manager/cert-manager/pkg/metrics" - "github.com/cert-manager/cert-manager/pkg/util/pki" utilpki "github.com/cert-manager/cert-manager/pkg/util/pki" ) @@ -53,7 +52,7 @@ const ( // Issuer or ClusterIssuer type Venafi struct { issuerOptions controllerpkg.IssuerOptions - secretsLister corelisters.SecretLister + secretsLister internalinformers.SecretLister certClient certificatesclient.CertificateSigningRequestInterface recorder record.EventRecorder @@ -63,6 +62,9 @@ type Venafi struct { // fieldManager is the manager name used for the Apply operations. fieldManager string + + // userAgent is the string used as the UserAgent when making HTTP calls. + userAgent string } func init() { @@ -76,12 +78,13 @@ func init() { func NewVenafi(ctx *controllerpkg.Context) certificatesigningrequests.Signer { return &Venafi{ issuerOptions: ctx.IssuerOptions, - secretsLister: ctx.KubeSharedInformerFactory.Core().V1().Secrets().Lister(), + secretsLister: ctx.KubeSharedInformerFactory.Secrets().Lister(), certClient: ctx.Client.CertificatesV1().CertificateSigningRequests(), recorder: ctx.Recorder, clientBuilder: venaficlient.New, fieldManager: ctx.FieldManager, metrics: ctx.Metrics, + userAgent: ctx.RESTConfig.UserAgent, } } @@ -99,7 +102,7 @@ func (v *Venafi) Sign(ctx context.Context, csr *certificatesv1.CertificateSignin resourceNamespace := v.issuerOptions.ResourceNamespace(issuerObj) - client, err := v.clientBuilder(resourceNamespace, v.secretsLister, issuerObj, v.metrics, log) + client, err := v.clientBuilder(resourceNamespace, v.secretsLister, issuerObj, v.metrics, log, v.userAgent) if apierrors.IsNotFound(err) { message := "Required secret resource not found" v.recorder.Event(csr, corev1.EventTypeWarning, "SecretNotFound", message) @@ -126,7 +129,7 @@ func (v *Venafi) Sign(ctx context.Context, csr *certificatesv1.CertificateSignin } } - duration, err := pki.DurationFromCertificateSigningRequest(csr) + duration, err := utilpki.DurationFromCertificateSigningRequest(csr) if err != nil { message := fmt.Sprintf("Failed to parse requested duration: %s", err) log.Error(err, message) diff --git a/pkg/controller/certificatesigningrequests/venafi/venafi_test.go b/pkg/controller/certificatesigningrequests/venafi/venafi_test.go index fb992beadb3..9fb1e278fae 100644 --- a/pkg/controller/certificatesigningrequests/venafi/venafi_test.go +++ b/pkg/controller/certificatesigningrequests/venafi/venafi_test.go @@ -17,14 +17,13 @@ limitations under the License. package venafi import ( - "context" "crypto/x509" "errors" "fmt" "testing" "time" - "github.com/Venafi/vcert/v4/pkg/endpoint" + "github.com/Venafi/vcert/v5/pkg/endpoint" "github.com/go-logr/logr" authzv1 "k8s.io/api/authorization/v1" certificatesv1 "k8s.io/api/certificates/v1" @@ -33,15 +32,16 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" - corelisters "k8s.io/client-go/listers/core/v1" + "k8s.io/apimachinery/pkg/types" coretesting "k8s.io/client-go/testing" fakeclock "k8s.io/utils/clock/testing" + internalinformers "github.com/cert-manager/cert-manager/internal/informers" apiutil "github.com/cert-manager/cert-manager/pkg/api/util" "github.com/cert-manager/cert-manager/pkg/apis/certmanager" cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" - "github.com/cert-manager/cert-manager/pkg/controller" + controllerpkg "github.com/cert-manager/cert-manager/pkg/controller" "github.com/cert-manager/cert-manager/pkg/controller/certificatesigningrequests" "github.com/cert-manager/cert-manager/pkg/controller/certificatesigningrequests/util" testpkg "github.com/cert-manager/cert-manager/pkg/controller/test" @@ -69,7 +69,12 @@ func TestProcessItem(t *testing.T) { t.Fatal(err) } - rootTmpl, err := pki.GenerateTemplateFromCSRPEM(rootCSRPEM, time.Hour, true) + rootTmpl, err := pki.CertificateTemplateFromCSRPEM( + rootCSRPEM, + pki.CertificateTemplateOverrideDuration(time.Hour), + pki.CertificateTemplateValidateAndOverrideBasicConstraints(true, nil), + pki.CertificateTemplateValidateAndOverrideKeyUsages(0, nil), + ) if err != nil { t.Fatal(err) } @@ -84,7 +89,12 @@ func TestProcessItem(t *testing.T) { if err != nil { t.Fatal(err) } - leafTmpl, err := pki.GenerateTemplateFromCSRPEM(leafCSRPEM, time.Hour, false) + leafTmpl, err := pki.CertificateTemplateFromCSRPEM( + leafCSRPEM, + pki.CertificateTemplateOverrideDuration(time.Hour), + pki.CertificateTemplateValidateAndOverrideBasicConstraints(false, nil), + pki.CertificateTemplateValidateAndOverrideKeyUsages(0, nil), + ) if err != nil { t.Fatal(err) } @@ -154,7 +164,7 @@ func TestProcessItem(t *testing.T) { Status: corev1.ConditionTrue, }), ), - clientBuilder: func(_ string, _ corelisters.SecretLister, _ cmapi.GenericIssuer, _ *metrics.Metrics, _ logr.Logger) (venaficlient.Interface, error) { + clientBuilder: func(_ string, _ internalinformers.SecretLister, _ cmapi.GenericIssuer, _ *metrics.Metrics, _ logr.Logger, _ string) (venaficlient.Interface, error) { return nil, apierrors.NewNotFound(schema.GroupResource{}, "test-secret") }, builder: &testpkg.Builder{ @@ -196,7 +206,7 @@ func TestProcessItem(t *testing.T) { Status: corev1.ConditionTrue, }), ), - clientBuilder: func(_ string, _ corelisters.SecretLister, _ cmapi.GenericIssuer, _ *metrics.Metrics, _ logr.Logger) (venaficlient.Interface, error) { + clientBuilder: func(_ string, _ internalinformers.SecretLister, _ cmapi.GenericIssuer, _ *metrics.Metrics, _ logr.Logger, _ string) (venaficlient.Interface, error) { return nil, errors.New("generic error") }, expectedErr: true, @@ -242,7 +252,7 @@ func TestProcessItem(t *testing.T) { Status: corev1.ConditionTrue, }), ), - clientBuilder: func(_ string, _ corelisters.SecretLister, _ cmapi.GenericIssuer, _ *metrics.Metrics, _ logr.Logger) (venaficlient.Interface, error) { + clientBuilder: func(_ string, _ internalinformers.SecretLister, _ cmapi.GenericIssuer, _ *metrics.Metrics, _ logr.Logger, _ string) (venaficlient.Interface, error) { return &fakevenaficlient.Venafi{}, nil }, builder: &testpkg.Builder{ @@ -302,7 +312,7 @@ func TestProcessItem(t *testing.T) { "an approved CSR where the requested duration annotations contains garbage data should mark as Failed": { csr: gen.CertificateSigningRequestFrom(baseCSR, gen.AddCertificateSigningRequestAnnotations(map[string]string{ - "venafi.experimental.cert-manager.io/custom-fields": `[ {"name": "field-name", "value": "vield value"}]`, + "venafi.experimental.cert-manager.io/custom-fields": `[ {"name": "field-name", "value": "field value"}]`, }), gen.SetCertificateSigningRequestDuration("garbage-duration"), gen.SetCertificateSigningRequestStatusCondition(certificatesv1.CertificateSigningRequestCondition{ @@ -310,7 +320,7 @@ func TestProcessItem(t *testing.T) { Status: corev1.ConditionTrue, }), ), - clientBuilder: func(_ string, _ corelisters.SecretLister, _ cmapi.GenericIssuer, _ *metrics.Metrics, _ logr.Logger) (venaficlient.Interface, error) { + clientBuilder: func(_ string, _ internalinformers.SecretLister, _ cmapi.GenericIssuer, _ *metrics.Metrics, _ logr.Logger, _ string) (venaficlient.Interface, error) { return &fakevenaficlient.Venafi{}, nil }, builder: &testpkg.Builder{ @@ -349,7 +359,7 @@ func TestProcessItem(t *testing.T) { gen.CertificateSigningRequestFrom(baseCSR.DeepCopy(), gen.SetCertificateSigningRequestDuration("garbage-duration"), gen.AddCertificateSigningRequestAnnotations(map[string]string{ - "venafi.experimental.cert-manager.io/custom-fields": `[ {"name": "field-name", "value": "vield value"}]`, + "venafi.experimental.cert-manager.io/custom-fields": `[ {"name": "field-name", "value": "field value"}]`, }), gen.SetCertificateSigningRequestStatusCondition(certificatesv1.CertificateSigningRequestCondition{ Type: certificatesv1.CertificateApproved, @@ -371,14 +381,14 @@ func TestProcessItem(t *testing.T) { "an approved CSR which does not yet have a pickup ID, but the client responds with fields type error, should mark as Failed": { csr: gen.CertificateSigningRequestFrom(baseCSR, gen.AddCertificateSigningRequestAnnotations(map[string]string{ - "venafi.experimental.cert-manager.io/custom-fields": `[ {"name": "field-name", "value": "vield value"}]`, + "venafi.experimental.cert-manager.io/custom-fields": `[ {"name": "field-name", "value": "field value"}]`, }), gen.SetCertificateSigningRequestStatusCondition(certificatesv1.CertificateSigningRequestCondition{ Type: certificatesv1.CertificateApproved, Status: corev1.ConditionTrue, }), ), - clientBuilder: func(_ string, _ corelisters.SecretLister, _ cmapi.GenericIssuer, _ *metrics.Metrics, _ logr.Logger) (venaficlient.Interface, error) { + clientBuilder: func(_ string, _ internalinformers.SecretLister, _ cmapi.GenericIssuer, _ *metrics.Metrics, _ logr.Logger, _ string) (venaficlient.Interface, error) { return &fakevenaficlient.Venafi{ RequestCertificateFn: func(_ []byte, _ time.Duration, _ []venafiapi.CustomField) (string, error) { return "", venaficlient.ErrCustomFieldsType{Type: "test-type"} @@ -420,7 +430,7 @@ func TestProcessItem(t *testing.T) { "", gen.CertificateSigningRequestFrom(baseCSR.DeepCopy(), gen.AddCertificateSigningRequestAnnotations(map[string]string{ - "venafi.experimental.cert-manager.io/custom-fields": `[ {"name": "field-name", "value": "vield value"}]`, + "venafi.experimental.cert-manager.io/custom-fields": `[ {"name": "field-name", "value": "field value"}]`, }), gen.SetCertificateSigningRequestStatusCondition(certificatesv1.CertificateSigningRequestCondition{ Type: certificatesv1.CertificateApproved, @@ -442,14 +452,14 @@ func TestProcessItem(t *testing.T) { "an approved CSR which does not yet have a pickup ID, but the client responds a generic error, should mark as Failed": { csr: gen.CertificateSigningRequestFrom(baseCSR, gen.AddCertificateSigningRequestAnnotations(map[string]string{ - "venafi.experimental.cert-manager.io/custom-fields": `[ {"name": "field-name", "value": "vield value"}]`, + "venafi.experimental.cert-manager.io/custom-fields": `[ {"name": "field-name", "value": "field value"}]`, }), gen.SetCertificateSigningRequestStatusCondition(certificatesv1.CertificateSigningRequestCondition{ Type: certificatesv1.CertificateApproved, Status: corev1.ConditionTrue, }), ), - clientBuilder: func(_ string, _ corelisters.SecretLister, _ cmapi.GenericIssuer, _ *metrics.Metrics, _ logr.Logger) (venaficlient.Interface, error) { + clientBuilder: func(_ string, _ internalinformers.SecretLister, _ cmapi.GenericIssuer, _ *metrics.Metrics, _ logr.Logger, _ string) (venaficlient.Interface, error) { return &fakevenaficlient.Venafi{ RequestCertificateFn: func(_ []byte, _ time.Duration, _ []venafiapi.CustomField) (string, error) { return "", errors.New("generic error") @@ -491,7 +501,7 @@ func TestProcessItem(t *testing.T) { "", gen.CertificateSigningRequestFrom(baseCSR.DeepCopy(), gen.AddCertificateSigningRequestAnnotations(map[string]string{ - "venafi.experimental.cert-manager.io/custom-fields": `[ {"name": "field-name", "value": "vield value"}]`, + "venafi.experimental.cert-manager.io/custom-fields": `[ {"name": "field-name", "value": "field value"}]`, }), gen.SetCertificateSigningRequestStatusCondition(certificatesv1.CertificateSigningRequestCondition{ Type: certificatesv1.CertificateApproved, @@ -513,14 +523,14 @@ func TestProcessItem(t *testing.T) { "an approved CSR which does not yet have a pickup ID, should update the annotation with one and return": { csr: gen.CertificateSigningRequestFrom(baseCSR, gen.AddCertificateSigningRequestAnnotations(map[string]string{ - "venafi.experimental.cert-manager.io/custom-fields": `[ {"name": "field-name", "value": "vield value"}]`, + "venafi.experimental.cert-manager.io/custom-fields": `[ {"name": "field-name", "value": "field value"}]`, }), gen.SetCertificateSigningRequestStatusCondition(certificatesv1.CertificateSigningRequestCondition{ Type: certificatesv1.CertificateApproved, Status: corev1.ConditionTrue, }), ), - clientBuilder: func(_ string, _ corelisters.SecretLister, _ cmapi.GenericIssuer, _ *metrics.Metrics, _ logr.Logger) (venaficlient.Interface, error) { + clientBuilder: func(_ string, _ internalinformers.SecretLister, _ cmapi.GenericIssuer, _ *metrics.Metrics, _ logr.Logger, _ string) (venaficlient.Interface, error) { return &fakevenaficlient.Venafi{ RequestCertificateFn: func(_ []byte, _ time.Duration, _ []venafiapi.CustomField) (string, error) { return "test-pickup-id", nil @@ -559,7 +569,7 @@ func TestProcessItem(t *testing.T) { "", gen.CertificateSigningRequestFrom(baseCSR.DeepCopy(), gen.AddCertificateSigningRequestAnnotations(map[string]string{ - "venafi.experimental.cert-manager.io/custom-fields": `[ {"name": "field-name", "value": "vield value"}]`, + "venafi.experimental.cert-manager.io/custom-fields": `[ {"name": "field-name", "value": "field value"}]`, "venafi.experimental.cert-manager.io/pickup-id": "test-pickup-id", }), gen.SetCertificateSigningRequestStatusCondition(certificatesv1.CertificateSigningRequestCondition{ @@ -574,7 +584,7 @@ func TestProcessItem(t *testing.T) { "an approved CSR which has a pickup ID, retrieve certificate returns a pending error, fire event and return error": { csr: gen.CertificateSigningRequestFrom(baseCSR, gen.AddCertificateSigningRequestAnnotations(map[string]string{ - "venafi.experimental.cert-manager.io/custom-fields": `[ {"name": "field-name", "value": "vield value"}]`, + "venafi.experimental.cert-manager.io/custom-fields": `[ {"name": "field-name", "value": "field value"}]`, "venafi.experimental.cert-manager.io/pickup-id": "test-pickup-id", }), gen.SetCertificateSigningRequestStatusCondition(certificatesv1.CertificateSigningRequestCondition{ @@ -582,7 +592,7 @@ func TestProcessItem(t *testing.T) { Status: corev1.ConditionTrue, }), ), - clientBuilder: func(_ string, _ corelisters.SecretLister, _ cmapi.GenericIssuer, _ *metrics.Metrics, _ logr.Logger) (venaficlient.Interface, error) { + clientBuilder: func(_ string, _ internalinformers.SecretLister, _ cmapi.GenericIssuer, _ *metrics.Metrics, _ logr.Logger, _ string) (venaficlient.Interface, error) { return &fakevenaficlient.Venafi{ RetrieveCertificateFn: func(_ string, _ []byte, _ time.Duration, _ []venafiapi.CustomField) ([]byte, error) { return nil, endpoint.ErrCertificatePending{} @@ -625,7 +635,7 @@ func TestProcessItem(t *testing.T) { "an approved CSR which has a pickup ID, retrieve certificate returns a timeout error, fire event and return error": { csr: gen.CertificateSigningRequestFrom(baseCSR, gen.AddCertificateSigningRequestAnnotations(map[string]string{ - "venafi.experimental.cert-manager.io/custom-fields": `[ {"name": "field-name", "value": "vield value"}]`, + "venafi.experimental.cert-manager.io/custom-fields": `[ {"name": "field-name", "value": "field value"}]`, "venafi.experimental.cert-manager.io/pickup-id": "test-pickup-id", }), gen.SetCertificateSigningRequestStatusCondition(certificatesv1.CertificateSigningRequestCondition{ @@ -633,7 +643,7 @@ func TestProcessItem(t *testing.T) { Status: corev1.ConditionTrue, }), ), - clientBuilder: func(_ string, _ corelisters.SecretLister, _ cmapi.GenericIssuer, _ *metrics.Metrics, _ logr.Logger) (venaficlient.Interface, error) { + clientBuilder: func(_ string, _ internalinformers.SecretLister, _ cmapi.GenericIssuer, _ *metrics.Metrics, _ logr.Logger, _ string) (venaficlient.Interface, error) { return &fakevenaficlient.Venafi{ RetrieveCertificateFn: func(_ string, _ []byte, _ time.Duration, _ []venafiapi.CustomField) ([]byte, error) { return nil, endpoint.ErrRetrieveCertificateTimeout{} @@ -676,7 +686,7 @@ func TestProcessItem(t *testing.T) { "an approved CSR which has a pickup ID, retrieve certificate returns a generic error, fire event and return error": { csr: gen.CertificateSigningRequestFrom(baseCSR, gen.AddCertificateSigningRequestAnnotations(map[string]string{ - "venafi.experimental.cert-manager.io/custom-fields": `[ {"name": "field-name", "value": "vield value"}]`, + "venafi.experimental.cert-manager.io/custom-fields": `[ {"name": "field-name", "value": "field value"}]`, "venafi.experimental.cert-manager.io/pickup-id": "test-pickup-id", }), gen.SetCertificateSigningRequestStatusCondition(certificatesv1.CertificateSigningRequestCondition{ @@ -684,7 +694,7 @@ func TestProcessItem(t *testing.T) { Status: corev1.ConditionTrue, }), ), - clientBuilder: func(_ string, _ corelisters.SecretLister, _ cmapi.GenericIssuer, _ *metrics.Metrics, _ logr.Logger) (venaficlient.Interface, error) { + clientBuilder: func(_ string, _ internalinformers.SecretLister, _ cmapi.GenericIssuer, _ *metrics.Metrics, _ logr.Logger, _ string) (venaficlient.Interface, error) { return &fakevenaficlient.Venafi{ RetrieveCertificateFn: func(_ string, _ []byte, _ time.Duration, _ []venafiapi.CustomField) ([]byte, error) { return nil, errors.New("generic error") @@ -727,7 +737,7 @@ func TestProcessItem(t *testing.T) { "an approved CSR which has a pickup ID, retrieve certificate returns garbage certificates, should mark as Failed": { csr: gen.CertificateSigningRequestFrom(baseCSR, gen.AddCertificateSigningRequestAnnotations(map[string]string{ - "venafi.experimental.cert-manager.io/custom-fields": `[ {"name": "field-name", "value": "vield value"}]`, + "venafi.experimental.cert-manager.io/custom-fields": `[ {"name": "field-name", "value": "field value"}]`, "venafi.experimental.cert-manager.io/pickup-id": "test-pickup-id", }), gen.SetCertificateSigningRequestStatusCondition(certificatesv1.CertificateSigningRequestCondition{ @@ -735,7 +745,7 @@ func TestProcessItem(t *testing.T) { Status: corev1.ConditionTrue, }), ), - clientBuilder: func(_ string, _ corelisters.SecretLister, _ cmapi.GenericIssuer, _ *metrics.Metrics, _ logr.Logger) (venaficlient.Interface, error) { + clientBuilder: func(_ string, _ internalinformers.SecretLister, _ cmapi.GenericIssuer, _ *metrics.Metrics, _ logr.Logger, _ string) (venaficlient.Interface, error) { return &fakevenaficlient.Venafi{ RetrieveCertificateFn: func(_ string, _ []byte, _ time.Duration, _ []venafiapi.CustomField) ([]byte, error) { return []byte("garbage"), nil @@ -745,7 +755,7 @@ func TestProcessItem(t *testing.T) { builder: &testpkg.Builder{ CertManagerObjects: []runtime.Object{baseIssuer.DeepCopy()}, ExpectedEvents: []string{ - "Warning ErrorParse Failed to parse returned certificate bundle: error decoding certificate PEM block", + "Warning ErrorParse Failed to parse returned certificate bundle: error decoding certificate PEM block: no valid certificates found", }, ExpectedActions: []testpkg.Action{ testpkg.NewAction(coretesting.NewCreateAction( @@ -777,7 +787,7 @@ func TestProcessItem(t *testing.T) { "", gen.CertificateSigningRequestFrom(baseCSR.DeepCopy(), gen.AddCertificateSigningRequestAnnotations(map[string]string{ - "venafi.experimental.cert-manager.io/custom-fields": `[ {"name": "field-name", "value": "vield value"}]`, + "venafi.experimental.cert-manager.io/custom-fields": `[ {"name": "field-name", "value": "field value"}]`, "venafi.experimental.cert-manager.io/pickup-id": "test-pickup-id", }), gen.SetCertificateSigningRequestStatusCondition(certificatesv1.CertificateSigningRequestCondition{ @@ -788,7 +798,7 @@ func TestProcessItem(t *testing.T) { Type: certificatesv1.CertificateFailed, Status: corev1.ConditionTrue, Reason: "ErrorParse", - Message: "Failed to parse returned certificate bundle: error decoding certificate PEM block", + Message: "Failed to parse returned certificate bundle: error decoding certificate PEM block: no valid certificates found", LastTransitionTime: metaFixedClockStart, LastUpdateTime: metaFixedClockStart, }), @@ -800,7 +810,7 @@ func TestProcessItem(t *testing.T) { "an approved CSR which has a pickup ID, retrieve certificate returns a CA and certificate should update with certificate": { csr: gen.CertificateSigningRequestFrom(baseCSR, gen.AddCertificateSigningRequestAnnotations(map[string]string{ - "venafi.experimental.cert-manager.io/custom-fields": `[ {"name": "field-name", "value": "vield value"}]`, + "venafi.experimental.cert-manager.io/custom-fields": `[ {"name": "field-name", "value": "field value"}]`, "venafi.experimental.cert-manager.io/pickup-id": "test-pickup-id", }), gen.SetCertificateSigningRequestStatusCondition(certificatesv1.CertificateSigningRequestCondition{ @@ -808,7 +818,7 @@ func TestProcessItem(t *testing.T) { Status: corev1.ConditionTrue, }), ), - clientBuilder: func(_ string, _ corelisters.SecretLister, _ cmapi.GenericIssuer, _ *metrics.Metrics, _ logr.Logger) (venaficlient.Interface, error) { + clientBuilder: func(_ string, _ internalinformers.SecretLister, _ cmapi.GenericIssuer, _ *metrics.Metrics, _ logr.Logger, _ string) (venaficlient.Interface, error) { return &fakevenaficlient.Venafi{ RetrieveCertificateFn: func(_ string, _ []byte, _ time.Duration, _ []venafiapi.CustomField) ([]byte, error) { return []byte(fmt.Sprintf("%s%s", certBundle.ChainPEM, certBundle.CAPEM)), nil @@ -850,7 +860,7 @@ func TestProcessItem(t *testing.T) { "", gen.CertificateSigningRequestFrom(baseCSR.DeepCopy(), gen.AddCertificateSigningRequestAnnotations(map[string]string{ - "venafi.experimental.cert-manager.io/custom-fields": `[ {"name": "field-name", "value": "vield value"}]`, + "venafi.experimental.cert-manager.io/custom-fields": `[ {"name": "field-name", "value": "field value"}]`, "venafi.experimental.cert-manager.io/pickup-id": "test-pickup-id", }), gen.SetCertificateSigningRequestStatusCondition(certificatesv1.CertificateSigningRequestCondition{ @@ -874,7 +884,7 @@ func TestProcessItem(t *testing.T) { fixedClock.SetTime(fixedClockStart) test.builder.Clock = fixedClock test.builder.T = t - test.builder.Init() + test.builder.InitWithRESTConfig() // Always return true for SubjectAccessReviews in tests test.builder.FakeKubeClient().PrependReactor("create", "*", func(action coretesting.Action) (bool, runtime.Object, error) { @@ -895,12 +905,16 @@ func TestProcessItem(t *testing.T) { controller := certificatesigningrequests.New( apiutil.IssuerVenafi, - func(*controller.Context) certificatesigningrequests.Signer { return venafi }, + func(*controllerpkg.Context) certificatesigningrequests.Signer { return venafi }, ) - controller.Register(test.builder.Context) + if _, _, err := controller.Register(test.builder.Context); err != nil { + t.Fatal(err) + } test.builder.Start() - err := controller.ProcessItem(context.Background(), test.csr.Name) + err := controller.ProcessItem(t.Context(), types.NamespacedName{ + Name: test.csr.Name, + }) if err != nil && !test.expectedErr { t.Errorf("expected to not get an error, but got: %v", err) } diff --git a/pkg/controller/clusterissuers/checks.go b/pkg/controller/clusterissuers/checks.go index 705b2b7ae8b..092b036c384 100644 --- a/pkg/controller/clusterissuers/checks.go +++ b/pkg/controller/clusterissuers/checks.go @@ -19,16 +19,17 @@ package clusterissuers import ( "fmt" - v1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/labels" + + v1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" ) func (c *controller) issuersForSecret(secret *corev1.Secret) ([]*v1.ClusterIssuer, error) { issuers, err := c.clusterIssuerLister.List(labels.NewSelector()) if err != nil { - return nil, fmt.Errorf("error listing certificates: %s", err.Error()) + return nil, fmt.Errorf("error listing issuers: %s", err.Error()) } var affected []*v1.ClusterIssuer @@ -59,6 +60,12 @@ func (c *controller) issuersForSecret(secret *corev1.Secret) ([]*v1.ClusterIssue affected = append(affected, iss) continue } + if iss.Spec.Venafi.TPP.CABundleSecretRef != nil { + if iss.Spec.Venafi.TPP.CABundleSecretRef.Name == secret.Name { + affected = append(affected, iss) + continue + } + } } if iss.Spec.Venafi.Cloud != nil { if iss.Spec.Venafi.Cloud.APITokenSecretRef.Name == secret.Name { diff --git a/pkg/controller/clusterissuers/controller.go b/pkg/controller/clusterissuers/controller.go index a5fb655b1ba..89a1784021b 100644 --- a/pkg/controller/clusterissuers/controller.go +++ b/pkg/controller/clusterissuers/controller.go @@ -18,15 +18,16 @@ package clusterissuers import ( "context" + "fmt" "github.com/go-logr/logr" - corev1 "k8s.io/api/core/v1" k8sErrors "k8s.io/apimachinery/pkg/api/errors" - corelisters "k8s.io/client-go/listers/core/v1" + "k8s.io/apimachinery/pkg/types" "k8s.io/client-go/tools/cache" "k8s.io/client-go/tools/record" "k8s.io/client-go/util/workqueue" + internalinformers "github.com/cert-manager/cert-manager/internal/informers" cmclient "github.com/cert-manager/cert-manager/pkg/client/clientset/versioned" cmlisters "github.com/cert-manager/cert-manager/pkg/client/listers/certmanager/v1" controllerpkg "github.com/cert-manager/cert-manager/pkg/controller" @@ -36,11 +37,11 @@ import ( type controller struct { clusterIssuerLister cmlisters.ClusterIssuerLister - secretLister corelisters.SecretLister + secretLister internalinformers.SecretLister // maintain a reference to the workqueue for this controller // so the handleOwnedResource method can enqueue resources - queue workqueue.RateLimitingInterface + queue workqueue.TypedRateLimitingInterface[types.NamespacedName] // logger to be used by this controller log logr.Logger @@ -56,7 +57,7 @@ type controller struct { issuerFactory issuer.Factory // clusterResourceNamespace is the namespace used to store resources - // referenced by ClusterIssuer resources, e.g. acme account secrets + // referenced by ClusterIssuer resources, e.g., acme account secrets clusterResourceNamespace string // fieldManager is the manager name used for the Apply operations. @@ -66,16 +67,21 @@ type controller struct { // Register registers and constructs the controller using the provided context. // It returns the workqueue to be used to enqueue items, a list of // InformerSynced functions that must be synced, or an error. -func (c *controller) Register(ctx *controllerpkg.Context) (workqueue.RateLimitingInterface, []cache.InformerSynced, error) { +func (c *controller) Register(ctx *controllerpkg.Context) (workqueue.TypedRateLimitingInterface[types.NamespacedName], []cache.InformerSynced, error) { // construct a new named logger to be reused throughout the controller c.log = logf.FromContext(ctx.RootContext, ControllerName) // create a queue used to queue up items to be processed - c.queue = workqueue.NewNamedRateLimitingQueue(controllerpkg.DefaultItemBasedRateLimiter(), ControllerName) + c.queue = workqueue.NewTypedRateLimitingQueueWithConfig( + controllerpkg.DefaultItemBasedRateLimiter(), + workqueue.TypedRateLimitingQueueConfig[types.NamespacedName]{ + Name: ControllerName, + }, + ) // obtain references to all the informers used by this controller clusterIssuerInformer := ctx.SharedInformerFactory.Certmanager().V1().ClusterIssuers() - secretInformer := ctx.KubeSharedInformerFactory.Core().V1().Secrets() + secretInformer := ctx.KubeSharedInformerFactory.Secrets() // build a list of InformerSynced functions that will be returned by the Register method. // the controller will only begin processing items once all of these informers have synced. mustSync := []cache.InformerSynced{ @@ -88,8 +94,12 @@ func (c *controller) Register(ctx *controllerpkg.Context) (workqueue.RateLimitin c.secretLister = secretInformer.Lister() // register handler functions - clusterIssuerInformer.Informer().AddEventHandler(&controllerpkg.QueuingEventHandler{Queue: c.queue}) - secretInformer.Informer().AddEventHandler(&controllerpkg.BlockingEventHandler{WorkFunc: c.secretDeleted}) + if _, err := clusterIssuerInformer.Informer().AddEventHandler(&controllerpkg.QueuingEventHandler{Queue: c.queue}); err != nil { + return nil, nil, fmt.Errorf("error setting up event handler: %v", err) + } + if _, err := secretInformer.Informer().AddEventHandler(&controllerpkg.BlockingEventHandler{WorkFunc: c.secretEvent}); err != nil { + return nil, nil, fmt.Errorf("error setting up event handler: %v", err) + } // instantiate additional helpers used by this controller c.issuerFactory = issuer.NewFactory(ctx) @@ -102,14 +112,12 @@ func (c *controller) Register(ctx *controllerpkg.Context) (workqueue.RateLimitin } // TODO: replace with generic handleObject function (like Navigator) -func (c *controller) secretDeleted(obj interface{}) { - log := c.log.WithName("secretDeleted") +func (c *controller) secretEvent(obj interface{}) { + log := c.log.WithName("secretEvent") - var secret *corev1.Secret - var ok bool - secret, ok = obj.(*corev1.Secret) + secret, ok := controllerpkg.ToSecret(obj) if !ok { - log.Error(nil, "object was not a Secret object") + log.Error(nil, "object is not a secret", "object", obj) return } log = logf.WithResource(log, secret) @@ -120,41 +128,31 @@ func (c *controller) secretDeleted(obj interface{}) { return } for _, iss := range issuers { - log := logf.WithRelatedResource(log, iss) - key, err := keyFunc(iss) - if err != nil { - log.Error(err, "error computing key for resource") - continue - } - c.queue.AddRateLimited(key) + c.queue.Add(types.NamespacedName{ + Name: iss.Name, + Namespace: iss.Namespace, + }) } } -func (c *controller) ProcessItem(ctx context.Context, key string) error { +func (c *controller) ProcessItem(ctx context.Context, key types.NamespacedName) error { log := logf.FromContext(ctx) - _, name, err := cache.SplitMetaNamespaceKey(key) - if err != nil { - log.Error(nil, "invalid resource key") - return nil - } + name := key.Name issuer, err := c.clusterIssuerLister.Get(name) - if err != nil { - if k8sErrors.IsNotFound(err) { - log.Error(err, "clusterissuer in work queue no longer exists") - return nil - } - + if err != nil && !k8sErrors.IsNotFound(err) { return err } + if issuer == nil || issuer.DeletionTimestamp != nil { + // If the ClusterIssuer object was/ is being deleted, we don't want to update its status. + return nil + } ctx = logf.NewContext(ctx, logf.WithResource(log, issuer)) return c.Sync(ctx, issuer) } -var keyFunc = controllerpkg.KeyFunc - const ( // ControllerName is the name of the ClusterIssuers controller. ControllerName = "clusterissuers" diff --git a/pkg/controller/clusterissuers/sync.go b/pkg/controller/clusterissuers/sync.go index 9b09c9e40e5..05578d398ad 100644 --- a/pkg/controller/clusterissuers/sync.go +++ b/pkg/controller/clusterissuers/sync.go @@ -56,7 +56,7 @@ func (c *controller) Sync(ctx context.Context, iss *cmapi.ClusterIssuer) (err er return err } - err = i.Setup(ctx) + err = i.Setup(ctx, issuerCopy) if err != nil { s := messageErrorInitIssuer + err.Error() log.Error(err, "error setting up issuer") @@ -67,14 +67,14 @@ func (c *controller) Sync(ctx context.Context, iss *cmapi.ClusterIssuer) (err er return nil } -func (c *controller) updateIssuerStatus(ctx context.Context, old, new *cmapi.ClusterIssuer) error { - if apiequality.Semantic.DeepEqual(old.Status, new.Status) { +func (c *controller) updateIssuerStatus(ctx context.Context, oldIssuer, newIssuer *cmapi.ClusterIssuer) error { + if apiequality.Semantic.DeepEqual(oldIssuer.Status, newIssuer.Status) { return nil } if utilfeature.DefaultFeatureGate.Enabled(feature.ServerSideApply) { - return internalissuers.ApplyClusterIssuerStatus(ctx, c.cmClient, c.fieldManager, new) + return internalissuers.ApplyClusterIssuerStatus(ctx, c.cmClient, c.fieldManager, newIssuer) } else { - _, err := c.cmClient.CertmanagerV1().ClusterIssuers().UpdateStatus(ctx, new, metav1.UpdateOptions{}) + _, err := c.cmClient.CertmanagerV1().ClusterIssuers().UpdateStatus(ctx, newIssuer, metav1.UpdateOptions{}) return err } } diff --git a/pkg/controller/clusterissuers/sync_test.go b/pkg/controller/clusterissuers/sync_test.go index 4304113ad17..50711242316 100644 --- a/pkg/controller/clusterissuers/sync_test.go +++ b/pkg/controller/clusterissuers/sync_test.go @@ -17,7 +17,6 @@ limitations under the License. package clusterissuers import ( - "context" "reflect" "runtime/debug" "testing" @@ -63,7 +62,7 @@ func TestUpdateIssuerStatus(t *testing.T) { originalIssuer := newFakeIssuerWithStatus("test", v1.IssuerStatus{}) - issuer, err := fakeClient.CertmanagerV1().ClusterIssuers().Create(context.TODO(), originalIssuer, metav1.CreateOptions{}) + issuer, err := fakeClient.CertmanagerV1().ClusterIssuers().Create(t.Context(), originalIssuer, metav1.CreateOptions{}) assertErrIsNil(t, fatalf, err) assertNumberOfActions(t, fatalf, filter(fakeClient.Actions()), 1) @@ -79,7 +78,7 @@ func TestUpdateIssuerStatus(t *testing.T) { issuerCopy := issuer.DeepCopy() issuerCopy.Status = newStatus - err = c.updateIssuerStatus(context.TODO(), issuer, issuerCopy) + err = c.updateIssuerStatus(t.Context(), issuer, issuerCopy) assertErrIsNil(t, fatalf, err) actions := filter(fakeClient.Actions()) diff --git a/pkg/controller/configfile/configfile.go b/pkg/controller/configfile/configfile.go new file mode 100644 index 00000000000..9999f7275c6 --- /dev/null +++ b/pkg/controller/configfile/configfile.go @@ -0,0 +1,85 @@ +/* +Copyright 2021 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. +*/ + +package configfile + +import ( + "fmt" + + "k8s.io/apimachinery/pkg/runtime/serializer" + + config "github.com/cert-manager/cert-manager/internal/apis/config/controller" + "github.com/cert-manager/cert-manager/internal/apis/config/controller/scheme" +) + +type ControllerConfigFile struct { + Config *config.ControllerConfiguration +} + +func New() *ControllerConfigFile { + return &ControllerConfigFile{ + Config: &config.ControllerConfiguration{}, + } +} + +func decodeConfiguration(data []byte) (*config.ControllerConfiguration, error) { + _, codec, err := scheme.NewSchemeAndCodecs(serializer.EnableStrict) + if err != nil { + return nil, err + } + + obj, _, err := codec.UniversalDecoder().Decode(data, nil, nil) + if err != nil { + return nil, fmt.Errorf("failed to decode: %w", err) + } + + c, ok := obj.(*config.ControllerConfiguration) + if !ok { + return nil, fmt.Errorf("failed to cast object to ControllerConfiguration, unexpected type") + } + + return c, nil + +} + +func (cfg *ControllerConfigFile) DecodeAndConfigure(data []byte) error { + config, err := decodeConfiguration(data) + if err != nil { + return err + } + cfg.Config = config + + return nil +} + +func (cfg *ControllerConfigFile) GetPathRefs() ([]*string, error) { + paths, err := ControllerConfigurationPathRefs(cfg.Config) + if err != nil { + return nil, err + } + return paths, err + +} + +// ControllerConfigurationPathRefs returns pointers to all the ControllerConfiguration fields that contain filepaths. +// You might use this, for example, to resolve all relative paths against some common root before +// passing the configuration to the application. This method must be kept up to date as new fields are added. +func ControllerConfigurationPathRefs(cfg *config.ControllerConfiguration) ([]*string, error) { + + return []*string{ + &cfg.KubeConfig, + }, nil +} diff --git a/pkg/controller/configfile/configfile_test.go b/pkg/controller/configfile/configfile_test.go new file mode 100644 index 00000000000..400818cdd89 --- /dev/null +++ b/pkg/controller/configfile/configfile_test.go @@ -0,0 +1,54 @@ +/* +Copyright 2021 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. +*/ + +package configfile + +import ( + "fmt" + "testing" + + "github.com/cert-manager/cert-manager/pkg/util/configfile" +) + +func TestFSLoader_Load(t *testing.T) { + const expectedFilename = "/path/to/config/file" + const kubeConfigPath = "path/to/kubeconfig/file" + + controllerConfig := New() + + loader, err := configfile.NewConfigurationFSLoader(func(filename string) ([]byte, error) { + if filename != expectedFilename { + t.Fatalf("unexpected filename %q passed to ReadFile", filename) + return nil, fmt.Errorf("unexpected filename %q", filename) + } + return []byte(fmt.Sprintf(`apiVersion: controller.config.cert-manager.io/v1alpha1 +kind: ControllerConfiguration +kubeConfig: %s`, kubeConfigPath)), nil + }, expectedFilename) + if err != nil { + t.Fatal(err) + } + + if err := loader.Load(controllerConfig); err != nil { + t.Fatal(err) + } + + // the config loader will force paths to be 'absolute' if they are provided as relative. + absKubeConfigPath := "/path/to/config/path/to/kubeconfig/file" + if controllerConfig.Config.KubeConfig != absKubeConfigPath { + t.Errorf("expected kubeConfig to be set to %q but got %q", absKubeConfigPath, controllerConfig.Config.KubeConfig) + } +} diff --git a/pkg/controller/context.go b/pkg/controller/context.go index 4767ab201b1..4b410b7b367 100644 --- a/pkg/controller/context.go +++ b/pkg/controller/context.go @@ -26,23 +26,31 @@ import ( corev1 "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/resource" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/selection" + utilruntime "k8s.io/apimachinery/pkg/util/runtime" "k8s.io/client-go/discovery" - kubeinformers "k8s.io/client-go/informers" "k8s.io/client-go/kubernetes" - "k8s.io/client-go/kubernetes/scheme" + kscheme "k8s.io/client-go/kubernetes/scheme" clientv1 "k8s.io/client-go/kubernetes/typed/core/v1" + "k8s.io/client-go/metadata" + "k8s.io/client-go/metadata/metadatainformer" "k8s.io/client-go/rest" - "k8s.io/client-go/tools/clientcmd" "k8s.io/client-go/tools/record" "k8s.io/client-go/util/flowcontrol" "k8s.io/utils/clock" - gwapi "sigs.k8s.io/gateway-api/apis/v1alpha2" + gwapi "sigs.k8s.io/gateway-api/apis/v1" gwclient "sigs.k8s.io/gateway-api/pkg/client/clientset/versioned" gwscheme "sigs.k8s.io/gateway-api/pkg/client/clientset/versioned/scheme" gwinformers "sigs.k8s.io/gateway-api/pkg/client/informers/externalversions" "github.com/cert-manager/cert-manager/internal/controller/feature" + internalinformers "github.com/cert-manager/cert-manager/internal/informers" + "github.com/cert-manager/cert-manager/internal/kube" "github.com/cert-manager/cert-manager/pkg/acme/accounts" + cmacme "github.com/cert-manager/cert-manager/pkg/apis/acme/v1" clientset "github.com/cert-manager/cert-manager/pkg/client/clientset/versioned" cmscheme "github.com/cert-manager/cert-manager/pkg/client/clientset/versioned/scheme" informers "github.com/cert-manager/cert-manager/pkg/client/informers/externalversions" @@ -68,36 +76,53 @@ type Context struct { // RootContext is the root context for the controller RootContext context.Context - // StopCh is a channel that will be closed when the controller is signalled - // to exit - StopCh <-chan struct{} - // FieldManager is the string that should be used as the field manager when // applying API object. This value is derived from the user agent. FieldManager string // RESTConfig is the loaded Kubernetes apiserver rest client configuration RESTConfig *rest.Config + // Scheme is the Kubernetes scheme that should be used when serialising and + // deserialising API objects + Scheme *runtime.Scheme // Client is a Kubernetes clientset Client kubernetes.Interface // CMClient is a cert-manager clientset CMClient clientset.Interface // GWClient is a GatewayAPI clientset. GWClient gwclient.Interface + // MetadataClient is a PartialObjectMetadata client + MetadataClient metadata.Interface // DiscoveryClient is a discovery interface. Usually set to Client.Discovery unless a fake client is in use. DiscoveryClient discovery.DiscoveryInterface + // Clock should be used to access the current time instead of relying on + // time.Now, to make it easier to test controllers that utilise time + Clock clock.Clock + + // ACMEAccountRegistry is used as a cache of ACME accounts between various + // components of cert-manager + ACMEAccountRegistry accounts.Registry + + // Metrics is used for exposing Prometheus metrics across the controllers + Metrics *metrics.Metrics + // Recorder to record events to Recorder record.EventRecorder // KubeSharedInformerFactory can be used to obtain shared // SharedIndexInformer instances for Kubernetes types - KubeSharedInformerFactory kubeinformers.SharedInformerFactory + KubeSharedInformerFactory internalinformers.KubeInformerFactory + // SharedInformerFactory can be used to obtain shared SharedIndexInformer - // instances + // instances for cert-manager.io types SharedInformerFactory informers.SharedInformerFactory - // The Gateway API is an external CRD, which means its shared informers are - // not available in controllerpkg.Context. + // HTTP01ResourceMetadataInformersFactory is a metadata only informers + // factory with a http-01 resource label filter selector + HTTP01ResourceMetadataInformersFactory metadatainformer.SharedInformerFactory + + // GWShared can be used to obtain SharedIndexInformer instances for + // gateway.networking.k8s.io types GWShared gwinformers.SharedInformerFactory GatewaySolverEnabled bool @@ -124,23 +149,22 @@ type ContextOptions struct { // If unset, operates on all namespaces Namespace string - // Clock should be used to access the current time instead of relying on - // time.Now, to make it easier to test controllers that utilise time - Clock clock.Clock - - // Metrics is used for exposing Prometheus metrics across the controllers - Metrics *metrics.Metrics - IssuerOptions ACMEOptions IngressShimOptions CertificateOptions SchedulerOptions + ConfigOptions +} + +type ConfigOptions struct { + // EnableGatewayAPI indicates if the user has enabled GatewayAPI support. + EnableGatewayAPI bool } type IssuerOptions struct { // ClusterResourceNamespace is the namespace to store resources created by - // non-namespaced resources (e.g. ClusterIssuer) in. + // non-namespaced resources (e.g., ClusterIssuer) in. ClusterResourceNamespace string // ClusterIssuerAmbientCredentials controls whether a cluster issuer should @@ -170,6 +194,9 @@ type ACMEOptions struct { // HTTP01SolverResourceLimitsMemory defines the ACME pod's resource limits Memory size HTTP01SolverResourceLimitsMemory resource.Quantity + // ACMEHTTP01SolverRunAsNonRoot sets the ACME pod's ability to run as root + ACMEHTTP01SolverRunAsNonRoot bool + // HTTP01SolverNameservers is a list of nameservers to use when performing self-checks // for ACME HTTP01 validations. HTTP01SolverNameservers []string @@ -182,10 +209,6 @@ type ACMEOptions struct { // for ACME DNS01 validations. DNS01Nameservers []string - // AccountRegistry is used as a cache of ACME accounts between various - // components of cert-manager - AccountRegistry accounts.Registry - // DNS01CheckRetryPeriod is the time the controller should wait between checking if a ACME dns entry exists. DNS01CheckRetryPeriod time.Duration } @@ -198,6 +221,7 @@ type IngressShimOptions struct { DefaultIssuerKind string DefaultIssuerGroup string DefaultAutoCertificateAnnotations []string + ExtraCertificateAnnotations []string } type CertificateOptions struct { @@ -215,7 +239,7 @@ type SchedulerOptions struct { MaxConcurrentChallenges int } -// ContextFactory is used for constructing new Contexts who's clients have been +// ContextFactory is used for constructing new Contexts whose clients have been // configured with a User Agent built from the component name. type ContextFactory struct { // baseRestConfig is the base Kubernetes REST config that can authenticate to @@ -235,7 +259,7 @@ type ContextFactory struct { // corresponding QPS and Burst buckets. func NewContextFactory(ctx context.Context, opts ContextOptions) (*ContextFactory, error) { // Load the users Kubernetes config - restConfig, err := clientcmd.BuildConfigFromFlags(opts.APIServerHost, opts.Kubeconfig) + restConfig, err := kube.BuildClientConfig(opts.APIServerHost, opts.Kubeconfig) if err != nil { return nil, fmt.Errorf("error creating rest config: %w", err) } @@ -255,36 +279,71 @@ func NewContextFactory(ctx context.Context, opts ContextOptions) (*ContextFactor restConfig.RateLimiter = flowcontrol.NewTokenBucketRateLimiter(restConfig.QPS, restConfig.Burst) } - clients, err := buildClients(restConfig) + clients, err := buildClients(restConfig, opts) if err != nil { return nil, err } sharedInformerFactory := informers.NewSharedInformerFactoryWithOptions(clients.cmClient, resyncPeriod, informers.WithNamespace(opts.Namespace)) - kubeSharedInformerFactory := kubeinformers.NewSharedInformerFactoryWithOptions(clients.kubeClient, resyncPeriod, kubeinformers.WithNamespace(opts.Namespace)) + + var kubeSharedInformerFactory internalinformers.KubeInformerFactory + if utilfeature.DefaultFeatureGate.Enabled(feature.SecretsFilteredCaching) { + kubeSharedInformerFactory = internalinformers.NewFilteredSecretsKubeInformerFactory(ctx, clients.kubeClient, clients.metadataOnlyClient, resyncPeriod, opts.Namespace) + } else { + kubeSharedInformerFactory = internalinformers.NewBaseKubeInformerFactory(clients.kubeClient, resyncPeriod, opts.Namespace) + } + r, err := labels.NewRequirement(cmacme.DomainLabelKey, selection.Exists, nil) + if err != nil { + panic(fmt.Errorf("internal error: failed to build label selector to filter HTTP-01 challenge resources: %w", err)) + } + isHTTP01ChallengeResourceLabelSelector := labels.NewSelector().Add(*r) + http01ResourceMetadataInformerFactory := metadatainformer.NewFilteredSharedInformerFactory(clients.metadataOnlyClient, resyncPeriod, opts.Namespace, func(listOptions *metav1.ListOptions) { + // metadataInformersFactory is at the moment only used for pods + // and services for http-01 challenge which can be identified by + // the same label keys, so it is okay to set the label selector + // here. If we start using it for other resources then we'll + // have to set the selectors on individual informers instead. + listOptions.LabelSelector = isHTTP01ChallengeResourceLabelSelector.String() + + }) + gwSharedInformerFactory := gwinformers.NewSharedInformerFactoryWithOptions(clients.gwClient, resyncPeriod, gwinformers.WithNamespace(opts.Namespace)) + clock := clock.RealClock{} + log := logf.FromContext(ctx) + metrics := metrics.New(log, clock) + return &ContextFactory{ baseRestConfig: restConfig, - log: logf.FromContext(ctx), + log: log, ctx: &Context{ - RootContext: ctx, - StopCh: ctx.Done(), - KubeSharedInformerFactory: kubeSharedInformerFactory, - SharedInformerFactory: sharedInformerFactory, - GWShared: gwSharedInformerFactory, - GatewaySolverEnabled: clients.gatewayAvailable, - ContextOptions: opts, + RootContext: ctx, + KubeSharedInformerFactory: kubeSharedInformerFactory, + SharedInformerFactory: sharedInformerFactory, + GWShared: gwSharedInformerFactory, + GatewaySolverEnabled: clients.gatewayAvailable, + HTTP01ResourceMetadataInformersFactory: http01ResourceMetadataInformerFactory, + ContextOptions: opts, + Clock: clock, + Metrics: metrics, + ACMEAccountRegistry: accounts.NewDefaultRegistry( + accounts.NewClient(metrics, restConfig.UserAgent), + ), }, }, nil } -// Build builds a new controller Context who's clients have a User Agent +// Build builds a new controller Context whose clients have a User Agent // derived from the optional component name. func (c *ContextFactory) Build(component ...string) (*Context, error) { restConfig := util.RestConfigWithUserAgent(c.baseRestConfig, component...) - clients, err := buildClients(restConfig) + scheme := runtime.NewScheme() + utilruntime.Must(kscheme.AddToScheme(scheme)) + utilruntime.Must(cmscheme.AddToScheme(scheme)) + utilruntime.Must(gwscheme.AddToScheme(scheme)) + + clients, err := buildClients(restConfig, c.ctx.ContextOptions) if err != nil { return nil, err } @@ -292,20 +351,21 @@ func (c *ContextFactory) Build(component ...string) (*Context, error) { // Create event broadcaster. // Add cert-manager types to the default Kubernetes Scheme so Events can be // logged properly. - cmscheme.AddToScheme(scheme.Scheme) - gwscheme.AddToScheme(scheme.Scheme) + c.log.V(logf.DebugLevel).Info("creating event broadcaster") eventBroadcaster := record.NewBroadcaster() eventBroadcaster.StartLogging(logf.WithInfof(c.log.V(logf.DebugLevel)).Infof) eventBroadcaster.StartRecordingToSink(&clientv1.EventSinkImpl{Interface: clients.kubeClient.CoreV1().Events("")}) - recorder := eventBroadcaster.NewRecorder(scheme.Scheme, corev1.EventSource{Component: util.PrefixFromUserAgent(restConfig.UserAgent)}) + recorder := eventBroadcaster.NewRecorder(scheme, corev1.EventSource{Component: util.PrefixFromUserAgent(restConfig.UserAgent)}) ctx := *c.ctx ctx.FieldManager = util.PrefixFromUserAgent(restConfig.UserAgent) ctx.RESTConfig = restConfig + ctx.Scheme = scheme ctx.Client = clients.kubeClient ctx.CMClient = clients.cmClient ctx.GWClient = clients.gwClient + ctx.MetadataClient = clients.metadataOnlyClient ctx.DiscoveryClient = clients.kubeClient.Discovery() ctx.Recorder = recorder @@ -314,30 +374,42 @@ func (c *ContextFactory) Build(component ...string) (*Context, error) { // contextClients is a helper struct containing API clients. type contextClients struct { - kubeClient kubernetes.Interface - cmClient clientset.Interface - gwClient gwclient.Interface - gatewayAvailable bool + kubeClient kubernetes.Interface + cmClient clientset.Interface + gwClient gwclient.Interface + metadataOnlyClient metadata.Interface + gatewayAvailable bool } // buildClients builds all required clients for the context using the given // REST config. -func buildClients(restConfig *rest.Config) (contextClients, error) { +func buildClients(restConfig *rest.Config, opts ContextOptions) (contextClients, error) { + httpClient, err := rest.HTTPClientFor(restConfig) + if err != nil { + return contextClients{}, fmt.Errorf("error creating HTTP client: %w", err) + } + // Create a cert-manager api client - cmClient, err := clientset.NewForConfig(restConfig) + cmClient, err := clientset.NewForConfigAndClient(restConfig, httpClient) if err != nil { - return contextClients{}, fmt.Errorf("error creating internal group client: %w", err) + return contextClients{}, fmt.Errorf("error creating cert-manager client: %w", err) } // Create a Kubernetes api client - kubeClient, err := kubernetes.NewForConfig(restConfig) + kubeClient, err := kubernetes.NewForConfigAndClient(restConfig, httpClient) if err != nil { return contextClients{}, fmt.Errorf("error creating kubernetes client: %w", err) } + // create a metadata-only client + metadataOnlyClient, err := metadata.NewForConfigAndClient(restConfig, httpClient) + if err != nil { + return contextClients{}, fmt.Errorf("error creating metadata-only client: %w", err) + } + var gatewayAvailable bool // Check if the Gateway API feature gate was enabled - if utilfeature.DefaultFeatureGate.Enabled(feature.ExperimentalGatewayAPISupport) { + if utilfeature.DefaultFeatureGate.Enabled(feature.ExperimentalGatewayAPISupport) && opts.EnableGatewayAPI { // Check if the gateway API CRDs are available. If they are not found // return an error which will cause cert-manager to crashloopbackoff. d := kubeClient.Discovery() @@ -357,10 +429,10 @@ func buildClients(restConfig *rest.Config) (contextClients, error) { } // Create a GatewayAPI client. - gwClient, err := gwclient.NewForConfig(restConfig) + gwClient, err := gwclient.NewForConfigAndClient(restConfig, httpClient) if err != nil { return contextClients{}, fmt.Errorf("error creating kubernetes client: %w", err) } - return contextClients{kubeClient, cmClient, gwClient, gatewayAvailable}, nil + return contextClients{kubeClient, cmClient, gwClient, metadataOnlyClient, gatewayAvailable}, nil } diff --git a/pkg/controller/context_test.go b/pkg/controller/context_test.go index 4400ade9444..810f76e8601 100644 --- a/pkg/controller/context_test.go +++ b/pkg/controller/context_test.go @@ -17,14 +17,13 @@ limitations under the License. package controller import ( - "context" "testing" "github.com/stretchr/testify/assert" ) func Test_NewContextFactory(t *testing.T) { - ctxFactory, err := NewContextFactory(context.TODO(), ContextOptions{ + ctxFactory, err := NewContextFactory(t.Context(), ContextOptions{ APIServerHost: "localhost:8443", KubernetesAPIQPS: 10, KubernetesAPIBurst: 10, diff --git a/pkg/controller/controller.go b/pkg/controller/controller.go index 72e7a6dc9e0..b06423e4eef 100644 --- a/pkg/controller/controller.go +++ b/pkg/controller/controller.go @@ -23,6 +23,7 @@ import ( "sync" "time" + "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/wait" genericregistry "k8s.io/apiserver/pkg/registry/generic/registry" "k8s.io/client-go/tools/cache" @@ -40,21 +41,19 @@ type runDurationFunc struct { } type queueingController interface { - Register(*Context) (workqueue.RateLimitingInterface, []cache.InformerSynced, error) - ProcessItem(ctx context.Context, key string) error + Register(*Context) (workqueue.TypedRateLimitingInterface[types.NamespacedName], []cache.InformerSynced, error) + ProcessItem(ctx context.Context, key types.NamespacedName) error } func NewController( - ctx context.Context, name string, metrics *metrics.Metrics, - syncFunc func(ctx context.Context, key string) error, + syncFunc func(ctx context.Context, key types.NamespacedName) error, mustSync []cache.InformerSynced, runDurationFuncs []runDurationFunc, - queue workqueue.RateLimitingInterface, + queue workqueue.TypedRateLimitingInterface[types.NamespacedName], ) Interface { return &controller{ - ctx: ctx, name: name, metrics: metrics, syncHandler: syncFunc, @@ -65,15 +64,12 @@ func NewController( } type controller struct { - // ctx is the root golang context for the controller - ctx context.Context - // name is the name for this controller name string // the function that should be called when an item is popped // off the workqueue - syncHandler func(ctx context.Context, key string) error + syncHandler func(ctx context.Context, key types.NamespacedName) error // mustSync is a slice of informers that must have synced before // this controller can start @@ -87,26 +83,26 @@ type controller struct { // queue is a reference to the queue used to enqueue resources // to be processed - queue workqueue.RateLimitingInterface + queue workqueue.TypedRateLimitingInterface[types.NamespacedName] // metrics is used to expose Prometheus, shared by all controllers metrics *metrics.Metrics } // Run starts the controller loop -func (c *controller) Run(workers int, stopCh <-chan struct{}) error { - ctx, cancel := context.WithCancel(c.ctx) +func (c *controller) Run(workers int, ctx context.Context) error { + ctx, cancel := context.WithCancel(ctx) defer cancel() - log := logf.FromContext(ctx) + log := logf.FromContext(ctx, c.name) log.V(logf.DebugLevel).Info("starting control loop") // wait for all the informer caches we depend on are synced - if !cache.WaitForCacheSync(stopCh, c.mustSync...) { + if !cache.WaitForCacheSync(ctx.Done(), c.mustSync...) { return fmt.Errorf("error waiting for informer caches to sync") } var wg sync.WaitGroup - for i := 0; i < workers; i++ { + for range workers { wg.Add(1) go func() { defer wg.Done() @@ -119,11 +115,10 @@ func (c *controller) Run(workers int, stopCh <-chan struct{}) error { } for _, f := range c.runDurationFuncs { - f := f // capture range variable - go wait.Until(func() { f.fn(ctx) }, f.duration, stopCh) + go wait.Until(func() { f.fn(ctx) }, f.duration, ctx.Done()) } - <-stopCh + <-ctx.Done() log.V(logf.InfoLevel).Info("shutting down queue as workqueue signaled shutdown") c.queue.ShutDown() log.V(logf.DebugLevel).Info("waiting for workers to exit...") @@ -133,7 +128,7 @@ func (c *controller) Run(workers int, stopCh <-chan struct{}) error { } func (c *controller) worker(ctx context.Context) { - log := logf.FromContext(c.ctx) + log := logf.FromContext(ctx) log.V(logf.DebugLevel).Info("starting worker") for { @@ -142,21 +137,16 @@ func (c *controller) worker(ctx context.Context) { break } - var key string // use an inlined function so we can use defer func() { defer c.queue.Done(obj) - var ok bool - if key, ok = obj.(string); !ok { - return - } - log := log.WithValues("key", key) + log.V(logf.DebugLevel).Info("syncing item") // Increase sync count for this controller c.metrics.IncrementSyncCallCount(c.name) - err := c.syncHandler(ctx, key) + err := c.syncHandler(ctx, obj) if err != nil { if strings.Contains(err.Error(), genericregistry.OptimisticLockErrorMsg) { log.Info("re-queuing item due to optimistic locking on resource", "error", err.Error()) diff --git a/pkg/controller/helper.go b/pkg/controller/helper.go index 3a04445c898..c27e4fff596 100644 --- a/pkg/controller/helper.go +++ b/pkg/controller/helper.go @@ -18,6 +18,7 @@ package controller import ( cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" + cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" ) // ResourceNamespace returns the Kubernetes namespace where resources @@ -30,8 +31,23 @@ func (o IssuerOptions) ResourceNamespace(iss cmapi.GenericIssuer) string { return ns } +// ResourceNamespaceRef returns the Kubernetes namespace where resources +// created or read by the referenced issuer are located. +// This function is identical to ResourceNamespace, but takes a reference to +// the issuer instead of the issuer itself (which means we don't need to fetch the +// issuer from the API server). +func (o IssuerOptions) ResourceNamespaceRef(ref cmmeta.IssuerReference, challengeNamespace string) string { + switch ref.Kind { + case cmapi.ClusterIssuerKind: + return o.ClusterResourceNamespace + case "", cmapi.IssuerKind: + return challengeNamespace + } + return challengeNamespace // Should not be reached +} + // CanUseAmbientCredentials returns whether `iss` will attempt to configure itself -// from ambient credentials (e.g. from a cloud metadata service). +// from ambient credentials (e.g., from a cloud metadata service). func (o IssuerOptions) CanUseAmbientCredentials(iss cmapi.GenericIssuer) bool { switch iss.(type) { case *cmapi.ClusterIssuer: @@ -41,3 +57,18 @@ func (o IssuerOptions) CanUseAmbientCredentials(iss cmapi.GenericIssuer) bool { } return false } + +// CanUseAmbientCredentialsFromRef returns whether the referenced issuer will attempt +// to configure itself from ambient credentials (e.g., from a cloud metadata service). +// This function is identical to CanUseAmbientCredentials, but takes a reference to +// the issuer instead of the issuer itself (which means we don't need to fetch the +// issuer from the API server). +func (o IssuerOptions) CanUseAmbientCredentialsFromRef(ref cmmeta.IssuerReference) bool { + switch ref.Kind { + case cmapi.ClusterIssuerKind: + return o.ClusterIssuerAmbientCredentials + case "", cmapi.IssuerKind: + return o.IssuerAmbientCredentials + } + return false +} diff --git a/pkg/controller/issuers/checks.go b/pkg/controller/issuers/checks.go index 02c3dcf8126..8f03e658caf 100644 --- a/pkg/controller/issuers/checks.go +++ b/pkg/controller/issuers/checks.go @@ -19,16 +19,17 @@ package issuers import ( "fmt" - v1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/labels" + + v1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" ) func (c *controller) issuersForSecret(secret *corev1.Secret) ([]*v1.Issuer, error) { issuers, err := c.issuerLister.List(labels.NewSelector()) if err != nil { - return nil, fmt.Errorf("error listing certificates: %s", err.Error()) + return nil, fmt.Errorf("error listing issuers: %s", err.Error()) } var affected []*v1.Issuer @@ -61,6 +62,12 @@ func (c *controller) issuersForSecret(secret *corev1.Secret) ([]*v1.Issuer, erro affected = append(affected, iss) continue } + if iss.Spec.Venafi.TPP.CABundleSecretRef != nil { + if iss.Spec.Venafi.TPP.CABundleSecretRef.Name == secret.Name { + affected = append(affected, iss) + continue + } + } } if iss.Spec.Venafi.Cloud != nil { if iss.Spec.Venafi.Cloud.APITokenSecretRef.Name == secret.Name { diff --git a/pkg/controller/issuers/controller.go b/pkg/controller/issuers/controller.go index d2b6c2f5e3c..4a746fc4ff5 100644 --- a/pkg/controller/issuers/controller.go +++ b/pkg/controller/issuers/controller.go @@ -18,15 +18,16 @@ package issuers import ( "context" + "fmt" "github.com/go-logr/logr" - corev1 "k8s.io/api/core/v1" k8sErrors "k8s.io/apimachinery/pkg/api/errors" - corelisters "k8s.io/client-go/listers/core/v1" + "k8s.io/apimachinery/pkg/types" "k8s.io/client-go/tools/cache" "k8s.io/client-go/tools/record" "k8s.io/client-go/util/workqueue" + internalinformers "github.com/cert-manager/cert-manager/internal/informers" cmclient "github.com/cert-manager/cert-manager/pkg/client/clientset/versioned" cmlisters "github.com/cert-manager/cert-manager/pkg/client/listers/certmanager/v1" controllerpkg "github.com/cert-manager/cert-manager/pkg/controller" @@ -36,11 +37,11 @@ import ( type controller struct { issuerLister cmlisters.IssuerLister - secretLister corelisters.SecretLister + secretLister internalinformers.SecretLister // maintain a reference to the workqueue for this controller // so the handleOwnedResource method can enqueue resources - queue workqueue.RateLimitingInterface + queue workqueue.TypedRateLimitingInterface[types.NamespacedName] // logger to be used by this controller log logr.Logger @@ -62,16 +63,21 @@ type controller struct { // Register registers and constructs the controller using the provided context. // It returns the workqueue to be used to enqueue items, a list of // InformerSynced functions that must be synced, or an error. -func (c *controller) Register(ctx *controllerpkg.Context) (workqueue.RateLimitingInterface, []cache.InformerSynced, error) { +func (c *controller) Register(ctx *controllerpkg.Context) (workqueue.TypedRateLimitingInterface[types.NamespacedName], []cache.InformerSynced, error) { // construct a new named logger to be reused throughout the controller c.log = logf.FromContext(ctx.RootContext, ControllerName) // create a queue used to queue up items to be processed - c.queue = workqueue.NewNamedRateLimitingQueue(controllerpkg.DefaultItemBasedRateLimiter(), ControllerName) + c.queue = workqueue.NewTypedRateLimitingQueueWithConfig( + controllerpkg.DefaultItemBasedRateLimiter(), + workqueue.TypedRateLimitingQueueConfig[types.NamespacedName]{ + Name: ControllerName, + }, + ) // obtain references to all the informers used by this controller issuerInformer := ctx.SharedInformerFactory.Certmanager().V1().Issuers() - secretInformer := ctx.KubeSharedInformerFactory.Core().V1().Secrets() + secretInformer := ctx.KubeSharedInformerFactory.Secrets() // build a list of InformerSynced functions that will be returned by the Register method. // the controller will only begin processing items once all of these informers have synced. mustSync := []cache.InformerSynced{ @@ -84,8 +90,12 @@ func (c *controller) Register(ctx *controllerpkg.Context) (workqueue.RateLimitin c.secretLister = secretInformer.Lister() // register handler functions - issuerInformer.Informer().AddEventHandler(&controllerpkg.QueuingEventHandler{Queue: c.queue}) - secretInformer.Informer().AddEventHandler(&controllerpkg.BlockingEventHandler{WorkFunc: c.secretDeleted}) + if _, err := issuerInformer.Informer().AddEventHandler(&controllerpkg.QueuingEventHandler{Queue: c.queue}); err != nil { + return nil, nil, fmt.Errorf("error setting up event handler: %v", err) + } + if _, err := secretInformer.Informer().AddEventHandler(&controllerpkg.BlockingEventHandler{WorkFunc: c.secretEvent}); err != nil { + return nil, nil, fmt.Errorf("error setting up event handler: %v", err) + } // instantiate additional helpers used by this controller c.issuerFactory = issuer.NewFactory(ctx) @@ -97,16 +107,14 @@ func (c *controller) Register(ctx *controllerpkg.Context) (workqueue.RateLimitin } // TODO: replace with generic handleObject function (like Navigator) -func (c *controller) secretDeleted(obj interface{}) { - log := c.log.WithName("secretDeleted") - - var secret *corev1.Secret - var ok bool - secret, ok = obj.(*corev1.Secret) +func (c *controller) secretEvent(obj interface{}) { + log := c.log.WithName("secretEvent") + secret, ok := controllerpkg.ToSecret(obj) if !ok { - log.Error(nil, "object was not a secret object") + log.Error(nil, "object is not a secret", "object", obj) return } + log = logf.WithResource(log, secret) issuers, err := c.issuersForSecret(secret) if err != nil { @@ -114,39 +122,30 @@ func (c *controller) secretDeleted(obj interface{}) { return } for _, iss := range issuers { - key, err := keyFunc(iss) - if err != nil { - log.Error(err, "error computing key for resource") - continue - } - c.queue.AddRateLimited(key) + c.queue.Add(types.NamespacedName{ + Name: iss.Name, + Namespace: iss.Namespace, + }) } } -func (c *controller) ProcessItem(ctx context.Context, key string) error { +func (c *controller) ProcessItem(ctx context.Context, key types.NamespacedName) error { log := logf.FromContext(ctx) - namespace, name, err := cache.SplitMetaNamespaceKey(key) - if err != nil { - log.Error(err, "invalid resource key") - return nil - } + namespace, name := key.Namespace, key.Name issuer, err := c.issuerLister.Issuers(namespace).Get(name) - if err != nil { - if k8sErrors.IsNotFound(err) { - log.Error(err, "issuer in work queue no longer exists") - return nil - } - + if err != nil && !k8sErrors.IsNotFound(err) { return err } + if issuer == nil || issuer.DeletionTimestamp != nil { + // If the Issuer object was/ is being deleted, we don't want to update its status. + return nil + } ctx = logf.NewContext(ctx, logf.WithResource(log, issuer)) return c.Sync(ctx, issuer) } -var keyFunc = controllerpkg.KeyFunc - const ( ControllerName = "issuers" ) diff --git a/pkg/controller/issuers/sync.go b/pkg/controller/issuers/sync.go index 32f3a53283d..c8f79dcc063 100644 --- a/pkg/controller/issuers/sync.go +++ b/pkg/controller/issuers/sync.go @@ -56,7 +56,7 @@ func (c *controller) Sync(ctx context.Context, iss *cmapi.Issuer) (err error) { return err } - err = i.Setup(ctx) + err = i.Setup(ctx, issuerCopy) if err != nil { s := messageErrorInitIssuer + err.Error() log.V(logf.WarnLevel).Info(s) @@ -67,15 +67,15 @@ func (c *controller) Sync(ctx context.Context, iss *cmapi.Issuer) (err error) { return nil } -func (c *controller) updateIssuerStatus(ctx context.Context, old, new *cmapi.Issuer) error { - if apiequality.Semantic.DeepEqual(old.Status, new.Status) { +func (c *controller) updateIssuerStatus(ctx context.Context, oldIssuer, newIssuer *cmapi.Issuer) error { + if apiequality.Semantic.DeepEqual(oldIssuer.Status, newIssuer.Status) { return nil } if utilfeature.DefaultFeatureGate.Enabled(feature.ServerSideApply) { - return internalissuers.ApplyIssuerStatus(ctx, c.cmClient, c.fieldManager, new) + return internalissuers.ApplyIssuerStatus(ctx, c.cmClient, c.fieldManager, newIssuer) } else { - _, err := c.cmClient.CertmanagerV1().Issuers(new.Namespace).UpdateStatus(ctx, new, metav1.UpdateOptions{}) + _, err := c.cmClient.CertmanagerV1().Issuers(newIssuer.Namespace).UpdateStatus(ctx, newIssuer, metav1.UpdateOptions{}) return err } } diff --git a/pkg/controller/issuers/sync_test.go b/pkg/controller/issuers/sync_test.go index 00cc6c9a277..29f4f3b0840 100644 --- a/pkg/controller/issuers/sync_test.go +++ b/pkg/controller/issuers/sync_test.go @@ -17,7 +17,6 @@ limitations under the License. package issuers import ( - "context" "reflect" "runtime/debug" "testing" @@ -64,7 +63,7 @@ func TestUpdateIssuerStatus(t *testing.T) { originalIssuer := newFakeIssuerWithStatus("test", v1.IssuerStatus{}) - issuer, err := cmClient.CertmanagerV1().Issuers("testns").Create(context.TODO(), originalIssuer, metav1.CreateOptions{}) + issuer, err := cmClient.CertmanagerV1().Issuers("testns").Create(t.Context(), originalIssuer, metav1.CreateOptions{}) assertErrIsNil(t, fatalf, err) assertNumberOfActions(t, fatalf, filter(cmClient.Actions()), 1) @@ -80,7 +79,7 @@ func TestUpdateIssuerStatus(t *testing.T) { issuerCopy := issuer.DeepCopy() issuerCopy.Status = newStatus - err = c.updateIssuerStatus(context.TODO(), issuer, issuerCopy) + err = c.updateIssuerStatus(t.Context(), issuer, issuerCopy) assertErrIsNil(t, fatalf, err) actions := filter(cmClient.Actions()) diff --git a/pkg/controller/register.go b/pkg/controller/register.go index ccd6d6e0054..7a22d1feb77 100644 --- a/pkg/controller/register.go +++ b/pkg/controller/register.go @@ -16,6 +16,8 @@ limitations under the License. package controller +import "context" + // This file defines types for controllers to register themselves with the // controller package. @@ -26,7 +28,7 @@ type Interface interface { // run, and the workers should shut down upon a signal on stopCh. // This method should block until all workers have exited cleanly, thus // allowing for graceful shutdown of control loops. - Run(workers int, stopCh <-chan struct{}) error + Run(workers int, ctx context.Context) error } // Constructor is a function that creates a new control loop given a diff --git a/pkg/controller/test/actions.go b/pkg/controller/test/actions.go index 0e20efb50da..67cf175bae6 100644 --- a/pkg/controller/test/actions.go +++ b/pkg/controller/test/actions.go @@ -18,9 +18,8 @@ package test import ( "fmt" - "reflect" - "github.com/kr/pretty" + "github.com/google/go-cmp/cmp" coretesting "k8s.io/client-go/testing" ) @@ -79,19 +78,16 @@ func (a *action) Action() coretesting.Action { // Matches compares action.action with another Action. func (a *action) Matches(act coretesting.Action) error { - matches := reflect.DeepEqual(a.action, act) - if matches { - return nil + diff := cmp.Diff(a.action, act, + // We ignore differences in .ManagedFields since the expected object does not have them. + // FIXME: don't ignore this field + cmp.FilterPath(func(p cmp.Path) bool { + // FIXME: Must ignore managed fields as newer fake clients are tracking them + return p.Last().String() == ".ManagedFields" + }, cmp.Ignore()), + ) + if diff != "" { + return fmt.Errorf("unexpected difference between actions (-want +got):\n%s", diff) } - - objAct, ok := act.(coretesting.CreateAction) - if !ok { - return nil - } - objExp, ok := a.action.(coretesting.CreateAction) - if !ok { - return nil - } - - return fmt.Errorf("unexpected difference between actions: %s", pretty.Diff(objExp.GetObject(), objAct.GetObject())) + return nil } diff --git a/pkg/controller/test/context_builder.go b/pkg/controller/test/context_builder.go index 2ae2e7d09e7..714f1222945 100644 --- a/pkg/controller/test/context_builder.go +++ b/pkg/controller/test/context_builder.go @@ -20,7 +20,7 @@ import ( "context" "flag" "fmt" - "reflect" + "slices" "testing" "time" @@ -28,16 +28,19 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" utilerrors "k8s.io/apimachinery/pkg/util/errors" - kubeinformers "k8s.io/client-go/informers" + "k8s.io/apimachinery/pkg/util/rand" kubefake "k8s.io/client-go/kubernetes/fake" + metadatafake "k8s.io/client-go/metadata/fake" + "k8s.io/client-go/metadata/metadatainformer" "k8s.io/client-go/rest" coretesting "k8s.io/client-go/testing" - "k8s.io/client-go/tools/cache" "k8s.io/utils/clock" fakeclock "k8s.io/utils/clock/testing" + ctrl "sigs.k8s.io/controller-runtime" gwfake "sigs.k8s.io/gateway-api/pkg/client/clientset/versioned/fake" gwinformers "sigs.k8s.io/gateway-api/pkg/client/informers/externalversions" + internalinformers "github.com/cert-manager/cert-manager/internal/informers" apiutil "github.com/cert-manager/cert-manager/pkg/api/util" cmfake "github.com/cert-manager/cert-manager/pkg/client/clientset/versioned/fake" informers "github.com/cert-manager/cert-manager/pkg/client/informers/externalversions" @@ -49,11 +52,14 @@ import ( ) func init() { - logs.InitLogs(nil) - _ = flag.Set("alsologtostderr", fmt.Sprintf("%t", true)) - _ = flag.Lookup("v").Value.Set("4") + logs.InitLogs() + _ = flag.Set("alsologtostderr", "true") + _ = flag.Set("v", "4") + ctrl.SetLogger(logs.Log) } +type StringGenerator func(n int) string + // Builder is a structure used to construct new Contexts for use during tests. // Currently, only KubeObjects, CertManagerObjects and GWObjects can be // specified. These will be auto loaded into the constructed fake Clientsets. @@ -61,12 +67,13 @@ func init() { type Builder struct { T *testing.T - KubeObjects []runtime.Object - CertManagerObjects []runtime.Object - GWObjects []runtime.Object - ExpectedActions []Action - ExpectedEvents []string - StringGenerator StringGenerator + KubeObjects []runtime.Object + CertManagerObjects []runtime.Object + GWObjects []runtime.Object + PartialMetadataObjects []runtime.Object + ExpectedActions []Action + ExpectedEvents []string + StringGenerator StringGenerator // Clock will be the Clock set on the controller context. // If not specified, the RealClock will be used. @@ -80,9 +87,7 @@ type Builder struct { // test). CheckFn func(*Builder, ...interface{}) - stopCh chan struct{} - requiredReactors map[string]bool - additionalSyncFuncs []cache.InformerSynced + stopCh chan struct{} *controller.Context } @@ -97,23 +102,35 @@ func (b *Builder) generateNameReactor(action coretesting.Action) (handled bool, return false, obj.(runtime.Object), nil } +// informerResyncPeriod is the resync period used by the test informers. We +// want this period to be as short as possible to make the tests faster. +// However, client-go imposes a minimum resync period of 1 second, so that +// is the lowest we can go. +// https://github.com/kubernetes/client-go/blob/5a019202120ab4dd7dfb3788e5cb87269f343ebe/tools/cache/shared_informer.go#L575 const informerResyncPeriod = time.Second // Init will construct a new context for this builder and set default values // for any unset fields. func (b *Builder) Init() { if b.Context == nil { - b.Context = &controller.Context{ - RootContext: context.Background(), - } + b.Context = &controller.Context{} + } + if b.Context.RootContext == nil { + b.Context.RootContext = context.Background() } if b.StringGenerator == nil { - b.StringGenerator = RandStringBytes + b.StringGenerator = rand.String } - b.requiredReactors = make(map[string]bool) - b.Client = kubefake.NewSimpleClientset(b.KubeObjects...) - b.CMClient = cmfake.NewSimpleClientset(b.CertManagerObjects...) + scheme := metadatafake.NewTestScheme() + if err := metav1.AddMetaToScheme(scheme); err != nil { + b.T.Fatalf("error adding meta to scheme: %v", err) + } + b.ACMEOptions.ACMEHTTP01SolverRunAsNonRoot = true // default from cmd/controller/app/options/options.go + b.Client = kubefake.NewClientset(b.KubeObjects...) + b.CMClient = cmfake.NewClientset(b.CertManagerObjects...) + // FIXME: It seems like the gateway-api fake.NewClientset is misbehaving and is not usable per July 2025 b.GWClient = gwfake.NewSimpleClientset(b.GWObjects...) + b.MetadataClient = metadatafake.NewSimpleMetadataClient(scheme, b.PartialMetadataObjects...) b.DiscoveryClient = discoveryfake.NewDiscovery().WithServerResourcesForGroupVersion(func(groupVersion string) (*metav1.APIResourceList, error) { if groupVersion == networkingv1.SchemeGroupVersion.String() { return &metav1.APIResourceList{ @@ -141,9 +158,11 @@ func (b *Builder) Init() { b.FakeKubeClient().PrependReactor("create", "*", b.generateNameReactor) b.FakeCMClient().PrependReactor("create", "*", b.generateNameReactor) b.FakeGWClient().PrependReactor("create", "*", b.generateNameReactor) - b.KubeSharedInformerFactory = kubeinformers.NewSharedInformerFactory(b.Client, informerResyncPeriod) + b.FakeMetadataClient().PrependReactor("create", "*", b.generateNameReactor) + b.KubeSharedInformerFactory = internalinformers.NewBaseKubeInformerFactory(b.Client, informerResyncPeriod, "") b.SharedInformerFactory = informers.NewSharedInformerFactory(b.CMClient, informerResyncPeriod) b.GWShared = gwinformers.NewSharedInformerFactory(b.GWClient, informerResyncPeriod) + b.HTTP01ResourceMetadataInformersFactory = metadatainformer.NewFilteredSharedInformerFactory(b.MetadataClient, informerResyncPeriod, "", func(listOptions *metav1.ListOptions) {}) b.stopCh = make(chan struct{}) b.Metrics = metrics.New(logs.Log, clock.RealClock{}) @@ -169,7 +188,7 @@ func (b *Builder) FakeKubeClient() *kubefake.Clientset { return b.Context.Client.(*kubefake.Clientset) } -func (b *Builder) FakeKubeInformerFactory() kubeinformers.SharedInformerFactory { +func (b *Builder) FakeKubeInformerFactory() internalinformers.KubeInformerFactory { return b.Context.KubeSharedInformerFactory } @@ -185,16 +204,12 @@ func (b *Builder) FakeCMInformerFactory() informers.SharedInformerFactory { return b.Context.SharedInformerFactory } -func (b *Builder) EnsureReactorCalled(testName string, fn coretesting.ReactionFunc) coretesting.ReactionFunc { - b.requiredReactors[testName] = false - return func(action coretesting.Action) (handled bool, ret runtime.Object, err error) { - handled, ret, err = fn(action) - if !handled { - return - } - b.requiredReactors[testName] = true - return - } +func (b *Builder) FakeMetadataClient() *metadatafake.FakeMetadataClient { + return b.Context.MetadataClient.(*metadatafake.FakeMetadataClient) +} + +func (b *Builder) FakeDiscoveryClient() *discoveryfake.Discovery { + return b.Context.DiscoveryClient.(*discoveryfake.Discovery) } // CheckAndFinish will run ensure: all reactors are called, all actions are @@ -202,14 +217,11 @@ func (b *Builder) EnsureReactorCalled(testName string, fn coretesting.ReactionFu // It will then call the Builder's CheckFn, if defined. func (b *Builder) CheckAndFinish(args ...interface{}) { defer b.Stop() - if err := b.AllReactorsCalled(); err != nil { - b.T.Errorf("Not all expected reactors were called: %v", err) - } if err := b.AllActionsExecuted(); err != nil { - b.T.Errorf(err.Error()) + b.T.Error(err) } if err := b.AllEventsCalled(); err != nil { - b.T.Errorf(err.Error()) + b.T.Error(err) } // resync listers before running checks @@ -220,16 +232,6 @@ func (b *Builder) CheckAndFinish(args ...interface{}) { } } -func (b *Builder) AllReactorsCalled() error { - var errs []error - for n, reactorCalled := range b.requiredReactors { - if !reactorCalled { - errs = append(errs, fmt.Errorf("reactor not called: %s", n)) - } - } - return utilerrors.NewAggregate(errs) -} - func (b *Builder) AllEventsCalled() error { var errs []error if !util.EqualUnsorted(b.ExpectedEvents, b.Events()) { @@ -248,8 +250,7 @@ func (b *Builder) AllActionsExecuted() error { var unexpectedActions []coretesting.Action var errs []error - missingActions := make([]Action, len(b.ExpectedActions)) - copy(missingActions, b.ExpectedActions) + missingActions := slices.Clone(b.ExpectedActions) for _, a := range firedActions { // skip list and watch actions if a.GetVerb() == "list" || a.GetVerb() == "watch" { @@ -314,11 +315,20 @@ func (b *Builder) Start() { b.KubeSharedInformerFactory.Start(b.stopCh) b.SharedInformerFactory.Start(b.stopCh) b.GWShared.Start(b.stopCh) + b.HTTP01ResourceMetadataInformersFactory.Start(b.stopCh) // wait for caches to sync b.Sync() } +// Sync is a function used by tests to wait for all informers to be synced. This function +// is called initially by the Start method, to wait for the caches to be populated. It is +// also called directly by tests to wait for any updates made by the fake clients to be +// reflected in the informer caches. +// Sync calls the WaitForCacheSync method on all informers to make sure they have populated +// their caches. The WaitForCacheSync method is only useful at startup. In order to wait +// for updates made by the fake clients to be reflected in the informer caches, we need +// to sleep for the informerResyncPeriod. func (b *Builder) Sync() { if err := mustAllSync(b.KubeSharedInformerFactory.WaitForCacheSync(b.stopCh)); err != nil { panic("Error waiting for kubeSharedInformerFactory to sync: " + err.Error()) @@ -329,19 +339,13 @@ func (b *Builder) Sync() { if err := mustAllSync(b.GWShared.WaitForCacheSync(b.stopCh)); err != nil { panic("Error waiting for GWShared to sync: " + err.Error()) } - if b.additionalSyncFuncs != nil { - cache.WaitForCacheSync(b.stopCh, b.additionalSyncFuncs...) + if err := mustAllSync(b.HTTP01ResourceMetadataInformersFactory.WaitForCacheSync(b.stopCh)); err != nil { + panic("Error waiting for MetadataInformerFactory to sync:" + err.Error()) } - time.Sleep(informerResyncPeriod) -} -// RegisterAdditionalSyncFuncs registers an additional InformerSynced function -// with the builder. -// When the Sync method is called, the builder will also wait for the given -// listers to be synced as well as the listers that were registered with the -// informer factories that the builder provides. -func (b *Builder) RegisterAdditionalSyncFuncs(fns ...cache.InformerSynced) { - b.additionalSyncFuncs = append(b.additionalSyncFuncs, fns...) + // Wait for the informerResyncPeriod to make sure any update made by any of the fake clients + // is reflected in the informer caches. + time.Sleep(informerResyncPeriod) } func (b *Builder) Events() []string { @@ -352,7 +356,7 @@ func (b *Builder) Events() []string { return nil } -func mustAllSync(in map[reflect.Type]bool) error { +func mustAllSync[E comparable](in map[E]bool) error { var errs []error for t, started := range in { if !started { diff --git a/pkg/controller/test/reactors.go b/pkg/controller/test/reactors.go deleted file mode 100644 index e041ca4598c..00000000000 --- a/pkg/controller/test/reactors.go +++ /dev/null @@ -1,76 +0,0 @@ -/* -Copyright 2020 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. -*/ - -package test - -import ( - "reflect" - "testing" - - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - coretesting "k8s.io/client-go/testing" -) - -func NTimesReactor(f coretesting.ReactionFunc, numberCalls int) coretesting.ReactionFunc { - calls := 0 - return func(action coretesting.Action) (handled bool, ret runtime.Object, err error) { - if numberCalls == calls { - return false, nil, nil - } - handled, ret, err = f(action) - if handled { - calls++ - } - return handled, ret, err - } -} - -func ObjectCreatedReactor(t *testing.T, b *Builder, expectedObj runtime.Object) coretesting.ReactionFunc { - return func(action coretesting.Action) (handled bool, ret runtime.Object, err error) { - createAction, ok := action.(coretesting.CreateAction) - if !ok { - return - } - obj, ok := createAction.GetObject().(runtime.Object) - if !ok { - t.Errorf("object passed to Create does not implement runtime.Object") - } - - if !reflect.DeepEqual(obj, expectedObj) { - t.Errorf("expected %+v to equal %+v", obj, expectedObj) - } - - return true, obj, nil - } -} - -func ObjectDeletedReactor(t *testing.T, b *Builder, obj runtime.Object) coretesting.ReactionFunc { - metaExpObj := obj.(metav1.Object) - return func(action coretesting.Action) (handled bool, ret runtime.Object, err error) { - delAction, ok := action.(coretesting.DeleteAction) - if !ok { - return - } - - namespace, name := delAction.GetNamespace(), delAction.GetName() - if namespace != metaExpObj.GetNamespace() || name != metaExpObj.GetName() { - t.Errorf("expected %s/%s to equal %s/%s", namespace, name, metaExpObj.GetNamespace(), metaExpObj.GetName()) - } - - return true, obj, nil - } -} diff --git a/pkg/controller/test/recorder.go b/pkg/controller/test/recorder.go index 195e7cae69a..a7003dc09f1 100644 --- a/pkg/controller/test/recorder.go +++ b/pkg/controller/test/recorder.go @@ -42,5 +42,5 @@ func (f *FakeRecorder) PastEventf(object runtime.Object, timestamp metav1.Time, } func (f *FakeRecorder) AnnotatedEventf(object runtime.Object, annotations map[string]string, eventtype, reason, messageFmt string, args ...interface{}) { - f.Eventf(object, eventtype, reason, messageFmt, args) + f.Eventf(object, eventtype, reason, messageFmt, args...) } diff --git a/pkg/controller/util.go b/pkg/controller/util.go index 4d706906e5d..90612c294ee 100644 --- a/pkg/controller/util.go +++ b/pkg/controller/util.go @@ -22,9 +22,11 @@ import ( "time" "github.com/go-logr/logr" + corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/runtime" "k8s.io/client-go/tools/cache" "k8s.io/client-go/util/workqueue" @@ -32,23 +34,33 @@ import ( logf "github.com/cert-manager/cert-manager/pkg/logs" ) -var ( - // KeyFunc creates a key for an API object. The key can be passed to a - // worker function that processes an object from a queue such as - // ProcessItem. - KeyFunc = cache.DeletionHandlingMetaNamespaceKeyFunc -) - // DefaultItemBasedRateLimiter returns a new rate limiter with base delay of 5 // seconds, max delay of 5 minutes. -func DefaultItemBasedRateLimiter() workqueue.RateLimiter { - return workqueue.NewItemExponentialFailureRateLimiter(time.Second*5, time.Minute*5) +func DefaultItemBasedRateLimiter() workqueue.TypedRateLimiter[types.NamespacedName] { + return workqueue.NewTypedItemExponentialFailureRateLimiter[types.NamespacedName](time.Second*5, time.Minute*5) +} + +// DefaultCertificateRateLimiter returns a new rate limiter with base delay of 1 +// seconds, max delay of 30 seconds. +func DefaultCertificateRateLimiter() workqueue.TypedRateLimiter[types.NamespacedName] { + return workqueue.NewTypedItemExponentialFailureRateLimiter[types.NamespacedName](time.Second*1, time.Second*30) } -// HandleOwnedResourceNamespacedFunc returns a function thataccepts a +// DefaultACMERateLimiter returns a new rate limiter with base delay of 5 +// seconds, max delay of 30 minutes. +func DefaultACMERateLimiter() workqueue.TypedRateLimiter[types.NamespacedName] { + return workqueue.NewTypedItemExponentialFailureRateLimiter[types.NamespacedName](time.Second*5, time.Minute*30) +} + +// HandleOwnedResourceNamespacedFunc returns a function that accepts a // Kubernetes object and adds its owner references to the workqueue. // https://kubernetes.io/docs/concepts/workloads/controllers/garbage-collection/#owners-and-dependents -func HandleOwnedResourceNamespacedFunc(log logr.Logger, queue workqueue.RateLimitingInterface, ownerGVK schema.GroupVersionKind, get func(namespace, name string) (interface{}, error)) func(obj interface{}) { +func HandleOwnedResourceNamespacedFunc[T metav1.Object]( + log logr.Logger, + queue workqueue.TypedRateLimitingInterface[types.NamespacedName], + ownerGVK schema.GroupVersionKind, + get func(namespace, name string) (T, error), +) func(obj interface{}) { return func(obj interface{}) { log := log.WithName("handleOwnedResource") @@ -88,12 +100,10 @@ func HandleOwnedResourceNamespacedFunc(log logr.Logger, queue workqueue.RateLimi log.Error(err, "error getting referenced owning resource from cache") continue } - objKey, err := KeyFunc(obj) - if err != nil { - log.Error(err, "error computing key for resource") - continue - } - queue.Add(objKey) + queue.Add(types.NamespacedName{ + Name: obj.GetName(), + Namespace: obj.GetNamespace(), + }) } } } @@ -102,30 +112,33 @@ func HandleOwnedResourceNamespacedFunc(log logr.Logger, queue workqueue.RateLimi // QueuingEventHandler is an implementation of cache.ResourceEventHandler that // simply queues objects that are added/updated/deleted. type QueuingEventHandler struct { - Queue workqueue.RateLimitingInterface + Queue workqueue.TypedRateLimitingInterface[types.NamespacedName] } // Enqueue adds a key for an object to the workqueue. func (q *QueuingEventHandler) Enqueue(obj interface{}) { - key, err := KeyFunc(obj) + objectName, err := cache.DeletionHandlingObjectToName(obj) if err != nil { runtime.HandleError(err) return } - q.Queue.Add(key) + q.Queue.Add(types.NamespacedName{ + Name: objectName.Name, + Namespace: objectName.Namespace, + }) } // OnAdd adds a newly created object to the workqueue. -func (q *QueuingEventHandler) OnAdd(obj interface{}) { +func (q *QueuingEventHandler) OnAdd(obj interface{}, isInInitialList bool) { q.Enqueue(obj) } // OnUpdate adds an updated object to the workqueue. -func (q *QueuingEventHandler) OnUpdate(old, new interface{}) { - if reflect.DeepEqual(old, new) { +func (q *QueuingEventHandler) OnUpdate(oldObj, newObj interface{}) { + if reflect.DeepEqual(oldObj, newObj) { return } - q.Enqueue(new) + q.Enqueue(newObj) } // OnDelete adds a deleted object to the workqueue for processing. @@ -150,16 +163,16 @@ func (b *BlockingEventHandler) Enqueue(obj interface{}) { } // OnAdd synchronously adds a newly created object to the workqueue. -func (b *BlockingEventHandler) OnAdd(obj interface{}) { +func (b *BlockingEventHandler) OnAdd(obj interface{}, isInInitialList bool) { b.WorkFunc(obj) } // OnUpdate synchronously adds an updated object to the workqueue. -func (b *BlockingEventHandler) OnUpdate(old, new interface{}) { - if reflect.DeepEqual(old, new) { +func (b *BlockingEventHandler) OnUpdate(oldObj, newObj interface{}) { + if reflect.DeepEqual(oldObj, newObj) { return } - b.WorkFunc(new) + b.WorkFunc(newObj) } // OnDelete synchronously adds a deleted object to the workqueue. @@ -171,7 +184,7 @@ func (b *BlockingEventHandler) OnDelete(obj interface{}) { b.WorkFunc(obj) } -// BuildAnnotationsCopy takes a map of annotations and a list of prefix +// BuildAnnotationsToCopy takes a map of annotations and a list of prefix // filters and builds a filtered map of annotations. It is used to filter // annotations to be copied from Certificate to CertificateRequest and from // CertificateSigningRequest to Order. @@ -199,3 +212,23 @@ func BuildAnnotationsToCopy(allAnnotations map[string]string, prefixes []string) } return filteredAnnotations } + +func ToSecret(obj interface{}) (*corev1.Secret, bool) { + secret, ok := obj.(*corev1.Secret) + if !ok { + meta, ok := obj.(*metav1.PartialObjectMetadata) + if !ok { + // TODO: I wasn't able to get GVK from PartialMetadata, + // however perhaps this should be possible and then we + // could verify that this really is a Secret. At the + // moment this is okay as there is no path how any + // reconcile loop would receive PartialObjectMetadata + // for any other type. + return nil, false + } + secret = &corev1.Secret{} + secret.SetName(meta.Name) + secret.SetNamespace(meta.Namespace) + } + return secret, true +} diff --git a/pkg/ctl/scheme.go b/pkg/ctl/scheme.go deleted file mode 100644 index 015ca5182a6..00000000000 --- a/pkg/ctl/scheme.go +++ /dev/null @@ -1,77 +0,0 @@ -/* -Copyright 2020 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. -*/ - -// Package ctl was created to have a scheme that has the internal cert-manager types, -// and their conversion functions as well as the List object type registered, which is needed for ctl command like -// `convert` or `create certificaterequest`. - -package ctl - -import ( - corev1 "k8s.io/api/core/v1" - metainternalversion "k8s.io/apimachinery/pkg/apis/meta/internalversion" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/conversion" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/runtime/schema" - utilruntime "k8s.io/apimachinery/pkg/util/runtime" - kscheme "k8s.io/client-go/kubernetes/scheme" - - acmeinstall "github.com/cert-manager/cert-manager/internal/apis/acme/install" - cminstall "github.com/cert-manager/cert-manager/internal/apis/certmanager/install" - metainstall "github.com/cert-manager/cert-manager/internal/apis/meta/install" -) - -// Define a Scheme that has all cert-manager API types registered, including -// the internal API version, defaulting functions and conversion functions for -// all external versions. - -var ( - // Scheme is a Kubernetes runtime.Scheme with all internal and external API - // versions for cert-manager types registered. - Scheme = runtime.NewScheme() -) - -func init() { - cminstall.Install(Scheme) - acmeinstall.Install(Scheme) - metainstall.Install(Scheme) - - // This is used to add the List object type - listGroupVersion := schema.GroupVersionKind{Group: "", Version: runtime.APIVersionInternal, Kind: "List"} - Scheme.AddKnownTypeWithName(listGroupVersion, &metainternalversion.List{}) - metav1.AddToGroupVersion(Scheme, schema.GroupVersion{Version: "v1"}) - - utilruntime.Must(kscheme.AddToScheme(Scheme)) - utilruntime.Must(metainternalversion.AddToScheme(Scheme)) - - // Adds the conversion between internalmeta.List and corev1.List - _ = Scheme.AddConversionFunc((*corev1.List)(nil), (*metainternalversion.List)(nil), func(a, b interface{}, scope conversion.Scope) error { - metaList := &metav1.List{} - metaList.Items = a.(*corev1.List).Items - return metainternalversion.Convert_v1_List_To_internalversion_List(metaList, b.(*metainternalversion.List), scope) - }) - - _ = Scheme.AddConversionFunc((*metainternalversion.List)(nil), (*corev1.List)(nil), func(a, b interface{}, scope conversion.Scope) error { - metaList := &metav1.List{} - err := metainternalversion.Convert_internalversion_List_To_v1_List(a.(*metainternalversion.List), metaList, scope) - if err != nil { - return err - } - b.(*corev1.List).Items = metaList.Items - return nil - }) -} diff --git a/pkg/healthz/clock_health.go b/pkg/healthz/clock_health.go new file mode 100644 index 00000000000..2b8df55d1f6 --- /dev/null +++ b/pkg/healthz/clock_health.go @@ -0,0 +1,83 @@ +/* +Copyright 2020 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. +*/ + +package healthz + +import ( + "fmt" + "net/http" + "time" + + "k8s.io/utils/clock" +) + +const maxClockSkew = 5 * time.Minute + +// The clockHealthAdaptor implements the HealthChecker interface. +// It checks the system clock is in sync with the internal monotonic clock. +// This is important because the internal monotonic clock is used to trigger certificate +// reconciles for renewals. If the monotonic clock is out of sync with the system clock +// then renewals might not be triggered in time. Ideally we would trigger renewals based +// on the system clock, but this is not (yet) possible in Go. +// See https://github.com/golang/go/issues/35012 +// +// A clock skew can be caused by: +// 1. The system clock being adjusted +// -> this, e.g., happens when ntp adjusts the system clock +// 2. Pausing the process (e.g., with SIGSTOP) +// -> the monotonic clock will stop, but the system clock will continue +// -> this, e.g., happens when you pause a VM/ hibernate a laptop +// +// Small clock skews of < 5m are allowed, because they can happen when the system clock is +// adjusted. However, we do compound the clock skew over time, so that if the clock skew +// is small but constant, it will eventually fail the health check. +type clockHealthAdaptor struct { + clock clock.Clock + startTimeReal time.Time + startTimeMonotonic time.Time +} + +func NewClockHealthAdaptor(c clock.Clock) *clockHealthAdaptor { + now := c.Now() + return &clockHealthAdaptor{ + clock: c, + startTimeReal: now.Round(0), // .Round(0) removes the monotonic part from the time + startTimeMonotonic: now, + } +} + +func (c *clockHealthAdaptor) skew() time.Duration { + now := c.clock.Now() + realDuration := now.Sub(c.startTimeReal) + monotonicDuration := now.Sub(c.startTimeMonotonic) + + return (realDuration - monotonicDuration).Abs() +} + +// Name returns the name of the health check we are implementing. +func (l *clockHealthAdaptor) Name() string { + return "clockHealth" +} + +// Check is called by the healthz endpoint handler. +// It fails (returns an error) when the system clock is out of sync with the +// internal monotonic clock by more than the maxClockSkew. +func (l *clockHealthAdaptor) Check(req *http.Request) error { + if skew := l.skew(); skew > maxClockSkew { + return fmt.Errorf("the system clock is out of sync with the internal monotonic clock by %v, which is more than the allowed %v", skew, maxClockSkew) + } + return nil +} diff --git a/pkg/healthz/doc.go b/pkg/healthz/doc.go new file mode 100644 index 00000000000..566492a8ae2 --- /dev/null +++ b/pkg/healthz/doc.go @@ -0,0 +1,30 @@ +/* +Copyright 2020 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. +*/ + +// Package healthz provides an HTTP server which responds to HTTP liveness probes +// and performs health checks. +// +// Currently it only checks that the LeaderElector has an up to date LeaderElectionRecord. +// Normally the parent process should exit if the LeaderElectionRecord is stale, +// but it is possible that the process is prevented from exiting by a bug, +// in which case this check will fail, the liveness probe will fail and then the +// Kubelet will restart the process. +// See the following issue and PR to understand how this problem was solved in +// Kubernetes: +// * [kube-controller-manager becomes deadlocked but still passes healthcheck](https://github.com/kubernetes/kubernetes/issues/70819) +// * [Report KCM as unhealthy if leader election is wedged](https://github.com/kubernetes/kubernetes/pull/70971) + +package healthz diff --git a/pkg/healthz/healthz.go b/pkg/healthz/healthz.go new file mode 100644 index 00000000000..efbf58105dc --- /dev/null +++ b/pkg/healthz/healthz.go @@ -0,0 +1,91 @@ +/* +Copyright 2020 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. +*/ + +package healthz + +import ( + "context" + "errors" + "net" + "net/http" + "time" + + "golang.org/x/sync/errgroup" + "k8s.io/apiserver/pkg/server/healthz" + "k8s.io/client-go/tools/leaderelection" + "k8s.io/utils/clock" +) + +const ( + // Copied from pkg/metrics/metrics.go + healthzServerReadTimeout = 8 * time.Second + healthzServerWriteTimeout = 8 * time.Second + healthzServerMaxHeaderBytes = 1 << 20 // 1 MiB +) + +// Server responds to HTTP requests to a /livez endpoint and responds with an +// error if the LeaderElector has exited or has not observed the +// LeaderElectionRecord for a given amount of time. +type Server struct { + server *http.Server + // LeaderHealthzAdaptor is public so that it can be retrieved by the caller + // and used as the value for `LeaderElectionConfig.Watchdog` when + // initializing the LeaderElector. + LeaderHealthzAdaptor *leaderelection.HealthzAdaptor +} + +// NewServer creates a new healthz.Server. +// The supplied leaderElectionHealthzAdaptorTimeout controls how long after the +// leader lease time, the leader election will be considered to have failed. +func NewServer(leaderElectionHealthzAdaptorTimeout time.Duration) *Server { + leaderHealthzAdaptor := leaderelection.NewLeaderHealthzAdaptor(leaderElectionHealthzAdaptorTimeout) + clockHealthAdaptor := NewClockHealthAdaptor(clock.RealClock{}) + mux := http.NewServeMux() + healthz.InstallLivezHandler(mux, leaderHealthzAdaptor, clockHealthAdaptor) + return &Server{ + server: &http.Server{ + ReadTimeout: healthzServerReadTimeout, + WriteTimeout: healthzServerWriteTimeout, + MaxHeaderBytes: healthzServerMaxHeaderBytes, + Handler: mux, + }, + LeaderHealthzAdaptor: leaderHealthzAdaptor, + } +} + +// Start makes the server listen on the supplied socket, until the supplied +// context is cancelled, after which the server will gracefully shutdown and Start will +// exit. +// The server is given 5 seconds to shutdown gracefully. +func (o *Server) Start(ctx context.Context, l net.Listener) error { + var g errgroup.Group + g.Go(func() error { + if err := o.server.Serve(l); err != nil && !errors.Is(err, http.ErrServerClosed) { + return err + } + return nil + }) + g.Go(func() error { + <-ctx.Done() + // allow a timeout for graceful shutdown + shutdownCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + + // nolint: contextcheck + return o.server.Shutdown(shutdownCtx) + }) + return g.Wait() +} diff --git a/pkg/healthz/healthz_test.go b/pkg/healthz/healthz_test.go new file mode 100644 index 00000000000..43a45024d1a --- /dev/null +++ b/pkg/healthz/healthz_test.go @@ -0,0 +1,396 @@ +/* +Copyright 2020 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. +*/ + +package healthz_test + +import ( + "context" + "encoding/json" + "fmt" + "io" + "net" + "net/http" + "sync" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "golang.org/x/sync/errgroup" + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/client-go/tools/leaderelection" + "k8s.io/client-go/tools/leaderelection/resourcelock" + "k8s.io/klog/v2" + "k8s.io/klog/v2/ktesting" + + "github.com/cert-manager/cert-manager/pkg/healthz" + + _ "k8s.io/klog/v2/ktesting/init" // add command line flags +) + +const ( + localIdentity = "local-node" + remoteIdentity = "remote-node" + lockDescription = "fake-resource-lock" +) + +// TestHealthzLivezLeaderElection checks the responses of the `/livez/leaderElection` endpoint. +// +// These tests are intended to demonstrate that the LeaderElectionHealthzAdaptor +// does indeed cause the `/livez` endpoint to return errors if the healthz +// server continues to run after the LeaderElector go-routine has exited. +func TestHealthzLivezLeaderElection(t *testing.T) { + + type input struct { + leaderElectionEnabled bool + resourceLock *fakeResourceLock + onNewLeaderHook func(in *input) + } + + type output struct { + responseBody string + responseCode int + } + + type testCase struct { + name string + in input + out output + } + + tests := []testCase{ + { + // OK: when leader-election is disabled (--leader-elect=false) the leader + // election healthz adaptor always returns OK. + // + // LeaderElectionHealthzAdaptor.Check returns nil if its + // LeaderElector pointer has not been set. + // See https://github.com/kubernetes/client-go/blob/8cbca742aebe24b24f7f4e32fd999942fa9133e8/tools/leaderelection/healthzadaptor.go#L43-L52 + name: "ok-leader-election-disabled", + in: input{ + leaderElectionEnabled: false, + }, + out: output{ + responseBody: "ok", + responseCode: http.StatusOK, + }, + }, + { + // OK: when the local node wins and holds the leader election + name: "ok-local-leader", + in: input{ + leaderElectionEnabled: true, + resourceLock: &fakeResourceLock{}, + }, + out: output{ + responseBody: "ok", + responseCode: http.StatusOK, + }, + }, + { + // OK: when a remote node is leader and has updated the leader + // election record. + // + // LeaderElect.Check always succeeds when another node has the + // leader lock. + // See https://github.com/kubernetes/client-go/blob/8cbca742aebe24b24f7f4e32fd999942fa9133e8/tools/leaderelection/leaderelection.go#L385-L399 + name: "ok-remote-leader", + in: input{ + leaderElectionEnabled: true, + resourceLock: &fakeResourceLock{ + record: &resourcelock.LeaderElectionRecord{ + HolderIdentity: remoteIdentity, + }, + }, + }, + out: output{ + responseBody: "ok", + responseCode: http.StatusOK, + }, + }, + { + // Failure: when update starts to fail after the local node has once + // acquired the leader election lock. + // + // This is intended to simulate the situation where the + // LeaderElector go-routine has exited, but the parent process is + // wedged and has not exited. + // In this situation, the /livez endpoint responds with an error, + // because the LeaderElectionHealthzAdaptor still has a reference to + // the no-longer running LeaderElector and its last state. + // + // Start LeaderElector without a LeaderElectionRecord, wait for the + // record to be created, and then when LeaderElector calls the + // OnNewLeader callback, set the fakeResourceLock to return an error + // when Update is called. + // This persistent error causes `LeaderElector.renew` to exit and + // causes LeaderElector.Run to exit after the `RenewDeadline`. + // + // The LeaderElection go-routine will exit but the healthz server + // will continue running. + name: "fail-delayed-update-error", + in: input{ + leaderElectionEnabled: true, + resourceLock: &fakeResourceLock{ + record: nil, + }, + onNewLeaderHook: func(in *input) { + in.resourceLock.updateError = fmt.Errorf("simulated-delayed-update-error") + }, + }, + out: output{ + responseBody: "internal server error: failed election to renew leadership on lease \n", + responseCode: http.StatusInternalServerError, + }, + }, + { + // Failure: when the local node attempts to acquire the lease but fails to update + // the leader election record. + // + // Like the fail-delayed-update-error test, this is intended to + // cause the LeaderElector to exit, leaving the healthz server + // running and querying the last state of the exited LeaderElector. + // + // In this simulation, there is already a LeaderElectionRecord belonging to the local node, + // and the update is simulated to fail on the first attempt. + // + // TODO(wallrj): This test may be redundant because it has the same + // effect as `fail-delayed-update-error`, in causing the running + // LeaderElector to exit. + name: "fail-immediate-update-error", + in: input{ + leaderElectionEnabled: true, + resourceLock: &fakeResourceLock{ + record: &resourcelock.LeaderElectionRecord{ + HolderIdentity: localIdentity, + }, + updateError: fmt.Errorf("simulated-update-error"), + }, + }, + out: output{ + responseBody: "internal server error: failed election to renew leadership on lease \n", + responseCode: http.StatusInternalServerError, + }, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + log, ctx := ktesting.NewTestContext(t) + + ctx, cancel := context.WithTimeout(ctx, 5*time.Second) + defer cancel() + + lc := net.ListenConfig{} + l, err := lc.Listen(t.Context(), "tcp", "127.0.0.1:0") + require.NoError(t, err) + + livezURL := "http://" + l.Addr().String() + "/livez/leaderElection" + + const leaderElectionHealthzAdaptorTimeout = 0 + s := healthz.NewServer(leaderElectionHealthzAdaptorTimeout) + + g, gCTX := errgroup.WithContext(ctx) + + leaderElected := make(chan struct{}) + + if tc.in.leaderElectionEnabled { + const ( + leaseDuration = 500 * time.Millisecond + renewDeadline = 400 * time.Millisecond + retryPeriod = 300 * time.Millisecond + ) + + log.Info( + "Starting leader election go-routine", + "leaseDuration", leaseDuration, + "renewDeadline", renewDeadline, + "retryPeriod", retryPeriod, + ) + tc.in.resourceLock.lockName = t.Name() + g.Go(func() error { + defer log.Info("Leader election go-routine finished") + leaderelection.RunOrDie(gCTX, leaderelection.LeaderElectionConfig{ + LeaseDuration: leaseDuration, + RenewDeadline: renewDeadline, + RetryPeriod: retryPeriod, + Callbacks: leaderelection.LeaderCallbacks{ + OnStartedLeading: func(context.Context) { + log.Info("leaderelection.LeaderCallbacks.OnStartedLeading") + }, + OnStoppedLeading: func() { + log.Info("leaderelection.LeaderCallbacks.OnStoppedLeading") + }, + OnNewLeader: func(identity string) { + log.Info("leaderelection.LeaderCallbacks.OnNewLeader", "identity", identity) + if tc.in.onNewLeaderHook != nil { + tc.in.onNewLeaderHook(&tc.in) + } + close(leaderElected) + }, + }, + Lock: tc.in.resourceLock, + WatchDog: s.LeaderHealthzAdaptor, + }) + return nil + }) + } + + log.Info("Starting healthz server go-routine") + g.Go(func() error { + defer log.Info("Healthz server go-routine finished") + return s.Start(gCTX, l) + }) + + if tc.in.leaderElectionEnabled { + log.Info("Waiting for a LeaderElector to know the current leader before polling liveness endpoint") + <-leaderElected + } + + const ( + pollingInterval = 500 * time.Millisecond + pollingTimeout = 3 * time.Second + ) + log.Info( + "Polling liveness endpoint", + "url", livezURL, + "interval", pollingInterval, + "timeout", pollingTimeout, + ) + var ( + lastResponseCode int + lastResponseBody string + ) + assert.Eventually(t, func() bool { + req, err := http.NewRequestWithContext(ctx, http.MethodGet, livezURL, nil) + require.NoError(t, err) + resp, err := http.DefaultClient.Do(req) + require.NoError(t, err) + defer func() { + require.NoError(t, resp.Body.Close()) + }() + bodyBytes, err := io.ReadAll(resp.Body) + require.NoError(t, err) + + lastResponseCode = resp.StatusCode + lastResponseBody = string(bodyBytes) + + log.Info("liveness-probe", "response-code", lastResponseCode, "response-body", lastResponseBody) + + return tc.out.responseCode == lastResponseCode && tc.out.responseBody == lastResponseBody + }, pollingTimeout, pollingInterval) + + assert.Equal(t, tc.out.responseBody, lastResponseBody) + assert.Equal(t, tc.out.responseCode, lastResponseCode) + cancel() + require.NoError(t, g.Wait()) + }) + } +} + +// fakeResourceLock implements resourcelock.Interface sufficiently to simulate: +// * successful acquisition of the leader election lock by the local node, +// * current possession of the leader election lock by a remote node, and +// * failures in leader election which cause the `LeaderElection.Run` function to exit. +// +// The intention is to be able to test the behavior of the +// LeaderElectionHealthzAdaptor under those circumstances. +type fakeResourceLock struct { + lockName string + record *resourcelock.LeaderElectionRecord + getError error + updateError error + lock sync.Mutex +} + +func (o *fakeResourceLock) Identity() string { + return localIdentity +} + +func (o *fakeResourceLock) Describe() string { + return o.lockName +} + +// Get returns not-found error if the leader election record is not currently +// set i.e. the zero value of fakeResourceLock, +// to simulate a situation where no leader has ever been elected. +// +// Or if there is an existing record, it simply returns it. +// This is to allow simulating the situation where the local node has won the +// election and updated the record by calling Create or subsequently Update. +// +// There is a special case, where if the holder == remote-node, +// we are simulating a remote leader. +// And in this case, we want to always return a unique []byte representation, +// which causes the leader election library to treat the leader election record +// as having been renewed. +// To do this we increment the LeaderTransitions field. +// +// This aspect of the LeaderElectionRecord API is documented as follows: +// > LeaderElectionRecord is the record that is stored in the leader election annotation. +// > This information should be used for observational purposes only and could be replaced +// > with a random string (e.g., UUID) with only slight modification of this code. +// > -- https://github.com/kubernetes/kubernetes/blob/7e25f1232a9f89875641431ae011c916f0376c57/staging/src/k8s.io/client-go/tools/leaderelection/resourcelock/interface.go#L107-L110 +func (o *fakeResourceLock) Get(ctx context.Context) (*resourcelock.LeaderElectionRecord, []byte, error) { + o.lock.Lock() + defer o.lock.Unlock() + + klog.FromContext(ctx).WithName("fakeResourceLock").Info("Get") + if o.getError != nil { + return nil, nil, o.getError + } + if o.record == nil { + err := errors.NewNotFound(schema.ParseGroupResource("configmap"), "foo") + return nil, nil, err + } + + // If simulating a remote-node leader, increment the LeaderTransitions field, + // simply to ensure a unique []byte representation each time. + // See the function documentation above for a fuller explanation. + if o.record.HolderIdentity == remoteIdentity { + o.record.LeaderTransitions++ + } + + lerByte, err := json.Marshal(*o.record) + if err != nil { + return nil, nil, err + } + return o.record, lerByte, nil +} + +func (o *fakeResourceLock) Create(ctx context.Context, ler resourcelock.LeaderElectionRecord) error { + o.lock.Lock() + defer o.lock.Unlock() + + klog.FromContext(ctx).WithName("fakeResourceLock").Info("Create") + o.record = &ler + return nil +} + +func (o *fakeResourceLock) Update(ctx context.Context, ler resourcelock.LeaderElectionRecord) error { + o.lock.Lock() + defer o.lock.Unlock() + + klog.FromContext(ctx).WithName("fakeResourceLock").Info("Update") + o.record = &ler + return o.updateError +} + +func (o *fakeResourceLock) RecordEvent(_ string) {} + +var _ resourcelock.Interface = &fakeResourceLock{} diff --git a/pkg/issuer/acme/acme.go b/pkg/issuer/acme/acme.go index daa9d8c5297..44a6f0d5662 100644 --- a/pkg/issuer/acme/acme.go +++ b/pkg/issuer/acme/acme.go @@ -19,18 +19,16 @@ package acme import ( "context" "crypto" - "fmt" core "k8s.io/client-go/kubernetes/typed/core/v1" - corelisters "k8s.io/client-go/listers/core/v1" "k8s.io/client-go/tools/record" + internalinformers "github.com/cert-manager/cert-manager/internal/informers" "github.com/cert-manager/cert-manager/pkg/acme/accounts" apiutil "github.com/cert-manager/cert-manager/pkg/api/util" - v1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" + cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" "github.com/cert-manager/cert-manager/pkg/controller" "github.com/cert-manager/cert-manager/pkg/issuer" - "github.com/cert-manager/cert-manager/pkg/metrics" "github.com/cert-manager/cert-manager/pkg/util/kube" ) @@ -38,8 +36,6 @@ import ( // certificates from any ACME server. It supports DNS01 and HTTP01 challenge // mechanisms. type Acme struct { - issuer v1.GenericIssuer - secretsClient core.SecretsGetter recorder record.EventRecorder @@ -51,35 +47,22 @@ type Acme struct { clientBuilder accounts.NewClientFunc // namespace of referenced resources when the given issuer is a ClusterIssuer - clusterResourceNamespace string + resourceNamespace func(iss cmapi.GenericIssuer) string // used as a cache for ACME clients accountRegistry accounts.Registry - - // metrics is used to create instrumented ACME clients - metrics *metrics.Metrics - - // userAgent is the string used as the UserAgent when making HTTP calls. - userAgent string } // New returns a new ACME issuer interface for the given issuer. -func New(ctx *controller.Context, issuer v1.GenericIssuer) (issuer.Interface, error) { - if issuer.GetSpec().ACME == nil { - return nil, fmt.Errorf("acme config may not be empty") - } - - secretsLister := ctx.KubeSharedInformerFactory.Core().V1().Secrets().Lister() +func New(ctx *controller.Context) (issuer.Interface, error) { + secretsLister := ctx.KubeSharedInformerFactory.Secrets().Lister() a := &Acme{ - issuer: issuer, - keyFromSecret: newKeyFromSecret(secretsLister), - clientBuilder: accounts.NewClient, - secretsClient: ctx.Client.CoreV1(), - recorder: ctx.Recorder, - clusterResourceNamespace: ctx.IssuerOptions.ClusterResourceNamespace, - accountRegistry: ctx.ACMEOptions.AccountRegistry, - metrics: ctx.Metrics, - userAgent: ctx.RESTConfig.UserAgent, + keyFromSecret: newKeyFromSecret(secretsLister), + clientBuilder: accounts.NewClient(ctx.Metrics, ctx.RESTConfig.UserAgent), + secretsClient: ctx.Client.CoreV1(), + recorder: ctx.Recorder, + resourceNamespace: ctx.IssuerOptions.ResourceNamespace, + accountRegistry: ctx.ACMEAccountRegistry, } return a, nil @@ -90,7 +73,7 @@ func New(ctx *controller.Context, issuer v1.GenericIssuer) (issuer.Interface, er type keyFromSecretFunc func(ctx context.Context, namespace, name, keyName string) (crypto.Signer, error) // newKeyFromSecret returns an implementation of keyFromSecretFunc for a secrets lister. -func newKeyFromSecret(secretLister corelisters.SecretLister) keyFromSecretFunc { +func newKeyFromSecret(secretLister internalinformers.SecretLister) keyFromSecretFunc { return func(ctx context.Context, namespace, name, keyName string) (crypto.Signer, error) { return kube.SecretTLSKeyRef(ctx, secretLister, namespace, name, keyName) } diff --git a/pkg/issuer/acme/dns/acmedns/acmedns.go b/pkg/issuer/acme/dns/acmedns/acmedns.go index 26427a664fb..e32a4d65e83 100644 --- a/pkg/issuer/acme/dns/acmedns/acmedns.go +++ b/pkg/issuer/acme/dns/acmedns/acmedns.go @@ -25,17 +25,18 @@ limitations under the License. package acmedns import ( + "context" "encoding/json" "fmt" "os" - "github.com/cpu/goacmedns" + "github.com/nrdcg/goacmedns" ) // DNSProvider is an implementation of the acme.ChallengeProvider interface type DNSProvider struct { dns01Nameservers []string - client goacmedns.Client + client *goacmedns.Client accounts map[string]goacmedns.Account } @@ -51,7 +52,10 @@ func NewDNSProvider(dns01Nameservers []string) (*DNSProvider, error) { // acme-dns server host is given in a string // credentials are stored in json in the given string func NewDNSProviderHostBytes(host string, accountJSON []byte, dns01Nameservers []string) (*DNSProvider, error) { - client := goacmedns.NewClient(host) + client, err := goacmedns.NewClient(host) + if err != nil { + return nil, fmt.Errorf("Error creating acme-dns client: %s", err) + } var accounts map[string]goacmedns.Account if err := json.Unmarshal(accountJSON, &accounts); err != nil { @@ -66,10 +70,10 @@ func NewDNSProviderHostBytes(host string, accountJSON []byte, dns01Nameservers [ } // Present creates a TXT record to fulfil the dns-01 challenge -func (c *DNSProvider) Present(domain, fqdn, value string) error { +func (c *DNSProvider) Present(ctx context.Context, domain, fqdn, value string) error { if account, exists := c.accounts[domain]; exists { // Update the acme-dns TXT record. - return c.client.UpdateTXTRecord(account, value) + return c.client.UpdateTXTRecord(ctx, account, value) } return fmt.Errorf("account credentials not found for domain %s", domain) @@ -77,7 +81,7 @@ func (c *DNSProvider) Present(domain, fqdn, value string) error { // CleanUp removes the record matching the specified parameters. It is not // implemented for the ACME-DNS provider. -func (c *DNSProvider) CleanUp(_, _, _ string) error { +func (c *DNSProvider) CleanUp(_ context.Context, _, _, _ string) error { // ACME-DNS doesn't support the notion of removing a record. For users of // ACME-DNS it is expected the stale records remain in-place. return nil diff --git a/pkg/issuer/acme/dns/acmedns/acmedns_test.go b/pkg/issuer/acme/dns/acmedns/acmedns_test.go index 357af597db9..08a2d4bbe98 100644 --- a/pkg/issuer/acme/dns/acmedns/acmedns_test.go +++ b/pkg/issuer/acme/dns/acmedns/acmedns_test.go @@ -20,8 +20,9 @@ import ( "os" "testing" - "github.com/cert-manager/cert-manager/pkg/issuer/acme/dns/util" "github.com/stretchr/testify/assert" + + "github.com/cert-manager/cert-manager/pkg/issuer/acme/dns/util" ) var ( @@ -45,7 +46,7 @@ func TestValidJsonAccount(t *testing.T) { "domain": { "fulldomain": "fooldom", "password": "secret", - "subdomain": "subdoom", + "subdomain": "subdom", "username": "usernom" } }`) @@ -74,6 +75,6 @@ func TestLiveAcmeDnsPresent(t *testing.T) { assert.NoError(t, err) // ACME-DNS requires 43 character keys or it throws a bad TXT error - err = provider.Present(acmednsDomain, "", "LG3tptA6W7T1vw4ujbmDxH2lLu6r8TUIqLZD3pzPmgE") + err = provider.Present(t.Context(), acmednsDomain, "", "LG3tptA6W7T1vw4ujbmDxH2lLu6r8TUIqLZD3pzPmgE") assert.NoError(t, err) } diff --git a/pkg/issuer/acme/dns/akamai/akamai.go b/pkg/issuer/acme/dns/akamai/akamai.go index a72a8707ba6..84de8688fe6 100644 --- a/pkg/issuer/acme/dns/akamai/akamai.go +++ b/pkg/issuer/acme/dns/akamai/akamai.go @@ -20,14 +20,14 @@ limitations under the License. package akamai import ( + "context" "fmt" "strings" - dns "github.com/akamai/AkamaiOPEN-edgegrid-golang/configdns-v2" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/edgegrid" - + dns "github.com/akamai/AkamaiOPEN-edgegrid-golang/v12/pkg/dns" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v12/pkg/edgegrid" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v12/pkg/session" "github.com/go-logr/logr" - "github.com/pkg/errors" "github.com/cert-manager/cert-manager/pkg/issuer/acme/dns/util" logf "github.com/cert-manager/cert-manager/pkg/logs" @@ -35,15 +35,15 @@ import ( // OpenEdgegridDNSService enables mocking and required functions type OpenEdgegridDNSService interface { - GetRecord(zone string, name string, recordType string) (*dns.RecordBody, error) - RecordSave(rec *dns.RecordBody, zone string) error - RecordUpdate(rec *dns.RecordBody, zone string) error - RecordDelete(rec *dns.RecordBody, zone string) error + GetRecord(ctx context.Context, zone string, name string, recordType string) (*dns.RecordBody, error) + RecordSave(ctx context.Context, rec *dns.RecordBody, zone string) error + RecordUpdate(ctx context.Context, rec *dns.RecordBody, zone string) error + RecordDelete(ctx context.Context, rec *dns.RecordBody, zone string) error } -// OpenDNSConfig contains akamai's config to create authorization header. -type OpenDNSConfig struct { - config edgegrid.Config +// OpenDNSClient holds Akamai's client to create authorization header. +type OpenDNSClient struct { + client dns.DNS } // DNSProvider is an implementation of the acme.ChallengeProvider interface @@ -52,7 +52,7 @@ type DNSProvider struct { serviceConsumerDomain string dnsclient OpenEdgegridDNSService TTL int - findHostedDomainByFqdn func(string, []string) (string, error) + findHostedDomainByFqdn func(context.Context, string, []string) (string, error) isNotFound func(error) bool log logr.Logger } @@ -62,33 +62,43 @@ func NewDNSProvider(serviceConsumerDomain, clientToken, clientSecret, accessToke // required Aka OpenEdgegrid creds + non empty dnsservers list if serviceConsumerDomain == "" || clientToken == "" || clientSecret == "" || accessToken == "" || len(dns01Nameservers) < 1 { - return nil, fmt.Errorf("edgedns: Provider creation failed. Missing required arguments.") + return nil, fmt.Errorf("edgedns: Provider creation failed. Missing required arguments") } dnsp := &DNSProvider{ dns01Nameservers: dns01Nameservers, serviceConsumerDomain: serviceConsumerDomain, - dnsclient: &OpenDNSConfig{}, + dnsclient: &OpenDNSClient{}, findHostedDomainByFqdn: findHostedDomainByFqdn, isNotFound: isNotFound, log: logf.Log.WithName("akamai-dns"), TTL: 300, } - dnsp.dnsclient.(*OpenDNSConfig).config = edgegrid.Config{ - Host: serviceConsumerDomain, - ClientToken: clientToken, - ClientSecret: clientSecret, - AccessToken: accessToken, - MaxBody: 131072, + cfg, err := edgegrid.New(func(c *edgegrid.Config) { + c.Host = serviceConsumerDomain + c.ClientToken = clientToken + c.ClientSecret = clientSecret + c.AccessToken = accessToken + c.MaxBody = 131072 + }) + if err != nil { + return nil, fmt.Errorf("edgedns: Provider config creation failed: %w", err) + } + + s, err := session.New( + session.WithSigner(cfg), + ) + if err != nil { + return nil, fmt.Errorf("edgedns: Error creating session: %w", err) } - dns.Init(dnsp.dnsclient.(*OpenDNSConfig).config) + dnsp.dnsclient.(*OpenDNSClient).client = dns.Client(s) return dnsp, nil } -func findHostedDomainByFqdn(fqdn string, ns []string) (string, error) { - zone, err := util.FindZoneByFqdn(fqdn, ns) +func findHostedDomainByFqdn(ctx context.Context, fqdn string, ns []string) (string, error) { + zone, err := util.FindZoneByFqdn(ctx, fqdn, ns) if err != nil { return "", err } @@ -97,26 +107,25 @@ func findHostedDomainByFqdn(fqdn string, ns []string) (string, error) { } // Present creates/updates a TXT record to fulfill the dns-01 challenge. -func (a *DNSProvider) Present(domain, fqdn, value string) error { - - logf.V(logf.DebugLevel).Infof("entering Present. domain: %s, fqdn: %s, value: %s", domain, fqdn, value) +func (a *DNSProvider) Present(ctx context.Context, domain, fqdn, value string) error { + logf.FromContext(ctx).V(logf.DebugLevel).Info("entering Present", "domain", domain, "fqdn", fqdn, "value", value) - hostedDomain, err := a.findHostedDomainByFqdn(fqdn, a.dns01Nameservers) + hostedDomain, err := a.findHostedDomainByFqdn(ctx, fqdn, a.dns01Nameservers) if err != nil { - return errors.Wrapf(err, "edgedns: failed to determine hosted domain for %q", fqdn) + return fmt.Errorf("edgedns: failed to determine hosted domain for %q: %w", fqdn, err) } hostedDomain = util.UnFqdn(hostedDomain) - logf.V(logf.DebugLevel).Infof("hostedDomain: %s", hostedDomain) + logf.FromContext(ctx).V(logf.DebugLevel).Info("calculated hosted domain", "hostedDomain", hostedDomain) recordName, err := makeTxtRecordName(fqdn, hostedDomain) if err != nil { - return errors.Wrapf(err, "edgedns: failed to create TXT record name") + return fmt.Errorf("edgedns: failed to create TXT record name: %w", err) } - logf.V(logf.DebugLevel).Infof("recordName: %s", recordName) + logf.FromContext(ctx).V(logf.DebugLevel).Info("calculated TXT record name", "recordName", recordName) - record, err := a.dnsclient.GetRecord(hostedDomain, recordName, "TXT") + record, err := a.dnsclient.GetRecord(ctx, hostedDomain, recordName, "TXT") if err != nil && !a.isNotFound(err) { - return errors.Wrapf(err, "edgedns: failed to retrieve TXT record") + return fmt.Errorf("edgedns: failed to retrieve TXT record: %w", err) } if err == nil && record == nil { @@ -124,7 +133,7 @@ func (a *DNSProvider) Present(domain, fqdn, value string) error { } if record != nil { - logf.V(logf.InfoLevel).Infof("edgedns: TXT record already exists. Updating target") + logf.FromContext(ctx).V(logf.InfoLevel).Info("edgedns: TXT record already exists. Updating target") if containsValue(record.Target, value) { // have a record and have entry already @@ -134,9 +143,9 @@ func (a *DNSProvider) Present(domain, fqdn, value string) error { record.Target = append(record.Target, `"`+value+`"`) record.TTL = a.TTL - err = a.dnsclient.RecordUpdate(record, hostedDomain) + err = a.dnsclient.RecordUpdate(ctx, record, hostedDomain) if err != nil { - return errors.Wrapf(err, "edgedns: failed to update TXT record") + return fmt.Errorf("edgedns: failed to update TXT record: %w", err) } return nil @@ -149,38 +158,37 @@ func (a *DNSProvider) Present(domain, fqdn, value string) error { Target: []string{`"` + value + `"`}, } - err = a.dnsclient.RecordSave(record, hostedDomain) + err = a.dnsclient.RecordSave(ctx, record, hostedDomain) if err != nil { - return errors.Wrapf(err, "edgedns: failed to create TXT record") + return fmt.Errorf("edgedns: failed to create TXT record: %w", err) } return nil } // CleanUp removes/updates the TXT record matching the specified parameters. -func (a *DNSProvider) CleanUp(domain, fqdn, value string) error { +func (a *DNSProvider) CleanUp(ctx context.Context, domain, fqdn, value string) error { + logf.FromContext(ctx).V(logf.DebugLevel).Info("entering CleanUp", "domain", domain, "fqdn", fqdn, "value", value) - logf.V(logf.DebugLevel).Infof("entering CleanUp. domain: %s, fqdn: %s, value: %s", domain, fqdn, value) - - hostedDomain, err := a.findHostedDomainByFqdn(fqdn, a.dns01Nameservers) + hostedDomain, err := a.findHostedDomainByFqdn(ctx, fqdn, a.dns01Nameservers) if err != nil { - return errors.Wrapf(err, "edgedns: failed to determine hosted domain for %q", fqdn) + return fmt.Errorf("edgedns: failed to determine hosted domain for %q: %w", fqdn, err) } hostedDomain = util.UnFqdn(hostedDomain) - logf.V(logf.DebugLevel).Infof("hostedDomain: %s", hostedDomain) + logf.FromContext(ctx).V(logf.DebugLevel).Info("calculated hosted domain", "hostedDomain", hostedDomain) recordName, err := makeTxtRecordName(fqdn, hostedDomain) if err != nil { - return errors.Wrapf(err, "edgedns: failed to create TXT record name") + return fmt.Errorf("edgedns: failed to create TXT record name: %w", err) } - logf.V(logf.DebugLevel).Infof("recordName: %s", recordName) + logf.FromContext(ctx).V(logf.DebugLevel).Info("calculated TXT record name", "recordName", recordName) - existingRec, err := a.dnsclient.GetRecord(hostedDomain, recordName, "TXT") + existingRec, err := a.dnsclient.GetRecord(ctx, hostedDomain, recordName, "TXT") if err != nil { if a.isNotFound(err) { return nil } - return errors.Wrapf(err, "edgedns: failed to retrieve TXT record") + return fmt.Errorf("edgedns: failed to retrieve TXT record: %w", err) } if existingRec == nil { @@ -206,19 +214,19 @@ func (a *DNSProvider) CleanUp(domain, fqdn, value string) error { if len(newRData) > 0 { existingRec.Target = newRData - logf.V(logf.DebugLevel).Infof("updating Akamai TXT record: %s, data: %s", existingRec.Name, newRData) - err = a.dnsclient.RecordUpdate(existingRec, hostedDomain) + logf.FromContext(ctx).V(logf.DebugLevel).Info("updating Akamai TXT record", "recordName", existingRec.Name, "data", newRData) + err = a.dnsclient.RecordUpdate(ctx, existingRec, hostedDomain) if err != nil { - return errors.Wrapf(err, "edgedns: TXT record update failed") + return fmt.Errorf("edgedns: TXT record update failed: %w", err) } return nil } - logf.V(logf.DebugLevel).Infof("deleting Akamai TXT record %s", existingRec.Name) - err = a.dnsclient.RecordDelete(existingRec, hostedDomain) + logf.FromContext(ctx).V(logf.DebugLevel).Info("deleting Akamai TXT record", "recordName", existingRec.Name) + err = a.dnsclient.RecordDelete(ctx, existingRec, hostedDomain) if err != nil { - return errors.Wrapf(err, "edgedns: TXT record delete failed") + return fmt.Errorf("edgedns: TXT record delete failed: %w", err) } return nil @@ -235,24 +243,19 @@ func containsValue(values []string, value string) bool { } func isNotFound(err error) bool { - if err == nil { return false } - _, ok := err.(*dns.RecordError) - if ok { - return true - } - - return false + _, ok := err.(*dns.Error) + return ok } func makeTxtRecordName(fqdn, hostedDomain string) (string, error) { recName := util.UnFqdn(fqdn) if !strings.HasSuffix(recName, hostedDomain) { - return "", errors.Errorf("fqdn %q is not part of %q", fqdn, hostedDomain) + return "", fmt.Errorf("fqdn %q is not part of %q", fqdn, hostedDomain) } return recName, nil @@ -260,27 +263,47 @@ func makeTxtRecordName(fqdn, hostedDomain string) (string, error) { // GetRecord gets a single Recordset as RecordBody. Sets Akamai OPEN Edgegrid API // global variable. -func (o OpenDNSConfig) GetRecord(zone string, name string, recordType string) (*dns.RecordBody, error) { +func (o OpenDNSClient) GetRecord(ctx context.Context, zone string, name string, recordType string) (*dns.RecordBody, error) { + recordResponse, err := o.client.GetRecord(ctx, dns.GetRecordRequest{ + RecordType: recordType, + Name: name, + Zone: zone, + }) - dns.Config = o.config + if err != nil { + return nil, err + } - return dns.GetRecord(zone, name, recordType) + return &dns.RecordBody{ + Name: recordResponse.Name, + TTL: recordResponse.TTL, + Target: recordResponse.Target, + Active: recordResponse.Active, + RecordType: recordResponse.RecordType, + }, nil } // RecordSave is a function that saves the given zone in the given RecordBody. -func (o OpenDNSConfig) RecordSave(rec *dns.RecordBody, zone string) error { - - return rec.Save(zone) +func (o OpenDNSClient) RecordSave(ctx context.Context, rec *dns.RecordBody, zone string) error { + return o.client.CreateRecord(ctx, dns.CreateRecordRequest{ + Record: rec, + Zone: zone, + }) } // RecordUpdate is a function that updates the given zone in the given RecordBody. -func (o OpenDNSConfig) RecordUpdate(rec *dns.RecordBody, zone string) error { - - return rec.Update(zone) +func (o OpenDNSClient) RecordUpdate(ctx context.Context, rec *dns.RecordBody, zone string) error { + return o.client.UpdateRecord(ctx, dns.UpdateRecordRequest{ + Record: rec, + Zone: zone, + }) } // RecordDelete is a function that deletes the given zone in the given RecordBody. -func (o OpenDNSConfig) RecordDelete(rec *dns.RecordBody, zone string) error { - - return rec.Delete(zone) +func (o OpenDNSClient) RecordDelete(ctx context.Context, rec *dns.RecordBody, zone string) error { + return o.client.DeleteRecord(ctx, dns.DeleteRecordRequest{ + RecordType: rec.RecordType, + Name: rec.Name, + Zone: zone, + }) } diff --git a/pkg/issuer/acme/dns/akamai/akamai_test.go b/pkg/issuer/acme/dns/akamai/akamai_test.go index d37998df2d4..eed978eae42 100644 --- a/pkg/issuer/acme/dns/akamai/akamai_test.go +++ b/pkg/issuer/acme/dns/akamai/akamai_test.go @@ -17,15 +17,15 @@ limitations under the License. package akamai import ( - "testing" - + "context" "fmt" "reflect" + "testing" - dns "github.com/akamai/AkamaiOPEN-edgegrid-golang/configdns-v2" + dns "github.com/akamai/AkamaiOPEN-edgegrid-golang/v12/pkg/dns" + "github.com/stretchr/testify/assert" "github.com/cert-manager/cert-manager/pkg/issuer/acme/dns/util" - "github.com/stretchr/testify/assert" ) func testRecordBodyData() *dns.RecordBody { @@ -48,14 +48,13 @@ func testRecordBodyDataExist() *dns.RecordBody { } } -// OpenEdggrid DNS Stub +// OpenEdgegrid DNS Stub type StubOpenDNSConfig struct { FuncOutput map[string]interface{} FuncErrors map[string]error } -func findStubHostedDomainByFqdn(fqdn string, ns []string) (string, error) { - +func findStubHostedDomainByFqdn(_ context.Context, fqdn string, ns []string) (string, error) { return "test.example.com", nil } @@ -75,13 +74,13 @@ func TestNewDNSProvider(t *testing.T) { akamai, err := NewDNSProvider("akamai.example.com", "token", "secret", "access-token", util.RecursiveNameservers) assert.NoError(t, err) - // samplee couple important fields + // sample couple important fields assert.Equal(t, akamai.serviceConsumerDomain, "akamai.example.com") - assert.Equal(t, fmt.Sprintf("%T", akamai.dnsclient), "*akamai.OpenDNSConfig") + assert.Equal(t, fmt.Sprintf("%T", akamai.dnsclient), "*akamai.OpenDNSClient") } -// TestPresentBasicFlow tests basic flow, e.g. no record exists. +// TestPresentBasicFlow tests basic flow, e.g., no record exists. func TestPresentBasicFlow(t *testing.T) { akamai, err := NewDNSProvider("akamai.example.com", "token", "secret", "access-token", util.RecursiveNameservers) assert.NoError(t, err) @@ -94,7 +93,7 @@ func TestPresentBasicFlow(t *testing.T) { akamai.dnsclient.(*StubOpenDNSConfig).FuncErrors["RecordUpdate"] = fmt.Errorf("Update not expected") akamai.dnsclient.(*StubOpenDNSConfig).FuncErrors["RecordDelete"] = fmt.Errorf("Delete not expected") - assert.NoError(t, akamai.Present("test.example.com", "_acme-challenge.test.example.com.", "dns01-key")) + assert.NoError(t, akamai.Present(t.Context(), "test.example.com", "_acme-challenge.test.example.com.", "dns01-key")) } @@ -111,7 +110,7 @@ func TestPresentExists(t *testing.T) { akamai.dnsclient.(*StubOpenDNSConfig).FuncErrors["RecordSave"] = fmt.Errorf("Save not expected") akamai.dnsclient.(*StubOpenDNSConfig).FuncErrors["RecordDelete"] = fmt.Errorf("Delete not expected") - assert.NoError(t, akamai.Present("test.example.com", "_acme-challenge.test.example.com.", "dns01-key-stub")) + assert.NoError(t, akamai.Present(t.Context(), "test.example.com", "_acme-challenge.test.example.com.", "dns01-key-stub")) } @@ -128,7 +127,7 @@ func TestPresentValueExists(t *testing.T) { akamai.dnsclient.(*StubOpenDNSConfig).FuncErrors["RecordUpdate"] = fmt.Errorf("Update not expected") akamai.dnsclient.(*StubOpenDNSConfig).FuncErrors["RecordDelete"] = fmt.Errorf("Delete not expected") - assert.NoError(t, akamai.Present("test.example.com", "_acme-challenge.test.example.com.", "dns01-key")) + assert.NoError(t, akamai.Present(t.Context(), "test.example.com", "_acme-challenge.test.example.com.", "dns01-key")) } @@ -145,7 +144,7 @@ func TestPresentFailGetRecord(t *testing.T) { akamai.dnsclient.(*StubOpenDNSConfig).FuncErrors["RecordUpdate"] = fmt.Errorf("Update not expected") akamai.dnsclient.(*StubOpenDNSConfig).FuncErrors["RecordDelete"] = fmt.Errorf("Delete not expected") - assert.Error(t, akamai.Present("test.example.com", "_acme-challenge.test.example.com.", "dns01-key")) + assert.Error(t, akamai.Present(t.Context(), "test.example.com", "_acme-challenge.test.example.com.", "dns01-key")) } @@ -161,7 +160,7 @@ func TestPresentFailSaveRecord(t *testing.T) { akamai.dnsclient.(*StubOpenDNSConfig).FuncErrors["RecordUpdate"] = fmt.Errorf("Update not expected") akamai.dnsclient.(*StubOpenDNSConfig).FuncErrors["RecordDelete"] = fmt.Errorf("Delete not expected") - assert.Error(t, akamai.Present("test.example.com", "_acme-challenge.test.example.com.", "dns01-key")) + assert.Error(t, akamai.Present(t.Context(), "test.example.com", "_acme-challenge.test.example.com.", "dns01-key")) } @@ -178,7 +177,7 @@ func TestPresentFailUpdateRecord(t *testing.T) { akamai.dnsclient.(*StubOpenDNSConfig).FuncErrors["RecordUpdate"] = fmt.Errorf("Update failed") akamai.dnsclient.(*StubOpenDNSConfig).FuncErrors["RecordDelete"] = fmt.Errorf("Delete not expected") - assert.Error(t, akamai.Present("test.example.com", "_acme-challenge.test.example.com.", "dns01-key-stub")) + assert.Error(t, akamai.Present(t.Context(), "test.example.com", "_acme-challenge.test.example.com.", "dns01-key-stub")) } @@ -195,7 +194,7 @@ func TestCleanUpBasicFlow(t *testing.T) { akamai.dnsclient.(*StubOpenDNSConfig).FuncErrors["RecordSave"] = fmt.Errorf("Save not expected") akamai.dnsclient.(*StubOpenDNSConfig).FuncErrors["RecordUpdate"] = fmt.Errorf("Update not expected") - assert.NoError(t, akamai.CleanUp("test.example.com", "_acme-challenge.test.example.com.", "dns01-key")) + assert.NoError(t, akamai.CleanUp(t.Context(), "test.example.com", "_acme-challenge.test.example.com.", "dns01-key")) } @@ -212,7 +211,7 @@ func TestCleanUpExists(t *testing.T) { akamai.dnsclient.(*StubOpenDNSConfig).FuncErrors["RecordSave"] = fmt.Errorf("Save not expected") akamai.dnsclient.(*StubOpenDNSConfig).FuncErrors["RecordDelete"] = fmt.Errorf("Delete not expected") - assert.NoError(t, akamai.CleanUp("test.example.com", "_acme-challenge.test.example.com.", "dns01-key-stub")) + assert.NoError(t, akamai.CleanUp(t.Context(), "test.example.com", "_acme-challenge.test.example.com.", "dns01-key-stub")) } @@ -229,7 +228,7 @@ func TestCleanUpExistsNoValue(t *testing.T) { akamai.dnsclient.(*StubOpenDNSConfig).FuncErrors["RecordUpdate"] = fmt.Errorf("Update not expected") akamai.dnsclient.(*StubOpenDNSConfig).FuncErrors["RecordDelete"] = fmt.Errorf("Delete not expected") - assert.NoError(t, akamai.CleanUp("test.example.com", "_acme-challenge.test.example.com.", "dns01-key-stub")) + assert.NoError(t, akamai.CleanUp(t.Context(), "test.example.com", "_acme-challenge.test.example.com.", "dns01-key-stub")) } @@ -246,7 +245,7 @@ func TestCleanUpNoRecord(t *testing.T) { akamai.dnsclient.(*StubOpenDNSConfig).FuncErrors["RecordUpdate"] = fmt.Errorf("Update not expected") akamai.dnsclient.(*StubOpenDNSConfig).FuncErrors["RecordDelete"] = fmt.Errorf("Delete not expected") - assert.NoError(t, akamai.CleanUp("test.example.com", "_acme-challenge.test.example.com.", "dns01")) + assert.NoError(t, akamai.CleanUp(t.Context(), "test.example.com", "_acme-challenge.test.example.com.", "dns01")) } @@ -263,7 +262,7 @@ func TestCleanUpFailGetRecord(t *testing.T) { akamai.dnsclient.(*StubOpenDNSConfig).FuncErrors["RecordUpdate"] = fmt.Errorf("Update not expected") akamai.dnsclient.(*StubOpenDNSConfig).FuncErrors["RecordDelete"] = fmt.Errorf("Delete not expected") - assert.Error(t, akamai.CleanUp("test.example.com", "_acme-challenge.test.example.com.", "dns01-key")) + assert.Error(t, akamai.CleanUp(t.Context(), "test.example.com", "_acme-challenge.test.example.com.", "dns01-key")) } @@ -280,7 +279,7 @@ func TestCleanUpFailUpdateRecord(t *testing.T) { akamai.dnsclient.(*StubOpenDNSConfig).FuncErrors["RecordUpdate"] = fmt.Errorf("Update failed") akamai.dnsclient.(*StubOpenDNSConfig).FuncErrors["RecordDelete"] = fmt.Errorf("Delete not expected") - assert.Error(t, akamai.CleanUp("test.example.com", "_acme-challenge.test.example.com.", "dns01-key-stub")) + assert.Error(t, akamai.CleanUp(t.Context(), "test.example.com", "_acme-challenge.test.example.com.", "dns01-key-stub")) } @@ -297,12 +296,12 @@ func TestCleanUpFailDeleteRecord(t *testing.T) { akamai.dnsclient.(*StubOpenDNSConfig).FuncErrors["RecordUpdate"] = fmt.Errorf("Update not expected") akamai.dnsclient.(*StubOpenDNSConfig).FuncErrors["RecordDelete"] = fmt.Errorf("Delete failed") - assert.Error(t, akamai.CleanUp("test.example.com", "_acme-challenge.test.example.com.", "dns01-key")) + assert.Error(t, akamai.CleanUp(t.Context(), "test.example.com", "_acme-challenge.test.example.com.", "dns01-key")) } // Stub Get Record -func (o StubOpenDNSConfig) GetRecord(zone string, name string, recordType string) (*dns.RecordBody, error) { +func (o StubOpenDNSConfig) GetRecord(ctx context.Context, zone string, name string, recordType string) (*dns.RecordBody, error) { var rec *dns.RecordBody @@ -317,7 +316,7 @@ func (o StubOpenDNSConfig) GetRecord(zone string, name string, recordType string return nil, fmt.Errorf("GetRecord: Unexpected nil") } rec = exp.(*dns.RecordBody) - // comare passed with expected + // compare passed with expected if name != rec.Name { return nil, fmt.Errorf("GetRecord: expected/actual Name don't match") } @@ -330,11 +329,11 @@ func (o StubOpenDNSConfig) GetRecord(zone string, name string, recordType string } -func (o StubOpenDNSConfig) RecordSave(rec *dns.RecordBody, zone string) error { +func (o StubOpenDNSConfig) RecordSave(ctx context.Context, rec *dns.RecordBody, zone string) error { exp, ok := o.FuncOutput["RecordSave"] if ok { - // comare passed with expected + // compare passed with expected if rec.Name != exp.(*dns.RecordBody).Name { return fmt.Errorf("RecordSave: expected/actual Name don't match") } @@ -357,11 +356,11 @@ func (o StubOpenDNSConfig) RecordSave(rec *dns.RecordBody, zone string) error { } -func (o StubOpenDNSConfig) RecordUpdate(rec *dns.RecordBody, zone string) error { +func (o StubOpenDNSConfig) RecordUpdate(ctx context.Context, rec *dns.RecordBody, zone string) error { exp, ok := o.FuncOutput["RecordUpdate"] if ok { - // comare passed with expected + // compare passed with expected if rec.Name != exp.(*dns.RecordBody).Name { return fmt.Errorf("RecordUpdate: expected/actual Name don't match") } @@ -383,11 +382,11 @@ func (o StubOpenDNSConfig) RecordUpdate(rec *dns.RecordBody, zone string) error return nil } -func (o StubOpenDNSConfig) RecordDelete(rec *dns.RecordBody, zone string) error { +func (o StubOpenDNSConfig) RecordDelete(ctx context.Context, rec *dns.RecordBody, zone string) error { exp, ok := o.FuncOutput["RecordDelete"] if ok { - // comare passed with expected + // compare passed with expected if rec.Name != exp.(*dns.RecordBody).Name { return fmt.Errorf("RecordDelete: expected/actual Name don't match") } diff --git a/pkg/issuer/acme/dns/azuredns/azuredns.go b/pkg/issuer/acme/dns/azuredns/azuredns.go index f674bbccafb..00b9f075df1 100644 --- a/pkg/issuer/acme/dns/azuredns/azuredns.go +++ b/pkg/issuer/acme/dns/azuredns/azuredns.go @@ -12,17 +12,21 @@ package azuredns import ( "context" + "errors" "fmt" + "net/http" + "os" "strings" + "github.com/Azure/azure-sdk-for-go/sdk/azcore" + "github.com/Azure/azure-sdk-for-go/sdk/azcore/arm" + "github.com/Azure/azure-sdk-for-go/sdk/azcore/cloud" + "github.com/Azure/azure-sdk-for-go/sdk/azcore/policy" + "github.com/Azure/azure-sdk-for-go/sdk/azcore/to" + "github.com/Azure/azure-sdk-for-go/sdk/azidentity" + dns "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/dns/armdns" "github.com/go-logr/logr" - "github.com/Azure/azure-sdk-for-go/services/dns/mgmt/2017-10-01/dns" - "github.com/Azure/go-autorest/autorest" - "github.com/Azure/go-autorest/autorest/adal" - "github.com/Azure/go-autorest/autorest/azure" - "github.com/Azure/go-autorest/autorest/to" - cmacme "github.com/cert-manager/cert-manager/pkg/apis/acme/v1" "github.com/cert-manager/cert-manager/pkg/issuer/acme/dns/util" logf "github.com/cert-manager/cert-manager/pkg/logs" @@ -31,8 +35,8 @@ import ( // DNSProvider implements the util.ChallengeProvider interface type DNSProvider struct { dns01Nameservers []string - recordClient dns.RecordSetsClient - zoneClient dns.ZonesClient + recordClient *dns.RecordSetsClient + zoneClient *dns.ZonesClient resourceGroupName string zoneName string log logr.Logger @@ -41,25 +45,24 @@ type DNSProvider struct { // NewDNSProviderCredentials returns a DNSProvider instance configured for the Azure // DNS service using static credentials from its parameters func NewDNSProviderCredentials(environment, clientID, clientSecret, subscriptionID, tenantID, resourceGroupName, zoneName string, dns01Nameservers []string, ambient bool, managedIdentity *cmacme.AzureManagedIdentity) (*DNSProvider, error) { - env := azure.PublicCloud - if environment != "" { - var err error - env, err = azure.EnvironmentFromName(environment) - if err != nil { - return nil, err - } + cloudCfg, err := getCloudConfiguration(environment) + if err != nil { + return nil, err } - spt, err := getAuthorization(env, clientID, clientSecret, subscriptionID, tenantID, ambient, managedIdentity) + clientOpt := policy.ClientOptions{Cloud: cloudCfg} + cred, err := getAuthorization(clientOpt, clientID, clientSecret, tenantID, ambient, managedIdentity) + if err != nil { + return nil, err + } + rc, err := dns.NewRecordSetsClient(subscriptionID, cred, &arm.ClientOptions{ClientOptions: clientOpt}) + if err != nil { + return nil, err + } + zc, err := dns.NewZonesClient(subscriptionID, cred, &arm.ClientOptions{ClientOptions: clientOpt}) if err != nil { return nil, err } - - rc := dns.NewRecordSetsClientWithBaseURI(env.ResourceManagerEndpoint, subscriptionID) - rc.Authorizer = autorest.NewBearerAuthorizer(spt) - - zc := dns.NewZonesClientWithBaseURI(env.ResourceManagerEndpoint, subscriptionID) - zc.Authorizer = autorest.NewBearerAuthorizer(spt) return &DNSProvider{ dns01Nameservers: dns01Nameservers, @@ -71,122 +74,269 @@ func NewDNSProviderCredentials(environment, clientID, clientSecret, subscription }, nil } -func getAuthorization(env azure.Environment, clientID, clientSecret, subscriptionID, tenantID string, ambient bool, managedIdentity *cmacme.AzureManagedIdentity) (*adal.ServicePrincipalToken, error) { +func getCloudConfiguration(name string) (cloud.Configuration, error) { + switch strings.ToUpper(name) { + case "AZURECLOUD", "AZUREPUBLICCLOUD", "": + return cloud.AzurePublic, nil + case "AZUREUSGOVERNMENT", "AZUREUSGOVERNMENTCLOUD": + return cloud.AzureGovernment, nil + case "AZURECHINACLOUD": + return cloud.AzureChina, nil + } + return cloud.Configuration{}, fmt.Errorf("unknown cloud configuration name: %s", name) +} + +func getAuthorization(clientOpt policy.ClientOptions, clientID, clientSecret, tenantID string, ambient bool, managedIdentity *cmacme.AzureManagedIdentity) (azcore.TokenCredential, error) { if clientID != "" { logf.Log.V(logf.InfoLevel).Info("azuredns authenticating with clientID and secret key") - oauthConfig, err := adal.NewOAuthConfig(env.ActiveDirectoryEndpoint, tenantID) - if err != nil { - return nil, err - } - spt, err := adal.NewServicePrincipalToken(*oauthConfig, clientID, clientSecret, env.ResourceManagerEndpoint) + cred, err := azidentity.NewClientSecretCredential(tenantID, clientID, clientSecret, &azidentity.ClientSecretCredentialOptions{ClientOptions: clientOpt}) if err != nil { return nil, err } - return spt, nil + return cred, nil } - logf.Log.V(logf.InfoLevel).Info("No ClientID found: authenticating azuredns with managed identity (MSI)") + + logf.Log.V(logf.InfoLevel).Info("No ClientID found: attempting to authenticate with ambient credentials (Azure Workload Identity or Azure Managed Service Identity, in that order)") if !ambient { - return nil, fmt.Errorf("ClientID is not set but neither `--cluster-issuer-ambient-credentials` nor `--issuer-ambient-credentials` are set. These are necessary to enable Azure Managed Identities") + return nil, fmt.Errorf("ClientID was omitted without providing one of `--cluster-issuer-ambient-credentials` or `--issuer-ambient-credentials`. These are necessary to enable Azure Managed Identities") + } + + // Use Workload Identity if present + if os.Getenv("AZURE_FEDERATED_TOKEN_FILE") != "" { + wcOpt := &azidentity.WorkloadIdentityCredentialOptions{ + ClientOptions: clientOpt, + } + if managedIdentity != nil { + if managedIdentity.ClientID != "" { + wcOpt.ClientID = managedIdentity.ClientID + } + if managedIdentity.TenantID != "" { + wcOpt.TenantID = managedIdentity.TenantID + } + } + + return azidentity.NewWorkloadIdentityCredential(wcOpt) } - opt := adal.ManagedIdentityOptions{} + logf.Log.V(logf.InfoLevel).Info("No Azure Workload Identity found: attempting to authenticate with an Azure Managed Service Identity (MSI)") + msiOpt := &azidentity.ManagedIdentityCredentialOptions{ClientOptions: clientOpt} if managedIdentity != nil { - opt.ClientID = managedIdentity.ClientID - opt.IdentityResourceID = managedIdentity.ResourceID + if managedIdentity.ClientID != "" { + msiOpt.ID = azidentity.ClientID(managedIdentity.ClientID) + } + if managedIdentity.ResourceID != "" { + msiOpt.ID = azidentity.ResourceID(managedIdentity.ResourceID) + } } - spt, err := adal.NewServicePrincipalTokenFromManagedIdentity(env.ServiceManagementEndpoint, &opt) + cred, err := azidentity.NewManagedIdentityCredential(msiOpt) if err != nil { return nil, fmt.Errorf("failed to create the managed service identity token: %v", err) } - return spt, nil + return cred, nil } // Present creates a TXT record using the specified parameters -func (c *DNSProvider) Present(domain, fqdn, value string) error { - return c.createRecord(fqdn, value, 60) +func (c *DNSProvider) Present(ctx context.Context, domain, fqdn, value string) error { + return c.updateTXTRecord(ctx, fqdn, func(set *dns.RecordSet) { + var found bool + for _, r := range set.Properties.TxtRecords { + if len(r.Value) > 0 && *r.Value[0] == value { + found = true + break + } + } + + if !found { + set.Properties.TxtRecords = append(set.Properties.TxtRecords, &dns.TxtRecord{ + Value: []*string{to.Ptr(value)}, + }) + } + }) } // CleanUp removes the TXT record matching the specified parameters -func (c *DNSProvider) CleanUp(domain, fqdn, value string) error { - z, err := c.getHostedZoneName(fqdn) - if err != nil { - c.log.Error(err, "Error getting hosted zone name for:", fqdn) - return err - } +func (c *DNSProvider) CleanUp(ctx context.Context, domain, fqdn, value string) error { + return c.updateTXTRecord(ctx, fqdn, func(set *dns.RecordSet) { + var records []*dns.TxtRecord + for _, r := range set.Properties.TxtRecords { + if len(r.Value) > 0 && *r.Value[0] != value { + records = append(records, r) + } + } - _, err = c.recordClient.Delete( - context.TODO(), - c.resourceGroupName, - z, - c.trimFqdn(fqdn, z), - dns.TXT, "") + set.Properties.TxtRecords = records + }) +} +func (c *DNSProvider) getHostedZoneName(ctx context.Context, fqdn string) (string, error) { + if c.zoneName != "" { + return c.zoneName, nil + } + z, err := util.FindZoneByFqdn(ctx, fqdn, c.dns01Nameservers) if err != nil { - return err + return "", err } - return nil + if len(z) == 0 { + return "", fmt.Errorf("Zone %s not found for domain %s", z, fqdn) + } + + if _, err := c.zoneClient.Get(ctx, c.resourceGroupName, util.UnFqdn(z), nil); err != nil { + c.log.Error(err, "Error getting Zone for domain", "zone", z, "domain", fqdn, "resource group", c.resourceGroupName) + return "", fmt.Errorf("Zone %s not found in AzureDNS for domain %s. Err: %v", z, fqdn, stabilizeError(err)) + } + + return util.UnFqdn(z), nil } -func (c *DNSProvider) createRecord(fqdn, value string, ttl int) error { - rparams := &dns.RecordSet{ - RecordSetProperties: &dns.RecordSetProperties{ - TTL: to.Int64Ptr(int64(ttl)), - TxtRecords: &[]dns.TxtRecord{ - {Value: &[]string{value}}, - }, - }, +// Trims DNS zone from the fqdn. Defaults to DNSProvider.zoneName if it is specified. +func (c *DNSProvider) trimFqdn(fqdn string, zone string) string { + z := zone + if len(c.zoneName) > 0 { + z = c.zoneName } + return strings.TrimSuffix(strings.TrimSuffix(fqdn, "."), "."+z) +} - z, err := c.getHostedZoneName(fqdn) +// Updates or removes DNS TXT record while respecting optimistic concurrency control +func (c *DNSProvider) updateTXTRecord(ctx context.Context, fqdn string, updater func(*dns.RecordSet)) error { + zone, err := c.getHostedZoneName(ctx, fqdn) if err != nil { - c.log.Error(err, "Error getting hosted zone name for:", fqdn) return err } - _, err = c.recordClient.CreateOrUpdate( - context.TODO(), - c.resourceGroupName, - z, - c.trimFqdn(fqdn, z), - dns.TXT, - *rparams, "", "") + name := c.trimFqdn(fqdn, zone) + + var set *dns.RecordSet + resp, err := c.recordClient.Get(ctx, c.resourceGroupName, zone, name, dns.RecordTypeTXT, nil) if err != nil { - c.log.Error(err, "Error creating TXT:", z) - return err + var respErr *azcore.ResponseError + if errors.As(err, &respErr); respErr != nil && respErr.StatusCode == http.StatusNotFound { + set = &dns.RecordSet{ + Properties: &dns.RecordSetProperties{ + TTL: to.Ptr(int64(60)), + TxtRecords: []*dns.TxtRecord{}, + }, + Etag: to.Ptr(""), + } + } else { + c.log.Error(err, "Error reading TXT", "zone", zone, "domain", fqdn, "resource group", c.resourceGroupName) + return stabilizeError(err) + } + } else { + set = &resp.RecordSet } - return nil -} -func (c *DNSProvider) getHostedZoneName(fqdn string) (string, error) { - if c.zoneName != "" { - return c.zoneName, nil + updater(set) + + if len(set.Properties.TxtRecords) == 0 { + if *set.Etag != "" { + // Etag will cause the deletion to fail if any updates happen concurrently + _, err = c.recordClient.Delete(ctx, c.resourceGroupName, zone, name, dns.RecordTypeTXT, &dns.RecordSetsClientDeleteOptions{IfMatch: set.Etag}) + if err != nil { + c.log.Error(err, "Error deleting TXT", "zone", zone, "domain", fqdn, "resource group", c.resourceGroupName) + return stabilizeError(err) + } + } + + return nil } - z, err := util.FindZoneByFqdn(fqdn, c.dns01Nameservers) - if err != nil { - return "", err + + opts := &dns.RecordSetsClientCreateOrUpdateOptions{} + if *set.Etag == "" { + // This is used to indicate that we want the API call to fail if a conflicting record was created concurrently + // Only relevant when this is a new record, for updates conflicts are solved with Etag + opts.IfNoneMatch = to.Ptr("*") + } else { + opts.IfMatch = set.Etag } - if len(z) == 0 { - return "", fmt.Errorf("Zone %s not found for domain %s", z, fqdn) + _, err = c.recordClient.CreateOrUpdate( + ctx, + c.resourceGroupName, + zone, + name, + dns.RecordTypeTXT, + *set, + opts) + if err != nil { + c.log.Error(err, "Error upserting TXT", "zone", zone, "domain", fqdn, "resource group", c.resourceGroupName) + return stabilizeError(err) } - _, err = c.zoneClient.Get(context.TODO(), c.resourceGroupName, util.UnFqdn(z)) + return nil +} - if err != nil { - return "", fmt.Errorf("Zone %s not found in AzureDNS for domain %s. Err: %v", z, fqdn, err) +// The azure-sdk library returns the contents of the HTTP requests in its +// error messages. We want our error messages to be the same when the cause +// is the same to avoid spurious challenge updates. +// +// The given error must not be nil. This function must be called everywhere +// we have a non-nil error coming from an azure-sdk func that makes API calls. +func stabilizeError(err error) error { + if err == nil { + return nil } - return util.UnFqdn(z), nil + return NormalizedError{ + Cause: err, + } } -// Trims DNS zone from the fqdn. Defaults to DNSProvider.zoneName if it is specified. -func (c *DNSProvider) trimFqdn(fqdn string, zone string) string { - z := zone - if len(c.zoneName) > 0 { - z = c.zoneName +type NormalizedError struct { + Cause error +} + +func (e NormalizedError) Error() string { + var ( + authErr *azidentity.AuthenticationFailedError + respErr *azcore.ResponseError + ) + + switch { + case errors.As(e.Cause, &authErr): + msg := new(strings.Builder) + fmt.Fprintln(msg, "authentication failed:") + + if authErr.RawResponse != nil { + if authErr.RawResponse.Request != nil { + fmt.Fprintf(msg, "%s %s://%s%s\n", authErr.RawResponse.Request.Method, authErr.RawResponse.Request.URL.Scheme, authErr.RawResponse.Request.URL.Host, authErr.RawResponse.Request.URL.Path) + } + + fmt.Fprintln(msg, "--------------------------------------------------------------------------------") + fmt.Fprintf(msg, "RESPONSE %s\n", authErr.RawResponse.Status) + fmt.Fprintln(msg, "--------------------------------------------------------------------------------") + } + + fmt.Fprint(msg, "see logs for more information") + + return msg.String() + case errors.As(e.Cause, &respErr): + msg := new(strings.Builder) + fmt.Fprintln(msg, "request error:") + + if respErr.RawResponse != nil { + if respErr.RawResponse.Request != nil { + fmt.Fprintf(msg, "%s %s://%s%s\n", respErr.RawResponse.Request.Method, respErr.RawResponse.Request.URL.Scheme, respErr.RawResponse.Request.URL.Host, respErr.RawResponse.Request.URL.Path) + } + + fmt.Fprintln(msg, "--------------------------------------------------------------------------------") + fmt.Fprintf(msg, "RESPONSE %s\n", respErr.RawResponse.Status) + if respErr.ErrorCode != "" { + fmt.Fprintf(msg, "ERROR CODE: %s\n", respErr.ErrorCode) + } else { + fmt.Fprintln(msg, "ERROR CODE UNAVAILABLE") + } + fmt.Fprintln(msg, "--------------------------------------------------------------------------------") + } + + fmt.Fprint(msg, "see logs for more information") + + return msg.String() + + default: + return e.Cause.Error() } - return strings.TrimSuffix(strings.TrimSuffix(fqdn, "."), "."+z) } diff --git a/pkg/issuer/acme/dns/azuredns/azuredns_test.go b/pkg/issuer/acme/dns/azuredns/azuredns_test.go index 50be548551a..e75aa3f893f 100644 --- a/pkg/issuer/acme/dns/azuredns/azuredns_test.go +++ b/pkg/issuer/acme/dns/azuredns/azuredns_test.go @@ -9,13 +9,28 @@ this directory. package azuredns import ( + "encoding/json" + "fmt" + "io" + "net/http" + "net/http/httptest" "os" + "reflect" + "strings" "testing" "time" + "github.com/Azure/azure-sdk-for-go/sdk/azcore/arm" + "github.com/Azure/azure-sdk-for-go/sdk/azcore/cloud" + "github.com/Azure/azure-sdk-for-go/sdk/azcore/policy" + "github.com/Azure/azure-sdk-for-go/sdk/azidentity" + dns "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/dns/armdns" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "k8s.io/apimachinery/pkg/util/rand" + v1 "github.com/cert-manager/cert-manager/pkg/apis/acme/v1" "github.com/cert-manager/cert-manager/pkg/issuer/acme/dns/util" - "github.com/stretchr/testify/assert" ) var ( @@ -49,7 +64,20 @@ func TestLiveAzureDnsPresent(t *testing.T) { provider, err := NewDNSProviderCredentials("", azureClientID, azureClientSecret, azuresubscriptionID, azureTenantID, azureResourceGroupName, azureHostedZoneName, util.RecursiveNameservers, false, &v1.AzureManagedIdentity{}) assert.NoError(t, err) - err = provider.Present(azureDomain, "_acme-challenge."+azureDomain+".", "123d==") + err = provider.Present(t.Context(), azureDomain, "_acme-challenge."+azureDomain+".", "123d==") + assert.NoError(t, err) +} + +func TestLiveAzureDnsPresentMultiple(t *testing.T) { + if !azureLiveTest { + t.Skip("skipping live test") + } + provider, err := NewDNSProviderCredentials("", azureClientID, azureClientSecret, azuresubscriptionID, azureTenantID, azureResourceGroupName, azureHostedZoneName, util.RecursiveNameservers, false, &v1.AzureManagedIdentity{}) + assert.NoError(t, err) + + err = provider.Present(t.Context(), azureDomain, "_acme-challenge."+azureDomain+".", "123d==") + assert.NoError(t, err) + err = provider.Present(t.Context(), azureDomain, "_acme-challenge."+azureDomain+".", "1123d==") assert.NoError(t, err) } @@ -63,17 +91,342 @@ func TestLiveAzureDnsCleanUp(t *testing.T) { provider, err := NewDNSProviderCredentials("", azureClientID, azureClientSecret, azuresubscriptionID, azureTenantID, azureResourceGroupName, azureHostedZoneName, util.RecursiveNameservers, false, &v1.AzureManagedIdentity{}) assert.NoError(t, err) - err = provider.CleanUp(azureDomain, "_acme-challenge."+azureDomain+".", "123d==") + err = provider.CleanUp(t.Context(), azureDomain, "_acme-challenge."+azureDomain+".", "123d==") + assert.NoError(t, err) +} + +func TestLiveAzureDnsCleanUpMultiple(t *testing.T) { + if !azureLiveTest { + t.Skip("skipping live test") + } + + time.Sleep(time.Second * 10) + + provider, err := NewDNSProviderCredentials("", azureClientID, azureClientSecret, azuresubscriptionID, azureTenantID, azureResourceGroupName, azureHostedZoneName, util.RecursiveNameservers, false, &v1.AzureManagedIdentity{}) + assert.NoError(t, err) + + err = provider.CleanUp(t.Context(), azureDomain, "_acme-challenge."+azureDomain+".", "123d==") + assert.NoError(t, err) + err = provider.CleanUp(t.Context(), azureDomain, "_acme-challenge."+azureDomain+".", "1123d==") assert.NoError(t, err) } func TestInvalidAzureDns(t *testing.T) { - validEnv := []string{"", "AzurePublicCloud", "AzureChinaCloud", "AzureGermanCloud", "AzureUSGovernmentCloud"} + validEnv := []string{"", "AzurePublicCloud", "AzureChinaCloud", "AzureUSGovernmentCloud"} for _, env := range validEnv { - _, err := NewDNSProviderCredentials(env, "cid", "secret", "", "", "", "", util.RecursiveNameservers, false, &v1.AzureManagedIdentity{}) + _, err := NewDNSProviderCredentials(env, "cid", "secret", "", "tenid", "", "", util.RecursiveNameservers, false, &v1.AzureManagedIdentity{}) assert.NoError(t, err) } - _, err := NewDNSProviderCredentials("invalid env", "cid", "secret", "", "", "", "", util.RecursiveNameservers, false, &v1.AzureManagedIdentity{}) + // Invalid environment + _, err := NewDNSProviderCredentials("invalid env", "cid", "secret", "", "tenid", "", "", util.RecursiveNameservers, false, &v1.AzureManagedIdentity{}) assert.Error(t, err) + + // Invalid tenantID + _, err = NewDNSProviderCredentials("", "cid", "secret", "", "invalid env value", "", "", util.RecursiveNameservers, false, &v1.AzureManagedIdentity{}) + assert.Error(t, err) +} + +func TestAuthenticationError(t *testing.T) { + provider, err := NewDNSProviderCredentials("", "invalid-client-id", "invalid-client-secret", "subid", "tenid", "rg", "example.com", util.RecursiveNameservers, false, &v1.AzureManagedIdentity{}) + assert.NoError(t, err) + + err = provider.Present(t.Context(), "example.com", "_acme-challenge.example.com.", "123d==") + assert.Error(t, err) + + err = provider.CleanUp(t.Context(), "example.com", "_acme-challenge.example.com.", "123d==") + assert.Error(t, err) +} + +func populateFederatedToken(t *testing.T, filename string, content string) { + t.Helper() + + f, err := os.Create(filename) + if err != nil { + assert.FailNow(t, err.Error()) + } + + if _, err := io.WriteString(f, content); err != nil { + assert.FailNow(t, err.Error()) + } + + if err := f.Close(); err != nil { + assert.FailNow(t, err.Error()) + } +} + +func TestGetAuthorizationFederatedSPT(t *testing.T) { + // Create a file that will be used to store a federated token + f, err := os.CreateTemp(t.TempDir(), "") + if err != nil { + assert.FailNow(t, err.Error()) + } + + // Close the file to simplify logic within populateFederatedToken helper + if err := f.Close(); err != nil { + assert.FailNow(t, err.Error()) + } + + // The initial federated token is never used, so we don't care about the value yet + // Though, it's a requirement from adal to have a non-empty value set + populateFederatedToken(t, f.Name(), "random-jwt") + + // Prepare environment variables adal will rely on. Skip changes for some envs if they are already defined (=live environment) + // Envs themselves are described here: https://azure.github.io/azure-workload-identity/docs/installation/mutating-admission-webhook.html + if os.Getenv("AZURE_TENANT_ID") == "" { + // TODO(wallrj): This is a hack. It is a quick way to `DisableInstanceDiscovery` during tests, + // to avoid the client attempting to connect to https://login.microsoftonline.com/common/discovery/instance. + // It works because there is a special case in azure-sdk-for-go which + // disables the instance discovery when the tenant ID is `adfs`. See: + // https://github.com/Azure/azure-sdk-for-go/blob/7288bda422654bde520a09034dd755b8f2dd4168/sdk/azidentity/public_client.go#L237-L239 + // https://learn.microsoft.com/en-us/windows-server/identity/ad-fs/ad-fs-overview + // + // Find a better way to test this code. + t.Setenv("AZURE_TENANT_ID", "adfs") + } + + if os.Getenv("AZURE_CLIENT_ID") == "" { + t.Setenv("AZURE_CLIENT_ID", "fakeClientID") + } + + t.Setenv("AZURE_FEDERATED_TOKEN_FILE", f.Name()) + + t.Run("token refresh", func(t *testing.T) { + // Basically, we want one token to be exchanged for the other (key and value respectively) + tokens := map[string]string{ + "initialFederatedToken": "initialAccessToken", + "refreshedFederatedToken": "refreshedAccessToken", + } + + ts := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if strings.HasSuffix(r.URL.Path, "/.well-known/openid-configuration") { + tenantURL := strings.TrimSuffix("https://"+r.Host+r.URL.Path, "/.well-known/openid-configuration") + + w.Header().Set("Content-Type", "application/json") + openidConfiguration := map[string]string{ + "token_endpoint": tenantURL + "/oauth2/token", + "authorization_endpoint": tenantURL + "/oauth2/authorize", + "issuer": tenantURL + "/adfs/", + } + + if err := json.NewEncoder(w).Encode(openidConfiguration); err != nil { + assert.FailNow(t, err.Error()) + } + + return + } + + if err := r.ParseForm(); err != nil { + assert.FailNow(t, err.Error()) + } + + w.Header().Set("Content-Type", "application/json") + receivedFederatedToken := r.FormValue("client_assertion") + accessToken := map[string]any{ + "access_token": tokens[receivedFederatedToken], + // the Azure SDK will not use tokens that are within 5 minutes of their expiration + // so "expires_on": time.Now().Add(4 * time.Minute) would work too + "expires_on": time.Now().Add(-1 * time.Second), + } + + if err := json.NewEncoder(w).Encode(accessToken); err != nil { + assert.FailNow(t, err.Error()) + } + + // Expected format: http:////oauth2/token?api-version=1.0 + assert.Contains(t, r.RequestURI, strings.ToLower(os.Getenv("AZURE_TENANT_ID")), "URI should contain the tenant ID exposed through env variable") + + assert.Equal(t, os.Getenv("AZURE_CLIENT_ID"), r.FormValue("client_id"), "client_id should match the value exposed through env variable") + })) + defer ts.Close() + + ambient := true + clientOpt := policy.ClientOptions{ + Cloud: cloud.Configuration{ActiveDirectoryAuthorityHost: ts.URL}, + Transport: ts.Client(), + } + managedIdentity := &v1.AzureManagedIdentity{ClientID: ""} + + spt, err := getAuthorization(clientOpt, "", "", "", ambient, managedIdentity) + assert.NoError(t, err) + + for federatedToken, accessToken := range tokens { + populateFederatedToken(t, f.Name(), federatedToken) + token, err := spt.GetToken(t.Context(), policy.TokenRequestOptions{Scopes: []string{"test"}}) + assert.NoError(t, err) + assert.Equal(t, accessToken, token.Token, "Access token should have been set to a value returned by the webserver") + + // Overwrite the expires field to force the token to be re-read from disk. + // Also, we set expires_on such that the token we got from the API has expired + // already too. + expiresField := reflect. + ValueOf(spt.(*azidentity.WorkloadIdentityCredential)). + Elem(). + FieldByName("expires") + reflect. + NewAt(expiresField.Type(), expiresField.Addr().UnsafePointer()). + Elem(). + Set(reflect.ValueOf(time.Now().Add(-1 * time.Second))) + } + }) + + t.Run("clientID overrides through managedIdentity section", func(t *testing.T) { + managedIdentity := &v1.AzureManagedIdentity{ClientID: "anotherClientID"} + + ts := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if strings.HasSuffix(r.URL.Path, "/.well-known/openid-configuration") { + tenantURL := strings.TrimSuffix("https://"+r.Host+r.URL.Path, "/.well-known/openid-configuration") + + w.Header().Set("Content-Type", "application/json") + openidConfiguration := map[string]string{ + "token_endpoint": tenantURL + "/oauth2/token", + "authorization_endpoint": tenantURL + "/oauth2/authorize", + "issuer": tenantURL + "/adfs/", + } + + if err := json.NewEncoder(w).Encode(openidConfiguration); err != nil { + assert.FailNow(t, err.Error()) + } + + return + } + + if err := r.ParseForm(); err != nil { + assert.FailNow(t, err.Error()) + } + + w.Header().Set("Content-Type", "application/json") + accessToken := map[string]any{ + "access_token": "abc", + "expires_in": 500, + } + + if err := json.NewEncoder(w).Encode(accessToken); err != nil { + assert.FailNow(t, err.Error()) + } + + assert.Equal(t, managedIdentity.ClientID, r.FormValue("client_id"), "client_id should match the value passed through managedIdentity section") + + w.WriteHeader(http.StatusOK) + })) + defer ts.Close() + + ambient := true + clientOpt := policy.ClientOptions{ + Cloud: cloud.Configuration{ActiveDirectoryAuthorityHost: ts.URL}, + Transport: ts.Client(), + } + + spt, err := getAuthorization(clientOpt, "", "", "", ambient, managedIdentity) + assert.NoError(t, err) + + token, err := spt.GetToken(t.Context(), policy.TokenRequestOptions{Scopes: []string{"test"}}) + assert.NoError(t, err) + assert.NotEmpty(t, token.Token, "Access token should have been set to a value returned by the webserver") + }) + + // This test tests the stabilizeError function, it makes sure that authentication errors + // are also made stable. We want our error messages to be the same when the cause + // is the same to avoid spurious challenge updates. + // Specifically, this test makes sure that the errors of type AuthenticationFailedError + // are made stable. These errors are returned by the recordClient and zoneClient when + // they fail to authenticate. We simulate this by calling the GetToken function and + // returning a 502 Bad Gateway error. + t.Run("errors should be made stable", func(t *testing.T) { + managedIdentity := &v1.AzureManagedIdentity{ClientID: "anotherClientID"} + + ts := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if strings.HasSuffix(r.URL.Path, "/.well-known/openid-configuration") { + tenantURL := strings.TrimSuffix("https://"+r.Host+r.URL.Path, "/.well-known/openid-configuration") + + w.Header().Set("Content-Type", "application/json") + openidConfiguration := map[string]string{ + "token_endpoint": tenantURL + "/oauth2/token", + "authorization_endpoint": tenantURL + "/oauth2/authorize", + "issuer": tenantURL + "/adfs/", + } + + if err := json.NewEncoder(w).Encode(openidConfiguration); err != nil { + assert.FailNow(t, err.Error()) + } + + return + } + + w.WriteHeader(http.StatusBadGateway) + randomMessage := "test error message: " + rand.String(10) + payload := fmt.Sprintf(`{"error":{"code":"TEST_ERROR_CODE","message":"%s"}}`, randomMessage) + if _, err := w.Write([]byte(payload)); err != nil { + assert.FailNow(t, err.Error()) + } + })) + defer ts.Close() + + ambient := true + clientOpt := policy.ClientOptions{ + Cloud: cloud.Configuration{ActiveDirectoryAuthorityHost: ts.URL}, + Transport: ts.Client(), + } + + spt, err := getAuthorization(clientOpt, "", "", "", ambient, managedIdentity) + assert.NoError(t, err) + + _, err = spt.GetToken(t.Context(), policy.TokenRequestOptions{Scopes: []string{"test"}}) + err = stabilizeError(err) + assert.Error(t, err) + assert.ErrorContains(t, err, fmt.Sprintf(`authentication failed: +POST %s/adfs/oauth2/token +-------------------------------------------------------------------------------- +RESPONSE 502 Bad Gateway +-------------------------------------------------------------------------------- +see logs for more information`, ts.URL)) + }) +} + +// TestStabilizeResponseError tests that the ResponseError errors returned by the AzureDNS API are +// changed to be stable. We want our error messages to be the same when the cause +// is the same to avoid spurious challenge updates. +func TestStabilizeResponseError(t *testing.T) { + ts := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusBadGateway) + randomMessage := "test error message: " + rand.String(10) + payload := fmt.Sprintf(`{"error":{"code":"TEST_ERROR_CODE","message":"%s"}}`, randomMessage) + if _, err := w.Write([]byte(payload)); err != nil { + assert.FailNow(t, err.Error()) + } + })) + + defer ts.Close() + + clientOpt := policy.ClientOptions{ + Cloud: cloud.Configuration{ + ActiveDirectoryAuthorityHost: ts.URL, + Services: map[cloud.ServiceName]cloud.ServiceConfiguration{ + cloud.ResourceManager: { + Audience: ts.URL, + Endpoint: ts.URL, + }, + }, + }, + Transport: ts.Client(), + } + + zc, err := dns.NewZonesClient("subscriptionID", nil, &arm.ClientOptions{ClientOptions: clientOpt}) + require.NoError(t, err) + + dnsProvider := DNSProvider{ + dns01Nameservers: util.RecursiveNameservers, + resourceGroupName: "resourceGroupName", + zoneClient: zc, + } + + err = dnsProvider.Present(t.Context(), "test.com", "fqdn.test.com.", "test123") + require.Error(t, err) + require.ErrorContains(t, err, fmt.Sprintf(`Zone test.com. not found in AzureDNS for domain fqdn.test.com.. Err: request error: +GET %s/subscriptions/subscriptionID/resourceGroups/resourceGroupName/providers/Microsoft.Network/dnsZones/test.com +-------------------------------------------------------------------------------- +RESPONSE 502 Bad Gateway +ERROR CODE: TEST_ERROR_CODE +-------------------------------------------------------------------------------- +see logs for more information`, ts.URL)) } diff --git a/pkg/issuer/acme/dns/clouddns/clouddns.go b/pkg/issuer/acme/dns/clouddns/clouddns.go index 3f9bfaf4a7f..60e85aa2bb9 100644 --- a/pkg/issuer/acme/dns/clouddns/clouddns.go +++ b/pkg/issuer/acme/dns/clouddns/clouddns.go @@ -14,17 +14,16 @@ import ( "context" "fmt" "os" + "strings" "time" - logf "github.com/cert-manager/cert-manager/pkg/logs" - "github.com/go-logr/logr" - "golang.org/x/oauth2/google" "google.golang.org/api/dns/v1" "google.golang.org/api/option" "github.com/cert-manager/cert-manager/pkg/issuer/acme/dns/util" + logf "github.com/cert-manager/cert-manager/pkg/logs" ) // DNSProvider is an implementation of the DNSProvider interface. @@ -37,7 +36,7 @@ type DNSProvider struct { } // NewDNSProvider returns a new DNSProvider Instance with configuration -func NewDNSProvider(project string, saBytes []byte, dns01Nameservers []string, ambient bool, hostedZoneName string) (*DNSProvider, error) { +func NewDNSProvider(ctx context.Context, project string, saBytes []byte, dns01Nameservers []string, ambient bool, hostedZoneName string) (*DNSProvider, error) { // project is a required field if project == "" { return nil, fmt.Errorf("Google Cloud project name missing") @@ -48,11 +47,11 @@ func NewDNSProvider(project string, saBytes []byte, dns01Nameservers []string, a if !ambient { return nil, fmt.Errorf("unable to construct clouddns provider: empty credentials; perhaps you meant to enable ambient credentials?") } - return NewDNSProviderCredentials(project, dns01Nameservers, hostedZoneName) + return NewDNSProviderCredentials(ctx, project, dns01Nameservers, hostedZoneName) } // if service account data is provided, we instantiate using that if len(saBytes) != 0 { - return NewDNSProviderServiceAccountBytes(project, saBytes, dns01Nameservers, hostedZoneName) + return NewDNSProviderServiceAccountBytes(ctx, project, saBytes, dns01Nameservers, hostedZoneName) } return nil, fmt.Errorf("missing Google Cloud DNS provider credentials") } @@ -61,30 +60,31 @@ func NewDNSProvider(project string, saBytes []byte, dns01Nameservers []string, a // DNS. Project name must be passed in the environment variable: GCE_PROJECT. // A Service Account file can be passed in the environment variable: // GCE_SERVICE_ACCOUNT_FILE -func NewDNSProviderEnvironment(dns01Nameservers []string, hostedZoneName string) (*DNSProvider, error) { +func NewDNSProviderEnvironment(ctx context.Context, dns01Nameservers []string, hostedZoneName string) (*DNSProvider, error) { project := os.Getenv("GCE_PROJECT") if saFile, ok := os.LookupEnv("GCE_SERVICE_ACCOUNT_FILE"); ok { - return NewDNSProviderServiceAccount(project, saFile, dns01Nameservers, hostedZoneName) + return NewDNSProviderServiceAccount(ctx, project, saFile, dns01Nameservers, hostedZoneName) } - return NewDNSProviderCredentials(project, dns01Nameservers, hostedZoneName) + return NewDNSProviderCredentials(ctx, project, dns01Nameservers, hostedZoneName) } // NewDNSProviderCredentials uses the supplied credentials to return a // DNSProvider instance configured for Google Cloud DNS. -func NewDNSProviderCredentials(project string, dns01Nameservers []string, hostedZoneName string) (*DNSProvider, error) { +func NewDNSProviderCredentials(ctx context.Context, project string, dns01Nameservers []string, hostedZoneName string) (*DNSProvider, error) { if project == "" { return nil, fmt.Errorf("Google Cloud project name missing") } - ctx := context.Background() client, err := google.DefaultClient(ctx, dns.NdevClouddnsReadwriteScope) if err != nil { return nil, fmt.Errorf("Unable to get Google Cloud client: %v", err) } + svc, err := dns.NewService(ctx, option.WithHTTPClient(client)) if err != nil { return nil, fmt.Errorf("Unable to create Google Cloud DNS service: %v", err) } + return &DNSProvider{ project: project, client: svc, @@ -96,7 +96,7 @@ func NewDNSProviderCredentials(project string, dns01Nameservers []string, hosted // NewDNSProviderServiceAccount uses the supplied service account JSON file to // return a DNSProvider instance configured for Google Cloud DNS. -func NewDNSProviderServiceAccount(project string, saFile string, dns01Nameservers []string, hostedZoneName string) (*DNSProvider, error) { +func NewDNSProviderServiceAccount(ctx context.Context, project string, saFile string, dns01Nameservers []string, hostedZoneName string) (*DNSProvider, error) { if project == "" { return nil, fmt.Errorf("Google Cloud project name missing") } @@ -108,12 +108,12 @@ func NewDNSProviderServiceAccount(project string, saFile string, dns01Nameserver if err != nil { return nil, fmt.Errorf("Unable to read Service Account file: %v", err) } - return NewDNSProviderServiceAccountBytes(project, dat, dns01Nameservers, hostedZoneName) + return NewDNSProviderServiceAccountBytes(ctx, project, dat, dns01Nameservers, hostedZoneName) } // NewDNSProviderServiceAccountBytes uses the supplied service account JSON // file data to return a DNSProvider instance configured for Google Cloud DNS. -func NewDNSProviderServiceAccountBytes(project string, saBytes []byte, dns01Nameservers []string, hostedZoneName string) (*DNSProvider, error) { +func NewDNSProviderServiceAccountBytes(ctx context.Context, project string, saBytes []byte, dns01Nameservers []string, hostedZoneName string) (*DNSProvider, error) { if project == "" { return nil, fmt.Errorf("Google Cloud project name missing") } @@ -126,7 +126,6 @@ func NewDNSProviderServiceAccountBytes(project string, saBytes []byte, dns01Name return nil, fmt.Errorf("Unable to acquire config: %v", err) } - ctx := context.Background() client := conf.Client(ctx) svc, err := dns.NewService(ctx, option.WithHTTPClient(client)) @@ -143,8 +142,8 @@ func NewDNSProviderServiceAccountBytes(project string, saBytes []byte, dns01Name } // Present creates a TXT record to fulfil the dns-01 challenge. -func (c *DNSProvider) Present(domain, fqdn, value string) error { - zone, err := c.getHostedZone(fqdn) +func (c *DNSProvider) Present(ctx context.Context, domain, fqdn, value string) error { + zone, err := c.getHostedZone(ctx, fqdn) if err != nil { return err } @@ -155,21 +154,38 @@ func (c *DNSProvider) Present(domain, fqdn, value string) error { Ttl: int64(60), Type: "TXT", } - change := &dns.Change{ - Additions: []*dns.ResourceRecordSet{rec}, - } + change := &dns.Change{} // Look for existing records. - list, err := c.client.ResourceRecordSets.List(c.project, zone).Name(fqdn).Type("TXT").Do() + list, err := c.client.ResourceRecordSets. + List(c.project, zone). + Name(fqdn). + Type("TXT"). + Context(ctx). + Do() if err != nil { return err } if len(list.Rrsets) > 0 { - // Attempt to delete the existing records when adding our new one. + // Merge the existing RR Data into the new one, requires a delete and an add operation, or it will fail. + // The operations are applied atomically to the zone, so there is no point in time where the entire TXT record is deleted. + // Reference; https://cloud.google.com/dns/docs/reference/v1/changes change.Deletions = list.Rrsets + for _, r := range list.Rrsets { + if r.Type == "TXT" && r.Name == fqdn { + // Check if record is already present + for _, s := range r.Rrdatas { + if strings.Trim(s, "\"") == value { + return nil + } + } + rec.Rrdatas = append(rec.Rrdatas, r.Rrdatas...) + } + } } + change.Additions = []*dns.ResourceRecordSet{rec} - chg, err := c.client.Changes.Create(c.project, zone, change).Do() + chg, err := c.client.Changes.Create(c.project, zone, change).Context(ctx).Do() if err != nil { return err } @@ -178,7 +194,7 @@ func (c *DNSProvider) Present(domain, fqdn, value string) error { for chg.Status == "pending" { time.Sleep(time.Second) - chg, err = c.client.Changes.Get(c.project, zone, chg.Id).Do() + chg, err = c.client.Changes.Get(c.project, zone, chg.Id).Context(ctx).Do() if err != nil { return err } @@ -188,13 +204,13 @@ func (c *DNSProvider) Present(domain, fqdn, value string) error { } // CleanUp removes the TXT record matching the specified parameters. -func (c *DNSProvider) CleanUp(domain, fqdn, value string) error { - zone, err := c.getHostedZone(fqdn) +func (c *DNSProvider) CleanUp(ctx context.Context, domain, fqdn, value string) error { + zone, err := c.getHostedZone(ctx, fqdn) if err != nil { return err } - records, err := c.findTxtRecords(zone, fqdn) + records, err := c.findTxtRecords(ctx, zone, fqdn, value) if err != nil { return err } @@ -203,7 +219,20 @@ func (c *DNSProvider) CleanUp(domain, fqdn, value string) error { change := &dns.Change{ Deletions: []*dns.ResourceRecordSet{rec}, } - _, err = c.client.Changes.Create(c.project, zone, change).Do() + // If more than our rrdata, then filter it out but keep the rest + // Like in the Present() call, to keep the other rrdata we must delete, and re-add it, in one atomic operation. + if len(rec.Rrdatas) > 1 { + filtered := new(dns.ResourceRecordSet) + *filtered = *rec // shallow copy + filtered.Rrdatas = make([]string, 0, len(rec.Rrdatas)) + for _, r := range rec.Rrdatas { + if strings.Trim(r, "\"") != value { + filtered.Rrdatas = append(filtered.Rrdatas, r) + } + } + change.Additions = []*dns.ResourceRecordSet{filtered} + } + _, err = c.client.Changes.Create(c.project, zone, change).Context(ctx).Do() if err != nil { return err } @@ -212,12 +241,12 @@ func (c *DNSProvider) CleanUp(domain, fqdn, value string) error { } // getHostedZone returns the managed-zone -func (c *DNSProvider) getHostedZone(domain string) (string, error) { +func (c *DNSProvider) getHostedZone(ctx context.Context, domain string) (string, error) { if c.hostedZoneName != "" { return c.hostedZoneName, nil } - authZone, err := util.FindZoneByFqdn(util.ToFqdn(domain), c.dns01Nameservers) + authZone, err := util.FindZoneByFqdn(ctx, util.ToFqdn(domain), c.dns01Nameservers) if err != nil { return "", err } @@ -225,6 +254,7 @@ func (c *DNSProvider) getHostedZone(domain string) (string, error) { zones, err := c.client.ManagedZones. List(c.project). DnsName(authZone). + Context(ctx). Do() if err != nil { return "", fmt.Errorf("GoogleCloud API call failed: %v", err) @@ -241,21 +271,30 @@ func (c *DNSProvider) getHostedZone(domain string) (string, error) { } } - c.log.V(logf.DebugLevel).Info("No matching public GoogleCloud managed-zone for domain, falling back to a private managed-zone", authZone) + c.log.V(logf.DebugLevel).Info("No matching public GoogleCloud managed-zone for domain, falling back to a private managed-zone", "authZone", authZone) // fall back to first available zone, if none public return zones.ManagedZones[0].Name, nil } -func (c *DNSProvider) findTxtRecords(zone, fqdn string) ([]*dns.ResourceRecordSet, error) { - recs, err := c.client.ResourceRecordSets.List(c.project, zone).Do() +func (c *DNSProvider) findTxtRecords(ctx context.Context, zone, fqdn, value string) ([]*dns.ResourceRecordSet, error) { + recs, err := c.client.ResourceRecordSets. + List(c.project, zone). + Context(ctx). + Do() if err != nil { return nil, err } found := []*dns.ResourceRecordSet{} +RecLoop: for _, r := range recs.Rrsets { if r.Type == "TXT" && r.Name == fqdn { - found = append(found, r) + for _, s := range r.Rrdatas { + if strings.Trim(s, "\"") == value { + found = append(found, r) + continue RecLoop + } + } } } diff --git a/pkg/issuer/acme/dns/clouddns/clouddns_test.go b/pkg/issuer/acme/dns/clouddns/clouddns_test.go index 1745d0ab852..1e3af41a271 100644 --- a/pkg/issuer/acme/dns/clouddns/clouddns_test.go +++ b/pkg/issuer/acme/dns/clouddns/clouddns_test.go @@ -14,11 +14,11 @@ import ( "testing" "time" + "github.com/stretchr/testify/assert" "golang.org/x/oauth2/google" "google.golang.org/api/dns/v1" "github.com/cert-manager/cert-manager/pkg/issuer/acme/dns/util" - "github.com/stretchr/testify/assert" ) var ( @@ -36,35 +36,28 @@ func init() { } } -func restoreGCloudEnv() { - os.Setenv("GCE_PROJECT", gcloudProject) -} - func TestNewDNSProviderValid(t *testing.T) { if !gcloudLiveTest { t.Skip("skipping live test (requires credentials)") } - os.Setenv("GCE_PROJECT", "") - _, err := NewDNSProviderCredentials("my-project", util.RecursiveNameservers, "") + t.Setenv("GCE_PROJECT", "") + _, err := NewDNSProviderCredentials(t.Context(), "my-project", util.RecursiveNameservers, "") assert.NoError(t, err) - restoreGCloudEnv() } func TestNewDNSProviderValidEnv(t *testing.T) { if !gcloudLiveTest { t.Skip("skipping live test (requires credentials)") } - os.Setenv("GCE_PROJECT", "my-project") - _, err := NewDNSProviderEnvironment(util.RecursiveNameservers, "") + t.Setenv("GCE_PROJECT", "my-project") + _, err := NewDNSProviderEnvironment(t.Context(), util.RecursiveNameservers, "") assert.NoError(t, err) - restoreGCloudEnv() } func TestNewDNSProviderMissingCredErr(t *testing.T) { - os.Setenv("GCE_PROJECT", "") - _, err := NewDNSProviderEnvironment(util.RecursiveNameservers, "") + t.Setenv("GCE_PROJECT", "") + _, err := NewDNSProviderEnvironment(t.Context(), util.RecursiveNameservers, "") assert.EqualError(t, err, "Google Cloud project name missing") - restoreGCloudEnv() } func TestLiveGoogleCloudPresent(t *testing.T) { @@ -72,10 +65,10 @@ func TestLiveGoogleCloudPresent(t *testing.T) { t.Skip("skipping live test") } - provider, err := NewDNSProviderCredentials(gcloudProject, util.RecursiveNameservers, "") + provider, err := NewDNSProviderCredentials(t.Context(), gcloudProject, util.RecursiveNameservers, "") assert.NoError(t, err) - err = provider.Present(gcloudDomain, "_acme-challenge."+gcloudDomain+".", "123d==") + err = provider.Present(t.Context(), gcloudDomain, "_acme-challenge."+gcloudDomain+".", "123d==") assert.NoError(t, err) } @@ -84,13 +77,13 @@ func TestLiveGoogleCloudPresentMultiple(t *testing.T) { t.Skip("skipping live test") } - provider, err := NewDNSProviderCredentials(gcloudProject, util.RecursiveNameservers, "") + provider, err := NewDNSProviderCredentials(t.Context(), gcloudProject, util.RecursiveNameservers, "") assert.NoError(t, err) // Check that we're able to create multiple entries - err = provider.Present(gcloudDomain, "_acme-challenge."+gcloudDomain+".", "123d==") + err = provider.Present(t.Context(), gcloudDomain, "_acme-challenge."+gcloudDomain+".", "123d==") assert.NoError(t, err) - err = provider.Present(gcloudDomain, "_acme-challenge."+gcloudDomain+".", "1123d==") + err = provider.Present(t.Context(), gcloudDomain, "_acme-challenge."+gcloudDomain+".", "1123d==") assert.NoError(t, err) } @@ -101,10 +94,10 @@ func TestLiveGoogleCloudCleanUp(t *testing.T) { time.Sleep(time.Second * 1) - provider, err := NewDNSProviderCredentials(gcloudProject, util.RecursiveNameservers, "") + provider, err := NewDNSProviderCredentials(t.Context(), gcloudProject, util.RecursiveNameservers, "") assert.NoError(t, err) - err = provider.CleanUp(gcloudDomain, "_acme-challenge."+gcloudDomain+".", "123d==") + err = provider.CleanUp(t.Context(), gcloudDomain, "_acme-challenge."+gcloudDomain+".", "123d==") assert.NoError(t, err) } @@ -113,7 +106,7 @@ func TestDNSProvider_getHostedZone(t *testing.T) { t.Skip("skipping live test") } - testProvider, err := NewDNSProviderCredentials("my-project", util.RecursiveNameservers, "test-zone") + testProvider, err := NewDNSProviderCredentials(t.Context(), "my-project", util.RecursiveNameservers, "test-zone") assert.NoError(t, err) type args struct { @@ -137,7 +130,7 @@ func TestDNSProvider_getHostedZone(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { c := tt.provider - got, err := c.getHostedZone(tt.args.domain) + got, err := c.getHostedZone(t.Context(), tt.args.domain) if (err != nil) != tt.wantErr { t.Errorf("getHostedZone() error = %v, wantErr %v", err, tt.wantErr) return diff --git a/pkg/issuer/acme/dns/cloudflare/cloudflare.go b/pkg/issuer/acme/dns/cloudflare/cloudflare.go index 133f9256e70..74eb5f6d683 100644 --- a/pkg/issuer/acme/dns/cloudflare/cloudflare.go +++ b/pkg/issuer/acme/dns/cloudflare/cloudflare.go @@ -12,6 +12,7 @@ package cloudflare import ( "bytes" + "context" "encoding/json" "errors" "fmt" @@ -28,9 +29,13 @@ import ( // TODO: Unexport? const CloudFlareAPIURL = "https://api.cloudflare.com/client/v4" +// cloudFlareMaxBodySize is the max size of a received response body. The value is arbitrary +// and is chosen to be large enough that any reasonable response would fit. +const cloudFlareMaxBodySize = 1024 * 1024 // 1mb + // DNSProviderType is the Mockable Interface type DNSProviderType interface { - makeRequest(method, uri string, body io.Reader) (json.RawMessage, error) + makeRequest(ctx context.Context, method, uri string, body io.Reader) (json.RawMessage, error) } // DNSProvider is an implementation of the acme.ChallengeProvider interface @@ -43,7 +48,7 @@ type DNSProvider struct { userAgent string } -// DNSZone is the Zone-Record returned from Cloudflare (we`ll ignore everything we don't need) +// DNSZone is the Zone-Record returned from Cloudflare (we'll ignore everything we don't need) // See https://api.cloudflare.com/#zone-properties type DNSZone struct { ID string `json:"id"` @@ -100,24 +105,24 @@ func NewDNSProviderCredentials(email, key, token string, dns01Nameservers []stri // // It will try to call the API for each branch (from bottom to top) and see if there's a Zone-Record returned. // Calling See https://api.cloudflare.com/#zone-list-zones -func FindNearestZoneForFQDN(c DNSProviderType, fqdn string) (DNSZone, error) { +func FindNearestZoneForFQDN(ctx context.Context, c DNSProviderType, fqdn string) (DNSZone, error) { if fqdn == "" { return DNSZone{}, fmt.Errorf("FindNearestZoneForFQDN: FQDN-Parameter can't be empty, please specify a domain!") } mappedFQDN := strings.Split(fqdn, ".") - nextName := util.UnFqdn(fqdn) //remove the trailing dot + nextName := util.UnFqdn(fqdn) // remove the trailing dot var lastErr error - for i := 0; i < len(mappedFQDN)-1; i++ { + for i := range len(mappedFQDN) - 1 { var from, to = len(mappedFQDN[i]) + 1, len(nextName) if from > to { continue } - if mappedFQDN[i] == "*" { //skip wildcard sub-domain-entries + if mappedFQDN[i] == "*" { // skip wildcard sub-domain-entries nextName = string([]rune(nextName)[from:to]) continue } lastErr = nil - result, err := c.makeRequest("GET", "/zones?name="+nextName, nil) + result, err := c.makeRequest(ctx, "GET", "/zones?name="+nextName, nil) if err != nil { lastErr = err continue @@ -129,7 +134,7 @@ func FindNearestZoneForFQDN(c DNSProviderType, fqdn string) (DNSZone, error) { } if len(zones) > 0 { - return zones[0], nil //we're returning the first zone found, might need to test that further + return zones[0], nil // we're returning the first zone found, might need to test that further } nextName = string([]rune(nextName)[from:to]) } @@ -140,42 +145,34 @@ func FindNearestZoneForFQDN(c DNSProviderType, fqdn string) (DNSZone, error) { } // Present creates a TXT record to fulfil the dns-01 challenge -func (c *DNSProvider) Present(domain, fqdn, value string) error { - zoneID, err := c.getHostedZoneID(fqdn) - if err != nil { - return err - } +func (c *DNSProvider) Present(ctx context.Context, domain, fqdn, value string) error { + _, err := c.findTxtRecord(ctx, fqdn, value) + if err == errNoExistingRecord { + rec := cloudFlareRecord{ + Type: "TXT", + Name: util.UnFqdn(fqdn), + Content: value, + TTL: 120, + } - record, err := c.findTxtRecord(fqdn) - if err != nil && err != errNoExistingRecord { - // this is a real error - return err - } - if record != nil { - if record.Content == value { - // the record is already set to the desired value - return nil + body, err := json.Marshal(rec) + if err != nil { + return err } - _, err = c.makeRequest("DELETE", fmt.Sprintf("/zones/%s/dns_records/%s", record.ZoneID, record.ID), nil) + zoneID, err := c.getHostedZoneID(ctx, fqdn) if err != nil { return err } - } - rec := cloudFlareRecord{ - Type: "TXT", - Name: util.UnFqdn(fqdn), - Content: value, - TTL: 120, - } + _, err = c.makeRequest(ctx, "POST", fmt.Sprintf("/zones/%s/dns_records", zoneID), bytes.NewReader(body)) + if err != nil { + return err + } - body, err := json.Marshal(rec) - if err != nil { - return err + return nil } - _, err = c.makeRequest("POST", fmt.Sprintf("/zones/%s/dns_records", zoneID), bytes.NewReader(body)) if err != nil { return err } @@ -184,8 +181,8 @@ func (c *DNSProvider) Present(domain, fqdn, value string) error { } // CleanUp removes the TXT record matching the specified parameters -func (c *DNSProvider) CleanUp(domain, fqdn, value string) error { - record, err := c.findTxtRecord(fqdn) +func (c *DNSProvider) CleanUp(ctx context.Context, domain, fqdn, value string) error { + record, err := c.findTxtRecord(ctx, fqdn, value) // Nothing to cleanup if err == errNoExistingRecord { return nil @@ -194,7 +191,7 @@ func (c *DNSProvider) CleanUp(domain, fqdn, value string) error { return err } - _, err = c.makeRequest("DELETE", fmt.Sprintf("/zones/%s/dns_records/%s", record.ZoneID, record.ID), nil) + _, err = c.makeRequest(ctx, "DELETE", fmt.Sprintf("/zones/%s/dns_records/%s", record.ZoneID, record.ID), nil) if err != nil { return err } @@ -202,8 +199,8 @@ func (c *DNSProvider) CleanUp(domain, fqdn, value string) error { return nil } -func (c *DNSProvider) getHostedZoneID(fqdn string) (string, error) { - hostedZone, err := FindNearestZoneForFQDN(c, fqdn) +func (c *DNSProvider) getHostedZoneID(ctx context.Context, fqdn string) (string, error) { + hostedZone, err := FindNearestZoneForFQDN(ctx, c, fqdn) if err != nil { return "", err } @@ -212,13 +209,14 @@ func (c *DNSProvider) getHostedZoneID(fqdn string) (string, error) { var errNoExistingRecord = errors.New("No existing record found") -func (c *DNSProvider) findTxtRecord(fqdn string) (*cloudFlareRecord, error) { - zoneID, err := c.getHostedZoneID(fqdn) +func (c *DNSProvider) findTxtRecord(ctx context.Context, fqdn, content string) (*cloudFlareRecord, error) { + zoneID, err := c.getHostedZoneID(ctx, fqdn) if err != nil { return nil, err } result, err := c.makeRequest( + ctx, "GET", fmt.Sprintf("/zones/%s/dns_records?per_page=100&type=TXT&name=%s", zoneID, util.UnFqdn(fqdn)), nil, @@ -234,7 +232,11 @@ func (c *DNSProvider) findTxtRecord(fqdn string) (*cloudFlareRecord, error) { } for _, rec := range records { - if rec.Name == util.UnFqdn(fqdn) { + if rec.Name == util.UnFqdn(fqdn) && rec.Content == content { + // Cloudflare made a breaking change to their API and removed the ZoneID from responses: + // https://developers.cloudflare.com/fundamentals/api/reference/deprecations/#2024-11-30 + // The simplest fix is to set the ZoneID manually here + rec.ZoneID = zoneID return &rec, nil } } @@ -242,7 +244,7 @@ func (c *DNSProvider) findTxtRecord(fqdn string) (*cloudFlareRecord, error) { return nil, errNoExistingRecord } -func (c *DNSProvider) makeRequest(method, uri string, body io.Reader) (json.RawMessage, error) { +func (c *DNSProvider) makeRequest(ctx context.Context, method, uri string, body io.Reader) (json.RawMessage, error) { // APIError contains error details for failed requests type APIError struct { Code int `json:"code,omitempty"` @@ -257,7 +259,7 @@ func (c *DNSProvider) makeRequest(method, uri string, body io.Reader) (json.RawM Result json.RawMessage `json:"result"` } - req, err := http.NewRequest(method, fmt.Sprintf("%s%s", CloudFlareAPIURL, uri), body) + req, err := http.NewRequestWithContext(ctx, method, fmt.Sprintf("%s%s", CloudFlareAPIURL, uri), body) if err != nil { return nil, err } @@ -283,7 +285,7 @@ func (c *DNSProvider) makeRequest(method, uri string, body io.Reader) (json.RawM defer resp.Body.Close() var r APIResponse - err = json.NewDecoder(resp.Body).Decode(&r) + err = json.NewDecoder(io.LimitReader(resp.Body, cloudFlareMaxBodySize)).Decode(&r) if err != nil { return nil, err } @@ -318,7 +320,7 @@ type cloudFlareRecord struct { // following functions are copy-pasted from go's internal // http server func validHeaderFieldValue(v string) bool { - for i := 0; i < len(v); i++ { + for i := range len(v) { b := v[i] if isCTL(b) && !isLWS(b) { return false diff --git a/pkg/issuer/acme/dns/cloudflare/cloudflare_test.go b/pkg/issuer/acme/dns/cloudflare/cloudflare_test.go index 5d2db2d3d61..f93eb692aff 100644 --- a/pkg/issuer/acme/dns/cloudflare/cloudflare_test.go +++ b/pkg/issuer/acme/dns/cloudflare/cloudflare_test.go @@ -9,6 +9,7 @@ this directory. package cloudflare import ( + "context" "encoding/json" "fmt" "io" @@ -34,8 +35,8 @@ type DNSProviderMock struct { mock.Mock } -func (c *DNSProviderMock) makeRequest(method, uri string, body io.Reader) (json.RawMessage, error) { - //stub makeRequest +func (c *DNSProviderMock) makeRequest(ctx context.Context, method, uri string, body io.Reader) (json.RawMessage, error) { + // stub makeRequest args := c.Called(method, uri, nil) return args.Get(0).([]uint8), args.Error(1) } @@ -50,49 +51,39 @@ func init() { } } -func restoreCloudFlareEnv() { - os.Setenv("CLOUDFLARE_EMAIL", cflareEmail) - os.Setenv("CLOUDFLARE_API_KEY", cflareAPIKey) -} - func TestNewDNSProviderValidAPIKey(t *testing.T) { - os.Setenv("CLOUDFLARE_EMAIL", "") - os.Setenv("CLOUDFLARE_API_KEY", "") + t.Setenv("CLOUDFLARE_EMAIL", "") + t.Setenv("CLOUDFLARE_API_KEY", "") _, err := NewDNSProviderCredentials("123", "123", "", util.RecursiveNameservers, "cert-manager-test") assert.NoError(t, err) - restoreCloudFlareEnv() } func TestNewDNSProviderValidAPIToken(t *testing.T) { - os.Setenv("CLOUDFLARE_EMAIL", "") - os.Setenv("CLOUDFLARE_API_KEY", "") + t.Setenv("CLOUDFLARE_EMAIL", "") + t.Setenv("CLOUDFLARE_API_KEY", "") _, err := NewDNSProviderCredentials("123", "", "123", util.RecursiveNameservers, "cert-manager-test") assert.NoError(t, err) - restoreCloudFlareEnv() } func TestNewDNSProviderKeyAndTokenProvided(t *testing.T) { - os.Setenv("CLOUDFLARE_EMAIL", "") - os.Setenv("CLOUDFLARE_API_KEY", "") + t.Setenv("CLOUDFLARE_EMAIL", "") + t.Setenv("CLOUDFLARE_API_KEY", "") _, err := NewDNSProviderCredentials("123", "123", "123", util.RecursiveNameservers, "cert-manager-test") assert.EqualError(t, err, "the Cloudflare API key and API token cannot be both present simultaneously") - restoreCloudFlareEnv() } func TestNewDNSProviderValidApiKeyEnv(t *testing.T) { - os.Setenv("CLOUDFLARE_EMAIL", "test@example.com") - os.Setenv("CLOUDFLARE_API_KEY", "123") + t.Setenv("CLOUDFLARE_EMAIL", "test@example.com") + t.Setenv("CLOUDFLARE_API_KEY", "123") _, err := NewDNSProvider(util.RecursiveNameservers, "cert-manager-test") assert.NoError(t, err) - restoreCloudFlareEnv() } func TestNewDNSProviderMissingCredErr(t *testing.T) { - os.Setenv("CLOUDFLARE_EMAIL", "") - os.Setenv("CLOUDFLARE_API_KEY", "") + t.Setenv("CLOUDFLARE_EMAIL", "") + t.Setenv("CLOUDFLARE_API_KEY", "") _, err := NewDNSProvider(util.RecursiveNameservers, "cert-manager-test") assert.EqualError(t, err, "no Cloudflare credential has been given (can be either an API key or an API token)") - restoreCloudFlareEnv() } func TestFindNearestZoneForFQDN(t *testing.T) { @@ -106,7 +97,7 @@ func TestFindNearestZoneForFQDN(t *testing.T) { {"id":"1a23cc4567b8def91a01c23a456e78cd","name":"sub.domain.com"} ]`), nil) - zone, err := FindNearestZoneForFQDN(dnsProvider, "_acme-challenge.test.sub.domain.com.") + zone, err := FindNearestZoneForFQDN(t.Context(), dnsProvider, "_acme-challenge.test.sub.domain.com.") assert.NoError(t, err) assert.Equal(t, zone, DNSZone{ID: "1a23cc4567b8def91a01c23a456e78cd", Name: "sub.domain.com"}) @@ -125,7 +116,7 @@ func TestFindNearestZoneForFQDNInvalidToken(t *testing.T) { while querying the Cloudflare API for GET "/zones?name=_acme-challenge.test.sub.domain.com" Error: 9109: Invalid access token`)) - _, err := FindNearestZoneForFQDN(dnsProvider, "_acme-challenge.test.sub.domain.com.") + _, err := FindNearestZoneForFQDN(t.Context(), dnsProvider, "_acme-challenge.test.sub.domain.com.") assert.Error(t, err) assert.Contains(t, err.Error(), "Invalid access token") @@ -139,7 +130,7 @@ func TestCloudFlarePresent(t *testing.T) { provider, err := NewDNSProviderCredentials(cflareEmail, cflareAPIKey, cflareAPIToken, util.RecursiveNameservers, "cert-manager-test") assert.NoError(t, err) - err = provider.Present(cflareDomain, "_acme-challenge."+cflareDomain+".", "123d==") + err = provider.Present(t.Context(), cflareDomain, "_acme-challenge."+cflareDomain+".", "123d==") assert.NoError(t, err) } @@ -153,6 +144,6 @@ func TestCloudFlareCleanUp(t *testing.T) { provider, err := NewDNSProviderCredentials(cflareEmail, cflareAPIKey, cflareAPIToken, util.RecursiveNameservers, "cert-manager-test") assert.NoError(t, err) - err = provider.CleanUp(cflareDomain, "_acme-challenge."+cflareDomain+".", "123d==") + err = provider.CleanUp(t.Context(), cflareDomain, "_acme-challenge."+cflareDomain+".", "123d==") assert.NoError(t, err) } diff --git a/pkg/issuer/acme/dns/digitalocean/digitalocean.go b/pkg/issuer/acme/dns/digitalocean/digitalocean.go index f7598955aad..670e1370fb2 100644 --- a/pkg/issuer/acme/dns/digitalocean/digitalocean.go +++ b/pkg/issuer/acme/dns/digitalocean/digitalocean.go @@ -38,39 +38,43 @@ type DNSProvider struct { // NewDNSProvider returns a DNSProvider instance configured for digitalocean. // The access token must be passed in the environment variable DIGITALOCEAN_TOKEN -func NewDNSProvider(dns01Nameservers []string) (*DNSProvider, error) { +func NewDNSProvider(dns01Nameservers []string, userAgent string) (*DNSProvider, error) { token := os.Getenv("DIGITALOCEAN_TOKEN") - return NewDNSProviderCredentials(token, dns01Nameservers) + return NewDNSProviderCredentials(token, dns01Nameservers, userAgent) } // NewDNSProviderCredentials uses the supplied credentials to return a // DNSProvider instance configured for digitalocean. -func NewDNSProviderCredentials(token string, dns01Nameservers []string) (*DNSProvider, error) { +func NewDNSProviderCredentials(token string, dns01Nameservers []string, userAgent string) (*DNSProvider, error) { if token == "" { return nil, fmt.Errorf("DigitalOcean token missing") } - c := oauth2.NewClient( - context.Background(), - oauth2.StaticTokenSource(&oauth2.Token{AccessToken: token}), - ) + unusedCtx := context.Background() // context is not actually used + c := oauth2.NewClient(unusedCtx, oauth2.StaticTokenSource(&oauth2.Token{AccessToken: token})) + + clientOpts := []godo.ClientOpt{godo.SetUserAgent(userAgent)} + client, err := godo.New(c, clientOpts...) + if err != nil { + return nil, err + } return &DNSProvider{ dns01Nameservers: dns01Nameservers, - client: godo.NewClient(c), + client: client, }, nil } // Present creates a TXT record to fulfil the dns-01 challenge -func (c *DNSProvider) Present(domain, fqdn, value string) error { +func (c *DNSProvider) Present(ctx context.Context, _, fqdn, value string) error { // if DigitalOcean does not have this zone then we will find out later - zoneName, err := util.FindZoneByFqdn(fqdn, c.dns01Nameservers) + zoneName, err := util.FindZoneByFqdn(ctx, fqdn, c.dns01Nameservers) if err != nil { return err } // check if the record has already been created - records, err := c.findTxtRecord(fqdn) + records, err := c.findTxtRecord(ctx, fqdn) if err != nil { return err } @@ -79,7 +83,6 @@ func (c *DNSProvider) Present(domain, fqdn, value string) error { if record.Type == "TXT" && record.Data == value { return nil } - } createRequest := &godo.DomainRecordEditRequest{ @@ -90,7 +93,7 @@ func (c *DNSProvider) Present(domain, fqdn, value string) error { } _, _, err = c.client.Domains.CreateRecord( - context.Background(), + ctx, util.UnFqdn(zoneName), createRequest, ) @@ -103,19 +106,19 @@ func (c *DNSProvider) Present(domain, fqdn, value string) error { } // CleanUp removes the TXT record matching the specified parameters -func (c *DNSProvider) CleanUp(domain, fqdn, value string) error { - zoneName, err := util.FindZoneByFqdn(fqdn, c.dns01Nameservers) +func (c *DNSProvider) CleanUp(ctx context.Context, domain, fqdn, value string) error { + zoneName, err := util.FindZoneByFqdn(ctx, fqdn, c.dns01Nameservers) if err != nil { return err } - records, err := c.findTxtRecord(fqdn) + records, err := c.findTxtRecord(ctx, fqdn) if err != nil { return err } for _, record := range records { - _, err = c.client.Domains.DeleteRecord(context.Background(), util.UnFqdn(zoneName), record.ID) + _, err = c.client.Domains.DeleteRecord(ctx, util.UnFqdn(zoneName), record.ID) if err != nil { return err @@ -125,16 +128,16 @@ func (c *DNSProvider) CleanUp(domain, fqdn, value string) error { return nil } -func (c *DNSProvider) findTxtRecord(fqdn string) ([]godo.DomainRecord, error) { - - zoneName, err := util.FindZoneByFqdn(fqdn, c.dns01Nameservers) +func (c *DNSProvider) findTxtRecord(ctx context.Context, fqdn string) ([]godo.DomainRecord, error) { + zoneName, err := util.FindZoneByFqdn(ctx, fqdn, c.dns01Nameservers) if err != nil { return nil, err } - allRecords, _, err := c.client.Domains.Records( - context.Background(), + allRecords, _, err := c.client.Domains.RecordsByType( + ctx, util.UnFqdn(zoneName), + "TXT", nil, ) @@ -142,10 +145,7 @@ func (c *DNSProvider) findTxtRecord(fqdn string) ([]godo.DomainRecord, error) { // The record Name doesn't contain the zoneName, so // lets remove it before filtering the array of record - targetName := fqdn - if strings.HasSuffix(fqdn, zoneName) { - targetName = fqdn[:len(fqdn)-len(zoneName)] - } + targetName := strings.TrimSuffix(fqdn, zoneName) for _, record := range allRecords { if util.ToFqdn(record.Name) == targetName { diff --git a/pkg/issuer/acme/dns/digitalocean/digitalocean_test.go b/pkg/issuer/acme/dns/digitalocean/digitalocean_test.go index 55d9298a412..85aaf34f209 100644 --- a/pkg/issuer/acme/dns/digitalocean/digitalocean_test.go +++ b/pkg/issuer/acme/dns/digitalocean/digitalocean_test.go @@ -21,8 +21,9 @@ import ( "testing" "time" - "github.com/cert-manager/cert-manager/pkg/issuer/acme/dns/util" "github.com/stretchr/testify/assert" + + "github.com/cert-manager/cert-manager/pkg/issuer/acme/dns/util" ) var ( @@ -39,29 +40,22 @@ func init() { } } -func restoreEnv() { - os.Setenv("DIGITALOCEAN_TOKEN", doToken) -} - func TestNewDNSProviderValid(t *testing.T) { - os.Setenv("DIGITALOCEAN_TOKEN", "") - _, err := NewDNSProviderCredentials("123", util.RecursiveNameservers) + t.Setenv("DIGITALOCEAN_TOKEN", "") + _, err := NewDNSProviderCredentials("123", util.RecursiveNameservers, "cert-manager-test") assert.NoError(t, err) - restoreEnv() } func TestNewDNSProviderValidEnv(t *testing.T) { - os.Setenv("DIGITALOCEAN_TOKEN", "123") - _, err := NewDNSProvider(util.RecursiveNameservers) + t.Setenv("DIGITALOCEAN_TOKEN", "123") + _, err := NewDNSProvider(util.RecursiveNameservers, "cert-manager-test") assert.NoError(t, err) - restoreEnv() } func TestNewDNSProviderMissingCredErr(t *testing.T) { - os.Setenv("DIGITALOCEAN_TOKEN", "") - _, err := NewDNSProvider(util.RecursiveNameservers) + t.Setenv("DIGITALOCEAN_TOKEN", "") + _, err := NewDNSProvider(util.RecursiveNameservers, "cert-manager-test") assert.EqualError(t, err, "DigitalOcean token missing") - restoreEnv() } func TestDigitalOceanPresent(t *testing.T) { @@ -69,10 +63,10 @@ func TestDigitalOceanPresent(t *testing.T) { t.Skip("skipping live test") } - provider, err := NewDNSProviderCredentials(doToken, util.RecursiveNameservers) + provider, err := NewDNSProviderCredentials(doToken, util.RecursiveNameservers, "cert-manager-test") assert.NoError(t, err) - err = provider.Present(doDomain, "_acme-challenge."+doDomain+".", "123d==") + err = provider.Present(t.Context(), doDomain, "_acme-challenge."+doDomain+".", "123d==") assert.NoError(t, err) } @@ -83,13 +77,9 @@ func TestDigitalOceanCleanUp(t *testing.T) { time.Sleep(time.Second * 2) - provider, err := NewDNSProviderCredentials(doToken, util.RecursiveNameservers) + provider, err := NewDNSProviderCredentials(doToken, util.RecursiveNameservers, "cert-manager-test") assert.NoError(t, err) - err = provider.CleanUp(doDomain, "_acme-challenge."+doDomain+".", "123d==") + err = provider.CleanUp(t.Context(), doDomain, "_acme-challenge."+doDomain+".", "123d==") assert.NoError(t, err) } - -func TestDigitalOceanSolveForProvider(t *testing.T) { - -} diff --git a/pkg/issuer/acme/dns/dns.go b/pkg/issuer/acme/dns/dns.go index e9aeb1cc760..282d7442edd 100644 --- a/pkg/issuer/acme/dns/dns.go +++ b/pkg/issuer/acme/dns/dns.go @@ -19,14 +19,17 @@ package dns import ( "context" "encoding/json" + "errors" "fmt" "strings" "time" - "github.com/pkg/errors" + authv1 "k8s.io/api/authentication/v1" apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" - corev1listers "k8s.io/client-go/listers/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/utils/ptr" + internalinformers "github.com/cert-manager/cert-manager/internal/informers" "github.com/cert-manager/cert-manager/pkg/acme/webhook" whapi "github.com/cert-manager/cert-manager/pkg/acme/webhook/apis/acme/v1alpha1" cmacme "github.com/cert-manager/cert-manager/pkg/apis/acme/v1" @@ -49,20 +52,20 @@ import ( // solver is the old solver type interface. // All new solvers should be implemented using the new webhook.Solver interface. type solver interface { - Present(domain, fqdn, value string) error - CleanUp(domain, fqdn, value string) error + Present(ctx context.Context, domain, fqdn, value string) error + CleanUp(ctx context.Context, domain, fqdn, value string) error } // dnsProviderConstructors defines how each provider may be constructed. // It is useful for mocking out a given provider since an alternate set of // constructors may be set. type dnsProviderConstructors struct { - cloudDNS func(project string, serviceAccount []byte, dns01Nameservers []string, ambient bool, hostedZoneName string) (*clouddns.DNSProvider, error) + cloudDNS func(ctx context.Context, project string, serviceAccount []byte, dns01Nameservers []string, ambient bool, hostedZoneName string) (*clouddns.DNSProvider, error) cloudFlare func(email, apikey, apiToken string, dns01Nameservers []string, userAgent string) (*cloudflare.DNSProvider, error) - route53 func(accessKey, secretKey, hostedZoneID, region, role string, ambient bool, dns01Nameservers []string, userAgent string) (*route53.DNSProvider, error) + route53 func(ctx context.Context, accessKey, secretKey, hostedZoneID, region, role, webIdentityToken string, ambient bool, dns01Nameservers []string, userAgent string) (*route53.DNSProvider, error) azureDNS func(environment, clientID, clientSecret, subscriptionID, tenantID, resourceGroupName, hostedZoneName string, dns01Nameservers []string, ambient bool, managedIdentity *cmacme.AzureManagedIdentity) (*azuredns.DNSProvider, error) acmeDNS func(host string, accountJson []byte, dns01Nameservers []string) (*acmedns.DNSProvider, error) - digitalOcean func(token string, dns01Nameservers []string) (*digitalocean.DNSProvider, error) + digitalOcean func(token string, dns01Nameservers []string, userAgent string) (*digitalocean.DNSProvider, error) } // Solver is a solver for the acme dns01 challenge. @@ -70,18 +73,18 @@ type dnsProviderConstructors struct { // the certificate, and configures it based on the referenced issuer. type Solver struct { *controller.Context - secretLister corev1listers.SecretLister + secretLister internalinformers.SecretLister dnsProviderConstructors dnsProviderConstructors webhookSolvers map[string]webhook.Solver } // Present performs the work to configure DNS to resolve a DNS01 challenge. -func (s *Solver) Present(ctx context.Context, issuer v1.GenericIssuer, ch *cmacme.Challenge) error { +func (s *Solver) Present(ctx context.Context, _ v1.GenericIssuer, ch *cmacme.Challenge) error { log := logf.WithResource(logf.FromContext(ctx, "Present"), ch).WithValues("domain", ch.Spec.DNSName) ctx = logf.NewContext(ctx, log) - webhookSolver, req, err := s.prepareChallengeRequest(issuer, ch) - if err != nil && err != errNotFound { + webhookSolver, req, err := s.prepareChallengeRequest(ctx, ch) + if err != nil && !errors.Is(err, errNotFound) { return err } if err == nil { @@ -89,33 +92,33 @@ func (s *Solver) Present(ctx context.Context, issuer v1.GenericIssuer, ch *cmacm return webhookSolver.Present(req) } - slv, providerConfig, err := s.solverForChallenge(ctx, issuer, ch) + slv, providerConfig, err := s.solverForChallenge(ctx, ch) if err != nil { return err } - fqdn, err := util.DNS01LookupFQDN(ch.Spec.DNSName, followCNAME(providerConfig.CNAMEStrategy), s.DNS01Nameservers...) + fqdn, err := util.DNS01LookupFQDN(ctx, ch.Spec.DNSName, followCNAME(providerConfig.CNAMEStrategy), s.DNS01Nameservers...) if err != nil { return err } log.V(logf.DebugLevel).Info("presenting DNS01 challenge for domain") - return slv.Present(ch.Spec.DNSName, fqdn, ch.Spec.Key) + return slv.Present(ctx, ch.Spec.DNSName, fqdn, ch.Spec.Key) } // Check verifies that the DNS records for the ACME challenge have propagated. func (s *Solver) Check(ctx context.Context, issuer v1.GenericIssuer, ch *cmacme.Challenge) error { log := logf.WithResource(logf.FromContext(ctx, "Check"), ch).WithValues("domain", ch.Spec.DNSName) - fqdn, err := util.DNS01LookupFQDN(ch.Spec.DNSName, false, s.DNS01Nameservers...) + fqdn, err := util.DNS01LookupFQDN(ctx, ch.Spec.DNSName, false, s.DNS01Nameservers...) if err != nil { return err } log.V(logf.DebugLevel).Info("checking DNS propagation", "nameservers", s.Context.DNS01Nameservers) - ok, err := util.PreCheckDNS(fqdn, ch.Spec.Key, s.Context.DNS01Nameservers, + ok, err := util.PreCheckDNS(ctx, fqdn, ch.Spec.Key, s.Context.DNS01Nameservers, s.Context.DNS01CheckAuthoritative) if err != nil { return err @@ -134,11 +137,11 @@ func (s *Solver) Check(ctx context.Context, issuer v1.GenericIssuer, ch *cmacme. // CleanUp removes DNS records which are no longer needed after // certificate issuance. -func (s *Solver) CleanUp(ctx context.Context, issuer v1.GenericIssuer, ch *cmacme.Challenge) error { +func (s *Solver) CleanUp(ctx context.Context, ch *cmacme.Challenge) error { log := logf.WithResource(logf.FromContext(ctx, "CleanUp"), ch).WithValues("domain", ch.Spec.DNSName) ctx = logf.NewContext(ctx, log) - webhookSolver, req, err := s.prepareChallengeRequest(issuer, ch) + webhookSolver, req, err := s.prepareChallengeRequest(ctx, ch) if err != nil && err != errNotFound { return err } @@ -147,17 +150,17 @@ func (s *Solver) CleanUp(ctx context.Context, issuer v1.GenericIssuer, ch *cmacm return webhookSolver.CleanUp(req) } - slv, providerConfig, err := s.solverForChallenge(ctx, issuer, ch) + slv, providerConfig, err := s.solverForChallenge(ctx, ch) if err != nil { return err } - fqdn, err := util.DNS01LookupFQDN(ch.Spec.DNSName, followCNAME(providerConfig.CNAMEStrategy), s.DNS01Nameservers...) + fqdn, err := util.DNS01LookupFQDN(ctx, ch.Spec.DNSName, followCNAME(providerConfig.CNAMEStrategy), s.DNS01Nameservers...) if err != nil { return err } - return slv.CleanUp(ch.Spec.DNSName, fqdn, ch.Spec.Key) + return slv.CleanUp(ctx, ch.Spec.DNSName, fqdn, ch.Spec.Key) } func followCNAME(strategy cmacme.CNAMEStrategy) bool { @@ -175,12 +178,12 @@ func extractChallengeSolverConfig(ch *cmacme.Challenge) (*cmacme.ACMEChallengeSo // solverForChallenge returns a Solver for the given providerName. // The providerName is the name of an ACME DNS-01 challenge provider as // specified on the Issuer resource for the Solver. -func (s *Solver) solverForChallenge(ctx context.Context, issuer v1.GenericIssuer, ch *cmacme.Challenge) (solver, *cmacme.ACMEChallengeSolverDNS01, error) { +func (s *Solver) solverForChallenge(ctx context.Context, ch *cmacme.Challenge) (solver, *cmacme.ACMEChallengeSolverDNS01, error) { log := logf.FromContext(ctx, "solverForChallenge") dbg := log.V(logf.DebugLevel) - resourceNamespace := s.ResourceNamespace(issuer) - canUseAmbientCredentials := s.CanUseAmbientCredentials(issuer) + resourceNamespace := s.ResourceNamespaceRef(ch.Spec.IssuerRef, ch.Namespace) + canUseAmbientCredentials := s.CanUseAmbientCredentialsFromRef(ch.Spec.IssuerRef) providerConfig, err := extractChallengeSolverConfig(ch) if err != nil { @@ -193,17 +196,17 @@ func (s *Solver) solverForChallenge(ctx context.Context, issuer v1.GenericIssuer dbg.Info("preparing to create Akamai provider") clientToken, err := s.loadSecretData(&providerConfig.Akamai.ClientToken, resourceNamespace) if err != nil { - return nil, nil, errors.Wrap(err, "error getting akamai client token") + return nil, nil, fmt.Errorf("error getting akamai client token: %w", err) } clientSecret, err := s.loadSecretData(&providerConfig.Akamai.ClientSecret, resourceNamespace) if err != nil { - return nil, nil, errors.Wrap(err, "error getting akamai client secret") + return nil, nil, fmt.Errorf("error getting akamai client secret: %w", err) } accessToken, err := s.loadSecretData(&providerConfig.Akamai.AccessToken, resourceNamespace) if err != nil { - return nil, nil, errors.Wrap(err, "error getting akamai access token") + return nil, nil, fmt.Errorf("error getting akamai client token: %w", err) } impl, err = akamai.NewDNSProvider( @@ -213,7 +216,7 @@ func (s *Solver) solverForChallenge(ctx context.Context, issuer v1.GenericIssuer string(accessToken), s.DNS01Nameservers) if err != nil { - return nil, nil, errors.Wrap(err, "error instantiating akamai challenge solver") + return nil, nil, fmt.Errorf("error instantiating akamai challenge solver: %w", err) } case providerConfig.CloudDNS != nil: dbg.Info("preparing to create CloudDNS provider") @@ -236,7 +239,7 @@ func (s *Solver) solverForChallenge(ctx context.Context, issuer v1.GenericIssuer } // attempt to construct the cloud dns provider - impl, err = s.dnsProviderConstructors.cloudDNS(providerConfig.CloudDNS.Project, keyData, s.DNS01Nameservers, s.CanUseAmbientCredentials(issuer), providerConfig.CloudDNS.HostedZoneName) + impl, err = s.dnsProviderConstructors.cloudDNS(ctx, providerConfig.CloudDNS.Project, keyData, s.DNS01Nameservers, s.CanUseAmbientCredentialsFromRef(ch.Spec.IssuerRef), providerConfig.CloudDNS.HostedZoneName) if err != nil { return nil, nil, fmt.Errorf("error instantiating google clouddns challenge solver: %s", err) } @@ -286,7 +289,7 @@ func (s *Solver) solverForChallenge(ctx context.Context, issuer v1.GenericIssuer apiToken := string(apiTokenSecret.Data[providerConfig.DigitalOcean.Token.Key]) - impl, err = s.dnsProviderConstructors.digitalOcean(strings.TrimSpace(apiToken), s.DNS01Nameservers) + impl, err = s.dnsProviderConstructors.digitalOcean(strings.TrimSpace(apiToken), s.DNS01Nameservers, s.RESTConfig.UserAgent) if err != nil { return nil, nil, fmt.Errorf("error instantiating digitalocean challenge solver: %s", err.Error()) } @@ -344,12 +347,33 @@ func (s *Solver) solverForChallenge(ctx context.Context, issuer v1.GenericIssuer secretAccessKey = string(secretAccessKeyBytes) } + webIdentityToken := "" + if providerConfig.Route53.Auth != nil && providerConfig.Route53.Auth.Kubernetes != nil && providerConfig.Route53.Auth.Kubernetes.ServiceAccountRef != nil { + if providerConfig.Route53.Auth.Kubernetes.ServiceAccountRef.Name == "" { + return nil, nil, fmt.Errorf("service account name is required for Kubernetes auth") + } + + audiences := []string{"sts.amazonaws.com"} + if len(providerConfig.Route53.Auth.Kubernetes.ServiceAccountRef.TokenAudiences) != 0 { + audiences = providerConfig.Route53.Auth.Kubernetes.ServiceAccountRef.TokenAudiences + } + + jwt, err := s.createToken(ctx, resourceNamespace, providerConfig.Route53.Auth.Kubernetes.ServiceAccountRef.Name, audiences) + if err != nil { + return nil, nil, fmt.Errorf("error getting service account token: %w", err) + } + + webIdentityToken = jwt + } + impl, err = s.dnsProviderConstructors.route53( + ctx, secretAccessKeyID, strings.TrimSpace(secretAccessKey), providerConfig.Route53.HostedZoneID, providerConfig.Route53.Region, providerConfig.Route53.Role, + webIdentityToken, canUseAmbientCredentials, s.DNS01Nameservers, s.RESTConfig.UserAgent, @@ -416,7 +440,7 @@ func (s *Solver) solverForChallenge(ctx context.Context, issuer v1.GenericIssuer return impl, providerConfig, nil } -func (s *Solver) prepareChallengeRequest(issuer v1.GenericIssuer, ch *cmacme.Challenge) (webhook.Solver, *whapi.ChallengeRequest, error) { +func (s *Solver) prepareChallengeRequest(ctx context.Context, ch *cmacme.Challenge) (webhook.Solver, *whapi.ChallengeRequest, error) { dns01Config, err := extractChallengeSolverConfig(ch) if err != nil { return nil, nil, err @@ -427,18 +451,18 @@ func (s *Solver) prepareChallengeRequest(issuer v1.GenericIssuer, ch *cmacme.Cha return nil, nil, err } - fqdn, err := util.DNS01LookupFQDN(ch.Spec.DNSName, followCNAME(dns01Config.CNAMEStrategy), s.DNS01Nameservers...) + fqdn, err := util.DNS01LookupFQDN(ctx, ch.Spec.DNSName, followCNAME(dns01Config.CNAMEStrategy), s.DNS01Nameservers...) if err != nil { return nil, nil, err } - zone, err := util.FindZoneByFqdn(fqdn, s.DNS01Nameservers) + zone, err := util.FindZoneByFqdn(ctx, fqdn, s.DNS01Nameservers) if err != nil { return nil, nil, err } - resourceNamespace := s.ResourceNamespace(issuer) - canUseAmbientCredentials := s.CanUseAmbientCredentials(issuer) + resourceNamespace := s.ResourceNamespaceRef(ch.Spec.IssuerRef, ch.Namespace) + canUseAmbientCredentials := s.CanUseAmbientCredentialsFromRef(ch.Spec.IssuerRef) // construct a ChallengeRequest which can be passed to DNS solvers. // The provided config will be encoded to JSON in order to avoid a coupling @@ -488,9 +512,10 @@ func (s *Solver) dns01SolverForConfig(config *cmacme.ACMEChallengeSolverDNS01) ( // NewSolver creates a Solver which can instantiate the appropriate DNS // provider. func NewSolver(ctx *controller.Context) (*Solver, error) { + secretsLister := ctx.KubeSharedInformerFactory.Secrets().Lister() webhookSolvers := []webhook.Solver{ &webhookslv.Webhook{}, - rfc2136.New(rfc2136.WithNamespace(ctx.Namespace)), + rfc2136.New(rfc2136.WithNamespace(ctx.Namespace), rfc2136.WithSecretsLister(secretsLister)), } initialized := make(map[string]webhook.Solver) @@ -500,7 +525,7 @@ func NewSolver(ctx *controller.Context) (*Solver, error) { if ctx.RESTConfig != nil { // initialize all DNS providers for _, s := range webhookSolvers { - err := s.Initialize(ctx.RESTConfig, ctx.StopCh) + err := s.Initialize(ctx.RESTConfig, ctx.RootContext.Done()) if err != nil { return nil, fmt.Errorf("error initializing DNS provider %q: %v", s.Name(), err) } @@ -510,7 +535,7 @@ func NewSolver(ctx *controller.Context) (*Solver, error) { return &Solver{ Context: ctx, - secretLister: ctx.KubeSharedInformerFactory.Core().V1().Secrets().Lister(), + secretLister: ctx.KubeSharedInformerFactory.Secrets().Lister(), dnsProviderConstructors: dnsProviderConstructors{ clouddns.NewDNSProvider, cloudflare.NewDNSProviderCredentials, @@ -526,12 +551,26 @@ func NewSolver(ctx *controller.Context) (*Solver, error) { func (s *Solver) loadSecretData(selector *cmmeta.SecretKeySelector, ns string) ([]byte, error) { secret, err := s.secretLister.Secrets(ns).Get(selector.Name) if err != nil { - return nil, errors.Wrapf(err, "failed to load secret %q", ns+"/"+selector.Name) + return nil, fmt.Errorf("failed to load secret %q: %w", ns+"/"+selector.Name, err) } if data, ok := secret.Data[selector.Key]; ok { return data, nil } - return nil, errors.Errorf("no key %q in secret %q", selector.Key, ns+"/"+selector.Name) + return nil, fmt.Errorf("no key %q in secret %q", selector.Key, ns+"/"+selector.Name) +} + +func (s *Solver) createToken(ctx context.Context, ns, serviceAccount string, audiences []string) (string, error) { + tokenrequest, err := s.Client.CoreV1().ServiceAccounts(ns).CreateToken(ctx, serviceAccount, &authv1.TokenRequest{ + Spec: authv1.TokenRequestSpec{ + Audiences: audiences, + ExpirationSeconds: ptr.To(int64(600)), + }, + }, metav1.CreateOptions{}) + if err != nil { + return "", fmt.Errorf("failed to request token for %s/%s: %w", ns, serviceAccount, err) + } + + return tokenrequest.Status.Token, nil } diff --git a/pkg/issuer/acme/dns/dns_test.go b/pkg/issuer/acme/dns/dns_test.go index 39ce2b665df..fa1d19893e2 100644 --- a/pkg/issuer/acme/dns/dns_test.go +++ b/pkg/issuer/acme/dns/dns_test.go @@ -17,7 +17,6 @@ limitations under the License. package dns import ( - "context" "reflect" "testing" @@ -27,38 +26,88 @@ import ( "k8s.io/client-go/rest" cmacme "github.com/cert-manager/cert-manager/pkg/apis/acme/v1" - v1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" "github.com/cert-manager/cert-manager/pkg/controller" "github.com/cert-manager/cert-manager/pkg/controller/test" "github.com/cert-manager/cert-manager/pkg/issuer/acme/dns/acmedns" "github.com/cert-manager/cert-manager/pkg/issuer/acme/dns/cloudflare" "github.com/cert-manager/cert-manager/pkg/issuer/acme/dns/util" + "github.com/cert-manager/cert-manager/test/unit/gen" ) -func newIssuer(name, namespace string) *v1.Issuer { - return &v1.Issuer{ +const ( + fakeIssuerNamespace = "fake-issuer-namespace" + fakeClusterIssuerResourceNamespace = "fake-cluster-resource-namespace" +) + +func newSecret(name string, data map[string][]byte, namespace string) *corev1.Secret { + return &corev1.Secret{ ObjectMeta: metav1.ObjectMeta{ Name: name, Namespace: namespace, }, - Spec: v1.IssuerSpec{ - IssuerConfig: v1.IssuerConfig{ - ACME: &cmacme.ACMEIssuer{}, - }, - }, + Data: data, } } -func newSecret(name, namespace string, data map[string][]byte) *corev1.Secret { - return &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Namespace: namespace, +func TestClusterIssuerNamespace(t *testing.T) { + f := &solverFixture{ + Builder: &test.Builder{ + KubeObjects: []runtime.Object{ + newSecret( + "route53", + map[string][]byte{ + "secret": []byte("AKIENDINNEWLINE \n"), + }, + fakeClusterIssuerResourceNamespace, // since this is a ClusterIssuer, the secret should be in the clusterResourceNamespace + ), + }, + Context: &controller.Context{ + ContextOptions: controller.ContextOptions{ + IssuerOptions: controller.IssuerOptions{ + ClusterResourceNamespace: fakeClusterIssuerResourceNamespace, + }, + }, + }, }, - Data: data, + Challenge: &cmacme.Challenge{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "random-certificate-namespace", // Random namespace in which the Certificate and Challenge live + }, + Spec: cmacme.ChallengeSpec{ + Solver: cmacme.ACMEChallengeSolver{ + DNS01: &cmacme.ACMEChallengeSolverDNS01{ + Route53: &cmacme.ACMEIssuerDNS01ProviderRoute53{ + AccessKeyID: " test_with_spaces ", + Region: "us-west-2", + SecretAccessKey: cmmeta.SecretKeySelector{ + LocalObjectReference: cmmeta.LocalObjectReference{ + Name: "route53", + }, + Key: "secret", + }, + }, + }, + }, + IssuerRef: cmmeta.IssuerReference{ + Name: "test-issuer", + Kind: "ClusterIssuer", // ClusterIssuer reference, so should use the clusterResourceNamespace + }, + }, + }, + dnsProviders: newFakeDNSProviders(), + } + + f.Setup(t) + defer f.Finish(t) + + s := f.Solver + _, _, err := s.solverForChallenge(t.Context(), f.Challenge) + if err != nil { + t.Fatalf("expected solverFor to not error, but got: %s", err) } } + func TestSolverFor(t *testing.T) { type testT struct { *solverFixture @@ -71,13 +120,15 @@ func TestSolverFor(t *testing.T) { solverFixture: &solverFixture{ Builder: &test.Builder{ KubeObjects: []runtime.Object{ - newSecret("cloudflare-key", "default", map[string][]byte{ + newSecret("cloudflare-key", map[string][]byte{ "api-key": []byte("a-cloudflare-api-key"), - }), + }, fakeIssuerNamespace), }, }, - Issuer: newIssuer("test", "default"), Challenge: &cmacme.Challenge{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: fakeIssuerNamespace, + }, Spec: cmacme.ChallengeSpec{ Solver: cmacme.ACMEChallengeSolver{ DNS01: &cmacme.ACMEChallengeSolverDNS01{ @@ -92,6 +143,9 @@ func TestSolverFor(t *testing.T) { }, }, }, + IssuerRef: cmmeta.IssuerReference{ + Name: "test-issuer", + }, }, }, }, @@ -102,13 +156,15 @@ func TestSolverFor(t *testing.T) { solverFixture: &solverFixture{ Builder: &test.Builder{ KubeObjects: []runtime.Object{ - newSecret("cloudflare-token", "default", map[string][]byte{ + newSecret("cloudflare-token", map[string][]byte{ "api-token": []byte("a-cloudflare-api-token"), - }), + }, fakeIssuerNamespace), }, }, - Issuer: newIssuer("test", "default"), Challenge: &cmacme.Challenge{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: fakeIssuerNamespace, + }, Spec: cmacme.ChallengeSpec{ Solver: cmacme.ACMEChallengeSolver{ DNS01: &cmacme.ACMEChallengeSolverDNS01{ @@ -123,6 +179,9 @@ func TestSolverFor(t *testing.T) { }, }, }, + IssuerRef: cmmeta.IssuerReference{ + Name: "test-issuer", + }, }, }, }, @@ -131,9 +190,11 @@ func TestSolverFor(t *testing.T) { }, "fails to load a cloudflare provider with a missing secret": { solverFixture: &solverFixture{ - Issuer: newIssuer("test", "default"), // don't include any secrets in the lister Challenge: &cmacme.Challenge{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: fakeIssuerNamespace, + }, Spec: cmacme.ChallengeSpec{ Solver: cmacme.ACMEChallengeSolver{ DNS01: &cmacme.ACMEChallengeSolverDNS01{ @@ -148,6 +209,9 @@ func TestSolverFor(t *testing.T) { }, }, }, + IssuerRef: cmmeta.IssuerReference{ + Name: "test-issuer", + }, }, }, }, @@ -156,9 +220,11 @@ func TestSolverFor(t *testing.T) { }, "fails to load a cloudflare provider when key and token are provided": { solverFixture: &solverFixture{ - Issuer: newIssuer("test", "default"), // don't include any secrets in the lister Challenge: &cmacme.Challenge{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: fakeIssuerNamespace, + }, Spec: cmacme.ChallengeSpec{ Solver: cmacme.ACMEChallengeSolver{ DNS01: &cmacme.ACMEChallengeSolverDNS01{ @@ -179,6 +245,9 @@ func TestSolverFor(t *testing.T) { }, }, }, + IssuerRef: cmmeta.IssuerReference{ + Name: "test-issuer", + }, }, }, }, @@ -189,13 +258,15 @@ func TestSolverFor(t *testing.T) { solverFixture: &solverFixture{ Builder: &test.Builder{ KubeObjects: []runtime.Object{ - newSecret("cloudflare-key", "default", map[string][]byte{ + newSecret("cloudflare-key", map[string][]byte{ "api-key-oops": []byte("a-cloudflare-api-key"), - }), + }, fakeIssuerNamespace), }, }, - Issuer: newIssuer("test", "default"), Challenge: &cmacme.Challenge{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: fakeIssuerNamespace, + }, Spec: cmacme.ChallengeSpec{ Solver: cmacme.ACMEChallengeSolver{ DNS01: &cmacme.ACMEChallengeSolverDNS01{ @@ -210,6 +281,9 @@ func TestSolverFor(t *testing.T) { }, }, }, + IssuerRef: cmmeta.IssuerReference{ + Name: "test-issuer", + }, }, }, }, @@ -220,13 +294,15 @@ func TestSolverFor(t *testing.T) { solverFixture: &solverFixture{ Builder: &test.Builder{ KubeObjects: []runtime.Object{ - newSecret("cloudflare-token", "default", map[string][]byte{ + newSecret("cloudflare-token", map[string][]byte{ "api-key-oops": []byte("a-cloudflare-api-token"), - }), + }, fakeIssuerNamespace), }, }, - Issuer: newIssuer("test", "default"), Challenge: &cmacme.Challenge{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: fakeIssuerNamespace, + }, Spec: cmacme.ChallengeSpec{ Solver: cmacme.ACMEChallengeSolver{ DNS01: &cmacme.ACMEChallengeSolverDNS01{ @@ -241,6 +317,9 @@ func TestSolverFor(t *testing.T) { }, }, }, + IssuerRef: cmmeta.IssuerReference{ + Name: "test-issuer", + }, }, }, }, @@ -251,13 +330,15 @@ func TestSolverFor(t *testing.T) { solverFixture: &solverFixture{ Builder: &test.Builder{ KubeObjects: []runtime.Object{ - newSecret("acmedns-key", "default", map[string][]byte{ + newSecret("acmedns-key", map[string][]byte{ "acmedns.json": []byte("{}"), - }), + }, fakeIssuerNamespace), }, }, - Issuer: newIssuer("test", "default"), Challenge: &cmacme.Challenge{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: fakeIssuerNamespace, + }, Spec: cmacme.ChallengeSpec{ Solver: cmacme.ACMEChallengeSolver{ DNS01: &cmacme.ACMEChallengeSolverDNS01{ @@ -272,6 +353,9 @@ func TestSolverFor(t *testing.T) { }, }, }, + IssuerRef: cmmeta.IssuerReference{ + Name: "test-issuer", + }, }, }, }, @@ -284,7 +368,7 @@ func TestSolverFor(t *testing.T) { test.Setup(t) defer test.Finish(t) s := test.Solver - dnsSolver, _, err := s.solverForChallenge(context.Background(), test.Issuer, test.Challenge) + dnsSolver, _, err := s.solverForChallenge(t.Context(), test.Challenge) if err != nil && !test.expectErr { t.Errorf("expected solverFor to not error, but got: %s", err.Error()) return @@ -305,13 +389,15 @@ func TestSolveForDigitalOcean(t *testing.T) { f := &solverFixture{ Builder: &test.Builder{ KubeObjects: []runtime.Object{ - newSecret("digitalocean", "default", map[string][]byte{ + newSecret("digitalocean", map[string][]byte{ "token": []byte("FAKE-TOKEN"), - }), + }, fakeIssuerNamespace), }, }, - Issuer: newIssuer("test", "default"), Challenge: &cmacme.Challenge{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: fakeIssuerNamespace, + }, Spec: cmacme.ChallengeSpec{ Solver: cmacme.ACMEChallengeSolver{ DNS01: &cmacme.ACMEChallengeSolverDNS01{ @@ -325,6 +411,9 @@ func TestSolveForDigitalOcean(t *testing.T) { }, }, }, + IssuerRef: cmmeta.IssuerReference{ + Name: "test-issuer", + }, }, }, dnsProviders: newFakeDNSProviders(), @@ -334,7 +423,7 @@ func TestSolveForDigitalOcean(t *testing.T) { defer f.Finish(t) s := f.Solver - _, _, err := s.solverForChallenge(context.Background(), f.Issuer, f.Challenge) + _, _, err := s.solverForChallenge(t.Context(), f.Challenge) if err != nil { t.Fatalf("expected solverFor to not error, but got: %s", err) } @@ -356,13 +445,15 @@ func TestRoute53TrimCreds(t *testing.T) { f := &solverFixture{ Builder: &test.Builder{ KubeObjects: []runtime.Object{ - newSecret("route53", "default", map[string][]byte{ + newSecret("route53", map[string][]byte{ "secret": []byte("AKIENDINNEWLINE \n"), - }), + }, fakeIssuerNamespace), }, }, - Issuer: newIssuer("test", "default"), Challenge: &cmacme.Challenge{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: fakeIssuerNamespace, + }, Spec: cmacme.ChallengeSpec{ Solver: cmacme.ACMEChallengeSolver{ DNS01: &cmacme.ACMEChallengeSolverDNS01{ @@ -378,6 +469,9 @@ func TestRoute53TrimCreds(t *testing.T) { }, }, }, + IssuerRef: cmmeta.IssuerReference{ + Name: "test-issuer", + }, }, }, dnsProviders: newFakeDNSProviders(), @@ -387,7 +481,7 @@ func TestRoute53TrimCreds(t *testing.T) { defer f.Finish(t) s := f.Solver - _, _, err := s.solverForChallenge(context.Background(), f.Issuer, f.Challenge) + _, _, err := s.solverForChallenge(t.Context(), f.Challenge) if err != nil { t.Fatalf("expected solverFor to not error, but got: %s", err) } @@ -395,7 +489,7 @@ func TestRoute53TrimCreds(t *testing.T) { expectedR53Call := []fakeDNSProviderCall{ { name: "route53", - args: []interface{}{"test_with_spaces", "AKIENDINNEWLINE", "", "us-west-2", "", false, util.RecursiveNameservers}, + args: []interface{}{"test_with_spaces", "AKIENDINNEWLINE", "", "us-west-2", "", "", false, util.RecursiveNameservers}, }, } @@ -408,14 +502,16 @@ func TestRoute53SecretAccessKey(t *testing.T) { f := &solverFixture{ Builder: &test.Builder{ KubeObjects: []runtime.Object{ - newSecret("route53", "default", map[string][]byte{ + newSecret("route53", map[string][]byte{ "accessKeyID": []byte("AWSACCESSKEYID"), "secretAccessKey": []byte("AKIENDINNEWLINE \n"), - }), + }, fakeIssuerNamespace), }, }, - Issuer: newIssuer("test", "default"), Challenge: &cmacme.Challenge{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: fakeIssuerNamespace, + }, Spec: cmacme.ChallengeSpec{ Solver: cmacme.ACMEChallengeSolver{ DNS01: &cmacme.ACMEChallengeSolverDNS01{ @@ -436,6 +532,9 @@ func TestRoute53SecretAccessKey(t *testing.T) { }, }, }, + IssuerRef: cmmeta.IssuerReference{ + Name: "test-issuer", + }, }, }, dnsProviders: newFakeDNSProviders(), @@ -445,7 +544,7 @@ func TestRoute53SecretAccessKey(t *testing.T) { defer f.Finish(t) s := f.Solver - _, _, err := s.solverForChallenge(context.Background(), f.Issuer, f.Challenge) + _, _, err := s.solverForChallenge(t.Context(), f.Challenge) if err != nil { t.Fatalf("expected solverFor to not error, but got: %s", err) } @@ -453,7 +552,7 @@ func TestRoute53SecretAccessKey(t *testing.T) { expectedR53Call := []fakeDNSProviderCall{ { name: "route53", - args: []interface{}{"AWSACCESSKEYID", "AKIENDINNEWLINE", "", "us-west-2", "", false, util.RecursiveNameservers}, + args: []interface{}{"AWSACCESSKEYID", "AKIENDINNEWLINE", "", "us-west-2", "", "", false, util.RecursiveNameservers}, }, } @@ -484,24 +583,20 @@ func TestRoute53AmbientCreds(t *testing.T) { }, }, }, - Issuer: newIssuer("test", "default"), dnsProviders: newFakeDNSProviders(), - Challenge: &cmacme.Challenge{ - Spec: cmacme.ChallengeSpec{ - Solver: cmacme.ACMEChallengeSolver{ - DNS01: &cmacme.ACMEChallengeSolverDNS01{ - Route53: &cmacme.ACMEIssuerDNS01ProviderRoute53{ - Region: "us-west-2", - }, - }, - }, - }, - }, + Challenge: gen.Challenge("", + gen.SetChallengeNamespace(fakeIssuerNamespace), + gen.SetChallengeIssuer(cmmeta.IssuerReference{Name: "test-issuer"}), + gen.SetChallengeSolverDNS01(cmacme.ACMEChallengeSolverDNS01{ + Route53: &cmacme.ACMEIssuerDNS01ProviderRoute53{ + Region: "us-west-2", + }}), + ), }, result{ expectedCall: &fakeDNSProviderCall{ name: "route53", - args: []interface{}{"", "", "", "us-west-2", "", true, util.RecursiveNameservers}, + args: []interface{}{"", "", "", "us-west-2", "", "", true, util.RecursiveNameservers}, }, }, }, @@ -517,9 +612,11 @@ func TestRoute53AmbientCreds(t *testing.T) { }, }, }, - Issuer: newIssuer("test", "default"), dnsProviders: newFakeDNSProviders(), Challenge: &cmacme.Challenge{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: fakeIssuerNamespace, + }, Spec: cmacme.ChallengeSpec{ Solver: cmacme.ACMEChallengeSolver{ DNS01: &cmacme.ACMEChallengeSolverDNS01{ @@ -528,13 +625,16 @@ func TestRoute53AmbientCreds(t *testing.T) { }, }, }, + IssuerRef: cmmeta.IssuerReference{ + Name: "test-issuer", + }, }, }, }, result{ expectedCall: &fakeDNSProviderCall{ name: "route53", - args: []interface{}{"", "", "", "us-west-2", "", false, util.RecursiveNameservers}, + args: []interface{}{"", "", "", "us-west-2", "", "", false, util.RecursiveNameservers}, }, }, }, @@ -545,7 +645,7 @@ func TestRoute53AmbientCreds(t *testing.T) { f.Setup(t) defer f.Finish(t) s := f.Solver - _, _, err := s.solverForChallenge(context.Background(), f.Issuer, f.Challenge) + _, _, err := s.solverForChallenge(t.Context(), f.Challenge) if tt.out.expectedErr != err { t.Fatalf("expected error %v, got error %v", tt.out.expectedErr, err) } @@ -580,25 +680,21 @@ func TestRoute53AssumeRole(t *testing.T) { }, }, }, - Issuer: newIssuer("test", "default"), dnsProviders: newFakeDNSProviders(), - Challenge: &cmacme.Challenge{ - Spec: cmacme.ChallengeSpec{ - Solver: cmacme.ACMEChallengeSolver{ - DNS01: &cmacme.ACMEChallengeSolverDNS01{ - Route53: &cmacme.ACMEIssuerDNS01ProviderRoute53{ - Region: "us-west-2", - Role: "my-role", - }, - }, - }, - }, - }, + Challenge: gen.Challenge("", + gen.SetChallengeNamespace(fakeIssuerNamespace), + gen.SetChallengeIssuer(cmmeta.IssuerReference{Name: "test-issuer"}), + gen.SetChallengeSolverDNS01(cmacme.ACMEChallengeSolverDNS01{ + Route53: &cmacme.ACMEIssuerDNS01ProviderRoute53{ + Region: "us-west-2", + Role: "my-role", + }}), + ), }, result{ expectedCall: &fakeDNSProviderCall{ name: "route53", - args: []interface{}{"", "", "", "us-west-2", "my-role", true, util.RecursiveNameservers}, + args: []interface{}{"", "", "", "us-west-2", "my-role", "", true, util.RecursiveNameservers}, }, }, }, @@ -614,9 +710,11 @@ func TestRoute53AssumeRole(t *testing.T) { }, }, }, - Issuer: newIssuer("test", "default"), dnsProviders: newFakeDNSProviders(), Challenge: &cmacme.Challenge{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: fakeIssuerNamespace, + }, Spec: cmacme.ChallengeSpec{ Solver: cmacme.ACMEChallengeSolver{ DNS01: &cmacme.ACMEChallengeSolverDNS01{ @@ -626,13 +724,16 @@ func TestRoute53AssumeRole(t *testing.T) { }, }, }, + IssuerRef: cmmeta.IssuerReference{ + Name: "test-issuer", + }, }, }, }, result{ expectedCall: &fakeDNSProviderCall{ name: "route53", - args: []interface{}{"", "", "", "us-west-2", "my-other-role", false, util.RecursiveNameservers}, + args: []interface{}{"", "", "", "us-west-2", "my-other-role", "", false, util.RecursiveNameservers}, }, }, }, @@ -643,7 +744,7 @@ func TestRoute53AssumeRole(t *testing.T) { f.Setup(t) defer f.Finish(t) s := f.Solver - _, _, err := s.solverForChallenge(context.Background(), f.Issuer, f.Challenge) + _, _, err := s.solverForChallenge(t.Context(), f.Challenge) if tt.out.expectedErr != err { t.Fatalf("expected error %v, got error %v", tt.out.expectedErr, err) } diff --git a/pkg/issuer/acme/dns/rfc2136/provider.go b/pkg/issuer/acme/dns/rfc2136/provider.go index be68ee665de..94dfbc53f3f 100644 --- a/pkg/issuer/acme/dns/rfc2136/provider.go +++ b/pkg/issuer/acme/dns/rfc2136/provider.go @@ -27,14 +27,19 @@ import ( corelisters "k8s.io/client-go/listers/core/v1" restclient "k8s.io/client-go/rest" + internalinformers "github.com/cert-manager/cert-manager/internal/informers" whapi "github.com/cert-manager/cert-manager/pkg/acme/webhook/apis/acme/v1alpha1" cmacme "github.com/cert-manager/cert-manager/pkg/apis/acme/v1" cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" logf "github.com/cert-manager/cert-manager/pkg/logs" ) +const SolverName = "rfc2136" + type Solver struct { - secretLister corelisters.SecretLister + secretLister internalinformers.SecretLister + // options to apply when the lister gets initialized + initOpts []Option // If specified, namespace will cause the rfc2136 provider to limit the // scope of the lister/watcher to a single namespace, to allow for @@ -50,6 +55,27 @@ func WithNamespace(ns string) Option { } } +func WithSecretsLister(secretLister internalinformers.SecretLister) Option { + return func(s *Solver) { + s.secretLister = secretLister + } +} + +// InitializeResetLister is a hack to make RFC2136 solver fit the Solver +// interface. Unlike external solvers that are run as apiserver implementations, +// this solver is created as part of challenge controller initialization. That +// makes its Initialize method not fit the Solver interface very well as we want +// a way to initialize the solver with the existing Secrets lister rather than a +// new kube apiserver client. InitializeResetLister allows to reset secrets +// lister when Initialize function is called so that a new lister can be +// created. This is useful in tests where a kube clientset can get recreated for +// an existing solver (which would not happen when this solver runs normally). +func InitializeResetLister() Option { + return func(s *Solver) { + s.initOpts = []Option{func(s *Solver) { s.secretLister = nil }} + } +} + func New(opts ...Option) *Solver { s := &Solver{} for _, o := range opts { @@ -59,7 +85,7 @@ func New(opts ...Option) *Solver { } func (s *Solver) Name() string { - return "rfc2136" + return SolverName } func (s *Solver) Present(ch *whapi.ChallengeRequest) error { @@ -91,18 +117,25 @@ func (s *Solver) CleanUp(ch *whapi.ChallengeRequest) error { } func (s *Solver) Initialize(kubeClientConfig *restclient.Config, stopCh <-chan struct{}) error { - cl, err := kubernetes.NewForConfig(kubeClientConfig) - if err != nil { - return err + for _, opt := range s.initOpts { + opt(s) + } + // Only start a secrets informerfactory if it is needed (if the solver + // is not already initialized with a secrets lister) This is legacy + // functionality and is currently only used in integration tests. + if s.secretLister == nil { + cl, err := kubernetes.NewForConfig(kubeClientConfig) + if err != nil { + return err + } + + // obtain a secret lister and start the informer factory to populate the + // secret cache + factory := informers.NewSharedInformerFactoryWithOptions(cl, time.Minute*5, informers.WithNamespace(s.namespace)) + s.secretLister = factory.Core().V1().Secrets().Lister() + factory.Start(stopCh) + factory.WaitForCacheSync(stopCh) } - - // obtain a secret lister and start the informer factory to populate the - // secret cache - factory := informers.NewSharedInformerFactoryWithOptions(cl, time.Minute*5, informers.WithNamespace(s.namespace)) - s.secretLister = factory.Core().V1().Secrets().Lister() - factory.Start(stopCh) - factory.WaitForCacheSync(stopCh) - return nil } @@ -157,5 +190,5 @@ func (s *Solver) buildDNSProvider(ch *whapi.ChallengeRequest) (*DNSProvider, err key = string(secret) } - return NewDNSProviderCredentials(cfg.Nameserver, cfg.TSIGAlgorithm, cfg.TSIGKeyName, key) + return NewDNSProviderCredentials(cfg.Nameserver, cfg.TSIGAlgorithm, cfg.TSIGKeyName, key, WithNetwork(string(cfg.Protocol))) } diff --git a/pkg/issuer/acme/dns/rfc2136/rfc2136.go b/pkg/issuer/acme/dns/rfc2136/rfc2136.go index 4c192e56b37..9384a8ee047 100644 --- a/pkg/issuer/acme/dns/rfc2136/rfc2136.go +++ b/pkg/issuer/acme/dns/rfc2136/rfc2136.go @@ -45,19 +45,38 @@ var supportedAlgorithms = map[string]string{ type DNSProvider struct { nameserver string tsigAlgorithm string + network string tsigKeyName string tsigSecret string } +// ProviderOption is some configuration that modifies rfc2136 DNS provider. +// It is meant to replace optional parameters and move towards function parameters and higher order functions. +// Currently only protocol is migrated and future work is to be done on this. +type ProviderOption func(*DNSProvider) + +func WithNetwork(network string) ProviderOption { + return func(d *DNSProvider) { + if network == "" { + network = "udp" + } + d.network = strings.ToLower(network) + } +} + // NewDNSProviderCredentials uses the supplied credentials to return a // DNSProvider instance configured for rfc2136 dynamic update. To disable TSIG // authentication, leave the TSIG parameters as empty strings. // nameserver must be a network address in the form "IP" or "IP:port". -func NewDNSProviderCredentials(nameserver, tsigAlgorithm, tsigKeyName, tsigSecret string) (*DNSProvider, error) { +func NewDNSProviderCredentials(nameserver, tsigAlgorithm, tsigKeyName, tsigSecret string, opts ...ProviderOption) (*DNSProvider, error) { logf.Log.V(logf.DebugLevel).Info("Creating RFC2136 Provider") d := &DNSProvider{} + for _, opt := range opts { + opt(d) + } + if validNameserver, err := util.ValidNameserver(nameserver); err != nil { return nil, err } else { @@ -76,21 +95,22 @@ func NewDNSProviderCredentials(nameserver, tsigAlgorithm, tsigKeyName, tsigSecre tsigAlgorithm = value } else { return nil, fmt.Errorf("algorithm '%v' is not supported", tsigAlgorithm) - } } d.tsigAlgorithm = tsigAlgorithm - logf.V(logf.DebugLevel).Infof("DNSProvider nameserver: %s\n", d.nameserver) - logf.V(logf.DebugLevel).Infof(" tsigAlgorithm: %s\n", d.tsigAlgorithm) - logf.V(logf.DebugLevel).Infof(" tsigKeyName: %s\n", d.tsigKeyName) keyLen := len(d.tsigSecret) mask := make([]rune, keyLen/2) for i := range mask { mask[i] = '*' } masked := d.tsigSecret[0:keyLen/4] + string(mask) + d.tsigSecret[keyLen/4*3:keyLen] - logf.V(logf.DebugLevel).Infof(" tsigSecret: %s\n", masked) + logf.Log.V(logf.DebugLevel).Info("DNSProvider", + "nameserver", d.nameserver, + "tsigAlgorithm", d.tsigAlgorithm, + "tsigKeyName", d.tsigKeyName, + "tsigSecret", masked, + ) return d, nil } @@ -105,10 +125,10 @@ func (r *DNSProvider) CleanUp(_, fqdn, zone, value string) error { return r.changeRecord("REMOVE", fqdn, zone, value, 60) } -func (r *DNSProvider) changeRecord(action, fqdn, zone, value string, ttl int) error { +func (r *DNSProvider) changeRecord(action, fqdn, zone, value string, ttl uint32) error { // Create RR rr := new(dns.TXT) - rr.Hdr = dns.RR_Header{Name: fqdn, Rrtype: dns.TypeTXT, Class: dns.ClassINET, Ttl: uint32(ttl)} + rr.Hdr = dns.RR_Header{Name: fqdn, Rrtype: dns.TypeTXT, Class: dns.ClassINET, Ttl: ttl} rr.Txt = []string{value} rrs := []dns.RR{rr} @@ -125,9 +145,8 @@ func (r *DNSProvider) changeRecord(action, fqdn, zone, value string, ttl int) er } // Setup client - c := new(dns.Client) + c := &dns.Client{Net: r.network} c.TsigProvider = tsigHMACProvider(r.tsigSecret) - c.SingleInflight = true // TSIG authentication / msg signing if len(r.tsigKeyName) > 0 && len(r.tsigSecret) > 0 { m.SetTsig(dns.Fqdn(r.tsigKeyName), r.tsigAlgorithm, 300, time.Now().Unix()) diff --git a/pkg/issuer/acme/dns/rfc2136/tsig.go b/pkg/issuer/acme/dns/rfc2136/tsig.go index 15a99709122..152e42232d2 100644 --- a/pkg/issuer/acme/dns/rfc2136/tsig.go +++ b/pkg/issuer/acme/dns/rfc2136/tsig.go @@ -21,8 +21,8 @@ package rfc2136 import ( "crypto/hmac" - "crypto/md5" - "crypto/sha1" + "crypto/md5" // #nosec G501 -- MD5 is a supported TSIG Algorithm + "crypto/sha1" // #nosec G505 -- SHA1 is a supported TSIG Algorithm "crypto/sha256" "crypto/sha512" "encoding/base64" diff --git a/pkg/issuer/acme/dns/rfc2136/tsig_test.go b/pkg/issuer/acme/dns/rfc2136/tsig_test.go index 0ce8cabfa58..5cb723e606b 100644 --- a/pkg/issuer/acme/dns/rfc2136/tsig_test.go +++ b/pkg/issuer/acme/dns/rfc2136/tsig_test.go @@ -21,8 +21,8 @@ package rfc2136 import ( "crypto/hmac" - "crypto/md5" - "crypto/sha1" + "crypto/md5" // #nosec G501 -- MD5 is a supported TSIG Algorithm + "crypto/sha1" // #nosec G505 -- SHA1 is a supported TSIG Algorithm "crypto/sha256" "crypto/sha512" "encoding/base64" @@ -34,7 +34,7 @@ import ( func Test_tsigHMACProvider_Generate(t *testing.T) { var ( - someSecret = base64.StdEncoding.EncodeToString(([]byte("foo-secret"))) + someSecret = base64.StdEncoding.EncodeToString([]byte("foo-secret")) someMessage = "foo-message" someMessageMD5 = md5Message(t, []byte("foo-secret"), []byte("foo-message")) someMessageSHA1 = sha1Message(t, []byte("foo-secret"), []byte("foo-message")) diff --git a/pkg/issuer/acme/dns/route53/fixtures_test.go b/pkg/issuer/acme/dns/route53/fixtures_test.go index 248f81cbf65..186217b2a33 100644 --- a/pkg/issuer/acme/dns/route53/fixtures_test.go +++ b/pkg/issuer/acme/dns/route53/fixtures_test.go @@ -57,6 +57,19 @@ var ListHostedZonesByNameResponse = ` 1 ` +// An example of an error returned by the ListHostedZonesByName API when the +// request contains an invalid domain name: +// - https://docs.aws.amazon.com/Route53/latest/APIReference/API_ListHostedZonesByName.html#API_ListHostedZonesByName_Errors +var ListHostedZonesByName400ResponseInvalidDomainName = ` + + + InvalidDomainName + Simulated message + + SOMEREQUESTID + +` + var GetChangeResponse = ` @@ -75,3 +88,32 @@ var ChangeResourceRecordSets403Response = ` SOMEREQUESTID ` + +// An example of an error returned by the ChangeResourceRecordSets API when the +// request refers to a record set that does not exist: +// - https://docs.aws.amazon.com/Route53/latest/APIReference/API_ChangeResourceRecordSets.html#API_ChangeResourceRecordSets_Errors +// +// This sample XML error was obtained by capturing an API response from AWS +// using `mitmproxy`, while running `HTTPS_PROXY=localhost:8080 go test ./pkg/issuer/acme/dns/route53/... - v -run Test_Cleanup`, +// with the following ad-hoc Go test: +// +// func Test_Cleanup(t *testing.T) { +// l := ktesting.NewLogger(t, ktesting.NewConfig(ktesting.Verbosity(10))) +// ctx := logr.NewContext(context.Background(), l) +// p, err := NewDNSProvider(ctx, "", "", "Z0984294TRL0R8AT3SQA", "", "", "", true, []string{}, "cert-manager/tests") +// require.NoError(t, err) +// err = p.CleanUp(ctx, "example.com", "www", "foo") +// require.NoError(t, err) +// } +// +// NB: This does not match the example in the following file, which may just be out-of-date: +// - https://github.com/aws/aws-sdk-go-v2/blob/f529add9a2cd0d97281fd81f711c620c1e95cfb8/service/route53/internal/customizations/doc.go#L16C1-L21C25 +var ChangeResourceRecordSets400Response = ` + + + Sender + InvalidChangeBatch + Tried to delete resource record set [name='_acme-challenge.example.com.', type='TXT', set-identifier='"5CiRHXrp9tvpLNX8F9M8qbi8u9kwb3xnHrKdLNDlRQA"'] but it was not found + + SOMEREQUESTID +` diff --git a/pkg/issuer/acme/dns/route53/route53.go b/pkg/issuer/acme/dns/route53/route53.go index dd18fba6f0b..635ec3507e5 100644 --- a/pkg/issuer/acme/dns/route53/route53.go +++ b/pkg/issuer/acme/dns/route53/route53.go @@ -11,23 +11,25 @@ this directory. package route53 import ( + "context" + "errors" "fmt" "strings" "time" - logf "github.com/cert-manager/cert-manager/pkg/logs" - - "github.com/go-logr/logr" + "github.com/aws/aws-sdk-go-v2/aws" + awsmiddleware "github.com/aws/aws-sdk-go-v2/aws/middleware" + awshttp "github.com/aws/aws-sdk-go-v2/aws/transport/http" + "github.com/aws/aws-sdk-go-v2/config" + "github.com/aws/aws-sdk-go-v2/credentials" + "github.com/aws/aws-sdk-go-v2/service/route53" + route53types "github.com/aws/aws-sdk-go-v2/service/route53/types" + "github.com/aws/aws-sdk-go-v2/service/sts" + "github.com/aws/smithy-go/logging" + "github.com/aws/smithy-go/middleware" - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/aws/awserr" - "github.com/aws/aws-sdk-go/aws/credentials" - "github.com/aws/aws-sdk-go/aws/request" - "github.com/aws/aws-sdk-go/aws/session" - "github.com/aws/aws-sdk-go/service/route53" - "github.com/aws/aws-sdk-go/service/sts" - "github.com/aws/aws-sdk-go/service/sts/stsiface" "github.com/cert-manager/cert-manager/pkg/issuer/acme/dns/util" + logf "github.com/cert-manager/cert-manager/pkg/logs" ) const ( @@ -37,153 +39,229 @@ const ( // DNSProvider implements the util.ChallengeProvider interface type DNSProvider struct { dns01Nameservers []string - client *route53.Route53 + client *route53.Client hostedZoneID string - log logr.Logger - - userAgent string + userAgent string } type sessionProvider struct { - AccessKeyID string - SecretAccessKey string - Ambient bool - Region string - Role string - StsProvider func(*session.Session) stsiface.STSAPI - log logr.Logger - userAgent string + AccessKeyID string + SecretAccessKey string + Ambient bool + Region string + Role string + WebIdentityToken string + StsProvider func(aws.Config) StsClient + userAgent string } -func (d *sessionProvider) GetSession() (*session.Session, error) { - if d.AccessKeyID == "" && d.SecretAccessKey == "" { - if !d.Ambient { - return nil, fmt.Errorf("unable to construct route53 provider: empty credentials; perhaps you meant to enable ambient credentials?") +type StsClient interface { + AssumeRole(ctx context.Context, params *sts.AssumeRoleInput, optFns ...func(*sts.Options)) (*sts.AssumeRoleOutput, error) + AssumeRoleWithWebIdentity(ctx context.Context, params *sts.AssumeRoleWithWebIdentityInput, optFns ...func(*sts.Options)) (*sts.AssumeRoleWithWebIdentityOutput, error) +} + +func (d *sessionProvider) GetSession(ctx context.Context) (aws.Config, error) { + switch { + case d.Role == "" && d.WebIdentityToken != "": + return aws.Config{}, fmt.Errorf("unable to construct route53 provider: role must be set when web identity token is set") + case d.AccessKeyID == "" && d.SecretAccessKey == "": + if !d.Ambient && d.WebIdentityToken == "" { + return aws.Config{}, fmt.Errorf("unable to construct route53 provider: empty credentials; perhaps you meant to enable ambient credentials?") } - } else if d.AccessKeyID == "" || d.SecretAccessKey == "" { + case d.AccessKeyID == "" || d.SecretAccessKey == "": // It's always an error to set one of those but not the other - return nil, fmt.Errorf("unable to construct route53 provider: only one of access and secret key was provided") + return aws.Config{}, fmt.Errorf("unable to construct route53 provider: only one of access and secret key was provided") } - useAmbientCredentials := d.Ambient && (d.AccessKeyID == "" && d.SecretAccessKey == "") + useAmbientCredentials := d.Ambient && (d.AccessKeyID == "" && d.SecretAccessKey == "") && d.WebIdentityToken == "" - config := aws.NewConfig() - sessionOpts := session.Options{ - Config: *config, + log := logf.FromContext(ctx) + optFns := []func(*config.LoadOptions) error{ + // Print AWS API requests but only at cert-manager debug level + config.WithLogger(logging.LoggerFunc(func(classification logging.Classification, format string, v ...interface{}) { + log := log.WithValues("aws-classification", classification) + if classification == logging.Debug { + log = log.V(logf.DebugLevel) + } + log.Info(fmt.Sprintf(format, v...)) + })), + config.WithClientLogMode(aws.LogDeprecatedUsage | aws.LogRequest), + config.WithLogConfigurationWarnings(true), + // Append cert-manager user-agent string to all AWS API requests + config.WithAPIOptions( + []func(*middleware.Stack) error{ + func(stack *middleware.Stack) error { + return awsmiddleware.AddUserAgentKeyValue("cert-manager", d.userAgent)(stack) + }, + }, + ), } - if useAmbientCredentials { - d.log.V(logf.DebugLevel).Info("using ambient credentials") + var envRegionFound bool + { + envConfig, err := config.NewEnvConfig() + if err != nil { + return aws.Config{}, err + } + envRegionFound = envConfig.Region != "" + } + + if !envRegionFound && d.Region == "" { + log.Info( + "Region not found", + "reason", "The AWS_REGION or AWS_DEFAULT_REGION environment variables were not set and the Issuer region field was empty", + ) + } + + if d.Region != "" { + if envRegionFound && useAmbientCredentials { + log.Info( + "Ignoring Issuer region", + "reason", "Issuer is configured to use ambient credentials and AWS_REGION or AWS_DEFAULT_REGION environment variables were found", + "suggestion", "Since cert-manager 1.16, the Issuer region field is optional and can be removed from your Issuer or ClusterIssuer", + "issuer-region", d.Region, + ) + } else { + optFns = append(optFns, + config.WithRegion(d.Region), + ) + } + } + + switch { + case d.Role != "" && d.WebIdentityToken != "": + log.V(logf.DebugLevel).Info("using assume role with web identity") + case useAmbientCredentials: + log.V(logf.DebugLevel).Info("using ambient credentials") // Leaving credentials unset results in a default credential chain being // used; this chain is a reasonable default for getting ambient creds. // https://docs.aws.amazon.com/sdk-for-go/v1/developer-guide/configuring-sdk.html#specifying-credentials - } else { - d.log.V(logf.DebugLevel).Info("not using ambient credentials") - sessionOpts.Config.Credentials = credentials.NewStaticCredentials(d.AccessKeyID, d.SecretAccessKey, "") - // also disable 'ambient' region sources - sessionOpts.SharedConfigState = session.SharedConfigDisable + default: + log.V(logf.DebugLevel).Info("not using ambient credentials") + optFns = append(optFns, config.WithCredentialsProvider(credentials.NewStaticCredentialsProvider(d.AccessKeyID, d.SecretAccessKey, ""))) } - sess, err := session.NewSessionWithOptions(sessionOpts) + cfg, err := config.LoadDefaultConfig(ctx, optFns...) if err != nil { - return nil, fmt.Errorf("unable to create aws session: %s", err) + return aws.Config{}, fmt.Errorf("unable to create aws config: %s", err) } - if d.Role != "" { - d.log.V(logf.DebugLevel).WithValues("role", d.Role).Info("assuming role") - stsSvc := d.StsProvider(sess) - result, err := stsSvc.AssumeRole(&sts.AssumeRoleInput{ + if d.Role != "" && d.WebIdentityToken == "" { + log.V(logf.DebugLevel).WithValues("role", d.Role).Info("assuming role") + stsSvc := d.StsProvider(cfg) + result, err := stsSvc.AssumeRole(ctx, &sts.AssumeRoleInput{ RoleArn: aws.String(d.Role), RoleSessionName: aws.String("cert-manager"), }) if err != nil { - return nil, fmt.Errorf("unable to assume role: %s", err) + return aws.Config{}, fmt.Errorf("unable to assume role: %s", removeReqID(err)) } - creds := credentials.Value{ - AccessKeyID: *result.Credentials.AccessKeyId, - SecretAccessKey: *result.Credentials.SecretAccessKey, - SessionToken: *result.Credentials.SessionToken, - } - sessionOpts.Config.Credentials = credentials.NewStaticCredentialsFromCreds(creds) + cfg.Credentials = credentials.NewStaticCredentialsProvider( + *result.Credentials.AccessKeyId, + *result.Credentials.SecretAccessKey, + *result.Credentials.SessionToken, + ) + } + + if d.Role != "" && d.WebIdentityToken != "" { + log.V(logf.DebugLevel).WithValues("role", d.Role).Info("assuming role with web identity") - sess, err = session.NewSessionWithOptions(sessionOpts) + stsSvc := d.StsProvider(cfg) + result, err := stsSvc.AssumeRoleWithWebIdentity(ctx, &sts.AssumeRoleWithWebIdentityInput{ + RoleArn: aws.String(d.Role), + RoleSessionName: aws.String("cert-manager"), + WebIdentityToken: aws.String(d.WebIdentityToken), + }) if err != nil { - return nil, fmt.Errorf("unable to create aws session: %s", err) + return aws.Config{}, fmt.Errorf("unable to assume role with web identity: %s", removeReqID(err)) } - } - // If ambient credentials aren't permitted, always set the region, even if to - // empty string, to avoid it falling back on the environment. - // this has to be set after session is constructed - if d.Region != "" || !useAmbientCredentials { - sess.Config.WithRegion(d.Region) + cfg.Credentials = credentials.NewStaticCredentialsProvider( + *result.Credentials.AccessKeyId, + *result.Credentials.SecretAccessKey, + *result.Credentials.SessionToken, + ) } - sess.Handlers.Build.PushBack(request.WithAppendUserAgent(d.userAgent)) - return sess, nil + // Log some key values of the loaded configuration, so that users can + // self-diagnose problems in the field. If users shared logs in their bug + // reports, we can know whether the region was detected and whether an + // alternative defaults mode has been configured. + // + // TODO(wallrj): Loop through the cfg.ConfigSources and log which config + // source was used to load the region and credentials, so that it is clearer + // to the user where environment variables or config files or IMDS metadata + // are being used. + log.V(logf.DebugLevel).Info( + "loaded-config", + "defaults-mode", cfg.DefaultsMode, + "region", cfg.Region, + "runtime-environment", cfg.RuntimeEnvironment, + ) + + return cfg, nil } -func newSessionProvider(accessKeyID, secretAccessKey, region, role string, ambient bool, userAgent string) (*sessionProvider, error) { +func newSessionProvider(accessKeyID, secretAccessKey, region, role string, webIdentityToken string, ambient bool, userAgent string) *sessionProvider { return &sessionProvider{ - AccessKeyID: accessKeyID, - SecretAccessKey: secretAccessKey, - Ambient: ambient, - Region: region, - Role: role, - StsProvider: defaultSTSProvider, - log: logf.Log.WithName("route53-session-provider"), - userAgent: userAgent, - }, nil + AccessKeyID: accessKeyID, + SecretAccessKey: secretAccessKey, + Ambient: ambient, + Region: region, + Role: role, + WebIdentityToken: webIdentityToken, + StsProvider: defaultSTSProvider, + userAgent: userAgent, + } } -func defaultSTSProvider(sess *session.Session) stsiface.STSAPI { - return sts.New(sess) +func defaultSTSProvider(cfg aws.Config) StsClient { + return sts.NewFromConfig(cfg) } // NewDNSProvider returns a DNSProvider instance configured for the AWS // Route 53 service using static credentials from its parameters or, if they're // unset and the 'ambient' option is set, credentials from the environment. -func NewDNSProvider(accessKeyID, secretAccessKey, hostedZoneID, region, role string, +func NewDNSProvider( + ctx context.Context, + accessKeyID, secretAccessKey, hostedZoneID, region, role, webIdentityToken string, ambient bool, dns01Nameservers []string, userAgent string, ) (*DNSProvider, error) { - provider, err := newSessionProvider(accessKeyID, secretAccessKey, region, role, ambient, userAgent) - if err != nil { - return nil, err - } + provider := newSessionProvider(accessKeyID, secretAccessKey, region, role, webIdentityToken, ambient, userAgent) - sess, err := provider.GetSession() + cfg, err := provider.GetSession(ctx) if err != nil { return nil, err } - client := route53.New(sess) + client := route53.NewFromConfig(cfg) return &DNSProvider{ client: client, hostedZoneID: hostedZoneID, dns01Nameservers: dns01Nameservers, - log: logf.Log.WithName("route53"), userAgent: userAgent, }, nil } // Present creates a TXT record using the specified parameters -func (r *DNSProvider) Present(domain, fqdn, value string) error { +func (r *DNSProvider) Present(ctx context.Context, domain, fqdn, value string) error { value = `"` + value + `"` - return r.changeRecord(route53.ChangeActionUpsert, fqdn, value, route53TTL) + return r.changeRecord(ctx, route53types.ChangeActionUpsert, fqdn, value, route53TTL) } // CleanUp removes the TXT record matching the specified parameters -func (r *DNSProvider) CleanUp(domain, fqdn, value string) error { +func (r *DNSProvider) CleanUp(ctx context.Context, domain, fqdn, value string) error { value = `"` + value + `"` - return r.changeRecord(route53.ChangeActionDelete, fqdn, value, route53TTL) + return r.changeRecord(ctx, route53types.ChangeActionDelete, fqdn, value, route53TTL) } -func (r *DNSProvider) changeRecord(action, fqdn, value string, ttl int) error { - hostedZoneID, err := r.getHostedZoneID(fqdn) +func (r *DNSProvider) changeRecord(ctx context.Context, action route53types.ChangeAction, fqdn, value string, ttl int) error { + log := logf.FromContext(ctx) + hostedZoneID, err := r.getHostedZoneID(ctx, fqdn) if err != nil { return fmt.Errorf("failed to determine Route 53 hosted zone ID: %v", err) } @@ -191,26 +269,29 @@ func (r *DNSProvider) changeRecord(action, fqdn, value string, ttl int) error { recordSet := newTXTRecordSet(fqdn, value, ttl) reqParams := &route53.ChangeResourceRecordSetsInput{ HostedZoneId: aws.String(hostedZoneID), - ChangeBatch: &route53.ChangeBatch{ + ChangeBatch: &route53types.ChangeBatch{ Comment: aws.String("Managed by cert-manager"), - Changes: []*route53.Change{ + Changes: []route53types.Change{ { - Action: &action, + Action: action, ResourceRecordSet: recordSet, }, }, }, } - resp, err := r.client.ChangeResourceRecordSets(reqParams) + resp, err := r.client.ChangeResourceRecordSets(ctx, reqParams) if err != nil { - if awserr, ok := err.(awserr.Error); ok { - if action == route53.ChangeActionDelete && awserr.Code() == route53.ErrCodeInvalidChangeBatch { - r.log.V(logf.DebugLevel).WithValues("error", err).Info("ignoring InvalidChangeBatch error") - // If we try to delete something and get a 'InvalidChangeBatch' that - // means it's already deleted, no need to consider it an error. - return nil - } + // If we try to delete something and get a 'InvalidChangeBatch' that + // means it's already deleted, no need to consider it an error. + var apiErr *route53types.InvalidChangeBatch + if errors.As(err, &apiErr) && action == route53types.ChangeActionDelete { + log.V(logf.DebugLevel).Info( + "Got InvalidChangeBatch error when attempting to delete the TXT record. "+ + "Ignoring the error and assuming that the TXT record has already been deleted.", + "error", err, + ) + return nil } return fmt.Errorf("failed to change Route 53 record set: %v", removeReqID(err)) @@ -222,23 +303,23 @@ func (r *DNSProvider) changeRecord(action, fqdn, value string, ttl int) error { reqParams := &route53.GetChangeInput{ Id: statusID, } - resp, err := r.client.GetChange(reqParams) + resp, err := r.client.GetChange(ctx, reqParams) if err != nil { return false, fmt.Errorf("failed to query Route 53 change status: %v", removeReqID(err)) } - if *resp.ChangeInfo.Status == route53.ChangeStatusInsync { + if resp.ChangeInfo.Status == route53types.ChangeStatusInsync { return true, nil } return false, nil }) } -func (r *DNSProvider) getHostedZoneID(fqdn string) (string, error) { +func (r *DNSProvider) getHostedZoneID(ctx context.Context, fqdn string) (string, error) { if r.hostedZoneID != "" { return r.hostedZoneID, nil } - authZone, err := util.FindZoneByFqdn(fqdn, r.dns01Nameservers) + authZone, err := util.FindZoneByFqdn(ctx, fqdn, r.dns01Nameservers) if err != nil { return "", fmt.Errorf("error finding zone from fqdn: %v", err) } @@ -247,7 +328,7 @@ func (r *DNSProvider) getHostedZoneID(fqdn string) (string, error) { reqParams := &route53.ListHostedZonesByNameInput{ DNSName: aws.String(util.UnFqdn(authZone)), } - resp, err := r.client.ListHostedZonesByName(reqParams) + resp, err := r.client.ListHostedZonesByName(ctx, reqParams) if err != nil { return "", removeReqID(err) } @@ -256,7 +337,7 @@ func (r *DNSProvider) getHostedZoneID(fqdn string) (string, error) { var hostedZones []string for _, hostedZone := range resp.HostedZones { // .Name has a trailing dot - if !*hostedZone.Config.PrivateZone { + if !hostedZone.Config.PrivateZone { zoneToID[*hostedZone.Name] = *hostedZone.Id hostedZones = append(hostedZones, *hostedZone.Name) } @@ -272,21 +353,19 @@ func (r *DNSProvider) getHostedZoneID(fqdn string) (string, error) { return "", fmt.Errorf("zone %s not found in Route 53 for domain %s", authZone, fqdn) } - if strings.HasPrefix(hostedZoneID, "/hostedzone/") { - hostedZoneID = strings.TrimPrefix(hostedZoneID, "/hostedzone/") - } + hostedZoneID = strings.TrimPrefix(hostedZoneID, "/hostedzone/") return hostedZoneID, nil } -func newTXTRecordSet(fqdn, value string, ttl int) *route53.ResourceRecordSet { - return &route53.ResourceRecordSet{ +func newTXTRecordSet(fqdn, value string, ttl int) *route53types.ResourceRecordSet { + return &route53types.ResourceRecordSet{ Name: aws.String(fqdn), - Type: aws.String(route53.RRTypeTxt), + Type: route53types.RRTypeTxt, TTL: aws.Int64(int64(ttl)), MultiValueAnswer: aws.Bool(true), SetIdentifier: aws.String(value), - ResourceRecords: []*route53.ResourceRecord{ + ResourceRecords: []route53types.ResourceRecord{ {Value: aws.String(value)}, }, } @@ -296,19 +375,16 @@ func newTXTRecordSet(fqdn, value string, ttl int) *route53.ResourceRecordSet { // want our error messages to be the same when the cause is the same to // avoid spurious challenge updates. // -// The given error must not be nil. This function must be called everywhere -// we have a non-nil error coming from an aws-sdk-go func. +// This function must be called everywhere we have an error coming from +// an aws-sdk-go func. The passed error is modified in place. func removeReqID(err error) error { - // NOTE(mael): I first tried to unwrap the RequestFailure to get rid of - // this request id. But the concrete type requestFailure is private, so - // I can't unwrap it. Instead, I recreate a new awserr.baseError. It's - // also a awserr.Error except it doesn't have the request id. - // - // Also note that we do not give the origErr to awserr.New. If we did, - // err.Error() would show the origErr, which we don't want since it - // contains a request id. - if e, ok := err.(awserr.RequestFailure); ok { - return awserr.New(e.Code(), e.Message(), nil) + var responseError *awshttp.ResponseError + if errors.As(err, &responseError) { + before := responseError.Error() + // remove the request id from the error message + responseError.RequestID = "" + after := responseError.Error() + return errors.New(strings.Replace(err.Error(), before, after, 1)) } return err } diff --git a/pkg/issuer/acme/dns/route53/route53_test.go b/pkg/issuer/acme/dns/route53/route53_test.go index 85601392081..5154a01bbac 100644 --- a/pkg/issuer/acme/dns/route53/route53_test.go +++ b/pkg/issuer/acme/dns/route53/route53_test.go @@ -9,112 +9,249 @@ this directory. package route53 import ( + "context" "errors" "fmt" + "net/http" "net/http/httptest" - "os" "testing" - logf "github.com/cert-manager/cert-manager/pkg/logs" - - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/aws/awserr" - "github.com/aws/aws-sdk-go/aws/credentials" - "github.com/aws/aws-sdk-go/aws/session" - "github.com/aws/aws-sdk-go/service/route53" - "github.com/aws/aws-sdk-go/service/sts" - "github.com/aws/aws-sdk-go/service/sts/stsiface" + "github.com/aws/aws-sdk-go-v2/aws" + awshttp "github.com/aws/aws-sdk-go-v2/aws/transport/http" + "github.com/aws/aws-sdk-go-v2/config" + "github.com/aws/aws-sdk-go-v2/credentials" + "github.com/aws/aws-sdk-go-v2/service/route53" + "github.com/aws/aws-sdk-go-v2/service/sts" + ststypes "github.com/aws/aws-sdk-go-v2/service/sts/types" + "github.com/aws/smithy-go" + smithyhttp "github.com/aws/smithy-go/transport/http" + "github.com/go-logr/logr" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "k8s.io/klog/v2" + "k8s.io/klog/v2/ktesting" "github.com/cert-manager/cert-manager/pkg/issuer/acme/dns/util" ) -var ( - route53Secret string - route53Key string - route53Region string -) - -func init() { - route53Key = os.Getenv("AWS_ACCESS_KEY_ID") - route53Secret = os.Getenv("AWS_SECRET_ACCESS_KEY") - route53Region = os.Getenv("AWS_REGION") -} - -func restoreRoute53Env() { - os.Setenv("AWS_ACCESS_KEY_ID", route53Key) - os.Setenv("AWS_SECRET_ACCESS_KEY", route53Secret) - os.Setenv("AWS_REGION", route53Region) -} +const jwt string = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJzdHMuYW1hem9uYXdzLmNvbSIsImV4cCI6MTc0MTg4NzYwOCwiaWF0IjoxNzEwMzUxNjM4LCJpc3MiOiJodHRwczovL2V4YW1wbGUuY29tIiwibmFtZSI6IkpvaG4gRG9lIiwic3ViIjoiMTIzNDU2Nzg5MCJ9.SfuV3SW-vEdV-tLFIr2PK2DnN6QYmozygav5OeoH36Q" func makeRoute53Provider(ts *httptest.Server) (*DNSProvider, error) { - config := &aws.Config{ - Credentials: credentials.NewStaticCredentials("abc", "123", " "), - Endpoint: aws.String(ts.URL), - Region: aws.String("mock-region"), - MaxRetries: aws.Int(1), - } - - sess, err := session.NewSession(config) + cfg, err := config.LoadDefaultConfig( + context.TODO(), + config.WithCredentialsProvider(credentials.NewStaticCredentialsProvider("abc", "123", " ")), + config.WithRegion("mock-region"), + config.WithRetryMaxAttempts(1), + config.WithHTTPClient(ts.Client()), + ) if err != nil { return nil, err } - client := route53.New(sess) + + cfg.BaseEndpoint = aws.String(ts.URL) + + client := route53.NewFromConfig(cfg) return &DNSProvider{client: client, dns01Nameservers: util.RecursiveNameservers}, nil } func TestAmbientCredentialsFromEnv(t *testing.T) { - os.Setenv("AWS_ACCESS_KEY_ID", "123") - os.Setenv("AWS_SECRET_ACCESS_KEY", "123") - os.Setenv("AWS_REGION", "us-east-1") - defer restoreRoute53Env() + t.Setenv("AWS_ACCESS_KEY_ID", "123") + t.Setenv("AWS_SECRET_ACCESS_KEY", "123") + t.Setenv("AWS_REGION", "us-east-1") - provider, err := NewDNSProvider("", "", "", "", "", true, util.RecursiveNameservers, "cert-manager-test") + _, ctx := ktesting.NewTestContext(t) + provider, err := NewDNSProvider(ctx, "", "", "", "", "", "", true, util.RecursiveNameservers, "cert-manager-test") assert.NoError(t, err, "Expected no error constructing DNSProvider") - _, err = provider.client.Config.Credentials.Get() + _, err = provider.client.Options().Credentials.Retrieve(ctx) assert.NoError(t, err, "Expected credentials to be set from environment") - assert.Equal(t, provider.client.Config.Region, aws.String("us-east-1")) + + assert.Equal(t, provider.client.Options().Region, "us-east-1") } func TestNoCredentialsFromEnv(t *testing.T) { - os.Setenv("AWS_ACCESS_KEY_ID", "123") - os.Setenv("AWS_SECRET_ACCESS_KEY", "123") - os.Setenv("AWS_REGION", "us-east-1") - defer restoreRoute53Env() + t.Setenv("AWS_ACCESS_KEY_ID", "123") + t.Setenv("AWS_SECRET_ACCESS_KEY", "123") + t.Setenv("AWS_REGION", "us-east-1") - _, err := NewDNSProvider("", "", "", "", "", false, util.RecursiveNameservers, "cert-manager-test") + _, ctx := ktesting.NewTestContext(t) + _, err := NewDNSProvider(ctx, "", "", "", "", "", "", false, util.RecursiveNameservers, "cert-manager-test") assert.Error(t, err, "Expected error constructing DNSProvider with no credentials and not ambient") } -func TestAmbientRegionFromEnv(t *testing.T) { - os.Setenv("AWS_REGION", "us-east-1") - defer restoreRoute53Env() +type bitmask byte - provider, err := NewDNSProvider("", "", "", "", "", true, util.RecursiveNameservers, "cert-manager-test") - assert.NoError(t, err, "Expected no error constructing DNSProvider") - - assert.Equal(t, "us-east-1", *provider.client.Config.Region, "Expected Region to be set from environment") +func (haystack bitmask) Has(needle bitmask) bool { + return haystack&needle != 0 } -func TestNoRegionFromEnv(t *testing.T) { - os.Setenv("AWS_REGION", "us-east-1") - defer restoreRoute53Env() +// TestSessionProviderGetSessionRegion calls sessionProvider.GetSession with all +// permutations of those inputs that influence how it selects an AWS region. +// The desired region selection properties are documented alongside each +// assertion. +func TestSessionProviderGetSessionRegion(t *testing.T) { + const ( + fakeAmbientRegion = "ambient-region-1" + fakeIssuerRegion = "issuer-region-1" + ) + + testFunc := func(t *testing.T, allowAmbientCredentials, setAmbientRegion, supplyAccessKey, supplyWebIdentity, supplyIssuerRegion bool) { + t.Log( + "ambient-credentials-allowed", allowAmbientCredentials, + "ambient-region-set", setAmbientRegion, + "access-key-supplied", supplyAccessKey, + "web-identity-supplied", supplyWebIdentity, + "issuer-region-supplied", supplyIssuerRegion, + ) + var ( + accessKeyID string + secretAccessKey string + region string + role string + webIdentityToken string + userAgent string + ) + if supplyAccessKey { + accessKeyID = "fake-access-key-id" + secretAccessKey = "fake-secret-access-key" + } + if supplyWebIdentity { + webIdentityToken = "fake-web-identity-token" + role = "fake-web-identity-role" + } + if setAmbientRegion { + t.Setenv("AWS_REGION", fakeAmbientRegion) + } + if supplyIssuerRegion { + region = fakeIssuerRegion + } + + p := newSessionProvider(accessKeyID, secretAccessKey, region, role, webIdentityToken, allowAmbientCredentials, userAgent) + p.StsProvider = func(cfg aws.Config) StsClient { + return &mockSTS{ + AssumeRoleWithWebIdentityFn: func( + ctx context.Context, + params *sts.AssumeRoleWithWebIdentityInput, + optFns ...func(*sts.Options), + ) (*sts.AssumeRoleWithWebIdentityOutput, error) { + return &sts.AssumeRoleWithWebIdentityOutput{ + Credentials: &ststypes.Credentials{ + AccessKeyId: aws.String("fake-sts-access-key-id"), + SecretAccessKey: aws.String("fake-sts-secret-access-key"), + SessionToken: aws.String("fake-sts-session-token"), + }, + }, nil + }, + } + } - provider, err := NewDNSProvider("marx", "swordfish", "", "", "", false, util.RecursiveNameservers, "cert-manager-test") - assert.NoError(t, err, "Expected no error constructing DNSProvider") + logger := ktesting.NewLogger(t, ktesting.NewConfig(ktesting.BufferLogs(true))) + ctx := klog.NewContext(t.Context(), logger) + + cfg, err := p.GetSession(ctx) + + testingLogger, ok := logger.GetSink().(ktesting.Underlier) + require.True(t, ok) + logMessages := testingLogger.GetBuffer().String() + + if !supplyAccessKey && !supplyWebIdentity && !allowAmbientCredentials { + assert.EqualError(t, err, "unable to construct route53 provider: empty credentials; perhaps you meant to enable ambient credentials?") + return + } else { + require.NoError(t, err) + } + + // IRSA and Pod Identity are the most widely used "ambient credential" + // mechanisms and both use webhooks to inject the AWS_REGION environment + // variable into the cert-manager Pod. + // When ambient credentials are in use and an environment region is detected, + // cert-manager will use the environment region and ignore any Issuer region. + // This if for backwards compatibility with cert-manager < 1.16 where + // the Issuer region was a required field, but ignored. + if !supplyAccessKey && !supplyWebIdentity && setAmbientRegion { + assert.Equal(t, fakeAmbientRegion, cfg.Region, + "If using ambient credentials, and there is a region in the environment, "+ + "use the region from the environment. Ignore the region in the Issuer region.") + } + + // If the Issuer region has been ignored (see above), log an info + // message to alert the user that the Issuer region is no longer a + // required field and can be omitted in this situation. + if !supplyAccessKey && !supplyWebIdentity && setAmbientRegion && supplyIssuerRegion { + assert.Contains(t, logMessages, "Ignoring Issuer region", + "If using ambient credentials, and there is a region in the environment and in the Issuer resource, "+ + "log a warning to say the Issuer region will be ignored.") + } + + // In the case of ambient credentials from EC2 instance metadata service + // (IMDS), the AWS_REGION environment variable is not necessarily set + // and the Issuer region **should** be used. + if !supplyAccessKey && !supplyWebIdentity && !setAmbientRegion && supplyIssuerRegion { + assert.Equal(t, fakeIssuerRegion, cfg.Region, + "If using ambient credentials but no environment region, "+ + "use the Issuer region.") + } + + // In the general case, the environment region should always be used + // if it is set and if the Issuer region is omitted. + if setAmbientRegion && !supplyIssuerRegion { + assert.Equal(t, fakeAmbientRegion, cfg.Region, + "If there is a region in the environment and not in the Issuer resource, "+ + "the region in the environment should always be used.") + } + + // In the general case, the Issuer region should always be used if it is set. + // and if the environment region is not detected. + if !setAmbientRegion && supplyIssuerRegion { + assert.Equal(t, fakeIssuerRegion, cfg.Region, + "If there is an Issuer region but no environment region, "+ + "the Issuer region in the environment should always be used.") + } + + // And if no region is detected, log an info message to alert the user + // to the mis-configuration + if !setAmbientRegion && !supplyIssuerRegion { + assert.Contains(t, logMessages, "Region not found", + "If no region was detected, "+ + "log a warning to explain how to set the region.") + } + } - assert.Equal(t, "", *provider.client.Config.Region, "Expected Region to not be set from environment") + const ( + allowAmbientCredentials bitmask = 1 << iota + setAmbientRegion + supplyAccessKey + supplyWebIdentity + supplyIssuerRegion + ) + allFalse := bitmask(0) + allTrue := allowAmbientCredentials | setAmbientRegion | supplyAccessKey | supplyWebIdentity | supplyIssuerRegion + + for input := allFalse; input <= allTrue; input++ { + t.Run( + fmt.Sprintf("%v", input), + func(t *testing.T) { + testFunc( + t, + input.Has(allowAmbientCredentials), + input.Has(setAmbientRegion), + input.Has(supplyAccessKey), + input.Has(supplyWebIdentity), + input.Has(supplyIssuerRegion), + ) + }, + ) + } } func TestRoute53Present(t *testing.T) { + _, ctx := ktesting.NewTestContext(t) mockResponses := MockResponseMap{ - "/2013-04-01/hostedzonesbyname": MockResponse{StatusCode: 200, Body: ListHostedZonesByNameResponse}, - "/2013-04-01/hostedzone/ABCDEFG/rrset/": MockResponse{StatusCode: 200, Body: ChangeResourceRecordSetsResponse}, - "/2013-04-01/hostedzone/HIJKLMN/rrset/": MockResponse{StatusCode: 200, Body: ChangeResourceRecordSetsResponse}, - "/2013-04-01/change/123456": MockResponse{StatusCode: 200, Body: GetChangeResponse}, - "/2013-04-01/hostedzone/OPQRSTU/rrset/": MockResponse{StatusCode: 403, Body: ChangeResourceRecordSets403Response}, + "/2013-04-01/hostedzonesbyname": MockResponse{StatusCode: 200, Body: ListHostedZonesByNameResponse}, + "/2013-04-01/hostedzone/ABCDEFG/rrset": MockResponse{StatusCode: 200, Body: ChangeResourceRecordSetsResponse}, + "/2013-04-01/hostedzone/HIJKLMN/rrset": MockResponse{StatusCode: 200, Body: ChangeResourceRecordSetsResponse}, + "/2013-04-01/change/123456": MockResponse{StatusCode: 200, Body: GetChangeResponse}, + "/2013-04-01/hostedzone/OPQRSTU/rrset": MockResponse{StatusCode: 403, Body: ChangeResourceRecordSets403Response}, } ts := newMockServer(t, mockResponses) @@ -126,47 +263,169 @@ func TestRoute53Present(t *testing.T) { domain := "example.com" keyAuth := "123456d==" - err = provider.Present(domain, "_acme-challenge."+domain+".", keyAuth) + err = provider.Present(ctx, domain, "_acme-challenge."+domain+".", keyAuth) assert.NoError(t, err, "Expected Present to return no error") subDomain := "foo.example.com" - err = provider.Present(subDomain, "_acme-challenge."+subDomain+".", keyAuth) + err = provider.Present(ctx, subDomain, "_acme-challenge."+subDomain+".", keyAuth) assert.NoError(t, err, "Expected Present to return no error") nonExistentSubDomain := "bar.foo.example.com" - err = provider.Present(nonExistentSubDomain, nonExistentSubDomain+".", keyAuth) + err = provider.Present(ctx, nonExistentSubDomain, nonExistentSubDomain+".", keyAuth) assert.NoError(t, err, "Expected Present to return no error") nonExistentDomain := "baz.com" - err = provider.Present(nonExistentDomain, nonExistentDomain+".", keyAuth) + err = provider.Present(ctx, nonExistentDomain, nonExistentDomain+".", keyAuth) assert.Error(t, err, "Expected Present to return an error") // This test case makes sure that the request id has been properly // stripped off. It has to be stripped because it changes on every // request which causes spurious challenge updates. - err = provider.Present("bar.example.com", "bar.example.com.", keyAuth) + err = provider.Present(ctx, "bar.example.com", "bar.example.com.", keyAuth) require.Error(t, err, "Expected Present to return an error") - assert.Equal(t, `failed to change Route 53 record set: AccessDenied: User: arn:aws:iam::0123456789:user/test-cert-manager is not authorized to perform: route53:ChangeResourceRecordSets on resource: arn:aws:route53:::hostedzone/OPQRSTU`, err.Error()) + assert.Equal(t, `failed to change Route 53 record set: operation error Route 53: ChangeResourceRecordSets, https response error StatusCode: 403, RequestID: , api error AccessDenied: User: arn:aws:iam::0123456789:user/test-cert-manager is not authorized to perform: route53:ChangeResourceRecordSets on resource: arn:aws:route53:::hostedzone/OPQRSTU`, err.Error()) +} + +func TestRoute53Cleanup(t *testing.T) { + type testCase struct { + name string + responses MockResponseMap + expectedError string + } + + tests := []testCase{ + { + // Cleanup succeeds if the hosted zone is found and the submitted + // change action (to delete the challenge record) is eventually + // synced by AWS. + name: "success", + responses: MockResponseMap{ + "/2013-04-01/hostedzonesbyname": MockResponse{StatusCode: 200, Body: ListHostedZonesByNameResponse}, + "/2013-04-01/hostedzone/ABCDEFG/rrset": MockResponse{StatusCode: 200, Body: ChangeResourceRecordSetsResponse}, + "/2013-04-01/change/123456": MockResponse{StatusCode: 200, Body: GetChangeResponse}, + }, + }, + { + // Cleanup fails if the hostedzonesbyname API call returns an error. + name: "hosted-zone-lookup-failure", + responses: MockResponseMap{ + "/2013-04-01/hostedzonesbyname": MockResponse{StatusCode: 400, Body: ListHostedZonesByName400ResponseInvalidDomainName}, + }, + expectedError: `failed to determine Route 53 hosted zone ID: operation error Route 53: ListHostedZonesByName, https response error StatusCode: 400, RequestID: , InvalidDomainName: Simulated message`, + }, + { + // Cleanup fails if the changeresourcerecordsets API call returns an error. + name: "change-resource-records-failure", + responses: MockResponseMap{ + "/2013-04-01/hostedzonesbyname": MockResponse{StatusCode: 200, Body: ListHostedZonesByNameResponse}, + "/2013-04-01/hostedzone/ABCDEFG/rrset": MockResponse{StatusCode: 400, Body: ChangeResourceRecordSets403Response}, + }, + expectedError: `failed to change Route 53 record set: operation error Route 53: ChangeResourceRecordSets, https response error StatusCode: 400, RequestID: , api error AccessDenied: User: arn:aws:iam::0123456789:user/test-cert-manager is not authorized to perform: route53:ChangeResourceRecordSets on resource: arn:aws:route53:::hostedzone/OPQRSTU`, + }, + { + // Cleanup succeeds if the record has already been deleted; the + // changeresourcerecordsets API call returns an expected error: + // InvalidChangeBatch. + name: "change-resource-records-failure-ignore-not-found", + responses: MockResponseMap{ + "/2013-04-01/hostedzonesbyname": MockResponse{StatusCode: 200, Body: ListHostedZonesByNameResponse}, + "/2013-04-01/hostedzone/ABCDEFG/rrset": MockResponse{StatusCode: 400, Body: ChangeResourceRecordSets400Response}, + }, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + l := ktesting.NewLogger(t, ktesting.NewConfig(ktesting.Verbosity(6))) + ctx := logr.NewContext(t.Context(), l) + + ts := newMockServer(t, tc.responses) + defer ts.Close() + + provider, err := makeRoute53Provider(ts) + require.NoError(t, err, "Expected to make a Route 53 provider without error") + + domain := "example.com" + keyAuth := "123456d==" + + err = provider.CleanUp(ctx, domain, "_acme-challenge."+domain+".", keyAuth) + if tc.expectedError == "" { + assert.NoError(t, err, "Expected Cleanup to return no error") + } else { + assert.EqualError(t, err, tc.expectedError) + } + }) + } } func TestAssumeRole(t *testing.T) { - creds := &sts.Credentials{ + // Set the AWS config file to a non-existent file to ensure that the + // SDK does not load any local configuration. + t.Setenv("AWS_CONFIG_FILE", "/dev/null") + creds := &ststypes.Credentials{ AccessKeyId: aws.String("foo"), SecretAccessKey: aws.String("bar"), SessionToken: aws.String("my-token"), } cases := []struct { - name string - ambient bool - role string - expErr bool - expCreds *sts.Credentials - expRegion string - key string - secret string - region string - mockSTS *mockSTS + name string + ambient bool + role string + webIdentityToken string + expErr bool + expErrMessage string + expCreds *ststypes.Credentials + expRegion string + key string + secret string + region string + mockSTS *mockSTS }{ + { + name: "should remove request ID for assumeRole", + role: "my-role", + ambient: true, + expErr: true, + expErrMessage: "unable to assume role: https response error StatusCode: 0, RequestID: , foo", + expCreds: creds, + expRegion: "", + mockSTS: &mockSTS{ + AssumeRoleFn: func(ctx context.Context, params *sts.AssumeRoleInput, optFns ...func(*sts.Options)) (*sts.AssumeRoleOutput, error) { + return nil, &awshttp.ResponseError{ + RequestID: "fake-request-id", + ResponseError: &smithyhttp.ResponseError{ + Err: errors.New("foo"), + Response: &smithyhttp.Response{ + Response: &http.Response{}, + }, + }, + } + }, + }, + }, + { + name: "should remove request ID for assumeRoleWithWebIdentity", + role: "my-role", + webIdentityToken: jwt, + ambient: true, + expErr: true, + expErrMessage: "unable to assume role with web identity: https response error StatusCode: 0, RequestID: , foo", + expCreds: creds, + expRegion: "", + mockSTS: &mockSTS{ + AssumeRoleWithWebIdentityFn: func(ctx context.Context, params *sts.AssumeRoleWithWebIdentityInput, optFns ...func(*sts.Options)) (*sts.AssumeRoleWithWebIdentityOutput, error) { + return nil, &awshttp.ResponseError{ + RequestID: "fake-request-id", + ResponseError: &smithyhttp.ResponseError{ + Err: errors.New("foo"), + Response: &smithyhttp.Response{ + Response: &http.Response{}, + }, + }, + } + }, + }, + }, { name: "should assume role w/ ambient creds", role: "my-role", @@ -178,7 +437,7 @@ func TestAssumeRole(t *testing.T) { expCreds: creds, expRegion: "", mockSTS: &mockSTS{ - AssumeRoleFn: func(input *sts.AssumeRoleInput) (*sts.AssumeRoleOutput, error) { + AssumeRoleFn: func(ctx context.Context, params *sts.AssumeRoleInput, optFns ...func(*sts.Options)) (*sts.AssumeRoleOutput, error) { return &sts.AssumeRoleOutput{ Credentials: creds, }, nil @@ -195,7 +454,7 @@ func TestAssumeRole(t *testing.T) { expErr: false, expCreds: creds, mockSTS: &mockSTS{ - AssumeRoleFn: func(input *sts.AssumeRoleInput) (*sts.AssumeRoleOutput, error) { + AssumeRoleFn: func(ctx context.Context, params *sts.AssumeRoleInput, optFns ...func(*sts.Options)) (*sts.AssumeRoleOutput, error) { return &sts.AssumeRoleOutput{ Credentials: creds, }, nil @@ -210,12 +469,12 @@ func TestAssumeRole(t *testing.T) { secret: "my-explicit-secret", region: "eu-central-1", expErr: false, - expCreds: &sts.Credentials{ + expCreds: &ststypes.Credentials{ AccessKeyId: aws.String("my-explicit-key"), // from above SecretAccessKey: aws.String("my-explicit-secret"), // from above }, mockSTS: &mockSTS{ - AssumeRoleFn: func(input *sts.AssumeRoleInput) (*sts.AssumeRoleOutput, error) { + AssumeRoleFn: func(ctx context.Context, params *sts.AssumeRoleInput, optFns ...func(*sts.Options)) (*sts.AssumeRoleOutput, error) { return &sts.AssumeRoleOutput{ Credentials: creds, }, nil @@ -233,81 +492,147 @@ func TestAssumeRole(t *testing.T) { expErr: true, expCreds: nil, mockSTS: &mockSTS{ - AssumeRoleFn: func(input *sts.AssumeRoleInput) (*sts.AssumeRoleOutput, error) { + AssumeRoleFn: func(ctx context.Context, params *sts.AssumeRoleInput, optFns ...func(*sts.Options)) (*sts.AssumeRoleOutput, error) { return nil, fmt.Errorf("error assuming mock role") }, }, }, + { + name: "should assume role with web identity", + role: "my-role", + webIdentityToken: jwt, + expErr: false, + expCreds: creds, + mockSTS: &mockSTS{ + AssumeRoleWithWebIdentityFn: func(ctx context.Context, params *sts.AssumeRoleWithWebIdentityInput, optFns ...func(*sts.Options)) (*sts.AssumeRoleWithWebIdentityOutput, error) { + return &sts.AssumeRoleWithWebIdentityOutput{ + Credentials: creds, + }, nil + }, + }, + }, + { + name: "require role when using assume role with web identity", + webIdentityToken: jwt, + expErr: true, + expCreds: nil, + mockSTS: &mockSTS{ + AssumeRoleWithWebIdentityFn: func(ctx context.Context, params *sts.AssumeRoleWithWebIdentityInput, optFns ...func(*sts.Options)) (*sts.AssumeRoleWithWebIdentityOutput, error) { + return nil, fmt.Errorf("error assuming mock role with web identity") + }, + }, + }, } for _, c := range cases { t.Run(c.name, func(t *testing.T) { - provider, err := makeMockSessionProvider(func(sess *session.Session) stsiface.STSAPI { + provider := makeMockSessionProvider(func(cfg aws.Config) StsClient { return c.mockSTS - }, c.key, c.secret, c.region, c.role, c.ambient) - assert.NoError(t, err) - sess, err := provider.GetSession() + }, c.key, c.secret, c.region, c.role, c.webIdentityToken, c.ambient) + _, ctx := ktesting.NewTestContext(t) + cfg, err := provider.GetSession(ctx) if c.expErr { assert.NotNil(t, err) + if c.expErrMessage != "" { + assert.EqualError(t, err, c.expErrMessage) + } } else { - sessCreds, _ := sess.Config.Credentials.Get() + assert.Nil(t, err) + sessCreds, _ := cfg.Credentials.Retrieve(ctx) assert.Equal(t, c.mockSTS.assumedRole, c.role) assert.Equal(t, *c.expCreds.SecretAccessKey, sessCreds.SecretAccessKey) assert.Equal(t, *c.expCreds.AccessKeyId, sessCreds.AccessKeyID) - assert.Equal(t, c.region, *sess.Config.Region) + assert.Equal(t, c.region, cfg.Region) } }) } } type mockSTS struct { - *sts.STS - AssumeRoleFn func(input *sts.AssumeRoleInput) (*sts.AssumeRoleOutput, error) - assumedRole string + AssumeRoleFn func(ctx context.Context, params *sts.AssumeRoleInput, optFns ...func(*sts.Options)) (*sts.AssumeRoleOutput, error) + AssumeRoleWithWebIdentityFn func(ctx context.Context, params *sts.AssumeRoleWithWebIdentityInput, optFns ...func(*sts.Options)) (*sts.AssumeRoleWithWebIdentityOutput, error) + assumedRole string } -func (m *mockSTS) AssumeRole(input *sts.AssumeRoleInput) (*sts.AssumeRoleOutput, error) { +func (m *mockSTS) AssumeRole(ctx context.Context, params *sts.AssumeRoleInput, optFns ...func(*sts.Options)) (*sts.AssumeRoleOutput, error) { if m.AssumeRoleFn != nil { - m.assumedRole = *input.RoleArn - return m.AssumeRoleFn(input) + m.assumedRole = *params.RoleArn + return m.AssumeRoleFn(ctx, params, optFns...) + } + + return nil, nil +} + +func (m *mockSTS) AssumeRoleWithWebIdentity(ctx context.Context, params *sts.AssumeRoleWithWebIdentityInput, optFns ...func(*sts.Options)) (*sts.AssumeRoleWithWebIdentityOutput, error) { + if m.AssumeRoleWithWebIdentityFn != nil { + m.assumedRole = *params.RoleArn + return m.AssumeRoleWithWebIdentityFn(ctx, params, optFns...) } return nil, nil } -func makeMockSessionProvider(defaultSTSProvider func(sess *session.Session) stsiface.STSAPI, accessKeyID, secretAccessKey, region, role string, ambient bool) (*sessionProvider, error) { +func makeMockSessionProvider( + defaultSTSProvider func(aws.Config) StsClient, + accessKeyID, secretAccessKey, region, role, webIdentityToken string, + ambient bool, +) *sessionProvider { return &sessionProvider{ - AccessKeyID: accessKeyID, - SecretAccessKey: secretAccessKey, - Ambient: ambient, - Region: region, - Role: role, - StsProvider: defaultSTSProvider, - log: logf.Log.WithName("route53-session"), - }, nil + AccessKeyID: accessKeyID, + SecretAccessKey: secretAccessKey, + Ambient: ambient, + Region: region, + Role: role, + WebIdentityToken: webIdentityToken, + StsProvider: defaultSTSProvider, + } } func Test_removeReqID(t *testing.T) { + newResponseError := func() *smithyhttp.ResponseError { + return &smithyhttp.ResponseError{ + Err: errors.New("foo"), + Response: &smithyhttp.Response{ + Response: &http.Response{}, + }, + } + } + tests := []struct { name string err error wantErr error }{ { - name: "should remove the request id and the origin error", - err: awserr.NewRequestFailure(awserr.New("foo", "bar", nil), 400, "SOMEREQUESTID"), - wantErr: awserr.New("foo", "bar", nil), + name: "should replace the request id in a nested error with a static value to keep the message stable", + err: &smithy.OperationError{OperationName: "test", Err: &awshttp.ResponseError{RequestID: "SOMEREQUESTID", ResponseError: newResponseError()}}, + wantErr: &smithy.OperationError{OperationName: "test", Err: &awshttp.ResponseError{RequestID: "", ResponseError: newResponseError()}}, + }, + { + name: "should replace the request id with a static value to keep the message stable", + err: &awshttp.ResponseError{RequestID: "SOMEREQUESTID", ResponseError: newResponseError()}, + wantErr: &awshttp.ResponseError{RequestID: "", ResponseError: newResponseError()}, + }, + { + name: "should replace the request id in a %w wrapped error", + err: fmt.Errorf("failed to refresh cached credentials, %w", &awshttp.ResponseError{RequestID: "SOMEREQUESTID", ResponseError: newResponseError()}), + wantErr: fmt.Errorf("failed to refresh cached credentials, %w", &awshttp.ResponseError{RequestID: "", ResponseError: newResponseError()}), }, { name: "should do nothing if no request id is set", - err: awserr.New("foo", "bar", nil), - wantErr: awserr.New("foo", "bar", nil), + err: newResponseError(), + wantErr: newResponseError(), }, { name: "should do nothing if the error is not an aws error", err: errors.New("foo"), wantErr: errors.New("foo"), }, + { + name: "should ignore nil errors", + err: nil, + wantErr: nil, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/pkg/issuer/acme/dns/util/dns.go b/pkg/issuer/acme/dns/util/dns.go index db2b4e47944..4aac28d789e 100644 --- a/pkg/issuer/acme/dns/util/dns.go +++ b/pkg/issuer/acme/dns/util/dns.go @@ -9,6 +9,7 @@ this directory. package util import ( + "context" "fmt" "github.com/miekg/dns" @@ -17,13 +18,13 @@ import ( // DNS01LookupFQDN returns a DNS name which will be updated to solve the dns-01 // challenge // TODO: move this into the pkg/acme package -func DNS01LookupFQDN(domain string, followCNAME bool, nameservers ...string) (string, error) { +func DNS01LookupFQDN(ctx context.Context, domain string, followCNAME bool, nameservers ...string) (string, error) { fqdn := fmt.Sprintf("_acme-challenge.%s.", domain) // Check if the domain has CNAME then return that if followCNAME { var err error - fqdn, err = followCNAMEs(fqdn, nameservers) + fqdn, err = followCNAMEs(ctx, fqdn, nameservers) if err != nil { return "", err } diff --git a/pkg/issuer/acme/dns/util/dns_test.go b/pkg/issuer/acme/dns/util/dns_test.go index d167648dd93..a9ac76af093 100644 --- a/pkg/issuer/acme/dns/util/dns_test.go +++ b/pkg/issuer/acme/dns/util/dns_test.go @@ -1,3 +1,5 @@ +//go:build !livedns_test + // +skip_license_check package util diff --git a/pkg/issuer/acme/dns/util/livedns_test.go b/pkg/issuer/acme/dns/util/livedns_test.go new file mode 100644 index 00000000000..1d6ab4c7eaf --- /dev/null +++ b/pkg/issuer/acme/dns/util/livedns_test.go @@ -0,0 +1,123 @@ +//go:build livedns_test + +/* +Copyright 2025 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. +*/ + +/* +Addition to the above license statement: + +This file *may* contain code directly taken from the 'xenolf/lego' project. + +A copy of the license for this code can be found in the file named LICENSE in +this directory. +*/ + +package util + +// The tests in this file connect to *live* DNS or DNS-over-HTTPS services, +// and rely on various DNS records which are out of the control of the cert-manager +// project. As such, these tests are: +// 1. More likely to flake due to network connectivity issues +// 2. Liable to break if the upstream DNS records change +// 3. Unable to run in restrictive computing environments +// (such as MitM corporate proxies which block DNS / DNS-over-HTTPS) + +// Because of the above, these tests live behind a build tag so they're not +// run by mistake. + +import ( + "context" + "fmt" + "testing" + "time" +) + +const ( + standardTimeout = time.Second * 5 +) + +func TestPreCheckDNSOverHTTPS(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), standardTimeout) + defer cancel() + + ok, err := PreCheckDNS(ctx, "google.com.", "v=spf1 include:_spf.google.com ~all", []string{"https://8.8.8.8/dns-query"}, true) + if err != nil || !ok { + t.Errorf("preCheckDNS failed for dns-over-https (authoritative): ok=%v err=%s", ok, err.Error()) + } +} + +func TestPreCheckDNSOverHTTPSNoAuthoritative(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), standardTimeout) + defer cancel() + + ok, err := PreCheckDNS(ctx, "google.com.", "v=spf1 include:_spf.google.com ~all", []string{"https://1.1.1.1/dns-query"}, false) + if err != nil || !ok { + t.Errorf("preCheckDNS failed for dns-over-https (non-authoritative): ok=%v err=%s", ok, err.Error()) + } +} + +func TestPreCheckDNS(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), standardTimeout) + defer cancel() + + ok, err := PreCheckDNS(ctx, "google.com.", "v=spf1 include:_spf.google.com ~all", []string{"8.8.8.8:53"}, true) + if err != nil || !ok { + t.Errorf("preCheckDNS failed for dns on port 53 (authoritative): ok=%v err=%s", ok, err.Error()) + } +} + +func TestPreCheckDNSNonAuthoritative(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), standardTimeout) + defer cancel() + + ok, err := PreCheckDNS(ctx, "google.com.", "v=spf1 include:_spf.google.com ~all", []string{"1.1.1.1:53"}, false) + if err != nil || !ok { + t.Errorf("preCheckDNS failed for dns on port 53 (non-authoritative): ok=%v err=%s", ok, err.Error()) + } +} + +func TestCheckAuthoritativeNss(t *testing.T) { + checkAuthoritativeNssTests := []struct { + fqdn, value string + ns []string + ok bool + }{ + // TXT RR w/ expected value + {"8.8.8.8.asn.routeviews.org.", "151698.8.8.024", []string{"asnums.routeviews.org.:53"}, + true, + }, + // No TXT RR + {"ns1.google.com.", "", []string{"ns2.google.com.:53"}, + false, + }, + // TXT RR w/ unexpected value + {"8.8.8.8.asn.routeviews.org.", "fe01=", []string{"asnums.routeviews.org.:53"}, + false, + }, + } + + for i, tt := range checkAuthoritativeNssTests { + t.Run(fmt.Sprintf("test %d", i), func(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), standardTimeout) + defer cancel() + + ok, _ := checkAuthoritativeNss(ctx, tt.fqdn, tt.value, tt.ns) + if ok != tt.ok { + t.Errorf("%s: got %t; want %t", tt.fqdn, ok, tt.ok) + } + }) + } +} diff --git a/pkg/issuer/acme/dns/util/wait.go b/pkg/issuer/acme/dns/util/wait.go index 562af9fdb45..cedcfe1377d 100644 --- a/pkg/issuer/acme/dns/util/wait.go +++ b/pkg/issuer/acme/dns/util/wait.go @@ -9,8 +9,12 @@ this directory. package util import ( + "bytes" + "context" "fmt" + "io" "net" + "net/http" "strings" "sync" "time" @@ -20,9 +24,14 @@ import ( logf "github.com/cert-manager/cert-manager/pkg/logs" ) -type preCheckDNSFunc func(fqdn, value string, nameservers []string, +type preCheckDNSFunc func(ctx context.Context, fqdn, value string, nameservers []string, useAuthoritative bool) (bool, error) -type dnsQueryFunc func(fqdn string, rtype uint16, nameservers []string, recursive bool) (in *dns.Msg, err error) +type dnsQueryFunc func(ctx context.Context, fqdn string, rtype uint16, nameservers []string, recursive bool) (in *dns.Msg, err error) + +type cachedEntry struct { + Response *dns.Msg + ExpiryTime time.Time +} var ( // PreCheckDNS checks DNS propagation before notifying ACME that @@ -33,14 +42,11 @@ var ( dnsQuery dnsQueryFunc = DNSQuery fqdnToZoneLock sync.RWMutex - fqdnToZone = map[string]string{} + fqdnToZone = map[string]cachedEntry{} ) const defaultResolvConf = "/etc/resolv.conf" -const issueTag = "issue" -const issuewildTag = "issuewild" - var defaultNameservers = []string{ "8.8.8.8:53", "8.8.4.4:53", @@ -74,8 +80,8 @@ func getNameservers(path string, defaults []string) []string { // that it finds. Returns an error when a loop is found in the CNAME chain. The // argument fqdnChain is used by the function itself to keep track of which fqdns it // already encountered and detect loops. -func followCNAMEs(fqdn string, nameservers []string, fqdnChain ...string) (string, error) { - r, err := dnsQuery(fqdn, dns.TypeCNAME, nameservers, true) +func followCNAMEs(ctx context.Context, fqdn string, nameservers []string, fqdnChain ...string) (string, error) { + r, err := dnsQuery(ctx, fqdn, dns.TypeCNAME, nameservers, true) if err != nil { return "", err } @@ -87,7 +93,7 @@ func followCNAMEs(fqdn string, nameservers []string, fqdnChain ...string) (strin if !ok || cn.Hdr.Name != fqdn { continue } - logf.V(logf.DebugLevel).Infof("Updating FQDN: %s with its CNAME: %s", fqdn, cn.Target) + logf.FromContext(ctx).V(logf.DebugLevel).Info("Updating FQDN", "fqdn", fqdn, "cname", cn.Target) // Check if we were here before to prevent loops in the chain of CNAME records. for _, fqdnInChain := range fqdnChain { if cn.Target != fqdnInChain { @@ -95,26 +101,26 @@ func followCNAMEs(fqdn string, nameservers []string, fqdnChain ...string) (strin } return "", fmt.Errorf("Found recursive CNAME record to %q when looking up %q", cn.Target, fqdn) } - return followCNAMEs(cn.Target, nameservers, append(fqdnChain, fqdn)...) + return followCNAMEs(ctx, cn.Target, nameservers, append(fqdnChain, fqdn)...) } return fqdn, nil } // checkDNSPropagation checks if the expected TXT record has been propagated to all authoritative nameservers. -func checkDNSPropagation(fqdn, value string, nameservers []string, +func checkDNSPropagation(ctx context.Context, fqdn, value string, nameservers []string, useAuthoritative bool) (bool, error) { var err error - fqdn, err = followCNAMEs(fqdn, nameservers) + fqdn, err = followCNAMEs(ctx, fqdn, nameservers) if err != nil { return false, err } if !useAuthoritative { - return checkAuthoritativeNss(fqdn, value, nameservers) + return checkAuthoritativeNss(ctx, fqdn, value, nameservers) } - authoritativeNss, err := lookupNameservers(fqdn, nameservers) + authoritativeNss, err := lookupNameservers(ctx, fqdn, nameservers) if err != nil { return false, err } @@ -122,13 +128,13 @@ func checkDNSPropagation(fqdn, value string, nameservers []string, for i, ans := range authoritativeNss { authoritativeNss[i] = net.JoinHostPort(ans, "53") } - return checkAuthoritativeNss(fqdn, value, authoritativeNss) + return checkAuthoritativeNss(ctx, fqdn, value, authoritativeNss) } // checkAuthoritativeNss queries each of the given nameservers for the expected TXT record. -func checkAuthoritativeNss(fqdn, value string, nameservers []string) (bool, error) { +func checkAuthoritativeNss(ctx context.Context, fqdn, value string, nameservers []string) (bool, error) { for _, ns := range nameservers { - r, err := DNSQuery(fqdn, dns.TypeTXT, []string{ns}, true) + r, err := dnsQuery(ctx, fqdn, dns.TypeTXT, []string{ns}, true) if err != nil { return false, err } @@ -138,7 +144,7 @@ func checkAuthoritativeNss(fqdn, value string, nameservers []string) (bool, erro return false, fmt.Errorf("NS %s returned %s for %s", ns, dns.RcodeToString[r.Rcode], fqdn) } - logf.V(logf.DebugLevel).Infof("Looking up TXT records for %q", fqdn) + logf.FromContext(ctx).V(logf.DebugLevel).Info("Looking up TXT records", "fqdn", fqdn) var found bool for _, rr := range r.Answer { if txt, ok := rr.(*dns.TXT); ok { @@ -153,13 +159,20 @@ func checkAuthoritativeNss(fqdn, value string, nameservers []string) (bool, erro return false, nil } } - + logf.FromContext(ctx).V(logf.DebugLevel).Info("Selfchecking using the DNS Lookup method was successful") return true, nil } // DNSQuery will query a nameserver, iterating through the supplied servers as it retries // The nameserver should include a port, to facilitate testing where we talk to a mock dns server. -func DNSQuery(fqdn string, rtype uint16, nameservers []string, recursive bool) (in *dns.Msg, err error) { +func DNSQuery(ctx context.Context, fqdn string, rtype uint16, nameservers []string, recursive bool) (in *dns.Msg, err error) { + switch rtype { + case dns.TypeCAA, dns.TypeCNAME, dns.TypeNS, dns.TypeSOA, dns.TypeTXT: + default: + // We explicitly specified here what types are supported, so we can more confidently create tests for this function. + return nil, fmt.Errorf("unsupported DNS record type %d", rtype) + } + m := new(dns.Msg) m.SetQuestion(fqdn, rtype) m.SetEdns0(4096, false) @@ -168,18 +181,30 @@ func DNSQuery(fqdn string, rtype uint16, nameservers []string, recursive bool) ( m.RecursionDesired = false } + udp := &dns.Client{Net: "udp", Timeout: DNSTimeout} + tcp := &dns.Client{Net: "tcp", Timeout: DNSTimeout} + httpClient := *http.DefaultClient + httpClient.Timeout = DNSTimeout + http := httpDNSClient{ + HTTPClient: &httpClient, + } + // Will retry the request based on the number of servers (n+1) - for i := 1; i <= len(nameservers)+1; i++ { - ns := nameservers[i%len(nameservers)] - udp := &dns.Client{Net: "udp", Timeout: DNSTimeout} - in, _, err = udp.Exchange(m, ns) - - if (in != nil && in.Truncated) || - (err != nil && strings.HasPrefix(err.Error(), "read udp") && strings.HasSuffix(err.Error(), "i/o timeout")) { - logf.V(logf.DebugLevel).Infof("UDP dns lookup failed, retrying with TCP: %v", err) - tcp := &dns.Client{Net: "tcp", Timeout: DNSTimeout} - // If the TCP request succeeds, the err will reset to nil - in, _, err = tcp.Exchange(m, ns) + for _, ns := range nameservers { + // If the TCP request succeeds, the err will reset to nil + if strings.HasPrefix(ns, "https://") { + in, _, err = http.Exchange(ctx, m, ns) + + } else { + in, _, err = udp.ExchangeContext(ctx, m, ns) + + // Try TCP if UDP fails + if (in != nil && in.Truncated) || + (err != nil && strings.HasPrefix(err.Error(), "read udp") && strings.HasSuffix(err.Error(), "i/o timeout")) { + logf.FromContext(ctx).V(logf.DebugLevel).Info("UDP dns lookup failed, retrying with TCP", "err", err) + // If the TCP request succeeds, the err will reset to nil + in, _, err = tcp.ExchangeContext(ctx, m, ns) + } } if err == nil { @@ -189,119 +214,75 @@ func DNSQuery(fqdn string, rtype uint16, nameservers []string, recursive bool) ( return } -func ValidateCAA(domain string, issuerID []string, iswildcard bool, nameservers []string) error { - // see https://tools.ietf.org/html/rfc6844#section-4 - // for more information about how CAA lookup is performed - fqdn := ToFqdn(domain) +type httpDNSClient struct { + HTTPClient *http.Client +} + +const dohMimeType = "application/dns-message" - issuerSet := make(map[string]bool) - for _, s := range issuerID { - issuerSet[s] = true +func (c *httpDNSClient) Exchange(ctx context.Context, m *dns.Msg, a string) (r *dns.Msg, rtt time.Duration, err error) { + p, err := m.Pack() + if err != nil { + return nil, 0, err } - var caas []*dns.CAA - for { - // follow at most 8 cnames per label - queryDomain := fqdn - var msg *dns.Msg - var err error - for i := 0; i < 8; i++ { - // usually, we should be able to just ask the local recursive - // nameserver for CAA records, but some setups will return SERVFAIL - // on unknown types like CAA. Instead, ask the authoritative server - var authNS []string - authNS, err = lookupNameservers(queryDomain, nameservers) - if err != nil { - return fmt.Errorf("Could not validate CAA record: %s", err) - } - for i, ans := range authNS { - authNS[i] = net.JoinHostPort(ans, "53") - } - msg, err = DNSQuery(queryDomain, dns.TypeCAA, authNS, false) - if err != nil { - return fmt.Errorf("Could not validate CAA record: %s", err) - } - // domain may not exist, which is fine. It will fail HTTP01 checks - // but DNS01 checks will create a proper domain - if msg.Rcode == dns.RcodeNameError { - break - } - if msg.Rcode != dns.RcodeSuccess { - return fmt.Errorf("Could not validate CAA: Unexpected response code '%s' for %s", - dns.RcodeToString[msg.Rcode], domain) - } - oldQuery := queryDomain - queryDomain, err := followCNAMEs(queryDomain, nameservers) - if err != nil { - return fmt.Errorf("while trying to follow CNAMEs for domain %s using nameservers %v: %w", queryDomain, nameservers, err) - } - if queryDomain == oldQuery { - break - } - } - // we have a response that's not a CNAME. It might be empty. - // if it is, go up a label and ask again - for _, rr := range msg.Answer { - caa, ok := rr.(*dns.CAA) - if !ok { - continue - } - caas = append(caas, caa) - } - // once we've found any CAA records, we use these CAAs - if len(caas) != 0 { - break - } + req, err := http.NewRequestWithContext(ctx, http.MethodPost, a, bytes.NewReader(p)) + if err != nil { + return nil, 0, err + } - index := strings.Index(fqdn, ".") - if index == -1 { - panic("should never happen") - } - fqdn = fqdn[index+1:] - if len(fqdn) == 0 { - // we reached the root with no CAA, don't bother asking - return nil - } + req.Header.Set("Content-Type", dohMimeType) + req.Header.Set("Accept", dohMimeType) + + hc := http.DefaultClient + if c.HTTPClient != nil { + hc = c.HTTPClient } - if !matchCAA(caas, issuerSet, iswildcard) { - // TODO(dmo): better error message - return fmt.Errorf("CAA record does not match issuer") + req = req.WithContext(ctx) + + t := time.Now() + + resp, err := hc.Do(req) + if err != nil { + return nil, 0, err } - return nil -} + defer resp.Body.Close() -func matchCAA(caas []*dns.CAA, issuerIDs map[string]bool, iswildcard bool) bool { - matches := false - for _, caa := range caas { - // if we require a wildcard certificate, we must prioritize any issuewild - // tags - only if it matches (regardless of any other entries) can we - // issue a wildcard certificate - if iswildcard && caa.Tag == issuewildTag { - return issuerIDs[caa.Value] - } + if resp.StatusCode != http.StatusOK { + return nil, 0, fmt.Errorf("dns: server returned HTTP %d error: %q", resp.StatusCode, resp.Status) + } - // issue tags allow any certificate, we perform a check which will only - // be returned if we do not need a wildcard certificate, or if we need - // a wildcard certificate and no issuewild entries are present - if caa.Tag == issueTag { - matches = matches || issuerIDs[caa.Value] - } + if ct := resp.Header.Get("Content-Type"); ct != dohMimeType { + return nil, 0, fmt.Errorf("dns: unexpected Content-Type %q; expected %q", ct, dohMimeType) + } + + p, err = io.ReadAll(resp.Body) + if err != nil { + return nil, 0, err } - return matches + + rtt = time.Since(t) + + r = new(dns.Msg) + if err := r.Unpack(p); err != nil { + return r, 0, err + } + + return r, rtt, nil } // lookupNameservers returns the authoritative nameservers for the given fqdn. -func lookupNameservers(fqdn string, nameservers []string) ([]string, error) { +func lookupNameservers(ctx context.Context, fqdn string, nameservers []string) ([]string, error) { var authoritativeNss []string - logf.V(logf.DebugLevel).Infof("Searching fqdn %q using seed nameservers [%s]", fqdn, strings.Join(nameservers, ", ")) - zone, err := FindZoneByFqdn(fqdn, nameservers) + logf.FromContext(ctx).V(logf.DebugLevel).Info("Searching fqdn", "fqdn", fqdn, "seedNameservers", nameservers) + zone, err := FindZoneByFqdn(ctx, fqdn, nameservers) if err != nil { return nil, fmt.Errorf("Could not determine the zone for %q: %v", fqdn, err) } - r, err := DNSQuery(zone, dns.TypeNS, nameservers, true) + r, err := dnsQuery(ctx, zone, dns.TypeNS, nameservers, true) if err != nil { return nil, err } @@ -313,7 +294,7 @@ func lookupNameservers(fqdn string, nameservers []string) ([]string, error) { } if len(authoritativeNss) > 0 { - logf.V(logf.DebugLevel).Infof("Returning authoritative nameservers [%s]", strings.Join(authoritativeNss, ", ")) + logf.FromContext(ctx).V(logf.DebugLevel).Info("Returning authoritative nameservers", "authoritativeNameservers", authoritativeNss) return authoritativeNss, nil } return nil, fmt.Errorf("Could not determine authoritative nameservers for %q", fqdn) @@ -321,16 +302,25 @@ func lookupNameservers(fqdn string, nameservers []string) ([]string, error) { // FindZoneByFqdn determines the zone apex for the given fqdn by recursing up the // domain labels until the nameserver returns a SOA record in the answer section. -func FindZoneByFqdn(fqdn string, nameservers []string) (string, error) { - fqdnToZoneLock.RLock() +func FindZoneByFqdn(ctx context.Context, fqdn string, nameservers []string) (string, error) { // Do we have it cached? - if zone, ok := fqdnToZone[fqdn]; ok { - fqdnToZoneLock.RUnlock() - logf.V(logf.DebugLevel).Infof("Returning cached zone record %q for fqdn %q", zone, fqdn) - return zone, nil - } + fqdnToZoneLock.RLock() + cachedEntryItem, existsInCache := fqdnToZone[fqdn] fqdnToZoneLock.RUnlock() + if existsInCache { + // ensure cachedEntry is not expired + if time.Now().Before(cachedEntryItem.ExpiryTime) { + logf.FromContext(ctx).V(logf.DebugLevel).Info("Returning cached DNS response", "fqdn", fqdn) + return cachedEntryItem.Response.Answer[0].(*dns.SOA).Hdr.Name, nil + } + + // Remove expired entry + fqdnToZoneLock.Lock() + delete(fqdnToZone, fqdn) + fqdnToZoneLock.Unlock() + } + labelIndexes := dns.Split(fqdn) // We are climbing up the domain tree, looking for the SOA record on @@ -349,7 +339,7 @@ func FindZoneByFqdn(fqdn string, nameservers []string) (string, error) { for _, index := range labelIndexes { domain := fqdn[index:] - in, err := DNSQuery(domain, dns.TypeSOA, nameservers, true) + in, err := dnsQuery(ctx, domain, dns.TypeSOA, nameservers, true) if err != nil { return "", err } @@ -378,10 +368,13 @@ func FindZoneByFqdn(fqdn string, nameservers []string) (string, error) { fqdnToZoneLock.Lock() defer fqdnToZoneLock.Unlock() - zone := soa.Hdr.Name - fqdnToZone[fqdn] = zone - logf.V(logf.DebugLevel).Infof("Returning discovered zone record %q for fqdn %q", zone, fqdn) - return zone, nil + fqdnToZone[fqdn] = cachedEntry{ + Response: in, + ExpiryTime: time.Now().Add(time.Duration(soa.Hdr.Ttl) * time.Second), + } + + logf.FromContext(ctx).V(logf.DebugLevel).Info("Caching DNS response", "fqdn", fqdn, "ttl", soa.Hdr.Ttl) + return soa.Hdr.Name, nil } } } @@ -401,20 +394,16 @@ func dnsMsgContainsCNAME(msg *dns.Msg) bool { // ToFqdn converts the name into a fqdn appending a trailing dot. func ToFqdn(name string) string { - n := len(name) - if n == 0 || name[n-1] == '.' { + if name == "" || strings.HasSuffix(name, ".") { return name } + return name + "." } // UnFqdn converts the fqdn into a name removing the trailing dot. func UnFqdn(name string) string { - n := len(name) - if n != 0 && name[n-1] == '.' { - return name[:n-1] - } - return name + return strings.TrimSuffix(name, ".") } // WaitFor polls the given function 'f', once every 'interval', up to 'timeout'. diff --git a/pkg/issuer/acme/dns/util/wait_test.go b/pkg/issuer/acme/dns/util/wait_test.go index 9ae18cf8b75..89ed1d3b80e 100644 --- a/pkg/issuer/acme/dns/util/wait_test.go +++ b/pkg/issuer/acme/dns/util/wait_test.go @@ -1,3 +1,5 @@ +//go:build !livedns_test + // +skip_license_check /* @@ -9,243 +11,296 @@ this directory. package util import ( + "context" "fmt" "reflect" "sort" - "strings" + "sync" + "sync/atomic" "testing" "github.com/miekg/dns" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) -var lookupNameserversTestsOK = []struct { - fqdn string - nss []string -}{ - {"books.google.com.ng.", - []string{"ns1.google.com.", "ns2.google.com.", "ns3.google.com.", "ns4.google.com."}, - }, - {"www.google.com.", - []string{"ns1.google.com.", "ns2.google.com.", "ns3.google.com.", "ns4.google.com."}, - }, -} - -var lookupNameserversTestsErr = []struct { - fqdn string - error string -}{ - // invalid tld - {"_null.n0n0.", - "Could not determine the zone", - }, -} - -var findZoneByFqdnTests = []struct { - fqdn string - zone string -}{ - {"mail.google.com.", "google.com."}, // domain is a CNAME - {"foo.google.com.", "google.com."}, // domain is a non-existent subdomain - {"example.com.ac.", "ac."}, // domain is a eTLD - {"cross-zone-example.assets.sh.", "assets.sh."}, // domain is a cross-zone CNAME -} - -var checkAuthoritativeNssTests = []struct { - fqdn, value string - ns []string - ok bool -}{ - // TXT RR w/ expected value - {"8.8.8.8.asn.routeviews.org.", "151698.8.8.024", []string{"asnums.routeviews.org.:53"}, - true, - }, - // No TXT RR - {"ns1.google.com.", "", []string{"ns2.google.com.:53"}, - false, - }, - // TXT RR /w unexpected value - {"8.8.8.8.asn.routeviews.org.", "fe01=", []string{"asnums.routeviews.org.:53"}, - false, - }, -} - -var checkAuthoritativeNssTestsErr = []struct { - fqdn, value string - ns []string - error string -}{ - // invalid nameserver - {"8.8.8.8.asn.routeviews.org.", "fe01=", []string{"invalidns.com."}, - "", - }, -} - -var checkResolvConfServersTests = []struct { - fixture string - expected []string - defaults []string -}{ - {"testdata/resolv.conf.1", []string{"10.200.3.249:53", "10.200.3.250:5353", "[2001:4860:4860::8844]:53", "[10.0.0.1]:5353"}, []string{"127.0.0.1:53"}}, - {"testdata/resolv.conf.nonexistent", []string{"127.0.0.1:53"}, []string{"127.0.0.1:53"}}, -} - -func TestMatchCAA(t *testing.T) { - tests := map[string]struct { - caas []*dns.CAA - issuerIDs map[string]bool - isWildcard bool - matches bool +func TestLookupNameserversOK(t *testing.T) { + tests := []struct { + givenFQDN string + expectNSs []string + mockDNS []interaction // Key example: "SOA en.wikipedia.org." }{ - "matches with a single 'issue' caa for a non-wildcard domain": { - caas: []*dns.CAA{{Tag: issueTag, Value: "example-ca"}}, - issuerIDs: map[string]bool{"example-ca": true}, - isWildcard: false, - matches: true, - }, - "matches with a single 'issue' caa for a wildcard domain": { - caas: []*dns.CAA{{Tag: issueTag, Value: "example-ca"}}, - issuerIDs: map[string]bool{"example-ca": true}, - isWildcard: true, - matches: true, - }, - "does not match with a single 'issue' caa for a non-wildcard domain": { - caas: []*dns.CAA{{Tag: issueTag, Value: "example-ca"}}, - issuerIDs: map[string]bool{"not-example-ca": true}, - isWildcard: false, - matches: false, - }, - "matches with a single 'issuewild' caa for a wildcard domain": { - caas: []*dns.CAA{{Tag: issuewildTag, Value: "example-ca"}}, - issuerIDs: map[string]bool{"example-ca": true}, - isWildcard: true, - matches: true, - }, - "does not match with a single 'issuewild' caa for a non-wildcard domain": { - caas: []*dns.CAA{{Tag: issuewildTag, Value: "example-ca"}}, - issuerIDs: map[string]bool{"example-ca": true}, - isWildcard: false, - matches: false, - }, - "still matches if only one of two CAAs does not match issuerID": { - caas: []*dns.CAA{ - {Tag: issueTag, Value: "not-example-ca"}, - {Tag: issueTag, Value: "example-ca"}, + { + givenFQDN: "en.wikipedia.org.", + mockDNS: []interaction{ + {"SOA en.wikipedia.org.", &dns.Msg{ + MsgHdr: dns.MsgHdr{Rcode: dns.RcodeSuccess}, + Answer: []dns.RR{ + &dns.CNAME{Hdr: dns.RR_Header{Name: "en.wikipedia.org.", Rrtype: dns.TypeCNAME, Class: dns.ClassINET, Ttl: 13213}, Target: "dyna.wikimedia.org."}, + }, + Ns: []dns.RR{ + &dns.SOA{Hdr: dns.RR_Header{Name: "wikimedia.org.", Rrtype: dns.TypeSOA, Class: dns.ClassINET, Ttl: 400}, Ns: "ns0.wikimedia.org.", Mbox: "hostmaster.wikimedia.org.", Serial: 2025050119, Refresh: 43200, Retry: 7200, Expire: 1209600, Minttl: 600}, + }, + }}, + {"SOA wikipedia.org.", &dns.Msg{ + MsgHdr: dns.MsgHdr{Rcode: dns.RcodeSuccess}, + Answer: []dns.RR{ + &dns.SOA{Hdr: dns.RR_Header{Name: "wikipedia.org.", Rrtype: dns.TypeSOA, Class: dns.ClassINET, Ttl: 2920}, Ns: "ns0.wikimedia.org.", Mbox: "hostmaster.wikimedia.org.", Serial: 2025032815, Refresh: 43200, Retry: 7200, Expire: 1209600, Minttl: 3600}, + }, + }}, + {"NS wikipedia.org.", &dns.Msg{ + MsgHdr: dns.MsgHdr{Rcode: dns.RcodeSuccess}, + Answer: []dns.RR{ + &dns.NS{Hdr: dns.RR_Header{Name: "wikipedia.org.", Rrtype: dns.TypeNS, Class: dns.ClassINET, Ttl: 86297}, Ns: "ns1.wikimedia.org."}, + &dns.NS{Hdr: dns.RR_Header{Name: "wikipedia.org.", Rrtype: dns.TypeNS, Class: dns.ClassINET, Ttl: 86297}, Ns: "ns2.wikimedia.org."}, + &dns.NS{Hdr: dns.RR_Header{Name: "wikipedia.org.", Rrtype: dns.TypeNS, Class: dns.ClassINET, Ttl: 86297}, Ns: "ns0.wikimedia.org."}, + }, + }}, }, - issuerIDs: map[string]bool{"example-ca": true}, - isWildcard: false, - matches: true, + expectNSs: []string{"ns0.wikimedia.org.", "ns1.wikimedia.org.", "ns2.wikimedia.org."}, }, - "matches with a wildcard name if the wildcard tag permits the CA": { - caas: []*dns.CAA{ - {Tag: issueTag, Value: "not-example-ca"}, - {Tag: issuewildTag, Value: "example-ca"}, + { + givenFQDN: "www.google.com.", + mockDNS: []interaction{ + {"SOA www.google.com.", &dns.Msg{ + MsgHdr: dns.MsgHdr{Rcode: dns.RcodeSuccess}, + Ns: []dns.RR{ + &dns.SOA{Hdr: dns.RR_Header{Name: "google.com.", Rrtype: dns.TypeSOA, Class: dns.ClassINET, Ttl: 6}, Ns: "ns1.google.com.", Mbox: "dns-admin.google.com.", Serial: 754576681, Refresh: 900, Retry: 900, Expire: 1800, Minttl: 60}, + }, + }}, + {"SOA google.com.", &dns.Msg{ + MsgHdr: dns.MsgHdr{Rcode: dns.RcodeSuccess}, + Answer: []dns.RR{ + &dns.SOA{Hdr: dns.RR_Header{Name: "google.com.", Rrtype: dns.TypeSOA, Class: dns.ClassINET, Ttl: 60}, Ns: "ns1.google.com.", Mbox: "dns-admin.google.com.", Serial: 754576681, Refresh: 900, Retry: 900, Expire: 1800, Minttl: 60}, + }, + }}, + {"NS google.com.", &dns.Msg{ + MsgHdr: dns.MsgHdr{Rcode: dns.RcodeSuccess}, + Answer: []dns.RR{ + &dns.NS{Hdr: dns.RR_Header{Name: "google.com.", Rrtype: dns.TypeNS, Class: dns.ClassINET, Ttl: 73176}, Ns: "ns4.google.com."}, + &dns.NS{Hdr: dns.RR_Header{Name: "google.com.", Rrtype: dns.TypeNS, Class: dns.ClassINET, Ttl: 73176}, Ns: "ns2.google.com."}, + &dns.NS{Hdr: dns.RR_Header{Name: "google.com.", Rrtype: dns.TypeNS, Class: dns.ClassINET, Ttl: 73176}, Ns: "ns1.google.com."}, + &dns.NS{Hdr: dns.RR_Header{Name: "google.com.", Rrtype: dns.TypeNS, Class: dns.ClassINET, Ttl: 73176}, Ns: "ns3.google.com."}, + }, + }}, }, - issuerIDs: map[string]bool{"example-ca": true}, - isWildcard: true, - matches: true, + expectNSs: []string{"ns1.google.com.", "ns2.google.com.", "ns3.google.com.", "ns4.google.com."}, }, - "does not match with a wildcard name if the issuewild tag is set and does not match, but an issue tag does": { - caas: []*dns.CAA{ - {Tag: issueTag, Value: "example-ca"}, - {Tag: issuewildTag, Value: "not-example-ca"}, + { + givenFQDN: "physics.georgetown.edu.", + mockDNS: []interaction{ + {"SOA physics.georgetown.edu.", &dns.Msg{ + MsgHdr: dns.MsgHdr{Rcode: dns.RcodeSuccess}, + Answer: []dns.RR{ + &dns.SOA{Hdr: dns.RR_Header{Name: "physics.georgetown.edu.", Rrtype: dns.TypeSOA, Class: dns.ClassINET, Ttl: 300}, Ns: "ns.b1ddi.physics.georgetown.edu.", Mbox: "ncs-sm.georgetown.edu.", Serial: 2011022637, Refresh: 10800, Retry: 3600, Expire: 2419200, Minttl: 300}, + }, + }}, + {"NS physics.georgetown.edu.", &dns.Msg{ + MsgHdr: dns.MsgHdr{Rcode: dns.RcodeSuccess}, + Answer: []dns.RR{ + &dns.NS{Hdr: dns.RR_Header{Name: "physics.georgetown.edu.", Rrtype: dns.TypeNS, Class: dns.ClassINET, Ttl: 196}, Ns: "ns4.georgetown.edu."}, + &dns.NS{Hdr: dns.RR_Header{Name: "physics.georgetown.edu.", Rrtype: dns.TypeNS, Class: dns.ClassINET, Ttl: 196}, Ns: "ns.b1ddi.physics.georgetown.edu."}, + &dns.NS{Hdr: dns.RR_Header{Name: "physics.georgetown.edu.", Rrtype: dns.TypeNS, Class: dns.ClassINET, Ttl: 196}, Ns: "ns6.georgetown.edu."}, + &dns.NS{Hdr: dns.RR_Header{Name: "physics.georgetown.edu.", Rrtype: dns.TypeNS, Class: dns.ClassINET, Ttl: 196}, Ns: "ns5.georgetown.edu."}, + }, + }}, }, - issuerIDs: map[string]bool{"example-ca": true}, - isWildcard: true, - matches: false, + expectNSs: []string{"ns.b1ddi.physics.georgetown.edu.", "ns4.georgetown.edu.", "ns5.georgetown.edu.", "ns6.georgetown.edu."}, }, } - for n, test := range tests { - t.Run(n, func(t *testing.T) { - m := matchCAA(test.caas, test.issuerIDs, test.isWildcard) - if test.matches != m { - t.Errorf("expected match to equal %t but got %t", test.matches, m) - } + for _, tc := range tests { + t.Run(tc.givenFQDN, func(t *testing.T) { + withMockDNSQuery(t, tc.mockDNS) + nss, err := lookupNameservers(t.Context(), tc.givenFQDN, []string{"not-used"}) + require.NoError(t, err) + assert.ElementsMatch(t, tc.expectNSs, nss, "Expected nameservers do not match") }) } } -func TestPreCheckDNS(t *testing.T) { - // TODO: find a better TXT record to use in tests - ok, err := PreCheckDNS("google.com.", "v=spf1 include:_spf.google.com ~all", []string{"8.8.8.8:53"}, true) - if err != nil || !ok { - t.Errorf("preCheckDNS failed for acme-staging.api.letsencrypt.org: %s", err.Error()) - } -} - -func TestPreCheckDNSNonAuthoritative(t *testing.T) { - // TODO: find a better TXT record to use in tests - ok, err := PreCheckDNS("google.com.", "v=spf1 include:_spf.google.com ~all", []string{"1.1.1.1:53"}, false) - if err != nil || !ok { - t.Errorf("preCheckDNS failed for acme-staging.api.letsencrypt.org: %s", err.Error()) - } -} - -func TestLookupNameserversOK(t *testing.T) { - for _, tt := range lookupNameserversTestsOK { - nss, err := lookupNameservers(tt.fqdn, RecursiveNameservers) - if err != nil { - t.Fatalf("#%s: got %q; want nil", tt.fqdn, err) - } - - sort.Strings(nss) - sort.Strings(tt.nss) - - if !reflect.DeepEqual(nss, tt.nss) { - t.Errorf("#%s: got %v; want %v", tt.fqdn, nss, tt.nss) - } - } -} - func TestLookupNameserversErr(t *testing.T) { - for _, tt := range lookupNameserversTestsErr { - _, err := lookupNameservers(tt.fqdn, RecursiveNameservers) - if err == nil { - t.Fatalf("#%s: expected %q (error); got ", tt.fqdn, tt.error) - } - - if !strings.Contains(err.Error(), tt.error) { - t.Errorf("#%s: expected %q (error); got %q", tt.fqdn, tt.error, err) - continue - } - } + t.Run("no SOA record can be found", func(t *testing.T) { + withMockDNSQuery(t, []interaction{ + {"SOA _null.n0n0.", &dns.Msg{ + MsgHdr: dns.MsgHdr{Rcode: dns.RcodeSuccess}, + Ns: []dns.RR{ + &dns.SOA{Hdr: dns.RR_Header{Name: ".", Rrtype: dns.TypeSOA, Class: dns.ClassINET, Ttl: 2655}, Ns: "a.root-servers.net.", Mbox: "nstld.verisign-grs.com.", Serial: 2025050500, Refresh: 1800, Retry: 900, Expire: 604800, Minttl: 86400}, + }, + }}, + {"SOA n0n0.", &dns.Msg{ + MsgHdr: dns.MsgHdr{Rcode: dns.RcodeSuccess}, + Ns: []dns.RR{ + &dns.SOA{Hdr: dns.RR_Header{Name: ".", Rrtype: dns.TypeSOA, Class: dns.ClassINET, Ttl: 2664}, Ns: "a.root-servers.net.", Mbox: "nstld.verisign-grs.com.", Serial: 2025050500, Refresh: 1800, Retry: 900, Expire: 604800, Minttl: 86400}, + }, + }}, + }) + _, err := lookupNameservers(t.Context(), "_null.n0n0.", []string{"not-used"}) + require.Error(t, err) + assert.Contains(t, err.Error(), "Could not determine the zone") + }) } func TestFindZoneByFqdn(t *testing.T) { - for _, tt := range findZoneByFqdnTests { - res, err := FindZoneByFqdn(tt.fqdn, RecursiveNameservers) - if err != nil { - t.Errorf("FindZoneByFqdn failed for %s: %v", tt.fqdn, err) - } - if res != tt.zone { - t.Errorf("%s: got %s; want %s", tt.fqdn, res, tt.zone) - } + tests := []struct { + givenFQDN string + mockDNS []interaction + expectZone string + }{ + { + // In this test, we make sure that we are able to recurse up to + // google.com given that it is a CNAME that points to + // googlemail.l.google.com. Data from 2021-04-17: + // https://dnsviz.net/d/mail.google.com/YHtmLQ/responses/ + givenFQDN: "mail.google.com.", + expectZone: "google.com.", + mockDNS: []interaction{ + {"SOA mail.google.com.", &dns.Msg{ + MsgHdr: dns.MsgHdr{Rcode: dns.RcodeSuccess}, + Answer: []dns.RR{ + &dns.CNAME{Hdr: dns.RR_Header{Name: "mail.google.com.", Rrtype: dns.TypeCNAME, Class: dns.ClassINET, Ttl: 604800}, Target: "googlemail.l.google.com."}, + }, + Ns: []dns.RR{ + &dns.SOA{Hdr: dns.RR_Header{Name: "google.com.", Rrtype: dns.TypeSOA, Class: dns.ClassINET, Ttl: 300}, Ns: "ns1.google.com.", Mbox: "dns-admin.google.com.", Serial: 754990191, Refresh: 900, Retry: 900, Expire: 1800, Minttl: 60}, + }, + }}, + {"SOA google.com.", &dns.Msg{ + MsgHdr: dns.MsgHdr{Rcode: dns.RcodeSuccess}, + Answer: []dns.RR{ + &dns.SOA{Hdr: dns.RR_Header{Name: "google.com.", Rrtype: dns.TypeSOA, Class: dns.ClassINET, Ttl: 32}, Ns: "ns1.google.com.", Mbox: "dns-admin.google.com.", Serial: 754990191, Refresh: 900, Retry: 900, Expire: 1800, Minttl: 60}, + }, + }}, + }, + }, + { + // This test checks that we do not return SOA records that are not a + // suffix of the domain. In the below test, the SOA RR `example.com` + // must be ignored. We detect such a case by ignoring SOA that are + // returned alongside CNAME records. This is a consequence of RFC + // 2181 that states that CNAME records cannot exist at the root of a + // zone. See: https://github.com/go-acme/lego/pull/449. + givenFQDN: "cross-zone-example.assets.sh.", + expectZone: "assets.sh.", + mockDNS: []interaction{ + {"SOA cross-zone-example.assets.sh.", &dns.Msg{ + MsgHdr: dns.MsgHdr{Rcode: dns.RcodeSuccess}, + Answer: []dns.RR{ + &dns.CNAME{Hdr: dns.RR_Header{Name: "cross-zone-example.assets.sh.", Rrtype: dns.TypeCNAME, Class: dns.ClassINET, Ttl: 300}, Target: "example.com."}, + &dns.SOA{Hdr: dns.RR_Header{Name: "example.com.", Rrtype: dns.TypeSOA, Class: dns.ClassINET, Ttl: 2633}, Ns: "ns.icann.org.", Mbox: "noc.dns.icann.org.", Serial: 2025011636, Refresh: 7200, Retry: 3600, Expire: 1209600, Minttl: 3600}, + }, + }}, + {"SOA assets.sh.", &dns.Msg{ + MsgHdr: dns.MsgHdr{Rcode: dns.RcodeSuccess}, + Answer: []dns.RR{ + &dns.SOA{Hdr: dns.RR_Header{Name: "assets.sh.", Rrtype: dns.TypeSOA, Class: dns.ClassINET, Ttl: 979}, Ns: "gina.ns.cloudflare.com.", Mbox: "dns.cloudflare.com.", Serial: 2371821451, Refresh: 10000, Retry: 2400, Expire: 604800, Minttl: 1800}, + }, + }}, + }, + }, + { + // This test shows that FindZoneByFqdn can work is able to continue + // climbing up the tree when a non-existent domain is found. We do + // this because the `_acme-challenge` subdomain may not exist yet, + // but we still want to find the zone for the domain. + givenFQDN: "nonexistent.cert-manager.io.", + expectZone: "cert-manager.io.", + mockDNS: []interaction{ + {"SOA nonexistent.cert-manager.io.", &dns.Msg{ + MsgHdr: dns.MsgHdr{Rcode: dns.RcodeNameError}, // NXDOMAIN + Ns: []dns.RR{ + &dns.SOA{Hdr: dns.RR_Header{Name: "cert-manager.io.", Rrtype: dns.TypeSOA, Class: dns.ClassINET, Ttl: 21}, Ns: "ns-cloud-a1.googledomains.com.", Mbox: "cloud-dns-hostmaster.google.com.", Serial: 2, Refresh: 21600, Retry: 3600, Expire: 259200, Minttl: 300}, + }, + }}, + {"SOA cert-manager.io.", &dns.Msg{ + MsgHdr: dns.MsgHdr{Rcode: dns.RcodeSuccess}, + Answer: []dns.RR{ + &dns.SOA{Hdr: dns.RR_Header{Name: "cert-manager.io.", Rrtype: dns.TypeSOA, Class: dns.ClassINET, Ttl: 31}, Ns: "ns-cloud-a1.googledomains.com.", Mbox: "cloud-dns-hostmaster.google.com.", Serial: 2, Refresh: 21600, Retry: 3600, Expire: 259200, Minttl: 300}, + }, + }}, + }, + }, + { + // This test shows that FindZoneByFqdn works with eTLD domains + // (effective top-level domain). + givenFQDN: "example.com.ac.", + expectZone: "ac.", + mockDNS: []interaction{ + {"SOA example.com.ac.", &dns.Msg{ + MsgHdr: dns.MsgHdr{Rcode: dns.RcodeNameError}, // NXDOMAIN + Ns: []dns.RR{ + &dns.SOA{Hdr: dns.RR_Header{Name: "ac.", Rrtype: dns.TypeSOA, Class: dns.ClassINET, Ttl: 3500}, Ns: "a0.nic.ac", Mbox: "hostmaster.donuts.email", Serial: 1746448794, Refresh: 7200, Retry: 900, Expire: 1209600, Minttl: 3600}, + }, + }}, + {"SOA com.ac.", &dns.Msg{ + MsgHdr: dns.MsgHdr{Rcode: dns.RcodeNameError}, // NXDOMAIN + Ns: []dns.RR{ + &dns.SOA{Hdr: dns.RR_Header{Name: "ac.", Rrtype: dns.TypeSOA, Class: dns.ClassINET, Ttl: 3496}, Ns: "a0.nic.ac", Mbox: "hostmaster.donuts.email", Serial: 1746448794, Refresh: 7200, Retry: 900, Expire: 1209600, Minttl: 3600}, + }, + }}, + {"SOA ac.", &dns.Msg{ + MsgHdr: dns.MsgHdr{Rcode: dns.RcodeSuccess}, + Answer: []dns.RR{ + &dns.SOA{Hdr: dns.RR_Header{Name: "ac.", Rrtype: dns.TypeSOA, Class: dns.ClassINET, Ttl: 3486}, Ns: "a0.nic.ac", Mbox: "hostmaster.donuts.email", Serial: 1746448794, Refresh: 7200, Retry: 900, Expire: 1209600, Minttl: 3600}, + }, + }}, + }, + }, + } + for _, tt := range tests { + t.Run(tt.givenFQDN, func(t *testing.T) { + withMockDNSQuery(t, tt.mockDNS) + gotZone, err := FindZoneByFqdn(t.Context(), tt.givenFQDN, []string{"not-used"}) + require.NoError(t, err) + assert.Equal(t, tt.expectZone, gotZone) + }) } } func TestCheckAuthoritativeNss(t *testing.T) { - for _, tt := range checkAuthoritativeNssTests { - ok, _ := checkAuthoritativeNss(tt.fqdn, tt.value, tt.ns) - if ok != tt.ok { - t.Errorf("%s: got %t; want %t", tt.fqdn, ok, tt.ok) - } - } -} + t.Run("happy path", func(t *testing.T) { + withMockDNSQuery(t, []interaction{ + {"TXT 8.8.8.8.asn.routeviews.org.", &dns.Msg{ + MsgHdr: dns.MsgHdr{Rcode: dns.RcodeSuccess}, + Answer: []dns.RR{ + &dns.TXT{Hdr: dns.RR_Header{Name: "8.8.8.8.asn.routeviews.org.", Rrtype: dns.TypeTXT, Class: dns.ClassINET, Ttl: 300}, Txt: []string{"fe01="}}, + }, + }}, + }) + ok, err := checkAuthoritativeNss(t.Context(), "8.8.8.8.asn.routeviews.org.", "fe01=", []string{"1.1.1.1:53"}) + require.NoError(t, err) + assert.True(t, ok) + }) -func TestCheckAuthoritativeNssErr(t *testing.T) { - for _, tt := range checkAuthoritativeNssTestsErr { - _, err := checkAuthoritativeNss(tt.fqdn, tt.value, tt.ns) - if err == nil { - t.Fatalf("#%s: expected %q (error); got ", tt.fqdn, tt.error) - } - if !strings.Contains(err.Error(), tt.error) { - t.Errorf("#%s: expected %q (error); got %q", tt.fqdn, tt.error, err) - continue - } - } + t.Run("TXT not found", func(t *testing.T) { + withMockDNSQuery(t, []interaction{ + {"TXT 8.8.8.8.asn.routeviews.org.", &dns.Msg{ + MsgHdr: dns.MsgHdr{Rcode: dns.RcodeNameError}, + }}, + }) + ok, err := checkAuthoritativeNss(t.Context(), "8.8.8.8.asn.routeviews.org.", "fe01=", []string{"1.1.1.1:53"}) + require.NoError(t, err) + assert.False(t, ok) + }) + + t.Run("errors out when DnsQuery fails", func(t *testing.T) { + withMockDNSQueryErr(t, fmt.Errorf("some error coming from DnsQuery")) + + _, err := checkAuthoritativeNss(t.Context(), "8.8.8.8.asn.routeviews.org.", "fe01=", []string{"1.1.1.1:53"}) + assert.EqualError(t, err, "some error coming from DnsQuery") + }) } +// These tests don't require mocking out dnsQuery as getNameservers doesn't rely +// on it. func TestResolveConfServers(t *testing.T) { + checkResolvConfServersTests := []struct { + fixture string + expected []string + defaults []string + }{ + {"testdata/resolv.conf.1", []string{"10.200.3.249:53", "10.200.3.250:5353", "[2001:4860:4860::8844]:53", "[10.0.0.1]:5353"}, []string{"127.0.0.1:53"}}, + {"testdata/resolv.conf.nonexistent", []string{"127.0.0.1:53"}, []string{"127.0.0.1:53"}}, + } for _, tt := range checkResolvConfServersTests { result := getNameservers(tt.fixture, tt.defaults) @@ -257,85 +312,7 @@ func TestResolveConfServers(t *testing.T) { } } -// TODO: find a website which uses issuewild? -func TestValidateCAA(t *testing.T) { - // google installs a CAA record at google.com - // ask for the www.google.com record to test that - // we recurse up the labels - err := ValidateCAA("www.google.com", []string{"letsencrypt", "pki.goog"}, false, RecursiveNameservers) - if err != nil { - t.Fatalf("unexpected error: %s", err) - } - // now ask, expecting a CA that won't match - err = ValidateCAA("www.google.com", []string{"daniel.homebrew.ca"}, false, RecursiveNameservers) - if err == nil { - t.Fatalf("expected err, got success") - } - // if the CAA record allows non-wildcards then it has an `issue` tag, - // and it is known that it has no issuewild tags, then wildcard certificates - // will also be allowed - err = ValidateCAA("www.google.com", []string{"pki.goog"}, true, RecursiveNameservers) - if err != nil { - t.Fatalf("unexpected error: %s", err) - } - // ask for a domain you know does not have CAA records. - // it should succeed - err = ValidateCAA("www.example.org", []string{"daniel.homebrew.ca"}, false, RecursiveNameservers) - if err != nil { - t.Fatalf("expected err, got %s", err) - } -} - func Test_followCNAMEs(t *testing.T) { - dnsQuery = func(fqdn string, rtype uint16, nameservers []string, recursive bool) (in *dns.Msg, err error) { - msg := &dns.Msg{} - msg.Rcode = dns.RcodeSuccess - switch fqdn { - case "test1.example.com": - msg.Answer = []dns.RR{ - &dns.CNAME{ - Target: "test2.example.com", - }, - } - case "test2.example.com": - msg.Answer = []dns.RR{ - &dns.CNAME{ - - Target: "test3.example.com", - }, - } - case "recursive.example.com": - msg.Answer = []dns.RR{ - &dns.CNAME{ - - Target: "recursive1.example.com", - }, - } - case "recursive1.example.com": - msg.Answer = []dns.RR{ - &dns.CNAME{ - Target: "recursive.example.com", - }, - } - case "error.example.com": - return nil, fmt.Errorf("Error while mocking resolve for %q", fqdn) - } - - // inject fqdn in headers - for _, rr := range msg.Answer { - if cn, ok := rr.(*dns.CNAME); ok { - cn.Hdr = dns.RR_Header{ - Name: fqdn, - } - } - } - - return msg, nil - } - defer func() { - // restore the mock - dnsQuery = DNSQuery - }() type args struct { fqdn string nameservers []string @@ -343,6 +320,7 @@ func Test_followCNAMEs(t *testing.T) { } tests := []struct { name string + mock []interaction args args want string wantErr bool @@ -350,37 +328,69 @@ func Test_followCNAMEs(t *testing.T) { { name: "Resolve CNAME 3 down", args: args{ - fqdn: "test1.example.com", + fqdn: "test1.example.com.", }, - want: "test3.example.com", + want: "test3.example.com.", wantErr: false, + mock: []interaction{ + {"CNAME test1.example.com.", &dns.Msg{ + MsgHdr: dns.MsgHdr{Rcode: dns.RcodeSuccess}, + Answer: []dns.RR{ + &dns.CNAME{Hdr: dns.RR_Header{Name: "test1.example.com.", Rrtype: dns.TypeCNAME, Class: dns.ClassINET, Ttl: 300}, Target: "test2.example.com."}, + }, + }}, + {"CNAME test2.example.com.", &dns.Msg{ + MsgHdr: dns.MsgHdr{Rcode: dns.RcodeSuccess}, + Answer: []dns.RR{ + &dns.CNAME{Hdr: dns.RR_Header{Name: "test2.example.com.", Rrtype: dns.TypeCNAME, Class: dns.ClassINET, Ttl: 300}, Target: "test3.example.com."}, + }, + }}, + {"CNAME test3.example.com.", &dns.Msg{ + MsgHdr: dns.MsgHdr{Rcode: dns.RcodeSuccess}, + Answer: []dns.RR{}, + }}, + }, }, { name: "Resolve CNAME 1 down", args: args{ - fqdn: "test3.example.com", + fqdn: "test3.example.com.", }, - want: "test3.example.com", + want: "test3.example.com.", wantErr: false, - }, - { - name: "Error when DNS fails", - args: args{ - fqdn: "error.example.com", + mock: []interaction{ + {"CNAME test3.example.com.", &dns.Msg{ + MsgHdr: dns.MsgHdr{Rcode: dns.RcodeSuccess}, + Answer: []dns.RR{}, + }}, }, - wantErr: true, }, { name: "Error on recursive CNAME", args: args{ - fqdn: "recursive.example.com", + fqdn: "recursive.example.com.", }, wantErr: true, + mock: []interaction{ + {"CNAME recursive.example.com.", &dns.Msg{ + MsgHdr: dns.MsgHdr{Rcode: dns.RcodeSuccess}, + Answer: []dns.RR{ + &dns.CNAME{Hdr: dns.RR_Header{Name: "recursive.example.com.", Rrtype: dns.TypeCNAME, Class: dns.ClassINET, Ttl: 300}, Target: "recursive1.example.com."}, + }, + }}, + {"CNAME recursive1.example.com.", &dns.Msg{ + MsgHdr: dns.MsgHdr{Rcode: dns.RcodeSuccess}, + Answer: []dns.RR{ + &dns.CNAME{Hdr: dns.RR_Header{Name: "recursive1.example.com.", Rrtype: dns.TypeCNAME, Class: dns.ClassINET, Ttl: 300}, Target: "recursive.example.com."}, + }, + }}, + }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got, err := followCNAMEs(tt.args.fqdn, tt.args.nameservers, tt.args.fqdnChain...) + withMockDNSQuery(t, tt.mock) + got, err := followCNAMEs(t.Context(), tt.args.fqdn, tt.args.nameservers, tt.args.fqdnChain...) if (err != nil) != tt.wantErr { t.Errorf("followCNAMEs() error = %v, wantErr %v", err, tt.wantErr) return @@ -391,3 +401,57 @@ func Test_followCNAMEs(t *testing.T) { }) } } + +type interaction struct { + expectedQuery string // E.g., "SOA en.wikipedia.org." + mockAnswer *dns.Msg +} + +var mu = &sync.Mutex{} // Protects the global dnsQuery variable. + +func withMockDNSQuery(t *testing.T, mockDNS []interaction) { + mu.Lock() + t.Cleanup(func() { + mu.Unlock() + }) + + // Since dnsQuery is a global variable, we need to save its original value + // and restore it after the test. + origDNSQuery := dnsQuery + t.Cleanup(func() { dnsQuery = origDNSQuery }) + + count := atomic.Int32{} + t.Cleanup(func() { + assert.Equal(t, len(mockDNS), int(count.Load()), "not all DNS queries were called") + }) + + dnsQuery = func(ctx context.Context, fqdn string, rtype uint16, nameservers []string, recursive bool) (in *dns.Msg, err error) { + got := dns.TypeToString[rtype] + " " + fqdn + + count.Add(1) + if int(count.Load()) > len(mockDNS) { + t.Fatalf("too many DNS queries, was expecting %d queries but got %d. The unexpected query is: %s", len(mockDNS), count.Load(), got) + } + + mock := mockDNS[count.Load()-1] + assert.Equal(t, mock.expectedQuery, got, "DNS query doesn't match the expected query #%d", count.Load()) + return mock.mockAnswer.Copy().SetQuestion(fqdn, rtype), nil + } +} + +// Same as above except it simulates an error. +func withMockDNSQueryErr(t *testing.T, err error) { + mu.Lock() + t.Cleanup(func() { + mu.Unlock() + }) + + // Since dnsQuery is a global variable, we need to save its original value + // and restore it after the test. + origDNSQuery := dnsQuery + t.Cleanup(func() { dnsQuery = origDNSQuery }) + + dnsQuery = func(ctx context.Context, fqdn string, rtype uint16, nameservers []string, recursive bool) (in *dns.Msg, err2 error) { + return nil, err + } +} diff --git a/pkg/issuer/acme/dns/util_test.go b/pkg/issuer/acme/dns/util_test.go index 175b1d26226..32345e12003 100644 --- a/pkg/issuer/acme/dns/util_test.go +++ b/pkg/issuer/acme/dns/util_test.go @@ -17,11 +17,11 @@ limitations under the License. package dns import ( + "context" "errors" "testing" cmacme "github.com/cert-manager/cert-manager/pkg/apis/acme/v1" - v1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" "github.com/cert-manager/cert-manager/pkg/controller/test" "github.com/cert-manager/cert-manager/pkg/issuer/acme/dns/acmedns" "github.com/cert-manager/cert-manager/pkg/issuer/acme/dns/azuredns" @@ -30,11 +30,6 @@ import ( "github.com/cert-manager/cert-manager/pkg/issuer/acme/dns/digitalocean" "github.com/cert-manager/cert-manager/pkg/issuer/acme/dns/route53" "github.com/cert-manager/cert-manager/pkg/issuer/acme/dns/util" - "github.com/cert-manager/cert-manager/test/unit/gen" -) - -const ( - defaultTestIssuerName = "test-issuer" ) type solverFixture struct { @@ -42,8 +37,6 @@ type solverFixture struct { Solver *Solver *test.Builder - // Issuer to be passed to functions on the Solver (a default will be used if nil) - Issuer v1.GenericIssuer // Challenge resource to use during tests Challenge *cmacme.Challenge @@ -53,7 +46,7 @@ type solverFixture struct { // This is useful if you want to load the clientset with some resources *after* the // fixture has been created. PreFn func(*testing.T, *solverFixture) - // CheckFn should performs checks to ensure the output of the test is as expected. + // CheckFn should perform checks to ensure the output of the test is as expected. // Optional additional values may be provided, which represent the output of the // function under test. CheckFn func(*testing.T, *solverFixture, ...interface{}) @@ -66,9 +59,6 @@ type solverFixture struct { } func (s *solverFixture) Setup(t *testing.T) { - if s.Issuer == nil { - s.Issuer = gen.Issuer(defaultTestIssuerName, gen.SetIssuerACME(cmacme.ACMEIssuer{})) - } if s.testResources == nil { s.testResources = map[string]interface{}{} } @@ -102,7 +92,7 @@ func buildFakeSolver(b *test.Builder, dnsProviders dnsProviderConstructors) *Sol b.InitWithRESTConfig() s := &Solver{ Context: b.Context, - secretLister: b.Context.KubeSharedInformerFactory.Core().V1().Secrets().Lister(), + secretLister: b.Context.KubeSharedInformerFactory.Secrets().Lister(), dnsProviderConstructors: dnsProviders, } b.Start() @@ -128,7 +118,7 @@ func newFakeDNSProviders() *fakeDNSProviders { calls: []fakeDNSProviderCall{}, } f.constructors = dnsProviderConstructors{ - cloudDNS: func(project string, serviceAccount []byte, dns01Nameservers []string, ambient bool, hostedZoneName string) (*clouddns.DNSProvider, error) { + cloudDNS: func(ctx context.Context, project string, serviceAccount []byte, dns01Nameservers []string, ambient bool, hostedZoneName string) (*clouddns.DNSProvider, error) { f.call("clouddns", project, serviceAccount, util.RecursiveNameservers, ambient, hostedZoneName) return nil, nil }, @@ -139,8 +129,8 @@ func newFakeDNSProviders() *fakeDNSProviders { } return nil, nil }, - route53: func(accessKey, secretKey, hostedZoneID, region, role string, ambient bool, dns01Nameservers []string, userAgent string) (*route53.DNSProvider, error) { - f.call("route53", accessKey, secretKey, hostedZoneID, region, role, ambient, util.RecursiveNameservers) + route53: func(ctx context.Context, accessKey, secretKey, hostedZoneID, region, role, webIdentityToken string, ambient bool, dns01Nameservers []string, userAgent string) (*route53.DNSProvider, error) { + f.call("route53", accessKey, secretKey, hostedZoneID, region, role, webIdentityToken, ambient, util.RecursiveNameservers) return nil, nil }, azureDNS: func(environment, clientID, clientSecret, subscriptionID, tenantID, resourceGroupName, hostedZoneName string, dns01Nameservers []string, ambient bool, managedIdentity *cmacme.AzureManagedIdentity) (*azuredns.DNSProvider, error) { @@ -151,7 +141,7 @@ func newFakeDNSProviders() *fakeDNSProviders { f.call("acmedns", host, accountJson, dns01Nameservers) return nil, nil }, - digitalOcean: func(token string, dns01Nameservers []string) (*digitalocean.DNSProvider, error) { + digitalOcean: func(token string, dns01Nameservers []string, userAgent string) (*digitalocean.DNSProvider, error) { f.call("digitalocean", token, util.RecursiveNameservers) return nil, nil }, diff --git a/pkg/issuer/acme/http/http.go b/pkg/issuer/acme/http/http.go index 574b90b2cdd..4ec8e0b3804 100644 --- a/pkg/issuer/acme/http/http.go +++ b/pkg/issuer/acme/http/http.go @@ -29,10 +29,10 @@ import ( corev1 "k8s.io/api/core/v1" utilerrors "k8s.io/apimachinery/pkg/util/errors" - corev1listers "k8s.io/client-go/listers/core/v1" networkingv1listers "k8s.io/client-go/listers/networking/v1" + "k8s.io/client-go/tools/cache" k8snet "k8s.io/utils/net" - gwapilisters "sigs.k8s.io/gateway-api/pkg/client/listers/apis/v1alpha2" + gwapilisters "sigs.k8s.io/gateway-api/pkg/client/listers/apis/v1" cmacme "github.com/cert-manager/cert-manager/pkg/apis/acme/v1" v1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" @@ -49,6 +49,11 @@ const ( acmeSolverListenPort = 8089 loggerName = "http01" + + // maxAcmeChallengeBodySize is the max size of a received response body for an + // acme http challenge. The value is arbitrary and is chosen to be large enough + // that any reasonable response would fit. + maxAcmeChallengeBodySize = 1024 * 1024 // 1mb ) var ( @@ -59,8 +64,8 @@ var ( type Solver struct { *controller.Context - podLister corev1listers.PodLister - serviceLister corev1listers.ServiceLister + podLister cache.GenericLister + serviceLister cache.GenericLister ingressLister networkingv1listers.IngressLister httpRouteLister gwapilisters.HTTPRouteLister @@ -74,10 +79,10 @@ type reachabilityTest func(ctx context.Context, url *url.URL, key string, dnsSer func NewSolver(ctx *controller.Context) (*Solver, error) { return &Solver{ Context: ctx, - podLister: ctx.KubeSharedInformerFactory.Core().V1().Pods().Lister(), - serviceLister: ctx.KubeSharedInformerFactory.Core().V1().Services().Lister(), - ingressLister: ctx.KubeSharedInformerFactory.Networking().V1().Ingresses().Lister(), - httpRouteLister: ctx.GWShared.Gateway().V1alpha2().HTTPRoutes().Lister(), + podLister: ctx.HTTP01ResourceMetadataInformersFactory.ForResource(corev1.SchemeGroupVersion.WithResource("pods")).Lister(), + serviceLister: ctx.HTTP01ResourceMetadataInformersFactory.ForResource(corev1.SchemeGroupVersion.WithResource("services")).Lister(), + ingressLister: ctx.KubeSharedInformerFactory.Ingresses().Lister(), + httpRouteLister: ctx.GWShared.Gateway().V1().HTTPRoutes().Lister(), testReachability: testReachability, requiredPasses: 5, }, nil @@ -108,19 +113,22 @@ func (s *Solver) Present(ctx context.Context, issuer v1.GenericIssuer, ch *cmacm log := logf.FromContext(ctx).WithName(loggerName) ctx = logf.NewContext(ctx, log) - _, podErr := s.ensurePod(ctx, ch) - svc, svcErr := s.ensureService(ctx, ch) + podErr := s.ensurePod(ctx, ch) + svcName, svcErr := s.ensureService(ctx, ch) if svcErr != nil { return utilerrors.NewAggregate([]error{podErr, svcErr}) } var ingressErr, gatewayErr error if ch.Spec.Solver.HTTP01 != nil { if ch.Spec.Solver.HTTP01.Ingress != nil { - _, ingressErr = s.ensureIngress(ctx, ch, svc.Name) + _, ingressErr = s.ensureIngress(ctx, ch, svcName) return utilerrors.NewAggregate([]error{podErr, svcErr, ingressErr}) } if ch.Spec.Solver.HTTP01.GatewayHTTPRoute != nil { - _, gatewayErr = s.ensureGatewayHTTPRoute(ctx, ch, svc.Name) + if !s.GatewaySolverEnabled { + return fmt.Errorf("couldn't Present challenge %s/%s: gateway api is not enabled", ch.Namespace, ch.Name) + } + _, gatewayErr = s.ensureGatewayHTTPRoute(ctx, ch, svcName) return utilerrors.NewAggregate([]error{podErr, svcErr, gatewayErr}) } } @@ -160,13 +168,17 @@ func (s *Solver) Check(ctx context.Context, issuer v1.GenericIssuer, ch *cmacme. ctx = logf.NewContext(ctx, log) log.V(logf.DebugLevel).Info("running self check multiple times to ensure challenge has propagated", "required_passes", s.requiredPasses) - for i := 0; i < s.requiredPasses; i++ { + for i := range s.requiredPasses { err := s.testReachability(ctx, url, ch.Spec.Key, s.HTTP01SolverNameservers, s.Context.RESTConfig.UserAgent) if err != nil { return err } log.V(logf.DebugLevel).Info("reachability test passed, re-checking in 2s time") - time.Sleep(time.Second * 2) + + if i != s.requiredPasses-1 { + // sleep for 2s between checks + time.Sleep(time.Second * 2) + } } log.V(logf.DebugLevel).Info("self check succeeded") @@ -176,7 +188,7 @@ func (s *Solver) Check(ctx context.Context, issuer v1.GenericIssuer, ch *cmacme. // CleanUp will ensure the created service, ingress and pod are clean/deleted of any // cert-manager created data. -func (s *Solver) CleanUp(ctx context.Context, issuer v1.GenericIssuer, ch *cmacme.Challenge) error { +func (s *Solver) CleanUp(ctx context.Context, ch *cmacme.Challenge) error { var errs []error errs = append(errs, s.cleanupPods(ctx, ch)) errs = append(errs, s.cleanupServices(ctx, ch)) @@ -254,7 +266,7 @@ func testReachability(ctx context.Context, url *url.URL, key string, dnsServers // > When redirected to an HTTPS URL, it does not validate certificates (since // > this challenge is intended to bootstrap valid certificates, it may encounter // > self-signed or expired certificates along the way). - InsecureSkipVerify: true, + InsecureSkipVerify: true, // #nosec G402 -- false positive }, } @@ -290,14 +302,14 @@ func testReachability(ctx context.Context, url *url.URL, key string, dnsServers log.V(logf.DebugLevel).Info("failed to perform self check GET request", "error", err) return fmt.Errorf("failed to perform self check GET request '%s': %v", url, err) } + defer response.Body.Close() if response.StatusCode != http.StatusOK { log.V(logf.DebugLevel).Info("received HTTP status code was not StatusOK (200)", "code", response.StatusCode) return fmt.Errorf("wrong status code '%d', expected '%d'", response.StatusCode, http.StatusOK) } - defer response.Body.Close() - presentedKey, err := io.ReadAll(response.Body) + presentedKey, err := io.ReadAll(io.LimitReader(response.Body, maxAcmeChallengeBodySize)) if err != nil { log.V(logf.DebugLevel).Info("failed to decode response body", "error", err) return fmt.Errorf("failed to read response body: %v", err) diff --git a/pkg/issuer/acme/http/http_test.go b/pkg/issuer/acme/http/http_test.go index 9d77697c471..e2800509eb8 100644 --- a/pkg/issuer/acme/http/http_test.go +++ b/pkg/issuer/acme/http/http_test.go @@ -79,7 +79,7 @@ func TestCheck(t *testing.T) { requiredPasses: requiredCallsForPass, } - err := s.Check(context.Background(), nil, test.challenge) + err := s.Check(t.Context(), nil, test.challenge) if err != nil && !test.expectedErr { t.Errorf("Expected Check to return non-nil error, but got %v", err) return @@ -102,17 +102,24 @@ func TestReachabilityCustomDnsServers(t *testing.T) { if err != nil { t.Fatalf("Failed to parse url %s: %v", site, err) } - ips, err := net.LookupIP(u.Host) + ips, err := net.LookupIP(u.Host) // nolint: noctx // We intentionally use LookupIP here for test compatibility if err != nil { t.Fatalf("Failed to resolve %s: %v", u.Host, err) } + dnsServerStarted := make(chan struct{}) dnsServerCalled := int32(0) - server := &dns.Server{Addr: "127.0.0.1:15353", Net: "udp"} - defer server.Shutdown() + server := &dns.Server{Addr: "127.0.0.1:15353", Net: "udp", NotifyStartedFunc: func() { close(dnsServerStarted) }} + defer func() { + if err := server.Shutdown(); err != nil { + t.Error(err) + } + }() - dns.HandleFunc(".", func(w dns.ResponseWriter, r *dns.Msg) { + mux := &dns.ServeMux{} + server.Handler = mux + mux.HandleFunc(".", func(w dns.ResponseWriter, r *dns.Msg) { m := new(dns.Msg) m.SetReply(r) @@ -154,7 +161,15 @@ func TestReachabilityCustomDnsServers(t *testing.T) { t.Errorf("failed to write DNS response: %v", err) } }) - go server.ListenAndServe() + + go func() { + if err := server.ListenAndServe(); err != nil { + t.Error(err) + } + }() + + // Wait for server to have started + <-dnsServerStarted key := "there is no key" @@ -176,7 +191,7 @@ func TestReachabilityCustomDnsServers(t *testing.T) { for _, tt := range tests { atomic.StoreInt32(&dnsServerCalled, 0) - err = testReachability(context.Background(), u, key, tt.dnsServers, "cert-manager-test") + err = testReachability(t.Context(), u, key, tt.dnsServers, "cert-manager-test") switch { case err == nil: t.Errorf("Expected error for testReachability, but got none") diff --git a/pkg/issuer/acme/http/httproute.go b/pkg/issuer/acme/http/httproute.go index 775b2c00eca..ba573147fc4 100644 --- a/pkg/issuer/acme/http/httproute.go +++ b/pkg/issuer/acme/http/httproute.go @@ -19,13 +19,14 @@ package http import ( "context" "fmt" + "maps" "reflect" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" "k8s.io/client-go/util/retry" - "k8s.io/utils/pointer" - gwapi "sigs.k8s.io/gateway-api/apis/v1alpha2" + "k8s.io/utils/ptr" + gwapi "sigs.k8s.io/gateway-api/apis/v1" cmacme "github.com/cert-manager/cert-manager/pkg/apis/acme/v1" logf "github.com/cert-manager/cert-manager/pkg/logs" @@ -79,7 +80,7 @@ func (s *Solver) getGatewayHTTPRoute(ctx context.Context, ch *cmacme.Challenge) // If we find this, try to delete them. for _, httpRoute := range httpRoutes[1:] { log.Info("Deleting extra HTTPRoute", "name", httpRoute.Name, "namespace", httpRoute.Namespace) - err := s.GWClient.GatewayV1alpha2().HTTPRoutes(httpRoute.Namespace).Delete(ctx, httpRoute.Name, metav1.DeleteOptions{}) + err := s.GWClient.GatewayV1().HTTPRoutes(httpRoute.Namespace).Delete(ctx, httpRoute.Name, metav1.DeleteOptions{}) if err != nil { return nil, err } @@ -90,21 +91,17 @@ func (s *Solver) getGatewayHTTPRoute(ctx context.Context, ch *cmacme.Challenge) func (s *Solver) createGatewayHTTPRoute(ctx context.Context, ch *cmacme.Challenge, svcName string) (*gwapi.HTTPRoute, error) { labels := podLabels(ch) - if ch.Spec.Solver.HTTP01.GatewayHTTPRoute.Labels != nil { - for k, v := range ch.Spec.Solver.HTTP01.GatewayHTTPRoute.Labels { - labels[k] = v - } - } + maps.Copy(labels, ch.Spec.Solver.HTTP01.GatewayHTTPRoute.Labels) httpRoute := &gwapi.HTTPRoute{ ObjectMeta: metav1.ObjectMeta{ - GenerateName: "cm-acme-http-solver", + GenerateName: "cm-acme-http-solver-", Namespace: ch.Namespace, Labels: labels, OwnerReferences: []metav1.OwnerReference{*metav1.NewControllerRef(ch, challengeGvk)}, }, Spec: generateHTTPRouteSpec(ch, svcName), } - newHTTPRoute, err := s.GWClient.GatewayV1alpha2().HTTPRoutes(ch.Namespace).Create(ctx, httpRoute, metav1.CreateOptions{}) + newHTTPRoute, err := s.GWClient.GatewayV1().HTTPRoutes(ch.Namespace).Create(ctx, httpRoute, metav1.CreateOptions{}) if err != nil { return nil, err } @@ -116,12 +113,8 @@ func (s *Solver) checkAndUpdateGatewayHTTPRoute(ctx context.Context, ch *cmacme. expectedSpec := generateHTTPRouteSpec(ch, svcName) actualSpec := httpRoute.Spec expectedLabels := podLabels(ch) - if ch.Spec.Solver.HTTP01.GatewayHTTPRoute.Labels != nil { - for k, v := range ch.Spec.Solver.HTTP01.GatewayHTTPRoute.Labels { - expectedLabels[k] = v - } - } - actualLabels := ch.Labels + maps.Copy(expectedLabels, ch.Spec.Solver.HTTP01.GatewayHTTPRoute.Labels) + actualLabels := httpRoute.Labels if reflect.DeepEqual(expectedSpec, actualSpec) && reflect.DeepEqual(expectedLabels, actualLabels) { return httpRoute, nil } @@ -129,14 +122,14 @@ func (s *Solver) checkAndUpdateGatewayHTTPRoute(ctx context.Context, ch *cmacme. var ret *gwapi.HTTPRoute var err error if err = retry.RetryOnConflict(retry.DefaultBackoff, func() error { - oldHTTPRoute, err := s.GWClient.GatewayV1alpha2().HTTPRoutes(httpRoute.Namespace).Get(ctx, httpRoute.Name, metav1.GetOptions{}) + oldHTTPRoute, err := s.GWClient.GatewayV1().HTTPRoutes(httpRoute.Namespace).Get(ctx, httpRoute.Name, metav1.GetOptions{}) if err != nil { return err } newHTTPRoute := oldHTTPRoute.DeepCopy() newHTTPRoute.Spec = expectedSpec newHTTPRoute.Labels = expectedLabels - ret, err = s.GWClient.GatewayV1alpha2().HTTPRoutes(newHTTPRoute.Namespace).Update(ctx, newHTTPRoute, metav1.UpdateOptions{}) + ret, err = s.GWClient.GatewayV1().HTTPRoutes(newHTTPRoute.Namespace).Update(ctx, newHTTPRoute, metav1.UpdateOptions{}) if err != nil { return err } @@ -161,7 +154,7 @@ func generateHTTPRouteSpec(ch *cmacme.Challenge, svcName string) gwapi.HTTPRoute { Path: &gwapi.HTTPPathMatch{ Type: func() *gwapi.PathMatchType { p := gwapi.PathMatchExact; return &p }(), - Value: pointer.String(fmt.Sprintf("/.well-known/acme-challenge/%s", ch.Spec.Token)), + Value: ptr.To(fmt.Sprintf("/.well-known/acme-challenge/%s", ch.Spec.Token)), }, }, }, @@ -169,12 +162,13 @@ func generateHTTPRouteSpec(ch *cmacme.Challenge, svcName string) gwapi.HTTPRoute { BackendRef: gwapi.BackendRef{ BackendObjectReference: gwapi.BackendObjectReference{ + Group: func() *gwapi.Group { g := gwapi.Group(""); return &g }(), Kind: func() *gwapi.Kind { k := gwapi.Kind("Service"); return &k }(), Name: gwapi.ObjectName(svcName), Namespace: func() *gwapi.Namespace { n := gwapi.Namespace(ch.Namespace); return &n }(), Port: func() *gwapi.PortNumber { p := gwapi.PortNumber(acmeSolverListenPort); return &p }(), }, - Weight: pointer.Int32(1), + Weight: ptr.To(int32(1)), }, }, }, @@ -182,8 +176,3 @@ func generateHTTPRouteSpec(ch *cmacme.Challenge, svcName string) gwapi.HTTPRoute }, } } - -func (s *Solver) cleanupGatewayHTTPRoutes(_ context.Context, _ *cmacme.Challenge) error { - // Unlike Ingress, we don't modify existing HTTPRoutes so there is nothing to do here. - return nil -} diff --git a/pkg/issuer/acme/http/httproute_test.go b/pkg/issuer/acme/http/httproute_test.go new file mode 100644 index 00000000000..f50d4923ba0 --- /dev/null +++ b/pkg/issuer/acme/http/httproute_test.go @@ -0,0 +1,262 @@ +/* +Copyright 2020 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. +*/ + +package http + +import ( + "reflect" + "testing" + + "k8s.io/apimachinery/pkg/labels" + "k8s.io/utils/diff" + gwapi "sigs.k8s.io/gateway-api/apis/v1" + + cmacme "github.com/cert-manager/cert-manager/pkg/apis/acme/v1" +) + +func TestGetGatewayHTTPRouteForChallenge(t *testing.T) { + const createdHTTPRouteKey = "createdHTTPRoute" + tests := map[string]solverFixture{ + "should return one httproute that matches": { + Challenge: &cmacme.Challenge{ + Spec: cmacme.ChallengeSpec{ + DNSName: "example.com", + Solver: cmacme.ACMEChallengeSolver{ + HTTP01: &cmacme.ACMEChallengeSolverHTTP01{ + GatewayHTTPRoute: &cmacme.ACMEChallengeSolverHTTP01GatewayHTTPRoute{}, + }, + }, + }, + }, + PreFn: func(t *testing.T, s *solverFixture) { + httpRoute, err := s.Solver.createGatewayHTTPRoute(t.Context(), s.Challenge, "fakeservice") + if err != nil { + t.Errorf("error preparing test: %v", err) + } + + s.testResources[createdHTTPRouteKey] = httpRoute + s.Builder.Sync() + }, + CheckFn: func(t *testing.T, s *solverFixture, args ...interface{}) { + createdHTTPRoute := s.testResources[createdHTTPRouteKey].(*gwapi.HTTPRoute) + gotHttpRoute := args[0].(*gwapi.HTTPRoute) + if !reflect.DeepEqual(gotHttpRoute, createdHTTPRoute) { + t.Errorf("Expected %v to equal %v", gotHttpRoute, createdHTTPRoute) + } + }, + }, + "should return one httproute for IP that matches": { + Challenge: &cmacme.Challenge{ + Spec: cmacme.ChallengeSpec{ + DNSName: "10.0.0.1", + Solver: cmacme.ACMEChallengeSolver{ + HTTP01: &cmacme.ACMEChallengeSolverHTTP01{ + GatewayHTTPRoute: &cmacme.ACMEChallengeSolverHTTP01GatewayHTTPRoute{}, + }, + }, + }, + }, + PreFn: func(t *testing.T, s *solverFixture) { + httpRoute, err := s.Solver.createGatewayHTTPRoute(t.Context(), s.Challenge, "fakeservice") + if err != nil { + t.Errorf("error preparing test: %v", err) + } + + s.testResources[createdHTTPRouteKey] = httpRoute + s.Builder.Sync() + }, + CheckFn: func(t *testing.T, s *solverFixture, args ...interface{}) { + createdHTTPRoute := s.testResources[createdHTTPRouteKey].(*gwapi.HTTPRoute) + gotHttpRoute := args[0].(*gwapi.HTTPRoute) + if !reflect.DeepEqual(gotHttpRoute, createdHTTPRoute) { + t.Errorf("Expected %v to equal %v", gotHttpRoute, createdHTTPRoute) + } + }, + }, + "should not return an httproute for the same certificate but different domain": { + Challenge: &cmacme.Challenge{ + Spec: cmacme.ChallengeSpec{ + DNSName: "example.com", + Solver: cmacme.ACMEChallengeSolver{ + HTTP01: &cmacme.ACMEChallengeSolverHTTP01{ + GatewayHTTPRoute: &cmacme.ACMEChallengeSolverHTTP01GatewayHTTPRoute{}, + }, + }, + }, + }, + PreFn: func(t *testing.T, s *solverFixture) { + differentChallenge := s.Challenge.DeepCopy() + differentChallenge.Spec.DNSName = "notexample.com" + _, err := s.Solver.createGatewayHTTPRoute(t.Context(), differentChallenge, "fakeservice") + if err != nil { + t.Errorf("error preparing test: %v", err) + } + + s.Builder.Sync() + }, + CheckFn: func(t *testing.T, s *solverFixture, args ...interface{}) { + gotHttpRoute := args[0].(*gwapi.HTTPRoute) + if gotHttpRoute != nil { + t.Errorf("Expected function to not return an HTTPRoute, but got: %v", gotHttpRoute) + } + }, + }, + "should clean-up if there are multiple httproute resources": { + Challenge: &cmacme.Challenge{ + Spec: cmacme.ChallengeSpec{ + DNSName: "example.com", + Solver: cmacme.ACMEChallengeSolver{ + HTTP01: &cmacme.ACMEChallengeSolverHTTP01{ + GatewayHTTPRoute: &cmacme.ACMEChallengeSolverHTTP01GatewayHTTPRoute{}, + }, + }, + }, + }, + Err: true, + PreFn: func(t *testing.T, s *solverFixture) { + _, err := s.Solver.createGatewayHTTPRoute(t.Context(), s.Challenge, "fakeservice") + if err != nil { + t.Errorf("error preparing test: %v", err) + } + + _, err = s.Solver.createGatewayHTTPRoute(t.Context(), s.Challenge, "fakeservice") + if err != nil { + t.Errorf("error preparing test: %v", err) + } + + s.Builder.Sync() + }, + CheckFn: func(t *testing.T, s *solverFixture, args ...interface{}) { + httpRoutes, err := s.Solver.httpRouteLister.List(labels.NewSelector()) + if err != nil { + t.Errorf("error listing HTTPRoutes: %v", err) + t.Fail() + return + } + if len(httpRoutes) != 1 { + t.Errorf("Expected 1 HTTPRoute, but got: %v", len(httpRoutes)) + } + }, + }, + } + for name, test := range tests { + t.Run(name, func(t *testing.T) { + test.Setup(t) + resp, err := test.Solver.getGatewayHTTPRoute(t.Context(), test.Challenge) + if err != nil && !test.Err { + t.Errorf("Expected function to not error, but got: %v", err) + } + if err == nil && test.Err { + t.Errorf("Expected function to get an error, but got: %v", err) + } + test.Finish(t, resp, err) + }) + } +} + +func TestEnsureGatewayHTTPRoute(t *testing.T) { + tests := map[string]solverFixture{ + "should not create another httproute if one exists": { + Challenge: &cmacme.Challenge{ + Spec: cmacme.ChallengeSpec{ + DNSName: "example.com", + Solver: cmacme.ACMEChallengeSolver{ + HTTP01: &cmacme.ACMEChallengeSolverHTTP01{ + GatewayHTTPRoute: &cmacme.ACMEChallengeSolverHTTP01GatewayHTTPRoute{}, + }, + }, + }, + }, + PreFn: func(t *testing.T, s *solverFixture) { + _, err := s.Solver.createGatewayHTTPRoute(t.Context(), s.Challenge, "fakeservice") + if err != nil { + t.Errorf("error preparing test: %v", err) + } + s.Builder.Sync() + }, + CheckFn: func(t *testing.T, s *solverFixture, args ...interface{}) { + httpRoutes, err := s.Solver.httpRouteLister.List(labels.NewSelector()) + if err != nil { + t.Errorf("error listing HTTPRoutes: %v", err) + t.Fail() + return + } + + if len(httpRoutes) != 1 { + t.Errorf("Expected 1 HTTPRoute, but got: %v", len(httpRoutes)) + } + + gotHTTPRouteSpec := httpRoutes[0].Spec + expectedHTTPRoute := generateHTTPRouteSpec(s.Challenge, "fakeservice") + if !reflect.DeepEqual(gotHTTPRouteSpec, expectedHTTPRoute) { + t.Errorf("Expected HTTPRoute specs to match, but got diff:\n%v", + diff.ObjectDiff(gotHTTPRouteSpec, expectedHTTPRoute)) + } + }, + }, + "should update challenge httproute if service changes": { + Challenge: &cmacme.Challenge{ + Spec: cmacme.ChallengeSpec{ + DNSName: "example.com", + Solver: cmacme.ACMEChallengeSolver{ + HTTP01: &cmacme.ACMEChallengeSolverHTTP01{ + GatewayHTTPRoute: &cmacme.ACMEChallengeSolverHTTP01GatewayHTTPRoute{}, + }, + }, + }, + }, + PreFn: func(t *testing.T, s *solverFixture) { + _, err := s.Solver.createGatewayHTTPRoute(t.Context(), s.Challenge, "anotherfakeservice") + if err != nil { + t.Errorf("error preparing test: %v", err) + } + s.Builder.Sync() + }, + CheckFn: func(t *testing.T, s *solverFixture, args ...interface{}) { + httpRoutes, err := s.Solver.httpRouteLister.List(labels.NewSelector()) + if err != nil { + t.Errorf("error listing HTTPRoutes: %v", err) + t.Fail() + return + } + + if len(httpRoutes) != 1 { + t.Errorf("Expected 1 HTTPRoute, but got: %v", len(httpRoutes)) + } + + gotHTTPRouteSpec := httpRoutes[0].Spec + expectedHTTPRoute := generateHTTPRouteSpec(s.Challenge, "fakeservice") + if !reflect.DeepEqual(gotHTTPRouteSpec, expectedHTTPRoute) { + t.Errorf("Expected HTTPRoute specs to match, but got diff:\n%v", + diff.ObjectDiff(gotHTTPRouteSpec, expectedHTTPRoute)) + } + }, + }, + } + for name, test := range tests { + t.Run(name, func(t *testing.T) { + test.Setup(t) + resp, err := test.Solver.ensureGatewayHTTPRoute(t.Context(), test.Challenge, "fakeservice") + if err != nil && !test.Err { + t.Errorf("Expected function to not error, but got: %v", err) + } + if err == nil && test.Err { + t.Errorf("Expected function to get an error, but got: %v", err) + } + test.Finish(t, resp, err) + }) + } +} diff --git a/pkg/issuer/acme/http/ingress.go b/pkg/issuer/acme/http/ingress.go index 987e73049a5..c59f580a82a 100644 --- a/pkg/issuer/acme/http/ingress.go +++ b/pkg/issuer/acme/http/ingress.go @@ -19,6 +19,7 @@ package http import ( "context" "fmt" + "maps" "net" "strings" @@ -29,9 +30,11 @@ import ( "k8s.io/apimachinery/pkg/selection" utilerrors "k8s.io/apimachinery/pkg/util/errors" + "github.com/cert-manager/cert-manager/internal/controller/feature" cmacme "github.com/cert-manager/cert-manager/pkg/apis/acme/v1" "github.com/cert-manager/cert-manager/pkg/issuer/acme/http/solver" logf "github.com/cert-manager/cert-manager/pkg/logs" + utilfeature "github.com/cert-manager/cert-manager/pkg/util/feature" ) const ( @@ -154,14 +157,19 @@ func buildIngressResource(ch *cmacme.Challenge, svcName string) (*networkingv1.I // TODO: Figure out how to remove this without breaking users who depend on it. ingAnnotations["nginx.ingress.kubernetes.io/whitelist-source-range"] = "0.0.0.0/0,::/0" - // Use the Ingress Class annotation defined in networkingv1beta1 even though our Ingress objects - // are networkingv1, for maximum compatibility with all Ingress controllers. - // if the `kubernetes.io/ingress.class` annotation is present, it takes precedence over the - // `spec.IngressClassName` field. - // See discussion in https://github.com/cert-manager/cert-manager/issues/4537. + // The Kubernetes API won't allow both having the annotation and the field + // set. + if http01IngressCfg.Class != nil && http01IngressCfg.IngressClassName != nil { + return nil, fmt.Errorf("the fields ingressClassName and class cannot be set at the same time") + } + + var ingressClassName *string if http01IngressCfg.Class != nil { ingAnnotations[annotationIngressClass] = *http01IngressCfg.Class } + if http01IngressCfg.IngressClassName != nil { + ingressClassName = http01IngressCfg.IngressClassName + } ingPathToAdd := ingressPath(ch.Spec.Token, svcName) @@ -179,8 +187,7 @@ func buildIngressResource(ch *cmacme.Challenge, svcName string) (*networkingv1.I OwnerReferences: []metav1.OwnerReference{*metav1.NewControllerRef(ch, challengeGvk)}, }, Spec: networkingv1.IngressSpec{ - // https://github.com/cert-manager/cert-manager/issues/4537 - IngressClassName: nil, + IngressClassName: ingressClassName, Rules: []networkingv1.IngressRule{ { Host: httpHost, @@ -207,9 +214,7 @@ func (s *Solver) mergeIngressObjectMetaWithIngressResourceTemplate(ingress *netw ingress.Labels = make(map[string]string) } - for k, v := range ingressTempl.Labels { - ingress.Labels[k] = v - } + maps.Copy(ingress.Labels, ingressTempl.Labels) if ingress.Annotations == nil { ingress.Annotations = make(map[string]string) @@ -373,8 +378,16 @@ func (s *Solver) cleanupIngresses(ctx context.Context, ch *cmacme.Challenge) err // challenge. func ingressPath(token, serviceName string) networkingv1.HTTPIngressPath { return networkingv1.HTTPIngressPath{ - Path: solverPathFn(token), - PathType: func() *networkingv1.PathType { s := networkingv1.PathTypeImplementationSpecific; return &s }(), + Path: solverPathFn(token), + PathType: func() *networkingv1.PathType { + var s networkingv1.PathType + if utilfeature.DefaultFeatureGate.Enabled(feature.ACMEHTTP01IngressPathTypeExact) { + s = networkingv1.PathTypeExact + } else { + s = networkingv1.PathTypeImplementationSpecific + } + return &s + }(), Backend: networkingv1.IngressBackend{ Service: &networkingv1.IngressServiceBackend{ Name: serviceName, diff --git a/pkg/issuer/acme/http/ingress_test.go b/pkg/issuer/acme/http/ingress_test.go index 4de41125e80..28878e19de8 100644 --- a/pkg/issuer/acme/http/ingress_test.go +++ b/pkg/issuer/acme/http/ingress_test.go @@ -17,21 +17,25 @@ limitations under the License. package http import ( - "context" "fmt" "reflect" "testing" + "github.com/google/go-cmp/cmp" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" networkingv1 "k8s.io/api/networking/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/util/diff" coretesting "k8s.io/client-go/testing" + featuregatetesting "k8s.io/component-base/featuregate/testing" + internalfeature "github.com/cert-manager/cert-manager/internal/controller/feature" cmacme "github.com/cert-manager/cert-manager/pkg/apis/acme/v1" "github.com/cert-manager/cert-manager/pkg/controller/test" + "github.com/cert-manager/cert-manager/pkg/util/feature" ) func TestGetIngressesForChallenge(t *testing.T) { @@ -49,7 +53,7 @@ func TestGetIngressesForChallenge(t *testing.T) { }, }, PreFn: func(t *testing.T, s *solverFixture) { - ing, err := s.Solver.createIngress(context.TODO(), s.Challenge, "fakeservice") + ing, err := s.Solver.createIngress(t.Context(), s.Challenge, "fakeservice") if err != nil { t.Errorf("error preparing test: %v", err) } @@ -70,6 +74,80 @@ func TestGetIngressesForChallenge(t *testing.T) { } }, }, + "should return one ingress with pathType Exact": { + Challenge: &cmacme.Challenge{ + Spec: cmacme.ChallengeSpec{ + DNSName: "example.com", + Solver: cmacme.ACMEChallengeSolver{ + HTTP01: &cmacme.ACMEChallengeSolverHTTP01{ + Ingress: &cmacme.ACMEChallengeSolverHTTP01Ingress{}, + }, + }, + }, + }, + PreFn: func(t *testing.T, s *solverFixture) { + featuregatetesting.SetFeatureGateDuringTest(t, feature.DefaultFeatureGate, internalfeature.ACMEHTTP01IngressPathTypeExact, true) + ing, err := s.Solver.createIngress(t.Context(), s.Challenge, "fakeservice") + if err != nil { + t.Errorf("error preparing test: %v", err) + } + + s.testResources[createdIngressKey] = ing + s.Builder.Sync() + }, + CheckFn: func(t *testing.T, s *solverFixture, args ...interface{}) { + createdIngress := s.testResources[createdIngressKey].(*networkingv1.Ingress) + resp := args[0].([]*networkingv1.Ingress) + if len(resp) != 1 { + t.Errorf("expected one ingress to be returned, but got %d", len(resp)) + t.Fail() + return + } + if !reflect.DeepEqual(resp[0], createdIngress) { + t.Errorf("Expected %v to equal %v", resp[0], createdIngress) + } + if *resp[0].Spec.Rules[0].HTTP.Paths[0].PathType != networkingv1.PathTypeExact { + t.Errorf("Expected pathType to be Exact, but got %s", *resp[0].Spec.Rules[0].HTTP.Paths[0].PathType) + } + }, + }, + "should return one ingress with pathType ImplementationSpecific": { + Challenge: &cmacme.Challenge{ + Spec: cmacme.ChallengeSpec{ + DNSName: "example.com", + Solver: cmacme.ACMEChallengeSolver{ + HTTP01: &cmacme.ACMEChallengeSolverHTTP01{ + Ingress: &cmacme.ACMEChallengeSolverHTTP01Ingress{}, + }, + }, + }, + }, + PreFn: func(t *testing.T, s *solverFixture) { + featuregatetesting.SetFeatureGateDuringTest(t, feature.DefaultFeatureGate, internalfeature.ACMEHTTP01IngressPathTypeExact, false) + ing, err := s.Solver.createIngress(t.Context(), s.Challenge, "fakeservice") + if err != nil { + t.Errorf("error preparing test: %v", err) + } + + s.testResources[createdIngressKey] = ing + s.Builder.Sync() + }, + CheckFn: func(t *testing.T, s *solverFixture, args ...interface{}) { + createdIngress := s.testResources[createdIngressKey].(*networkingv1.Ingress) + resp := args[0].([]*networkingv1.Ingress) + if len(resp) != 1 { + t.Errorf("expected one ingress to be returned, but got %d", len(resp)) + t.Fail() + return + } + if !reflect.DeepEqual(resp[0], createdIngress) { + t.Errorf("Expected %v to equal %v", resp[0], createdIngress) + } + if *resp[0].Spec.Rules[0].HTTP.Paths[0].PathType != networkingv1.PathTypeImplementationSpecific { + t.Errorf("Expected pathType to be ImplementationSpecific, but got %s", *resp[0].Spec.Rules[0].HTTP.Paths[0].PathType) + } + }, + }, "should return one ingress for IP that matches": { Challenge: &cmacme.Challenge{ Spec: cmacme.ChallengeSpec{ @@ -82,7 +160,7 @@ func TestGetIngressesForChallenge(t *testing.T) { }, }, PreFn: func(t *testing.T, s *solverFixture) { - ing, err := s.Solver.createIngress(context.TODO(), s.Challenge, "fakeservice") + ing, err := s.Solver.createIngress(t.Context(), s.Challenge, "fakeservice") if err != nil { t.Errorf("error preparing test: %v", err) } @@ -117,7 +195,7 @@ func TestGetIngressesForChallenge(t *testing.T) { PreFn: func(t *testing.T, s *solverFixture) { differentChallenge := s.Challenge.DeepCopy() differentChallenge.Spec.DNSName = "notexample.com" - _, err := s.Solver.createIngress(context.TODO(), differentChallenge, "fakeservice") + _, err := s.Solver.createIngress(t.Context(), differentChallenge, "fakeservice") if err != nil { t.Errorf("error preparing test: %v", err) } @@ -137,7 +215,7 @@ func TestGetIngressesForChallenge(t *testing.T) { for name, test := range tests { t.Run(name, func(t *testing.T) { test.Setup(t) - resp, err := test.Solver.getIngressesForChallenge(context.TODO(), test.Challenge) + resp, err := test.Solver.getIngressesForChallenge(t.Context(), test.Challenge) if err != nil && !test.Err { t.Errorf("Expected function to not error, but got: %v", err) } @@ -167,7 +245,7 @@ func TestCleanupIngresses(t *testing.T) { }, }, PreFn: func(t *testing.T, s *solverFixture) { - ing, err := s.Solver.createIngress(context.TODO(), s.Challenge, "fakeservice") + ing, err := s.Solver.createIngress(t.Context(), s.Challenge, "fakeservice") if err != nil { t.Errorf("error preparing test: %v", err) } @@ -176,7 +254,7 @@ func TestCleanupIngresses(t *testing.T) { }, CheckFn: func(t *testing.T, s *solverFixture, args ...interface{}) { createdIngress := s.testResources[createdIngressKey].(*networkingv1.Ingress) - ing, err := s.Builder.FakeKubeClient().NetworkingV1().Ingresses(s.Challenge.Namespace).Get(context.TODO(), createdIngress.Name, metav1.GetOptions{}) + ing, err := s.Builder.FakeKubeClient().NetworkingV1().Ingresses(s.Challenge.Namespace).Get(t.Context(), createdIngress.Name, metav1.GetOptions{}) if err != nil && !apierrors.IsNotFound(err) { t.Errorf("error when getting test ingress, expected 'not found' but got: %v", err) } @@ -202,7 +280,7 @@ func TestCleanupIngresses(t *testing.T) { PreFn: func(t *testing.T, s *solverFixture) { differentChallenge := s.Challenge.DeepCopy() differentChallenge.Spec.DNSName = "notexample.com" - ing, err := s.Solver.createIngress(context.TODO(), differentChallenge, "fakeservice") + ing, err := s.Solver.createIngress(t.Context(), differentChallenge, "fakeservice") if err != nil { t.Errorf("error preparing test: %v", err) } @@ -210,7 +288,7 @@ func TestCleanupIngresses(t *testing.T) { }, CheckFn: func(t *testing.T, s *solverFixture, args ...interface{}) { createdIngress := s.testResources[createdIngressKey].(*networkingv1.Ingress) - _, err := s.Builder.FakeKubeClient().NetworkingV1().Ingresses(s.Challenge.Namespace).Get(context.TODO(), createdIngress.Name, metav1.GetOptions{}) + _, err := s.Builder.FakeKubeClient().NetworkingV1().Ingresses(s.Challenge.Namespace).Get(t.Context(), createdIngress.Name, metav1.GetOptions{}) if apierrors.IsNotFound(err) { t.Errorf("expected ingress resource %q to not be deleted, but it was deleted", createdIngress.Name) } @@ -285,7 +363,7 @@ func TestCleanupIngresses(t *testing.T) { expectedIng := s.KubeObjects[0].(*networkingv1.Ingress).DeepCopy() expectedIng.Spec.Rules = nil - actualIng, err := s.Builder.FakeKubeClient().NetworkingV1().Ingresses(s.Challenge.Namespace).Get(context.TODO(), expectedIng.Name, metav1.GetOptions{}) + actualIng, err := s.Builder.FakeKubeClient().NetworkingV1().Ingresses(s.Challenge.Namespace).Get(t.Context(), expectedIng.Name, metav1.GetOptions{}) if apierrors.IsNotFound(err) { t.Errorf("expected ingress resource %q to not be deleted, but it was deleted", expectedIng.Name) } @@ -293,8 +371,8 @@ func TestCleanupIngresses(t *testing.T) { t.Errorf("error getting ingress resource: %v", err) } - if !reflect.DeepEqual(expectedIng, actualIng) { - t.Errorf("expected did not match actual: %v", diff.ObjectDiff(expectedIng, actualIng)) + if diff := cmp.Diff(expectedIng, actualIng); diff != "" { + t.Errorf("expected did not match actual (-want +got):\n%s", diff) } }, }, @@ -384,7 +462,7 @@ func TestCleanupIngresses(t *testing.T) { expectedIng := s.KubeObjects[0].(*networkingv1.Ingress).DeepCopy() expectedIng.Spec.Rules = []networkingv1.IngressRule{expectedIng.Spec.Rules[1]} - actualIng, err := s.Builder.FakeKubeClient().NetworkingV1().Ingresses(s.Challenge.Namespace).Get(context.TODO(), expectedIng.Name, metav1.GetOptions{}) + actualIng, err := s.Builder.FakeKubeClient().NetworkingV1().Ingresses(s.Challenge.Namespace).Get(t.Context(), expectedIng.Name, metav1.GetOptions{}) if apierrors.IsNotFound(err) { t.Errorf("expected ingress resource %q to not be deleted, but it was deleted", expectedIng.Name) } @@ -392,8 +470,10 @@ func TestCleanupIngresses(t *testing.T) { t.Errorf("error getting ingress resource: %v", err) } - if !reflect.DeepEqual(expectedIng, actualIng) { - t.Errorf("expected did not match actual: %v", diff.ObjectDiff(expectedIng, actualIng)) + expectedIng.ManagedFields = actualIng.ManagedFields + + if diff := cmp.Diff(expectedIng, actualIng); diff != "" { + t.Errorf("expected did not match actual (-want +got):\n%s", diff) } }, }, @@ -416,7 +496,7 @@ func TestCleanupIngresses(t *testing.T) { s.Builder.FakeKubeClient().PrependReactor("delete", "ingresses", func(action coretesting.Action) (handled bool, ret runtime.Object, err error) { return true, nil, fmt.Errorf("simulated error") }) - ing, err := s.Solver.createIngress(context.TODO(), s.Challenge, "fakeservice") + ing, err := s.Solver.createIngress(t.Context(), s.Challenge, "fakeservice") if err != nil { t.Errorf("error preparing test: %v", err) } @@ -428,7 +508,7 @@ func TestCleanupIngresses(t *testing.T) { for name, test := range tests { t.Run(name, func(t *testing.T) { test.Setup(t) - err := test.Solver.cleanupIngresses(context.TODO(), test.Challenge) + err := test.Solver.cleanupIngresses(t.Context(), test.Challenge) if err != nil && !test.Err { t.Errorf("Expected function to not error, but got: %v", err) } @@ -455,7 +535,7 @@ func TestEnsureIngress(t *testing.T) { }, Err: true, PreFn: func(t *testing.T, s *solverFixture) { - _, err := s.Solver.createIngress(context.TODO(), s.Challenge, "anotherfakeservice") + _, err := s.Solver.createIngress(t.Context(), s.Challenge, "anotherfakeservice") if err != nil { t.Errorf("error preparing test: %v", err) } @@ -473,11 +553,33 @@ func TestEnsureIngress(t *testing.T) { } }, }, + "class field is passed to ingress as the annotation kubernetes.io/ingress.class": { + Challenge: &cmacme.Challenge{Spec: cmacme.ChallengeSpec{Solver: cmacme.ACMEChallengeSolver{HTTP01: &cmacme.ACMEChallengeSolverHTTP01{ + Ingress: &cmacme.ACMEChallengeSolverHTTP01Ingress{ + Class: strPtr("nginx"), + }}}}, + }, + CheckFn: checkOneIngress(func(t *testing.T, ingress *networkingv1.Ingress) { + assert.Equal(t, "nginx", ingress.Annotations["kubernetes.io/ingress.class"]) + assert.Empty(t, ingress.Spec.IngressClassName) + }), + }, + "ingressClassName field is passed to the ingress": { + Challenge: &cmacme.Challenge{Spec: cmacme.ChallengeSpec{Solver: cmacme.ACMEChallengeSolver{HTTP01: &cmacme.ACMEChallengeSolverHTTP01{ + Ingress: &cmacme.ACMEChallengeSolverHTTP01Ingress{ + IngressClassName: strPtr("nginx"), + }}}}, + }, + CheckFn: checkOneIngress(func(t *testing.T, ingress *networkingv1.Ingress) { + assert.Empty(t, ingress.Annotations["kubernetes.io/ingress.class"]) + assert.Equal(t, strPtr("nginx"), ingress.Spec.IngressClassName) + }), + }, } for name, test := range tests { t.Run(name, func(t *testing.T) { test.Setup(t) - resp, err := test.Solver.ensureIngress(context.TODO(), test.Challenge, "fakeservice") + resp, err := test.Solver.ensureIngress(t.Context(), test.Challenge, "fakeservice") if err != nil && !test.Err { t.Errorf("Expected function to not error, but got: %v", err) } @@ -489,6 +591,15 @@ func TestEnsureIngress(t *testing.T) { } } +func checkOneIngress(check func(*testing.T, *networkingv1.Ingress)) func(*testing.T, *solverFixture, ...interface{}) { + return func(t *testing.T, s *solverFixture, _ ...interface{}) { + ingresses, err := s.Solver.ingressLister.List(labels.NewSelector()) + assert.NoError(t, err) + require.Len(t, ingresses, 1) + check(t, ingresses[0]) + } +} + func TestMergeIngressObjectMetaWithIngressResourceTemplate(t *testing.T) { const createdIngressKey = "createdIngressKey" tests := map[string]solverFixture{ @@ -548,11 +659,14 @@ func TestMergeIngressObjectMetaWithIngressResourceTemplate(t *testing.T) { return } + expectedIngress.APIVersion = resp.APIVersion + expectedIngress.Kind = resp.Kind expectedIngress.OwnerReferences = resp.OwnerReferences + expectedIngress.ManagedFields = resp.ManagedFields expectedIngress.Name = resp.Name - if !reflect.DeepEqual(resp, expectedIngress) { - t.Errorf("unexpected ingress generated from merge\nexp=%+v\ngot=%+v", expectedIngress, resp) + if diff := cmp.Diff(expectedIngress, resp); diff != "" { + t.Errorf("unexpected ingress generated from merge (-want +got):\n%s", diff) } }, }, @@ -561,7 +675,7 @@ func TestMergeIngressObjectMetaWithIngressResourceTemplate(t *testing.T) { for name, test := range tests { t.Run(name, func(t *testing.T) { test.Setup(t) - resp, err := test.Solver.createIngress(context.TODO(), test.Challenge, "fakeservice") + resp, err := test.Solver.createIngress(t.Context(), test.Challenge, "fakeservice") test.Finish(t, resp, err) }) } @@ -626,11 +740,14 @@ func TestOverrideNginxIngressWhitelistAnnotation(t *testing.T) { return } + expectedIngress.APIVersion = resp.APIVersion + expectedIngress.Kind = resp.Kind expectedIngress.OwnerReferences = resp.OwnerReferences + expectedIngress.ManagedFields = resp.ManagedFields expectedIngress.Name = resp.Name - if !reflect.DeepEqual(resp, expectedIngress) { - t.Errorf("unexpected ingress generated from merge\nexp=%+v\ngot=%+v", expectedIngress, resp) + if diff := cmp.Diff(expectedIngress, resp); diff != "" { + t.Errorf("unexpected ingress generated from merge (-want +got):\n%s", diff) } }, }, @@ -639,7 +756,7 @@ func TestOverrideNginxIngressWhitelistAnnotation(t *testing.T) { for name, test := range tests { t.Run(name, func(t *testing.T) { test.Setup(t) - resp, err := test.Solver.createIngress(context.TODO(), test.Challenge, "fakeservice") + resp, err := test.Solver.createIngress(t.Context(), test.Challenge, "fakeservice") test.Finish(t, resp, err) }) } diff --git a/pkg/issuer/acme/http/pod.go b/pkg/issuer/acme/http/pod.go index 77f2efd3e5b..d38414cd660 100644 --- a/pkg/issuer/acme/http/pod.go +++ b/pkg/issuer/acme/http/pod.go @@ -20,13 +20,14 @@ import ( "context" "fmt" "hash/adler32" + "maps" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/selection" utilerrors "k8s.io/apimachinery/pkg/util/errors" - "k8s.io/utils/pointer" + "k8s.io/utils/ptr" cmacme "github.com/cert-manager/cert-manager/pkg/apis/acme/v1" logf "github.com/cert-manager/cert-manager/pkg/logs" @@ -47,35 +48,36 @@ func podLabels(ch *cmacme.Challenge) map[string]string { } } -func (s *Solver) ensurePod(ctx context.Context, ch *cmacme.Challenge) (*corev1.Pod, error) { +func (s *Solver) ensurePod(ctx context.Context, ch *cmacme.Challenge) error { log := logf.FromContext(ctx).WithName("ensurePod") log.V(logf.DebugLevel).Info("checking for existing HTTP01 solver pods") existingPods, err := s.getPodsForChallenge(ctx, ch) if err != nil { - return nil, err + return err } if len(existingPods) == 1 { logf.WithRelatedResource(log, existingPods[0]).Info("found one existing HTTP01 solver pod") - return existingPods[0], nil + return nil } if len(existingPods) > 1 { log.V(logf.InfoLevel).Info("multiple challenge solver pods found for challenge. cleaning up all existing pods.") err := s.cleanupPods(ctx, ch) if err != nil { - return nil, err + return err } - return nil, fmt.Errorf("multiple existing challenge solver pods found and cleaned up. retrying challenge sync") + return fmt.Errorf("multiple existing challenge solver pods found and cleaned up. retrying challenge sync") } log.V(logf.InfoLevel).Info("creating HTTP01 challenge solver pod") - return s.createPod(ctx, ch) + _, err = s.createPod(ctx, ch) + return err } // getPodsForChallenge returns a list of pods that were created to solve // the given challenge -func (s *Solver) getPodsForChallenge(ctx context.Context, ch *cmacme.Challenge) ([]*corev1.Pod, error) { +func (s *Solver) getPodsForChallenge(ctx context.Context, ch *cmacme.Challenge) ([]*metav1.PartialObjectMetadata, error) { log := logf.FromContext(ctx) podLabels := podLabels(ch) @@ -88,19 +90,23 @@ func (s *Solver) getPodsForChallenge(ctx context.Context, ch *cmacme.Challenge) orderSelector = orderSelector.Add(*req) } - podList, err := s.podLister.Pods(ch.Namespace).List(orderSelector) + podMetadataList, err := s.podLister.ByNamespace(ch.Namespace).List(orderSelector) if err != nil { return nil, err } - var relevantPods []*corev1.Pod - for _, pod := range podList { - if !metav1.IsControlledBy(pod, ch) { - logf.WithRelatedResource(log, pod).Info("found existing solver pod for this challenge resource, however " + + var relevantPods []*metav1.PartialObjectMetadata + for _, pod := range podMetadataList { + p, ok := pod.(*metav1.PartialObjectMetadata) + if !ok { + return nil, fmt.Errorf("internal error: cannot cast PartialMetadata: %+#v", pod) + } + if !metav1.IsControlledBy(p, ch) { + logf.WithRelatedResource(log, p).Info("found existing solver pod for this challenge resource, however " + "it does not have an appropriate OwnerReference referencing this challenge. Skipping it altogether.") continue } - relevantPods = append(relevantPods, pod) + relevantPods = append(relevantPods, p) } return relevantPods, nil @@ -111,7 +117,7 @@ func (s *Solver) cleanupPods(ctx context.Context, ch *cmacme.Challenge) error { pods, err := s.getPodsForChallenge(ctx, ch) if err != nil { - return err + return fmt.Errorf("error retrieving pods for cleanup: %w", err) } var errs []error for _, pod := range pods { @@ -121,7 +127,7 @@ func (s *Solver) cleanupPods(ctx context.Context, ch *cmacme.Challenge) error { err := s.Client.CoreV1().Pods(pod.Namespace).Delete(ctx, pod.Name, metav1.DeleteOptions{}) if err != nil { log.V(logf.WarnLevel).Info("failed to delete pod resource", "error", err) - errs = append(errs, err) + errs = append(errs, fmt.Errorf("error deleting pod: %w", err)) continue } log.V(logf.InfoLevel).Info("successfully deleted pod resource") @@ -150,6 +156,10 @@ func (s *Solver) buildPod(ch *cmacme.Challenge) *corev1.Pod { pod = s.mergePodObjectMetaWithPodTemplate(pod, ch.Spec.Solver.HTTP01.Ingress.PodTemplate) } + if ch.Spec.Solver.HTTP01.GatewayHTTPRoute != nil { + pod = s.mergePodObjectMetaWithPodTemplate(pod, + ch.Spec.Solver.HTTP01.GatewayHTTPRoute.PodTemplate) + } } return pod @@ -158,7 +168,7 @@ func (s *Solver) buildPod(ch *cmacme.Challenge) *corev1.Pod { // Note: this function builds pod spec using defaults and any configuration // options passed via flags to cert-manager controller. // Solver pod configuration via flags is a now deprecated -// mechanism- please use pod template instead when adding any new +// mechanism - please use pod template instead when adding any new // configuration options // https://github.com/cert-manager/cert-manager/blob/f1d7c432763100c3fb6eb6a1654d29060b479b3c/pkg/apis/acme/v1/types_issuer.go#L270 func (s *Solver) buildDefaultPod(ch *cmacme.Challenge) *corev1.Pod { @@ -170,26 +180,32 @@ func (s *Solver) buildDefaultPod(ch *cmacme.Challenge) *corev1.Pod { Namespace: ch.Namespace, Labels: podLabels, Annotations: map[string]string{ - "sidecar.istio.io/inject": "false", + "sidecar.istio.io/inject": "false", + "cluster-autoscaler.kubernetes.io/safe-to-evict": "true", }, OwnerReferences: []metav1.OwnerReference{*metav1.NewControllerRef(ch, challengeGvk)}, }, Spec: corev1.PodSpec{ + // The HTTP01 solver process does not need access to the + // Kubernetes API server, so we turn off automounting of + // the Kubernetes ServiceAccount token. + // See https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/#opt-out-of-api-credential-automounting + AutomountServiceAccountToken: ptr.To(false), NodeSelector: map[string]string{ "kubernetes.io/os": "linux", }, - RestartPolicy: corev1.RestartPolicyOnFailure, + RestartPolicy: corev1.RestartPolicyOnFailure, + EnableServiceLinks: ptr.To(false), SecurityContext: &corev1.PodSecurityContext{ - RunAsNonRoot: pointer.BoolPtr(true), + RunAsNonRoot: ptr.To(s.ACMEOptions.ACMEHTTP01SolverRunAsNonRoot), SeccompProfile: &corev1.SeccompProfile{ Type: corev1.SeccompProfileTypeRuntimeDefault, }, }, Containers: []corev1.Container{ { - Name: "acmesolver", - // TODO: use an image as specified as a config option - Image: s.Context.HTTP01SolverImage, + Name: "acmesolver", + Image: s.ACMEOptions.HTTP01SolverImage, ImagePullPolicy: corev1.PullIfNotPresent, // TODO: replace this with some kind of cmdline generator Args: []string{ @@ -215,7 +231,8 @@ func (s *Solver) buildDefaultPod(ch *cmacme.Challenge) *corev1.Pod { }, }, SecurityContext: &corev1.SecurityContext{ - AllowPrivilegeEscalation: pointer.BoolPtr(false), + ReadOnlyRootFilesystem: ptr.To(true), + AllowPrivilegeEscalation: ptr.To(false), Capabilities: &corev1.Capabilities{ Drop: []corev1.Capability{"ALL"}, }, @@ -236,25 +253,19 @@ func (s *Solver) mergePodObjectMetaWithPodTemplate(pod *corev1.Pod, podTempl *cm pod.Labels = make(map[string]string) } - for k, v := range podTempl.Labels { - pod.Labels[k] = v - } + maps.Copy(pod.Labels, podTempl.Labels) if pod.Annotations == nil { pod.Annotations = make(map[string]string) } - for k, v := range podTempl.Annotations { - pod.Annotations[k] = v - } + maps.Copy(pod.Annotations, podTempl.Annotations) if pod.Spec.NodeSelector == nil { pod.Spec.NodeSelector = make(map[string]string) } - for k, v := range podTempl.Spec.NodeSelector { - pod.Spec.NodeSelector[k] = v - } + maps.Copy(pod.Spec.NodeSelector, podTempl.Spec.NodeSelector) if pod.Spec.Tolerations == nil { pod.Spec.Tolerations = []corev1.Toleration{} @@ -274,5 +285,43 @@ func (s *Solver) mergePodObjectMetaWithPodTemplate(pod *corev1.Pod, podTempl *cm pod.Spec.ServiceAccountName = podTempl.Spec.ServiceAccountName } + if pod.Spec.ImagePullSecrets == nil { + pod.Spec.ImagePullSecrets = []corev1.LocalObjectReference{} + } + + pod.Spec.ImagePullSecrets = append(pod.Spec.ImagePullSecrets, podTempl.Spec.ImagePullSecrets...) + + if podTempl.Spec.SecurityContext != nil { + pod.Spec.SecurityContext = &corev1.PodSecurityContext{} + pod.Spec.SecurityContext.SELinuxOptions = podTempl.Spec.SecurityContext.SELinuxOptions + pod.Spec.SecurityContext.RunAsUser = podTempl.Spec.SecurityContext.RunAsUser + pod.Spec.SecurityContext.RunAsGroup = podTempl.Spec.SecurityContext.RunAsGroup + pod.Spec.SecurityContext.RunAsNonRoot = podTempl.Spec.SecurityContext.RunAsNonRoot + pod.Spec.SecurityContext.SupplementalGroups = podTempl.Spec.SecurityContext.SupplementalGroups + pod.Spec.SecurityContext.FSGroup = podTempl.Spec.SecurityContext.FSGroup + pod.Spec.SecurityContext.Sysctls = podTempl.Spec.SecurityContext.Sysctls + pod.Spec.SecurityContext.FSGroupChangePolicy = podTempl.Spec.SecurityContext.FSGroupChangePolicy + pod.Spec.SecurityContext.SeccompProfile = podTempl.Spec.SecurityContext.SeccompProfile + } + + // Merge container resources based on precedence: + // 1. If pod template resources are set, use them (highest priority) + // 2. Otherwise use values from ACMEOptions (already set in the 'buildDefaultPod' function) + if podTempl.Spec.Resources != nil { + container := &pod.Spec.Containers[0] + if podTempl.Spec.Resources.Requests != nil { + if container.Resources.Requests == nil { + container.Resources.Requests = make(corev1.ResourceList) + } + maps.Copy(container.Resources.Requests, podTempl.Spec.Resources.Requests) + } + if podTempl.Spec.Resources.Limits != nil { + if container.Resources.Limits == nil { + container.Resources.Limits = make(corev1.ResourceList) + } + maps.Copy(container.Resources.Limits, podTempl.Spec.Resources.Limits) + } + } + return pod } diff --git a/pkg/issuer/acme/http/pod_test.go b/pkg/issuer/acme/http/pod_test.go index cbb0a362d42..aabfd924bad 100644 --- a/pkg/issuer/acme/http/pod_test.go +++ b/pkg/issuer/acme/http/pod_test.go @@ -17,23 +17,147 @@ limitations under the License. package http import ( - "context" - "reflect" + "fmt" "testing" + "github.com/stretchr/testify/assert" corev1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/api/resource" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" coretesting "k8s.io/client-go/testing" + "k8s.io/utils/ptr" cmacme "github.com/cert-manager/cert-manager/pkg/apis/acme/v1" + "github.com/cert-manager/cert-manager/pkg/controller" + testpkg "github.com/cert-manager/cert-manager/pkg/controller/test" ) func TestEnsurePod(t *testing.T) { - const createdPodKey = "createdPod" - tests := map[string]solverFixture{ - "should return an existing pod if one already exists": { - Challenge: &cmacme.Challenge{ + type testT struct { + builder *testpkg.Builder + chal *cmacme.Challenge + expectedErr bool + } + cpuRequest, err := resource.ParseQuantity("10m") + assert.NoError(t, err) + cpuLimit, err := resource.ParseQuantity("100m") + assert.NoError(t, err) + memoryRequest, err := resource.ParseQuantity("64Mi") + assert.NoError(t, err) + memoryLimit, err := resource.ParseQuantity("64Mi") + assert.NoError(t, err) + var ( + testNamespace = "foo" + chal = &cmacme.Challenge{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: testNamespace, + }, + Spec: cmacme.ChallengeSpec{ + DNSName: "example.com", + Token: "token", + Key: "key", + Solver: cmacme.ACMEChallengeSolver{ + HTTP01: &cmacme.ACMEChallengeSolverHTTP01{ + Ingress: &cmacme.ACMEChallengeSolverHTTP01Ingress{}, + }, + }, + }, + } + pod = &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + GenerateName: "cm-acme-http-solver-", + Namespace: testNamespace, + Labels: podLabels(chal), + Annotations: map[string]string{ + "sidecar.istio.io/inject": "false", + "cluster-autoscaler.kubernetes.io/safe-to-evict": "true", + }, + OwnerReferences: []metav1.OwnerReference{*metav1.NewControllerRef(chal, challengeGvk)}, + }, + Spec: corev1.PodSpec{ + AutomountServiceAccountToken: ptr.To(false), + EnableServiceLinks: ptr.To(false), + NodeSelector: map[string]string{ + "kubernetes.io/os": "linux", + }, + RestartPolicy: corev1.RestartPolicyOnFailure, + SecurityContext: &corev1.PodSecurityContext{ + RunAsNonRoot: ptr.To(true), + SeccompProfile: &corev1.SeccompProfile{ + Type: corev1.SeccompProfileTypeRuntimeDefault, + }, + }, + Containers: []corev1.Container{ + { + Name: "acmesolver", + ImagePullPolicy: corev1.PullIfNotPresent, + Args: []string{ + fmt.Sprintf("--listen-port=%d", acmeSolverListenPort), + fmt.Sprintf("--domain=%s", chal.Spec.DNSName), + fmt.Sprintf("--token=%s", chal.Spec.Token), + fmt.Sprintf("--key=%s", chal.Spec.Key), + }, + Resources: corev1.ResourceRequirements{ + Requests: corev1.ResourceList{ + corev1.ResourceCPU: cpuRequest, + corev1.ResourceMemory: memoryRequest, + }, + Limits: corev1.ResourceList{ + corev1.ResourceCPU: cpuLimit, + corev1.ResourceMemory: memoryLimit, + }, + }, + Ports: []corev1.ContainerPort{ + { + Name: "http", + ContainerPort: acmeSolverListenPort, + }, + }, + SecurityContext: &corev1.SecurityContext{ + ReadOnlyRootFilesystem: ptr.To(true), + AllowPrivilegeEscalation: ptr.To(false), + Capabilities: &corev1.Capabilities{ + Drop: []corev1.Capability{"ALL"}, + }, + }, + }, + }, + }, + } + podMeta = &metav1.PartialObjectMetadata{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "v1", + Kind: "Pod", + }, + ObjectMeta: pod.ObjectMeta, + } + ) + scPod := pod.DeepCopy() + scPod.Spec.SecurityContext.RunAsUser = ptr.To(int64(1020)) + scPod.Spec.SecurityContext.RunAsNonRoot = nil + scPod.Spec.ImagePullSecrets = []corev1.LocalObjectReference{} + scPod.Spec.Tolerations = []corev1.Toleration{} + tests := map[string]testT{ + "should do nothing if pod already exists": { + builder: &testpkg.Builder{ + PartialMetadataObjects: []runtime.Object{podMeta}, + ExpectedActions: []testpkg.Action{}, + }, + chal: chal, + }, + "should create a new pod if one does not exist": { + builder: &testpkg.Builder{ + PartialMetadataObjects: []runtime.Object{}, + ExpectedActions: []testpkg.Action{testpkg.NewAction(coretesting.NewCreateAction(corev1.SchemeGroupVersion.WithResource("pods"), testNamespace, pod))}, + }, + chal: chal, + }, + "should have the correct default security context": { + chal: &cmacme.Challenge{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: testNamespace, + }, Spec: cmacme.ChallengeSpec{ DNSName: "example.com", Token: "token", @@ -45,234 +169,314 @@ func TestEnsurePod(t *testing.T) { }, }, }, - PreFn: func(t *testing.T, s *solverFixture) { - ing, err := s.Solver.createPod(context.TODO(), s.Challenge) - if err != nil { - t.Errorf("error preparing test: %v", err) - } - s.testResources[createdPodKey] = ing - - // TODO: replace this with expectedActions to make sure no other actions are performed - // create a reactor that fails the test if a pod is created - s.Builder.FakeKubeClient().PrependReactor("create", "pods", func(action coretesting.Action) (handled bool, ret runtime.Object, err error) { - t.Errorf("ensurePod should not create a pod if one already exists") - t.Fail() - return false, ret, nil - }) - - s.Builder.Sync() - }, - CheckFn: func(t *testing.T, s *solverFixture, args ...interface{}) { - createdPod := s.testResources[createdPodKey].(*corev1.Pod) - resp := args[0].(*corev1.Pod) - if resp == nil { - t.Errorf("unexpected pod = nil") - t.Fail() - return - } - if !reflect.DeepEqual(resp, createdPod) { - t.Errorf("Expected %v to equal %v", resp, createdPod) - } + builder: &testpkg.Builder{ + PartialMetadataObjects: []runtime.Object{}, + ExpectedActions: []testpkg.Action{testpkg.NewAction(coretesting.NewCreateAction(corev1.SchemeGroupVersion.WithResource("pods"), testNamespace, pod))}, }, }, - "should create a new pod if one does not exist": { - Challenge: &cmacme.Challenge{ + "security context should be configurable": { + chal: &cmacme.Challenge{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: testNamespace, + }, Spec: cmacme.ChallengeSpec{ DNSName: "example.com", Token: "token", Key: "key", Solver: cmacme.ACMEChallengeSolver{ HTTP01: &cmacme.ACMEChallengeSolverHTTP01{ - Ingress: &cmacme.ACMEChallengeSolverHTTP01Ingress{}, + Ingress: &cmacme.ACMEChallengeSolverHTTP01Ingress{ + PodTemplate: &cmacme.ACMEChallengeSolverHTTP01IngressPodTemplate{ + Spec: cmacme.ACMEChallengeSolverHTTP01IngressPodSpec{ + SecurityContext: &cmacme.ACMEChallengeSolverHTTP01IngressPodSecurityContext{ + RunAsUser: ptr.To(int64(1020)), + SeccompProfile: &corev1.SeccompProfile{ + Type: corev1.SeccompProfileTypeRuntimeDefault, + }, + }, + }, + }, + }, }, }, }, }, - PreFn: func(t *testing.T, s *solverFixture) { - expectedPod := s.Solver.buildPod(s.Challenge) - // create a reactor that fails the test if a pod is created - s.Builder.FakeKubeClient().PrependReactor("create", "pods", func(action coretesting.Action) (handled bool, ret runtime.Object, err error) { - pod := action.(coretesting.CreateAction).GetObject().(*corev1.Pod) - // clear pod name as we don't know it yet in the expectedPod - pod.Name = "" - if !reflect.DeepEqual(pod, expectedPod) { - t.Errorf("Expected %v to equal %v", pod, expectedPod) - } - return false, ret, nil - }) - - s.Builder.Sync() - }, - CheckFn: func(t *testing.T, s *solverFixture, args ...interface{}) { - resp := args[0].(*corev1.Pod) - err := args[1] - if resp == nil && err == nil { - t.Errorf("unexpected pod = nil") - t.Fail() - return - } - pods, err := s.Solver.podLister.List(labels.NewSelector()) - if err != nil { - t.Errorf("unexpected error listing pods: %v", err) - t.Fail() - return - } - if len(pods) != 1 { - t.Errorf("unexpected %d pods in lister: %+v", len(pods), pods) - t.Fail() - return - } - if !reflect.DeepEqual(pods[0], resp) { - t.Errorf("Expected %v to equal %v", pods[0], resp) - } + builder: &testpkg.Builder{ + PartialMetadataObjects: []runtime.Object{}, + ExpectedActions: []testpkg.Action{testpkg.NewAction(coretesting.NewCreateAction(corev1.SchemeGroupVersion.WithResource("pods"), testNamespace, scPod))}, }, }, - "should clean up if multiple pods exist": { - Challenge: &cmacme.Challenge{ + "security context should be configurable using gateway-api": { + chal: &cmacme.Challenge{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: testNamespace, + }, Spec: cmacme.ChallengeSpec{ DNSName: "example.com", Token: "token", Key: "key", Solver: cmacme.ACMEChallengeSolver{ HTTP01: &cmacme.ACMEChallengeSolverHTTP01{ - Ingress: &cmacme.ACMEChallengeSolverHTTP01Ingress{}, + GatewayHTTPRoute: &cmacme.ACMEChallengeSolverHTTP01GatewayHTTPRoute{ + PodTemplate: &cmacme.ACMEChallengeSolverHTTP01IngressPodTemplate{ + Spec: cmacme.ACMEChallengeSolverHTTP01IngressPodSpec{ + SecurityContext: &cmacme.ACMEChallengeSolverHTTP01IngressPodSecurityContext{ + RunAsUser: ptr.To(int64(1020)), + SeccompProfile: &corev1.SeccompProfile{ + Type: corev1.SeccompProfileTypeRuntimeDefault, + }, + }, + }, + }, + }, }, }, }, }, - Err: true, - PreFn: func(t *testing.T, s *solverFixture) { - _, err := s.Solver.createPod(context.TODO(), s.Challenge) - if err != nil { - t.Errorf("error preparing test: %v", err) - } - _, err = s.Solver.createPod(context.TODO(), s.Challenge) - if err != nil { - t.Errorf("error preparing test: %v", err) - } - - s.Builder.Sync() + builder: &testpkg.Builder{ + PartialMetadataObjects: []runtime.Object{}, + ExpectedActions: []testpkg.Action{testpkg.NewAction(coretesting.NewCreateAction(corev1.SchemeGroupVersion.WithResource("pods"), testNamespace, scPod))}, }, - CheckFn: func(t *testing.T, s *solverFixture, args ...interface{}) { - pods, err := s.Solver.podLister.List(labels.NewSelector()) - if err != nil { - t.Errorf("error listing pods: %v", err) - t.Fail() - return - } - if len(pods) != 0 { - t.Errorf("expected pods to have been cleaned up, but there were %d pods left", len(pods)) - } + }, + "should clean up if multiple pods exist": { + builder: &testpkg.Builder{ + PartialMetadataObjects: []runtime.Object{podMeta, func(p metav1.PartialObjectMetadata) *metav1.PartialObjectMetadata { p.Name = "foobar"; return &p }(*podMeta)}, + KubeObjects: []runtime.Object{pod, func(p corev1.Pod) *corev1.Pod { p.Name = "foobar"; return &p }(*pod)}, + ExpectedActions: []testpkg.Action{testpkg.NewAction(coretesting.NewDeleteAction(corev1.SchemeGroupVersion.WithResource("pods"), testNamespace, "foobar")), + testpkg.NewAction(coretesting.NewDeleteAction(corev1.SchemeGroupVersion.WithResource("pods"), testNamespace, ""))}, }, + chal: chal, + expectedErr: true, }, } - for name, test := range tests { + for name, scenario := range tests { t.Run(name, func(t *testing.T) { - test.Setup(t) - resp, err := test.Solver.ensurePod(context.TODO(), test.Challenge) - if err != nil && !test.Err { - t.Errorf("Expected function to not error, but got: %v", err) + scenario.builder.T = t + scenario.builder.InitWithRESTConfig() + s := &Solver{ + Context: scenario.builder.Context, + podLister: scenario.builder.HTTP01ResourceMetadataInformersFactory.ForResource(corev1.SchemeGroupVersion.WithResource("pods")).Lister(), } - if err == nil && test.Err { - t.Errorf("Expected function to get an error, but got: %v", err) + s.Context.ACMEOptions = controller.ACMEOptions{ + HTTP01SolverResourceRequestCPU: cpuRequest, + HTTP01SolverResourceRequestMemory: memoryRequest, + HTTP01SolverResourceLimitsCPU: cpuLimit, + HTTP01SolverResourceLimitsMemory: memoryLimit, + ACMEHTTP01SolverRunAsNonRoot: true, + } + scenario.builder.Start() + defer scenario.builder.Stop() + err := s.ensurePod(t.Context(), scenario.chal) + if err != nil != scenario.expectedErr { + t.Fatalf("unexpected error: wants err: %t, got err %v", scenario.expectedErr, err) + } - test.Finish(t, resp, err) + scenario.builder.CheckAndFinish() }) + } } -func TestGetPodsForCertificate(t *testing.T) { - const createdPodKey = "createdPod" - tests := map[string]solverFixture{ - "should return one pod that matches": { - Challenge: &cmacme.Challenge{ - Spec: cmacme.ChallengeSpec{ - DNSName: "example.com", - Solver: cmacme.ACMEChallengeSolver{ - HTTP01: &cmacme.ACMEChallengeSolverHTTP01{ - Ingress: &cmacme.ACMEChallengeSolverHTTP01Ingress{}, - }, +func TestGetPodsForChallenge(t *testing.T) { + type testT struct { + builder *testpkg.Builder + chal *cmacme.Challenge + wantedPodMetas []*metav1.PartialObjectMetadata + wantsErr bool + } + var ( + testNamespace = "foo" + chal = &cmacme.Challenge{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: testNamespace, + }, + Spec: cmacme.ChallengeSpec{ + DNSName: "example.com", + Token: "token", + Key: "key", + Solver: cmacme.ACMEChallengeSolver{ + HTTP01: &cmacme.ACMEChallengeSolverHTTP01{ + Ingress: &cmacme.ACMEChallengeSolverHTTP01Ingress{}, }, }, }, - PreFn: func(t *testing.T, s *solverFixture) { - ing, err := s.Solver.createPod(context.TODO(), s.Challenge) - if err != nil { - t.Errorf("error preparing test: %v", err) - } - - s.testResources[createdPodKey] = ing - s.Builder.Sync() + } + pod = &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + GenerateName: "cm-acme-http-solver-", + Namespace: testNamespace, + Labels: podLabels(chal), + OwnerReferences: []metav1.OwnerReference{*metav1.NewControllerRef(chal, challengeGvk)}, }, - CheckFn: func(t *testing.T, s *solverFixture, args ...interface{}) { - createdPod := s.testResources[createdPodKey].(*corev1.Pod) - resp := args[0].([]*corev1.Pod) - if len(resp) != 1 { - t.Errorf("expected one pod to be returned, but got %d", len(resp)) - t.Fail() - return - } - if !reflect.DeepEqual(resp[0], createdPod) { - t.Errorf("Expected %v to equal %v", resp[0], createdPod) - } + } + podMeta = &metav1.PartialObjectMetadata{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "v1", + Kind: "Pod", + }, + ObjectMeta: pod.ObjectMeta, + } + podMeta2 = &metav1.PartialObjectMetadata{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "v1", + Kind: "Pod", }, + ObjectMeta: *pod.ObjectMeta.DeepCopy(), + } + ) + podMeta2.Labels[cmacme.DomainLabelKey] = "foo" + tests := map[string]testT{ + "should return one pod that matches": { + chal: chal, + builder: &testpkg.Builder{ + PartialMetadataObjects: []runtime.Object{podMeta}, + }, + wantedPodMetas: []*metav1.PartialObjectMetadata{podMeta}, }, "should not return a pod for the same certificate but different domain": { + chal: chal, + builder: &testpkg.Builder{ + PartialMetadataObjects: []runtime.Object{&metav1.PartialObjectMetadata{}}, + }, + }, + } + for name, scenario := range tests { + t.Run(name, func(t *testing.T) { + scenario.builder.T = t + scenario.builder.InitWithRESTConfig() + s := &Solver{ + Context: scenario.builder.Context, + podLister: scenario.builder.HTTP01ResourceMetadataInformersFactory.ForResource(corev1.SchemeGroupVersion.WithResource("pods")).Lister(), + } + defer scenario.builder.Stop() + scenario.builder.Start() + gotPodMetas, err := s.getPodsForChallenge(s.RootContext, scenario.chal) + if err != nil != scenario.wantsErr { + t.Fatalf("unexpected error: wants error: %t, got error: %v", scenario.wantsErr, err) + } + assert.ElementsMatch(t, gotPodMetas, scenario.wantedPodMetas) + scenario.builder.CheckAndFinish() + }) + } +} + +func TestMergePodObjectMetaWithPodTemplate(t *testing.T) { + const ( + createdPodKey = "createdPod" + + defaultCPURequest = "10m" + defaultCPULimit = "100m" + defaultMemoryRequest = "64Mi" + defaultMemoryLimit = "64Mi" + ) + + // setupACMEOptionsWithDefaultsResources sets up ACMEOptions with default resource values from global controller flags + setupACMEOptionsWithDefaultsResources := func(s *solverFixture) { + s.Solver.ACMEOptions.HTTP01SolverResourceRequestCPU = resource.MustParse(defaultCPURequest) + s.Solver.ACMEOptions.HTTP01SolverResourceRequestMemory = resource.MustParse(defaultMemoryRequest) + s.Solver.ACMEOptions.HTTP01SolverResourceLimitsCPU = resource.MustParse(defaultCPULimit) + s.Solver.ACMEOptions.HTTP01SolverResourceLimitsMemory = resource.MustParse(defaultMemoryLimit) + } + + tests := map[string]solverFixture{ + "should use labels, annotations and spec fields from template": { Challenge: &cmacme.Challenge{ Spec: cmacme.ChallengeSpec{ DNSName: "example.com", Solver: cmacme.ACMEChallengeSolver{ HTTP01: &cmacme.ACMEChallengeSolverHTTP01{ - Ingress: &cmacme.ACMEChallengeSolverHTTP01Ingress{}, + Ingress: &cmacme.ACMEChallengeSolverHTTP01Ingress{ + PodTemplate: &cmacme.ACMEChallengeSolverHTTP01IngressPodTemplate{ + ACMEChallengeSolverHTTP01IngressPodObjectMeta: cmacme.ACMEChallengeSolverHTTP01IngressPodObjectMeta{ + Labels: map[string]string{ + "this is a": "label", + cmacme.DomainLabelKey: "44655555555", + }, + Annotations: map[string]string{ + "sidecar.istio.io/inject": "true", + "cluster-autoscaler.kubernetes.io/safe-to-evict": "false", + "foo": "bar", + }, + }, + Spec: cmacme.ACMEChallengeSolverHTTP01IngressPodSpec{ + PriorityClassName: "high", + NodeSelector: map[string]string{ + "node": "selector", + }, + Tolerations: []corev1.Toleration{ + { + Key: "key", + Operator: "Exists", + Effect: "NoSchedule", + }, + }, + ServiceAccountName: "cert-manager", + ImagePullSecrets: []corev1.LocalObjectReference{{Name: "cred"}}, + }, + }, + }, }, }, }, }, PreFn: func(t *testing.T, s *solverFixture) { - differentChallenge := s.Challenge.DeepCopy() - differentChallenge.Spec.DNSName = "notexample.com" - _, err := s.Solver.createPod(context.TODO(), differentChallenge) - if err != nil { - t.Errorf("error preparing test: %v", err) + resultingPod := s.Solver.buildDefaultPod(s.Challenge) + resultingPod.Labels = map[string]string{ + "this is a": "label", + cmacme.DomainLabelKey: "44655555555", + cmacme.TokenLabelKey: "1", + cmacme.SolverIdentificationLabelKey: "true", + } + resultingPod.Annotations = map[string]string{ + "sidecar.istio.io/inject": "true", + "cluster-autoscaler.kubernetes.io/safe-to-evict": "false", + "foo": "bar", + } + resultingPod.Spec.NodeSelector = map[string]string{ + "kubernetes.io/os": "linux", + "node": "selector", } + resultingPod.Spec.Tolerations = []corev1.Toleration{ + { + Key: "key", + Operator: "Exists", + Effect: "NoSchedule", + }, + } + resultingPod.Spec.PriorityClassName = "high" + resultingPod.Spec.ServiceAccountName = "cert-manager" + resultingPod.Spec.ImagePullSecrets = []corev1.LocalObjectReference{{Name: "cred"}} + s.testResources[createdPodKey] = resultingPod s.Builder.Sync() }, CheckFn: func(t *testing.T, s *solverFixture, args ...interface{}) { - resp := args[0].([]*corev1.Pod) - if len(resp) != 0 { - t.Errorf("expected zero pods to be returned, but got %d", len(resp)) + resultingPod := s.testResources[createdPodKey].(*corev1.Pod) + + resp, ok := args[0].(*corev1.Pod) + if !ok { + t.Errorf("expected pod to be returned, but got %v", args[0]) t.Fail() return } + + // ignore pointer differences here + resultingPod.OwnerReferences = resp.OwnerReferences + + if resp.String() != resultingPod.String() { + t.Errorf("unexpected pod generated from merge\nexp=%s\ngot=%s", + resultingPod, resp) + t.Fail() + } }, }, - } - for name, test := range tests { - t.Run(name, func(t *testing.T) { - test.Setup(t) - resp, err := test.Solver.getPodsForChallenge(context.TODO(), test.Challenge) - if err != nil && !test.Err { - t.Errorf("Expected function to not error, but got: %v", err) - } - if err == nil && test.Err { - t.Errorf("Expected function to get an error, but got: %v", err) - } - test.Finish(t, resp, err) - }) - } -} - -func TestMergePodObjectMetaWithPodTemplate(t *testing.T) { - const createdPodKey = "createdPod" - tests := map[string]solverFixture{ - "should use labels and annotations from template": { + "should use labels, annotations and spec fields from template when using gateway-api": { Challenge: &cmacme.Challenge{ Spec: cmacme.ChallengeSpec{ DNSName: "example.com", Solver: cmacme.ACMEChallengeSolver{ HTTP01: &cmacme.ACMEChallengeSolverHTTP01{ - Ingress: &cmacme.ACMEChallengeSolverHTTP01Ingress{ + GatewayHTTPRoute: &cmacme.ACMEChallengeSolverHTTP01GatewayHTTPRoute{ PodTemplate: &cmacme.ACMEChallengeSolverHTTP01IngressPodTemplate{ ACMEChallengeSolverHTTP01IngressPodObjectMeta: cmacme.ACMEChallengeSolverHTTP01IngressPodObjectMeta{ Labels: map[string]string{ @@ -280,8 +484,9 @@ func TestMergePodObjectMetaWithPodTemplate(t *testing.T) { cmacme.DomainLabelKey: "44655555555", }, Annotations: map[string]string{ - "sidecar.istio.io/inject": "true", - "foo": "bar", + "sidecar.istio.io/inject": "true", + "cluster-autoscaler.kubernetes.io/safe-to-evict": "false", + "foo": "bar", }, }, Spec: cmacme.ACMEChallengeSolverHTTP01IngressPodSpec{ @@ -297,6 +502,7 @@ func TestMergePodObjectMetaWithPodTemplate(t *testing.T) { }, }, ServiceAccountName: "cert-manager", + ImagePullSecrets: []corev1.LocalObjectReference{{Name: "cred"}}, }, }, }, @@ -313,8 +519,9 @@ func TestMergePodObjectMetaWithPodTemplate(t *testing.T) { cmacme.SolverIdentificationLabelKey: "true", } resultingPod.Annotations = map[string]string{ - "sidecar.istio.io/inject": "true", - "foo": "bar", + "sidecar.istio.io/inject": "true", + "cluster-autoscaler.kubernetes.io/safe-to-evict": "false", + "foo": "bar", } resultingPod.Spec.NodeSelector = map[string]string{ "kubernetes.io/os": "linux", @@ -329,6 +536,7 @@ func TestMergePodObjectMetaWithPodTemplate(t *testing.T) { } resultingPod.Spec.PriorityClassName = "high" resultingPod.Spec.ServiceAccountName = "cert-manager" + resultingPod.Spec.ImagePullSecrets = []corev1.LocalObjectReference{{Name: "cred"}} s.testResources[createdPodKey] = resultingPod s.Builder.Sync() @@ -403,8 +611,147 @@ func TestMergePodObjectMetaWithPodTemplate(t *testing.T) { } }, }, - } + "should use ACMEOptions values when podTemplate resources are not set": { + Challenge: &cmacme.Challenge{ + Spec: cmacme.ChallengeSpec{ + DNSName: "example.com", + Solver: cmacme.ACMEChallengeSolver{ + HTTP01: &cmacme.ACMEChallengeSolverHTTP01{ + Ingress: &cmacme.ACMEChallengeSolverHTTP01Ingress{ + PodTemplate: &cmacme.ACMEChallengeSolverHTTP01IngressPodTemplate{ + Spec: cmacme.ACMEChallengeSolverHTTP01IngressPodSpec{ + // No resources specified in template + }, + }, + }, + }, + }, + }, + }, + PreFn: func(t *testing.T, s *solverFixture) { + setupACMEOptionsWithDefaultsResources(s) + }, + CheckFn: func(t *testing.T, s *solverFixture, args ...interface{}) { + resp, ok := args[0].(*corev1.Pod) + if !ok { + t.Errorf("expected pod to be returned, but got %v", args[0]) + t.Fail() + return + } + + container := resp.Spec.Containers[0] + + // Verify that actual container resources match values from ACMEOptions + expectedRequests := corev1.ResourceList{ + corev1.ResourceCPU: resource.MustParse(defaultCPURequest), + corev1.ResourceMemory: resource.MustParse(defaultMemoryRequest), + } + expectedLimits := corev1.ResourceList{ + corev1.ResourceCPU: resource.MustParse(defaultCPULimit), + corev1.ResourceMemory: resource.MustParse(defaultMemoryLimit), + } + validateContainerResources(t, container, expectedRequests, expectedLimits) + }, + }, + "should override ACMEOptions values when podTemplate resources are set": { + Challenge: &cmacme.Challenge{ + Spec: cmacme.ChallengeSpec{ + DNSName: "example.com", + Solver: cmacme.ACMEChallengeSolver{ + HTTP01: &cmacme.ACMEChallengeSolverHTTP01{ + Ingress: &cmacme.ACMEChallengeSolverHTTP01Ingress{ + PodTemplate: &cmacme.ACMEChallengeSolverHTTP01IngressPodTemplate{ + Spec: cmacme.ACMEChallengeSolverHTTP01IngressPodSpec{ + Resources: &cmacme.ACMEChallengeSolverHTTP01IngressPodResources{ + Requests: corev1.ResourceList{ + corev1.ResourceCPU: resource.MustParse("75m"), + corev1.ResourceMemory: resource.MustParse("96Mi"), + }, + Limits: corev1.ResourceList{ + corev1.ResourceCPU: resource.MustParse("150m"), + corev1.ResourceMemory: resource.MustParse("192Mi"), + }, + }, + }, + }, + }, + }, + }, + }, + }, + PreFn: func(t *testing.T, s *solverFixture) { + setupACMEOptionsWithDefaultsResources(s) + }, + CheckFn: func(t *testing.T, s *solverFixture, args ...interface{}) { + resp, ok := args[0].(*corev1.Pod) + if !ok { + t.Errorf("expected pod to be returned, but got %v", args[0]) + t.Fail() + return + } + + container := resp.Spec.Containers[0] + + // Verify that actual container resources match values from podTemplate + expectedRequests := corev1.ResourceList{ + corev1.ResourceCPU: resource.MustParse("75m"), + corev1.ResourceMemory: resource.MustParse("96Mi"), + } + expectedLimits := corev1.ResourceList{ + corev1.ResourceCPU: resource.MustParse("150m"), + corev1.ResourceMemory: resource.MustParse("192Mi"), + } + validateContainerResources(t, container, expectedRequests, expectedLimits) + }, + }, + "should handle partial resources in podTemplate by merging with ACMEOptions values": { + Challenge: &cmacme.Challenge{ + Spec: cmacme.ChallengeSpec{ + DNSName: "example.com", + Solver: cmacme.ACMEChallengeSolver{ + HTTP01: &cmacme.ACMEChallengeSolverHTTP01{ + Ingress: &cmacme.ACMEChallengeSolverHTTP01Ingress{ + PodTemplate: &cmacme.ACMEChallengeSolverHTTP01IngressPodTemplate{ + Spec: cmacme.ACMEChallengeSolverHTTP01IngressPodSpec{ + Resources: &cmacme.ACMEChallengeSolverHTTP01IngressPodResources{ + // Only CPU request specified + Requests: corev1.ResourceList{ + corev1.ResourceCPU: resource.MustParse("25m"), + }, + }, + }, + }, + }, + }, + }, + }, + }, + PreFn: func(t *testing.T, s *solverFixture) { + setupACMEOptionsWithDefaultsResources(s) + }, + CheckFn: func(t *testing.T, s *solverFixture, args ...interface{}) { + resp, ok := args[0].(*corev1.Pod) + if !ok { + t.Errorf("expected pod to be returned, but got %v", args[0]) + t.Fail() + return + } + + container := resp.Spec.Containers[0] + // Verify that actual container resources match CPU request from template override, others from ACMEOptions + expectedRequests := corev1.ResourceList{ + corev1.ResourceCPU: resource.MustParse("25m"), + corev1.ResourceMemory: resource.MustParse(defaultMemoryRequest), + } + expectedLimits := corev1.ResourceList{ + corev1.ResourceCPU: resource.MustParse(defaultCPULimit), + corev1.ResourceMemory: resource.MustParse(defaultMemoryLimit), + } + validateContainerResources(t, container, expectedRequests, expectedLimits) + }, + }, + } for name, test := range tests { t.Run(name, func(t *testing.T) { test.Setup(t) @@ -413,3 +760,19 @@ func TestMergePodObjectMetaWithPodTemplate(t *testing.T) { }) } } + +// validateContainerResources checks that container resources match expected values +func validateContainerResources(t *testing.T, container corev1.Container, expectedRequests, expectedLimits corev1.ResourceList) { + for resourceType, expectedQuantity := range expectedRequests { + actualQuantity := container.Resources.Requests[resourceType] + if !actualQuantity.Equal(expectedQuantity) { + t.Errorf("%s request mismatch: got %v, expected %v", resourceType, actualQuantity, expectedQuantity) + } + } + for resourceType, expectedQuantity := range expectedLimits { + actualQuantity := container.Resources.Limits[resourceType] + if !actualQuantity.Equal(expectedQuantity) { + t.Errorf("%s limit mismatch: got %v, expected %v", resourceType, actualQuantity, expectedQuantity) + } + } +} diff --git a/pkg/issuer/acme/http/service.go b/pkg/issuer/acme/http/service.go index 16f1ab47c92..2700d64ad02 100644 --- a/pkg/issuer/acme/http/service.go +++ b/pkg/issuer/acme/http/service.go @@ -31,34 +31,37 @@ import ( logf "github.com/cert-manager/cert-manager/pkg/logs" ) -func (s *Solver) ensureService(ctx context.Context, ch *cmacme.Challenge) (*corev1.Service, error) { +// ensureService ensures that a Service exists for the given Challenge. It +// returns the name of the Service and error if any. +func (s *Solver) ensureService(ctx context.Context, ch *cmacme.Challenge) (string, error) { log := logf.FromContext(ctx).WithName("ensureService") log.V(logf.DebugLevel).Info("checking for existing HTTP01 solver services for challenge") existingServices, err := s.getServicesForChallenge(ctx, ch) if err != nil { - return nil, err + return "", err } if len(existingServices) == 1 { logf.WithRelatedResource(log, existingServices[0]).Info("found one existing HTTP01 solver Service for challenge resource") - return existingServices[0], nil + return existingServices[0].Name, nil } if len(existingServices) > 1 { log.V(logf.DebugLevel).Info("multiple challenge solver services found for challenge. cleaning up all existing services.") err := s.cleanupServices(ctx, ch) if err != nil { - return nil, err + return "", err } - return nil, fmt.Errorf("multiple existing challenge solver services found and cleaned up. retrying challenge sync") + return "", fmt.Errorf("multiple existing challenge solver services found and cleaned up. retrying challenge sync") } log.V(logf.DebugLevel).Info("creating HTTP01 challenge solver service") - return s.createService(ctx, ch) + svc, err := s.createService(ctx, ch) + return svc.Name, err } // getServicesForChallenge returns a list of services that were created to solve // http challenges for the given domain -func (s *Solver) getServicesForChallenge(ctx context.Context, ch *cmacme.Challenge) ([]*corev1.Service, error) { +func (s *Solver) getServicesForChallenge(ctx context.Context, ch *cmacme.Challenge) ([]*metav1.PartialObjectMetadata, error) { log := logf.FromContext(ctx).WithName("getServicesForChallenge") podLabels := podLabels(ch) @@ -71,19 +74,23 @@ func (s *Solver) getServicesForChallenge(ctx context.Context, ch *cmacme.Challen selector = selector.Add(*req) } - serviceList, err := s.serviceLister.Services(ch.Namespace).List(selector) + serviceList, err := s.serviceLister.ByNamespace(ch.Namespace).List(selector) if err != nil { return nil, err } - var relevantServices []*corev1.Service + var relevantServices []*metav1.PartialObjectMetadata for _, service := range serviceList { - if !metav1.IsControlledBy(service, ch) { - logf.WithRelatedResource(log, service).Info("found existing solver pod for this challenge resource, however " + + s, ok := service.(*metav1.PartialObjectMetadata) + if !ok { + return nil, fmt.Errorf("internal error: cannot cast Service PartialObjectMetadata") + } + if !metav1.IsControlledBy(s, ch) { + logf.WithRelatedResource(log, s).Info("found existing solver pod for this challenge resource, however " + "it does not have an appropriate OwnerReference referencing this challenge. Skipping it altogether.") continue } - relevantServices = append(relevantServices, service) + relevantServices = append(relevantServices, s) } return relevantServices, nil @@ -117,7 +124,7 @@ func buildService(ch *cmacme.Challenge) (*corev1.Service, error) { { Name: "http", Port: acmeSolverListenPort, - TargetPort: intstr.FromInt(acmeSolverListenPort), + TargetPort: intstr.FromInt32(acmeSolverListenPort), }, }, Selector: podLabels, diff --git a/pkg/issuer/acme/http/service_test.go b/pkg/issuer/acme/http/service_test.go index 7a135733803..67c39aec045 100644 --- a/pkg/issuer/acme/http/service_test.go +++ b/pkg/issuer/acme/http/service_test.go @@ -17,477 +17,229 @@ limitations under the License. package http import ( - "context" - "reflect" "testing" - v1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/labels" + "github.com/stretchr/testify/assert" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/util/intstr" coretesting "k8s.io/client-go/testing" cmacme "github.com/cert-manager/cert-manager/pkg/apis/acme/v1" + testpkg "github.com/cert-manager/cert-manager/pkg/controller/test" ) func TestEnsureService(t *testing.T) { - const createdServiceKey = "createdService" - tests := map[string]solverFixture{ - "should return an existing service if one already exists": { - Challenge: &cmacme.Challenge{ - Spec: cmacme.ChallengeSpec{ - DNSName: "example.com", - Solver: cmacme.ACMEChallengeSolver{ - HTTP01: &cmacme.ACMEChallengeSolverHTTP01{ - Ingress: &cmacme.ACMEChallengeSolverHTTP01Ingress{}, - }, + type testT struct { + builder *testpkg.Builder + chal *cmacme.Challenge + expectedErr bool + } + var ( + testNamespace = "foo" + chal = &cmacme.Challenge{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: testNamespace, + }, + Spec: cmacme.ChallengeSpec{ + DNSName: "example.com", + Token: "token", + Key: "key", + Solver: cmacme.ACMEChallengeSolver{ + HTTP01: &cmacme.ACMEChallengeSolverHTTP01{ + Ingress: &cmacme.ACMEChallengeSolverHTTP01Ingress{}, }, }, }, - PreFn: func(t *testing.T, s *solverFixture) { - svc, err := s.Solver.createService(context.TODO(), s.Challenge) - if err != nil { - t.Errorf("error preparing test: %v", err) - } - s.testResources[createdServiceKey] = svc - - // TODO: replace this with expectedActions to make sure no other actions are performed - // create a reactor that fails the test if a service is created - s.FakeKubeClient().PrependReactor("create", "services", func(action coretesting.Action) (handled bool, ret runtime.Object, err error) { - t.Errorf("ensureService should not create a service if one already exists") - t.Fail() - return false, ret, nil - }) - - s.Builder.Sync() - }, - CheckFn: func(t *testing.T, s *solverFixture, args ...interface{}) { - createdService := s.testResources[createdServiceKey].(*v1.Service) - resp := args[0].(*v1.Service) - if resp == nil { - t.Errorf("unexpected service = nil") - t.Fail() - return - } - if !reflect.DeepEqual(resp, createdService) { - t.Errorf("Expected %v to equal %v", resp, createdService) - } - }, - }, - "should create a new service if one does not exist": { - Challenge: &cmacme.Challenge{ - Spec: cmacme.ChallengeSpec{ - DNSName: "example.com", - Solver: cmacme.ACMEChallengeSolver{ - HTTP01: &cmacme.ACMEChallengeSolverHTTP01{ - Ingress: &cmacme.ACMEChallengeSolverHTTP01Ingress{}, - }, - }, + } + service = &corev1.Service{ + ObjectMeta: metav1.ObjectMeta{ + GenerateName: "cm-acme-http-solver-", + Namespace: testNamespace, + Labels: podLabels(chal), + Annotations: map[string]string{ + "auth.istio.io/8089": "NONE", }, - }, - PreFn: func(t *testing.T, s *solverFixture) { - expectedService, err := buildService(s.Challenge) - if err != nil { - t.Errorf("expectedService returned an error whilst building test fixture: %v", err) - } - // create a reactor that fails the test if a service is created - s.Builder.FakeKubeClient().PrependReactor("create", "services", func(action coretesting.Action) (handled bool, ret runtime.Object, err error) { - service := action.(coretesting.CreateAction).GetObject().(*v1.Service) - // clear service name as we don't know it yet in the expectedService - service.Name = "" - if !reflect.DeepEqual(service, expectedService) { - t.Errorf("Expected %v to equal %v", service, expectedService) - } - return false, ret, nil - }) - - s.Builder.Sync() - }, - CheckFn: func(t *testing.T, s *solverFixture, args ...interface{}) { - resp := args[0].(*v1.Service) - err := args[1] - if resp == nil && err == nil { - t.Errorf("unexpected service = nil") - t.Fail() - return - } - services, err := s.Solver.serviceLister.List(labels.NewSelector()) - if err != nil { - t.Errorf("unexpected error listing services: %v", err) - t.Fail() - return - } - if len(services) != 1 { - t.Errorf("unexpected %d services in lister: %+v", len(services), services) - t.Fail() - return - } - if !reflect.DeepEqual(services[0], resp) { - t.Errorf("Expected %v to equal %v", services[0], resp) - } - }, - }, - "should clean up if multiple services exist": { - Challenge: &cmacme.Challenge{ - Spec: cmacme.ChallengeSpec{ - DNSName: "example.com", - Solver: cmacme.ACMEChallengeSolver{ - HTTP01: &cmacme.ACMEChallengeSolverHTTP01{ - Ingress: &cmacme.ACMEChallengeSolverHTTP01Ingress{}, - }, + OwnerReferences: []metav1.OwnerReference{*metav1.NewControllerRef(chal, challengeGvk)}, + }, + Spec: corev1.ServiceSpec{ + Type: corev1.ServiceTypeNodePort, + Ports: []corev1.ServicePort{ + { + Name: "http", + Port: acmeSolverListenPort, + TargetPort: intstr.FromInt32(acmeSolverListenPort), }, }, + Selector: podLabels(chal), + }, + } + serviceMeta = &metav1.PartialObjectMetadata{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "v1", + Kind: "Service", + }, + ObjectMeta: service.ObjectMeta, + } + ) + tests := map[string]testT{ + "should return an existing service if one already exists": { + builder: &testpkg.Builder{ + PartialMetadataObjects: []runtime.Object{serviceMeta}, }, - Err: true, - PreFn: func(t *testing.T, s *solverFixture) { - _, err := s.Solver.createService(context.TODO(), s.Challenge) - if err != nil { - t.Errorf("error preparing test: %v", err) - } - _, err = s.Solver.createService(context.TODO(), s.Challenge) - if err != nil { - t.Errorf("error preparing test: %v", err) - } - - s.Builder.Sync() - }, - CheckFn: func(t *testing.T, s *solverFixture, args ...interface{}) { - services, err := s.Solver.serviceLister.List(labels.NewSelector()) - if err != nil { - t.Errorf("error listing services: %v", err) - t.Fail() - return - } - if len(services) != 0 { - t.Errorf("expected services to have been cleaned up, but there were %d services left", len(services)) - } - }, + chal: chal, }, - "http-01 ingress challenge without a service type should default to NodePort": { - Challenge: &cmacme.Challenge{ - Spec: cmacme.ChallengeSpec{ - DNSName: "test.com", - Solver: cmacme.ACMEChallengeSolver{ - HTTP01: &cmacme.ACMEChallengeSolverHTTP01{ - Ingress: &cmacme.ACMEChallengeSolverHTTP01Ingress{}, - }, - }, - }, - }, - PreFn: func(t *testing.T, s *solverFixture) { - expectedService, err := buildService(s.Challenge) - if err != nil { - t.Errorf("expectedService returned an error whilst building test fixture: %v", err) - } - // create a reactor that fails the test if a service is created - s.Builder.FakeKubeClient().PrependReactor("create", "services", func(action coretesting.Action) (handled bool, ret runtime.Object, err error) { - service := action.(coretesting.CreateAction).GetObject().(*v1.Service) - // clear service name as we don't know it yet in the expectedService - service.Name = "" - if !reflect.DeepEqual(service, expectedService) { - t.Errorf("Expected %v to equal %v", service, expectedService) - } - return false, ret, nil - }) - - s.Builder.Sync() - }, - CheckFn: func(t *testing.T, s *solverFixture, args ...interface{}) { - resp := args[0].(*v1.Service) - err := args[1] - if resp == nil && err == nil { - t.Errorf("unexpected service = nil") - t.Fail() - return - } - services, err := s.Solver.serviceLister.List(labels.NewSelector()) - if err != nil { - t.Errorf("unexpected error listing services: %v", err) - t.Fail() - return - } - if len(services) != 1 { - t.Errorf("unexpected %d services in lister: %+v", len(services), services) - t.Fail() - return - } - if !reflect.DeepEqual(services[0], resp) { - t.Errorf("Expected %v to equal %v", services[0], resp) - } - if services[0].Spec.Type != v1.ServiceTypeNodePort { - t.Errorf("Blank service type should default to NodePort, but was %q", services[0].Spec.Type) - } + "should create a new service if one does not exist": { + builder: &testpkg.Builder{ + ExpectedActions: []testpkg.Action{testpkg.NewAction(coretesting.NewCreateAction(corev1.SchemeGroupVersion.WithResource("services"), testNamespace, service))}, }, - Err: false, + chal: chal, + }, + "should clean up if multiple services exist": { + builder: &testpkg.Builder{ + PartialMetadataObjects: []runtime.Object{serviceMeta, func(s metav1.PartialObjectMetadata) *metav1.PartialObjectMetadata { s.Name = "foobar"; return &s }(*serviceMeta)}, + KubeObjects: []runtime.Object{service, func(s corev1.Service) *corev1.Service { s.Name = "foobar"; return &s }(*service)}, + ExpectedActions: []testpkg.Action{testpkg.NewAction(coretesting.NewDeleteAction(corev1.SchemeGroupVersion.WithResource("services"), testNamespace, "foobar")), + testpkg.NewAction(coretesting.NewDeleteAction(corev1.SchemeGroupVersion.WithResource("services"), testNamespace, ""))}, + }, + chal: chal, + expectedErr: true, }, "http-01 ingress challenge with a service type specified should end up on the generated solver service": { - Challenge: &cmacme.Challenge{ - Spec: cmacme.ChallengeSpec{ - DNSName: "test.com", - Solver: cmacme.ACMEChallengeSolver{ - HTTP01: &cmacme.ACMEChallengeSolverHTTP01{ - Ingress: &cmacme.ACMEChallengeSolverHTTP01Ingress{ - ServiceType: v1.ServiceTypeClusterIP, - }, - }, - }, - }, + chal: func(chal *cmacme.Challenge) *cmacme.Challenge { + chal.Spec.Solver.HTTP01.Ingress.ServiceType = corev1.ServiceTypeClusterIP + return chal + }(chal.DeepCopy()), + builder: &testpkg.Builder{ + ExpectedActions: []testpkg.Action{testpkg.NewAction(coretesting.NewCreateAction(corev1.SchemeGroupVersion.WithResource("services"), testNamespace, func(s *corev1.Service) *corev1.Service { s.Spec.Type = corev1.ServiceTypeClusterIP; return s }(service.DeepCopy())))}, }, - PreFn: func(t *testing.T, s *solverFixture) { - expectedService, err := buildService(s.Challenge) - if err != nil { - t.Errorf("expectedService returned an error whilst building test fixture: %v", err) - } - // create a reactor that fails the test if a service is created - s.Builder.FakeKubeClient().PrependReactor("create", "services", func(action coretesting.Action) (handled bool, ret runtime.Object, err error) { - service := action.(coretesting.CreateAction).GetObject().(*v1.Service) - // clear service name as we don't know it yet in the expectedService - service.Name = "" - if !reflect.DeepEqual(service, expectedService) { - t.Errorf("Expected %v to equal %v", service, expectedService) - } - return false, ret, nil - }) - - s.Builder.Sync() - }, - CheckFn: func(t *testing.T, s *solverFixture, args ...interface{}) { - resp := args[0].(*v1.Service) - err := args[1] - if resp == nil && err == nil { - t.Errorf("unexpected service = nil") - t.Fail() - return - } - services, err := s.Solver.serviceLister.List(labels.NewSelector()) - if err != nil { - t.Errorf("unexpected error listing services: %v", err) - t.Fail() - return - } - if len(services) != 1 { - t.Errorf("unexpected %d services in lister: %+v", len(services), services) - t.Fail() - return - } - if !reflect.DeepEqual(services[0], resp) { - t.Errorf("Expected %v to equal %v", services[0], resp) - } - if services[0].Spec.Type != v1.ServiceTypeClusterIP { - t.Errorf("expected service type %q, but was %q", v1.ServiceTypeClusterIP, services[0].Spec.Type) - } - }, - Err: false, }, "http-01 gateway httpRoute challenge without a service type should default to NodePort": { - Challenge: &cmacme.Challenge{ - Spec: cmacme.ChallengeSpec{ - DNSName: "test.com", - Solver: cmacme.ACMEChallengeSolver{ - HTTP01: &cmacme.ACMEChallengeSolverHTTP01{ - GatewayHTTPRoute: &cmacme.ACMEChallengeSolverHTTP01GatewayHTTPRoute{}, - }, - }, - }, - }, - PreFn: func(t *testing.T, s *solverFixture) { - expectedService, err := buildService(s.Challenge) - if err != nil { - t.Errorf("expectedService returned an error whilst building test fixture: %v", err) - } - // create a reactor that fails the test if a service is created - s.Builder.FakeKubeClient().PrependReactor("create", "services", func(action coretesting.Action) (handled bool, ret runtime.Object, err error) { - service := action.(coretesting.CreateAction).GetObject().(*v1.Service) - // clear service name as we don't know it yet in the expectedService - service.Name = "" - if !reflect.DeepEqual(service, expectedService) { - t.Errorf("Expected %v to equal %v", service, expectedService) - } - return false, ret, nil - }) - - s.Builder.Sync() + chal: func(chal *cmacme.Challenge) *cmacme.Challenge { + chal.Spec.Solver.HTTP01.Ingress = nil + chal.Spec.Solver.HTTP01.GatewayHTTPRoute = &cmacme.ACMEChallengeSolverHTTP01GatewayHTTPRoute{} + return chal + }(chal.DeepCopy()), + builder: &testpkg.Builder{ + ExpectedActions: []testpkg.Action{testpkg.NewAction(coretesting.NewCreateAction(corev1.SchemeGroupVersion.WithResource("services"), testNamespace, service))}, }, - CheckFn: func(t *testing.T, s *solverFixture, args ...interface{}) { - resp := args[0].(*v1.Service) - err := args[1] - if resp == nil && err == nil { - t.Errorf("unexpected service = nil") - t.Fail() - return - } - services, err := s.Solver.serviceLister.List(labels.NewSelector()) - if err != nil { - t.Errorf("unexpected error listing services: %v", err) - t.Fail() - return - } - if len(services) != 1 { - t.Errorf("unexpected %d services in lister: %+v", len(services), services) - t.Fail() - return - } - if !reflect.DeepEqual(services[0], resp) { - t.Errorf("Expected %v to equal %v", services[0], resp) - } - if services[0].Spec.Type != v1.ServiceTypeNodePort { - t.Errorf("Blank service type should default to NodePort, but was \"%s\"", services[0].Spec.Type) - } - }, - Err: false, }, "http-01 gateway httpRoute challenge with a service type specified should end up on the generated solver service": { - Challenge: &cmacme.Challenge{ - Spec: cmacme.ChallengeSpec{ - DNSName: "test.com", - Solver: cmacme.ACMEChallengeSolver{ - HTTP01: &cmacme.ACMEChallengeSolverHTTP01{ - GatewayHTTPRoute: &cmacme.ACMEChallengeSolverHTTP01GatewayHTTPRoute{ - ServiceType: v1.ServiceTypeClusterIP, - }, - }, - }, - }, - }, - PreFn: func(t *testing.T, s *solverFixture) { - expectedService, err := buildService(s.Challenge) - if err != nil { - t.Errorf("expectedService returned an error whilst building test fixture: %v", err) - } - // create a reactor that fails the test if a service is created - s.Builder.FakeKubeClient().PrependReactor("create", "services", func(action coretesting.Action) (handled bool, ret runtime.Object, err error) { - service := action.(coretesting.CreateAction).GetObject().(*v1.Service) - // clear service name as we don't know it yet in the expectedService - service.Name = "" - if !reflect.DeepEqual(service, expectedService) { - t.Errorf("Expected %v to equal %v", service, expectedService) - } - return false, ret, nil - }) - - s.Builder.Sync() - }, - CheckFn: func(t *testing.T, s *solverFixture, args ...interface{}) { - resp := args[0].(*v1.Service) - err := args[1] - if resp == nil && err == nil { - t.Errorf("unexpected service = nil") - t.Fail() - return - } - services, err := s.Solver.serviceLister.List(labels.NewSelector()) - if err != nil { - t.Errorf("unexpected error listing services: %v", err) - t.Fail() - return - } - if len(services) != 1 { - t.Errorf("unexpected %d services in lister: %+v", len(services), services) - t.Fail() - return - } - if !reflect.DeepEqual(services[0], resp) { - t.Errorf("Expected %v to equal %v", services[0], resp) - } - if services[0].Spec.Type != v1.ServiceTypeClusterIP { - t.Errorf("expected service type %q, but was %q", v1.ServiceTypeClusterIP, services[0].Spec.Type) + chal: func(chal *cmacme.Challenge) *cmacme.Challenge { + chal.Spec.Solver.HTTP01.Ingress = nil + chal.Spec.Solver.HTTP01.GatewayHTTPRoute = &cmacme.ACMEChallengeSolverHTTP01GatewayHTTPRoute{ + ServiceType: corev1.ServiceTypeClusterIP, } + return chal + }(chal.DeepCopy()), + builder: &testpkg.Builder{ + ExpectedActions: []testpkg.Action{testpkg.NewAction(coretesting.NewCreateAction(corev1.SchemeGroupVersion.WithResource("services"), testNamespace, func(s *corev1.Service) *corev1.Service { s.Spec.Type = corev1.ServiceTypeClusterIP; return s }(service.DeepCopy())))}, }, - Err: false, }, } - for name, test := range tests { + for name, scenario := range tests { t.Run(name, func(t *testing.T) { - test.Setup(t) - resp, err := test.Solver.ensureService(context.TODO(), test.Challenge) - if err != nil && !test.Err { - t.Errorf("Expected function to not error, but got: %v", err) + scenario.builder.T = t + scenario.builder.InitWithRESTConfig() + s := &Solver{ + Context: scenario.builder.Context, + serviceLister: scenario.builder.HTTP01ResourceMetadataInformersFactory.ForResource(corev1.SchemeGroupVersion.WithResource("services")).Lister(), } - if err == nil && test.Err { - t.Errorf("Expected function to get an error, but got: %v", err) + scenario.builder.Start() + defer scenario.builder.Stop() + _, err := s.ensureService(t.Context(), scenario.chal) + if err != nil != scenario.expectedErr { + t.Fatalf("unexpected error: wants err: %t, got err %v", scenario.expectedErr, err) + } - test.Finish(t, resp, err) + scenario.builder.CheckAndFinish() }) + } } func TestGetServicesForChallenge(t *testing.T) { - const createdServiceKey = "createdService" - tests := map[string]solverFixture{ - "should return one service that matches": { - Challenge: &cmacme.Challenge{ - Spec: cmacme.ChallengeSpec{ - DNSName: "example.com", - Solver: cmacme.ACMEChallengeSolver{ - HTTP01: &cmacme.ACMEChallengeSolverHTTP01{ - Ingress: &cmacme.ACMEChallengeSolverHTTP01Ingress{}, - }, + type testT struct { + builder *testpkg.Builder + chal *cmacme.Challenge + wantedServiceMetas []*metav1.PartialObjectMetadata + expectedErr bool + } + var ( + testNamespace = "foo" + chal = &cmacme.Challenge{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: testNamespace, + }, + Spec: cmacme.ChallengeSpec{ + DNSName: "example.com", + Token: "token", + Key: "key", + Solver: cmacme.ACMEChallengeSolver{ + HTTP01: &cmacme.ACMEChallengeSolverHTTP01{ + Ingress: &cmacme.ACMEChallengeSolverHTTP01Ingress{}, }, }, }, - PreFn: func(t *testing.T, s *solverFixture) { - ing, err := s.Solver.createService(context.TODO(), s.Challenge) - if err != nil { - t.Errorf("error preparing test: %v", err) - } - - s.testResources[createdServiceKey] = ing - s.Builder.Sync() - }, - CheckFn: func(t *testing.T, s *solverFixture, args ...interface{}) { - createdService := s.testResources[createdServiceKey].(*v1.Service) - resp := args[0].([]*v1.Service) - if len(resp) != 1 { - t.Errorf("expected one service to be returned, but got %d", len(resp)) - t.Fail() - return - } - if !reflect.DeepEqual(resp[0], createdService) { - t.Errorf("Expected %v to equal %v", resp[0], createdService) - } - }, - }, - "should not return a service for the same certificate but different domain": { - Challenge: &cmacme.Challenge{ - Spec: cmacme.ChallengeSpec{ - DNSName: "example.com", - Solver: cmacme.ACMEChallengeSolver{ - HTTP01: &cmacme.ACMEChallengeSolverHTTP01{ - Ingress: &cmacme.ACMEChallengeSolverHTTP01Ingress{}, - }, + } + service = &corev1.Service{ + ObjectMeta: metav1.ObjectMeta{ + GenerateName: "cm-acme-http-solver-", + Namespace: testNamespace, + Labels: podLabels(chal), + Annotations: map[string]string{ + "auth.istio.io/8089": "NONE", + }, + OwnerReferences: []metav1.OwnerReference{*metav1.NewControllerRef(chal, challengeGvk)}, + }, + Spec: corev1.ServiceSpec{ + Type: corev1.ServiceTypeNodePort, + Ports: []corev1.ServicePort{ + { + Name: "http", + Port: acmeSolverListenPort, + TargetPort: intstr.FromInt32(acmeSolverListenPort), }, }, + Selector: podLabels(chal), + }, + } + serviceMeta = &metav1.PartialObjectMetadata{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "v1", + Kind: "Service", + }, + ObjectMeta: service.ObjectMeta, + } + ) + tests := map[string]testT{ + "should return one service that matches": { + chal: chal, + builder: &testpkg.Builder{ + PartialMetadataObjects: []runtime.Object{serviceMeta}, }, - PreFn: func(t *testing.T, s *solverFixture) { - differentChallenge := s.Challenge.DeepCopy() - differentChallenge.Spec.DNSName = "invaliddomain" - _, err := s.Solver.createService(context.TODO(), differentChallenge) - if err != nil { - t.Errorf("error preparing test: %v", err) - } - - s.Builder.Sync() - }, - CheckFn: func(t *testing.T, s *solverFixture, args ...interface{}) { - resp := args[0].([]*v1.Service) - if len(resp) != 0 { - t.Errorf("expected zero services to be returned, but got %d", len(resp)) - t.Fail() - return - } - }, + wantedServiceMetas: []*metav1.PartialObjectMetadata{serviceMeta}, }, } - for name, test := range tests { + for name, scenario := range tests { t.Run(name, func(t *testing.T) { - test.Setup(t) - resp, err := test.Solver.getServicesForChallenge(context.TODO(), test.Challenge) - if err != nil && !test.Err { - t.Errorf("Expected function to not error, but got: %v", err) + scenario.builder.T = t + scenario.builder.InitWithRESTConfig() + s := &Solver{ + Context: scenario.builder.Context, + serviceLister: scenario.builder.HTTP01ResourceMetadataInformersFactory.ForResource(corev1.SchemeGroupVersion.WithResource("services")).Lister(), } - if err == nil && test.Err { - t.Errorf("Expected function to get an error, but got: %v", err) + scenario.builder.Start() + defer scenario.builder.Stop() + gotServiceMetas, err := s.getServicesForChallenge(t.Context(), scenario.chal) + if err != nil != scenario.expectedErr { + t.Fatalf("unexpected error: wants err: %t, got err %v", scenario.expectedErr, err) + } - test.Finish(t, resp, err) + assert.ElementsMatch(t, gotServiceMetas, scenario.wantedServiceMetas) + scenario.builder.CheckAndFinish() }) + } } diff --git a/pkg/issuer/acme/http/solver/solver.go b/pkg/issuer/acme/http/solver/solver.go index d5c698a4228..970d365690d 100644 --- a/pkg/issuer/acme/http/solver/solver.go +++ b/pkg/issuer/acme/http/solver/solver.go @@ -19,12 +19,25 @@ package solver import ( "fmt" "net/http" + "net/netip" "path" "strings" + "time" "github.com/go-logr/logr" ) +const ( + // This is intended to mitigate "slowloris" attacks by limiting the time a + // deliberately slow client can spend sending HTTP headers. + // This default value is copied from: + // * kubernetes api-server: + // https://github.com/kubernetes/kubernetes/blob/9e028b40b9e970142191259effe796b3dab39828/staging/src/k8s.io/apiserver/pkg/server/secure_serving.go#L165-L173 + // * controller-runtime: + // https://github.com/kubernetes-sigs/controller-runtime/blob/1ea2be573f7887a9fbd766e9a921c5af344da6eb/pkg/internal/httpserver/server.go#L14 + defaultReadHeaderTimeout = 32 * time.Second +) + type HTTP01Solver struct { ListenPort int @@ -43,9 +56,19 @@ func (h *HTTP01Solver) Listen(log logr.Logger) error { "listen_port", h.ListenPort, ) - handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + h.Server = http.Server{ + Addr: fmt.Sprintf(":%d", h.ListenPort), + Handler: h.challengeHandler(log), + ReadHeaderTimeout: defaultReadHeaderTimeout, // Mitigation for G112: Potential slowloris attack + } + + return h.Server.ListenAndServe() +} + +func (h *HTTP01Solver) challengeHandler(log logr.Logger) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { // extract vars from the request - host := strings.Split(r.Host, ":")[0] + host := parseHost(r.Host) basePath := path.Dir(r.URL.EscapedPath()) token := path.Base(r.URL.EscapedPath()) @@ -54,7 +77,9 @@ func (h *HTTP01Solver) Listen(log logr.Logger) error { "path", r.URL.EscapedPath(), "base_path", basePath, "token", token, + "headers", r.Header, ) + if r.URL.EscapedPath() == "/" || r.URL.EscapedPath() == "/healthz" { log.Info("responding OK to health check") w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate") @@ -88,12 +113,22 @@ func (h *HTTP01Solver) Listen(log logr.Logger) error { w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate") w.WriteHeader(http.StatusOK) fmt.Fprint(w, h.Key) - }) + } +} - h.Server = http.Server{ - Addr: fmt.Sprintf(":%d", h.ListenPort), - Handler: handler, +func parseHost(s string) string { + // ip v4/v6 with port + addrPort, err := netip.ParseAddrPort(s) + if err == nil { + return addrPort.Addr().String() } - return h.Server.ListenAndServe() + // ip v4/v6 without port + addr, err := netip.ParseAddr(s) + if err == nil { + return addr.String() + } + + host := strings.Split(s, ":") + return host[0] } diff --git a/pkg/issuer/acme/http/solver/solver_test.go b/pkg/issuer/acme/http/solver/solver_test.go new file mode 100644 index 00000000000..ff325c38635 --- /dev/null +++ b/pkg/issuer/acme/http/solver/solver_test.go @@ -0,0 +1,141 @@ +/* +Copyright 2020 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. +*/ + +package solver + +import ( + "net/http" + "net/http/httptest" + "testing" + + "github.com/go-logr/logr" +) + +func TestSolver(t *testing.T) { + cases := map[string]struct { + solverPort int + solverDomain string + solverToken string + solverKey string + requestTarget string + expectedResponseCode int + }{ + "return ok if healthcheck url - /": { + requestTarget: "/", + expectedResponseCode: http.StatusOK, + }, + "return ok if healthcheck url - /healthz": { + requestTarget: "/", + expectedResponseCode: http.StatusOK, + }, + "return not found if not-challenge url reached": { + requestTarget: "/test", + expectedResponseCode: http.StatusNotFound, + }, + "return not found if tokens do not match": { + solverDomain: "www.example.com", + solverToken: "not-secret", + requestTarget: "http://www.example.com" + HTTPChallengePath + "/secret", + expectedResponseCode: http.StatusNotFound, + }, + "return not found if domains do not match": { + solverDomain: "www.example2.com", + solverToken: "secret", + requestTarget: "http://www.example.com" + HTTPChallengePath + "/secret", + expectedResponseCode: http.StatusNotFound, + }, + "return ok if domain and token match": { + solverDomain: "www.example.com", + solverToken: "secret", + solverKey: "test-key", + requestTarget: "http://www.example.com" + HTTPChallengePath + "/secret", + expectedResponseCode: http.StatusOK, + }, + "return ok with ipv4": { + solverPort: 8080, + solverDomain: "192.168.1.1", + solverToken: "secret", + solverKey: "test-key", + requestTarget: "http://192.168.1.1:8080" + HTTPChallengePath + "/secret", + expectedResponseCode: http.StatusOK, + }, + "return ok with ipv4 when request goes through proxy": { + solverPort: 8080, + solverDomain: "192.168.1.1", + solverToken: "secret", + solverKey: "test-key", + requestTarget: "http://192.168.1.1:80" + HTTPChallengePath + "/secret", + expectedResponseCode: http.StatusOK, + }, + "return ok with ipv4 without specified port in the request": { + solverPort: 80, + solverDomain: "192.168.1.1", + solverToken: "secret", + solverKey: "test-key", + requestTarget: "http://192.168.1.1" + HTTPChallengePath + "/secret", + expectedResponseCode: http.StatusOK, + }, + "return ok with ipv6": { + solverPort: 1234, + solverDomain: "2001:db8:3333:4444:5555:6666:7777:8888", + solverToken: "secret", + solverKey: "test-key", + requestTarget: "http://[2001:db8:3333:4444:5555:6666:7777:8888]:1234" + HTTPChallengePath + "/secret", + expectedResponseCode: http.StatusOK, + }, + "return ok with ipv6 - 2": { + solverPort: 1234, + solverDomain: "2a00:8a00:4000:435::13a", + solverToken: "secret", + solverKey: "test-key", + requestTarget: "http://[2a00:8a00:4000:435::13a]:1234" + HTTPChallengePath + "/secret", + expectedResponseCode: http.StatusOK, + }, + "return ok with ipv6 without specified port in the request": { + solverPort: 80, + solverDomain: "2001:db8:3333:4444:5555:6666:7777:8888", + solverToken: "secret", + solverKey: "test-key", + requestTarget: "http://2001:db8:3333:4444:5555:6666:7777:8888" + HTTPChallengePath + "/secret", + expectedResponseCode: http.StatusOK, + }, + } + + for name, tc := range cases { + t.Run(name, func(t *testing.T) { + solver := HTTP01Solver{ + ListenPort: tc.solverPort, + Domain: tc.solverDomain, + Token: tc.solverToken, + Key: tc.solverKey, + } + + r := httptest.NewRequest(http.MethodGet, tc.requestTarget, nil) + w := httptest.NewRecorder() + + solver.challengeHandler(logr.Discard()).ServeHTTP(w, r) + + if w.Code != tc.expectedResponseCode { + t.Errorf("Expected response code %d, got %d", tc.expectedResponseCode, w.Code) + } + response := w.Body.String() + if tc.solverKey != "" && response != tc.solverKey { + t.Errorf("Expected response body %q, got %q", tc.solverKey, response) + } + + }) + } +} diff --git a/pkg/issuer/acme/http/util_test.go b/pkg/issuer/acme/http/util_test.go index a9a63671635..28f76a6f414 100644 --- a/pkg/issuer/acme/http/util_test.go +++ b/pkg/issuer/acme/http/util_test.go @@ -40,7 +40,7 @@ type solverFixture struct { // This is useful if you want to load the clientset with some resources *after* the // fixture has been created. PreFn func(*testing.T, *solverFixture) - // CheckFn should performs checks to ensure the output of the test is as expected. + // CheckFn should perform checks to ensure the output of the test is as expected. // Optional additional values may be provided, which represent the output of the // function under test. CheckFn func(*testing.T, *solverFixture, ...interface{}) diff --git a/pkg/issuer/acme/setup.go b/pkg/issuer/acme/setup.go index 4438f767645..84f6ed349e6 100644 --- a/pkg/issuer/acme/setup.go +++ b/pkg/issuer/acme/setup.go @@ -19,12 +19,13 @@ package acme import ( "context" "crypto/rsa" + "crypto/sha256" + "crypto/x509" "encoding/base64" "fmt" "net/url" "strings" - acmeapi "golang.org/x/crypto/acme" corev1 "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -38,6 +39,7 @@ import ( logf "github.com/cert-manager/cert-manager/pkg/logs" "github.com/cert-manager/cert-manager/pkg/util/errors" "github.com/cert-manager/cert-manager/pkg/util/pki" + acmeapi "github.com/cert-manager/cert-manager/third_party/forked/acme" ) const ( @@ -67,86 +69,122 @@ const ( // Setup will verify an existing ACME registration, or create one if not // already registered. -func (a *Acme) Setup(ctx context.Context) error { - log := logf.FromContext(ctx) +func (a *Acme) Setup(ctx context.Context, issuer v1.GenericIssuer) error { + result := a.setup(ctx, issuer) + apiutil.SetIssuerCondition( + issuer, + issuer.GetGeneration(), + v1.IssuerConditionReady, + result.status, + result.reason, + result.message, + ) + + return result.err +} + +type setupResult struct { + err error + + status cmmeta.ConditionStatus + reason string + message string +} - // Correct reason and message for issuer's Ready condition must be always set - // before returning from this function. Status must be set if not false. - status := cmmeta.ConditionFalse - var reason, msg string - defer func() { - apiutil.SetIssuerCondition(a.issuer, - a.issuer.GetGeneration(), - v1.IssuerConditionReady, - status, - reason, - msg) - }() +func (a *Acme) setup(ctx context.Context, issuer v1.GenericIssuer) setupResult { + log := logf.FromContext(ctx) // check if user has specified a v1 account URL, and set a status condition if so. - if newURL, ok := acmev1ToV2Mappings[a.issuer.GetSpec().ACME.Server]; ok { - reason = errorInvalidConfig - msg = fmt.Sprintf(messageTemplateUpdateToV2, a.issuer.GetSpec().ACME.Server, newURL) + if newURL, ok := acmev1ToV2Mappings[issuer.GetSpec().ACME.Server]; ok { + msg := fmt.Sprintf(messageTemplateUpdateToV2, issuer.GetSpec().ACME.Server, newURL) // Return nil, because we do not want to re-queue an Issuer with an invalid spec. - return nil + return setupResult{ + err: nil, + + status: cmmeta.ConditionFalse, + reason: errorInvalidConfig, + message: msg, + } } // if the namespace field is not set, we are working on a ClusterIssuer resource // therefore we should check for the ACME private key in the 'cluster resource namespace'. - ns := a.issuer.GetObjectMeta().Namespace - if ns == "" { - ns = a.clusterResourceNamespace - } + ns := a.resourceNamespace(issuer) - log = logf.WithRelatedResourceName(log, a.issuer.GetSpec().ACME.PrivateKey.Name, ns, "Secret") + log = logf.WithRelatedResourceName(log, issuer.GetSpec().ACME.PrivateKey.Name, ns, "Secret") // attempt to obtain the existing private key from the apiserver. // if it does not exist then we generate one // if it contains invalid data, warn the user and return without error. // if any other error occurs, return it and retry. - privateKeySelector := acme.PrivateKeySelector(a.issuer.GetSpec().ACME.PrivateKey) + privateKeySelector := acme.PrivateKeySelector(issuer.GetSpec().ACME.PrivateKey) pk, err := a.keyFromSecret(ctx, ns, privateKeySelector.Name, privateKeySelector.Key) switch { - case !a.issuer.GetSpec().ACME.DisableAccountKeyGeneration && apierrors.IsNotFound(err): + case !issuer.GetSpec().ACME.DisableAccountKeyGeneration && apierrors.IsNotFound(err): log.V(logf.InfoLevel).Info("generating acme account private key") pk, err = a.createAccountPrivateKey(ctx, privateKeySelector, ns) if err != nil { - msg = messageAccountRegistrationFailed + err.Error() - reason = errorAccountRegistrationFailed - return fmt.Errorf(msg) + msg := messageAccountRegistrationFailed + err.Error() + return setupResult{ + err: fmt.Errorf("%s", msg), + + status: cmmeta.ConditionFalse, + reason: errorAccountRegistrationFailed, + message: msg, + } } // We clear the ACME account URI as we have generated a new private key - a.issuer.GetStatus().ACMEStatus().URI = "" + issuer.GetStatus().ACMEStatus().URI = "" - case a.issuer.GetSpec().ACME.DisableAccountKeyGeneration && apierrors.IsNotFound(err): + case issuer.GetSpec().ACME.DisableAccountKeyGeneration && apierrors.IsNotFound(err): wrapErr := fmt.Errorf("%s%s%v", messageAccountVerificationFailed, messageNoSecretKeyGenerationDisabled, err) - reason = errorAccountVerificationFailed - msg = wrapErr.Error() // TODO: we should not re-queue the Issuer here as a resync will happen // when the user adds the Secret or changes Issuer's spec. Should be // fixed by https://github.com/cert-manager/cert-manager/issues/4004 - return wrapErr + return setupResult{ + err: wrapErr, + status: cmmeta.ConditionFalse, + reason: errorAccountVerificationFailed, + message: wrapErr.Error(), + } case errors.IsInvalidData(err): - reason = errorAccountVerificationFailed - msg = fmt.Sprintf("%s%v", messageInvalidPrivateKey, err) - return nil + msg := fmt.Sprintf("%s%v", messageInvalidPrivateKey, err) + return setupResult{ + err: nil, + + status: cmmeta.ConditionFalse, + reason: errorAccountVerificationFailed, + message: msg, + } case err != nil: - reason = errorAccountVerificationFailed - msg = messageAccountVerificationFailed + err.Error() - return fmt.Errorf(msg) + msg := messageAccountVerificationFailed + err.Error() + return setupResult{ + err: fmt.Errorf("%s", msg), + + status: cmmeta.ConditionFalse, + reason: errorAccountVerificationFailed, + message: msg, + } } rsaPk, ok := pk.(*rsa.PrivateKey) if !ok { - reason = errorAccountVerificationFailed - msg = fmt.Sprintf(messageTemplateNotRSA, - a.issuer.GetSpec().ACME.PrivateKey.Name) - return nil + msg := fmt.Sprintf(messageTemplateNotRSA, + issuer.GetSpec().ACME.PrivateKey.Name) + return setupResult{ + err: nil, + + status: cmmeta.ConditionFalse, + reason: errorAccountVerificationFailed, + message: msg, + } } + isPKChecksumSame := a.accountRegistry.IsKeyCheckSumCached(issuer.GetStatus().ACMEStatus().LastPrivateKeyHash, rsaPk) + // TODO: don't always clear the client cache. // In future we should intelligently manage items in the account cache // and remove them when the corresponding issuer is updated/deleted. @@ -154,9 +192,14 @@ func (a *Acme) Setup(ctx context.Context) error { // probably don't want other controllers to use its client from the cache. // We could therefore move the removing of the client up to the start of // this function. - a.accountRegistry.RemoveClient(string(a.issuer.GetUID())) - httpClient := accounts.BuildHTTPClient(a.metrics, a.issuer.GetSpec().ACME.SkipTLSVerify) - cl := a.clientBuilder(httpClient, *a.issuer.GetSpec().ACME, rsaPk, a.userAgent) + a.accountRegistry.RemoveClient(string(issuer.GetUID())) + + cl := a.clientBuilder(accounts.NewClientOptions{ + SkipTLSVerify: issuer.GetSpec().ACME.SkipTLSVerify, + CABundle: issuer.GetSpec().ACME.CABundle, + Server: issuer.GetSpec().ACME.Server, + PrivateKey: rsaPk, + }) // TODO: perform a complex check to determine whether we need to verify // the existing registration with the ACME server. @@ -166,27 +209,36 @@ func (a *Acme) Setup(ctx context.Context) error { // the most recent copy of the Issuer and Secret resource we have checked // already. - rawServerURL := a.issuer.GetSpec().ACME.Server + rawServerURL := issuer.GetSpec().ACME.Server parsedServerURL, err := url.Parse(rawServerURL) if err != nil { - reason = errorInvalidURL - msg = fmt.Sprintf(messageTemplateFailedToParseURL, rawServerURL, err) - a.recorder.Event(a.issuer, corev1.EventTypeWarning, errorInvalidURL, msg) + msg := fmt.Sprintf(messageTemplateFailedToParseURL, rawServerURL, err) + a.recorder.Event(issuer, corev1.EventTypeWarning, errorInvalidURL, msg) // absorb errors as retrying will not help resolve this error - return nil + return setupResult{ + err: nil, + + status: cmmeta.ConditionFalse, + reason: errorInvalidURL, + message: msg, + } } - rawAccountURL := a.issuer.GetStatus().ACMEStatus().URI + rawAccountURL := issuer.GetStatus().ACMEStatus().URI parsedAccountURL, err := url.Parse(rawAccountURL) if err != nil { - reason = errorInvalidURL - msg = fmt.Sprintf(messageTemplateFailedToParseAccountURL, rawAccountURL, err) - a.recorder.Event(a.issuer, corev1.EventTypeWarning, errorInvalidURL, msg) + msg := fmt.Sprintf(messageTemplateFailedToParseAccountURL, rawAccountURL, err) + a.recorder.Event(issuer, corev1.EventTypeWarning, errorInvalidURL, msg) // absorb errors as retrying will not help resolve this error - return nil - } + return setupResult{ + err: nil, - hasReadyCondition := apiutil.IssuerHasCondition(a.issuer, v1.IssuerCondition{ + status: cmmeta.ConditionFalse, + reason: errorInvalidURL, + message: msg, + } + } + hasReadyCondition := apiutil.IssuerHasCondition(issuer, v1.IssuerCondition{ Type: v1.IssuerConditionReady, Status: cmmeta.ConditionTrue, }) @@ -196,49 +248,64 @@ func (a *Acme) Setup(ctx context.Context) error { // we skip re-checking the account status to save excess calls to the // ACME api. if hasReadyCondition && - a.issuer.GetStatus().ACMEStatus().URI != "" && + issuer.GetStatus().ACMEStatus().URI != "" && parsedAccountURL.Host == parsedServerURL.Host && - a.issuer.GetStatus().ACMEStatus().LastRegisteredEmail == a.issuer.GetSpec().ACME.Email { + issuer.GetStatus().ACMEStatus().LastRegisteredEmail == issuer.GetSpec().ACME.Email && + isPKChecksumSame { log.V(logf.InfoLevel).Info("skipping re-verifying ACME account as cached registration " + "details look sufficient") - // Updating issuer's Ready condition here will ensure that observed - // generation gets bumped correctly if this re-sync was triggered by a - // spec change. Last transition time on the condition will not be modified. - // TODO: perhaps we should retrieve the existing message and reason. - reason = successAccountRegistered - msg = messageAccountRegistered - status = cmmeta.ConditionTrue - // ensure the cached client in the account registry is up to date - a.accountRegistry.AddClient(httpClient, string(a.issuer.GetUID()), *a.issuer.GetSpec().ACME, rsaPk, a.userAgent) - return nil + a.accountRegistry.AddClient(string(issuer.GetUID()), accounts.NewClientOptions{ + SkipTLSVerify: issuer.GetSpec().ACME.SkipTLSVerify, + CABundle: issuer.GetSpec().ACME.CABundle, + Server: issuer.GetSpec().ACME.Server, + PrivateKey: rsaPk, + }) + + return setupResult{ + err: nil, + + status: cmmeta.ConditionTrue, + reason: successAccountRegistered, + message: messageAccountRegistered, + } } if parsedAccountURL.Host != parsedServerURL.Host { log.V(logf.InfoLevel).Info("ACME server URL host and ACME private key registration " + "host differ. Re-checking ACME account registration") - a.issuer.GetStatus().ACMEStatus().URI = "" + issuer.GetStatus().ACMEStatus().URI = "" } var eabAccount *acmeapi.ExternalAccountBinding - if eabObj := a.issuer.GetSpec().ACME.ExternalAccountBinding; eabObj != nil { - eabKey, err := a.getEABKey(ctx, ns) + if eabObj := issuer.GetSpec().ACME.ExternalAccountBinding; eabObj != nil { + eabKey, err := a.getEABKey(ctx, ns, eabObj.Key) switch { // Do not re-try if we fail to get the MAC key as it does not exist at the reference. case apierrors.IsNotFound(err), errors.IsInvalidData(err): log.Error(err, "failed to verify ACME account") - reason = errorAccountRegistrationFailed - msg = messageAccountRegistrationFailed + err.Error() - a.recorder.Event(a.issuer, corev1.EventTypeWarning, + msg := messageAccountRegistrationFailed + err.Error() + a.recorder.Event(issuer, corev1.EventTypeWarning, errorAccountRegistrationFailed, msg) - return nil + return setupResult{ + err: nil, + + status: cmmeta.ConditionFalse, + reason: errorAccountRegistrationFailed, + message: msg, + } case err != nil: - reason = errorAccountRegistrationFailed - msg = messageAccountRegistrationFailed + err.Error() - return fmt.Errorf(msg) + msg := messageAccountRegistrationFailed + err.Error() + return setupResult{ + err: fmt.Errorf("%s", msg), + + status: cmmeta.ConditionFalse, + reason: errorAccountRegistrationFailed, + message: msg, + } } // set the external account binding @@ -249,19 +316,24 @@ func (a *Acme) Setup(ctx context.Context) error { } // register an ACME account or retrieve it if it already exists. - account, err := a.registerAccount(ctx, cl, eabAccount) + account, err := a.registerAccount(ctx, cl, issuer.GetSpec().ACME.Email, eabAccount) if err != nil { // TODO: this error could be from an account registration or an attempt - // to retrieve an existing account- perhaps we should log different + // to retrieve an existing account - perhaps we should log different // messages in those two scenarios. - reason = errorAccountRegistrationFailed - msg = messageAccountRegistrationFailed + err.Error() + msg := messageAccountRegistrationFailed + err.Error() log.Error(err, "failed to register an ACME account") acmeErr, ok := err.(*acmeapi.Error) // If this is not an ACME error, we will simply return it and retry later if !ok { - return err + return setupResult{ + err: err, + + status: cmmeta.ConditionFalse, + reason: errorAccountRegistrationFailed, + message: msg, + } } // If the status code is 400 (BadRequest), we will *not* retry this registration @@ -270,27 +342,44 @@ func (a *Acme) Setup(ctx context.Context) error { if acmeErr.StatusCode >= 400 && acmeErr.StatusCode < 500 { log.Error(acmeErr, "skipping retrying account registration as a "+ "BadRequest response was returned from the ACME server") - return nil + return setupResult{ + err: nil, + + status: cmmeta.ConditionFalse, + reason: errorAccountRegistrationFailed, + message: msg, + } } // Otherwise if we receive anything other than a 400, we will retry. - return err + return setupResult{ + err: err, + + status: cmmeta.ConditionFalse, + reason: errorAccountRegistrationFailed, + message: msg, + } } // if we got an account successfully, we must check if the registered // email is the same as in the issuer spec - specEmail := a.issuer.GetSpec().ACME.Email + specEmail := issuer.GetSpec().ACME.Email account, registeredEmail, err := ensureEmailUpToDate(ctx, cl, account, specEmail) if err != nil { - reason = errorAccountUpdateFailed - msg = messageAccountUpdateFailed + err.Error() + msg := messageAccountUpdateFailed + err.Error() log.Error(err, "failed to update ACME account") - a.recorder.Event(a.issuer, corev1.EventTypeWarning, errorAccountUpdateFailed, msg) + a.recorder.Event(issuer, corev1.EventTypeWarning, errorAccountUpdateFailed, msg) acmeErr, ok := err.(*acmeapi.Error) // If this is not an ACME error, we will simply return it and retry later if !ok { - return err + return setupResult{ + err: err, + + status: cmmeta.ConditionFalse, + reason: errorAccountUpdateFailed, + message: msg, + } } // If the status code is 400 (BadRequest), we will *not* retry this registration @@ -299,23 +388,47 @@ func (a *Acme) Setup(ctx context.Context) error { if acmeErr.StatusCode >= 400 && acmeErr.StatusCode < 500 { log.Error(acmeErr, "skipping updating account email as a "+ "BadRequest response was returned from the ACME server") - return nil + return setupResult{ + err: nil, + + status: cmmeta.ConditionFalse, + reason: errorAccountUpdateFailed, + message: msg, + } } // Otherwise if we receive anything other than a 400, we will retry. - return err + return setupResult{ + err: err, + + status: cmmeta.ConditionFalse, + reason: errorAccountUpdateFailed, + message: msg, + } } log.V(logf.InfoLevel).Info("verified existing registration with ACME server") - status = cmmeta.ConditionTrue - reason = successAccountRegistered - msg = messageAccountRegistered - a.issuer.GetStatus().ACMEStatus().URI = account.URI - a.issuer.GetStatus().ACMEStatus().LastRegisteredEmail = registeredEmail + privateKeyBytes := x509.MarshalPKCS1PrivateKey(rsaPk) + checksum := sha256.Sum256(privateKeyBytes) + checksumString := base64.StdEncoding.EncodeToString(checksum[:]) + issuer.GetStatus().ACMEStatus().URI = account.URI + issuer.GetStatus().ACMEStatus().LastRegisteredEmail = registeredEmail + issuer.GetStatus().ACMEStatus().LastPrivateKeyHash = checksumString // ensure the cached client in the account registry is up to date - a.accountRegistry.AddClient(httpClient, string(a.issuer.GetUID()), *a.issuer.GetSpec().ACME, rsaPk, a.userAgent) + a.accountRegistry.AddClient(string(issuer.GetUID()), accounts.NewClientOptions{ + SkipTLSVerify: issuer.GetSpec().ACME.SkipTLSVerify, + CABundle: issuer.GetSpec().ACME.CABundle, + Server: issuer.GetSpec().ACME.Server, + PrivateKey: rsaPk, + }) - return nil + return setupResult{ + err: nil, + + status: cmmeta.ConditionTrue, + reason: successAccountRegistered, + message: messageAccountRegistered, + } } func ensureEmailUpToDate(ctx context.Context, cl client.Interface, acc *acmeapi.Account, specEmail string) (*acmeapi.Account, string, error) { @@ -353,10 +466,10 @@ func ensureEmailUpToDate(ctx context.Context, cl client.Interface, acc *acmeapi. // account with the clients private key already exists, it will attempt to look // up and verify the corresponding account, and will return that. If this fails // due to a not found error it will register a new account with the given key. -func (a *Acme) registerAccount(ctx context.Context, cl client.Interface, eabAccount *acmeapi.ExternalAccountBinding) (*acmeapi.Account, error) { +func (a *Acme) registerAccount(ctx context.Context, cl client.Interface, acmeEmail string, eabAccount *acmeapi.ExternalAccountBinding) (*acmeapi.Account, error) { emailurl := []string(nil) - if a.issuer.GetSpec().ACME.Email != "" { - emailurl = []string{fmt.Sprintf("mailto:%s", strings.ToLower(a.issuer.GetSpec().ACME.Email))} + if acmeEmail != "" { + emailurl = []string{fmt.Sprintf("mailto:%s", strings.ToLower(acmeEmail))} } acc := &acmeapi.Account{ @@ -381,8 +494,7 @@ func (a *Acme) registerAccount(ctx context.Context, cl client.Interface, eabAcco return acc, nil } -func (a *Acme) getEABKey(ctx context.Context, ns string) ([]byte, error) { - eab := a.issuer.GetSpec().ACME.ExternalAccountBinding.Key +func (a *Acme) getEABKey(ctx context.Context, ns string, eab cmmeta.SecretKeySelector) ([]byte, error) { sec, err := a.secretsClient.Secrets(ns).Get(ctx, eab.Name, metav1.GetOptions{}) // Surface IsNotFound API error to not cause re-sync if apierrors.IsNotFound(err) { @@ -423,6 +535,9 @@ func (a *Acme) createAccountPrivateKey(ctx context.Context, sel cmmeta.SecretKey ObjectMeta: metav1.ObjectMeta{ Name: sel.Name, Namespace: ns, + Labels: map[string]string{ + "app.kubernetes.io/managed-by": "cert-manager", + }, }, Data: map[string][]byte{ sel.Key: pki.EncodePKCS1PrivateKey(accountPrivKey), diff --git a/pkg/issuer/acme/setup_test.go b/pkg/issuer/acme/setup_test.go index 050596e5449..4bac269a44c 100644 --- a/pkg/issuer/acme/setup_test.go +++ b/pkg/issuer/acme/setup_test.go @@ -21,13 +21,12 @@ import ( "crypto" "crypto/rsa" "fmt" - "net/http" "net/url" "reflect" + "slices" "testing" "time" - acmeapi "golang.org/x/crypto/acme" corev1 "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -41,11 +40,11 @@ import ( cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" controllertest "github.com/cert-manager/cert-manager/pkg/controller/test" - "github.com/cert-manager/cert-manager/pkg/util" "github.com/cert-manager/cert-manager/pkg/util/errors" "github.com/cert-manager/cert-manager/pkg/util/pki" "github.com/cert-manager/cert-manager/test/unit/coreclients" "github.com/cert-manager/cert-manager/test/unit/gen" + acmeapi "github.com/cert-manager/cert-manager/third_party/forked/acme" ) func TestAcme_Setup(t *testing.T) { @@ -253,6 +252,7 @@ func TestAcme_Setup(t *testing.T) { gen.SetIssuerACMEAccountURL(acmev2Prod), gen.SetIssuerACMEEmail(someEmail), gen.SetIssuerACMELastRegisteredEmail(someEmail), + gen.SetIssuerACMELastPrivateKeyHash(someString), gen.AddIssuerCondition( *gen.IssuerConditionFrom(readyTrueCondition, gen.SetIssuerConditionStatus(cmmeta.ConditionTrue)))), @@ -535,9 +535,12 @@ func TestAcme_Setup(t *testing.T) { RemoveClientFunc: func(string) { removeClientWasCalled = true }, - AddClientFunc: func(string, cmacme.ACMEIssuer, *rsa.PrivateKey, string) { + AddClientFunc: func(string, accounts.NewClientOptions) { addClientWasCalled = true }, + IsKeyCheckSumCachedFunc: func(lastPrivateKeyHash string, privateKey *rsa.PrivateKey) bool { + return true + }, } // Mock ACME client. @@ -558,7 +561,9 @@ func TestAcme_Setup(t *testing.T) { // Mock events recorder. recorder := new(controllertest.FakeRecorder) a := Acme{ - issuer: test.issuer, + resourceNamespace: func(iss cmapi.GenericIssuer) string { + return iss.GetNamespace() + }, secretsClient: secretsClient, accountRegistry: ar, keyFromSecret: kfs, @@ -571,7 +576,7 @@ func TestAcme_Setup(t *testing.T) { apiutil.Clock = fakeclock // Verify that an error is/is not returned as expected. - gotErr := a.Setup(context.Background()) + gotErr := a.Setup(t.Context(), test.issuer) if gotErr == nil && test.wantsErr { t.Errorf("Expected error %v, got %v", test.wantsErr, gotErr) } @@ -598,7 +603,7 @@ func TestAcme_Setup(t *testing.T) { } // Verify issuer's state after Setup was called. - gotConditions := a.issuer.GetStatus().Conditions + gotConditions := test.issuer.GetStatus().Conditions // Issuer can only have a single condition, so no need to sort the // conditions. if !reflect.DeepEqual(gotConditions, test.expectedConditions) { @@ -607,7 +612,7 @@ func TestAcme_Setup(t *testing.T) { } // Verify that the expected events were recorded. - if !util.EqualSorted(test.expectedEvents, recorder.Events) { + if !slices.Equal(test.expectedEvents, recorder.Events) { t.Errorf("Expected events:\n%+#v\ngot:%+#v", test.expectedEvents, recorder.Events) @@ -625,7 +630,7 @@ func keyFromSecretMockBuilder(wasCalled *bool, key crypto.Signer, err error) key } func clientBuilderMock(cl acmecl.Interface) accounts.NewClientFunc { - return func(*http.Client, cmacme.ACMEIssuer, *rsa.PrivateKey, string) acmecl.Interface { + return func(_ accounts.NewClientOptions) acmecl.Interface { return cl } } diff --git a/pkg/issuer/ca/ca.go b/pkg/issuer/ca/ca.go index 760b457f5d5..f835f8862c8 100644 --- a/pkg/issuer/ca/ca.go +++ b/pkg/issuer/ca/ca.go @@ -17,10 +17,8 @@ limitations under the License. package ca import ( - corelisters "k8s.io/client-go/listers/core/v1" - + internalinformers "github.com/cert-manager/cert-manager/internal/informers" apiutil "github.com/cert-manager/cert-manager/pkg/api/util" - v1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" "github.com/cert-manager/cert-manager/pkg/controller" "github.com/cert-manager/cert-manager/pkg/issuer" ) @@ -30,23 +28,15 @@ import ( // used to sign certificates. type CA struct { *controller.Context - issuer v1.GenericIssuer - secretsLister corelisters.SecretLister - - // Namespace in which to read resources related to this Issuer from. - // For Issuers, this will be the namespace of the Issuer. - // For ClusterIssuers, this will be the cluster resource namespace. - resourceNamespace string + secretsLister internalinformers.SecretLister } -func NewCA(ctx *controller.Context, issuer v1.GenericIssuer) (issuer.Interface, error) { - secretsLister := ctx.KubeSharedInformerFactory.Core().V1().Secrets().Lister() +func NewCA(ctx *controller.Context) (issuer.Interface, error) { + secretsLister := ctx.KubeSharedInformerFactory.Secrets().Lister() return &CA{ - Context: ctx, - issuer: issuer, - secretsLister: secretsLister, - resourceNamespace: ctx.IssuerOptions.ResourceNamespace(issuer), + Context: ctx, + secretsLister: secretsLister, }, nil } diff --git a/pkg/issuer/ca/setup.go b/pkg/issuer/ca/setup.go index 7f95088e018..70bd0df1202 100644 --- a/pkg/issuer/ca/setup.go +++ b/pkg/issuer/ca/setup.go @@ -40,40 +40,42 @@ const ( ) // Setup verifies signing CA. -func (c *CA) Setup(ctx context.Context) error { +func (c *CA) Setup(ctx context.Context, issuer v1.GenericIssuer) error { log := logf.FromContext(ctx, "setup") - cert, err := kube.SecretTLSCert(ctx, c.secretsLister, c.resourceNamespace, c.issuer.GetSpec().CA.SecretName) + resourceNamespace := c.ResourceNamespace(issuer) + + cert, err := kube.SecretTLSCert(ctx, c.secretsLister, resourceNamespace, issuer.GetSpec().CA.SecretName) if err != nil { log.Error(err, "error getting signing CA TLS certificate") s := messageErrorGetKeyPair + err.Error() - c.Recorder.Event(c.issuer, corev1.EventTypeWarning, errorGetKeyPair, s) - apiutil.SetIssuerCondition(c.issuer, c.issuer.GetGeneration(), v1.IssuerConditionReady, cmmeta.ConditionFalse, errorGetKeyPair, s) + c.Recorder.Event(issuer, corev1.EventTypeWarning, errorGetKeyPair, s) + apiutil.SetIssuerCondition(issuer, issuer.GetGeneration(), v1.IssuerConditionReady, cmmeta.ConditionFalse, errorGetKeyPair, s) return err } - _, err = kube.SecretTLSKey(ctx, c.secretsLister, c.resourceNamespace, c.issuer.GetSpec().CA.SecretName) + _, err = kube.SecretTLSKey(ctx, c.secretsLister, resourceNamespace, issuer.GetSpec().CA.SecretName) if err != nil { log.Error(err, "error getting signing CA private key") s := messageErrorGetKeyPair + err.Error() - c.Recorder.Event(c.issuer, corev1.EventTypeWarning, errorGetKeyPair, s) - apiutil.SetIssuerCondition(c.issuer, c.issuer.GetGeneration(), v1.IssuerConditionReady, cmmeta.ConditionFalse, errorGetKeyPair, s) + c.Recorder.Event(issuer, corev1.EventTypeWarning, errorGetKeyPair, s) + apiutil.SetIssuerCondition(issuer, issuer.GetGeneration(), v1.IssuerConditionReady, cmmeta.ConditionFalse, errorGetKeyPair, s) return err } - log = logf.WithRelatedResourceName(log, c.issuer.GetSpec().CA.SecretName, c.resourceNamespace, "Secret") + log = logf.WithRelatedResourceName(log, issuer.GetSpec().CA.SecretName, resourceNamespace, "Secret") if !cert.IsCA { s := messageErrorGetKeyPair + "certificate is not a CA" log.Error(nil, "signing certificate is not a CA") - c.Recorder.Event(c.issuer, corev1.EventTypeWarning, errorInvalidKeyPair, s) - apiutil.SetIssuerCondition(c.issuer, c.issuer.GetGeneration(), v1.IssuerConditionReady, cmmeta.ConditionFalse, errorInvalidKeyPair, s) + c.Recorder.Event(issuer, corev1.EventTypeWarning, errorInvalidKeyPair, s) + apiutil.SetIssuerCondition(issuer, issuer.GetGeneration(), v1.IssuerConditionReady, cmmeta.ConditionFalse, errorInvalidKeyPair, s) // Don't return an error here as there is nothing more we can do return nil } log.V(logf.DebugLevel).Info("signing CA verified") - c.Recorder.Event(c.issuer, corev1.EventTypeNormal, successKeyPairVerified, messageKeyPairVerified) - apiutil.SetIssuerCondition(c.issuer, c.issuer.GetGeneration(), v1.IssuerConditionReady, cmmeta.ConditionTrue, successKeyPairVerified, messageKeyPairVerified) + c.Recorder.Event(issuer, corev1.EventTypeNormal, successKeyPairVerified, messageKeyPairVerified) + apiutil.SetIssuerCondition(issuer, issuer.GetGeneration(), v1.IssuerConditionReady, cmmeta.ConditionTrue, successKeyPairVerified, messageKeyPairVerified) return nil } diff --git a/pkg/issuer/factory.go b/pkg/issuer/factory.go index 3e030568078..4e67f3cd5fc 100644 --- a/pkg/issuer/factory.go +++ b/pkg/issuer/factory.go @@ -27,7 +27,7 @@ import ( // IssuerConstructor constructs an issuer given an Issuer resource and a Context. // An error will be returned if the appropriate issuer is not registered. -type IssuerConstructor func(*controller.Context, v1.GenericIssuer) (Interface, error) +type IssuerConstructor func(*controller.Context) (Interface, error) var ( constructors = make(map[string]IssuerConstructor) @@ -77,7 +77,7 @@ func (f *factory) IssuerFor(issuer v1.GenericIssuer) (Interface, error) { constructorsLock.RLock() defer constructorsLock.RUnlock() if constructor, ok := constructors[issuerType]; ok { - return constructor(f.ctx, issuer) + return constructor(f.ctx) } return nil, fmt.Errorf("issuer '%s' not registered", issuerType) diff --git a/pkg/issuer/fake/helper.go b/pkg/issuer/fake/helper.go index 51a193c0768..cd622568c9c 100644 --- a/pkg/issuer/fake/helper.go +++ b/pkg/issuer/fake/helper.go @@ -23,11 +23,11 @@ import ( ) type Helper struct { - GetGenericIssuerFunc func(ref cmmeta.ObjectReference, ns string) (cmapi.GenericIssuer, error) + GetGenericIssuerFunc func(ref cmmeta.IssuerReference, ns string) (cmapi.GenericIssuer, error) } var _ issuerpkg.Helper = &Helper{} -func (f *Helper) GetGenericIssuer(ref cmmeta.ObjectReference, ns string) (cmapi.GenericIssuer, error) { +func (f *Helper) GetGenericIssuer(ref cmmeta.IssuerReference, ns string) (cmapi.GenericIssuer, error) { return f.GetGenericIssuerFunc(ref, ns) } diff --git a/pkg/issuer/fake/issuer.go b/pkg/issuer/fake/issuer.go index 301adacd59b..225d1ce67d5 100644 --- a/pkg/issuer/fake/issuer.go +++ b/pkg/issuer/fake/issuer.go @@ -24,17 +24,17 @@ import ( ) type Issuer struct { - SetupFunc func(context.Context) error + SetupFunc func(context.Context, cmapi.GenericIssuer) error IssueFunc func(context.Context, *cmapi.Certificate) (*issuer.IssueResponse, error) } var _ issuer.Interface = &Issuer{} -// Setup initialises the issuer. This may include registering accounts with +// Setup initializes the issuer. This may include registering accounts with // a service, creating a CA and storing it somewhere, or verifying // credentials and authorization with a remote server. -func (i *Issuer) Setup(ctx context.Context) error { - return i.SetupFunc(ctx) +func (i *Issuer) Setup(ctx context.Context, issuer cmapi.GenericIssuer) error { + return i.SetupFunc(ctx, issuer) } // Issue attempts to issue a certificate as described by the certificate diff --git a/pkg/issuer/helper.go b/pkg/issuer/helper.go index 528410cfa0e..80efbfde408 100644 --- a/pkg/issuer/helper.go +++ b/pkg/issuer/helper.go @@ -27,7 +27,7 @@ import ( // Helper is an interface that defines a method that returns an issuer for the given // IssuerRef and namespace. type Helper interface { - GetGenericIssuer(ref cmmeta.ObjectReference, ns string) (cmapi.GenericIssuer, error) + GetGenericIssuer(ref cmmeta.IssuerReference, ns string) (cmapi.GenericIssuer, error) } // Type Helper provides a set of commonly useful functions for use when building @@ -54,7 +54,7 @@ func NewHelper(issuerLister cmlisters.IssuerLister, clusterIssuerLister cmlister // This namespace will be used to read the Issuer resource. // In most cases, the ns parameter should be set to the namespace of the resource // that defines the IssuerRef (i.e. the namespace of the Certificate resource). -func (h *helperImpl) GetGenericIssuer(ref cmmeta.ObjectReference, ns string) (cmapi.GenericIssuer, error) { +func (h *helperImpl) GetGenericIssuer(ref cmmeta.IssuerReference, ns string) (cmapi.GenericIssuer, error) { switch ref.Kind { case "", cmapi.IssuerKind: return h.issuerLister.Issuers(ns).Get(ref.Name) diff --git a/pkg/issuer/helper_test.go b/pkg/issuer/helper_test.go index 70aa7eee4c1..e4d37ff57a5 100644 --- a/pkg/issuer/helper_test.go +++ b/pkg/issuer/helper_test.go @@ -99,7 +99,7 @@ func TestGetGenericIssuer(t *testing.T) { stopCh := make(chan struct{}) defer close(stopCh) - actual, err := c.GetGenericIssuer(cmmeta.ObjectReference{Name: row.Name, Kind: row.Kind}, row.Namespace) + actual, err := c.GetGenericIssuer(cmmeta.IssuerReference{Name: row.Name, Kind: row.Kind}, row.Namespace) if err != nil && !row.Err { t.Errorf("Expected no error, but got: %s", err) } diff --git a/pkg/issuer/issuer.go b/pkg/issuer/issuer.go index 01e8fed2acc..8c5fe6b1498 100644 --- a/pkg/issuer/issuer.go +++ b/pkg/issuer/issuer.go @@ -18,13 +18,15 @@ package issuer import ( "context" + + v1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" ) type Interface interface { - // Setup initialises the issuer. This may include registering accounts with + // Setup initializes the issuer. This may include registering accounts with // a service, creating a CA and storing it somewhere, or verifying // credentials and authorization with a remote server. - Setup(ctx context.Context) error + Setup(ctx context.Context, issuer v1.GenericIssuer) error } type IssueResponse struct { diff --git a/pkg/issuer/selfsigned/selfsigned.go b/pkg/issuer/selfsigned/selfsigned.go index d2afcaec27e..93207fe1660 100644 --- a/pkg/issuer/selfsigned/selfsigned.go +++ b/pkg/issuer/selfsigned/selfsigned.go @@ -17,28 +17,25 @@ limitations under the License. package selfsigned import ( - corelisters "k8s.io/client-go/listers/core/v1" - + internalinformers "github.com/cert-manager/cert-manager/internal/informers" apiutil "github.com/cert-manager/cert-manager/pkg/api/util" - v1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" "github.com/cert-manager/cert-manager/pkg/controller" "github.com/cert-manager/cert-manager/pkg/issuer" ) // SelfSigned is an Issuer implementation the simply self-signs Certificates. +// For more info see: https://cert-manager.io/docs/configuration/selfsigned/ type SelfSigned struct { *controller.Context - issuer v1.GenericIssuer - secretsLister corelisters.SecretLister + secretsLister internalinformers.SecretLister } -func NewSelfSigned(ctx *controller.Context, issuer v1.GenericIssuer) (issuer.Interface, error) { - secretsLister := ctx.KubeSharedInformerFactory.Core().V1().Secrets().Lister() +func NewSelfSigned(ctx *controller.Context) (issuer.Interface, error) { + secretsLister := ctx.KubeSharedInformerFactory.Secrets().Lister() return &SelfSigned{ Context: ctx, - issuer: issuer, secretsLister: secretsLister, }, nil } diff --git a/pkg/issuer/selfsigned/setup.go b/pkg/issuer/selfsigned/setup.go index 1cbf4e2bd32..edb60494736 100644 --- a/pkg/issuer/selfsigned/setup.go +++ b/pkg/issuer/selfsigned/setup.go @@ -28,7 +28,7 @@ const ( successReady = "IsReady" ) -func (c *SelfSigned) Setup(ctx context.Context) error { - apiutil.SetIssuerCondition(c.issuer, c.issuer.GetGeneration(), v1.IssuerConditionReady, cmmeta.ConditionTrue, successReady, "") +func (c *SelfSigned) Setup(ctx context.Context, issuer v1.GenericIssuer) error { + apiutil.SetIssuerCondition(issuer, issuer.GetGeneration(), v1.IssuerConditionReady, cmmeta.ConditionTrue, successReady, "") return nil } diff --git a/pkg/issuer/vault/setup.go b/pkg/issuer/vault/setup.go index 41bcade0985..d4f8987a82a 100644 --- a/pkg/issuer/vault/setup.go +++ b/pkg/issuer/vault/setup.go @@ -20,6 +20,8 @@ import ( "context" "fmt" + "k8s.io/klog/v2" + vaultinternal "github.com/cert-manager/cert-manager/internal/vault" apiutil "github.com/cert-manager/cert-manager/pkg/api/util" v1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" @@ -33,90 +35,115 @@ const ( errorVault = "VaultError" - messageVaultClientInitFailed = "Failed to initialize Vault client: " - messageVaultStatusVerificationFailed = "Vault is not initialized or is sealed" - messageVaultConfigRequired = "Vault config cannot be empty" - messageServerAndPathRequired = "Vault server and path are required fields" - messageAuthFieldsRequired = "Vault tokenSecretRef, appRole, or kubernetes is required" - messageMultipleAuthFieldsSet = "Multiple auth methods cannot be set on the same Vault issuer" + messageVaultClientInitFailed = "Failed to initialize Vault client" + messageVaultInitializedAndUnsealedFailed = "Failed to verify Vault is initialized and unsealed" + messageVaultConfigRequired = "Vault config cannot be empty" + messageServerAndPathRequired = "Vault server and path are required fields" + messageAuthFieldsRequired = "Vault tokenSecretRef, appRole, clientCertificate, or kubernetes is required" + messageMultipleAuthFieldsSet = "Multiple auth methods cannot be set on the same Vault issuer" - messageKubeAuthFieldsRequired = "Vault Kubernetes auth requires both role and secretRef.name" + messageKubeAuthRoleRequired = "Vault Kubernetes auth requires a role to be set" + messageKubeAuthEitherRequired = "Vault Kubernetes auth requires either secretRef.name or serviceAccountRef.name to be set" + messageKubeAuthSingleRequired = "Vault Kubernetes auth cannot be used with both secretRef.name and serviceAccountRef.name" messageTokenAuthNameRequired = "Vault Token auth requires tokenSecretRef.name" messageAppRoleAuthFieldsRequired = "Vault AppRole auth requires both roleId and tokenSecretRef.name" + messageAppRoleAuthKeyRequired = "Vault AppRole auth requires secretRef.key" ) // Setup creates a new Vault client and attempts to authenticate with the Vault instance and sets the issuer's conditions to reflect the success of the setup. -func (v *Vault) Setup(ctx context.Context) error { - if v.issuer.GetSpec().Vault == nil { - logf.V(logf.WarnLevel).Infof("%s: %s", v.issuer.GetObjectMeta().Name, messageVaultConfigRequired) - apiutil.SetIssuerCondition(v.issuer, v.issuer.GetGeneration(), v1.IssuerConditionReady, cmmeta.ConditionFalse, errorVault, messageVaultConfigRequired) +func (v *Vault) Setup(ctx context.Context, issuer v1.GenericIssuer) error { + if issuer.GetSpec().Vault == nil { + logf.FromContext(ctx).V(logf.WarnLevel).Info(messageVaultConfigRequired, "issuer", klog.KObj(issuer)) + apiutil.SetIssuerCondition(issuer, issuer.GetGeneration(), v1.IssuerConditionReady, cmmeta.ConditionFalse, errorVault, messageVaultConfigRequired) return nil } // check if Vault server info is specified. - if v.issuer.GetSpec().Vault.Server == "" || - v.issuer.GetSpec().Vault.Path == "" { - logf.V(logf.WarnLevel).Infof("%s: %s", v.issuer.GetObjectMeta().Name, messageServerAndPathRequired) - apiutil.SetIssuerCondition(v.issuer, v.issuer.GetGeneration(), v1.IssuerConditionReady, cmmeta.ConditionFalse, errorVault, messageServerAndPathRequired) + if issuer.GetSpec().Vault.Server == "" || + issuer.GetSpec().Vault.Path == "" { + logf.FromContext(ctx).V(logf.WarnLevel).Info(messageServerAndPathRequired, "issuer", klog.KObj(issuer)) + apiutil.SetIssuerCondition(issuer, issuer.GetGeneration(), v1.IssuerConditionReady, cmmeta.ConditionFalse, errorVault, messageServerAndPathRequired) return nil } - tokenAuth := v.issuer.GetSpec().Vault.Auth.TokenSecretRef - appRoleAuth := v.issuer.GetSpec().Vault.Auth.AppRole - kubeAuth := v.issuer.GetSpec().Vault.Auth.Kubernetes + tokenAuth := issuer.GetSpec().Vault.Auth.TokenSecretRef + appRoleAuth := issuer.GetSpec().Vault.Auth.AppRole + clientCertificateAuth := issuer.GetSpec().Vault.Auth.ClientCertificate + kubeAuth := issuer.GetSpec().Vault.Auth.Kubernetes // check if at least one auth method is specified. - if tokenAuth == nil && appRoleAuth == nil && kubeAuth == nil { - logf.V(logf.WarnLevel).Infof("%s: %s", v.issuer.GetObjectMeta().Name, messageAuthFieldsRequired) - apiutil.SetIssuerCondition(v.issuer, v.issuer.GetGeneration(), v1.IssuerConditionReady, cmmeta.ConditionFalse, errorVault, messageAuthFieldsRequired) + if tokenAuth == nil && appRoleAuth == nil && clientCertificateAuth == nil && kubeAuth == nil { + logf.FromContext(ctx).V(logf.WarnLevel).Info(messageAuthFieldsRequired, "issuer", klog.KObj(issuer)) + apiutil.SetIssuerCondition(issuer, issuer.GetGeneration(), v1.IssuerConditionReady, cmmeta.ConditionFalse, errorVault, messageAuthFieldsRequired) return nil } - // check only one auth method set - if (tokenAuth != nil && appRoleAuth != nil) || - (tokenAuth != nil && kubeAuth != nil) || - (appRoleAuth != nil && kubeAuth != nil) { - logf.V(logf.WarnLevel).Infof("%s: %s", v.issuer.GetObjectMeta().Name, messageMultipleAuthFieldsSet) - apiutil.SetIssuerCondition(v.issuer, v.issuer.GetGeneration(), v1.IssuerConditionReady, cmmeta.ConditionFalse, errorVault, messageMultipleAuthFieldsSet) + // check only one auth method is set + if !((tokenAuth != nil && appRoleAuth == nil && clientCertificateAuth == nil && kubeAuth == nil) || + (tokenAuth == nil && appRoleAuth != nil && clientCertificateAuth == nil && kubeAuth == nil) || + (tokenAuth == nil && appRoleAuth == nil && clientCertificateAuth != nil && kubeAuth == nil) || + (tokenAuth == nil && appRoleAuth == nil && clientCertificateAuth == nil && kubeAuth != nil)) { + logf.FromContext(ctx).V(logf.WarnLevel).Info(messageMultipleAuthFieldsSet, "issuer", klog.KObj(issuer)) + apiutil.SetIssuerCondition(issuer, issuer.GetGeneration(), v1.IssuerConditionReady, cmmeta.ConditionFalse, errorVault, messageMultipleAuthFieldsSet) return nil } // check if all mandatory Vault Token fields are set. if tokenAuth != nil && len(tokenAuth.Name) == 0 { - logf.V(logf.WarnLevel).Infof("%s: %s", v.issuer.GetObjectMeta().Name, messageTokenAuthNameRequired) - apiutil.SetIssuerCondition(v.issuer, v.issuer.GetGeneration(), v1.IssuerConditionReady, cmmeta.ConditionFalse, errorVault, messageTokenAuthNameRequired) + logf.FromContext(ctx).V(logf.WarnLevel).Info(messageTokenAuthNameRequired, "issuer", klog.KObj(issuer)) + apiutil.SetIssuerCondition(issuer, issuer.GetGeneration(), v1.IssuerConditionReady, cmmeta.ConditionFalse, errorVault, messageTokenAuthNameRequired) return nil } // check if all mandatory Vault appRole fields are set. if appRoleAuth != nil && (len(appRoleAuth.RoleId) == 0 || len(appRoleAuth.SecretRef.Name) == 0) { - logf.V(logf.WarnLevel).Infof("%s: %s", v.issuer.GetObjectMeta().Name, messageAppRoleAuthFieldsRequired) - apiutil.SetIssuerCondition(v.issuer, v.issuer.GetGeneration(), v1.IssuerConditionReady, cmmeta.ConditionFalse, errorVault, messageAppRoleAuthFieldsRequired) + logf.FromContext(ctx).V(logf.WarnLevel).Info(messageAppRoleAuthFieldsRequired, "issuer", klog.KObj(issuer)) + apiutil.SetIssuerCondition(issuer, issuer.GetGeneration(), v1.IssuerConditionReady, cmmeta.ConditionFalse, errorVault, messageAppRoleAuthFieldsRequired) + return nil + } + if appRoleAuth != nil && len(appRoleAuth.SecretRef.Key) == 0 { + logf.FromContext(ctx).V(logf.WarnLevel).Info(messageAppRoleAuthKeyRequired, "issuer", klog.KObj(issuer)) + apiutil.SetIssuerCondition(issuer, issuer.GetGeneration(), v1.IssuerConditionReady, cmmeta.ConditionFalse, errorVault, messageAppRoleAuthKeyRequired) + return nil + } + + // When using the Kubernetes auth, giving a role is mandatory. + if kubeAuth != nil && len(kubeAuth.Role) == 0 { + logf.FromContext(ctx).V(logf.WarnLevel).Info(messageKubeAuthRoleRequired, "issuer", klog.KObj(issuer)) + apiutil.SetIssuerCondition(issuer, issuer.GetGeneration(), v1.IssuerConditionReady, cmmeta.ConditionFalse, errorVault, messageKubeAuthRoleRequired) return nil } - // check if all mandatory Vault Kubernetes fields are set. - if kubeAuth != nil && (len(kubeAuth.SecretRef.Name) == 0 || len(kubeAuth.Role) == 0) { - logf.V(logf.WarnLevel).Infof("%s: %s", v.issuer.GetObjectMeta().Name, messageKubeAuthFieldsRequired) - apiutil.SetIssuerCondition(v.issuer, v.issuer.GetGeneration(), v1.IssuerConditionReady, cmmeta.ConditionFalse, errorVault, messageKubeAuthFieldsRequired) + // When using the Kubernetes auth, you must either set secretRef or + // serviceAccountRef. + if kubeAuth != nil && (kubeAuth.SecretRef.Name == "" && kubeAuth.ServiceAccountRef == nil) { + logf.FromContext(ctx).V(logf.WarnLevel).Info(messageKubeAuthEitherRequired, "issuer", klog.KObj(issuer)) + apiutil.SetIssuerCondition(issuer, issuer.GetGeneration(), v1.IssuerConditionReady, cmmeta.ConditionFalse, errorVault, messageKubeAuthEitherRequired) return nil } - client, err := vaultinternal.New(v.resourceNamespace, v.secretsLister, v.issuer) + // When using the Kubernetes auth, you can't use secretRef and + // serviceAccountRef simultaneously. + if kubeAuth != nil && (kubeAuth.SecretRef.Name != "" && kubeAuth.ServiceAccountRef != nil) { + logf.FromContext(ctx).V(logf.WarnLevel).Info(messageKubeAuthSingleRequired, "issuer", klog.KObj(issuer)) + apiutil.SetIssuerCondition(issuer, issuer.GetGeneration(), v1.IssuerConditionReady, cmmeta.ConditionFalse, errorVault, messageKubeAuthSingleRequired) + return nil + } + + client, err := vaultinternal.New(ctx, v.ResourceNamespace(issuer), v.createTokenFn, v.secretsLister, issuer) if err != nil { - s := messageVaultClientInitFailed + err.Error() - logf.V(logf.WarnLevel).Infof("%s: %s", v.issuer.GetObjectMeta().Name, s) - apiutil.SetIssuerCondition(v.issuer, v.issuer.GetGeneration(), v1.IssuerConditionReady, cmmeta.ConditionFalse, errorVault, s) + logf.FromContext(ctx).V(logf.WarnLevel).Info(messageVaultClientInitFailed, "err", err, "issuer", klog.KObj(issuer)) + apiutil.SetIssuerCondition(issuer, issuer.GetGeneration(), v1.IssuerConditionReady, cmmeta.ConditionFalse, errorVault, fmt.Sprintf("%s: %s", messageVaultClientInitFailed, err.Error())) return err } if err := client.IsVaultInitializedAndUnsealed(); err != nil { - logf.V(logf.WarnLevel).Infof("%s: %s: error: %s", v.issuer.GetObjectMeta().Name, messageVaultStatusVerificationFailed, err.Error()) - apiutil.SetIssuerCondition(v.issuer, v.issuer.GetGeneration(), v1.IssuerConditionReady, cmmeta.ConditionFalse, errorVault, messageVaultStatusVerificationFailed) - return fmt.Errorf(messageVaultStatusVerificationFailed) + logf.FromContext(ctx).V(logf.WarnLevel).Info(messageVaultInitializedAndUnsealedFailed, "err", err, "issuer", klog.KObj(issuer)) + apiutil.SetIssuerCondition(issuer, issuer.GetGeneration(), v1.IssuerConditionReady, cmmeta.ConditionFalse, errorVault, fmt.Sprintf("%s: %s", messageVaultInitializedAndUnsealedFailed, err.Error())) + return err } - logf.Log.V(logf.DebugLevel).Info(messageVaultVerified) - apiutil.SetIssuerCondition(v.issuer, v.issuer.GetGeneration(), v1.IssuerConditionReady, cmmeta.ConditionTrue, successVaultVerified, messageVaultVerified) + logf.FromContext(ctx).V(logf.DebugLevel).Info(messageVaultVerified, "issuer", klog.KObj(issuer)) + apiutil.SetIssuerCondition(issuer, issuer.GetGeneration(), v1.IssuerConditionReady, cmmeta.ConditionTrue, successVaultVerified, messageVaultVerified) return nil } diff --git a/pkg/issuer/vault/setup_test.go b/pkg/issuer/vault/setup_test.go new file mode 100644 index 00000000000..6d84bdbf50c --- /dev/null +++ b/pkg/issuer/vault/setup_test.go @@ -0,0 +1,492 @@ +/* +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. +*/ + +package vault + +import ( + "context" + "fmt" + "net/http" + "net/http/httptest" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + authv1 "k8s.io/api/authentication/v1" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/validation/field" + corelisters "k8s.io/client-go/listers/core/v1" + + internalapi "github.com/cert-manager/cert-manager/internal/apis/certmanager" + internalv1 "github.com/cert-manager/cert-manager/internal/apis/certmanager/v1" + "github.com/cert-manager/cert-manager/internal/apis/certmanager/validation" + vaultinternal "github.com/cert-manager/cert-manager/internal/vault" + v1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" + cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" + cmfake "github.com/cert-manager/cert-manager/pkg/client/clientset/versioned/fake" + "github.com/cert-manager/cert-manager/pkg/controller" + testlisters "github.com/cert-manager/cert-manager/test/unit/listers" +) + +func TestVault_Setup(t *testing.T) { + // Create a mock Vault HTTP server. + vaultServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.URL.Path == "/v1/auth/approle/login" || r.URL.Path == "/v1/auth/kubernetes/login" || r.URL.Path == "/v1/auth/cert/login" { + w.WriteHeader(http.StatusOK) + if _, err := w.Write([]byte(`{"auth":{"client_token": "5b1a0318-679c-9c45-e5c6-d1b9a9035d49"}}`)); err != nil { + t.Fatal(err) + } + } + })) + defer vaultServer.Close() + + tests := []struct { + name string + givenIssuer v1.IssuerConfig + expectCond string + expectErr string + webhookReject bool + mockGetSecret *corev1.Secret + mockGetSecretErr error + }{ + { + name: "developer mistake: the vault field is empty", + givenIssuer: v1.IssuerConfig{ + Vault: nil, + }, + expectCond: "Ready False: VaultError: Vault config cannot be empty", + webhookReject: true, + }, + { + name: "path is missing", + givenIssuer: v1.IssuerConfig{ + Vault: &v1.VaultIssuer{ + Server: "https://vault.example.com", + }, + }, + expectCond: "Ready False: VaultError: Vault server and path are required fields", + webhookReject: true, + }, + { + name: "server is missing", + givenIssuer: v1.IssuerConfig{ + Vault: &v1.VaultIssuer{ + Path: "pki_int", + }, + }, + expectCond: "Ready False: VaultError: Vault server and path are required fields", + webhookReject: true, + }, + { + name: "auth.appRole, auth.kubernetes, and auth.tokenSecretRef are mutually exclusive", + givenIssuer: v1.IssuerConfig{ + Vault: &v1.VaultIssuer{ + + Path: "pki_int", + Server: "https://vault.example.com", + Auth: v1.VaultAuth{ + AppRole: &v1.VaultAppRole{ + SecretRef: cmmeta.SecretKeySelector{ + LocalObjectReference: cmmeta.LocalObjectReference{ + Name: "cert-manager", + }, + }, + }, + Kubernetes: &v1.VaultKubernetesAuth{ + SecretRef: cmmeta.SecretKeySelector{ + LocalObjectReference: cmmeta.LocalObjectReference{ + Name: "cert-manager", + }, + }, + Path: "kubernetes", + Role: "cert-manager", + }, + TokenSecretRef: &cmmeta.SecretKeySelector{ + LocalObjectReference: cmmeta.LocalObjectReference{ + Name: "cert-manager", + }, + Key: "token", + }, + }, + }, + }, + expectCond: "Ready False: VaultError: Multiple auth methods cannot be set on the same Vault issuer", + webhookReject: true, + }, + { + name: "valid auth.appRole", + givenIssuer: v1.IssuerConfig{ + Vault: &v1.VaultIssuer{ + Path: "pki_int", + Server: vaultServer.URL, + Auth: v1.VaultAuth{ + AppRole: &v1.VaultAppRole{ + RoleId: "cert-manager", + SecretRef: cmmeta.SecretKeySelector{ + LocalObjectReference: cmmeta.LocalObjectReference{ + Name: "cert-manager", + }, + Key: "token", + }, + Path: "approle", + }, + }, + }, + }, + expectCond: "Ready True: VaultVerified: Vault verified", + }, + { + name: "invalid auth.appRole: secretRef.key can be omitted", + givenIssuer: v1.IssuerConfig{ + Vault: &v1.VaultIssuer{ + Path: "pki_int", + Server: "https://vault.example.com", + Auth: v1.VaultAuth{ + AppRole: &v1.VaultAppRole{ + RoleId: "cert-manager", + SecretRef: cmmeta.SecretKeySelector{ + LocalObjectReference: cmmeta.LocalObjectReference{ + Name: "cert-manager", + }, + }, + Path: "approle", + }, + }, + }, + }, + expectCond: "Ready False: VaultError: Vault AppRole auth requires secretRef.key", + }, + { + name: "invalid auth.appRole: roleId is missing", + givenIssuer: v1.IssuerConfig{ + Vault: &v1.VaultIssuer{ + Path: "pki_int", + Server: "https://vault.example.com", + Auth: v1.VaultAuth{ + AppRole: &v1.VaultAppRole{ + SecretRef: cmmeta.SecretKeySelector{ + LocalObjectReference: cmmeta.LocalObjectReference{ + Name: "cert-manager", + }, + }, + }, + }, + }, + }, + expectCond: "Ready False: VaultError: Vault AppRole auth requires both roleId and tokenSecretRef.name", + webhookReject: true, + }, + { + name: "invalid auth.appRole: secretRef.name is missing", + givenIssuer: v1.IssuerConfig{ + Vault: &v1.VaultIssuer{ + Path: "pki_int", + Server: "https://vault.example.com", + Auth: v1.VaultAuth{ + AppRole: &v1.VaultAppRole{ + RoleId: "cert-manager", + }, + }, + }, + }, + expectCond: "Ready False: VaultError: Vault AppRole auth requires both roleId and tokenSecretRef.name", + webhookReject: true, + }, + { + name: "valid auth.kubernetes.secretRef", + givenIssuer: v1.IssuerConfig{ + Vault: &v1.VaultIssuer{ + Path: "pki_int", + Server: vaultServer.URL, + Auth: v1.VaultAuth{ + Kubernetes: &v1.VaultKubernetesAuth{ + Role: "cert-manager", + SecretRef: cmmeta.SecretKeySelector{ + LocalObjectReference: cmmeta.LocalObjectReference{ + Name: "cert-manager", + }, + Key: "token", + }, + }, + }, + }, + }, + expectCond: "Ready True: VaultVerified: Vault verified", + }, + { + name: "invalid auth.kubernetes.secretRef: name is missing", + givenIssuer: v1.IssuerConfig{ + Vault: &v1.VaultIssuer{ + Path: "pki_int", + Server: "https://vault.example.com", + Auth: v1.VaultAuth{ + Kubernetes: &v1.VaultKubernetesAuth{ + Role: "cert-manager", + }, + }, + }, + }, + expectCond: "Ready False: VaultError: Vault Kubernetes auth requires either secretRef.name or serviceAccountRef.name to be set", + webhookReject: true, + }, + { + // The field auth.kubernetes.secretRef.key defaults to 'token' if + // not set. + name: "valid auth.kubernetes.secretRef: key can be left empty and defaults to 'token'", + givenIssuer: v1.IssuerConfig{ + Vault: &v1.VaultIssuer{ + Path: "pki_int", + Server: vaultServer.URL, + Auth: v1.VaultAuth{ + Kubernetes: &v1.VaultKubernetesAuth{ + Role: "cert-manager", + SecretRef: cmmeta.SecretKeySelector{ + LocalObjectReference: cmmeta.LocalObjectReference{ + Name: "cert-manager", + }, + }, + }, + }, + }, + }, + expectCond: "Ready True: VaultVerified: Vault verified", + }, + { + name: "invalid auth.kubernetes: role is missing", + givenIssuer: v1.IssuerConfig{ + Vault: &v1.VaultIssuer{ + Path: "pki_int", + Server: "https://vault.example.com", + Auth: v1.VaultAuth{ + Kubernetes: &v1.VaultKubernetesAuth{ + Role: "", + // We set secretRef.name just for the purpose of + // testing whether the "role" is properly checked. + SecretRef: cmmeta.SecretKeySelector{ + LocalObjectReference: cmmeta.LocalObjectReference{ + Name: "cert-manager", + }, + }, + }, + }, + }, + }, + expectCond: "Ready False: VaultError: Vault Kubernetes auth requires a role to be set", + webhookReject: true, + }, + { + name: "valid auth.kubernetes.serviceAccountRef", + givenIssuer: v1.IssuerConfig{ + Vault: &v1.VaultIssuer{ + Path: "pki_int", + Server: vaultServer.URL, + Auth: v1.VaultAuth{ + Kubernetes: &v1.VaultKubernetesAuth{ + Role: "cert-manager", + ServiceAccountRef: &v1.ServiceAccountRef{ + Name: "cert-manager", + }, + }, + }, + }, + }, + expectCond: "Ready True: VaultVerified: Vault verified", + }, + { + name: "invalid auth.kubernetes: serviceAccountRef and secretRef are both set", + givenIssuer: v1.IssuerConfig{ + Vault: &v1.VaultIssuer{ + Path: "pki_int", + Server: vaultServer.URL, + Auth: v1.VaultAuth{ + Kubernetes: &v1.VaultKubernetesAuth{ + Role: "cert-manager", + ServiceAccountRef: &v1.ServiceAccountRef{ + Name: "cert-manager", + }, + SecretRef: cmmeta.SecretKeySelector{ + LocalObjectReference: cmmeta.LocalObjectReference{ + Name: "cert-manager", + }, + }, + }, + }, + }, + }, + expectCond: "Ready False: VaultError: Vault Kubernetes auth cannot be used with both secretRef.name and serviceAccountRef.name", + webhookReject: true, + }, + { + name: "valid auth.tokenSecretRef", + givenIssuer: v1.IssuerConfig{ + Vault: &v1.VaultIssuer{ + Path: "pki_int", + Server: vaultServer.URL, + Auth: v1.VaultAuth{ + TokenSecretRef: &cmmeta.SecretKeySelector{ + LocalObjectReference: cmmeta.LocalObjectReference{ + Name: "cert-manager", + }, + Key: "token", + }, + }, + }, + }, + expectCond: "Ready True: VaultVerified: Vault verified", + }, + { + // The default value for auth.tokenSecretRef.key is 'token'. This + // behavior is not documented in the API reference, but we keep it + // for backward compatibility. + name: "valid auth.tokenSecretRef: key can be omitted", + givenIssuer: v1.IssuerConfig{ + Vault: &v1.VaultIssuer{ + Path: "pki_int", + Server: vaultServer.URL, + Auth: v1.VaultAuth{ + TokenSecretRef: &cmmeta.SecretKeySelector{ + LocalObjectReference: cmmeta.LocalObjectReference{ + Name: "cert-manager", + }, + Key: "", + }, + }, + }, + }, + expectCond: "Ready True: VaultVerified: Vault verified", + }, + { + name: "server with invalid url should fail to setup", + givenIssuer: v1.IssuerConfig{ + Vault: &v1.VaultIssuer{ + Path: "pki_int", + Server: "https:/vault.example.com", + Auth: v1.VaultAuth{ + TokenSecretRef: &cmmeta.SecretKeySelector{ + LocalObjectReference: cmmeta.LocalObjectReference{ + Name: "cert-manager", + }, + Key: "", + }, + }, + }, + }, + expectErr: "Get \"https:///vault.example.com/v1/sys/health\": http: no Host in request URL", + }, + { + name: "server with leading whitespace should fail to parse", + givenIssuer: v1.IssuerConfig{ + Vault: &v1.VaultIssuer{ + Path: "pki_int", + Server: " https://vault.example.com", + Auth: v1.VaultAuth{ + TokenSecretRef: &cmmeta.SecretKeySelector{ + LocalObjectReference: cmmeta.LocalObjectReference{ + Name: "cert-manager", + }, + Key: "", + }, + }, + }, + }, + expectErr: "error initializing Vault client: parse \" https://vault.example.com\": first path segment in URL cannot contain colon", + }, + { + name: "valid auth.clientCertificate: All fields can be omitted", + givenIssuer: v1.IssuerConfig{ + Vault: &v1.VaultIssuer{ + Path: "pki_int", + Server: vaultServer.URL, + Auth: v1.VaultAuth{ + ClientCertificate: &v1.VaultClientCertificateAuth{}, + }, + }, + }, + expectCond: "Ready True: VaultVerified: Vault verified", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + givenIssuer := &v1.Issuer{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-issuer", + Namespace: "test-namespace", + }, + Spec: v1.IssuerSpec{ + IssuerConfig: tt.givenIssuer, + }, + } + cmclient := cmfake.NewClientset(givenIssuer) + + v := &Vault{ + Context: &controller.Context{CMClient: cmclient}, + createTokenFn: func(ns string) vaultinternal.CreateToken { + return func(ctx context.Context, saName string, req *authv1.TokenRequest, opts metav1.CreateOptions) (*authv1.TokenRequest, error) { + return &authv1.TokenRequest{Status: authv1.TokenRequestStatus{ + Token: "token", + }}, nil + } + }, + secretsLister: &testlisters.FakeSecretLister{ + SecretsFn: func(namespace string) corelisters.SecretNamespaceLister { + return &testlisters.FakeSecretNamespaceLister{ + GetFn: func(name string) (ret *corev1.Secret, err error) { + assert.Equal(t, "cert-manager", name) + assert.Equal(t, "test-namespace", namespace) + return &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{Name: "cert-manager", Namespace: "test-namespace"}, + Data: map[string][]byte{"token": []byte("root")}, + }, nil + }, + } + }, + }, + } + + err := v.Setup(t.Context(), givenIssuer) + if tt.expectErr != "" { + assert.EqualError(t, err, tt.expectErr) + return + } + assert.NoError(t, err) + + // The webhook-side validation of the Vault issuer configuration + // didn't exist for a long time. The only validation that was done + // was the controller-side validation (i.e., the validation that we + // do in setup.go). To prevent the breakage of existing Issuer or + // ClusterIssuers resources due to the webhook-side validation + // suddenly becoming stricter than the controller-side validation, + // we perform the webhook validation too and check that it passes. + converted := internalapi.IssuerConfig{} + err = internalv1.Convert_v1_IssuerConfig_To_certmanager_IssuerConfig(&tt.givenIssuer, &converted, nil) + assert.NoError(t, err) + errlist, _ := validation.ValidateIssuerConfig(&converted, field.NewPath("spec", "vault")) + if tt.webhookReject { + assert.Error(t, errlist.ToAggregate()) + } else { + assert.NoError(t, errlist.ToAggregate()) + } + + if tt.expectCond != "" { + require.Len(t, givenIssuer.Status.Conditions, 1) + assert.Equal(t, tt.expectCond, fmt.Sprintf("%s %s: %s: %s", givenIssuer.Status.Conditions[0].Type, givenIssuer.Status.Conditions[0].Status, givenIssuer.Status.Conditions[0].Reason, givenIssuer.Status.Conditions[0].Message)) + } else { + require.Len(t, givenIssuer.Status.Conditions, 0) + } + }) + } +} diff --git a/pkg/issuer/vault/vault.go b/pkg/issuer/vault/vault.go index 06dc4286650..2f292436fb5 100644 --- a/pkg/issuer/vault/vault.go +++ b/pkg/issuer/vault/vault.go @@ -17,10 +17,9 @@ limitations under the License. package vault import ( - corelisters "k8s.io/client-go/listers/core/v1" - + internalinformers "github.com/cert-manager/cert-manager/internal/informers" + vaultinternal "github.com/cert-manager/cert-manager/internal/vault" apiutil "github.com/cert-manager/cert-manager/pkg/api/util" - v1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" "github.com/cert-manager/cert-manager/pkg/controller" "github.com/cert-manager/cert-manager/pkg/issuer" ) @@ -28,25 +27,21 @@ import ( // Vault Issuer for the certificate authority of Vault type Vault struct { *controller.Context - issuer v1.GenericIssuer - secretsLister corelisters.SecretLister + secretsLister internalinformers.SecretLister - // Namespace in which to read resources related to this Issuer from. - // For Issuers, this will be the namespace of the Issuer. - // For ClusterIssuers, this will be the cluster resource namespace. - resourceNamespace string + // For testing purposes. + createTokenFn func(ns string) vaultinternal.CreateToken } // NewVault returns a new Vault -func NewVault(ctx *controller.Context, issuer v1.GenericIssuer) (issuer.Interface, error) { - secretsLister := ctx.KubeSharedInformerFactory.Core().V1().Secrets().Lister() +func NewVault(ctx *controller.Context) (issuer.Interface, error) { + secretsLister := ctx.KubeSharedInformerFactory.Secrets().Lister() return &Vault{ - Context: ctx, - issuer: issuer, - secretsLister: secretsLister, - resourceNamespace: ctx.IssuerOptions.ResourceNamespace(issuer), + Context: ctx, + secretsLister: secretsLister, + createTokenFn: func(ns string) vaultinternal.CreateToken { return ctx.Client.CoreV1().ServiceAccounts(ns).CreateToken }, }, nil } diff --git a/pkg/issuer/venafi/client/fake/connector.go b/pkg/issuer/venafi/client/fake/connector.go index 0c07794ff44..3999c033480 100644 --- a/pkg/issuer/venafi/client/fake/connector.go +++ b/pkg/issuer/venafi/client/fake/connector.go @@ -17,9 +17,9 @@ limitations under the License. package fake import ( - "github.com/Venafi/vcert/v4/pkg/certificate" - "github.com/Venafi/vcert/v4/pkg/endpoint" - "github.com/Venafi/vcert/v4/pkg/venafi/fake" + "github.com/Venafi/vcert/v5/pkg/certificate" + "github.com/Venafi/vcert/v5/pkg/endpoint" + "github.com/Venafi/vcert/v5/pkg/venafi/fake" ) type Connector struct { diff --git a/pkg/issuer/venafi/client/fake/venafi.go b/pkg/issuer/venafi/client/fake/venafi.go index 45ca05d2c47..fb9e2688fcb 100644 --- a/pkg/issuer/venafi/client/fake/venafi.go +++ b/pkg/issuer/venafi/client/fake/venafi.go @@ -19,7 +19,7 @@ package fake import ( "time" - "github.com/Venafi/vcert/v4/pkg/endpoint" + "github.com/Venafi/vcert/v5/pkg/endpoint" "github.com/cert-manager/cert-manager/pkg/issuer/venafi/client/api" ) diff --git a/pkg/issuer/venafi/client/instrumentedvenaficlient.go b/pkg/issuer/venafi/client/instrumentedvenaficlient.go index 70ce2b2c9a1..90b3df4f3ee 100644 --- a/pkg/issuer/venafi/client/instrumentedvenaficlient.go +++ b/pkg/issuer/venafi/client/instrumentedvenaficlient.go @@ -19,8 +19,8 @@ package client import ( "time" - "github.com/Venafi/vcert/v4/pkg/certificate" - "github.com/Venafi/vcert/v4/pkg/endpoint" + "github.com/Venafi/vcert/v5/pkg/certificate" + "github.com/Venafi/vcert/v5/pkg/endpoint" "github.com/go-logr/logr" logf "github.com/cert-manager/cert-manager/pkg/logs" @@ -35,7 +35,7 @@ type instrumentedConnector struct { var _ connector = instrumentedConnector{} -func newInstumentedConnector(conn connector, metrics *metrics.Metrics, log logr.Logger) connector { +func newInstrumentedConnector(conn connector, metrics *metrics.Metrics, log logr.Logger) connector { return instrumentedConnector{ conn: conn, metrics: metrics, diff --git a/pkg/issuer/venafi/client/request.go b/pkg/issuer/venafi/client/request.go index 928e743ece6..de849e7209a 100644 --- a/pkg/issuer/venafi/client/request.go +++ b/pkg/issuer/venafi/client/request.go @@ -23,14 +23,16 @@ import ( "strings" "time" - "github.com/Venafi/vcert/v4/pkg/certificate" + "github.com/Venafi/vcert/v5/pkg/certificate" + "github.com/Venafi/vcert/v5/pkg/util" + "github.com/Venafi/vcert/v5/pkg/venafi/tpp" "github.com/cert-manager/cert-manager/pkg/issuer/venafi/client/api" "github.com/cert-manager/cert-manager/pkg/util/pki" ) // ErrCustomFieldsType provides a common error structure for an invalid Venafi custom field type -type ErrCustomFieldsType struct { +type ErrCustomFieldsType struct { //nolint:errname Type api.CustomFieldType } @@ -38,7 +40,7 @@ func (err ErrCustomFieldsType) Error() string { return fmt.Sprintf("certificate request contains an invalid Venafi custom fields type: %q", err.Type) } -var ErrorMissingSubject = errors.New("Certificate requests submitted to Venafi issuers must have the 'commonName' field or at least one other subject field set.") +var ErrorMissingSubject = errors.New("Certificate requests submitted to Venafi issuers must have the 'commonName' field or at least one other subject field set.") //nolint:errname // This function sends a request to Venafi to for a signed certificate. // The CSR will be decoded to be validated against the zone configuration policy. @@ -49,6 +51,34 @@ func (v *Venafi) RequestCertificate(csrPEM []byte, duration time.Duration, custo if err != nil { return "", err } + + // If the connector is TPP, we unconditionally reset any prior failed enrollment + // so that we don't get stuck with "Fix any errors, and then click Retry." + // (60% of the time) or "WebSDK CertRequest" (40% of the time). + // + // It would be preferable to only reset when necessary to avoid the extra + // call. We tried that in https://github.com/Venafi/vcert/pull/269. It turns + // out that calling "request" followed by "reset(restart=true)" causes a + // race in TPP. + // + // Unconditionally resetting isn't optimal, but "reset(restart=false)" is + // lightweight. We haven't verified that it doesn't slow things down on + // large TPP instances. + // + // Note that resetting won't affect the existing certificate if one was + // already issued. + if v.tppClient != nil { + // We can't use the instrumented v.vcertClient because its concrete + // value is `instrumentedConnector`, which doesn't give access to the + // *tpp.Connector it wraps. Also, `instrumentedConnector` doesn't + // support `ResetCertificate`. + err := v.tppClient.ResetCertificate(vreq, false) + notFoundErr := &tpp.ErrCertNotFound{} + if err != nil && !errors.As(err, ¬FoundErr) { + return "", err + } + } + return v.vcertClient.RequestCertificate(vreq) } @@ -83,7 +113,7 @@ func (v *Venafi) buildVReq(csrPEM []byte, duration time.Duration, customFields [ return nil, err } - tmpl, err := pki.GenerateTemplateFromCSRPEM(csrPEM, duration, false) + tmpl, err := pki.CertificateTemplateFromCSRPEM(csrPEM) if err != nil { return nil, err } @@ -93,7 +123,7 @@ func (v *Venafi) buildVReq(csrPEM []byte, duration time.Duration, customFields [ } // Create a vcert Request structure - vreq := newVRequest(tmpl) + vreq := newVRequest(tmpl, duration) // Convert over custom fields from our struct type to venafi's vfields, err := convertCustomFieldsToVcert(customFields) @@ -153,8 +183,11 @@ func convertCustomFieldsToVcert(customFields []api.CustomField) ([]certificate.C return out, nil } -func newVRequest(cert *x509.Certificate) *certificate.Request { +func newVRequest(cert *x509.Certificate, duration time.Duration) *certificate.Request { req := certificate.NewRequest(cert) + + req.ValidityDuration = &duration + req.IssuerHint = util.IssuerHintAllIssuers req.ChainOption = certificate.ChainOptionRootLast // overwrite entire Subject block diff --git a/pkg/issuer/venafi/client/request_test.go b/pkg/issuer/venafi/client/request_test.go index 54055381eca..8f7278e6292 100644 --- a/pkg/issuer/venafi/client/request_test.go +++ b/pkg/issuer/venafi/client/request_test.go @@ -18,22 +18,19 @@ package client import ( "crypto" - "crypto/rand" - "crypto/x509" - "crypto/x509/pkix" - "encoding/pem" "errors" "testing" "time" - "github.com/Venafi/vcert/v4/pkg/certificate" - "github.com/Venafi/vcert/v4/pkg/endpoint" - "github.com/Venafi/vcert/v4/pkg/venafi/fake" + "github.com/Venafi/vcert/v5/pkg/certificate" + "github.com/Venafi/vcert/v5/pkg/endpoint" + "github.com/Venafi/vcert/v5/pkg/venafi/fake" "github.com/cert-manager/cert-manager/pkg/issuer/venafi/client/api" internalfake "github.com/cert-manager/cert-manager/pkg/issuer/venafi/client/fake" "github.com/cert-manager/cert-manager/pkg/util" "github.com/cert-manager/cert-manager/pkg/util/pki" + "github.com/cert-manager/cert-manager/test/unit/gen" ) func checkCertificateIssued(t *testing.T, csrPEM []byte, resp []byte) { @@ -77,22 +74,15 @@ func checkCertificateIssued(t *testing.T, csrPEM []byte, resp []byte) { } } -func generateCSR(t *testing.T, sk crypto.Signer, commonName string, dnsNames []string) []byte { - template := x509.CertificateRequest{ - Subject: pkix.Name{ - CommonName: commonName, - }, - SignatureAlgorithm: x509.SHA256WithRSA, - DNSNames: dnsNames, - } - - csrBytes, err := x509.CreateCertificateRequest(rand.Reader, &template, sk) +func generateCSR(t *testing.T, secretKey crypto.Signer, commonName string, dnsNames []string) []byte { + csr, err := gen.CSRWithSigner(secretKey, + gen.SetCSRCommonName(commonName), + gen.SetCSRDNSNames(dnsNames...), + ) if err != nil { - t.Error(err) - t.FailNow() + t.Fatal(err) } - csr := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE REQUEST", Bytes: csrBytes}) return csr } diff --git a/pkg/issuer/venafi/client/venaficlient.go b/pkg/issuer/venafi/client/venaficlient.go index cf684890fc3..da3ecde69ad 100644 --- a/pkg/issuer/venafi/client/venaficlient.go +++ b/pkg/issuer/venafi/client/venaficlient.go @@ -17,32 +17,43 @@ limitations under the License. package client import ( + "crypto/tls" + "crypto/x509" "fmt" + "net" + "net/http" "time" - vcert "github.com/Venafi/vcert/v4" - "github.com/Venafi/vcert/v4/pkg/certificate" - "github.com/Venafi/vcert/v4/pkg/endpoint" - "github.com/Venafi/vcert/v4/pkg/venafi/cloud" - "github.com/Venafi/vcert/v4/pkg/venafi/tpp" + vcert "github.com/Venafi/vcert/v5" + "github.com/Venafi/vcert/v5/pkg/certificate" + "github.com/Venafi/vcert/v5/pkg/endpoint" + "github.com/Venafi/vcert/v5/pkg/venafi/cloud" + "github.com/Venafi/vcert/v5/pkg/venafi/tpp" "github.com/go-logr/logr" - corelisters "k8s.io/client-go/listers/core/v1" + "k8s.io/utils/ptr" + internalinformers "github.com/cert-manager/cert-manager/internal/informers" cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" + cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" "github.com/cert-manager/cert-manager/pkg/issuer/venafi/client/api" "github.com/cert-manager/cert-manager/pkg/metrics" + "github.com/cert-manager/cert-manager/pkg/util" ) const ( - tppUsernameKey = "username" - tppPasswordKey = "password" - tppAccessTokenKey = "access-token" + tppUsernameKey = "username" + tppPasswordKey = "password" + tppAccessTokenKey = "access-token" + tppClientIdKey = "client-id" + defaultTppClientId = "cert-manager.io" + // Setting Scope statically for simplicity + tppScopes = "certificate:manage" defaultAPIKeyKey = "api-key" ) -type VenafiClientBuilder func(namespace string, secretsLister corelisters.SecretLister, - issuer cmapi.GenericIssuer, metrics *metrics.Metrics, logger logr.Logger) (Interface, error) +type VenafiClientBuilder func(namespace string, secretsLister internalinformers.SecretLister, + issuer cmapi.GenericIssuer, metrics *metrics.Metrics, logger logr.Logger, userAgent string) (Interface, error) // Interface implements a Venafi client type Interface interface { @@ -54,13 +65,13 @@ type Interface interface { VerifyCredentials() error } -// Venafi is a implementation of vcert library to manager certificates from TPP or Venafi Cloud +// Venafi is an implementation of vcert library to manager certificates from TPP or Venafi Cloud type Venafi struct { // Namespace in which to read resources related to this Issuer from. // For Issuers, this will be the namespace of the Issuer. // For ClusterIssuers, this will be the cluster resource namespace. namespace string - secretsLister corelisters.SecretLister + secretsLister internalinformers.SecretLister vcertClient connector tppClient *tpp.Connector @@ -75,19 +86,28 @@ type connector interface { ReadZoneConfiguration() (config *endpoint.ZoneConfiguration, err error) RequestCertificate(req *certificate.Request) (requestID string, err error) RetrieveCertificate(req *certificate.Request) (certificates *certificate.PEMCollection, err error) - // TODO: (irbekrm) this method is never used- can it be removed? + // TODO: (irbekrm) this method is never used - can it be removed? RenewCertificate(req *certificate.RenewalRequest) (requestID string, err error) } // New constructs a Venafi client Interface. Errors may be network errors and // should be considered for retrying. -func New(namespace string, secretsLister corelisters.SecretLister, issuer cmapi.GenericIssuer, metrics *metrics.Metrics, logger logr.Logger) (Interface, error) { - cfg, err := configForIssuer(issuer, secretsLister, namespace) +func New(namespace string, secretsLister internalinformers.SecretLister, issuer cmapi.GenericIssuer, metrics *metrics.Metrics, logger logr.Logger, userAgent string) (Interface, error) { + cfg, err := configForIssuer(issuer, secretsLister, namespace, userAgent) if err != nil { return nil, err } - vcertClient, err := vcert.NewClient(cfg) + // Using `false` here ensures we do not immediately authenticate to the + // Venafi backend. Doing so invokes a call which forces the use of APIKey + // on the TPP side. This auth method has been removed since 22.4 of TPP. + // This results in an APIKey usage error. + // Reference code from vcert library which still refers to the APIKey. + // ref: https://github.com/Venafi/vcert/blob/master/pkg/venafi/tpp/connector.go#L137-L146 + // + // cert-manager uses the VerifyCredentials function below after the client + // has been created. + vcertClient, err := vcert.NewClient(cfg, false) if err != nil { return nil, fmt.Errorf("error creating Venafi client: %s", err.Error()) } @@ -106,60 +126,85 @@ func New(namespace string, secretsLister corelisters.SecretLister, issuer cmapi. if ok { cc = c } + default: + return nil, fmt.Errorf("unsupported Venafi connector type: %v", vcertClient.GetType()) } - instrumentedVCertClient := newInstumentedConnector(vcertClient, metrics, logger) + instrumentedVCertClient := newInstrumentedConnector(vcertClient, metrics, logger) - return &Venafi{ + v := &Venafi{ namespace: namespace, secretsLister: secretsLister, vcertClient: instrumentedVCertClient, cloudClient: cc, tppClient: tppc, config: cfg, - }, nil + } + + // Since we did not authenticate when creating the client, authenticate + // now to verify the credentials passed. Ensure that upon leaving this + // function that credentials have been verified. + if err := v.VerifyCredentials(); err != nil { + return nil, err + } + return v, nil } // configForIssuer will convert a cert-manager Venafi issuer into a vcert.Config // that can be used to instantiate an API client. -func configForIssuer(iss cmapi.GenericIssuer, secretsLister corelisters.SecretLister, namespace string) (*vcert.Config, error) { - venCfg := iss.GetSpec().Venafi +func configForIssuer(iss cmapi.GenericIssuer, secretsLister internalinformers.SecretLister, namespace string, userAgent string) (*vcert.Config, error) { + venaCfg := iss.GetSpec().Venafi + switch { - case venCfg.TPP != nil: - tpp := venCfg.TPP + case venaCfg.TPP != nil: + tpp := venaCfg.TPP tppSecret, err := secretsLister.Secrets(namespace).Get(tpp.CredentialsRef.Name) if err != nil { return nil, err } + caBundle, err := caBundleForVcertTPP(tpp, secretsLister, namespace) + if err != nil { + return nil, err + } + username := string(tppSecret.Data[tppUsernameKey]) password := string(tppSecret.Data[tppPasswordKey]) + clientId := string(tppSecret.Data[tppClientIdKey]) + // fallback to default client-id if not provided + if clientId == "" { + clientId = defaultTppClientId + } accessToken := string(tppSecret.Data[tppAccessTokenKey]) - caBundle := string(tpp.CABundle) return &vcert.Config{ ConnectorType: endpoint.ConnectorTypeTPP, BaseUrl: tpp.URL, - Zone: venCfg.Zone, + Zone: venaCfg.Zone, // always enable verbose logging for now - LogVerbose: true, - ConnectionTrust: caBundle, + LogVerbose: true, + // We supply the CA bundle here, to trigger the vcert's builtin + // validation of the supplied PEM content. + // This is somewhat redundant because the value (if valid) will be + // ignored by vcert since we also supply a custom HTTP client, + // below. But we want to retain the CA bundle validation errors that + // were returned in previous versions of this code. + // https://github.com/Venafi/vcert/blob/89645a7710a7b529765274cb60dc5e28066217a1/client.go#L55-L61 + ConnectionTrust: string(caBundle), Credentials: &endpoint.Authentication{ User: username, Password: password, AccessToken: accessToken, + ClientId: clientId, }, - // this is needed for local development when tunneling to the TPP server - //Client: &http.Client{ - // Transport: &http.Transport{ - // TLSClientConfig: &tls.Config{ - // Renegotiation: tls.RenegotiateOnceAsClient, - // }, - // }, - //}, + Client: httpClientForVcert(&httpClientForVcertOptions{ + UserAgent: ptr.To(userAgent), + CABundle: caBundle, + TLSRenegotiationSupport: ptr.To(tls.RenegotiateOnceAsClient), + }), }, nil - case venCfg.Cloud != nil: - cloud := venCfg.Cloud + case venaCfg.Cloud != nil: + cloud := venaCfg.Cloud cloudSecret, err := secretsLister.Secrets(namespace).Get(cloud.APITokenSecretRef.Name) if err != nil { return nil, err @@ -174,12 +219,15 @@ func configForIssuer(iss cmapi.GenericIssuer, secretsLister corelisters.SecretLi return &vcert.Config{ ConnectorType: endpoint.ConnectorTypeCloud, BaseUrl: cloud.URL, - Zone: venCfg.Zone, + Zone: venaCfg.Zone, // always enable verbose logging for now LogVerbose: true, Credentials: &endpoint.Authentication{ APIKey: apiKey, }, + Client: httpClientForVcert(&httpClientForVcertOptions{ + UserAgent: ptr.To(userAgent), + }), }, nil } // API validation in webhook and in the ClusterIssuer and Issuer controller @@ -187,6 +235,160 @@ func configForIssuer(iss cmapi.GenericIssuer, secretsLister corelisters.SecretLi return nil, fmt.Errorf("neither Venafi Cloud or TPP configuration found") } +// httpClientForVcertOptions contains options for `httpClientForVcert`, to allow +// you to customize the HTTP client. +type httpClientForVcertOptions struct { + // UserAgent will add a User-Agent header to all HTTP requests. + UserAgent *string + // CABundle will override the CA certificates used to verify server + // certificates. + CABundle []byte + // TLSRenegotiationSupport will override the TLSRenegotiationSupport setting + // of the client. + TLSRenegotiationSupport *tls.RenegotiationSupport +} + +// httpClientForVcert creates an HTTP client which matches the default HTTP client of vcert, +// but allows you to customize client TLS renegotiation, and User-Agent. +// +// Why is it necessary to create our own HTTP client for vcert? +// +// 1. We need to customize the client TLS renegotiation setting when connecting +// to certain TPP servers. +// 2. We need to customize the User-Agent header for all HTTP requests to Venafi +// REST API endpoints. +// 3. The vcert package does not currently provide an easier way to change those +// settings. See: +// * https://github.com/Venafi/vcert/issues/437 +// * https://github.com/Venafi/vcert/issues/438 +// +// Why is it necessary to customize the client TLS renegotiation? +// +// 1. The TPP API server is served by Microsoft Windows Server and IIS. +// 2. IIS uses TLS-1.2 by default[1] and it uses a +// TLS-1.2 feature called "renegotiation" to allow client certificate +// settings to be configured at the folder level. e.g. +// https://tpp.example.com/vedauth may Require or Accept client +// certificates while https://tpp.example.com/vedsdk may Ignore +// client certificates. +// 3. When IIS is configured this way it behaves as follows[2]: +// "Server receives a connection request on port 443; it begins a +// handshake. The server does not ask for a client certificate. Once +// the handshake is completed, the client sends the actual target URL +// as a HTTP request in the SSL tunnel. Up to that point, the server +// did not know which page was targeted; it only knew, at best, the +// intended server name (through the Server Name Indication). Now +// that the server knows which page is targeted, he knows which +// "site" (i.e. part of the server, in IIS terminology) is to be +// used." +// 4. In this scenario, the Go HTTP client MUST be configured to +// renegotiate (by default it will refuse to renegotiate). +// We use RenegotiateOnceAsClient rather than RenegotiateFreelyAsClient +// because cert-manager establishes a new HTTPS connection for each API +// request and therefore should only ever need to renegotiate once in this +// scenario. +// +// Why do we supply CA bundle in the HTTP client **and** in the vcert.Config? +// +// 1. Overriding the HTTP client causes vcert to ignore the +// `vcert.Config.ConnectionTrust` field, so we also have to set up the root +// CA trust pool ourselves. +// 2. And the value of RootCAs MUST be nil unless the user has supplied a +// custom CA, because a nil value causes the Go HTTP client to load the +// system default root CAs. +// +// [1] TLS protocol version support in Microsoft Windows: https://learn.microsoft.com/en-us/windows/win32/secauthn/protocols-in-tls-ssl--schannel-ssp-#tls-protocol-version-support +// [2] Should I use SSL/TLS renegotiation?: https://security.stackexchange.com/a/24569 +func httpClientForVcert(options *httpClientForVcertOptions) *http.Client { + // Copy vcert's default HTTP transport, which is mostly identical to the + // http.DefaultTransport settings in Go's stdlib. + // https://github.com/Venafi/vcert/blob/89645a7710a7b529765274cb60dc5e28066217a1/pkg/venafi/tpp/tpp.go#L481-L513 + transport := &http.Transport{ + Proxy: http.ProxyFromEnvironment, + DialContext: (&net.Dialer{ + Timeout: 30 * time.Second, + KeepAlive: 30 * time.Second, + // Note: This DualStack setting is copied from vcert but + // deviates from the http.DefaultTransport in Go's stdlib. + DualStack: true, + }).DialContext, + MaxIdleConns: 100, + IdleConnTimeout: 90 * time.Second, + TLSHandshakeTimeout: 10 * time.Second, + ExpectContinueTimeout: 1 * time.Second, + } + + // Copy vcert's initialization of the TLS client config + tlsClientConfig := http.DefaultTransport.(*http.Transport).TLSClientConfig.Clone() + if tlsClientConfig == nil { + tlsClientConfig = &tls.Config{MinVersion: tls.VersionTLS12} + } + if len(options.CABundle) > 0 { + rootCAs := x509.NewCertPool() + rootCAs.AppendCertsFromPEM(options.CABundle) + tlsClientConfig.RootCAs = rootCAs + } + transport.TLSClientConfig = tlsClientConfig + + if options.TLSRenegotiationSupport != nil { + transport.TLSClientConfig.Renegotiation = *options.TLSRenegotiationSupport + } + + var roundTripper http.RoundTripper = transport + if options.UserAgent != nil { + roundTripper = util.UserAgentRoundTripper(transport, *options.UserAgent) + } + + // Copy vcert's initialization of the HTTP client, which overrides the default timeout. + // https://github.com/Venafi/vcert/blob/89645a7710a7b529765274cb60dc5e28066217a1/pkg/venafi/tpp/tpp.go#L481-L513 + return &http.Client{ + Transport: roundTripper, + Timeout: time.Second * 30, + } +} + +// caBundleForVcertTPP is used to by ConnectionTrust and Client fields of vcert.Config. +// This function sets appropriate CA based on provided bundle or kubernetes secret +// If no custom CA bundle is configured, an empty byte slice is returned. +// Assumes exactly one of the in-line/Secret CA bundles are defined. +// If the `key` of the Secret CA bundle is not defined, its value defaults to +// `ca.crt`. +func caBundleForVcertTPP(tpp *cmapi.VenafiTPP, secretsLister internalinformers.SecretLister, namespace string) (caBundle []byte, err error) { + if len(tpp.CABundle) > 0 { + return tpp.CABundle, nil + } + + secretRef := tpp.CABundleSecretRef + if secretRef == nil { + return nil, nil + } + + var certBytes []byte + var ok bool + + if secretRef != nil { + secret, err := secretsLister.Secrets(namespace).Get(secretRef.Name) + if err != nil { + return nil, fmt.Errorf("could not access secret '%s/%s': %s", namespace, secretRef.Name, err) + } + + var key string + if secretRef.Key != "" { + key = secretRef.Key + } else { + key = cmmeta.TLSCAKey + } + + certBytes, ok = secret.Data[key] + if !ok { + return nil, fmt.Errorf("no data for %q in secret '%s/%s'", key, namespace, secretRef.Name) + } + + } + + return certBytes, nil +} + func (v *Venafi) Ping() error { return v.vcertClient.Ping() } @@ -230,9 +432,24 @@ func (v *Venafi) VerifyCredentials() error { } if v.config.Credentials.User != "" && v.config.Credentials.Password != "" { - err := v.tppClient.Authenticate(&endpoint.Authentication{ + // Use vcert library GetRefreshToken which brings back a token pair. + // This includes the access_token which we set against the tppClient. + // Replaces usage of v.tppClient.Authenticate function which would + // have called the APIKey endpoint resulting in error. + resp, err := v.tppClient.GetRefreshToken(&endpoint.Authentication{ User: v.config.Credentials.User, Password: v.config.Credentials.Password, + ClientId: v.config.Credentials.ClientId, + Scope: tppScopes, + }) + + if err != nil { + return fmt.Errorf("tppClient.GetRefreshToken: %v", err) + } + + // Ensure that the access_token is stored on the tppClient object. + err = v.tppClient.Authenticate(&endpoint.Authentication{ + AccessToken: resp.Access_token, }) if err != nil { diff --git a/pkg/issuer/venafi/client/venaficlient_test.go b/pkg/issuer/venafi/client/venaficlient_test.go index 3d752b9ae77..f112e3647b4 100644 --- a/pkg/issuer/venafi/client/venaficlient_test.go +++ b/pkg/issuer/venafi/client/venaficlient_test.go @@ -20,16 +20,62 @@ import ( "errors" "testing" - vcert "github.com/Venafi/vcert/v4" + vcert "github.com/Venafi/vcert/v5" corev1 "k8s.io/api/core/v1" corelisters "k8s.io/client-go/listers/core/v1" + internalinformers "github.com/cert-manager/cert-manager/internal/informers" cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" "github.com/cert-manager/cert-manager/test/unit/gen" testlisters "github.com/cert-manager/cert-manager/test/unit/listers" ) +const ( + zone = "test-zone" + username = "test-username" + password = "test-password" + defaultClientId = "cert-manager.io" + accessToken = "KT2EEVTIjWM/37L78dqJAg==" + apiKey = "test-api-key" + customKey = "test-custom-key" + defaultCaKey = "ca.crt" + customCaKey = "custom-ca-key" + tppUrl = "https://tpp.example.com/vedsdk" + customCaSecretName = "custom-ca-secret" + testLeafCertificate = `-----BEGIN CERTIFICATE----- +MIIFFTCCAv2gAwIBAgICEAAwDQYJKoZIhvcNAQELBQAwRjELMAkGA1UEBhMCVVMx +CzAJBgNVBAgMAkNBMRQwEgYDVQQKDAtDRVJUTUFOQUdFUjEUMBIGA1UEAwwLZm9v +LmJhci5pbnQwHhcNMjAxMDAyMTQ1NzMwWhcNMjExMDEyMTQ1NzMwWjBKMQswCQYD +VQQGEwJVUzELMAkGA1UECAwCQ0ExFDASBgNVBAoMC0NFUlRNQU5BR0VSMRgwFgYD +VQQDDA9leGFtcGxlLmZvby5iYXIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK +AoIBAQC8yTGzYIX3OoRma11vewbNf8dgKHc9GgvJJ29SVjaNwRAJjKOXokGOwcyQ +7Ieb1puYQ5KdSPC1IxyUx77URovIvd3Wql+J1gIxyrdN3om3uQdJ2ck6xatBZ8BI +Y3Z+6WpUQ2067Wk4KpUGfMrbGg5zVcesh6zc8J9yEiItUENeR+6GyEf+B8IJ0xqe +5lps2LaxZp6I6vaKeMELjj17Nb9r81Rjyk8BN7yX74tFE1mUGX9o75tsODU9IrYW +nqSl5gr2PO9Zb/bd6zhoncLJr9kj2tk6cLRPht+JOPoA2LAP6D0aEdC3a2XWuj2E +EsUYJR9e5C/X49VQaak0VdNnhO6RAgMBAAGjggEHMIIBAzAJBgNVHRMEAjAAMBEG +CWCGSAGG+EIBAQQEAwIGQDAzBglghkgBhvhCAQ0EJhYkT3BlblNTTCBHZW5lcmF0 +ZWQgU2VydmVyIENlcnRpZmljYXRlMB0GA1UdDgQWBBQ41U/GiA2rQtuMz6tNL55C +o4pnBDBqBgNVHSMEYzBhgBSfus9cb7UA/PCfHJAGtL6ot2EpLKFFpEMwQTEPMA0G +A1UEAwwGYmFyLmNhMQswCQYDVQQGEwJVUzELMAkGA1UECAwCQ0ExFDASBgNVBAoM +C0NFUlRNQU5BR0VSggIQADAOBgNVHQ8BAf8EBAMCBaAwEwYDVR0lBAwwCgYIKwYB +BQUHAwEwDQYJKoZIhvcNAQELBQADggIBAFFTJNqKSkkJNWWt+R7WFNIEKoaPcFH5 +yupCQRYX9LK2cXdBQpF458/PFxREyt5jKFUcDyzQhOglFYq0hfcoAc2EB3Vw8Ww9 +c4QCiCU6ehJVMRt7MzZ9uUVGCRVOA+Fa1tIFfL3dKlI+4pTSbDhNHRqDtFhfWOZK +bgtruQEUOW1lQR61AsidOF1iwDBU6ckpVY9Lc2SHEAfQFs0MoXmJ8B4MqFptF4+H +al+IAeQ1bC/2EccFYg3tq9+YKHDCyghHf8qeKJR9tZslvkHrAzuX56e0MHxM3AD6 +D0L8nG3DsrHcjK0MlVUWmq0QFnY5t+78iocLoQZzpILZYuZn3p+XNlUdW4lcqSBn +y5fUwQ3RIuvN66GBhTeDV4vzYPa7g3i9PoBFoG50Ayr6VtIVn08rnl03lgp57Edv +A5oRrSHcd8Hd8/lk0Y9BpFTnZEg7RLhFhh9nazVp1/pjwaGx449uHIGEoxREQoPq +9Q+KLGMJR2IqiNI6+U1z2j8BChTOPkuAvsnSuAXyotu4BXBL5zbDzfDoggEk1ps1 +bfHWnmdelE0WP7h7B0PSA0EXn0pdg2VQIQsknV6y3MCzFQCCSAog/OSguokXG1PG +l6fctDJ3+AF07EjtgArOBkUn7Nt3/CgMN8I1rnBZ1Vmd8yrHEP0E3yRXBL7cDj5j +Fqmd89NQLlGs +-----END CERTIFICATE----- +` +) + func checkNoConfigReturned(t *testing.T, cnf *vcert.Config) { if cnf != nil { t.Errorf("expected no config to be returned, got=%+v", cnf) @@ -47,7 +93,29 @@ func checkZone(t *testing.T, zone string, cnf *vcert.Config) { } } -func generateSecretLister(s *corev1.Secret, err error) corelisters.SecretLister { +func checkTppUrl(t *testing.T, tppUrl string, cnf *vcert.Config) { + if cnf == nil { + t.Errorf("expected config but got: %+v", cnf) + } + + if tppUrl != cnf.BaseUrl { + t.Errorf("got unexpected BaseUrl set, exp=%s got=%s", + tppUrl, cnf.BaseUrl) + } +} + +func checkTppCa(t *testing.T, ca string, cnf *vcert.Config) { + if cnf == nil { + t.Errorf("expected config but got: %+v", cnf) + } + + if ca != cnf.ConnectionTrust { + t.Errorf("got unexpected CA as trust, exp=%s got=%s", + ca, cnf.ConnectionTrust) + } +} + +func generateSecretLister(s *corev1.Secret, err error) internalinformers.SecretLister { return &testlisters.FakeSecretLister{ SecretsFn: func(string) corelisters.SecretNamespaceLister { return &testlisters.FakeSecretNamespaceLister{ @@ -63,6 +131,7 @@ func TestConfigForIssuerT(t *testing.T) { zone := "test-zone" username := "test-username" password := "test-password" + clientId := "test-client-id" accessToken := "KT2EEVTIjWM/37L78dqJAg==" apiKey := "test-api-key" customKey := "test-custom-key" @@ -78,6 +147,36 @@ func TestConfigForIssuerT(t *testing.T) { }), ) + tppIssuerWithoutCA := gen.IssuerFrom(baseIssuer, + gen.SetIssuerVenafi(cmapi.VenafiIssuer{ + Zone: zone, + TPP: &cmapi.VenafiTPP{ + URL: tppUrl, + }, + }), + ) + + tppIssuerWithCABundle := gen.IssuerFrom(tppIssuerWithoutCA, + gen.SetIssuerVenafi(cmapi.VenafiIssuer{ + TPP: &cmapi.VenafiTPP{ + CABundle: []byte(testLeafCertificate), + }, + }), + ) + + tppIssuerWithCABundleSecretRef := gen.IssuerFrom(tppIssuer, + gen.SetIssuerVenafi(cmapi.VenafiIssuer{ + TPP: &cmapi.VenafiTPP{ + CABundleSecretRef: &cmmeta.SecretKeySelector{ + Key: customCaKey, + LocalObjectReference: cmmeta.LocalObjectReference{ + Name: customCaSecretName, + }, + }, + }, + }), + ) + cloudIssuer := gen.IssuerFrom(baseIssuer, gen.SetIssuerVenafi(cmapi.VenafiIssuer{ Zone: zone, @@ -108,6 +207,22 @@ func TestConfigForIssuerT(t *testing.T) { CheckFn: checkNoConfigReturned, expectedErr: true, }, + "if TPP and neither caBundle nor caBundleSecretRef is specified, CA bundle is not set in vcert config": { + iss: tppIssuerWithoutCA, + secretsLister: generateSecretLister(&corev1.Secret{ + Data: map[string][]byte{ + tppUsernameKey: []byte(username), + tppPasswordKey: []byte(password), + }, + }, nil), + CheckFn: func(t *testing.T, cnf *vcert.Config) { + if trust := cnf.ConnectionTrust; trust != "" { + t.Errorf("got unexpected CA bundle: %s", trust) + } + checkTppUrl(t, tppUrl, cnf) + }, + expectedErr: false, + }, "if TPP and secret returns user/pass, should return config with those credentials": { iss: tppIssuer, secretsLister: generateSecretLister(&corev1.Secret{ @@ -123,6 +238,32 @@ func TestConfigForIssuerT(t *testing.T) { if pass := cnf.Credentials.Password; pass != password { t.Errorf("got unexpected password: %s", pass) } + if cId := cnf.Credentials.ClientId; cId != defaultClientId { + t.Errorf("got unexpected clientId: %s", cId) + } + checkZone(t, zone, cnf) + }, + expectedErr: false, + }, + "if TPP and secret returns user/pass/clientId, should return config with those credentials": { + iss: tppIssuer, + secretsLister: generateSecretLister(&corev1.Secret{ + Data: map[string][]byte{ + tppUsernameKey: []byte(username), + tppPasswordKey: []byte(password), + tppClientIdKey: []byte(clientId), + }, + }, nil), + CheckFn: func(t *testing.T, cnf *vcert.Config) { + if user := cnf.Credentials.User; user != username { + t.Errorf("got unexpected username: %s", user) + } + if pass := cnf.Credentials.Password; pass != password { + t.Errorf("got unexpected password: %s", pass) + } + if cId := cnf.Credentials.ClientId; cId != clientId { + t.Errorf("got unexpected clientId: %s", cId) + } checkZone(t, zone, cnf) }, expectedErr: false, @@ -142,6 +283,32 @@ func TestConfigForIssuerT(t *testing.T) { }, expectedErr: false, }, + // NOTE: Below scenarios assume valid TPP CAs, the scenarios with invalid TPP CAs are run part of TestCaBundleForVcertTPP test + "if TPP and a good caBundle specified, CA bundle should be added to ConnectionTrust and Client in vcert config": { + iss: tppIssuerWithCABundle, + secretsLister: generateSecretLister(&corev1.Secret{ + Data: map[string][]byte{ + tppAccessTokenKey: []byte(accessToken), + }, + }, nil), + CheckFn: func(t *testing.T, cnf *vcert.Config) { + checkTppCa(t, testLeafCertificate, cnf) + }, + expectedErr: false, + }, + "if TPP and a good caBundleSecretRef specified, CA bundle should be added to ConnectionTrust and Client in vcert config": { + iss: tppIssuerWithCABundleSecretRef, + // tppAccessTokenKey secret lister is not passed as we only have single secretsLister in testConfigForIssuerT struck + secretsLister: generateSecretLister(&corev1.Secret{ + Data: map[string][]byte{ + customCaKey: []byte(testLeafCertificate), + }, + }, nil), + CheckFn: func(t *testing.T, cnf *vcert.Config) { + checkTppCa(t, testLeafCertificate, cnf) + }, + expectedErr: false, + }, "if Cloud but getting secret fails, should error": { iss: cloudIssuer, secretsLister: generateSecretLister(nil, errors.New("this is a network error")), @@ -212,9 +379,108 @@ func TestConfigForIssuerT(t *testing.T) { } } +func TestCaBundleForVcertTPP(t *testing.T) { + baseIssuer := gen.Issuer("non-venafi-issue", + gen.SetIssuerVenafi(cmapi.VenafiIssuer{}), + ) + + tppIssuer := gen.IssuerFrom(baseIssuer, + gen.SetIssuerVenafi(cmapi.VenafiIssuer{ + Zone: zone, + TPP: &cmapi.VenafiTPP{}, + }), + ) + + tppIssuerWithCABundle := gen.IssuerFrom(tppIssuer, + gen.SetIssuerVenafi(cmapi.VenafiIssuer{ + TPP: &cmapi.VenafiTPP{ + CABundle: []byte(testLeafCertificate), + }, + }), + ) + + tppIssuerWithCABundleSecretRefNoKey := gen.IssuerFrom(tppIssuer, + gen.SetIssuerVenafi(cmapi.VenafiIssuer{ + TPP: &cmapi.VenafiTPP{ + CABundleSecretRef: &cmmeta.SecretKeySelector{ + LocalObjectReference: cmmeta.LocalObjectReference{ + Name: customCaSecretName, + }, + }, + }, + }), + ) + + tppIssuerWithCABundleSecretRef := gen.IssuerFrom(tppIssuer, + gen.SetIssuerVenafi(cmapi.VenafiIssuer{ + TPP: &cmapi.VenafiTPP{ + CABundleSecretRef: &cmmeta.SecretKeySelector{ + Key: customCaKey, + LocalObjectReference: cmmeta.LocalObjectReference{ + Name: customCaSecretName, + }, + }, + }, + }), + ) + + tests := map[string]testConfigForIssuerT{ + "if TPP and neither of caBundle nor caBundleSecretRef is specified, CA bundle is not returned": { + iss: tppIssuer, + caBundle: "", + expectedErr: false, + }, + "if TPP and caBundle is specified, correct CA bundle from CABundle should be returned": { + iss: tppIssuerWithCABundle, + caBundle: testLeafCertificate, + expectedErr: false, + }, + "if TPP and caBundleSecretRef is specified, correct CA bundle from CABundleSecretRef should be returned": { + iss: tppIssuerWithCABundleSecretRef, + caBundle: testLeafCertificate, + secretsLister: generateSecretLister(&corev1.Secret{ + Data: map[string][]byte{ + customCaKey: []byte(testLeafCertificate), + }, + }, nil), + expectedErr: false, + }, + "if TPP and caBundleSecretRef is specified without `key`, correct CA bundle from CABundleSecretRef with default key should be returned": { + iss: tppIssuerWithCABundleSecretRefNoKey, + caBundle: testLeafCertificate, + secretsLister: generateSecretLister(&corev1.Secret{ + Data: map[string][]byte{ + defaultCaKey: []byte(testLeafCertificate), + }, + }, nil), + expectedErr: false, + }, + "if TPP and caBundleSecretRef is specified, but getting secret fails should error": { + iss: tppIssuerWithCABundleSecretRef, + caBundle: testLeafCertificate, + secretsLister: generateSecretLister(nil, errors.New("this is a network error")), + expectedErr: true, + }, + // TODO: write test cases where bad CA is passed. + // above TODO can be ignored if the checks are added to issuer validations per below link + // https://github.com/cert-manager/cert-manager/blob/v1.14.4/internal/apis/certmanager/validation/issuer.go#L354 + // even though we are not prevalidating, vcert http.Client would anyway fail when using invalid CA + // 2 scenarios with bad CAs: + // "if TPP and caBundle is specified, a bad bundle from CABundle should error" + // "if TPP and caBundleSecretRef is specified, a bad bundle from a CABundleSecretRef should error" + } + + for name, test := range tests { + t.Run(name, func(t *testing.T) { + test.runTppCaTest(t) + }) + } +} + type testConfigForIssuerT struct { iss cmapi.GenericIssuer - secretsLister corelisters.SecretLister + secretsLister internalinformers.SecretLister + caBundle string expectedErr bool @@ -222,7 +488,7 @@ type testConfigForIssuerT struct { } func (c *testConfigForIssuerT) runTest(t *testing.T) { - resp, err := configForIssuer(c.iss, c.secretsLister, "test-namespace") + resp, err := configForIssuer(c.iss, c.secretsLister, "test-namespace", "cert-manager/v0.0.0") if err != nil && !c.expectedErr { t.Errorf("expected to not get an error, but got: %v", err) } @@ -234,3 +500,21 @@ func (c *testConfigForIssuerT) runTest(t *testing.T) { c.CheckFn(t, resp) } } + +func (c *testConfigForIssuerT) runTppCaTest(t *testing.T) { + caResp, err := caBundleForVcertTPP(c.iss.GetSpec().Venafi.TPP, c.secretsLister, "test-namespace") + + if err != nil && !c.expectedErr { + t.Errorf("expected to not get an error, but got: %v", err) + } + if err == nil && c.expectedErr { + t.Errorf("expected to get an error but did not get one") + } + + if !c.expectedErr { + if c.caBundle != string(caResp) { + t.Errorf("got unexpected CA bundle, exp=%s got=%s", + c.caBundle, caResp) + } + } +} diff --git a/pkg/issuer/venafi/setup.go b/pkg/issuer/venafi/setup.go index cbdc72ba595..d953a1443c7 100644 --- a/pkg/issuer/venafi/setup.go +++ b/pkg/issuer/venafi/setup.go @@ -28,17 +28,19 @@ import ( logf "github.com/cert-manager/cert-manager/pkg/logs" ) -func (v *Venafi) Setup(ctx context.Context) (err error) { +func (v *Venafi) Setup(ctx context.Context, issuer cmapi.GenericIssuer) (err error) { defer func() { if err != nil { errorMessage := "Failed to setup Venafi issuer" v.log.Error(err, errorMessage) - apiutil.SetIssuerCondition(v.issuer, v.issuer.GetGeneration(), cmapi.IssuerConditionReady, cmmeta.ConditionFalse, "ErrorSetup", fmt.Sprintf("%s: %v", errorMessage, err)) + apiutil.SetIssuerCondition(issuer, issuer.GetGeneration(), cmapi.IssuerConditionReady, cmmeta.ConditionFalse, "ErrorSetup", fmt.Sprintf("%s: %v", errorMessage, err)) err = fmt.Errorf("%s: %v", errorMessage, err) } }() - client, err := v.clientBuilder(v.resourceNamespace, v.secretsLister, v.issuer, v.Metrics, v.log) + resourceNamespace := v.ResourceNamespace(issuer) + + client, err := v.clientBuilder(resourceNamespace, v.secretsLister, issuer, v.Metrics, v.log, v.userAgent) if err != nil { return fmt.Errorf("error building client: %v", err) } @@ -54,14 +56,14 @@ func (v *Venafi) Setup(ctx context.Context) (err error) { // If it does not already have a 'ready' condition, we'll also log an event // to make it really clear to users that this Issuer is ready. - if !apiutil.IssuerHasCondition(v.issuer, cmapi.IssuerCondition{ + if !apiutil.IssuerHasCondition(issuer, cmapi.IssuerCondition{ Type: cmapi.IssuerConditionReady, Status: cmmeta.ConditionTrue, }) { - v.Recorder.Eventf(v.issuer, corev1.EventTypeNormal, "Ready", "Verified issuer with Venafi server") + v.Recorder.Eventf(issuer, corev1.EventTypeNormal, "Ready", "Verified issuer with Venafi server") } v.log.V(logf.DebugLevel).Info("Venafi issuer started") - apiutil.SetIssuerCondition(v.issuer, v.issuer.GetGeneration(), cmapi.IssuerConditionReady, cmmeta.ConditionTrue, "Venafi issuer started", "Venafi issuer started") + apiutil.SetIssuerCondition(issuer, issuer.GetGeneration(), cmapi.IssuerConditionReady, cmmeta.ConditionTrue, "Venafi issuer started", "Venafi issuer started") return nil } diff --git a/pkg/issuer/venafi/setup_test.go b/pkg/issuer/venafi/setup_test.go index c7a4befbb30..4a792b66d28 100644 --- a/pkg/issuer/venafi/setup_test.go +++ b/pkg/issuer/venafi/setup_test.go @@ -17,37 +17,34 @@ limitations under the License. package venafi import ( - "context" "errors" "fmt" + "slices" "testing" "github.com/go-logr/logr" - logf "github.com/cert-manager/cert-manager/pkg/logs" - "github.com/cert-manager/cert-manager/pkg/metrics" - - corelisters "k8s.io/client-go/listers/core/v1" - + internalinformers "github.com/cert-manager/cert-manager/internal/informers" cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" - "github.com/cert-manager/cert-manager/pkg/controller" + controllerpkg "github.com/cert-manager/cert-manager/pkg/controller" controllertest "github.com/cert-manager/cert-manager/pkg/controller/test" "github.com/cert-manager/cert-manager/pkg/issuer/venafi/client" internalvenafifake "github.com/cert-manager/cert-manager/pkg/issuer/venafi/client/fake" - "github.com/cert-manager/cert-manager/pkg/util" + logf "github.com/cert-manager/cert-manager/pkg/logs" + "github.com/cert-manager/cert-manager/pkg/metrics" "github.com/cert-manager/cert-manager/test/unit/gen" ) func TestSetup(t *testing.T) { baseIssuer := gen.Issuer("test-issuer") - failingClientBuilder := func(string, corelisters.SecretLister, - cmapi.GenericIssuer, *metrics.Metrics, logr.Logger) (client.Interface, error) { + failingClientBuilder := func(string, internalinformers.SecretLister, + cmapi.GenericIssuer, *metrics.Metrics, logr.Logger, string) (client.Interface, error) { return nil, errors.New("this is an error") } - failingPingClient := func(string, corelisters.SecretLister, - cmapi.GenericIssuer, *metrics.Metrics, logr.Logger) (client.Interface, error) { + failingPingClient := func(string, internalinformers.SecretLister, + cmapi.GenericIssuer, *metrics.Metrics, logr.Logger, string) (client.Interface, error) { return &internalvenafifake.Venafi{ PingFn: func() error { return errors.New("this is a ping error") @@ -55,8 +52,8 @@ func TestSetup(t *testing.T) { }, nil } - pingClient := func(string, corelisters.SecretLister, - cmapi.GenericIssuer, *metrics.Metrics, logr.Logger) (client.Interface, error) { + pingClient := func(string, internalinformers.SecretLister, + cmapi.GenericIssuer, *metrics.Metrics, logr.Logger, string) (client.Interface, error) { return &internalvenafifake.Venafi{ PingFn: func() error { return nil @@ -64,7 +61,7 @@ func TestSetup(t *testing.T) { }, nil } - verifyCredentialsClient := func(string, corelisters.SecretLister, cmapi.GenericIssuer, *metrics.Metrics, logr.Logger) (client.Interface, error) { + verifyCredentialsClient := func(string, internalinformers.SecretLister, cmapi.GenericIssuer, *metrics.Metrics, logr.Logger, string) (client.Interface, error) { return &internalvenafifake.Venafi{ PingFn: func() error { return nil @@ -75,7 +72,7 @@ func TestSetup(t *testing.T) { }, nil } - failingVerifyCredentialsClient := func(string, corelisters.SecretLister, cmapi.GenericIssuer, *metrics.Metrics, logr.Logger) (client.Interface, error) { + failingVerifyCredentialsClient := func(string, internalinformers.SecretLister, cmapi.GenericIssuer, *metrics.Metrics, logr.Logger, string) (client.Interface, error) { return &internalvenafifake.Venafi{ PingFn: func() error { return nil @@ -168,16 +165,14 @@ func (s *testSetupT) runTest(t *testing.T) { rec := &controllertest.FakeRecorder{} v := &Venafi{ - resourceNamespace: "test-namespace", - Context: &controller.Context{ + Context: &controllerpkg.Context{ Recorder: rec, }, - issuer: s.iss, clientBuilder: s.clientBuilder, log: logf.Log.WithName("venafi"), } - err := v.Setup(context.TODO()) + err := v.Setup(t.Context(), s.iss) if err != nil && !s.expectedErr { t.Errorf("expected to not get an error, but got: %v", err) } @@ -185,7 +180,7 @@ func (s *testSetupT) runTest(t *testing.T) { t.Errorf("expected to get an error but did not get one") } - if !util.EqualSorted(s.expectedEvents, rec.Events) { + if !slices.Equal(s.expectedEvents, rec.Events) { t.Errorf("got unexpected events, exp='%s' got='%s'", s.expectedEvents, rec.Events) } diff --git a/pkg/issuer/venafi/venafi.go b/pkg/issuer/venafi/venafi.go index 51729fc2ba2..974bd985000 100644 --- a/pkg/issuer/venafi/venafi.go +++ b/pkg/issuer/venafi/venafi.go @@ -19,44 +19,35 @@ package venafi import ( "github.com/go-logr/logr" - corelisters "k8s.io/client-go/listers/core/v1" - + internalinformers "github.com/cert-manager/cert-manager/internal/informers" apiutil "github.com/cert-manager/cert-manager/pkg/api/util" - cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" "github.com/cert-manager/cert-manager/pkg/controller" "github.com/cert-manager/cert-manager/pkg/issuer" "github.com/cert-manager/cert-manager/pkg/issuer/venafi/client" logf "github.com/cert-manager/cert-manager/pkg/logs" - "github.com/cert-manager/cert-manager/pkg/metrics" ) -// Venafi is a implementation of govcert library to manager certificates from TPP or Venafi Cloud +// Venafi is an implementation of govcert library to manager certificates from TPP or Venafi Cloud type Venafi struct { - issuer cmapi.GenericIssuer *controller.Context - secretsLister corelisters.SecretLister - - // Namespace in which to read resources related to this Issuer from. - // For Issuers, this will be the namespace of the Issuer. - // For ClusterIssuers, this will be the cluster resource namespace. - resourceNamespace string + secretsLister internalinformers.SecretLister clientBuilder client.VenafiClientBuilder - metrics *metrics.Metrics - log logr.Logger + + // userAgent is the string used as the UserAgent when making HTTP calls. + userAgent string } -func NewVenafi(ctx *controller.Context, issuer cmapi.GenericIssuer) (issuer.Interface, error) { +func NewVenafi(ctx *controller.Context) (issuer.Interface, error) { return &Venafi{ - issuer: issuer, - secretsLister: ctx.KubeSharedInformerFactory.Core().V1().Secrets().Lister(), - resourceNamespace: ctx.IssuerOptions.ResourceNamespace(issuer), - clientBuilder: client.New, - Context: ctx, - log: logf.Log.WithName("venafi"), + secretsLister: ctx.KubeSharedInformerFactory.Secrets().Lister(), + clientBuilder: client.New, + Context: ctx, + log: logf.Log.WithName("venafi"), + userAgent: ctx.RESTConfig.UserAgent, }, nil } diff --git a/pkg/logs/logs.go b/pkg/logs/logs.go index 4aeb09d22a1..011973ed4a1 100644 --- a/pkg/logs/logs.go +++ b/pkg/logs/logs.go @@ -20,24 +20,23 @@ import ( "context" "flag" "fmt" - "log" - "time" "github.com/go-logr/logr" + "github.com/spf13/pflag" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/apimachinery/pkg/util/wait" + "k8s.io/component-base/logs" + logsapi "k8s.io/component-base/logs/api/v1" "k8s.io/klog/v2" - "k8s.io/klog/v2/klogr" "github.com/cert-manager/cert-manager/pkg/api" -) -var ( - Log = klogr.New().WithName("cert-manager") + _ "k8s.io/component-base/logs/json/register" ) +var Log = klog.TODO().WithName("cert-manager") + const ( // Following analog to https://github.com/kubernetes/community/blob/master/contributors/devel/sig-instrumentation/logging.md @@ -49,35 +48,50 @@ const ( TraceLevel = 5 ) -var logFlushFreq = flag.Duration("log-flush-frequency", 5*time.Second, "Maximum number of seconds between log flushes") +// InitLogs initializes logs the way we want for kubernetes. +func InitLogs() { + logs.InitLogs() -// GlogWriter serves as a bridge between the standard log package and the glog package. -type GlogWriter struct{} + klog.EnableContextualLogging(true) // Enable contextual logging +} -// Write implements the io.Writer interface. -func (writer GlogWriter) Write(data []byte) (n int, err error) { - klog.Info(string(data)) - return len(data), nil +func AddFlagsNonDeprecated(opts *logsapi.LoggingConfiguration, fs *pflag.FlagSet) { + var allFlags pflag.FlagSet + logsapi.AddFlags(opts, &allFlags) + + allFlags.VisitAll(func(f *pflag.Flag) { + switch f.Name { + case "logging-format", "log-flush-frequency", "v", "vmodule": + fs.AddFlag(f) + } + }) } -// InitLogs initializes logs the way we want for kubernetes. -func InitLogs(fs *flag.FlagSet) { - if fs == nil { - fs = flag.CommandLine - } - klog.InitFlags(fs) - _ = fs.Set("logtostderr", "true") +func AddFlags(opts *logsapi.LoggingConfiguration, fs *pflag.FlagSet) { + var allFlags flag.FlagSet + klog.InitFlags(&allFlags) + + allFlags.VisitAll(func(f *flag.Flag) { + switch f.Name { + case "add_dir_header", "alsologtostderr", "log_backtrace_at", "log_dir", "log_file", "log_file_max_size", + "logtostderr", "one_output", "skip_headers", "skip_log_headers", "stderrthreshold": + pf := pflag.PFlagFromGoFlag(f) + pf.Deprecated = "this flag may be removed in the future" + pf.Hidden = true + fs.AddFlag(pf) + } + }) - log.SetOutput(GlogWriter{}) - log.SetFlags(0) + AddFlagsNonDeprecated(opts, fs) +} - // The default glog flush interval is 30 seconds, which is frighteningly long. - go wait.Until(klog.Flush, *logFlushFreq, wait.NeverStop) +func ValidateAndApply(opts *logsapi.LoggingConfiguration) error { + return logsapi.ValidateAndApply(opts, nil) } // FlushLogs flushes logs immediately. func FlushLogs() { - klog.Flush() + logs.FlushLogs() } const ( @@ -136,8 +150,6 @@ func WithRelatedResourceName(l logr.Logger, name, namespace, kind string) logr.L ) } -var contextKey = &struct{}{} - func FromContext(ctx context.Context, names ...string) logr.Logger { l, err := logr.FromContext(ctx) if err != nil { @@ -156,10 +168,6 @@ func NewContext(ctx context.Context, l logr.Logger, names ...string) context.Con return logr.NewContext(ctx, l) } -func V(level int) klog.Verbose { - return klog.V(klog.Level(level)) -} - // LogWithFormat is a wrapper for logger that adds Infof method to log messages // with the given format and arguments. // diff --git a/pkg/metrics/certificates.go b/pkg/metrics/certificates.go deleted file mode 100644 index 071e53897c7..00000000000 --- a/pkg/metrics/certificates.go +++ /dev/null @@ -1,122 +0,0 @@ -/* -Copyright 2020 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. -*/ - -package metrics - -import ( - "context" - - "github.com/prometheus/client_golang/prometheus" - "k8s.io/client-go/tools/cache" - - cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" - cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" - logf "github.com/cert-manager/cert-manager/pkg/logs" -) - -// UpdateCertificate will update the given Certificate's metrics for its expiry, renewal, and status -// condition. -func (m *Metrics) UpdateCertificate(ctx context.Context, crt *cmapi.Certificate) { - key, err := cache.MetaNamespaceKeyFunc(crt) - if err != nil { - log := logf.WithRelatedResource(m.log, crt) - log.Error(err, "failed to get key from certificate object") - return - } - - m.updateCertificateStatus(key, crt) - m.updateCertificateExpiry(ctx, key, crt) - m.updateCertificateRenewalTime(crt) -} - -// updateCertificateExpiry updates the expiry time of a certificate -func (m *Metrics) updateCertificateExpiry(ctx context.Context, key string, crt *cmapi.Certificate) { - expiryTime := 0.0 - - if crt.Status.NotAfter != nil { - expiryTime = float64(crt.Status.NotAfter.Unix()) - } - - m.certificateExpiryTimeSeconds.With(prometheus.Labels{ - "name": crt.Name, - "namespace": crt.Namespace, - "issuer_name": crt.Spec.IssuerRef.Name, - "issuer_kind": crt.Spec.IssuerRef.Kind, - "issuer_group": crt.Spec.IssuerRef.Group}).Set(expiryTime) -} - -// updateCertificateRenewalTime updates the renew before duration of a certificate -func (m *Metrics) updateCertificateRenewalTime(crt *cmapi.Certificate) { - renewalTime := 0.0 - - if crt.Status.RenewalTime != nil { - renewalTime = float64(crt.Status.RenewalTime.Unix()) - } - - m.certificateRenewalTimeSeconds.With(prometheus.Labels{ - "name": crt.Name, - "namespace": crt.Namespace, - "issuer_name": crt.Spec.IssuerRef.Name, - "issuer_kind": crt.Spec.IssuerRef.Kind, - "issuer_group": crt.Spec.IssuerRef.Group}).Set(renewalTime) - -} - -// updateCertificateStatus will update the metric for that Certificate -func (m *Metrics) updateCertificateStatus(key string, crt *cmapi.Certificate) { - for _, c := range crt.Status.Conditions { - if c.Type == cmapi.CertificateConditionReady { - m.updateCertificateReadyStatus(crt, c.Status) - return - } - } - - // If no status condition set yet, set to Unknown - m.updateCertificateReadyStatus(crt, cmmeta.ConditionUnknown) -} - -func (m *Metrics) updateCertificateReadyStatus(crt *cmapi.Certificate, current cmmeta.ConditionStatus) { - for _, condition := range readyConditionStatuses { - value := 0.0 - - if current == condition { - value = 1.0 - } - - m.certificateReadyStatus.With(prometheus.Labels{ - "name": crt.Name, - "namespace": crt.Namespace, - "condition": string(condition), - "issuer_name": crt.Spec.IssuerRef.Name, - "issuer_kind": crt.Spec.IssuerRef.Kind, - "issuer_group": crt.Spec.IssuerRef.Group, - }).Set(value) - } -} - -// RemoveCertificate will delete the Certificate metrics from continuing to be -// exposed. -func (m *Metrics) RemoveCertificate(key string) { - namespace, name, err := cache.SplitMetaNamespaceKey(key) - if err != nil { - m.log.Error(err, "failed to get namespace and name from key") - return - } - - m.certificateExpiryTimeSeconds.DeletePartialMatch(prometheus.Labels{"name": name, "namespace": namespace}) - m.certificateRenewalTimeSeconds.DeletePartialMatch(prometheus.Labels{"name": name, "namespace": namespace}) - m.certificateReadyStatus.DeletePartialMatch(prometheus.Labels{"name": name, "namespace": namespace}) -} diff --git a/pkg/metrics/certificates_test.go b/pkg/metrics/certificates_test.go index e530a4395a4..e5100646ee3 100644 --- a/pkg/metrics/certificates_test.go +++ b/pkg/metrics/certificates_test.go @@ -17,28 +17,40 @@ limitations under the License. package metrics import ( - "context" "strings" "testing" "time" - logtesting "github.com/go-logr/logr/testing" + "github.com/go-logr/logr/testr" "github.com/prometheus/client_golang/prometheus/testutil" + "github.com/stretchr/testify/assert" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/utils/clock" cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" + "github.com/cert-manager/cert-manager/pkg/client/clientset/versioned/fake" + "github.com/cert-manager/cert-manager/pkg/client/informers/externalversions" "github.com/cert-manager/cert-manager/test/unit/gen" ) +const notBeforeMetadata = ` + # HELP certmanager_certificate_not_before_timestamp_seconds The timestamp before which the certificate is invalid, expressed as a Unix Epoch Time. + # TYPE certmanager_certificate_not_before_timestamp_seconds gauge +` + +const notAfterMetadata = ` + # HELP certmanager_certificate_not_after_timestamp_seconds The timestamp after which the certificate is invalid, expressed as a Unix Epoch Time. + # TYPE certmanager_certificate_not_after_timestamp_seconds gauge +` + const expiryMetadata = ` - # HELP certmanager_certificate_expiration_timestamp_seconds The date after which the certificate expires. Expressed as a Unix Epoch Time. + # HELP certmanager_certificate_expiration_timestamp_seconds The timestamp after which the certificate expires, expressed in Unix Epoch Time. # TYPE certmanager_certificate_expiration_timestamp_seconds gauge ` const renewalTimeMetadata = ` - # HELP certmanager_certificate_renewal_timestamp_seconds The number of seconds before expiration time the certificate should renew. + # HELP certmanager_certificate_renewal_timestamp_seconds The timestamp after which the certificate should be renewed, expressed in Unix Epoch Time. # TYPE certmanager_certificate_renewal_timestamp_seconds gauge ` @@ -49,18 +61,21 @@ const readyMetadata = ` func TestCertificateMetrics(t *testing.T) { type testT struct { - crt *cmapi.Certificate - expectedExpiry, expectedReady, expectedRenewalTime string + crt *cmapi.Certificate + expectedNotBefore, expectedNotAfter, expectedExpiry, expectedReady, expectedRenewalTime string } tests := map[string]testT{ - "certificate with expiry and ready status": { + "certificate with issuance and expiry time, and ready status": { crt: gen.Certificate("test-certificate", gen.SetCertificateNamespace("test-ns"), - gen.SetCertificateIssuer(cmmeta.ObjectReference{ + gen.SetCertificateIssuer(cmmeta.IssuerReference{ Name: "test-issuer", Kind: "test-issuer-kind", Group: "test-issuer-group", }), + gen.SetCertificateNotBefore(metav1.Time{ + Time: time.Unix(100, 0), + }), gen.SetCertificateNotAfter(metav1.Time{ Time: time.Unix(2208988804, 0), }), @@ -69,8 +84,14 @@ func TestCertificateMetrics(t *testing.T) { Status: cmmeta.ConditionTrue, }), ), + expectedNotAfter: ` + certmanager_certificate_not_after_timestamp_seconds{issuer_group="test-issuer-group",issuer_kind="test-issuer-kind",issuer_name="test-issuer",name="test-certificate",namespace="test-ns"} 2.208988804e+09 +`, + expectedNotBefore: ` + certmanager_certificate_not_before_timestamp_seconds{issuer_group="test-issuer-group",issuer_kind="test-issuer-kind",issuer_name="test-issuer",name="test-certificate",namespace="test-ns"} 100 +`, expectedExpiry: ` - certmanager_certificate_expiration_timestamp_seconds{issuer_group="test-issuer-group",issuer_kind="test-issuer-kind",issuer_name="test-issuer",name="test-certificate",namespace="test-ns"} 2.208988804e+09 + certmanager_certificate_expiration_timestamp_seconds{issuer_group="test-issuer-group",issuer_kind="test-issuer-kind",issuer_name="test-issuer",name="test-certificate",namespace="test-ns"} 2.208988804e+09 `, expectedReady: ` certmanager_certificate_ready_status{condition="False",issuer_group="test-issuer-group",issuer_kind="test-issuer-kind",issuer_name="test-issuer",name="test-certificate",namespace="test-ns"} 0 @@ -82,17 +103,23 @@ func TestCertificateMetrics(t *testing.T) { `, }, - "certificate with no expiry and no status should give an expiry of 0 and Unknown status": { + "certificate with no expiry and no status should give an issuance and expiry of 0 and Unknown status": { crt: gen.Certificate("test-certificate", gen.SetCertificateNamespace("test-ns"), - gen.SetCertificateIssuer(cmmeta.ObjectReference{ + gen.SetCertificateIssuer(cmmeta.IssuerReference{ Name: "test-issuer", Kind: "test-issuer-kind", Group: "test-issuer-group", }), ), + expectedNotAfter: ` + certmanager_certificate_not_after_timestamp_seconds{issuer_group="test-issuer-group",issuer_kind="test-issuer-kind",issuer_name="test-issuer",name="test-certificate",namespace="test-ns"} 0 +`, + expectedNotBefore: ` + certmanager_certificate_not_before_timestamp_seconds{issuer_group="test-issuer-group",issuer_kind="test-issuer-kind",issuer_name="test-issuer",name="test-certificate",namespace="test-ns"} 0 +`, expectedExpiry: ` - certmanager_certificate_expiration_timestamp_seconds{issuer_group="test-issuer-group",issuer_kind="test-issuer-kind",issuer_name="test-issuer",name="test-certificate",namespace="test-ns"} 0 + certmanager_certificate_expiration_timestamp_seconds{issuer_group="test-issuer-group",issuer_kind="test-issuer-kind",issuer_name="test-issuer",name="test-certificate",namespace="test-ns"} 0 `, expectedReady: ` certmanager_certificate_ready_status{condition="False",issuer_group="test-issuer-group",issuer_kind="test-issuer-kind",issuer_name="test-issuer",name="test-certificate",namespace="test-ns"} 0 @@ -104,14 +131,17 @@ func TestCertificateMetrics(t *testing.T) { `, }, - "certificate with expiry and status False should give an expiry and False status": { + "certificate with issuance, expiry, and status False should give an expiry and False status": { crt: gen.Certificate("test-certificate", gen.SetCertificateNamespace("test-ns"), - gen.SetCertificateIssuer(cmmeta.ObjectReference{ + gen.SetCertificateIssuer(cmmeta.IssuerReference{ Name: "test-issuer", Kind: "test-issuer-kind", Group: "test-issuer-group", }), + gen.SetCertificateNotBefore(metav1.Time{ + Time: time.Unix(10, 0), + }), gen.SetCertificateNotAfter(metav1.Time{ Time: time.Unix(100, 0), }), @@ -120,8 +150,14 @@ func TestCertificateMetrics(t *testing.T) { Status: cmmeta.ConditionFalse, }), ), + expectedNotAfter: ` + certmanager_certificate_not_after_timestamp_seconds{issuer_group="test-issuer-group",issuer_kind="test-issuer-kind",issuer_name="test-issuer",name="test-certificate",namespace="test-ns"} 100 +`, + expectedNotBefore: ` + certmanager_certificate_not_before_timestamp_seconds{issuer_group="test-issuer-group",issuer_kind="test-issuer-kind",issuer_name="test-issuer",name="test-certificate",namespace="test-ns"} 10 +`, expectedExpiry: ` - certmanager_certificate_expiration_timestamp_seconds{issuer_group="test-issuer-group",issuer_kind="test-issuer-kind",issuer_name="test-issuer",name="test-certificate",namespace="test-ns"} 100 + certmanager_certificate_expiration_timestamp_seconds{issuer_group="test-issuer-group",issuer_kind="test-issuer-kind",issuer_name="test-issuer",name="test-certificate",namespace="test-ns"} 100 `, expectedReady: ` certmanager_certificate_ready_status{condition="False",issuer_group="test-issuer-group",issuer_kind="test-issuer-kind",issuer_name="test-issuer",name="test-certificate",namespace="test-ns"} 1 @@ -132,14 +168,17 @@ func TestCertificateMetrics(t *testing.T) { certmanager_certificate_renewal_timestamp_seconds{issuer_group="test-issuer-group",issuer_kind="test-issuer-kind",issuer_name="test-issuer",name="test-certificate",namespace="test-ns"} 0 `, }, - "certificate with expiry and status Unknown should give an expiry and Unknown status": { + "certificate with issuance, expiry, and status Unknown should give an expiry and Unknown status": { crt: gen.Certificate("test-certificate", gen.SetCertificateNamespace("test-ns"), - gen.SetCertificateIssuer(cmmeta.ObjectReference{ + gen.SetCertificateIssuer(cmmeta.IssuerReference{ Name: "test-issuer", Kind: "test-issuer-kind", Group: "test-issuer-group", }), + gen.SetCertificateNotBefore(metav1.Time{ + Time: time.Unix(10, 0), + }), gen.SetCertificateNotAfter(metav1.Time{ Time: time.Unix(99999, 0), }), @@ -148,8 +187,14 @@ func TestCertificateMetrics(t *testing.T) { Status: cmmeta.ConditionUnknown, }), ), + expectedNotAfter: ` + certmanager_certificate_not_after_timestamp_seconds{issuer_group="test-issuer-group",issuer_kind="test-issuer-kind",issuer_name="test-issuer",name="test-certificate",namespace="test-ns"} 99999 +`, + expectedNotBefore: ` + certmanager_certificate_not_before_timestamp_seconds{issuer_group="test-issuer-group",issuer_kind="test-issuer-kind",issuer_name="test-issuer",name="test-certificate",namespace="test-ns"} 10 +`, expectedExpiry: ` - certmanager_certificate_expiration_timestamp_seconds{issuer_group="test-issuer-group",issuer_kind="test-issuer-kind",issuer_name="test-issuer",name="test-certificate",namespace="test-ns"} 99999 + certmanager_certificate_expiration_timestamp_seconds{issuer_group="test-issuer-group",issuer_kind="test-issuer-kind",issuer_name="test-issuer",name="test-certificate",namespace="test-ns"} 99999 `, expectedReady: ` certmanager_certificate_ready_status{condition="False",issuer_group="test-issuer-group",issuer_kind="test-issuer-kind",issuer_name="test-issuer",name="test-certificate",namespace="test-ns"} 0 @@ -163,11 +208,14 @@ func TestCertificateMetrics(t *testing.T) { "certificate with expiry and ready status and renew before": { crt: gen.Certificate("test-certificate", gen.SetCertificateNamespace("test-ns"), - gen.SetCertificateIssuer(cmmeta.ObjectReference{ + gen.SetCertificateIssuer(cmmeta.IssuerReference{ Name: "test-issuer", Kind: "test-issuer-kind", Group: "test-issuer-group", }), + gen.SetCertificateNotBefore(metav1.Time{ + Time: time.Unix(10, 0), + }), gen.SetCertificateNotAfter(metav1.Time{ Time: time.Unix(2208988804, 0), }), @@ -179,8 +227,14 @@ func TestCertificateMetrics(t *testing.T) { Time: time.Unix(2208988804, 0), }), ), + expectedNotAfter: ` + certmanager_certificate_not_after_timestamp_seconds{issuer_group="test-issuer-group",issuer_kind="test-issuer-kind",issuer_name="test-issuer",name="test-certificate",namespace="test-ns"} 2.208988804e+09 +`, + expectedNotBefore: ` + certmanager_certificate_not_before_timestamp_seconds{issuer_group="test-issuer-group",issuer_kind="test-issuer-kind",issuer_name="test-issuer",name="test-certificate",namespace="test-ns"} 10 +`, expectedExpiry: ` - certmanager_certificate_expiration_timestamp_seconds{issuer_group="test-issuer-group",issuer_kind="test-issuer-kind",issuer_name="test-issuer",name="test-certificate",namespace="test-ns"} 2.208988804e+09 + certmanager_certificate_expiration_timestamp_seconds{issuer_group="test-issuer-group",issuer_kind="test-issuer-kind",issuer_name="test-issuer",name="test-certificate",namespace="test-ns"} 2.208988804e+09 `, expectedReady: ` certmanager_certificate_ready_status{condition="False",issuer_group="test-issuer-group",issuer_kind="test-issuer-kind",issuer_name="test-issuer",name="test-certificate",namespace="test-ns"} 0 @@ -194,43 +248,71 @@ func TestCertificateMetrics(t *testing.T) { } for n, test := range tests { t.Run(n, func(t *testing.T) { - m := New(logtesting.NewTestLogger(t), clock.RealClock{}) - m.UpdateCertificate(context.TODO(), test.crt) + m := New(testr.New(t), clock.RealClock{}) + + fakeClient := fake.NewClientset() + factory := externalversions.NewSharedInformerFactory(fakeClient, 0) + certsInformer := factory.Certmanager().V1().Certificates() + + err := certsInformer.Informer().GetIndexer().Add(test.crt) + assert.NoError(t, err) - if err := testutil.CollectAndCompare(m.certificateExpiryTimeSeconds, + m.SetupCertificateCollector(certsInformer.Lister()) + + if err := testutil.CollectAndCompare(m.certificateCollector, + strings.NewReader(notAfterMetadata+test.expectedNotAfter), + "certmanager_certificate_not_after_timestamp_seconds", + ); err != nil { + t.Errorf("unexpected collecting result:\n%s", err) + } + + if err := testutil.CollectAndCompare(m.certificateCollector, + strings.NewReader(notBeforeMetadata+test.expectedNotBefore), + "certmanager_certificate_not_before_timestamp_seconds", + ); err != nil { + t.Errorf("unexpected collecting result:\n%s", err) + } + + if err := testutil.CollectAndCompare(m.certificateCollector, strings.NewReader(expiryMetadata+test.expectedExpiry), "certmanager_certificate_expiration_timestamp_seconds", ); err != nil { t.Errorf("unexpected collecting result:\n%s", err) } - if err := testutil.CollectAndCompare(m.certificateRenewalTimeSeconds, + if err := testutil.CollectAndCompare(m.certificateCollector, strings.NewReader(renewalTimeMetadata+test.expectedRenewalTime), "certmanager_certificate_renewal_timestamp_seconds", ); err != nil { t.Errorf("unexpected collecting result:\n%s", err) } - if err := testutil.CollectAndCompare(m.certificateReadyStatus, + if err := testutil.CollectAndCompare(m.certificateCollector, strings.NewReader(readyMetadata+test.expectedReady), "certmanager_certificate_ready_status", ); err != nil { t.Errorf("unexpected collecting result:\n%s", err) } + + err = certsInformer.Informer().GetIndexer().Delete(test.crt) + assert.NoError(t, err) }) } } func TestCertificateCache(t *testing.T) { - m := New(logtesting.NewTestLogger(t), clock.RealClock{}) + m := New(testr.New(t), clock.RealClock{}) crt1 := gen.Certificate("crt1", gen.SetCertificateUID("uid-1"), - gen.SetCertificateIssuer(cmmeta.ObjectReference{ + gen.SetCertificateIssuer(cmmeta.IssuerReference{ Name: "test-issuer", Kind: "test-issuer-kind", Group: "test-issuer-group", }), + gen.SetCertificateNotBefore(metav1.Time{ + Time: time.Unix(99, 0), + }), gen.SetCertificateNotAfter(metav1.Time{ Time: time.Unix(100, 0), }), @@ -243,11 +325,14 @@ func TestCertificateCache(t *testing.T) { })) crt2 := gen.Certificate("crt2", gen.SetCertificateUID("uid-2"), - gen.SetCertificateIssuer(cmmeta.ObjectReference{ + gen.SetCertificateIssuer(cmmeta.IssuerReference{ Name: "test-issuer", Kind: "test-issuer-kind", Group: "test-issuer-group", }), + gen.SetCertificateNotBefore(metav1.Time{ + Time: time.Unix(199, 0), + }), gen.SetCertificateNotAfter(metav1.Time{ Time: time.Unix(200, 0), }), @@ -261,11 +346,14 @@ func TestCertificateCache(t *testing.T) { ) crt3 := gen.Certificate("crt3", gen.SetCertificateUID("uid-3"), - gen.SetCertificateIssuer(cmmeta.ObjectReference{ + gen.SetCertificateIssuer(cmmeta.IssuerReference{ Name: "test-issuer", Kind: "test-issuer-kind", Group: "test-issuer-group", }), + gen.SetCertificateNotBefore(metav1.Time{ + Time: time.Unix(299, 0), + }), gen.SetCertificateNotAfter(metav1.Time{ Time: time.Unix(300, 0), }), @@ -276,15 +364,24 @@ func TestCertificateCache(t *testing.T) { gen.SetCertificateRenewalTime(metav1.Time{ Time: time.Unix(300, 0), }), + gen.SetCertificateDuration(&metav1.Duration{Duration: time.Second}), ) - // Observe all three Certificate metrics - m.UpdateCertificate(context.TODO(), crt1) - m.UpdateCertificate(context.TODO(), crt2) - m.UpdateCertificate(context.TODO(), crt3) + fakeClient := fake.NewClientset() + factory := externalversions.NewSharedInformerFactory(fakeClient, 0) + certsInformer := factory.Certmanager().V1().Certificates() + + err := certsInformer.Informer().GetIndexer().Add(crt1) + assert.NoError(t, err) + err = certsInformer.Informer().GetIndexer().Add(crt2) + assert.NoError(t, err) + err = certsInformer.Informer().GetIndexer().Add(crt3) + assert.NoError(t, err) + + m.SetupCertificateCollector(certsInformer.Lister()) // Check all three metrics exist - if err := testutil.CollectAndCompare(m.certificateReadyStatus, + if err := testutil.CollectAndCompare(m.certificateCollector, strings.NewReader(readyMetadata+` certmanager_certificate_ready_status{condition="False",issuer_group="test-issuer-group",issuer_kind="test-issuer-kind",issuer_name="test-issuer",name="crt1",namespace="default-unit-test-ns"} 0 certmanager_certificate_ready_status{condition="False",issuer_group="test-issuer-group",issuer_kind="test-issuer-kind",issuer_name="test-issuer",name="crt2",namespace="default-unit-test-ns"} 0 @@ -300,7 +397,30 @@ func TestCertificateCache(t *testing.T) { ); err != nil { t.Errorf("unexpected collecting result:\n%s", err) } - if err := testutil.CollectAndCompare(m.certificateExpiryTimeSeconds, + + if err := testutil.CollectAndCompare(m.certificateCollector, + strings.NewReader(notAfterMetadata+` + certmanager_certificate_not_after_timestamp_seconds{issuer_group="test-issuer-group",issuer_kind="test-issuer-kind",issuer_name="test-issuer",name="crt1",namespace="default-unit-test-ns"} 100 + certmanager_certificate_not_after_timestamp_seconds{issuer_group="test-issuer-group",issuer_kind="test-issuer-kind",issuer_name="test-issuer",name="crt2",namespace="default-unit-test-ns"} 200 + certmanager_certificate_not_after_timestamp_seconds{issuer_group="test-issuer-group",issuer_kind="test-issuer-kind",issuer_name="test-issuer",name="crt3",namespace="default-unit-test-ns"} 300 +`), + "certmanager_certificate_not_after_timestamp_seconds", + ); err != nil { + t.Errorf("unexpected collecting result:\n%s", err) + } + + if err := testutil.CollectAndCompare(m.certificateCollector, + strings.NewReader(notBeforeMetadata+` + certmanager_certificate_not_before_timestamp_seconds{issuer_group="test-issuer-group",issuer_kind="test-issuer-kind",issuer_name="test-issuer",name="crt1",namespace="default-unit-test-ns"} 99 + certmanager_certificate_not_before_timestamp_seconds{issuer_group="test-issuer-group",issuer_kind="test-issuer-kind",issuer_name="test-issuer",name="crt2",namespace="default-unit-test-ns"} 199 + certmanager_certificate_not_before_timestamp_seconds{issuer_group="test-issuer-group",issuer_kind="test-issuer-kind",issuer_name="test-issuer",name="crt3",namespace="default-unit-test-ns"} 299 +`), + "certmanager_certificate_not_before_timestamp_seconds", + ); err != nil { + t.Errorf("unexpected collecting result:\n%s", err) + } + + if err := testutil.CollectAndCompare(m.certificateCollector, strings.NewReader(expiryMetadata+` certmanager_certificate_expiration_timestamp_seconds{issuer_group="test-issuer-group",issuer_kind="test-issuer-kind",issuer_name="test-issuer",name="crt1",namespace="default-unit-test-ns"} 100 certmanager_certificate_expiration_timestamp_seconds{issuer_group="test-issuer-group",issuer_kind="test-issuer-kind",issuer_name="test-issuer",name="crt2",namespace="default-unit-test-ns"} 200 @@ -311,7 +431,7 @@ func TestCertificateCache(t *testing.T) { t.Errorf("unexpected collecting result:\n%s", err) } - if err := testutil.CollectAndCompare(m.certificateRenewalTimeSeconds, + if err := testutil.CollectAndCompare(m.certificateCollector, strings.NewReader(renewalTimeMetadata+` certmanager_certificate_renewal_timestamp_seconds{issuer_group="test-issuer-group",issuer_kind="test-issuer-kind",issuer_name="test-issuer",name="crt1",namespace="default-unit-test-ns"} 100 certmanager_certificate_renewal_timestamp_seconds{issuer_group="test-issuer-group",issuer_kind="test-issuer-kind",issuer_name="test-issuer",name="crt2",namespace="default-unit-test-ns"} 200 @@ -323,44 +443,71 @@ func TestCertificateCache(t *testing.T) { } // Remove second certificate and check not exists - m.RemoveCertificate("default-unit-test-ns/crt2") - if err := testutil.CollectAndCompare(m.certificateReadyStatus, + err = certsInformer.Informer().GetIndexer().Delete(crt2) + assert.NoError(t, err) + + if err := testutil.CollectAndCompare(m.certificateCollector, strings.NewReader(readyMetadata+` - certmanager_certificate_ready_status{condition="False",issuer_group="test-issuer-group",issuer_kind="test-issuer-kind",issuer_name="test-issuer",name="crt1",namespace="default-unit-test-ns"} 0 - certmanager_certificate_ready_status{condition="False",issuer_group="test-issuer-group",issuer_kind="test-issuer-kind",issuer_name="test-issuer",name="crt3",namespace="default-unit-test-ns"} 1 - certmanager_certificate_ready_status{condition="True",issuer_group="test-issuer-group",issuer_kind="test-issuer-kind",issuer_name="test-issuer",name="crt1",namespace="default-unit-test-ns"} 0 - certmanager_certificate_ready_status{condition="True",issuer_group="test-issuer-group",issuer_kind="test-issuer-kind",issuer_name="test-issuer",name="crt3",namespace="default-unit-test-ns"} 0 - certmanager_certificate_ready_status{condition="Unknown",issuer_group="test-issuer-group",issuer_kind="test-issuer-kind",issuer_name="test-issuer",name="crt1",namespace="default-unit-test-ns"} 1 - certmanager_certificate_ready_status{condition="Unknown",issuer_group="test-issuer-group",issuer_kind="test-issuer-kind",issuer_name="test-issuer",name="crt3",namespace="default-unit-test-ns"} 0 -`), + certmanager_certificate_ready_status{condition="False",issuer_group="test-issuer-group",issuer_kind="test-issuer-kind",issuer_name="test-issuer",name="crt1",namespace="default-unit-test-ns"} 0 + certmanager_certificate_ready_status{condition="False",issuer_group="test-issuer-group",issuer_kind="test-issuer-kind",issuer_name="test-issuer",name="crt3",namespace="default-unit-test-ns"} 1 + certmanager_certificate_ready_status{condition="True",issuer_group="test-issuer-group",issuer_kind="test-issuer-kind",issuer_name="test-issuer",name="crt1",namespace="default-unit-test-ns"} 0 + certmanager_certificate_ready_status{condition="True",issuer_group="test-issuer-group",issuer_kind="test-issuer-kind",issuer_name="test-issuer",name="crt3",namespace="default-unit-test-ns"} 0 + certmanager_certificate_ready_status{condition="Unknown",issuer_group="test-issuer-group",issuer_kind="test-issuer-kind",issuer_name="test-issuer",name="crt1",namespace="default-unit-test-ns"} 1 + certmanager_certificate_ready_status{condition="Unknown",issuer_group="test-issuer-group",issuer_kind="test-issuer-kind",issuer_name="test-issuer",name="crt3",namespace="default-unit-test-ns"} 0 + `), "certmanager_certificate_ready_status", ); err != nil { t.Errorf("unexpected collecting result:\n%s", err) } - if err := testutil.CollectAndCompare(m.certificateExpiryTimeSeconds, + + if err := testutil.CollectAndCompare(m.certificateCollector, strings.NewReader(expiryMetadata+` - certmanager_certificate_expiration_timestamp_seconds{issuer_group="test-issuer-group",issuer_kind="test-issuer-kind",issuer_name="test-issuer",name="crt1",namespace="default-unit-test-ns"} 100 - certmanager_certificate_expiration_timestamp_seconds{issuer_group="test-issuer-group",issuer_kind="test-issuer-kind",issuer_name="test-issuer",name="crt3",namespace="default-unit-test-ns"} 300 -`), + certmanager_certificate_expiration_timestamp_seconds{issuer_group="test-issuer-group",issuer_kind="test-issuer-kind",issuer_name="test-issuer",name="crt1",namespace="default-unit-test-ns"} 100 + certmanager_certificate_expiration_timestamp_seconds{issuer_group="test-issuer-group",issuer_kind="test-issuer-kind",issuer_name="test-issuer",name="crt3",namespace="default-unit-test-ns"} 300 + `), "certmanager_certificate_expiration_timestamp_seconds", ); err != nil { t.Errorf("unexpected collecting result:\n%s", err) } - // Remove all Certificates (even is already removed) and observe no Certificates - m.RemoveCertificate("default-unit-test-ns/crt1") - m.RemoveCertificate("default-unit-test-ns/crt2") - m.RemoveCertificate("default-unit-test-ns/crt3") - if err := testutil.CollectAndCompare(m.certificateReadyStatus, - strings.NewReader(readyMetadata), - "certmanager_certificate_ready_status", + if err := testutil.CollectAndCompare(m.certificateCollector, + strings.NewReader(notAfterMetadata+` + certmanager_certificate_not_after_timestamp_seconds{issuer_group="test-issuer-group",issuer_kind="test-issuer-kind",issuer_name="test-issuer",name="crt1",namespace="default-unit-test-ns"} 100 + certmanager_certificate_not_after_timestamp_seconds{issuer_group="test-issuer-group",issuer_kind="test-issuer-kind",issuer_name="test-issuer",name="crt3",namespace="default-unit-test-ns"} 300 + `), + "certmanager_certificate_not_after_timestamp_seconds", ); err != nil { t.Errorf("unexpected collecting result:\n%s", err) } - if err := testutil.CollectAndCompare(m.certificateExpiryTimeSeconds, - strings.NewReader(expiryMetadata), - "certmanager_certificate_expiration_timestamp_seconds", + + if err := testutil.CollectAndCompare(m.certificateCollector, + strings.NewReader(notBeforeMetadata+` + certmanager_certificate_not_before_timestamp_seconds{issuer_group="test-issuer-group",issuer_kind="test-issuer-kind",issuer_name="test-issuer",name="crt1",namespace="default-unit-test-ns"} 99 + certmanager_certificate_not_before_timestamp_seconds{issuer_group="test-issuer-group",issuer_kind="test-issuer-kind",issuer_name="test-issuer",name="crt3",namespace="default-unit-test-ns"} 299 + `), + "certmanager_certificate_not_before_timestamp_seconds", ); err != nil { t.Errorf("unexpected collecting result:\n%s", err) } + + // Remove all Certificates (even is already removed) and observe no Certificates + err = certsInformer.Informer().GetIndexer().Delete(crt1) + assert.NoError(t, err) + err = certsInformer.Informer().GetIndexer().Delete(crt2) + assert.NoError(t, err) + err = certsInformer.Informer().GetIndexer().Delete(crt3) + assert.NoError(t, err) + + if testutil.CollectAndCount(m.certificateCollector, "certmanager_certificate_ready_status") != 0 { + t.Errorf("unexpected collecting result") + } + if testutil.CollectAndCount(m.certificateCollector, "certmanager_certificate_expiration_timestamp_seconds") != 0 { + t.Errorf("unexpected collecting result") + } + if testutil.CollectAndCount(m.certificateCollector, "certmanager_certificate_not_after_timestamp_seconds") != 0 { + t.Errorf("unexpected collecting result") + } + if testutil.CollectAndCount(m.certificateCollector, "certmanager_certificate_not_before_timestamp_seconds") != 0 { + t.Errorf("unexpected collecting result") + } } diff --git a/pkg/metrics/clusterissuer_test.go b/pkg/metrics/clusterissuer_test.go new file mode 100644 index 00000000000..b4673002244 --- /dev/null +++ b/pkg/metrics/clusterissuer_test.go @@ -0,0 +1,110 @@ +/* +Copyright 2020 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. +*/ + +package metrics + +import ( + "strings" + "testing" + + "github.com/go-logr/logr/testr" + "github.com/prometheus/client_golang/prometheus/testutil" + "github.com/stretchr/testify/assert" + "k8s.io/utils/clock" + + cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" + cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" + "github.com/cert-manager/cert-manager/pkg/client/clientset/versioned/fake" + "github.com/cert-manager/cert-manager/pkg/client/informers/externalversions" + "github.com/cert-manager/cert-manager/test/unit/gen" +) + +const cissReadyMetadata = ` + # HELP certmanager_clusterissuer_ready_status The ready status of the ClusterIssuer. + # TYPE certmanager_clusterissuer_ready_status gauge +` + +func TestClusterIssuerMetrics(t *testing.T) { + type testT struct { + ciss *cmapi.ClusterIssuer + expectedReady string + } + tests := map[string]testT{ + "clusterissuer with ready status True": { + ciss: gen.ClusterIssuer("test-clusterissuer", + gen.AddIssuerCondition(cmapi.IssuerCondition{ + Type: cmapi.IssuerConditionReady, + Status: cmmeta.ConditionTrue, + }), + ), + expectedReady: ` + certmanager_clusterissuer_ready_status{condition="True",name="test-clusterissuer"} 1 + certmanager_clusterissuer_ready_status{condition="False",name="test-clusterissuer"} 0 + certmanager_clusterissuer_ready_status{condition="Unknown",name="test-clusterissuer"} 0 +`, + }, + "clusterissuer with ready status False": { + ciss: gen.ClusterIssuer("test-clusterissuer", + gen.AddIssuerCondition(cmapi.IssuerCondition{ + Type: cmapi.IssuerConditionReady, + Status: cmmeta.ConditionFalse, + }), + ), + expectedReady: ` + certmanager_clusterissuer_ready_status{condition="True",name="test-clusterissuer"} 0 + certmanager_clusterissuer_ready_status{condition="False",name="test-clusterissuer"} 1 + certmanager_clusterissuer_ready_status{condition="Unknown",name="test-clusterissuer"} 0 +`, + }, + "clusterissuer with ready status Unknown": { + ciss: gen.ClusterIssuer("test-clusterissuer", + gen.AddIssuerCondition(cmapi.IssuerCondition{ + Type: cmapi.IssuerConditionReady, + Status: cmmeta.ConditionUnknown, + }), + ), + expectedReady: ` + certmanager_clusterissuer_ready_status{condition="True",name="test-clusterissuer"} 0 + certmanager_clusterissuer_ready_status{condition="False",name="test-clusterissuer"} 0 + certmanager_clusterissuer_ready_status{condition="Unknown",name="test-clusterissuer"} 1 +`, + }, + } + for n, test := range tests { + t.Run(n, func(t *testing.T) { + m := New(testr.New(t), clock.RealClock{}) + + fakeClient := fake.NewClientset() + factory := externalversions.NewSharedInformerFactory(fakeClient, 0) + cissInformer := factory.Certmanager().V1().ClusterIssuers() + + err := cissInformer.Informer().GetIndexer().Add(test.ciss) + assert.NoError(t, err) + + m.SetupClusterIssuerCollector(cissInformer.Lister()) + + if err := testutil.CollectAndCompare(m.clusterIssuerCollector, + strings.NewReader(cissReadyMetadata+test.expectedReady), + "certmanager_clusterissuer_ready_status", + ); err != nil { + t.Errorf("unexpected collecting result:\n%s", err) + } + + err = cissInformer.Informer().GetIndexer().Delete(test.ciss) + assert.NoError(t, err) + }) + } +} diff --git a/pkg/metrics/issuer_test.go b/pkg/metrics/issuer_test.go new file mode 100644 index 00000000000..649fe6832db --- /dev/null +++ b/pkg/metrics/issuer_test.go @@ -0,0 +1,113 @@ +/* +Copyright 2020 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. +*/ + +package metrics + +import ( + "strings" + "testing" + + "github.com/go-logr/logr/testr" + "github.com/prometheus/client_golang/prometheus/testutil" + "github.com/stretchr/testify/assert" + "k8s.io/utils/clock" + + cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" + cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" + "github.com/cert-manager/cert-manager/pkg/client/clientset/versioned/fake" + "github.com/cert-manager/cert-manager/pkg/client/informers/externalversions" + "github.com/cert-manager/cert-manager/test/unit/gen" +) + +const issReadyMetadata = ` + # HELP certmanager_issuer_ready_status The ready status of the Issuer. + # TYPE certmanager_issuer_ready_status gauge +` + +func TestIssuerMetrics(t *testing.T) { + type testT struct { + iss *cmapi.Issuer + expectedReady string + } + tests := map[string]testT{ + "issuer with ready status True": { + iss: gen.Issuer("test-issuer", + gen.SetIssuerNamespace("test-ns"), + gen.AddIssuerCondition(cmapi.IssuerCondition{ + Type: cmapi.IssuerConditionReady, + Status: cmmeta.ConditionTrue, + }), + ), + expectedReady: ` + certmanager_issuer_ready_status{condition="True",name="test-issuer",namespace="test-ns"} 1 + certmanager_issuer_ready_status{condition="False",name="test-issuer",namespace="test-ns"} 0 + certmanager_issuer_ready_status{condition="Unknown",name="test-issuer",namespace="test-ns"} 0 +`, + }, + "issuer with ready status False": { + iss: gen.Issuer("test-issuer", + gen.SetIssuerNamespace("test-ns"), + gen.AddIssuerCondition(cmapi.IssuerCondition{ + Type: cmapi.IssuerConditionReady, + Status: cmmeta.ConditionFalse, + }), + ), + expectedReady: ` + certmanager_issuer_ready_status{condition="True",name="test-issuer",namespace="test-ns"} 0 + certmanager_issuer_ready_status{condition="False",name="test-issuer",namespace="test-ns"} 1 + certmanager_issuer_ready_status{condition="Unknown",name="test-issuer",namespace="test-ns"} 0 +`, + }, + "issuer with ready status Unknown": { + iss: gen.Issuer("test-issuer", + gen.SetIssuerNamespace("test-ns"), + gen.AddIssuerCondition(cmapi.IssuerCondition{ + Type: cmapi.IssuerConditionReady, + Status: cmmeta.ConditionUnknown, + }), + ), + expectedReady: ` + certmanager_issuer_ready_status{condition="True",name="test-issuer",namespace="test-ns"} 0 + certmanager_issuer_ready_status{condition="False",name="test-issuer",namespace="test-ns"} 0 + certmanager_issuer_ready_status{condition="Unknown",name="test-issuer",namespace="test-ns"} 1 +`, + }, + } + for n, test := range tests { + t.Run(n, func(t *testing.T) { + m := New(testr.New(t), clock.RealClock{}) + + fakeClient := fake.NewClientset() + factory := externalversions.NewSharedInformerFactory(fakeClient, 0) + issInformer := factory.Certmanager().V1().Issuers() + + err := issInformer.Informer().GetIndexer().Add(test.iss) + assert.NoError(t, err) + + m.SetupIssuerCollector(issInformer.Lister()) + + if err := testutil.CollectAndCompare(m.issuerCollector, + strings.NewReader(issReadyMetadata+test.expectedReady), + "certmanager_issuer_ready_status", + ); err != nil { + t.Errorf("unexpected collecting result:\n%s", err) + } + + err = issInformer.Informer().GetIndexer().Delete(test.iss) + assert.NoError(t, err) + }) + } +} diff --git a/pkg/metrics/metrics.go b/pkg/metrics/metrics.go index e3032375ed9..50adcc6cbf0 100644 --- a/pkg/metrics/metrics.go +++ b/pkg/metrics/metrics.go @@ -19,8 +19,9 @@ limitations under the License. // certificate_expiration_timestamp_seconds{name, namespace, issuer_name, issuer_kind, issuer_group} // certificate_renewal_timestamp_seconds{name, namespace, issuer_name, issuer_kind, issuer_group} // certificate_ready_status{name, namespace, condition, issuer_name, issuer_kind, issuer_group} -// acme_client_request_count{"scheme", "host", "path", "method", "status"} -// acme_client_request_duration_seconds{"scheme", "host", "path", "method", "status"} +// certificate_challenge_status{status, domain, reason, processing, id, type} +// acme_client_request_count{"scheme", "host", "action", "method", "status"} +// acme_client_request_duration_seconds{"scheme", "host", "action", "method", "status"} // venafi_client_request_duration_seconds{"scheme", "host", "path", "method", "status"} // controller_sync_call_count{"controller"} package metrics @@ -32,10 +33,13 @@ import ( "github.com/go-logr/logr" "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/collectors" "github.com/prometheus/client_golang/prometheus/promhttp" "k8s.io/utils/clock" - cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" + cmcollectors "github.com/cert-manager/cert-manager/internal/collectors" + cmacmelisters "github.com/cert-manager/cert-manager/pkg/client/listers/acme/v1" + cmlisters "github.com/cert-manager/cert-manager/pkg/client/listers/certmanager/v1" ) const ( @@ -54,23 +58,23 @@ type Metrics struct { clockTimeSeconds prometheus.CounterFunc clockTimeSecondsGauge prometheus.GaugeFunc - certificateExpiryTimeSeconds *prometheus.GaugeVec - certificateRenewalTimeSeconds *prometheus.GaugeVec - certificateReadyStatus *prometheus.GaugeVec acmeClientRequestDurationSeconds *prometheus.SummaryVec acmeClientRequestCount *prometheus.CounterVec venafiClientRequestDurationSeconds *prometheus.SummaryVec controllerSyncCallCount *prometheus.CounterVec controllerSyncErrorCount *prometheus.CounterVec + challengeCollector prometheus.Collector + certificateCollector prometheus.Collector + issuerCollector prometheus.Collector + clusterIssuerCollector prometheus.Collector } -var readyConditionStatuses = [...]cmmeta.ConditionStatus{cmmeta.ConditionTrue, cmmeta.ConditionFalse, cmmeta.ConditionUnknown} - // New creates a Metrics struct and populates it with prometheus metric types. func New(log logr.Logger, c clock.Clock) *Metrics { var ( // Deprecated in favour of clock_time_seconds_gauge. clockTimeSeconds = prometheus.NewCounterFunc( + //nolint:promlinter // This metric is deprecated and should be removed prometheus.CounterOpts{ Namespace: namespace, Name: "clock_time_seconds", @@ -90,66 +94,47 @@ func New(log logr.Logger, c clock.Clock) *Metrics { // a new `clock_time_seconds_gauge` metric of type gauge is added which // implements the same thing. clockTimeSecondsGauge = prometheus.NewGaugeFunc( + //nolint:promlinter prometheus.GaugeOpts{ Namespace: namespace, Name: "clock_time_seconds_gauge", - Help: "The clock time given in seconds (from 1970/01/01 UTC).", + Help: "The clock time given in seconds (from 1970/01/01 UTC). Gauge form of the deprecated clock_time_seconds counter. No labels.", }, func() float64 { return float64(c.Now().Unix()) }, ) - certificateExpiryTimeSeconds = prometheus.NewGaugeVec( - prometheus.GaugeOpts{ - Namespace: namespace, - Name: "certificate_expiration_timestamp_seconds", - Help: "The date after which the certificate expires. Expressed as a Unix Epoch Time.", - }, - []string{"name", "namespace", "issuer_name", "issuer_kind", "issuer_group"}, - ) - - certificateRenewalTimeSeconds = prometheus.NewGaugeVec( - prometheus.GaugeOpts{ - Namespace: namespace, - Name: "certificate_renewal_timestamp_seconds", - Help: "The number of seconds before expiration time the certificate should renew.", - }, - []string{"name", "namespace", "issuer_name", "issuer_kind", "issuer_group"}, - ) - - certificateReadyStatus = prometheus.NewGaugeVec( - prometheus.GaugeOpts{ - Namespace: namespace, - Name: "certificate_ready_status", - Help: "The ready status of the certificate.", - }, - []string{"name", "namespace", "condition", "issuer_name", "issuer_kind", "issuer_group"}, - ) - // acmeClientRequestCount is a Prometheus summary to collect the number of // requests made to each endpoint with the ACME client. acmeClientRequestCount = prometheus.NewCounterVec( + //nolint:promlinter prometheus.CounterOpts{ Namespace: namespace, Name: "acme_client_request_count", - Help: "The number of requests made by the ACME client.", + Help: "Total number of outbound ACME HTTP requests. " + + "Labels: scheme (http/https), host (ACME host), action (logical ACME operation), " + + "method (HTTP verb), status (HTTP status code).", Subsystem: "http", }, - []string{"scheme", "host", "path", "method", "status"}, + []string{"scheme", "host", "action", "method", "status"}, ) // acmeClientRequestDurationSeconds is a Prometheus summary to collect request // times for the ACME client. acmeClientRequestDurationSeconds = prometheus.NewSummaryVec( prometheus.SummaryOpts{ - Namespace: namespace, - Name: "acme_client_request_duration_seconds", - Help: "The HTTP request latencies in seconds for the ACME client.", + Namespace: namespace, + Name: "acme_client_request_duration_seconds", + Help: "Latency of outbound ACME HTTP requests in seconds. " + + "Summary quantiles approximate request distribution. " + + "Labels: scheme (http/https), host (ACME host), action (logical ACME operation), " + + "method (HTTP verb), status (HTTP status code). " + + "Use with acme_client_request_count for rate/error analysis.", Subsystem: "http", Objectives: map[float64]float64{0.5: 0.05, 0.9: 0.01, 0.99: 0.001}, }, - []string{"scheme", "host", "path", "method", "status"}, + []string{"scheme", "host", "action", "method", "status"}, ) // venafiClientRequestDurationSeconds is a Prometheus summary to @@ -168,34 +153,39 @@ func New(log logr.Logger, c clock.Clock) *Metrics { ) controllerSyncCallCount = prometheus.NewCounterVec( + //nolint:promlinter prometheus.CounterOpts{ Namespace: namespace, Name: "controller_sync_call_count", - Help: "The number of sync() calls made by a controller.", + Help: "The number of sync() calls made by a controller. Label: controller (fixed small set of controller names).", }, []string{"controller"}, ) controllerSyncErrorCount = prometheus.NewCounterVec( + //nolint:promlinter prometheus.CounterOpts{ Namespace: namespace, Name: "controller_sync_error_count", - Help: "The number of errors encountered during controller sync().", + Help: "The number of errors encountered during controller sync(). Label: controller. Use with controller_sync_call_count to derive error rates.", }, []string{"controller"}, ) ) + // Create Registry and register the recommended collectors + registry := prometheus.NewRegistry() + registry.MustRegister( + collectors.NewProcessCollector(collectors.ProcessCollectorOpts{}), + collectors.NewGoCollector(), + ) // Create server and register Prometheus metrics handler m := &Metrics{ log: log.WithName("metrics"), - registry: prometheus.NewRegistry(), + registry: registry, clockTimeSeconds: clockTimeSeconds, clockTimeSecondsGauge: clockTimeSecondsGauge, - certificateExpiryTimeSeconds: certificateExpiryTimeSeconds, - certificateRenewalTimeSeconds: certificateRenewalTimeSeconds, - certificateReadyStatus: certificateReadyStatus, acmeClientRequestCount: acmeClientRequestCount, acmeClientRequestDurationSeconds: acmeClientRequestDurationSeconds, venafiClientRequestDurationSeconds: venafiClientRequestDurationSeconds, @@ -206,19 +196,52 @@ func New(log logr.Logger, c clock.Clock) *Metrics { return m } +func (m *Metrics) SetupACMECollector(acmeInformers cmacmelisters.ChallengeLister) { + m.challengeCollector = cmcollectors.NewACMECollector(acmeInformers) +} + +func (m *Metrics) SetupCertificateCollector(certLister cmlisters.CertificateLister) { + m.certificateCollector = cmcollectors.NewCertificateCollector(certLister) +} + +func (m *Metrics) SetupIssuerCollector(issuerLister cmlisters.IssuerLister) { + m.issuerCollector = cmcollectors.NewIssuerCollector(issuerLister) +} + +func (m *Metrics) SetupClusterIssuerCollector(clusterIssuerLister cmlisters.ClusterIssuerLister) { + m.clusterIssuerCollector = cmcollectors.NewClusterIssuerCollector(clusterIssuerLister) +} + +func (m *Metrics) ACMERequestCounter() *prometheus.CounterVec { + return m.acmeClientRequestCount +} + // NewServer registers Prometheus metrics and returns a new Prometheus metrics HTTP server. func (m *Metrics) NewServer(ln net.Listener) *http.Server { m.registry.MustRegister(m.clockTimeSeconds) m.registry.MustRegister(m.clockTimeSecondsGauge) - m.registry.MustRegister(m.certificateExpiryTimeSeconds) - m.registry.MustRegister(m.certificateRenewalTimeSeconds) - m.registry.MustRegister(m.certificateReadyStatus) m.registry.MustRegister(m.acmeClientRequestDurationSeconds) m.registry.MustRegister(m.venafiClientRequestDurationSeconds) m.registry.MustRegister(m.acmeClientRequestCount) m.registry.MustRegister(m.controllerSyncCallCount) m.registry.MustRegister(m.controllerSyncErrorCount) + if m.challengeCollector != nil { + m.registry.MustRegister(m.challengeCollector) + } + + if m.certificateCollector != nil { + m.registry.MustRegister(m.certificateCollector) + } + + if m.issuerCollector != nil { + m.registry.MustRegister(m.issuerCollector) + } + + if m.clusterIssuerCollector != nil { + m.registry.MustRegister(m.clusterIssuerCollector) + } + mux := http.NewServeMux() mux.Handle("/metrics", promhttp.HandlerFor(m.registry, promhttp.HandlerOpts{})) diff --git a/pkg/metrics/metrics_test.go b/pkg/metrics/metrics_test.go index 97b04ee8dbe..8fe83fc518a 100644 --- a/pkg/metrics/metrics_test.go +++ b/pkg/metrics/metrics_test.go @@ -22,17 +22,21 @@ import ( "testing" "time" - logtesting "github.com/go-logr/logr/testing" + "github.com/go-logr/logr/testr" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/testutil" "github.com/stretchr/testify/assert" - fakeclock "k8s.io/utils/clock/testing" + + acmemeta "github.com/cert-manager/cert-manager/pkg/apis/acme/v1" + "github.com/cert-manager/cert-manager/pkg/client/clientset/versioned/fake" + "github.com/cert-manager/cert-manager/pkg/client/informers/externalversions" + "github.com/cert-manager/cert-manager/test/unit/gen" ) func Test_clockTimeSeconds(t *testing.T) { fixedClock := fakeclock.NewFakeClock(time.Now()) - m := New(logtesting.NewTestLogger(t), fixedClock) + m := New(testr.New(t), fixedClock) tests := map[string]struct { metricName string @@ -53,7 +57,7 @@ certmanager_clock_time_seconds %f metricName: "certmanager_clock_time_seconds_gauge", metric: m.clockTimeSecondsGauge, expected: fmt.Sprintf(` -# HELP certmanager_clock_time_seconds_gauge The clock time given in seconds (from 1970/01/01 UTC). +# HELP certmanager_clock_time_seconds_gauge The clock time given in seconds (from 1970/01/01 UTC). Gauge form of the deprecated clock_time_seconds counter. No labels. # TYPE certmanager_clock_time_seconds_gauge gauge certmanager_clock_time_seconds_gauge %f `, float64(fixedClock.Now().Unix())), @@ -68,3 +72,73 @@ certmanager_clock_time_seconds_gauge %f }) } } + +func Test_ACMEChallenges(t *testing.T) { + fixedClock := fakeclock.NewFakeClock(time.Now()) + m := New(testr.New(t), fixedClock) + + challenges := make([]*acmemeta.Challenge, 0) + challenges = append(challenges, gen.Challenge("test-challenge-status", + gen.SetChallengeDNSName("example.com"), + gen.SetChallengeProcessing(false), + gen.SetChallengeType(acmemeta.ACMEChallengeTypeDNS01), + gen.SetChallengeState(acmemeta.Pending), + gen.SetChallengeNamespace("test-challenge"), + ), gen.Challenge("test-challenge-status-1", + gen.SetChallengeDNSName("example.com"), + gen.SetChallengeProcessing(false), + gen.SetChallengeType(acmemeta.ACMEChallengeTypeDNS01), + gen.SetChallengeState(acmemeta.Ready), + gen.SetChallengeNamespace("test-challenge"), + )) + + fakeClient := fake.NewClientset() + factory := externalversions.NewSharedInformerFactory(fakeClient, 0) + challengesInformer := factory.Acme().V1().Challenges() + for _, ch := range challenges { + err := challengesInformer.Informer().GetIndexer().Add(ch) + assert.NoError(t, err) + } + + m.SetupACMECollector(challengesInformer.Lister()) + + tests := map[string]struct { + metricName string + metric prometheus.Collector + + expected string + }{ + "challenge_status": { + metricName: "certmanager_certificate_challenge_status", + metric: m.challengeCollector, + expected: ` +# HELP certmanager_certificate_challenge_status The status of certificate challenges +# TYPE certmanager_certificate_challenge_status gauge +certmanager_certificate_challenge_status{domain="example.com",name="test-challenge-status",namespace="test-challenge",processing="false",reason="",status="",type="DNS-01"} 0 +certmanager_certificate_challenge_status{domain="example.com",name="test-challenge-status",namespace="test-challenge",processing="false",reason="",status="errored",type="DNS-01"} 0 +certmanager_certificate_challenge_status{domain="example.com",name="test-challenge-status",namespace="test-challenge",processing="false",reason="",status="expired",type="DNS-01"} 0 +certmanager_certificate_challenge_status{domain="example.com",name="test-challenge-status",namespace="test-challenge",processing="false",reason="",status="invalid",type="DNS-01"} 0 +certmanager_certificate_challenge_status{domain="example.com",name="test-challenge-status",namespace="test-challenge",processing="false",reason="",status="pending",type="DNS-01"} 1 +certmanager_certificate_challenge_status{domain="example.com",name="test-challenge-status",namespace="test-challenge",processing="false",reason="",status="processing",type="DNS-01"} 0 +certmanager_certificate_challenge_status{domain="example.com",name="test-challenge-status",namespace="test-challenge",processing="false",reason="",status="ready",type="DNS-01"} 0 +certmanager_certificate_challenge_status{domain="example.com",name="test-challenge-status",namespace="test-challenge",processing="false",reason="",status="valid",type="DNS-01"} 0 +certmanager_certificate_challenge_status{domain="example.com",name="test-challenge-status-1",namespace="test-challenge",processing="false",reason="",status="",type="DNS-01"} 0 +certmanager_certificate_challenge_status{domain="example.com",name="test-challenge-status-1",namespace="test-challenge",processing="false",reason="",status="errored",type="DNS-01"} 0 +certmanager_certificate_challenge_status{domain="example.com",name="test-challenge-status-1",namespace="test-challenge",processing="false",reason="",status="expired",type="DNS-01"} 0 +certmanager_certificate_challenge_status{domain="example.com",name="test-challenge-status-1",namespace="test-challenge",processing="false",reason="",status="invalid",type="DNS-01"} 0 +certmanager_certificate_challenge_status{domain="example.com",name="test-challenge-status-1",namespace="test-challenge",processing="false",reason="",status="pending",type="DNS-01"} 0 +certmanager_certificate_challenge_status{domain="example.com",name="test-challenge-status-1",namespace="test-challenge",processing="false",reason="",status="processing",type="DNS-01"} 0 +certmanager_certificate_challenge_status{domain="example.com",name="test-challenge-status-1",namespace="test-challenge",processing="false",reason="",status="ready",type="DNS-01"} 1 +certmanager_certificate_challenge_status{domain="example.com",name="test-challenge-status-1",namespace="test-challenge",processing="false",reason="",status="valid",type="DNS-01"} 0 + `, + }, + } + + for name, test := range tests { + t.Run(name, func(t *testing.T) { + assert.NoError(t, + testutil.CollectAndCompare(test.metric, strings.NewReader(test.expected), test.metricName), + ) + }) + } +} diff --git a/pkg/scheduler/scheduler.go b/pkg/scheduler/scheduler.go index 4a7624e3c59..1c95992000c 100644 --- a/pkg/scheduler/scheduler.go +++ b/pkg/scheduler/scheduler.go @@ -23,7 +23,7 @@ import ( "k8s.io/utils/clock" ) -// We are writting our own time.AfterFunc to be able to mock the clock. The +// We are writing our own time.AfterFunc to be able to mock the clock. The // cancel function can be called concurrently. func afterFunc(c clock.Clock, d time.Duration, f func()) (cancel func()) { t := c.NewTimer(d) @@ -54,24 +54,25 @@ func afterFunc(c clock.Clock, d time.Duration, f func()) (cancel func()) { } // ProcessFunc is a function to process an item in the work queue. -type ProcessFunc func(interface{}) +type ProcessFunc[T comparable] func(T) // ScheduledWorkQueue is an interface to describe a queue that will execute the // given ProcessFunc with the object given to Add once the time.Duration is up, // since the time of calling Add. -type ScheduledWorkQueue interface { +type ScheduledWorkQueue[T comparable] interface { // Add will add an item to this queue, executing the ProcessFunc after the // Duration has come (since the time Add was called). If an existing Timer // for obj already exists, the previous timer will be cancelled. - Add(interface{}, time.Duration) + Add(T, time.Duration) + // Forget will cancel the timer for the given object, if the timer exists. - Forget(interface{}) + Forget(T) } -type scheduledWorkQueue struct { - processFunc ProcessFunc +type scheduledWorkQueue[T comparable] struct { + processFunc ProcessFunc[T] clock clock.Clock - work map[interface{}]func() + work map[T]func() workLock sync.Mutex // Testing purposes. @@ -79,11 +80,11 @@ type scheduledWorkQueue struct { } // NewScheduledWorkQueue will create a new workqueue with the given processFunc -func NewScheduledWorkQueue(clock clock.Clock, processFunc ProcessFunc) ScheduledWorkQueue { - return &scheduledWorkQueue{ +func NewScheduledWorkQueue[T comparable](clock clock.Clock, processFunc ProcessFunc[T]) ScheduledWorkQueue[T] { + return &scheduledWorkQueue[T]{ processFunc: processFunc, clock: clock, - work: make(map[interface{}]func()), + work: make(map[T]func()), workLock: sync.Mutex{}, afterFunc: afterFunc, @@ -93,7 +94,7 @@ func NewScheduledWorkQueue(clock clock.Clock, processFunc ProcessFunc) Scheduled // Add will add an item to this queue, executing the ProcessFunc after the // Duration has come (since the time Add was called). If an existing Timer for // obj already exists, the previous timer will be cancelled. -func (s *scheduledWorkQueue) Add(obj interface{}, duration time.Duration) { +func (s *scheduledWorkQueue[T]) Add(obj T, duration time.Duration) { s.workLock.Lock() defer s.workLock.Unlock() @@ -109,7 +110,7 @@ func (s *scheduledWorkQueue) Add(obj interface{}, duration time.Duration) { } // Forget will cancel the timer for the given object, if the timer exists. -func (s *scheduledWorkQueue) Forget(obj interface{}) { +func (s *scheduledWorkQueue[T]) Forget(obj T) { s.workLock.Lock() defer s.workLock.Unlock() diff --git a/pkg/scheduler/scheduler_test.go b/pkg/scheduler/scheduler_test.go index ef017784437..08f27a782d1 100644 --- a/pkg/scheduler/scheduler_test.go +++ b/pkg/scheduler/scheduler_test.go @@ -27,7 +27,7 @@ import ( ) func Test_afterFunc(t *testing.T) { - // Note that re-implimenting AfterFunc is not a good idea, since testing it + // Note that re-implementing AfterFunc is not a good idea, since testing it // is tricky as seen in time_test.go in the standard library. We will just // focus on two important cases: "f" should be run after the duration @@ -99,7 +99,7 @@ func TestAdd(t *testing.T) { waitSubtest := make(chan struct{}) return func(t *testing.T) { startTime := after.currentTime - queue := NewScheduledWorkQueue(clock.RealClock{}, func(obj interface{}) { + queue := NewScheduledWorkQueue(clock.RealClock{}, func(obj string) { defer wg.Done() durationEarly := test.duration - after.currentTime.Sub(startTime) @@ -111,7 +111,7 @@ func TestAdd(t *testing.T) { } waitSubtest <- struct{}{} }) - queue.(*scheduledWorkQueue).afterFunc = after.AfterFunc + queue.(*scheduledWorkQueue[string]).afterFunc = after.AfterFunc queue.Add(test.obj, test.duration) after.warp(test.duration + time.Millisecond) <-waitSubtest @@ -140,10 +140,10 @@ func TestForget(t *testing.T) { t.Run(test.obj, func(test testT) func(*testing.T) { return func(t *testing.T) { defer wg.Done() - queue := NewScheduledWorkQueue(clock.RealClock{}, func(_ interface{}) { + queue := NewScheduledWorkQueue(clock.RealClock{}, func(_ string) { t.Errorf("scheduled function should never be called") }) - queue.(*scheduledWorkQueue).afterFunc = after.AfterFunc + queue.(*scheduledWorkQueue[string]).afterFunc = after.AfterFunc queue.Add(test.obj, test.duration) queue.Forget(test.obj) after.warp(test.duration * 2) @@ -160,12 +160,12 @@ func TestConcurrentAdd(t *testing.T) { after := newMockAfter() var wg sync.WaitGroup - queue := NewScheduledWorkQueue(clock.RealClock{}, func(obj interface{}) { + queue := NewScheduledWorkQueue(clock.RealClock{}, func(obj int) { t.Fatalf("should not be called, but was called with %v", obj) }) - queue.(*scheduledWorkQueue).afterFunc = after.AfterFunc + queue.(*scheduledWorkQueue[int]).afterFunc = after.AfterFunc - for i := 0; i < 1000; i++ { + for range 1000 { wg.Add(1) go func() { queue.Add(1, 1*time.Second) diff --git a/pkg/scheduler/test/fake.go b/pkg/scheduler/test/fake.go index 89d66e3f5f1..20843f9a292 100644 --- a/pkg/scheduler/test/fake.go +++ b/pkg/scheduler/test/fake.go @@ -19,21 +19,23 @@ package test import ( "time" + "k8s.io/apimachinery/pkg/types" + "github.com/cert-manager/cert-manager/pkg/scheduler" ) -var _ scheduler.ScheduledWorkQueue = &FakeScheduler{} +var _ scheduler.ScheduledWorkQueue[types.NamespacedName] = &FakeScheduler{} // FakeScheduler allows stubbing the methods of scheduler.ScheduledWorkQueue in tests. type FakeScheduler struct { - AddFunc func(interface{}, time.Duration) - ForgetFunc func(interface{}) + AddFunc func(types.NamespacedName, time.Duration) + ForgetFunc func(types.NamespacedName) } -func (f *FakeScheduler) Add(obj interface{}, duration time.Duration) { +func (f *FakeScheduler) Add(obj types.NamespacedName, duration time.Duration) { f.AddFunc(obj, duration) } -func (f *FakeScheduler) Forget(obj interface{}) { +func (f *FakeScheduler) Forget(obj types.NamespacedName) { f.ForgetFunc(obj) } diff --git a/pkg/server/listener.go b/pkg/server/listener.go new file mode 100644 index 00000000000..be953b168e9 --- /dev/null +++ b/pkg/server/listener.go @@ -0,0 +1,111 @@ +/* +Copyright 2020 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. +*/ + +package server + +import ( + "context" + "crypto/tls" + "net" + + ciphers "k8s.io/component-base/cli/flag" + + servertls "github.com/cert-manager/cert-manager/pkg/server/tls" +) + +// ListenerConfig defines the config of the listener, this mainly deals with +// configuring the TLSConfig +type ListenerConfig struct { + TLSEnabled bool + TLSConfig tls.Config +} + +// ListenerOption is function used to mutate the config, it allows for convenience +// methods such as WithCertificateSource +type ListenerOption func(*ListenerConfig) error + +// Listen will listen on a given network and port, with additional options available +// for enabling TLS and obtaining certificates. +func Listen(ctx context.Context, network, addr string, options ...ListenerOption) (net.Listener, error) { + // Create the base listener on the configured network and address + lc := net.ListenConfig{} + listener, err := lc.Listen(ctx, network, addr) + if err != nil { + return nil, err + } + + // Apply the options, these configure the TLS options + config := ListenerConfig{} + for _, option := range options { + if err := option(&config); err != nil { + return nil, err + } + } + + // If the options have enabled TLS we wrap the original listener with + // a TLS listener + if config.TLSEnabled { + listener = tls.NewListener(listener, &config.TLSConfig) + } + + return listener, nil +} + +// WithCertificateSource specifies the certificate source for TLS, this also implicitly +// enables TLS for the listener when not nil +func WithCertificateSource(certificateSource servertls.CertificateSource) ListenerOption { + return func(config *ListenerConfig) error { + if certificateSource != nil { + config.TLSEnabled = true + config.TLSConfig.GetCertificate = certificateSource.GetCertificate + } + return nil + } +} + +// WithTLSCipherSuites specifies the allowed cipher suites, when an empty/nil array is passed +// the go defaults are used +func WithTLSCipherSuites(suites []string) ListenerOption { + return func(config *ListenerConfig) error { + if len(suites) > 0 { + cipherSuites, err := ciphers.TLSCipherSuites(suites) + if err != nil { + return err + } + + config.TLSConfig.CipherSuites = cipherSuites + } + + return nil + } +} + +// WithTLSMinVersion specifies the minimum TLS version, when an empty string is passed the +// go defaults are used +func WithTLSMinVersion(version string) ListenerOption { + return func(config *ListenerConfig) error { + if len(version) > 0 { + minVersion, err := ciphers.TLSVersion(version) + if err != nil { + return err + } + + config.TLSConfig.MinVersion = minVersion + } + + return nil + } +} diff --git a/pkg/webhook/authority/authority.go b/pkg/server/tls/authority/authority.go similarity index 74% rename from pkg/webhook/authority/authority.go rename to pkg/server/tls/authority/authority.go index c13580313cf..7a11efcd23f 100644 --- a/pkg/webhook/authority/authority.go +++ b/pkg/server/tls/authority/authority.go @@ -20,12 +20,11 @@ import ( "bytes" "context" "crypto" - "crypto/rand" "crypto/tls" "crypto/x509" "crypto/x509/pkix" + "errors" "fmt" - "math/big" "sync" "time" @@ -56,9 +55,16 @@ type DynamicAuthority struct { // Namespace and Name of the Secret resource used to store the authority. SecretNamespace, SecretName string + // Labels added to the Secret resource used to store the authority. + SecretLabels map[string]string + // RESTConfig used to connect to the apiserver. RESTConfig *rest.Config + // CommonName of the CA subject. + // Defaults to 'cert-manager-webhook-ca' for backwards compatibility reasons. + CommonName string + // The amount of time the root CA certificate will be valid for. // This must be greater than LeafDuration. // Defaults to 365d. @@ -84,7 +90,9 @@ type DynamicAuthority struct { ensureMutex sync.Mutex // watchMutex gates access to the slice of watch channels watchMutex sync.Mutex - watches []chan struct{} + watches []chan<- struct{} + + newClient func(c *rest.Config) (kubernetes.Interface, error) // for testing } type SignFunc func(template *x509.Certificate) (*x509.Certificate, error) @@ -99,6 +107,9 @@ func (d *DynamicAuthority) Run(ctx context.Context) error { if d.SecretName == "" { return fmt.Errorf("SecretName must be set") } + if d.CommonName == "" { + d.CommonName = "cert-manager-webhook-ca" + } if d.CADuration == 0 { d.CADuration = time.Hour * 24 * 365 // 365d } @@ -106,7 +117,13 @@ func (d *DynamicAuthority) Run(ctx context.Context) error { d.LeafDuration = time.Hour * 24 * 7 // 7d } - cl, err := kubernetes.NewForConfig(d.RESTConfig) + if d.newClient == nil { + d.newClient = func(c *rest.Config) (kubernetes.Interface, error) { + return kubernetes.NewForConfig(c) + } + } + + cl, err := d.newClient(d.RESTConfig) if err != nil { return err } @@ -119,18 +136,26 @@ func (d *DynamicAuthority) Run(ctx context.Context) error { }), ) informer := factory.Core().V1().Secrets().Informer() - informer.AddEventHandler(cache.ResourceEventHandlerFuncs{ + if _, err := informer.AddEventHandler(cache.ResourceEventHandlerFuncs{ AddFunc: d.handleAdd, UpdateFunc: d.handleUpdate, DeleteFunc: d.handleDelete, - }) + }); err != nil { + return fmt.Errorf("error setting up event handler: %v", err) + } d.lister = factory.Core().V1().Secrets().Lister().Secrets(d.SecretNamespace) d.client = cl.CoreV1().Secrets(d.SecretNamespace) // start the informers and wait for the cache to sync factory.Start(ctx.Done()) + defer factory.Shutdown() + if !cache.WaitForCacheSync(ctx.Done(), informer.HasSynced) { + if ctx.Err() != nil { // context was cancelled, we are shutting down; so, don't return an error because the informer caches were not yet synced + return nil + } + return fmt.Errorf("failed waiting for informer caches to sync") } @@ -138,17 +163,17 @@ func (d *DynamicAuthority) Run(ctx context.Context) error { // been missed that could cause us to get into an idle state where the // Secret resource does not exist and so the informers handler functions // are not triggered. - if err = wait.PollImmediateUntil(time.Second*10, func() (done bool, err error) { + if err := wait.PollUntilContextCancel(ctx, time.Second*10, true, func(ctx context.Context) (done bool, err error) { if err := d.ensureCA(ctx); err != nil { d.log.Error(err, "error ensuring CA") } // never return 'done'. // this poll only ends when stopCh is closed. return false, nil - }, ctx.Done()); err != nil { - // If error cause was context, return that error instead - if ctx.Err() != nil { - return ctx.Err() + }); err != nil { + if errors.Is(err, context.Canceled) { + // context was cancelled, return nil + return nil } return err @@ -164,6 +189,10 @@ func (d *DynamicAuthority) Sign(template *x509.Certificate) (*x509.Certificate, d.signMutex.Lock() defer d.signMutex.Unlock() + if d.currentCertData == nil || d.currentPrivateKeyData == nil { + return nil, fmt.Errorf("no tls.Certificate available yet, try again later") + } + // tls.X509KeyPair performs a number of verification checks against the // keypair, so we run it to verify the certificate and private key are // valid. @@ -182,12 +211,6 @@ func (d *DynamicAuthority) Sign(template *x509.Certificate) (*x509.Certificate, return nil, fmt.Errorf("failed decoding CA private key: %v", err) } - serialNumber, err := rand.Int(rand.Reader, serialNumberLimit) - if err != nil { - return nil, err - } - template.Version = 3 - template.SerialNumber = serialNumber template.BasicConstraintsValid = true template.NotBefore = time.Now() template.NotAfter = template.NotBefore.Add(d.LeafDuration) @@ -208,28 +231,29 @@ func (d *DynamicAuthority) Sign(template *x509.Certificate) (*x509.Certificate, return cert, nil } -// WatchRotation will returns a channel that fires notifications if the CA +// WatchRotation will return a channel that fires notifications if the CA // certificate is rotated/updated. // This can be used to automatically trigger rotation of leaf certificates // when the root CA changes. -func (d *DynamicAuthority) WatchRotation(stopCh <-chan struct{}) <-chan struct{} { +func (d *DynamicAuthority) WatchRotation(output chan<- struct{}) { d.watchMutex.Lock() defer d.watchMutex.Unlock() - ch := make(chan struct{}, 1) - d.watches = append(d.watches, ch) - go func() { - defer close(ch) - <-stopCh - d.watchMutex.Lock() - defer d.watchMutex.Unlock() - for i, c := range d.watches { - if c == ch { - d.watches = append(d.watches[:i], d.watches[i+1:]...) - return - } + + // Add the output channel to the list of watches + d.watches = append(d.watches, output) +} + +func (d *DynamicAuthority) StopWatchingRotation(output chan<- struct{}) { + d.watchMutex.Lock() + defer d.watchMutex.Unlock() + + // Remove the output channel from the list of watches + for i, c := range d.watches { + if c == output { + d.watches = append(d.watches[:i], d.watches[i+1:]...) + return } - }() - return ch + } } func (d *DynamicAuthority) ensureCA(ctx context.Context) error { @@ -238,12 +262,14 @@ func (d *DynamicAuthority) ensureCA(ctx context.Context) error { s, err := d.lister.Get(d.SecretName) if apierrors.IsNotFound(err) { + d.log.V(logf.InfoLevel).Info("Will regenerate CA", "reason", "CA secret not found") return d.regenerateCA(ctx, nil) } if err != nil { return err } - if d.caRequiresRegeneration(s) { + if required, reason := caRequiresRegeneration(s); required { + d.log.V(logf.InfoLevel).Info("Will regenerate CA", "reason", reason) return d.regenerateCA(ctx, s.DeepCopy()) } d.notifyWatches(s.Data[corev1.TLSCertKey], s.Data[corev1.TLSPrivateKeyKey]) @@ -256,72 +282,69 @@ func (d *DynamicAuthority) notifyWatches(newCertData, newPrivateKeyData []byte) return } - d.log.V(logf.DebugLevel).Info("Detected change in CA secret data, notifying watchers...") + d.log.V(logf.InfoLevel).Info("Detected change in CA secret data, update current CA data and notify watches") - d.watchMutex.Lock() - defer d.watchMutex.Unlock() - for _, ch := range d.watches { - // the watch channels have a buffer of 1 - drop events to slow - // consumers - select { - case ch <- struct{}{}: - default: - } - } + func() { + d.signMutex.Lock() + defer d.signMutex.Unlock() + d.currentCertData = newCertData + d.currentPrivateKeyData = newPrivateKeyData + }() - d.signMutex.Lock() - defer d.signMutex.Unlock() - d.currentCertData = newCertData - d.currentPrivateKeyData = newPrivateKeyData + func() { + d.watchMutex.Lock() + defer d.watchMutex.Unlock() + for _, ch := range d.watches { + // the watch channels have a buffer of 1 - drop events to slow + // consumers + select { + case ch <- struct{}{}: + default: + } + } + }() } // caRequiresRegeneration will check data in a Secret resource and return true // if the CA needs to be regenerated for any reason. -func (d *DynamicAuthority) caRequiresRegeneration(s *corev1.Secret) bool { +func caRequiresRegeneration(s *corev1.Secret) (bool, string) { if s.Data == nil { - return true + return true, "Missing data in CA secret." } caData := s.Data[cmmeta.TLSCAKey] pkData := s.Data[corev1.TLSPrivateKeyKey] certData := s.Data[corev1.TLSCertKey] if len(caData) == 0 || len(pkData) == 0 || len(certData) == 0 { - d.log.V(logf.InfoLevel).Info("Missing data in CA secret. Regenerating") - return true + return true, "Missing data in CA secret." } // ensure that the ca.crt and tls.crt keys are equal if !bytes.Equal(caData, certData) { - return true + return true, "Different data in ca.crt and tls.crt." } cert, err := tls.X509KeyPair(certData, pkData) if err != nil { - d.log.Error(err, "Failed to parse data in CA secret. Regenerating") - return true + return true, "Failed to parse data in CA secret." } x509Cert, err := x509.ParseCertificate(cert.Certificate[0]) if err != nil { - d.log.Error(err, "internal error parsing x509 certificate") - return true + return true, "Internal error parsing x509 certificate." } if !x509Cert.IsCA { - d.log.V(logf.InfoLevel).Info("Stored certificate is not marked as a CA. Regenerating...") - return true + return true, "Stored certificate is not marked as a CA." } // renew the root CA when the current one is 2/3 of the way through its life - if x509Cert.NotAfter.Sub(time.Now()) < (d.CADuration / 3) { - d.log.V(logf.InfoLevel).Info("Root CA certificate is nearing expiry. Regenerating...") - return true + if time.Until(x509Cert.NotAfter) < (x509Cert.NotAfter.Sub(x509Cert.NotBefore) / 3) { + return true, "CA certificate is nearing expiry." } - return false -} -var serialNumberLimit = new(big.Int).Lsh(big.NewInt(1), 128) + return false, "" +} // regenerateCA will regenerate and store a new CA. // If the provided Secret is nil, a new secret resource will be Created. // Otherwise, the provided resource will be modified and Updated. func (d *DynamicAuthority) regenerateCA(ctx context.Context, s *corev1.Secret) error { - d.log.V(logf.DebugLevel).Info("Generating new root CA") pk, err := pki.GenerateECPrivateKey(384) if err != nil { return err @@ -331,17 +354,14 @@ func (d *DynamicAuthority) regenerateCA(ctx context.Context, s *corev1.Secret) e return err } - serialNumber, err := rand.Int(rand.Reader, serialNumberLimit) if err != nil { return err } cert := &x509.Certificate{ - Version: 2, BasicConstraintsValid: true, - SerialNumber: serialNumber, PublicKeyAlgorithm: x509.ECDSA, Subject: pkix.Name{ - CommonName: "cert-manager-webhook-ca", + CommonName: d.CommonName, }, IsCA: true, NotBefore: time.Now(), @@ -363,6 +383,7 @@ func (d *DynamicAuthority) regenerateCA(ctx context.Context, s *corev1.Secret) e ObjectMeta: metav1.ObjectMeta{ Name: d.SecretName, Namespace: d.SecretNamespace, + Labels: d.SecretLabels, Annotations: map[string]string{ cmapi.AllowsInjectionFromSecretAnnotation: "true", }, @@ -373,6 +394,13 @@ func (d *DynamicAuthority) regenerateCA(ctx context.Context, s *corev1.Secret) e cmmeta.TLSCAKey: certBytes, }, }, metav1.CreateOptions{}) + if err != nil && apierrors.IsAlreadyExists(err) { + // another controller has created the secret, we expect a watch event + // to trigger another call to ensureCA + d.log.V(logf.DebugLevel).Info("Failed to create new root CA Secret, another controller has created it, waiting for watch event") + return nil + } + d.log.V(logf.InfoLevel).Info("Created new root CA Secret") return err } @@ -385,7 +413,7 @@ func (d *DynamicAuthority) regenerateCA(ctx context.Context, s *corev1.Secret) e if _, err := d.client.Update(ctx, s, metav1.UpdateOptions{}); err != nil { return err } - d.log.V(logf.DebugLevel).Info("Generated new root CA") + d.log.V(logf.InfoLevel).Info("Updated existing root CA Secret") return nil } diff --git a/pkg/server/tls/authority/authority_test.go b/pkg/server/tls/authority/authority_test.go new file mode 100644 index 00000000000..a10267006ca --- /dev/null +++ b/pkg/server/tls/authority/authority_test.go @@ -0,0 +1,336 @@ +/* +Copyright 2020 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. +*/ + +package authority + +import ( + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "crypto/x509" + "crypto/x509/pkix" + "fmt" + "testing" + "time" + + "github.com/go-logr/logr/testr" + "github.com/stretchr/testify/assert" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes" + kubefake "k8s.io/client-go/kubernetes/fake" + "k8s.io/client-go/rest" + + cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" + logf "github.com/cert-manager/cert-manager/pkg/logs" + "github.com/cert-manager/cert-manager/pkg/util/pki" +) + +// Integration tests for the authority can be found in `test/integration/webhook/dynamic_authority_test.go`. + +func testAuthority(t *testing.T, name string, cs *kubefake.Clientset) *DynamicAuthority { + logger := testr.NewWithOptions(t, testr.Options{ + Verbosity: 3, + }) + logger = logger.WithName(name) + + da := &DynamicAuthority{ + SecretNamespace: "test-namespace", + SecretName: "test-secret", + CommonName: "test-common-name", + CADuration: 365 * 24 * time.Hour, + LeafDuration: 7 * 24 * time.Hour, + + newClient: func(_ *rest.Config) (kubernetes.Interface, error) { + return cs, nil + }, + } + + done := make(chan struct{}) + go func() { + defer close(done) + if err := da.Run(logf.NewContext(t.Context(), logger)); err != nil { + t.Error(err) + } + }() + t.Cleanup(func() { + <-done + }) + + return da +} + +func TestDynamicAuthority(t *testing.T) { + fake := kubefake.NewClientset() + + da := testAuthority(t, "authority", fake) + + // Test WatchRotation function + output := make(chan struct{}, 1) + da.WatchRotation(output) + defer da.StopWatchingRotation(output) + + waitForRotationAndSign := func(testInitial bool) { + privateKey, err := ecdsa.GenerateKey(elliptic.P521(), rand.Reader) + if err != nil { + t.Fatal(err) + } + template := &x509.Certificate{ + PublicKey: privateKey.Public(), + } + + if testInitial { + // If Sign works, we don't need to wait for rotation + cert, err := da.Sign(template) + if err == nil { + assert.NotNil(t, cert) + return + } + } + + select { + case <-output: + // Rotation detected + cert, err := da.Sign(template) + assert.NoError(t, err) + assert.NotNil(t, cert) + case <-time.After(5 * time.Second): + t.Error("Timeout waiting for rotation") + + t.Log("Queue length:", len(output)) + } + } + + waitForRotationAndSign(true) + + err := fake.CoreV1().Secrets(da.SecretNamespace).Delete(t.Context(), da.SecretName, metav1.DeleteOptions{}) + assert.NoError(t, err) + + waitForRotationAndSign(false) + + secret, err := fake.CoreV1().Secrets(da.SecretNamespace).Get(t.Context(), da.SecretName, metav1.GetOptions{}) + assert.NoError(t, err) + + secret.Data = map[string][]byte{ + "tls.crt": []byte("test"), + "tls.key": []byte("test"), + } + _, err = fake.CoreV1().Secrets(da.SecretNamespace).Update(t.Context(), secret, metav1.UpdateOptions{}) + assert.NoError(t, err) + + waitForRotationAndSign(false) +} + +func TestDynamicAuthorityMulti(t *testing.T) { + fake := kubefake.NewClientset() + + authorities := make([]*DynamicAuthority, 0) + for i := range 200 { + da := testAuthority(t, fmt.Sprintf("authority-%d", i), fake) + authorities = append(authorities, da) + } + + da := authorities[0] + + output := make(chan struct{}, 1) + da.WatchRotation(output) + defer da.StopWatchingRotation(output) + + waitForRotationAndSign := func() { + privateKey, err := ecdsa.GenerateKey(elliptic.P521(), rand.Reader) + if err != nil { + t.Fatal(err) + } + template := &x509.Certificate{ + PublicKey: privateKey.Public(), + } + + // If Sign works, we don't need to wait for rotation + cert, err := da.Sign(template) + if err == nil { + assert.NotNil(t, cert) + return + } + + select { + case <-output: + // Rotation detected + cert, err := da.Sign(template) + assert.NoError(t, err) + assert.NotNil(t, cert) + case <-time.After(5 * time.Second): + t.Error("Timeout waiting for rotation") + + t.Log("Queue length:", len(output)) + } + } + + waitForRotationAndSign() +} + +func Test__caRequiresRegeneration(t *testing.T) { + generateSecretData := func(mod func(*x509.Certificate)) map[string][]byte { + // Generate a certificate and private key pair + pk, err := pki.GenerateECPrivateKey(384) + assert.NoError(t, err) + pkBytes, err := pki.EncodePrivateKey(pk, cmapi.PKCS8) + assert.NoError(t, err) + cert := &x509.Certificate{ + BasicConstraintsValid: true, + PublicKeyAlgorithm: x509.ECDSA, + Subject: pkix.Name{ + CommonName: "cert-manager-webhook-ca", + }, + IsCA: true, + NotBefore: time.Now(), + NotAfter: time.Now().Add(5 * time.Minute), + KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment | x509.KeyUsageCertSign, + } + if mod != nil { + mod(cert) + } + _, cert, err = pki.SignCertificate(cert, cert, pk.Public(), pk) + assert.NoError(t, err) + certBytes, err := pki.EncodeX509(cert) + assert.NoError(t, err) + + return map[string][]byte{ + "tls.crt": certBytes, + "ca.crt": certBytes, + "tls.key": pkBytes, + } + } + + tests := []struct { + name string + secret *corev1.Secret + expect bool + expectReason string + }{ + { + name: "Missing data in CA secret (nil data)", + secret: &corev1.Secret{ + Data: nil, + }, + expect: true, + expectReason: "Missing data in CA secret.", + }, + { + name: "Missing data in CA secret (missing ca.crt)", + secret: &corev1.Secret{ + Data: map[string][]byte{ + "tls.key": []byte("private key"), + }, + }, + expect: true, + expectReason: "Missing data in CA secret.", + }, + { + name: "Different data in ca.crt and tls.crt", + secret: &corev1.Secret{ + Data: map[string][]byte{ + "tls.crt": []byte("data1"), + "ca.crt": []byte("data2"), + "tls.key": []byte("secret"), + }, + }, + expect: true, + expectReason: "Different data in ca.crt and tls.crt.", + }, + { + name: "Failed to parse data in CA secret", + secret: &corev1.Secret{ + Data: map[string][]byte{ + "tls.crt": []byte("cert"), + "ca.crt": []byte("cert"), + "tls.key": []byte("secret"), + }, + }, + expect: true, + expectReason: "Failed to parse data in CA secret.", + }, + { + name: "Stored certificate is not marked as a CA", + secret: &corev1.Secret{ + Data: generateSecretData( + func(cert *x509.Certificate) { + cert.IsCA = false + }, + ), + }, + expect: true, + expectReason: "Stored certificate is not marked as a CA.", + }, + { + name: "Root CA certificate is JUST nearing expiry", + secret: &corev1.Secret{ + Data: generateSecretData( + func(cert *x509.Certificate) { + cert.NotBefore = time.Now().Add(-2*time.Hour - 1*time.Minute) + cert.NotAfter = cert.NotBefore.Add(3 * time.Hour) + }, + ), + }, + expect: true, + expectReason: "CA certificate is nearing expiry.", + }, + { + name: "Root CA certificate is ALMOST nearing expiry", + secret: &corev1.Secret{ + Data: generateSecretData( + func(cert *x509.Certificate) { + cert.NotBefore = time.Now().Add(-2*time.Hour + 1*time.Minute) + cert.NotAfter = cert.NotBefore.Add(3 * time.Hour) + }, + ), + }, + expect: false, + }, + { + name: "Root CA certificate is expired", + secret: &corev1.Secret{ + Data: generateSecretData( + func(cert *x509.Certificate) { + cert.NotBefore = time.Now().Add(-1 * time.Hour) + cert.NotAfter = time.Now().Add(-1 * time.Minute) + }, + ), + }, + expect: true, + expectReason: "CA certificate is nearing expiry.", + }, + { + name: "Ok", + secret: &corev1.Secret{ + Data: generateSecretData(nil), + }, + expect: false, + expectReason: "", + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + required, reason := caRequiresRegeneration(test.secret) + if required != test.expect { + t.Errorf("Expected %v, but got %v", test.expect, required) + } + if reason != test.expectReason { + t.Errorf("Expected %q, but got %q", test.expectReason, reason) + } + }) + } +} diff --git a/pkg/server/tls/dynamic_source.go b/pkg/server/tls/dynamic_source.go new file mode 100644 index 00000000000..0b28f4b55b7 --- /dev/null +++ b/pkg/server/tls/dynamic_source.go @@ -0,0 +1,292 @@ +/* +Copyright 2020 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. +*/ + +package tls + +import ( + "context" + "crypto" + "crypto/tls" + "crypto/x509" + "errors" + "fmt" + "sync" + "time" + + "github.com/go-logr/logr" + "golang.org/x/sync/errgroup" + "k8s.io/apimachinery/pkg/util/wait" + + cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" + logf "github.com/cert-manager/cert-manager/pkg/logs" + "github.com/cert-manager/cert-manager/pkg/util/pki" +) + +type Authority interface { + // Run starts the authority and blocks until it is stopped or an error occurs. + Run(ctx context.Context) error + + // WatchRotation adds a watcher to the authority that will notify the given + // channel when the root CA has been rotated. It is guaranteed to post a message + // to the channel when the root CA has been rotated and the channel is not full. + WatchRotation(ch chan<- struct{}) + + // StopWatchingRotation removes the watcher from the authority. + StopWatchingRotation(ch chan<- struct{}) + + // Sign signs the given certificate template and returns the signed certificate. + // WARNING: The WatchRotation method should be called before Sign to ensure that + // the rotation of the CA used to sign the certificate in this call is detected. + Sign(template *x509.Certificate) (*x509.Certificate, error) +} + +// DynamicSource provides certificate data for a golang HTTP server by +// automatically generating certificates using an authority.SignFunc. +type DynamicSource struct { + // DNSNames that will be set on certificates this source produces. + DNSNames []string + + // The authority used to sign certificate templates. + Authority Authority + + RetryInterval time.Duration + + log logr.Logger + + cachedCertificate *tls.Certificate + lock sync.Mutex +} + +var _ CertificateSource = &DynamicSource{} + +// Implements Runnable (https://github.com/kubernetes-sigs/controller-runtime/blob/56159419231e985c091ef3e7a8a3dee40ddf1d73/pkg/manager/manager.go#L287) +func (f *DynamicSource) Start(ctx context.Context) error { + f.log = logf.FromContext(ctx) + + if f.RetryInterval == 0 { + f.RetryInterval = 1 * time.Second + } + + group, ctx := errgroup.WithContext(ctx) + group.Go(func() error { + if err := f.Authority.Run(ctx); err != nil { + return fmt.Errorf("failed to run certificate authority: %w", err) + } + + if ctx.Err() == nil { + return fmt.Errorf("certificate authority stopped unexpectedly") + } + + // Context was cancelled, return nil + return nil + }) + + // channel which will be notified when the authority has rotated its root CA + // We start watching the rotation of the root CA before we start generating + // certificates to ensure we don't miss any rotations. + rotationChan := make(chan struct{}, 1) + f.Authority.WatchRotation(rotationChan) + defer f.Authority.StopWatchingRotation(rotationChan) + + nextRenewCh := make(chan time.Time, 1) + + // initially fetch a certificate from the signing CA + if err := f.tryRegenerateCertificate(ctx, nextRenewCh); err != nil { + if err := group.Wait(); err != nil { + return err + } + + if errors.Is(err, context.Canceled) { + return nil + } + + return err + } + + // channel which will be notified when the leaf certificate reaches 2/3 of its lifetime + // and needs to be renewed + renewalChan := make(chan struct{}, 1) + group.Go(func() error { + var renewMoment time.Time + + for { + if done := func() bool { + var timerChannel <-chan time.Time + if !renewMoment.IsZero() { + timer := time.NewTimer(time.Until(renewMoment)) + defer timer.Stop() + + renewMoment = time.Time{} + timerChannel = timer.C + } + + // Wait for the timer to expire, or for a new renewal moment to be received + select { + case <-ctx.Done(): + // context was cancelled, return nil + return true + case <-timerChannel: + // Continue to the next select to try to send a message on renewalChan + case renewMoment = <-nextRenewCh: + // We received a renew moment, next loop iteration will update the timer + return false + } + + // the renewal channel has a buffer of 1 - drop event if we are already issuing + select { + case renewalChan <- struct{}{}: + default: + } + + return false + }(); done { + return nil + } + } + }) + + // check the current certificate in case it needs updating + if err := func() error { + for { + // regenerate the serving certificate if the root CA has been rotated + select { + // check if the context has been cancelled + case <-ctx.Done(): + return ctx.Err() + + // trigger regeneration if the root CA has been rotated + case <-rotationChan: + f.log.V(logf.InfoLevel).Info("Detected root CA rotation - regenerating serving certificates") + + // trigger regeneration if a renewal is required + case <-renewalChan: + f.log.V(logf.InfoLevel).Info("cert-manager webhook certificate requires renewal, regenerating", "DNSNames", f.DNSNames) + } + + if err := f.tryRegenerateCertificate(ctx, nextRenewCh); err != nil { + return err + } + } + }(); err != nil { + if err := group.Wait(); err != nil { + return err + } + + if errors.Is(err, context.Canceled) { + return nil + } + + return err + } + + return nil +} + +// Implements LeaderElectionRunnable (https://github.com/kubernetes-sigs/controller-runtime/blob/56159419231e985c091ef3e7a8a3dee40ddf1d73/pkg/manager/manager.go#L305) +func (f *DynamicSource) NeedLeaderElection() bool { + return false +} + +func (f *DynamicSource) GetCertificate(*tls.ClientHelloInfo) (*tls.Certificate, error) { + f.lock.Lock() + defer f.lock.Unlock() + if f.cachedCertificate == nil { + return nil, ErrNotAvailable + } + return f.cachedCertificate, nil +} + +func (f *DynamicSource) Healthy() bool { + f.lock.Lock() + defer f.lock.Unlock() + return f.cachedCertificate != nil +} + +func (f *DynamicSource) tryRegenerateCertificate(ctx context.Context, nextRenewCh chan<- time.Time) error { + return wait.PollUntilContextCancel(ctx, f.RetryInterval, true, func(ctx context.Context) (done bool, err error) { + if err := f.regenerateCertificate(ctx, nextRenewCh); err != nil { + f.log.Error(err, "Failed to generate serving certificate, retrying...", "interval", f.RetryInterval) + return false, nil + } + + return true, nil + }) +} + +// regenerateCertificate will trigger the cached certificate and private key to +// be regenerated by requesting a new certificate from the authority. +func (f *DynamicSource) regenerateCertificate(ctx context.Context, nextRenew chan<- time.Time) error { + f.log.V(logf.DebugLevel).Info("Generating new ECDSA private key") + pk, err := pki.GenerateECPrivateKey(384) + if err != nil { + return err + } + + // create the certificate template to be signed + template := &x509.Certificate{ + PublicKeyAlgorithm: x509.ECDSA, + PublicKey: pk.Public(), + DNSNames: f.DNSNames, + KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, + } + + f.log.V(logf.DebugLevel).Info("Signing new serving certificate") + cert, err := f.Authority.Sign(template) + if err != nil { + return err + } + + f.log.V(logf.DebugLevel).Info("Signed new serving certificate") + + return f.updateCertificate(ctx, pk, cert, nextRenew) +} + +func (f *DynamicSource) updateCertificate(ctx context.Context, pk crypto.Signer, cert *x509.Certificate, nextRenewCh chan<- time.Time) error { + f.lock.Lock() + defer f.lock.Unlock() + + pkData, err := pki.EncodePrivateKey(pk, cmapi.PKCS8) + if err != nil { + return err + } + + certData, err := pki.EncodeX509(cert) + if err != nil { + return err + } + + bundle, err := tls.X509KeyPair(certData, pkData) + if err != nil { + return err + } + + f.cachedCertificate = &bundle + certDuration := cert.NotAfter.Sub(cert.NotBefore) + // renew the certificate 1/3 of the time before its expiry + renewMoment := cert.NotAfter.Add(certDuration / -3) + + select { + case <-ctx.Done(): + return nil + + case nextRenewCh <- renewMoment: + } + + f.log.V(logf.InfoLevel).Info("Updated cert-manager TLS certificate", "DNSNames", f.DNSNames) + + return nil +} diff --git a/pkg/server/tls/dynamic_source_test.go b/pkg/server/tls/dynamic_source_test.go new file mode 100644 index 00000000000..a490cd170a5 --- /dev/null +++ b/pkg/server/tls/dynamic_source_test.go @@ -0,0 +1,307 @@ +/* +Copyright 2020 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. +*/ + +package tls + +import ( + "context" + "crypto" + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "crypto/tls" + "crypto/x509" + "crypto/x509/pkix" + "fmt" + "math/big" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "golang.org/x/sync/errgroup" + + "github.com/cert-manager/cert-manager/pkg/server/tls/authority" + "github.com/cert-manager/cert-manager/pkg/util/pki" +) + +// Integration tests for the DynamicSource can be found in `test/integration/webhook/dynamic_source_test.go`. + +func signUsingTempCA(t *testing.T, template *x509.Certificate) *x509.Certificate { + // generate random ca private key + caPrivateKey, err := ecdsa.GenerateKey(elliptic.P521(), rand.Reader) + if err != nil { + t.Fatal(err) + } + + caCRT := &x509.Certificate{ + SerialNumber: big.NewInt(1), + Subject: pkix.Name{ + Organization: []string{"Acme Co"}, + }, + NotBefore: time.Now(), + NotAfter: time.Now().Add(time.Hour * 24 * 180), + + KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, + BasicConstraintsValid: true, + } + + _, cert, err := pki.SignCertificate(template, caCRT, template.PublicKey.(crypto.PublicKey), caPrivateKey) + if err != nil { + t.Fatal(err) + } + + return cert +} + +type mockAuthority struct { + doneCh chan error + notifyCh chan<- struct{} + signFunc authority.SignFunc +} + +func (m *mockAuthority) Run(ctx context.Context) error { + select { + case <-ctx.Done(): + return nil + case err := <-m.doneCh: + return err + } +} + +func (m *mockAuthority) WatchRotation(ch chan<- struct{}) { + m.notifyCh = ch +} + +func (m *mockAuthority) StopWatchingRotation(ch chan<- struct{}) {} + +func (m *mockAuthority) Sign(template *x509.Certificate) (*x509.Certificate, error) { + return m.signFunc(template) +} + +func TestDynamicSource_FailingSign(t *testing.T) { + type testCase struct { + name string + signFunc authority.SignFunc + testFn func(t *testing.T, source *DynamicSource, mockAuth *mockAuthority) + expStartErr string + } + + tests := []testCase{ + { + name: "sign function returns error", + signFunc: func(template *x509.Certificate) (*x509.Certificate, error) { + return nil, fmt.Errorf("mock error") + }, + testFn: func(t *testing.T, source *DynamicSource, mockAuth *mockAuthority) { + // Call the GetCertificate method, should return a non-ready error + cert, err := source.GetCertificate(&tls.ClientHelloInfo{}) + assert.Nil(t, cert) + assert.Error(t, err) + assert.Contains(t, err.Error(), "no tls.Certificate available") + + // The authority is now failing because of the faulty sign function, + // we now stop the authority and wait for the DynamicSource to stop + mockAuth.doneCh <- fmt.Errorf("mock error") + }, + expStartErr: "mock error", + }, + { + name: "certificate authority stopped unexpectedly", + signFunc: func(template *x509.Certificate) (*x509.Certificate, error) { + return nil, fmt.Errorf("mock error") + }, + testFn: func(t *testing.T, source *DynamicSource, mockAuth *mockAuthority) { + // Stop the authority + mockAuth.doneCh <- nil + }, + expStartErr: "certificate authority stopped unexpectedly", + }, + { + name: "sign function returns error (retry, then success)", + signFunc: func() authority.SignFunc { + var called int + return func(template *x509.Certificate) (*x509.Certificate, error) { + called++ + if called != 5 { + return nil, fmt.Errorf("mock error") + } + + template.SerialNumber = big.NewInt(10) + template.NotBefore = time.Now() + template.NotAfter = template.NotBefore.Add(time.Minute) + + return signUsingTempCA(t, template), nil + } + }(), + testFn: func(t *testing.T, source *DynamicSource, mockAuth *mockAuthority) { + for !source.Healthy() { + time.Sleep(50 * time.Millisecond) + } + + // Call the GetCertificate method, should return a certificate + cert, err := source.GetCertificate(&tls.ClientHelloInfo{}) + assert.NoError(t, err) + assert.NotNil(t, cert) + }, + }, + { + name: "don't rotate root", + signFunc: func(template *x509.Certificate) (*x509.Certificate, error) { + template.SerialNumber = big.NewInt(10) + template.NotBefore = time.Now() + template.NotAfter = template.NotBefore.Add(time.Minute) + + return signUsingTempCA(t, template), nil + }, + testFn: func(t *testing.T, source *DynamicSource, mockAuth *mockAuthority) { + for !source.Healthy() { + time.Sleep(50 * time.Millisecond) + } + + // Call the GetCertificate method, should return a certificate + cert, err := source.GetCertificate(&tls.ClientHelloInfo{}) + assert.NoError(t, err) + assert.NotNil(t, cert) + + // Sleep for a short time to allow the DynamicSource to generate a new certificate + // Which it should not do, as the root CA has not been rotated + time.Sleep(50 * time.Millisecond) + + // Call the GetCertificate method, should return a NEW certificate + cert2, err := source.GetCertificate(&tls.ClientHelloInfo{}) + assert.NoError(t, err) + assert.NotNil(t, cert2) + + assert.Equal(t, cert.Certificate[0], cert2.Certificate[0]) + }, + }, + { + name: "rotate root", + signFunc: func(template *x509.Certificate) (*x509.Certificate, error) { + template.SerialNumber = big.NewInt(10) + template.NotBefore = time.Now() + template.NotAfter = template.NotBefore.Add(time.Minute) + + return signUsingTempCA(t, template), nil + }, + testFn: func(t *testing.T, source *DynamicSource, mockAuth *mockAuthority) { + for !source.Healthy() { + time.Sleep(50 * time.Millisecond) + } + + // Call the GetCertificate method, should return a certificate + cert, err := source.GetCertificate(&tls.ClientHelloInfo{}) + assert.NoError(t, err) + assert.NotNil(t, cert) + + for range 10 { + // Rotate the root + mockAuth.notifyCh <- struct{}{} + + // Sleep for a short time to allow the DynamicSource to generate a new certificate + time.Sleep(50 * time.Millisecond) + + // Call the GetCertificate method, should return a NEW certificate + cert2, err := source.GetCertificate(&tls.ClientHelloInfo{}) + assert.NoError(t, err) + assert.NotNil(t, cert2) + + assert.NotEqual(t, cert.Certificate[0], cert2.Certificate[0]) + } + }, + }, + { + name: "expire leaf", + signFunc: func(template *x509.Certificate) (*x509.Certificate, error) { + template.SerialNumber = big.NewInt(10) + template.NotBefore = time.Now() + template.NotAfter = template.NotBefore.Add(150 * time.Millisecond) + + signedCert := signUsingTempCA(t, template) + // Reset the NotBefore and NotAfter so we have high precision values here + signedCert.NotBefore = time.Now() + signedCert.NotAfter = signedCert.NotBefore.Add(150 * time.Millisecond) + + // Should renew at 100ms after the NotBefore time + + return signedCert, nil + }, + testFn: func(t *testing.T, source *DynamicSource, mockAuth *mockAuthority) { + for !source.Healthy() { + time.Sleep(50 * time.Millisecond) + } + + // Call the GetCertificate method, should return a certificate + cert, err := source.GetCertificate(&tls.ClientHelloInfo{}) + assert.NoError(t, err) + assert.NotNil(t, cert) + + for range 5 { + // Sleep for a short time to allow the DynamicSource to generate a new certificate + // The certificate should get renewed after 100ms, we wait for 200ms to allow for + // possible delays of max 100ms (based on experiments, we noticed that issuance of + // a cert takes about 30ms, so 100ms should be a large enough margin). + time.Sleep(200 * time.Millisecond) + + // Call the GetCertificate method, should return a NEW certificate + newCert, err := source.GetCertificate(&tls.ClientHelloInfo{}) + assert.NoError(t, err) + assert.NotNil(t, newCert) + + assert.NotEqual(t, cert.Certificate[0], newCert.Certificate[0]) + + cert = newCert + } + }, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + // Create a mock authority + mockAuth := &mockAuthority{ + doneCh: make(chan error), + signFunc: tc.signFunc, + } + + // Create a DynamicSource instance with the mock authority + source := &DynamicSource{ + Authority: mockAuth, + RetryInterval: 1 * time.Millisecond, + } + + // Start the DynamicSource + group, gctx := errgroup.WithContext(t.Context()) + group.Go(func() error { + return source.Start(gctx) + }) + t.Cleanup(func() { + err := group.Wait() + if tc.expStartErr == "" { + assert.NoError(t, err) + } else { + assert.Error(t, err) + assert.Contains(t, err.Error(), tc.expStartErr) + } + }) + + tc.testFn(t, source, mockAuth) + }) + } +} diff --git a/pkg/webhook/server/tls/file_source.go b/pkg/server/tls/file_source.go similarity index 98% rename from pkg/webhook/server/tls/file_source.go rename to pkg/server/tls/file_source.go index f084c8a95b4..9f5723961d2 100644 --- a/pkg/webhook/server/tls/file_source.go +++ b/pkg/server/tls/file_source.go @@ -66,7 +66,7 @@ const defaultMaxFailures = 12 var _ CertificateSource = &FileCertificateSource{} -func (f *FileCertificateSource) Run(ctx context.Context) error { +func (f *FileCertificateSource) Start(ctx context.Context) error { f.log = logf.FromContext(ctx) updateInterval := f.UpdateInterval diff --git a/pkg/webhook/server/tls/file_source_test.go b/pkg/server/tls/file_source_test.go similarity index 83% rename from pkg/webhook/server/tls/file_source_test.go rename to pkg/server/tls/file_source_test.go index dd84519fddf..eaf83649b1c 100644 --- a/pkg/webhook/server/tls/file_source_test.go +++ b/pkg/server/tls/file_source_test.go @@ -18,17 +18,15 @@ package tls import ( "context" - "crypto/rand" "crypto/x509" "crypto/x509/pkix" - "math/big" "os" "path/filepath" "testing" "time" "github.com/go-logr/logr" - logtesting "github.com/go-logr/logr/testing" + "github.com/go-logr/logr/testr" "golang.org/x/sync/errgroup" cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" @@ -36,15 +34,7 @@ import ( ) func TestFileSource_ReadsFile(t *testing.T) { - dir, err := os.MkdirTemp("", "test-filesource-readsfile-") - if err != nil { - t.Fatal(err) - } - defer func() { - if err := os.RemoveAll(dir); err != nil { - t.Fatal(err) - } - }() + dir := t.TempDir() serial := "serial1" pkBytes, certBytes := generatePrivateKeyAndCertificate(t, serial) @@ -56,12 +46,12 @@ func TestFileSource_ReadsFile(t *testing.T) { CertPath: certFile, KeyPath: pkFile, UpdateInterval: interval, - log: logtesting.NewTestLogger(t), + log: testr.New(t), } - ctx, cancel := context.WithCancel(logr.NewContext(context.Background(), logtesting.NewTestLogger(t))) + ctx, cancel := context.WithCancel(logr.NewContext(t.Context(), testr.New(t))) errGroup := new(errgroup.Group) errGroup.Go(func() error { - return source.Run(ctx) + return source.Start(ctx) }) time.Sleep(interval * 2) @@ -86,15 +76,7 @@ func TestFileSource_ReadsFile(t *testing.T) { } func TestFileSource_UpdatesFile(t *testing.T) { - dir, err := os.MkdirTemp("", "test-filesource-updatesfile-") - if err != nil { - t.Fatal(err) - } - defer func() { - if err := os.RemoveAll(dir); err != nil { - t.Fatal(err) - } - }() + dir := t.TempDir() serial := "serial1" pkBytes, certBytes := generatePrivateKeyAndCertificate(t, serial) @@ -107,10 +89,10 @@ func TestFileSource_UpdatesFile(t *testing.T) { KeyPath: pkFile, UpdateInterval: interval, } - ctx, cancel := context.WithCancel(logr.NewContext(context.Background(), logtesting.NewTestLogger(t))) + ctx, cancel := context.WithCancel(logr.NewContext(t.Context(), testr.New(t))) errGroup := new(errgroup.Group) errGroup.Go(func() error { - return source.Run(ctx) + return source.Start(ctx) }) time.Sleep(interval * 2) @@ -157,8 +139,6 @@ func TestFileSource_UpdatesFile(t *testing.T) { } } -var serialNumberLimit = new(big.Int).Lsh(big.NewInt(1), 128) - func generatePrivateKeyAndCertificate(t *testing.T, serial string) ([]byte, []byte) { pk, err := pki.GenerateRSAPrivateKey(2048) if err != nil { @@ -169,14 +149,8 @@ func generatePrivateKeyAndCertificate(t *testing.T, serial string) ([]byte, []by t.Fatal(err) } - serialNumber, err := rand.Int(rand.Reader, serialNumberLimit) - if err != nil { - t.Fatal(err) - } cert := &x509.Certificate{ - Version: 2, BasicConstraintsValid: true, - SerialNumber: serialNumber, PublicKeyAlgorithm: x509.RSA, Subject: pkix.Name{ SerialNumber: serial, diff --git a/pkg/webhook/server/tls/source.go b/pkg/server/tls/source.go similarity index 90% rename from pkg/webhook/server/tls/source.go rename to pkg/server/tls/source.go index 588c2fa19da..b88cd6e4333 100644 --- a/pkg/webhook/server/tls/source.go +++ b/pkg/server/tls/source.go @@ -36,12 +36,12 @@ type CertificateSource interface { // first element of Certificates will be used. GetCertificate(*tls.ClientHelloInfo) (*tls.Certificate, error) - // Run will start the certificate source. + // Start will start the certificate source. // This may include setting up watches on certificate stores, or any other // kind of background operation. - // The Run function should return when stopCh is closed, and may return an + // The Start function should return when stopCh is closed, and may return an // error if an irrecoverable error occurs whilst running. - Run(context.Context) error + Start(context.Context) error // Healthy can be used to check the status of the CertificateSource. // It will return true if the source has a certificate available. diff --git a/pkg/util/cmapichecker/cmapichecker.go b/pkg/util/cmapichecker/cmapichecker.go index 8cb057d86ff..b81d696993b 100644 --- a/pkg/util/cmapichecker/cmapichecker.go +++ b/pkg/util/cmapichecker/cmapichecker.go @@ -17,37 +17,49 @@ limitations under the License. package cmapichecker import ( + "bytes" "context" + "encoding/pem" "fmt" + "net/http" "regexp" - errors "github.com/pkg/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/client-go/rest" + "k8s.io/utils/ptr" "sigs.k8s.io/controller-runtime/pkg/client" cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" + "github.com/cert-manager/cert-manager/pkg/util/pki" ) var ( - ErrCertManagerCRDsNotFound = errors.New("the cert-manager CRDs are not yet installed on the Kubernetes API server") - ErrWebhookServiceFailure = errors.New("the cert-manager webhook service is not created yet") - ErrWebhookDeploymentFailure = errors.New("the cert-manager webhook deployment is not ready yet") - ErrWebhookCertificateFailure = errors.New("the cert-manager webhook CA bundle is not injected yet") -) - -const ( - crdsMappingError = `error finding the scope of the object: failed to get restmapping: no matches for kind "Certificate" in group "cert-manager.io"` - crdsNotFoundError = `the server could not find the requested resource (post certificates.cert-manager.io)` + ErrCertManagerCRDsNotFound = fmt.Errorf("the cert-manager CRDs are not yet installed on the Kubernetes API server") + ErrWebhookServiceFailure = fmt.Errorf("the cert-manager webhook service is not created yet") + ErrWebhookDeploymentFailure = fmt.Errorf("the cert-manager webhook deployment is not ready yet") + ErrWebhookCertificateFailure = fmt.Errorf("the cert-manager webhook CA bundle is not injected yet") + ErrMutationWebhookMissing = fmt.Errorf("the cert-manager mutation webhook did not mutate the dry-run CertificateRequest object") + ErrValidatingWebhookMissing = fmt.Errorf("the cert-manager validating webhook did not validate the dry-run CertificateRequest object") + ErrMutationWebhookIncorrect = fmt.Errorf("the cert-manager validating webhook failed because the dry-run CertificateRequest object was mutated incorrectly") + + ErrFailedToCheckAPI = fmt.Errorf("failed to check the cert-manager API") ) var ( - regexErrCertManagerCRDsNotFound = regexp.MustCompile(`^(` + regexp.QuoteMeta(crdsMappingError) + `|` + regexp.QuoteMeta(crdsNotFoundError) + `)$`) + regexErrCertManagerCRDsNotFound1 = regexp.MustCompile(`the server could not find the requested resource`) + regexErrCertManagerCRDsNotFound2 = regexp.MustCompile(`failed to find API group "cert-manager\.io"`) + regexErrCertManagerCRDsNotFound3 = regexp.MustCompile(`no resources found for group "cert-manager\.io/v1"`) + regexErrCertManagerCRDsNotFound4 = regexp.MustCompile(`no matches for kind "CertificateRequest" in group "cert-manager\.io"`) + regexErrCertManagerCRDsNotFound5 = regexp.MustCompile(`no matches for kind "CertificateRequest" in version "cert-manager\.io/v1"`) regexErrWebhookServiceFailure = regexp.MustCompile(`Post "(.*)": service "(.*)-webhook" not found`) regexErrWebhookDeploymentFailure = regexp.MustCompile(`Post "(.*)": (.*): connect: connection refused`) regexErrWebhookCertificateFailure = regexp.MustCompile(`Post "(.*)": x509: certificate signed by unknown authority`) + regexErrCertmanagerDeniedRequest = regexp.MustCompile(`admission webhook "webhook\.cert-manager\.io" denied the request: (.*)`) + + regexErrForbidden = regexp.MustCompile(`certificaterequests\.cert-manager\.io is forbidden`) + regexErrDenied = regexp.MustCompile(`admission webhook "(.*)" denied the request: (.*)`) ) // Interface is used to check that the cert-manager CRDs have been installed and are usable. @@ -57,23 +69,94 @@ type Interface interface { type cmapiChecker struct { client client.Client + + testValidCR *cmapi.CertificateRequest + testInvalidCR *cmapi.CertificateRequest } // New returns a cert-manager API checker -func New(restcfg *rest.Config, scheme *runtime.Scheme, namespace string) (Interface, error) { +func New(restcfg *rest.Config, namespace string) (Interface, error) { + httpClient, err := rest.HTTPClientFor(restcfg) + if err != nil { + return nil, fmt.Errorf("while creating HTTP client: %w", err) + } + + return NewForConfigAndClient(restcfg, httpClient, namespace) +} + +func NewForConfigAndClient(restcfg *rest.Config, httpClient *http.Client, namespace string) (Interface, error) { + scheme := runtime.NewScheme() if err := cmapi.AddToScheme(scheme); err != nil { - return nil, errors.Wrap(err, "while configuring scheme") + return nil, fmt.Errorf("while configuring scheme: %w", err) } cl, err := client.New(restcfg, client.Options{ - Scheme: scheme, + HTTPClient: httpClient, + Scheme: scheme, + DryRun: ptr.To(true), }) if err != nil { - return nil, errors.Wrap(err, "while creating client") + return nil, fmt.Errorf("while creating client: %w", err) + } + + cl = client.NewNamespacedClient(cl, namespace) + + x509CertReq, err := pki.GenerateCSR( + &cmapi.Certificate{ + Spec: cmapi.CertificateSpec{ + DNSNames: []string{"example.com"}, + PrivateKey: &cmapi.CertificatePrivateKey{ + Algorithm: "ECDSA", + Size: 521, + }, + }, + }, + pki.WithEncodeBasicConstraintsInRequest(true), + ) + if err != nil { + return nil, fmt.Errorf("while generating CSR: %w", err) + } + + pk, err := pki.GenerateECPrivateKey(521) + if err != nil { + return nil, fmt.Errorf("while generating private key: %w", err) + } + + csrDER, err := pki.EncodeCSR(x509CertReq, pk) + if err != nil { + return nil, fmt.Errorf("while encoding CSR: %w", err) + } + + csrPEM := bytes.NewBuffer([]byte{}) + err = pem.Encode(csrPEM, &pem.Block{Type: "CERTIFICATE REQUEST", Bytes: csrDER}) + if err != nil { + return nil, fmt.Errorf("while encoding CSR to PEM: %w", err) } return &cmapiChecker{ - client: client.NewNamespacedClient(client.NewDryRunClient(cl), namespace), + client: cl, + testValidCR: &cmapi.CertificateRequest{ + ObjectMeta: metav1.ObjectMeta{ + GenerateName: "cmapichecker-valid-", + }, + Spec: cmapi.CertificateRequestSpec{ + Request: csrPEM.Bytes(), + IssuerRef: cmmeta.IssuerReference{ + Name: "cmapichecker", + }, + }, + }, + testInvalidCR: &cmapi.CertificateRequest{ + ObjectMeta: metav1.ObjectMeta{ + GenerateName: "cmapichecker-invalid-", + }, + Spec: cmapi.CertificateRequestSpec{ + Request: []byte("invalid-csr"), + IssuerRef: cmmeta.IssuerReference{ + Name: "cmapichecker", + }, + }, + }, }, nil } @@ -85,48 +168,41 @@ func New(restcfg *rest.Config, scheme *runtime.Scheme, namespace string) (Interf // we have disabled the serving of non-v1 CRD versions, so it is no longer // possible to test the reachability of the conversion webhook. func (o *cmapiChecker) Check(ctx context.Context) error { - cert := &cmapi.Certificate{ - ObjectMeta: metav1.ObjectMeta{ - GenerateName: "cmapichecker-", - }, - Spec: cmapi.CertificateSpec{ - DNSNames: []string{"cmapichecker.example"}, - SecretName: "cmapichecker", - IssuerRef: cmmeta.ObjectReference{ - Name: "cmapichecker", - }, - }, - } + // Test the mutating webhook, which should add the username, UID, and groups + if err := func() error { + certReq := o.testValidCR.DeepCopy() + if err := o.client.Create(ctx, certReq); err != nil { + return err + } - if err := o.client.Create(ctx, cert); err != nil { - return &ApiCheckError{ - SimpleError: translateToSimpleError(err), - UnderlyingError: err, + if certReq.Spec.Username == "" && + certReq.Spec.UID == "" { + return ErrMutationWebhookMissing } + + return nil + }(); err != nil { + return err } - return nil -} -type ApiCheckError struct { - SimpleError error - UnderlyingError error -} + // Test the validating webhook, which should reject the request + if err := func() error { + certReq := o.testInvalidCR.DeepCopy() + if err := o.client.Create(ctx, certReq); err == nil { + return ErrValidatingWebhookMissing + } else if !regexErrCertmanagerDeniedRequest.MatchString(err.Error()) { + return err + } -func (e *ApiCheckError) Error() string { - // If no simple error exists, print underlying error - if e.SimpleError == nil { - return e.UnderlyingError.Error() + return nil + }(); err != nil { + return err } - return fmt.Sprintf("%v (%v)", e.SimpleError.Error(), e.UnderlyingError.Error()) -} -// If no simple error exists, this function will return nil -// which indicates that the error is not unwrappable -func (e *ApiCheckError) Unwrap() error { - return e.SimpleError + return nil } -// This translateToSimpleError function detects errors based on the error message. +// TranslateToSimpleError detects errors based on the error message. // It tries to map these error messages to a better understandable error message that // explains what is wrong. If it cannot create a simple error, it will return nil. // ErrCertManagerCRDsNotFound: @@ -137,18 +213,36 @@ func (e *ApiCheckError) Unwrap() error { // - Internal error occurred: failed calling webhook "webhook.cert-manager.io": Post "https://cert-manager-webhook.cert-manager.svc:443/mutate?timeout=10s": dial tcp 10.96.38.90:443: connect: connection refused // ErrWebhookCertificateFailure: // - Internal error occurred: failed calling webhook "webhook.cert-manager.io": Post "https://cert-manager-webhook.cert-manager.svc:443/mutate?timeout=10s": x509: certificate signed by unknown authority (possibly because of "x509: ECDSA verification failure" while trying to verify candidate authority certificate "cert-manager-webhook-ca") -func translateToSimpleError(err error) error { - s := err.Error() +// ErrMutationWebhookIncorrect: +// - admission webhook "webhook.cert-manager.io" denied the request: [spec.username: Forbidden: username identity must be that of the requester, spec.groups: Forbidden: groups identity must be that of the requester] +// ErrFailedToCheckAPI: +// - certificaterequests.cert-manager.io is forbidden: User "test" cannot create resource "certificaterequests" in API group "cert-manager.io" in the namespace "default" +// - admission webhook "validate.kyverno.svc-fail" denied the request: ... +func TranslateToSimpleError(err error) error { + if err == nil { + return nil + } - if regexErrCertManagerCRDsNotFound.MatchString(s) { + s := err.Error() + switch { + case regexErrCertManagerCRDsNotFound1.MatchString(s) || + regexErrCertManagerCRDsNotFound2.MatchString(s) || + regexErrCertManagerCRDsNotFound3.MatchString(s) || + regexErrCertManagerCRDsNotFound4.MatchString(s) || + regexErrCertManagerCRDsNotFound5.MatchString(s): return ErrCertManagerCRDsNotFound - } else if regexErrWebhookServiceFailure.MatchString(s) { + case regexErrWebhookServiceFailure.MatchString(s): return ErrWebhookServiceFailure - } else if regexErrWebhookDeploymentFailure.MatchString(s) { + case regexErrWebhookDeploymentFailure.MatchString(s): return ErrWebhookDeploymentFailure - } else if regexErrWebhookCertificateFailure.MatchString(s) { + case regexErrWebhookCertificateFailure.MatchString(s): return ErrWebhookCertificateFailure + case regexErrCertmanagerDeniedRequest.MatchString(s): + return ErrMutationWebhookIncorrect + case regexErrForbidden.MatchString(s) || + regexErrDenied.MatchString(s): + return ErrFailedToCheckAPI + default: + return nil } - - return nil } diff --git a/pkg/util/cmapichecker/cmapichecker_test.go b/pkg/util/cmapichecker/cmapichecker_test.go index b514c862a50..31dcb86ac62 100644 --- a/pkg/util/cmapichecker/cmapichecker_test.go +++ b/pkg/util/cmapichecker/cmapichecker_test.go @@ -17,179 +17,362 @@ limitations under the License. package cmapichecker import ( - "context" - "errors" - "fmt" + "encoding/json" + "net/http" + "net/http/httptest" "testing" - "k8s.io/apimachinery/pkg/runtime" - "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/client/fake" - - cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/rest" ) -type fakeErrorClient struct { - client.Client +const ( + crNoMutation = `{ + "kind":"CertificateRequest", + "apiVersion":"cert-manager.io/v1", + "metadata":{ + "name":"cmapichecker-0001", + "namespace":"test-namespace" + }, + "spec":{ + "issuerRef":{"name":"cmapichecker"}, + "request":"PENTUi1WQUxVRT4=" + } + }` + crAfterMutation = `{ + "kind":"CertificateRequest", + "apiVersion":"cert-manager.io/v1", + "metadata":{ + "name":"cmapichecker-0001", + "namespace":"test-namespace" + }, + "spec":{ + "issuerRef":{"name":"cmapichecker"}, + "request":"PENTUi1WQUxVRT4=", + "username":"test-user", + "uid":"test-uid" + }, + "status":{} + }` +) - createError error -} +func TestCheck(t *testing.T) { + type testT struct { + discoveryResponse func(t *testing.T, r *http.Request) (int, []byte) + createValidResponse func(t *testing.T, r *http.Request) (int, []byte) + createInvalidResponse func(t *testing.T, r *http.Request) (int, []byte) -func (cl *fakeErrorClient) Create(ctx context.Context, obj client.Object, opts ...client.CreateOption) error { - if cl.createError != nil { - return cl.createError + expectedError string + expectedSimpleError string } - return cl.Client.Create(ctx, obj, opts...) -} - -func newFakeCmapiChecker() (*fakeErrorClient, Interface, error) { - scheme := runtime.NewScheme() - if err := cmapi.AddToScheme(scheme); err != nil { - return nil, nil, err - } - cl := fake.NewClientBuilder().WithScheme(scheme).Build() - errorClient := &fakeErrorClient{ - Client: cl, - createError: nil, - } - - return errorClient, &cmapiChecker{ - client: errorClient, - }, nil -} - -const ( - errCertManagerCRDsMapping = `error finding the scope of the object: failed to get restmapping: no matches for kind "Certificate" in group "cert-manager.io"` - errCertManagerCRDsNotFound = `the server could not find the requested resource (post certificates.cert-manager.io)` - - errMutatingWebhookServiceFailure = `Internal error occurred: failed calling webhook "webhook.cert-manager.io": Post "https://cert-manager-webhook.cert-manager.svc:443/mutate?timeout=10s": service "cert-manager-webhook" not found` - errMutatingWebhookDeploymentFailure = `Internal error occurred: failed calling webhook "webhook.cert-manager.io": Post "https://cert-manager-webhook.cert-manager.svc:443/mutate?timeout=10s": dial tcp 10.96.38.90:443: connect: connection refused` - errMutatingWebhookCertificateFailure = `Internal error occurred: failed calling webhook "webhook.cert-manager.io": Post "https://cert-manager-webhook.cert-manager.svc:443/mutate?timeout=10s": x509: certificate signed by unknown authority (possibly because of "x509: ECDSA verification failure" while trying to verify candidate authority certificate "cert-manager-webhook-ca"` - - // These /convert error examples test that we can correctly parse errors - // while connecting to the conversion webhook, - // but as of cert-manager 1.6 the conversion webhook will no-longer be used - // because legacy CRD versions will no longer be "served" - // and in 1.7 the conversion webhook may be removed at which point these can - // be removed too. - // TODO: Add tests for errors when connecting to the /validate - // ValidatingWebhook endpoint. - errConversionWebhookServiceFailure = `conversion webhook for cert-manager.io/v1alpha2, Kind=Certificate failed: Post "https://cert-manager-webhook.cert-manager.svc:443/convert?timeout=30s": service "cert-manager-webhook" not found` - errConversionWebhookDeploymentFailure = `conversion webhook for cert-manager.io/v1alpha2, Kind=Certificate failed: Post "https://cert-manager-webhook.cert-manager.svc:443/convert?timeout=30s": dial tcp 10.96.38.90:443: connect: connection refused` - errConversionWebhookCertificateFailure = `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` -) - -func TestCmapiChecker(t *testing.T) { tests := map[string]testT{ - "check API without errors": { - createError: nil, - - expectedSimpleError: "", - expectedVerboseError: "", + "no errors": {}, + "without any cert-manager CRDs installed (404)": { + discoveryResponse: func(t *testing.T, r *http.Request) (int, []byte) { + return http.StatusNotFound, nil + }, + expectedError: `error finding the scope of the object: failed to get restmapping: no matches for kind "CertificateRequest" in version "cert-manager.io/v1"`, + expectedSimpleError: ErrCertManagerCRDsNotFound.Error(), }, - "check API without CRDs installed 1": { - createError: errors.New(errCertManagerCRDsMapping), - - expectedSimpleError: ErrCertManagerCRDsNotFound.Error(), - expectedVerboseError: fmt.Sprintf("%s (%s)", ErrCertManagerCRDsNotFound.Error(), errCertManagerCRDsMapping), + "without any cert-manager CRDs installed (empty list)": { + discoveryResponse: func(t *testing.T, r *http.Request) (int, []byte) { + return http.StatusOK, []byte(`{ + "kind":"APIResourceList", + "apiVersion":"v1", + "groupVersion":"cert-manager.io/v1", + "resources":[] + }`) + }, + expectedError: `error finding the scope of the object: failed to get restmapping: no matches for kind "CertificateRequest" in version "cert-manager.io/v1"`, + expectedSimpleError: ErrCertManagerCRDsNotFound.Error(), }, - "check API without CRDs installed 2": { - createError: errors.New(errCertManagerCRDsNotFound), - - expectedSimpleError: ErrCertManagerCRDsNotFound.Error(), - expectedVerboseError: fmt.Sprintf("%s (%s)", ErrCertManagerCRDsNotFound.Error(), errCertManagerCRDsNotFound), + "without certificate request CRD installed": { + discoveryResponse: func(t *testing.T, r *http.Request) (int, []byte) { + return http.StatusOK, []byte(`{ + "kind":"APIResourceList", + "apiVersion":"v1", + "groupVersion":"cert-manager.io/v1", + "resources":[ + { + "name":"test", + "singularName":"", + "namespaced":true, + "kind":"Test", + "verbs":["get","patch","update"] + } + ] + }`) + }, + expectedError: `error finding the scope of the object: failed to get restmapping: no matches for kind "CertificateRequest" in version "cert-manager.io/v1"`, + expectedSimpleError: ErrCertManagerCRDsNotFound.Error(), }, - - "check API with mutating webhook service not ready": { - createError: errors.New(errMutatingWebhookServiceFailure), - - expectedSimpleError: ErrWebhookServiceFailure.Error(), - expectedVerboseError: fmt.Sprintf("%s (%s)", ErrWebhookServiceFailure.Error(), errMutatingWebhookServiceFailure), + "with missing certificate request endpoint": { + discoveryResponse: func(t *testing.T, r *http.Request) (int, []byte) { + return http.StatusNotFound, nil + }, + expectedError: `error finding the scope of the object: failed to get restmapping: no matches for kind "CertificateRequest" in version "cert-manager.io/v1"`, + expectedSimpleError: ErrCertManagerCRDsNotFound.Error(), }, - "check API with conversion webhook service not ready": { - createError: errors.New(errConversionWebhookServiceFailure), - - expectedSimpleError: ErrWebhookServiceFailure.Error(), - expectedVerboseError: fmt.Sprintf("%s (%s)", ErrWebhookServiceFailure.Error(), errConversionWebhookServiceFailure), + "dry-run certificate request was not mutated": { + createValidResponse: func(t *testing.T, r *http.Request) (int, []byte) { + return http.StatusOK, []byte(crNoMutation) + }, + expectedError: ErrMutationWebhookMissing.Error(), + }, + "cr was denied by 3rd party webhook": { + createInvalidResponse: func(t *testing.T, r *http.Request) (int, []byte) { + return http.StatusNotAcceptable, []byte(`{ + "kind":"Status", + "apiVersion":"v1", + "metadata":{}, + "status":"Failure", + "message":"admission webhook \"other-webhook.io\" denied the request: [ERROR MESSAGE]", + "reason":"NotAcceptable", + "code":406 + }`) + }, + expectedError: "admission webhook \"other-webhook.io\" denied the request: [ERROR MESSAGE]", + expectedSimpleError: ErrFailedToCheckAPI.Error(), + }, + "missing validation error": { + createInvalidResponse: func(t *testing.T, r *http.Request) (int, []byte) { + return http.StatusOK, []byte(crAfterMutation) + }, + expectedError: ErrValidatingWebhookMissing.Error(), }, + } - "check API with mutating webhook pod not accepting connections": { - createError: errors.New(errMutatingWebhookDeploymentFailure), + type testFailure struct { + message string + reason string + code int + simpleError string + } - expectedSimpleError: ErrWebhookDeploymentFailure.Error(), - expectedVerboseError: fmt.Sprintf("%s (%s)", ErrWebhookDeploymentFailure.Error(), errMutatingWebhookDeploymentFailure), - }, - "check API with conversion webhook pod not accepting connections": { - createError: errors.New(errConversionWebhookDeploymentFailure), + for name, test := range map[string]testFailure{ + "no permission": { + message: `certificaterequests.cert-manager.io is forbidden: User "test" cannot create resource "certificaterequests" in API group "cert-manager.io" in the namespace "test-namespace"`, + reason: "Forbidden", + code: http.StatusForbidden, - expectedSimpleError: ErrWebhookDeploymentFailure.Error(), - expectedVerboseError: fmt.Sprintf("%s (%s)", ErrWebhookDeploymentFailure.Error(), errConversionWebhookDeploymentFailure), + simpleError: ErrFailedToCheckAPI.Error(), }, - "check API with webhook certificate not updated in mutation webhook resource definitions": { - createError: errors.New(errMutatingWebhookCertificateFailure), + "service not found": { + message: `failed calling webhook "webhook.cert-manager.io": Post "https://cert-manager-webhook.cert-manager.svc:443/mutate?timeout=10s": service "cert-manager-webhook" not found`, + reason: "InternalError", + code: 500, - expectedSimpleError: ErrWebhookCertificateFailure.Error(), - expectedVerboseError: fmt.Sprintf("%s (%s)", ErrWebhookCertificateFailure.Error(), errMutatingWebhookCertificateFailure), + simpleError: ErrWebhookServiceFailure.Error(), }, - "check API with webhook certificate not updated in conversion webhook resource definitions": { - createError: errors.New(errConversionWebhookCertificateFailure), + "connection refused": { + message: `failed calling webhook "webhook.cert-manager.io": failed to call webhook: Post "https://cert-manager-webhook.cert-manager.svc:443/mutate?timeout=30s": dial tcp 10.96.19.42:443: connect: connection refused`, + reason: "InternalError", + code: 500, - expectedSimpleError: ErrWebhookCertificateFailure.Error(), - expectedVerboseError: fmt.Sprintf("%s (%s)", ErrWebhookCertificateFailure.Error(), errConversionWebhookCertificateFailure), + simpleError: ErrWebhookDeploymentFailure.Error(), }, - "unexpected error": { - createError: errors.New("unexpected error"), - expectedSimpleError: "", - expectedVerboseError: "unexpected error", - }, - } + "certificate signed by unknown authority": { + message: `failed calling webhook "webhook.cert-manager.io": failed to call webhook: Post "https://cert-manager-webhook.cert-manager.svc:443/mutate?timeout=30s": x509: certificate signed by unknown authority`, + reason: "NotAcceptable", + code: 406, - for n, test := range tests { - t.Run(n, func(t *testing.T) { - runTest(t, test) - }) - } -} + simpleError: ErrWebhookCertificateFailure.Error(), + }, + "certificate signed by unknown authority (ECDSA verification failure)": { + message: `failed calling webhook "webhook.cert-manager.io": Post "https://cert-manager-webhook.cert-manager.svc:443/mutate?timeout=10s": x509: certificate signed by unknown authority (possibly because of "x509: ECDSA verification failure" while trying to verify candidate authority certificate "cert-manager-webhook-ca"`, + reason: "NotAcceptable", + code: 406, -type testT struct { - createError error + simpleError: ErrWebhookCertificateFailure.Error(), + }, - expectedSimpleError string - expectedVerboseError string -} + "validating webhook error (3rd party)": { + message: `admission webhook "other-webhook.io" denied the request: [ERROR MESSAGE]`, + reason: "NotAcceptable", + code: 406, -func runTest(t *testing.T, test testT) { - errorClient, checker, err := newFakeCmapiChecker() - if err != nil { - t.Error(err) - } + simpleError: ErrFailedToCheckAPI.Error(), + }, + "missing mutating webhook": { + message: `admission webhook "webhook.cert-manager.io" denied the request: [spec.username: Forbidden: username identity must be that of the requester, spec.groups: Forbidden: groups identity must be that of the requester]`, + reason: "NotAcceptable", + code: 406, - errorClient.createError = test.createError + simpleError: ErrMutationWebhookIncorrect.Error(), + }, + "validating webhook error": { + message: `admission webhook "webhook.cert-manager.io" denied the request: spec.request: Invalid value: []byte{0x00}: error decoding certificate request PEM block`, + reason: "NotAcceptable", + code: 406, - var unwrappedErr error - err = checker.Check(context.TODO()) - if err != nil { - if err.Error() != test.expectedVerboseError { - t.Errorf("error differs from expected error:\n%s\n vs \n%s", err.Error(), test.expectedVerboseError) - } + simpleError: ErrMutationWebhookIncorrect.Error(), + }, - unwrappedErr = errors.Unwrap(err) - } else { - if test.expectedVerboseError != "" { - t.Errorf("expected error did not occure:\n%s", test.expectedVerboseError) + "unknown error": { + message: `UNKNOWN ERROR`, + reason: "InternalError", + code: 500, + }, + } { + tests["valid_failure_"+name] = testT{ + createValidResponse: func(t *testing.T, r *http.Request) (int, []byte) { + byteResponse, err := json.Marshal(map[string]interface{}{ + "kind": "Status", + "apiVersion": "v1", + "metadata": map[string]interface{}{}, + "status": "Failure", + "message": test.message, + "reason": test.reason, + "code": test.code, + }) + if err != nil { + t.Error(err) + } + return test.code, byteResponse + }, + expectedError: test.message, + expectedSimpleError: test.simpleError, } } - if unwrappedErr != nil { - if unwrappedErr.Error() != test.expectedSimpleError { - t.Errorf("simple error differs from expected error:\n%s\n vs \n%s", unwrappedErr.Error(), test.expectedSimpleError) - } - } else { - if test.expectedSimpleError != "" { - t.Errorf("expected simple error did not occure:\n%s", test.expectedSimpleError) - } + for name, test := range tests { + t.Run(name, func(t *testing.T) { + // fake https server to simulate the Kubernetes API server responses + mockKubernetesAPI := func(t *testing.T, r *http.Request) (int, []byte) { + switch r.URL.Path { + case "/api": + return http.StatusOK, []byte(`{ + "kind":"APIVersions", + "versions":["v1"], + "serverAddressByClientCIDRs":[ + { + "clientCIDR":"0.0.0.0/0", + "serverAddress":"10.10.1.2:6443" + } + ] + }`) + + case "/apis": + return http.StatusOK, []byte(`{ + "kind":"APIGroupList", + "apiVersion":"v1", + "groups":[ + { + "name":"cert-manager.io", + "versions":[{"groupVersion":"cert-manager.io/v1","version":"v1"}], + "preferredVersion":{"groupVersion":"cert-manager.io/v1","version":"v1"} + } + ] + }`) + + case "/apis/cert-manager.io/v1": + if test.discoveryResponse != nil { + return test.discoveryResponse(t, r) + } + + return http.StatusOK, []byte(`{ + "kind":"APIResourceList", + "apiVersion":"v1", + "groupVersion":"cert-manager.io/v1", + "resources":[ + { + "name":"certificaterequests", + "singularName":"certificaterequest", + "namespaced":true, + "kind":"CertificateRequest", + "verbs":["delete","deletecollection","get","list","patch","create","update","watch"], + "shortNames":["cr","crs"], + "categories":["cert-manager"], + "storageVersionHash":"tuxiikMaACg=" + }, + { + "name":"certificaterequests/status", + "singularName":"", + "namespaced":true, + "kind":"CertificateRequest", + "verbs":["get","patch","update"] + } + ] + }`) + case "/apis/cert-manager.io/v1/namespaces/test-namespace/certificaterequests": + obj := metav1.PartialObjectMetadata{} + if err := json.NewDecoder(r.Body).Decode(&obj); err != nil { + t.Errorf("failed to decode request body: %v", err) + } + + switch obj.GenerateName { + case "cmapichecker-valid-": + if test.createValidResponse != nil { + return test.createValidResponse(t, r) + } + + return http.StatusOK, []byte(crAfterMutation) + case "cmapichecker-invalid-": + if test.createInvalidResponse != nil { + return test.createInvalidResponse(t, r) + } + + return http.StatusNotAcceptable, []byte(`{ + "kind":"Status", + "apiVersion":"v1", + "metadata":{}, + "status":"Failure", + "message":"admission webhook \"webhook.cert-manager.io\" denied the request: [ERROR MESSAGE]", + "reason":"NotAcceptable", + "code":406 + }`) + } + default: + } + + return http.StatusNotFound, nil + } + testServer := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + statusCode, content := mockKubernetesAPI(t, r) + w.WriteHeader(statusCode) + if content == nil { + return + } + + if _, err := w.Write(content); err != nil { + t.Errorf("failed to write response: %v", err) + } + })) + t.Cleanup(testServer.Close) + + restConfig := &rest.Config{ + Host: testServer.URL, + } + checker, err := NewForConfigAndClient(restConfig, testServer.Client(), "test-namespace") + if err != nil { + t.Fatalf("failed to create checker: %v", err) + } + + for i := range 10 { + t.Logf("# check %d", i) + + err = checker.Check(t.Context()) + switch { + case err == nil && test.expectedError == "": + case err == nil && test.expectedError != "": + t.Errorf("expected error %q, got nil", test.expectedError) + case err.Error() != test.expectedError: + t.Errorf("expected error %q, got %q", test.expectedError, err.Error()) + } + + simpleErr := TranslateToSimpleError(err) + switch { + case simpleErr == nil && test.expectedSimpleError == "": + case simpleErr == nil && test.expectedSimpleError != "": + t.Errorf("expected error %q, got nil", test.expectedSimpleError) + case simpleErr.Error() != test.expectedSimpleError: + t.Errorf("expected error %q, got %q", test.expectedSimpleError, simpleErr.Error()) + } + } + }) } } diff --git a/pkg/util/configfile/configfile.go b/pkg/util/configfile/configfile.go new file mode 100644 index 00000000000..9eac67707da --- /dev/null +++ b/pkg/util/configfile/configfile.go @@ -0,0 +1,83 @@ +/* +Copyright 2021 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. +*/ + +package configfile + +import ( + "fmt" + "os" + "path/filepath" +) + +type configurationFSLoader struct { + readFileFunc func(filename string) ([]byte, error) + filename string +} + +type ConfigFile interface { + DecodeAndConfigure([]byte) error + GetPathRefs() ([]*string, error) +} + +func (f *configurationFSLoader) Load(config ConfigFile) error { + data, err := f.readFileFunc(f.filename) + if err != nil { + return fmt.Errorf("failed to read config file %q, error: %v", f.filename, err) + } + + if len(data) == 0 { + return fmt.Errorf("config file %q was empty", f.filename) + } + + if err := config.DecodeAndConfigure(data); err != nil { + return err + } + + // make all paths absolute + if paths, err := config.GetPathRefs(); err != nil { + return err + } else { + resolveRelativePaths(paths, filepath.Dir(f.filename)) + } + + return nil +} + +func NewConfigurationFSLoader(readFileFunc func(filename string) ([]byte, error), filename string) (*configurationFSLoader, error) { + var f func(string) ([]byte, error) + + // Default the readfile function to use os.Readfile for convenience. + if readFileFunc == nil { + f = os.ReadFile + } else { + f = readFileFunc + } + + return &configurationFSLoader{ + readFileFunc: f, + filename: filename, + }, nil +} + +func resolveRelativePaths(paths []*string, root string) { + for _, path := range paths { + // leave empty paths alone, "no path" is a valid input + // do not attempt to resolve paths that are already absolute + if len(*path) > 0 && !filepath.IsAbs(*path) { + *path = filepath.Join(root, *path) + } + } +} diff --git a/pkg/webhook/configfile/configfile_test.go b/pkg/util/configfile/configfile_test.go similarity index 73% rename from pkg/webhook/configfile/configfile_test.go rename to pkg/util/configfile/configfile_test.go index 475960b30b7..45de96dcf27 100644 --- a/pkg/webhook/configfile/configfile_test.go +++ b/pkg/util/configfile/configfile_test.go @@ -19,13 +19,17 @@ package configfile import ( "fmt" "testing" + + "github.com/cert-manager/cert-manager/pkg/webhook/configfile" ) func TestFSLoader_Load(t *testing.T) { const expectedFilename = "/path/to/config/file" const kubeConfigPath = "path/to/kubeconfig/file" - loader, err := NewFSLoader(newFakeFS(func(filename string) ([]byte, error) { + webhookConfig := configfile.New() + + loader, err := NewConfigurationFSLoader(func(filename string) ([]byte, error) { if filename != expectedFilename { t.Fatalf("unexpected filename %q passed to ReadFile", filename) return nil, fmt.Errorf("unexpected filename %q", filename) @@ -33,31 +37,18 @@ func TestFSLoader_Load(t *testing.T) { return []byte(fmt.Sprintf(`apiVersion: webhook.config.cert-manager.io/v1alpha1 kind: WebhookConfiguration kubeConfig: %s`, kubeConfigPath)), nil - }), expectedFilename) + }, expectedFilename) if err != nil { t.Fatal(err) } - cfg, err := loader.Load() - if err != nil { + if err := loader.Load(webhookConfig); err != nil { t.Fatal(err) } // the config loader will force paths to be 'absolute' if they are provided as relative. absKubeConfigPath := "/path/to/config/path/to/kubeconfig/file" - if cfg.KubeConfig != absKubeConfigPath { - t.Errorf("expected kubeConfig to be set to %q but got %q", absKubeConfigPath, cfg.KubeConfig) + if webhookConfig.Config.KubeConfig != absKubeConfigPath { + t.Errorf("expected kubeConfig to be set to %q but got %q", absKubeConfigPath, webhookConfig.Config.KubeConfig) } } - -func newFakeFS(readFileFunc func(string) ([]byte, error)) Filesystem { - return fakeFS{readFileFunc: readFileFunc} -} - -type fakeFS struct { - readFileFunc func(string) ([]byte, error) -} - -func (f fakeFS) ReadFile(filename string) ([]byte, error) { - return f.readFileFunc(filename) -} diff --git a/pkg/util/feature/feature_gate.go b/pkg/util/feature/feature_gate.go index 512b96799ae..3a67691c828 100644 --- a/pkg/util/feature/feature_gate.go +++ b/pkg/util/feature/feature_gate.go @@ -24,7 +24,7 @@ var ( // DefaultMutableFeatureGate is a mutable version of DefaultFeatureGate. // Only top-level commands/options setup and the k8s.io/component-base/featuregate/testing package should make use of this. // Tests that need to modify feature gates for the duration of their test should use: - // defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features., )() + // featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features., ) DefaultMutableFeatureGate featuregate.MutableFeatureGate = featuregate.NewFeatureGate() // DefaultFeatureGate is a shared global FeatureGate. diff --git a/pkg/util/kube/pki.go b/pkg/util/kube/pki.go index 8ed83a15e08..eea3ee98dc3 100644 --- a/pkg/util/kube/pki.go +++ b/pkg/util/kube/pki.go @@ -22,8 +22,8 @@ import ( "crypto/x509" corev1 "k8s.io/api/core/v1" - corelisters "k8s.io/client-go/listers/core/v1" + internalinformers "github.com/cert-manager/cert-manager/internal/informers" cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" "github.com/cert-manager/cert-manager/pkg/util/errors" "github.com/cert-manager/cert-manager/pkg/util/pki" @@ -32,7 +32,7 @@ import ( // SecretTLSKeyRef will decode a PKCS1/SEC1 (in effect, a RSA or ECDSA) private key stored in a // secret with 'name' in 'namespace'. It will read the private key data from the secret // entry with name 'keyName'. -func SecretTLSKeyRef(ctx context.Context, secretLister corelisters.SecretLister, namespace, name, keyName string) (crypto.Signer, error) { +func SecretTLSKeyRef(ctx context.Context, secretLister internalinformers.SecretLister, namespace, name, keyName string) (crypto.Signer, error) { secret, err := secretLister.Secrets(namespace).Get(name) if err != nil { return nil, err @@ -49,7 +49,7 @@ func SecretTLSKeyRef(ctx context.Context, secretLister corelisters.SecretLister, // SecretTLSKey will decode a PKCS1/SEC1 (in effect, a RSA or ECDSA) private key stored in a // secret with 'name' in 'namespace'. It will read the private key data from the secret // entry with name 'keyName'. -func SecretTLSKey(ctx context.Context, secretLister corelisters.SecretLister, namespace, name string) (crypto.Signer, error) { +func SecretTLSKey(ctx context.Context, secretLister internalinformers.SecretLister, namespace, name string) (crypto.Signer, error) { return SecretTLSKeyRef(ctx, secretLister, namespace, name, corev1.TLSPrivateKeyKey) } @@ -63,13 +63,13 @@ func ParseTLSKeyFromSecret(secret *corev1.Secret, keyName string) (crypto.Signer key, err := pki.DecodePrivateKeyBytes(keyBytes) if err != nil { - return nil, keyBytes, errors.NewInvalidData(err.Error()) + return nil, keyBytes, errors.NewInvalidData("%s", err) } return key, keyBytes, nil } -func SecretTLSCertChain(ctx context.Context, secretLister corelisters.SecretLister, namespace, name string) ([]*x509.Certificate, error) { +func SecretTLSCertChain(ctx context.Context, secretLister internalinformers.SecretLister, namespace, name string) ([]*x509.Certificate, error) { secret, err := secretLister.Secrets(namespace).Get(name) if err != nil { return nil, err @@ -82,7 +82,7 @@ func SecretTLSCertChain(ctx context.Context, secretLister corelisters.SecretList cert, err := pki.DecodeX509CertificateChainBytes(certBytes) if err != nil { - return cert, errors.NewInvalidData(err.Error()) + return cert, errors.NewInvalidData("%s", err) } return cert, nil @@ -91,7 +91,7 @@ func SecretTLSCertChain(ctx context.Context, secretLister corelisters.SecretList // SecretTLSKeyPairAndCA returns the X.509 certificate chain and private key of // the leaf certificate contained in the target Secret. If the ca.crt field exists // on the Secret, it is parsed and added to the end of the certificate chain. -func SecretTLSKeyPairAndCA(ctx context.Context, secretLister corelisters.SecretLister, namespace, name string) ([]*x509.Certificate, crypto.Signer, error) { +func SecretTLSKeyPairAndCA(ctx context.Context, secretLister internalinformers.SecretLister, namespace, name string) ([]*x509.Certificate, crypto.Signer, error) { certs, key, err := SecretTLSKeyPair(ctx, secretLister, namespace, name) if err != nil { return nil, nil, err @@ -108,13 +108,13 @@ func SecretTLSKeyPairAndCA(ctx context.Context, secretLister corelisters.SecretL } ca, err := pki.DecodeX509CertificateBytes(caBytes) if err != nil { - return nil, key, errors.NewInvalidData(err.Error()) + return nil, key, errors.NewInvalidData("%s", err) } return append(certs, ca), key, nil } -func SecretTLSKeyPair(ctx context.Context, secretLister corelisters.SecretLister, namespace, name string) ([]*x509.Certificate, crypto.Signer, error) { +func SecretTLSKeyPair(ctx context.Context, secretLister internalinformers.SecretLister, namespace, name string) ([]*x509.Certificate, crypto.Signer, error) { secret, err := secretLister.Secrets(namespace).Get(name) if err != nil { return nil, nil, err @@ -126,7 +126,7 @@ func SecretTLSKeyPair(ctx context.Context, secretLister corelisters.SecretLister } key, err := pki.DecodePrivateKeyBytes(keyBytes) if err != nil { - return nil, nil, errors.NewInvalidData(err.Error()) + return nil, nil, errors.NewInvalidData("%s", err) } certBytes, ok := secret.Data[corev1.TLSCertKey] @@ -135,13 +135,13 @@ func SecretTLSKeyPair(ctx context.Context, secretLister corelisters.SecretLister } cert, err := pki.DecodeX509CertificateChainBytes(certBytes) if err != nil { - return nil, key, errors.NewInvalidData(err.Error()) + return nil, key, errors.NewInvalidData("%s", err) } return cert, key, nil } -func SecretTLSCert(ctx context.Context, secretLister corelisters.SecretLister, namespace, name string) (*x509.Certificate, error) { +func SecretTLSCert(ctx context.Context, secretLister internalinformers.SecretLister, namespace, name string) (*x509.Certificate, error) { certs, err := SecretTLSCertChain(ctx, secretLister, namespace, name) if err != nil { return nil, err diff --git a/pkg/util/pki/asn1_util.go b/pkg/util/pki/asn1_util.go new file mode 100644 index 00000000000..360c0ef5e88 --- /dev/null +++ b/pkg/util/pki/asn1_util.go @@ -0,0 +1,199 @@ +/* +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. +*/ + +// This file contains some code copied from the Go standard library under the following license: https://github.com/golang/go/blob/c95fe91d0715dc0a8d55ac80a80f383c3635548b/LICENSE +package pki + +import ( + "encoding/asn1" + "errors" + "fmt" + "strconv" + "strings" + "unicode" + "unicode/utf8" +) + +// ParseObjectIdentifier parses an object identifier from its string representation. +func ParseObjectIdentifier(oidString string) (oid asn1.ObjectIdentifier, err error) { + if len(oidString) == 0 { + return nil, errors.New("zero length OBJECT IDENTIFIER") + } + + parts := strings.Split(oidString, ".") + + oid = make(asn1.ObjectIdentifier, 0, len(parts)) + for _, part := range parts { + value, err := strconv.Atoi(part) + if err != nil { + return nil, err + } + + oid = append(oid, value) + } + + return oid, nil +} + +type UniversalValueType int + +const ( + UniversalValueTypeBytes UniversalValueType = iota + UniversalValueTypeIA5String + UniversalValueTypeUTF8String + UniversalValueTypePrintableString +) + +type UniversalValue struct { + Bytes []byte + IA5String string + UTF8String string + PrintableString string +} + +func (uv UniversalValue) Type() UniversalValueType { + isBytes := uv.Bytes != nil + isIA5String := uv.IA5String != "" + isUTF8String := uv.UTF8String != "" + isPrintableString := uv.PrintableString != "" + + switch { + case isBytes && !isIA5String && !isUTF8String && !isPrintableString: + return UniversalValueTypeBytes + case !isBytes && isIA5String && !isUTF8String && !isPrintableString: + return UniversalValueTypeIA5String + case !isBytes && !isIA5String && isUTF8String && !isPrintableString: + return UniversalValueTypeUTF8String + case !isBytes && !isIA5String && !isUTF8String && isPrintableString: + return UniversalValueTypePrintableString + } + + return -1 // Either no field is set or two fields are set. +} + +func MarshalUniversalValue(uv UniversalValue) ([]byte, error) { + switch uvType := uv.Type(); uvType { + case -1: + return nil, errors.New("UniversalValue should have exactly one field set") + case UniversalValueTypeBytes: + return uv.Bytes, nil + case UniversalValueTypeIA5String: + if err := isIA5String(uv.IA5String); err != nil { + return nil, errors.New("asn1: invalid IA5 string") + } + return marshalRawString(asn1.TagIA5String, []byte(uv.IA5String)) + case UniversalValueTypeUTF8String: + if !utf8.ValidString(uv.UTF8String) { + return nil, errors.New("asn1: invalid UTF-8 string") + } + return marshalRawString(asn1.TagUTF8String, []byte(uv.UTF8String)) + case UniversalValueTypePrintableString: + if !isPrintable(uv.PrintableString) { + return nil, errors.New("asn1: invalid PrintableString string") + } + return marshalRawString(asn1.TagPrintableString, []byte(uv.PrintableString)) + default: + return nil, fmt.Errorf("unsupported UniversalValue type: %d", uvType) + } +} + +func marshalRawString(tag int, value []byte) ([]byte, error) { + rawValue := asn1.RawValue{ + Class: asn1.ClassUniversal, + Tag: tag, + IsCompound: false, + Bytes: value, + } + + return asn1.Marshal(rawValue) +} + +func UnmarshalUniversalValue(rawValue asn1.RawValue) (UniversalValue, error) { + var uv UniversalValue + + if rawValue.FullBytes == nil { + fullBytes, err := asn1.Marshal(rawValue) + if err != nil { + return uv, err + } + rawValue.FullBytes = fullBytes + } + + var rest []byte + var err error + switch rawValue.Tag { + case asn1.TagIA5String: + rest, err = asn1.UnmarshalWithParams(rawValue.FullBytes, &uv.IA5String, "ia5") + case asn1.TagUTF8String: + rest, err = asn1.UnmarshalWithParams(rawValue.FullBytes, &uv.UTF8String, "utf8") + case asn1.TagPrintableString: + rest, err = asn1.UnmarshalWithParams(rawValue.FullBytes, &uv.PrintableString, "printable") + default: + uv.Bytes = rawValue.FullBytes + } + if err != nil { + return uv, err + } + if len(rest) != 0 { + return uv, fmt.Errorf("trailing data") + } + + return uv, nil +} + +// Copied from: https://github.com/golang/go/blob/c95fe91d0715dc0a8d55ac80a80f383c3635548b/src/crypto/x509/x509.go#L1093 +func isIA5String(s string) error { + for _, r := range s { + // Per RFC5280 "IA5String is limited to the set of ASCII characters" + if r > unicode.MaxASCII { + return fmt.Errorf("x509: %q cannot be encoded as an IA5String", s) + } + } + + return nil +} + +// isPrintable reports whether the given b is in the ASN.1 PrintableString set. +// '*' and '&' are also allowed, reflecting existing practice. +// Copied from: https://github.com/golang/go/blob/c95fe91d0715dc0a8d55ac80a80f383c3635548b/src/crypto/x509/parser.go#L34 +func isPrintable(s string) bool { + for _, b := range s { + if 'a' <= b && b <= 'z' || + 'A' <= b && b <= 'Z' || + '0' <= b && b <= '9' || + '\'' <= b && b <= ')' || + '+' <= b && b <= '/' || + b == ' ' || + b == ':' || + b == '=' || + b == '?' || + // This is technically not allowed in a PrintableString. + // However, x509 certificates with wildcard strings don't + // always use the correct string type so we permit it. + b == '*' || + // This is not technically allowed either. However, not + // only is it relatively common, but there are also a + // handful of CA certificates that contain it. At least + // one of which will not expire until 2027. + b == '&' { + continue + } + + return false + } + + return true +} diff --git a/pkg/util/pki/asn1_util_test.go b/pkg/util/pki/asn1_util_test.go new file mode 100644 index 00000000000..bc43f0d722c --- /dev/null +++ b/pkg/util/pki/asn1_util_test.go @@ -0,0 +1,268 @@ +/* +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. +*/ + +package pki + +import ( + "encoding/asn1" + "errors" + "reflect" + "testing" +) + +func TestParseObjectIdentifier(t *testing.T) { + testCases := []struct { + oidString string + expectedOid asn1.ObjectIdentifier + expectedErr error + }{ + { + oidString: "1.2.3.4.5", + expectedOid: asn1.ObjectIdentifier{1, 2, 3, 4, 5}, + expectedErr: nil, + }, + { + oidString: "1.2.840.113549.1.1.1", + expectedOid: asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 1}, + expectedErr: nil, + }, + { + oidString: "1.3.6.1.4.1.311.60.2.1.3", + expectedOid: asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 311, 60, 2, 1, 3}, + expectedErr: nil, + }, + { + oidString: ".", + expectedOid: nil, + expectedErr: errors.New("strconv.Atoi: parsing \"\": invalid syntax"), + }, + { + oidString: ".555", + expectedOid: nil, + expectedErr: errors.New("strconv.Atoi: parsing \"\": invalid syntax"), + }, + { + oidString: "555.", + expectedOid: nil, + expectedErr: errors.New("strconv.Atoi: parsing \"\": invalid syntax"), + }, + { + oidString: "test.5", + expectedOid: nil, + expectedErr: errors.New("strconv.Atoi: parsing \"test\": invalid syntax"), + }, + } + + for _, tc := range testCases { + oid, err := ParseObjectIdentifier(tc.oidString) + if err != nil { + if tc.expectedErr == nil { + t.Errorf("Unexpected error: %v", err) + } else if err.Error() != tc.expectedErr.Error() { + t.Errorf("Expected error: %v, got: %v", tc.expectedErr, err) + } + } else if !oid.Equal(tc.expectedOid) { + t.Errorf("Expected OID: %v, got: %v", tc.expectedOid, oid) + } + } +} + +func TestMarshalAndUnmarshalUniversalValue(t *testing.T) { + testCases := []struct { + name string + uv UniversalValue + raw asn1.RawValue + overrideRoundtripUv *UniversalValue + }{ + { + name: "Test with IA5String", + uv: UniversalValue{ + IA5String: "test", + }, + raw: asn1.RawValue{ + Bytes: []byte("test"), + Class: asn1.ClassUniversal, + Tag: asn1.TagIA5String, + }, + }, + { + name: "Test with Utf8String", + uv: UniversalValue{ + UTF8String: "test", + }, + raw: asn1.RawValue{ + Bytes: []byte("test"), + Class: asn1.ClassUniversal, + Tag: asn1.TagUTF8String, + }, + }, + { + name: "Test with PrintableString", + uv: UniversalValue{ + PrintableString: "test", + }, + raw: asn1.RawValue{ + Bytes: []byte("test"), + Class: asn1.ClassUniversal, + Tag: asn1.TagPrintableString, + }, + }, + { + name: "Test with Bytes", + uv: UniversalValue{ + // Ia5String byte array with value "test" + // https://lapo.it/asn1js/#FgR0ZXN0 + Bytes: []byte{0x16, 0x04, 0x74, 0x65, 0x73, 0x74}, + }, + overrideRoundtripUv: &UniversalValue{ + IA5String: "test", + }, + raw: asn1.RawValue{ + Bytes: []byte("test"), + Class: asn1.ClassUniversal, + Tag: asn1.TagIA5String, + }, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + { + rawValue, err := MarshalUniversalValue(tc.uv) + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + + // Calculate fullBytes + fullBytes, err := asn1.Marshal(tc.raw) + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + + if !reflect.DeepEqual(rawValue, fullBytes) { + t.Errorf("Expected rawValue: %v, got: %v", fullBytes, rawValue) + } + } + + { + uv, err := UnmarshalUniversalValue(tc.raw) + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + + targetUv := tc.uv + if tc.overrideRoundtripUv != nil { + targetUv = *tc.overrideRoundtripUv + } + if !reflect.DeepEqual(uv, targetUv) { + t.Errorf("Expected uv: %v, got: %v", targetUv, uv) + } + } + }) + } +} + +// Since we make use of the standard utf.ValidString +// we just do a sanity check to ensure it is used on Marshall/UnMarshal +func TestMarshalUTF8Validation(t *testing.T) { + + uv := UniversalValue{ + // Invalid utf8 byte sequence, string() just casts byte[] verbatim whereas "" causes compile error + UTF8String: string([]byte{0xc3, 0x28}), + } + + _, err := MarshalUniversalValue(uv) + if err == nil { + t.Error("Expected invalid UTF8 string to raise error") + } + + inValidASN1UTF8 := asn1.RawValue{ + Tag: asn1.TagUTF8String, + Class: asn1.ClassUniversal, + Bytes: []byte{0xe2, 0x82, 0x28}, // Another out of range utf8 byte sequence + } + + _, err = UnmarshalUniversalValue(inValidASN1UTF8) + if err == nil { + t.Error("Expected invalid UTF8 asn1 value to raise error") + } +} + +func TestIsIA5String(t *testing.T) { + ia5Strings := []string{ + "test", + "1234", + "!@#$", + " ", + "", + } + + for _, ia5String := range ia5Strings { + err := isIA5String(ia5String) + + if err != nil { + t.Errorf("Expected IA5 string %q, got: %s", ia5String, err.Error()) + } + } + + nonIA5Strings := []string{ + "中文", //nolint: gosmopolitan + } + + for _, nonIA5String := range nonIA5Strings { + err := isIA5String(nonIA5String) + + if err == nil { + t.Errorf("Expected non-IA5 string error for %s, got: nil", nonIA5String) + } + } +} + +func TestIsPrintable(t *testing.T) { + printableStrings := []string{ + "test", + "1234", + "*AA:-)/?", + " ", + "", + "Test*", + "Test&", + } + + for _, printableString := range printableStrings { + isPrintable := isPrintable(printableString) + + if !isPrintable { + t.Errorf("Expected printable string %q, got: %v", printableString, isPrintable) + } + } + + nonPrintableStrings := []string{ + "中文", //nolint: gosmopolitan + "Test!", + "Test@", + "Test#", + "Test%", + } + + for _, nonPrintableString := range nonPrintableStrings { + isPrintable := isPrintable(nonPrintableString) + + if isPrintable { + t.Errorf("Expected non-printable string %q, got: %v", nonPrintableString, isPrintable) + } + } +} diff --git a/pkg/util/pki/basicconstraints.go b/pkg/util/pki/basicconstraints.go new file mode 100644 index 00000000000..916469ebeed --- /dev/null +++ b/pkg/util/pki/basicconstraints.go @@ -0,0 +1,68 @@ +/* +Copyright 2020 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. +*/ + +package pki + +import ( + "crypto/x509/pkix" + "encoding/asn1" + "errors" +) + +// Copied from x509.go +var ( + OIDExtensionBasicConstraints = []int{2, 5, 29, 19} +) + +// Copied from x509.go +type basicConstraints struct { + IsCA bool `asn1:"optional"` + MaxPathLen int `asn1:"optional,default:-1"` +} + +// Adapted from x509.go +func MarshalBasicConstraints(isCA bool, maxPathLen *int) (pkix.Extension, error) { + ext := pkix.Extension{Id: OIDExtensionBasicConstraints, Critical: true} + + // A value of -1 causes encoding/asn1 to omit the value as desired. + maxPathLenValue := -1 + if maxPathLen != nil { + maxPathLenValue = *maxPathLen + } + + var err error + ext.Value, err = asn1.Marshal(basicConstraints{isCA, maxPathLenValue}) + return ext, err +} + +// Adapted from x509.go +func UnmarshalBasicConstraints(value []byte) (isCA bool, maxPathLen *int, err error) { + var constraints basicConstraints + var rest []byte + + if rest, err = asn1.Unmarshal(value, &constraints); err != nil { + return isCA, maxPathLen, err + } else if len(rest) != 0 { + return isCA, maxPathLen, errors.New("x509: trailing data after X.509 BasicConstraints") + } + + isCA = constraints.IsCA + if constraints.MaxPathLen >= 0 { + maxPathLen = new(int) + *maxPathLen = constraints.MaxPathLen + } + return isCA, maxPathLen, nil +} diff --git a/pkg/util/pki/certificatetemplate.go b/pkg/util/pki/certificatetemplate.go new file mode 100644 index 00000000000..957cdcfdc36 --- /dev/null +++ b/pkg/util/pki/certificatetemplate.go @@ -0,0 +1,345 @@ +/* +Copyright 2020 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. +*/ + +package pki + +import ( + "crypto/x509" + "crypto/x509/pkix" + "encoding/asn1" + "fmt" + "slices" + "strings" + "time" + + certificatesv1 "k8s.io/api/certificates/v1" + + apiutil "github.com/cert-manager/cert-manager/pkg/api/util" + v1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" + experimentalapi "github.com/cert-manager/cert-manager/pkg/apis/experimental/v1alpha1" +) + +type CertificateTemplateValidatorMutator func(*x509.CertificateRequest, *x509.Certificate) error + +func hasExtension(checkReq *x509.CertificateRequest, extensionID asn1.ObjectIdentifier) bool { + for _, ext := range checkReq.Extensions { + if ext.Id.Equal(extensionID) { + return true + } + } + + for _, ext := range checkReq.ExtraExtensions { + if ext.Id.Equal(extensionID) { + return true + } + } + + return false +} + +// CertificateTemplateOverrideDuration returns a CertificateTemplateValidatorMutator that overrides the +// certificate duration. +func CertificateTemplateOverrideDuration(duration time.Duration) CertificateTemplateValidatorMutator { + return func(req *x509.CertificateRequest, cert *x509.Certificate) error { + cert.NotBefore = time.Now() + cert.NotAfter = cert.NotBefore.Add(duration) + return nil + } +} + +// CertificateTemplateValidateAndOverrideBasicConstraints returns a CertificateTemplateValidatorMutator that overrides +// the certificate basic constraints. +func CertificateTemplateValidateAndOverrideBasicConstraints(isCA bool, maxPathLen *int) CertificateTemplateValidatorMutator { + return func(req *x509.CertificateRequest, cert *x509.Certificate) error { + if hasExtension(req, OIDExtensionBasicConstraints) { + if !cert.BasicConstraintsValid { + return fmt.Errorf("encoded CSR error: BasicConstraintsValid is not true") + } + + if cert.IsCA != isCA { + return fmt.Errorf("encoded CSR error: IsCA %v does not match expected value %v", cert.IsCA, isCA) + } + + // We explicitly do not check the MaxPathLen and MaxPathLenZero fields here, as there is no way to + // configure these fields in a CertificateRequest or CSR object yet. If we ever add a way to configure + // these fields, we should add a check here to ensure that the values match the expected values. + // The provided maxPathLen is only used to override the value, not to validate it. + // TODO: if we add support for maxPathLen, we should add a check here to ensure that the value in the + // CertificateRequest or CSR matches the value encoded in the CSR blob. + } + + cert.BasicConstraintsValid = true + cert.IsCA = isCA + if maxPathLen != nil { + cert.MaxPathLen = *maxPathLen + cert.MaxPathLenZero = *maxPathLen == 0 + } else { + cert.MaxPathLen = 0 + cert.MaxPathLenZero = false + } + return nil + } +} + +// CertificateTemplateValidateAndOverrideKeyUsages returns a CertificateTemplateValidatorMutator that overrides the +// certificate key usages. +func CertificateTemplateValidateAndOverrideKeyUsages(keyUsage x509.KeyUsage, extKeyUsage []x509.ExtKeyUsage) CertificateTemplateValidatorMutator { + return func(req *x509.CertificateRequest, cert *x509.Certificate) error { + if hasExtension(req, OIDExtensionKeyUsage) || hasExtension(req, OIDExtensionExtendedKeyUsage) { + if cert.KeyUsage != keyUsage { + return fmt.Errorf("encoded CSR error: the KeyUsages %s do not match the expected KeyUsages %s", + printKeyUsage(apiutil.KeyUsageStrings(cert.KeyUsage)), + printKeyUsage(apiutil.KeyUsageStrings(keyUsage)), + ) + } + + if !slices.Equal(cert.ExtKeyUsage, extKeyUsage) { + return fmt.Errorf("encoded CSR error: the ExtKeyUsages %s do not match the expected ExtKeyUsages %s", + printKeyUsage(apiutil.ExtKeyUsageStrings(cert.ExtKeyUsage)), + printKeyUsage(apiutil.ExtKeyUsageStrings(extKeyUsage)), + ) + } + } + + cert.KeyUsage = keyUsage + cert.ExtKeyUsage = extKeyUsage + return nil + } +} + +type printKeyUsage []v1.KeyUsage + +func (k printKeyUsage) String() string { + var sb strings.Builder + sb.WriteString("[") + for i, u := range k { + sb.WriteString(" '") + sb.WriteString(string(u)) + sb.WriteString("'") + if i < len(k)-1 { + sb.WriteString(",") + } + } + if len(k) > 0 { + sb.WriteString(" ") + } + sb.WriteString("]") + return sb.String() +} + +// CertificateTemplateFromCSR will create a x509.Certificate for the +// given *x509.CertificateRequest. +func CertificateTemplateFromCSR(csr *x509.CertificateRequest, validatorMutators ...CertificateTemplateValidatorMutator) (*x509.Certificate, error) { + cert := &x509.Certificate{ + PublicKeyAlgorithm: csr.PublicKeyAlgorithm, + PublicKey: csr.PublicKey, + Subject: csr.Subject, + RawSubject: csr.RawSubject, + + DNSNames: csr.DNSNames, + IPAddresses: csr.IPAddresses, + EmailAddresses: csr.EmailAddresses, + URIs: csr.URIs, + } + + // Start by copying all extensions from the CSR + extractExtensions := func(template *x509.Certificate, val pkix.Extension) error { + // Check the CSR for the X.509 BasicConstraints (RFC 5280, 4.2.1.9) + // extension and append to template if necessary + if val.Id.Equal(OIDExtensionBasicConstraints) { + unmarshalIsCA, unmarshalMaxPathLen, err := UnmarshalBasicConstraints(val.Value) + if err != nil { + return err + } + + template.BasicConstraintsValid = true + template.IsCA = unmarshalIsCA + if unmarshalMaxPathLen != nil { + template.MaxPathLen = *unmarshalMaxPathLen + template.MaxPathLenZero = *unmarshalMaxPathLen == 0 + } else { + template.MaxPathLen = 0 + template.MaxPathLenZero = false + } + } + + if val.Id.Equal(OIDExtensionNameConstraints) { + nameConstraints, err := UnmarshalNameConstraints(val.Value) + if err != nil { + return err + } + template.PermittedDNSDomainsCritical = val.Critical + template.PermittedDNSDomains = nameConstraints.PermittedDNSDomains + template.PermittedIPRanges = nameConstraints.PermittedIPRanges + template.PermittedEmailAddresses = nameConstraints.PermittedEmailAddresses + template.PermittedURIDomains = nameConstraints.PermittedURIDomains + template.ExcludedDNSDomains = nameConstraints.ExcludedDNSDomains + template.ExcludedIPRanges = nameConstraints.ExcludedIPRanges + template.ExcludedEmailAddresses = nameConstraints.ExcludedEmailAddresses + template.ExcludedURIDomains = nameConstraints.ExcludedURIDomains + } + + // RFC 5280, 4.2.1.3 + if val.Id.Equal(OIDExtensionKeyUsage) { + usage, err := UnmarshalKeyUsage(val.Value) + if err != nil { + return err + } + + template.KeyUsage = usage + } + + if val.Id.Equal(OIDExtensionExtendedKeyUsage) { + extUsages, unknownUsages, err := UnmarshalExtKeyUsage(val.Value) + if err != nil { + return err + } + + template.ExtKeyUsage = extUsages + template.UnknownExtKeyUsage = unknownUsages + } + + // The SANs fields in the Certificate resource are not enough to + // represent the full set of SANs that can be encoded in a CSR. + // Therefore, we need to copy the SANs from the CSR into the + // ExtraExtensions field of the certificate template. + if val.Id.Equal(oidExtensionSubjectAltName) { + template.ExtraExtensions = append(template.ExtraExtensions, val) + } + + return nil + } + + for _, val := range csr.Extensions { + if err := extractExtensions(cert, val); err != nil { + return nil, err + } + } + + for _, val := range csr.ExtraExtensions { + if err := extractExtensions(cert, val); err != nil { + return nil, err + } + } + + cert.Extensions = csr.Extensions + + for _, validatorMutator := range validatorMutators { + if err := validatorMutator(csr, cert); err != nil { + return nil, err + } + } + + // Finally, we fix up the certificate template to ensure that it is valid + { + // If the certificate has an empty Subject, we set any SAN extensions to be critical + var asn1Subject []byte + var err error + if cert.RawSubject != nil { + asn1Subject = cert.RawSubject + } else { + asn1Subject, err = asn1.Marshal(cert.Subject.ToRDNSequence()) + if err != nil { + return nil, fmt.Errorf("failed to marshal subject to ASN.1 DER: %s", err.Error()) + } + } + + for i := range cert.ExtraExtensions { + if cert.ExtraExtensions[i].Id.Equal(oidExtensionSubjectAltName) { + cert.ExtraExtensions[i].Critical = IsASN1SubjectEmpty(asn1Subject) + } + } + } + + return cert, nil +} + +// CertificateTemplateFromCSRPEM will create a x509.Certificate for the +// given csrPEM. +func CertificateTemplateFromCSRPEM(csrPEM []byte, validatorMutators ...CertificateTemplateValidatorMutator) (*x509.Certificate, error) { + csr, err := DecodeX509CertificateRequestBytes(csrPEM) + if err != nil { + return nil, err + } + + if err := csr.CheckSignature(); err != nil { + return nil, err + } + + return CertificateTemplateFromCSR(csr, validatorMutators...) +} + +// CertificateTemplateFromCertificate will create a x509.Certificate for the given +// Certificate resource +func CertificateTemplateFromCertificate(crt *v1.Certificate) (*x509.Certificate, error) { + csr, err := GenerateCSR(crt) + if err != nil { + return nil, err + } + + certDuration := apiutil.DefaultCertDuration(crt.Spec.Duration) + keyUsage, extKeyUsage, err := KeyUsagesForCertificateOrCertificateRequest(crt.Spec.Usages, crt.Spec.IsCA) + if err != nil { + return nil, err + } + + return CertificateTemplateFromCSR( + csr, + CertificateTemplateOverrideDuration(certDuration), + CertificateTemplateValidateAndOverrideBasicConstraints(crt.Spec.IsCA, nil), + CertificateTemplateValidateAndOverrideKeyUsages(keyUsage, extKeyUsage), + ) +} + +// CertificateTemplateFromCertificateRequest will create a x509.Certificate for the given +// CertificateRequest resource +func CertificateTemplateFromCertificateRequest(cr *v1.CertificateRequest) (*x509.Certificate, error) { + certDuration := apiutil.DefaultCertDuration(cr.Spec.Duration) + keyUsage, extKeyUsage, err := KeyUsagesForCertificateOrCertificateRequest(cr.Spec.Usages, cr.Spec.IsCA) + if err != nil { + return nil, err + } + + return CertificateTemplateFromCSRPEM( + cr.Spec.Request, + CertificateTemplateOverrideDuration(certDuration), + CertificateTemplateValidateAndOverrideBasicConstraints(cr.Spec.IsCA, nil), // Override the basic constraints, but make sure they match the constraints in the CSR if present + CertificateTemplateValidateAndOverrideKeyUsages(keyUsage, extKeyUsage), // Override the key usages, but make sure they match the usages in the CSR if present + ) +} + +// CertificateTemplateFromCertificateSigningRequest will create a x509.Certificate for the given +// CertificateSigningRequest resource +func CertificateTemplateFromCertificateSigningRequest(csr *certificatesv1.CertificateSigningRequest) (*x509.Certificate, error) { + duration, err := DurationFromCertificateSigningRequest(csr) + if err != nil { + return nil, err + } + + ku, eku, err := BuildKeyUsagesKube(csr.Spec.Usages) + if err != nil { + return nil, err + } + + isCA := csr.Annotations[experimentalapi.CertificateSigningRequestIsCAAnnotationKey] == "true" + + return CertificateTemplateFromCSRPEM( + csr.Spec.Request, + CertificateTemplateOverrideDuration(duration), + CertificateTemplateValidateAndOverrideBasicConstraints(isCA, nil), // Override the basic constraints, but make sure they match the constraints in the CSR if present + CertificateTemplateValidateAndOverrideKeyUsages(ku, eku), // Override the key usages, but make sure they match the usages in the CSR if present + ) +} diff --git a/pkg/util/pki/certificatetemplate_test.go b/pkg/util/pki/certificatetemplate_test.go new file mode 100644 index 00000000000..492954f718a --- /dev/null +++ b/pkg/util/pki/certificatetemplate_test.go @@ -0,0 +1,160 @@ +/* +Copyright 2020 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. +*/ + +package pki + +import ( + "crypto/x509" + "crypto/x509/pkix" + "encoding/asn1" + "reflect" + "testing" +) + +func TestCertificateTemplateFromCSR(t *testing.T) { + subjectGenerator := func(t *testing.T, name pkix.Name) []byte { + data, err := MarshalRDNSequenceToRawDERBytes(name.ToRDNSequence()) + if err != nil { + t.Fatal(err) + } + return data + } + + sansGenerator := func(t *testing.T, generalNames []asn1.RawValue, critical bool) pkix.Extension { + val, err := asn1.Marshal(generalNames) + if err != nil { + t.Fatal(err) + } + + return pkix.Extension{ + Id: oidExtensionSubjectAltName, + Critical: critical, + Value: val, + } + } + + testCases := []struct { + name string + csr *x509.CertificateRequest + expected *x509.Certificate + }{ + { + name: "should copy subject", + csr: &x509.CertificateRequest{ + Subject: pkix.Name{ + Country: []string{"US"}, + Organization: []string{"cert-manager"}, + OrganizationalUnit: []string{"test"}, + CommonName: "test", + }, + }, + expected: &x509.Certificate{ + Subject: pkix.Name{ + Country: []string{"US"}, + Organization: []string{"cert-manager"}, + OrganizationalUnit: []string{"test"}, + CommonName: "test", + }, + }, + }, + { + name: "should copy raw subject + SANs", + csr: &x509.CertificateRequest{ + RawSubject: subjectGenerator(t, pkix.Name{ + Country: []string{"US"}, + Organization: []string{"cert-manager"}, + OrganizationalUnit: []string{"test"}, + }), + DNSNames: []string{"test.example.com"}, + }, + expected: &x509.Certificate{ + RawSubject: subjectGenerator(t, pkix.Name{ + Country: []string{"US"}, + Organization: []string{"cert-manager"}, + OrganizationalUnit: []string{"test"}, + }), + DNSNames: []string{"test.example.com"}, + }, + }, + { + name: "should ignore unknown extensions", + csr: &x509.CertificateRequest{ + ExtraExtensions: []pkix.Extension{ + { + Id: []int{1, 2, 3}, + Value: []byte("test"), + }, + }, + }, + expected: &x509.Certificate{}, + }, + { + name: "should copy SANs and not fix critical flag subject is set", + csr: &x509.CertificateRequest{ + Subject: pkix.Name{ + Country: []string{"US"}, + Organization: []string{"cert-manager"}, + }, + ExtraExtensions: []pkix.Extension{ + sansGenerator(t, []asn1.RawValue{ + {Tag: 2, Class: 2, Bytes: []byte("test.example.com")}, + }, false), + }, + }, + expected: &x509.Certificate{ + Subject: pkix.Name{ + Country: []string{"US"}, + Organization: []string{"cert-manager"}, + }, + ExtraExtensions: []pkix.Extension{ + sansGenerator(t, []asn1.RawValue{ + {Tag: 2, Class: 2, Bytes: []byte("test.example.com")}, + }, false), + }, + }, + }, + { + name: "should copy SANs and fix its critical flag", + csr: &x509.CertificateRequest{ + ExtraExtensions: []pkix.Extension{ + sansGenerator(t, []asn1.RawValue{ + {Tag: 2, Class: 2, Bytes: []byte("test.example.com")}, + }, false), + }, + }, + expected: &x509.Certificate{ + ExtraExtensions: []pkix.Extension{ + sansGenerator(t, []asn1.RawValue{ + {Tag: 2, Class: 2, Bytes: []byte("test.example.com")}, + }, true), + }, + }, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + result, err := CertificateTemplateFromCSR(tc.csr) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + + if !reflect.DeepEqual(result, tc.expected) { + t.Errorf("unexpected result: %v", result) + } + }) + } +} diff --git a/pkg/util/pki/csr.go b/pkg/util/pki/csr.go index 2afe88e766d..eb24ac9c86f 100644 --- a/pkg/util/pki/csr.go +++ b/pkg/util/pki/csr.go @@ -19,76 +19,27 @@ package pki import ( "bytes" "crypto" + "crypto/ecdsa" + "crypto/ed25519" + "crypto/elliptic" "crypto/rand" + "crypto/rsa" "crypto/x509" "crypto/x509/pkix" "encoding/asn1" "encoding/pem" "errors" "fmt" - "math/big" "net" + "net/netip" "net/url" - "strings" - "time" - "github.com/cert-manager/cert-manager/internal/controller/feature" apiutil "github.com/cert-manager/cert-manager/pkg/api/util" v1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" - utilfeature "github.com/cert-manager/cert-manager/pkg/util/feature" ) -func IPAddressesForCertificate(crt *v1.Certificate) []net.IP { - var ipAddresses []net.IP - var ip net.IP - for _, ipName := range crt.Spec.IPAddresses { - ip = net.ParseIP(ipName) - if ip != nil { - ipAddresses = append(ipAddresses, ip) - } - } - return ipAddresses -} - -func URIsForCertificate(crt *v1.Certificate) ([]*url.URL, error) { - uris, err := URLsFromStrings(crt.Spec.URIs) - if err != nil { - return nil, fmt.Errorf("failed to parse URIs: %s", err) - } - - return uris, nil -} - -func DNSNamesForCertificate(crt *v1.Certificate) ([]string, error) { - _, err := URLsFromStrings(crt.Spec.DNSNames) - if err != nil { - return nil, fmt.Errorf("failed to parse DNSNames: %s", err) - } - - return crt.Spec.DNSNames, nil -} - -func URLsFromStrings(urlStrs []string) ([]*url.URL, error) { - var urls []*url.URL - var errs []string - - for _, urlStr := range urlStrs { - url, err := url.Parse(urlStr) - if err != nil { - errs = append(errs, err.Error()) - continue - } - - urls = append(urls, url) - } - - if len(errs) > 0 { - return nil, errors.New(strings.Join(errs, ", ")) - } - - return urls, nil -} - +// IPAddressesToString converts a slice of IP addresses to strings, which can be useful for +// printing a list of addresses but MUST NOT be used for comparing two slices of IP addresses. func IPAddressesToString(ipAddresses []net.IP) []string { var ipNames []string for _, ip := range ipAddresses { @@ -97,6 +48,22 @@ func IPAddressesToString(ipAddresses []net.IP) []string { return ipNames } +func IPAddressesFromStrings(ipStrings []string) ([]net.IP, error) { + var ipAddresses []net.IP + for _, ipString := range ipStrings { + ip, err := netip.ParseAddr(ipString) + if err != nil || ip.Zone() != "" { + return nil, err + } + addr := ip.AsSlice() + if len(addr) == 0 { + return nil, fmt.Errorf("failed to parse IP address %q", ipString) + } + ipAddresses = append(ipAddresses, net.IP(addr)) + } + return ipAddresses, nil +} + func URLsToString(uris []*url.URL) []string { var uriStrs []string for _, uri := range uris { @@ -110,30 +77,6 @@ func URLsToString(uris []*url.URL) []string { return uriStrs } -func removeDuplicates(in []string) []string { - var found []string -Outer: - for _, i := range in { - for _, i2 := range found { - if i2 == i { - continue Outer - } - } - found = append(found, i) - } - return found -} - -// OrganizationForCertificate will return the Organization to set for the -// Certificate resource. -// If an Organization is not specifically set, a default will be used. -func OrganizationForCertificate(crt *v1.Certificate) []string { - if crt.Spec.Subject == nil { - return nil - } - return crt.Spec.Subject.Organizations -} - // SubjectForCertificate will return the Subject from the Certificate resource or an empty one if it is not set func SubjectForCertificate(crt *v1.Certificate) v1.X509Subject { if crt.Spec.Subject == nil { @@ -143,16 +86,18 @@ func SubjectForCertificate(crt *v1.Certificate) v1.X509Subject { return *crt.Spec.Subject } -var serialNumberLimit = new(big.Int).Lsh(big.NewInt(1), 128) - -func BuildKeyUsages(usages []v1.KeyUsage, isCA bool) (ku x509.KeyUsage, eku []x509.ExtKeyUsage, err error) { +func KeyUsagesForCertificateOrCertificateRequest(usages []v1.KeyUsage, isCA bool) (ku x509.KeyUsage, eku []x509.ExtKeyUsage, err error) { var unk []v1.KeyUsage if isCA { ku |= x509.KeyUsageCertSign } + + // If no usages are specified, default to the ones specified in the + // Kubernetes API. if len(usages) == 0 { - usages = append(usages, v1.DefaultKeyUsages()...) + usages = v1.DefaultKeyUsages() } + for _, u := range usages { if kuse, ok := apiutil.KeyUsageType(u); ok { ku |= kuse @@ -168,300 +113,227 @@ func BuildKeyUsages(usages []v1.KeyUsage, isCA bool) (ku x509.KeyUsage, eku []x5 return } -func BuildCertManagerKeyUsages(ku x509.KeyUsage, eku []x509.ExtKeyUsage) []v1.KeyUsage { - usages := apiutil.KeyUsageStrings(ku) - usages = append(usages, apiutil.ExtKeyUsageStrings(eku)...) - - return usages +type generateCSROptions struct { + EncodeBasicConstraintsInRequest bool + EncodeNameConstraints bool + EncodeOtherNames bool + UseLiteralSubject bool } -// GenerateCSR will generate a new *x509.CertificateRequest template to be used -// by issuers that utilise CSRs to obtain Certificates. -// The CSR will not be signed, and should be passed to either EncodeCSR or -// to the x509.CreateCertificateRequest function. -func GenerateCSR(crt *v1.Certificate) (*x509.CertificateRequest, error) { - commonName, err := extractCommonName(crt.Spec) - if err != nil { - return nil, err - } - - iPAddresses := IPAddressesForCertificate(crt) - organization := OrganizationForCertificate(crt) - subject := SubjectForCertificate(crt) +type GenerateCSROption func(*generateCSROptions) - dnsNames, err := DNSNamesForCertificate(crt) - if err != nil { - return nil, err +// WithEncodeBasicConstraintsInRequest determines whether the BasicConstraints +// extension should be encoded in the CSR. +// NOTE: this is a temporary option that will be removed in a future release. +func WithEncodeBasicConstraintsInRequest(encode bool) GenerateCSROption { + return func(o *generateCSROptions) { + o.EncodeBasicConstraintsInRequest = encode } +} - uriNames, err := URIsForCertificate(crt) - if err != nil { - return nil, err +func WithNameConstraints(enabled bool) GenerateCSROption { + return func(o *generateCSROptions) { + o.EncodeNameConstraints = enabled } +} - if len(commonName) == 0 && len(dnsNames) == 0 && len(uriNames) == 0 && len(crt.Spec.EmailAddresses) == 0 && len(crt.Spec.IPAddresses) == 0 { - return nil, fmt.Errorf("no common name, DNS name, URI SAN, or Email SAN specified on certificate") - } - - pubKeyAlgo, sigAlgo, err := SignatureAlgorithm(crt) - if err != nil { - return nil, err +func WithOtherNames(enabled bool) GenerateCSROption { + return func(o *generateCSROptions) { + o.EncodeOtherNames = enabled } +} - var extraExtensions []pkix.Extension - if crt.Spec.EncodeUsagesInRequest == nil || *crt.Spec.EncodeUsagesInRequest { - extraExtensions, err = buildKeyUsagesExtensionsForCertificate(crt) - if err != nil { - return nil, err - } +func WithUseLiteralSubject(useLiteralSubject bool) GenerateCSROption { + return func(o *generateCSROptions) { + o.UseLiteralSubject = useLiteralSubject } +} - if isLiteralCertificateSubjectEnabled() && len(crt.Spec.LiteralSubject) > 0 { - rawSubject, err := ParseSubjectStringToRawDerBytes(crt.Spec.LiteralSubject) +// GenerateCSR will generate a new *x509.CertificateRequest template to be used +// by issuers that utilise CSRs to obtain Certificates. +// The CSR will not be signed, and should be passed to either EncodeCSR or +// to the x509.CreateCertificateRequest function. +func GenerateCSR(crt *v1.Certificate, optFuncs ...GenerateCSROption) (*x509.CertificateRequest, error) { + opts := &generateCSROptions{ + EncodeBasicConstraintsInRequest: false, + EncodeNameConstraints: false, + EncodeOtherNames: false, + UseLiteralSubject: false, + } + for _, opt := range optFuncs { + opt(opts) + } + + // Generate the Subject field for the CSR. + var commonName string + var rdnSubject pkix.RDNSequence + if opts.UseLiteralSubject && len(crt.Spec.LiteralSubject) > 0 { + subjectRDNSequence, err := UnmarshalSubjectStringToRDNSequence(crt.Spec.LiteralSubject) if err != nil { return nil, err } - return &x509.CertificateRequest{ - // Version 0 is the only one defined in the PKCS#10 standard, RFC2986. - // This value isn't used by Go at the time of writing. - // https://datatracker.ietf.org/doc/html/rfc2986#section-4 - Version: 0, - SignatureAlgorithm: sigAlgo, - PublicKeyAlgorithm: pubKeyAlgo, - RawSubject: rawSubject, - DNSNames: dnsNames, - IPAddresses: iPAddresses, - URIs: uriNames, - EmailAddresses: crt.Spec.EmailAddresses, - ExtraExtensions: extraExtensions, - }, nil + commonName = ExtractCommonNameFromRDNSequence(subjectRDNSequence) + rdnSubject = subjectRDNSequence } else { - return &x509.CertificateRequest{ - // Version 0 is the only one defined in the PKCS#10 standard, RFC2986. - // This value isn't used by Go at the time of writing. - // https://datatracker.ietf.org/doc/html/rfc2986#section-4 - Version: 0, - SignatureAlgorithm: sigAlgo, - PublicKeyAlgorithm: pubKeyAlgo, - - Subject: pkix.Name{ - Country: subject.Countries, - Organization: organization, - OrganizationalUnit: subject.OrganizationalUnits, - Locality: subject.Localities, - Province: subject.Provinces, - StreetAddress: subject.StreetAddresses, - PostalCode: subject.PostalCodes, - SerialNumber: subject.SerialNumber, - CommonName: commonName, - }, - DNSNames: dnsNames, - IPAddresses: iPAddresses, - URIs: uriNames, - EmailAddresses: crt.Spec.EmailAddresses, - ExtraExtensions: extraExtensions, - }, nil - } - -} - -func buildKeyUsagesExtensionsForCertificate(crt *v1.Certificate) ([]pkix.Extension, error) { - ku, ekus, err := BuildKeyUsages(crt.Spec.Usages, crt.Spec.IsCA) + subject := SubjectForCertificate(crt) + + commonName = crt.Spec.CommonName + rdnSubject = pkix.Name{ + Country: subject.Countries, + Organization: subject.Organizations, + OrganizationalUnit: subject.OrganizationalUnits, + Locality: subject.Localities, + Province: subject.Provinces, + StreetAddress: subject.StreetAddresses, + PostalCode: subject.PostalCodes, + SerialNumber: subject.SerialNumber, + CommonName: commonName, + }.ToRDNSequence() + } + + // Generate the SANs for the CSR. + ipAddresses, err := IPAddressesFromStrings(crt.Spec.IPAddresses) if err != nil { - return nil, fmt.Errorf("failed to build key usages: %w", err) + return nil, err } - usage, err := buildASN1KeyUsageRequest(ku) - if err != nil { - return nil, fmt.Errorf("failed to asn1 encode usages: %w", err) - } - asn1ExtendedUsages := []asn1.ObjectIdentifier{} - for _, eku := range ekus { - if oid, ok := OIDFromExtKeyUsage(eku); ok { - asn1ExtendedUsages = append(asn1ExtendedUsages, oid) - } + sans := GeneralNames{ + RFC822Names: crt.Spec.EmailAddresses, + DNSNames: crt.Spec.DNSNames, + UniformResourceIdentifiers: crt.Spec.URIs, + IPAddresses: ipAddresses, } - extraExtensions := []pkix.Extension{usage} - if len(ekus) > 0 { - extendedUsage := pkix.Extension{ - Id: OIDExtensionExtendedKeyUsage, - } - extendedUsage.Value, err = asn1.Marshal(asn1ExtendedUsages) - if err != nil { - return nil, fmt.Errorf("failed to asn1 encode extended usages: %w", err) - } + if opts.EncodeOtherNames { + for _, otherName := range crt.Spec.OtherNames { + oid, err := ParseObjectIdentifier(otherName.OID) + if err != nil { + return nil, err + } + + value, err := MarshalUniversalValue(UniversalValue{ + UTF8String: otherName.UTF8Value, + }) + if err != nil { + return nil, err + } - extraExtensions = append(extraExtensions, extendedUsage) + sans.OtherNames = append(sans.OtherNames, OtherName{ + TypeID: oid, + Value: asn1.RawValue{ + Tag: 0, + Class: asn1.ClassContextSpecific, + IsCompound: true, + Bytes: value, + }, + }) + } } - return extraExtensions, nil -} -// GenerateTemplate will create a x509.Certificate for the given Certificate resource. -// This should create a Certificate template that is equivalent to the CertificateRequest -// generated by GenerateCSR. -// The PublicKey field must be populated by the caller. -func GenerateTemplate(crt *v1.Certificate) (*x509.Certificate, error) { - commonName, err := extractCommonName(crt.Spec) - if err != nil { - return nil, err + if len(commonName) == 0 && sans.Empty() { + return nil, fmt.Errorf("no common name (from the commonName field or from a literalSubject), DNS name, URI SAN, Email SAN, IP or OtherName SAN specified on certificate") } - dnsNames := crt.Spec.DNSNames - ipAddresses := IPAddressesForCertificate(crt) - organization := OrganizationForCertificate(crt) - subject := SubjectForCertificate(crt) - uris, err := URLsFromStrings(crt.Spec.URIs) + pubKeyAlgo, sigAlgo, err := SignatureAlgorithm(crt) if err != nil { return nil, err } - keyUsages, extKeyUsages, err := BuildKeyUsages(crt.Spec.Usages, crt.Spec.IsCA) + + asn1Subject, err := MarshalRDNSequenceToRawDERBytes(rdnSubject) if err != nil { return nil, err } - if len(commonName) == 0 && len(dnsNames) == 0 && len(ipAddresses) == 0 && len(uris) == 0 && len(crt.Spec.EmailAddresses) == 0 { - return nil, fmt.Errorf("no common name or subject alt names requested on certificate") - } + var extraExtensions []pkix.Extension - serialNumber, err := rand.Int(rand.Reader, serialNumberLimit) - if err != nil { - return nil, fmt.Errorf("failed to generate serial number: %s", err.Error()) + if !sans.Empty() { + sanExtension, err := MarshalSANs(sans, !IsASN1SubjectEmpty(asn1Subject)) + if err != nil { + return nil, err + } + extraExtensions = append(extraExtensions, sanExtension) } - certDuration := apiutil.DefaultCertDuration(crt.Spec.Duration) + if crt.Spec.EncodeUsagesInRequest == nil || *crt.Spec.EncodeUsagesInRequest { + ku, ekus, err := KeyUsagesForCertificateOrCertificateRequest(crt.Spec.Usages, crt.Spec.IsCA) + if err != nil { + return nil, fmt.Errorf("failed to build key usages: %w", err) + } - pubKeyAlgo, _, err := SignatureAlgorithm(crt) - if err != nil { - return nil, err + if ku != 0 { + usage, err := MarshalKeyUsage(ku) + if err != nil { + return nil, fmt.Errorf("failed to asn1 encode usages: %w", err) + } + extraExtensions = append(extraExtensions, usage) + } + + // Only add extended usages if they are specified. + if len(ekus) > 0 { + extendedUsages, err := MarshalExtKeyUsage(ekus, nil) + if err != nil { + return nil, fmt.Errorf("failed to asn1 encode extended usages: %w", err) + } + extraExtensions = append(extraExtensions, extendedUsages) + } } - if isLiteralCertificateSubjectEnabled() && len(crt.Spec.LiteralSubject) > 0 { - rawSubject, err := ParseSubjectStringToRawDerBytes(crt.Spec.LiteralSubject) + // NOTE(@inteon): opts.EncodeBasicConstraintsInRequest is a temporary solution and will + // be removed/ replaced in a future release. + if opts.EncodeBasicConstraintsInRequest { + basicExtension, err := MarshalBasicConstraints(crt.Spec.IsCA, nil) if err != nil { return nil, err } - - return &x509.Certificate{ - // Version must be 2 according to RFC5280. - // A version value of 2 confusingly means version 3. - // This value isn't used by Go at the time of writing. - // https://datatracker.ietf.org/doc/html/rfc5280#section-4.1.2.1 - Version: 2, - BasicConstraintsValid: true, - SerialNumber: serialNumber, - PublicKeyAlgorithm: pubKeyAlgo, - IsCA: crt.Spec.IsCA, - RawSubject: rawSubject, - NotBefore: time.Now(), - NotAfter: time.Now().Add(certDuration), - // see http://golang.org/pkg/crypto/x509/#KeyUsage - KeyUsage: keyUsages, - ExtKeyUsage: extKeyUsages, - DNSNames: dnsNames, - IPAddresses: ipAddresses, - URIs: uris, - EmailAddresses: crt.Spec.EmailAddresses, - }, nil - } else { - - return &x509.Certificate{ - // Version must be 2 according to RFC5280. - // A version value of 2 confusingly means version 3. - // This value isn't used by Go at the time of writing. - // https://datatracker.ietf.org/doc/html/rfc5280#section-4.1.2.1 - Version: 2, - BasicConstraintsValid: true, - SerialNumber: serialNumber, - PublicKeyAlgorithm: pubKeyAlgo, - IsCA: crt.Spec.IsCA, - Subject: pkix.Name{ - Country: subject.Countries, - Organization: organization, - OrganizationalUnit: subject.OrganizationalUnits, - Locality: subject.Localities, - Province: subject.Provinces, - StreetAddress: subject.StreetAddresses, - PostalCode: subject.PostalCodes, - SerialNumber: subject.SerialNumber, - CommonName: commonName, - }, - NotBefore: time.Now(), - NotAfter: time.Now().Add(certDuration), - // see http://golang.org/pkg/crypto/x509/#KeyUsage - KeyUsage: keyUsages, - ExtKeyUsage: extKeyUsages, - DNSNames: dnsNames, - IPAddresses: ipAddresses, - URIs: uris, - EmailAddresses: crt.Spec.EmailAddresses, - }, nil + extraExtensions = append(extraExtensions, basicExtension) } -} -// GenerateTemplate will create a x509.Certificate for the given -// CertificateRequest resource -func GenerateTemplateFromCertificateRequest(cr *v1.CertificateRequest) (*x509.Certificate, error) { - certDuration := apiutil.DefaultCertDuration(cr.Spec.Duration) - keyUsage, extKeyUsage, err := BuildKeyUsages(cr.Spec.Usages, cr.Spec.IsCA) - if err != nil { - return nil, err - } - return GenerateTemplateFromCSRPEMWithUsages(cr.Spec.Request, certDuration, cr.Spec.IsCA, keyUsage, extKeyUsage) -} + if opts.EncodeNameConstraints && crt.Spec.NameConstraints != nil { + nameConstraints := &NameConstraints{} -func GenerateTemplateFromCSRPEM(csrPEM []byte, duration time.Duration, isCA bool) (*x509.Certificate, error) { - var ( - ku x509.KeyUsage - eku []x509.ExtKeyUsage - ) - return GenerateTemplateFromCSRPEMWithUsages(csrPEM, duration, isCA, ku, eku) -} + if crt.Spec.NameConstraints.Permitted != nil { + nameConstraints.PermittedDNSDomains = crt.Spec.NameConstraints.Permitted.DNSDomains + nameConstraints.PermittedIPRanges, err = parseCIDRs(crt.Spec.NameConstraints.Permitted.IPRanges) + if err != nil { + return nil, err + } + nameConstraints.PermittedEmailAddresses = crt.Spec.NameConstraints.Permitted.EmailAddresses + nameConstraints.PermittedURIDomains = crt.Spec.NameConstraints.Permitted.URIDomains + } -func GenerateTemplateFromCSRPEMWithUsages(csrPEM []byte, duration time.Duration, isCA bool, keyUsage x509.KeyUsage, extKeyUsage []x509.ExtKeyUsage) (*x509.Certificate, error) { - block, _ := pem.Decode(csrPEM) - if block == nil { - return nil, errors.New("failed to decode csr") - } + if crt.Spec.NameConstraints.Excluded != nil { + nameConstraints.ExcludedDNSDomains = crt.Spec.NameConstraints.Excluded.DNSDomains + nameConstraints.ExcludedIPRanges, err = parseCIDRs(crt.Spec.NameConstraints.Excluded.IPRanges) + if err != nil { + return nil, err + } + nameConstraints.ExcludedEmailAddresses = crt.Spec.NameConstraints.Excluded.EmailAddresses + nameConstraints.ExcludedURIDomains = crt.Spec.NameConstraints.Excluded.URIDomains + } - csr, err := x509.ParseCertificateRequest(block.Bytes) - if err != nil { - return nil, err - } + if !nameConstraints.IsEmpty() { + extension, err := MarshalNameConstraints(nameConstraints, crt.Spec.NameConstraints.Critical) + if err != nil { + return nil, err + } - if err := csr.CheckSignature(); err != nil { - return nil, err + extraExtensions = append(extraExtensions, extension) + } } - serialNumber, err := rand.Int(rand.Reader, serialNumberLimit) - if err != nil { - return nil, fmt.Errorf("failed to generate serial number: %s", err.Error()) + cr := &x509.CertificateRequest{ + // Version 0 is the only one defined in the PKCS#10 standard, RFC2986. + // This value isn't used by Go at the time of writing. + // https://datatracker.ietf.org/doc/html/rfc2986#section-4 + Version: 0, + SignatureAlgorithm: sigAlgo, + PublicKeyAlgorithm: pubKeyAlgo, + RawSubject: asn1Subject, + ExtraExtensions: extraExtensions, } - return &x509.Certificate{ - // Version must be 2 according to RFC5280. - // A version value of 2 confusingly means version 3. - // This value isn't used by Go at the time of writing. - // https://datatracker.ietf.org/doc/html/rfc5280#section-4.1.2.1 - Version: 2, - BasicConstraintsValid: true, - SerialNumber: serialNumber, - PublicKeyAlgorithm: csr.PublicKeyAlgorithm, - PublicKey: csr.PublicKey, - IsCA: isCA, - Subject: csr.Subject, - RawSubject: csr.RawSubject, - NotBefore: time.Now(), - NotAfter: time.Now().Add(duration), - // see http://golang.org/pkg/crypto/x509/#KeyUsage - KeyUsage: keyUsage, - ExtKeyUsage: extKeyUsage, - DNSNames: csr.DNSNames, - IPAddresses: csr.IPAddresses, - EmailAddresses: csr.EmailAddresses, - URIs: csr.URIs, - }, nil + return cr, nil } // SignCertificate returns a signed *x509.Certificate given a template @@ -470,9 +342,45 @@ func GenerateTemplateFromCSRPEMWithUsages(csrPEM []byte, duration time.Duration, // key of the signer. // It returns a PEM encoded copy of the Certificate as well as a *x509.Certificate // which can be used for reading the encoded values. -func SignCertificate(template *x509.Certificate, issuerCert *x509.Certificate, publicKey crypto.PublicKey, signerKey interface{}) ([]byte, *x509.Certificate, error) { - derBytes, err := x509.CreateCertificate(rand.Reader, template, issuerCert, publicKey, signerKey) +func SignCertificate(template *x509.Certificate, issuerCert *x509.Certificate, publicKey crypto.PublicKey, signerKey any) ([]byte, *x509.Certificate, error) { + typedSigner, ok := signerKey.(crypto.Signer) + if !ok { + return nil, nil, fmt.Errorf("didn't get an expected Signer in call to SignCertificate") + } + + var pubKeyAlgo x509.PublicKeyAlgorithm + var sigAlgoArg any + + // NB: can't rely on issuerCert.Public or issuercert.PublicKeyAlgorithm being set reliably; + // but we know that signerKey.Public() will work! + switch pubKey := typedSigner.Public().(type) { + case *rsa.PublicKey: + pubKeyAlgo = x509.RSA + + // Size is in bytes so multiply by 8 to get bits because they're more familiar + // This is technically not portable but if you're using cert-manager on a platform + // with bytes that don't have 8 bits, you've got bigger problems than this! + sigAlgoArg = pubKey.Size() * 8 + + case *ecdsa.PublicKey: + pubKeyAlgo = x509.ECDSA + sigAlgoArg = pubKey.Curve + + case ed25519.PublicKey: + pubKeyAlgo = x509.Ed25519 + sigAlgoArg = nil // ignored by signatureAlgorithmFromPublicKey + + default: + return nil, nil, fmt.Errorf("unknown public key type on signing certificate: %T", issuerCert.PublicKey) + } + var err error + template.SignatureAlgorithm, err = signatureAlgorithmFromPublicKey(pubKeyAlgo, sigAlgoArg) + if err != nil { + return nil, nil, err + } + + derBytes, err := x509.CreateCertificate(rand.Reader, template, issuerCert, publicKey, signerKey) if err != nil { return nil, nil, fmt.Errorf("error creating x509 certificate: %s", err.Error()) } @@ -493,16 +401,16 @@ func SignCertificate(template *x509.Certificate, issuerCert *x509.Certificate, p // SignCSRTemplate signs a certificate template usually based upon a CSR. This // function expects all fields to be present in the certificate template, -// including it's public key. +// including its public key. // It returns the PEM bundle containing certificate data and the CA data, encoded in PEM format. -func SignCSRTemplate(caCerts []*x509.Certificate, caKey crypto.Signer, template *x509.Certificate) (PEMBundle, error) { +func SignCSRTemplate(caCerts []*x509.Certificate, caPrivateKey crypto.Signer, template *x509.Certificate) (PEMBundle, error) { if len(caCerts) == 0 { return PEMBundle{}, errors.New("no CA certificates given to sign CSR template") } issuingCACert := caCerts[0] - _, cert, err := SignCertificate(template, issuingCACert, template.PublicKey, caKey) + _, cert, err := SignCertificate(template, issuingCACert, template.PublicKey, caPrivateKey) if err != nil { return PEMBundle{}, err } @@ -564,83 +472,143 @@ func EncodeX509Chain(certs []*x509.Certificate) ([]byte, error) { return caPem.Bytes(), nil } +var keyAlgorithms = map[v1.PrivateKeyAlgorithm]x509.PublicKeyAlgorithm{ + v1.RSAKeyAlgorithm: x509.RSA, + v1.ECDSAKeyAlgorithm: x509.ECDSA, + v1.Ed25519KeyAlgorithm: x509.Ed25519, +} +var sigAlgorithms = map[v1.SignatureAlgorithm]x509.SignatureAlgorithm{ + v1.SHA256WithRSA: x509.SHA256WithRSA, + v1.SHA384WithRSA: x509.SHA384WithRSA, + v1.SHA512WithRSA: x509.SHA512WithRSA, + v1.ECDSAWithSHA256: x509.ECDSAWithSHA256, + v1.ECDSAWithSHA384: x509.ECDSAWithSHA384, + v1.ECDSAWithSHA512: x509.ECDSAWithSHA512, + v1.PureEd25519: x509.PureEd25519, +} + // SignatureAlgorithm will determine the appropriate signature algorithm for // the given certificate. // Adapted from https://github.com/cloudflare/cfssl/blob/master/csr/csr.go#L102 func SignatureAlgorithm(crt *v1.Certificate) (x509.PublicKeyAlgorithm, x509.SignatureAlgorithm, error) { - var sigAlgo x509.SignatureAlgorithm var pubKeyAlgo x509.PublicKeyAlgorithm var specAlgorithm v1.PrivateKeyAlgorithm + var specKeySize int + if crt.Spec.PrivateKey != nil { specAlgorithm = crt.Spec.PrivateKey.Algorithm + specKeySize = crt.Spec.PrivateKey.Size } - switch specAlgorithm { - case v1.PrivateKeyAlgorithm(""): - // If keyAlgorithm is not specified, we default to rsa with keysize 2048 - pubKeyAlgo = x509.RSA - sigAlgo = x509.SHA256WithRSA - case v1.RSAKeyAlgorithm: + + var sigAlgoArg any + + var ok bool + if specAlgorithm == "" { pubKeyAlgo = x509.RSA - switch { - case crt.Spec.PrivateKey.Size >= 4096: - sigAlgo = x509.SHA512WithRSA - case crt.Spec.PrivateKey.Size >= 3072: - sigAlgo = x509.SHA384WithRSA - case crt.Spec.PrivateKey.Size >= 2048: - sigAlgo = x509.SHA256WithRSA - // 0 == not set - case crt.Spec.PrivateKey.Size == 0: - sigAlgo = x509.SHA256WithRSA - default: - return x509.UnknownPublicKeyAlgorithm, x509.UnknownSignatureAlgorithm, fmt.Errorf("unsupported rsa keysize specified: %d. min keysize %d", crt.Spec.PrivateKey.Size, MinRSAKeySize) + } else { + pubKeyAlgo, ok = keyAlgorithms[specAlgorithm] + if !ok { + return x509.UnknownPublicKeyAlgorithm, x509.UnknownSignatureAlgorithm, fmt.Errorf("unsupported algorithm specified: %s. should be either 'ecdsa', 'ed25519' or 'rsa", crt.Spec.PrivateKey.Algorithm) } - case v1.Ed25519KeyAlgorithm: - pubKeyAlgo = x509.Ed25519 - sigAlgo = x509.PureEd25519 - case v1.ECDSAKeyAlgorithm: - pubKeyAlgo = x509.ECDSA - switch crt.Spec.PrivateKey.Size { + } + + var sigAlgo x509.SignatureAlgorithm + if crt.Spec.SignatureAlgorithm != "" { + sigAlgo, ok = sigAlgorithms[crt.Spec.SignatureAlgorithm] + if !ok { + return x509.UnknownPublicKeyAlgorithm, x509.UnknownSignatureAlgorithm, fmt.Errorf("unsupported signature algorithm: %s", crt.Spec.SignatureAlgorithm) + } + return pubKeyAlgo, sigAlgo, nil + } + + switch pubKeyAlgo { + case x509.RSA: + if specKeySize == 0 { + sigAlgoArg = MinRSAKeySize + } else { + sigAlgoArg = specKeySize + } + case x509.ECDSA: + switch specKeySize { case 521: - sigAlgo = x509.ECDSAWithSHA512 + sigAlgoArg = elliptic.P521() case 384: - sigAlgo = x509.ECDSAWithSHA384 - case 256: - sigAlgo = x509.ECDSAWithSHA256 - case 0: - sigAlgo = x509.ECDSAWithSHA256 + sigAlgoArg = elliptic.P384() + case 256, 0: + sigAlgoArg = elliptic.P256() default: return x509.UnknownPublicKeyAlgorithm, x509.UnknownSignatureAlgorithm, fmt.Errorf("unsupported ecdsa keysize specified: %d", crt.Spec.PrivateKey.Size) } default: - return x509.UnknownPublicKeyAlgorithm, x509.UnknownSignatureAlgorithm, fmt.Errorf("unsupported algorithm specified: %s. should be either 'ecdsa' or 'rsa", crt.Spec.PrivateKey.Algorithm) + // ok + } + + sigAlgo, err := signatureAlgorithmFromPublicKey(pubKeyAlgo, sigAlgoArg) + if err != nil { + return x509.UnknownPublicKeyAlgorithm, x509.UnknownSignatureAlgorithm, err } + return pubKeyAlgo, sigAlgo, nil } -func extractCommonName(spec v1.CertificateSpec) (string, error) { - var commonName = spec.CommonName - if isLiteralCertificateSubjectEnabled() && len(spec.LiteralSubject) > 0 { - commonName = "" - sequence, err := ParseSubjectStringToRdnSequence(spec.LiteralSubject) - if err != nil { - return "", err +// signatureAlgorithmFromPublicKey takes a public key type and an argument specific to that public +// key, and returns an appropriate signature algorithm for that key. +// If alg is x509.RSA, arg must be an integer key size in bits +// If alg is x509.ECDSA, arg must be an elliptic.Curve +// If alg is x509.Ed25519, arg is ignored +// All other algorithms and args cause an error +// The signature algorithms returned by this function are to some degree a matter of preference. The +// choices here are motivated by what is common and what is required by bodies such as the US DoD. +func signatureAlgorithmFromPublicKey(alg x509.PublicKeyAlgorithm, arg any) (x509.SignatureAlgorithm, error) { + var signatureAlgorithm x509.SignatureAlgorithm + + switch alg { + case x509.RSA: + size, ok := arg.(int) + if !ok { + return x509.UnknownSignatureAlgorithm, fmt.Errorf("expected to get an integer key size for RSA key but got %T", arg) } - for _, rdns := range sequence { - for _, atv := range rdns { - if atv.Type.Equal(OIDConstants.CommonName) { - if str, ok := atv.Value.(string); ok { - commonName = str - } - } - } + switch { + case size >= 4096: + signatureAlgorithm = x509.SHA512WithRSA + + case size >= 3072: + signatureAlgorithm = x509.SHA384WithRSA + + case size >= 2048: + signatureAlgorithm = x509.SHA256WithRSA + + default: + return x509.UnknownSignatureAlgorithm, fmt.Errorf("invalid size %d for RSA key on signing certificate", size) } - } - return commonName, nil + case x509.ECDSA: + curve, ok := arg.(elliptic.Curve) + if !ok { + return x509.UnknownSignatureAlgorithm, fmt.Errorf("expected to get an ECDSA curve for ECDSA key but got %T", arg) + } -} + switch curve { + case elliptic.P521(): + signatureAlgorithm = x509.ECDSAWithSHA512 + + case elliptic.P384(): + signatureAlgorithm = x509.ECDSAWithSHA384 + + case elliptic.P256(): + signatureAlgorithm = x509.ECDSAWithSHA256 + + default: + return x509.UnknownSignatureAlgorithm, fmt.Errorf("unknown / unsupported curve attached to ECDSA signing certificate") + } + + case x509.Ed25519: + signatureAlgorithm = x509.PureEd25519 + + default: + return x509.UnknownSignatureAlgorithm, fmt.Errorf("got unsupported public key type when trying to calculate signature algorithm") + } -func isLiteralCertificateSubjectEnabled() bool { - return utilfeature.DefaultFeatureGate.Enabled(feature.LiteralCertificateSubject) + return signatureAlgorithm, nil } diff --git a/pkg/util/pki/csr_test.go b/pkg/util/pki/csr_test.go index 4430db6d4a8..78e5142319b 100644 --- a/pkg/util/pki/csr_test.go +++ b/pkg/util/pki/csr_test.go @@ -19,34 +19,29 @@ package pki import ( "bytes" "crypto" + "crypto/ecdsa" + "crypto/ed25519" + "crypto/elliptic" + "crypto/rand" + "crypto/rsa" "crypto/x509" "crypto/x509/pkix" "encoding/asn1" + "fmt" "math/big" + "net" "reflect" "testing" "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - featuregatetesting "k8s.io/component-base/featuregate/testing" - "github.com/cert-manager/cert-manager/internal/controller/feature" cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" "github.com/cert-manager/cert-manager/pkg/util" - utilfeature "github.com/cert-manager/cert-manager/pkg/util/feature" ) -func buildCertificate(cn string, dnsNames ...string) *cmapi.Certificate { - return &cmapi.Certificate{ - Spec: cmapi.CertificateSpec{ - CommonName: cn, - DNSNames: dnsNames, - }, - } -} - -func TestBuildUsages(t *testing.T) { +func TestKeyUsagesForCertificate(t *testing.T) { type testT struct { name string usages []cmapi.KeyUsage @@ -101,7 +96,7 @@ func TestBuildUsages(t *testing.T) { } testFn := func(test testT) func(*testing.T) { return func(t *testing.T) { - ku, eku, err := BuildKeyUsages(test.usages, test.isCa) + ku, eku, err := KeyUsagesForCertificateOrCertificateRequest(test.usages, test.isCa) if err != nil && !test.expectedError { t.Errorf("got unexpected error generating cert: %q", err) return @@ -121,117 +116,12 @@ func TestBuildUsages(t *testing.T) { } } -func TestCommonNameForCertificate(t *testing.T) { - type testT struct { - name string - crtCN string - crtDNSNames []string - expectedCN string - } - tests := []testT{ - { - name: "certificate with CommonName set", - crtCN: "test", - expectedCN: "test", - }, - { - name: "certificate with one DNS name set", - crtDNSNames: []string{"dnsname"}, - expectedCN: "", - }, - { - name: "certificate with both common name and dnsName set", - crtCN: "cn", - crtDNSNames: []string{"dnsname"}, - expectedCN: "cn", - }, - { - name: "certificate with multiple dns names set", - crtDNSNames: []string{"dnsname1", "dnsname2"}, - expectedCN: "", - }, - } - testFn := func(test testT) func(*testing.T) { - return func(t *testing.T) { - actualCN := buildCertificate(test.crtCN, test.crtDNSNames...).Spec.CommonName - if actualCN != test.expectedCN { - t.Errorf("expected %q but got %q", test.expectedCN, actualCN) - return - } - } - } - for _, test := range tests { - t.Run(test.name, testFn(test)) - } -} - -func TestDNSNamesForCertificate(t *testing.T) { - type testT struct { - name string - crtCN string - crtDNSNames []string - expectDNSNames []string - } - tests := []testT{ - { - name: "certificate with CommonName set", - crtCN: "test", - expectDNSNames: []string{}, - }, - { - name: "certificate with one DNS name set", - crtDNSNames: []string{"dnsname"}, - expectDNSNames: []string{"dnsname"}, - }, - { - name: "certificate with both common name and dnsName set", - crtCN: "cn", - crtDNSNames: []string{"dnsname"}, - expectDNSNames: []string{"dnsname"}, - }, - { - name: "certificate with multiple dns names set", - crtDNSNames: []string{"dnsname1", "dnsname2"}, - expectDNSNames: []string{"dnsname1", "dnsname2"}, - }, - { - name: "certificate with dnsName[0] set to equal common name", - crtCN: "cn", - crtDNSNames: []string{"cn", "dnsname"}, - expectDNSNames: []string{"cn", "dnsname"}, - }, - { - name: "certificate with a dnsName equal to cn", - crtCN: "cn", - crtDNSNames: []string{"dnsname", "cn"}, - expectDNSNames: []string{"dnsname", "cn"}, - }, - } - testFn := func(test testT) func(*testing.T) { - return func(t *testing.T) { - actualDNSNames := buildCertificate(test.crtCN, test.crtDNSNames...).Spec.DNSNames - if len(actualDNSNames) != len(test.expectDNSNames) { - t.Errorf("expected %q but got %q", test.expectDNSNames, actualDNSNames) - return - } - for i, actual := range actualDNSNames { - if test.expectDNSNames[i] != actual { - t.Errorf("expected %q but got %q", test.expectDNSNames, actualDNSNames) - return - } - } - } - } - for _, test := range tests { - t.Run(test.name, testFn(test)) - } -} - func TestSignatureAlgorithmForCertificate(t *testing.T) { type testT struct { name string keyAlgo cmapi.PrivateKeyAlgorithm keySize int + sigAlg cmapi.SignatureAlgorithm expectErr bool expectedSigAlgo x509.SignatureAlgorithm expectedKeyType x509.PublicKeyAlgorithm @@ -316,6 +206,19 @@ func TestSignatureAlgorithmForCertificate(t *testing.T) { keySize: 100, expectErr: true, }, + { + name: "certificate no key and custom signature", + sigAlg: cmapi.SHA384WithRSA, + expectedSigAlgo: x509.SHA384WithRSA, + expectedKeyType: x509.RSA, + }, + { + name: "certificate explicit key and custom signature", + keyAlgo: cmapi.RSAKeyAlgorithm, + sigAlg: cmapi.SHA384WithRSA, + expectedSigAlgo: x509.SHA384WithRSA, + expectedKeyType: x509.RSA, + }, { name: "certificate with KeyAlgorithm set to unknown key algo", keyAlgo: cmapi.PrivateKeyAlgorithm("blah"), @@ -325,7 +228,7 @@ func TestSignatureAlgorithmForCertificate(t *testing.T) { testFn := func(test testT) func(*testing.T) { return func(t *testing.T) { - actualPKAlgo, actualSigAlgo, err := SignatureAlgorithm(buildCertificateWithKeyParams(test.keyAlgo, test.keySize)) + actualPKAlgo, actualSigAlgo, err := SignatureAlgorithm(buildCertificateWithKeyAndSigParams(test.keyAlgo, test.keySize, test.sigAlg)) if test.expectErr && err == nil { t.Error("expected err, but got no error") return @@ -388,52 +291,144 @@ func TestRemoveDuplicates(t *testing.T) { } } +func removeDuplicates(in []string) []string { + var found []string +Outer: + for _, i := range in { + for _, i2 := range found { + if i2 == i { + continue Outer + } + } + found = append(found, i) + } + return found +} + +func OtherNameSANRawVal(expectedOID asn1.ObjectIdentifier) (asn1.RawValue, error) { + var otherNameParam = fmt.Sprintf("tag:%d", nameTypeOtherName) + + value, err := MarshalUniversalValue(UniversalValue{ + UTF8String: "user@example.org", + }) + if err != nil { + return asn1.NullRawValue, err + } + + otherNameDer, err := asn1.MarshalWithParams(OtherName{ + TypeID: expectedOID, // UPN OID + Value: asn1.RawValue{ + Tag: 0, + Class: asn1.ClassContextSpecific, + IsCompound: true, + Bytes: value, + }, + }, otherNameParam) + + if err != nil { + return asn1.NullRawValue, err + } + rawVal := asn1.RawValue{ + FullBytes: otherNameDer, + } + return rawVal, nil +} + func TestGenerateCSR(t *testing.T) { - // 0xa0 = DigitalSignature and Encipherment usage - asn1KeyUsage, err := asn1.Marshal(asn1.BitString{Bytes: []byte{0xa0}, BitLength: asn1BitLength([]byte{0xa0})}) + exampleLiteralSubject := "CN=actual-cn, OU=FooLong, OU=Bar, O=example.org" + exampleMultiValueRDNLiteralSubject := "CN=actual-cn, OU=FooLong+OU=Bar, O=example.org" + + asn1otherNameUpnSANRawVal, err := OtherNameSANRawVal(asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 311, 20, 2, 3}) // UPN OID if err != nil { t.Fatal(err) } - defaultExtraExtensions := []pkix.Extension{ - { - Id: OIDExtensionKeyUsage, - Value: asn1KeyUsage, - }, + + asn1otherNamesAMAAccountNameRawVal, err := OtherNameSANRawVal(asn1.ObjectIdentifier{1, 2, 840, 113556, 1, 4, 221}) // sAMAccountName OID + if err != nil { + t.Fatal(err) } - asn1ExtKeyUsage, err := asn1.Marshal([]asn1.ObjectIdentifier{oidExtKeyUsageIPSECEndSystem}) + // 0xa0 = DigitalSignature and Encipherment usage + asn1DefaultKeyUsage, err := asn1.Marshal(asn1.BitString{Bytes: []byte{0xa0}, BitLength: asn1BitLength([]byte{0xa0})}) if err != nil { t.Fatal(err) } - ipsecExtraExtensions := []pkix.Extension{ - { - Id: OIDExtensionKeyUsage, - Value: asn1KeyUsage, - }, - { - Id: OIDExtensionExtendedKeyUsage, - Value: asn1ExtKeyUsage, - }, + + // 0xa4 = DigitalSignature, Encipherment and KeyCertSign usage + asn1KeyUsageWithCa, err := asn1.Marshal(asn1.BitString{Bytes: []byte{0xa4}, BitLength: asn1BitLength([]byte{0xa4})}) + if err != nil { + t.Fatal(err) } - exampleLiteralSubject := "CN=actual-cn, OU=FooLong, OU=Bar, O=example.org" - rawExampleLiteralSubject, err := ParseSubjectStringToRawDerBytes(exampleLiteralSubject) + asn1ClientAuth, err := asn1.Marshal([]asn1.ObjectIdentifier{oidExtKeyUsageClientAuth}) if err != nil { t.Fatal(err) } - exampleMultiValueRDNLiteralSubject := "CN=actual-cn, OU=FooLong+OU=Bar, O=example.org" - rawExampleMultiValueRDNLiteralSubject, err := ParseSubjectStringToRawDerBytes(exampleMultiValueRDNLiteralSubject) + asn1ServerClientAuth, err := asn1.Marshal([]asn1.ObjectIdentifier{oidExtKeyUsageServerAuth, oidExtKeyUsageClientAuth}) if err != nil { t.Fatal(err) } + asn1ExtKeyUsage, err := asn1.Marshal([]asn1.ObjectIdentifier{oidExtKeyUsageIPSECEndSystem}) + if err != nil { + t.Fatal(err) + } + + basicConstraintsGenerator := func(t *testing.T, isCA bool) []byte { + data, err := asn1.Marshal(struct { + IsCA bool `asn1:"optional"` + }{ + IsCA: isCA, + }) + if err != nil { + t.Fatal(err) + } + return data + } + + subjectGenerator := func(t *testing.T, name pkix.Name) []byte { + data, err := MarshalRDNSequenceToRawDERBytes(name.ToRDNSequence()) + if err != nil { + t.Fatal(err) + } + return data + } + + sansGenerator := func(t *testing.T, generalNames []asn1.RawValue, critical bool) pkix.Extension { + val, err := asn1.Marshal(generalNames) + if err != nil { + t.Fatal(err) + } + + return pkix.Extension{ + Id: oidExtensionSubjectAltName, + Critical: critical, + Value: val, + } + } + + literalSubjectGenerator := func(t *testing.T, literal string) []byte { + rawSubject, err := UnmarshalSubjectStringToRDNSequence(literal) + if err != nil { + t.Fatal(err) + } + asn1Subject, err := MarshalRDNSequenceToRawDERBytes(rawSubject) + if err != nil { + t.Fatal(err) + } + return asn1Subject + } + tests := []struct { name string crt *cmapi.Certificate want *x509.CertificateRequest wantErr bool literalCertificateSubjectFeatureEnabled bool + basicConstraintsFeatureEnabled bool + nameConstraintsFeatureEnabled bool + otherNamesFeatureEnabled bool }{ { name: "Generate CSR from certificate with only DNS", @@ -442,8 +437,48 @@ func TestGenerateCSR(t *testing.T) { Version: 0, SignatureAlgorithm: x509.SHA256WithRSA, PublicKeyAlgorithm: x509.RSA, - DNSNames: []string{"example.org"}, - ExtraExtensions: defaultExtraExtensions, + ExtraExtensions: []pkix.Extension{ + sansGenerator( + t, + []asn1.RawValue{ + {Tag: nameTypeDNSName, Class: 2, Bytes: []byte("example.org")}, + }, + true, // SAN is critical as the Subject is empty + ), + { + Id: OIDExtensionKeyUsage, + Value: asn1DefaultKeyUsage, + Critical: true, + }, + }, + RawSubject: subjectGenerator(t, pkix.Name{}), + }, + }, + { + name: "Generate CSR from certificate with subject and DNS", + crt: &cmapi.Certificate{Spec: cmapi.CertificateSpec{ + Subject: &cmapi.X509Subject{Organizations: []string{"example inc."}}, + DNSNames: []string{"example.org"}, + }}, + want: &x509.CertificateRequest{ + Version: 0, + SignatureAlgorithm: x509.SHA256WithRSA, + PublicKeyAlgorithm: x509.RSA, + ExtraExtensions: []pkix.Extension{ + sansGenerator( + t, + []asn1.RawValue{ + {Tag: nameTypeDNSName, Class: 2, Bytes: []byte("example.org")}, + }, + false, // SAN is NOT critical as the Subject is not empty + ), + { + Id: OIDExtensionKeyUsage, + Value: asn1DefaultKeyUsage, + Critical: true, + }, + }, + RawSubject: subjectGenerator(t, pkix.Name{Organization: []string{"example inc."}}), }, }, { @@ -453,9 +488,78 @@ func TestGenerateCSR(t *testing.T) { Version: 0, SignatureAlgorithm: x509.SHA256WithRSA, PublicKeyAlgorithm: x509.RSA, - Subject: pkix.Name{CommonName: "example.org"}, - ExtraExtensions: defaultExtraExtensions, + ExtraExtensions: []pkix.Extension{ + { + Id: OIDExtensionKeyUsage, + Value: asn1DefaultKeyUsage, + Critical: true, + }, + }, + RawSubject: subjectGenerator(t, pkix.Name{CommonName: "example.org"}), + }, + }, + { + name: "Generate CSR from certificate with isCA set", + crt: &cmapi.Certificate{Spec: cmapi.CertificateSpec{CommonName: "example.org", IsCA: true}}, + want: &x509.CertificateRequest{ + Version: 0, + SignatureAlgorithm: x509.SHA256WithRSA, + PublicKeyAlgorithm: x509.RSA, + ExtraExtensions: []pkix.Extension{ + { + Id: OIDExtensionKeyUsage, + Value: asn1KeyUsageWithCa, + Critical: true, + }, + }, + RawSubject: subjectGenerator(t, pkix.Name{CommonName: "example.org"}), + }, + }, + { + name: "Generate CSR from certificate with isCA not set and with UseCertificateRequestBasicConstraints flag enabled", + crt: &cmapi.Certificate{Spec: cmapi.CertificateSpec{CommonName: "example.org"}}, + want: &x509.CertificateRequest{ + Version: 0, + SignatureAlgorithm: x509.SHA256WithRSA, + PublicKeyAlgorithm: x509.RSA, + ExtraExtensions: []pkix.Extension{ + { + Id: OIDExtensionKeyUsage, + Value: asn1DefaultKeyUsage, + Critical: true, + }, + { + Id: OIDExtensionBasicConstraints, + Value: basicConstraintsGenerator(t, false), + Critical: true, + }, + }, + RawSubject: subjectGenerator(t, pkix.Name{CommonName: "example.org"}), + }, + basicConstraintsFeatureEnabled: true, + }, + { + name: "Generate CSR from certificate with isCA set and with UseCertificateRequestBasicConstraints flag enabled", + crt: &cmapi.Certificate{Spec: cmapi.CertificateSpec{CommonName: "example.org", IsCA: true}}, + want: &x509.CertificateRequest{ + Version: 0, + SignatureAlgorithm: x509.SHA256WithRSA, + PublicKeyAlgorithm: x509.RSA, + ExtraExtensions: []pkix.Extension{ + { + Id: OIDExtensionKeyUsage, + Value: asn1KeyUsageWithCa, + Critical: true, + }, + { + Id: OIDExtensionBasicConstraints, + Value: basicConstraintsGenerator(t, true), + Critical: true, + }, + }, + RawSubject: subjectGenerator(t, pkix.Name{CommonName: "example.org"}), }, + basicConstraintsFeatureEnabled: true, }, { name: "Generate CSR from certificate with extended key usages", @@ -464,9 +568,96 @@ func TestGenerateCSR(t *testing.T) { Version: 0, SignatureAlgorithm: x509.SHA256WithRSA, PublicKeyAlgorithm: x509.RSA, - Subject: pkix.Name{CommonName: "example.org"}, - ExtraExtensions: ipsecExtraExtensions, + ExtraExtensions: []pkix.Extension{ + { + Id: OIDExtensionKeyUsage, + Value: asn1DefaultKeyUsage, + Critical: true, + }, + { + Id: OIDExtensionExtendedKeyUsage, + Value: asn1ExtKeyUsage, + }, + }, + RawSubject: subjectGenerator(t, pkix.Name{CommonName: "example.org"}), + }, + }, + { + name: "Generate CSR from certificate with a single otherNameSAN set to an oid (UPN)", // only a shallow validation is expected + crt: &cmapi.Certificate{Spec: cmapi.CertificateSpec{OtherNames: []cmapi.OtherName{ + { + OID: "1.3.6.1.4.1.311.20.2.3", + UTF8Value: "user@example.org", + }, + }}}, + want: &x509.CertificateRequest{ + Version: 0, + SignatureAlgorithm: x509.SHA256WithRSA, + PublicKeyAlgorithm: x509.RSA, + ExtraExtensions: []pkix.Extension{ + sansGenerator( + t, + []asn1.RawValue{asn1otherNameUpnSANRawVal}, + true, + ), + { + Id: OIDExtensionKeyUsage, + Value: asn1DefaultKeyUsage, + Critical: true, + }, + }, + RawSubject: subjectGenerator(t, pkix.Name{}), + }, + otherNamesFeatureEnabled: true, + }, + { + name: "Generate CSR from certificate with multiple valid otherName oids and emailSANs set", + crt: &cmapi.Certificate{Spec: cmapi.CertificateSpec{ + EmailAddresses: []string{"user@example.org", "alt-email@example.org"}, + OtherNames: []cmapi.OtherName{ + { + OID: "1.3.6.1.4.1.311.20.2.3", + UTF8Value: "user@example.org", + }, + { + OID: "1.2.840.113556.1.4.221", + UTF8Value: "user@example.org", + }, + }}}, + want: &x509.CertificateRequest{ + Version: 0, + SignatureAlgorithm: x509.SHA256WithRSA, + PublicKeyAlgorithm: x509.RSA, + ExtraExtensions: []pkix.Extension{ + sansGenerator( + t, + []asn1.RawValue{ + {Tag: nameTypeRFC822Name, Class: 2, Bytes: []byte("user@example.org")}, + {Tag: nameTypeRFC822Name, Class: 2, Bytes: []byte("alt-email@example.org")}, + asn1otherNameUpnSANRawVal, + asn1otherNamesAMAAccountNameRawVal, + }, + true, + ), + { + Id: OIDExtensionKeyUsage, + Value: asn1DefaultKeyUsage, + Critical: true, + }, + }, + RawSubject: subjectGenerator(t, pkix.Name{}), }, + otherNamesFeatureEnabled: true, + }, + { + name: "Generate CSR from certificate with malformed otherName oid type", + crt: &cmapi.Certificate{Spec: cmapi.CertificateSpec{OtherNames: []cmapi.OtherName{ + { + OID: "NOTANOID@garbage", + UTF8Value: "user@example.org", + }, + }}}, + wantErr: true, }, { name: "Generate CSR from certificate with double signing key usages", @@ -475,8 +666,14 @@ func TestGenerateCSR(t *testing.T) { Version: 0, SignatureAlgorithm: x509.SHA256WithRSA, PublicKeyAlgorithm: x509.RSA, - Subject: pkix.Name{CommonName: "example.org"}, - ExtraExtensions: defaultExtraExtensions, + ExtraExtensions: []pkix.Extension{ + { + Id: OIDExtensionKeyUsage, + Value: asn1DefaultKeyUsage, + Critical: true, + }, + }, + RawSubject: subjectGenerator(t, pkix.Name{CommonName: "example.org"}), }, }, { @@ -485,133 +682,214 @@ func TestGenerateCSR(t *testing.T) { wantErr: true, }, { - name: "Generate CSR from certficate with literal subject honouring the exact order", + name: "Generate CSR from certificate with literal subject honouring the exact order", crt: &cmapi.Certificate{Spec: cmapi.CertificateSpec{LiteralSubject: exampleLiteralSubject}}, want: &x509.CertificateRequest{ Version: 0, SignatureAlgorithm: x509.SHA256WithRSA, PublicKeyAlgorithm: x509.RSA, - RawSubject: rawExampleLiteralSubject, - ExtraExtensions: defaultExtraExtensions, + ExtraExtensions: []pkix.Extension{ + { + Id: OIDExtensionKeyUsage, + Value: asn1DefaultKeyUsage, + Critical: true, + }, + }, + RawSubject: literalSubjectGenerator(t, exampleLiteralSubject), }, literalCertificateSubjectFeatureEnabled: true, }, { - name: "Generate CSR from certficate with literal multi value subject honouring the exact order", + name: "Generate CSR from certificate with literal multi value subject honouring the exact order", crt: &cmapi.Certificate{Spec: cmapi.CertificateSpec{LiteralSubject: exampleMultiValueRDNLiteralSubject}}, want: &x509.CertificateRequest{ Version: 0, SignatureAlgorithm: x509.SHA256WithRSA, PublicKeyAlgorithm: x509.RSA, - RawSubject: rawExampleMultiValueRDNLiteralSubject, - ExtraExtensions: defaultExtraExtensions, + ExtraExtensions: []pkix.Extension{ + { + Id: OIDExtensionKeyUsage, + Value: asn1DefaultKeyUsage, + Critical: true, + }, + }, + RawSubject: literalSubjectGenerator(t, exampleMultiValueRDNLiteralSubject), }, literalCertificateSubjectFeatureEnabled: true, }, { - name: "Error on generating CSR from certificate without CommonName in LiteralSubject, uri names, email address, or ip addresses", + name: "Error on generating CSR from certificate without CommonName in LiteralSubject, uri names, email address, ip addresses or otherName set", crt: &cmapi.Certificate{Spec: cmapi.CertificateSpec{LiteralSubject: "O=EmptyOrg"}}, wantErr: true, literalCertificateSubjectFeatureEnabled: true, }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultMutableFeatureGate, feature.LiteralCertificateSubject, tt.literalCertificateSubjectFeatureEnabled)() - got, err := GenerateCSR(tt.crt) - if (err != nil) != tt.wantErr { - t.Errorf("GenerateCSR() error = %v, wantErr %v", err, tt.wantErr) - return - } - if !reflect.DeepEqual(got, tt.want) { - t.Errorf("GenerateCSR() got = %v, want %v", got, tt.want) - } - }) - } -} - -func Test_buildKeyUsagesExtensionsForCertificate(t *testing.T) { - // 0xa0 = DigitalSignature and Encipherment usage - asn1DefaultKeyUsage, err := asn1.Marshal(asn1.BitString{Bytes: []byte{0xa0}, BitLength: asn1BitLength([]byte{0xa0})}) - if err != nil { - t.Fatal(err) - } - - asn1ClientAuth, err := asn1.Marshal([]asn1.ObjectIdentifier{oidExtKeyUsageClientAuth}) - if err != nil { - t.Fatal(err) - } - - asn1ServerClientAuth, err := asn1.Marshal([]asn1.ObjectIdentifier{oidExtKeyUsageServerAuth, oidExtKeyUsageClientAuth}) - if err != nil { - t.Fatal(err) - } - - tests := []struct { - name string - crt *cmapi.Certificate - want []pkix.Extension - wantErr bool - }{ { - name: "Test no usages set", - crt: &cmapi.Certificate{}, - want: []pkix.Extension{ - { - Id: OIDExtensionKeyUsage, - Value: asn1DefaultKeyUsage, + name: "KeyUsages and ExtendedKeyUsages: no usages set", + crt: &cmapi.Certificate{Spec: cmapi.CertificateSpec{DNSNames: []string{"example.org"}}}, + want: &x509.CertificateRequest{ + Version: 0, + SignatureAlgorithm: x509.SHA256WithRSA, + PublicKeyAlgorithm: x509.RSA, + ExtraExtensions: []pkix.Extension{ + sansGenerator( + t, + []asn1.RawValue{ + {Tag: nameTypeDNSName, Class: 2, Bytes: []byte("example.org")}, + }, + true, + ), + { + Id: OIDExtensionKeyUsage, + Value: asn1DefaultKeyUsage, + Critical: true, + }, }, + RawSubject: subjectGenerator(t, pkix.Name{}), }, wantErr: false, }, { - name: "Test client auth extended usage set", + name: "KeyUsages and ExtendedKeyUsages: client auth extended usage set", crt: &cmapi.Certificate{ Spec: cmapi.CertificateSpec{ - Usages: []cmapi.KeyUsage{cmapi.UsageDigitalSignature, cmapi.UsageKeyEncipherment, cmapi.UsageClientAuth}, + DNSNames: []string{"example.org"}, + Usages: []cmapi.KeyUsage{cmapi.UsageDigitalSignature, cmapi.UsageKeyEncipherment, cmapi.UsageClientAuth}, }, }, - want: []pkix.Extension{ - { - Id: OIDExtensionKeyUsage, - Value: asn1DefaultKeyUsage, - }, - { - Id: OIDExtensionExtendedKeyUsage, - Value: asn1ClientAuth, + want: &x509.CertificateRequest{ + Version: 0, + SignatureAlgorithm: x509.SHA256WithRSA, + PublicKeyAlgorithm: x509.RSA, + ExtraExtensions: []pkix.Extension{ + sansGenerator( + t, + []asn1.RawValue{ + {Tag: nameTypeDNSName, Class: 2, Bytes: []byte("example.org")}, + }, + true, + ), + { + Id: OIDExtensionKeyUsage, + Value: asn1DefaultKeyUsage, + Critical: true, + }, + { + Id: OIDExtensionExtendedKeyUsage, + Value: asn1ClientAuth, + }, }, + RawSubject: subjectGenerator(t, pkix.Name{}), }, wantErr: false, }, { - name: "Test server + client auth extended usage set", + name: "KeyUsages and ExtendedKeyUsages: server + client auth extended usage set", crt: &cmapi.Certificate{ Spec: cmapi.CertificateSpec{ - Usages: []cmapi.KeyUsage{cmapi.UsageDigitalSignature, cmapi.UsageKeyEncipherment, cmapi.UsageServerAuth, cmapi.UsageClientAuth}, + DNSNames: []string{"example.org"}, + Usages: []cmapi.KeyUsage{cmapi.UsageDigitalSignature, cmapi.UsageKeyEncipherment, cmapi.UsageServerAuth, cmapi.UsageClientAuth}, }, }, - want: []pkix.Extension{ - { - Id: OIDExtensionKeyUsage, - Value: asn1DefaultKeyUsage, - }, - { - Id: OIDExtensionExtendedKeyUsage, - Value: asn1ServerClientAuth, + want: &x509.CertificateRequest{ + Version: 0, + SignatureAlgorithm: x509.SHA256WithRSA, + PublicKeyAlgorithm: x509.RSA, + ExtraExtensions: []pkix.Extension{ + sansGenerator( + t, + []asn1.RawValue{ + {Tag: nameTypeDNSName, Class: 2, Bytes: []byte("example.org")}, + }, + true, + ), + { + Id: OIDExtensionKeyUsage, + Value: asn1DefaultKeyUsage, + Critical: true, + }, + { + Id: OIDExtensionExtendedKeyUsage, + Value: asn1ServerClientAuth, + }, }, + RawSubject: subjectGenerator(t, pkix.Name{}), }, wantErr: false, }, + { + name: "Generate CSR from certificate with NameConstraints flag enabled", + crt: &cmapi.Certificate{Spec: cmapi.CertificateSpec{ + CommonName: "example.org", + IsCA: true, + NameConstraints: &cmapi.NameConstraints{ + Critical: true, + Permitted: &cmapi.NameConstraintItem{ + DNSDomains: []string{"example.org"}, + IPRanges: []string{"10.10.0.0/16"}, + EmailAddresses: []string{"email@email.org"}, + }, + Excluded: &cmapi.NameConstraintItem{ + IPRanges: []string{"10.10.0.0/24"}, + }, + }, + }}, + want: &x509.CertificateRequest{ + Version: 0, + SignatureAlgorithm: x509.SHA256WithRSA, + PublicKeyAlgorithm: x509.RSA, + ExtraExtensions: []pkix.Extension{ + { + Id: OIDExtensionKeyUsage, + Value: asn1KeyUsageWithCa, + Critical: true, + }, + { + Id: OIDExtensionNameConstraints, + Value: []byte{0x30, 0x3e, 0xa0, 0x2e, 0x30, 0xd, 0x82, 0xb, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x6f, 0x72, 0x67, 0x30, 0xa, 0x87, 0x8, 0xa, 0xa, 0x0, 0x0, 0xff, 0xff, 0x0, 0x0, 0x30, 0x11, 0x81, 0xf, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x40, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x2e, 0x6f, 0x72, 0x67, 0xa1, 0xc, 0x30, 0xa, 0x87, 0x8, 0xa, 0xa, 0x0, 0x0, 0xff, 0xff, 0xff, 0x0}, + Critical: true, + }, + }, + RawSubject: subjectGenerator(t, pkix.Name{CommonName: "example.org"}), + }, + nameConstraintsFeatureEnabled: true, + }, } + for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got, err := buildKeyUsagesExtensionsForCertificate(tt.crt) + got, err := GenerateCSR( + tt.crt, + WithEncodeBasicConstraintsInRequest(tt.basicConstraintsFeatureEnabled), + WithNameConstraints(tt.nameConstraintsFeatureEnabled), + WithOtherNames(tt.otherNamesFeatureEnabled), + WithUseLiteralSubject(tt.literalCertificateSubjectFeatureEnabled), + ) if (err != nil) != tt.wantErr { - t.Errorf("buildKeyUsagesExtensionsForCertificate() error = %v, wantErr %v", err, tt.wantErr) + t.Errorf("GenerateCSR() error = %v, wantErr %v", err, tt.wantErr) return } if !reflect.DeepEqual(got, tt.want) { - t.Errorf("buildKeyUsagesExtensionsForCertificate() got = %v, want %v", got, tt.want) + t.Errorf("GenerateCSR() got = %v, want %v", got, tt.want) + return + } + + // TODO find a better way around the nil check + if got != nil { + // also check CSR generates valid certificate + pk, err := GenerateRSAPrivateKey(2048) + if err != nil { + t.Fatal(err) + } + + csrDER, err := EncodeCSR(got, pk) + if err != nil { + t.Fatal(err) + } + + _, err = x509.ParseCertificateRequest(csrDER) + if err != nil { + t.Errorf("Failed to parse generated certificate %s, Der: %v", err.Error(), csrDER) + } } }) } @@ -622,11 +900,14 @@ func TestSignCSRTemplate(t *testing.T) { // for that, we construct a chain of four certificates: // a root CA, two intermediate CA, and a leaf certificate. - mustCreatePair := func(issuerCert *x509.Certificate, issuerPK crypto.Signer, name string, isCA bool) ([]byte, *x509.Certificate, *x509.Certificate, crypto.Signer) { + mustCreatePair := func(issuerCert *x509.Certificate, issuerPK crypto.Signer, name string, isCA bool, nameConstraints *NameConstraints) ([]byte, *x509.Certificate, *x509.Certificate, crypto.Signer) { pk, err := GenerateECPrivateKey(256) require.NoError(t, err) + var permittedIPRanges []*net.IPNet + if nameConstraints != nil { + permittedIPRanges = nameConstraints.PermittedIPRanges + } tmpl := &x509.Certificate{ - Version: 2, BasicConstraintsValid: true, SerialNumber: big.NewInt(0), Subject: pkix.Name{ @@ -638,6 +919,7 @@ func TestSignCSRTemplate(t *testing.T) { KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, PublicKey: pk.Public(), IsCA: isCA, + PermittedIPRanges: permittedIPRanges, } if isCA { @@ -656,10 +938,16 @@ func TestSignCSRTemplate(t *testing.T) { return pem, cert, tmpl, pk } - rootPEM, rootCert, rootTmpl, rootPK := mustCreatePair(nil, nil, "root", true) - int1PEM, int1Cert, int1Tmpl, int1PK := mustCreatePair(rootCert, rootPK, "int1", true) - int2PEM, int2Cert, int2Tmpl, int2PK := mustCreatePair(int1Cert, int1PK, "int2", true) - leafPEM, _, leafTmpl, _ := mustCreatePair(int2Cert, int2PK, "leaf", false) + rootPEM, rootCert, rootTmpl, rootPK := mustCreatePair(nil, nil, "root", true, nil) + int1PEM, int1Cert, int1Tmpl, int1PK := mustCreatePair(rootCert, rootPK, "int1", true, nil) + int2PEM, int2Cert, int2Tmpl, int2PK := mustCreatePair(int1Cert, int1PK, "int2", true, nil) + leafPEM, _, leafTmpl, _ := mustCreatePair(int2Cert, int2PK, "leaf", false, nil) + + // vars for testing name constraints + _, permittedIPNet, _ := net.ParseCIDR("10.10.0.0/16") + _, ncRootCert, _, ncRootPK := mustCreatePair(nil, nil, "ncroot", true, &NameConstraints{PermittedIPRanges: []*net.IPNet{permittedIPNet}}) + _, _, ncLeafTmpl, _ := mustCreatePair(ncRootCert, ncRootPK, "ncleaf", false, nil) + ncLeafTmpl.IPAddresses = []net.IP{net.ParseIP("10.20.0.5")} tests := map[string]struct { caCerts []*x509.Certificate @@ -809,3 +1097,120 @@ func TestEncodeX509Chain(t *testing.T) { }) } } + +func rsaKey(t *testing.T, size int) crypto.Signer { + t.Helper() + + key, err := rsa.GenerateKey(rand.Reader, size) + if err != nil { + t.Fatalf("failed to generate RSA key with size %d: %s", size, err) + } + + return key +} + +func ecdsaKey(t *testing.T, curve elliptic.Curve) crypto.Signer { + t.Helper() + + key, err := ecdsa.GenerateKey(curve, rand.Reader) + if err != nil { + t.Fatalf("failed to generate ECDSA key with curve %s: %s", curve, err) + } + + return key +} + +func ed25519Key(t *testing.T) crypto.Signer { + t.Helper() + + _, priv, err := ed25519.GenerateKey(rand.Reader) + if err != nil { + t.Fatalf("failed to generate ed25519 key: %s", err) + } + + return priv +} + +func Test_SignCertificate_Signatures(t *testing.T) { + specs := map[string]struct { + SignerKey crypto.Signer + ExpectedSignatureAlgorithm x509.SignatureAlgorithm + ExpectErr bool + }{ + "RSA 2048": { + SignerKey: rsaKey(t, 2048), + ExpectedSignatureAlgorithm: x509.SHA256WithRSA, + }, + "RSA 3072": { + SignerKey: rsaKey(t, 3072), + ExpectedSignatureAlgorithm: x509.SHA384WithRSA, + }, + "RSA 4096": { + SignerKey: rsaKey(t, 4096), + ExpectedSignatureAlgorithm: x509.SHA512WithRSA, + }, + "RSA 8192": { + SignerKey: rsaKey(t, 8192), + ExpectedSignatureAlgorithm: x509.SHA512WithRSA, + }, + "RSA 1024 should error": { + SignerKey: rsaKey(t, 1024), + ExpectedSignatureAlgorithm: x509.UnknownSignatureAlgorithm, + ExpectErr: true, + }, + "ECDSA P-224 should error": { + SignerKey: ecdsaKey(t, elliptic.P224()), + ExpectedSignatureAlgorithm: x509.UnknownSignatureAlgorithm, + ExpectErr: true, + }, + "ECDSA P-256": { + SignerKey: ecdsaKey(t, elliptic.P256()), + ExpectedSignatureAlgorithm: x509.ECDSAWithSHA256, + }, + "ECDSA P-384": { + SignerKey: ecdsaKey(t, elliptic.P384()), + ExpectedSignatureAlgorithm: x509.ECDSAWithSHA384, + }, + "ECDSA P-521": { + SignerKey: ecdsaKey(t, elliptic.P521()), + ExpectedSignatureAlgorithm: x509.ECDSAWithSHA512, + }, + "Ed25519": { + SignerKey: ed25519Key(t), + ExpectedSignatureAlgorithm: x509.PureEd25519, + }, + } + + for name, spec := range specs { + t.Run(name, func(t *testing.T) { + t.Parallel() + + signerKey := spec.SignerKey + pub := signerKey.Public() + + tmpl := &x509.Certificate{ + PublicKey: pub, + Subject: pkix.Name{CommonName: "abc123"}, + + DNSNames: []string{"example.com"}, + } + + leafPriv := ed25519Key(t) + leafPub := leafPriv.Public() + + _, cert, err := SignCertificate(tmpl, tmpl, leafPub, signerKey) + if (err != nil) != spec.ExpectErr { + t.Errorf("failed to SignCertificate: %s", err) + } + + if spec.ExpectErr { + return + } + + if cert.SignatureAlgorithm != spec.ExpectedSignatureAlgorithm { + t.Errorf("wanted sigalg=%v but got %v", spec.ExpectedSignatureAlgorithm, cert.SignatureAlgorithm) + return + } + }) + } +} diff --git a/pkg/util/pki/generate.go b/pkg/util/pki/generate.go index 0d7421b0140..4b8b2335493 100644 --- a/pkg/util/pki/generate.go +++ b/pkg/util/pki/generate.go @@ -116,7 +116,6 @@ func GenerateECPrivateKey(keySize int) (*ecdsa.PrivateKey, error) { // GenerateEd25519PrivateKey will generate an Ed25519 private key func GenerateEd25519PrivateKey() (ed25519.PrivateKey, error) { - _, prvkey, err := ed25519.GenerateKey(rand.Reader) return prvkey, err diff --git a/pkg/controller/test/util.go b/pkg/util/pki/generate_keysize_test.go similarity index 53% rename from pkg/controller/test/util.go rename to pkg/util/pki/generate_keysize_test.go index 51bbc598ea1..d615c6120a4 100644 --- a/pkg/controller/test/util.go +++ b/pkg/util/pki/generate_keysize_test.go @@ -1,5 +1,5 @@ /* -Copyright 2020 The cert-manager Authors. +Copyright 2024 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. @@ -14,26 +14,14 @@ See the License for the specific language governing permissions and limitations under the License. */ -package test +package pki -import ( - "math/rand" - "time" -) +import "testing" -func init() { - rand.Seed(time.Now().UnixNano()) -} - -type StringGenerator func(n int) string - -const letterBytes = "abcdefghijklmnopqrstuvwxyz0123456789" - -// RandStringBytes generates a pseudo-random string of length `n`. -func RandStringBytes(n int) string { - b := make([]byte, n) - for i := range b { - b[i] = letterBytes[rand.Intn(len(letterBytes))] +func TestMaxRSAKeySizeSanity(t *testing.T) { + // This test ensures that if we bump our max RSA key size in the future, someone will come and + // re-check our assumptions about max PEM sizes. + if MaxRSAKeySize > 8192 { + t.Fatalf("MaxRSAKeySize has been increased which may invalidate the assumptions for safe PEM decoding in internal/pem/*.go\nCheck the max sizes and change this test once complete!") } - return string(b) } diff --git a/pkg/util/pki/generate_test.go b/pkg/util/pki/generate_test.go index 99161e963cc..d32ea0ca6e0 100644 --- a/pkg/util/pki/generate_test.go +++ b/pkg/util/pki/generate_test.go @@ -25,16 +25,20 @@ import ( "crypto/rsa" "crypto/x509" "crypto/x509/pkix" - "encoding/pem" "fmt" "strings" "testing" "time" + "github.com/cert-manager/cert-manager/internal/pem" v1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" ) func buildCertificateWithKeyParams(keyAlgo v1.PrivateKeyAlgorithm, keySize int) *v1.Certificate { + return buildCertificateWithKeyAndSigParams(keyAlgo, keySize, "") +} + +func buildCertificateWithKeyAndSigParams(keyAlgo v1.PrivateKeyAlgorithm, keySize int, sigAlg v1.SignatureAlgorithm) *v1.Certificate { return &v1.Certificate{ Spec: v1.CertificateSpec{ CommonName: "test", @@ -43,6 +47,7 @@ func buildCertificateWithKeyParams(keyAlgo v1.PrivateKeyAlgorithm, keySize int) Algorithm: keyAlgo, Size: keySize, }, + SignatureAlgorithm: sigAlg, }, } } @@ -219,7 +224,7 @@ func TestGeneratePrivateKeyForCertificate(t *testing.T) { curve, err := ecCurveForKeySize(test.keySize) if err != nil { - t.Errorf(err.Error()) + t.Error(err) return } @@ -249,15 +254,8 @@ func TestGeneratePrivateKeyForCertificate(t *testing.T) { func signTestCert(key crypto.Signer) *x509.Certificate { commonName := "testingcert" - serialNumber, err := rand.Int(rand.Reader, serialNumberLimit) - if err != nil { - panic(fmt.Errorf("failed to generate serial number: %s", err.Error())) - } - template := &x509.Certificate{ - Version: 2, BasicConstraintsValid: true, - SerialNumber: serialNumber, SignatureAlgorithm: x509.SHA256WithRSA, Subject: pkix.Name{ Organization: []string{"cert-manager"}, @@ -318,6 +316,7 @@ func TestPublicKeyMatchesCertificateRequest(t *testing.T) { } template := &x509.CertificateRequest{ + Version: 0, // SignatureAlgorithm: sigAlgo, Subject: pkix.Name{ CommonName: "cn", @@ -422,11 +421,16 @@ O7WnDn8nuLFdW+NzzbIrTw== testFn := func(test testT) func(*testing.T) { return func(t *testing.T) { - block, _ := pem.Decode(privateKeyBytes) + block, _, err := pem.SafeDecodePrivateKey(privateKeyBytes) + if err != nil { + t.Fatalf("expected no PEM decode err but got %s", err) + } + decodedKey, err := x509.ParsePKCS8PrivateKey(block.Bytes) if err != nil { t.Fatal(err) } + encodedKey, err := EncodePrivateKey(decodedKey, test.keyEncoding) if test.expectErr { if err == nil { @@ -448,7 +452,10 @@ O7WnDn8nuLFdW+NzzbIrTw== expectedEncoding := test.keyEncoding actualEncoding := v1.PrivateKeyEncoding("") - block, _ := pem.Decode(encodedKey) + block, _, err := pem.SafeDecodePrivateKey(encodedKey) + if err != nil { + t.Fatalf("expected no PEM decode err but got %s", err) + } switch block.Type { case "PRIVATE KEY": diff --git a/pkg/util/pki/keyusage.go b/pkg/util/pki/keyusage.go index 37f414401d1..78c7b3d2b43 100644 --- a/pkg/util/pki/keyusage.go +++ b/pkg/util/pki/keyusage.go @@ -20,6 +20,7 @@ import ( "crypto/x509" "crypto/x509/pkix" "encoding/asn1" + "errors" ) // Copied from x509.go @@ -106,7 +107,7 @@ func asn1BitLength(bitString []byte) int { for i := range bitString { b := bitString[len(bitString)-i-1] - for bit := uint(0); bit < 8; bit++ { + for bit := range uint(8) { if (b>>bit)&1 == 1 { return bitLen } @@ -126,10 +127,9 @@ func reverseBitsInAByte(in byte) byte { } // Adapted from x509.go -func buildASN1KeyUsageRequest(usage x509.KeyUsage) (pkix.Extension, error) { - OIDExtensionKeyUsage := pkix.Extension{ - Id: OIDExtensionKeyUsage, - } +func MarshalKeyUsage(usage x509.KeyUsage) (pkix.Extension, error) { + ext := pkix.Extension{Id: OIDExtensionKeyUsage, Critical: true} + var a [2]byte a[0] = reverseBitsInAByte(byte(usage)) a[1] = reverseBitsInAByte(byte(usage >> 8)) @@ -141,10 +141,67 @@ func buildASN1KeyUsageRequest(usage x509.KeyUsage) (pkix.Extension, error) { bitString := a[:l] var err error - OIDExtensionKeyUsage.Value, err = asn1.Marshal(asn1.BitString{Bytes: bitString, BitLength: asn1BitLength(bitString)}) - if err != nil { - return pkix.Extension{}, err + ext.Value, err = asn1.Marshal(asn1.BitString{Bytes: bitString, BitLength: asn1BitLength(bitString)}) + return ext, err +} + +func UnmarshalKeyUsage(value []byte) (usage x509.KeyUsage, err error) { + var asn1bits asn1.BitString + var rest []byte + + if rest, err = asn1.Unmarshal(value, &asn1bits); err != nil { + return usage, err + } else if len(rest) != 0 { + return usage, errors.New("x509: trailing data after X.509 KeyUsage") + } + + var usageInt int + for i := range 9 { + if asn1bits.At(i) != 0 { + usageInt |= 1 << uint(i) // #nosec G115 -- gosec can somehow not detect that this is safe + } + } + + return x509.KeyUsage(usageInt), nil +} + +// Adapted from x509.go +func MarshalExtKeyUsage(extUsages []x509.ExtKeyUsage, unknownUsages []asn1.ObjectIdentifier) (pkix.Extension, error) { + ext := pkix.Extension{Id: OIDExtensionExtendedKeyUsage} + + oids := make([]asn1.ObjectIdentifier, len(extUsages)+len(unknownUsages)) + for i, u := range extUsages { + if oid, ok := OIDFromExtKeyUsage(u); ok { + oids[i] = oid + } else { + return ext, errors.New("x509: unknown extended key usage") + } + } + + copy(oids[len(extUsages):], unknownUsages) + + var err error + ext.Value, err = asn1.Marshal(oids) + return ext, err +} + +func UnmarshalExtKeyUsage(value []byte) (extUsages []x509.ExtKeyUsage, unknownUsages []asn1.ObjectIdentifier, err error) { + var asn1ExtendedUsages []asn1.ObjectIdentifier + var rest []byte + + if rest, err = asn1.Unmarshal(value, &asn1ExtendedUsages); err != nil { + return extUsages, unknownUsages, err + } else if len(rest) != 0 { + return extUsages, unknownUsages, errors.New("x509: trailing data after X.509 ExtendedKeyUsage") + } + + for _, asnExtUsage := range asn1ExtendedUsages { + if eku, ok := ExtKeyUsageFromOID(asnExtUsage); ok { + extUsages = append(extUsages, eku) + } else { + unknownUsages = append(unknownUsages, asnExtUsage) + } } - return OIDExtensionKeyUsage, nil + return extUsages, unknownUsages, nil } diff --git a/pkg/util/pki/kube.go b/pkg/util/pki/kube.go index d2bec20ecf0..a7987a906d8 100644 --- a/pkg/util/pki/kube.go +++ b/pkg/util/pki/kube.go @@ -28,24 +28,6 @@ import ( experimentalapi "github.com/cert-manager/cert-manager/pkg/apis/experimental/v1alpha1" ) -// GenerateTemplateFromCertificateSigningRequest will create an -// *x509.Certificate from the given CertificateSigningRequest resource -func GenerateTemplateFromCertificateSigningRequest(csr *certificatesv1.CertificateSigningRequest) (*x509.Certificate, error) { - duration, err := DurationFromCertificateSigningRequest(csr) - if err != nil { - return nil, err - } - - ku, eku, err := BuildKeyUsagesKube(csr.Spec.Usages) - if err != nil { - return nil, err - } - - isCA := csr.Annotations[experimentalapi.CertificateSigningRequestIsCAAnnotationKey] == "true" - - return GenerateTemplateFromCSRPEMWithUsages(csr.Spec.Request, duration, isCA, ku, eku) -} - // DurationFromCertificateSigningRequest returns the duration that the user may // have requested using the annotation // "experimental.cert-manager.io/request-duration" or via the CSR diff --git a/pkg/util/pki/kube_test.go b/pkg/util/pki/kube_test.go index cd3b60d2cd8..85465e045bb 100644 --- a/pkg/util/pki/kube_test.go +++ b/pkg/util/pki/kube_test.go @@ -19,6 +19,7 @@ package pki_test import ( "crypto/x509" "crypto/x509/pkix" + "encoding/asn1" "math" "testing" "time" @@ -30,12 +31,27 @@ import ( "github.com/cert-manager/cert-manager/test/unit/gen" ) -func TestGenerateTemplateFromCertificateSigningRequest(t *testing.T) { +func TestCertificateTemplateFromCertificateSigningRequest(t *testing.T) { csr, pk, err := gen.CSR(x509.RSA, gen.SetCSRCommonName("example.com"), gen.SetCSRDNSNames("example.com", "foo.example.com")) if err != nil { t.Fatal(err) } + sansGenerator := func(t *testing.T, generalNames []asn1.RawValue, critical bool) pkix.Extension { + var oidExtensionSubjectAltName = []int{2, 5, 29, 17} + + val, err := asn1.Marshal(generalNames) + if err != nil { + t.Fatal(err) + } + + return pkix.Extension{ + Id: oidExtensionSubjectAltName, + Critical: critical, + Value: val, + } + } + tests := map[string]struct { csr *certificatesv1.CertificateSigningRequest expCertificate *x509.Certificate @@ -79,9 +95,7 @@ func TestGenerateTemplateFromCertificateSigningRequest(t *testing.T) { gen.SetCertificateSigningRequestRequest(csr), ), expCertificate: &x509.Certificate{ - Version: 2, BasicConstraintsValid: true, - SerialNumber: nil, PublicKeyAlgorithm: x509.RSA, PublicKey: pk.Public(), IsCA: true, @@ -96,6 +110,18 @@ func TestGenerateTemplateFromCertificateSigningRequest(t *testing.T) { x509.ExtKeyUsageCodeSigning, }, DNSNames: []string{"example.com", "foo.example.com"}, + Extensions: []pkix.Extension{ + sansGenerator(t, []asn1.RawValue{ + {Tag: 2, Class: 2, Bytes: []byte("example.com")}, + {Tag: 2, Class: 2, Bytes: []byte("foo.example.com")}, + }, false), + }, + ExtraExtensions: []pkix.Extension{ + sansGenerator(t, []asn1.RawValue{ + {Tag: 2, Class: 2, Bytes: []byte("example.com")}, + {Tag: 2, Class: 2, Bytes: []byte("foo.example.com")}, + }, false), + }, }, }, "a CSR with isCA=false that is valid should return a valid *x509.Certificate": { @@ -112,9 +138,7 @@ func TestGenerateTemplateFromCertificateSigningRequest(t *testing.T) { gen.SetCertificateSigningRequestRequest(csr), ), expCertificate: &x509.Certificate{ - Version: 2, BasicConstraintsValid: true, - SerialNumber: nil, PublicKeyAlgorithm: x509.RSA, PublicKey: pk.Public(), IsCA: false, @@ -129,6 +153,18 @@ func TestGenerateTemplateFromCertificateSigningRequest(t *testing.T) { x509.ExtKeyUsageCodeSigning, }, DNSNames: []string{"example.com", "foo.example.com"}, + Extensions: []pkix.Extension{ + sansGenerator(t, []asn1.RawValue{ + {Tag: 2, Class: 2, Bytes: []byte("example.com")}, + {Tag: 2, Class: 2, Bytes: []byte("foo.example.com")}, + }, false), + }, + ExtraExtensions: []pkix.Extension{ + sansGenerator(t, []asn1.RawValue{ + {Tag: 2, Class: 2, Bytes: []byte("example.com")}, + {Tag: 2, Class: 2, Bytes: []byte("foo.example.com")}, + }, false), + }, }, }, "a CSR with expiration seconds that is valid should return a valid *x509.Certificate": { @@ -145,9 +181,7 @@ func TestGenerateTemplateFromCertificateSigningRequest(t *testing.T) { gen.SetCertificateSigningRequestRequest(csr), ), expCertificate: &x509.Certificate{ - Version: 2, BasicConstraintsValid: true, - SerialNumber: nil, PublicKeyAlgorithm: x509.RSA, PublicKey: pk.Public(), IsCA: false, @@ -162,6 +196,18 @@ func TestGenerateTemplateFromCertificateSigningRequest(t *testing.T) { x509.ExtKeyUsageCodeSigning, }, DNSNames: []string{"example.com", "foo.example.com"}, + Extensions: []pkix.Extension{ + sansGenerator(t, []asn1.RawValue{ + {Tag: 2, Class: 2, Bytes: []byte("example.com")}, + {Tag: 2, Class: 2, Bytes: []byte("foo.example.com")}, + }, false), + }, + ExtraExtensions: []pkix.Extension{ + sansGenerator(t, []asn1.RawValue{ + {Tag: 2, Class: 2, Bytes: []byte("example.com")}, + {Tag: 2, Class: 2, Bytes: []byte("foo.example.com")}, + }, false), + }, }, }, "a CSR with expiration seconds and duration annotation should prefer the annotation duration": { @@ -179,9 +225,7 @@ func TestGenerateTemplateFromCertificateSigningRequest(t *testing.T) { gen.SetCertificateSigningRequestRequest(csr), ), expCertificate: &x509.Certificate{ - Version: 2, BasicConstraintsValid: true, - SerialNumber: nil, PublicKeyAlgorithm: x509.RSA, PublicKey: pk.Public(), IsCA: false, @@ -196,13 +240,25 @@ func TestGenerateTemplateFromCertificateSigningRequest(t *testing.T) { x509.ExtKeyUsageCodeSigning, }, DNSNames: []string{"example.com", "foo.example.com"}, + Extensions: []pkix.Extension{ + sansGenerator(t, []asn1.RawValue{ + {Tag: 2, Class: 2, Bytes: []byte("example.com")}, + {Tag: 2, Class: 2, Bytes: []byte("foo.example.com")}, + }, false), + }, + ExtraExtensions: []pkix.Extension{ + sansGenerator(t, []asn1.RawValue{ + {Tag: 2, Class: 2, Bytes: []byte("example.com")}, + {Tag: 2, Class: 2, Bytes: []byte("foo.example.com")}, + }, false), + }, }, }, } for name, test := range tests { t.Run(name, func(t *testing.T) { - templ, err := pki.GenerateTemplateFromCertificateSigningRequest(test.csr) + templ, err := pki.CertificateTemplateFromCertificateSigningRequest(test.csr) assert.Equal(t, test.expErr, err != nil) if err == nil { @@ -218,7 +274,6 @@ func TestGenerateTemplateFromCertificateSigningRequest(t *testing.T) { test.expCertificate.NotBefore = time.Time{} templ.NotAfter = time.Time{} templ.NotBefore = time.Time{} - templ.SerialNumber = nil templ.Subject.Names = nil templ.RawSubject = nil diff --git a/pkg/util/pki/match.go b/pkg/util/pki/match.go new file mode 100644 index 00000000000..df26a0df12e --- /dev/null +++ b/pkg/util/pki/match.go @@ -0,0 +1,398 @@ +/* +Copyright 2020 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. +*/ + +package pki + +import ( + "bytes" + "crypto" + "crypto/ecdsa" + "crypto/ed25519" + "crypto/rsa" + "crypto/x509" + "crypto/x509/pkix" + "encoding/asn1" + "fmt" + "net" + + "k8s.io/apimachinery/pkg/util/sets" + + "github.com/cert-manager/cert-manager/pkg/apis/certmanager" + cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" + "github.com/cert-manager/cert-manager/pkg/util" +) + +// PrivateKeyMatchesSpec returns a list of violations for the provided private +// key against the provided CertificateSpec. It will return an empty list/ nil +// if there are no violations found. RSA, Ed25519 and ECDSA private keys are +// supported. +// The function panics if the CertificateSpec contains an unknown key algorithm, +// since this should have been caught by the CertificateSpec validation already. +func PrivateKeyMatchesSpec(pk crypto.PrivateKey, spec cmapi.CertificateSpec) []string { + spec = *spec.DeepCopy() + if spec.PrivateKey == nil { + spec.PrivateKey = &cmapi.CertificatePrivateKey{} + } + switch spec.PrivateKey.Algorithm { + case "", cmapi.RSAKeyAlgorithm: + return rsaPrivateKeyMatchesSpec(pk, spec) + case cmapi.Ed25519KeyAlgorithm: + return ed25519PrivateKeyMatchesSpec(pk) + case cmapi.ECDSAKeyAlgorithm: + return ecdsaPrivateKeyMatchesSpec(pk, spec) + default: + // This should never happen as the CertificateSpec validation should + // catch this before it reaches this point. + panic(fmt.Sprintf("[PROGRAMMING ERROR] unrecognised key algorithm type %q", spec.PrivateKey.Algorithm)) + } +} + +func rsaPrivateKeyMatchesSpec(pk crypto.PrivateKey, spec cmapi.CertificateSpec) []string { + rsaPk, ok := pk.(*rsa.PrivateKey) + if !ok { + return []string{"spec.privateKey.algorithm"} + } + var violations []string + // TODO: we should not use implicit defaulting here, and instead rely on + // defaulting performed within the Kubernetes apiserver here. + // This requires careful handling in order to not interrupt users upgrading + // from older versions. + // The default RSA keySize is set to 2048. + keySize := MinRSAKeySize + if spec.PrivateKey.Size > 0 { + keySize = spec.PrivateKey.Size + } + if rsaPk.N.BitLen() != keySize { + violations = append(violations, "spec.privateKey.size") + } + return violations +} + +func ecdsaPrivateKeyMatchesSpec(pk crypto.PrivateKey, spec cmapi.CertificateSpec) []string { + ecdsaPk, ok := pk.(*ecdsa.PrivateKey) + if !ok { + return []string{"spec.privateKey.algorithm"} + } + var violations []string + // TODO: we should not use implicit defaulting here, and instead rely on + // defaulting performed within the Kubernetes apiserver here. + // This requires careful handling in order to not interrupt users upgrading + // from older versions. + // The default EC curve type is EC256 + expectedKeySize := ECCurve256 + if spec.PrivateKey.Size > 0 { + expectedKeySize = spec.PrivateKey.Size + } + if expectedKeySize != ecdsaPk.Curve.Params().BitSize { + violations = append(violations, "spec.privateKey.size") + } + return violations +} + +func ed25519PrivateKeyMatchesSpec(pk crypto.PrivateKey) []string { + _, ok := pk.(ed25519.PrivateKey) + if !ok { + return []string{"spec.privateKey.algorithm"} + } + + return nil +} + +func ipSlicesMatch(parsedIPs []net.IP, stringIPs []string) bool { + parsedStringIPs := make([]net.IP, len(stringIPs)) + + for i, s := range stringIPs { + parsedStringIPs[i] = net.ParseIP(s) + } + + return util.EqualIPsUnsorted(parsedStringIPs, parsedIPs) +} + +// RequestMatchesSpec compares a CertificateRequest with a CertificateSpec +// and returns a list of field names on the Certificate that do not match their +// counterpart fields on the CertificateRequest. +// If decoding the x509 certificate request fails, an error will be returned. +func RequestMatchesSpec(req *cmapi.CertificateRequest, spec cmapi.CertificateSpec) ([]string, error) { + x509req, err := DecodeX509CertificateRequestBytes(req.Spec.Request) + if err != nil { + return nil, err + } + + // It is safe to mutate top-level fields in `spec` as it is not a pointer + // meaning changes will not affect the caller. + if spec.Subject == nil { + spec.Subject = &cmapi.X509Subject{} + } + + var violations []string + + if !ipSlicesMatch(x509req.IPAddresses, spec.IPAddresses) { + violations = append(violations, "spec.ipAddresses") + } + + if !util.EqualUnsorted(URLsToString(x509req.URIs), spec.URIs) { + violations = append(violations, "spec.uris") + } + + if !util.EqualUnsorted(x509req.EmailAddresses, spec.EmailAddresses) { + violations = append(violations, "spec.emailAddresses") + } + + if !util.EqualUnsorted(x509req.DNSNames, spec.DNSNames) { + violations = append(violations, "spec.dnsNames") + } + + if spec.OtherNames != nil { + matched, err := matchOtherNames(x509req.Extensions, spec.OtherNames) + if err != nil { + return nil, err + } + if !matched { + violations = append(violations, "spec.otherNames") + } + } + + if spec.LiteralSubject == "" { + // Comparing Subject fields + if x509req.Subject.CommonName != spec.CommonName { + violations = append(violations, "spec.commonName") + } + if x509req.Subject.SerialNumber != spec.Subject.SerialNumber { + violations = append(violations, "spec.subject.serialNumber") + } + if !util.EqualUnsorted(x509req.Subject.Organization, spec.Subject.Organizations) { + violations = append(violations, "spec.subject.organizations") + } + if !util.EqualUnsorted(x509req.Subject.Country, spec.Subject.Countries) { + violations = append(violations, "spec.subject.countries") + } + if !util.EqualUnsorted(x509req.Subject.Locality, spec.Subject.Localities) { + violations = append(violations, "spec.subject.localities") + } + if !util.EqualUnsorted(x509req.Subject.OrganizationalUnit, spec.Subject.OrganizationalUnits) { + violations = append(violations, "spec.subject.organizationalUnits") + } + if !util.EqualUnsorted(x509req.Subject.PostalCode, spec.Subject.PostalCodes) { + violations = append(violations, "spec.subject.postCodes") + } + if !util.EqualUnsorted(x509req.Subject.Province, spec.Subject.Provinces) { + violations = append(violations, "spec.subject.provinces") + } + if !util.EqualUnsorted(x509req.Subject.StreetAddress, spec.Subject.StreetAddresses) { + violations = append(violations, "spec.subject.streetAddresses") + } + + } else { + // we have a LiteralSubject, generate the RDNSequence and encode it to compare + // with the request's subject + + rdnSequenceFromCertificate, err := UnmarshalSubjectStringToRDNSequence(spec.LiteralSubject) + if err != nil { + return nil, err + } + + asn1Sequence, err := asn1.Marshal(rdnSequenceFromCertificate) + if err != nil { + return nil, err + } + + if !bytes.Equal(x509req.RawSubject, asn1Sequence) { + violations = append(violations, "spec.literalSubject") + } + } + + if req.Spec.IsCA != spec.IsCA { + violations = append(violations, "spec.isCA") + } + if !util.EqualKeyUsagesUnsorted(req.Spec.Usages, spec.Usages) { + violations = append(violations, "spec.usages") + } + if req.Spec.Duration != nil && spec.Duration != nil && + req.Spec.Duration.Duration != spec.Duration.Duration { + violations = append(violations, "spec.duration") + } + // RequestMatchesSpec compares the IssuerRef in the CertificateRequest and + // CertificateSpec, regardless of any differences which are solely due to + // the presence or absence of default group (cert-manager.io) and kind (Issuer). + // + // We do not want to re-issue the Certificate if the user explicitly adds + // the default issuer group and kind. + // Nor do we want to re-issue if the user removes the default issuer group and kind. + // + // And we want to avoid re-issuing if a future version of the cert-manager + // CRDs introduces API defaults for issuerRef group and kind. Specifically, + // we want to gracefully handle a situation where the platform admin + // upgrades the CRDs to a version that has defaults, but not the controller. + // In that situation, when the CRDs are upgraded, the controller + // re-establishes its watches and refreshes its caches with updated Certificates + // and CertificateRequests, containing the new API defaults. But this + // doesn't happen transactionally, so the updated Certificates may start + // being reconciled before the cached CertificateRequests have been updated + // and there will be a mis-match if the Certificate has the default + // group/kind set but the CertificateRequest does not. + if req.Spec.IssuerRef.Name != spec.IssuerRef.Name || + !issuerKindsEqual(req.Spec.IssuerRef.Kind, spec.IssuerRef.Kind) || + !issuerGroupsEqual(req.Spec.IssuerRef.Group, spec.IssuerRef.Group) { + violations = append(violations, "spec.issuerRef") + } + + // TODO: check spec.EncodeBasicConstraintsInRequest and spec.EncodeUsagesInRequest + + return violations, nil +} + +// These defaults are also applied at runtime by the cert-manager +// CertificateRequest controller. +const ( + // defaultIssuerKind is the default value for an IssuerRef's kind field + // if it is not specified. + defaultIssuerKind = cmapi.IssuerKind + // defaultIssuerGroup is the default value for an IssuerRef's group field + // if it is not specified. + defaultIssuerGroup = certmanager.GroupName +) + +// issuerKindsEqual returns true if the two issuer reference kinds are equal, +// taking into account the defaulting of the kind to "Issuer". +func issuerKindsEqual(l, r string) bool { + if l == "" { + l = defaultIssuerKind + } + if r == "" { + r = defaultIssuerKind + } + return l == r +} + +// issuerGroupsEqual returns true if the two issuer reference groups are equal, +// taking into account defaulting of the group to "cert-manager.io". +func issuerGroupsEqual(l, r string) bool { + if l == "" { + l = defaultIssuerGroup + } + if r == "" { + r = defaultIssuerGroup + } + return l == r +} + +func matchOtherNames(extension []pkix.Extension, specOtherNames []cmapi.OtherName) (bool, error) { + x509SANExtension, err := extractSANExtension(extension) + if err != nil { + return false, nil //nolint:nilerr + } + + x509GeneralNames, err := UnmarshalSANs(x509SANExtension.Value) + if err != nil { + return false, err + } + + x509OtherNames := make([]cmapi.OtherName, 0, len(x509GeneralNames.OtherNames)) + for _, otherName := range x509GeneralNames.OtherNames { + + var otherNameInnerValue asn1.RawValue + // We have to perform one more level of unwrapping because value is still context specific class + // tagged 0 + _, err := asn1.Unmarshal(otherName.Value.Bytes, &otherNameInnerValue) + if err != nil { + return false, err + } + + uv, err := UnmarshalUniversalValue(otherNameInnerValue) + if err != nil { + return false, err + } + + if uv.Type() != UniversalValueTypeUTF8String { + // This means the CertificateRequest's otherName was not an utf8 value + return false, fmt.Errorf("otherName is not an utf8 value, got: %v", uv.Type()) + } + + x509OtherNames = append(x509OtherNames, cmapi.OtherName{ + OID: otherName.TypeID.String(), + UTF8Value: uv.UTF8String, + }) + } + + if !util.EqualOtherNamesUnsorted(x509OtherNames, specOtherNames) { + return false, nil + } + + return true, nil +} + +// FuzzyX509AltNamesMatchSpec will compare a X509 Certificate to a CertificateSpec +// and return a list of 'violations' for any fields that do not match their counterparts. +// +// This is a purposely less comprehensive check than RequestMatchesSpec as some +// issuers override/force certain fields. +// +// Deprecated: This function is very fuzzy and makes too many assumptions about +// how the issuer maps a CSR to a certificate. We only keep it for backward compatibility +// reasons, but use other comparison functions when possible. +func FuzzyX509AltNamesMatchSpec(x509cert *x509.Certificate, spec cmapi.CertificateSpec) []string { + var violations []string + + // Perform a 'loose' check on the x509 certificate to determine if the + // commonName and dnsNames fields are up to date. + // This check allows names to move between the DNSNames and CommonName + // field freely in order to account for CAs behaviour of promoting DNSNames + // to be CommonNames or vice-versa. + expectedDNSNames := sets.New(spec.DNSNames...) + if spec.CommonName != "" { + expectedDNSNames.Insert(spec.CommonName) + } + allDNSNames := sets.New(x509cert.DNSNames...) + if x509cert.Subject.CommonName != "" { + allDNSNames.Insert(x509cert.Subject.CommonName) + } + if !allDNSNames.Equal(expectedDNSNames) { + // We know a mismatch occurred, so now determine which fields mismatched. + if (spec.CommonName != "" && !allDNSNames.Has(spec.CommonName)) || (x509cert.Subject.CommonName != "" && !expectedDNSNames.Has(x509cert.Subject.CommonName)) { + violations = append(violations, "spec.commonName") + } + + if !allDNSNames.HasAll(spec.DNSNames...) || !expectedDNSNames.HasAll(x509cert.DNSNames...) { + violations = append(violations, "spec.dnsNames") + } + } + + if !ipSlicesMatch(x509cert.IPAddresses, spec.IPAddresses) { + violations = append(violations, "spec.ipAddresses") + } + + if !util.EqualUnsorted(URLsToString(x509cert.URIs), spec.URIs) { + violations = append(violations, "spec.uris") + } + + if !util.EqualUnsorted(x509cert.EmailAddresses, spec.EmailAddresses) { + violations = append(violations, "spec.emailAddresses") + } + + return violations +} + +func extractSANExtension(extensions []pkix.Extension) (pkix.Extension, error) { + oidExtensionSubjectAltName := []int{2, 5, 29, 17} + + for _, extension := range extensions { + if extension.Id.Equal(oidExtensionSubjectAltName) { + return extension, nil + } + } + + return pkix.Extension{}, fmt.Errorf("SAN extension not present!") +} diff --git a/pkg/util/pki/match_test.go b/pkg/util/pki/match_test.go new file mode 100644 index 00000000000..0bdb2577863 --- /dev/null +++ b/pkg/util/pki/match_test.go @@ -0,0 +1,675 @@ +/* +Copyright 2020 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. +*/ + +package pki_test + +import ( + "crypto" + "crypto/rand" + "crypto/x509" + "encoding/asn1" + "reflect" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" + cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" + "github.com/cert-manager/cert-manager/pkg/util/pki" + "github.com/cert-manager/cert-manager/test/unit/gen" +) + +func mustGenerateRSA(t *testing.T, keySize int) crypto.PrivateKey { + pk, err := pki.GenerateRSAPrivateKey(keySize) + if err != nil { + t.Fatal(err) + } + return pk +} + +func mustGenerateECDSA(t *testing.T, keySize int) crypto.PrivateKey { + pk, err := pki.GenerateECPrivateKey(keySize) + if err != nil { + t.Fatal(err) + } + return pk +} + +func mustGenerateEd25519(t *testing.T) crypto.PrivateKey { + pk, err := pki.GenerateEd25519PrivateKey() + if err != nil { + t.Fatal(err) + } + return pk +} + +func TestPrivateKeyMatchesSpec(t *testing.T) { + tests := map[string]struct { + key crypto.PrivateKey + expectedAlgo cmapi.PrivateKeyAlgorithm + expectedSize int + violations []string + }{ + "should match if keySize and algorithm are correct (RSA)": { + key: mustGenerateRSA(t, 2048), + expectedAlgo: cmapi.RSAKeyAlgorithm, + expectedSize: 2048, + }, + "should not match if RSA keySize is incorrect": { + key: mustGenerateRSA(t, 2048), + expectedAlgo: cmapi.RSAKeyAlgorithm, + expectedSize: 4096, + violations: []string{"spec.privateKey.size"}, + }, + "should match if keySize and algorithm are correct (ECDSA)": { + key: mustGenerateECDSA(t, pki.ECCurve256), + expectedAlgo: cmapi.ECDSAKeyAlgorithm, + expectedSize: 256, + }, + "should not match if ECDSA keySize is incorrect": { + key: mustGenerateECDSA(t, pki.ECCurve256), + expectedAlgo: cmapi.ECDSAKeyAlgorithm, + expectedSize: pki.ECCurve521, + violations: []string{"spec.privateKey.size"}, + }, + "should not match if keyAlgorithm is incorrect": { + key: mustGenerateECDSA(t, pki.ECCurve256), + expectedAlgo: cmapi.RSAKeyAlgorithm, + expectedSize: 2048, + violations: []string{"spec.privateKey.algorithm"}, + }, + "should match if keySize and algorithm are correct (Ed25519)": { + key: mustGenerateEd25519(t), + expectedAlgo: cmapi.Ed25519KeyAlgorithm, + }, + } + for name, test := range tests { + t.Run(name, func(t *testing.T) { + violations := pki.PrivateKeyMatchesSpec( + test.key, + cmapi.CertificateSpec{ + PrivateKey: &cmapi.CertificatePrivateKey{ + Algorithm: test.expectedAlgo, + Size: test.expectedSize, + }, + }, + ) + if !reflect.DeepEqual(violations, test.violations) { + t.Errorf("violations did not match, got=%s, exp=%s", violations, test.violations) + } + }) + } +} + +func TestCertificateRequestOtherNamesMatchSpec(t *testing.T) { + tests := map[string]struct { + crSpec *cmapi.CertificateRequest + certSpec cmapi.CertificateSpec + err string + violations []string + }{ + "should not report any violation if Certificate otherName(s) match the CertificateRequest's": { + crSpec: mustBuildCertificateRequest(t, &cmapi.Certificate{Spec: cmapi.CertificateSpec{ + CommonName: "cn", + OtherNames: []cmapi.OtherName{ + { + OID: "1.3.6.1.4.1.311.20.2.3", + UTF8Value: "upn@testdomain.local", + }, + }, + }}), + certSpec: cmapi.CertificateSpec{ + CommonName: "cn", + OtherNames: []cmapi.OtherName{ + { + OID: "1.3.6.1.4.1.311.20.2.3", + UTF8Value: "upn@testdomain.local", + }, + }, + }, + err: "", + }, + "should report violation if Certificate otherName(s) mismatch the CertificateRequest's": { + crSpec: mustBuildCertificateRequest(t, &cmapi.Certificate{Spec: cmapi.CertificateSpec{ + CommonName: "cn", + OtherNames: []cmapi.OtherName{ + { + OID: "1.3.6.1.4.1.311.20.2.3", + UTF8Value: "upn@testdomain.local", + }, + }, + }}), + certSpec: cmapi.CertificateSpec{ + CommonName: "cn", + OtherNames: []cmapi.OtherName{ + { + OID: "1.3.6.1.4.1.311.20.2.3", + UTF8Value: "upn2@testdomain.local", + }, + }, + }, + err: "", + violations: []string{ + "spec.otherNames", + }, + }, + "should not report violation if Certificate otherName(s) match the CertificateRequest's (with different order)": { + crSpec: mustBuildCertificateRequest(t, &cmapi.Certificate{Spec: cmapi.CertificateSpec{ + CommonName: "cn", + OtherNames: []cmapi.OtherName{ + { + OID: "1.3.6.1.4.1.311.20.2.3", + UTF8Value: "anotherupn@testdomain.local", + }, + { + OID: "1.3.6.1.4.1.311.20.2.3", + UTF8Value: "upn@testdomain.local", + }, + }, + }}), + certSpec: cmapi.CertificateSpec{ + CommonName: "cn", + OtherNames: []cmapi.OtherName{ + { + OID: "1.3.6.1.4.1.311.20.2.3", + UTF8Value: "upn@testdomain.local", + }, + { + OID: "1.3.6.1.4.1.311.20.2.3", + UTF8Value: "anotherupn@testdomain.local", + }, + }, + }, + err: "", + }, + } + for name, test := range tests { + t.Run(name, func(t *testing.T) { + violations, err := pki.RequestMatchesSpec(test.crSpec, test.certSpec) + if err != nil { + if test.err == "" { + t.Errorf("Unexpected error: %s", err.Error()) + } else if test.err != err.Error() { + t.Errorf("Expected error: %s but got: %s instead", err.Error(), test.err) + } + } + + if !reflect.DeepEqual(violations, test.violations) { + t.Errorf("violations did not match, got=%s, exp=%s", violations, test.violations) + } + }) + } +} + +func TestRequestMatchesSpecSubject(t *testing.T) { + createCSRBlob := func(literalSubject string) []byte { + seq, err := pki.UnmarshalSubjectStringToRDNSequence(literalSubject) + if err != nil { + t.Fatal(err) + } + + asn1Seq, err := asn1.Marshal(seq) + if err != nil { + t.Fatal(err) + } + + pemBytes, _, err := gen.CSR(x509.Ed25519, func(cr *x509.CertificateRequest) error { + cr.RawSubject = asn1Seq + return nil + }) + if err != nil { + t.Fatal(err) + } + + return pemBytes + } + + tests := []struct { + name string + subject *cmapi.X509Subject + literalSubject string + x509CSR []byte + err string + violations []string + }{ + { + name: "Matching LiteralSubjects", + literalSubject: "CN=example.com,OU=example,O=example,L=example,ST=example,C=US", + x509CSR: createCSRBlob("CN=example.com,OU=example,O=example,L=example,ST=example,C=US"), + }, + { + name: "Matching LiteralSubjects", + literalSubject: "ST=example,C=US", + x509CSR: createCSRBlob("ST=example"), + violations: []string{"spec.literalSubject"}, + }, + { + name: "Matching LiteralSubjects", + literalSubject: "ST=example,C=US,O=#04024869", + x509CSR: createCSRBlob("ST=example,C=US,O=#04024869"), + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + violations, err := pki.RequestMatchesSpec( + &cmapi.CertificateRequest{ + Spec: cmapi.CertificateRequestSpec{ + Request: test.x509CSR, + }, + }, + cmapi.CertificateSpec{ + Subject: test.subject, + LiteralSubject: test.literalSubject, + }, + ) + if err != nil { + if test.err == "" { + t.Errorf("Unexpected error: %s", err.Error()) + } else if test.err != err.Error() { + t.Errorf("Expected error: %s but got: %s instead", err.Error(), test.err) + } + } + + if !reflect.DeepEqual(violations, test.violations) { + t.Errorf("violations did not match, got=%s, exp=%s", violations, test.violations) + } + }) + } +} + +// RequestMatchesSpecIssuerRef tests that RequestMatchesSpec correctly compares +// the IssuerRef in the CertificateRequest and CertificateSpec, regardless of +// any differences which are solely due to the presence or absence of default +// group and kind. +// +// For example, we do not want to re-issue the Certificate if the user +// explicitly adds the default issuer group and kind. Nor do we want to re-issue +// if the user removes the default issuer group and kind. +// +// And we want to avoid re-issuing if a future version of the cert-manager +// CRDs introduces API defaults for issuerRef group and kind. Specifically, +// we want to gracefully handle a situation where the platform admin +// upgrades the CRDs to a version that has defaults, but not the controller. +// In that situation, when the CRDs are upgraded, the controller +// re-establishes its watches and refreshes its caches with updated Certificates +// and CertificateRequests, containing the new API defaults. But this +// doesn't happen transactionally, so the updated Certificates may start +// being reconciled before the cached CertificateRequests have been updated +// and there will be a mis-match if the Certificate has the default +// group/kind set but the CertificateRequest does not. +func TestRequestMatchesSpecIssuerRef(t *testing.T) { + type testCase struct { + crSpec *cmapi.CertificateRequest + certSpec cmapi.CertificateSpec + err string + violations []string + } + + tests := map[string]testCase{ + "should not report any violation if Certificate issuerRef matches the CertificateRequest's": { + crSpec: mustBuildCertificateRequest(t, &cmapi.Certificate{Spec: cmapi.CertificateSpec{ + CommonName: "dummy-common-name", + IssuerRef: cmmeta.IssuerReference{ + Name: "test-issuer", + Kind: "Issuer", + Group: "cert-manager.io", + }, + }}), + certSpec: cmapi.CertificateSpec{ + CommonName: "dummy-common-name", + IssuerRef: cmmeta.IssuerReference{ + Name: "test-issuer", + Kind: "Issuer", + Group: "cert-manager.io", + }, + }, + err: "", + }, + "should not report any violation if both Certificate and CertificateRequest issuerRef Kind and Group are empty": { + crSpec: mustBuildCertificateRequest(t, &cmapi.Certificate{Spec: cmapi.CertificateSpec{ + CommonName: "dummy-common-name", + IssuerRef: cmmeta.IssuerReference{ + Name: "test-issuer", + }, + }}), + certSpec: cmapi.CertificateSpec{ + CommonName: "dummy-common-name", + IssuerRef: cmmeta.IssuerReference{ + Name: "test-issuer", + }, + }, + err: "", + }, + "should not report any violation if Certificate issuerRef Kind and Group are defaulted and CertificateRequest issuerRef Group is empty": { + crSpec: mustBuildCertificateRequest(t, &cmapi.Certificate{Spec: cmapi.CertificateSpec{ + CommonName: "dummy-common-name", + IssuerRef: cmmeta.IssuerReference{ + Name: "test-issuer", + Kind: "Issuer", + }, + }}), + certSpec: cmapi.CertificateSpec{ + CommonName: "dummy-common-name", + IssuerRef: cmmeta.IssuerReference{ + Name: "test-issuer", + Kind: "Issuer", + Group: "cert-manager.io", + }, + }, + err: "", + }, + "should not report any violation if Certificate issuerRef Kind and Group are defaulted and CertificateRequest issuerRef Kind is empty": { + crSpec: mustBuildCertificateRequest(t, &cmapi.Certificate{Spec: cmapi.CertificateSpec{ + CommonName: "dummy-common-name", + IssuerRef: cmmeta.IssuerReference{ + Name: "test-issuer", + Group: "cert-manager.io", + }, + }}), + certSpec: cmapi.CertificateSpec{ + CommonName: "dummy-common-name", + IssuerRef: cmmeta.IssuerReference{ + Name: "test-issuer", + Kind: "Issuer", + Group: "cert-manager.io", + }, + }, + err: "", + }, + "should report violation if Certificate issuerRef name mismatches the CertificateRequest's": { + crSpec: mustBuildCertificateRequest(t, &cmapi.Certificate{Spec: cmapi.CertificateSpec{ + CommonName: "dummy-common-name", + IssuerRef: cmmeta.IssuerReference{ + Name: "test-issuer", + Kind: "Issuer", + Group: "cert-manager.io", + }, + }}), + certSpec: cmapi.CertificateSpec{ + CommonName: "dummy-common-name", + IssuerRef: cmmeta.IssuerReference{ + Name: "different-issuer", + Kind: "Issuer", + Group: "cert-manager.io", + }, + }, + err: "", + violations: []string{"spec.issuerRef"}, + }, + "should not report any violation if Certificate issuerRef Kind and Group are defaulted and CertificateRequest's are empty": { + crSpec: mustBuildCertificateRequest(t, &cmapi.Certificate{Spec: cmapi.CertificateSpec{ + CommonName: "dummy-common-name", + IssuerRef: cmmeta.IssuerReference{ + Name: "test-issuer", + }, + }}), + certSpec: cmapi.CertificateSpec{ + CommonName: "dummy-common-name", + IssuerRef: cmmeta.IssuerReference{ + Name: "test-issuer", + Kind: "Issuer", + Group: "cert-manager.io", + }, + }, + err: "", + }, + "should report violation if Certificate issuerRef Kind mismatches the CertificateRequest's (defaulted vs non-defaulted)": { + crSpec: mustBuildCertificateRequest(t, &cmapi.Certificate{Spec: cmapi.CertificateSpec{ + CommonName: "dummy-common-name", + IssuerRef: cmmeta.IssuerReference{ + Name: "test-issuer", + Kind: "ClusterIssuer", + Group: "cert-manager.io", + }, + }}), + certSpec: cmapi.CertificateSpec{ + CommonName: "dummy-common-name", + IssuerRef: cmmeta.IssuerReference{ + Name: "test-issuer", + Kind: "Issuer", + Group: "cert-manager.io", + }, + }, + err: "", + violations: []string{"spec.issuerRef"}, + }, + "should report violation if Certificate issuerRef Group mismatches the CertificateRequest's (defaulted vs non-defaulted)": { + crSpec: mustBuildCertificateRequest(t, &cmapi.Certificate{Spec: cmapi.CertificateSpec{ + CommonName: "dummy-common-name", + IssuerRef: cmmeta.IssuerReference{ + Name: "test-issuer", + Kind: "Issuer", + Group: "different-group.io", + }, + }}), + certSpec: cmapi.CertificateSpec{ + CommonName: "dummy-common-name", + IssuerRef: cmmeta.IssuerReference{ + Name: "test-issuer", + Kind: "Issuer", + Group: "cert-manager.io", + }, + }, + err: "", + violations: []string{"spec.issuerRef"}, + }, + } + for name, test := range tests { + t.Run(name, func(t *testing.T) { + violations, err := pki.RequestMatchesSpec(test.crSpec, test.certSpec) + if test.err != "" { + assert.EqualError(t, err, test.err) + assert.Empty(t, violations) + return + } + require.NoError(t, err) + assert.Equal(t, test.violations, violations) + }) + } +} + +func TestFuzzyX509AltNamesMatchSpec(t *testing.T) { + tests := map[string]struct { + x509 *x509.Certificate + spec cmapi.CertificateSpec + violations []string + }{ + "should match if common name and dns names exactly equal": { + spec: cmapi.CertificateSpec{ + CommonName: "cn", + DNSNames: []string{"at", "least", "one"}, + }, + x509: selfSignCertificate(t, cmapi.CertificateSpec{ + CommonName: "cn", + DNSNames: []string{"at", "least", "one"}, + }), + }, + "should match if commonName is missing but is present in dnsNames": { + spec: cmapi.CertificateSpec{ + CommonName: "cn", + DNSNames: []string{"at", "least", "one"}, + }, + x509: selfSignCertificate(t, cmapi.CertificateSpec{ + DNSNames: []string{"cn", "at", "least", "one"}, + }), + }, + "should match if commonName is missing but is present in dnsNames (not first)": { + spec: cmapi.CertificateSpec{ + CommonName: "cn", + DNSNames: []string{"at", "least", "one"}, + }, + x509: selfSignCertificate(t, cmapi.CertificateSpec{ + DNSNames: []string{"at", "least", "one", "cn"}, + }), + }, + "should match if commonName is one of the requested dnsNames": { + spec: cmapi.CertificateSpec{ + DNSNames: []string{"at", "least", "one"}, + }, + x509: selfSignCertificate(t, cmapi.CertificateSpec{ + CommonName: "at", + DNSNames: []string{"least", "one"}, + }), + }, + "should not match if commonName is not present on certificate": { + spec: cmapi.CertificateSpec{ + CommonName: "cn", + DNSNames: []string{"at", "least", "one"}, + }, + x509: selfSignCertificate(t, cmapi.CertificateSpec{ + DNSNames: []string{"at", "least", "one"}, + }), + violations: []string{"spec.commonName"}, + }, + "should report violation for both commonName and dnsNames if both are missing": { + spec: cmapi.CertificateSpec{ + CommonName: "cn", + DNSNames: []string{"at", "least", "one", "other"}, + }, + x509: selfSignCertificate(t, cmapi.CertificateSpec{ + DNSNames: []string{"at", "least", "one"}, + }), + violations: []string{"spec.commonName", "spec.dnsNames"}, + }, + "should report violation for both commonName and dnsNames if not requested": { + spec: cmapi.CertificateSpec{ + DNSNames: []string{"at", "least", "one"}, + }, + x509: selfSignCertificate(t, cmapi.CertificateSpec{ + CommonName: "cn", + DNSNames: []string{"at", "least", "one", "other"}, + }), + violations: []string{"spec.commonName", "spec.dnsNames"}, + }, + "should not match if certificate has more dnsNames than spec": { + spec: cmapi.CertificateSpec{ + CommonName: "cn", + DNSNames: []string{"at", "least", "one"}, + }, + x509: selfSignCertificate(t, cmapi.CertificateSpec{ + CommonName: "cn", + DNSNames: []string{"at", "least", "one", "other"}, + }), + violations: []string{"spec.dnsNames"}, + }, + "should match if commonName is a duplicated dnsName (but not requested)": { + spec: cmapi.CertificateSpec{ + DNSNames: []string{"at", "least", "one"}, + }, + x509: selfSignCertificate(t, cmapi.CertificateSpec{ + CommonName: "at", + DNSNames: []string{"at", "least", "one"}, + }), + }, + "should match if commonName is a duplicated dnsName": { + spec: cmapi.CertificateSpec{ + CommonName: "cn", + DNSNames: []string{"at", "least", "one"}, + }, + x509: selfSignCertificate(t, cmapi.CertificateSpec{ + CommonName: "at", + DNSNames: []string{"at", "least", "one", "cn"}, + }), + }, + "should match if ipAddresses are equal": { + spec: cmapi.CertificateSpec{ + IPAddresses: []string{"127.0.0.1"}, + }, + x509: selfSignCertificate(t, cmapi.CertificateSpec{ + IPAddresses: []string{"127.0.0.1"}, + }), + }, + "should not match if ipAddresses are not equal": { + spec: cmapi.CertificateSpec{ + IPAddresses: []string{"127.0.0.1"}, + }, + x509: selfSignCertificate(t, cmapi.CertificateSpec{ + IPAddresses: []string{"127.0.2.1"}, + }), + violations: []string{"spec.ipAddresses"}, + }, + "should not match if ipAddresses has been made the commonName": { + spec: cmapi.CertificateSpec{ + IPAddresses: []string{"127.0.0.1"}, + }, + x509: selfSignCertificate(t, cmapi.CertificateSpec{ + CommonName: "127.0.0.1", + IPAddresses: []string{"127.0.0.1"}, + }), + violations: []string{"spec.commonName"}, + }, + } + for name, test := range tests { + t.Run(name, func(t *testing.T) { + violations := pki.FuzzyX509AltNamesMatchSpec(test.x509, test.spec) + if !reflect.DeepEqual(violations, test.violations) { + t.Errorf("violations did not match, got=%s, exp=%s", violations, test.violations) + } + }) + } +} + +func selfSignCertificate(t *testing.T, spec cmapi.CertificateSpec) *x509.Certificate { + template, err := pki.CertificateTemplateFromCertificate(&cmapi.Certificate{Spec: spec}) + if err != nil { + t.Fatal(err) + } + + pk, err := pki.GenerateRSAPrivateKey(2048) + if err != nil { + t.Fatal(err) + } + + // Marshal and unmarshal to ensure all fields are set correctly + certDER, err := x509.CreateCertificate(rand.Reader, template, template, pk.Public(), pk) + if err != nil { + t.Fatal(err) + } + + cert, err := x509.ParseCertificate(certDER) + if err != nil { + t.Fatal(err) + } + + return cert +} + +func mustBuildCertificateRequest(t *testing.T, crt *cmapi.Certificate) *cmapi.CertificateRequest { + pemData, _, err := gen.CSRForCertificate(crt) + if err != nil { + t.Fatal(err) + } + + cr := &cmapi.CertificateRequest{ + ObjectMeta: metav1.ObjectMeta{ + Name: t.Name(), + Annotations: crt.Annotations, + Labels: crt.Labels, + }, + Spec: cmapi.CertificateRequestSpec{ + Request: pemData, + Duration: crt.Spec.Duration, + IssuerRef: crt.Spec.IssuerRef, + IsCA: crt.Spec.IsCA, + Usages: crt.Spec.Usages, + }, + } + + return cr +} diff --git a/pkg/util/pki/nameconstraints.go b/pkg/util/pki/nameconstraints.go new file mode 100644 index 00000000000..8a7583de2f6 --- /dev/null +++ b/pkg/util/pki/nameconstraints.go @@ -0,0 +1,316 @@ +/* +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. +*/ + +package pki + +import ( + "crypto/x509/pkix" + "errors" + "fmt" + "net" + + "golang.org/x/crypto/cryptobyte" + cryptobyte_asn1 "golang.org/x/crypto/cryptobyte/asn1" +) + +// Copied from x509.go +var ( + OIDExtensionNameConstraints = []int{2, 5, 29, 30} +) + +// NameConstraints represents the NameConstraints extension. +type NameConstraints struct { + PermittedDNSDomains []string + ExcludedDNSDomains []string + PermittedIPRanges []*net.IPNet + ExcludedIPRanges []*net.IPNet + PermittedEmailAddresses []string + ExcludedEmailAddresses []string + PermittedURIDomains []string + ExcludedURIDomains []string +} + +func (nc NameConstraints) IsEmpty() bool { + return len(nc.PermittedDNSDomains) == 0 && + len(nc.PermittedIPRanges) == 0 && + len(nc.PermittedEmailAddresses) == 0 && + len(nc.PermittedURIDomains) == 0 && + len(nc.ExcludedDNSDomains) == 0 && + len(nc.ExcludedIPRanges) == 0 && + len(nc.ExcludedEmailAddresses) == 0 && + len(nc.ExcludedURIDomains) == 0 +} + +// Adapted from x509.go +func MarshalNameConstraints(nameConstraints *NameConstraints, critical bool) (pkix.Extension, error) { + ipAndMask := func(ipNet *net.IPNet) []byte { + maskedIP := ipNet.IP.Mask(ipNet.Mask) + ipAndMask := make([]byte, 0, len(maskedIP)+len(ipNet.Mask)) + ipAndMask = append(ipAndMask, maskedIP...) + ipAndMask = append(ipAndMask, ipNet.Mask...) + return ipAndMask + } + + serialiseConstraints := func(dns []string, ips []*net.IPNet, emails []string, uriDomains []string) (der []byte, err error) { + var b cryptobyte.Builder + + for _, name := range dns { + if err = isIA5String(name); err != nil { + return nil, err + } + + b.AddASN1(cryptobyte_asn1.SEQUENCE, func(b *cryptobyte.Builder) { + b.AddASN1(cryptobyte_asn1.Tag(2).ContextSpecific(), func(b *cryptobyte.Builder) { + b.AddBytes([]byte(name)) + }) + }) + } + + for _, ipNet := range ips { + b.AddASN1(cryptobyte_asn1.SEQUENCE, func(b *cryptobyte.Builder) { + b.AddASN1(cryptobyte_asn1.Tag(7).ContextSpecific(), func(b *cryptobyte.Builder) { + b.AddBytes(ipAndMask(ipNet)) + }) + }) + } + + for _, email := range emails { + if err = isIA5String(email); err != nil { + return nil, err + } + + b.AddASN1(cryptobyte_asn1.SEQUENCE, func(b *cryptobyte.Builder) { + b.AddASN1(cryptobyte_asn1.Tag(1).ContextSpecific(), func(b *cryptobyte.Builder) { + b.AddBytes([]byte(email)) + }) + }) + } + + for _, uriDomain := range uriDomains { + if err = isIA5String(uriDomain); err != nil { + return nil, err + } + + b.AddASN1(cryptobyte_asn1.SEQUENCE, func(b *cryptobyte.Builder) { + b.AddASN1(cryptobyte_asn1.Tag(6).ContextSpecific(), func(b *cryptobyte.Builder) { + b.AddBytes([]byte(uriDomain)) + }) + }) + } + + return b.Bytes() + } + + var permitted []byte + var err error + permitted, err = serialiseConstraints(nameConstraints.PermittedDNSDomains, nameConstraints.PermittedIPRanges, nameConstraints.PermittedEmailAddresses, nameConstraints.PermittedURIDomains) + if err != nil { + return pkix.Extension{}, err + } + + var excluded []byte + excluded, err = serialiseConstraints(nameConstraints.ExcludedDNSDomains, nameConstraints.ExcludedIPRanges, nameConstraints.ExcludedEmailAddresses, nameConstraints.ExcludedURIDomains) + if err != nil { + return pkix.Extension{}, err + } + + var b cryptobyte.Builder + b.AddASN1(cryptobyte_asn1.SEQUENCE, func(b *cryptobyte.Builder) { + if len(permitted) > 0 { + b.AddASN1(cryptobyte_asn1.Tag(0).ContextSpecific().Constructed(), func(b *cryptobyte.Builder) { + b.AddBytes(permitted) + }) + } + + if len(excluded) > 0 { + b.AddASN1(cryptobyte_asn1.Tag(1).ContextSpecific().Constructed(), func(b *cryptobyte.Builder) { + b.AddBytes(excluded) + }) + } + }) + + bytes, err := b.Bytes() + if err != nil { + return pkix.Extension{}, err + } + + return pkix.Extension{ + Id: OIDExtensionNameConstraints, + Critical: critical, + Value: bytes, + }, nil +} + +func parseCIDRs(cidrs []string) ([]*net.IPNet, error) { + ipRanges := []*net.IPNet{} + for _, cidr := range cidrs { + _, ipNet, err := net.ParseCIDR(cidr) + if err != nil { + return nil, err + } + ipRanges = append(ipRanges, &net.IPNet{ + IP: ipNet.IP, + Mask: ipNet.Mask, + }) + } + return ipRanges, nil +} + +// Adapted from crypto/x509/parser.go +func UnmarshalNameConstraints(value []byte) (*NameConstraints, error) { + // RFC 5280, 4.2.1.10 + + // NameConstraints ::= SEQUENCE { + // permittedSubtrees [0] GeneralSubtrees OPTIONAL, + // excludedSubtrees [1] GeneralSubtrees OPTIONAL } + // + // GeneralSubtrees ::= SEQUENCE SIZE (1..MAX) OF GeneralSubtree + // + // GeneralSubtree ::= SEQUENCE { + // base GeneralName, + // minimum [0] BaseDistance DEFAULT 0, + // maximum [1] BaseDistance OPTIONAL } + // + // BaseDistance ::= INTEGER (0..MAX) + + outer := cryptobyte.String(value) + var toplevel, permitted, excluded cryptobyte.String + var havePermitted, haveExcluded bool + if !outer.ReadASN1(&toplevel, cryptobyte_asn1.SEQUENCE) || + !outer.Empty() || + !toplevel.ReadOptionalASN1(&permitted, &havePermitted, cryptobyte_asn1.Tag(0).ContextSpecific().Constructed()) || + !toplevel.ReadOptionalASN1(&excluded, &haveExcluded, cryptobyte_asn1.Tag(1).ContextSpecific().Constructed()) || + !toplevel.Empty() { + return nil, errors.New("x509: invalid NameConstraints extension") + } + + if !havePermitted && !haveExcluded || len(permitted) == 0 && len(excluded) == 0 { + // From RFC 5280, Section 4.2.1.10: + // “either the permittedSubtrees field + // or the excludedSubtrees MUST be + // present” + return nil, errors.New("x509: empty name constraints extension") + } + + getValues := func(subtrees cryptobyte.String) (dnsNames []string, ips []*net.IPNet, emails, uriDomains []string, err error) { + for !subtrees.Empty() { + var seq, value cryptobyte.String + var tag cryptobyte_asn1.Tag + if !subtrees.ReadASN1(&seq, cryptobyte_asn1.SEQUENCE) || + !seq.ReadAnyASN1(&value, &tag) { + return nil, nil, nil, nil, fmt.Errorf("x509: invalid NameConstraints extension") + } + + var ( + dnsTag = cryptobyte_asn1.Tag(2).ContextSpecific() + emailTag = cryptobyte_asn1.Tag(1).ContextSpecific() + ipTag = cryptobyte_asn1.Tag(7).ContextSpecific() + uriTag = cryptobyte_asn1.Tag(6).ContextSpecific() + ) + + switch tag { + case dnsTag: + domain := string(value) + if err := isIA5String(domain); err != nil { + return nil, nil, nil, nil, errors.New("x509: invalid constraint value: " + err.Error()) + } + + dnsNames = append(dnsNames, domain) + + case ipTag: + l := len(value) + var ip, mask []byte + + switch l { + case 2 * net.IPv4len: + ip = value[:net.IPv4len] + mask = value[net.IPv4len:] + + case 2 * net.IPv6len: + ip = value[:net.IPv6len] + mask = value[net.IPv6len:] + + default: + return nil, nil, nil, nil, fmt.Errorf("x509: IP constraint contained value of length %d", l) + } + + if !isValidIPMask(mask) { + return nil, nil, nil, nil, fmt.Errorf("x509: IP constraint contained invalid mask %x", mask) + } + + ips = append(ips, &net.IPNet{IP: net.IP(ip), Mask: net.IPMask(mask)}) + + case emailTag: + constraint := string(value) + if err := isIA5String(constraint); err != nil { + return nil, nil, nil, nil, errors.New("x509: invalid constraint value: " + err.Error()) + } + + emails = append(emails, constraint) + + case uriTag: + domain := string(value) + if err := isIA5String(domain); err != nil { + return nil, nil, nil, nil, errors.New("x509: invalid constraint value: " + err.Error()) + } + + uriDomains = append(uriDomains, domain) + + default: + return nil, nil, nil, nil, fmt.Errorf("x509: unsupported NameConstraints tag: %v", tag) + } + } + + return dnsNames, ips, emails, uriDomains, nil + } + + out := &NameConstraints{} + + var err error + if out.PermittedDNSDomains, out.PermittedIPRanges, out.PermittedEmailAddresses, out.PermittedURIDomains, err = getValues(permitted); err != nil { + return nil, err + } + if out.ExcludedDNSDomains, out.ExcludedIPRanges, out.ExcludedEmailAddresses, out.ExcludedURIDomains, err = getValues(excluded); err != nil { + return nil, err + } + + return out, nil +} + +// isValidIPMask reports whether mask consists of zero or more 1 bits, followed by zero bits. +func isValidIPMask(mask []byte) bool { + seenZero := false + + for _, b := range mask { + if seenZero { + if b != 0 { + return false + } + + continue + } + + switch b { + case 0x00, 0x80, 0xc0, 0xe0, 0xf0, 0xf8, 0xfc, 0xfe: + seenZero = true + case 0xff: + default: + return false + } + } + + return true +} diff --git a/pkg/util/pki/nameconstraints_test.go b/pkg/util/pki/nameconstraints_test.go new file mode 100644 index 00000000000..e7e15679937 --- /dev/null +++ b/pkg/util/pki/nameconstraints_test.go @@ -0,0 +1,227 @@ +/* +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. +*/ + +package pki + +import ( + "bytes" + "crypto/x509" + "crypto/x509/pkix" + "fmt" + "net" + "strings" + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/cert-manager/cert-manager/internal/pem" +) + +// TestMarshalNameConstraints tests the MarshalNameConstraints function +// To generate the expectedPEM, do something like this: +// openssl req -new -key private_key.pem -out csr1.pem -subj "/CN=example.org" -config config.cnf +// +// where config.cnf is(replace nameConstraints with the values mentioned in the testcase): +// [req] +// default_bits = 2048 +// prompt = no +// default_md = sha256 +// req_extensions = req_ext + +// [req_ext] +// nameConstraints = critical,permitted;DNS:example.com,permitted;IP:192.168.1.0/255.255.255.0,permitted;email:user@example.com,permitted;URI:https://example.com,excluded;DNS:excluded.com,excluded;IP:192.168.0.0/255.255.255.0,excluded;email:user@excluded.com,excluded;URI:https://excluded.com +func TestMarshalUnmarshalNameConstraints(t *testing.T) { + // Test data + testCases := []struct { + name string + input *NameConstraints + expectedErr error + expectedPEM string + }{ + { + name: "Permitted constraints", + input: &NameConstraints{ + PermittedDNSDomains: []string{"example.com"}, + PermittedIPRanges: []*net.IPNet{{IP: net.IPv4(192, 168, 1, 0), Mask: net.IPv4Mask(255, 255, 255, 0)}}, + PermittedEmailAddresses: []string{"user@example.com"}, + PermittedURIDomains: []string{"https://example.com"}, + }, + expectedErr: nil, + // nameConstraints = critical,permitted;DNS:example.com,permitted;IP:192.168.1.0/255.255.255.0,permitted;email:user@example.com,permitted;URI:https://example.com + expectedPEM: `-----BEGIN CERTIFICATE REQUEST----- +MIICwjCCAaoCAQAwFjEUMBIGA1UEAwwLZXhhbXBsZS5vcmcwggEiMA0GCSqGSIb3 +DQEBAQUAA4IBDwAwggEKAoIBAQCXy2XEkqESyr8/Y2x1A7AQaQlu3wry8QSmVwcb +QYQ12xpA9derxd6f2qV+UZq/7tSwvaFfcdzbY4MTG+dq3QmlyXNEpVmzg/CbQJpQ +ae/aacnb7MEvPGQpD8eHBt14QdoH0B5qreARa/IND4I+BazEAn9yAWc9o5BQMqPb +5OGa5PMWR8apRyJrMfupMS0R3Nnmi+BP0fWepbOZHzRA6d2rbwkPBNBHQUyinxXS +oIMg/WbrG0tbps8H6PTZg3Ki+XutPm5rFJ3CKVCzIfWLFIa3jHDNbeRc359EgBI9 +r1H7ecuPKxhxewugl0NirKIaEgzc609FIP++pmm3J5P10HF7AgMBAAGgZzBlBgkq +hkiG9w0BCQ4xWDBWMFQGA1UdHgEB/wRKMEigRjANggtleGFtcGxlLmNvbTAKhwjA +qAEA////ADASgRB1c2VyQGV4YW1wbGUuY29tMBWGE2h0dHBzOi8vZXhhbXBsZS5j +b20wDQYJKoZIhvcNAQELBQADggEBAG4mhMt9iOGu1LInHW7oZyD8/FILhhafO7NF +OLPLNK37yZmPWn3idIei/oooFspKspLSMqyCGgibr6jo613+6ENCHgzM/MUDrbfP +i0VmriogMVB6qF73Qozylk1HPMcNe32aKsZygFAzKT586aO/F/exMx3NlKWa36m2 +rXKPgtD+T4R+hBxmsYAGVWFlvish+L1UIXtxddna4dYHSbLBz+uZXzrxyuJgSQV3 +2wF++GJ1zOi47CEUukqQOAZKPCE59erY+vUas8hwMTHMT22D5ZGbdjg6qVBCQdqW +Nu6OGP4KFgW0HWyeGeNBzioGUeyIHFKILLvj2n94WJMqXNyT5eE= +-----END CERTIFICATE REQUEST-----`, + }, + { + name: "Mixed constraints", + input: &NameConstraints{ + PermittedDNSDomains: []string{"example.com"}, + PermittedIPRanges: []*net.IPNet{{IP: net.IPv4(192, 168, 1, 0), Mask: net.IPv4Mask(255, 255, 255, 0)}}, + PermittedEmailAddresses: []string{"user@example.com"}, + PermittedURIDomains: []string{"https://example.com"}, + ExcludedDNSDomains: []string{"excluded.com"}, + ExcludedIPRanges: []*net.IPNet{{IP: net.IPv4(192, 168, 0, 0), Mask: net.IPv4Mask(255, 255, 255, 0)}}, + ExcludedEmailAddresses: []string{"user@excluded.com"}, + ExcludedURIDomains: []string{"https://excluded.com"}, + }, + expectedErr: nil, + // nameConstraints = critical,permitted;DNS:example.com,permitted;IP:192.168.1.0/255.255.255.0,permitted;email:user@example.com,permitted;URI:https://example.com,excluded;DNS:excluded.com,excluded;IP:192.168.0.0/255.255.255.0,excluded;email:user@excluded.com,excluded;URI:https://excluded.com + expectedPEM: `-----BEGIN CERTIFICATE REQUEST----- +MIIDFDCCAfwCAQAwFjEUMBIGA1UEAwwLZXhhbXBsZS5vcmcwggEiMA0GCSqGSIb3 +DQEBAQUAA4IBDwAwggEKAoIBAQCXy2XEkqESyr8/Y2x1A7AQaQlu3wry8QSmVwcb +QYQ12xpA9derxd6f2qV+UZq/7tSwvaFfcdzbY4MTG+dq3QmlyXNEpVmzg/CbQJpQ +ae/aacnb7MEvPGQpD8eHBt14QdoH0B5qreARa/IND4I+BazEAn9yAWc9o5BQMqPb +5OGa5PMWR8apRyJrMfupMS0R3Nnmi+BP0fWepbOZHzRA6d2rbwkPBNBHQUyinxXS +oIMg/WbrG0tbps8H6PTZg3Ki+XutPm5rFJ3CKVCzIfWLFIa3jHDNbeRc359EgBI9 +r1H7ecuPKxhxewugl0NirKIaEgzc609FIP++pmm3J5P10HF7AgMBAAGggbgwgbUG +CSqGSIb3DQEJDjGBpzCBpDCBoQYDVR0eAQH/BIGWMIGToEYwDYILZXhhbXBsZS5j +b20wCocIwKgBAP///wAwEoEQdXNlckBleGFtcGxlLmNvbTAVhhNodHRwczovL2V4 +YW1wbGUuY29toUkwDoIMZXhjbHVkZWQuY29tMAqHCMCoAAD///8AMBOBEXVzZXJA +ZXhjbHVkZWQuY29tMBaGFGh0dHBzOi8vZXhjbHVkZWQuY29tMA0GCSqGSIb3DQEB +CwUAA4IBAQCEBMhHw4wbP+aBDViKtvpaMar3ZWYVuV7j2qck5yDlXYGhpTQlwg5C +XEIP7zKM1yGgCITEpA5KML4PV55rEU6TCa2E9oQfy51QQcmSTGYLjolOahpALwzn +38n9e4WBiHwDVMVsSR5Zhw2dy9tqSslAHjp3TFFCcx7gaKoTs6OOJzv784PzX7xp +Vbm68hvWwkdD0lwGJlNkykPmNGxpC1kVn6L1p7LUubWOkkqBHwgny+DW3fPtKpvO +AHpUq+yDI0oaIz6BIfn2Vs7jUSXCZIoQBwajALg9kGqh3O6+ds617+AzxGXk0LBQ +0GsHVWCimOgcqgU5Qg4K6iMUtlDU2WAW +-----END CERTIFICATE REQUEST-----`, + }, + { + name: "Excluded constraints", + input: &NameConstraints{ + ExcludedDNSDomains: []string{"excluded.com"}, + ExcludedIPRanges: []*net.IPNet{{IP: net.IPv4(192, 168, 0, 0), Mask: net.IPv4Mask(255, 255, 255, 0)}}, + ExcludedEmailAddresses: []string{"user@excluded.com"}, + ExcludedURIDomains: []string{"https://excluded.com"}, + }, + expectedErr: nil, + // nameConstraints = critical,excluded;DNS:excluded.com,excluded;IP:192.168.0.0/255.255.255.0,excluded;email:user@excluded.com,excluded;URI:https://excluded.com + expectedPEM: `-----BEGIN CERTIFICATE REQUEST----- +MIICxTCCAa0CAQAwFjEUMBIGA1UEAwwLZXhhbXBsZS5vcmcwggEiMA0GCSqGSIb3 +DQEBAQUAA4IBDwAwggEKAoIBAQCXy2XEkqESyr8/Y2x1A7AQaQlu3wry8QSmVwcb +QYQ12xpA9derxd6f2qV+UZq/7tSwvaFfcdzbY4MTG+dq3QmlyXNEpVmzg/CbQJpQ +ae/aacnb7MEvPGQpD8eHBt14QdoH0B5qreARa/IND4I+BazEAn9yAWc9o5BQMqPb +5OGa5PMWR8apRyJrMfupMS0R3Nnmi+BP0fWepbOZHzRA6d2rbwkPBNBHQUyinxXS +oIMg/WbrG0tbps8H6PTZg3Ki+XutPm5rFJ3CKVCzIfWLFIa3jHDNbeRc359EgBI9 +r1H7ecuPKxhxewugl0NirKIaEgzc609FIP++pmm3J5P10HF7AgMBAAGgajBoBgkq +hkiG9w0BCQ4xWzBZMFcGA1UdHgEB/wRNMEuhSTAOggxleGNsdWRlZC5jb20wCocI +wKgAAP///wAwE4ERdXNlckBleGNsdWRlZC5jb20wFoYUaHR0cHM6Ly9leGNsdWRl +ZC5jb20wDQYJKoZIhvcNAQELBQADggEBABQGXpovgvk8Ag+FSv0fVcHAalNrNHkL +8kJmLjJKMjYhrI4KwkrVDwRvm96ueSfDYLMu56Vd/cLzVbqgFNEeGY+7/fwty/PK +PwjPjMC3i09D1JZjrpc2gpIxmrwP/vf1DpxPUVF5wzE9xRiYvKu3/ZHy1d3FYYgT +cpf+w2cqzt2J8imToJUtjbVTACqBwhwRrn7xyP0trvAo1tfHS4qK7urJxbuT+OAf +mYfy24EOPhpvyIyYS+lbkc9wdYT4BSIjQCFNAjcBD+/04SkHgtbFLy0i8xsKcfOy +3haWYno4zTZ0v6LAdn3CgtbvUtFBfIMjmEfsldVZpIbpuSEqjMFDGls= +-----END CERTIFICATE REQUEST-----`, + }, + } + + compareIPArrays := func(a, b []*net.IPNet) bool { + if len(a) != len(b) { + return false + } + + for i, ipNet := range a { + if !ipNet.IP.Equal(b[i].IP) || !bytes.Equal(ipNet.Mask, b[i].Mask) { + return false + } + } + + return true + } + + for _, tc := range testCases { + t.Run(tc.name+"_marshal", func(t *testing.T) { + expectedResult, err := getExtensionFromPem(tc.expectedPEM) + assert.NoError(t, err) + result, err := MarshalNameConstraints(tc.input, expectedResult.Critical) + if tc.expectedErr != nil { + assert.Error(t, err) + assert.EqualError(t, err, tc.expectedErr.Error()) + } else { + assert.NoError(t, err) + assert.Equal(t, expectedResult.Id, result.Id) + assert.Equal(t, expectedResult.Critical, result.Critical) + assert.Equal(t, expectedResult.Value, result.Value) + } + }) + + t.Run(tc.name+"_unmarshal", func(t *testing.T) { + expectedResult, err := getExtensionFromPem(tc.expectedPEM) + assert.NoError(t, err) + constraints, err := UnmarshalNameConstraints(expectedResult.Value) + if tc.expectedErr != nil { + assert.Error(t, err) + assert.EqualError(t, err, tc.expectedErr.Error()) + } else { + assert.NoError(t, err) + assert.Equal(t, constraints.ExcludedDNSDomains, tc.input.ExcludedDNSDomains) + assert.Equal(t, constraints.ExcludedEmailAddresses, tc.input.ExcludedEmailAddresses) + assert.True(t, compareIPArrays(constraints.ExcludedIPRanges, tc.input.ExcludedIPRanges)) + assert.Equal(t, constraints.ExcludedURIDomains, tc.input.ExcludedURIDomains) + assert.Equal(t, constraints.PermittedDNSDomains, tc.input.PermittedDNSDomains) + assert.Equal(t, constraints.PermittedEmailAddresses, tc.input.PermittedEmailAddresses) + assert.True(t, compareIPArrays(constraints.PermittedIPRanges, tc.input.PermittedIPRanges)) + assert.Equal(t, constraints.PermittedURIDomains, tc.input.PermittedURIDomains) + } + }) + } +} + +func getExtensionFromPem(pemData string) (pkix.Extension, error) { + if pemData == "" { + return pkix.Extension{}, nil + } + + pemData = strings.TrimSpace(pemData) + csrPEM := []byte(pemData) + + block, _, err := pem.SafeDecodeCSR(csrPEM) + if err != nil { + return pkix.Extension{}, fmt.Errorf("expected no PEM decode err but got %s", err) + } + + if block == nil || block.Type != "CERTIFICATE REQUEST" { + return pkix.Extension{}, fmt.Errorf("Failed to decode PEM block or the type is not 'CERTIFICATE REQUEST'") + } + + csr, err := x509.ParseCertificateRequest(block.Bytes) + if err != nil { + return pkix.Extension{}, fmt.Errorf("Error parsing CSR: %v", err) + } + + for _, ext := range csr.Extensions { + if ext.Id.Equal(OIDExtensionNameConstraints) { + return ext, nil + } + } + + return pkix.Extension{}, nil +} diff --git a/pkg/util/pki/parse.go b/pkg/util/pki/parse.go index e6376e5dd7d..07753cb3da8 100644 --- a/pkg/util/pki/parse.go +++ b/pkg/util/pki/parse.go @@ -18,23 +18,20 @@ package pki import ( "crypto" - "crypto/rsa" "crypto/x509" - "crypto/x509/pkix" - "encoding/asn1" - "encoding/pem" + stdpem "encoding/pem" + "github.com/cert-manager/cert-manager/internal/pem" "github.com/cert-manager/cert-manager/pkg/util/errors" - "github.com/go-ldap/ldap/v3" ) // DecodePrivateKeyBytes will decode a PEM encoded private key into a crypto.Signer. -// It supports ECDSA and RSA private keys only. All other types will return err. +// It supports ECDSA, RSA and EdDSA private keys only. All other types will return err. func DecodePrivateKeyBytes(keyBytes []byte) (crypto.Signer, error) { // decode the private key pem - block, _ := pem.Decode(keyBytes) - if block == nil { - return nil, errors.NewInvalidData("error decoding private key PEM block") + block, _, err := pem.SafeDecodePrivateKey(keyBytes) + if err != nil { + return nil, errors.NewInvalidData("error decoding private key PEM block: %s", err.Error()) } switch block.Type { @@ -72,56 +69,57 @@ func DecodePrivateKeyBytes(keyBytes []byte) (crypto.Signer, error) { } } -// DecodePKCS1PrivateKeyBytes will decode a PEM encoded RSA private key. -func DecodePKCS1PrivateKeyBytes(keyBytes []byte) (*rsa.PrivateKey, error) { - // decode the private key pem - block, _ := pem.Decode(keyBytes) - if block == nil { - return nil, errors.NewInvalidData("error decoding private key PEM block") - } - // parse the private key - key, err := x509.ParsePKCS1PrivateKey(block.Bytes) - if err != nil { - return nil, errors.NewInvalidData("error parsing private key: %s", err.Error()) - } - // validate the private key - if err = key.Validate(); err != nil { - return nil, errors.NewInvalidData("private key failed validation: %s", err.Error()) - } - return key, nil -} - -// DecodeX509CertificateChainBytes will decode a PEM encoded x509 Certificate chain. -func DecodeX509CertificateChainBytes(certBytes []byte) ([]*x509.Certificate, error) { +func decodeMultipleCerts(certBytes []byte, decodeFn func([]byte) (*stdpem.Block, []byte, error)) ([]*x509.Certificate, error) { certs := []*x509.Certificate{} - var block *pem.Block + var block *stdpem.Block for { + var err error + // decode the tls certificate pem - block, certBytes = pem.Decode(certBytes) - if block == nil { - break + block, certBytes, err = decodeFn(certBytes) + if err != nil { + if err == pem.ErrNoPEMData { + break + } + + return nil, err } // parse the tls certificate cert, err := x509.ParseCertificate(block.Bytes) if err != nil { - return nil, errors.NewInvalidData("error parsing TLS certificate: %s", err.Error()) + return nil, errors.NewInvalidData("error parsing X.509 certificate: %s", err.Error()) } + certs = append(certs, cert) } if len(certs) == 0 { - return nil, errors.NewInvalidData("error decoding certificate PEM block") + return nil, errors.NewInvalidData("error decoding certificate PEM block: no valid certificates found") } return certs, nil } +// DecodeX509CertificateChainBytes will decode a PEM encoded x509 Certificate chain with a tight +// size limit to reduce the risk of DoS attacks. If you need to decode many certificates, use +// DecodeX509CertificateSetBytes instead. +func DecodeX509CertificateChainBytes(certBytes []byte) ([]*x509.Certificate, error) { + return decodeMultipleCerts(certBytes, pem.SafeDecodeCertificateChain) +} + +// DecodeX509CertificateSetBytes will decode a concatenated set of PEM encoded x509 Certificates, +// with generous size limits to enable parsing of TLS trust bundles. +// If you need to decode a single certificate chain, use DecodeX509CertificateChainBytes instead. +func DecodeX509CertificateSetBytes(certBytes []byte) ([]*x509.Certificate, error) { + return decodeMultipleCerts(certBytes, pem.SafeDecodeCertificateBundle) +} + // DecodeX509CertificateBytes will decode a PEM encoded x509 Certificate. func DecodeX509CertificateBytes(certBytes []byte) (*x509.Certificate, error) { - certs, err := DecodeX509CertificateChainBytes(certBytes) + certs, err := DecodeX509CertificateSetBytes(certBytes) if err != nil { return nil, err } @@ -131,9 +129,9 @@ func DecodeX509CertificateBytes(certBytes []byte) (*x509.Certificate, error) { // DecodeX509CertificateRequestBytes will decode a PEM encoded x509 Certificate Request. func DecodeX509CertificateRequestBytes(csrBytes []byte) (*x509.CertificateRequest, error) { - block, _ := pem.Decode(csrBytes) - if block == nil { - return nil, errors.NewInvalidData("error decoding certificate request PEM block") + block, _, err := pem.SafeDecodeCSR(csrBytes) + if err != nil { + return nil, errors.NewInvalidData("error decoding certificate request PEM block: %s", err) } csr, err := x509.ParseCertificateRequest(block.Bytes) @@ -143,294 +141,3 @@ func DecodeX509CertificateRequestBytes(csrBytes []byte) (*x509.CertificateReques return csr, nil } - -// PEMBundle includes the PEM encoded X.509 certificate chain and CA. CAPEM -// contains either 1 CA certificate, or is empty if only a single certificate -// exists in the chain. -type PEMBundle struct { - CAPEM []byte - ChainPEM []byte -} - -type chainNode struct { - cert *x509.Certificate - issuer *chainNode -} - -// ParseSingleCertificateChainPEM decodes a PEM encoded certificate chain before -// calling ParseSingleCertificateChainPEM -func ParseSingleCertificateChainPEM(pembundle []byte) (PEMBundle, error) { - certs, err := DecodeX509CertificateChainBytes(pembundle) - if err != nil { - return PEMBundle{}, err - } - return ParseSingleCertificateChain(certs) -} - -// ParseSingleCertificateChain returns the PEM-encoded chain of certificates as -// well as the PEM-encoded CA certificate. The certificate chain contains the -// leaf certificate first. -// -// The CA may not be a true root, but the highest intermediate certificate. -// The returned CA may be empty if a single certificate was passed. -// -// This function removes duplicate certificate entries as well as comments and -// unnecessary white space. -// -// An error is returned if the passed bundle is not a valid flat tree chain, -// the bundle is malformed, or the chain is broken. -func ParseSingleCertificateChain(certs []*x509.Certificate) (PEMBundle, error) { - // De-duplicate certificates. This moves "complicated" logic away from - // consumers and into a shared function, who would otherwise have to do this - // anyway. - for i := 0; i < len(certs)-1; i++ { - for j := 1; j < len(certs); j++ { - if i == j { - continue - } - if certs[i].Equal(certs[j]) { - certs = append(certs[:j], certs[j+1:]...) - } - } - } - - // A certificate chain can be well described as a linked list. Here we build - // multiple lists that contain a single node, each being a single certificate - // that was passed. - var chains []*chainNode - for i := range certs { - chains = append(chains, &chainNode{cert: certs[i]}) - } - - // The task is to build a single list which represents a single certificate - // chain. The strategy is to iteratively attempt to join items in the list to - // build this single chain. Once we have a single list, we have built the - // chain. If the number of lists do not decrease after a pass, then the list - // can never be reduced to a single chain and we error. - for { - // If a single list is left, then we have built the entire chain. Stop - // iterating. - if len(chains) == 1 { - break - } - - // lastChainsLength is used to ensure that at every pass, the number of - // tested chains gets smaller. - lastChainsLength := len(chains) - for i := 0; i < len(chains)-1; i++ { - for j := 1; j < len(chains); j++ { - if i == j { - continue - } - - // attempt to add both chains together - chain, ok := chains[i].tryMergeChain(chains[j]) - if ok { - // If adding the chains together was successful, remove inner chain from - // list - chains = append(chains[:j], chains[j+1:]...) - } - - chains[i] = chain - } - } - - // If no chains were merged in this pass, the chain can never be built as a - // single list. Error. - if lastChainsLength == len(chains) { - return PEMBundle{}, errors.NewInvalidData("certificate chain is malformed or broken") - } - } - - // There is only a single chain left at index 0. Return chain as PEM. - return chains[0].toBundleAndCA() -} - -// toBundleAndCA will return the PEM bundle of this chain. -func (c *chainNode) toBundleAndCA() (PEMBundle, error) { - var ( - certs []*x509.Certificate - ca *x509.Certificate - ) - - for { - // If the issuer is nil, we have hit the root of the chain. Assign the CA - // to this certificate and stop traversing. If the certificate at the root - // of the chain is not self-signed (i.e. is not a root CA), then also append - // that certificate to the chain. - - // Root certificates are omitted from the chain as per - // https://datatracker.ietf.org/doc/html/rfc5246#section-7.4.2 - // > [T]he 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. - - if c.issuer == nil { - if len(certs) > 0 && !isSelfSignedCertificate(c.cert) { - certs = append(certs, c.cert) - } - - ca = c.cert - break - } - - // Add this node's certificate to the list at the end. Ready to check - // next node up. - certs = append(certs, c.cert) - c = c.issuer - } - - caPEM, err := EncodeX509(ca) - if err != nil { - return PEMBundle{}, err - } - - // If no certificates parsed, then CA is the only certificate and should be - // the chain. If the CA is also self-signed, then by definition it's also the - // issuer and so can be placed in CAPEM too. - if len(certs) == 0 { - if isSelfSignedCertificate(ca) { - return PEMBundle{ChainPEM: caPEM, CAPEM: caPEM}, nil - } - - return PEMBundle{ChainPEM: caPEM}, nil - } - - // Encode full certificate chain - chainPEM, err := EncodeX509Chain(certs) - if err != nil { - return PEMBundle{}, err - } - - // Return chain and ca - return PEMBundle{CAPEM: caPEM, ChainPEM: chainPEM}, nil -} - -// tryMergeChain glues two chains A and B together by adding one on top of -// the other. The function tries both gluing A on top of B and B on top of -// A, which is why the argument order for the two input chains does not -// matter. -// -// Gluability: We say that the chains A and B are glueable when either the -// leaf certificate of A can be verified using the root certificate of B, -// or that the leaf certificate of B can be verified using the root certificate -// of A. -// -// A leaf certificate C (as in "child") is verified by a certificate P -// (as in "parent"), when they satisfy C.CheckSignatureFrom(P). In the -// following diagram, C.CheckSignatureFrom(P) is satisfied, i.e., the -// signature ("sig") on the certificate C can be verified using the parent P: -// -// head tail -// +------+-------+ +------+-------+ +------+-------+ -// | | | | | | | | | -// | | sig ------->| C | sig ------->| P | | -// | | | | | | | | | -// +------+-------+ +------+-------+ +------+-------+ -// leaf certificate root certificate -// -// The function returns false if the chains A and B are not gluable. -func (c *chainNode) tryMergeChain(chain *chainNode) (*chainNode, bool) { - // The given chain's root has been signed by this node. Add this node on top - // of the given chain. - if chain.root().cert.CheckSignatureFrom(c.cert) == nil { - chain.root().issuer = c - return chain, true - } - - // The given chain is the issuer of the root of this node. Add the given - // chain on top of the root of this node. - if c.root().cert.CheckSignatureFrom(chain.cert) == nil { - c.root().issuer = chain - return c, true - } - - // Chains cannot be added together. - return c, false -} - -// Return the root most node of this chain. -func (c *chainNode) root() *chainNode { - for c.issuer != nil { - c = c.issuer - } - - return c -} - -// isSelfSignedCertificate returns true if the given X.509 certificate has been -// signed by itself, which would make it a "root" certificate. -func isSelfSignedCertificate(cert *x509.Certificate) bool { - return cert.CheckSignatureFrom(cert) == nil -} - -var OIDConstants = struct { - Country []int - Organization []int - OrganizationalUnit []int - CommonName []int - SerialNumber []int - Locality []int - Province []int - StreetAddress []int -}{ - Country: []int{2, 5, 4, 6}, - Organization: []int{2, 5, 4, 10}, - OrganizationalUnit: []int{2, 5, 4, 11}, - CommonName: []int{2, 5, 4, 3}, - SerialNumber: []int{2, 5, 4, 5}, - Locality: []int{2, 5, 4, 7}, - Province: []int{2, 5, 4, 8}, - StreetAddress: []int{2, 5, 4, 9}, -} - -// Copied from pkix.attributeTypeNames and inverted. (Sadly it is private.) -// Source: https://cs.opensource.google/go/go/+/refs/tags/go1.18.2:src/crypto/x509/pkix/pkix.go;l=26 -var attributeTypeNames = map[string][]int{ - "C": OIDConstants.Country, - "O": OIDConstants.Organization, - "OU": OIDConstants.OrganizationalUnit, - "CN": OIDConstants.CommonName, - "SERIALNUMBER": OIDConstants.SerialNumber, - "L": OIDConstants.Locality, - "ST": OIDConstants.Province, - "STREET": OIDConstants.StreetAddress, -} - -func ParseSubjectStringToRdnSequence(subject string) (pkix.RDNSequence, error) { - - dns, err := ldap.ParseDN(subject) - if err != nil { - return nil, err - } - - // Traverse the parsed RDNSequence in REVERSE order as RDNs in String format are expected to be written in reverse order. - // Meaning, a string of "CN=Foo,OU=Bar,O=Baz" actually should have "O=Baz" as the first element in the RDNSequence. - var rdns pkix.RDNSequence - for i := range dns.RDNs { - ldapRelativeDN := dns.RDNs[len(dns.RDNs)-i-1] - - var atvs []pkix.AttributeTypeAndValue - for _, ldapATV := range ldapRelativeDN.Attributes { - - atvs = append(atvs, pkix.AttributeTypeAndValue{ - Type: attributeTypeNames[ldapATV.Type], - Value: ldapATV.Value, - }) - - } - rdns = append(rdns, atvs) - } - return rdns, nil - -} - -func ParseSubjectStringToRawDerBytes(subject string) ([]byte, error) { - rdnSequenceFromLiteralString, err := ParseSubjectStringToRdnSequence(subject) - if err != nil { - return nil, err - } - - return asn1.Marshal(rdnSequenceFromLiteralString) - -} diff --git a/pkg/util/pki/parse_certificate_chain.go b/pkg/util/pki/parse_certificate_chain.go new file mode 100644 index 00000000000..7064208f58a --- /dev/null +++ b/pkg/util/pki/parse_certificate_chain.go @@ -0,0 +1,271 @@ +/* +Copyright 2020 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. +*/ + +package pki + +import ( + "bytes" + "crypto/x509" + "slices" + + "github.com/cert-manager/cert-manager/pkg/util/errors" +) + +// PEMBundle includes the PEM encoded X.509 certificate chain and CA. CAPEM +// contains either 1 CA certificate, or is empty if only a single certificate +// exists in the chain. +type PEMBundle struct { + CAPEM []byte + ChainPEM []byte +} + +type chainNode struct { + cert *x509.Certificate + issuer *chainNode +} + +// ParseSingleCertificateChainPEM decodes a PEM encoded certificate chain before +// calling ParseSingleCertificateChainPEM +func ParseSingleCertificateChainPEM(pembundle []byte) (PEMBundle, error) { + certs, err := DecodeX509CertificateChainBytes(pembundle) + if err != nil { + return PEMBundle{}, err + } + return ParseSingleCertificateChain(certs) +} + +// ParseSingleCertificateChain returns the PEM-encoded chain of certificates as +// well as the PEM-encoded CA certificate. +// +// The CA (CAPEM) may not be a true root, but the highest intermediate certificate. +// The certificate is chosen as follows: +// - If the chain has a self-signed root, the root certificate. +// - If the chain has no self-signed root and has > 1 certificates, the highest certificate in the chain. +// - If the chain has no self-signed root and has == 1 certificate, nil. +// +// The certificate chain (ChainPEM) starts with the leaf certificate and ends with the +// highest certificate in the chain which is not self-signed. Self-signed certificates +// are not included in the chain because we are certain they are known and trusted by the +// client already. +// +// This function removes duplicate certificate entries as well as comments and +// unnecessary white space. +// +// An error is returned if the passed bundle is not a valid single chain, +// the bundle is malformed, or the chain is broken. +func ParseSingleCertificateChain(certs []*x509.Certificate) (PEMBundle, error) { + for _, cert := range certs { + if cert == nil { + return PEMBundle{}, errors.NewInvalidData("certificate chain contains nil certificate") + } + + if len(cert.Raw) == 0 { + return PEMBundle{}, errors.NewInvalidData("certificate chain contains certificate without Raw set") + } + } + + { + // De-duplicate certificates. This moves "complicated" logic away from + // consumers and into a shared function, who would otherwise have to do this + // anyway. + // For lots of certificates, the time complexity is O(n log n). + uniqueCerts := append([]*x509.Certificate{}, certs...) + slices.SortFunc(uniqueCerts, func(a, b *x509.Certificate) int { + return bytes.Compare(a.Raw, b.Raw) + }) + uniqueCerts = slices.CompactFunc(uniqueCerts, func(a, b *x509.Certificate) bool { + return bytes.Equal(a.Raw, b.Raw) + }) + certs = uniqueCerts + } + + // To prevent a malicious input from causing a DoS, we limit the number of unique + // certificates. This helps us avoid issues with O(n^2) time complexity in the algorithm below. + if len(certs) > 1000 { + return PEMBundle{}, errors.NewInvalidData("certificate chain is too long, must be less than 1000 certificates") + } + + // A certificate chain can be well described as a linked list. Here we build + // multiple lists that contain a single node, each being a single certificate + // that was passed. + var chains []*chainNode + for i := range certs { + chains = append(chains, &chainNode{cert: certs[i]}) + } + + // The task is to build a single list which represents a single certificate + // chain. The strategy is to iteratively attempt to join items in the list to + // build this single chain. Once we have a single list, we have built the + // chain. If no match is found after a pass, then the list can never be reduced + // to a single chain and we error. + // For lots of certificates, the time complexity is O(n^2). + // + // If a single list is left, then we have built the entire chain. Stop + // iterating. + for len(chains) > 1 { + // If we were not able to merge two chains in this pass, then the chain is + // broken and cannot be built. Error. + mergedTwoChains := false + + // Pop the last chain off the list and attempt to find a chain it can be + // merged with. + lastChain := chains[len(chains)-1] + chains = chains[:len(chains)-1] + + for i, chain := range chains { + // attempt to add both chains together + chain, ok := lastChain.tryMergeChain(chain) + if ok { + // If adding the chains together was successful, replace the chain at + // index i with the new chain. + chains[i] = chain + mergedTwoChains = true + break + } + } + + // If no chains were merged in this pass, the chain can never be built as a + // single list. Error. + if !mergedTwoChains { + return PEMBundle{}, errors.NewInvalidData("certificate chain is malformed or broken") + } + } + + // There is only a single chain left at index 0. Return chain as PEM. + return chains[0].toBundleAndCA() +} + +// toBundleAndCA will return the PEM bundle of this chain. +func (c *chainNode) toBundleAndCA() (PEMBundle, error) { + var ( + certs []*x509.Certificate + ca *x509.Certificate + ) + + for { + // If the issuer is nil, we have hit the root of the chain. Assign the CA + // to this certificate and stop traversing. If the certificate at the root + // of the chain is not self-signed (i.e. is not a root CA), then also append + // that certificate to the chain. + + // Root certificates are omitted from the chain as per + // https://datatracker.ietf.org/doc/html/rfc5246#section-7.4.2 + // > [T]he 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. + + if c.issuer == nil { + if len(certs) > 0 && !isSelfSignedCertificate(c.cert) { + certs = append(certs, c.cert) + } + + ca = c.cert + break + } + + // Add this node's certificate to the list at the end. Ready to check + // next node up. + certs = append(certs, c.cert) + c = c.issuer + } + + caPEM, err := EncodeX509(ca) + if err != nil { + return PEMBundle{}, err + } + + // If no certificates parsed, then CA is the only certificate and should be + // the chain. If the CA is also self-signed, then by definition it's also the + // issuer and so can be placed in CAPEM too. + if len(certs) == 0 { + if isSelfSignedCertificate(ca) { + return PEMBundle{ChainPEM: caPEM, CAPEM: caPEM}, nil + } + + return PEMBundle{ChainPEM: caPEM}, nil + } + + // Encode full certificate chain + chainPEM, err := EncodeX509Chain(certs) + if err != nil { + return PEMBundle{}, err + } + + // Return chain and ca + return PEMBundle{CAPEM: caPEM, ChainPEM: chainPEM}, nil +} + +// tryMergeChain glues two chains A and B together by adding one on top of +// the other. The function tries both gluing A on top of B and B on top of +// A, which is why the argument order for the two input chains does not +// matter. +// +// Glueability: We say that the chains A and B are glueable when either the +// leaf certificate of A can be verified using the root certificate of B, +// or that the leaf certificate of B can be verified using the root certificate +// of A. +// +// A leaf certificate C (as in "child") is verified by a certificate P +// (as in "parent"), when they satisfy C.CheckSignatureFrom(P). In the +// following diagram, C.CheckSignatureFrom(P) is satisfied, i.e., the +// signature ("sig") on the certificate C can be verified using the parent P: +// +// head tail +// +------+-------+ +------+-------+ +------+-------+ +// | | | | | | | | | +// | | sig ------->| C | sig ------->| P | | +// | | | | | | | | | +// +------+-------+ +------+-------+ +------+-------+ +// leaf certificate root certificate +// +// The function returns false if the chains A and B are not glueable. +func (a *chainNode) tryMergeChain(b *chainNode) (*chainNode, bool) { + bRoot := b.root() + + // b's root has been signed by a. Add a as parent of b's root. + if bytes.Equal(bRoot.cert.RawIssuer, a.cert.RawSubject) && + bRoot.cert.CheckSignatureFrom(a.cert) == nil { + bRoot.issuer = a + return b, true + } + + aRoot := a.root() + + // a's root has been signed by b. Add b as parent of a's root. + if bytes.Equal(aRoot.cert.RawIssuer, b.cert.RawSubject) && + aRoot.cert.CheckSignatureFrom(b.cert) == nil { + aRoot.issuer = b + return a, true + } + + // Chains cannot be added together. + return a, false +} + +// Return the root most node of this chain. +func (c *chainNode) root() *chainNode { + for c.issuer != nil { + c = c.issuer + } + + return c +} + +// isSelfSignedCertificate returns true if the given X.509 certificate has been +// signed by itself, which would make it a "root" certificate. +func isSelfSignedCertificate(cert *x509.Certificate) bool { + return cert.CheckSignatureFrom(cert) == nil +} diff --git a/pkg/util/pki/parse_certificate_chain_test.go b/pkg/util/pki/parse_certificate_chain_test.go new file mode 100644 index 00000000000..c1215948008 --- /dev/null +++ b/pkg/util/pki/parse_certificate_chain_test.go @@ -0,0 +1,277 @@ +/* +Copyright 2020 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. +*/ + +package pki + +import ( + "crypto" + "crypto/x509" + "crypto/x509/pkix" + "fmt" + "reflect" + "slices" + "testing" + "time" +) + +type testBundle struct { + cert *x509.Certificate + pem []byte + pk crypto.PrivateKey +} + +func mustCreateBundle(t *testing.T, issuer *testBundle, name string) *testBundle { + pk, err := GenerateECPrivateKey(256) + if err != nil { + t.Fatal(err) + } + + template := &x509.Certificate{ + BasicConstraintsValid: true, + PublicKeyAlgorithm: x509.ECDSA, + PublicKey: pk.Public(), + IsCA: true, + Subject: pkix.Name{ + CommonName: name, + }, + NotBefore: time.Now(), + NotAfter: time.Now().Add(time.Minute), + KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, + } + + var ( + issuerKey crypto.PrivateKey + issuerCert *x509.Certificate + ) + + if issuer == nil { + // No issuer implies the cert should be self-signed + issuerKey = pk + issuerCert = template + } else { + issuerKey = issuer.pk + issuerCert = issuer.cert + } + + certPEM, cert, err := SignCertificate(template, issuerCert, pk.Public(), issuerKey) + if err != nil { + t.Fatal(err) + } + + return &testBundle{pem: certPEM, cert: cert, pk: pk} +} + +func joinPEM(first []byte, rest ...[]byte) []byte { + for _, b := range rest { + first = append(first, b...) + } + + return first +} + +func TestParseSingleCertificateChainPEM(t *testing.T) { + root := mustCreateBundle(t, nil, "root") + intA1 := mustCreateBundle(t, root, "intA-1") + intA2 := mustCreateBundle(t, intA1, "intA-2") + intB1 := mustCreateBundle(t, root, "intB-1") + intB2 := mustCreateBundle(t, intB1, "intB-2") + leaf := mustCreateBundle(t, intA2, "leaf") + leafInterCN := mustCreateBundle(t, intA2, intA2.cert.Subject.CommonName) + random := mustCreateBundle(t, nil, "random") + + var bigCertBundle PEMBundle + { + root := mustCreateBundle(t, nil, "root") + bigCertBundle.CAPEM = root.pem + + cert := root + var pems [][]byte + for i := range 100 { + cert = mustCreateBundle(t, cert, fmt.Sprintf("int-%d", i)) + pems = append(pems, cert.pem) + } + + for _, pem := range slices.Backward(pems) { + bigCertBundle.ChainPEM = joinPEM(bigCertBundle.ChainPEM, pem) + } + } + + tests := map[string]struct { + inputBundle []byte + expPEMBundle PEMBundle + expErr bool + expErrString string + }{ + "if two certificate chain passed in order, should return single ca and certificate": { + inputBundle: joinPEM(intA1.pem, root.pem), + expPEMBundle: PEMBundle{ChainPEM: intA1.pem, CAPEM: root.pem}, + expErr: false, + }, + "if two certificate chain passed with leaf and intermediate, should return both certs in chain with intermediate as CA": { + inputBundle: joinPEM(leaf.pem, intA2.pem), + expPEMBundle: PEMBundle{ChainPEM: joinPEM(leaf.pem, intA2.pem), CAPEM: intA2.pem}, + expErr: false, + }, + "if two certificate chain passed out of order, should return single ca and certificate": { + inputBundle: joinPEM(root.pem, intA1.pem), + expPEMBundle: PEMBundle{ChainPEM: intA1.pem, CAPEM: root.pem}, + expErr: false, + }, + "if 3 certificate chain passed out of order, should return single ca and chain in order": { + inputBundle: joinPEM(root.pem, intA2.pem, intA1.pem), + expPEMBundle: PEMBundle{ChainPEM: joinPEM(intA2.pem, intA1.pem), CAPEM: root.pem}, + expErr: false, + }, + "empty entries should be ignored, and return ca and certificate": { + inputBundle: joinPEM(root.pem, intA2.pem, []byte("\n#foo\n \n"), intA1.pem), + expPEMBundle: PEMBundle{ChainPEM: joinPEM(intA2.pem, intA1.pem), CAPEM: root.pem}, + expErr: false, + }, + "if 4 certificate chain passed in order, should return single ca and chain in order": { + inputBundle: joinPEM(leaf.pem, intA1.pem, intA2.pem, root.pem), + expPEMBundle: PEMBundle{ChainPEM: joinPEM(leaf.pem, intA2.pem, intA1.pem), CAPEM: root.pem}, + expErr: false, + }, + "if certificate chain has two certs with the same CN, shouldn't affect output": { + // see https://github.com/cert-manager/cert-manager/issues/4142 + inputBundle: joinPEM(leafInterCN.pem, intA1.pem, intA2.pem, root.pem), + expPEMBundle: PEMBundle{ChainPEM: joinPEM(leafInterCN.pem, intA2.pem, intA1.pem), CAPEM: root.pem}, + expErr: false, + }, + "if 4 certificate chain passed out of order, should return single ca and chain in order": { + inputBundle: joinPEM(root.pem, intA1.pem, leaf.pem, intA2.pem), + expPEMBundle: PEMBundle{ChainPEM: joinPEM(leaf.pem, intA2.pem, intA1.pem), CAPEM: root.pem}, + expErr: false, + }, + "if 3 certificate chain but has break in the chain, should return error": { + inputBundle: joinPEM(root.pem, intA1.pem, leaf.pem), + expPEMBundle: PEMBundle{}, + expErr: true, + expErrString: "certificate chain is malformed or broken", + }, + "if 4 certificate chain but also random certificate, should return error": { + inputBundle: joinPEM(root.pem, intA1.pem, leaf.pem, intA2.pem, random.pem), + expPEMBundle: PEMBundle{}, + expErr: true, + expErrString: "certificate chain is malformed or broken", + }, + "if 6 certificate chain but some are duplicates, duplicates should be removed and return single ca with chain": { + inputBundle: joinPEM(intA2.pem, intA1.pem, root.pem, leaf.pem, intA1.pem, root.pem), + expPEMBundle: PEMBundle{ChainPEM: joinPEM(leaf.pem, intA2.pem, intA1.pem), CAPEM: root.pem}, + expErr: false, + }, + "if 6 certificate chain in different configuration but some are duplicates, duplicates should be removed and return single ca with chain": { + inputBundle: joinPEM(root.pem, intA1.pem, intA2.pem, leaf.pem, root.pem, intA1.pem), + expPEMBundle: PEMBundle{ChainPEM: joinPEM(leaf.pem, intA2.pem, intA1.pem), CAPEM: root.pem}, + expErr: false, + }, + "if certificate chain contains branches, then should error": { + inputBundle: joinPEM(root.pem, intA1.pem, intA2.pem, intB1.pem, intB2.pem), + expPEMBundle: PEMBundle{}, + expErr: true, + expErrString: "certificate chain is malformed or broken", + }, + "if certificate chain does not have a root ca, should append all intermediates to ChainPEM and use the root-most cert as CAPEM": { + inputBundle: joinPEM(intA1.pem, intA2.pem, leaf.pem), + expPEMBundle: PEMBundle{ChainPEM: joinPEM(leaf.pem, intA2.pem, intA1.pem), CAPEM: intA1.pem}, + expErr: false, + }, + "if only a single leaf certificate was parsed, ChainPEM should contain a single leaf certificate and CAPEM should remain empty": { + inputBundle: joinPEM(leaf.pem), + expPEMBundle: PEMBundle{ChainPEM: joinPEM(leaf.pem), CAPEM: nil}, + expErr: false, + }, + "if only a single intermediate certificate was parsed, ChainPEM should contain a single intermediate certificate and CAPEM should remain empty": { + inputBundle: joinPEM(intA1.pem), + expPEMBundle: PEMBundle{ChainPEM: joinPEM(intA1.pem), CAPEM: nil}, + expErr: false, + }, + "if only a single root certificate was parsed, ChainPEM should contain a single root certificate and CAPEM should also contain that root": { + inputBundle: joinPEM(root.pem), + expPEMBundle: PEMBundle{ChainPEM: joinPEM(root.pem), CAPEM: root.pem}, + expErr: false, + }, + "if acceptable long chain is passed, a result should be returned quickly": { + inputBundle: joinPEM(bigCertBundle.ChainPEM, bigCertBundle.CAPEM), + expPEMBundle: bigCertBundle, + expErr: false, + }, + "if unacceptably long chain is passed, should error without DoS": { + inputBundle: func() []byte { + root := mustCreateBundle(t, nil, "root") + + cert := root + var chain []byte + for i := range 501 { + cert = mustCreateBundle(t, cert, fmt.Sprintf("int-%d", i)) + chain = joinPEM(chain, cert.pem) + } + + return chain + }(), + expPEMBundle: PEMBundle{}, + expErr: true, + expErrString: "provided PEM data was larger than the maximum 95000B", + }, + } + + for name, test := range tests { + t.Run(name, func(t *testing.T) { + startTime := time.Now() + bundle, err := ParseSingleCertificateChainPEM(test.inputBundle) + if (err != nil) != test.expErr { + t.Errorf("unexpected error, exp=%t got=%v", + test.expErr, err) + } + + if time.Since(startTime) > time.Second { + t.Errorf("ParseSingleCertificateChainPEM took too long to complete, input could cause DoS") + } + + if err != nil && err.Error() != test.expErrString { + t.Errorf("unexpected error string, exp=%s got=%s", + test.expErrString, err.Error()) + } + + if !reflect.DeepEqual(bundle, test.expPEMBundle) { + t.Errorf("unexpected pem bundle, exp=%+s got=%+s", + test.expPEMBundle, bundle) + } + }) + } +} + +func TestParseSingleCertificateChain(t *testing.T) { + // ParseSingleCertificateChain is mostly tested in TestParseSingleCertificateChainPEM; + // this test checks that passing in too many small certs is correctly rejected + var inputBundle []*x509.Certificate + + for i := range 1001 { + cert := mustCreateBundle(t, nil, fmt.Sprintf("cert-%d", i)) + inputBundle = append(inputBundle, cert.cert) + } + + _, err := ParseSingleCertificateChain(inputBundle) + if err == nil { + t.Fatalf("expected error but got none from ParseSingleCertificateChain") + } + + expErr := "certificate chain is too long, must be less than 1000 certificates" + + if err.Error() != expErr { + t.Fatalf("expected err to be %s but it was %s", expErr, err.Error()) + } +} diff --git a/pkg/util/pki/parse_test.go b/pkg/util/pki/parse_test.go index 07f0f12a1cd..13f6109ef3e 100644 --- a/pkg/util/pki/parse_test.go +++ b/pkg/util/pki/parse_test.go @@ -17,21 +17,13 @@ limitations under the License. package pki import ( - "crypto" "crypto/ecdsa" - "crypto/rand" "crypto/rsa" - "crypto/x509" - "crypto/x509/pkix" - "encoding/asn1" "encoding/pem" - "reflect" "strings" "testing" - "time" v1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" - "github.com/stretchr/testify/assert" ) func generatePrivateKeyBytes(keyAlgo v1.PrivateKeyAlgorithm, keySize int) ([]byte, error) { @@ -85,7 +77,7 @@ func TestDecodePrivateKeyBytes(t *testing.T) { return } - block := &pem.Block{Type: "BLAH BLAH BLAH", Bytes: []byte("blahblahblah")} + block := &pem.Block{Type: "BLAHBLAHBLAH", Bytes: []byte("blahblahblah")} blahKeyBytes := pem.EncodeToMemory(block) privateKeyBlock := &pem.Block{Type: "PRIVATE KEY", Bytes: []byte("blahblahblah")} @@ -182,275 +174,3 @@ func TestDecodePrivateKeyBytes(t *testing.T) { t.Run(test.name, testFn(test)) } } - -type testBundle struct { - cert *x509.Certificate - pem []byte - pk crypto.PrivateKey -} - -func mustCreateBundle(t *testing.T, issuer *testBundle, name string) *testBundle { - pk, err := GenerateECPrivateKey(256) - if err != nil { - t.Fatal(err) - } - - serialNumber, err := rand.Int(rand.Reader, serialNumberLimit) - if err != nil { - t.Fatal(err) - } - - template := &x509.Certificate{ - Version: 3, - BasicConstraintsValid: true, - SerialNumber: serialNumber, - PublicKeyAlgorithm: x509.ECDSA, - PublicKey: pk.Public(), - IsCA: true, - Subject: pkix.Name{ - CommonName: name, - }, - NotBefore: time.Now(), - NotAfter: time.Now().Add(time.Minute), - KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, - } - - var ( - issuerKey crypto.PrivateKey - issuerCert *x509.Certificate - ) - - if issuer == nil { - // No issuer implies the cert should be self signed - issuerKey = pk - issuerCert = template - } else { - issuerKey = issuer.pk - issuerCert = issuer.cert - } - - certPEM, cert, err := SignCertificate(template, issuerCert, pk.Public(), issuerKey) - if err != nil { - t.Fatal(err) - } - - return &testBundle{pem: certPEM, cert: cert, pk: pk} -} - -func joinPEM(first []byte, rest ...[]byte) []byte { - for _, b := range rest { - first = append(first, b...) - } - - return first -} - -func TestParseSingleCertificateChain(t *testing.T) { - root := mustCreateBundle(t, nil, "root") - intA1 := mustCreateBundle(t, root, "intA-1") - intA2 := mustCreateBundle(t, intA1, "intA-2") - intB1 := mustCreateBundle(t, root, "intB-1") - intB2 := mustCreateBundle(t, intB1, "intB-2") - leaf := mustCreateBundle(t, intA2, "leaf") - leafInterCN := mustCreateBundle(t, intA2, intA2.cert.Subject.CommonName) - random := mustCreateBundle(t, nil, "random") - - tests := map[string]struct { - inputBundle []byte - expPEMBundle PEMBundle - expErr bool - }{ - "if two certificate chain passed in order, should return single ca and certificate": { - inputBundle: joinPEM(intA1.pem, root.pem), - expPEMBundle: PEMBundle{ChainPEM: intA1.pem, CAPEM: root.pem}, - expErr: false, - }, - "if two certificate chain passed with leaf and intermediate, should return both certs in chain with intermediate as CA": { - inputBundle: joinPEM(leaf.pem, intA2.pem), - expPEMBundle: PEMBundle{ChainPEM: joinPEM(leaf.pem, intA2.pem), CAPEM: intA2.pem}, - expErr: false, - }, - "if two certificate chain passed out of order, should return single ca and certificate": { - inputBundle: joinPEM(root.pem, intA1.pem), - expPEMBundle: PEMBundle{ChainPEM: intA1.pem, CAPEM: root.pem}, - expErr: false, - }, - "if 3 certificate chain passed out of order, should return single ca and chain in order": { - inputBundle: joinPEM(root.pem, intA2.pem, intA1.pem), - expPEMBundle: PEMBundle{ChainPEM: joinPEM(intA2.pem, intA1.pem), CAPEM: root.pem}, - expErr: false, - }, - "empty entries should be ignored, and return ca and certificate": { - inputBundle: joinPEM(root.pem, intA2.pem, []byte("\n#foo\n \n"), intA1.pem), - expPEMBundle: PEMBundle{ChainPEM: joinPEM(intA2.pem, intA1.pem), CAPEM: root.pem}, - expErr: false, - }, - "if 4 certificate chain passed in order, should return single ca and chain in order": { - inputBundle: joinPEM(leaf.pem, intA1.pem, intA2.pem, root.pem), - expPEMBundle: PEMBundle{ChainPEM: joinPEM(leaf.pem, intA2.pem, intA1.pem), CAPEM: root.pem}, - expErr: false, - }, - "if certificate chain has two certs with the same CN, shouldn't affect output": { - // see https://github.com/cert-manager/cert-manager/issues/4142 - inputBundle: joinPEM(leafInterCN.pem, intA1.pem, intA2.pem, root.pem), - expPEMBundle: PEMBundle{ChainPEM: joinPEM(leafInterCN.pem, intA2.pem, intA1.pem), CAPEM: root.pem}, - expErr: false, - }, - "if 4 certificate chain passed out of order, should return single ca and chain in order": { - inputBundle: joinPEM(root.pem, intA1.pem, leaf.pem, intA2.pem), - expPEMBundle: PEMBundle{ChainPEM: joinPEM(leaf.pem, intA2.pem, intA1.pem), CAPEM: root.pem}, - expErr: false, - }, - "if 3 certificate chain but has break in the chain, should return error": { - inputBundle: joinPEM(root.pem, intA1.pem, leaf.pem), - expPEMBundle: PEMBundle{}, - expErr: true, - }, - "if 4 certificate chain but also random certificate, should return error": { - inputBundle: joinPEM(root.pem, intA1.pem, leaf.pem, intA2.pem, random.pem), - expPEMBundle: PEMBundle{}, - expErr: true, - }, - "if 6 certificate chain but some are duplicates, duplicates should be removed and return single ca with chain": { - inputBundle: joinPEM(intA2.pem, intA1.pem, root.pem, leaf.pem, intA1.pem, root.pem), - expPEMBundle: PEMBundle{ChainPEM: joinPEM(leaf.pem, intA2.pem, intA1.pem), CAPEM: root.pem}, - expErr: false, - }, - "if 6 certificate chain in different configuration but some are duplicates, duplicates should be removed and return single ca with chain": { - inputBundle: joinPEM(root.pem, intA1.pem, intA2.pem, leaf.pem, root.pem, intA1.pem), - expPEMBundle: PEMBundle{ChainPEM: joinPEM(leaf.pem, intA2.pem, intA1.pem), CAPEM: root.pem}, - expErr: false, - }, - "if certificate chain contains branches, then should error": { - inputBundle: joinPEM(root.pem, intA1.pem, intA2.pem, intB1.pem, intB2.pem), - expPEMBundle: PEMBundle{}, - expErr: true, - }, - "if certificate chain does not have a root ca, should append all intermediates to ChainPEM and use the root-most cert as CAPEM": { - inputBundle: joinPEM(intA1.pem, intA2.pem, leaf.pem), - expPEMBundle: PEMBundle{ChainPEM: joinPEM(leaf.pem, intA2.pem, intA1.pem), CAPEM: intA1.pem}, - expErr: false, - }, - "if only a single leaf certificate was parsed, ChainPEM should contain a single leaf certificate and CAPEM should remain empty": { - inputBundle: joinPEM(leaf.pem), - expPEMBundle: PEMBundle{ChainPEM: joinPEM(leaf.pem), CAPEM: nil}, - expErr: false, - }, - "if only a single intermediate certificate was parsed, ChainPEM should contain a single intermediate certificate and CAPEM should remain empty": { - inputBundle: joinPEM(intA1.pem), - expPEMBundle: PEMBundle{ChainPEM: joinPEM(intA1.pem), CAPEM: nil}, - expErr: false, - }, - "if only a single root certificate was parsed, ChainPEM should contain a single root certificate and CAPEM should also contain that root": { - inputBundle: joinPEM(root.pem), - expPEMBundle: PEMBundle{ChainPEM: joinPEM(root.pem), CAPEM: root.pem}, - expErr: false, - }, - } - - for name, test := range tests { - t.Run(name, func(t *testing.T) { - bundle, err := ParseSingleCertificateChainPEM(test.inputBundle) - if (err != nil) != test.expErr { - t.Errorf("unexpected error, exp=%t got=%v", - test.expErr, err) - } - - if !reflect.DeepEqual(bundle, test.expPEMBundle) { - t.Errorf("unexpected pem bundle, exp=%+s got=%+s", - test.expPEMBundle, bundle) - } - }) - } -} - -func TestMustParseRDN(t *testing.T) { - subject := "SERIALNUMBER=42, L=some-locality, ST=some-state-or-province, STREET=some-street, CN=foo-long.com, OU=FooLong, OU=Barq, OU=Baz, OU=Dept., O=Corp., C=US" - rdnSeq, err := ParseSubjectStringToRdnSequence(subject) - if err != nil { - t.Fatal(err) - } - - expectedRdnSeq := - pkix.RDNSequence{ - []pkix.AttributeTypeAndValue{ - {Type: OIDConstants.Country, Value: "US"}, - }, - []pkix.AttributeTypeAndValue{ - {Type: OIDConstants.Organization, Value: "Corp."}, - }, - []pkix.AttributeTypeAndValue{ - {Type: OIDConstants.OrganizationalUnit, Value: "Dept."}, - }, - []pkix.AttributeTypeAndValue{ - {Type: OIDConstants.OrganizationalUnit, Value: "Baz"}, - }, - []pkix.AttributeTypeAndValue{ - {Type: OIDConstants.OrganizationalUnit, Value: "Barq"}, - }, - []pkix.AttributeTypeAndValue{ - {Type: OIDConstants.OrganizationalUnit, Value: "FooLong"}, - }, - []pkix.AttributeTypeAndValue{ - {Type: OIDConstants.CommonName, Value: "foo-long.com"}, - }, - []pkix.AttributeTypeAndValue{ - {Type: OIDConstants.StreetAddress, Value: "some-street"}, - }, - []pkix.AttributeTypeAndValue{ - {Type: OIDConstants.Province, Value: "some-state-or-province"}, - }, - []pkix.AttributeTypeAndValue{ - {Type: OIDConstants.Locality, Value: "some-locality"}, - }, - []pkix.AttributeTypeAndValue{ - {Type: OIDConstants.SerialNumber, Value: "42"}, - }, - } - - assert.Equal(t, expectedRdnSeq, rdnSeq) -} - -func TestMustKeepOrderInRawDerBytes(t *testing.T) { - subject := "CN=foo-long.com,OU=FooLong,OU=Barq,OU=Baz,OU=Dept.,O=Corp.,C=US" - bytes, err := ParseSubjectStringToRawDerBytes(subject) - if err != nil { - t.Fatal(err) - } - - var rdnSeq pkix.RDNSequence - _, err2 := asn1.Unmarshal(bytes, &rdnSeq) - if err2 != nil { - t.Fatal(err2) - } - - t.Log(bytes) - - expectedRdnSeq := - pkix.RDNSequence{ - []pkix.AttributeTypeAndValue{ - {Type: OIDConstants.Country, Value: "US"}, - }, - []pkix.AttributeTypeAndValue{ - {Type: OIDConstants.Organization, Value: "Corp."}, - }, - []pkix.AttributeTypeAndValue{ - {Type: OIDConstants.OrganizationalUnit, Value: "Dept."}, - }, - []pkix.AttributeTypeAndValue{ - {Type: OIDConstants.OrganizationalUnit, Value: "Baz"}, - }, - []pkix.AttributeTypeAndValue{ - {Type: OIDConstants.OrganizationalUnit, Value: "Barq"}, - }, - []pkix.AttributeTypeAndValue{ - {Type: OIDConstants.OrganizationalUnit, Value: "FooLong"}, - }, - []pkix.AttributeTypeAndValue{ - {Type: OIDConstants.CommonName, Value: "foo-long.com"}, - }, - } - - assert.Equal(t, expectedRdnSeq, rdnSeq) - assert.Equal(t, subject, rdnSeq.String()) -} diff --git a/pkg/util/pki/renewaltime.go b/pkg/util/pki/renewaltime.go new file mode 100644 index 00000000000..6e558abcaef --- /dev/null +++ b/pkg/util/pki/renewaltime.go @@ -0,0 +1,76 @@ +/* +Copyright 2020 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. +*/ + +package pki + +import ( + "time" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// RenewalTimeFunc is a custom function type for calculating renewal time of a certificate. +type RenewalTimeFunc func(time.Time, time.Time, *metav1.Duration, *int32) *metav1.Time + +// RenewalTime calculates renewal time for a certificate. +// If renewBefore is non-nil and less than the certificate's lifetime, renewal +// time will be the computed renewBefore period before expiry. +// If renewBeforePercentage is non-nil and in the range (0,100), renewal time +// will be the computed period before expiry based on the renewBeforePercentage +// value and certificate lifetime. +// Default renewal time is 2/3 through certificate's lifetime. +func RenewalTime(notBefore, notAfter time.Time, renewBefore *metav1.Duration, renewBeforePercentage *int32) *metav1.Time { + // 1. Calculate how long before expiry a cert should be renewed + actualDuration := notAfter.Sub(notBefore) + + actualRenewBefore := RenewBefore(actualDuration, renewBefore, renewBeforePercentage) + + // 2. Calculate when a cert should be renewed + + // Truncate the renewal time to nearest second. This is important + // because the renewal time also gets stored on Certificate's status + // where it is truncated to the nearest second. We use the renewal time + // from Certificate's status to determine when the Certificate will be + // added to the queue to be renewed, but then re-calculate whether it + // needs to be renewed _now_ using this function, so returning a + // non-truncated value here would potentially cause Certificates to be + // re-queued for renewal earlier than the calculated renewal time thus + // causing Certificates to not be automatically renewed. See + // https://github.com/cert-manager/cert-manager/pull/4399. + rt := metav1.NewTime(notAfter.Add(-1 * actualRenewBefore).Truncate(time.Second)) + return &rt +} + +// RenewBefore calculates how far before expiry a certificate should be renewed. +// If renewBefore is non-nil and less than the certificate's lifetime, renewal +// time will be the computed renewBefore period before expiry. +// If renewBeforePercentage is non-nil and in the range (0,100), renewal time +// will be the computed period before expiry based on the renewBeforePercentage +// and actualDuration values. +// Default is 2/3 through certificate's lifetime. +func RenewBefore(actualDuration time.Duration, renewBefore *metav1.Duration, renewBeforePercentage *int32) time.Duration { + // If spec.renewBefore or spec.renewBeforePercentage was set (and is + // valid) respect that. We don't want to prevent users from renewing + // longer lived certs more frequently. + if renewBefore != nil && renewBefore.Duration > 0 && renewBefore.Duration < actualDuration { + return renewBefore.Duration + } else if renewBeforePercentage != nil && *renewBeforePercentage > 0 && *renewBeforePercentage < 100 { + return actualDuration * time.Duration(*renewBeforePercentage) / 100 + } + + // Otherwise, default to renewing 2/3 through certificate's lifetime. + return actualDuration / 3 +} diff --git a/pkg/util/pki/renewaltime_test.go b/pkg/util/pki/renewaltime_test.go new file mode 100644 index 00000000000..7362cd7c6d0 --- /dev/null +++ b/pkg/util/pki/renewaltime_test.go @@ -0,0 +1,143 @@ +/* +Copyright 2020 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. +*/ + +package pki + +import ( + "fmt" + "testing" + "time" + + "github.com/stretchr/testify/assert" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/utils/ptr" +) + +func TestRenewalTime(t *testing.T) { + type scenario struct { + notBefore time.Time + notAfter time.Time + renewBefore *metav1.Duration + renewBeforePct *int32 + expectedRenewalTime *metav1.Time + } + now := time.Now().Truncate(time.Second) + tests := map[string]scenario{ + "short lived cert, spec.renewBefore is not set": { + notBefore: now, + notAfter: now.Add(time.Hour * 3), + renewBefore: nil, + expectedRenewalTime: &metav1.Time{Time: now.Add(time.Hour * 2)}, + }, + "long lived cert, spec.renewBefore is not set": { + notBefore: now, + notAfter: now.Add(time.Hour * 4380), // 6 months + renewBefore: nil, + expectedRenewalTime: &metav1.Time{Time: now.Add(time.Hour * 2920)}, // renew in 4 months + }, + "spec.renewBefore is set": { + notBefore: now, + notAfter: now.Add(time.Hour * 24), + renewBefore: &metav1.Duration{Duration: time.Hour * 20}, + expectedRenewalTime: &metav1.Time{Time: now.Add(time.Hour * 4)}, + }, + "long lived cert, spec.renewBefore is set to renew every day": { + notBefore: now, + notAfter: now.Add(time.Hour * 730), // 1 month + renewBefore: &metav1.Duration{Duration: time.Hour * 706}, // 1 month - 1 day + expectedRenewalTime: &metav1.Time{Time: now.Add(time.Hour * 24)}, + }, + "spec.renewBefore is set, but would result in renewal time after expiry": { + notBefore: now, + notAfter: now.Add(time.Hour * 24), + renewBefore: &metav1.Duration{Duration: time.Hour * 25}, + expectedRenewalTime: &metav1.Time{Time: now.Add(time.Hour * 16)}, + }, + "long lived cert, spec.renewBeforePercentage is set to renew 30% before expiry": { + notBefore: now, + notAfter: now.Add(time.Hour * 730), // 1 month + renewBeforePct: ptr.To(int32(30)), + expectedRenewalTime: &metav1.Time{Time: now.Add(time.Hour * 511)}, // 70% of 1 month + }, + // This test case is here to show the scenario where users set + // renewBefore to very slightly less than actual duration. This + // will result in cert being renewed 'continuously'. + "spec.renewBefore is set to a value slightly less than cert's duration": { + notBefore: now, + notAfter: now.Add(time.Hour*24 + time.Minute*3), + renewBefore: &metav1.Duration{Duration: time.Hour * 24}, + expectedRenewalTime: &metav1.Time{Time: now.Add(time.Minute * 3)}, // renew in 3 minutes + }, + // This test case is here to guard against an earlier bug where + // a non-truncated renewal time returned from this function + // caused certs to not be renewed. + // See https://github.com/cert-manager/cert-manager/pull/4399 + "certificate's duration is skewed by a second": { + notBefore: now, + notAfter: now.Add(time.Hour * 24).Add(time.Second * -1), + expectedRenewalTime: &metav1.Time{Time: now.Add(time.Hour * 16).Add(time.Second * -1)}, + }, + } + for n, s := range tests { + t.Run(n, func(t *testing.T) { + renewalTime := RenewalTime(s.notBefore, s.notAfter, s.renewBefore, s.renewBeforePct) + assert.Equal(t, s.expectedRenewalTime, renewalTime, fmt.Sprintf("Expected renewal time: %v got: %v", s.expectedRenewalTime, renewalTime)) + }) + } +} + +func TestRenewBefore(t *testing.T) { + const duration = time.Hour * 3 + + type scenario struct { + renewBefore *metav1.Duration + renewBeforePct *int32 + expectedRenewBefore time.Duration + } + + tests := map[string]scenario{ + "spec.renewBefore and spec.renewBeforePercentage are not set": { + renewBefore: nil, + expectedRenewBefore: time.Hour, + }, + "spec.renewBeforePercentage is valid": { + renewBeforePct: ptr.To(int32(25)), + expectedRenewBefore: 45 * time.Minute, + }, + "spec.renewBeforePercentage is too large so default is used": { + renewBeforePct: ptr.To(int32(100)), + expectedRenewBefore: time.Hour, + }, + "spec.renewBeforePercentage is too small so default is used": { + renewBeforePct: ptr.To(int32(0)), + expectedRenewBefore: time.Hour, + }, + "spec.renewBefore is valid": { + renewBefore: &metav1.Duration{Duration: time.Hour * 1}, + expectedRenewBefore: time.Hour, + }, + "spec.renewBefore is invalid so default is used": { + renewBefore: &metav1.Duration{Duration: time.Hour * 4}, + expectedRenewBefore: time.Hour, + }, + } + for n, s := range tests { + t.Run(n, func(t *testing.T) { + renewBefore := RenewBefore(duration, s.renewBefore, s.renewBeforePct) + assert.Equal(t, s.expectedRenewBefore, renewBefore, fmt.Sprintf("Expected renewBefore time: %v got: %v", s.expectedRenewBefore, renewBefore)) + }) + } +} diff --git a/pkg/util/pki/sans.go b/pkg/util/pki/sans.go new file mode 100644 index 00000000000..b5005c63909 --- /dev/null +++ b/pkg/util/pki/sans.go @@ -0,0 +1,284 @@ +/* +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. +*/ + +package pki + +import ( + "crypto/x509/pkix" + "encoding/asn1" + "errors" + "fmt" + "net" + "strconv" +) + +// Copied from x509.go +var ( + oidExtensionSubjectAltName = []int{2, 5, 29, 17} +) + +// Based on RFC 5280, section 4.2.1.6 +// see https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.6 +/* + OtherName ::= SEQUENCE { + type-id OBJECT IDENTIFIER, + value [0] EXPLICIT ANY DEFINED BY type-id } +*/ +type OtherName struct { + TypeID asn1.ObjectIdentifier + Value asn1.RawValue `asn1:"tag:0,explicit"` +} + +// Based on RFC 5280, section 4.2.1.6 +// see https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.6 +/* + EDIPartyName ::= SEQUENCE { + nameAssigner [0] DirectoryString OPTIONAL, + partyName [1] DirectoryString } +*/ +type EDIPartyName struct { + NameAssigner string `asn1:"tag:0,optional"` + PartyName string `asn1:"tag:1"` +} + +// Based on RFC 5280, section 4.2.1.6 +// see https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.6 +/* + GeneralName ::= CHOICE { + otherName [0] OtherName, + rfc822Name [1] IA5String, + dnsName [2] IA5String, + x400Address [3] ORAddress, + directoryName [4] Name, + ediPartyName [5] EDIPartyName, + uniformResourceIdentifier [6] IA5String, + ipAddress [7] OCTET STRING, + registeredID [8] OBJECT IDENTIFIER } +*/ +const ( + nameTypeOtherName = 0 + nameTypeRFC822Name = 1 + nameTypeDNSName = 2 + nameTypeX400Address = 3 + nameTypeDirectoryName = 4 + nameTypeEDIPartyName = 5 + nameTypeUniformResourceIdentifier = 6 + nameTypeIPAddress = 7 + nameTypeRegisteredID = 8 +) + +type GeneralNames struct { + OtherNames []OtherName + RFC822Names []string + DNSNames []string + X400Addresses []asn1.RawValue + DirectoryNames []pkix.RDNSequence + EDIPartyNames []EDIPartyName + UniformResourceIdentifiers []string + IPAddresses []net.IP + RegisteredIDs []asn1.ObjectIdentifier +} + +func (gns GeneralNames) Empty() bool { + return len(gns.OtherNames) == 0 && + len(gns.RFC822Names) == 0 && + len(gns.DNSNames) == 0 && + len(gns.X400Addresses) == 0 && + len(gns.DirectoryNames) == 0 && + len(gns.EDIPartyNames) == 0 && + len(gns.UniformResourceIdentifiers) == 0 && + len(gns.IPAddresses) == 0 && + len(gns.RegisteredIDs) == 0 +} + +// adapted from https://cs.opensource.google/go/go/+/master:src/crypto/x509/parser.go;l=373-416;drc=16d3040a84be821d801b75bd1a3d8ab4cc89ee36 +func UnmarshalSANs(value []byte) (GeneralNames, error) { + var gns GeneralNames + err := forEachSAN(value, func(v asn1.RawValue) error { + switch v.Tag { + case nameTypeOtherName: + var otherName OtherName + if _, err := asn1.UnmarshalWithParams(v.FullBytes, &otherName, fmt.Sprintf("tag:%d", nameTypeOtherName)); err != nil { + return err + } + gns.OtherNames = append(gns.OtherNames, otherName) + case nameTypeRFC822Name: + email := string(v.Bytes) + if err := isIA5String(email); err != nil { + return errors.New("x509: SAN rfc822Name is malformed") + } + gns.RFC822Names = append(gns.RFC822Names, email) + case nameTypeDNSName: + name := string(v.Bytes) + if err := isIA5String(name); err != nil { + return errors.New("x509: SAN dNSName is malformed") + } + gns.DNSNames = append(gns.DNSNames, name) + case nameTypeX400Address: + gns.X400Addresses = append(gns.X400Addresses, v) + case nameTypeDirectoryName: + var rdn pkix.RDNSequence + if _, err := asn1.UnmarshalWithParams(v.FullBytes, &rdn, fmt.Sprintf("tag:%d", nameTypeDirectoryName)); err != nil { + return err + } + gns.DirectoryNames = append(gns.DirectoryNames, rdn) + case nameTypeEDIPartyName: + var edipn EDIPartyName + if _, err := asn1.UnmarshalWithParams(v.FullBytes, &edipn, fmt.Sprintf("tag:%d", nameTypeEDIPartyName)); err != nil { + return err + } + gns.EDIPartyNames = append(gns.EDIPartyNames, edipn) + case nameTypeUniformResourceIdentifier: + uriStr := string(v.Bytes) + if err := isIA5String(uriStr); err != nil { + return errors.New("x509: SAN uniformResourceIdentifier is malformed") + } + gns.UniformResourceIdentifiers = append(gns.UniformResourceIdentifiers, uriStr) + case nameTypeIPAddress: + switch len(v.Bytes) { + case net.IPv4len, net.IPv6len: + gns.IPAddresses = append(gns.IPAddresses, v.Bytes) + default: + return errors.New("x509: cannot parse IP address of length " + strconv.Itoa(len(v.Bytes))) + } + case nameTypeRegisteredID: + var oid asn1.ObjectIdentifier + if _, err := asn1.UnmarshalWithParams(v.FullBytes, &oid, fmt.Sprintf("tag:%d", nameTypeRegisteredID)); err != nil { + return err + } + gns.RegisteredIDs = append(gns.RegisteredIDs, oid) + default: + return asn1.StructuralError{Msg: "bad SAN choice"} + } + + return nil + }) + + return gns, err +} + +func forEachSAN(extension []byte, callback func(v asn1.RawValue) error) error { + var seq asn1.RawValue + rest, err := asn1.Unmarshal(extension, &seq) + if err != nil { + return err + } else if len(rest) != 0 { + return fmt.Errorf("x509: trailing data after X.509 extension") + } + if !seq.IsCompound || seq.Tag != asn1.TagSequence || seq.Class != asn1.ClassUniversal { + return asn1.StructuralError{Msg: "bad SAN sequence"} + } + + rest = seq.Bytes + for len(rest) > 0 { + var v asn1.RawValue + rest, err = asn1.Unmarshal(rest, &v) + if err != nil { + return err + } + + if err := callback(v); err != nil { + return err + } + } + + return nil +} + +// adapted from https://cs.opensource.google/go/go/+/master:src/crypto/x509/x509.go;l=1059-1103;drc=e2d9574b14b3db044331da0c6fadeb62315c644a +// MarshalSANs marshals a list of addresses into the contents of an X.509 +// SubjectAlternativeName extension. +func MarshalSANs(gns GeneralNames, hasSubject bool) (pkix.Extension, error) { + var rawValues []asn1.RawValue + addMarshalable := func(tag int, val interface{}) error { + fullBytes, err := asn1.MarshalWithParams(val, fmt.Sprint("tag:", tag)) + if err != nil { + return err + } + rawValues = append(rawValues, asn1.RawValue{FullBytes: fullBytes}) + return nil + } + addIA5String := func(tag int, val string) error { + if err := isIA5String(val); err != nil { + return fmt.Errorf("x509: %q cannot be encoded as an IA5String", val) + } + rawValues = append(rawValues, asn1.RawValue{Tag: tag, Class: asn1.ClassContextSpecific, Bytes: []byte(val)}) + return nil + } + + // Maintain the order of the SANs as produced by the Go x509 library. + for _, val := range gns.DNSNames { + if err := addIA5String(nameTypeDNSName, val); err != nil { + return pkix.Extension{}, err + } + } + for _, val := range gns.RFC822Names { + if err := addIA5String(nameTypeRFC822Name, val); err != nil { + return pkix.Extension{}, err + } + } + for _, rawIP := range gns.IPAddresses { + // If possible, we always want to encode IPv4 addresses in 4 bytes. + ip := rawIP.To4() + if ip == nil { + ip = rawIP + } + rawValues = append(rawValues, asn1.RawValue{Tag: nameTypeIPAddress, Class: asn1.ClassContextSpecific, Bytes: ip}) + } + for _, val := range gns.UniformResourceIdentifiers { + if err := addIA5String(nameTypeUniformResourceIdentifier, val); err != nil { + return pkix.Extension{}, err + } + } + + // Add support for the remaining SAN types. + for _, val := range gns.OtherNames { + if err := addMarshalable(nameTypeOtherName, val); err != nil { + return pkix.Extension{}, err + } + } + for _, val := range gns.X400Addresses { + if err := addMarshalable(nameTypeX400Address, val); err != nil { + return pkix.Extension{}, err + } + } + for _, val := range gns.DirectoryNames { + if err := addMarshalable(nameTypeDirectoryName, val); err != nil { + return pkix.Extension{}, err + } + } + for _, val := range gns.EDIPartyNames { + if err := addMarshalable(nameTypeEDIPartyName, val); err != nil { + return pkix.Extension{}, err + } + } + for _, val := range gns.RegisteredIDs { + if err := addMarshalable(nameTypeRegisteredID, val); err != nil { + return pkix.Extension{}, err + } + } + + byteValue, err := asn1.Marshal(rawValues) + if err != nil { + return pkix.Extension{}, err + } + + return pkix.Extension{ + Id: oidExtensionSubjectAltName, + Critical: !hasSubject, + Value: byteValue, + }, nil +} diff --git a/pkg/util/pki/sans_test.go b/pkg/util/pki/sans_test.go new file mode 100644 index 00000000000..5567685f858 --- /dev/null +++ b/pkg/util/pki/sans_test.go @@ -0,0 +1,241 @@ +/* +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. +*/ + +package pki + +import ( + "crypto/x509" + "crypto/x509/pkix" + "encoding/asn1" + "reflect" + "testing" + + "github.com/cert-manager/cert-manager/internal/pem" +) + +func extractSANsFromCertificate(t *testing.T, certDER string) pkix.Extension { + block, rest, err := pem.SafeDecodeSingleCertificate([]byte(certDER)) + if err != nil { + t.Fatalf("expected no PEM decode err but got %s", err) + } + + if len(rest) > 0 { + t.Fatal("Expected no rest") + } + + cert, err := x509.ParseCertificate(block.Bytes) + if err != nil { + t.Fatalf("certificate.ParseCertificate returned an error: %v", err) + } + + for _, extension := range cert.Extensions { + if extension.Id.Equal(oidExtensionSubjectAltName) { + return extension + } + } + + t.Fatal("Could not find SANs in certificate") + return pkix.Extension{} +} + +func extractSANsFromCertificateRequest(t *testing.T, csrDER string) pkix.Extension { + block, rest, err := pem.SafeDecodeCSR([]byte(csrDER)) + if err != nil { + t.Fatalf("expected no PEM decode err but got %s", err) + } + + if len(rest) > 0 { + t.Fatal("Expected no rest") + } + + csr, err := x509.ParseCertificateRequest(block.Bytes) + if err != nil { + t.Fatalf("certificate.ParseCertificate returned an error: %v", err) + } + + for _, extension := range csr.Extensions { + if extension.Id.Equal(oidExtensionSubjectAltName) { + return extension + } + } + + t.Fatal("Could not find SANs in certificate") + return pkix.Extension{} +} + +func generateOtherName(t *testing.T, val UniversalValue) asn1.RawValue { + bytes, err := MarshalUniversalValue(val) + if err != nil { + t.Fatalf("MarshalUniversalValue returned an error: %v", err) + } + + rv := asn1.RawValue{ + Tag: 0, + Class: asn1.ClassContextSpecific, + IsCompound: true, + Bytes: bytes, + } + + fullBytes, err := asn1.Marshal(rv) + if err != nil { + t.Fatalf("asn1.Marshal returned an error: %v", err) + } + rv.FullBytes = fullBytes + + return rv +} + +func TestMarshalAndUnmarshalSANs(t *testing.T) { + type testCase struct { + hasSubject bool + gns GeneralNames + sanExtension pkix.Extension + } + + type testCases map[string]testCase + + testcases := testCases{ + "OtherName simple test": { + hasSubject: true, + gns: GeneralNames{ + OtherNames: []OtherName{ + { + TypeID: asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 311, 20, 2, 3}, + Value: generateOtherName(t, UniversalValue{ + UTF8String: "3goats@acme.com", + }), + }, + }, + }, + sanExtension: extractSANsFromCertificateRequest(t, `-----BEGIN CERTIFICATE REQUEST----- +MIICnDCCAYQCAQAwGjEYMBYGA1UEAwwPM2dvYXRzLmFjbWUuY29tMIIBIjANBgkqhkiG9w0BAQEF +AAOCAQ8AMIIBCgKCAQEAsMWNfjdYm8jr57nMrs3ubdS20GDTcLzyu2KQqhGFCMY7COaVCP9ndZVv +nFv7q2LRB8P5MA9ROYNAXqgrF9CatWiaL1WaB3A5VICj3M9iQnaPw7XpZJW+GvZTltDOWhW0kPSW +3aQidsVocPGol2Co1qVrD3GXu610+EgDkSkyEI2/rMJPtjYf9OSuZoHeZn8xzny6+nlFQKVhHQ16 +3blPkkrKMe6KQApGs49x9HvQAUT7UfMIb4btQMW/6+wQfWC/t0y0IsRU0fLiOr6+r4jYKAhewSEF +Pii4y4ds9GK3ZziaXPxPlDonyzezePJUiTRHJY/HEHnkmo+VX3rpzVdTFwIDAQABoD0wOwYJKoZI +hvcNAQkOMS4wLDAqBgNVHREEIzAhoB8GCisGAQQBgjcUAgOgEQwPM2dvYXRzQGFjbWUuY29tMA0G +CSqGSIb3DQEBCwUAA4IBAQABLr+BhRi4/Kb86kt2aO7J3FxdlPaEG6aUCxcbXkW5sGzxcmT2BSJQ +k2zDDu6t4paFV8sdWspb3IFdnF4loG/PKOaBOjXcfyaBk5mXWIcb7N/QhKHtgc79yPf3ywW/+FUy +97aNCtcyGuz54GRgGI/VValnQBjqoZ7cqPdb+TmSu8Zmn3hfF5Evs9AKWLaHBkPcb8//qQJFlqc3 +Vr7q+PwwKejeH83BzE0jKW3l95no6H0M3Ng5trzS7aooD/24xe6lzRc1NnHJ3/mXVk9BvPu1H6yP +KkR5sV2iISL9klJn+YmoLOcr92mg/WfSE3bvaDYnjEGiunSNh+nZlBcRZVUA +-----END CERTIFICATE REQUEST-----`), + }, + "OtherName + RFC822 email SAN set": { + hasSubject: true, + gns: GeneralNames{ + OtherNames: []OtherName{ + { + TypeID: asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 311, 20, 2, 3}, + Value: generateOtherName(t, UniversalValue{ + UTF8String: "upn@domain.test", + }), + }, + }, + RFC822Names: []string{"email@domain.test"}, + }, + sanExtension: extractSANsFromCertificateRequest(t, ` +generated with: openssl req -nodes -newkey rsa:2048 -subj "/CN=someCN" \ + -addext 'subjectAltName=email:email@domain.test,otherName:msUPN;UTF8:upn@domain.test' +-----BEGIN CERTIFICATE REQUEST----- +MIICpjCCAY4CAQAwETEPMA0GA1UEAwwGc29tZUNOMIIBIjANBgkqhkiG9w0BAQEF +AAOCAQ8AMIIBCgKCAQEAt9fJR9OCqfWo6BUNYi70biX4tLhR3bgzbNAiNG6gE/UK +6JCmVCFpMwdR2p+DluHDysU7+QKp7BBMe6AcZrGs4ru7aWvS8quZnsVlPPxhJHh8 +TjoazO39Qte6CyqIVLkWdc8P65I2jlMeua1qPg8+jx5Pd65UNiop1Abmj6CU3e6t +m79AFQ/3AEa1XTVdQw/PjAgixW+cLpdNYeTbK7r9EncHdtTFcFZVR26ZWfDvs4I8 +Rx9wi5kgL2eB3XNKxg95CUjhCY/wfyVYI2xCBTDQgyx33YLLQotjf30ZbKXRQgjd +eFVsUNNfVn8f6uZHAJaWZWVMMDTZsNQ/IhD7YLc02wIDAQABoFAwTgYJKoZIhvcN +AQkOMUEwPzA9BgNVHREENjA0gRFlbWFpbEBkb21haW4udGVzdKAfBgorBgEEAYI3 +FAIDoBEMD3VwbkBkb21haW4udGVzdDANBgkqhkiG9w0BAQsFAAOCAQEAXVF6VfHO +qAIxnlWIUnc9SyxaUqr5WvCkJfvgIahA6/GvQXo+QVH/6kr3tRXAjWf8nPQ4QirV +55MQFCcJtNo/RIv+KZoudCCeegv2lCVDU9fGe8hGAw+XWUqSlTnWywNaLuY1BvdV +r7h5deMc4OSTOgYqPlu8JMmxwrb7Gm5ea+UYtxjcmG+ROB2B3via+g2uwNp27cKh +v1PJQs8lq4K/CPuRoMhhgQpYAazYkcHAdCmDq3jGYUE/Ax2vbjJNWxyLRUtLpupE +/VTkJMD/ggF2y4I6ZLYFWeJ/zVqHw19c4suIuR4atYGk3JCHtNgHzdfxDs6Ky0+A +f1fD+Pn5lU6rAA== +-----END CERTIFICATE REQUEST----- +`), + }, + "OtherName byte literal": { + hasSubject: true, + gns: GeneralNames{ + OtherNames: []OtherName{ + { + TypeID: asn1.ObjectIdentifier{1, 3, 6, 1, 5, 2, 2}, + Value: generateOtherName(t, UniversalValue{ + Bytes: []byte{ + 0x30, 0x2f, 0xa0, 0x10, 0x1b, 0xe, 0x59, 0x4f, + 0x55, 0x52, 0x5f, 0x52, 0x45, 0x41, 0x4c, 0x4d, + 0x4e, 0x41, 0x4d, 0x45, 0xa1, 0x1b, 0x30, 0x19, + 0xa0, 0x3, 0x2, 0x1, 0x1, 0xa1, 0x12, 0x30, 0x10, + 0x1b, 0xe, 0x59, 0x4f, 0x55, 0x52, 0x5f, 0x50, + 0x52, 0x49, 0x4e, 0x43, 0x4e, 0x41, 0x4d, 0x45, + }, + }), + }, + }, + }, + sanExtension: extractSANsFromCertificate(t, `-----BEGIN CERTIFICATE----- +MIID2zCCAsOgAwIBAgIUKGdEqu7o6HfNYvNzRMqA5MFvuK4wDQYJKoZIhvcNAQEL +BQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM +GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0yMzEwMzExMDExMzJaFw0yNDEw +MzAxMDExMzJaMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEw +HwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwggEiMA0GCSqGSIb3DQEB +AQUAA4IBDwAwggEKAoIBAQCjuph5kaTvvd2xJ0HYFWrxjcLyISQCkucqIVP1YLTB +vvK+SwrXjGAOyPYUnL8NTTrIPGUuuMik8xKdYS2Fbn57Pse8TateYIB7y3tiPi1O +KEkEB16wam+HpqG8U273lAl8C2chwEnR7MnaYrOmiDK6j8uUgaeEDa7lAth05xNt +bkknPzT6xy30PC4wvhg55RsRdAJON1CVEKa/DzIHpgKEuSnIBV75NavIq9NF6MYd +RquvY6bPXRK0Yy/A/I4qwrnSKTW2aPJewRmXWKQnps+ohS9+ZCTme3+2cjJwL6dq +91qVZbPBgrU5v+CXD9+VteYFNyxYPrqR22hjI7taeKGnAgMBAAGjgcIwgb8wCQYD +VR0TBAIwADALBgNVHQ8EBAMCA6gwEgYDVR0lBAswCQYHKwYBBQIDBDAdBgNVHQ4E +FgQUbL4ZtZgpxz/nZDFv0d1Qhot3GFQwHwYDVR0jBBgwFoAUPRnKJ8PE+qJx95jJ +x7px6H6A53AwCQYDVR0SBAIwADBGBgNVHREEPzA9oDsGBisGAQUCAqAxMC+gEBsO +WU9VUl9SRUFMTU5BTUWhGzAZoAMCAQGhEjAQGw5ZT1VSX1BSSU5DTkFNRTANBgkq +hkiG9w0BAQsFAAOCAQEAU9Xlhsh8tp8psdyeQj3YcFgR/4dpy+TmIUToP+deukUQ +cpzev6e+tMtBwWwVJFuY3d5SVQBhrMF1x4/CmusCA6JuDrYKaCJGPuURvSaZ/CNb +fWuE/tdh1DxR20x4JruTiDpy3tVswAnOWKv6TWCqmdo9HydnLVx+7nXcbyzbZ8lX +U8GrBNFMcOI3rpYTeQWjzSbr2gGeM59CVlPqgLbG2WcN6bBSJDfiPk6rPGthzfph +jsDo7Ui1glzZOaHat9f17nMxpgTM8l+oqexvcUnZ+Cfr+FBRWkRNLsxBdOOPoBqY +wWy44hfcegrvch51oNMscwQ5NCJRGYI6q3T9yexVug== +-----END CERTIFICATE-----`), + }, + } + + for testName, tc := range testcases { + { + extension, err := MarshalSANs(tc.gns, tc.hasSubject) + if err != nil { + t.Errorf("test: %s MarshalSANs returned an error: %v", testName, err) + } + + if !reflect.DeepEqual(extension, tc.sanExtension) { + t.Errorf("test: %s Expected extension: %v, got: %v", testName, tc.sanExtension, extension) + } + } + + { + gns, err := UnmarshalSANs(tc.sanExtension.Value) + if err != nil { + t.Errorf("test: %s UnmarshalSANs returned an error: %v", testName, err) + } + + if !reflect.DeepEqual(gns, tc.gns) { + t.Errorf("test: %s Expected GeneralNames: %v, got: %v", testName, tc.gns, gns) + } + } + } +} diff --git a/pkg/util/pki/subject.go b/pkg/util/pki/subject.go new file mode 100644 index 00000000000..87ebe7f0659 --- /dev/null +++ b/pkg/util/pki/subject.go @@ -0,0 +1,139 @@ +/* +Copyright 2020 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. +*/ + +package pki + +import ( + "bytes" + "crypto/x509/pkix" + "encoding/asn1" + "errors" + + "github.com/go-ldap/ldap/v3" +) + +var OIDConstants = struct { + Country []int + Organization []int + OrganizationalUnit []int + CommonName []int + SerialNumber []int + Locality []int + Province []int + StreetAddress []int + DomainComponent []int + UniqueIdentifier []int +}{ + Country: []int{2, 5, 4, 6}, + Organization: []int{2, 5, 4, 10}, + OrganizationalUnit: []int{2, 5, 4, 11}, + CommonName: []int{2, 5, 4, 3}, + SerialNumber: []int{2, 5, 4, 5}, + Locality: []int{2, 5, 4, 7}, + Province: []int{2, 5, 4, 8}, + StreetAddress: []int{2, 5, 4, 9}, + DomainComponent: []int{0, 9, 2342, 19200300, 100, 1, 25}, + UniqueIdentifier: []int{0, 9, 2342, 19200300, 100, 1, 1}, +} + +// Copied from pkix.attributeTypeNames and inverted. (Sadly it is private.) +// Source: https://cs.opensource.google/go/go/+/refs/tags/go1.18.2:src/crypto/x509/pkix/pkix.go;l=26 +// Added RDNs identifier to support rfc4514 LDAP certificates, cf https://github.com/cert-manager/cert-manager/issues/5582 +var attributeTypeNames = map[string][]int{ + "C": OIDConstants.Country, + "O": OIDConstants.Organization, + "OU": OIDConstants.OrganizationalUnit, + "CN": OIDConstants.CommonName, + "SERIALNUMBER": OIDConstants.SerialNumber, + "L": OIDConstants.Locality, + "ST": OIDConstants.Province, + "STREET": OIDConstants.StreetAddress, + "DC": OIDConstants.DomainComponent, + "UID": OIDConstants.UniqueIdentifier, +} + +func UnmarshalSubjectStringToRDNSequence(subject string) (pkix.RDNSequence, error) { + dn, err := ldap.ParseDN(subject) + if err != nil { + return nil, err + } + + // Traverse the parsed RDNSequence in REVERSE order as RDNs in String format are expected to be written in reverse order. + // Meaning, a string of "CN=Foo,OU=Bar,O=Baz" actually should have "O=Baz" as the first element in the RDNSequence. + rdns := make(pkix.RDNSequence, 0, len(dn.RDNs)) + for i := range dn.RDNs { + ldapRelativeDN := dn.RDNs[len(dn.RDNs)-i-1] + + atvs := make([]pkix.AttributeTypeAndValue, 0, len(ldapRelativeDN.Attributes)) + for _, ldapATV := range ldapRelativeDN.Attributes { + oid, ok := attributeTypeNames[ldapATV.Type] + if !ok { + // If the attribute type is not known, we try to parse it as an OID. + // If it is not an OID, we set Type=nil + + oid, err = ParseObjectIdentifier(ldapATV.Type) + if err != nil { + oid = nil + } + } + + atvs = append(atvs, pkix.AttributeTypeAndValue{ + Type: oid, + Value: ldapATV.Value, + }) + } + rdns = append(rdns, atvs) + } + return rdns, nil +} + +func IsASN1SubjectEmpty(asn1Subject []byte) bool { + // emptyASN1Subject is the ASN.1 DER encoding of an empty Subject, which is + // just an empty SEQUENCE. + var emptyASN1Subject = []byte{0x30, 0} + + return bytes.Equal(asn1Subject, emptyASN1Subject) +} + +func MarshalRDNSequenceToRawDERBytes(rdnSequence pkix.RDNSequence) ([]byte, error) { + return asn1.Marshal(rdnSequence) +} + +func UnmarshalRawDerBytesToRDNSequence(der []byte) (rdnSequence pkix.RDNSequence, err error) { + var rest []byte + + if rest, err = asn1.Unmarshal(der, &rdnSequence); err != nil { + return rdnSequence, err + } else if len(rest) != 0 { + return rdnSequence, errors.New("RDNSequence: trailing data after Subject") + } else { + return rdnSequence, nil + } +} + +func ExtractCommonNameFromRDNSequence(rdns pkix.RDNSequence) string { + for _, rdn := range rdns { + for _, atv := range rdn { + if atv.Type.Equal(OIDConstants.CommonName) { + if str, ok := atv.Value.(string); ok { + return str + } + } + } + } + + return "" +} diff --git a/pkg/util/pki/subject_test.go b/pkg/util/pki/subject_test.go new file mode 100644 index 00000000000..a2d7bb0b094 --- /dev/null +++ b/pkg/util/pki/subject_test.go @@ -0,0 +1,225 @@ +/* +Copyright 2024 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. +*/ + +package pki + +import ( + "crypto/x509/pkix" + "encoding/asn1" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestMustParseRDN(t *testing.T) { + subject := "SERIALNUMBER=42, L=some-locality, ST=some-state-or-province, STREET=some-street, CN=foo-long.com, OU=FooLong, OU=Barq, OU=Baz, OU=Dept., O=Corp., C=US+123.544.555= A Test Value " + rdnSeq, err := UnmarshalSubjectStringToRDNSequence(subject) + if err != nil { + t.Fatal(err) + } + + expectedRdnSeq := + pkix.RDNSequence{ + []pkix.AttributeTypeAndValue{ + {Type: OIDConstants.Country, Value: "US"}, + {Type: asn1.ObjectIdentifier{123, 544, 555}, Value: "A Test Value"}, + }, + []pkix.AttributeTypeAndValue{ + {Type: OIDConstants.Organization, Value: "Corp."}, + }, + []pkix.AttributeTypeAndValue{ + {Type: OIDConstants.OrganizationalUnit, Value: "Dept."}, + }, + []pkix.AttributeTypeAndValue{ + {Type: OIDConstants.OrganizationalUnit, Value: "Baz"}, + }, + []pkix.AttributeTypeAndValue{ + {Type: OIDConstants.OrganizationalUnit, Value: "Barq"}, + }, + []pkix.AttributeTypeAndValue{ + {Type: OIDConstants.OrganizationalUnit, Value: "FooLong"}, + }, + []pkix.AttributeTypeAndValue{ + {Type: OIDConstants.CommonName, Value: "foo-long.com"}, + }, + []pkix.AttributeTypeAndValue{ + {Type: OIDConstants.StreetAddress, Value: "some-street"}, + }, + []pkix.AttributeTypeAndValue{ + {Type: OIDConstants.Province, Value: "some-state-or-province"}, + }, + []pkix.AttributeTypeAndValue{ + {Type: OIDConstants.Locality, Value: "some-locality"}, + }, + []pkix.AttributeTypeAndValue{ + {Type: OIDConstants.SerialNumber, Value: "42"}, + }, + } + + assert.Equal(t, expectedRdnSeq, rdnSeq) +} + +func TestMustKeepOrderInRawDerBytes(t *testing.T) { + subject := "CN=foo-long.com,OU=FooLong,OU=Barq,OU=Baz,OU=Dept.,O=Corp.,C=US" + rdnSeq, err := UnmarshalSubjectStringToRDNSequence(subject) + if err != nil { + t.Fatal(err) + } + + expectedRdnSeq := + pkix.RDNSequence{ + []pkix.AttributeTypeAndValue{ + {Type: OIDConstants.Country, Value: "US"}, + }, + []pkix.AttributeTypeAndValue{ + {Type: OIDConstants.Organization, Value: "Corp."}, + }, + []pkix.AttributeTypeAndValue{ + {Type: OIDConstants.OrganizationalUnit, Value: "Dept."}, + }, + []pkix.AttributeTypeAndValue{ + {Type: OIDConstants.OrganizationalUnit, Value: "Baz"}, + }, + []pkix.AttributeTypeAndValue{ + {Type: OIDConstants.OrganizationalUnit, Value: "Barq"}, + }, + []pkix.AttributeTypeAndValue{ + {Type: OIDConstants.OrganizationalUnit, Value: "FooLong"}, + }, + []pkix.AttributeTypeAndValue{ + {Type: OIDConstants.CommonName, Value: "foo-long.com"}, + }, + } + + assert.Equal(t, expectedRdnSeq, rdnSeq) + assert.Equal(t, subject, rdnSeq.String()) +} + +func TestShouldFailForHexDER(t *testing.T) { + _, err := UnmarshalSubjectStringToRDNSequence("DF=#6666666666665006838820013100000746939546349182108463491821809FBFFFFFFFFF") + if err == nil { + t.Fatal("expected error, but got none") + } + + assert.Contains(t, err.Error(), "failed to decode BER encoding: unexpected EOF") +} + +// TestRoundTripRDNSequence tests a set of RDNSequences to ensure that they are +// the same after a round trip through String() and UnmarshalSubjectStringToRDNSequence(). +func TestRoundTripRDNSequence(t *testing.T) { + type testCase struct { + name string + rdn pkix.RDNSequence + } + rdnSequences := []testCase{ + { + name: "Simple RDNSequence", + rdn: pkix.RDNSequence{ + []pkix.AttributeTypeAndValue{ + {Type: OIDConstants.Organization, Value: "Corp."}, + {Type: OIDConstants.OrganizationalUnit, Value: "FooLong"}, + }, + }, + }, + { + name: "Character Escaping", + rdn: pkix.RDNSequence{ + []pkix.AttributeTypeAndValue{ + {Type: OIDConstants.CommonName, Value: "foo-lon❤️\\g.com "}, + {Type: OIDConstants.OrganizationalUnit, Value: "Foo===Long"}, + {Type: OIDConstants.OrganizationalUnit, Value: "Ba rq"}, + {Type: OIDConstants.OrganizationalUnit, Value: "Baz"}, + {Type: OIDConstants.Country, Value: "fo\x00o-long.com"}, + }, + []pkix.AttributeTypeAndValue{ + {Type: OIDConstants.Organization, Value: "C; orp."}, + {Type: OIDConstants.Country, Value: "US"}, + }, + }, + }, + { + name: "Numeric OID", + rdn: pkix.RDNSequence{ + []pkix.AttributeTypeAndValue{ + {Type: asn1.ObjectIdentifier{0, 5, 80, 99, 58962185}, Value: "String Value"}, + }, + }, + }, + } + + for _, tc := range rdnSequences { + t.Run(tc.name, func(t *testing.T) { + newRDNSeq, err := UnmarshalSubjectStringToRDNSequence(tc.rdn.String()) + if err != nil { + t.Fatal(err) + } + + assert.Equal(t, tc.rdn, newRDNSeq) + }) + } +} + +// FuzzRoundTripRDNSequence fuzzes the UnmarshalSubjectStringToRDNSequence function +// by generating random subject strings and for each successfully parsed RDNSequence, +// it will ensure that the round trip through String() and UnmarshalSubjectStringToRDNSequence() +// results in the same RDNSequence. +func FuzzRoundTripRDNSequence(f *testing.F) { + f.Add("CN=foo-long.com,OU=FooLong,OU=Barq,OU=Baz,OU=Dept.,O=Corp.,C=US") + f.Add("CN=foo-lon❤️\\,g.com,OU=Foo===Long,OU=Ba # rq,OU=Baz,O=C\\; orp.,C=US") + f.Add("CN=fo\x00o-long.com,OU=\x04FooLong") + f.Add("1.2.3.4=String Value") + f.Add("1.3.6.1.4.1.1466.0=#04024869") + + f.Fuzz(func(t *testing.T, subjectString string) { + t.Parallel() + rdnSeq, err := UnmarshalSubjectStringToRDNSequence(subjectString) + if err != nil { + t.Skip() + } + + hasSpecialChar := func(s string) bool { + for _, char := range s { + if char < ' ' || char > '~' { + return true + } + } + return false + } + for _, rdn := range rdnSeq { + for _, tv := range rdn { + // Skip if the Type was not recognized. The String() output will be + // an invalid type, value pair with empty type, which will give a "DN ended with + // an incomplete type, value pair" error when parsing. + if tv.Type.String() == "" { + t.Skip() + } + + // Skip if the value contains special characters, as the String() function + // will not escape them. + if hasSpecialChar(tv.Value.(string)) { + t.Skip() + } + } + } + + newRDNSeq, err := UnmarshalSubjectStringToRDNSequence(rdnSeq.String()) + if err != nil { + t.Fatal(err) + } + + assert.Equal(t, rdnSeq, newRDNSeq) + }) +} diff --git a/pkg/util/pki/temporarycertificate.go b/pkg/util/pki/temporarycertificate.go new file mode 100644 index 00000000000..81817e02d29 --- /dev/null +++ b/pkg/util/pki/temporarycertificate.go @@ -0,0 +1,68 @@ +/* +Copyright 2020 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. +*/ + +package pki + +import cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" + +// staticTemporarySerialNumber is a fixed serial number we use for temporary certificates +const staticTemporarySerialNumber = "1234567890" + +// GenerateLocallySignedTemporaryCertificate signs a temporary certificate for +// the given certificate resource using a one-use temporary CA that is then +// discarded afterwards. +// This is to mitigate a potential attack against x509 certificates that use a +// predictable serial number and weak MD5 hashing algorithms. +// In practice, this shouldn't really be a concern anyway. +func GenerateLocallySignedTemporaryCertificate(crt *cmapi.Certificate, pkData []byte) ([]byte, error) { + // generate a throwaway self-signed root CA + caPk, err := GenerateECPrivateKey(ECCurve521) + if err != nil { + return nil, err + } + caCertTemplate, err := CertificateTemplateFromCertificate(&cmapi.Certificate{ + Spec: cmapi.CertificateSpec{ + CommonName: "cert-manager.local", + IsCA: true, + }, + }) + if err != nil { + return nil, err + } + _, caCert, err := SignCertificate(caCertTemplate, caCertTemplate, caPk.Public(), caPk) + if err != nil { + return nil, err + } + + // sign a temporary certificate using the root CA + template, err := CertificateTemplateFromCertificate(crt) + if err != nil { + return nil, err + } + template.Subject.SerialNumber = staticTemporarySerialNumber + + signeeKey, err := DecodePrivateKeyBytes(pkData) + if err != nil { + return nil, err + } + + b, _, err := SignCertificate(template, caCert, signeeKey.Public(), caPk) + if err != nil { + return nil, err + } + + return b, nil +} diff --git a/pkg/util/predicate/certificate.go b/pkg/util/predicate/certificate.go index 4f8a10da3d6..b93f07bdc22 100644 --- a/pkg/util/predicate/certificate.go +++ b/pkg/util/predicate/certificate.go @@ -31,7 +31,7 @@ func CertificateSecretName(name string) Func { } } -// CertificateSecretName returns a predicate that used to filter Certificates +// CertificateNextPrivateKeySecretName returns a predicate that used to filter Certificates // to only those with the given 'status.nextPrivateKeySecretName'. // It is not possible to select Certificates with a 'nil' secret name using // this predicate function. diff --git a/pkg/util/predicate/certificate_test.go b/pkg/util/predicate/certificate_test.go index 716f8fffbb3..cd059d5ca9c 100644 --- a/pkg/util/predicate/certificate_test.go +++ b/pkg/util/predicate/certificate_test.go @@ -19,7 +19,7 @@ package predicate import ( "testing" - "k8s.io/utils/pointer" + "k8s.io/utils/ptr" cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" ) @@ -69,12 +69,12 @@ func TestCertificateNextPrivateKeySecretName(t *testing.T) { }{ "returns true if secret name matches": { secretName: "abc", - cert: certWithSecretName(pointer.StringPtr("abc")), + cert: certWithSecretName(ptr.To("abc")), expected: true, }, "returns false if secret name does not match": { secretName: "abc", - cert: certWithSecretName(pointer.StringPtr("abcd")), + cert: certWithSecretName(ptr.To("abcd")), expected: false, }, "returns false if secret name is nil": { diff --git a/pkg/util/solverpicker/solverpicker.go b/pkg/util/solverpicker/solverpicker.go new file mode 100644 index 00000000000..502bdcc8a90 --- /dev/null +++ b/pkg/util/solverpicker/solverpicker.go @@ -0,0 +1,185 @@ +/* +Copyright 2020 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. +*/ + +package solverpicker + +import ( + "context" + + cmacme "github.com/cert-manager/cert-manager/pkg/apis/acme/v1" + "github.com/cert-manager/cert-manager/pkg/controller/acmeorders/selectors" + logf "github.com/cert-manager/cert-manager/pkg/logs" +) + +// Pick will select a solver based on the type of challenge, labels, dns names and dns zones +func Pick(ctx context.Context, domainToFind string, challenges []cmacme.ACMEChallenge, solvers []cmacme.ACMEChallengeSolver, o *cmacme.Order) (*cmacme.ACMEChallengeSolver, *cmacme.ACMEChallenge) { + log := logf.FromContext(ctx, "selectSolver") + dbg := log.V(logf.DebugLevel) + + var selectedSolver *cmacme.ACMEChallengeSolver + var selectedChallenge *cmacme.ACMEChallenge + selectedNumLabelsMatch := 0 + selectedNumDNSNamesMatch := 0 + selectedNumDNSZonesMatch := 0 + + challengeForSolver := func(solver *cmacme.ACMEChallengeSolver) *cmacme.ACMEChallenge { + for _, ch := range challenges { + switch { + case ch.Type == "http-01" && solver.HTTP01 != nil: + return &ch + case ch.Type == "dns-01" && solver.DNS01 != nil: + return &ch + } + } + return nil + } + + // 2. filter solvers to only those that matchLabels + for _, cfg := range solvers { + acmech := challengeForSolver(&cfg) // #nosec G601 -- False positive. See https://github.com/golang/go/discussions/56010 + if acmech == nil { + dbg.Info("cannot use solver as the ACME authorization does not allow solvers of this type") + continue + } + + if cfg.Selector == nil { + if selectedSolver != nil { + dbg.Info("not selecting solver as previously selected solver has a just as or more specific selector") + continue + } + dbg.Info("selecting solver due to match all selector and no previously selected solver") + selectedSolver = cfg.DeepCopy() + selectedChallenge = acmech + continue + } + + labelsMatch, numLabelsMatch := selectors.Labels(*cfg.Selector).Matches(o.ObjectMeta, domainToFind) + dnsNamesMatch, numDNSNamesMatch := selectors.DNSNames(*cfg.Selector).Matches(o.ObjectMeta, domainToFind) + dnsZonesMatch, numDNSZonesMatch := selectors.DNSZones(*cfg.Selector).Matches(o.ObjectMeta, domainToFind) + + if !labelsMatch || !dnsNamesMatch || !dnsZonesMatch { + dbg.Info("not selecting solver", "labels_match", labelsMatch, "dnsnames_match", dnsNamesMatch, "dnszones_match", dnsZonesMatch) + continue + } + + dbg.Info("selector matches") + + selectSolver := func() { + selectedSolver = cfg.DeepCopy() + selectedChallenge = acmech + selectedNumLabelsMatch = numLabelsMatch + selectedNumDNSNamesMatch = numDNSNamesMatch + selectedNumDNSZonesMatch = numDNSZonesMatch + } + + if selectedSolver == nil { + dbg.Info("selecting solver as there is no previously selected solver") + selectSolver() + continue + } + + dbg.Info("determining whether this match is more significant than last") + + // because we don't count multiple dnsName matches as extra 'weight' + // in the selection process, we normalize the numDNSNamesMatch vars + // to be either 1 or 0 (i.e. true or false) + selectedHasMatchingDNSNames := selectedNumDNSNamesMatch > 0 + hasMatchingDNSNames := numDNSNamesMatch > 0 + + // dnsName selectors have the highest precedence, so check them first + switch { + case !selectedHasMatchingDNSNames && hasMatchingDNSNames: + dbg.Info("selecting solver as this solver has matching DNS names and the previous one does not") + selectSolver() + continue + case selectedHasMatchingDNSNames && !hasMatchingDNSNames: + dbg.Info("not selecting solver as the previous one has matching DNS names and this one does not") + continue + case !selectedHasMatchingDNSNames && !hasMatchingDNSNames: + dbg.Info("solver does not have any matching DNS names, checking dnsZones") + // check zones + case selectedHasMatchingDNSNames && hasMatchingDNSNames: + dbg.Info("both this solver and the previously selected one matches dnsNames, comparing zones") + if numDNSZonesMatch > selectedNumDNSZonesMatch { + dbg.Info("selecting solver as this one has a more specific dnsZone match than the previously selected one") + selectSolver() + continue + } + if selectedNumDNSZonesMatch > numDNSZonesMatch { + dbg.Info("not selecting this solver as the previously selected one has a more specific dnsZone match") + continue + } + dbg.Info("both this solver and the previously selected one match dnsZones, comparing labels") + // choose the one with the most labels + if numLabelsMatch > selectedNumLabelsMatch { + dbg.Info("selecting solver as this one has more labels than the previously selected one") + selectSolver() + continue + } + dbg.Info("not selecting this solver as previous one has either the same number of or more labels") + continue + } + + selectedHasMatchingDNSZones := selectedNumDNSZonesMatch > 0 + hasMatchingDNSZones := numDNSZonesMatch > 0 + + switch { + case !selectedHasMatchingDNSZones && hasMatchingDNSZones: + dbg.Info("selecting solver as this solver has matching DNS zones and the previous one does not") + selectSolver() + continue + case selectedHasMatchingDNSZones && !hasMatchingDNSZones: + dbg.Info("not selecting solver as the previous one has matching DNS zones and this one does not") + continue + case !selectedHasMatchingDNSZones && !hasMatchingDNSZones: + dbg.Info("solver does not have any matching DNS zones, checking labels") + // check labels + case selectedHasMatchingDNSZones && hasMatchingDNSZones: + dbg.Info("both this solver and the previously selected one matches dnsZones") + dbg.Info("comparing number of matching domain segments") + // choose the one with the most matching DNS zone segments + if numDNSZonesMatch > selectedNumDNSZonesMatch { + dbg.Info("selecting solver because this one has more matching DNS zone segments") + selectSolver() + continue + } + if selectedNumDNSZonesMatch > numDNSZonesMatch { + dbg.Info("not selecting solver because previous one has more matching DNS zone segments") + continue + } + // choose the one with the most labels + if numLabelsMatch > selectedNumLabelsMatch { + dbg.Info("selecting solver because this one has more labels than the previous one") + selectSolver() + continue + } + dbg.Info("not selecting solver as this one's number of matching labels is equal to or less than the last one") + continue + } + + if numLabelsMatch > selectedNumLabelsMatch { + dbg.Info("selecting solver as this one has more labels than the last one") + selectSolver() + continue + } + + dbg.Info("not selecting solver as this one's number of matching labels is equal to or less than the last one (reached end of loop)") + // if we get here, the number of matches is less than or equal so we + // fallback to choosing the first in the list + } + + return selectedSolver, selectedChallenge +} diff --git a/pkg/util/solverpicker/solverpicker_test.go b/pkg/util/solverpicker/solverpicker_test.go new file mode 100644 index 00000000000..c5fb6f1391f --- /dev/null +++ b/pkg/util/solverpicker/solverpicker_test.go @@ -0,0 +1,994 @@ +/* +Copyright 2020 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. +*/ + +package solverpicker + +import ( + "reflect" + "testing" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/utils/ptr" + + cmacme "github.com/cert-manager/cert-manager/pkg/apis/acme/v1" + cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" +) + +func TestPick(t *testing.T) { + emptySelectorSolverHTTP01 := cmacme.ACMEChallengeSolver{ + HTTP01: &cmacme.ACMEChallengeSolverHTTP01{ + Ingress: &cmacme.ACMEChallengeSolverHTTP01Ingress{ + Name: "empty-selector-solver", + }, + }, + } + emptySelectorSolverDNS01 := cmacme.ACMEChallengeSolver{ + DNS01: &cmacme.ACMEChallengeSolverDNS01{ + Cloudflare: &cmacme.ACMEIssuerDNS01ProviderCloudflare{ + Email: "test-cloudflare-email", + }, + }, + } + nonMatchingSelectorSolver := cmacme.ACMEChallengeSolver{ + Selector: &cmacme.CertificateDNSNameSelector{ + MatchLabels: map[string]string{ + "label": "does-not-exist", + "does-not": "match", + }, + }, + HTTP01: &cmacme.ACMEChallengeSolverHTTP01{ + Ingress: &cmacme.ACMEChallengeSolverHTTP01Ingress{ + Name: "non-matching-selector-solver", + }, + }, + } + exampleComDNSNameSelectorSolver := cmacme.ACMEChallengeSolver{ + Selector: &cmacme.CertificateDNSNameSelector{ + DNSNames: []string{"example.com"}, + }, + HTTP01: &cmacme.ACMEChallengeSolverHTTP01{ + Ingress: &cmacme.ACMEChallengeSolverHTTP01Ingress{ + Name: "example-com-dns-name-selector-solver", + }, + }, + } + // define ACME challenges that are used during tests + acmeChallengeHTTP01 := &cmacme.ACMEChallenge{ + Type: "http-01", + Token: "http-01-token", + } + acmeChallengeDNS01 := &cmacme.ACMEChallenge{ + Type: "dns-01", + Token: "dns-01-token", + } + + tests := map[string]struct { + issuer cmapi.GenericIssuer + order *cmacme.Order + authz *cmacme.ACMEAuthorization + expectedSolver *cmacme.ACMEChallengeSolver + expectedChallenge *cmacme.ACMEChallenge + }{ + "should use configured default solver when no others are present": { + issuer: &cmapi.Issuer{ + Spec: cmapi.IssuerSpec{ + IssuerConfig: cmapi.IssuerConfig{ + ACME: &cmacme.ACMEIssuer{ + Solvers: []cmacme.ACMEChallengeSolver{emptySelectorSolverHTTP01}, + }, + }, + }, + }, + order: &cmacme.Order{ + Spec: cmacme.OrderSpec{ + DNSNames: []string{"example.com"}, + }, + }, + authz: &cmacme.ACMEAuthorization{ + Identifier: "example.com", + Challenges: []cmacme.ACMEChallenge{*acmeChallengeHTTP01}, + }, + expectedSolver: &emptySelectorSolverHTTP01, + expectedChallenge: acmeChallengeHTTP01, + }, + "should use configured default solver when no others are present but selector is non-nil": { + issuer: &cmapi.Issuer{ + Spec: cmapi.IssuerSpec{ + IssuerConfig: cmapi.IssuerConfig{ + ACME: &cmacme.ACMEIssuer{ + Solvers: []cmacme.ACMEChallengeSolver{ + { + Selector: &cmacme.CertificateDNSNameSelector{}, + HTTP01: &cmacme.ACMEChallengeSolverHTTP01{ + Ingress: &cmacme.ACMEChallengeSolverHTTP01Ingress{ + Name: "empty-selector-solver", + }, + }, + }, + }, + }, + }, + }, + }, + order: &cmacme.Order{ + Spec: cmacme.OrderSpec{ + DNSNames: []string{"example.com"}, + }, + }, + authz: &cmacme.ACMEAuthorization{ + Identifier: "example.com", + Challenges: []cmacme.ACMEChallenge{*acmeChallengeHTTP01}, + }, + expectedSolver: &cmacme.ACMEChallengeSolver{ + Selector: &cmacme.CertificateDNSNameSelector{}, + HTTP01: &cmacme.ACMEChallengeSolverHTTP01{ + Ingress: &cmacme.ACMEChallengeSolverHTTP01Ingress{ + Name: "empty-selector-solver", + }, + }, + }, + expectedChallenge: acmeChallengeHTTP01, + }, + "should use configured default solver when others do not match": { + issuer: &cmapi.Issuer{ + Spec: cmapi.IssuerSpec{ + IssuerConfig: cmapi.IssuerConfig{ + ACME: &cmacme.ACMEIssuer{ + Solvers: []cmacme.ACMEChallengeSolver{ + emptySelectorSolverHTTP01, + nonMatchingSelectorSolver, + }, + }, + }, + }, + }, + order: &cmacme.Order{ + Spec: cmacme.OrderSpec{ + DNSNames: []string{"example.com"}, + }, + }, + authz: &cmacme.ACMEAuthorization{ + Identifier: "example.com", + Challenges: []cmacme.ACMEChallenge{*acmeChallengeHTTP01}, + }, + expectedSolver: &emptySelectorSolverHTTP01, + expectedChallenge: acmeChallengeHTTP01, + }, + "should use DNS01 solver over HTTP01 if challenge is of type DNS01": { + issuer: &cmapi.Issuer{ + Spec: cmapi.IssuerSpec{ + IssuerConfig: cmapi.IssuerConfig{ + ACME: &cmacme.ACMEIssuer{ + Solvers: []cmacme.ACMEChallengeSolver{ + emptySelectorSolverHTTP01, + emptySelectorSolverDNS01, + }, + }, + }, + }, + }, + order: &cmacme.Order{ + Spec: cmacme.OrderSpec{ + DNSNames: []string{"example.com"}, + }, + }, + authz: &cmacme.ACMEAuthorization{ + Identifier: "example.com", + Challenges: []cmacme.ACMEChallenge{*acmeChallengeDNS01}, + }, + expectedSolver: &emptySelectorSolverDNS01, + expectedChallenge: acmeChallengeDNS01, + }, + "should return nil if none match": { + issuer: &cmapi.Issuer{ + Spec: cmapi.IssuerSpec{ + IssuerConfig: cmapi.IssuerConfig{ + ACME: &cmacme.ACMEIssuer{ + Solvers: []cmacme.ACMEChallengeSolver{ + nonMatchingSelectorSolver, + }, + }, + }, + }, + }, + order: &cmacme.Order{ + Spec: cmacme.OrderSpec{ + DNSNames: []string{"example.com"}, + }, + }, + authz: &cmacme.ACMEAuthorization{ + Identifier: "example.com", + Challenges: []cmacme.ACMEChallenge{*acmeChallengeHTTP01}, + }, + expectedSolver: nil, + expectedChallenge: nil, + }, + "uses correct solver when selector explicitly names dnsName": { + issuer: &cmapi.Issuer{ + Spec: cmapi.IssuerSpec{ + IssuerConfig: cmapi.IssuerConfig{ + ACME: &cmacme.ACMEIssuer{ + Solvers: []cmacme.ACMEChallengeSolver{ + emptySelectorSolverHTTP01, + exampleComDNSNameSelectorSolver, + }, + }, + }, + }, + }, + order: &cmacme.Order{ + Spec: cmacme.OrderSpec{ + DNSNames: []string{"example.com"}, + }, + }, + authz: &cmacme.ACMEAuthorization{ + Identifier: "example.com", + Challenges: []cmacme.ACMEChallenge{*acmeChallengeHTTP01}, + }, + expectedSolver: &exampleComDNSNameSelectorSolver, + expectedChallenge: acmeChallengeHTTP01, + }, + "uses default solver if dnsName does not match": { + issuer: &cmapi.Issuer{ + Spec: cmapi.IssuerSpec{ + IssuerConfig: cmapi.IssuerConfig{ + ACME: &cmacme.ACMEIssuer{ + Solvers: []cmacme.ACMEChallengeSolver{ + emptySelectorSolverHTTP01, + exampleComDNSNameSelectorSolver, + }, + }, + }, + }, + }, + order: &cmacme.Order{ + Spec: cmacme.OrderSpec{ + DNSNames: []string{"notexample.com"}, + }, + }, + authz: &cmacme.ACMEAuthorization{ + Identifier: "notexample.com", + Challenges: []cmacme.ACMEChallenge{*acmeChallengeHTTP01}, + }, + expectedSolver: &emptySelectorSolverHTTP01, + expectedChallenge: acmeChallengeHTTP01, + }, + "if two solvers specify the same dnsName, the one with the most labels should be chosen": { + issuer: &cmapi.Issuer{ + Spec: cmapi.IssuerSpec{ + IssuerConfig: cmapi.IssuerConfig{ + ACME: &cmacme.ACMEIssuer{ + Solvers: []cmacme.ACMEChallengeSolver{ + exampleComDNSNameSelectorSolver, + { + Selector: &cmacme.CertificateDNSNameSelector{ + MatchLabels: map[string]string{ + "label": "exists", + }, + DNSNames: []string{"example.com"}, + }, + HTTP01: &cmacme.ACMEChallengeSolverHTTP01{ + Ingress: &cmacme.ACMEChallengeSolverHTTP01Ingress{ + Name: "example-com-dns-name-labels-selector-solver", + }, + }, + }, + }, + }, + }, + }, + }, + order: &cmacme.Order{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{ + "label": "exists", + }, + }, + Spec: cmacme.OrderSpec{ + DNSNames: []string{"example.com"}, + }, + }, + authz: &cmacme.ACMEAuthorization{ + Identifier: "example.com", + Challenges: []cmacme.ACMEChallenge{*acmeChallengeHTTP01}, + }, + expectedSolver: &cmacme.ACMEChallengeSolver{ + Selector: &cmacme.CertificateDNSNameSelector{ + MatchLabels: map[string]string{ + "label": "exists", + }, + DNSNames: []string{"example.com"}, + }, + HTTP01: &cmacme.ACMEChallengeSolverHTTP01{ + Ingress: &cmacme.ACMEChallengeSolverHTTP01Ingress{ + Name: "example-com-dns-name-labels-selector-solver", + }, + }, + }, + expectedChallenge: acmeChallengeHTTP01, + }, + "if one solver matches with dnsNames, and the other solver matches with labels, the dnsName solver should be chosen": { + issuer: &cmapi.Issuer{ + Spec: cmapi.IssuerSpec{ + IssuerConfig: cmapi.IssuerConfig{ + ACME: &cmacme.ACMEIssuer{ + Solvers: []cmacme.ACMEChallengeSolver{ + exampleComDNSNameSelectorSolver, + { + Selector: &cmacme.CertificateDNSNameSelector{ + MatchLabels: map[string]string{ + "label": "exists", + }, + }, + HTTP01: &cmacme.ACMEChallengeSolverHTTP01{ + Ingress: &cmacme.ACMEChallengeSolverHTTP01Ingress{ + Name: "example-com-labels-selector-solver", + }, + }, + }, + }, + }, + }, + }, + }, + order: &cmacme.Order{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{ + "label": "exists", + }, + }, + Spec: cmacme.OrderSpec{ + DNSNames: []string{"example.com"}, + }, + }, + authz: &cmacme.ACMEAuthorization{ + Identifier: "example.com", + Challenges: []cmacme.ACMEChallenge{*acmeChallengeHTTP01}, + }, + expectedSolver: &exampleComDNSNameSelectorSolver, + expectedChallenge: acmeChallengeHTTP01, + }, + // identical to the test above, but the solvers are listed in reverse + // order to ensure that this behaviour isn't just incidental + "if one solver matches with dnsNames, and the other solver matches with labels, the dnsName solver should be chosen (solvers listed in reverse order)": { + issuer: &cmapi.Issuer{ + Spec: cmapi.IssuerSpec{ + IssuerConfig: cmapi.IssuerConfig{ + ACME: &cmacme.ACMEIssuer{ + Solvers: []cmacme.ACMEChallengeSolver{ + { + Selector: &cmacme.CertificateDNSNameSelector{ + MatchLabels: map[string]string{ + "label": "exists", + }, + }, + HTTP01: &cmacme.ACMEChallengeSolverHTTP01{ + Ingress: &cmacme.ACMEChallengeSolverHTTP01Ingress{ + Name: "example-com-labels-selector-solver", + }, + }, + }, + exampleComDNSNameSelectorSolver, + }, + }, + }, + }, + }, + order: &cmacme.Order{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{ + "label": "exists", + }, + }, + Spec: cmacme.OrderSpec{ + DNSNames: []string{"example.com"}, + }, + }, + authz: &cmacme.ACMEAuthorization{ + Identifier: "example.com", + Challenges: []cmacme.ACMEChallenge{*acmeChallengeHTTP01}, + }, + expectedSolver: &exampleComDNSNameSelectorSolver, + expectedChallenge: acmeChallengeHTTP01, + }, + "if one solver matches with dnsNames, and the other solver matches with 2 labels, the dnsName solver should be chosen": { + issuer: &cmapi.Issuer{ + Spec: cmapi.IssuerSpec{ + IssuerConfig: cmapi.IssuerConfig{ + ACME: &cmacme.ACMEIssuer{ + Solvers: []cmacme.ACMEChallengeSolver{ + exampleComDNSNameSelectorSolver, + { + Selector: &cmacme.CertificateDNSNameSelector{ + MatchLabels: map[string]string{ + "label": "exists", + "another": "label", + }, + }, + HTTP01: &cmacme.ACMEChallengeSolverHTTP01{ + Ingress: &cmacme.ACMEChallengeSolverHTTP01Ingress{ + Name: "example-com-labels-selector-solver", + }, + }, + }, + }, + }, + }, + }, + }, + order: &cmacme.Order{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{ + "label": "exists", + "another": "label", + }, + }, + Spec: cmacme.OrderSpec{ + DNSNames: []string{"example.com"}, + }, + }, + authz: &cmacme.ACMEAuthorization{ + Identifier: "example.com", + Challenges: []cmacme.ACMEChallenge{*acmeChallengeHTTP01}, + }, + expectedSolver: &exampleComDNSNameSelectorSolver, + expectedChallenge: acmeChallengeHTTP01, + }, + "should choose the solver with the most labels matching if multiple match": { + issuer: &cmapi.Issuer{ + Spec: cmapi.IssuerSpec{ + IssuerConfig: cmapi.IssuerConfig{ + ACME: &cmacme.ACMEIssuer{ + Solvers: []cmacme.ACMEChallengeSolver{ + { + Selector: &cmacme.CertificateDNSNameSelector{ + MatchLabels: map[string]string{ + "label": "exists", + }, + }, + HTTP01: &cmacme.ACMEChallengeSolverHTTP01{ + Ingress: &cmacme.ACMEChallengeSolverHTTP01Ingress{ + Name: "example-com-labels-selector-solver", + }, + }, + }, + { + Selector: &cmacme.CertificateDNSNameSelector{ + MatchLabels: map[string]string{ + "label": "exists", + "another": "matches", + }, + }, + HTTP01: &cmacme.ACMEChallengeSolverHTTP01{ + Ingress: &cmacme.ACMEChallengeSolverHTTP01Ingress{ + Name: "example-com-multiple-labels-selector-solver", + }, + }, + }, + }, + }, + }, + }, + }, + order: &cmacme.Order{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{ + "label": "exists", + "another": "matches", + }, + }, + Spec: cmacme.OrderSpec{ + DNSNames: []string{"example.com"}, + }, + }, + authz: &cmacme.ACMEAuthorization{ + Identifier: "example.com", + Challenges: []cmacme.ACMEChallenge{*acmeChallengeHTTP01}, + }, + expectedSolver: &cmacme.ACMEChallengeSolver{ + Selector: &cmacme.CertificateDNSNameSelector{ + MatchLabels: map[string]string{ + "label": "exists", + "another": "matches", + }, + }, + HTTP01: &cmacme.ACMEChallengeSolverHTTP01{ + Ingress: &cmacme.ACMEChallengeSolverHTTP01Ingress{ + Name: "example-com-multiple-labels-selector-solver", + }, + }, + }, + expectedChallenge: acmeChallengeHTTP01, + }, + "should match wildcard dnsName solver if authorization has Wildcard=true": { + issuer: &cmapi.Issuer{ + Spec: cmapi.IssuerSpec{ + IssuerConfig: cmapi.IssuerConfig{ + ACME: &cmacme.ACMEIssuer{ + Solvers: []cmacme.ACMEChallengeSolver{ + emptySelectorSolverDNS01, + { + Selector: &cmacme.CertificateDNSNameSelector{ + DNSNames: []string{"*.example.com"}, + }, + DNS01: &cmacme.ACMEChallengeSolverDNS01{ + Cloudflare: &cmacme.ACMEIssuerDNS01ProviderCloudflare{ + Email: "example-com-wc-dnsname-selector-solver", + }, + }, + }, + }, + }, + }, + }, + }, + order: &cmacme.Order{ + Spec: cmacme.OrderSpec{ + DNSNames: []string{"*.example.com"}, + }, + }, + authz: &cmacme.ACMEAuthorization{ + Identifier: "example.com", + Wildcard: ptr.To(true), + Challenges: []cmacme.ACMEChallenge{*acmeChallengeDNS01}, + }, + expectedSolver: &cmacme.ACMEChallengeSolver{ + Selector: &cmacme.CertificateDNSNameSelector{ + DNSNames: []string{"*.example.com"}, + }, + DNS01: &cmacme.ACMEChallengeSolverDNS01{ + Cloudflare: &cmacme.ACMEIssuerDNS01ProviderCloudflare{ + Email: "example-com-wc-dnsname-selector-solver", + }, + }, + }, + expectedChallenge: acmeChallengeDNS01, + }, + "dnsName selectors should take precedence over dnsZone selectors": { + issuer: &cmapi.Issuer{ + Spec: cmapi.IssuerSpec{ + IssuerConfig: cmapi.IssuerConfig{ + ACME: &cmacme.ACMEIssuer{ + Solvers: []cmacme.ACMEChallengeSolver{ + exampleComDNSNameSelectorSolver, + { + Selector: &cmacme.CertificateDNSNameSelector{ + DNSZones: []string{"com"}, + }, + HTTP01: &cmacme.ACMEChallengeSolverHTTP01{ + Ingress: &cmacme.ACMEChallengeSolverHTTP01Ingress{ + Name: "com-dnszone-selector-solver", + }, + }, + }, + }, + }, + }, + }, + }, + order: &cmacme.Order{ + Spec: cmacme.OrderSpec{ + DNSNames: []string{"example.com"}, + }, + }, + authz: &cmacme.ACMEAuthorization{ + Identifier: "example.com", + Challenges: []cmacme.ACMEChallenge{*acmeChallengeHTTP01}, + }, + expectedSolver: &exampleComDNSNameSelectorSolver, + expectedChallenge: acmeChallengeHTTP01, + }, + "dnsName selectors should take precedence over dnsZone selectors (reversed order)": { + issuer: &cmapi.Issuer{ + Spec: cmapi.IssuerSpec{ + IssuerConfig: cmapi.IssuerConfig{ + ACME: &cmacme.ACMEIssuer{ + Solvers: []cmacme.ACMEChallengeSolver{ + { + Selector: &cmacme.CertificateDNSNameSelector{ + DNSZones: []string{"com"}, + }, + DNS01: &cmacme.ACMEChallengeSolverDNS01{ + Cloudflare: &cmacme.ACMEIssuerDNS01ProviderCloudflare{ + Email: "com-dnszone-selector-solver", + }, + }, + }, + exampleComDNSNameSelectorSolver, + }, + }, + }, + }, + }, + order: &cmacme.Order{ + Spec: cmacme.OrderSpec{ + DNSNames: []string{"example.com"}, + }, + }, + authz: &cmacme.ACMEAuthorization{ + Identifier: "example.com", + Challenges: []cmacme.ACMEChallenge{*acmeChallengeHTTP01}, + }, + expectedSolver: &exampleComDNSNameSelectorSolver, + expectedChallenge: acmeChallengeHTTP01, + }, + "should allow matching with dnsZones": { + issuer: &cmapi.Issuer{ + Spec: cmapi.IssuerSpec{ + IssuerConfig: cmapi.IssuerConfig{ + ACME: &cmacme.ACMEIssuer{ + Solvers: []cmacme.ACMEChallengeSolver{ + emptySelectorSolverDNS01, + { + Selector: &cmacme.CertificateDNSNameSelector{ + DNSZones: []string{"example.com"}, + }, + DNS01: &cmacme.ACMEChallengeSolverDNS01{ + Cloudflare: &cmacme.ACMEIssuerDNS01ProviderCloudflare{ + Email: "example-com-dnszone-selector-solver", + }, + }, + }, + }, + }, + }, + }, + }, + order: &cmacme.Order{ + Spec: cmacme.OrderSpec{ + DNSNames: []string{"www.example.com"}, + }, + }, + authz: &cmacme.ACMEAuthorization{ + Identifier: "www.example.com", + Wildcard: ptr.To(true), + Challenges: []cmacme.ACMEChallenge{*acmeChallengeDNS01}, + }, + expectedSolver: &cmacme.ACMEChallengeSolver{ + Selector: &cmacme.CertificateDNSNameSelector{ + DNSZones: []string{"example.com"}, + }, + DNS01: &cmacme.ACMEChallengeSolverDNS01{ + Cloudflare: &cmacme.ACMEIssuerDNS01ProviderCloudflare{ + Email: "example-com-dnszone-selector-solver", + }, + }, + }, + expectedChallenge: acmeChallengeDNS01, + }, + "most specific dnsZone should be selected if multiple match": { + issuer: &cmapi.Issuer{ + Spec: cmapi.IssuerSpec{ + IssuerConfig: cmapi.IssuerConfig{ + ACME: &cmacme.ACMEIssuer{ + Solvers: []cmacme.ACMEChallengeSolver{ + { + Selector: &cmacme.CertificateDNSNameSelector{ + DNSZones: []string{"example.com"}, + }, + DNS01: &cmacme.ACMEChallengeSolverDNS01{ + Cloudflare: &cmacme.ACMEIssuerDNS01ProviderCloudflare{ + Email: "example-com-dnszone-selector-solver", + }, + }, + }, + { + Selector: &cmacme.CertificateDNSNameSelector{ + DNSZones: []string{"prod.example.com"}, + }, + DNS01: &cmacme.ACMEChallengeSolverDNS01{ + Cloudflare: &cmacme.ACMEIssuerDNS01ProviderCloudflare{ + Email: "prod-example-com-dnszone-selector-solver", + }, + }, + }, + }, + }, + }, + }, + }, + order: &cmacme.Order{ + Spec: cmacme.OrderSpec{ + DNSNames: []string{"www.prod.example.com"}, + }, + }, + authz: &cmacme.ACMEAuthorization{ + Identifier: "www.prod.example.com", + Wildcard: ptr.To(true), + Challenges: []cmacme.ACMEChallenge{*acmeChallengeDNS01}, + }, + expectedSolver: &cmacme.ACMEChallengeSolver{ + Selector: &cmacme.CertificateDNSNameSelector{ + DNSZones: []string{"prod.example.com"}, + }, + DNS01: &cmacme.ACMEChallengeSolverDNS01{ + Cloudflare: &cmacme.ACMEIssuerDNS01ProviderCloudflare{ + Email: "prod-example-com-dnszone-selector-solver", + }, + }, + }, + expectedChallenge: acmeChallengeDNS01, + }, + "most specific dnsZone should be selected if multiple match (reversed)": { + issuer: &cmapi.Issuer{ + Spec: cmapi.IssuerSpec{ + IssuerConfig: cmapi.IssuerConfig{ + ACME: &cmacme.ACMEIssuer{ + Solvers: []cmacme.ACMEChallengeSolver{ + { + Selector: &cmacme.CertificateDNSNameSelector{ + DNSZones: []string{"prod.example.com"}, + }, + DNS01: &cmacme.ACMEChallengeSolverDNS01{ + Cloudflare: &cmacme.ACMEIssuerDNS01ProviderCloudflare{ + Email: "prod-example-com-dnszone-selector-solver", + }, + }, + }, + { + Selector: &cmacme.CertificateDNSNameSelector{ + DNSZones: []string{"example.com"}, + }, + DNS01: &cmacme.ACMEChallengeSolverDNS01{ + Cloudflare: &cmacme.ACMEIssuerDNS01ProviderCloudflare{ + Email: "example-com-dnszone-selector-solver", + }, + }, + }, + }, + }, + }, + }, + }, + order: &cmacme.Order{ + Spec: cmacme.OrderSpec{ + DNSNames: []string{"www.prod.example.com"}, + }, + }, + authz: &cmacme.ACMEAuthorization{ + Identifier: "www.prod.example.com", + Wildcard: ptr.To(true), + Challenges: []cmacme.ACMEChallenge{*acmeChallengeDNS01}, + }, + expectedSolver: &cmacme.ACMEChallengeSolver{ + Selector: &cmacme.CertificateDNSNameSelector{ + DNSZones: []string{"prod.example.com"}, + }, + DNS01: &cmacme.ACMEChallengeSolverDNS01{ + Cloudflare: &cmacme.ACMEIssuerDNS01ProviderCloudflare{ + Email: "prod-example-com-dnszone-selector-solver", + }, + }, + }, + expectedChallenge: acmeChallengeDNS01, + }, + "if two solvers specify the same dnsZone, the one with the most labels should be chosen": { + issuer: &cmapi.Issuer{ + Spec: cmapi.IssuerSpec{ + IssuerConfig: cmapi.IssuerConfig{ + ACME: &cmacme.ACMEIssuer{ + Solvers: []cmacme.ACMEChallengeSolver{ + { + Selector: &cmacme.CertificateDNSNameSelector{ + DNSZones: []string{"example.com"}, + }, + HTTP01: &cmacme.ACMEChallengeSolverHTTP01{ + Ingress: &cmacme.ACMEChallengeSolverHTTP01Ingress{ + Name: "example-com-dnszone-selector-solver", + }, + }, + }, + { + Selector: &cmacme.CertificateDNSNameSelector{ + MatchLabels: map[string]string{ + "label": "exists", + }, + DNSZones: []string{"example.com"}, + }, + HTTP01: &cmacme.ACMEChallengeSolverHTTP01{ + Ingress: &cmacme.ACMEChallengeSolverHTTP01Ingress{ + Name: "example-com-dnszone-labels-selector-solver", + }, + }, + }, + }, + }, + }, + }, + }, + order: &cmacme.Order{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{ + "label": "exists", + }, + }, + Spec: cmacme.OrderSpec{ + DNSNames: []string{"www.example.com"}, + }, + }, + authz: &cmacme.ACMEAuthorization{ + Identifier: "www.example.com", + Challenges: []cmacme.ACMEChallenge{*acmeChallengeHTTP01}, + }, + expectedSolver: &cmacme.ACMEChallengeSolver{ + Selector: &cmacme.CertificateDNSNameSelector{ + MatchLabels: map[string]string{ + "label": "exists", + }, + DNSZones: []string{"example.com"}, + }, + HTTP01: &cmacme.ACMEChallengeSolverHTTP01{ + Ingress: &cmacme.ACMEChallengeSolverHTTP01Ingress{ + Name: "example-com-dnszone-labels-selector-solver", + }, + }, + }, + expectedChallenge: acmeChallengeHTTP01, + }, + "if both solvers match dnsNames, and one also matches dnsZones, choose the one that matches dnsZones": { + issuer: &cmapi.Issuer{ + Spec: cmapi.IssuerSpec{ + IssuerConfig: cmapi.IssuerConfig{ + ACME: &cmacme.ACMEIssuer{ + Solvers: []cmacme.ACMEChallengeSolver{ + { + Selector: &cmacme.CertificateDNSNameSelector{ + DNSNames: []string{"www.example.com"}, + }, + HTTP01: &cmacme.ACMEChallengeSolverHTTP01{ + Ingress: &cmacme.ACMEChallengeSolverHTTP01Ingress{ + Name: "example-com-dnsname-selector-solver", + }, + }, + }, + { + Selector: &cmacme.CertificateDNSNameSelector{ + DNSZones: []string{"example.com"}, + DNSNames: []string{"www.example.com"}, + }, + HTTP01: &cmacme.ACMEChallengeSolverHTTP01{ + Ingress: &cmacme.ACMEChallengeSolverHTTP01Ingress{ + Name: "example-com-dnsname-dnszone-selector-solver", + }, + }, + }, + }, + }, + }, + }, + }, + order: &cmacme.Order{ + Spec: cmacme.OrderSpec{ + DNSNames: []string{"www.example.com"}, + }, + }, + authz: &cmacme.ACMEAuthorization{ + Identifier: "www.example.com", + Challenges: []cmacme.ACMEChallenge{*acmeChallengeHTTP01}, + }, + expectedSolver: &cmacme.ACMEChallengeSolver{ + Selector: &cmacme.CertificateDNSNameSelector{ + DNSZones: []string{"example.com"}, + DNSNames: []string{"www.example.com"}, + }, + HTTP01: &cmacme.ACMEChallengeSolverHTTP01{ + Ingress: &cmacme.ACMEChallengeSolverHTTP01Ingress{ + Name: "example-com-dnsname-dnszone-selector-solver", + }, + }, + }, + expectedChallenge: acmeChallengeHTTP01, + }, + "if both solvers match dnsNames, and one also matches dnsZones, choose the one that matches dnsZones (reversed)": { + issuer: &cmapi.Issuer{ + Spec: cmapi.IssuerSpec{ + IssuerConfig: cmapi.IssuerConfig{ + ACME: &cmacme.ACMEIssuer{ + Solvers: []cmacme.ACMEChallengeSolver{ + { + Selector: &cmacme.CertificateDNSNameSelector{ + DNSZones: []string{"example.com"}, + DNSNames: []string{"www.example.com"}, + }, + HTTP01: &cmacme.ACMEChallengeSolverHTTP01{ + Ingress: &cmacme.ACMEChallengeSolverHTTP01Ingress{ + Name: "example-com-dnsname-dnszone-selector-solver", + }, + }, + }, + { + Selector: &cmacme.CertificateDNSNameSelector{ + DNSNames: []string{"www.example.com"}, + }, + HTTP01: &cmacme.ACMEChallengeSolverHTTP01{ + Ingress: &cmacme.ACMEChallengeSolverHTTP01Ingress{ + Name: "example-com-dnsname-selector-solver", + }, + }, + }, + }, + }, + }, + }, + }, + order: &cmacme.Order{ + Spec: cmacme.OrderSpec{ + DNSNames: []string{"www.example.com"}, + }, + }, + authz: &cmacme.ACMEAuthorization{ + Identifier: "www.example.com", + Challenges: []cmacme.ACMEChallenge{*acmeChallengeHTTP01}, + }, + expectedSolver: &cmacme.ACMEChallengeSolver{ + Selector: &cmacme.CertificateDNSNameSelector{ + DNSZones: []string{"example.com"}, + DNSNames: []string{"www.example.com"}, + }, + HTTP01: &cmacme.ACMEChallengeSolverHTTP01{ + Ingress: &cmacme.ACMEChallengeSolverHTTP01Ingress{ + Name: "example-com-dnsname-dnszone-selector-solver", + }, + }, + }, + expectedChallenge: acmeChallengeHTTP01, + }, + "uses correct solver when selector explicitly names dnsName (reversed)": { + issuer: &cmapi.Issuer{ + Spec: cmapi.IssuerSpec{ + IssuerConfig: cmapi.IssuerConfig{ + ACME: &cmacme.ACMEIssuer{ + Solvers: []cmacme.ACMEChallengeSolver{ + exampleComDNSNameSelectorSolver, + emptySelectorSolverHTTP01, + }, + }, + }, + }, + }, + order: &cmacme.Order{ + Spec: cmacme.OrderSpec{ + DNSNames: []string{"example.com"}, + }, + }, + authz: &cmacme.ACMEAuthorization{ + Identifier: "example.com", + Challenges: []cmacme.ACMEChallenge{*acmeChallengeHTTP01}, + }, + expectedSolver: &exampleComDNSNameSelectorSolver, + expectedChallenge: acmeChallengeHTTP01, + }, + } + + for name, test := range tests { + t.Run(name, func(t *testing.T) { + domainToFind := test.authz.Identifier + if test.authz.Wildcard != nil { + domainToFind = "*." + domainToFind + } + + solver, ch := Pick(t.Context(), domainToFind, test.authz.Challenges, test.issuer.GetSpec().ACME.Solvers, test.order) + + if !reflect.DeepEqual(test.expectedSolver, solver) { + t.Errorf("expected solver %v, got %v", test.expectedSolver, solver) + } + + if !reflect.DeepEqual(test.expectedChallenge, ch) { + t.Errorf("expected challenge token %v, got %v", test.expectedChallenge, ch) + } + }) + } +} diff --git a/pkg/util/useragent.go b/pkg/util/useragent.go index 34a5a757bee..7372031a12a 100644 --- a/pkg/util/useragent.go +++ b/pkg/util/useragent.go @@ -19,6 +19,7 @@ package util import ( "bytes" "fmt" + "net/http" "strings" "unicode" "unicode/utf8" @@ -58,3 +59,25 @@ func PrefixFromUserAgent(u string) string { } return buf.String() } + +// UserAgentRoundTripper implements the http.RoundTripper interface and adds a User-Agent +// header. +type userAgentRoundTripper struct { + inner http.RoundTripper + userAgent string +} + +// UserAgentRoundTripper returns a RoundTripper that functions identically to +// the provided 'inner' round tripper, other than also setting a user agent. +func UserAgentRoundTripper(inner http.RoundTripper, userAgent string) http.RoundTripper { + return userAgentRoundTripper{ + inner: inner, + userAgent: userAgent, + } +} + +// RoundTrip implements http.RoundTripper +func (u userAgentRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) { + req.Header.Set("User-Agent", u.userAgent) + return u.inner.RoundTrip(req) +} diff --git a/pkg/util/util.go b/pkg/util/util.go index 40bd5c54b28..7893e43b911 100644 --- a/pkg/util/util.go +++ b/pkg/util/util.go @@ -17,168 +17,122 @@ limitations under the License. package util import ( - "math/rand" + "bytes" + "encoding/csv" + "fmt" "net" "net/url" - "sort" - "time" + "slices" + "strings" cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" ) -func OnlyOneNotNil(items ...interface{}) (any bool, one bool) { - oneNotNil := false - for _, i := range items { - if i != nil { - if oneNotNil { - return true, false - } - oneNotNil = true - } - } - return oneNotNil, oneNotNil -} - -func EqualSorted(s1, s2 []string) bool { +// genericEqualUnsorted reports whether two slices are identical up to reordering +// using a comparison function. +// If the lengths are different, genericEqualUnsorted returns false. Otherwise, the +// elements are sorted using the comparison function, and the sorted slices are +// compared element by element using the same comparison function. If all elements +// are equal, genericEqualUnsorted returns true. Otherwise it returns false. +func genericEqualUnsorted[S ~[]E, E any]( + s1 S, s2 S, + cmp func(a, b E) int, +) bool { if len(s1) != len(s2) { return false } - for i := range s1 { - if s1[i] != s2[i] { - return false - } - } + s1, s2 = slices.Clone(s1), slices.Clone(s2) - return true + slices.SortStableFunc(s1, cmp) + slices.SortStableFunc(s2, cmp) + + return slices.EqualFunc(s1, s2, func(a, b E) bool { + return cmp(a, b) == 0 + }) } func EqualUnsorted(s1 []string, s2 []string) bool { - if len(s1) != len(s2) { - return false - } - s1_2, s2_2 := make([]string, len(s1)), make([]string, len(s2)) - copy(s1_2, s1) - copy(s2_2, s2) - sort.Strings(s1_2) - sort.Strings(s2_2) - for i, s := range s1_2 { - if s != s2_2[i] { - return false - } - } - return true + return genericEqualUnsorted(s1, s2, strings.Compare) } // Test for equal URL slices even if unsorted. Panics if any element is nil func EqualURLsUnsorted(s1, s2 []*url.URL) bool { - if len(s1) != len(s2) { - return false - } - s1_2, s2_2 := make([]*url.URL, len(s1)), make([]*url.URL, len(s2)) - copy(s1_2, s1) - copy(s2_2, s2) - - sort.SliceStable(s1_2, func(i, j int) bool { - return s1_2[i].String() < s1_2[j].String() - }) - sort.SliceStable(s2_2, func(i, j int) bool { - return s2_2[i].String() < s2_2[j].String() + return genericEqualUnsorted(s1, s2, func(a, b *url.URL) int { + return strings.Compare(a.String(), b.String()) }) +} - for i, s := range s1_2 { - if s.String() != s2_2[i].String() { - return false +// Test for equal cmapi.OtherName slices even if unsorted. Panics if any element is nil +func EqualOtherNamesUnsorted(s1, s2 []cmapi.OtherName) bool { + return genericEqualUnsorted(s1, s2, func(a cmapi.OtherName, b cmapi.OtherName) int { + if a.OID == b.OID { + return strings.Compare(a.UTF8Value, b.UTF8Value) } - } - return true + return strings.Compare(a.OID, b.OID) + }) + } -// Test for equal IP slices even if unsorted +// EqualIPsUnsorted checks if the given slices of IP addresses contain the same elements, even if in a different order func EqualIPsUnsorted(s1, s2 []net.IP) bool { - if len(s1) != len(s2) { - return false - } - s1_2, s2_2 := make([]string, len(s1)), make([]string, len(s2)) - // we may want to implement a sort interface here instead of []byte conversion - for i := range s1 { - s1_2[i] = string(s1[i]) - s2_2[i] = string(s2[i]) - } - - sort.SliceStable(s1_2, func(i, j int) bool { - return s1_2[i] < s1_2[j] + // Two IPv4 addresses can compare unequal with bytes.Equal which is why net.IP.Equal exists. + // We still want to sort the lists, though, and we don't want different representations of IPv4 addresses + // to be sorted differently. That can happen if one is stored as a 4-byte address while + // the other is stored as a 16-byte representation + + // To avoid ambiguity, we ensure that only the 16-byte form is used for all addresses we work with. + return genericEqualUnsorted(s1, s2, func(a, b net.IP) int { + return bytes.Compare(a.To16(), b.To16()) }) - sort.SliceStable(s2_2, func(i, j int) bool { - return s2_2[i] < s2_2[j] - }) - - for i, s := range s1_2 { - if s != s2_2[i] { - return false - } - } - return true } // Test for equal KeyUsage slices even if unsorted func EqualKeyUsagesUnsorted(s1, s2 []cmapi.KeyUsage) bool { - if len(s1) != len(s2) { - return false - } - s1_2, s2_2 := make([]string, len(s1)), make([]string, len(s2)) - // we may want to implement a sort interface here instead of []byte conversion - for i := range s1 { - s1_2[i] = string(s1[i]) - s2_2[i] = string(s2[i]) - } - - sort.SliceStable(s1_2, func(i, j int) bool { - return s1_2[i] < s1_2[j] + return genericEqualUnsorted(s1, s2, func(a, b cmapi.KeyUsage) int { + return strings.Compare(string(a), string(b)) }) - sort.SliceStable(s2_2, func(i, j int) bool { - return s2_2[i] < s2_2[j] - }) - - for i, s := range s1_2 { - if s != s2_2[i] { - return false - } - } - return true -} - -func init() { - rand.Seed(time.Now().UnixNano()) } -var letterRunes = []rune("abcdefghijklmnopqrstuvwxyz") +// JoinWithEscapeCSV returns the given list as a single line of CSV that +// is escaped with quotes if necessary +func JoinWithEscapeCSV(in []string) (string, error) { + b := new(bytes.Buffer) + writer := csv.NewWriter(b) + if err := writer.Write(in); err != nil { + return "", fmt.Errorf("failed to write %q as CSV: %w", in, err) + } + writer.Flush() -func RandStringRunes(n int) string { - b := make([]rune, n) - for i := range b { - b[i] = letterRunes[rand.Intn(len(letterRunes))] + if err := writer.Error(); err != nil { + return "", fmt.Errorf("failed to write %q as CSV: %w", in, err) } - return string(b) + + s := b.String() + // CSV writer adds a trailing new line, we need to clean it up + s = strings.TrimSuffix(s, "\n") + return s, nil } -// Contains returns true if a string is contained in a string slice -func Contains(ss []string, s string) bool { - for _, v := range ss { - if v == s { - return true - } +// SplitWithEscapeCSV parses the given input as a single line of CSV, which allows +// a comma-separated list of strings to be parsed while allowing commas to be present +// in each field. For example, a user can specify: +// "10 Downing Street, Westminster",Manchester +// to produce []string{"10 Downing Street, Westminster", "Manchester"}, keeping the comma +// in the first address. Empty lines or multiple CSV records are both rejected. +func SplitWithEscapeCSV(in string) ([]string, error) { + reader := csv.NewReader(strings.NewReader(in)) + + records, err := reader.ReadAll() + if err != nil { + return nil, fmt.Errorf("failed to parse %q as CSV: %w", in, err) } - return false -} -// Subset returns true if one slice is an unsorted subset of the first. -func Subset(set, subset []string) bool { - for _, s := range subset { - if !Contains(set, s) { - return false - } + if len(records) == 0 { + return nil, fmt.Errorf("no values found after parsing %q", in) + } else if len(records) > 1 { + return nil, fmt.Errorf("refusing to use %q as input as it parses as multiple lines of CSV", in) } - return true + return records[0], nil } diff --git a/pkg/util/util_test.go b/pkg/util/util_test.go index 64ef624cccd..cbc708e3583 100644 --- a/pkg/util/util_test.go +++ b/pkg/util/util_test.go @@ -19,6 +19,7 @@ package util import ( "net" "net/url" + "slices" "testing" ) @@ -88,15 +89,104 @@ func TestEqualURLsUnsorted(t *testing.T) { } func TestEqualIPsUnsorted(t *testing.T) { - for _, test := range stringSliceTestData { - s1, s2 := parseIPs(t, test.s1), parseIPs(t, test.s2) - t.Run(test.desc, func(test testT) func(*testing.T) { - return func(t *testing.T) { - if actual := EqualIPsUnsorted(s1, s2); actual != test.equal { - t.Errorf("equalIpsUnsorted(%+v, %+v) = %t, but expected %t", s1, s2, actual, test.equal) - } + // This test uses string representations of IP addresses because it's much more convenient to + // represent different types of IPv6 address as strings. This implicitly relies on the behavior + // of net.ParseIP under the hood when it comes to parsing IPv4 addresses, though - it could return + // either a 4 or 16 byte slice to represent an IPv4 address. As such, we have a separate test + // which uses raw net.IP byte slices below, which checks that we're not relying on underlying + // behavior of ParseIP when comparing. + specs := map[string]struct { + s1 []string + s2 []string + + expEqual bool + }{ + "simple ipv4 comparison": { + s1: []string{"8.8.8.8", "1.1.1.1"}, + s2: []string{"1.1.1.1", "8.8.8.8"}, + expEqual: true, + }, + "simple ipv6 comparison": { + s1: []string{"2a00:1450:4009:822::200e", "2a03:2880:f166:81:face:b00c:0:25de"}, + s2: []string{"2a03:2880:f166:81:face:b00c:0:25de", "2a00:1450:4009:822::200e"}, + expEqual: true, + }, + "mixed ipv4 and ipv6": { + s1: []string{"2a00:1450:4009:822::200e", "2a03:2880:f166:81:face:b00c:0:25de", "1.1.1.1"}, + s2: []string{"2a03:2880:f166:81:face:b00c:0:25de", "1.1.1.1", "2a00:1450:4009:822::200e"}, + expEqual: true, + }, + "mixed ipv6 specificity": { + s1: []string{"2a03:2880:f166:0081:face:b00c:0000:25de"}, + s2: []string{"2a03:2880:f166:81:face:b00c:0:25de"}, + expEqual: true, + }, + "unequal addresses ipv6": { + s1: []string{"2a03:2880:f166:0081:face::25de"}, + s2: []string{"2a03:2880:f166:81:face:b00c:1:25de"}, + expEqual: false, + }, + } + + for name, spec := range specs { + s1 := parseIPs(spec.s1) + s2 := parseIPs(spec.s2) + + t.Run(name, func(t *testing.T) { + got := EqualIPsUnsorted(s1, s2) + + if got != spec.expEqual { + t.Errorf("EqualIPsUnsorted(%+v, %+v) = %t, but expected %t", s1, s2, got, spec.expEqual) } - }(test)) + }) + } +} + +func TestEqualIPsUnsorted_RawIPs(t *testing.T) { + // See description in TestEqualIPsUnsorted for motivation here + specs := map[string]struct { + s1 []net.IP + s2 []net.IP + + expEqual bool + }{ + "simple ipv4 comparison": { + s1: []net.IP{net.IP([]byte{0x1, 0x1, 0x1, 0x1}), net.IP([]byte{0x8, 0x8, 0x8, 0x8})}, + s2: []net.IP{net.IP([]byte{0x8, 0x8, 0x8, 0x8}), net.IP([]byte{0x1, 0x1, 0x1, 0x1})}, + expEqual: true, + }, + "simple ipv6 comparison": { + s1: []net.IP{ + net.IP([]byte{0x2a, 0xe, 0x23, 0x45, 0x67, 0x89, 0x0, 0x1, 0x0, 0x1, 0x0, 0x2, 0x0, 0x0, 0x0, 0x6}), + net.IP([]byte{0x2a, 0x03, 0x28, 0x80, 0xf1, 0x66, 0x00, 0x81, 0xfa, 0xce, 0xb0, 0x0c, 0x00, 0x00, 0x25, 0xde}), + }, + s2: []net.IP{ + net.IP([]byte{0x2a, 0x03, 0x28, 0x80, 0xf1, 0x66, 0x00, 0x81, 0xfa, 0xce, 0xb0, 0x0c, 0x00, 0x00, 0x25, 0xde}), + net.IP([]byte{0x2a, 0xe, 0x23, 0x45, 0x67, 0x89, 0x0, 0x1, 0x0, 0x1, 0x0, 0x2, 0x0, 0x0, 0x0, 0x6}), + }, + expEqual: true, + }, + "mixed ipv4 lengths": { + // This is the most important test in this test function! + // IPv4 addresses have two valid representations as `net.IP`s and we shouldn't miss the case where they're equal + s1: []net.IP{ + net.IP([]byte{0xa, 0x0, 0x0, 0xce}), + }, + s2: []net.IP{ + net.IP([]byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0xff, 0xa, 0x0, 0x0, 0xce}), + }, + expEqual: true, + }, + } + + for name, spec := range specs { + t.Run(name, func(t *testing.T) { + got := EqualIPsUnsorted(spec.s1, spec.s2) + + if got != spec.expEqual { + t.Errorf("EqualIPsUnsorted(%+v, %+v) = %t, but expected %t", spec.s1, spec.s2, got, spec.expEqual) + } + }) } } @@ -130,7 +220,7 @@ func TestContains(t *testing.T) { for _, test := range tests { t.Run(test.desc, func(test testT) func(*testing.T) { return func(t *testing.T) { - if actual := Contains(test.slice, test.value); actual != test.equal { + if actual := slices.Contains(test.slice, test.value); actual != test.equal { t.Errorf("Contains(%+v, %+v) = %t, but expected %t", test.slice, test.value, actual, test.equal) } } @@ -154,11 +244,11 @@ func parseURLs(t *testing.T, urlStrs []string) []*url.URL { return urls } -func parseIPs(t *testing.T, ipStrs []string) []net.IP { +func parseIPs(ipStrs []string) []net.IP { var ips []net.IP for _, i := range ipStrs { - ips = append(ips, []byte(i)) + ips = append(ips, net.ParseIP(i)) } return ips diff --git a/pkg/util/versionchecker/fromcrd.go b/pkg/util/versionchecker/fromcrd.go deleted file mode 100644 index a69ce5f41ba..00000000000 --- a/pkg/util/versionchecker/fromcrd.go +++ /dev/null @@ -1,91 +0,0 @@ -/* -Copyright 2021 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. -*/ - -package versionchecker - -import ( - "context" - - apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" - apiextensionsv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1" - apierrors "k8s.io/apimachinery/pkg/api/errors" - "sigs.k8s.io/controller-runtime/pkg/client" -) - -func (o *VersionChecker) extractVersionFromCrd(ctx context.Context, crdName string) error { - crdKey := client.ObjectKey{Name: crdName} - - objv1 := &apiextensionsv1.CustomResourceDefinition{} - err := o.client.Get(ctx, crdKey, objv1) - if err == nil { - if label := extractVersionFromLabels(objv1.Labels); label != "" { - o.versionSources["crdLabelVersion"] = label - } - - return o.extractVersionFromCrdv1(ctx, objv1) - } - - // If error differs from not found, don't continue and return error - if !apierrors.IsNotFound(err) { - return err - } - - objv1beta1 := &apiextensionsv1beta1.CustomResourceDefinition{} - err = o.client.Get(ctx, crdKey, objv1beta1) - if err == nil { - if label := extractVersionFromLabels(objv1beta1.Labels); label != "" { - o.versionSources["crdLabelVersion"] = label - } - - return o.extractVersionFromCrdv1beta1(ctx, objv1beta1) - } - - // If error differs from not found, don't continue and return error - if !apierrors.IsNotFound(err) { - return err - } - - return ErrCertManagerCRDsNotFound -} - -func (o *VersionChecker) extractVersionFromCrdv1(ctx context.Context, crd *apiextensionsv1.CustomResourceDefinition) error { - if (crd.Spec.Conversion == nil) || - (crd.Spec.Conversion.Webhook == nil) || - (crd.Spec.Conversion.Webhook.ClientConfig == nil) || - (crd.Spec.Conversion.Webhook.ClientConfig.Service == nil) { - return nil - } - - return o.extractVersionFromService( - ctx, - crd.Spec.Conversion.Webhook.ClientConfig.Service.Namespace, - crd.Spec.Conversion.Webhook.ClientConfig.Service.Name, - ) -} - -func (o *VersionChecker) extractVersionFromCrdv1beta1(ctx context.Context, crd *apiextensionsv1beta1.CustomResourceDefinition) error { - if (crd.Spec.Conversion == nil) || - (crd.Spec.Conversion.WebhookClientConfig == nil) || - (crd.Spec.Conversion.WebhookClientConfig.Service == nil) { - return nil - } - - return o.extractVersionFromService( - ctx, - crd.Spec.Conversion.WebhookClientConfig.Service.Namespace, - crd.Spec.Conversion.WebhookClientConfig.Service.Name, - ) -} diff --git a/pkg/util/versionchecker/fromlabels.go b/pkg/util/versionchecker/fromlabels.go deleted file mode 100644 index 24337e4ec08..00000000000 --- a/pkg/util/versionchecker/fromlabels.go +++ /dev/null @@ -1,45 +0,0 @@ -/* -Copyright 2021 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. -*/ - -package versionchecker - -import ( - "regexp" -) - -var helmChartVersion = regexp.MustCompile(`-(v(?:\d+)\.(?:\d+)\.(?:\d+)(?:.*))$`) - -func extractVersionFromLabels(crdLabels map[string]string) string { - if version, ok := crdLabels["app.kubernetes.io/version"]; ok { - return version - } - - if chartName, ok := crdLabels["helm.sh/chart"]; ok { - version := helmChartVersion.FindStringSubmatch(chartName) - if len(version) == 2 { - return version[1] - } - } - - if chartName, ok := crdLabels["chart"]; ok { - version := helmChartVersion.FindStringSubmatch(chartName) - if len(version) == 2 { - return version[1] - } - } - - return "" -} diff --git a/pkg/util/versionchecker/fromservice.go b/pkg/util/versionchecker/fromservice.go deleted file mode 100644 index 9e382f86817..00000000000 --- a/pkg/util/versionchecker/fromservice.go +++ /dev/null @@ -1,74 +0,0 @@ -/* -Copyright 2021 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. -*/ - -package versionchecker - -import ( - "context" - "regexp" - - corev1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/labels" - "sigs.k8s.io/controller-runtime/pkg/client" -) - -var imageVersion = regexp.MustCompile(`^quay.io/jetstack/cert-manager-webhook:(v(?:\d+)\.(?:\d+)\.(?:\d+)(?:.*))$`) - -func (o *VersionChecker) extractVersionFromService( - ctx context.Context, - namespace string, - serviceName string, -) error { - service := &corev1.Service{} - serviceKey := client.ObjectKey{Namespace: namespace, Name: serviceName} - err := o.client.Get(ctx, serviceKey, service) - if err != nil { - return err - } - - if label := extractVersionFromLabels(service.Labels); label != "" { - o.versionSources["webhookServiceLabelVersion"] = label - } - - listOptions := client.MatchingLabelsSelector{ - Selector: labels.Set(service.Spec.Selector).AsSelector(), - } - pods := &corev1.PodList{} - err = o.client.List(ctx, pods, listOptions) - if err != nil { - return err - } - - for _, pod := range pods.Items { - if pod.Status.Phase != corev1.PodRunning { - continue - } - - if label := extractVersionFromLabels(pod.Labels); label != "" { - o.versionSources["webhookPodLabelVersion"] = label - } - - for _, container := range pod.Spec.Containers { - version := imageVersion.FindStringSubmatch(container.Image) - if len(version) == 2 { - o.versionSources["webhookPodImageVersion"] = version[1] - return nil - } - } - } - - return nil -} diff --git a/pkg/util/versionchecker/versionchecker.go b/pkg/util/versionchecker/versionchecker.go deleted file mode 100644 index 342e6fb8af8..00000000000 --- a/pkg/util/versionchecker/versionchecker.go +++ /dev/null @@ -1,169 +0,0 @@ -/* -Copyright 2021 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. -*/ - -package versionchecker - -import ( - "context" - "fmt" - - errors "github.com/pkg/errors" - corev1 "k8s.io/api/core/v1" - apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" - apiextensionsv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/client-go/rest" - "sigs.k8s.io/controller-runtime/pkg/client" -) - -const certificatesCertManagerCrdName = "certificates.cert-manager.io" -const certificatesCertManagerOldCrdName = "certificates.certmanager.k8s.io" - -var certManagerLabelSelector = map[string]string{ - "app.kubernetes.io/instance": "cert-manager", -} -var certManagerOldLabelSelector = map[string]string{ - "release": "cert-manager", -} - -var ( - ErrCertManagerCRDsNotFound = errors.New("the cert-manager CRDs are not yet installed on the Kubernetes API server") - ErrVersionNotDetected = errors.New("could not detect the cert-manager version") - ErrMultipleVersionsDetected = errors.New("detect multiple different cert-manager versions") -) - -type Version struct { - // If all found versions are the same, - // this field will contain that version - Detected string `json:"detected,omitempty"` - - Sources map[string]string `json:"sources"` -} - -func shouldReturn(err error) bool { - return (err == nil) || (!errors.Is(err, ErrVersionNotDetected)) -} - -// Interface is used to check what cert-manager version is installed -type Interface interface { - Version(context.Context) (*Version, error) -} - -// VersionChecker implements a version checker using a controller-runtime client -type VersionChecker struct { - client client.Client - - versionSources map[string]string -} - -// New returns a cert-manager version checker. Prefer New over NewFromClient -// since New will ensure the scheme is configured correctly. -func New(restcfg *rest.Config, scheme *runtime.Scheme) (*VersionChecker, error) { - if err := corev1.AddToScheme(scheme); err != nil { - return nil, err - } - - if err := apiextensionsv1.AddToScheme(scheme); err != nil { - return nil, err - } - - if err := apiextensionsv1beta1.AddToScheme(scheme); err != nil { - return nil, err - } - - cl, err := client.New(restcfg, client.Options{ - Scheme: scheme, - }) - if err != nil { - return nil, err - } - - return &VersionChecker{ - client: cl, - versionSources: map[string]string{}, - }, nil -} - -// NewFromClient initialises a VersionChecker using the given client. Prefer New -// instead, which will ensure that the scheme on the client is configured correctly. -func NewFromClient(cl client.Client) *VersionChecker { - return &VersionChecker{ - client: cl, - versionSources: map[string]string{}, - } -} - -// Version determines the installed cert-manager version. First, we look for -// the "certificates.cert-manager.io" CRD and try to extract the version from that -// resource's labels. Then, if it uses a webhook, that webhook service resource's -// labels are checked for a label. Lastly the pods linked to the webhook its labels -// are checked and the image tag is used to determine the version. -// If no "certificates.cert-manager.io" CRD is found, the older -// "certificates.certmanager.k8s.io" CRD is tried too. -func (o *VersionChecker) Version(ctx context.Context) (*Version, error) { - // Use the "certificates.cert-manager.io" CRD as a starting point - err := o.extractVersionFromCrd(ctx, certificatesCertManagerCrdName) - - if err != nil && errors.Is(err, ErrCertManagerCRDsNotFound) { - // Retry using the old CRD name "certificates.certmanager.k8s.io" as - // a starting point and overwrite ErrCertManagerCRDsNotFound error - err = o.extractVersionFromCrd(ctx, certificatesCertManagerOldCrdName) - } - - // From the found versions, now determine if we have found any/ - // if they are all the same version - version, detectionError := o.determineVersion() - - if err != nil && detectionError != nil { - // There was an error while determining the version (which is probably - // caused by a bad setup/ permission or networking issue) and there also - // was an error while trying to reduce the found versions to 1 version - // Display both. - err = fmt.Errorf("%v: %v", detectionError, err) - } else if detectionError != nil { - // An error occured while trying to reduce the found versions to 1 version - err = detectionError - } - - return version, err -} - -// determineVersion attempts to determine the version of the cert-manager install based on all found -// versions. The function tries to reduce the found versions to 1 correct version. -// An error is returned if no sources were found or if multiple different versions -// were found. -func (o *VersionChecker) determineVersion() (*Version, error) { - if len(o.versionSources) == 0 { - return nil, ErrVersionNotDetected - } - - var detectedVersion string - for _, version := range o.versionSources { - if detectedVersion != "" && version != detectedVersion { - // We have found a conflicting version - return &Version{ - Sources: o.versionSources, - }, ErrMultipleVersionsDetected - } - - detectedVersion = version - } - - return &Version{ - Detected: detectedVersion, - Sources: o.versionSources, - }, nil -} diff --git a/pkg/webhook/admission/chain.go b/pkg/webhook/admission/chain.go index 68025c89a72..2f22b870372 100644 --- a/pkg/webhook/admission/chain.go +++ b/pkg/webhook/admission/chain.go @@ -20,6 +20,7 @@ import ( "context" admissionv1 "k8s.io/api/admission/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" utilerrors "k8s.io/apimachinery/pkg/util/errors" ) @@ -55,7 +56,7 @@ func (pc PluginChain) Validate(ctx context.Context, request admissionv1.Admissio return allWarnings, utilerrors.NewAggregate(allErrors) } -func (pc PluginChain) Mutate(ctx context.Context, request admissionv1.AdmissionRequest, obj runtime.Object) error { +func (pc PluginChain) Mutate(ctx context.Context, request admissionv1.AdmissionRequest, obj *unstructured.Unstructured) error { for _, handler := range pc { if !handler.Handles(request.Operation) { continue diff --git a/pkg/webhook/admission/chain_test.go b/pkg/webhook/admission/chain_test.go index b77e6211eb6..9888c16e884 100644 --- a/pkg/webhook/admission/chain_test.go +++ b/pkg/webhook/admission/chain_test.go @@ -23,10 +23,10 @@ import ( "testing" admissionv1 "k8s.io/api/admission/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" "github.com/cert-manager/cert-manager/pkg/webhook/admission" - v1 "github.com/cert-manager/cert-manager/pkg/webhook/handlers/testdata/apis/testgroup/v1" ) func TestChainHandles(t *testing.T) { @@ -82,13 +82,13 @@ func TestChainValidate(t *testing.T) { }, mutatingImplementation{ handles: handles(true).Handles, - mutate: func(ctx context.Context, request admissionv1.AdmissionRequest, obj runtime.Object) error { + mutate: func(ctx context.Context, request admissionv1.AdmissionRequest, obj *unstructured.Unstructured) error { t.Errorf("mutate function was unexpectedly called during a validate call") return fmt.Errorf("unexpected error") }, }, }) - warnings, err := pc.Validate(context.Background(), admissionv1.AdmissionRequest{}, nil, nil) + warnings, err := pc.Validate(t.Context(), admissionv1.AdmissionRequest{}, nil, nil) if err != nil { t.Errorf("unexpected error: %v", err) } @@ -117,7 +117,7 @@ func TestChainValidate_Fails(t *testing.T) { }, }, }) - warnings, err := pc.Validate(context.Background(), admissionv1.AdmissionRequest{}, nil, nil) + warnings, err := pc.Validate(t.Context(), admissionv1.AdmissionRequest{}, nil, nil) if err == nil { t.Errorf("didn't get an error when one was expected") } @@ -131,32 +131,29 @@ func TestChainMutate(t *testing.T) { // this handler should be called mutatingImplementation{ handles: handles(true).Handles, - mutate: func(ctx context.Context, request admissionv1.AdmissionRequest, obj runtime.Object) error { - tt := obj.(*v1.TestType) - tt.TestField = "testvalue" - return nil + mutate: func(ctx context.Context, request admissionv1.AdmissionRequest, obj *unstructured.Unstructured) error { + return unstructured.SetNestedField(obj.Object, "testvalue", "testField1") }, }, // this handler should not be called mutatingImplementation{ handles: handles(false).Handles, - mutate: func(ctx context.Context, request admissionv1.AdmissionRequest, obj runtime.Object) error { - tt := obj.(*v1.TestType) - tt.TestFieldImmutable = "hopefully-not-set" - return nil + mutate: func(ctx context.Context, request admissionv1.AdmissionRequest, obj *unstructured.Unstructured) error { + return unstructured.SetNestedField(obj.Object, "hopefully-not-set", "testField2") }, }, }) - tt := &v1.TestType{} - err := pc.Mutate(context.Background(), admissionv1.AdmissionRequest{}, tt) + tt := &unstructured.Unstructured{Object: map[string]any{}} + err := pc.Mutate(t.Context(), admissionv1.AdmissionRequest{}, tt) if err != nil { t.Errorf("unexpected error: %v", err) } - if tt.TestField != "testvalue" { - t.Errorf("expected tt.TestField=testvalue but got %q", tt.TestField) + + if val, ok, err := unstructured.NestedString(tt.Object, "testField1"); err != nil || !ok || val != "testvalue" { + t.Errorf("expected tt.testField1=testvalue but got %q", tt.Object) } - if tt.TestFieldImmutable != "" { - t.Errorf("expected tt.TestFieldImmutable to not be set, but got %q", tt.TestFieldImmutable) + if val, ok, err := unstructured.NestedString(tt.Object, "testField2"); err != nil || ok || val != "" { + t.Errorf("expected tt.testField2 to not be set, but got %q", tt.Object) } } @@ -165,13 +162,12 @@ func TestChainMutate_Fails(t *testing.T) { // this handler should be called and should error mutatingImplementation{ handles: handles(true).Handles, - mutate: func(ctx context.Context, request admissionv1.AdmissionRequest, obj runtime.Object) error { + mutate: func(ctx context.Context, request admissionv1.AdmissionRequest, obj *unstructured.Unstructured) error { return fmt.Errorf("error") }, }, }) - tt := &v1.TestType{} - err := pc.Mutate(context.Background(), admissionv1.AdmissionRequest{}, tt) + err := pc.Mutate(t.Context(), admissionv1.AdmissionRequest{}, &unstructured.Unstructured{Object: map[string]any{}}) if err == nil { t.Errorf("expected error but got none") } diff --git a/pkg/webhook/admission/custom_decoder.go b/pkg/webhook/admission/custom_decoder.go new file mode 100644 index 00000000000..90ad626b00d --- /dev/null +++ b/pkg/webhook/admission/custom_decoder.go @@ -0,0 +1,70 @@ +/* +Copyright 2020 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. +*/ + +package admission + +import ( + "fmt" + + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/runtime/serializer" + "k8s.io/utils/ptr" +) + +// decoder knows how to decode the contents of an admission +// request into a concrete object. +type internalDecoder struct { + scheme *runtime.Scheme + codecs serializer.CodecFactory +} + +// DecodeRaw decodes a RawExtension object. +// It errors out if rawObj is empty i.e. containing 0 raw bytes. +func (d *internalDecoder) DecodeRaw(rawObj runtime.RawExtension, rawKind schema.GroupVersionKind) (runtime.Object, error) { + // we error out if rawObj is an empty object. + if len(rawObj.Raw) == 0 { + return nil, fmt.Errorf("there is no content to decode") + } + + obj, gvk, err := d.codecs.UniversalDeserializer().Decode(rawObj.Raw, ptr.To(rawKind), nil) + if err != nil { + return nil, err + } + if obj.GetObjectKind().GroupVersionKind().Empty() && gvk != nil { + obj.GetObjectKind().SetGroupVersionKind(*gvk) + } + + return d.scheme.UnsafeConvertToVersion(obj, runtime.InternalGroupVersioner) +} + +// DecodeRawUnstructured decodes a RawExtension object into an unstructured object. +func DecodeRawUnstructured(rawObj runtime.RawExtension, rawKind schema.GroupVersionKind) (*unstructured.Unstructured, error) { + if len(rawObj.Raw) == 0 { + return nil, fmt.Errorf("there is no content to decode") + } + + obj := &unstructured.Unstructured{} + if err := obj.UnmarshalJSON(rawObj.Raw); err != nil { + return nil, err + } + if obj.GetObjectKind().GroupVersionKind().Empty() { + obj.GetObjectKind().SetGroupVersionKind(rawKind) + } + + return obj, nil +} diff --git a/pkg/webhook/admission/custom_mutation_webhook.go b/pkg/webhook/admission/custom_mutation_webhook.go new file mode 100644 index 00000000000..57f3178f954 --- /dev/null +++ b/pkg/webhook/admission/custom_mutation_webhook.go @@ -0,0 +1,90 @@ +/* +Copyright 2020 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. +*/ + +package admission + +import ( + "context" + "errors" + "net/http" + + admissionv1 "k8s.io/api/admission/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/runtime/schema" + "sigs.k8s.io/controller-runtime/pkg/webhook/admission" +) + +func NewCustomMutationWebhook( + mutationWebhook MutationInterface, +) *admission.Webhook { + return &admission.Webhook{ + Handler: &mutator{ + mutationWebhook: mutationWebhook, + }, + } +} + +type mutator struct { + mutationWebhook MutationInterface +} + +// Handle handles admission requests. +func (h *mutator) Handle(ctx context.Context, req admission.Request) admission.Response { + // short-path + if h.mutationWebhook == nil || !h.mutationWebhook.Handles(req.AdmissionRequest.Operation) { + return admission.Allowed("") + } + + // Always skip when a DELETE operation received in custom mutation handler. + if req.Operation == admissionv1.Delete { + return admission.Allowed("") + } + + ctx = admission.NewContextWithRequest(ctx, req) + gvk := schema.GroupVersionKind{ + Group: req.Kind.Group, + Version: req.Kind.Version, + Kind: req.Kind.Kind, + } + + // Get the object in the request + obj, err := DecodeRawUnstructured(req.Object, gvk) + if err != nil { + return admission.Errored(http.StatusBadRequest, err) + } + + // Default the object + if err := h.mutationWebhook.Mutate(ctx, req.AdmissionRequest, obj); err != nil { + var apiStatus apierrors.APIStatus + if errors.As(err, &apiStatus) { + status := apiStatus.Status() + return admission.Response{ + AdmissionResponse: admissionv1.AdmissionResponse{ + Allowed: false, + Result: &status, + }, + } + } + return admission.Denied(err.Error()) + } + + // Create the patch + marshalled, err := obj.MarshalJSON() + if err != nil { + return admission.Errored(http.StatusInternalServerError, err) + } + return admission.PatchResponseFromRaw(req.Object.Raw, marshalled) +} diff --git a/pkg/webhook/admission/custom_validator_webhook.go b/pkg/webhook/admission/custom_validator_webhook.go new file mode 100644 index 00000000000..fedc2337f03 --- /dev/null +++ b/pkg/webhook/admission/custom_validator_webhook.go @@ -0,0 +1,124 @@ +/* +Copyright 2020 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. +*/ + +package admission + +import ( + "context" + "errors" + "fmt" + "net/http" + + admissionv1 "k8s.io/api/admission/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/runtime/serializer" + "sigs.k8s.io/controller-runtime/pkg/webhook/admission" +) + +func NewCustomValidationWebhook( + scheme *runtime.Scheme, + validationWebhook ValidationInterface, +) *admission.Webhook { + return &admission.Webhook{ + Handler: &validator{ + decoder: &internalDecoder{ + scheme: scheme, + codecs: serializer.NewCodecFactory(scheme), + }, + validationWebhook: validationWebhook, + }, + } +} + +type validator struct { + decoder *internalDecoder + validationWebhook ValidationInterface +} + +// Handle handles admission requests. +func (h *validator) Handle(ctx context.Context, req admission.Request) admission.Response { + if h.decoder == nil { + panic("decoder should never be nil") + } + + // short-path + if h.validationWebhook == nil || !h.validationWebhook.Handles(req.AdmissionRequest.Operation) { + return admission.Allowed("") + } + + ctx = admission.NewContextWithRequest(ctx, req) + gvk := schema.GroupVersionKind{ + Group: req.Kind.Group, + Version: req.Kind.Version, + Kind: req.Kind.Kind, + } + + var obj runtime.Object + var oldObj runtime.Object + var err error + var warnings []string + + switch req.Operation { + case admissionv1.Connect: + // No validation for connect requests. + // TODO(vincepri): Should we validate CONNECT requests? In what cases? + case admissionv1.Create: + if obj, err = h.decoder.DecodeRaw(req.Object, gvk); err != nil { + return admission.Errored(http.StatusBadRequest, err) + } + + warnings, err = h.validationWebhook.Validate(ctx, req.AdmissionRequest, nil, obj) + case admissionv1.Update: + if obj, err = h.decoder.DecodeRaw(req.Object, gvk); err != nil { + return admission.Errored(http.StatusBadRequest, err) + } + if oldObj, err = h.decoder.DecodeRaw(req.OldObject, gvk); err != nil { + return admission.Errored(http.StatusBadRequest, err) + } + + warnings, err = h.validationWebhook.Validate(ctx, req.AdmissionRequest, oldObj, obj) + case admissionv1.Delete: + // In reference to PR: https://github.com/kubernetes/kubernetes/pull/76346 + // OldObject contains the object being deleted + if oldObj, err = h.decoder.DecodeRaw(req.OldObject, gvk); err != nil { + return admission.Errored(http.StatusBadRequest, err) + } + + warnings, err = h.validationWebhook.Validate(ctx, req.AdmissionRequest, oldObj, nil) + default: + return admission.Errored(http.StatusBadRequest, fmt.Errorf("unknown operation %q", req.Operation)) + } + + // Check the error message first. + if err != nil { + var apiStatus apierrors.APIStatus + if errors.As(err, &apiStatus) { + status := apiStatus.Status() + return admission.Response{ + AdmissionResponse: admissionv1.AdmissionResponse{ + Allowed: false, + Result: &status, + }, + }.WithWarnings(warnings...) + } + return admission.Denied(err.Error()).WithWarnings(warnings...) + } + + // Return allowed if everything succeeded. + return admission.Allowed("").WithWarnings(warnings...) +} diff --git a/pkg/webhook/admission/handler.go b/pkg/webhook/admission/handler.go index 119f18e19dd..64b771175d6 100644 --- a/pkg/webhook/admission/handler.go +++ b/pkg/webhook/admission/handler.go @@ -22,7 +22,7 @@ import ( ) type Handler struct { - operations sets.String + operations sets.Set[string] } func (h Handler) Handles(operation admissionv1.Operation) bool { @@ -32,7 +32,7 @@ func (h Handler) Handles(operation admissionv1.Operation) bool { var _ Interface = &Handler{} func NewHandler(ops ...admissionv1.Operation) *Handler { - operations := sets.NewString() + operations := sets.New[string]() for _, op := range ops { operations.Insert(string(op)) } diff --git a/pkg/webhook/admission/initializer/initializer.go b/pkg/webhook/admission/initializer/initializer.go deleted file mode 100644 index 7fedaedec70..00000000000 --- a/pkg/webhook/admission/initializer/initializer.go +++ /dev/null @@ -1,73 +0,0 @@ -/* -Copyright 2021 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. -*/ - -/* -Copyright 2017 The Kubernetes Authors. -Derived from https://github.com/kubernetes/kubernetes/blob/9d0d2e8ece9bdd0cd8c23be2f36eee5473afc648/staging/src/k8s.io/apiserver/pkg/admission/initializer/initializer.go -*/ - -package initializer - -import ( - "k8s.io/apiserver/pkg/authorization/authorizer" - "k8s.io/client-go/informers" - "k8s.io/client-go/kubernetes" - "k8s.io/component-base/featuregate" - - "github.com/cert-manager/cert-manager/pkg/webhook/admission" -) - -type pluginInitializer struct { - externalClient kubernetes.Interface - externalInformers informers.SharedInformerFactory - authorizer authorizer.Authorizer - featureGates featuregate.FeatureGate -} - -// New creates an instance of admission plugins initializer. -// This constructor is public with a long param list so that callers immediately know that new information can be expected -// during compilation when they update a level. -func New(extClientset kubernetes.Interface, extInformers informers.SharedInformerFactory, authz authorizer.Authorizer, featureGates featuregate.FeatureGate) pluginInitializer { - return pluginInitializer{ - externalClient: extClientset, - externalInformers: extInformers, - authorizer: authz, - featureGates: featureGates, - } -} - -// Initialize checks the initialization interfaces implemented by a plugin -// and provide the appropriate initialization data -func (i pluginInitializer) Initialize(plugin admission.Interface) { - // First tell the plugin about enabled features, so it can decide whether to start informers or not - if wants, ok := plugin.(WantsFeatures); ok { - wants.InspectFeatureGates(i.featureGates) - } - - if wants, ok := plugin.(WantsExternalKubeClientSet); ok { - wants.SetExternalKubeClientSet(i.externalClient) - } - - if wants, ok := plugin.(WantsExternalKubeInformerFactory); ok { - wants.SetExternalKubeInformerFactory(i.externalInformers) - } - - if wants, ok := plugin.(WantsAuthorizer); ok { - wants.SetAuthorizer(i.authorizer) - } -} - -var _ admission.PluginInitializer = pluginInitializer{} diff --git a/pkg/webhook/admission/initializer/initializer_test.go b/pkg/webhook/admission/initializer/initializer_test.go deleted file mode 100644 index 3f770fc32ba..00000000000 --- a/pkg/webhook/admission/initializer/initializer_test.go +++ /dev/null @@ -1,159 +0,0 @@ -/* -Copyright 2021 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. -*/ - -/* -Copyright 2017 The Kubernetes Authors. -Derived from https://github.com/kubernetes/kubernetes/blob/9d0d2e8ece9bdd0cd8c23be2f36eee5473afc648/staging/src/k8s.io/apiserver/pkg/admission/initializer/initializer_test.go -*/ - -package initializer_test - -import ( - "context" - "testing" - "time" - - admissionv1 "k8s.io/api/admission/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apiserver/pkg/authorization/authorizer" - "k8s.io/client-go/informers" - "k8s.io/client-go/kubernetes" - "k8s.io/client-go/kubernetes/fake" - "k8s.io/component-base/featuregate" - - "github.com/cert-manager/cert-manager/pkg/webhook/admission" - "github.com/cert-manager/cert-manager/pkg/webhook/admission/initializer" -) - -// TestWantsFeature ensures that the feature gates are injected -// when the WantsFeatures interface is implemented by a plugin. -func TestWantsFeatures(t *testing.T) { - target := initializer.New(nil, nil, nil, featuregate.NewFeatureGate()) - wantFeaturesAdmission := &WantsFeaturesAdmission{} - target.Initialize(wantFeaturesAdmission) - if wantFeaturesAdmission.features == nil { - t.Errorf("expected features to be initialized but found nil") - } -} - -// TestWantsAuthorizer ensures that the authorizer is injected -// when the WantsAuthorizer interface is implemented by a plugin. -func TestWantsAuthorizer(t *testing.T) { - target := initializer.New(nil, nil, &TestAuthorizer{}, nil) - wantAuthorizerAdmission := &WantAuthorizerAdmission{} - target.Initialize(wantAuthorizerAdmission) - if wantAuthorizerAdmission.auth == nil { - t.Errorf("expected authorizer to be initialized but found nil") - } -} - -// TestWantsExternalKubeClientSet ensures that the clientset is injected -// when the WantsExternalKubeClientSet interface is implemented by a plugin. -func TestWantsExternalKubeClientSet(t *testing.T) { - cs := &fake.Clientset{} - target := initializer.New(cs, nil, &TestAuthorizer{}, nil) - wantExternalKubeClientSet := &WantExternalKubeClientSet{} - target.Initialize(wantExternalKubeClientSet) - if wantExternalKubeClientSet.cs != cs { - t.Errorf("expected clientset to be initialized") - } -} - -// TestWantsExternalKubeInformerFactory ensures that the informer factory is injected -// when the WantsExternalKubeInformerFactory interface is implemented by a plugin. -func TestWantsExternalKubeInformerFactory(t *testing.T) { - cs := &fake.Clientset{} - sf := informers.NewSharedInformerFactory(cs, time.Duration(1)*time.Second) - target := initializer.New(cs, sf, &TestAuthorizer{}, nil) - wantExternalKubeInformerFactory := &WantExternalKubeInformerFactory{} - target.Initialize(wantExternalKubeInformerFactory) - if wantExternalKubeInformerFactory.sf != sf { - t.Errorf("expected informer factory to be initialized") - } -} - -// WantExternalKubeInformerFactory is a test stub that fulfills the WantsExternalKubeInformerFactory interface -type WantExternalKubeInformerFactory struct { - sf informers.SharedInformerFactory -} - -func (self *WantExternalKubeInformerFactory) SetExternalKubeInformerFactory(sf informers.SharedInformerFactory) { - self.sf = sf -} -func (self *WantExternalKubeInformerFactory) Validate(ctx context.Context, request admissionv1.AdmissionRequest, oldObj, obj runtime.Object) (warnings []string, err error) { - return nil, nil -} -func (self *WantExternalKubeInformerFactory) Handles(o admissionv1.Operation) bool { return false } -func (self *WantExternalKubeInformerFactory) ValidateInitialization() error { return nil } - -var _ admission.Interface = &WantExternalKubeInformerFactory{} -var _ initializer.WantsExternalKubeInformerFactory = &WantExternalKubeInformerFactory{} - -// WantExternalKubeClientSet is a test stub that fulfills the WantsExternalKubeClientSet interface -type WantExternalKubeClientSet struct { - cs kubernetes.Interface -} - -func (self *WantExternalKubeClientSet) SetExternalKubeClientSet(cs kubernetes.Interface) { - self.cs = cs -} -func (self *WantExternalKubeClientSet) Validate(ctx context.Context, request admissionv1.AdmissionRequest, oldObj, obj runtime.Object) (warnings []string, err error) { - return nil, nil -} -func (self *WantExternalKubeClientSet) Handles(o admissionv1.Operation) bool { return false } -func (self *WantExternalKubeClientSet) ValidateInitialization() error { return nil } - -var _ admission.Interface = &WantExternalKubeClientSet{} -var _ initializer.WantsExternalKubeClientSet = &WantExternalKubeClientSet{} - -// WantAuthorizerAdmission is a test stub that fulfills the WantsAuthorizer interface. -type WantAuthorizerAdmission struct { - auth authorizer.Authorizer -} - -func (self *WantAuthorizerAdmission) SetAuthorizer(a authorizer.Authorizer) { self.auth = a } -func (self *WantAuthorizerAdmission) Validate(ctx context.Context, request admissionv1.AdmissionRequest, oldObj, obj runtime.Object) (warnings []string, err error) { - return nil, nil -} -func (self *WantAuthorizerAdmission) Handles(o admissionv1.Operation) bool { return false } -func (self *WantAuthorizerAdmission) ValidateInitialization() error { return nil } - -var _ admission.Interface = &WantAuthorizerAdmission{} -var _ initializer.WantsAuthorizer = &WantAuthorizerAdmission{} - -// TestAuthorizer is a test stub that fulfills the WantsAuthorizer interface. -type TestAuthorizer struct{} - -func (t *TestAuthorizer) Authorize(ctx context.Context, a authorizer.Attributes) (authorized authorizer.Decision, reason string, err error) { - return authorizer.DecisionNoOpinion, "", nil -} - -// WantsFeaturesAdmission is a test stub that fulfills the WantsFeatures interface. -type WantsFeaturesAdmission struct { - features featuregate.FeatureGate -} - -func (self *WantsFeaturesAdmission) InspectFeatureGates(gate featuregate.FeatureGate) { - self.features = gate -} -func (self *WantsFeaturesAdmission) Validate(ctx context.Context, request admissionv1.AdmissionRequest, oldObj, obj runtime.Object) (warnings []string, err error) { - return nil, nil -} -func (self *WantsFeaturesAdmission) Handles(o admissionv1.Operation) bool { return false } -func (self *WantsFeaturesAdmission) ValidateInitialization() error { return nil } - -var _ admission.Interface = &WantsFeaturesAdmission{} -var _ initializer.WantsFeatures = &WantsFeaturesAdmission{} diff --git a/pkg/webhook/admission/initializer/interfaces.go b/pkg/webhook/admission/initializer/interfaces.go deleted file mode 100644 index f19f80fc0e0..00000000000 --- a/pkg/webhook/admission/initializer/interfaces.go +++ /dev/null @@ -1,68 +0,0 @@ -/* -Copyright 2021 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. -*/ - -/* -Copyright 2017 The Kubernetes Authors. -Derived from https://github.com/kubernetes/kubernetes/blob/9d0d2e8ece9bdd0cd8c23be2f36eee5473afc648/staging/src/k8s.io/apiserver/pkg/admission/initializer/interfaces.go -*/ - -package initializer - -import ( - "k8s.io/apiserver/pkg/authorization/authorizer" - quota "k8s.io/apiserver/pkg/quota/v1" - "k8s.io/client-go/informers" - "k8s.io/client-go/kubernetes" - "k8s.io/component-base/featuregate" - - "github.com/cert-manager/cert-manager/pkg/webhook/admission" -) - -// WantsExternalKubeClientSet defines a function which sets external ClientSet for admission plugins that need it -type WantsExternalKubeClientSet interface { - SetExternalKubeClientSet(kubernetes.Interface) - admission.InitializationValidator -} - -// WantsExternalKubeInformerFactory defines a function which sets InformerFactory for admission plugins that need it -type WantsExternalKubeInformerFactory interface { - SetExternalKubeInformerFactory(informers.SharedInformerFactory) - admission.InitializationValidator -} - -// WantsAuthorizer defines a function which sets Authorizer for admission plugins that need it. -type WantsAuthorizer interface { - SetAuthorizer(authorizer.Authorizer) - admission.InitializationValidator -} - -// WantsQuotaConfiguration defines a function which sets quota configuration for admission plugins that need it. -type WantsQuotaConfiguration interface { - SetQuotaConfiguration(quota.Configuration) - admission.InitializationValidator -} - -// WantsFeatures defines a function which passes the featureGates for inspection by an admission plugin. -// Admission plugins should not hold a reference to the featureGates. Instead, they should query a particular one -// and assign it to a simple bool in the admission plugin struct. -// -// func (a *admissionPlugin) InspectFeatureGates(features featuregate.FeatureGate){ -// a.myFeatureIsOn = features.Enabled("my-feature") -// } -type WantsFeatures interface { - InspectFeatureGates(featuregate.FeatureGate) - admission.InitializationValidator -} diff --git a/pkg/webhook/admission/interfaces.go b/pkg/webhook/admission/interfaces.go index e00db44dd90..260311e627a 100644 --- a/pkg/webhook/admission/interfaces.go +++ b/pkg/webhook/admission/interfaces.go @@ -20,26 +20,10 @@ import ( "context" admissionv1 "k8s.io/api/admission/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" ) -// Factory constructs an admission plugin. -// This may be used in future to provide an `io.Reader` to the -// plugin to be used for loading plugin specific configuration. -type Factory func() (Interface, error) - -// PluginInitializer is used for initialization of shareable resources between admission plugins. -// After initialization the resources have to be set separately -type PluginInitializer interface { - Initialize(plugin Interface) -} - -// InitializationValidator holds ValidateInitialization functions, which are responsible for validation of initialized -// shared resources and should be implemented on admission plugins -type InitializationValidator interface { - ValidateInitialization() error -} - // Interface is the base admission interface type Interface interface { Handles(admissionv1.Operation) bool @@ -58,5 +42,5 @@ type ValidationInterface interface { type MutationInterface interface { Interface - Mutate(ctx context.Context, request admissionv1.AdmissionRequest, obj runtime.Object) (err error) + Mutate(ctx context.Context, request admissionv1.AdmissionRequest, obj *unstructured.Unstructured) (err error) } diff --git a/pkg/webhook/admission/plugins.go b/pkg/webhook/admission/plugins.go deleted file mode 100644 index 4e0b8270bde..00000000000 --- a/pkg/webhook/admission/plugins.go +++ /dev/null @@ -1,94 +0,0 @@ -/* -Copyright 2021 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. -*/ - -package admission - -import ( - "fmt" - - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/runtime/serializer" -) - -// Plugins manages initialising, registering and executing admission plugins -// for both validation and mutation. -type Plugins struct { - decoder runtime.Decoder - - pluginFactory map[string]Factory -} - -func NewPlugins(scheme *runtime.Scheme) *Plugins { - return &Plugins{ - decoder: serializer.NewCodecFactory(scheme).UniversalDecoder(), - pluginFactory: make(map[string]Factory), - } -} - -func (ps *Plugins) Register(name string, factory Factory) { - ps.pluginFactory[name] = factory -} - -func (ps *Plugins) NewFromPlugins(names []string, pluginInitializer PluginInitializer) (Interface, error) { - var plugins []Interface - for _, pluginName := range names { - plugin, err := ps.InitPlugin(pluginName, pluginInitializer) - if err != nil { - return nil, err - } - plugins = append(plugins, plugin) - } - return PluginChain(plugins), nil -} - -func (ps *Plugins) getPlugin(name string) (Interface, bool, error) { - f, ok := ps.pluginFactory[name] - if !ok { - return nil, false, nil - } - - plugin, err := f() - return plugin, true, err -} - -func (ps *Plugins) InitPlugin(name string, pluginInitializer PluginInitializer) (Interface, error) { - plugin, found, err := ps.getPlugin(name) - if err != nil { - return nil, err - } - if !found { - return nil, fmt.Errorf("No plugin named %q registered", name) - } - - pluginInitializer.Initialize(plugin) - if err := ValidateInitialization(plugin); err != nil { - return nil, err - } - - return plugin, nil -} - -// ValidateInitialization will call the InitializationValidate function in each plugin if they implement -// the InitializationValidator interface. -func ValidateInitialization(plugin Interface) error { - if validater, ok := plugin.(InitializationValidator); ok { - err := validater.ValidateInitialization() - if err != nil { - return err - } - } - return nil -} diff --git a/pkg/webhook/admission/plugins_test.go b/pkg/webhook/admission/plugins_test.go deleted file mode 100644 index 062b37a4fac..00000000000 --- a/pkg/webhook/admission/plugins_test.go +++ /dev/null @@ -1,146 +0,0 @@ -/* -Copyright 2021 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. -*/ - -package admission_test - -import ( - "fmt" - "testing" - - admissionv1 "k8s.io/api/admission/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/client-go/kubernetes" - "k8s.io/client-go/kubernetes/fake" - - "github.com/cert-manager/cert-manager/pkg/webhook/admission" - "github.com/cert-manager/cert-manager/pkg/webhook/admission/initializer" -) - -func TestPlugins_InitializesNamedOnly(t *testing.T) { - scheme := runtime.NewScheme() - p := admission.NewPlugins(scheme) - - testPlugin1 := &testPlugin{} - p.Register("TestPlugin1", func() (admission.Interface, error) { - return testPlugin1, nil - }) - - testPlugin2 := &testPlugin{ - initErr: fmt.Errorf("failed init"), - } - p.Register("TestPlugin2", func() (admission.Interface, error) { - return testPlugin2, nil - }) - - // only initialize TestPlugin1 - _, err := p.NewFromPlugins([]string{"TestPlugin1"}, initializer.New(fake.NewSimpleClientset(), nil, nil, nil)) - if err != nil { - t.Errorf("got unexpected error: %v", err) - } - if testPlugin1.kc == nil { - t.Errorf("expected TestPlugin1 to be initialized") - } - if testPlugin2.kc != nil { - t.Errorf("expected TestPlugin2 to not be initialized") - } -} - -func TestPlugins_FailsIfAnyPluginFails(t *testing.T) { - scheme := runtime.NewScheme() - p := admission.NewPlugins(scheme) - - testPlugin1 := &testPlugin{} - p.Register("TestPlugin1", func() (admission.Interface, error) { - return testPlugin1, nil - }) - - testPlugin2 := &testPlugin{ - initErr: fmt.Errorf("failed init"), - } - p.Register("TestPlugin2", func() (admission.Interface, error) { - return testPlugin2, nil - }) - - // only initialize TestPlugin1 - _, err := p.NewFromPlugins([]string{"TestPlugin1", "TestPlugin2"}, initializer.New(fake.NewSimpleClientset(), nil, nil, nil)) - if err == nil { - t.Errorf("expected an error but got none") - } - if testPlugin1.kc == nil { - t.Errorf("expected TestPlugin1 to be initialized") - } - if testPlugin2.kc == nil { - t.Errorf("expected TestPlugin2 to be initialized") - } -} - -func TestPlugins_FailsNonExistingPlugin(t *testing.T) { - scheme := runtime.NewScheme() - p := admission.NewPlugins(scheme) - - testPlugin1 := &testPlugin{} - p.Register("TestPlugin1", func() (admission.Interface, error) { - return testPlugin1, nil - }) - - // only initialize TestPlugin1 - _, err := p.NewFromPlugins([]string{"TestPlugin1", "TestPluginDoesNotExist"}, initializer.New(fake.NewSimpleClientset(), nil, nil, nil)) - if err == nil { - t.Errorf("expected an error but got none") - } - if testPlugin1.kc == nil { - t.Errorf("expected TestPlugin1 to be initialized") - } -} - -func TestPlugins_FailsIfPluginFailsToBuild(t *testing.T) { - scheme := runtime.NewScheme() - p := admission.NewPlugins(scheme) - - testPlugin1 := &testPlugin{} - p.Register("TestPlugin1", func() (admission.Interface, error) { - return testPlugin1, fmt.Errorf("an early error occurred") - }) - - // only initialize TestPlugin1 - _, err := p.NewFromPlugins([]string{"TestPlugin1"}, initializer.New(fake.NewSimpleClientset(), nil, nil, nil)) - if err == nil { - t.Errorf("expected an error but got none") - } - if testPlugin1.kc != nil { - t.Errorf("expected TestPlugin1 to not be initialized") - } -} - -type testPlugin struct { - kc kubernetes.Interface - initErr error -} - -var _ admission.Interface = &testPlugin{} -var _ initializer.WantsExternalKubeClientSet = &testPlugin{} - -func (t *testPlugin) Handles(_ admissionv1.Operation) bool { - return true -} - -func (t *testPlugin) SetExternalKubeClientSet(k kubernetes.Interface) { - t.kc = k -} - -func (t *testPlugin) ValidateInitialization() error { - return t.initErr -} diff --git a/pkg/webhook/admission/request_handler.go b/pkg/webhook/admission/request_handler.go deleted file mode 100644 index 3d6c65b402c..00000000000 --- a/pkg/webhook/admission/request_handler.go +++ /dev/null @@ -1,274 +0,0 @@ -/* -Copyright 2021 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. -*/ - -package admission - -import ( - "bytes" - "context" - "encoding/json" - "fmt" - "net/http" - "sort" - - "gomodules.xyz/jsonpatch/v2" - admissionv1 "k8s.io/api/admission/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/apimachinery/pkg/runtime/serializer" - apijson "k8s.io/apimachinery/pkg/runtime/serializer/json" - - "github.com/cert-manager/cert-manager/pkg/webhook/handlers" -) - -// RequestHandler is an implementation of the webhook's request handling that -// invokes a validating and/or mutating admission plugin (or chain of plugins). -// -// All runtime.Objects passed to the mutation and validation handlers will be in -// their internal versions to make handling multiple API versions easier. -// -// During mutation, objects will be decoded using the scheme provided during the -// NewRequestHandler call. This scheme will also be used to invoke defaulting functions -// when the object is decoded. -// This means that all resources passed to mutating admission plugins will have default -// values applied before converting them into the internal version. -type RequestHandler struct { - scheme *runtime.Scheme - - // codecFactory used to create encoders and decoders - codecFactory serializer.CodecFactory - - // serializer used to write resources as JSON after mutation to determine - // the final jsonpatch for resources - serializer *apijson.Serializer - - // decoder used to decode & convert resources in AdmissionRequests into - // their internal versions - decoder runtime.Decoder - - validator ValidationInterface - mutator MutationInterface -} - -// NewRequestHandler will construct a new request handler using the given scheme for -// conversion & defaulting. Either validator or mutator can be nil, and if so no -// action will be taken. -func NewRequestHandler(scheme *runtime.Scheme, validator ValidationInterface, mutator MutationInterface) *RequestHandler { - cf := serializer.NewCodecFactory(scheme) - return &RequestHandler{ - scheme: scheme, - codecFactory: cf, - serializer: apijson.NewSerializerWithOptions(apijson.DefaultMetaFactory, scheme, scheme, apijson.SerializerOptions{}), - decoder: cf.UniversalDecoder(), - validator: validator, - mutator: mutator, - } -} - -var _ handlers.ValidatingAdmissionHook = &RequestHandler{} -var _ handlers.MutatingAdmissionHook = &RequestHandler{} - -// Validate will decode the Object (and OldObject, if set) in the AdmissionRequest into the -// internal API version. -// It will then invoke the validation handler to build a list of warning messages and any -// errors generated during the admission chain. -func (rh *RequestHandler) Validate(ctx context.Context, admissionSpec *admissionv1.AdmissionRequest) *admissionv1.AdmissionResponse { - status := &admissionv1.AdmissionResponse{} - status.UID = admissionSpec.UID - // short-path if there is no validator actually registered or the handler does not handle this operation. - if rh.validator == nil || !rh.validator.Handles(admissionSpec.Operation) { - status.Allowed = true - return status - } - - // decode new version of object - obj, err := rh.deseralizeToInternalVersion(admissionSpec.Object.Raw) - if err != nil { - return badRequestError(status, err) - } - - // attempt to decode old object - var oldObj runtime.Object - if len(admissionSpec.OldObject.Raw) > 0 { - oldObj, err = rh.deseralizeToInternalVersion(admissionSpec.OldObject.Raw) - if err != nil { - return badRequestError(status, err) - } - } - - warnings, err := rh.validator.Validate(ctx, *admissionSpec, oldObj, obj) - status.Warnings = warnings - - // return with allowed = false if any errors occurred - if err != nil { - status.Allowed = false - status.Result = &metav1.Status{ - Status: metav1.StatusFailure, Code: http.StatusNotAcceptable, Reason: metav1.StatusReasonNotAcceptable, - Message: err.Error(), - } - return status - } - status.Allowed = true - return status -} - -func (rh *RequestHandler) Mutate(ctx context.Context, admissionSpec *admissionv1.AdmissionRequest) *admissionv1.AdmissionResponse { - status := &admissionv1.AdmissionResponse{} - status.UID = admissionSpec.UID - status.Allowed = true - // short-path if there is no mutator actually registered - // we still continue if the mutator does not handle the resource so scheme-registered - // defaulting functions are still run against the object. - if rh.mutator == nil { - status.Allowed = true - return status - } - - // If the resource submitted to the webhook is in a different version to the request version, - // we must take special steps to ensure the correct defaults are applied to the resource (as - // defaults are applied by the decoder when the resource is decoded in the version of the - // encoded resource). - obj, errResponse := rh.decodeRequestObject(status, admissionSpec.Kind, *admissionSpec.RequestKind, admissionSpec.Object.Raw) - if errResponse != nil { - return errResponse - } - - if rh.mutator.Handles(admissionSpec.Operation) { - if err := rh.mutator.Mutate(ctx, *admissionSpec, obj); err != nil { - return internalServerError(status, err) - } - } - - // Convert the object into the original version that was submitted to the webhook - // before generating the patch. - outputGroupVersioner := runtime.NewMultiGroupVersioner(schema.GroupVersion{Group: admissionSpec.Kind.Group, Version: admissionSpec.Kind.Version}) - finalObject, err := rh.scheme.ConvertToVersion(obj, outputGroupVersioner) - if err != nil { - return internalServerError(status, err) - } - - patch, err := rh.createMutatePatch(admissionSpec, finalObject) - if err != nil { - return internalServerError(status, err) - } - - patchType := admissionv1.PatchTypeJSONPatch - status.PatchType = &patchType - status.Patch = patch - - return status -} - -// decodeRequestObject will decode the given 'bytes' into the internal API version. -// It will apply defaults using the 'defaultsInGVK', regardless of what API version -// the encoded bytes are in. -func (rh *RequestHandler) decodeRequestObject(status *admissionv1.AdmissionResponse, objectGVK, defaultInGVK metav1.GroupVersionKind, bytes []byte) (runtime.Object, *admissionv1.AdmissionResponse) { - if objectGVK == defaultInGVK { - obj, _, err := rh.decoder.Decode(bytes, nil, nil) - if err != nil { - return nil, badRequestError(status, err) - } - return obj, nil - } - - // Decode the object to the internal version without defaulting - internalObj, err := rh.deseralizeToInternalVersion(bytes) - if err != nil { - return nil, badRequestError(status, err) - } - - // Now convert into the request version so we can apply the appropriate defaults - requestGroupVersioner := runtime.NewMultiGroupVersioner(schema.GroupVersion{Group: defaultInGVK.Group, Version: defaultInGVK.Version}) - requestObj, err := rh.scheme.ConvertToVersion(internalObj, requestGroupVersioner) - if err != nil { - return nil, internalServerError(status, err) - } - - // At last, apply defaults in the request API version - rh.scheme.Default(requestObj) - - // Finally, convert the resource back to the internal version so regular admission can proceed - obj, err := rh.scheme.ConvertToVersion(requestObj, runtime.InternalGroupVersioner) - if err != nil { - return nil, internalServerError(status, err) - } - - return obj, nil -} - -// deseralizeToInternalVersion will decode an object into its internal version -// without applying default values. -func (rh *RequestHandler) deseralizeToInternalVersion(bytes []byte) (runtime.Object, error) { - // First, use the UniversalDeserializer to decode the bytes (which does not perform - // conversion or defaulting). - obj, _, err := rh.codecFactory.UniversalDeserializer().Decode(bytes, nil, nil) - if err != nil { - return nil, err - } - - // Then convert to the internal version - return rh.scheme.ConvertToVersion(obj, runtime.InternalGroupVersioner) -} - -func badRequestError(status *admissionv1.AdmissionResponse, err error) *admissionv1.AdmissionResponse { - status.Allowed = false - status.Result = &metav1.Status{ - Status: metav1.StatusFailure, Code: http.StatusBadRequest, Reason: metav1.StatusReasonBadRequest, - Message: err.Error(), - } - return status -} - -func internalServerError(status *admissionv1.AdmissionResponse, err error) *admissionv1.AdmissionResponse { - status.Allowed = false - status.Result = &metav1.Status{ - Status: metav1.StatusFailure, Code: http.StatusInternalServerError, Reason: metav1.StatusReasonInternalError, - Message: err.Error(), - } - return status -} - -// createMutatePatch will generate a JSON patch based upon the given original -// raw object, and the mutated typed object. -func (rh *RequestHandler) createMutatePatch(req *admissionv1.AdmissionRequest, obj runtime.Object) ([]byte, error) { - var buf bytes.Buffer - - encoder := rh.codecFactory.EncoderForVersion(rh.serializer, schema.GroupVersion{Group: req.Kind.Group, Version: req.Kind.Version}) - if err := encoder.Encode(obj, &buf); err != nil { - return nil, fmt.Errorf("failed to encode object after mutation: %s", err) - } - - ops, err := jsonpatch.CreatePatch(req.Object.Raw, buf.Bytes()) - if err != nil { - return nil, fmt.Errorf("failed to set mutation patch: %s", err) - } - - sortOps(ops) - - patch, err := json.Marshal(ops) - if err != nil { - return nil, fmt.Errorf("failed to generate json patch: %s", err) - } - - return patch, nil -} - -func sortOps(ops []jsonpatch.JsonPatchOperation) { - sort.Slice(ops, func(i, j int) bool { - return ops[i].Path < ops[j].Path - }) -} diff --git a/pkg/webhook/admission/request_handler_test.go b/pkg/webhook/admission/request_handler_test.go deleted file mode 100644 index adf8d8f7350..00000000000 --- a/pkg/webhook/admission/request_handler_test.go +++ /dev/null @@ -1,321 +0,0 @@ -/* -Copyright 2021 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. -*/ - -package admission_test - -import ( - "context" - "encoding/json" - "fmt" - "net/http" - "reflect" - "testing" - - "gomodules.xyz/jsonpatch/v2" - admissionv1 "k8s.io/api/admission/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/types" - "k8s.io/utils/diff" - - "github.com/cert-manager/cert-manager/pkg/webhook/admission" - "github.com/cert-manager/cert-manager/pkg/webhook/handlers/testdata/apis/testgroup" - "github.com/cert-manager/cert-manager/pkg/webhook/handlers/testdata/apis/testgroup/install" -) - -var ( - jsonPatchType = admissionv1.PatchTypeJSONPatch -) - -// Tests to ensure that the RequestHandler applies scheme-registered -// defaults when mutating objects. -func TestRequestHandler_MutateAppliesDefaultValues(t *testing.T) { - scheme := runtime.NewScheme() - install.Install(scheme) - - rh := admission.NewRequestHandler(scheme, nil, testMutator{ - handles: true, - mutate: func(_ context.Context, _ admissionv1.AdmissionRequest, obj runtime.Object) error { - obj.(*testgroup.TestType).TestField = "some-value" - return nil - }, - }) - inputRequest := admissionv1.AdmissionRequest{ - UID: types.UID("abc"), - Operation: admissionv1.Create, - Kind: metav1.GroupVersionKind{ - Group: "testgroup.testing.cert-manager.io", - Version: "v1", - Kind: "TestType", - }, - RequestKind: &metav1.GroupVersionKind{ - Group: "testgroup.testing.cert-manager.io", - Version: "v1", - Kind: "TestType", - }, - Object: runtime.RawExtension{ - Raw: []byte(` -{ - "apiVersion": "testgroup.testing.cert-manager.io/v1", - "kind": "TestType", - "metadata": { - "name": "testing", - "namespace": "abc", - "creationTimestamp": null - }, - "testFieldImmutable": "abc", - "testDefaultingField": "set-to-something" -} -`), - }, - } - expectedResponse := admissionv1.AdmissionResponse{ - UID: types.UID("abc"), - Allowed: true, - Patch: responseForOperations( - jsonpatch.JsonPatchOperation{ - Operation: "add", - Path: "/testField", - Value: "some-value", - }, - jsonpatch.JsonPatchOperation{ - Operation: "add", - Path: "/testFieldPtr", - Value: "teststr", - }, - ), - PatchType: &jsonPatchType, - } - - resp := rh.Mutate(context.TODO(), &inputRequest) - if !reflect.DeepEqual(&expectedResponse, resp) { - t.Errorf("Response was not as expected: %v", diff.ObjectGoPrintSideBySide(&expectedResponse, resp)) - } -} - -func TestRequestHandler_MutateAppliesDefaultsInRequestVersion(t *testing.T) { - scheme := runtime.NewScheme() - install.Install(scheme) - - rh := admission.NewRequestHandler(scheme, nil, testMutator{ - handles: true, - mutate: func(_ context.Context, _ admissionv1.AdmissionRequest, obj runtime.Object) error { - // Doesn't do anything as the request handler itself will generate patches to apply - // defaults instead of it being applied within a particular admission plugin. - return nil - }, - }) - inputRequest := admissionv1.AdmissionRequest{ - UID: types.UID("abc"), - Operation: admissionv1.Create, - Kind: metav1.GroupVersionKind{ - Group: "testgroup.testing.cert-manager.io", - Version: "v1", - Kind: "TestType", - }, - RequestKind: &metav1.GroupVersionKind{ - Group: "testgroup.testing.cert-manager.io", - // Because the API version is v2, we expect the `testDefaultingField` field to be set to `set-in-v2`. - // In v1, the field will be set to `set-in-v1`. - Version: "v2", - Kind: "TestType", - }, - Object: runtime.RawExtension{ - Raw: []byte(` -{ - "apiVersion": "testgroup.testing.cert-manager.io/v1", - "kind": "TestType", - "metadata": { - "name": "testing", - "namespace": "abc", - "creationTimestamp": null - }, - "testField": "set-to-something-to-avoid-extra-mutations", - "testFieldImmutable": "set-to-something-to-avoid-extra-mutations", - "testFieldPtr": "set-to-something-to-avoid-extra-mutations" -} -`), - }, - } - expectedResponse := admissionv1.AdmissionResponse{ - UID: types.UID("abc"), - Allowed: true, - Patch: responseForOperations( - jsonpatch.JsonPatchOperation{ - Operation: "add", - Path: "/testDefaultingField", - Value: "set-in-v2", - }, - ), - PatchType: &jsonPatchType, - } - - resp := rh.Mutate(context.TODO(), &inputRequest) - if !reflect.DeepEqual(&expectedResponse, resp) { - t.Errorf("Response was not as expected: %v", diff.ObjectGoPrintSideBySide(&expectedResponse, resp)) - } -} - -// Tests to ensure that the RequestHandler skips running mutation handlers -// that do not return true to Handles, but still applies scheme based defaulting. -func TestRequestHandler_MutateSkipsMutation(t *testing.T) { - scheme := runtime.NewScheme() - install.Install(scheme) - - rh := admission.NewRequestHandler(scheme, nil, testMutator{ - handles: false, - }) - inputRequest := admissionv1.AdmissionRequest{ - UID: types.UID("abc"), - Operation: admissionv1.Create, - Kind: metav1.GroupVersionKind{ - Group: "testgroup.testing.cert-manager.io", - Version: "v1", - Kind: "TestType", - }, - RequestKind: &metav1.GroupVersionKind{ - Group: "testgroup.testing.cert-manager.io", - Version: "v1", - Kind: "TestType", - }, - Object: runtime.RawExtension{ - Raw: []byte(` -{ - "apiVersion": "testgroup.testing.cert-manager.io/v1", - "kind": "TestType", - "metadata": { - "name": "testing", - "namespace": "abc", - "creationTimestamp": null - }, - "testField": "some-value", - "testFieldImmutable": "abc", - "testDefaultingField": "set-to-something" -} -`), - }, - } - expectedResponse := admissionv1.AdmissionResponse{ - UID: types.UID("abc"), - Allowed: true, - Patch: responseForOperations( - jsonpatch.JsonPatchOperation{ - Operation: "add", - Path: "/testFieldPtr", - Value: "teststr", - }, - ), - PatchType: &jsonPatchType, - } - - resp := rh.Mutate(context.TODO(), &inputRequest) - if !reflect.DeepEqual(&expectedResponse, resp) { - t.Errorf("Response was not as expected: %v", diff.ObjectGoPrintSideBySide(&expectedResponse, resp)) - } -} - -func TestRequestHandler_ValidateReturnsErrorsAndWarnings(t *testing.T) { - scheme := runtime.NewScheme() - install.Install(scheme) - - rh := admission.NewRequestHandler(scheme, testValidator{ - handles: true, - warnings: []string{"a warning"}, - err: fmt.Errorf("some synthetic error"), - }, nil) - inputRequest := admissionv1.AdmissionRequest{ - UID: types.UID("abc"), - Operation: admissionv1.Create, - Kind: metav1.GroupVersionKind{ - Group: "testgroup.testing.cert-manager.io", - Version: "v1", - Kind: "TestType", - }, - RequestKind: &metav1.GroupVersionKind{ - Group: "testgroup.testing.cert-manager.io", - Version: "v1", - Kind: "TestType", - }, - Object: runtime.RawExtension{ - Raw: []byte(` -{ - "apiVersion": "testgroup.testing.cert-manager.io/v1", - "kind": "TestType", - "metadata": { - "name": "testing", - "namespace": "abc" - } -} -`), - }, - } - expectedResponse := admissionv1.AdmissionResponse{ - UID: types.UID("abc"), - Allowed: false, - Result: &metav1.Status{ - Status: metav1.StatusFailure, - Message: "some synthetic error", - Reason: metav1.StatusReasonNotAcceptable, - Code: http.StatusNotAcceptable, - }, - Warnings: []string{"a warning"}, - } - - resp := rh.Validate(context.TODO(), &inputRequest) - if !reflect.DeepEqual(&expectedResponse, resp) { - t.Errorf("Response was not as expected: %v", diff.ObjectGoPrintSideBySide(&expectedResponse, resp)) - } -} - -func responseForOperations(ops ...jsonpatch.JsonPatchOperation) []byte { - b, err := json.Marshal(ops) - if err != nil { - // this shouldn't ever be reached - panic("failed to encode JSON test data") - } - return b -} - -type testValidator struct { - handles bool - warnings []string - err error -} - -var _ admission.ValidationInterface = testValidator{} - -func (t testValidator) Handles(operation admissionv1.Operation) bool { - return t.handles -} - -func (t testValidator) Validate(ctx context.Context, request admissionv1.AdmissionRequest, oldObj, obj runtime.Object) (warnings []string, err error) { - return t.warnings, t.err -} - -type testMutator struct { - handles bool - mutate func(_ context.Context, _ admissionv1.AdmissionRequest, obj runtime.Object) error -} - -var _ admission.MutationInterface = testMutator{} - -func (t testMutator) Handles(_ admissionv1.Operation) bool { - return t.handles -} - -func (t testMutator) Mutate(ctx context.Context, req admissionv1.AdmissionRequest, obj runtime.Object) error { - return t.mutate(ctx, req, obj) -} diff --git a/pkg/webhook/admission/util_test.go b/pkg/webhook/admission/util_test.go index 62393453140..fee9e59e1c5 100644 --- a/pkg/webhook/admission/util_test.go +++ b/pkg/webhook/admission/util_test.go @@ -20,6 +20,7 @@ import ( "context" admissionv1 "k8s.io/api/admission/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" "github.com/cert-manager/cert-manager/pkg/webhook/admission" @@ -48,14 +49,14 @@ var _ admission.ValidationInterface = &validatingImplementation{} type mutatingImplementation struct { handles func(admissionv1.Operation) bool - mutate func(ctx context.Context, request admissionv1.AdmissionRequest, obj runtime.Object) error + mutate func(ctx context.Context, request admissionv1.AdmissionRequest, obj *unstructured.Unstructured) error } func (v mutatingImplementation) Handles(operation admissionv1.Operation) bool { return v.handles(operation) } -func (v mutatingImplementation) Mutate(ctx context.Context, request admissionv1.AdmissionRequest, obj runtime.Object) error { +func (v mutatingImplementation) Mutate(ctx context.Context, request admissionv1.AdmissionRequest, obj *unstructured.Unstructured) error { return v.mutate(ctx, request, obj) } diff --git a/pkg/webhook/authority/authority_test.go b/pkg/webhook/authority/authority_test.go deleted file mode 100644 index 17e4321c942..00000000000 --- a/pkg/webhook/authority/authority_test.go +++ /dev/null @@ -1,19 +0,0 @@ -/* -Copyright 2020 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. -*/ - -package authority - -// Integration tests for the authority can be found in `test/integration/webhook/dynamic_authority_test.go`. diff --git a/pkg/webhook/configfile/configfile.go b/pkg/webhook/configfile/configfile.go index 102a88ba7d6..7f8ff3b7ad6 100644 --- a/pkg/webhook/configfile/configfile.go +++ b/pkg/webhook/configfile/configfile.go @@ -18,8 +18,6 @@ package configfile import ( "fmt" - "io/ioutil" - "path/filepath" "k8s.io/apimachinery/pkg/runtime/serializer" @@ -27,98 +25,64 @@ import ( "github.com/cert-manager/cert-manager/internal/apis/config/webhook/scheme" ) -// Filesystem is an interface used to mock out calls to ReadFile -type Filesystem interface { - ReadFile(filename string) ([]byte, error) +type WebhookConfigFile struct { + Config *config.WebhookConfiguration } -type realFS struct{} - -func (fs realFS) ReadFile(filename string) ([]byte, error) { - return ioutil.ReadFile(filename) -} - -// NewRealFS builds a Filesystem that wraps around `ioutil.ReadFile`. -func NewRealFS() Filesystem { - return realFS{} -} - -type Loader interface { - Load() (*config.WebhookConfiguration, error) -} - -type fsLoader struct { - fs Filesystem - filename string - codec *serializer.CodecFactory +func New() *WebhookConfigFile { + return &WebhookConfigFile{ + Config: &config.WebhookConfiguration{}, + } } -var _ Loader = &fsLoader{} - -func (f *fsLoader) Load() (*config.WebhookConfiguration, error) { - data, err := f.fs.ReadFile(f.filename) +func decodeConfiguration(data []byte) (*config.WebhookConfiguration, error) { + _, codec, err := scheme.NewSchemeAndCodecs(serializer.EnableStrict) if err != nil { - return nil, fmt.Errorf("failed to read webhook config file %q, error: %v", f.filename, err) + return nil, err } - if len(data) == 0 { - return nil, fmt.Errorf("webhook config file %q was empty", f.filename) + obj, _, err := codec.UniversalDecoder().Decode(data, nil, nil) + if err != nil { + return nil, fmt.Errorf("failed to decode: %w", err) } - cfg, err := decodeWebhookConfiguration(f.codec, data) - if err != nil { - return nil, err + c, ok := obj.(*config.WebhookConfiguration) + if !ok { + return nil, fmt.Errorf("failed to cast object to WebhookConfiguration, unexpected type") } - // make all paths absolute - resolveRelativePaths(webhookConfigurationPathRefs(cfg), filepath.Dir(f.filename)) - return cfg, nil + return c, nil + } -func NewFSLoader(fs Filesystem, name string) (Loader, error) { - _, webhookCodec, err := scheme.NewSchemeAndCodecs(serializer.EnableStrict) +func (cfg *WebhookConfigFile) DecodeAndConfigure(data []byte) error { + config, err := decodeConfiguration(data) if err != nil { - return nil, err + return err } + cfg.Config = config - return &fsLoader{ - fs: fs, - filename: name, - codec: webhookCodec, - }, nil -} - -func resolveRelativePaths(paths []*string, root string) { - for _, path := range paths { - // leave empty paths alone, "no path" is a valid input - // do not attempt to resolve paths that are already absolute - if len(*path) > 0 && !filepath.IsAbs(*path) { - *path = filepath.Join(root, *path) - } - } + return nil } -func decodeWebhookConfiguration(codec *serializer.CodecFactory, data []byte) (*config.WebhookConfiguration, error) { - obj, gvk, err := codec.UniversalDecoder().Decode(data, nil, nil) +func (cfg *WebhookConfigFile) GetPathRefs() ([]*string, error) { + paths, err := WebhookConfigurationPathRefs(cfg.Config) if err != nil { - return nil, fmt.Errorf("failed to decode: %w", err) - } - - internalObj, ok := obj.(*config.WebhookConfiguration) - if !ok { - return nil, fmt.Errorf("failed to cast object to WebhookConfiguration, unexpected type: %v", gvk) + return nil, err } + return paths, nil - return internalObj, nil } -// webhookConfigurationPathRefs returns pointers to all the WebhookConfiguration fields that contain filepaths. +// WebhookConfigurationPathRefs returns pointers to all the WebhookConfiguration fields that contain filepaths. // You might use this, for example, to resolve all relative paths against some common root before // passing the configuration to the application. This method must be kept up to date as new fields are added. -func webhookConfigurationPathRefs(cfg *config.WebhookConfiguration) []*string { +func WebhookConfigurationPathRefs(cfg *config.WebhookConfiguration) ([]*string, error) { return []*string{ &cfg.TLSConfig.Filesystem.KeyFile, &cfg.TLSConfig.Filesystem.CertFile, + &cfg.MetricsTLSConfig.Filesystem.KeyFile, + &cfg.MetricsTLSConfig.Filesystem.CertFile, &cfg.KubeConfig, - } + }, nil } diff --git a/pkg/webhook/handlers/conversion.go b/pkg/webhook/handlers/conversion.go deleted file mode 100644 index 8b8c4f83f3d..00000000000 --- a/pkg/webhook/handlers/conversion.go +++ /dev/null @@ -1,103 +0,0 @@ -/* -Copyright 2020 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. -*/ - -package handlers - -import ( - "bytes" - "fmt" - "net/http" - - apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" - - "github.com/go-logr/logr" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/runtime/schema" - apijson "k8s.io/apimachinery/pkg/runtime/serializer/json" - "k8s.io/apimachinery/pkg/runtime/serializer/versioning" - - logf "github.com/cert-manager/cert-manager/pkg/logs" -) - -type SchemeBackedConverter struct { - log logr.Logger - scheme *runtime.Scheme - serializer *apijson.Serializer -} - -var _ ConversionHook = &SchemeBackedConverter{} - -func NewSchemeBackedConverter(log logr.Logger, scheme *runtime.Scheme) *SchemeBackedConverter { - serializer := apijson.NewSerializerWithOptions(apijson.DefaultMetaFactory, scheme, scheme, apijson.SerializerOptions{}) - return &SchemeBackedConverter{ - log: log, - scheme: scheme, - serializer: serializer, - } -} - -func (c *SchemeBackedConverter) convertObjects(desiredAPIVersion string, objects []runtime.RawExtension) ([]runtime.RawExtension, error) { - desiredGV, err := schema.ParseGroupVersion(desiredAPIVersion) - if err != nil { - return nil, fmt.Errorf("Failed to parse desired apiVersion: %v", err) - } - - c.log.V(logf.DebugLevel).Info("Parsed desired groupVersion", "desired_group_version", desiredGV) - - groupVersioner := schema.GroupVersions([]schema.GroupVersion{desiredGV}) - codec := versioning.NewCodec( - c.serializer, - c.serializer, - runtime.UnsafeObjectConvertor(c.scheme), - c.scheme, - c.scheme, - nil, - groupVersioner, - runtime.InternalGroupVersioner, c.scheme.Name(), - ) - - convertedObjects := make([]runtime.RawExtension, len(objects)) - for i, raw := range objects { - decodedObject, currentGVK, err := codec.Decode(raw.Raw, nil, nil) - if err != nil { - return nil, fmt.Errorf("Failed to decode into apiVersion: %v", err) - } - c.log.V(logf.DebugLevel).Info("Decoded resource", "decoded_group_version_kind", currentGVK) - buf := bytes.Buffer{} - if err := codec.Encode(decodedObject, &buf); err != nil { - return nil, fmt.Errorf("Failed to convert to desired apiVersion: %v", err) - } - convertedObjects[i] = runtime.RawExtension{Raw: buf.Bytes()} - } - return convertedObjects, nil -} - -func (c *SchemeBackedConverter) Convert(conversionSpec *apiextensionsv1.ConversionRequest) *apiextensionsv1.ConversionResponse { - result := metav1.Status{Status: metav1.StatusSuccess} - convertedObjects, err := c.convertObjects(conversionSpec.DesiredAPIVersion, conversionSpec.Objects) - if err != nil { - result.Status = metav1.StatusFailure - result.Code = http.StatusBadRequest - result.Reason = metav1.StatusReasonBadRequest - result.Message = err.Error() - } - return &apiextensionsv1.ConversionResponse{ - UID: conversionSpec.UID, - ConvertedObjects: convertedObjects, - Result: result, - } -} diff --git a/pkg/webhook/handlers/conversion_test.go b/pkg/webhook/handlers/conversion_test.go deleted file mode 100644 index fd8f3277b1d..00000000000 --- a/pkg/webhook/handlers/conversion_test.go +++ /dev/null @@ -1,226 +0,0 @@ -/* -Copyright 2020 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. -*/ - -package handlers - -import ( - "reflect" - "testing" - - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/types" - "k8s.io/klog/v2/klogr" - "k8s.io/utils/diff" - - "github.com/cert-manager/cert-manager/pkg/webhook/handlers/testdata/apis/testgroup" - "github.com/cert-manager/cert-manager/pkg/webhook/handlers/testdata/apis/testgroup/install" - apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" -) - -func TestConvertTestType(t *testing.T) { - scheme := runtime.NewScheme() - install.Install(scheme) - - log := klogr.New() - c := NewSchemeBackedConverter(log, scheme) - - type conversionTestT struct { - inputRequest apiextensionsv1.ConversionRequest - expectedResponse apiextensionsv1.ConversionResponse - } - - tests := map[string]conversionTestT{ - "correctly handles requests with multiple input items": { - inputRequest: apiextensionsv1.ConversionRequest{ - DesiredAPIVersion: testgroup.GroupName + "/v1", - Objects: []runtime.RawExtension{ - { - Raw: []byte(` -{ - "apiVersion": "testgroup.testing.cert-manager.io/v1", - "kind": "TestType", - "metadata": { - "name": "testing", - "namespace": "abc", - "creationTimestamp": null - } -} -`), - }, - { - Raw: []byte(` -{ - "apiVersion": "testgroup.testing.cert-manager.io/v1", - "kind": "TestType", - "metadata": { - "name": "testing", - "namespace": "abc", - "creationTimestamp": null - } -} -`), - }, - }, - }, - expectedResponse: apiextensionsv1.ConversionResponse{ - Result: metav1.Status{ - Status: metav1.StatusSuccess, - }, - ConvertedObjects: []runtime.RawExtension{ - { - Raw: []byte(`{"kind":"TestType","apiVersion":"testgroup.testing.cert-manager.io/v1","metadata":{"name":"testing","namespace":"abc","creationTimestamp":null},"testField":"","testFieldImmutable":""} -`), - }, - { - Raw: []byte(`{"kind":"TestType","apiVersion":"testgroup.testing.cert-manager.io/v1","metadata":{"name":"testing","namespace":"abc","creationTimestamp":null},"testField":"","testFieldImmutable":""} -`), - }, - }, - }, - }, - "succeeds when handling requests with no input items": { - inputRequest: apiextensionsv1.ConversionRequest{ - DesiredAPIVersion: testgroup.GroupName + "/v1", - Objects: []runtime.RawExtension{}, - }, - expectedResponse: apiextensionsv1.ConversionResponse{ - Result: metav1.Status{ - Status: metav1.StatusSuccess, - }, - ConvertedObjects: []runtime.RawExtension{}, - }, - }, - "copies across request UID to the response field": { - inputRequest: apiextensionsv1.ConversionRequest{ - DesiredAPIVersion: testgroup.GroupName + "/v1", - Objects: []runtime.RawExtension{}, - UID: types.UID("abc"), - }, - expectedResponse: apiextensionsv1.ConversionResponse{ - Result: metav1.Status{ - Status: metav1.StatusSuccess, - }, - UID: types.UID("abc"), - ConvertedObjects: []runtime.RawExtension{}, - }, - }, - "converts from v1 to v1 without applying defaults": { - inputRequest: apiextensionsv1.ConversionRequest{ - DesiredAPIVersion: testgroup.GroupName + "/v1", - Objects: []runtime.RawExtension{ - { - Raw: []byte(` -{ - "apiVersion": "testgroup.testing.cert-manager.io/v1", - "kind": "TestType", - "metadata": { - "name": "testing", - "namespace": "abc", - "creationTimestamp": null - } -} -`), - }, - }, - }, - expectedResponse: apiextensionsv1.ConversionResponse{ - Result: metav1.Status{ - Status: metav1.StatusSuccess, - }, - ConvertedObjects: []runtime.RawExtension{ - { - Raw: []byte(`{"kind":"TestType","apiVersion":"testgroup.testing.cert-manager.io/v1","metadata":{"name":"testing","namespace":"abc","creationTimestamp":null},"testField":"","testFieldImmutable":""} -`), - }, - }, - }, - }, - "converts from v1 to v2 without applying defaults": { - inputRequest: apiextensionsv1.ConversionRequest{ - DesiredAPIVersion: testgroup.GroupName + "/v2", - Objects: []runtime.RawExtension{ - { - Raw: []byte(` -{ - "apiVersion": "testgroup.testing.cert-manager.io/v1", - "kind": "TestType", - "metadata": { - "name": "testing", - "namespace": "abc", - "creationTimestamp": null - } -} -`), - }, - }, - }, - expectedResponse: apiextensionsv1.ConversionResponse{ - Result: metav1.Status{ - Status: metav1.StatusSuccess, - }, - ConvertedObjects: []runtime.RawExtension{ - { - Raw: []byte(`{"kind":"TestType","apiVersion":"testgroup.testing.cert-manager.io/v2","metadata":{"name":"testing","namespace":"abc","creationTimestamp":null},"testField":"","testFieldImmutable":""} -`), - }, - }, - }, - }, - "converts from v1 to v2": { - inputRequest: apiextensionsv1.ConversionRequest{ - DesiredAPIVersion: testgroup.GroupName + "/v2", - Objects: []runtime.RawExtension{ - { - Raw: []byte(` -{ - "apiVersion": "testgroup.testing.cert-manager.io/v1", - "kind": "TestType", - "metadata": { - "name": "testing", - "namespace": "abc", - "creationTimestamp": null - }, - "testField": "atest", - "testFieldPtr": "something" -} -`), - }, - }, - }, - expectedResponse: apiextensionsv1.ConversionResponse{ - Result: metav1.Status{ - Status: metav1.StatusSuccess, - }, - ConvertedObjects: []runtime.RawExtension{ - { - Raw: []byte(`{"kind":"TestType","apiVersion":"testgroup.testing.cert-manager.io/v2","metadata":{"name":"testing","namespace":"abc","creationTimestamp":null},"testField":"atest","testFieldPtrAlt":"something","testFieldImmutable":""} -`), - }, - }, - }, - }, - } - - for n, test := range tests { - t.Run(n, func(t *testing.T) { - resp := c.Convert(&test.inputRequest) - if !reflect.DeepEqual(&test.expectedResponse, resp) { - t.Errorf("Response was not as expected: %v", diff.ObjectGoPrintSideBySide(&test.expectedResponse, resp)) - } - }) - } -} diff --git a/pkg/webhook/handlers/interfaces.go b/pkg/webhook/handlers/interfaces.go index 8e9bab8243f..cf59d271f2d 100644 --- a/pkg/webhook/handlers/interfaces.go +++ b/pkg/webhook/handlers/interfaces.go @@ -20,7 +20,6 @@ import ( "context" admissionv1 "k8s.io/api/admission/v1" - apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" ) type ValidatingAdmissionHook interface { @@ -34,8 +33,3 @@ type MutatingAdmissionHook interface { // use the Patch field to mutate the object from the passed AdmissionRequest. Mutate(ctx context.Context, admissionSpec *admissionv1.AdmissionRequest) *admissionv1.AdmissionResponse } - -type ConversionHook interface { - // Convert is called to convert a resource in one version into a different version. - Convert(conversionSpec *apiextensionsv1.ConversionRequest) *apiextensionsv1.ConversionResponse -} diff --git a/pkg/webhook/handlers/testdata/apis/testgroup/crds/README.md b/pkg/webhook/handlers/testdata/apis/testgroup/crds/README.md deleted file mode 100644 index 5190f492851..00000000000 --- a/pkg/webhook/handlers/testdata/apis/testgroup/crds/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# README - -These CRDs are auto generated by `hack/update-crds.sh`. diff --git a/pkg/webhook/handlers/testdata/apis/testgroup/crds/testgroup.testing.cert-manager.io_testtypes.yaml b/pkg/webhook/handlers/testdata/apis/testgroup/crds/testgroup.testing.cert-manager.io_testtypes.yaml deleted file mode 100644 index 8a494f133c2..00000000000 --- a/pkg/webhook/handlers/testdata/apis/testgroup/crds/testgroup.testing.cert-manager.io_testtypes.yaml +++ /dev/null @@ -1,93 +0,0 @@ ---- -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.10.0 - creationTimestamp: null - name: testtypes.testgroup.testing.cert-manager.io -spec: - group: testgroup.testing.cert-manager.io - names: - kind: TestType - listKind: TestTypeList - plural: testtypes - singular: testtype - scope: Namespaced - versions: - - name: v1 - schema: - openAPIV3Schema: - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - testDefaultingField: - description: TestDefaultingField is used to test defaulting. In the v1 - API, it defaults to `set-in-v1`. In the v2 API, it defaults to `set-in-v2`. - type: string - testField: - description: TestField is used in tests. Validation doesn't allow this - to be set to the value of TestFieldValueNotAllowed. - type: string - testFieldImmutable: - description: TestFieldImmutable cannot be changed after being set to a - non-zero value - type: string - testFieldPtr: - type: string - required: - - metadata - - testField - - testFieldImmutable - type: object - served: true - storage: false - - name: v2 - schema: - openAPIV3Schema: - description: TestType in v2 is identical to v1, except TestFieldPtr has been - renamed to TestFieldPtrAlt - properties: - apiVersion: - description: 'APIVersion defines the versioned schema of this representation - of an object. Servers should convert recognized schemas to the latest - internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' - type: string - kind: - description: 'Kind is a string value representing the REST resource this - object represents. Servers may infer this from the endpoint the client - submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - testDefaultingField: - description: TestDefaultingField is used to test defaulting. In the v1 - API, it defaults to `set-in-v1`. In the v2 API, it defaults to `set-in-v2`. - type: string - testField: - description: TestField is used in tests. Validation doesn't allow this - to be set to the value of TestFieldValueNotAllowed. - type: string - testFieldImmutable: - description: TestFieldImmutable cannot be changed after being set to a - non-zero value - type: string - testFieldPtrAlt: - type: string - required: - - metadata - - testField - - testFieldImmutable - type: object - served: true - storage: true diff --git a/pkg/webhook/handlers/testdata/apis/testgroup/fuzzer/fuzzer.go b/pkg/webhook/handlers/testdata/apis/testgroup/fuzzer/fuzzer.go deleted file mode 100644 index 640a6fcbd2f..00000000000 --- a/pkg/webhook/handlers/testdata/apis/testgroup/fuzzer/fuzzer.go +++ /dev/null @@ -1,38 +0,0 @@ -/* -Copyright 2020 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. -*/ - -package fuzzer - -import ( - fuzz "github.com/google/gofuzz" - runtimeserializer "k8s.io/apimachinery/pkg/runtime/serializer" - "k8s.io/utils/pointer" - - "github.com/cert-manager/cert-manager/pkg/webhook/handlers/testdata/apis/testgroup" -) - -// Funcs returns the fuzzer functions for the apps api group. -var Funcs = func(codecs runtimeserializer.CodecFactory) []interface{} { - return []interface{}{ - func(s *testgroup.TestType, c fuzz.Continue) { - c.FuzzNoCustom(s) // fuzz self without calling this function again - - if s.TestFieldPtr == nil { - s.TestFieldPtr = pointer.StringPtr("teststr") - } - }, - } -} diff --git a/pkg/webhook/handlers/testdata/apis/testgroup/types.go b/pkg/webhook/handlers/testdata/apis/testgroup/types.go deleted file mode 100644 index 9c8b10b826b..00000000000 --- a/pkg/webhook/handlers/testdata/apis/testgroup/types.go +++ /dev/null @@ -1,39 +0,0 @@ -/* -Copyright 2020 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. -*/ - -package testgroup - -import ( - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object - -type TestType struct { - metav1.TypeMeta - metav1.ObjectMeta - - // TestField is used in tests. - // Validation doesn't allow this to be set to the value of TestFieldValueNotAllowed. - TestField string - TestFieldPtr *string - - // TestFieldImmutable cannot be changed after being set to a non-zero value - TestFieldImmutable string - - // TestDefaultingField is used to test defaulting. - TestDefaultingField string -} diff --git a/pkg/webhook/handlers/testdata/apis/testgroup/v1/defaults.go b/pkg/webhook/handlers/testdata/apis/testgroup/v1/defaults.go deleted file mode 100644 index 740bf66b4eb..00000000000 --- a/pkg/webhook/handlers/testdata/apis/testgroup/v1/defaults.go +++ /dev/null @@ -1,35 +0,0 @@ -/* -Copyright 2020 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. -*/ - -package v1 - -import ( - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/utils/pointer" -) - -func addDefaultingFuncs(scheme *runtime.Scheme) error { - return RegisterDefaults(scheme) -} - -func SetDefaults_TestType(obj *TestType) { - if obj.TestFieldPtr == nil { - obj.TestFieldPtr = pointer.StringPtr("teststr") - } - if obj.TestDefaultingField == "" { - obj.TestDefaultingField = "set-in-v1" - } -} diff --git a/pkg/webhook/handlers/testdata/apis/testgroup/v1/types.go b/pkg/webhook/handlers/testdata/apis/testgroup/v1/types.go deleted file mode 100644 index 8516e48be04..00000000000 --- a/pkg/webhook/handlers/testdata/apis/testgroup/v1/types.go +++ /dev/null @@ -1,45 +0,0 @@ -/* -Copyright 2020 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. -*/ - -package v1 - -import ( - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object - -type TestType struct { - metav1.TypeMeta `json:",inline"` - metav1.ObjectMeta `json:"metadata"` - - // TestField is used in tests. - // Validation doesn't allow this to be set to the value of TestFieldValueNotAllowed. - TestField string `json:"testField"` - TestFieldPtr *string `json:"testFieldPtr,omitempty"` - - // TestFieldImmutable cannot be changed after being set to a non-zero value - TestFieldImmutable string `json:"testFieldImmutable"` - - // TestDefaultingField is used to test defaulting. - // In the v1 API, it defaults to `set-in-v1`. - // In the v2 API, it defaults to `set-in-v2`. - TestDefaultingField string `json:"testDefaultingField,omitempty"` -} - -const ( - TestFieldValueNotAllowed = "not-allowed-value" -) diff --git a/pkg/webhook/handlers/testdata/apis/testgroup/v1/zz_generated.conversion.go b/pkg/webhook/handlers/testdata/apis/testgroup/v1/zz_generated.conversion.go deleted file mode 100644 index 6358d46b99e..00000000000 --- a/pkg/webhook/handlers/testdata/apis/testgroup/v1/zz_generated.conversion.go +++ /dev/null @@ -1,78 +0,0 @@ -//go:build !ignore_autogenerated -// +build !ignore_autogenerated - -/* -Copyright 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. -*/ - -// Code generated by conversion-gen. DO NOT EDIT. - -package v1 - -import ( - unsafe "unsafe" - - testgroup "github.com/cert-manager/cert-manager/pkg/webhook/handlers/testdata/apis/testgroup" - conversion "k8s.io/apimachinery/pkg/conversion" - runtime "k8s.io/apimachinery/pkg/runtime" -) - -func init() { - localSchemeBuilder.Register(RegisterConversions) -} - -// RegisterConversions adds conversion functions to the given scheme. -// Public to allow building arbitrary schemes. -func RegisterConversions(s *runtime.Scheme) error { - if err := s.AddGeneratedConversionFunc((*TestType)(nil), (*testgroup.TestType)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1_TestType_To_testgroup_TestType(a.(*TestType), b.(*testgroup.TestType), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*testgroup.TestType)(nil), (*TestType)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_testgroup_TestType_To_v1_TestType(a.(*testgroup.TestType), b.(*TestType), scope) - }); err != nil { - return err - } - return nil -} - -func autoConvert_v1_TestType_To_testgroup_TestType(in *TestType, out *testgroup.TestType, s conversion.Scope) error { - out.ObjectMeta = in.ObjectMeta - out.TestField = in.TestField - out.TestFieldPtr = (*string)(unsafe.Pointer(in.TestFieldPtr)) - out.TestFieldImmutable = in.TestFieldImmutable - out.TestDefaultingField = in.TestDefaultingField - return nil -} - -// Convert_v1_TestType_To_testgroup_TestType is an autogenerated conversion function. -func Convert_v1_TestType_To_testgroup_TestType(in *TestType, out *testgroup.TestType, s conversion.Scope) error { - return autoConvert_v1_TestType_To_testgroup_TestType(in, out, s) -} - -func autoConvert_testgroup_TestType_To_v1_TestType(in *testgroup.TestType, out *TestType, s conversion.Scope) error { - out.ObjectMeta = in.ObjectMeta - out.TestField = in.TestField - out.TestFieldPtr = (*string)(unsafe.Pointer(in.TestFieldPtr)) - out.TestFieldImmutable = in.TestFieldImmutable - out.TestDefaultingField = in.TestDefaultingField - return nil -} - -// Convert_testgroup_TestType_To_v1_TestType is an autogenerated conversion function. -func Convert_testgroup_TestType_To_v1_TestType(in *testgroup.TestType, out *TestType, s conversion.Scope) error { - return autoConvert_testgroup_TestType_To_v1_TestType(in, out, s) -} diff --git a/pkg/webhook/handlers/testdata/apis/testgroup/v1/zz_generated.deepcopy.go b/pkg/webhook/handlers/testdata/apis/testgroup/v1/zz_generated.deepcopy.go deleted file mode 100644 index fe11fcc7ac0..00000000000 --- a/pkg/webhook/handlers/testdata/apis/testgroup/v1/zz_generated.deepcopy.go +++ /dev/null @@ -1,57 +0,0 @@ -//go:build !ignore_autogenerated -// +build !ignore_autogenerated - -/* -Copyright 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. -*/ - -// Code generated by deepcopy-gen. DO NOT EDIT. - -package v1 - -import ( - runtime "k8s.io/apimachinery/pkg/runtime" -) - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *TestType) DeepCopyInto(out *TestType) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - if in.TestFieldPtr != nil { - in, out := &in.TestFieldPtr, &out.TestFieldPtr - *out = new(string) - **out = **in - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TestType. -func (in *TestType) DeepCopy() *TestType { - if in == nil { - return nil - } - out := new(TestType) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *TestType) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} diff --git a/pkg/webhook/handlers/testdata/apis/testgroup/v2/convert.go b/pkg/webhook/handlers/testdata/apis/testgroup/v2/convert.go deleted file mode 100644 index 4e2c6919891..00000000000 --- a/pkg/webhook/handlers/testdata/apis/testgroup/v2/convert.go +++ /dev/null @@ -1,41 +0,0 @@ -/* -Copyright 2020 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. -*/ - -package v2 - -import ( - "unsafe" - - "k8s.io/apimachinery/pkg/conversion" - - "github.com/cert-manager/cert-manager/pkg/webhook/handlers/testdata/apis/testgroup" -) - -func Convert_v2_TestType_To_testgroup_TestType(in *TestType, out *testgroup.TestType, s conversion.Scope) error { - if err := autoConvert_v2_TestType_To_testgroup_TestType(in, out, s); err != nil { - return err - } - out.TestFieldPtr = (*string)(unsafe.Pointer(in.TestFieldPtrAlt)) - return nil -} - -func Convert_testgroup_TestType_To_v2_TestType(in *testgroup.TestType, out *TestType, s conversion.Scope) error { - if err := autoConvert_testgroup_TestType_To_v2_TestType(in, out, s); err != nil { - return err - } - out.TestFieldPtrAlt = (*string)(unsafe.Pointer(in.TestFieldPtr)) - return nil -} diff --git a/pkg/webhook/handlers/testdata/apis/testgroup/v2/defaults.go b/pkg/webhook/handlers/testdata/apis/testgroup/v2/defaults.go deleted file mode 100644 index 7466c42f33f..00000000000 --- a/pkg/webhook/handlers/testdata/apis/testgroup/v2/defaults.go +++ /dev/null @@ -1,35 +0,0 @@ -/* -Copyright 2020 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. -*/ - -package v2 - -import ( - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/utils/pointer" -) - -func addDefaultingFuncs(scheme *runtime.Scheme) error { - return RegisterDefaults(scheme) -} - -func SetDefaults_TestType(obj *TestType) { - if obj.TestFieldPtrAlt == nil { - obj.TestFieldPtrAlt = pointer.StringPtr("teststr") - } - if obj.TestDefaultingField == "" { - obj.TestDefaultingField = "set-in-v2" - } -} diff --git a/pkg/webhook/handlers/testdata/apis/testgroup/v2/types.go b/pkg/webhook/handlers/testdata/apis/testgroup/v2/types.go deleted file mode 100644 index a6f49622ae2..00000000000 --- a/pkg/webhook/handlers/testdata/apis/testgroup/v2/types.go +++ /dev/null @@ -1,48 +0,0 @@ -/* -Copyright 2020 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. -*/ - -package v2 - -import ( - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object - -// +kubebuilder:storageversion -// TestType in v2 is identical to v1, except TestFieldPtr has been renamed to TestFieldPtrAlt -type TestType struct { - metav1.TypeMeta `json:",inline"` - metav1.ObjectMeta `json:"metadata"` - - // TestField is used in tests. - // Validation doesn't allow this to be set to the value of TestFieldValueNotAllowed. - TestField string `json:"testField"` - // +optional - TestFieldPtrAlt *string `json:"testFieldPtrAlt,omitempty"` - - // TestFieldImmutable cannot be changed after being set to a non-zero value - TestFieldImmutable string `json:"testFieldImmutable"` - - // TestDefaultingField is used to test defaulting. - // In the v1 API, it defaults to `set-in-v1`. - // In the v2 API, it defaults to `set-in-v2`. - TestDefaultingField string `json:"testDefaultingField,omitempty"` -} - -const ( - DisallowedTestFieldValue = "not-allowed-in-v2" -) diff --git a/pkg/webhook/handlers/testdata/apis/testgroup/v2/validation.go b/pkg/webhook/handlers/testdata/apis/testgroup/v2/validation.go deleted file mode 100644 index 0ad874e20d2..00000000000 --- a/pkg/webhook/handlers/testdata/apis/testgroup/v2/validation.go +++ /dev/null @@ -1,32 +0,0 @@ -/* -Copyright 2020 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. -*/ - -package v2 - -import ( - admissionv1 "k8s.io/api/admission/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/util/validation/field" -) - -func ValidateTestType(_ *admissionv1.AdmissionRequest, obj runtime.Object) (field.ErrorList, []string) { - el := field.ErrorList{} - tt := obj.(*TestType) - if tt.TestField == DisallowedTestFieldValue { - el = append(el, field.Invalid(field.NewPath("testField"), tt.TestField, "value not allowed")) - } - return el, nil -} diff --git a/pkg/webhook/handlers/testdata/apis/testgroup/v2/zz_generated.conversion.go b/pkg/webhook/handlers/testdata/apis/testgroup/v2/zz_generated.conversion.go deleted file mode 100644 index 40f95819793..00000000000 --- a/pkg/webhook/handlers/testdata/apis/testgroup/v2/zz_generated.conversion.go +++ /dev/null @@ -1,66 +0,0 @@ -//go:build !ignore_autogenerated -// +build !ignore_autogenerated - -/* -Copyright 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. -*/ - -// Code generated by conversion-gen. DO NOT EDIT. - -package v2 - -import ( - testgroup "github.com/cert-manager/cert-manager/pkg/webhook/handlers/testdata/apis/testgroup" - conversion "k8s.io/apimachinery/pkg/conversion" - runtime "k8s.io/apimachinery/pkg/runtime" -) - -func init() { - localSchemeBuilder.Register(RegisterConversions) -} - -// RegisterConversions adds conversion functions to the given scheme. -// Public to allow building arbitrary schemes. -func RegisterConversions(s *runtime.Scheme) error { - if err := s.AddConversionFunc((*testgroup.TestType)(nil), (*TestType)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_testgroup_TestType_To_v2_TestType(a.(*testgroup.TestType), b.(*TestType), scope) - }); err != nil { - return err - } - if err := s.AddConversionFunc((*TestType)(nil), (*testgroup.TestType)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v2_TestType_To_testgroup_TestType(a.(*TestType), b.(*testgroup.TestType), scope) - }); err != nil { - return err - } - return nil -} - -func autoConvert_v2_TestType_To_testgroup_TestType(in *TestType, out *testgroup.TestType, s conversion.Scope) error { - out.ObjectMeta = in.ObjectMeta - out.TestField = in.TestField - // WARNING: in.TestFieldPtrAlt requires manual conversion: does not exist in peer-type - out.TestFieldImmutable = in.TestFieldImmutable - out.TestDefaultingField = in.TestDefaultingField - return nil -} - -func autoConvert_testgroup_TestType_To_v2_TestType(in *testgroup.TestType, out *TestType, s conversion.Scope) error { - out.ObjectMeta = in.ObjectMeta - out.TestField = in.TestField - // WARNING: in.TestFieldPtr requires manual conversion: does not exist in peer-type - out.TestFieldImmutable = in.TestFieldImmutable - out.TestDefaultingField = in.TestDefaultingField - return nil -} diff --git a/pkg/webhook/handlers/testdata/apis/testgroup/v2/zz_generated.deepcopy.go b/pkg/webhook/handlers/testdata/apis/testgroup/v2/zz_generated.deepcopy.go deleted file mode 100644 index 4ffe8ea19c3..00000000000 --- a/pkg/webhook/handlers/testdata/apis/testgroup/v2/zz_generated.deepcopy.go +++ /dev/null @@ -1,57 +0,0 @@ -//go:build !ignore_autogenerated -// +build !ignore_autogenerated - -/* -Copyright 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. -*/ - -// Code generated by deepcopy-gen. DO NOT EDIT. - -package v2 - -import ( - runtime "k8s.io/apimachinery/pkg/runtime" -) - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *TestType) DeepCopyInto(out *TestType) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - if in.TestFieldPtrAlt != nil { - in, out := &in.TestFieldPtrAlt, &out.TestFieldPtrAlt - *out = new(string) - **out = **in - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TestType. -func (in *TestType) DeepCopy() *TestType { - if in == nil { - return nil - } - out := new(TestType) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *TestType) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} diff --git a/pkg/webhook/handlers/testdata/apis/testgroup/validation/validation.go b/pkg/webhook/handlers/testdata/apis/testgroup/validation/validation.go deleted file mode 100644 index 7678f0cc63f..00000000000 --- a/pkg/webhook/handlers/testdata/apis/testgroup/validation/validation.go +++ /dev/null @@ -1,49 +0,0 @@ -/* -Copyright 2020 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. -*/ - -package validation - -import ( - admissionv1 "k8s.io/api/admission/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/util/validation/field" - - "github.com/cert-manager/cert-manager/pkg/webhook/handlers/testdata/apis/testgroup" - v1 "github.com/cert-manager/cert-manager/pkg/webhook/handlers/testdata/apis/testgroup/v1" -) - -func ValidateTestType(_ *admissionv1.AdmissionRequest, obj runtime.Object) (field.ErrorList, []string) { - testType := obj.(*testgroup.TestType) - el := field.ErrorList{} - if testType.TestField == v1.TestFieldValueNotAllowed { - el = append(el, field.Invalid(field.NewPath("testField"), testType.TestField, "invalid value")) - } - return el, nil -} - -func ValidateTestTypeUpdate(_ *admissionv1.AdmissionRequest, oldObj, newObj runtime.Object) (field.ErrorList, []string) { - old, ok := oldObj.(*testgroup.TestType) - new := newObj.(*testgroup.TestType) - // if oldObj is not set, the Update operation is always valid. - if !ok || old == nil { - return nil, nil - } - el := field.ErrorList{} - if old.TestFieldImmutable != "" && old.TestFieldImmutable != new.TestFieldImmutable { - el = append(el, field.Forbidden(field.NewPath("testFieldImmutable"), "field is immutable once set")) - } - return el, nil -} diff --git a/pkg/webhook/handlers/testdata/apis/testgroup/validation/validation_test.go b/pkg/webhook/handlers/testdata/apis/testgroup/validation/validation_test.go deleted file mode 100644 index adada703e76..00000000000 --- a/pkg/webhook/handlers/testdata/apis/testgroup/validation/validation_test.go +++ /dev/null @@ -1,158 +0,0 @@ -/* -Copyright 2020 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. -*/ - -package validation - -import ( - "reflect" - "testing" - - "k8s.io/apimachinery/pkg/util/validation/field" - - "github.com/cert-manager/cert-manager/pkg/webhook/handlers/testdata/apis/testgroup" - v1 "github.com/cert-manager/cert-manager/pkg/webhook/handlers/testdata/apis/testgroup/v1" -) - -func TestValidateTestType(t *testing.T) { - scenarios := map[string]struct { - obj *testgroup.TestType - errs []*field.Error - warnings []string - }{ - "does not allow testField to be TestFieldValueNotAllowed": { - obj: &testgroup.TestType{ - TestField: v1.TestFieldValueNotAllowed, - }, - errs: []*field.Error{ - field.Invalid(field.NewPath("testField"), v1.TestFieldValueNotAllowed, "invalid value"), - }, - }, - } - for n, s := range scenarios { - t.Run(n, func(t *testing.T) { - errs, warnings := ValidateTestType(nil, s.obj) - if len(errs) != len(s.errs) { - t.Errorf("Expected errors %v but got %v", s.errs, errs) - return - } - for i, e := range errs { - expectedErr := s.errs[i] - if !reflect.DeepEqual(e, expectedErr) { - t.Errorf("Expected error %v but got %v", expectedErr, e) - } - } - if !reflect.DeepEqual(warnings, s.warnings) { - t.Errorf("Expected warnings %+#v but got %+#v", s.warnings, warnings) - } - }) - } -} - -func TestValidateTestTypeUpdate(t *testing.T) { - testImmutableTestTypeField(t, field.NewPath("testFieldImmutable"), func(obj *testgroup.TestType, s testValue) { - obj.TestFieldImmutable = string(s) - }) - - scenarios := map[string]struct { - old, new *testgroup.TestType - errs []*field.Error - warnings []string - }{ - "allows all updates if old is nil": { - new: &testgroup.TestType{ - TestFieldImmutable: "abc", - }, - }, - } - for n, s := range scenarios { - t.Run(n, func(t *testing.T) { - errs, warnings := ValidateTestTypeUpdate(nil, s.old, s.new) - if len(errs) != len(s.errs) { - t.Errorf("Expected errors %v but got %v", s.errs, errs) - return - } - for i, e := range errs { - expectedErr := s.errs[i] - if !reflect.DeepEqual(e, expectedErr) { - t.Errorf("Expected error %v but got %v", expectedErr, e) - } - } - if !reflect.DeepEqual(warnings, s.warnings) { - t.Errorf("Expected warnings %+#v but got %+#v", s.warnings, warnings) - } - }) - } -} - -type testValue string - -const ( - testValueNone = "" - testValueOptionOne = "one" - testValueOptionTwo = "two" -) - -// testImmutableOrderField will test that the field at path fldPath does -// not allow changes after being set, but does allow changes if the old field -// is not set. -func testImmutableTestTypeField(t *testing.T, fldPath *field.Path, setter func(*testgroup.TestType, testValue)) { - t.Run("should reject updates to "+fldPath.String(), func(t *testing.T) { - var expectedWarnings []string - expectedErrs := []*field.Error{ - field.Forbidden(fldPath, "field is immutable once set"), - } - old := &testgroup.TestType{} - new := &testgroup.TestType{} - setter(old, testValueOptionOne) - setter(new, testValueOptionTwo) - errs, warnings := ValidateTestTypeUpdate(nil, old, new) - if len(errs) != len(expectedErrs) { - t.Errorf("Expected errors %v but got %v", expectedErrs, errs) - return - } - for i, e := range errs { - expectedErr := expectedErrs[i] - if !reflect.DeepEqual(e, expectedErr) { - t.Errorf("Expected error %v but got %v", expectedErr, e) - } - } - if !reflect.DeepEqual(warnings, expectedWarnings) { - t.Errorf("Expected warnings %+#v got %+#v", expectedWarnings, warnings) - } - }) - t.Run("should allow updates to "+fldPath.String()+" if not already set", func(t *testing.T) { - var expectedWarnings []string - expectedErrs := []*field.Error{} - old := &testgroup.TestType{} - new := &testgroup.TestType{} - setter(old, testValueNone) - setter(new, testValueOptionOne) - errs, warnings := ValidateTestTypeUpdate(nil, old, new) - if len(errs) != len(expectedErrs) { - t.Errorf("Expected errors %v but got %v", expectedErrs, errs) - return - } - for i, e := range errs { - expectedErr := expectedErrs[i] - if !reflect.DeepEqual(e, expectedErr) { - t.Errorf("Expected error %v but got %v", expectedErr, e) - } - } - if !reflect.DeepEqual(warnings, expectedWarnings) { - t.Errorf("Expected warnings %+#v but got %+#v", expectedWarnings, warnings) - } - }) -} diff --git a/pkg/webhook/handlers/testdata/apis/testgroup/zz_generated.deepcopy.go b/pkg/webhook/handlers/testdata/apis/testgroup/zz_generated.deepcopy.go deleted file mode 100644 index 010463b8506..00000000000 --- a/pkg/webhook/handlers/testdata/apis/testgroup/zz_generated.deepcopy.go +++ /dev/null @@ -1,57 +0,0 @@ -//go:build !ignore_autogenerated -// +build !ignore_autogenerated - -/* -Copyright 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. -*/ - -// Code generated by deepcopy-gen. DO NOT EDIT. - -package testgroup - -import ( - runtime "k8s.io/apimachinery/pkg/runtime" -) - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *TestType) DeepCopyInto(out *TestType) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - if in.TestFieldPtr != nil { - in, out := &in.TestFieldPtr, &out.TestFieldPtr - *out = new(string) - **out = **in - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TestType. -func (in *TestType) DeepCopy() *TestType { - if in == nil { - return nil - } - out := new(TestType) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *TestType) DeepCopyObject() runtime.Object { - if c := in.DeepCopy(); c != nil { - return c - } - return nil -} diff --git a/cmd/webhook/app/options/options.go b/pkg/webhook/options/options.go similarity index 60% rename from cmd/webhook/app/options/options.go rename to pkg/webhook/options/options.go index fb2395c9771..5d1f03a678f 100644 --- a/cmd/webhook/app/options/options.go +++ b/pkg/webhook/options/options.go @@ -25,6 +25,7 @@ import ( config "github.com/cert-manager/cert-manager/internal/apis/config/webhook" configscheme "github.com/cert-manager/cert-manager/internal/apis/config/webhook/scheme" configv1alpha1 "github.com/cert-manager/cert-manager/pkg/apis/config/webhook/v1alpha1" + logf "github.com/cert-manager/cert-manager/pkg/logs" utilfeature "github.com/cert-manager/cert-manager/pkg/util/feature" ) @@ -42,11 +43,6 @@ func (f *WebhookFlags) AddFlags(fs *pflag.FlagSet) { fs.StringVar(&f.Config, "config", "", "Path to a file containing a WebhookConfiguration object used to configure the webhook") } -func ValidateWebhookFlags(f *WebhookFlags) error { - // No validation needed today - return nil -} - func NewWebhookConfiguration() (*config.WebhookConfiguration, error) { scheme, _, err := configscheme.NewSchemeAndCodecs() if err != nil { @@ -62,14 +58,15 @@ func NewWebhookConfiguration() (*config.WebhookConfiguration, error) { } func AddConfigFlags(fs *pflag.FlagSet, c *config.WebhookConfiguration) { - fs.IntVar(c.SecurePort, "secure-port", *c.SecurePort, "port number to listen on for secure TLS connections") - fs.IntVar(c.HealthzPort, "healthz-port", *c.HealthzPort, "port number to listen on for insecure healthz connections") + fs.Int32Var(&c.SecurePort, "secure-port", c.SecurePort, "port number to listen on for secure TLS connections") + fs.Int32Var(&c.HealthzPort, "healthz-port", c.HealthzPort, "port number to listen on for insecure healthz connections") fs.StringVar(&c.TLSConfig.Filesystem.CertFile, "tls-cert-file", c.TLSConfig.Filesystem.CertFile, "path to the file containing the TLS certificate to serve with") fs.StringVar(&c.TLSConfig.Filesystem.KeyFile, "tls-private-key-file", c.TLSConfig.Filesystem.KeyFile, "path to the file containing the TLS private key to serve with") + fs.DurationVar(&c.TLSConfig.Dynamic.LeafDuration, "dynamic-serving-leaf-duration", c.TLSConfig.Dynamic.LeafDuration, "leaf duration of serving certificates") fs.StringVar(&c.TLSConfig.Dynamic.SecretNamespace, "dynamic-serving-ca-secret-namespace", c.TLSConfig.Dynamic.SecretNamespace, "namespace of the secret used to store the CA that signs serving certificates") - fs.StringVar(&c.TLSConfig.Dynamic.SecretName, "dynamic-serving-ca-secret-name", c.TLSConfig.Dynamic.SecretName, "name of the secret used to store the CA that signs serving certificates certificates") + fs.StringVar(&c.TLSConfig.Dynamic.SecretName, "dynamic-serving-ca-secret-name", c.TLSConfig.Dynamic.SecretName, "name of the secret used to store the CA that signs serving certificates") fs.StringSliceVar(&c.TLSConfig.Dynamic.DNSNames, "dynamic-serving-dns-names", c.TLSConfig.Dynamic.DNSNames, "DNS names that should be present on certificates generated by the dynamic serving CA") fs.StringVar(&c.KubeConfig, "kubeconfig", c.KubeConfig, "optional path to the kubeconfig used to connect to the apiserver. If not specified, in-cluster-config will be used") @@ -83,12 +80,30 @@ func AddConfigFlags(fs *pflag.FlagSet, c *config.WebhookConfiguration) { tlsCipherPossibleValues := cliflag.TLSCipherPossibleValues() fs.StringSliceVar(&c.TLSConfig.CipherSuites, "tls-cipher-suites", c.TLSConfig.CipherSuites, "Comma-separated list of cipher suites for the server. "+ - "If omitted, the default Go cipher suites will be use. "+ + "If omitted, the default Go cipher suites will be used. "+ "Possible values: "+strings.Join(tlsCipherPossibleValues, ",")) tlsPossibleVersions := cliflag.TLSPossibleVersions() fs.StringVar(&c.TLSConfig.MinTLSVersion, "tls-min-version", c.TLSConfig.MinTLSVersion, - "Minimum TLS version supported. "+ + "Minimum TLS version supported. If omitted, the default Go minimum version will be used. "+ "Possible values: "+strings.Join(tlsPossibleVersions, ", ")) fs.Var(cliflag.NewMapStringBool(&c.FeatureGates), "feature-gates", "A set of key=value pairs that describe feature gates for alpha/experimental features. "+ "Options are:\n"+strings.Join(utilfeature.DefaultFeatureGate.KnownFeatures(), "\n")) + + logf.AddFlags(&c.Logging, fs) + + fs.StringVar(&c.MetricsListenAddress, "metrics-listen-address", c.MetricsListenAddress, "The host and port that the metrics endpoint should listen on. The value '0' disables the metrics server") + fs.StringVar(&c.MetricsTLSConfig.Filesystem.CertFile, "metrics-tls-cert-file", c.MetricsTLSConfig.Filesystem.CertFile, "path to the file containing the TLS certificate to serve metrics with") + fs.StringVar(&c.MetricsTLSConfig.Filesystem.KeyFile, "metrics-tls-private-key-file", c.MetricsTLSConfig.Filesystem.KeyFile, "path to the file containing the TLS private key to serve metrics with") + + fs.DurationVar(&c.MetricsTLSConfig.Dynamic.LeafDuration, "metrics-dynamic-serving-leaf-duration", c.MetricsTLSConfig.Dynamic.LeafDuration, "leaf duration of metrics serving certificates") + fs.StringVar(&c.MetricsTLSConfig.Dynamic.SecretNamespace, "metrics-dynamic-serving-ca-secret-namespace", c.MetricsTLSConfig.Dynamic.SecretNamespace, "namespace of the secret used to store the CA that signs metrics serving certificates") + fs.StringVar(&c.MetricsTLSConfig.Dynamic.SecretName, "metrics-dynamic-serving-ca-secret-name", c.MetricsTLSConfig.Dynamic.SecretName, "name of the secret used to store the CA that signs serving certificates") + fs.StringSliceVar(&c.MetricsTLSConfig.Dynamic.DNSNames, "metrics-dynamic-serving-dns-names", c.MetricsTLSConfig.Dynamic.DNSNames, "DNS names that should be present on certificates generated by the metrics dynamic serving CA") + fs.StringSliceVar(&c.MetricsTLSConfig.CipherSuites, "metrics-tls-cipher-suites", c.MetricsTLSConfig.CipherSuites, + "Comma-separated list of cipher suites for the metrics server. "+ + "If omitted, the default Go cipher suites will be used. "+ + "Possible values: "+strings.Join(tlsCipherPossibleValues, ",")) + fs.StringVar(&c.MetricsTLSConfig.MinTLSVersion, "metrics-tls-min-version", c.MetricsTLSConfig.MinTLSVersion, + "Minimum TLS version supported by the metrics server. If omitted, the default Go minimum version will be used. "+ + "Possible values: "+strings.Join(tlsPossibleVersions, ", ")) } diff --git a/pkg/webhook/server/server.go b/pkg/webhook/server/server.go index 8c7cce0e595..424967f917b 100644 --- a/pkg/webhook/server/server.go +++ b/pkg/webhook/server/server.go @@ -21,87 +21,63 @@ import ( "crypto/tls" "errors" "fmt" - "io" "net" "net/http" "time" - "github.com/go-logr/logr" - "golang.org/x/sync/errgroup" - admissionv1 "k8s.io/api/admission/v1" - apiextensionsinstall "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/install" - apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/apimachinery/pkg/runtime/serializer/json" - runtimeutil "k8s.io/apimachinery/pkg/util/runtime" + "k8s.io/client-go/rest" ciphers "k8s.io/component-base/cli/flag" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/manager" + metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server" + "sigs.k8s.io/controller-runtime/pkg/webhook" logf "github.com/cert-manager/cert-manager/pkg/logs" + servertls "github.com/cert-manager/cert-manager/pkg/server/tls" "github.com/cert-manager/cert-manager/pkg/util/profiling" - "github.com/cert-manager/cert-manager/pkg/webhook/handlers" - servertls "github.com/cert-manager/cert-manager/pkg/webhook/server/tls" + cmadmission "github.com/cert-manager/cert-manager/pkg/webhook/admission" ) -var ( - // defaultScheme is used to encode and decode the AdmissionReview and - // ConversionReview resources submitted to the webhook server. - // It is not used for performing validation, mutation or conversion. - defaultScheme = runtime.NewScheme() +const ( + // This is intended to mitigate "slowloris" attacks by limiting the time a + // deliberately slow client can spend sending HTTP headers. + // This default value is copied from: + // * kubernetes api-server: + // https://github.com/kubernetes/kubernetes/blob/9e028b40b9e970142191259effe796b3dab39828/staging/src/k8s.io/apiserver/pkg/server/secure_serving.go#L165-L173 + // * controller-runtime: + // https://github.com/kubernetes-sigs/controller-runtime/blob/1ea2be573f7887a9fbd766e9a921c5af344da6eb/pkg/internal/httpserver/server.go#L14 + defaultReadHeaderTimeout = 32 * time.Second +) +var ( ErrNotListening = errors.New("Server is not listening yet") ) -func init() { - apiextensionsinstall.Install(defaultScheme) - runtimeutil.Must(admissionv1.AddToScheme(defaultScheme)) - - // we need to add the options to empty v1 - // TODO fix the server code to avoid this - metav1.AddToGroupVersion(defaultScheme, schema.GroupVersion{Version: "v1"}) - - // TODO: keep the generic API server from wanting this - unversioned := schema.GroupVersion{Group: "", Version: "v1"} - defaultScheme.AddUnversionedTypes(unversioned, - &metav1.Status{}, - &metav1.APIVersions{}, - &metav1.APIGroupList{}, - &metav1.APIGroup{}, - &metav1.APIResourceList{}, - &metav1.CreateOptions{}, - ) -} - type Server struct { // ListenAddr is the address the HTTP server should listen on // This must be specified. - ListenAddr string + ListenAddr int // HealthzAddr is the address the healthz HTTP server should listen on // If not specified, the healthz endpoint will not be exposed. - HealthzAddr string + HealthzAddr *int - // PprofAddr is the address the pprof endpoint should be served on if enabled. - PprofAddr string + // PprofAddress is the address the pprof endpoint should be served on if enabled. + PprofAddress string // EnablePprof determines whether pprof is enabled. EnablePprof bool - // Scheme is used to decode/encode request/response payloads. - // If not specified, a default scheme that registers the AdmissionReview - // and ConversionReview resource types will be used. - // It is not used for performing validation, mutation or conversion. - Scheme *runtime.Scheme + // ResourceScheme is used to decode resources and convert them to + // internal types when validating. + ResourceScheme *runtime.Scheme // If specified, the server will listen with TLS using certificates // provided by this CertificateSource. CertificateSource servertls.CertificateSource - ValidationWebhook handlers.ValidatingAdmissionHook - MutationWebhook handlers.MutatingAdmissionHook - ConversionWebhook handlers.ConversionHook - - log logr.Logger + ValidationWebhook cmadmission.ValidationInterface + MutationWebhook cmadmission.MutationInterface // 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). @@ -111,18 +87,102 @@ type Server struct { // Values are from tls package constants (https://golang.org/pkg/crypto/tls/#pkg-constants). MinTLSVersion string - listener net.Listener -} + // The host and port that the metrics endpoint should listen on. + MetricsListenAddress string -type handleFunc func(context.Context, runtime.Object) (runtime.Object, error) + // If specified, the metrics server will listen with TLS using certificates + // provided by this CertificateSource. + MetricsCertificateSource servertls.CertificateSource + + // MetricsCipherSuites is the list of allowed cipher suites for the server. + // Values are from tls package constants (https://golang.org/pkg/crypto/tls/#pkg-constants). + MetricsCipherSuites []string + + // MetricsMinTLSVersion is the minimum TLS version supported. + // Values are from tls package constants (https://golang.org/pkg/crypto/tls/#pkg-constants). + MetricsMinTLSVersion string +} func (s *Server) Run(ctx context.Context) error { - s.log = logf.FromContext(ctx) - g, gctx := errgroup.WithContext(ctx) + if s.CertificateSource == nil { + return fmt.Errorf("no CertificateSource specified") + } + + log := logf.FromContext(ctx) + + cipherSuites, err := ciphers.TLSCipherSuites(s.CipherSuites) + if err != nil { + return err + } + minVersion, err := ciphers.TLSVersion(s.MinTLSVersion) + if err != nil { + return err + } + + metricsCipherSuites, err := ciphers.TLSCipherSuites(s.MetricsCipherSuites) + if err != nil { + return err + } + metricsMinVersion, err := ciphers.TLSVersion(s.MetricsMinTLSVersion) + if err != nil { + return err + } + + if s.ListenAddr == 0 { + webhookPort, err := freePort() + if err != nil { + return err + } + + s.ListenAddr = webhookPort + } + + mgr, err := ctrl.NewManager( + &rest.Config{}, // controller-runtime does not need to talk to the API server + ctrl.Options{ + Scheme: s.ResourceScheme, + Logger: log, + LeaderElection: false, // The webhook component does not need to perform leader election + Metrics: metricsserver.Options{ + BindAddress: s.MetricsListenAddress, + SecureServing: s.MetricsCertificateSource != nil, + TLSOpts: []func(*tls.Config){ + func(cfg *tls.Config) { + cfg.CipherSuites = metricsCipherSuites + cfg.MinVersion = metricsMinVersion + cfg.GetCertificate = s.MetricsCertificateSource.GetCertificate + }, + }, + }, + WebhookServer: webhook.NewServer(webhook.Options{ + Port: s.ListenAddr, + TLSOpts: []func(*tls.Config){ + func(cfg *tls.Config) { + cfg.CipherSuites = cipherSuites + cfg.MinVersion = minVersion + cfg.GetCertificate = s.CertificateSource.GetCertificate + }, + }, + }), + }) + if err != nil { + return fmt.Errorf("error creating manager: %v", err) + } + + if err := mgr.Add(s.CertificateSource); err != nil { + return err + } + + if s.MetricsCertificateSource != nil { + if err := mgr.Add(s.MetricsCertificateSource); err != nil { + return err + } + } // if a HealthzAddr is provided, start the healthz listener - if s.HealthzAddr != "" { - healthzListener, err := net.Listen("tcp", s.HealthzAddr) + if s.HealthzAddr != nil { + lc := net.ListenConfig{} + healthzListener, err := lc.Listen(ctx, "tcp", fmt.Sprintf(":%d", *s.HealthzAddr)) if err != nil { return err } @@ -130,32 +190,42 @@ func (s *Server) Run(ctx context.Context) error { healthMux := http.NewServeMux() healthMux.HandleFunc("/healthz", s.handleHealthz) healthMux.HandleFunc("/livez", s.handleLivez) - s.log.V(logf.InfoLevel).Info("listening for insecure healthz connections", "address", s.HealthzAddr) + log.V(logf.InfoLevel).Info("listening for insecure healthz connections", "address", s.HealthzAddr) server := &http.Server{ - Handler: healthMux, + Handler: healthMux, + ReadHeaderTimeout: defaultReadHeaderTimeout, // Mitigation for G112: Potential slowloris attack } - g.Go(func() error { - <-gctx.Done() + + if err := mgr.Add(manager.RunnableFunc(func(ctx context.Context) error { + <-ctx.Done() + // allow a timeout for graceful shutdown - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + shutdownCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() - if err := server.Shutdown(ctx); err != nil { + // nolint: contextcheck + if err := server.Shutdown(shutdownCtx); err != nil { return err } return nil - }) - g.Go(func() error { + })); err != nil { + return err + } + + if err := mgr.Add(manager.RunnableFunc(func(ctx context.Context) error { if err := server.Serve(healthzListener); err != http.ErrServerClosed { return err } return nil - }) + })); err != nil { + return err + } } // if a PprofAddr is provided, start the pprof listener if s.EnablePprof { - pprofListener, err := net.Listen("tcp", s.PprofAddr) + lc := net.ListenConfig{} + pprofListener, err := lc.Listen(ctx, "tcp", s.PprofAddress) if err != nil { return err } @@ -163,181 +233,72 @@ func (s *Server) Run(ctx context.Context) error { profilerMux := http.NewServeMux() // Add pprof endpoints to this mux profiling.Install(profilerMux) - s.log.V(logf.InfoLevel).Info("running go profiler on", "address", s.PprofAddr) + log.V(logf.InfoLevel).Info("running go profiler on", "address", s.PprofAddress) server := &http.Server{ - Handler: profilerMux, + Handler: profilerMux, + ReadHeaderTimeout: defaultReadHeaderTimeout, // Mitigation for G112: Potential slowloris attack } - g.Go(func() error { - <-gctx.Done() + + if err := mgr.Add(manager.RunnableFunc(func(ctx context.Context) error { + <-ctx.Done() + // allow a timeout for graceful shutdown - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + shutdownCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() - if err := server.Shutdown(ctx); err != nil { + // nolint: contextcheck + if err := server.Shutdown(shutdownCtx); err != nil { return err } return nil - }) - g.Go(func() error { - if err := server.Serve(pprofListener); err != http.ErrServerClosed { - return err - } - return nil - }) - } - - // create a listener for actual webhook requests - listener, err := net.Listen("tcp", s.ListenAddr) - if err != nil { - return err - } + })); err != nil { + return err + } - // wrap the listener with TLS if a CertificateSource is provided - if s.CertificateSource != nil { - s.log.V(logf.InfoLevel).Info("listening for secure connections", "address", s.ListenAddr) - g.Go(func() error { - if err := s.CertificateSource.Run(gctx); (err != nil) && !errors.Is(err, context.Canceled) { + if err := mgr.Add(manager.RunnableFunc(func(ctx context.Context) error { + if err := server.Serve(pprofListener); err != http.ErrServerClosed { return err } return nil - }) - cipherSuites, err := ciphers.TLSCipherSuites(s.CipherSuites) - if err != nil { + })); err != nil { return err } - minVersion, err := ciphers.TLSVersion(s.MinTLSVersion) - if err != nil { - return err - } - listener = tls.NewListener(listener, &tls.Config{ - GetCertificate: s.CertificateSource.GetCertificate, - CipherSuites: cipherSuites, - MinVersion: minVersion, - PreferServerCipherSuites: true, - }) - } else { - s.log.V(logf.InfoLevel).Info("listening for insecure connections", "address", s.ListenAddr) } - s.listener = listener - serverMux := http.NewServeMux() - serverMux.HandleFunc("/validate", s.handle(s.validate)) - serverMux.HandleFunc("/mutate", s.handle(s.mutate)) - serverMux.HandleFunc("/convert", s.handle(s.convert)) - server := &http.Server{ - Handler: serverMux, - } - g.Go(func() error { - <-gctx.Done() - // allow a timeout for graceful shutdown - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) - defer cancel() + mgr.GetWebhookServer().Register("/mutate", cmadmission.NewCustomMutationWebhook(s.MutationWebhook)) - if err := server.Shutdown(ctx); err != nil { - return err - } - return nil - }) - g.Go(func() error { - if err := server.Serve(s.listener); err != http.ErrServerClosed { - return err - } - return nil - }) + mgr.GetWebhookServer().Register("/validate", cmadmission.NewCustomValidationWebhook(mgr.GetScheme(), s.ValidationWebhook)) - return g.Wait() + return mgr.Start(ctx) } -// Port returns the port number that the webhook listener is listening on -func (s *Server) Port() (int, error) { - if s.listener == nil { - return 0, ErrNotListening - } - tcpAddr, ok := s.listener.Addr().(*net.TCPAddr) - if !ok { - return 0, errors.New("unexpected listen address type (expected tcp)") - } - return tcpAddr.Port, nil -} - -func (s *Server) scheme() *runtime.Scheme { - if s.Scheme == nil { - return defaultScheme - } - return s.Scheme -} - -func (s *Server) validate(ctx context.Context, obj runtime.Object) (runtime.Object, error) { - review, isV1 := obj.(*admissionv1.AdmissionReview) - if !isV1 { - return nil, errors.New("request is not of type apiextensions v1") +func freePort() (int, error) { + l, err := net.ListenTCP("tcp", &net.TCPAddr{ + IP: net.ParseIP("127.0.0.1"), + Port: 0, + }) + if err != nil { + return -1, err } - review.Response = s.ValidationWebhook.Validate(ctx, review.Request) - return review, nil -} + defer l.Close() -func (s *Server) mutate(ctx context.Context, obj runtime.Object) (runtime.Object, error) { - review, isV1 := obj.(*admissionv1.AdmissionReview) - if !isV1 { - return nil, errors.New("request is not of type apiextensions v1") - } - review.Response = s.MutationWebhook.Mutate(ctx, review.Request) - return review, nil + return l.Addr().(*net.TCPAddr).Port, nil } -func (s *Server) convert(_ context.Context, obj runtime.Object) (runtime.Object, error) { - switch review := obj.(type) { - case *apiextensionsv1.ConversionReview: - if review.Request == nil { - return nil, errors.New("review.request was nil") - } - review.Response = s.ConversionWebhook.Convert(review.Request) - return review, nil - default: - return nil, fmt.Errorf("unsupported conversion review type: %T", review) +// Port returns the port number that the webhook listener is listening on +func (s *Server) Port() (int, error) { + if s.ListenAddr == 0 { + return 0, ErrNotListening } -} - -func (s *Server) handle(inner handleFunc) func(w http.ResponseWriter, req *http.Request) { - return func(w http.ResponseWriter, req *http.Request) { - defer req.Body.Close() - - data, err := io.ReadAll(req.Body) - if err != nil { - s.log.Error(err, "failed to read request body") - w.WriteHeader(http.StatusBadRequest) - return - } - - codec := json.NewSerializerWithOptions(json.DefaultMetaFactory, s.scheme(), s.scheme(), json.SerializerOptions{ - Pretty: true, - }) - obj, _, err := codec.Decode(data, nil, nil) - if err != nil { - s.log.Error(err, "failed to decode request body") - w.WriteHeader(http.StatusBadRequest) - return - } - result, err := inner(req.Context(), obj) - if err != nil { - s.log.Error(err, "failed to process webhook request") - w.WriteHeader(http.StatusInternalServerError) - return - } - if err := codec.Encode(result, w); err != nil { - s.log.Error(err, "failed to encode response body") - w.WriteHeader(http.StatusInternalServerError) - return - } - } + return s.ListenAddr, nil } func (s *Server) handleHealthz(w http.ResponseWriter, req *http.Request) { defer req.Body.Close() if s.CertificateSource != nil && !s.CertificateSource.Healthy() { - s.log.V(logf.WarnLevel).Info("Health check failed as CertificateSource is unhealthy") + logf.FromContext(req.Context()).V(logf.WarnLevel).Info("Health check failed as CertificateSource is unhealthy") w.WriteHeader(http.StatusInternalServerError) return } diff --git a/pkg/webhook/server/server_test.go b/pkg/webhook/server/server_test.go deleted file mode 100644 index 0abfd4e65ca..00000000000 --- a/pkg/webhook/server/server_test.go +++ /dev/null @@ -1,82 +0,0 @@ -/* -Copyright 2020 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. -*/ - -package server - -import ( - "context" - "testing" - - logtesting "github.com/go-logr/logr/testing" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" - apiextensionsv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1" - "k8s.io/apimachinery/pkg/runtime" - - "github.com/cert-manager/cert-manager/pkg/webhook/handlers" -) - -func TestConvert(t *testing.T) { - type testCase struct { - name string - in runtime.Object - err string - } - tests := []testCase{ - { - name: "unsupported conversion review type", - in: &apiextensionsv1.CustomResourceDefinition{}, - err: "unsupported conversion review type: *v1.CustomResourceDefinition", - }, - { - name: "unsupported conversion review version", - in: &apiextensionsv1beta1.ConversionReview{ - Request: &apiextensionsv1beta1.ConversionRequest{}, - }, - err: "unsupported conversion review type: *v1beta1.ConversionReview", - }, - { - name: "v1 conversion review", - in: &apiextensionsv1.ConversionReview{ - Request: &apiextensionsv1.ConversionRequest{}, - }, - }, - { - name: "v1 conversion review with nil Request", - in: &apiextensionsv1.ConversionReview{}, - err: "review.request was nil", - }, - } - - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - log := logtesting.NewTestLogger(t) - s := &Server{ - ConversionWebhook: handlers.NewSchemeBackedConverter(log, defaultScheme), - log: log, - } - out, err := s.convert(context.TODO(), tc.in) - if tc.err != "" { - assert.EqualError(t, err, tc.err) - assert.Nil(t, out) - return - } - require.NoError(t, err) - assert.NotNil(t, out) - }) - } -} diff --git a/pkg/webhook/server/tls/dynamic_source.go b/pkg/webhook/server/tls/dynamic_source.go deleted file mode 100644 index 5a6b24c8802..00000000000 --- a/pkg/webhook/server/tls/dynamic_source.go +++ /dev/null @@ -1,269 +0,0 @@ -/* -Copyright 2020 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. -*/ - -package tls - -import ( - "context" - "crypto" - "crypto/tls" - "crypto/x509" - "errors" - "fmt" - "sync" - "time" - - "github.com/go-logr/logr" - "k8s.io/apimachinery/pkg/util/wait" - - cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" - logf "github.com/cert-manager/cert-manager/pkg/logs" - "github.com/cert-manager/cert-manager/pkg/util/pki" - "github.com/cert-manager/cert-manager/pkg/webhook/authority" -) - -// DynamicSource provides certificate data for a golang HTTP server by -// automatically generating certificates using an authority.SignFunc. -type DynamicSource struct { - // DNSNames that will be set on certificates this source produces. - DNSNames []string - - // The authority used to sign certificate templates. - Authority *authority.DynamicAuthority - - log logr.Logger - - cachedCertificate *tls.Certificate - lock sync.Mutex -} - -var _ CertificateSource = &DynamicSource{} - -func (f *DynamicSource) Run(ctx context.Context) error { - f.log = logf.FromContext(ctx) - - // Run the authority in a separate goroutine - authorityErrChan := make(chan error) - go func() { - defer close(authorityErrChan) - authorityErrChan <- f.Authority.Run(ctx) - }() - - nextRenewCh := make(chan time.Time, 1) - - // initially fetch a certificate from the signing CA - interval := time.Second - if err := wait.PollUntil(interval, func() (done bool, err error) { - // check for errors from the authority here too, to prevent retrying - // if the authority has failed to start - select { - case err, ok := <-authorityErrChan: - if err != nil { - return true, fmt.Errorf("failed to run certificate authority: %w", err) - } - if !ok { - return true, context.Canceled - } - default: - // this case avoids blocking if the authority is still running - } - - if err := f.regenerateCertificate(nextRenewCh); err != nil { - f.log.Error(err, "Failed to generate initial serving certificate, retrying...", "interval", interval) - return false, nil - } - return true, nil - }, ctx.Done()); err != nil { - // In case of an error, the stopCh is closed; wait for authorityErrChan to be closed too - <-authorityErrChan - - // If there was an ErrWaitTimeout error, this must be caused by closing stopCh - if errors.Is(err, wait.ErrWaitTimeout) { - return context.Canceled - } - - return err - } - - // watch for changes to the root CA - rotationChan := f.Authority.WatchRotation(ctx.Done()) - renewalChan := func() <-chan struct{} { - ch := make(chan struct{}) - go func() { - defer close(ch) - - var renewMoment time.Time - select { - case renewMoment = <-nextRenewCh: - // We recevieved a renew moment - default: - // This should never happen - panic("Unreacheable") - } - - for { - timer := time.NewTimer(time.Until(renewMoment)) - defer timer.Stop() - - select { - case <-ctx.Done(): - return - case <-timer.C: - // Try to send a message on ch, but also allow for a stop signal or - // a new renewMoment to be received - select { - case <-ctx.Done(): - return - case ch <- struct{}{}: - // Message was sent on channel - case renewMoment = <-nextRenewCh: - // We recevieved a renew moment, next loop iteration will update the timer - } - case renewMoment = <-nextRenewCh: - // We recevieved a renew moment, next loop iteration will update the timer - } - } - }() - return ch - }() - - // check the current certificate every 10s in case it needs updating - if err := wait.PollImmediateUntil(time.Second*10, func() (done bool, err error) { - // regenerate the serving certificate if the root CA has been rotated - select { - // if the authority has stopped for whatever reason, exit and return the error - case err, ok := <-authorityErrChan: - if err != nil { - return true, fmt.Errorf("failed to run certificate authority: %w", err) - } - if !ok { - return true, context.Canceled - } - // trigger regeneration if the root CA has been rotated - case _, ok := <-rotationChan: - if !ok { - return true, context.Canceled - } - f.log.V(logf.InfoLevel).Info("Detected root CA rotation - regenerating serving certificates") - if err := f.regenerateCertificate(nextRenewCh); err != nil { - f.log.Error(err, "Failed to regenerate serving certificate") - // Return an error here and stop the source running - this case should never - // occur, and if it does, indicates some form of internal error. - return false, err - } - // trigger regeneration if a renewal is required - case <-renewalChan: - f.log.V(logf.InfoLevel).Info("cert-manager webhook certificate requires renewal, regenerating", "DNSNames", f.DNSNames) - if err := f.regenerateCertificate(nextRenewCh); err != nil { - f.log.Error(err, "Failed to regenerate serving certificate") - // Return an error here and stop the source running - this case should never - // occur, and if it does, indicates some form of internal error. - return false, err - } - case <-ctx.Done(): - return true, context.Canceled - } - return false, nil - }, ctx.Done()); err != nil { - // In case of an error, the stopCh is closed; wait for all channels to close - <-authorityErrChan - <-rotationChan - <-renewalChan - - // If there was an ErrWaitTimeout error, this must be caused by closing stopCh - if errors.Is(err, wait.ErrWaitTimeout) { - return context.Canceled - } - - return err - } - - return nil -} - -func (f *DynamicSource) GetCertificate(*tls.ClientHelloInfo) (*tls.Certificate, error) { - f.lock.Lock() - defer f.lock.Unlock() - if f.cachedCertificate == nil { - return nil, ErrNotAvailable - } - return f.cachedCertificate, nil -} - -func (f *DynamicSource) Healthy() bool { - return f.cachedCertificate != nil -} - -// regenerateCertificate will trigger the cached certificate and private key to -// be regenerated by requesting a new certificate from the authority. -func (f *DynamicSource) regenerateCertificate(nextRenew chan<- time.Time) error { - f.log.V(logf.DebugLevel).Info("Generating new ECDSA private key") - pk, err := pki.GenerateECPrivateKey(384) - if err != nil { - return err - } - - // create the certificate template to be signed - template := &x509.Certificate{ - Version: 2, - PublicKeyAlgorithm: x509.ECDSA, - PublicKey: pk.Public(), - DNSNames: f.DNSNames, - KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment, - ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, - } - - f.log.V(logf.DebugLevel).Info("Signing new serving certificate") - cert, err := f.Authority.Sign(template) - if err != nil { - return err - } - - f.log.V(logf.DebugLevel).Info("Signed new serving certificate") - - if err := f.updateCertificate(pk, cert, nextRenew); err != nil { - return err - } - return nil -} - -func (f *DynamicSource) updateCertificate(pk crypto.Signer, cert *x509.Certificate, nextRenew chan<- time.Time) error { - f.lock.Lock() - defer f.lock.Unlock() - - pkData, err := pki.EncodePrivateKey(pk, cmapi.PKCS8) - if err != nil { - return err - } - - certData, err := pki.EncodeX509(cert) - if err != nil { - return err - } - - bundle, err := tls.X509KeyPair(certData, pkData) - if err != nil { - return err - } - - f.cachedCertificate = &bundle - certDuration := cert.NotAfter.Sub(cert.NotBefore) - // renew the certificate 1/3 of the time before its expiry - nextRenew <- cert.NotAfter.Add(certDuration / -3) - f.log.V(logf.InfoLevel).Info("Updated cert-manager webhook TLS certificate", "DNSNames", f.DNSNames) - - return nil -} diff --git a/test/acme/dns/fixture.go b/test/acme/fixture.go similarity index 71% rename from test/acme/dns/fixture.go rename to test/acme/fixture.go index 9bd1116b352..76bb34691f4 100644 --- a/test/acme/dns/fixture.go +++ b/test/acme/fixture.go @@ -24,26 +24,26 @@ import ( "time" apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" + utilruntime "k8s.io/apimachinery/pkg/util/runtime" "k8s.io/client-go/kubernetes" "sigs.k8s.io/controller-runtime/pkg/envtest" "github.com/cert-manager/cert-manager/pkg/acme/webhook" - "github.com/cert-manager/cert-manager/test/internal/apiserver" + "github.com/cert-manager/cert-manager/test/apiserver" ) func init() { vFlag := flag.Lookup("v") if vFlag != nil { - flag.Set("alsologtostderr", fmt.Sprintf("%t", true)) - vFlag.Value.Set("12") + utilruntime.Must(flag.Set("alsologtostderr", fmt.Sprintf("%t", true))) + utilruntime.Must(vFlag.Value.Set("12")) } } type fixture struct { // testSolver is the actual DNS solver that is under test. // It is set when calling the NewFixture function. - testSolver webhook.Solver - + testSolver webhook.Solver resolvedFQDN string resolvedZone string allowAmbientCredentials bool @@ -72,40 +72,19 @@ type fixture struct { setupLock sync.Mutex environment *envtest.Environment - clientset kubernetes.Interface + // An admin user for running kubectl commands against this envtest + // environment. + adminUser *envtest.AuthenticatedUser + clientset kubernetes.Interface pollInterval time.Duration propagationLimit time.Duration } -func (f *fixture) setup(t *testing.T) func() { - f.setupLock.Lock() - defer f.setupLock.Unlock() - - if err := validate(f); err != nil { - t.Fatalf("error validating test fixture configuration: %v", err) - } - - env, stopFunc := apiserver.RunBareControlPlane(t) - f.environment = env - - cl, err := kubernetes.NewForConfig(env.Config) - if err != nil { - t.Fatal(err) - } - f.clientset = cl - - stopCh := make(chan struct{}) - f.testSolver.Initialize(env.Config, stopCh) - - return func() { - close(stopCh) - stopFunc() - } -} - // RunConformance will execute all conformance tests using the supplied -// configuration +// configuration These conformance tests should be run by all external DNS +// solver webhook implementations, see +// https://github.com/cert-manager/webhook-example func (f *fixture) RunConformance(t *testing.T) { defer f.setup(t)() t.Run("Conformance", func(t *testing.T) { @@ -127,3 +106,45 @@ func (f *fixture) RunExtended(t *testing.T) { t.Run("DeletingOneRecordRetainsOthers", f.TestExtendedDeletingOneRecordRetainsOthers) }) } + +func (f *fixture) setup(t *testing.T) func() { + f.setupLock.Lock() + defer f.setupLock.Unlock() + + if err := validate(f); err != nil { + t.Fatalf("error validating test fixture configuration: %v", err) + } + + env, stopControlPlaneFn := apiserver.RunBareControlPlane(t) + f.environment = env + + // An admin user instance for running kubectl against this envtest + // environment. + // Derived from the envtest global config which is configured with very high + // QPS and Burst settings for rapid interactions with the API server. + adminUser, err := env.AddUser(envtest.User{ + Name: "envtest-admin", + Groups: []string{"system:masters"}, + }, env.Config) + if err != nil { + t.Fatalf("unable to provision admin user: %s", err) + } + f.adminUser = adminUser + + cl, err := kubernetes.NewForConfig(env.Config) + if err != nil { + t.Fatal(err) + } + f.clientset = cl + + stopCh := make(chan struct{}) + + if err := f.testSolver.Initialize(env.Config, stopCh); err != nil { + t.Fatalf("error initializing solver: %v", err) + } + + return func() { + close(stopCh) + stopControlPlaneFn() + } +} diff --git a/test/acme/dns/options.go b/test/acme/options.go similarity index 94% rename from test/acme/dns/options.go rename to test/acme/options.go index 66693eb8757..ee3541ce463 100644 --- a/test/acme/dns/options.go +++ b/test/acme/options.go @@ -32,7 +32,10 @@ import ( type Option func(*fixture) // NewFixture constructs a new *fixture, applying the given Options before -// returning. +// returning. Solver is an implementation of +// https://github.com/cert-manager/cert-manager/blob/v1.11.0/pkg/acme/webhook/webhook.go#L27-L45 +// and could be RFC2136 solver or any of external solvers that run these +// conformance tests. func NewFixture(solver webhook.Solver, opts ...Option) *fixture { f := &fixture{ testSolver: solver, diff --git a/test/acme/dns/server/doc.go b/test/acme/server/doc.go similarity index 100% rename from test/acme/dns/server/doc.go rename to test/acme/server/doc.go diff --git a/test/acme/dns/server/rfc2136.go b/test/acme/server/rfc2136.go similarity index 93% rename from test/acme/dns/server/rfc2136.go rename to test/acme/server/rfc2136.go index 2a5bf4f0bca..4bb4d0475ce 100644 --- a/test/acme/dns/server/rfc2136.go +++ b/test/acme/server/rfc2136.go @@ -19,15 +19,17 @@ package server import ( "fmt" "sync" + "testing" "time" - logf "github.com/cert-manager/cert-manager/pkg/logs" - "github.com/go-logr/logr" "github.com/miekg/dns" + + logf "github.com/cert-manager/cert-manager/pkg/logs" ) type rfc2136Handler struct { + t *testing.T log logr.Logger txtRecords map[string][]string @@ -36,7 +38,7 @@ type rfc2136Handler struct { lock sync.Mutex } -// serveDNS implements github.com/miekg/dns.Handler +// ServeDNS implements github.com/miekg/dns.Handler func (b *rfc2136Handler) ServeDNS(w dns.ResponseWriter, req *dns.Msg) { b.lock.Lock() defer b.lock.Unlock() @@ -44,7 +46,11 @@ func (b *rfc2136Handler) ServeDNS(w dns.ResponseWriter, req *dns.Msg) { m := new(dns.Msg) m.SetReply(req) - defer w.WriteMsg(m) + defer func() { + if err := w.WriteMsg(m); err != nil { + b.t.Errorf("failed to write response: %v", err) + } + }() var zone string if len(req.Question) > 0 { diff --git a/test/acme/dns/server/server.go b/test/acme/server/server.go similarity index 75% rename from test/acme/dns/server/server.go rename to test/acme/server/server.go index a36818b67be..a5bc062b380 100644 --- a/test/acme/dns/server/server.go +++ b/test/acme/server/server.go @@ -21,6 +21,7 @@ import ( "fmt" "net" "sync" + "testing" "time" "github.com/miekg/dns" @@ -33,6 +34,8 @@ const ( ) type BasicServer struct { + T *testing.T + // Zones is a list of DNS zones that this server should accept responses // for. Zones []string @@ -56,27 +59,38 @@ type BasicServer struct { } // Run starts the test DNS server, binding to a random port on 127.0.0.1 -func (b *BasicServer) Run(ctx context.Context) error { - return b.RunWithAddress(ctx, "127.0.0.1:0") +func (b *BasicServer) Run(ctx context.Context, network string) error { + return b.RunWithAddress(ctx, "127.0.0.1:0", network) } // RunWithAddress starts the test DNS server using the specified listen address. -func (b *BasicServer) RunWithAddress(ctx context.Context, listenAddr string) error { +func (b *BasicServer) RunWithAddress(ctx context.Context, listenAddr, network string) error { log := logf.FromContext(ctx, "dnsBasicServer") if listenAddr == "" { return fmt.Errorf("listen address must be provided") } - pc, err := net.ListenPacket("udp", listenAddr) - if err != nil { - return err + lc := net.ListenConfig{} + if network == "tcp" { + listener, err := lc.Listen(ctx, "tcp", listenAddr) + if err != nil { + return err + } + + b.server = &dns.Server{Listener: listener, ReadTimeout: time.Hour, WriteTimeout: time.Hour, MsgAcceptFunc: msgAcceptFunc} + b.listenAddr = listener.Addr().String() + } else { + pc, err := lc.ListenPacket(ctx, "udp", listenAddr) + if err != nil { + return err + } + b.server = &dns.Server{PacketConn: pc, ReadTimeout: time.Hour, WriteTimeout: time.Hour, MsgAcceptFunc: msgAcceptFunc} + b.listenAddr = pc.LocalAddr().String() } - b.listenAddr = pc.LocalAddr().String() log = log.WithValues("address", b.listenAddr) - log.V(logf.InfoLevel).Info("listening on UDP port") + log.V(logf.InfoLevel).Info(fmt.Sprintf("listening on %s port", network)) - b.server = &dns.Server{PacketConn: pc, ReadTimeout: time.Hour, WriteTimeout: time.Hour, MsgAcceptFunc: msgAcceptFunc} if b.EnableTSIG { log.V(logf.DebugLevel).Info("enabling TSIG support") b.server.TsigSecret = map[string]string{b.TSIGKeyName: b.TSIGKeySecret} @@ -84,6 +98,7 @@ func (b *BasicServer) RunWithAddress(ctx context.Context, listenAddr string) err if b.Handler == nil { b.Handler = &rfc2136Handler{ + t: b.T, log: log, txtRecords: make(map[string][]string), zones: b.Zones, @@ -98,9 +113,10 @@ func (b *BasicServer) RunWithAddress(ctx context.Context, listenAddr string) err b.server.NotifyStartedFunc = waitLock.Unlock go func() { log.V(logf.DebugLevel).Info("starting DNS server") - b.server.ActivateAndServe() + if err := b.server.ActivateAndServe(); err != nil { + b.T.Errorf("failed to start DNS server: %v", err) + } log.V(logf.DebugLevel).Info("DNS server exited") - pc.Close() }() waitLock.Lock() defer waitLock.Unlock() diff --git a/test/acme/dns/suite.go b/test/acme/suite.go similarity index 74% rename from test/acme/dns/suite.go rename to test/acme/suite.go index 6cfe5534016..f6579d9d763 100644 --- a/test/acme/dns/suite.go +++ b/test/acme/suite.go @@ -32,7 +32,7 @@ import ( func (f *fixture) TestBasicPresentRecord(t *testing.T) { ns, cleanup := f.setupNamespace(t, "basic-present-record") defer cleanup() - ch := f.buildChallengeRequest(t, ns) + ch := f.buildChallengeRequest(ns) t.Logf("Calling Present with ChallengeRequest: %#v", ch) // present the record @@ -40,12 +40,14 @@ func (f *fixture) TestBasicPresentRecord(t *testing.T) { t.Errorf("expected Present to not error, but got: %v", err) return } - defer f.testSolver.CleanUp(ch) + defer func() { + if err := f.testSolver.CleanUp(ch); err != nil { + t.Errorf("expected CleanUp to not error, but got: %v", err) + } + }() // wait until the record has propagated - if err := wait.PollUntil(f.getPollInterval(), - f.recordHasPropagatedCheck(ch.ResolvedFQDN, ch.Key), - closingStopCh(f.getPropagationLimit())); err != nil { + if err := wait.PollUntilContextTimeout(t.Context(), f.getPollInterval(), f.getPropagationLimit(), true, f.recordHasPropagatedCheck(ch.ResolvedFQDN, ch.Key)); err != nil { t.Errorf("error waiting for DNS record propagation: %v", err) return } @@ -56,9 +58,7 @@ func (f *fixture) TestBasicPresentRecord(t *testing.T) { } // wait until the record has been deleted - if err := wait.PollUntil(f.getPollInterval(), - f.recordHasBeenDeletedCheck(ch.ResolvedFQDN, ch.Key), - closingStopCh(f.getPropagationLimit())); err != nil { + if err := wait.PollUntilContextTimeout(t.Context(), f.getPollInterval(), f.getPropagationLimit(), true, f.recordHasBeenDeletedCheck(ch.ResolvedFQDN, ch.Key)); err != nil { t.Errorf("error waiting for record to be deleted: %v", err) return } @@ -75,8 +75,8 @@ func (f *fixture) TestExtendedDeletingOneRecordRetainsOthers(t *testing.T) { ns, cleanup := f.setupNamespace(t, "extended-supports-multiple-same-domain") defer cleanup() - ch := f.buildChallengeRequest(t, ns) - ch2 := f.buildChallengeRequest(t, ns) + ch := f.buildChallengeRequest(ns) + ch2 := f.buildChallengeRequest(ns) ch2.Key = "anothertestingkey" // present the first record @@ -84,22 +84,33 @@ func (f *fixture) TestExtendedDeletingOneRecordRetainsOthers(t *testing.T) { t.Errorf("expected Present to not error, but got: %v", err) return } - defer f.testSolver.CleanUp(ch) + defer func() { + if err := f.testSolver.CleanUp(ch); err != nil { + t.Errorf("expected CleanUp to not error, but got: %v", err) + } + }() // present the second record if err := f.testSolver.Present(ch2); err != nil { t.Errorf("expected Present to not error, but got: %v", err) return } - defer f.testSolver.CleanUp(ch2) + defer func() { + if err := f.testSolver.CleanUp(ch2); err != nil { + t.Errorf("expected CleanUp to not error, but got: %v", err) + } + }() // wait until all records have propagated - if err := wait.PollUntil(f.getPollInterval(), + if err := wait.PollUntilContextTimeout( + t.Context(), + f.getPollInterval(), + f.getPropagationLimit(), + true, allConditions( f.recordHasPropagatedCheck(ch.ResolvedFQDN, ch.Key), f.recordHasPropagatedCheck(ch2.ResolvedFQDN, ch2.Key), - ), - closingStopCh(f.getPropagationLimit())); err != nil { + )); err != nil { t.Errorf("error waiting for DNS record propagation: %v", err) return } @@ -110,12 +121,15 @@ func (f *fixture) TestExtendedDeletingOneRecordRetainsOthers(t *testing.T) { } // wait until the second record has been deleted and the first one remains - if err := wait.PollUntil(f.getPollInterval(), + if err := wait.PollUntilContextTimeout( + t.Context(), + f.getPollInterval(), + f.getPropagationLimit(), + true, allConditions( f.recordHasBeenDeletedCheck(ch2.ResolvedFQDN, ch2.Key), f.recordHasPropagatedCheck(ch.ResolvedFQDN, ch.Key), - ), - closingStopCh(f.getPropagationLimit())); err != nil { + )); err != nil { t.Errorf("error waiting for DNS record propagation: %v", err) return } diff --git a/test/acme/dns/util.go b/test/acme/util.go similarity index 71% rename from test/acme/dns/util.go rename to test/acme/util.go index 9caaf0a9539..6f96815d890 100644 --- a/test/acme/dns/util.go +++ b/test/acme/util.go @@ -14,6 +14,8 @@ See the License for the specific language governing permissions and limitations under the License. */ +// package dns contains a framework for testing ACME DNS solver implementations. +// Used by both internal and external solvers. package dns import ( @@ -21,6 +23,7 @@ import ( "fmt" "os" "path/filepath" + "slices" "testing" "time" @@ -40,10 +43,15 @@ var ( func (f *fixture) setupNamespace(t *testing.T, name string) (string, func()) { ns := &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: name}} - if _, err := f.clientset.CoreV1().Namespaces().Create(context.TODO(), ns, metav1.CreateOptions{}); err != nil { + if _, err := f.clientset.CoreV1().Namespaces().Create(t.Context(), ns, metav1.CreateOptions{}); err != nil { t.Fatalf("error creating test namespace %q: %v", name, err) } + kubectl, err := f.adminUser.Kubectl() + if err != nil { + t.Fatalf("enable to create kubectl instance: %s", err) + } + if f.kubectlManifestsPath != "" { if err := filepath.Walk(f.kubectlManifestsPath, func(path string, info os.FileInfo, err error) error { if err != nil { @@ -59,8 +67,7 @@ func (f *fixture) setupNamespace(t *testing.T, name string) (string, func()) { t.Logf("skipping file %q with unrecognised extension", path) return nil } - - _, _, err = f.environment.ControlPlane.KubeCtl().Run("apply", "--namespace", name, "-f", path) + _, _, err = kubectl.Run("apply", "--namespace", name, "-f", path) if err != nil { return err } @@ -76,11 +83,13 @@ func (f *fixture) setupNamespace(t *testing.T, name string) (string, func()) { } return name, func() { - f.clientset.CoreV1().Namespaces().Delete(context.TODO(), name, metav1.DeleteOptions{}) + if err := f.clientset.CoreV1().Namespaces().Delete(t.Context(), name, metav1.DeleteOptions{}); err != nil { + t.Fatalf("error deleting test namespace %q: %v", name, err) + } } } -func (f *fixture) buildChallengeRequest(t *testing.T, ns string) *whapi.ChallengeRequest { +func (f *fixture) buildChallengeRequest(ns string) *whapi.ChallengeRequest { return &whapi.ChallengeRequest{ ResourceNamespace: ns, ResolvedFQDN: f.resolvedFQDN, @@ -92,10 +101,10 @@ func (f *fixture) buildChallengeRequest(t *testing.T, ns string) *whapi.Challeng } } -func allConditions(c ...wait.ConditionFunc) wait.ConditionFunc { - return func() (bool, error) { +func allConditions(c ...wait.ConditionWithContextFunc) wait.ConditionWithContextFunc { + return func(ctx context.Context) (bool, error) { for _, fn := range c { - ok, err := fn() + ok, err := fn(ctx) if err != nil || !ok { return ok, err } @@ -104,24 +113,15 @@ func allConditions(c ...wait.ConditionFunc) wait.ConditionFunc { } } -func closingStopCh(t time.Duration) <-chan struct{} { - stopCh := make(chan struct{}) - go func() { - defer close(stopCh) - <-time.After(t) - }() - return stopCh -} - -func (f *fixture) recordHasPropagatedCheck(fqdn, value string) func() (bool, error) { - return func() (bool, error) { - return util.PreCheckDNS(fqdn, value, []string{f.testDNSServer}, *f.useAuthoritative) +func (f *fixture) recordHasPropagatedCheck(fqdn, value string) func(ctx context.Context) (bool, error) { + return func(ctx context.Context) (bool, error) { + return util.PreCheckDNS(ctx, fqdn, value, []string{f.testDNSServer}, *f.useAuthoritative) } } -func (f *fixture) recordHasBeenDeletedCheck(fqdn, value string) func() (bool, error) { - return func() (bool, error) { - msg, err := util.DNSQuery(fqdn, dns.TypeTXT, []string{f.testDNSServer}, *f.useAuthoritative) +func (f *fixture) recordHasBeenDeletedCheck(fqdn, value string) func(ctx context.Context) (bool, error) { + return func(ctx context.Context) (bool, error) { + msg, err := util.DNSQuery(ctx, fqdn, dns.TypeTXT, []string{f.testDNSServer}, *f.useAuthoritative) if err != nil { return false, err } @@ -136,10 +136,8 @@ func (f *fixture) recordHasBeenDeletedCheck(fqdn, value string) func() (bool, er if !ok { continue } - for _, k := range txt.Txt { - if k == value { - return false, nil - } + if slices.Contains(txt.Txt, value) { + return false, nil } } return true, nil diff --git a/test/internal/apiserver/apiserver.go b/test/apiserver/apiserver.go similarity index 94% rename from test/internal/apiserver/apiserver.go rename to test/apiserver/apiserver.go index 6d623300710..9520f0ac135 100644 --- a/test/internal/apiserver/apiserver.go +++ b/test/apiserver/apiserver.go @@ -14,6 +14,8 @@ See the License for the specific language governing permissions and limitations under the License. */ +// package apiserver contains functionality to set up a Kubernetes control plane +// for tests. package apiserver import ( diff --git a/test/internal/apiserver/envs.go b/test/apiserver/envs.go similarity index 100% rename from test/internal/apiserver/envs.go rename to test/apiserver/envs.go diff --git a/test/e2e/bin/cloudflare-clean/main.go b/test/e2e/bin/cloudflare-clean/main.go index d7bdd413da3..18da2ef39ee 100644 --- a/test/e2e/bin/cloudflare-clean/main.go +++ b/test/e2e/bin/cloudflare-clean/main.go @@ -19,12 +19,17 @@ package main import ( "context" "flag" + "fmt" "log" "time" - cf "github.com/cloudflare/cloudflare-go" + cf "github.com/cloudflare/cloudflare-go/v6" + "github.com/cloudflare/cloudflare-go/v6/dns" + "github.com/cloudflare/cloudflare-go/v6/option" + "github.com/cloudflare/cloudflare-go/v6/packages/pagination" + cfz "github.com/cloudflare/cloudflare-go/v6/zones" - "github.com/cert-manager/cert-manager/cmd/util" + "github.com/cert-manager/cert-manager/internal/cmd/util" ) var ( @@ -37,34 +42,45 @@ var ( ) func main() { - stopCh, exit := util.SetupExitHandler(util.GracefulShutdown) + ctx, exit := util.SetupExitHandler(context.Background(), util.GracefulShutdown) defer exit() // This function might call os.Exit, so defer last flag.Parse() - ctx := util.ContextWithStopCh(context.Background(), stopCh) + if err := Main(ctx); err != nil { + log.Print(err) + util.SetExitCode(err) + } +} - cl, err := cf.New(*apiKey, *email) - if err != nil { - log.Fatalf("error creating cloudflare client: %v", err) +func Main(ctx context.Context) error { + cl := cf.NewClient( + option.WithAPIKey(*apiKey), + option.WithAPIEmail(*email), + ) + if cl.Zones == nil || cl.DNS == nil { + return fmt.Errorf("error creating cloudflare client; check permissions related to the client.") } - zones, err := cl.ListZones(ctx, *zoneName) + zones, err := convertPagerToSlice(cl.Zones.ListAutoPaging(ctx, cfz.ZoneListParams{ + Name: cf.F(*zoneName), + })) if err != nil { - log.Fatalf("error listing zones: %v", err) + return fmt.Errorf("error listing zones: %v", err) } if len(zones) == 0 { - log.Fatalf("could not find zone with name %q", *zoneName) + return fmt.Errorf("could not find zone with name %q", *zoneName) } if len(zones) > 1 { - log.Fatalf("found multiple zones for name %q", *zoneName) + return fmt.Errorf("found multiple zones for name %q", *zoneName) } zone := zones[0] - rrs, err := cl.DNSRecords(ctx, zone.ID, cf.DNSRecord{ - Type: "TXT", - }) + rrs, err := convertPagerToSlice(cl.DNS.Records.ListAutoPaging(ctx, dns.RecordListParams{ + ZoneID: cf.F(zone.ID), + Type: cf.F(dns.RecordListParamsTypeTXT), + })) if err != nil { - log.Fatalf("error listing TXT records in zone: %v", err) + return fmt.Errorf("error listing TXT records in zone: %v", err) } log.Printf("Evaluating %d records", len(rrs)) @@ -84,7 +100,9 @@ func main() { continue } - err := cl.DeleteDNSRecord(ctx, rr.ZoneID, rr.ID) + _, err := cl.DNS.Records.Delete(ctx, rr.ID, dns.RecordDeleteParams{ + ZoneID: cf.F(zone.ID), + }) if err != nil { log.Printf("Error deleting record: %v", err) errs = append(errs, err) @@ -95,16 +113,18 @@ func main() { } if len(errs) > 0 { - log.Fatalf("Encountered %d errors whilst cleaning up zone", len(errs)) + return fmt.Errorf("encountered %d errors whilst cleaning up zone", len(errs)) } log.Print() log.Printf("Skipped: %d", skipped) log.Printf("Deleted: %d", deleted) log.Printf("Cleanup complete!") + + return nil } -func shouldDelete(rr cf.DNSRecord) bool { +func shouldDelete(rr dns.RecordResponse) bool { // be extra safe about only deleting TXT records if rr.Type != "TXT" { return false @@ -120,3 +140,17 @@ func shouldDelete(rr cf.DNSRecord) bool { } return true } + +func convertPagerToSlice[T any](arr *pagination.V4PagePaginationArrayAutoPager[T]) ([]T, error) { + var rtArr []T + + for arr.Next() { + rtArr = append(rtArr, arr.Current()) + } + + if arr.Err() != nil { + return nil, arr.Err() + } + + return rtArr, nil +} diff --git a/test/e2e/charts/vault/Chart.yaml b/test/e2e/charts/vault/Chart.yaml deleted file mode 100644 index e990eb8e19b..00000000000 --- a/test/e2e/charts/vault/Chart.yaml +++ /dev/null @@ -1,4 +0,0 @@ -apiVersion: v1 -description: A Helm chart for Kubernetes -name: vault -version: 0.1.0 diff --git a/test/e2e/charts/vault/templates/_helpers.tpl b/test/e2e/charts/vault/templates/_helpers.tpl deleted file mode 100644 index f0d83d2edba..00000000000 --- a/test/e2e/charts/vault/templates/_helpers.tpl +++ /dev/null @@ -1,16 +0,0 @@ -{{/* vim: set filetype=mustache: */}} -{{/* -Expand the name of the chart. -*/}} -{{- define "name" -}} -{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} -{{- end -}} - -{{/* -Create a default fully qualified app name. -We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). -*/}} -{{- define "fullname" -}} -{{- $name := default .Chart.Name .Values.nameOverride -}} -{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} -{{- end -}} diff --git a/test/e2e/charts/vault/templates/vault-config.yaml b/test/e2e/charts/vault/templates/vault-config.yaml deleted file mode 100644 index bda91bdc301..00000000000 --- a/test/e2e/charts/vault/templates/vault-config.yaml +++ /dev/null @@ -1,9 +0,0 @@ -apiVersion: v1 -kind: ConfigMap -metadata: - name: vault-config - labels: - app: vault -data: - config.json: | - {{ .Values.vault.config | toJson }} diff --git a/test/e2e/charts/vault/templates/vault-deployment.yaml b/test/e2e/charts/vault/templates/vault-deployment.yaml deleted file mode 100644 index e53f0479fb6..00000000000 --- a/test/e2e/charts/vault/templates/vault-deployment.yaml +++ /dev/null @@ -1,62 +0,0 @@ ---- -apiVersion: apps/v1 -kind: Deployment -metadata: - labels: - app: vault - name: vault -spec: - replicas: 1 - strategy: - type: Recreate - selector: - matchLabels: - app: {{ template "name" . }} - release: {{ .Release.Name }} - template: - metadata: - labels: - app: vault - release: {{ .Release.Name }} - spec: - containers: - - name: vault - image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" - imagePullPolicy: Never - command: ["vault", "server", "-dev", "-dev-listen-address=[::]:8202", "-config", "/vault/config/config.json"] - # command: ["/bin/sh", "-c", "sleep 9999"] - ports: - - containerPort: 8200 - name: vaultport - protocol: TCP - resources: - requests: - cpu: 50m - memory: 64Mi - limits: - cpu: 200m - memory: 256Mi - securityContext: - capabilities: - add: - - IPC_LOCK - env: - - name: VAULT_DEV_ROOT_TOKEN_ID - value: vault-root-token - readinessProbe: - httpGet: - path: /v1/sys/health - port: 8200 - scheme: HTTPS - volumeMounts: - - name: vault-config - mountPath: /vault/config - - name: vault-tls - mountPath: /vault/tls - volumes: - - name: vault-config - configMap: - name: vault-config - - name: vault-tls - secret: - secretName: vault-tls diff --git a/test/e2e/charts/vault/templates/vault-secret.yaml b/test/e2e/charts/vault/templates/vault-secret.yaml deleted file mode 100644 index e0d6be7f6ba..00000000000 --- a/test/e2e/charts/vault/templates/vault-secret.yaml +++ /dev/null @@ -1,8 +0,0 @@ -apiVersion: v1 -kind: Secret -metadata: - name: vault-tls -type: Opaque -data: - server.crt: {{ .Values.vault.publicKey }} - server.key: {{ .Values.vault.privateKey }} diff --git a/test/e2e/charts/vault/templates/vault-service.yaml b/test/e2e/charts/vault/templates/vault-service.yaml deleted file mode 100644 index 9029e39f1ae..00000000000 --- a/test/e2e/charts/vault/templates/vault-service.yaml +++ /dev/null @@ -1,12 +0,0 @@ -apiVersion: v1 -kind: Service -metadata: - name: vault - labels: - app: vault -spec: - ports: - - name: vault - port: 8200 - selector: - app: vault diff --git a/test/e2e/charts/vault/values.yaml b/test/e2e/charts/vault/values.yaml deleted file mode 100644 index 169a0b5295f..00000000000 --- a/test/e2e/charts/vault/values.yaml +++ /dev/null @@ -1,18 +0,0 @@ -image: - repository: local/vault - tag: local - -vault: - publicKey: - privateKey: - - config: - listener: - tcp: - address: '[::]:8200' - cluster_address: '[::]:8201' - tls_disable: false - tls_prefer_server_cipher_suites: true - tls_cipher_suites: TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,TLS_RSA_WITH_AES_128_GCM_SHA256,TLS_RSA_WITH_AES_256_GCM_SHA384,TLS_RSA_WITH_AES_128_CBC_SHA,TLS_RSA_WITH_AES_256_CBC_SHA - tls_cert_file: /vault/tls/server.crt - tls_key_file: /vault/tls/server.key diff --git a/test/e2e/e2e.go b/test/e2e/e2e.go index 5ec334136b1..26c34a38eb1 100644 --- a/test/e2e/e2e.go +++ b/test/e2e/e2e.go @@ -17,39 +17,78 @@ limitations under the License. package e2e import ( + "context" + "encoding/json" "os" "path" "github.com/onsi/ginkgo/v2" - "github.com/cert-manager/cert-manager/test/e2e/framework" - "github.com/cert-manager/cert-manager/test/e2e/framework/addon" - "github.com/cert-manager/cert-manager/test/e2e/framework/log" + "github.com/cert-manager/cert-manager/e2e-tests/framework" + "github.com/cert-manager/cert-manager/e2e-tests/framework/addon" + "github.com/cert-manager/cert-manager/e2e-tests/framework/log" ) var cfg = framework.DefaultConfig -var _ = ginkgo.SynchronizedBeforeSuite(func() []byte { +// isGinkgoProcessNumberOne is true if this is the first ginkgo process to run. +// Only the first ginkgo process will run the global addon Setup, Provision & +// Deprovision code. +// All other ginkgo processes will only run the global addon Setup code using +// the data transferred from the Setup function on the first ginkgo process. +var isGinkgoProcessNumberOne = false + +var _ = ginkgo.SynchronizedBeforeSuite(func(ctx context.Context) []byte { addon.InitGlobals(cfg) - err := addon.ProvisionGlobals(cfg) + isGinkgoProcessNumberOne = true + + // We first setup the global addons, but do not provision them yet. + // This is because we need to transfer the data from ginkgo process #1 + // to the other ginkgo processes. + toBeTransferred, err := addon.SetupGlobalsPrimary(ctx, cfg) if err != nil { framework.Failf("Error provisioning global addons: %v", err) } - return nil -}, func([]byte) { - addon.InitGlobals(cfg) + encodedData, err := json.Marshal(toBeTransferred) + if err != nil { + framework.Failf("Error encoding global addon data: %v", err) + } - err := addon.SetupGlobals(cfg) + return encodedData +}, func(ctx context.Context, encodedData []byte) { + transferredData := []addon.AddonTransferableData{} + err := json.Unmarshal(encodedData, &transferredData) if err != nil { - framework.Failf("Error configuring global addons: %v", err) + framework.Failf("Error decoding global addon data: %v", err) + } + + if isGinkgoProcessNumberOne { + // For ginkgo process #1, we need to run ProvisionGlobals to + // actually provision the global addons. + err = addon.ProvisionGlobals(ctx, cfg) + if err != nil { + framework.Failf("Error configuring global addons: %v", err) + } + } else { + // For gingko process #2 and above, we need to run Setup with + // the Setup data returned by ginkgo process #1. + addon.InitGlobals(cfg) + + err := addon.SetupGlobalsNonPrimary(ctx, cfg, transferredData) + if err != nil { + framework.Failf("Error provisioning global addons: %v", err) + } } }) -var _ = ginkgo.SynchronizedAfterSuite(func() {}, func() { +var _ = ginkgo.SynchronizedAfterSuite(func(ctx context.Context) { + // Reset the isGinkgoProcessNumberOne flag to false for the next run (when --repeat flag is used) + isGinkgoProcessNumberOne = false +}, func(ctx context.Context) { ginkgo.By("Retrieving logs for global addons") - globalLogs, err := addon.GlobalLogs() + globalLogs, err := addon.GlobalLogs(ctx) if err != nil { log.Logf("Failed to retrieve global addon logs: " + err.Error()) } @@ -72,7 +111,7 @@ var _ = ginkgo.SynchronizedAfterSuite(func() {}, func() { } ginkgo.By("Cleaning up the provisioned globals") - err = addon.DeprovisionGlobals(cfg) + err = addon.DeprovisionGlobals(ctx, cfg) if err != nil { framework.Failf("Error deprovisioning global addons: %v", err) } diff --git a/test/e2e/e2e_test.go b/test/e2e/e2e_test.go index 438d2f1f88f..065ca996548 100644 --- a/test/e2e/e2e_test.go +++ b/test/e2e/e2e_test.go @@ -26,20 +26,23 @@ import ( "github.com/onsi/ginkgo/v2" "github.com/onsi/gomega" "k8s.io/apimachinery/pkg/util/wait" + ctrl "sigs.k8s.io/controller-runtime" - "github.com/cert-manager/cert-manager/pkg/logs" - _ "github.com/cert-manager/cert-manager/test/e2e/suite" + logf "github.com/cert-manager/cert-manager/pkg/logs" + + _ "github.com/cert-manager/cert-manager/e2e-tests/suite" ) func init() { - logs.InitLogs(flag.CommandLine) + logf.InitLogs() cfg.AddFlags(flag.CommandLine) + ctrl.SetLogger(logf.Log) wait.ForeverTestTimeout = time.Second * 60 } func TestE2E(t *testing.T) { - defer logs.FlushLogs() + defer logf.FlushLogs() gomega.RegisterFailHandler(ginkgo.Fail) diff --git a/test/e2e/framework/addon/base/base.go b/test/e2e/framework/addon/base/base.go index 22f768211b1..1ea108251d7 100644 --- a/test/e2e/framework/addon/base/base.go +++ b/test/e2e/framework/addon/base/base.go @@ -19,11 +19,15 @@ limitations under the License. package base import ( + "context" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/rest" - "github.com/cert-manager/cert-manager/test/e2e/framework/config" - "github.com/cert-manager/cert-manager/test/e2e/framework/helper" - "github.com/cert-manager/cert-manager/test/e2e/framework/util" + "github.com/cert-manager/cert-manager/e2e-tests/framework/addon/internal" + "github.com/cert-manager/cert-manager/e2e-tests/framework/config" + "github.com/cert-manager/cert-manager/e2e-tests/framework/helper" + "github.com/cert-manager/cert-manager/e2e-tests/framework/util" ) type Base struct { @@ -33,12 +37,17 @@ type Base struct { details *Details } +var _ internal.Addon = &Base{} + // Details return the details about the certmanager instance deployed type Details struct { // Config is exposed here to make it easier for upstream consumers to access // the global configuration. Config *config.Config + // KubeConfig is the loaded Kubernetes configuration for addons to use. + KubeConfig *rest.Config + // KubeClient is a configured Kubernetes clientset for addons to use. KubeClient kubernetes.Interface } @@ -49,30 +58,35 @@ func (d *Details) Helper() *helper.Helper { } } -func (b *Base) Setup(c *config.Config) error { - kubeConfig, err := util.LoadConfig(c.KubeConfig, c.KubeContext) +func (b *Base) Setup(_ context.Context, cfg *config.Config, _ ...internal.AddonTransferableData) (internal.AddonTransferableData, error) { + kubeConfig, err := util.LoadConfig(cfg.KubeConfig, cfg.KubeContext) if err != nil { - return err + return nil, err } + kubeConfig.Burst = 9000 + kubeConfig.QPS = 9000 + kubeClientset, err := kubernetes.NewForConfig(kubeConfig) if err != nil { - return err + return nil, err } b.details = &Details{ - Config: c, + Config: cfg, + + KubeConfig: kubeConfig, KubeClient: kubeClientset, } - return nil + return nil, nil } -func (b *Base) Provision() error { +func (b *Base) Provision(_ context.Context) error { return nil } -func (b *Base) Deprovision() error { +func (b *Base) Deprovision(_ context.Context) error { return nil } diff --git a/test/e2e/framework/addon/chart/addon.go b/test/e2e/framework/addon/chart/addon.go index c4d916f02f3..cfec3ccfc31 100644 --- a/test/e2e/framework/addon/chart/addon.go +++ b/test/e2e/framework/addon/chart/addon.go @@ -17,6 +17,7 @@ limitations under the License. package chart import ( + "bytes" "context" "fmt" "io" @@ -27,8 +28,9 @@ import ( corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "github.com/cert-manager/cert-manager/test/e2e/framework/addon/base" - "github.com/cert-manager/cert-manager/test/e2e/framework/config" + "github.com/cert-manager/cert-manager/e2e-tests/framework/addon/base" + "github.com/cert-manager/cert-manager/e2e-tests/framework/addon/internal" + "github.com/cert-manager/cert-manager/e2e-tests/framework/config" ) // Chart is a generic Helm chart addon for the test environment @@ -68,6 +70,19 @@ type Chart struct { // before installing. // This should only be set to true when the ChartName is a local path on disk. UpdateDeps bool + + // repository source of this Chart + Repo Repo +} + +var _ internal.Addon = &Chart{} + +type Repo struct { + // name of the repository + Name string + + // source URL of the repository + Url string } // StringTuple is a tuple of strings, used to create ordered maps @@ -85,56 +100,60 @@ type Details struct { Namespace string } -func (c *Chart) Setup(cfg *config.Config) error { +func (c *Chart) Setup(_ context.Context, cfg *config.Config, _ ...internal.AddonTransferableData) (internal.AddonTransferableData, error) { var err error c.config = cfg if c.config.Addons.Helm.Path == "" { - return fmt.Errorf("--helm-binary-path must be set") + return nil, fmt.Errorf("--helm-binary-path must be set") } c.home, err = os.MkdirTemp("", "helm-chart-install") if err != nil { - return err + return nil, err } - return nil + return nil, nil } // Provision an instance of tiller-deploy -func (c *Chart) Provision() error { +func (c *Chart) Provision(ctx context.Context) error { + if len(c.Repo.Name) > 0 && len(c.Repo.Url) > 0 { + err := c.addRepo(ctx) + if err != nil { + return fmt.Errorf("error adding helm repo: %v", err) + } + } + if c.UpdateDeps { - err := c.runDepUpdate() + err := c.runDepUpdate(ctx) if err != nil { return fmt.Errorf("error updating helm chart dependencies: %v", err) } } - err := c.runInstall() + err := c.runInstall(ctx) if err != nil { return fmt.Errorf("error install helm chart: %v", err) } - err = c.Base.Details().Helper().WaitForAllPodsRunningInNamespace(c.Namespace) - if err != nil { - return err - } - return nil } -func (c *Chart) runDepUpdate() error { - err := c.buildHelmCmd("dep", "update", c.ChartName).Run() +func (c *Chart) runDepUpdate(ctx context.Context) error { + err := c.buildHelmCmd(ctx, "dep", "update", c.ChartName).Run() if err != nil { return err } return nil } -func (c *Chart) runInstall() error { - args := []string{"install", c.ReleaseName, c.ChartName, +func (c *Chart) runInstall(ctx context.Context) error { + args := []string{"upgrade", c.ReleaseName, c.ChartName, + "--install", "--wait", "--namespace", c.Namespace, + "--create-namespace", "--version", c.ChartVersion} for _, v := range c.Values { @@ -145,73 +164,31 @@ func (c *Chart) runInstall() error { args = append(args, "--set", fmt.Sprintf("%s=%s", s.Key, s.Value)) } - cmd := c.buildHelmCmd(args...) - cmd.Stdout = nil - out, err := cmd.StdoutPipe() - if err != nil { - return err - } - defer out.Close() - - err = cmd.Run() - if err != nil { - _, err2 := io.Copy(os.Stdout, out) - if err2 != nil { - return fmt.Errorf("cmd.Run: %v: io.Copy: %v", err, err2) - } + cmd := c.buildHelmCmd(ctx, args...) - return fmt.Errorf("cmd.Run: %v", err) - } - - return nil + return cmd.Run() } -func (c *Chart) buildHelmCmd(args ...string) *exec.Cmd { +func (c *Chart) buildHelmCmd(ctx context.Context, args ...string) *exec.Cmd { args = append([]string{ "--kubeconfig", c.config.KubeConfig, "--kube-context", c.config.KubeContext, }, args...) - cmd := exec.Command(c.config.Addons.Helm.Path, args...) + cmd := exec.CommandContext(ctx, c.config.Addons.Helm.Path, args...) cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr return cmd } -func (c *Chart) getHelmVersion() (string, error) { - cmd := c.buildHelmCmd("version", "--template", "{{.Client.Version}}") - cmd.Stdout = nil - out, err := cmd.StdoutPipe() - if err != nil { - return "", err - } - defer out.Close() - - err = cmd.Run() - if err != nil { - return "", err - } - - outBytes, err := io.ReadAll(out) - if err != nil { - return "", err - } - - return string(outBytes), nil -} - -// Deprovision the deployed instance of tiller-deploy -func (c *Chart) Deprovision() error { - cmd := c.buildHelmCmd("delete", "--namespace", c.Namespace, c.ReleaseName) - cmd.Stdout = nil - out, err := cmd.StdoutPipe() - if err != nil { - return err - } - defer out.Close() +// Deprovision the deployed chart +func (c *Chart) Deprovision(ctx context.Context) error { + cmd := c.buildHelmCmd(ctx, "delete", "--namespace", c.Namespace, c.ReleaseName) + stdoutBuf := &bytes.Buffer{} + cmd.Stdout = stdoutBuf - err = cmd.Run() + err := cmd.Run() if err != nil { - _, err2 := io.Copy(os.Stdout, out) + _, err2 := io.Copy(os.Stdout, stdoutBuf) if err2 != nil { return fmt.Errorf("cmd.Run: %v: io.Copy: %v", err, err2) } @@ -243,26 +220,23 @@ func (c *Chart) SupportsGlobal() bool { // We can't run in global mode if the release name is not set, as there's // no way for us to communicate the generated release name to other test // runners when running in parallel mode. - if c.ReleaseName == "" { - return false - } - - return true + return c.ReleaseName != "" } -func (c *Chart) Logs() (map[string]string, error) { +func (c *Chart) Logs(ctx context.Context) (map[string]string, error) { kc := c.Base.Details().KubeClient - oldLabelPods, err := kc.CoreV1().Pods(c.Namespace).List(context.TODO(), metav1.ListOptions{LabelSelector: "release=" + c.ReleaseName}) + oldLabelPods, err := kc.CoreV1().Pods(c.Namespace).List(ctx, metav1.ListOptions{LabelSelector: "release=" + c.ReleaseName}) if err != nil { return nil, err } // also check pods with the new style labels used in the cert-manager chart - newLabelPods, err := kc.CoreV1().Pods(c.Namespace).List(context.TODO(), metav1.ListOptions{LabelSelector: "app.kubernetes.io/instance=" + c.ReleaseName}) + newLabelPods, err := kc.CoreV1().Pods(c.Namespace).List(ctx, metav1.ListOptions{LabelSelector: "app.kubernetes.io/instance=" + c.ReleaseName}) if err != nil { return nil, err } - podList := append(oldLabelPods.Items, newLabelPods.Items...) + podList := append([]corev1.Pod(nil), oldLabelPods.Items...) + podList = append(podList, newLabelPods.Items...) out := make(map[string]string) for _, pod := range podList { @@ -271,7 +245,7 @@ func (c *Chart) Logs() (map[string]string, error) { resp := kc.CoreV1().Pods(pod.Namespace).GetLogs(pod.Name, &corev1.PodLogOptions{ Container: con.Name, Previous: b, - }).Do(context.TODO()) + }).Do(ctx) err := resp.Error() if err != nil { @@ -297,3 +271,11 @@ func (c *Chart) Logs() (map[string]string, error) { return out, nil } + +func (c *Chart) addRepo(ctx context.Context) error { + err := c.buildHelmCmd(ctx, "repo", "add", c.Repo.Name, c.Repo.Url).Run() + if err != nil { + return err + } + return nil +} diff --git a/test/e2e/framework/addon/globals.go b/test/e2e/framework/addon/globals.go index a3ce39e9d40..1938a46de48 100644 --- a/test/e2e/framework/addon/globals.go +++ b/test/e2e/framework/addon/globals.go @@ -17,21 +17,21 @@ limitations under the License. package addon import ( + "context" "fmt" + "slices" utilerrors "k8s.io/apimachinery/pkg/util/errors" - "github.com/cert-manager/cert-manager/test/e2e/framework/addon/base" - "github.com/cert-manager/cert-manager/test/e2e/framework/config" - "github.com/cert-manager/cert-manager/test/e2e/framework/log" + "github.com/cert-manager/cert-manager/e2e-tests/framework/addon/base" + "github.com/cert-manager/cert-manager/e2e-tests/framework/addon/internal" + "github.com/cert-manager/cert-manager/e2e-tests/framework/addon/vault" + "github.com/cert-manager/cert-manager/e2e-tests/framework/config" + "github.com/cert-manager/cert-manager/e2e-tests/framework/log" ) -type Addon interface { - Setup(*config.Config) error - Provision() error - Deprovision() error - SupportsGlobal() bool -} +type Addon = internal.Addon +type AddonTransferableData = internal.AddonTransferableData // This file is used to define global shared addon instances for the e2e suite. // We have to define these somewhere that can be imported by the framework and @@ -40,7 +40,9 @@ type Addon interface { var ( // Base is a base addon containing Kubernetes clients - Base = &base.Base{} + Base = &base.Base{} + Vault = &vault.Vault{} + VaultEnforceMtls = &vault.Vault{} // allAddons is populated by InitGlobals and defines the order in which // addons will be provisioned @@ -61,33 +63,78 @@ func InitGlobals(cfg *config.Config) { } globalsInited = true *Base = base.Base{} + *Vault = vault.Vault{ + Base: Base, + Namespace: "e2e-vault", + Name: "vault", + } + *VaultEnforceMtls = vault.Vault{ + Base: Base, + Namespace: "e2e-vault-mtls", + Name: "vault-mtls", + EnforceMtls: true, + } allAddons = []Addon{ Base, + Vault, + VaultEnforceMtls, } } -// ProvisionGlobals provisions all of the global addons, including calling Setup. -// This should be called by the test suite entrypoint in a SynchronizedBeforeSuite -// block to ensure it is run once per suite. -func ProvisionGlobals(cfg *config.Config) error { - // TODO: if we want to provision dependencies in parallel we will need - // to improve the logic here. - for _, g := range allAddons { - if err := provisionGlobal(g, cfg); err != nil { +// SetupGlobalsPrimary setups all of the global addons. +// The primary ginkgo process is the process with index 1. +// This function should be called by the test suite entrypoint in a SynchronizedBeforeSuite +// block to ensure it is run only on ginkgo process #1. It has to be run before +// any other ginkgo processes are started, because the return value of this function +// has to be transferred to the other ginkgo processes. +func SetupGlobalsPrimary(ctx context.Context, cfg *config.Config) ([]AddonTransferableData, error) { + toBeTransferred := make([]AddonTransferableData, len(allAddons)) + for addonIdx, g := range allAddons { + data, err := g.Setup(ctx, cfg) + if err != nil { + return nil, err + } + if !g.SupportsGlobal() { + return nil, fmt.Errorf("requested global plugin does not support shared mode with current configuration") + } + toBeTransferred[addonIdx] = data + } + return toBeTransferred, nil +} + +// SetupGlobalsNonPrimary setups all of the global addons. +// A non-primary ginkgo process is one that is not process #1 (process #2 and above). +// This function should be called by the test suite entrypoint in a SynchronizedBeforeSuite +// block on all ginkgo processes except #1. It has to be run after the primary process has +// run SetupGlobalsPrimary, so that the data returned by SetupGlobalsPrimary on process #1 +// can be passed into this function. This function calls Setup on all of the non-primary +// processes (processes #2 and above) and passes in the AddonTransferableData data returned +// by the primary process. +func SetupGlobalsNonPrimary(ctx context.Context, cfg *config.Config, transferred []AddonTransferableData) error { + for addonIdx, g := range allAddons { + _, err := g.Setup(ctx, cfg, transferred[addonIdx]) + if err != nil { return err } + if !g.SupportsGlobal() { + return fmt.Errorf("requested global plugin does not support shared mode with current configuration") + } } return nil } -// SetupGlobals will call Setup on all of the global addons, but not provision. -// This should be called by the test suite entrypoint in a BeforeSuite block -// on all ginkgo nodes to ensure global instances are configured for each test -// runner. -func SetupGlobals(cfg *config.Config) error { +// ProvisionGlobals calls Provision on all of the global addons. +// This should be called by the test suite in a SynchronizedBeforeSuite block +// after the Setup data has been transferred to all ginkgo processes, so that +// not all processes have to wait for the addons to be provisioned. Instead, +// the individual test has to check that the addon is provisioned (e.g., by querying +// the API server for a resource that the addon creates or by checking that an +// HTTP endpoint is available) +// This function should be run only on ginkgo process #1. +func ProvisionGlobals(ctx context.Context, cfg *config.Config) error { for _, g := range allAddons { - err := g.Setup(cfg) - if err != nil { + provisioned = append(provisioned, g) + if err := g.Provision(ctx); err != nil { return err } } @@ -95,10 +142,10 @@ func SetupGlobals(cfg *config.Config) error { } type loggableAddon interface { - Logs() (map[string]string, error) + Logs(ctx context.Context) (map[string]string, error) } -func GlobalLogs() (map[string]string, error) { +func GlobalLogs(ctx context.Context) (map[string]string, error) { out := make(map[string]string) for _, p := range provisioned { p, ok := p.(loggableAddon) @@ -106,7 +153,7 @@ func GlobalLogs() (map[string]string, error) { continue } - l, err := p.Logs() + l, err := p.Logs(ctx) if err != nil { return nil, err } @@ -123,37 +170,17 @@ func GlobalLogs() (map[string]string, error) { // DeprovisionGlobals deprovisions all of the global addons. // This should be called by the test suite in a SynchronizedAfterSuite to ensure -// all global addons are cleaned up after a run. -func DeprovisionGlobals(cfg *config.Config) error { +// all global addons are cleaned up after a run. This should be run only on ginkgo +// process #1. +func DeprovisionGlobals(ctx context.Context, cfg *config.Config) error { if !cfg.Cleanup { log.Logf("Skipping deprovisioning as cleanup set to false.") return nil } var errs []error // deprovision addons in the reverse order to that of provisioning - for i := len(provisioned) - 1; i >= 0; i-- { - a := provisioned[i] - errs = append(errs, a.Deprovision()) + for _, a := range slices.Backward(provisioned) { + errs = append(errs, a.Deprovision(ctx)) } return utilerrors.NewAggregate(errs) } - -func provisionGlobal(a Addon, cfg *config.Config) error { - if err := a.Setup(cfg); err != nil { - return err - } - if !a.SupportsGlobal() { - return fmt.Errorf("Requested global plugin does not support shared mode with current configuration") - } - if cfg.Cleanup { - err := a.Deprovision() - if err != nil { - return err - } - } - provisioned = append(provisioned, a) - if err := a.Provision(); err != nil { - return err - } - return nil -} diff --git a/test/e2e/framework/addon/internal/globals.go b/test/e2e/framework/addon/internal/globals.go new file mode 100644 index 00000000000..efb80112874 --- /dev/null +++ b/test/e2e/framework/addon/internal/globals.go @@ -0,0 +1,52 @@ +/* +Copyright 2020 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. +*/ + +package internal + +import ( + "context" + + "github.com/cert-manager/cert-manager/e2e-tests/framework/config" +) + +// Addon is an interface that defines an e2e addon. +type Addon interface { + // For non-global addons, this function is called on all ginkgo processes without + // any arguments. For global addons, this function is called on ginkgo process #1 + // without any arguments, but the returned data is passed to the Setup function + // on all other ginkgo processes. + Setup(ctx context.Context, cfg *config.Config, leaderData ...AddonTransferableData) (AddonTransferableData, error) + + // For non-global addons, this function is called on all ginkgo processes. For global + // addons, this function is called only on ginkgo process #1. + Provision(ctx context.Context) error + + // For non-global addons, this function is called on all ginkgo processes. For global + // addons, this function is called only on ginkgo process #1. + Deprovision(ctx context.Context) error + + SupportsGlobal() bool +} + +// TransferableData is data generated by a global addons' Setup function running on ginigo +// process #1 that should be copied to all other ginkgo processes. This is used to setup these +// processes with the same data as ginkgo process #1. The data has to be json serializable. +// +// e.g., The process #1 Setup function generates a private key and certificate and transfers +// it to all other ginkgo processes. Process #1 then starts a shared server that trusts the +// certificate. All other ginkgo processes can authenticate to this server using the private +// key and certificate that was transferred to them. +type AddonTransferableData interface{} diff --git a/test/e2e/framework/addon/vault/proxy.go b/test/e2e/framework/addon/vault/proxy.go index 5205558dc92..43dfdf0f95a 100644 --- a/test/e2e/framework/addon/vault/proxy.go +++ b/test/e2e/framework/addon/vault/proxy.go @@ -17,194 +17,145 @@ limitations under the License. package vault import ( - "crypto/x509" + "context" "fmt" + "io" "net" "net/http" - "os/exec" "sync" - "time" - vault "github.com/hashicorp/vault/api" - "k8s.io/apimachinery/pkg/util/wait" - - "github.com/cert-manager/cert-manager/test/e2e/framework/log" + "github.com/onsi/ginkgo/v2" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/rest" + "k8s.io/client-go/tools/portforward" + "k8s.io/client-go/transport/spdy" ) type proxy struct { - client *vault.Client - cmd *exec.Cmd - - ns, podName string - kubectl string - vaultCA []byte - + clientset kubernetes.Interface + kubeConfig *rest.Config listenPort int - mu sync.Mutex - closeCh chan struct{} -} -func newProxy(ns, podName, kubectl string, vaultCA []byte) *proxy { - return &proxy{ - ns: ns, - podName: podName, - kubectl: kubectl, - vaultCA: vaultCA, - closeCh: make(chan struct{}), - } + podNamespace, podName string + + stopCh chan struct{} + mu sync.Mutex + doneCh chan error } -func (p *proxy) init() (*vault.Client, error) { - listenPort, err := freePort() +func newProxy( + ctx context.Context, + clientset kubernetes.Interface, + kubeConfig *rest.Config, + podNamespace, podName string, +) *proxy { + freePort, err := freePort(ctx) if err != nil { - return nil, err + panic(err) } - p.listenPort = listenPort - - cfg := vault.DefaultConfig() - cfg.Address = fmt.Sprintf("https://127.0.0.1:%d", p.listenPort) - caCertPool := x509.NewCertPool() - ok := caCertPool.AppendCertsFromPEM(p.vaultCA) - if ok == false { - return nil, fmt.Errorf("error loading Vault CA bundle: %s", p.vaultCA) - } - - cfg.HttpClient.Transport.(*http.Transport).TLSClientConfig.RootCAs = caCertPool - - client, err := vault.NewClient(cfg) - if err != nil { - return nil, fmt.Errorf("unable to initialize vault client: %s", err) - } + return &proxy{ + clientset: clientset, + kubeConfig: kubeConfig, - client.SetToken(vaultToken) - p.client = client + podNamespace: podNamespace, + podName: podName, + listenPort: freePort, - if err := p.runProxy(); err != nil { - return nil, fmt.Errorf("failed to start vault port forward: %s", err) + stopCh: make(chan struct{}), } - - go p.nurseProxy() - - return client, nil -} - -func (p *proxy) vaultCmd() *exec.Cmd { - args := []string{"port-forward", "-n", p.ns, p.podName, fmt.Sprintf("%d:8200", p.listenPort)} - return exec.Command(p.kubectl, args...) } -func (p *proxy) nurseProxy() { - for { - kCh := make(chan struct{}) - go func() { - _ = p.cmd.Wait() - close(kCh) - }() - - select { - // if we are stopping the port forward completely then kill the process and exit - case <-p.closeCh: - return - - // if the process died, then attempt to recover it - case <-kCh: - if err := p.runProxy(); err != nil { - log.Logf("failed to recover vault port forward: %s", err) - return - } - - // new proxy started, loop again - } +func freePort(ctx context.Context) (int, error) { + // Reserve a port for the proxy. + lc := net.ListenConfig{} + listener, err := lc.Listen(ctx, "tcp", "localhost:0") + if err != nil { + return -1, err } + defer listener.Close() + return listener.Addr().(*net.TCPAddr).Port, nil } -func (p *proxy) callVault(method, url, field string, params map[string]string) (string, error) { +func (p *proxy) start() error { p.mu.Lock() defer p.mu.Unlock() - req := p.client.NewRequest(method, url) + select { + case <-p.stopCh: + return nil + default: + } - err := req.SetJSONBody(params) - if err != nil { - return "", fmt.Errorf("error encoding Vault parameters: %s", err.Error()) + stopCh := p.stopCh + doneCh := make(chan error, 1) - } + p.doneCh = doneCh - resp, err := p.client.RawRequest(req) - if err != nil { - return "", fmt.Errorf("error calling Vault server: %s", err.Error()) + reqURL := p.clientset.CoreV1().RESTClient().Post(). + Resource("pods"). + Namespace(p.podNamespace). + Name(p.podName). + SubResource("portforward"). + URL() + transport, upgrader, err := spdy.RoundTripperFor(p.kubeConfig) + if err != nil { + return err } - defer resp.Body.Close() - result := map[string]interface{}{} - resp.DecodeJSON(&result) + dialer := spdy.NewDialer(upgrader, &http.Client{Transport: transport}, http.MethodPost, reqURL) + + runForwarder := func() error { + fw, err := portforward.New(dialer, []string{fmt.Sprintf("%d:8200", p.listenPort)}, stopCh, make(chan struct{}), io.Discard, io.Discard) + if err != nil { + return fmt.Errorf("port forwarder creation error: %v", err) + } - fieldData := "" - if field != "" { - data := result["data"].(map[string]interface{}) - fieldData = data[field].(string) + err = fw.ForwardPorts() + if err != nil { + return fmt.Errorf("port forwarder error: %v", err) + } + return nil } - return fieldData, err -} + go func() { + defer close(doneCh) -func (p *proxy) clean() { - close(p.closeCh) + for { + err := runForwarder() - if p.cmd != nil && p.cmd.Process != nil { - p.cmd.Process.Kill() - p.cmd.Process.Wait() - } + select { + case <-stopCh: + doneCh <- err + return + default: + fmt.Fprintf(ginkgo.GinkgoWriter, "error while forwarding port: %v\n", err) + } + } + }() + + return nil } -func (p *proxy) runProxy() error { +func (p *proxy) stop(ctx context.Context) error { + close(p.stopCh) + p.mu.Lock() defer p.mu.Unlock() - err := wait.PollImmediate(time.Second, time.Second*10, func() (bool, error) { - p.cmd = p.vaultCmd() - - err := p.cmd.Start() - if err != nil { - log.Logf("failed to start port-forward: %s", err) - return false, nil - } - - return true, nil - }) - if err != nil { - return err + if p.doneCh == nil { + return nil } - err = wait.PollImmediate(time.Second, time.Second*30, func() (bool, error) { - // If the response is 400 or higher or can't connect then we get an error. - // Anything else is considered ready for serving. - _, err := p.client.Sys().Health() + select { + case <-ctx.Done(): + return ctx.Err() + case err := <-p.doneCh: if err != nil { - log.Logf("vault health failed: %s", err) - return false, nil + return fmt.Errorf("error while forwarding port: %v", err) } - - return true, nil - }) - if err != nil { - return err } return nil } - -func freePort() (int, error) { - l, err := net.ListenTCP("tcp", &net.TCPAddr{ - IP: net.ParseIP("127.0.0.1"), - Port: 0, - }) - if err != nil { - return -1, err - } - defer l.Close() - - return l.Addr().(*net.TCPAddr).Port, nil -} diff --git a/test/e2e/framework/addon/vault/setup.go b/test/e2e/framework/addon/vault/setup.go index ae17a3fc0e3..5e4ccb85ede 100644 --- a/test/e2e/framework/addon/vault/setup.go +++ b/test/e2e/framework/addon/vault/setup.go @@ -18,14 +18,28 @@ package vault import ( "context" + cryptorand "crypto/rand" + "crypto/tls" + "crypto/x509" + "crypto/x509/pkix" + "encoding/pem" "fmt" + "math/big" + "net" + "net/http" + "net/url" "path" + "time" vault "github.com/hashicorp/vault/api" corev1 "k8s.io/api/core/v1" rbacv1 "k8s.io/api/rbac/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + k8srand "k8s.io/apimachinery/pkg/util/rand" + "k8s.io/apimachinery/pkg/util/wait" "k8s.io/client-go/kubernetes" + + "github.com/cert-manager/cert-manager/pkg/util/pki" ) const vaultToken = "vault-root-token" @@ -34,96 +48,172 @@ const vaultToken = "vault-root-token" // Vault server for all tests. PKIs are mounted and unmounted for each test // scenario that uses them. type VaultInitializer struct { - client *vault.Client - proxy *proxy + kubeClient kubernetes.Interface + client *vault.Client + + details Details - Details + rootMount string + intermediateMount string + role string // AppRole auth role + appRoleAuthPath string // AppRole auth mount point in Vault + clientCertAuthPath string // Client certificate auth mount point in Vault + kubernetesAuthPath string // Kubernetes auth mount point in Vault - RootMount string - IntermediateMount string // Whether the intermediate CA should be configured with root CA - ConfigureWithRoot bool - Role string // AppRole auth Role - AppRoleAuthPath string // AppRole auth mount point in Vault - KubernetesAuthPath string // Kubernetes auth mount point in Vault - APIServerURL string // Kubernetes API Server URL - APIServerCA string // Kubernetes API Server CA certificate + configureWithRoot bool + kubernetesAPIServerURL string // Kubernetes API Server URL } -func NewVaultTokenSecret(name string) *corev1.Secret { - return &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - }, - StringData: map[string]string{ - "token": vaultToken, - }, +func NewVaultInitializerClientCertificate( + kubeClient kubernetes.Interface, + details Details, + configureWithRoot bool, +) *VaultInitializer { + testId := k8srand.String(10) + rootMount := fmt.Sprintf("%s-root-ca", testId) + intermediateMount := fmt.Sprintf("%s-intermediate-ca", testId) + role := fmt.Sprintf("%s-role", testId) + clientCertAuthPath := fmt.Sprintf("%s-auth-clientcert", testId) + + return &VaultInitializer{ + kubeClient: kubeClient, + details: details, + + rootMount: rootMount, + intermediateMount: intermediateMount, + role: role, + clientCertAuthPath: clientCertAuthPath, + + configureWithRoot: configureWithRoot, } } -func NewVaultAppRoleSecret(name, secretId string) *corev1.Secret { - return &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - GenerateName: name, - }, - StringData: map[string]string{ - "secretkey": secretId, - }, +func NewVaultInitializerAppRole( + kubeClient kubernetes.Interface, + details Details, + configureWithRoot bool, +) *VaultInitializer { + testId := k8srand.String(10) + rootMount := fmt.Sprintf("%s-root-ca", testId) + intermediateMount := fmt.Sprintf("%s-intermediate-ca", testId) + role := fmt.Sprintf("%s-role", testId) + appRoleAuthPath := fmt.Sprintf("%s-auth-approle", testId) + + return &VaultInitializer{ + kubeClient: kubeClient, + details: details, + + rootMount: rootMount, + intermediateMount: intermediateMount, + role: role, + appRoleAuthPath: appRoleAuthPath, + + configureWithRoot: configureWithRoot, } } -func NewVaultServiceAccount(name string) *corev1.ServiceAccount { - return &corev1.ServiceAccount{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - }, +func NewVaultInitializerKubernetes( + kubeClient kubernetes.Interface, + details Details, + configureWithRoot bool, + apiServerURL string, +) *VaultInitializer { + testId := k8srand.String(10) + rootMount := fmt.Sprintf("%s-root-ca", testId) + intermediateMount := fmt.Sprintf("%s-intermediate-ca", testId) + role := fmt.Sprintf("%s-role", testId) + kubernetesAuthPath := fmt.Sprintf("%s-auth-kubernetes", testId) + + return &VaultInitializer{ + kubeClient: kubeClient, + details: details, + + rootMount: rootMount, + intermediateMount: intermediateMount, + role: role, + kubernetesAuthPath: kubernetesAuthPath, + + configureWithRoot: configureWithRoot, + kubernetesAPIServerURL: apiServerURL, } } -func NewVaultServiceAccountRole(namespace, serviceAccountName string) *rbacv1.ClusterRole { - return &rbacv1.ClusterRole{ - ObjectMeta: metav1.ObjectMeta{ - Name: fmt.Sprintf("auth-delegator:%s:%s", namespace, serviceAccountName), - }, - Rules: []rbacv1.PolicyRule{ - { - APIGroups: []string{"authentication.k8s.io"}, - Resources: []string{"tokenreviews"}, - Verbs: []string{"create"}, - }, - { - APIGroups: []string{"authorization.k8s.io"}, - Resources: []string{"subjectaccessreviews"}, - Verbs: []string{"create"}, - }, - }, +func NewVaultInitializerAllAuth( + kubeClient kubernetes.Interface, + details Details, + configureWithRoot bool, + apiServerURL string, +) *VaultInitializer { + testId := k8srand.String(10) + rootMount := fmt.Sprintf("%s-root-ca", testId) + intermediateMount := fmt.Sprintf("%s-intermediate-ca", testId) + role := fmt.Sprintf("%s-role", testId) + appRoleAuthPath := fmt.Sprintf("%s-auth-approle", testId) + kubernetesAuthPath := fmt.Sprintf("%s-auth-kubernetes", testId) + clientCertAuthPath := fmt.Sprintf("%s-client-certificate", testId) + + return &VaultInitializer{ + kubeClient: kubeClient, + details: details, + + rootMount: rootMount, + intermediateMount: intermediateMount, + role: role, + appRoleAuthPath: appRoleAuthPath, + kubernetesAuthPath: kubernetesAuthPath, + clientCertAuthPath: clientCertAuthPath, + + configureWithRoot: configureWithRoot, + kubernetesAPIServerURL: apiServerURL, } } -func NewVaultServiceAccountClusterRoleBinding(roleName, namespace, subject string) *rbacv1.ClusterRoleBinding { - return &rbacv1.ClusterRoleBinding{ +func (v *VaultInitializer) RootMount() string { + return v.rootMount +} + +func (v *VaultInitializer) IntermediateMount() string { + return v.intermediateMount +} + +func (v *VaultInitializer) Role() string { + return v.role +} + +// AppRoleAuthPath returns the AppRole auth mount point in Vault. +// The format is "xxxxx-auth-approle". +func (v *VaultInitializer) AppRoleAuthPath() string { + return v.appRoleAuthPath +} + +// AppRoleAuthPath returns the AppRole auth mount point in Vault. +// The format is "/v1/auth/xxxxx-auth-clientcert". +func (v *VaultInitializer) ClientCertificateAuthPath() string { + return path.Join("/v1", "auth", v.clientCertAuthPath) +} + +// KubernetesAuthPath returns the Kubernetes auth mount point in Vault. +// The format is "/v1/auth/xxxxx-auth-kubernetes". +func (v *VaultInitializer) KubernetesAuthPath() string { + return path.Join("/v1", "auth", v.kubernetesAuthPath) +} + +func NewVaultAppRoleSecret(secretName, secretId string) *corev1.Secret { + return &corev1.Secret{ ObjectMeta: metav1.ObjectMeta{ - Name: roleName, + GenerateName: secretName, }, - RoleRef: rbacv1.RoleRef{ - APIGroup: "rbac.authorization.k8s.io", - Kind: "ClusterRole", - Name: roleName, - }, - Subjects: []rbacv1.Subject{ - { - Name: subject, - Kind: "ServiceAccount", - Namespace: namespace, - }, + StringData: map[string]string{ + "secretkey": secretId, }, } } -func NewVaultKubernetesSecret(name string, serviceAccountName string) *corev1.Secret { +func NewVaultKubernetesSecret(secretName, serviceAccountName string) *corev1.Secret { return &corev1.Secret{ ObjectMeta: metav1.ObjectMeta{ - Name: name, + Name: secretName, Annotations: map[string]string{ "kubernetes.io/service-account.name": serviceAccountName, }, @@ -132,313 +222,372 @@ func NewVaultKubernetesSecret(name string, serviceAccountName string) *corev1.Se } } -// Set up a new Vault client, port-forward to the Vault instance. -func (v *VaultInitializer) Init() error { - if v.AppRoleAuthPath == "" { - v.AppRoleAuthPath = "approle" +func NewVaultClientCertificateSecret(secretName string, certificate, key []byte) *corev1.Secret { + return &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: secretName, + }, + Data: map[string][]byte{ + corev1.TLSCertKey: certificate, + corev1.TLSPrivateKeyKey: key, + }, + Type: corev1.SecretTypeTLS, } +} - if v.KubernetesAuthPath == "" { - v.KubernetesAuthPath = "kubernetes" +// Set up a new Vault client, port-forward to the Vault instance. +func (v *VaultInitializer) Init(ctx context.Context) error { + cfg := vault.DefaultConfig() + cfg.Address = v.details.ProxyURL + + caCertPool := x509.NewCertPool() + if ok := caCertPool.AppendCertsFromPEM(v.details.VaultCA); !ok { + return fmt.Errorf("error loading Vault CA bundle: %s", v.details.VaultCA) + } + // Build a custom transport with our TLS settings + tlsCfg := &tls.Config{RootCAs: caCertPool, MinVersion: tls.VersionTLS12} + if v.details.EnforceMtls { + clientCertificate, err := tls.X509KeyPair(v.details.VaultClientCertificate, v.details.VaultClientPrivateKey) + if err != nil { + return fmt.Errorf("unable to read vault client certificate: %s", err) + } + tlsCfg.Certificates = []tls.Certificate{clientCertificate} + } + if cfg.HttpClient == nil { + cfg.HttpClient = &http.Client{} } + cfg.HttpClient.Transport = &http.Transport{TLSClientConfig: tlsCfg} - v.proxy = newProxy(v.Namespace, v.PodName, v.Kubectl, v.VaultCA) - client, err := v.proxy.init() + client, err := vault.NewClient(cfg) if err != nil { - return err + return fmt.Errorf("unable to initialize vault client: %s", err) } + + client.SetToken(vaultToken) v.client = client + // Wait for port-forward to be ready + { + proxyUrl, err := url.Parse(v.details.ProxyURL) + if err != nil { + return fmt.Errorf("error parsing proxy URL: %s", err.Error()) + } + var lastError error + // The timeout below must be aligned with the time taken by the Vault addons to start, + // each addon safely takes about 20 seconds to start and two addons are started one after another, + // one for without mTLS enforced and another with mTLS enforced + err = wait.PollUntilContextTimeout(ctx, time.Second, 45*time.Second, true, func(ctx context.Context) (bool, error) { + conn, err := (&net.Dialer{Timeout: time.Second}).DialContext(ctx, "tcp", proxyUrl.Host) + if err != nil { + lastError = err + return false, nil //nolint:nilerr + } + + conn.Close() + return true, nil + }) + if err != nil { + return fmt.Errorf("error waiting for port-forward to be ready: %w", lastError) + } + } + + // Wait for Vault to be ready + { + var lastError error + err = wait.PollUntilContextTimeout(ctx, time.Second, 20*time.Second, true, func(ctx context.Context) (bool, error) { + _, err := v.client.Sys().HealthWithContext(ctx) + if err != nil { + lastError = err + return false, nil //nolint:nilerr + } + + return true, nil + }) + if err != nil { + return fmt.Errorf("error waiting for Vault to be ready: %w", lastError) + } + } + return nil } // Set up a Vault PKI. -func (v *VaultInitializer) Setup() error { +func (v *VaultInitializer) Setup(ctx context.Context) error { // Enable a new Vault secrets engine at v.RootMount - if err := v.mountPKI(v.RootMount, "87600h"); err != nil { + if err := v.mountPKI(ctx, v.rootMount, "87600h"); err != nil { return err } // Generate a self-signed CA cert using the engine at v.RootMount - rootCa, err := v.generateRootCert() + rootCa, err := v.generateRootCert(ctx) if err != nil { return err } // Configure issuing certificate endpoints and CRL distribution points to be // set on certs issued by v.RootMount. - if err := v.configureCert(v.RootMount); err != nil { + if err := v.configureCert(ctx, v.rootMount); err != nil { return err } - // Enable a new Vault secrets engine at v.IntermediateMount - if err := v.mountPKI(v.IntermediateMount, "43800h"); err != nil { + // Enable a new Vault secrets engine at v.intermediateMount + if err := v.mountPKI(ctx, v.intermediateMount, "43800h"); err != nil { return err } - // Generate a CSR for secrets engine at v.IntermediateMount - csr, err := v.generateIntermediateSigningReq() + // Generate a CSR for secrets engine at v.intermediateMount + csr, err := v.generateIntermediateSigningReq(ctx) if err != nil { return err } // Issue a new intermediate CA from v.RootMount for the CSR created above. - intermediateCa, err := v.signCertificate(csr) + intermediateCa, err := v.signCertificate(ctx, csr) if err != nil { return err } - // Set the engine at v.IntermediateMount as an intermediateCA using the cert + // Set the engine at v.intermediateMount as an intermediateCA using the cert // issued by v.RootMount, above and optionally the root CA cert. caChain := intermediateCa - if v.ConfigureWithRoot { + if v.configureWithRoot { caChain = fmt.Sprintf("%s\n%s", intermediateCa, rootCa) } - if err := v.importSignIntermediate(caChain, v.IntermediateMount); err != nil { + if err := v.importSignIntermediate(ctx, caChain, v.intermediateMount); err != nil { return err } // Configure issuing certificate endpoints and CRL distribution points to be - // set on certs issued by v.IntermediateMount. - if err := v.configureCert(v.IntermediateMount); err != nil { + // set on certs issued by v.intermediateMount. + if err := v.configureCert(ctx, v.intermediateMount); err != nil { return err } - if err := v.setupRole(); err != nil { + if err := v.configureIntermediateRoles(ctx); err != nil { return err } - if err := v.setupKubernetesBasedAuth(); err != nil { - return err + if v.appRoleAuthPath != "" { + if err := v.setupAppRoleAuth(ctx); err != nil { + return err + } + } + + if v.kubernetesAuthPath != "" { + if err := v.setupKubernetesBasedAuth(ctx); err != nil { + return err + } + } + + if v.clientCertAuthPath != "" { + if err := v.setupClientCertAuth(ctx); err != nil { + return err + } } return nil } -func (v *VaultInitializer) Clean() error { - if err := v.client.Sys().Unmount("/" + v.IntermediateMount); err != nil { - return fmt.Errorf("Unable to unmount %v: %v", v.IntermediateMount, err) +func (v *VaultInitializer) Clean(ctx context.Context) error { + if err := v.client.Sys().UnmountWithContext(ctx, v.intermediateMount); err != nil { + return fmt.Errorf("unable to unmount %v: %v", v.intermediateMount, err) } - if err := v.client.Sys().Unmount("/" + v.RootMount); err != nil { - return fmt.Errorf("Unable to unmount %v: %v", v.RootMount, err) + if err := v.client.Sys().UnmountWithContext(ctx, v.rootMount); err != nil { + return fmt.Errorf("unable to unmount %v: %v", v.rootMount, err) } - v.proxy.clean() - return nil } -func (v *VaultInitializer) CreateAppRole() (string, string, error) { +func (v *VaultInitializer) CreateAppRole(ctx context.Context) (string, string, error) { // create policy - role_path := path.Join(v.IntermediateMount, "sign", v.Role) - policy := fmt.Sprintf("path \"%s\" { capabilities = [ \"create\", \"update\" ] }", role_path) - err := v.client.Sys().PutPolicy(v.Role, policy) + policy := fmt.Sprintf(`path "%s" { capabilities = [ "create", "update" ] }`, v.IntermediateSignPath()) + err := v.client.Sys().PutPolicyWithContext(ctx, v.role, policy) if err != nil { - return "", "", fmt.Errorf("Error creating policy: %s", err.Error()) + return "", "", fmt.Errorf("error creating policy: %s", err.Error()) } - // # create approle - params := map[string]string{ - "period": "24h", - "policies": v.Role, - } - - baseUrl := path.Join("/v1", "auth", v.AppRoleAuthPath, "role", v.Role) - _, err = v.proxy.callVault("POST", baseUrl, "", params) + // create approle role + _, err = v.client.Logical().WriteWithContext(ctx, path.Join("auth", v.appRoleAuthPath, "role", v.role), map[string]interface{}{ + "token_period": "24h", + "token_policies": []string{v.role}, + }) if err != nil { - return "", "", fmt.Errorf("Error creating approle: %s", err.Error()) + return "", "", fmt.Errorf("error creating approle: %s", err.Error()) } // # read the role-id - url := path.Join(baseUrl, "role-id") - roleId, err := v.proxy.callVault("GET", url, "role_id", map[string]string{}) + respRoleId, err := v.client.Logical().ReadWithContext(ctx, path.Join("auth", v.appRoleAuthPath, "role", v.role, "role-id")) if err != nil { - return "", "", fmt.Errorf("Error reading role_id: %s", err.Error()) + return "", "", fmt.Errorf("error reading role_id: %s", err.Error()) } // # read the secret-id - url = path.Join(baseUrl, "secret-id") - secretId, err := v.proxy.callVault("POST", url, "secret_id", map[string]string{}) + resp, err := v.client.Logical().WriteWithContext(ctx, path.Join("auth", v.appRoleAuthPath, "role", v.role, "secret-id"), nil) if err != nil { - return "", "", fmt.Errorf("Error reading secret_id: %s", err.Error()) + return "", "", fmt.Errorf("error reading secret_id: %s", err.Error()) } - - return roleId, secretId, nil + return respRoleId.Data["role_id"].(string), resp.Data["secret_id"].(string), nil } -func (v *VaultInitializer) CleanAppRole() error { - url := path.Join("/v1", "auth", v.AppRoleAuthPath, "role", v.Role) - _, err := v.proxy.callVault("DELETE", url, "", map[string]string{}) +func (v *VaultInitializer) CleanAppRole(ctx context.Context) error { + _, err := v.client.Logical().DeleteWithContext(ctx, path.Join("auth", v.appRoleAuthPath, "role", v.role)) if err != nil { - return fmt.Errorf("Error deleting AppRole: %s", err.Error()) + return fmt.Errorf("error deleting AppRole: %s", err.Error()) } - err = v.client.Sys().DeletePolicy(v.Role) + err = v.client.Sys().DeletePolicyWithContext(ctx, v.role) if err != nil { - return fmt.Errorf("Error deleting policy: %s", err.Error()) + return fmt.Errorf("error deleting policy: %s", err.Error()) } return nil } -func (v *VaultInitializer) mountPKI(mount, ttl string) error { - opts := &vault.MountInput{ +func (v *VaultInitializer) mountPKI(ctx context.Context, mount, ttl string) error { + // Equivalent to: vault secrets enable -path= -max-lease-ttl= pki + err := v.client.Sys().MountWithContext(ctx, mount, &vault.MountInput{ Type: "pki", Config: vault.MountConfigInput{ - MaxLeaseTTL: "87600h", + MaxLeaseTTL: ttl, }, - } - if err := v.client.Sys().Mount("/"+mount, opts); err != nil { - return fmt.Errorf("Error mounting %s: %s", mount, err.Error()) + }) + if err != nil { + return fmt.Errorf("error mounting %s: %s", mount, err.Error()) } return nil } -func (v *VaultInitializer) generateRootCert() (string, error) { - params := map[string]string{ +func (v *VaultInitializer) generateRootCert(ctx context.Context) (string, error) { + resp, err := v.client.Logical().WriteWithContext(ctx, path.Join(v.rootMount, "root", "generate", "internal"), map[string]interface{}{ "common_name": "Root CA", "ttl": "87600h", - "exclude_cn_from_sans": "true", + "exclude_cn_from_sans": true, "key_type": "ec", - "key_bits": "256", - } - url := path.Join("/v1", v.RootMount, "root", "generate", "internal") - - cert, err := v.proxy.callVault("POST", url, "certificate", params) + "key_bits": 256, + }) if err != nil { - return "", fmt.Errorf("Error generating CA root certificate: %s", err.Error()) + return "", fmt.Errorf("error generating CA root certificate: %s", err.Error()) } - - return cert, nil + return resp.Data["certificate"].(string), nil } -func (v *VaultInitializer) generateIntermediateSigningReq() (string, error) { - params := map[string]string{ +func (v *VaultInitializer) generateIntermediateSigningReq(ctx context.Context) (string, error) { + resp, err := v.client.Logical().WriteWithContext(ctx, path.Join(v.intermediateMount, "intermediate", "generate", "internal"), map[string]interface{}{ "common_name": "Intermediate CA", "ttl": "43800h", - "exclude_cn_from_sans": "true", + "exclude_cn_from_sans": true, "key_type": "ec", - "key_bits": "256", - } - url := path.Join("/v1", v.IntermediateMount, "intermediate", "generate", "internal") - - csr, err := v.proxy.callVault("POST", url, "csr", params) + "key_bits": 256, + }) if err != nil { - return "", fmt.Errorf("Error generating CA intermediate certificate: %s", err.Error()) + return "", fmt.Errorf("error generating CA intermediate certificate: %s", err.Error()) } - - return csr, nil + return resp.Data["csr"].(string), nil } -func (v *VaultInitializer) signCertificate(csr string) (string, error) { - params := map[string]string{ - "use_csr_values": "true", +func (v *VaultInitializer) signCertificate(ctx context.Context, csr string) (string, error) { + resp, err := v.client.Logical().WriteWithContext(ctx, path.Join(v.rootMount, "root", "sign-intermediate"), map[string]interface{}{ + "use_csr_values": true, "ttl": "43800h", - "exclude_cn_from_sans": "true", + "exclude_cn_from_sans": true, "csr": csr, - } - url := path.Join("/v1", v.RootMount, "root", "sign-intermediate") - - cert, err := v.proxy.callVault("POST", url, "certificate", params) + }) if err != nil { - return "", fmt.Errorf("Error signing intermediate Vault certificate: %s", err.Error()) + return "", fmt.Errorf("error signing intermediate Vault certificate: %s", err.Error()) } - return cert, nil + return resp.Data["certificate"].(string), nil } -func (v *VaultInitializer) importSignIntermediate(caChain, intermediateMount string) error { - params := map[string]string{ +func (v *VaultInitializer) importSignIntermediate(ctx context.Context, caChain, intermediateMount string) error { + _, err := v.client.Logical().WriteWithContext(ctx, path.Join(intermediateMount, "intermediate", "set-signed"), map[string]interface{}{ "certificate": caChain, - } - url := path.Join("/v1", intermediateMount, "intermediate", "set-signed") - - _, err := v.proxy.callVault("POST", url, "", params) + }) if err != nil { - return fmt.Errorf("Error importing intermediate Vault certificate: %s", err.Error()) + return fmt.Errorf("error importing intermediate Vault certificate: %s", err.Error()) } return nil } -func (v *VaultInitializer) configureCert(mount string) error { - params := map[string]string{ - "issuing_certificates": fmt.Sprintf("https://vault.vault:8200/v1/%s/ca", mount), - "crl_distribution_points": fmt.Sprintf("https://vault.vault:8200/v1/%s/crl", mount), - } - url := path.Join("/v1", mount, "config", "urls") - - _, err := v.proxy.callVault("POST", url, "", params) +func (v *VaultInitializer) configureCert(ctx context.Context, mount string) error { + _, err := v.client.Logical().WriteWithContext(ctx, path.Join(mount, "config", "urls"), map[string]interface{}{ + "issuing_certificates": []string{fmt.Sprintf("https://vault.vault:8200/v1/%s/ca", mount)}, + "crl_distribution_points": []string{fmt.Sprintf("https://vault.vault:8200/v1/%s/crl", mount)}, + }) if err != nil { - return fmt.Errorf("Error configuring Vault certificate: %s", err.Error()) + return fmt.Errorf("error configuring Vault certificate: %s", err.Error()) } return nil } -func (v *VaultInitializer) setupRole() error { - // vault auth-enable approle - auths, err := v.client.Sys().ListAuth() - if err != nil { - return fmt.Errorf("Error fetching auth mounts: %s", err.Error()) - } - - if _, ok := auths[v.AppRoleAuthPath]; !ok { - options := &vault.EnableAuthOptions{Type: "approle"} - if err := v.client.Sys().EnableAuthWithOptions(v.AppRoleAuthPath, options); err != nil { - return fmt.Errorf("Error enabling approle: %s", err.Error()) - } - } - - params := map[string]string{ +func (v *VaultInitializer) configureIntermediateRoles(ctx context.Context) error { + params := map[string]interface{}{ "allow_any_name": "true", "max_ttl": "2160h", "key_type": "any", "require_cn": "false", + "allowed_other_sans": "*", + "use_csr_sans": "true", "allowed_uri_sans": "spiffe://cluster.local/*", "enforce_hostnames": "false", "allow_bare_domains": "true", } - url := path.Join("/v1", v.IntermediateMount, "roles", v.Role) + url := path.Join(v.intermediateMount, "roles", v.role) - _, err = v.proxy.callVault("POST", url, "", params) + _, err := v.client.Logical().WriteWithContext(ctx, url, params) if err != nil { - return fmt.Errorf("Error creating role %s: %s", v.Role, err.Error()) + return fmt.Errorf("error creating role %s: %s", v.role, err.Error()) } return nil } -func (v *VaultInitializer) setupKubernetesBasedAuth() error { - if len(v.APIServerURL) == 0 { - // skip initialization if not provided +func (v *VaultInitializer) setupAppRoleAuth(ctx context.Context) error { + // vault auth-enable approle + mounts, err := v.client.Sys().ListAuthWithContext(ctx) + if err != nil { + return fmt.Errorf("error fetching auth mounts: %s", err.Error()) + } + + if _, ok := mounts[v.appRoleAuthPath+"/"]; ok { return nil } - // vault auth-enable kubernetes - auths, err := v.client.Sys().ListAuth() + err = v.client.Sys().EnableAuthWithOptionsWithContext(ctx, v.appRoleAuthPath, &vault.EnableAuthOptions{Type: "approle"}) if err != nil { - return fmt.Errorf("Error fetching auth mounts: %s", err.Error()) + return fmt.Errorf("error enabling approle auth: %s", err.Error()) } - if _, ok := auths[v.KubernetesAuthPath]; !ok { - options := &vault.EnableAuthOptions{Type: "kubernetes"} - if err := v.client.Sys().EnableAuthWithOptions(v.KubernetesAuthPath, options); err != nil { - return fmt.Errorf("Error enabling kubernetes auth: %s", err.Error()) - } + return nil +} + +func (v *VaultInitializer) setupKubernetesBasedAuth(ctx context.Context) error { + // vault auth-enable kubernetes + mounts, err := v.client.Sys().ListAuthWithContext(ctx) + if err != nil { + return fmt.Errorf("error fetching auth mounts: %s", err.Error()) } - // vault write auth/kubernetes/config - params := map[string]string{ - "kubernetes_host": v.APIServerURL, - "kubernetes_ca_cert": v.APIServerCA, + if _, ok := mounts[v.kubernetesAuthPath+"/"]; ok { + return nil } - url := fmt.Sprintf("/v1/auth/%s/config", v.KubernetesAuthPath) - _, err = v.proxy.callVault("POST", url, "", params) + err = v.client.Sys().EnableAuthWithOptionsWithContext(ctx, v.kubernetesAuthPath, &vault.EnableAuthOptions{Type: "kubernetes"}) + if err != nil { + return fmt.Errorf("error enabling kubernetes auth: %s", err.Error()) + } + // vault write auth/kubernetes/config + _, err = v.client.Logical().WriteWithContext(ctx, path.Join("auth", v.kubernetesAuthPath, "config"), map[string]interface{}{ + "kubernetes_host": v.kubernetesAPIServerURL, + // See https://www.vaultproject.io/docs/auth/kubernetes#kubernetes-1-21 + "disable_iss_validation": true, + }) if err != nil { return fmt.Errorf("error configuring kubernetes auth backend: %s", err.Error()) } @@ -446,104 +595,186 @@ func (v *VaultInitializer) setupKubernetesBasedAuth() error { return nil } -// CreateKubernetesRole creates a service account and ClusterRoleBinding for Kubernetes auth delegation -func (v *VaultInitializer) CreateKubernetesRole(client kubernetes.Interface, namespace, roleName, serviceAccountName string) error { - serviceAccount := NewVaultServiceAccount(serviceAccountName) - _, err := client.CoreV1().ServiceAccounts(namespace).Create(context.TODO(), serviceAccount, metav1.CreateOptions{}) - +// CreateKubernetesRole creates a service account and ClusterRoleBinding for +// Kubernetes auth delegation. The name "boundSA" refers to the Vault param +// "bound_service_account_names". +func (v *VaultInitializer) CreateKubernetesRole(ctx context.Context, client kubernetes.Interface, boundNS, boundSA string) error { + serviceAccount := &corev1.ServiceAccount{ + ObjectMeta: metav1.ObjectMeta{ + Name: boundSA, + }, + } + _, err := client.CoreV1().ServiceAccounts(boundNS).Create(ctx, serviceAccount, metav1.CreateOptions{}) if err != nil { return fmt.Errorf("error creating ServiceAccount for Kubernetes auth: %s", err.Error()) } - role := NewVaultServiceAccountRole(namespace, serviceAccountName) - _, err = client.RbacV1().ClusterRoles().Create(context.TODO(), role, metav1.CreateOptions{}) + // create policy + policy := fmt.Sprintf(`path "%s" { capabilities = [ "create", "update" ] }`, v.IntermediateSignPath()) + err = v.client.Sys().PutPolicyWithContext(ctx, v.role, policy) if err != nil { - return fmt.Errorf("error creating Role for Kubernetes auth ServiceAccount: %s", err.Error()) + return fmt.Errorf("error creating policy: %s", err.Error()) } - roleBinding := NewVaultServiceAccountClusterRoleBinding(role.Name, namespace, serviceAccountName) - _, err = client.RbacV1().ClusterRoleBindings().Create(context.TODO(), roleBinding, metav1.CreateOptions{}) - + // create kubernetes auth role + _, err = v.client.Logical().WriteWithContext(ctx, path.Join("auth", v.kubernetesAuthPath, "role", v.role), map[string]interface{}{ + "token_period": "24h", + "token_policies": []string{v.role}, + "bound_service_account_names": []string{boundSA}, + "bound_service_account_namespaces": []string{boundNS}, + }) if err != nil { - return fmt.Errorf("error creating RoleBinding for Kubernetes auth ServiceAccount: %s", err.Error()) + return fmt.Errorf("error creating kubernetes role: %s", err.Error()) } - // vault write auth/kubernetes/role/ - roleParams := map[string]string{ - "bound_service_account_names": serviceAccountName, - "bound_service_account_namespaces": namespace, - "policies": "[" + v.Role + "]", + return nil +} + +func (v *VaultInitializer) IntermediateSignPath() string { + return path.Join(v.intermediateMount, "sign", v.role) +} + +// CleanKubernetesRole cleans up the ClusterRoleBinding and ServiceAccount for Kubernetes auth delegation +func (v *VaultInitializer) CleanKubernetesRole(ctx context.Context, client kubernetes.Interface, boundNS, boundSA string) error { + if err := client.CoreV1().ServiceAccounts(boundNS).Delete(ctx, boundSA, metav1.DeleteOptions{}); err != nil { + return err } - url := path.Join(fmt.Sprintf("/v1/auth/%s/role", v.KubernetesAuthPath), roleName) - _, err = v.proxy.callVault("POST", url, "", roleParams) + // vault delete auth/kubernetes/role/ + _, err := v.client.Logical().DeleteWithContext(ctx, path.Join("auth", v.kubernetesAuthPath, "role", v.role)) if err != nil { - return fmt.Errorf("error configuring kubernetes auth role: %s", err.Error()) + return fmt.Errorf("error cleaning up kubernetes auth role: %s", err.Error()) } - params := map[string]string{ - "allow_any_name": "true", - "max_ttl": "2160h", - "key_type": "any", - "require_cn": "false", - "allowed_uri_sans": "spiffe://cluster.local/*", - "enforce_hostnames": "false", - "allow_bare_domains": "true", - "bound_service_account_names": serviceAccountName, - "bound_service_account_namespaces": namespace, + err = v.client.Sys().DeletePolicyWithContext(ctx, v.role) + if err != nil { + return fmt.Errorf("error deleting policy: %s", err.Error()) } - url = path.Join("/v1", v.IntermediateMount, "roles", v.Role) - _, err = v.proxy.callVault("POST", url, "", params) + return nil +} + +func RoleAndBindingForServiceAccountRefAuth(roleName, namespace, serviceAccount string) (*rbacv1.Role, *rbacv1.RoleBinding) { + return &rbacv1.Role{ + ObjectMeta: metav1.ObjectMeta{ + Name: roleName, + Namespace: namespace, + }, + Rules: []rbacv1.PolicyRule{ + { + APIGroups: []string{""}, + Resources: []string{"serviceaccounts/token"}, + ResourceNames: []string{serviceAccount}, + Verbs: []string{"create"}, + }, + }, + }, + &rbacv1.RoleBinding{ + ObjectMeta: metav1.ObjectMeta{ + Name: roleName, + }, + RoleRef: rbacv1.RoleRef{ + APIGroup: "rbac.authorization.k8s.io", + Kind: "Role", + Name: roleName, + }, + Subjects: []rbacv1.Subject{ + { + Name: "cert-manager", + Namespace: "cert-manager", + Kind: "ServiceAccount", + }, + }, + } +} + +// CreateKubernetesRoleForServiceAccountRefAuth creates a service account and a +// role for using the "serviceAccountRef" field. +func CreateKubernetesRoleForServiceAccountRefAuth(ctx context.Context, client kubernetes.Interface, roleName, saNS, saName string) error { + role, binding := RoleAndBindingForServiceAccountRefAuth(roleName, saNS, saName) + _, err := client.RbacV1().Roles(saNS).Create(ctx, role, metav1.CreateOptions{}) + if err != nil { + return fmt.Errorf("error creating Role for Kubernetes auth ServiceAccount with serviceAccountRef: %s", err.Error()) + } + _, err = client.RbacV1().RoleBindings(saNS).Create(ctx, binding, metav1.CreateOptions{}) if err != nil { - return fmt.Errorf("error creating role %s: %s", v.Role, err.Error()) + return fmt.Errorf("error creating RoleBinding for Kubernetes auth ServiceAccount with serviceAccountRef: %s", err.Error()) } - // create policy - role_path := path.Join(v.IntermediateMount, "sign", v.Role) - policy := fmt.Sprintf(`path "%s" { capabilities = [ "create", "update" ] }`, role_path) - err = v.client.Sys().PutPolicy(v.Role, policy) + return nil +} + +func CleanKubernetesRoleForServiceAccountRefAuth(ctx context.Context, client kubernetes.Interface, roleName, saNS, saName string) error { + if err := client.RbacV1().RoleBindings(saNS).Delete(ctx, roleName, metav1.DeleteOptions{}); err != nil { + return err + } + + if err := client.RbacV1().Roles(saNS).Delete(ctx, roleName, metav1.DeleteOptions{}); err != nil { + return err + } + + return nil +} + +func (v *VaultInitializer) setupClientCertAuth(ctx context.Context) error { + // vault auth-enable cert + mounts, err := v.client.Sys().ListAuthWithContext(ctx) if err != nil { - return fmt.Errorf("error creating policy: %s", err.Error()) + return fmt.Errorf("error fetching auth mounts: %s", err.Error()) } - // # create approle - params = map[string]string{ - "period": "24h", - "policies": v.Role, - "bound_service_account_names": serviceAccountName, - "bound_service_account_namespaces": namespace, + if _, ok := mounts[v.clientCertAuthPath+"/"]; ok { + return nil } - baseUrl := path.Join("/v1", "auth", v.KubernetesAuthPath, "role", v.Role) - _, err = v.proxy.callVault("POST", baseUrl, "", params) + err = v.client.Sys().EnableAuthWithOptionsWithContext(ctx, v.clientCertAuthPath, &vault.EnableAuthOptions{Type: "cert"}) if err != nil { - return fmt.Errorf("error creating kubernetes role: %s", err.Error()) + return fmt.Errorf("error enabling cert auth: %s", err.Error()) } return nil } -// CleanKubernetesRole cleans up the ClusterRoleBinding and ServiceAccount for Kubernetes auth delegation -func (v *VaultInitializer) CleanKubernetesRole(client kubernetes.Interface, namespace, roleName, serviceAccountName string) error { - if err := client.RbacV1().RoleBindings(namespace).Delete(context.TODO(), roleName, metav1.DeleteOptions{}); err != nil { - return err +func (v *VaultInitializer) CreateClientCertRole(ctx context.Context) (key []byte, cert []byte, _ error) { + privateKey, err := pki.GenerateRSAPrivateKey(2048) + if err != nil { + return nil, nil, err } - if err := client.RbacV1().Roles(namespace).Delete(context.TODO(), roleName, metav1.DeleteOptions{}); err != nil { - return err + template := x509.Certificate{ + SerialNumber: big.NewInt(1), + Subject: pkix.Name{CommonName: "example.com"}, + NotBefore: time.Now(), + NotAfter: time.Now().AddDate(1, 0, 0), + BasicConstraintsValid: true, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth}, } - if err := client.CoreV1().ServiceAccounts(namespace).Delete(context.TODO(), serviceAccountName, metav1.DeleteOptions{}); err != nil { - return err + certificateBytes, err := x509.CreateCertificate(cryptorand.Reader, &template, &template, &privateKey.PublicKey, privateKey) + if err != nil { + return nil, nil, err } - // vault delete auth/kubernetes/role/ - url := path.Join(fmt.Sprintf("/v1/auth/%s/role", v.KubernetesAuthPath), roleName) - _, err := v.proxy.callVault("DELETE", url, "", nil) + privateKeyBytes := x509.MarshalPKCS1PrivateKey(privateKey) + privateKeyPEM := pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: privateKeyBytes}) + certificatePEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certificateBytes}) + + role_path := v.IntermediateSignPath() + policy := fmt.Sprintf(`path "%s" { capabilities = [ "create", "update" ] } `, role_path) + err = v.client.Sys().PutPolicyWithContext(ctx, v.role, policy) if err != nil { - return fmt.Errorf("error cleaning up kubernetes auth role: %s", err.Error()) + return nil, nil, fmt.Errorf("error creating policy: %s", err.Error()) } - return nil + _, err = v.client.Logical().WriteWithContext(ctx, path.Join("auth", v.clientCertAuthPath, "certs", v.role), map[string]interface{}{ + "display_name": v.role, + "certificate": string(certificatePEM), + "token_policies": []string{v.role}, + "token_ttl": "3600", + }) + if err != nil { + return nil, nil, fmt.Errorf("error creating cert auth role: %s", err.Error()) + } + + return privateKeyPEM, certificatePEM, nil } diff --git a/test/e2e/framework/addon/vault/vault.go b/test/e2e/framework/addon/vault/vault.go index aa4ae2f636d..4a4863a8b6f 100644 --- a/test/e2e/framework/addon/vault/vault.go +++ b/test/e2e/framework/addon/vault/vault.go @@ -14,35 +14,47 @@ See the License for the specific language governing permissions and limitations under the License. */ -// package vault contains an addon that installs Vault +// Package vault contains an addon that installs Vault package vault import ( "context" "crypto/rand" - "crypto/rsa" "crypto/tls" "crypto/x509" "crypto/x509/pkix" - "encoding/base64" + "encoding/json" "encoding/pem" "fmt" "math/big" "net" + "os" + "strconv" + "strings" "time" + corev1 "k8s.io/api/core/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/wait" + "k8s.io/utils/ptr" + + "github.com/cert-manager/cert-manager/e2e-tests/framework/addon/base" + "github.com/cert-manager/cert-manager/e2e-tests/framework/addon/chart" + "github.com/cert-manager/cert-manager/e2e-tests/framework/addon/internal" + "github.com/cert-manager/cert-manager/e2e-tests/framework/config" + "github.com/cert-manager/cert-manager/pkg/util/pki" +) - "github.com/cert-manager/cert-manager/test/e2e/framework/addon/base" - "github.com/cert-manager/cert-manager/test/e2e/framework/addon/chart" - "github.com/cert-manager/cert-manager/test/e2e/framework/config" +const ( + vaultHelmChartRepo = "https://helm.releases.hashicorp.com" + vaultHelmChartVersion = "0.25.0" ) // Vault describes the configuration details for an instance of Vault // deployed to the test cluster type Vault struct { - config *config.Config - chart *chart.Chart + chart *chart.Chart Base *base.Base @@ -52,123 +64,352 @@ type Vault struct { // Namespace is the namespace to deploy Vault into Namespace string + // EnforceMtls defines if mTLS is enforced in the vault server + // and clients must provide client certificates + EnforceMtls bool + + // Proxy is the proxy that can be used to connect to Vault + proxy *proxy + + // vaultCert and vaultCertPrivateKey are the certificate and private key + // used to sign the Vault serving certificate + vaultCert, vaultCertPrivateKey []byte + details Details } +var _ internal.Addon = &Vault{} + type Details struct { - // Kubectl is the path to kubectl - Kubectl string + // URL is the url that can be used to connect to Vault inside the cluster + URL string - // Host is the hostname that can be used to connect to Vault - Host string + // ProxyURL is the url that can be used to connect to Vault outside of the cluster + ProxyURL string - // PodName is the name of the Vault pod - PodName string + // VaultCA is the CA used to sign the vault serving certificate + VaultCA []byte - // Namespace is the namespace vault has been deployed into - Namespace string + // VaultClientCertificate is the certificate used by clients when connecting to vault + VaultClientCertificate []byte - // VaultCA is the CA used to sign the vault serving certificate - VaultCA []byte - VaultCAPrivateKey []byte + // VaultClientPrivateKey is the private key used by clients when connecting to vault + VaultClientPrivateKey []byte + + // EnforceMtls defines if mTLS is enforced in the vault server + // and clients must provide client certificates + EnforceMtls bool +} + +func convertInterfaceToDetails(unmarshalled interface{}) (Details, error) { + jsonEncoded, err := json.Marshal(unmarshalled) + if err != nil { + return Details{}, err + } + + var details Details + err = json.Unmarshal(jsonEncoded, &details) //nolint:musttag + if err != nil { + return Details{}, err + } - // VaultCert is the vault serving certificate - VaultCert []byte - VaultCertPrivateKey []byte + return details, nil } -func (v *Vault) Setup(cfg *config.Config) error { +func (v *Vault) Setup(ctx context.Context, cfg *config.Config, leaderData ...internal.AddonTransferableData) (internal.AddonTransferableData, error) { if v.Name == "" { - return fmt.Errorf("Name field must be set on Vault addon") + return nil, fmt.Errorf("'Name' field must be set on Vault addon") } if v.Namespace == "" { // TODO: in non-global instances, we could generate a new namespace just // for this addon to be used from. - return fmt.Errorf("Namespace name must be specified") + return nil, fmt.Errorf("'Namespace' name must be specified") } if v.Base == nil { - return fmt.Errorf("Base field must be set on Vault addon") + return nil, fmt.Errorf("'Base' field must be set on Vault addon") } - var err error - // Generate CA details before deploying the chart - v.details.VaultCA, v.details.VaultCAPrivateKey, err = v.GenerateCA() - if err != nil { - return err - } - v.details.VaultCert, v.details.VaultCertPrivateKey, err = v.generateCert() - if err != nil { - return err - } - if cfg.Kubectl == "" { - return fmt.Errorf("path to kubectl must be set") - } - v.details.Kubectl = cfg.Kubectl v.chart = &chart.Chart{ - Base: v.Base, - ReleaseName: "chart-vault-" + v.Name, - Namespace: v.Namespace, - ChartName: cfg.RepoRoot + "/test/e2e/charts/vault", - // doesn't matter when installing from disk - ChartVersion: "0", + Base: v.Base, + ReleaseName: "chart-vault-" + v.Name, + Namespace: v.Namespace, + ChartName: "hashicorp/vault", + ChartVersion: vaultHelmChartVersion, + Repo: chart.Repo{ + Name: "hashicorp", + Url: vaultHelmChartRepo, + }, Vars: []chart.StringTuple{ { - Key: "vault.publicKey", - Value: base64.StdEncoding.EncodeToString(v.details.VaultCert), + Key: "injector.enabled", + Value: "false", + }, + { + Key: "server.dataStorage.enabled", + Value: "false", + }, + { + Key: "server.standalone.enabled", + Value: "true", + }, + // configure dev mode + // we cannot use the 'server.dev.enabled' Helm value here, because as soon + // as you enable 'server.dev' you cannot specify a config file anymore + { + Key: "server.extraArgs", + Value: "-dev-tls -dev-listen-address=[::]:8202", + }, + // configure root token + { + Key: "server.extraEnvironmentVars.VAULT_DEV_ROOT_TOKEN_ID", + Value: "vault-root-token", + }, + // configure client certificates used in the readiness/liveness probes exec commands + { + Key: "server.extraEnvironmentVars.VAULT_CLIENT_CERT", + Value: "/vault/tls/client.crt", + }, + { + Key: "server.extraEnvironmentVars.VAULT_CLIENT_KEY", + Value: "/vault/tls/client.key", + }, + // configure tls certificate + { + Key: "global.tlsDisable", + Value: "false", + }, + { + Key: "server.standalone.config", + Value: fmt.Sprintf(` + listener "tcp" { + address = "[::]:8200" + cluster_address = "[::]:8201" + tls_disable = false + tls_client_ca_file = "/vault/tls/ca.crt" + tls_cert_file = "/vault/tls/server.crt" + tls_key_file = "/vault/tls/server.key" + tls_require_and_verify_client_cert = %s + }`, strconv.FormatBool(v.EnforceMtls)), + }, + { + Key: "server.volumes[0].name", + Value: "vault-tls", + }, + { + Key: "server.volumes[0].secret.secretName", + Value: "vault-tls", }, { - Key: "vault.privateKey", - Value: base64.StdEncoding.EncodeToString(v.details.VaultCertPrivateKey), + Key: "server.volumeMounts[0].name", + Value: "vault-tls", + }, + { + Key: "server.volumeMounts[0].mountPath", + Value: "/vault/tls", + }, + // configure resource requests + { + Key: "server.resources.requests.cpu", + Value: "50m", + }, + { + Key: "server.resources.requests.memory", + Value: "64Mi", }, }, } - err = v.chart.Setup(cfg) + + // When the tests have been launched by make, the cluster will be a kind + // cluster into which we will have loaded some locally cached Vault images. + // But we also want people to be able to compile the E2E test binary and run + // the tests on their chosen cluster, in which case we do not override the + // Vault image and the default chart image will be downloaded and run + // instead. + // E2E_VAULT_IMAGE is exported by `make/e2e-setup.mk`. + if vaultImage := os.Getenv("E2E_VAULT_IMAGE"); vaultImage != "" { + parts := strings.Split(vaultImage, ":") + vaultImageRepository := parts[0] + vaultImageTag := parts[1] + v.chart.Vars = append( + v.chart.Vars, + []chart.StringTuple{ + // configure image and repo + { + Key: "server.image.repository", + Value: vaultImageRepository, + }, + { + Key: "server.image.tag", + Value: vaultImageTag, + }, + { + Key: "server.image.pullPolicy", + Value: "Never", + }, + }..., + ) + } + + // Set E2E_OPENSHIFT=true if you're running the E2E tests against an OpenShift + // cluster. + // OpenShift requires some different settings. See + // https://developer.hashicorp.com/vault/tutorials/kubernetes/kubernetes-openshift + if os.Getenv("E2E_OPENSHIFT") == "true" { + v.chart.Vars = append( + v.chart.Vars, + []chart.StringTuple{ + { + Key: "global.openshift", + Value: "true", + }, + }..., + ) + } + + _, err := v.chart.Setup(ctx, cfg) if err != nil { - return err + return nil, err } - return nil + + if len(leaderData) == 1 { + details, err := convertInterfaceToDetails(leaderData[0]) + if err != nil { + return nil, fmt.Errorf("leader data is not of type Details: %w", err) + } + v.details = details + } else { + dnsName := fmt.Sprintf("%s.%s.svc.cluster.local", v.chart.ReleaseName, v.Namespace) + + // Generate CA details before deploying the chart + vaultCA, vaultCAPrivateKey, err := GenerateCA() + if err != nil { + return nil, err + } + v.details.VaultCA = vaultCA + + v.vaultCert, v.vaultCertPrivateKey = generateVaultServingCert(vaultCA, vaultCAPrivateKey, dnsName) + + vaultClientCertificate, vaultClientPrivateKey := generateVaultClientCert(vaultCA, vaultCAPrivateKey) + v.details.VaultClientCertificate = vaultClientCertificate + v.details.VaultClientPrivateKey = vaultClientPrivateKey + v.details.EnforceMtls = v.EnforceMtls + + if cfg.Kubectl == "" { + return nil, fmt.Errorf("path to kubectl must be specified") + } + v.proxy = newProxy( + ctx, + v.Base.Details().KubeClient, + v.Base.Details().KubeConfig, + v.Namespace, + fmt.Sprintf("%s-0", v.chart.ReleaseName), + ) + + v.details.URL = fmt.Sprintf("https://%s", net.JoinHostPort(dnsName, "8200")) + v.details.ProxyURL = fmt.Sprintf("https://%s", net.JoinHostPort("127.0.0.1", strconv.Itoa(v.proxy.listenPort))) + } + + return v.details, nil } // Provision will actually deploy this instance of Vault to the cluster. -func (v *Vault) Provision() error { - err := v.chart.Provision() +func (v *Vault) Provision(ctx context.Context) error { + kubeClient := v.Base.Details().KubeClient + + // If the namespace doesn't exist, create it + _, err := kubeClient.CoreV1().Namespaces().Create(ctx, &corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: v.Namespace, + }, + }, metav1.CreateOptions{}) + if err != nil && !apierrors.IsAlreadyExists(err) { + return err + } + + // Create the TLS secret + tlsSecret := &corev1.Secret{ + TypeMeta: metav1.TypeMeta{ + Kind: "secret", + APIVersion: "v1", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "vault-tls", + Namespace: v.Namespace, + }, + StringData: map[string]string{ + "ca.crt": string(v.details.VaultCA), + "server.crt": string(v.vaultCert), + "server.key": string(v.vaultCertPrivateKey), + "client.crt": string(v.details.VaultClientCertificate), + "client.key": string(v.details.VaultClientPrivateKey), + }, + } + _, err = kubeClient.CoreV1().Secrets(v.Namespace).Create(ctx, tlsSecret, metav1.CreateOptions{}) if err != nil { return err } - // otherwise lookup the newly created pods name - kubeClient := v.Base.Details().KubeClient + // Deploy the vault chart + err = v.chart.Provision(ctx) + if err != nil { + return err + } + + // Wait for the vault pod to be ready + { + allContainersReady := func(pod *corev1.Pod) bool { + for _, containerStatus := range pod.Status.ContainerStatuses { + if !containerStatus.Ready { + return false + } + } + return true + } + + var lastError error + err = wait.PollUntilContextTimeout(ctx, 5*time.Second, 5*time.Minute, true, func(ctx context.Context) (bool, error) { + pod, err := kubeClient.CoreV1().Pods(v.proxy.podNamespace).Get(ctx, v.proxy.podName, metav1.GetOptions{}) + if err != nil && !apierrors.IsNotFound(err) { + return false, err + } - retries := 5 - for { - pods, err := kubeClient.CoreV1().Pods(v.Namespace).List(context.TODO(), metav1.ListOptions{ - LabelSelector: "app=vault", + if err != nil && apierrors.IsNotFound(err) { + lastError = fmt.Errorf("pod not found") + return false, nil + } + + if pod.Status.Phase != corev1.PodRunning { + lastError = fmt.Errorf("pod is not running, current phase: %s", pod.Status.Phase) + return false, nil + } + + if !allContainersReady(pod) { + lastError = fmt.Errorf("pod has containers that are not ready: %v", pod.Status.ContainerStatuses) + return false, nil + } + + return true, nil }) if err != nil { - return err - } - if len(pods.Items) == 0 { - if retries == 0 { - return fmt.Errorf("failed to create vault pod within 10s") + logs, err := kubeClient. + CoreV1(). + Pods(v.proxy.podNamespace). + GetLogs(v.proxy.podName, &corev1.PodLogOptions{ + TailLines: ptr.To(int64(100)), + }). + DoRaw(ctx) + + if err != nil { + return fmt.Errorf("error waiting for vault pod to be ready: %w; failed to retrieve logs: %w", lastError, err) } - retries-- - time.Sleep(time.Second * 2) - continue - } - vaultPod := pods.Items[0] - // If the vault pod exists but is just waiting to be created, we allow - // it a bit longer. - if len(vaultPod.Status.ContainerStatuses) == 0 || !vaultPod.Status.ContainerStatuses[0].Ready { - retries-- - time.Sleep(time.Second * 5) - continue + + return fmt.Errorf("error waiting for vault pod to be ready: %w; logs: %s", lastError, logs) } - v.details.PodName = vaultPod.Name - break } - v.details.Namespace = v.Namespace - v.details.Host = fmt.Sprintf("https://vault.%s:8200", v.Namespace) + if err := v.proxy.start(); err != nil { + return err + } return nil } @@ -179,89 +420,146 @@ func (v *Vault) Details() *Details { } // Deprovision will destroy this instance of Vault -func (v *Vault) Deprovision() error { - return v.chart.Deprovision() +func (v *Vault) Deprovision(ctx context.Context) error { + if err := v.proxy.stop(ctx); err != nil { + return err + } + + kubeClient := v.Base.Details().KubeClient + err := kubeClient.CoreV1().Secrets(v.Namespace).Delete(ctx, "vault-tls", metav1.DeleteOptions{}) + if err != nil { + return err + } + + return v.chart.Deprovision(ctx) } func (v *Vault) SupportsGlobal() bool { - // We don't support global instances of vault currently as we need to generate - // PKI details at deploy time and make them available to tests. - return false + return v.chart.SupportsGlobal() } -func (v *Vault) Logs() (map[string]string, error) { - return v.chart.Logs() +func (v *Vault) Logs(ctx context.Context) (map[string]string, error) { + return v.chart.Logs(ctx) } -func (v *Vault) GenerateCA() ([]byte, []byte, error) { - ca := &x509.Certificate{ - SerialNumber: big.NewInt(1653), +func generateVaultServingCert(vaultCA []byte, vaultCAPrivateKey []byte, dnsName string) ([]byte, []byte) { + catls, err := tls.X509KeyPair(vaultCA, vaultCAPrivateKey) + if err != nil { + panic(err) + } + + ca, err := x509.ParseCertificate(catls.Certificate[0]) + if err != nil { + panic(err) + } + + cert := &x509.Certificate{ + SerialNumber: big.NewInt(1658), Subject: pkix.Name{ - Organization: []string{"cert-manager test"}, + CommonName: dnsName, + Organization: []string{"cert-manager vault server"}, }, - NotBefore: time.Now(), - NotAfter: time.Now().AddDate(10, 0, 0), - IsCA: true, - ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth}, - KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, - BasicConstraintsValid: true, + NotBefore: time.Now(), + NotAfter: time.Now().AddDate(10, 0, 0), + SubjectKeyId: []byte{1, 2, 3, 4, 6}, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth}, + KeyUsage: x509.KeyUsageDigitalSignature, + IPAddresses: []net.IP{net.IPv4(127, 0, 0, 1)}, + DNSNames: []string{dnsName}, } - privateKey, _ := rsa.GenerateKey(rand.Reader, 2048) - pubKey := &privateKey.PublicKey - caBytes, err := x509.CreateCertificate(rand.Reader, ca, ca, pubKey, privateKey) + privateKey, err := pki.GenerateRSAPrivateKey(2048) if err != nil { - return nil, nil, fmt.Errorf("create ca failed: %v", err) + panic(err) } - return encodePublicKey(caBytes), encodePrivateKey(privateKey), nil + certBytes, err := x509.CreateCertificate(rand.Reader, cert, ca, &privateKey.PublicKey, catls.PrivateKey) + if err != nil { + panic(err) + } + + encodedPrivateKey, err := pki.EncodePKCS8PrivateKey(privateKey) + if err != nil { + panic(err) + } + + return encodePublicKey(certBytes), encodedPrivateKey } -func (v *Vault) generateCert() ([]byte, []byte, error) { - catls, err := tls.X509KeyPair(v.details.VaultCA, v.details.VaultCAPrivateKey) +func generateVaultClientCert(vaultCA []byte, vaultCAPrivateKey []byte) ([]byte, []byte) { + catls, err := tls.X509KeyPair(vaultCA, vaultCAPrivateKey) if err != nil { - return nil, nil, fmt.Errorf("parsing ca key pair failed: %s", err.Error()) + panic(err) } + ca, err := x509.ParseCertificate(catls.Certificate[0]) if err != nil { - return nil, nil, fmt.Errorf("parsing ca failed: %s", err.Error()) + panic(err) } cert := &x509.Certificate{ SerialNumber: big.NewInt(1658), Subject: pkix.Name{ - CommonName: "vault." + v.Namespace, - Organization: []string{"cert-manager vault server"}, + CommonName: "cert-manager vault client", + Organization: []string{"cert-manager"}, }, NotBefore: time.Now(), NotAfter: time.Now().AddDate(10, 0, 0), SubjectKeyId: []byte{1, 2, 3, 4, 6}, - ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth}, - KeyUsage: x509.KeyUsageDigitalSignature, - IPAddresses: []net.IP{net.IPv4(127, 0, 0, 1)}, - DNSNames: []string{"vault." + v.Namespace}, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth}, + KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment, } - privateKey, err := rsa.GenerateKey(rand.Reader, 2048) + + privateKey, err := pki.GenerateRSAPrivateKey(2048) if err != nil { - return nil, nil, fmt.Errorf("private key generation failed: %s", err.Error()) + panic(err) } - publicKey := &privateKey.PublicKey + certBytes, err := x509.CreateCertificate(rand.Reader, cert, ca, &privateKey.PublicKey, catls.PrivateKey) + if err != nil { + panic(err) + } - certBytes, err := x509.CreateCertificate(rand.Reader, cert, ca, publicKey, catls.PrivateKey) + encodedPrivateKey, err := pki.EncodePKCS8PrivateKey(privateKey) + if err != nil { + panic(err) + } + + return encodePublicKey(certBytes), encodedPrivateKey +} + +func GenerateCA() ([]byte, []byte, error) { + ca := &x509.Certificate{ + SerialNumber: big.NewInt(1653), + Subject: pkix.Name{ + Organization: []string{"cert-manager test"}, + }, + NotBefore: time.Now(), + NotAfter: time.Now().AddDate(10, 0, 0), + IsCA: true, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth}, + KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, + BasicConstraintsValid: true, + } + + privateKey, err := pki.GenerateRSAPrivateKey(2048) if err != nil { return nil, nil, err } - return encodePublicKey(certBytes), encodePrivateKey(privateKey), nil + caBytes, err := x509.CreateCertificate(rand.Reader, ca, ca, &privateKey.PublicKey, privateKey) + if err != nil { + return nil, nil, err + } + + encodedPrivateKey, err := pki.EncodePKCS8PrivateKey(privateKey) + if err != nil { + return nil, nil, err + } + + return encodePublicKey(caBytes), encodedPrivateKey, nil } func encodePublicKey(pub []byte) []byte { return pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: pub}) } - -func encodePrivateKey(priv *rsa.PrivateKey) []byte { - block := &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(priv)} - - return pem.EncodeToMemory(block) -} diff --git a/test/e2e/framework/addon/venafi/cloud.go b/test/e2e/framework/addon/venafi/cloud.go index 213fbbefd84..1ecdf7dad19 100644 --- a/test/e2e/framework/addon/venafi/cloud.go +++ b/test/e2e/framework/addon/venafi/cloud.go @@ -23,11 +23,12 @@ import ( corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "github.com/cert-manager/cert-manager/e2e-tests/framework/addon/base" + "github.com/cert-manager/cert-manager/e2e-tests/framework/addon/internal" + "github.com/cert-manager/cert-manager/e2e-tests/framework/config" + "github.com/cert-manager/cert-manager/e2e-tests/framework/util/errors" cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" - "github.com/cert-manager/cert-manager/test/e2e/framework/addon/base" - "github.com/cert-manager/cert-manager/test/e2e/framework/config" - "github.com/cert-manager/cert-manager/test/e2e/framework/util/errors" ) type VenafiCloud struct { @@ -42,32 +43,34 @@ type VenafiCloud struct { createdSecret *corev1.Secret } +var _ internal.Addon = &VenafiCloud{} + type CloudDetails struct { issuerTemplate cmapi.VenafiIssuer } -func (v *VenafiCloud) Setup(cfg *config.Config) error { +func (v *VenafiCloud) Setup(ctx context.Context, cfg *config.Config, _ ...internal.AddonTransferableData) (internal.AddonTransferableData, error) { v.config = cfg if v.Base == nil { v.Base = &base.Base{} - err := v.Base.Setup(cfg) + _, err := v.Base.Setup(ctx, cfg) if err != nil { - return err + return nil, err } } if v.config.Addons.Venafi.Cloud.Zone == "" { - return errors.NewSkip(fmt.Errorf("Venafi Cloud Zone must be set")) + return nil, errors.NewSkip(fmt.Errorf("Venafi Cloud Zone must be set")) } if v.config.Addons.Venafi.Cloud.APIToken == "" { - return errors.NewSkip(fmt.Errorf("Venafi Cloud APIToken must be set")) + return nil, errors.NewSkip(fmt.Errorf("Venafi Cloud APIToken must be set")) } - return nil + return nil, nil } -func (v *VenafiCloud) Provision() error { +func (v *VenafiCloud) Provision(ctx context.Context) error { secret := &corev1.Secret{ ObjectMeta: metav1.ObjectMeta{ GenerateName: "cm-e2e-venafi-cloud-", @@ -78,7 +81,7 @@ func (v *VenafiCloud) Provision() error { }, } - s, err := v.Base.Details().KubeClient.CoreV1().Secrets(v.Namespace).Create(context.TODO(), secret, metav1.CreateOptions{}) + s, err := v.Base.Details().KubeClient.CoreV1().Secrets(v.Namespace).Create(ctx, secret, metav1.CreateOptions{}) if err != nil { return err } @@ -103,12 +106,12 @@ func (v *VenafiCloud) Details() *CloudDetails { return &v.details } -func (v *VenafiCloud) Deprovision() error { - return v.Base.Details().KubeClient.CoreV1().Secrets(v.createdSecret.Namespace).Delete(context.TODO(), v.createdSecret.Name, metav1.DeleteOptions{}) +func (v *VenafiCloud) Deprovision(ctx context.Context) error { + return v.Base.Details().KubeClient.CoreV1().Secrets(v.createdSecret.Namespace).Delete(ctx, v.createdSecret.Name, metav1.DeleteOptions{}) } func (v *VenafiCloud) SupportsGlobal() bool { - return true + return false } func (t *CloudDetails) BuildIssuer() *cmapi.Issuer { @@ -138,9 +141,9 @@ func (t *CloudDetails) BuildClusterIssuer() *cmapi.ClusterIssuer { } // SetAPIKey sets the Secret data["apikey"] value -func (v *VenafiCloud) SetAPIKey(token string) error { +func (v *VenafiCloud) SetAPIKey(ctx context.Context, token string) error { v.createdSecret.Data["apikey"] = []byte(token) - s, err := v.Base.Details().KubeClient.CoreV1().Secrets(v.Namespace).Update(context.TODO(), v.createdSecret, metav1.UpdateOptions{}) + s, err := v.Base.Details().KubeClient.CoreV1().Secrets(v.Namespace).Update(ctx, v.createdSecret, metav1.UpdateOptions{}) if err != nil { return err } diff --git a/test/e2e/framework/addon/venafi/tpp.go b/test/e2e/framework/addon/venafi/tpp.go index e1cdb06f78c..c89d3da7b44 100644 --- a/test/e2e/framework/addon/venafi/tpp.go +++ b/test/e2e/framework/addon/venafi/tpp.go @@ -23,11 +23,12 @@ import ( corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "github.com/cert-manager/cert-manager/e2e-tests/framework/addon/base" + "github.com/cert-manager/cert-manager/e2e-tests/framework/addon/internal" + "github.com/cert-manager/cert-manager/e2e-tests/framework/config" + "github.com/cert-manager/cert-manager/e2e-tests/framework/util/errors" cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" - "github.com/cert-manager/cert-manager/test/e2e/framework/addon/base" - "github.com/cert-manager/cert-manager/test/e2e/framework/config" - "github.com/cert-manager/cert-manager/test/e2e/framework/util/errors" ) type VenafiTPP struct { @@ -42,41 +43,43 @@ type VenafiTPP struct { createdSecret *corev1.Secret } +var _ internal.Addon = &VenafiTPP{} + type TPPDetails struct { issuerTemplate cmapi.VenafiIssuer } -func (v *VenafiTPP) Setup(cfg *config.Config) error { +func (v *VenafiTPP) Setup(ctx context.Context, cfg *config.Config, _ ...internal.AddonTransferableData) (internal.AddonTransferableData, error) { v.config = cfg if v.Base == nil { v.Base = &base.Base{} - err := v.Base.Setup(cfg) + _, err := v.Base.Setup(ctx, cfg) if err != nil { - return err + return nil, err } } if v.config.Addons.Venafi.TPP.URL == "" { - return errors.NewSkip(fmt.Errorf("Venafi TPP URL must be set")) + return nil, errors.NewSkip(fmt.Errorf("Venafi TPP URL must be set")) } if v.config.Addons.Venafi.TPP.Zone == "" { - return errors.NewSkip(fmt.Errorf("Venafi TPP Zone must be set")) + return nil, errors.NewSkip(fmt.Errorf("Venafi TPP Zone must be set")) } if v.config.Addons.Venafi.TPP.AccessToken == "" { if v.config.Addons.Venafi.TPP.Username == "" { - return errors.NewSkip(fmt.Errorf("Venafi TPP requires either an access-token or username-password to be set: missing username")) + return nil, errors.NewSkip(fmt.Errorf("Venafi TPP requires either an access-token or username-password to be set: missing username")) } if v.config.Addons.Venafi.TPP.Password == "" { - return errors.NewSkip(fmt.Errorf("Venafi TPP requires either an access-token or username-password to be set: missing password")) + return nil, errors.NewSkip(fmt.Errorf("Venafi TPP requires either an access-token or username-password to be set: missing password")) } } - return nil + return nil, nil } -func (v *VenafiTPP) Provision() error { +func (v *VenafiTPP) Provision(ctx context.Context) error { secret := &corev1.Secret{ ObjectMeta: metav1.ObjectMeta{ GenerateName: "cm-e2e-venafi-", @@ -89,7 +92,7 @@ func (v *VenafiTPP) Provision() error { }, } - s, err := v.Base.Details().KubeClient.CoreV1().Secrets(v.Namespace).Create(context.TODO(), secret, metav1.CreateOptions{}) + s, err := v.Base.Details().KubeClient.CoreV1().Secrets(v.Namespace).Create(ctx, secret, metav1.CreateOptions{}) if err != nil { return err } @@ -111,12 +114,16 @@ func (v *VenafiTPP) Details() *TPPDetails { return &v.details } -func (v *VenafiTPP) Deprovision() error { - return v.Base.Details().KubeClient.CoreV1().Secrets(v.createdSecret.Namespace).Delete(context.TODO(), v.createdSecret.Name, metav1.DeleteOptions{}) +func (v *VenafiTPP) Deprovision(ctx context.Context) error { + if v.createdSecret == nil { + return nil + } + + return v.Base.Details().KubeClient.CoreV1().Secrets(v.createdSecret.Namespace).Delete(ctx, v.createdSecret.Name, metav1.DeleteOptions{}) } func (v *VenafiTPP) SupportsGlobal() bool { - return true + return false } func (t *TPPDetails) BuildIssuer() *cmapi.Issuer { @@ -146,9 +153,9 @@ func (t *TPPDetails) BuildClusterIssuer() *cmapi.ClusterIssuer { } // SetAccessToken sets the Secret data["access-token"] value -func (v *VenafiTPP) SetAccessToken(token string) error { +func (v *VenafiTPP) SetAccessToken(ctx context.Context, token string) error { v.createdSecret.Data["access-token"] = []byte(token) - s, err := v.Base.Details().KubeClient.CoreV1().Secrets(v.Namespace).Update(context.TODO(), v.createdSecret, metav1.UpdateOptions{}) + s, err := v.Base.Details().KubeClient.CoreV1().Secrets(v.Namespace).Update(ctx, v.createdSecret, metav1.UpdateOptions{}) if err != nil { return err } diff --git a/test/e2e/framework/cleanup.go b/test/e2e/framework/cleanup.go index b3063ba430b..3da587d209b 100644 --- a/test/e2e/framework/cleanup.go +++ b/test/e2e/framework/cleanup.go @@ -18,17 +18,20 @@ limitations under the License. package framework -import "sync" +import ( + "context" + "sync" +) type CleanupActionHandle *int var cleanupActionsLock sync.Mutex -var cleanupActions = map[CleanupActionHandle]func(){} +var cleanupActions = map[CleanupActionHandle]func(ctx context.Context){} // AddCleanupAction installs a function that will be called in the event of the // whole test being terminated. This allows arbitrary pieces of the overall // test to hook into SynchronizedAfterSuite(). -func AddCleanupAction(fn func()) CleanupActionHandle { +func AddCleanupAction(fn func(ctx context.Context)) CleanupActionHandle { p := CleanupActionHandle(new(int)) cleanupActionsLock.Lock() defer cleanupActionsLock.Unlock() @@ -47,8 +50,8 @@ func RemoveCleanupAction(p CleanupActionHandle) { // RunCleanupActions runs all functions installed by AddCleanupAction. It does // not remove them (see RemoveCleanupAction) but it does run unlocked, so they // may remove themselves. -func RunCleanupActions() { - list := []func(){} +func RunCleanupActions(ctx context.Context) { + list := []func(ctx context.Context){} func() { cleanupActionsLock.Lock() defer cleanupActionsLock.Unlock() @@ -58,6 +61,6 @@ func RunCleanupActions() { }() // Run unlocked. for _, fn := range list { - fn() + fn(ctx) } } diff --git a/test/e2e/framework/config/helm.go b/test/e2e/framework/config/helm.go index ebdafc10c16..282ddec61da 100644 --- a/test/e2e/framework/config/helm.go +++ b/test/e2e/framework/config/helm.go @@ -17,9 +17,8 @@ limitations under the License. package config import ( - "fmt" - "flag" + "fmt" ) type Helm struct { diff --git a/test/e2e/framework/config/suite.go b/test/e2e/framework/config/suite.go index 611deb0c8ec..64570923acf 100644 --- a/test/e2e/framework/config/suite.go +++ b/test/e2e/framework/config/suite.go @@ -17,9 +17,8 @@ limitations under the License. package config import ( - "os" - "flag" + "os" ) type Suite struct { diff --git a/test/e2e/framework/framework.go b/test/e2e/framework/framework.go index 6a7509762b5..35691357a4e 100644 --- a/test/e2e/framework/framework.go +++ b/test/e2e/framework/framework.go @@ -18,44 +18,31 @@ package framework import ( "context" - "time" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" + "slices" api "k8s.io/api/core/v1" apiext "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" apiextcs "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/client-go/kubernetes" kscheme "k8s.io/client-go/kubernetes/scheme" "k8s.io/client-go/rest" apireg "k8s.io/kube-aggregator/pkg/apis/apiregistration/v1" crclient "sigs.k8s.io/controller-runtime/pkg/client" - gwapi "sigs.k8s.io/gateway-api/pkg/client/clientset/versioned" - - v1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" + gwapiclient "sigs.k8s.io/gateway-api/pkg/client/clientset/versioned" + + "github.com/cert-manager/cert-manager/e2e-tests/framework/addon" + "github.com/cert-manager/cert-manager/e2e-tests/framework/config" + "github.com/cert-manager/cert-manager/e2e-tests/framework/helper" + "github.com/cert-manager/cert-manager/e2e-tests/framework/log" + "github.com/cert-manager/cert-manager/e2e-tests/framework/util" + "github.com/cert-manager/cert-manager/e2e-tests/framework/util/errors" clientset "github.com/cert-manager/cert-manager/pkg/client/clientset/versioned" certmgrscheme "github.com/cert-manager/cert-manager/pkg/client/clientset/versioned/scheme" - "github.com/cert-manager/cert-manager/pkg/util/pki" - "github.com/cert-manager/cert-manager/test/e2e/framework/addon" - "github.com/cert-manager/cert-manager/test/e2e/framework/config" - "github.com/cert-manager/cert-manager/test/e2e/framework/helper" - "github.com/cert-manager/cert-manager/test/e2e/framework/log" - "github.com/cert-manager/cert-manager/test/e2e/framework/util" - "github.com/cert-manager/cert-manager/test/e2e/framework/util/errors" -) - -// TODO: this really should be done somewhere in cert-manager proper -var Scheme = runtime.NewScheme() -func init() { - kscheme.AddToScheme(Scheme) - certmgrscheme.AddToScheme(Scheme) - apiext.AddToScheme(Scheme) - apireg.AddToScheme(Scheme) -} + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) // DefaultConfig contains the default shared config the is likely parsed from // command line arguments. @@ -69,10 +56,12 @@ type Framework struct { // KubeClientConfig which was used to create the connection. KubeClientConfig *rest.Config + // Scheme which is used to encode/decode kubernetes objects. + Scheme *runtime.Scheme // Kubernetes API clientsets KubeClientSet kubernetes.Interface - GWClientSet gwapi.Interface + GWClientSet gwapiclient.Interface CertManagerClientSet clientset.Interface APIExtensionsClientSet apiextcs.Interface @@ -93,7 +82,7 @@ type Framework struct { // NewDefaultFramework makes a new framework for you, similar to NewFramework. // It uses the suite-wide 'DefaultConfig' which should be populated by the -// testing harness in test/e2e/e2e_test.go +// testing harness in e2e-tests/e2e_test.go func NewDefaultFramework(baseName string) *Framework { return NewFramework(baseName, DefaultConfig) } @@ -102,9 +91,16 @@ func NewDefaultFramework(baseName string) *Framework { // you (you can write additional before/after each functions). // It uses the config provided to it for the duration of the tests. func NewFramework(baseName string, cfg *config.Config) *Framework { + scheme := runtime.NewScheme() + Expect(kscheme.AddToScheme(scheme)).NotTo(HaveOccurred()) + Expect(certmgrscheme.AddToScheme(scheme)).NotTo(HaveOccurred()) + Expect(apiext.AddToScheme(scheme)).NotTo(HaveOccurred()) + Expect(apireg.AddToScheme(scheme)).NotTo(HaveOccurred()) + f := &Framework{ Config: cfg, BaseName: baseName, + Scheme: scheme, } f.helper = helper.NewHelper(cfg) @@ -115,12 +111,16 @@ func NewFramework(baseName string, cfg *config.Config) *Framework { } // BeforeEach gets a client and makes a namespace. -func (f *Framework) BeforeEach() { +func (f *Framework) BeforeEach(ctx context.Context) { f.cleanupHandle = AddCleanupAction(f.AfterEach) By("Creating a kubernetes client") kubeConfig, err := util.LoadConfig(f.Config.KubeConfig, f.Config.KubeContext) Expect(err).NotTo(HaveOccurred()) + + kubeConfig.Burst = 9000 + kubeConfig.QPS = 9000 + f.KubeClientConfig = kubeConfig f.KubeClientSet, err = kubernetes.NewForConfig(kubeConfig) @@ -135,21 +135,21 @@ func (f *Framework) BeforeEach() { Expect(err).NotTo(HaveOccurred()) By("Creating a controller-runtime client") - f.CRClient, err = crclient.New(kubeConfig, crclient.Options{Scheme: Scheme}) + f.CRClient, err = crclient.New(kubeConfig, crclient.Options{Scheme: f.Scheme}) Expect(err).NotTo(HaveOccurred()) By("Creating a gateway-api client") - f.GWClientSet, err = gwapi.NewForConfig(kubeConfig) + f.GWClientSet, err = gwapiclient.NewForConfig(kubeConfig) Expect(err).NotTo(HaveOccurred()) By("Building a namespace api object") - f.Namespace, err = f.CreateKubeNamespace(f.BaseName) + f.Namespace, err = f.CreateKubeNamespace(ctx, f.BaseName) Expect(err).NotTo(HaveOccurred()) By("Using the namespace " + f.Namespace.Name) By("Building a ResourceQuota api object") - _, err = f.CreateKubeResourceQuota() + _, err = f.CreateKubeResourceQuota(ctx) Expect(err).NotTo(HaveOccurred()) f.helper.CMClient = f.CertManagerClientSet @@ -157,32 +157,31 @@ func (f *Framework) BeforeEach() { } // AfterEach deletes the namespace, after reading its events. -func (f *Framework) AfterEach() { +func (f *Framework) AfterEach(ctx context.Context) { RemoveCleanupAction(f.cleanupHandle) - f.printAddonLogs() + f.printAddonLogs(ctx) if !f.Config.Cleanup { return } - for i := len(f.requiredAddons) - 1; i >= 0; i-- { - a := f.requiredAddons[i] + for _, a := range slices.Backward(f.requiredAddons) { By("De-provisioning test-scoped addon") - err := a.Deprovision() + err := a.Deprovision(ctx) Expect(err).NotTo(HaveOccurred()) } By("Deleting test namespace") - err := f.DeleteKubeNamespace(f.Namespace.Name) + err := f.DeleteKubeNamespace(ctx, f.Namespace.Name) Expect(err).NotTo(HaveOccurred()) } -func (f *Framework) printAddonLogs() { +func (f *Framework) printAddonLogs(ctx context.Context) { if CurrentSpecReport().Failed() { for _, a := range f.requiredAddons { if a, ok := a.(loggableAddon); ok { - l, err := a.Logs() + l, err := a.Logs(ctx) Expect(err).NotTo(HaveOccurred()) for ident, l := range l { @@ -201,15 +200,15 @@ func (f *Framework) printAddonLogs() { // are present in the 'globals' variable, as they will not be Provisioned properly // otherwise. func (f *Framework) RequireGlobalAddon(a addon.Addon) { - BeforeEach(func() { + BeforeEach(func(ctx context.Context) { By("Setting up access for global shared addon") - err := a.Setup(f.Config) + _, err := a.Setup(ctx, f.Config) Expect(err).NotTo(HaveOccurred()) }) } type loggableAddon interface { - Logs() (map[string]string, error) + Logs(ctx context.Context) (map[string]string, error) } // RequireAddon calls the Setup and Provision method on the given addon, failing @@ -217,15 +216,15 @@ type loggableAddon interface { func (f *Framework) RequireAddon(a addon.Addon) { f.requiredAddons = append(f.requiredAddons, a) - BeforeEach(func() { + BeforeEach(func(ctx context.Context) { By("Provisioning test-scoped addon") - err := a.Setup(f.Config) + _, err := a.Setup(ctx, f.Config) if errors.IsSkip(err) { Skipf("Skipping test as addon could not be setup: %v", err) } Expect(err).NotTo(HaveOccurred()) - err = a.Provision() + err = a.Provision(ctx) Expect(err).NotTo(HaveOccurred()) }) } @@ -234,39 +233,6 @@ func (f *Framework) Helper() *helper.Helper { return f.helper } -func (f *Framework) CertificateDurationValid(c *v1.Certificate, duration, fuzz time.Duration) { - By("Verifying TLS certificate exists") - secret, err := f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Get(context.TODO(), c.Spec.SecretName, metav1.GetOptions{}) - Expect(err).NotTo(HaveOccurred()) - certBytes, ok := secret.Data[api.TLSCertKey] - if !ok { - Failf("No certificate data found for Certificate %q", c.Name) - } - cert, err := pki.DecodeX509CertificateBytes(certBytes) - Expect(err).NotTo(HaveOccurred()) - By("Verifying that the duration is valid") - certDuration := cert.NotAfter.Sub(cert.NotBefore) - if certDuration > (duration+fuzz) || certDuration < duration { - Failf("Expected duration of %s, got %s (fuzz: %s) [NotBefore: %s, NotAfter: %s]", duration, certDuration, - fuzz, cert.NotBefore.Format(time.RFC3339), cert.NotAfter.Format(time.RFC3339)) - } -} - -func (f *Framework) CertificateRequestDurationValid(c *v1.CertificateRequest, duration, fuzz time.Duration) { - By("Verifying TLS certificate exists") - if len(c.Status.Certificate) == 0 { - Failf("No certificate data found for CertificateRequest %s", c.Name) - } - cert, err := pki.DecodeX509CertificateBytes(c.Status.Certificate) - Expect(err).NotTo(HaveOccurred()) - By("Verifying that the duration is valid") - certDuration := cert.NotAfter.Sub(cert.NotBefore) - if certDuration > (duration+fuzz) || certDuration < duration { - Failf("Expected duration of %s, got %s (fuzz: %s) [NotBefore: %s, NotAfter: %s]", duration, certDuration, - fuzz, cert.NotBefore.Format(time.RFC3339), cert.NotAfter.Format(time.RFC3339)) - } -} - // CertManagerDescribe is a wrapper function for ginkgo describe. Adds namespacing. func CertManagerDescribe(text string, body func()) bool { return Describe("[cert-manager] "+text, body) diff --git a/test/e2e/framework/helper/certificaterequests.go b/test/e2e/framework/helper/certificaterequests.go index 36b69fbc096..8bd9e00918a 100644 --- a/test/e2e/framework/helper/certificaterequests.go +++ b/test/e2e/framework/helper/certificaterequests.go @@ -19,168 +19,233 @@ package helper import ( "context" "crypto" - "crypto/ecdsa" - "crypto/ed25519" - "crypto/rsa" "crypto/x509" + "errors" "fmt" + "slices" + "strings" "time" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + kerrors "k8s.io/apimachinery/pkg/util/errors" + "k8s.io/apimachinery/pkg/util/sets" "k8s.io/apimachinery/pkg/util/wait" + "github.com/cert-manager/cert-manager/e2e-tests/framework/helper/validation/certificaterequests" + "github.com/cert-manager/cert-manager/e2e-tests/framework/log" apiutil "github.com/cert-manager/cert-manager/pkg/api/util" cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" - "github.com/cert-manager/cert-manager/pkg/util" "github.com/cert-manager/cert-manager/pkg/util/pki" - "github.com/cert-manager/cert-manager/test/e2e/framework/log" ) +// ErrCertificateRequestFailed is returned when the CertificateRequest has Ready condition False +var ErrCertificateRequestFailed = errors.New("CertificateRequest failed; it has Ready status False and reason Failed") + // WaitForCertificateRequestReady waits for the CertificateRequest resource to // enter a Ready state. -func (h *Helper) WaitForCertificateRequestReady(ns, name string, timeout time.Duration) (*cmapi.CertificateRequest, error) { +func (h *Helper) WaitForCertificateRequestReady(ctx context.Context, ns, name string, timeout time.Duration) (*cmapi.CertificateRequest, error) { var cr *cmapi.CertificateRequest logf, done := log.LogBackoff() defer done() - err := wait.PollImmediate(time.Second, timeout, - func() (bool, error) { - var err error - logf("Waiting for CertificateRequest %s to be ready", name) - cr, err = h.CMClient.CertmanagerV1().CertificateRequests(ns).Get(context.TODO(), name, metav1.GetOptions{}) - if err != nil { - return false, fmt.Errorf("error getting CertificateRequest %s: %v", name, err) - } - isReady := apiutil.CertificateRequestHasCondition(cr, cmapi.CertificateRequestCondition{ - Type: cmapi.CertificateRequestConditionReady, - Status: cmmeta.ConditionTrue, - }) - if !isReady { - logf("Expected CertificateRequest to have Ready condition 'true' but it has: %v", cr.Status.Conditions) - return false, nil - } - return true, nil - }, - ) - - if err != nil { - return nil, err - } + err := wait.PollUntilContextTimeout(ctx, time.Second, timeout, true, func(ctx context.Context) (bool, error) { + var err error + logf("Waiting for CertificateRequest %s to be ready", name) + cr, err = h.CMClient.CertmanagerV1().CertificateRequests(ns).Get(ctx, name, metav1.GetOptions{}) + if err != nil { + return false, fmt.Errorf("error getting CertificateRequest %s: %v", name, err) + } - return cr, nil + readyCondition := apiutil.GetCertificateRequestCondition(cr, cmapi.CertificateRequestConditionReady) + switch { + case readyCondition == nil: + logf( + "Expected CertificateRequest to have Ready condition 'true' but the Ready condition was not present: %v", + cr.Status.Conditions, + ) + return false, nil + case readyCondition.Status == cmmeta.ConditionUnknown: + logf("Expected CertificateRequest to have Ready condition 'true' but it has: %v", cr.Status.Conditions) + return false, nil + case readyCondition.Status == cmmeta.ConditionFalse: + if readyCondition.Reason == cmapi.CertificateRequestReasonFailed { + return true, fmt.Errorf("%w: %v", ErrCertificateRequestFailed, readyCondition) + } + logf("Expected CertificateRequest to have Ready condition 'true' but it has: %v", cr.Status.Conditions) + return false, nil + } + return true, nil + }) + return cr, err } // ValidateIssuedCertificateRequest will ensure that the given // CertificateRequest has a certificate issued for it, and that the details on // the x509 certificate are correct as defined by the CertificateRequest's // spec. -func (h *Helper) ValidateIssuedCertificateRequest(cr *cmapi.CertificateRequest, key crypto.Signer, rootCAPEM []byte) (*x509.Certificate, error) { +func (h *Helper) ValidateIssuedCertificateRequest(ctx context.Context, cr *cmapi.CertificateRequest, key crypto.Signer) (*x509.Certificate, error) { csr, err := pki.DecodeX509CertificateRequestBytes(cr.Spec.Request) if err != nil { return nil, fmt.Errorf("failed to decode CertificateRequest's Spec.Request: %s", err) } - // validate private key is of the correct type (rsa or ecdsa) - switch csr.PublicKeyAlgorithm { - case x509.RSA: - _, ok := key.(*rsa.PrivateKey) - if !ok { - return nil, fmt.Errorf("Expected private key of type RSA, but it was: %T", key) - } - case x509.ECDSA: - _, ok := key.(*ecdsa.PrivateKey) - if !ok { - return nil, fmt.Errorf("Expected private key of type ECDSA, but it was: %T", key) - } - case x509.Ed25519: - _, ok := key.(ed25519.PrivateKey) - if !ok { - return nil, fmt.Errorf("Expected private key of type Ed25519, but it was: %T", key) + var issuerSpec *cmapi.IssuerSpec + switch issuerRef := cr.Spec.IssuerRef; issuerRef.Kind { + case "ClusterIssuer": + issuerObj, err := h.CMClient.CertmanagerV1().ClusterIssuers().Get(ctx, issuerRef.Name, metav1.GetOptions{}) + if err != nil { + return nil, fmt.Errorf("failed to find referenced ClusterIssuer %v: %s", + issuerRef, err) } + + issuerSpec = &issuerObj.Spec default: - return nil, fmt.Errorf("unrecognised requested private key algorithm %q", csr.PublicKeyAlgorithm) - } + issuerObj, err := h.CMClient.CertmanagerV1().Issuers(cr.Namespace).Get(ctx, issuerRef.Name, metav1.GetOptions{}) + if err != nil { + return nil, fmt.Errorf("failed to find referenced Issuer %v: %s", + issuerRef, err) + } - // TODO: validate private key KeySize + issuerSpec = &issuerObj.Spec + } - // check the provided certificate is valid - expectedOrganization := csr.Subject.Organization - expectedDNSNames := csr.DNSNames - expectedIPAddresses := csr.IPAddresses - expectedURIs := csr.URIs + if err := certificaterequests.ExpectValidPrivateKeyData(cr, key); err != nil { + return nil, err + } cert, err := pki.DecodeX509CertificateBytes(cr.Status.Certificate) if err != nil { return nil, err } - commonNameCorrect := true - expectedCN := csr.Subject.CommonName - if len(expectedCN) == 0 && len(cert.Subject.CommonName) > 0 { - if !util.Contains(cert.DNSNames, cert.Subject.CommonName) { + // Verify CN + { + commonNameCorrect := true + expectedCN := csr.Subject.CommonName + // Do not verify the CN when using an ACME issuer with the ACME test + // server "Pebble", because Pebble ignores the requested CN and it does + // not currently allow us to simulate the Lets Encrypt behavior of + // "promoting" the first DNS name to CN. See: + // - https://github.com/letsencrypt/pebble/pull/491 + if issuerSpec.ACME != nil && strings.Contains(issuerSpec.ACME.Server, "pebble") { + expectedCN = "" + } + // Some issuers such as Let's Encrypt, "promote" one of the DNS names as + // the CN if a specific CN has not been requested. See: + // - https://community.letsencrypt.org/t/is-common-name-automatically-included-in-san/214002 + if len(expectedCN) == 0 && len(cert.Subject.CommonName) > 0 { + if !slices.Contains(cert.DNSNames, cert.Subject.CommonName) { + commonNameCorrect = false + } + } else if expectedCN != cert.Subject.CommonName { commonNameCorrect = false } - } else if expectedCN != cert.Subject.CommonName { - commonNameCorrect = false + if !commonNameCorrect { + return nil, fmt.Errorf("Expected certificate valid for CN %q but got a certificate valid for CN %q", expectedCN, cert.Subject.CommonName) + } } - if !commonNameCorrect || - !util.EqualUnsorted(cert.DNSNames, expectedDNSNames) || - !util.EqualUnsorted(cert.Subject.Organization, expectedOrganization) || - !util.EqualIPsUnsorted(cert.IPAddresses, expectedIPAddresses) || - !util.EqualURLsUnsorted(cert.URIs, expectedURIs) { - return nil, fmt.Errorf("Expected certificate valid for CN %q, O %v, dnsNames %v, IPs %v, URIs %v but got a certificate valid for CN %q, O %v, dnsNames %v, IPs %v URIs %v", - expectedCN, expectedOrganization, expectedDNSNames, expectedIPAddresses, expectedURIs, - cert.Subject.CommonName, cert.Subject.Organization, cert.DNSNames, cert.IPAddresses, cert.URIs) + if err := certificaterequests.ExpectCertificateDNSNamesToMatch(cr, key); err != nil { + return nil, err } - - var expectedDNSName string - if len(expectedDNSNames) > 0 { - expectedDNSName = expectedDNSNames[0] + if err := certificaterequests.ExpectCertificateOrganizationToMatch(cr, key); err != nil { + return nil, err } - - certificateKeyUsages, certificateExtKeyUsages, err := pki.BuildKeyUsages(cr.Spec.Usages, cr.Spec.IsCA) - if err != nil { - return nil, fmt.Errorf("failed to build key usages from certificate: %s", err) + if err := certificaterequests.ExpectCertificateIPsToMatch(cr, key); err != nil { + return nil, err + } + if err := certificaterequests.ExpectCertificateURIsToMatch(cr, key); err != nil { + return nil, err } - var keyAlg cmapi.PrivateKeyAlgorithm - switch csr.PublicKeyAlgorithm { - case x509.RSA: - keyAlg = cmapi.RSAKeyAlgorithm - case x509.ECDSA: - keyAlg = cmapi.ECDSAKeyAlgorithm - case x509.Ed25519: - keyAlg = cmapi.Ed25519KeyAlgorithm - default: - return nil, fmt.Errorf("unsupported key algorithm type: %s", csr.PublicKeyAlgorithm) + // Verify KU and EKU + { + var keyAlg cmapi.PrivateKeyAlgorithm + switch csr.PublicKeyAlgorithm { + case x509.RSA: + keyAlg = cmapi.RSAKeyAlgorithm + case x509.ECDSA: + keyAlg = cmapi.ECDSAKeyAlgorithm + case x509.Ed25519: + keyAlg = cmapi.Ed25519KeyAlgorithm + default: + return nil, fmt.Errorf("unsupported key algorithm type: %s", csr.PublicKeyAlgorithm) + } + + expectedKeyUsages, expectedExtendedKeyUsages, err := computeExpectedKeyUsages(cr.Spec.Usages, cr.Spec.IsCA, keyAlg, issuerSpec) + if err != nil { + return nil, fmt.Errorf("failed to compute expected key usages: %s", err) + } + + if !h.keyUsagesMatch(cert.KeyUsage, cert.ExtKeyUsage, + expectedKeyUsages, expectedExtendedKeyUsages) { + return nil, fmt.Errorf("key usages and extended key usages do not match: exp=%s got=%s exp=%s got=%s", + apiutil.KeyUsageStrings(expectedKeyUsages), apiutil.KeyUsageStrings(cert.KeyUsage), + apiutil.ExtKeyUsageStrings(expectedExtendedKeyUsages), apiutil.ExtKeyUsageStrings(cert.ExtKeyUsage)) + } } - defaultCertKeyUsages, defaultCertExtKeyUsages, err := h.defaultKeyUsagesToAdd(cr.Namespace, &cr.Spec.IssuerRef) - if err != nil { + if err := certificaterequests.ExpectConditionApproved(cr, key); err != nil { + return nil, err + } + if err := certificaterequests.ExpectConditionNotDenied(cr, key); err != nil { return nil, err } - certificateKeyUsages |= defaultCertKeyUsages - certificateExtKeyUsages = append(certificateExtKeyUsages, defaultCertExtKeyUsages...) + return cert, nil +} - certificateExtKeyUsages = h.deduplicateExtKeyUsages(certificateExtKeyUsages) +func (h *Helper) WaitCertificateRequestIssuedValid(ctx context.Context, ns, name string, timeout time.Duration, key crypto.Signer) error { + cr, err := h.WaitForCertificateRequestReady(ctx, ns, name, timeout) + if err != nil { + log.Logf("Error waiting for CertificateRequest to become Ready: %v", err) + return kerrors.NewAggregate([]error{ + err, + h.Kubectl(ns).DescribeResource(ctx, "certificaterequest", name), + h.Kubectl(ns).Describe(ctx, "order", "challenge"), + }) + } - // If using ECDSA then ignore key encipherment - if keyAlg == cmapi.ECDSAKeyAlgorithm { - certificateKeyUsages &^= x509.KeyUsageKeyEncipherment - cert.KeyUsage &^= x509.KeyUsageKeyEncipherment + _, err = h.ValidateIssuedCertificateRequest(ctx, cr, key) + if err != nil { + log.Logf("Error validating issued certificate: %v", err) + return kerrors.NewAggregate([]error{ + err, + h.Kubectl(ns).DescribeResource(ctx, "certificaterequest", name), + h.Kubectl(ns).Describe(ctx, "order", "challenge"), + }) } - if !h.keyUsagesMatch(cert.KeyUsage, cert.ExtKeyUsage, - certificateKeyUsages, certificateExtKeyUsages) { - return nil, fmt.Errorf("key usages and extended key usages do not match: exp=%s got=%s exp=%s got=%s", - apiutil.KeyUsageStrings(certificateKeyUsages), apiutil.KeyUsageStrings(cert.KeyUsage), - apiutil.ExtKeyUsageStrings(certificateExtKeyUsages), apiutil.ExtKeyUsageStrings(cert.ExtKeyUsage)) + return nil +} + +func (h *Helper) WaitCertificateRequestIssuedValidTLS(ctx context.Context, ns, name string, timeout time.Duration, key crypto.Signer, rootCAPEM []byte) error { + if err := h.WaitCertificateRequestIssuedValid(ctx, ns, name, timeout, key); err != nil { + return err } - // TODO: move this verification step out of this function - if rootCAPEM != nil { + { + cr, err := h.WaitForCertificateRequestReady(ctx, ns, name, timeout) + if err != nil { + return err + } + + csr, err := pki.DecodeX509CertificateRequestBytes(cr.Spec.Request) + if err != nil { + return fmt.Errorf("failed to decode CertificateRequest's Spec.Request: %s", err) + } + + cert, err := pki.DecodeX509CertificateBytes(cr.Status.Certificate) + if err != nil { + return err + } + + expectedDNSNames := csr.DNSNames + var expectedDNSName string + if len(expectedDNSNames) > 0 { + expectedDNSName = expectedDNSNames[0] + } + rootCertPool := x509.NewCertPool() rootCertPool.AppendCertsFromPEM(rootCAPEM) intermediateCertPool := x509.NewCertPool() @@ -192,40 +257,64 @@ func (h *Helper) ValidateIssuedCertificateRequest(cr *cmapi.CertificateRequest, } if _, err := cert.Verify(opts); err != nil { - return nil, err + return err } } - if !apiutil.CertificateRequestIsApproved(cr) { - return nil, fmt.Errorf("CertificateRequest does not have an Approved condition set to True: %+v", cr.Status.Conditions) - } - if apiutil.CertificateRequestIsDenied(cr) { - return nil, fmt.Errorf("CertificateRequest has a Denied conditon set to True: %+v", cr.Status.Conditions) - } - - return cert, nil -} - -func (h *Helper) WaitCertificateRequestIssuedValid(ns, name string, timeout time.Duration, key crypto.Signer) error { - return h.WaitCertificateRequestIssuedValidTLS(ns, name, timeout, key, nil) + return nil } -func (h *Helper) WaitCertificateRequestIssuedValidTLS(ns, name string, timeout time.Duration, key crypto.Signer, rootCAPEM []byte) error { - cr, err := h.WaitForCertificateRequestReady(ns, name, timeout) +// computeExpectedKeyUsages computes the expected KUs and EKUs based on the +// requested usages (from a Certificate or CertificateRequest) and the Issuer +// spec. +// This is to account for the fact that different issuers may add, drop, or +// override the KUs and EKUs. Some completely ignore the requested usages while +// others respect some or all of the requested usages. +// There are also some special cases where the usages are influenced by the key +// algorithm and the isCA field. +func computeExpectedKeyUsages(requestedUsages []cmapi.KeyUsage, isCA bool, keyAlg cmapi.PrivateKeyAlgorithm, issuerSpec *cmapi.IssuerSpec) (x509.KeyUsage, []x509.ExtKeyUsage, error) { + + requestedKeyUsages, requestedExtendedKeyUsages, err := pki.KeyUsagesForCertificateOrCertificateRequest(requestedUsages, isCA) + var expectedKeyUsages x509.KeyUsage if err != nil { - log.Logf("Error waiting for CertificateRequest to become Ready: %v", err) - h.Kubectl(ns).DescribeResource("certificaterequest", name) - h.Kubectl(ns).Describe("order", "challenge") - return err + return expectedKeyUsages, nil, fmt.Errorf("failed to build key usages from certificate: %s", err) } - _, err = h.ValidateIssuedCertificateRequest(cr, key, rootCAPEM) - if err != nil { - log.Logf("Error validating issued certificate: %v", err) - h.Kubectl(ns).DescribeResource("certificaterequest", name) - h.Kubectl(ns).Describe("order", "challenge") - return err + // By default we assume that issuers will satisfy the requested KUs and EKUs. + expectedKeyUsages = requestedKeyUsages + expectedExtendedKeyUsages := sets.New(requestedExtendedKeyUsages...) + + switch { + case issuerSpec.ACME != nil: + // The ACME test server "Pebble" only adds one EKU: "server + // auth" and only adds one KU: "Digital Signature". + // It ignores any other KUs in the CSR. + // - https://github.com/letsencrypt/pebble/pull/472 + if issuerSpec.ACME != nil { + expectedKeyUsages = x509.KeyUsageDigitalSignature + expectedExtendedKeyUsages.Clear().Insert(x509.ExtKeyUsageServerAuth) + } + case issuerSpec.Vault != nil: + // Vault issuers will add "server auth" and "client auth" extended key + // usages by default so we need to add them to the list of expected usages + // Vault issuers will also add "key agreement" key usage + expectedExtendedKeyUsages.Insert(x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth) + expectedKeyUsages |= x509.KeyUsageKeyAgreement + + case issuerSpec.Venafi != nil: + // Venafi issue adds "server auth" key usage + expectedExtendedKeyUsages.Insert(x509.ExtKeyUsageServerAuth) } - return nil + // Most issuers will drop the "key encipherment" KU, if using ECDSA keys. + // The best explanation I can find is: https://security.stackexchange.com/a/224509 + // The exceptions are the CA and SelfSigned issuers. + // + // TODO(wallrj): Perhaps CA and SelfSigned issuers should also drop or + // reject KeyEncipherment for ECDSA certificates. + if issuerSpec.SelfSigned == nil && issuerSpec.CA == nil && keyAlg == cmapi.ECDSAKeyAlgorithm { + expectedKeyUsages &^= x509.KeyUsageKeyEncipherment + } + + return expectedKeyUsages, sets.List(expectedExtendedKeyUsages), nil } diff --git a/test/e2e/framework/helper/certificates.go b/test/e2e/framework/helper/certificates.go index 9f69506be33..2e0e3628f9e 100644 --- a/test/e2e/framework/helper/certificates.go +++ b/test/e2e/framework/helper/certificates.go @@ -25,26 +25,27 @@ import ( errors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + kerrors "k8s.io/apimachinery/pkg/util/errors" "k8s.io/apimachinery/pkg/util/wait" + "github.com/cert-manager/cert-manager/e2e-tests/framework/log" apiutil "github.com/cert-manager/cert-manager/pkg/api/util" - cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" - v1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" + cmapiv1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" clientset "github.com/cert-manager/cert-manager/pkg/client/clientset/versioned/typed/certmanager/v1" - "github.com/cert-manager/cert-manager/test/e2e/framework/log" ) // WaitForCertificateToExist waits for the named certificate to exist and returns the certificate -func (h *Helper) WaitForCertificateToExist(namespace string, name string, timeout time.Duration) (*cmapi.Certificate, error) { +func (h *Helper) WaitForCertificateToExist(ctx context.Context, namespace string, name string, timeout time.Duration) (*cmapiv1.Certificate, error) { client := h.CMClient.CertmanagerV1().Certificates(namespace) - var certificate *v1.Certificate + var certificate *cmapiv1.Certificate logf, done := log.LogBackoff() defer done() - pollErr := wait.PollImmediate(500*time.Millisecond, timeout, func() (bool, error) { + + pollErr := wait.PollUntilContextTimeout(ctx, 500*time.Millisecond, timeout, true, func(ctx context.Context) (bool, error) { logf("Waiting for Certificate %v to exist", name) var err error - certificate, err = client.Get(context.TODO(), name, metav1.GetOptions{}) + certificate, err = client.Get(ctx, name, metav1.GetOptions{}) if errors.IsNotFound(err) { return false, nil } @@ -57,11 +58,11 @@ func (h *Helper) WaitForCertificateToExist(namespace string, name string, timeou return certificate, pollErr } -func (h *Helper) waitForCertificateCondition(client clientset.CertificateInterface, name string, check func(*v1.Certificate) bool, timeout time.Duration) (*cmapi.Certificate, error) { - var certificate *v1.Certificate - pollErr := wait.PollImmediate(500*time.Millisecond, timeout, func() (bool, error) { +func (h *Helper) waitForCertificateCondition(ctx context.Context, client clientset.CertificateInterface, name string, check func(*cmapiv1.Certificate) bool, timeout time.Duration) (*cmapiv1.Certificate, error) { + var certificate *cmapiv1.Certificate + pollErr := wait.PollUntilContextTimeout(ctx, 500*time.Millisecond, timeout, true, func(ctx context.Context) (bool, error) { var err error - certificate, err = client.Get(context.TODO(), name, metav1.GetOptions{}) + certificate, err = client.Get(ctx, name, metav1.GetOptions{}) if nil != err { certificate = nil return false, fmt.Errorf("error getting Certificate %v: %v", name, err) @@ -73,38 +74,42 @@ func (h *Helper) waitForCertificateCondition(client clientset.CertificateInterfa if pollErr != nil && certificate != nil { log.Logf("Failed waiting for certificate %v: %v\n", name, pollErr.Error()) + errs := []error{pollErr} + log.Logf("Certificate:\n") - h.describeCMObject(certificate) + errs = append(errs, h.describeCMObject(certificate)) log.Logf("Order and challenge descriptions:\n") - h.Kubectl(certificate.Namespace).Describe("order", "challenge") + errs = append(errs, h.Kubectl(certificate.Namespace).Describe(ctx, "order", "challenge")) log.Logf("CertificateRequest description:\n") crName, err := apiutil.ComputeName(certificate.Name, certificate.Spec) if err != nil { - log.Logf("Failed to compute CertificateRequest name from certificate: %s", err) + errs = append(errs, fmt.Errorf("failed to compute CertificateRequest name from certificate: %w", err)) } else { - h.Kubectl(certificate.Namespace).DescribeResource("certificaterequest", crName) + errs = append(errs, h.Kubectl(certificate.Namespace).DescribeResource(ctx, "certificaterequest", crName)) } + + pollErr = kerrors.NewAggregate(errs) } return certificate, pollErr } // WaitForCertificateReadyAndDoneIssuing waits for the certificate resource to be in a Ready=True state and not be in an Issuing state. // The Ready=True condition will be checked against the provided certificate to make sure that it is up-to-date (condition gen. >= cert gen.). -func (h *Helper) WaitForCertificateReadyAndDoneIssuing(cert *cmapi.Certificate, timeout time.Duration) (*cmapi.Certificate, error) { - ready_true_condition := cmapi.CertificateCondition{ - Type: cmapi.CertificateConditionReady, +func (h *Helper) WaitForCertificateReadyAndDoneIssuing(ctx context.Context, cert *cmapiv1.Certificate, timeout time.Duration) (*cmapiv1.Certificate, error) { + ready_true_condition := cmapiv1.CertificateCondition{ + Type: cmapiv1.CertificateConditionReady, Status: cmmeta.ConditionTrue, ObservedGeneration: cert.Generation, } - issuing_true_condition := cmapi.CertificateCondition{ - Type: cmapi.CertificateConditionIssuing, + issuing_true_condition := cmapiv1.CertificateCondition{ + Type: cmapiv1.CertificateConditionIssuing, Status: cmmeta.ConditionTrue, } logf, done := log.LogBackoff() defer done() - return h.waitForCertificateCondition(h.CMClient.CertmanagerV1().Certificates(cert.Namespace), cert.Name, func(certificate *v1.Certificate) bool { + return h.waitForCertificateCondition(ctx, h.CMClient.CertmanagerV1().Certificates(cert.Namespace), cert.Name, func(certificate *cmapiv1.Certificate) bool { if !apiutil.CertificateHasConditionWithObservedGeneration(certificate, ready_true_condition) { logf( "Expected Certificate %v condition %v=%v (generation >= %v) but it has: %v", @@ -133,19 +138,19 @@ func (h *Helper) WaitForCertificateReadyAndDoneIssuing(cert *cmapi.Certificate, // WaitForCertificateNotReadyAndDoneIssuing waits for the certificate resource to be in a Ready=False state and not be in an Issuing state. // The Ready=False condition will be checked against the provided certificate to make sure that it is up-to-date (condition gen. >= cert gen.). -func (h *Helper) WaitForCertificateNotReadyAndDoneIssuing(cert *cmapi.Certificate, timeout time.Duration) (*cmapi.Certificate, error) { - ready_false_condition := cmapi.CertificateCondition{ - Type: cmapi.CertificateConditionReady, +func (h *Helper) WaitForCertificateNotReadyAndDoneIssuing(ctx context.Context, cert *cmapiv1.Certificate, timeout time.Duration) (*cmapiv1.Certificate, error) { + ready_false_condition := cmapiv1.CertificateCondition{ + Type: cmapiv1.CertificateConditionReady, Status: cmmeta.ConditionFalse, ObservedGeneration: cert.Generation, } - issuing_true_condition := cmapi.CertificateCondition{ - Type: cmapi.CertificateConditionIssuing, + issuing_true_condition := cmapiv1.CertificateCondition{ + Type: cmapiv1.CertificateConditionIssuing, Status: cmmeta.ConditionTrue, } logf, done := log.LogBackoff() defer done() - return h.waitForCertificateCondition(h.CMClient.CertmanagerV1().Certificates(cert.Namespace), cert.Name, func(certificate *v1.Certificate) bool { + return h.waitForCertificateCondition(ctx, h.CMClient.CertmanagerV1().Certificates(cert.Namespace), cert.Name, func(certificate *cmapiv1.Certificate) bool { if !apiutil.CertificateHasConditionWithObservedGeneration(certificate, ready_false_condition) { logf( "Expected Certificate %v condition %v=%v (generation >= %v) but it has: %v", @@ -172,11 +177,11 @@ func (h *Helper) WaitForCertificateNotReadyAndDoneIssuing(cert *cmapi.Certificat }, timeout) } -func (h *Helper) waitForIssuerCondition(client clientset.IssuerInterface, name string, check func(issuer *v1.Issuer) bool, timeout time.Duration) (*cmapi.Issuer, error) { - var issuer *v1.Issuer - pollErr := wait.PollImmediate(500*time.Millisecond, timeout, func() (bool, error) { +func (h *Helper) waitForIssuerCondition(ctx context.Context, client clientset.IssuerInterface, name string, check func(issuer *cmapiv1.Issuer) bool, timeout time.Duration) (*cmapiv1.Issuer, error) { + var issuer *cmapiv1.Issuer + pollErr := wait.PollUntilContextTimeout(ctx, 500*time.Millisecond, timeout, true, func(ctx context.Context) (bool, error) { var err error - issuer, err = client.Get(context.TODO(), name, metav1.GetOptions{}) + issuer, err = client.Get(ctx, name, metav1.GetOptions{}) if nil != err { issuer = nil return false, fmt.Errorf("error getting Issuer %v: %v", name, err) @@ -188,23 +193,23 @@ func (h *Helper) waitForIssuerCondition(client clientset.IssuerInterface, name s log.Logf("Failed waiting for issuer %v :%v\n", name, pollErr.Error()) log.Logf("Issuer:\n") - h.describeCMObject(issuer) + pollErr = kerrors.NewAggregate([]error{pollErr, h.describeCMObject(issuer)}) } return issuer, pollErr } // WaitIssuerReady waits for the Issuer resource to be in a Ready=True state -// The Ready=True condition will be checked against the provided issuer to make sure its ready. -func (h *Helper) WaitIssuerReady(issuer *cmapi.Issuer, timeout time.Duration) (*cmapi.Issuer, error) { - ready_true_condition := cmapi.IssuerCondition{ - Type: cmapi.IssuerConditionReady, +// The Ready=True condition will be checked against the provided issuer to make sure it's ready. +func (h *Helper) WaitIssuerReady(ctx context.Context, issuer *cmapiv1.Issuer, timeout time.Duration) (*cmapiv1.Issuer, error) { + ready_true_condition := cmapiv1.IssuerCondition{ + Type: cmapiv1.IssuerConditionReady, Status: cmmeta.ConditionTrue, } logf, done := log.LogBackoff() defer done() - return h.waitForIssuerCondition(h.CMClient.CertmanagerV1().Issuers(issuer.Namespace), issuer.Name, func(issuer *v1.Issuer) bool { + return h.waitForIssuerCondition(ctx, h.CMClient.CertmanagerV1().Issuers(issuer.Namespace), issuer.Name, func(issuer *cmapiv1.Issuer) bool { if !apiutil.IssuerHasCondition(issuer, ready_true_condition) { logf( "Expected Issuer %v condition %v=%v but it has: %v", @@ -219,11 +224,11 @@ func (h *Helper) WaitIssuerReady(issuer *cmapi.Issuer, timeout time.Duration) (* }, timeout) } -func (h *Helper) waitForClusterIssuerCondition(client clientset.ClusterIssuerInterface, name string, check func(issuer *v1.ClusterIssuer) bool, timeout time.Duration) (*cmapi.ClusterIssuer, error) { - var issuer *v1.ClusterIssuer - pollErr := wait.PollImmediate(500*time.Millisecond, timeout, func() (bool, error) { +func (h *Helper) waitForClusterIssuerCondition(ctx context.Context, client clientset.ClusterIssuerInterface, name string, check func(issuer *cmapiv1.ClusterIssuer) bool, timeout time.Duration) (*cmapiv1.ClusterIssuer, error) { + var issuer *cmapiv1.ClusterIssuer + pollErr := wait.PollUntilContextTimeout(ctx, 500*time.Millisecond, timeout, true, func(ctx context.Context) (bool, error) { var err error - issuer, err = client.Get(context.TODO(), name, metav1.GetOptions{}) + issuer, err = client.Get(ctx, name, metav1.GetOptions{}) if nil != err { issuer = nil return false, fmt.Errorf("error getting Issuer %v: %v", name, err) @@ -235,22 +240,22 @@ func (h *Helper) waitForClusterIssuerCondition(client clientset.ClusterIssuerInt log.Logf("Failed waiting for issuer %v :%v\n", name, pollErr.Error()) log.Logf("Issuer:\n") - h.describeCMObject(issuer) + pollErr = kerrors.NewAggregate([]error{pollErr, h.describeCMObject(issuer)}) } return issuer, pollErr } // WaitClusterIssuerReady waits for the Cluster Issuer resource to be in a Ready=True state -// The Ready=True condition will be checked against the provided issuer to make sure its ready. -func (h *Helper) WaitClusterIssuerReady(issuer *cmapi.ClusterIssuer, timeout time.Duration) (*cmapi.ClusterIssuer, error) { - ready_true_condition := cmapi.IssuerCondition{ - Type: cmapi.IssuerConditionReady, +// The Ready=True condition will be checked against the provided issuer to make sure it's ready. +func (h *Helper) WaitClusterIssuerReady(ctx context.Context, issuer *cmapiv1.ClusterIssuer, timeout time.Duration) (*cmapiv1.ClusterIssuer, error) { + ready_true_condition := cmapiv1.IssuerCondition{ + Type: cmapiv1.IssuerConditionReady, Status: cmmeta.ConditionTrue, } logf, done := log.LogBackoff() defer done() - return h.waitForClusterIssuerCondition(h.CMClient.CertmanagerV1().ClusterIssuers(), issuer.Name, func(issuer *v1.ClusterIssuer) bool { + return h.waitForClusterIssuerCondition(ctx, h.CMClient.CertmanagerV1().ClusterIssuers(), issuer.Name, func(issuer *cmapiv1.ClusterIssuer) bool { if !apiutil.IssuerHasCondition(issuer, ready_true_condition) { logf( "Expected Cluster Issuer %v condition %v=%v but it has: %v", @@ -265,65 +270,6 @@ func (h *Helper) WaitClusterIssuerReady(issuer *cmapi.ClusterIssuer, timeout tim }, timeout) } -func (h *Helper) deduplicateExtKeyUsages(us []x509.ExtKeyUsage) []x509.ExtKeyUsage { - extKeyUsagesMap := make(map[x509.ExtKeyUsage]bool) - for _, e := range us { - extKeyUsagesMap[e] = true - } - - us = make([]x509.ExtKeyUsage, 0) - for e, ok := range extKeyUsagesMap { - if ok { - us = append(us, e) - } - } - - return us -} - -func (h *Helper) defaultKeyUsagesToAdd(ns string, issuerRef *cmmeta.ObjectReference) (x509.KeyUsage, []x509.ExtKeyUsage, error) { - var issuerSpec *cmapi.IssuerSpec - switch issuerRef.Kind { - case "ClusterIssuer": - issuerObj, err := h.CMClient.CertmanagerV1().ClusterIssuers().Get(context.TODO(), issuerRef.Name, metav1.GetOptions{}) - if err != nil { - return 0, nil, fmt.Errorf("failed to find referenced ClusterIssuer %v: %s", - issuerRef, err) - } - - issuerSpec = &issuerObj.Spec - default: - issuerObj, err := h.CMClient.CertmanagerV1().Issuers(ns).Get(context.TODO(), issuerRef.Name, metav1.GetOptions{}) - if err != nil { - return 0, nil, fmt.Errorf("failed to find referenced Issuer %v: %s", - issuerRef, err) - } - - issuerSpec = &issuerObj.Spec - } - - var keyUsages x509.KeyUsage - var extKeyUsages []x509.ExtKeyUsage - - // Vault and ACME issuers will add server auth and client auth extended key - // usages by default so we need to add them to the list of expected usages - if issuerSpec.ACME != nil || issuerSpec.Vault != nil { - extKeyUsages = append(extKeyUsages, x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth) - } - - // Vault issuers will add key agreement key usage - if issuerSpec.Vault != nil { - keyUsages |= x509.KeyUsageKeyAgreement - } - - // Venafi issue adds server auth key usage - if issuerSpec.Venafi != nil { - extKeyUsages = append(extKeyUsages, x509.ExtKeyUsageServerAuth) - } - - return keyUsages, extKeyUsages, nil -} - func (h *Helper) keyUsagesMatch(aKU x509.KeyUsage, aEKU []x509.ExtKeyUsage, bKU x509.KeyUsage, bEKU []x509.ExtKeyUsage) bool { if aKU != bKU { diff --git a/test/e2e/framework/helper/certificatesigningrequests.go b/test/e2e/framework/helper/certificatesigningrequests.go index 17f67fd4f55..569c6afbd74 100644 --- a/test/e2e/framework/helper/certificatesigningrequests.go +++ b/test/e2e/framework/helper/certificatesigningrequests.go @@ -25,35 +25,33 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/wait" + "github.com/cert-manager/cert-manager/e2e-tests/framework/log" "github.com/cert-manager/cert-manager/pkg/controller/certificatesigningrequests/util" - "github.com/cert-manager/cert-manager/test/e2e/framework/log" ) // WaitForCertificateSigningRequestSigned waits for the // CertificateSigningRequest resource to be signed. -func (h *Helper) WaitForCertificateSigningRequestSigned(name string, timeout time.Duration) (*certificatesv1.CertificateSigningRequest, error) { +func (h *Helper) WaitForCertificateSigningRequestSigned(ctx context.Context, name string, timeout time.Duration) (*certificatesv1.CertificateSigningRequest, error) { var csr *certificatesv1.CertificateSigningRequest logf, done := log.LogBackoff() defer done() - err := wait.PollImmediate(time.Second, timeout, - func() (bool, error) { - var err error - logf("Waiting for CertificateSigningRequest %s to be ready", name) - csr, err = h.KubeClient.CertificatesV1().CertificateSigningRequests().Get(context.TODO(), name, metav1.GetOptions{}) - if err != nil { - return false, fmt.Errorf("error getting CertificateSigningRequest %s: %v", name, err) - } - - if util.CertificateSigningRequestIsFailed(csr) { - return false, fmt.Errorf("CertificateSigningRequest has failed: %v", csr.Status) - } - - if len(csr.Status.Certificate) == 0 { - return false, nil - } - return true, nil - }, - ) + err := wait.PollUntilContextTimeout(ctx, time.Second, timeout, true, func(ctx context.Context) (bool, error) { + var err error + logf("Waiting for CertificateSigningRequest %s to be ready", name) + csr, err = h.KubeClient.CertificatesV1().CertificateSigningRequests().Get(ctx, name, metav1.GetOptions{}) + if err != nil { + return false, fmt.Errorf("error getting CertificateSigningRequest %s: %v", name, err) + } + + if util.CertificateSigningRequestIsFailed(csr) { + return false, fmt.Errorf("CertificateSigningRequest has failed: %v", csr.Status) + } + + if len(csr.Status.Certificate) == 0 { + return false, nil + } + return true, nil + }) if err != nil { return nil, err diff --git a/test/e2e/framework/helper/describe.go b/test/e2e/framework/helper/describe.go index d9c9403a1af..c226c0afe15 100644 --- a/test/e2e/framework/helper/describe.go +++ b/test/e2e/framework/helper/describe.go @@ -17,22 +17,21 @@ limitations under the License. package helper import ( - "os" - "k8s.io/apimachinery/pkg/runtime" runtimejson "k8s.io/apimachinery/pkg/runtime/serializer/json" - kubescheme "k8s.io/client-go/kubernetes/scheme" + kscheme "k8s.io/client-go/kubernetes/scheme" + "github.com/cert-manager/cert-manager/e2e-tests/framework/log" cmscheme "github.com/cert-manager/cert-manager/pkg/client/clientset/versioned/scheme" ) func (h *Helper) describeKubeObject(object runtime.Object) error { - serializer := runtimejson.NewSerializerWithOptions(runtimejson.DefaultMetaFactory, kubescheme.Scheme, kubescheme.Scheme, runtimejson.SerializerOptions{ + serializer := runtimejson.NewSerializerWithOptions(runtimejson.DefaultMetaFactory, kscheme.Scheme, kscheme.Scheme, runtimejson.SerializerOptions{ Yaml: true, Pretty: true, }) - encoder := kubescheme.Codecs.WithoutConversion().EncoderForVersion(serializer, nil) - return encoder.Encode(object, os.Stdout) + encoder := kscheme.Codecs.WithoutConversion().EncoderForVersion(serializer, nil) + return encoder.Encode(object, log.Writer) } func (h *Helper) describeCMObject(object runtime.Object) error { @@ -41,5 +40,5 @@ func (h *Helper) describeCMObject(object runtime.Object) error { Pretty: true, }) encoder := cmscheme.Codecs.WithoutConversion().EncoderForVersion(serializer, nil) - return encoder.Encode(object, os.Stdout) + return encoder.Encode(object, log.Writer) } diff --git a/test/e2e/framework/helper/featureset/featureset.go b/test/e2e/framework/helper/featureset/featureset.go index 00af198e6fb..b28cb6858bf 100644 --- a/test/e2e/framework/helper/featureset/featureset.go +++ b/test/e2e/framework/helper/featureset/featureset.go @@ -16,72 +16,19 @@ limitations under the License. package featureset -import "strings" +import ( + "k8s.io/apimachinery/pkg/util/sets" +) // NewFeatureSet constructs a new feature set with the given features. func NewFeatureSet(feats ...Feature) FeatureSet { - fs := make(FeatureSet) - for _, f := range feats { - fs.Add(f) - } - return fs + return sets.New(feats...) } // FeatureSet represents a set of features. // This type does not indicate whether or not features are enabled, rather it // just defines a grouping of features (i.e. a 'set'). -type FeatureSet map[Feature]struct{} - -// Add adds features to the set -func (fs FeatureSet) Add(f ...Feature) FeatureSet { - for _, feat := range f { - fs[feat] = struct{}{} - } - return fs -} - -// Delete removes a feature from the set -func (fs FeatureSet) Delete(f Feature) { - delete(fs, f) -} - -// Contains returns true if the FeatureSet contains the given feature -func (fs FeatureSet) Contains(f Feature) bool { - _, ok := fs[f] - return ok -} - -// Copy returns a new copy of an existing Feature Set. -// It is not safe to be called by multiple goroutines. -func (fs FeatureSet) Copy() FeatureSet { - new := make(FeatureSet) - for k, v := range fs { - new[k] = v - } - return new -} - -// List returns a slice of all features in the set. -func (fs FeatureSet) List() []Feature { - var ret []Feature - for k := range fs { - ret = append(ret, k) - } - return ret -} - -// String returns this FeatureSet as a comma separated string -func (fs FeatureSet) String() string { - featsSlice := make([]string, len(fs)) - - i := 0 - for f := range fs { - featsSlice[i] = string(f) - i++ - } - - return strings.Join(featsSlice, ", ") -} +type FeatureSet = sets.Set[Feature] type Feature string @@ -162,4 +109,9 @@ const ( // a certificate containing an arbitrary Subject in the CSR, without // imposing requirements on form or structure. LiteralSubjectFeature Feature = "LiteralCertificateSubject" + + // OtherNameFeature denotes whether the target issuer is able to sign + // a certificate containing otherName SAN values in the CSR, without + // imposing requirements on form or structure. + OtherNamesFeature Feature = "OtherNames" ) diff --git a/test/e2e/framework/helper/helper.go b/test/e2e/framework/helper/helper.go index 382d3cf0286..5315f1d48cf 100644 --- a/test/e2e/framework/helper/helper.go +++ b/test/e2e/framework/helper/helper.go @@ -19,8 +19,8 @@ package helper import ( "k8s.io/client-go/kubernetes" + "github.com/cert-manager/cert-manager/e2e-tests/framework/config" cmclient "github.com/cert-manager/cert-manager/pkg/client/clientset/versioned" - "github.com/cert-manager/cert-manager/test/e2e/framework/config" ) // Helper provides methods for common operations needed during tests. diff --git a/test/e2e/framework/helper/kubectl.go b/test/e2e/framework/helper/kubectl.go index 24e496cccff..95d9d17bbf6 100644 --- a/test/e2e/framework/helper/kubectl.go +++ b/test/e2e/framework/helper/kubectl.go @@ -17,10 +17,11 @@ limitations under the License. package helper import ( + "context" "os/exec" "strings" - "github.com/cert-manager/cert-manager/test/e2e/framework/log" + "github.com/cert-manager/cert-manager/e2e-tests/framework/log" ) type Kubectl struct { @@ -30,13 +31,13 @@ type Kubectl struct { kubecontext string } -func (k *Kubectl) Describe(resources ...string) error { +func (k *Kubectl) Describe(ctx context.Context, resources ...string) error { resourceNames := strings.Join(resources, ",") - return k.Run("describe", resourceNames) + return k.Run(ctx, "describe", resourceNames) } -func (k *Kubectl) DescribeResource(resource, name string) error { - return k.Run("describe", resource, name) +func (k *Kubectl) DescribeResource(ctx context.Context, resource, name string) error { + return k.Run(ctx, "describe", resource, name) } func (h *Helper) Kubectl(ns string) *Kubectl { @@ -48,7 +49,7 @@ func (h *Helper) Kubectl(ns string) *Kubectl { } } -func (k *Kubectl) Run(args ...string) error { +func (k *Kubectl) Run(ctx context.Context, args ...string) error { baseArgs := []string{"--kubeconfig", k.kubeconfig, "--context", k.kubecontext} if k.namespace == "" { baseArgs = append(baseArgs, "--all-namespaces") @@ -56,7 +57,7 @@ func (k *Kubectl) Run(args ...string) error { baseArgs = []string{"--namespace", k.namespace} } args = append(baseArgs, args...) - cmd := exec.Command(k.kubectl, args...) + cmd := exec.CommandContext(ctx, k.kubectl, args...) cmd.Stdout = log.Writer cmd.Stderr = log.Writer return cmd.Run() diff --git a/test/e2e/framework/helper/pod_start.go b/test/e2e/framework/helper/pod_start.go deleted file mode 100644 index 5eb432e270e..00000000000 --- a/test/e2e/framework/helper/pod_start.go +++ /dev/null @@ -1,119 +0,0 @@ -/* -Copyright 2020 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. -*/ - -package helper - -import ( - "context" - "fmt" - "time" - - "github.com/onsi/ginkgo/v2" - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/util/wait" - - "github.com/cert-manager/cert-manager/test/e2e/framework/log" -) - -const ( - // Poll is how often the API is polled in Wait operations by default - Poll = time.Second * 2 - - // PodStartTimeout is the default amount of time to wait in pod start operations - PodStartTimeout = time.Minute * 2 -) - -// WaitForAllPodsRunningInNamespace waits default amount of time (PodStartTimeout) -// for all pods in the specified namespace to become running. -func (h *Helper) WaitForAllPodsRunningInNamespace(ns string) error { - return h.WaitForAllPodsRunningInNamespaceTimeout(ns, PodStartTimeout) -} - -func (h *Helper) WaitForAllPodsRunningInNamespaceTimeout(ns string, timeout time.Duration) error { - ginkgo.By("Waiting " + timeout.String() + " for all pods in namespace '" + ns + "' to be Ready") - logf, done := log.LogBackoff() - defer done() - return wait.PollImmediate(Poll, timeout, func() (bool, error) { - pods, err := h.KubeClient.CoreV1().Pods(ns).List(context.TODO(), metav1.ListOptions{}) - if err != nil { - return false, err - } - - if len(pods.Items) == 0 { - logf("No pods found in namespace %s. Checking again...", ns) - return false, nil - } - - var errs []string - for _, p := range pods.Items { - c := GetPodReadyCondition(p.Status) - if c == nil { - errs = append(errs, fmt.Sprintf("Pod %q not ready (no Ready condition)", p.Name)) - continue - } - if c.Reason == "PodCompleted" { - logf("Pod %q has Completed, assuming it is ready/expected", p.Name) - continue - } - // This pod does not have the ready condition set to True - if c.Status != corev1.ConditionTrue { - errs = append(errs, fmt.Sprintf("Pod %q not ready: %s", p.Name, c.String())) - } - } - - if len(errs) > 0 { - for _, err := range errs { - logf(err) - } - return false, nil - } - - return true, nil - }) -} - -// IsPodReady returns true if a pod is ready; false otherwise. -func IsPodReady(pod *corev1.Pod) bool { - return IsPodReadyConditionTrue(pod.Status) -} - -// IsPodReadyConditionTrue returns true if a pod is ready; false otherwise. -func IsPodReadyConditionTrue(status corev1.PodStatus) bool { - condition := GetPodReadyCondition(status) - return condition != nil && condition.Status == corev1.ConditionTrue -} - -// GetPodReadyCondition extracts the pod ready condition from the given status and returns that. -// Returns nil if the condition is not present. -func GetPodReadyCondition(status corev1.PodStatus) *corev1.PodCondition { - _, condition := GetPodCondition(&status, corev1.PodReady) - return condition -} - -// GetPodCondition extracts the provided condition from the given status and returns that. -// Returns nil and -1 if the condition is not present, and the index of the located condition. -func GetPodCondition(status *corev1.PodStatus, conditionType corev1.PodConditionType) (int, *corev1.PodCondition) { - if status == nil { - return -1, nil - } - for i := range status.Conditions { - if status.Conditions[i].Type == conditionType { - return i, &status.Conditions[i] - } - } - return -1, nil -} diff --git a/test/e2e/framework/helper/secret.go b/test/e2e/framework/helper/secret.go index 9bc327eddb1..9312cf62537 100644 --- a/test/e2e/framework/helper/secret.go +++ b/test/e2e/framework/helper/secret.go @@ -25,33 +25,31 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/wait" - "github.com/cert-manager/cert-manager/test/e2e/framework/log" + "github.com/cert-manager/cert-manager/e2e-tests/framework/log" ) // WaitForSecretCertificateData waits for the certificate data to be ready // inside a Secret created by cert-manager. -func (h *Helper) WaitForSecretCertificateData(ns, name string, timeout time.Duration) (*corev1.Secret, error) { +func (h *Helper) WaitForSecretCertificateData(ctx context.Context, ns, name string, timeout time.Duration) (*corev1.Secret, error) { var secret *corev1.Secret logf, done := log.LogBackoff() defer done() - err := wait.PollImmediate(time.Second, timeout, - func() (bool, error) { - var err error - logf("Waiting for Secret %s:%s to contain a certificate", ns, name) - secret, err = h.KubeClient.CoreV1().Secrets(ns).Get(context.TODO(), name, metav1.GetOptions{}) - if err != nil { - return false, fmt.Errorf("error getting secret %s: %s", name, err) - } - - if len(secret.Data[corev1.TLSCertKey]) > 0 { - return true, nil - } - - logf("Secret still does not contain certificate data %s/%s", - secret.Namespace, secret.Name) - return false, nil - }, - ) + err := wait.PollUntilContextTimeout(ctx, time.Second, timeout, true, func(ctx context.Context) (bool, error) { + var err error + logf("Waiting for Secret %s:%s to contain a certificate", ns, name) + secret, err = h.KubeClient.CoreV1().Secrets(ns).Get(ctx, name, metav1.GetOptions{}) + if err != nil { + return false, fmt.Errorf("error getting secret %s: %s", name, err) + } + + if len(secret.Data[corev1.TLSCertKey]) > 0 { + return true, nil + } + + logf("Secret still does not contain certificate data %s/%s", + secret.Namespace, secret.Name) + return false, nil + }) if err != nil { return nil, err diff --git a/test/e2e/framework/helper/validate.go b/test/e2e/framework/helper/validate.go index 59befe31e9b..96cb6b362ef 100644 --- a/test/e2e/framework/helper/validate.go +++ b/test/e2e/framework/helper/validate.go @@ -21,18 +21,22 @@ import ( "crypto" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - + "k8s.io/apimachinery/pkg/types" + kerrors "k8s.io/apimachinery/pkg/util/errors" + + "github.com/cert-manager/cert-manager/e2e-tests/framework/helper/featureset" + "github.com/cert-manager/cert-manager/e2e-tests/framework/helper/validation" + "github.com/cert-manager/cert-manager/e2e-tests/framework/helper/validation/certificaterequests" + "github.com/cert-manager/cert-manager/e2e-tests/framework/helper/validation/certificates" + "github.com/cert-manager/cert-manager/e2e-tests/framework/helper/validation/certificatesigningrequests" + "github.com/cert-manager/cert-manager/e2e-tests/framework/log" cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" - "github.com/cert-manager/cert-manager/test/e2e/framework/helper/validation" - "github.com/cert-manager/cert-manager/test/e2e/framework/helper/validation/certificates" - "github.com/cert-manager/cert-manager/test/e2e/framework/helper/validation/certificatesigningrequests" - "github.com/cert-manager/cert-manager/test/e2e/framework/log" ) // ValidateCertificate retrieves the issued certificate and runs all validation functions func (h *Helper) ValidateCertificate(certificate *cmapi.Certificate, validations ...certificates.ValidationFunc) error { if len(validations) == 0 { - validations = validation.DefaultCertificateSet() + validations = validation.CertificateSetForUnsupportedFeatureSet(featureset.NewFeatureSet()) } secret, err := h.KubeClient.CoreV1().Secrets(certificate.Namespace).Get(context.TODO(), certificate.Spec.SecretName, metav1.GetOptions{}) @@ -43,12 +47,34 @@ func (h *Helper) ValidateCertificate(certificate *cmapi.Certificate, validations for _, fn := range validations { err := fn(certificate, secret) if err != nil { + errs := []error{err} log.Logf("Certificate:\n") - h.describeCMObject(certificate) + errs = append(errs, h.describeCMObject(certificate)) log.Logf("Secret:\n") - h.describeKubeObject(secret) + errs = append(errs, h.describeKubeObject(secret)) + + return kerrors.NewAggregate(errs) + } + } + + return nil +} + +// ValidateCertificateRequest retrieves the issued certificate and runs all validation functions +func (h *Helper) ValidateCertificateRequest(name types.NamespacedName, key crypto.Signer, validations ...certificaterequests.ValidationFunc) error { + if len(validations) == 0 { + validations = validation.CertificateRequestSetForUnsupportedFeatureSet(featureset.NewFeatureSet()) + } + + cr, err := h.CMClient.CertmanagerV1().CertificateRequests(name.Namespace).Get(context.TODO(), name.Name, metav1.GetOptions{}) + if err != nil { + return err + } + for _, fn := range validations { + err := fn(cr, key) + if err != nil { return err } } @@ -59,7 +85,7 @@ func (h *Helper) ValidateCertificate(certificate *cmapi.Certificate, validations // ValidateCertificateSigningRequest retrieves the issued certificate and runs all validation functions func (h *Helper) ValidateCertificateSigningRequest(name string, key crypto.Signer, validations ...certificatesigningrequests.ValidationFunc) error { if len(validations) == 0 { - validations = validation.DefaultCertificateSigningRequestSet() + validations = validation.CertificateSigningRequestSetForUnsupportedFeatureSet(featureset.NewFeatureSet()) } csr, err := h.KubeClient.CertificatesV1().CertificateSigningRequests().Get(context.TODO(), name, metav1.GetOptions{}) if err != nil { diff --git a/test/e2e/framework/helper/validation/certificaterequests/certificaterequests.go b/test/e2e/framework/helper/validation/certificaterequests/certificaterequests.go new file mode 100644 index 00000000000..35f3920c50e --- /dev/null +++ b/test/e2e/framework/helper/validation/certificaterequests/certificaterequests.go @@ -0,0 +1,298 @@ +/* +Copyright 2020 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. +*/ + +package certificaterequests + +import ( + "crypto" + "crypto/ecdsa" + "crypto/ed25519" + "crypto/rsa" + "crypto/x509" + "errors" + "fmt" + "slices" + "time" + + apiutil "github.com/cert-manager/cert-manager/pkg/api/util" + cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" + "github.com/cert-manager/cert-manager/pkg/util" + "github.com/cert-manager/cert-manager/pkg/util/pki" +) + +// ValidationFunc describes a CertificateRequest validation helper function +type ValidationFunc func(certificaterequest *cmapi.CertificateRequest, key crypto.Signer) error + +// ExpectValidCertificate checks if the certificate is a valid x509 certificate +func ExpectValidCertificate(cr *cmapi.CertificateRequest, _ crypto.Signer) error { + _, err := pki.DecodeX509CertificateBytes(cr.Status.Certificate) + return err +} + +// ExpectCertificateOrganizationToMatch checks if the issued +// certificate has the same Organization as the requested one +func ExpectCertificateOrganizationToMatch(cr *cmapi.CertificateRequest, _ crypto.Signer) error { + cert, err := pki.DecodeX509CertificateBytes(cr.Status.Certificate) + if err != nil { + return err + } + req, err := pki.DecodeX509CertificateRequestBytes(cr.Spec.Request) + if err != nil { + return err + } + + expectedOrganization := req.Subject.Organization + if !util.EqualUnsorted(cert.Subject.Organization, expectedOrganization) { + return fmt.Errorf("Expected certificate valid for O %v, but got a certificate valid for O %v", expectedOrganization, cert.Subject.Organization) + } + + return nil +} + +// ExpectValidPrivateKeyData checks the requesting private key matches the +// signed certificate +func ExpectValidPrivateKeyData(cr *cmapi.CertificateRequest, key crypto.Signer) error { + cert, err := pki.DecodeX509CertificateBytes(cr.Status.Certificate) + if err != nil { + return err + } + + equal := func() (bool, error) { + switch pub := key.Public().(type) { + case *rsa.PublicKey: + return pub.Equal(cert.PublicKey), nil + case *ecdsa.PublicKey: + return pub.Equal(cert.PublicKey), nil + case ed25519.PublicKey: + return pub.Equal(cert.PublicKey), nil + default: + return false, fmt.Errorf("Unrecognised public key type: %T", key) + } + } + + ok, err := equal() + if err != nil { + return err + } + if !ok { + return errors.New("Expected signed certificate's public key to match requester's private key") + } + + return nil +} + +// ExpectCertificateDNSNamesToMatch checks if the issued certificate has all +// DNS names it requested, accounting for the CommonName being optionally +// copied to the DNS Names +func ExpectCertificateDNSNamesToMatch(cr *cmapi.CertificateRequest, _ crypto.Signer) error { + cert, err := pki.DecodeX509CertificateBytes(cr.Status.Certificate) + if err != nil { + return err + } + req, err := pki.DecodeX509CertificateRequestBytes(cr.Spec.Request) + if err != nil { + return err + } + + if !util.EqualUnsorted(cert.DNSNames, req.DNSNames) && + !util.EqualUnsorted(cert.DNSNames, append(req.DNSNames, req.Subject.CommonName)) { + return fmt.Errorf("Expected certificate valid for DNSNames %v, but got a certificate valid for DNSNames %v", req.DNSNames, cert.DNSNames) + } + + return nil +} + +// ExpectCertificateURIsToMatch checks if the issued certificate +// has all URI SANs names it requested +func ExpectCertificateURIsToMatch(cr *cmapi.CertificateRequest, _ crypto.Signer) error { + cert, err := pki.DecodeX509CertificateBytes(cr.Status.Certificate) + if err != nil { + return err + } + req, err := pki.DecodeX509CertificateRequestBytes(cr.Spec.Request) + if err != nil { + return err + } + + actualURIs := pki.URLsToString(cert.URIs) + expectedURIs := pki.URLsToString(req.URIs) + if !util.EqualUnsorted(actualURIs, expectedURIs) { + return fmt.Errorf("Expected certificate valid for URIs %v, but got a certificate valid for URIs %v", expectedURIs, actualURIs) + } + + return nil +} + +// ExpectCertificateIPsToMatch checks if the issued certificate +// has all IP SANs names it requested +func ExpectCertificateIPsToMatch(cr *cmapi.CertificateRequest, _ crypto.Signer) error { + cert, err := pki.DecodeX509CertificateBytes(cr.Status.Certificate) + if err != nil { + return err + } + req, err := pki.DecodeX509CertificateRequestBytes(cr.Spec.Request) + if err != nil { + return err + } + + actualIPs := pki.IPAddressesToString(cert.IPAddresses) + expectedIPs := pki.IPAddressesToString(req.IPAddresses) + if !util.EqualUnsorted(actualIPs, expectedIPs) { + return fmt.Errorf("Expected certificate valid for IPs %v, but got a certificate valid for IPs %v", expectedIPs, actualIPs) + } + + return nil +} + +// ExpectValidCommonName checks if the issued certificate has the requested CN or one of the DNS (or IP Address) SANs +func ExpectValidCommonName(cr *cmapi.CertificateRequest, _ crypto.Signer) error { + cert, err := pki.DecodeX509CertificateBytes(cr.Status.Certificate) + if err != nil { + return err + } + req, err := pki.DecodeX509CertificateRequestBytes(cr.Spec.Request) + if err != nil { + return err + } + + expectedCN := req.Subject.CommonName + + if len(expectedCN) == 0 && len(cert.Subject.CommonName) > 0 { + // no CN is specified but our CA set one, checking if it is one of our DNS names or IP Addresses + if !slices.Contains(cert.DNSNames, cert.Subject.CommonName) && !slices.Contains(pki.IPAddressesToString(cert.IPAddresses), cert.Subject.CommonName) { + return fmt.Errorf("Expected a common name for one of our DNSNames %v or IP Addresses %v, but got a CN of %v", cert.DNSNames, pki.IPAddressesToString(cert.IPAddresses), cert.Subject.CommonName) + } + } else if expectedCN != cert.Subject.CommonName { + return fmt.Errorf("Expected a common name of %v, but got a CN of %v", expectedCN, cert.Subject.CommonName) + } + + return nil +} + +// ExpectKeyUsageExtKeyUsageServerAuth checks if the issued certificate has the +// extended key usage of server auth +func ExpectKeyUsageExtKeyUsageServerAuth(cr *cmapi.CertificateRequest, _ crypto.Signer) error { + cert, err := pki.DecodeX509CertificateBytes(cr.Status.Certificate) + if err != nil { + return err + } + + if !slices.Contains(cert.ExtKeyUsage, x509.ExtKeyUsageServerAuth) { + return fmt.Errorf("Expected certificate to have ExtKeyUsageServerAuth, but got %v", cert.ExtKeyUsage) + } + return nil +} + +// ExpectKeyUsageExtKeyUsageClientAuth checks if the issued certificate has the +// extended key usage of client auth +func ExpectKeyUsageExtKeyUsageClientAuth(cr *cmapi.CertificateRequest, _ crypto.Signer) error { + cert, err := pki.DecodeX509CertificateBytes(cr.Status.Certificate) + if err != nil { + return err + } + + if !slices.Contains(cert.ExtKeyUsage, x509.ExtKeyUsageClientAuth) { + return fmt.Errorf("Expected certificate to have ExtKeyUsageClientAuth, but got %v", cert.ExtKeyUsage) + } + return nil +} + +// ExpectEmailsToMatch check if the issued certificate has all requested email SANs +func ExpectEmailsToMatch(cr *cmapi.CertificateRequest, _ crypto.Signer) error { + cert, err := pki.DecodeX509CertificateBytes(cr.Status.Certificate) + if err != nil { + return err + } + req, err := pki.DecodeX509CertificateRequestBytes(cr.Spec.Request) + if err != nil { + return err + } + + if !util.EqualUnsorted(cert.EmailAddresses, req.EmailAddresses) { + return fmt.Errorf("certificate doesn't contain Email SANs: exp=%v got=%v", req.EmailAddresses, cert.EmailAddresses) + } + + return nil +} + +// ExpectValidBasicConstraints checks the certificate is a CA if requested +func ExpectValidBasicConstraints(cr *cmapi.CertificateRequest, _ crypto.Signer) error { + cert, err := pki.DecodeX509CertificateBytes(cr.Status.Certificate) + if err != nil { + return err + } + + if cert.IsCA != cr.Spec.IsCA { + return fmt.Errorf("requested certificate does not match expected IsCA, exp=%t got=%t", + cr.Spec.IsCA, cert.IsCA) + } + + // TODO: also validate pathLen + + return nil +} + +// ExpectConditionApproved checks that the CertificateRequest has been +// Approved +func ExpectConditionApproved(cr *cmapi.CertificateRequest, _ crypto.Signer) error { + if !apiutil.CertificateRequestIsApproved(cr) { + return fmt.Errorf("CertificateRequest does not have an Approved condition: %v", cr.Status.Conditions) + } + + return nil +} + +// ExpectConditionNotDenied checks that the CertificateRequest has not +// been Denied +func ExpectConditionNotDenied(cr *cmapi.CertificateRequest, _ crypto.Signer) error { + if apiutil.CertificateRequestIsDenied(cr) { + return fmt.Errorf("CertificateRequest has a Denied condition: %v", cr.Status.Conditions) + } + + return nil +} + +// ExpectDuration checks if the issued certificate matches the CertificateRequest's duration +func ExpectDurationToMatch(cr *cmapi.CertificateRequest, key crypto.Signer) error { + certDuration := apiutil.DefaultCertDuration(cr.Spec.Duration) + return ExpectDuration(certDuration, 30*time.Second)(cr, key) +} + +func ExpectDuration(expectedDuration, fuzz time.Duration) func(cr *cmapi.CertificateRequest, key crypto.Signer) error { + return func(cr *cmapi.CertificateRequest, key crypto.Signer) error { + certBytes := cr.Status.Certificate + if len(certBytes) == 0 { + return fmt.Errorf("no certificate data found in CertificateRequest.Status.Certificate") + } + cert, err := pki.DecodeX509CertificateBytes(certBytes) + if err != nil { + return err + } + + // Here we ensure that the requested duration is what is signed on the + // certificate. We tolerate fuzz either way. + certDuration := cert.NotAfter.Sub(cert.NotBefore) + if certDuration > (expectedDuration+fuzz) || certDuration < (expectedDuration-fuzz) { + return fmt.Errorf( + "expected duration of %s, got %s (fuzz: %s) [NotBefore: %s, NotAfter: %s]", + expectedDuration, certDuration, fuzz, + cert.NotBefore.Format(time.RFC3339), cert.NotAfter.Format(time.RFC3339), + ) + } + + return nil + } +} diff --git a/test/e2e/framework/helper/validation/certificates/certificates.go b/test/e2e/framework/helper/validation/certificates/certificates.go index 3fbfd749875..b974eee8c08 100644 --- a/test/e2e/framework/helper/validation/certificates/certificates.go +++ b/test/e2e/framework/helper/validation/certificates/certificates.go @@ -18,16 +18,16 @@ package certificates import ( "bytes" - "crypto/ecdsa" - "crypto/ed25519" - "crypto/rsa" "crypto/x509" "encoding/pem" "fmt" + "slices" "strings" + "time" - "github.com/kr/pretty" corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/util/dump" + "k8s.io/apimachinery/pkg/util/sets" apiutil "github.com/cert-manager/cert-manager/pkg/api/util" cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" @@ -82,31 +82,11 @@ func ExpectValidPrivateKeyData(certificate *cmapi.Certificate, secret *corev1.Se return err } - // validate private key is of the correct type (rsa, ed25519 or ecdsa) - if certificate.Spec.PrivateKey != nil { - switch certificate.Spec.PrivateKey.Algorithm { - case cmapi.PrivateKeyAlgorithm(""), - cmapi.RSAKeyAlgorithm: - _, ok := key.(*rsa.PrivateKey) - if !ok { - return fmt.Errorf("Expected private key of type RSA, but it was: %T", key) - } - case cmapi.ECDSAKeyAlgorithm: - _, ok := key.(*ecdsa.PrivateKey) - if !ok { - return fmt.Errorf("Expected private key of type ECDSA, but it was: %T", key) - } - case cmapi.Ed25519KeyAlgorithm: - _, ok := key.(ed25519.PrivateKey) - if !ok { - return fmt.Errorf("Expected private key of type Ed25519, but it was: %T", key) - } - default: - return fmt.Errorf("unrecognised requested private key algorithm %q", certificate.Spec.PrivateKey.Algorithm) - } + violations := pki.PrivateKeyMatchesSpec(key, certificate.Spec) + if len(violations) > 0 { + return fmt.Errorf("Private key does not match Certificate: %s", strings.Join(violations, ", ")) } - // TODO: validate private key KeySize return nil } @@ -132,7 +112,27 @@ func ExpectCertificateOrganizationToMatch(certificate *cmapi.Certificate, secret return err } - expectedOrganization := pki.OrganizationForCertificate(certificate) + var expectedOrganization []string + if certificate.Spec.Subject != nil { + expectedOrganization = certificate.Spec.Subject.Organizations + } + if certificate.Spec.LiteralSubject != "" { + sequence, err := pki.UnmarshalSubjectStringToRDNSequence(certificate.Spec.LiteralSubject) + if err != nil { + return err + } + + for _, rdns := range sequence { + for _, atv := range rdns { + if atv.Type.Equal(pki.OIDConstants.Organization) { + if str, ok := atv.Value.(string); ok { + expectedOrganization = append(expectedOrganization, str) + } + } + } + } + } + if !util.EqualUnsorted(cert.Subject.Organization, expectedOrganization) { return fmt.Errorf("Expected certificate valid for O %v, but got a certificate valid for O %v", expectedOrganization, cert.Subject.Organization) } @@ -147,9 +147,10 @@ func ExpectCertificateDNSNamesToMatch(certificate *cmapi.Certificate, secret *co return err } - expectedDNSNames := certificate.Spec.DNSNames - if !util.Subset(cert.DNSNames, expectedDNSNames) { - return fmt.Errorf("Expected certificate valid for DNSNames %v, but got a certificate valid for DNSNames %v", expectedDNSNames, cert.DNSNames) + x509DNSNames := sets.New(cert.DNSNames...) + expectedDNSNames := sets.New(certificate.Spec.DNSNames...) + if !x509DNSNames.IsSuperset(expectedDNSNames) { + return fmt.Errorf("Expected certificate valid for DNSNames %v, but got a certificate valid for DNSNames %v", sets.List(expectedDNSNames), sets.List(x509DNSNames)) } return nil @@ -162,20 +163,32 @@ func ExpectCertificateURIsToMatch(certificate *cmapi.Certificate, secret *corev1 return err } - uris, err := pki.URIsForCertificate(certificate) - if err != nil { - return fmt.Errorf("failed to parse URIs: %s", err) - } actualURIs := pki.URLsToString(cert.URIs) - expectedURIs := pki.URLsToString(uris) + expectedURIs := certificate.Spec.URIs if !util.EqualUnsorted(actualURIs, expectedURIs) { - return fmt.Errorf("Expected certificate valid for URIs %v, but got a certificate valid for URIs %v", expectedURIs, pki.URLsToString(cert.URIs)) + return fmt.Errorf("Expected certificate valid for URIs %v, but got a certificate valid for URIs %v", expectedURIs, actualURIs) } return nil } -// ExpectValidCommonName checks if the issued certificate has the requested CN or one of the DNS SANs +// ExpectCertificateIPsToMatch checks if the issued certificate has all IP SANs names it requested +func ExpectCertificateIPsToMatch(certificate *cmapi.Certificate, secret *corev1.Secret) error { + cert, err := pki.DecodeX509CertificateBytes(secret.Data[corev1.TLSCertKey]) + if err != nil { + return err + } + + actualIPs := pki.IPAddressesToString(cert.IPAddresses) + expectedIPs := certificate.Spec.IPAddresses + if !util.EqualUnsorted(actualIPs, expectedIPs) { + return fmt.Errorf("Expected certificate valid for IPs %v, but got a certificate valid for IPs %v", expectedIPs, actualIPs) + } + + return nil +} + +// ExpectValidCommonName checks if the issued certificate has the requested CN or one of the DNS (or IP Address) SANs func ExpectValidCommonName(certificate *cmapi.Certificate, secret *corev1.Secret) error { cert, err := pki.DecodeX509CertificateBytes(secret.Data[corev1.TLSCertKey]) if err != nil { @@ -186,7 +199,7 @@ func ExpectValidCommonName(certificate *cmapi.Certificate, secret *corev1.Secret if len(expectedCN) == 0 && len(cert.Subject.CommonName) > 0 { // no CN is specified but our CA set one, checking if it is one of our DNS names or IP Addresses - if !util.Contains(cert.DNSNames, cert.Subject.CommonName) && !util.Contains(pki.IPAddressesToString(cert.IPAddresses), cert.Subject.CommonName) { + if !slices.Contains(cert.DNSNames, cert.Subject.CommonName) && !slices.Contains(pki.IPAddressesToString(cert.IPAddresses), cert.Subject.CommonName) { return fmt.Errorf("Expected a common name for one of our DNSNames %v or IP Addresses %v, but got a CN of %v", cert.DNSNames, pki.IPAddressesToString(cert.IPAddresses), cert.Subject.CommonName) } } else if expectedCN != cert.Subject.CommonName { @@ -196,7 +209,7 @@ func ExpectValidCommonName(certificate *cmapi.Certificate, secret *corev1.Secret return nil } -// ExpectValidNotAfterDate checks if the issued certificate matches the requested duration +// ExpectValidNotAfterDate checks if the certificate's NotAfter status matches the NotAfter time of the encoded certificate. func ExpectValidNotAfterDate(certificate *cmapi.Certificate, secret *corev1.Secret) error { cert, err := pki.DecodeX509CertificateBytes(secret.Data[corev1.TLSCertKey]) if err != nil { @@ -213,15 +226,6 @@ func ExpectValidNotAfterDate(certificate *cmapi.Certificate, secret *corev1.Secr return nil } -func containsExtKeyUsage(s []x509.ExtKeyUsage, e x509.ExtKeyUsage) bool { - for _, a := range s { - if a == e { - return true - } - } - return false -} - // ExpectKeyUsageExtKeyUsageServerAuth checks if the issued certificate has the extended key usage of server auth func ExpectKeyUsageExtKeyUsageServerAuth(certificate *cmapi.Certificate, secret *corev1.Secret) error { cert, err := pki.DecodeX509CertificateBytes(secret.Data[corev1.TLSCertKey]) @@ -229,7 +233,7 @@ func ExpectKeyUsageExtKeyUsageServerAuth(certificate *cmapi.Certificate, secret return err } - if !containsExtKeyUsage(cert.ExtKeyUsage, x509.ExtKeyUsageServerAuth) { + if !slices.Contains(cert.ExtKeyUsage, x509.ExtKeyUsageServerAuth) { return fmt.Errorf("Expected certificate to have ExtKeyUsageServerAuth, but got %v", cert.ExtKeyUsage) } return nil @@ -242,7 +246,7 @@ func ExpectKeyUsageExtKeyUsageClientAuth(certificate *cmapi.Certificate, secret return err } - if !containsExtKeyUsage(cert.ExtKeyUsage, x509.ExtKeyUsageClientAuth) { + if !slices.Contains(cert.ExtKeyUsage, x509.ExtKeyUsageClientAuth) { return fmt.Errorf("Expected certificate to have ExtKeyUsageClientAuth, but got %v", cert.ExtKeyUsage) } return nil @@ -322,10 +326,10 @@ func ExpectCorrectTrustChain(certificate *cmapi.Certificate, secret *corev1.Secr if _, err := cert.Verify(opts); err != nil { return fmt.Errorf( - "verify error. CERT:\n%s\nROOTS\n%s\nINTERMEDIATES\n%v\nERROR\n%s\n", - pretty.Sprint(cert), - pretty.Sprint(rootCertPool), - pretty.Sprint(intermediateCertPool), + "verify error. CERT:\n%s\nROOTS\n%s\nINTERMEDIATES\n%s\nERROR\n%s\n", + dump.Pretty(cert), + dump.Pretty(rootCertPool), + dump.Pretty(intermediateCertPool), err, ) } @@ -387,7 +391,7 @@ func ExpectValidAdditionalOutputFormats(certificate *cmapi.Certificate, secret * privateKey := secret.Data[corev1.TLSPrivateKeyKey] block, _ := pem.Decode(privateKey) if !bytes.Equal(derKey, block.Bytes) { - return fmt.Errorf("expected additional output Format DER %s to contain the binary formated private Key", cmapi.CertificateOutputFormatDERKey) + return fmt.Errorf("expected additional output Format DER %s to contain the binary formatted private Key", cmapi.CertificateOutputFormatDERKey) } } else { return fmt.Errorf("expected additional output format DER key %s to be present in secret", cmapi.CertificateOutputFormatDERKey) @@ -412,3 +416,36 @@ func ExpectValidAdditionalOutputFormats(certificate *cmapi.Certificate, secret * return nil } + +// ExpectDuration checks if the issued certificate matches the Certificate's duration +func ExpectDurationToMatch(certificate *cmapi.Certificate, secret *corev1.Secret) error { + certDuration := apiutil.DefaultCertDuration(certificate.Spec.Duration) + return ExpectDuration(certDuration, 30*time.Second)(certificate, secret) +} + +// ExpectDuration checks if the issued certificate matches the provided duration +func ExpectDuration(expectedDuration, fuzz time.Duration) func(certificate *cmapi.Certificate, secret *corev1.Secret) error { + return func(certificate *cmapi.Certificate, secret *corev1.Secret) error { + certBytes, ok := secret.Data[corev1.TLSCertKey] + if !ok { + return fmt.Errorf("no certificate data found in secret %q", secret.Name) + } + cert, err := pki.DecodeX509CertificateBytes(certBytes) + if err != nil { + return err + } + + // Here we ensure that the requested duration is what is signed on the + // certificate. We tolerate fuzz either way. + actualDuration := cert.NotAfter.Sub(cert.NotBefore) + if actualDuration > (expectedDuration+fuzz) || actualDuration < (expectedDuration-fuzz) { + return fmt.Errorf( + "expected duration of %s, got %s (fuzz: %s) [NotBefore: %s, NotAfter: %s]", + expectedDuration, actualDuration, fuzz, + cert.NotBefore.Format(time.RFC3339), cert.NotAfter.Format(time.RFC3339), + ) + } + + return nil + } +} diff --git a/test/e2e/framework/helper/validation/certificatesigningrequests/certificatesigningrequests.go b/test/e2e/framework/helper/validation/certificatesigningrequests/certificatesigningrequests.go index 84cc524c747..ff64e9df61d 100644 --- a/test/e2e/framework/helper/validation/certificatesigningrequests/certificatesigningrequests.go +++ b/test/e2e/framework/helper/validation/certificatesigningrequests/certificatesigningrequests.go @@ -24,11 +24,11 @@ import ( "crypto/x509" "errors" "fmt" + "slices" "time" certificatesv1 "k8s.io/api/certificates/v1" - cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" experimentalapi "github.com/cert-manager/cert-manager/pkg/apis/experimental/v1alpha1" ctrlutil "github.com/cert-manager/cert-manager/pkg/controller/certificatesigningrequests/util" "github.com/cert-manager/cert-manager/pkg/util" @@ -38,7 +38,7 @@ import ( // ValidationFunc describes a CertificateSigningRequest validation helper function type ValidationFunc func(csr *certificatesv1.CertificateSigningRequest, key crypto.Signer) error -// ExpectValidCertificateCertificate checks if the certificate is a valid x509 certificate +// ExpectValidCertificate checks if the certificate is a valid x509 certificate func ExpectValidCertificate(csr *certificatesv1.CertificateSigningRequest, _ crypto.Signer) error { _, err := pki.DecodeX509CertificateBytes(csr.Status.Certificate) return err @@ -159,7 +159,7 @@ func ExpectCertificateIPsToMatch(csr *certificatesv1.CertificateSigningRequest, return nil } -// ExpectValidCommonName checks if the issued certificate has the requested CN or one of the DNS SANs +// ExpectValidCommonName checks if the issued certificate has the requested CN or one of the DNS (or IP Address) SANs func ExpectValidCommonName(csr *certificatesv1.CertificateSigningRequest, _ crypto.Signer) error { cert, err := pki.DecodeX509CertificateBytes(csr.Status.Certificate) if err != nil { @@ -174,7 +174,7 @@ func ExpectValidCommonName(csr *certificatesv1.CertificateSigningRequest, _ cryp if len(expectedCN) == 0 && len(cert.Subject.CommonName) > 0 { // no CN is specified but our CA set one, checking if it is one of our DNS names or IP Addresses - if !util.Contains(cert.DNSNames, cert.Subject.CommonName) && !util.Contains(pki.IPAddressesToString(cert.IPAddresses), cert.Subject.CommonName) { + if !slices.Contains(cert.DNSNames, cert.Subject.CommonName) && !slices.Contains(pki.IPAddressesToString(cert.IPAddresses), cert.Subject.CommonName) { return fmt.Errorf("Expected a common name for one of our DNSNames %v or IP Addresses %v, but got a CN of %v", cert.DNSNames, pki.IPAddressesToString(cert.IPAddresses), cert.Subject.CommonName) } } else if expectedCN != cert.Subject.CommonName { @@ -184,47 +184,36 @@ func ExpectValidCommonName(csr *certificatesv1.CertificateSigningRequest, _ cryp return nil } -// ExpectValidDuration checks if the issued certificate matches the requested duration -func ExpectValidDuration(csr *certificatesv1.CertificateSigningRequest, _ crypto.Signer) error { - cert, err := pki.DecodeX509CertificateBytes(csr.Status.Certificate) +// ExpectDuration checks if the issued certificate matches the CertificateSigningRequest's duration +func ExpectDurationToMatch(csr *certificatesv1.CertificateSigningRequest, signer crypto.Signer) error { + certDuration, err := pki.DurationFromCertificateSigningRequest(csr) if err != nil { return err } + return ExpectDuration(certDuration, 30*time.Second)(csr, signer) +} - var expectedDuration time.Duration - durationString, ok := csr.Annotations[experimentalapi.CertificateSigningRequestDurationAnnotationKey] - if !ok { - if csr.Spec.ExpirationSeconds != nil { - expectedDuration = time.Duration(*csr.Spec.ExpirationSeconds) * time.Second - } else { - // If duration wasn't requested, then we match against the default. - expectedDuration = cmapi.DefaultCertificateDuration - } - } else { - expectedDuration, err = time.ParseDuration(durationString) +// ExpectDuration checks if the issued certificate matches the provided duration +func ExpectDuration(expectedDuration, fuzz time.Duration) func(csr *certificatesv1.CertificateSigningRequest, _ crypto.Signer) error { + return func(csr *certificatesv1.CertificateSigningRequest, _ crypto.Signer) error { + cert, err := pki.DecodeX509CertificateBytes(csr.Status.Certificate) if err != nil { return err } - } - - actualDuration := cert.NotAfter.Sub(cert.NotBefore) - - // Here we ensure that the requested duration is what is signed on the - // certificate. We tolerate a 30 second fuzz either way. - if actualDuration > expectedDuration+time.Second*30 || actualDuration < expectedDuration-time.Second*30 { - return fmt.Errorf("Expected certificate expiry date to be %v, but got %v", expectedDuration, actualDuration) - } - - return nil -} -func containsExtKeyUsage(s []x509.ExtKeyUsage, e x509.ExtKeyUsage) bool { - for _, a := range s { - if a == e { - return true + // Here we ensure that the requested duration is what is signed on the + // certificate. We tolerate fuzz either way. + actualDuration := cert.NotAfter.Sub(cert.NotBefore) + if actualDuration > (expectedDuration+fuzz) || actualDuration < (expectedDuration-fuzz) { + return fmt.Errorf( + "Expected duration of %s, got %s (fuzz: %s) [NotBefore: %s, NotAfter: %s]", + expectedDuration, actualDuration, fuzz, + cert.NotBefore.Format(time.RFC3339), cert.NotAfter.Format(time.RFC3339), + ) } + + return nil } - return false } // ExpectKeyUsageExtKeyUsageServerAuth checks if the issued certificate has the @@ -235,7 +224,7 @@ func ExpectKeyUsageExtKeyUsageServerAuth(csr *certificatesv1.CertificateSigningR return err } - if !containsExtKeyUsage(cert.ExtKeyUsage, x509.ExtKeyUsageServerAuth) { + if !slices.Contains(cert.ExtKeyUsage, x509.ExtKeyUsageServerAuth) { return fmt.Errorf("Expected certificate to have ExtKeyUsageServerAuth, but got %v", cert.ExtKeyUsage) } return nil @@ -249,7 +238,7 @@ func ExpectKeyUsageExtKeyUsageClientAuth(csr *certificatesv1.CertificateSigningR return err } - if !containsExtKeyUsage(cert.ExtKeyUsage, x509.ExtKeyUsageClientAuth) { + if !slices.Contains(cert.ExtKeyUsage, x509.ExtKeyUsageClientAuth) { return fmt.Errorf("Expected certificate to have ExtKeyUsageClientAuth, but got %v", cert.ExtKeyUsage) } return nil @@ -313,27 +302,21 @@ func ExpectEmailsToMatch(csr *certificatesv1.CertificateSigningRequest, _ crypto return nil } -// ExpectIsCA checks the certificate is a CA if requested -func ExpectIsCA(csr *certificatesv1.CertificateSigningRequest, _ crypto.Signer) error { +// ExpectValidBasicConstraints checks the certificate is a CA if requested +func ExpectValidBasicConstraints(csr *certificatesv1.CertificateSigningRequest, _ crypto.Signer) error { cert, err := pki.DecodeX509CertificateBytes(csr.Status.Certificate) if err != nil { return err } - markedIsCA := false - if csr.Annotations[experimentalapi.CertificateSigningRequestIsCAAnnotationKey] == "true" { - markedIsCA = true - } + markedIsCA := csr.Annotations[experimentalapi.CertificateSigningRequestIsCAAnnotationKey] == "true" if cert.IsCA != markedIsCA { return fmt.Errorf("requested certificate does not match expected IsCA, exp=%t got=%t", markedIsCA, cert.IsCA) } - hasCertSign := (cert.KeyUsage & x509.KeyUsageCertSign) == x509.KeyUsageCertSign - if hasCertSign != markedIsCA { - return fmt.Errorf("Expected certificate to have KeyUsageCertSign=%t, but got=%t", markedIsCA, hasCertSign) - } + // TODO: also validate pathLen return nil } @@ -350,7 +333,7 @@ func ExpectConditionApproved(csr *certificatesv1.CertificateSigningRequest, _ cr // ExpectConditionNotDenied checks that the CertificateSigningRequest has not // been Denied -func ExpectConditiotNotDenied(csr *certificatesv1.CertificateSigningRequest, _ crypto.Signer) error { +func ExpectConditionNotDenied(csr *certificatesv1.CertificateSigningRequest, _ crypto.Signer) error { if ctrlutil.CertificateSigningRequestIsDenied(csr) { return fmt.Errorf("CertificateSigningRequest has a Denied condition: %v", csr.Status.Conditions) } diff --git a/test/e2e/framework/helper/validation/validation.go b/test/e2e/framework/helper/validation/validation.go index 9371cb11de3..6a083e3b6f1 100644 --- a/test/e2e/framework/helper/validation/validation.go +++ b/test/e2e/framework/helper/validation/validation.go @@ -17,76 +17,53 @@ limitations under the License. package validation import ( - "github.com/cert-manager/cert-manager/test/e2e/framework/helper/featureset" - "github.com/cert-manager/cert-manager/test/e2e/framework/helper/validation/certificates" - "github.com/cert-manager/cert-manager/test/e2e/framework/helper/validation/certificatesigningrequests" + "github.com/cert-manager/cert-manager/e2e-tests/framework/helper/featureset" + "github.com/cert-manager/cert-manager/e2e-tests/framework/helper/validation/certificaterequests" + "github.com/cert-manager/cert-manager/e2e-tests/framework/helper/validation/certificates" + "github.com/cert-manager/cert-manager/e2e-tests/framework/helper/validation/certificatesigningrequests" ) -func DefaultCertificateSet() []certificates.ValidationFunc { - return []certificates.ValidationFunc{ - certificates.ExpectValidKeysInSecret, +func CertificateSetForUnsupportedFeatureSet(fs featureset.FeatureSet) []certificates.ValidationFunc { + // basics + out := []certificates.ValidationFunc{ certificates.ExpectCertificateDNSNamesToMatch, certificates.ExpectCertificateOrganizationToMatch, - certificates.ExpectCertificateURIsToMatch, - certificates.ExpectCorrectTrustChain, - certificates.ExpectCARootCertificate, - certificates.ExpectEmailsToMatch, - certificates.ExpectValidAnnotations, certificates.ExpectValidCertificate, - certificates.ExpectValidCommonName, - certificates.ExpectValidNotAfterDate, certificates.ExpectValidPrivateKeyData, - certificates.ExpectConditionReadyObservedGeneration, certificates.ExpectValidBasicConstraints, - certificates.ExpectValidAdditionalOutputFormats, - } -} -func DefaultCertificateSigningRequestSet() []certificatesigningrequests.ValidationFunc { - return []certificatesigningrequests.ValidationFunc{ - certificatesigningrequests.ExpectValidCertificate, - certificatesigningrequests.ExpectCertificateOrganizationToMatch, - certificatesigningrequests.ExpectValidPrivateKeyData, - certificatesigningrequests.ExpectCertificateDNSNamesToMatch, - certificatesigningrequests.ExpectCertificateURIsToMatch, - certificatesigningrequests.ExpectCertificateIPsToMatch, - certificatesigningrequests.ExpectValidCommonName, - certificatesigningrequests.ExpectKeyUsageUsageDigitalSignature, - certificatesigningrequests.ExpectEmailsToMatch, - certificatesigningrequests.ExpectIsCA, - certificatesigningrequests.ExpectConditionApproved, - certificatesigningrequests.ExpectConditiotNotDenied, - certificatesigningrequests.ExpectConditionNotFailed, - } -} - -func CertificateSetForUnsupportedFeatureSet(fs featureset.FeatureSet) []certificates.ValidationFunc { - // basics - out := []certificates.ValidationFunc{ + certificates.ExpectValidNotAfterDate, certificates.ExpectValidKeysInSecret, - certificates.ExpectCertificateDNSNamesToMatch, - certificates.ExpectCertificateOrganizationToMatch, certificates.ExpectValidAnnotations, - certificates.ExpectValidCertificate, - certificates.ExpectValidCommonName, - certificates.ExpectValidNotAfterDate, - certificates.ExpectValidPrivateKeyData, + certificates.ExpectValidAdditionalOutputFormats, + certificates.ExpectConditionReadyObservedGeneration, - certificates.ExpectValidBasicConstraints, } - if !fs.Contains(featureset.URISANsFeature) { + if !fs.Has(featureset.CommonNameFeature) { + out = append(out, certificates.ExpectValidCommonName) + } + + if !fs.Has(featureset.URISANsFeature) { out = append(out, certificates.ExpectCertificateURIsToMatch) } - if !fs.Contains(featureset.EmailSANsFeature) { + if !fs.Has(featureset.EmailSANsFeature) { out = append(out, certificates.ExpectEmailsToMatch) } - if !fs.Contains(featureset.SaveCAToSecret) { + if !fs.Has(featureset.IPAddressFeature) { + out = append(out, certificates.ExpectCertificateIPsToMatch) + } + + if !fs.Has(featureset.DurationFeature) { + out = append(out, certificates.ExpectDurationToMatch) + } + + if !fs.Has(featureset.SaveCAToSecret) { out = append(out, certificates.ExpectCorrectTrustChain) - if !fs.Contains(featureset.SaveRootCAToSecret) { + if !fs.Has(featureset.SaveRootCAToSecret) { out = append(out, certificates.ExpectCARootCertificate) } } @@ -94,12 +71,77 @@ func CertificateSetForUnsupportedFeatureSet(fs featureset.FeatureSet) []certific return out } +func CertificateRequestSetForUnsupportedFeatureSet(fs featureset.FeatureSet) []certificaterequests.ValidationFunc { + // basics + out := []certificaterequests.ValidationFunc{ + certificaterequests.ExpectCertificateDNSNamesToMatch, + certificaterequests.ExpectCertificateOrganizationToMatch, + certificaterequests.ExpectValidCertificate, + certificaterequests.ExpectValidPrivateKeyData, + certificaterequests.ExpectValidBasicConstraints, + + certificaterequests.ExpectConditionApproved, + certificaterequests.ExpectConditionNotDenied, + } + + if !fs.Has(featureset.CommonNameFeature) { + out = append(out, certificaterequests.ExpectValidCommonName) + } + + if !fs.Has(featureset.URISANsFeature) { + out = append(out, certificaterequests.ExpectCertificateURIsToMatch) + } + + if !fs.Has(featureset.EmailSANsFeature) { + out = append(out, certificaterequests.ExpectEmailsToMatch) + } + + if !fs.Has(featureset.IPAddressFeature) { + out = append(out, certificaterequests.ExpectCertificateIPsToMatch) + } + + if !fs.Has(featureset.DurationFeature) { + out = append(out, certificaterequests.ExpectDurationToMatch) + } + + return out +} + func CertificateSigningRequestSetForUnsupportedFeatureSet(fs featureset.FeatureSet) []certificatesigningrequests.ValidationFunc { - validations := DefaultCertificateSigningRequestSet() + // basics + out := []certificatesigningrequests.ValidationFunc{ + certificatesigningrequests.ExpectCertificateDNSNamesToMatch, + certificatesigningrequests.ExpectCertificateOrganizationToMatch, + certificatesigningrequests.ExpectValidCertificate, + certificatesigningrequests.ExpectValidPrivateKeyData, + certificatesigningrequests.ExpectValidBasicConstraints, + + certificatesigningrequests.ExpectKeyUsageUsageDigitalSignature, + + certificatesigningrequests.ExpectConditionApproved, + certificatesigningrequests.ExpectConditionNotDenied, + certificatesigningrequests.ExpectConditionNotFailed, + } - if !fs.Contains(featureset.DurationFeature) { - validations = append(validations, certificatesigningrequests.ExpectValidDuration) + if !fs.Has(featureset.CommonNameFeature) { + out = append(out, certificatesigningrequests.ExpectValidCommonName) } - return validations + if !fs.Has(featureset.URISANsFeature) { + out = append(out, certificatesigningrequests.ExpectCertificateURIsToMatch) + } + + if !fs.Has(featureset.EmailSANsFeature) { + out = append(out, certificatesigningrequests.ExpectEmailsToMatch) + } + + if !fs.Has(featureset.IPAddressFeature) { + out = append(out, certificatesigningrequests.ExpectCertificateIPsToMatch) + } + + if !fs.Has(featureset.DurationFeature) { + out = append(out, certificatesigningrequests.ExpectDurationToMatch) + } + + return out } diff --git a/test/e2e/framework/log/log.go b/test/e2e/framework/log/log.go index e570b88adf2..44f20e09994 100644 --- a/test/e2e/framework/log/log.go +++ b/test/e2e/framework/log/log.go @@ -31,12 +31,12 @@ func nowStamp() string { return time.Now().Format(time.StampMilli) } -func log(level string, format string, args ...interface{}) { +func logf(level string, format string, args ...interface{}) { fmt.Fprintf(Writer, nowStamp()+": "+level+": "+format+"\n", args...) } func Logf(format string, args ...interface{}) { - log("INFO", format, args...) + logf("INFO", format, args...) } // LogBackoff gives you a logger with an exponential backoff. If the diff --git a/test/e2e/framework/matcher/have_condition_matcher.go b/test/e2e/framework/matcher/have_condition_matcher.go index f9b14c9eec0..091ea793bf5 100644 --- a/test/e2e/framework/matcher/have_condition_matcher.go +++ b/test/e2e/framework/matcher/have_condition_matcher.go @@ -24,8 +24,8 @@ import ( "github.com/onsi/gomega/types" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "github.com/cert-manager/cert-manager/e2e-tests/framework" cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" - "github.com/cert-manager/cert-manager/test/e2e/framework" ) // HaveCondition will wait for up to the diff --git a/test/e2e/framework/matcher/san_matchers.go b/test/e2e/framework/matcher/san_matchers.go new file mode 100644 index 00000000000..b7e19124c48 --- /dev/null +++ b/test/e2e/framework/matcher/san_matchers.go @@ -0,0 +1,142 @@ +/* +Copyright 2020 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. +*/ + +package matcher + +import ( + "crypto/x509" + "crypto/x509/pkix" + "encoding/asn1" + "encoding/base64" + "encoding/pem" + "fmt" + "reflect" + "sort" + + "github.com/onsi/gomega/types" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +func HaveSameSANsAs(certWithExpectedSAN string) types.GomegaMatcher { + return SANEquals(extractSANsFromCertificate(certWithExpectedSAN)) +} + +// HaveSans will check that the PEM of the certificates +func SANEquals(sanExtensionExpected interface{}) *SANMatcher { + extension, ok := sanExtensionExpected.(pkix.Extension) + if !ok || !extension.Id.Equal(oidExtensionSubjectAltName) { + Fail("Invalid use of the SANEquals matcher, please supply a valid SAN pkix.Extension") + } + return &SANMatcher{ + SANExtensionExpected: extension, + } +} + +type SANMatcher struct { + SANExtensionExpected pkix.Extension +} + +// Comparing pkix.Extensions obtained from an expected pkix.Extension +func (s *SANMatcher) Match(actual interface{}) (success bool, err error) { + actualExtensions, ok := actual.([]pkix.Extension) + if !ok { + return false, fmt.Errorf("Invalid use of the SANEquals matcher, please supply a valid SAN pkix.Extension") + } + + var actualSANExtension pkix.Extension + var SANFound bool + for _, extension := range actualExtensions { + if extension.Id.Equal(oidExtensionSubjectAltName) { + actualSANExtension = extension + SANFound = true + } + } + + if !SANFound { + return false, fmt.Errorf("The supplied Extensions does not contain a SAN extension, got: %v", actualExtensions) + } + + var actualGeneralNames []asn1.RawValue + rest, err := asn1.Unmarshal(actualSANExtension.Value, &actualGeneralNames) + if err != nil { + return false, err + } else if len(rest) != 0 { + return false, fmt.Errorf("x509: trailing data after X.509 extension") + } + + var expectedGeneralNames []asn1.RawValue + rest, err = asn1.Unmarshal(s.SANExtensionExpected.Value, &expectedGeneralNames) + if err != nil { + return false, err + } else if len(rest) != 0 { + return false, fmt.Errorf("x509: trailing data after X.509 extension") + } + + sortGeneralNamesByTagBytes(actualGeneralNames) + sortGeneralNamesByTagBytes(expectedGeneralNames) + + return reflect.DeepEqual(actualGeneralNames, expectedGeneralNames), nil + +} + +// TODO tested manually with same SAN, same type with different ordering successfully +// we should still add unit tests in future as it's a non trivial matcher +func sortGeneralNamesByTagBytes(generalNames []asn1.RawValue) { + + sort.Slice(generalNames, func(i, j int) bool { + if generalNames[i].Tag < generalNames[j].Tag { + return true + } + if generalNames[i].Tag == generalNames[j].Tag { + // we compare the stringified base64 encoding of the bytes to ensure a different ordering when the + // same SAN type is used twice + + return base64.StdEncoding.EncodeToString(generalNames[i].Bytes) < base64.StdEncoding.EncodeToString(generalNames[j].Bytes) + } + return false + }) + +} + +func (s *SANMatcher) FailureMessage(actual interface{}) (message string) { + return fmt.Sprintf("Supplied SAN did not match the expected SAN (even disregarding ordering).\n Actual: %v\nExpected:%v", actual, s.SANExtensionExpected) +} + +func (s *SANMatcher) NegatedFailureMessage(actual interface{}) (message string) { + return fmt.Sprintf("Supplied SAN matched the expected SAN (modulo ordering) which was not expected.\n Actual: %v\nExpected: %v", actual, s.SANExtensionExpected) + +} + +var oidExtensionSubjectAltName = []int{2, 5, 29, 17} + +func extractSANsFromCertificate(certDER string) pkix.Extension { + block, rest := pem.Decode([]byte(certDER)) + Expect(rest).To(BeEmpty()) + + cert, err := x509.ParseCertificate(block.Bytes) + Expect(err).NotTo(HaveOccurred()) + + for _, extension := range cert.Extensions { + if extension.Id.Equal(oidExtensionSubjectAltName) { + return extension + } + } + + Fail("Could not find SANs in certificate") + return pkix.Extension{} +} diff --git a/test/e2e/framework/testenv.go b/test/e2e/framework/testenv.go index dc04804613d..f248164393f 100644 --- a/test/e2e/framework/testenv.go +++ b/test/e2e/framework/testenv.go @@ -22,11 +22,8 @@ import ( "time" v1 "k8s.io/api/core/v1" - apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/util/wait" - "k8s.io/client-go/kubernetes" ) // Defines methods that help provision test environments @@ -37,19 +34,19 @@ const ( ) // CreateKubeNamespace creates a new Kubernetes Namespace for a test. -func (f *Framework) CreateKubeNamespace(baseName string) (*v1.Namespace, error) { +func (f *Framework) CreateKubeNamespace(ctx context.Context, baseName string) (*v1.Namespace, error) { ns := &v1.Namespace{ ObjectMeta: metav1.ObjectMeta{ GenerateName: fmt.Sprintf("e2e-tests-%v-", baseName), }, } - return f.KubeClientSet.CoreV1().Namespaces().Create(context.TODO(), ns, metav1.CreateOptions{}) + return f.KubeClientSet.CoreV1().Namespaces().Create(ctx, ns, metav1.CreateOptions{}) } // CreateKubeResourceQuota provisions a ResourceQuota resource in the target // namespace. -func (f *Framework) CreateKubeResourceQuota() (*v1.ResourceQuota, error) { +func (f *Framework) CreateKubeResourceQuota(ctx context.Context) (*v1.ResourceQuota, error) { quota := &v1.ResourceQuota{ ObjectMeta: metav1.ObjectMeta{ Name: "default-e2e-quota", @@ -66,29 +63,10 @@ func (f *Framework) CreateKubeResourceQuota() (*v1.ResourceQuota, error) { }, }, } - return f.KubeClientSet.CoreV1().ResourceQuotas(f.Namespace.Name).Create(context.TODO(), quota, metav1.CreateOptions{}) + return f.KubeClientSet.CoreV1().ResourceQuotas(f.Namespace.Name).Create(ctx, quota, metav1.CreateOptions{}) } // DeleteKubeNamespace will delete a namespace resource -func (f *Framework) DeleteKubeNamespace(namespace string) error { - return f.KubeClientSet.CoreV1().Namespaces().Delete(context.TODO(), namespace, metav1.DeleteOptions{}) -} - -// WaitForKubeNamespaceNotExist will wait for the namespace with the given name -// to not exist for up to 2 minutes. -func (f *Framework) WaitForKubeNamespaceNotExist(namespace string) error { - return wait.PollImmediate(Poll, time.Minute*2, namespaceNotExist(f.KubeClientSet, namespace)) -} - -func namespaceNotExist(c kubernetes.Interface, namespace string) wait.ConditionFunc { - return func() (bool, error) { - _, err := c.CoreV1().Namespaces().Get(context.TODO(), namespace, metav1.GetOptions{}) - if apierrors.IsNotFound(err) { - return true, nil - } - if err != nil { - return false, err - } - return false, nil - } +func (f *Framework) DeleteKubeNamespace(ctx context.Context, namespace string) error { + return f.KubeClientSet.CoreV1().Namespaces().Delete(ctx, namespace, metav1.DeleteOptions{}) } diff --git a/test/e2e/framework/util.go b/test/e2e/framework/util.go index d5937d674ca..d571480fa73 100644 --- a/test/e2e/framework/util.go +++ b/test/e2e/framework/util.go @@ -21,18 +21,16 @@ import ( "fmt" "time" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - authorizationv1 "k8s.io/api/authorization/v1" v1 "k8s.io/api/core/v1" rbacv1 "k8s.io/api/rbac/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes" - "k8s.io/client-go/rest" "k8s.io/component-base/featuregate" - . "github.com/cert-manager/cert-manager/test/e2e/framework/log" + . "github.com/cert-manager/cert-manager/e2e-tests/framework/log" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" ) func nowStamp() string { @@ -51,14 +49,14 @@ func Skipf(format string, args ...interface{}) { Skip(nowStamp() + ": " + msg) } -func RequireFeatureGate(f *Framework, featureSet featuregate.FeatureGate, gate featuregate.Feature) { +func RequireFeatureGate(featureSet featuregate.FeatureGate, gate featuregate.Feature) { if !featureSet.Enabled(gate) { Skipf("feature gate %q is not enabled, skipping test", gate) } } // TODO: move this function into a different package -func RbacClusterRoleHasAccessToResource(f *Framework, clusterRole string, verb string, resource string) bool { +func RbacClusterRoleHasAccessToResource(ctx context.Context, f *Framework, clusterRole string, verb string, resource string) bool { By("Creating a service account") viewServiceAccount := &v1.ServiceAccount{ ObjectMeta: metav1.ObjectMeta{ @@ -66,7 +64,7 @@ func RbacClusterRoleHasAccessToResource(f *Framework, clusterRole string, verb s }, } serviceAccountClient := f.KubeClientSet.CoreV1().ServiceAccounts(f.Namespace.Name) - serviceAccount, err := serviceAccountClient.Create(context.TODO(), viewServiceAccount, metav1.CreateOptions{}) + serviceAccount, err := serviceAccountClient.Create(ctx, viewServiceAccount, metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred()) viewServiceAccountName := serviceAccount.Name @@ -85,7 +83,7 @@ func RbacClusterRoleHasAccessToResource(f *Framework, clusterRole string, verb s }, } roleBindingClient := f.KubeClientSet.RbacV1().ClusterRoleBindings() - _, err = roleBindingClient.Create(context.TODO(), viewRoleBinding, metav1.CreateOptions{}) + _, err = roleBindingClient.Create(ctx, viewRoleBinding, metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred()) By("Sleeping for a second.") @@ -93,8 +91,7 @@ func RbacClusterRoleHasAccessToResource(f *Framework, clusterRole string, verb s time.Sleep(time.Second) By("Impersonating the Service Account") - var impersonateConfig *rest.Config - impersonateConfig = f.KubeClientConfig + impersonateConfig := f.KubeClientConfig impersonateConfig.Impersonate.UserName = "system:serviceaccount:" + f.Namespace.Name + ":" + viewServiceAccountName impersonateClient, err := kubernetes.NewForConfig(impersonateConfig) Expect(err).NotTo(HaveOccurred()) @@ -111,7 +108,7 @@ func RbacClusterRoleHasAccessToResource(f *Framework, clusterRole string, verb s }, }, } - response, err := sarClient.Create(context.TODO(), sar, metav1.CreateOptions{}) + response, err := sarClient.Create(ctx, sar, metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred()) return response.Status.Allowed } diff --git a/test/e2e/framework/util/errors/errors.go b/test/e2e/framework/util/errors/errors.go index babc752be0e..62bd7789041 100644 --- a/test/e2e/framework/util/errors/errors.go +++ b/test/e2e/framework/util/errors/errors.go @@ -18,17 +18,17 @@ limitations under the License. // upon to communicate information about why something has failed package errors -type errSkipTest struct { +type skipTestError struct { error } func IsSkip(err error) bool { - if _, ok := err.(errSkipTest); ok { + if _, ok := err.(skipTestError); ok { return true } return false } func NewSkip(err error) error { - return errSkipTest{error: err} + return skipTestError{error: err} } diff --git a/test/e2e/go.mod b/test/e2e/go.mod new file mode 100644 index 00000000000..249dbd10c11 --- /dev/null +++ b/test/e2e/go.mod @@ -0,0 +1,117 @@ +module github.com/cert-manager/cert-manager/e2e-tests + +go 1.25.0 + +// Do not remove this comment: +// please place any replace statements here at the top for visibility and add a +// comment to it as to when it can be removed + +replace github.com/cert-manager/cert-manager => ../../ + +require ( + github.com/cert-manager/cert-manager v0.0.0-00010101000000-000000000000 + github.com/cloudflare/cloudflare-go/v6 v6.2.0 + github.com/hashicorp/vault/api v1.22.0 + github.com/onsi/ginkgo/v2 v2.27.2 + github.com/onsi/gomega v1.38.2 + github.com/spf13/pflag v1.0.10 + k8s.io/api v0.34.1 + k8s.io/apiextensions-apiserver v0.34.1 + k8s.io/apimachinery v0.34.1 + k8s.io/client-go v0.34.1 + k8s.io/component-base v0.34.1 + k8s.io/kube-aggregator v0.34.1 + k8s.io/utils v0.0.0-20250820121507-0af2bda4dd1d + sigs.k8s.io/controller-runtime v0.22.3 + sigs.k8s.io/gateway-api v1.4.0 + sigs.k8s.io/structured-merge-diff/v6 v6.3.0 +) + +require ( + github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 // indirect + github.com/Masterminds/semver/v3 v3.4.0 // indirect + github.com/beorn7/perks v1.0.1 // indirect + github.com/blang/semver/v4 v4.0.0 // indirect + github.com/cenkalti/backoff/v4 v4.3.0 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/emicklei/go-restful/v3 v3.13.0 // indirect + github.com/evanphx/json-patch/v5 v5.9.11 // indirect + github.com/fsnotify/fsnotify v1.9.0 // indirect + github.com/fxamacker/cbor/v2 v2.9.0 // indirect + github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667 // indirect + github.com/go-jose/go-jose/v4 v4.1.2 // indirect + github.com/go-ldap/ldap/v3 v3.4.12 // indirect + github.com/go-logr/logr v1.4.3 // indirect + github.com/go-logr/zapr v1.3.0 // indirect + github.com/go-openapi/jsonpointer v0.22.1 // indirect + github.com/go-openapi/jsonreference v0.21.2 // indirect + github.com/go-openapi/swag v0.23.1 // indirect + github.com/go-openapi/swag/jsonname v0.25.1 // indirect + github.com/go-task/slim-sprig/v3 v3.0.0 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/google/btree v1.1.3 // indirect + github.com/google/gnostic-models v0.7.0 // indirect + github.com/google/go-cmp v0.7.0 // indirect + github.com/google/pprof v0.0.0-20250820193118-f64d9cf942d6 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 // indirect + github.com/hashicorp/errwrap v1.1.0 // indirect + github.com/hashicorp/go-cleanhttp v0.5.2 // indirect + github.com/hashicorp/go-multierror v1.1.1 // indirect + github.com/hashicorp/go-retryablehttp v0.7.8 // indirect + github.com/hashicorp/go-rootcerts v1.0.2 // indirect + github.com/hashicorp/go-secure-stdlib/parseutil v0.2.0 // indirect + github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 // indirect + github.com/hashicorp/go-sockaddr v1.0.7 // indirect + github.com/hashicorp/hcl v1.0.1-vault-7 // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/josharian/intern v1.0.0 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/mailru/easyjson v0.9.0 // indirect + github.com/mitchellh/go-homedir v1.1.0 // indirect + github.com/mitchellh/mapstructure v1.5.0 // indirect + github.com/moby/spdystream v0.5.0 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + github.com/prometheus/client_golang v1.23.2 // indirect + github.com/prometheus/client_model v0.6.2 // indirect + github.com/prometheus/common v0.66.1 // indirect + github.com/prometheus/procfs v0.17.0 // indirect + github.com/ryanuber/go-glob v1.0.0 // indirect + github.com/spf13/cobra v1.10.1 // indirect + github.com/tidwall/gjson v1.18.0 // indirect + github.com/tidwall/match v1.1.1 // indirect + github.com/tidwall/pretty v1.2.1 // indirect + github.com/tidwall/sjson v1.2.5 // indirect + github.com/x448/float16 v0.8.4 // indirect + go.opentelemetry.io/otel v1.37.0 // indirect + go.opentelemetry.io/otel/trace v1.37.0 // indirect + go.uber.org/multierr v1.11.0 // indirect + go.uber.org/zap v1.27.0 // indirect + go.yaml.in/yaml/v2 v2.4.2 // indirect + go.yaml.in/yaml/v3 v3.0.4 // indirect + golang.org/x/crypto v0.43.0 // indirect + golang.org/x/mod v0.28.0 // indirect + golang.org/x/net v0.46.0 // indirect + golang.org/x/oauth2 v0.32.0 // indirect + golang.org/x/sync v0.17.0 // indirect + golang.org/x/sys v0.37.0 // indirect + golang.org/x/term v0.36.0 // indirect + golang.org/x/text v0.30.0 // indirect + golang.org/x/time v0.14.0 // indirect + golang.org/x/tools v0.37.0 // indirect + gomodules.xyz/jsonpatch/v2 v2.5.0 // indirect + google.golang.org/protobuf v1.36.10 // indirect + gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect + gopkg.in/inf.v0 v0.9.1 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect + k8s.io/klog/v2 v2.130.1 // indirect + k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 // indirect + sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect + sigs.k8s.io/randfill v1.0.0 // indirect + sigs.k8s.io/yaml v1.6.0 // indirect +) diff --git a/test/e2e/go.sum b/test/e2e/go.sum new file mode 100644 index 00000000000..a3c583ca3ce --- /dev/null +++ b/test/e2e/go.sum @@ -0,0 +1,312 @@ +github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 h1:mFRzDkZVAjdal+s7s0MwaRv9igoPqLRdzOLzw/8Xvq8= +github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU= +github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0= +github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= +github.com/alexbrainman/sspi v0.0.0-20250919150558-7d374ff0d59e h1:4dAU9FXIyQktpoUAgOJK3OTFc/xug0PCXYCqU0FgDKI= +github.com/alexbrainman/sspi v0.0.0-20250919150558-7d374ff0d59e/go.mod h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4= +github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= +github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= +github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= +github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= +github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cloudflare/cloudflare-go/v6 v6.2.0 h1:VuJAXeVlnftU/XIcAi/xXwEkU/TOaHhmM68HKVpyLD8= +github.com/cloudflare/cloudflare-go/v6 v6.2.0/go.mod h1:Lj3MUqjvKctXRpdRhLQxZYRrNZHuRs0XYuH8JtQGyoI= +github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/emicklei/go-restful/v3 v3.13.0 h1:C4Bl2xDndpU6nJ4bc1jXd+uTmYPVUwkD6bFY/oTyCes= +github.com/emicklei/go-restful/v3 v3.13.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/evanphx/json-patch v5.9.0+incompatible h1:fBXyNpNMuTTDdquAq/uisOr2lShz4oaXpDTX2bLe7ls= +github.com/evanphx/json-patch v5.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/evanphx/json-patch/v5 v5.9.11 h1:/8HVnzMq13/3x9TPvjG08wUGqBTmZBsCWzjTM0wiaDU= +github.com/evanphx/json-patch/v5 v5.9.11/go.mod h1:3j+LviiESTElxA4p3EMKAB9HXj3/XEtnUf6OZxqIQTM= +github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= +github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= +github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= +github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= +github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM= +github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= +github.com/gkampitakis/ciinfo v0.3.2 h1:JcuOPk8ZU7nZQjdUhctuhQofk7BGHuIy0c9Ez8BNhXs= +github.com/gkampitakis/ciinfo v0.3.2/go.mod h1:1NIwaOcFChN4fa/B0hEBdAb6npDlFL8Bwx4dfRLRqAo= +github.com/gkampitakis/go-diff v1.3.2 h1:Qyn0J9XJSDTgnsgHRdz9Zp24RaJeKMUHg2+PDZZdC4M= +github.com/gkampitakis/go-diff v1.3.2/go.mod h1:LLgOrpqleQe26cte8s36HTWcTmMEur6OPYerdAAS9tk= +github.com/gkampitakis/go-snaps v0.5.15 h1:amyJrvM1D33cPHwVrjo9jQxX8g/7E2wYdZ+01KS3zGE= +github.com/gkampitakis/go-snaps v0.5.15/go.mod h1:HNpx/9GoKisdhw9AFOBT1N7DBs9DiHo/hGheFGBZ+mc= +github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667 h1:BP4M0CvQ4S3TGls2FvczZtj5Re/2ZzkV9VwqPHH/3Bo= +github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= +github.com/go-jose/go-jose/v4 v4.1.2 h1:TK/7NqRQZfgAh+Td8AlsrvtPoUyiHh0LqVvokh+1vHI= +github.com/go-jose/go-jose/v4 v4.1.2/go.mod h1:22cg9HWM1pOlnRiY+9cQYJ9XHmya1bYW8OeDM6Ku6Oo= +github.com/go-ldap/ldap/v3 v3.4.12 h1:1b81mv7MagXZ7+1r7cLTWmyuTqVqdwbtJSjC0DAp9s4= +github.com/go-ldap/ldap/v3 v3.4.12/go.mod h1:+SPAGcTtOfmGsCb3h1RFiq4xpp4N636G75OEace8lNo= +github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= +github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ= +github.com/go-logr/zapr v1.3.0/go.mod h1:YKepepNBd1u/oyhd/yQmtjVXmm9uML4IXUgMOwR8/Gg= +github.com/go-openapi/jsonpointer v0.22.1 h1:sHYI1He3b9NqJ4wXLoJDKmUmHkWy/L7rtEo92JUxBNk= +github.com/go-openapi/jsonpointer v0.22.1/go.mod h1:pQT9OsLkfz1yWoMgYFy4x3U5GY5nUlsOn1qSBH5MkCM= +github.com/go-openapi/jsonreference v0.21.2 h1:Wxjda4M/BBQllegefXrY/9aq1fxBA8sI5M/lFU6tSWU= +github.com/go-openapi/jsonreference v0.21.2/go.mod h1:pp3PEjIsJ9CZDGCNOyXIQxsNuroxm8FAJ/+quA0yKzQ= +github.com/go-openapi/swag v0.23.1 h1:lpsStH0n2ittzTnbaSloVZLuB5+fvSY/+hnagBjSNZU= +github.com/go-openapi/swag v0.23.1/go.mod h1:STZs8TbRvEQQKUA+JZNAm3EWlgaOBGpyFDqQnDHMef0= +github.com/go-openapi/swag/jsonname v0.25.1 h1:Sgx+qbwa4ej6AomWC6pEfXrA6uP2RkaNjA9BR8a1RJU= +github.com/go-openapi/swag/jsonname v0.25.1/go.mod h1:71Tekow6UOLBD3wS7XhdT98g5J5GR13NOTQ9/6Q11Zo= +github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= +github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= +github.com/go-test/deep v1.1.1 h1:0r/53hagsehfO4bzD2Pgr/+RgHqhmf+k1Bpse2cTu1U= +github.com/go-test/deep v1.1.1/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= +github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw= +github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg= +github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= +github.com/google/gnostic-models v0.7.0 h1:qwTtogB15McXDaNqTZdzPJRHvaVJlAl+HVQnLmJEJxo= +github.com/google/gnostic-models v0.7.0/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= +github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/pprof v0.0.0-20250820193118-f64d9cf942d6 h1:EEHtgt9IwisQ2AZ4pIsMjahcegHh6rmhqxzIRQIyepY= +github.com/google/pprof v0.0.0-20250820193118-f64d9cf942d6/go.mod h1:I6V7YzU0XDpsHqbsyrghnFZLO1gwK6NPTNvmetQIk9U= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 h1:JeSE6pjso5THxAzdVpqr6/geYxZytqFMBCOtn/ujyeo= +github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674/go.mod h1:r4w70xmWCQKmi1ONH4KIaBptdivuRPyosB9RmPlGEwA= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= +github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= +github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= +github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k= +github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= +github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= +github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= +github.com/hashicorp/go-retryablehttp v0.7.8 h1:ylXZWnqa7Lhqpk0L1P1LzDtGcCR0rPVUrx/c8Unxc48= +github.com/hashicorp/go-retryablehttp v0.7.8/go.mod h1:rjiScheydd+CxvumBsIrFKlx3iS0jrZ7LvzFGFmuKbw= +github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc= +github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= +github.com/hashicorp/go-secure-stdlib/parseutil v0.2.0 h1:U+kC2dOhMFQctRfhK0gRctKAPTloZdMU5ZJxaesJ/VM= +github.com/hashicorp/go-secure-stdlib/parseutil v0.2.0/go.mod h1:Ll013mhdmsVDuoIXVfBtvgGJsXDYkTw1kooNcoCXuE0= +github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 h1:kes8mmyCpxJsI7FTwtzRqEy9CdjCtrXrXGuOpxEA7Ts= +github.com/hashicorp/go-secure-stdlib/strutil v0.1.2/go.mod h1:Gou2R9+il93BqX25LAKCLuM+y9U2T4hlwvT1yprcna4= +github.com/hashicorp/go-sockaddr v1.0.7 h1:G+pTkSO01HpR5qCxg7lxfsFEZaG+C0VssTy/9dbT+Fw= +github.com/hashicorp/go-sockaddr v1.0.7/go.mod h1:FZQbEYa1pxkQ7WLpyXJ6cbjpT8q0YgQaK/JakXqGyWw= +github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= +github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/hcl v1.0.1-vault-7 h1:ag5OxFVy3QYTFTJODRzTKVZ6xvdfLLCA1cy/Y6xGI0I= +github.com/hashicorp/hcl v1.0.1-vault-7/go.mod h1:XYhtn6ijBSAj6n4YqAaf7RBPS4I06AItNorpy+MoQNM= +github.com/hashicorp/vault/api v1.22.0 h1:+HYFquE35/B74fHoIeXlZIP2YADVboaPjaSicHEZiH0= +github.com/hashicorp/vault/api v1.22.0/go.mod h1:IUZA2cDvr4Ok3+NtK2Oq/r+lJeXkeCrHRmqdyWfpmGM= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/jcmturner/aescts/v2 v2.0.0 h1:9YKLH6ey7H4eDBXW8khjYslgyqG2xZikXP0EQFKrle8= +github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs= +github.com/jcmturner/dnsutils/v2 v2.0.0 h1:lltnkeZGL0wILNvrNiVCR6Ro5PGU/SeBvVO/8c/iPbo= +github.com/jcmturner/dnsutils/v2 v2.0.0/go.mod h1:b0TnjGOvI/n42bZa+hmXL+kFJZsFT7G4t3HTlQ184QM= +github.com/jcmturner/gofork v1.7.6 h1:QH0l3hzAU1tfT3rZCnW5zXl+orbkNMMRGJfdJjHVETg= +github.com/jcmturner/gofork v1.7.6/go.mod h1:1622LH6i/EZqLloHfE7IeZ0uEJwMSUyQ/nDd82IeqRo= +github.com/jcmturner/goidentity/v6 v6.0.1 h1:VKnZd2oEIMorCTsFBnJWbExfNN7yZr3EhJAxwOkZg6o= +github.com/jcmturner/goidentity/v6 v6.0.1/go.mod h1:X1YW3bgtvwAXju7V3LCIMpY0Gbxyjn/mY9zx4tFonSg= +github.com/jcmturner/gokrb5/v8 v8.4.4 h1:x1Sv4HaTpepFkXbt2IkL29DXRf8sOfZXo8eRKh687T8= +github.com/jcmturner/gokrb5/v8 v8.4.4/go.mod h1:1btQEpgT6k+unzCwX1KdWMEwPPkkgBtP+F6aCACiMrs= +github.com/jcmturner/rpc/v2 v2.0.3 h1:7FXXj8Ti1IaVFpSAziCZWNzbNuZmnvw/i6CqLNdWfZY= +github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/joshdk/go-junit v1.0.0 h1:S86cUKIdwBHWwA6xCmFlf3RTLfVXYQfvanM5Uh+K6GE= +github.com/joshdk/go-junit v1.0.0/go.mod h1:TiiV0PqkaNfFXjEiyjWM3XXrhVyCa1K4Zfga6W52ung= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= +github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4= +github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= +github.com/maruel/natural v1.1.1 h1:Hja7XhhmvEFhcByqDoHz9QZbkWey+COd9xWfCfn1ioo= +github.com/maruel/natural v1.1.1/go.mod h1:v+Rfd79xlw1AgVBjbO0BEQmptqb5HvL/k9GRHB7ZKEg= +github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= +github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mfridman/tparse v0.18.0 h1:wh6dzOKaIwkUGyKgOntDW4liXSo37qg5AXbIhkMV3vE= +github.com/mfridman/tparse v0.18.0/go.mod h1:gEvqZTuCgEhPbYk/2lS3Kcxg1GmTxxU7kTC8DvP0i/A= +github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/moby/spdystream v0.5.0 h1:7r0J1Si3QO/kjRitvSLVVFUjxMEb/YLj6S9FF62JBCU= +github.com/moby/spdystream v0.5.0/go.mod h1:xBAYlnt/ay+11ShkdFKNAG7LsyK/tmNBVvVOwrfMgdI= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8= +github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f h1:y5//uYreIhSUg3J1GEMiLbxo1LJaP8RfCpH6pymGZus= +github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= +github.com/onsi/ginkgo/v2 v2.27.2 h1:LzwLj0b89qtIy6SSASkzlNvX6WktqurSHwkk2ipF/Ns= +github.com/onsi/ginkgo/v2 v2.27.2/go.mod h1:ArE1D/XhNXBXCBkKOLkbsb2c81dQHCRcF5zwn/ykDRo= +github.com/onsi/gomega v1.38.2 h1:eZCjf2xjZAqe+LeWvKb5weQ+NcPwX84kqJ0cZNxok2A= +github.com/onsi/gomega v1.38.2/go.mod h1:W2MJcYxRGV63b418Ai34Ud0hEdTVXq9NW9+Sx6uXf3k= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o= +github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg= +github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= +github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= +github.com/prometheus/common v0.66.1 h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9ZoGs= +github.com/prometheus/common v0.66.1/go.mod h1:gcaUsgf3KfRSwHY4dIMXLPV0K/Wg1oZ8+SbZk/HH/dA= +github.com/prometheus/procfs v0.17.0 h1:FuLQ+05u4ZI+SS/w9+BWEM2TXiHKsUQ9TADiRH7DuK0= +github.com/prometheus/procfs v0.17.0/go.mod h1:oPQLaDAMRbA+u8H5Pbfq+dl3VDAvHxMUOVhe0wYB2zw= +github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= +github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk= +github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc= +github.com/spf13/cobra v1.10.1 h1:lJeBwCfmrnXthfAupyUTzJ/J4Nc1RsHC/mSRU2dll/s= +github.com/spf13/cobra v1.10.1/go.mod h1:7SmJGaTHFVBY0jW4NXGluQoLvhqFQM+6XSKD+P4XaB0= +github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= +github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY= +github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= +github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= +github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= +github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= +github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= +github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ= +go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I= +go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4= +go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= +go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= +go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI= +go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU= +go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= +go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04= +golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.28.0 h1:gQBtGhjxykdjY9YhZpSlZIsbnaE2+PgjfLWUQTnoZ1U= +golang.org/x/mod v0.28.0/go.mod h1:yfB/L0NOf/kmEbXjzCPOx1iK1fRutOydrCMsqRhEBxI= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.46.0 h1:giFlY12I07fugqwPuWJi68oOnpfqFnJIJzaIIm2JVV4= +golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210= +golang.org/x/oauth2 v0.32.0 h1:jsCblLleRMDrxMN29H3z/k1KliIvpLgCkE6R8FXXNgY= +golang.org/x/oauth2 v0.32.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug= +golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ= +golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/term v0.36.0 h1:zMPR+aF8gfksFprF/Nc/rd1wRS1EI6nDBGyWAvDzx2Q= +golang.org/x/term v0.36.0/go.mod h1:Qu394IJq6V6dCBRgwqshf3mPF85AqzYEzofzRdZkWss= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k= +golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM= +golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI= +golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.37.0 h1:DVSRzp7FwePZW356yEAChSdNcQo6Nsp+fex1SUW09lE= +golang.org/x/tools v0.37.0/go.mod h1:MBN5QPQtLMHVdvsbtarmTNukZDdgwdwlO5qGacAzF0w= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gomodules.xyz/jsonpatch/v2 v2.5.0 h1:JELs8RLM12qJGXU4u/TO3V25KW8GreMKl9pdkk14RM0= +gomodules.xyz/jsonpatch/v2 v2.5.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY= +google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= +google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/evanphx/json-patch.v4 v4.13.0 h1:czT3CmqEaQ1aanPc5SdlgQrrEIb8w/wwCvWWnfEbYzo= +gopkg.in/evanphx/json-patch.v4 v4.13.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= +gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= +gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +k8s.io/api v0.34.1 h1:jC+153630BMdlFukegoEL8E/yT7aLyQkIVuwhmwDgJM= +k8s.io/api v0.34.1/go.mod h1:SB80FxFtXn5/gwzCoN6QCtPD7Vbu5w2n1S0J5gFfTYk= +k8s.io/apiextensions-apiserver v0.34.1 h1:NNPBva8FNAPt1iSVwIE0FsdrVriRXMsaWFMqJbII2CI= +k8s.io/apiextensions-apiserver v0.34.1/go.mod h1:hP9Rld3zF5Ay2Of3BeEpLAToP+l4s5UlxiHfqRaRcMc= +k8s.io/apimachinery v0.34.1 h1:dTlxFls/eikpJxmAC7MVE8oOeP1zryV7iRyIjB0gky4= +k8s.io/apimachinery v0.34.1/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw= +k8s.io/client-go v0.34.1 h1:ZUPJKgXsnKwVwmKKdPfw4tB58+7/Ik3CrjOEhsiZ7mY= +k8s.io/client-go v0.34.1/go.mod h1:kA8v0FP+tk6sZA0yKLRG67LWjqufAoSHA2xVGKw9Of8= +k8s.io/component-base v0.34.1 h1:v7xFgG+ONhytZNFpIz5/kecwD+sUhVE6HU7qQUiRM4A= +k8s.io/component-base v0.34.1/go.mod h1:mknCpLlTSKHzAQJJnnHVKqjxR7gBeHRv0rPXA7gdtQ0= +k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= +k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= +k8s.io/kube-aggregator v0.34.1 h1:WNLV0dVNoFKmuyvdWLd92iDSyD/TSTjqwaPj0U9XAEU= +k8s.io/kube-aggregator v0.34.1/go.mod h1:RU8j+5ERfp0h+gIvWtxRPfsa5nK7rboDm8RST8BJfYQ= +k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 h1:Y3gxNAuB0OBLImH611+UDZcmKS3g6CthxToOb37KgwE= +k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912/go.mod h1:kdmbQkyfwUagLfXIad1y2TdrjPFWp2Q89B3qkRwf/pQ= +k8s.io/utils v0.0.0-20250820121507-0af2bda4dd1d h1:wAhiDyZ4Tdtt7e46e9M5ZSAJ/MnPGPs+Ki1gHw4w1R0= +k8s.io/utils v0.0.0-20250820121507-0af2bda4dd1d/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +sigs.k8s.io/controller-runtime v0.22.3 h1:I7mfqz/a/WdmDCEnXmSPm8/b/yRTy6JsKKENTijTq8Y= +sigs.k8s.io/controller-runtime v0.22.3/go.mod h1:+QX1XUpTXN4mLoblf4tqr5CQcyHPAki2HLXqQMY6vh8= +sigs.k8s.io/gateway-api v1.4.0 h1:ZwlNM6zOHq0h3WUX2gfByPs2yAEsy/EenYJB78jpQfQ= +sigs.k8s.io/gateway-api v1.4.0/go.mod h1:AR5RSqciWP98OPckEjOjh2XJhAe2Na4LHyXD2FUY7Qk= +sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg= +sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= +sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= +sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= +sigs.k8s.io/structured-merge-diff/v6 v6.3.0 h1:jTijUJbW353oVOd9oTlifJqOGEkUw2jB/fXCbTiQEco= +sigs.k8s.io/structured-merge-diff/v6 v6.3.0/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE= +sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs= +sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4= diff --git a/test/e2e/suite/certificaterequests/approval/approval.go b/test/e2e/suite/certificaterequests/approval/approval.go index 9e307dcb5d8..1682a6cd683 100644 --- a/test/e2e/suite/certificaterequests/approval/approval.go +++ b/test/e2e/suite/certificaterequests/approval/approval.go @@ -23,24 +23,26 @@ import ( "strings" "time" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" corev1 "k8s.io/api/core/v1" rbacv1 "k8s.io/api/rbac/v1" crdapi "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" crdclientset "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/rand" "k8s.io/apimachinery/pkg/util/wait" "k8s.io/client-go/util/retry" + "github.com/cert-manager/cert-manager/e2e-tests/framework" + testutil "github.com/cert-manager/cert-manager/e2e-tests/framework/util" + e2eutil "github.com/cert-manager/cert-manager/e2e-tests/util" apiutil "github.com/cert-manager/cert-manager/pkg/api/util" cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" clientset "github.com/cert-manager/cert-manager/pkg/client/clientset/versioned" - "github.com/cert-manager/cert-manager/test/e2e/framework" - testutil "github.com/cert-manager/cert-manager/test/e2e/framework/util" - e2eutil "github.com/cert-manager/cert-manager/test/e2e/util" "github.com/cert-manager/cert-manager/test/unit/gen" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" ) // This test ensures that the approval condition may only be set by users who @@ -53,14 +55,15 @@ var _ = framework.CertManagerDescribe("Approval CertificateRequests", func() { saclient clientset.Interface request *cmapi.CertificateRequest - crd *crdapi.CustomResourceDefinition - crdclient crdclientset.Interface - group string + crd *crdapi.CustomResourceDefinition + crdclient crdclientset.Interface + issuerKind string + group string ) // isNotFoundError returns true if an error from the cert-manager admission - // webhook contains a is not found error. - isNotFoundError := func(issuerRef cmmeta.ObjectReference, err error) bool { + // webhook contains an is not found error. + isNotFoundError := func(issuerRef cmmeta.IssuerReference, err error) bool { if err == nil { return false } @@ -82,7 +85,7 @@ var _ = framework.CertManagerDescribe("Approval CertificateRequests", func() { // isNotFoundError returns true if an error from the cert-manager admission // webhook contains a permissions error when the client attempts to approve // or deny a CertificateRequest. - isPermissionError := func(sa *corev1.ServiceAccount, issuerRef cmmeta.ObjectReference, err error) bool { + isPermissionError := func(sa *corev1.ServiceAccount, issuerRef cmmeta.IssuerReference, err error) bool { if err == nil { return false } @@ -96,19 +99,20 @@ var _ = framework.CertManagerDescribe("Approval CertificateRequests", func() { // retryNotFound returns true when either the resource is not found for the // issuer, or the webhook returns a context deadline error. Useful for // retrying a request when the expected response is a different or no error. - retryOnNotFound := func(issuerRef cmmeta.ObjectReference) func(error) bool { + retryOnNotFound := func(issuerRef cmmeta.IssuerReference) func(error) bool { return func(err error) bool { return isNotFoundError(issuerRef, err) || isTimeoutError(err) } } - JustBeforeEach(func() { + JustBeforeEach(func(testingCtx context.Context) { var err error crdclient, err = crdclientset.NewForConfig(f.KubeClientConfig) Expect(err).NotTo(HaveOccurred()) + issuerKind = fmt.Sprintf("Issuer%s", rand.String(5)) group = e2eutil.RandomSubdomain("example.io") - sa, err = f.KubeClientSet.CoreV1().ServiceAccounts(f.Namespace.Name).Create(context.TODO(), &corev1.ServiceAccount{ + sa, err = f.KubeClientSet.CoreV1().ServiceAccounts(f.Namespace.Name).Create(testingCtx, &corev1.ServiceAccount{ ObjectMeta: metav1.ObjectMeta{ GenerateName: "test-sa-", Namespace: f.Namespace.Name, @@ -116,7 +120,7 @@ var _ = framework.CertManagerDescribe("Approval CertificateRequests", func() { }, metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred()) - role, err := f.KubeClientSet.RbacV1().Roles(f.Namespace.Name).Create(context.TODO(), &rbacv1.Role{ + role, err := f.KubeClientSet.RbacV1().Roles(f.Namespace.Name).Create(testingCtx, &rbacv1.Role{ ObjectMeta: metav1.ObjectMeta{ GenerateName: "certificaterequest-creator-", Namespace: f.Namespace.Name, @@ -137,7 +141,7 @@ var _ = framework.CertManagerDescribe("Approval CertificateRequests", func() { Expect(err).NotTo(HaveOccurred()) By("Creating certificaterequest-creator rolebinding for ServiceAccount") - _, err = f.KubeClientSet.RbacV1().RoleBindings(f.Namespace.Name).Create(context.TODO(), &rbacv1.RoleBinding{ + _, err = f.KubeClientSet.RbacV1().RoleBindings(f.Namespace.Name).Create(testingCtx, &rbacv1.RoleBinding{ ObjectMeta: metav1.ObjectMeta{ GenerateName: "certificaterequest-creator-", Namespace: f.Namespace.Name, @@ -159,7 +163,7 @@ var _ = framework.CertManagerDescribe("Approval CertificateRequests", func() { // Manually create a Secret to be populated with Service Account token // https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/#manually-create-a-service-account-api-token - secret, err := f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Create(context.TODO(), &corev1.Secret{ + secret, err := f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Create(testingCtx, &corev1.Secret{ ObjectMeta: metav1.ObjectMeta{ GenerateName: "sa-secret-", Name: f.Namespace.Name, @@ -174,23 +178,21 @@ var _ = framework.CertManagerDescribe("Approval CertificateRequests", func() { token []byte ok bool ) - err = wait.PollImmediate(time.Second, time.Second*10, - func() (bool, error) { - secret, err = f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Get(context.TODO(), secret.Name, metav1.GetOptions{}) - if err != nil { - return false, err - } - - if len(secret.Data) == 0 { - return false, nil - } - if token, ok = secret.Data["token"]; !ok { - return false, nil - } - - return true, nil - }, - ) + err = wait.PollUntilContextTimeout(testingCtx, time.Second, time.Second*10, true, func(ctx context.Context) (bool, error) { + secret, err = f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Get(ctx, secret.Name, metav1.GetOptions{}) + if err != nil { + return false, err + } + + if len(secret.Data) == 0 { + return false, nil + } + if token, ok = secret.Data["token"]; !ok { + return false, nil + } + + return true, nil + }) Expect(err).NotTo(HaveOccurred(), fmt.Sprintf("Error: %s", err)) By("Building ServiceAccount kubernetes clientset") @@ -198,7 +200,9 @@ var _ = framework.CertManagerDescribe("Approval CertificateRequests", func() { kubeConfig, err := testutil.LoadConfig(f.Config.KubeConfig, f.Config.KubeContext) Expect(err).NotTo(HaveOccurred()) - kubeConfig.BearerToken = fmt.Sprintf("%s", token) + kubeConfig.QPS = 9000 + kubeConfig.Burst = 9000 + kubeConfig.BearerToken = string(token) kubeConfig.CertData = nil kubeConfig.KeyData = nil kubeConfig.Timeout = time.Second * 20 @@ -212,160 +216,173 @@ var _ = framework.CertManagerDescribe("Approval CertificateRequests", func() { request = gen.CertificateRequest("", gen.SetCertificateRequestNamespace(f.Namespace.Name), gen.SetCertificateRequestCSR(csr), - gen.SetCertificateRequestIssuer(cmmeta.ObjectReference{ + gen.SetCertificateRequestIssuer(cmmeta.IssuerReference{ Name: "test-issuer", - Kind: "Issuer", + Kind: issuerKind, Group: group, }), ) request.GenerateName = "test-request-" - request, err = saclient.CertmanagerV1().CertificateRequests(f.Namespace.Name).Create(context.TODO(), request, metav1.CreateOptions{}) + request, err = saclient.CertmanagerV1().CertificateRequests(f.Namespace.Name).Create(testingCtx, request, metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred()) }) - JustAfterEach(func() { - err := f.KubeClientSet.CoreV1().ServiceAccounts(f.Namespace.Name).Delete(context.TODO(), sa.Name, metav1.DeleteOptions{}) + JustAfterEach(func(testingCtx context.Context) { + err := f.KubeClientSet.CoreV1().ServiceAccounts(f.Namespace.Name).Delete(testingCtx, sa.Name, metav1.DeleteOptions{}) Expect(err).NotTo(HaveOccurred()) - err = f.CertManagerClientSet.CertmanagerV1().CertificateRequests(f.Namespace.Name).Delete(context.TODO(), request.Name, metav1.DeleteOptions{}) + err = f.CertManagerClientSet.CertmanagerV1().CertificateRequests(f.Namespace.Name).Delete(testingCtx, request.Name, metav1.DeleteOptions{}) Expect(err).NotTo(HaveOccurred()) if crd != nil { By("Removing CustomResource Definition") - err = crdclient.ApiextensionsV1().CustomResourceDefinitions().Delete(context.TODO(), crd.Name, metav1.DeleteOptions{}) + err = crdclient.ApiextensionsV1().CustomResourceDefinitions().Delete(testingCtx, crd.Name, metav1.DeleteOptions{}) Expect(err).NotTo(HaveOccurred()) } crd = nil }) - It("attempting to approve a certificate request without the approve permission should error", func() { - createCRD(crdclient, group, "issuers", "Issuer", crdapi.NamespaceScoped) + It("attempting to approve a certificate request without the approve permission should error", func(testingCtx context.Context) { + createCRD(testingCtx, crdclient, group, "issuers", issuerKind, crdapi.NamespaceScoped) approvedCR := request.DeepCopy() apiutil.SetCertificateRequestCondition(approvedCR, cmapi.CertificateRequestConditionApproved, cmmeta.ConditionTrue, "cert-manager.io", "e2e") err := retry.OnError(retry.DefaultBackoff, retryOnNotFound(approvedCR.Spec.IssuerRef), func() error { - _, err := saclient.CertmanagerV1().CertificateRequests(f.Namespace.Name).UpdateStatus(context.TODO(), approvedCR, metav1.UpdateOptions{}) + _, err := saclient.CertmanagerV1().CertificateRequests(f.Namespace.Name).UpdateStatus(testingCtx, approvedCR, metav1.UpdateOptions{}) return err }) Expect(isPermissionError(sa, approvedCR.Spec.IssuerRef, err)).To(BeTrue(), err.Error()) }) - It("attempting to deny a certificate request without the approve permission should error", func() { - createCRD(crdclient, group, "issuers", "Issuer", crdapi.NamespaceScoped) + It("attempting to deny a certificate request without the approve permission should error", func(testingCtx context.Context) { + createCRD(testingCtx, crdclient, group, "issuers", issuerKind, crdapi.NamespaceScoped) deniedCR := request.DeepCopy() apiutil.SetCertificateRequestCondition(deniedCR, cmapi.CertificateRequestConditionDenied, cmmeta.ConditionTrue, "cert-manager.io", "e2e") err := retry.OnError(retry.DefaultBackoff, retryOnNotFound(deniedCR.Spec.IssuerRef), func() error { - _, err := saclient.CertmanagerV1().CertificateRequests(f.Namespace.Name).UpdateStatus(context.TODO(), deniedCR, metav1.UpdateOptions{}) + _, err := saclient.CertmanagerV1().CertificateRequests(f.Namespace.Name).UpdateStatus(testingCtx, deniedCR, metav1.UpdateOptions{}) return err }) Expect(isPermissionError(sa, deniedCR.Spec.IssuerRef, err)).To(BeTrue(), err.Error()) }) - It("a service account with the approve permissions for a resource that doesn't exist attempting to approve should error", func() { - bindServiceAccountToApprove(f, sa, fmt.Sprintf("issuers.%s/*", group)) + It("a service account with the approve permissions for a resource that doesn't exist attempting to approve should error", func(testingCtx context.Context) { + bindServiceAccountToApprove(testingCtx, f, sa, fmt.Sprintf("issuers.%s/*", group)) - approvedCR, err := f.CertManagerClientSet.CertmanagerV1().CertificateRequests(f.Namespace.Name).Get(context.TODO(), request.Name, metav1.GetOptions{}) + approvedCR, err := f.CertManagerClientSet.CertmanagerV1().CertificateRequests(f.Namespace.Name).Get(testingCtx, request.Name, metav1.GetOptions{}) Expect(err).NotTo(HaveOccurred()) apiutil.SetCertificateRequestCondition(approvedCR, cmapi.CertificateRequestConditionApproved, cmmeta.ConditionTrue, "cert-manager.io", "e2e") Consistently(func() bool { err := retry.OnError(retry.DefaultBackoff, isTimeoutError, func() error { - _, err = saclient.CertmanagerV1().CertificateRequests(f.Namespace.Name).UpdateStatus(context.TODO(), approvedCR, metav1.UpdateOptions{}) + _, err = saclient.CertmanagerV1().CertificateRequests(f.Namespace.Name).UpdateStatus(testingCtx, approvedCR, metav1.UpdateOptions{}) return err }) return isNotFoundError(approvedCR.Spec.IssuerRef, err) }).Should(BeTrue()) }) - It("a service account with the approve permissions for a resource that doesn't exist attempting to deny should error", func() { - bindServiceAccountToApprove(f, sa, fmt.Sprintf("issuers.%s/*", group)) + It("a service account with the approve permissions for a resource that doesn't exist attempting to deny should error", func(testingCtx context.Context) { + bindServiceAccountToApprove(testingCtx, f, sa, fmt.Sprintf("issuers.%s/*", group)) - deniedCR, err := f.CertManagerClientSet.CertmanagerV1().CertificateRequests(f.Namespace.Name).Get(context.TODO(), request.Name, metav1.GetOptions{}) + deniedCR, err := f.CertManagerClientSet.CertmanagerV1().CertificateRequests(f.Namespace.Name).Get(testingCtx, request.Name, metav1.GetOptions{}) Expect(err).NotTo(HaveOccurred()) apiutil.SetCertificateRequestCondition(deniedCR, cmapi.CertificateRequestConditionDenied, cmmeta.ConditionTrue, "cert-manager.io", "e2e") Consistently(func() bool { err := retry.OnError(retry.DefaultBackoff, isTimeoutError, func() error { - _, err = saclient.CertmanagerV1().CertificateRequests(f.Namespace.Name).UpdateStatus(context.TODO(), deniedCR, metav1.UpdateOptions{}) + _, err = saclient.CertmanagerV1().CertificateRequests(f.Namespace.Name).UpdateStatus(testingCtx, deniedCR, metav1.UpdateOptions{}) return err }) return isNotFoundError(deniedCR.Spec.IssuerRef, err) }).Should(BeTrue()) }) - It("a service account with the approve permissions for cluster scoped issuers.example.io/* should be able to approve requests", func() { - crd = createCRD(crdclient, group, "issuers", "Issuer", crdapi.ClusterScoped) - bindServiceAccountToApprove(f, sa, fmt.Sprintf("issuers.%s/*", group)) + It("a service account with the approve permissions for cluster scoped issuers.example.io/* should be able to approve requests", func(testingCtx context.Context) { + crd = createCRD(testingCtx, crdclient, group, "issuers", issuerKind, crdapi.ClusterScoped) + bindServiceAccountToApprove(testingCtx, f, sa, fmt.Sprintf("issuers.%s/*", group)) - approvedCR, err := f.CertManagerClientSet.CertmanagerV1().CertificateRequests(f.Namespace.Name).Get(context.TODO(), request.Name, metav1.GetOptions{}) + approvedCR, err := f.CertManagerClientSet.CertmanagerV1().CertificateRequests(f.Namespace.Name).Get(testingCtx, request.Name, metav1.GetOptions{}) Expect(err).NotTo(HaveOccurred()) apiutil.SetCertificateRequestCondition(approvedCR, cmapi.CertificateRequestConditionApproved, cmmeta.ConditionTrue, "cert-manager.io", "e2e") Expect(retry.OnError(retry.DefaultBackoff, retryOnNotFound(approvedCR.Spec.IssuerRef), func() error { - _, err = saclient.CertmanagerV1().CertificateRequests(f.Namespace.Name).UpdateStatus(context.TODO(), approvedCR, metav1.UpdateOptions{}) + _, err = saclient.CertmanagerV1().CertificateRequests(f.Namespace.Name).UpdateStatus(testingCtx, approvedCR, metav1.UpdateOptions{}) return err })).ToNot(HaveOccurred()) }) - It("a service account with the approve permissions for cluster scoped issuers.example.io/* should be able to deny requests", func() { - crd = createCRD(crdclient, group, "issuers", "Issuer", crdapi.ClusterScoped) - bindServiceAccountToApprove(f, sa, fmt.Sprintf("issuers.%s/*", group)) + It("a service account with the approve permissions for cluster scoped issuers.example.io/* should be able to deny requests", func(testingCtx context.Context) { + crd = createCRD(testingCtx, crdclient, group, "issuers", issuerKind, crdapi.ClusterScoped) + bindServiceAccountToApprove(testingCtx, f, sa, fmt.Sprintf("issuers.%s/*", group)) - deniedCR, err := f.CertManagerClientSet.CertmanagerV1().CertificateRequests(f.Namespace.Name).Get(context.TODO(), request.Name, metav1.GetOptions{}) + deniedCR, err := f.CertManagerClientSet.CertmanagerV1().CertificateRequests(f.Namespace.Name).Get(testingCtx, request.Name, metav1.GetOptions{}) Expect(err).NotTo(HaveOccurred()) apiutil.SetCertificateRequestCondition(deniedCR, cmapi.CertificateRequestConditionDenied, cmmeta.ConditionTrue, "cert-manager.io", "e2e") Expect(retry.OnError(retry.DefaultBackoff, retryOnNotFound(deniedCR.Spec.IssuerRef), func() error { - _, err = saclient.CertmanagerV1().CertificateRequests(f.Namespace.Name).UpdateStatus(context.TODO(), deniedCR, metav1.UpdateOptions{}) + _, err = saclient.CertmanagerV1().CertificateRequests(f.Namespace.Name).UpdateStatus(testingCtx, deniedCR, metav1.UpdateOptions{}) + return err + })).ToNot(HaveOccurred()) + }) + + It("a service account with the approve permissions for cluster scoped issuers.example.io/test-issuer should be able to approve requests", func(testingCtx context.Context) { + crd = createCRD(testingCtx, crdclient, group, "issuers", issuerKind, crdapi.ClusterScoped) + bindServiceAccountToApprove(testingCtx, f, sa, fmt.Sprintf("issuers.%s/test-issuer", group)) + + approvedCR, err := f.CertManagerClientSet.CertmanagerV1().CertificateRequests(f.Namespace.Name).Get(testingCtx, request.Name, metav1.GetOptions{}) + Expect(err).NotTo(HaveOccurred()) + apiutil.SetCertificateRequestCondition(approvedCR, cmapi.CertificateRequestConditionApproved, cmmeta.ConditionTrue, "cert-manager.io", "e2e") + Expect(retry.OnError(retry.DefaultBackoff, retryOnNotFound(approvedCR.Spec.IssuerRef), func() error { + _, err = saclient.CertmanagerV1().CertificateRequests(f.Namespace.Name).UpdateStatus(testingCtx, approvedCR, metav1.UpdateOptions{}) return err })).ToNot(HaveOccurred()) }) - It("a service account with the approve permissions for cluster scoped issuers.example.io/test-issuer should be able to approve requests", func() { - crd = createCRD(crdclient, group, "issuers", "Issuer", crdapi.ClusterScoped) - bindServiceAccountToApprove(f, sa, fmt.Sprintf("issuers.%s/test-issuer", group)) + It("a service account with the approve permissions for cluster scoped clusterissuers.example.io/test-issuer should be able to approve requests", func(testingCtx context.Context) { + crd = createCRD(testingCtx, crdclient, group, "clusterissuers", issuerKind, crdapi.ClusterScoped) + bindServiceAccountToApprove(testingCtx, f, sa, fmt.Sprintf("clusterissuers.%s/test-issuer", group)) - approvedCR, err := f.CertManagerClientSet.CertmanagerV1().CertificateRequests(f.Namespace.Name).Get(context.TODO(), request.Name, metav1.GetOptions{}) + approvedCR, err := f.CertManagerClientSet.CertmanagerV1().CertificateRequests(f.Namespace.Name).Get(testingCtx, request.Name, metav1.GetOptions{}) Expect(err).NotTo(HaveOccurred()) apiutil.SetCertificateRequestCondition(approvedCR, cmapi.CertificateRequestConditionApproved, cmmeta.ConditionTrue, "cert-manager.io", "e2e") Expect(retry.OnError(retry.DefaultBackoff, retryOnNotFound(approvedCR.Spec.IssuerRef), func() error { - _, err = saclient.CertmanagerV1().CertificateRequests(f.Namespace.Name).UpdateStatus(context.TODO(), approvedCR, metav1.UpdateOptions{}) + _, err = saclient.CertmanagerV1().CertificateRequests(f.Namespace.Name).UpdateStatus(testingCtx, approvedCR, metav1.UpdateOptions{}) return err })).ToNot(HaveOccurred()) }) - It("a service account with the approve permissions for cluster scoped issuers.example.io/.test-issuer should not be able to approve requests", func() { - crd = createCRD(crdclient, group, "issuers", "Issuer", crdapi.ClusterScoped) - bindServiceAccountToApprove(f, sa, fmt.Sprintf("issuers.%s/%s.test-issuer", f.Namespace.Name, group)) + It("a service account with the approve permissions for cluster scoped issuers.example.io/.test-issuer should not be able to approve requests", func(testingCtx context.Context) { + crd = createCRD(testingCtx, crdclient, group, "issuers", issuerKind, crdapi.ClusterScoped) + bindServiceAccountToApprove(testingCtx, f, sa, fmt.Sprintf("issuers.%s/%s.test-issuer", f.Namespace.Name, group)) - approvedCR, err := f.CertManagerClientSet.CertmanagerV1().CertificateRequests(f.Namespace.Name).Get(context.TODO(), request.Name, metav1.GetOptions{}) + approvedCR, err := f.CertManagerClientSet.CertmanagerV1().CertificateRequests(f.Namespace.Name).Get(testingCtx, request.Name, metav1.GetOptions{}) Expect(err).NotTo(HaveOccurred()) apiutil.SetCertificateRequestCondition(approvedCR, cmapi.CertificateRequestConditionApproved, cmmeta.ConditionTrue, "cert-manager.io", "e2e") err = retry.OnError(retry.DefaultBackoff, retryOnNotFound(approvedCR.Spec.IssuerRef), func() error { - _, err = saclient.CertmanagerV1().CertificateRequests(f.Namespace.Name).UpdateStatus(context.TODO(), approvedCR, metav1.UpdateOptions{}) + _, err = saclient.CertmanagerV1().CertificateRequests(f.Namespace.Name).UpdateStatus(testingCtx, approvedCR, metav1.UpdateOptions{}) return err }) Expect(isPermissionError(sa, approvedCR.Spec.IssuerRef, err)).To(BeTrue(), err.Error()) }) - It("a service account with the approve permissions for namespaced scoped issuers.example.io/.test-issuer should be able to approve requests", func() { - crd = createCRD(crdclient, group, "issuers", "Issuer", crdapi.NamespaceScoped) - bindServiceAccountToApprove(f, sa, fmt.Sprintf("issuers.%s/%s.test-issuer", group, f.Namespace.Name)) + It("a service account with the approve permissions for namespaced scoped issuers.example.io/.test-issuer should be able to approve requests", func(testingCtx context.Context) { + crd = createCRD(testingCtx, crdclient, group, "issuers", issuerKind, crdapi.NamespaceScoped) + bindServiceAccountToApprove(testingCtx, f, sa, fmt.Sprintf("issuers.%s/%s.test-issuer", group, f.Namespace.Name)) - approvedCR, err := f.CertManagerClientSet.CertmanagerV1().CertificateRequests(f.Namespace.Name).Get(context.TODO(), request.Name, metav1.GetOptions{}) + approvedCR, err := f.CertManagerClientSet.CertmanagerV1().CertificateRequests(f.Namespace.Name).Get(testingCtx, request.Name, metav1.GetOptions{}) Expect(err).NotTo(HaveOccurred()) apiutil.SetCertificateRequestCondition(approvedCR, cmapi.CertificateRequestConditionApproved, cmmeta.ConditionTrue, "cert-manager.io", "e2e") Expect(retry.OnError(retry.DefaultBackoff, retryOnNotFound(approvedCR.Spec.IssuerRef), func() error { - _, err = saclient.CertmanagerV1().CertificateRequests(f.Namespace.Name).UpdateStatus(context.TODO(), approvedCR, metav1.UpdateOptions{}) + _, err = saclient.CertmanagerV1().CertificateRequests(f.Namespace.Name).UpdateStatus(testingCtx, approvedCR, metav1.UpdateOptions{}) return err })).ToNot(HaveOccurred()) }) - It("a service account with the approve permissions for namespaced scoped issuers.example.io/test-issuer should not be able to approve requests", func() { - crd = createCRD(crdclient, group, "issuers", "Issuer", crdapi.NamespaceScoped) - bindServiceAccountToApprove(f, sa, fmt.Sprintf("issuers.%s/test-issuer", group)) + It("a service account with the approve permissions for namespaced scoped issuers.example.io/test-issuer should not be able to approve requests", func(testingCtx context.Context) { + crd = createCRD(testingCtx, crdclient, group, "issuers", issuerKind, crdapi.NamespaceScoped) + bindServiceAccountToApprove(testingCtx, f, sa, fmt.Sprintf("issuers.%s/test-issuer", group)) - approvedCR, err := f.CertManagerClientSet.CertmanagerV1().CertificateRequests(f.Namespace.Name).Get(context.TODO(), request.Name, metav1.GetOptions{}) + approvedCR, err := f.CertManagerClientSet.CertmanagerV1().CertificateRequests(f.Namespace.Name).Get(testingCtx, request.Name, metav1.GetOptions{}) Expect(err).NotTo(HaveOccurred()) apiutil.SetCertificateRequestCondition(approvedCR, cmapi.CertificateRequestConditionApproved, cmmeta.ConditionTrue, "cert-manager.io", "e2e") err = retry.OnError(retry.DefaultBackoff, retryOnNotFound(approvedCR.Spec.IssuerRef), func() error { - _, err = saclient.CertmanagerV1().CertificateRequests(f.Namespace.Name).UpdateStatus(context.TODO(), approvedCR, metav1.UpdateOptions{}) + _, err = saclient.CertmanagerV1().CertificateRequests(f.Namespace.Name).UpdateStatus(testingCtx, approvedCR, metav1.UpdateOptions{}) return err }) Expect(isPermissionError(sa, approvedCR.Spec.IssuerRef, err)).To(BeTrue(), err.Error()) @@ -373,63 +390,63 @@ var _ = framework.CertManagerDescribe("Approval CertificateRequests", func() { // - It("a service account with the approve permissions for cluster scoped issuers.example.io/test-issuer should be able to deny requests", func() { - crd = createCRD(crdclient, group, "issuers", "Issuer", crdapi.ClusterScoped) - bindServiceAccountToApprove(f, sa, fmt.Sprintf("issuers.%s/test-issuer", group)) + It("a service account with the approve permissions for cluster scoped issuers.example.io/test-issuer should be able to deny requests", func(testingCtx context.Context) { + crd = createCRD(testingCtx, crdclient, group, "issuers", issuerKind, crdapi.ClusterScoped) + bindServiceAccountToApprove(testingCtx, f, sa, fmt.Sprintf("issuers.%s/test-issuer", group)) - deniedCR, err := f.CertManagerClientSet.CertmanagerV1().CertificateRequests(f.Namespace.Name).Get(context.TODO(), request.Name, metav1.GetOptions{}) + deniedCR, err := f.CertManagerClientSet.CertmanagerV1().CertificateRequests(f.Namespace.Name).Get(testingCtx, request.Name, metav1.GetOptions{}) Expect(err).NotTo(HaveOccurred()) apiutil.SetCertificateRequestCondition(deniedCR, cmapi.CertificateRequestConditionDenied, cmmeta.ConditionTrue, "cert-manager.io", "e2e") Expect(retry.OnError(retry.DefaultBackoff, retryOnNotFound(deniedCR.Spec.IssuerRef), func() error { - _, err = saclient.CertmanagerV1().CertificateRequests(f.Namespace.Name).UpdateStatus(context.TODO(), deniedCR, metav1.UpdateOptions{}) + _, err = saclient.CertmanagerV1().CertificateRequests(f.Namespace.Name).UpdateStatus(testingCtx, deniedCR, metav1.UpdateOptions{}) return err })).ToNot(HaveOccurred()) }) - It("a service account with the approve permissions for cluster scoped issuers.example.io/.test-issuer should not be able to deny requests", func() { - crd = createCRD(crdclient, group, "issuers", "Issuer", crdapi.ClusterScoped) - bindServiceAccountToApprove(f, sa, fmt.Sprintf("issuers.%s/%s.test-issuer", f.Namespace.Name, group)) + It("a service account with the approve permissions for cluster scoped issuers.example.io/.test-issuer should not be able to deny requests", func(testingCtx context.Context) { + crd = createCRD(testingCtx, crdclient, group, "issuers", issuerKind, crdapi.ClusterScoped) + bindServiceAccountToApprove(testingCtx, f, sa, fmt.Sprintf("issuers.%s/%s.test-issuer", f.Namespace.Name, group)) - deniedCR, err := f.CertManagerClientSet.CertmanagerV1().CertificateRequests(f.Namespace.Name).Get(context.TODO(), request.Name, metav1.GetOptions{}) + deniedCR, err := f.CertManagerClientSet.CertmanagerV1().CertificateRequests(f.Namespace.Name).Get(testingCtx, request.Name, metav1.GetOptions{}) Expect(err).NotTo(HaveOccurred()) apiutil.SetCertificateRequestCondition(deniedCR, cmapi.CertificateRequestConditionDenied, cmmeta.ConditionTrue, "cert-manager.io", "e2e") err = retry.OnError(retry.DefaultBackoff, retryOnNotFound(deniedCR.Spec.IssuerRef), func() error { - _, err = saclient.CertmanagerV1().CertificateRequests(f.Namespace.Name).UpdateStatus(context.TODO(), deniedCR, metav1.UpdateOptions{}) + _, err = saclient.CertmanagerV1().CertificateRequests(f.Namespace.Name).UpdateStatus(testingCtx, deniedCR, metav1.UpdateOptions{}) return err }) Expect(isPermissionError(sa, deniedCR.Spec.IssuerRef, err)).To(BeTrue(), err.Error()) }) - It("a service account with the approve permissions for namespaced scoped issuers.example.io/.test-issuer should be able to deny requests", func() { - crd = createCRD(crdclient, group, "issuers", "Issuer", crdapi.NamespaceScoped) - bindServiceAccountToApprove(f, sa, fmt.Sprintf("issuers.%s/%s.test-issuer", group, f.Namespace.Name)) + It("a service account with the approve permissions for namespaced scoped issuers.example.io/.test-issuer should be able to deny requests", func(testingCtx context.Context) { + crd = createCRD(testingCtx, crdclient, group, "issuers", issuerKind, crdapi.NamespaceScoped) + bindServiceAccountToApprove(testingCtx, f, sa, fmt.Sprintf("issuers.%s/%s.test-issuer", group, f.Namespace.Name)) - deniedCR, err := f.CertManagerClientSet.CertmanagerV1().CertificateRequests(f.Namespace.Name).Get(context.TODO(), request.Name, metav1.GetOptions{}) + deniedCR, err := f.CertManagerClientSet.CertmanagerV1().CertificateRequests(f.Namespace.Name).Get(testingCtx, request.Name, metav1.GetOptions{}) Expect(err).NotTo(HaveOccurred()) apiutil.SetCertificateRequestCondition(deniedCR, cmapi.CertificateRequestConditionDenied, cmmeta.ConditionTrue, "cert-manager.io", "e2e") Expect(retry.OnError(retry.DefaultBackoff, retryOnNotFound(deniedCR.Spec.IssuerRef), func() error { - _, err = saclient.CertmanagerV1().CertificateRequests(f.Namespace.Name).UpdateStatus(context.TODO(), deniedCR, metav1.UpdateOptions{}) + _, err = saclient.CertmanagerV1().CertificateRequests(f.Namespace.Name).UpdateStatus(testingCtx, deniedCR, metav1.UpdateOptions{}) return err })).ToNot(HaveOccurred()) }) - It("a service account with the approve permissions for namespaced scoped issuers.example.io/test-issuer should not be able to denied requests", func() { - crd = createCRD(crdclient, group, "issuers", "Issuer", crdapi.NamespaceScoped) - bindServiceAccountToApprove(f, sa, fmt.Sprintf("issuers.%s/test-issuer", group)) + It("a service account with the approve permissions for namespaced scoped issuers.example.io/test-issuer should not be able to denied requests", func(testingCtx context.Context) { + crd = createCRD(testingCtx, crdclient, group, "issuers", issuerKind, crdapi.NamespaceScoped) + bindServiceAccountToApprove(testingCtx, f, sa, fmt.Sprintf("issuers.%s/test-issuer", group)) - deniedCR, err := f.CertManagerClientSet.CertmanagerV1().CertificateRequests(f.Namespace.Name).Get(context.TODO(), request.Name, metav1.GetOptions{}) + deniedCR, err := f.CertManagerClientSet.CertmanagerV1().CertificateRequests(f.Namespace.Name).Get(testingCtx, request.Name, metav1.GetOptions{}) Expect(err).NotTo(HaveOccurred()) apiutil.SetCertificateRequestCondition(deniedCR, cmapi.CertificateRequestConditionDenied, cmmeta.ConditionTrue, "cert-manager.io", "e2e") err = retry.OnError(retry.DefaultBackoff, retryOnNotFound(deniedCR.Spec.IssuerRef), func() error { - _, err = saclient.CertmanagerV1().CertificateRequests(f.Namespace.Name).UpdateStatus(context.TODO(), deniedCR, metav1.UpdateOptions{}) + _, err = saclient.CertmanagerV1().CertificateRequests(f.Namespace.Name).UpdateStatus(testingCtx, deniedCR, metav1.UpdateOptions{}) return err }) Expect(isPermissionError(sa, deniedCR.Spec.IssuerRef, err)).To(BeTrue(), err.Error()) }) }) -func createCRD(crdclient crdclientset.Interface, group, plural, kind string, scope crdapi.ResourceScope) *crdapi.CustomResourceDefinition { - crd, err := crdclient.ApiextensionsV1().CustomResourceDefinitions().Create(context.TODO(), &crdapi.CustomResourceDefinition{ +func createCRD(testingCtx context.Context, crdclient crdclientset.Interface, group, plural, kind string, scope crdapi.ResourceScope) *crdapi.CustomResourceDefinition { + crd, err := crdclient.ApiextensionsV1().CustomResourceDefinitions().Create(testingCtx, &crdapi.CustomResourceDefinition{ ObjectMeta: metav1.ObjectMeta{ Name: fmt.Sprintf("%s.%s", plural, group), }, @@ -459,8 +476,8 @@ func createCRD(crdclient crdclientset.Interface, group, plural, kind string, sco return crd } -func bindServiceAccountToApprove(f *framework.Framework, sa *corev1.ServiceAccount, resourceName string) { - clusterrole, err := f.KubeClientSet.RbacV1().ClusterRoles().Create(context.TODO(), &rbacv1.ClusterRole{ +func bindServiceAccountToApprove(testingCtx context.Context, f *framework.Framework, sa *corev1.ServiceAccount, resourceName string) { + clusterrole, err := f.KubeClientSet.RbacV1().ClusterRoles().Create(testingCtx, &rbacv1.ClusterRole{ ObjectMeta: metav1.ObjectMeta{ GenerateName: "certificaterequest-approver-", }, @@ -475,7 +492,7 @@ func bindServiceAccountToApprove(f *framework.Framework, sa *corev1.ServiceAccou }, metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred()) - _, err = f.KubeClientSet.RbacV1().ClusterRoleBindings().Create(context.TODO(), &rbacv1.ClusterRoleBinding{ + _, err = f.KubeClientSet.RbacV1().ClusterRoleBindings().Create(testingCtx, &rbacv1.ClusterRoleBinding{ ObjectMeta: metav1.ObjectMeta{ GenerateName: "certificaterequest-approver-", }, diff --git a/test/e2e/suite/certificaterequests/approval/userinfo.go b/test/e2e/suite/certificaterequests/approval/userinfo.go index b695c02d6e9..5b544ebde71 100644 --- a/test/e2e/suite/certificaterequests/approval/userinfo.go +++ b/test/e2e/suite/certificaterequests/approval/userinfo.go @@ -22,19 +22,21 @@ import ( "fmt" "time" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" corev1 "k8s.io/api/core/v1" rbacv1 "k8s.io/api/rbac/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/wait" + "github.com/cert-manager/cert-manager/e2e-tests/framework" + testutil "github.com/cert-manager/cert-manager/e2e-tests/framework/util" cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" clientset "github.com/cert-manager/cert-manager/pkg/client/clientset/versioned" "github.com/cert-manager/cert-manager/pkg/util" - "github.com/cert-manager/cert-manager/test/e2e/framework" - testutil "github.com/cert-manager/cert-manager/test/e2e/framework/util" + "github.com/cert-manager/cert-manager/pkg/util/pki" "github.com/cert-manager/cert-manager/test/unit/gen" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" ) // Check that the UserInfo fields on CertificateRequests are populated @@ -42,24 +44,31 @@ import ( var _ = framework.CertManagerDescribe("UserInfo CertificateRequests", func() { f := framework.NewDefaultFramework("userinfo-certificaterequests") - It("should appropriately create set UserInfo of CertificateRequests, and reject changes", func() { + It("should appropriately create set UserInfo of CertificateRequests, and reject changes", func(testingCtx context.Context) { var ( adminUsername = "kubernetes-admin" - adminGroups = []string{"system:masters", "system:authenticated"} ) + // Kubeadm >= 1.29 changed the groups of the admin user from + // system:masters to kubeadm:cluster-admins, so instead of hard coding + // the group names we try and read them from the client certificate. + // https://github.com/kubernetes/kubeadm/issues/2414 + cert, err := pki.DecodeX509CertificateBytes(f.KubeClientConfig.CertData) + Expect(err).NotTo(HaveOccurred()) + adminGroups := append([]string{"system:authenticated"}, cert.Subject.Organization...) + csr, _, err := gen.CSR(x509.RSA) Expect(err).NotTo(HaveOccurred()) cr := gen.CertificateRequest("test-v1", gen.SetCertificateRequestNamespace(f.Namespace.Name), gen.SetCertificateRequestCSR(csr), - gen.SetCertificateRequestIssuer(cmmeta.ObjectReference{ + gen.SetCertificateRequestIssuer(cmmeta.IssuerReference{ Name: "issuer", }), ) By("Creating CertificateRequest") - cr, err = f.CertManagerClientSet.CertmanagerV1().CertificateRequests(f.Namespace.Name).Create(context.TODO(), cr, metav1.CreateOptions{}) + cr, err = f.CertManagerClientSet.CertmanagerV1().CertificateRequests(f.Namespace.Name).Create(testingCtx, cr, metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred()) By("Ensure UserInfo fields are set") @@ -73,13 +82,13 @@ var _ = framework.CertManagerDescribe("UserInfo CertificateRequests", func() { By("Should error when attempting to update UserInfo fields") cr.Spec.Username = "abc" cr.Spec.UID = "123" - cr, err = f.CertManagerClientSet.CertmanagerV1().CertificateRequests(f.Namespace.Name).Update(context.TODO(), cr, metav1.UpdateOptions{}) + _, err = f.CertManagerClientSet.CertmanagerV1().CertificateRequests(f.Namespace.Name).Update(testingCtx, cr, metav1.UpdateOptions{}) Expect(err).To(HaveOccurred()) }) - It("should populate UserInfo with ServiceAccount if is the requester", func() { + It("should populate UserInfo with ServiceAccount if is the requester", func(testingCtx context.Context) { By("Creating ServiceAccount") - sa, err := f.KubeClientSet.CoreV1().ServiceAccounts(f.Namespace.Name).Create(context.TODO(), &corev1.ServiceAccount{ + sa, err := f.KubeClientSet.CoreV1().ServiceAccounts(f.Namespace.Name).Create(testingCtx, &corev1.ServiceAccount{ ObjectMeta: metav1.ObjectMeta{ Name: "test-sa", Namespace: f.Namespace.Name, @@ -88,7 +97,7 @@ var _ = framework.CertManagerDescribe("UserInfo CertificateRequests", func() { Expect(err).NotTo(HaveOccurred()) By("Creating certificaterequest-creator role") - role, err := f.KubeClientSet.RbacV1().Roles(f.Namespace.Name).Create(context.TODO(), &rbacv1.Role{ + role, err := f.KubeClientSet.RbacV1().Roles(f.Namespace.Name).Create(testingCtx, &rbacv1.Role{ ObjectMeta: metav1.ObjectMeta{ Name: "certificaterequest-creator", Namespace: f.Namespace.Name, @@ -104,7 +113,7 @@ var _ = framework.CertManagerDescribe("UserInfo CertificateRequests", func() { Expect(err).NotTo(HaveOccurred()) By("Creating certificaterequest-creator rolebinding for ServiceAccount") - _, err = f.KubeClientSet.RbacV1().RoleBindings(f.Namespace.Name).Create(context.TODO(), &rbacv1.RoleBinding{ + _, err = f.KubeClientSet.RbacV1().RoleBindings(f.Namespace.Name).Create(testingCtx, &rbacv1.RoleBinding{ ObjectMeta: metav1.ObjectMeta{ Name: "certificaterequest-creator", Namespace: f.Namespace.Name, @@ -126,7 +135,7 @@ var _ = framework.CertManagerDescribe("UserInfo CertificateRequests", func() { // Manually create a Secret to be populated with Service Account token // https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/#manually-create-a-service-account-api-token - secret, err := f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Create(context.TODO(), &corev1.Secret{ + secret, err := f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Create(testingCtx, &corev1.Secret{ ObjectMeta: metav1.ObjectMeta{ GenerateName: "sa-secret-", Name: f.Namespace.Name, @@ -142,23 +151,21 @@ var _ = framework.CertManagerDescribe("UserInfo CertificateRequests", func() { ok bool ) By("Waiting for service account secret to be created") - err = wait.PollImmediate(time.Second, time.Second*10, - func() (bool, error) { - secret, err = f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Get(context.TODO(), secret.Name, metav1.GetOptions{}) - if err != nil { - return false, err - } - - if len(secret.Data) == 0 { - return false, nil - } - if token, ok = secret.Data["token"]; !ok { - return false, nil - } - - return true, nil - }, - ) + err = wait.PollUntilContextTimeout(testingCtx, time.Second, time.Second*10, true, func(ctx context.Context) (bool, error) { + secret, err = f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Get(ctx, secret.Name, metav1.GetOptions{}) + if err != nil { + return false, err + } + + if len(secret.Data) == 0 { + return false, nil + } + if token, ok = secret.Data["token"]; !ok { + return false, nil + } + + return true, nil + }) Expect(err).NotTo(HaveOccurred()) By("Building ServiceAccount kubernetes clientset") @@ -166,7 +173,9 @@ var _ = framework.CertManagerDescribe("UserInfo CertificateRequests", func() { kubeConfig, err := testutil.LoadConfig(f.Config.KubeConfig, f.Config.KubeContext) Expect(err).NotTo(HaveOccurred()) - kubeConfig.BearerToken = fmt.Sprintf("%s", token) + kubeConfig.QPS = 9000 + kubeConfig.Burst = 9000 + kubeConfig.BearerToken = string(token) kubeConfig.CertData = nil kubeConfig.KeyData = nil @@ -179,13 +188,13 @@ var _ = framework.CertManagerDescribe("UserInfo CertificateRequests", func() { cr := gen.CertificateRequest("test-v1", gen.SetCertificateRequestNamespace(f.Namespace.Name), gen.SetCertificateRequestCSR(csr), - gen.SetCertificateRequestIssuer(cmmeta.ObjectReference{ + gen.SetCertificateRequestIssuer(cmmeta.IssuerReference{ Name: "issuer", }), ) By("Creating CertificateRequest") - cr, err = client.CertmanagerV1().CertificateRequests(f.Namespace.Name).Create(context.TODO(), cr, metav1.CreateOptions{}) + cr, err = client.CertmanagerV1().CertificateRequests(f.Namespace.Name).Create(testingCtx, cr, metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred()) expUsername := fmt.Sprintf("system:serviceaccount:%s:%s", f.Namespace.Name, sa.Name) diff --git a/test/e2e/suite/certificaterequests/doc.go b/test/e2e/suite/certificaterequests/doc.go index 05f74e32cd6..480144a5847 100644 --- a/test/e2e/suite/certificaterequests/doc.go +++ b/test/e2e/suite/certificaterequests/doc.go @@ -17,6 +17,6 @@ limitations under the License. package certificaterequests import ( - _ "github.com/cert-manager/cert-manager/test/e2e/suite/certificaterequests/approval" - _ "github.com/cert-manager/cert-manager/test/e2e/suite/certificaterequests/selfsigned" + _ "github.com/cert-manager/cert-manager/e2e-tests/suite/certificaterequests/approval" + _ "github.com/cert-manager/cert-manager/e2e-tests/suite/certificaterequests/selfsigned" ) diff --git a/test/e2e/suite/certificaterequests/selfsigned/secret.go b/test/e2e/suite/certificaterequests/selfsigned/secret.go index 2cae09db14e..b41d7062a7c 100644 --- a/test/e2e/suite/certificaterequests/selfsigned/secret.go +++ b/test/e2e/suite/certificaterequests/selfsigned/secret.go @@ -19,17 +19,18 @@ package selfsigned import ( "context" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/utils/clock" + "github.com/cert-manager/cert-manager/e2e-tests/framework" apiutil "github.com/cert-manager/cert-manager/pkg/api/util" cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" - "github.com/cert-manager/cert-manager/test/e2e/framework" testcrypto "github.com/cert-manager/cert-manager/test/unit/crypto" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" ) // This test ensures that a self-signed certificaterequest will still be signed @@ -45,7 +46,7 @@ var _ = framework.CertManagerDescribe("CertificateRequests SelfSigned Secret", f bundle *testcrypto.CryptoBundle ) - JustBeforeEach(func() { + JustBeforeEach(func(testingCtx context.Context) { var err error bundle, err = testcrypto.CreateCryptoBundle(&cmapi.Certificate{ Spec: cmapi.CertificateSpec{ @@ -55,35 +56,35 @@ var _ = framework.CertManagerDescribe("CertificateRequests SelfSigned Secret", f Expect(err).NotTo(HaveOccurred()) }) - JustAfterEach(func() { - Expect(f.CRClient.Delete(context.TODO(), request)).NotTo(HaveOccurred()) - Expect(f.CRClient.Delete(context.TODO(), issuer)).NotTo(HaveOccurred()) - Expect(f.CRClient.Delete(context.TODO(), secret)).NotTo(HaveOccurred()) + JustAfterEach(func(testingCtx context.Context) { + Expect(f.CRClient.Delete(testingCtx, request)).NotTo(HaveOccurred()) + Expect(f.CRClient.Delete(testingCtx, issuer)).NotTo(HaveOccurred()) + Expect(f.CRClient.Delete(testingCtx, secret)).NotTo(HaveOccurred()) }) - It("Issuer: the private key Secret is created after the request is created should still be signed", func() { + It("Issuer: the private key Secret is created after the request is created should still be signed", func(testingCtx context.Context) { var err error - issuer, err = f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Create(context.TODO(), &cmapi.Issuer{ + issuer, err = f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Create(testingCtx, &cmapi.Issuer{ ObjectMeta: metav1.ObjectMeta{GenerateName: "selfsigned-", Namespace: f.Namespace.Name}, Spec: cmapi.IssuerSpec{IssuerConfig: cmapi.IssuerConfig{SelfSigned: new(cmapi.SelfSignedIssuer)}}, }, metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred()) - request, err = f.CertManagerClientSet.CertmanagerV1().CertificateRequests(f.Namespace.Name).Create(context.TODO(), &cmapi.CertificateRequest{ + request, err = f.CertManagerClientSet.CertmanagerV1().CertificateRequests(f.Namespace.Name).Create(testingCtx, &cmapi.CertificateRequest{ ObjectMeta: metav1.ObjectMeta{ GenerateName: "selfsigned-", Namespace: f.Namespace.Name, Annotations: map[string]string{"cert-manager.io/private-key-secret-name": "selfsigned-test"}, }, Spec: cmapi.CertificateRequestSpec{ Request: bundle.CSRBytes, - IssuerRef: cmmeta.ObjectReference{Name: issuer.GetName(), Kind: "Issuer", Group: "cert-manager.io"}, + IssuerRef: cmmeta.IssuerReference{Name: issuer.GetName(), Kind: "Issuer", Group: "cert-manager.io"}, }, }, metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred()) By("waiting for request to be set to pending") Eventually(func() bool { - request, err = f.CertManagerClientSet.CertmanagerV1().CertificateRequests(f.Namespace.Name).Get(context.TODO(), request.Name, metav1.GetOptions{}) + request, err = f.CertManagerClientSet.CertmanagerV1().CertificateRequests(f.Namespace.Name).Get(testingCtx, request.Name, metav1.GetOptions{}) Expect(err).NotTo(HaveOccurred()) return apiutil.CertificateRequestHasCondition(request, cmapi.CertificateRequestCondition{ Type: cmapi.CertificateRequestConditionReady, @@ -93,7 +94,7 @@ var _ = framework.CertManagerDescribe("CertificateRequests SelfSigned Secret", f }, "20s", "1s").Should(BeTrue(), "request was not set to pending in time: %#+v", request) By("creating Secret with private key should result in the request to be signed") - secret, err = f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Create(context.TODO(), &corev1.Secret{ + secret, err = f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Create(testingCtx, &corev1.Secret{ ObjectMeta: metav1.ObjectMeta{Name: "selfsigned-test", Namespace: f.Namespace.Name}, Data: map[string][]byte{ "tls.key": bundle.PrivateKeyBytes, @@ -101,7 +102,7 @@ var _ = framework.CertManagerDescribe("CertificateRequests SelfSigned Secret", f }, metav1.CreateOptions{}) Eventually(func() bool { - request, err = f.CertManagerClientSet.CertmanagerV1().CertificateRequests(f.Namespace.Name).Get(context.TODO(), request.Name, metav1.GetOptions{}) + request, err = f.CertManagerClientSet.CertmanagerV1().CertificateRequests(f.Namespace.Name).Get(testingCtx, request.Name, metav1.GetOptions{}) Expect(err).NotTo(HaveOccurred()) return apiutil.CertificateRequestHasCondition(request, cmapi.CertificateRequestCondition{ Type: cmapi.CertificateRequestConditionReady, @@ -111,35 +112,36 @@ var _ = framework.CertManagerDescribe("CertificateRequests SelfSigned Secret", f }, "20s", "1s").Should(BeTrue(), "request was not signed in time: %#+v", request) }) - It("Issuer: private key Secret is updated with a valid private key after the request is created should still be signed", func() { + It("Issuer: private key Secret is updated with a valid private key after the request is created should still be signed", func(testingCtx context.Context) { var err error By("creating Secret with missing private key") - secret, err = f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Create(context.TODO(), &corev1.Secret{ + secret, err = f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Create(testingCtx, &corev1.Secret{ ObjectMeta: metav1.ObjectMeta{Name: "selfsigned-test", Namespace: f.Namespace.Name}, Data: map[string][]byte{}, }, metav1.CreateOptions{}) + Expect(err).NotTo(HaveOccurred()) - issuer, err = f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Create(context.TODO(), &cmapi.Issuer{ + issuer, err = f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Create(testingCtx, &cmapi.Issuer{ ObjectMeta: metav1.ObjectMeta{GenerateName: "selfsigned-", Namespace: f.Namespace.Name}, Spec: cmapi.IssuerSpec{IssuerConfig: cmapi.IssuerConfig{SelfSigned: new(cmapi.SelfSignedIssuer)}}, }, metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred()) - request, err = f.CertManagerClientSet.CertmanagerV1().CertificateRequests(f.Namespace.Name).Create(context.TODO(), &cmapi.CertificateRequest{ + request, err = f.CertManagerClientSet.CertmanagerV1().CertificateRequests(f.Namespace.Name).Create(testingCtx, &cmapi.CertificateRequest{ ObjectMeta: metav1.ObjectMeta{ GenerateName: "selfsigned-", Namespace: f.Namespace.Name, Annotations: map[string]string{"cert-manager.io/private-key-secret-name": "selfsigned-test"}, }, Spec: cmapi.CertificateRequestSpec{ Request: bundle.CSRBytes, - IssuerRef: cmmeta.ObjectReference{Name: issuer.GetName(), Kind: "Issuer", Group: "cert-manager.io"}, + IssuerRef: cmmeta.IssuerReference{Name: issuer.GetName(), Kind: "Issuer", Group: "cert-manager.io"}, }, }, metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred()) By("waiting for request to be set to pending") Eventually(func() bool { - request, err = f.CertManagerClientSet.CertmanagerV1().CertificateRequests(f.Namespace.Name).Get(context.TODO(), request.Name, metav1.GetOptions{}) + request, err = f.CertManagerClientSet.CertmanagerV1().CertificateRequests(f.Namespace.Name).Get(testingCtx, request.Name, metav1.GetOptions{}) Expect(err).NotTo(HaveOccurred()) return apiutil.CertificateRequestHasCondition(request, cmapi.CertificateRequestCondition{ Type: cmapi.CertificateRequestConditionReady, @@ -150,10 +152,10 @@ var _ = framework.CertManagerDescribe("CertificateRequests SelfSigned Secret", f By("updating referenced private key Secret should get the request signed") secret.Data = map[string][]byte{"tls.key": bundle.PrivateKeyBytes} - _, err = f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Update(context.TODO(), secret, metav1.UpdateOptions{}) + _, err = f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Update(testingCtx, secret, metav1.UpdateOptions{}) Expect(err).NotTo(HaveOccurred()) Eventually(func() bool { - request, err = f.CertManagerClientSet.CertmanagerV1().CertificateRequests(f.Namespace.Name).Get(context.TODO(), request.Name, metav1.GetOptions{}) + request, err = f.CertManagerClientSet.CertmanagerV1().CertificateRequests(f.Namespace.Name).Get(testingCtx, request.Name, metav1.GetOptions{}) Expect(err).NotTo(HaveOccurred()) return apiutil.CertificateRequestHasCondition(request, cmapi.CertificateRequestCondition{ Type: cmapi.CertificateRequestConditionReady, @@ -163,29 +165,29 @@ var _ = framework.CertManagerDescribe("CertificateRequests SelfSigned Secret", f }, "20s", "1s").Should(BeTrue(), "request was not signed in time: %#+v", request) }) - It("ClusterIssuer: the private key Secret is created after the request is created should still be signed", func() { + It("ClusterIssuer: the private key Secret is created after the request is created should still be signed", func(testingCtx context.Context) { var err error - issuer, err = f.CertManagerClientSet.CertmanagerV1().ClusterIssuers().Create(context.TODO(), &cmapi.ClusterIssuer{ + issuer, err = f.CertManagerClientSet.CertmanagerV1().ClusterIssuers().Create(testingCtx, &cmapi.ClusterIssuer{ ObjectMeta: metav1.ObjectMeta{GenerateName: "selfsigned-"}, Spec: cmapi.IssuerSpec{IssuerConfig: cmapi.IssuerConfig{SelfSigned: new(cmapi.SelfSignedIssuer)}}, }, metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred()) - request, err = f.CertManagerClientSet.CertmanagerV1().CertificateRequests(f.Namespace.Name).Create(context.TODO(), &cmapi.CertificateRequest{ + request, err = f.CertManagerClientSet.CertmanagerV1().CertificateRequests(f.Namespace.Name).Create(testingCtx, &cmapi.CertificateRequest{ ObjectMeta: metav1.ObjectMeta{ GenerateName: "selfsigned-", Namespace: f.Namespace.Name, Annotations: map[string]string{"cert-manager.io/private-key-secret-name": "selfsigned-test"}, }, Spec: cmapi.CertificateRequestSpec{ Request: bundle.CSRBytes, - IssuerRef: cmmeta.ObjectReference{Name: issuer.GetName(), Kind: "ClusterIssuer", Group: "cert-manager.io"}, + IssuerRef: cmmeta.IssuerReference{Name: issuer.GetName(), Kind: "ClusterIssuer", Group: "cert-manager.io"}, }, }, metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred()) By("waiting for request to be set to pending") Eventually(func() bool { - request, err = f.CertManagerClientSet.CertmanagerV1().CertificateRequests(f.Namespace.Name).Get(context.TODO(), request.Name, metav1.GetOptions{}) + request, err = f.CertManagerClientSet.CertmanagerV1().CertificateRequests(f.Namespace.Name).Get(testingCtx, request.Name, metav1.GetOptions{}) Expect(err).NotTo(HaveOccurred()) return apiutil.CertificateRequestHasCondition(request, cmapi.CertificateRequestCondition{ Type: cmapi.CertificateRequestConditionReady, @@ -195,7 +197,7 @@ var _ = framework.CertManagerDescribe("CertificateRequests SelfSigned Secret", f }, "20s", "1s").Should(BeTrue(), "request was not set to pending in time: %#+v", request) By("creating Secret with private key should result in the request to be signed") - secret, err = f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Create(context.TODO(), &corev1.Secret{ + secret, err = f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Create(testingCtx, &corev1.Secret{ ObjectMeta: metav1.ObjectMeta{Name: "selfsigned-test", Namespace: f.Namespace.Name}, Data: map[string][]byte{ "tls.key": bundle.PrivateKeyBytes, @@ -203,7 +205,7 @@ var _ = framework.CertManagerDescribe("CertificateRequests SelfSigned Secret", f }, metav1.CreateOptions{}) Eventually(func() bool { - request, err = f.CertManagerClientSet.CertmanagerV1().CertificateRequests(f.Namespace.Name).Get(context.TODO(), request.Name, metav1.GetOptions{}) + request, err = f.CertManagerClientSet.CertmanagerV1().CertificateRequests(f.Namespace.Name).Get(testingCtx, request.Name, metav1.GetOptions{}) Expect(err).NotTo(HaveOccurred()) return apiutil.CertificateRequestHasCondition(request, cmapi.CertificateRequestCondition{ Type: cmapi.CertificateRequestConditionReady, @@ -213,35 +215,36 @@ var _ = framework.CertManagerDescribe("CertificateRequests SelfSigned Secret", f }, "20s", "1s").Should(BeTrue(), "request was not signed in time: %#+v", request) }) - It("ClusterIssuer: private key Secret is updated with a valid private key after the request is created should still be signed", func() { + It("ClusterIssuer: private key Secret is updated with a valid private key after the request is created should still be signed", func(testingCtx context.Context) { var err error By("creating Secret with missing private key") - secret, err = f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Create(context.TODO(), &corev1.Secret{ + secret, err = f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Create(testingCtx, &corev1.Secret{ ObjectMeta: metav1.ObjectMeta{Name: "selfsigned-test", Namespace: f.Namespace.Name}, Data: map[string][]byte{}, }, metav1.CreateOptions{}) + Expect(err).NotTo(HaveOccurred()) - issuer, err = f.CertManagerClientSet.CertmanagerV1().ClusterIssuers().Create(context.TODO(), &cmapi.ClusterIssuer{ + issuer, err = f.CertManagerClientSet.CertmanagerV1().ClusterIssuers().Create(testingCtx, &cmapi.ClusterIssuer{ ObjectMeta: metav1.ObjectMeta{GenerateName: "selfsigned-"}, Spec: cmapi.IssuerSpec{IssuerConfig: cmapi.IssuerConfig{SelfSigned: new(cmapi.SelfSignedIssuer)}}, }, metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred()) - request, err = f.CertManagerClientSet.CertmanagerV1().CertificateRequests(f.Namespace.Name).Create(context.TODO(), &cmapi.CertificateRequest{ + request, err = f.CertManagerClientSet.CertmanagerV1().CertificateRequests(f.Namespace.Name).Create(testingCtx, &cmapi.CertificateRequest{ ObjectMeta: metav1.ObjectMeta{ GenerateName: "selfsigned-", Namespace: f.Namespace.Name, Annotations: map[string]string{"cert-manager.io/private-key-secret-name": "selfsigned-test"}, }, Spec: cmapi.CertificateRequestSpec{ Request: bundle.CSRBytes, - IssuerRef: cmmeta.ObjectReference{Name: issuer.GetName(), Kind: "ClusterIssuer", Group: "cert-manager.io"}, + IssuerRef: cmmeta.IssuerReference{Name: issuer.GetName(), Kind: "ClusterIssuer", Group: "cert-manager.io"}, }, }, metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred()) By("waiting for request to be set to pending") Eventually(func() bool { - request, err = f.CertManagerClientSet.CertmanagerV1().CertificateRequests(f.Namespace.Name).Get(context.TODO(), request.Name, metav1.GetOptions{}) + request, err = f.CertManagerClientSet.CertmanagerV1().CertificateRequests(f.Namespace.Name).Get(testingCtx, request.Name, metav1.GetOptions{}) Expect(err).NotTo(HaveOccurred()) return apiutil.CertificateRequestHasCondition(request, cmapi.CertificateRequestCondition{ Type: cmapi.CertificateRequestConditionReady, @@ -252,10 +255,10 @@ var _ = framework.CertManagerDescribe("CertificateRequests SelfSigned Secret", f By("updating referenced private key Secret should get the request signed") secret.Data = map[string][]byte{"tls.key": bundle.PrivateKeyBytes} - _, err = f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Update(context.TODO(), secret, metav1.UpdateOptions{}) + _, err = f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Update(testingCtx, secret, metav1.UpdateOptions{}) Expect(err).NotTo(HaveOccurred()) Eventually(func() bool { - request, err = f.CertManagerClientSet.CertmanagerV1().CertificateRequests(f.Namespace.Name).Get(context.TODO(), request.Name, metav1.GetOptions{}) + request, err = f.CertManagerClientSet.CertmanagerV1().CertificateRequests(f.Namespace.Name).Get(testingCtx, request.Name, metav1.GetOptions{}) Expect(err).NotTo(HaveOccurred()) return apiutil.CertificateRequestHasCondition(request, cmapi.CertificateRequestCondition{ Type: cmapi.CertificateRequestConditionReady, diff --git a/test/e2e/suite/certificates/additionaloutputformats.go b/test/e2e/suite/certificates/additionaloutputformats.go index bfb861194cf..1db31355967 100644 --- a/test/e2e/suite/certificates/additionaloutputformats.go +++ b/test/e2e/suite/certificates/additionaloutputformats.go @@ -22,36 +22,37 @@ import ( "encoding/pem" "time" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - . "github.com/onsi/gomega/gstruct" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/util/retry" - "k8s.io/utils/pointer" - "sigs.k8s.io/structured-merge-diff/v4/fieldpath" + "k8s.io/utils/ptr" + "sigs.k8s.io/structured-merge-diff/v6/fieldpath" + "github.com/cert-manager/cert-manager/e2e-tests/framework" + e2eutil "github.com/cert-manager/cert-manager/e2e-tests/util" "github.com/cert-manager/cert-manager/internal/controller/feature" apiutil "github.com/cert-manager/cert-manager/pkg/api/util" cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" utilfeature "github.com/cert-manager/cert-manager/pkg/util/feature" - "github.com/cert-manager/cert-manager/test/e2e/framework" - e2eutil "github.com/cert-manager/cert-manager/test/e2e/util" "github.com/cert-manager/cert-manager/test/unit/gen" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + . "github.com/onsi/gomega/gstruct" ) -// This test ensures that the Certificates AdditionalCertificateOutputFormats +// This test ensures that the Certificates additionalOutputFormats // is reflected on the Certificate's target Secret, and is reconciled on modify // events. -var _ = framework.CertManagerDescribe("Certificate AdditionalCertificateOutputFormats", func() { +var _ = framework.CertManagerDescribe("Certificate additionalOutputFormats", func() { const ( issuerName = "certificate-additional-output-formats" secretName = "test-additional-output-formats" ) - createCertificate := func(f *framework.Framework, aof []cmapi.CertificateAdditionalOutputFormat) (string, *cmapi.Certificate) { - framework.RequireFeatureGate(f, utilfeature.DefaultFeatureGate, feature.AdditionalCertificateOutputFormats) + f := framework.NewDefaultFramework("certificates-additional-output-formats") + createCertificate := func(testingCtx context.Context, f *framework.Framework, aof []cmapi.CertificateAdditionalOutputFormat) (string, *cmapi.Certificate) { crt := &cmapi.Certificate{ ObjectMeta: metav1.ObjectMeta{ GenerateName: "test-additional-output-formats-", @@ -61,7 +62,7 @@ var _ = framework.CertManagerDescribe("Certificate AdditionalCertificateOutputFo CommonName: "test", SecretName: secretName, PrivateKey: &cmapi.CertificatePrivateKey{RotationPolicy: cmapi.RotationPolicyAlways}, - IssuerRef: cmmeta.ObjectReference{ + IssuerRef: cmmeta.IssuerReference{ Name: issuerName, Kind: "Issuer", Group: "cert-manager.io", }, AdditionalOutputFormats: aof, @@ -69,55 +70,53 @@ var _ = framework.CertManagerDescribe("Certificate AdditionalCertificateOutputFo } By("creating Certificate with AdditionalOutputFormats") - crt, err := f.CertManagerClientSet.CertmanagerV1().Certificates(f.Namespace.Name).Create(context.Background(), crt, metav1.CreateOptions{}) + crt, err := f.CertManagerClientSet.CertmanagerV1().Certificates(f.Namespace.Name).Create(testingCtx, crt, metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred()) - crt, err = f.Helper().WaitForCertificateReadyAndDoneIssuing(crt, time.Minute*2) + crt, err = f.Helper().WaitForCertificateReadyAndDoneIssuing(testingCtx, crt, time.Minute*2) Expect(err).NotTo(HaveOccurred(), "failed to wait for Certificate to become Ready") return crt.Name, crt } - f := framework.NewDefaultFramework("certificates-additional-output-formats") - - BeforeEach(func() { + BeforeEach(func(testingCtx context.Context) { By("creating a self-signing issuer") issuer := gen.Issuer(issuerName, gen.SetIssuerNamespace(f.Namespace.Name), gen.SetIssuerSelfSigned(cmapi.SelfSignedIssuer{})) - Expect(f.CRClient.Create(context.Background(), issuer)).To(Succeed()) + Expect(f.CRClient.Create(testingCtx, issuer)).To(Succeed()) By("Waiting for Issuer to become Ready") - err := e2eutil.WaitForIssuerCondition(f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name), + err := e2eutil.WaitForIssuerCondition(testingCtx, f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name), issuerName, cmapi.IssuerCondition{Type: cmapi.IssuerConditionReady, Status: cmmeta.ConditionTrue}) Expect(err).NotTo(HaveOccurred()) }) - AfterEach(func() { - Expect(f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Delete(context.Background(), issuerName, metav1.DeleteOptions{})).NotTo(HaveOccurred()) + AfterEach(func(testingCtx context.Context) { + Expect(f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Delete(testingCtx, issuerName, metav1.DeleteOptions{})).NotTo(HaveOccurred()) }) - It("should not remove Secret data keys which have been added by a third party, and not present in the Certificate's AdditionalOutputFormats", func() { - createCertificate(f, nil) + It("should not remove Secret data keys which have been added by a third party, and not present in the Certificate's AdditionalOutputFormats", func(testingCtx context.Context) { + createCertificate(testingCtx, f, nil) - secret, err := f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Get(context.Background(), secretName, metav1.GetOptions{}) + secret, err := f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Get(testingCtx, secretName, metav1.GetOptions{}) Expect(err).NotTo(HaveOccurred()) By("ensure Secret has only expected keys") Expect(secret.Data).To(MatchAllKeys(Keys{"tls.crt": Not(BeEmpty()), "tls.key": Not(BeEmpty()), "ca.crt": Not(BeEmpty())})) By("add extra Data keys to the secret which should not be removed") - secret, err = f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Get(context.Background(), secretName, metav1.GetOptions{}) + secret, err = f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Get(testingCtx, secretName, metav1.GetOptions{}) Expect(err).NotTo(HaveOccurred()) secret.Data["random-1"] = []byte("data-1") secret.Data["random-2"] = []byte("data-2") secret.Data["tls-combined.pem"] = []byte("data-3") secret.Data["key.der"] = []byte("data-4") - secret, err = f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Update(context.Background(), secret, metav1.UpdateOptions{}) + secret, err = f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Update(testingCtx, secret, metav1.UpdateOptions{}) Expect(err).NotTo(HaveOccurred()) Consistently(func() map[string][]byte { - secret, err = f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Get(context.Background(), secretName, metav1.GetOptions{}) + secret, err = f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Get(testingCtx, secretName, metav1.GetOptions{}) Expect(err).NotTo(HaveOccurred()) return secret.Data }).WithTimeout(5 * time.Second).WithPolling(time.Second).Should(MatchAllKeys(Keys{ @@ -131,11 +130,11 @@ var _ = framework.CertManagerDescribe("Certificate AdditionalCertificateOutputFo })) }) - It("should add additional output formats to the Secret when the Certificate's AdditionalOutputFormats is updated, then removed when removed from AdditionalOutputFormats", func() { - crtName, crt := createCertificate(f, nil) + It("should add additional output formats to the Secret when the Certificate's AdditionalOutputFormats is updated, then removed when removed from AdditionalOutputFormats", func(testingCtx context.Context) { + crtName, crt := createCertificate(testingCtx, f, nil) By("ensure Secret has only expected keys") - secret, err := f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Get(context.Background(), secretName, metav1.GetOptions{}) + secret, err := f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Get(testingCtx, secretName, metav1.GetOptions{}) Expect(err).NotTo(HaveOccurred()) Expect(secret.Data).To(MatchAllKeys(Keys{ "ca.crt": Not(BeEmpty()), @@ -148,19 +147,19 @@ var _ = framework.CertManagerDescribe("Certificate AdditionalCertificateOutputFo By("add Combined PEM to Certificate's Additional Output Formats") err = retry.RetryOnConflict(retry.DefaultRetry, func() error { - crt, err = f.CertManagerClientSet.CertmanagerV1().Certificates(f.Namespace.Name).Get(context.Background(), crtName, metav1.GetOptions{}) + crt, err = f.CertManagerClientSet.CertmanagerV1().Certificates(f.Namespace.Name).Get(testingCtx, crtName, metav1.GetOptions{}) if err != nil { return err } crt.Spec.AdditionalOutputFormats = []cmapi.CertificateAdditionalOutputFormat{{Type: "CombinedPEM"}} - crt, err = f.CertManagerClientSet.CertmanagerV1().Certificates(f.Namespace.Name).Update(context.Background(), crt, metav1.UpdateOptions{}) + crt, err = f.CertManagerClientSet.CertmanagerV1().Certificates(f.Namespace.Name).Update(testingCtx, crt, metav1.UpdateOptions{}) return err }) Expect(err).NotTo(HaveOccurred()) By("ensure Secret has correct Combined PEM additional output formats") Eventually(func() map[string][]byte { - secret, err = f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Get(context.Background(), secretName, metav1.GetOptions{}) + secret, err = f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Get(testingCtx, secretName, metav1.GetOptions{}) Expect(err).NotTo(HaveOccurred()) return secret.Data }).WithTimeout(5 * time.Second).WithPolling(time.Second).Should(MatchAllKeys(Keys{ @@ -172,19 +171,19 @@ var _ = framework.CertManagerDescribe("Certificate AdditionalCertificateOutputFo By("add DER to Certificate's Additional Output Formats") err = retry.RetryOnConflict(retry.DefaultRetry, func() error { - crt, err = f.CertManagerClientSet.CertmanagerV1().Certificates(f.Namespace.Name).Get(context.Background(), crtName, metav1.GetOptions{}) + crt, err = f.CertManagerClientSet.CertmanagerV1().Certificates(f.Namespace.Name).Get(testingCtx, crtName, metav1.GetOptions{}) if err != nil { return err } crt.Spec.AdditionalOutputFormats = []cmapi.CertificateAdditionalOutputFormat{{Type: "CombinedPEM"}, {Type: "DER"}} - crt, err = f.CertManagerClientSet.CertmanagerV1().Certificates(f.Namespace.Name).Update(context.Background(), crt, metav1.UpdateOptions{}) + crt, err = f.CertManagerClientSet.CertmanagerV1().Certificates(f.Namespace.Name).Update(testingCtx, crt, metav1.UpdateOptions{}) return err }) Expect(err).NotTo(HaveOccurred()) By("ensure Secret has correct Combined PEM and DER additional output formats") Eventually(func() map[string][]byte { - secret, err = f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Get(context.Background(), secretName, metav1.GetOptions{}) + secret, err = f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Get(testingCtx, secretName, metav1.GetOptions{}) Expect(err).NotTo(HaveOccurred()) return secret.Data }).WithTimeout(5 * time.Second).WithPolling(time.Second).Should(MatchAllKeys(Keys{ @@ -197,19 +196,19 @@ var _ = framework.CertManagerDescribe("Certificate AdditionalCertificateOutputFo By("remove Combined PEM from Certificate's Additional Output Formats") err = retry.RetryOnConflict(retry.DefaultRetry, func() error { - crt, err = f.CertManagerClientSet.CertmanagerV1().Certificates(f.Namespace.Name).Get(context.Background(), crtName, metav1.GetOptions{}) + crt, err = f.CertManagerClientSet.CertmanagerV1().Certificates(f.Namespace.Name).Get(testingCtx, crtName, metav1.GetOptions{}) if err != nil { return err } crt.Spec.AdditionalOutputFormats = []cmapi.CertificateAdditionalOutputFormat{{Type: "DER"}} - crt, err = f.CertManagerClientSet.CertmanagerV1().Certificates(f.Namespace.Name).Update(context.Background(), crt, metav1.UpdateOptions{}) + crt, err = f.CertManagerClientSet.CertmanagerV1().Certificates(f.Namespace.Name).Update(testingCtx, crt, metav1.UpdateOptions{}) return err }) Expect(err).NotTo(HaveOccurred()) By("ensure Secret has correct DER additional output formats") Eventually(func() map[string][]byte { - secret, err = f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Get(context.Background(), secretName, metav1.GetOptions{}) + secret, err = f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Get(testingCtx, secretName, metav1.GetOptions{}) Expect(err).NotTo(HaveOccurred()) return secret.Data }).WithTimeout(5 * time.Second).WithPolling(time.Second).Should(MatchAllKeys(Keys{ @@ -221,19 +220,19 @@ var _ = framework.CertManagerDescribe("Certificate AdditionalCertificateOutputFo By("remove DER from Certificate's Additional Output Formats") err = retry.RetryOnConflict(retry.DefaultRetry, func() error { - crt, err = f.CertManagerClientSet.CertmanagerV1().Certificates(f.Namespace.Name).Get(context.Background(), crtName, metav1.GetOptions{}) + crt, err = f.CertManagerClientSet.CertmanagerV1().Certificates(f.Namespace.Name).Get(testingCtx, crtName, metav1.GetOptions{}) if err != nil { return err } crt.Spec.AdditionalOutputFormats = nil - crt, err = f.CertManagerClientSet.CertmanagerV1().Certificates(f.Namespace.Name).Update(context.Background(), crt, metav1.UpdateOptions{}) + crt, err = f.CertManagerClientSet.CertmanagerV1().Certificates(f.Namespace.Name).Update(testingCtx, crt, metav1.UpdateOptions{}) return err }) Expect(err).NotTo(HaveOccurred()) By("ensure Secret has no additional output formats") Eventually(func() map[string][]byte { - secret, err = f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Get(context.Background(), secretName, metav1.GetOptions{}) + secret, err = f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Get(testingCtx, secretName, metav1.GetOptions{}) Expect(err).NotTo(HaveOccurred()) return secret.Data }).WithTimeout(5 * time.Second).WithPolling(time.Second).Should(MatchAllKeys(Keys{ @@ -243,11 +242,11 @@ var _ = framework.CertManagerDescribe("Certificate AdditionalCertificateOutputFo })) }) - It("should update the values of Additional Output Format keys that have been modified on the Secret", func() { - createCertificate(f, []cmapi.CertificateAdditionalOutputFormat{{Type: "CombinedPEM"}, {Type: "DER"}}) + It("should update the values of Additional Output Format keys that have been modified on the Secret", func(testingCtx context.Context) { + createCertificate(testingCtx, f, []cmapi.CertificateAdditionalOutputFormat{{Type: "CombinedPEM"}, {Type: "DER"}}) By("ensure Secret has only expected keys") - secret, err := f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Get(context.Background(), secretName, metav1.GetOptions{}) + secret, err := f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Get(testingCtx, secretName, metav1.GetOptions{}) Expect(err).NotTo(HaveOccurred()) crtPEM := secret.Data["tls.crt"] pkPEM := secret.Data["tls.key"] @@ -263,11 +262,11 @@ var _ = framework.CertManagerDescribe("Certificate AdditionalCertificateOutputFo By("changing the values of additional output format keys, should have that value reverted to the correct value") secret.Data["tls-combined.pem"] = []byte("random-1") secret.Data["key.der"] = []byte("random-2") - secret, err = f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Update(context.Background(), secret, metav1.UpdateOptions{}) + secret, err = f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Update(testingCtx, secret, metav1.UpdateOptions{}) Expect(err).NotTo(HaveOccurred()) By("wait for those values to be reverted on the Secret") Eventually(func() map[string][]byte { - secret, err = f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Get(context.Background(), secretName, metav1.GetOptions{}) + secret, err = f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Get(testingCtx, secretName, metav1.GetOptions{}) Expect(err).NotTo(HaveOccurred()) return secret.Data }).WithTimeout(30 * time.Second).WithPolling(time.Second).Should(MatchAllKeys(Keys{ @@ -279,11 +278,11 @@ var _ = framework.CertManagerDescribe("Certificate AdditionalCertificateOutputFo })) }) - It("renewing a Certificate should have output format values reflect the new certificate and private key", func() { - crtName, crt := createCertificate(f, []cmapi.CertificateAdditionalOutputFormat{{Type: "CombinedPEM"}, {Type: "DER"}}) + It("renewing a Certificate should have output format values reflect the new certificate and private key", func(testingCtx context.Context) { + crtName, crt := createCertificate(testingCtx, f, []cmapi.CertificateAdditionalOutputFormat{{Type: "CombinedPEM"}, {Type: "DER"}}) By("ensure Secret has only expected keys") - secret, err := f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Get(context.Background(), secretName, metav1.GetOptions{}) + secret, err := f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Get(testingCtx, secretName, metav1.GetOptions{}) Expect(err).NotTo(HaveOccurred()) crtPEM := secret.Data["tls.crt"] pkPEM := secret.Data["tls.key"] @@ -300,21 +299,21 @@ var _ = framework.CertManagerDescribe("Certificate AdditionalCertificateOutputFo oldCrtPEM := secret.Data["tls.crt"] oldPKPEM := secret.Data["tls.key"] err = retry.RetryOnConflict(retry.DefaultRetry, func() error { - crt, err = f.CertManagerClientSet.CertmanagerV1().Certificates(f.Namespace.Name).Get(context.Background(), crtName, metav1.GetOptions{}) + crt, err = f.CertManagerClientSet.CertmanagerV1().Certificates(f.Namespace.Name).Get(testingCtx, crtName, metav1.GetOptions{}) if err != nil { return err } apiutil.SetCertificateCondition(crt, crt.Generation, cmapi.CertificateConditionIssuing, cmmeta.ConditionTrue, "e2e-testing", "Renewing for AdditionalOutputFormat e2e test") - crt, err = f.CertManagerClientSet.CertmanagerV1().Certificates(f.Namespace.Name).UpdateStatus(context.Background(), crt, metav1.UpdateOptions{}) + crt, err = f.CertManagerClientSet.CertmanagerV1().Certificates(f.Namespace.Name).UpdateStatus(testingCtx, crt, metav1.UpdateOptions{}) return err }) Expect(err).NotTo(HaveOccurred()) - crt, err = f.Helper().WaitForCertificateReadyAndDoneIssuing(crt, time.Minute*2) + crt, err = f.Helper().WaitForCertificateReadyAndDoneIssuing(testingCtx, crt, time.Minute*2) Expect(err).NotTo(HaveOccurred(), "failed to wait for Certificate to become Ready") By("ensuring additional output formats reflect the new private key and certificate") - secret, err = f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Get(context.Background(), secretName, metav1.GetOptions{}) + secret, err = f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Get(testingCtx, secretName, metav1.GetOptions{}) Expect(err).NotTo(HaveOccurred()) crtPEM = secret.Data["tls.crt"] pkPEM = secret.Data["tls.key"] @@ -328,38 +327,38 @@ var _ = framework.CertManagerDescribe("Certificate AdditionalCertificateOutputFo })) }) - It("if a third party set additional output formats, they then get added to the Certificate, when they are removed again they should persist as they are still owned by a third party", func() { + It("if a third party set additional output formats, they then get added to the Certificate, when they are removed again they should persist as they are still owned by a third party", func(testingCtx context.Context) { // This e2e test requires that the ServerSideApply feature gate is enabled. - framework.RequireFeatureGate(f, utilfeature.DefaultFeatureGate, feature.ServerSideApply) + framework.RequireFeatureGate(utilfeature.DefaultFeatureGate, feature.ServerSideApply) - crtName, crt := createCertificate(f, nil) + crtName, crt := createCertificate(testingCtx, f, nil) By("add additional output formats manually to the secret") - secret, err := f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Get(context.Background(), secretName, metav1.GetOptions{}) + secret, err := f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Get(testingCtx, secretName, metav1.GetOptions{}) Expect(err).NotTo(HaveOccurred()) crtPEM := secret.Data["tls.crt"] pkPEM := secret.Data["tls.key"] block, _ := pem.Decode(pkPEM) secret.Data["tls-combined.pem"] = append(append(pkPEM, '\n'), crtPEM...) secret.Data["key.der"] = block.Bytes - secret, err = f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Update(context.Background(), secret, metav1.UpdateOptions{}) + secret, err = f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Update(testingCtx, secret, metav1.UpdateOptions{}) Expect(err).NotTo(HaveOccurred()) By("add additional output formats to Certificate") err = retry.RetryOnConflict(retry.DefaultRetry, func() error { - crt, err = f.CertManagerClientSet.CertmanagerV1().Certificates(f.Namespace.Name).Get(context.Background(), crtName, metav1.GetOptions{}) + crt, err = f.CertManagerClientSet.CertmanagerV1().Certificates(f.Namespace.Name).Get(testingCtx, crtName, metav1.GetOptions{}) if err != nil { return err } crt.Spec.AdditionalOutputFormats = []cmapi.CertificateAdditionalOutputFormat{{Type: "CombinedPEM"}, {Type: "DER"}} - crt, err = f.CertManagerClientSet.CertmanagerV1().Certificates(f.Namespace.Name).Update(context.Background(), crt, metav1.UpdateOptions{}) + crt, err = f.CertManagerClientSet.CertmanagerV1().Certificates(f.Namespace.Name).Update(testingCtx, crt, metav1.UpdateOptions{}) return err }) Expect(err).NotTo(HaveOccurred()) By("wait for cert-manager to assigned ownership to the additional output format fields") Eventually(func() bool { - secret, err = f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Get(context.Background(), secretName, metav1.GetOptions{}) + secret, err = f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Get(testingCtx, secretName, metav1.GetOptions{}) Expect(err).NotTo(HaveOccurred()) for _, managedField := range secret.ManagedFields { // The field manager of the issuing controller is currently @@ -368,13 +367,13 @@ var _ = framework.CertManagerDescribe("Certificate AdditionalCertificateOutputFo continue } var fieldset fieldpath.Set - Expect(fieldset.FromJSON(bytes.NewReader(managedField.FieldsV1.Raw))) + Expect(fieldset.FromJSON(bytes.NewReader(managedField.FieldsV1.Raw))).NotTo(HaveOccurred()) if fieldset.Has(fieldpath.Path{ - {FieldName: pointer.String("data")}, - {FieldName: pointer.String("tls-combined.pem")}, + {FieldName: ptr.To("data")}, + {FieldName: ptr.To("tls-combined.pem")}, }) && fieldset.Has(fieldpath.Path{ - {FieldName: pointer.String("data")}, - {FieldName: pointer.String("key.der")}, + {FieldName: ptr.To("data")}, + {FieldName: ptr.To("key.der")}, }) { return true } @@ -384,19 +383,19 @@ var _ = framework.CertManagerDescribe("Certificate AdditionalCertificateOutputFo By("remove additional output formats from Certificate") err = retry.RetryOnConflict(retry.DefaultRetry, func() error { - crt, err = f.CertManagerClientSet.CertmanagerV1().Certificates(f.Namespace.Name).Get(context.Background(), crtName, metav1.GetOptions{}) + crt, err = f.CertManagerClientSet.CertmanagerV1().Certificates(f.Namespace.Name).Get(testingCtx, crtName, metav1.GetOptions{}) if err != nil { return err } crt.Spec.AdditionalOutputFormats = nil - crt, err = f.CertManagerClientSet.CertmanagerV1().Certificates(f.Namespace.Name).Update(context.Background(), crt, metav1.UpdateOptions{}) + crt, err = f.CertManagerClientSet.CertmanagerV1().Certificates(f.Namespace.Name).Update(testingCtx, crt, metav1.UpdateOptions{}) return err }) Expect(err).NotTo(HaveOccurred()) By("observe secret maintain the additional output format keys and values since they are owned by a third party") Consistently(func() map[string][]byte { - secret, err = f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Get(context.Background(), secretName, metav1.GetOptions{}) + secret, err = f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Get(testingCtx, secretName, metav1.GetOptions{}) Expect(err).NotTo(HaveOccurred()) return secret.Data }).WithTimeout(5 * time.Second).WithPolling(time.Second).Should(MatchAllKeys(Keys{ diff --git a/test/e2e/suite/certificates/duplicatesecretname.go b/test/e2e/suite/certificates/duplicatesecretname.go new file mode 100644 index 00000000000..c2d62a05369 --- /dev/null +++ b/test/e2e/suite/certificates/duplicatesecretname.go @@ -0,0 +1,174 @@ +/* +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. +*/ + +package certificates + +import ( + "context" + "fmt" + "time" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/util/retry" + + "github.com/cert-manager/cert-manager/e2e-tests/framework" + e2eutil "github.com/cert-manager/cert-manager/e2e-tests/util" + apiutil "github.com/cert-manager/cert-manager/pkg/api/util" + cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" + cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" + "github.com/cert-manager/cert-manager/pkg/util/predicate" + "github.com/cert-manager/cert-manager/test/unit/gen" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +// This test ensures that Certificates in the same Namespace who share the same +// `spec.secretName` value are put into a blocking state. This state prevents +// CertificateRequest creation runaway. +var _ = framework.CertManagerDescribe("Certificate Duplicate Secret Name", func() { + const ( + issuerName = "certificate-duplicate-secret-name" + secretName = "test-duplicate-secret-name" + ) + + f := framework.NewDefaultFramework("certificates-duplicate-secret-name") + + createCertificate := func(testingCtx context.Context, f *framework.Framework, pk cmapi.PrivateKeyAlgorithm) string { + crt := &cmapi.Certificate{ + ObjectMeta: metav1.ObjectMeta{ + GenerateName: "test-duplicate-secret-name-", + Namespace: f.Namespace.Name, + }, + Spec: cmapi.CertificateSpec{ + CommonName: "test", + SecretName: secretName, + PrivateKey: &cmapi.CertificatePrivateKey{ + Algorithm: pk, + RotationPolicy: cmapi.RotationPolicyAlways, + }, + IssuerRef: cmmeta.IssuerReference{ + Name: issuerName, + Kind: "Issuer", + Group: "cert-manager.io", + }, + }, + } + + By("creating Certificate") + + crt, err := f.CertManagerClientSet.CertmanagerV1().Certificates(f.Namespace.Name).Create(testingCtx, crt, metav1.CreateOptions{}) + Expect(err).NotTo(HaveOccurred()) + + return crt.Name + } + + BeforeEach(func(testingCtx context.Context) { + By("creating a self-signing issuer") + issuer := gen.Issuer("self-signed", + gen.SetIssuerNamespace(f.Namespace.Name), + gen.SetIssuerSelfSigned(cmapi.SelfSignedIssuer{})) + Expect(f.CRClient.Create(testingCtx, issuer)).To(Succeed()) + + By("Waiting for Issuer to become Ready") + err := e2eutil.WaitForIssuerCondition(testingCtx, f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name), + "self-signed", cmapi.IssuerCondition{Type: cmapi.IssuerConditionReady, Status: cmmeta.ConditionTrue}) + Expect(err).NotTo(HaveOccurred()) + + // Here we use a CA issuer because if we didn't, we would often get a + // CertificateRequest failure because private keys do not match on + // duplicate target Secret names. This failure fails Certificates. + // This failure is not the point of this test, and the InConflict + // condition isn't attempting to catch this case. + By("creating a CA Issuer") + crt := gen.Certificate(issuerName, + gen.SetCertificateNamespace(f.Namespace.Name), + gen.SetCertificateIssuer(cmmeta.IssuerReference{Name: "self-signed"}), + gen.SetCertificateDNSNames("example.com"), + gen.SetCertificateIsCA(true), + gen.SetCertificateSecretName("ca-issuer"), + ) + Expect(f.CRClient.Create(testingCtx, crt)).To(Succeed()) + _, err = f.Helper().WaitForCertificateReadyAndDoneIssuing(testingCtx, crt, time.Second*10) + Expect(err).NotTo(HaveOccurred()) + issuer = gen.Issuer(issuerName, + gen.SetIssuerNamespace(f.Namespace.Name), + gen.SetIssuerCA(cmapi.CAIssuer{SecretName: "ca-issuer"}), + ) + Expect(f.CRClient.Create(testingCtx, issuer)).To(Succeed()) + err = e2eutil.WaitForIssuerCondition(testingCtx, f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name), + issuerName, cmapi.IssuerCondition{Type: cmapi.IssuerConditionReady, Status: cmmeta.ConditionTrue}) + Expect(err).NotTo(HaveOccurred()) + }) + + AfterEach(func(testingCtx context.Context) { + Expect(f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Delete(testingCtx, issuerName, metav1.DeleteOptions{})).NotTo(HaveOccurred()) + }) + + It("if Certificates are created in the same Namespace with the same spec.secretName, they should block issuance, and never create more than one request.", func(testingCtx context.Context) { + crt1, crt2, crt3 := createCertificate(testingCtx, f, cmapi.ECDSAKeyAlgorithm), createCertificate(testingCtx, f, cmapi.RSAKeyAlgorithm), createCertificate(testingCtx, f, cmapi.ECDSAKeyAlgorithm) + + for _, crtName := range []string{crt1, crt2, crt3} { + crt, err := f.CertManagerClientSet.CertmanagerV1().Certificates(f.Namespace.Name).Get(testingCtx, crtName, metav1.GetOptions{}) + Expect(err).NotTo(HaveOccurred()) + Consistently(func() int { + reqs, err := f.CertManagerClientSet.CertmanagerV1().CertificateRequests(f.Namespace.Name).List(testingCtx, metav1.ListOptions{}) + Expect(err).NotTo(HaveOccurred()) + var ownedReqs int + for _, req := range reqs.Items { + if predicate.ResourceOwnedBy(crt)(&req) /* #nosec G601 -- False positive. See https://github.com/golang/go/discussions/56010 */ { + ownedReqs++ + } + } + return ownedReqs + }, "3s", "500ms").Should(Or(Equal(0), Equal(1)), "expected only zero or single request to be created") + } + + Consistently(func() bool { + numberOfReadyCerts := 0 + + for _, crtName := range []string{crt1, crt2, crt3} { + crt, err := f.CertManagerClientSet.CertmanagerV1().Certificates(f.Namespace.Name).Get(testingCtx, crtName, metav1.GetOptions{}) + Expect(err).NotTo(HaveOccurred()) + + cond := apiutil.GetCertificateCondition(crt, cmapi.CertificateConditionReady) + if cond != nil && cond.Status == cmmeta.ConditionTrue { + numberOfReadyCerts += 1 + } + } + + return numberOfReadyCerts <= 1 // only one Certificate should be Ready + }, "10s", "1s").Should(BeTrue(), "expected at most one Certificate to be Ready") + + By("expect all Certificates to be successfully be issued once all SecretNames are unique") + for i, crtName := range []string{crt1, crt2, crt3} { + Expect(retry.RetryOnConflict(retry.DefaultRetry, func() error { + crt, err := f.CertManagerClientSet.CertmanagerV1().Certificates(f.Namespace.Name).Get(testingCtx, crtName, metav1.GetOptions{}) + Expect(err).NotTo(HaveOccurred()) + crt.Spec.SecretName = fmt.Sprintf("unique-secret-%d", i) + _, err = f.CertManagerClientSet.CertmanagerV1().Certificates(f.Namespace.Name).Update(testingCtx, crt, metav1.UpdateOptions{}) + return err + })).NotTo(HaveOccurred()) + } + + for _, crtName := range []string{crt1, crt2, crt3} { + crt, err := f.CertManagerClientSet.CertmanagerV1().Certificates(f.Namespace.Name).Get(testingCtx, crtName, metav1.GetOptions{}) + Expect(err).NotTo(HaveOccurred()) + _, err = f.Helper().WaitForCertificateReadyAndDoneIssuing(testingCtx, crt, time.Second*10) + Expect(err).NotTo(HaveOccurred(), "failed to wait for Certificate to become Ready") + } + }) +}) diff --git a/test/e2e/suite/certificates/foregrounddeletion.go b/test/e2e/suite/certificates/foregrounddeletion.go new file mode 100644 index 00000000000..0334bb1f160 --- /dev/null +++ b/test/e2e/suite/certificates/foregrounddeletion.go @@ -0,0 +1,169 @@ +/* +Copyright 2020 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. +*/ + +package certificates + +import ( + "context" + "time" + + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/utils/ptr" + + "github.com/cert-manager/cert-manager/e2e-tests/framework" + e2eutil "github.com/cert-manager/cert-manager/e2e-tests/util" + cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" + cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" + "github.com/cert-manager/cert-manager/pkg/util/predicate" + "github.com/cert-manager/cert-manager/test/unit/gen" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +// This test ensures that if a Certificates using --cascade=foreground then it +// does not re-create the CertificateRequest and Secret objects +var _ = framework.CertManagerDescribe("Certificate Foreground Deletion", func() { + const ( + issuerName = "certificate-foreground-deletion" + secretName = "test-foreground-deletion" + finalizer = "e2e.cert-manager.io/foreground-deletion" + ) + + f := framework.NewDefaultFramework("certificates-foreground-deletion") + + var crt *cmapi.Certificate + + BeforeEach(func(testingCtx context.Context) { + By("creating a self-signing issuer") + issuer := gen.Issuer(issuerName+"-self-signed", + gen.SetIssuerNamespace(f.Namespace.Name), + gen.SetIssuerSelfSigned(cmapi.SelfSignedIssuer{})) + Expect(f.CRClient.Create(testingCtx, issuer)).To(Succeed()) + + By("waiting for self-signing Issuer to become Ready") + err := e2eutil.WaitForIssuerCondition(testingCtx, f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name), + issuerName+"-self-signed", cmapi.IssuerCondition{Type: cmapi.IssuerConditionReady, Status: cmmeta.ConditionTrue}) + Expect(err).NotTo(HaveOccurred()) + + By("creating a CA Certificate") + ca := gen.Certificate(issuerName, + gen.SetCertificateNamespace(f.Namespace.Name), + gen.SetCertificateIssuer(cmmeta.IssuerReference{Name: issuerName + "-self-signed"}), + gen.SetCertificateDNSNames("example.com"), + gen.SetCertificateIsCA(true), + gen.SetCertificateSecretName("ca-issuer"), + ) + Expect(f.CRClient.Create(testingCtx, ca)).To(Succeed()) + _, err = f.Helper().WaitForCertificateReadyAndDoneIssuing(testingCtx, ca, time.Second*10) + Expect(err).NotTo(HaveOccurred()) + + By("creating a CA issuer") + issuer = gen.Issuer(issuerName, + gen.SetIssuerNamespace(f.Namespace.Name), + gen.SetIssuerCA(cmapi.CAIssuer{SecretName: "ca-issuer"}), + ) + Expect(f.CRClient.Create(testingCtx, issuer)).To(Succeed()) + + By("waiting for CA Issuer to become Ready") + err = e2eutil.WaitForIssuerCondition(testingCtx, f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name), + issuerName, cmapi.IssuerCondition{Type: cmapi.IssuerConditionReady, Status: cmmeta.ConditionTrue}) + Expect(err).NotTo(HaveOccurred()) + + crt = &cmapi.Certificate{ + ObjectMeta: metav1.ObjectMeta{ + GenerateName: "test-foreground-deletion-", + Namespace: f.Namespace.Name, + }, + Spec: cmapi.CertificateSpec{ + CommonName: "test", + SecretName: secretName, + PrivateKey: &cmapi.CertificatePrivateKey{RotationPolicy: cmapi.RotationPolicyAlways}, + IssuerRef: cmmeta.IssuerReference{ + Name: issuerName, Kind: "Issuer", Group: "cert-manager.io", + }, + }, + } + + By("creating a Certificate") + crt, err = f.CertManagerClientSet.CertmanagerV1().Certificates(f.Namespace.Name).Create(testingCtx, crt, metav1.CreateOptions{}) + Expect(err).NotTo(HaveOccurred()) + crt, err = f.Helper().WaitForCertificateReadyAndDoneIssuing(testingCtx, crt, time.Minute*2) + Expect(err).NotTo(HaveOccurred(), "failed to wait for Certificate to become Ready") + + By("adding a finalizer to the Certificate") + Eventually(e2eutil.AddFinalizer). + WithContext(testingCtx). + WithArguments(f.CRClient, crt, finalizer). + Should(Succeed()) + + By("performing a foreground deletion of the Certificate") + Expect(f.CertManagerClientSet.CertmanagerV1().Certificates(f.Namespace.Name).Delete(testingCtx, crt.Name, metav1.DeleteOptions{PropagationPolicy: ptr.To(metav1.DeletePropagationForeground)})).ToNot(HaveOccurred(), "failed to delete the Certificate") + + // Deleting the secret would normally trigger a new issuance, creating a certificate request + By("deleting the Certificate secret") + Expect(f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Delete(testingCtx, crt.Spec.SecretName, metav1.DeleteOptions{})).ToNot(HaveOccurred(), "failed to delete the Secret") + }) + + AfterEach(func(testingCtx context.Context) { + By("deleting the self-signed issuer") + err := f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Delete(testingCtx, issuerName+"-self-signed", metav1.DeleteOptions{}) + Expect(err).NotTo(HaveOccurred()) + + By("deleting the CA issuer") + err = f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Delete(testingCtx, issuerName, metav1.DeleteOptions{}) + Expect(err).NotTo(HaveOccurred()) + + By("removing the finalizer from the Certificate") + Eventually(e2eutil.RemoveFinalizer). + WithContext(testingCtx). + WithArguments(f.CRClient, crt, finalizer). + Should(Succeed()) + }) + + It("should not create a CertificateRequest while the Certificate is being deleted", func(testingCtx context.Context) { + By("ensuring all CertificateRequest objects are deleted") + Eventually(e2eutil.ListMatchingPredicates[cmapi.CertificateRequest, cmapi.CertificateRequestList]). + WithContext(testingCtx). + WithArguments( + f.CRClient, + predicate.ResourceOwnedBy(crt), + ). + WithTimeout(time.Second * 10). + MustPassRepeatedly(10). + Should(BeEmpty()) + }) + + It("should not create a Secret while the Certificate is being deleted", func(testingCtx context.Context) { + By("ensuring all Secret objects are deleted") + Eventually(e2eutil.ListMatchingPredicates[corev1.Secret, corev1.SecretList]). + WithContext(testingCtx). + WithArguments( + f.CRClient, + func(obj runtime.Object) bool { + secret := obj.(*corev1.Secret) + return secret.Annotations != nil && + secret.Annotations["cert-manager.io/certificate-name"] == crt.Name && + secret.Namespace == crt.Namespace + }, + ). + WithTimeout(time.Second * 10). + MustPassRepeatedly(10). + Should(BeEmpty()) + }) +}) diff --git a/test/e2e/suite/certificates/literalsubjectrdns.go b/test/e2e/suite/certificates/literalsubjectrdns.go new file mode 100644 index 00000000000..e3312c0a5a3 --- /dev/null +++ b/test/e2e/suite/certificates/literalsubjectrdns.go @@ -0,0 +1,125 @@ +/* +Copyright 2020 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. +*/ + +package certificates + +import ( + "context" + "crypto/x509" + "crypto/x509/pkix" + "encoding/asn1" + "encoding/pem" + "time" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "github.com/cert-manager/cert-manager/e2e-tests/framework" + e2eutil "github.com/cert-manager/cert-manager/e2e-tests/util" + "github.com/cert-manager/cert-manager/internal/webhook/feature" + cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" + cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" + utilfeature "github.com/cert-manager/cert-manager/pkg/util/feature" + "github.com/cert-manager/cert-manager/test/unit/gen" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +var _ = framework.CertManagerDescribe("literalsubject rdn parsing", func() { + const ( + testName = "test-literalsubject-rdn-parsing" + issuerName = "certificate-literalsubject-rdns" + secretName = testName + ) + + f := framework.NewDefaultFramework("certificate-literalsubject-rdns") + + createCertificate := func(testingCtx context.Context, f *framework.Framework, literalSubject string) (*cmapi.Certificate, error) { + framework.RequireFeatureGate(utilfeature.DefaultFeatureGate, feature.LiteralCertificateSubject) + crt := &cmapi.Certificate{ + ObjectMeta: metav1.ObjectMeta{ + GenerateName: testName + "-", + Namespace: f.Namespace.Name, + }, + Spec: cmapi.CertificateSpec{ + SecretName: secretName, + PrivateKey: &cmapi.CertificatePrivateKey{RotationPolicy: cmapi.RotationPolicyAlways}, + IssuerRef: cmmeta.IssuerReference{ + Name: issuerName, Kind: "Issuer", Group: "cert-manager.io", + }, + LiteralSubject: literalSubject, + }, + } + + By("creating Certificate with LiteralSubject") + return f.CertManagerClientSet.CertmanagerV1().Certificates(f.Namespace.Name).Create(testingCtx, crt, metav1.CreateOptions{}) + } + + BeforeEach(func(testingCtx context.Context) { + By("creating a self-signing issuer") + issuer := gen.Issuer(issuerName, + gen.SetIssuerNamespace(f.Namespace.Name), + gen.SetIssuerSelfSigned(cmapi.SelfSignedIssuer{})) + Expect(f.CRClient.Create(testingCtx, issuer)).To(Succeed()) + + By("Waiting for Issuer to become Ready") + err := e2eutil.WaitForIssuerCondition(testingCtx, f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name), + issuerName, cmapi.IssuerCondition{Type: cmapi.IssuerConditionReady, Status: cmmeta.ConditionTrue}) + Expect(err).NotTo(HaveOccurred()) + }) + + AfterEach(func(testingCtx context.Context) { + Expect(f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Delete(testingCtx, issuerName, metav1.DeleteOptions{})).NotTo(HaveOccurred()) + }) + + // The parsed RDNSequence should be in REVERSE order as RDNs in String format are expected to be written in reverse order. + // Meaning, a string of "CN=Foo,OU=Bar,O=Baz" actually should have "O=Baz" as the first element in the RDNSequence. + It("Should create a certificate with all the supplied RDNs as subject names in reverse string order, including DC and UID", func(testingCtx context.Context) { + crt, err := createCertificate(testingCtx, f, "CN=James \\\"Jim\\\" Smith\\, III,UID=jamessmith,SERIALNUMBER=1234512345,OU=Admins,OU=IT,DC=net,DC=dc,O=Acme,STREET=La Rambla,L=Barcelona,C=Spain") + Expect(err).NotTo(HaveOccurred()) + _, err = f.Helper().WaitForCertificateReadyAndDoneIssuing(testingCtx, crt, time.Minute*2) + Expect(err).NotTo(HaveOccurred(), "failed to wait for Certificate to become Ready") + + secret, err := f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Get(testingCtx, secretName, metav1.GetOptions{}) + Expect(err).NotTo(HaveOccurred()) + Expect(secret.Data).To(HaveKey("tls.crt")) + crtPEM := secret.Data["tls.crt"] + pemBlock, _ := pem.Decode(crtPEM) + cert, err := x509.ParseCertificate(pemBlock.Bytes) + Expect(err).NotTo(HaveOccurred()) + + Expect(cert.Subject.Names).To(Equal([]pkix.AttributeTypeAndValue{ + {Type: asn1.ObjectIdentifier{2, 5, 4, 6}, Value: "Spain"}, + {Type: asn1.ObjectIdentifier{2, 5, 4, 7}, Value: "Barcelona"}, + {Type: asn1.ObjectIdentifier{2, 5, 4, 9}, Value: "La Rambla"}, + {Type: asn1.ObjectIdentifier{2, 5, 4, 10}, Value: "Acme"}, + {Type: asn1.ObjectIdentifier{0, 9, 2342, 19200300, 100, 1, 25}, Value: "dc"}, + {Type: asn1.ObjectIdentifier{0, 9, 2342, 19200300, 100, 1, 25}, Value: "net"}, + {Type: asn1.ObjectIdentifier{2, 5, 4, 11}, Value: "IT"}, + {Type: asn1.ObjectIdentifier{2, 5, 4, 11}, Value: "Admins"}, + {Type: asn1.ObjectIdentifier{2, 5, 4, 5}, Value: "1234512345"}, + {Type: asn1.ObjectIdentifier{0, 9, 2342, 19200300, 100, 1, 1}, Value: "jamessmith"}, + {Type: asn1.ObjectIdentifier{2, 5, 4, 3}, Value: "James \"Jim\" Smith, III"}, + })) + }) + + It("Should not allow unknown RDN component", func(testingCtx context.Context) { + _, err := createCertificate(testingCtx, f, "UNKNOWN=blah") + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("Literal subject contains unrecognized key with value [blah]")) + }) + +}) diff --git a/test/e2e/suite/certificates/othernamesan.go b/test/e2e/suite/certificates/othernamesan.go new file mode 100644 index 00000000000..f4ed1781315 --- /dev/null +++ b/test/e2e/suite/certificates/othernamesan.go @@ -0,0 +1,173 @@ +/* +Copyright 2020 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. +*/ + +package certificates + +import ( + "context" + "crypto/x509" + "encoding/pem" + "time" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "github.com/cert-manager/cert-manager/e2e-tests/framework" + e2eutil "github.com/cert-manager/cert-manager/e2e-tests/util" + "github.com/cert-manager/cert-manager/internal/webhook/feature" + cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" + cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" + utilfeature "github.com/cert-manager/cert-manager/pkg/util/feature" + "github.com/cert-manager/cert-manager/test/unit/gen" + + . "github.com/cert-manager/cert-manager/e2e-tests/framework/matcher" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +var _ = framework.CertManagerDescribe("other name san processing", func() { + + const ( + testName = "test-other-name-san-processing" + issuerName = "certificate-other-name-san-processing" + secretName = testName + ) + + var ( + emailAddresses = []string{"email@domain.test"} + ) + + f := framework.NewDefaultFramework("certificate-other-name-san-processing") + + createCertificate := func(testingCtx context.Context, f *framework.Framework, OtherNames []cmapi.OtherName) (*cmapi.Certificate, error) { + crt := &cmapi.Certificate{ + ObjectMeta: metav1.ObjectMeta{ + GenerateName: testName + "-", + Namespace: f.Namespace.Name, + }, + Spec: cmapi.CertificateSpec{ + SecretName: secretName, + PrivateKey: &cmapi.CertificatePrivateKey{RotationPolicy: cmapi.RotationPolicyAlways}, + IssuerRef: cmmeta.IssuerReference{ + Name: issuerName, Kind: "Issuer", Group: "cert-manager.io", + }, + OtherNames: OtherNames, + EmailAddresses: emailAddresses, + CommonName: "SOMECN", + }, + } + By("creating Certificate with OtherNames") + return f.CertManagerClientSet.CertmanagerV1().Certificates(f.Namespace.Name).Create(testingCtx, crt, metav1.CreateOptions{}) + } + + BeforeEach(func(testingCtx context.Context) { + framework.RequireFeatureGate(utilfeature.DefaultFeatureGate, feature.OtherNames) + + By("creating a self-signing issuer") + issuer := gen.Issuer(issuerName, + gen.SetIssuerNamespace(f.Namespace.Name), + gen.SetIssuerSelfSigned(cmapi.SelfSignedIssuer{})) + Expect(f.CRClient.Create(testingCtx, issuer)).To(Succeed()) + + By("Waiting for Issuer to become Ready") + err := e2eutil.WaitForIssuerCondition(testingCtx, f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name), + issuerName, cmapi.IssuerCondition{Type: cmapi.IssuerConditionReady, Status: cmmeta.ConditionTrue}) + Expect(err).NotTo(HaveOccurred()) + }) + + AfterEach(func(testingCtx context.Context) { + Expect(f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Delete(testingCtx, issuerName, metav1.DeleteOptions{})).NotTo(HaveOccurred()) + }) + + It("Should create a certificate with the supplied otherName SAN value and emailAddress included", func(testingCtx context.Context) { + crt, err := createCertificate(testingCtx, f, []cmapi.OtherName{ + { + OID: "1.3.6.1.4.1.311.20.2.3", + UTF8Value: "upn@domain.test", + }, + }) + Expect(err).NotTo(HaveOccurred()) + _, err = f.Helper().WaitForCertificateReadyAndDoneIssuing(testingCtx, crt, time.Minute*2) + Expect(err).NotTo(HaveOccurred(), "failed to wait for Certificate to become Ready") + + secret, err := f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Get(testingCtx, secretName, metav1.GetOptions{}) + Expect(err).ToNot(HaveOccurred()) + Expect(secret.Data).To(HaveKey("tls.crt")) + crtPEM := secret.Data["tls.crt"] + pemBlock, _ := pem.Decode(crtPEM) + cert, err := x509.ParseCertificate(pemBlock.Bytes) + Expect(err).ToNot(HaveOccurred()) + + By("Including the appropriate GeneralNames ( RFC822 email Address and OtherName) in generated Certificate") + + /* openssl req -nodes -newkey rsa:2048 -subj "/CN=someCN" \ + -addext 'subjectAltName=email:email@domain.test,otherName:msUPN;UTF8:upn@domain.test' -x509 -out server.crt + */ + expectedSanExtension := `-----BEGIN CERTIFICATE----- +MIIDRDCCAiygAwIBAgIUdotGup0k8gdZ+irmcuvLeJDm5wkwDQYJKoZIhvcNAQEL +BQAwETEPMA0GA1UEAwwGc29tZUNOMB4XDTIzMTIyMTE2NDQyOFoXDTI0MDEyMDE2 +NDQyOFowETEPMA0GA1UEAwwGc29tZUNOMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A +MIIBCgKCAQEAyIIWkA1mNi0ZpdwkGeBjmZKZD9J8D9NlpYpOTzoxRLstuJdUNOb0 +BgsRk9FWr6rzg6SdSL7NxUS9ZJc0X0P8gn7bPUVtaF7vbj2apz1W2fhx2ifmBRaT +n7ZbpO1aapzr0kiPEZKc82X4jualnFW2YMjjQMc6YuMykcaTQnpv9R4/mzM0kzal +gpKp82tnUogG7EC79cO6xubk0kgIxBFwpH+H6EPLtRY12wW5fONmw9smRgsfleIs +lMSHNuJvUMqyktb8YzAX/XCz3Idumu1UA4ZCFRNCZ019JnmaFF9McGqaC6zrPwnl +aONLw1x9tD+D9bwi6idHNbq/PmQwfs7zzQIDAQABo4GTMIGQMB0GA1UdDgQWBBRm +myeY1slW3mXcGLZs7uciGpfCQzAfBgNVHSMEGDAWgBRmmyeY1slW3mXcGLZs7uci +GpfCQzAPBgNVHRMBAf8EBTADAQH/MD0GA1UdEQQ2MDSBEWVtYWlsQGRvbWFpbi50 +ZXN0oB8GCisGAQQBgjcUAgOgEQwPdXBuQGRvbWFpbi50ZXN0MA0GCSqGSIb3DQEB +CwUAA4IBAQCgpAMWkSqA0jV+Bd6UEw7phROTkan5IWTXqYT56RI3AS+LZ83cVglS +FP0UKUssQjLKmubcJWo84T83woxfZVSj15x8X+ohzSvSK8wIe2uobKKNl8F0yW8X +3267YrKGnY6eDqsmNZT8P1isSyYF0PUP3EIDlO6D1YICMawvZItnE+tf9QR+5IIH +3dEzwc2wJsUVYLQ6fgZ4KMfY+fMThY7EDQPsR2M7YFW3p4+3GPQMGBGCOQZysuVh +4uvQbrc9rUWzLMmmJrbb2/xwMm1iCoJfRyLKOGqQV8O6NfnYz5n0/vYzXUCvEbfl +YH0ROM05IRf2nOI6KInaiz4POk6JvdTb +-----END CERTIFICATE----- +` + + Expect(cert.Extensions).To(HaveSameSANsAs(expectedSanExtension)) + }) + + It("Should error if a certificate is supplied with an `otherName` containing an invalid oid value", func(testingCtx context.Context) { + _, err := createCertificate(testingCtx, f, []cmapi.OtherName{ + { + OID: "BAD_OID", + UTF8Value: "userprincipal@domain.com", + }, + { + OID: "1.2.840.113556.1.4.221", // this is the legacy sAMAccountName + UTF8Value: "user@example.org", + }, + }) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("admission webhook \"webhook.cert-manager.io\" denied the request: spec.otherNames[0].oid: Invalid value: \"BAD_OID\": oid syntax invalid")) + + }) + + It("Should error if a certificate is supplied with an `otherName` without a UTF8 value", func(testingCtx context.Context) { + _, err := createCertificate(testingCtx, f, []cmapi.OtherName{ + { + OID: "1.3.6.1.4.1.311.20.2.3", + }, + { + OID: "1.2.840.113556.1.4.221", // this is the legacy sAMAccountName + UTF8Value: "user@example.org", + }, + }) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("admission webhook \"webhook.cert-manager.io\" denied the request: spec.otherNames[0].utf8Value: Required value: must be set to a valid non-empty UTF8 string")) + + }) +}) diff --git a/test/e2e/suite/certificates/secrettemplate.go b/test/e2e/suite/certificates/secrettemplate.go index 64c3db5f654..fca4dbcfd92 100644 --- a/test/e2e/suite/certificates/secrettemplate.go +++ b/test/e2e/suite/certificates/secrettemplate.go @@ -22,20 +22,21 @@ import ( "strings" "time" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + applycorev1 "k8s.io/client-go/applyconfigurations/core/v1" "k8s.io/client-go/util/retry" - "k8s.io/utils/pointer" - "sigs.k8s.io/structured-merge-diff/v4/fieldpath" + "k8s.io/utils/ptr" + "sigs.k8s.io/structured-merge-diff/v6/fieldpath" + "github.com/cert-manager/cert-manager/e2e-tests/framework" + e2eutil "github.com/cert-manager/cert-manager/e2e-tests/util" cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" - "github.com/cert-manager/cert-manager/test/e2e/framework" - e2eutil "github.com/cert-manager/cert-manager/test/e2e/util" "github.com/cert-manager/cert-manager/test/unit/gen" - applycorev1 "k8s.io/client-go/applyconfigurations/core/v1" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" ) // This test ensures that the Certificates SecretTemplate is reflected on the @@ -48,7 +49,7 @@ var _ = framework.CertManagerDescribe("Certificate SecretTemplate", func() { f := framework.NewDefaultFramework("certificates-secret-template") - createCertificate := func(f *framework.Framework, secretTemplate *cmapi.CertificateSecretTemplate) string { + createCertificate := func(testingCtx context.Context, f *framework.Framework, secretTemplate *cmapi.CertificateSecretTemplate) string { crt := &cmapi.Certificate{ ObjectMeta: metav1.ObjectMeta{ GenerateName: "test-secret-template-", @@ -57,7 +58,7 @@ var _ = framework.CertManagerDescribe("Certificate SecretTemplate", func() { Spec: cmapi.CertificateSpec{ CommonName: "test", SecretName: secretName, - IssuerRef: cmmeta.ObjectReference{ + IssuerRef: cmmeta.IssuerReference{ Name: issuerName, Kind: "Issuer", Group: "cert-manager.io", @@ -68,36 +69,36 @@ var _ = framework.CertManagerDescribe("Certificate SecretTemplate", func() { By("creating Certificate with SecretTemplate") - crt, err := f.CertManagerClientSet.CertmanagerV1().Certificates(f.Namespace.Name).Create(context.Background(), crt, metav1.CreateOptions{}) + crt, err := f.CertManagerClientSet.CertmanagerV1().Certificates(f.Namespace.Name).Create(testingCtx, crt, metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred()) - crt, err = f.Helper().WaitForCertificateReadyAndDoneIssuing(crt, time.Minute*2) + crt, err = f.Helper().WaitForCertificateReadyAndDoneIssuing(testingCtx, crt, time.Minute*2) Expect(err).NotTo(HaveOccurred(), "failed to wait for Certificate to become Ready") return crt.Name } - BeforeEach(func() { + BeforeEach(func(testingCtx context.Context) { By("creating a self-signing issuer") issuer := gen.Issuer(issuerName, gen.SetIssuerNamespace(f.Namespace.Name), gen.SetIssuerSelfSigned(cmapi.SelfSignedIssuer{})) - Expect(f.CRClient.Create(context.Background(), issuer)).To(Succeed()) + Expect(f.CRClient.Create(testingCtx, issuer)).To(Succeed()) By("Waiting for Issuer to become Ready") - err := e2eutil.WaitForIssuerCondition(f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name), + err := e2eutil.WaitForIssuerCondition(testingCtx, f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name), issuerName, cmapi.IssuerCondition{Type: cmapi.IssuerConditionReady, Status: cmmeta.ConditionTrue}) Expect(err).NotTo(HaveOccurred()) }) - AfterEach(func() { - Expect(f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Delete(context.Background(), issuerName, metav1.DeleteOptions{})).NotTo(HaveOccurred()) + AfterEach(func(testingCtx context.Context) { + Expect(f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Delete(testingCtx, issuerName, metav1.DeleteOptions{})).NotTo(HaveOccurred()) }) - It("should not remove Annotations and Labels which have been added by a third party and not present in the SecretTemplate", func() { - createCertificate(f, &cmapi.CertificateSecretTemplate{Annotations: map[string]string{"foo": "bar"}, Labels: map[string]string{"abc": "123"}}) + It("should not remove Annotations and Labels which have been added by a third party and not present in the SecretTemplate", func(testingCtx context.Context) { + createCertificate(testingCtx, f, &cmapi.CertificateSecretTemplate{Annotations: map[string]string{"foo": "bar"}, Labels: map[string]string{"abc": "123"}}) - secret, err := f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Get(context.Background(), secretName, metav1.GetOptions{}) + secret, err := f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Get(testingCtx, secretName, metav1.GetOptions{}) Expect(err).NotTo(HaveOccurred()) By("ensure Secret has correct Labels and Annotations with SecretTemplate") @@ -105,43 +106,43 @@ var _ = framework.CertManagerDescribe("Certificate SecretTemplate", func() { Expect(secret.Labels).To(HaveKeyWithValue("abc", "123")) By("add Annotation to Secret which should not be removed") - secret, err = f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Get(context.Background(), secretName, metav1.GetOptions{}) + secret, err = f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Get(testingCtx, secretName, metav1.GetOptions{}) Expect(err).NotTo(HaveOccurred()) secret.Annotations["random"] = "annotation" - secret, err = f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Update(context.Background(), secret, metav1.UpdateOptions{}) + secret, err = f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Update(testingCtx, secret, metav1.UpdateOptions{}) Expect(err).NotTo(HaveOccurred()) Consistently(func() map[string]string { - secret, err = f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Get(context.Background(), secretName, metav1.GetOptions{}) + secret, err = f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Get(testingCtx, secretName, metav1.GetOptions{}) Expect(err).NotTo(HaveOccurred()) return secret.Annotations }, "20s", "1s").Should(HaveKeyWithValue("foo", "bar")) Expect(secret.Annotations).To(HaveKeyWithValue("random", "annotation")) By("add Label to Secret which should not be removed") - secret, err = f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Get(context.Background(), secretName, metav1.GetOptions{}) + secret, err = f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Get(testingCtx, secretName, metav1.GetOptions{}) Expect(err).NotTo(HaveOccurred()) secret.Labels["random"] = "label" - secret, err = f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Update(context.Background(), secret, metav1.UpdateOptions{}) + secret, err = f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Update(testingCtx, secret, metav1.UpdateOptions{}) Expect(err).NotTo(HaveOccurred()) Consistently(func() map[string]string { - secret, err = f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Get(context.Background(), secretName, metav1.GetOptions{}) + secret, err = f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Get(testingCtx, secretName, metav1.GetOptions{}) Expect(err).NotTo(HaveOccurred()) return secret.Labels }, "20s", "1s").Should(HaveKeyWithValue("abc", "123")) }) - It("should add Annotations and Labels to the Secret when the Certificate's SecretTemplate is updated, then remove Annotations and Labels when removed from the SecretTemplate", func() { - crtName := createCertificate(f, &cmapi.CertificateSecretTemplate{ + It("should add Annotations and Labels to the Secret when the Certificate's SecretTemplate is updated, then remove Annotations and Labels when removed from the SecretTemplate", func(testingCtx context.Context) { + crtName := createCertificate(testingCtx, f, &cmapi.CertificateSecretTemplate{ Annotations: map[string]string{"foo": "bar", "bar": "foo"}, Labels: map[string]string{"abc": "123", "def": "456"}, }) By("ensure Secret has correct Labels and Annotations with SecretTemplate") - secret, err := f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Get(context.Background(), secretName, metav1.GetOptions{}) + secret, err := f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Get(testingCtx, secretName, metav1.GetOptions{}) Expect(err).NotTo(HaveOccurred()) Expect(secret.Annotations).To(HaveKeyWithValue("foo", "bar")) Expect(secret.Annotations).To(HaveKeyWithValue("bar", "foo")) @@ -151,7 +152,7 @@ var _ = framework.CertManagerDescribe("Certificate SecretTemplate", func() { By("adding Annotations and Labels to SecretTemplate should appear on the Secret") Expect(retry.RetryOnConflict(retry.DefaultRetry, func() error { - crt, err := f.CertManagerClientSet.CertmanagerV1().Certificates(f.Namespace.Name).Get(context.Background(), crtName, metav1.GetOptions{}) + crt, err := f.CertManagerClientSet.CertmanagerV1().Certificates(f.Namespace.Name).Get(testingCtx, crtName, metav1.GetOptions{}) if err != nil { return err } @@ -159,12 +160,12 @@ var _ = framework.CertManagerDescribe("Certificate SecretTemplate", func() { crt.Spec.SecretTemplate.Annotations["another"] = "random annotation" crt.Spec.SecretTemplate.Labels["hello"] = "world" crt.Spec.SecretTemplate.Labels["random"] = "label" - _, err = f.CertManagerClientSet.CertmanagerV1().Certificates(f.Namespace.Name).Update(context.Background(), crt, metav1.UpdateOptions{}) + _, err = f.CertManagerClientSet.CertmanagerV1().Certificates(f.Namespace.Name).Update(testingCtx, crt, metav1.UpdateOptions{}) return err })).NotTo(HaveOccurred()) Eventually(func() map[string]string { - secret, err = f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Get(context.Background(), secretName, metav1.GetOptions{}) + secret, err = f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Get(testingCtx, secretName, metav1.GetOptions{}) Expect(err).NotTo(HaveOccurred()) return secret.Annotations }, "20s", "1s").Should(HaveKeyWithValue("random", "annotation")) @@ -173,7 +174,7 @@ var _ = framework.CertManagerDescribe("Certificate SecretTemplate", func() { Expect(secret.Annotations).To(HaveKeyWithValue("another", "random annotation")) Eventually(func() map[string]string { - secret, err = f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Get(context.Background(), secretName, metav1.GetOptions{}) + secret, err = f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Get(testingCtx, secretName, metav1.GetOptions{}) Expect(err).NotTo(HaveOccurred()) return secret.Labels }, "20s", "1s").Should(HaveKeyWithValue("hello", "world")) @@ -183,7 +184,7 @@ var _ = framework.CertManagerDescribe("Certificate SecretTemplate", func() { By("removing Annotations and Labels in SecretTemplate should get removed on the Secret") Expect(retry.RetryOnConflict(retry.DefaultRetry, func() error { - crt, err := f.CertManagerClientSet.CertmanagerV1().Certificates(f.Namespace.Name).Get(context.Background(), crtName, metav1.GetOptions{}) + crt, err := f.CertManagerClientSet.CertmanagerV1().Certificates(f.Namespace.Name).Get(testingCtx, crtName, metav1.GetOptions{}) if err != nil { return err } @@ -191,12 +192,12 @@ var _ = framework.CertManagerDescribe("Certificate SecretTemplate", func() { delete(crt.Spec.SecretTemplate.Annotations, "random") delete(crt.Spec.SecretTemplate.Labels, "abc") delete(crt.Spec.SecretTemplate.Labels, "another") - _, err = f.CertManagerClientSet.CertmanagerV1().Certificates(f.Namespace.Name).Update(context.Background(), crt, metav1.UpdateOptions{}) + _, err = f.CertManagerClientSet.CertmanagerV1().Certificates(f.Namespace.Name).Update(testingCtx, crt, metav1.UpdateOptions{}) return err })).NotTo(HaveOccurred()) Eventually(func() map[string]string { - secret, err = f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Get(context.Background(), secretName, metav1.GetOptions{}) + secret, err = f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Get(testingCtx, secretName, metav1.GetOptions{}) Expect(err).NotTo(HaveOccurred()) return secret.Annotations }, "20s", "1s").ShouldNot(HaveKey("foo")) @@ -206,14 +207,14 @@ var _ = framework.CertManagerDescribe("Certificate SecretTemplate", func() { Expect(secret.Labels).ToNot(HaveKey("another")) }) - It("should update the values of keys that have been modified in the SecretTemplate", func() { - crtName := createCertificate(f, &cmapi.CertificateSecretTemplate{ + It("should update the values of keys that have been modified in the SecretTemplate", func(testingCtx context.Context) { + crtName := createCertificate(testingCtx, f, &cmapi.CertificateSecretTemplate{ Annotations: map[string]string{"foo": "bar", "bar": "foo"}, Labels: map[string]string{"abc": "123", "def": "456"}, }) By("ensure Secret has correct Labels and Annotations with SecretTemplate") - secret, err := f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Get(context.Background(), secretName, metav1.GetOptions{}) + secret, err := f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Get(testingCtx, secretName, metav1.GetOptions{}) Expect(err).NotTo(HaveOccurred()) Expect(secret.Annotations).To(HaveKeyWithValue("foo", "bar")) @@ -224,7 +225,7 @@ var _ = framework.CertManagerDescribe("Certificate SecretTemplate", func() { By("changing Annotation and Label keys on the SecretTemplate should be reflected on the Secret") Expect(retry.RetryOnConflict(retry.DefaultRetry, func() error { - crt, err := f.CertManagerClientSet.CertmanagerV1().Certificates(f.Namespace.Name).Get(context.Background(), crtName, metav1.GetOptions{}) + crt, err := f.CertManagerClientSet.CertmanagerV1().Certificates(f.Namespace.Name).Get(testingCtx, crtName, metav1.GetOptions{}) if err != nil { return err } @@ -232,12 +233,12 @@ var _ = framework.CertManagerDescribe("Certificate SecretTemplate", func() { crt.Spec.SecretTemplate.Annotations["bar"] = "not foo" crt.Spec.SecretTemplate.Labels["abc"] = "098" crt.Spec.SecretTemplate.Labels["def"] = "555" - _, err = f.CertManagerClientSet.CertmanagerV1().Certificates(f.Namespace.Name).Update(context.Background(), crt, metav1.UpdateOptions{}) + _, err = f.CertManagerClientSet.CertmanagerV1().Certificates(f.Namespace.Name).Update(testingCtx, crt, metav1.UpdateOptions{}) return err })).NotTo(HaveOccurred()) Eventually(func() map[string]string { - secret, err = f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Get(context.Background(), secretName, metav1.GetOptions{}) + secret, err = f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Get(testingCtx, secretName, metav1.GetOptions{}) Expect(err).NotTo(HaveOccurred()) return secret.Annotations }, "20s", "1s").Should(HaveKeyWithValue("foo", "123")) @@ -246,13 +247,13 @@ var _ = framework.CertManagerDescribe("Certificate SecretTemplate", func() { Expect(secret.Labels).To(HaveKeyWithValue("def", "555")) }) - It("should add cert-manager manager to existing Annotation and Labels fields which are added to SecretTemplate, should not be removed if they are removed by the third party", func() { + It("should add cert-manager manager to existing Annotation and Labels fields which are added to SecretTemplate, should not be removed if they are removed by the third party", func(testingCtx context.Context) { By("Secret Annotations and Labels should not be removed if the field still hold a field manager") - crtName := createCertificate(f, nil) + crtName := createCertificate(testingCtx, f, nil) By("add Labels and Annotations to the Secret that are not owned by cert-manager") - secret, err := f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Get(context.Background(), secretName, metav1.GetOptions{}) + secret, err := f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Get(testingCtx, secretName, metav1.GetOptions{}) Expect(err).NotTo(HaveOccurred()) if secret.Annotations == nil { @@ -266,24 +267,25 @@ var _ = framework.CertManagerDescribe("Certificate SecretTemplate", func() { secret.Labels["abc"] = "123" secret.Labels["foo"] = "bar" - secret, err = f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Update(context.Background(), secret, metav1.UpdateOptions{}) + _, err = f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Update(testingCtx, secret, metav1.UpdateOptions{}) Expect(err).NotTo(HaveOccurred()) - secret, err = f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Get(context.Background(), secretName, metav1.GetOptions{}) + secret, err = f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Get(testingCtx, secretName, metav1.GetOptions{}) Expect(err).NotTo(HaveOccurred()) Expect(secret.Annotations).To(HaveKeyWithValue("an-annotation", "bar")) Expect(secret.Annotations).To(HaveKeyWithValue("another-annotation", "def")) Expect(secret.Labels).To(HaveKeyWithValue("abc", "123")) Expect(secret.Labels).To(HaveKeyWithValue("foo", "bar")) - secret, err = f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Apply(context.Background(), + _, err = f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Apply(testingCtx, applycorev1.Secret(secret.Name, secret.Namespace). WithAnnotations(secret.Annotations). WithLabels(secret.Labels), metav1.ApplyOptions{FieldManager: "e2e-test-client"}) + Expect(err).NotTo(HaveOccurred()) By("expect those Annotations and Labels to be present on the Secret") - secret, err = f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Get(context.Background(), secretName, metav1.GetOptions{}) + secret, err = f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Get(testingCtx, secretName, metav1.GetOptions{}) Expect(err).NotTo(HaveOccurred()) Expect(secret.Annotations).To(HaveKeyWithValue("an-annotation", "bar")) Expect(secret.Annotations).To(HaveKeyWithValue("another-annotation", "def")) @@ -292,7 +294,7 @@ var _ = framework.CertManagerDescribe("Certificate SecretTemplate", func() { By("add those Annotations and Labels to the SecretTemplate of the Certificate") Expect(retry.RetryOnConflict(retry.DefaultRetry, func() error { - crt, err := f.CertManagerClientSet.CertmanagerV1().Certificates(f.Namespace.Name).Get(context.Background(), crtName, metav1.GetOptions{}) + crt, err := f.CertManagerClientSet.CertmanagerV1().Certificates(f.Namespace.Name).Get(testingCtx, crtName, metav1.GetOptions{}) if err != nil { return err } @@ -300,13 +302,13 @@ var _ = framework.CertManagerDescribe("Certificate SecretTemplate", func() { Annotations: map[string]string{"an-annotation": "bar", "another-annotation": "def"}, Labels: map[string]string{"abc": "123", "foo": "bar"}, } - _, err = f.CertManagerClientSet.CertmanagerV1().Certificates(f.Namespace.Name).Update(context.Background(), crt, metav1.UpdateOptions{}) + _, err = f.CertManagerClientSet.CertmanagerV1().Certificates(f.Namespace.Name).Update(testingCtx, crt, metav1.UpdateOptions{}) return err })).NotTo(HaveOccurred()) By("waiting for those Annotation and Labels on the Secret to contain managed fields from cert-manager") Eventually(func() bool { - secret, err = f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Get(context.Background(), secretName, metav1.GetOptions{}) + secret, err = f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Get(testingCtx, secretName, metav1.GetOptions{}) Expect(err).NotTo(HaveOccurred()) var managedLabels, managedAnnotations []string @@ -323,13 +325,13 @@ var _ = framework.CertManagerDescribe("Certificate SecretTemplate", func() { } metadata := fieldset.Children.Descend(fieldpath.PathElement{ - FieldName: pointer.String("metadata"), + FieldName: ptr.To("metadata"), }) labels := metadata.Children.Descend(fieldpath.PathElement{ - FieldName: pointer.String("labels"), + FieldName: ptr.To("labels"), }) annotations := metadata.Children.Descend(fieldpath.PathElement{ - FieldName: pointer.String("annotations"), + FieldName: ptr.To("annotations"), }) labels.Iterate(func(path fieldpath.Path) { @@ -340,10 +342,10 @@ var _ = framework.CertManagerDescribe("Certificate SecretTemplate", func() { }) } - for _, expectedAnnoation := range []string{"an-annotation", "another-annotation"} { + for _, expectedAnnotation := range []string{"an-annotation", "another-annotation"} { var found bool for _, managedAnnotation := range managedAnnotations { - if expectedAnnoation == managedAnnotation { + if expectedAnnotation == managedAnnotation { found = true break } @@ -373,14 +375,14 @@ var _ = framework.CertManagerDescribe("Certificate SecretTemplate", func() { By("removing Annotation and Labels from by third party client should not remove them as they are also managed by cert-manager") - secret, err = f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Apply(context.Background(), + secret, err = f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Apply(testingCtx, applycorev1.Secret(secret.Name, secret.Namespace). WithAnnotations(make(map[string]string)). WithLabels(make(map[string]string)), metav1.ApplyOptions{FieldManager: "e2e-test-client"}) Consistently(func() map[string]string { - secret, err = f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Get(context.Background(), secretName, metav1.GetOptions{}) + secret, err = f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Get(testingCtx, secretName, metav1.GetOptions{}) Expect(err).NotTo(HaveOccurred()) return secret.Annotations }, "20s", "1s").Should(HaveKeyWithValue("an-annotation", "bar")) @@ -389,57 +391,57 @@ var _ = framework.CertManagerDescribe("Certificate SecretTemplate", func() { Expect(secret.Labels).To(HaveKeyWithValue("foo", "bar")) }) - It("if data keys are added to the Secret, they should not be removed", func() { - createCertificate(f, &cmapi.CertificateSecretTemplate{ + It("if data keys are added to the Secret, they should not be removed", func(testingCtx context.Context) { + createCertificate(testingCtx, f, &cmapi.CertificateSecretTemplate{ Annotations: map[string]string{"abc": "123"}, Labels: map[string]string{"foo": "bar"}, }) - secret, err := f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Get(context.Background(), secretName, metav1.GetOptions{}) + secret, err := f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Get(testingCtx, secretName, metav1.GetOptions{}) Expect(err).NotTo(HaveOccurred()) secret.Data["random-key"] = []byte("hello-world") - secret, err = f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Update(context.Background(), secret, metav1.UpdateOptions{}) + secret, err = f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Update(testingCtx, secret, metav1.UpdateOptions{}) Expect(err).NotTo(HaveOccurred()) Consistently(func() map[string][]byte { - secret, err = f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Get(context.Background(), secretName, metav1.GetOptions{}) + secret, err = f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Get(testingCtx, secretName, metav1.GetOptions{}) Expect(err).NotTo(HaveOccurred()) return secret.Data }, "20s", "1s").Should(HaveKeyWithValue("random-key", []byte("hello-world"))) }) - It("if values are modified on the Certificate's SecretTemplate, than those values should be reflected on the Secret", func() { - crtName := createCertificate(f, &cmapi.CertificateSecretTemplate{ + It("if values are modified on the Certificate's SecretTemplate, than those values should be reflected on the Secret", func(testingCtx context.Context) { + crtName := createCertificate(testingCtx, f, &cmapi.CertificateSecretTemplate{ Annotations: map[string]string{"abc": "123"}, Labels: map[string]string{"foo": "bar"}, }) Expect(retry.RetryOnConflict(retry.DefaultRetry, func() error { - crt, err := f.CertManagerClientSet.CertmanagerV1().Certificates(f.Namespace.Name).Get(context.Background(), crtName, metav1.GetOptions{}) + crt, err := f.CertManagerClientSet.CertmanagerV1().Certificates(f.Namespace.Name).Get(testingCtx, crtName, metav1.GetOptions{}) if err != nil { return err } crt.Spec.SecretTemplate.Annotations["abc"] = "456" crt.Spec.SecretTemplate.Labels["foo"] = "foo" - _, err = f.CertManagerClientSet.CertmanagerV1().Certificates(f.Namespace.Name).Update(context.Background(), crt, metav1.UpdateOptions{}) + _, err = f.CertManagerClientSet.CertmanagerV1().Certificates(f.Namespace.Name).Update(testingCtx, crt, metav1.UpdateOptions{}) return err })).NotTo(HaveOccurred()) Eventually(func() map[string]string { - secret, err := f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Get(context.Background(), secretName, metav1.GetOptions{}) + secret, err := f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Get(testingCtx, secretName, metav1.GetOptions{}) Expect(err).NotTo(HaveOccurred()) return secret.Annotations }, "20s", "1s").Should(HaveKeyWithValue("abc", "456")) Eventually(func() map[string]string { - secret, err := f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Get(context.Background(), secretName, metav1.GetOptions{}) + secret, err := f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Get(testingCtx, secretName, metav1.GetOptions{}) Expect(err).NotTo(HaveOccurred()) return secret.Labels }, "20s", "1s").Should(HaveKeyWithValue("foo", "foo")) }) - It("deleting a Certificate's SecretTemplate should remove all keys it defined", func() { - crtName := createCertificate(f, &cmapi.CertificateSecretTemplate{ + It("deleting a Certificate's SecretTemplate should remove all keys it defined", func(testingCtx context.Context) { + crtName := createCertificate(testingCtx, f, &cmapi.CertificateSecretTemplate{ Annotations: map[string]string{"abc": "123", "def": "456"}, Labels: map[string]string{"foo": "bar", "label": "hello-world"}, }) @@ -449,7 +451,7 @@ var _ = framework.CertManagerDescribe("Certificate SecretTemplate", func() { err error ) Eventually(func() map[string]string { - secret, err = f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Get(context.Background(), secretName, metav1.GetOptions{}) + secret, err = f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Get(testingCtx, secretName, metav1.GetOptions{}) Expect(err).NotTo(HaveOccurred()) return secret.Annotations }, "20s", "1s").Should(HaveKeyWithValue("abc", "123")) @@ -458,17 +460,17 @@ var _ = framework.CertManagerDescribe("Certificate SecretTemplate", func() { Expect(secret.Labels).To(HaveKeyWithValue("label", "hello-world")) Expect(retry.RetryOnConflict(retry.DefaultRetry, func() error { - crt, err := f.CertManagerClientSet.CertmanagerV1().Certificates(f.Namespace.Name).Get(context.Background(), crtName, metav1.GetOptions{}) + crt, err := f.CertManagerClientSet.CertmanagerV1().Certificates(f.Namespace.Name).Get(testingCtx, crtName, metav1.GetOptions{}) if err != nil { return err } crt.Spec.SecretTemplate = nil - _, err = f.CertManagerClientSet.CertmanagerV1().Certificates(f.Namespace.Name).Update(context.Background(), crt, metav1.UpdateOptions{}) + _, err = f.CertManagerClientSet.CertmanagerV1().Certificates(f.Namespace.Name).Update(testingCtx, crt, metav1.UpdateOptions{}) return err })).NotTo(HaveOccurred()) Eventually(func() map[string]string { - secret, err = f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Get(context.Background(), secretName, metav1.GetOptions{}) + secret, err = f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Get(testingCtx, secretName, metav1.GetOptions{}) Expect(err).NotTo(HaveOccurred()) return secret.Annotations }, "20s", "1s").ShouldNot(HaveKey("abc")) diff --git a/test/e2e/suite/certificatesigningrequests/doc.go b/test/e2e/suite/certificatesigningrequests/doc.go index 3ffccb86544..9bc00553f24 100644 --- a/test/e2e/suite/certificatesigningrequests/doc.go +++ b/test/e2e/suite/certificatesigningrequests/doc.go @@ -17,5 +17,5 @@ limitations under the License. package certificatesigningrequests import ( - _ "github.com/cert-manager/cert-manager/test/e2e/suite/certificatesigningrequests/selfsigned" + _ "github.com/cert-manager/cert-manager/e2e-tests/suite/certificatesigningrequests/selfsigned" ) diff --git a/test/e2e/suite/certificatesigningrequests/selfsigned/selfsigned.go b/test/e2e/suite/certificatesigningrequests/selfsigned/selfsigned.go index 5c7a0598853..1561e8627bc 100644 --- a/test/e2e/suite/certificatesigningrequests/selfsigned/selfsigned.go +++ b/test/e2e/suite/certificatesigningrequests/selfsigned/selfsigned.go @@ -21,18 +21,19 @@ import ( "fmt" "time" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" certificatesv1 "k8s.io/api/certificates/v1" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/utils/clock" + "github.com/cert-manager/cert-manager/e2e-tests/framework" "github.com/cert-manager/cert-manager/internal/controller/feature" cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" utilfeature "github.com/cert-manager/cert-manager/pkg/util/feature" - "github.com/cert-manager/cert-manager/test/e2e/framework" testcrypto "github.com/cert-manager/cert-manager/test/unit/crypto" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" ) // This test ensures that a self-signed certificatesigningrequests will still @@ -48,31 +49,31 @@ var _ = framework.CertManagerDescribe("CertificateSigningRequests SelfSigned Sec bundle *testcrypto.CryptoBundle ) - JustBeforeEach(func() { + JustBeforeEach(func(testingCtx context.Context) { var err error bundle, err = testcrypto.CreateCryptoBundle(&cmapi.Certificate{ Spec: cmapi.CertificateSpec{CommonName: "selfsigned-test"}}, clock.RealClock{}) Expect(err).NotTo(HaveOccurred()) }) - JustAfterEach(func() { - Expect(f.CRClient.Delete(context.TODO(), request)).NotTo(HaveOccurred()) - Expect(f.CRClient.Delete(context.TODO(), issuer)).NotTo(HaveOccurred()) - Expect(f.CRClient.Delete(context.TODO(), secret)).NotTo(HaveOccurred()) + JustAfterEach(func(testingCtx context.Context) { + Expect(f.CRClient.Delete(testingCtx, request)).NotTo(HaveOccurred()) + Expect(f.CRClient.Delete(testingCtx, issuer)).NotTo(HaveOccurred()) + Expect(f.CRClient.Delete(testingCtx, secret)).NotTo(HaveOccurred()) }) - It("Issuer: the private key Secret is created after the request is created should still be signed", func() { - framework.RequireFeatureGate(f, utilfeature.DefaultFeatureGate, feature.ExperimentalCertificateSigningRequestControllers) + It("Issuer: the private key Secret is created after the request is created should still be signed", func(testingCtx context.Context) { + framework.RequireFeatureGate(utilfeature.DefaultFeatureGate, feature.ExperimentalCertificateSigningRequestControllers) var err error - issuer, err = f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Create(context.TODO(), &cmapi.Issuer{ + issuer, err = f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Create(testingCtx, &cmapi.Issuer{ ObjectMeta: metav1.ObjectMeta{GenerateName: "selfsigned-", Namespace: f.Namespace.Name}, Spec: cmapi.IssuerSpec{IssuerConfig: cmapi.IssuerConfig{SelfSigned: new(cmapi.SelfSignedIssuer)}}, }, metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred()) By("creating request") - request, err = f.KubeClientSet.CertificatesV1().CertificateSigningRequests().Create(context.TODO(), &certificatesv1.CertificateSigningRequest{ + request, err = f.KubeClientSet.CertificatesV1().CertificateSigningRequests().Create(testingCtx, &certificatesv1.CertificateSigningRequest{ ObjectMeta: metav1.ObjectMeta{ GenerateName: "selfsigned-", Annotations: map[string]string{"experimental.cert-manager.io/private-key-secret-name": "selfsigned-test"}, @@ -80,7 +81,7 @@ var _ = framework.CertManagerDescribe("CertificateSigningRequests SelfSigned Sec Spec: certificatesv1.CertificateSigningRequestSpec{ Request: bundle.CSRBytes, SignerName: fmt.Sprintf("issuers.cert-manager.io/%s.%s", f.Namespace.Name, issuer.GetName()), - Usages: []certificatesv1.KeyUsage{certificatesv1.UsageClientAuth, certificatesv1.UsageServerAuth}, + Usages: []certificatesv1.KeyUsage{certificatesv1.UsageKeyEncipherment, certificatesv1.UsageDigitalSignature}, }, }, metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred()) @@ -88,15 +89,15 @@ var _ = framework.CertManagerDescribe("CertificateSigningRequests SelfSigned Sec By("approving request") request.Status.Conditions = append(request.Status.Conditions, certificatesv1.CertificateSigningRequestCondition{ Type: certificatesv1.CertificateApproved, Status: corev1.ConditionTrue, - Reason: "Approved", Message: "approved for cert-manager.io selfigned e2e test", + Reason: "Approved", Message: "approved for cert-manager.io self-signed e2e test", LastUpdateTime: metav1.NewTime(time.Now()), LastTransitionTime: metav1.NewTime(time.Now()), }) - request, err = f.KubeClientSet.CertificatesV1().CertificateSigningRequests().UpdateApproval(context.TODO(), request.Name, request, metav1.UpdateOptions{}) + request, err = f.KubeClientSet.CertificatesV1().CertificateSigningRequests().UpdateApproval(testingCtx, request.Name, request, metav1.UpdateOptions{}) Expect(err).NotTo(HaveOccurred()) By("waiting for request to have SecretNotFound event") Eventually(func() bool { - events, err := f.KubeClientSet.EventsV1().Events("default").List(context.TODO(), metav1.ListOptions{ + events, err := f.KubeClientSet.EventsV1().Events("default").List(testingCtx, metav1.ListOptions{ FieldSelector: "reason=SecretNotFound,type=Warning", }) Expect(err).NotTo(HaveOccurred()) @@ -109,7 +110,7 @@ var _ = framework.CertManagerDescribe("CertificateSigningRequests SelfSigned Sec }, "20s", "1s").Should(BeTrue(), "SecretNotFound event not found for request") By("creating Secret with private key should result in the request to be signed") - secret, err = f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Create(context.TODO(), &corev1.Secret{ + secret, err = f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Create(testingCtx, &corev1.Secret{ ObjectMeta: metav1.ObjectMeta{Name: "selfsigned-test", Namespace: f.Namespace.Name}, Data: map[string][]byte{ "tls.key": bundle.PrivateKeyBytes, @@ -117,30 +118,31 @@ var _ = framework.CertManagerDescribe("CertificateSigningRequests SelfSigned Sec }, metav1.CreateOptions{}) Eventually(func() bool { - request, err = f.KubeClientSet.CertificatesV1().CertificateSigningRequests().Get(context.TODO(), request.Name, metav1.GetOptions{}) + request, err = f.KubeClientSet.CertificatesV1().CertificateSigningRequests().Get(testingCtx, request.Name, metav1.GetOptions{}) Expect(err).NotTo(HaveOccurred()) return len(request.Status.Certificate) > 0 }, "20s", "1s").Should(BeTrue(), "request was not signed in time: %#+v", request) }) - It("Issuer: private key Secret is updated with a valid private key after the request is created should still be signed", func() { - framework.RequireFeatureGate(f, utilfeature.DefaultFeatureGate, feature.ExperimentalCertificateSigningRequestControllers) + It("Issuer: private key Secret is updated with a valid private key after the request is created should still be signed", func(testingCtx context.Context) { + framework.RequireFeatureGate(utilfeature.DefaultFeatureGate, feature.ExperimentalCertificateSigningRequestControllers) var err error By("creating Secret with missing private key") - secret, err = f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Create(context.TODO(), &corev1.Secret{ + secret, err = f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Create(testingCtx, &corev1.Secret{ ObjectMeta: metav1.ObjectMeta{Name: "selfsigned-test", Namespace: f.Namespace.Name}, Data: map[string][]byte{}, }, metav1.CreateOptions{}) + Expect(err).NotTo(HaveOccurred()) - issuer, err = f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Create(context.TODO(), &cmapi.Issuer{ + issuer, err = f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Create(testingCtx, &cmapi.Issuer{ ObjectMeta: metav1.ObjectMeta{GenerateName: "selfsigned-", Namespace: f.Namespace.Name}, Spec: cmapi.IssuerSpec{IssuerConfig: cmapi.IssuerConfig{SelfSigned: new(cmapi.SelfSignedIssuer)}}, }, metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred()) By("creating request") - request, err = f.KubeClientSet.CertificatesV1().CertificateSigningRequests().Create(context.TODO(), &certificatesv1.CertificateSigningRequest{ + request, err = f.KubeClientSet.CertificatesV1().CertificateSigningRequests().Create(testingCtx, &certificatesv1.CertificateSigningRequest{ ObjectMeta: metav1.ObjectMeta{ GenerateName: "selfsigned-", Annotations: map[string]string{"experimental.cert-manager.io/private-key-secret-name": "selfsigned-test"}, @@ -148,7 +150,7 @@ var _ = framework.CertManagerDescribe("CertificateSigningRequests SelfSigned Sec Spec: certificatesv1.CertificateSigningRequestSpec{ Request: bundle.CSRBytes, SignerName: fmt.Sprintf("issuers.cert-manager.io/%s.%s", f.Namespace.Name, issuer.GetName()), - Usages: []certificatesv1.KeyUsage{certificatesv1.UsageClientAuth, certificatesv1.UsageServerAuth}, + Usages: []certificatesv1.KeyUsage{certificatesv1.UsageKeyEncipherment, certificatesv1.UsageDigitalSignature}, }, }, metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred()) @@ -156,15 +158,15 @@ var _ = framework.CertManagerDescribe("CertificateSigningRequests SelfSigned Sec By("approving request") request.Status.Conditions = append(request.Status.Conditions, certificatesv1.CertificateSigningRequestCondition{ Type: certificatesv1.CertificateApproved, Status: corev1.ConditionTrue, - Reason: "Approved", Message: "approved for cert-manager.io selfigned e2e test", + Reason: "Approved", Message: "approved for cert-manager.io self-signed e2e test", LastUpdateTime: metav1.NewTime(time.Now()), LastTransitionTime: metav1.NewTime(time.Now()), }) - request, err = f.KubeClientSet.CertificatesV1().CertificateSigningRequests().UpdateApproval(context.TODO(), request.Name, request, metav1.UpdateOptions{}) + request, err = f.KubeClientSet.CertificatesV1().CertificateSigningRequests().UpdateApproval(testingCtx, request.Name, request, metav1.UpdateOptions{}) Expect(err).NotTo(HaveOccurred()) By("waiting for request to have ErrorParsingKey event") Eventually(func() bool { - events, err := f.KubeClientSet.EventsV1().Events("default").List(context.TODO(), metav1.ListOptions{ + events, err := f.KubeClientSet.EventsV1().Events("default").List(testingCtx, metav1.ListOptions{ FieldSelector: "reason=ErrorParsingKey,type=Warning", }) Expect(err).NotTo(HaveOccurred()) @@ -178,27 +180,27 @@ var _ = framework.CertManagerDescribe("CertificateSigningRequests SelfSigned Sec By("updating referenced private key Secret should get the request signed") secret.Data = map[string][]byte{"tls.key": bundle.PrivateKeyBytes} - _, err = f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Update(context.TODO(), secret, metav1.UpdateOptions{}) + _, err = f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Update(testingCtx, secret, metav1.UpdateOptions{}) Expect(err).NotTo(HaveOccurred()) Eventually(func() bool { - request, err = f.KubeClientSet.CertificatesV1().CertificateSigningRequests().Get(context.TODO(), request.Name, metav1.GetOptions{}) + request, err = f.KubeClientSet.CertificatesV1().CertificateSigningRequests().Get(testingCtx, request.Name, metav1.GetOptions{}) Expect(err).NotTo(HaveOccurred()) return len(request.Status.Certificate) > 0 }, "20s", "1s").Should(BeTrue(), "request was not signed in time: %#+v", request) }) - It("ClusterIssuer: the private key Secret is created after the request is created should still be signed", func() { - framework.RequireFeatureGate(f, utilfeature.DefaultFeatureGate, feature.ExperimentalCertificateSigningRequestControllers) + It("ClusterIssuer: the private key Secret is created after the request is created should still be signed", func(testingCtx context.Context) { + framework.RequireFeatureGate(utilfeature.DefaultFeatureGate, feature.ExperimentalCertificateSigningRequestControllers) var err error - issuer, err = f.CertManagerClientSet.CertmanagerV1().ClusterIssuers().Create(context.TODO(), &cmapi.ClusterIssuer{ + issuer, err = f.CertManagerClientSet.CertmanagerV1().ClusterIssuers().Create(testingCtx, &cmapi.ClusterIssuer{ ObjectMeta: metav1.ObjectMeta{GenerateName: "selfsigned-"}, Spec: cmapi.IssuerSpec{IssuerConfig: cmapi.IssuerConfig{SelfSigned: new(cmapi.SelfSignedIssuer)}}, }, metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred()) By("creating request") - request, err = f.KubeClientSet.CertificatesV1().CertificateSigningRequests().Create(context.TODO(), &certificatesv1.CertificateSigningRequest{ + request, err = f.KubeClientSet.CertificatesV1().CertificateSigningRequests().Create(testingCtx, &certificatesv1.CertificateSigningRequest{ ObjectMeta: metav1.ObjectMeta{ GenerateName: "selfsigned-", Annotations: map[string]string{"experimental.cert-manager.io/private-key-secret-name": "selfsigned-test"}, @@ -206,7 +208,7 @@ var _ = framework.CertManagerDescribe("CertificateSigningRequests SelfSigned Sec Spec: certificatesv1.CertificateSigningRequestSpec{ Request: bundle.CSRBytes, SignerName: fmt.Sprintf("clusterissuers.cert-manager.io/" + issuer.GetName()), - Usages: []certificatesv1.KeyUsage{certificatesv1.UsageClientAuth, certificatesv1.UsageServerAuth}, + Usages: []certificatesv1.KeyUsage{certificatesv1.UsageKeyEncipherment, certificatesv1.UsageDigitalSignature}, }, }, metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred()) @@ -214,15 +216,15 @@ var _ = framework.CertManagerDescribe("CertificateSigningRequests SelfSigned Sec By("approving request") request.Status.Conditions = append(request.Status.Conditions, certificatesv1.CertificateSigningRequestCondition{ Type: certificatesv1.CertificateApproved, Status: corev1.ConditionTrue, - Reason: "Approved", Message: "approved for cert-manager.io selfigned e2e test", + Reason: "Approved", Message: "approved for cert-manager.io self-signed e2e test", LastUpdateTime: metav1.NewTime(time.Now()), LastTransitionTime: metav1.NewTime(time.Now()), }) - request, err = f.KubeClientSet.CertificatesV1().CertificateSigningRequests().UpdateApproval(context.TODO(), request.Name, request, metav1.UpdateOptions{}) + request, err = f.KubeClientSet.CertificatesV1().CertificateSigningRequests().UpdateApproval(testingCtx, request.Name, request, metav1.UpdateOptions{}) Expect(err).NotTo(HaveOccurred()) By("waiting for request to have SecretNotFound event") Eventually(func() bool { - events, err := f.KubeClientSet.EventsV1().Events("default").List(context.TODO(), metav1.ListOptions{ + events, err := f.KubeClientSet.EventsV1().Events("default").List(testingCtx, metav1.ListOptions{ FieldSelector: "reason=SecretNotFound,type=Warning", }) Expect(err).NotTo(HaveOccurred()) @@ -235,7 +237,7 @@ var _ = framework.CertManagerDescribe("CertificateSigningRequests SelfSigned Sec }, "20s", "1s").Should(BeTrue(), "SecretNotFound event not found for request") By("creating Secret with private key should result in the request to be signed") - secret, err = f.KubeClientSet.CoreV1().Secrets("cert-manager").Create(context.TODO(), &corev1.Secret{ + secret, err = f.KubeClientSet.CoreV1().Secrets("cert-manager").Create(testingCtx, &corev1.Secret{ ObjectMeta: metav1.ObjectMeta{Name: "selfsigned-test", Namespace: "cert-manager"}, Data: map[string][]byte{ "tls.key": bundle.PrivateKeyBytes, @@ -243,30 +245,31 @@ var _ = framework.CertManagerDescribe("CertificateSigningRequests SelfSigned Sec }, metav1.CreateOptions{}) Eventually(func() bool { - request, err = f.KubeClientSet.CertificatesV1().CertificateSigningRequests().Get(context.TODO(), request.Name, metav1.GetOptions{}) + request, err = f.KubeClientSet.CertificatesV1().CertificateSigningRequests().Get(testingCtx, request.Name, metav1.GetOptions{}) Expect(err).NotTo(HaveOccurred()) return len(request.Status.Certificate) > 0 }, "20s", "1s").Should(BeTrue(), "request was not signed in time: %#+v", request) }) - It("ClusterIssuer: private key Secret is updated with a valid private key after the request is created should still be signed", func() { - framework.RequireFeatureGate(f, utilfeature.DefaultFeatureGate, feature.ExperimentalCertificateSigningRequestControllers) + It("ClusterIssuer: private key Secret is updated with a valid private key after the request is created should still be signed", func(testingCtx context.Context) { + framework.RequireFeatureGate(utilfeature.DefaultFeatureGate, feature.ExperimentalCertificateSigningRequestControllers) var err error By("creating Secret with missing private key") - secret, err = f.KubeClientSet.CoreV1().Secrets("cert-manager").Create(context.TODO(), &corev1.Secret{ + secret, err = f.KubeClientSet.CoreV1().Secrets("cert-manager").Create(testingCtx, &corev1.Secret{ ObjectMeta: metav1.ObjectMeta{Name: "selfsigned-test", Namespace: "cert-manager"}, Data: map[string][]byte{}, }, metav1.CreateOptions{}) + Expect(err).NotTo(HaveOccurred()) - issuer, err = f.CertManagerClientSet.CertmanagerV1().ClusterIssuers().Create(context.TODO(), &cmapi.ClusterIssuer{ + issuer, err = f.CertManagerClientSet.CertmanagerV1().ClusterIssuers().Create(testingCtx, &cmapi.ClusterIssuer{ ObjectMeta: metav1.ObjectMeta{GenerateName: "selfsigned-"}, Spec: cmapi.IssuerSpec{IssuerConfig: cmapi.IssuerConfig{SelfSigned: new(cmapi.SelfSignedIssuer)}}, }, metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred()) By("creating request") - request, err = f.KubeClientSet.CertificatesV1().CertificateSigningRequests().Create(context.TODO(), &certificatesv1.CertificateSigningRequest{ + request, err = f.KubeClientSet.CertificatesV1().CertificateSigningRequests().Create(testingCtx, &certificatesv1.CertificateSigningRequest{ ObjectMeta: metav1.ObjectMeta{ GenerateName: "selfsigned-", Annotations: map[string]string{"experimental.cert-manager.io/private-key-secret-name": "selfsigned-test"}, @@ -274,7 +277,7 @@ var _ = framework.CertManagerDescribe("CertificateSigningRequests SelfSigned Sec Spec: certificatesv1.CertificateSigningRequestSpec{ Request: bundle.CSRBytes, SignerName: fmt.Sprintf("clusterissuers.cert-manager.io/" + issuer.GetName()), - Usages: []certificatesv1.KeyUsage{certificatesv1.UsageClientAuth, certificatesv1.UsageServerAuth}, + Usages: []certificatesv1.KeyUsage{certificatesv1.UsageKeyEncipherment, certificatesv1.UsageDigitalSignature}, }, }, metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred()) @@ -282,15 +285,15 @@ var _ = framework.CertManagerDescribe("CertificateSigningRequests SelfSigned Sec By("approving request") request.Status.Conditions = append(request.Status.Conditions, certificatesv1.CertificateSigningRequestCondition{ Type: certificatesv1.CertificateApproved, Status: corev1.ConditionTrue, - Reason: "Approved", Message: "approved for cert-manager.io selfigned e2e test", + Reason: "Approved", Message: "approved for cert-manager.io self-signed e2e test", LastUpdateTime: metav1.NewTime(time.Now()), LastTransitionTime: metav1.NewTime(time.Now()), }) - request, err = f.KubeClientSet.CertificatesV1().CertificateSigningRequests().UpdateApproval(context.TODO(), request.Name, request, metav1.UpdateOptions{}) + request, err = f.KubeClientSet.CertificatesV1().CertificateSigningRequests().UpdateApproval(testingCtx, request.Name, request, metav1.UpdateOptions{}) Expect(err).NotTo(HaveOccurred()) By("waiting for request to have ErrorParsingKey event") Eventually(func() bool { - events, err := f.KubeClientSet.EventsV1().Events("default").List(context.TODO(), metav1.ListOptions{ + events, err := f.KubeClientSet.EventsV1().Events("default").List(testingCtx, metav1.ListOptions{ FieldSelector: "reason=ErrorParsingKey,type=Warning", }) Expect(err).NotTo(HaveOccurred()) @@ -304,10 +307,10 @@ var _ = framework.CertManagerDescribe("CertificateSigningRequests SelfSigned Sec By("updating referenced private key Secret should get the request signed") secret.Data = map[string][]byte{"tls.key": bundle.PrivateKeyBytes} - _, err = f.KubeClientSet.CoreV1().Secrets("cert-manager").Update(context.TODO(), secret, metav1.UpdateOptions{}) + _, err = f.KubeClientSet.CoreV1().Secrets("cert-manager").Update(testingCtx, secret, metav1.UpdateOptions{}) Expect(err).NotTo(HaveOccurred()) Eventually(func() bool { - request, err = f.KubeClientSet.CertificatesV1().CertificateSigningRequests().Get(context.TODO(), request.Name, metav1.GetOptions{}) + request, err = f.KubeClientSet.CertificatesV1().CertificateSigningRequests().Get(testingCtx, request.Name, metav1.GetOptions{}) Expect(err).NotTo(HaveOccurred()) return len(request.Status.Certificate) > 0 }, "20s", "1s").Should(BeTrue(), "request was not signed in time: %#+v", request) diff --git a/test/e2e/suite/conformance/certificates/acme/acme.go b/test/e2e/suite/conformance/certificates/acme/acme.go index 55e9c8e099e..758c4bc1d79 100644 --- a/test/e2e/suite/conformance/certificates/acme/acme.go +++ b/test/e2e/suite/conformance/certificates/acme/acme.go @@ -18,23 +18,22 @@ package acme import ( "context" - "encoding/base64" "strings" "time" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - gwapi "sigs.k8s.io/gateway-api/apis/v1alpha2" + gwapi "sigs.k8s.io/gateway-api/apis/v1" + "github.com/cert-manager/cert-manager/e2e-tests/framework" + "github.com/cert-manager/cert-manager/e2e-tests/framework/helper/featureset" + "github.com/cert-manager/cert-manager/e2e-tests/suite/conformance/certificates" cmacme "github.com/cert-manager/cert-manager/pkg/apis/acme/v1" cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" - "github.com/cert-manager/cert-manager/test/e2e/framework" - "github.com/cert-manager/cert-manager/test/e2e/framework/helper/featureset" - "github.com/cert-manager/cert-manager/test/e2e/suite/conformance/certificates" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" ) var _ = framework.ConformanceDescribe("Certificates", func() { @@ -59,9 +58,10 @@ func runACMEIssuerTests(eab *cmacme.ACMEExternalAccountBinding) { featureset.SaveCAToSecret, featureset.IssueCAFeature, featureset.LiteralSubjectFeature, + featureset.OtherNamesFeature, ) - var unsupportedHTTP01GatewayFeatures = unsupportedHTTP01Features.Copy().Add( + var unsupportedHTTP01GatewayFeatures = unsupportedHTTP01Features.Clone().Insert( // Gateway API does not allow raw IP addresses to be specified // in HTTPRoutes, so challenges for an IP address will never work. featureset.IPAddressFeature, @@ -79,11 +79,12 @@ func runACMEIssuerTests(eab *cmacme.ACMEExternalAccountBinding) { featureset.SaveCAToSecret, featureset.IssueCAFeature, featureset.LiteralSubjectFeature, + featureset.OtherNamesFeature, ) // UnsupportedPublicACMEServerFeatures are additional ACME features not supported by // public ACME servers - var unsupportedPublicACMEServerFeatures = unsupportedHTTP01Features.Copy().Add( + var unsupportedPublicACMEServerFeatures = unsupportedHTTP01Features.Clone().Insert( // Let's Encrypt doesn't yet support IP Address certificates. featureset.IPAddressFeature, // Ed25519 is not yet approved by the CA Browser forum. @@ -93,6 +94,7 @@ func runACMEIssuerTests(eab *cmacme.ACMEExternalAccountBinding) { // 64 bytes. Skip the long domain test in this case. featureset.LongDomainFeatureSet, featureset.LiteralSubjectFeature, + featureset.OtherNamesFeature, ) provisionerHTTP01 := &acmeIssuerProvisioner{ @@ -169,14 +171,14 @@ type acmeIssuerProvisioner struct { secretNamespace string } -func (a *acmeIssuerProvisioner) delete(f *framework.Framework, ref cmmeta.ObjectReference) { +func (a *acmeIssuerProvisioner) delete(ctx context.Context, f *framework.Framework, ref cmmeta.IssuerReference) { if a.eab != nil { - err := f.KubeClientSet.CoreV1().Secrets(a.secretNamespace).Delete(context.TODO(), a.eab.Key.Name, metav1.DeleteOptions{}) + err := f.KubeClientSet.CoreV1().Secrets(a.secretNamespace).Delete(ctx, a.eab.Key.Name, metav1.DeleteOptions{}) Expect(err).NotTo(HaveOccurred()) } if ref.Kind == "ClusterIssuer" { - err := f.CertManagerClientSet.CertmanagerV1().ClusterIssuers().Delete(context.TODO(), ref.Name, metav1.DeleteOptions{}) + err := f.CertManagerClientSet.CertmanagerV1().ClusterIssuers().Delete(ctx, ref.Name, metav1.DeleteOptions{}) Expect(err).NotTo(HaveOccurred()) } } @@ -187,8 +189,8 @@ func (a *acmeIssuerProvisioner) delete(f *framework.Framework, ref cmmeta.Object // - pebble // - a properly configured Issuer resource -func (a *acmeIssuerProvisioner) createHTTP01IngressIssuer(f *framework.Framework) cmmeta.ObjectReference { - a.ensureEABSecret(f, "") +func (a *acmeIssuerProvisioner) createHTTP01IngressIssuer(ctx context.Context, f *framework.Framework) cmmeta.IssuerReference { + a.ensureEABSecret(ctx, f, "") By("Creating an ACME HTTP01 Ingress Issuer") issuer := &cmapi.Issuer{ @@ -198,23 +200,23 @@ func (a *acmeIssuerProvisioner) createHTTP01IngressIssuer(f *framework.Framework Spec: a.createHTTP01IngressIssuerSpec(f.Config.Addons.ACMEServer.URL), } - issuer, err := f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Create(context.TODO(), issuer, metav1.CreateOptions{}) + issuer, err := f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Create(ctx, issuer, metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred(), "failed to create acme HTTP01 issuer") // wait for issuer to be ready By("Waiting for acme HTTP01 Ingress Issuer to be Ready") - issuer, err = f.Helper().WaitIssuerReady(issuer, time.Minute*5) + issuer, err = f.Helper().WaitIssuerReady(ctx, issuer, time.Minute*5) Expect(err).ToNot(HaveOccurred()) - return cmmeta.ObjectReference{ + return cmmeta.IssuerReference{ Group: cmapi.SchemeGroupVersion.Group, Kind: cmapi.IssuerKind, Name: issuer.Name, } } -func (a *acmeIssuerProvisioner) createHTTP01IngressClusterIssuer(f *framework.Framework) cmmeta.ObjectReference { - a.ensureEABSecret(f, f.Config.Addons.CertManager.ClusterResourceNamespace) +func (a *acmeIssuerProvisioner) createHTTP01IngressClusterIssuer(ctx context.Context, f *framework.Framework) cmmeta.IssuerReference { + a.ensureEABSecret(ctx, f, f.Config.Addons.CertManager.ClusterResourceNamespace) By("Creating an ACME HTTP01 Ingress ClusterIssuer") issuer := &cmapi.ClusterIssuer{ @@ -224,23 +226,23 @@ func (a *acmeIssuerProvisioner) createHTTP01IngressClusterIssuer(f *framework.Fr Spec: a.createHTTP01IngressIssuerSpec(f.Config.Addons.ACMEServer.URL), } - issuer, err := f.CertManagerClientSet.CertmanagerV1().ClusterIssuers().Create(context.TODO(), issuer, metav1.CreateOptions{}) + issuer, err := f.CertManagerClientSet.CertmanagerV1().ClusterIssuers().Create(ctx, issuer, metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred(), "failed to create acme HTTP01 cluster issuer") // wait for issuer to be ready By("Waiting for acme HTTP01 Ingress Cluster Issuer to be Ready") - issuer, err = f.Helper().WaitClusterIssuerReady(issuer, time.Minute*5) + issuer, err = f.Helper().WaitClusterIssuerReady(ctx, issuer, time.Minute*5) Expect(err).ToNot(HaveOccurred()) - return cmmeta.ObjectReference{ + return cmmeta.IssuerReference{ Group: cmapi.SchemeGroupVersion.Group, Kind: cmapi.ClusterIssuerKind, Name: issuer.Name, } } -func (a *acmeIssuerProvisioner) createHTTP01GatewayIssuer(f *framework.Framework) cmmeta.ObjectReference { - a.ensureEABSecret(f, "") +func (a *acmeIssuerProvisioner) createHTTP01GatewayIssuer(ctx context.Context, f *framework.Framework) cmmeta.IssuerReference { + a.ensureEABSecret(ctx, f, "") labelFlag := strings.Split(f.Config.Addons.Gateway.Labels, ",") labels := make(map[string]string) @@ -260,22 +262,22 @@ func (a *acmeIssuerProvisioner) createHTTP01GatewayIssuer(f *framework.Framework Spec: a.createHTTP01GatewayIssuerSpec(f.Config.Addons.ACMEServer.URL, labels), } - issuer, err := f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Create(context.TODO(), issuer, metav1.CreateOptions{}) + issuer, err := f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Create(ctx, issuer, metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred(), "failed to create acme HTTP01 issuer") // wait for issuer to be ready By("Waiting for acme HTTP01 Gateway Issuer to be Ready") - issuer, err = f.Helper().WaitIssuerReady(issuer, time.Minute*5) + issuer, err = f.Helper().WaitIssuerReady(ctx, issuer, time.Minute*5) Expect(err).ToNot(HaveOccurred()) - return cmmeta.ObjectReference{ + return cmmeta.IssuerReference{ Group: cmapi.SchemeGroupVersion.Group, Kind: cmapi.IssuerKind, Name: issuer.Name, } } -func (a *acmeIssuerProvisioner) createPublicACMEServerStagingHTTP01Issuer(f *framework.Framework) cmmeta.ObjectReference { +func (a *acmeIssuerProvisioner) createPublicACMEServerStagingHTTP01Issuer(ctx context.Context, f *framework.Framework) cmmeta.IssuerReference { By("Creating a Public ACME Server Staging HTTP01 Issuer") var PublicACMEServerStagingURL string @@ -292,23 +294,23 @@ func (a *acmeIssuerProvisioner) createPublicACMEServerStagingHTTP01Issuer(f *fra Spec: a.createHTTP01IngressIssuerSpec(PublicACMEServerStagingURL), } - issuer, err := f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Create(context.TODO(), issuer, metav1.CreateOptions{}) + issuer, err := f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Create(ctx, issuer, metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred(), "failed to create Public ACME Server Staging HTTP01 issuer") // wait for issuer to be ready By("Waiting for Public ACME Server Staging HTTP01 Issuer to be Ready") - issuer, err = f.Helper().WaitIssuerReady(issuer, time.Minute*5) + issuer, err = f.Helper().WaitIssuerReady(ctx, issuer, time.Minute*5) Expect(err).ToNot(HaveOccurred()) - return cmmeta.ObjectReference{ + return cmmeta.IssuerReference{ Group: cmapi.SchemeGroupVersion.Group, Kind: cmapi.IssuerKind, Name: issuer.Name, } } -func (a *acmeIssuerProvisioner) createHTTP01GatewayClusterIssuer(f *framework.Framework) cmmeta.ObjectReference { - a.ensureEABSecret(f, f.Config.Addons.CertManager.ClusterResourceNamespace) +func (a *acmeIssuerProvisioner) createHTTP01GatewayClusterIssuer(ctx context.Context, f *framework.Framework) cmmeta.IssuerReference { + a.ensureEABSecret(ctx, f, f.Config.Addons.CertManager.ClusterResourceNamespace) labelFlag := strings.Split(f.Config.Addons.Gateway.Labels, ",") labels := make(map[string]string) @@ -329,14 +331,14 @@ func (a *acmeIssuerProvisioner) createHTTP01GatewayClusterIssuer(f *framework.Fr Spec: a.createHTTP01GatewayIssuerSpec(f.Config.Addons.ACMEServer.URL, labels), } - issuer, err := f.CertManagerClientSet.CertmanagerV1().ClusterIssuers().Create(context.TODO(), issuer, metav1.CreateOptions{}) + issuer, err := f.CertManagerClientSet.CertmanagerV1().ClusterIssuers().Create(ctx, issuer, metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred(), "failed to create acme HTTP01 cluster issuer") By("Waiting for acme HTTP01 Gateway Cluster Issuer to be Ready") - issuer, err = f.Helper().WaitClusterIssuerReady(issuer, time.Minute*5) + issuer, err = f.Helper().WaitClusterIssuerReady(ctx, issuer, time.Minute*5) Expect(err).ToNot(HaveOccurred()) - return cmmeta.ObjectReference{ + return cmmeta.IssuerReference{ Group: cmapi.SchemeGroupVersion.Group, Kind: cmapi.ClusterIssuerKind, Name: issuer.Name, @@ -403,8 +405,8 @@ func (a *acmeIssuerProvisioner) createHTTP01GatewayIssuerSpec(serverURL string, } } -func (a *acmeIssuerProvisioner) createDNS01Issuer(f *framework.Framework) cmmeta.ObjectReference { - a.ensureEABSecret(f, f.Namespace.Name) +func (a *acmeIssuerProvisioner) createDNS01Issuer(ctx context.Context, f *framework.Framework) cmmeta.IssuerReference { + a.ensureEABSecret(ctx, f, f.Namespace.Name) By("Creating an ACME DNS01 Issuer") issuer := &cmapi.Issuer{ @@ -413,23 +415,23 @@ func (a *acmeIssuerProvisioner) createDNS01Issuer(f *framework.Framework) cmmeta }, Spec: a.createDNS01IssuerSpec(f.Config.Addons.ACMEServer.URL, f.Config.Addons.ACMEServer.DNSServer), } - issuer, err := f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Create(context.TODO(), issuer, metav1.CreateOptions{}) + issuer, err := f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Create(ctx, issuer, metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred(), "failed to create acme DNS01 Issuer") // wait for issuer to be ready By("Waiting for acme DNS01 Issuer to be Ready") - issuer, err = f.Helper().WaitIssuerReady(issuer, time.Minute*5) + issuer, err = f.Helper().WaitIssuerReady(ctx, issuer, time.Minute*5) Expect(err).ToNot(HaveOccurred()) - return cmmeta.ObjectReference{ + return cmmeta.IssuerReference{ Group: cmapi.SchemeGroupVersion.Group, Kind: cmapi.IssuerKind, Name: issuer.Name, } } -func (a *acmeIssuerProvisioner) createDNS01ClusterIssuer(f *framework.Framework) cmmeta.ObjectReference { - a.ensureEABSecret(f, f.Config.Addons.CertManager.ClusterResourceNamespace) +func (a *acmeIssuerProvisioner) createDNS01ClusterIssuer(ctx context.Context, f *framework.Framework) cmmeta.IssuerReference { + a.ensureEABSecret(ctx, f, f.Config.Addons.CertManager.ClusterResourceNamespace) By("Creating an ACME DNS01 ClusterIssuer") issuer := &cmapi.ClusterIssuer{ @@ -438,15 +440,15 @@ func (a *acmeIssuerProvisioner) createDNS01ClusterIssuer(f *framework.Framework) }, Spec: a.createDNS01IssuerSpec(f.Config.Addons.ACMEServer.URL, f.Config.Addons.ACMEServer.DNSServer), } - issuer, err := f.CertManagerClientSet.CertmanagerV1().ClusterIssuers().Create(context.TODO(), issuer, metav1.CreateOptions{}) + issuer, err := f.CertManagerClientSet.CertmanagerV1().ClusterIssuers().Create(ctx, issuer, metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred(), "failed to create acme DNS01 ClusterIssuer") // wait for issuer to be ready By("Waiting for acme DNS01 Cluster Issuer to be Ready") - issuer, err = f.Helper().WaitClusterIssuerReady(issuer, time.Minute*5) + issuer, err = f.Helper().WaitClusterIssuerReady(ctx, issuer, time.Minute*5) Expect(err).ToNot(HaveOccurred()) - return cmmeta.ObjectReference{ + return cmmeta.IssuerReference{ Group: cmapi.SchemeGroupVersion.Group, Kind: cmapi.ClusterIssuerKind, Name: issuer.Name, @@ -479,7 +481,7 @@ func (a *acmeIssuerProvisioner) createDNS01IssuerSpec(serverURL, dnsServer strin } } -func (a *acmeIssuerProvisioner) ensureEABSecret(f *framework.Framework, ns string) { +func (a *acmeIssuerProvisioner) ensureEABSecret(ctx context.Context, f *framework.Framework, ns string) { if a.eab == nil { return } @@ -487,14 +489,15 @@ func (a *acmeIssuerProvisioner) ensureEABSecret(f *framework.Framework, ns strin if ns == "" { ns = f.Namespace.Name } - sec, err := f.KubeClientSet.CoreV1().Secrets(ns).Create(context.TODO(), &corev1.Secret{ + sec, err := f.KubeClientSet.CoreV1().Secrets(ns).Create(ctx, &corev1.Secret{ ObjectMeta: metav1.ObjectMeta{ GenerateName: "external-account-binding-", Namespace: ns, }, Data: map[string][]byte{ - // base64 url encode (without padding) the HMAC key - "key": []byte(base64.RawURLEncoding.EncodeToString([]byte("kid-secret-1"))), + // Must match the key in the Pebble config. See: + // config.json in make/config/pebble/templates/configmaps.yaml + "key": []byte("zWNDZM6eQGHWpSRTPal5eIUYFTu7EajVIoguysqZ9wG44nMEtx3MUAsUDkMTQ12W"), }, }, metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred()) diff --git a/test/e2e/suite/conformance/certificates/ca/ca.go b/test/e2e/suite/conformance/certificates/ca/ca.go index d797883ddd4..96e55793759 100644 --- a/test/e2e/suite/conformance/certificates/ca/ca.go +++ b/test/e2e/suite/conformance/certificates/ca/ca.go @@ -20,15 +20,16 @@ import ( "context" "time" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "github.com/cert-manager/cert-manager/e2e-tests/framework" + "github.com/cert-manager/cert-manager/e2e-tests/suite/conformance/certificates" cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" - "github.com/cert-manager/cert-manager/test/e2e/framework" - "github.com/cert-manager/cert-manager/test/e2e/suite/conformance/certificates" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" ) var _ = framework.ConformanceDescribe("Certificates", func() { @@ -50,15 +51,15 @@ type ca struct { secretName string } -func (c *ca) createCAIssuer(f *framework.Framework) cmmeta.ObjectReference { +func (c *ca) createCAIssuer(ctx context.Context, f *framework.Framework) cmmeta.IssuerReference { By("Creating a CA Issuer") - rootCertSecret, err := f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Create(context.TODO(), newSigningKeypairSecret("root-ca-cert-"), metav1.CreateOptions{}) + rootCertSecret, err := f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Create(ctx, newSigningKeypairSecret("root-ca-cert-"), metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred(), "failed to create root signing keypair secret") c.secretName = rootCertSecret.Name - issuer, err := f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Create(context.TODO(), &cmapi.Issuer{ + issuer, err := f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Create(ctx, &cmapi.Issuer{ ObjectMeta: metav1.ObjectMeta{ GenerateName: "ca-issuer-", }, @@ -69,25 +70,25 @@ func (c *ca) createCAIssuer(f *framework.Framework) cmmeta.ObjectReference { // wait for issuer to be ready By("Waiting for CA Issuer to be Ready") - issuer, err = f.Helper().WaitIssuerReady(issuer, time.Minute*5) + issuer, err = f.Helper().WaitIssuerReady(ctx, issuer, time.Minute*5) Expect(err).ToNot(HaveOccurred()) - return cmmeta.ObjectReference{ + return cmmeta.IssuerReference{ Group: cmapi.SchemeGroupVersion.Group, Kind: cmapi.IssuerKind, Name: issuer.Name, } } -func (c *ca) createCAClusterIssuer(f *framework.Framework) cmmeta.ObjectReference { +func (c *ca) createCAClusterIssuer(ctx context.Context, f *framework.Framework) cmmeta.IssuerReference { By("Creating a CA ClusterIssuer") - rootCertSecret, err := f.KubeClientSet.CoreV1().Secrets(f.Config.Addons.CertManager.ClusterResourceNamespace).Create(context.TODO(), newSigningKeypairSecret("root-ca-cert-"), metav1.CreateOptions{}) + rootCertSecret, err := f.KubeClientSet.CoreV1().Secrets(f.Config.Addons.CertManager.ClusterResourceNamespace).Create(ctx, newSigningKeypairSecret("root-ca-cert-"), metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred(), "failed to create root signing keypair secret") c.secretName = rootCertSecret.Name - issuer, err := f.CertManagerClientSet.CertmanagerV1().ClusterIssuers().Create(context.TODO(), &cmapi.ClusterIssuer{ + issuer, err := f.CertManagerClientSet.CertmanagerV1().ClusterIssuers().Create(ctx, &cmapi.ClusterIssuer{ ObjectMeta: metav1.ObjectMeta{ GenerateName: "ca-cluster-issuer-", }, @@ -98,23 +99,23 @@ func (c *ca) createCAClusterIssuer(f *framework.Framework) cmmeta.ObjectReferenc // wait for issuer to be ready By("Waiting for CA Cluster Issuer to be Ready") - issuer, err = f.Helper().WaitClusterIssuerReady(issuer, time.Minute*5) + issuer, err = f.Helper().WaitClusterIssuerReady(ctx, issuer, time.Minute*5) Expect(err).ToNot(HaveOccurred()) - return cmmeta.ObjectReference{ + return cmmeta.IssuerReference{ Group: cmapi.SchemeGroupVersion.Group, Kind: cmapi.ClusterIssuerKind, Name: issuer.Name, } } -func (c *ca) deleteCAClusterIssuer(f *framework.Framework, issuer cmmeta.ObjectReference) { +func (c *ca) deleteCAClusterIssuer(ctx context.Context, f *framework.Framework, issuer cmmeta.IssuerReference) { By("Deleting CA ClusterIssuer") - err := f.KubeClientSet.CoreV1().Secrets(f.Config.Addons.CertManager.ClusterResourceNamespace).Delete(context.TODO(), c.secretName, metav1.DeleteOptions{}) + err := f.KubeClientSet.CoreV1().Secrets(f.Config.Addons.CertManager.ClusterResourceNamespace).Delete(ctx, c.secretName, metav1.DeleteOptions{}) Expect(err).NotTo(HaveOccurred(), "failed to delete root signing keypair secret") - err = f.CertManagerClientSet.CertmanagerV1().ClusterIssuers().Delete(context.TODO(), issuer.Name, metav1.DeleteOptions{}) + err = f.CertManagerClientSet.CertmanagerV1().ClusterIssuers().Delete(ctx, issuer.Name, metav1.DeleteOptions{}) Expect(err).NotTo(HaveOccurred(), "failed to delete ca issuer") } diff --git a/test/e2e/suite/conformance/certificates/external/external.go b/test/e2e/suite/conformance/certificates/external/external.go index c268a45bc39..fa864c9bcfd 100644 --- a/test/e2e/suite/conformance/certificates/external/external.go +++ b/test/e2e/suite/conformance/certificates/external/external.go @@ -20,18 +20,19 @@ import ( "context" "fmt" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/types" crtclient "sigs.k8s.io/controller-runtime/pkg/client" + "github.com/cert-manager/cert-manager/e2e-tests/framework" + "github.com/cert-manager/cert-manager/e2e-tests/framework/helper/featureset" + "github.com/cert-manager/cert-manager/e2e-tests/suite/conformance/certificates" cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" - "github.com/cert-manager/cert-manager/test/e2e/framework" - "github.com/cert-manager/cert-manager/test/e2e/framework/helper/featureset" - "github.com/cert-manager/cert-manager/test/e2e/suite/conformance/certificates" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" ) const ( @@ -46,6 +47,7 @@ var _ = framework.ConformanceDescribe("Certificates", func() { featureset.Ed25519FeatureSet, featureset.IssueCAFeature, featureset.LiteralSubjectFeature, + featureset.OtherNamesFeature, ) issuerBuilder := newIssuerBuilder("Issuer") @@ -114,9 +116,7 @@ func (o *issuerBuilder) secretAndIssuerForTest(f *framework.Framework) (*corev1. return secret, issuer, err } -func (o *issuerBuilder) create(f *framework.Framework) cmmeta.ObjectReference { - ctx := context.TODO() - +func (o *issuerBuilder) create(ctx context.Context, f *framework.Framework) cmmeta.IssuerReference { By("Creating an Issuer") secret, issuer, err := o.secretAndIssuerForTest(f) Expect(err).NotTo(HaveOccurred(), "failed to initialise test objects") @@ -130,17 +130,15 @@ func (o *issuerBuilder) create(f *framework.Framework) cmmeta.ObjectReference { err = crt.Create(ctx, issuer) Expect(err).NotTo(HaveOccurred(), "failed to create issuer") - return cmmeta.ObjectReference{ + return cmmeta.IssuerReference{ Group: issuer.GroupVersionKind().Group, Kind: issuer.GroupVersionKind().Kind, Name: issuer.GetName(), } } -func (o *issuerBuilder) delete(f *framework.Framework, _ cmmeta.ObjectReference) { +func (o *issuerBuilder) delete(ctx context.Context, f *framework.Framework, _ cmmeta.IssuerReference) { By("Deleting the issuer") - ctx := context.TODO() - crt, err := crtclient.New(f.KubeClientConfig, crtclient.Options{}) Expect(err).NotTo(HaveOccurred(), "failed to create controller-runtime client") diff --git a/test/e2e/suite/conformance/certificates/selfsigned/selfsigned.go b/test/e2e/suite/conformance/certificates/selfsigned/selfsigned.go index dd0b5d564df..9a790d66d1c 100644 --- a/test/e2e/suite/conformance/certificates/selfsigned/selfsigned.go +++ b/test/e2e/suite/conformance/certificates/selfsigned/selfsigned.go @@ -20,14 +20,15 @@ import ( "context" "time" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "github.com/cert-manager/cert-manager/e2e-tests/framework" + "github.com/cert-manager/cert-manager/e2e-tests/suite/conformance/certificates" cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" - "github.com/cert-manager/cert-manager/test/e2e/framework" - "github.com/cert-manager/cert-manager/test/e2e/suite/conformance/certificates" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" ) var _ = framework.ConformanceDescribe("Certificates", func() { @@ -43,51 +44,51 @@ var _ = framework.ConformanceDescribe("Certificates", func() { }).Define() }) -func createSelfSignedIssuer(f *framework.Framework) cmmeta.ObjectReference { +func createSelfSignedIssuer(ctx context.Context, f *framework.Framework) cmmeta.IssuerReference { By("Creating a SelfSigned Issuer") - issuer, err := f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Create(context.TODO(), &cmapi.Issuer{ + issuer, err := f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Create(ctx, &cmapi.Issuer{ ObjectMeta: metav1.ObjectMeta{ GenerateName: "selfsigned-issuer-", }, Spec: createSelfSignedIssuerSpec(), }, metav1.CreateOptions{}) - Expect(err).NotTo(HaveOccurred(), "failed to create self signed issuer") + Expect(err).NotTo(HaveOccurred(), "failed to create self-signed issuer") // wait for issuer to be ready By("Waiting for Self Signed Issuer to be Ready") - issuer, err = f.Helper().WaitIssuerReady(issuer, time.Minute*5) + issuer, err = f.Helper().WaitIssuerReady(ctx, issuer, time.Minute*5) Expect(err).ToNot(HaveOccurred()) - return cmmeta.ObjectReference{ + return cmmeta.IssuerReference{ Group: cmapi.SchemeGroupVersion.Group, Kind: cmapi.IssuerKind, Name: issuer.Name, } } -func deleteSelfSignedClusterIssuer(f *framework.Framework, issuer cmmeta.ObjectReference) { - err := f.CertManagerClientSet.CertmanagerV1().ClusterIssuers().Delete(context.TODO(), issuer.Name, metav1.DeleteOptions{}) +func deleteSelfSignedClusterIssuer(ctx context.Context, f *framework.Framework, issuer cmmeta.IssuerReference) { + err := f.CertManagerClientSet.CertmanagerV1().ClusterIssuers().Delete(ctx, issuer.Name, metav1.DeleteOptions{}) Expect(err).NotTo(HaveOccurred()) } -func createSelfSignedClusterIssuer(f *framework.Framework) cmmeta.ObjectReference { +func createSelfSignedClusterIssuer(ctx context.Context, f *framework.Framework) cmmeta.IssuerReference { By("Creating a SelfSigned ClusterIssuer") - issuer, err := f.CertManagerClientSet.CertmanagerV1().ClusterIssuers().Create(context.TODO(), &cmapi.ClusterIssuer{ + issuer, err := f.CertManagerClientSet.CertmanagerV1().ClusterIssuers().Create(ctx, &cmapi.ClusterIssuer{ ObjectMeta: metav1.ObjectMeta{ GenerateName: "selfsigned-cluster-issuer-", }, Spec: createSelfSignedIssuerSpec(), }, metav1.CreateOptions{}) - Expect(err).NotTo(HaveOccurred(), "failed to create self signed issuer") + Expect(err).NotTo(HaveOccurred(), "failed to create self-signed issuer") // wait for issuer to be ready By("Waiting for Self Signed Cluster Issuer to be Ready") - issuer, err = f.Helper().WaitClusterIssuerReady(issuer, time.Minute*5) + issuer, err = f.Helper().WaitClusterIssuerReady(ctx, issuer, time.Minute*5) Expect(err).ToNot(HaveOccurred()) - return cmmeta.ObjectReference{ + return cmmeta.IssuerReference{ Group: cmapi.SchemeGroupVersion.Group, Kind: cmapi.ClusterIssuerKind, Name: issuer.Name, diff --git a/test/e2e/suite/conformance/certificates/suite.go b/test/e2e/suite/conformance/certificates/suite.go index 75e78d501cf..b7ff10db005 100644 --- a/test/e2e/suite/conformance/certificates/suite.go +++ b/test/e2e/suite/conformance/certificates/suite.go @@ -17,17 +17,21 @@ limitations under the License. package certificates import ( - . "github.com/onsi/ginkgo/v2" + "context" + "github.com/cert-manager/cert-manager/e2e-tests/framework" + "github.com/cert-manager/cert-manager/e2e-tests/framework/helper/featureset" + "github.com/cert-manager/cert-manager/internal/controller/feature" cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" - "github.com/cert-manager/cert-manager/test/e2e/framework" - "github.com/cert-manager/cert-manager/test/e2e/framework/helper/featureset" + utilfeature "github.com/cert-manager/cert-manager/pkg/util/feature" + + . "github.com/onsi/ginkgo/v2" ) // Suite defines a reusable conformance test suite that can be used against any // Issuer implementation. type Suite struct { - // Name is the name of the issuer being tested, e.g. SelfSigned, CA, ACME + // Name is the name of the issuer being tested, e.g., SelfSigned, CA, ACME // This field must be provided. Name string @@ -35,14 +39,19 @@ type Suite struct { // returns an ObjectReference to that Issuer that will be used as the // IssuerRef on Certificate resources that this suite creates. // This field must be provided. - CreateIssuerFunc func(*framework.Framework) cmmeta.ObjectReference + CreateIssuerFunc func(context.Context, *framework.Framework) cmmeta.IssuerReference // DeleteIssuerFunc is a function that is run after the test has completed - // in order to clean up resources created for a test (e.g. the resources + // in order to clean up resources created for a test (e.g., the resources // created in CreateIssuerFunc). // This function will be run regardless whether the test passes or fails. // If not specified, this function will be skipped. - DeleteIssuerFunc func(*framework.Framework, cmmeta.ObjectReference) + DeleteIssuerFunc func(context.Context, *framework.Framework, cmmeta.IssuerReference) + + // SharedIPAddress is the IP address that will be used in all certificates + // that require an IP address to be set. For HTTP-01 tests, this IP address + // will be set to the IP address of the Ingress/ Gateway controller. + SharedIPAddress string // DomainSuffix is a suffix used on all domain requests. // This is useful when the issuer being tested requires special @@ -62,18 +71,21 @@ type Suite struct { // certain features due to restrictions in their implementation. UnsupportedFeatures featureset.FeatureSet - // completed is used internally to track whether Complete() has been called - completed bool + // validated is used internally to track whether Validate has been called already. + validated bool } -// complete will validate configuration and set default values. -func (s *Suite) complete(f *framework.Framework) { - if s.Name == "" { - Fail("Name must be set") - } - - if s.CreateIssuerFunc == nil { - Fail("CreateIssuerFunc must be set") +// setup will set default values for fields on the Suite struct. +func (s *Suite) setup(f *framework.Framework) { + if s.SharedIPAddress == "" { + switch s.HTTP01TestType { + case "Ingress": + s.SharedIPAddress = f.Config.Addons.ACMEServer.IngressIP + case "Gateway": + s.SharedIPAddress = f.Config.Addons.ACMEServer.GatewayIP + default: + s.SharedIPAddress = "127.0.0.1" + } } if s.DomainSuffix == "" { @@ -90,42 +102,43 @@ func (s *Suite) complete(f *framework.Framework) { if s.UnsupportedFeatures == nil { s.UnsupportedFeatures = make(featureset.FeatureSet) } +} + +// validate will validate the Suite struct to ensure all required fields are set. +func (s *Suite) validate() { + if s.validated { + return + } - s.completed = true + if s.Name == "" { + Fail("Name must be set") + } + + if s.CreateIssuerFunc == nil { + Fail("CreateIssuerFunc must be set") + } + + if s.HTTP01TestType == "Gateway" { + framework.RequireFeatureGate(utilfeature.DefaultFeatureGate, feature.ExperimentalGatewayAPISupport) + } + + s.validated = true } // it is called by the tests to in Define() to setup and run the test -func (s *Suite) it(f *framework.Framework, name string, fn func(cmmeta.ObjectReference), requiredFeatures ...featureset.Feature) { - if !s.checkFeatures(requiredFeatures...) { +func (s *Suite) it(f *framework.Framework, name string, fn func(context.Context, cmmeta.IssuerReference), requiredFeatures ...featureset.Feature) { + if s.UnsupportedFeatures.HasAny(requiredFeatures...) { return } - It(name, func() { + It(name, func(testingCtx context.Context) { By("Creating an issuer resource") - issuerRef := s.CreateIssuerFunc(f) + issuerRef := s.CreateIssuerFunc(testingCtx, f) defer func() { if s.DeleteIssuerFunc != nil { By("Cleaning up the issuer resource") - s.DeleteIssuerFunc(f, issuerRef) + s.DeleteIssuerFunc(testingCtx, f, issuerRef) } }() - fn(issuerRef) + fn(testingCtx, issuerRef) }) } - -// checkFeatures is a helper function that is used to ensure that the features -// required for a given test case are supported by the suite. -// It will return 'true' if all features are supported and the test should run, -// or return 'false' if any required feature is not supported. -func (s *Suite) checkFeatures(fs ...featureset.Feature) bool { - unsupported := make(featureset.FeatureSet) - for _, f := range fs { - if s.UnsupportedFeatures.Contains(f) { - unsupported.Add(f) - } - } - // all features supported, return early! - if len(unsupported) == 0 { - return true - } - return false -} diff --git a/test/e2e/suite/conformance/certificates/tests.go b/test/e2e/suite/conformance/certificates/tests.go index 38414df395b..fb697ad22d7 100644 --- a/test/e2e/suite/conformance/certificates/tests.go +++ b/test/e2e/suite/conformance/certificates/tests.go @@ -18,713 +18,444 @@ package certificates import ( "context" + "crypto/x509" "crypto/x509/pkix" "encoding/asn1" "encoding/base64" + "encoding/pem" "fmt" "reflect" "strconv" "strings" "time" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - corev1 "k8s.io/api/core/v1" - networkingv1 "k8s.io/api/networking/v1" - networkingv1beta1 "k8s.io/api/networking/v1beta1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/types" - "k8s.io/client-go/util/retry" - "k8s.io/utils/pointer" - - "github.com/cert-manager/cert-manager/internal/controller/feature" - cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" - cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" - "github.com/cert-manager/cert-manager/pkg/util" - utilfeature "github.com/cert-manager/cert-manager/pkg/util/feature" - "github.com/cert-manager/cert-manager/pkg/util/pki" - "github.com/cert-manager/cert-manager/test/e2e/framework" - "github.com/cert-manager/cert-manager/test/e2e/framework/helper/featureset" - "github.com/cert-manager/cert-manager/test/e2e/framework/helper/validation" - "github.com/cert-manager/cert-manager/test/e2e/framework/helper/validation/certificates" - e2eutil "github.com/cert-manager/cert-manager/test/e2e/util" -) - -// Define defines simple conformance tests that can be run against any issuer type. -// If Complete has not been called on this Suite before Define, it will be -// automatically called. -func (s *Suite) Define() { - Describe("with issuer type "+s.Name, func() { - ctx := context.Background() - f := framework.NewDefaultFramework("certificates") - - sharedIPAddress := "127.0.0.1" - - // Wrap this in a BeforeEach else flags will not have been parsed and - // f.Config will not be populated at the time that this code is run. - BeforeEach(func() { - // Special case Public ACME Servers against being run in the standard - // e2e tests. - if strings.Contains(s.Name, "Public ACME Server") && strings.Contains(f.Config.Addons.ACMEServer.URL, "pebble") { - Skip("Not running public ACME tests against local cluster.") - return - } - if s.completed { - return - } - s.complete(f) - - switch s.HTTP01TestType { - case "Ingress": - sharedIPAddress = f.Config.Addons.ACMEServer.IngressIP - case "Gateway": - sharedIPAddress = f.Config.Addons.ACMEServer.GatewayIP - framework.RequireFeatureGate(f, utilfeature.DefaultFeatureGate, feature.ExperimentalGatewayAPISupport) - } - }) - - s.it(f, "should issue a basic, defaulted certificate for a single distinct DNS Name", func(issuerRef cmmeta.ObjectReference) { - testCertificate := &cmapi.Certificate{ - ObjectMeta: metav1.ObjectMeta{ - Name: "testcert", - Namespace: f.Namespace.Name, - }, - Spec: cmapi.CertificateSpec{ - SecretName: "testcert-tls", - IssuerRef: issuerRef, - DNSNames: []string{e2eutil.RandomSubdomain(s.DomainSuffix)}, - }, - } - By("Creating a Certificate") - err := f.CRClient.Create(ctx, testCertificate) - Expect(err).NotTo(HaveOccurred()) - - By("Waiting for the Certificate to be issued...") - testCertificate, err = f.Helper().WaitForCertificateReadyAndDoneIssuing(testCertificate, time.Minute*8) - Expect(err).NotTo(HaveOccurred()) - - By("Validating the issued Certificate...") - err = f.Helper().ValidateCertificate(testCertificate, validation.CertificateSetForUnsupportedFeatureSet(s.UnsupportedFeatures)...) - Expect(err).NotTo(HaveOccurred()) - }, featureset.OnlySAN) - - s.it(f, "should issue a CA certificate with the CA basicConstraint set", func(issuerRef cmmeta.ObjectReference) { - testCertificate := &cmapi.Certificate{ - ObjectMeta: metav1.ObjectMeta{ - Name: "testcert", - Namespace: f.Namespace.Name, - }, - Spec: cmapi.CertificateSpec{ - SecretName: "testcert-tls", - IsCA: true, - IssuerRef: issuerRef, - DNSNames: []string{e2eutil.RandomSubdomain(s.DomainSuffix)}, - }, - } - By("Creating a Certificate") - err := f.CRClient.Create(ctx, testCertificate) - Expect(err).NotTo(HaveOccurred()) - - By("Waiting for the Certificate to be issued...") - testCertificate, err = f.Helper().WaitForCertificateReadyAndDoneIssuing(testCertificate, time.Minute*8) - Expect(err).NotTo(HaveOccurred()) - - By("Validating the issued Certificate...") - err = f.Helper().ValidateCertificate(testCertificate, validation.CertificateSetForUnsupportedFeatureSet(s.UnsupportedFeatures)...) - Expect(err).NotTo(HaveOccurred()) - }, featureset.IssueCAFeature) - - s.it(f, "should issue an ECDSA, defaulted certificate for a single distinct DNS Name", func(issuerRef cmmeta.ObjectReference) { - testCertificate := &cmapi.Certificate{ - ObjectMeta: metav1.ObjectMeta{ - Name: "testcert", - Namespace: f.Namespace.Name, - }, - Spec: cmapi.CertificateSpec{ - SecretName: "testcert-tls", - PrivateKey: &cmapi.CertificatePrivateKey{ - Algorithm: cmapi.ECDSAKeyAlgorithm, - }, - DNSNames: []string{e2eutil.RandomSubdomain(s.DomainSuffix)}, - IssuerRef: issuerRef, - }, - } - By("Creating a Certificate") - err := f.CRClient.Create(ctx, testCertificate) - Expect(err).NotTo(HaveOccurred()) - - By("Waiting for the Certificate to be issued...") - testCertificate, err = f.Helper().WaitForCertificateReadyAndDoneIssuing(testCertificate, time.Minute*8) - Expect(err).NotTo(HaveOccurred()) - - By("Validating the issued Certificate...") - err = f.Helper().ValidateCertificate(testCertificate, validation.CertificateSetForUnsupportedFeatureSet(s.UnsupportedFeatures)...) - Expect(err).NotTo(HaveOccurred()) - }, featureset.ECDSAFeature, featureset.OnlySAN) - - s.it(f, "should issue an Ed25519, defaulted certificate for a single distinct DNS Name", func(issuerRef cmmeta.ObjectReference) { - testCertificate := &cmapi.Certificate{ - ObjectMeta: metav1.ObjectMeta{ - Name: "testcert", - Namespace: f.Namespace.Name, - }, - Spec: cmapi.CertificateSpec{ - SecretName: "testcert-tls", - PrivateKey: &cmapi.CertificatePrivateKey{ - Algorithm: cmapi.Ed25519KeyAlgorithm, - }, - DNSNames: []string{e2eutil.RandomSubdomain(s.DomainSuffix)}, - IssuerRef: issuerRef, - }, - } - By("Creating a Certificate") - err := f.CRClient.Create(ctx, testCertificate) - Expect(err).NotTo(HaveOccurred()) - - By("Waiting for the Certificate to be issued...") - testCertificate, err = f.Helper().WaitForCertificateReadyAndDoneIssuing(testCertificate, time.Minute*8) - Expect(err).NotTo(HaveOccurred()) - - By("Validating the issued Certificate...") - err = f.Helper().ValidateCertificate(testCertificate, validation.CertificateSetForUnsupportedFeatureSet(s.UnsupportedFeatures)...) - Expect(err).NotTo(HaveOccurred()) - }, featureset.OnlySAN, featureset.Ed25519FeatureSet) - - s.it(f, "should issue a basic, defaulted certificate for a single Common Name", func(issuerRef cmmeta.ObjectReference) { - // Some issuers use the CN to define the cert's "ID" - // if one cert manages to be in an error state in the issuer it might throw an error - // this makes the CN more unique - cn := "test-common-name-" + util.RandStringRunes(10) - testCertificate := &cmapi.Certificate{ - ObjectMeta: metav1.ObjectMeta{ - Name: "testcert", - Namespace: f.Namespace.Name, - }, - Spec: cmapi.CertificateSpec{ - SecretName: "testcert-tls", - IssuerRef: issuerRef, - CommonName: cn, - }, - } - By("Creating a Certificate") - err := f.CRClient.Create(ctx, testCertificate) - Expect(err).NotTo(HaveOccurred()) - - By("Waiting for the Certificate to be issued...") - testCertificate, err = f.Helper().WaitForCertificateReadyAndDoneIssuing(testCertificate, time.Minute*8) - Expect(err).NotTo(HaveOccurred()) - - By("Validating the issued Certificate...") - err = f.Helper().ValidateCertificate(testCertificate, validation.CertificateSetForUnsupportedFeatureSet(s.UnsupportedFeatures)...) - Expect(err).NotTo(HaveOccurred()) - }, featureset.CommonNameFeature) - - s.it(f, "should issue a basic, defaulted certificate for a single distinct DNS Name with a literal subject", func(issuerRef cmmeta.ObjectReference) { - framework.RequireFeatureGate(f, utilfeature.DefaultFeatureGate, feature.LiteralCertificateSubject) - // Some issuers use the CN to define the cert's "ID" - // if one cert manages to be in an error state in the issuer it might throw an error - // this makes the CN more unique - host := fmt.Sprintf("*.%s.foo-long.bar.com", util.RandStringRunes(10)) - literalSubject := fmt.Sprintf("CN=%s,OU=FooLong,OU=Bar,OU=Baz,OU=Dept.,O=Corp.", host) - testCertificate := &cmapi.Certificate{ - ObjectMeta: metav1.ObjectMeta{ - Name: "testcert", - Namespace: f.Namespace.Name, - }, - Spec: cmapi.CertificateSpec{ - SecretName: "testcert-tls", - IssuerRef: issuerRef, - LiteralSubject: literalSubject, - DNSNames: []string{host}, - }, - } - By("Creating a Certificate") - err := f.CRClient.Create(ctx, testCertificate) - Expect(err).NotTo(HaveOccurred()) - - By("Waiting for the Certificate to be issued...") - - testCertificate, err = f.Helper().WaitForCertificateReadyAndDoneIssuing(testCertificate, time.Minute*5) - Expect(err).NotTo(HaveOccurred()) - - //type ValidationFunc func(certificate *cmapi.Certificate, secret *corev1.Secret) error - valFunc := func(certificate *cmapi.Certificate, secret *corev1.Secret) error { - certBytes, ok := secret.Data[corev1.TLSCertKey] - if !ok { - return fmt.Errorf("no certificate data found for Certificate %q (secret %q)", certificate.Name, certificate.Spec.SecretName) - } - - createdCert, err := pki.DecodeX509CertificateBytes(certBytes) - if err != nil { - return err - } - - var dns pkix.RDNSequence - rest, err := asn1.Unmarshal(createdCert.RawSubject, &dns) - - if err != nil { - return err - } - - rdnSeq, err2 := pki.ParseSubjectStringToRdnSequence(literalSubject) - - if err2 != nil { - return err2 - } - - fmt.Fprintln(GinkgoWriter, "cert", base64.StdEncoding.EncodeToString(createdCert.RawSubject), dns, err, rest) - if !reflect.DeepEqual(rdnSeq, dns) { - return fmt.Errorf("generated certificate's subject [%s] does not match expected subject [%s]", dns.String(), literalSubject) - } - return nil - } - - By("Validating the issued Certificate...") - - err = f.Helper().ValidateCertificate(testCertificate, valFunc) - Expect(err).NotTo(HaveOccurred()) - }, featureset.LiteralSubjectFeature) - - s.it(f, "should issue an ECDSA, defaulted certificate for a single Common Name", func(issuerRef cmmeta.ObjectReference) { - // Some issuers use the CN to define the cert's "ID" - // if one cert manages to be in an error state in the issuer it might throw an error - // this makes the CN more unique - cn := "test-common-name-" + util.RandStringRunes(10) - testCertificate := &cmapi.Certificate{ - ObjectMeta: metav1.ObjectMeta{ - Name: "testcert", - Namespace: f.Namespace.Name, - }, - Spec: cmapi.CertificateSpec{ - SecretName: "testcert-tls", - PrivateKey: &cmapi.CertificatePrivateKey{ - Algorithm: cmapi.ECDSAKeyAlgorithm, - }, - CommonName: cn, - IssuerRef: issuerRef, - }, - } - By("Creating a Certificate") - err := f.CRClient.Create(ctx, testCertificate) - Expect(err).NotTo(HaveOccurred()) - - By("Waiting for the Certificate to be issued...") - testCertificate, err = f.Helper().WaitForCertificateReadyAndDoneIssuing(testCertificate, time.Minute*8) - Expect(err).NotTo(HaveOccurred()) - - By("Validating the issued Certificate...") - err = f.Helper().ValidateCertificate(testCertificate, validation.CertificateSetForUnsupportedFeatureSet(s.UnsupportedFeatures)...) - Expect(err).NotTo(HaveOccurred()) - }, featureset.ECDSAFeature, featureset.CommonNameFeature) - - s.it(f, "should issue an Ed25519, defaulted certificate for a single Common Name", func(issuerRef cmmeta.ObjectReference) { - // Some issuers use the CN to define the cert's "ID" - // if one cert manages to be in an error state in the issuer it might throw an error - // this makes the CN more unique - cn := "test-common-name-" + util.RandStringRunes(10) - testCertificate := &cmapi.Certificate{ - ObjectMeta: metav1.ObjectMeta{ - Name: "testcert", - Namespace: f.Namespace.Name, - }, - Spec: cmapi.CertificateSpec{ - SecretName: "testcert-tls", - PrivateKey: &cmapi.CertificatePrivateKey{ - Algorithm: cmapi.Ed25519KeyAlgorithm, - }, - CommonName: cn, - IssuerRef: issuerRef, - }, - } - By("Creating a Certificate") - err := f.CRClient.Create(ctx, testCertificate) - Expect(err).NotTo(HaveOccurred()) - - By("Waiting for the Certificate to be issued...") - testCertificate, err = f.Helper().WaitForCertificateReadyAndDoneIssuing(testCertificate, time.Minute*8) - Expect(err).NotTo(HaveOccurred()) - - By("Validating the issued Certificate...") - err = f.Helper().ValidateCertificate(testCertificate, validation.CertificateSetForUnsupportedFeatureSet(s.UnsupportedFeatures)...) - Expect(err).NotTo(HaveOccurred()) - }, featureset.Ed25519FeatureSet, featureset.CommonNameFeature) - - s.it(f, "should issue a certificate that defines an IP Address", func(issuerRef cmmeta.ObjectReference) { - testCertificate := &cmapi.Certificate{ - ObjectMeta: metav1.ObjectMeta{ - Name: "testcert", - Namespace: f.Namespace.Name, - }, - Spec: cmapi.CertificateSpec{ - SecretName: "testcert-tls", - IPAddresses: []string{sharedIPAddress}, - IssuerRef: issuerRef, - }, - } - By("Creating a Certificate") - err := f.CRClient.Create(ctx, testCertificate) - Expect(err).NotTo(HaveOccurred()) - - By("Waiting for the Certificate to be issued...") - testCertificate, err = f.Helper().WaitForCertificateReadyAndDoneIssuing(testCertificate, time.Minute*8) - Expect(err).NotTo(HaveOccurred()) - - By("Validating the issued Certificate...") - err = f.Helper().ValidateCertificate(testCertificate, validation.CertificateSetForUnsupportedFeatureSet(s.UnsupportedFeatures)...) - Expect(err).NotTo(HaveOccurred()) - }, featureset.IPAddressFeature) - - s.it(f, "should issue a certificate that defines a DNS Name and IP Address", func(issuerRef cmmeta.ObjectReference) { - testCertificate := &cmapi.Certificate{ - ObjectMeta: metav1.ObjectMeta{ - Name: "testcert", - Namespace: f.Namespace.Name, - }, - Spec: cmapi.CertificateSpec{ - SecretName: "testcert-tls", - IPAddresses: []string{sharedIPAddress}, - DNSNames: []string{e2eutil.RandomSubdomain(s.DomainSuffix)}, - IssuerRef: issuerRef, - }, - } - By("Creating a Certificate") - err := f.CRClient.Create(ctx, testCertificate) - Expect(err).NotTo(HaveOccurred()) - - By("Waiting for the Certificate to be issued...") - testCertificate, err = f.Helper().WaitForCertificateReadyAndDoneIssuing(testCertificate, time.Minute*8) - Expect(err).NotTo(HaveOccurred()) - - By("Validating the issued Certificate...") - err = f.Helper().ValidateCertificate(testCertificate, validation.CertificateSetForUnsupportedFeatureSet(s.UnsupportedFeatures)...) - Expect(err).NotTo(HaveOccurred()) - }, featureset.OnlySAN, featureset.IPAddressFeature) - - s.it(f, "should issue a certificate that defines a Common Name and IP Address", func(issuerRef cmmeta.ObjectReference) { - // Some issuers use the CN to define the cert's "ID" - // if one cert manages to be in an error state in the issuer it might throw an error - // this makes the CN more unique - cn := "test-common-name-" + util.RandStringRunes(10) - testCertificate := &cmapi.Certificate{ - ObjectMeta: metav1.ObjectMeta{ - Name: "testcert", - Namespace: f.Namespace.Name, - }, - Spec: cmapi.CertificateSpec{ - SecretName: "testcert-tls", - CommonName: cn, - IPAddresses: []string{sharedIPAddress}, - IssuerRef: issuerRef, - }, - } - By("Creating a Certificate") - err := f.CRClient.Create(ctx, testCertificate) - Expect(err).NotTo(HaveOccurred()) - - By("Waiting for the Certificate to be issued...") - testCertificate, err = f.Helper().WaitForCertificateReadyAndDoneIssuing(testCertificate, time.Minute*8) - Expect(err).NotTo(HaveOccurred()) - - By("Validating the issued Certificate...") - err = f.Helper().ValidateCertificate(testCertificate, validation.CertificateSetForUnsupportedFeatureSet(s.UnsupportedFeatures)...) - Expect(err).NotTo(HaveOccurred()) - }, featureset.CommonNameFeature, featureset.IPAddressFeature) - - s.it(f, "should issue a certificate that defines an Email Address", func(issuerRef cmmeta.ObjectReference) { - testCertificate := &cmapi.Certificate{ - ObjectMeta: metav1.ObjectMeta{ - Name: "testcert", - Namespace: f.Namespace.Name, - }, - Spec: cmapi.CertificateSpec{ - SecretName: "testcert-tls", - EmailAddresses: []string{"alice@example.com"}, - IssuerRef: issuerRef, - }, - } - By("Creating a Certificate") - err := f.CRClient.Create(ctx, testCertificate) - Expect(err).NotTo(HaveOccurred()) - - By("Waiting for the Certificate to be issued...") - testCertificate, err = f.Helper().WaitForCertificateReadyAndDoneIssuing(testCertificate, time.Minute*8) - Expect(err).NotTo(HaveOccurred()) - - By("Validating the issued Certificate...") - err = f.Helper().ValidateCertificate(testCertificate, validation.CertificateSetForUnsupportedFeatureSet(s.UnsupportedFeatures)...) - Expect(err).NotTo(HaveOccurred()) - }, featureset.EmailSANsFeature, featureset.OnlySAN) - - s.it(f, "should issue a certificate that defines a Common Name and URI SAN", func(issuerRef cmmeta.ObjectReference) { - // Some issuers use the CN to define the cert's "ID" - // if one cert manages to be in an error state in the issuer it might throw an error - // this makes the CN more unique - cn := "test-common-name-" + util.RandStringRunes(10) - testCertificate := &cmapi.Certificate{ - ObjectMeta: metav1.ObjectMeta{ - Name: "testcert", - Namespace: f.Namespace.Name, - }, - Spec: cmapi.CertificateSpec{ - SecretName: "testcert-tls", - CommonName: cn, - URIs: []string{"spiffe://cluster.local/ns/sandbox/sa/foo"}, - IssuerRef: issuerRef, - }, - } - By("Creating a Certificate") - err := f.CRClient.Create(ctx, testCertificate) - Expect(err).NotTo(HaveOccurred()) - - By("Waiting for the Certificate to be issued...") - testCertificate, err = f.Helper().WaitForCertificateReadyAndDoneIssuing(testCertificate, time.Minute*8) - Expect(err).NotTo(HaveOccurred()) - - By("Validating the issued Certificate...") - err = f.Helper().ValidateCertificate(testCertificate, validation.CertificateSetForUnsupportedFeatureSet(s.UnsupportedFeatures)...) - Expect(err).NotTo(HaveOccurred()) - }, featureset.URISANsFeature, featureset.CommonNameFeature) - - s.it(f, "should issue a certificate that defines a 2 distinct DNS Names with one copied to the Common Name", func(issuerRef cmmeta.ObjectReference) { - testCertificate := &cmapi.Certificate{ - ObjectMeta: metav1.ObjectMeta{ - Name: "testcert", - Namespace: f.Namespace.Name, - }, - Spec: cmapi.CertificateSpec{ - SecretName: "testcert-tls", - CommonName: e2eutil.RandomSubdomain(s.DomainSuffix), - IssuerRef: issuerRef, - }, - } - testCertificate.Spec.DNSNames = []string{ - testCertificate.Spec.CommonName, e2eutil.RandomSubdomain(s.DomainSuffix), - } + corev1 "k8s.io/api/core/v1" + networkingv1 "k8s.io/api/networking/v1" + networkingv1beta1 "k8s.io/api/networking/v1beta1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/rand" + "k8s.io/apimachinery/pkg/util/sets" + "k8s.io/client-go/util/retry" + "k8s.io/utils/ptr" - By("Creating a Certificate") - err := f.CRClient.Create(ctx, testCertificate) - Expect(err).NotTo(HaveOccurred()) + "github.com/cert-manager/cert-manager/e2e-tests/framework" + "github.com/cert-manager/cert-manager/e2e-tests/framework/helper/featureset" + "github.com/cert-manager/cert-manager/e2e-tests/framework/helper/validation" + "github.com/cert-manager/cert-manager/e2e-tests/framework/helper/validation/certificates" + e2eutil "github.com/cert-manager/cert-manager/e2e-tests/util" + "github.com/cert-manager/cert-manager/internal/controller/feature" + cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" + cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" + utilfeature "github.com/cert-manager/cert-manager/pkg/util/feature" + "github.com/cert-manager/cert-manager/pkg/util/pki" + "github.com/cert-manager/cert-manager/test/unit/gen" - By("Waiting for the Certificate to be issued...") - testCertificate, err = f.Helper().WaitForCertificateReadyAndDoneIssuing(testCertificate, time.Minute*8) - Expect(err).NotTo(HaveOccurred()) + . "github.com/cert-manager/cert-manager/e2e-tests/framework/matcher" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) - By("Validating the issued Certificate...") - err = f.Helper().ValidateCertificate(testCertificate, validation.CertificateSetForUnsupportedFeatureSet(s.UnsupportedFeatures)...) - Expect(err).NotTo(HaveOccurred()) - }, featureset.CommonNameFeature) +// Define defines simple conformance tests that can be run against any issuer type. +// If Complete has not been called on this Suite before Define, it will be +// automatically called. +func (s *Suite) Define() { + Describe("with issuer type "+s.Name, func() { + f := framework.NewDefaultFramework("certificates") + s.setup(f) - s.it(f, "should issue a certificate that defines a distinct DNS Name and another distinct Common Name", func(issuerRef cmmeta.ObjectReference) { - testCertificate := &cmapi.Certificate{ - ObjectMeta: metav1.ObjectMeta{ - Name: "testcert", - Namespace: f.Namespace.Name, - }, - Spec: cmapi.CertificateSpec{ - SecretName: "testcert-tls", - CommonName: e2eutil.RandomSubdomain(s.DomainSuffix), - IssuerRef: issuerRef, - DNSNames: []string{e2eutil.RandomSubdomain(s.DomainSuffix)}, - }, + BeforeEach(func(testingCtx context.Context) { + // Special case Public ACME Servers against being run in the standard + // e2e tests. + if strings.Contains(s.Name, "Public ACME Server") && strings.Contains(f.Config.Addons.ACMEServer.URL, "pebble") { + Skip("Not running public ACME tests against local cluster.") + return } + s.validate() + }) - By("Creating a Certificate") - err := f.CRClient.Create(ctx, testCertificate) - Expect(err).NotTo(HaveOccurred()) - - By("Waiting for the Certificate to be issued...") - testCertificate, err = f.Helper().WaitForCertificateReadyAndDoneIssuing(testCertificate, time.Minute*8) - Expect(err).NotTo(HaveOccurred()) - - By("Validating the issued Certificate...") - err = f.Helper().ValidateCertificate(testCertificate, validation.CertificateSetForUnsupportedFeatureSet(s.UnsupportedFeatures)...) - Expect(err).NotTo(HaveOccurred()) - }, featureset.CommonNameFeature) - - s.it(f, "should issue a certificate that defines a DNS Name and sets a duration", func(issuerRef cmmeta.ObjectReference) { - testCertificate := &cmapi.Certificate{ - ObjectMeta: metav1.ObjectMeta{ - Name: "testcert", - Namespace: f.Namespace.Name, - }, - Spec: cmapi.CertificateSpec{ - SecretName: "testcert-tls", - IssuerRef: issuerRef, - DNSNames: []string{e2eutil.RandomSubdomain(s.DomainSuffix)}, - Duration: &metav1.Duration{ - Duration: time.Hour * 896, + type testCase struct { + name string // ginkgo v2 does not support using map[string] to store the test names (#5345) + certModifiers []gen.CertificateModifier + // The list of features that are required by the Issuer for the test to + // run. + requiredFeatures []featureset.Feature + // Extra validations which may be needed for testing, on a test case by + // case basis. All default validations will be run on every test. + extraValidations []certificates.ValidationFunc + } + + tests := []testCase{ + { + name: "should issue a basic, defaulted certificate for a single distinct DNS Name", + certModifiers: []gen.CertificateModifier{ + gen.SetCertificateDNSNames(e2eutil.RandomSubdomain(s.DomainSuffix)), + }, + requiredFeatures: []featureset.Feature{featureset.OnlySAN}, + }, + { + name: "should issue a CA certificate with the CA basicConstraint set", + certModifiers: []gen.CertificateModifier{ + gen.SetCertificateIsCA(true), + gen.SetCertificateDNSNames(e2eutil.RandomSubdomain(s.DomainSuffix)), + }, + requiredFeatures: []featureset.Feature{featureset.IssueCAFeature}, + }, + { + name: "should issue an ECDSA, defaulted certificate for a single distinct DNS Name", + certModifiers: []gen.CertificateModifier{ + func(c *cmapi.Certificate) { + c.Spec.PrivateKey = &cmapi.CertificatePrivateKey{ + Algorithm: cmapi.ECDSAKeyAlgorithm, + } }, - }, - } - By("Creating a Certificate") - err := f.CRClient.Create(ctx, testCertificate) - Expect(err).NotTo(HaveOccurred()) - - By("Waiting for the Certificate to be issued...") - testCertificate, err = f.Helper().WaitForCertificateReadyAndDoneIssuing(testCertificate, time.Minute*8) - Expect(err).NotTo(HaveOccurred()) - - By("Validating the issued Certificate...") - err = f.Helper().ValidateCertificate(testCertificate, validation.CertificateSetForUnsupportedFeatureSet(s.UnsupportedFeatures)...) - Expect(err).NotTo(HaveOccurred()) - - // We set a weird time here as the duration with should never be used as - // a default by an issuer. This lets us test issuers are using our given - // duration. - // We set a 30 second buffer time here since Vault issues certificates - // with an extra 30 seconds on its duration. - f.CertificateDurationValid(testCertificate, time.Hour*896, 30*time.Second) - }, featureset.DurationFeature, featureset.OnlySAN) - - s.it(f, "should issue a certificate that defines a wildcard DNS Name", func(issuerRef cmmeta.ObjectReference) { - testCertificate := &cmapi.Certificate{ - ObjectMeta: metav1.ObjectMeta{ - Name: "testcert", - Namespace: f.Namespace.Name, - }, - Spec: cmapi.CertificateSpec{ - SecretName: "testcert-tls", - IssuerRef: issuerRef, - DNSNames: []string{"*." + e2eutil.RandomSubdomain(s.DomainSuffix)}, - }, - } - By("Creating a Certificate") - err := f.CRClient.Create(ctx, testCertificate) - Expect(err).NotTo(HaveOccurred()) - - By("Waiting for the Certificate to be issued...") - testCertificate, err = f.Helper().WaitForCertificateReadyAndDoneIssuing(testCertificate, time.Minute*8) - Expect(err).NotTo(HaveOccurred()) - - By("Validating the issued Certificate...") - err = f.Helper().ValidateCertificate(testCertificate, validation.CertificateSetForUnsupportedFeatureSet(s.UnsupportedFeatures)...) - Expect(err).NotTo(HaveOccurred()) - }, featureset.WildcardsFeature, featureset.OnlySAN) - - s.it(f, "should issue a certificate that includes only a URISANs name", func(issuerRef cmmeta.ObjectReference) { - testCertificate := &cmapi.Certificate{ - ObjectMeta: metav1.ObjectMeta{ - Name: "testcert", - Namespace: f.Namespace.Name, - }, - Spec: cmapi.CertificateSpec{ - SecretName: "testcert-tls", - URIs: []string{ - "spiffe://cluster.local/ns/sandbox/sa/foo", + gen.SetCertificateDNSNames(e2eutil.RandomSubdomain(s.DomainSuffix)), + }, + requiredFeatures: []featureset.Feature{featureset.ECDSAFeature, featureset.OnlySAN}, + }, + { + name: "should issue an Ed25519, defaulted certificate for a single distinct DNS Name", + certModifiers: []gen.CertificateModifier{ + func(c *cmapi.Certificate) { + c.Spec.PrivateKey = &cmapi.CertificatePrivateKey{ + Algorithm: cmapi.Ed25519KeyAlgorithm, + } + }, + gen.SetCertificateDNSNames(e2eutil.RandomSubdomain(s.DomainSuffix)), + }, + requiredFeatures: []featureset.Feature{featureset.OnlySAN, featureset.Ed25519FeatureSet}, + }, + { + name: "should issue a basic, defaulted certificate for a single Common Name", + certModifiers: []gen.CertificateModifier{ + // Some issuers use the CN to define the cert's "ID" + // if one cert manages to be in an error state in the issuer it might throw an error + // this makes the CN more unique + gen.SetCertificateCommonName("test-common-name-" + rand.String(10)), + }, + requiredFeatures: []featureset.Feature{featureset.CommonNameFeature}, + }, + { + name: "should issue a certificate with a couple valid otherName SAN values set as well as an emailAddress", + certModifiers: []gen.CertificateModifier{ + gen.SetCertificateOtherNames( + cmapi.OtherName{ + OID: "1.3.6.1.4.1.311.20.2.3", + UTF8Value: "upn@domain.test", + }, + cmapi.OtherName{ + OID: "1.3.6.1.4.1.311.20.2.3", + UTF8Value: "upn@domain2.test", + }, + ), + gen.SetCertificateEmails("email@domain.test"), + gen.SetCertificateCommonName("someCN"), + }, + extraValidations: []certificates.ValidationFunc{ + func(certificate *cmapi.Certificate, secret *corev1.Secret) error { + certBytes, ok := secret.Data[corev1.TLSCertKey] + if !ok { + return fmt.Errorf("no certificate data found for Certificate %q (secret %q)", certificate.Name, certificate.Spec.SecretName) + } + + pemBlock, _ := pem.Decode(certBytes) + cert, err := x509.ParseCertificate(pemBlock.Bytes) + Expect(err).ToNot(HaveOccurred()) + + By("Including the appropriate GeneralNames ( RFC822 email Address and OtherName) in generated Certificate") + /* openssl req -nodes -newkey rsa:2048 -subj "/CN=someCN" \ + -addext 'subjectAltName=email:email@domain.test,otherName:msUPN;utf8:upn@domain2.test,otherName:msUPN;UTF8:upn@domain.test' -x509 -out server.crt + */ + Expect(cert.Extensions).Should(HaveSameSANsAs(`-----BEGIN CERTIFICATE----- +MIIDZjCCAk6gAwIBAgIUWmJ+z4OCWZg4V3XjSfEN+hItXjUwDQYJKoZIhvcNAQEL +BQAwETEPMA0GA1UEAwwGc29tZUNOMB4XDTI0MDEwMzA4NTU1NloXDTI0MDIwMjA4 +NTU1NlowETEPMA0GA1UEAwwGc29tZUNOMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A +MIIBCgKCAQEAr5xmoX7/vp+wid+gOvbigYXLP/OvILyRpyj/e6IqJqj83+ImMtHt +QtOHN/E1bYQ8juVXqhhwy5BDXV6qHCfEjAKJF/oHpdVGk4GoMV/noAjbyAdqxFb+ +Cr/62sZWFHcuBuh/msJj6MWWAYZkb6HPiyDaV4HdRrrefifQnBGmsO0DE2guy7Yr +CMnE25H0yZ6z1e2tecsXSEkHyPNpil39oJ+1dT3UG8coU32rMOMKs7Za/xF0yMtU +TrCzZ/ylFL4vJi/s0i9zgjBQloJud+s3J+MnbYFgv0MIaosZXuk7/FR0HNIM19Zw +VLH6dgVCcF02bnnVpOAd6KPEzdqjYdDv/QIDAQABo4G1MIGyMB0GA1UdDgQWBBRF +KVGbYoD2H1NE47wJL6xFQ83Q+DAfBgNVHSMEGDAWgBRFKVGbYoD2H1NE47wJL6xF +Q83Q+DAPBgNVHRMBAf8EBTADAQH/MF8GA1UdEQRYMFaBEWVtYWlsQGRvbWFpbi50 +ZXN0oCAGCisGAQQBgjcUAgOgEgwQdXBuQGRvbWFpbjIudGVzdKAfBgorBgEEAYI3 +FAIDoBEMD3VwbkBkb21haW4udGVzdDANBgkqhkiG9w0BAQsFAAOCAQEAmrouGUth +yyL3jJTe2XZCqbjNgwXrT5N8SwF8JrPNzTyuh4Qiug3N/3djmq4N4V60UAJU8Xpr +Uf8TZBQwF6VD/TSvvJKB3qjSW0T46cF++10ueEgT7mT/icyPeiMw1syWpQlciIvv +WZ/PIvHm2sTB+v8v9rhiFDyQxlnvbtG0D0TV/dEZmyrqfrBpWOP8TFgexRMQU2/4 +Gb9fYHRK+LBKRTFudEXNWcDYxK3umfht/ZUsMeWUP70XaNsTd9tQWRsctxGpU10s +cKK5t8N1YDX5CV+01X3vvxpM3ciYuCY9y+lSegrIEI+izRyD7P9KaZlwMaYmsBZq +/XMa5c3nWcbXcA== +-----END CERTIFICATE----- +`)) + return nil }, - IssuerRef: issuerRef, }, - } - By("Creating a Certificate") - err := f.CRClient.Create(ctx, testCertificate) - Expect(err).NotTo(HaveOccurred()) - - By("Waiting for the Certificate to be issued...") - testCertificate, err = f.Helper().WaitForCertificateReadyAndDoneIssuing(testCertificate, time.Minute*8) - Expect(err).NotTo(HaveOccurred()) - - By("Validating the issued Certificate...") - err = f.Helper().ValidateCertificate(testCertificate, validation.CertificateSetForUnsupportedFeatureSet(s.UnsupportedFeatures)...) - Expect(err).NotTo(HaveOccurred()) - }, featureset.URISANsFeature, featureset.OnlySAN) - - s.it(f, "should issue a certificate that includes arbitrary key usages", func(issuerRef cmmeta.ObjectReference) { - testCertificate := &cmapi.Certificate{ - ObjectMeta: metav1.ObjectMeta{ - Name: "testcert", - Namespace: f.Namespace.Name, + requiredFeatures: []featureset.Feature{featureset.OtherNamesFeature}, + }, + { + name: "should issue a basic, defaulted certificate for a single distinct DNS Name with a literal subject", + certModifiers: func() []gen.CertificateModifier { + host := fmt.Sprintf("*.%s.foo-long.bar.com", rand.String(10)) + literalSubject := fmt.Sprintf("CN=%s,OU=FooLong,OU=Bar,OU=Baz,OU=Dept.,O=Corp.", host) + + return []gen.CertificateModifier{ + func(c *cmapi.Certificate) { + c.Spec.LiteralSubject = literalSubject + }, + gen.SetCertificateDNSNames(host), + } + }(), + extraValidations: []certificates.ValidationFunc{ + func(certificate *cmapi.Certificate, secret *corev1.Secret) error { + certBytes, ok := secret.Data[corev1.TLSCertKey] + if !ok { + return fmt.Errorf("no certificate data found for Certificate %q (secret %q)", certificate.Name, certificate.Spec.SecretName) + } + + createdCert, err := pki.DecodeX509CertificateBytes(certBytes) + if err != nil { + return err + } + + var dns pkix.RDNSequence + rest, err := asn1.Unmarshal(createdCert.RawSubject, &dns) + + if err != nil { + return err + } + + rdnSeq, err2 := pki.UnmarshalSubjectStringToRDNSequence(certificate.Spec.LiteralSubject) + + if err2 != nil { + return err2 + } + + fmt.Fprintln(GinkgoWriter, "cert", base64.StdEncoding.EncodeToString(createdCert.RawSubject), dns, err, string(rest)) + if !reflect.DeepEqual(rdnSeq, dns) { + return fmt.Errorf("generated certificate's subject [%s] does not match expected subject [%s]", dns.String(), certificate.Spec.LiteralSubject) + } + return nil + }, }, - Spec: cmapi.CertificateSpec{ - SecretName: "testcert-tls", - DNSNames: []string{e2eutil.RandomSubdomain(s.DomainSuffix)}, - IssuerRef: issuerRef, - Usages: []cmapi.KeyUsage{ + requiredFeatures: []featureset.Feature{featureset.LiteralSubjectFeature}, + }, + { + name: "should issue an ECDSA, defaulted certificate for a single Common Name", + certModifiers: []gen.CertificateModifier{ + func(c *cmapi.Certificate) { + c.Spec.PrivateKey = &cmapi.CertificatePrivateKey{ + Algorithm: cmapi.ECDSAKeyAlgorithm, + } + }, + // Some issuers use the CN to define the cert's "ID" + // if one cert manages to be in an error state in the issuer it might throw an error + // this makes the CN more unique + gen.SetCertificateCommonName("test-common-name-" + rand.String(10)), + }, + requiredFeatures: []featureset.Feature{featureset.ECDSAFeature, featureset.CommonNameFeature}, + }, + { + name: "should issue an Ed25519, defaulted certificate for a single Common Name", + certModifiers: []gen.CertificateModifier{ + func(c *cmapi.Certificate) { + c.Spec.PrivateKey = &cmapi.CertificatePrivateKey{ + Algorithm: cmapi.Ed25519KeyAlgorithm, + } + }, + // Some issuers use the CN to define the cert's "ID" + // if one cert manages to be in an error state in the issuer it might throw an error + // this makes the CN more unique + gen.SetCertificateCommonName("test-common-name-" + rand.String(10)), + }, + requiredFeatures: []featureset.Feature{featureset.Ed25519FeatureSet, featureset.CommonNameFeature}, + }, + { + name: "should issue a certificate that defines an IP Address", + certModifiers: []gen.CertificateModifier{ + gen.SetCertificateIPs(s.SharedIPAddress), + }, + requiredFeatures: []featureset.Feature{featureset.IPAddressFeature}, + }, + { + name: "should issue a certificate that defines a DNS Name and IP Address", + certModifiers: []gen.CertificateModifier{ + gen.SetCertificateIPs(s.SharedIPAddress), + gen.SetCertificateDNSNames(e2eutil.RandomSubdomain(s.DomainSuffix)), + }, + requiredFeatures: []featureset.Feature{featureset.OnlySAN, featureset.IPAddressFeature}, + }, + { + name: "should issue a certificate that defines a Common Name and IP Address", + certModifiers: []gen.CertificateModifier{ + gen.SetCertificateIPs(s.SharedIPAddress), + // Some issuers use the CN to define the cert's "ID" + // if one cert manages to be in an error state in the issuer it might throw an error + // this makes the CN more unique + gen.SetCertificateCommonName("test-common-name-" + rand.String(10)), + }, + requiredFeatures: []featureset.Feature{featureset.CommonNameFeature, featureset.IPAddressFeature}, + }, + { + name: "should issue a certificate that defines an Email Address", + certModifiers: []gen.CertificateModifier{ + gen.SetCertificateEmails("alice@example.com"), + }, + requiredFeatures: []featureset.Feature{featureset.EmailSANsFeature, featureset.OnlySAN}, + }, + { + name: "should issue a certificate that defines a Common Name and URI SAN", + certModifiers: []gen.CertificateModifier{ + gen.SetCertificateURIs("spiffe://cluster.local/ns/sandbox/sa/foo"), + // Some issuers use the CN to define the cert's "ID" + // if one cert manages to be in an error state in the issuer it might throw an error + // this makes the CN more unique + gen.SetCertificateCommonName("test-common-name-" + rand.String(10)), + }, + requiredFeatures: []featureset.Feature{featureset.URISANsFeature, featureset.CommonNameFeature}, + }, + { + name: "should issue a certificate that defines a 2 distinct DNS Names with one copied to the Common Name", + certModifiers: func() []gen.CertificateModifier { + commonName := e2eutil.RandomSubdomain(s.DomainSuffix) + + return []gen.CertificateModifier{ + gen.SetCertificateCommonName(commonName), + gen.SetCertificateDNSNames(commonName, e2eutil.RandomSubdomain(s.DomainSuffix)), + } + }(), + requiredFeatures: []featureset.Feature{featureset.CommonNameFeature}, + }, + { + name: "should issue a certificate that defines a distinct DNS Name and another distinct Common Name", + certModifiers: []gen.CertificateModifier{ + gen.SetCertificateCommonName(e2eutil.RandomSubdomain(s.DomainSuffix)), + gen.SetCertificateDNSNames(e2eutil.RandomSubdomain(s.DomainSuffix)), + }, + requiredFeatures: []featureset.Feature{featureset.CommonNameFeature}, + }, + { + name: "should issue a certificate that defines a DNS Name and sets a duration", + certModifiers: []gen.CertificateModifier{ + gen.SetCertificateDNSNames(e2eutil.RandomSubdomain(s.DomainSuffix)), + gen.SetCertificateDuration(&metav1.Duration{Duration: time.Hour * 896}), + }, + requiredFeatures: []featureset.Feature{featureset.DurationFeature, featureset.OnlySAN}, + }, + { + name: "should issue a certificate that defines a wildcard DNS Name", + certModifiers: []gen.CertificateModifier{ + gen.SetCertificateDNSNames("*." + e2eutil.RandomSubdomain(s.DomainSuffix)), + }, + requiredFeatures: []featureset.Feature{featureset.WildcardsFeature, featureset.OnlySAN}, + }, + { + name: "should issue a certificate that includes only a URISANs name", + certModifiers: []gen.CertificateModifier{ + gen.SetCertificateURIs("spiffe://cluster.local/ns/sandbox/sa/foo"), + }, + requiredFeatures: []featureset.Feature{featureset.URISANsFeature, featureset.OnlySAN}, + }, + { + name: "should issue a certificate that includes arbitrary key usages", + certModifiers: []gen.CertificateModifier{ + gen.SetCertificateDNSNames(e2eutil.RandomSubdomain(s.DomainSuffix)), + gen.SetCertificateKeyUsages( cmapi.UsageSigning, cmapi.UsageDataEncipherment, cmapi.UsageServerAuth, cmapi.UsageClientAuth, - }, - }, - } - By("Creating a Certificate") - err := f.CRClient.Create(ctx, testCertificate) - Expect(err).NotTo(HaveOccurred()) - - By("Waiting for the Certificate to be issued...") - testCertificate, err = f.Helper().WaitForCertificateReadyAndDoneIssuing(testCertificate, time.Minute*8) - Expect(err).NotTo(HaveOccurred()) - - By("Validating the issued Certificate...") - - validations := []certificates.ValidationFunc{ - certificates.ExpectKeyUsageExtKeyUsageClientAuth, - certificates.ExpectKeyUsageExtKeyUsageServerAuth, - certificates.ExpectKeyUsageUsageDigitalSignature, - certificates.ExpectKeyUsageUsageDataEncipherment, - } - validations = append(validations, validation.CertificateSetForUnsupportedFeatureSet(s.UnsupportedFeatures)...) - - err = f.Helper().ValidateCertificate(testCertificate, validations...) - Expect(err).NotTo(HaveOccurred()) - }, featureset.KeyUsagesFeature, featureset.OnlySAN) - - s.it(f, "should issue another certificate with the same private key if the existing certificate and CertificateRequest are deleted", func(issuerRef cmmeta.ObjectReference) { - testCertificate := &cmapi.Certificate{ - ObjectMeta: metav1.ObjectMeta{ - Name: "testcert", - Namespace: f.Namespace.Name, - }, - Spec: cmapi.CertificateSpec{ - SecretName: "testcert-tls", - DNSNames: []string{e2eutil.RandomSubdomain(s.DomainSuffix)}, - IssuerRef: issuerRef, - }, - } - By("Creating a Certificate") - err := f.CRClient.Create(ctx, testCertificate) - Expect(err).NotTo(HaveOccurred()) - - By("Waiting for the Certificate to be issued...") - testCertificate, err = f.Helper().WaitForCertificateReadyAndDoneIssuing(testCertificate, time.Minute*8) - Expect(err).NotTo(HaveOccurred()) - - By("Validating the issued Certificate...") - err = f.Helper().ValidateCertificate(testCertificate, validation.CertificateSetForUnsupportedFeatureSet(s.UnsupportedFeatures)...) - Expect(err).NotTo(HaveOccurred()) + ), + }, + extraValidations: []certificates.ValidationFunc{ + certificates.ExpectKeyUsageExtKeyUsageClientAuth, + certificates.ExpectKeyUsageExtKeyUsageServerAuth, + certificates.ExpectKeyUsageUsageDigitalSignature, + certificates.ExpectKeyUsageUsageDataEncipherment, + }, + requiredFeatures: []featureset.Feature{featureset.KeyUsagesFeature, featureset.OnlySAN}, + }, + { + name: "should issue a certificate that defines a long domain", + certModifiers: func() []gen.CertificateModifier { + const maxLengthOfDomainSegment = 63 + return []gen.CertificateModifier{ + gen.SetCertificateDNSNames(e2eutil.RandomSubdomainLength(s.DomainSuffix, maxLengthOfDomainSegment)), + } + }(), + requiredFeatures: []featureset.Feature{featureset.OnlySAN, featureset.LongDomainFeatureSet}, + }, + { + name: "should issue a certificate that defines a wildcard DNS Name and its apex DNS Name", + certModifiers: func() []gen.CertificateModifier { + dnsDomain := e2eutil.RandomSubdomain(s.DomainSuffix) + + return []gen.CertificateModifier{ + gen.SetCertificateDNSNames("*."+dnsDomain, dnsDomain), + } + }(), + requiredFeatures: []featureset.Feature{featureset.WildcardsFeature, featureset.OnlySAN}, + }, + } + + defineTest := func(test testCase) { + s.it(f, test.name, func(ctx context.Context, issuerRef cmmeta.IssuerReference) { + requiredFeatures := sets.New(test.requiredFeatures...) + + if requiredFeatures.Has(featureset.OtherNamesFeature) { + framework.RequireFeatureGate(utilfeature.DefaultFeatureGate, feature.OtherNames) + } - By("Deleting existing certificate data in Secret") - sec, err := f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name). - Get(context.TODO(), testCertificate.Spec.SecretName, metav1.GetOptions{}) - Expect(err).NotTo(HaveOccurred(), "failed to get secret containing signed certificate key pair data") + if requiredFeatures.Has(featureset.LiteralSubjectFeature) { + framework.RequireFeatureGate(utilfeature.DefaultFeatureGate, feature.LiteralCertificateSubject) + } - sec = sec.DeepCopy() - crtPEM1 := sec.Data[corev1.TLSCertKey] - crt1, err := pki.DecodeX509CertificateBytes(crtPEM1) - Expect(err).NotTo(HaveOccurred(), "failed to get decode first signed certificate data") + randomTestID := rand.String(10) + certificate := &cmapi.Certificate{ + ObjectMeta: metav1.ObjectMeta{ + Name: "e2e-conformance-" + randomTestID, + Namespace: f.Namespace.Name, + Annotations: map[string]string{ + "conformance.cert-manager.io/test-name": s.Name + " " + test.name, + }, + }, + Spec: cmapi.CertificateSpec{ + SecretName: "e2e-conformance-tls-" + randomTestID, + IssuerRef: issuerRef, + }, + } - sec.Data[corev1.TLSCertKey] = []byte{} + certificate = gen.CertificateFrom( + certificate, + test.certModifiers..., + ) - _, err = f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Update(context.TODO(), sec, metav1.UpdateOptions{}) - Expect(err).NotTo(HaveOccurred(), "failed to update secret by deleting the signed certificate data") + By("Creating a Certificate") + err := f.CRClient.Create(ctx, certificate) + Expect(err).NotTo(HaveOccurred()) - By("Waiting for the Certificate to re-issue a certificate") - sec, err = f.Helper().WaitForSecretCertificateData(f.Namespace.Name, sec.Name, time.Minute*8) - Expect(err).NotTo(HaveOccurred(), "failed to wait for secret to have a valid 2nd certificate") + By("Waiting for the Certificate to be issued...") + certificate, err = f.Helper().WaitForCertificateReadyAndDoneIssuing(ctx, certificate, time.Minute*8) + Expect(err).NotTo(HaveOccurred()) - crtPEM2 := sec.Data[corev1.TLSCertKey] - crt2, err := pki.DecodeX509CertificateBytes(crtPEM2) - Expect(err).NotTo(HaveOccurred(), "failed to get decode second signed certificate data") + By("Validating the issued Certificate...") + validations := []certificates.ValidationFunc(nil) + validations = append(validations, test.extraValidations...) + validations = append(validations, validation.CertificateSetForUnsupportedFeatureSet(s.UnsupportedFeatures)...) + err = f.Helper().ValidateCertificate(certificate, validations...) + Expect(err).NotTo(HaveOccurred()) + }, test.requiredFeatures...) + } - By("Ensuing both certificates are signed by same private key") - match, err := pki.PublicKeysEqual(crt1.PublicKey, crt2.PublicKey) - Expect(err).NotTo(HaveOccurred(), "failed to check public keys of both signed certificates") + for _, test := range tests { + defineTest(test) + } - if !match { - Fail("Both signed certificates not signed by same private key") - } - }, featureset.ReusePrivateKeyFeature, featureset.OnlySAN) + ///////////////////////////////////// + ////// Gateway/ Ingress Tests /////// + ///////////////////////////////////// - s.it(f, "should issue a certificate for a single distinct DNS Name defined by an ingress with annotations", func(issuerRef cmmeta.ObjectReference) { + s.it(f, "should issue a certificate for a single distinct DNS Name defined by an ingress with annotations", func(ctx context.Context, issuerRef cmmeta.IssuerReference) { if s.HTTP01TestType != "Ingress" { // TODO @jakexks: remove this skip once either haproxy or traefik fully support gateway API Skip("Skipping ingress-specific as non ingress HTTP-01 solver is in use") @@ -739,7 +470,7 @@ func (s *Suite) Define() { secretName := "testcert-ingress-tls" By("Creating an Ingress with the issuer name annotation set") - ingress, err := ingClient.Create(context.TODO(), e2eutil.NewIngress(name, secretName, map[string]string{ + ingress, err := ingClient.Create(ctx, e2eutil.NewIngress(name, secretName, map[string]string{ "cert-manager.io/issuer": issuerRef.Name, "cert-manager.io/issuer-kind": issuerRef.Kind, "cert-manager.io/issuer-group": issuerRef.Group, @@ -752,7 +483,7 @@ func (s *Suite) Define() { secretName := "testcert-ingress-tls" By("Creating an Ingress with the issuer name annotation set") - ingress, err := ingClient.Create(context.TODO(), e2eutil.NewV1Beta1Ingress(name, secretName, map[string]string{ + ingress, err := ingClient.Create(ctx, e2eutil.NewV1Beta1Ingress(name, secretName, map[string]string{ "cert-manager.io/issuer": issuerRef.Name, "cert-manager.io/issuer-kind": issuerRef.Kind, "cert-manager.io/issuer-group": issuerRef.Group, @@ -764,11 +495,11 @@ func (s *Suite) Define() { } By("Waiting for the Certificate to exist...") - cert, err := f.Helper().WaitForCertificateToExist(f.Namespace.Name, certName, time.Minute) + cert, err := f.Helper().WaitForCertificateToExist(ctx, f.Namespace.Name, certName, time.Minute) Expect(err).NotTo(HaveOccurred()) By("Waiting for the Certificate to be issued...") - cert, err = f.Helper().WaitForCertificateReadyAndDoneIssuing(cert, time.Minute*8) + cert, err = f.Helper().WaitForCertificateReadyAndDoneIssuing(ctx, cert, time.Minute*8) Expect(err).NotTo(HaveOccurred()) By("Validating the issued Certificate...") @@ -776,7 +507,7 @@ func (s *Suite) Define() { Expect(err).NotTo(HaveOccurred()) }, featureset.OnlySAN) - s.it(f, "should issue a certificate defined by an ingress with certificate field annotations", func(issuerRef cmmeta.ObjectReference) { + s.it(f, "should issue a certificate defined by an ingress with certificate field annotations", func(ctx context.Context, issuerRef cmmeta.IssuerReference) { if s.HTTP01TestType != "Ingress" { // TODO @jakexks: remove this skip once either haproxy or traefik fully support gateway API Skip("Skipping ingress-specific as non ingress HTTP-01 solver is in use") @@ -786,7 +517,7 @@ func (s *Suite) Define() { domain := e2eutil.RandomSubdomain(s.DomainSuffix) duration := time.Hour * 999 renewBefore := time.Hour * 111 - revisionHistoryLimit := pointer.Int32(7) + revisionHistoryLimit := ptr.To(int32(7)) privateKeyAlgorithm := cmapi.RSAKeyAlgorithm privateKeyEncoding := cmapi.PKCS1 privateKeySize := 4096 @@ -800,7 +531,7 @@ func (s *Suite) Define() { secretName := "testcert-ingress-tls" By("Creating an Ingress with annotations for issuerRef and other Certificate fields") - ingress, err := ingClient.Create(context.TODO(), e2eutil.NewIngress(name, secretName, map[string]string{ + ingress, err := ingClient.Create(ctx, e2eutil.NewIngress(name, secretName, map[string]string{ "cert-manager.io/issuer": issuerRef.Name, "cert-manager.io/issuer-kind": issuerRef.Kind, "cert-manager.io/issuer-group": issuerRef.Group, @@ -823,7 +554,7 @@ func (s *Suite) Define() { secretName := "testcert-ingress-tls" By("Creating an Ingress with annotations for issuerRef and other Certificate fields") - ingress, err := ingClient.Create(context.TODO(), e2eutil.NewV1Beta1Ingress(name, secretName, map[string]string{ + ingress, err := ingClient.Create(ctx, e2eutil.NewV1Beta1Ingress(name, secretName, map[string]string{ "cert-manager.io/issuer": issuerRef.Name, "cert-manager.io/issuer-kind": issuerRef.Kind, "cert-manager.io/issuer-group": issuerRef.Group, @@ -844,21 +575,26 @@ func (s *Suite) Define() { } By("Waiting for the Certificate to exist...") - cert, err := f.Helper().WaitForCertificateToExist(f.Namespace.Name, certName, time.Minute) + cert, err := f.Helper().WaitForCertificateToExist(ctx, f.Namespace.Name, certName, time.Minute) Expect(err).NotTo(HaveOccurred()) By("Waiting for the Certificate to be issued...") - cert, err = f.Helper().WaitForCertificateReadyAndDoneIssuing(cert, time.Minute*8) + cert, err = f.Helper().WaitForCertificateReadyAndDoneIssuing(ctx, cert, time.Minute*8) Expect(err).NotTo(HaveOccurred()) // Verify that the ingres-shim has translated all the supplied // annotations into equivalent Certificate field values + // TODO(wallrj): These checks are redundant. The unit + // tests for certificate-shim Sync already verify that + // the annotations are converted to Certificate fields. By("Validating the created Certificate") err = f.Helper().ValidateCertificate( cert, func(certificate *cmapi.Certificate, _ *corev1.Secret) error { Expect(certificate.Spec.DNSNames).To(ConsistOf(domain)) - Expect(certificate.Spec.CommonName).To(Equal(domain)) + if !s.UnsupportedFeatures.Has(featureset.CommonNameFeature) { + Expect(certificate.Spec.CommonName).To(Equal(domain)) + } Expect(certificate.Spec.Duration.Duration).To(Equal(duration)) Expect(certificate.Spec.RenewBefore.Duration).To(Equal(renewBefore)) Expect(certificate.Spec.RevisionHistoryLimit).To(Equal(revisionHistoryLimit)) @@ -878,8 +614,8 @@ func (s *Suite) Define() { Expect(err).NotTo(HaveOccurred()) }) - s.it(f, "Creating a Gateway with annotations for issuerRef and other Certificate fields", func(issuerRef cmmeta.ObjectReference) { - framework.RequireFeatureGate(f, utilfeature.DefaultFeatureGate, feature.ExperimentalGatewayAPISupport) + s.it(f, "Creating a Gateway with annotations for issuerRef and other Certificate fields", func(ctx context.Context, issuerRef cmmeta.IssuerReference) { + framework.RequireFeatureGate(utilfeature.DefaultFeatureGate, feature.ExperimentalGatewayAPISupport) name := "testcert-gateway" secretName := "testcert-gateway-tls" @@ -897,7 +633,7 @@ func (s *Suite) Define() { "cert-manager.io/renew-before": renewBefore.String(), }, domain) - gw, err := f.GWClientSet.GatewayV1alpha2().Gateways(f.Namespace.Name).Create(context.TODO(), gw, metav1.CreateOptions{}) + gw, err := f.GWClientSet.GatewayV1().Gateways(f.Namespace.Name).Create(ctx, gw, metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred()) // XXX(Mael): the CertificateRef seems to contain the Gateway name @@ -906,11 +642,11 @@ func (s *Suite) Define() { certName := string(gw.Spec.Listeners[0].TLS.CertificateRefs[0].Name) By("Waiting for the Certificate to exist...") - cert, err := f.Helper().WaitForCertificateToExist(f.Namespace.Name, certName, time.Minute) + cert, err := f.Helper().WaitForCertificateToExist(ctx, f.Namespace.Name, certName, time.Minute) Expect(err).NotTo(HaveOccurred()) By("Waiting for the Certificate to be issued...") - cert, err = f.Helper().WaitForCertificateReadyAndDoneIssuing(cert, time.Minute*8) + cert, err = f.Helper().WaitForCertificateReadyAndDoneIssuing(ctx, cert, time.Minute*8) Expect(err).NotTo(HaveOccurred()) // Verify that the gateway-shim has translated all the supplied @@ -922,10 +658,11 @@ func (s *Suite) Define() { Expect(cert.Spec.RenewBefore.Duration).To(Equal(renewBefore)) }) - s.it(f, "should issue a certificate that defines a long domain", func(issuerRef cmmeta.ObjectReference) { - // the maximum length of a single segment of the domain being requested - const maxLengthOfDomainSegment = 63 + //////////////////////////////////////// + /////// Complex behavioral tests /////// + //////////////////////////////////////// + s.it(f, "should issue another certificate with the same private key if the existing certificate and CertificateRequest are deleted", func(ctx context.Context, issuerRef cmmeta.IssuerReference) { testCertificate := &cmapi.Certificate{ ObjectMeta: metav1.ObjectMeta{ Name: "testcert", @@ -933,26 +670,62 @@ func (s *Suite) Define() { }, Spec: cmapi.CertificateSpec{ SecretName: "testcert-tls", - DNSNames: []string{e2eutil.RandomSubdomainLength(s.DomainSuffix, maxLengthOfDomainSegment)}, + DNSNames: []string{e2eutil.RandomSubdomain(s.DomainSuffix)}, IssuerRef: issuerRef, + PrivateKey: &cmapi.CertificatePrivateKey{ + // Explicitly set RotationPolicy to Never to test the + // behavior of reusing the same private key when a + // certificate is reissued. + // The default value is Always. + RotationPolicy: cmapi.RotationPolicyNever, + }, }, } - validations := validation.CertificateSetForUnsupportedFeatureSet(s.UnsupportedFeatures) - By("Creating a Certificate") err := f.CRClient.Create(ctx, testCertificate) Expect(err).NotTo(HaveOccurred()) By("Waiting for the Certificate to be issued...") - testCertificate, err = f.Helper().WaitForCertificateReadyAndDoneIssuing(testCertificate, time.Minute*8) + testCertificate, err = f.Helper().WaitForCertificateReadyAndDoneIssuing(ctx, testCertificate, time.Minute*8) Expect(err).NotTo(HaveOccurred()) - By("Sanity-check the issued Certificate") - err = f.Helper().ValidateCertificate(testCertificate, validations...) + By("Validating the issued Certificate...") + err = f.Helper().ValidateCertificate(testCertificate, validation.CertificateSetForUnsupportedFeatureSet(s.UnsupportedFeatures)...) Expect(err).NotTo(HaveOccurred()) - }, featureset.OnlySAN, featureset.LongDomainFeatureSet) - s.it(f, "should allow updating an existing certificate with a new DNS Name", func(issuerRef cmmeta.ObjectReference) { + By("Deleting existing certificate data in Secret") + sec, err := f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name). + Get(ctx, testCertificate.Spec.SecretName, metav1.GetOptions{}) + Expect(err).NotTo(HaveOccurred(), "failed to get secret containing signed certificate key pair data") + + sec = sec.DeepCopy() + crtPEM1 := sec.Data[corev1.TLSCertKey] + crt1, err := pki.DecodeX509CertificateBytes(crtPEM1) + Expect(err).NotTo(HaveOccurred(), "failed to get decode first signed certificate data") + + sec.Data[corev1.TLSCertKey] = []byte{} + + _, err = f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Update(ctx, sec, metav1.UpdateOptions{}) + Expect(err).NotTo(HaveOccurred(), "failed to update secret by deleting the signed certificate data") + + By("Waiting for the Certificate to re-issue a certificate") + sec, err = f.Helper().WaitForSecretCertificateData(ctx, f.Namespace.Name, sec.Name, time.Minute*8) + Expect(err).NotTo(HaveOccurred(), "failed to wait for secret to have a valid 2nd certificate") + + crtPEM2 := sec.Data[corev1.TLSCertKey] + crt2, err := pki.DecodeX509CertificateBytes(crtPEM2) + Expect(err).NotTo(HaveOccurred(), "failed to get decode second signed certificate data") + + By("Ensuring both certificates are signed by same private key") + match, err := pki.PublicKeysEqual(crt1.PublicKey, crt2.PublicKey) + Expect(err).NotTo(HaveOccurred(), "failed to check public keys of both signed certificates") + + if !match { + Fail("Both signed certificates not signed by same private key") + } + }, featureset.ReusePrivateKeyFeature, featureset.OnlySAN) + + s.it(f, "should allow updating an existing certificate with a new DNS Name", func(ctx context.Context, issuerRef cmmeta.IssuerReference) { testCertificate := &cmapi.Certificate{ ObjectMeta: metav1.ObjectMeta{ Name: "testcert", @@ -971,7 +744,7 @@ func (s *Suite) Define() { Expect(err).NotTo(HaveOccurred()) By("Waiting for the Certificate to be ready") - testCertificate, err = f.Helper().WaitForCertificateReadyAndDoneIssuing(testCertificate, time.Minute*8) + testCertificate, err = f.Helper().WaitForCertificateReadyAndDoneIssuing(ctx, testCertificate, time.Minute*8) Expect(err).NotTo(HaveOccurred()) By("Sanity-check the issued Certificate") @@ -980,56 +753,28 @@ func (s *Suite) Define() { By("Updating the Certificate after having added an additional dnsName") newDNSName := e2eutil.RandomSubdomain(s.DomainSuffix) - retry.RetryOnConflict(retry.DefaultRetry, func() error { - err = f.CRClient.Get(context.Background(), types.NamespacedName{Name: testCertificate.Name, Namespace: testCertificate.Namespace}, testCertificate) + err = retry.RetryOnConflict(retry.DefaultRetry, func() error { + err := f.CRClient.Get(ctx, types.NamespacedName{Name: testCertificate.Name, Namespace: testCertificate.Namespace}, testCertificate) if err != nil { return err } testCertificate.Spec.DNSNames = append(testCertificate.Spec.DNSNames, newDNSName) - err = f.CRClient.Update(context.Background(), testCertificate) + err = f.CRClient.Update(ctx, testCertificate) if err != nil { return err } return nil }) - Expect(err).NotTo(HaveOccurred()) By("Waiting for the Certificate Ready condition to be updated") - testCertificate, err = f.Helper().WaitForCertificateReadyAndDoneIssuing(testCertificate, time.Minute*8) + testCertificate, err = f.Helper().WaitForCertificateReadyAndDoneIssuing(ctx, testCertificate, time.Minute*8) Expect(err).NotTo(HaveOccurred()) By("Sanity-check the issued Certificate") err = f.Helper().ValidateCertificate(testCertificate, validations...) Expect(err).NotTo(HaveOccurred()) }, featureset.OnlySAN) - - s.it(f, "should issue a certificate that defines a wildcard DNS Name and its apex DNS Name", func(issuerRef cmmeta.ObjectReference) { - dnsDomain := e2eutil.RandomSubdomain(s.DomainSuffix) - testCertificate := &cmapi.Certificate{ - ObjectMeta: metav1.ObjectMeta{ - Name: "testcert", - Namespace: f.Namespace.Name, - }, - Spec: cmapi.CertificateSpec{ - SecretName: "testcert-tls", - IssuerRef: issuerRef, - DNSNames: []string{"*." + dnsDomain, dnsDomain}, - }, - } - By("Creating a Certificate") - err := f.CRClient.Create(ctx, testCertificate) - Expect(err).NotTo(HaveOccurred()) - - // use a longer timeout for this, as it requires performing 2 dns validations in serial - By("Waiting for the Certificate to be issued...") - testCertificate, err = f.Helper().WaitForCertificateReadyAndDoneIssuing(testCertificate, time.Minute*10) - Expect(err).NotTo(HaveOccurred()) - - By("Validating the issued Certificate...") - err = f.Helper().ValidateCertificate(testCertificate, validation.CertificateSetForUnsupportedFeatureSet(s.UnsupportedFeatures)...) - Expect(err).NotTo(HaveOccurred()) - }, featureset.WildcardsFeature, featureset.OnlySAN) }) } diff --git a/test/e2e/suite/conformance/certificates/vault/vault_approle.go b/test/e2e/suite/conformance/certificates/vault/vault_approle.go index 655ed3fc06d..53b92394c9e 100644 --- a/test/e2e/suite/conformance/certificates/vault/vault_approle.go +++ b/test/e2e/suite/conformance/certificates/vault/vault_approle.go @@ -18,27 +18,20 @@ package vault import ( "context" - "path" "time" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "github.com/cert-manager/cert-manager/e2e-tests/framework" + "github.com/cert-manager/cert-manager/e2e-tests/framework/addon" + "github.com/cert-manager/cert-manager/e2e-tests/framework/addon/vault" + "github.com/cert-manager/cert-manager/e2e-tests/framework/helper/featureset" + "github.com/cert-manager/cert-manager/e2e-tests/suite/conformance/certificates" cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" - "github.com/cert-manager/cert-manager/test/e2e/framework" - "github.com/cert-manager/cert-manager/test/e2e/framework/addon" - "github.com/cert-manager/cert-manager/test/e2e/framework/addon/vault" - "github.com/cert-manager/cert-manager/test/e2e/framework/helper/featureset" - "github.com/cert-manager/cert-manager/test/e2e/suite/conformance/certificates" -) -const ( - intermediateMount = "intermediate-ca" - role = "kubernetes-vault" - vaultSecretAppRoleName = "vault-role-" - authPath = "approle" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" ) var _ = framework.ConformanceDescribe("Certificates", func() { @@ -69,8 +62,7 @@ var _ = framework.ConformanceDescribe("Certificates", func() { }) type vaultAppRoleProvisioner struct { - vault *vault.Vault - vaultInit *vault.VaultInitializer + setup *vault.VaultInitializer *vaultSecrets } @@ -83,102 +75,93 @@ type vaultSecrets struct { secretNamespace string } -func (v *vaultAppRoleProvisioner) delete(f *framework.Framework, ref cmmeta.ObjectReference) { - Expect(v.vaultInit.Clean()).NotTo(HaveOccurred(), "failed to deprovision vault initializer") - Expect(v.vault.Deprovision()).NotTo(HaveOccurred(), "failed to deprovision vault") +func (v *vaultAppRoleProvisioner) delete(ctx context.Context, f *framework.Framework, ref cmmeta.IssuerReference) { + Expect(v.setup.Clean(ctx)).NotTo(HaveOccurred(), "failed to deprovision vault initializer") - err := f.KubeClientSet.CoreV1().Secrets(v.secretNamespace).Delete(context.TODO(), v.secretName, metav1.DeleteOptions{}) + err := f.KubeClientSet.CoreV1().Secrets(v.secretNamespace).Delete(ctx, v.secretName, metav1.DeleteOptions{}) Expect(err).NotTo(HaveOccurred()) if ref.Kind == "ClusterIssuer" { - err = f.CertManagerClientSet.CertmanagerV1().ClusterIssuers().Delete(context.TODO(), ref.Name, metav1.DeleteOptions{}) + err = f.CertManagerClientSet.CertmanagerV1().ClusterIssuers().Delete(ctx, ref.Name, metav1.DeleteOptions{}) Expect(err).NotTo(HaveOccurred()) } } -func (v *vaultAppRoleProvisioner) createIssuer(f *framework.Framework) cmmeta.ObjectReference { +func (v *vaultAppRoleProvisioner) createIssuer(ctx context.Context, f *framework.Framework) cmmeta.IssuerReference { + appRoleSecretGeneratorName := "vault-approle-secret-" By("Creating a VaultAppRole Issuer") - v.vaultSecrets = v.initVault(f) + v.vaultSecrets = v.initVault(ctx) - sec, err := f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Create(context.TODO(), vault.NewVaultAppRoleSecret(vaultSecretAppRoleName, v.secretID), metav1.CreateOptions{}) + sec, err := f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Create(ctx, vault.NewVaultAppRoleSecret(appRoleSecretGeneratorName, v.secretID), metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred(), "vault to store app role secret from vault") v.secretName = sec.Name v.secretNamespace = sec.Namespace - issuer, err := f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Create(context.TODO(), &cmapi.Issuer{ + issuer, err := f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Create(ctx, &cmapi.Issuer{ ObjectMeta: metav1.ObjectMeta{ GenerateName: "vault-issuer-", }, - Spec: v.createIssuerSpec(f), + Spec: v.createIssuerSpec(), }, metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred(), "failed to create vault issuer") // wait for issuer to be ready By("Waiting for Vault Issuer to be Ready") - issuer, err = f.Helper().WaitIssuerReady(issuer, time.Minute*5) + issuer, err = f.Helper().WaitIssuerReady(ctx, issuer, time.Minute*5) Expect(err).ToNot(HaveOccurred()) - return cmmeta.ObjectReference{ + return cmmeta.IssuerReference{ Group: cmapi.SchemeGroupVersion.Group, Kind: cmapi.IssuerKind, Name: issuer.Name, } } -func (v *vaultAppRoleProvisioner) createClusterIssuer(f *framework.Framework) cmmeta.ObjectReference { +func (v *vaultAppRoleProvisioner) createClusterIssuer(ctx context.Context, f *framework.Framework) cmmeta.IssuerReference { + appRoleSecretGeneratorName := "vault-approle-secret-" By("Creating a VaultAppRole ClusterIssuer") - v.vaultSecrets = v.initVault(f) + v.vaultSecrets = v.initVault(ctx) - sec, err := f.KubeClientSet.CoreV1().Secrets(f.Config.Addons.CertManager.ClusterResourceNamespace).Create(context.TODO(), vault.NewVaultAppRoleSecret(vaultSecretAppRoleName, v.secretID), metav1.CreateOptions{}) + sec, err := f.KubeClientSet.CoreV1().Secrets(f.Config.Addons.CertManager.ClusterResourceNamespace).Create(ctx, vault.NewVaultAppRoleSecret(appRoleSecretGeneratorName, v.secretID), metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred(), "vault to store app role secret from vault") v.secretName = sec.Name v.secretNamespace = sec.Namespace - issuer, err := f.CertManagerClientSet.CertmanagerV1().ClusterIssuers().Create(context.TODO(), &cmapi.ClusterIssuer{ + issuer, err := f.CertManagerClientSet.CertmanagerV1().ClusterIssuers().Create(ctx, &cmapi.ClusterIssuer{ ObjectMeta: metav1.ObjectMeta{ GenerateName: "vault-cluster-issuer-", }, - Spec: v.createIssuerSpec(f), + Spec: v.createIssuerSpec(), }, metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred(), "failed to create vault issuer") // wait for issuer to be ready By("Waiting for Vault Cluster Issuer to be Ready") - issuer, err = f.Helper().WaitClusterIssuerReady(issuer, time.Minute*5) + issuer, err = f.Helper().WaitClusterIssuerReady(ctx, issuer, time.Minute*5) Expect(err).ToNot(HaveOccurred()) - return cmmeta.ObjectReference{ + return cmmeta.IssuerReference{ Group: cmapi.SchemeGroupVersion.Group, Kind: cmapi.ClusterIssuerKind, Name: issuer.Name, } } -func (v *vaultAppRoleProvisioner) initVault(f *framework.Framework) *vaultSecrets { - v.vault = &vault.Vault{ - Base: addon.Base, - Namespace: f.Namespace.Name, - Name: "cm-e2e-create-vault-issuer", - } - Expect(v.vault.Setup(f.Config)).NotTo(HaveOccurred(), "failed to setup vault") - Expect(v.vault.Provision()).NotTo(HaveOccurred(), "failed to provision vault") - +func (v *vaultAppRoleProvisioner) initVault(ctx context.Context) *vaultSecrets { By("Configuring the VaultAppRole server") - v.vaultInit = &vault.VaultInitializer{ - Details: *v.vault.Details(), - RootMount: "root-ca", - IntermediateMount: intermediateMount, - Role: role, - AppRoleAuthPath: authPath, - } - Expect(v.vaultInit.Init()).NotTo(HaveOccurred(), "failed to init vault") - Expect(v.vaultInit.Setup()).NotTo(HaveOccurred(), "failed to setup vault") + v.setup = vault.NewVaultInitializerAppRole( + addon.Base.Details().KubeClient, + *addon.Vault.Details(), + false, + ) + Expect(v.setup.Init(ctx)).NotTo(HaveOccurred(), "failed to init vault") + Expect(v.setup.Setup(ctx)).NotTo(HaveOccurred(), "failed to setup vault") - roleID, secretID, err := v.vaultInit.CreateAppRole() + roleID, secretID, err := v.setup.CreateAppRole(ctx) Expect(err).NotTo(HaveOccurred(), "vault to create app role from vault") return &vaultSecrets{ @@ -187,18 +170,16 @@ func (v *vaultAppRoleProvisioner) initVault(f *framework.Framework) *vaultSecret } } -func (v *vaultAppRoleProvisioner) createIssuerSpec(f *framework.Framework) cmapi.IssuerSpec { - vaultPath := path.Join(intermediateMount, "sign", role) - +func (v *vaultAppRoleProvisioner) createIssuerSpec() cmapi.IssuerSpec { return cmapi.IssuerSpec{ IssuerConfig: cmapi.IssuerConfig{ Vault: &cmapi.VaultIssuer{ - Server: v.vault.Details().Host, - Path: vaultPath, - CABundle: v.vault.Details().VaultCA, + Server: addon.Vault.Details().URL, + Path: v.setup.IntermediateSignPath(), + CABundle: addon.Vault.Details().VaultCA, Auth: cmapi.VaultAuth{ AppRole: &cmapi.VaultAppRole{ - Path: authPath, + Path: v.setup.AppRoleAuthPath(), RoleId: v.roleID, SecretRef: cmmeta.SecretKeySelector{ Key: "secretkey", diff --git a/test/e2e/suite/conformance/certificates/venafi/venafi.go b/test/e2e/suite/conformance/certificates/venafi/venafi.go index 5eea99cf20d..69544de9a1e 100644 --- a/test/e2e/suite/conformance/certificates/venafi/venafi.go +++ b/test/e2e/suite/conformance/certificates/venafi/venafi.go @@ -20,17 +20,18 @@ import ( "context" "time" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "github.com/cert-manager/cert-manager/e2e-tests/framework" + vaddon "github.com/cert-manager/cert-manager/e2e-tests/framework/addon/venafi" + "github.com/cert-manager/cert-manager/e2e-tests/framework/helper/featureset" + "github.com/cert-manager/cert-manager/e2e-tests/framework/util/errors" + "github.com/cert-manager/cert-manager/e2e-tests/suite/conformance/certificates" cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" - "github.com/cert-manager/cert-manager/test/e2e/framework" - vaddon "github.com/cert-manager/cert-manager/test/e2e/framework/addon/venafi" - "github.com/cert-manager/cert-manager/test/e2e/framework/helper/featureset" - "github.com/cert-manager/cert-manager/test/e2e/framework/util/errors" - "github.com/cert-manager/cert-manager/test/e2e/suite/conformance/certificates" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" ) var _ = framework.ConformanceDescribe("Certificates", func() { @@ -76,71 +77,71 @@ type venafiProvisioner struct { tpp *vaddon.VenafiTPP } -func (v *venafiProvisioner) delete(f *framework.Framework, ref cmmeta.ObjectReference) { - Expect(v.tpp.Deprovision()).NotTo(HaveOccurred(), "failed to deprovision tpp venafi") +func (v *venafiProvisioner) delete(ctx context.Context, f *framework.Framework, ref cmmeta.IssuerReference) { + Expect(v.tpp.Deprovision(ctx)).NotTo(HaveOccurred(), "failed to deprovision tpp venafi") if ref.Kind == "ClusterIssuer" { - err := f.CertManagerClientSet.CertmanagerV1().ClusterIssuers().Delete(context.TODO(), ref.Name, metav1.DeleteOptions{}) + err := f.CertManagerClientSet.CertmanagerV1().ClusterIssuers().Delete(ctx, ref.Name, metav1.DeleteOptions{}) Expect(err).NotTo(HaveOccurred()) } } -func (v *venafiProvisioner) createIssuer(f *framework.Framework) cmmeta.ObjectReference { +func (v *venafiProvisioner) createIssuer(ctx context.Context, f *framework.Framework) cmmeta.IssuerReference { By("Creating a Venafi Issuer") v.tpp = &vaddon.VenafiTPP{ Namespace: f.Namespace.Name, } - err := v.tpp.Setup(f.Config) + _, err := v.tpp.Setup(ctx, f.Config) if errors.IsSkip(err) { framework.Skipf("Skipping test as addon could not be setup: %v", err) } Expect(err).NotTo(HaveOccurred(), "failed to setup tpp venafi") - Expect(v.tpp.Provision()).NotTo(HaveOccurred(), "failed to provision tpp venafi") + Expect(v.tpp.Provision(ctx)).NotTo(HaveOccurred(), "failed to provision tpp venafi") issuer := v.tpp.Details().BuildIssuer() - issuer, err = f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Create(context.TODO(), issuer, metav1.CreateOptions{}) + issuer, err = f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Create(ctx, issuer, metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred(), "failed to create issuer for venafi") // wait for issuer to be ready By("Waiting for Venafi Issuer to be Ready") - issuer, err = f.Helper().WaitIssuerReady(issuer, time.Minute*5) + issuer, err = f.Helper().WaitIssuerReady(ctx, issuer, time.Minute*5) Expect(err).ToNot(HaveOccurred()) - return cmmeta.ObjectReference{ + return cmmeta.IssuerReference{ Group: cmapi.SchemeGroupVersion.Group, Kind: cmapi.IssuerKind, Name: issuer.Name, } } -func (v *venafiProvisioner) createClusterIssuer(f *framework.Framework) cmmeta.ObjectReference { +func (v *venafiProvisioner) createClusterIssuer(ctx context.Context, f *framework.Framework) cmmeta.IssuerReference { By("Creating a Venafi ClusterIssuer") v.tpp = &vaddon.VenafiTPP{ Namespace: f.Config.Addons.CertManager.ClusterResourceNamespace, } - err := v.tpp.Setup(f.Config) + _, err := v.tpp.Setup(ctx, f.Config) if errors.IsSkip(err) { framework.Skipf("Skipping test as addon could not be setup: %v", err) } Expect(err).NotTo(HaveOccurred(), "failed to setup tpp venafi") - Expect(v.tpp.Provision()).NotTo(HaveOccurred(), "failed to provision tpp venafi") + Expect(v.tpp.Provision(ctx)).NotTo(HaveOccurred(), "failed to provision tpp venafi") issuer := v.tpp.Details().BuildClusterIssuer() - issuer, err = f.CertManagerClientSet.CertmanagerV1().ClusterIssuers().Create(context.TODO(), issuer, metav1.CreateOptions{}) + issuer, err = f.CertManagerClientSet.CertmanagerV1().ClusterIssuers().Create(ctx, issuer, metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred(), "failed to create issuer for venafi") // wait for issuer to be ready By("Waiting for Venafi Cluster Issuer to be Ready") - issuer, err = f.Helper().WaitClusterIssuerReady(issuer, time.Minute*5) + issuer, err = f.Helper().WaitClusterIssuerReady(ctx, issuer, time.Minute*5) Expect(err).ToNot(HaveOccurred()) - return cmmeta.ObjectReference{ + return cmmeta.IssuerReference{ Group: cmapi.SchemeGroupVersion.Group, Kind: cmapi.ClusterIssuerKind, Name: issuer.Name, diff --git a/test/e2e/suite/conformance/certificates/venaficloud/cloud.go b/test/e2e/suite/conformance/certificates/venaficloud/cloud.go index a8ce0063670..660e3bda790 100644 --- a/test/e2e/suite/conformance/certificates/venaficloud/cloud.go +++ b/test/e2e/suite/conformance/certificates/venaficloud/cloud.go @@ -20,17 +20,18 @@ import ( "context" "time" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "github.com/cert-manager/cert-manager/e2e-tests/framework" + vaddon "github.com/cert-manager/cert-manager/e2e-tests/framework/addon/venafi" + "github.com/cert-manager/cert-manager/e2e-tests/framework/helper/featureset" + "github.com/cert-manager/cert-manager/e2e-tests/framework/util/errors" + "github.com/cert-manager/cert-manager/e2e-tests/suite/conformance/certificates" cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" - "github.com/cert-manager/cert-manager/test/e2e/framework" - vaddon "github.com/cert-manager/cert-manager/test/e2e/framework/addon/venafi" - "github.com/cert-manager/cert-manager/test/e2e/framework/helper/featureset" - "github.com/cert-manager/cert-manager/test/e2e/framework/util/errors" - "github.com/cert-manager/cert-manager/test/e2e/suite/conformance/certificates" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" ) var _ = framework.ConformanceDescribe("Certificates", func() { @@ -51,6 +52,9 @@ var _ = framework.ConformanceDescribe("Certificates", func() { featureset.Ed25519FeatureSet, featureset.IssueCAFeature, featureset.LiteralSubjectFeature, + // The Venafi Cloud server that we use for these tests has not yet been + // configured to allow OtherName fields. + featureset.OtherNamesFeature, ) provisioner := new(venafiProvisioner) @@ -73,71 +77,71 @@ type venafiProvisioner struct { cloud *vaddon.VenafiCloud } -func (v *venafiProvisioner) delete(f *framework.Framework, ref cmmeta.ObjectReference) { - Expect(v.cloud.Deprovision()).NotTo(HaveOccurred(), "failed to deprovision cloud venafi") +func (v *venafiProvisioner) delete(ctx context.Context, f *framework.Framework, ref cmmeta.IssuerReference) { + Expect(v.cloud.Deprovision(ctx)).NotTo(HaveOccurred(), "failed to deprovision cloud venafi") if ref.Kind == "ClusterIssuer" { - err := f.CertManagerClientSet.CertmanagerV1().ClusterIssuers().Delete(context.TODO(), ref.Name, metav1.DeleteOptions{}) + err := f.CertManagerClientSet.CertmanagerV1().ClusterIssuers().Delete(ctx, ref.Name, metav1.DeleteOptions{}) Expect(err).NotTo(HaveOccurred()) } } -func (v *venafiProvisioner) createIssuer(f *framework.Framework) cmmeta.ObjectReference { +func (v *venafiProvisioner) createIssuer(ctx context.Context, f *framework.Framework) cmmeta.IssuerReference { By("Creating a Venafi Cloud Issuer") v.cloud = &vaddon.VenafiCloud{ Namespace: f.Namespace.Name, } - err := v.cloud.Setup(f.Config) + _, err := v.cloud.Setup(ctx, f.Config) if errors.IsSkip(err) { framework.Skipf("Skipping test as addon could not be setup: %v", err) } Expect(err).NotTo(HaveOccurred(), "failed to provision venafi cloud issuer") - Expect(v.cloud.Provision()).NotTo(HaveOccurred(), "failed to provision tpp venafi") + Expect(v.cloud.Provision(ctx)).NotTo(HaveOccurred(), "failed to provision tpp venafi") issuer := v.cloud.Details().BuildIssuer() - issuer, err = f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Create(context.TODO(), issuer, metav1.CreateOptions{}) + issuer, err = f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Create(ctx, issuer, metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred(), "failed to create issuer for venafi") // wait for issuer to be ready By("Waiting for Venafi Cloud Issuer to be Ready") - issuer, err = f.Helper().WaitIssuerReady(issuer, time.Minute*5) + issuer, err = f.Helper().WaitIssuerReady(ctx, issuer, time.Minute*5) Expect(err).ToNot(HaveOccurred()) - return cmmeta.ObjectReference{ + return cmmeta.IssuerReference{ Group: cmapi.SchemeGroupVersion.Group, Kind: cmapi.IssuerKind, Name: issuer.Name, } } -func (v *venafiProvisioner) createClusterIssuer(f *framework.Framework) cmmeta.ObjectReference { +func (v *venafiProvisioner) createClusterIssuer(ctx context.Context, f *framework.Framework) cmmeta.IssuerReference { By("Creating a Venafi ClusterIssuer") v.cloud = &vaddon.VenafiCloud{ Namespace: f.Config.Addons.CertManager.ClusterResourceNamespace, } - err := v.cloud.Setup(f.Config) + _, err := v.cloud.Setup(ctx, f.Config) if errors.IsSkip(err) { framework.Skipf("Skipping test as addon could not be setup: %v", err) } Expect(err).NotTo(HaveOccurred(), "failed to setup tpp venafi") - Expect(v.cloud.Provision()).NotTo(HaveOccurred(), "failed to provision tpp venafi") + Expect(v.cloud.Provision(ctx)).NotTo(HaveOccurred(), "failed to provision tpp venafi") issuer := v.cloud.Details().BuildClusterIssuer() - issuer, err = f.CertManagerClientSet.CertmanagerV1().ClusterIssuers().Create(context.TODO(), issuer, metav1.CreateOptions{}) + issuer, err = f.CertManagerClientSet.CertmanagerV1().ClusterIssuers().Create(ctx, issuer, metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred(), "failed to create issuer for venafi") // wait for issuer to be ready By("Waiting for Venafi Cloud Cluster Issuer to be Ready") - issuer, err = f.Helper().WaitClusterIssuerReady(issuer, time.Minute*5) + issuer, err = f.Helper().WaitClusterIssuerReady(ctx, issuer, time.Minute*5) Expect(err).ToNot(HaveOccurred()) - return cmmeta.ObjectReference{ + return cmmeta.IssuerReference{ Group: cmapi.SchemeGroupVersion.Group, Kind: cmapi.ClusterIssuerKind, Name: issuer.Name, diff --git a/test/e2e/suite/conformance/certificatesigningrequests/acme/acme.go b/test/e2e/suite/conformance/certificatesigningrequests/acme/acme.go index e2b69f80c61..c93d487b6c3 100644 --- a/test/e2e/suite/conformance/certificatesigningrequests/acme/acme.go +++ b/test/e2e/suite/conformance/certificatesigningrequests/acme/acme.go @@ -18,18 +18,18 @@ package acme import ( "context" - "encoding/base64" - . "github.com/onsi/gomega" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "github.com/cert-manager/cert-manager/e2e-tests/framework" + "github.com/cert-manager/cert-manager/e2e-tests/framework/helper/featureset" + "github.com/cert-manager/cert-manager/e2e-tests/suite/conformance/certificatesigningrequests" cmacme "github.com/cert-manager/cert-manager/pkg/apis/acme/v1" cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" "github.com/cert-manager/cert-manager/pkg/controller/certificatesigningrequests/util" - "github.com/cert-manager/cert-manager/test/e2e/framework" - "github.com/cert-manager/cert-manager/test/e2e/framework/helper/featureset" - "github.com/cert-manager/cert-manager/test/e2e/suite/conformance/certificatesigningrequests" + + . "github.com/onsi/gomega" ) var _ = framework.ConformanceDescribe("Certificates", func() { @@ -45,13 +45,15 @@ func runACMEIssuerTests(eab *cmacme.ACMEExternalAccountBinding) { // unsupportedHTTP01Features is a list of features that are not supported by the ACME // issuer type using HTTP01 var unsupportedHTTP01Features = featureset.NewFeatureSet( - featureset.IPAddressFeature, featureset.DurationFeature, featureset.WildcardsFeature, featureset.URISANsFeature, featureset.CommonNameFeature, featureset.KeyUsagesFeature, featureset.EmailSANsFeature, + featureset.SaveCAToSecret, + featureset.IssueCAFeature, + featureset.OtherNamesFeature, ) // unsupportedDNS01Features is a list of features that are not supported by the ACME @@ -63,6 +65,9 @@ func runACMEIssuerTests(eab *cmacme.ACMEExternalAccountBinding) { featureset.CommonNameFeature, featureset.KeyUsagesFeature, featureset.EmailSANsFeature, + featureset.SaveCAToSecret, + featureset.IssueCAFeature, + featureset.OtherNamesFeature, ) http01 := &acme{ @@ -74,8 +79,8 @@ func runACMEIssuerTests(eab *cmacme.ACMEExternalAccountBinding) { } (&certificatesigningrequests.Suite{ - Name: "ACME HTTP01 Issuer", - DomainSuffix: "ingress-nginx.http01.example.com", + Name: "ACME HTTP01 Issuer (Ingress)", + HTTP01TestType: "Ingress", CreateIssuerFunc: http01.createHTTP01Issuer, DeleteIssuerFunc: http01.delete, UnsupportedFeatures: unsupportedHTTP01Features, @@ -90,8 +95,8 @@ func runACMEIssuerTests(eab *cmacme.ACMEExternalAccountBinding) { }).Define() (&certificatesigningrequests.Suite{ - Name: "ACME HTTP01 ClusterIssuer", - DomainSuffix: "ingress-nginx.http01.example.com", + Name: "ACME HTTP01 ClusterIssuer (Ingress)", + HTTP01TestType: "Ingress", CreateIssuerFunc: http01.createHTTP01ClusterIssuer, DeleteIssuerFunc: http01.delete, UnsupportedFeatures: unsupportedHTTP01Features, @@ -111,21 +116,21 @@ type acme struct { secretNamespace string } -func (a *acme) delete(f *framework.Framework, signerName string) { +func (a *acme) delete(ctx context.Context, f *framework.Framework, signerName string) { if a.eab != nil { - err := f.KubeClientSet.CoreV1().Secrets(a.secretNamespace).Delete(context.TODO(), a.eab.Key.Name, metav1.DeleteOptions{}) + err := f.KubeClientSet.CoreV1().Secrets(a.secretNamespace).Delete(ctx, a.eab.Key.Name, metav1.DeleteOptions{}) Expect(err).NotTo(HaveOccurred()) } ref, _ := util.SignerIssuerRefFromSignerName(signerName) if ref.Type == "clusterissuers" { - err := f.CertManagerClientSet.CertmanagerV1().ClusterIssuers().Delete(context.TODO(), ref.Name, metav1.DeleteOptions{}) + err := f.CertManagerClientSet.CertmanagerV1().ClusterIssuers().Delete(ctx, ref.Name, metav1.DeleteOptions{}) Expect(err).NotTo(HaveOccurred()) } } -func (a *acme) ensureEABSecret(f *framework.Framework, ns string) { +func (a *acme) ensureEABSecret(ctx context.Context, f *framework.Framework, ns string) { if a.eab == nil { return } @@ -133,14 +138,15 @@ func (a *acme) ensureEABSecret(f *framework.Framework, ns string) { if ns == "" { ns = f.Namespace.Name } - sec, err := f.KubeClientSet.CoreV1().Secrets(ns).Create(context.TODO(), &corev1.Secret{ + sec, err := f.KubeClientSet.CoreV1().Secrets(ns).Create(ctx, &corev1.Secret{ ObjectMeta: metav1.ObjectMeta{ GenerateName: "external-account-binding-", Namespace: ns, }, Data: map[string][]byte{ - // base64 url encode (without padding) the HMAC key - "key": []byte(base64.RawURLEncoding.EncodeToString([]byte("kid-secret-1"))), + // Must match the key in the Pebble config. See: + // config.json in make/config/pebble/templates/configmaps.yaml + "key": []byte("zWNDZM6eQGHWpSRTPal5eIUYFTu7EajVIoguysqZ9wG44nMEtx3MUAsUDkMTQ12W"), }, }, metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred()) diff --git a/test/e2e/suite/conformance/certificatesigningrequests/acme/dns01.go b/test/e2e/suite/conformance/certificatesigningrequests/acme/dns01.go index fea86faa28a..2412ec9ee5c 100644 --- a/test/e2e/suite/conformance/certificatesigningrequests/acme/dns01.go +++ b/test/e2e/suite/conformance/certificatesigningrequests/acme/dns01.go @@ -21,18 +21,19 @@ import ( "fmt" "time" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "github.com/cert-manager/cert-manager/e2e-tests/framework" cmacme "github.com/cert-manager/cert-manager/pkg/apis/acme/v1" cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" - "github.com/cert-manager/cert-manager/test/e2e/framework" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" ) -func (a *acme) createDNS01Issuer(f *framework.Framework) string { - a.ensureEABSecret(f, f.Namespace.Name) +func (a *acme) createDNS01Issuer(ctx context.Context, f *framework.Framework) string { + a.ensureEABSecret(ctx, f, f.Namespace.Name) By("Creating an ACME DNS01 Issuer") issuer := &cmapi.Issuer{ @@ -41,19 +42,19 @@ func (a *acme) createDNS01Issuer(f *framework.Framework) string { }, Spec: a.createDNS01IssuerSpec(f.Config.Addons.ACMEServer.URL, f.Config.Addons.ACMEServer.DNSServer), } - issuer, err := f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Create(context.TODO(), issuer, metav1.CreateOptions{}) + issuer, err := f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Create(ctx, issuer, metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred(), "failed to create acme DNS01 Issuer") // wait for issuer to be ready By("Waiting for acme DNS01 Issuer to be Ready") - issuer, err = f.Helper().WaitIssuerReady(issuer, time.Minute*5) + issuer, err = f.Helper().WaitIssuerReady(ctx, issuer, time.Minute*5) Expect(err).ToNot(HaveOccurred()) return fmt.Sprintf("issuers.cert-manager.io/%s.%s", issuer.Namespace, issuer.Name) } -func (a *acme) createDNS01ClusterIssuer(f *framework.Framework) string { - a.ensureEABSecret(f, f.Config.Addons.CertManager.ClusterResourceNamespace) +func (a *acme) createDNS01ClusterIssuer(ctx context.Context, f *framework.Framework) string { + a.ensureEABSecret(ctx, f, f.Config.Addons.CertManager.ClusterResourceNamespace) By("Creating an ACME DNS01 ClusterIssuer") issuer := &cmapi.ClusterIssuer{ @@ -62,12 +63,12 @@ func (a *acme) createDNS01ClusterIssuer(f *framework.Framework) string { }, Spec: a.createDNS01IssuerSpec(f.Config.Addons.ACMEServer.URL, f.Config.Addons.ACMEServer.DNSServer), } - issuer, err := f.CertManagerClientSet.CertmanagerV1().ClusterIssuers().Create(context.TODO(), issuer, metav1.CreateOptions{}) + issuer, err := f.CertManagerClientSet.CertmanagerV1().ClusterIssuers().Create(ctx, issuer, metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred(), "failed to create acme DNS01 ClusterIssuer") // wait for issuer to be ready By("Waiting for acme DNS01 Cluster Issuer to be Ready") - issuer, err = f.Helper().WaitClusterIssuerReady(issuer, time.Minute*5) + issuer, err = f.Helper().WaitClusterIssuerReady(ctx, issuer, time.Minute*5) Expect(err).ToNot(HaveOccurred()) return fmt.Sprintf("clusterissuers.cert-manager.io/%s", issuer.Name) diff --git a/test/e2e/suite/conformance/certificatesigningrequests/acme/http01.go b/test/e2e/suite/conformance/certificatesigningrequests/acme/http01.go index a32143668a9..695b58c1fcc 100644 --- a/test/e2e/suite/conformance/certificatesigningrequests/acme/http01.go +++ b/test/e2e/suite/conformance/certificatesigningrequests/acme/http01.go @@ -21,18 +21,19 @@ import ( "fmt" "time" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "github.com/cert-manager/cert-manager/e2e-tests/framework" cmacme "github.com/cert-manager/cert-manager/pkg/apis/acme/v1" cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" - "github.com/cert-manager/cert-manager/test/e2e/framework" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" ) -func (a *acme) createHTTP01Issuer(f *framework.Framework) string { - a.ensureEABSecret(f, "") +func (a *acme) createHTTP01Issuer(ctx context.Context, f *framework.Framework) string { + a.ensureEABSecret(ctx, f, "") By("Creating an ACME HTTP01 Issuer") issuer := &cmapi.Issuer{ @@ -42,19 +43,19 @@ func (a *acme) createHTTP01Issuer(f *framework.Framework) string { Spec: a.createHTTP01IssuerSpec(f.Config.Addons.ACMEServer.URL), } - issuer, err := f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Create(context.TODO(), issuer, metav1.CreateOptions{}) + issuer, err := f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Create(ctx, issuer, metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred(), "failed to create acme HTTP01 issuer") // wait for issuer to be ready By("Waiting for acme HTTP01 Issuer to be Ready") - issuer, err = f.Helper().WaitIssuerReady(issuer, time.Minute*5) + issuer, err = f.Helper().WaitIssuerReady(ctx, issuer, time.Minute*5) Expect(err).ToNot(HaveOccurred()) return fmt.Sprintf("issuers.cert-manager.io/%s.%s", issuer.Namespace, issuer.Name) } -func (a *acme) createHTTP01ClusterIssuer(f *framework.Framework) string { - a.ensureEABSecret(f, f.Config.Addons.CertManager.ClusterResourceNamespace) +func (a *acme) createHTTP01ClusterIssuer(ctx context.Context, f *framework.Framework) string { + a.ensureEABSecret(ctx, f, f.Config.Addons.CertManager.ClusterResourceNamespace) By("Creating an ACME HTTP01 ClusterIssuer") issuer := &cmapi.ClusterIssuer{ @@ -64,12 +65,12 @@ func (a *acme) createHTTP01ClusterIssuer(f *framework.Framework) string { Spec: a.createHTTP01IssuerSpec(f.Config.Addons.ACMEServer.URL), } - issuer, err := f.CertManagerClientSet.CertmanagerV1().ClusterIssuers().Create(context.TODO(), issuer, metav1.CreateOptions{}) + issuer, err := f.CertManagerClientSet.CertmanagerV1().ClusterIssuers().Create(ctx, issuer, metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred(), "failed to create acme HTTP01 cluster issuer") // wait for issuer to be ready By("Waiting for acme HTTP01 Cluster Issuer to be Ready") - issuer, err = f.Helper().WaitClusterIssuerReady(issuer, time.Minute*5) + issuer, err = f.Helper().WaitClusterIssuerReady(ctx, issuer, time.Minute*5) Expect(err).ToNot(HaveOccurred()) return fmt.Sprintf("clusterissuers.cert-manager.io/%s", issuer.Name) diff --git a/test/e2e/suite/conformance/certificatesigningrequests/ca/ca.go b/test/e2e/suite/conformance/certificatesigningrequests/ca/ca.go index 66874ffd374..8a33291f049 100644 --- a/test/e2e/suite/conformance/certificatesigningrequests/ca/ca.go +++ b/test/e2e/suite/conformance/certificatesigningrequests/ca/ca.go @@ -24,16 +24,17 @@ import ( "math/big" "time" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "github.com/cert-manager/cert-manager/e2e-tests/framework" + "github.com/cert-manager/cert-manager/e2e-tests/suite/conformance/certificatesigningrequests" cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" "github.com/cert-manager/cert-manager/pkg/controller/certificatesigningrequests/util" "github.com/cert-manager/cert-manager/pkg/util/pki" - "github.com/cert-manager/cert-manager/test/e2e/framework" - "github.com/cert-manager/cert-manager/test/e2e/suite/conformance/certificatesigningrequests" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" ) var _ = framework.ConformanceDescribe("CertificateSigningRequests", func() { @@ -55,15 +56,15 @@ type ca struct { secretName string } -func (c *ca) createIssuer(f *framework.Framework) string { +func (c *ca) createIssuer(ctx context.Context, f *framework.Framework) string { By("Creating a CA Issuer") - rootCertSecret, err := f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Create(context.TODO(), newSigningKeypairSecret("root-ca-cert-"), metav1.CreateOptions{}) + rootCertSecret, err := f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Create(ctx, newSigningKeypairSecret("root-ca-cert-"), metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred(), "failed to create root signing keypair secret") c.secretName = rootCertSecret.Name - issuer, err := f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Create(context.TODO(), &cmapi.Issuer{ + issuer, err := f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Create(ctx, &cmapi.Issuer{ ObjectMeta: metav1.ObjectMeta{ GenerateName: "ca-issuer-", }, @@ -79,21 +80,21 @@ func (c *ca) createIssuer(f *framework.Framework) string { // wait for issuer to be ready By("Waiting for CA Issuer to be Ready") - issuer, err = f.Helper().WaitIssuerReady(issuer, time.Minute*5) + issuer, err = f.Helper().WaitIssuerReady(ctx, issuer, time.Minute*5) Expect(err).ToNot(HaveOccurred()) return fmt.Sprintf("issuers.cert-manager.io/%s.%s", f.Namespace.Name, issuer.Name) } -func (c *ca) createClusterIssuer(f *framework.Framework) string { +func (c *ca) createClusterIssuer(ctx context.Context, f *framework.Framework) string { By("Creating a CA ClusterIssuer") - rootCertSecret, err := f.KubeClientSet.CoreV1().Secrets(f.Config.Addons.CertManager.ClusterResourceNamespace).Create(context.TODO(), newSigningKeypairSecret("root-ca-cert-"), metav1.CreateOptions{}) + rootCertSecret, err := f.KubeClientSet.CoreV1().Secrets(f.Config.Addons.CertManager.ClusterResourceNamespace).Create(ctx, newSigningKeypairSecret("root-ca-cert-"), metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred(), "failed to create root signing keypair secret") c.secretName = rootCertSecret.Name - issuer, err := f.CertManagerClientSet.CertmanagerV1().ClusterIssuers().Create(context.TODO(), &cmapi.ClusterIssuer{ + issuer, err := f.CertManagerClientSet.CertmanagerV1().ClusterIssuers().Create(ctx, &cmapi.ClusterIssuer{ ObjectMeta: metav1.ObjectMeta{ GenerateName: "ca-cluster-issuer-", }, @@ -109,20 +110,20 @@ func (c *ca) createClusterIssuer(f *framework.Framework) string { // wait for issuer to be ready By("Waiting for CA Cluster Issuer to be Ready") - issuer, err = f.Helper().WaitClusterIssuerReady(issuer, time.Minute*5) + issuer, err = f.Helper().WaitClusterIssuerReady(ctx, issuer, time.Minute*5) Expect(err).ToNot(HaveOccurred()) return fmt.Sprintf("clusterissuers.cert-manager.io/%s", issuer.Name) } -func (c *ca) deleteClusterIssuer(f *framework.Framework, signerName string) { +func (c *ca) deleteClusterIssuer(ctx context.Context, f *framework.Framework, signerName string) { By("Deleting CA ClusterIssuer") ref, _ := util.SignerIssuerRefFromSignerName(signerName) - err := f.KubeClientSet.CoreV1().Secrets(f.Config.Addons.CertManager.ClusterResourceNamespace).Delete(context.TODO(), c.secretName, metav1.DeleteOptions{}) + err := f.KubeClientSet.CoreV1().Secrets(f.Config.Addons.CertManager.ClusterResourceNamespace).Delete(ctx, c.secretName, metav1.DeleteOptions{}) Expect(err).NotTo(HaveOccurred(), "failed to delete root signing keypair secret") - err = f.CertManagerClientSet.CertmanagerV1().ClusterIssuers().Delete(context.TODO(), ref.Name, metav1.DeleteOptions{}) + err = f.CertManagerClientSet.CertmanagerV1().ClusterIssuers().Delete(ctx, ref.Name, metav1.DeleteOptions{}) Expect(err).NotTo(HaveOccurred(), "failed to delete ca issuer") } @@ -131,7 +132,6 @@ func newSigningKeypairSecret(name string) *corev1.Secret { Expect(err).NotTo(HaveOccurred()) tmpl := &x509.Certificate{ - Version: 3, BasicConstraintsValid: true, SerialNumber: big.NewInt(0), Subject: pkix.Name{ diff --git a/test/e2e/suite/conformance/certificatesigningrequests/selfsigned/selfsigned.go b/test/e2e/suite/conformance/certificatesigningrequests/selfsigned/selfsigned.go index d0855672617..3745f749c41 100644 --- a/test/e2e/suite/conformance/certificatesigningrequests/selfsigned/selfsigned.go +++ b/test/e2e/suite/conformance/certificatesigningrequests/selfsigned/selfsigned.go @@ -22,18 +22,19 @@ import ( "fmt" "time" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" certificatesv1 "k8s.io/api/certificates/v1" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "github.com/cert-manager/cert-manager/e2e-tests/framework" + "github.com/cert-manager/cert-manager/e2e-tests/suite/conformance/certificatesigningrequests" cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" experimentalapi "github.com/cert-manager/cert-manager/pkg/apis/experimental/v1alpha1" "github.com/cert-manager/cert-manager/pkg/controller/certificatesigningrequests/util" "github.com/cert-manager/cert-manager/pkg/util/pki" - "github.com/cert-manager/cert-manager/test/e2e/framework" - "github.com/cert-manager/cert-manager/test/e2e/suite/conformance/certificatesigningrequests" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" ) var _ = framework.ConformanceDescribe("CertificateSigningRequests", func() { @@ -53,7 +54,7 @@ var _ = framework.ConformanceDescribe("CertificateSigningRequests", func() { }).Define() }) -func provision(f *framework.Framework, csr *certificatesv1.CertificateSigningRequest, key crypto.Signer) { +func provision(ctx context.Context, f *framework.Framework, csr *certificatesv1.CertificateSigningRequest, key crypto.Signer) { By("Creating SelfSigned requester key Secret") ref, _ := util.SignerIssuerRefFromSignerName(csr.Spec.SignerName) ns := "cert-manager" @@ -64,7 +65,7 @@ func provision(f *framework.Framework, csr *certificatesv1.CertificateSigningReq keyPEM, err := pki.EncodePKCS8PrivateKey(key) Expect(err).NotTo(HaveOccurred(), "failed to encode requester's private key") - secret, err := f.KubeClientSet.CoreV1().Secrets(ns).Create(context.TODO(), &corev1.Secret{ + secret, err := f.KubeClientSet.CoreV1().Secrets(ns).Create(ctx, &corev1.Secret{ ObjectMeta: metav1.ObjectMeta{ GenerateName: "selfsigned-requester-key-", Namespace: ns, @@ -80,7 +81,7 @@ func provision(f *framework.Framework, csr *certificatesv1.CertificateSigningReq } csr.Annotations[experimentalapi.CertificateSigningRequestPrivateKeyAnnotationKey] = secret.Name } -func deProvision(f *framework.Framework, csr *certificatesv1.CertificateSigningRequest) { +func deProvision(ctx context.Context, f *framework.Framework, csr *certificatesv1.CertificateSigningRequest) { By("Deleting SelfSigned requester key Secret") ref, _ := util.SignerIssuerRefFromSignerName(csr.Spec.SignerName) ns := f.Config.Addons.CertManager.ClusterResourceNamespace @@ -88,14 +89,14 @@ func deProvision(f *framework.Framework, csr *certificatesv1.CertificateSigningR ns = ref.Namespace } - err := f.KubeClientSet.CoreV1().Secrets(ns).Delete(context.TODO(), csr.Annotations[experimentalapi.CertificateSigningRequestPrivateKeyAnnotationKey], metav1.DeleteOptions{}) + err := f.KubeClientSet.CoreV1().Secrets(ns).Delete(ctx, csr.Annotations[experimentalapi.CertificateSigningRequestPrivateKeyAnnotationKey], metav1.DeleteOptions{}) Expect(err).NotTo(HaveOccurred(), "failed to create requester's private key Secret") } -func createSelfSignedIssuer(f *framework.Framework) string { +func createSelfSignedIssuer(ctx context.Context, f *framework.Framework) string { By("Creating a SelfSigned Issuer") - issuer, err := f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Create(context.TODO(), &cmapi.Issuer{ + issuer, err := f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Create(ctx, &cmapi.Issuer{ ObjectMeta: metav1.ObjectMeta{ GenerateName: "selfsigned-issuer-", }, @@ -105,20 +106,20 @@ func createSelfSignedIssuer(f *framework.Framework) string { }, }, }, metav1.CreateOptions{}) - Expect(err).NotTo(HaveOccurred(), "failed to create self signed issuer") + Expect(err).NotTo(HaveOccurred(), "failed to create self-signed issuer") // wait for issuer to be ready By("Waiting for Self Signed Issuer to be Ready") - issuer, err = f.Helper().WaitIssuerReady(issuer, time.Minute*5) + issuer, err = f.Helper().WaitIssuerReady(ctx, issuer, time.Minute*5) Expect(err).ToNot(HaveOccurred()) return fmt.Sprintf("issuers.cert-manager.io/%s.%s", f.Namespace.Name, issuer.Name) } -func createSelfSignedClusterIssuer(f *framework.Framework) string { +func createSelfSignedClusterIssuer(ctx context.Context, f *framework.Framework) string { By("Creating a SelfSigned ClusterIssuer") - issuer, err := f.CertManagerClientSet.CertmanagerV1().ClusterIssuers().Create(context.TODO(), &cmapi.ClusterIssuer{ + issuer, err := f.CertManagerClientSet.CertmanagerV1().ClusterIssuers().Create(ctx, &cmapi.ClusterIssuer{ ObjectMeta: metav1.ObjectMeta{ GenerateName: "selfsigned-cluster-issuer-", }, @@ -128,18 +129,18 @@ func createSelfSignedClusterIssuer(f *framework.Framework) string { }, }, }, metav1.CreateOptions{}) - Expect(err).NotTo(HaveOccurred(), "failed to create self signed issuer") + Expect(err).NotTo(HaveOccurred(), "failed to create self-signed issuer") // wait for issuer to be ready By("Waiting for Self Signed Cluster Issuer to be Ready") - issuer, err = f.Helper().WaitClusterIssuerReady(issuer, time.Minute*5) + issuer, err = f.Helper().WaitClusterIssuerReady(ctx, issuer, time.Minute*5) Expect(err).ToNot(HaveOccurred()) return fmt.Sprintf("clusterissuers.cert-manager.io/%s", issuer.Name) } -func deleteSelfSignedClusterIssuer(f *framework.Framework, signerName string) { +func deleteSelfSignedClusterIssuer(ctx context.Context, f *framework.Framework, signerName string) { ref, _ := util.SignerIssuerRefFromSignerName(signerName) - err := f.CertManagerClientSet.CertmanagerV1().ClusterIssuers().Delete(context.TODO(), ref.Name, metav1.DeleteOptions{}) + err := f.CertManagerClientSet.CertmanagerV1().ClusterIssuers().Delete(ctx, ref.Name, metav1.DeleteOptions{}) Expect(err).NotTo(HaveOccurred()) } diff --git a/test/e2e/suite/conformance/certificatesigningrequests/suite.go b/test/e2e/suite/conformance/certificatesigningrequests/suite.go index 01d8d0a67f5..9a488557ce2 100644 --- a/test/e2e/suite/conformance/certificatesigningrequests/suite.go +++ b/test/e2e/suite/conformance/certificatesigningrequests/suite.go @@ -17,21 +17,23 @@ limitations under the License. package certificatesigningrequests import ( + "context" "crypto" - . "github.com/onsi/ginkgo/v2" certificatesv1 "k8s.io/api/certificates/v1" + "github.com/cert-manager/cert-manager/e2e-tests/framework" + "github.com/cert-manager/cert-manager/e2e-tests/framework/helper/featureset" "github.com/cert-manager/cert-manager/internal/controller/feature" utilfeature "github.com/cert-manager/cert-manager/pkg/util/feature" - "github.com/cert-manager/cert-manager/test/e2e/framework" - "github.com/cert-manager/cert-manager/test/e2e/framework/helper/featureset" + + . "github.com/onsi/ginkgo/v2" ) // Suite defines a reusable conformance test suite that can be used against any // Issuer implementation. type Suite struct { - // Name is the name of the issuer being tested, e.g. SelfSigned, CA, ACME + // Name is the name of the issuer being tested, e.g., SelfSigned, CA, ACME // This field must be provided. Name string @@ -39,14 +41,14 @@ type Suite struct { // returns an SignerName to that Issuer that will be used as the SignerName // on CertificateSigningRequest resources that this suite creates. // This field must be provided. - CreateIssuerFunc func(*framework.Framework) string + CreateIssuerFunc func(context.Context, *framework.Framework) string // DeleteIssuerFunc is a function that is run after the test has completed - // in order to clean up resources created for a test (e.g. the resources + // in order to clean up resources created for a test (e.g., the resources // created in CreateIssuerFunc). // This function will be run regardless whether the test passes or fails. // If not specified, this function will be skipped. - DeleteIssuerFunc func(*framework.Framework, string) + DeleteIssuerFunc func(context.Context, *framework.Framework, string) // ProvisionFunc is a function that is run every test just before the // CertificateSigningRequest is created within a test. This is used to @@ -55,12 +57,17 @@ type Suite struct { // CertificateSigningRequest, or create a resource like a Secret needed for // signing. // If not specified, this function will be skipped. - ProvisionFunc func(*framework.Framework, *certificatesv1.CertificateSigningRequest, crypto.Signer) + ProvisionFunc func(context.Context, *framework.Framework, *certificatesv1.CertificateSigningRequest, crypto.Signer) // DeProvisionFunc is run after every test. This is to be used to remove and // clean-up any resources which may have been created by ProvisionFunc. // If not specified, this function will be skipped. - DeProvisionFunc func(*framework.Framework, *certificatesv1.CertificateSigningRequest) + DeProvisionFunc func(context.Context, *framework.Framework, *certificatesv1.CertificateSigningRequest) + + // SharedIPAddress is the IP address that will be used in all certificates + // that require an IP address to be set. For HTTP-01 tests, this IP address + // will be set to the IP address of the Ingress/ Gateway controller. + SharedIPAddress string // DomainSuffix is a suffix used on all domain requests. // This is useful when the issuer being tested requires special @@ -70,18 +77,55 @@ type Suite struct { // nginx-ingress addon. DomainSuffix string + // HTTP01TestType is set to "Ingress" or "Gateway" to determine which IPs + // and Domains will be used to run the ACME HTTP-01 test suites. + HTTP01TestType string + // UnsupportedFeatures is a list of features that are not supported by this // invocation of the test suite. // This is useful if a particular issuers explicitly does not support // certain features due to restrictions in their implementation. UnsupportedFeatures featureset.FeatureSet - // completed is used internally to track whether Complete() has been called - completed bool + // validated is used internally to track whether Validate has been called already. + validated bool +} + +// setup will set default values for fields on the Suite struct. +func (s *Suite) setup(f *framework.Framework) { + if s.SharedIPAddress == "" { + switch s.HTTP01TestType { + case "Ingress": + s.SharedIPAddress = f.Config.Addons.ACMEServer.IngressIP + case "Gateway": + s.SharedIPAddress = f.Config.Addons.ACMEServer.GatewayIP + default: + s.SharedIPAddress = "127.0.0.1" + } + } + + if s.DomainSuffix == "" { + switch s.HTTP01TestType { + case "Ingress": + s.DomainSuffix = f.Config.Addons.IngressController.Domain + case "Gateway": + s.DomainSuffix = f.Config.Addons.Gateway.Domain + default: + s.DomainSuffix = "example.com" + } + } + + if s.UnsupportedFeatures == nil { + s.UnsupportedFeatures = make(featureset.FeatureSet) + } } -// complete will validate configuration and set default values. -func (s *Suite) complete(f *framework.Framework) { +// validate will validate the Suite struct to ensure all required fields are set. +func (s *Suite) validate() { + if s.validated { + return + } + if s.Name == "" { Fail("Name must be set") } @@ -90,51 +134,29 @@ func (s *Suite) complete(f *framework.Framework) { Fail("CreateIssuerFunc must be set") } - if s.DomainSuffix == "" { - s.DomainSuffix = f.Config.Addons.IngressController.Domain + if s.HTTP01TestType == "Gateway" { + framework.RequireFeatureGate(utilfeature.DefaultFeatureGate, feature.ExperimentalGatewayAPISupport) } - if s.UnsupportedFeatures == nil { - s.UnsupportedFeatures = make(featureset.FeatureSet) - } - - s.completed = true + s.validated = true } // it is called by the tests to in Define() to setup and run the test -func (s *Suite) it(f *framework.Framework, name string, fn func(string), requiredFeatures ...featureset.Feature) { - if !s.checkFeatures(requiredFeatures...) { +func (s *Suite) it(f *framework.Framework, name string, fn func(context.Context, string), requiredFeatures ...featureset.Feature) { + if s.UnsupportedFeatures.HasAny(requiredFeatures...) { return } - It(name, func() { - framework.RequireFeatureGate(f, utilfeature.DefaultFeatureGate, feature.ExperimentalCertificateSigningRequestControllers) + It(name, func(ctx context.Context) { + framework.RequireFeatureGate(utilfeature.DefaultFeatureGate, feature.ExperimentalCertificateSigningRequestControllers) By("Creating an issuer resource") - signerName := s.CreateIssuerFunc(f) + signerName := s.CreateIssuerFunc(ctx, f) defer func() { if s.DeleteIssuerFunc != nil { By("Cleaning up the issuer resource") - s.DeleteIssuerFunc(f, signerName) + s.DeleteIssuerFunc(ctx, f, signerName) } }() - fn(signerName) + fn(ctx, signerName) }) } - -// checkFeatures is a helper function that is used to ensure that the features -// required for a given test case are supported by the suite. -// It will return 'true' if all features are supported and the test should run, -// or return 'false' if any required feature is not supported. -func (s *Suite) checkFeatures(fs ...featureset.Feature) bool { - unsupported := make(featureset.FeatureSet) - for _, f := range fs { - if s.UnsupportedFeatures.Contains(f) { - unsupported.Add(f) - } - } - // all features supported, return early! - if len(unsupported) == 0 { - return true - } - return false -} diff --git a/test/e2e/suite/conformance/certificatesigningrequests/tests.go b/test/e2e/suite/conformance/certificatesigningrequests/tests.go index 076ff2120e4..d96f0cb6a04 100644 --- a/test/e2e/suite/conformance/certificatesigningrequests/tests.go +++ b/test/e2e/suite/conformance/certificatesigningrequests/tests.go @@ -23,21 +23,22 @@ import ( "net/url" "time" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" certificatesv1 "k8s.io/api/certificates/v1" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/utils/pointer" + "k8s.io/apimachinery/pkg/util/rand" + "k8s.io/utils/ptr" + "github.com/cert-manager/cert-manager/e2e-tests/framework" + "github.com/cert-manager/cert-manager/e2e-tests/framework/helper/featureset" + "github.com/cert-manager/cert-manager/e2e-tests/framework/helper/validation" + "github.com/cert-manager/cert-manager/e2e-tests/framework/helper/validation/certificatesigningrequests" + e2eutil "github.com/cert-manager/cert-manager/e2e-tests/util" experimentalapi "github.com/cert-manager/cert-manager/pkg/apis/experimental/v1alpha1" - "github.com/cert-manager/cert-manager/pkg/util" - "github.com/cert-manager/cert-manager/test/e2e/framework" - "github.com/cert-manager/cert-manager/test/e2e/framework/helper/featureset" - "github.com/cert-manager/cert-manager/test/e2e/framework/helper/validation" - "github.com/cert-manager/cert-manager/test/e2e/framework/helper/validation/certificatesigningrequests" - e2eutil "github.com/cert-manager/cert-manager/test/e2e/util" "github.com/cert-manager/cert-manager/test/unit/gen" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" ) // Defines simple conformance tests that can be run against any issuer type. @@ -49,10 +50,9 @@ import ( // they are not active, these tests will fail. func (s *Suite) Define() { Describe("CertificateSigningRequest with issuer type "+s.Name, func() { - ctx := context.Background() f := framework.NewDefaultFramework("certificatesigningrequests") + s.setup(f) - sharedCommonName := "" sharedURI, err := url.Parse("spiffe://cluster.local/ns/sandbox/sa/foo") if err != nil { // This should never happen, and is a bug. Panic to prevent garbage test @@ -62,23 +62,17 @@ func (s *Suite) Define() { // Wrap this in a BeforeEach else flags will not have been parsed and // f.Config will not be populated at the time that this code is run. - BeforeEach(func() { - if s.completed { - return - } - - s.complete(f) - - sharedCommonName = e2eutil.RandomSubdomain(s.DomainSuffix) + BeforeEach(func(testingCtx context.Context) { + s.validate() }) type testCase struct { name string // ginkgo v2 does not support using map[string] to store the test names (#5345) keyAlgo x509.PublicKeyAlgorithm - // csrModifers define the shape of the X.509 CSR which is used in the + // csrModifiers define the shape of the X.509 CSR which is used in the // test case. We use a function to allow access to variables that are // initialized at test runtime by complete(). - csrModifiers func() []gen.CSRModifier + csrModifiers []gen.CSRModifier kubeCSRUsages []certificatesv1.KeyUsage kubeCSRAnnotations map[string]string kubeCSRExpirationSeconds *int32 @@ -94,8 +88,8 @@ func (s *Suite) Define() { { name: "should issue an RSA certificate for a single distinct DNS Name", keyAlgo: x509.RSA, - csrModifiers: func() []gen.CSRModifier { - return []gen.CSRModifier{gen.SetCSRDNSNames(e2eutil.RandomSubdomain(s.DomainSuffix))} + csrModifiers: []gen.CSRModifier{ + gen.SetCSRDNSNames(e2eutil.RandomSubdomain(s.DomainSuffix)), }, kubeCSRUsages: []certificatesv1.KeyUsage{ certificatesv1.UsageDigitalSignature, @@ -106,8 +100,8 @@ func (s *Suite) Define() { { name: "should issue an ECDSA certificate for a single distinct DNS Name", keyAlgo: x509.ECDSA, - csrModifiers: func() []gen.CSRModifier { - return []gen.CSRModifier{gen.SetCSRDNSNames(e2eutil.RandomSubdomain(s.DomainSuffix))} + csrModifiers: []gen.CSRModifier{ + gen.SetCSRDNSNames(e2eutil.RandomSubdomain(s.DomainSuffix)), }, kubeCSRUsages: []certificatesv1.KeyUsage{ certificatesv1.UsageDigitalSignature, @@ -118,8 +112,8 @@ func (s *Suite) Define() { { name: "should issue an Ed25519 certificate for a single distinct DNS Name", keyAlgo: x509.Ed25519, - csrModifiers: func() []gen.CSRModifier { - return []gen.CSRModifier{gen.SetCSRDNSNames(e2eutil.RandomSubdomain(s.DomainSuffix))} + csrModifiers: []gen.CSRModifier{ + gen.SetCSRDNSNames(e2eutil.RandomSubdomain(s.DomainSuffix)), }, kubeCSRUsages: []certificatesv1.KeyUsage{ certificatesv1.UsageDigitalSignature, @@ -130,8 +124,8 @@ func (s *Suite) Define() { { name: "should issue an RSA certificate for a single Common Name", keyAlgo: x509.RSA, - csrModifiers: func() []gen.CSRModifier { - return []gen.CSRModifier{gen.SetCSRCommonName("test-common-name-" + util.RandStringRunes(10))} + csrModifiers: []gen.CSRModifier{ + gen.SetCSRCommonName("test-common-name-" + rand.String(10)), }, kubeCSRUsages: []certificatesv1.KeyUsage{ certificatesv1.UsageDigitalSignature, @@ -142,8 +136,8 @@ func (s *Suite) Define() { { name: "should issue an ECDSA certificate for a single Common Name", keyAlgo: x509.ECDSA, - csrModifiers: func() []gen.CSRModifier { - return []gen.CSRModifier{gen.SetCSRCommonName("test-common-name-" + util.RandStringRunes(10))} + csrModifiers: []gen.CSRModifier{ + gen.SetCSRCommonName("test-common-name-" + rand.String(10)), }, kubeCSRUsages: []certificatesv1.KeyUsage{ certificatesv1.UsageDigitalSignature, @@ -154,8 +148,8 @@ func (s *Suite) Define() { { name: "should issue an Ed25519 certificate for a single Common Name", keyAlgo: x509.Ed25519, - csrModifiers: func() []gen.CSRModifier { - return []gen.CSRModifier{gen.SetCSRCommonName("test-common-name-" + util.RandStringRunes(10))} + csrModifiers: []gen.CSRModifier{ + gen.SetCSRCommonName("test-common-name-" + rand.String(10)), }, kubeCSRUsages: []certificatesv1.KeyUsage{ certificatesv1.UsageDigitalSignature, @@ -166,11 +160,9 @@ func (s *Suite) Define() { { name: "should issue a certificate that defines a Common Name and IP Address", keyAlgo: x509.RSA, - csrModifiers: func() []gen.CSRModifier { - return []gen.CSRModifier{ - gen.SetCSRCommonName("test-common-name-" + util.RandStringRunes(10)), - gen.SetCSRIPAddresses(net.IPv4(127, 0, 0, 1), net.IPv4(8, 8, 8, 8)), - } + csrModifiers: []gen.CSRModifier{ + gen.SetCSRCommonName("test-common-name-" + rand.String(10)), + gen.SetCSRIPAddresses(net.ParseIP(s.SharedIPAddress)), }, kubeCSRUsages: []certificatesv1.KeyUsage{ certificatesv1.UsageDigitalSignature, @@ -178,13 +170,51 @@ func (s *Suite) Define() { }, requiredFeatures: []featureset.Feature{featureset.CommonNameFeature, featureset.IPAddressFeature}, }, + { + name: "should issue a certificate that defines an IP Address", + keyAlgo: x509.RSA, + csrModifiers: []gen.CSRModifier{ + gen.SetCSRIPAddresses(net.ParseIP(s.SharedIPAddress)), + }, + kubeCSRUsages: []certificatesv1.KeyUsage{ + certificatesv1.UsageDigitalSignature, + certificatesv1.UsageKeyEncipherment, + }, + requiredFeatures: []featureset.Feature{featureset.IPAddressFeature}, + }, + { + name: "should issue a certificate that defines a DNS Name and IP Address", + keyAlgo: x509.RSA, + csrModifiers: []gen.CSRModifier{ + gen.SetCSRIPAddresses(net.ParseIP(s.SharedIPAddress)), + gen.SetCSRDNSNames(e2eutil.RandomSubdomain(s.DomainSuffix)), + }, + kubeCSRUsages: []certificatesv1.KeyUsage{ + certificatesv1.UsageDigitalSignature, + certificatesv1.UsageKeyEncipherment, + }, + requiredFeatures: []featureset.Feature{featureset.OnlySAN, featureset.IPAddressFeature}, + }, + { + name: "should issue a CA certificate with the CA basicConstraint set", + keyAlgo: x509.RSA, + csrModifiers: []gen.CSRModifier{ + gen.SetCSRDNSNames(e2eutil.RandomSubdomain(s.DomainSuffix)), + }, + kubeCSRAnnotations: map[string]string{ + experimentalapi.CertificateSigningRequestIsCAAnnotationKey: "true", + }, + kubeCSRUsages: []certificatesv1.KeyUsage{ + certificatesv1.UsageDigitalSignature, + certificatesv1.UsageKeyEncipherment, + }, + requiredFeatures: []featureset.Feature{featureset.OnlySAN, featureset.IssueCAFeature}, + }, { name: "should issue a certificate that defines an Email Address", keyAlgo: x509.RSA, - csrModifiers: func() []gen.CSRModifier { - return []gen.CSRModifier{ - gen.SetCSREmails([]string{"alice@example.com", "bob@cert-manager.io"}), - } + csrModifiers: []gen.CSRModifier{ + gen.SetCSREmails([]string{"alice@example.com", "bob@cert-manager.io"}), }, kubeCSRUsages: []certificatesv1.KeyUsage{ certificatesv1.UsageDigitalSignature, @@ -195,11 +225,9 @@ func (s *Suite) Define() { { name: "should issue a certificate that defines a Common Name and URI SAN", keyAlgo: x509.RSA, - csrModifiers: func() []gen.CSRModifier { - return []gen.CSRModifier{ - gen.SetCSRCommonName("test-common-name-" + util.RandStringRunes(10)), - gen.SetCSRURIs(sharedURI), - } + csrModifiers: []gen.CSRModifier{ + gen.SetCSRCommonName("test-common-name-" + rand.String(10)), + gen.SetCSRURIs(sharedURI), }, kubeCSRUsages: []certificatesv1.KeyUsage{ certificatesv1.UsageDigitalSignature, @@ -208,28 +236,28 @@ func (s *Suite) Define() { requiredFeatures: []featureset.Feature{featureset.CommonNameFeature, featureset.URISANsFeature}, }, { - name: "should issue a certificate that defines a 2 distinct DNS Name with one copied to the Common Name", + name: "should issue a certificate that define 2 distinct DNS Names with one copied to the Common Name", keyAlgo: x509.RSA, csrModifiers: func() []gen.CSRModifier { + commonName := e2eutil.RandomSubdomain(s.DomainSuffix) + return []gen.CSRModifier{ - gen.SetCSRCommonName(sharedCommonName), - gen.SetCSRDNSNames(sharedCommonName, e2eutil.RandomSubdomain(s.DomainSuffix)), + gen.SetCSRCommonName(commonName), + gen.SetCSRDNSNames(commonName, e2eutil.RandomSubdomain(s.DomainSuffix)), } - }, + }(), kubeCSRUsages: []certificatesv1.KeyUsage{ certificatesv1.UsageDigitalSignature, certificatesv1.UsageKeyEncipherment, }, - requiredFeatures: []featureset.Feature{}, + requiredFeatures: []featureset.Feature{featureset.CommonNameFeature}, }, { name: "should issue a certificate that defines a distinct DNS Name and another distinct Common Name", keyAlgo: x509.RSA, - csrModifiers: func() []gen.CSRModifier { - return []gen.CSRModifier{ - gen.SetCSRCommonName(e2eutil.RandomSubdomain(s.DomainSuffix)), - gen.SetCSRDNSNames(e2eutil.RandomSubdomain(s.DomainSuffix)), - } + csrModifiers: []gen.CSRModifier{ + gen.SetCSRCommonName(e2eutil.RandomSubdomain(s.DomainSuffix)), + gen.SetCSRDNSNames(e2eutil.RandomSubdomain(s.DomainSuffix)), }, kubeCSRUsages: []certificatesv1.KeyUsage{ certificatesv1.UsageDigitalSignature, @@ -241,11 +269,13 @@ func (s *Suite) Define() { name: "should issue a certificate that defines a Common Name, DNS Name, and sets a duration", keyAlgo: x509.RSA, csrModifiers: func() []gen.CSRModifier { + commonName := e2eutil.RandomSubdomain(s.DomainSuffix) + return []gen.CSRModifier{ - gen.SetCSRDNSNames(sharedCommonName), - gen.SetCSRDNSNames(sharedCommonName), + gen.SetCSRCommonName(commonName), + gen.SetCSRDNSNames(commonName), } - }, + }(), kubeCSRUsages: []certificatesv1.KeyUsage{ certificatesv1.UsageDigitalSignature, certificatesv1.UsageKeyEncipherment, @@ -259,25 +289,25 @@ func (s *Suite) Define() { name: "should issue a certificate that defines a Common Name, DNS Name, and sets a duration via expiration seconds", keyAlgo: x509.RSA, csrModifiers: func() []gen.CSRModifier { + commonName := e2eutil.RandomSubdomain(s.DomainSuffix) + return []gen.CSRModifier{ - gen.SetCSRDNSNames(sharedCommonName), - gen.SetCSRDNSNames(sharedCommonName), + gen.SetCSRCommonName(commonName), + gen.SetCSRDNSNames(commonName), } - }, + }(), kubeCSRUsages: []certificatesv1.KeyUsage{ certificatesv1.UsageDigitalSignature, certificatesv1.UsageKeyEncipherment, }, - kubeCSRExpirationSeconds: pointer.Int32(3333), + kubeCSRExpirationSeconds: ptr.To(int32(3333)), requiredFeatures: []featureset.Feature{featureset.DurationFeature}, }, { name: "should issue a certificate that defines a DNS Name and sets a duration", keyAlgo: x509.RSA, - csrModifiers: func() []gen.CSRModifier { - return []gen.CSRModifier{ - gen.SetCSRDNSNames(e2eutil.RandomSubdomain(s.DomainSuffix)), - } + csrModifiers: []gen.CSRModifier{ + gen.SetCSRDNSNames(e2eutil.RandomSubdomain(s.DomainSuffix)), }, kubeCSRUsages: []certificatesv1.KeyUsage{ certificatesv1.UsageDigitalSignature, @@ -291,10 +321,8 @@ func (s *Suite) Define() { { name: "should issue a certificate which has a wildcard DNS Name defined", keyAlgo: x509.RSA, - csrModifiers: func() []gen.CSRModifier { - return []gen.CSRModifier{ - gen.SetCSRDNSNames("*." + e2eutil.RandomSubdomain(s.DomainSuffix)), - } + csrModifiers: []gen.CSRModifier{ + gen.SetCSRDNSNames("*." + e2eutil.RandomSubdomain(s.DomainSuffix)), }, kubeCSRUsages: []certificatesv1.KeyUsage{ certificatesv1.UsageDigitalSignature, @@ -303,12 +331,26 @@ func (s *Suite) Define() { requiredFeatures: []featureset.Feature{featureset.WildcardsFeature, featureset.OnlySAN}, }, { - name: "should issue a certificate that includes only a URISANs name", + name: "should issue a certificate which has a wildcard DNS Name and its apex DNS Name defined", keyAlgo: x509.RSA, csrModifiers: func() []gen.CSRModifier { + dnsDomain := e2eutil.RandomSubdomain(s.DomainSuffix) + return []gen.CSRModifier{ - gen.SetCSRURIs(sharedURI), + gen.SetCSRDNSNames("*."+dnsDomain, dnsDomain), } + }(), + kubeCSRUsages: []certificatesv1.KeyUsage{ + certificatesv1.UsageDigitalSignature, + certificatesv1.UsageKeyEncipherment, + }, + requiredFeatures: []featureset.Feature{featureset.WildcardsFeature, featureset.OnlySAN}, + }, + { + name: "should issue a certificate that includes only a URISANs name", + keyAlgo: x509.RSA, + csrModifiers: []gen.CSRModifier{ + gen.SetCSRURIs(sharedURI), }, kubeCSRUsages: []certificatesv1.KeyUsage{ certificatesv1.UsageDigitalSignature, @@ -317,13 +359,10 @@ func (s *Suite) Define() { requiredFeatures: []featureset.Feature{featureset.URISANsFeature, featureset.OnlySAN}, }, { - name: "should issue a certificate that includes arbitrary key usages", + name: "should issue a certificate that includes arbitrary key usages with common name", keyAlgo: x509.RSA, - csrModifiers: func() []gen.CSRModifier { - return []gen.CSRModifier{ - gen.SetCSRCommonName(sharedCommonName), - gen.SetCSRDNSNames(sharedCommonName), - } + csrModifiers: []gen.CSRModifier{ + gen.SetCSRCommonName(e2eutil.RandomSubdomain(s.DomainSuffix)), }, kubeCSRUsages: []certificatesv1.KeyUsage{ certificatesv1.UsageServerAuth, @@ -331,21 +370,19 @@ func (s *Suite) Define() { certificatesv1.UsageDigitalSignature, certificatesv1.UsageDataEncipherment, }, - requiredFeatures: []featureset.Feature{featureset.KeyUsagesFeature}, extraValidations: []certificatesigningrequests.ValidationFunc{ certificatesigningrequests.ExpectKeyUsageExtKeyUsageClientAuth, certificatesigningrequests.ExpectKeyUsageExtKeyUsageServerAuth, certificatesigningrequests.ExpectKeyUsageUsageDigitalSignature, certificatesigningrequests.ExpectKeyUsageUsageDataEncipherment, }, + requiredFeatures: []featureset.Feature{featureset.KeyUsagesFeature}, }, { name: "should issue a signing CA certificate that has a large duration", keyAlgo: x509.RSA, - csrModifiers: func() []gen.CSRModifier { - return []gen.CSRModifier{ - gen.SetCSRCommonName("cert-manager-ca"), - } + csrModifiers: []gen.CSRModifier{ + gen.SetCSRCommonName("cert-manager-ca"), }, kubeCSRUsages: []certificatesv1.KeyUsage{ certificatesv1.UsageDigitalSignature, @@ -358,19 +395,47 @@ func (s *Suite) Define() { }, requiredFeatures: []featureset.Feature{featureset.KeyUsagesFeature, featureset.DurationFeature, featureset.CommonNameFeature}, }, + { + name: "should issue a certificate that defines a long domain", + keyAlgo: x509.RSA, + csrModifiers: func() []gen.CSRModifier { + const maxLengthOfDomainSegment = 63 + return []gen.CSRModifier{ + gen.SetCSRDNSNames(e2eutil.RandomSubdomainLength(s.DomainSuffix, maxLengthOfDomainSegment)), + } + }(), + kubeCSRUsages: []certificatesv1.KeyUsage{ + certificatesv1.UsageDigitalSignature, + certificatesv1.UsageKeyEncipherment, + }, + requiredFeatures: []featureset.Feature{featureset.OnlySAN, featureset.LongDomainFeatureSet}, + }, + } + + addAnnotation := func(annotations map[string]string, key, value string) map[string]string { + if annotations == nil { + annotations = map[string]string{} + } + annotations[key] = value + return annotations } defineTest := func(test testCase) { - s.it(f, test.name, func(signerName string) { + s.it(f, test.name, func(ctx context.Context, signerName string) { // Generate request CSR - csr, key, err := gen.CSR(test.keyAlgo, test.csrModifiers()...) + csr, key, err := gen.CSR(test.keyAlgo, test.csrModifiers...) Expect(err).NotTo(HaveOccurred()) // Create CertificateSigningRequest + randomTestID := rand.String(10) kubeCSR := &certificatesv1.CertificateSigningRequest{ ObjectMeta: metav1.ObjectMeta{ - GenerateName: "e2e-conformance-", - Annotations: test.kubeCSRAnnotations, + Name: "e2e-conformance-" + randomTestID, + Annotations: addAnnotation( + test.kubeCSRAnnotations, + "conformance.cert-manager.io/test-name", + s.Name+" "+test.name, + ), }, Spec: certificatesv1.CertificateSigningRequestSpec{ Request: csr, @@ -383,17 +448,23 @@ func (s *Suite) Define() { // Provision any resources needed for the request, or modify the // request based on Issuer requirements if s.ProvisionFunc != nil { - s.ProvisionFunc(f, kubeCSR, key) + s.ProvisionFunc(ctx, f, kubeCSR, key) } // Ensure related resources are cleaned up at the end of the test if s.DeProvisionFunc != nil { - defer s.DeProvisionFunc(f, kubeCSR) + defer s.DeProvisionFunc(ctx, f, kubeCSR) } // Create the request, and delete at the end of the test By("Creating a CertificateSigningRequest") Expect(f.CRClient.Create(ctx, kubeCSR)).NotTo(HaveOccurred()) - defer f.CRClient.Delete(context.TODO(), kubeCSR) + defer func() { + cleanupCtx, cancel := context.WithTimeout(context.WithoutCancel(ctx), 10*time.Second) + defer cancel() + + err := f.CRClient.Delete(cleanupCtx, kubeCSR) + Expect(err).NotTo(HaveOccurred()) + }() // Approve the request for testing, so that cert-manager may sign the // request. @@ -404,19 +475,21 @@ func (s *Suite) Define() { Reason: "e2e.cert-manager.io", Message: "Request approved for e2e testing.", }) - kubeCSR, err = f.KubeClientSet.CertificatesV1().CertificateSigningRequests().UpdateApproval(context.TODO(), kubeCSR.Name, kubeCSR, metav1.UpdateOptions{}) + kubeCSR, err = f.KubeClientSet.CertificatesV1().CertificateSigningRequests().UpdateApproval(ctx, kubeCSR.Name, kubeCSR, metav1.UpdateOptions{}) Expect(err).NotTo(HaveOccurred()) // Wait for the status.Certificate and CA annotation to be populated in // a reasonable amount of time. By("Waiting for the CertificateSigningRequest to be issued...") - kubeCSR, err = f.Helper().WaitForCertificateSigningRequestSigned(kubeCSR.Name, time.Minute*5) + kubeCSR, err = f.Helper().WaitForCertificateSigningRequestSigned(ctx, kubeCSR.Name, time.Minute*5) Expect(err).NotTo(HaveOccurred()) // Validate that the request was signed as expected. Add extra // validations which may be required for this test. By("Validating the issued CertificateSigningRequest...") - validations := append(test.extraValidations, validation.CertificateSigningRequestSetForUnsupportedFeatureSet(s.UnsupportedFeatures)...) + validations := []certificatesigningrequests.ValidationFunc(nil) + validations = append(validations, test.extraValidations...) + validations = append(validations, validation.CertificateSigningRequestSetForUnsupportedFeatureSet(s.UnsupportedFeatures)...) err = f.Helper().ValidateCertificateSigningRequest(kubeCSR.Name, key, validations...) Expect(err).NotTo(HaveOccurred()) }, test.requiredFeatures...) diff --git a/test/e2e/suite/conformance/certificatesigningrequests/vault/approle.go b/test/e2e/suite/conformance/certificatesigningrequests/vault/approle.go index 3c5a4075045..c14514479f0 100644 --- a/test/e2e/suite/conformance/certificatesigningrequests/vault/approle.go +++ b/test/e2e/suite/conformance/certificatesigningrequests/vault/approle.go @@ -19,38 +19,27 @@ package vault import ( "context" "fmt" - "path" "time" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "github.com/cert-manager/cert-manager/e2e-tests/framework" + "github.com/cert-manager/cert-manager/e2e-tests/framework/addon" + "github.com/cert-manager/cert-manager/e2e-tests/framework/addon/vault" + "github.com/cert-manager/cert-manager/e2e-tests/framework/helper/featureset" + "github.com/cert-manager/cert-manager/e2e-tests/suite/conformance/certificatesigningrequests" cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" "github.com/cert-manager/cert-manager/pkg/controller/certificatesigningrequests/util" - "github.com/cert-manager/cert-manager/test/e2e/framework" - "github.com/cert-manager/cert-manager/test/e2e/framework/addon" - "github.com/cert-manager/cert-manager/test/e2e/framework/addon/vault" - "github.com/cert-manager/cert-manager/test/e2e/framework/helper/featureset" - "github.com/cert-manager/cert-manager/test/e2e/suite/conformance/certificatesigningrequests" -) -const ( - rootMount = "root-ca" - intermediateMount = "intermediate-ca" - role = "kubernetes-vault" - secretAppRoleName = "vault-role-" - authPath = "approle" - customAuthPath = "custom/path" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" ) type approle struct { - authPath string testWithRootCA bool - addon *vault.Vault - initializer *vault.VaultInitializer + setup *vault.VaultInitializer *secrets } @@ -64,153 +53,133 @@ type secrets struct { } var _ = framework.ConformanceDescribe("CertificateSigningRequests", func() { + var unsupportedFeatures = featureset.NewFeatureSet( + featureset.KeyUsagesFeature, + featureset.Ed25519FeatureSet, + featureset.IssueCAFeature, + ) + issuer := &approle{ testWithRootCA: true, - authPath: authPath, } (&certificatesigningrequests.Suite{ - Name: "Vault AppRole Issuer With Root CA", - CreateIssuerFunc: issuer.createIssuer, - DeleteIssuerFunc: issuer.delete, - UnsupportedFeatures: featureset.NewFeatureSet( - featureset.KeyUsagesFeature, - featureset.Ed25519FeatureSet, - ), + Name: "Vault AppRole Issuer With Root CA", + CreateIssuerFunc: issuer.createIssuer, + DeleteIssuerFunc: issuer.delete, + UnsupportedFeatures: unsupportedFeatures, }).Define() issuerNoRoot := &approle{ testWithRootCA: false, - authPath: authPath, } (&certificatesigningrequests.Suite{ - Name: "Vault AppRole Issuer Without Root CA", - CreateIssuerFunc: issuerNoRoot.createIssuer, - DeleteIssuerFunc: issuerNoRoot.delete, - UnsupportedFeatures: featureset.NewFeatureSet( - featureset.KeyUsagesFeature, - featureset.Ed25519FeatureSet, - ), + Name: "Vault AppRole Issuer Without Root CA", + CreateIssuerFunc: issuerNoRoot.createIssuer, + DeleteIssuerFunc: issuerNoRoot.delete, + UnsupportedFeatures: unsupportedFeatures, }).Define() clusterIssuer := &approle{ testWithRootCA: true, - authPath: authPath, } (&certificatesigningrequests.Suite{ - Name: "Vault AppRole ClusterIssuer With Root CA", - CreateIssuerFunc: clusterIssuer.createClusterIssuer, - DeleteIssuerFunc: clusterIssuer.delete, - UnsupportedFeatures: featureset.NewFeatureSet( - featureset.KeyUsagesFeature, - featureset.Ed25519FeatureSet, - ), + Name: "Vault AppRole ClusterIssuer With Root CA", + CreateIssuerFunc: clusterIssuer.createClusterIssuer, + DeleteIssuerFunc: clusterIssuer.delete, + UnsupportedFeatures: unsupportedFeatures, }).Define() clusterIssuerNoRoot := &approle{ testWithRootCA: false, - authPath: authPath, } (&certificatesigningrequests.Suite{ - Name: "Vault AppRole ClusterIssuer Without Root CA", - CreateIssuerFunc: clusterIssuerNoRoot.createClusterIssuer, - DeleteIssuerFunc: clusterIssuerNoRoot.delete, - UnsupportedFeatures: featureset.NewFeatureSet( - featureset.KeyUsagesFeature, - featureset.Ed25519FeatureSet, - ), + Name: "Vault AppRole ClusterIssuer Without Root CA", + CreateIssuerFunc: clusterIssuerNoRoot.createClusterIssuer, + DeleteIssuerFunc: clusterIssuerNoRoot.delete, + UnsupportedFeatures: unsupportedFeatures, }).Define() }) -func (a *approle) delete(f *framework.Framework, signerName string) { - Expect(a.initializer.Clean()).NotTo(HaveOccurred(), "failed to deprovision vault initializer") - Expect(a.addon.Deprovision()).NotTo(HaveOccurred(), "failed to deprovision vault") +func (a *approle) delete(ctx context.Context, f *framework.Framework, signerName string) { + Expect(a.setup.Clean(ctx)).NotTo(HaveOccurred(), "failed to deprovision vault initializer") - err := f.KubeClientSet.CoreV1().Secrets(a.secretNamespace).Delete(context.TODO(), a.secretName, metav1.DeleteOptions{}) + err := f.KubeClientSet.CoreV1().Secrets(a.secretNamespace).Delete(ctx, a.secretName, metav1.DeleteOptions{}) Expect(err).NotTo(HaveOccurred()) ref, _ := util.SignerIssuerRefFromSignerName(signerName) if kind, _ := util.IssuerKindFromType(ref.Type); kind == cmapi.ClusterIssuerKind { - err = f.CertManagerClientSet.CertmanagerV1().ClusterIssuers().Delete(context.TODO(), ref.Name, metav1.DeleteOptions{}) + err = f.CertManagerClientSet.CertmanagerV1().ClusterIssuers().Delete(ctx, ref.Name, metav1.DeleteOptions{}) Expect(err).NotTo(HaveOccurred()) } } -func (a *approle) createIssuer(f *framework.Framework) string { +func (a *approle) createIssuer(ctx context.Context, f *framework.Framework) string { + appRoleSecretGeneratorName := "vault-approle-secret-" By("Creating a VaultAppRole Issuer") - a.secrets = a.initVault(f) + a.secrets = a.initVault(ctx) - sec, err := f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Create(context.TODO(), vault.NewVaultAppRoleSecret(secretAppRoleName, a.secretID), metav1.CreateOptions{}) + sec, err := f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Create(ctx, vault.NewVaultAppRoleSecret(appRoleSecretGeneratorName, a.secretID), metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred(), "vault to store app role secret from vault") a.secretName = sec.Name a.secretNamespace = sec.Namespace - issuer, err := f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Create(context.TODO(), &cmapi.Issuer{ + issuer, err := f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Create(ctx, &cmapi.Issuer{ ObjectMeta: metav1.ObjectMeta{ GenerateName: "vault-issuer-", }, - Spec: a.createIssuerSpec(f), + Spec: a.createIssuerSpec(), }, metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred(), "failed to create vault issuer") // wait for issuer to be ready By("Waiting for Vault Issuer to be Ready") - issuer, err = f.Helper().WaitIssuerReady(issuer, time.Minute*5) + issuer, err = f.Helper().WaitIssuerReady(ctx, issuer, time.Minute*5) Expect(err).ToNot(HaveOccurred()) return fmt.Sprintf("issuers.cert-manager.io/%s.%s", f.Namespace.Name, issuer.Name) } -func (a *approle) createClusterIssuer(f *framework.Framework) string { +func (a *approle) createClusterIssuer(ctx context.Context, f *framework.Framework) string { + appRoleSecretGeneratorName := "vault-approle-secret-" By("Creating a VaultAppRole ClusterIssuer") - a.secrets = a.initVault(f) + a.secrets = a.initVault(ctx) - sec, err := f.KubeClientSet.CoreV1().Secrets(f.Config.Addons.CertManager.ClusterResourceNamespace).Create(context.TODO(), vault.NewVaultAppRoleSecret(secretAppRoleName, a.secretID), metav1.CreateOptions{}) + sec, err := f.KubeClientSet.CoreV1().Secrets(f.Config.Addons.CertManager.ClusterResourceNamespace).Create(ctx, vault.NewVaultAppRoleSecret(appRoleSecretGeneratorName, a.secretID), metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred(), "vault to store app role secret from vault") a.secretName = sec.Name a.secretNamespace = sec.Namespace - issuer, err := f.CertManagerClientSet.CertmanagerV1().ClusterIssuers().Create(context.TODO(), &cmapi.ClusterIssuer{ + issuer, err := f.CertManagerClientSet.CertmanagerV1().ClusterIssuers().Create(ctx, &cmapi.ClusterIssuer{ ObjectMeta: metav1.ObjectMeta{ GenerateName: "vault-cluster-issuer-", }, - Spec: a.createIssuerSpec(f), + Spec: a.createIssuerSpec(), }, metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred(), "failed to create vault issuer") // wait for issuer to be ready By("Waiting for Vault Cluster Issuer to be Ready") - issuer, err = f.Helper().WaitClusterIssuerReady(issuer, time.Minute*5) + issuer, err = f.Helper().WaitClusterIssuerReady(ctx, issuer, time.Minute*5) Expect(err).ToNot(HaveOccurred()) return fmt.Sprintf("clusterissuers.cert-manager.io/%s", issuer.Name) } -func (a *approle) initVault(f *framework.Framework) *secrets { - a.addon = &vault.Vault{ - Base: addon.Base, - Namespace: f.Namespace.Name, - Name: "cm-e2e-create-vault-issuer", - } - Expect(a.addon.Setup(f.Config)).NotTo(HaveOccurred(), "failed to setup vault") - Expect(a.addon.Provision()).NotTo(HaveOccurred(), "failed to provision vault") - +func (a *approle) initVault(ctx context.Context) *secrets { By("Configuring the VaultAppRole server") - a.initializer = &vault.VaultInitializer{ - Details: *a.addon.Details(), - RootMount: rootMount, - IntermediateMount: intermediateMount, - ConfigureWithRoot: a.testWithRootCA, - Role: role, - AppRoleAuthPath: a.authPath, - } - Expect(a.initializer.Init()).NotTo(HaveOccurred(), "failed to init vault") - Expect(a.initializer.Setup()).NotTo(HaveOccurred(), "failed to setup vault") - - roleID, secretID, err := a.initializer.CreateAppRole() + a.setup = vault.NewVaultInitializerAppRole( + addon.Base.Details().KubeClient, + *addon.Vault.Details(), + a.testWithRootCA, + ) + Expect(a.setup.Init(ctx)).NotTo(HaveOccurred(), "failed to init vault") + Expect(a.setup.Setup(ctx)).NotTo(HaveOccurred(), "failed to setup vault") + + roleID, secretID, err := a.setup.CreateAppRole(ctx) Expect(err).NotTo(HaveOccurred(), "vault to create app role from vault") return &secrets{ @@ -219,18 +188,16 @@ func (a *approle) initVault(f *framework.Framework) *secrets { } } -func (a *approle) createIssuerSpec(f *framework.Framework) cmapi.IssuerSpec { - vaultPath := path.Join(intermediateMount, "sign", role) - +func (a *approle) createIssuerSpec() cmapi.IssuerSpec { return cmapi.IssuerSpec{ IssuerConfig: cmapi.IssuerConfig{ Vault: &cmapi.VaultIssuer{ - Server: a.addon.Details().Host, - Path: vaultPath, - CABundle: a.addon.Details().VaultCA, + Server: addon.Vault.Details().URL, + Path: a.setup.IntermediateSignPath(), + CABundle: addon.Vault.Details().VaultCA, Auth: cmapi.VaultAuth{ AppRole: &cmapi.VaultAppRole{ - Path: a.authPath, + Path: a.setup.AppRoleAuthPath(), RoleId: a.roleID, SecretRef: cmmeta.SecretKeySelector{ Key: "secretkey", diff --git a/test/e2e/suite/conformance/certificatesigningrequests/vault/approle_custom_mount.go b/test/e2e/suite/conformance/certificatesigningrequests/vault/approle_custom_mount.go deleted file mode 100644 index 535894b0ef7..00000000000 --- a/test/e2e/suite/conformance/certificatesigningrequests/vault/approle_custom_mount.go +++ /dev/null @@ -1,81 +0,0 @@ -/* -Copyright 2021 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. -*/ - -package vault - -import ( - "github.com/cert-manager/cert-manager/test/e2e/framework" - "github.com/cert-manager/cert-manager/test/e2e/framework/helper/featureset" - "github.com/cert-manager/cert-manager/test/e2e/suite/conformance/certificatesigningrequests" -) - -var _ = framework.ConformanceDescribe("CertificateSigningRequests", func() { - issuer := &approle{ - testWithRootCA: true, - authPath: customAuthPath, - } - (&certificatesigningrequests.Suite{ - Name: "Vault AppRole Custom Auth Path Issuer With Root CA", - CreateIssuerFunc: issuer.createIssuer, - DeleteIssuerFunc: issuer.delete, - UnsupportedFeatures: featureset.NewFeatureSet( - featureset.KeyUsagesFeature, - featureset.Ed25519FeatureSet, - ), - }).Define() - - issuerNoRoot := &approle{ - testWithRootCA: false, - authPath: customAuthPath, - } - (&certificatesigningrequests.Suite{ - Name: "Vault AppRole Custom Auth Path Issuer Without Root CA", - CreateIssuerFunc: issuerNoRoot.createIssuer, - DeleteIssuerFunc: issuerNoRoot.delete, - UnsupportedFeatures: featureset.NewFeatureSet( - featureset.KeyUsagesFeature, - featureset.Ed25519FeatureSet, - ), - }).Define() - - clusterIssuer := &approle{ - testWithRootCA: true, - authPath: customAuthPath, - } - (&certificatesigningrequests.Suite{ - Name: "Vault AppRole Custom Auth Path ClusterIssuer With Root CA", - CreateIssuerFunc: clusterIssuer.createClusterIssuer, - DeleteIssuerFunc: clusterIssuer.delete, - UnsupportedFeatures: featureset.NewFeatureSet( - featureset.KeyUsagesFeature, - featureset.Ed25519FeatureSet, - ), - }).Define() - - clusterIssuerNoRoot := &approle{ - testWithRootCA: false, - authPath: customAuthPath, - } - (&certificatesigningrequests.Suite{ - Name: "Vault AppRole Custom Auth Path ClusterIssuer Without Root CA", - CreateIssuerFunc: clusterIssuerNoRoot.createClusterIssuer, - DeleteIssuerFunc: clusterIssuerNoRoot.delete, - UnsupportedFeatures: featureset.NewFeatureSet( - featureset.KeyUsagesFeature, - featureset.Ed25519FeatureSet, - ), - }).Define() -}) diff --git a/test/e2e/suite/conformance/certificatesigningrequests/vault/kubernetes.go b/test/e2e/suite/conformance/certificatesigningrequests/vault/kubernetes.go index 8b657f409ca..3fcb6726c15 100644 --- a/test/e2e/suite/conformance/certificatesigningrequests/vault/kubernetes.go +++ b/test/e2e/suite/conformance/certificatesigningrequests/vault/kubernetes.go @@ -19,172 +19,160 @@ package vault import ( "context" "fmt" - "path" "time" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/rand" + "github.com/cert-manager/cert-manager/e2e-tests/framework" + "github.com/cert-manager/cert-manager/e2e-tests/framework/addon" + "github.com/cert-manager/cert-manager/e2e-tests/framework/addon/vault" + "github.com/cert-manager/cert-manager/e2e-tests/framework/helper/featureset" + "github.com/cert-manager/cert-manager/e2e-tests/suite/conformance/certificatesigningrequests" cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" csrutil "github.com/cert-manager/cert-manager/pkg/controller/certificatesigningrequests/util" - "github.com/cert-manager/cert-manager/pkg/util" - "github.com/cert-manager/cert-manager/test/e2e/framework" - "github.com/cert-manager/cert-manager/test/e2e/framework/addon" - "github.com/cert-manager/cert-manager/test/e2e/framework/addon/vault" - "github.com/cert-manager/cert-manager/test/e2e/framework/helper/featureset" - "github.com/cert-manager/cert-manager/test/e2e/suite/conformance/certificatesigningrequests" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" ) var _ = framework.ConformanceDescribe("CertificateSigningRequests", func() { + var unsupportedFeatures = featureset.NewFeatureSet( + featureset.KeyUsagesFeature, + featureset.Ed25519FeatureSet, + featureset.IssueCAFeature, + ) + issuer := &kubernetes{ testWithRootCA: true, } (&certificatesigningrequests.Suite{ - Name: "Vault Kubernetes Auth Issuer With Root CA", - CreateIssuerFunc: issuer.createIssuer, - DeleteIssuerFunc: issuer.delete, - UnsupportedFeatures: featureset.NewFeatureSet( - featureset.KeyUsagesFeature, - featureset.Ed25519FeatureSet, - ), + Name: "Vault Kubernetes Auth Issuer With Root CA", + CreateIssuerFunc: issuer.createIssuer, + DeleteIssuerFunc: issuer.delete, + UnsupportedFeatures: unsupportedFeatures, }).Define() clusterIssuer := &kubernetes{ testWithRootCA: true, } (&certificatesigningrequests.Suite{ - Name: "Vault Kubernetes Auth ClusterIssuer With Root CA", - CreateIssuerFunc: clusterIssuer.createClusterIssuer, - DeleteIssuerFunc: clusterIssuer.delete, - UnsupportedFeatures: featureset.NewFeatureSet( - featureset.KeyUsagesFeature, - featureset.Ed25519FeatureSet, - ), + Name: "Vault Kubernetes Auth ClusterIssuer With Root CA", + CreateIssuerFunc: clusterIssuer.createClusterIssuer, + DeleteIssuerFunc: clusterIssuer.delete, + UnsupportedFeatures: unsupportedFeatures, }).Define() }) type kubernetes struct { testWithRootCA bool - role string + // saTokenSecretName is the name of the Secret containing the service account token + saTokenSecretName string + // saBoundSA is the name of the service account which is used in the test, will be cleaned up after the test + saBoundSA string - addon *vault.Vault - initializer *vault.VaultInitializer + setup *vault.VaultInitializer } -func (k *kubernetes) createIssuer(f *framework.Framework) string { - k.initVault(f, f.Namespace.Name) +func (k *kubernetes) createIssuer(ctx context.Context, f *framework.Framework) string { + k.initVault(ctx, f, f.Namespace.Name) By("Creating a VaultKubernetes Issuer") - issuer, err := f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Create(context.TODO(), &cmapi.Issuer{ + issuer, err := f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Create(ctx, &cmapi.Issuer{ ObjectMeta: metav1.ObjectMeta{ GenerateName: "vault-issuer-", Namespace: f.Namespace.Name, }, - Spec: k.issuerSpec(f), + Spec: k.issuerSpec(), }, metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred()) // wait for issuer to be ready By("Waiting for VaultKubernetes Issuer to be Ready") - issuer, err = f.Helper().WaitIssuerReady(issuer, time.Minute*5) + issuer, err = f.Helper().WaitIssuerReady(ctx, issuer, time.Minute*5) Expect(err).ToNot(HaveOccurred()) return fmt.Sprintf("issuers.cert-manager.io/%s.%s", issuer.Namespace, issuer.Name) } -func (k *kubernetes) createClusterIssuer(f *framework.Framework) string { - k.initVault(f, f.Config.Addons.CertManager.ClusterResourceNamespace) +func (k *kubernetes) createClusterIssuer(ctx context.Context, f *framework.Framework) string { + k.initVault(ctx, f, f.Config.Addons.CertManager.ClusterResourceNamespace) By("Creating a VaultKubernetes ClusterIssuer") - issuer, err := f.CertManagerClientSet.CertmanagerV1().ClusterIssuers().Create(context.TODO(), &cmapi.ClusterIssuer{ + issuer, err := f.CertManagerClientSet.CertmanagerV1().ClusterIssuers().Create(ctx, &cmapi.ClusterIssuer{ ObjectMeta: metav1.ObjectMeta{ GenerateName: "vault-issuer-", }, - Spec: k.issuerSpec(f), + Spec: k.issuerSpec(), }, metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred()) // wait for issuer to be ready By("Waiting for VaultKubernetes Cluster Issuer to be Ready") - issuer, err = f.Helper().WaitClusterIssuerReady(issuer, time.Minute*5) + issuer, err = f.Helper().WaitClusterIssuerReady(ctx, issuer, time.Minute*5) Expect(err).ToNot(HaveOccurred()) return fmt.Sprintf("clusterissuers.cert-manager.io/%s", issuer.Name) } -func (k *kubernetes) delete(f *framework.Framework, signerName string) { +func (k *kubernetes) delete(ctx context.Context, f *framework.Framework, signerName string) { ref, _ := csrutil.SignerIssuerRefFromSignerName(signerName) if kind, _ := csrutil.IssuerKindFromType(ref.Type); kind == cmapi.ClusterIssuerKind { - err := f.CertManagerClientSet.CertmanagerV1().ClusterIssuers().Delete(context.TODO(), ref.Name, metav1.DeleteOptions{}) + err := f.CertManagerClientSet.CertmanagerV1().ClusterIssuers().Delete(ctx, ref.Name, metav1.DeleteOptions{}) Expect(err).NotTo(HaveOccurred()) - k.initializer.CleanKubernetesRole(f.KubeClientSet, f.Config.Addons.CertManager.ClusterResourceNamespace, k.role, k.role) - } - - Expect(k.initializer.Clean()).NotTo(HaveOccurred(), "failed to deprovision vault initializer") - Expect(k.addon.Deprovision()).NotTo(HaveOccurred(), "failed to deprovision vault") - -} + err = k.setup.CleanKubernetesRole(ctx, f.KubeClientSet, f.Config.Addons.CertManager.ClusterResourceNamespace, k.saBoundSA) + Expect(err).NotTo(HaveOccurred()) + } else { + err := f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Delete(ctx, ref.Name, metav1.DeleteOptions{}) + Expect(err).NotTo(HaveOccurred()) -func (k *kubernetes) initVault(f *framework.Framework, ns string) { - By("Configuring the Vault server") - k.addon = &vault.Vault{ - Base: addon.Base, - Name: "cm-e2e-create-vault-issuer", - Namespace: f.Namespace.Name, + err = k.setup.CleanKubernetesRole(ctx, f.KubeClientSet, f.Namespace.Name, k.saBoundSA) + Expect(err).NotTo(HaveOccurred()) } - k.role = "vault-issuer-" + util.RandStringRunes(5) - - Expect(k.addon.Setup(f.Config)).NotTo(HaveOccurred(), "failed to setup vault") - Expect(k.addon.Provision()).NotTo(HaveOccurred(), "failed to provision vault") + Expect(k.setup.Clean(ctx)).NotTo(HaveOccurred(), "failed to deprovision vault initializer") +} +func (k *kubernetes) initVault(ctx context.Context, f *framework.Framework, boundNS string) { By("Configuring the VaultKubernetes server") - apiHost := "https://kubernetes.default.svc.cluster.local" // since vault is running in-cluster - caCert := string(f.KubeClientConfig.CAData) - Expect(caCert).NotTo(BeEmpty()) - Expect(apiHost).NotTo(BeEmpty()) - k.initializer = &vault.VaultInitializer{ - Details: *k.addon.Details(), - RootMount: rootMount, - IntermediateMount: intermediateMount, - ConfigureWithRoot: k.testWithRootCA, - KubernetesAuthPath: "kubernetes", - Role: k.role, - APIServerURL: apiHost, - APIServerCA: caCert, - } - Expect(k.initializer.Init()).NotTo(HaveOccurred(), "failed to init vault") - Expect(k.initializer.Setup()).NotTo(HaveOccurred(), "failed to setup vault") + k.setup = vault.NewVaultInitializerKubernetes( + addon.Base.Details().KubeClient, + *addon.Vault.Details(), + k.testWithRootCA, + "https://kubernetes.default.svc.cluster.local", + ) + Expect(k.setup.Init(ctx)).NotTo(HaveOccurred(), "failed to init vault") + Expect(k.setup.Setup(ctx)).NotTo(HaveOccurred(), "failed to setup vault") By("Creating a ServiceAccount for Vault authentication") - err := k.initializer.CreateKubernetesRole(f.KubeClientSet, ns, k.role, k.role) - Expect(err).NotTo(HaveOccurred()) - _, err = f.KubeClientSet.CoreV1().Secrets(ns).Create(context.TODO(), vault.NewVaultKubernetesSecret(k.role, k.role), metav1.CreateOptions{}) + + // boundNS is name of the service account for which a Secret containing the service account token will be created + k.saBoundSA = "vault-issuer-" + rand.String(5) + err := k.setup.CreateKubernetesRole(ctx, f.KubeClientSet, boundNS, k.saBoundSA) Expect(err).NotTo(HaveOccurred()) - _, _, err = k.initializer.CreateAppRole() + + k.saTokenSecretName = "vault-sa-secret-" + rand.String(5) + _, err = f.KubeClientSet.CoreV1().Secrets(boundNS).Create(ctx, vault.NewVaultKubernetesSecret(k.saTokenSecretName, k.saBoundSA), metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred()) } -func (k *kubernetes) issuerSpec(f *framework.Framework) cmapi.IssuerSpec { - vaultPath := path.Join(intermediateMount, "sign", k.role) - +func (k *kubernetes) issuerSpec() cmapi.IssuerSpec { return cmapi.IssuerSpec{ IssuerConfig: cmapi.IssuerConfig{ Vault: &cmapi.VaultIssuer{ - Server: k.addon.Details().Host, - Path: vaultPath, - CABundle: k.addon.Details().VaultCA, + Server: addon.Vault.Details().URL, + Path: k.setup.IntermediateSignPath(), + CABundle: addon.Vault.Details().VaultCA, Auth: cmapi.VaultAuth{ Kubernetes: &cmapi.VaultKubernetesAuth{ - Path: "/v1/auth/kubernetes", - Role: k.role, + Path: k.setup.KubernetesAuthPath(), + Role: k.setup.Role(), SecretRef: cmmeta.SecretKeySelector{ LocalObjectReference: cmmeta.LocalObjectReference{ - Name: k.role, + Name: k.saTokenSecretName, }, }, }, diff --git a/test/e2e/suite/conformance/certificatesigningrequests/venafi/cloud.go b/test/e2e/suite/conformance/certificatesigningrequests/venafi/cloud.go index c6e7471f0cf..c87b7d61a6a 100644 --- a/test/e2e/suite/conformance/certificatesigningrequests/venafi/cloud.go +++ b/test/e2e/suite/conformance/certificatesigningrequests/venafi/cloud.go @@ -21,16 +21,17 @@ import ( "fmt" "time" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "github.com/cert-manager/cert-manager/e2e-tests/framework" + "github.com/cert-manager/cert-manager/e2e-tests/framework/addon/venafi" + "github.com/cert-manager/cert-manager/e2e-tests/framework/helper/featureset" + "github.com/cert-manager/cert-manager/e2e-tests/framework/util/errors" + "github.com/cert-manager/cert-manager/e2e-tests/suite/conformance/certificatesigningrequests" "github.com/cert-manager/cert-manager/pkg/controller/certificatesigningrequests/util" - "github.com/cert-manager/cert-manager/test/e2e/framework" - "github.com/cert-manager/cert-manager/test/e2e/framework/addon/venafi" - "github.com/cert-manager/cert-manager/test/e2e/framework/helper/featureset" - "github.com/cert-manager/cert-manager/test/e2e/framework/util/errors" - "github.com/cert-manager/cert-manager/test/e2e/suite/conformance/certificatesigningrequests" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" ) var _ = framework.ConformanceDescribe("CertificateSigningRequests", func() { @@ -76,38 +77,38 @@ type cloud struct { *venafi.VenafiCloud } -func (c *cloud) delete(f *framework.Framework, signerName string) { - Expect(c.Deprovision()).NotTo(HaveOccurred(), "failed to deprovision cloud venafi") +func (c *cloud) delete(ctx context.Context, f *framework.Framework, signerName string) { + Expect(c.Deprovision(ctx)).NotTo(HaveOccurred(), "failed to deprovision cloud venafi") ref, _ := util.SignerIssuerRefFromSignerName(signerName) if ref.Type == "clusterissuers" { - err := f.CertManagerClientSet.CertmanagerV1().ClusterIssuers().Delete(context.TODO(), ref.Name, metav1.DeleteOptions{}) + err := f.CertManagerClientSet.CertmanagerV1().ClusterIssuers().Delete(ctx, ref.Name, metav1.DeleteOptions{}) Expect(err).NotTo(HaveOccurred()) } } -func (c *cloud) createIssuer(f *framework.Framework) string { +func (c *cloud) createIssuer(ctx context.Context, f *framework.Framework) string { By("Creating a Venafi Cloud Issuer") c.VenafiCloud = &venafi.VenafiCloud{ Namespace: f.Namespace.Name, } - err := c.Setup(f.Config) + _, err := c.Setup(ctx, f.Config) if errors.IsSkip(err) { framework.Skipf("Skipping test as addon could not be setup: %v", err) } Expect(err).NotTo(HaveOccurred(), "failed to provision venafi cloud issuer") - Expect(c.Provision()).NotTo(HaveOccurred(), "failed to provision tpp venafi") + Expect(c.Provision(ctx)).NotTo(HaveOccurred(), "failed to provision tpp venafi") issuer := c.Details().BuildIssuer() - issuer, err = f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Create(context.TODO(), issuer, metav1.CreateOptions{}) + issuer, err = f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Create(ctx, issuer, metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred(), "failed to create issuer for venafi") // wait for issuer to be ready By("Waiting for Venafi Cloud Issuer to be Ready") - issuer, err = f.Helper().WaitIssuerReady(issuer, time.Minute*5) + issuer, err = f.Helper().WaitIssuerReady(ctx, issuer, time.Minute*5) Expect(err).ToNot(HaveOccurred()) return fmt.Sprintf("issuers.cert-manager.io/%s.%s", issuer.Namespace, issuer.Name) @@ -116,28 +117,28 @@ func (c *cloud) createIssuer(f *framework.Framework) string { // createClusterIssuer creates and returns name of a Venafi Cloud // ClusterIssuer. The name is of the form // "clusterissuers.cert-manager.io/issuer-ab3de1". -func (c *cloud) createClusterIssuer(f *framework.Framework) string { +func (c *cloud) createClusterIssuer(ctx context.Context, f *framework.Framework) string { By("Creating a Venafi Cloud ClusterIssuer") c.VenafiCloud = &venafi.VenafiCloud{ Namespace: f.Config.Addons.CertManager.ClusterResourceNamespace, } - err := c.Setup(f.Config) + _, err := c.Setup(ctx, f.Config) if errors.IsSkip(err) { framework.Skipf("Skipping test as addon could not be setup: %v", err) } Expect(err).NotTo(HaveOccurred(), "failed to setup tpp venafi") - Expect(c.Provision()).NotTo(HaveOccurred(), "failed to provision tpp venafi") + Expect(c.Provision(ctx)).NotTo(HaveOccurred(), "failed to provision tpp venafi") issuer := c.Details().BuildClusterIssuer() - issuer, err = f.CertManagerClientSet.CertmanagerV1().ClusterIssuers().Create(context.TODO(), issuer, metav1.CreateOptions{}) + issuer, err = f.CertManagerClientSet.CertmanagerV1().ClusterIssuers().Create(ctx, issuer, metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred(), "failed to create issuer for venafi") // wait for issuer to be ready By("Waiting for Venafi Cloud Cluster Issuer to be Ready") - issuer, err = f.Helper().WaitClusterIssuerReady(issuer, time.Minute*5) + issuer, err = f.Helper().WaitClusterIssuerReady(ctx, issuer, time.Minute*5) Expect(err).ToNot(HaveOccurred()) return fmt.Sprintf("clusterissuers.cert-manager.io/%s", issuer.Name) diff --git a/test/e2e/suite/conformance/certificatesigningrequests/venafi/tpp.go b/test/e2e/suite/conformance/certificatesigningrequests/venafi/tpp.go index 6c84580e432..7d25a6eeb59 100644 --- a/test/e2e/suite/conformance/certificatesigningrequests/venafi/tpp.go +++ b/test/e2e/suite/conformance/certificatesigningrequests/venafi/tpp.go @@ -20,17 +20,18 @@ import ( "context" "fmt" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/rand" + "github.com/cert-manager/cert-manager/e2e-tests/framework" + "github.com/cert-manager/cert-manager/e2e-tests/framework/addon/venafi" + "github.com/cert-manager/cert-manager/e2e-tests/framework/helper/featureset" + "github.com/cert-manager/cert-manager/e2e-tests/framework/util/errors" + "github.com/cert-manager/cert-manager/e2e-tests/suite/conformance/certificatesigningrequests" "github.com/cert-manager/cert-manager/pkg/controller/certificatesigningrequests/util" - cmutil "github.com/cert-manager/cert-manager/pkg/util" - "github.com/cert-manager/cert-manager/test/e2e/framework" - "github.com/cert-manager/cert-manager/test/e2e/framework/addon/venafi" - "github.com/cert-manager/cert-manager/test/e2e/framework/helper/featureset" - "github.com/cert-manager/cert-manager/test/e2e/framework/util/errors" - "github.com/cert-manager/cert-manager/test/e2e/suite/conformance/certificatesigningrequests" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" ) var _ = framework.ConformanceDescribe("CertificateSigningRequests", func() { @@ -61,7 +62,7 @@ var _ = framework.ConformanceDescribe("CertificateSigningRequests", func() { CreateIssuerFunc: venafiIssuer.createIssuer, DeleteIssuerFunc: venafiIssuer.delete, UnsupportedFeatures: unsupportedFeatures, - DomainSuffix: fmt.Sprintf("%s-venafi-e2e", cmutil.RandStringRunes(5)), + DomainSuffix: fmt.Sprintf("%s-venafi-e2e", rand.String(5)), }).Define() venafiClusterIssuer := new(tpp) @@ -70,7 +71,7 @@ var _ = framework.ConformanceDescribe("CertificateSigningRequests", func() { CreateIssuerFunc: venafiClusterIssuer.createClusterIssuer, DeleteIssuerFunc: venafiClusterIssuer.delete, UnsupportedFeatures: unsupportedFeatures, - DomainSuffix: fmt.Sprintf("%s-venafi-e2e", cmutil.RandStringRunes(5)), + DomainSuffix: fmt.Sprintf("%s-venafi-e2e", rand.String(5)), }).Define() }) @@ -78,55 +79,55 @@ type tpp struct { *venafi.VenafiTPP } -func (t *tpp) delete(f *framework.Framework, signerName string) { - Expect(t.Deprovision()).NotTo(HaveOccurred(), "failed to deprovision tpp venafi") +func (t *tpp) delete(ctx context.Context, f *framework.Framework, signerName string) { + Expect(t.Deprovision(ctx)).NotTo(HaveOccurred(), "failed to deprovision tpp venafi") ref, _ := util.SignerIssuerRefFromSignerName(signerName) if ref.Type == "clusterissuers" { - err := f.CertManagerClientSet.CertmanagerV1().ClusterIssuers().Delete(context.TODO(), ref.Name, metav1.DeleteOptions{}) + err := f.CertManagerClientSet.CertmanagerV1().ClusterIssuers().Delete(ctx, ref.Name, metav1.DeleteOptions{}) Expect(err).NotTo(HaveOccurred()) } } -func (t *tpp) createIssuer(f *framework.Framework) string { +func (t *tpp) createIssuer(ctx context.Context, f *framework.Framework) string { By("Creating a Venafi Issuer") t.VenafiTPP = &venafi.VenafiTPP{ Namespace: f.Namespace.Name, } - err := t.Setup(f.Config) + _, err := t.Setup(ctx, f.Config) if errors.IsSkip(err) { framework.Skipf("Skipping test as addon could not be setup: %v", err) } Expect(err).NotTo(HaveOccurred(), "failed to setup tpp venafi") - Expect(t.Provision()).NotTo(HaveOccurred(), "failed to provision tpp venafi") + Expect(t.Provision(ctx)).NotTo(HaveOccurred(), "failed to provision tpp venafi") issuer := t.Details().BuildIssuer() - issuer, err = f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Create(context.TODO(), issuer, metav1.CreateOptions{}) + issuer, err = f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Create(ctx, issuer, metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred(), "failed to create issuer for venafi") return fmt.Sprintf("issuers.cert-manager.io/%s.%s", issuer.Namespace, issuer.Name) } -func (t *tpp) createClusterIssuer(f *framework.Framework) string { +func (t *tpp) createClusterIssuer(ctx context.Context, f *framework.Framework) string { By("Creating a Venafi ClusterIssuer") t.VenafiTPP = &venafi.VenafiTPP{ Namespace: f.Config.Addons.CertManager.ClusterResourceNamespace, } - err := t.Setup(f.Config) + _, err := t.Setup(ctx, f.Config) if errors.IsSkip(err) { framework.Skipf("Skipping test as addon could not be setup: %v", err) } Expect(err).NotTo(HaveOccurred(), "failed to setup tpp venafi") - Expect(t.Provision()).NotTo(HaveOccurred(), "failed to provision tpp venafi") + Expect(t.Provision(ctx)).NotTo(HaveOccurred(), "failed to provision tpp venafi") issuer := t.Details().BuildClusterIssuer() - issuer, err = f.CertManagerClientSet.CertmanagerV1().ClusterIssuers().Create(context.TODO(), issuer, metav1.CreateOptions{}) + issuer, err = f.CertManagerClientSet.CertmanagerV1().ClusterIssuers().Create(ctx, issuer, metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred(), "failed to create issuer for venafi") return fmt.Sprintf("clusterissuers.cert-manager.io/%s", issuer.Name) diff --git a/test/e2e/suite/conformance/import.go b/test/e2e/suite/conformance/import.go index d83a435ddde..910f2377cdc 100644 --- a/test/e2e/suite/conformance/import.go +++ b/test/e2e/suite/conformance/import.go @@ -17,17 +17,17 @@ limitations under the License. package conformance import ( - _ "github.com/cert-manager/cert-manager/test/e2e/suite/conformance/certificates/acme" - _ "github.com/cert-manager/cert-manager/test/e2e/suite/conformance/certificates/ca" - _ "github.com/cert-manager/cert-manager/test/e2e/suite/conformance/certificates/external" - _ "github.com/cert-manager/cert-manager/test/e2e/suite/conformance/certificates/selfsigned" - _ "github.com/cert-manager/cert-manager/test/e2e/suite/conformance/certificates/vault" - _ "github.com/cert-manager/cert-manager/test/e2e/suite/conformance/certificates/venafi" - _ "github.com/cert-manager/cert-manager/test/e2e/suite/conformance/certificates/venaficloud" - _ "github.com/cert-manager/cert-manager/test/e2e/suite/conformance/certificatesigningrequests/acme" - _ "github.com/cert-manager/cert-manager/test/e2e/suite/conformance/certificatesigningrequests/ca" - _ "github.com/cert-manager/cert-manager/test/e2e/suite/conformance/certificatesigningrequests/selfsigned" - _ "github.com/cert-manager/cert-manager/test/e2e/suite/conformance/certificatesigningrequests/vault" - _ "github.com/cert-manager/cert-manager/test/e2e/suite/conformance/certificatesigningrequests/venafi" - _ "github.com/cert-manager/cert-manager/test/e2e/suite/conformance/rbac" + _ "github.com/cert-manager/cert-manager/e2e-tests/suite/conformance/certificates/acme" + _ "github.com/cert-manager/cert-manager/e2e-tests/suite/conformance/certificates/ca" + _ "github.com/cert-manager/cert-manager/e2e-tests/suite/conformance/certificates/external" + _ "github.com/cert-manager/cert-manager/e2e-tests/suite/conformance/certificates/selfsigned" + _ "github.com/cert-manager/cert-manager/e2e-tests/suite/conformance/certificates/vault" + _ "github.com/cert-manager/cert-manager/e2e-tests/suite/conformance/certificates/venafi" + _ "github.com/cert-manager/cert-manager/e2e-tests/suite/conformance/certificates/venaficloud" + _ "github.com/cert-manager/cert-manager/e2e-tests/suite/conformance/certificatesigningrequests/acme" + _ "github.com/cert-manager/cert-manager/e2e-tests/suite/conformance/certificatesigningrequests/ca" + _ "github.com/cert-manager/cert-manager/e2e-tests/suite/conformance/certificatesigningrequests/selfsigned" + _ "github.com/cert-manager/cert-manager/e2e-tests/suite/conformance/certificatesigningrequests/vault" + _ "github.com/cert-manager/cert-manager/e2e-tests/suite/conformance/certificatesigningrequests/venafi" + _ "github.com/cert-manager/cert-manager/e2e-tests/suite/conformance/rbac" ) diff --git a/test/e2e/suite/conformance/rbac/certificate.go b/test/e2e/suite/conformance/rbac/certificate.go index e369973bccb..2db8999ab3b 100644 --- a/test/e2e/suite/conformance/rbac/certificate.go +++ b/test/e2e/suite/conformance/rbac/certificate.go @@ -17,10 +17,12 @@ limitations under the License. package rbac import ( + "context" + + "github.com/cert-manager/cert-manager/e2e-tests/framework" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - - "github.com/cert-manager/cert-manager/test/e2e/framework" ) var _ = RBACDescribe("Certificates", func() { @@ -29,176 +31,176 @@ var _ = RBACDescribe("Certificates", func() { Context("with namespace view access", func() { clusterRole := "view" - It("shouldn't be able to create certificates", func() { + It("shouldn't be able to create certificates", func(testingCtx context.Context) { verb := "create" - hasAccess := framework.RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) + hasAccess := framework.RbacClusterRoleHasAccessToResource(testingCtx, f, clusterRole, verb, resource) Expect(hasAccess).Should(BeFalse()) }) - It("shouldn't be able to delete certificates", func() { + It("shouldn't be able to delete certificates", func(testingCtx context.Context) { verb := "delete" - hasAccess := framework.RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) + hasAccess := framework.RbacClusterRoleHasAccessToResource(testingCtx, f, clusterRole, verb, resource) Expect(hasAccess).Should(BeFalse()) }) - It("shouldn't be able to delete collections of certificates", func() { + It("shouldn't be able to delete collections of certificates", func(testingCtx context.Context) { verb := "deletecollection" - hasAccess := framework.RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) + hasAccess := framework.RbacClusterRoleHasAccessToResource(testingCtx, f, clusterRole, verb, resource) Expect(hasAccess).Should(BeFalse()) }) - It("shouldn't be able to patch certificates", func() { + It("shouldn't be able to patch certificates", func(testingCtx context.Context) { verb := "patch" - hasAccess := framework.RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) + hasAccess := framework.RbacClusterRoleHasAccessToResource(testingCtx, f, clusterRole, verb, resource) Expect(hasAccess).Should(BeFalse()) }) - It("shouldn't be able to update certificates", func() { + It("shouldn't be able to update certificates", func(testingCtx context.Context) { verb := "update" - hasAccess := framework.RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) + hasAccess := framework.RbacClusterRoleHasAccessToResource(testingCtx, f, clusterRole, verb, resource) Expect(hasAccess).Should(BeFalse()) }) - It("should be able to get certificates", func() { + It("should be able to get certificates", func(testingCtx context.Context) { verb := "get" - hasAccess := framework.RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) + hasAccess := framework.RbacClusterRoleHasAccessToResource(testingCtx, f, clusterRole, verb, resource) Expect(hasAccess).Should(BeTrue()) }) - It("should be able to list certificates", func() { + It("should be able to list certificates", func(testingCtx context.Context) { verb := "list" - hasAccess := framework.RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) + hasAccess := framework.RbacClusterRoleHasAccessToResource(testingCtx, f, clusterRole, verb, resource) Expect(hasAccess).Should(BeTrue()) }) - It("should be able to watch certificates", func() { + It("should be able to watch certificates", func(testingCtx context.Context) { verb := "watch" - hasAccess := framework.RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) + hasAccess := framework.RbacClusterRoleHasAccessToResource(testingCtx, f, clusterRole, verb, resource) Expect(hasAccess).Should(BeTrue()) }) }) Context("with namespace edit access", func() { clusterRole := "edit" - It("should be able to create certificates", func() { + It("should be able to create certificates", func(testingCtx context.Context) { verb := "create" - hasAccess := framework.RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) + hasAccess := framework.RbacClusterRoleHasAccessToResource(testingCtx, f, clusterRole, verb, resource) Expect(hasAccess).Should(BeTrue()) }) - It("should be able to delete certificates", func() { + It("should be able to delete certificates", func(testingCtx context.Context) { verb := "delete" - hasAccess := framework.RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) + hasAccess := framework.RbacClusterRoleHasAccessToResource(testingCtx, f, clusterRole, verb, resource) Expect(hasAccess).Should(BeTrue()) }) - It("should be able to delete collections of certificates", func() { + It("should be able to delete collections of certificates", func(testingCtx context.Context) { verb := "deletecollection" - hasAccess := framework.RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) + hasAccess := framework.RbacClusterRoleHasAccessToResource(testingCtx, f, clusterRole, verb, resource) Expect(hasAccess).Should(BeTrue()) }) - It("should be able to patch certificates", func() { + It("should be able to patch certificates", func(testingCtx context.Context) { verb := "patch" - hasAccess := framework.RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) + hasAccess := framework.RbacClusterRoleHasAccessToResource(testingCtx, f, clusterRole, verb, resource) Expect(hasAccess).Should(BeTrue()) }) - It("should be able to update certificates", func() { + It("should be able to update certificates", func(testingCtx context.Context) { verb := "update" - hasAccess := framework.RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) + hasAccess := framework.RbacClusterRoleHasAccessToResource(testingCtx, f, clusterRole, verb, resource) Expect(hasAccess).Should(BeTrue()) }) - It("should be able to get certificates", func() { + It("should be able to get certificates", func(testingCtx context.Context) { verb := "get" - hasAccess := framework.RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) + hasAccess := framework.RbacClusterRoleHasAccessToResource(testingCtx, f, clusterRole, verb, resource) Expect(hasAccess).Should(BeTrue()) }) - It("should be able to list certificates", func() { + It("should be able to list certificates", func(testingCtx context.Context) { verb := "list" - hasAccess := framework.RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) + hasAccess := framework.RbacClusterRoleHasAccessToResource(testingCtx, f, clusterRole, verb, resource) Expect(hasAccess).Should(BeTrue()) }) - It("should be able to watch certificates", func() { + It("should be able to watch certificates", func(testingCtx context.Context) { verb := "watch" - hasAccess := framework.RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) + hasAccess := framework.RbacClusterRoleHasAccessToResource(testingCtx, f, clusterRole, verb, resource) Expect(hasAccess).Should(BeTrue()) }) }) Context("with namespace admin access", func() { clusterRole := "admin" - It("should be able to create certificates", func() { + It("should be able to create certificates", func(testingCtx context.Context) { verb := "create" - hasAccess := framework.RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) + hasAccess := framework.RbacClusterRoleHasAccessToResource(testingCtx, f, clusterRole, verb, resource) Expect(hasAccess).Should(BeTrue()) }) - It("should be able to delete certificates", func() { + It("should be able to delete certificates", func(testingCtx context.Context) { verb := "delete" - hasAccess := framework.RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) + hasAccess := framework.RbacClusterRoleHasAccessToResource(testingCtx, f, clusterRole, verb, resource) Expect(hasAccess).Should(BeTrue()) }) - It("should be able to delete collections of certificates", func() { + It("should be able to delete collections of certificates", func(testingCtx context.Context) { verb := "deletecollection" - hasAccess := framework.RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) + hasAccess := framework.RbacClusterRoleHasAccessToResource(testingCtx, f, clusterRole, verb, resource) Expect(hasAccess).Should(BeTrue()) }) - It("should be able to patch certificates", func() { + It("should be able to patch certificates", func(testingCtx context.Context) { verb := "patch" - hasAccess := framework.RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) + hasAccess := framework.RbacClusterRoleHasAccessToResource(testingCtx, f, clusterRole, verb, resource) Expect(hasAccess).Should(BeTrue()) }) - It("should be able to update certificates", func() { + It("should be able to update certificates", func(testingCtx context.Context) { verb := "update" - hasAccess := framework.RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) + hasAccess := framework.RbacClusterRoleHasAccessToResource(testingCtx, f, clusterRole, verb, resource) Expect(hasAccess).Should(BeTrue()) }) - It("should be able to get certificates", func() { + It("should be able to get certificates", func(testingCtx context.Context) { verb := "get" - hasAccess := framework.RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) + hasAccess := framework.RbacClusterRoleHasAccessToResource(testingCtx, f, clusterRole, verb, resource) Expect(hasAccess).Should(BeTrue()) }) - It("should be able to list certificates", func() { + It("should be able to list certificates", func(testingCtx context.Context) { verb := "list" - hasAccess := framework.RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) + hasAccess := framework.RbacClusterRoleHasAccessToResource(testingCtx, f, clusterRole, verb, resource) Expect(hasAccess).Should(BeTrue()) }) - It("should be able to watch certificates", func() { + It("should be able to watch certificates", func(testingCtx context.Context) { verb := "watch" - hasAccess := framework.RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) + hasAccess := framework.RbacClusterRoleHasAccessToResource(testingCtx, f, clusterRole, verb, resource) Expect(hasAccess).Should(BeTrue()) }) }) diff --git a/test/e2e/suite/conformance/rbac/certificaterequest.go b/test/e2e/suite/conformance/rbac/certificaterequest.go index b84d69ac690..79f0ab778b8 100644 --- a/test/e2e/suite/conformance/rbac/certificaterequest.go +++ b/test/e2e/suite/conformance/rbac/certificaterequest.go @@ -17,10 +17,12 @@ limitations under the License. package rbac import ( + "context" + + "github.com/cert-manager/cert-manager/e2e-tests/framework" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - - "github.com/cert-manager/cert-manager/test/e2e/framework" ) var _ = RBACDescribe("CertificateRequests", func() { @@ -29,176 +31,176 @@ var _ = RBACDescribe("CertificateRequests", func() { Context("with namespace view access", func() { clusterRole := "view" - It("shouldn't be able to create certificaterequests", func() { + It("shouldn't be able to create certificaterequests", func(testingCtx context.Context) { verb := "create" - hasAccess := framework.RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) + hasAccess := framework.RbacClusterRoleHasAccessToResource(testingCtx, f, clusterRole, verb, resource) Expect(hasAccess).Should(BeFalse()) }) - It("shouldn't be able to delete certificaterequests", func() { + It("shouldn't be able to delete certificaterequests", func(testingCtx context.Context) { verb := "delete" - hasAccess := framework.RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) + hasAccess := framework.RbacClusterRoleHasAccessToResource(testingCtx, f, clusterRole, verb, resource) Expect(hasAccess).Should(BeFalse()) }) - It("shouldn't be able to delete collections of certificaterequests", func() { + It("shouldn't be able to delete collections of certificaterequests", func(testingCtx context.Context) { verb := "deletecollection" - hasAccess := framework.RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) + hasAccess := framework.RbacClusterRoleHasAccessToResource(testingCtx, f, clusterRole, verb, resource) Expect(hasAccess).Should(BeFalse()) }) - It("shouldn't be able to patch certificaterequests", func() { + It("shouldn't be able to patch certificaterequests", func(testingCtx context.Context) { verb := "patch" - hasAccess := framework.RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) + hasAccess := framework.RbacClusterRoleHasAccessToResource(testingCtx, f, clusterRole, verb, resource) Expect(hasAccess).Should(BeFalse()) }) - It("shouldn't be able to update certificaterequests", func() { + It("shouldn't be able to update certificaterequests", func(testingCtx context.Context) { verb := "update" - hasAccess := framework.RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) + hasAccess := framework.RbacClusterRoleHasAccessToResource(testingCtx, f, clusterRole, verb, resource) Expect(hasAccess).Should(BeFalse()) }) - It("should be able to get certificaterequests", func() { + It("should be able to get certificaterequests", func(testingCtx context.Context) { verb := "get" - hasAccess := framework.RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) + hasAccess := framework.RbacClusterRoleHasAccessToResource(testingCtx, f, clusterRole, verb, resource) Expect(hasAccess).Should(BeTrue()) }) - It("should be able to list certificaterequests", func() { + It("should be able to list certificaterequests", func(testingCtx context.Context) { verb := "list" - hasAccess := framework.RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) + hasAccess := framework.RbacClusterRoleHasAccessToResource(testingCtx, f, clusterRole, verb, resource) Expect(hasAccess).Should(BeTrue()) }) - It("should be able to watch certificaterequests", func() { + It("should be able to watch certificaterequests", func(testingCtx context.Context) { verb := "watch" - hasAccess := framework.RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) + hasAccess := framework.RbacClusterRoleHasAccessToResource(testingCtx, f, clusterRole, verb, resource) Expect(hasAccess).Should(BeTrue()) }) }) Context("with namespace edit access", func() { clusterRole := "edit" - It("should be able to create certificaterequests", func() { + It("should be able to create certificaterequests", func(testingCtx context.Context) { verb := "create" - hasAccess := framework.RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) + hasAccess := framework.RbacClusterRoleHasAccessToResource(testingCtx, f, clusterRole, verb, resource) Expect(hasAccess).Should(BeTrue()) }) - It("should be able to delete certificaterequests", func() { + It("should be able to delete certificaterequests", func(testingCtx context.Context) { verb := "delete" - hasAccess := framework.RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) + hasAccess := framework.RbacClusterRoleHasAccessToResource(testingCtx, f, clusterRole, verb, resource) Expect(hasAccess).Should(BeTrue()) }) - It("should be able to delete collections of certificaterequests", func() { + It("should be able to delete collections of certificaterequests", func(testingCtx context.Context) { verb := "deletecollection" - hasAccess := framework.RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) + hasAccess := framework.RbacClusterRoleHasAccessToResource(testingCtx, f, clusterRole, verb, resource) Expect(hasAccess).Should(BeTrue()) }) - It("should be able to patch certificaterequests", func() { + It("should be able to patch certificaterequests", func(testingCtx context.Context) { verb := "patch" - hasAccess := framework.RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) + hasAccess := framework.RbacClusterRoleHasAccessToResource(testingCtx, f, clusterRole, verb, resource) Expect(hasAccess).Should(BeTrue()) }) - It("should be able to update certificaterequests", func() { + It("should be able to update certificaterequests", func(testingCtx context.Context) { verb := "update" - hasAccess := framework.RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) + hasAccess := framework.RbacClusterRoleHasAccessToResource(testingCtx, f, clusterRole, verb, resource) Expect(hasAccess).Should(BeTrue()) }) - It("should be able to get certificaterequests", func() { + It("should be able to get certificaterequests", func(testingCtx context.Context) { verb := "get" - hasAccess := framework.RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) + hasAccess := framework.RbacClusterRoleHasAccessToResource(testingCtx, f, clusterRole, verb, resource) Expect(hasAccess).Should(BeTrue()) }) - It("should be able to list certificaterequests", func() { + It("should be able to list certificaterequests", func(testingCtx context.Context) { verb := "list" - hasAccess := framework.RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) + hasAccess := framework.RbacClusterRoleHasAccessToResource(testingCtx, f, clusterRole, verb, resource) Expect(hasAccess).Should(BeTrue()) }) - It("should be able to watch certificaterequests", func() { + It("should be able to watch certificaterequests", func(testingCtx context.Context) { verb := "watch" - hasAccess := framework.RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) + hasAccess := framework.RbacClusterRoleHasAccessToResource(testingCtx, f, clusterRole, verb, resource) Expect(hasAccess).Should(BeTrue()) }) }) Context("with namespace admin access", func() { clusterRole := "admin" - It("should be able to create certificaterequests", func() { + It("should be able to create certificaterequests", func(testingCtx context.Context) { verb := "create" - hasAccess := framework.RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) + hasAccess := framework.RbacClusterRoleHasAccessToResource(testingCtx, f, clusterRole, verb, resource) Expect(hasAccess).Should(BeTrue()) }) - It("should be able to delete certificaterequests", func() { + It("should be able to delete certificaterequests", func(testingCtx context.Context) { verb := "delete" - hasAccess := framework.RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) + hasAccess := framework.RbacClusterRoleHasAccessToResource(testingCtx, f, clusterRole, verb, resource) Expect(hasAccess).Should(BeTrue()) }) - It("should be able to delete collections of certificaterequests", func() { + It("should be able to delete collections of certificaterequests", func(testingCtx context.Context) { verb := "deletecollection" - hasAccess := framework.RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) + hasAccess := framework.RbacClusterRoleHasAccessToResource(testingCtx, f, clusterRole, verb, resource) Expect(hasAccess).Should(BeTrue()) }) - It("should be able to patch certificaterequests", func() { + It("should be able to patch certificaterequests", func(testingCtx context.Context) { verb := "patch" - hasAccess := framework.RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) + hasAccess := framework.RbacClusterRoleHasAccessToResource(testingCtx, f, clusterRole, verb, resource) Expect(hasAccess).Should(BeTrue()) }) - It("should be able to update certificaterequests", func() { + It("should be able to update certificaterequests", func(testingCtx context.Context) { verb := "update" - hasAccess := framework.RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) + hasAccess := framework.RbacClusterRoleHasAccessToResource(testingCtx, f, clusterRole, verb, resource) Expect(hasAccess).Should(BeTrue()) }) - It("should be able to get certificaterequests", func() { + It("should be able to get certificaterequests", func(testingCtx context.Context) { verb := "get" - hasAccess := framework.RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) + hasAccess := framework.RbacClusterRoleHasAccessToResource(testingCtx, f, clusterRole, verb, resource) Expect(hasAccess).Should(BeTrue()) }) - It("should be able to list certificaterequests", func() { + It("should be able to list certificaterequests", func(testingCtx context.Context) { verb := "list" - hasAccess := framework.RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) + hasAccess := framework.RbacClusterRoleHasAccessToResource(testingCtx, f, clusterRole, verb, resource) Expect(hasAccess).Should(BeTrue()) }) - It("should be able to watch certificaterequests", func() { + It("should be able to watch certificaterequests", func(testingCtx context.Context) { verb := "watch" - hasAccess := framework.RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) + hasAccess := framework.RbacClusterRoleHasAccessToResource(testingCtx, f, clusterRole, verb, resource) Expect(hasAccess).Should(BeTrue()) }) }) diff --git a/test/e2e/suite/conformance/rbac/doc.go b/test/e2e/suite/conformance/rbac/doc.go index 41d2fdc6ff1..d7e329f88fc 100644 --- a/test/e2e/suite/conformance/rbac/doc.go +++ b/test/e2e/suite/conformance/rbac/doc.go @@ -17,7 +17,7 @@ limitations under the License. package rbac import ( - "github.com/cert-manager/cert-manager/test/e2e/framework" + "github.com/cert-manager/cert-manager/e2e-tests/framework" ) // RBACDescribe wraps ConformanceDescribe with namespacing for RBAC tests diff --git a/test/e2e/suite/conformance/rbac/issuer.go b/test/e2e/suite/conformance/rbac/issuer.go index 1db7f425947..1d85e8049ef 100644 --- a/test/e2e/suite/conformance/rbac/issuer.go +++ b/test/e2e/suite/conformance/rbac/issuer.go @@ -17,10 +17,12 @@ limitations under the License. package rbac import ( + "context" + + "github.com/cert-manager/cert-manager/e2e-tests/framework" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - - "github.com/cert-manager/cert-manager/test/e2e/framework" ) var _ = RBACDescribe("Issuers", func() { @@ -29,176 +31,176 @@ var _ = RBACDescribe("Issuers", func() { Context("with namespace view access", func() { clusterRole := "view" - It("shouldn't be able to create issuers", func() { + It("shouldn't be able to create issuers", func(testingCtx context.Context) { verb := "create" - hasAccess := framework.RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) + hasAccess := framework.RbacClusterRoleHasAccessToResource(testingCtx, f, clusterRole, verb, resource) Expect(hasAccess).Should(BeFalse()) }) - It("shouldn't be able to delete issuers", func() { + It("shouldn't be able to delete issuers", func(testingCtx context.Context) { verb := "delete" - hasAccess := framework.RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) + hasAccess := framework.RbacClusterRoleHasAccessToResource(testingCtx, f, clusterRole, verb, resource) Expect(hasAccess).Should(BeFalse()) }) - It("shouldn't be able to delete collections of issuers", func() { + It("shouldn't be able to delete collections of issuers", func(testingCtx context.Context) { verb := "deletecollection" - hasAccess := framework.RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) + hasAccess := framework.RbacClusterRoleHasAccessToResource(testingCtx, f, clusterRole, verb, resource) Expect(hasAccess).Should(BeFalse()) }) - It("shouldn't be able to patch issuers", func() { + It("shouldn't be able to patch issuers", func(testingCtx context.Context) { verb := "patch" - hasAccess := framework.RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) + hasAccess := framework.RbacClusterRoleHasAccessToResource(testingCtx, f, clusterRole, verb, resource) Expect(hasAccess).Should(BeFalse()) }) - It("shouldn't be able to update issuers", func() { + It("shouldn't be able to update issuers", func(testingCtx context.Context) { verb := "update" - hasAccess := framework.RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) + hasAccess := framework.RbacClusterRoleHasAccessToResource(testingCtx, f, clusterRole, verb, resource) Expect(hasAccess).Should(BeFalse()) }) - It("should be able to get issuers", func() { + It("should be able to get issuers", func(testingCtx context.Context) { verb := "get" - hasAccess := framework.RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) + hasAccess := framework.RbacClusterRoleHasAccessToResource(testingCtx, f, clusterRole, verb, resource) Expect(hasAccess).Should(BeTrue()) }) - It("should be able to list issuers", func() { + It("should be able to list issuers", func(testingCtx context.Context) { verb := "list" - hasAccess := framework.RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) + hasAccess := framework.RbacClusterRoleHasAccessToResource(testingCtx, f, clusterRole, verb, resource) Expect(hasAccess).Should(BeTrue()) }) - It("should be able to watch issuers", func() { + It("should be able to watch issuers", func(testingCtx context.Context) { verb := "watch" - hasAccess := framework.RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) + hasAccess := framework.RbacClusterRoleHasAccessToResource(testingCtx, f, clusterRole, verb, resource) Expect(hasAccess).Should(BeTrue()) }) }) Context("with namespace edit access", func() { clusterRole := "edit" - It("should be able to create issuers", func() { + It("should be able to create issuers", func(testingCtx context.Context) { verb := "create" - hasAccess := framework.RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) + hasAccess := framework.RbacClusterRoleHasAccessToResource(testingCtx, f, clusterRole, verb, resource) Expect(hasAccess).Should(BeTrue()) }) - It("should be able to delete issuers", func() { + It("should be able to delete issuers", func(testingCtx context.Context) { verb := "delete" - hasAccess := framework.RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) + hasAccess := framework.RbacClusterRoleHasAccessToResource(testingCtx, f, clusterRole, verb, resource) Expect(hasAccess).Should(BeTrue()) }) - It("should be able to delete collections of issuers", func() { + It("should be able to delete collections of issuers", func(testingCtx context.Context) { verb := "deletecollection" - hasAccess := framework.RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) + hasAccess := framework.RbacClusterRoleHasAccessToResource(testingCtx, f, clusterRole, verb, resource) Expect(hasAccess).Should(BeTrue()) }) - It("should be able to patch issuers", func() { + It("should be able to patch issuers", func(testingCtx context.Context) { verb := "patch" - hasAccess := framework.RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) + hasAccess := framework.RbacClusterRoleHasAccessToResource(testingCtx, f, clusterRole, verb, resource) Expect(hasAccess).Should(BeTrue()) }) - It("should be able to update issuers", func() { + It("should be able to update issuers", func(testingCtx context.Context) { verb := "update" - hasAccess := framework.RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) + hasAccess := framework.RbacClusterRoleHasAccessToResource(testingCtx, f, clusterRole, verb, resource) Expect(hasAccess).Should(BeTrue()) }) - It("should be able to get issuers", func() { + It("should be able to get issuers", func(testingCtx context.Context) { verb := "get" - hasAccess := framework.RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) + hasAccess := framework.RbacClusterRoleHasAccessToResource(testingCtx, f, clusterRole, verb, resource) Expect(hasAccess).Should(BeTrue()) }) - It("should be able to list issuers", func() { + It("should be able to list issuers", func(testingCtx context.Context) { verb := "list" - hasAccess := framework.RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) + hasAccess := framework.RbacClusterRoleHasAccessToResource(testingCtx, f, clusterRole, verb, resource) Expect(hasAccess).Should(BeTrue()) }) - It("should be able to watch issuers", func() { + It("should be able to watch issuers", func(testingCtx context.Context) { verb := "watch" - hasAccess := framework.RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) + hasAccess := framework.RbacClusterRoleHasAccessToResource(testingCtx, f, clusterRole, verb, resource) Expect(hasAccess).Should(BeTrue()) }) }) Context("with namespace admin access", func() { clusterRole := "admin" - It("should be able to create issuers", func() { + It("should be able to create issuers", func(testingCtx context.Context) { verb := "create" - hasAccess := framework.RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) + hasAccess := framework.RbacClusterRoleHasAccessToResource(testingCtx, f, clusterRole, verb, resource) Expect(hasAccess).Should(BeTrue()) }) - It("should be able to delete issuers", func() { + It("should be able to delete issuers", func(testingCtx context.Context) { verb := "delete" - hasAccess := framework.RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) + hasAccess := framework.RbacClusterRoleHasAccessToResource(testingCtx, f, clusterRole, verb, resource) Expect(hasAccess).Should(BeTrue()) }) - It("should be able to delete collections of issuers", func() { + It("should be able to delete collections of issuers", func(testingCtx context.Context) { verb := "deletecollection" - hasAccess := framework.RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) + hasAccess := framework.RbacClusterRoleHasAccessToResource(testingCtx, f, clusterRole, verb, resource) Expect(hasAccess).Should(BeTrue()) }) - It("should be able to patch issuers", func() { + It("should be able to patch issuers", func(testingCtx context.Context) { verb := "patch" - hasAccess := framework.RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) + hasAccess := framework.RbacClusterRoleHasAccessToResource(testingCtx, f, clusterRole, verb, resource) Expect(hasAccess).Should(BeTrue()) }) - It("should be able to update issuers", func() { + It("should be able to update issuers", func(testingCtx context.Context) { verb := "update" - hasAccess := framework.RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) + hasAccess := framework.RbacClusterRoleHasAccessToResource(testingCtx, f, clusterRole, verb, resource) Expect(hasAccess).Should(BeTrue()) }) - It("should be able to get issuers", func() { + It("should be able to get issuers", func(testingCtx context.Context) { verb := "get" - hasAccess := framework.RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) + hasAccess := framework.RbacClusterRoleHasAccessToResource(testingCtx, f, clusterRole, verb, resource) Expect(hasAccess).Should(BeTrue()) }) - It("should be able to list issuers", func() { + It("should be able to list issuers", func(testingCtx context.Context) { verb := "list" - hasAccess := framework.RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) + hasAccess := framework.RbacClusterRoleHasAccessToResource(testingCtx, f, clusterRole, verb, resource) Expect(hasAccess).Should(BeTrue()) }) - It("should be able to watch issuers", func() { + It("should be able to watch issuers", func(testingCtx context.Context) { verb := "watch" - hasAccess := framework.RbacClusterRoleHasAccessToResource(f, clusterRole, verb, resource) + hasAccess := framework.RbacClusterRoleHasAccessToResource(testingCtx, f, clusterRole, verb, resource) Expect(hasAccess).Should(BeTrue()) }) }) diff --git a/test/e2e/suite/doc.go b/test/e2e/suite/doc.go index c734dc7a1b3..dcbd05cc4fb 100644 --- a/test/e2e/suite/doc.go +++ b/test/e2e/suite/doc.go @@ -17,10 +17,10 @@ limitations under the License. package suite import ( - _ "github.com/cert-manager/cert-manager/test/e2e/suite/certificaterequests" - _ "github.com/cert-manager/cert-manager/test/e2e/suite/certificates" - _ "github.com/cert-manager/cert-manager/test/e2e/suite/certificatesigningrequests" - _ "github.com/cert-manager/cert-manager/test/e2e/suite/conformance" - _ "github.com/cert-manager/cert-manager/test/e2e/suite/issuers" - _ "github.com/cert-manager/cert-manager/test/e2e/suite/serving" + _ "github.com/cert-manager/cert-manager/e2e-tests/suite/certificaterequests" + _ "github.com/cert-manager/cert-manager/e2e-tests/suite/certificates" + _ "github.com/cert-manager/cert-manager/e2e-tests/suite/certificatesigningrequests" + _ "github.com/cert-manager/cert-manager/e2e-tests/suite/conformance" + _ "github.com/cert-manager/cert-manager/e2e-tests/suite/issuers" + _ "github.com/cert-manager/cert-manager/e2e-tests/suite/serving" ) diff --git a/test/e2e/suite/issuers/acme/certificate/http01.go b/test/e2e/suite/issuers/acme/certificate/http01.go index 9534f0c2209..a7f5def7fa9 100644 --- a/test/e2e/suite/issuers/acme/certificate/http01.go +++ b/test/e2e/suite/issuers/acme/certificate/http01.go @@ -24,8 +24,6 @@ import ( "strings" "time" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" corev1 "k8s.io/api/core/v1" networkingv1 "k8s.io/api/networking/v1" networkingv1beta1 "k8s.io/api/networking/v1beta1" @@ -33,19 +31,21 @@ import ( "k8s.io/apimachinery/pkg/util/intstr" "k8s.io/apimachinery/pkg/util/wait" "k8s.io/client-go/util/retry" - "k8s.io/utils/pointer" + "k8s.io/utils/ptr" + "github.com/cert-manager/cert-manager/e2e-tests/framework" + "github.com/cert-manager/cert-manager/e2e-tests/framework/helper/featureset" + "github.com/cert-manager/cert-manager/e2e-tests/framework/helper/validation" + "github.com/cert-manager/cert-manager/e2e-tests/framework/log" + e2eutil "github.com/cert-manager/cert-manager/e2e-tests/util" cmacme "github.com/cert-manager/cert-manager/pkg/apis/acme/v1" v1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" - "github.com/cert-manager/cert-manager/test/e2e/framework" - "github.com/cert-manager/cert-manager/test/e2e/framework/helper/featureset" - "github.com/cert-manager/cert-manager/test/e2e/framework/helper/validation" - "github.com/cert-manager/cert-manager/test/e2e/framework/log" - . "github.com/cert-manager/cert-manager/test/e2e/framework/matcher" - "github.com/cert-manager/cert-manager/test/e2e/util" - e2eutil "github.com/cert-manager/cert-manager/test/e2e/util" "github.com/cert-manager/cert-manager/test/unit/gen" + + . "github.com/cert-manager/cert-manager/e2e-tests/framework/matcher" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" ) var _ = framework.CertManagerDescribe("ACME Certificate (HTTP01)", func() { @@ -60,12 +60,17 @@ var _ = framework.CertManagerDescribe("ACME Certificate (HTTP01)", func() { // To utilise this solver, add the 'testing.cert-manager.io/fixed-ingress: "true"' label. fixedIngressName := "testingress" - // ACME Issuer does not return a ca.crt. See: - // https://github.com/cert-manager/cert-manager/issues/1571 - unsupportedFeatures := featureset.NewFeatureSet(featureset.SaveCAToSecret) + unsupportedFeatures := featureset.NewFeatureSet( + // ACME Issuer does not return a ca.crt. See: + // https://github.com/cert-manager/cert-manager/issues/1571 + featureset.SaveCAToSecret, + // ACME does not match the duration specified + // in the CertificateRequest resource. + featureset.DurationFeature, + ) validations := validation.CertificateSetForUnsupportedFeatureSet(unsupportedFeatures) - BeforeEach(func() { + BeforeEach(func(testingCtx context.Context) { solvers := []cmacme.ACMEChallengeSolver{ { HTTP01: &cmacme.ACMEChallengeSolverHTTP01{ @@ -95,10 +100,10 @@ var _ = framework.CertManagerDescribe("ACME Certificate (HTTP01)", func() { gen.SetIssuerACMESkipTLSVerify(true), gen.SetIssuerACMESolvers(solvers)) By("Creating an Issuer") - _, err := f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Create(context.TODO(), acmeIssuer, metav1.CreateOptions{}) + _, err := f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Create(testingCtx, acmeIssuer, metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred()) By("Waiting for Issuer to become Ready") - err = util.WaitForIssuerCondition(f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name), + err = e2eutil.WaitForIssuerCondition(testingCtx, f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name), issuerName, v1.IssuerCondition{ Type: v1.IssuerConditionReady, @@ -106,7 +111,7 @@ var _ = framework.CertManagerDescribe("ACME Certificate (HTTP01)", func() { }) Expect(err).NotTo(HaveOccurred()) By("Verifying the ACME account URI is set") - err = util.WaitForIssuerStatusFunc(f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name), + err = e2eutil.WaitForIssuerStatusFunc(testingCtx, f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name), issuerName, func(i *v1.Issuer) (bool, error) { if i.GetStatus().ACMEStatus().URI == "" { @@ -116,45 +121,46 @@ var _ = framework.CertManagerDescribe("ACME Certificate (HTTP01)", func() { }) Expect(err).NotTo(HaveOccurred()) By("Verifying ACME account private key exists") - secret, err := f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Get(context.TODO(), f.Config.Addons.ACMEServer.TestingACMEPrivateKey, metav1.GetOptions{}) + secret, err := f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Get(testingCtx, f.Config.Addons.ACMEServer.TestingACMEPrivateKey, metav1.GetOptions{}) Expect(err).NotTo(HaveOccurred()) if len(secret.Data) != 1 { Fail("Expected 1 key in ACME account private key secret, but there was %d", len(secret.Data)) } }) - JustBeforeEach(func() { + JustBeforeEach(func(testingCtx context.Context) { acmeIngressDomain = e2eutil.RandomSubdomain(f.Config.Addons.IngressController.Domain) }) - AfterEach(func() { + AfterEach(func(testingCtx context.Context) { By("Cleaning up") - f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Delete(context.TODO(), issuerName, metav1.DeleteOptions{}) - f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Delete(context.TODO(), f.Config.Addons.ACMEServer.TestingACMEPrivateKey, metav1.DeleteOptions{}) + err := f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Delete(testingCtx, issuerName, metav1.DeleteOptions{}) + Expect(err).NotTo(HaveOccurred()) + err = f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Delete(testingCtx, f.Config.Addons.ACMEServer.TestingACMEPrivateKey, metav1.DeleteOptions{}) + Expect(err).NotTo(HaveOccurred()) }) - It("should allow updating an existing failing certificate that had a blocked dns name", func() { + It("should allow updating an existing failing certificate that had a blocked dns name", func(testingCtx context.Context) { certClient := f.CertManagerClientSet.CertmanagerV1().Certificates(f.Namespace.Name) By("Creating a failing Certificate") // In "make/config/pebble/chart/templates/configmap.yaml" // the "google.com" domain is configured in the pebble blocklist. cert := gen.Certificate(certificateName, + gen.SetCertificateNamespace(f.Namespace.Name), gen.SetCertificateSecretName(certificateSecretName), - gen.SetCertificateIssuer(cmmeta.ObjectReference{Name: issuerName}), + gen.SetCertificateIssuer(cmmeta.IssuerReference{Name: issuerName}), gen.SetCertificateDNSNames("google.com"), ) - cert.Namespace = f.Namespace.Name - - cert, err := certClient.Create(context.TODO(), cert, metav1.CreateOptions{}) + cert, err := certClient.Create(testingCtx, cert, metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred()) By("Making sure the Order failed with a 400 since google.com is invalid") order := &cmacme.Order{} logf, done := log.LogBackoff() defer done() - err = wait.PollImmediate(1*time.Second, 1*time.Minute, func() (done bool, err error) { - orders, err := listOwnedOrders(f.CertManagerClientSet, cert) + err = wait.PollUntilContextTimeout(testingCtx, 1*time.Second, 1*time.Minute, true, func(ctx context.Context) (done bool, err error) { + orders, err := listOwnedOrders(ctx, f.CertManagerClientSet, cert) Expect(err).NotTo(HaveOccurred()) if len(orders) == 0 || len(orders) > 1 { @@ -174,12 +180,12 @@ var _ = framework.CertManagerDescribe("ACME Certificate (HTTP01)", func() { Expect(err).NotTo(HaveOccurred()) By("Waiting for the Certificate to be not ready") - cert, err = f.Helper().WaitForCertificateNotReadyAndDoneIssuing(cert, 30*time.Second) + cert, err = f.Helper().WaitForCertificateNotReadyAndDoneIssuing(testingCtx, cert, 30*time.Second) Expect(err).NotTo(HaveOccurred()) err = retry.RetryOnConflict(retry.DefaultRetry, func() error { By("Getting the latest version of the Certificate") - cert, err = certClient.Get(context.TODO(), certificateName, metav1.GetOptions{}) + cert, err = certClient.Get(testingCtx, certificateName, metav1.GetOptions{}) if err != nil { return err } @@ -187,7 +193,7 @@ var _ = framework.CertManagerDescribe("ACME Certificate (HTTP01)", func() { By("Replacing dnsNames with a valid dns name") cert = cert.DeepCopy() cert.Spec.DNSNames = []string{e2eutil.RandomSubdomain(acmeIngressDomain)} - _, err = certClient.Update(context.TODO(), cert, metav1.UpdateOptions{}) + _, err = certClient.Update(testingCtx, cert, metav1.UpdateOptions{}) if err != nil { return err } @@ -196,7 +202,7 @@ var _ = framework.CertManagerDescribe("ACME Certificate (HTTP01)", func() { Expect(err).NotTo(HaveOccurred()) By("Waiting for the Certificate to have the Ready=True condition") - cert, err = f.Helper().WaitForCertificateReadyAndDoneIssuing(cert, time.Minute*5) + cert, err = f.Helper().WaitForCertificateReadyAndDoneIssuing(testingCtx, cert, time.Minute*5) Expect(err).NotTo(HaveOccurred()) By("Sanity checking the issued Certificate") @@ -215,18 +221,17 @@ var _ = framework.CertManagerDescribe("ACME Certificate (HTTP01)", func() { Expect(err).NotTo(HaveOccurred()) }) - It("should fail to obtain a certificate for a blocked ACME dns name", func() { + It("should fail to obtain a certificate for a blocked ACME dns name", func(testingCtx context.Context) { By("Creating a Certificate") // In "make/config/pebble/chart/templates/configmap.yaml" // the "google.com" domain is configured in the pebble blocklist. cert := gen.Certificate(certificateName, + gen.SetCertificateNamespace(f.Namespace.Name), gen.SetCertificateSecretName(certificateSecretName), - gen.SetCertificateIssuer(cmmeta.ObjectReference{Name: issuerName}), + gen.SetCertificateIssuer(cmmeta.IssuerReference{Name: issuerName}), gen.SetCertificateDNSNames("google.com"), ) - cert.Namespace = f.Namespace.Name - - cert, err := f.CertManagerClientSet.CertmanagerV1().Certificates(f.Namespace.Name).Create(context.TODO(), cert, metav1.CreateOptions{}) + cert, err := f.CertManagerClientSet.CertmanagerV1().Certificates(f.Namespace.Name).Create(testingCtx, cert, metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred()) notReadyCondition := v1.CertificateCondition{ @@ -237,20 +242,20 @@ var _ = framework.CertManagerDescribe("ACME Certificate (HTTP01)", func() { Consistently(cert, "1m", "10s").Should(HaveCondition(f, notReadyCondition)) }) - It("should obtain a signed certificate with a single CN from the ACME server when putting an annotation on an ingress resource", func() { + It("should obtain a signed certificate with a single CN from the ACME server when putting an annotation on an ingress resource", func(testingCtx context.Context) { switch { - case util.HasIngresses(f.KubeClientSet.Discovery(), networkingv1.SchemeGroupVersion.String()): + case e2eutil.HasIngresses(f.KubeClientSet.Discovery(), networkingv1.SchemeGroupVersion.String()): ingClient := f.KubeClientSet.NetworkingV1().Ingresses(f.Namespace.Name) By("Creating an Ingress with the issuer name annotation set") - _, err := ingClient.Create(context.TODO(), util.NewIngress(certificateSecretName, certificateSecretName, map[string]string{ + _, err := ingClient.Create(testingCtx, e2eutil.NewIngress(certificateSecretName, certificateSecretName, map[string]string{ "cert-manager.io/issuer": issuerName, }, acmeIngressDomain), metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred()) - case util.HasIngresses(f.KubeClientSet.Discovery(), networkingv1beta1.SchemeGroupVersion.String()): + case e2eutil.HasIngresses(f.KubeClientSet.Discovery(), networkingv1beta1.SchemeGroupVersion.String()): ingClient := f.KubeClientSet.NetworkingV1beta1().Ingresses(f.Namespace.Name) By("Creating an Ingress with the issuer name annotation set") - _, err := ingClient.Create(context.TODO(), util.NewV1Beta1Ingress(certificateSecretName, certificateSecretName, map[string]string{ + _, err := ingClient.Create(testingCtx, e2eutil.NewV1Beta1Ingress(certificateSecretName, certificateSecretName, map[string]string{ "cert-manager.io/issuer": issuerName, }, acmeIngressDomain), metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred()) @@ -259,11 +264,11 @@ var _ = framework.CertManagerDescribe("ACME Certificate (HTTP01)", func() { } By("Waiting for Certificate to exist") - cert, err := f.Helper().WaitForCertificateToExist(f.Namespace.Name, certificateSecretName, time.Second*60) + cert, err := f.Helper().WaitForCertificateToExist(testingCtx, f.Namespace.Name, certificateSecretName, time.Second*60) Expect(err).NotTo(HaveOccurred()) By("Waiting for the Certificate to be issued...") - cert, err = f.Helper().WaitForCertificateReadyAndDoneIssuing(cert, time.Minute*5) + cert, err = f.Helper().WaitForCertificateReadyAndDoneIssuing(testingCtx, cert, time.Minute*5) Expect(err).NotTo(HaveOccurred()) By("Validating the issued Certificate...") @@ -271,7 +276,7 @@ var _ = framework.CertManagerDescribe("ACME Certificate (HTTP01)", func() { Expect(err).NotTo(HaveOccurred()) }) - It("should obtain a signed certificate with a single CN from the ACME server when redirected", func() { + It("should obtain a signed certificate with a single CN from the ACME server when redirected", func(testingCtx context.Context) { certClient := f.CertManagerClientSet.CertmanagerV1().Certificates(f.Namespace.Name) // force-ssl-redirect should make every request turn into a redirect, @@ -279,13 +284,13 @@ var _ = framework.CertManagerDescribe("ACME Certificate (HTTP01)", func() { // the self-sign issuer to make it have a "proper" TLS cert // TODO: investigate if we still need to use the self-signed issuer here - issuer := gen.Issuer("selfsign", + issuer := gen.Issuer("self-sign", gen.SetIssuerNamespace(f.Namespace.Name), gen.SetIssuerSelfSigned(v1.SelfSignedIssuer{})) - _, err := f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Create(context.TODO(), issuer, metav1.CreateOptions{}) + _, err := f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Create(testingCtx, issuer, metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred()) - By("Waiting for (selfsign) Issuer to become Ready") - err = util.WaitForIssuerCondition(f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name), + By("Waiting for (self-sign) Issuer to become Ready") + err = e2eutil.WaitForIssuerCondition(testingCtx, f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name), issuerName, v1.IssuerCondition{ Type: v1.IssuerConditionReady, @@ -296,12 +301,22 @@ var _ = framework.CertManagerDescribe("ACME Certificate (HTTP01)", func() { const dummycert = "dummy-tls" const secretname = "dummy-tls-secret" - selfcert := util.NewCertManagerBasicCertificate("dummy-tls", secretname, "selfsign", v1.IssuerKind, nil, nil, acmeIngressDomain) - selfcert, err = certClient.Create(context.TODO(), selfcert, metav1.CreateOptions{}) + selfcert := gen.Certificate(dummycert, + gen.SetCertificateNamespace(f.Namespace.Name), + gen.SetCertificateSecretName(secretname), + gen.SetCertificateIssuer(cmmeta.IssuerReference{ + Name: "self-sign", + Kind: v1.IssuerKind, + }), + gen.SetCertificateCommonName(acmeIngressDomain), + gen.SetCertificateOrganization("test-org"), + gen.SetCertificateDNSNames(acmeIngressDomain), + ) + selfcert, err = certClient.Create(testingCtx, selfcert, metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred()) By("Waiting for the Certificate to be issued...") - selfcert, err = f.Helper().WaitForCertificateReadyAndDoneIssuing(selfcert, time.Minute*5) + selfcert, err = f.Helper().WaitForCertificateReadyAndDoneIssuing(testingCtx, selfcert, time.Minute*5) Expect(err).NotTo(HaveOccurred()) By("Validating the issued Certificate...") @@ -312,9 +327,9 @@ var _ = framework.CertManagerDescribe("ACME Certificate (HTTP01)", func() { // using the TLS secret that we just got from the self-sign switch { - case util.HasIngresses(f.KubeClientSet.Discovery(), networkingv1.SchemeGroupVersion.String()): + case e2eutil.HasIngresses(f.KubeClientSet.Discovery(), networkingv1.SchemeGroupVersion.String()): ingress := f.KubeClientSet.NetworkingV1().Ingresses(f.Namespace.Name) - _, err = ingress.Create(context.TODO(), &networkingv1.Ingress{ + _, err = ingress.Create(testingCtx, &networkingv1.Ingress{ ObjectMeta: metav1.ObjectMeta{ Name: fixedIngressName, Annotations: map[string]string{ @@ -322,7 +337,7 @@ var _ = framework.CertManagerDescribe("ACME Certificate (HTTP01)", func() { }, }, Spec: networkingv1.IngressSpec{ - IngressClassName: pointer.StringPtr("nginx"), + IngressClassName: ptr.To("nginx"), TLS: []networkingv1.IngressTLS{ { Hosts: []string{acmeIngressDomain}, @@ -355,9 +370,9 @@ var _ = framework.CertManagerDescribe("ACME Certificate (HTTP01)", func() { }, }, metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred()) - case util.HasIngresses(f.KubeClientSet.Discovery(), networkingv1beta1.SchemeGroupVersion.String()): + case e2eutil.HasIngresses(f.KubeClientSet.Discovery(), networkingv1beta1.SchemeGroupVersion.String()): ingress := f.KubeClientSet.NetworkingV1beta1().Ingresses(f.Namespace.Name) - _, err = ingress.Create(context.TODO(), &networkingv1beta1.Ingress{ + _, err = ingress.Create(testingCtx, &networkingv1beta1.Ingress{ ObjectMeta: metav1.ObjectMeta{ Name: fixedIngressName, Annotations: map[string]string{ @@ -365,7 +380,7 @@ var _ = framework.CertManagerDescribe("ACME Certificate (HTTP01)", func() { }, }, Spec: networkingv1beta1.IngressSpec{ - IngressClassName: pointer.StringPtr("nginx"), + IngressClassName: ptr.To("nginx"), TLS: []networkingv1beta1.IngressTLS{ { Hosts: []string{acmeIngressDomain}, @@ -382,7 +397,7 @@ var _ = framework.CertManagerDescribe("ACME Certificate (HTTP01)", func() { Path: "/", Backend: networkingv1beta1.IngressBackend{ ServiceName: "doesnotexist", - ServicePort: intstr.FromInt(443), + ServicePort: intstr.FromInt32(443), }, }, }, @@ -401,20 +416,19 @@ var _ = framework.CertManagerDescribe("ACME Certificate (HTTP01)", func() { // class By("Creating a Certificate") cert := gen.Certificate(certificateName, + gen.SetCertificateNamespace(f.Namespace.Name), + gen.AddCertificateLabels(map[string]string{ + "testing.cert-manager.io/fixed-ingress": "true", + }), gen.SetCertificateSecretName(certificateSecretName), - gen.SetCertificateIssuer(cmmeta.ObjectReference{Name: issuerName}), + gen.SetCertificateIssuer(cmmeta.IssuerReference{Name: issuerName}), gen.SetCertificateDNSNames(acmeIngressDomain), ) - cert.Namespace = f.Namespace.Name - cert.Labels = map[string]string{ - "testing.cert-manager.io/fixed-ingress": "true", - } - - cert, err = certClient.Create(context.TODO(), cert, metav1.CreateOptions{}) + cert, err = certClient.Create(testingCtx, cert, metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred()) By("Waiting for the Certificate to be issued...") - cert, err = f.Helper().WaitForCertificateReadyAndDoneIssuing(cert, time.Minute*5) + cert, err = f.Helper().WaitForCertificateReadyAndDoneIssuing(testingCtx, cert, time.Minute*5) Expect(err).NotTo(HaveOccurred()) By("Validating the issued Certificate...") @@ -422,17 +436,17 @@ var _ = framework.CertManagerDescribe("ACME Certificate (HTTP01)", func() { Expect(err).NotTo(HaveOccurred()) }) - It("should automatically recreate challenge pod and still obtain a certificate if it is manually deleted", func() { + It("should automatically recreate challenge pod and still obtain a certificate if it is manually deleted", func(testingCtx context.Context) { certClient := f.CertManagerClientSet.CertmanagerV1().Certificates(f.Namespace.Name) By("Creating a Certificate") cert := gen.Certificate(certificateName, + gen.SetCertificateNamespace(f.Namespace.Name), gen.SetCertificateSecretName(certificateSecretName), - gen.SetCertificateIssuer(cmmeta.ObjectReference{Name: issuerName}), + gen.SetCertificateIssuer(cmmeta.IssuerReference{Name: issuerName}), gen.SetCertificateDNSNames(acmeIngressDomain), ) - cert.Namespace = f.Namespace.Name - cert, err := certClient.Create(context.TODO(), cert, metav1.CreateOptions{}) + _, err := certClient.Create(testingCtx, cert, metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred()) By("killing the solver pod") @@ -440,33 +454,31 @@ var _ = framework.CertManagerDescribe("ACME Certificate (HTTP01)", func() { var pod corev1.Pod logf, done := log.LogBackoff() defer done() - err = wait.PollImmediate(1*time.Second, time.Minute*3, - func() (bool, error) { - logf("Waiting for solver pod to exist") - podlist, err := podClient.List(context.TODO(), metav1.ListOptions{}) - if err != nil { - return false, err - } + err = wait.PollUntilContextTimeout(testingCtx, 1*time.Second, time.Minute*3, true, func(ctx context.Context) (bool, error) { + logf("Waiting for solver pod to exist") + podlist, err := podClient.List(ctx, metav1.ListOptions{}) + if err != nil { + return false, err + } - for _, p := range podlist.Items { - logf("solver pod %s", p.Name) - // TODO(dmo): make this cleaner instead of just going by name - if strings.Contains(p.Name, "http-solver") { - pod = p - return true, nil - } + for _, p := range podlist.Items { + logf("solver pod %s", p.Name) + // TODO(dmo): make this cleaner instead of just going by name + if strings.Contains(p.Name, "http-solver") { + pod = p + return true, nil } - return false, nil + } + return false, nil - }, - ) + }) Expect(err).NotTo(HaveOccurred()) - err = podClient.Delete(context.TODO(), pod.Name, metav1.DeleteOptions{}) + err = podClient.Delete(testingCtx, pod.Name, metav1.DeleteOptions{}) Expect(err).NotTo(HaveOccurred()) By("Waiting for Certificate to exist") - cert, err = f.Helper().WaitForCertificateToExist(f.Namespace.Name, certificateName, time.Second*60) + cert, err = f.Helper().WaitForCertificateToExist(testingCtx, f.Namespace.Name, certificateName, time.Second*60) Expect(err).NotTo(HaveOccurred()) // The pod should get remade and the certificate should be made valid. @@ -474,7 +486,7 @@ var _ = framework.CertManagerDescribe("ACME Certificate (HTTP01)", func() { // were to ask us for the challenge after the pod was killed, but because // we kill it so early, we should always be in the self-check phase By("Waiting for the Certificate to be issued...") - cert, err = f.Helper().WaitForCertificateReadyAndDoneIssuing(cert, time.Minute*5) + cert, err = f.Helper().WaitForCertificateReadyAndDoneIssuing(testingCtx, cert, time.Minute*5) Expect(err).NotTo(HaveOccurred()) By("Validating the issued Certificate...") diff --git a/test/e2e/suite/issuers/acme/certificate/notafter.go b/test/e2e/suite/issuers/acme/certificate/notafter.go index 61e0bbae214..5a75849ac22 100644 --- a/test/e2e/suite/issuers/acme/certificate/notafter.go +++ b/test/e2e/suite/issuers/acme/certificate/notafter.go @@ -21,22 +21,21 @@ import ( "fmt" "time" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "github.com/cert-manager/cert-manager/e2e-tests/framework" + "github.com/cert-manager/cert-manager/e2e-tests/framework/helper/featureset" + "github.com/cert-manager/cert-manager/e2e-tests/framework/helper/validation" + e2eutil "github.com/cert-manager/cert-manager/e2e-tests/util" cmacme "github.com/cert-manager/cert-manager/pkg/apis/acme/v1" v1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" "github.com/cert-manager/cert-manager/pkg/util/pki" - "github.com/cert-manager/cert-manager/test/e2e/framework" - "github.com/cert-manager/cert-manager/test/e2e/framework/helper/featureset" - "github.com/cert-manager/cert-manager/test/e2e/framework/helper/validation" - "github.com/cert-manager/cert-manager/test/e2e/util" - e2eutil "github.com/cert-manager/cert-manager/test/e2e/util" "github.com/cert-manager/cert-manager/test/unit/gen" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" ) var _ = framework.CertManagerDescribe("ACME Certificate (HTTP01 + Not After)", func() { @@ -56,7 +55,7 @@ var _ = framework.CertManagerDescribe("ACME Certificate (HTTP01 + Not After)", f unsupportedFeatures := featureset.NewFeatureSet(featureset.SaveCAToSecret) validations := validation.CertificateSetForUnsupportedFeatureSet(unsupportedFeatures) - BeforeEach(func() { + BeforeEach(func(testingCtx context.Context) { solvers := []cmacme.ACMEChallengeSolver{ { HTTP01: &cmacme.ACMEChallengeSolverHTTP01{ @@ -88,10 +87,10 @@ var _ = framework.CertManagerDescribe("ACME Certificate (HTTP01 + Not After)", f gen.SetIssuerACMEDuration(true), gen.SetIssuerACMESolvers(solvers)) By("Creating an Issuer") - _, err := f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Create(context.TODO(), acmeIssuer, metav1.CreateOptions{}) + _, err := f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Create(testingCtx, acmeIssuer, metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred()) By("Waiting for Issuer to become Ready") - err = util.WaitForIssuerCondition(f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name), + err = e2eutil.WaitForIssuerCondition(testingCtx, f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name), issuerName, v1.IssuerCondition{ Type: v1.IssuerConditionReady, @@ -99,7 +98,7 @@ var _ = framework.CertManagerDescribe("ACME Certificate (HTTP01 + Not After)", f }) Expect(err).NotTo(HaveOccurred()) By("Verifying the ACME account URI is set") - err = util.WaitForIssuerStatusFunc(f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name), + err = e2eutil.WaitForIssuerStatusFunc(testingCtx, f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name), issuerName, func(i *v1.Issuer) (bool, error) { if i.GetStatus().ACMEStatus().URI == "" { @@ -109,48 +108,50 @@ var _ = framework.CertManagerDescribe("ACME Certificate (HTTP01 + Not After)", f }) Expect(err).NotTo(HaveOccurred()) By("Verifying ACME account private key exists") - secret, err := f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Get(context.TODO(), f.Config.Addons.ACMEServer.TestingACMEPrivateKey, metav1.GetOptions{}) + secret, err := f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Get(testingCtx, f.Config.Addons.ACMEServer.TestingACMEPrivateKey, metav1.GetOptions{}) Expect(err).NotTo(HaveOccurred()) if len(secret.Data) != 1 { Fail("Expected 1 key in ACME account private key secret, but there was %d", len(secret.Data)) } }) - JustBeforeEach(func() { + JustBeforeEach(func(testingCtx context.Context) { acmeIngressDomain = e2eutil.RandomSubdomain(f.Config.Addons.IngressController.Domain) }) - AfterEach(func() { + AfterEach(func(testingCtx context.Context) { By("Cleaning up") - f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Delete(context.TODO(), issuerName, metav1.DeleteOptions{}) - f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Delete(context.TODO(), f.Config.Addons.ACMEServer.TestingACMEPrivateKey, metav1.DeleteOptions{}) + err := f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Delete(testingCtx, issuerName, metav1.DeleteOptions{}) + Expect(err).NotTo(HaveOccurred()) + err = f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Delete(testingCtx, f.Config.Addons.ACMEServer.TestingACMEPrivateKey, metav1.DeleteOptions{}) + Expect(err).NotTo(HaveOccurred()) }) - It("should obtain a signed certificate with a single CN from the ACME server with 1 hour validity", func() { + It("should obtain a signed certificate with a single CN from the ACME server with 1 hour validity", func(testingCtx context.Context) { certClient := f.CertManagerClientSet.CertmanagerV1().Certificates(f.Namespace.Name) By("Creating a Certificate") cert := gen.Certificate(certificateName, - gen.SetCertificateDuration(time.Hour), - gen.SetCertificateRenewBefore(45*time.Minute), + gen.SetCertificateDuration(&metav1.Duration{Duration: time.Hour}), + gen.SetCertificateRenewBefore(&metav1.Duration{Duration: 45 * time.Minute}), gen.SetCertificateSecretName(certificateSecretName), - gen.SetCertificateIssuer(cmmeta.ObjectReference{Name: issuerName}), + gen.SetCertificateIssuer(cmmeta.IssuerReference{Name: issuerName}), gen.SetCertificateDNSNames(acmeIngressDomain), ) cert.Namespace = f.Namespace.Name - cert, err := certClient.Create(context.TODO(), cert, metav1.CreateOptions{}) + cert, err := certClient.Create(testingCtx, cert, metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred()) By("Waiting for the Certificate to be issued...") - cert, err = f.Helper().WaitForCertificateReadyAndDoneIssuing(cert, time.Minute*5) + cert, err = f.Helper().WaitForCertificateReadyAndDoneIssuing(testingCtx, cert, time.Minute*5) Expect(err).NotTo(HaveOccurred()) By("Validating the issued Certificate...") err = f.Helper().ValidateCertificate(cert, validations...) Expect(err).NotTo(HaveOccurred()) - sec, err := f.Helper().WaitForSecretCertificateData(f.Namespace.Name, certificateSecretName, time.Minute*5) + sec, err := f.Helper().WaitForSecretCertificateData(testingCtx, f.Namespace.Name, certificateSecretName, time.Minute*5) Expect(err).NotTo(HaveOccurred(), "failed to wait for secret") crtPEM := sec.Data[corev1.TLSCertKey] diff --git a/test/e2e/suite/issuers/acme/certificate/webhook.go b/test/e2e/suite/issuers/acme/certificate/webhook.go index 048abce621a..a0efb3d6190 100644 --- a/test/e2e/suite/issuers/acme/certificate/webhook.go +++ b/test/e2e/suite/issuers/acme/certificate/webhook.go @@ -20,25 +20,25 @@ import ( "context" "time" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/wait" + "github.com/cert-manager/cert-manager/e2e-tests/framework" + "github.com/cert-manager/cert-manager/e2e-tests/framework/log" + "github.com/cert-manager/cert-manager/e2e-tests/util" cmacme "github.com/cert-manager/cert-manager/pkg/apis/acme/v1" v1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" "github.com/cert-manager/cert-manager/pkg/client/clientset/versioned" - "github.com/cert-manager/cert-manager/test/e2e/framework" - "github.com/cert-manager/cert-manager/test/e2e/framework/log" - "github.com/cert-manager/cert-manager/test/e2e/util" "github.com/cert-manager/cert-manager/test/unit/gen" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" ) var _ = framework.CertManagerDescribe("ACME webhook DNS provider", func() { f := framework.NewDefaultFramework("acme-dns01-sample-webhook") - //h := f.Helper() Context("with the sample webhook solver deployed", func() { issuerName := "test-acme-issuer" @@ -46,7 +46,7 @@ var _ = framework.CertManagerDescribe("ACME webhook DNS provider", func() { certificateSecretName := "test-acme-certificate" dnsDomain := "" - BeforeEach(func() { + BeforeEach(func(testingCtx context.Context) { dnsDomain = "example.com" By("Creating an Issuer") @@ -75,10 +75,10 @@ var _ = framework.CertManagerDescribe("ACME webhook DNS provider", func() { }, })) issuer.Namespace = f.Namespace.Name - issuer, err := f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Create(context.TODO(), issuer, metav1.CreateOptions{}) + _, err := f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Create(testingCtx, issuer, metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred()) By("Waiting for Issuer to become Ready") - err = util.WaitForIssuerCondition(f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name), + err = util.WaitForIssuerCondition(testingCtx, f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name), issuerName, v1.IssuerCondition{ Type: v1.IssuerConditionReady, @@ -86,7 +86,7 @@ var _ = framework.CertManagerDescribe("ACME webhook DNS provider", func() { }) Expect(err).NotTo(HaveOccurred()) By("Verifying the ACME account URI is set") - err = util.WaitForIssuerStatusFunc(f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name), + err = util.WaitForIssuerStatusFunc(testingCtx, f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name), issuerName, func(i *v1.Issuer) (bool, error) { if i.GetStatus().ACMEStatus().URI == "" { @@ -96,89 +96,86 @@ var _ = framework.CertManagerDescribe("ACME webhook DNS provider", func() { }) Expect(err).NotTo(HaveOccurred()) By("Verifying ACME account private key exists") - secret, err := f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Get(context.TODO(), f.Config.Addons.ACMEServer.TestingACMEPrivateKey, metav1.GetOptions{}) + secret, err := f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Get(testingCtx, f.Config.Addons.ACMEServer.TestingACMEPrivateKey, metav1.GetOptions{}) Expect(err).NotTo(HaveOccurred()) if len(secret.Data) != 1 { Fail("Expected 1 key in ACME account private key secret, but there was %d", len(secret.Data)) } }) - AfterEach(func() { + AfterEach(func(testingCtx context.Context) { By("Cleaning up") - f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Delete(context.TODO(), issuerName, metav1.DeleteOptions{}) - f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Delete(context.TODO(), f.Config.Addons.ACMEServer.TestingACMEPrivateKey, metav1.DeleteOptions{}) - f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Delete(context.TODO(), certificateSecretName, metav1.DeleteOptions{}) + err := f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Delete(testingCtx, issuerName, metav1.DeleteOptions{}) + Expect(err).NotTo(HaveOccurred()) + err = f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Delete(testingCtx, f.Config.Addons.ACMEServer.TestingACMEPrivateKey, metav1.DeleteOptions{}) + Expect(err).NotTo(HaveOccurred()) }) - It("should call the dummy webhook provider and mark the challenges as presented=true", func() { + It("should call the dummy webhook provider and mark the challenges as presented=true", func(testingCtx context.Context) { By("Creating a Certificate") certClient := f.CertManagerClientSet.CertmanagerV1().Certificates(f.Namespace.Name) cert := gen.Certificate(certificateName, gen.SetCertificateSecretName(certificateSecretName), - gen.SetCertificateIssuer(cmmeta.ObjectReference{Name: issuerName}), + gen.SetCertificateIssuer(cmmeta.IssuerReference{Name: issuerName}), gen.SetCertificateDNSNames(dnsDomain), ) cert.Namespace = f.Namespace.Name - cert, err := certClient.Create(context.TODO(), cert, metav1.CreateOptions{}) + cert, err := certClient.Create(testingCtx, cert, metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred()) var order *cmacme.Order logf, done := log.LogBackoff() defer done() - pollErr := wait.PollImmediate(2*time.Second, time.Minute*1, - func() (bool, error) { - orders, err := listOwnedOrders(f.CertManagerClientSet, cert) - Expect(err).NotTo(HaveOccurred()) - - logf("Found %d orders for certificate", len(orders)) - if len(orders) == 1 { - order = orders[0] - logf("Found order named %q", order.Name) - return true, nil - } + pollErr := wait.PollUntilContextTimeout(testingCtx, 2*time.Second, time.Minute*1, true, func(ctx context.Context) (bool, error) { + orders, err := listOwnedOrders(ctx, f.CertManagerClientSet, cert) + Expect(err).NotTo(HaveOccurred()) + + logf("Found %d orders for certificate", len(orders)) + if len(orders) == 1 { + order = orders[0] + logf("Found order named %q", order.Name) + return true, nil + } - logf("Waiting as one Order should exist, but we found %d", len(orders)) - return false, nil - }, - ) + logf("Waiting as one Order should exist, but we found %d", len(orders)) + return false, nil + }) Expect(pollErr).NotTo(HaveOccurred()) logf, done = log.LogBackoff() defer done() - pollErr = wait.PollImmediate(2*time.Second, time.Minute*3, - func() (bool, error) { - l, err := listOwnedChallenges(f.CertManagerClientSet, order) - Expect(err).NotTo(HaveOccurred()) - - logf("Found %d challenges", len(l)) - if len(l) == 0 { - logf("Waiting for at least one challenge to exist") - return false, nil - } + pollErr = wait.PollUntilContextTimeout(testingCtx, 2*time.Second, time.Minute*3, true, func(ctx context.Context) (bool, error) { + l, err := listOwnedChallenges(ctx, f.CertManagerClientSet, order) + Expect(err).NotTo(HaveOccurred()) + + logf("Found %d challenges", len(l)) + if len(l) == 0 { + logf("Waiting for at least one challenge to exist") + return false, nil + } - allPresented := true - for _, ch := range l { - logf("Found challenge named %q", ch.Name) + allPresented := true + for _, ch := range l { + logf("Found challenge named %q", ch.Name) - if ch.Status.Presented == false { - logf("Challenge %q has not been 'Presented'", ch.Name) - allPresented = false - } + if !ch.Status.Presented { + logf("Challenge %q has not been 'Presented'", ch.Name) + allPresented = false } + } - return allPresented, nil - }, - ) + return allPresented, nil + }) Expect(pollErr).NotTo(HaveOccurred()) }) }) }) -func listOwnedChallenges(cl versioned.Interface, owner *cmacme.Order) ([]*cmacme.Challenge, error) { - l, err := cl.AcmeV1().Challenges(owner.Namespace).List(context.TODO(), metav1.ListOptions{}) +func listOwnedChallenges(ctx context.Context, cl versioned.Interface, owner *cmacme.Order) ([]*cmacme.Challenge, error) { + l, err := cl.AcmeV1().Challenges(owner.Namespace).List(ctx, metav1.ListOptions{}) if err != nil { return nil, err } @@ -194,8 +191,8 @@ func listOwnedChallenges(cl versioned.Interface, owner *cmacme.Order) ([]*cmacme return owned, nil } -func listOwnedOrders(cl versioned.Interface, owner *v1.Certificate) ([]*cmacme.Order, error) { - l, err := cl.AcmeV1().Orders(owner.Namespace).List(context.TODO(), metav1.ListOptions{}) +func listOwnedOrders(ctx context.Context, cl versioned.Interface, owner *v1.Certificate) ([]*cmacme.Order, error) { + l, err := cl.AcmeV1().Orders(owner.Namespace).List(ctx, metav1.ListOptions{}) if err != nil { return nil, err } diff --git a/test/e2e/suite/issuers/acme/certificaterequest/dns01.go b/test/e2e/suite/issuers/acme/certificaterequest/dns01.go index 935b678d88a..a8aaaae3fb7 100644 --- a/test/e2e/suite/issuers/acme/certificaterequest/dns01.go +++ b/test/e2e/suite/issuers/acme/certificaterequest/dns01.go @@ -21,24 +21,19 @@ import ( "crypto/x509" "time" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "github.com/cert-manager/cert-manager/e2e-tests/framework" + "github.com/cert-manager/cert-manager/e2e-tests/suite/issuers/acme/dnsproviders" + "github.com/cert-manager/cert-manager/e2e-tests/util" cmacme "github.com/cert-manager/cert-manager/pkg/apis/acme/v1" v1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" - "github.com/cert-manager/cert-manager/test/e2e/framework" - "github.com/cert-manager/cert-manager/test/e2e/framework/addon" - "github.com/cert-manager/cert-manager/test/e2e/suite/issuers/acme/dnsproviders" - "github.com/cert-manager/cert-manager/test/e2e/util" "github.com/cert-manager/cert-manager/test/unit/gen" -) -type dns01Provider interface { - Details() *dnsproviders.Details - addon.Addon -} + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) const testingACMEEmail = "e2e@cert-manager.io" const testingACMEPrivateKey = "test-acme-private-key" @@ -61,7 +56,7 @@ func testRFC2136DNSProvider() bool { p := &dnsproviders.RFC2136{} f.RequireAddon(p) - BeforeEach(func() { + BeforeEach(func(testingCtx context.Context) { By("Creating an Issuer") dnsDomain = util.RandomSubdomain(p.Details().BaseDomain) issuer := gen.Issuer(issuerName, @@ -81,10 +76,10 @@ func testRFC2136DNSProvider() bool { }, })) issuer.Namespace = f.Namespace.Name - issuer, err := f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Create(context.TODO(), issuer, metav1.CreateOptions{}) + _, err := f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Create(testingCtx, issuer, metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred()) By("Waiting for Issuer to become Ready") - err = util.WaitForIssuerCondition(f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name), + err = util.WaitForIssuerCondition(testingCtx, f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name), issuerName, v1.IssuerCondition{ Type: v1.IssuerConditionReady, @@ -92,7 +87,7 @@ func testRFC2136DNSProvider() bool { }) Expect(err).NotTo(HaveOccurred()) By("Verifying the ACME account URI is set") - err = util.WaitForIssuerStatusFunc(f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name), + err = util.WaitForIssuerStatusFunc(testingCtx, f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name), issuerName, func(i *v1.Issuer) (bool, error) { if i.GetStatus().ACMEStatus().URI == "" { @@ -102,58 +97,72 @@ func testRFC2136DNSProvider() bool { }) Expect(err).NotTo(HaveOccurred()) By("Verifying ACME account private key exists") - secret, err := f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Get(context.TODO(), testingACMEPrivateKey, metav1.GetOptions{}) + secret, err := f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Get(testingCtx, testingACMEPrivateKey, metav1.GetOptions{}) Expect(err).NotTo(HaveOccurred()) if len(secret.Data) != 1 { Fail("Expected 1 key in ACME account private key secret, but there was %d", len(secret.Data)) } }) - AfterEach(func() { + AfterEach(func(testingCtx context.Context) { By("Cleaning up") - f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Delete(context.TODO(), issuerName, metav1.DeleteOptions{}) - f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Delete(context.TODO(), testingACMEPrivateKey, metav1.DeleteOptions{}) + err := f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Delete(testingCtx, issuerName, metav1.DeleteOptions{}) + Expect(err).NotTo(HaveOccurred()) + err = f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Delete(testingCtx, testingACMEPrivateKey, metav1.DeleteOptions{}) + Expect(err).NotTo(HaveOccurred()) }) - It("should obtain a signed certificate for a regular domain", func() { + It("should obtain a signed certificate for a regular domain", func(testingCtx context.Context) { By("Creating a CertificateRequest") crClient := f.CertManagerClientSet.CertmanagerV1().CertificateRequests(f.Namespace.Name) - cr, key, err := util.NewCertManagerBasicCertificateRequest(certificateRequestName, issuerName, v1.IssuerKind, nil, - []string{dnsDomain}, nil, nil, x509.RSA) + csr, key, err := gen.CSR(x509.RSA, gen.SetCSRCommonName(dnsDomain), gen.SetCSRDNSNames(dnsDomain)) Expect(err).NotTo(HaveOccurred()) + cr := gen.CertificateRequest(certificateRequestName, + gen.SetCertificateRequestNamespace(f.Namespace.Name), + gen.SetCertificateRequestIssuer(cmmeta.IssuerReference{Kind: v1.IssuerKind, Name: issuerName}), + gen.SetCertificateRequestCSR(csr), + ) - cr, err = crClient.Create(context.TODO(), cr, metav1.CreateOptions{}) + _, err = crClient.Create(testingCtx, cr, metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred()) - err = h.WaitCertificateRequestIssuedValid(f.Namespace.Name, certificateRequestName, time.Minute*5, key) + err = h.WaitCertificateRequestIssuedValid(testingCtx, f.Namespace.Name, certificateRequestName, time.Minute*5, key) Expect(err).NotTo(HaveOccurred()) }) - It("should obtain a signed certificate for a wildcard domain", func() { + It("should obtain a signed certificate for a wildcard domain", func(testingCtx context.Context) { By("Creating a CertificateRequest") - cr, key, err := util.NewCertManagerBasicCertificateRequest(certificateRequestName, issuerName, v1.IssuerKind, nil, - []string{"*." + dnsDomain}, nil, nil, x509.RSA) + csr, key, err := gen.CSR(x509.RSA, gen.SetCSRCommonName("*."+dnsDomain), gen.SetCSRDNSNames("*."+dnsDomain)) Expect(err).NotTo(HaveOccurred()) + cr := gen.CertificateRequest(certificateRequestName, + gen.SetCertificateRequestNamespace(f.Namespace.Name), + gen.SetCertificateRequestIssuer(cmmeta.IssuerReference{Kind: v1.IssuerKind, Name: issuerName}), + gen.SetCertificateRequestCSR(csr), + ) - cr, err = f.CertManagerClientSet.CertmanagerV1().CertificateRequests(f.Namespace.Name).Create(context.TODO(), cr, metav1.CreateOptions{}) + _, err = f.CertManagerClientSet.CertmanagerV1().CertificateRequests(f.Namespace.Name).Create(testingCtx, cr, metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred()) - err = h.WaitCertificateRequestIssuedValid(f.Namespace.Name, certificateRequestName, time.Minute*5, key) + err = h.WaitCertificateRequestIssuedValid(testingCtx, f.Namespace.Name, certificateRequestName, time.Minute*5, key) Expect(err).NotTo(HaveOccurred()) }) - It("should obtain a signed certificate for a wildcard and apex domain", func() { + It("should obtain a signed certificate for a wildcard and apex domain", func(testingCtx context.Context) { By("Creating a CertificateRequest") - cr, key, err := util.NewCertManagerBasicCertificateRequest(certificateRequestName, issuerName, v1.IssuerKind, nil, - []string{"*." + dnsDomain, dnsDomain}, nil, nil, x509.RSA) + csr, key, err := gen.CSR(x509.RSA, gen.SetCSRCommonName("*."+dnsDomain), gen.SetCSRDNSNames("*."+dnsDomain, dnsDomain)) Expect(err).NotTo(HaveOccurred()) + cr := gen.CertificateRequest(certificateRequestName, + gen.SetCertificateRequestNamespace(f.Namespace.Name), + gen.SetCertificateRequestIssuer(cmmeta.IssuerReference{Kind: v1.IssuerKind, Name: issuerName}), + gen.SetCertificateRequestCSR(csr), + ) - cr, err = f.CertManagerClientSet.CertmanagerV1().CertificateRequests(f.Namespace.Name).Create(context.TODO(), cr, metav1.CreateOptions{}) + _, err = f.CertManagerClientSet.CertmanagerV1().CertificateRequests(f.Namespace.Name).Create(testingCtx, cr, metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred()) // use a longer timeout for this, as it requires performing 2 dns validations in serial - err = h.WaitCertificateRequestIssuedValid(f.Namespace.Name, certificateRequestName, time.Minute*10, key) + err = h.WaitCertificateRequestIssuedValid(testingCtx, f.Namespace.Name, certificateRequestName, time.Minute*10, key) Expect(err).NotTo(HaveOccurred()) }) }) diff --git a/test/e2e/suite/issuers/acme/certificaterequest/http01.go b/test/e2e/suite/issuers/acme/certificaterequest/http01.go index 116ebbfcfd8..5da863f7b77 100644 --- a/test/e2e/suite/issuers/acme/certificaterequest/http01.go +++ b/test/e2e/suite/issuers/acme/certificaterequest/http01.go @@ -22,21 +22,21 @@ import ( "strings" "time" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/wait" + "github.com/cert-manager/cert-manager/e2e-tests/framework" + "github.com/cert-manager/cert-manager/e2e-tests/framework/log" + e2eutil "github.com/cert-manager/cert-manager/e2e-tests/util" cmacme "github.com/cert-manager/cert-manager/pkg/apis/acme/v1" v1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" - "github.com/cert-manager/cert-manager/test/e2e/framework" - "github.com/cert-manager/cert-manager/test/e2e/framework/log" - . "github.com/cert-manager/cert-manager/test/e2e/framework/matcher" - "github.com/cert-manager/cert-manager/test/e2e/util" - e2eutil "github.com/cert-manager/cert-manager/test/e2e/util" "github.com/cert-manager/cert-manager/test/unit/gen" + + . "github.com/cert-manager/cert-manager/e2e-tests/framework/matcher" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" ) var _ = framework.CertManagerDescribe("ACME CertificateRequest (HTTP01)", func() { @@ -51,7 +51,7 @@ var _ = framework.CertManagerDescribe("ACME CertificateRequest (HTTP01)", func() // To utilise this solver, add the 'testing.cert-manager.io/fixed-ingress: "true"' label. fixedIngressName := "testingress" - BeforeEach(func() { + BeforeEach(func(testingCtx context.Context) { solvers := []cmacme.ACMEChallengeSolver{ { HTTP01: &cmacme.ACMEChallengeSolverHTTP01{ @@ -81,10 +81,10 @@ var _ = framework.CertManagerDescribe("ACME CertificateRequest (HTTP01)", func() gen.SetIssuerACMESkipTLSVerify(true), gen.SetIssuerACMESolvers(solvers)) By("Creating an Issuer") - _, err := f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Create(context.TODO(), acmeIssuer, metav1.CreateOptions{}) + _, err := f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Create(testingCtx, acmeIssuer, metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred()) By("Waiting for Issuer to become Ready") - err = util.WaitForIssuerCondition(f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name), + err = e2eutil.WaitForIssuerCondition(testingCtx, f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name), issuerName, v1.IssuerCondition{ Type: v1.IssuerConditionReady, @@ -92,7 +92,7 @@ var _ = framework.CertManagerDescribe("ACME CertificateRequest (HTTP01)", func() }) Expect(err).NotTo(HaveOccurred()) By("Verifying the ACME account URI is set") - err = util.WaitForIssuerStatusFunc(f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name), + err = e2eutil.WaitForIssuerStatusFunc(testingCtx, f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name), issuerName, func(i *v1.Issuer) (bool, error) { if i.GetStatus().ACMEStatus().URI == "" { @@ -102,98 +102,116 @@ var _ = framework.CertManagerDescribe("ACME CertificateRequest (HTTP01)", func() }) Expect(err).NotTo(HaveOccurred()) By("Verifying ACME account private key exists") - secret, err := f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Get(context.TODO(), testingACMEPrivateKey, metav1.GetOptions{}) + secret, err := f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Get(testingCtx, testingACMEPrivateKey, metav1.GetOptions{}) Expect(err).NotTo(HaveOccurred()) if len(secret.Data) != 1 { Fail("Expected 1 key in ACME account private key secret, but there was %d", len(secret.Data)) } }) - JustBeforeEach(func() { + JustBeforeEach(func(testingCtx context.Context) { acmeIngressDomain = e2eutil.RandomSubdomain(f.Config.Addons.IngressController.Domain) }) - AfterEach(func() { + AfterEach(func(testingCtx context.Context) { By("Cleaning up") - f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Delete(context.TODO(), issuerName, metav1.DeleteOptions{}) - f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Delete(context.TODO(), testingACMEPrivateKey, metav1.DeleteOptions{}) + err := f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Delete(testingCtx, issuerName, metav1.DeleteOptions{}) + Expect(err).NotTo(HaveOccurred()) + err = f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Delete(testingCtx, testingACMEPrivateKey, metav1.DeleteOptions{}) + Expect(err).NotTo(HaveOccurred()) }) - It("should obtain a signed certificate with a single CN from the ACME server", func() { + It("should obtain a signed certificate with a single CN from the ACME server", func(testingCtx context.Context) { crClient := f.CertManagerClientSet.CertmanagerV1().CertificateRequests(f.Namespace.Name) By("Creating a CertificateRequest") - cr, key, err := util.NewCertManagerBasicCertificateRequest(certificateRequestName, issuerName, v1.IssuerKind, nil, - []string{acmeIngressDomain}, nil, nil, x509.RSA) + csr, key, err := gen.CSR(x509.RSA, gen.SetCSRCommonName(acmeIngressDomain), gen.SetCSRDNSNames(acmeIngressDomain)) Expect(err).NotTo(HaveOccurred()) + cr := gen.CertificateRequest(certificateRequestName, + gen.SetCertificateRequestNamespace(f.Namespace.Name), + gen.SetCertificateRequestIssuer(cmmeta.IssuerReference{Kind: v1.IssuerKind, Name: issuerName}), + gen.SetCertificateRequestCSR(csr), + ) - cr, err = crClient.Create(context.TODO(), cr, metav1.CreateOptions{}) + _, err = crClient.Create(testingCtx, cr, metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred()) By("Verifying the Certificate is valid") - err = h.WaitCertificateRequestIssuedValid(f.Namespace.Name, certificateRequestName, time.Minute*5, key) + err = h.WaitCertificateRequestIssuedValid(testingCtx, f.Namespace.Name, certificateRequestName, time.Minute*5, key) Expect(err).NotTo(HaveOccurred()) }) - It("should obtain a signed ecdsa certificate with a single CN from the ACME server", func() { + It("should obtain a signed ecdsa certificate with a single CN from the ACME server", func(testingCtx context.Context) { crClient := f.CertManagerClientSet.CertmanagerV1().CertificateRequests(f.Namespace.Name) By("Creating a CertificateRequest") - cr, key, err := util.NewCertManagerBasicCertificateRequest(certificateRequestName, issuerName, v1.IssuerKind, nil, - []string{acmeIngressDomain}, nil, nil, x509.ECDSA) + csr, key, err := gen.CSR(x509.ECDSA, gen.SetCSRCommonName(acmeIngressDomain), gen.SetCSRDNSNames(acmeIngressDomain)) Expect(err).NotTo(HaveOccurred()) + cr := gen.CertificateRequest(certificateRequestName, + gen.SetCertificateRequestNamespace(f.Namespace.Name), + gen.SetCertificateRequestIssuer(cmmeta.IssuerReference{Kind: v1.IssuerKind, Name: issuerName}), + gen.SetCertificateRequestCSR(csr), + ) - _, err = crClient.Create(context.TODO(), cr, metav1.CreateOptions{}) + _, err = crClient.Create(testingCtx, cr, metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred()) By("Verifying the Certificate is valid and of type ECDSA") - err = h.WaitCertificateRequestIssuedValid(f.Namespace.Name, certificateRequestName, time.Minute*5, key) + err = h.WaitCertificateRequestIssuedValid(testingCtx, f.Namespace.Name, certificateRequestName, time.Minute*5, key) Expect(err).NotTo(HaveOccurred()) }) - It("should obtain a signed certificate for a long domain using http01 validation", func() { + It("should obtain a signed certificate for a long domain using http01 validation", func(testingCtx context.Context) { crClient := f.CertManagerClientSet.CertmanagerV1().CertificateRequests(f.Namespace.Name) // the maximum length of a single segment of the domain being requested const maxLengthOfDomainSegment = 63 By("Creating a CertificateRequest") - cr, key, err := util.NewCertManagerBasicCertificateRequest(certificateRequestName, issuerName, v1.IssuerKind, nil, - []string{ - acmeIngressDomain, - e2eutil.RandomSubdomainLength(acmeIngressDomain, maxLengthOfDomainSegment), - }, - nil, nil, x509.RSA) + csr, key, err := gen.CSR(x509.ECDSA, gen.SetCSRCommonName(acmeIngressDomain), gen.SetCSRDNSNames(acmeIngressDomain, e2eutil.RandomSubdomainLength(acmeIngressDomain, maxLengthOfDomainSegment))) Expect(err).NotTo(HaveOccurred()) + cr := gen.CertificateRequest(certificateRequestName, + gen.SetCertificateRequestNamespace(f.Namespace.Name), + gen.SetCertificateRequestIssuer(cmmeta.IssuerReference{Kind: v1.IssuerKind, Name: issuerName}), + gen.SetCertificateRequestCSR(csr), + ) - _, err = crClient.Create(context.TODO(), cr, metav1.CreateOptions{}) + _, err = crClient.Create(testingCtx, cr, metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred()) - err = h.WaitCertificateRequestIssuedValid(f.Namespace.Name, certificateRequestName, time.Minute*5, key) + err = h.WaitCertificateRequestIssuedValid(testingCtx, f.Namespace.Name, certificateRequestName, time.Minute*5, key) Expect(err).NotTo(HaveOccurred()) }) - It("should obtain a signed certificate with a CN and single subdomain as dns name from the ACME server", func() { + It("should obtain a signed certificate with a CN and single subdomain as dns name from the ACME server", func(testingCtx context.Context) { crClient := f.CertManagerClientSet.CertmanagerV1().CertificateRequests(f.Namespace.Name) By("Creating a CertificateRequest") - cr, key, err := util.NewCertManagerBasicCertificateRequest(certificateRequestName, issuerName, v1.IssuerKind, nil, - []string{e2eutil.RandomSubdomain(acmeIngressDomain)}, - nil, nil, x509.RSA) + dnsName := e2eutil.RandomSubdomain(acmeIngressDomain) + csr, key, err := gen.CSR(x509.RSA, gen.SetCSRCommonName(dnsName), gen.SetCSRDNSNames(dnsName)) Expect(err).NotTo(HaveOccurred()) + cr := gen.CertificateRequest(certificateRequestName, + gen.SetCertificateRequestNamespace(f.Namespace.Name), + gen.SetCertificateRequestIssuer(cmmeta.IssuerReference{Kind: v1.IssuerKind, Name: issuerName}), + gen.SetCertificateRequestCSR(csr), + ) - _, err = crClient.Create(context.TODO(), cr, metav1.CreateOptions{}) + _, err = crClient.Create(testingCtx, cr, metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred()) By("Verifying the CertificateRequest is valid") - err = h.WaitCertificateRequestIssuedValid(f.Namespace.Name, certificateRequestName, time.Minute*5, key) + err = h.WaitCertificateRequestIssuedValid(testingCtx, f.Namespace.Name, certificateRequestName, time.Minute*5, key) Expect(err).NotTo(HaveOccurred()) }) - It("should fail to obtain a certificate for an invalid ACME dns name", func() { + It("should fail to obtain a certificate for an invalid ACME dns name", func(testingCtx context.Context) { // create test fixture By("Creating a CertificateRequest") - cr, _, err := util.NewCertManagerBasicCertificateRequest(certificateRequestName, issuerName, v1.IssuerKind, nil, - []string{"google.com"}, nil, nil, x509.RSA) + csr, _, err := gen.CSR(x509.RSA, gen.SetCSRCommonName("google.com"), gen.SetCSRDNSNames("google.com")) Expect(err).NotTo(HaveOccurred()) + cr := gen.CertificateRequest(certificateRequestName, + gen.SetCertificateRequestNamespace(f.Namespace.Name), + gen.SetCertificateRequestIssuer(cmmeta.IssuerReference{Kind: v1.IssuerKind, Name: issuerName}), + gen.SetCertificateRequestCSR(csr), + ) - cr, err = f.CertManagerClientSet.CertmanagerV1().CertificateRequests(f.Namespace.Name).Create(context.TODO(), cr, metav1.CreateOptions{}) + cr, err = f.CertManagerClientSet.CertmanagerV1().CertificateRequests(f.Namespace.Name).Create(testingCtx, cr, metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred()) notReadyCondition := v1.CertificateRequestCondition{ @@ -204,15 +222,19 @@ var _ = framework.CertManagerDescribe("ACME CertificateRequest (HTTP01)", func() Consistently(cr, "1m", "10s").Should(HaveCondition(f, notReadyCondition)) }) - It("should automatically recreate challenge pod and still obtain a certificate if it is manually deleted", func() { + It("should automatically recreate challenge pod and still obtain a certificate if it is manually deleted", func(testingCtx context.Context) { crClient := f.CertManagerClientSet.CertmanagerV1().CertificateRequests(f.Namespace.Name) By("Creating a CertificateRequest") - cr, key, err := util.NewCertManagerBasicCertificateRequest(certificateRequestName, issuerName, v1.IssuerKind, nil, - []string{acmeIngressDomain}, nil, nil, x509.RSA) + csr, key, err := gen.CSR(x509.RSA, gen.SetCSRCommonName(acmeIngressDomain), gen.SetCSRDNSNames(acmeIngressDomain)) Expect(err).NotTo(HaveOccurred()) + cr := gen.CertificateRequest(certificateRequestName, + gen.SetCertificateRequestNamespace(f.Namespace.Name), + gen.SetCertificateRequestIssuer(cmmeta.IssuerReference{Kind: v1.IssuerKind, Name: issuerName}), + gen.SetCertificateRequestCSR(csr), + ) - _, err = crClient.Create(context.TODO(), cr, metav1.CreateOptions{}) + _, err = crClient.Create(testingCtx, cr, metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred()) By("killing the solver pod") @@ -220,29 +242,26 @@ var _ = framework.CertManagerDescribe("ACME CertificateRequest (HTTP01)", func() var pod corev1.Pod logf, done := log.LogBackoff() defer done() - err = wait.PollImmediate(1*time.Second, time.Minute*3, - func() (bool, error) { - logf("Waiting for solver pod to exist") - podlist, err := podClient.List(context.TODO(), metav1.ListOptions{}) - if err != nil { - return false, err + err = wait.PollUntilContextTimeout(testingCtx, 1*time.Second, time.Minute*3, true, func(ctx context.Context) (bool, error) { + logf("Waiting for solver pod to exist") + podlist, err := podClient.List(ctx, metav1.ListOptions{}) + if err != nil { + return false, err + } + + for _, p := range podlist.Items { + logf("solver pod %s", p.Name) + // TODO(dmo): make this cleaner instead of just going by name + if strings.Contains(p.Name, "http-solver") { + pod = p + return true, nil } - - for _, p := range podlist.Items { - logf("solver pod %s", p.Name) - // TODO(dmo): make this cleaner instead of just going by name - if strings.Contains(p.Name, "http-solver") { - pod = p - return true, nil - } - } - return false, nil - - }, - ) + } + return false, nil + }) Expect(err).NotTo(HaveOccurred()) - err = podClient.Delete(context.TODO(), pod.Name, metav1.DeleteOptions{}) + err = podClient.Delete(testingCtx, pod.Name, metav1.DeleteOptions{}) Expect(err).NotTo(HaveOccurred()) // The pod should get remade and the certificate should be made valid. @@ -250,7 +269,7 @@ var _ = framework.CertManagerDescribe("ACME CertificateRequest (HTTP01)", func() // were to ask us for the challenge after the pod was killed, but because // we kill it so early, we should always be in the self-check phase By("Verifying the CertificateRequest is valid") - err = h.WaitCertificateRequestIssuedValid(f.Namespace.Name, certificateRequestName, time.Minute*5, key) + err = h.WaitCertificateRequestIssuedValid(testingCtx, f.Namespace.Name, certificateRequestName, time.Minute*5, key) Expect(err).NotTo(HaveOccurred()) }) }) diff --git a/test/e2e/suite/issuers/acme/certificaterequest/profiles.go b/test/e2e/suite/issuers/acme/certificaterequest/profiles.go new file mode 100644 index 00000000000..acdaf7f7d5c --- /dev/null +++ b/test/e2e/suite/issuers/acme/certificaterequest/profiles.go @@ -0,0 +1,178 @@ +/* +Copyright 2020 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. +*/ + +// E2E tests for the ACME profiles extension + +package certificate + +import ( + "context" + "crypto/x509" + "fmt" + "time" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + + "github.com/cert-manager/cert-manager/e2e-tests/framework" + "github.com/cert-manager/cert-manager/e2e-tests/framework/helper" + "github.com/cert-manager/cert-manager/e2e-tests/framework/helper/validation/certificaterequests" + e2eutil "github.com/cert-manager/cert-manager/e2e-tests/util" + apiutil "github.com/cert-manager/cert-manager/pkg/api/util" + cmacme "github.com/cert-manager/cert-manager/pkg/apis/acme/v1" + v1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" + cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" + "github.com/cert-manager/cert-manager/test/unit/gen" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +var _ = framework.CertManagerDescribe("ACME Profiles Extension", func() { + f := framework.NewDefaultFramework("acme-profiles-extension") + h := f.Helper() + + var acmeProfile string + var acmeIngressDomain string + issuerName := "test-acme-issuer" + certificateRequestName := "test-acme-certificate-request" + // fixedIngressName is the name of an ingress resource that is configured + // with a challenge solve. + // To utilise this solver, add the 'testing.cert-manager.io/fixed-ingress: "true"' label. + fixedIngressName := "testingress" + + // JustBeforeEach is necessary here so that the `acmeProfile` variable can + // be mutated before we create the Issuer resource. + // https://onsi.github.io/ginkgo/#separating-creation-and-configuration-justbeforeeach + JustBeforeEach(func(testingCtx context.Context) { + acmeIngressDomain = e2eutil.RandomSubdomain(f.Config.Addons.IngressController.Domain) + + solvers := []cmacme.ACMEChallengeSolver{ + { + HTTP01: &cmacme.ACMEChallengeSolverHTTP01{ + Ingress: &cmacme.ACMEChallengeSolverHTTP01Ingress{ + Class: &f.Config.Addons.IngressController.IngressClass, + }, + }, + }, + { + Selector: &cmacme.CertificateDNSNameSelector{ + MatchLabels: map[string]string{ + "testing.cert-manager.io/fixed-ingress": "true", + }, + }, + HTTP01: &cmacme.ACMEChallengeSolverHTTP01{ + Ingress: &cmacme.ACMEChallengeSolverHTTP01Ingress{ + Name: fixedIngressName, + }, + }, + }, + } + acmeIssuer := gen.Issuer(issuerName, + gen.SetIssuerNamespace(f.Namespace.Name), + gen.SetIssuerACMEEmail(testingACMEEmail), + gen.SetIssuerACMEURL(f.Config.Addons.ACMEServer.URL), + gen.SetIssuerACMEPrivKeyRef(testingACMEPrivateKey), + gen.SetIssuerACMESkipTLSVerify(true), + gen.SetIssuerACMESolvers(solvers), + gen.SetIssuerACMEProfile(acmeProfile), + ) + By(fmt.Sprintf("Creating an Issuer with profile: %q", acmeProfile)) + _, err := f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Create(testingCtx, acmeIssuer, metav1.CreateOptions{}) + Expect(err).NotTo(HaveOccurred()) + By("Waiting for Issuer to become Ready") + err = e2eutil.WaitForIssuerCondition(testingCtx, f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name), + issuerName, + v1.IssuerCondition{ + Type: v1.IssuerConditionReady, + Status: cmmeta.ConditionTrue, + }) + Expect(err).NotTo(HaveOccurred()) + }) + + AfterEach(func(testingCtx context.Context) { + By("Cleaning up") + err := f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Delete(testingCtx, issuerName, metav1.DeleteOptions{}) + Expect(err).NotTo(HaveOccurred()) + err = f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Delete(testingCtx, testingACMEPrivateKey, metav1.DeleteOptions{}) + Expect(err).NotTo(HaveOccurred()) + }) + + When("the Issuer has a profile which is supported by the ACME server", func() { + BeforeEach(func(testingCtx context.Context) { + // The supported profiles are defined in the Pebble configuration: + // /make/config/pebble/charts/templates/configmap.yaml + acmeProfile = "123h" + }) + + It("should obtain a signed certificate, with duration matching the profile", func(testingCtx context.Context) { + crClient := f.CertManagerClientSet.CertmanagerV1().CertificateRequests(f.Namespace.Name) + + By("Creating a CertificateRequest") + csr, key, err := gen.CSR(x509.RSA, gen.SetCSRDNSNames(acmeIngressDomain)) + Expect(err).NotTo(HaveOccurred()) + cr := gen.CertificateRequest(certificateRequestName, + gen.SetCertificateRequestNamespace(f.Namespace.Name), + gen.SetCertificateRequestIssuer(cmmeta.IssuerReference{Kind: v1.IssuerKind, Name: issuerName}), + gen.SetCertificateRequestCSR(csr), + ) + + _, err = crClient.Create(testingCtx, cr, metav1.CreateOptions{}) + Expect(err).NotTo(HaveOccurred()) + + By("Verifying the Certificate is Ready") + _, err = h.WaitForCertificateRequestReady(testingCtx, f.Namespace.Name, certificateRequestName, time.Minute*5) + Expect(err).NotTo(HaveOccurred()) + + By("Verifying the Certificate duration matches the profile") + err = h.ValidateCertificateRequest( + types.NamespacedName{Namespace: cr.Namespace, Name: cr.Name}, + key, + certificaterequests.ExpectDuration(time.Hour*123, time.Second), + ) + Expect(err).NotTo(HaveOccurred()) + }) + }) + When("the Issuer has a profile which is not supported by the ACME server", func() { + BeforeEach(func(testingCtx context.Context) { + acmeProfile = "unsupported-profile" + }) + + It("should set the CertificateRequest as failed, with an actionable error message", func(testingCtx context.Context) { + crClient := f.CertManagerClientSet.CertmanagerV1().CertificateRequests(f.Namespace.Name) + + By("Creating a CertificateRequest") + csr, _, err := gen.CSR(x509.RSA, gen.SetCSRDNSNames(acmeIngressDomain)) + Expect(err).NotTo(HaveOccurred()) + cr := gen.CertificateRequest(certificateRequestName, + gen.SetCertificateRequestNamespace(f.Namespace.Name), + gen.SetCertificateRequestIssuer(cmmeta.IssuerReference{Kind: v1.IssuerKind, Name: issuerName}), + gen.SetCertificateRequestCSR(csr), + ) + + _, err = crClient.Create(testingCtx, cr, metav1.CreateOptions{}) + Expect(err).NotTo(HaveOccurred()) + + By("Verifying the Certificate is failed") + cr, err = h.WaitForCertificateRequestReady(testingCtx, f.Namespace.Name, certificateRequestName, time.Minute*5) + Expect(err).To(MatchError(helper.ErrCertificateRequestFailed)) + readyCondition := apiutil.GetCertificateRequestCondition(cr, v1.CertificateRequestConditionReady) + Expect(readyCondition.Message).To(ContainSubstring( + "Failed to create Order: acme: certificate authority does not advertise a profile with name unsupported-profile", + )) + }) + }) +}) diff --git a/test/e2e/suite/issuers/acme/dnsproviders/cloudflare.go b/test/e2e/suite/issuers/acme/dnsproviders/cloudflare.go index ba502f55c7e..17ca84c3843 100644 --- a/test/e2e/suite/issuers/acme/dnsproviders/cloudflare.go +++ b/test/e2e/suite/issuers/acme/dnsproviders/cloudflare.go @@ -22,11 +22,12 @@ import ( corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "github.com/cert-manager/cert-manager/e2e-tests/framework/addon" + "github.com/cert-manager/cert-manager/e2e-tests/framework/addon/base" + "github.com/cert-manager/cert-manager/e2e-tests/framework/config" + "github.com/cert-manager/cert-manager/e2e-tests/framework/util/errors" cmacme "github.com/cert-manager/cert-manager/pkg/apis/acme/v1" cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" - "github.com/cert-manager/cert-manager/test/e2e/framework/addon/base" - "github.com/cert-manager/cert-manager/test/e2e/framework/config" - "github.com/cert-manager/cert-manager/test/e2e/framework/util/errors" ) // Cloudflare provisions cloudflare credentials in a namespace for cert-manager @@ -44,24 +45,24 @@ type Cloudflare struct { createdSecret *corev1.Secret } -func (b *Cloudflare) Setup(c *config.Config) error { - if c.Suite.ACME.Cloudflare.APIKey == "" || - c.Suite.ACME.Cloudflare.Domain == "" || - c.Suite.ACME.Cloudflare.Email == "" { - return errors.NewSkip(ErrNoCredentials) +func (b *Cloudflare) Setup(ctx context.Context, cfg *config.Config, _ ...addon.AddonTransferableData) (addon.AddonTransferableData, error) { + if cfg.Suite.ACME.Cloudflare.APIKey == "" || + cfg.Suite.ACME.Cloudflare.Domain == "" || + cfg.Suite.ACME.Cloudflare.Email == "" { + return nil, errors.NewSkip(ErrNoCredentials) } if b.Base == nil { b.Base = &base.Base{} - err := b.Base.Setup(c) + _, err := b.Base.Setup(ctx, cfg) if err != nil { - return err + return nil, err } } - b.cf = c.Suite.ACME.Cloudflare + b.cf = cfg.Suite.ACME.Cloudflare - return nil + return nil, nil } // Provision will create a copy of the DNS provider credentials in a secret in @@ -102,8 +103,7 @@ func (b *Cloudflare) Provision() error { } func (b *Cloudflare) Deprovision() error { - b.Base.Details().KubeClient.CoreV1().Secrets(b.createdSecret.Namespace).Delete(context.TODO(), b.createdSecret.Name, metav1.DeleteOptions{}) - return nil + return b.Base.Details().KubeClient.CoreV1().Secrets(b.createdSecret.Namespace).Delete(context.TODO(), b.createdSecret.Name, metav1.DeleteOptions{}) } func (b *Cloudflare) Details() *Details { diff --git a/test/e2e/suite/issuers/acme/dnsproviders/rfc2136.go b/test/e2e/suite/issuers/acme/dnsproviders/rfc2136.go index 6969120f7f7..af5c26466e0 100644 --- a/test/e2e/suite/issuers/acme/dnsproviders/rfc2136.go +++ b/test/e2e/suite/issuers/acme/dnsproviders/rfc2136.go @@ -17,8 +17,11 @@ limitations under the License. package dnsproviders import ( + "context" + + "github.com/cert-manager/cert-manager/e2e-tests/framework/addon" + "github.com/cert-manager/cert-manager/e2e-tests/framework/config" cmacme "github.com/cert-manager/cert-manager/pkg/apis/acme/v1" - "github.com/cert-manager/cert-manager/test/e2e/framework/config" ) type RFC2136 struct { @@ -26,15 +29,15 @@ type RFC2136 struct { nameserver string } -func (b *RFC2136) Setup(c *config.Config) error { - b.nameserver = c.Addons.ACMEServer.DNSServer - return nil +func (b *RFC2136) Setup(_ context.Context, cfg *config.Config, _ ...addon.AddonTransferableData) (addon.AddonTransferableData, error) { + b.nameserver = cfg.Addons.ACMEServer.DNSServer + return nil, nil } // Provision will create a copy of the DNS provider credentials in a secret in // the APIServer, and return a portion of an Issuer that can be used to // utilise these credentials in tests. -func (b *RFC2136) Provision() error { +func (b *RFC2136) Provision(_ context.Context) error { b.details.ProviderConfig = cmacme.ACMEChallengeSolverDNS01{ RFC2136: &cmacme.ACMEIssuerDNS01ProviderRFC2136{ Nameserver: b.nameserver, @@ -44,7 +47,7 @@ func (b *RFC2136) Provision() error { return nil } -func (b *RFC2136) Deprovision() error { +func (b *RFC2136) Deprovision(_ context.Context) error { return nil } diff --git a/test/e2e/suite/issuers/acme/doc.go b/test/e2e/suite/issuers/acme/doc.go index 70960e6393f..3f312f0ac42 100644 --- a/test/e2e/suite/issuers/acme/doc.go +++ b/test/e2e/suite/issuers/acme/doc.go @@ -17,6 +17,6 @@ limitations under the License. package acme import ( - _ "github.com/cert-manager/cert-manager/test/e2e/suite/issuers/acme/certificate" - _ "github.com/cert-manager/cert-manager/test/e2e/suite/issuers/acme/certificaterequest" + _ "github.com/cert-manager/cert-manager/e2e-tests/suite/issuers/acme/certificate" + _ "github.com/cert-manager/cert-manager/e2e-tests/suite/issuers/acme/certificaterequest" ) diff --git a/test/e2e/suite/issuers/acme/issuer.go b/test/e2e/suite/issuers/acme/issuer.go index c6f65af5bf9..64892190860 100644 --- a/test/e2e/suite/issuers/acme/issuer.go +++ b/test/e2e/suite/issuers/acme/issuer.go @@ -18,23 +18,23 @@ package acme import ( "context" - "encoding/base64" "errors" "fmt" "time" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/wait" + "github.com/cert-manager/cert-manager/e2e-tests/framework" + "github.com/cert-manager/cert-manager/e2e-tests/util" apiutil "github.com/cert-manager/cert-manager/pkg/api/util" cmacme "github.com/cert-manager/cert-manager/pkg/apis/acme/v1" v1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" - "github.com/cert-manager/cert-manager/test/e2e/framework" - "github.com/cert-manager/cert-manager/test/e2e/util" "github.com/cert-manager/cert-manager/test/unit/gen" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" ) var _ = framework.CertManagerDescribe("ACME Issuer", func() { @@ -42,13 +42,15 @@ var _ = framework.CertManagerDescribe("ACME Issuer", func() { issuerName := "test-acme-issuer" - AfterEach(func() { + AfterEach(func(testingCtx context.Context) { By("Cleaning up") - f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Delete(context.TODO(), issuerName, metav1.DeleteOptions{}) - f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Delete(context.TODO(), f.Config.Addons.ACMEServer.TestingACMEPrivateKey, metav1.DeleteOptions{}) + err := f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Delete(testingCtx, issuerName, metav1.DeleteOptions{}) + Expect(err).NotTo(HaveOccurred()) + err = f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Delete(testingCtx, f.Config.Addons.ACMEServer.TestingACMEPrivateKey, metav1.DeleteOptions{}) + Expect(err).NotTo(HaveOccurred()) }) - It("should register ACME account", func() { + It("should register ACME account", func(testingCtx context.Context) { acmeIssuer := gen.Issuer(issuerName, gen.SetIssuerNamespace(f.Namespace.Name), gen.SetIssuerACMEEmail(f.Config.Addons.ACMEServer.TestingACMEEmail), @@ -56,11 +58,11 @@ var _ = framework.CertManagerDescribe("ACME Issuer", func() { gen.SetIssuerACMESkipTLSVerify(true), gen.SetIssuerACMEPrivKeyRef(f.Config.Addons.ACMEServer.TestingACMEPrivateKey)) By("Creating an Issuer") - _, err := f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Create(context.TODO(), acmeIssuer, metav1.CreateOptions{}) + _, err := f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Create(testingCtx, acmeIssuer, metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred()) By("Waiting for Issuer to become Ready") - err = util.WaitForIssuerCondition(f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name), + err = util.WaitForIssuerCondition(testingCtx, f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name), acmeIssuer.Name, v1.IssuerCondition{ Type: v1.IssuerConditionReady, @@ -69,7 +71,7 @@ var _ = framework.CertManagerDescribe("ACME Issuer", func() { Expect(err).NotTo(HaveOccurred()) By("Verifying the ACME account URI is set") - err = util.WaitForIssuerStatusFunc(f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name), + err = util.WaitForIssuerStatusFunc(testingCtx, f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name), acmeIssuer.Name, func(i *v1.Issuer) (bool, error) { if i.GetStatus().ACMEStatus().URI == "" { @@ -80,14 +82,14 @@ var _ = framework.CertManagerDescribe("ACME Issuer", func() { Expect(err).NotTo(HaveOccurred()) By("Verifying ACME account private key exists") - secret, err := f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Get(context.TODO(), f.Config.Addons.ACMEServer.TestingACMEPrivateKey, metav1.GetOptions{}) + secret, err := f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Get(testingCtx, f.Config.Addons.ACMEServer.TestingACMEPrivateKey, metav1.GetOptions{}) Expect(err).NotTo(HaveOccurred()) if len(secret.Data) != 1 { Fail("Expected 1 key in ACME account private key secret, but there was %d", len(secret.Data)) } }) - It("should recover a lost ACME account URI", func() { + It("should recover a lost ACME account URI", func(testingCtx context.Context) { acmeIssuer := gen.Issuer(issuerName, gen.SetIssuerNamespace(f.Namespace.Name), gen.SetIssuerACMEEmail(f.Config.Addons.ACMEServer.TestingACMEEmail), @@ -95,11 +97,11 @@ var _ = framework.CertManagerDescribe("ACME Issuer", func() { gen.SetIssuerACMESkipTLSVerify(true), gen.SetIssuerACMEPrivKeyRef(f.Config.Addons.ACMEServer.TestingACMEPrivateKey)) By("Creating an Issuer") - _, err := f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Create(context.TODO(), acmeIssuer, metav1.CreateOptions{}) + _, err := f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Create(testingCtx, acmeIssuer, metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred()) By("Waiting for Issuer to become Ready") - err = util.WaitForIssuerCondition(f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name), + err = util.WaitForIssuerCondition(testingCtx, f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name), acmeIssuer.Name, v1.IssuerCondition{ Type: v1.IssuerConditionReady, @@ -109,7 +111,7 @@ var _ = framework.CertManagerDescribe("ACME Issuer", func() { By("Verifying the ACME account URI is set") var finalURI string - err = util.WaitForIssuerStatusFunc(f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name), + err = util.WaitForIssuerStatusFunc(testingCtx, f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name), acmeIssuer.Name, func(i *v1.Issuer) (bool, error) { if i.GetStatus().ACMEStatus().URI == "" { @@ -121,7 +123,7 @@ var _ = framework.CertManagerDescribe("ACME Issuer", func() { Expect(err).NotTo(HaveOccurred()) By("Verifying ACME account private key exists") - secret, err := f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Get(context.TODO(), f.Config.Addons.ACMEServer.TestingACMEPrivateKey, metav1.GetOptions{}) + secret, err := f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Get(testingCtx, f.Config.Addons.ACMEServer.TestingACMEPrivateKey, metav1.GetOptions{}) Expect(err).NotTo(HaveOccurred()) if len(secret.Data) != 1 { @@ -129,7 +131,7 @@ var _ = framework.CertManagerDescribe("ACME Issuer", func() { } By("Deleting the Issuer") - err = f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Delete(context.TODO(), acmeIssuer.Name, metav1.DeleteOptions{}) + err = f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Delete(testingCtx, acmeIssuer.Name, metav1.DeleteOptions{}) Expect(err).NotTo(HaveOccurred()) By("Recreating the Issuer") @@ -139,11 +141,11 @@ var _ = framework.CertManagerDescribe("ACME Issuer", func() { gen.SetIssuerACMEURL(f.Config.Addons.ACMEServer.URL), gen.SetIssuerACMESkipTLSVerify(true), gen.SetIssuerACMEPrivKeyRef(f.Config.Addons.ACMEServer.TestingACMEPrivateKey)) - _, err = f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Create(context.TODO(), acmeIssuer, metav1.CreateOptions{}) + _, err = f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Create(testingCtx, acmeIssuer, metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred()) By("Waiting for Issuer to become Ready") - err = util.WaitForIssuerCondition(f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name), + err = util.WaitForIssuerCondition(testingCtx, f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name), acmeIssuer.Name, v1.IssuerCondition{ Type: v1.IssuerConditionReady, @@ -152,7 +154,7 @@ var _ = framework.CertManagerDescribe("ACME Issuer", func() { Expect(err).NotTo(HaveOccurred()) By("Verifying the ACME account URI has been recovered correctly") - err = util.WaitForIssuerStatusFunc(f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name), + err = util.WaitForIssuerStatusFunc(testingCtx, f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name), acmeIssuer.Name, func(i *v1.Issuer) (bool, error) { uri := i.GetStatus().ACMEStatus().URI @@ -167,7 +169,7 @@ var _ = framework.CertManagerDescribe("ACME Issuer", func() { Expect(err).NotTo(HaveOccurred()) }) - It("should fail to register an ACME account", func() { + It("should fail to register an ACME account", func(testingCtx context.Context) { acmeIssuer := gen.Issuer(issuerName, gen.SetIssuerNamespace(f.Namespace.Name), gen.SetIssuerACMEEmail(f.Config.Addons.ACMEServer.TestingACMEEmail), @@ -176,11 +178,11 @@ var _ = framework.CertManagerDescribe("ACME Issuer", func() { gen.SetIssuerACMEPrivKeyRef(f.Config.Addons.ACMEServer.TestingACMEPrivateKey)) By("Creating an Issuer with an invalid server") - _, err := f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Create(context.TODO(), acmeIssuer, metav1.CreateOptions{}) + _, err := f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Create(testingCtx, acmeIssuer, metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred()) By("Waiting for Issuer to become non-Ready") - err = util.WaitForIssuerCondition(f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name), + err = util.WaitForIssuerCondition(testingCtx, f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name), acmeIssuer.Name, v1.IssuerCondition{ Type: v1.IssuerConditionReady, @@ -189,7 +191,7 @@ var _ = framework.CertManagerDescribe("ACME Issuer", func() { Expect(err).NotTo(HaveOccurred()) }) - It("should handle updates to the email field", func() { + It("should handle updates to the email field", func(testingCtx context.Context) { acmeIssuer := gen.Issuer(issuerName, gen.SetIssuerNamespace(f.Namespace.Name), gen.SetIssuerACMEEmail(f.Config.Addons.ACMEServer.TestingACMEEmail), @@ -198,11 +200,11 @@ var _ = framework.CertManagerDescribe("ACME Issuer", func() { gen.SetIssuerACMEPrivKeyRef(f.Config.Addons.ACMEServer.TestingACMEPrivateKey)) By("Creating an Issuer") - acmeIssuer, err := f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Create(context.TODO(), acmeIssuer, metav1.CreateOptions{}) + acmeIssuer, err := f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Create(testingCtx, acmeIssuer, metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred()) By("Waiting for Issuer to become Ready") - err = util.WaitForIssuerCondition(f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name), + err = util.WaitForIssuerCondition(testingCtx, f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name), acmeIssuer.Name, v1.IssuerCondition{ Type: v1.IssuerConditionReady, @@ -211,7 +213,7 @@ var _ = framework.CertManagerDescribe("ACME Issuer", func() { Expect(err).NotTo(HaveOccurred()) By("Verifying the ACME account URI is set") - err = util.WaitForIssuerStatusFunc(f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name), + err = util.WaitForIssuerStatusFunc(testingCtx, f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name), acmeIssuer.Name, func(i *v1.Issuer) (bool, error) { if i.GetStatus().ACMEStatus().URI == "" { @@ -222,14 +224,14 @@ var _ = framework.CertManagerDescribe("ACME Issuer", func() { Expect(err).NotTo(HaveOccurred()) By("Verifying ACME account private key exists") - secret, err := f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Get(context.TODO(), f.Config.Addons.ACMEServer.TestingACMEPrivateKey, metav1.GetOptions{}) + secret, err := f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Get(testingCtx, f.Config.Addons.ACMEServer.TestingACMEPrivateKey, metav1.GetOptions{}) Expect(err).NotTo(HaveOccurred()) if len(secret.Data) != 1 { Fail("Expected 1 key in ACME account private key secret, but there was %d", len(secret.Data)) } By("Verifying the ACME account email has been registered") - err = util.WaitForIssuerStatusFunc(f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name), + err = util.WaitForIssuerStatusFunc(testingCtx, f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name), acmeIssuer.Name, func(i *v1.Issuer) (bool, error) { registeredEmail := i.GetStatus().ACMEStatus().LastRegisteredEmail @@ -241,14 +243,14 @@ var _ = framework.CertManagerDescribe("ACME Issuer", func() { Expect(err).NotTo(HaveOccurred()) By("Changing the email field") - acmeIssuer, err = f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Get(context.TODO(), acmeIssuer.Name, metav1.GetOptions{}) + acmeIssuer, err = f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Get(testingCtx, acmeIssuer.Name, metav1.GetOptions{}) Expect(err).NotTo(HaveOccurred()) acmeIssuer.Spec.ACME.Email = f.Config.Addons.ACMEServer.TestingACMEEmailAlternative - acmeIssuer, err = f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Update(context.TODO(), acmeIssuer, metav1.UpdateOptions{}) + acmeIssuer, err = f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Update(testingCtx, acmeIssuer, metav1.UpdateOptions{}) Expect(err).NotTo(HaveOccurred()) By("Waiting for Issuer to become Ready") - err = util.WaitForIssuerCondition(f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name), + err = util.WaitForIssuerCondition(testingCtx, f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name), acmeIssuer.Name, v1.IssuerCondition{ Type: v1.IssuerConditionReady, @@ -257,7 +259,7 @@ var _ = framework.CertManagerDescribe("ACME Issuer", func() { Expect(err).NotTo(HaveOccurred()) By("Verifying the changed ACME account email has been registered") - err = util.WaitForIssuerStatusFunc(f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name), + err = util.WaitForIssuerStatusFunc(testingCtx, f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name), acmeIssuer.Name, func(i *v1.Issuer) (bool, error) { registeredEmail := i.GetStatus().ACMEStatus().LastRegisteredEmail @@ -268,23 +270,25 @@ var _ = framework.CertManagerDescribe("ACME Issuer", func() { }) Expect(err).NotTo(HaveOccurred()) }) - It("ACME account with External Account Binding", func() { + It("ACME account with External Account Binding", func(testingCtx context.Context) { By("providing the legacy keyAlgorithm value") var ( secretName = "test-secret" - keyID = "kid-1" - key = "kid-secret-1" + // Must match the keyID and key in the Pebble config. See: + // config.json in make/config/pebble/templates/configmaps.yaml + keyID = "kid-1" + key = "zWNDZM6eQGHWpSRTPal5eIUYFTu7EajVIoguysqZ9wG44nMEtx3MUAsUDkMTQ12W" ) - keyBytes := []byte(base64.RawURLEncoding.EncodeToString([]byte(key))) + keyBytes := []byte(key) s := gen.Secret(secretName, gen.SetSecretNamespace(f.Namespace.Name), gen.SetSecretData(map[string][]byte{ "key": keyBytes, })) - _, err := f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Create(context.TODO(), s, metav1.CreateOptions{}) + _, err := f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Create(testingCtx, s, metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred()) acmeIssuer := gen.Issuer(issuerName, @@ -296,10 +300,10 @@ var _ = framework.CertManagerDescribe("ACME Issuer", func() { gen.SetIssuerACMEEABWithKeyAlgorithm(keyID, secretName, cmacme.HS256)) acmeIssuer, err = f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Create( - context.TODO(), acmeIssuer, metav1.CreateOptions{}) + testingCtx, acmeIssuer, metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred()) - err = util.WaitForIssuerCondition(f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name), + err = util.WaitForIssuerCondition(testingCtx, f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name), acmeIssuer.Name, v1.IssuerCondition{ Type: v1.IssuerConditionReady, @@ -309,21 +313,28 @@ var _ = framework.CertManagerDescribe("ACME Issuer", func() { By("removing the legacy keyAlgorithm value") - acmeIssuer, err = f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Get(context.TODO(), acmeIssuer.Name, metav1.GetOptions{}) + acmeIssuer, err = f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Get(testingCtx, acmeIssuer.Name, metav1.GetOptions{}) Expect(err).NotTo(HaveOccurred()) acmeIssuer = gen.IssuerFrom(acmeIssuer, gen.SetIssuerACMEEAB(keyID, secretName)) - _, err = f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Update(context.TODO(), acmeIssuer, metav1.UpdateOptions{}) + _, err = f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Update(testingCtx, acmeIssuer, metav1.UpdateOptions{}) Expect(err).NotTo(HaveOccurred()) // TODO: we should use observedGeneration here, but currently it won't // be incremented correctly in this scenario. // Verify that Issuer's Ready condition remains True for 5 seconds. - err = wait.Poll(time.Millisecond*200, time.Second*5, func() (bool, error) { - iss, err := f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Get( - context.TODO(), issuerName, metav1.GetOptions{}) + startTime := time.Now() + successful := false + err = wait.PollUntilContextCancel(testingCtx, time.Millisecond*200, true, func(ctx context.Context) (bool, error) { + // Check if issuer has been ready for 5s + if time.Since(startTime) > time.Second*5 { + successful = true + return true, nil + } + + iss, err := f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Get(ctx, issuerName, metav1.GetOptions{}) if err != nil { return false, err } @@ -336,7 +347,7 @@ var _ = framework.CertManagerDescribe("ACME Issuer", func() { // keep polling return false, nil }) - Expect(err).To(HaveOccurred()) - Expect(err).To(MatchError(wait.ErrWaitTimeout)) + Expect(err).NotTo(HaveOccurred()) + Expect(successful).To(BeTrue()) }) }) diff --git a/test/e2e/suite/issuers/ca/certificate.go b/test/e2e/suite/issuers/ca/certificate.go index a9b3cd8122b..64ac238dfed 100644 --- a/test/e2e/suite/issuers/ca/certificate.go +++ b/test/e2e/suite/issuers/ca/certificate.go @@ -20,17 +20,17 @@ import ( "context" "time" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "github.com/cert-manager/cert-manager/internal/controller/feature" + "github.com/cert-manager/cert-manager/e2e-tests/framework" + "github.com/cert-manager/cert-manager/e2e-tests/framework/helper/validation/certificates" + "github.com/cert-manager/cert-manager/e2e-tests/util" v1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" - utilfeature "github.com/cert-manager/cert-manager/pkg/util/feature" - "github.com/cert-manager/cert-manager/test/e2e/framework" - "github.com/cert-manager/cert-manager/test/e2e/util" "github.com/cert-manager/cert-manager/test/unit/gen" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" ) var _ = framework.CertManagerDescribe("CA Certificate", func() { @@ -41,15 +41,15 @@ var _ = framework.CertManagerDescribe("CA Certificate", func() { certificateName := "test-ca-certificate" certificateSecretName := "test-ca-certificate" - JustBeforeEach(func() { + JustBeforeEach(func(testingCtx context.Context) { By("Creating an Issuer") issuer := gen.Issuer(issuerName, gen.SetIssuerNamespace(f.Namespace.Name), gen.SetIssuerCASecretName(issuerSecretName)) - _, err := f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Create(context.TODO(), issuer, metav1.CreateOptions{}) + _, err := f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Create(testingCtx, issuer, metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred()) By("Waiting for Issuer to become Ready") - err = util.WaitForIssuerCondition(f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name), + err = util.WaitForIssuerCondition(testingCtx, f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name), issuerName, v1.IssuerCondition{ Type: v1.IssuerConditionReady, @@ -58,28 +58,40 @@ var _ = framework.CertManagerDescribe("CA Certificate", func() { Expect(err).NotTo(HaveOccurred()) }) - AfterEach(func() { + AfterEach(func(testingCtx context.Context) { By("Cleaning up") - f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Delete(context.TODO(), issuerSecretName, metav1.DeleteOptions{}) - f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Delete(context.TODO(), issuerName, metav1.DeleteOptions{}) + err := f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Delete(testingCtx, issuerSecretName, metav1.DeleteOptions{}) + Expect(err).NotTo(HaveOccurred()) + err = f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Delete(testingCtx, issuerName, metav1.DeleteOptions{}) + Expect(err).NotTo(HaveOccurred()) }) Context("when the CA is the root", func() { - BeforeEach(func() { + BeforeEach(func(testingCtx context.Context) { By("Creating a signing keypair fixture") - _, err := f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Create(context.TODO(), newSigningKeypairSecret(issuerSecretName), metav1.CreateOptions{}) + _, err := f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Create(testingCtx, newSigningKeypairSecret(issuerSecretName), metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred()) }) - It("should generate a signed keypair", func() { + It("should generate a signed keypair", func(testingCtx context.Context) { certClient := f.CertManagerClientSet.CertmanagerV1().Certificates(f.Namespace.Name) By("Creating a Certificate") - cert, err := certClient.Create(context.TODO(), util.NewCertManagerBasicCertificate(certificateName, certificateSecretName, issuerName, v1.IssuerKind, nil, nil), metav1.CreateOptions{}) + cert := gen.Certificate(certificateName, + gen.SetCertificateNamespace(f.Namespace.Name), + gen.SetCertificateSecretName(certificateSecretName), + gen.SetCertificateIssuer(cmmeta.IssuerReference{ + Name: issuerName, + Kind: v1.IssuerKind, + }), + gen.SetCertificateCommonName("test.domain.com"), + gen.SetCertificateOrganization("test-org"), + ) + cert, err := certClient.Create(testingCtx, cert, metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred()) By("Verifying the Certificate is valid") By("Waiting for the Certificate to be issued...") - cert, err = f.Helper().WaitForCertificateReadyAndDoneIssuing(cert, time.Minute*5) + cert, err = f.Helper().WaitForCertificateReadyAndDoneIssuing(testingCtx, cert, time.Minute*5) Expect(err).NotTo(HaveOccurred()) By("Validating the issued Certificate...") @@ -87,19 +99,27 @@ var _ = framework.CertManagerDescribe("CA Certificate", func() { Expect(err).NotTo(HaveOccurred()) }) - It("should be able to obtain an ECDSA key from a RSA backed issuer", func() { + It("should be able to obtain an ECDSA key from a RSA backed issuer", func(testingCtx context.Context) { certClient := f.CertManagerClientSet.CertmanagerV1().Certificates(f.Namespace.Name) - cert := util.NewCertManagerBasicCertificate(certificateName, certificateSecretName, issuerName, v1.IssuerKind, nil, nil) - cert.Spec.PrivateKey.Algorithm = v1.ECDSAKeyAlgorithm - cert.Spec.PrivateKey.Size = 521 - By("Creating a Certificate") - cert, err := certClient.Create(context.TODO(), cert, metav1.CreateOptions{}) + cert := gen.Certificate(certificateName, + gen.SetCertificateNamespace(f.Namespace.Name), + gen.SetCertificateSecretName(certificateSecretName), + gen.SetCertificateIssuer(cmmeta.IssuerReference{ + Name: issuerName, + Kind: v1.IssuerKind, + }), + gen.SetCertificateCommonName("test.domain.com"), + gen.SetCertificateOrganization("test-org"), + gen.SetCertificateKeyAlgorithm(v1.ECDSAKeyAlgorithm), + gen.SetCertificateKeySize(521), + ) + cert, err := certClient.Create(testingCtx, cert, metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred()) By("Waiting for the Certificate to be issued...") - cert, err = f.Helper().WaitForCertificateReadyAndDoneIssuing(cert, time.Minute*5) + cert, err = f.Helper().WaitForCertificateReadyAndDoneIssuing(testingCtx, cert, time.Minute*5) Expect(err).NotTo(HaveOccurred()) By("Validating the issued Certificate...") @@ -107,18 +127,26 @@ var _ = framework.CertManagerDescribe("CA Certificate", func() { Expect(err).NotTo(HaveOccurred()) }) - It("should be able to obtain an Ed25519 key from a RSA backed issuer", func() { + It("should be able to obtain an Ed25519 key from a RSA backed issuer", func(testingCtx context.Context) { certClient := f.CertManagerClientSet.CertmanagerV1().Certificates(f.Namespace.Name) - cert := util.NewCertManagerBasicCertificate(certificateName, certificateSecretName, issuerName, v1.IssuerKind, nil, nil) - cert.Spec.PrivateKey.Algorithm = v1.Ed25519KeyAlgorithm - By("Creating a Certificate") - cert, err := certClient.Create(context.TODO(), cert, metav1.CreateOptions{}) + cert := gen.Certificate(certificateName, + gen.SetCertificateNamespace(f.Namespace.Name), + gen.SetCertificateSecretName(certificateSecretName), + gen.SetCertificateIssuer(cmmeta.IssuerReference{ + Name: issuerName, + Kind: v1.IssuerKind, + }), + gen.SetCertificateCommonName("test.domain.com"), + gen.SetCertificateOrganization("test-org"), + gen.SetCertificateKeyAlgorithm(v1.Ed25519KeyAlgorithm), + ) + cert, err := certClient.Create(testingCtx, cert, metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred()) By("Waiting for the Certificate to be issued...") - cert, err = f.Helper().WaitForCertificateReadyAndDoneIssuing(cert, time.Minute*5) + cert, err = f.Helper().WaitForCertificateReadyAndDoneIssuing(testingCtx, cert, time.Minute*5) Expect(err).NotTo(HaveOccurred()) By("Validating the issued Certificate...") @@ -126,25 +154,29 @@ var _ = framework.CertManagerDescribe("CA Certificate", func() { Expect(err).NotTo(HaveOccurred()) }) - It("should be able to create a certificate with additional output formats", func() { - // Output formats is only enabled via this feature gate being enabled. - // Don't run test if the gate isn't enabled. - framework.RequireFeatureGate(f, utilfeature.DefaultFeatureGate, feature.AdditionalCertificateOutputFormats) - + It("should be able to create a certificate with additional output formats", func(testingCtx context.Context) { certClient := f.CertManagerClientSet.CertmanagerV1().Certificates(f.Namespace.Name) - cert := util.NewCertManagerBasicCertificate(certificateName, certificateSecretName, issuerName, v1.IssuerKind, nil, nil) - cert.Spec.AdditionalOutputFormats = []v1.CertificateAdditionalOutputFormat{ - {Type: "DER"}, - {Type: "CombinedPEM"}, - } - By("Creating a Certificate") - cert, err := certClient.Create(context.TODO(), cert, metav1.CreateOptions{}) + cert := gen.Certificate(certificateName, + gen.SetCertificateNamespace(f.Namespace.Name), + gen.SetCertificateSecretName(certificateSecretName), + gen.SetCertificateIssuer(cmmeta.IssuerReference{ + Name: issuerName, + Kind: v1.IssuerKind, + }), + gen.SetCertificateCommonName("test.domain.com"), + gen.SetCertificateOrganization("test-org"), + gen.SetCertificateAdditionalOutputFormats( + v1.CertificateAdditionalOutputFormat{Type: "DER"}, + v1.CertificateAdditionalOutputFormat{Type: "CombinedPEM"}, + ), + ) + cert, err := certClient.Create(testingCtx, cert, metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred()) By("Waiting for the Certificate to be issued...") - cert, err = f.Helper().WaitForCertificateReadyAndDoneIssuing(cert, time.Minute*5) + cert, err = f.Helper().WaitForCertificateReadyAndDoneIssuing(testingCtx, cert, time.Minute*5) Expect(err).NotTo(HaveOccurred()) By("Validating the issued Certificate...") @@ -172,41 +204,63 @@ var _ = framework.CertManagerDescribe("CA Certificate", func() { }, } for _, v := range cases { - v := v - It("should generate a signed keypair valid for "+v.label, func() { + It("should generate a signed keypair valid for "+v.label, func(testingCtx context.Context) { certClient := f.CertManagerClientSet.CertmanagerV1().Certificates(f.Namespace.Name) By("Creating a Certificate") - cert, err := certClient.Create(context.TODO(), util.NewCertManagerBasicCertificate(certificateName, certificateSecretName, issuerName, v1.IssuerKind, v.inputDuration, v.inputRenewBefore), metav1.CreateOptions{}) + cert := gen.Certificate(certificateName, + gen.SetCertificateNamespace(f.Namespace.Name), + gen.SetCertificateSecretName(certificateSecretName), + gen.SetCertificateIssuer(cmmeta.IssuerReference{ + Name: issuerName, + Kind: v1.IssuerKind, + }), + gen.SetCertificateDuration(v.inputDuration), + gen.SetCertificateRenewBefore(v.inputRenewBefore), + gen.SetCertificateCommonName("test.domain.com"), + gen.SetCertificateOrganization("test-org"), + ) + cert, err := certClient.Create(testingCtx, cert, metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred()) By("Waiting for the Certificate to be issued...") - cert, err = f.Helper().WaitForCertificateReadyAndDoneIssuing(cert, time.Minute*5) + cert, err = f.Helper().WaitForCertificateReadyAndDoneIssuing(testingCtx, cert, time.Minute*5) Expect(err).NotTo(HaveOccurred()) By("Validating the issued Certificate...") err = f.Helper().ValidateCertificate(cert) Expect(err).NotTo(HaveOccurred()) - f.CertificateDurationValid(cert, v.expectedDuration, 0) + err = f.Helper().ValidateCertificate(cert, certificates.ExpectDuration(v.expectedDuration, 0)) + Expect(err).NotTo(HaveOccurred()) }) } }) Context("when the CA is an issuer", func() { - BeforeEach(func() { + BeforeEach(func(testingCtx context.Context) { By("Creating a signing keypair fixture") - _, err := f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Create(context.TODO(), newSigningIssuer1KeypairSecret(issuerSecretName), metav1.CreateOptions{}) + _, err := f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Create(testingCtx, newSigningIssuer1KeypairSecret(issuerSecretName), metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred()) }) - It("should generate a signed keypair", func() { + It("should generate a signed keypair", func(testingCtx context.Context) { certClient := f.CertManagerClientSet.CertmanagerV1().Certificates(f.Namespace.Name) By("Creating a Certificate") - cert, err := certClient.Create(context.TODO(), util.NewCertManagerBasicCertificate(certificateName, certificateSecretName, issuerName, v1.IssuerKind, nil, nil), metav1.CreateOptions{}) + cert := gen.Certificate(certificateName, + gen.SetCertificateNamespace(f.Namespace.Name), + gen.SetCertificateSecretName(certificateSecretName), + gen.SetCertificateIssuer(cmmeta.IssuerReference{ + Name: issuerName, + Kind: v1.IssuerKind, + }), + gen.SetCertificateCommonName("test.domain.com"), + gen.SetCertificateOrganization("test-org"), + ) + cert, err := certClient.Create(testingCtx, cert, metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred()) By("Waiting for the Certificate to be issued...") - cert, err = f.Helper().WaitForCertificateReadyAndDoneIssuing(cert, time.Minute*5) + cert, err = f.Helper().WaitForCertificateReadyAndDoneIssuing(testingCtx, cert, time.Minute*5) Expect(err).NotTo(HaveOccurred()) By("Validating the issued Certificate...") @@ -216,20 +270,20 @@ var _ = framework.CertManagerDescribe("CA Certificate", func() { }) Context("when the CA is a second level issuer", func() { - BeforeEach(func() { + BeforeEach(func(testingCtx context.Context) { By("Creating a signing keypair fixture") - _, err := f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Create(context.TODO(), newSigningIssuer2KeypairSecret(issuerSecretName), metav1.CreateOptions{}) + _, err := f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Create(testingCtx, newSigningIssuer2KeypairSecret(issuerSecretName), metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred()) }) - It("should generate a signed keypair", func() { + It("should generate a signed keypair", func(testingCtx context.Context) { certClient := f.CertManagerClientSet.CertmanagerV1().Certificates(f.Namespace.Name) By("Creating a Certificate with Usages") - cert, err := certClient.Create(context.TODO(), gen.Certificate(certificateName, gen.SetCertificateNamespace(f.Namespace.Name), gen.SetCertificateCommonName("test.domain.com"), gen.SetCertificateSecretName(certificateSecretName), gen.SetCertificateIssuer(cmmeta.ObjectReference{Name: issuerName, Kind: v1.IssuerKind}), gen.SetCertificateKeyUsages(v1.UsageServerAuth, v1.UsageClientAuth)), metav1.CreateOptions{}) + cert, err := certClient.Create(testingCtx, gen.Certificate(certificateName, gen.SetCertificateNamespace(f.Namespace.Name), gen.SetCertificateCommonName("test.domain.com"), gen.SetCertificateSecretName(certificateSecretName), gen.SetCertificateIssuer(cmmeta.IssuerReference{Name: issuerName, Kind: v1.IssuerKind}), gen.SetCertificateKeyUsages(v1.UsageServerAuth, v1.UsageClientAuth)), metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred()) By("Waiting for the Certificate to be issued...") - cert, err = f.Helper().WaitForCertificateReadyAndDoneIssuing(cert, time.Minute*5) + cert, err = f.Helper().WaitForCertificateReadyAndDoneIssuing(testingCtx, cert, time.Minute*5) Expect(err).NotTo(HaveOccurred()) By("Validating the issued Certificate...") diff --git a/test/e2e/suite/issuers/ca/certificaterequest.go b/test/e2e/suite/issuers/ca/certificaterequest.go index c80e8e4c692..77963aa57c5 100644 --- a/test/e2e/suite/issuers/ca/certificaterequest.go +++ b/test/e2e/suite/issuers/ca/certificaterequest.go @@ -23,15 +23,18 @@ import ( "net/url" "time" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "github.com/cert-manager/cert-manager/e2e-tests/framework" + "github.com/cert-manager/cert-manager/e2e-tests/framework/helper/validation/certificaterequests" + "github.com/cert-manager/cert-manager/e2e-tests/util" v1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" - "github.com/cert-manager/cert-manager/test/e2e/framework" - "github.com/cert-manager/cert-manager/test/e2e/util" "github.com/cert-manager/cert-manager/test/unit/gen" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" ) func exampleURLs() (urls []*url.URL) { @@ -54,17 +57,16 @@ var _ = framework.CertManagerDescribe("CA CertificateRequest", func() { []byte{8, 8, 8, 8}, []byte{1, 1, 1, 1}, } - exampleURIs := []string{"spiffe://foo.foo.example.net", "spiffe://foo.bar.example.net"} - JustBeforeEach(func() { + JustBeforeEach(func(testingCtx context.Context) { By("Creating an Issuer") issuer := gen.Issuer(issuerName, gen.SetIssuerNamespace(f.Namespace.Name), gen.SetIssuerCASecretName(issuerSecretName)) - _, err := f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Create(context.TODO(), issuer, metav1.CreateOptions{}) + _, err := f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Create(testingCtx, issuer, metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred()) By("Waiting for Issuer to become Ready") - err = util.WaitForIssuerCondition(f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name), + err = util.WaitForIssuerCondition(testingCtx, f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name), issuerName, v1.IssuerCondition{ Type: v1.IssuerConditionReady, @@ -73,67 +75,75 @@ var _ = framework.CertManagerDescribe("CA CertificateRequest", func() { Expect(err).NotTo(HaveOccurred()) }) - AfterEach(func() { + AfterEach(func(testingCtx context.Context) { By("Cleaning up") - f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Delete(context.TODO(), issuerSecretName, metav1.DeleteOptions{}) - f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Delete(context.TODO(), issuerName, metav1.DeleteOptions{}) + err := f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Delete(testingCtx, issuerSecretName, metav1.DeleteOptions{}) + Expect(err).NotTo(HaveOccurred()) + err = f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Delete(testingCtx, issuerName, metav1.DeleteOptions{}) + Expect(err).NotTo(HaveOccurred()) }) Context("when the CA is the root", func() { - BeforeEach(func() { + BeforeEach(func(testingCtx context.Context) { By("Creating a signing keypair fixture") - _, err := f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Create(context.TODO(), newSigningKeypairSecret(issuerSecretName), metav1.CreateOptions{}) + _, err := f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Create(testingCtx, newSigningKeypairSecret(issuerSecretName), metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred()) }) - It("should generate a valid certificate from CSR", func() { + It("should generate a valid certificate from CSR", func(testingCtx context.Context) { certRequestClient := f.CertManagerClientSet.CertmanagerV1().CertificateRequests(f.Namespace.Name) By("Creating a CertificateRequest") - cr, key, err := util.NewCertManagerBasicCertificateRequest(certificateRequestName, issuerName, v1.IssuerKind, - &metav1.Duration{ - Duration: time.Hour * 24 * 90, - }, - exampleDNSNames, exampleIPAddresses, exampleURIs, x509.RSA) + csr, key, err := gen.CSR(x509.RSA, gen.SetCSRCommonName(exampleDNSNames[0]), gen.SetCSRDNSNames(exampleDNSNames...), gen.SetCSRIPAddresses(exampleIPAddresses...), gen.SetCSRURIs(exampleURLs()...)) Expect(err).NotTo(HaveOccurred()) - _, err = certRequestClient.Create(context.TODO(), cr, metav1.CreateOptions{}) + cr := gen.CertificateRequest(certificateRequestName, + gen.SetCertificateRequestNamespace(f.Namespace.Name), + gen.SetCertificateRequestIssuer(cmmeta.IssuerReference{Kind: v1.IssuerKind, Name: issuerName}), + gen.SetCertificateRequestDuration(&metav1.Duration{Duration: time.Hour * 24 * 90}), + gen.SetCertificateRequestCSR(csr), + ) + _, err = certRequestClient.Create(testingCtx, cr, metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred()) By("Verifying the Certificate is valid") - err = h.WaitCertificateRequestIssuedValidTLS(f.Namespace.Name, certificateRequestName, time.Second*30, key, []byte(rootCert)) + err = h.WaitCertificateRequestIssuedValidTLS(testingCtx, f.Namespace.Name, certificateRequestName, time.Second*30, key, []byte(rootCert)) Expect(err).NotTo(HaveOccurred()) }) - It("should be able to obtain an ECDSA key from a RSA backed issuer", func() { + It("should be able to obtain an ECDSA key from a RSA backed issuer", func(testingCtx context.Context) { certRequestClient := f.CertManagerClientSet.CertmanagerV1().CertificateRequests(f.Namespace.Name) By("Creating a CertificateRequest") - cr, key, err := util.NewCertManagerBasicCertificateRequest(certificateRequestName, issuerName, v1.IssuerKind, - &metav1.Duration{ - Duration: time.Hour * 24 * 90, - }, - exampleDNSNames, exampleIPAddresses, exampleURIs, x509.ECDSA) + csr, key, err := gen.CSR(x509.ECDSA, gen.SetCSRCommonName(exampleDNSNames[0]), gen.SetCSRDNSNames(exampleDNSNames...), gen.SetCSRIPAddresses(exampleIPAddresses...), gen.SetCSRURIs(exampleURLs()...)) Expect(err).NotTo(HaveOccurred()) - _, err = certRequestClient.Create(context.TODO(), cr, metav1.CreateOptions{}) + cr := gen.CertificateRequest(certificateRequestName, + gen.SetCertificateRequestNamespace(f.Namespace.Name), + gen.SetCertificateRequestIssuer(cmmeta.IssuerReference{Kind: v1.IssuerKind, Name: issuerName}), + gen.SetCertificateRequestDuration(&metav1.Duration{Duration: time.Hour * 24 * 90}), + gen.SetCertificateRequestCSR(csr), + ) + _, err = certRequestClient.Create(testingCtx, cr, metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred()) By("Verifying the Certificate is valid") - err = h.WaitCertificateRequestIssuedValidTLS(f.Namespace.Name, certificateRequestName, time.Second*30, key, []byte(rootCert)) + err = h.WaitCertificateRequestIssuedValidTLS(testingCtx, f.Namespace.Name, certificateRequestName, time.Second*30, key, []byte(rootCert)) Expect(err).NotTo(HaveOccurred()) }) - It("should be able to obtain an Ed25519 key from a RSA backed issuer", func() { + It("should be able to obtain an Ed25519 key from a RSA backed issuer", func(testingCtx context.Context) { certRequestClient := f.CertManagerClientSet.CertmanagerV1().CertificateRequests(f.Namespace.Name) By("Creating a CertificateRequest") - cr, key, err := util.NewCertManagerBasicCertificateRequest(certificateRequestName, issuerName, v1.IssuerKind, - &metav1.Duration{ - Duration: time.Hour * 24 * 90, - }, - exampleDNSNames, exampleIPAddresses, exampleURIs, x509.Ed25519) + csr, key, err := gen.CSR(x509.Ed25519, gen.SetCSRCommonName(exampleDNSNames[0]), gen.SetCSRDNSNames(exampleDNSNames...), gen.SetCSRIPAddresses(exampleIPAddresses...), gen.SetCSRURIs(exampleURLs()...)) Expect(err).NotTo(HaveOccurred()) - _, err = certRequestClient.Create(context.TODO(), cr, metav1.CreateOptions{}) + cr := gen.CertificateRequest(certificateRequestName, + gen.SetCertificateRequestNamespace(f.Namespace.Name), + gen.SetCertificateRequestIssuer(cmmeta.IssuerReference{Kind: v1.IssuerKind, Name: issuerName}), + gen.SetCertificateRequestDuration(&metav1.Duration{Duration: time.Hour * 24 * 90}), + gen.SetCertificateRequestCSR(csr), + ) + _, err = certRequestClient.Create(testingCtx, cr, metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred()) By("Verifying the Certificate is valid") - err = h.WaitCertificateRequestIssuedValidTLS(f.Namespace.Name, certificateRequestName, time.Second*30, key, []byte(rootCert)) + err = h.WaitCertificateRequestIssuedValidTLS(testingCtx, f.Namespace.Name, certificateRequestName, time.Second*30, key, []byte(rootCert)) Expect(err).NotTo(HaveOccurred()) }) @@ -154,23 +164,24 @@ var _ = framework.CertManagerDescribe("CA CertificateRequest", func() { }, } for _, v := range cases { - v := v - It("should generate a signed certificate valid for "+v.label, func() { + It("should generate a signed certificate valid for "+v.label, func(testingCtx context.Context) { crClient := f.CertManagerClientSet.CertmanagerV1().CertificateRequests(f.Namespace.Name) By("Creating a CertificateRequest with Usages") csr, key, err := gen.CSR(x509.RSA, gen.SetCSRDNSNames(exampleDNSNames...), gen.SetCSRIPAddresses(exampleIPAddresses...), gen.SetCSRURIs(exampleURLs()...)) Expect(err).NotTo(HaveOccurred()) - cr := gen.CertificateRequest(certificateRequestName, gen.SetCertificateRequestNamespace(f.Namespace.Name), gen.SetCertificateRequestIssuer(cmmeta.ObjectReference{Kind: v1.IssuerKind, Name: issuerName}), gen.SetCertificateRequestDuration(v.inputDuration), gen.SetCertificateRequestCSR(csr)) - cr, err = crClient.Create(context.TODO(), cr, metav1.CreateOptions{}) + cr := gen.CertificateRequest(certificateRequestName, gen.SetCertificateRequestNamespace(f.Namespace.Name), gen.SetCertificateRequestIssuer(cmmeta.IssuerReference{Kind: v1.IssuerKind, Name: issuerName}), gen.SetCertificateRequestDuration(v.inputDuration), gen.SetCertificateRequestCSR(csr)) + _, err = crClient.Create(testingCtx, cr, metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred()) By("Verifying the CertificateRequest is valid") - err = h.WaitCertificateRequestIssuedValid(f.Namespace.Name, certificateRequestName, time.Second*30, key) + err = h.WaitCertificateRequestIssuedValid(testingCtx, f.Namespace.Name, certificateRequestName, time.Second*30, key) Expect(err).NotTo(HaveOccurred()) - cr, err = crClient.Get(context.TODO(), cr.Name, metav1.GetOptions{}) + err = h.ValidateCertificateRequest(types.NamespacedName{ + Namespace: f.Namespace.Name, + Name: certificateRequestName, + }, key, certificaterequests.ExpectDuration(v.expectedDuration, 0)) Expect(err).NotTo(HaveOccurred()) - f.CertificateRequestDurationValid(cr, v.expectedDuration, 0) }) } }) diff --git a/test/e2e/suite/issuers/ca/clusterissuer.go b/test/e2e/suite/issuers/ca/clusterissuer.go index b660051da39..c8b82c3a27a 100644 --- a/test/e2e/suite/issuers/ca/clusterissuer.go +++ b/test/e2e/suite/issuers/ca/clusterissuer.go @@ -19,44 +19,47 @@ package ca import ( "context" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/rand" + "github.com/cert-manager/cert-manager/e2e-tests/framework" + "github.com/cert-manager/cert-manager/e2e-tests/util" v1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" - cmutil "github.com/cert-manager/cert-manager/pkg/util" - "github.com/cert-manager/cert-manager/test/e2e/framework" - "github.com/cert-manager/cert-manager/test/e2e/util" "github.com/cert-manager/cert-manager/test/unit/gen" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" ) var _ = framework.CertManagerDescribe("CA ClusterIssuer", func() { f := framework.NewDefaultFramework("create-ca-clusterissuer") - issuerName := "test-ca-clusterissuer" + cmutil.RandStringRunes(5) - secretName := "ca-clusterissuer-signing-keypair-" + cmutil.RandStringRunes(5) + issuerName := "test-ca-clusterissuer" + rand.String(5) + secretName := "ca-clusterissuer-signing-keypair-" + rand.String(5) - BeforeEach(func() { + BeforeEach(func(testingCtx context.Context) { By("Creating a signing keypair fixture") - _, err := f.KubeClientSet.CoreV1().Secrets(f.Config.Addons.CertManager.ClusterResourceNamespace).Create(context.TODO(), newSigningKeypairSecret(secretName), metav1.CreateOptions{}) + _, err := f.KubeClientSet.CoreV1().Secrets(f.Config.Addons.CertManager.ClusterResourceNamespace).Create(testingCtx, newSigningKeypairSecret(secretName), metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred()) }) - AfterEach(func() { + AfterEach(func(testingCtx context.Context) { By("Cleaning up") - f.KubeClientSet.CoreV1().Secrets(f.Config.Addons.CertManager.ClusterResourceNamespace).Delete(context.TODO(), secretName, metav1.DeleteOptions{}) - f.CertManagerClientSet.CertmanagerV1().ClusterIssuers().Delete(context.TODO(), issuerName, metav1.DeleteOptions{}) + err := f.KubeClientSet.CoreV1().Secrets(f.Config.Addons.CertManager.ClusterResourceNamespace).Delete(testingCtx, secretName, metav1.DeleteOptions{}) + Expect(err).NotTo(HaveOccurred()) + err = f.CertManagerClientSet.CertmanagerV1().ClusterIssuers().Delete(testingCtx, issuerName, metav1.DeleteOptions{}) + Expect(err).NotTo(HaveOccurred()) }) - It("should validate a signing keypair", func() { + It("should validate a signing keypair", func(testingCtx context.Context) { By("Creating an Issuer") clusterIssuer := gen.ClusterIssuer(issuerName, gen.SetIssuerCASecretName(secretName)) - _, err := f.CertManagerClientSet.CertmanagerV1().ClusterIssuers().Create(context.TODO(), clusterIssuer, metav1.CreateOptions{}) + _, err := f.CertManagerClientSet.CertmanagerV1().ClusterIssuers().Create(testingCtx, clusterIssuer, metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred()) By("Waiting for Issuer to become Ready") - err = util.WaitForClusterIssuerCondition(f.CertManagerClientSet.CertmanagerV1().ClusterIssuers(), + err = util.WaitForClusterIssuerCondition(testingCtx, f.CertManagerClientSet.CertmanagerV1().ClusterIssuers(), issuerName, v1.IssuerCondition{ Type: v1.IssuerConditionReady, diff --git a/test/e2e/suite/issuers/ca/fixtures.go b/test/e2e/suite/issuers/ca/fixtures.go index 48d43a87aa5..0759501b6f5 100644 --- a/test/e2e/suite/issuers/ca/fixtures.go +++ b/test/e2e/suite/issuers/ca/fixtures.go @@ -21,57 +21,68 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) +// These hardcoded certificates are generated using cert-manager. +// The YAML used to create these certificates is at the bottom of this file. +// Each cert was created and then copied by hand, with intermediate 2 having its +// chain in 'tls.crt' trimmed manually + +// rootCert is a hardcoded issuer certificate. Its dumped value is below: +// +// Version: 3 (0x2) +// Serial Number: +// f2:68:07:5e:fb:b1:5e:74:ab:27:cf:a5:7c:03:2f:b8 +// Signature Algorithm: ecdsa-with-SHA256 +// Issuer: C = UK, O = cert-manager, CN = cert-manager testing CA +// Validity +// Not Before: Nov 14 13:13:15 2023 GMT +// Not After : Oct 21 13:13:15 2123 GMT +// Subject: C = UK, O = cert-manager, CN = cert-manager testing CA +// Subject Public Key Info: +// Public Key Algorithm: id-ecPublicKey +// Public-Key: (256 bit) +// pub: +// 04:d9:d7:61:40:b6:5a:e3:17:3e:8f:c4:27:49:cf: +// 6b:7d:35:24:d4:b7:c1:18:57:2c:6e:5d:aa:3c:ae: +// a4:75:6d:f6:f6:d1:10:7a:0d:3e:0a:70:b9:3f:98: +// 5c:70:db:17:49:d2:9c:4e:9c:2b:3f:cc:45:2e:d4: +// 31:3c:3d:6a:90 +// ASN1 OID: prime256v1 +// NIST CURVE: P-256 +// X509v3 extensions: +// X509v3 Key Usage: critical +// Digital Signature, Key Encipherment, Certificate Sign +// X509v3 Basic Constraints: critical +// CA:TRUE +// X509v3 Subject Key Identifier: +// DA:C7:45:E4:F1:67:F2:5F:F4:02:49:37:5A:F9:A9:C4:92:E7:65:F8 +// +// Signature Algorithm: ecdsa-with-SHA256 +// Signature Value: +// +// 30:44:02:20:7f:5a:00:45:00:5f:e1:bc:b6:36:4f:30:be:24: +// 7f:ce:01:e6:61:12:95:41:3a:69:1b:63:b7:63:13:d5:34:5d: +// 02:20:1d:52:3e:11:e5:f6:54:31:aa:93:f0:9d:81:9b:01:40: +// 8a:c2:0d:c4:ed:fc:23:cd:39:19:42:7e:a4:7d:c6:4a const rootCert = `-----BEGIN CERTIFICATE----- -MIID4DCCAsigAwIBAgIJAJzTROInmDkQMA0GCSqGSIb3DQEBCwUAMFMxCzAJBgNV -BAYTAlVLMQswCQYDVQQIEwJOQTEVMBMGA1UEChMMY2VydC1tYW5hZ2VyMSAwHgYD -VQQDExdjZXJ0LW1hbmFnZXIgdGVzdGluZyBDQTAeFw0xNzA5MTAxODMzNDNaFw0y -NzA5MDgxODMzNDNaMFMxCzAJBgNVBAYTAlVLMQswCQYDVQQIEwJOQTEVMBMGA1UE -ChMMY2VydC1tYW5hZ2VyMSAwHgYDVQQDExdjZXJ0LW1hbmFnZXIgdGVzdGluZyBD -QTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAM+Q2AO4hARav0qwjk7I -4mEh5R201HS8s7HpaLOXBNvvh7qJ9yJz6jLqYg6EvP0K/bK56Cp2oe2igd7GOxpV -3YPOc3CG0CCqHMprEcvxj2xBKX00Rtcn4oVLhDPhAb0BV/R7NFLeWxzh+ggvPI1X -m1qLaWYqYZEJ5bBsYXD3tPdS4GGINRz8Zvih46f0Z2wVkCGoTpsbX8HO74sa2Day -UjzAsWGlO5bZGiMSHjDEnf9yek2TcjEyVoohoOLaQg/ng21T5RWzeZKTl1cznwuG -Vr9tZfHFqxQ5qeaId+1ICtxNvkEjbTnZl6Wy9Cthn0dxwOeS5TqMJ7SFNXy1gp4j -f/MCAwEAAaOBtjCBszAdBgNVHQ4EFgQUBtrjvWfbkLA0iX6sKVRhKUo864kwgYMG -A1UdIwR8MHqAFAba471n25CwNIl+rClUYSlKPOuJoVekVTBTMQswCQYDVQQGEwJV -SzELMAkGA1UECBMCTkExFTATBgNVBAoTDGNlcnQtbWFuYWdlcjEgMB4GA1UEAxMX -Y2VydC1tYW5hZ2VyIHRlc3RpbmcgQ0GCCQCc00TiJ5g5EDAMBgNVHRMEBTADAQH/ -MA0GCSqGSIb3DQEBCwUAA4IBAQCR+jXhup5tCKwhAf8xgvp589BczQOjmotuZGEL -Dcint2y263ChEdsoLhyJfvFCAZfTSm+UT95Hl+ZKVuoVEcAS7udaFUFpC/gIYVOi -H4/uvJps4SpVCB7+T/orcTjZ2ewT23mQAQg+B+iwX9VCof+fadkYOg1XD9/eaj6E -9McXID3iuCXg02RmEOwVMrTggHPwHrOGAilSaZc58cJZHmMYlT5rGrJcWS/AyXnH -VOodKC004yjh7w9aSbCCbAL0tDEnhm4Jrb8cxt7pDWbdEVUeuk9LZRQtluYBnmJU -kQ7ALfUfUh/RUpCV4uI6sEI3NDX2YqQbOtsBD/hNaL1F85FA ------END CERTIFICATE-----` - -const rootKey = `-----BEGIN RSA PRIVATE KEY----- -MIIEpAIBAAKCAQEAz5DYA7iEBFq/SrCOTsjiYSHlHbTUdLyzselos5cE2++Huon3 -InPqMupiDoS8/Qr9srnoKnah7aKB3sY7GlXdg85zcIbQIKocymsRy/GPbEEpfTRG -1yfihUuEM+EBvQFX9Hs0Ut5bHOH6CC88jVebWotpZiphkQnlsGxhcPe091LgYYg1 -HPxm+KHjp/RnbBWQIahOmxtfwc7vixrYNrJSPMCxYaU7ltkaIxIeMMSd/3J6TZNy -MTJWiiGg4tpCD+eDbVPlFbN5kpOXVzOfC4ZWv21l8cWrFDmp5oh37UgK3E2+QSNt -OdmXpbL0K2GfR3HA55LlOowntIU1fLWCniN/8wIDAQABAoIBAQCYvGvIKSG0FpbG -vi6pmLbEZO20s1jW4fiUxT2PUWR49sR4pocdahB/EOvA5TowNcNDnftSK+Ox+q/4 -HwRkt6R+Fg/qULmcH7F53dnFqeYw8a42/J3YOvg7v7rzdfISg4eWVobFJ+wBz+Nt -3FyBYWLm+MlBLZSH5rGG5em59/zJNHWIhH+oQPfCxAkYEvd8tXOTUzjhqvEfjaJy -FZghnT9xto4MwDdNCPbtzdNjTMhiv0AHkcZGGtRJfkehXX2qhXOQ2UzzO9XrMZnv -5KgYf+bXKJsyS3SPl6TTl7vg2gKBciRvsdFhMy5I5GyIADrEDJnNNmXQRtiaFLfd -k/aqfPT5AoGBAPquMouZUbVS/Qh+qbls7G4zAuznfCiqdctcKmUGPRP4sTTjWdUp -fjI+UTt1e8hncmr4RY7Oa9kUV/kDwzS5spUZZ+u0PczS3XKxOwNOleoH00dfc9vt -cxctHdPdDTndRi8Z4k3m931jIX7jB/Pyx8qeNYB3pj0k3ThktwMbAVLnAoGBANP4 -beI5zpbvtAdExJcuxx2mRDGF0lIdKC0bvQaeqM3Lwqnmc0Fz1dbP7KXDa+SdJWPd -res+NHPZoEPeEJuDTSngXOLNECZe4Ja9frn1TeY858vMJBwIkyc8zu+sgXxjQUM+ -TWUlTUhtXyybkRnxAEny4OT2TTgmXITJaKOmV1UVAoGAHaXSlo4YitB42rNYUXTf -dZ0U4H30Qj7+1YFeBjq5qI4GL1IgQsS4hyq1osmfTTFm593bJCunt7HfQbU/NhIs -W9P4ZXkYwgvCYxkw+JAnzNkGFO/mHQG1Ve1hFLiVIt3XuiRejoYdiTfbM02YmDKD -jKQvgbUk9SBSBaRrvLNJ8csCgYAYnrZEnGo+ZcEHRxl+ZdSCwRkSl3SCTRiphJtD -9ZGttYj6quWgKJAhzyyxZC1X9FivbMQSmrsE6bYPq+9J4MpJnuGrBh5mFocHeyMI -/lD5+QEDTsay6twMpqdydxrjE7Q01zuuD9MWIn33dGo6FR/vduJgNatqZipA0hPx -ThS+sQKBgQDh0+cVo1mfYiCkp3IQPB8QYiJ/g2/UBk6pH8ZZDZ+A5td6NveiWO1y -wTEUWkX2qyz9SLxWDGOhdKqxNrLCUSYSOV/5/JQEtBm6K50ArFtrY40JP/T/5KvM -tSK2ayFX1wQ3PuEmewAogy/20tWo80cr556AXA62Utl2PzLK30Db8w== ------END RSA PRIVATE KEY-----` +MIIBzjCCAXWgAwIBAgIRAPJoB177sV50qyfPpXwDL7gwCgYIKoZIzj0EAwIwRjEL +MAkGA1UEBhMCVUsxFTATBgNVBAoTDGNlcnQtbWFuYWdlcjEgMB4GA1UEAxMXY2Vy +dC1tYW5hZ2VyIHRlc3RpbmcgQ0EwIBcNMjMxMTE0MTMxMzE1WhgPMjEyMzEwMjEx +MzEzMTVaMEYxCzAJBgNVBAYTAlVLMRUwEwYDVQQKEwxjZXJ0LW1hbmFnZXIxIDAe +BgNVBAMTF2NlcnQtbWFuYWdlciB0ZXN0aW5nIENBMFkwEwYHKoZIzj0CAQYIKoZI +zj0DAQcDQgAE2ddhQLZa4xc+j8QnSc9rfTUk1LfBGFcsbl2qPK6kdW329tEQeg0+ +CnC5P5hccNsXSdKcTpwrP8xFLtQxPD1qkKNCMEAwDgYDVR0PAQH/BAQDAgKkMA8G +A1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFNrHReTxZ/Jf9AJJN1r5qcSS52X4MAoG +CCqGSM49BAMCA0cAMEQCIH9aAEUAX+G8tjZPML4kf84B5mESlUE6aRtjt2MT1TRd +AiAdUj4R5fZUMaqT8J2BmwFAisINxO38I805GUJ+pH3GSg== +-----END CERTIFICATE----- +` + +const rootKey = `-----BEGIN EC PRIVATE KEY----- +MHcCAQEEIJpxHkhfBgd6I8P03Ny3nN14uJESxJgb+RZRMpNbZwxmoAoGCCqGSM49 +AwEHoUQDQgAE2ddhQLZa4xc+j8QnSc9rfTUk1LfBGFcsbl2qPK6kdW329tEQeg0+ +CnC5P5hccNsXSdKcTpwrP8xFLtQxPD1qkA== +-----END EC PRIVATE KEY----- +` func newSigningKeypairSecret(name string) *corev1.Secret { return &corev1.Secret{ @@ -85,57 +96,66 @@ func newSigningKeypairSecret(name string) *corev1.Secret { } } +// issuer1Cert is a hardcoded issuer certificate. Its dumped value is below: +// +// Version: 3 (0x2) +// Serial Number: +// e9:8f:6f:02:16:60:5f:0a:9c:60:6e:e5:2c:c2:89:c4 +// Signature Algorithm: ecdsa-with-SHA256 +// Issuer: C = UK, O = cert-manager, CN = cert-manager testing CA +// Validity +// Not Before: Nov 14 13:13:20 2023 GMT +// Not After : Oct 21 13:13:20 2122 GMT +// Subject: C = UK, O = cert-manager, CN = cert-manager testing Issuer +// Subject Public Key Info: +// Public Key Algorithm: id-ecPublicKey +// Public-Key: (256 bit) +// pub: +// 04:10:ce:5a:a1:67:6d:56:50:9a:4f:a5:d3:fc:6a: +// 06:dd:80:0f:df:57:93:fc:e1:a3:01:c2:32:05:61: +// 7d:82:a5:61:96:a0:42:61:af:6f:df:c4:02:bf:21: +// a5:a7:75:ce:37:69:db:1d:6e:6a:cc:af:3a:e6:c2: +// e6:92:52:e4:f1 +// ASN1 OID: prime256v1 +// NIST CURVE: P-256 +// X509v3 extensions: +// X509v3 Key Usage: critical +// Digital Signature, Key Encipherment, Certificate Sign +// X509v3 Basic Constraints: critical +// CA:TRUE +// X509v3 Subject Key Identifier: +// C5:9C:69:C7:DB:59:72:5A:A7:53:44:66:FF:81:4E:89:BC:68:56:34 +// X509v3 Authority Key Identifier: +// DA:C7:45:E4:F1:67:F2:5F:F4:02:49:37:5A:F9:A9:C4:92:E7:65:F8 +// +// Signature Algorithm: ecdsa-with-SHA256 +// Signature Value: +// +// 30:45:02:20:16:53:d3:c3:0e:3e:35:23:08:e3:0b:c5:82:a3: +// ab:59:5c:2d:f2:d4:06:7c:85:11:3f:5b:0e:c0:e7:37:7a:2b: +// 02:21:00:ac:57:c5:a4:e4:42:93:31:03:4a:d2:20:de:da:f3: +// 40:af:46:52:df:e3:2f:1c:fc:e9:8c:3f:82:47:aa:c5:27 const issuer1Cert = `-----BEGIN CERTIFICATE----- -MIIDnjCCAoagAwIBAgIUCAJmM4rqnkj65/0sFRSIjXNlmGYwDQYJKoZIhvcNAQEL -BQAwUzELMAkGA1UEBhMCVUsxCzAJBgNVBAgTAk5BMRUwEwYDVQQKEwxjZXJ0LW1h -bmFnZXIxIDAeBgNVBAMTF2NlcnQtbWFuYWdlciB0ZXN0aW5nIENBMB4XDTE4MTEx -NTAwMDQwMFoXDTIzMTExNDAwMDQwMFowVzELMAkGA1UEBhMCVUsxCzAJBgNVBAgT -Ak5BMRUwEwYDVQQKEwxjZXJ0LW1hbmFnZXIxJDAiBgNVBAMTG2NlcnQtbWFuYWdl -ciB0ZXN0aW5nIElzc3VlcjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB -AKubAgcLJfXspsDNNR/TO+UUy0s9DE28w4OXs7pAppe7rtK1a531M9lGg+jZPryT -PER4HeobhIk7h1iTmcVHp1mDB3IFDfKL8jKNEnsHGTcn5xY1RkFihFPphBiyGwvY -S4nGi1NubxTA+kW0Pbcf3po2NWNdntAHaMcvMEkq+NdoSEK1HACHQ8QqtqfKUxMD -XMFDmJD21/4PM6iqhDw2HPe87FY7KKdYAsMV8KnT5DIGJ6UbuarTuMzXZq0a8/aW -sto/hrBJir+CQwmNIYg41G8m1CgUz0a3FYxtvLNZweeW9+SiVl0FCiajLws0HIW5 -4RTJ44Omr2/byIB+lmV63AMCAwEAAaNmMGQwDgYDVR0PAQH/BAQDAgGmMBIGA1Ud -EwEB/wQIMAYBAf8CAQMwHQYDVR0OBBYEFESJnTHvnJn8qIOb/JD+nw4o0yxnMB8G -A1UdIwQYMBaAFAba471n25CwNIl+rClUYSlKPOuJMA0GCSqGSIb3DQEBCwUAA4IB -AQBre0a1hD4T0W9E/yGhk6O8k11i63vhgIcMeN1/RMtgJRwIWIf3iKXAwAeIjkXZ -eGGSNWh8pC1wFvE9LIomhZLPSn+98FJ9dLfcaQXDOEyZM71OTsWQKS4NVNloHOxV -zujEujIIZ4caVbOlQWxf7lPydnXP+S7GsMU8vlOsU2RC9jN+yeuho+ZVguSC76ni -CG+k/Lzf46CMAZtRLdv9FPFttodBnodapOEgkhGwhyz/J6eLR1t9DWlxpQ1vk45H -dT3HDz1CNlF/5HzYpVBus553Z7SFh2x1umKfmTUWqmbFsslr2y4w2nkhyG2+jH+k -lh+Eve9i4q7YaO0EMlOOJMar +MIIB9DCCAZqgAwIBAgIRAOmPbwIWYF8KnGBu5SzCicQwCgYIKoZIzj0EAwIwRjEL +MAkGA1UEBhMCVUsxFTATBgNVBAoTDGNlcnQtbWFuYWdlcjEgMB4GA1UEAxMXY2Vy +dC1tYW5hZ2VyIHRlc3RpbmcgQ0EwIBcNMjMxMTE0MTMxMzIwWhgPMjEyMjEwMjEx +MzEzMjBaMEoxCzAJBgNVBAYTAlVLMRUwEwYDVQQKEwxjZXJ0LW1hbmFnZXIxJDAi +BgNVBAMTG2NlcnQtbWFuYWdlciB0ZXN0aW5nIElzc3VlcjBZMBMGByqGSM49AgEG +CCqGSM49AwEHA0IABBDOWqFnbVZQmk+l0/xqBt2AD99Xk/zhowHCMgVhfYKlYZag +QmGvb9/EAr8hpad1zjdp2x1uasyvOubC5pJS5PGjYzBhMA4GA1UdDwEB/wQEAwIC +pDAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTFnGnH21lyWqdTRGb/gU6JvGhW +NDAfBgNVHSMEGDAWgBTax0Xk8WfyX/QCSTda+anEkudl+DAKBggqhkjOPQQDAgNI +ADBFAiAWU9PDDj41IwjjC8WCo6tZXC3y1AZ8hRE/Ww7A5zd6KwIhAKxXxaTkQpMx +A0rSIN7a80CvRlLf4y8c/OmMP4JHqsUn -----END CERTIFICATE----- ` -const issuer1Key = `-----BEGIN RSA PRIVATE KEY----- -MIIEpQIBAAKCAQEAq5sCBwsl9eymwM01H9M75RTLSz0MTbzDg5ezukCml7uu0rVr -nfUz2UaD6Nk+vJM8RHgd6huEiTuHWJOZxUenWYMHcgUN8ovyMo0SewcZNyfnFjVG -QWKEU+mEGLIbC9hLicaLU25vFMD6RbQ9tx/emjY1Y12e0Adoxy8wSSr412hIQrUc -AIdDxCq2p8pTEwNcwUOYkPbX/g8zqKqEPDYc97zsVjsop1gCwxXwqdPkMgYnpRu5 -qtO4zNdmrRrz9pay2j+GsEmKv4JDCY0hiDjUbybUKBTPRrcVjG28s1nB55b35KJW -XQUKJqMvCzQchbnhFMnjg6avb9vIgH6WZXrcAwIDAQABAoIBAHm3VFTSn3YzCIOw -CYItPUpa2WbgQh3RSYvIyf3NZVwyDun9K/u5s7DkxyMdE9aFSDX4TJ+ELRl5U6KL -7oFzNUvUGC/TTfU/NeaNERKaElSAxPOHjfFKgzlRZBRwH6bjH5D1dlUS+07pIZrX -IP8GZ8lRscRs3vwGhVbiLYl4JVACydgyV/Th1yJYFEOXlmHV4Kk0ce3swsXL0NUb -BFQ53RULSxLVaYy4XXF3azSUdMkalDf8DxxeFtPUSW49zp6/iOArZTNCoiGavOHo -YvtnUXjt2QK64SdjFYMyCD8EcLlMTOUtAS10lw9NwUS3JMp3u79bO2uvRwJpT+IP -Hb0Sg8ECgYEAyi41EwEE6cwNVOAZxkOgv+ejhBjKuUrhzp0vwg3Uziuy6TZPJEoA -5e/8pFuvxbfU0lGUe6CkHdpSQPO7ifsTuxYxO/ZX8DqSaCwnRp+kJUyi7Jz3Ypfk -LsVg3TMW9Hmvntz8kPTN8DJMo6W7TC0m05L5pyfvM2BpBXqYIPNLInkCgYEA2Uk8 -mnA43ME+oaqLxcqgIE1+AXeg+voH17kiuO7hVWlprxJv/b6AAjm0nxcuLcdofKJT -JgaWrwyhI676q5T/lqQn/gdJ7rwz/83WnforW7WVza2XT+aDFcwNq07vHYoeCK6B -5RJFIY4Yuk4CORXeElYipz/VyCO2mUgJfHNDs1sCgYEAkS3lBqRwtsHDwPK7D1d4 -ktTu4eg7ihpvU0IkDSCJcxKGAljxM4nAY1yU+iCsczmyJORXzv5nWthuwB1Eyav1 -Wx5wdDJMq0Aj6ZHrEheIcxA43ddI/Q881yj8iVoqXZsTtOvSoPRo/NXhmpFjkSvK -+ZpMku9mIGpWf4ysuNx7U2ECgYEAlOk+IVFbht7g/4aT99+f0cOJ4ZOMvbPxAASf -KUJ9Jz3w8cye97VAoUXO5WDLgxAwKYpNlbfaOOlc3cmjfUfFygWCavOv1W8h6+Oz -e9zhLh7KJYUcN+PwXlXT4F1ePk5TuvtthgH5Yr+xbqzblSfJY6OoaBq1dk4TbAUU -izerZBUCgYEAn28gG04dByfcyY/crwpRLNVlaA0J93v5H9E/wlEiV1PhEYTdj2S8 -PLm9ur3V+kkBSarBur9+rRil0BHvVgC9K6kwMr60JcVT+bmZi0AbPOlPZsp9OPQf -YK5kMSMSbh4t9OUtadogDGI299P6Q9leaU65XRAar96wVsz8X/XdPPc= ------END RSA PRIVATE KEY-----` +const issuer1Key = `-----BEGIN EC PRIVATE KEY----- +MHcCAQEEIOgqbZ1Z5PVkxq4s89+CZaE5hwMNQiW9B1ldCwDFXaN9oAoGCCqGSM49 +AwEHoUQDQgAEEM5aoWdtVlCaT6XT/GoG3YAP31eT/OGjAcIyBWF9gqVhlqBCYa9v +38QCvyGlp3XON2nbHW5qzK865sLmklLk8Q== +-----END EC PRIVATE KEY----- +` func newSigningIssuer1KeypairSecret(name string) *corev1.Secret { return &corev1.Secret{ @@ -149,57 +169,66 @@ func newSigningIssuer1KeypairSecret(name string) *corev1.Secret { } } +// issuer2Cert is a hardcoded issuer certificate. Its dumped value is below: +// +// Version: 3 (0x2) +// Serial Number: +// ad:3c:69:dd:89:4a:a6:5c:e0:12:9e:1b:a2:3a:28:d8 +// Signature Algorithm: ecdsa-with-SHA256 +// Issuer: C = UK, O = cert-manager, CN = cert-manager testing Issuer +// Validity +// Not Before: Nov 14 13:13:40 2023 GMT +// Not After : Oct 21 13:13:40 2121 GMT +// Subject: C = UK, O = cert-manager, CN = cert-manager testing Issuer Level 2 +// Subject Public Key Info: +// Public Key Algorithm: id-ecPublicKey +// Public-Key: (256 bit) +// pub: +// 04:dc:8e:15:e3:e7:cc:bb:18:37:c9:bc:d3:73:a6: +// a9:e6:6f:5d:b1:ea:32:45:af:7f:3d:7e:9a:ff:5a: +// c6:6e:c2:79:fd:8d:57:c8:25:47:9d:16:e1:06:4e: +// 26:2c:01:e0:df:ac:f6:c8:ef:06:72:51:9e:55:88: +// 7d:f1:0f:d4:e7 +// ASN1 OID: prime256v1 +// NIST CURVE: P-256 +// X509v3 extensions: +// X509v3 Key Usage: critical +// Digital Signature, Key Encipherment, Certificate Sign +// X509v3 Basic Constraints: critical +// CA:TRUE +// X509v3 Subject Key Identifier: +// 4D:6E:AA:29:39:75:2E:A1:E0:6A:4E:F2:F4:E4:07:B4:99:D5:23:8B +// X509v3 Authority Key Identifier: +// C5:9C:69:C7:DB:59:72:5A:A7:53:44:66:FF:81:4E:89:BC:68:56:34 +// +// Signature Algorithm: ecdsa-with-SHA256 +// Signature Value: +// +// 30:44:02:20:4a:78:8d:cb:56:b9:12:d1:0b:dd:bd:77:f1:28: +// 14:71:b3:e1:6e:30:a6:27:73:ba:de:c9:a8:53:9e:c3:43:cb: +// 02:20:68:92:6b:13:72:35:18:70:3e:66:cb:e1:ca:b5:47:0f: +// d9:16:5e:1a:00:2d:58:61:a4:05:29:08:a1:ea:c8:87 const issuer2Cert = `-----BEGIN CERTIFICATE----- -MIIDqjCCApKgAwIBAgIUHqm61uyYt2ICGRcZnBSjYaPonuowDQYJKoZIhvcNAQEL -BQAwVzELMAkGA1UEBhMCVUsxCzAJBgNVBAgTAk5BMRUwEwYDVQQKEwxjZXJ0LW1h -bmFnZXIxJDAiBgNVBAMTG2NlcnQtbWFuYWdlciB0ZXN0aW5nIElzc3VlcjAeFw0x -ODExMTUwMDA0MDBaFw0yMzExMTQwMDA0MDBaMF8xCzAJBgNVBAYTAlVLMQswCQYD -VQQIEwJOQTEVMBMGA1UEChMMY2VydC1tYW5hZ2VyMSwwKgYDVQQDEyNjZXJ0LW1h -bmFnZXIgdGVzdGluZyBJc3N1ZXIgTGV2ZWwgMjCCASIwDQYJKoZIhvcNAQEBBQAD -ggEPADCCAQoCggEBAMRm1cYCcHmA7UtF3vISLiob5eh234njNp33nkFWjDsE9Zgi -CIxVb9FBd+rkKn0xkPMke79lmr1kVkmjpAZ0Y0w/IDSEX8JMJvtyuAoS79r0W+rn -dEG5GzJGLswOK0gsvGyl4i8E9a5itUkRa01OETFIiay0iwNMUYnIflm8G/Uu2Jhr -/HSyWND+KLzX5gMDsiv4HdtCsNHstdMwBr4dkiCzpi+N/b2KTggmY84KeVQVpmRc -IVoVr06uc3YTa2mlqrw3qX16d5r9DLYrrq1UT3HXB0PJvvsIjJN8eqKk33Mcbinj -VR1Ywg9QYaJHpBPPxLL0AzNG29SebRLtGvKexoUCAwEAAaNmMGQwDgYDVR0PAQH/ -BAQDAgGmMBIGA1UdEwEB/wQIMAYBAf8CAQMwHQYDVR0OBBYEFHp3C+Se1LZMcQ0B -0iycJLvwqo9lMB8GA1UdIwQYMBaAFESJnTHvnJn8qIOb/JD+nw4o0yxnMA0GCSqG -SIb3DQEBCwUAA4IBAQA/lnvr+GnMJDA+Z7MEMRAcqdIScO38LVQNO340jFMcMkmW -YTnyNoEvI4fnCon9Oz2FsFcZp90Gniu01lDLyzR+1SsfFf6zwqGVUV29hidR6BvD -VGLM6SMnbgXUd+RPvAIrHU3BuSF2sRPiw7YqzgNVZQ2dUF+Q+R+Onu5i47CwVFOd -6Dd7xr5+ECaHGyuIH/RsXLvB+2reJ5dEl3oBxiyyzY1oOkt6y4HrB8n90JWPmXIf -9oQ8T+p3PbsFkz667nbVnVCkdAKtU/ZX09S1jGVKsOKszA1qhxFcMy+wkkyHq4Jj -v+q/VgVxL5HzEw4zyKS9Y2lcwhCicMrLKIGt91fQ +MIIB/zCCAaagAwIBAgIRAK08ad2JSqZc4BKeG6I6KNgwCgYIKoZIzj0EAwIwSjEL +MAkGA1UEBhMCVUsxFTATBgNVBAoTDGNlcnQtbWFuYWdlcjEkMCIGA1UEAxMbY2Vy +dC1tYW5hZ2VyIHRlc3RpbmcgSXNzdWVyMCAXDTIzMTExNDEzMTM0MFoYDzIxMjEx +MDIxMTMxMzQwWjBSMQswCQYDVQQGEwJVSzEVMBMGA1UEChMMY2VydC1tYW5hZ2Vy +MSwwKgYDVQQDEyNjZXJ0LW1hbmFnZXIgdGVzdGluZyBJc3N1ZXIgTGV2ZWwgMjBZ +MBMGByqGSM49AgEGCCqGSM49AwEHA0IABNyOFePnzLsYN8m803OmqeZvXbHqMkWv +fz1+mv9axm7Cef2NV8glR50W4QZOJiwB4N+s9sjvBnJRnlWIffEP1OejYzBhMA4G +A1UdDwEB/wQEAwICpDAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRNbqopOXUu +oeBqTvL05Ae0mdUjizAfBgNVHSMEGDAWgBTFnGnH21lyWqdTRGb/gU6JvGhWNDAK +BggqhkjOPQQDAgNHADBEAiBKeI3LVrkS0QvdvXfxKBRxs+FuMKYnc7reyahTnsND +ywIgaJJrE3I1GHA+ZsvhyrVHD9kWXhoALVhhpAUpCKHqyIc= -----END CERTIFICATE----- ` -const issuer2Key = `-----BEGIN RSA PRIVATE KEY----- -MIIEowIBAAKCAQEAxGbVxgJweYDtS0Xe8hIuKhvl6HbfieM2nfeeQVaMOwT1mCII -jFVv0UF36uQqfTGQ8yR7v2WavWRWSaOkBnRjTD8gNIRfwkwm+3K4ChLv2vRb6ud0 -QbkbMkYuzA4rSCy8bKXiLwT1rmK1SRFrTU4RMUiJrLSLA0xRich+Wbwb9S7YmGv8 -dLJY0P4ovNfmAwOyK/gd20Kw0ey10zAGvh2SILOmL439vYpOCCZjzgp5VBWmZFwh -WhWvTq5zdhNraaWqvDepfXp3mv0MtiuurVRPcdcHQ8m++wiMk3x6oqTfcxxuKeNV -HVjCD1BhokekE8/EsvQDM0bb1J5tEu0a8p7GhQIDAQABAoIBAFwCzV3RoL3bn8/m -8Pa5e7UwkrogjsM7lkfVTOfRUysHPMPEFfsgv5zqLfL2Z811HjI6wlq9kAvwaNhg -+KQpfKeo3z6bUX1mTdD5Qq09h+8tEa7wNi/gN5SK+ruQW8iZZMEFyfw7N5o2FjYg -GgQCcd2D3TPy9TlbVMvXCRKjJPns4PvWnjcR6YryPCluhnm6t0UEdusAj5baENU5 -95XG3e+7ZWzz4uejY778pyV/4yCfMXG9HZInkw9Uj3aNibiP/oKyF8Z0m1tAheLp -SfLH/KxC8sWW/Cn3YFAvq+3fSH3ezeaFNdQFi8L0uGA9h9ucZmKaT5jI1bM9Mj55 -Vrsg/wECgYEA7rCQ/NFLtQ6PZNSApxRdWG+67mDrWMuaHho9KB+g0vIzGoxj2+DS -iVlk4F1zVjZ5S8yjSmBm2pxF4ornUdQUs5+iKHJqeweSQenZ3Ylx10rhACfUWhZ+ -Zo/mrG30MJs2ceOaYJww1zrcjI3ktFwpZlX95J/e26gGqY8GKA8KaEECgYEA0qUp -3eWvwiTn2ztKEHZ06jNoPB1E3tAA939+W1Cy5VTDH2ZJYDE6lELTgW/7PuS6Auty -cJur3nyIJMQkb2GBqh8jgxb7huDpOkf8kAdPoD9PnmWTisF5XKO5Uv3O2t/xKQNl -pKAC9P1au3uCz8HA2ZbyLqiuXE7SKsIqQmMtbUUCgYArkAwWKDiyBcND+si0NbJH -prSuNwAdB6PMJKvOu98FQPD0wnSjN6gVKzyO+l9Hd8+xdtrCg0+iTG0wyHspYxSY -J+VXjnJCnAIkh4KcvS4Kxf7EoYBPJNXS8CaAh9zOVjWcmZaeVUNQtMx11pvMExn3 -NHCPHmJ1Inh8z76m5v/WQQKBgEeQFyYs10ZU9XQ0s1fedp/ucRYjN3efIQT0ioAJ -bY2d+2BahskoUGd4QJTz716RpGRDizCYoo5GrpYXEO3KKZwbUhxCHZfYJ0RGmpZv -9WxStgDxL2vviQShFuAMHE+dzzeI0OpZ9kc3H7EcJ/ffMl55+rNBWWNA4APozSSa -vx8lAoGBAODUjD1S1w/l+OTZWqo+bUvpC58CSioZ+gvNi4KE0h+1ZgLgE1RivQOM -UxwyspRQp2exnQ3hvCpzjhx+ji/FlhK86lspGjyZqTd+ifa/tO51+tvU217/XDtx -JypkAFhZ398YzhuqsRbFNMFnxA6QT+YFsqjT+R0vSFM8n2qptJHB ------END RSA PRIVATE KEY-----` +const issuer2Key = `-----BEGIN EC PRIVATE KEY----- +MHcCAQEEIKAcZcHAM0aunfX5bZcTGW6p5FR0PCH+mJT7R5SgKFaOoAoGCCqGSM49 +AwEHoUQDQgAE3I4V4+fMuxg3ybzTc6ap5m9dseoyRa9/PX6a/1rGbsJ5/Y1XyCVH +nRbhBk4mLAHg36z2yO8GclGeVYh98Q/U5w== +-----END EC PRIVATE KEY----- +` func newSigningIssuer2KeypairSecret(name string) *corev1.Secret { return &corev1.Secret{ @@ -212,3 +241,106 @@ func newSigningIssuer2KeypairSecret(name string) *corev1.Secret { }, } } + +// YAML for creating the hardcoded certificates in this file: + +/* +apiVersion: cert-manager.io/v1 +kind: ClusterIssuer +metadata: + name: selfsigned-issuer +spec: + selfSigned: {} + +--- + +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + name: root-cert +spec: + isCA: true + commonName: cert-manager testing CA + secretName: root-secret + duration: 876000h # 365 days * 100 years + subject: + organizations: + - cert-manager + countries: + - UK + privateKey: + algorithm: ECDSA + size: 256 + issuerRef: + name: selfsigned-issuer + kind: ClusterIssuer + group: cert-manager.io + +--- + +apiVersion: cert-manager.io/v1 +kind: Issuer +metadata: + name: root-ca-issuer +spec: + ca: + secretName: root-secret + +--- + +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + name: intermediate-cert-1 +spec: + isCA: true + commonName: cert-manager testing Issuer + secretName: intermediate-cert-1-secret + duration: 867240h # 365 days * 99 years + subject: + organizations: + - cert-manager + countries: + - UK + privateKey: + algorithm: ECDSA + size: 256 + issuerRef: + name: root-ca-issuer + kind: Issuer + group: cert-manager.io + +--- + +apiVersion: cert-manager.io/v1 +kind: Issuer +metadata: + name: intermediate-cert-1-issuer +spec: + ca: + secretName: intermediate-cert-1-secret + +--- + +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + name: intermediate-cert-2 +spec: + isCA: true + commonName: cert-manager testing Issuer Level 2 + secretName: intermediate-cert-2-secret + duration: 858480h # 365 days * 98 years + subject: + organizations: + - cert-manager + countries: + - UK + privateKey: + algorithm: ECDSA + size: 256 + issuerRef: + name: intermediate-cert-1-issuer + kind: Issuer + group: cert-manager.io +*/ diff --git a/test/e2e/suite/issuers/ca/issuer.go b/test/e2e/suite/issuers/ca/issuer.go index 3bda9180948..3a1207f898b 100644 --- a/test/e2e/suite/issuers/ca/issuer.go +++ b/test/e2e/suite/issuers/ca/issuer.go @@ -19,15 +19,16 @@ package ca import ( "context" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "github.com/cert-manager/cert-manager/e2e-tests/framework" + "github.com/cert-manager/cert-manager/e2e-tests/util" v1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" - "github.com/cert-manager/cert-manager/test/e2e/framework" - "github.com/cert-manager/cert-manager/test/e2e/util" "github.com/cert-manager/cert-manager/test/unit/gen" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" ) var _ = framework.CertManagerDescribe("CA Issuer", func() { @@ -36,26 +37,27 @@ var _ = framework.CertManagerDescribe("CA Issuer", func() { issuerName := "test-ca-issuer" secretName := "ca-issuer-signing-keypair" - BeforeEach(func() { + BeforeEach(func(testingCtx context.Context) { By("Creating a signing keypair fixture") - _, err := f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Create(context.TODO(), newSigningKeypairSecret(secretName), metav1.CreateOptions{}) + _, err := f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Create(testingCtx, newSigningKeypairSecret(secretName), metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred()) }) - AfterEach(func() { + AfterEach(func(testingCtx context.Context) { By("Cleaning up") - f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Delete(context.TODO(), secretName, metav1.DeleteOptions{}) + err := f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Delete(testingCtx, secretName, metav1.DeleteOptions{}) + Expect(err).NotTo(HaveOccurred()) }) - It("should generate a signing keypair", func() { + It("should generate a signing keypair", func(testingCtx context.Context) { By("Creating an Issuer") issuer := gen.Issuer(issuerName, gen.SetIssuerNamespace(f.Namespace.Name), gen.SetIssuerCASecretName(secretName)) - _, err := f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Create(context.TODO(), issuer, metav1.CreateOptions{}) + _, err := f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Create(testingCtx, issuer, metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred()) By("Waiting for Issuer to become Ready") - err = util.WaitForIssuerCondition(f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name), + err = util.WaitForIssuerCondition(testingCtx, f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name), issuerName, v1.IssuerCondition{ Type: v1.IssuerConditionReady, diff --git a/test/e2e/suite/issuers/doc.go b/test/e2e/suite/issuers/doc.go index cd0368092f6..990f084359a 100644 --- a/test/e2e/suite/issuers/doc.go +++ b/test/e2e/suite/issuers/doc.go @@ -17,9 +17,9 @@ limitations under the License. package suite import ( - _ "github.com/cert-manager/cert-manager/test/e2e/suite/issuers/acme" - _ "github.com/cert-manager/cert-manager/test/e2e/suite/issuers/ca" - _ "github.com/cert-manager/cert-manager/test/e2e/suite/issuers/selfsigned" - _ "github.com/cert-manager/cert-manager/test/e2e/suite/issuers/vault" - _ "github.com/cert-manager/cert-manager/test/e2e/suite/issuers/venafi" + _ "github.com/cert-manager/cert-manager/e2e-tests/suite/issuers/acme" + _ "github.com/cert-manager/cert-manager/e2e-tests/suite/issuers/ca" + _ "github.com/cert-manager/cert-manager/e2e-tests/suite/issuers/selfsigned" + _ "github.com/cert-manager/cert-manager/e2e-tests/suite/issuers/vault" + _ "github.com/cert-manager/cert-manager/e2e-tests/suite/issuers/venafi" ) diff --git a/test/e2e/suite/issuers/selfsigned/certificate.go b/test/e2e/suite/issuers/selfsigned/certificate.go index 422cc5aada9..84ce28a88fc 100644 --- a/test/e2e/suite/issuers/selfsigned/certificate.go +++ b/test/e2e/suite/issuers/selfsigned/certificate.go @@ -21,15 +21,17 @@ import ( "fmt" "time" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "github.com/cert-manager/cert-manager/e2e-tests/framework" + "github.com/cert-manager/cert-manager/e2e-tests/framework/helper/validation/certificates" + "github.com/cert-manager/cert-manager/e2e-tests/util" v1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" - "github.com/cert-manager/cert-manager/test/e2e/framework" - "github.com/cert-manager/cert-manager/test/e2e/util" "github.com/cert-manager/cert-manager/test/unit/gen" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" ) var _ = framework.CertManagerDescribe("Self Signed Certificate", func() { @@ -39,7 +41,7 @@ var _ = framework.CertManagerDescribe("Self Signed Certificate", func() { certificateName := "test-selfsigned-certificate" certificateSecretName := "test-selfsigned-certificate" - It("should generate a signed keypair", func() { + It("should generate a signed keypair", func(testingCtx context.Context) { By("Creating an Issuer") certClient := f.CertManagerClientSet.CertmanagerV1().Certificates(f.Namespace.Name) @@ -47,10 +49,10 @@ var _ = framework.CertManagerDescribe("Self Signed Certificate", func() { issuer := gen.Issuer(issuerName, gen.SetIssuerNamespace(f.Namespace.Name), gen.SetIssuerSelfSigned(v1.SelfSignedIssuer{})) - _, err := f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Create(context.TODO(), issuer, metav1.CreateOptions{}) + _, err := f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Create(testingCtx, issuer, metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred()) By("Waiting for Issuer to become Ready") - err = util.WaitForIssuerCondition(f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name), + err = util.WaitForIssuerCondition(testingCtx, f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name), issuerName, v1.IssuerCondition{ Type: v1.IssuerConditionReady, @@ -58,10 +60,20 @@ var _ = framework.CertManagerDescribe("Self Signed Certificate", func() { }) Expect(err).NotTo(HaveOccurred()) By("Creating a Certificate") - cert, err := certClient.Create(context.TODO(), util.NewCertManagerBasicCertificate(certificateName, certificateSecretName, issuerName, v1.IssuerKind, nil, nil), metav1.CreateOptions{}) + cert := gen.Certificate(certificateName, + gen.SetCertificateNamespace(f.Namespace.Name), + gen.SetCertificateSecretName(certificateSecretName), + gen.SetCertificateIssuer(cmmeta.IssuerReference{ + Name: issuerName, + Kind: v1.IssuerKind, + }), + gen.SetCertificateCommonName("test.domain.com"), + gen.SetCertificateOrganization("test-org"), + ) + cert, err = certClient.Create(testingCtx, cert, metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred()) By("Waiting for the Certificate to be issued...") - cert, err = f.Helper().WaitForCertificateReadyAndDoneIssuing(cert, time.Minute*5) + cert, err = f.Helper().WaitForCertificateReadyAndDoneIssuing(testingCtx, cert, time.Minute*5) Expect(err).NotTo(HaveOccurred()) By("Validating the issued Certificate...") @@ -89,8 +101,7 @@ var _ = framework.CertManagerDescribe("Self Signed Certificate", func() { }, } for _, v := range cases { - v := v - It("should generate a signed keypair valid for "+v.label, func() { + It("should generate a signed keypair valid for "+v.label, func(testingCtx context.Context) { certClient := f.CertManagerClientSet.CertmanagerV1().Certificates(f.Namespace.Name) By("Creating an Issuer") @@ -98,10 +109,10 @@ var _ = framework.CertManagerDescribe("Self Signed Certificate", func() { issuer := gen.Issuer(issuerDurationName, gen.SetIssuerNamespace(f.Namespace.Name), gen.SetIssuerSelfSigned(v1.SelfSignedIssuer{})) - _, err := f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Create(context.TODO(), issuer, metav1.CreateOptions{}) + _, err := f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Create(testingCtx, issuer, metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred()) By("Waiting for Issuer to become Ready") - err = util.WaitForIssuerCondition(f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name), + err = util.WaitForIssuerCondition(testingCtx, f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name), issuerDurationName, v1.IssuerCondition{ Type: v1.IssuerConditionReady, @@ -110,21 +121,34 @@ var _ = framework.CertManagerDescribe("Self Signed Certificate", func() { Expect(err).NotTo(HaveOccurred()) By("Creating a Certificate") - cert, err := certClient.Create(context.TODO(), util.NewCertManagerBasicCertificate(certificateName, certificateSecretName, issuerDurationName, v1.IssuerKind, v.inputDuration, v.inputRenewBefore), metav1.CreateOptions{}) + cert := gen.Certificate(certificateName, + gen.SetCertificateNamespace(f.Namespace.Name), + gen.SetCertificateSecretName(certificateSecretName), + gen.SetCertificateIssuer(cmmeta.IssuerReference{ + Name: issuerDurationName, + Kind: v1.IssuerKind, + }), + gen.SetCertificateDuration(v.inputDuration), + gen.SetCertificateRenewBefore(v.inputRenewBefore), + gen.SetCertificateCommonName("test.domain.com"), + gen.SetCertificateOrganization("test-org"), + ) + cert, err = certClient.Create(testingCtx, cert, metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred()) By("Waiting for the Certificate to be issued...") - cert, err = f.Helper().WaitForCertificateReadyAndDoneIssuing(cert, time.Minute*5) + cert, err = f.Helper().WaitForCertificateReadyAndDoneIssuing(testingCtx, cert, time.Minute*5) Expect(err).NotTo(HaveOccurred()) By("Validating the issued Certificate...") err = f.Helper().ValidateCertificate(cert) Expect(err).NotTo(HaveOccurred()) - f.CertificateDurationValid(cert, v.expectedDuration, 0) + err = f.Helper().ValidateCertificate(cert, certificates.ExpectDuration(v.expectedDuration, 0)) + Expect(err).NotTo(HaveOccurred()) }) } - It("should correctly encode a certificate's private key based on the key encoding", func() { + It("should correctly encode a certificate's private key based on the key encoding", func(testingCtx context.Context) { By("Creating an Issuer") certClient := f.CertManagerClientSet.CertmanagerV1().Certificates(f.Namespace.Name) @@ -132,18 +156,26 @@ var _ = framework.CertManagerDescribe("Self Signed Certificate", func() { issuer := gen.Issuer(issuerName, gen.SetIssuerNamespace(f.Namespace.Name), gen.SetIssuerSelfSigned(v1.SelfSignedIssuer{})) - _, err := f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Create(context.TODO(), issuer, metav1.CreateOptions{}) + _, err := f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Create(testingCtx, issuer, metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred()) - cert := util.NewCertManagerBasicCertificate(certificateName, certificateSecretName, issuerName, v1.IssuerKind, nil, nil) - cert.Spec.PrivateKey.Encoding = v1.PKCS8 - By("Creating a Certificate") - cert, err = certClient.Create(context.TODO(), cert, metav1.CreateOptions{}) + cert := gen.Certificate(certificateName, + gen.SetCertificateNamespace(f.Namespace.Name), + gen.SetCertificateSecretName(certificateSecretName), + gen.SetCertificateIssuer(cmmeta.IssuerReference{ + Name: issuerName, + Kind: v1.IssuerKind, + }), + gen.SetCertificateCommonName("test.domain.com"), + gen.SetCertificateOrganization("test-org"), + gen.SetCertificateKeyEncoding(v1.PKCS8), + ) + cert, err = certClient.Create(testingCtx, cert, metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred()) By("Waiting for the Certificate to be issued...") - cert, err = f.Helper().WaitForCertificateReadyAndDoneIssuing(cert, time.Minute*5) + cert, err = f.Helper().WaitForCertificateReadyAndDoneIssuing(testingCtx, cert, time.Minute*5) Expect(err).NotTo(HaveOccurred()) By("Validating the issued Certificate...") diff --git a/test/e2e/suite/issuers/selfsigned/certificaterequest.go b/test/e2e/suite/issuers/selfsigned/certificaterequest.go index 159709c782c..59c77dc021f 100644 --- a/test/e2e/suite/issuers/selfsigned/certificaterequest.go +++ b/test/e2e/suite/issuers/selfsigned/certificaterequest.go @@ -20,16 +20,19 @@ import ( "context" "time" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "github.com/cert-manager/cert-manager/e2e-tests/framework" + "github.com/cert-manager/cert-manager/e2e-tests/framework/helper/validation/certificaterequests" + "github.com/cert-manager/cert-manager/e2e-tests/util" "github.com/cert-manager/cert-manager/pkg/apis/certmanager" v1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" - "github.com/cert-manager/cert-manager/test/e2e/framework" - "github.com/cert-manager/cert-manager/test/e2e/util" "github.com/cert-manager/cert-manager/test/unit/gen" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" ) var _ = framework.CertManagerDescribe("SelfSigned CertificateRequest", func() { @@ -41,15 +44,15 @@ var _ = framework.CertManagerDescribe("SelfSigned CertificateRequest", func() { certificateRequestName := "test-selfsigned-certificaterequest" certificateRequestSecretName := "test-selfsigned-private-key" - JustBeforeEach(func() { + JustBeforeEach(func(testingCtx context.Context) { By("Creating an Issuer") issuer := gen.Issuer(issuerName, gen.SetIssuerNamespace(f.Namespace.Name), gen.SetIssuerSelfSigned(v1.SelfSignedIssuer{})) - _, err := f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Create(context.TODO(), issuer, metav1.CreateOptions{}) + _, err := f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Create(testingCtx, issuer, metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred()) By("Waiting for Issuer to become Ready") - err = util.WaitForIssuerCondition(f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name), + err = util.WaitForIssuerCondition(testingCtx, f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name), issuerName, v1.IssuerCondition{ Type: v1.IssuerConditionReady, @@ -61,7 +64,7 @@ var _ = framework.CertManagerDescribe("SelfSigned CertificateRequest", func() { basicCR = gen.CertificateRequest(certificateRequestName, gen.SetCertificateRequestNamespace(f.Namespace.Name), gen.SetCertificateRequestIsCA(true), - gen.SetCertificateRequestIssuer(cmmeta.ObjectReference{ + gen.SetCertificateRequestIssuer(cmmeta.IssuerReference{ Name: issuerName, Group: certmanager.GroupName, Kind: "Issuer", @@ -72,41 +75,43 @@ var _ = framework.CertManagerDescribe("SelfSigned CertificateRequest", func() { ) }) - AfterEach(func() { + AfterEach(func(testingCtx context.Context) { By("Cleaning up") - f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Delete(context.TODO(), certificateRequestSecretName, metav1.DeleteOptions{}) - f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Delete(context.TODO(), issuerName, metav1.DeleteOptions{}) + err := f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Delete(testingCtx, certificateRequestSecretName, metav1.DeleteOptions{}) + Expect(err).NotTo(HaveOccurred()) + err = f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Delete(testingCtx, issuerName, metav1.DeleteOptions{}) + Expect(err).NotTo(HaveOccurred()) }) Context("Self Signed and private key", func() { - BeforeEach(func() { + BeforeEach(func(testingCtx context.Context) { By("Creating a signing keypair fixture") - _, err := f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Create(context.TODO(), newPrivateKeySecret( + _, err := f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Create(testingCtx, newPrivateKeySecret( certificateRequestSecretName, f.Namespace.Name, rootRSAKey), metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred()) }) - It("should generate a valid certificate from CSR backed by a RSA key", func() { + It("should generate a valid certificate from CSR backed by a RSA key", func(testingCtx context.Context) { crClient := f.CertManagerClientSet.CertmanagerV1().CertificateRequests(f.Namespace.Name) By("Creating a CertificateRequest") csr, err := generateRSACSR() Expect(err).NotTo(HaveOccurred()) - _, err = crClient.Create(context.TODO(), gen.CertificateRequestFrom(basicCR, + _, err = crClient.Create(testingCtx, gen.CertificateRequestFrom(basicCR, gen.SetCertificateRequestCSR(csr), ), metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred()) By("Verifying the Certificate is valid") - err = h.WaitCertificateRequestIssuedValid(f.Namespace.Name, certificateRequestName, time.Second*30, rootRSAKeySigner) + err = h.WaitCertificateRequestIssuedValid(testingCtx, f.Namespace.Name, certificateRequestName, time.Second*30, rootRSAKeySigner) Expect(err).NotTo(HaveOccurred()) }) - It("should be able to obtain an ECDSA Certificate backed by a ECSDA key", func() { + It("should be able to obtain an ECDSA Certificate backed by a ECDSA key", func(testingCtx context.Context) { // Replace RSA key secret with ECDSA one - _, err := f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Update(context.TODO(), newPrivateKeySecret( + _, err := f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Update(testingCtx, newPrivateKeySecret( certificateRequestSecretName, f.Namespace.Name, rootECKey), metav1.UpdateOptions{}) Expect(err).NotTo(HaveOccurred()) @@ -115,19 +120,19 @@ var _ = framework.CertManagerDescribe("SelfSigned CertificateRequest", func() { csr, err := generateECCSR() Expect(err).NotTo(HaveOccurred()) - _, err = crClient.Create(context.TODO(), gen.CertificateRequestFrom(basicCR, + _, err = crClient.Create(testingCtx, gen.CertificateRequestFrom(basicCR, gen.SetCertificateRequestCSR(csr), ), metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred()) By("Verifying the Certificate is valid") - err = h.WaitCertificateRequestIssuedValid(f.Namespace.Name, certificateRequestName, time.Second*30, rootECKeySigner) + err = h.WaitCertificateRequestIssuedValid(testingCtx, f.Namespace.Name, certificateRequestName, time.Second*30, rootECKeySigner) Expect(err).NotTo(HaveOccurred()) }) - It("should be able to obtain an Ed25519 Certificate backed by a Ed25519 key", func() { + It("should be able to obtain an Ed25519 Certificate backed by a Ed25519 key", func(testingCtx context.Context) { // Replace previous key secret with Ed25519 one - _, err := f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Update(context.TODO(), newPrivateKeySecret( + _, err := f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Update(testingCtx, newPrivateKeySecret( certificateRequestSecretName, f.Namespace.Name, rootEd25519Key), metav1.UpdateOptions{}) Expect(err).NotTo(HaveOccurred()) @@ -136,13 +141,13 @@ var _ = framework.CertManagerDescribe("SelfSigned CertificateRequest", func() { csr, err := generateEd25519CSR() Expect(err).NotTo(HaveOccurred()) - _, err = crClient.Create(context.TODO(), gen.CertificateRequestFrom(basicCR, + _, err = crClient.Create(testingCtx, gen.CertificateRequestFrom(basicCR, gen.SetCertificateRequestCSR(csr), ), metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred()) By("Verifying the Certificate is valid") - err = h.WaitCertificateRequestIssuedValid(f.Namespace.Name, certificateRequestName, time.Second*30, rootEd25519Signer) + err = h.WaitCertificateRequestIssuedValid(testingCtx, f.Namespace.Name, certificateRequestName, time.Second*30, rootEd25519Signer) Expect(err).NotTo(HaveOccurred()) }) @@ -163,26 +168,27 @@ var _ = framework.CertManagerDescribe("SelfSigned CertificateRequest", func() { }, } for _, v := range cases { - v := v // capture range variable - It("should generate a signed certificate valid for "+v.label, func() { + It("should generate a signed certificate valid for "+v.label, func(testingCtx context.Context) { crClient := f.CertManagerClientSet.CertmanagerV1().CertificateRequests(f.Namespace.Name) By("Creating a CertificateRequest") csr, err := generateRSACSR() Expect(err).NotTo(HaveOccurred()) - _, err = crClient.Create(context.TODO(), gen.CertificateRequestFrom(basicCR, + _, err = crClient.Create(testingCtx, gen.CertificateRequestFrom(basicCR, gen.SetCertificateRequestCSR(csr), gen.SetCertificateRequestDuration(v.inputDuration), ), metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred()) By("Verifying the CertificateRequest is valid") - err = h.WaitCertificateRequestIssuedValid(f.Namespace.Name, certificateRequestName, time.Second*30, rootRSAKeySigner) + err = h.WaitCertificateRequestIssuedValid(testingCtx, f.Namespace.Name, certificateRequestName, time.Second*30, rootRSAKeySigner) Expect(err).NotTo(HaveOccurred()) - cr, err := crClient.Get(context.TODO(), certificateRequestName, metav1.GetOptions{}) + err = h.ValidateCertificateRequest(types.NamespacedName{ + Namespace: f.Namespace.Name, + Name: certificateRequestName, + }, rootRSAKeySigner, certificaterequests.ExpectDuration(v.expectedDuration, 0)) Expect(err).NotTo(HaveOccurred()) - f.CertificateRequestDurationValid(cr, v.expectedDuration, 0) }) } }) diff --git a/test/e2e/suite/issuers/selfsigned/fixtures.go b/test/e2e/suite/issuers/selfsigned/fixtures.go index 2b22f51c75b..31dd4d3fd88 100644 --- a/test/e2e/suite/issuers/selfsigned/fixtures.go +++ b/test/e2e/suite/issuers/selfsigned/fixtures.go @@ -18,18 +18,12 @@ package selfsigned import ( "crypto" - "crypto/rand" - "crypto/x509" - "crypto/x509/pkix" - "encoding/asn1" - "encoding/pem" - "net" - "net/url" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "github.com/cert-manager/cert-manager/pkg/util/pki" + "github.com/cert-manager/cert-manager/test/unit/gen" ) var rootRSAKeySigner, rootECKeySigner, rootEd25519Signer crypto.Signer @@ -107,7 +101,7 @@ func newPrivateKeySecret(name, namespace string, keyData []byte) *corev1.Secret } func generateRSACSR() ([]byte, error) { - csr, err := generateCSR(rootRSAKeySigner, x509.SHA256WithRSA) + csr, err := generateCSR(rootRSAKeySigner) if err != nil { return nil, err } @@ -116,7 +110,7 @@ func generateRSACSR() ([]byte, error) { } func generateECCSR() ([]byte, error) { - csr, err := generateCSR(rootECKeySigner, x509.ECDSAWithSHA256) + csr, err := generateCSR(rootECKeySigner) if err != nil { return nil, err } @@ -125,7 +119,7 @@ func generateECCSR() ([]byte, error) { } func generateEd25519CSR() ([]byte, error) { - csr, err := generateCSR(rootEd25519Signer, x509.PureEd25519) + csr, err := generateCSR(rootEd25519Signer) if err != nil { return nil, err } @@ -133,40 +127,16 @@ func generateEd25519CSR() ([]byte, error) { return csr, nil } -func generateCSR(privateKey crypto.Signer, alg x509.SignatureAlgorithm) ([]byte, error) { - var uris []*url.URL - for _, uri := range []string{ - "spiffe://foo.foo.example.net", - "spiffe://foo.bar.example.net", - } { - parsed, err := url.Parse(uri) - if err != nil { - return nil, err - } - uris = append(uris, parsed) - } - - asn1Subj, _ := asn1.Marshal(pkix.Name{ - CommonName: "my-common-name", - }.ToRDNSequence()) - template := x509.CertificateRequest{ - RawSubject: asn1Subj, - SignatureAlgorithm: alg, - URIs: uris, - - DNSNames: []string{"dnsName1.co", "dnsName2.ninja"}, - IPAddresses: []net.IP{ - []byte{8, 8, 8, 8}, - []byte{1, 1, 1, 1}, - }, - } - - csrBytes, err := x509.CreateCertificateRequest(rand.Reader, &template, privateKey) +func generateCSR(secretKey crypto.Signer) ([]byte, error) { + csr, err := gen.CSRWithSigner(secretKey, + gen.SetCSRCommonName("my-common-name"), + gen.SetCSRURIsFromStrings("spiffe://foo.foo.example.net", "spiffe://foo.bar.example.net"), + gen.SetCSRDNSNames("dnsName1.co", "dnsName2.ninja"), + gen.SetCSRIPAddresses([]byte{8, 8, 8, 8}, []byte{1, 1, 1, 1}), + ) if err != nil { return nil, err } - csr := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE REQUEST", Bytes: csrBytes}) - return csr, nil } diff --git a/test/e2e/suite/issuers/vault/certificate/approle.go b/test/e2e/suite/issuers/vault/certificate/approle.go index 2677161725c..25712d96a4c 100644 --- a/test/e2e/suite/issuers/vault/certificate/approle.go +++ b/test/e2e/suite/issuers/vault/certificate/approle.go @@ -18,23 +18,23 @@ package certificate import ( "context" - "path" "time" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "github.com/cert-manager/cert-manager/e2e-tests/framework" + "github.com/cert-manager/cert-manager/e2e-tests/framework/addon" + vaultaddon "github.com/cert-manager/cert-manager/e2e-tests/framework/addon/vault" + "github.com/cert-manager/cert-manager/e2e-tests/framework/helper/featureset" + "github.com/cert-manager/cert-manager/e2e-tests/framework/helper/validation" + "github.com/cert-manager/cert-manager/e2e-tests/framework/helper/validation/certificates" + "github.com/cert-manager/cert-manager/e2e-tests/util" cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" - "github.com/cert-manager/cert-manager/test/e2e/framework" - "github.com/cert-manager/cert-manager/test/e2e/framework/addon" - vaultaddon "github.com/cert-manager/cert-manager/test/e2e/framework/addon/vault" - "github.com/cert-manager/cert-manager/test/e2e/framework/helper/featureset" - "github.com/cert-manager/cert-manager/test/e2e/framework/helper/validation" - "github.com/cert-manager/cert-manager/test/e2e/util" "github.com/cert-manager/cert-manager/test/unit/gen" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" ) var _ = framework.CertManagerDescribe("Vault Issuer Certificate (AppRole, CA without root)", func() { @@ -58,33 +58,17 @@ var _ = framework.CertManagerDescribe("Vault ClusterIssuer Certificate (AppRole, func runVaultAppRoleTests(issuerKind string, testWithRoot bool, unsupportedFeatures featureset.FeatureSet) { f := framework.NewDefaultFramework("create-vault-certificate") - var ( - vault = &vaultaddon.Vault{ - Base: addon.Base, - Name: "cm-e2e-create-vault-certificate", - } - ) - - BeforeEach(func() { - vault.Namespace = f.Namespace.Name - }) - - f.RequireAddon(vault) - - rootMount := "root-ca" - intermediateMount := "intermediate-ca" - role := "kubernetes-vault" certificateName := "test-vault-certificate" certificateSecretName := "test-vault-certificate" - vaultSecretAppRoleName := "vault-role" - vaultPath := path.Join(intermediateMount, "sign", role) - authPath := "approle" - var roleId, secretId, vaultSecretName string - var vaultInit *vaultaddon.VaultInitializer + var vaultIssuerName string + + appRoleSecretGeneratorName := "vault-approle-secret-" + var roleId, secretId string + var vaultSecretName, vaultSecretNamespace string - var vaultIssuerName, vaultSecretNamespace string + var setup *vaultaddon.VaultInitializer - BeforeEach(func() { + BeforeEach(func(testingCtx context.Context) { By("Configuring the Vault server") if issuerKind == cmapi.IssuerKind { vaultSecretNamespace = f.Namespace.Name @@ -92,42 +76,42 @@ func runVaultAppRoleTests(issuerKind string, testWithRoot bool, unsupportedFeatu vaultSecretNamespace = f.Config.Addons.CertManager.ClusterResourceNamespace } - vaultInit = &vaultaddon.VaultInitializer{ - Details: *vault.Details(), - RootMount: rootMount, - IntermediateMount: intermediateMount, - ConfigureWithRoot: testWithRoot, - Role: role, - AppRoleAuthPath: authPath, - } - err := vaultInit.Init() - Expect(err).NotTo(HaveOccurred()) - err = vaultInit.Setup() - Expect(err).NotTo(HaveOccurred()) - roleId, secretId, err = vaultInit.CreateAppRole() - Expect(err).NotTo(HaveOccurred()) - sec, err := f.KubeClientSet.CoreV1().Secrets(vaultSecretNamespace).Create(context.TODO(), vaultaddon.NewVaultAppRoleSecret(vaultSecretAppRoleName, secretId), metav1.CreateOptions{}) + setup = vaultaddon.NewVaultInitializerAppRole( + addon.Base.Details().KubeClient, + *addon.Vault.Details(), + testWithRoot, + ) + Expect(setup.Init(testingCtx)).NotTo(HaveOccurred(), "failed to init vault") + Expect(setup.Setup(testingCtx)).NotTo(HaveOccurred(), "failed to setup vault") + + var err error + roleId, secretId, err = setup.CreateAppRole(testingCtx) Expect(err).NotTo(HaveOccurred()) + sec, err := f.KubeClientSet.CoreV1().Secrets(vaultSecretNamespace).Create(testingCtx, vaultaddon.NewVaultAppRoleSecret(appRoleSecretGeneratorName, secretId), metav1.CreateOptions{}) + Expect(err).NotTo(HaveOccurred()) vaultSecretName = sec.Name }) - JustAfterEach(func() { + JustAfterEach(func(testingCtx context.Context) { By("Cleaning up") - Expect(vaultInit.Clean()).NotTo(HaveOccurred()) + Expect(setup.Clean(testingCtx)).NotTo(HaveOccurred()) if issuerKind == cmapi.IssuerKind { - f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Delete(context.TODO(), vaultIssuerName, metav1.DeleteOptions{}) + err := f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Delete(testingCtx, vaultIssuerName, metav1.DeleteOptions{}) + Expect(err).NotTo(HaveOccurred()) } else { - f.CertManagerClientSet.CertmanagerV1().ClusterIssuers().Delete(context.TODO(), vaultIssuerName, metav1.DeleteOptions{}) + err := f.CertManagerClientSet.CertmanagerV1().ClusterIssuers().Delete(testingCtx, vaultIssuerName, metav1.DeleteOptions{}) + Expect(err).NotTo(HaveOccurred()) } - f.KubeClientSet.CoreV1().Secrets(vaultSecretNamespace).Delete(context.TODO(), vaultSecretName, metav1.DeleteOptions{}) + err := f.KubeClientSet.CoreV1().Secrets(vaultSecretNamespace).Delete(testingCtx, vaultSecretName, metav1.DeleteOptions{}) + Expect(err).NotTo(HaveOccurred()) }) - It("should generate a new valid certificate", func() { + It("should generate a new valid certificate", func(testingCtx context.Context) { By("Creating an Issuer") - vaultURL := vault.Details().Host + vaultURL := addon.Vault.Details().URL certClient := f.CertManagerClientSet.CertmanagerV1().Certificates(f.Namespace.Name) @@ -136,20 +120,20 @@ func runVaultAppRoleTests(issuerKind string, testWithRoot bool, unsupportedFeatu vaultIssuer := gen.IssuerWithRandomName("test-vault-issuer-", gen.SetIssuerNamespace(f.Namespace.Name), gen.SetIssuerVaultURL(vaultURL), - gen.SetIssuerVaultPath(vaultPath), - gen.SetIssuerVaultCABundle(vault.Details().VaultCA), - gen.SetIssuerVaultAppRoleAuth("secretkey", vaultSecretName, roleId, authPath)) - iss, err := f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Create(context.TODO(), vaultIssuer, metav1.CreateOptions{}) + gen.SetIssuerVaultPath(setup.IntermediateSignPath()), + gen.SetIssuerVaultCABundle(addon.Vault.Details().VaultCA), + gen.SetIssuerVaultAppRoleAuth("secretkey", vaultSecretName, roleId, setup.AppRoleAuthPath())) + iss, err := f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Create(testingCtx, vaultIssuer, metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred()) vaultIssuerName = iss.Name } else { vaultIssuer := gen.ClusterIssuerWithRandomName("test-vault-issuer-", gen.SetIssuerVaultURL(vaultURL), - gen.SetIssuerVaultPath(vaultPath), - gen.SetIssuerVaultCABundle(vault.Details().VaultCA), - gen.SetIssuerVaultAppRoleAuth("secretkey", vaultSecretName, roleId, authPath)) - iss, err := f.CertManagerClientSet.CertmanagerV1().ClusterIssuers().Create(context.TODO(), vaultIssuer, metav1.CreateOptions{}) + gen.SetIssuerVaultPath(setup.IntermediateSignPath()), + gen.SetIssuerVaultCABundle(addon.Vault.Details().VaultCA), + gen.SetIssuerVaultAppRoleAuth("secretkey", vaultSecretName, roleId, setup.AppRoleAuthPath())) + iss, err := f.CertManagerClientSet.CertmanagerV1().ClusterIssuers().Create(testingCtx, vaultIssuer, metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred()) vaultIssuerName = iss.Name @@ -158,14 +142,14 @@ func runVaultAppRoleTests(issuerKind string, testWithRoot bool, unsupportedFeatu By("Waiting for Issuer to become Ready") if issuerKind == cmapi.IssuerKind { - err = util.WaitForIssuerCondition(f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name), + err = util.WaitForIssuerCondition(testingCtx, f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name), vaultIssuerName, cmapi.IssuerCondition{ Type: cmapi.IssuerConditionReady, Status: cmmeta.ConditionTrue, }) } else { - err = util.WaitForClusterIssuerCondition(f.CertManagerClientSet.CertmanagerV1().ClusterIssuers(), + err = util.WaitForClusterIssuerCondition(testingCtx, f.CertManagerClientSet.CertmanagerV1().ClusterIssuers(), vaultIssuerName, cmapi.IssuerCondition{ Type: cmapi.IssuerConditionReady, @@ -176,11 +160,11 @@ func runVaultAppRoleTests(issuerKind string, testWithRoot bool, unsupportedFeatu Expect(err).NotTo(HaveOccurred()) By("Creating a Certificate") - cert, err := certClient.Create(context.TODO(), util.NewCertManagerVaultCertificate(certificateName, certificateSecretName, vaultIssuerName, issuerKind, nil, nil), metav1.CreateOptions{}) + cert, err := certClient.Create(testingCtx, util.NewCertManagerVaultCertificate(certificateName, certificateSecretName, vaultIssuerName, issuerKind, nil, nil), metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred()) By("Waiting for the Certificate to be issued...") - cert, err = f.Helper().WaitForCertificateReadyAndDoneIssuing(cert, time.Minute*5) + cert, err = f.Helper().WaitForCertificateReadyAndDoneIssuing(testingCtx, cert, time.Minute*5) Expect(err).NotTo(HaveOccurred()) By("Validating the issued Certificate...") @@ -223,29 +207,28 @@ func runVaultAppRoleTests(issuerKind string, testWithRoot bool, unsupportedFeatu } for _, v := range cases { - v := v - It("should generate a new certificate "+v.label, func() { + It("should generate a new certificate "+v.label, func(testingCtx context.Context) { By("Creating an Issuer") var err error if issuerKind == cmapi.IssuerKind { vaultIssuer := gen.IssuerWithRandomName("test-vault-issuer-", gen.SetIssuerNamespace(f.Namespace.Name), - gen.SetIssuerVaultURL(vault.Details().Host), - gen.SetIssuerVaultPath(vaultPath), - gen.SetIssuerVaultCABundle(vault.Details().VaultCA), - gen.SetIssuerVaultAppRoleAuth("secretkey", vaultSecretName, roleId, authPath)) - iss, err := f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Create(context.TODO(), vaultIssuer, metav1.CreateOptions{}) + gen.SetIssuerVaultURL(addon.Vault.Details().URL), + gen.SetIssuerVaultPath(setup.IntermediateSignPath()), + gen.SetIssuerVaultCABundle(addon.Vault.Details().VaultCA), + gen.SetIssuerVaultAppRoleAuth("secretkey", vaultSecretName, roleId, setup.AppRoleAuthPath())) + iss, err := f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Create(testingCtx, vaultIssuer, metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred()) vaultIssuerName = iss.Name } else { vaultIssuer := gen.ClusterIssuerWithRandomName("test-vault-issuer-", - gen.SetIssuerVaultURL(vault.Details().Host), - gen.SetIssuerVaultPath(vaultPath), - gen.SetIssuerVaultCABundle(vault.Details().VaultCA), - gen.SetIssuerVaultAppRoleAuth("secretkey", vaultSecretName, roleId, authPath)) - iss, err := f.CertManagerClientSet.CertmanagerV1().ClusterIssuers().Create(context.TODO(), vaultIssuer, metav1.CreateOptions{}) + gen.SetIssuerVaultURL(addon.Vault.Details().URL), + gen.SetIssuerVaultPath(setup.IntermediateSignPath()), + gen.SetIssuerVaultCABundle(addon.Vault.Details().VaultCA), + gen.SetIssuerVaultAppRoleAuth("secretkey", vaultSecretName, roleId, setup.AppRoleAuthPath())) + iss, err := f.CertManagerClientSet.CertmanagerV1().ClusterIssuers().Create(testingCtx, vaultIssuer, metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred()) vaultIssuerName = iss.Name @@ -254,14 +237,14 @@ func runVaultAppRoleTests(issuerKind string, testWithRoot bool, unsupportedFeatu By("Waiting for Issuer to become Ready") if issuerKind == cmapi.IssuerKind { - err = util.WaitForIssuerCondition(f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name), + err = util.WaitForIssuerCondition(testingCtx, f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name), vaultIssuerName, cmapi.IssuerCondition{ Type: cmapi.IssuerConditionReady, Status: cmmeta.ConditionTrue, }) } else { - err = util.WaitForClusterIssuerCondition(f.CertManagerClientSet.CertmanagerV1().ClusterIssuers(), + err = util.WaitForClusterIssuerCondition(testingCtx, f.CertManagerClientSet.CertmanagerV1().ClusterIssuers(), vaultIssuerName, cmapi.IssuerCondition{ Type: cmapi.IssuerConditionReady, @@ -271,19 +254,26 @@ func runVaultAppRoleTests(issuerKind string, testWithRoot bool, unsupportedFeatu Expect(err).NotTo(HaveOccurred()) By("Creating a Certificate") - cert, err := f.CertManagerClientSet.CertmanagerV1().Certificates(f.Namespace.Name).Create(context.TODO(), util.NewCertManagerVaultCertificate(certificateName, certificateSecretName, vaultIssuerName, issuerKind, v.inputDuration, v.inputRenewBefore), metav1.CreateOptions{}) + cert, err := f.CertManagerClientSet.CertmanagerV1().Certificates(f.Namespace.Name).Create(testingCtx, util.NewCertManagerVaultCertificate(certificateName, certificateSecretName, vaultIssuerName, issuerKind, v.inputDuration, v.inputRenewBefore), metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred()) By("Waiting for the Certificate to be issued...") - cert, err = f.Helper().WaitForCertificateReadyAndDoneIssuing(cert, time.Minute*5) + cert, err = f.Helper().WaitForCertificateReadyAndDoneIssuing(testingCtx, cert, time.Minute*5) Expect(err).NotTo(HaveOccurred()) By("Validating the issued Certificate...") - err = f.Helper().ValidateCertificate(cert, validation.CertificateSetForUnsupportedFeatureSet(unsupportedFeatures)...) + err = f.Helper().ValidateCertificate(cert, validation.CertificateSetForUnsupportedFeatureSet( + // X509 certificate duration will not match requested duration in Certificate for + // all test cases. Instead, we disable the duration check here and compare the duration + // with our expected duration below (by calling ExpectDuration explicitly). + unsupportedFeatures.Clone(). + Insert(featureset.DurationFeature), + )...) Expect(err).NotTo(HaveOccurred()) // Vault subtract 30 seconds to the NotBefore date. - f.CertificateDurationValid(cert, v.expectedDuration, time.Second*30) + err = f.Helper().ValidateCertificate(cert, certificates.ExpectDuration(v.expectedDuration, time.Second*30)) + Expect(err).NotTo(HaveOccurred()) }) } } diff --git a/test/e2e/suite/issuers/vault/certificate/approle_custom_mount.go b/test/e2e/suite/issuers/vault/certificate/approle_custom_mount.go deleted file mode 100644 index 6ca865d4c11..00000000000 --- a/test/e2e/suite/issuers/vault/certificate/approle_custom_mount.go +++ /dev/null @@ -1,188 +0,0 @@ -/* -Copyright 2020 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. -*/ - -package certificate - -import ( - "context" - "path" - "time" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - - cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" - cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" - "github.com/cert-manager/cert-manager/test/e2e/framework" - "github.com/cert-manager/cert-manager/test/e2e/framework/addon" - vaultaddon "github.com/cert-manager/cert-manager/test/e2e/framework/addon/vault" - "github.com/cert-manager/cert-manager/test/e2e/framework/helper/featureset" - "github.com/cert-manager/cert-manager/test/e2e/framework/helper/validation" - "github.com/cert-manager/cert-manager/test/e2e/util" - "github.com/cert-manager/cert-manager/test/unit/gen" -) - -var _ = framework.CertManagerDescribe("Vault Issuer Certificate (AppRole with a custom mount path, CA without root)", func() { - fs := featureset.NewFeatureSet(featureset.SaveRootCAToSecret) - runVaultCustomAppRoleTests(cmapi.IssuerKind, false, fs) -}) - -var _ = framework.CertManagerDescribe("Vault Issuer Certificate (AppRole with a custom mount path, CA with root)", func() { - fs := featureset.NewFeatureSet() - runVaultCustomAppRoleTests(cmapi.IssuerKind, true, fs) -}) -var _ = framework.CertManagerDescribe("Vault ClusterIssuer Certificate (AppRole with a custom mount path, CA without root)", func() { - fs := featureset.NewFeatureSet(featureset.SaveRootCAToSecret) - runVaultCustomAppRoleTests(cmapi.ClusterIssuerKind, false, fs) -}) -var _ = framework.CertManagerDescribe("Vault ClusterIssuer Certificate (AppRole with a custom mount path, CA with root)", func() { - fs := featureset.NewFeatureSet() - runVaultCustomAppRoleTests(cmapi.ClusterIssuerKind, true, fs) -}) - -func runVaultCustomAppRoleTests(issuerKind string, testWithRoot bool, unsupportedFeatures featureset.FeatureSet) { - f := framework.NewDefaultFramework("create-vault-certificate") - - var ( - vault = &vaultaddon.Vault{ - Base: addon.Base, - Name: "cm-e2e-create-vault-certificate", - } - ) - - BeforeEach(func() { - vault.Namespace = f.Namespace.Name - }) - - f.RequireAddon(vault) - - rootMount := "root-ca" - intermediateMount := "intermediate-ca" - authPath := "custom/path" - role := "kubernetes-vault" - certificateName := "test-vault-certificate" - certificateSecretName := "test-vault-certificate" - vaultSecretAppRoleName := "vault-role" - vaultPath := path.Join(intermediateMount, "sign", role) - var roleId, secretId, vaultSecretName string - var vaultIssuerName, vaultSecretNamespace string - - var vaultInit *vaultaddon.VaultInitializer - - BeforeEach(func() { - By("Configuring the Vault server") - if issuerKind == cmapi.IssuerKind { - vaultSecretNamespace = f.Namespace.Name - } else { - vaultSecretNamespace = f.Config.Addons.CertManager.ClusterResourceNamespace - } - - vaultInit = &vaultaddon.VaultInitializer{ - Details: *vault.Details(), - RootMount: rootMount, - IntermediateMount: intermediateMount, - ConfigureWithRoot: testWithRoot, - Role: role, - AppRoleAuthPath: authPath, - } - err := vaultInit.Init() - Expect(err).NotTo(HaveOccurred()) - err = vaultInit.Setup() - Expect(err).NotTo(HaveOccurred()) - roleId, secretId, err = vaultInit.CreateAppRole() - Expect(err).NotTo(HaveOccurred()) - sec, err := f.KubeClientSet.CoreV1().Secrets(vaultSecretNamespace).Create(context.TODO(), vaultaddon.NewVaultAppRoleSecret(vaultSecretAppRoleName, secretId), metav1.CreateOptions{}) - Expect(err).NotTo(HaveOccurred()) - - vaultSecretName = sec.Name - }) - - JustAfterEach(func() { - By("Cleaning up") - Expect(vaultInit.Clean()).NotTo(HaveOccurred()) - - if issuerKind == cmapi.IssuerKind { - f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Delete(context.TODO(), vaultIssuerName, metav1.DeleteOptions{}) - } else { - f.CertManagerClientSet.CertmanagerV1().ClusterIssuers().Delete(context.TODO(), vaultIssuerName, metav1.DeleteOptions{}) - } - - f.KubeClientSet.CoreV1().Secrets(vaultSecretNamespace).Delete(context.TODO(), vaultSecretName, metav1.DeleteOptions{}) - }) - - It("should generate a new valid certificate", func() { - By("Creating an Issuer") - vaultURL := vault.Details().Host - - certClient := f.CertManagerClientSet.CertmanagerV1().Certificates(f.Namespace.Name) - - var err error - if issuerKind == cmapi.IssuerKind { - vaultIssuer := gen.IssuerWithRandomName("test-vault-issuer-", - gen.SetIssuerNamespace(f.Namespace.Name), - gen.SetIssuerVaultURL(vaultURL), - gen.SetIssuerVaultPath(vaultPath), - gen.SetIssuerVaultCABundle(vault.Details().VaultCA), - gen.SetIssuerVaultAppRoleAuth("secretkey", vaultSecretName, roleId, authPath)) - iss, err := f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Create(context.TODO(), vaultIssuer, metav1.CreateOptions{}) - Expect(err).NotTo(HaveOccurred()) - - vaultIssuerName = iss.Name - } else { - vaultIssuer := gen.ClusterIssuerWithRandomName("test-vault-issuer-", - gen.SetIssuerVaultURL(vaultURL), - gen.SetIssuerVaultPath(vaultPath), - gen.SetIssuerVaultCABundle(vault.Details().VaultCA), - gen.SetIssuerVaultAppRoleAuth("secretkey", vaultSecretName, roleId, authPath)) - iss, err := f.CertManagerClientSet.CertmanagerV1().ClusterIssuers().Create(context.TODO(), vaultIssuer, metav1.CreateOptions{}) - Expect(err).NotTo(HaveOccurred()) - - vaultIssuerName = iss.Name - } - - By("Waiting for Issuer to become Ready") - if issuerKind == cmapi.IssuerKind { - err = util.WaitForIssuerCondition(f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name), - vaultIssuerName, - cmapi.IssuerCondition{ - Type: cmapi.IssuerConditionReady, - Status: cmmeta.ConditionTrue, - }) - } else { - err = util.WaitForClusterIssuerCondition(f.CertManagerClientSet.CertmanagerV1().ClusterIssuers(), - vaultIssuerName, - cmapi.IssuerCondition{ - Type: cmapi.IssuerConditionReady, - Status: cmmeta.ConditionTrue, - }) - } - - Expect(err).NotTo(HaveOccurred()) - - By("Creating a Certificate") - cert, err := certClient.Create(context.TODO(), util.NewCertManagerVaultCertificate(certificateName, certificateSecretName, vaultIssuerName, issuerKind, nil, nil), metav1.CreateOptions{}) - Expect(err).NotTo(HaveOccurred()) - - By("Waiting for the Certificate to be issued...") - cert, err = f.Helper().WaitForCertificateReadyAndDoneIssuing(cert, time.Minute*5) - Expect(err).NotTo(HaveOccurred()) - - By("Validating the issued Certificate...") - err = f.Helper().ValidateCertificate(cert, validation.CertificateSetForUnsupportedFeatureSet(unsupportedFeatures)...) - Expect(err).NotTo(HaveOccurred()) - }) -} diff --git a/test/e2e/suite/issuers/vault/certificate/cert_auth.go b/test/e2e/suite/issuers/vault/certificate/cert_auth.go new file mode 100644 index 00000000000..0671e21f87d --- /dev/null +++ b/test/e2e/suite/issuers/vault/certificate/cert_auth.go @@ -0,0 +1,289 @@ +/* +Copyright 2020 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. +*/ + +package certificate + +import ( + "context" + "time" + + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "github.com/cert-manager/cert-manager/e2e-tests/framework" + "github.com/cert-manager/cert-manager/e2e-tests/framework/addon" + vaultaddon "github.com/cert-manager/cert-manager/e2e-tests/framework/addon/vault" + "github.com/cert-manager/cert-manager/e2e-tests/framework/helper/featureset" + "github.com/cert-manager/cert-manager/e2e-tests/framework/helper/validation" + "github.com/cert-manager/cert-manager/e2e-tests/framework/helper/validation/certificates" + "github.com/cert-manager/cert-manager/e2e-tests/util" + cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" + cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" + "github.com/cert-manager/cert-manager/test/unit/gen" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +var _ = framework.CertManagerDescribe("Vault Issuer Certificate (ClientCert, CA without root)", func() { + fs := featureset.NewFeatureSet(featureset.SaveRootCAToSecret) + runVaultClientCertAuthTest(cmapi.IssuerKind, false, fs) +}) +var _ = framework.CertManagerDescribe("Vault Issuer Certificate (ClientCert, CA with root)", func() { + fs := featureset.NewFeatureSet() + runVaultClientCertAuthTest(cmapi.IssuerKind, true, fs) +}) + +var _ = framework.CertManagerDescribe("Vault ClusterIssuer Certificate (ClientCert, CA without root)", func() { + fs := featureset.NewFeatureSet(featureset.SaveRootCAToSecret) + runVaultClientCertAuthTest(cmapi.ClusterIssuerKind, false, fs) +}) +var _ = framework.CertManagerDescribe("Vault ClusterIssuer Certificate (ClientCert, CA with root)", func() { + fs := featureset.NewFeatureSet() + runVaultClientCertAuthTest(cmapi.ClusterIssuerKind, true, fs) +}) + +func runVaultClientCertAuthTest(issuerKind string, testWithRoot bool, unsupportedFeatures featureset.FeatureSet) { + f := framework.NewDefaultFramework("create-vault-certificate") + + certificateName := "test-vault-certificate" + certificateSecretName := "test-vault-certificate" + var vaultIssuerName string + + var vaultSecretName, vaultSecretNamespace string + var keyPEM, certPEM []byte + + var setup *vaultaddon.VaultInitializer + + BeforeEach(func(testingCtx context.Context) { + By("Configuring the Vault server") + if issuerKind == cmapi.IssuerKind { + vaultSecretNamespace = f.Namespace.Name + } else { + vaultSecretNamespace = f.Config.Addons.CertManager.ClusterResourceNamespace + } + + setup = vaultaddon.NewVaultInitializerClientCertificate( + addon.Base.Details().KubeClient, + *addon.Vault.Details(), + testWithRoot, + ) + Expect(setup.Init(testingCtx)).NotTo(HaveOccurred(), "failed to init vault") + Expect(setup.Setup(testingCtx)).NotTo(HaveOccurred(), "failed to setup vault") + + var err error + keyPEM, certPEM, err = setup.CreateClientCertRole(testingCtx) + Expect(err).NotTo(HaveOccurred()) + + sec, err := f.KubeClientSet.CoreV1().Secrets(vaultSecretNamespace).Create(testingCtx, &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{GenerateName: "vault-client-cert-"}, + StringData: map[string]string{ + "tls.key": string(keyPEM), + "tls.crt": string(certPEM), + }, + }, metav1.CreateOptions{}) + Expect(err).NotTo(HaveOccurred()) + vaultSecretName = sec.Name + }) + + JustAfterEach(func(testingCtx context.Context) { + By("Cleaning up") + Expect(setup.Clean(testingCtx)).NotTo(HaveOccurred()) + + if issuerKind == cmapi.IssuerKind { + err := f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Delete(testingCtx, vaultIssuerName, metav1.DeleteOptions{}) + Expect(err).NotTo(HaveOccurred()) + } else { + err := f.CertManagerClientSet.CertmanagerV1().ClusterIssuers().Delete(testingCtx, vaultIssuerName, metav1.DeleteOptions{}) + Expect(err).NotTo(HaveOccurred()) + } + + err := f.KubeClientSet.CoreV1().Secrets(vaultSecretNamespace).Delete(testingCtx, vaultSecretName, metav1.DeleteOptions{}) + Expect(err).NotTo(HaveOccurred()) + }) + + It("should generate a new valid certificate", func(testingCtx context.Context) { + By("Creating an Issuer") + vaultURL := addon.Vault.Details().URL + + certClient := f.CertManagerClientSet.CertmanagerV1().Certificates(f.Namespace.Name) + + var err error + if issuerKind == cmapi.IssuerKind { + vaultIssuer := gen.IssuerWithRandomName("test-vault-issuer-", + gen.SetIssuerNamespace(f.Namespace.Name), + gen.SetIssuerVaultURL(vaultURL), + gen.SetIssuerVaultPath(setup.IntermediateSignPath()), + gen.SetIssuerVaultCABundle(addon.Vault.Details().VaultCA), + gen.SetIssuerVaultClientCertificateAuth(setup.ClientCertificateAuthPath(), vaultSecretName), + ) + iss, err := f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Create(testingCtx, vaultIssuer, metav1.CreateOptions{}) + Expect(err).NotTo(HaveOccurred()) + + vaultIssuerName = iss.Name + } else { + vaultIssuer := gen.ClusterIssuerWithRandomName("test-vault-issuer-", + gen.SetIssuerVaultURL(vaultURL), + gen.SetIssuerVaultPath(setup.IntermediateSignPath()), + gen.SetIssuerVaultCABundle(addon.Vault.Details().VaultCA), + gen.SetIssuerVaultClientCertificateAuth(setup.ClientCertificateAuthPath(), vaultSecretName), + ) + iss, err := f.CertManagerClientSet.CertmanagerV1().ClusterIssuers().Create(testingCtx, vaultIssuer, metav1.CreateOptions{}) + Expect(err).NotTo(HaveOccurred()) + + vaultIssuerName = iss.Name + } + + By("Waiting for Issuer to become Ready") + + if issuerKind == cmapi.IssuerKind { + err = util.WaitForIssuerCondition(testingCtx, f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name), + vaultIssuerName, + cmapi.IssuerCondition{ + Type: cmapi.IssuerConditionReady, + Status: cmmeta.ConditionTrue, + }) + } else { + err = util.WaitForClusterIssuerCondition(testingCtx, f.CertManagerClientSet.CertmanagerV1().ClusterIssuers(), + vaultIssuerName, + cmapi.IssuerCondition{ + Type: cmapi.IssuerConditionReady, + Status: cmmeta.ConditionTrue, + }) + } + + Expect(err).NotTo(HaveOccurred()) + + By("Creating a Certificate") + cert, err := certClient.Create(testingCtx, util.NewCertManagerVaultCertificate(certificateName, certificateSecretName, vaultIssuerName, issuerKind, nil, nil), metav1.CreateOptions{}) + Expect(err).NotTo(HaveOccurred()) + + By("Waiting for the Certificate to be issued...") + cert, err = f.Helper().WaitForCertificateReadyAndDoneIssuing(testingCtx, cert, time.Minute*5) + Expect(err).NotTo(HaveOccurred()) + + By("Validating the issued Certificate...") + err = f.Helper().ValidateCertificate(cert, validation.CertificateSetForUnsupportedFeatureSet(unsupportedFeatures)...) + Expect(err).NotTo(HaveOccurred()) + + }) + + cases := []struct { + inputDuration *metav1.Duration + inputRenewBefore *metav1.Duration + expectedDuration time.Duration + label string + event string + }{ + { + inputDuration: &metav1.Duration{Duration: time.Hour * 24 * 35}, + inputRenewBefore: nil, + expectedDuration: time.Hour * 24 * 35, + label: "valid for 35 days", + }, + { + inputDuration: nil, + inputRenewBefore: nil, + expectedDuration: time.Hour * 24 * 90, + label: "valid for the default value (90 days)", + }, + { + inputDuration: &metav1.Duration{Duration: time.Hour * 24 * 365}, + inputRenewBefore: nil, + expectedDuration: time.Hour * 24 * 90, + label: "with Vault configured maximum TTL duration (90 days) when requested duration is greater than TTL", + }, + { + inputDuration: &metav1.Duration{Duration: time.Hour * 24 * 240}, + inputRenewBefore: &metav1.Duration{Duration: time.Hour * 24 * 120}, + expectedDuration: time.Hour * 24 * 90, + label: "with a warning event when renewBefore is bigger than the duration", + }, + } + + for _, v := range cases { + It("should generate a new certificate "+v.label, func(testingCtx context.Context) { + By("Creating an Issuer") + + var err error + if issuerKind == cmapi.IssuerKind { + vaultIssuer := gen.IssuerWithRandomName("test-vault-issuer-", + gen.SetIssuerNamespace(f.Namespace.Name), + gen.SetIssuerVaultURL(addon.Vault.Details().URL), + gen.SetIssuerVaultPath(setup.IntermediateSignPath()), + gen.SetIssuerVaultCABundle(addon.Vault.Details().VaultCA), + gen.SetIssuerVaultClientCertificateAuth(setup.ClientCertificateAuthPath(), vaultSecretName), + ) + iss, err := f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Create(testingCtx, vaultIssuer, metav1.CreateOptions{}) + Expect(err).NotTo(HaveOccurred()) + + vaultIssuerName = iss.Name + } else { + vaultIssuer := gen.ClusterIssuerWithRandomName("test-vault-issuer-", + gen.SetIssuerVaultURL(addon.Vault.Details().URL), + gen.SetIssuerVaultPath(setup.IntermediateSignPath()), + gen.SetIssuerVaultCABundle(addon.Vault.Details().VaultCA), + gen.SetIssuerVaultClientCertificateAuth(setup.ClientCertificateAuthPath(), vaultSecretName), + ) + iss, err := f.CertManagerClientSet.CertmanagerV1().ClusterIssuers().Create(testingCtx, vaultIssuer, metav1.CreateOptions{}) + Expect(err).NotTo(HaveOccurred()) + + vaultIssuerName = iss.Name + } + + By("Waiting for Issuer to become Ready") + + if issuerKind == cmapi.IssuerKind { + err = util.WaitForIssuerCondition(testingCtx, f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name), + vaultIssuerName, + cmapi.IssuerCondition{ + Type: cmapi.IssuerConditionReady, + Status: cmmeta.ConditionTrue, + }) + } else { + err = util.WaitForClusterIssuerCondition(testingCtx, f.CertManagerClientSet.CertmanagerV1().ClusterIssuers(), + vaultIssuerName, + cmapi.IssuerCondition{ + Type: cmapi.IssuerConditionReady, + Status: cmmeta.ConditionTrue, + }) + } + Expect(err).NotTo(HaveOccurred()) + + By("Creating a Certificate") + cert, err := f.CertManagerClientSet.CertmanagerV1().Certificates(f.Namespace.Name).Create(testingCtx, util.NewCertManagerVaultCertificate(certificateName, certificateSecretName, vaultIssuerName, issuerKind, v.inputDuration, v.inputRenewBefore), metav1.CreateOptions{}) + Expect(err).NotTo(HaveOccurred()) + + By("Waiting for the Certificate to be issued...") + cert, err = f.Helper().WaitForCertificateReadyAndDoneIssuing(testingCtx, cert, time.Minute*5) + Expect(err).NotTo(HaveOccurred()) + + By("Validating the issued Certificate...") + err = f.Helper().ValidateCertificate(cert, validation.CertificateSetForUnsupportedFeatureSet( + // X509 certificate duration will not match requested duration in Certificate for + // all test cases. Instead, we disable the duration check here and compare the duration + // with our expected duration below (by calling ExpectDuration explicitly). + unsupportedFeatures.Clone(). + Insert(featureset.DurationFeature), + )...) + Expect(err).NotTo(HaveOccurred()) + + // Vault subtract 30 seconds to the NotBefore date. + err = f.Helper().ValidateCertificate(cert, certificates.ExpectDuration(v.expectedDuration, time.Second*30)) + Expect(err).NotTo(HaveOccurred()) + }) + } +} diff --git a/test/e2e/suite/issuers/vault/certificaterequest/approle.go b/test/e2e/suite/issuers/vault/certificaterequest/approle.go index b90833d282e..e7d8bf6dd62 100644 --- a/test/e2e/suite/issuers/vault/certificaterequest/approle.go +++ b/test/e2e/suite/issuers/vault/certificaterequest/approle.go @@ -20,20 +20,22 @@ import ( "context" "crypto/x509" "net" - "path" "time" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "github.com/cert-manager/cert-manager/e2e-tests/framework" + "github.com/cert-manager/cert-manager/e2e-tests/framework/addon" + vaultaddon "github.com/cert-manager/cert-manager/e2e-tests/framework/addon/vault" + "github.com/cert-manager/cert-manager/e2e-tests/framework/helper/validation/certificaterequests" + "github.com/cert-manager/cert-manager/e2e-tests/util" cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" - "github.com/cert-manager/cert-manager/test/e2e/framework" - "github.com/cert-manager/cert-manager/test/e2e/framework/addon" - vaultaddon "github.com/cert-manager/cert-manager/test/e2e/framework/addon/vault" - "github.com/cert-manager/cert-manager/test/e2e/util" "github.com/cert-manager/cert-manager/test/unit/gen" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" ) var _ = framework.CertManagerDescribe("Vault Issuer CertificateRequest (AppRole)", func() { @@ -49,11 +51,6 @@ func runVaultAppRoleTests(issuerKind string) { h := f.Helper() var ( - vault = &vaultaddon.Vault{ - Base: addon.Base, - Name: "cm-e2e-create-vault-certificaterequest", - } - crDNSNames = []string{"dnsName1.co", "dnsName2.ninja"} crIPAddresses = []net.IP{ []byte{8, 8, 8, 8}, @@ -61,25 +58,16 @@ func runVaultAppRoleTests(issuerKind string) { } ) - BeforeEach(func() { - vault.Namespace = f.Namespace.Name - }) - - f.RequireAddon(vault) - - rootMount := "root-ca" - intermediateMount := "intermediate-ca" - role := "kubernetes-vault" certificateRequestName := "test-vault-certificaterequest" - vaultSecretAppRoleName := "vault-role-" - vaultPath := path.Join(intermediateMount, "sign", role) - authPath := "approle" + var vaultIssuerName string + + appRoleSecretGeneratorName := "vault-approle-secret-" var roleId, secretId string - var vaultInit *vaultaddon.VaultInitializer + var vaultSecretName, vaultSecretNamespace string - var vaultIssuerName, vaultSecretName, vaultSecretNamespace string + var setup *vaultaddon.VaultInitializer - BeforeEach(func() { + BeforeEach(func(testingCtx context.Context) { By("Configuring the Vault server") if issuerKind == cmapi.IssuerKind { vaultSecretNamespace = f.Namespace.Name @@ -87,41 +75,42 @@ func runVaultAppRoleTests(issuerKind string) { vaultSecretNamespace = f.Config.Addons.CertManager.ClusterResourceNamespace } - vaultInit = &vaultaddon.VaultInitializer{ - Details: *vault.Details(), - RootMount: rootMount, - IntermediateMount: intermediateMount, - Role: role, - AppRoleAuthPath: authPath, - } - err := vaultInit.Init() - Expect(err).NotTo(HaveOccurred()) - err = vaultInit.Setup() - Expect(err).NotTo(HaveOccurred()) - roleId, secretId, err = vaultInit.CreateAppRole() - Expect(err).NotTo(HaveOccurred()) - sec, err := f.KubeClientSet.CoreV1().Secrets(vaultSecretNamespace).Create(context.TODO(), vaultaddon.NewVaultAppRoleSecret(vaultSecretAppRoleName, secretId), metav1.CreateOptions{}) + setup = vaultaddon.NewVaultInitializerAppRole( + addon.Base.Details().KubeClient, + *addon.Vault.Details(), + false, + ) + Expect(setup.Init(testingCtx)).NotTo(HaveOccurred(), "failed to init vault") + Expect(setup.Setup(testingCtx)).NotTo(HaveOccurred(), "failed to setup vault") + + var err error + roleId, secretId, err = setup.CreateAppRole(testingCtx) Expect(err).NotTo(HaveOccurred()) + sec, err := f.KubeClientSet.CoreV1().Secrets(vaultSecretNamespace).Create(testingCtx, vaultaddon.NewVaultAppRoleSecret(appRoleSecretGeneratorName, secretId), metav1.CreateOptions{}) + Expect(err).NotTo(HaveOccurred()) vaultSecretName = sec.Name }) - JustAfterEach(func() { + JustAfterEach(func(testingCtx context.Context) { By("Cleaning up") - Expect(vaultInit.Clean()).NotTo(HaveOccurred()) + Expect(setup.Clean(testingCtx)).NotTo(HaveOccurred()) if issuerKind == cmapi.IssuerKind { - f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Delete(context.TODO(), vaultIssuerName, metav1.DeleteOptions{}) + err := f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Delete(testingCtx, vaultIssuerName, metav1.DeleteOptions{}) + Expect(err).NotTo(HaveOccurred()) } else { - f.CertManagerClientSet.CertmanagerV1().ClusterIssuers().Delete(context.TODO(), vaultIssuerName, metav1.DeleteOptions{}) + err := f.CertManagerClientSet.CertmanagerV1().ClusterIssuers().Delete(testingCtx, vaultIssuerName, metav1.DeleteOptions{}) + Expect(err).NotTo(HaveOccurred()) } - f.KubeClientSet.CoreV1().Secrets(vaultSecretNamespace).Delete(context.TODO(), vaultSecretName, metav1.DeleteOptions{}) + err := f.KubeClientSet.CoreV1().Secrets(vaultSecretNamespace).Delete(testingCtx, vaultSecretName, metav1.DeleteOptions{}) + Expect(err).NotTo(HaveOccurred()) }) - It("should generate a new valid certificate", func() { + It("should generate a new valid certificate", func(testingCtx context.Context) { By("Creating an Issuer") - vaultURL := vault.Details().Host + vaultURL := addon.Vault.Details().URL crClient := f.CertManagerClientSet.CertmanagerV1().CertificateRequests(f.Namespace.Name) @@ -130,20 +119,20 @@ func runVaultAppRoleTests(issuerKind string) { vaultIssuer := gen.IssuerWithRandomName("test-vault-issuer-", gen.SetIssuerNamespace(f.Namespace.Name), gen.SetIssuerVaultURL(vaultURL), - gen.SetIssuerVaultPath(vaultPath), - gen.SetIssuerVaultCABundle(vault.Details().VaultCA), - gen.SetIssuerVaultAppRoleAuth("secretkey", vaultSecretName, roleId, authPath)) - iss, err := f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Create(context.TODO(), vaultIssuer, metav1.CreateOptions{}) + gen.SetIssuerVaultPath(setup.IntermediateSignPath()), + gen.SetIssuerVaultCABundle(addon.Vault.Details().VaultCA), + gen.SetIssuerVaultAppRoleAuth("secretkey", vaultSecretName, roleId, setup.AppRoleAuthPath())) + iss, err := f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Create(testingCtx, vaultIssuer, metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred()) vaultIssuerName = iss.Name } else { vaultIssuer := gen.ClusterIssuerWithRandomName("test-vault-issuer-", gen.SetIssuerVaultURL(vaultURL), - gen.SetIssuerVaultPath(vaultPath), - gen.SetIssuerVaultCABundle(vault.Details().VaultCA), - gen.SetIssuerVaultAppRoleAuth("secretkey", vaultSecretName, roleId, authPath)) - iss, err := f.CertManagerClientSet.CertmanagerV1().ClusterIssuers().Create(context.TODO(), vaultIssuer, metav1.CreateOptions{}) + gen.SetIssuerVaultPath(setup.IntermediateSignPath()), + gen.SetIssuerVaultCABundle(addon.Vault.Details().VaultCA), + gen.SetIssuerVaultAppRoleAuth("secretkey", vaultSecretName, roleId, setup.AppRoleAuthPath())) + iss, err := f.CertManagerClientSet.CertmanagerV1().ClusterIssuers().Create(testingCtx, vaultIssuer, metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred()) vaultIssuerName = iss.Name @@ -151,14 +140,14 @@ func runVaultAppRoleTests(issuerKind string) { By("Waiting for Issuer to become Ready") if issuerKind == cmapi.IssuerKind { - err = util.WaitForIssuerCondition(f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name), + err = util.WaitForIssuerCondition(testingCtx, f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name), vaultIssuerName, cmapi.IssuerCondition{ Type: cmapi.IssuerConditionReady, Status: cmmeta.ConditionTrue, }) } else { - err = util.WaitForClusterIssuerCondition(f.CertManagerClientSet.CertmanagerV1().ClusterIssuers(), + err = util.WaitForClusterIssuerCondition(testingCtx, f.CertManagerClientSet.CertmanagerV1().ClusterIssuers(), vaultIssuerName, cmapi.IssuerCondition{ Type: cmapi.IssuerConditionReady, @@ -168,17 +157,19 @@ func runVaultAppRoleTests(issuerKind string) { Expect(err).NotTo(HaveOccurred()) By("Creating a CertificateRequest") - cr, key, err := util.NewCertManagerBasicCertificateRequest(certificateRequestName, vaultIssuerName, issuerKind, - &metav1.Duration{ - Duration: time.Hour * 24 * 90, - }, - crDNSNames, crIPAddresses, nil, x509.RSA) + csr, key, err := gen.CSR(x509.RSA, gen.SetCSRCommonName(crDNSNames[0]), gen.SetCSRDNSNames(crDNSNames...), gen.SetCSRIPAddresses(crIPAddresses...)) Expect(err).NotTo(HaveOccurred()) - _, err = crClient.Create(context.TODO(), cr, metav1.CreateOptions{}) + cr := gen.CertificateRequest(certificateRequestName, + gen.SetCertificateRequestNamespace(f.Namespace.Name), + gen.SetCertificateRequestIssuer(cmmeta.IssuerReference{Kind: issuerKind, Name: vaultIssuerName}), + gen.SetCertificateRequestDuration(&metav1.Duration{Duration: time.Hour * 24 * 90}), + gen.SetCertificateRequestCSR(csr), + ) + _, err = crClient.Create(testingCtx, cr, metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred()) By("Verifying the Certificate is valid") - err = h.WaitCertificateRequestIssuedValid(f.Namespace.Name, certificateRequestName, time.Minute*5, key) + err = h.WaitCertificateRequestIssuedValid(testingCtx, f.Namespace.Name, certificateRequestName, time.Minute*5, key) Expect(err).NotTo(HaveOccurred()) }) @@ -211,29 +202,28 @@ func runVaultAppRoleTests(issuerKind string) { } for _, v := range cases { - v := v - It("should generate a new certificate "+v.label, func() { + It("should generate a new certificate "+v.label, func(testingCtx context.Context) { By("Creating an Issuer") var err error if issuerKind == cmapi.IssuerKind { vaultIssuer := gen.IssuerWithRandomName("test-vault-issuer-", gen.SetIssuerNamespace(f.Namespace.Name), - gen.SetIssuerVaultURL(vault.Details().Host), - gen.SetIssuerVaultPath(vaultPath), - gen.SetIssuerVaultCABundle(vault.Details().VaultCA), - gen.SetIssuerVaultAppRoleAuth("secretkey", vaultSecretName, roleId, authPath)) - iss, err := f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Create(context.TODO(), vaultIssuer, metav1.CreateOptions{}) + gen.SetIssuerVaultURL(addon.Vault.Details().URL), + gen.SetIssuerVaultPath(setup.IntermediateSignPath()), + gen.SetIssuerVaultCABundle(addon.Vault.Details().VaultCA), + gen.SetIssuerVaultAppRoleAuth("secretkey", vaultSecretName, roleId, setup.AppRoleAuthPath())) + iss, err := f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Create(testingCtx, vaultIssuer, metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred()) vaultIssuerName = iss.Name } else { vaultIssuer := gen.ClusterIssuerWithRandomName("test-vault-issuer-", - gen.SetIssuerVaultURL(vault.Details().Host), - gen.SetIssuerVaultPath(vaultPath), - gen.SetIssuerVaultCABundle(vault.Details().VaultCA), - gen.SetIssuerVaultAppRoleAuth("secretkey", vaultSecretName, roleId, authPath)) - iss, err := f.CertManagerClientSet.CertmanagerV1().ClusterIssuers().Create(context.TODO(), vaultIssuer, metav1.CreateOptions{}) + gen.SetIssuerVaultURL(addon.Vault.Details().URL), + gen.SetIssuerVaultPath(setup.IntermediateSignPath()), + gen.SetIssuerVaultCABundle(addon.Vault.Details().VaultCA), + gen.SetIssuerVaultAppRoleAuth("secretkey", vaultSecretName, roleId, setup.AppRoleAuthPath())) + iss, err := f.CertManagerClientSet.CertmanagerV1().ClusterIssuers().Create(testingCtx, vaultIssuer, metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred()) vaultIssuerName = iss.Name @@ -241,14 +231,14 @@ func runVaultAppRoleTests(issuerKind string) { By("Waiting for Issuer to become Ready") if issuerKind == cmapi.IssuerKind { - err = util.WaitForIssuerCondition(f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name), + err = util.WaitForIssuerCondition(testingCtx, f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name), vaultIssuerName, cmapi.IssuerCondition{ Type: cmapi.IssuerConditionReady, Status: cmmeta.ConditionTrue, }) } else { - err = util.WaitForClusterIssuerCondition(f.CertManagerClientSet.CertmanagerV1().ClusterIssuers(), + err = util.WaitForClusterIssuerCondition(testingCtx, f.CertManagerClientSet.CertmanagerV1().ClusterIssuers(), vaultIssuerName, cmapi.IssuerCondition{ Type: cmapi.IssuerConditionReady, @@ -260,20 +250,29 @@ func runVaultAppRoleTests(issuerKind string) { By("Creating a CertificateRequest") crClient := f.CertManagerClientSet.CertmanagerV1().CertificateRequests(f.Namespace.Name) - cr, key, err := util.NewCertManagerBasicCertificateRequest(certificateRequestName, vaultIssuerName, - issuerKind, v.inputDuration, crDNSNames, crIPAddresses, nil, x509.RSA) + csr, key, err := gen.CSR(x509.RSA, gen.SetCSRCommonName(crDNSNames[0]), gen.SetCSRDNSNames(crDNSNames...), gen.SetCSRIPAddresses(crIPAddresses...)) Expect(err).NotTo(HaveOccurred()) - _, err = crClient.Create(context.TODO(), cr, metav1.CreateOptions{}) + cr := gen.CertificateRequest(certificateRequestName, + gen.SetCertificateRequestNamespace(f.Namespace.Name), + gen.SetCertificateRequestIssuer(cmmeta.IssuerReference{Kind: issuerKind, Name: vaultIssuerName}), + gen.SetCertificateRequestDuration(v.inputDuration), + gen.SetCertificateRequestCSR(csr), + ) + _, err = crClient.Create(testingCtx, cr, metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred()) - err = h.WaitCertificateRequestIssuedValid(f.Namespace.Name, certificateRequestName, time.Minute*5, key) + err = h.WaitCertificateRequestIssuedValid(testingCtx, f.Namespace.Name, certificateRequestName, time.Minute*5, key) Expect(err).NotTo(HaveOccurred()) By("Verifying the Certificate is valid") - cr, err = crClient.Get(context.TODO(), cr.Name, metav1.GetOptions{}) + _, err = crClient.Get(testingCtx, cr.Name, metav1.GetOptions{}) Expect(err).NotTo(HaveOccurred()) // Vault can issue certificates with slightly skewed duration. - f.CertificateRequestDurationValid(cr, v.expectedDuration, 30*time.Second) + err = h.ValidateCertificateRequest(types.NamespacedName{ + Namespace: f.Namespace.Name, + Name: certificateRequestName, + }, key, certificaterequests.ExpectDuration(v.expectedDuration, 30*time.Second)) + Expect(err).NotTo(HaveOccurred()) }) } } diff --git a/test/e2e/suite/issuers/vault/certificaterequest/approle_custom_mount.go b/test/e2e/suite/issuers/vault/certificaterequest/approle_custom_mount.go deleted file mode 100644 index db80ff562b2..00000000000 --- a/test/e2e/suite/issuers/vault/certificaterequest/approle_custom_mount.go +++ /dev/null @@ -1,186 +0,0 @@ -/* -Copyright 2020 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. -*/ - -package certificaterequest - -import ( - "context" - "crypto/x509" - "net" - "path" - "time" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - - cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" - cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" - "github.com/cert-manager/cert-manager/test/e2e/framework" - "github.com/cert-manager/cert-manager/test/e2e/framework/addon" - vaultaddon "github.com/cert-manager/cert-manager/test/e2e/framework/addon/vault" - "github.com/cert-manager/cert-manager/test/e2e/util" - "github.com/cert-manager/cert-manager/test/unit/gen" -) - -var _ = framework.CertManagerDescribe("Vault Issuer CertificateRequest (AppRole with a custom mount path)", func() { - runVaultCustomAppRoleTests(cmapi.IssuerKind) -}) - -var _ = framework.CertManagerDescribe("Vault ClusterIssuer CertificateRequest (AppRole with a custom mount path)", func() { - runVaultCustomAppRoleTests(cmapi.ClusterIssuerKind) -}) - -func runVaultCustomAppRoleTests(issuerKind string) { - f := framework.NewDefaultFramework("create-vault-certificaterequest") - h := f.Helper() - - var ( - vault = &vaultaddon.Vault{ - Base: addon.Base, - Name: "cm-e2e-create-vault-certificaterequest", - } - - crDNSNames = []string{"dnsName1.co", "dnsName2.ninja"} - crIPAddresses = []net.IP{ - []byte{8, 8, 8, 8}, - []byte{1, 1, 1, 1}, - } - ) - - BeforeEach(func() { - vault.Namespace = f.Namespace.Name - }) - - f.RequireAddon(vault) - - rootMount := "root-ca" - intermediateMount := "intermediate-ca" - authPath := "custom/path" - role := "kubernetes-vault" - certificateRequestName := "test-vault-certificaterequest" - vaultSecretAppRoleName := "vault-role-" - vaultPath := path.Join(intermediateMount, "sign", role) - var roleId, secretId, vaultSecretName string - - var vaultInit *vaultaddon.VaultInitializer - - var vaultIssuerName, vaultSecretNamespace string - - BeforeEach(func() { - By("Configuring the Vault server") - - if issuerKind == cmapi.IssuerKind { - vaultSecretNamespace = f.Namespace.Name - } else { - vaultSecretNamespace = f.Config.Addons.CertManager.ClusterResourceNamespace - } - - vaultInit = &vaultaddon.VaultInitializer{ - Details: *vault.Details(), - RootMount: rootMount, - IntermediateMount: intermediateMount, - Role: role, - AppRoleAuthPath: authPath, - } - err := vaultInit.Init() - Expect(err).NotTo(HaveOccurred()) - err = vaultInit.Setup() - Expect(err).NotTo(HaveOccurred()) - roleId, secretId, err = vaultInit.CreateAppRole() - Expect(err).NotTo(HaveOccurred()) - sec, err := f.KubeClientSet.CoreV1().Secrets(vaultSecretNamespace).Create(context.TODO(), vaultaddon.NewVaultAppRoleSecret(vaultSecretAppRoleName, secretId), metav1.CreateOptions{}) - Expect(err).NotTo(HaveOccurred()) - - vaultSecretName = sec.Name - }) - - JustAfterEach(func() { - By("Cleaning up") - Expect(vaultInit.Clean()).NotTo(HaveOccurred()) - - if issuerKind == cmapi.IssuerKind { - f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Delete(context.TODO(), vaultIssuerName, metav1.DeleteOptions{}) - } else { - f.CertManagerClientSet.CertmanagerV1().ClusterIssuers().Delete(context.TODO(), vaultIssuerName, metav1.DeleteOptions{}) - } - - f.KubeClientSet.CoreV1().Secrets(vaultSecretNamespace).Delete(context.TODO(), vaultSecretName, metav1.DeleteOptions{}) - }) - - It("should generate a new valid certificate", func() { - By("Creating an Issuer") - vaultURL := vault.Details().Host - - crClient := f.CertManagerClientSet.CertmanagerV1().CertificateRequests(f.Namespace.Name) - - var err error - if issuerKind == cmapi.IssuerKind { - vaultIssuer := gen.IssuerWithRandomName("test-vault-issuer-", - gen.SetIssuerNamespace(f.Namespace.Name), - gen.SetIssuerVaultURL(vaultURL), - gen.SetIssuerVaultPath(vaultPath), - gen.SetIssuerVaultCABundle(vault.Details().VaultCA), - gen.SetIssuerVaultAppRoleAuth("secretkey", vaultSecretName, roleId, authPath)) - iss, err := f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Create(context.TODO(), vaultIssuer, metav1.CreateOptions{}) - Expect(err).NotTo(HaveOccurred()) - - vaultIssuerName = iss.Name - } else { - vaultIssuer := gen.ClusterIssuerWithRandomName("test-vault-issuer-", - gen.SetIssuerVaultURL(vaultURL), - gen.SetIssuerVaultPath(vaultPath), - gen.SetIssuerVaultCABundle(vault.Details().VaultCA), - gen.SetIssuerVaultAppRoleAuth("secretkey", vaultSecretName, roleId, authPath)) - iss, err := f.CertManagerClientSet.CertmanagerV1().ClusterIssuers().Create(context.TODO(), vaultIssuer, metav1.CreateOptions{}) - Expect(err).NotTo(HaveOccurred()) - - vaultIssuerName = iss.Name - } - - By("Waiting for Issuer to become Ready") - if issuerKind == cmapi.IssuerKind { - err = util.WaitForIssuerCondition(f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name), - vaultIssuerName, - cmapi.IssuerCondition{ - Type: cmapi.IssuerConditionReady, - Status: cmmeta.ConditionTrue, - }) - } else { - err = util.WaitForClusterIssuerCondition(f.CertManagerClientSet.CertmanagerV1().ClusterIssuers(), - vaultIssuerName, - cmapi.IssuerCondition{ - Type: cmapi.IssuerConditionReady, - Status: cmmeta.ConditionTrue, - }) - } - - Expect(err).NotTo(HaveOccurred()) - - By("Creating a CertificateRequest") - cr, key, err := util.NewCertManagerBasicCertificateRequest(certificateRequestName, vaultIssuerName, - issuerKind, &metav1.Duration{ - Duration: time.Hour * 24 * 90, - }, - crDNSNames, crIPAddresses, nil, x509.RSA) - Expect(err).NotTo(HaveOccurred()) - _, err = crClient.Create(context.TODO(), cr, metav1.CreateOptions{}) - Expect(err).NotTo(HaveOccurred()) - - err = h.WaitCertificateRequestIssuedValid(f.Namespace.Name, certificateRequestName, time.Minute*5, key) - Expect(err).NotTo(HaveOccurred()) - }) -} diff --git a/test/e2e/suite/issuers/vault/import.go b/test/e2e/suite/issuers/vault/import.go index 56481333b2a..d5c11783f94 100644 --- a/test/e2e/suite/issuers/vault/import.go +++ b/test/e2e/suite/issuers/vault/import.go @@ -17,6 +17,6 @@ limitations under the License. package vault import ( - _ "github.com/cert-manager/cert-manager/test/e2e/suite/issuers/vault/certificate" - _ "github.com/cert-manager/cert-manager/test/e2e/suite/issuers/vault/certificaterequest" + _ "github.com/cert-manager/cert-manager/e2e-tests/suite/issuers/vault/certificate" + _ "github.com/cert-manager/cert-manager/e2e-tests/suite/issuers/vault/certificaterequest" ) diff --git a/test/e2e/suite/issuers/vault/issuer.go b/test/e2e/suite/issuers/vault/issuer.go index c8e8b742d83..3d81ef47ee8 100644 --- a/test/e2e/suite/issuers/vault/issuer.go +++ b/test/e2e/suite/issuers/vault/issuer.go @@ -18,118 +18,99 @@ package vault import ( "context" - "fmt" - "path" - "time" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/rand" + "github.com/cert-manager/cert-manager/e2e-tests/framework" + "github.com/cert-manager/cert-manager/e2e-tests/framework/addon" + vaultaddon "github.com/cert-manager/cert-manager/e2e-tests/framework/addon/vault" + e2eutil "github.com/cert-manager/cert-manager/e2e-tests/util" v1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" - "github.com/cert-manager/cert-manager/test/e2e/framework" - "github.com/cert-manager/cert-manager/test/e2e/framework/addon" - vaultaddon "github.com/cert-manager/cert-manager/test/e2e/framework/addon/vault" - "github.com/cert-manager/cert-manager/test/e2e/util" "github.com/cert-manager/cert-manager/test/unit/gen" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" ) var _ = framework.CertManagerDescribe("Vault Issuer", func() { f := framework.NewDefaultFramework("create-vault-issuer") - var ( - vault = &vaultaddon.Vault{ - Base: addon.Base, - Name: "cm-e2e-create-vault-issuer", - } - ) - - BeforeEach(func() { - vault.Namespace = f.Namespace.Name - }) - - f.RequireAddon(vault) - - issuerName := "test-vault-issuer" - rootMount := "root-ca" - intermediateMount := "intermediate-ca" - role := "kubernetes-vault" - vaultSecretAppRoleName := "vault-role-" - vaultSecretTokenName := "vault-token" + issuerGeneratorName := "test-vault-issuer-" + var issuerName string vaultSecretServiceAccount := "vault-serviceaccount" - vaultKubernetesRoleName := "kubernetes-role" - vaultPath := path.Join(intermediateMount, "sign", role) - appRoleAuthPath := "approle" - kubernetesAuthPath := "/v1/auth/kubernetes" var roleId, secretId, vaultSecretName string - var vaultInit *vaultaddon.VaultInitializer - - BeforeEach(func() { - By("Configuring the Vault server") - - apiHost := "https://kubernetes.default.svc.cluster.local" // since vault is running in-cluster - caCert := string(f.KubeClientConfig.CAData) - Expect(apiHost).NotTo(BeEmpty()) - Expect(caCert).NotTo(BeEmpty()) + appRoleSecretGeneratorName := "vault-approle-secret-" + var setup *vaultaddon.VaultInitializer - vaultInit = &vaultaddon.VaultInitializer{ - Details: *vault.Details(), - RootMount: rootMount, - IntermediateMount: intermediateMount, - Role: role, - AppRoleAuthPath: appRoleAuthPath, - APIServerURL: apiHost, - APIServerCA: caCert, - } + BeforeEach(func(testingCtx context.Context) { + By("Configuring the Vault server") - err := vaultInit.Init() - Expect(err).NotTo(HaveOccurred()) - err = vaultInit.Setup() - Expect(err).NotTo(HaveOccurred()) - roleId, secretId, err = vaultInit.CreateAppRole() + setup = vaultaddon.NewVaultInitializerAllAuth( + addon.Base.Details().KubeClient, + *addon.Vault.Details(), + false, + "https://kubernetes.default.svc.cluster.local", + ) + Expect(setup.Init(testingCtx)).NotTo(HaveOccurred(), "failed to init vault") + Expect(setup.Setup(testingCtx)).NotTo(HaveOccurred(), "failed to setup vault") + + var err error + roleId, secretId, err = setup.CreateAppRole(testingCtx) Expect(err).NotTo(HaveOccurred()) + issuerName = "" + vaultSecretName = "" + By("creating a service account for Vault authentication") - err = vaultInit.CreateKubernetesRole(f.KubeClientSet, f.Namespace.Name, vaultKubernetesRoleName, vaultSecretServiceAccount) + err = setup.CreateKubernetesRole(testingCtx, f.KubeClientSet, f.Namespace.Name, vaultSecretServiceAccount) Expect(err).NotTo(HaveOccurred()) }) - JustAfterEach(func() { + JustAfterEach(func(testingCtx context.Context) { By("Cleaning up AppRole") - f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Delete(context.TODO(), issuerName, metav1.DeleteOptions{}) - f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Delete(context.TODO(), vaultSecretName, metav1.DeleteOptions{}) - vaultInit.CleanAppRole() + if issuerName != "" { // When we test validation errors, the issuer won't be created + err := f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Delete(testingCtx, issuerName, metav1.DeleteOptions{}) + Expect(err).NotTo(HaveOccurred()) + } + if vaultSecretName != "" { + err := f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Delete(testingCtx, vaultSecretName, metav1.DeleteOptions{}) + Expect(err).NotTo(HaveOccurred()) + } + err := setup.CleanAppRole(testingCtx) + Expect(err).NotTo(HaveOccurred()) By("Cleaning up Kubernetes") - vaultInit.CleanKubernetesRole(f.KubeClientSet, f.Namespace.Name, vaultKubernetesRoleName, vaultSecretServiceAccount) + err = setup.CleanKubernetesRole(testingCtx, f.KubeClientSet, f.Namespace.Name, vaultSecretServiceAccount) + Expect(err).NotTo(HaveOccurred()) By("Cleaning up Vault") - Expect(vaultInit.Clean()).NotTo(HaveOccurred()) + Expect(setup.Clean(testingCtx)).NotTo(HaveOccurred()) }) - const vaultDefaultDuration = time.Hour * 24 * 90 - - It("should be ready with a valid AppRole", func() { - sec, err := f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Create(context.TODO(), vaultaddon.NewVaultAppRoleSecret(vaultSecretAppRoleName, secretId), metav1.CreateOptions{}) + It("should be ready with a valid AppRole", func(testingCtx context.Context) { + sec, err := f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Create(testingCtx, vaultaddon.NewVaultAppRoleSecret(appRoleSecretGeneratorName, secretId), metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred()) vaultSecretName = sec.Name - vaultIssuer := gen.IssuerWithRandomName(issuerName, + vaultIssuer := gen.IssuerWithRandomName(issuerGeneratorName, gen.SetIssuerNamespace(f.Namespace.Name), - gen.SetIssuerVaultURL(vault.Details().Host), - gen.SetIssuerVaultPath(vaultPath), - gen.SetIssuerVaultCABundle(vault.Details().VaultCA), - gen.SetIssuerVaultAppRoleAuth("secretkey", vaultSecretName, roleId, appRoleAuthPath)) - iss, err := f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Create(context.TODO(), vaultIssuer, metav1.CreateOptions{}) + gen.SetIssuerVaultURL(addon.Vault.Details().URL), + gen.SetIssuerVaultPath(setup.IntermediateSignPath()), + gen.SetIssuerVaultCABundle(addon.Vault.Details().VaultCA), + gen.SetIssuerVaultAppRoleAuth("secretkey", vaultSecretName, roleId, setup.AppRoleAuthPath())) + vaultIssuer, err = f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Create(testingCtx, vaultIssuer, metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred()) + issuerName = vaultIssuer.Name + By("Waiting for Issuer to become Ready") - err = util.WaitForIssuerCondition(f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name), - iss.Name, + err = e2eutil.WaitForIssuerCondition(testingCtx, f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name), + vaultIssuer.Name, v1.IssuerCondition{ Type: v1.IssuerConditionReady, Status: cmmeta.ConditionTrue, @@ -137,20 +118,22 @@ var _ = framework.CertManagerDescribe("Vault Issuer", func() { Expect(err).NotTo(HaveOccurred()) }) - It("should fail to init with missing Vault AppRole", func() { + It("should fail to init with missing Vault AppRole", func(testingCtx context.Context) { By("Creating an Issuer") - vaultIssuer := gen.IssuerWithRandomName(issuerName, + vaultIssuer := gen.IssuerWithRandomName(issuerGeneratorName, gen.SetIssuerNamespace(f.Namespace.Name), - gen.SetIssuerVaultURL(vault.Details().Host), - gen.SetIssuerVaultPath(vaultPath), - gen.SetIssuerVaultCABundle(vault.Details().VaultCA), - gen.SetIssuerVaultAppRoleAuth("secretkey", vaultSecretAppRoleName, roleId, appRoleAuthPath)) - iss, err := f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Create(context.TODO(), vaultIssuer, metav1.CreateOptions{}) + gen.SetIssuerVaultURL(addon.Vault.Details().URL), + gen.SetIssuerVaultPath(setup.IntermediateSignPath()), + gen.SetIssuerVaultCABundle(addon.Vault.Details().VaultCA), + gen.SetIssuerVaultAppRoleAuth("secretkey", roleId, setup.Role(), setup.AppRoleAuthPath())) + vaultIssuer, err := f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Create(testingCtx, vaultIssuer, metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred()) + issuerName = vaultIssuer.Name + By("Waiting for Issuer to become Ready") - err = util.WaitForIssuerCondition(f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name), - iss.Name, + err = e2eutil.WaitForIssuerCondition(testingCtx, f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name), + vaultIssuer.Name, v1.IssuerCondition{ Type: v1.IssuerConditionReady, Status: cmmeta.ConditionFalse, @@ -158,20 +141,22 @@ var _ = framework.CertManagerDescribe("Vault Issuer", func() { Expect(err).NotTo(HaveOccurred()) }) - It("should fail to init with missing Vault Token", func() { + It("should fail to init with missing Vault Token", func(testingCtx context.Context) { By("Creating an Issuer") - vaultIssuer := gen.Issuer(issuerName, + vaultIssuer := gen.IssuerWithRandomName(issuerGeneratorName, gen.SetIssuerNamespace(f.Namespace.Name), - gen.SetIssuerVaultURL(vault.Details().Host), - gen.SetIssuerVaultPath(vaultPath), - gen.SetIssuerVaultCABundle(vault.Details().VaultCA), - gen.SetIssuerVaultTokenAuth("secretkey", vaultSecretTokenName)) - _, err := f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Create(context.TODO(), vaultIssuer, metav1.CreateOptions{}) + gen.SetIssuerVaultURL(addon.Vault.Details().URL), + gen.SetIssuerVaultPath(setup.IntermediateSignPath()), + gen.SetIssuerVaultCABundle(addon.Vault.Details().VaultCA), + gen.SetIssuerVaultTokenAuth("secretkey", "vault-token")) + vaultIssuer, err := f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Create(testingCtx, vaultIssuer, metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred()) + issuerName = vaultIssuer.Name + By("Waiting for Issuer to become Ready") - err = util.WaitForIssuerCondition(f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name), - issuerName, + err = e2eutil.WaitForIssuerCondition(testingCtx, f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name), + vaultIssuer.Name, v1.IssuerCondition{ Type: v1.IssuerConditionReady, Status: cmmeta.ConditionFalse, @@ -179,22 +164,25 @@ var _ = framework.CertManagerDescribe("Vault Issuer", func() { Expect(err).NotTo(HaveOccurred()) }) - It("should be ready with a valid Kubernetes Role and ServiceAccount Secret", func() { - _, err := f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Create(context.TODO(), vaultaddon.NewVaultKubernetesSecret(vaultSecretServiceAccount, vaultSecretServiceAccount), metav1.CreateOptions{}) + It("should be ready with a valid Kubernetes Role and ServiceAccount Secret", func(testingCtx context.Context) { + saTokenSecretName := "vault-sa-secret-" + rand.String(5) + _, err := f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Create(testingCtx, vaultaddon.NewVaultKubernetesSecret(saTokenSecretName, vaultSecretServiceAccount), metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred()) - vaultIssuer := gen.Issuer(issuerName, + vaultIssuer := gen.IssuerWithRandomName(issuerGeneratorName, gen.SetIssuerNamespace(f.Namespace.Name), - gen.SetIssuerVaultURL(vault.Details().Host), - gen.SetIssuerVaultPath(vaultPath), - gen.SetIssuerVaultCABundle(vault.Details().VaultCA), - gen.SetIssuerVaultKubernetesAuth("token", vaultSecretServiceAccount, vaultKubernetesRoleName, kubernetesAuthPath)) - _, err = f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Create(context.TODO(), vaultIssuer, metav1.CreateOptions{}) + gen.SetIssuerVaultURL(addon.Vault.Details().URL), + gen.SetIssuerVaultPath(setup.IntermediateSignPath()), + gen.SetIssuerVaultCABundle(addon.Vault.Details().VaultCA), + gen.SetIssuerVaultKubernetesAuthSecret("token", saTokenSecretName, setup.Role(), setup.KubernetesAuthPath())) + vaultIssuer, err = f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Create(testingCtx, vaultIssuer, metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred()) + issuerName = vaultIssuer.Name + By("Waiting for Issuer to become Ready") - err = util.WaitForIssuerCondition(f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name), - issuerName, + err = e2eutil.WaitForIssuerCondition(testingCtx, f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name), + vaultIssuer.Name, v1.IssuerCondition{ Type: v1.IssuerConditionReady, Status: cmmeta.ConditionTrue, @@ -202,19 +190,25 @@ var _ = framework.CertManagerDescribe("Vault Issuer", func() { Expect(err).NotTo(HaveOccurred()) }) - It("should fail to init with missing Kubernetes Role", func() { + It("should fail to init with missing Kubernetes Role", func(testingCtx context.Context) { + saTokenSecretName := "vault-sa-secret-" + rand.String(5) + // we test without creating the secret + By("Creating an Issuer") - vaultIssuer := gen.Issuer(issuerName, + vaultIssuer := gen.IssuerWithRandomName(issuerGeneratorName, gen.SetIssuerNamespace(f.Namespace.Name), - gen.SetIssuerVaultURL(vault.Details().Host), - gen.SetIssuerVaultPath(vaultPath), - gen.SetIssuerVaultCABundle(vault.Details().VaultCA), - gen.SetIssuerVaultKubernetesAuth("token", vaultSecretServiceAccount, vaultKubernetesRoleName, kubernetesAuthPath)) - _, err := f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Create(context.TODO(), vaultIssuer, metav1.CreateOptions{}) + gen.SetIssuerVaultURL(addon.Vault.Details().URL), + gen.SetIssuerVaultPath(setup.IntermediateSignPath()), + gen.SetIssuerVaultCABundle(addon.Vault.Details().VaultCA), + gen.SetIssuerVaultKubernetesAuthSecret("token", saTokenSecretName, setup.Role(), setup.KubernetesAuthPath())) + vaultIssuer, err := f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Create(testingCtx, vaultIssuer, metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred()) + + issuerName = vaultIssuer.Name + By("Waiting for Issuer to become Ready") - err = util.WaitForIssuerCondition(f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name), - issuerName, + err = e2eutil.WaitForIssuerCondition(testingCtx, f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name), + vaultIssuer.Name, v1.IssuerCondition{ Type: v1.IssuerConditionReady, Status: cmmeta.ConditionFalse, @@ -222,50 +216,53 @@ var _ = framework.CertManagerDescribe("Vault Issuer", func() { Expect(err).NotTo(HaveOccurred()) }) - It("should fail to init when both caBundle and caBundleSecretRef are set", func() { + It("should fail to init when both caBundle and caBundleSecretRef are set", func(testingCtx context.Context) { By("Creating an Issuer") - vaultIssuer := gen.Issuer(issuerName, + vaultIssuer := gen.IssuerWithRandomName(issuerGeneratorName, gen.SetIssuerNamespace(f.Namespace.Name), - gen.SetIssuerVaultURL(vault.Details().Host), - gen.SetIssuerVaultPath(vaultPath), - gen.SetIssuerVaultCABundle(vault.Details().VaultCA), + gen.SetIssuerVaultURL(addon.Vault.Details().URL), + gen.SetIssuerVaultPath(setup.IntermediateSignPath()), + gen.SetIssuerVaultCABundle(addon.Vault.Details().VaultCA), gen.SetIssuerVaultCABundleSecretRef("ca-bundle", f.Namespace.Name, "ca.crt")) - _, err := f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Create(context.TODO(), vaultIssuer, metav1.CreateOptions{}) + _, err := f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Create(testingCtx, vaultIssuer, metav1.CreateOptions{}) Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(ContainSubstring(fmt.Sprintf( - "spec.vault.caBundle: Invalid value: %#+v: specified caBundle and caBundleSecretRef cannot be used together", - vault.Details().VaultCA, - ))) + + Expect(err.Error()).To(ContainSubstring( + "spec.vault.caBundle: Invalid value: \"\": specified caBundle and caBundleSecretRef cannot be used together", + )) Expect(err.Error()).To(ContainSubstring("spec.vault.caBundleSecretRef: Invalid value: \"ca-bundle\": specified caBundleSecretRef and caBundle cannot be used together")) }) - It("should be ready with a caBundle from a Kubernetes Secret", func() { - _, err := f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Create(context.TODO(), vaultaddon.NewVaultKubernetesSecret(vaultSecretServiceAccount, vaultSecretServiceAccount), metav1.CreateOptions{}) + It("should be ready with a caBundle from a Kubernetes Secret", func(testingCtx context.Context) { + saTokenSecretName := "vault-sa-secret-" + rand.String(5) + _, err := f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Create(testingCtx, vaultaddon.NewVaultKubernetesSecret(saTokenSecretName, vaultSecretServiceAccount), metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred()) - _, err = f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Create(context.TODO(), &corev1.Secret{ + _, err = f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Create(testingCtx, &corev1.Secret{ ObjectMeta: metav1.ObjectMeta{ Name: "ca-bundle", }, Type: "Opaque", Data: map[string][]byte{ - "ca.crt": vault.Details().VaultCA, + "ca.crt": addon.Vault.Details().VaultCA, }, }, metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred()) - vaultIssuer := gen.Issuer(issuerName, + vaultIssuer := gen.IssuerWithRandomName(issuerGeneratorName, gen.SetIssuerNamespace(f.Namespace.Name), - gen.SetIssuerVaultURL(vault.Details().Host), - gen.SetIssuerVaultPath(vaultPath), + gen.SetIssuerVaultURL(addon.Vault.Details().URL), + gen.SetIssuerVaultPath(setup.IntermediateSignPath()), gen.SetIssuerVaultCABundleSecretRef("ca-bundle", f.Namespace.Name, "ca.crt"), - gen.SetIssuerVaultKubernetesAuth("token", vaultSecretServiceAccount, vaultKubernetesRoleName, kubernetesAuthPath)) - _, err = f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Create(context.TODO(), vaultIssuer, metav1.CreateOptions{}) + gen.SetIssuerVaultKubernetesAuthSecret("token", saTokenSecretName, setup.Role(), setup.KubernetesAuthPath())) + vaultIssuer, err = f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Create(testingCtx, vaultIssuer, metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred()) + issuerName = vaultIssuer.Name + By("Waiting for Issuer to become Ready") - err = util.WaitForIssuerCondition(f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name), - issuerName, + err = e2eutil.WaitForIssuerCondition(testingCtx, f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name), + vaultIssuer.Name, v1.IssuerCondition{ Type: v1.IssuerConditionReady, Status: cmmeta.ConditionTrue, @@ -273,42 +270,45 @@ var _ = framework.CertManagerDescribe("Vault Issuer", func() { Expect(err).NotTo(HaveOccurred()) }) - It("should be eventually ready when the CA bundle secret gets created after the Issuer", func() { - _, err := f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Create(context.TODO(), vaultaddon.NewVaultKubernetesSecret(vaultSecretServiceAccount, vaultSecretServiceAccount), metav1.CreateOptions{}) + It("should be eventually ready when the CA bundle secret gets created after the Issuer", func(testingCtx context.Context) { + saTokenSecretName := "vault-sa-secret-" + rand.String(5) + _, err := f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Create(testingCtx, vaultaddon.NewVaultKubernetesSecret(saTokenSecretName, vaultSecretServiceAccount), metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred()) - vaultIssuer := gen.Issuer(issuerName, + vaultIssuer := gen.IssuerWithRandomName(issuerGeneratorName, gen.SetIssuerNamespace(f.Namespace.Name), - gen.SetIssuerVaultURL(vault.Details().Host), - gen.SetIssuerVaultPath(vaultPath), + gen.SetIssuerVaultURL(addon.Vault.Details().URL), + gen.SetIssuerVaultPath(setup.IntermediateSignPath()), gen.SetIssuerVaultCABundleSecretRef("ca-bundle", f.Namespace.Name, "ca.crt"), - gen.SetIssuerVaultKubernetesAuth("token", vaultSecretServiceAccount, vaultKubernetesRoleName, kubernetesAuthPath)) - _, err = f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Create(context.TODO(), vaultIssuer, metav1.CreateOptions{}) + gen.SetIssuerVaultKubernetesAuthSecret("token", saTokenSecretName, setup.Role(), setup.KubernetesAuthPath())) + vaultIssuer, err = f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Create(testingCtx, vaultIssuer, metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred()) + issuerName = vaultIssuer.Name + By("Validate that the Issuer is not ready yet") - err = util.WaitForIssuerCondition(f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name), - issuerName, + err = e2eutil.WaitForIssuerCondition(testingCtx, f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name), + vaultIssuer.Name, v1.IssuerCondition{ Type: v1.IssuerConditionReady, Status: cmmeta.ConditionFalse, }) Expect(err).NotTo(HaveOccurred()) - _, err = f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Create(context.TODO(), &corev1.Secret{ + _, err = f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Create(testingCtx, &corev1.Secret{ ObjectMeta: metav1.ObjectMeta{ Name: "ca-bundle", }, Type: "Opaque", Data: map[string][]byte{ - "ca.crt": vault.Details().VaultCA, + "ca.crt": addon.Vault.Details().VaultCA, }, }, metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred()) By("Waiting for Issuer to become Ready") - err = util.WaitForIssuerCondition(f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name), - issuerName, + err = e2eutil.WaitForIssuerCondition(testingCtx, f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name), + vaultIssuer.Name, v1.IssuerCondition{ Type: v1.IssuerConditionReady, Status: cmmeta.ConditionTrue, @@ -316,33 +316,36 @@ var _ = framework.CertManagerDescribe("Vault Issuer", func() { Expect(err).NotTo(HaveOccurred()) }) - It("it should become not ready when the CA certificate in the secret changes and doesn't match Vault's CA anymore", func() { - _, err := f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Create(context.TODO(), vaultaddon.NewVaultKubernetesSecret(vaultSecretServiceAccount, vaultSecretServiceAccount), metav1.CreateOptions{}) + It("it should become not ready when the CA certificate in the secret changes and doesn't match Vault's CA anymore", func(testingCtx context.Context) { + saTokenSecretName := "vault-sa-secret-" + rand.String(5) + _, err := f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Create(testingCtx, vaultaddon.NewVaultKubernetesSecret(saTokenSecretName, vaultSecretServiceAccount), metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred()) - _, err = f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Create(context.TODO(), &corev1.Secret{ + _, err = f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Create(testingCtx, &corev1.Secret{ ObjectMeta: metav1.ObjectMeta{ Name: "ca-bundle", }, Type: "Opaque", Data: map[string][]byte{ - "ca.crt": vault.Details().VaultCA, + "ca.crt": addon.Vault.Details().VaultCA, }, }, metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred()) - vaultIssuer := gen.Issuer(issuerName, + vaultIssuer := gen.IssuerWithRandomName(issuerGeneratorName, gen.SetIssuerNamespace(f.Namespace.Name), - gen.SetIssuerVaultURL(vault.Details().Host), - gen.SetIssuerVaultPath(vaultPath), + gen.SetIssuerVaultURL(addon.Vault.Details().URL), + gen.SetIssuerVaultPath(setup.IntermediateSignPath()), gen.SetIssuerVaultCABundleSecretRef("ca-bundle", f.Namespace.Name, "ca.crt"), - gen.SetIssuerVaultKubernetesAuth("token", vaultSecretServiceAccount, vaultKubernetesRoleName, kubernetesAuthPath)) - _, err = f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Create(context.TODO(), vaultIssuer, metav1.CreateOptions{}) + gen.SetIssuerVaultKubernetesAuthSecret("token", saTokenSecretName, setup.Role(), setup.KubernetesAuthPath())) + vaultIssuer, err = f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Create(testingCtx, vaultIssuer, metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred()) + issuerName = vaultIssuer.Name + By("Waiting for Issuer to become Ready") - err = util.WaitForIssuerCondition(f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name), - issuerName, + err = e2eutil.WaitForIssuerCondition(testingCtx, f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name), + vaultIssuer.Name, v1.IssuerCondition{ Type: v1.IssuerConditionReady, Status: cmmeta.ConditionTrue, @@ -350,8 +353,9 @@ var _ = framework.CertManagerDescribe("Vault Issuer", func() { Expect(err).NotTo(HaveOccurred()) By("Updating CA bundle") - public, _, err := vault.GenerateCA() - _, err = f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Update(context.TODO(), &corev1.Secret{ + public, _, err := vaultaddon.GenerateCA() + Expect(err).NotTo(HaveOccurred()) + _, err = f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Update(testingCtx, &corev1.Secret{ ObjectMeta: metav1.ObjectMeta{ Name: "ca-bundle", }, @@ -362,12 +366,44 @@ var _ = framework.CertManagerDescribe("Vault Issuer", func() { Expect(err).NotTo(HaveOccurred()) By("Validate that the issuer isn't ready anymore due to Vault still using the old certificate") - err = util.WaitForIssuerCondition(f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name), - issuerName, + err = e2eutil.WaitForIssuerCondition(testingCtx, f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name), + vaultIssuer.Name, v1.IssuerCondition{ Type: v1.IssuerConditionReady, Status: cmmeta.ConditionFalse, }) Expect(err).NotTo(HaveOccurred()) }) + It("should be ready with a valid serviceAccountRef", func(testingCtx context.Context) { + // Note that we reuse the same service account as for the Kubernetes + // auth based on secretRef. There should be no problem doing so. + By("Creating the Role and RoleBinding to let cert-manager use TokenRequest for the ServiceAccount") + err := vaultaddon.CreateKubernetesRoleForServiceAccountRefAuth(testingCtx, f.KubeClientSet, setup.Role(), f.Namespace.Name, vaultSecretServiceAccount) + Expect(err).NotTo(HaveOccurred()) + defer func() { + err := vaultaddon.CleanKubernetesRoleForServiceAccountRefAuth(testingCtx, f.KubeClientSet, setup.Role(), f.Namespace.Name, vaultSecretServiceAccount) + Expect(err).NotTo(HaveOccurred()) + }() + + By("Creating an Issuer") + vaultIssuer := gen.IssuerWithRandomName(issuerGeneratorName, + gen.SetIssuerNamespace(f.Namespace.Name), + gen.SetIssuerVaultURL(addon.Vault.Details().URL), + gen.SetIssuerVaultPath(setup.IntermediateSignPath()), + gen.SetIssuerVaultCABundle(addon.Vault.Details().VaultCA), + gen.SetIssuerVaultKubernetesAuthServiceAccount(vaultSecretServiceAccount, setup.Role(), setup.KubernetesAuthPath())) + vaultIssuer, err = f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Create(testingCtx, vaultIssuer, metav1.CreateOptions{}) + Expect(err).NotTo(HaveOccurred()) + + issuerName = vaultIssuer.Name + + By("Waiting for Issuer to become Ready") + err = e2eutil.WaitForIssuerCondition(testingCtx, f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name), + vaultIssuer.Name, + v1.IssuerCondition{ + Type: v1.IssuerConditionReady, + Status: cmmeta.ConditionTrue, + }) + Expect(err).NotTo(HaveOccurred()) + }) }) diff --git a/test/e2e/suite/issuers/vault/mtls.go b/test/e2e/suite/issuers/vault/mtls.go new file mode 100644 index 00000000000..979dbde7545 --- /dev/null +++ b/test/e2e/suite/issuers/vault/mtls.go @@ -0,0 +1,509 @@ +/* +Copyright 2020 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. +*/ + +package vault + +import ( + "context" + + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/rand" + + "github.com/cert-manager/cert-manager/e2e-tests/framework" + "github.com/cert-manager/cert-manager/e2e-tests/framework/addon" + vaultaddon "github.com/cert-manager/cert-manager/e2e-tests/framework/addon/vault" + e2eutil "github.com/cert-manager/cert-manager/e2e-tests/util" + v1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" + cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" + "github.com/cert-manager/cert-manager/test/unit/gen" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +var _ = framework.CertManagerDescribe("Vault Issuer [mtls]", func() { + f := framework.NewDefaultFramework("create-vault-issuer") + + issuerGeneratorName := "test-vault-issuer-" + var issuerName string + vaultSecretServiceAccount := "vault-serviceaccount" + vaultClientCertificateSecretName := "vault-client-cert-secret-" + rand.String(5) + var roleId, secretId, vaultSecretName string + + appRoleSecretGeneratorName := "vault-approle-secret-" + var setup *vaultaddon.VaultInitializer + + details := addon.VaultEnforceMtls.Details() + + BeforeEach(func(testingCtx context.Context) { + By("Configuring the Vault server") + + setup = vaultaddon.NewVaultInitializerAllAuth( + addon.Base.Details().KubeClient, + *details, + false, + "https://kubernetes.default.svc.cluster.local", + ) + Expect(setup.Init(testingCtx)).NotTo(HaveOccurred(), "failed to init vault") + Expect(setup.Setup(testingCtx)).NotTo(HaveOccurred(), "failed to setup vault") + + var err error + roleId, secretId, err = setup.CreateAppRole(testingCtx) + Expect(err).NotTo(HaveOccurred()) + + issuerName = "" + vaultSecretName = "" + + By("creating a service account for Vault authentication") + err = setup.CreateKubernetesRole(testingCtx, f.KubeClientSet, f.Namespace.Name, vaultSecretServiceAccount) + Expect(err).NotTo(HaveOccurred()) + + By("creating a client certificate for Vault mTLS") + secret := vaultaddon.NewVaultClientCertificateSecret(vaultClientCertificateSecretName, details.VaultClientCertificate, details.VaultClientPrivateKey) + _, err = f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Create(testingCtx, secret, metav1.CreateOptions{}) + Expect(err).NotTo(HaveOccurred()) + }) + + JustAfterEach(func(testingCtx context.Context) { + By("Cleaning up AppRole") + if issuerName != "" { // When we test validation errors, the issuer won't be created + err := f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Delete(testingCtx, issuerName, metav1.DeleteOptions{}) + Expect(err).NotTo(HaveOccurred()) + } + if vaultSecretName != "" { + err := f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Delete(testingCtx, vaultSecretName, metav1.DeleteOptions{}) + Expect(err).NotTo(HaveOccurred()) + } + err := setup.CleanAppRole(testingCtx) + Expect(err).NotTo(HaveOccurred()) + + By("Cleaning up Kubernetes") + err = setup.CleanKubernetesRole(testingCtx, f.KubeClientSet, f.Namespace.Name, vaultSecretServiceAccount) + Expect(err).NotTo(HaveOccurred()) + + By("Cleaning up Vault") + Expect(setup.Clean(testingCtx)).NotTo(HaveOccurred()) + }) + + It("should be ready with a valid AppRole", func(testingCtx context.Context) { + sec, err := f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Create(testingCtx, vaultaddon.NewVaultAppRoleSecret(appRoleSecretGeneratorName, secretId), metav1.CreateOptions{}) + Expect(err).NotTo(HaveOccurred()) + + vaultSecretName = sec.Name + + vaultIssuer := gen.IssuerWithRandomName(issuerGeneratorName, + gen.SetIssuerNamespace(f.Namespace.Name), + gen.SetIssuerVaultURL(details.URL), + gen.SetIssuerVaultPath(setup.IntermediateSignPath()), + gen.SetIssuerVaultCABundle(details.VaultCA), + gen.SetIssuerVaultClientCertSecretRef(vaultClientCertificateSecretName, corev1.TLSCertKey), + gen.SetIssuerVaultClientKeySecretRef(vaultClientCertificateSecretName, corev1.TLSPrivateKeyKey), + gen.SetIssuerVaultAppRoleAuth("secretkey", vaultSecretName, roleId, setup.AppRoleAuthPath())) + vaultIssuer, err = f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Create(testingCtx, vaultIssuer, metav1.CreateOptions{}) + Expect(err).NotTo(HaveOccurred()) + + issuerName = vaultIssuer.Name + + By("Waiting for Issuer to become Ready") + err = e2eutil.WaitForIssuerCondition(testingCtx, f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name), + vaultIssuer.Name, + v1.IssuerCondition{ + Type: v1.IssuerConditionReady, + Status: cmmeta.ConditionTrue, + }) + Expect(err).NotTo(HaveOccurred()) + }) + + It("should fail to init with missing client certificates", func(testingCtx context.Context) { + sec, err := f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Create(testingCtx, vaultaddon.NewVaultAppRoleSecret(appRoleSecretGeneratorName, secretId), metav1.CreateOptions{}) + Expect(err).NotTo(HaveOccurred()) + + vaultSecretName = sec.Name + + By("Creating an Issuer") + vaultIssuer := gen.IssuerWithRandomName(issuerGeneratorName, + gen.SetIssuerNamespace(f.Namespace.Name), + gen.SetIssuerVaultURL(details.URL), + gen.SetIssuerVaultPath(setup.IntermediateSignPath()), + gen.SetIssuerVaultCABundle(details.VaultCA), + gen.SetIssuerVaultAppRoleAuth("secretkey", vaultSecretName, roleId, setup.AppRoleAuthPath())) + vaultIssuer, err = f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Create(testingCtx, vaultIssuer, metav1.CreateOptions{}) + Expect(err).NotTo(HaveOccurred()) + + issuerName = vaultIssuer.Name + + By("Waiting for Issuer to become Ready") + err = e2eutil.WaitForIssuerCondition(testingCtx, f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name), + vaultIssuer.Name, + v1.IssuerCondition{ + Type: v1.IssuerConditionReady, + Status: cmmeta.ConditionFalse, + }) + Expect(err).NotTo(HaveOccurred()) + }) + + It("should fail to init with missing Vault AppRole", func(testingCtx context.Context) { + By("Creating an Issuer") + vaultIssuer := gen.IssuerWithRandomName(issuerGeneratorName, + gen.SetIssuerNamespace(f.Namespace.Name), + gen.SetIssuerVaultURL(details.URL), + gen.SetIssuerVaultPath(setup.IntermediateSignPath()), + gen.SetIssuerVaultCABundle(details.VaultCA), + gen.SetIssuerVaultClientCertSecretRef(vaultClientCertificateSecretName, corev1.TLSCertKey), + gen.SetIssuerVaultClientKeySecretRef(vaultClientCertificateSecretName, corev1.TLSPrivateKeyKey), + gen.SetIssuerVaultAppRoleAuth("secretkey", roleId, setup.Role(), setup.AppRoleAuthPath())) + vaultIssuer, err := f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Create(testingCtx, vaultIssuer, metav1.CreateOptions{}) + Expect(err).NotTo(HaveOccurred()) + + issuerName = vaultIssuer.Name + + By("Waiting for Issuer to become Ready") + err = e2eutil.WaitForIssuerCondition(testingCtx, f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name), + vaultIssuer.Name, + v1.IssuerCondition{ + Type: v1.IssuerConditionReady, + Status: cmmeta.ConditionFalse, + }) + Expect(err).NotTo(HaveOccurred()) + }) + + It("should fail to init with missing Vault Token", func(testingCtx context.Context) { + By("Creating an Issuer") + vaultIssuer := gen.IssuerWithRandomName(issuerGeneratorName, + gen.SetIssuerNamespace(f.Namespace.Name), + gen.SetIssuerVaultURL(details.URL), + gen.SetIssuerVaultPath(setup.IntermediateSignPath()), + gen.SetIssuerVaultCABundle(details.VaultCA), + gen.SetIssuerVaultClientCertSecretRef(vaultClientCertificateSecretName, corev1.TLSCertKey), + gen.SetIssuerVaultClientKeySecretRef(vaultClientCertificateSecretName, corev1.TLSPrivateKeyKey), + gen.SetIssuerVaultTokenAuth("secretkey", "vault-token")) + vaultIssuer, err := f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Create(testingCtx, vaultIssuer, metav1.CreateOptions{}) + Expect(err).NotTo(HaveOccurred()) + + issuerName = vaultIssuer.Name + + By("Waiting for Issuer to become Ready") + err = e2eutil.WaitForIssuerCondition(testingCtx, f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name), + vaultIssuer.Name, + v1.IssuerCondition{ + Type: v1.IssuerConditionReady, + Status: cmmeta.ConditionFalse, + }) + Expect(err).NotTo(HaveOccurred()) + }) + + It("should be ready with a valid Kubernetes Role and ServiceAccount Secret", func(testingCtx context.Context) { + saTokenSecretName := "vault-sa-secret-" + rand.String(5) + _, err := f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Create(testingCtx, vaultaddon.NewVaultKubernetesSecret(saTokenSecretName, vaultSecretServiceAccount), metav1.CreateOptions{}) + Expect(err).NotTo(HaveOccurred()) + + vaultIssuer := gen.IssuerWithRandomName(issuerGeneratorName, + gen.SetIssuerNamespace(f.Namespace.Name), + gen.SetIssuerVaultURL(details.URL), + gen.SetIssuerVaultPath(setup.IntermediateSignPath()), + gen.SetIssuerVaultCABundle(details.VaultCA), + gen.SetIssuerVaultClientCertSecretRef(vaultClientCertificateSecretName, corev1.TLSCertKey), + gen.SetIssuerVaultClientKeySecretRef(vaultClientCertificateSecretName, corev1.TLSPrivateKeyKey), + gen.SetIssuerVaultKubernetesAuthSecret("token", saTokenSecretName, setup.Role(), setup.KubernetesAuthPath())) + vaultIssuer, err = f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Create(testingCtx, vaultIssuer, metav1.CreateOptions{}) + Expect(err).NotTo(HaveOccurred()) + + issuerName = vaultIssuer.Name + + By("Waiting for Issuer to become Ready") + err = e2eutil.WaitForIssuerCondition(testingCtx, f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name), + vaultIssuer.Name, + v1.IssuerCondition{ + Type: v1.IssuerConditionReady, + Status: cmmeta.ConditionTrue, + }) + Expect(err).NotTo(HaveOccurred()) + }) + + It("should fail to init with missing Kubernetes Role", func(testingCtx context.Context) { + saTokenSecretName := "vault-sa-secret-" + rand.String(5) + // we test without creating the secret + + By("Creating an Issuer") + vaultIssuer := gen.IssuerWithRandomName(issuerGeneratorName, + gen.SetIssuerNamespace(f.Namespace.Name), + gen.SetIssuerVaultURL(details.URL), + gen.SetIssuerVaultPath(setup.IntermediateSignPath()), + gen.SetIssuerVaultCABundle(details.VaultCA), + gen.SetIssuerVaultClientCertSecretRef(vaultClientCertificateSecretName, corev1.TLSCertKey), + gen.SetIssuerVaultClientKeySecretRef(vaultClientCertificateSecretName, corev1.TLSPrivateKeyKey), + gen.SetIssuerVaultKubernetesAuthSecret("token", saTokenSecretName, setup.Role(), setup.KubernetesAuthPath())) + vaultIssuer, err := f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Create(testingCtx, vaultIssuer, metav1.CreateOptions{}) + Expect(err).NotTo(HaveOccurred()) + + issuerName = vaultIssuer.Name + + By("Waiting for Issuer to become Ready") + err = e2eutil.WaitForIssuerCondition(testingCtx, f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name), + vaultIssuer.Name, + v1.IssuerCondition{ + Type: v1.IssuerConditionReady, + Status: cmmeta.ConditionFalse, + }) + Expect(err).NotTo(HaveOccurred()) + }) + + It("should fail to init when both caBundle and caBundleSecretRef are set", func(testingCtx context.Context) { + By("Creating an Issuer") + vaultIssuer := gen.IssuerWithRandomName(issuerGeneratorName, + gen.SetIssuerNamespace(f.Namespace.Name), + gen.SetIssuerVaultURL(details.URL), + gen.SetIssuerVaultPath(setup.IntermediateSignPath()), + gen.SetIssuerVaultCABundle(details.VaultCA), + gen.SetIssuerVaultClientCertSecretRef(vaultClientCertificateSecretName, corev1.TLSCertKey), + gen.SetIssuerVaultClientKeySecretRef(vaultClientCertificateSecretName, corev1.TLSPrivateKeyKey), + gen.SetIssuerVaultCABundleSecretRef("ca-bundle", f.Namespace.Name, "ca.crt")) + _, err := f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Create(testingCtx, vaultIssuer, metav1.CreateOptions{}) + Expect(err).To(HaveOccurred()) + + Expect(err.Error()).To(ContainSubstring( + "spec.vault.caBundle: Invalid value: \"\": specified caBundle and caBundleSecretRef cannot be used together", + )) + Expect(err.Error()).To(ContainSubstring("spec.vault.caBundleSecretRef: Invalid value: \"ca-bundle\": specified caBundleSecretRef and caBundle cannot be used together")) + }) + + It("should be ready with a caBundle from a Kubernetes Secret", func(testingCtx context.Context) { + saTokenSecretName := "vault-sa-secret-" + rand.String(5) + _, err := f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Create(testingCtx, vaultaddon.NewVaultKubernetesSecret(saTokenSecretName, vaultSecretServiceAccount), metav1.CreateOptions{}) + Expect(err).NotTo(HaveOccurred()) + + _, err = f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Create(testingCtx, &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "ca-bundle", + }, + Type: "Opaque", + Data: map[string][]byte{ + "ca.crt": details.VaultCA, + }, + }, metav1.CreateOptions{}) + Expect(err).NotTo(HaveOccurred()) + + vaultIssuer := gen.IssuerWithRandomName(issuerGeneratorName, + gen.SetIssuerNamespace(f.Namespace.Name), + gen.SetIssuerVaultURL(details.URL), + gen.SetIssuerVaultPath(setup.IntermediateSignPath()), + gen.SetIssuerVaultCABundleSecretRef("ca-bundle", f.Namespace.Name, "ca.crt"), + gen.SetIssuerVaultClientCertSecretRef(vaultClientCertificateSecretName, corev1.TLSCertKey), + gen.SetIssuerVaultClientKeySecretRef(vaultClientCertificateSecretName, corev1.TLSPrivateKeyKey), + gen.SetIssuerVaultKubernetesAuthSecret("token", saTokenSecretName, setup.Role(), setup.KubernetesAuthPath())) + vaultIssuer, err = f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Create(testingCtx, vaultIssuer, metav1.CreateOptions{}) + Expect(err).NotTo(HaveOccurred()) + + issuerName = vaultIssuer.Name + + By("Waiting for Issuer to become Ready") + err = e2eutil.WaitForIssuerCondition(testingCtx, f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name), + vaultIssuer.Name, + v1.IssuerCondition{ + Type: v1.IssuerConditionReady, + Status: cmmeta.ConditionTrue, + }) + Expect(err).NotTo(HaveOccurred()) + }) + + It("should be eventually ready when the CA bundle secret gets created after the Issuer", func(testingCtx context.Context) { + saTokenSecretName := "vault-sa-secret-" + rand.String(5) + _, err := f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Create(testingCtx, vaultaddon.NewVaultKubernetesSecret(saTokenSecretName, vaultSecretServiceAccount), metav1.CreateOptions{}) + Expect(err).NotTo(HaveOccurred()) + + vaultIssuer := gen.IssuerWithRandomName(issuerGeneratorName, + gen.SetIssuerNamespace(f.Namespace.Name), + gen.SetIssuerVaultURL(details.URL), + gen.SetIssuerVaultPath(setup.IntermediateSignPath()), + gen.SetIssuerVaultCABundleSecretRef("ca-bundle", f.Namespace.Name, "ca.crt"), + gen.SetIssuerVaultClientCertSecretRef(vaultClientCertificateSecretName, corev1.TLSCertKey), + gen.SetIssuerVaultClientKeySecretRef(vaultClientCertificateSecretName, corev1.TLSPrivateKeyKey), + gen.SetIssuerVaultKubernetesAuthSecret("token", saTokenSecretName, setup.Role(), setup.KubernetesAuthPath())) + vaultIssuer, err = f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Create(testingCtx, vaultIssuer, metav1.CreateOptions{}) + Expect(err).NotTo(HaveOccurred()) + + issuerName = vaultIssuer.Name + + By("Validate that the Issuer is not ready yet") + err = e2eutil.WaitForIssuerCondition(testingCtx, f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name), + vaultIssuer.Name, + v1.IssuerCondition{ + Type: v1.IssuerConditionReady, + Status: cmmeta.ConditionFalse, + }) + Expect(err).NotTo(HaveOccurred()) + + _, err = f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Create(testingCtx, &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "ca-bundle", + }, + Type: "Opaque", + Data: map[string][]byte{ + "ca.crt": details.VaultCA, + }, + }, metav1.CreateOptions{}) + Expect(err).NotTo(HaveOccurred()) + + By("Waiting for Issuer to become Ready") + err = e2eutil.WaitForIssuerCondition(testingCtx, f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name), + vaultIssuer.Name, + v1.IssuerCondition{ + Type: v1.IssuerConditionReady, + Status: cmmeta.ConditionTrue, + }) + Expect(err).NotTo(HaveOccurred()) + }) + + It("should be eventually ready when the Vault client certificate secret gets created after the Issuer", func(testingCtx context.Context) { + sec, err := f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Create(testingCtx, vaultaddon.NewVaultAppRoleSecret(appRoleSecretGeneratorName, secretId), metav1.CreateOptions{}) + Expect(err).NotTo(HaveOccurred()) + + vaultSecretName = sec.Name + customVaultClientCertificateSecretName := "vault-client-cert-secret-custom-" + rand.String(5) + + vaultIssuer := gen.IssuerWithRandomName(issuerGeneratorName, + gen.SetIssuerNamespace(f.Namespace.Name), + gen.SetIssuerVaultURL(details.URL), + gen.SetIssuerVaultPath(setup.IntermediateSignPath()), + gen.SetIssuerVaultCABundle(details.VaultCA), + gen.SetIssuerVaultClientCertSecretRef(customVaultClientCertificateSecretName, corev1.TLSCertKey), + gen.SetIssuerVaultClientKeySecretRef(customVaultClientCertificateSecretName, corev1.TLSPrivateKeyKey), + gen.SetIssuerVaultAppRoleAuth("secretkey", vaultSecretName, roleId, setup.AppRoleAuthPath())) + vaultIssuer, err = f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Create(testingCtx, vaultIssuer, metav1.CreateOptions{}) + Expect(err).NotTo(HaveOccurred()) + + issuerName = vaultIssuer.Name + + By("Validate that the Issuer is not ready yet") + err = e2eutil.WaitForIssuerCondition(testingCtx, f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name), + vaultIssuer.Name, + v1.IssuerCondition{ + Type: v1.IssuerConditionReady, + Status: cmmeta.ConditionFalse, + }) + Expect(err).NotTo(HaveOccurred()) + + By("creating a client certificate for Vault mTLS") + secret := vaultaddon.NewVaultClientCertificateSecret(customVaultClientCertificateSecretName, details.VaultClientCertificate, details.VaultClientPrivateKey) + _, err = f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Create(testingCtx, secret, metav1.CreateOptions{}) + Expect(err).NotTo(HaveOccurred()) + + By("Waiting for Issuer to become Ready") + err = e2eutil.WaitForIssuerCondition(testingCtx, f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name), + vaultIssuer.Name, + v1.IssuerCondition{ + Type: v1.IssuerConditionReady, + Status: cmmeta.ConditionTrue, + }) + Expect(err).NotTo(HaveOccurred()) + }) + + It("it should become not ready when the CA certificate in the secret changes and doesn't match Vault's CA anymore", func(testingCtx context.Context) { + saTokenSecretName := "vault-sa-secret-" + rand.String(5) + _, err := f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Create(testingCtx, vaultaddon.NewVaultKubernetesSecret(saTokenSecretName, vaultSecretServiceAccount), metav1.CreateOptions{}) + Expect(err).NotTo(HaveOccurred()) + + _, err = f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Create(testingCtx, &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "ca-bundle", + }, + Type: "Opaque", + Data: map[string][]byte{ + "ca.crt": details.VaultCA, + }, + }, metav1.CreateOptions{}) + Expect(err).NotTo(HaveOccurred()) + + vaultIssuer := gen.IssuerWithRandomName(issuerGeneratorName, + gen.SetIssuerNamespace(f.Namespace.Name), + gen.SetIssuerVaultURL(details.URL), + gen.SetIssuerVaultPath(setup.IntermediateSignPath()), + gen.SetIssuerVaultCABundleSecretRef("ca-bundle", f.Namespace.Name, "ca.crt"), + gen.SetIssuerVaultClientCertSecretRef(vaultClientCertificateSecretName, corev1.TLSCertKey), + gen.SetIssuerVaultClientKeySecretRef(vaultClientCertificateSecretName, corev1.TLSPrivateKeyKey), + gen.SetIssuerVaultKubernetesAuthSecret("token", saTokenSecretName, setup.Role(), setup.KubernetesAuthPath())) + vaultIssuer, err = f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Create(testingCtx, vaultIssuer, metav1.CreateOptions{}) + Expect(err).NotTo(HaveOccurred()) + + issuerName = vaultIssuer.Name + + By("Waiting for Issuer to become Ready") + err = e2eutil.WaitForIssuerCondition(testingCtx, f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name), + vaultIssuer.Name, + v1.IssuerCondition{ + Type: v1.IssuerConditionReady, + Status: cmmeta.ConditionTrue, + }) + Expect(err).NotTo(HaveOccurred()) + + By("Updating CA bundle") + public, _, err := vaultaddon.GenerateCA() + Expect(err).NotTo(HaveOccurred()) + _, err = f.KubeClientSet.CoreV1().Secrets(f.Namespace.Name).Update(testingCtx, &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "ca-bundle", + }, + Data: map[string][]byte{ + "ca.crt": public, + }, + }, metav1.UpdateOptions{}) + Expect(err).NotTo(HaveOccurred()) + + By("Validate that the issuer isn't ready anymore due to Vault still using the old certificate") + err = e2eutil.WaitForIssuerCondition(testingCtx, f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name), + vaultIssuer.Name, + v1.IssuerCondition{ + Type: v1.IssuerConditionReady, + Status: cmmeta.ConditionFalse, + }) + Expect(err).NotTo(HaveOccurred()) + }) + It("should be ready with a valid serviceAccountRef", func(testingCtx context.Context) { + // Note that we reuse the same service account as for the Kubernetes + // auth based on secretRef. There should be no problem doing so. + By("Creating the Role and RoleBinding to let cert-manager use TokenRequest for the ServiceAccount") + err := vaultaddon.CreateKubernetesRoleForServiceAccountRefAuth(testingCtx, f.KubeClientSet, setup.Role(), f.Namespace.Name, vaultSecretServiceAccount) + Expect(err).NotTo(HaveOccurred()) + defer func() { + err := vaultaddon.CleanKubernetesRoleForServiceAccountRefAuth(testingCtx, f.KubeClientSet, setup.Role(), f.Namespace.Name, vaultSecretServiceAccount) + Expect(err).NotTo(HaveOccurred()) + }() + + By("Creating an Issuer") + vaultIssuer := gen.IssuerWithRandomName(issuerGeneratorName, + gen.SetIssuerNamespace(f.Namespace.Name), + gen.SetIssuerVaultURL(details.URL), + gen.SetIssuerVaultPath(setup.IntermediateSignPath()), + gen.SetIssuerVaultCABundle(details.VaultCA), + gen.SetIssuerVaultClientCertSecretRef(vaultClientCertificateSecretName, corev1.TLSCertKey), + gen.SetIssuerVaultClientKeySecretRef(vaultClientCertificateSecretName, corev1.TLSPrivateKeyKey), + gen.SetIssuerVaultKubernetesAuthServiceAccount(vaultSecretServiceAccount, setup.Role(), setup.KubernetesAuthPath())) + vaultIssuer, err = f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Create(testingCtx, vaultIssuer, metav1.CreateOptions{}) + Expect(err).NotTo(HaveOccurred()) + + issuerName = vaultIssuer.Name + + By("Waiting for Issuer to become Ready") + err = e2eutil.WaitForIssuerCondition(testingCtx, f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name), + vaultIssuer.Name, + v1.IssuerCondition{ + Type: v1.IssuerConditionReady, + Status: cmmeta.ConditionTrue, + }) + Expect(err).NotTo(HaveOccurred()) + }) +}) diff --git a/test/e2e/suite/issuers/venafi/cloud/setup.go b/test/e2e/suite/issuers/venafi/cloud/setup.go index bab62351a15..e79e38cc8ff 100644 --- a/test/e2e/suite/issuers/venafi/cloud/setup.go +++ b/test/e2e/suite/issuers/venafi/cloud/setup.go @@ -19,15 +19,16 @@ package cloud import ( "context" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "github.com/cert-manager/cert-manager/e2e-tests/framework" + vaddon "github.com/cert-manager/cert-manager/e2e-tests/framework/addon/venafi" + "github.com/cert-manager/cert-manager/e2e-tests/util" cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" - "github.com/cert-manager/cert-manager/test/e2e/framework" - vaddon "github.com/cert-manager/cert-manager/test/e2e/framework/addon/venafi" - "github.com/cert-manager/cert-manager/test/e2e/util" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" ) func CloudDescribe(name string, body func()) bool { @@ -42,26 +43,27 @@ var _ = CloudDescribe("properly configured Venafi Cloud Issuer", func() { cloudAddon = &vaddon.VenafiCloud{} ) - BeforeEach(func() { + BeforeEach(func(testingCtx context.Context) { cloudAddon.Namespace = f.Namespace.Name }) f.RequireAddon(cloudAddon) - AfterEach(func() { + AfterEach(func(testingCtx context.Context) { By("Cleaning up") - f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Delete(context.TODO(), issuer.Name, metav1.DeleteOptions{}) + err := f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Delete(testingCtx, issuer.Name, metav1.DeleteOptions{}) + Expect(err).NotTo(HaveOccurred()) }) - It("should set Ready=True accordingly", func() { + It("should set Ready=True accordingly", func(testingCtx context.Context) { var err error By("Creating a Venafi Cloud Issuer resource") issuer = cloudAddon.Details().BuildIssuer() - issuer, err = f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Create(context.TODO(), issuer, metav1.CreateOptions{}) + issuer, err = f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Create(testingCtx, issuer, metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred()) By("Waiting for Issuer to become Ready") - err = util.WaitForIssuerCondition(f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name), + err = util.WaitForIssuerCondition(testingCtx, f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name), issuer.Name, cmapi.IssuerCondition{ Type: cmapi.IssuerConditionReady, @@ -70,13 +72,13 @@ var _ = CloudDescribe("properly configured Venafi Cloud Issuer", func() { Expect(err).NotTo(HaveOccurred()) }) - It("should set Ready=False with a bad access token", func() { + It("should set Ready=False with a bad access token", func(testingCtx context.Context) { var err error By("Creating a Venafi Cloud Issuer resource") issuer = cloudAddon.Details().BuildIssuer() - issuer, err = f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Create(context.TODO(), issuer, metav1.CreateOptions{}) + issuer, err = f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Create(testingCtx, issuer, metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred()) - err = util.WaitForIssuerCondition(f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name), + err = util.WaitForIssuerCondition(testingCtx, f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name), issuer.Name, cmapi.IssuerCondition{ Type: cmapi.IssuerConditionReady, @@ -85,9 +87,9 @@ var _ = CloudDescribe("properly configured Venafi Cloud Issuer", func() { Expect(err).NotTo(HaveOccurred()) By("Changing the API key to something bad") - err = cloudAddon.SetAPIKey("this_is_a_bad_key") + err = cloudAddon.SetAPIKey(testingCtx, "this_is_a_bad_key") Expect(err).NotTo(HaveOccurred()) - err = util.WaitForIssuerCondition(f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name), + err = util.WaitForIssuerCondition(testingCtx, f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name), issuer.Name, cmapi.IssuerCondition{ Type: cmapi.IssuerConditionReady, diff --git a/test/e2e/suite/issuers/venafi/import.go b/test/e2e/suite/issuers/venafi/import.go index 570581a38db..6b0b791bd3a 100644 --- a/test/e2e/suite/issuers/venafi/import.go +++ b/test/e2e/suite/issuers/venafi/import.go @@ -17,6 +17,6 @@ limitations under the License. package venafi import ( - _ "github.com/cert-manager/cert-manager/test/e2e/suite/issuers/venafi/cloud" - _ "github.com/cert-manager/cert-manager/test/e2e/suite/issuers/venafi/tpp" + _ "github.com/cert-manager/cert-manager/e2e-tests/suite/issuers/venafi/cloud" + _ "github.com/cert-manager/cert-manager/e2e-tests/suite/issuers/venafi/tpp" ) diff --git a/test/e2e/suite/issuers/venafi/tpp/certificate.go b/test/e2e/suite/issuers/venafi/tpp/certificate.go index 694612d0204..5d7ce3cfb99 100644 --- a/test/e2e/suite/issuers/venafi/tpp/certificate.go +++ b/test/e2e/suite/issuers/venafi/tpp/certificate.go @@ -20,16 +20,18 @@ import ( "context" "time" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/rand" + "github.com/cert-manager/cert-manager/e2e-tests/framework" + vaddon "github.com/cert-manager/cert-manager/e2e-tests/framework/addon/venafi" + "github.com/cert-manager/cert-manager/e2e-tests/util" cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" - cmutil "github.com/cert-manager/cert-manager/pkg/util" - "github.com/cert-manager/cert-manager/test/e2e/framework" - vaddon "github.com/cert-manager/cert-manager/test/e2e/framework/addon/venafi" - "github.com/cert-manager/cert-manager/test/e2e/util" + "github.com/cert-manager/cert-manager/test/unit/gen" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" ) var _ = TPPDescribe("Certificate with a properly configured Issuer", func() { @@ -42,23 +44,23 @@ var _ = TPPDescribe("Certificate with a properly configured Issuer", func() { certificateSecretName = "test-venafi-cert-tls" ) - BeforeEach(func() { + BeforeEach(func(testingCtx context.Context) { tppAddon.Namespace = f.Namespace.Name }) f.RequireAddon(tppAddon) // Create the Issuer resource - BeforeEach(func() { + BeforeEach(func(testingCtx context.Context) { var err error By("Creating a Venafi Issuer resource") issuer = tppAddon.Details().BuildIssuer() - issuer, err = f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Create(context.TODO(), issuer, metav1.CreateOptions{}) + issuer, err = f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Create(testingCtx, issuer, metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred()) By("Waiting for Issuer to become Ready") - err = util.WaitForIssuerCondition(f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name), + err = util.WaitForIssuerCondition(testingCtx, f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name), issuer.Name, cmapi.IssuerCondition{ Type: cmapi.IssuerConditionReady, @@ -67,23 +69,33 @@ var _ = TPPDescribe("Certificate with a properly configured Issuer", func() { Expect(err).NotTo(HaveOccurred()) }) - AfterEach(func() { + AfterEach(func(testingCtx context.Context) { By("Cleaning up") - f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Delete(context.TODO(), issuer.Name, metav1.DeleteOptions{}) + if issuer != nil { + err := f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Delete(testingCtx, issuer.Name, metav1.DeleteOptions{}) + Expect(err).NotTo(HaveOccurred()) + } }) - It("should obtain a signed certificate for a single domain", func() { + It("should obtain a signed certificate for a single domain", func(testingCtx context.Context) { certClient := f.CertManagerClientSet.CertmanagerV1().Certificates(f.Namespace.Name) - cert := util.NewCertManagerBasicCertificate(certificateName, certificateSecretName, issuer.Name, cmapi.IssuerKind, nil, nil) - cert.Spec.CommonName = cmutil.RandStringRunes(10) + ".venafi-e2e.example" - By("Creating a Certificate") - cert, err := certClient.Create(context.TODO(), cert, metav1.CreateOptions{}) + cert := gen.Certificate(certificateName, + gen.SetCertificateNamespace(f.Namespace.Name), + gen.SetCertificateSecretName(certificateSecretName), + gen.SetCertificateIssuer(cmmeta.IssuerReference{ + Name: issuer.Name, + Kind: cmapi.IssuerKind, + }), + gen.SetCertificateCommonName(rand.String(10)+".venafi-e2e.example"), + gen.SetCertificateOrganization("test-org"), + ) + cert, err := certClient.Create(testingCtx, cert, metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred()) By("Waiting for the Certificate to be issued...") - cert, err = f.Helper().WaitForCertificateReadyAndDoneIssuing(cert, time.Minute*5) + cert, err = f.Helper().WaitForCertificateReadyAndDoneIssuing(testingCtx, cert, time.Minute*5) Expect(err).NotTo(HaveOccurred()) By("Validating the issued Certificate...") diff --git a/test/e2e/suite/issuers/venafi/tpp/certificaterequest.go b/test/e2e/suite/issuers/venafi/tpp/certificaterequest.go index fc59f968ce6..07f5a00dc2e 100644 --- a/test/e2e/suite/issuers/venafi/tpp/certificaterequest.go +++ b/test/e2e/suite/issuers/venafi/tpp/certificaterequest.go @@ -21,16 +21,18 @@ import ( "crypto/x509" "time" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/rand" + "github.com/cert-manager/cert-manager/e2e-tests/framework" + vaddon "github.com/cert-manager/cert-manager/e2e-tests/framework/addon/venafi" + "github.com/cert-manager/cert-manager/e2e-tests/util" cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" - cmutil "github.com/cert-manager/cert-manager/pkg/util" - "github.com/cert-manager/cert-manager/test/e2e/framework" - vaddon "github.com/cert-manager/cert-manager/test/e2e/framework/addon/venafi" - "github.com/cert-manager/cert-manager/test/e2e/util" + "github.com/cert-manager/cert-manager/test/unit/gen" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" ) var _ = TPPDescribe("CertificateRequest with a properly configured Issuer", func() { @@ -43,23 +45,23 @@ var _ = TPPDescribe("CertificateRequest with a properly configured Issuer", func certificateRequestName = "test-venafi-certificaterequest" ) - BeforeEach(func() { + BeforeEach(func(testingCtx context.Context) { tppAddon.Namespace = f.Namespace.Name }) f.RequireAddon(tppAddon) // Create the Issuer resource - BeforeEach(func() { + BeforeEach(func(testingCtx context.Context) { var err error By("Creating a Venafi Issuer resource") issuer = tppAddon.Details().BuildIssuer() - issuer, err = f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Create(context.TODO(), issuer, metav1.CreateOptions{}) + issuer, err = f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Create(testingCtx, issuer, metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred()) By("Waiting for Issuer to become Ready") - err = util.WaitForIssuerCondition(f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name), + err = util.WaitForIssuerCondition(testingCtx, f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name), issuer.Name, cmapi.IssuerCondition{ Type: cmapi.IssuerConditionReady, @@ -68,25 +70,33 @@ var _ = TPPDescribe("CertificateRequest with a properly configured Issuer", func Expect(err).NotTo(HaveOccurred()) }) - AfterEach(func() { + AfterEach(func(testingCtx context.Context) { By("Cleaning up") - f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Delete(context.TODO(), issuer.Name, metav1.DeleteOptions{}) + if issuer != nil { + err := f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Delete(testingCtx, issuer.Name, metav1.DeleteOptions{}) + Expect(err).NotTo(HaveOccurred()) + } }) - It("should obtain a signed certificate for a single domain", func() { + It("should obtain a signed certificate for a single domain", func(testingCtx context.Context) { crClient := f.CertManagerClientSet.CertmanagerV1().CertificateRequests(f.Namespace.Name) - dnsNames := []string{cmutil.RandStringRunes(10) + ".venafi-e2e.example"} + dnsNames := []string{rand.String(10) + ".venafi-e2e.example"} - cr, key, err := util.NewCertManagerBasicCertificateRequest(certificateRequestName, issuer.Name, cmapi.IssuerKind, nil, dnsNames, nil, nil, x509.RSA) + csr, key, err := gen.CSR(x509.RSA, gen.SetCSRCommonName(dnsNames[0]), gen.SetCSRDNSNames(dnsNames...)) Expect(err).NotTo(HaveOccurred()) + cr := gen.CertificateRequest(certificateRequestName, + gen.SetCertificateRequestNamespace(f.Namespace.Name), + gen.SetCertificateRequestIssuer(cmmeta.IssuerReference{Kind: cmapi.IssuerKind, Name: issuer.Name}), + gen.SetCertificateRequestCSR(csr), + ) By("Creating a CertificateRequest") - _, err = crClient.Create(context.TODO(), cr, metav1.CreateOptions{}) + _, err = crClient.Create(testingCtx, cr, metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred()) By("Verifying the CertificateRequest is valid") - err = h.WaitCertificateRequestIssuedValid(f.Namespace.Name, certificateRequestName, time.Second*30, key) + err = h.WaitCertificateRequestIssuedValid(testingCtx, f.Namespace.Name, certificateRequestName, time.Second*30, key) Expect(err).NotTo(HaveOccurred()) }) }) diff --git a/test/e2e/suite/issuers/venafi/tpp/doc.go b/test/e2e/suite/issuers/venafi/tpp/doc.go index 64350c00561..ca4bca33095 100644 --- a/test/e2e/suite/issuers/venafi/tpp/doc.go +++ b/test/e2e/suite/issuers/venafi/tpp/doc.go @@ -18,7 +18,7 @@ limitations under the License. package tpp import ( - "github.com/cert-manager/cert-manager/test/e2e/framework" + "github.com/cert-manager/cert-manager/e2e-tests/framework" ) func TPPDescribe(name string, body func()) bool { diff --git a/test/e2e/suite/issuers/venafi/tpp/setup.go b/test/e2e/suite/issuers/venafi/tpp/setup.go index 7210444be0c..b25e004d680 100644 --- a/test/e2e/suite/issuers/venafi/tpp/setup.go +++ b/test/e2e/suite/issuers/venafi/tpp/setup.go @@ -19,15 +19,16 @@ package tpp import ( "context" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "github.com/cert-manager/cert-manager/e2e-tests/framework" + vaddon "github.com/cert-manager/cert-manager/e2e-tests/framework/addon/venafi" + "github.com/cert-manager/cert-manager/e2e-tests/util" cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" - "github.com/cert-manager/cert-manager/test/e2e/framework" - vaddon "github.com/cert-manager/cert-manager/test/e2e/framework/addon/venafi" - "github.com/cert-manager/cert-manager/test/e2e/util" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" ) var _ = TPPDescribe("properly configured Venafi TPP Issuer", func() { @@ -38,26 +39,29 @@ var _ = TPPDescribe("properly configured Venafi TPP Issuer", func() { tppAddon = &vaddon.VenafiTPP{} ) - BeforeEach(func() { + BeforeEach(func(testingCtx context.Context) { tppAddon.Namespace = f.Namespace.Name }) f.RequireAddon(tppAddon) - AfterEach(func() { + AfterEach(func(testingCtx context.Context) { By("Cleaning up") - f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Delete(context.TODO(), issuer.Name, metav1.DeleteOptions{}) + if issuer != nil { + err := f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Delete(testingCtx, issuer.Name, metav1.DeleteOptions{}) + Expect(err).NotTo(HaveOccurred()) + } }) - It("should set Ready=True accordingly", func() { + It("should set Ready=True accordingly", func(testingCtx context.Context) { var err error By("Creating a Venafi Issuer resource") issuer = tppAddon.Details().BuildIssuer() - issuer, err = f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Create(context.TODO(), issuer, metav1.CreateOptions{}) + issuer, err = f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Create(testingCtx, issuer, metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred()) By("Waiting for Issuer to become Ready") - err = util.WaitForIssuerCondition(f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name), + err = util.WaitForIssuerCondition(testingCtx, f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name), issuer.Name, cmapi.IssuerCondition{ Type: cmapi.IssuerConditionReady, @@ -66,13 +70,13 @@ var _ = TPPDescribe("properly configured Venafi TPP Issuer", func() { Expect(err).NotTo(HaveOccurred()) }) - It("should set Ready=False with a bad access token", func() { + It("should set Ready=False with a bad access token", func(testingCtx context.Context) { var err error By("Creating a Venafi Issuer resource") issuer = tppAddon.Details().BuildIssuer() - issuer, err = f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Create(context.TODO(), issuer, metav1.CreateOptions{}) + issuer, err = f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name).Create(testingCtx, issuer, metav1.CreateOptions{}) Expect(err).NotTo(HaveOccurred()) - err = util.WaitForIssuerCondition(f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name), + err = util.WaitForIssuerCondition(testingCtx, f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name), issuer.Name, cmapi.IssuerCondition{ Type: cmapi.IssuerConditionReady, @@ -81,9 +85,9 @@ var _ = TPPDescribe("properly configured Venafi TPP Issuer", func() { Expect(err).NotTo(HaveOccurred()) By("Changing the Access Token to something bad") - err = tppAddon.SetAccessToken("this_is_a_bad_token") + err = tppAddon.SetAccessToken(testingCtx, "this_is_a_bad_token") Expect(err).NotTo(HaveOccurred()) - err = util.WaitForIssuerCondition(f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name), + err = util.WaitForIssuerCondition(testingCtx, f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name), issuer.Name, cmapi.IssuerCondition{ Type: cmapi.IssuerConditionReady, diff --git a/test/e2e/suite/serving/cainjector.go b/test/e2e/suite/serving/cainjector.go index 460c60dd305..c7861c74ff7 100644 --- a/test/e2e/suite/serving/cainjector.go +++ b/test/e2e/suite/serving/cainjector.go @@ -21,8 +21,6 @@ import ( "fmt" "time" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" admissionreg "k8s.io/api/admissionregistration/v1" corev1 "k8s.io/api/core/v1" apiext "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" @@ -33,23 +31,26 @@ import ( apireg "k8s.io/kube-aggregator/pkg/apis/apiregistration/v1" "sigs.k8s.io/controller-runtime/pkg/client" - certmanager "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" - v1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" + "github.com/cert-manager/cert-manager/e2e-tests/framework" + "github.com/cert-manager/cert-manager/e2e-tests/util" + "github.com/cert-manager/cert-manager/internal/cainjector/feature" + cmapiv1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" - "github.com/cert-manager/cert-manager/test/e2e/framework" - "github.com/cert-manager/cert-manager/test/e2e/util" + utilfeature "github.com/cert-manager/cert-manager/pkg/util/feature" "github.com/cert-manager/cert-manager/test/unit/gen" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" ) type injectableTest struct { makeInjectable func(namePrefix string) client.Object getCAs func(runtime.Object) [][]byte - subject string disabled string } var _ = framework.CertManagerDescribe("CA Injector", func() { - f := framework.NewDefaultFramework("ca-injector") + f := framework.NewDefaultFramework("cainjector") issuerName := "inject-cert-issuer" secretName := "serving-certs-data" @@ -58,46 +59,54 @@ var _ = framework.CertManagerDescribe("CA Injector", func() { Context("for "+subj+"s", func() { var toCleanup client.Object - BeforeEach(func() { + BeforeEach(func(testingCtx context.Context) { By("creating a self-signing issuer") issuer := gen.Issuer(issuerName, gen.SetIssuerNamespace(f.Namespace.Name), - gen.SetIssuerSelfSigned(v1.SelfSignedIssuer{})) - Expect(f.CRClient.Create(context.Background(), issuer)).To(Succeed()) + gen.SetIssuerSelfSigned(cmapiv1.SelfSignedIssuer{})) + Expect(f.CRClient.Create(testingCtx, issuer)).To(Succeed()) By("Waiting for Issuer to become Ready") - err := util.WaitForIssuerCondition(f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name), + err := util.WaitForIssuerCondition(testingCtx, f.CertManagerClientSet.CertmanagerV1().Issuers(f.Namespace.Name), issuerName, - certmanager.IssuerCondition{ - Type: certmanager.IssuerConditionReady, + cmapiv1.IssuerCondition{ + Type: cmapiv1.IssuerConditionReady, Status: cmmeta.ConditionTrue, }) Expect(err).NotTo(HaveOccurred()) }) - AfterEach(func() { + AfterEach(func(testingCtx context.Context) { if toCleanup == nil { return } - Expect(f.CRClient.Delete(context.Background(), toCleanup)).To(Succeed()) + Expect(f.CRClient.Delete(testingCtx, toCleanup)).To(Succeed()) }) - generalSetup := func(injectable client.Object) (runtime.Object, *certmanager.Certificate) { + generalSetup := func(ctx context.Context, injectable client.Object) (runtime.Object, *cmapiv1.Certificate) { By("creating a " + subj + " pointing to a cert") - Expect(f.CRClient.Create(context.Background(), injectable)).To(Succeed()) + Expect(f.CRClient.Create(ctx, injectable)).To(Succeed()) toCleanup = injectable By("creating a certificate") secretName := types.NamespacedName{Name: secretName, Namespace: f.Namespace.Name} - cert := util.NewCertManagerBasicCertificate("serving-certs", secretName.Name, issuerName, certmanager.IssuerKind, nil, nil) - cert.Namespace = f.Namespace.Name - Expect(f.CRClient.Create(context.Background(), cert)).To(Succeed()) - - cert, err := f.Helper().WaitForCertificateReadyAndDoneIssuing(cert, time.Minute*2) + cert := gen.Certificate("serving-certs", + gen.SetCertificateNamespace(f.Namespace.Name), + gen.SetCertificateSecretName(secretName.Name), + gen.SetCertificateIssuer(cmmeta.IssuerReference{ + Name: issuerName, + Kind: cmapiv1.IssuerKind, + }), + gen.SetCertificateCommonName("test.domain.com"), + gen.SetCertificateOrganization("test-org"), + ) + Expect(f.CRClient.Create(ctx, cert)).To(Succeed()) + + cert, err := f.Helper().WaitForCertificateReadyAndDoneIssuing(ctx, cert, time.Minute*2) Expect(err).NotTo(HaveOccurred(), "failed to wait for Certificate to become Ready") By("grabbing the corresponding secret") var secret corev1.Secret - Expect(f.CRClient.Get(context.Background(), secretName, &secret)).To(Succeed()) + Expect(f.CRClient.Get(ctx, secretName, &secret)).To(Succeed()) By("checking that all webhooks have a populated CA") caData := secret.Data["ca.crt"] @@ -108,7 +117,7 @@ var _ = framework.CertManagerDescribe("CA Injector", func() { } Eventually(func() ([][]byte, error) { newInjectable := injectable.DeepCopyObject().(client.Object) - if err := f.CRClient.Get(context.Background(), types.NamespacedName{Name: injectable.(metav1.Object).GetName()}, newInjectable); err != nil { + if err := f.CRClient.Get(ctx, types.NamespacedName{Name: injectable.(metav1.Object).GetName()}, newInjectable); err != nil { return nil, err } return test.getCAs(newInjectable), nil @@ -117,105 +126,118 @@ var _ = framework.CertManagerDescribe("CA Injector", func() { return injectable, cert } - It("should inject the CA data into all CA fields", func() { + It("should inject the CA data into all CA fields", func(testingCtx context.Context) { if test.disabled != "" { Skip(test.disabled) } - generalSetup(test.makeInjectable("injected")) + generalSetup(testingCtx, test.makeInjectable("injected")) }) - It("should not inject CA into non-annotated objects", func() { + It("should not inject CA into non-annotated objects", func(testingCtx context.Context) { if test.disabled != "" { Skip(test.disabled) } By("creating a validating webhook not pointing to a cert") injectable := test.makeInjectable("non-injected") injectable.(metav1.Object).SetAnnotations(map[string]string{}) // wipe out the inject annotation - Expect(f.CRClient.Create(context.Background(), injectable)).To(Succeed()) + Expect(f.CRClient.Create(testingCtx, injectable)).To(Succeed()) toCleanup = injectable By("expecting the CA data to remain in place") expectedCAs := test.getCAs(injectable) Consistently(func() ([][]byte, error) { newInjectable := injectable.DeepCopyObject().(client.Object) - if err := f.CRClient.Get(context.Background(), types.NamespacedName{Name: injectable.(metav1.Object).GetName()}, newInjectable); err != nil { + if err := f.CRClient.Get(testingCtx, types.NamespacedName{Name: injectable.(metav1.Object).GetName()}, newInjectable); err != nil { return nil, err } return test.getCAs(newInjectable), nil }).Should(Equal(expectedCAs)) }) - It("should update data when the certificate changes", func() { + It("should update data when the certificate changes", func(testingCtx context.Context) { if test.disabled != "" { Skip(test.disabled) } - injectable, cert := generalSetup(test.makeInjectable("changed")) + injectable, cert := generalSetup(testingCtx, test.makeInjectable("changed")) + + By("grabbing the original secret") + var oldSecret corev1.Secret + secretName := types.NamespacedName{Name: cert.Spec.SecretName, Namespace: f.Namespace.Name} + Expect(f.CRClient.Get(testingCtx, secretName, &oldSecret)).To(Succeed()) By("changing the name of the corresponding secret in the cert") - retry.RetryOnConflict(retry.DefaultRetry, func() error { - err := f.CRClient.Get(context.Background(), types.NamespacedName{Name: cert.Name, Namespace: cert.Namespace}, cert) + err := retry.RetryOnConflict(retry.DefaultRetry, func() error { + err := f.CRClient.Get(testingCtx, types.NamespacedName{Name: cert.Name, Namespace: cert.Namespace}, cert) if err != nil { return err } cert.Spec.DNSNames = append(cert.Spec.DNSNames, "something.com") - err = f.CRClient.Update(context.Background(), cert) + err = f.CRClient.Update(testingCtx, cert) if err != nil { return err } return nil }) + Expect(err).NotTo(HaveOccurred()) - cert, err := f.Helper().WaitForCertificateReadyAndDoneIssuing(cert, time.Minute*2) + cert, err = f.Helper().WaitForCertificateReadyAndDoneIssuing(testingCtx, cert, time.Minute*2) Expect(err).NotTo(HaveOccurred(), "failed to wait for Certificate to become updated") By("grabbing the new secret") - var secret corev1.Secret - secretName := types.NamespacedName{Name: cert.Spec.SecretName, Namespace: f.Namespace.Name} - Expect(f.CRClient.Get(context.Background(), secretName, &secret)).To(Succeed()) + var newSecret corev1.Secret + Expect(f.CRClient.Get(testingCtx, secretName, &newSecret)).To(Succeed()) By("verifying that the hooks have the new data") - caData := secret.Data["ca.crt"] expectedLen := len(test.getCAs(injectable)) expectedCAs := make([][]byte, expectedLen) - for i := range expectedCAs { - expectedCAs[i] = caData + if utilfeature.DefaultFeatureGate.Enabled(feature.CAInjectorMerging) { + caData := append(oldSecret.Data["ca.crt"], newSecret.Data["ca.crt"]...) + for i := range expectedCAs { + expectedCAs[i] = caData + } + } else { + caData := newSecret.Data["ca.crt"] + for i := range expectedCAs { + expectedCAs[i] = caData + } } + Eventually(func() ([][]byte, error) { newInjectable := injectable.DeepCopyObject().(client.Object) - if err := f.CRClient.Get(context.Background(), types.NamespacedName{Name: injectable.(metav1.Object).GetName()}, newInjectable); err != nil { + if err := f.CRClient.Get(testingCtx, types.NamespacedName{Name: injectable.(metav1.Object).GetName()}, newInjectable); err != nil { return nil, err } return test.getCAs(newInjectable), nil }, "10s", "2s").Should(Equal(expectedCAs)) }) - It("should ignore objects with invalid annotations", func() { + It("should ignore objects with invalid annotations", func(testingCtx context.Context) { if test.disabled != "" { Skip(test.disabled) } By("creating a validating webhook with an invalid cert name") injectable := test.makeInjectable("invalid") injectable.(metav1.Object).SetAnnotations(map[string]string{ - certmanager.WantInjectAnnotation: "serving-certs", // an invalid annotation + cmapiv1.WantInjectAnnotation: "serving-certs", // an invalid annotation }) - Expect(f.CRClient.Create(context.Background(), injectable)).To(Succeed()) + Expect(f.CRClient.Create(testingCtx, injectable)).To(Succeed()) toCleanup = injectable By("expecting the CA data to remain in place") expectedCAs := test.getCAs(injectable) Consistently(func() ([][]byte, error) { newInjectable := injectable.DeepCopyObject().(client.Object) - if err := f.CRClient.Get(context.Background(), types.NamespacedName{Name: injectable.(metav1.Object).GetName()}, newInjectable); err != nil { + if err := f.CRClient.Get(testingCtx, types.NamespacedName{Name: injectable.(metav1.Object).GetName()}, newInjectable); err != nil { return nil, err } return test.getCAs(newInjectable), nil }).Should(Equal(expectedCAs)) }) - It("should inject the apiserver CA if the inject-apiserver-ca annotation is present", func() { + It("should inject the apiserver CA if the inject-apiserver-ca annotation is present", func(testingCtx context.Context) { if test.disabled != "" { Skip(test.disabled) } @@ -225,9 +247,9 @@ var _ = framework.CertManagerDescribe("CA Injector", func() { By("creating an injectable with the inject-apiserver-ca annotation") injectable := test.makeInjectable("apiserver-ca") injectable.(metav1.Object).SetAnnotations(map[string]string{ - certmanager.WantInjectAPIServerCAAnnotation: "true", + cmapiv1.WantInjectAPIServerCAAnnotation: "true", }) - Expect(f.CRClient.Create(context.Background(), injectable)).To(Succeed()) + Expect(f.CRClient.Create(testingCtx, injectable)).To(Succeed()) toCleanup = injectable By("checking that all webhooks have a populated CA") @@ -239,14 +261,14 @@ var _ = framework.CertManagerDescribe("CA Injector", func() { } Eventually(func() ([][]byte, error) { newInjectable := injectable.DeepCopyObject().(client.Object) - if err := f.CRClient.Get(context.Background(), types.NamespacedName{Name: injectable.(metav1.Object).GetName()}, newInjectable); err != nil { + if err := f.CRClient.Get(testingCtx, types.NamespacedName{Name: injectable.(metav1.Object).GetName()}, newInjectable); err != nil { return nil, err } return test.getCAs(newInjectable), nil }, "1m", "2s").Should(Equal(expectedCAs)) }) - It("should inject a CA directly from a secret if the inject-ca-from-secret annotation is present", func() { + It("should inject a CA directly from a secret if the inject-ca-from-secret annotation is present", func(testingCtx context.Context) { if test.disabled != "" { Skip(test.disabled) } @@ -256,20 +278,20 @@ var _ = framework.CertManagerDescribe("CA Injector", func() { Name: secretName.Name, Namespace: secretName.Namespace, Annotations: map[string]string{ - certmanager.AllowsInjectionFromSecretAnnotation: "true", + cmapiv1.AllowsInjectionFromSecretAnnotation: "true", }, }, } - Expect(f.CRClient.Create(context.Background(), &annotatedSecret)).To(Succeed()) + Expect(f.CRClient.Create(testingCtx, &annotatedSecret)).To(Succeed()) injectable := test.makeInjectable("from-secret") injectable.(metav1.Object).SetAnnotations(map[string]string{ - certmanager.WantInjectFromSecretAnnotation: secretName.String(), + cmapiv1.WantInjectFromSecretAnnotation: secretName.String(), }) - generalSetup(injectable) + generalSetup(testingCtx, injectable) }) - It("should refuse to inject a CA directly from a secret if the allow-direct-injection annotation is not 'true'", func() { + It("should refuse to inject a CA directly from a secret if the allow-direct-injection annotation is not 'true'", func(testingCtx context.Context) { if test.disabled != "" { Skip(test.disabled) } @@ -279,28 +301,36 @@ var _ = framework.CertManagerDescribe("CA Injector", func() { Name: secretName.Name, Namespace: secretName.Namespace, Annotations: map[string]string{ - certmanager.AllowsInjectionFromSecretAnnotation: "false", + cmapiv1.AllowsInjectionFromSecretAnnotation: "false", }, }, } - Expect(f.CRClient.Create(context.Background(), &annotatedSecret)).To(Succeed()) + Expect(f.CRClient.Create(testingCtx, &annotatedSecret)).To(Succeed()) By("creating a " + subj + " pointing to a secret") injectable := test.makeInjectable("from-secret-not-allowed") injectable.(metav1.Object).SetAnnotations(map[string]string{ - certmanager.WantInjectFromSecretAnnotation: secretName.String(), + cmapiv1.WantInjectFromSecretAnnotation: secretName.String(), }) - Expect(f.CRClient.Create(context.Background(), injectable)).To(Succeed()) + Expect(f.CRClient.Create(testingCtx, injectable)).To(Succeed()) toCleanup = injectable By("creating a certificate") - cert := util.NewCertManagerBasicCertificate("serving-certs", secretName.Name, issuerName, certmanager.IssuerKind, nil, nil) - cert.Namespace = f.Namespace.Name - Expect(f.CRClient.Create(context.Background(), cert)).To(Succeed()) + cert := gen.Certificate("serving-certs", + gen.SetCertificateNamespace(f.Namespace.Name), + gen.SetCertificateSecretName(secretName.Name), + gen.SetCertificateIssuer(cmmeta.IssuerReference{ + Name: issuerName, + Kind: cmapiv1.IssuerKind, + }), + gen.SetCertificateCommonName("test.domain.com"), + gen.SetCertificateOrganization("test-org"), + ) + Expect(f.CRClient.Create(testingCtx, cert)).To(Succeed()) By("grabbing the corresponding secret") var secret corev1.Secret - Eventually(func() error { return f.CRClient.Get(context.Background(), secretName, &secret) }, "30s", "2s").Should(Succeed()) + Eventually(func() error { return f.CRClient.Get(testingCtx, secretName, &secret) }, "30s", "2s").Should(Succeed()) By("checking that all webhooks have an empty CA") expectedLen := len(test.getCAs(injectable)) @@ -310,7 +340,7 @@ var _ = framework.CertManagerDescribe("CA Injector", func() { } Consistently(func() ([][]byte, error) { newInjectable := injectable.DeepCopyObject().(client.Object) - if err := f.CRClient.Get(context.Background(), types.NamespacedName{Name: injectable.(metav1.Object).GetName()}, newInjectable); err != nil { + if err := f.CRClient.Get(testingCtx, types.NamespacedName{Name: injectable.(metav1.Object).GetName()}, newInjectable); err != nil { return nil, err } return test.getCAs(newInjectable), nil @@ -328,7 +358,7 @@ var _ = framework.CertManagerDescribe("CA Injector", func() { ObjectMeta: metav1.ObjectMeta{ GenerateName: fmt.Sprintf("%s-hook", namePrefix), Annotations: map[string]string{ - certmanager.WantInjectAnnotation: types.NamespacedName{Name: "serving-certs", Namespace: f.Namespace.Name}.String(), + cmapiv1.WantInjectAnnotation: types.NamespacedName{Name: "serving-certs", Namespace: f.Namespace.Name}.String(), }, }, Webhooks: []admissionreg.ValidatingWebhook{ @@ -371,7 +401,7 @@ var _ = framework.CertManagerDescribe("CA Injector", func() { ObjectMeta: metav1.ObjectMeta{ GenerateName: fmt.Sprintf("%s-hook", namePrefix), Annotations: map[string]string{ - certmanager.WantInjectAnnotation: types.NamespacedName{Name: "serving-certs", Namespace: f.Namespace.Name}.String(), + cmapiv1.WantInjectAnnotation: types.NamespacedName{Name: "serving-certs", Namespace: f.Namespace.Name}.String(), }, }, Webhooks: []admissionreg.MutatingWebhook{ @@ -416,7 +446,7 @@ var _ = framework.CertManagerDescribe("CA Injector", func() { ObjectMeta: metav1.ObjectMeta{ Name: "objs." + namePrefix + ".testing.cert-manager.io", Annotations: map[string]string{ - certmanager.WantInjectAnnotation: types.NamespacedName{Name: "serving-certs", Namespace: f.Namespace.Name}.String(), + cmapiv1.WantInjectAnnotation: types.NamespacedName{Name: "serving-certs", Namespace: f.Namespace.Name}.String(), }, }, Spec: apiext.CustomResourceDefinitionSpec{ @@ -457,7 +487,7 @@ var _ = framework.CertManagerDescribe("CA Injector", func() { ObjectMeta: metav1.ObjectMeta{ Name: "v1." + namePrefix + ".testing.cert-manager.io", Annotations: map[string]string{ - certmanager.WantInjectAnnotation: types.NamespacedName{Name: "serving-certs", Namespace: f.Namespace.Name}.String(), + cmapiv1.WantInjectAnnotation: types.NamespacedName{Name: "serving-certs", Namespace: f.Namespace.Name}.String(), }, }, Spec: apireg.APIServiceSpec{ diff --git a/test/e2e/util/domains.go b/test/e2e/util/domains.go index c9f3bb514b8..dae31780de2 100644 --- a/test/e2e/util/domains.go +++ b/test/e2e/util/domains.go @@ -19,18 +19,18 @@ package util import ( "fmt" - cmutil "github.com/cert-manager/cert-manager/pkg/util" + "k8s.io/apimachinery/pkg/util/rand" ) // RandomSubdomain returns a new subdomain domain of the domain suffix. -// e.g. abcd.example.com. +// e.g., abcd.example.com. func RandomSubdomain(domain string) string { return RandomSubdomainLength(domain, 5) } // RandomSubdomainLength returns a new subdomain domain of the domain suffix, where the // subdomain has `length` number of characters. -// e.g. abcdefghij.example.com. +// e.g., abcdefghij.example.com. func RandomSubdomainLength(domain string, length int) string { - return fmt.Sprintf("%s.%s", cmutil.RandStringRunes(length), domain) + return fmt.Sprintf("%s.%s", rand.String(length), domain) } diff --git a/test/e2e/util/util.go b/test/e2e/util/util.go index 73b30a4093b..4eb9519707a 100644 --- a/test/e2e/util/util.go +++ b/test/e2e/util/util.go @@ -22,8 +22,6 @@ import ( "context" "crypto" "crypto/x509" - "crypto/x509/pkix" - "encoding/pem" "fmt" "net" "net/url" @@ -34,19 +32,26 @@ import ( networkingv1beta1 "k8s.io/api/networking/v1beta1" apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/typed/apiextensions/v1" "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/util/intstr" "k8s.io/apimachinery/pkg/util/wait" "k8s.io/client-go/discovery" - gwapiv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + gwapi "sigs.k8s.io/gateway-api/apis/v1" + "github.com/cert-manager/cert-manager/e2e-tests/framework/log" apiutil "github.com/cert-manager/cert-manager/pkg/api/util" v1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" clientset "github.com/cert-manager/cert-manager/pkg/client/clientset/versioned/typed/certmanager/v1" "github.com/cert-manager/cert-manager/pkg/util" - "github.com/cert-manager/cert-manager/pkg/util/pki" - "github.com/cert-manager/cert-manager/test/e2e/framework/log" + "github.com/cert-manager/cert-manager/pkg/util/predicate" + "github.com/cert-manager/cert-manager/test/unit/gen" + + . "github.com/onsi/gomega" ) func CertificateOnlyValidForDomains(cert *x509.Certificate, commonName string, dnsNames ...string) bool { @@ -56,43 +61,40 @@ func CertificateOnlyValidForDomains(cert *x509.Certificate, commonName string, d return true } -func WaitForIssuerStatusFunc(client clientset.IssuerInterface, name string, fn func(*v1.Issuer) (bool, error)) error { - return wait.PollImmediate(500*time.Millisecond, time.Minute, - func() (bool, error) { - issuer, err := client.Get(context.TODO(), name, metav1.GetOptions{}) - if err != nil { - return false, fmt.Errorf("error getting Issuer %q: %v", name, err) - } - return fn(issuer) - }) +func WaitForIssuerStatusFunc(ctx context.Context, client clientset.IssuerInterface, name string, fn func(*v1.Issuer) (bool, error)) error { + return wait.PollUntilContextTimeout(ctx, 500*time.Millisecond, time.Minute, true, func(ctx context.Context) (bool, error) { + issuer, err := client.Get(ctx, name, metav1.GetOptions{}) + if err != nil { + return false, fmt.Errorf("error getting Issuer %q: %v", name, err) + } + return fn(issuer) + }) } // WaitForIssuerCondition waits for the status of the named issuer to contain // a condition whose type and status matches the supplied one. -func WaitForIssuerCondition(client clientset.IssuerInterface, name string, condition v1.IssuerCondition) error { +func WaitForIssuerCondition(ctx context.Context, client clientset.IssuerInterface, name string, condition v1.IssuerCondition) error { logf, done := log.LogBackoff() defer done() - pollErr := wait.PollImmediate(500*time.Millisecond, time.Minute, - func() (bool, error) { - logf("Waiting for issuer %v condition %#v", name, condition) - issuer, err := client.Get(context.TODO(), name, metav1.GetOptions{}) - if nil != err { - return false, fmt.Errorf("error getting Issuer %q: %v", name, err) - } - - return apiutil.IssuerHasCondition(issuer, condition), nil - }, - ) - return wrapErrorWithIssuerStatusCondition(client, pollErr, name, condition.Type) + pollErr := wait.PollUntilContextTimeout(ctx, 500*time.Millisecond, time.Minute, true, func(ctx context.Context) (bool, error) { + logf("Waiting for issuer %v condition %#v", name, condition) + issuer, err := client.Get(ctx, name, metav1.GetOptions{}) + if nil != err { + return false, fmt.Errorf("error getting Issuer %q: %v", name, err) + } + + return apiutil.IssuerHasCondition(issuer, condition), nil + }) + return wrapErrorWithIssuerStatusCondition(ctx, client, pollErr, name, condition.Type) } // try to retrieve last condition to help diagnose tests. -func wrapErrorWithIssuerStatusCondition(client clientset.IssuerInterface, pollErr error, name string, conditionType v1.IssuerConditionType) error { +func wrapErrorWithIssuerStatusCondition(ctx context.Context, client clientset.IssuerInterface, pollErr error, name string, conditionType v1.IssuerConditionType) error { if pollErr == nil { return nil } - issuer, err := client.Get(context.TODO(), name, metav1.GetOptions{}) + issuer, err := client.Get(ctx, name, metav1.GetOptions{}) if err != nil { return pollErr } @@ -109,30 +111,28 @@ func wrapErrorWithIssuerStatusCondition(client clientset.IssuerInterface, pollEr // WaitForClusterIssuerCondition waits for the status of the named issuer to contain // a condition whose type and status matches the supplied one. -func WaitForClusterIssuerCondition(client clientset.ClusterIssuerInterface, name string, condition v1.IssuerCondition) error { +func WaitForClusterIssuerCondition(ctx context.Context, client clientset.ClusterIssuerInterface, name string, condition v1.IssuerCondition) error { logf, done := log.LogBackoff() defer done() - pollErr := wait.PollImmediate(500*time.Millisecond, time.Minute, - func() (bool, error) { - logf("Waiting for clusterissuer %v condition %#v", name, condition) - issuer, err := client.Get(context.TODO(), name, metav1.GetOptions{}) - if nil != err { - return false, fmt.Errorf("error getting ClusterIssuer %v: %v", name, err) - } - - return apiutil.IssuerHasCondition(issuer, condition), nil - }, - ) - return wrapErrorWithClusterIssuerStatusCondition(client, pollErr, name, condition.Type) + pollErr := wait.PollUntilContextTimeout(ctx, 500*time.Millisecond, time.Minute, true, func(ctx context.Context) (bool, error) { + logf("Waiting for clusterissuer %v condition %#v", name, condition) + issuer, err := client.Get(ctx, name, metav1.GetOptions{}) + if nil != err { + return false, fmt.Errorf("error getting ClusterIssuer %v: %v", name, err) + } + + return apiutil.IssuerHasCondition(issuer, condition), nil + }) + return wrapErrorWithClusterIssuerStatusCondition(ctx, client, pollErr, name, condition.Type) } // try to retrieve last condition to help diagnose tests. -func wrapErrorWithClusterIssuerStatusCondition(client clientset.ClusterIssuerInterface, pollErr error, name string, conditionType v1.IssuerConditionType) error { +func wrapErrorWithClusterIssuerStatusCondition(ctx context.Context, client clientset.ClusterIssuerInterface, pollErr error, name string, conditionType v1.IssuerConditionType) error { if pollErr == nil { return nil } - issuer, err := client.Get(context.TODO(), name, metav1.GetOptions{}) + issuer, err := client.Get(ctx, name, metav1.GetOptions{}) if err != nil { return pollErr } @@ -149,57 +149,32 @@ func wrapErrorWithClusterIssuerStatusCondition(client clientset.ClusterIssuerInt // WaitForCRDToNotExist waits for the CRD with the given name to no // longer exist. -func WaitForCRDToNotExist(client apiextensionsv1.CustomResourceDefinitionInterface, name string) error { +func WaitForCRDToNotExist(ctx context.Context, client apiextensionsv1.CustomResourceDefinitionInterface, name string) error { logf, done := log.LogBackoff() defer done() - return wait.PollImmediate(500*time.Millisecond, time.Minute, - func() (bool, error) { - logf("Waiting for CRD %v to not exist", name) - _, err := client.Get(context.TODO(), name, metav1.GetOptions{}) - if nil == err { - return false, nil - } - - if errors.IsNotFound(err) { - return true, nil - } - + return wait.PollUntilContextTimeout(ctx, 500*time.Millisecond, time.Minute, true, func(ctx context.Context) (bool, error) { + logf("Waiting for CRD %v to not exist", name) + _, err := client.Get(ctx, name, metav1.GetOptions{}) + if nil == err { return false, nil - }, - ) -} + } -// Deprecated: use test/unit/gen/Certificate in future -func NewCertManagerBasicCertificate(name, secretName, issuerName string, issuerKind string, duration, renewBefore *metav1.Duration, dnsNames ...string) *v1.Certificate { - cn := "test.domain.com" - if len(dnsNames) > 0 { - cn = dnsNames[0] - } - return &v1.Certificate{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - }, - Spec: v1.CertificateSpec{ - CommonName: cn, - DNSNames: dnsNames, - Subject: &v1.X509Subject{ - Organizations: []string{"test-org"}, - }, - SecretName: secretName, - Duration: duration, - RenewBefore: renewBefore, - PrivateKey: &v1.CertificatePrivateKey{}, - IssuerRef: cmmeta.ObjectReference{ - Name: issuerName, - Kind: issuerKind, - }, - }, - } + if errors.IsNotFound(err) { + return true, nil + } + + return false, nil + }) } // Deprecated: use test/unit/gen/CertificateRequest in future -func NewCertManagerBasicCertificateRequest(name, issuerName string, issuerKind string, duration *metav1.Duration, - dnsNames []string, ips []net.IP, uris []string, keyAlgorithm x509.PublicKeyAlgorithm) (*v1.CertificateRequest, crypto.Signer, error) { +func NewCertManagerBasicCertificateRequest( + name, namespace string, + issuerName, issuerKind string, + duration *metav1.Duration, + dnsNames []string, ips []net.IP, uris []string, + keyAlgorithm x509.PublicKeyAlgorithm, +) (*v1.CertificateRequest, crypto.Signer, error) { cn := "test.domain.com" if len(dnsNames) > 0 { cn = dnsNames[0] @@ -214,71 +189,28 @@ func NewCertManagerBasicCertificateRequest(name, issuerName string, issuerKind s parsedURIs = append(parsedURIs, parsed) } - var sk crypto.Signer - var signatureAlgorithm x509.SignatureAlgorithm - var err error - - switch keyAlgorithm { - case x509.RSA: - sk, err = pki.GenerateRSAPrivateKey(2048) - if err != nil { - return nil, nil, err - } - signatureAlgorithm = x509.SHA256WithRSA - case x509.ECDSA: - sk, err = pki.GenerateECPrivateKey(pki.ECCurve256) - if err != nil { - return nil, nil, err - } - signatureAlgorithm = x509.ECDSAWithSHA256 - case x509.Ed25519: - sk, err = pki.GenerateEd25519PrivateKey() - if err != nil { - return nil, nil, err - } - signatureAlgorithm = x509.PureEd25519 - default: - return nil, nil, fmt.Errorf("unrecognised key algorithm: %s", err) - } - - csr := &x509.CertificateRequest{ - Version: 3, - SignatureAlgorithm: signatureAlgorithm, - PublicKeyAlgorithm: keyAlgorithm, - PublicKey: sk.Public(), - Subject: pkix.Name{ - CommonName: cn, - }, - DNSNames: dnsNames, - IPAddresses: ips, - URIs: parsedURIs, - } - - csrBytes, err := pki.EncodeCSR(csr, sk) + csrPEM, sk, err := gen.CSR(keyAlgorithm, + gen.SetCSRCommonName(cn), + gen.SetCSRDNSNames(dnsNames...), + gen.SetCSRIPAddresses(ips...), + gen.SetCSRURIs(parsedURIs...), + ) if err != nil { return nil, nil, err } - csrPEM := pem.EncodeToMemory(&pem.Block{ - Type: "CERTIFICATE REQUEST", Bytes: csrBytes, - }) - - return &v1.CertificateRequest{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - }, - Spec: v1.CertificateRequestSpec{ - Duration: duration, - Request: csrPEM, - IssuerRef: cmmeta.ObjectReference{ - Name: issuerName, - Kind: issuerKind, - }, - }, - }, sk, nil + return gen.CertificateRequest(name, + gen.SetCertificateRequestNamespace(namespace), + gen.SetCertificateRequestDuration(duration), + gen.SetCertificateRequestCSR(csrPEM), + gen.SetCertificateRequestIssuer(cmmeta.IssuerReference{ + Name: issuerName, + Kind: issuerKind, + }), + ), sk, nil } -func NewCertManagerVaultCertificate(name, secretName, issuerName string, issuerKind string, duration, renewBefore *metav1.Duration) *v1.Certificate { +func NewCertManagerVaultCertificate(name, secretName, issuerName string, issuerKind string, duration *metav1.Duration, renewBefore *metav1.Duration) *v1.Certificate { return &v1.Certificate{ ObjectMeta: metav1.ObjectMeta{ Name: name, @@ -288,7 +220,7 @@ func NewCertManagerVaultCertificate(name, secretName, issuerName string, issuerK SecretName: secretName, Duration: duration, RenewBefore: renewBefore, - IssuerRef: cmmeta.ObjectReference{ + IssuerRef: cmmeta.IssuerReference{ Name: issuerName, Kind: issuerKind, }, @@ -359,7 +291,7 @@ func NewV1Beta1Ingress(name, secretName string, annotations map[string]string, d Path: "/", Backend: networkingv1beta1.IngressBackend{ ServiceName: "somesvc", - ServicePort: intstr.FromInt(80), + ServicePort: intstr.FromInt32(80), }, }, }, @@ -380,19 +312,19 @@ func pathTypePrefix() *networkingv1.PathType { // watching the 'foo' gateway class, so this Gateway will not be used to // actually route traffic, but can be used to test cert-manager controllers that // sync Gateways, such as gateway-shim. -func NewGateway(gatewayName, ns, secretName string, annotations map[string]string, dnsNames ...string) *gwapiv1alpha2.Gateway { +func NewGateway(gatewayName, ns, secretName string, annotations map[string]string, dnsNames ...string) *gwapi.Gateway { - return &gwapiv1alpha2.Gateway{ + return &gwapi.Gateway{ ObjectMeta: metav1.ObjectMeta{ Name: gatewayName, Annotations: annotations, }, - Spec: gwapiv1alpha2.GatewaySpec{ + Spec: gwapi.GatewaySpec{ GatewayClassName: "foo", - Listeners: []gwapiv1alpha2.Listener{{ - AllowedRoutes: &gwapiv1alpha2.AllowedRoutes{ - Namespaces: &gwapiv1alpha2.RouteNamespaces{ - From: func() *gwapiv1alpha2.FromNamespaces { f := gwapiv1alpha2.NamespacesFromSame; return &f }(), + Listeners: []gwapi.Listener{{ + AllowedRoutes: &gwapi.AllowedRoutes{ + Namespaces: &gwapi.RouteNamespaces{ + From: func() *gwapi.FromNamespaces { f := gwapi.NamespacesFromSame; return &f }(), Selector: &metav1.LabelSelector{MatchLabels: map[string]string{ "gw": gatewayName, }}, @@ -400,16 +332,16 @@ func NewGateway(gatewayName, ns, secretName string, annotations map[string]strin Kinds: nil, }, Name: "acme-solver", - Protocol: gwapiv1alpha2.TCPProtocolType, - Port: gwapiv1alpha2.PortNumber(80), - Hostname: (*gwapiv1alpha2.Hostname)(&dnsNames[0]), - TLS: &gwapiv1alpha2.GatewayTLSConfig{ - CertificateRefs: []gwapiv1alpha2.SecretObjectReference{ + Protocol: gwapi.TLSProtocolType, + Port: gwapi.PortNumber(443), + Hostname: (*gwapi.Hostname)(&dnsNames[0]), + TLS: &gwapi.ListenerTLSConfig{ + CertificateRefs: []gwapi.SecretObjectReference{ { - Kind: func() *gwapiv1alpha2.Kind { k := gwapiv1alpha2.Kind("Secret"); return &k }(), - Name: gwapiv1alpha2.ObjectName(secretName), - Group: func() *gwapiv1alpha2.Group { g := gwapiv1alpha2.Group(corev1.GroupName); return &g }(), - Namespace: (*gwapiv1alpha2.Namespace)(&ns), + Kind: func() *gwapi.Kind { k := gwapi.Kind("Secret"); return &k }(), + Name: gwapi.ObjectName(secretName), + Group: func() *gwapi.Group { g := gwapi.Group(corev1.GroupName); return &g }(), + Namespace: (*gwapi.Namespace)(&ns), }, }, }, @@ -420,8 +352,8 @@ func NewGateway(gatewayName, ns, secretName string, annotations map[string]strin // HasIngresses lets you know if an API exists in the discovery API // calling this function always performs a request to the API server. -func HasIngresses(d discovery.DiscoveryInterface, GroupVersion string) bool { - resourceList, err := d.ServerResourcesForGroupVersion(GroupVersion) +func HasIngresses(d discovery.DiscoveryInterface, groupVersion string) bool { + resourceList, err := d.ServerResourcesForGroupVersion(groupVersion) if err != nil { return false } @@ -432,3 +364,64 @@ func HasIngresses(d discovery.DiscoveryInterface, GroupVersion string) bool { } return false } + +// AddFinalizer will add a finalizer to the given object, it is designed to +// be used within Eventually calls so conflicts get resolved +func AddFinalizer(g Gomega, ctx context.Context, cli client.Client, obj client.Object, finalizer string) { + key := client.ObjectKeyFromObject(obj) + g.Expect(cli.Get(ctx, key, obj)).NotTo(HaveOccurred(), "failed to get %T", obj) + + if controllerutil.AddFinalizer(obj, finalizer) { + g.Expect(cli.Update(ctx, obj)).NotTo(HaveOccurred(), "failed to update %T", obj) + } +} + +// RemoveFinalizer will remove a finalizer to the given object, it is designed to +// be used within Eventually calls so conflicts get resolved. If the object +// does not exist then no error is returned as removing the finalizer may cause +// deletion +func RemoveFinalizer(g Gomega, ctx context.Context, cli client.Client, obj client.Object, finalizer string) { + key := client.ObjectKeyFromObject(obj) + + if err := cli.Get(ctx, key, obj); err != nil { + g.Expect(client.IgnoreNotFound(err)).NotTo(HaveOccurred(), "failed to get %T", obj) + return + } + + if controllerutil.RemoveFinalizer(obj, finalizer) { + g.Expect(client.IgnoreNotFound(cli.Update(ctx, obj))).NotTo(HaveOccurred(), "failed to update %T", obj) + } +} + +// ObjectPtrConstraint is a constraint used for ensuring T is a both a pointer +// and implements client.Object +type ObjectPtrConstraint[T any] interface { + *T + client.Object +} + +// ObjectListPtrConstraint is a constraint used for ensuring T is a both a +// pointer and implements client.ObjectList +type ObjectListPtrConstraint[T any] interface { + *T + client.ObjectList +} + +// ListMatchingPredicates will list the objects that match a set of predicates +func ListMatchingPredicates[O any, OL any, P ObjectPtrConstraint[O], PL ObjectListPtrConstraint[OL]](g Gomega, ctx context.Context, cli client.Client, predicates ...predicate.Func) []O { + list := PL(new(OL)) + g.Expect(cli.List(ctx, list)).ToNot(HaveOccurred(), "failed to list objects") + + // Evaluate predicates + funcs := predicate.Funcs(predicates) + out := make([]O, 0) + err := meta.EachListItem(list, func(o runtime.Object) error { + if funcs.Evaluate(o) { + out = append(out, *(o.(P))) + } + return nil + }) + Expect(err).NotTo(HaveOccurred(), "failed to iterate over objects") + + return out +} diff --git a/test/fixtures/upgrade/overlay/cainjector-ops.yaml b/test/fixtures/upgrade/overlay/cainjector-ops.yaml index 23877819ab6..1f1fa9b50fa 100644 --- a/test/fixtures/upgrade/overlay/cainjector-ops.yaml +++ b/test/fixtures/upgrade/overlay/cainjector-ops.yaml @@ -22,4 +22,4 @@ spec: spec: containers: #@overlay/match by=overlay.subset({"name": "cert-manager-cainjector"}) - - image: #@ "docker.io/library/cert-manager-cainjector-amd64:{}".format(data.values.app_version) + - image: #@ "docker.io/library/cert-manager-cainjector-{}:{}".format(data.values.arch, data.values.app_version) diff --git a/test/fixtures/upgrade/overlay/controller-ops.yaml b/test/fixtures/upgrade/overlay/controller-ops.yaml index 3533b6589b9..9acd1de61bc 100644 --- a/test/fixtures/upgrade/overlay/controller-ops.yaml +++ b/test/fixtures/upgrade/overlay/controller-ops.yaml @@ -22,5 +22,5 @@ spec: spec: containers: #@overlay/match by=overlay.subset({"name": "cert-manager-controller"}) - - image: #@ "docker.io/library/cert-manager-controller-amd64:{}".format(data.values.app_version) + - image: #@ "docker.io/library/cert-manager-controller-{}:{}".format(data.values.arch, data.values.app_version) diff --git a/test/fixtures/upgrade/overlay/webhook-ops.yaml b/test/fixtures/upgrade/overlay/webhook-ops.yaml index ba9cc2b8d75..e2f76c11b52 100644 --- a/test/fixtures/upgrade/overlay/webhook-ops.yaml +++ b/test/fixtures/upgrade/overlay/webhook-ops.yaml @@ -22,4 +22,4 @@ spec: spec: containers: #@overlay/match by=overlay.subset({"name": "cert-manager-webhook"}) - - image: #@ "docker.io/library/cert-manager-webhook-amd64:{}".format(data.values.app_version) + - image: #@ "docker.io/library/cert-manager-webhook-{}:{}".format(data.values.arch, data.values.app_version) diff --git a/test/integration/acme/orders_controller_test.go b/test/integration/acme/orders_controller_test.go index 559d9f9984d..6fcdb4d7b27 100644 --- a/test/integration/acme/orders_controller_test.go +++ b/test/integration/acme/orders_controller_test.go @@ -22,12 +22,12 @@ import ( "testing" "time" - acmeapi "golang.org/x/crypto/acme" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/wait" "k8s.io/utils/clock" + "github.com/cert-manager/cert-manager/integration-tests/framework" accountstest "github.com/cert-manager/cert-manager/pkg/acme/accounts/test" acmecl "github.com/cert-manager/cert-manager/pkg/acme/client" cmacme "github.com/cert-manager/cert-manager/pkg/apis/acme/v1" @@ -36,26 +36,23 @@ import ( "github.com/cert-manager/cert-manager/pkg/controller/acmeorders" logf "github.com/cert-manager/cert-manager/pkg/logs" "github.com/cert-manager/cert-manager/pkg/metrics" - "github.com/cert-manager/cert-manager/test/integration/framework" "github.com/cert-manager/cert-manager/test/unit/gen" + acmeapi "github.com/cert-manager/cert-manager/third_party/forked/acme" ) func TestAcmeOrdersController(t *testing.T) { - ctx, cancel := context.WithTimeout(context.Background(), time.Second*40) - defer cancel() - - config, stopFn := framework.RunControlPlane(t, ctx) - defer stopFn() + config, stopFn := framework.RunControlPlane(t) + t.Cleanup(stopFn) // Create clients and informer factories for Kubernetes API and // cert-manager. - kubeClient, factory, cmCl, cmFactory := framework.NewClients(t, config) + kubeClient, factory, cmCl, cmFactory, scheme := framework.NewClients(t, config) // some test values var ( - testName string = "acmetest" - challengeType string = "dns-01" - authType string = "dns" + testName = "acmetest" + challengeType = "dns-01" + authType = "dns" ) // Initial ACME authorization to be returned by GetAuthorization. @@ -118,20 +115,30 @@ func TestAcmeOrdersController(t *testing.T) { }, } + controllerContext := controllerpkg.Context{ + Client: kubeClient, + Scheme: scheme, + KubeSharedInformerFactory: factory, + CMClient: cmCl, + SharedInformerFactory: cmFactory, + ACMEAccountRegistry: accountRegistry, + Clock: clock.RealClock{}, + ContextOptions: controllerpkg.ContextOptions{}, + + Recorder: framework.NewEventRecorder(t, scheme), + FieldManager: "cert-manager-orders-test", + } + // Create a new orders controller. - ctrl, queue, mustSync := acmeorders.NewController( + ctrl, queue, mustSync, err := acmeorders.NewController( logf.Log, - cmCl, - factory, - cmFactory, - accountRegistry, - framework.NewEventRecorder(t), - clock.RealClock{}, + &controllerContext, false, - "cert-manager-test", ) + if err != nil { + t.Fatal(err) + } c := controllerpkg.NewController( - ctx, "orders_test", metrics.New(logf.Log, clock.RealClock{}), ctrl.ProcessItem, @@ -151,8 +158,7 @@ func TestAcmeOrdersController(t *testing.T) { // Create a Namespace. ns := &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: testName}} - _, err := kubeClient.CoreV1().Namespaces().Create(ctx, ns, metav1.CreateOptions{}) - if err != nil { + if _, err := kubeClient.CoreV1().Namespaces().Create(t.Context(), ns, metav1.CreateOptions{}); err != nil { t.Fatal(err) } @@ -177,7 +183,7 @@ func TestAcmeOrdersController(t *testing.T) { gen.SetIssuerNamespace(testName), gen.SetIssuerACME(acmeIssuer)) - _, err = cmCl.CertmanagerV1().Issuers(testName).Create(ctx, iss, metav1.CreateOptions{}) + _, err = cmCl.CertmanagerV1().Issuers(testName).Create(t.Context(), iss, metav1.CreateOptions{}) if err != nil { t.Fatal(err) } @@ -186,26 +192,26 @@ func TestAcmeOrdersController(t *testing.T) { // Create an Order CR. order := gen.Order(testName, - gen.SetOrderIssuer(cmmeta.ObjectReference{ + gen.SetOrderIssuer(cmmeta.IssuerReference{ Name: testName, }), gen.SetOrderNamespace(testName), gen.SetOrderCsr([]byte(testName)), gen.SetOrderDNSNames(testName)) - order, err = cmCl.AcmeV1().Orders(testName).Create(ctx, order, metav1.CreateOptions{}) + order, err = cmCl.AcmeV1().Orders(testName).Create(t.Context(), order, metav1.CreateOptions{}) if err != nil { t.Fatal(err) } // Wait for the Challenge to be created. var chal *cmacme.Challenge - err = wait.PollImmediateUntil(time.Millisecond*100, func() (done bool, err error) { - chals, err := cmCl.AcmeV1().Challenges(testName).List(ctx, metav1.ListOptions{}) + err = wait.PollUntilContextCancel(t.Context(), time.Millisecond*100, true, func(ctx context.Context) (done bool, err error) { + challenges, err := cmCl.AcmeV1().Challenges(testName).List(ctx, metav1.ListOptions{}) if err != nil { return false, err } - l := len(chals.Items) + l := len(challenges.Items) // Challenge has not been created yet if l == 0 { return false, nil @@ -215,12 +221,12 @@ func TestAcmeOrdersController(t *testing.T) { return false, fmt.Errorf("expected maximum 1 challenge, got %d", l) } // Check that the Challenge is owned by our Order. - chal = &chals.Items[0] + chal = &challenges.Items[0] if !metav1.IsControlledBy(chal, order) { return false, fmt.Errorf("found an unexpected Challenge resource: %v", chal.Name) } return true, nil - }, ctx.Done()) + }) if err != nil { t.Fatal(err) } @@ -232,10 +238,10 @@ func TestAcmeOrdersController(t *testing.T) { // valid. // https://github.com/cert-manager/cert-manager/issues/2868 - // Set the Challenge state to valid- the status of the ACME order remains 'pending'. + // Set the Challenge state to valid, the status of the ACME order remains 'pending'. chal = chal.DeepCopy() chal.Status.State = cmacme.Valid - _, err = cmCl.AcmeV1().Challenges(testName).UpdateStatus(ctx, chal, metav1.UpdateOptions{}) + _, err = cmCl.AcmeV1().Challenges(testName).UpdateStatus(t.Context(), chal, metav1.UpdateOptions{}) if err != nil { t.Fatal(err) } @@ -249,9 +255,15 @@ func TestAcmeOrdersController(t *testing.T) { // Reason field on Order's status. Change this test once we are setting // Reasons on intermittent Order states. var pendingOrder *cmacme.Order - timeoutCtx, timeoutCancel := context.WithTimeout(ctx, acmeorders.RequeuePeriod) - defer timeoutCancel() - err = wait.PollImmediateUntil(time.Millisecond*200, func() (bool, error) { + startTime := time.Now() + successful := false + err = wait.PollUntilContextCancel(t.Context(), time.Millisecond*200, true, func(ctx context.Context) (bool, error) { + // Check if order has been pending for 2s (requeue period) + if time.Since(startTime) > acmeorders.RequeuePeriod { + successful = true + return true, nil + } + pendingOrder, err = cmCl.AcmeV1().Orders(testName).Get(ctx, testName, metav1.GetOptions{}) if err != nil { return false, err @@ -260,16 +272,12 @@ func TestAcmeOrdersController(t *testing.T) { return true, nil } return false, nil - }, timeoutCtx.Done()) + }) switch { - case err == nil: + case err == nil && !successful: t.Fatalf("Expected Order to have pending status instead got: %v", pendingOrder.Status.State) - case err == wait.ErrWaitTimeout: - if ctx.Err() != nil { - t.Error(ctx.Err()) - } - - // 'happy case' - Order remained pending + case err == nil && successful: + // this is the expected 'happy case' default: t.Fatal(err) } @@ -278,7 +286,7 @@ func TestAcmeOrdersController(t *testing.T) { acmeOrder.Status = acmeapi.StatusReady // Wait for the status of the Order to become Valid. - err = wait.PollImmediateUntil(time.Millisecond*100, func() (bool, error) { + err = wait.PollUntilContextCancel(t.Context(), time.Millisecond*100, true, func(ctx context.Context) (done bool, err error) { o, err := cmCl.AcmeV1().Orders(testName).Get(ctx, testName, metav1.GetOptions{}) if err != nil { return false, err @@ -288,7 +296,7 @@ func TestAcmeOrdersController(t *testing.T) { return false, nil } return true, nil - }, ctx.Done()) + }) if err != nil { t.Fatal(err) } diff --git a/test/integration/certificaterequests/apply_test.go b/test/integration/certificaterequests/apply_test.go index 67d4b5a25e9..a4aa9ffbea0 100644 --- a/test/integration/certificaterequests/apply_test.go +++ b/test/integration/certificaterequests/apply_test.go @@ -17,19 +17,17 @@ limitations under the License. package certificaterequests import ( - "context" "testing" - "time" "github.com/stretchr/testify/assert" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" fakeclock "k8s.io/utils/clock/testing" + "github.com/cert-manager/cert-manager/integration-tests/framework" internalcertificaterequests "github.com/cert-manager/cert-manager/internal/controller/certificaterequests" cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" - "github.com/cert-manager/cert-manager/test/integration/framework" testcrypto "github.com/cert-manager/cert-manager/test/unit/crypto" ) @@ -41,24 +39,21 @@ func Test_Apply(t *testing.T) { name = "test-apply" ) - ctx, cancel := context.WithTimeout(context.Background(), time.Second*40) - defer cancel() + restConfig, stopFn := framework.RunControlPlane(t) + t.Cleanup(stopFn) - restConfig, stopFn := framework.RunControlPlane(t, ctx) - defer stopFn() - - kubeClient, _, cmClient, _ := framework.NewClients(t, restConfig) + kubeClient, _, cmClient, _, _ := framework.NewClients(t, restConfig) t.Log("creating test Namespace") ns := &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: namespace}} - _, err := kubeClient.CoreV1().Namespaces().Create(ctx, ns, metav1.CreateOptions{}) + _, err := kubeClient.CoreV1().Namespaces().Create(t.Context(), ns, metav1.CreateOptions{}) assert.NoError(t, err) bundle := testcrypto.MustCreateCryptoBundle(t, &cmapi.Certificate{ ObjectMeta: metav1.ObjectMeta{Namespace: namespace, Name: name}, Spec: cmapi.CertificateSpec{ CommonName: "test-bundle-1", - IssuerRef: cmmeta.ObjectReference{Name: "test-bundle-1"}, + IssuerRef: cmmeta.IssuerReference{Name: "test-bundle-1"}, }}, &fakeclock.FakeClock{}, ) @@ -69,11 +64,11 @@ func Test_Apply(t *testing.T) { req.Annotations = nil t.Log("creating CertificateRequest") - _, err = cmClient.CertmanagerV1().CertificateRequests(namespace).Create(ctx, req, metav1.CreateOptions{FieldManager: "cert-manager-test"}) + _, err = cmClient.CertmanagerV1().CertificateRequests(namespace).Create(t.Context(), req, metav1.CreateOptions{FieldManager: "cert-manager-test"}) assert.NoError(t, err) t.Log("ensuring apply will can set annotations and labels") - req, err = internalcertificaterequests.Apply(ctx, cmClient, "cert-manager-test", &cmapi.CertificateRequest{ + req, err = internalcertificaterequests.Apply(t.Context(), cmClient, "cert-manager-test", &cmapi.CertificateRequest{ ObjectMeta: metav1.ObjectMeta{ Namespace: namespace, Name: name, Annotations: map[string]string{"test-1": "abc", "test-2": "def"}, @@ -87,14 +82,14 @@ func Test_Apply(t *testing.T) { t.Log("ensuring apply will can status") assert.NoError(t, - internalcertificaterequests.ApplyStatus(ctx, cmClient, "cert-manager-test", &cmapi.CertificateRequest{ + internalcertificaterequests.ApplyStatus(t.Context(), cmClient, "cert-manager-test", &cmapi.CertificateRequest{ ObjectMeta: metav1.ObjectMeta{Namespace: namespace, Name: name}, Status: cmapi.CertificateRequestStatus{ Conditions: []cmapi.CertificateRequestCondition{{Type: cmapi.CertificateRequestConditionType("Random"), Status: cmmeta.ConditionTrue, Reason: "reason", Message: "message"}}, }, }), ) - req, err = cmClient.CertmanagerV1().CertificateRequests(namespace).Get(ctx, name, metav1.GetOptions{}) + req, err = cmClient.CertmanagerV1().CertificateRequests(namespace).Get(t.Context(), name, metav1.GetOptions{}) assert.NoError(t, err) assert.Equal(t, []cmapi.CertificateRequestCondition{{Type: cmapi.CertificateRequestConditionType("Random"), Status: cmmeta.ConditionTrue, Reason: "reason", Message: "message"}}, req.Status.Conditions) } diff --git a/test/integration/certificaterequests/condition_list_type_test.go b/test/integration/certificaterequests/condition_list_type_test.go index 18bc097e429..1fdaa04baca 100644 --- a/test/integration/certificaterequests/condition_list_type_test.go +++ b/test/integration/certificaterequests/condition_list_type_test.go @@ -17,20 +17,18 @@ limitations under the License. package certificaterequests import ( - "context" "testing" - "time" "github.com/stretchr/testify/assert" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" fakeclock "k8s.io/utils/clock/testing" + "github.com/cert-manager/cert-manager/integration-tests/framework" internalcertificaterequests "github.com/cert-manager/cert-manager/internal/controller/certificaterequests" cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" "github.com/cert-manager/cert-manager/pkg/util" - "github.com/cert-manager/cert-manager/test/integration/framework" testcrypto "github.com/cert-manager/cert-manager/test/unit/crypto" ) @@ -44,31 +42,28 @@ func Test_ConditionsListType(t *testing.T) { name = "test-condition-list-type" ) - ctx, cancel := context.WithTimeout(context.Background(), time.Second*40) - defer cancel() - - restConfig, stopFn := framework.RunControlPlane(t, ctx) - defer stopFn() + restConfig, stopFn := framework.RunControlPlane(t) + t.Cleanup(stopFn) // Build clients with different field managers. aliceRestConfig := util.RestConfigWithUserAgent(restConfig, "alice") aliceFieldManager := util.PrefixFromUserAgent(aliceRestConfig.UserAgent) - aliceKubeClient, _, aliceCMClient, _ := framework.NewClients(t, aliceRestConfig) + aliceKubeClient, _, aliceCMClient, _, _ := framework.NewClients(t, aliceRestConfig) bobRestConfig := util.RestConfigWithUserAgent(restConfig, "bob") bobFieldManager := util.PrefixFromUserAgent(bobRestConfig.UserAgent) - _, _, bobCMClient, _ := framework.NewClients(t, bobRestConfig) + _, _, bobCMClient, _, _ := framework.NewClients(t, bobRestConfig) t.Log("creating test Namespace") ns := &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: namespace}} - _, err := aliceKubeClient.CoreV1().Namespaces().Create(ctx, ns, metav1.CreateOptions{}) + _, err := aliceKubeClient.CoreV1().Namespaces().Create(t.Context(), ns, metav1.CreateOptions{}) assert.NoError(t, err) bundle := testcrypto.MustCreateCryptoBundle(t, &cmapi.Certificate{ ObjectMeta: metav1.ObjectMeta{Namespace: namespace, Name: name}, Spec: cmapi.CertificateSpec{ CommonName: "test-bundle-1", - IssuerRef: cmmeta.ObjectReference{Name: "test-bundle-1"}, + IssuerRef: cmmeta.IssuerReference{Name: "test-bundle-1"}, }}, &fakeclock.FakeClock{}, ) @@ -77,11 +72,11 @@ func Test_ConditionsListType(t *testing.T) { req.Name = name t.Log("creating CertificateRequest") - _, err = aliceCMClient.CertmanagerV1().CertificateRequests(namespace).Create(ctx, req, metav1.CreateOptions{FieldManager: "cert-manager-test"}) + _, err = aliceCMClient.CertmanagerV1().CertificateRequests(namespace).Create(t.Context(), req, metav1.CreateOptions{FieldManager: "cert-manager-test"}) assert.NoError(t, err) t.Log("ensuring alice can set Ready condition") - assert.NoError(t, internalcertificaterequests.ApplyStatus(ctx, aliceCMClient, aliceFieldManager, &cmapi.CertificateRequest{ + assert.NoError(t, internalcertificaterequests.ApplyStatus(t.Context(), aliceCMClient, aliceFieldManager, &cmapi.CertificateRequest{ TypeMeta: metav1.TypeMeta{Kind: cmapi.CertificateRequestKind, APIVersion: cmapi.SchemeGroupVersion.Identifier()}, ObjectMeta: metav1.ObjectMeta{Namespace: namespace, Name: name}, Status: cmapi.CertificateRequestStatus{ @@ -90,7 +85,7 @@ func Test_ConditionsListType(t *testing.T) { })) t.Log("ensuring bob can set a district random condition, without changing the ready condition") - assert.NoError(t, internalcertificaterequests.ApplyStatus(ctx, bobCMClient, bobFieldManager, &cmapi.CertificateRequest{ + assert.NoError(t, internalcertificaterequests.ApplyStatus(t.Context(), bobCMClient, bobFieldManager, &cmapi.CertificateRequest{ TypeMeta: metav1.TypeMeta{Kind: cmapi.CertificateRequestKind, APIVersion: cmapi.SchemeGroupVersion.Identifier()}, ObjectMeta: metav1.ObjectMeta{Namespace: namespace, Name: name}, Status: cmapi.CertificateRequestStatus{ @@ -98,7 +93,7 @@ func Test_ConditionsListType(t *testing.T) { }, })) - req, err = bobCMClient.CertmanagerV1().CertificateRequests(namespace).Get(ctx, name, metav1.GetOptions{}) + req, err = bobCMClient.CertmanagerV1().CertificateRequests(namespace).Get(t.Context(), name, metav1.GetOptions{}) assert.NoError(t, err) assert.Equal(t, []cmapi.CertificateRequestCondition{ {Type: cmapi.CertificateRequestConditionReady, Status: cmmeta.ConditionTrue, Reason: "reason", Message: "message"}, @@ -106,7 +101,7 @@ func Test_ConditionsListType(t *testing.T) { }, req.Status.Conditions, "conditions did not match the expected 2 distinct condition types") t.Log("alice should override an existing condition by another manager, and can delete an existing owned condition type through omission") - assert.NoError(t, internalcertificaterequests.ApplyStatus(ctx, aliceCMClient, aliceFieldManager, &cmapi.CertificateRequest{ + assert.NoError(t, internalcertificaterequests.ApplyStatus(t.Context(), aliceCMClient, aliceFieldManager, &cmapi.CertificateRequest{ TypeMeta: metav1.TypeMeta{Kind: cmapi.CertificateRequestKind, APIVersion: cmapi.SchemeGroupVersion.Identifier()}, ObjectMeta: metav1.ObjectMeta{Namespace: namespace, Name: name}, Status: cmapi.CertificateRequestStatus{ @@ -114,14 +109,14 @@ func Test_ConditionsListType(t *testing.T) { }, })) - req, err = aliceCMClient.CertmanagerV1().CertificateRequests(namespace).Get(ctx, name, metav1.GetOptions{}) + req, err = aliceCMClient.CertmanagerV1().CertificateRequests(namespace).Get(t.Context(), name, metav1.GetOptions{}) assert.NoError(t, err) assert.Equal(t, []cmapi.CertificateRequestCondition{ {Type: cmapi.CertificateRequestConditionType("Random"), Status: cmmeta.ConditionFalse, Reason: "another-reason", Message: "another-message"}, }, req.Status.Conditions, "conditions did not match expected deleted ready condition, and overwritten random condition") t.Log("bob can re-add a Ready condition and not change Random condition") - assert.NoError(t, internalcertificaterequests.ApplyStatus(ctx, bobCMClient, bobFieldManager, &cmapi.CertificateRequest{ + assert.NoError(t, internalcertificaterequests.ApplyStatus(t.Context(), bobCMClient, bobFieldManager, &cmapi.CertificateRequest{ TypeMeta: metav1.TypeMeta{Kind: cmapi.CertificateRequestKind, APIVersion: cmapi.SchemeGroupVersion.Identifier()}, ObjectMeta: metav1.ObjectMeta{Namespace: namespace, Name: name}, Status: cmapi.CertificateRequestStatus{ @@ -129,7 +124,7 @@ func Test_ConditionsListType(t *testing.T) { }, })) - req, err = bobCMClient.CertmanagerV1().CertificateRequests(namespace).Get(ctx, name, metav1.GetOptions{}) + req, err = bobCMClient.CertmanagerV1().CertificateRequests(namespace).Get(t.Context(), name, metav1.GetOptions{}) assert.NoError(t, err) assert.Equal(t, []cmapi.CertificateRequestCondition{ {Type: cmapi.CertificateRequestConditionType("Random"), Status: cmmeta.ConditionFalse, Reason: "another-reason", Message: "another-message"}, diff --git a/test/integration/certificates/condition_list_type_test.go b/test/integration/certificates/condition_list_type_test.go index cc8204e6fdd..59dc0c4e1eb 100644 --- a/test/integration/certificates/condition_list_type_test.go +++ b/test/integration/certificates/condition_list_type_test.go @@ -17,21 +17,19 @@ limitations under the License. package certificates import ( - "context" "encoding/json" "testing" - "time" "github.com/stretchr/testify/assert" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" apitypes "k8s.io/apimachinery/pkg/types" - "k8s.io/utils/pointer" + "k8s.io/utils/ptr" + "github.com/cert-manager/cert-manager/integration-tests/framework" cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" "github.com/cert-manager/cert-manager/pkg/util" - "github.com/cert-manager/cert-manager/test/integration/framework" ) // Test_ConditionsListType ensures that the Certificate's Conditions API field @@ -44,34 +42,31 @@ func Test_ConditionsListType(t *testing.T) { name = "test-condition-list-type" ) - ctx, cancel := context.WithTimeout(context.Background(), time.Second*40) - defer cancel() - - restConfig, stopFn := framework.RunControlPlane(t, ctx) - defer stopFn() + restConfig, stopFn := framework.RunControlPlane(t) + t.Cleanup(stopFn) // Build clients with different field managers. aliceRestConfig := util.RestConfigWithUserAgent(restConfig, "alice") aliceFieldManager := util.PrefixFromUserAgent(aliceRestConfig.UserAgent) - aliceKubeClient, _, aliceCMClient, _ := framework.NewClients(t, aliceRestConfig) + aliceKubeClient, _, aliceCMClient, _, _ := framework.NewClients(t, aliceRestConfig) bobRestConfig := util.RestConfigWithUserAgent(restConfig, "bob") bobFieldManager := util.PrefixFromUserAgent(bobRestConfig.UserAgent) - _, _, bobCMClient, _ := framework.NewClients(t, bobRestConfig) + _, _, bobCMClient, _, _ := framework.NewClients(t, bobRestConfig) t.Log("creating test Namespace") ns := &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: namespace}} - _, err := aliceKubeClient.CoreV1().Namespaces().Create(ctx, ns, metav1.CreateOptions{}) + _, err := aliceKubeClient.CoreV1().Namespaces().Create(t.Context(), ns, metav1.CreateOptions{}) assert.NoError(t, err) t.Log("creating empty Certificate") crt := &cmapi.Certificate{ ObjectMeta: metav1.ObjectMeta{Namespace: namespace, Name: name}, Spec: cmapi.CertificateSpec{ - CommonName: "test", SecretName: "test", IssuerRef: cmmeta.ObjectReference{Name: "test"}, + CommonName: "test", SecretName: "test", IssuerRef: cmmeta.IssuerReference{Name: "test"}, }, } - _, err = aliceCMClient.CertmanagerV1().Certificates(namespace).Create(ctx, crt, metav1.CreateOptions{}) + _, err = aliceCMClient.CertmanagerV1().Certificates(namespace).Create(t.Context(), crt, metav1.CreateOptions{}) assert.NoError(t, err) t.Log("ensuring alice can set Ready condition") @@ -85,8 +80,8 @@ func Test_ConditionsListType(t *testing.T) { crtData, err := json.Marshal(crt) assert.NoError(t, err) _, err = aliceCMClient.CertmanagerV1().Certificates(namespace).Patch( - ctx, name, apitypes.ApplyPatchType, crtData, - metav1.PatchOptions{Force: pointer.Bool(true), FieldManager: aliceFieldManager}, "status", + t.Context(), name, apitypes.ApplyPatchType, crtData, + metav1.PatchOptions{Force: ptr.To(true), FieldManager: aliceFieldManager}, "status", ) assert.NoError(t, err) @@ -101,12 +96,12 @@ func Test_ConditionsListType(t *testing.T) { crtData, err = json.Marshal(crt) assert.NoError(t, err) _, err = bobCMClient.CertmanagerV1().Certificates(namespace).Patch( - ctx, name, apitypes.ApplyPatchType, crtData, - metav1.PatchOptions{Force: pointer.Bool(true), FieldManager: bobFieldManager}, "status", + t.Context(), name, apitypes.ApplyPatchType, crtData, + metav1.PatchOptions{Force: ptr.To(true), FieldManager: bobFieldManager}, "status", ) assert.NoError(t, err) - crt, err = bobCMClient.CertmanagerV1().Certificates(namespace).Get(ctx, name, metav1.GetOptions{}) + crt, err = bobCMClient.CertmanagerV1().Certificates(namespace).Get(t.Context(), name, metav1.GetOptions{}) assert.NoError(t, err) assert.Equal(t, []cmapi.CertificateCondition{ {Type: cmapi.CertificateConditionReady, Status: cmmeta.ConditionTrue, Reason: "reason", Message: "message"}, @@ -124,12 +119,12 @@ func Test_ConditionsListType(t *testing.T) { crtData, err = json.Marshal(crt) assert.NoError(t, err) _, err = aliceCMClient.CertmanagerV1().Certificates(namespace).Patch( - ctx, name, apitypes.ApplyPatchType, crtData, - metav1.PatchOptions{Force: pointer.Bool(true), FieldManager: aliceFieldManager}, "status", + t.Context(), name, apitypes.ApplyPatchType, crtData, + metav1.PatchOptions{Force: ptr.To(true), FieldManager: aliceFieldManager}, "status", ) assert.NoError(t, err) - crt, err = aliceCMClient.CertmanagerV1().Certificates(namespace).Get(ctx, name, metav1.GetOptions{}) + crt, err = aliceCMClient.CertmanagerV1().Certificates(namespace).Get(t.Context(), name, metav1.GetOptions{}) assert.NoError(t, err) assert.Equal(t, []cmapi.CertificateCondition{ {Type: cmapi.CertificateConditionIssuing, Status: cmmeta.ConditionFalse, Reason: "another-reason", Message: "another-message"}, @@ -146,12 +141,12 @@ func Test_ConditionsListType(t *testing.T) { crtData, err = json.Marshal(crt) assert.NoError(t, err) _, err = bobCMClient.CertmanagerV1().Certificates(namespace).Patch( - ctx, name, apitypes.ApplyPatchType, crtData, - metav1.PatchOptions{Force: pointer.Bool(true), FieldManager: bobFieldManager}, "status", + t.Context(), name, apitypes.ApplyPatchType, crtData, + metav1.PatchOptions{Force: ptr.To(true), FieldManager: bobFieldManager}, "status", ) assert.NoError(t, err) - crt, err = bobCMClient.CertmanagerV1().Certificates(namespace).Get(ctx, name, metav1.GetOptions{}) + crt, err = bobCMClient.CertmanagerV1().Certificates(namespace).Get(t.Context(), name, metav1.GetOptions{}) assert.NoError(t, err) assert.Equal(t, []cmapi.CertificateCondition{ {Type: cmapi.CertificateConditionIssuing, Status: cmmeta.ConditionFalse, Reason: "another-reason", Message: "another-message"}, diff --git a/test/integration/certificates/generates_new_private_key_per_request_test.go b/test/integration/certificates/generates_new_private_key_per_request_test.go index 74050691267..a3c7ae378c8 100644 --- a/test/integration/certificates/generates_new_private_key_per_request_test.go +++ b/test/integration/certificates/generates_new_private_key_per_request_test.go @@ -18,69 +18,81 @@ package certificates import ( "context" - "crypto" "fmt" "testing" "time" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/wait" "k8s.io/client-go/rest" "k8s.io/utils/clock" + "github.com/cert-manager/cert-manager/integration-tests/framework" "github.com/cert-manager/cert-manager/internal/controller/certificates/policies" apiutil "github.com/cert-manager/cert-manager/pkg/api/util" cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" controllerpkg "github.com/cert-manager/cert-manager/pkg/controller" - "github.com/cert-manager/cert-manager/pkg/controller/certificates" "github.com/cert-manager/cert-manager/pkg/controller/certificates/issuing" "github.com/cert-manager/cert-manager/pkg/controller/certificates/keymanager" "github.com/cert-manager/cert-manager/pkg/controller/certificates/readiness" "github.com/cert-manager/cert-manager/pkg/controller/certificates/requestmanager" "github.com/cert-manager/cert-manager/pkg/controller/certificates/revisionmanager" "github.com/cert-manager/cert-manager/pkg/controller/certificates/trigger" - testpkg "github.com/cert-manager/cert-manager/pkg/controller/test" logf "github.com/cert-manager/cert-manager/pkg/logs" "github.com/cert-manager/cert-manager/pkg/metrics" "github.com/cert-manager/cert-manager/pkg/util/pki" - "github.com/cert-manager/cert-manager/test/integration/framework" ) func TestGeneratesNewPrivateKeyIfMarkedInvalidRequest(t *testing.T) { namespace := "default" - ctx, cancel := context.WithTimeout(context.Background(), time.Second*30) - defer cancel() - config, stopFn := framework.RunControlPlane(t, ctx) - defer stopFn() + config, stopFn := framework.RunControlPlane(t) + t.Cleanup(stopFn) // Build, instantiate and run all required controllers - stopControllers := runAllControllers(t, ctx, config) + stopControllers := runAllControllers(t, config) defer stopControllers() - _, _, cmCl, _ := framework.NewClients(t, config) - crt, err := cmCl.CertmanagerV1().Certificates(namespace).Create(ctx, &cmapi.Certificate{ + kCl, _, cmCl, _, _ := framework.NewClients(t, config) + crt := &cmapi.Certificate{ ObjectMeta: metav1.ObjectMeta{Name: "testcrt"}, Spec: cmapi.CertificateSpec{ SecretName: "testsecret", DNSNames: []string{"something"}, - IssuerRef: cmmeta.ObjectReference{ + IssuerRef: cmmeta.IssuerReference{ Name: "issuer", }, PrivateKey: &cmapi.CertificatePrivateKey{ - // This doesn't actually make any difference in this test case because there is no existing private - // key, meaning there's no private key to re-use. - RotationPolicy: cmapi.RotationPolicyAlways, + // The default private key rotation policy is Always. + // RotationPolicy: cmapi.RotationPolicyAlways, }, }, - }, metav1.CreateOptions{}) - if err != nil { - t.Fatalf("failed to create certificate: %v", err) } + t.Log("Simulating an existing private key to test private key rotation") + pk, err := pki.GeneratePrivateKeyForCertificate(crt) + require.NoError(t, err) + pkBytes, err := pki.EncodePrivateKey(pk, cmapi.PKCS1) + require.NoError(t, err) + _, err = kCl.CoreV1().Secrets(namespace).Create(t.Context(), &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: crt.Spec.SecretName, + }, + Data: map[string][]byte{ + "tls.key": pkBytes, + }, + }, metav1.CreateOptions{}) + require.NoError(t, err) + + crt, err = cmCl.CertmanagerV1().Certificates(namespace).Create(t.Context(), crt, metav1.CreateOptions{}) + require.NoError(t, err, "failed to create certificate") + var firstReq *cmapi.CertificateRequest - if err := wait.Poll(time.Millisecond*500, time.Second*10, func() (done bool, err error) { + if err := wait.PollUntilContextTimeout(t.Context(), time.Millisecond*500, time.Second*10, true, func(ctx context.Context) (bool, error) { reqs, err := cmCl.CertmanagerV1().CertificateRequests(namespace).List(ctx, metav1.ListOptions{}) if err != nil { return false, err @@ -106,14 +118,14 @@ func TestGeneratesNewPrivateKeyIfMarkedInvalidRequest(t *testing.T) { // Mark the CSR as 'InvalidRequest' apiutil.SetCertificateRequestCondition(firstReq, cmapi.CertificateRequestConditionInvalidRequest, cmmeta.ConditionTrue, cmapi.CertificateRequestReasonFailed, "manually failed") - _, err = cmCl.CertmanagerV1().CertificateRequests(firstReq.Namespace).UpdateStatus(ctx, firstReq, metav1.UpdateOptions{}) + _, err = cmCl.CertmanagerV1().CertificateRequests(firstReq.Namespace).UpdateStatus(t.Context(), firstReq, metav1.UpdateOptions{}) if err != nil { t.Fatalf("failed to mark CertificateRequest as Failed: %v", err) } t.Log("Marked CertificateRequest as InvalidRequest") // Wait for Certificate to be marked as Failed - if err := wait.Poll(time.Millisecond*500, time.Second*50, func() (done bool, err error) { + if err := wait.PollUntilContextTimeout(t.Context(), time.Millisecond*500, time.Second*50, true, func(ctx context.Context) (bool, error) { crt, err := cmCl.CertmanagerV1().Certificates(crt.Namespace).Get(ctx, crt.Name, metav1.GetOptions{}) if err != nil { return false, err @@ -127,30 +139,33 @@ func TestGeneratesNewPrivateKeyIfMarkedInvalidRequest(t *testing.T) { t.Logf("Issuance acknowledged as failed as expected") t.Logf("Triggering new issuance") - crt, err = cmCl.CertmanagerV1().Certificates(crt.Namespace).Get(ctx, crt.Name, metav1.GetOptions{}) + crt, err = cmCl.CertmanagerV1().Certificates(crt.Namespace).Get(t.Context(), crt.Name, metav1.GetOptions{}) if err != nil { t.Fatalf("failed to get certificate: %v", err) } apiutil.SetCertificateCondition(crt, crt.Generation, cmapi.CertificateConditionIssuing, cmmeta.ConditionTrue, "ManualTrigger", "triggered by test case manually") - crt, err = cmCl.CertmanagerV1().Certificates(crt.Namespace).UpdateStatus(ctx, crt, metav1.UpdateOptions{}) + crt, err = cmCl.CertmanagerV1().Certificates(crt.Namespace).UpdateStatus(t.Context(), crt, metav1.UpdateOptions{}) if err != nil { t.Fatalf("failed to update certificate: %v", err) } - var secondReq *cmapi.CertificateRequest - if err := wait.Poll(time.Millisecond*500, time.Second*10, func() (done bool, err error) { + var secondReq cmapi.CertificateRequest + if err := wait.PollUntilContextTimeout(t.Context(), time.Millisecond*500, time.Second*10, true, func(ctx context.Context) (bool, error) { reqs, err := cmCl.CertmanagerV1().CertificateRequests(namespace).List(ctx, metav1.ListOptions{}) if err != nil { return false, err } for _, req := range reqs.Items { - if req.Name == firstReq.Name { + // We expect a new request to be created (with the same name as the first request) + // and the old request to be deleted. We can check this by comparing the UID of the + // first request with the UID of the second request. + if req.UID == firstReq.UID { continue } - secondReq = &req + secondReq = req return true, nil } @@ -170,12 +185,9 @@ func TestGeneratesNewPrivateKeyIfMarkedInvalidRequest(t *testing.T) { t.Fatalf("failed to parse first CSR: %v", err) } - pk1 := csr1.PublicKey.(crypto.PublicKey) - pk2 := csr2.PublicKey.(crypto.PublicKey) - - if pk1.(comparablePublicKey).Equal(pk2) { - t.Errorf("expected the two requests to have been signed by distinct private keys, but the private key has been reused") - } + match, err := pki.PublicKeysEqual(csr1.PublicKey, csr2.PublicKey) + require.NoError(t, err) + assert.False(t, match, "expected the two requests to have been signed by distinct private keys, but the private key has been reused") } // Runs all Certificate controllers to exercise the full flow of attempting issuance. @@ -183,38 +195,51 @@ func TestGeneratesNewPrivateKeyIfMarkedInvalidRequest(t *testing.T) { // to sign the second request. func TestGeneratesNewPrivateKeyPerRequest(t *testing.T) { namespace := "default" - ctx, cancel := context.WithTimeout(context.Background(), time.Second*30) - defer cancel() - config, stopFn := framework.RunControlPlane(t, ctx) - defer stopFn() + config, stopFn := framework.RunControlPlane(t) + t.Cleanup(stopFn) // Build, instantiate and run all required controllers - stopControllers := runAllControllers(t, ctx, config) + stopControllers := runAllControllers(t, config) defer stopControllers() - _, _, cmCl, _ := framework.NewClients(t, config) - crt, err := cmCl.CertmanagerV1().Certificates(namespace).Create(ctx, &cmapi.Certificate{ + kCl, _, cmCl, _, _ := framework.NewClients(t, config) + crt := &cmapi.Certificate{ ObjectMeta: metav1.ObjectMeta{Name: "testcrt"}, Spec: cmapi.CertificateSpec{ SecretName: "testsecret", DNSNames: []string{"something"}, - IssuerRef: cmmeta.ObjectReference{ + IssuerRef: cmmeta.IssuerReference{ Name: "issuer", }, PrivateKey: &cmapi.CertificatePrivateKey{ - // This doesn't actually make any difference in this test case because there is no existing private - // key, meaning there's no private key to re-use. - RotationPolicy: cmapi.RotationPolicyAlways, + // The default private key rotation policy is Always. + // RotationPolicy: cmapi.RotationPolicyAlways, }, }, - }, metav1.CreateOptions{}) - if err != nil { - t.Fatalf("failed to create certificate: %v", err) } + t.Log("Simulating an existing private key to test private key rotation") + pk, err := pki.GeneratePrivateKeyForCertificate(crt) + require.NoError(t, err) + pkBytes, err := pki.EncodePrivateKey(pk, cmapi.PKCS1) + require.NoError(t, err) + + _, err = kCl.CoreV1().Secrets(namespace).Create(t.Context(), &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: crt.Spec.SecretName, + }, + Data: map[string][]byte{ + "tls.key": pkBytes, + }, + }, metav1.CreateOptions{}) + require.NoError(t, err) + + crt, err = cmCl.CertmanagerV1().Certificates(namespace).Create(t.Context(), crt, metav1.CreateOptions{}) + require.NoError(t, err, "failed to create certificate") + var firstReq *cmapi.CertificateRequest - if err := wait.Poll(time.Millisecond*500, time.Second*10, func() (done bool, err error) { + if err := wait.PollUntilContextTimeout(t.Context(), time.Millisecond*500, time.Second*10, true, func(ctx context.Context) (bool, error) { reqs, err := cmCl.CertmanagerV1().CertificateRequests(namespace).List(ctx, metav1.ListOptions{}) if err != nil { return false, err @@ -240,14 +265,14 @@ func TestGeneratesNewPrivateKeyPerRequest(t *testing.T) { // Mark the CSR as 'Failed' apiutil.SetCertificateRequestCondition(firstReq, cmapi.CertificateRequestConditionReady, cmmeta.ConditionFalse, cmapi.CertificateRequestReasonFailed, "manually failed") - _, err = cmCl.CertmanagerV1().CertificateRequests(firstReq.Namespace).UpdateStatus(ctx, firstReq, metav1.UpdateOptions{}) + _, err = cmCl.CertmanagerV1().CertificateRequests(firstReq.Namespace).UpdateStatus(t.Context(), firstReq, metav1.UpdateOptions{}) if err != nil { t.Fatalf("failed to mark CertificateRequest as Failed: %v", err) } t.Log("Marked CertificateRequest as Failed") // Wait for Certificate to be marked as Failed - if err := wait.Poll(time.Millisecond*500, time.Second*50, func() (done bool, err error) { + if err := wait.PollUntilContextTimeout(t.Context(), time.Millisecond*500, time.Second*50, true, func(ctx context.Context) (bool, error) { crt, err := cmCl.CertmanagerV1().Certificates(crt.Namespace).Get(ctx, crt.Name, metav1.GetOptions{}) if err != nil { return false, err @@ -261,30 +286,33 @@ func TestGeneratesNewPrivateKeyPerRequest(t *testing.T) { t.Logf("Issuance acknowledged as failed as expected") t.Logf("Triggering new issuance") - crt, err = cmCl.CertmanagerV1().Certificates(crt.Namespace).Get(ctx, crt.Name, metav1.GetOptions{}) + crt, err = cmCl.CertmanagerV1().Certificates(crt.Namespace).Get(t.Context(), crt.Name, metav1.GetOptions{}) if err != nil { t.Fatalf("failed to get certificate: %v", err) } apiutil.SetCertificateCondition(crt, crt.Generation, cmapi.CertificateConditionIssuing, cmmeta.ConditionTrue, "ManualTrigger", "triggered by test case manually") - crt, err = cmCl.CertmanagerV1().Certificates(crt.Namespace).UpdateStatus(ctx, crt, metav1.UpdateOptions{}) + crt, err = cmCl.CertmanagerV1().Certificates(crt.Namespace).UpdateStatus(t.Context(), crt, metav1.UpdateOptions{}) if err != nil { t.Fatalf("failed to update certificate: %v", err) } - var secondReq *cmapi.CertificateRequest - if err := wait.Poll(time.Millisecond*500, time.Second*10, func() (done bool, err error) { + var secondReq cmapi.CertificateRequest + if err := wait.PollUntilContextTimeout(t.Context(), time.Millisecond*500, time.Second*10, true, func(ctx context.Context) (bool, error) { reqs, err := cmCl.CertmanagerV1().CertificateRequests(namespace).List(ctx, metav1.ListOptions{}) if err != nil { return false, err } for _, req := range reqs.Items { - if req.Name == firstReq.Name { + // We expect a new request to be created (with the same name as the first request) + // and the old request to be deleted. We can check this by comparing the UID of the + // first request with the UID of the second request. + if req.UID == firstReq.UID { continue } - secondReq = &req + secondReq = req return true, nil } @@ -304,41 +332,65 @@ func TestGeneratesNewPrivateKeyPerRequest(t *testing.T) { t.Fatalf("failed to parse first CSR: %v", err) } - pk1 := csr1.PublicKey.(crypto.PublicKey) - pk2 := csr2.PublicKey.(crypto.PublicKey) - - if pk1.(comparablePublicKey).Equal(pk2) { - t.Errorf("expected the two requests to have been signed by distinct private keys, but the private key has been reused") - } -} - -type comparablePublicKey interface { - Equal(crypto.PublicKey) bool + match, err := pki.PublicKeysEqual(csr1.PublicKey, csr2.PublicKey) + require.NoError(t, err) + assert.False(t, match, "expected the two requests to have been signed by distinct private keys, but the private key has been reused") } -func runAllControllers(t *testing.T, ctx context.Context, config *rest.Config) framework.StopFunc { - kubeClient, factory, cmCl, cmFactory := framework.NewClients(t, config) +func runAllControllers(t *testing.T, config *rest.Config) framework.StopFunc { + kubeClient, factory, cmCl, cmFactory, scheme := framework.NewClients(t, config) log := logf.Log clock := clock.RealClock{} metrics := metrics.New(log, clock) + controllerContext := controllerpkg.Context{ + Client: kubeClient, + Scheme: scheme, + KubeSharedInformerFactory: factory, + CMClient: cmCl, + SharedInformerFactory: cmFactory, + Metrics: metrics, + Clock: clock, + ContextOptions: controllerpkg.ContextOptions{}, + Recorder: framework.NewEventRecorder(t, scheme), + FieldManager: "cert-manager-certificates-issuing-test", + } - revCtrl, revQueue, revMustSync := revisionmanager.NewController(log, cmCl, cmFactory) - revisionManager := controllerpkg.NewController(ctx, "revisionmanager_controller", metrics, revCtrl.ProcessItem, revMustSync, nil, revQueue) + // TODO: set field manager before calling each of those - is that what we do in actual code? + revCtrl, revQueue, revMustSync, err := revisionmanager.NewController(log, &controllerContext) + if err != nil { + t.Fatal(err) + } + revisionManager := controllerpkg.NewController("revisionmanager_controller", metrics, revCtrl.ProcessItem, revMustSync, nil, revQueue) - readyCtrl, readyQueue, readyMustSync := readiness.NewController(log, cmCl, factory, cmFactory, policies.NewReadinessPolicyChain(clock), certificates.RenewalTime, readiness.BuildReadyConditionFromChain, "readiness") - readinessManager := controllerpkg.NewController(ctx, "readiness_controller", metrics, readyCtrl.ProcessItem, readyMustSync, nil, readyQueue) + readyCtrl, readyQueue, readyMustSync, err := readiness.NewController(log, &controllerContext, policies.NewReadinessPolicyChain(clock), pki.RenewalTime, readiness.BuildReadyConditionFromChain) + if err != nil { + t.Fatal(err) + } + readinessManager := controllerpkg.NewController("readiness_controller", metrics, readyCtrl.ProcessItem, readyMustSync, nil, readyQueue) - issueCtrl, issueQueue, issueMustSync := issuing.NewController(log, kubeClient, cmCl, factory, cmFactory, &testpkg.FakeRecorder{}, clock, controllerpkg.CertificateOptions{}, "issuing") - issueManager := controllerpkg.NewController(ctx, "issuing_controller", metrics, issueCtrl.ProcessItem, issueMustSync, nil, issueQueue) + issueCtrl, issueQueue, issueMustSync, err := issuing.NewController(log, &controllerContext) + if err != nil { + t.Fatal(err) + } + issueManager := controllerpkg.NewController("issuing_controller", metrics, issueCtrl.ProcessItem, issueMustSync, nil, issueQueue) - reqCtrl, reqQueue, reqMustSync := requestmanager.NewController(log, cmCl, factory, cmFactory, &testpkg.FakeRecorder{}, clock, controllerpkg.CertificateOptions{}, "requestmanager") - requestManager := controllerpkg.NewController(ctx, "requestmanager_controller", metrics, reqCtrl.ProcessItem, reqMustSync, nil, reqQueue) + reqCtrl, reqQueue, reqMustSync, err := requestmanager.NewController(log, &controllerContext) + if err != nil { + t.Fatal(err) + } + requestManager := controllerpkg.NewController("requestmanager_controller", metrics, reqCtrl.ProcessItem, reqMustSync, nil, reqQueue) - keyCtrl, keyQueue, keyMustSync := keymanager.NewController(log, cmCl, kubeClient, factory, cmFactory, &testpkg.FakeRecorder{}, "keymanager") - keyManager := controllerpkg.NewController(ctx, "keymanager_controller", metrics, keyCtrl.ProcessItem, keyMustSync, nil, keyQueue) + keyCtrl, keyQueue, keyMustSync, err := keymanager.NewController(log, &controllerContext) + if err != nil { + t.Fatal(err) + } + keyManager := controllerpkg.NewController("keymanager_controller", metrics, keyCtrl.ProcessItem, keyMustSync, nil, keyQueue) - triggerCtrl, triggerQueue, triggerMustSync := trigger.NewController(log, cmCl, factory, cmFactory, &testpkg.FakeRecorder{}, clock, policies.NewTriggerPolicyChain(clock).Evaluate, "trigger") - triggerManager := controllerpkg.NewController(ctx, "trigger_controller", metrics, triggerCtrl.ProcessItem, triggerMustSync, nil, triggerQueue) + triggerCtrl, triggerQueue, triggerMustSync, err := trigger.NewController(log, &controllerContext, policies.NewTriggerPolicyChain(clock).Evaluate) + if err != nil { + t.Fatal(err) + } + triggerManager := controllerpkg.NewController("trigger_controller", metrics, triggerCtrl.ProcessItem, triggerMustSync, nil, triggerQueue) return framework.StartInformersAndControllers(t, factory, cmFactory, revisionManager, requestManager, keyManager, triggerManager, readinessManager, issueManager) } diff --git a/test/integration/certificates/issuing_controller_test.go b/test/integration/certificates/issuing_controller_test.go index 24259d9d005..6ff8a4302f8 100644 --- a/test/integration/certificates/issuing_controller_test.go +++ b/test/integration/certificates/issuing_controller_test.go @@ -34,9 +34,9 @@ import ( applycorev1 "k8s.io/client-go/applyconfigurations/core/v1" applymetav1 "k8s.io/client-go/applyconfigurations/meta/v1" "k8s.io/utils/clock" - "k8s.io/utils/pointer" + "k8s.io/utils/ptr" - "github.com/cert-manager/cert-manager/internal/webhook/feature" + "github.com/cert-manager/cert-manager/integration-tests/framework" apiutil "github.com/cert-manager/cert-manager/pkg/api/util" cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" @@ -44,12 +44,9 @@ import ( "github.com/cert-manager/cert-manager/pkg/controller/certificates/issuing" logf "github.com/cert-manager/cert-manager/pkg/logs" "github.com/cert-manager/cert-manager/pkg/metrics" - utilfeature "github.com/cert-manager/cert-manager/pkg/util/feature" utilpki "github.com/cert-manager/cert-manager/pkg/util/pki" - "github.com/cert-manager/cert-manager/test/integration/framework" testcrypto "github.com/cert-manager/cert-manager/test/unit/crypto" "github.com/cert-manager/cert-manager/test/unit/gen" - featuregatetesting "k8s.io/component-base/featuregate/testing" ) // TestIssuingController performs a basic test to ensure that the issuing @@ -58,23 +55,31 @@ import ( // certificate, ca, and private key is stored into the target Secret to // complete Issuing the Certificate. func TestIssuingController(t *testing.T) { - ctx, cancel := context.WithTimeout(context.Background(), time.Second*40) - defer cancel() - - config, stopFn := framework.RunControlPlane(t, ctx) - defer stopFn() + config, stopFn := framework.RunControlPlane(t) + t.Cleanup(stopFn) // Build, instantiate and run the issuing controller. - kubeClient, factory, cmCl, cmFactory := framework.NewClients(t, config) + kubeClient, factory, cmCl, cmFactory, scheme := framework.NewClients(t, config) controllerOptions := controllerpkg.CertificateOptions{ EnableOwnerRef: true, } + controllerContext := controllerpkg.Context{ + Client: kubeClient, + Scheme: scheme, + KubeSharedInformerFactory: factory, + CMClient: cmCl, + SharedInformerFactory: cmFactory, + Clock: clock.RealClock{}, + ContextOptions: controllerpkg.ContextOptions{ + CertificateOptions: controllerOptions, + }, + Recorder: framework.NewEventRecorder(t, scheme), + FieldManager: "cert-manager-certificates-issuing-test", + } - ctrl, queue, mustSync := issuing.NewController(logf.Log, kubeClient, - cmCl, factory, cmFactory, framework.NewEventRecorder(t), clock.RealClock{}, - controllerOptions, "cert-manage-certificates-issuing-test") + ctrl, queue, mustSync, err := issuing.NewController(logf.Log, &controllerContext) + require.NoError(t, err) c := controllerpkg.NewController( - ctx, "issuing_test", metrics.New(logf.Log, clock.RealClock{}), ctrl.ProcessItem, @@ -95,8 +100,7 @@ func TestIssuingController(t *testing.T) { // Create Namespace ns := &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: namespace}} - _, err := kubeClient.CoreV1().Namespaces().Create(ctx, ns, metav1.CreateOptions{}) - if err != nil { + if _, err := kubeClient.CoreV1().Namespaces().Create(t.Context(), ns, metav1.CreateOptions{}); err != nil { t.Fatal(err) } @@ -110,7 +114,7 @@ func TestIssuingController(t *testing.T) { skBytes := utilpki.EncodePKCS1PrivateKey(sk) // Store new private key in secret - _, err = kubeClient.CoreV1().Secrets(namespace).Create(ctx, &corev1.Secret{ + _, err = kubeClient.CoreV1().Secrets(namespace).Create(t.Context(), &corev1.Secret{ ObjectMeta: metav1.ObjectMeta{ Name: nextPrivateKeySecretName, Namespace: namespace, @@ -133,32 +137,21 @@ func TestIssuingController(t *testing.T) { gen.SetCertificateKeyAlgorithm(cmapi.RSAKeyAlgorithm), gen.SetCertificateKeySize(2048), gen.SetCertificateSecretName(secretName), - gen.SetCertificateIssuer(cmmeta.ObjectReference{Name: "testissuer", Group: "foo.io", Kind: "Issuer"}), + gen.SetCertificateIssuer(cmmeta.IssuerReference{Name: "testissuer", Group: "foo.io", Kind: "Issuer"}), ) - crt, err = cmCl.CertmanagerV1().Certificates(namespace).Create(ctx, crt, metav1.CreateOptions{}) - if err != nil { - t.Fatal(err) - } - - // Create x509 CSR from Certificate - csr, err := utilpki.GenerateCSR(crt) + crt, err = cmCl.CertmanagerV1().Certificates(namespace).Create(t.Context(), crt, metav1.CreateOptions{}) if err != nil { t.Fatal(err) } - // Encode CSR - csrDER, err := utilpki.EncodeCSR(csr, sk) + csrPEM, err := gen.CSRWithSignerForCertificate(crt, sk) if err != nil { t.Fatal(err) } - csrPEM := pem.EncodeToMemory(&pem.Block{ - Type: "CERTIFICATE REQUEST", Bytes: csrDER, - }) - // Sign Certificate - certTemplate, err := utilpki.GenerateTemplate(crt) + certTemplate, err := utilpki.CertificateTemplateFromCertificate(crt) if err != nil { t.Fatal(err) } @@ -182,7 +175,7 @@ func TestIssuingController(t *testing.T) { cmapi.SchemeGroupVersion.WithKind("Certificate"), )), ) - req, err = cmCl.CertmanagerV1().CertificateRequests(namespace).Create(ctx, req, metav1.CreateOptions{}) + req, err = cmCl.CertmanagerV1().CertificateRequests(namespace).Create(t.Context(), req, metav1.CreateOptions{}) if err != nil { t.Fatal(err) } @@ -191,7 +184,7 @@ func TestIssuingController(t *testing.T) { req.Status.CA = certPem req.Status.Certificate = certPem apiutil.SetCertificateRequestCondition(req, cmapi.CertificateRequestConditionReady, cmmeta.ConditionTrue, cmapi.CertificateRequestReasonIssued, "") - _, err = cmCl.CertmanagerV1().CertificateRequests(namespace).UpdateStatus(ctx, req, metav1.UpdateOptions{}) + _, err = cmCl.CertmanagerV1().CertificateRequests(namespace).UpdateStatus(t.Context(), req, metav1.UpdateOptions{}) if err != nil { t.Fatal(err) } @@ -200,14 +193,14 @@ func TestIssuingController(t *testing.T) { apiutil.SetCertificateCondition(crt, crt.Generation, cmapi.CertificateConditionIssuing, cmmeta.ConditionTrue, "", "") crt.Status.NextPrivateKeySecretName = &nextPrivateKeySecretName crt.Status.Revision = &revision - crt, err = cmCl.CertmanagerV1().Certificates(namespace).UpdateStatus(ctx, crt, metav1.UpdateOptions{}) + crt, err = cmCl.CertmanagerV1().Certificates(namespace).UpdateStatus(t.Context(), crt, metav1.UpdateOptions{}) if err != nil { t.Fatal(err) } // Wait for the Certificate to have the 'Issuing' condition removed, and // for the signed certificate, ca, and private key stored in the Secret. - err = wait.PollImmediateUntil(time.Millisecond*100, func() (done bool, err error) { + err = wait.PollUntilContextCancel(t.Context(), time.Millisecond*100, true, func(ctx context.Context) (done bool, err error) { crt, err = cmCl.CertmanagerV1().Certificates(namespace).Get(ctx, crtName, metav1.GetOptions{}) if err != nil { t.Logf("Failed to fetch Certificate resource, retrying: %v", err) @@ -256,7 +249,7 @@ func TestIssuingController(t *testing.T) { } return true, nil - }, ctx.Done()) + }) if err != nil { t.Fatalf("Failed to wait for final state: %+v", crt) @@ -264,23 +257,31 @@ func TestIssuingController(t *testing.T) { } func TestIssuingController_PKCS8_PrivateKey(t *testing.T) { - ctx, cancel := context.WithTimeout(context.Background(), time.Second*40) - defer cancel() - - config, stopFn := framework.RunControlPlane(t, ctx) - defer stopFn() + config, stopFn := framework.RunControlPlane(t) + t.Cleanup(stopFn) // Build, instantiate and run the issuing controller. - kubeClient, factory, cmCl, cmFactory := framework.NewClients(t, config) + kubeClient, factory, cmCl, cmFactory, scheme := framework.NewClients(t, config) controllerOptions := controllerpkg.CertificateOptions{ EnableOwnerRef: true, } + controllerContext := controllerpkg.Context{ + Client: kubeClient, + Scheme: scheme, + KubeSharedInformerFactory: factory, + CMClient: cmCl, + SharedInformerFactory: cmFactory, + Clock: clock.RealClock{}, + ContextOptions: controllerpkg.ContextOptions{ + CertificateOptions: controllerOptions, + }, + Recorder: framework.NewEventRecorder(t, scheme), + FieldManager: "cert-manager-certificates-issuing-test", + } - ctrl, queue, mustSync := issuing.NewController(logf.Log, kubeClient, - cmCl, factory, cmFactory, framework.NewEventRecorder(t), clock.RealClock{}, - controllerOptions, "cert-manage-certificates-issuing-test") + ctrl, queue, mustSync, err := issuing.NewController(logf.Log, &controllerContext) + require.NoError(t, err) c := controllerpkg.NewController( - ctx, "issuing_test", metrics.New(logf.Log, clock.RealClock{}), ctrl.ProcessItem, @@ -301,8 +302,7 @@ func TestIssuingController_PKCS8_PrivateKey(t *testing.T) { // Create Namespace ns := &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: namespace}} - _, err := kubeClient.CoreV1().Namespaces().Create(ctx, ns, metav1.CreateOptions{}) - if err != nil { + if _, err := kubeClient.CoreV1().Namespaces().Create(t.Context(), ns, metav1.CreateOptions{}); err != nil { t.Fatal(err) } @@ -320,7 +320,7 @@ func TestIssuingController_PKCS8_PrivateKey(t *testing.T) { } // Store new private key in secret - _, err = kubeClient.CoreV1().Secrets(namespace).Create(ctx, &corev1.Secret{ + _, err = kubeClient.CoreV1().Secrets(namespace).Create(t.Context(), &corev1.Secret{ ObjectMeta: metav1.ObjectMeta{ Name: nextPrivateKeySecretName, Namespace: namespace, @@ -346,32 +346,21 @@ func TestIssuingController_PKCS8_PrivateKey(t *testing.T) { gen.SetCertificateKeyEncoding(cmapi.PKCS8), gen.SetCertificateKeySize(2048), gen.SetCertificateSecretName(secretName), - gen.SetCertificateIssuer(cmmeta.ObjectReference{Name: "testissuer", Group: "foo.io", Kind: "Issuer"}), + gen.SetCertificateIssuer(cmmeta.IssuerReference{Name: "testissuer", Group: "foo.io", Kind: "Issuer"}), ) - crt, err = cmCl.CertmanagerV1().Certificates(namespace).Create(ctx, crt, metav1.CreateOptions{}) + crt, err = cmCl.CertmanagerV1().Certificates(namespace).Create(t.Context(), crt, metav1.CreateOptions{}) if err != nil { t.Fatal(err) } - // Create x509 CSR from Certificate - csr, err := utilpki.GenerateCSR(crt) + csrPEM, err := gen.CSRWithSignerForCertificate(crt, sk) if err != nil { t.Fatal(err) } - // Encode CSR - csrDER, err := utilpki.EncodeCSR(csr, sk) - if err != nil { - t.Fatal(err) - } - - csrPEM := pem.EncodeToMemory(&pem.Block{ - Type: "CERTIFICATE REQUEST", Bytes: csrDER, - }) - // Sign Certificate - certTemplate, err := utilpki.GenerateTemplate(crt) + certTemplate, err := utilpki.CertificateTemplateFromCertificate(crt) if err != nil { t.Fatal(err) } @@ -395,7 +384,7 @@ func TestIssuingController_PKCS8_PrivateKey(t *testing.T) { cmapi.SchemeGroupVersion.WithKind("Certificate"), )), ) - req, err = cmCl.CertmanagerV1().CertificateRequests(namespace).Create(ctx, req, metav1.CreateOptions{}) + req, err = cmCl.CertmanagerV1().CertificateRequests(namespace).Create(t.Context(), req, metav1.CreateOptions{}) if err != nil { t.Fatal(err) } @@ -404,7 +393,7 @@ func TestIssuingController_PKCS8_PrivateKey(t *testing.T) { req.Status.CA = certPem req.Status.Certificate = certPem apiutil.SetCertificateRequestCondition(req, cmapi.CertificateRequestConditionReady, cmmeta.ConditionTrue, cmapi.CertificateRequestReasonIssued, "") - _, err = cmCl.CertmanagerV1().CertificateRequests(namespace).UpdateStatus(ctx, req, metav1.UpdateOptions{}) + _, err = cmCl.CertmanagerV1().CertificateRequests(namespace).UpdateStatus(t.Context(), req, metav1.UpdateOptions{}) if err != nil { t.Fatal(err) } @@ -413,14 +402,14 @@ func TestIssuingController_PKCS8_PrivateKey(t *testing.T) { apiutil.SetCertificateCondition(crt, crt.Generation, cmapi.CertificateConditionIssuing, cmmeta.ConditionTrue, "", "") crt.Status.NextPrivateKeySecretName = &nextPrivateKeySecretName crt.Status.Revision = &revision - crt, err = cmCl.CertmanagerV1().Certificates(namespace).UpdateStatus(ctx, crt, metav1.UpdateOptions{}) + crt, err = cmCl.CertmanagerV1().Certificates(namespace).UpdateStatus(t.Context(), crt, metav1.UpdateOptions{}) if err != nil { t.Fatal(err) } // Wait for the Certificate to have the 'Issuing' condition removed, and for // the signed certificate, ca, and private key stored in the Secret. - err = wait.PollImmediateUntil(time.Millisecond*100, func() (done bool, err error) { + err = wait.PollUntilContextCancel(t.Context(), time.Millisecond*100, true, func(ctx context.Context) (done bool, err error) { crt, err = cmCl.CertmanagerV1().Certificates(namespace).Get(ctx, crtName, metav1.GetOptions{}) if err != nil { t.Logf("Failed to fetch Certificate resource, retrying: %v", err) @@ -469,7 +458,7 @@ func TestIssuingController_PKCS8_PrivateKey(t *testing.T) { } return true, nil - }, ctx.Done()) + }) if err != nil { t.Fatalf("Failed to wait for final state: %+v", crt) } @@ -477,25 +466,33 @@ func TestIssuingController_PKCS8_PrivateKey(t *testing.T) { // Test_IssuingController_SecretTemplate performs a basic check to ensure that // values in a Certificate's SecretTemplate will be copied to the target -// Secret- when they are both added and deleted. +// Secret - when they are both added and deleted. func Test_IssuingController_SecretTemplate(t *testing.T) { - ctx, cancel := context.WithTimeout(context.Background(), time.Second*40) - defer cancel() - - config, stopFn := framework.RunControlPlane(t, ctx) - defer stopFn() + config, stopFn := framework.RunControlPlane(t) + t.Cleanup(stopFn) // Build, instantiate and run the issuing controller. - kubeClient, factory, cmCl, cmFactory := framework.NewClients(t, config) + kubeClient, factory, cmCl, cmFactory, scheme := framework.NewClients(t, config) controllerOptions := controllerpkg.CertificateOptions{ EnableOwnerRef: true, } + controllerContext := controllerpkg.Context{ + Client: kubeClient, + Scheme: scheme, + KubeSharedInformerFactory: factory, + CMClient: cmCl, + SharedInformerFactory: cmFactory, + Clock: clock.RealClock{}, + ContextOptions: controllerpkg.ContextOptions{ + CertificateOptions: controllerOptions, + }, + Recorder: framework.NewEventRecorder(t, scheme), + FieldManager: "cert-manager-certificates-issuing-test", + } - ctrl, queue, mustSync := issuing.NewController(logf.Log, kubeClient, - cmCl, factory, cmFactory, framework.NewEventRecorder(t), clock.RealClock{}, - controllerOptions, "cert-manage-certificates-issuing-test") + ctrl, queue, mustSync, err := issuing.NewController(logf.Log, &controllerContext) + require.NoError(t, err) c := controllerpkg.NewController( - ctx, "issuing_test", metrics.New(logf.Log, clock.RealClock{}), ctrl.ProcessItem, @@ -516,8 +513,7 @@ func Test_IssuingController_SecretTemplate(t *testing.T) { // Create Namespace ns := &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: namespace}} - _, err := kubeClient.CoreV1().Namespaces().Create(ctx, ns, metav1.CreateOptions{}) - if err != nil { + if _, err := kubeClient.CoreV1().Namespaces().Create(t.Context(), ns, metav1.CreateOptions{}); err != nil { t.Fatal(err) } @@ -531,7 +527,7 @@ func Test_IssuingController_SecretTemplate(t *testing.T) { skBytes := utilpki.EncodePKCS1PrivateKey(sk) // Store new private key in secret - _, err = kubeClient.CoreV1().Secrets(namespace).Create(ctx, &corev1.Secret{ + _, err = kubeClient.CoreV1().Secrets(namespace).Create(t.Context(), &corev1.Secret{ ObjectMeta: metav1.ObjectMeta{ Name: nextPrivateKeySecretName, Namespace: namespace, @@ -554,32 +550,21 @@ func Test_IssuingController_SecretTemplate(t *testing.T) { gen.SetCertificateKeyAlgorithm(cmapi.RSAKeyAlgorithm), gen.SetCertificateKeySize(2048), gen.SetCertificateSecretName(secretName), - gen.SetCertificateIssuer(cmmeta.ObjectReference{Name: "testissuer", Group: "foo.io", Kind: "Issuer"}), + gen.SetCertificateIssuer(cmmeta.IssuerReference{Name: "testissuer", Group: "foo.io", Kind: "Issuer"}), ) - crt, err = cmCl.CertmanagerV1().Certificates(namespace).Create(ctx, crt, metav1.CreateOptions{}) - if err != nil { - t.Fatal(err) - } - - // Create x509 CSR from Certificate - csr, err := utilpki.GenerateCSR(crt) + crt, err = cmCl.CertmanagerV1().Certificates(namespace).Create(t.Context(), crt, metav1.CreateOptions{}) if err != nil { t.Fatal(err) } - // Encode CSR - csrDER, err := utilpki.EncodeCSR(csr, sk) + csrPEM, err := gen.CSRWithSignerForCertificate(crt, sk) if err != nil { t.Fatal(err) } - csrPEM := pem.EncodeToMemory(&pem.Block{ - Type: "CERTIFICATE REQUEST", Bytes: csrDER, - }) - // Sign Certificate - certTemplate, err := utilpki.GenerateTemplate(crt) + certTemplate, err := utilpki.CertificateTemplateFromCertificate(crt) if err != nil { t.Fatal(err) } @@ -603,7 +588,7 @@ func Test_IssuingController_SecretTemplate(t *testing.T) { cmapi.SchemeGroupVersion.WithKind("Certificate"), )), ) - req, err = cmCl.CertmanagerV1().CertificateRequests(namespace).Create(ctx, req, metav1.CreateOptions{}) + req, err = cmCl.CertmanagerV1().CertificateRequests(namespace).Create(t.Context(), req, metav1.CreateOptions{}) if err != nil { t.Fatal(err) } @@ -612,7 +597,7 @@ func Test_IssuingController_SecretTemplate(t *testing.T) { req.Status.CA = certPem req.Status.Certificate = certPem apiutil.SetCertificateRequestCondition(req, cmapi.CertificateRequestConditionReady, cmmeta.ConditionTrue, cmapi.CertificateRequestReasonIssued, "") - _, err = cmCl.CertmanagerV1().CertificateRequests(namespace).UpdateStatus(ctx, req, metav1.UpdateOptions{}) + _, err = cmCl.CertmanagerV1().CertificateRequests(namespace).UpdateStatus(t.Context(), req, metav1.UpdateOptions{}) if err != nil { t.Fatal(err) } @@ -621,14 +606,14 @@ func Test_IssuingController_SecretTemplate(t *testing.T) { apiutil.SetCertificateCondition(crt, crt.Generation, cmapi.CertificateConditionIssuing, cmmeta.ConditionTrue, "", "") crt.Status.NextPrivateKeySecretName = &nextPrivateKeySecretName crt.Status.Revision = &revision - crt, err = cmCl.CertmanagerV1().Certificates(namespace).UpdateStatus(ctx, crt, metav1.UpdateOptions{}) + crt, err = cmCl.CertmanagerV1().Certificates(namespace).UpdateStatus(t.Context(), crt, metav1.UpdateOptions{}) if err != nil { t.Fatal(err) } // Wait for the Certificate to have the 'Issuing' condition removed, and for // the signed certificate, ca, and private key stored in the Secret. - err = wait.PollImmediateUntil(time.Millisecond*100, func() (done bool, err error) { + err = wait.PollUntilContextCancel(t.Context(), time.Millisecond*100, true, func(ctx context.Context) (done bool, err error) { crt, err = cmCl.CertmanagerV1().Certificates(namespace).Get(ctx, crtName, metav1.GetOptions{}) if err != nil { t.Logf("Failed to fetch Certificate resource, retrying: %v", err) @@ -641,19 +626,22 @@ func Test_IssuingController_SecretTemplate(t *testing.T) { } return true, nil - }, ctx.Done()) + }) + if err != nil { + t.Fatal(err) + } // Add labels and annotations to the SecretTemplate. annotations := map[string]string{"annotation-1": "abc", "annotation-2": "123"} labels := map[string]string{"labels-1": "abc", "labels-2": "123"} crt = gen.CertificateFrom(crt, gen.SetCertificateSecretTemplate(annotations, labels)) - crt, err = cmCl.CertmanagerV1().Certificates(namespace).Update(ctx, crt, metav1.UpdateOptions{}) + crt, err = cmCl.CertmanagerV1().Certificates(namespace).Update(t.Context(), crt, metav1.UpdateOptions{}) if err != nil { t.Fatal(err) } // Wait for the Annotations and Labels to be observed on the Secret. - err = wait.PollImmediateUntil(time.Millisecond*100, func() (done bool, err error) { + err = wait.PollUntilContextCancel(t.Context(), time.Millisecond*100, true, func(ctx context.Context) (done bool, err error) { secret, err := kubeClient.CoreV1().Secrets(namespace).Get(ctx, secretName, metav1.GetOptions{}) if err != nil { t.Logf("Failed to fetch Secret resource, retrying: %s", err) @@ -670,20 +658,20 @@ func Test_IssuingController_SecretTemplate(t *testing.T) { } } return true, nil - }, ctx.Done()) + }) if err != nil { t.Fatal(err) } // Remove labels and annotations from the SecretTemplate. crt.Spec.SecretTemplate = nil - crt, err = cmCl.CertmanagerV1().Certificates(namespace).Update(ctx, crt, metav1.UpdateOptions{}) + crt, err = cmCl.CertmanagerV1().Certificates(namespace).Update(t.Context(), crt, metav1.UpdateOptions{}) if err != nil { t.Fatal(err) } // Wait for the Annotations and Labels to be removed from the Secret. - err = wait.PollImmediateUntil(time.Millisecond*100, func() (done bool, err error) { + err = wait.PollUntilContextCancel(t.Context(), time.Millisecond*100, true, func(ctx context.Context) (done bool, err error) { secret, err := kubeClient.CoreV1().Secrets(namespace).Get(ctx, secretName, metav1.GetOptions{}) if err != nil { t.Logf("Failed to fetch Secret resource, retrying: %s", err) @@ -702,33 +690,42 @@ func Test_IssuingController_SecretTemplate(t *testing.T) { } } return true, nil - }, ctx.Done()) + }) if err != nil { t.Fatal(err) } } // Test_IssuingController_AdditionalOutputFormats performs a basic check to -// ensure that values in a Certificate's AddiationOutputFormats will be copied -// to the target Secret- when they are both added and deleted. +// ensure that values in a Certificate's AdditionalOutputFormats will be copied +// to the target Secret - when they are both added and deleted. func Test_IssuingController_AdditionalOutputFormats(t *testing.T) { - defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, feature.AdditionalCertificateOutputFormats, true)() - - ctx, cancel := context.WithTimeout(context.Background(), time.Second*40) - defer cancel() - - config, stopFn := framework.RunControlPlane(t, ctx) - defer stopFn() + config, stopFn := framework.RunControlPlane(t) + t.Cleanup(stopFn) // Build, instantiate and run the issuing controller. - kubeClient, factory, cmCl, cmFactory := framework.NewClients(t, config) + kubeClient, factory, cmCl, cmFactory, scheme := framework.NewClients(t, config) controllerOptions := controllerpkg.CertificateOptions{ EnableOwnerRef: true, } - ctrl, queue, mustSync := issuing.NewController(logf.Log, kubeClient, cmCl, factory, cmFactory, framework.NewEventRecorder(t), clock.RealClock{}, controllerOptions, "cert-manager-issuing-test") + controllerContext := controllerpkg.Context{ + Client: kubeClient, + Scheme: scheme, + KubeSharedInformerFactory: factory, + CMClient: cmCl, + SharedInformerFactory: cmFactory, + Clock: clock.RealClock{}, + ContextOptions: controllerpkg.ContextOptions{ + CertificateOptions: controllerOptions, + }, + Recorder: framework.NewEventRecorder(t, scheme), + FieldManager: "cert-manager-certificates-issuing-test", + } + + ctrl, queue, mustSync, err := issuing.NewController(logf.Log, &controllerContext) + require.NoError(t, err) c := controllerpkg.NewController( - ctx, "issuing_test", metrics.New(logf.Log, clock.RealClock{}), ctrl.ProcessItem, @@ -749,8 +746,7 @@ func Test_IssuingController_AdditionalOutputFormats(t *testing.T) { // Create Namespace ns := &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: namespace}} - _, err := kubeClient.CoreV1().Namespaces().Create(ctx, ns, metav1.CreateOptions{}) - if err != nil { + if _, err := kubeClient.CoreV1().Namespaces().Create(t.Context(), ns, metav1.CreateOptions{}); err != nil { t.Fatal(err) } @@ -764,7 +760,7 @@ func Test_IssuingController_AdditionalOutputFormats(t *testing.T) { pkBytes := utilpki.EncodePKCS1PrivateKey(pk) // Store new private key in secret - _, err = kubeClient.CoreV1().Secrets(namespace).Create(ctx, &corev1.Secret{ + _, err = kubeClient.CoreV1().Secrets(namespace).Create(t.Context(), &corev1.Secret{ ObjectMeta: metav1.ObjectMeta{ Name: nextPrivateKeySecretName, Namespace: namespace, @@ -787,32 +783,21 @@ func Test_IssuingController_AdditionalOutputFormats(t *testing.T) { gen.SetCertificateKeyAlgorithm(cmapi.RSAKeyAlgorithm), gen.SetCertificateKeySize(2048), gen.SetCertificateSecretName(secretName), - gen.SetCertificateIssuer(cmmeta.ObjectReference{Name: "testissuer", Group: "foo.io", Kind: "Issuer"}), + gen.SetCertificateIssuer(cmmeta.IssuerReference{Name: "testissuer", Group: "foo.io", Kind: "Issuer"}), ) - crt, err = cmCl.CertmanagerV1().Certificates(namespace).Create(ctx, crt, metav1.CreateOptions{}) - if err != nil { - t.Fatal(err) - } - - // Create x509 CSR from Certificate - csr, err := utilpki.GenerateCSR(crt) + crt, err = cmCl.CertmanagerV1().Certificates(namespace).Create(t.Context(), crt, metav1.CreateOptions{}) if err != nil { t.Fatal(err) } - // Encode CSR - csrDER, err := utilpki.EncodeCSR(csr, pk) + csrPEM, err := gen.CSRWithSignerForCertificate(crt, pk) if err != nil { t.Fatal(err) } - csrPEM := pem.EncodeToMemory(&pem.Block{ - Type: "CERTIFICATE REQUEST", Bytes: csrDER, - }) - // Sign Certificate - certTemplate, err := utilpki.GenerateTemplate(crt) + certTemplate, err := utilpki.CertificateTemplateFromCertificate(crt) if err != nil { t.Fatal(err) } @@ -836,7 +821,7 @@ func Test_IssuingController_AdditionalOutputFormats(t *testing.T) { cmapi.SchemeGroupVersion.WithKind("Certificate"), )), ) - req, err = cmCl.CertmanagerV1().CertificateRequests(namespace).Create(ctx, req, metav1.CreateOptions{}) + req, err = cmCl.CertmanagerV1().CertificateRequests(namespace).Create(t.Context(), req, metav1.CreateOptions{}) if err != nil { t.Fatal(err) } @@ -845,7 +830,7 @@ func Test_IssuingController_AdditionalOutputFormats(t *testing.T) { req.Status.CA = certPEM req.Status.Certificate = certPEM apiutil.SetCertificateRequestCondition(req, cmapi.CertificateRequestConditionReady, cmmeta.ConditionTrue, cmapi.CertificateRequestReasonIssued, "") - _, err = cmCl.CertmanagerV1().CertificateRequests(namespace).UpdateStatus(ctx, req, metav1.UpdateOptions{}) + _, err = cmCl.CertmanagerV1().CertificateRequests(namespace).UpdateStatus(t.Context(), req, metav1.UpdateOptions{}) if err != nil { t.Fatal(err) } @@ -854,14 +839,14 @@ func Test_IssuingController_AdditionalOutputFormats(t *testing.T) { apiutil.SetCertificateCondition(crt, crt.Generation, cmapi.CertificateConditionIssuing, cmmeta.ConditionTrue, "", "") crt.Status.NextPrivateKeySecretName = &nextPrivateKeySecretName crt.Status.Revision = &revision - crt, err = cmCl.CertmanagerV1().Certificates(namespace).UpdateStatus(ctx, crt, metav1.UpdateOptions{}) + crt, err = cmCl.CertmanagerV1().Certificates(namespace).UpdateStatus(t.Context(), crt, metav1.UpdateOptions{}) if err != nil { t.Fatal(err) } // Wait for the Certificate to have the 'Issuing' condition removed, and for // the signed certificate, ca, and private key stored in the Secret. - err = wait.PollImmediateUntil(time.Millisecond*100, func() (done bool, err error) { + err = wait.PollUntilContextCancel(t.Context(), time.Millisecond*100, true, func(ctx context.Context) (done bool, err error) { crt, err = cmCl.CertmanagerV1().Certificates(namespace).Get(ctx, crtName, metav1.GetOptions{}) if err != nil { t.Logf("Failed to fetch Certificate resource, retrying: %v", err) @@ -874,14 +859,17 @@ func Test_IssuingController_AdditionalOutputFormats(t *testing.T) { } return true, nil - }, ctx.Done()) + }) + if err != nil { + t.Fatal(err) + } // Add additional output formats crt = gen.CertificateFrom(crt, gen.SetCertificateAdditionalOutputFormats( cmapi.CertificateAdditionalOutputFormat{Type: "CombinedPEM"}, cmapi.CertificateAdditionalOutputFormat{Type: "DER"}, )) - crt, err = cmCl.CertmanagerV1().Certificates(namespace).Update(ctx, crt, metav1.UpdateOptions{}) + crt, err = cmCl.CertmanagerV1().Certificates(namespace).Update(t.Context(), crt, metav1.UpdateOptions{}) if err != nil { t.Fatal(err) } @@ -890,8 +878,8 @@ func Test_IssuingController_AdditionalOutputFormats(t *testing.T) { pkDER := block.Bytes combinedPEM := append(append(pkBytes, '\n'), certPEM...) - // Wait for the additional output format values to to be observed on the Secret. - err = wait.PollImmediateUntil(time.Millisecond*100, func() (done bool, err error) { + // Wait for the additional output format values to be observed on the Secret. + err = wait.PollUntilContextCancel(t.Context(), time.Millisecond*100, true, func(ctx context.Context) (done bool, err error) { secret, err := kubeClient.CoreV1().Secrets(namespace).Get(ctx, secretName, metav1.GetOptions{}) if err != nil { t.Logf("Failed to fetch Secret resource, retrying: %s", err) @@ -901,20 +889,20 @@ func Test_IssuingController_AdditionalOutputFormats(t *testing.T) { "ca.crt": certPEM, "tls.crt": certPEM, "tls.key": pkBytes, "key.der": pkDER, "tls-combined.pem": combinedPEM, }, secret.Data), nil - }, ctx.Done()) + }) if err != nil { t.Fatal(err) } // Remove AdditionalOutputFormats crt.Spec.AdditionalOutputFormats = nil - crt, err = cmCl.CertmanagerV1().Certificates(namespace).Update(ctx, crt, metav1.UpdateOptions{}) + crt, err = cmCl.CertmanagerV1().Certificates(namespace).Update(t.Context(), crt, metav1.UpdateOptions{}) if err != nil { t.Fatal(err) } // Wait for the additional output formats to be removed from the Secret. - err = wait.PollImmediateUntil(time.Millisecond*100, func() (done bool, err error) { + err = wait.PollUntilContextCancel(t.Context(), time.Millisecond*100, true, func(ctx context.Context) (done bool, err error) { secret, err := kubeClient.CoreV1().Secrets(namespace).Get(ctx, secretName, metav1.GetOptions{}) if err != nil { t.Logf("Failed to fetch Secret resource, retrying: %s", err) @@ -923,7 +911,7 @@ func Test_IssuingController_AdditionalOutputFormats(t *testing.T) { return reflect.DeepEqual(map[string][]byte{ "ca.crt": certPEM, "tls.crt": certPEM, "tls.key": pkBytes, }, secret.Data), nil - }, ctx.Done()) + }) if err != nil { t.Fatal(err) } @@ -934,26 +922,34 @@ func Test_IssuingController_AdditionalOutputFormats(t *testing.T) { // is removed again when disabled. // Also ensures that changes to the Secret which modify the owner reference, // are reverted or corrected if needed by the issuing controller. -func Test_IssuingController_OwnerRefernece(t *testing.T) { +func Test_IssuingController_OwnerReference(t *testing.T) { const ( fieldManager = "cert-manager-issuing-test" ) - ctx, cancel := context.WithTimeout(context.Background(), time.Second*60) - defer cancel() - - config, stopFn := framework.RunControlPlane(t, ctx) - defer stopFn() + config, stopFn := framework.RunControlPlane(t) + t.Cleanup(stopFn) - kubeClient, factory, cmClient, cmFactory := framework.NewClients(t, config) + kubeClient, factory, cmClient, cmFactory, scheme := framework.NewClients(t, config) controllerOptions := controllerpkg.CertificateOptions{ EnableOwnerRef: false, } - ctrl, queue, mustSync := issuing.NewController(logf.Log, kubeClient, cmClient, - factory, cmFactory, framework.NewEventRecorder(t), clock.RealClock{}, - controllerOptions, fieldManager, - ) - c := controllerpkg.NewController(ctx, fieldManager, metrics.New(logf.Log, clock.RealClock{}), ctrl.ProcessItem, mustSync, nil, queue) + controllerContext := controllerpkg.Context{ + Client: kubeClient, + Scheme: scheme, + KubeSharedInformerFactory: factory, + CMClient: cmClient, + SharedInformerFactory: cmFactory, + Clock: clock.RealClock{}, + ContextOptions: controllerpkg.ContextOptions{ + CertificateOptions: controllerOptions, + }, + Recorder: framework.NewEventRecorder(t, scheme), + FieldManager: fieldManager, + } + ctrl, queue, mustSync, err := issuing.NewController(logf.Log, &controllerContext) + require.NoError(t, err) + c := controllerpkg.NewController(fieldManager, metrics.New(logf.Log, clock.RealClock{}), ctrl.ProcessItem, mustSync, nil, queue) stopControllerNoOwnerRef := framework.StartInformersAndController(t, factory, cmFactory, c) defer func() { if stopControllerNoOwnerRef != nil { @@ -962,7 +958,7 @@ func Test_IssuingController_OwnerRefernece(t *testing.T) { }() t.Log("creating a Secret and Certificate which does not need issuance") - ns, err := kubeClient.CoreV1().Namespaces().Create(ctx, &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "owner-reference-test"}}, metav1.CreateOptions{}) + ns, err := kubeClient.CoreV1().Namespaces().Create(t.Context(), &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "owner-reference-test"}}, metav1.CreateOptions{}) require.NoError(t, err) crt := gen.Certificate("owner-reference-test", gen.SetCertificateNamespace(ns.Name), @@ -973,10 +969,10 @@ func Test_IssuingController_OwnerRefernece(t *testing.T) { gen.SetCertificateKeyAlgorithm(cmapi.RSAKeyAlgorithm), gen.SetCertificateKeySize(2048), gen.SetCertificateSecretName("cert-manager-issuing-test-secret"), - gen.SetCertificateIssuer(cmmeta.ObjectReference{Name: "testissuer", Group: "foo.io", Kind: "Issuer"}), + gen.SetCertificateIssuer(cmmeta.IssuerReference{Name: "testissuer", Group: "foo.io", Kind: "Issuer"}), ) bundle := testcrypto.MustCreateCryptoBundle(t, crt, &clock.RealClock{}) - secret, err := kubeClient.CoreV1().Secrets(ns.Name).Create(ctx, &corev1.Secret{ + secret, err := kubeClient.CoreV1().Secrets(ns.Name).Create(t.Context(), &corev1.Secret{ ObjectMeta: metav1.ObjectMeta{Namespace: ns.Name, Name: crt.Spec.SecretName}, Data: map[string][]byte{ "ca.crt": bundle.CertBytes, @@ -985,18 +981,18 @@ func Test_IssuingController_OwnerRefernece(t *testing.T) { }, }, metav1.CreateOptions{FieldManager: fieldManager}) require.NoError(t, err) - crt, err = cmClient.CertmanagerV1().Certificates(ns.Name).Create(ctx, crt, metav1.CreateOptions{}) + crt, err = cmClient.CertmanagerV1().Certificates(ns.Name).Create(t.Context(), crt, metav1.CreateOptions{}) require.NoError(t, err) t.Log("ensure Certificate does not gain Issuing condition") require.Never(t, func() bool { - crt, err = cmClient.CertmanagerV1().Certificates(ns.Name).Get(ctx, crt.Name, metav1.GetOptions{}) + crt, err = cmClient.CertmanagerV1().Certificates(ns.Name).Get(t.Context(), crt.Name, metav1.GetOptions{}) require.NoError(t, err) return apiutil.CertificateHasCondition(crt, cmapi.CertificateCondition{Type: cmapi.CertificateConditionIssuing, Status: cmmeta.ConditionTrue}) }, time.Second*3, time.Millisecond*10, "expected Certificate to not gain Issuing condition") t.Log("added owner reference to Secret for Certificate with field manager should get removed") - secret, err = kubeClient.CoreV1().Secrets(ns.Name).Get(ctx, secret.Name, metav1.GetOptions{}) + secret, err = kubeClient.CoreV1().Secrets(ns.Name).Get(t.Context(), secret.Name, metav1.GetOptions{}) require.NoError(t, err) ref := *metav1.NewControllerRef(crt, cmapi.SchemeGroupVersion.WithKind("Certificate")) applyCnf := applycorev1.Secret(secret.Name, secret.Namespace). @@ -1006,71 +1002,82 @@ func Test_IssuingController_OwnerRefernece(t *testing.T) { Name: &ref.Name, UID: &ref.UID, Controller: ref.Controller, BlockOwnerDeletion: ref.BlockOwnerDeletion, }) - secret, err = kubeClient.CoreV1().Secrets(secret.Namespace).Apply(ctx, applyCnf, metav1.ApplyOptions{FieldManager: fieldManager, Force: true}) + secret, err = kubeClient.CoreV1().Secrets(secret.Namespace).Apply(t.Context(), applyCnf, metav1.ApplyOptions{FieldManager: fieldManager, Force: true}) require.NoError(t, err) require.Len(t, secret.OwnerReferences, 1) require.Eventually(t, func() bool { - secret, err = kubeClient.CoreV1().Secrets(ns.Name).Get(ctx, secret.Name, metav1.GetOptions{}) + secret, err = kubeClient.CoreV1().Secrets(ns.Name).Get(t.Context(), secret.Name, metav1.GetOptions{}) require.NoError(t, err) return len(secret.OwnerReferences) == 0 }, time.Second*3, time.Millisecond*10, "expected Secret to have owner reference to Certificate removed: %#+v", secret.OwnerReferences) t.Log("added owner reference to Secret for non Certificate UID with field manager should not get removed") - secret, err = kubeClient.CoreV1().Secrets(ns.Name).Get(ctx, secret.Name, metav1.GetOptions{}) + secret, err = kubeClient.CoreV1().Secrets(ns.Name).Get(t.Context(), secret.Name, metav1.GetOptions{}) require.NoError(t, err) - fooRef := metav1.OwnerReference{APIVersion: "foo.bar.io/v1", Kind: "Foo", Name: "Bar", UID: types.UID("not-cert"), Controller: pointer.Bool(false), BlockOwnerDeletion: pointer.Bool(false)} + fooRef := metav1.OwnerReference{APIVersion: "foo.bar.io/v1", Kind: "Foo", Name: "Bar", UID: types.UID("not-cert"), Controller: ptr.To(false), BlockOwnerDeletion: ptr.To(false)} applyCnf.OwnerReferences = []applymetav1.OwnerReferenceApplyConfiguration{{ APIVersion: &fooRef.APIVersion, Kind: &fooRef.Kind, Name: &fooRef.Name, UID: &fooRef.UID, Controller: fooRef.Controller, BlockOwnerDeletion: fooRef.BlockOwnerDeletion, }} - secret, err = kubeClient.CoreV1().Secrets(secret.Namespace).Apply(ctx, applyCnf, metav1.ApplyOptions{FieldManager: fieldManager, Force: true}) + secret, err = kubeClient.CoreV1().Secrets(secret.Namespace).Apply(t.Context(), applyCnf, metav1.ApplyOptions{FieldManager: fieldManager, Force: true}) require.NoError(t, err) require.Never(t, func() bool { - secret, err = kubeClient.CoreV1().Secrets(ns.Name).Get(ctx, secret.Name, metav1.GetOptions{}) + secret, err = kubeClient.CoreV1().Secrets(ns.Name).Get(t.Context(), secret.Name, metav1.GetOptions{}) require.NoError(t, err) return !apiequality.Semantic.DeepEqual(secret.OwnerReferences, []metav1.OwnerReference{fooRef}) }, time.Second*3, time.Millisecond*10, "expected Secret to not have owner reference to Foo removed: %#+v", secret.OwnerReferences) t.Log("restarting controller with secret owner reference option enabled") stopControllerNoOwnerRef() - kubeClient, factory, cmClient, cmFactory = framework.NewClients(t, config) + kubeClient, factory, cmClient, cmFactory, _ = framework.NewClients(t, config) stopControllerNoOwnerRef = nil controllerOptions.EnableOwnerRef = true - ctrl, queue, mustSync = issuing.NewController(logf.Log, kubeClient, cmClient, - factory, cmFactory, framework.NewEventRecorder(t), clock.RealClock{}, - controllerOptions, fieldManager, - ) - c = controllerpkg.NewController(ctx, fieldManager, metrics.New(logf.Log, clock.RealClock{}), ctrl.ProcessItem, mustSync, nil, queue) + controllerContext = controllerpkg.Context{ + Client: kubeClient, + Scheme: scheme, + KubeSharedInformerFactory: factory, + CMClient: cmClient, + SharedInformerFactory: cmFactory, + Clock: clock.RealClock{}, + ContextOptions: controllerpkg.ContextOptions{ + CertificateOptions: controllerOptions, + }, + Recorder: framework.NewEventRecorder(t, scheme), + FieldManager: fieldManager, + } + ctrl, queue, mustSync, err = issuing.NewController(logf.Log, &controllerContext) + require.NoError(t, err) + c = controllerpkg.NewController(fieldManager, metrics.New(logf.Log, clock.RealClock{}), ctrl.ProcessItem, mustSync, nil, queue) stopControllerOwnerRef := framework.StartInformersAndController(t, factory, cmFactory, c) defer stopControllerOwnerRef() t.Log("waiting for owner reference to be set") applyCnf.OwnerReferences = nil - secret, err = kubeClient.CoreV1().Secrets(secret.Namespace).Apply(ctx, applyCnf, metav1.ApplyOptions{FieldManager: fieldManager, Force: true}) + secret, err = kubeClient.CoreV1().Secrets(secret.Namespace).Apply(t.Context(), applyCnf, metav1.ApplyOptions{FieldManager: fieldManager, Force: true}) require.NoError(t, err) require.Eventually(t, func() bool { - secret, err = kubeClient.CoreV1().Secrets(ns.Name).Get(ctx, secret.Name, metav1.GetOptions{}) + secret, err = kubeClient.CoreV1().Secrets(ns.Name).Get(t.Context(), secret.Name, metav1.GetOptions{}) require.NoError(t, err) return apiequality.Semantic.DeepEqual(secret.OwnerReferences, []metav1.OwnerReference{*metav1.NewControllerRef(crt, cmapi.SchemeGroupVersion.WithKind("Certificate"))}) }, time.Second*10, time.Millisecond*10, "expected Secret to have owner reference to Certificate added: %#+v", secret.OwnerReferences) t.Log("deleting the owner reference, should have owner reference added back") applyCnf.OwnerReferences = []applymetav1.OwnerReferenceApplyConfiguration{} - secret, err = kubeClient.CoreV1().Secrets(secret.Namespace).Apply(ctx, applyCnf, metav1.ApplyOptions{FieldManager: fieldManager, Force: true}) + secret, err = kubeClient.CoreV1().Secrets(secret.Namespace).Apply(t.Context(), applyCnf, metav1.ApplyOptions{FieldManager: fieldManager, Force: true}) require.NoError(t, err) require.Len(t, secret.OwnerReferences, 0) require.Eventually(t, func() bool { - secret, err = kubeClient.CoreV1().Secrets(ns.Name).Get(ctx, secret.Name, metav1.GetOptions{}) + secret, err = kubeClient.CoreV1().Secrets(ns.Name).Get(t.Context(), secret.Name, metav1.GetOptions{}) require.NoError(t, err) return apiequality.Semantic.DeepEqual(secret.OwnerReferences, []metav1.OwnerReference{*metav1.NewControllerRef(crt, cmapi.SchemeGroupVersion.WithKind("Certificate"))}) }, time.Second*3, time.Millisecond*10, "expected Secret to have owner reference to Certificate added: %#+v", secret.OwnerReferences) t.Log("changing the options on the owner reference, should have the options reversed") secret.OwnerReferences[0].Name = "random-certificate-name" - secret, err = kubeClient.CoreV1().Secrets(secret.Namespace).Update(ctx, secret, metav1.UpdateOptions{}) + secret, err = kubeClient.CoreV1().Secrets(secret.Namespace).Update(t.Context(), secret, metav1.UpdateOptions{}) require.NoError(t, err) require.Eventually(t, func() bool { - secret, err = kubeClient.CoreV1().Secrets(ns.Name).Get(ctx, secret.Name, metav1.GetOptions{}) + secret, err = kubeClient.CoreV1().Secrets(ns.Name).Get(t.Context(), secret.Name, metav1.GetOptions{}) require.NoError(t, err) return apiequality.Semantic.DeepEqual(secret.OwnerReferences, []metav1.OwnerReference{*metav1.NewControllerRef(crt, cmapi.SchemeGroupVersion.WithKind("Certificate"))}) }, time.Second*3, time.Millisecond*10, "expected Secret to have owner reference options to Certificate reverse: %#+v", secret.OwnerReferences) diff --git a/test/integration/certificates/metrics_controller_test.go b/test/integration/certificates/metrics_controller_test.go index 74a8ea166dd..ea8944f0394 100644 --- a/test/integration/certificates/metrics_controller_test.go +++ b/test/integration/certificates/metrics_controller_test.go @@ -27,18 +27,18 @@ import ( "time" corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/wait" fakeclock "k8s.io/utils/clock/testing" + "github.com/cert-manager/cert-manager/integration-tests/framework" + acmemeta "github.com/cert-manager/cert-manager/pkg/apis/acme/v1" cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" controllerpkg "github.com/cert-manager/cert-manager/pkg/controller" controllermetrics "github.com/cert-manager/cert-manager/pkg/controller/certificates/metrics" logf "github.com/cert-manager/cert-manager/pkg/logs" "github.com/cert-manager/cert-manager/pkg/metrics" - "github.com/cert-manager/cert-manager/test/integration/framework" "github.com/cert-manager/cert-manager/test/unit/gen" ) @@ -49,30 +49,32 @@ var ( # TYPE certmanager_clock_time_seconds counter certmanager_clock_time_seconds %.9e`, float64(fixedClock.Now().Unix())) clockGaugeMetric = fmt.Sprintf(` -# HELP certmanager_clock_time_seconds_gauge The clock time given in seconds (from 1970/01/01 UTC). +# HELP certmanager_clock_time_seconds_gauge The clock time given in seconds (from 1970/01/01 UTC). Gauge form of the deprecated clock_time_seconds counter. No labels. # TYPE certmanager_clock_time_seconds_gauge gauge certmanager_clock_time_seconds_gauge %.9e`, float64(fixedClock.Now().Unix())) ) -// TestMetricscontoller performs a basic test to ensure that Certificates +// TestMetricsController performs a basic test to ensure that Certificates // metrics are exposed when a Certificate is created, updated, and removed when // it is deleted. func TestMetricsController(t *testing.T) { - ctx, cancel := context.WithTimeout(context.Background(), time.Second*40) - defer cancel() - - config, stopFn := framework.RunControlPlane(t, ctx) - defer stopFn() - + config, stopFn := framework.RunControlPlane(t) + t.Cleanup(stopFn) // Build, instantiate and run the issuing controller. - kubernetesCl, factory, cmClient, cmFactory := framework.NewClients(t, config) + kubernetesCl, factory, cmClient, cmFactory, scheme := framework.NewClients(t, config) metricsHandler := metrics.New(logf.Log, fixedClock) - ln, err := net.Listen("tcp", "127.0.0.1:0") + lc := net.ListenConfig{} + ln, err := lc.Listen(t.Context(), "tcp", "127.0.0.1:0") if err != nil { t.Fatal(err) } + challengesInformer := cmFactory.Acme().V1().Challenges() + certsInformer := cmFactory.Certmanager().V1().Certificates() + metricsHandler.SetupACMECollector(challengesInformer.Lister()) + metricsHandler.SetupCertificateCollector(certsInformer.Lister()) + server := metricsHandler.NewServer(ln) errCh := make(chan error) @@ -83,7 +85,7 @@ func TestMetricsController(t *testing.T) { } }() defer func() { - shutdownCtx, cancel := context.WithTimeout(context.Background(), time.Second*5) + shutdownCtx, cancel := context.WithTimeout(context.WithoutCancel(t.Context()), time.Second*5) defer cancel() if err := server.Shutdown(shutdownCtx); err != nil { @@ -95,9 +97,19 @@ func TestMetricsController(t *testing.T) { } }() - ctrl, queue, mustSync := controllermetrics.NewController(factory, cmFactory, metricsHandler) + // This is not required once the certificate controller is removed. + controllerContext := controllerpkg.Context{ + Scheme: scheme, + KubeSharedInformerFactory: factory, + SharedInformerFactory: cmFactory, + Metrics: metricsHandler, + ContextOptions: controllerpkg.ContextOptions{}, + } + ctrl, queue, mustSync, err := controllermetrics.NewController(&controllerContext) + if err != nil { + t.Fatal(err) + } c := controllerpkg.NewController( - ctx, "metrics_test", metricsHandler, ctrl.ProcessItem, @@ -105,6 +117,7 @@ func TestMetricsController(t *testing.T) { nil, queue, ) + stopController := framework.StartInformersAndController(t, factory, cmFactory, c) defer stopController() @@ -118,23 +131,29 @@ func TestMetricsController(t *testing.T) { // Create Namespace ns := &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: namespace}} - _, err = kubernetesCl.CoreV1().Namespaces().Create(ctx, ns, metav1.CreateOptions{}) + _, err = kubernetesCl.CoreV1().Namespaces().Create(t.Context(), ns, metav1.CreateOptions{}) if err != nil { t.Fatal(err) } testMetrics := func(expectedOutput string) error { - resp, err := http.DefaultClient.Get(metricsEndpoint) + req, err := http.NewRequestWithContext(t.Context(), http.MethodGet, metricsEndpoint, nil) + if err != nil { + return err + } + resp, err := http.DefaultClient.Do(req) if err != nil { return err } + defer resp.Body.Close() output, err := io.ReadAll(resp.Body) if err != nil { return err } - if strings.TrimSpace(string(output)) != strings.TrimSpace(expectedOutput) { + trimmedOutput := strings.SplitN(string(output), "# HELP go_gc_duration_seconds", 2)[0] + if strings.TrimSpace(trimmedOutput) != strings.TrimSpace(expectedOutput) { return fmt.Errorf("got unexpected metrics output\nexp:\n%s\ngot:\n%s\n", expectedOutput, output) } @@ -143,14 +162,14 @@ func TestMetricsController(t *testing.T) { } waitForMetrics := func(expectedOutput string) { - err := wait.PollImmediateUntil(time.Millisecond*100, func() (done bool, err error) { + err = wait.PollUntilContextCancel(t.Context(), time.Millisecond*100, true, func(ctx context.Context) (done bool, err error) { if err := testMetrics(expectedOutput); err != nil { lastErr = err - return false, nil + return false, nil //nolint:nilerr } return true, nil - }, ctx.Done()) + }) if err != nil { t.Fatalf("%s: failed to wait for expected metrics to be exposed: %s", err, lastErr) } @@ -161,32 +180,67 @@ func TestMetricsController(t *testing.T) { // Create Certificate crt := gen.Certificate(crtName, - gen.SetCertificateIssuer(cmmeta.ObjectReference{Kind: "Issuer", Name: "test-issuer", Group: "test-issuer-group"}), + gen.SetCertificateIssuer(cmmeta.IssuerReference{Kind: "Issuer", Name: "test-issuer", Group: "test-issuer-group"}), gen.SetCertificateSecretName(crtName), gen.SetCertificateCommonName(crtName), gen.SetCertificateNamespace(namespace), gen.SetCertificateUID("uid-1"), ) - crt, err = cmClient.CertmanagerV1().Certificates(namespace).Create(ctx, crt, metav1.CreateOptions{}) + challenge := gen.Challenge("test-challenge-status", + gen.SetChallengeDNSName("example.com"), + gen.SetChallengeProcessing(false), + gen.SetChallengeType(acmemeta.ACMEChallengeTypeDNS01), + gen.SetChallengeNamespace(namespace), + ) + + crt, err = cmClient.CertmanagerV1().Certificates(namespace).Create(t.Context(), crt, metav1.CreateOptions{}) + if err != nil { + t.Fatal(err) + } + + challenge, err = cmClient.AcmeV1().Challenges(namespace).Create(t.Context(), challenge, metav1.CreateOptions{}) + if err != nil { + t.Fatal(err) + } + + challenge.Status.State = acmemeta.Pending + challenge.Status.Processing = true + challenge, err = cmClient.AcmeV1().Challenges(namespace).UpdateStatus(t.Context(), challenge, metav1.UpdateOptions{}) if err != nil { t.Fatal(err) } // Should expose that Certificate as unknown with no expiry - waitForMetrics(`# HELP certmanager_certificate_expiration_timestamp_seconds The date after which the certificate expires. Expressed as a Unix Epoch Time. + waitForMetrics(`# HELP certmanager_certificate_challenge_status The status of certificate challenges +# TYPE certmanager_certificate_challenge_status gauge +certmanager_certificate_challenge_status{domain="example.com",name="test-challenge-status",namespace="testns",processing="true",reason="",status="",type="DNS-01"} 0 +certmanager_certificate_challenge_status{domain="example.com",name="test-challenge-status",namespace="testns",processing="true",reason="",status="errored",type="DNS-01"} 0 +certmanager_certificate_challenge_status{domain="example.com",name="test-challenge-status",namespace="testns",processing="true",reason="",status="expired",type="DNS-01"} 0 +certmanager_certificate_challenge_status{domain="example.com",name="test-challenge-status",namespace="testns",processing="true",reason="",status="invalid",type="DNS-01"} 0 +certmanager_certificate_challenge_status{domain="example.com",name="test-challenge-status",namespace="testns",processing="true",reason="",status="pending",type="DNS-01"} 1 +certmanager_certificate_challenge_status{domain="example.com",name="test-challenge-status",namespace="testns",processing="true",reason="",status="processing",type="DNS-01"} 0 +certmanager_certificate_challenge_status{domain="example.com",name="test-challenge-status",namespace="testns",processing="true",reason="",status="ready",type="DNS-01"} 0 +certmanager_certificate_challenge_status{domain="example.com",name="test-challenge-status",namespace="testns",processing="true",reason="",status="valid",type="DNS-01"} 0 +# HELP certmanager_certificate_expiration_timestamp_seconds The timestamp after which the certificate expires, expressed in Unix Epoch Time. # TYPE certmanager_certificate_expiration_timestamp_seconds gauge certmanager_certificate_expiration_timestamp_seconds{issuer_group="test-issuer-group",issuer_kind="Issuer",issuer_name="test-issuer",name="testcrt",namespace="testns"} 0 +# HELP certmanager_certificate_not_after_timestamp_seconds The timestamp after which the certificate is invalid, expressed as a Unix Epoch Time. +# TYPE certmanager_certificate_not_after_timestamp_seconds gauge +certmanager_certificate_not_after_timestamp_seconds{issuer_group="test-issuer-group",issuer_kind="Issuer",issuer_name="test-issuer",name="testcrt",namespace="testns"} 0 +# HELP certmanager_certificate_not_before_timestamp_seconds The timestamp before which the certificate is invalid, expressed as a Unix Epoch Time. +# TYPE certmanager_certificate_not_before_timestamp_seconds gauge +certmanager_certificate_not_before_timestamp_seconds{issuer_group="test-issuer-group",issuer_kind="Issuer",issuer_name="test-issuer",name="testcrt",namespace="testns"} 0 # HELP certmanager_certificate_ready_status The ready status of the certificate. # TYPE certmanager_certificate_ready_status gauge certmanager_certificate_ready_status{condition="False",issuer_group="test-issuer-group",issuer_kind="Issuer",issuer_name="test-issuer",name="testcrt",namespace="testns"} 0 certmanager_certificate_ready_status{condition="True",issuer_group="test-issuer-group",issuer_kind="Issuer",issuer_name="test-issuer",name="testcrt",namespace="testns"} 0 certmanager_certificate_ready_status{condition="Unknown",issuer_group="test-issuer-group",issuer_kind="Issuer",issuer_name="test-issuer",name="testcrt",namespace="testns"} 1 -# HELP certmanager_certificate_renewal_timestamp_seconds The number of seconds before expiration time the certificate should renew. +# HELP certmanager_certificate_renewal_timestamp_seconds The timestamp after which the certificate should be renewed, expressed in Unix Epoch Time. # TYPE certmanager_certificate_renewal_timestamp_seconds gauge certmanager_certificate_renewal_timestamp_seconds{issuer_group="test-issuer-group",issuer_kind="Issuer",issuer_name="test-issuer",name="testcrt",namespace="testns"} 0 ` + clockCounterMetric + clockGaugeMetric + ` -# HELP certmanager_controller_sync_call_count The number of sync() calls made by a controller. +# HELP certmanager_controller_sync_call_count The number of sync() calls made by a controller. Label: controller (fixed small set of controller names). # TYPE certmanager_controller_sync_call_count counter certmanager_controller_sync_call_count{controller="metrics_test"} 1 `) @@ -195,6 +249,9 @@ certmanager_controller_sync_call_count{controller="metrics_test"} 1 crt.Status.NotAfter = &metav1.Time{ Time: time.Unix(100, 0), } + crt.Status.NotBefore = &metav1.Time{ + Time: time.Unix(200, 0), + } crt.Status.Conditions = []cmapi.CertificateCondition{ { Type: cmapi.CertificateConditionReady, @@ -204,37 +261,65 @@ certmanager_controller_sync_call_count{controller="metrics_test"} 1 crt.Status.RenewalTime = &metav1.Time{ Time: time.Unix(100, 0), } - _, err = cmClient.CertmanagerV1().Certificates(namespace).UpdateStatus(ctx, crt, metav1.UpdateOptions{}) + _, err = cmClient.CertmanagerV1().Certificates(namespace).UpdateStatus(t.Context(), crt, metav1.UpdateOptions{}) + if err != nil { + t.Fatal(err) + } + + challenge.Status.State = acmemeta.Ready + challenge.Status.Processing = false + challenge.Status.Presented = true + _, err = cmClient.AcmeV1().Challenges(namespace).UpdateStatus(t.Context(), challenge, metav1.UpdateOptions{}) if err != nil { t.Fatal(err) } // Should expose that Certificate as ready with expiry - waitForMetrics(`# HELP certmanager_certificate_expiration_timestamp_seconds The date after which the certificate expires. Expressed as a Unix Epoch Time. + waitForMetrics(`# HELP certmanager_certificate_challenge_status The status of certificate challenges +# TYPE certmanager_certificate_challenge_status gauge +certmanager_certificate_challenge_status{domain="example.com",name="test-challenge-status",namespace="testns",processing="false",reason="",status="",type="DNS-01"} 0 +certmanager_certificate_challenge_status{domain="example.com",name="test-challenge-status",namespace="testns",processing="false",reason="",status="errored",type="DNS-01"} 0 +certmanager_certificate_challenge_status{domain="example.com",name="test-challenge-status",namespace="testns",processing="false",reason="",status="expired",type="DNS-01"} 0 +certmanager_certificate_challenge_status{domain="example.com",name="test-challenge-status",namespace="testns",processing="false",reason="",status="invalid",type="DNS-01"} 0 +certmanager_certificate_challenge_status{domain="example.com",name="test-challenge-status",namespace="testns",processing="false",reason="",status="pending",type="DNS-01"} 0 +certmanager_certificate_challenge_status{domain="example.com",name="test-challenge-status",namespace="testns",processing="false",reason="",status="processing",type="DNS-01"} 0 +certmanager_certificate_challenge_status{domain="example.com",name="test-challenge-status",namespace="testns",processing="false",reason="",status="ready",type="DNS-01"} 1 +certmanager_certificate_challenge_status{domain="example.com",name="test-challenge-status",namespace="testns",processing="false",reason="",status="valid",type="DNS-01"} 0 +# HELP certmanager_certificate_expiration_timestamp_seconds The timestamp after which the certificate expires, expressed in Unix Epoch Time. # TYPE certmanager_certificate_expiration_timestamp_seconds gauge certmanager_certificate_expiration_timestamp_seconds{issuer_group="test-issuer-group",issuer_kind="Issuer",issuer_name="test-issuer",name="testcrt",namespace="testns"} 100 +# HELP certmanager_certificate_not_after_timestamp_seconds The timestamp after which the certificate is invalid, expressed as a Unix Epoch Time. +# TYPE certmanager_certificate_not_after_timestamp_seconds gauge +certmanager_certificate_not_after_timestamp_seconds{issuer_group="test-issuer-group",issuer_kind="Issuer",issuer_name="test-issuer",name="testcrt",namespace="testns"} 100 +# HELP certmanager_certificate_not_before_timestamp_seconds The timestamp before which the certificate is invalid, expressed as a Unix Epoch Time. +# TYPE certmanager_certificate_not_before_timestamp_seconds gauge +certmanager_certificate_not_before_timestamp_seconds{issuer_group="test-issuer-group",issuer_kind="Issuer",issuer_name="test-issuer",name="testcrt",namespace="testns"} 200 # HELP certmanager_certificate_ready_status The ready status of the certificate. # TYPE certmanager_certificate_ready_status gauge certmanager_certificate_ready_status{condition="False",issuer_group="test-issuer-group",issuer_kind="Issuer",issuer_name="test-issuer",name="testcrt",namespace="testns"} 0 certmanager_certificate_ready_status{condition="True",issuer_group="test-issuer-group",issuer_kind="Issuer",issuer_name="test-issuer",name="testcrt",namespace="testns"} 1 certmanager_certificate_ready_status{condition="Unknown",issuer_group="test-issuer-group",issuer_kind="Issuer",issuer_name="test-issuer",name="testcrt",namespace="testns"} 0 -# HELP certmanager_certificate_renewal_timestamp_seconds The number of seconds before expiration time the certificate should renew. +# HELP certmanager_certificate_renewal_timestamp_seconds The timestamp after which the certificate should be renewed, expressed in Unix Epoch Time. # TYPE certmanager_certificate_renewal_timestamp_seconds gauge certmanager_certificate_renewal_timestamp_seconds{issuer_group="test-issuer-group",issuer_kind="Issuer",issuer_name="test-issuer",name="testcrt",namespace="testns"} 100 ` + clockCounterMetric + clockGaugeMetric + ` -# HELP certmanager_controller_sync_call_count The number of sync() calls made by a controller. +# HELP certmanager_controller_sync_call_count The number of sync() calls made by a controller. Label: controller (fixed small set of controller names). # TYPE certmanager_controller_sync_call_count counter certmanager_controller_sync_call_count{controller="metrics_test"} 2 `) + err = cmClient.CertmanagerV1().Certificates(namespace).Delete(t.Context(), crt.Name, metav1.DeleteOptions{}) + if err != nil { + t.Fatal(err) + } - err = cmClient.CertmanagerV1().Certificates(namespace).Delete(ctx, crt.Name, metav1.DeleteOptions{}) + err = cmClient.AcmeV1().Challenges(namespace).Delete(t.Context(), challenge.Name, metav1.DeleteOptions{}) if err != nil { t.Fatal(err) } // Should expose no Certificates and only metrics sync count increase waitForMetrics(clockCounterMetric + clockGaugeMetric + ` -# HELP certmanager_controller_sync_call_count The number of sync() calls made by a controller. +# HELP certmanager_controller_sync_call_count The number of sync() calls made by a controller. Label: controller (fixed small set of controller names). # TYPE certmanager_controller_sync_call_count counter certmanager_controller_sync_call_count{controller="metrics_test"} 3 `) diff --git a/test/integration/certificates/revisionmanager_controller_test.go b/test/integration/certificates/revisionmanager_controller_test.go index 153a4804e8e..bcb1adf8104 100644 --- a/test/integration/certificates/revisionmanager_controller_test.go +++ b/test/integration/certificates/revisionmanager_controller_test.go @@ -18,7 +18,6 @@ package certificates import ( "context" - "encoding/pem" "strconv" "testing" "time" @@ -28,6 +27,7 @@ import ( "k8s.io/apimachinery/pkg/util/wait" "k8s.io/utils/clock" + "github.com/cert-manager/cert-manager/integration-tests/framework" apiutil "github.com/cert-manager/cert-manager/pkg/api/util" cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" @@ -35,28 +35,31 @@ import ( "github.com/cert-manager/cert-manager/pkg/controller/certificates/revisionmanager" logf "github.com/cert-manager/cert-manager/pkg/logs" "github.com/cert-manager/cert-manager/pkg/metrics" - utilpki "github.com/cert-manager/cert-manager/pkg/util/pki" - "github.com/cert-manager/cert-manager/test/integration/framework" "github.com/cert-manager/cert-manager/test/unit/gen" ) // TestRevisionManagerController will ensure that the revision manager -// controller will delete old CertificateRequests occording to the +// controller will delete old CertificateRequests according to the // spec.revisionHistoryLimit value func TestRevisionManagerController(t *testing.T) { - ctx, cancel := context.WithTimeout(context.Background(), time.Second*40) - defer cancel() - - config, stopFn := framework.RunControlPlane(t, ctx) - defer stopFn() + config, stopFn := framework.RunControlPlane(t) + t.Cleanup(stopFn) // Build, instantiate and run the revision manager controller. - kubeClient, factory, cmCl, cmFactory := framework.NewClients(t, config) + kubeClient, factory, cmCl, cmFactory, scheme := framework.NewClients(t, config) - ctrl, queue, mustSync := revisionmanager.NewController(logf.Log, cmCl, cmFactory) + controllerContext := controllerpkg.Context{ + Scheme: scheme, + CMClient: cmCl, + SharedInformerFactory: cmFactory, + } + + ctrl, queue, mustSync, err := revisionmanager.NewController(logf.Log, &controllerContext) + if err != nil { + t.Fatal(err) + } c := controllerpkg.NewController( - ctx, "revisionmanager_controller_test", metrics.New(logf.Log, clock.RealClock{}), ctrl.ProcessItem, @@ -75,8 +78,7 @@ func TestRevisionManagerController(t *testing.T) { // Create Namespace ns := &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: namespace}} - _, err := kubeClient.CoreV1().Namespaces().Create(ctx, ns, metav1.CreateOptions{}) - if err != nil { + if _, err := kubeClient.CoreV1().Namespaces().Create(t.Context(), ns, metav1.CreateOptions{}); err != nil { t.Fatal(err) } @@ -86,10 +88,10 @@ func TestRevisionManagerController(t *testing.T) { gen.SetCertificateCommonName("my-common-name"), gen.SetCertificateSecretName(secretName), gen.SetCertificateRevisionHistoryLimit(3), - gen.SetCertificateIssuer(cmmeta.ObjectReference{Name: "testissuer", Group: "foo.io", Kind: "Issuer"}), + gen.SetCertificateIssuer(cmmeta.IssuerReference{Name: "testissuer", Group: "foo.io", Kind: "Issuer"}), ) - crt, err = cmCl.CertmanagerV1().Certificates(namespace).Create(ctx, crt, metav1.CreateOptions{}) + crt, err = cmCl.CertmanagerV1().Certificates(namespace).Create(t.Context(), crt, metav1.CreateOptions{}) if err != nil { t.Fatal(err) } @@ -97,35 +99,19 @@ func TestRevisionManagerController(t *testing.T) { // Set Certificate to Ready apiutil.SetCertificateCondition(crt, crt.Generation, cmapi.CertificateConditionReady, cmmeta.ConditionTrue, "Issued", "integration test") - crt, err = cmCl.CertmanagerV1().Certificates(namespace).UpdateStatus(ctx, crt, metav1.UpdateOptions{}) - if err != nil { - t.Fatal(err) - } - - // Create a new private key - sk, err := utilpki.GenerateRSAPrivateKey(2048) + crt, err = cmCl.CertmanagerV1().Certificates(namespace).UpdateStatus(t.Context(), crt, metav1.UpdateOptions{}) if err != nil { t.Fatal(err) } - csr, err := utilpki.GenerateCSR(crt) + csrPEM, _, err := gen.CSRForCertificate(crt) if err != nil { t.Fatal(err) } - // Encode CSR - csrDER, err := utilpki.EncodeCSR(csr, sk) - if err != nil { - t.Fatal(err) - } - - csrPEM := pem.EncodeToMemory(&pem.Block{ - Type: "CERTIFICATE REQUEST", Bytes: csrDER, - }) - // Create 6 CertificateRequests which are owned by this Certificate - for i := 0; i < 6; i++ { - _, err = cmCl.CertmanagerV1().CertificateRequests(namespace).Create(ctx, &cmapi.CertificateRequest{ + for i := range 6 { + _, err = cmCl.CertmanagerV1().CertificateRequests(namespace).Create(t.Context(), &cmapi.CertificateRequest{ ObjectMeta: metav1.ObjectMeta{ GenerateName: crtName + "-", Namespace: namespace, @@ -138,7 +124,7 @@ func TestRevisionManagerController(t *testing.T) { }, Spec: cmapi.CertificateRequestSpec{ Request: csrPEM, - IssuerRef: cmmeta.ObjectReference{Name: "testissuer", Group: "foo.io", Kind: "Issuer"}, + IssuerRef: cmmeta.IssuerReference{Name: "testissuer", Group: "foo.io", Kind: "Issuer"}, }, }, metav1.CreateOptions{}) if err != nil { @@ -149,7 +135,7 @@ func TestRevisionManagerController(t *testing.T) { var crs []cmapi.CertificateRequest // Wait for 3 CertificateRequests to be deleted, and that they have the correct revisions - err = wait.PollImmediateUntil(time.Millisecond*100, func() (done bool, err error) { + err = wait.PollUntilContextCancel(t.Context(), time.Millisecond*100, true, func(ctx context.Context) (done bool, err error) { requests, err := cmCl.CertmanagerV1().CertificateRequests(namespace).List(ctx, metav1.ListOptions{}) if err != nil { return false, err @@ -163,7 +149,7 @@ func TestRevisionManagerController(t *testing.T) { crs = requests.Items return true, nil - }, ctx.Done()) + }) if err != nil { t.Fatal(err) } diff --git a/test/integration/certificates/trigger_controller_test.go b/test/integration/certificates/trigger_controller_test.go index 1cf07088e4a..1f2b3b0dc54 100644 --- a/test/integration/certificates/trigger_controller_test.go +++ b/test/integration/certificates/trigger_controller_test.go @@ -29,8 +29,9 @@ import ( "k8s.io/apimachinery/pkg/util/wait" "k8s.io/utils/clock" fakeclock "k8s.io/utils/clock/testing" - "k8s.io/utils/pointer" + "k8s.io/utils/ptr" + "github.com/cert-manager/cert-manager/integration-tests/framework" "github.com/cert-manager/cert-manager/internal/controller/certificates/policies" apiutil "github.com/cert-manager/cert-manager/pkg/api/util" cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" @@ -41,7 +42,6 @@ import ( logf "github.com/cert-manager/cert-manager/pkg/logs" "github.com/cert-manager/cert-manager/pkg/metrics" "github.com/cert-manager/cert-manager/pkg/util/pki" - "github.com/cert-manager/cert-manager/test/integration/framework" ) // TestTriggerController performs a basic test to ensure that the trigger @@ -50,30 +50,38 @@ import ( // issuance is triggered when a new Certificate resource is created and // no Secret exists. func TestTriggerController(t *testing.T) { - ctx, cancel := context.WithTimeout(context.Background(), time.Second*40) - defer cancel() - - config, stopFn := framework.RunControlPlane(t, ctx) - defer stopFn() + config, stopFn := framework.RunControlPlane(t) + t.Cleanup(stopFn) fakeClock := &fakeclock.FakeClock{} // Build, instantiate and run the trigger controller. - kubeClient, factory, cmCl, cmFactory := framework.NewClients(t, config) + kubeClient, factory, cmCl, cmFactory, scheme := framework.NewClients(t, config) - namespace := "testns" + namespace := "testns-trigger" // Create Namespace ns := &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: namespace}} - _, err := kubeClient.CoreV1().Namespaces().Create(ctx, ns, metav1.CreateOptions{}) + _, err := kubeClient.CoreV1().Namespaces().Create(t.Context(), ns, metav1.CreateOptions{}) if err != nil { t.Fatal(err) } shouldReissue := policies.NewTriggerPolicyChain(fakeClock).Evaluate - ctrl, queue, mustSync := trigger.NewController(logf.Log, cmCl, factory, - cmFactory, framework.NewEventRecorder(t), fakeClock, shouldReissue, - "cert-manage-certificates-trigger-test") + controllerContext := &controllerpkg.Context{ + Scheme: scheme, + Client: kubeClient, + KubeSharedInformerFactory: factory, + CMClient: cmCl, + SharedInformerFactory: cmFactory, + Clock: fakeClock, + ContextOptions: controllerpkg.ContextOptions{}, + Recorder: framework.NewEventRecorder(t, scheme), + FieldManager: "cert-manager-certificates-trigger-test", + } + ctrl, queue, mustSync, err := trigger.NewController(logf.Log, controllerContext, shouldReissue) + if err != nil { + t.Fatal(err) + } c := controllerpkg.NewController( - ctx, "trigger_test", metrics.New(logf.Log, clock.RealClock{}), ctrl.ProcessItem, @@ -85,37 +93,34 @@ func TestTriggerController(t *testing.T) { defer stopController() // Create a Certificate resource and wait for it to have the 'Issuing' condition. - cert, err := cmCl.CertmanagerV1().Certificates(namespace).Create(ctx, &cmapi.Certificate{ - ObjectMeta: metav1.ObjectMeta{Name: "testcrt", Namespace: "testns"}, + cert, err := cmCl.CertmanagerV1().Certificates(namespace).Create(t.Context(), &cmapi.Certificate{ + ObjectMeta: metav1.ObjectMeta{Name: "testcrt", Namespace: namespace}, Spec: cmapi.CertificateSpec{ SecretName: "example", CommonName: "example.com", - IssuerRef: cmmeta.ObjectReference{Name: "testissuer"}, // doesn't need to exist + IssuerRef: cmmeta.IssuerReference{Name: "testissuer"}, // doesn't need to exist }, }, metav1.CreateOptions{}) if err != nil { t.Fatal(err) } - ensureCertificateHasIssuingCondition(t, ctx, cmCl, namespace, cert.Name) + ensureCertificateHasIssuingCondition(t, t.Context(), cmCl, namespace, cert.Name) } func TestTriggerController_RenewNearExpiry(t *testing.T) { - ctx, cancel := context.WithTimeout(context.Background(), time.Second*40) - defer cancel() - - config, stopFn := framework.RunControlPlane(t, ctx) - defer stopFn() + config, stopFn := framework.RunControlPlane(t) + t.Cleanup(stopFn) fakeClock := &fakeclock.FakeClock{} // Only use the 'current certificate nearing expiry' policy chain during the // test as we want to test the very specific cases of triggering/not // triggering depending on whether a renewal is required. - shoudReissue := policies.Chain{policies.CurrentCertificateNearingExpiry(fakeClock)}.Evaluate + shouldReissue := policies.Chain{policies.CurrentCertificateNearingExpiry(fakeClock)}.Evaluate // Build, instantiate and run the trigger controller. - kubeClient, factory, cmCl, cmFactory := framework.NewClients(t, config) + kubeClient, factory, cmCl, cmFactory, scheme := framework.NewClients(t, config) - namespace := "testns" + namespace := "testns-renew-near-expiry" secretName := "example" certName := "testcrt" @@ -126,7 +131,7 @@ func TestTriggerController_RenewNearExpiry(t *testing.T) { // Create namespace ns := &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: namespace}} - _, err := kubeClient.CoreV1().Namespaces().Create(ctx, ns, metav1.CreateOptions{}) + _, err := kubeClient.CoreV1().Namespaces().Create(t.Context(), ns, metav1.CreateOptions{}) if err != nil { t.Fatal(err) } @@ -138,7 +143,7 @@ func TestTriggerController_RenewNearExpiry(t *testing.T) { SecretName: secretName, CommonName: "example.com", RenewBefore: renewBefore, - IssuerRef: cmmeta.ObjectReference{Name: "testissuer"}, // doesn't need to exist + IssuerRef: cmmeta.IssuerReference{Name: "testissuer"}, // doesn't need to exist }, } @@ -151,7 +156,7 @@ func TestTriggerController_RenewNearExpiry(t *testing.T) { // Create an X.509 cert x509CertBytes := selfSignCertificateWithNotBeforeAfter(t, skBytes, cert, notBefore.Time, notAfter.Time) // Create a Secret with the X.509 cert - _, err = kubeClient.CoreV1().Secrets(namespace).Create(ctx, &corev1.Secret{ + _, err = kubeClient.CoreV1().Secrets(namespace).Create(t.Context(), &corev1.Secret{ ObjectMeta: metav1.ObjectMeta{ Name: secretName, Namespace: namespace, @@ -165,12 +170,23 @@ func TestTriggerController_RenewNearExpiry(t *testing.T) { t.Fatal(err) } + controllerContext := &controllerpkg.Context{ + Scheme: scheme, + Client: kubeClient, + KubeSharedInformerFactory: factory, + CMClient: cmCl, + SharedInformerFactory: cmFactory, + Clock: fakeClock, + ContextOptions: controllerpkg.ContextOptions{}, + Recorder: framework.NewEventRecorder(t, scheme), + FieldManager: "cert-manager-certificates-trigger-test", + } // Start the trigger controller - ctrl, queue, mustSync := trigger.NewController(logf.Log, cmCl, factory, - cmFactory, framework.NewEventRecorder(t), fakeClock, shoudReissue, - "cert-manage-certificates-trigger-test") + ctrl, queue, mustSync, err := trigger.NewController(logf.Log, controllerContext, shouldReissue) + if err != nil { + t.Fatal(err) + } c := controllerpkg.NewController( - logf.NewContext(ctx, logf.Log, "trigger_controller_RenewNearExpiry"), "trigger_test", metrics.New(logf.Log, clock.RealClock{}), ctrl.ProcessItem, @@ -182,7 +198,7 @@ func TestTriggerController_RenewNearExpiry(t *testing.T) { defer stopController() // Create a Certificate - cert, err = cmCl.CertmanagerV1().Certificates(namespace).Create(ctx, cert, metav1.CreateOptions{}) + cert, err = cmCl.CertmanagerV1().Certificates(namespace).Create(t.Context(), cert, metav1.CreateOptions{}) if err != nil { t.Fatal(err) } @@ -192,7 +208,7 @@ func TestTriggerController_RenewNearExpiry(t *testing.T) { // Wait for 2s, polling every 200ms to ensure that the controller does not set // the condition. t.Log("Ensuring Certificate does not have Issuing condition for 2s...") - ensureCertificateDoesNotHaveIssuingCondition(t, ctx, cmCl, namespace, certName) + ensureCertificateDoesNotHaveIssuingCondition(t, t.Context(), cmCl, namespace, certName) // 2. Test that a Certificate does get the Issuing status condition set to // True when the X.509 cert is nearing expiry. @@ -204,17 +220,15 @@ func TestTriggerController_RenewNearExpiry(t *testing.T) { fakeClock.SetTime(renewalTime.Add(time.Millisecond * 2)) // apply a random condition to cert to ensure the reconciler gets triggered - applyTestCondition(t, ctx, cert, cmCl) - ensureCertificateHasIssuingCondition(t, ctx, cmCl, namespace, certName) + applyTestCondition(t, t.Context(), cert, cmCl) + ensureCertificateHasIssuingCondition(t, t.Context(), cmCl, namespace, certName) } func TestTriggerController_ExpBackoff(t *testing.T) { t.Log("Testing that trigger controller applies exponential backoff when retrying failed issuances...") - ctx, cancel := context.WithTimeout(context.Background(), time.Second*40) - defer cancel() - config, stopFn := framework.RunControlPlane(t, ctx) - defer stopFn() + config, stopFn := framework.RunControlPlane(t) + t.Cleanup(stopFn) now := time.Now() metaNow := metav1.NewTime(now) @@ -223,11 +237,11 @@ func TestTriggerController_ExpBackoff(t *testing.T) { // Issuing condition will be applied because SecretDoesNotExist policy // will evaluate to true. However, this is not what we are testing in // this test. - shoudReissue := policies.NewTriggerPolicyChain(fakeClock).Evaluate + shouldReissue := policies.NewTriggerPolicyChain(fakeClock).Evaluate // Build, instantiate and run the trigger controller. - kubeClient, factory, cmCl, cmFactory := framework.NewClients(t, config) + kubeClient, factory, cmCl, cmFactory, scheme := framework.NewClients(t, config) - namespace := "testns" + namespace := "testns-expbackoff" secretName := "example" certName := "testcrt" @@ -236,7 +250,7 @@ func TestTriggerController_ExpBackoff(t *testing.T) { // Create namespace ns := &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: namespace}} - _, err := kubeClient.CoreV1().Namespaces().Create(ctx, ns, metav1.CreateOptions{}) + _, err := kubeClient.CoreV1().Namespaces().Create(t.Context(), ns, metav1.CreateOptions{}) if err != nil { t.Fatal(err) } @@ -247,14 +261,28 @@ func TestTriggerController_ExpBackoff(t *testing.T) { Spec: cmapi.CertificateSpec{ SecretName: secretName, CommonName: "example.com", - IssuerRef: cmmeta.ObjectReference{Name: "testissuer"}, // doesn't need to exist + IssuerRef: cmmeta.IssuerReference{Name: "testissuer"}, // doesn't need to exist }, } + controllerContext := &controllerpkg.Context{ + Scheme: scheme, + Client: kubeClient, + KubeSharedInformerFactory: factory, + CMClient: cmCl, + SharedInformerFactory: cmFactory, + Clock: fakeClock, + ContextOptions: controllerpkg.ContextOptions{}, + Recorder: framework.NewEventRecorder(t, scheme), + FieldManager: "cert-manager-certificates-trigger-test", + } + // Start the trigger controller - ctrl, queue, mustSync := trigger.NewController(logf.Log, cmCl, factory, cmFactory, framework.NewEventRecorder(t), fakeClock, shoudReissue, "cert-manger-certificates-trigger-test") + ctrl, queue, mustSync, err := trigger.NewController(logf.Log, controllerContext, shouldReissue) + if err != nil { + t.Fatal(err) + } c := controllerpkg.NewController( - logf.NewContext(ctx, logf.Log, "trigger_controller_RenewNearExpiry"), "trigger_test", metrics.New(logf.Log, clock.RealClock{}), ctrl.ProcessItem, @@ -266,25 +294,25 @@ func TestTriggerController_ExpBackoff(t *testing.T) { defer stopController() // Create a Certificate - _, err = cmCl.CertmanagerV1().Certificates(namespace).Create(ctx, cert, metav1.CreateOptions{}) + _, err = cmCl.CertmanagerV1().Certificates(namespace).Create(t.Context(), cert, metav1.CreateOptions{}) if err != nil { t.Fatal(err) } // 1. Test that Issuing condition gets set to True t.Log("Ensuring Certificate does get the Issuing condition set to true initially...") - ensureCertificateHasIssuingCondition(t, ctx, cmCl, namespace, certName) + ensureCertificateHasIssuingCondition(t, t.Context(), cmCl, namespace, certName) // Simulate issuance having failed t.Log("Simulate issuance having failed for 7th time in a row") - cert, err = cmCl.CertmanagerV1().Certificates(namespace).Get(ctx, certName, metav1.GetOptions{}) + cert, err = cmCl.CertmanagerV1().Certificates(namespace).Get(t.Context(), certName, metav1.GetOptions{}) if err != nil { t.Fatal(err) } apiutil.SetCertificateCondition(cert, 1, cmapi.CertificateConditionIssuing, cmmeta.ConditionFalse, "", "") cert.Status.FailedIssuanceAttempts = &failedIssuanceAttempts cert.Status.LastFailureTime = &metaNow - cert, err = cmCl.CertmanagerV1().Certificates(namespace).UpdateStatus(ctx, cert, metav1.UpdateOptions{}) + cert, err = cmCl.CertmanagerV1().Certificates(namespace).UpdateStatus(t.Context(), cert, metav1.UpdateOptions{}) if err != nil { t.Fatal(err) } @@ -294,27 +322,33 @@ func TestTriggerController_ExpBackoff(t *testing.T) { t.Log("Advance clock to slightly before the end of the backoff period") fakeClock.SetTime(now.Add(backoffPeriod - time.Minute)) // apply a random condition to cert to ensure the reconciler gets triggered - applyTestCondition(t, ctx, cert, cmCl) + applyTestCondition(t, t.Context(), cert, cmCl) t.Log("Ensuring Certificate does not have Issuing condition set to true for 2s...") - ensureCertificateDoesNotHaveIssuingCondition(t, ctx, cmCl, namespace, certName) + ensureCertificateDoesNotHaveIssuingCondition(t, t.Context(), cmCl, namespace, certName) // 3. Test that issuance gets retried once the backoff period is over t.Log("Advance clock to just after the backoff period") fakeClock.SetTime(now.Add(backoffPeriod + time.Second)) // apply a random condition to cert to ensure the reconciler gets triggered - applyTestCondition(t, ctx, cert, cmCl) + applyTestCondition(t, t.Context(), cert, cmCl) t.Log("Ensuring Certificate does get the Issuing condition set to true after the backoff period") - ensureCertificateHasIssuingCondition(t, ctx, cmCl, namespace, certName) + ensureCertificateHasIssuingCondition(t, t.Context(), cmCl, namespace, certName) } func ensureCertificateDoesNotHaveIssuingCondition(t *testing.T, ctx context.Context, cmCl cmclient.Interface, namespace, name string) { t.Helper() - timeoutCtx, cancel := context.WithTimeout(ctx, time.Second*2) - defer cancel() - err := wait.PollImmediateUntil(time.Millisecond*200, func() (done bool, err error) { + startTime := time.Now() + successful := false + err := wait.PollUntilContextCancel(ctx, time.Millisecond*200, true, func(ctx context.Context) (bool, error) { + // Check if certificate has not had condition for 2s + if time.Since(startTime) > time.Second*2 { + successful = true + return true, nil + } + c, err := cmCl.CertmanagerV1().Certificates(namespace).Get(ctx, name, metav1.GetOptions{}) if err != nil { return false, err @@ -327,15 +361,11 @@ func ensureCertificateDoesNotHaveIssuingCondition(t *testing.T, ctx context.Cont return true, nil } return false, nil - }, timeoutCtx.Done()) + }) switch { - case err == nil: + case err == nil && !successful: t.Fatal("expected Certificate to not have the Issuing condition") - case err == wait.ErrWaitTimeout: - if ctx.Err() != nil { - t.Error(ctx.Err()) - } - + case err == nil && successful: // this is the expected 'happy case' default: t.Fatal(err) @@ -344,7 +374,7 @@ func ensureCertificateDoesNotHaveIssuingCondition(t *testing.T, ctx context.Cont func ensureCertificateHasIssuingCondition(t *testing.T, ctx context.Context, cmCl cmclient.Interface, namespace, name string) { t.Helper() - err := wait.PollImmediateUntil(time.Millisecond*200, func() (done bool, err error) { + err := wait.PollUntilContextCancel(ctx, time.Millisecond*200, true, func(ctx context.Context) (done bool, err error) { c, err := cmCl.CertmanagerV1().Certificates(namespace).Get(ctx, name, metav1.GetOptions{}) if err != nil { return false, err @@ -356,7 +386,7 @@ func ensureCertificateHasIssuingCondition(t *testing.T, ctx context.Context, cmC return true, nil } return false, nil - }, ctx.Done()) + }) if err != nil { t.Error("Failed waiting for Certificate to have Issuing condition") } @@ -369,7 +399,7 @@ func selfSignCertificateWithNotBeforeAfter(t *testing.T, pkData []byte, spec *cm t.Fatal(err) } - template, err := pki.GenerateTemplate(spec) + template, err := pki.CertificateTemplateFromCertificate(spec) if err != nil { t.Fatal(err) } @@ -411,7 +441,7 @@ func applyTestCondition(t *testing.T, ctx context.Context, cert *cmapi.Certifica t.Errorf("failed to marshal cert data: %v", err) } _, err = client.CertmanagerV1().Certificates(cert.Namespace).Patch( - ctx, cert.Name, types.ApplyPatchType, statusUpdateJson, metav1.PatchOptions{FieldManager: "test", Force: pointer.Bool(true)}, + ctx, cert.Name, types.ApplyPatchType, statusUpdateJson, metav1.PatchOptions{FieldManager: "test", Force: ptr.To(true)}, "status", ) if err != nil { diff --git a/test/integration/challenges/apply_test.go b/test/integration/challenges/apply_test.go index eae2dad824e..65e4114c809 100644 --- a/test/integration/challenges/apply_test.go +++ b/test/integration/challenges/apply_test.go @@ -17,18 +17,16 @@ limitations under the License. package challenges import ( - "context" "testing" - "time" "github.com/stretchr/testify/assert" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "github.com/cert-manager/cert-manager/integration-tests/framework" internalchallenges "github.com/cert-manager/cert-manager/internal/controller/challenges" cmacme "github.com/cert-manager/cert-manager/pkg/apis/acme/v1" cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" - "github.com/cert-manager/cert-manager/test/integration/framework" ) // Test_Apply ensures that the Challenge Apply helpers can set both the @@ -39,17 +37,14 @@ func Test_Apply(t *testing.T) { name = "test-apply" ) - ctx, cancel := context.WithTimeout(context.Background(), time.Second*40) - defer cancel() + restConfig, stopFn := framework.RunControlPlane(t) + t.Cleanup(stopFn) - restConfig, stopFn := framework.RunControlPlane(t, ctx) - defer stopFn() - - kubeClient, _, cmClient, _ := framework.NewClients(t, restConfig) + kubeClient, _, cmClient, _, _ := framework.NewClients(t, restConfig) t.Log("creating test Namespace") ns := &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: namespace}} - _, err := kubeClient.CoreV1().Namespaces().Create(ctx, ns, metav1.CreateOptions{}) + _, err := kubeClient.CoreV1().Namespaces().Create(t.Context(), ns, metav1.CreateOptions{}) assert.NoError(t, err) ch := &cmacme.Challenge{ @@ -59,7 +54,7 @@ func Test_Apply(t *testing.T) { DNSName: "example.com", Wildcard: true, Type: cmacme.ACMEChallengeTypeDNS01, Token: "1234", Key: "5678", Solver: cmacme.ACMEChallengeSolver{}, - IssuerRef: cmmeta.ObjectReference{ + IssuerRef: cmmeta.IssuerReference{ Name: "issuer", Kind: "Issuer", Group: "cert-manager.io", @@ -68,11 +63,11 @@ func Test_Apply(t *testing.T) { } t.Log("creating Challenge") - _, err = cmClient.AcmeV1().Challenges(namespace).Create(ctx, ch, metav1.CreateOptions{FieldManager: "cert-manager-test"}) + _, err = cmClient.AcmeV1().Challenges(namespace).Create(t.Context(), ch, metav1.CreateOptions{FieldManager: "cert-manager-test"}) assert.NoError(t, err) t.Log("ensuring apply will can set annotations and labels") - _, err = internalchallenges.Apply(ctx, cmClient, "cert-manager-test", &cmacme.Challenge{ + _, err = internalchallenges.Apply(t.Context(), cmClient, "cert-manager-test", &cmacme.Challenge{ ObjectMeta: metav1.ObjectMeta{ Namespace: namespace, Name: name, Annotations: map[string]string{"test-1": "abc", "test-2": "def"}, @@ -81,13 +76,13 @@ func Test_Apply(t *testing.T) { Spec: ch.Spec, }) assert.NoError(t, err) - ch, err = cmClient.AcmeV1().Challenges(namespace).Get(ctx, name, metav1.GetOptions{}) + ch, err = cmClient.AcmeV1().Challenges(namespace).Get(t.Context(), name, metav1.GetOptions{}) assert.NoError(t, err) assert.Equal(t, map[string]string{"test-1": "abc", "test-2": "def"}, ch.Annotations, "annotations") assert.Equal(t, map[string]string{"123": "456", "789": "abc"}, ch.Labels, "labels") t.Log("ensuring apply can change status") - _, err = internalchallenges.ApplyStatus(ctx, cmClient, "cert-manager-test", &cmacme.Challenge{ + _, err = internalchallenges.ApplyStatus(t.Context(), cmClient, "cert-manager-test", &cmacme.Challenge{ ObjectMeta: metav1.ObjectMeta{Namespace: namespace, Name: name}, Status: cmacme.ChallengeStatus{ Processing: true, @@ -97,7 +92,7 @@ func Test_Apply(t *testing.T) { }, }) assert.NoError(t, err) - ch, err = cmClient.AcmeV1().Challenges(namespace).Get(ctx, name, metav1.GetOptions{}) + ch, err = cmClient.AcmeV1().Challenges(namespace).Get(t.Context(), name, metav1.GetOptions{}) assert.NoError(t, err) assert.Equal(t, cmacme.ChallengeStatus{ Processing: true, diff --git a/test/integration/conversion/conversion_test.go b/test/integration/conversion/conversion_test.go deleted file mode 100644 index b9996a8032c..00000000000 --- a/test/integration/conversion/conversion_test.go +++ /dev/null @@ -1,129 +0,0 @@ -/* -Copyright 2020 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. -*/ - -package conversion - -import ( - "context" - "testing" - "time" - - logtesting "github.com/go-logr/logr/testing" - "k8s.io/apimachinery/pkg/api/equality" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/utils/diff" - "k8s.io/utils/pointer" - "sigs.k8s.io/controller-runtime/pkg/client" - - "github.com/cert-manager/cert-manager/pkg/webhook/handlers" - testapi "github.com/cert-manager/cert-manager/pkg/webhook/handlers/testdata/apis/testgroup/install" - testv1 "github.com/cert-manager/cert-manager/pkg/webhook/handlers/testdata/apis/testgroup/v1" - testv2 "github.com/cert-manager/cert-manager/pkg/webhook/handlers/testdata/apis/testgroup/v2" - "github.com/cert-manager/cert-manager/test/integration/framework" -) - -func TestConversion(t *testing.T) { - tests := map[string]struct { - input client.Object - targetGVK schema.GroupVersionKind - output client.Object - }{ - "should convert from v1 to v2": { - input: &testv1.TestType{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test", - Namespace: "default", - }, - TestFieldPtr: pointer.StringPtr("test1"), - }, - targetGVK: testv2.SchemeGroupVersion.WithKind("TestType"), - output: &testv2.TestType{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test", - Namespace: "default", - }, - TestFieldPtrAlt: pointer.StringPtr("test1"), - }, - }, - "should convert from v2 to v1": { - input: &testv2.TestType{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test", - Namespace: "default", - }, - TestFieldPtrAlt: pointer.StringPtr("test1"), - }, - targetGVK: testv1.SchemeGroupVersion.WithKind("TestType"), - output: &testv1.TestType{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test", - Namespace: "default", - }, - TestFieldPtr: pointer.StringPtr("test1"), - }, - }, - } - for name, test := range tests { - t.Run(name, func(t *testing.T) { - log := logtesting.NewTestLogger(t) - - scheme := runtime.NewScheme() - testapi.Install(scheme) - - ctx, cancel := context.WithTimeout(context.Background(), time.Second*40) - defer cancel() - - config, stop := framework.RunControlPlane( - t, ctx, - framework.WithCRDDirectory("../../../pkg/webhook/handlers/testdata/apis/testgroup/crds"), - framework.WithWebhookConversionHandler(handlers.NewSchemeBackedConverter(log, scheme)), - ) - defer stop() - cl, err := client.New(config, client.Options{Scheme: scheme}) - if err != nil { - t.Fatal(err) - } - - if err := cl.Create(ctx, test.input); err != nil { - t.Fatal(err) - } - meta := test.input.(metav1.ObjectMetaAccessor) - - convertedObj, err := scheme.New(test.targetGVK) - if err != nil { - t.Fatal(err) - } - - if err := cl.Get(ctx, client.ObjectKey{Name: meta.GetObjectMeta().GetName(), Namespace: meta.GetObjectMeta().GetNamespace()}, convertedObj.(client.Object)); err != nil { - t.Fatalf("failed to fetch object in expected API version: %v", err) - } - - convertedObjMeta := convertedObj.(metav1.ObjectMetaAccessor) - convertedObjMeta.GetObjectMeta().SetCreationTimestamp(metav1.Time{}) - convertedObjMeta.GetObjectMeta().SetGeneration(0) - convertedObjMeta.GetObjectMeta().SetUID("") - convertedObjMeta.GetObjectMeta().SetSelfLink("") - convertedObjMeta.GetObjectMeta().SetResourceVersion("") - convertedObjMeta.GetObjectMeta().SetManagedFields([]metav1.ManagedFieldsEntry{}) - - if !equality.Semantic.DeepEqual(test.output, convertedObj) { - t.Errorf("unexpected output: %s", diff.ObjectReflectDiff(test.output, convertedObj)) - } - }) - } -} diff --git a/test/integration/ctl/ctl_convert_test.go b/test/integration/ctl/ctl_convert_test.go deleted file mode 100644 index 82fcd217bc6..00000000000 --- a/test/integration/ctl/ctl_convert_test.go +++ /dev/null @@ -1,175 +0,0 @@ -/* -Copyright 2020 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. -*/ - -package ctl - -import ( - "bytes" - "os" - "testing" - - "k8s.io/cli-runtime/pkg/genericclioptions" - - "github.com/cert-manager/cert-manager/cmd/ctl/pkg/convert" -) - -const ( - testdataResource1 = "./testdata/convert/input/resource1.yaml" - testdataResource2 = "./testdata/convert/input/resource2.yaml" - testdataResource3 = "./testdata/convert/input/resource3.yaml" - testdataResourceWithOrganizationV1alpha2 = "./testdata/convert/input/resource_with_organization_v1alpha2.yaml" - testdataResourcesAsListV1alpha2 = "./testdata/convert/input/resources_as_list_v1alpha2.yaml" - - testdataNoOutputError = "./testdata/convert/output/no_output_error.yaml" - testdataResource1V1 = "./testdata/convert/output/resource1_v1.yaml" - testdataResource1V1alpha2 = "./testdata/convert/output/resource1_v1alpha2.yaml" - testdataResource1V1alpha3 = "./testdata/convert/output/resource1_v1alpha3.yaml" - testdataResource2V1 = "./testdata/convert/output/resource2_v1.yaml" - testdataResource2V1alpha2 = "./testdata/convert/output/resource2_v1alpha2.yaml" - testdataResource2V1alpha3 = "./testdata/convert/output/resource2_v1alpha3.yaml" - testdataResourceWithOrganizationV1alpha3 = "./testdata/convert/output/resource_with_organization_v1alpha3.yaml" - testdataResourceWithOrganizationV1beta1 = "./testdata/convert/output/resource_with_organization_v1beta1.yaml" - testdataResourceWithOrganizationV1 = "./testdata/convert/output/resource_with_organization_v1.yaml" - testdataResourcesOutAsListV1alpha2 = "./testdata/convert/output/resources_as_list_v1alpha2.yaml" - testdataResourcesOutAsListV1alpha3 = "./testdata/convert/output/resources_as_list_v1alpha3.yaml" - testdataResourcesOutAsListV1beta1 = "./testdata/convert/output/resources_as_list_v1beta1.yaml" - testdataResourcesOutAsListV1 = "./testdata/convert/output/resources_as_list_v1.yaml" - - targetv1alpha2 = "cert-manager.io/v1alpha2" - targetv1alpha3 = "cert-manager.io/v1alpha3" - targetv1beta1 = "cert-manager.io/v1beta1" - targetv1 = "cert-manager.io/v1" -) - -func TestCtlConvert(t *testing.T) { - tests := map[string]struct { - input, expOutputFile string - targetVersion string - expErr bool - }{ - "a single cert-manager resource should convert to v1 with no target": { - input: testdataResource1, - expOutputFile: testdataResource1V1, - }, - "a single cert-manager resource should convert to v1alpha2 with target v1alpha2": { - input: testdataResource1, - targetVersion: targetv1alpha2, - expOutputFile: testdataResource1V1alpha2, - }, - "a single cert-manager resource should convert to v1alpha3 with target v1alpha3": { - input: testdataResource1, - targetVersion: targetv1alpha3, - expOutputFile: testdataResource1V1alpha3, - }, - "a list of cert-manager resources should convert to v1 with no target": { - input: testdataResource2, - expOutputFile: testdataResource2V1, - }, - "a list of cert-manager resources should convert to v1alpha2 with target v1alpha2": { - input: testdataResource2, - targetVersion: targetv1alpha2, - expOutputFile: testdataResource2V1alpha2, - }, - "a list of cert-manager resources should convert to v1alpha3 with target v1alpha3": { - input: testdataResource2, - targetVersion: targetv1alpha3, - expOutputFile: testdataResource2V1alpha3, - }, - "a list of a mix of cert-manager and non cert-manager resources should error with no target": { - input: testdataResource3, - expOutputFile: testdataNoOutputError, - expErr: true, - }, - "a list of a mix of cert-manager and non cert-manager resources should error with target v1alpha2": { - input: testdataResource3, - targetVersion: targetv1alpha2, - expOutputFile: testdataNoOutputError, - expErr: true, - }, - "a list of a mix of cert-manager and non cert-manager resources should error with target v1alpha3": { - input: testdataResource3, - targetVersion: targetv1alpha3, - expOutputFile: testdataNoOutputError, - expErr: true, - }, - "an object in v1alpha2 that uses a field that has been renamed in v1alpha3 should be converted properly": { - input: testdataResourceWithOrganizationV1alpha2, - targetVersion: targetv1alpha3, - expOutputFile: testdataResourceWithOrganizationV1alpha3, - }, - "an object in v1alpha2 that uses a field that has been renamed in v1beta1 should be converted properly": { - input: testdataResourceWithOrganizationV1alpha2, - targetVersion: targetv1beta1, - expOutputFile: testdataResourceWithOrganizationV1beta1, - }, - "an object in v1alpha2 that uses a field that has been renamed in v1 should be converted properly": { - input: testdataResourceWithOrganizationV1alpha2, - targetVersion: targetv1, - expOutputFile: testdataResourceWithOrganizationV1, - }, - "a list in v1alpha2 should parsed": { - input: testdataResourcesAsListV1alpha2, - targetVersion: targetv1alpha2, - expOutputFile: testdataResourcesOutAsListV1alpha2, - }, - "a list in v1alpha2 should be converted to v1alpha3": { - input: testdataResourcesAsListV1alpha2, - targetVersion: targetv1alpha3, - expOutputFile: testdataResourcesOutAsListV1alpha3, - }, - "a list in v1alpha2 should be converted to v1beta1": { - input: testdataResourcesAsListV1alpha2, - targetVersion: targetv1beta1, - expOutputFile: testdataResourcesOutAsListV1beta1, - }, - "a list in v1alpha2 should be converted to v1": { - input: testdataResourcesAsListV1alpha2, - targetVersion: targetv1, - expOutputFile: testdataResourcesOutAsListV1, - }, - } - - for name, test := range tests { - t.Run(name, func(t *testing.T) { - expOutput, err := os.ReadFile(test.expOutputFile) - if err != nil { - t.Fatalf("%s: %s", test.expOutputFile, err) - } - - // Run ctl convert command with input options - streams, _, outBuf, _ := genericclioptions.NewTestIOStreams() - - opts := convert.NewOptions(streams) - opts.OutputVersion = test.targetVersion - opts.Filenames = []string{test.input} - - if err := opts.Complete(); err != nil { - t.Fatal(err) - } - - err = opts.Run() - if test.expErr != (err != nil) { - t.Errorf("got unexpected error, exp=%t got=%v", - test.expErr, err) - } - - if !bytes.Equal(bytes.TrimSpace(expOutput), bytes.TrimSpace(outBuf.Bytes())) { - t.Errorf("got unexpected output, exp=%s\n got=%s", - bytes.TrimSpace(expOutput), bytes.TrimSpace(outBuf.Bytes())) - } - }) - } -} diff --git a/test/integration/ctl/ctl_create_cr_test.go b/test/integration/ctl/ctl_create_cr_test.go deleted file mode 100644 index b3e224f744a..00000000000 --- a/test/integration/ctl/ctl_create_cr_test.go +++ /dev/null @@ -1,428 +0,0 @@ -/* -Copyright 2020 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. -*/ - -package ctl - -import ( - "bytes" - "context" - "fmt" - "os" - "path" - "testing" - "time" - - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/util/wait" - "k8s.io/cli-runtime/pkg/genericclioptions" - - "github.com/cert-manager/cert-manager/cmd/ctl/pkg/create/certificaterequest" - "github.com/cert-manager/cert-manager/cmd/ctl/pkg/factory" - cmapiv1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" - cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" - "github.com/cert-manager/cert-manager/pkg/util/pki" - "github.com/cert-manager/cert-manager/test/integration/framework" -) - -type CreateCRTest struct { - inputFile string - inputArgs []string - inputNamespace string - keyFilename string - certFilename string - fetchCert bool - timeout time.Duration - crStatus cmapiv1.CertificateRequestStatus - - expRunErr bool - expErrMsg string - expNamespace string - expName string - expKeyFilename string - expCertFilename string - expCertFileContent []byte -} - -// TestCtlCreateCRBeforeCRIsCreated tests the behaviour in the case where the command fails -// after the private key has been written to file and before the CR is successfully created. -// Achieved by trying to create two CRs with the same name, storing the private key to two different files. -func TestCtlCreateCRBeforeCRIsCreated(t *testing.T) { - ctx, cancel := context.WithTimeout(context.Background(), time.Second*40) - defer cancel() - - config, stopFn := framework.RunControlPlane(t, ctx) - defer stopFn() - - // Build clients - kubernetesCl, _, cmCl, _ := framework.NewClients(t, config) - - testdataPath := getTestDataDir(t) - - const ( - cr5Name = "testcr-5" - ns1 = "testns-1" - ) - - // Create Namespace - ns := &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: ns1}} - _, err := kubernetesCl.CoreV1().Namespaces().Create(ctx, ns, metav1.CreateOptions{}) - if err != nil { - t.Fatal(err) - } - - tests := map[string]CreateCRTest{ - "path to file to store private key provided": { - inputFile: path.Join(testdataPath, "create_cr_cert_with_ns1.yaml"), - inputArgs: []string{cr5Name}, - inputNamespace: ns1, - keyFilename: "test.key", - expRunErr: true, - expErrMsg: fmt.Sprintf("error creating CertificateRequest: certificaterequests.cert-manager.io %q already exists", cr5Name), - expKeyFilename: "test.key", - }, - } - - for name, test := range tests { - t.Run(name, func(t *testing.T) { - streams, _, _, _ := genericclioptions.NewTestIOStreams() - - cleanUpFunc := setupPathForTest(t) - defer cleanUpFunc() - - // Options to run create CR command - opts := &certificaterequest.Options{ - InputFilename: test.inputFile, - CertFileName: test.certFilename, - Factory: &factory.Factory{ - CMClient: cmCl, - RESTConfig: config, - Namespace: test.inputNamespace, - EnforceNamespace: test.inputNamespace != "", - }, - IOStreams: streams, - } - - err := opts.Run(ctx, test.inputArgs) - if err != nil { - t.Fatal("failed to set up test to fail after writing private key to file and during creating CR") - } - - // By now we have created a CR already - // Now we try to create another CR with the same name, but storing the private key somewhere else - // This should break after writing private key to file and during creating CR - opts.KeyFilename = test.keyFilename - // Validating args and flags - err = opts.Validate(test.inputArgs) - if err != nil { - t.Fatal(err) - } - - // Run ctl create cr command with input options - err = opts.Run(ctx, test.inputArgs) - if err != nil { - if !test.expRunErr { - t.Errorf("got unexpected error when trying to create CR: %v", err) - } else if err.Error() != test.expErrMsg { - t.Errorf("got unexpected error when trying to create CR, expected: %v; actual: %v", test.expErrMsg, err) - } - } else { - // got no error - if test.expRunErr { - t.Errorf("expected but got no error when creating CR") - } - } - - // Check the file where the private key is stored - keyData, err := os.ReadFile(test.expKeyFilename) - if err != nil { - t.Errorf("error when reading file storing private key: %v", err) - } - _, err = pki.DecodePrivateKeyBytes(keyData) - if err != nil { - t.Errorf("invalid private key: %v", err) - } - - }) - } -} - -// TestCtlCreateCRSuccessful tests the behaviour in the case where the command successfully -// creates the CR, including the --fetch-certificate logic. -func TestCtlCreateCRSuccessful(t *testing.T) { - rootCtx, cancelRoot := context.WithTimeout(context.Background(), time.Second*200) - defer cancelRoot() - - config, stopFn := framework.RunControlPlane(t, rootCtx) - defer stopFn() - - // Build clients - kubernetesCl, _, cmCl, _ := framework.NewClients(t, config) - - testdataPath := getTestDataDir(t) - - const ( - cr1Name = "testcr-1" - cr2Name = "testcr-2" - cr5Name = "testcr-5" - cr6Name = "testcr-6" - cr7Name = "testcr-7" - ns1 = "testns-1" - ) - exampleCertificate := []byte(`LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUZUekNDQkRlZ0F3SUJBZ0lUQVBwOWhMUit2ODF2UTdpZSt6emxTMWY5MFRBTkJna3Foa2lHOXcwQkFRc0YKQURBaU1TQXdIZ1lEVlFRRERCZEdZV3RsSUV4RklFbHVkR1Z5YldWa2FXRjBaU0JZTVRBZUZ3MHlNREEyTXpBeApNelUyTkRoYUZ3MHlNREE1TWpneE16VTJORGhhTUNZeEpEQWlCZ05WQkFNVEcyaGhiM2hwWVc1bkxXZGpjQzVxClpYUnpkR0ZqYTJWeUxtNWxkRENDQVNJd0RRWUpLb1pJaHZjTkFRRUJCUUFEZ2dFUEFEQ0NBUW9DZ2dFQkFOTjIKTS9zZGtPazgvenJLbXNvMEE1SmxoUjRTQU9pTVhiWGZleEpvUzZ3b3krakszNVBCOUFDUDFQcllXR0diZjNYRwo1VngvZmRBSlNmdVFmL0NoZlRsa0kwQUYxUCsxUThhQU9BUXhKdU4ySVJxT0ErNlEwUTg2Vy9oZVFXbUdOUkI4CmxMcHQvWU9IV3NreHRqRDNmN3p1QXZZUkI1czFCZ3o2K2s1REF6d1pGNnlMMEtja1JpY3dFMHh3aisrZkcyeCsKdEpQb1AwdmliM0EzU0xySFhsRW5HbFdEL3ZSbkkrNkc1dFI2ZHJWbGcrcjhSRkFiYTJDc1VpTGFiM252Q2JqUQpDNG9xZWd1NklUNzk4R0thenBXbGw2b3M0SndQdFJnQzlvYS9FeklVanlWeStuRWhHU3pwSmlNQ0NZOS96b0daCmV1TGJ0M1lSdVVIaStiemludnNDQXdFQUFhT0NBbmd3Z2dKME1BNEdBMVVkRHdFQi93UUVBd0lGb0RBZEJnTlYKSFNVRUZqQVVCZ2dyQmdFRkJRY0RBUVlJS3dZQkJRVUhBd0l3REFZRFZSMFRBUUgvQkFJd0FEQWRCZ05WSFE0RQpGZ1FVRGZKNml2NlNoRlhzLzFrUTh5bmR1NGhTUEtrd0h3WURWUjBqQkJnd0ZvQVV3TXdEUnJsWUlNeGNjbkR6CjRTN0xJS2IxYURvd2R3WUlLd1lCQlFVSEFRRUVhekJwTURJR0NDc0dBUVVGQnpBQmhpWm9kSFJ3T2k4dmIyTnoKY0M1emRHY3RhVzUwTFhneExteGxkSE5sYm1OeWVYQjBMbTl5WnpBekJnZ3JCZ0VGQlFjd0FvWW5hSFIwY0RvdgpMMk5sY25RdWMzUm5MV2x1ZEMxNE1TNXNaWFJ6Wlc1amNubHdkQzV2Y21jdk1DWUdBMVVkRVFRZk1CMkNHMmhoCmIzaHBZVzVuTFdkamNDNXFaWFJ6ZEdGamEyVnlMbTVsZERCTUJnTlZIU0FFUlRCRE1BZ0dCbWVCREFFQ0FUQTMKQmdzckJnRUVBWUxmRXdFQkFUQW9NQ1lHQ0NzR0FRVUZCd0lCRmhwb2RIUndPaTh2WTNCekxteGxkSE5sYm1OeQplWEIwTG05eVp6Q0NBUVFHQ2lzR0FRUUIxbmtDQkFJRWdmVUVnZklBOEFCMkFMRE1nK1dsK1gxcnIzd0p6Q2hKCkJJY3F4K2lMRXl4alVMZkcvU2JoYkd4M0FBQUJjd1c3QXB3QUFBUURBRWN3UlFJaEFPai9nNm9ONjNTRnBqa00Ka3FmcjRDUlVzb0dWamZqQzN4MkRFdmR0RVZzNEFpQm05OTFzTHFHUzFJYksrM1VoemZzUDUvNTVjU2FpWkVPcwpwQmdVb1plb0l3QjJBTjJaTlB5bDV5U0F5VlpvZllFMG1RaEpza24zdFduWXg3eXJQMXpCODI1a0FBQUJjd1c3CkJJb0FBQVFEQUVjd1JRSWdVbTRDbW9hdDBIdTZaMUExcFRKbTc4WTRYaHZWcmJIQ3RYUUZaa0QweHZzQ0lRQ0IKbVBSTFFZS2RObUMyMXJLRW5hUjBBRjBZbS9ENEp6NjlhWTJUbEcwM1hqQU5CZ2txaGtpRzl3MEJBUXNGQUFPQwpBUUVBZHZoNFJuUGVaWEliazc3b2xjaTM0K0tZRmxCSUtDbFdUTkl3dXB5NlpGM0NYSlBzSjRQQWUvMGMzTVpaCkZSbDl4SHN2LzNESXZOaU5udkJSblRjdHJFMGp1V0cxYVlrWWIzaGRJMFVNcWlqUHNmc0doZW9LQnpRVDBoREcKRDFET0hPNXB5czQvNnp3NXk2TVMrdkoyVXY3aHlWem1PdldqaFp1c0xvUUZBcmpYY0ROY0puN3N2SkdOMXRFSgpZeUxHSk42SFpMV0xSeU8zdTBHYU9HQkk4SGRmc3JzbGVKaUk4b1ROaXdjaFZuekR1UUlLZFo0M040N0R5QlgwClpjTmplbElzeGtPSlhCUHJQVWJOaGltK1dNWjlicWxpUFZLamlhRUJFQ1BIaVRFK0Y2a3dkRkpkTktJZUVtL3UKR0JTRW5Zdmp2RWRJMzh4U1JWMXZDdDgxUUE9PQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCi0tLS0tQkVHSU4gQ0VSVElGSUNBVEUtLS0tLQpNSUlFcXpDQ0FwT2dBd0lCQWdJUkFJdmhLZzVaUk8wOFZHUXg4SmRoVCtVd0RRWUpLb1pJaHZjTkFRRUxCUUF3CkdqRVlNQllHQTFVRUF3d1BSbUZyWlNCTVJTQlNiMjkwSUZneE1CNFhEVEUyTURVeU16SXlNRGMxT1ZvWERUTTIKTURVeU16SXlNRGMxT1Zvd0lqRWdNQjRHQTFVRUF3d1hSbUZyWlNCTVJTQkpiblJsY20xbFpHbGhkR1VnV0RFdwpnZ0VpTUEwR0NTcUdTSWIzRFFFQkFRVUFBNElCRHdBd2dnRUtBb0lCQVFEdFdLeVNEbjdyV1pjNWdnanozWkIwCjhqTzR4dGkzdXpJTmZENXNRN0xqN2h6ZXRVVCt3UW9iK2lYU1praG52eCtJdmRiWEY1L3l0OGFXUHBVS25QeW0Kb0x4c1lpSTVnUUJMeE5EekllYzBPSWFmbFdxQXIyOW03SjgrTk50QXBFTjhuWkZuZjNiaGVoWlc3QXhtUzFtMApablNzZEh3MEZ3K2JnaXhQZzJNUTlrOW9lZkZlcWErN0txZGx6NWJiclVZVjJ2b2x4aERGdG5JNE1oOEJpV0NOCnhESDFIaXpxK0dLQ2NIc2luRFpXdXJDcWRlci9hZkpCblFzK1NCU0w2TVZBcEh0K2QzNXpqQkQ5MmZPMkplNTYKZGhNZnpDZ09LWGVKMzQwV2hXM1RqRDF6cUxaWGVhQ3lVTlJuZk9tV1pWOG5FaHRIT0ZiVUNVN3IvS2tqTVpPOQpBZ01CQUFHamdlTXdnZUF3RGdZRFZSMFBBUUgvQkFRREFnR0dNQklHQTFVZEV3RUIvd1FJTUFZQkFmOENBUUF3CkhRWURWUjBPQkJZRUZNRE1BMGE1V0NETVhISnc4K0V1eXlDbTlXZzZNSG9HQ0NzR0FRVUZCd0VCQkc0d2JEQTAKQmdnckJnRUZCUWN3QVlZb2FIUjBjRG92TDI5amMzQXVjM1JuTFhKdmIzUXRlREV1YkdWMGMyVnVZM0o1Y0hRdQpiM0puTHpBMEJnZ3JCZ0VGQlFjd0FvWW9hSFIwY0RvdkwyTmxjblF1YzNSbkxYSnZiM1F0ZURFdWJHVjBjMlZ1ClkzSjVjSFF1YjNKbkx6QWZCZ05WSFNNRUdEQVdnQlRCSm5Ta2lrU2c1dm9nS05oY0k1cEZpQmg1NERBTkJna3EKaGtpRzl3MEJBUXNGQUFPQ0FnRUFCWVN1NElsK2ZJME1ZVTQyT1RtRWorMUhxUTVEdnlBZXlDQTZzR3VaZHdqRgpVR2VWT3YzTm5MeWZvZnVVT2pFYlk1aXJGQ0R0bnYrMGNrdWtVWk45bHo0UTJZaldHVXBXNFRUdTNpZVRzYUM5CkFGdkNTZ05ISnlXU1Z0V3ZCNVhEeHNxYXdsMUt6SHp6d3IxMzJiRjJydEd0YXpTcVZxSzlFMDdzR0hNQ2YrenAKRFFWRFZWR3RxWlBId1gzS3FVdGVmRTYyMWI4Ukk2VkNsNG9EMzBPbGY4cGp1ekc0SktCRlJGY2x6TFJqby9oNwpJa2tmalo4d0RhN2ZhT2pWWHg2bitlVVEyOWNJTUN6cjgvck5XSFM5cFlHR1FLSmlZMnhtVkM5aDEySDk5WHlmCnpXRTl2YjV6S1AzTVZHNm5lWDFoU2RvN1BFQWI5ZnFSaEhrcVZzcVV2SmxJUm12WHZWS1R3TkNQM2VDalJDQ0kKUFRBdmpWKzRuaTc4NmlYd3dGWU56OGwzUG1QTEN5UVhXR29obko4aUJtKzVuazdPMnluYVBWVzBVMlcrcHQydwpTVnV2ZERNNXpHdjJmOWx0TldVaVlaSEoxbW1POTdqU1kvNllmZE9VSDY2aVJ0UXREa0hCUmRrTkJzTWJEK0VtCjJUZ0JsZHRITlNKQmZCM3BtOUZibGdPY0owRlNXY1VEV0o3dk8wK05UWGxnclJvZlJUNnBWeXd6eFZvNmRORDAKV3pZbFRXZVVWc080MHhKcWhnVVFSRVI5WUxPTHhKME82QzhpMHhGeEFNS090U2RvZE1CM1JJd3Q3UkZRMHV5dApuNVo1TXFrWWhsTUkzSjF0UFJUcDFuRXQ5ZnlHc3BCT08wNWdpMTQ4UWFzcCszTitzdnFLb21vUWdsTm9BeFU9Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K`) - - // Create Namespace - _, err := kubernetesCl.CoreV1().Namespaces().Create(rootCtx, &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: ns1}}, metav1.CreateOptions{}) - if err != nil { - t.Fatal(err) - } - - tests := map[string]CreateCRTest{ - "v1 Certificate given": { - inputFile: path.Join(testdataPath, "create_cr_cert_with_ns1.yaml"), - inputArgs: []string{cr1Name}, - inputNamespace: ns1, - keyFilename: "", - expRunErr: false, - expNamespace: ns1, - expName: cr1Name, - expKeyFilename: cr1Name + ".key", - }, - "v1alpha3 Certificate given": { - inputFile: path.Join(testdataPath, "create_cr_v1alpha3_cert_with_ns1.yaml"), - inputArgs: []string{cr2Name}, - inputNamespace: ns1, - keyFilename: "", - expRunErr: false, - expNamespace: ns1, - expName: cr2Name, - expKeyFilename: cr2Name + ".key", - }, - "path to file to store private key provided": { - inputFile: path.Join(testdataPath, "create_cr_cert_with_ns1.yaml"), - inputArgs: []string{cr5Name}, - inputNamespace: ns1, - keyFilename: "test.key", - expRunErr: false, - expNamespace: ns1, - expName: cr5Name, - expKeyFilename: "test.key", - }, - "fetch flag set and CR will be ready and status.certificate set": { - inputFile: path.Join(testdataPath, "create_cr_cert_with_ns1.yaml"), - inputArgs: []string{cr6Name}, - inputNamespace: ns1, - keyFilename: "", - fetchCert: true, - timeout: 5 * time.Minute, - crStatus: cmapiv1.CertificateRequestStatus{ - Conditions: []cmapiv1.CertificateRequestCondition{ - {Type: cmapiv1.CertificateRequestConditionReady, Status: cmmeta.ConditionTrue}, - }, - Certificate: exampleCertificate, - }, - expRunErr: false, - expNamespace: ns1, - expName: cr6Name, - expKeyFilename: cr6Name + ".key", - expCertFilename: cr6Name + ".crt", - expCertFileContent: exampleCertificate, - }, - "fetch flag set and CR will be ready but status.certificate empty": { - inputFile: path.Join(testdataPath, "create_cr_cert_with_ns1.yaml"), - inputArgs: []string{cr7Name}, - inputNamespace: ns1, - keyFilename: "", - fetchCert: true, - timeout: 5 * time.Second, - crStatus: cmapiv1.CertificateRequestStatus{ - Conditions: []cmapiv1.CertificateRequestCondition{ - {Type: cmapiv1.CertificateRequestConditionReady, Status: cmmeta.ConditionTrue}, - }, - }, - expRunErr: true, - expErrMsg: "error when waiting for CertificateRequest to be signed: timed out waiting for the condition", - expNamespace: ns1, - expName: cr7Name, - expKeyFilename: cr7Name + ".key", - expCertFilename: cr7Name + ".crt", - expCertFileContent: exampleCertificate, - }, - } - - for name, test := range tests { - t.Run(name, func(t *testing.T) { - ctx, cancel := context.WithTimeout(rootCtx, time.Second*20) - defer cancel() - - streams, _, _, _ := genericclioptions.NewTestIOStreams() - - cleanUpFunc := setupPathForTest(t) - defer cleanUpFunc() - - // Options to run create CR command - opts := &certificaterequest.Options{ - Factory: &factory.Factory{ - CMClient: cmCl, - RESTConfig: config, - Namespace: test.inputNamespace, - EnforceNamespace: test.inputNamespace != "", - }, - IOStreams: streams, - InputFilename: test.inputFile, - KeyFilename: test.keyFilename, - CertFileName: test.certFilename, - FetchCert: test.fetchCert, - Timeout: test.timeout, - } - - // Validating args and flags - err := opts.Validate(test.inputArgs) - if err != nil { - t.Fatal(err) - } - - if test.fetchCert { - req := &cmapiv1.CertificateRequest{} - // Start a goroutine that will periodically check whether the CertificateRequest has been created - // by the CLI command yet. - // Once it has been created, set the `status.certificate` and `Ready` condition so that the `--fetch-certificate` - // part of the command is able to proceed. - errCh := make(chan error) - pollCtx, cancelPoll := context.WithCancel(ctx) - defer func() { - cancelPoll() - err := <-errCh - if err != nil { - t.Fatal(err) - } - }() - go func() { - defer close(errCh) - err = wait.PollImmediateUntil(time.Second, func() (done bool, err error) { - req, err = cmCl.CertmanagerV1().CertificateRequests(test.inputNamespace).Get(pollCtx, test.inputArgs[0], metav1.GetOptions{}) - if err != nil { - return false, nil - } - return true, nil - }, pollCtx.Done()) - if err != nil { - errCh <- fmt.Errorf("timeout when waiting for CertificateRequest to be created, error: %v", err) - return - } - - // CR has been created, try update status - req.Status.Conditions = test.crStatus.Conditions - req.Status.Certificate = test.crStatus.Certificate - req, err = cmCl.CertmanagerV1().CertificateRequests(test.inputNamespace).UpdateStatus(pollCtx, req, metav1.UpdateOptions{}) - if err != nil { - errCh <- err - } - }() - } - - // Run ctl create cr command with input options - err = opts.Run(ctx, test.inputArgs) - if err != nil { - if !test.expRunErr { - t.Errorf("got unexpected error when trying to create CR: %v", err) - } else if err.Error() != test.expErrMsg { - t.Errorf("got unexpected error when trying to create CR, expected: %v; actual: %v", test.expErrMsg, err) - } - } else { - // got no error - if test.expRunErr { - t.Errorf("expected but got no error when creating CR") - } - } - - // Check the file where the private key is stored - keyData, err := os.ReadFile(test.expKeyFilename) - if err != nil { - t.Errorf("error when reading file storing private key: %v", err) - } - _, err = pki.DecodePrivateKeyBytes(keyData) - if err != nil { - t.Errorf("invalid private key: %v", err) - } - - // Finished creating CR, check if everything is expected - crName := test.inputArgs[0] - gotCr, err := cmCl.CertmanagerV1().CertificateRequests(test.inputNamespace).Get(ctx, crName, metav1.GetOptions{}) - if err != nil { - t.Fatal(err) - } - - if gotCr.Name != test.expName { - t.Errorf("CR created has unexpected Name, expected: %s, actual: %s", test.expName, gotCr.Name) - } - - if gotCr.Namespace != test.expNamespace { - t.Errorf("CR created in unexpected Namespace, expected: %s, actual: %s", test.expNamespace, gotCr.Namespace) - } - - // If applicable, check the file where the certificate is stored - // If the expected error message is the one below, we skip checking - // because no certificate will have been written to file - if test.fetchCert && test.expErrMsg != "error when waiting for CertificateRequest to be signed: timed out waiting for the condition" { - certData, err := os.ReadFile(test.expCertFilename) - if err != nil { - t.Errorf("error when reading file storing private key: %v", err) - } - - if !bytes.Equal(test.expCertFileContent, certData) { - t.Errorf("certificate written to file is wrong, expected: %s,\nactual: %s", test.expCertFileContent, certData) - } - } - }) - } -} - -func getTestDataDir(t *testing.T) string { - testWorkingDirectory, err := os.Getwd() - if err != nil { - t.Fatal(err) - } - return path.Join(testWorkingDirectory, "testdata") -} - -// setupPathForTest sets up a tmp directory and cd into it for tests as the command being tested creates files -// in the local directory. -// Returns a cleanup function which will change cd back to original working directory and remove the tmp directory. -func setupPathForTest(t *testing.T) func() { - workingDirectoryBeforeTest, err := os.Getwd() - if err != nil { - t.Fatal(err) - } - - // Create tmp directory and cd into it to store private key files - tmpDir, err := os.MkdirTemp("", "tmp-ctl-test-*") - if err != nil { - t.Fatal(err) - } - - if err := os.Chdir(tmpDir); err != nil { - t.Fatal(err) - } - return func() { - if err := os.Chdir(workingDirectoryBeforeTest); err != nil { - t.Fatal(err) - } - if err := os.RemoveAll(tmpDir); err != nil { - t.Fatal(err) - } - } -} diff --git a/test/integration/ctl/ctl_install.go b/test/integration/ctl/ctl_install.go deleted file mode 100644 index e68012c81d0..00000000000 --- a/test/integration/ctl/ctl_install.go +++ /dev/null @@ -1,143 +0,0 @@ -/* -Copyright 2021 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. -*/ - -package ctl - -import ( - "bytes" - "context" - "fmt" - "regexp" - "strings" - "testing" - "time" - - "github.com/sergi/go-diff/diffmatchpatch" - - "github.com/cert-manager/cert-manager/cmd/ctl/cmd" - "github.com/cert-manager/cert-manager/test/integration/ctl/install_framework" - "github.com/cert-manager/cert-manager/test/internal/util" -) - -func TestCtlInstall(t *testing.T) { - tests := map[string]struct { - prerun bool - preInputArgs []string - preExpErr bool - preExpOutput string - - inputArgs []string - expErr bool - expOutput string - }{ - "install cert-manager": { - inputArgs: []string{}, - expErr: false, - expOutput: `STATUS: deployed`, - }, - "install cert-manager (already installed)": { - prerun: true, - preInputArgs: []string{}, - preExpErr: false, - preExpOutput: `STATUS: deployed`, - - inputArgs: []string{}, - expErr: true, - expOutput: `^Found existing installed cert-manager CRDs! Cannot continue with installation.$`, - }, - "install cert-manager (already installed, in other namespace)": { - prerun: true, - preInputArgs: []string{"--namespace=test"}, - preExpErr: false, - preExpOutput: `STATUS: deployed`, - - inputArgs: []string{}, - expErr: true, - expOutput: `^Found existing installed cert-manager CRDs! Cannot continue with installation.$`, - }, - } - - for name, test := range tests { - t.Run(name, func(t *testing.T) { - testApiServer, cleanup := install_framework.NewTestInstallApiServer(t) - defer cleanup() - - ctx, cancel := context.WithTimeout(context.Background(), time.Second*40) - defer cancel() - - if test.prerun { - executeCommandAndCheckOutput(t, ctx, testApiServer.KubeConfig(), test.preInputArgs, test.preExpErr, test.preExpOutput) - } - - executeCommandAndCheckOutput(t, ctx, testApiServer.KubeConfig(), test.inputArgs, test.expErr, test.expOutput) - }) - } -} - -func executeCommandAndCheckOutput( - t *testing.T, - ctx context.Context, - kubeConfig string, - inputArgs []string, - expErr bool, - expOutput string, -) { - // Options to run status command - stdin := bytes.NewBufferString("") - stdout := bytes.NewBufferString("") - - chartPath := util.GetTestPath("deploy", "charts", "cert-manager", "cert-manager.tgz") - cmd := cmd.NewCertManagerCtlCommand(ctx, stdin, stdout, stdout) - cmd.SetArgs(append([]string{ - fmt.Sprintf("--kubeconfig=%s", kubeConfig), - "--wait=false", - fmt.Sprintf("--chart-name=%s", chartPath), - "x", - "install", - }, inputArgs...)) - - err := cmd.Execute() - if err != nil { - fmt.Fprintf(stdout, "%s\n", err) - - if !expErr { - t.Errorf("got unexpected error: %v", err) - } else { - t.Logf("got an error, which was expected, details: %v", err) - } - } else if expErr { - // expected error but error is nil - t.Errorf("expected but got no error") - } - - match, err := regexp.MatchString(strings.TrimSpace(expOutput), strings.TrimSpace(stdout.String())) - if err != nil { - t.Error(err) - } - dmp := diffmatchpatch.New() - if !match { - diffs := dmp.DiffMain(strings.TrimSpace(expOutput), strings.TrimSpace(stdout.String()), false) - t.Errorf( - "got unexpected output, diff (ignoring line anchors ^ and $ and regex for creation time):\n"+ - "diff: %s\n\n"+ - " exp: %s\n\n"+ - " got: %s", - dmp.DiffPrettyText(diffs), - expOutput, - stdout.String(), - ) - } -} diff --git a/test/integration/ctl/ctl_renew_test.go b/test/integration/ctl/ctl_renew_test.go deleted file mode 100644 index 06c4deadeb9..00000000000 --- a/test/integration/ctl/ctl_renew_test.go +++ /dev/null @@ -1,207 +0,0 @@ -/* -Copyright 2020 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. -*/ - -package ctl - -import ( - "context" - "testing" - "time" - - corev1 "k8s.io/api/core/v1" - - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/cli-runtime/pkg/genericclioptions" - - "github.com/cert-manager/cert-manager/cmd/ctl/pkg/factory" - "github.com/cert-manager/cert-manager/cmd/ctl/pkg/renew" - apiutil "github.com/cert-manager/cert-manager/pkg/api/util" - cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" - cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" - "github.com/cert-manager/cert-manager/test/integration/framework" - "github.com/cert-manager/cert-manager/test/unit/gen" -) - -// TestCtlRenew tests the renewal logic of the ctl CLI command against the -// cert-manager Issuing controller. -func TestCtlRenew(t *testing.T) { - ctx, cancel := context.WithTimeout(context.Background(), time.Second*40) - defer cancel() - - config, stopFn := framework.RunControlPlane(t, ctx) - defer stopFn() - - // Build clients - kubeClient, _, cmCl, _ := framework.NewClients(t, config) - - var ( - crt1Name = "testcrt-1" - crt2Name = "testcrt-2" - crt3Name = "testcrt-3" - crt4Name = "testcrt-4" - ns1 = "testns-1" - ns2 = "testns-2" - ) - - // Create Namespaces - for _, ns := range []string{ns1, ns2} { - _, err := kubeClient.CoreV1().Namespaces().Create(ctx, &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: ns}}, metav1.CreateOptions{}) - if err != nil { - t.Fatal(err) - } - } - - crt1 := gen.Certificate(crt1Name, - gen.SetCertificateNamespace(ns1), - gen.SetCertificateIssuer(cmmeta.ObjectReference{Kind: "Issuer", Name: "test-issuer"}), - gen.SetCertificateSecretName("crt1"), - gen.SetCertificateCommonName("crt1"), - ) - crt2 := gen.Certificate(crt2Name, - gen.SetCertificateNamespace(ns1), - gen.SetCertificateIssuer(cmmeta.ObjectReference{Kind: "Issuer", Name: "test-issuer"}), - gen.SetCertificateSecretName("crt2"), - gen.SetCertificateCommonName("crt2"), - gen.AddCertificateLabels(map[string]string{ - "foo": "bar", - }), - ) - crt3 := gen.Certificate(crt3Name, - gen.SetCertificateNamespace(ns2), - gen.SetCertificateIssuer(cmmeta.ObjectReference{Kind: "Issuer", Name: "test-issuer"}), - gen.SetCertificateSecretName("crt3"), - gen.SetCertificateCommonName("crt3"), - gen.AddCertificateLabels(map[string]string{ - "foo": "bar", - }), - ) - crt4 := gen.Certificate(crt4Name, - gen.SetCertificateIssuer(cmmeta.ObjectReference{Kind: "Issuer", Name: "test-issuer"}), - gen.SetCertificateSecretName("crt4"), - gen.SetCertificateCommonName("crt5"), - gen.SetCertificateNamespace(ns2), - ) - - tests := map[string]struct { - inputArgs []string - inputLabels string - inputNamespace string - inputAll bool - inputAllNamespaces bool - - crtsWithIssuing map[*cmapi.Certificate]bool - }{ - "certificate name and namespace given": { - inputArgs: []string{crt1Name, crt2Name}, - inputNamespace: ns1, - crtsWithIssuing: map[*cmapi.Certificate]bool{ - crt1: true, - crt2: true, - crt3: false, - crt4: false, - }, - }, - "--all and namespace given": { - inputAll: true, - inputNamespace: ns2, - - crtsWithIssuing: map[*cmapi.Certificate]bool{ - crt1: false, - crt2: false, - crt3: true, - crt4: true, - }, - }, - "--all and --all-namespaces given": { - inputAll: true, - inputAllNamespaces: true, - - crtsWithIssuing: map[*cmapi.Certificate]bool{ - crt1: true, - crt2: true, - crt3: true, - crt4: true, - }, - }, - "--all-namespaces and -l foo=bar given": { - inputAllNamespaces: true, - inputLabels: "foo=bar", - - crtsWithIssuing: map[*cmapi.Certificate]bool{ - crt1: false, - crt2: true, - crt3: true, - crt4: false, - }, - }, - } - - for name, test := range tests { - t.Run(name, func(t *testing.T) { - // Create Certificates - for _, crt := range []*cmapi.Certificate{crt1, crt2, crt3, crt4} { - _, err := cmCl.CertmanagerV1().Certificates(crt.Namespace).Create(ctx, crt, metav1.CreateOptions{}) - if err != nil { - t.Fatal(err) - } - } - - // Run ctl renew command with input options - streams, _, _, _ := genericclioptions.NewTestIOStreams() - - cmd := &renew.Options{ - LabelSelector: test.inputLabels, - All: test.inputAll, - AllNamespaces: test.inputAllNamespaces, - Factory: &factory.Factory{ - CMClient: cmCl, - RESTConfig: config, - Namespace: test.inputNamespace, - }, - IOStreams: streams, - } - - if err := cmd.Run(ctx, test.inputArgs); err != nil { - t.Fatal(err) - } - - // Check issuing condition against Certificates - for crt, shouldIssue := range test.crtsWithIssuing { - gotCrt, err := cmCl.CertmanagerV1().Certificates(crt.Namespace).Get(ctx, crt.Name, metav1.GetOptions{}) - if err != nil { - t.Fatal(err) - } - - hasCondition := true - if cond := apiutil.GetCertificateCondition(gotCrt, cmapi.CertificateConditionIssuing); cond == nil || cond.Status != cmmeta.ConditionTrue { - hasCondition = false - } - - if shouldIssue != hasCondition { - t.Errorf("%s/%s expected to have issuing condition=%t got=%t", crt.Namespace, crt.Name, shouldIssue, hasCondition) - } - } - - // Clean up Certificates - for _, crt := range []*cmapi.Certificate{crt1, crt2, crt3, crt4} { - err := cmCl.CertmanagerV1().Certificates(crt.Namespace).Delete(ctx, crt.Name, metav1.DeleteOptions{}) - if err != nil { - t.Fatal(err) - } - } - }) - } -} diff --git a/test/integration/ctl/ctl_status_certificate_test.go b/test/integration/ctl/ctl_status_certificate_test.go deleted file mode 100644 index 8477b2a0076..00000000000 --- a/test/integration/ctl/ctl_status_certificate_test.go +++ /dev/null @@ -1,691 +0,0 @@ -/* -Copyright 2020 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. -*/ - -package ctl - -import ( - "context" - "crypto/rand" - "crypto/x509" - "crypto/x509/pkix" - "encoding/asn1" - "encoding/pem" - "fmt" - "regexp" - "strings" - "testing" - "time" - - "github.com/sergi/go-diff/diffmatchpatch" - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/cli-runtime/pkg/genericclioptions" - "k8s.io/client-go/kubernetes" - "k8s.io/client-go/tools/reference" - - "github.com/cert-manager/cert-manager/cmd/ctl/pkg/factory" - statuscertcmd "github.com/cert-manager/cert-manager/cmd/ctl/pkg/status/certificate" - apiutil "github.com/cert-manager/cert-manager/pkg/api/util" - cmacme "github.com/cert-manager/cert-manager/pkg/apis/acme/v1" - cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" - cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" - "github.com/cert-manager/cert-manager/pkg/client/clientset/versioned" - "github.com/cert-manager/cert-manager/pkg/ctl" - "github.com/cert-manager/cert-manager/pkg/util/pki" - "github.com/cert-manager/cert-manager/test/integration/framework" - "github.com/cert-manager/cert-manager/test/unit/gen" -) - -func generateCSR(t *testing.T) []byte { - skRSA, err := pki.GenerateRSAPrivateKey(2048) - if err != nil { - t.Fatal(err) - } - asn1Subj, _ := asn1.Marshal(pkix.Name{ - CommonName: "test", - }.ToRDNSequence()) - template := x509.CertificateRequest{ - RawSubject: asn1Subj, - } - - csrBytes, err := x509.CreateCertificateRequest(rand.Reader, &template, skRSA) - if err != nil { - t.Fatal(err) - } - - csr := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE REQUEST", Bytes: csrBytes}) - - return csr -} - -func TestCtlStatusCert(t *testing.T) { - testCSR := generateCSR(t) - - ctx, cancel := context.WithTimeout(context.Background(), time.Second*40) - defer cancel() - - config, stopFn := framework.RunControlPlane(t, ctx) - defer stopFn() - - // Build clients - kubernetesCl, _, cmCl, _ := framework.NewClients(t, config) - - var ( - crt1Name = "testcrt-1" - crt2Name = "testcrt-2" - crt3Name = "testcrt-3" - crt4Name = "testcrt-4" - ns1 = "testns-1" - req1Name = "testreq-1" - req2Name = "testreq-2" - req3Name = "testreq-3" - revision1 = 1 - revision2 = 2 - - crtReadyAndUpToDateCond = cmapi.CertificateCondition{Type: cmapi.CertificateConditionReady, - Status: cmmeta.ConditionTrue, Message: "Certificate is up to date and has not expired"} - crtIssuingCond = cmapi.CertificateCondition{Type: cmapi.CertificateConditionIssuing, - Status: cmmeta.ConditionTrue, Message: "Issuance of a new Certificate is in Progress"} - - reqNotReadyCond = cmapi.CertificateRequestCondition{Type: cmapi.CertificateRequestConditionReady, Status: cmmeta.ConditionFalse, Reason: "Pending", Message: "Waiting on certificate issuance from order default/example-order: \"pending\""} - - tlsCrt = []byte(`-----BEGIN CERTIFICATE----- -MIICyTCCAbGgAwIBAgIRAOL4jtyULBSEYyGdqQn9YzowDQYJKoZIhvcNAQELBQAw -DzENMAsGA1UEAxMEdGVzdDAeFw0yMDA3MzAxNjExNDNaFw0yMDEwMjgxNjExNDNa -MA8xDTALBgNVBAMTBHRlc3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB -AQDdfNmjh5ag7f6U1hj1OAx/dEN9kQzPsSlBMXGb/Ho4k5iegrFd6w8JkYdCthFv -lfg3bIhw5tCKaw1o57HnWKBKKGt7XpeIu1mEcv8pveMIPO7TZ4+oElgX880NfJmL -DkjEcctEo/+FurudO1aEbNfbNWpzudYKj7gGtYshBytqaYt4/APqWARJBFCYVVys -wexZ0fLi5cBD8H1bQ1Ec3OCr5Mrq9thAGkj+rVlgYR0AZVGa9+SCOj27t6YCmyzR -AJSEQ35v58Zfxp5tNyYd6wcAswJ9YipnUXvwahF95PNlRmMhp3Eo15m9FxehcVXU -BOfxykMwZN7onMhuHiiwiB+NAgMBAAGjIDAeMA4GA1UdDwEB/wQEAwIFoDAMBgNV -HRMBAf8EAjAAMA0GCSqGSIb3DQEBCwUAA4IBAQALrnldWjTBTvV5WKapUHUG0rhA -vp2Cf+5FsPw8vKScXp4L+wKGdPOjhHz6NOiw5wu8A0HxlVUFawRpagkjFkeTL78O -9ghBHLiqn9xNPIKC6ID3WpnN5terwQxQeO/M54sVMslUWCcZm9Pu4Eb//2e6wEdu -eMmpfeISQmCsBC1CTmpxUjeUg5DEQ0X1TQykXq+bG2iso6RYPxZTFTHJFzXiDYEc -/X7H+bOmpo/dMrXapwfvp2gD+BEq96iVpf/DBzGYNs/657LAHJ4YtxtAZCa1CK9G -MA6koCR/K23HZfML8vT6lcHvQJp9XXaHRIe9NX/M/2f6VpfO7JjKWLou5k5a ------END CERTIFICATE-----`) - ) - certIsValidTime, err := time.Parse(time.RFC3339, "2020-09-16T09:26:18Z") - if err != nil { - t.Fatal(err) - } - - // Create Namespace - _, err = kubernetesCl.CoreV1().Namespaces().Create(ctx, &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: ns1}}, metav1.CreateOptions{}) - if err != nil { - t.Fatal(err) - } - - tests := map[string]struct { - certificate *cmapi.Certificate - certificateStatus *cmapi.CertificateStatus - inputArgs []string - inputNamespace string - req *cmapi.CertificateRequest - reqStatus *cmapi.CertificateRequestStatus - // At most one of issuer and clusterIssuer is not nil - issuer *cmapi.Issuer - clusterIssuer *cmapi.ClusterIssuer - secret *corev1.Secret - order *cmacme.Order - challenges []*cmacme.Challenge - crtEvents *corev1.EventList - issuerEvents *corev1.EventList - secretEvents *corev1.EventList - reqEvents *corev1.EventList - - expErr bool - expOutput string - }{ - "certificate issued and up-to-date with clusterIssuer": { - certificate: gen.Certificate(crt1Name, - gen.SetCertificateNamespace(ns1), - gen.SetCertificateDNSNames("www.example.com"), - gen.SetCertificateIssuer(cmmeta.ObjectReference{Name: "letsencrypt-prod", Kind: "ClusterIssuer"}), - gen.SetCertificateSecretName("example-tls")), - certificateStatus: &cmapi.CertificateStatus{Conditions: []cmapi.CertificateCondition{crtReadyAndUpToDateCond}, - NotAfter: &metav1.Time{Time: certIsValidTime}, Revision: &revision1}, - crtEvents: &corev1.EventList{ - Items: []corev1.Event{{ - ObjectMeta: metav1.ObjectMeta{ - Name: "crtEvent", - Namespace: ns1, - }, - Type: "type", - Reason: "reason", - Message: "message", - }}, - }, - inputArgs: []string{crt1Name}, - inputNamespace: ns1, - clusterIssuer: gen.ClusterIssuer("letsencrypt-prod", gen.SetIssuerSelfSigned(cmapi.SelfSignedIssuer{})), - expErr: false, - expOutput: `^Name: testcrt-1 -Namespace: testns-1 -Created at: .* -Conditions: - Ready: True, Reason: , Message: Certificate is up to date and has not expired -DNS Names: -- www.example.com -Events: - Type Reason Age From Message - ---- ------ ---- ---- ------- - type reason message -Issuer: - Name: letsencrypt-prod - Kind: ClusterIssuer - Conditions: - No Conditions set - Events: -error when finding Secret "example-tls": secrets "example-tls" not found -Not Before: -Not After: .* -Renewal Time: -No CertificateRequest found for this Certificate$`, - }, - "certificate issued and renewal in progress with Issuer": { - certificate: gen.Certificate(crt2Name, - gen.SetCertificateNamespace(ns1), - gen.SetCertificateDNSNames("www.example.com"), - gen.SetCertificateIssuer(cmmeta.ObjectReference{Name: "letsencrypt-prod", Kind: "Issuer"}), - gen.SetCertificateSecretName("existing-tls-secret")), - certificateStatus: &cmapi.CertificateStatus{Conditions: []cmapi.CertificateCondition{crtReadyAndUpToDateCond, crtIssuingCond}, - NotAfter: &metav1.Time{Time: certIsValidTime}, Revision: &revision1}, - inputArgs: []string{crt2Name}, - inputNamespace: ns1, - req: gen.CertificateRequest(req1Name, - gen.SetCertificateRequestNamespace(ns1), - gen.SetCertificateRequestAnnotations(map[string]string{cmapi.CertificateRequestRevisionAnnotationKey: fmt.Sprintf("%d", revision2)}), - gen.SetCertificateRequestIssuer(cmmeta.ObjectReference{Name: "letsencrypt-prod", Kind: "Issuer"}), - gen.SetCertificateRequestCSR(testCSR)), - reqStatus: &cmapi.CertificateRequestStatus{Conditions: []cmapi.CertificateRequestCondition{reqNotReadyCond}}, - issuer: gen.Issuer("letsencrypt-prod", - gen.SetIssuerNamespace(ns1), - gen.SetIssuerACME(cmacme.ACMEIssuer{ - Server: "https://dummy.acme.local/", - PrivateKey: cmmeta.SecretKeySelector{ - LocalObjectReference: cmmeta.LocalObjectReference{ - Name: "test", - }, - Key: "test", - }, - })), - issuerEvents: &corev1.EventList{ - Items: []corev1.Event{{ - ObjectMeta: metav1.ObjectMeta{ - Name: "issuerEvent", - }, - Type: "type", - Reason: "reason", - Message: "message", - }}, - }, - secret: gen.Secret("existing-tls-secret", - gen.SetSecretNamespace(ns1), - gen.SetSecretData(map[string][]byte{"tls.crt": tlsCrt})), - secretEvents: &corev1.EventList{ - Items: []corev1.Event{{ - ObjectMeta: metav1.ObjectMeta{ - Name: "secretEvent", - Namespace: ns1, - }, - Type: "type", - Reason: "reason", - Message: "message", - }}, - }, - order: gen.Order("example-order", - gen.SetOrderNamespace(ns1), - gen.SetOrderCsr(testCSR), - gen.SetOrderIssuer(cmmeta.ObjectReference{Name: "letsencrypt-prod", Kind: "Issuer"}), - gen.SetOrderDNSNames("www.example.com")), - challenges: []*cmacme.Challenge{ - gen.Challenge("test-challenge1", - gen.SetChallengeNamespace(ns1), - gen.SetChallengeType(cmacme.ACMEChallengeTypeHTTP01), - gen.SetChallengeToken("dummy-token1"), - gen.SetChallengePresented(false), - gen.SetChallengeProcessing(false), - ), - gen.Challenge("test-challenge2", - gen.SetChallengeNamespace(ns1), - gen.SetChallengeType(cmacme.ACMEChallengeTypeDNS01), - gen.SetChallengeToken("dummy-token2"), - gen.SetChallengePresented(false), - gen.SetChallengeProcessing(false), - ), - }, - expErr: false, - expOutput: `^Name: testcrt-2 -Namespace: testns-1 -Created at: .* -Conditions: - Ready: True, Reason: , Message: Certificate is up to date and has not expired - Issuing: True, Reason: , Message: Issuance of a new Certificate is in Progress -DNS Names: -- www.example.com -Events: -Issuer: - Name: letsencrypt-prod - Kind: Issuer - Conditions: - No Conditions set - Events: - Type Reason Age From Message - ---- ------ ---- ---- ------- - type reason message -Secret: - Name: existing-tls-secret - Issuer Country: - Issuer Organisation: - Issuer Common Name: test - Key Usage: Digital Signature, Key Encipherment - Extended Key Usages: - Public Key Algorithm: RSA - Signature Algorithm: SHA256-RSA - Subject Key ID: - Authority Key ID: - Serial Number: e2f88edc942c148463219da909fd633a - Events: - Type Reason Age From Message - ---- ------ ---- ---- ------- - type reason message -Not Before: -Not After: .* -Renewal Time: -CertificateRequest: - Name: testreq-1 - Namespace: testns-1 - Conditions: - Ready: False, Reason: Pending, Message: Waiting on certificate issuance from order default/example-order: "pending" - Events: -Order: - Name: example-order - State: , Reason: - No Authorizations for this Order -Challenges: -- Name: test-challenge1, Type: HTTP-01, Token: dummy-token1, Key: , State: , Reason: , Processing: false, Presented: false -- Name: test-challenge2, Type: DNS-01, Token: dummy-token2, Key: , State: , Reason: , Processing: false, Presented: false$`, - }, - "certificate issued and renewal in progress without Issuer": { - certificate: gen.Certificate(crt3Name, - gen.SetCertificateNamespace(ns1), - gen.SetCertificateDNSNames("www.example.com"), - gen.SetCertificateIssuer(cmmeta.ObjectReference{Name: "non-existing-issuer", Kind: "Issuer"}), - gen.SetCertificateSecretName("example-tls")), - certificateStatus: &cmapi.CertificateStatus{Conditions: []cmapi.CertificateCondition{crtReadyAndUpToDateCond, crtIssuingCond}, - NotAfter: &metav1.Time{Time: certIsValidTime}, Revision: &revision1}, - inputArgs: []string{crt3Name}, - inputNamespace: ns1, - req: gen.CertificateRequest(req2Name, - gen.SetCertificateRequestNamespace(ns1), - gen.SetCertificateRequestAnnotations(map[string]string{cmapi.CertificateRequestRevisionAnnotationKey: fmt.Sprintf("%d", revision2)}), - gen.SetCertificateRequestIssuer(cmmeta.ObjectReference{Name: "non-existing-issuer", Kind: "Issuer"}), - gen.SetCertificateRequestCSR(testCSR)), - reqStatus: &cmapi.CertificateRequestStatus{Conditions: []cmapi.CertificateRequestCondition{reqNotReadyCond}}, - reqEvents: &corev1.EventList{ - Items: []corev1.Event{{ - ObjectMeta: metav1.ObjectMeta{ - Name: "reqEvent", - Namespace: ns1, - }, - Type: "type", - Reason: "reason", - Message: "message", - }}, - }, - issuer: nil, - expErr: false, - expOutput: `^Name: testcrt-3 -Namespace: testns-1 -Created at: .* -Conditions: - Ready: True, Reason: , Message: Certificate is up to date and has not expired - Issuing: True, Reason: , Message: Issuance of a new Certificate is in Progress -DNS Names: -- www.example.com -Events: -error when getting Issuer: issuers.cert-manager.io "non-existing-issuer" not found -error when finding Secret "example-tls": secrets "example-tls" not found -Not Before: -Not After: .* -Renewal Time: -CertificateRequest: - Name: testreq-2 - Namespace: testns-1 - Conditions: - Ready: False, Reason: Pending, Message: Waiting on certificate issuance from order default/example-order: "pending" - Events: - Type Reason Age From Message - ---- ------ ---- ---- ------- - type reason message$`, - }, - "certificate issued and renewal in progress without ClusterIssuer": { - certificate: gen.Certificate(crt4Name, - gen.SetCertificateNamespace(ns1), - gen.SetCertificateDNSNames("www.example.com"), - gen.SetCertificateIssuer(cmmeta.ObjectReference{Name: "non-existing-clusterissuer", Kind: "ClusterIssuer"}), - gen.SetCertificateSecretName("example-tls")), - certificateStatus: &cmapi.CertificateStatus{Conditions: []cmapi.CertificateCondition{crtReadyAndUpToDateCond, crtIssuingCond}, - NotAfter: &metav1.Time{Time: certIsValidTime}, Revision: &revision1}, - inputArgs: []string{crt4Name}, - inputNamespace: ns1, - req: gen.CertificateRequest(req3Name, - gen.SetCertificateRequestNamespace(ns1), - gen.SetCertificateRequestIssuer(cmmeta.ObjectReference{Name: "non-existing-clusterissuer", Kind: "ClusterIssuer"}), - gen.SetCertificateRequestAnnotations(map[string]string{cmapi.CertificateRequestRevisionAnnotationKey: fmt.Sprintf("%d", revision2)}), - gen.SetCertificateRequestCSR(testCSR)), - reqStatus: &cmapi.CertificateRequestStatus{Conditions: []cmapi.CertificateRequestCondition{reqNotReadyCond}}, - issuer: nil, - expErr: false, - expOutput: `^Name: testcrt-4 -Namespace: testns-1 -Created at: .* -Conditions: - Ready: True, Reason: , Message: Certificate is up to date and has not expired - Issuing: True, Reason: , Message: Issuance of a new Certificate is in Progress -DNS Names: -- www.example.com -Events: -error when getting ClusterIssuer: clusterissuers.cert-manager.io "non-existing-clusterissuer" not found -error when finding Secret "example-tls": secrets "example-tls" not found -Not Before: -Not After: .* -Renewal Time: -CertificateRequest: - Name: testreq-3 - Namespace: testns-1 - Conditions: - Ready: False, Reason: Pending, Message: Waiting on certificate issuance from order default/example-order: "pending" - Events: $`, - }, - } - - for name, test := range tests { - t.Run(name, func(t *testing.T) { - // Create Certificate resource - crt, err := cmCl.CertmanagerV1().Certificates(test.inputNamespace).Create(ctx, test.certificate, metav1.CreateOptions{}) - if err != nil { - t.Fatal(err) - } - crt, err = setCertificateStatus(cmCl, crt, test.certificateStatus, ctx) - if err != nil { - t.Fatal(err) - } - if test.crtEvents != nil { - crtRef, err := reference.GetReference(ctl.Scheme, crt) - if err != nil { - t.Fatalf("error when getting ObjectReference: %v", err) - } - err = createEventsOwnedByRef(kubernetesCl, ctx, test.crtEvents, crtRef, crt.Namespace) - if err != nil { - t.Fatal(err) - } - } - - // Set up related resources - if test.req != nil { - req, err := createCROwnedByCrt(cmCl, ctx, crt, test.req, test.reqStatus) - if err != nil { - t.Fatal(err) - } - if test.reqEvents != nil { - reqRef, err := reference.GetReference(ctl.Scheme, req) - if err != nil { - t.Fatalf("error when getting ObjectReference: %v", err) - } - err = createEventsOwnedByRef(kubernetesCl, ctx, test.reqEvents, reqRef, req.Namespace) - if err != nil { - t.Fatal(err) - } - } - } - - if test.issuer != nil { - issuer, err := cmCl.CertmanagerV1().Issuers(crt.Namespace).Create(ctx, test.issuer, metav1.CreateOptions{}) - if err != nil { - t.Fatal(err) - } - if test.issuerEvents != nil { - issuerRef, err := reference.GetReference(ctl.Scheme, issuer) - if err != nil { - t.Fatalf("error when getting ObjectReference: %v", err) - } - err = createEventsOwnedByRef(kubernetesCl, ctx, test.issuerEvents, issuerRef, issuer.Namespace) - if err != nil { - t.Fatal(err) - } - } - } - if test.clusterIssuer != nil { - clusterIssuer, err := cmCl.CertmanagerV1().ClusterIssuers().Create(ctx, test.clusterIssuer, metav1.CreateOptions{}) - if err != nil { - t.Fatal(err) - } - if test.issuerEvents != nil { - issuerRef, err := reference.GetReference(ctl.Scheme, clusterIssuer) - if err != nil { - t.Fatalf("error when getting ObjectReference: %v", err) - } - err = createEventsOwnedByRef(kubernetesCl, ctx, test.issuerEvents, issuerRef, clusterIssuer.Namespace) - if err != nil { - t.Fatal(err) - } - } - } - - if test.secret != nil { - secret, err := kubernetesCl.CoreV1().Secrets(test.inputNamespace).Create(ctx, test.secret, metav1.CreateOptions{}) - if err != nil { - t.Fatal(err) - } - if test.secretEvents != nil { - secretRef, err := reference.GetReference(ctl.Scheme, secret) - if err != nil { - t.Fatalf("error when getting ObjectReference: %v", err) - } - err = createEventsOwnedByRef(kubernetesCl, ctx, test.secretEvents, secretRef, secret.Namespace) - if err != nil { - t.Fatal(err) - } - } - } - - if test.order != nil { - createdReq, err := cmCl.CertmanagerV1().CertificateRequests(test.req.Namespace).Get(ctx, test.req.Name, metav1.GetOptions{}) - if err != nil { - t.Fatal(err) - } - err = createOrderOwnedByCR(cmCl, ctx, createdReq, test.order) - if err != nil { - t.Fatal(err) - } - } - - if len(test.challenges) > 0 { - createdOrder, err := cmCl.AcmeV1().Orders(test.req.Namespace).Get(ctx, test.order.Name, metav1.GetOptions{}) - if err != nil { - t.Fatal(err) - } - err = createChallengesOwnedByOrder(cmCl, ctx, createdOrder, test.challenges) - if err != nil { - t.Fatal(err) - } - } - - // Options to run status command - streams, _, outBuf, _ := genericclioptions.NewTestIOStreams() - opts := &statuscertcmd.Options{ - Factory: &factory.Factory{ - CMClient: cmCl, - RESTConfig: config, - Namespace: test.inputNamespace, - }, - IOStreams: streams, - } - - err = opts.Run(ctx, test.inputArgs) - if err != nil { - if !test.expErr { - t.Errorf("got unexpected error: %v", err) - } else { - t.Logf("got an error, which was expected, details: %v", err) - } - return - } else if test.expErr { - // expected error but error is nil - t.Error("got no error but expected one") - return - } - - expectedOutput := strings.TrimSpace(test.expOutput) - commandOutput := strings.TrimSpace(outBuf.String()) - - match, err := regexp.MatchString(expectedOutput, commandOutput) - if err != nil { - t.Errorf("failed to match regex for output: %s", err) - } - - if !match { - dmp := diffmatchpatch.New() - diffs := dmp.DiffMain(expectedOutput, commandOutput, false) - t.Errorf("got unexpected output, diff (ignoring line anchors ^ and $ and regex for creation time):\n%s\n\n expected: \n%s\n\n got: \n%s", dmp.DiffPrettyText(diffs), test.expOutput, outBuf.String()) - } - - err = validateOutputTimes(commandOutput, certIsValidTime) - if err != nil { - t.Errorf("couldn't validate times in output: %s", err) - } - }) - } - -} - -func setCertificateStatus(cmCl versioned.Interface, crt *cmapi.Certificate, - status *cmapi.CertificateStatus, ctx context.Context) (*cmapi.Certificate, error) { - for _, cond := range status.Conditions { - apiutil.SetCertificateCondition(crt, crt.Generation, cond.Type, cond.Status, cond.Reason, cond.Message) - } - crt.Status.NotAfter = status.NotAfter - crt.Status.Revision = status.Revision - crt, err := cmCl.CertmanagerV1().Certificates(crt.Namespace).UpdateStatus(ctx, crt, metav1.UpdateOptions{}) - return crt, err -} - -func createCROwnedByCrt(cmCl versioned.Interface, ctx context.Context, crt *cmapi.Certificate, - req *cmapi.CertificateRequest, reqStatus *cmapi.CertificateRequestStatus) (*cmapi.CertificateRequest, error) { - - req, err := cmCl.CertmanagerV1().CertificateRequests(crt.Namespace).Create(ctx, req, metav1.CreateOptions{}) - if err != nil { - return nil, err - } - - req.OwnerReferences = append(req.OwnerReferences, *metav1.NewControllerRef(crt, cmapi.SchemeGroupVersion.WithKind("Certificate"))) - req, err = cmCl.CertmanagerV1().CertificateRequests(crt.Namespace).Update(ctx, req, metav1.UpdateOptions{}) - if err != nil { - return nil, fmt.Errorf("Update Err: %v", err) - } - - if reqStatus != nil { - req.Status.Conditions = reqStatus.Conditions - req, err = cmCl.CertmanagerV1().CertificateRequests(crt.Namespace).UpdateStatus(ctx, req, metav1.UpdateOptions{}) - if err != nil { - return nil, fmt.Errorf("Update Err: %v", err) - } - } - return req, nil -} - -func createOrderOwnedByCR(cmCl versioned.Interface, ctx context.Context, - req *cmapi.CertificateRequest, order *cmacme.Order) error { - - order, err := cmCl.AcmeV1().Orders(req.Namespace).Create(ctx, order, metav1.CreateOptions{}) - if err != nil { - return err - } - - order.OwnerReferences = append(order.OwnerReferences, *metav1.NewControllerRef(req, cmapi.SchemeGroupVersion.WithKind("CertificateRequest"))) - _, err = cmCl.AcmeV1().Orders(req.Namespace).Update(ctx, order, metav1.UpdateOptions{}) - if err != nil { - return fmt.Errorf("Update Err: %v", err) - } - - return nil -} - -func createChallengesOwnedByOrder(cmCl versioned.Interface, ctx context.Context, - order *cmacme.Order, challenges []*cmacme.Challenge) error { - - for _, c := range challenges { - challenge, err := cmCl.AcmeV1().Challenges(order.Namespace).Create(ctx, c, metav1.CreateOptions{}) - if err != nil { - return err - } - - challenge.OwnerReferences = append(challenge.OwnerReferences, *metav1.NewControllerRef(order, cmacme.SchemeGroupVersion.WithKind("Order"))) - _, err = cmCl.AcmeV1().Challenges(order.Namespace).Update(ctx, challenge, metav1.UpdateOptions{}) - if err != nil { - return fmt.Errorf("Update Err: %v", err) - } - } - - return nil -} - -func createEventsOwnedByRef(kubernetesCl kubernetes.Interface, ctx context.Context, - eventList *corev1.EventList, objRef *corev1.ObjectReference, ns string) error { - for _, event := range eventList.Items { - event.InvolvedObject = *objRef - _, err := kubernetesCl.CoreV1().Events(ns).Create(ctx, &event, metav1.CreateOptions{}) - if err != nil { - return fmt.Errorf(err.Error()) - } - } - return nil -} - -func validateOutputTimes(output string, expectedNotAfter time.Time) error { - for _, line := range strings.Split(output, "\n") { - rawParts := strings.Split(strings.TrimSpace(line), ":") - - if len(rawParts) == 1 { - continue - } - - partType := strings.ToLower(rawParts[0]) - rest := strings.TrimSpace(strings.Join(rawParts[1:], ":")) - - if partType == "created at" { - _, err := time.Parse(time.RFC3339, rest) - if err != nil { - return fmt.Errorf("couldn't parse 'created at' as an RFC3339 timestamp: %s", err) - } - } else if partType == "not after" { - notAfter, err := time.Parse(time.RFC3339, rest) - if err != nil { - return fmt.Errorf("couldn't parse 'not after' as an RFC3339 timestamp: %s", err) - } - - if !notAfter.Equal(expectedNotAfter) { - return fmt.Errorf("got unexpected 'not after' (note that time zone differences could be a red herring) - wanted %q but got %q", expectedNotAfter.Format(time.RFC3339), notAfter.Format(time.RFC3339)) - } - } - } - - return nil -} diff --git a/test/integration/ctl/install_framework/framework.go b/test/integration/ctl/install_framework/framework.go deleted file mode 100644 index d2216ca181b..00000000000 --- a/test/integration/ctl/install_framework/framework.go +++ /dev/null @@ -1,108 +0,0 @@ -/* -Copyright 2021 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. -*/ - -package install_framework - -import ( - "os" - "testing" - - "k8s.io/client-go/kubernetes" - "k8s.io/client-go/rest" - "sigs.k8s.io/controller-runtime/pkg/envtest" - - "github.com/cert-manager/cert-manager/test/internal/apiserver" -) - -type TestInstallApiServer struct { - environment *envtest.Environment - testUser *envtest.AuthenticatedUser - - kubeClient kubernetes.Interface - - kubeConfig string -} - -type CleanupFunction func() - -func NewTestInstallApiServer(t *testing.T) (*TestInstallApiServer, CleanupFunction) { - env, stopFn := apiserver.RunBareControlPlane(t) - - testUser, err := env.ControlPlane.AddUser( - envtest.User{ - Name: "test", - Groups: []string{"system:masters"}, - }, - &rest.Config{ - // gotta go fast during tests -- we don't really care about overwhelming our test API server - QPS: 1000.0, - Burst: 2000.0, - }, - ) - if err != nil { - t.Error(err) - } - - kubeConfig, removeFile := createKubeConfigFile(t, testUser) - - kubeClientset, err := kubernetes.NewForConfig(env.Config) - if err != nil { - t.Error(err) - } - - return &TestInstallApiServer{ - environment: env, - testUser: testUser, - - kubeClient: kubeClientset, - - kubeConfig: kubeConfig, - }, func() { - defer removeFile() - stopFn() - } -} - -func createKubeConfigFile(t *testing.T, user *envtest.AuthenticatedUser) (string, CleanupFunction) { - tmpfile, err := os.CreateTemp("", "config") - if err != nil { - t.Fatal(err) - } - path := tmpfile.Name() - - contents, err := user.KubeConfig() - if err != nil { - os.Remove(path) - t.Fatal(err) - } - if _, err := tmpfile.Write(contents); err != nil { - tmpfile.Close() - os.Remove(path) - t.Fatal(err) - } - if err := tmpfile.Close(); err != nil { - os.Remove(path) - t.Fatal(err) - } - - return path, func() { - os.Remove(path) - } -} - -func (s *TestInstallApiServer) KubeConfig() string { - return s.kubeConfig -} diff --git a/test/integration/ctl/migrate/ctl_upgrade_migrate_test.go b/test/integration/ctl/migrate/ctl_upgrade_migrate_test.go deleted file mode 100644 index f8dd3dbf4ef..00000000000 --- a/test/integration/ctl/migrate/ctl_upgrade_migrate_test.go +++ /dev/null @@ -1,384 +0,0 @@ -/* -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. -*/ - -package migrate - -import ( - "context" - "os" - "testing" - "time" - - testlogger "github.com/go-logr/logr/testing" - apiextinstall "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/install" - apiext "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/runtime/schema" - "sigs.k8s.io/controller-runtime/pkg/client" - - "github.com/cert-manager/cert-manager/cmd/ctl/pkg/upgrade/migrateapiversion" - "github.com/cert-manager/cert-manager/pkg/webhook/handlers" - "github.com/cert-manager/cert-manager/pkg/webhook/handlers/testdata/apis/testgroup/install" - v1 "github.com/cert-manager/cert-manager/pkg/webhook/handlers/testdata/apis/testgroup/v1" - v2 "github.com/cert-manager/cert-manager/pkg/webhook/handlers/testdata/apis/testgroup/v2" - "github.com/cert-manager/cert-manager/test/integration/framework" -) - -// Create a test resource at a given version. -func newResourceAtVersion(t *testing.T, version string) client.Object { - switch version { - case "v1": - return &v1.TestType{ - ObjectMeta: metav1.ObjectMeta{ - Name: "object", - Namespace: "default", - }, - TestField: "abc", - TestFieldImmutable: "def", - } - case "v2": - return &v2.TestType{ - ObjectMeta: metav1.ObjectMeta{ - Name: "object", - Namespace: "default", - }, - TestField: "abc", - TestFieldImmutable: "def", - } - default: - t.Fatalf("unknown version %q", version) - } - return nil -} - -func newScheme() *runtime.Scheme { - scheme := runtime.NewScheme() - apiextinstall.Install(scheme) - install.Install(scheme) - return scheme -} - -func TestCtlUpgradeMigrate(t *testing.T) { - ctx, cancel := context.WithTimeout(context.Background(), time.Second*30) - defer cancel() - - // Create the control plane with the TestType conversion handlers registered - scheme := newScheme() - // name of the testtype CRD resource - crdName := "testtypes.testgroup.testing.cert-manager.io" - restCfg, stop := framework.RunControlPlane(t, context.Background(), - framework.WithCRDDirectory("../../../../pkg/webhook/handlers/testdata/apis/testgroup/crds"), - framework.WithWebhookConversionHandler(handlers.NewSchemeBackedConverter(testlogger.NewTestLogger(t), scheme))) - defer stop() - - // Ensure the OpenAPI endpoint has been updated with the TestType CRD - framework.WaitForOpenAPIResourcesToBeLoaded(t, ctx, restCfg, schema.GroupVersionKind{ - Group: "testgroup.testing.cert-manager.io", - Version: "v1", - Kind: "TestType", - }) - - // Create an API client - cl, err := client.New(restCfg, client.Options{Scheme: scheme}) - if err != nil { - t.Fatal(err) - } - - // Fetch a copy of the recently created TestType CRD - crd := &apiext.CustomResourceDefinition{} - if err := cl.Get(ctx, client.ObjectKey{Name: crdName}, crd); err != nil { - t.Fatal(err) - } - - // Identify the current storage version and one non-storage version for this CRD. - // We'll be creating objects and then changing the storage version on the CRD to - // the 'nonStorageVersion' and ensuring the migration/upgrade is successful. - storageVersion, nonStorageVersion := versionsForCRD(crd) - if storageVersion == "" || nonStorageVersion == "" { - t.Fatal("this test requires testdata with both a storage and non-storage version set") - } - - // Ensure the original storage version is the only one on the CRD - if len(crd.Status.StoredVersions) != 1 || crd.Status.StoredVersions[0] != storageVersion { - t.Errorf("Expected status.storedVersions to only contain the storage version %q but it was: %v", storageVersion, crd.Status.StoredVersions) - } - - // Create a resource - obj := newResourceAtVersion(t, storageVersion) - if err := cl.Create(ctx, obj); err != nil { - t.Errorf("Failed to create test resource: %v", err) - } - - // Set the storage version to the 'nonStorageVersion' - setStorageVersion(crd, nonStorageVersion) - if err := cl.Update(ctx, crd); err != nil { - t.Fatalf("Failed to update CRD storage version: %v", err) - } - if len(crd.Status.StoredVersions) != 2 || crd.Status.StoredVersions[0] != storageVersion || crd.Status.StoredVersions[1] != nonStorageVersion { - t.Fatalf("Expected status.storedVersions to contain [%s, %s] but it was: %v", storageVersion, nonStorageVersion, crd.Status.StoredVersions) - } - - // Run the migrator and migrate all objects to the 'nonStorageVersion' (which is now the new storage version) - migrator := migrateapiversion.NewMigrator(cl, false, os.Stdout, os.Stderr) - migrated, err := migrator.Run(ctx, nonStorageVersion, []string{crdName}) - if err != nil { - t.Errorf("migrator failed to run: %v", err) - } - if !migrated { - t.Errorf("migrator didn't actually perform a migration") - } - - // Check the status.storedVersions field to ensure it only contains one element - crd = &apiext.CustomResourceDefinition{} - if err := cl.Get(ctx, client.ObjectKey{Name: crdName}, crd); err != nil { - t.Fatal(err) - } - if len(crd.Status.StoredVersions) != 1 || crd.Status.StoredVersions[0] != nonStorageVersion { - t.Fatalf("Expected status.storedVersions to be %q but it was: %v", nonStorageVersion, crd.Status.StoredVersions) - } - - // Remove the previous storage version from the CRD and update it - removeAPIVersion(crd, storageVersion) - if err := cl.Update(ctx, crd); err != nil { - t.Fatalf("Failed to remove old API version: %v", err) - } - - // Attempt to read a resource list in the new API version - objList := &unstructured.UnstructuredList{} - objList.SetGroupVersionKind(schema.GroupVersionKind{ - Group: crd.Spec.Group, - Version: nonStorageVersion, - Kind: crd.Spec.Names.ListKind, - }) - if err := cl.List(ctx, objList); err != nil { - t.Fatalf("Failed to list objects (gvk %v): %v", objList.GroupVersionKind(), err) - } - if len(objList.Items) != 1 { - t.Fatalf("Expected a single TestType resource to exist") - } -} - -func TestCtlUpgradeMigrate_FailsIfStorageVersionDoesNotEqualTargetVersion(t *testing.T) { - ctx, cancel := context.WithTimeout(context.Background(), time.Second*30) - defer cancel() - - // Create the control plane with the TestType conversion handlers registered - scheme := newScheme() - // name of the testtype CRD resource - crdName := "testtypes.testgroup.testing.cert-manager.io" - restCfg, stop := framework.RunControlPlane(t, context.Background(), - framework.WithCRDDirectory("../../../../pkg/webhook/handlers/testdata/apis/testgroup/crds"), - framework.WithWebhookConversionHandler(handlers.NewSchemeBackedConverter(testlogger.NewTestLogger(t), scheme))) - defer stop() - - // Ensure the OpenAPI endpoint has been updated with the TestType CRD - framework.WaitForOpenAPIResourcesToBeLoaded(t, ctx, restCfg, schema.GroupVersionKind{ - Group: "testgroup.testing.cert-manager.io", - Version: "v1", - Kind: "TestType", - }) - - // Create an API client - cl, err := client.New(restCfg, client.Options{Scheme: scheme}) - if err != nil { - t.Fatal(err) - } - - // Fetch a copy of the recently created TestType CRD - crd := &apiext.CustomResourceDefinition{} - if err := cl.Get(ctx, client.ObjectKey{Name: crdName}, crd); err != nil { - t.Fatal(err) - } - - // Identify the current storage version and one non-storage version for this CRD. - storageVersion, nonStorageVersion := versionsForCRD(crd) - if storageVersion == "" || nonStorageVersion == "" { - t.Fatal("this test requires testdata with both a storage and non-storage version set") - } - - // We expect this to fail, as we are attempting to migrate to the 'nonStorageVersion'. - migrator := migrateapiversion.NewMigrator(cl, false, os.Stdout, os.Stderr) - migrated, err := migrator.Run(ctx, nonStorageVersion, []string{crdName}) - if err == nil { - t.Errorf("expected an error to be returned but we got none") - } - if err.Error() != "preflight checks failed" { - t.Errorf("unexpected error: %v", err) - } - if migrated { - t.Errorf("migrator ran but it should not have") - } -} - -func TestCtlUpgradeMigrate_SkipsMigrationIfNothingToDo(t *testing.T) { - ctx, cancel := context.WithTimeout(context.Background(), time.Second*30) - defer cancel() - - // Create the control plane with the TestType conversion handlers registered - scheme := newScheme() - // name of the testtype CRD resource - crdName := "testtypes.testgroup.testing.cert-manager.io" - restCfg, stop := framework.RunControlPlane(t, context.Background(), - framework.WithCRDDirectory("../../../../pkg/webhook/handlers/testdata/apis/testgroup/crds"), - framework.WithWebhookConversionHandler(handlers.NewSchemeBackedConverter(testlogger.NewTestLogger(t), scheme))) - defer stop() - - // Ensure the OpenAPI endpoint has been updated with the TestType CRD - framework.WaitForOpenAPIResourcesToBeLoaded(t, ctx, restCfg, schema.GroupVersionKind{ - Group: "testgroup.testing.cert-manager.io", - Version: "v1", - Kind: "TestType", - }) - - // Create an API client - cl, err := client.New(restCfg, client.Options{Scheme: scheme}) - if err != nil { - t.Fatal(err) - } - - // Fetch a copy of the recently created TestType CRD - crd := &apiext.CustomResourceDefinition{} - if err := cl.Get(ctx, client.ObjectKey{Name: crdName}, crd); err != nil { - t.Fatal(err) - } - - // Identify the current storage version and one non-storage version for this CRD. - storageVersion, nonStorageVersion := versionsForCRD(crd) - if storageVersion == "" || nonStorageVersion == "" { - t.Fatal("this test requires testdata with both a storage and non-storage version set") - } - - // Ensure the original storage version is the only one on the CRD - if len(crd.Status.StoredVersions) != 1 || crd.Status.StoredVersions[0] != storageVersion { - t.Errorf("Expected status.storedVersions to only contain the storage version %q but it was: %v", storageVersion, crd.Status.StoredVersions) - } - - // Create a resource - obj := newResourceAtVersion(t, storageVersion) - if err := cl.Create(ctx, obj); err != nil { - t.Errorf("Failed to create test resource: %v", err) - } - - // We expect this to succeed and for the migration to not be run - migrator := migrateapiversion.NewMigrator(cl, false, os.Stdout, os.Stderr) - migrated, err := migrator.Run(ctx, storageVersion, []string{crdName}) - if err != nil { - t.Errorf("unexpected error: %v", err) - } - if migrated { - t.Errorf("migrator ran but it should not have") - } -} - -func TestCtlUpgradeMigrate_ForcesMigrationIfSkipStoredVersionCheckIsEnabled(t *testing.T) { - ctx, cancel := context.WithTimeout(context.Background(), time.Second*30) - defer cancel() - - // Create the control plane with the TestType conversion handlers registered - scheme := newScheme() - // name of the testtype CRD resource - crdName := "testtypes.testgroup.testing.cert-manager.io" - restCfg, stop := framework.RunControlPlane(t, context.Background(), - framework.WithCRDDirectory("../../../../pkg/webhook/handlers/testdata/apis/testgroup/crds"), - framework.WithWebhookConversionHandler(handlers.NewSchemeBackedConverter(testlogger.NewTestLogger(t), scheme))) - defer stop() - - // Ensure the OpenAPI endpoint has been updated with the TestType CRD - framework.WaitForOpenAPIResourcesToBeLoaded(t, ctx, restCfg, schema.GroupVersionKind{ - Group: "testgroup.testing.cert-manager.io", - Version: "v1", - Kind: "TestType", - }) - - // Create an API client - cl, err := client.New(restCfg, client.Options{Scheme: scheme}) - if err != nil { - t.Fatal(err) - } - - // Fetch a copy of the recently created TestType CRD - crd := &apiext.CustomResourceDefinition{} - if err := cl.Get(ctx, client.ObjectKey{Name: crdName}, crd); err != nil { - t.Fatal(err) - } - - // Identify the current storage version and one non-storage version for this CRD. - storageVersion, nonStorageVersion := versionsForCRD(crd) - if storageVersion == "" || nonStorageVersion == "" { - t.Fatal("this test requires testdata with both a storage and non-storage version set") - } - - // Ensure the original storage version is the only one on the CRD - if len(crd.Status.StoredVersions) != 1 || crd.Status.StoredVersions[0] != storageVersion { - t.Errorf("Expected status.storedVersions to only contain the storage version %q but it was: %v", storageVersion, crd.Status.StoredVersions) - } - - // Create a resource - obj := newResourceAtVersion(t, storageVersion) - if err := cl.Create(ctx, obj); err != nil { - t.Errorf("Failed to create test resource: %v", err) - } - - // We expect this to force a migration - migrator := migrateapiversion.NewMigrator(cl, true, os.Stdout, os.Stderr) - migrated, err := migrator.Run(ctx, storageVersion, []string{crdName}) - if err != nil { - t.Errorf("unexpected error: %v", err) - } - if !migrated { - t.Errorf("expected migrator to run due to skip flag being set") - } -} - -func versionsForCRD(crd *apiext.CustomResourceDefinition) (storage, nonstorage string) { - storageVersion := "" - nonStorageVersion := "" - for _, v := range crd.Spec.Versions { - if v.Storage { - storageVersion = v.Name - } else { - nonStorageVersion = v.Name - } - if storageVersion != "" && nonStorageVersion != "" { - break - } - } - - return storageVersion, nonStorageVersion -} - -func setStorageVersion(crd *apiext.CustomResourceDefinition, newStorageVersion string) { - for i, v := range crd.Spec.Versions { - if v.Name == newStorageVersion { - v.Storage = true - } else if v.Storage { - v.Storage = false - } - crd.Spec.Versions[i] = v - } -} - -func removeAPIVersion(crd *apiext.CustomResourceDefinition, version string) { - var newVersions []apiext.CustomResourceDefinitionVersion - for _, v := range crd.Spec.Versions { - if v.Name != version { - newVersions = append(newVersions, v) - } - } - crd.Spec.Versions = newVersions -} diff --git a/test/integration/ctl/testdata/convert/input/resource1.yaml b/test/integration/ctl/testdata/convert/input/resource1.yaml deleted file mode 100644 index 9b8d488c327..00000000000 --- a/test/integration/ctl/testdata/convert/input/resource1.yaml +++ /dev/null @@ -1,13 +0,0 @@ -apiVersion: cert-manager.io/v1alpha2 -kind: Certificate -metadata: - name: ca-issuer - namespace: sandbox -spec: - isCA: true - secretName: ca-key-pair - commonName: my-csi-app - issuerRef: - name: selfsigned-issuer - kind: Issuer - group: cert-manager.io diff --git a/test/integration/ctl/testdata/convert/input/resource2.yaml b/test/integration/ctl/testdata/convert/input/resource2.yaml deleted file mode 100644 index f24b51caeaf..00000000000 --- a/test/integration/ctl/testdata/convert/input/resource2.yaml +++ /dev/null @@ -1,39 +0,0 @@ ---- -# my comment -apiVersion: cert-manager.io/v1alpha2 -kind: Certificate -metadata: - name: ca-issuer - namespace: sandbox -spec: - isCA: true - secretName: ca-key-pair - commonName: my-csi-app - issuerRef: - name: selfsigned-issuer - kind: Issuer - group: cert-manager.io ---- -apiVersion: cert-manager.io/v1alpha2 -kind: Issuer -metadata: - name: ca-issuer - namespace: sandbox -spec: - ca: - secretName: ca-key-pair ---- -apiVersion: cert-manager.io/v1alpha2 -kind: Certificate -metadata: - name: ca-issuer-2 - namespace: sandbox -spec: - isCA: true - secretName: ca-key-pair - commonName: my-csi-app - issuerRef: - name: ca-issuer - kind: Issuer - group: cert-manager.io ---- diff --git a/test/integration/ctl/testdata/convert/input/resource3.yaml b/test/integration/ctl/testdata/convert/input/resource3.yaml deleted file mode 100644 index 8deca6a07ad..00000000000 --- a/test/integration/ctl/testdata/convert/input/resource3.yaml +++ /dev/null @@ -1,12 +0,0 @@ -apiVersion: v1 -kind: Namespace -metadata: - name: sandbox ---- -apiVersion: cert-manager.io/v1alpha2 -kind: Issuer -metadata: - name: selfsigned-issuer - namespace: sandbox -spec: - selfSigned: {} diff --git a/test/integration/ctl/testdata/convert/input/resource_with_organization_v1alpha2.yaml b/test/integration/ctl/testdata/convert/input/resource_with_organization_v1alpha2.yaml deleted file mode 100644 index cf23a6d1b7a..00000000000 --- a/test/integration/ctl/testdata/convert/input/resource_with_organization_v1alpha2.yaml +++ /dev/null @@ -1,15 +0,0 @@ -apiVersion: cert-manager.io/v1alpha2 -kind: Certificate -metadata: - name: ca-issuer - namespace: sandbox -spec: - isCA: true - secretName: ca-key-pair - organization: - - "hello world" - commonName: my-csi-app - issuerRef: - name: selfsigned-issuer - kind: Issuer - group: cert-manager.io diff --git a/test/integration/ctl/testdata/convert/input/resources_as_list_v1alpha2.yaml b/test/integration/ctl/testdata/convert/input/resources_as_list_v1alpha2.yaml deleted file mode 100644 index 93bbcb1bfb2..00000000000 --- a/test/integration/ctl/testdata/convert/input/resources_as_list_v1alpha2.yaml +++ /dev/null @@ -1,35 +0,0 @@ -apiVersion: v1 -kind: List -items: - - apiVersion: cert-manager.io/v1alpha2 - kind: Certificate - metadata: - name: list-test-1 - namespace: default - spec: - dnsNames: - - example.cert-manager.1 - duration: 24h - issuerRef: - name: cert-manager-test-1 - secretName: cert-manager-test-1 - - apiVersion: cert-manager.io/v1alpha2 - kind: Certificate - metadata: - name: list-test-1 - namespace: default - spec: - dnsNames: - - example.cert-manager.2 - duration: 24h - issuerRef: - name: cert-manager-test-2 - secretName: cert-manager-test-2 - - apiVersion: cert-manager.io/v1alpha2 - kind: Issuer - metadata: - name: ca-issuer - namespace: sandbox - spec: - ca: - secretName: ca-key-pair diff --git a/test/integration/ctl/testdata/convert/output/no_output_error.yaml b/test/integration/ctl/testdata/convert/output/no_output_error.yaml deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/test/integration/ctl/testdata/convert/output/resource1_v1.yaml b/test/integration/ctl/testdata/convert/output/resource1_v1.yaml deleted file mode 100644 index 78f17aef34a..00000000000 --- a/test/integration/ctl/testdata/convert/output/resource1_v1.yaml +++ /dev/null @@ -1,15 +0,0 @@ -apiVersion: cert-manager.io/v1 -kind: Certificate -metadata: - creationTimestamp: null - name: ca-issuer - namespace: sandbox -spec: - commonName: my-csi-app - isCA: true - issuerRef: - group: cert-manager.io - kind: Issuer - name: selfsigned-issuer - secretName: ca-key-pair -status: {} diff --git a/test/integration/ctl/testdata/convert/output/resource1_v1alpha2.yaml b/test/integration/ctl/testdata/convert/output/resource1_v1alpha2.yaml deleted file mode 100644 index af3513be841..00000000000 --- a/test/integration/ctl/testdata/convert/output/resource1_v1alpha2.yaml +++ /dev/null @@ -1,15 +0,0 @@ -apiVersion: cert-manager.io/v1alpha2 -kind: Certificate -metadata: - creationTimestamp: null - name: ca-issuer - namespace: sandbox -spec: - commonName: my-csi-app - isCA: true - issuerRef: - group: cert-manager.io - kind: Issuer - name: selfsigned-issuer - secretName: ca-key-pair -status: {} diff --git a/test/integration/ctl/testdata/convert/output/resource1_v1alpha3.yaml b/test/integration/ctl/testdata/convert/output/resource1_v1alpha3.yaml deleted file mode 100644 index b4531896b69..00000000000 --- a/test/integration/ctl/testdata/convert/output/resource1_v1alpha3.yaml +++ /dev/null @@ -1,15 +0,0 @@ -apiVersion: cert-manager.io/v1alpha3 -kind: Certificate -metadata: - creationTimestamp: null - name: ca-issuer - namespace: sandbox -spec: - commonName: my-csi-app - isCA: true - issuerRef: - group: cert-manager.io - kind: Issuer - name: selfsigned-issuer - secretName: ca-key-pair -status: {} diff --git a/test/integration/ctl/testdata/convert/output/resource2_v1.yaml b/test/integration/ctl/testdata/convert/output/resource2_v1.yaml deleted file mode 100644 index 634aa3141e7..00000000000 --- a/test/integration/ctl/testdata/convert/output/resource2_v1.yaml +++ /dev/null @@ -1,44 +0,0 @@ -apiVersion: v1 -items: -- apiVersion: cert-manager.io/v1 - kind: Certificate - metadata: - creationTimestamp: null - name: ca-issuer - namespace: sandbox - spec: - commonName: my-csi-app - isCA: true - issuerRef: - group: cert-manager.io - kind: Issuer - name: selfsigned-issuer - secretName: ca-key-pair - status: {} -- apiVersion: cert-manager.io/v1 - kind: Issuer - metadata: - creationTimestamp: null - name: ca-issuer - namespace: sandbox - spec: - ca: - secretName: ca-key-pair - status: {} -- apiVersion: cert-manager.io/v1 - kind: Certificate - metadata: - creationTimestamp: null - name: ca-issuer-2 - namespace: sandbox - spec: - commonName: my-csi-app - isCA: true - issuerRef: - group: cert-manager.io - kind: Issuer - name: ca-issuer - secretName: ca-key-pair - status: {} -kind: List -metadata: {} diff --git a/test/integration/ctl/testdata/convert/output/resource2_v1alpha2.yaml b/test/integration/ctl/testdata/convert/output/resource2_v1alpha2.yaml deleted file mode 100644 index 434a5f4a474..00000000000 --- a/test/integration/ctl/testdata/convert/output/resource2_v1alpha2.yaml +++ /dev/null @@ -1,44 +0,0 @@ -apiVersion: v1 -items: -- apiVersion: cert-manager.io/v1alpha2 - kind: Certificate - metadata: - creationTimestamp: null - name: ca-issuer - namespace: sandbox - spec: - commonName: my-csi-app - isCA: true - issuerRef: - group: cert-manager.io - kind: Issuer - name: selfsigned-issuer - secretName: ca-key-pair - status: {} -- apiVersion: cert-manager.io/v1alpha2 - kind: Issuer - metadata: - creationTimestamp: null - name: ca-issuer - namespace: sandbox - spec: - ca: - secretName: ca-key-pair - status: {} -- apiVersion: cert-manager.io/v1alpha2 - kind: Certificate - metadata: - creationTimestamp: null - name: ca-issuer-2 - namespace: sandbox - spec: - commonName: my-csi-app - isCA: true - issuerRef: - group: cert-manager.io - kind: Issuer - name: ca-issuer - secretName: ca-key-pair - status: {} -kind: List -metadata: {} diff --git a/test/integration/ctl/testdata/convert/output/resource2_v1alpha3.yaml b/test/integration/ctl/testdata/convert/output/resource2_v1alpha3.yaml deleted file mode 100644 index fa4661abc80..00000000000 --- a/test/integration/ctl/testdata/convert/output/resource2_v1alpha3.yaml +++ /dev/null @@ -1,44 +0,0 @@ -apiVersion: v1 -items: -- apiVersion: cert-manager.io/v1alpha3 - kind: Certificate - metadata: - creationTimestamp: null - name: ca-issuer - namespace: sandbox - spec: - commonName: my-csi-app - isCA: true - issuerRef: - group: cert-manager.io - kind: Issuer - name: selfsigned-issuer - secretName: ca-key-pair - status: {} -- apiVersion: cert-manager.io/v1alpha3 - kind: Issuer - metadata: - creationTimestamp: null - name: ca-issuer - namespace: sandbox - spec: - ca: - secretName: ca-key-pair - status: {} -- apiVersion: cert-manager.io/v1alpha3 - kind: Certificate - metadata: - creationTimestamp: null - name: ca-issuer-2 - namespace: sandbox - spec: - commonName: my-csi-app - isCA: true - issuerRef: - group: cert-manager.io - kind: Issuer - name: ca-issuer - secretName: ca-key-pair - status: {} -kind: List -metadata: {} diff --git a/test/integration/ctl/testdata/convert/output/resource_with_organization_v1.yaml b/test/integration/ctl/testdata/convert/output/resource_with_organization_v1.yaml deleted file mode 100644 index 73e2453b95d..00000000000 --- a/test/integration/ctl/testdata/convert/output/resource_with_organization_v1.yaml +++ /dev/null @@ -1,18 +0,0 @@ -apiVersion: cert-manager.io/v1 -kind: Certificate -metadata: - creationTimestamp: null - name: ca-issuer - namespace: sandbox -spec: - commonName: my-csi-app - isCA: true - issuerRef: - group: cert-manager.io - kind: Issuer - name: selfsigned-issuer - secretName: ca-key-pair - subject: - organizations: - - hello world -status: {} diff --git a/test/integration/ctl/testdata/convert/output/resource_with_organization_v1alpha3.yaml b/test/integration/ctl/testdata/convert/output/resource_with_organization_v1alpha3.yaml deleted file mode 100644 index 7eed7804027..00000000000 --- a/test/integration/ctl/testdata/convert/output/resource_with_organization_v1alpha3.yaml +++ /dev/null @@ -1,18 +0,0 @@ -apiVersion: cert-manager.io/v1alpha3 -kind: Certificate -metadata: - creationTimestamp: null - name: ca-issuer - namespace: sandbox -spec: - commonName: my-csi-app - isCA: true - issuerRef: - group: cert-manager.io - kind: Issuer - name: selfsigned-issuer - secretName: ca-key-pair - subject: - organizations: - - hello world -status: {} diff --git a/test/integration/ctl/testdata/convert/output/resource_with_organization_v1beta1.yaml b/test/integration/ctl/testdata/convert/output/resource_with_organization_v1beta1.yaml deleted file mode 100644 index 475f85a845e..00000000000 --- a/test/integration/ctl/testdata/convert/output/resource_with_organization_v1beta1.yaml +++ /dev/null @@ -1,18 +0,0 @@ -apiVersion: cert-manager.io/v1beta1 -kind: Certificate -metadata: - creationTimestamp: null - name: ca-issuer - namespace: sandbox -spec: - commonName: my-csi-app - isCA: true - issuerRef: - group: cert-manager.io - kind: Issuer - name: selfsigned-issuer - secretName: ca-key-pair - subject: - organizations: - - hello world -status: {} diff --git a/test/integration/ctl/testdata/convert/output/resources_as_list_v1.yaml b/test/integration/ctl/testdata/convert/output/resources_as_list_v1.yaml deleted file mode 100644 index 99fbbbb5649..00000000000 --- a/test/integration/ctl/testdata/convert/output/resources_as_list_v1.yaml +++ /dev/null @@ -1,42 +0,0 @@ -apiVersion: v1 -items: -- apiVersion: cert-manager.io/v1 - kind: Certificate - metadata: - creationTimestamp: null - name: list-test-1 - namespace: default - spec: - dnsNames: - - example.cert-manager.1 - duration: 24h0m0s - issuerRef: - name: cert-manager-test-1 - secretName: cert-manager-test-1 - status: {} -- apiVersion: cert-manager.io/v1 - kind: Certificate - metadata: - creationTimestamp: null - name: list-test-1 - namespace: default - spec: - dnsNames: - - example.cert-manager.2 - duration: 24h0m0s - issuerRef: - name: cert-manager-test-2 - secretName: cert-manager-test-2 - status: {} -- apiVersion: cert-manager.io/v1 - kind: Issuer - metadata: - creationTimestamp: null - name: ca-issuer - namespace: sandbox - spec: - ca: - secretName: ca-key-pair - status: {} -kind: List -metadata: {} diff --git a/test/integration/ctl/testdata/convert/output/resources_as_list_v1alpha2.yaml b/test/integration/ctl/testdata/convert/output/resources_as_list_v1alpha2.yaml deleted file mode 100644 index 20959842ea6..00000000000 --- a/test/integration/ctl/testdata/convert/output/resources_as_list_v1alpha2.yaml +++ /dev/null @@ -1,42 +0,0 @@ -apiVersion: v1 -items: -- apiVersion: cert-manager.io/v1alpha2 - kind: Certificate - metadata: - creationTimestamp: null - name: list-test-1 - namespace: default - spec: - dnsNames: - - example.cert-manager.1 - duration: 24h0m0s - issuerRef: - name: cert-manager-test-1 - secretName: cert-manager-test-1 - status: {} -- apiVersion: cert-manager.io/v1alpha2 - kind: Certificate - metadata: - creationTimestamp: null - name: list-test-1 - namespace: default - spec: - dnsNames: - - example.cert-manager.2 - duration: 24h0m0s - issuerRef: - name: cert-manager-test-2 - secretName: cert-manager-test-2 - status: {} -- apiVersion: cert-manager.io/v1alpha2 - kind: Issuer - metadata: - creationTimestamp: null - name: ca-issuer - namespace: sandbox - spec: - ca: - secretName: ca-key-pair - status: {} -kind: List -metadata: {} diff --git a/test/integration/ctl/testdata/convert/output/resources_as_list_v1alpha3.yaml b/test/integration/ctl/testdata/convert/output/resources_as_list_v1alpha3.yaml deleted file mode 100644 index 773bed9eb65..00000000000 --- a/test/integration/ctl/testdata/convert/output/resources_as_list_v1alpha3.yaml +++ /dev/null @@ -1,42 +0,0 @@ -apiVersion: v1 -items: -- apiVersion: cert-manager.io/v1alpha3 - kind: Certificate - metadata: - creationTimestamp: null - name: list-test-1 - namespace: default - spec: - dnsNames: - - example.cert-manager.1 - duration: 24h0m0s - issuerRef: - name: cert-manager-test-1 - secretName: cert-manager-test-1 - status: {} -- apiVersion: cert-manager.io/v1alpha3 - kind: Certificate - metadata: - creationTimestamp: null - name: list-test-1 - namespace: default - spec: - dnsNames: - - example.cert-manager.2 - duration: 24h0m0s - issuerRef: - name: cert-manager-test-2 - secretName: cert-manager-test-2 - status: {} -- apiVersion: cert-manager.io/v1alpha3 - kind: Issuer - metadata: - creationTimestamp: null - name: ca-issuer - namespace: sandbox - spec: - ca: - secretName: ca-key-pair - status: {} -kind: List -metadata: {} diff --git a/test/integration/ctl/testdata/convert/output/resources_as_list_v1beta1.yaml b/test/integration/ctl/testdata/convert/output/resources_as_list_v1beta1.yaml deleted file mode 100644 index c9ad3ac1ade..00000000000 --- a/test/integration/ctl/testdata/convert/output/resources_as_list_v1beta1.yaml +++ /dev/null @@ -1,42 +0,0 @@ -apiVersion: v1 -items: -- apiVersion: cert-manager.io/v1beta1 - kind: Certificate - metadata: - creationTimestamp: null - name: list-test-1 - namespace: default - spec: - dnsNames: - - example.cert-manager.1 - duration: 24h0m0s - issuerRef: - name: cert-manager-test-1 - secretName: cert-manager-test-1 - status: {} -- apiVersion: cert-manager.io/v1beta1 - kind: Certificate - metadata: - creationTimestamp: null - name: list-test-1 - namespace: default - spec: - dnsNames: - - example.cert-manager.2 - duration: 24h0m0s - issuerRef: - name: cert-manager-test-2 - secretName: cert-manager-test-2 - status: {} -- apiVersion: cert-manager.io/v1beta1 - kind: Issuer - metadata: - creationTimestamp: null - name: ca-issuer - namespace: sandbox - spec: - ca: - secretName: ca-key-pair - status: {} -kind: List -metadata: {} diff --git a/test/integration/ctl/testdata/create_cr_cert_with_ns1.yaml b/test/integration/ctl/testdata/create_cr_cert_with_ns1.yaml deleted file mode 100644 index ed491859ca6..00000000000 --- a/test/integration/ctl/testdata/create_cr_cert_with_ns1.yaml +++ /dev/null @@ -1,14 +0,0 @@ ---- -apiVersion: cert-manager.io/v1alpha2 -kind: Certificate -metadata: - name: testcert-1 - namespace: testns-1 -spec: - isCA: true - secretName: ca-key-pair - commonName: my-csi-app - issuerRef: - name: selfsigned-issuer - kind: Issuer - group: cert-manager.io diff --git a/test/integration/ctl/testdata/create_cr_issuer.yaml b/test/integration/ctl/testdata/create_cr_issuer.yaml deleted file mode 100644 index 6443dd329ce..00000000000 --- a/test/integration/ctl/testdata/create_cr_issuer.yaml +++ /dev/null @@ -1,8 +0,0 @@ -apiVersion: cert-manager.io/v1alpha2 -kind: Issuer -metadata: - name: ca-issuer - namespace: testns-1 -spec: - ca: - secretName: ca-key-pair diff --git a/test/integration/ctl/testdata/create_cr_v1alpha3_cert_with_ns1.yaml b/test/integration/ctl/testdata/create_cr_v1alpha3_cert_with_ns1.yaml deleted file mode 100644 index f5a3d72b1cf..00000000000 --- a/test/integration/ctl/testdata/create_cr_v1alpha3_cert_with_ns1.yaml +++ /dev/null @@ -1,17 +0,0 @@ ---- -apiVersion: cert-manager.io/v1alpha3 -kind: Certificate -metadata: - name: testcert-v1alpha3 - namespace: testns-1 -spec: - isCA: true - secretName: ca-key-pair - commonName: my-csi-app - issuerRef: - name: selfsigned-issuer - kind: Issuer - group: cert-manager.io - subject: - organizations: - - hello world diff --git a/test/integration/framework/apiserver.go b/test/integration/framework/apiserver.go index d155971866c..32015714c99 100644 --- a/test/integration/framework/apiserver.go +++ b/test/integration/framework/apiserver.go @@ -19,7 +19,6 @@ package framework import ( "context" "fmt" - "io/ioutil" "os" "path/filepath" "strings" @@ -34,26 +33,25 @@ import ( jsonserializer "k8s.io/apimachinery/pkg/runtime/serializer/json" "k8s.io/apimachinery/pkg/runtime/serializer/versioning" utilruntime "k8s.io/apimachinery/pkg/util/runtime" + kscheme "k8s.io/client-go/kubernetes/scheme" "k8s.io/client-go/rest" - "k8s.io/utils/pointer" + "k8s.io/utils/ptr" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/envtest" - webhooktesting "github.com/cert-manager/cert-manager/cmd/webhook/app/testing" "github.com/cert-manager/cert-manager/internal/test/paths" - "github.com/cert-manager/cert-manager/internal/webhook" - "github.com/cert-manager/cert-manager/pkg/api" - "github.com/cert-manager/cert-manager/pkg/webhook/handlers" - "github.com/cert-manager/cert-manager/test/internal/apiserver" + "github.com/cert-manager/cert-manager/test/apiserver" + webhooktesting "github.com/cert-manager/cert-manager/test/webhook" ) +// NOTE: all functions that return a StopFunc should use +// context.WithCancel(t.Context()) instead of just t.Context() type StopFunc func() // controlPlaneOptions has parameters for the control plane of the integration // test framework which can be overridden in tests. type controlPlaneOptions struct { - crdsDir *string - webhookConversionHandler handlers.ConversionHook + crdsDir *string } type RunControlPlaneOption func(*controlPlaneOptions) @@ -62,33 +60,29 @@ type RunControlPlaneOption func(*controlPlaneOptions) // server in tests. func WithCRDDirectory(directory string) RunControlPlaneOption { return func(o *controlPlaneOptions) { - o.crdsDir = pointer.StringPtr(directory) + o.crdsDir = ptr.To(directory) } } -// WithWebhookConversionHandler allows the webhook handler for the `/convert` -// endpoint to be overridden in tests. -func WithWebhookConversionHandler(handler handlers.ConversionHook) RunControlPlaneOption { - return func(o *controlPlaneOptions) { - o.webhookConversionHandler = handler - } -} +func RunControlPlane(t *testing.T, optionFunctions ...RunControlPlaneOption) (*rest.Config, StopFunc) { + // Making sure the rootCtx is canceled when StopFunc is called + // even when t.Context() has not been canceled yet. + stoppableCtx, stopCtxFn := context.WithCancel(t.Context()) -func RunControlPlane(t *testing.T, ctx context.Context, optionFunctions ...RunControlPlaneOption) (*rest.Config, StopFunc) { crdDirectoryPath, err := paths.CRDDirectory() if err != nil { t.Fatal(err) } options := &controlPlaneOptions{ - crdsDir: pointer.StringPtr(crdDirectoryPath), + crdsDir: ptr.To(crdDirectoryPath), } for _, f := range optionFunctions { f(options) } - env, stopControlPlane := apiserver.RunBareControlPlane(t) + env, stopControlPlaneFn := apiserver.RunBareControlPlane(t) testuser, err := env.ControlPlane.AddUser(envtest.User{Name: "test-user", Groups: []string{"cluster-admin"}}, env.Config) if err != nil { t.Fatal(err) @@ -99,7 +93,7 @@ func RunControlPlane(t *testing.T, ctx context.Context, optionFunctions ...RunCo t.Fatal(err) } - f, err := ioutil.TempFile("", "integration-") + f, err := os.CreateTemp(t.TempDir(), "integration-") if err != nil { t.Fatal(err) } @@ -112,15 +106,16 @@ func RunControlPlane(t *testing.T, ctx context.Context, optionFunctions ...RunCo } webhookOpts, stopWebhook := webhooktesting.StartWebhookServer( - t, ctx, []string{"--kubeconfig", f.Name()}, - webhook.WithConversionHandler(options.webhookConversionHandler), + // Disable the metrics server to avoid multiple webhook servers + // attempting to listen on metrics port 9402 when tests are running in + // parallel. + t, []string{"--kubeconfig", f.Name(), "--metrics-listen-address=0"}, ) crds := readCustomResourcesAtPath(t, *options.crdsDir) for _, crd := range crds { t.Logf("Found CRD with name %q", crd.Name) } - patchCRDConversion(crds, webhookOpts.URL, webhookOpts.CAPEM) if _, err := envtest.InstallCRDs(env.Config, envtest.CRDInstallOptions{ CRDs: crds, @@ -128,26 +123,27 @@ func RunControlPlane(t *testing.T, ctx context.Context, optionFunctions ...RunCo t.Fatal(err) } - cl, err := client.New(env.Config, client.Options{Scheme: api.Scheme}) + cl, err := client.New(env.Config, client.Options{Scheme: kscheme.Scheme}) if err != nil { t.Fatal(err) } - // installing the validating webhooks, not using WebhookInstallOptions as it patches the CA to be it's own - err = cl.Create(ctx, getValidatingWebhookConfig(webhookOpts.URL, webhookOpts.CAPEM)) + // installing the validating webhooks, not using WebhookInstallOptions as it patches the CA to be its own + err = cl.Create(stoppableCtx, getValidatingWebhookConfig(webhookOpts.URL, webhookOpts.CAPEM)) if err != nil { t.Fatal(err) } - // installing the mutating webhooks, not using WebhookInstallOptions as it patches the CA to be it's own - err = cl.Create(ctx, getMutatingWebhookConfig(webhookOpts.URL, webhookOpts.CAPEM)) + // installing the mutating webhooks, not using WebhookInstallOptions as it patches the CA to be its own + err = cl.Create(stoppableCtx, getMutatingWebhookConfig(webhookOpts.URL, webhookOpts.CAPEM)) if err != nil { t.Fatal(err) } return env.Config, func() { - defer stopWebhook() - stopControlPlane() + stopCtxFn() + stopControlPlaneFn() + stopWebhook() } } @@ -160,25 +156,6 @@ func init() { apiextensionsinstall.Install(internalScheme) } -// patchCRDConversion overrides the conversion configuration of the CRDs that -// are loaded in to the integration test API server, -// configuring the conversion to be handled by the local webhook server. -func patchCRDConversion(crds []*apiextensionsv1.CustomResourceDefinition, url string, caPEM []byte) { - for i := range crds { - url := fmt.Sprintf("%s%s", url, "/convert") - crds[i].Spec.Conversion = &apiextensionsv1.CustomResourceConversion{ - Strategy: apiextensionsv1.WebhookConverter, - Webhook: &apiextensionsv1.WebhookConversion{ - ClientConfig: &apiextensionsv1.WebhookClientConfig{ - URL: &url, - CABundle: caPEM, - }, - ConversionReviewVersions: []string{"v1"}, - }, - } - } -} - func readCustomResourcesAtPath(t *testing.T, path string) []*apiextensionsv1.CustomResourceDefinition { serializer := jsonserializer.NewSerializerWithOptions(jsonserializer.DefaultMetaFactory, internalScheme, internalScheme, jsonserializer.SerializerOptions{ Yaml: true, diff --git a/test/integration/framework/helpers.go b/test/integration/framework/helpers.go index 19227687083..dbcebe23181 100644 --- a/test/integration/framework/helpers.go +++ b/test/integration/framework/helpers.go @@ -23,67 +23,86 @@ import ( "golang.org/x/sync/errgroup" corev1 "k8s.io/api/core/v1" + apiext "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" + "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" + utilruntime "k8s.io/apimachinery/pkg/util/runtime" "k8s.io/apimachinery/pkg/util/wait" "k8s.io/client-go/discovery" - "k8s.io/client-go/informers" "k8s.io/client-go/kubernetes" - "k8s.io/client-go/kubernetes/scheme" + kscheme "k8s.io/client-go/kubernetes/scheme" "k8s.io/client-go/rest" "k8s.io/client-go/tools/record" + apireg "k8s.io/kube-aggregator/pkg/apis/apiregistration/v1" "k8s.io/kubectl/pkg/util/openapi" + gwapi "sigs.k8s.io/gateway-api/apis/v1" + internalinformers "github.com/cert-manager/cert-manager/internal/informers" cmclient "github.com/cert-manager/cert-manager/pkg/client/clientset/versioned" + certmgrscheme "github.com/cert-manager/cert-manager/pkg/client/clientset/versioned/scheme" cminformers "github.com/cert-manager/cert-manager/pkg/client/informers/externalversions" controllerpkg "github.com/cert-manager/cert-manager/pkg/controller" ) -func NewEventRecorder(t *testing.T) record.EventRecorder { +func NewEventRecorder(t *testing.T, scheme *runtime.Scheme) record.EventRecorder { eventBroadcaster := record.NewBroadcaster() eventBroadcaster.StartLogging(t.Logf) - return eventBroadcaster.NewRecorder(scheme.Scheme, corev1.EventSource{Component: t.Name()}) + return eventBroadcaster.NewRecorder(scheme, corev1.EventSource{Component: t.Name()}) } -func NewClients(t *testing.T, config *rest.Config) (kubernetes.Interface, informers.SharedInformerFactory, cmclient.Interface, cminformers.SharedInformerFactory) { - cl, err := kubernetes.NewForConfig(config) +func NewClients(t *testing.T, config *rest.Config) (kubernetes.Interface, internalinformers.KubeInformerFactory, cmclient.Interface, cminformers.SharedInformerFactory, *runtime.Scheme) { + httpClient, err := rest.HTTPClientFor(config) if err != nil { t.Fatal(err) } - factory := informers.NewSharedInformerFactory(cl, 0) - cmCl, err := cmclient.NewForConfig(config) + + cl, err := kubernetes.NewForConfigAndClient(config, httpClient) + if err != nil { + t.Fatal(err) + } + factory := internalinformers.NewBaseKubeInformerFactory(cl, 0, "") + + cmCl, err := cmclient.NewForConfigAndClient(config, httpClient) if err != nil { t.Fatal(err) } cmFactory := cminformers.NewSharedInformerFactory(cmCl, 0) - return cl, factory, cmCl, cmFactory + + scheme := runtime.NewScheme() + utilruntime.Must(kscheme.AddToScheme(scheme)) + utilruntime.Must(certmgrscheme.AddToScheme(scheme)) + utilruntime.Must(apiext.AddToScheme(scheme)) + utilruntime.Must(apireg.AddToScheme(scheme)) + utilruntime.Must(gwapi.Install(scheme)) + + return cl, factory, cmCl, cmFactory, scheme } -func StartInformersAndController(t *testing.T, factory informers.SharedInformerFactory, cmFactory cminformers.SharedInformerFactory, c controllerpkg.Interface) StopFunc { +func StartInformersAndController(t *testing.T, factory internalinformers.KubeInformerFactory, cmFactory cminformers.SharedInformerFactory, c controllerpkg.Interface) StopFunc { return StartInformersAndControllers(t, factory, cmFactory, c) } -func StartInformersAndControllers(t *testing.T, factory informers.SharedInformerFactory, cmFactory cminformers.SharedInformerFactory, cs ...controllerpkg.Interface) StopFunc { - stopCh := make(chan struct{}) - errCh := make(chan error) +func StartInformersAndControllers(t *testing.T, factory internalinformers.KubeInformerFactory, cmFactory cminformers.SharedInformerFactory, cs ...controllerpkg.Interface) StopFunc { + // Making sure the rootCtx is canceled when StopFunc is called + // even when t.Context() has not been canceled yet. + stoppableCtx, stopCtxFn := context.WithCancel(t.Context()) - factory.Start(stopCh) - cmFactory.Start(stopCh) - group, _ := errgroup.WithContext(context.Background()) + factory.Start(stoppableCtx.Done()) + cmFactory.Start(stoppableCtx.Done()) + group, gctx := errgroup.WithContext(stoppableCtx) go func() { - defer close(errCh) for _, c := range cs { func(c controllerpkg.Interface) { group.Go(func() error { - return c.Run(1, stopCh) + return c.Run(1, gctx) }) }(c) } - errCh <- group.Wait() }() + return func() { - close(stopCh) - err := <-errCh - if err != nil { + stopCtxFn() + if err := group.Wait(); err != nil { t.Fatal(err) } } @@ -95,7 +114,7 @@ func WaitForOpenAPIResourcesToBeLoaded(t *testing.T, ctx context.Context, config t.Fatal(err) } - err = wait.PollImmediateUntil(time.Second, func() (bool, error) { + err = wait.PollUntilContextCancel(ctx, time.Second, true, func(ctx context.Context) (done bool, err error) { og := openapi.NewOpenAPIGetter(dc) oapiResource, err := openapi.NewOpenAPIParser(og).Parse() if err != nil { @@ -106,7 +125,7 @@ func WaitForOpenAPIResourcesToBeLoaded(t *testing.T, ctx context.Context, config return true, nil } return false, nil - }, ctx.Done()) + }) if err != nil { t.Fatal("Our GVK isn't loaded into the OpenAPI resources API after waiting for 2 minutes", err) diff --git a/test/integration/fuzz/acme/pruning_test.go b/test/integration/fuzz/acme/pruning_test.go index cb5dc8622d7..d020f7a6046 100644 --- a/test/integration/fuzz/acme/pruning_test.go +++ b/test/integration/fuzz/acme/pruning_test.go @@ -27,6 +27,6 @@ import ( ) func TestPruneTypes(t *testing.T) { - crdfuzz.SchemaFuzzTestForCRDWithPath(t, api.Scheme, apitesting.PathForCRD(t, "orders"), acmefuzzer.Funcs) - crdfuzz.SchemaFuzzTestForCRDWithPath(t, api.Scheme, apitesting.PathForCRD(t, "challenges"), acmefuzzer.Funcs) + crdfuzz.SchemaFuzzTestForCRDWithPath(t, api.Scheme, apitesting.PathForCRD(t, "acme.cert-manager.io_orders"), acmefuzzer.Funcs) + crdfuzz.SchemaFuzzTestForCRDWithPath(t, api.Scheme, apitesting.PathForCRD(t, "acme.cert-manager.io_challenges"), acmefuzzer.Funcs) } diff --git a/test/integration/fuzz/cert-manager/pruning_test.go b/test/integration/fuzz/cert-manager/pruning_test.go index 398fe511053..a9f342d104c 100644 --- a/test/integration/fuzz/cert-manager/pruning_test.go +++ b/test/integration/fuzz/cert-manager/pruning_test.go @@ -27,8 +27,8 @@ import ( ) func TestPruneTypes(t *testing.T) { - crdfuzz.SchemaFuzzTestForCRDWithPath(t, api.Scheme, apitesting.PathForCRD(t, "certificates"), cmfuzzer.Funcs) - crdfuzz.SchemaFuzzTestForCRDWithPath(t, api.Scheme, apitesting.PathForCRD(t, "certificaterequests"), cmfuzzer.Funcs) - crdfuzz.SchemaFuzzTestForCRDWithPath(t, api.Scheme, apitesting.PathForCRD(t, "issuers"), cmfuzzer.Funcs) - crdfuzz.SchemaFuzzTestForCRDWithPath(t, api.Scheme, apitesting.PathForCRD(t, "clusterissuers"), cmfuzzer.Funcs) + crdfuzz.SchemaFuzzTestForCRDWithPath(t, api.Scheme, apitesting.PathForCRD(t, "cert-manager.io_certificates"), cmfuzzer.Funcs) + crdfuzz.SchemaFuzzTestForCRDWithPath(t, api.Scheme, apitesting.PathForCRD(t, "cert-manager.io_certificaterequests"), cmfuzzer.Funcs) + crdfuzz.SchemaFuzzTestForCRDWithPath(t, api.Scheme, apitesting.PathForCRD(t, "cert-manager.io_issuers"), cmfuzzer.Funcs) + crdfuzz.SchemaFuzzTestForCRDWithPath(t, api.Scheme, apitesting.PathForCRD(t, "cert-manager.io_clusterissuers"), cmfuzzer.Funcs) } diff --git a/test/integration/go.mod b/test/integration/go.mod new file mode 100644 index 00000000000..98b9e72fa72 --- /dev/null +++ b/test/integration/go.mod @@ -0,0 +1,129 @@ +module github.com/cert-manager/cert-manager/integration-tests + +go 1.25.0 + +// Do not remove this comment: +// please place any replace statements here at the top for visibility and add a +// comment to it as to when it can be removed + +replace github.com/cert-manager/cert-manager => ../../ + +replace github.com/cert-manager/cert-manager/webhook-binary => ../../cmd/webhook/ + +require ( + github.com/cert-manager/cert-manager v0.0.0-00010101000000-000000000000 + github.com/go-logr/logr v1.4.3 + github.com/miekg/dns v1.1.68 + github.com/munnerz/crd-schema-fuzz v1.1.0 + github.com/segmentio/encoding v0.5.3 + github.com/stretchr/testify v1.11.1 + golang.org/x/sync v0.17.0 + k8s.io/api v0.34.1 + k8s.io/apiextensions-apiserver v0.34.1 + k8s.io/apimachinery v0.34.1 + k8s.io/client-go v0.34.1 + k8s.io/kube-aggregator v0.34.1 + k8s.io/kubectl v0.34.1 + k8s.io/utils v0.0.0-20250820121507-0af2bda4dd1d + sigs.k8s.io/controller-runtime v0.22.3 + sigs.k8s.io/gateway-api v1.4.0 +) + +require ( + cel.dev/expr v0.24.0 // indirect + github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 // indirect + github.com/antlr4-go/antlr/v4 v4.13.1 // indirect + github.com/beorn7/perks v1.0.1 // indirect + github.com/blang/semver/v4 v4.0.0 // indirect + github.com/cenkalti/backoff/v5 v5.0.3 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/coreos/go-semver v0.3.1 // indirect + github.com/coreos/go-systemd/v22 v22.5.0 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/emicklei/go-restful/v3 v3.13.0 // indirect + github.com/evanphx/json-patch/v5 v5.9.11 // indirect + github.com/felixge/httpsnoop v1.0.4 // indirect + github.com/fsnotify/fsnotify v1.9.0 // indirect + github.com/fxamacker/cbor/v2 v2.9.0 // indirect + github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667 // indirect + github.com/go-ldap/ldap/v3 v3.4.12 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + github.com/go-logr/zapr v1.3.0 // indirect + github.com/go-openapi/jsonpointer v0.22.1 // indirect + github.com/go-openapi/jsonreference v0.21.2 // indirect + github.com/go-openapi/swag v0.23.1 // indirect + github.com/go-openapi/swag/jsonname v0.25.1 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang/protobuf v1.5.4 // indirect + github.com/google/btree v1.1.3 // indirect + github.com/google/cel-go v0.26.0 // indirect + github.com/google/gnostic-models v0.7.0 // indirect + github.com/google/go-cmp v0.7.0 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1 // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/josharian/intern v1.0.0 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/kylelemons/godebug v1.1.0 // indirect + github.com/mailru/easyjson v0.9.0 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/pavlo-v-chernykh/keystore-go/v4 v4.5.0 // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + github.com/prometheus/client_golang v1.23.2 // indirect + github.com/prometheus/client_model v0.6.2 // indirect + github.com/prometheus/common v0.66.1 // indirect + github.com/prometheus/procfs v0.17.0 // indirect + github.com/segmentio/asm v1.1.3 // indirect + github.com/spf13/cobra v1.10.1 // indirect + github.com/spf13/pflag v1.0.10 // indirect + github.com/stoewer/go-strcase v1.3.1 // indirect + github.com/x448/float16 v0.8.4 // indirect + go.etcd.io/etcd/api/v3 v3.6.4 // indirect + go.etcd.io/etcd/client/pkg/v3 v3.6.4 // indirect + go.etcd.io/etcd/client/v3 v3.6.4 // indirect + go.opentelemetry.io/auto/sdk v1.1.0 // indirect + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 // indirect + go.opentelemetry.io/otel v1.37.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.37.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.37.0 // indirect + go.opentelemetry.io/otel/metric v1.37.0 // indirect + go.opentelemetry.io/otel/sdk v1.37.0 // indirect + go.opentelemetry.io/otel/trace v1.37.0 // indirect + go.opentelemetry.io/proto/otlp v1.7.0 // indirect + go.uber.org/multierr v1.11.0 // indirect + go.uber.org/zap v1.27.0 // indirect + go.yaml.in/yaml/v2 v2.4.2 // indirect + go.yaml.in/yaml/v3 v3.0.4 // indirect + golang.org/x/crypto v0.43.0 // indirect + golang.org/x/exp v0.0.0-20250718183923-645b1fa84792 // indirect + golang.org/x/mod v0.28.0 // indirect + golang.org/x/net v0.46.0 // indirect + golang.org/x/oauth2 v0.32.0 // indirect + golang.org/x/sys v0.37.0 // indirect + golang.org/x/term v0.36.0 // indirect + golang.org/x/text v0.30.0 // indirect + golang.org/x/time v0.14.0 // indirect + golang.org/x/tools v0.37.0 // indirect + gomodules.xyz/jsonpatch/v2 v2.5.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20250804133106-a7a43d27e69b // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20251014184007-4626949a642f // indirect + google.golang.org/grpc v1.76.0 // indirect + google.golang.org/protobuf v1.36.10 // indirect + gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect + gopkg.in/inf.v0 v0.9.1 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect + k8s.io/apiserver v0.34.1 // indirect + k8s.io/component-base v0.34.1 // indirect + k8s.io/klog/v2 v2.130.1 // indirect + k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 // indirect + sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.33.0 // indirect + sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect + sigs.k8s.io/randfill v1.0.0 // indirect + sigs.k8s.io/structured-merge-diff/v6 v6.3.0 // indirect + sigs.k8s.io/yaml v1.6.0 // indirect + software.sslmate.com/src/go-pkcs12 v0.6.0 // indirect +) diff --git a/test/integration/go.sum b/test/integration/go.sum new file mode 100644 index 00000000000..7f5ebec09e3 --- /dev/null +++ b/test/integration/go.sum @@ -0,0 +1,351 @@ +cel.dev/expr v0.24.0 h1:56OvJKSH3hDGL0ml5uSxZmz3/3Pq4tJ+fb1unVLAFcY= +cel.dev/expr v0.24.0/go.mod h1:hLPLo1W4QUmuYdA72RBX06QTs6MXw941piREPl3Yfiw= +github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 h1:He8afgbRMd7mFxO99hRNu+6tazq8nFF9lIwo9JFroBk= +github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8= +github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 h1:mFRzDkZVAjdal+s7s0MwaRv9igoPqLRdzOLzw/8Xvq8= +github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU= +github.com/alexbrainman/sspi v0.0.0-20250919150558-7d374ff0d59e h1:4dAU9FXIyQktpoUAgOJK3OTFc/xug0PCXYCqU0FgDKI= +github.com/alexbrainman/sspi v0.0.0-20250919150558-7d374ff0d59e/go.mod h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4= +github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYWrPrQ= +github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw= +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= +github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= +github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM= +github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr4= +github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03VsM8rvUec= +github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= +github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= +github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= +github.com/emicklei/go-restful/v3 v3.13.0 h1:C4Bl2xDndpU6nJ4bc1jXd+uTmYPVUwkD6bFY/oTyCes= +github.com/emicklei/go-restful/v3 v3.13.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/evanphx/json-patch v5.9.0+incompatible h1:fBXyNpNMuTTDdquAq/uisOr2lShz4oaXpDTX2bLe7ls= +github.com/evanphx/json-patch v5.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/evanphx/json-patch/v5 v5.9.11 h1:/8HVnzMq13/3x9TPvjG08wUGqBTmZBsCWzjTM0wiaDU= +github.com/evanphx/json-patch/v5 v5.9.11/go.mod h1:3j+LviiESTElxA4p3EMKAB9HXj3/XEtnUf6OZxqIQTM= +github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= +github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= +github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= +github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM= +github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= +github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667 h1:BP4M0CvQ4S3TGls2FvczZtj5Re/2ZzkV9VwqPHH/3Bo= +github.com/go-asn1-ber/asn1-ber v1.5.8-0.20250403174932-29230038a667/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= +github.com/go-ldap/ldap/v3 v3.4.12 h1:1b81mv7MagXZ7+1r7cLTWmyuTqVqdwbtJSjC0DAp9s4= +github.com/go-ldap/ldap/v3 v3.4.12/go.mod h1:+SPAGcTtOfmGsCb3h1RFiq4xpp4N636G75OEace8lNo= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= +github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ= +github.com/go-logr/zapr v1.3.0/go.mod h1:YKepepNBd1u/oyhd/yQmtjVXmm9uML4IXUgMOwR8/Gg= +github.com/go-openapi/jsonpointer v0.22.1 h1:sHYI1He3b9NqJ4wXLoJDKmUmHkWy/L7rtEo92JUxBNk= +github.com/go-openapi/jsonpointer v0.22.1/go.mod h1:pQT9OsLkfz1yWoMgYFy4x3U5GY5nUlsOn1qSBH5MkCM= +github.com/go-openapi/jsonreference v0.21.2 h1:Wxjda4M/BBQllegefXrY/9aq1fxBA8sI5M/lFU6tSWU= +github.com/go-openapi/jsonreference v0.21.2/go.mod h1:pp3PEjIsJ9CZDGCNOyXIQxsNuroxm8FAJ/+quA0yKzQ= +github.com/go-openapi/swag v0.23.1 h1:lpsStH0n2ittzTnbaSloVZLuB5+fvSY/+hnagBjSNZU= +github.com/go-openapi/swag v0.23.1/go.mod h1:STZs8TbRvEQQKUA+JZNAm3EWlgaOBGpyFDqQnDHMef0= +github.com/go-openapi/swag/jsonname v0.25.1 h1:Sgx+qbwa4ej6AomWC6pEfXrA6uP2RkaNjA9BR8a1RJU= +github.com/go-openapi/swag/jsonname v0.25.1/go.mod h1:71Tekow6UOLBD3wS7XhdT98g5J5GR13NOTQ9/6Q11Zo= +github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= +github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= +github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo= +github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg= +github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= +github.com/google/cel-go v0.26.0 h1:DPGjXackMpJWH680oGY4lZhYjIameYmR+/6RBdDGmaI= +github.com/google/cel-go v0.26.0/go.mod h1:A9O8OU9rdvrK5MQyrqfIxo1a0u4g3sF8KB6PUIaryMM= +github.com/google/gnostic-models v0.7.0 h1:qwTtogB15McXDaNqTZdzPJRHvaVJlAl+HVQnLmJEJxo= +github.com/google/gnostic-models v0.7.0/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= +github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db h1:097atOisP2aRj7vFgYQBbFN4U4JNXUNYpxael3UzMyo= +github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 h1:JeSE6pjso5THxAzdVpqr6/geYxZytqFMBCOtn/ujyeo= +github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674/go.mod h1:r4w70xmWCQKmi1ONH4KIaBptdivuRPyosB9RmPlGEwA= +github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.0.1 h1:qnpSQwGEnkcRpTqNOIR6bJbR0gAorgP9CSALpRcKoAA= +github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.0.1/go.mod h1:lXGCsh6c22WGtjr+qGHj1otzZpV/1kwTMAqkwZsnWRU= +github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.0 h1:FbSCl+KggFl+Ocym490i/EyXF4lPgLoUtcSWquBM0Rs= +github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.0/go.mod h1:qOchhhIlmRcqk/O9uCo/puJlyo07YINaIqdZfZG3Jkc= +github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92BcuyuQ/YW4NSIpoGtfXNho= +github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1 h1:X5VWvz21y3gzm9Nw/kaUeku/1+uBhcekkmy4IkffJww= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1/go.mod h1:Zanoh4+gvIgluNqcfMVTJueD4wSS5hT7zTt4Mrutd90= +github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= +github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/jcmturner/aescts/v2 v2.0.0 h1:9YKLH6ey7H4eDBXW8khjYslgyqG2xZikXP0EQFKrle8= +github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs= +github.com/jcmturner/dnsutils/v2 v2.0.0 h1:lltnkeZGL0wILNvrNiVCR6Ro5PGU/SeBvVO/8c/iPbo= +github.com/jcmturner/dnsutils/v2 v2.0.0/go.mod h1:b0TnjGOvI/n42bZa+hmXL+kFJZsFT7G4t3HTlQ184QM= +github.com/jcmturner/gofork v1.7.6 h1:QH0l3hzAU1tfT3rZCnW5zXl+orbkNMMRGJfdJjHVETg= +github.com/jcmturner/gofork v1.7.6/go.mod h1:1622LH6i/EZqLloHfE7IeZ0uEJwMSUyQ/nDd82IeqRo= +github.com/jcmturner/goidentity/v6 v6.0.1 h1:VKnZd2oEIMorCTsFBnJWbExfNN7yZr3EhJAxwOkZg6o= +github.com/jcmturner/goidentity/v6 v6.0.1/go.mod h1:X1YW3bgtvwAXju7V3LCIMpY0Gbxyjn/mY9zx4tFonSg= +github.com/jcmturner/gokrb5/v8 v8.4.4 h1:x1Sv4HaTpepFkXbt2IkL29DXRf8sOfZXo8eRKh687T8= +github.com/jcmturner/gokrb5/v8 v8.4.4/go.mod h1:1btQEpgT6k+unzCwX1KdWMEwPPkkgBtP+F6aCACiMrs= +github.com/jcmturner/rpc/v2 v2.0.3 h1:7FXXj8Ti1IaVFpSAziCZWNzbNuZmnvw/i6CqLNdWfZY= +github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc= +github.com/jonboulle/clockwork v0.5.0 h1:Hyh9A8u51kptdkR+cqRpT1EebBwTn1oK9YfGYbdFz6I= +github.com/jonboulle/clockwork v0.5.0/go.mod h1:3mZlmanh0g2NDKO5TWZVJAfofYk64M7XN3SzBPjZF60= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= +github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4= +github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= +github.com/miekg/dns v1.1.68 h1:jsSRkNozw7G/mnmXULynzMNIsgY2dHC8LO6U6Ij2JEA= +github.com/miekg/dns v1.1.68/go.mod h1:fujopn7TB3Pu3JM69XaawiU0wqjpL9/8xGop5UrTPps= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8= +github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/munnerz/crd-schema-fuzz v1.1.0 h1:wduhV5HkPgOY1b7mSW0u/1wnj0sE7XCColFMQgvd6rg= +github.com/munnerz/crd-schema-fuzz v1.1.0/go.mod h1:PM4svxRzVDM7wURB/hKhSA8RtkctGBmp5iSOOzE+NOI= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/onsi/ginkgo/v2 v2.22.0 h1:Yed107/8DjTr0lKCNt7Dn8yQ6ybuDRQoMGrNFKzMfHg= +github.com/onsi/ginkgo/v2 v2.22.0/go.mod h1:7Du3c42kxCUegi0IImZ1wUQzMBVecgIHjR1C+NkhLQo= +github.com/onsi/gomega v1.36.1 h1:bJDPBO7ibjxcbHMgSCoo4Yj18UWbKDlLwX1x9sybDcw= +github.com/onsi/gomega v1.36.1/go.mod h1:PvZbdDc8J6XJEpDK4HCuRBm8a6Fzp9/DmhC9C7yFlog= +github.com/pavlo-v-chernykh/keystore-go/v4 v4.5.0 h1:2nosf3P75OZv2/ZO/9Px5ZgZ5gbKrzA3joN1QMfOGMQ= +github.com/pavlo-v-chernykh/keystore-go/v4 v4.5.0/go.mod h1:lAVhWwbNaveeJmxrxuSTxMgKpF6DjnuVpn6T8WiBwYQ= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o= +github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg= +github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= +github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= +github.com/prometheus/common v0.66.1 h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9ZoGs= +github.com/prometheus/common v0.66.1/go.mod h1:gcaUsgf3KfRSwHY4dIMXLPV0K/Wg1oZ8+SbZk/HH/dA= +github.com/prometheus/procfs v0.17.0 h1:FuLQ+05u4ZI+SS/w9+BWEM2TXiHKsUQ9TADiRH7DuK0= +github.com/prometheus/procfs v0.17.0/go.mod h1:oPQLaDAMRbA+u8H5Pbfq+dl3VDAvHxMUOVhe0wYB2zw= +github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= +github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/segmentio/asm v1.1.3 h1:WM03sfUOENvvKexOLp+pCqgb/WDjsi7EK8gIsICtzhc= +github.com/segmentio/asm v1.1.3/go.mod h1:Ld3L4ZXGNcSLRg4JBsZ3//1+f/TjYl0Mzen/DQy1EJg= +github.com/segmentio/encoding v0.5.3 h1:OjMgICtcSFuNvQCdwqMCv9Tg7lEOXGwm1J5RPQccx6w= +github.com/segmentio/encoding v0.5.3/go.mod h1:HS1ZKa3kSN32ZHVZ7ZLPLXWvOVIiZtyJnO1gPH1sKt0= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/soheilhy/cmux v0.1.5 h1:jjzc5WVemNEDTLwv9tlmemhC73tI08BNOIGwBOo10Js= +github.com/soheilhy/cmux v0.1.5/go.mod h1:T7TcVDs9LWfQgPlPsdngu6I6QIoyIFZDDC6sNE1GqG0= +github.com/spf13/cobra v1.10.1 h1:lJeBwCfmrnXthfAupyUTzJ/J4Nc1RsHC/mSRU2dll/s= +github.com/spf13/cobra v1.10.1/go.mod h1:7SmJGaTHFVBY0jW4NXGluQoLvhqFQM+6XSKD+P4XaB0= +github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= +github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/stoewer/go-strcase v1.3.1 h1:iS0MdW+kVTxgMoE1LAZyMiYJFKlOzLooE4MxjirtkAs= +github.com/stoewer/go-strcase v1.3.1/go.mod h1:fAH5hQ5pehh+j3nZfvwdk2RgEgQjAoM8wodgtPmh1xo= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/tmc/grpc-websocket-proxy v0.0.0-20220101234140-673ab2c3ae75 h1:6fotK7otjonDflCTK0BCfls4SPy3NcCVb5dqqmbRknE= +github.com/tmc/grpc-websocket-proxy v0.0.0-20220101234140-673ab2c3ae75/go.mod h1:KO6IkyS8Y3j8OdNO85qEYBsRPuteD+YciPomcXdrMnk= +github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= +github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= +github.com/xiang90/probing v0.0.0-20221125231312-a49e3df8f510 h1:S2dVYn90KE98chqDkyE9Z4N61UnQd+KOfgp5Iu53llk= +github.com/xiang90/probing v0.0.0-20221125231312-a49e3df8f510/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +go.etcd.io/bbolt v1.4.2 h1:IrUHp260R8c+zYx/Tm8QZr04CX+qWS5PGfPdevhdm1I= +go.etcd.io/bbolt v1.4.2/go.mod h1:Is8rSHO/b4f3XigBC0lL0+4FwAQv3HXEEIgFMuKHceM= +go.etcd.io/etcd/api/v3 v3.6.4 h1:7F6N7toCKcV72QmoUKa23yYLiiljMrT4xCeBL9BmXdo= +go.etcd.io/etcd/api/v3 v3.6.4/go.mod h1:eFhhvfR8Px1P6SEuLT600v+vrhdDTdcfMzmnxVXXSbk= +go.etcd.io/etcd/client/pkg/v3 v3.6.4 h1:9HBYrjppeOfFjBjaMTRxT3R7xT0GLK8EJMVC4xg6ok0= +go.etcd.io/etcd/client/pkg/v3 v3.6.4/go.mod h1:sbdzr2cl3HzVmxNw//PH7aLGVtY4QySjQFuaCgcRFAI= +go.etcd.io/etcd/client/v3 v3.6.4 h1:YOMrCfMhRzY8NgtzUsHl8hC2EBSnuqbR3dh84Uryl7A= +go.etcd.io/etcd/client/v3 v3.6.4/go.mod h1:jaNNHCyg2FdALyKWnd7hxZXZxZANb0+KGY+YQaEMISo= +go.etcd.io/etcd/pkg/v3 v3.6.4 h1:fy8bmXIec1Q35/jRZ0KOes8vuFxbvdN0aAFqmEfJZWA= +go.etcd.io/etcd/pkg/v3 v3.6.4/go.mod h1:kKcYWP8gHuBRcteyv6MXWSN0+bVMnfgqiHueIZnKMtE= +go.etcd.io/etcd/server/v3 v3.6.4 h1:LsCA7CzjVt+8WGrdsnh6RhC0XqCsLkBly3ve5rTxMAU= +go.etcd.io/etcd/server/v3 v3.6.4/go.mod h1:aYCL/h43yiONOv0QIR82kH/2xZ7m+IWYjzRmyQfnCAg= +go.etcd.io/raft/v3 v3.6.0 h1:5NtvbDVYpnfZWcIHgGRk9DyzkBIXOi8j+DDp1IcnUWQ= +go.etcd.io/raft/v3 v3.6.0/go.mod h1:nLvLevg6+xrVtHUmVaTcTz603gQPHfh7kUAwV6YpfGo= +go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= +go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 h1:q4XOmH/0opmeuJtPsbFNivyl7bCt7yRBbeEm2sC/XtQ= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0/go.mod h1:snMWehoOh2wsEwnvvwtDyFCxVeDAODenXHtn5vzrKjo= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 h1:F7Jx+6hwnZ41NSFTO5q4LYDtJRXBf2PD0rNBkeB/lus= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0/go.mod h1:UHB22Z8QsdRDrnAtX4PntOl36ajSxcdUMt1sF7Y6E7Q= +go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ= +go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.37.0 h1:Ahq7pZmv87yiyn3jeFz/LekZmPLLdKejuO3NcK9MssM= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.37.0/go.mod h1:MJTqhM0im3mRLw1i8uGHnCvUEeS7VwRyxlLC78PA18M= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.37.0 h1:EtFWSnwW9hGObjkIdmlnWSydO+Qs8OwzfzXLUPg4xOc= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.37.0/go.mod h1:QjUEoiGCPkvFZ/MjK6ZZfNOS6mfVEVKYE99dFhuN2LI= +go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE= +go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E= +go.opentelemetry.io/otel/sdk v1.37.0 h1:ItB0QUqnjesGRvNcmAcU0LyvkVyGJ2xftD29bWdDvKI= +go.opentelemetry.io/otel/sdk v1.37.0/go.mod h1:VredYzxUvuo2q3WRcDnKDjbdvmO0sCzOvVAiY+yUkAg= +go.opentelemetry.io/otel/sdk/metric v1.37.0 h1:90lI228XrB9jCMuSdA0673aubgRobVZFhbjxHHspCPc= +go.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps= +go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4= +go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0= +go.opentelemetry.io/proto/otlp v1.7.0 h1:jX1VolD6nHuFzOYso2E73H85i92Mv8JQYk0K9vz09os= +go.opentelemetry.io/proto/otlp v1.7.0/go.mod h1:fSKjH6YJ7HDlwzltzyMj036AJ3ejJLCgCSHGj4efDDo= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= +go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= +go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI= +go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU= +go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= +go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04= +golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0= +golang.org/x/exp v0.0.0-20250718183923-645b1fa84792 h1:R9PFI6EUdfVKgwKjZef7QIwGcBKu86OEFpJ9nUEP2l4= +golang.org/x/exp v0.0.0-20250718183923-645b1fa84792/go.mod h1:A+z0yzpGtvnG90cToK5n2tu8UJVP2XUATh+r+sfOOOc= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.28.0 h1:gQBtGhjxykdjY9YhZpSlZIsbnaE2+PgjfLWUQTnoZ1U= +golang.org/x/mod v0.28.0/go.mod h1:yfB/L0NOf/kmEbXjzCPOx1iK1fRutOydrCMsqRhEBxI= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.46.0 h1:giFlY12I07fugqwPuWJi68oOnpfqFnJIJzaIIm2JVV4= +golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210= +golang.org/x/oauth2 v0.32.0 h1:jsCblLleRMDrxMN29H3z/k1KliIvpLgCkE6R8FXXNgY= +golang.org/x/oauth2 v0.32.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug= +golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ= +golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/term v0.36.0 h1:zMPR+aF8gfksFprF/Nc/rd1wRS1EI6nDBGyWAvDzx2Q= +golang.org/x/term v0.36.0/go.mod h1:Qu394IJq6V6dCBRgwqshf3mPF85AqzYEzofzRdZkWss= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k= +golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM= +golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI= +golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.37.0 h1:DVSRzp7FwePZW356yEAChSdNcQo6Nsp+fex1SUW09lE= +golang.org/x/tools v0.37.0/go.mod h1:MBN5QPQtLMHVdvsbtarmTNukZDdgwdwlO5qGacAzF0w= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gomodules.xyz/jsonpatch/v2 v2.5.0 h1:JELs8RLM12qJGXU4u/TO3V25KW8GreMKl9pdkk14RM0= +gomodules.xyz/jsonpatch/v2 v2.5.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY= +gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= +gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= +google.golang.org/genproto/googleapis/api v0.0.0-20250804133106-a7a43d27e69b h1:ULiyYQ0FdsJhwwZUwbaXpZF5yUE3h+RA+gxvBu37ucc= +google.golang.org/genproto/googleapis/api v0.0.0-20250804133106-a7a43d27e69b/go.mod h1:oDOGiMSXHL4sDTJvFvIB9nRQCGdLP1o/iVaqQK8zB+M= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251014184007-4626949a642f h1:1FTH6cpXFsENbPR5Bu8NQddPSaUUE6NA2XdZdDSAJK4= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251014184007-4626949a642f/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= +google.golang.org/grpc v1.76.0 h1:UnVkv1+uMLYXoIz6o7chp59WfQUYA2ex/BXQ9rHZu7A= +google.golang.org/grpc v1.76.0/go.mod h1:Ju12QI8M6iQJtbcsV+awF5a4hfJMLi4X0JLo94ULZ6c= +google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= +google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/evanphx/json-patch.v4 v4.13.0 h1:czT3CmqEaQ1aanPc5SdlgQrrEIb8w/wwCvWWnfEbYzo= +gopkg.in/evanphx/json-patch.v4 v4.13.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= +gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= +gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc= +gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +k8s.io/api v0.34.1 h1:jC+153630BMdlFukegoEL8E/yT7aLyQkIVuwhmwDgJM= +k8s.io/api v0.34.1/go.mod h1:SB80FxFtXn5/gwzCoN6QCtPD7Vbu5w2n1S0J5gFfTYk= +k8s.io/apiextensions-apiserver v0.34.1 h1:NNPBva8FNAPt1iSVwIE0FsdrVriRXMsaWFMqJbII2CI= +k8s.io/apiextensions-apiserver v0.34.1/go.mod h1:hP9Rld3zF5Ay2Of3BeEpLAToP+l4s5UlxiHfqRaRcMc= +k8s.io/apimachinery v0.34.1 h1:dTlxFls/eikpJxmAC7MVE8oOeP1zryV7iRyIjB0gky4= +k8s.io/apimachinery v0.34.1/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw= +k8s.io/apiserver v0.34.1 h1:U3JBGdgANK3dfFcyknWde1G6X1F4bg7PXuvlqt8lITA= +k8s.io/apiserver v0.34.1/go.mod h1:eOOc9nrVqlBI1AFCvVzsob0OxtPZUCPiUJL45JOTBG0= +k8s.io/client-go v0.34.1 h1:ZUPJKgXsnKwVwmKKdPfw4tB58+7/Ik3CrjOEhsiZ7mY= +k8s.io/client-go v0.34.1/go.mod h1:kA8v0FP+tk6sZA0yKLRG67LWjqufAoSHA2xVGKw9Of8= +k8s.io/component-base v0.34.1 h1:v7xFgG+ONhytZNFpIz5/kecwD+sUhVE6HU7qQUiRM4A= +k8s.io/component-base v0.34.1/go.mod h1:mknCpLlTSKHzAQJJnnHVKqjxR7gBeHRv0rPXA7gdtQ0= +k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= +k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= +k8s.io/kube-aggregator v0.34.1 h1:WNLV0dVNoFKmuyvdWLd92iDSyD/TSTjqwaPj0U9XAEU= +k8s.io/kube-aggregator v0.34.1/go.mod h1:RU8j+5ERfp0h+gIvWtxRPfsa5nK7rboDm8RST8BJfYQ= +k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 h1:Y3gxNAuB0OBLImH611+UDZcmKS3g6CthxToOb37KgwE= +k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912/go.mod h1:kdmbQkyfwUagLfXIad1y2TdrjPFWp2Q89B3qkRwf/pQ= +k8s.io/kubectl v0.34.1 h1:1qP1oqT5Xc93K+H8J7ecpBjaz511gan89KO9Vbsh/OI= +k8s.io/kubectl v0.34.1/go.mod h1:JRYlhJpGPyk3dEmJ+BuBiOB9/dAvnrALJEiY/C5qa6A= +k8s.io/utils v0.0.0-20250820121507-0af2bda4dd1d h1:wAhiDyZ4Tdtt7e46e9M5ZSAJ/MnPGPs+Ki1gHw4w1R0= +k8s.io/utils v0.0.0-20250820121507-0af2bda4dd1d/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.33.0 h1:qPrZsv1cwQiFeieFlRqT627fVZ+tyfou/+S5S0H5ua0= +sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.33.0/go.mod h1:Ve9uj1L+deCXFrPOk1LpFXqTg7LCFzFso6PA48q/XZw= +sigs.k8s.io/controller-runtime v0.22.3 h1:I7mfqz/a/WdmDCEnXmSPm8/b/yRTy6JsKKENTijTq8Y= +sigs.k8s.io/controller-runtime v0.22.3/go.mod h1:+QX1XUpTXN4mLoblf4tqr5CQcyHPAki2HLXqQMY6vh8= +sigs.k8s.io/gateway-api v1.4.0 h1:ZwlNM6zOHq0h3WUX2gfByPs2yAEsy/EenYJB78jpQfQ= +sigs.k8s.io/gateway-api v1.4.0/go.mod h1:AR5RSqciWP98OPckEjOjh2XJhAe2Na4LHyXD2FUY7Qk= +sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg= +sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= +sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= +sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= +sigs.k8s.io/structured-merge-diff/v6 v6.3.0 h1:jTijUJbW353oVOd9oTlifJqOGEkUw2jB/fXCbTiQEco= +sigs.k8s.io/structured-merge-diff/v6 v6.3.0/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE= +sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs= +sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4= +software.sslmate.com/src/go-pkcs12 v0.6.0 h1:f3sQittAeF+pao32Vb+mkli+ZyT+VwKaD014qFGq6oU= +software.sslmate.com/src/go-pkcs12 v0.6.0/go.mod h1:Qiz0EyvDRJjjxGyUQa2cCNZn/wMyzrRJ/qcDXOQazLI= diff --git a/test/integration/issuers/condition_list_type_test.go b/test/integration/issuers/condition_list_type_test.go index 9e48a670975..96baac4b49c 100644 --- a/test/integration/issuers/condition_list_type_test.go +++ b/test/integration/issuers/condition_list_type_test.go @@ -17,19 +17,17 @@ limitations under the License. package issuers import ( - "context" "testing" - "time" "github.com/stretchr/testify/assert" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "github.com/cert-manager/cert-manager/integration-tests/framework" internalissuers "github.com/cert-manager/cert-manager/internal/controller/issuers" cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" "github.com/cert-manager/cert-manager/pkg/util" - "github.com/cert-manager/cert-manager/test/integration/framework" ) func Test_ConditionsListType_Issuers(t *testing.T) { @@ -38,28 +36,25 @@ func Test_ConditionsListType_Issuers(t *testing.T) { name = "test-condition-list-type" ) - ctx, cancel := context.WithTimeout(context.Background(), time.Second*40) - defer cancel() - - restConfig, stopFn := framework.RunControlPlane(t, ctx) - defer stopFn() + restConfig, stopFn := framework.RunControlPlane(t) + t.Cleanup(stopFn) // Build clients with different field managers. aliceRestConfig := util.RestConfigWithUserAgent(restConfig, "alice") aliceFieldManager := util.PrefixFromUserAgent(aliceRestConfig.UserAgent) - aliceKubeClient, _, aliceCMClient, _ := framework.NewClients(t, aliceRestConfig) + aliceKubeClient, _, aliceCMClient, _, _ := framework.NewClients(t, aliceRestConfig) bobRestConfig := util.RestConfigWithUserAgent(restConfig, "bob") bobFieldManager := util.PrefixFromUserAgent(bobRestConfig.UserAgent) - _, _, bobCMClient, _ := framework.NewClients(t, bobRestConfig) + _, _, bobCMClient, _, _ := framework.NewClients(t, bobRestConfig) t.Log("creating test Namespace") ns := &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: namespace}} - _, err := aliceKubeClient.CoreV1().Namespaces().Create(ctx, ns, metav1.CreateOptions{}) + _, err := aliceKubeClient.CoreV1().Namespaces().Create(t.Context(), ns, metav1.CreateOptions{}) assert.NoError(t, err) t.Log("creating Issuer") - _, err = aliceCMClient.CertmanagerV1().Issuers(namespace).Create(ctx, &cmapi.Issuer{ + _, err = aliceCMClient.CertmanagerV1().Issuers(namespace).Create(t.Context(), &cmapi.Issuer{ ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: namespace}, Spec: cmapi.IssuerSpec{IssuerConfig: cmapi.IssuerConfig{ SelfSigned: new(cmapi.SelfSignedIssuer), @@ -68,7 +63,7 @@ func Test_ConditionsListType_Issuers(t *testing.T) { assert.NoError(t, err) t.Log("ensuring alice can set Ready condition") - assert.NoError(t, internalissuers.ApplyIssuerStatus(ctx, aliceCMClient, aliceFieldManager, &cmapi.Issuer{ + assert.NoError(t, internalissuers.ApplyIssuerStatus(t.Context(), aliceCMClient, aliceFieldManager, &cmapi.Issuer{ ObjectMeta: metav1.ObjectMeta{Namespace: namespace, Name: name}, Status: cmapi.IssuerStatus{ Conditions: []cmapi.IssuerCondition{{Type: cmapi.IssuerConditionReady, Status: cmmeta.ConditionTrue, Reason: "reason", Message: "message"}}, @@ -76,14 +71,14 @@ func Test_ConditionsListType_Issuers(t *testing.T) { })) t.Log("ensuring bob can set a district random condition, without changing the ready condition") - assert.NoError(t, internalissuers.ApplyIssuerStatus(ctx, bobCMClient, bobFieldManager, &cmapi.Issuer{ + assert.NoError(t, internalissuers.ApplyIssuerStatus(t.Context(), bobCMClient, bobFieldManager, &cmapi.Issuer{ ObjectMeta: metav1.ObjectMeta{Namespace: namespace, Name: name}, Status: cmapi.IssuerStatus{ Conditions: []cmapi.IssuerCondition{{Type: cmapi.IssuerConditionType("Random"), Status: cmmeta.ConditionTrue, Reason: "reason", Message: "message"}}, }, })) - issuer, err := bobCMClient.CertmanagerV1().Issuers(namespace).Get(ctx, name, metav1.GetOptions{}) + issuer, err := bobCMClient.CertmanagerV1().Issuers(namespace).Get(t.Context(), name, metav1.GetOptions{}) assert.NoError(t, err) assert.Equal(t, []cmapi.IssuerCondition{ {Type: cmapi.IssuerConditionReady, Status: cmmeta.ConditionTrue, Reason: "reason", Message: "message"}, @@ -91,28 +86,28 @@ func Test_ConditionsListType_Issuers(t *testing.T) { }, issuer.Status.Conditions, "conditions did not match the expected 2 distinct condition types") t.Log("alice should override an existing condition by another manager, and can delete an existing owned condition type through omission") - assert.NoError(t, internalissuers.ApplyIssuerStatus(ctx, aliceCMClient, aliceFieldManager, &cmapi.Issuer{ + assert.NoError(t, internalissuers.ApplyIssuerStatus(t.Context(), aliceCMClient, aliceFieldManager, &cmapi.Issuer{ ObjectMeta: metav1.ObjectMeta{Namespace: namespace, Name: name}, Status: cmapi.IssuerStatus{ Conditions: []cmapi.IssuerCondition{{Type: cmapi.IssuerConditionType("Random"), Status: cmmeta.ConditionFalse, Reason: "another-reason", Message: "another-message"}}, }, })) - issuer, err = aliceCMClient.CertmanagerV1().Issuers(namespace).Get(ctx, name, metav1.GetOptions{}) + issuer, err = aliceCMClient.CertmanagerV1().Issuers(namespace).Get(t.Context(), name, metav1.GetOptions{}) assert.NoError(t, err) assert.Equal(t, []cmapi.IssuerCondition{ {Type: cmapi.IssuerConditionType("Random"), Status: cmmeta.ConditionFalse, Reason: "another-reason", Message: "another-message"}, }, issuer.Status.Conditions, "conditions did not match expected deleted ready condition, and overwritten random condition") t.Log("bob can re-add a Ready condition and not change Random condition") - assert.NoError(t, internalissuers.ApplyIssuerStatus(ctx, bobCMClient, bobFieldManager, &cmapi.Issuer{ + assert.NoError(t, internalissuers.ApplyIssuerStatus(t.Context(), bobCMClient, bobFieldManager, &cmapi.Issuer{ ObjectMeta: metav1.ObjectMeta{Namespace: namespace, Name: name}, Status: cmapi.IssuerStatus{ Conditions: []cmapi.IssuerCondition{{Type: cmapi.IssuerConditionReady, Status: cmmeta.ConditionFalse, Reason: "reason", Message: "message"}}, }, })) - issuer, err = bobCMClient.CertmanagerV1().Issuers(namespace).Get(ctx, name, metav1.GetOptions{}) + issuer, err = bobCMClient.CertmanagerV1().Issuers(namespace).Get(t.Context(), name, metav1.GetOptions{}) assert.NoError(t, err) assert.Equal(t, []cmapi.IssuerCondition{ {Type: cmapi.IssuerConditionType("Random"), Status: cmmeta.ConditionFalse, Reason: "another-reason", Message: "another-message"}, @@ -125,23 +120,20 @@ func Test_ConditionsListType_ClusterIssuers(t *testing.T) { name = "test-condition-list-type" ) - ctx, cancel := context.WithTimeout(context.Background(), time.Second*40) - defer cancel() - - restConfig, stopFn := framework.RunControlPlane(t, ctx) - defer stopFn() + restConfig, stopFn := framework.RunControlPlane(t) + t.Cleanup(stopFn) // Build clients with different field managers. aliceRestConfig := util.RestConfigWithUserAgent(restConfig, "alice") aliceFieldManager := util.PrefixFromUserAgent(aliceRestConfig.UserAgent) - _, _, aliceCMClient, _ := framework.NewClients(t, aliceRestConfig) + _, _, aliceCMClient, _, _ := framework.NewClients(t, aliceRestConfig) bobRestConfig := util.RestConfigWithUserAgent(restConfig, "bob") bobFieldManager := util.PrefixFromUserAgent(bobRestConfig.UserAgent) - _, _, bobCMClient, _ := framework.NewClients(t, bobRestConfig) + _, _, bobCMClient, _, _ := framework.NewClients(t, bobRestConfig) t.Log("creating ClusterIssuer") - _, err := aliceCMClient.CertmanagerV1().ClusterIssuers().Create(ctx, &cmapi.ClusterIssuer{ + _, err := aliceCMClient.CertmanagerV1().ClusterIssuers().Create(t.Context(), &cmapi.ClusterIssuer{ ObjectMeta: metav1.ObjectMeta{Name: name}, Spec: cmapi.IssuerSpec{IssuerConfig: cmapi.IssuerConfig{ SelfSigned: new(cmapi.SelfSignedIssuer), @@ -150,7 +142,7 @@ func Test_ConditionsListType_ClusterIssuers(t *testing.T) { assert.NoError(t, err) t.Log("ensuring alice can set Ready condition") - assert.NoError(t, internalissuers.ApplyClusterIssuerStatus(ctx, aliceCMClient, aliceFieldManager, &cmapi.ClusterIssuer{ + assert.NoError(t, internalissuers.ApplyClusterIssuerStatus(t.Context(), aliceCMClient, aliceFieldManager, &cmapi.ClusterIssuer{ ObjectMeta: metav1.ObjectMeta{Name: name}, Status: cmapi.IssuerStatus{ Conditions: []cmapi.IssuerCondition{{Type: cmapi.IssuerConditionReady, Status: cmmeta.ConditionTrue, Reason: "reason", Message: "message"}}, @@ -158,14 +150,14 @@ func Test_ConditionsListType_ClusterIssuers(t *testing.T) { })) t.Log("ensuring bob can set a district random condition, without changing the ready condition") - assert.NoError(t, internalissuers.ApplyClusterIssuerStatus(ctx, bobCMClient, bobFieldManager, &cmapi.ClusterIssuer{ + assert.NoError(t, internalissuers.ApplyClusterIssuerStatus(t.Context(), bobCMClient, bobFieldManager, &cmapi.ClusterIssuer{ ObjectMeta: metav1.ObjectMeta{Name: name}, Status: cmapi.IssuerStatus{ Conditions: []cmapi.IssuerCondition{{Type: cmapi.IssuerConditionType("Random"), Status: cmmeta.ConditionTrue, Reason: "reason", Message: "message"}}, }, })) - issuer, err := bobCMClient.CertmanagerV1().ClusterIssuers().Get(ctx, name, metav1.GetOptions{}) + issuer, err := bobCMClient.CertmanagerV1().ClusterIssuers().Get(t.Context(), name, metav1.GetOptions{}) assert.NoError(t, err) assert.Equal(t, []cmapi.IssuerCondition{ {Type: cmapi.IssuerConditionReady, Status: cmmeta.ConditionTrue, Reason: "reason", Message: "message"}, @@ -173,28 +165,28 @@ func Test_ConditionsListType_ClusterIssuers(t *testing.T) { }, issuer.Status.Conditions, "conditions did not match the expected 2 distinct condition types") t.Log("alice should override an existing condition by another manager, and can delete an existing owned condition type through omission") - assert.NoError(t, internalissuers.ApplyClusterIssuerStatus(ctx, aliceCMClient, aliceFieldManager, &cmapi.ClusterIssuer{ + assert.NoError(t, internalissuers.ApplyClusterIssuerStatus(t.Context(), aliceCMClient, aliceFieldManager, &cmapi.ClusterIssuer{ ObjectMeta: metav1.ObjectMeta{Name: name}, Status: cmapi.IssuerStatus{ Conditions: []cmapi.IssuerCondition{{Type: cmapi.IssuerConditionType("Random"), Status: cmmeta.ConditionFalse, Reason: "another-reason", Message: "another-message"}}, }, })) - issuer, err = aliceCMClient.CertmanagerV1().ClusterIssuers().Get(ctx, name, metav1.GetOptions{}) + issuer, err = aliceCMClient.CertmanagerV1().ClusterIssuers().Get(t.Context(), name, metav1.GetOptions{}) assert.NoError(t, err) assert.Equal(t, []cmapi.IssuerCondition{ {Type: cmapi.IssuerConditionType("Random"), Status: cmmeta.ConditionFalse, Reason: "another-reason", Message: "another-message"}, }, issuer.Status.Conditions, "conditions did not match expected deleted ready condition, and overwritten random condition") t.Log("bob can re-add a Ready condition and not change Random condition") - assert.NoError(t, internalissuers.ApplyClusterIssuerStatus(ctx, bobCMClient, bobFieldManager, &cmapi.ClusterIssuer{ + assert.NoError(t, internalissuers.ApplyClusterIssuerStatus(t.Context(), bobCMClient, bobFieldManager, &cmapi.ClusterIssuer{ ObjectMeta: metav1.ObjectMeta{Name: name}, Status: cmapi.IssuerStatus{ Conditions: []cmapi.IssuerCondition{{Type: cmapi.IssuerConditionReady, Status: cmmeta.ConditionFalse, Reason: "reason", Message: "message"}}, }, })) - issuer, err = bobCMClient.CertmanagerV1().ClusterIssuers().Get(ctx, name, metav1.GetOptions{}) + issuer, err = bobCMClient.CertmanagerV1().ClusterIssuers().Get(t.Context(), name, metav1.GetOptions{}) assert.NoError(t, err) assert.Equal(t, []cmapi.IssuerCondition{ {Type: cmapi.IssuerConditionType("Random"), Status: cmmeta.ConditionFalse, Reason: "another-reason", Message: "another-message"}, diff --git a/test/integration/rfc2136_dns01/provider_test.go b/test/integration/rfc2136_dns01/provider_test.go index 42cbbfad265..3eb6735a487 100644 --- a/test/integration/rfc2136_dns01/provider_test.go +++ b/test/integration/rfc2136_dns01/provider_test.go @@ -17,29 +17,29 @@ limitations under the License. package rfc2136 import ( - "context" "testing" - logtesting "github.com/go-logr/logr/testing" + "github.com/go-logr/logr/testr" cmacme "github.com/cert-manager/cert-manager/pkg/apis/acme/v1" cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" "github.com/cert-manager/cert-manager/pkg/issuer/acme/dns/rfc2136" logf "github.com/cert-manager/cert-manager/pkg/logs" - "github.com/cert-manager/cert-manager/test/acme/dns" - testserver "github.com/cert-manager/cert-manager/test/acme/dns/server" + dns "github.com/cert-manager/cert-manager/test/acme" + testserver "github.com/cert-manager/cert-manager/test/acme/server" ) func TestRunSuiteWithTSIG(t *testing.T) { - ctx := logf.NewContext(context.TODO(), logtesting.NewTestLogger(t), t.Name()) + ctx := logf.NewContext(t.Context(), testr.New(t), t.Name()) server := &testserver.BasicServer{ + T: t, Zones: []string{rfc2136TestZone}, EnableTSIG: true, TSIGZone: rfc2136TestZone, TSIGKeyName: rfc2136TestTsigKeyName, TSIGKeySecret: rfc2136TestTsigSecret, } - if err := server.Run(ctx); err != nil { + if err := server.Run(ctx, "UDP"); err != nil { t.Fatalf("failed to start test server: %v", err) } defer func() { @@ -59,7 +59,7 @@ func TestRunSuiteWithTSIG(t *testing.T) { TSIGKeyName: rfc2136TestTsigKeyName, } - fixture := dns.NewFixture(&rfc2136.Solver{}, + fixture := dns.NewFixture(rfc2136.New(rfc2136.InitializeResetLister()), dns.SetResolvedZone(rfc2136TestZone), dns.SetResolvedFQDN(rfc2136TestFqdn), dns.SetAllowAmbientCredentials(false), @@ -74,11 +74,12 @@ func TestRunSuiteWithTSIG(t *testing.T) { } func TestRunSuiteNoTSIG(t *testing.T) { - ctx := logf.NewContext(context.TODO(), logtesting.NewTestLogger(t), t.Name()) + ctx := logf.NewContext(t.Context(), testr.New(t), t.Name()) server := &testserver.BasicServer{ + T: t, Zones: []string{rfc2136TestZone}, } - if err := server.Run(ctx); err != nil { + if err := server.Run(ctx, "UDP"); err != nil { t.Fatalf("failed to start test server: %v", err) } defer func() { @@ -91,7 +92,7 @@ func TestRunSuiteNoTSIG(t *testing.T) { Nameserver: server.ListenAddr(), } - fixture := dns.NewFixture(&rfc2136.Solver{}, + fixture := dns.NewFixture(rfc2136.New(rfc2136.InitializeResetLister()), dns.SetResolvedZone(rfc2136TestZone), dns.SetResolvedFQDN(rfc2136TestFqdn), dns.SetAllowAmbientCredentials(false), diff --git a/test/integration/rfc2136_dns01/rfc2136_test.go b/test/integration/rfc2136_dns01/rfc2136_test.go index 2fde47637c0..7443704a733 100644 --- a/test/integration/rfc2136_dns01/rfc2136_test.go +++ b/test/integration/rfc2136_dns01/rfc2136_test.go @@ -22,20 +22,19 @@ limitations under the License. package rfc2136 import ( - "context" "fmt" "strings" "testing" "time" - logtesting "github.com/go-logr/logr/testing" + "github.com/go-logr/logr/testr" "github.com/miekg/dns" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/cert-manager/cert-manager/pkg/issuer/acme/dns/rfc2136" logf "github.com/cert-manager/cert-manager/pkg/logs" - testserver "github.com/cert-manager/cert-manager/test/acme/dns/server" + testserver "github.com/cert-manager/cert-manager/test/acme/server" ) var ( @@ -52,12 +51,13 @@ var ( const defaultPort = "53" func TestRFC2136CanaryLocalTestServer(t *testing.T) { - ctx := logf.NewContext(context.TODO(), logtesting.NewTestLogger(t), t.Name()) + ctx := logf.NewContext(t.Context(), testr.New(t), t.Name()) server := &testserver.BasicServer{ + T: t, Zones: []string{rfc2136TestZone}, Handler: dns.HandlerFunc((&testHandlers{t: t}).serverHandlerHello), } - if err := server.Run(ctx); err != nil { + if err := server.Run(ctx, "UDP"); err != nil { t.Fatalf("failed to start test server: %v", err) } defer func() { @@ -78,15 +78,20 @@ func TestRFC2136CanaryLocalTestServer(t *testing.T) { } func TestRFC2136ServerSuccess(t *testing.T) { - ctx := logf.NewContext(context.TODO(), logtesting.NewTestLogger(t), t.Name()) + ctx := logf.NewContext(t.Context(), testr.New(t), t.Name()) server := &testserver.BasicServer{ + T: t, Zones: []string{rfc2136TestZone}, Handler: dns.HandlerFunc((&testHandlers{t: t}).serverHandlerReturnSuccess), } - if err := server.Run(ctx); err != nil { + if err := server.Run(ctx, "UDP"); err != nil { t.Fatalf("failed to start test server: %v", err) } - defer server.Shutdown() + defer func() { + if err := server.Shutdown(); err != nil { + t.Fatalf("failed to shutdown test server: %v", err) + } + }() provider, err := rfc2136.NewDNSProviderCredentials(server.ListenAddr(), "", "", "") if err != nil { @@ -98,15 +103,20 @@ func TestRFC2136ServerSuccess(t *testing.T) { } func TestRFC2136ServerError(t *testing.T) { - ctx := logf.NewContext(context.TODO(), logtesting.NewTestLogger(t), t.Name()) + ctx := logf.NewContext(t.Context(), testr.New(t), t.Name()) server := &testserver.BasicServer{ + T: t, Zones: []string{rfc2136TestZone}, Handler: dns.HandlerFunc((&testHandlers{t: t}).serverHandlerReturnErr), } - if err := server.Run(ctx); err != nil { + if err := server.Run(ctx, "UDP"); err != nil { t.Fatalf("failed to start test server: %v", err) } - defer server.Shutdown() + defer func() { + if err := server.Shutdown(); err != nil { + t.Fatalf("failed to shutdown test server: %v", err) + } + }() provider, err := rfc2136.NewDNSProviderCredentials(server.ListenAddr(), "", "", "") if err != nil { @@ -120,8 +130,9 @@ func TestRFC2136ServerError(t *testing.T) { } func TestRFC2136TsigClient(t *testing.T) { - ctx := logf.NewContext(context.TODO(), logtesting.NewTestLogger(t), t.Name()) + ctx := logf.NewContext(t.Context(), testr.New(t), t.Name()) server := &testserver.BasicServer{ + T: t, Zones: []string{rfc2136TestZone}, Handler: dns.HandlerFunc((&testHandlers{t: t}).serverHandlerReturnSuccess), EnableTSIG: true, @@ -129,10 +140,14 @@ func TestRFC2136TsigClient(t *testing.T) { TSIGKeyName: rfc2136TestTsigKeyName, TSIGKeySecret: rfc2136TestTsigSecret, } - if err := server.Run(ctx); err != nil { + if err := server.Run(ctx, "UDP"); err != nil { t.Fatalf("failed to start test server: %v", err) } - defer server.Shutdown() + defer func() { + if err := server.Shutdown(); err != nil { + t.Fatalf("failed to shutdown test server: %v", err) + } + }() provider, err := rfc2136.NewDNSProviderCredentials(server.ListenAddr(), "", rfc2136TestTsigKeyName, rfc2136TestTsigSecret) if err != nil { @@ -166,7 +181,6 @@ func TestRFC2136NameserverIPv4WithoutPort(t *testing.T) { if dnsProvider.Nameserver() != nameserver+":"+defaultPort { t.Errorf("dnsProvider.Nameserver() to be %v:%v, but it is %v", nameserver, defaultPort, dnsProvider.Nameserver()) } - } func TestRFC2136NameserverIPv4WithEmptyPort(t *testing.T) { @@ -304,14 +318,19 @@ func TestRFC2136InvalidTSIGAlgorithm(t *testing.T) { } func TestRFC2136ValidUpdatePacket(t *testing.T) { - ctx := logf.NewContext(context.TODO(), logtesting.NewTestLogger(t), t.Name()) + ctx := logf.NewContext(t.Context(), testr.New(t), t.Name()) server := &testserver.BasicServer{ + T: t, Zones: []string{rfc2136TestZone}, } - if err := server.Run(ctx); err != nil { + if err := server.Run(ctx, "UDP"); err != nil { t.Fatalf("failed to start test server: %v", err) } - defer server.Shutdown() + defer func() { + if err := server.Shutdown(); err != nil { + t.Errorf("failed to gracefully shut down test server: %v", err) + } + }() txtRR, _ := dns.NewRR(fmt.Sprintf("%s %d IN TXT %s", rfc2136TestFqdn, rfc2136TestTTL, rfc2136TestValue)) rrs := []dns.RR{txtRR} @@ -332,6 +351,38 @@ func TestRFC2136ValidUpdatePacket(t *testing.T) { assert.NoError(t, err) } +func TestRFC2136TCPUpdate(t *testing.T) { + ctx := logf.NewContext(t.Context(), testr.New(t), t.Name()) + server := &testserver.BasicServer{ + T: t, + Zones: []string{rfc2136TestZone}, + } + if err := server.Run(ctx, "tcp"); err != nil { + t.Fatalf("failed to start test server: %v", err) + } + defer func() { + if err := server.Shutdown(); err != nil { + t.Errorf("failed to gracefully shut down test server: %v", err) + } + }() + + txtRR, _ := dns.NewRR(fmt.Sprintf("%s %d IN TXT %s", rfc2136TestFqdn, rfc2136TestTTL, rfc2136TestValue)) + rrs := []dns.RR{txtRR} + m := new(dns.Msg) + m.SetUpdate(rfc2136TestZone) + m.RemoveRRset(rrs) + m.Insert(rrs) + + provider, err := rfc2136.NewDNSProviderCredentials(server.ListenAddr(), "", "", "", rfc2136.WithNetwork("tcp")) + if err != nil { + t.Fatalf("Expected rfc2136.NewDNSProviderCredentials() to return no error but the error was -> %v", err) + } + + if err := provider.Present(rfc2136TestDomain, "_acme-challenge."+rfc2136TestDomain+".", rfc2136TestDomain+".", rfc2136TestValue); err != nil { + t.Errorf("Expected Present() to return no error but the error was -> %v", err) + } +} + // testHandlers provides DNS server handlers for use in tests and has a // reference to testing.T so that the handlers (which do not return errors) can // make test assertions and fail tests. diff --git a/test/integration/validation/certificate_test.go b/test/integration/validation/certificate_test.go index bb86956d20c..a1362844412 100644 --- a/test/integration/validation/certificate_test.go +++ b/test/integration/validation/certificate_test.go @@ -17,20 +17,18 @@ limitations under the License. package validation import ( - "context" "strings" "testing" - "time" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" "sigs.k8s.io/controller-runtime/pkg/client" + "github.com/cert-manager/cert-manager/integration-tests/framework" "github.com/cert-manager/cert-manager/pkg/api" cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" - "github.com/cert-manager/cert-manager/test/integration/framework" ) var certificateGVK = schema.GroupVersionKind{ @@ -55,7 +53,7 @@ func TestValidationCertificate(t *testing.T) { SecretName: "testing-tls", DNSNames: []string{"myhostname.com"}, Usages: []cmapi.KeyUsage{}, - IssuerRef: cmmeta.ObjectReference{ + IssuerRef: cmmeta.IssuerReference{ Name: "letsencrypt-staging", }, PrivateKey: &cmapi.CertificatePrivateKey{ @@ -75,7 +73,7 @@ func TestValidationCertificate(t *testing.T) { SecretName: "testing-tls", DNSNames: []string{"myhostname.com"}, Usages: []cmapi.KeyUsage{}, - IssuerRef: cmmeta.ObjectReference{ + IssuerRef: cmmeta.IssuerReference{ Name: "letsencrypt-staging", }, PrivateKey: &cmapi.CertificatePrivateKey{ @@ -93,20 +91,17 @@ func TestValidationCertificate(t *testing.T) { cert := test.input.(*cmapi.Certificate) cert.SetGroupVersionKind(certificateGVK) - ctx, cancel := context.WithTimeout(context.Background(), time.Second*40) - defer cancel() + config, stopFn := framework.RunControlPlane(t) + t.Cleanup(stopFn) - config, stop := framework.RunControlPlane(t, ctx) - defer stop() - - framework.WaitForOpenAPIResourcesToBeLoaded(t, ctx, config, certificateGVK) + framework.WaitForOpenAPIResourcesToBeLoaded(t, t.Context(), config, certificateGVK) cl, err := client.New(config, client.Options{Scheme: api.Scheme}) if err != nil { t.Fatal(err) } - err = cl.Create(ctx, cert) + err = cl.Create(t.Context(), cert) if test.expectError { if err == nil { diff --git a/test/integration/validation/certificaterequest_test.go b/test/integration/validation/certificaterequest_test.go index 45f48ba44bf..1085b293c1a 100644 --- a/test/integration/validation/certificaterequest_test.go +++ b/test/integration/validation/certificaterequest_test.go @@ -17,22 +17,20 @@ limitations under the License. package validation import ( - "context" - "encoding/pem" "strings" "testing" - "time" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/utils/ptr" "sigs.k8s.io/controller-runtime/pkg/client" + "github.com/cert-manager/cert-manager/integration-tests/framework" "github.com/cert-manager/cert-manager/pkg/api" cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" - "github.com/cert-manager/cert-manager/pkg/util/pki" - "github.com/cert-manager/cert-manager/test/integration/framework" + "github.com/cert-manager/cert-manager/test/unit/gen" ) var certGVK = schema.GroupVersionKind{ @@ -60,7 +58,7 @@ func TestValidationCertificateRequests(t *testing.T) { }, }), Usages: []cmapi.KeyUsage{}, - IssuerRef: cmmeta.ObjectReference{Name: "test"}, + IssuerRef: cmmeta.IssuerReference{Name: "test"}, }, }, expectError: false, @@ -79,7 +77,7 @@ func TestValidationCertificateRequests(t *testing.T) { }, }), Usages: []cmapi.KeyUsage{cmapi.UsageDigitalSignature, cmapi.UsageKeyEncipherment, cmapi.UsageClientAuth}, - IssuerRef: cmmeta.ObjectReference{Name: "test"}, + IssuerRef: cmmeta.IssuerReference{Name: "test"}, }, }, expectError: false, @@ -97,10 +95,11 @@ func TestValidationCertificateRequests(t *testing.T) { Usages: []cmapi.KeyUsage{cmapi.UsageDigitalSignature, cmapi.UsageKeyEncipherment, cmapi.UsageClientAuth}, }, }), - IssuerRef: cmmeta.ObjectReference{Name: "test"}, + IssuerRef: cmmeta.IssuerReference{Name: "test"}, }, }, - expectError: false, + expectError: true, + errorSuffix: "encoded CSR error: the ExtKeyUsages [ 'client auth' ] do not match the expected ExtKeyUsages []", }, "No errors on valid certificaterequest with special usages only set in spec": { input: &cmapi.CertificateRequest{ @@ -111,12 +110,13 @@ func TestValidationCertificateRequests(t *testing.T) { Spec: cmapi.CertificateRequestSpec{ Request: mustGenerateCSR(t, &cmapi.Certificate{ Spec: cmapi.CertificateSpec{ - DNSNames: []string{"example.com"}, - Usages: []cmapi.KeyUsage{}, + DNSNames: []string{"example.com"}, + Usages: []cmapi.KeyUsage{}, + EncodeUsagesInRequest: ptr.To(false), }, }), Usages: []cmapi.KeyUsage{cmapi.UsageDigitalSignature, cmapi.UsageKeyEncipherment, cmapi.UsageClientAuth}, - IssuerRef: cmmeta.ObjectReference{Name: "test"}, + IssuerRef: cmmeta.IssuerReference{Name: "test"}, }, }, expectError: false, @@ -135,11 +135,11 @@ func TestValidationCertificateRequests(t *testing.T) { }, }), Usages: []cmapi.KeyUsage{cmapi.UsageDigitalSignature, cmapi.UsageKeyEncipherment, cmapi.UsageCodeSigning}, - IssuerRef: cmmeta.ObjectReference{Name: "test"}, + IssuerRef: cmmeta.IssuerReference{Name: "test"}, }, }, expectError: true, - errorSuffix: "csr key usages do not match specified usages, these should match if both are set: [[2]: \"client auth\" != \"code signing\"]", + errorSuffix: "encoded CSR error: the ExtKeyUsages [ 'client auth' ] do not match the expected ExtKeyUsages [ 'code signing' ]", }, "Shouldn't error when setting user info, since this will be overwritten by the mutating webhook": { input: &cmapi.CertificateRequest{ @@ -150,12 +150,13 @@ func TestValidationCertificateRequests(t *testing.T) { Spec: cmapi.CertificateRequestSpec{ Request: mustGenerateCSR(t, &cmapi.Certificate{ Spec: cmapi.CertificateSpec{ - DNSNames: []string{"example.com"}, - Usages: []cmapi.KeyUsage{}, + DNSNames: []string{"example.com"}, + Usages: []cmapi.KeyUsage{}, + EncodeUsagesInRequest: ptr.To(false), }, }), Usages: []cmapi.KeyUsage{cmapi.UsageDigitalSignature, cmapi.UsageKeyEncipherment, cmapi.UsageClientAuth}, - IssuerRef: cmmeta.ObjectReference{Name: "test"}, + IssuerRef: cmmeta.IssuerReference{Name: "test"}, Username: "user-1", Groups: []string{"group-1", "group-2"}, }, @@ -168,13 +169,10 @@ func TestValidationCertificateRequests(t *testing.T) { cert := test.input.(*cmapi.CertificateRequest) cert.SetGroupVersionKind(certGVK) - ctx, cancel := context.WithTimeout(context.Background(), time.Second*40) - defer cancel() + config, stopFn := framework.RunControlPlane(t) + t.Cleanup(stopFn) - config, stop := framework.RunControlPlane(t, ctx) - defer stop() - - framework.WaitForOpenAPIResourcesToBeLoaded(t, ctx, config, certGVK) + framework.WaitForOpenAPIResourcesToBeLoaded(t, t.Context(), config, certGVK) // create the object to get any errors back from the webhook cl, err := client.New(config, client.Options{Scheme: api.Scheme}) @@ -182,7 +180,7 @@ func TestValidationCertificateRequests(t *testing.T) { t.Fatal(err) } - err = cl.Create(ctx, cert) + err = cl.Create(t.Context(), cert) if test.expectError != (err != nil) { t.Errorf("unexpected error, exp=%t got=%v", test.expectError, err) @@ -196,22 +194,9 @@ func TestValidationCertificateRequests(t *testing.T) { } func mustGenerateCSR(t *testing.T, cert *cmapi.Certificate) []byte { - request, err := pki.GenerateCSR(cert) + csr, _, err := gen.CSRForCertificate(cert) if err != nil { t.Fatal(err) } - - sk, err := pki.GenerateRSAPrivateKey(2048) - if err != nil { - t.Fatal(err) - } - csrBytes, err := pki.EncodeCSR(request, sk) - if err != nil { - t.Fatal(err) - } - csr := pem.EncodeToMemory(&pem.Block{ - Type: "CERTIFICATE REQUEST", Bytes: csrBytes, - }) - return csr } diff --git a/test/integration/validation/files/issuers.valid.yaml b/test/integration/validation/files/issuers.valid.yaml new file mode 100644 index 00000000000..08073503ad1 --- /dev/null +++ b/test/integration/validation/files/issuers.valid.yaml @@ -0,0 +1,20 @@ +# The AWS Route53 solver uses AWS SDK for Go V2, which can get +# credentials and region information from environment variables. So +# configuration is not **required**. +# This integration test demonstrates that the OpenAPI validation +# **and** webhook validation permit such an empty Route53 stanza. +# YAML and unstructured are used, instead of cert-manager API types to +# avoid confusion around zero values for strings such as region. +apiVersion: cert-manager.io/v1 +kind: Issuer +metadata: + name: test + namespace: default +spec: + acme: + server: https://acme.example.com + privateKeySecretRef: + name: testing-acme-private-key + solvers: + - dns01: + route53: {} diff --git a/test/integration/validation/issuer_test.go b/test/integration/validation/issuer_test.go new file mode 100644 index 00000000000..8ac201d93bc --- /dev/null +++ b/test/integration/validation/issuer_test.go @@ -0,0 +1,75 @@ +/* +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. +*/ + +package validation + +import ( + "fmt" + "io" + "os" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/util/yaml" + "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/cert-manager/cert-manager/integration-tests/framework" + "github.com/cert-manager/cert-manager/pkg/api" +) + +var issuerGVK = schema.GroupVersionKind{ + Group: "cert-manager.io", + Version: "v1", + Kind: "Issuer", +} + +// Regression tests to check Issuer configurations which are expected to pass +// the OpenAPI and webhook validation but which have been accidentally forbidden +// in the past by inconsistent use of validation annotations in the Go types or +// by incorrect logic in the validating webhook functions. +func TestValidationIssuer(t *testing.T) { + yamlFile := "files/issuers.valid.yaml" + yamlBytes, err := os.Open(yamlFile) + require.NoError(t, err) + + config, stopFn := framework.RunControlPlane(t) + t.Cleanup(stopFn) + + framework.WaitForOpenAPIResourcesToBeLoaded(t, t.Context(), config, issuerGVK) + + cl, err := client.New(config, client.Options{Scheme: api.Scheme}) + require.NoError(t, err) + + dec := yaml.NewYAMLOrJSONDecoder(yamlBytes, 4096) + documentIndex := 0 + for { + obj := &unstructured.Unstructured{} + err := dec.Decode(obj) + if err == io.EOF { + break + } + require.NoError(t, err) + name := fmt.Sprintf("%s:%d:%s/%s", yamlFile, documentIndex, obj.GetNamespace(), obj.GetName()) + t.Run(name, func(t *testing.T) { + err = cl.Create(t.Context(), obj) + assert.NoError(t, err) + }) + documentIndex++ + } +} diff --git a/test/integration/versionchecker/getpodfromtemplate_test.go b/test/integration/versionchecker/getpodfromtemplate_test.go deleted file mode 100644 index 60c8f37cfc4..00000000000 --- a/test/integration/versionchecker/getpodfromtemplate_test.go +++ /dev/null @@ -1,94 +0,0 @@ -/* -Copyright 2021 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. -*/ - -package versionchecker - -import ( - "fmt" - - v1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/api/meta" - "k8s.io/apimachinery/pkg/api/validation" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/labels" - "k8s.io/apimachinery/pkg/runtime" - - cmutil "github.com/cert-manager/cert-manager/pkg/util" -) - -// Based on https://github.com/kubernetes/kubernetes/blob/ca643a4d1f7bfe34773c74f79527be4afd95bf39/pkg/controller/controller_utils.go#L542 - -var validatePodName = validation.NameIsDNSSubdomain - -func getPodFromTemplate(template *v1.PodTemplateSpec, parentObject runtime.Object, controllerRef *metav1.OwnerReference) (*v1.Pod, error) { - desiredLabels := getPodsLabelSet(template) - desiredFinalizers := getPodsFinalizers(template) - desiredAnnotations := getPodsAnnotationSet(template) - accessor, err := meta.Accessor(parentObject) - if err != nil { - return nil, fmt.Errorf("parentObject does not have ObjectMeta, %v", err) - } - prefix := getPodsPrefix(accessor.GetName()) - - pod := &v1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Labels: desiredLabels, - Annotations: desiredAnnotations, - GenerateName: prefix, - Name: prefix + cmutil.RandStringRunes(5), - Finalizers: desiredFinalizers, - }, - Status: v1.PodStatus{ - Phase: v1.PodRunning, - }, - } - if controllerRef != nil { - pod.OwnerReferences = append(pod.OwnerReferences, *controllerRef) - } - pod.Spec = *template.Spec.DeepCopy() - return pod, nil -} - -func getPodsLabelSet(template *v1.PodTemplateSpec) labels.Set { - desiredLabels := make(labels.Set) - for k, v := range template.Labels { - desiredLabels[k] = v - } - return desiredLabels -} - -func getPodsFinalizers(template *v1.PodTemplateSpec) []string { - desiredFinalizers := make([]string, len(template.Finalizers)) - copy(desiredFinalizers, template.Finalizers) - return desiredFinalizers -} - -func getPodsAnnotationSet(template *v1.PodTemplateSpec) labels.Set { - desiredAnnotations := make(labels.Set) - for k, v := range template.Annotations { - desiredAnnotations[k] = v - } - return desiredAnnotations -} - -func getPodsPrefix(controllerName string) string { - // use the dash (if the name isn't too long) to make the pod name a bit prettier - prefix := fmt.Sprintf("%s-", controllerName) - if len(validatePodName(prefix, true)) != 0 { - prefix = controllerName - } - return prefix -} diff --git a/test/integration/versionchecker/testdata/.gitignore b/test/integration/versionchecker/testdata/.gitignore deleted file mode 100644 index bd64114247d..00000000000 --- a/test/integration/versionchecker/testdata/.gitignore +++ /dev/null @@ -1 +0,0 @@ -*.tar \ No newline at end of file diff --git a/test/integration/versionchecker/testdata/fetch.sh b/test/integration/versionchecker/testdata/fetch.sh deleted file mode 100755 index 704914b592d..00000000000 --- a/test/integration/versionchecker/testdata/fetch.sh +++ /dev/null @@ -1,72 +0,0 @@ -#!/usr/bin/env bash - -# Copyright 2020 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 - -SCRIPT_ROOT="$( cd "$( dirname "${BASH_SOURCE[0]}" )" > /dev/null && pwd )" - -if [[ $# -eq 4 ]]; then # Running inside bazel - echo "Updating generated clients..." >&2 -elif ! command -v bazel &>/dev/null; then - echo "Install bazel at https://bazel.build" >&2 - exit 1 -else - ( - set -o xtrace - bazel build //test/integration/versionchecker/testdata:test_manifests.tar - cp -f "$(bazel info bazel-bin)/test/integration/versionchecker/testdata/test_manifests.tar" "$SCRIPT_ROOT" - ) - exit 0 -fi - -CURRENT_VERSION=$(cat "$1") # $(location //:version) -current_version_yaml=$(realpath "$2") # $(location //deploy/manifests:cert-manager.yaml) -tags=$(cat "$3") # $(location :git_tags.txt) -test_manifests_tar=$(realpath "$4") # $(location test_manifests.tar) - -shift 4 - -# copy current version's manifest to current folder (will get included in tar) -cp "$current_version_yaml" "$CURRENT_VERSION.yaml" - -manifest_urls="" -for tag in $tags -do - # The "v1.2.0-alpha.1" manifest contains duplicate CRD resources - # (2 CRD resources with the same name); don't download this manifest - # as it will cause the test to fail when adding the CRD resources - # to the fake client - if [[ $tag == "v1.2.0-alpha.1" ]]; then - continue - fi - - manifest_urls+=",$tag" -done - -# remove leading "," in string -manifest_urls=${manifest_urls#","} - -# download all manifests -# --compressed: try gzip compressed download -# -s: don't show progress bar -# -f: fail if non success code -# -L: follow redirects -# -o: output to "#1.yaml" -curl --compressed -sfLo "#1.yaml" "https://github.com/cert-manager/cert-manager/releases/download/{$manifest_urls}/cert-manager.yaml" - -tar -cvf "$test_manifests_tar" *.yaml diff --git a/test/integration/versionchecker/versionchecker_test.go b/test/integration/versionchecker/versionchecker_test.go deleted file mode 100644 index de1f807be1c..00000000000 --- a/test/integration/versionchecker/versionchecker_test.go +++ /dev/null @@ -1,191 +0,0 @@ -/* -Copyright 2021 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. -*/ - -package versionchecker - -import ( - "archive/tar" - "context" - "embed" - "errors" - "io" - "path/filepath" - "strings" - "testing" - - appsv1 "k8s.io/api/apps/v1" - corev1 "k8s.io/api/core/v1" - apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" - apiextensionsv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/cli-runtime/pkg/resource" - kubernetesscheme "k8s.io/client-go/kubernetes/scheme" - "sigs.k8s.io/controller-runtime/pkg/client/fake" - - "github.com/cert-manager/cert-manager/pkg/util/versionchecker" -) - -//go:embed testdata/test_manifests.tar -var testFiles embed.FS - -func loadManifests() (io.Reader, error, func() (string, error), func()) { - data, err := testFiles.Open("testdata/test_manifests.tar") - if err != nil { - return nil, err, nil, nil - } - fileReader := tar.NewReader(data) - - nextFunc := func() (string, error) { - for { - header, err := fileReader.Next() - if err != nil { - return "", err - } - - headerName := filepath.Clean(strings.TrimSuffix(header.Name, ".yaml")) - if headerName == "." { - continue - } - - return headerName, nil - } - } - - closeFunc := func() { - if err := data.Close(); err != nil { - panic(err) - } - } - - return fileReader, nil, nextFunc, closeFunc -} - -func manifestToObject(manifest io.Reader) ([]runtime.Object, error) { - obj, err := resource. - NewLocalBuilder(). - Flatten(). - Unstructured(). - Stream(manifest, ""). - Do(). - Object() - if err != nil { - return nil, err - } - - list, ok := obj.(*corev1.List) - if !ok { - return nil, errors.New("Could not get list") - } - - return transformObjects(list.Items) -} - -func transformObjects(objects []runtime.RawExtension) ([]runtime.Object, error) { - transformedObjects := []runtime.Object{} - for _, resource := range objects { - var err error - gvk := resource.Object.GetObjectKind().GroupVersionKind() - - // Create a pod for a Deployment resource - if gvk.Group == "apps" && gvk.Version == "v1" && gvk.Kind == "Deployment" { - unstr := resource.Object.(*unstructured.Unstructured) - - var deployment appsv1.Deployment - err = runtime.DefaultUnstructuredConverter.FromUnstructured(unstr.Object, &deployment) - if err != nil { - return nil, err - } - - pod, err := getPodFromTemplate(&deployment.Spec.Template, resource.Object, nil) - if err != nil { - return nil, err - } - - transformedObjects = append(transformedObjects, pod) - } - - transformedObjects = append(transformedObjects, resource.Object) - } - - return transformedObjects, nil -} - -func setupFakeVersionChecker(manifest io.Reader) (*versionchecker.VersionChecker, error) { - scheme := runtime.NewScheme() - - if err := kubernetesscheme.AddToScheme(scheme); err != nil { - return nil, err - } - if err := appsv1.AddToScheme(scheme); err != nil { - return nil, err - } - if err := apiextensionsv1.AddToScheme(scheme); err != nil { - return nil, err - } - if err := apiextensionsv1beta1.AddToScheme(scheme); err != nil { - return nil, err - } - - objs, err := manifestToObject(manifest) - if err != nil { - return nil, err - } - - cl := fake. - NewClientBuilder(). - WithScheme(scheme). - WithRuntimeObjects(objs...). - Build() - - return versionchecker.NewFromClient(cl), nil -} - -func TestVersionChecker(t *testing.T) { - f, err, next, close := loadManifests() - if err != nil { - t.Fatal(err) - } - defer close() - - for { - expectedVersionString, err := next() - if err == io.EOF { - break - } else if err != nil { - t.Fatal(err) - } - - t.Run(expectedVersionString, func(t *testing.T) { - checker, err := setupFakeVersionChecker(f) - if err != nil { - t.Error(err) - return - } - - versionGuess, err := checker.Version(context.TODO()) - if err != nil { - t.Errorf("failed to detect expected version %q: %s", expectedVersionString, err) - return - } - - if expectedVersionString != versionGuess.Detected { - t.Errorf("wrong -> expected: %s vs detected: %s", expectedVersionString, versionGuess) - return - } - }) - } -} diff --git a/test/integration/webhook/dynamic_authority_test.go b/test/integration/webhook/dynamic_authority_test.go index c214a7fa97c..8eb371e3f58 100644 --- a/test/integration/webhook/dynamic_authority_test.go +++ b/test/integration/webhook/dynamic_authority_test.go @@ -27,16 +27,16 @@ import ( "time" "github.com/go-logr/logr" - logtesting "github.com/go-logr/logr/testing" + "github.com/go-logr/logr/testr" corev1 "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/wait" "k8s.io/client-go/kubernetes" + "github.com/cert-manager/cert-manager/integration-tests/framework" cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" - "github.com/cert-manager/cert-manager/pkg/webhook/authority" - "github.com/cert-manager/cert-manager/test/integration/framework" + "github.com/cert-manager/cert-manager/pkg/server/tls/authority" ) // Tests for the dynamic authority functionality to ensure it properly handles @@ -45,18 +45,15 @@ import ( // Ensure that when the controller is running against an empty API server, it // creates and stores a new CA keypair. func TestDynamicAuthority_Bootstrap(t *testing.T) { - ctx, cancel := context.WithTimeout(logr.NewContext(context.Background(), logtesting.NewTestLogger(t)), time.Second*40) - defer cancel() + config, stopFn := framework.RunControlPlane(t) + t.Cleanup(stopFn) - config, stop := framework.RunControlPlane(t, ctx) - defer stop() - - kubeClient, _, _, _ := framework.NewClients(t, config) + kubeClient, _, _, _, _ := framework.NewClients(t, config) namespace := "testns" ns := &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: namespace}} - _, err := kubeClient.CoreV1().Namespaces().Create(ctx, ns, metav1.CreateOptions{}) + _, err := kubeClient.CoreV1().Namespaces().Create(t.Context(), ns, metav1.CreateOptions{}) if err != nil { t.Fatal(err) } @@ -67,24 +64,23 @@ func TestDynamicAuthority_Bootstrap(t *testing.T) { RESTConfig: config, } errCh := make(chan error) - defer func() { - cancel() - err := <-errCh - if err != nil { + t.Cleanup(func() { + if err := <-errCh; err != nil { t.Fatal(err) } - }() + }) // run the dynamic authority controller in the background go func() { defer close(errCh) - if err := auth.Run(ctx); err != nil && !errors.Is(err, context.Canceled) { + authCtx := logr.NewContext(t.Context(), testr.New(t)) + if err := auth.Run(authCtx); err != nil && !errors.Is(err, context.Canceled) { errCh <- fmt.Errorf("Unexpected error running authority: %v", err) } }() cl := kubernetes.NewForConfigOrDie(config) // allow the controller to provision the Secret - if err := wait.PollImmediateUntil(time.Millisecond*500, authoritySecretReadyConditionFunc(t, ctx, cl, auth.SecretNamespace, auth.SecretName), ctx.Done()); err != nil { + if err := wait.PollUntilContextCancel(t.Context(), time.Millisecond*500, true, authoritySecretReadyConditionFunc(t, cl, auth.SecretNamespace, auth.SecretName)); err != nil { t.Errorf("Failed waiting for Secret to contain valid certificate: %v", err) return } @@ -93,18 +89,15 @@ func TestDynamicAuthority_Bootstrap(t *testing.T) { // Ensures that when the controller is running and the CA Secret is deleted, // it is automatically recreated within a bounded amount of time. func TestDynamicAuthority_Recreates(t *testing.T) { - ctx, cancel := context.WithTimeout(logr.NewContext(context.Background(), logtesting.NewTestLogger(t)), time.Second*40) - defer cancel() + config, stopFn := framework.RunControlPlane(t) + t.Cleanup(stopFn) - config, stop := framework.RunControlPlane(t, ctx) - defer stop() - - kubeClient, _, _, _ := framework.NewClients(t, config) + kubeClient, _, _, _, _ := framework.NewClients(t, config) namespace := "testns" ns := &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: namespace}} - _, err := kubeClient.CoreV1().Namespaces().Create(ctx, ns, metav1.CreateOptions{}) + _, err := kubeClient.CoreV1().Namespaces().Create(t.Context(), ns, metav1.CreateOptions{}) if err != nil { t.Fatal(err) } @@ -115,35 +108,34 @@ func TestDynamicAuthority_Recreates(t *testing.T) { RESTConfig: config, } errCh := make(chan error) - defer func() { - cancel() - err := <-errCh - if err != nil { + t.Cleanup(func() { + if err := <-errCh; err != nil { t.Fatal(err) } - }() + }) // run the dynamic authority controller in the background go func() { defer close(errCh) - if err := auth.Run(ctx); err != nil && !errors.Is(err, context.Canceled) { + authCtx := logr.NewContext(t.Context(), testr.New(t)) + if err := auth.Run(authCtx); err != nil && !errors.Is(err, context.Canceled) { errCh <- fmt.Errorf("Unexpected error running authority: %v", err) } }() cl := kubernetes.NewForConfigOrDie(config) // allow the controller to provision the Secret - if err := wait.PollImmediateUntil(time.Millisecond*500, authoritySecretReadyConditionFunc(t, ctx, cl, auth.SecretNamespace, auth.SecretName), ctx.Done()); err != nil { + if err := wait.PollUntilContextCancel(t.Context(), time.Millisecond*500, true, authoritySecretReadyConditionFunc(t, cl, auth.SecretNamespace, auth.SecretName)); err != nil { t.Errorf("Failed waiting for Secret to contain valid certificate: %v", err) return } t.Logf("Secret resource has been provisioned, deleting to ensure it is recreated") - if err := cl.CoreV1().Secrets(auth.SecretNamespace).Delete(ctx, auth.SecretName, metav1.DeleteOptions{}); err != nil { + if err := cl.CoreV1().Secrets(auth.SecretNamespace).Delete(t.Context(), auth.SecretName, metav1.DeleteOptions{}); err != nil { t.Fatal(err) } // allow the controller to provision the Secret again - if err := wait.PollImmediateUntil(time.Millisecond*500, authoritySecretReadyConditionFunc(t, ctx, cl, auth.SecretNamespace, auth.SecretName), ctx.Done()); err != nil { + if err := wait.PollUntilContextCancel(t.Context(), time.Millisecond*500, true, authoritySecretReadyConditionFunc(t, cl, auth.SecretNamespace, auth.SecretName)); err != nil { t.Errorf("Failed waiting for Secret to be recreated: %v", err) return } @@ -152,8 +144,8 @@ func TestDynamicAuthority_Recreates(t *testing.T) { // authoritySecretReadyConditionFunc will check a named Secret resource and // check if it contains a valid CA keypair used by the authority. // This can be used with the `k8s.io/apimachinery/pkg/util/wait` package. -func authoritySecretReadyConditionFunc(t *testing.T, ctx context.Context, cl kubernetes.Interface, namespace, name string) wait.ConditionFunc { - return func() (done bool, err error) { +func authoritySecretReadyConditionFunc(t *testing.T, cl kubernetes.Interface, namespace, name string) wait.ConditionWithContextFunc { + return func(ctx context.Context) (done bool, err error) { s, err := cl.CoreV1().Secrets(namespace).Get(ctx, name, metav1.GetOptions{}) if apierrors.IsNotFound(err) { t.Logf("Secret resource %s/%s does not yet exist, waiting...", namespace, name) diff --git a/test/integration/webhook/dynamic_source_test.go b/test/integration/webhook/dynamic_source_test.go index 05b7cd1b779..c2b217dd6a2 100644 --- a/test/integration/webhook/dynamic_source_test.go +++ b/test/integration/webhook/dynamic_source_test.go @@ -22,36 +22,38 @@ import ( "errors" "fmt" "math/big" + "sync/atomic" "testing" "time" "github.com/go-logr/logr" - logtesting "github.com/go-logr/logr/testing" + "github.com/go-logr/logr/testr" + "golang.org/x/sync/errgroup" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/wait" "k8s.io/client-go/kubernetes" + "sigs.k8s.io/controller-runtime/pkg/manager" + "sigs.k8s.io/controller-runtime/pkg/metrics/server" - "github.com/cert-manager/cert-manager/pkg/webhook/authority" - "github.com/cert-manager/cert-manager/pkg/webhook/server/tls" - "github.com/cert-manager/cert-manager/test/integration/framework" + "github.com/cert-manager/cert-manager/integration-tests/framework" + "github.com/cert-manager/cert-manager/pkg/server/tls" + "github.com/cert-manager/cert-manager/pkg/server/tls/authority" + "github.com/cert-manager/cert-manager/test/apiserver" ) // Ensure that when the source is running against an apiserver, it bootstraps // a CA and signs a valid certificate. func TestDynamicSource_Bootstrap(t *testing.T) { - ctx, cancel := context.WithTimeout(logr.NewContext(context.Background(), logtesting.NewTestLogger(t)), time.Second*40) - defer cancel() - - config, stop := framework.RunControlPlane(t, ctx) - defer stop() + config, stopFn := framework.RunControlPlane(t) + t.Cleanup(stopFn) - kubeClient, _, _, _ := framework.NewClients(t, config) + kubeClient, _, _, _, _ := framework.NewClients(t, config) namespace := "testns" ns := &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: namespace}} - _, err := kubeClient.CoreV1().Namespaces().Create(ctx, ns, metav1.CreateOptions{}) + _, err := kubeClient.CoreV1().Namespaces().Create(t.Context(), ns, metav1.CreateOptions{}) if err != nil { t.Fatal(err) } @@ -65,24 +67,23 @@ func TestDynamicSource_Bootstrap(t *testing.T) { }, } errCh := make(chan error) - defer func() { - cancel() - err := <-errCh - if err != nil { + t.Cleanup(func() { + if err := <-errCh; err != nil { t.Fatal(err) } - }() + }) // run the dynamic authority controller in the background go func() { defer close(errCh) - if err := source.Run(ctx); err != nil && !errors.Is(err, context.Canceled) { + authCtx := logr.NewContext(t.Context(), testr.New(t)) + if err := source.Start(authCtx); err != nil && !errors.Is(err, context.Canceled) { errCh <- fmt.Errorf("Unexpected error running source: %v", err) } }() // allow the controller 5s to provision the Secret - this is far longer // than it should ever take. - if err := wait.PollImmediateUntil(time.Millisecond*500, func() (done bool, err error) { + if err := wait.PollUntilContextCancel(t.Context(), time.Millisecond*500, true, func(ctx context.Context) (done bool, err error) { cert, err := source.GetCertificate(nil) if err == tls.ErrNotAvailable { t.Logf("GetCertificate has no certificate available, waiting...") @@ -96,7 +97,7 @@ func TestDynamicSource_Bootstrap(t *testing.T) { } t.Logf("Got non-nil certificate from dynamic source") return true, nil - }, ctx.Done()); err != nil { + }); err != nil { t.Errorf("Failed waiting for source to return a certificate: %v", err) return } @@ -105,18 +106,16 @@ func TestDynamicSource_Bootstrap(t *testing.T) { // Ensure that when the source is running against an apiserver, it bootstraps // a CA and signs a valid certificate. func TestDynamicSource_CARotation(t *testing.T) { - ctx, cancel := context.WithTimeout(logr.NewContext(context.Background(), logtesting.NewTestLogger(t)), time.Second*40) - defer cancel() + config, stopFn := framework.RunControlPlane(t) + t.Cleanup(stopFn) - config, stop := framework.RunControlPlane(t, ctx) - defer stop() + kubeClient, _, _, _, _ := framework.NewClients(t, config) - kubeClient, _, _, _ := framework.NewClients(t, config) + secretName := "testsecret" + secretNamespace := "testns" - namespace := "testns" - - ns := &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: namespace}} - _, err := kubeClient.CoreV1().Namespaces().Create(ctx, ns, metav1.CreateOptions{}) + ns := &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: secretNamespace}} + _, err := kubeClient.CoreV1().Namespaces().Create(t.Context(), ns, metav1.CreateOptions{}) if err != nil { t.Fatal(err) } @@ -124,23 +123,22 @@ func TestDynamicSource_CARotation(t *testing.T) { source := tls.DynamicSource{ DNSNames: []string{"example.com"}, Authority: &authority.DynamicAuthority{ - SecretNamespace: namespace, - SecretName: "testsecret", + SecretName: secretName, + SecretNamespace: secretNamespace, RESTConfig: config, }, } errCh := make(chan error) - defer func() { - cancel() - err := <-errCh - if err != nil { + t.Cleanup(func() { + if err := <-errCh; err != nil { t.Fatal(err) } - }() + }) // run the dynamic authority controller in the background go func() { defer close(errCh) - if err := source.Run(ctx); err != nil && !errors.Is(err, context.Canceled) { + authCtx := logr.NewContext(t.Context(), testr.New(t)) + if err := source.Start(authCtx); err != nil && !errors.Is(err, context.Canceled) { errCh <- fmt.Errorf("Unexpected error running source: %v", err) } }() @@ -148,7 +146,7 @@ func TestDynamicSource_CARotation(t *testing.T) { var serialNumber *big.Int // allow the controller 5s to provision the Secret - this is far longer // than it should ever take. - if err := wait.PollImmediateUntil(time.Millisecond*500, func() (done bool, err error) { + if err := wait.PollUntilContextCancel(t.Context(), time.Millisecond*500, true, func(ctx context.Context) (done bool, err error) { cert, err := source.GetCertificate(nil) if err == tls.ErrNotAvailable { t.Logf("GetCertificate has no certificate available, waiting...") @@ -169,19 +167,19 @@ func TestDynamicSource_CARotation(t *testing.T) { serialNumber = x509cert.SerialNumber return true, nil - }, ctx.Done()); err != nil { + }); err != nil { t.Errorf("Failed waiting for source to return a certificate: %v", err) return } cl := kubernetes.NewForConfigOrDie(config) - if err := cl.CoreV1().Secrets(source.Authority.SecretNamespace).Delete(ctx, source.Authority.SecretName, metav1.DeleteOptions{}); err != nil { + if err := cl.CoreV1().Secrets(secretNamespace).Delete(t.Context(), secretName, metav1.DeleteOptions{}); err != nil { t.Fatalf("Failed to delete CA secret: %v", err) } // wait for the serving certificate to have a new serial number (which // indicates it has been regenerated) - if err := wait.PollImmediateUntil(time.Millisecond*500, func() (done bool, err error) { + if err := wait.PollUntilContextCancel(t.Context(), time.Millisecond*500, true, func(ctx context.Context) (done bool, err error) { cert, err := source.GetCertificate(nil) if err == tls.ErrNotAvailable { t.Logf("GetCertificate has no certificate available, waiting...") @@ -206,8 +204,91 @@ func TestDynamicSource_CARotation(t *testing.T) { } return true, nil - }, ctx.Done()); err != nil { + }); err != nil { t.Errorf("Failed waiting for source to return a certificate: %v", err) return } } + +// Make sure that controller-runtime leader election does not cause the authority +// to not start on non-leader managers. +func TestDynamicSource_leaderelection(t *testing.T) { + const nrManagers = 2 // number of managers to start for this test + + env, stopFn := apiserver.RunBareControlPlane(t) + t.Cleanup(stopFn) + + var started int64 + + gctx, cancel := context.WithCancel(logr.NewContext(t.Context(), testr.New(t))) + defer cancel() + group, gctx := errgroup.WithContext(gctx) + + for i := range nrManagers { + group.Go(func() error { + mgr, err := manager.New(env.Config, manager.Options{ + Metrics: server.Options{BindAddress: "0"}, + BaseContext: func() context.Context { return gctx }, + + LeaderElection: true, + LeaderElectionID: "leader-test", + LeaderElectionNamespace: "default", + }) + if err != nil { + return err + } + + if err := mgr.Add(&tls.DynamicSource{ + DNSNames: []string{"example.com"}, + Authority: &testAuthority{ + t: t, + id: fmt.Sprintf("manager-%d", i), + started: &started, + }, + }); err != nil { + return err + } + + return mgr.Start(gctx) + }) + } + + time.Sleep(4 * time.Second) + + cancel() + + if err := group.Wait(); err != nil { + t.Fatal(err) + } + + startCount := atomic.LoadInt64(&started) + + if startCount != nrManagers { + t.Error("all managers should have started the authority, but only", startCount, "did") + } +} + +type testAuthority struct { + t *testing.T + id string + started *int64 +} + +func (m *testAuthority) Run(ctx context.Context) error { + if ctx.Err() != nil { + return nil //nolint:nilerr // context was cancelled, we are shutting down + } + + m.t.Log("Starting authority with id", m.id) + atomic.AddInt64(m.started, 1) + <-ctx.Done() + return nil +} + +func (m *testAuthority) WatchRotation(ch chan<- struct{}) {} + +func (m *testAuthority) StopWatchingRotation(ch chan<- struct{}) {} + +func (m *testAuthority) Sign(template *x509.Certificate) (*x509.Certificate, error) { + return nil, fmt.Errorf("not implemented") +} diff --git a/test/internal/util/paths.go b/test/internal/util/paths.go deleted file mode 100644 index 886e2b24677..00000000000 --- a/test/internal/util/paths.go +++ /dev/null @@ -1,29 +0,0 @@ -/* -Copyright 2021 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. -*/ - -package util - -import ( - "os" - "path/filepath" -) - -// GetTestPath returns the path for bazel golang test dependencies -// These dependencies are set in the go_test data attribute in the BUILD.bazel file -// see: https://github.com/bazelbuild/rules_go/blob/master/go/core.rst#go_test -> data attribute -func GetTestPath(path ...string) string { - return filepath.Join(append([]string{os.Getenv("RUNFILES_DIR"), "com_github_jetstack_cert_manager"}, path...)...) -} diff --git a/test/unit/crypto/crypto.go b/test/unit/crypto/crypto.go index be86d46c929..7ebe707ad03 100644 --- a/test/unit/crypto/crypto.go +++ b/test/unit/crypto/crypto.go @@ -30,7 +30,6 @@ import ( apiutil "github.com/cert-manager/cert-manager/pkg/api/util" cmapi "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" - "github.com/cert-manager/cert-manager/pkg/controller/certificates" "github.com/cert-manager/cert-manager/pkg/util/pki" "github.com/cert-manager/cert-manager/test/unit/gen" ) @@ -136,7 +135,7 @@ func CreateCryptoBundle(originalCert *cmapi.Certificate, clock clock.Clock) (*Cr }, } - unsignedCert, err := pki.GenerateTemplateFromCertificateRequest(certificateRequest) + unsignedCert, err := pki.CertificateTemplateFromCertificateRequest(certificateRequest) if err != nil { return nil, err } @@ -179,7 +178,7 @@ func CreateCryptoBundle(originalCert *cmapi.Certificate, clock clock.Clock) (*Cr }), ) - tempCertBytes, err := certificates.GenerateLocallySignedTemporaryCertificate(crt, privateKeyBytes) + tempCertBytes, err := pki.GenerateLocallySignedTemporaryCertificate(crt, privateKeyBytes) if err != nil { panic("failed to generate test fixture: " + err.Error()) } @@ -256,7 +255,7 @@ func MustCreateCertWithNotBeforeAfter(t *testing.T, pkData []byte, spec *cmapi.C t.Fatal(err) } - template, err := pki.GenerateTemplate(spec) + template, err := pki.CertificateTemplateFromCertificate(spec) if err != nil { t.Fatal(err) } @@ -279,7 +278,7 @@ func MustCreateCert(t *testing.T, pkData []byte, spec *cmapi.Certificate) []byte t.Fatal(err) } - template, err := pki.GenerateTemplate(spec) + template, err := pki.CertificateTemplateFromCertificate(spec) if err != nil { t.Fatal(err) } diff --git a/test/unit/discovery/discovery.go b/test/unit/discovery/discovery.go index e73ff6c4711..9dced596c81 100644 --- a/test/unit/discovery/discovery.go +++ b/test/unit/discovery/discovery.go @@ -17,7 +17,7 @@ limitations under the License. package discovery import ( - openapi_v2 "github.com/google/gnostic/openapiv2" + openapi_v2 "github.com/google/gnostic-models/openapiv2" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/version" "k8s.io/client-go/discovery" @@ -101,6 +101,12 @@ func (d *Discovery) OpenAPIV3() openapi.Client { return d.openAPIV3SchemaFn() } +func (d *Discovery) WithLegacy() discovery.DiscoveryInterface { + // setting the discovery client to legacy mode (not using the aggregated discovery client) doesn't + // make any difference for our testing purposes here, so we just return the same discovery client + return d +} + func (d *Discovery) RESTClient() restclient.Interface { return d.restClientFn() } diff --git a/test/unit/gen/certificate.go b/test/unit/gen/certificate.go index 22766e228a3..c5f021eb8b7 100644 --- a/test/unit/gen/certificate.go +++ b/test/unit/gen/certificate.go @@ -17,11 +17,10 @@ limitations under the License. package gen import ( - "time" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" + internalv1 "github.com/cert-manager/cert-manager/internal/apis/certmanager/v1" v1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" ) @@ -38,19 +37,21 @@ func Certificate(name string, mods ...CertificateModifier) *v1.Certificate { for _, mod := range mods { mod(c) } + internalv1.SetObjectDefaults_Certificate(c) return c } -func CertificateFrom(crt *v1.Certificate, mods ...CertificateModifier) *v1.Certificate { - crt = crt.DeepCopy() +func CertificateFrom(c *v1.Certificate, mods ...CertificateModifier) *v1.Certificate { + c = c.DeepCopy() for _, mod := range mods { - mod(crt) + mod(c) } - return crt + internalv1.SetObjectDefaults_Certificate(c) + return c } -// SetIssuer sets the Certificate.spec.issuerRef field -func SetCertificateIssuer(o cmmeta.ObjectReference) CertificateModifier { +// SetCertificateIssuer sets the Certificate.spec.issuerRef field +func SetCertificateIssuer(o cmmeta.IssuerReference) CertificateModifier { return func(c *v1.Certificate) { c.Spec.IssuerRef = o } @@ -74,6 +75,12 @@ func SetCertificateIPs(ips ...string) CertificateModifier { } } +func SetCertificateOtherNames(otherNames ...v1.OtherName) CertificateModifier { + return func(crt *v1.Certificate) { + crt.Spec.OtherNames = otherNames + } +} + func SetCertificateEmails(emails ...string) CertificateModifier { return func(crt *v1.Certificate) { crt.Spec.EmailAddresses = emails @@ -126,15 +133,15 @@ func SetCertificateSecretTemplate(annotations, labels map[string]string) Certifi } } -func SetCertificateDuration(duration time.Duration) CertificateModifier { +func SetCertificateDuration(duration *metav1.Duration) CertificateModifier { return func(crt *v1.Certificate) { - crt.Spec.Duration = &metav1.Duration{Duration: duration} + crt.Spec.Duration = duration } } -func SetCertificateRenewBefore(renewBefore time.Duration) CertificateModifier { +func SetCertificateRenewBefore(renewBefore *metav1.Duration) CertificateModifier { return func(crt *v1.Certificate) { - crt.Spec.RenewBefore = &metav1.Duration{Duration: renewBefore} + crt.Spec.RenewBefore = renewBefore } } @@ -191,6 +198,9 @@ func SetCertificateRenewalTime(p metav1.Time) CertificateModifier { func SetCertificateOrganization(orgs ...string) CertificateModifier { return func(ch *v1.Certificate) { + if ch.Spec.Subject == nil { + ch.Spec.Subject = &v1.X509Subject{} + } ch.Spec.Subject.Organizations = orgs } } @@ -225,6 +235,12 @@ func SetCertificateGeneration(gen int64) CertificateModifier { } } +func SetCertificateCreationTimestamp(creationTimestamp metav1.Time) CertificateModifier { + return func(crt *v1.Certificate) { + crt.ObjectMeta.CreationTimestamp = creationTimestamp + } +} + func AddCertificateAnnotations(annotations map[string]string) CertificateModifier { return func(crt *v1.Certificate) { if crt.Annotations == nil { @@ -278,3 +294,9 @@ func SetCertificateAdditionalOutputFormats(additionalOutputFormats ...v1.Certifi crt.Spec.AdditionalOutputFormats = additionalOutputFormats } } + +func SetCertificateKeystore(keystores *v1.CertificateKeystores) CertificateModifier { + return func(crt *v1.Certificate) { + crt.Spec.Keystores = keystores + } +} diff --git a/test/unit/gen/certificaterequest.go b/test/unit/gen/certificaterequest.go index 5d417e48ced..7525ef7a7b4 100644 --- a/test/unit/gen/certificaterequest.go +++ b/test/unit/gen/certificaterequest.go @@ -19,6 +19,7 @@ package gen import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + internalv1 "github.com/cert-manager/cert-manager/internal/apis/certmanager/v1" v1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" ) @@ -26,13 +27,14 @@ import ( type CertificateRequestModifier func(*v1.CertificateRequest) func CertificateRequest(name string, mods ...CertificateRequestModifier) *v1.CertificateRequest { - c := &v1.CertificateRequest{ + cr := &v1.CertificateRequest{ ObjectMeta: ObjectMeta(name), } for _, mod := range mods { - mod(c) + mod(cr) } - return c + internalv1.SetObjectDefaults_CertificateRequest(cr) + return cr } func CertificateRequestFrom(cr *v1.CertificateRequest, mods ...CertificateRequestModifier) *v1.CertificateRequest { @@ -40,11 +42,12 @@ func CertificateRequestFrom(cr *v1.CertificateRequest, mods ...CertificateReques for _, mod := range mods { mod(cr) } + internalv1.SetObjectDefaults_CertificateRequest(cr) return cr } -// SetIssuer sets the CertificateRequest.spec.issuerRef field -func SetCertificateRequestIssuer(o cmmeta.ObjectReference) CertificateRequestModifier { +// SetCertificateRequestIssuer sets the CertificateRequest.spec.issuerRef field +func SetCertificateRequestIssuer(o cmmeta.IssuerReference) CertificateRequestModifier { return func(c *v1.CertificateRequest) { c.Spec.IssuerRef = o } diff --git a/test/unit/gen/challenge.go b/test/unit/gen/challenge.go index 408c516551a..5a37b944d53 100644 --- a/test/unit/gen/challenge.go +++ b/test/unit/gen/challenge.go @@ -17,21 +17,24 @@ limitations under the License. package gen import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + internalv1 "github.com/cert-manager/cert-manager/internal/apis/acme/v1" cmacme "github.com/cert-manager/cert-manager/pkg/apis/acme/v1" cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) type ChallengeModifier func(*cmacme.Challenge) func Challenge(name string, mods ...ChallengeModifier) *cmacme.Challenge { - c := &cmacme.Challenge{ + ch := &cmacme.Challenge{ ObjectMeta: ObjectMeta(name), } for _, mod := range mods { - mod(c) + mod(ch) } - return c + internalv1.SetObjectDefaults_Challenge(ch) + return ch } func ChallengeFrom(ch *cmacme.Challenge, mods ...ChallengeModifier) *cmacme.Challenge { @@ -39,6 +42,7 @@ func ChallengeFrom(ch *cmacme.Challenge, mods ...ChallengeModifier) *cmacme.Chal for _, mod := range mods { mod(ch) } + internalv1.SetObjectDefaults_Challenge(ch) return ch } @@ -60,8 +64,14 @@ func SetChallengeToken(t string) ChallengeModifier { } } -// SetIssuer sets the challenge.spec.issuerRef field -func SetChallengeIssuer(o cmmeta.ObjectReference) ChallengeModifier { +func SetChallengeKey(k string) ChallengeModifier { + return func(ch *cmacme.Challenge) { + ch.Spec.Key = k + } +} + +// SetChallengeIssuer sets the challenge.spec.issuerRef field +func SetChallengeIssuer(o cmmeta.IssuerReference) ChallengeModifier { return func(c *cmacme.Challenge) { c.Spec.IssuerRef = o } @@ -126,3 +136,9 @@ func ResetChallengeStatus() ChallengeModifier { ch.Status = cmacme.ChallengeStatus{} } } + +func SetChallengeSolverDNS01(solver cmacme.ACMEChallengeSolverDNS01) ChallengeModifier { + return func(ch *cmacme.Challenge) { + ch.Spec.Solver.DNS01 = &solver + } +} diff --git a/test/unit/gen/csr.go b/test/unit/gen/csr.go index db2fcb0d1e1..a135c11ce8e 100644 --- a/test/unit/gen/csr.go +++ b/test/unit/gen/csr.go @@ -18,56 +18,145 @@ package gen import ( "crypto" + "crypto/ecdsa" + "crypto/ed25519" + "crypto/elliptic" + "crypto/rsa" "crypto/x509" "encoding/pem" "fmt" "net" "net/url" + v1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" "github.com/cert-manager/cert-manager/pkg/util/pki" ) -type CSRModifier func(*x509.CertificateRequest) +type CSRModifier func(*x509.CertificateRequest) error -func CSR(keyAlgorithm x509.PublicKeyAlgorithm, mods ...CSRModifier) (csr []byte, sk crypto.Signer, err error) { - var signatureAlgorithm x509.SignatureAlgorithm +var defaultGenerateCSROptions = []pki.GenerateCSROption{ + pki.WithEncodeBasicConstraintsInRequest(true), + pki.WithNameConstraints(true), + pki.WithOtherNames(true), + pki.WithUseLiteralSubject(true), +} + +func CSRForCertificate(crt *v1.Certificate, mods ...CSRModifier) (csr []byte, sk crypto.Signer, err error) { + cr, err := pki.GenerateCSR(crt, defaultGenerateCSROptions...) + if err != nil { + return nil, nil, err + } + + modifiers := []CSRModifier{} + modifiers = append(modifiers, func(c *x509.CertificateRequest) error { + *c = *cr + return nil + }) + modifiers = append(modifiers, mods...) + + return CSR(cr.PublicKeyAlgorithm, modifiers...) +} +func CSRWithSignerForCertificate(crt *v1.Certificate, sk crypto.Signer, mods ...CSRModifier) (csr []byte, err error) { + cr, err := pki.GenerateCSR(crt, defaultGenerateCSROptions...) + if err != nil { + return nil, err + } + + modifiers := []CSRModifier{} + modifiers = append(modifiers, func(c *x509.CertificateRequest) error { + if c.PublicKeyAlgorithm != cr.PublicKeyAlgorithm { + return fmt.Errorf("public key algorithm mismatch: %s != %s", c.PublicKeyAlgorithm, cr.PublicKeyAlgorithm) + } + if c.SignatureAlgorithm != cr.SignatureAlgorithm { + return fmt.Errorf("signature algorithm mismatch: %s != %s", c.SignatureAlgorithm, cr.SignatureAlgorithm) + } + *c = *cr + return nil + }) + modifiers = append(modifiers, mods...) + + return CSRWithSigner(sk, modifiers...) +} + +func CSR(keyAlgorithm x509.PublicKeyAlgorithm, mods ...CSRModifier) (csr []byte, sk crypto.Signer, err error) { switch keyAlgorithm { case x509.RSA: - sk, err = pki.GenerateRSAPrivateKey(2048) + sk, err = pki.GenerateRSAPrivateKey(pki.MinRSAKeySize) if err != nil { return nil, nil, err } - signatureAlgorithm = x509.SHA256WithRSA case x509.ECDSA: sk, err = pki.GenerateECPrivateKey(pki.ECCurve256) if err != nil { return nil, nil, err } - signatureAlgorithm = x509.ECDSAWithSHA256 case x509.Ed25519: sk, err = pki.GenerateEd25519PrivateKey() if err != nil { return nil, nil, err } + default: + return nil, nil, fmt.Errorf("unrecognised key algorithm: %s", keyAlgorithm) + } + + csr, err = CSRWithSigner(sk, mods...) + return +} + +func CSRWithSigner(sk crypto.Signer, mods ...CSRModifier) (csr []byte, err error) { + var keyAlgorithm x509.PublicKeyAlgorithm + var signatureAlgorithm x509.SignatureAlgorithm + + switch pub := sk.Public().(type) { + case *rsa.PublicKey: + keyAlgorithm = x509.RSA + keySize := pub.N.BitLen() + switch { + case keySize >= 4096: + signatureAlgorithm = x509.SHA512WithRSA + case keySize >= 3072: + signatureAlgorithm = x509.SHA384WithRSA + case keySize >= 2048: + signatureAlgorithm = x509.SHA256WithRSA + default: + signatureAlgorithm = x509.SHA1WithRSA + } + case *ecdsa.PublicKey: + keyAlgorithm = x509.ECDSA + switch pub.Curve { + case elliptic.P256(): + signatureAlgorithm = x509.ECDSAWithSHA256 + case elliptic.P384(): + signatureAlgorithm = x509.ECDSAWithSHA384 + case elliptic.P521(): + signatureAlgorithm = x509.ECDSAWithSHA512 + default: + signatureAlgorithm = x509.ECDSAWithSHA1 + } + case ed25519.PublicKey: + keyAlgorithm = x509.Ed25519 signatureAlgorithm = x509.PureEd25519 default: - return nil, nil, fmt.Errorf("unrecognised key algorithm: %s", err) + return nil, fmt.Errorf("unrecognised public key type: %T", sk) } cr := &x509.CertificateRequest{ - Version: 3, + Version: 0, SignatureAlgorithm: signatureAlgorithm, PublicKeyAlgorithm: keyAlgorithm, PublicKey: sk.Public(), } for _, mod := range mods { - mod(cr) + err = mod(cr) + if err != nil { + return + } } csrBytes, err := pki.EncodeCSR(cr, sk) if err != nil { - return nil, nil, err + return nil, err } csr = pem.EncodeToMemory(&pem.Block{ Type: "CERTIFICATE REQUEST", Bytes: csrBytes, @@ -76,31 +165,66 @@ func CSR(keyAlgorithm x509.PublicKeyAlgorithm, mods ...CSRModifier) (csr []byte, } func SetCSRDNSNames(dnsNames ...string) CSRModifier { - return func(c *x509.CertificateRequest) { + return func(c *x509.CertificateRequest) error { c.DNSNames = dnsNames + return nil } } func SetCSRIPAddresses(ips ...net.IP) CSRModifier { - return func(c *x509.CertificateRequest) { + return func(c *x509.CertificateRequest) error { c.IPAddresses = ips + return nil + } +} + +func SetCSRIPAddressesFromStrings(ips ...string) CSRModifier { + return func(c *x509.CertificateRequest) error { + var certIPs []net.IP + for _, ip := range ips { + if certIP := net.ParseIP(ip); certIP == nil { + return fmt.Errorf("invalid ip: %s", ip) + } else { + certIPs = append(certIPs, certIP) + } + } + c.IPAddresses = certIPs + return nil } } func SetCSRURIs(uris ...*url.URL) CSRModifier { - return func(c *x509.CertificateRequest) { + return func(c *x509.CertificateRequest) error { c.URIs = uris + return nil + } +} + +func SetCSRURIsFromStrings(uris ...string) CSRModifier { + return func(c *x509.CertificateRequest) error { + var certUris []*url.URL + for _, uri := range uris { + parsed, err := url.Parse(uri) + if err != nil { + return err + } + certUris = append(certUris, parsed) + } + c.URIs = certUris + return nil } } func SetCSRCommonName(commonName string) CSRModifier { - return func(c *x509.CertificateRequest) { + return func(c *x509.CertificateRequest) error { c.Subject.CommonName = commonName + return nil } } func SetCSREmails(emails []string) CSRModifier { - return func(c *x509.CertificateRequest) { + return func(c *x509.CertificateRequest) error { c.EmailAddresses = emails + return nil } } diff --git a/test/unit/gen/issuer.go b/test/unit/gen/issuer.go index 1d2ca419789..d2706a66511 100644 --- a/test/unit/gen/issuer.go +++ b/test/unit/gen/issuer.go @@ -17,10 +17,11 @@ limitations under the License. package gen import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + cmacme "github.com/cert-manager/cert-manager/pkg/apis/acme/v1" v1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) type IssuerModifier func(v1.GenericIssuer) @@ -95,6 +96,16 @@ func SetIssuerACME(a cmacme.ACMEIssuer) IssuerModifier { } } +func SetIssuerACMEPreferredChain(chain string) IssuerModifier { + return func(iss v1.GenericIssuer) { + spec := iss.GetSpec() + if spec.ACME == nil { + spec.ACME = &cmacme.ACMEIssuer{} + } + spec.ACME.PreferredChain = chain + } +} + func SetIssuerACMEURL(url string) IssuerModifier { return func(iss v1.GenericIssuer) { spec := iss.GetSpec() @@ -114,6 +125,17 @@ func SetIssuerACMEEmail(email string) IssuerModifier { spec.ACME.Email = email } } + +func SetIssuerACMEProfile(profile string) IssuerModifier { + return func(iss v1.GenericIssuer) { + spec := iss.GetSpec() + if spec.ACME == nil { + spec.ACME = &cmacme.ACMEIssuer{} + } + spec.ACME.Profile = profile + } +} + func SetIssuerACMEPrivKeyRef(privateKeyName string) IssuerModifier { return func(iss v1.GenericIssuer) { spec := iss.GetSpec() @@ -226,6 +248,16 @@ func SetIssuerACMELastRegisteredEmail(email string) IssuerModifier { } } +func SetIssuerACMELastPrivateKeyHash(privateKeyHash string) IssuerModifier { + return func(iss v1.GenericIssuer) { + status := iss.GetStatus() + if status.ACME == nil { + status.ACME = &cmacme.ACMEIssuerStatus{} + } + status.ACME.LastPrivateKeyHash = privateKeyHash + } +} + func SetIssuerCA(a v1.CAIssuer) IssuerModifier { return func(iss v1.GenericIssuer) { iss.GetSpec().CA = &a @@ -247,6 +279,7 @@ func SetIssuerVault(v v1.VaultIssuer) IssuerModifier { iss.GetSpec().Vault = &v } } + func SetIssuerVaultURL(url string) IssuerModifier { return func(iss v1.GenericIssuer) { spec := iss.GetSpec() @@ -292,6 +325,36 @@ func SetIssuerVaultCABundleSecretRef(name, namespace, key string) IssuerModifier } } +func SetIssuerVaultClientCertSecretRef(vaultClientCertificateSecretName, key string) IssuerModifier { + return func(iss v1.GenericIssuer) { + spec := iss.GetSpec() + if spec.Vault == nil { + spec.Vault = &v1.VaultIssuer{} + } + spec.Vault.ClientCertSecretRef = &cmmeta.SecretKeySelector{ + LocalObjectReference: cmmeta.LocalObjectReference{ + Name: vaultClientCertificateSecretName, + }, + Key: key, + } + } +} + +func SetIssuerVaultClientKeySecretRef(vaultClientCertificateSecretName, key string) IssuerModifier { + return func(iss v1.GenericIssuer) { + spec := iss.GetSpec() + if spec.Vault == nil { + spec.Vault = &v1.VaultIssuer{} + } + spec.Vault.ClientKeySecretRef = &cmmeta.SecretKeySelector{ + LocalObjectReference: cmmeta.LocalObjectReference{ + Name: vaultClientCertificateSecretName, + }, + Key: key, + } + } +} + func SetIssuerVaultTokenAuth(keyName, tokenName string) IssuerModifier { return func(iss v1.GenericIssuer) { spec := iss.GetSpec() @@ -325,25 +388,56 @@ func SetIssuerVaultAppRoleAuth(keyName, approleName, roleId, path string) Issuer } } -func SetIssuerVaultKubernetesAuth(keyName, secretServiceAccount, role, path string) IssuerModifier { +func SetIssuerVaultClientCertificateAuth(path, secretName string) IssuerModifier { + return func(iss v1.GenericIssuer) { + spec := iss.GetSpec() + if spec.Vault == nil { + spec.Vault = &v1.VaultIssuer{} + } + spec.Vault.Auth.ClientCertificate = &v1.VaultClientCertificateAuth{ + Path: path, + SecretName: secretName, + } + } +} + +func SetIssuerVaultKubernetesAuthSecret(secretKey, secretName, vaultRole, vaultPath string) IssuerModifier { return func(iss v1.GenericIssuer) { spec := iss.GetSpec() if spec.Vault == nil { spec.Vault = &v1.VaultIssuer{} } spec.Vault.Auth.Kubernetes = &v1.VaultKubernetesAuth{ - Path: path, + Path: vaultPath, SecretRef: cmmeta.SecretKeySelector{ - Key: keyName, + Key: secretKey, LocalObjectReference: cmmeta.LocalObjectReference{ - Name: secretServiceAccount, + Name: secretName, }, }, + Role: vaultRole, + } + + } +} + +func SetIssuerVaultKubernetesAuthServiceAccount(serviceAccount, role, path string) IssuerModifier { + return func(iss v1.GenericIssuer) { + spec := iss.GetSpec() + if spec.Vault == nil { + spec.Vault = &v1.VaultIssuer{} + } + spec.Vault.Auth.Kubernetes = &v1.VaultKubernetesAuth{ + Path: path, Role: role, + ServiceAccountRef: &v1.ServiceAccountRef{ + Name: serviceAccount, + }, } } } + func SetIssuerSelfSigned(a v1.SelfSignedIssuer) IssuerModifier { return func(iss v1.GenericIssuer) { iss.GetSpec().SelfSigned = &a diff --git a/test/unit/gen/order.go b/test/unit/gen/order.go index fe0edb5fc4e..95ed3ef79cc 100644 --- a/test/unit/gen/order.go +++ b/test/unit/gen/order.go @@ -21,6 +21,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + internalv1 "github.com/cert-manager/cert-manager/internal/apis/acme/v1" cmacme "github.com/cert-manager/cert-manager/pkg/apis/acme/v1" cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" ) @@ -34,6 +35,7 @@ func Order(name string, mods ...OrderModifier) *cmacme.Order { for _, mod := range mods { mod(order) } + internalv1.SetObjectDefaults_Order(order) return order } @@ -42,11 +44,12 @@ func OrderFrom(order *cmacme.Order, mods ...OrderModifier) *cmacme.Order { for _, mod := range mods { mod(order) } + internalv1.SetObjectDefaults_Order(order) return order } -// SetIssuer sets the Order.spec.issuerRef field -func SetOrderIssuer(o cmmeta.ObjectReference) OrderModifier { +// SetOrderIssuer sets the Order.spec.issuerRef field +func SetOrderIssuer(o cmmeta.IssuerReference) OrderModifier { return func(order *cmacme.Order) { order.Spec.IssuerRef = o } @@ -129,3 +132,9 @@ func SetOrderOwnerReference(ref metav1.OwnerReference) OrderModifier { order.OwnerReferences = []metav1.OwnerReference{ref} } } + +func SetOrderProfile(profile string) OrderModifier { + return func(order *cmacme.Order) { + order.Spec.Profile = profile + } +} diff --git a/cmd/webhook/app/testing/testwebhook.go b/test/webhook/testwebhook.go similarity index 83% rename from cmd/webhook/app/testing/testwebhook.go rename to test/webhook/testwebhook.go index 62211fb5323..4a59c468c3f 100644 --- a/cmd/webhook/app/testing/testwebhook.go +++ b/test/webhook/testwebhook.go @@ -31,17 +31,18 @@ import ( "testing" "time" - logtesting "github.com/go-logr/logr/testing" + "github.com/go-logr/logr/testr" "github.com/spf13/pflag" "k8s.io/apimachinery/pkg/util/wait" - "k8s.io/utils/pointer" - "github.com/cert-manager/cert-manager/cmd/webhook/app/options" "github.com/cert-manager/cert-manager/internal/webhook" "github.com/cert-manager/cert-manager/pkg/util/pki" + "github.com/cert-manager/cert-manager/pkg/webhook/options" "github.com/cert-manager/cert-manager/pkg/webhook/server" ) +// NOTE: all functions that return a StopFunc should use +// context.WithCancel(t.Context()) instead of just t.Context() type StopFunc func() type ServerOptions struct { @@ -56,8 +57,12 @@ type ServerOptions struct { CAPEM []byte } -func StartWebhookServer(t *testing.T, ctx context.Context, args []string, argumentsForNewServerWithOptions ...func(*server.Server)) (ServerOptions, StopFunc) { - log := logtesting.NewTestLogger(t) +func StartWebhookServer(t *testing.T, args []string, argumentsForNewServerWithOptions ...func(*server.Server)) (ServerOptions, StopFunc) { + // Making sure the rootCtx is canceled when StopFunc is called + // even when t.Context() has not been canceled yet. + stoppableCtx, stopCtxFn := context.WithCancel(t.Context()) + + log := testr.New(t) fs := pflag.NewFlagSet("testset", pflag.ExitOnError) webhookFlags := options.NewWebhookFlags() @@ -68,13 +73,12 @@ func StartWebhookServer(t *testing.T, ctx context.Context, args []string, argume webhookFlags.AddFlags(fs) options.AddConfigFlags(fs, webhookConfig) // Parse the arguments passed in into the WebhookOptions struct - fs.Parse(args) + if err := fs.Parse(args); err != nil { + t.Fatalf("Failed parsing arguments: %v", err) + } var caPEM []byte - tempDir, err := os.MkdirTemp("", "webhook-tls-") - if err != nil { - t.Fatal(err) - } + tempDir := t.TempDir() if !webhookConfig.TLSConfig.FilesystemConfigProvided() && !webhookConfig.TLSConfig.DynamicConfigProvided() { // Generate a CA and serving certificate ca, certificatePEM, privateKeyPEM, err := generateTLSAssets() @@ -95,26 +99,25 @@ func StartWebhookServer(t *testing.T, ctx context.Context, args []string, argume } // Listen on a random port number - webhookConfig.SecurePort = pointer.Int(0) - webhookConfig.HealthzPort = pointer.Int(0) + webhookConfig.SecurePort = 0 + webhookConfig.HealthzPort = 0 errCh := make(chan error) - srv, err := webhook.NewCertManagerWebhookServer(log, *webhookFlags, *webhookConfig, argumentsForNewServerWithOptions...) + srv, err := webhook.NewCertManagerWebhookServer(log, *webhookConfig, argumentsForNewServerWithOptions...) if err != nil { t.Fatal(err) } - ctx, cancel := context.WithCancel(ctx) go func() { defer close(errCh) - if err := srv.Run(ctx); err != nil { + if err := srv.Run(stoppableCtx); err != nil { errCh <- fmt.Errorf("error running webhook server: %v", err) } }() // Determine the random port number that was chosen var listenPort int - if err = wait.PollImmediateUntil(100*time.Millisecond, func() (bool, error) { + if err := wait.PollUntilContextCancel(stoppableCtx, 100*time.Millisecond, true, func(_ context.Context) (bool, error) { listenPort, err = srv.Port() if err != nil { if errors.Is(err, server.ErrNotListening) { @@ -123,7 +126,7 @@ func StartWebhookServer(t *testing.T, ctx context.Context, args []string, argume return false, err } return true, nil - }, ctx.Done()); err != nil { + }); err != nil { t.Fatalf("Failed waiting for ListenPort to be allocated (got error: %v)", err) } @@ -132,7 +135,7 @@ func StartWebhookServer(t *testing.T, ctx context.Context, args []string, argume CAPEM: caPEM, } return serverOpts, func() { - cancel() + stopCtxFn() err := <-errCh // Wait for shutdown if err != nil { t.Fatal(err) @@ -149,7 +152,6 @@ func generateTLSAssets() (caPEM, certificatePEM, privateKeyPEM []byte, err error return nil, nil, nil, err } rootCA := &x509.Certificate{ - Version: 3, BasicConstraintsValid: true, SerialNumber: big.NewInt(1658), PublicKeyAlgorithm: x509.RSA, @@ -170,7 +172,6 @@ func generateTLSAssets() (caPEM, certificatePEM, privateKeyPEM []byte, err error return nil, nil, nil, err } servingCert := &x509.Certificate{ - Version: 3, BasicConstraintsValid: true, SerialNumber: big.NewInt(1659), PublicKeyAlgorithm: x509.RSA, diff --git a/third_party/README.md b/third_party/README.md new file mode 100644 index 00000000000..dce5a3e95aa --- /dev/null +++ b/third_party/README.md @@ -0,0 +1,21 @@ +# README + +cert-manager follows the [Kubernetes conventions for third_party code]: + +- forked third party Go code goes in `third_party/forked`. +- forked _golang stdlib_ code goes in `third_party/forked/golang`. + +Third-party code must include licenses. This includes modified third-party code and excerpts, as well. + +[Kubernetes conventions for third_party code]: https://github.com/kubernetes/community/blob/master/contributors/guide/coding-conventions.md#directory-and-file-conventions + +To update the `third_party/` code: + +```bash +make update-third-party +``` + +To add a new `third_party/` sub-folder: +- Add the new folder and the source repo to `klone.yaml` +- Update `make/third_party.mk` to perform an additional post-processing of the cloned files. +- Update the import statements in any cert-manager Go packages to import the package from the third_party folder instead of the upstream Go module. diff --git a/third_party/forked/acme/LICENSE b/third_party/forked/acme/LICENSE new file mode 100644 index 00000000000..2a7cf70da6e --- /dev/null +++ b/third_party/forked/acme/LICENSE @@ -0,0 +1,27 @@ +Copyright 2009 The Go Authors. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google LLC nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/third_party/forked/acme/PATENTS b/third_party/forked/acme/PATENTS new file mode 100644 index 00000000000..733099041f8 --- /dev/null +++ b/third_party/forked/acme/PATENTS @@ -0,0 +1,22 @@ +Additional IP Rights Grant (Patents) + +"This implementation" means the copyrightable works distributed by +Google as part of the Go project. + +Google hereby grants to You a perpetual, worldwide, non-exclusive, +no-charge, royalty-free, irrevocable (except as stated in this section) +patent license to make, have made, use, offer to sell, sell, import, +transfer and otherwise run, modify and propagate the contents of this +implementation of Go, where such license applies only to those patent +claims, both currently owned or controlled by Google and acquired in +the future, licensable by Google that are necessarily infringed by this +implementation of Go. This grant does not include claims that would be +infringed only as a consequence of further modification of this +implementation. If you or your agent or exclusive licensee institute or +order or agree to the institution of patent litigation against any +entity (including a cross-claim or counterclaim in a lawsuit) alleging +that this implementation of Go or any code incorporated within this +implementation of Go constitutes direct or contributory patent +infringement, or inducement of patent infringement, then any patent +rights granted to you under this License for this implementation of Go +shall terminate as of the date such litigation is filed. diff --git a/third_party/forked/acme/acme.go b/third_party/forked/acme/acme.go new file mode 100644 index 00000000000..c61165bd332 --- /dev/null +++ b/third_party/forked/acme/acme.go @@ -0,0 +1,828 @@ +// Copyright 2015 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package acme provides an implementation of the +// Automatic Certificate Management Environment (ACME) spec, +// most famously used by Let's Encrypt. +// +// The initial implementation of this package was based on an early version +// of the spec. The current implementation supports only the modern +// RFC 8555 but some of the old API surface remains for compatibility. +// While code using the old API will still compile, it will return an error. +// Note the deprecation comments to update your code. +// +// See https://tools.ietf.org/html/rfc8555 for the spec. +// +// Most common scenarios will want to use autocert subdirectory instead, +// which provides automatic access to certificates from Let's Encrypt +// and any other ACME-based CA. +package acme + +import ( + "context" + "crypto" + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "crypto/sha256" + "crypto/tls" + "crypto/x509" + "crypto/x509/pkix" + "encoding/asn1" + "encoding/base64" + "encoding/hex" + "encoding/json" + "encoding/pem" + "errors" + "fmt" + "math/big" + "net/http" + "strings" + "sync" + "time" +) + +const ( + // LetsEncryptURL is the Directory endpoint of Let's Encrypt CA. + LetsEncryptURL = "https://acme-v02.api.letsencrypt.org/directory" + + // ALPNProto is the ALPN protocol name used by a CA server when validating + // tls-alpn-01 challenges. + // + // Package users must ensure their servers can negotiate the ACME ALPN in + // order for tls-alpn-01 challenge verifications to succeed. + // See the crypto/tls package's Config.NextProtos field. + ALPNProto = "acme-tls/1" +) + +// idPeACMEIdentifier is the OID for the ACME extension for the TLS-ALPN challenge. +// https://tools.ietf.org/html/draft-ietf-acme-tls-alpn-05#section-5.1 +var idPeACMEIdentifier = asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 1, 31} + +const ( + maxChainLen = 5 // max depth and breadth of a certificate chain + maxCertSize = 1 << 20 // max size of a certificate, in DER bytes + // Used for decoding certs from application/pem-certificate-chain response, + // the default when in RFC mode. + maxCertChainSize = maxCertSize * maxChainLen + + // Max number of collected nonces kept in memory. + // Expect usual peak of 1 or 2. + maxNonces = 100 +) + +// Client is an ACME client. +// +// The only required field is Key. An example of creating a client with a new key +// is as follows: +// +// key, err := rsa.GenerateKey(rand.Reader, 2048) +// if err != nil { +// log.Fatal(err) +// } +// client := &Client{Key: key} +type Client struct { + // Key is the account key used to register with a CA and sign requests. + // Key.Public() must return a *rsa.PublicKey or *ecdsa.PublicKey. + // + // The following algorithms are supported: + // RS256, ES256, ES384 and ES512. + // See RFC 7518 for more details about the algorithms. + Key crypto.Signer + + // HTTPClient optionally specifies an HTTP client to use + // instead of http.DefaultClient. + HTTPClient *http.Client + + // DirectoryURL points to the CA directory endpoint. + // If empty, LetsEncryptURL is used. + // Mutating this value after a successful call of Client's Discover method + // will have no effect. + DirectoryURL string + + // RetryBackoff computes the duration after which the nth retry of a failed request + // should occur. The value of n for the first call on failure is 1. + // The values of r and resp are the request and response of the last failed attempt. + // If the returned value is negative or zero, no more retries are done and an error + // is returned to the caller of the original method. + // + // Requests which result in a 4xx client error are not retried, + // except for 400 Bad Request due to "bad nonce" errors and 429 Too Many Requests. + // + // If RetryBackoff is nil, a truncated exponential backoff algorithm + // with the ceiling of 10 seconds is used, where each subsequent retry n + // is done after either ("Retry-After" + jitter) or (2^n seconds + jitter), + // preferring the former if "Retry-After" header is found in the resp. + // The jitter is a random value up to 1 second. + RetryBackoff func(n int, r *http.Request, resp *http.Response) time.Duration + + // UserAgent is prepended to the User-Agent header sent to the ACME server, + // which by default is this package's name and version. + // + // Reusable libraries and tools in particular should set this value to be + // identifiable by the server, in case they are causing issues. + UserAgent string + + cacheMu sync.Mutex + dir *Directory // cached result of Client's Discover method + // KID is the key identifier provided by the CA. If not provided it will be + // retrieved from the CA by making a call to the registration endpoint. + KID KeyID + + noncesMu sync.Mutex + nonces map[string]struct{} // nonces collected from previous responses +} + +// accountKID returns a key ID associated with c.Key, the account identity +// provided by the CA during RFC based registration. +// It assumes c.Discover has already been called. +// +// accountKID requires at most one network roundtrip. +// It caches only successful result. +// +// When in pre-RFC mode or when c.getRegRFC responds with an error, accountKID +// returns noKeyID. +func (c *Client) accountKID(ctx context.Context) KeyID { + c.cacheMu.Lock() + defer c.cacheMu.Unlock() + if c.KID != noKeyID { + return c.KID + } + a, err := c.getRegRFC(ctx) + if err != nil { + return noKeyID + } + c.KID = KeyID(a.URI) + return c.KID +} + +var errPreRFC = errors.New("acme: server does not support the RFC 8555 version of ACME") + +// Discover performs ACME server discovery using c.DirectoryURL. +// +// It caches successful result. So, subsequent calls will not result in +// a network round-trip. This also means mutating c.DirectoryURL after successful call +// of this method will have no effect. +func (c *Client) Discover(ctx context.Context) (Directory, error) { + c.cacheMu.Lock() + defer c.cacheMu.Unlock() + if c.dir != nil { + return *c.dir, nil + } + + res, err := c.get(ctx, c.directoryURL(), wantStatus(http.StatusOK)) + if err != nil { + return Directory{}, err + } + defer res.Body.Close() + c.addNonce(res.Header) + + var v struct { + Reg string `json:"newAccount"` + Authz string `json:"newAuthz"` + Order string `json:"newOrder"` + Revoke string `json:"revokeCert"` + Nonce string `json:"newNonce"` + KeyChange string `json:"keyChange"` + Meta struct { + Terms string `json:"termsOfService"` + Website string `json:"website"` + CAA []string `json:"caaIdentities"` + ExternalAcct bool `json:"externalAccountRequired"` + Profiles map[string]string `json:"profiles"` + } + } + if err := json.NewDecoder(res.Body).Decode(&v); err != nil { + return Directory{}, err + } + if v.Order == "" { + return Directory{}, errPreRFC + } + c.dir = &Directory{ + RegURL: v.Reg, + AuthzURL: v.Authz, + OrderURL: v.Order, + RevokeURL: v.Revoke, + NonceURL: v.Nonce, + KeyChangeURL: v.KeyChange, + Terms: v.Meta.Terms, + Website: v.Meta.Website, + CAA: v.Meta.CAA, + ExternalAccountRequired: v.Meta.ExternalAcct, + Profiles: v.Meta.Profiles, + } + return *c.dir, nil +} + +func (c *Client) directoryURL() string { + if c.DirectoryURL != "" { + return c.DirectoryURL + } + return LetsEncryptURL +} + +// CreateCert was part of the old version of ACME. It is incompatible with RFC 8555. +// +// Deprecated: this was for the pre-RFC 8555 version of ACME. Callers should use CreateOrderCert. +func (c *Client) CreateCert(ctx context.Context, csr []byte, exp time.Duration, bundle bool) (der [][]byte, certURL string, err error) { + return nil, "", errPreRFC +} + +// FetchCert retrieves already issued certificate from the given url, in DER format. +// It retries the request until the certificate is successfully retrieved, +// context is cancelled by the caller or an error response is received. +// +// If the bundle argument is true, the returned value also contains the CA (issuer) +// certificate chain. +// +// FetchCert returns an error if the CA's response or chain was unreasonably large. +// Callers are encouraged to parse the returned value to ensure the certificate is valid +// and has expected features. +func (c *Client) FetchCert(ctx context.Context, url string, bundle bool) ([][]byte, error) { + if _, err := c.Discover(ctx); err != nil { + return nil, err + } + return c.fetchCertRFC(ctx, url, bundle) +} + +// RevokeCert revokes a previously issued certificate cert, provided in DER format. +// +// The key argument, used to sign the request, must be authorized +// to revoke the certificate. It's up to the CA to decide which keys are authorized. +// For instance, the key pair of the certificate may be authorized. +// If the key is nil, c.Key is used instead. +func (c *Client) RevokeCert(ctx context.Context, key crypto.Signer, cert []byte, reason CRLReasonCode) error { + if _, err := c.Discover(ctx); err != nil { + return err + } + return c.revokeCertRFC(ctx, key, cert, reason) +} + +// AcceptTOS always returns true to indicate the acceptance of a CA's Terms of Service +// during account registration. See Register method of Client for more details. +func AcceptTOS(tosURL string) bool { return true } + +// Register creates a new account with the CA using c.Key. +// It returns the registered account. The account acct is not modified. +// +// The registration may require the caller to agree to the CA's Terms of Service (TOS). +// If so, and the account has not indicated the acceptance of the terms (see Account for details), +// Register calls prompt with a TOS URL provided by the CA. Prompt should report +// whether the caller agrees to the terms. To always accept the terms, the caller can use AcceptTOS. +// +// When interfacing with an RFC-compliant CA, non-RFC 8555 fields of acct are ignored +// and prompt is called if Directory's Terms field is non-zero. +// Also see Error's Instance field for when a CA requires already registered accounts to agree +// to an updated Terms of Service. +func (c *Client) Register(ctx context.Context, acct *Account, prompt func(tosURL string) bool) (*Account, error) { + if c.Key == nil { + return nil, errors.New("acme: client.Key must be set to Register") + } + if _, err := c.Discover(ctx); err != nil { + return nil, err + } + return c.registerRFC(ctx, acct, prompt) +} + +// GetReg retrieves an existing account associated with c.Key. +// +// The url argument is a legacy artifact of the pre-RFC 8555 API +// and is ignored. +func (c *Client) GetReg(ctx context.Context, url string) (*Account, error) { + if _, err := c.Discover(ctx); err != nil { + return nil, err + } + return c.getRegRFC(ctx) +} + +// UpdateReg updates an existing registration. +// It returns an updated account copy. The provided account is not modified. +// +// The account's URI is ignored and the account URL associated with +// c.Key is used instead. +func (c *Client) UpdateReg(ctx context.Context, acct *Account) (*Account, error) { + if _, err := c.Discover(ctx); err != nil { + return nil, err + } + return c.updateRegRFC(ctx, acct) +} + +// AccountKeyRollover attempts to transition a client's account key to a new key. +// On success client's Key is updated which is not concurrency safe. +// On failure an error will be returned. +// The new key is already registered with the ACME provider if the following is true: +// - error is of type acme.Error +// - StatusCode should be 409 (Conflict) +// - Location header will have the KID of the associated account +// +// More about account key rollover can be found at +// https://tools.ietf.org/html/rfc8555#section-7.3.5. +func (c *Client) AccountKeyRollover(ctx context.Context, newKey crypto.Signer) error { + return c.accountKeyRollover(ctx, newKey) +} + +// Authorize performs the initial step in the pre-authorization flow, +// as opposed to order-based flow. +// The caller will then need to choose from and perform a set of returned +// challenges using c.Accept in order to successfully complete authorization. +// +// Once complete, the caller can use AuthorizeOrder which the CA +// should provision with the already satisfied authorization. +// For pre-RFC CAs, the caller can proceed directly to requesting a certificate +// using CreateCert method. +// +// If an authorization has been previously granted, the CA may return +// a valid authorization which has its Status field set to StatusValid. +// +// More about pre-authorization can be found at +// https://tools.ietf.org/html/rfc8555#section-7.4.1. +func (c *Client) Authorize(ctx context.Context, domain string) (*Authorization, error) { + return c.authorize(ctx, "dns", domain) +} + +// AuthorizeIP is the same as Authorize but requests IP address authorization. +// Clients which successfully obtain such authorization may request to issue +// a certificate for IP addresses. +// +// See the ACME spec extension for more details about IP address identifiers: +// https://tools.ietf.org/html/draft-ietf-acme-ip. +func (c *Client) AuthorizeIP(ctx context.Context, ipaddr string) (*Authorization, error) { + return c.authorize(ctx, "ip", ipaddr) +} + +func (c *Client) authorize(ctx context.Context, typ, val string) (*Authorization, error) { + if _, err := c.Discover(ctx); err != nil { + return nil, err + } + if c.dir.AuthzURL == "" { + // Pre-Authorization is unsupported + return nil, errPreAuthorizationNotSupported + } + + type authzID struct { + Type string `json:"type"` + Value string `json:"value"` + } + req := struct { + Resource string `json:"resource"` + Identifier authzID `json:"identifier"` + }{ + Resource: "new-authz", + Identifier: authzID{Type: typ, Value: val}, + } + res, err := c.post(ctx, nil, c.dir.AuthzURL, req, wantStatus(http.StatusCreated)) + if err != nil { + return nil, err + } + defer res.Body.Close() + + var v wireAuthz + if err := json.NewDecoder(res.Body).Decode(&v); err != nil { + return nil, fmt.Errorf("acme: invalid response: %v", err) + } + if v.Status != StatusPending && v.Status != StatusValid { + return nil, fmt.Errorf("acme: unexpected status: %s", v.Status) + } + return v.authorization(res.Header.Get("Location")), nil +} + +// GetAuthorization retrieves an authorization identified by the given URL. +// +// If a caller needs to poll an authorization until its status is final, +// see the WaitAuthorization method. +func (c *Client) GetAuthorization(ctx context.Context, url string) (*Authorization, error) { + if _, err := c.Discover(ctx); err != nil { + return nil, err + } + + res, err := c.postAsGet(ctx, url, wantStatus(http.StatusOK)) + if err != nil { + return nil, err + } + defer res.Body.Close() + var v wireAuthz + if err := json.NewDecoder(res.Body).Decode(&v); err != nil { + return nil, fmt.Errorf("acme: invalid response: %v", err) + } + return v.authorization(url), nil +} + +// RevokeAuthorization relinquishes an existing authorization identified +// by the given URL. +// The url argument is an Authorization.URI value. +// +// If successful, the caller will be required to obtain a new authorization +// using the Authorize or AuthorizeOrder methods before being able to request +// a new certificate for the domain associated with the authorization. +// +// It does not revoke existing certificates. +func (c *Client) RevokeAuthorization(ctx context.Context, url string) error { + if _, err := c.Discover(ctx); err != nil { + return err + } + + req := struct { + Resource string `json:"resource"` + Status string `json:"status"` + Delete bool `json:"delete"` + }{ + Resource: "authz", + Status: "deactivated", + Delete: true, + } + res, err := c.post(ctx, nil, url, req, wantStatus(http.StatusOK)) + if err != nil { + return err + } + defer res.Body.Close() + return nil +} + +// WaitAuthorization polls an authorization at the given URL +// until it is in one of the final states, StatusValid or StatusInvalid, +// the ACME CA responded with a 4xx error code, or the context is done. +// +// It returns a non-nil Authorization only if its Status is StatusValid. +// In all other cases WaitAuthorization returns an error. +// If the Status is StatusInvalid, the returned error is of type *AuthorizationError. +func (c *Client) WaitAuthorization(ctx context.Context, url string) (*Authorization, error) { + if _, err := c.Discover(ctx); err != nil { + return nil, err + } + for { + res, err := c.postAsGet(ctx, url, wantStatus(http.StatusOK, http.StatusAccepted)) + if err != nil { + return nil, err + } + + var raw wireAuthz + err = json.NewDecoder(res.Body).Decode(&raw) + res.Body.Close() + switch { + case err != nil: + // Skip and retry. + case raw.Status == StatusValid: + return raw.authorization(url), nil + case raw.Status == StatusInvalid: + return nil, raw.error(url) + } + + // Exponential backoff is implemented in c.get above. + // This is just to prevent continuously hitting the CA + // while waiting for a final authorization status. + d := retryAfter(res.Header.Get("Retry-After")) + if d == 0 { + // Given that the fastest challenges TLS-SNI and HTTP-01 + // require a CA to make at least 1 network round trip + // and most likely persist a challenge state, + // this default delay seems reasonable. + d = time.Second + } + t := time.NewTimer(d) + select { + case <-ctx.Done(): + t.Stop() + return nil, ctx.Err() + case <-t.C: + // Retry. + } + } +} + +// GetChallenge retrieves the current status of an challenge. +// +// A client typically polls a challenge status using this method. +func (c *Client) GetChallenge(ctx context.Context, url string) (*Challenge, error) { + if _, err := c.Discover(ctx); err != nil { + return nil, err + } + + res, err := c.postAsGet(ctx, url, wantStatus(http.StatusOK, http.StatusAccepted)) + if err != nil { + return nil, err + } + + defer res.Body.Close() + v := wireChallenge{URI: url} + if err := json.NewDecoder(res.Body).Decode(&v); err != nil { + return nil, fmt.Errorf("acme: invalid response: %v", err) + } + return v.challenge(), nil +} + +// Accept informs the server that the client accepts one of its challenges +// previously obtained with c.Authorize. +// +// The server will then perform the validation asynchronously. +func (c *Client) Accept(ctx context.Context, chal *Challenge) (*Challenge, error) { + if _, err := c.Discover(ctx); err != nil { + return nil, err + } + + payload := json.RawMessage("{}") + if len(chal.Payload) != 0 { + payload = chal.Payload + } + res, err := c.post(ctx, nil, chal.URI, payload, wantStatus( + http.StatusOK, // according to the spec + http.StatusAccepted, // Let's Encrypt: see https://goo.gl/WsJ7VT (acme-divergences.md) + )) + if err != nil { + return nil, err + } + defer res.Body.Close() + + var v wireChallenge + if err := json.NewDecoder(res.Body).Decode(&v); err != nil { + return nil, fmt.Errorf("acme: invalid response: %v", err) + } + return v.challenge(), nil +} + +// DNS01ChallengeRecord returns a DNS record value for a dns-01 challenge response. +// A TXT record containing the returned value must be provisioned under +// "_acme-challenge" name of the domain being validated. +// +// The token argument is a Challenge.Token value. +func (c *Client) DNS01ChallengeRecord(token string) (string, error) { + ka, err := keyAuth(c.Key.Public(), token) + if err != nil { + return "", err + } + b := sha256.Sum256([]byte(ka)) + return base64.RawURLEncoding.EncodeToString(b[:]), nil +} + +// HTTP01ChallengeResponse returns the response for an http-01 challenge. +// Servers should respond with the value to HTTP requests at the URL path +// provided by HTTP01ChallengePath to validate the challenge and prove control +// over a domain name. +// +// The token argument is a Challenge.Token value. +func (c *Client) HTTP01ChallengeResponse(token string) (string, error) { + return keyAuth(c.Key.Public(), token) +} + +// HTTP01ChallengePath returns the URL path at which the response for an http-01 challenge +// should be provided by the servers. +// The response value can be obtained with HTTP01ChallengeResponse. +// +// The token argument is a Challenge.Token value. +func (c *Client) HTTP01ChallengePath(token string) string { + return "/.well-known/acme-challenge/" + token +} + +// TLSSNI01ChallengeCert creates a certificate for TLS-SNI-01 challenge response. +// +// Deprecated: This challenge type is unused in both draft-02 and RFC versions of the ACME spec. +func (c *Client) TLSSNI01ChallengeCert(token string, opt ...CertOption) (cert tls.Certificate, name string, err error) { + ka, err := keyAuth(c.Key.Public(), token) + if err != nil { + return tls.Certificate{}, "", err + } + b := sha256.Sum256([]byte(ka)) + h := hex.EncodeToString(b[:]) + name = fmt.Sprintf("%s.%s.acme.invalid", h[:32], h[32:]) + cert, err = tlsChallengeCert([]string{name}, opt) + if err != nil { + return tls.Certificate{}, "", err + } + return cert, name, nil +} + +// TLSSNI02ChallengeCert creates a certificate for TLS-SNI-02 challenge response. +// +// Deprecated: This challenge type is unused in both draft-02 and RFC versions of the ACME spec. +func (c *Client) TLSSNI02ChallengeCert(token string, opt ...CertOption) (cert tls.Certificate, name string, err error) { + b := sha256.Sum256([]byte(token)) + h := hex.EncodeToString(b[:]) + sanA := fmt.Sprintf("%s.%s.token.acme.invalid", h[:32], h[32:]) + + ka, err := keyAuth(c.Key.Public(), token) + if err != nil { + return tls.Certificate{}, "", err + } + b = sha256.Sum256([]byte(ka)) + h = hex.EncodeToString(b[:]) + sanB := fmt.Sprintf("%s.%s.ka.acme.invalid", h[:32], h[32:]) + + cert, err = tlsChallengeCert([]string{sanA, sanB}, opt) + if err != nil { + return tls.Certificate{}, "", err + } + return cert, sanA, nil +} + +// TLSALPN01ChallengeCert creates a certificate for TLS-ALPN-01 challenge response. +// Servers can present the certificate to validate the challenge and prove control +// over a domain name. For more details on TLS-ALPN-01 see +// https://tools.ietf.org/html/draft-shoemaker-acme-tls-alpn-00#section-3 +// +// The token argument is a Challenge.Token value. +// If a WithKey option is provided, its private part signs the returned cert, +// and the public part is used to specify the signee. +// If no WithKey option is provided, a new ECDSA key is generated using P-256 curve. +// +// The returned certificate is valid for the next 24 hours and must be presented only when +// the server name in the TLS ClientHello matches the domain, and the special acme-tls/1 ALPN protocol +// has been specified. +func (c *Client) TLSALPN01ChallengeCert(token, domain string, opt ...CertOption) (cert tls.Certificate, err error) { + ka, err := keyAuth(c.Key.Public(), token) + if err != nil { + return tls.Certificate{}, err + } + shasum := sha256.Sum256([]byte(ka)) + extValue, err := asn1.Marshal(shasum[:]) + if err != nil { + return tls.Certificate{}, err + } + acmeExtension := pkix.Extension{ + Id: idPeACMEIdentifier, + Critical: true, + Value: extValue, + } + + tmpl := defaultTLSChallengeCertTemplate() + + var newOpt []CertOption + for _, o := range opt { + switch o := o.(type) { + case *certOptTemplate: + t := *(*x509.Certificate)(o) // shallow copy is ok + tmpl = &t + default: + newOpt = append(newOpt, o) + } + } + tmpl.ExtraExtensions = append(tmpl.ExtraExtensions, acmeExtension) + newOpt = append(newOpt, WithTemplate(tmpl)) + return tlsChallengeCert([]string{domain}, newOpt) +} + +// popNonce returns a nonce value previously stored with c.addNonce +// or fetches a fresh one from c.dir.NonceURL. +// If NonceURL is empty, it first tries c.directoryURL() and, failing that, +// the provided url. +func (c *Client) popNonce(ctx context.Context, url string) (string, error) { + c.noncesMu.Lock() + defer c.noncesMu.Unlock() + if len(c.nonces) == 0 { + if c.dir != nil && c.dir.NonceURL != "" { + return c.fetchNonce(ctx, c.dir.NonceURL) + } + dirURL := c.directoryURL() + v, err := c.fetchNonce(ctx, dirURL) + if err != nil && url != dirURL { + v, err = c.fetchNonce(ctx, url) + } + return v, err + } + var nonce string + for nonce = range c.nonces { + delete(c.nonces, nonce) + break + } + return nonce, nil +} + +// clearNonces clears any stored nonces +func (c *Client) clearNonces() { + c.noncesMu.Lock() + defer c.noncesMu.Unlock() + c.nonces = make(map[string]struct{}) +} + +// addNonce stores a nonce value found in h (if any) for future use. +func (c *Client) addNonce(h http.Header) { + v := nonceFromHeader(h) + if v == "" { + return + } + c.noncesMu.Lock() + defer c.noncesMu.Unlock() + if len(c.nonces) >= maxNonces { + return + } + if c.nonces == nil { + c.nonces = make(map[string]struct{}) + } + c.nonces[v] = struct{}{} +} + +func (c *Client) fetchNonce(ctx context.Context, url string) (string, error) { + r, err := http.NewRequest("HEAD", url, nil) + if err != nil { + return "", err + } + resp, err := c.doNoRetry(ctx, r) + if err != nil { + return "", err + } + defer resp.Body.Close() + nonce := nonceFromHeader(resp.Header) + if nonce == "" { + if resp.StatusCode > 299 { + return "", responseError(resp) + } + return "", errors.New("acme: nonce not found") + } + return nonce, nil +} + +func nonceFromHeader(h http.Header) string { + return h.Get("Replay-Nonce") +} + +// linkHeader returns URI-Reference values of all Link headers +// with relation-type rel. +// See https://tools.ietf.org/html/rfc5988#section-5 for details. +func linkHeader(h http.Header, rel string) []string { + var links []string + for _, v := range h["Link"] { + parts := strings.Split(v, ";") + for _, p := range parts { + p = strings.TrimSpace(p) + if !strings.HasPrefix(p, "rel=") { + continue + } + if v := strings.Trim(p[4:], `"`); v == rel { + links = append(links, strings.Trim(parts[0], "<>")) + } + } + } + return links +} + +// keyAuth generates a key authorization string for a given token. +func keyAuth(pub crypto.PublicKey, token string) (string, error) { + th, err := JWKThumbprint(pub) + if err != nil { + return "", err + } + return fmt.Sprintf("%s.%s", token, th), nil +} + +// defaultTLSChallengeCertTemplate is a template used to create challenge certs for TLS challenges. +func defaultTLSChallengeCertTemplate() *x509.Certificate { + return &x509.Certificate{ + SerialNumber: big.NewInt(1), + NotBefore: time.Now(), + NotAfter: time.Now().Add(24 * time.Hour), + BasicConstraintsValid: true, + KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, + } +} + +// tlsChallengeCert creates a temporary certificate for TLS-SNI challenges +// with the given SANs and auto-generated public/private key pair. +// The Subject Common Name is set to the first SAN to aid debugging. +// To create a cert with a custom key pair, specify WithKey option. +func tlsChallengeCert(san []string, opt []CertOption) (tls.Certificate, error) { + var key crypto.Signer + tmpl := defaultTLSChallengeCertTemplate() + for _, o := range opt { + switch o := o.(type) { + case *certOptKey: + if key != nil { + return tls.Certificate{}, errors.New("acme: duplicate key option") + } + key = o.key + case *certOptTemplate: + t := *(*x509.Certificate)(o) // shallow copy is ok + tmpl = &t + default: + // package's fault, if we let this happen: + panic(fmt.Sprintf("unsupported option type %T", o)) + } + } + if key == nil { + var err error + if key, err = ecdsa.GenerateKey(elliptic.P256(), rand.Reader); err != nil { + return tls.Certificate{}, err + } + } + tmpl.DNSNames = san + if len(san) > 0 { + tmpl.Subject.CommonName = san[0] + } + + der, err := x509.CreateCertificate(rand.Reader, tmpl, tmpl, key.Public(), key) + if err != nil { + return tls.Certificate{}, err + } + return tls.Certificate{ + Certificate: [][]byte{der}, + PrivateKey: key, + }, nil +} + +// encodePEM returns b encoded as PEM with block of type typ. +func encodePEM(typ string, b []byte) []byte { + pb := &pem.Block{Type: typ, Bytes: b} + return pem.EncodeToMemory(pb) +} + +// timeNow is time.Now, except in tests which can mess with it. +var timeNow = time.Now diff --git a/third_party/forked/acme/acme_test.go b/third_party/forked/acme/acme_test.go new file mode 100644 index 00000000000..d286888eb40 --- /dev/null +++ b/third_party/forked/acme/acme_test.go @@ -0,0 +1,907 @@ +// Copyright 2015 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package acme + +import ( + "bytes" + "context" + "crypto/rand" + "crypto/rsa" + "crypto/tls" + "crypto/x509" + "crypto/x509/pkix" + "encoding/base64" + "encoding/hex" + "encoding/json" + "errors" + "fmt" + "io" + "math/big" + "net/http" + "net/http/httptest" + "reflect" + "sort" + "strings" + "testing" + "time" +) + +// newTestClient creates a client with a non-nil Directory so that it skips +// the discovery which is otherwise done on the first call of almost every +// exported method. +func newTestClient() *Client { + return &Client{ + Key: testKeyEC, + dir: &Directory{}, // skip discovery + } +} + +// Decodes a JWS-encoded request and unmarshals the decoded JSON into a provided +// interface. +func decodeJWSRequest(t *testing.T, v interface{}, r io.Reader) { + // Decode request + var req struct{ Payload string } + if err := json.NewDecoder(r).Decode(&req); err != nil { + t.Fatal(err) + } + payload, err := base64.RawURLEncoding.DecodeString(req.Payload) + if err != nil { + t.Fatal(err) + } + err = json.Unmarshal(payload, v) + if err != nil { + t.Fatal(err) + } +} + +type jwsHead struct { + Alg string + Nonce string + URL string `json:"url"` + KID string `json:"kid"` + JWK map[string]string `json:"jwk"` +} + +func decodeJWSHead(r io.Reader) (*jwsHead, error) { + var req struct{ Protected string } + if err := json.NewDecoder(r).Decode(&req); err != nil { + return nil, err + } + b, err := base64.RawURLEncoding.DecodeString(req.Protected) + if err != nil { + return nil, err + } + var head jwsHead + if err := json.Unmarshal(b, &head); err != nil { + return nil, err + } + return &head, nil +} + +func TestRegisterWithoutKey(t *testing.T) { + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.Method == "HEAD" { + w.Header().Set("Replay-Nonce", "test-nonce") + return + } + w.WriteHeader(http.StatusCreated) + fmt.Fprint(w, `{}`) + })) + defer ts.Close() + // First verify that using a complete client results in success. + c := Client{ + Key: testKeyEC, + DirectoryURL: ts.URL, + dir: &Directory{RegURL: ts.URL}, + } + if _, err := c.Register(context.Background(), &Account{}, AcceptTOS); err != nil { + t.Fatalf("c.Register() = %v; want success with a complete test client", err) + } + c.Key = nil + if _, err := c.Register(context.Background(), &Account{}, AcceptTOS); err == nil { + t.Error("c.Register() from client without key succeeded, wanted error") + } +} + +func TestAuthorize(t *testing.T) { + tt := []struct{ typ, value string }{ + {"dns", "example.com"}, + {"ip", "1.2.3.4"}, + } + for _, test := range tt { + t.Run(test.typ, func(t *testing.T) { + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.Method == "HEAD" { + w.Header().Set("Replay-Nonce", "test-nonce") + return + } + if r.Method != "POST" { + t.Errorf("r.Method = %q; want POST", r.Method) + } + + var j struct { + Resource string + Identifier struct { + Type string + Value string + } + } + decodeJWSRequest(t, &j, r.Body) + + // Test request + if j.Resource != "new-authz" { + t.Errorf("j.Resource = %q; want new-authz", j.Resource) + } + if j.Identifier.Type != test.typ { + t.Errorf("j.Identifier.Type = %q; want %q", j.Identifier.Type, test.typ) + } + if j.Identifier.Value != test.value { + t.Errorf("j.Identifier.Value = %q; want %q", j.Identifier.Value, test.value) + } + + w.Header().Set("Location", "https://ca.tld/acme/auth/1") + w.WriteHeader(http.StatusCreated) + fmt.Fprintf(w, `{ + "identifier": {"type":%q,"value":%q}, + "status":"pending", + "challenges":[ + { + "type":"http-01", + "status":"pending", + "uri":"https://ca.tld/acme/challenge/publickey/id1", + "token":"token1" + }, + { + "type":"tls-sni-01", + "status":"pending", + "uri":"https://ca.tld/acme/challenge/publickey/id2", + "token":"token2" + } + ], + "combinations":[[0],[1]] + }`, test.typ, test.value) + })) + defer ts.Close() + + var ( + auth *Authorization + err error + ) + cl := Client{ + Key: testKeyEC, + DirectoryURL: ts.URL, + dir: &Directory{AuthzURL: ts.URL}, + } + switch test.typ { + case "dns": + auth, err = cl.Authorize(context.Background(), test.value) + case "ip": + auth, err = cl.AuthorizeIP(context.Background(), test.value) + default: + t.Fatalf("unknown identifier type: %q", test.typ) + } + if err != nil { + t.Fatal(err) + } + + if auth.URI != "https://ca.tld/acme/auth/1" { + t.Errorf("URI = %q; want https://ca.tld/acme/auth/1", auth.URI) + } + if auth.Status != "pending" { + t.Errorf("Status = %q; want pending", auth.Status) + } + if auth.Identifier.Type != test.typ { + t.Errorf("Identifier.Type = %q; want %q", auth.Identifier.Type, test.typ) + } + if auth.Identifier.Value != test.value { + t.Errorf("Identifier.Value = %q; want %q", auth.Identifier.Value, test.value) + } + + if n := len(auth.Challenges); n != 2 { + t.Fatalf("len(auth.Challenges) = %d; want 2", n) + } + + c := auth.Challenges[0] + if c.Type != "http-01" { + t.Errorf("c.Type = %q; want http-01", c.Type) + } + if c.URI != "https://ca.tld/acme/challenge/publickey/id1" { + t.Errorf("c.URI = %q; want https://ca.tld/acme/challenge/publickey/id1", c.URI) + } + if c.Token != "token1" { + t.Errorf("c.Token = %q; want token1", c.Token) + } + + c = auth.Challenges[1] + if c.Type != "tls-sni-01" { + t.Errorf("c.Type = %q; want tls-sni-01", c.Type) + } + if c.URI != "https://ca.tld/acme/challenge/publickey/id2" { + t.Errorf("c.URI = %q; want https://ca.tld/acme/challenge/publickey/id2", c.URI) + } + if c.Token != "token2" { + t.Errorf("c.Token = %q; want token2", c.Token) + } + + combs := [][]int{{0}, {1}} + if !reflect.DeepEqual(auth.Combinations, combs) { + t.Errorf("auth.Combinations: %+v\nwant: %+v\n", auth.Combinations, combs) + } + + }) + } +} + +func TestAuthorizeValid(t *testing.T) { + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.Method == "HEAD" { + w.Header().Set("Replay-Nonce", "nonce") + return + } + w.WriteHeader(http.StatusCreated) + w.Write([]byte(`{"status":"valid"}`)) + })) + defer ts.Close() + client := Client{ + Key: testKey, + DirectoryURL: ts.URL, + dir: &Directory{AuthzURL: ts.URL}, + } + _, err := client.Authorize(context.Background(), "example.com") + if err != nil { + t.Errorf("err = %v", err) + } +} + +func TestAuthorizeUnsupported(t *testing.T) { + const ( + nonce = "https://example.com/acme/new-nonce" + reg = "https://example.com/acme/new-acct" + order = "https://example.com/acme/new-order" + revoke = "https://example.com/acme/revoke-cert" + keychange = "https://example.com/acme/key-change" + metaTerms = "https://example.com/acme/terms/2017-5-30" + metaWebsite = "https://www.example.com/" + metaCAA = "example.com" + ) + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Replay-Nonce", "nonce") + if r.Method == http.MethodHead { + return + } + switch r.URL.Path { + case "/": // Directory + w.Header().Set("Content-Type", "application/json") + fmt.Fprintf(w, `{ + "newNonce": %q, + "newAccount": %q, + "newOrder": %q, + "revokeCert": %q, + "keyChange": %q, + "meta": { + "termsOfService": %q, + "website": %q, + "caaIdentities": [%q], + "externalAccountRequired": true + } + }`, nonce, reg, order, revoke, keychange, metaTerms, metaWebsite, metaCAA) + w.WriteHeader(http.StatusOK) + case "/acme/new-authz": + w.WriteHeader(http.StatusBadRequest) + } + })) + defer ts.Close() + client := &Client{Key: testKey, DirectoryURL: ts.URL} + dir, err := client.Discover(context.Background()) + if err != nil { + t.Fatal(err) + } + if dir.AuthzURL != "" { + t.Fatalf("expected AuthzURL to be empty, got %q", dir.AuthzURL) + } + if _, err := client.Authorize(context.Background(), "example.com"); !errors.Is(err, errPreAuthorizationNotSupported) { + t.Errorf("expected err to indicate pre-authorization is unsupported, got %+v", err) + } +} + +func TestWaitAuthorization(t *testing.T) { + t.Run("wait loop", func(t *testing.T) { + var count int + authz, err := runWaitAuthorization(context.Background(), t, func(w http.ResponseWriter, r *http.Request) { + count++ + w.Header().Set("Retry-After", "0") + if count > 1 { + fmt.Fprintf(w, `{"status":"valid"}`) + return + } + fmt.Fprintf(w, `{"status":"pending"}`) + }) + if err != nil { + t.Fatalf("non-nil error: %v", err) + } + if authz == nil { + t.Fatal("authz is nil") + } + }) + t.Run("invalid status", func(t *testing.T) { + _, err := runWaitAuthorization(context.Background(), t, func(w http.ResponseWriter, r *http.Request) { + fmt.Fprintf(w, `{"status":"invalid"}`) + }) + if _, ok := err.(*AuthorizationError); !ok { + t.Errorf("err is %v (%T); want non-nil *AuthorizationError", err, err) + } + }) + t.Run("invalid status with error returns the authorization error", func(t *testing.T) { + _, err := runWaitAuthorization(context.Background(), t, func(w http.ResponseWriter, r *http.Request) { + fmt.Fprintf(w, `{ + "type": "dns-01", + "status": "invalid", + "error": { + "type": "urn:ietf:params:acme:error:caa", + "detail": "CAA record for prevents issuance", + "status": 403 + }, + "url": "https://acme-v02.api.letsencrypt.org/acme/chall-v3/xxx/xxx", + "token": "xxx", + "validationRecord": [ + { + "hostname": "" + } + ] + }`) + }) + + want := &AuthorizationError{ + Errors: []error{ + (&wireError{ + Status: 403, + Type: "urn:ietf:params:acme:error:caa", + Detail: "CAA record for prevents issuance", + }).error(nil), + }, + } + + _, ok := err.(*AuthorizationError) + if !ok { + t.Errorf("err is %T; want non-nil *AuthorizationError", err) + } + + if err.Error() != want.Error() { + t.Errorf("err is %v; want %v", err, want) + } + }) + t.Run("non-retriable error", func(t *testing.T) { + const code = http.StatusBadRequest + _, err := runWaitAuthorization(context.Background(), t, func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(code) + }) + res, ok := err.(*Error) + if !ok { + t.Fatalf("err is %v (%T); want a non-nil *Error", err, err) + } + if res.StatusCode != code { + t.Errorf("res.StatusCode = %d; want %d", res.StatusCode, code) + } + }) + for _, code := range []int{http.StatusTooManyRequests, http.StatusInternalServerError} { + t.Run(fmt.Sprintf("retriable %d error", code), func(t *testing.T) { + var count int + authz, err := runWaitAuthorization(context.Background(), t, func(w http.ResponseWriter, r *http.Request) { + count++ + w.Header().Set("Retry-After", "0") + if count > 1 { + fmt.Fprintf(w, `{"status":"valid"}`) + return + } + w.WriteHeader(code) + }) + if err != nil { + t.Fatalf("non-nil error: %v", err) + } + if authz == nil { + t.Fatal("authz is nil") + } + }) + } + t.Run("context cancel", func(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + _, err := runWaitAuthorization(ctx, t, func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Retry-After", "60") + fmt.Fprintf(w, `{"status":"pending"}`) + time.AfterFunc(1*time.Millisecond, cancel) + }) + if err == nil { + t.Error("err is nil") + } + }) +} + +func runWaitAuthorization(ctx context.Context, t *testing.T, h http.HandlerFunc) (*Authorization, error) { + t.Helper() + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Replay-Nonce", fmt.Sprintf("bad-test-nonce-%v", time.Now().UnixNano())) + h(w, r) + })) + defer ts.Close() + + client := &Client{ + Key: testKey, + DirectoryURL: ts.URL, + dir: &Directory{}, + KID: "some-key-id", // set to avoid lookup attempt + } + return client.WaitAuthorization(ctx, ts.URL) +} + +func TestRevokeAuthorization(t *testing.T) { + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.Method == "HEAD" { + w.Header().Set("Replay-Nonce", "nonce") + return + } + switch r.URL.Path { + case "/1": + var req struct { + Resource string + Status string + Delete bool + } + decodeJWSRequest(t, &req, r.Body) + if req.Resource != "authz" { + t.Errorf("req.Resource = %q; want authz", req.Resource) + } + if req.Status != "deactivated" { + t.Errorf("req.Status = %q; want deactivated", req.Status) + } + if !req.Delete { + t.Errorf("req.Delete is false") + } + case "/2": + w.WriteHeader(http.StatusBadRequest) + } + })) + defer ts.Close() + client := &Client{ + Key: testKey, + DirectoryURL: ts.URL, // don't dial outside of localhost + dir: &Directory{}, // don't do discovery + } + ctx := context.Background() + if err := client.RevokeAuthorization(ctx, ts.URL+"/1"); err != nil { + t.Errorf("err = %v", err) + } + if client.RevokeAuthorization(ctx, ts.URL+"/2") == nil { + t.Error("nil error") + } +} + +func TestFetchCertCancel(t *testing.T) { + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + <-r.Context().Done() + w.Header().Set("Retry-After", "0") + w.WriteHeader(http.StatusBadRequest) + })) + defer ts.Close() + ctx, cancel := context.WithCancel(context.Background()) + done := make(chan struct{}) + var err error + go func() { + cl := newTestClient() + _, err = cl.FetchCert(ctx, ts.URL, false) + close(done) + }() + cancel() + <-done + if err != context.Canceled { + t.Errorf("err = %v; want %v", err, context.Canceled) + } +} + +func TestFetchCertDepth(t *testing.T) { + var count byte + var ts *httptest.Server + ts = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + count++ + if count > maxChainLen+1 { + t.Errorf("count = %d; want at most %d", count, maxChainLen+1) + w.WriteHeader(http.StatusInternalServerError) + } + w.Header().Set("Link", fmt.Sprintf("<%s>;rel=up", ts.URL)) + w.Write([]byte{count}) + })) + defer ts.Close() + cl := newTestClient() + _, err := cl.FetchCert(context.Background(), ts.URL, true) + if err == nil { + t.Errorf("err is nil") + } +} + +func TestFetchCertBreadth(t *testing.T) { + var ts *httptest.Server + ts = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + for i := 0; i < maxChainLen+1; i++ { + w.Header().Add("Link", fmt.Sprintf("<%s>;rel=up", ts.URL)) + } + w.Write([]byte{1}) + })) + defer ts.Close() + cl := newTestClient() + _, err := cl.FetchCert(context.Background(), ts.URL, true) + if err == nil { + t.Errorf("err is nil") + } +} + +func TestFetchCertSize(t *testing.T) { + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + b := bytes.Repeat([]byte{1}, maxCertSize+1) + w.Write(b) + })) + defer ts.Close() + cl := newTestClient() + _, err := cl.FetchCert(context.Background(), ts.URL, false) + if err == nil { + t.Errorf("err is nil") + } +} + +func TestNonce_add(t *testing.T) { + var c Client + c.addNonce(http.Header{"Replay-Nonce": {"nonce"}}) + c.addNonce(http.Header{"Replay-Nonce": {}}) + c.addNonce(http.Header{"Replay-Nonce": {"nonce"}}) + + nonces := map[string]struct{}{"nonce": {}} + if !reflect.DeepEqual(c.nonces, nonces) { + t.Errorf("c.nonces = %q; want %q", c.nonces, nonces) + } +} + +func TestNonce_addMax(t *testing.T) { + c := &Client{nonces: make(map[string]struct{})} + for i := 0; i < maxNonces; i++ { + c.nonces[fmt.Sprintf("%d", i)] = struct{}{} + } + c.addNonce(http.Header{"Replay-Nonce": {"nonce"}}) + if n := len(c.nonces); n != maxNonces { + t.Errorf("len(c.nonces) = %d; want %d", n, maxNonces) + } +} + +func TestNonce_fetch(t *testing.T) { + tests := []struct { + code int + nonce string + }{ + {http.StatusOK, "nonce1"}, + {http.StatusBadRequest, "nonce2"}, + {http.StatusOK, ""}, + } + var i int + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.Method != "HEAD" { + t.Errorf("%d: r.Method = %q; want HEAD", i, r.Method) + } + w.Header().Set("Replay-Nonce", tests[i].nonce) + w.WriteHeader(tests[i].code) + })) + defer ts.Close() + for ; i < len(tests); i++ { + test := tests[i] + c := newTestClient() + n, err := c.fetchNonce(context.Background(), ts.URL) + if n != test.nonce { + t.Errorf("%d: n=%q; want %q", i, n, test.nonce) + } + switch { + case err == nil && test.nonce == "": + t.Errorf("%d: n=%q, err=%v; want non-nil error", i, n, err) + case err != nil && test.nonce != "": + t.Errorf("%d: n=%q, err=%v; want %q", i, n, err, test.nonce) + } + } +} + +func TestNonce_fetchError(t *testing.T) { + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusTooManyRequests) + })) + defer ts.Close() + c := newTestClient() + _, err := c.fetchNonce(context.Background(), ts.URL) + e, ok := err.(*Error) + if !ok { + t.Fatalf("err is %T; want *Error", err) + } + if e.StatusCode != http.StatusTooManyRequests { + t.Errorf("e.StatusCode = %d; want %d", e.StatusCode, http.StatusTooManyRequests) + } +} + +func TestNonce_popWhenEmpty(t *testing.T) { + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.Method != "HEAD" { + t.Errorf("r.Method = %q; want HEAD", r.Method) + } + switch r.URL.Path { + case "/dir-with-nonce": + w.Header().Set("Replay-Nonce", "dirnonce") + case "/new-nonce": + w.Header().Set("Replay-Nonce", "newnonce") + case "/dir-no-nonce", "/empty": + // No nonce in the header. + default: + t.Errorf("Unknown URL: %s", r.URL) + } + })) + defer ts.Close() + ctx := context.Background() + + tt := []struct { + dirURL, popURL, nonce string + wantOK bool + }{ + {ts.URL + "/dir-with-nonce", ts.URL + "/new-nonce", "dirnonce", true}, + {ts.URL + "/dir-no-nonce", ts.URL + "/new-nonce", "newnonce", true}, + {ts.URL + "/dir-no-nonce", ts.URL + "/empty", "", false}, + } + for _, test := range tt { + t.Run(fmt.Sprintf("nonce:%s wantOK:%v", test.nonce, test.wantOK), func(t *testing.T) { + c := Client{DirectoryURL: test.dirURL} + v, err := c.popNonce(ctx, test.popURL) + if !test.wantOK { + if err == nil { + t.Fatalf("c.popNonce(%q) returned nil error", test.popURL) + } + return + } + if err != nil { + t.Fatalf("c.popNonce(%q): %v", test.popURL, err) + } + if v != test.nonce { + t.Errorf("c.popNonce(%q) = %q; want %q", test.popURL, v, test.nonce) + } + }) + } +} + +func TestLinkHeader(t *testing.T) { + h := http.Header{"Link": { + `;rel="next"`, + `; rel=recover`, + `; foo=bar; rel="terms-of-service"`, + `;rel="next"`, + }} + tests := []struct { + rel string + out []string + }{ + {"next", []string{"https://example.com/acme/new-authz", "dup"}}, + {"recover", []string{"https://example.com/acme/recover-reg"}}, + {"terms-of-service", []string{"https://example.com/acme/terms"}}, + {"empty", nil}, + } + for i, test := range tests { + if v := linkHeader(h, test.rel); !reflect.DeepEqual(v, test.out) { + t.Errorf("%d: linkHeader(%q): %v; want %v", i, test.rel, v, test.out) + } + } +} + +func TestTLSSNI01ChallengeCert(t *testing.T) { + const ( + token = "evaGxfADs6pSRb2LAv9IZf17Dt3juxGJ-PCt92wr-oA" + // echo -n | shasum -a 256 + san = "dbbd5eefe7b4d06eb9d1d9f5acb4c7cd.a27d320e4b30332f0b6cb441734ad7b0.acme.invalid" + ) + + tlscert, name, err := newTestClient().TLSSNI01ChallengeCert(token) + if err != nil { + t.Fatal(err) + } + + if n := len(tlscert.Certificate); n != 1 { + t.Fatalf("len(tlscert.Certificate) = %d; want 1", n) + } + cert, err := x509.ParseCertificate(tlscert.Certificate[0]) + if err != nil { + t.Fatal(err) + } + if len(cert.DNSNames) != 1 || cert.DNSNames[0] != san { + t.Fatalf("cert.DNSNames = %v; want %q", cert.DNSNames, san) + } + if cert.DNSNames[0] != name { + t.Errorf("cert.DNSNames[0] != name: %q vs %q", cert.DNSNames[0], name) + } + if cn := cert.Subject.CommonName; cn != san { + t.Errorf("cert.Subject.CommonName = %q; want %q", cn, san) + } +} + +func TestTLSSNI02ChallengeCert(t *testing.T) { + const ( + token = "evaGxfADs6pSRb2LAv9IZf17Dt3juxGJ-PCt92wr-oA" + // echo -n evaGxfADs6pSRb2LAv9IZf17Dt3juxGJ-PCt92wr-oA | shasum -a 256 + sanA = "7ea0aaa69214e71e02cebb18bb867736.09b730209baabf60e43d4999979ff139.token.acme.invalid" + // echo -n | shasum -a 256 + sanB = "dbbd5eefe7b4d06eb9d1d9f5acb4c7cd.a27d320e4b30332f0b6cb441734ad7b0.ka.acme.invalid" + ) + + tlscert, name, err := newTestClient().TLSSNI02ChallengeCert(token) + if err != nil { + t.Fatal(err) + } + + if n := len(tlscert.Certificate); n != 1 { + t.Fatalf("len(tlscert.Certificate) = %d; want 1", n) + } + cert, err := x509.ParseCertificate(tlscert.Certificate[0]) + if err != nil { + t.Fatal(err) + } + names := []string{sanA, sanB} + if !reflect.DeepEqual(cert.DNSNames, names) { + t.Fatalf("cert.DNSNames = %v;\nwant %v", cert.DNSNames, names) + } + sort.Strings(cert.DNSNames) + i := sort.SearchStrings(cert.DNSNames, name) + if i >= len(cert.DNSNames) || cert.DNSNames[i] != name { + t.Errorf("%v doesn't have %q", cert.DNSNames, name) + } + if cn := cert.Subject.CommonName; cn != sanA { + t.Errorf("CommonName = %q; want %q", cn, sanA) + } +} + +func TestTLSALPN01ChallengeCert(t *testing.T) { + const ( + token = "evaGxfADs6pSRb2LAv9IZf17Dt3juxGJ-PCt92wr-oA" + keyAuth = "evaGxfADs6pSRb2LAv9IZf17Dt3juxGJ-PCt92wr-oA." + testKeyECThumbprint + // echo -n | shasum -a 256 + h = "0420dbbd5eefe7b4d06eb9d1d9f5acb4c7cda27d320e4b30332f0b6cb441734ad7b0" + domain = "example.com" + ) + + extValue, err := hex.DecodeString(h) + if err != nil { + t.Fatal(err) + } + + tlscert, err := newTestClient().TLSALPN01ChallengeCert(token, domain) + if err != nil { + t.Fatal(err) + } + + if n := len(tlscert.Certificate); n != 1 { + t.Fatalf("len(tlscert.Certificate) = %d; want 1", n) + } + cert, err := x509.ParseCertificate(tlscert.Certificate[0]) + if err != nil { + t.Fatal(err) + } + names := []string{domain} + if !reflect.DeepEqual(cert.DNSNames, names) { + t.Fatalf("cert.DNSNames = %v;\nwant %v", cert.DNSNames, names) + } + if cn := cert.Subject.CommonName; cn != domain { + t.Errorf("CommonName = %q; want %q", cn, domain) + } + acmeExts := []pkix.Extension{} + for _, ext := range cert.Extensions { + if idPeACMEIdentifier.Equal(ext.Id) { + acmeExts = append(acmeExts, ext) + } + } + if len(acmeExts) != 1 { + t.Errorf("acmeExts = %v; want exactly one", acmeExts) + } + if !acmeExts[0].Critical { + t.Errorf("acmeExt.Critical = %v; want true", acmeExts[0].Critical) + } + if bytes.Compare(acmeExts[0].Value, extValue) != 0 { + t.Errorf("acmeExt.Value = %v; want %v", acmeExts[0].Value, extValue) + } + +} + +func TestTLSChallengeCertOpt(t *testing.T) { + key, err := rsa.GenerateKey(rand.Reader, 1024) + if err != nil { + t.Fatal(err) + } + tmpl := &x509.Certificate{ + SerialNumber: big.NewInt(2), + Subject: pkix.Name{Organization: []string{"Test"}}, + DNSNames: []string{"should-be-overwritten"}, + } + opts := []CertOption{WithKey(key), WithTemplate(tmpl)} + + client := newTestClient() + cert1, _, err := client.TLSSNI01ChallengeCert("token", opts...) + if err != nil { + t.Fatal(err) + } + cert2, _, err := client.TLSSNI02ChallengeCert("token", opts...) + if err != nil { + t.Fatal(err) + } + + for i, tlscert := range []tls.Certificate{cert1, cert2} { + // verify generated cert private key + tlskey, ok := tlscert.PrivateKey.(*rsa.PrivateKey) + if !ok { + t.Errorf("%d: tlscert.PrivateKey is %T; want *rsa.PrivateKey", i, tlscert.PrivateKey) + continue + } + if tlskey.D.Cmp(key.D) != 0 { + t.Errorf("%d: tlskey.D = %v; want %v", i, tlskey.D, key.D) + } + // verify generated cert public key + x509Cert, err := x509.ParseCertificate(tlscert.Certificate[0]) + if err != nil { + t.Errorf("%d: %v", i, err) + continue + } + tlspub, ok := x509Cert.PublicKey.(*rsa.PublicKey) + if !ok { + t.Errorf("%d: x509Cert.PublicKey is %T; want *rsa.PublicKey", i, x509Cert.PublicKey) + continue + } + if tlspub.N.Cmp(key.N) != 0 { + t.Errorf("%d: tlspub.N = %v; want %v", i, tlspub.N, key.N) + } + // verify template option + sn := big.NewInt(2) + if x509Cert.SerialNumber.Cmp(sn) != 0 { + t.Errorf("%d: SerialNumber = %v; want %v", i, x509Cert.SerialNumber, sn) + } + org := []string{"Test"} + if !reflect.DeepEqual(x509Cert.Subject.Organization, org) { + t.Errorf("%d: Subject.Organization = %+v; want %+v", i, x509Cert.Subject.Organization, org) + } + for _, v := range x509Cert.DNSNames { + if !strings.HasSuffix(v, ".acme.invalid") { + t.Errorf("%d: invalid DNSNames element: %q", i, v) + } + } + } +} + +func TestHTTP01Challenge(t *testing.T) { + const ( + token = "xxx" + // thumbprint is precomputed for testKeyEC in jws_test.go + value = token + "." + testKeyECThumbprint + urlpath = "/.well-known/acme-challenge/" + token + ) + client := newTestClient() + val, err := client.HTTP01ChallengeResponse(token) + if err != nil { + t.Fatal(err) + } + if val != value { + t.Errorf("val = %q; want %q", val, value) + } + if path := client.HTTP01ChallengePath(token); path != urlpath { + t.Errorf("path = %q; want %q", path, urlpath) + } +} + +func TestDNS01ChallengeRecord(t *testing.T) { + // echo -n xxx. | \ + // openssl dgst -binary -sha256 | \ + // base64 | tr -d '=' | tr '/+' '_-' + const value = "8DERMexQ5VcdJ_prpPiA0mVdp7imgbCgjsG4SqqNMIo" + + val, err := newTestClient().DNS01ChallengeRecord("xxx") + if err != nil { + t.Fatal(err) + } + if val != value { + t.Errorf("val = %q; want %q", val, value) + } +} diff --git a/third_party/forked/acme/autocert/autocert.go b/third_party/forked/acme/autocert/autocert.go new file mode 100644 index 00000000000..6af4a254d25 --- /dev/null +++ b/third_party/forked/acme/autocert/autocert.go @@ -0,0 +1,1199 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package autocert provides automatic access to certificates from Let's Encrypt +// and any other ACME-based CA. +// +// This package is a work in progress and makes no API stability promises. +package autocert + +import ( + "bytes" + "context" + "crypto" + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "crypto/rsa" + "crypto/tls" + "crypto/x509" + "crypto/x509/pkix" + "encoding/pem" + "errors" + "fmt" + "io" + mathrand "math/rand" + "net" + "net/http" + "path" + "strings" + "sync" + "time" + + "github.com/cert-manager/cert-manager/third_party/forked/acme" + "golang.org/x/net/idna" +) + +// DefaultACMEDirectory is the default ACME Directory URL used when the Manager's Client is nil. +const DefaultACMEDirectory = "https://acme-v02.api.letsencrypt.org/directory" + +// createCertRetryAfter is how much time to wait before removing a failed state +// entry due to an unsuccessful createCert call. +// This is a variable instead of a const for testing. +// TODO: Consider making it configurable or an exp backoff? +var createCertRetryAfter = time.Minute + +// pseudoRand is safe for concurrent use. +var pseudoRand *lockedMathRand + +var errPreRFC = errors.New("autocert: ACME server doesn't support RFC 8555") + +func init() { + src := mathrand.NewSource(time.Now().UnixNano()) + pseudoRand = &lockedMathRand{rnd: mathrand.New(src)} +} + +// AcceptTOS is a Manager.Prompt function that always returns true to +// indicate acceptance of the CA's Terms of Service during account +// registration. +func AcceptTOS(tosURL string) bool { return true } + +// HostPolicy specifies which host names the Manager is allowed to respond to. +// It returns a non-nil error if the host should be rejected. +// The returned error is accessible via tls.Conn.Handshake and its callers. +// See Manager's HostPolicy field and GetCertificate method docs for more details. +type HostPolicy func(ctx context.Context, host string) error + +// HostWhitelist returns a policy where only the specified host names are allowed. +// Only exact matches are currently supported. Subdomains, regexp or wildcard +// will not match. +// +// Note that all hosts will be converted to Punycode via idna.Lookup.ToASCII so that +// Manager.GetCertificate can handle the Unicode IDN and mixedcase hosts correctly. +// Invalid hosts will be silently ignored. +func HostWhitelist(hosts ...string) HostPolicy { + whitelist := make(map[string]bool, len(hosts)) + for _, h := range hosts { + if h, err := idna.Lookup.ToASCII(h); err == nil { + whitelist[h] = true + } + } + return func(_ context.Context, host string) error { + if !whitelist[host] { + return fmt.Errorf("acme/autocert: host %q not configured in HostWhitelist", host) + } + return nil + } +} + +// defaultHostPolicy is used when Manager.HostPolicy is not set. +func defaultHostPolicy(context.Context, string) error { + return nil +} + +// Manager is a stateful certificate manager built on top of acme.Client. +// It obtains and refreshes certificates automatically using "tls-alpn-01" +// or "http-01" challenge types, as well as providing them to a TLS server +// via tls.Config. +// +// You must specify a cache implementation, such as DirCache, +// to reuse obtained certificates across program restarts. +// Otherwise your server is very likely to exceed the certificate +// issuer's request rate limits. +type Manager struct { + // Prompt specifies a callback function to conditionally accept a CA's Terms of Service (TOS). + // The registration may require the caller to agree to the CA's TOS. + // If so, Manager calls Prompt with a TOS URL provided by the CA. Prompt should report + // whether the caller agrees to the terms. + // + // To always accept the terms, the callers can use AcceptTOS. + Prompt func(tosURL string) bool + + // Cache optionally stores and retrieves previously-obtained certificates + // and other state. If nil, certs will only be cached for the lifetime of + // the Manager. Multiple Managers can share the same Cache. + // + // Using a persistent Cache, such as DirCache, is strongly recommended. + Cache Cache + + // HostPolicy controls which domains the Manager will attempt + // to retrieve new certificates for. It does not affect cached certs. + // + // If non-nil, HostPolicy is called before requesting a new cert. + // If nil, all hosts are currently allowed. This is not recommended, + // as it opens a potential attack where clients connect to a server + // by IP address and pretend to be asking for an incorrect host name. + // Manager will attempt to obtain a certificate for that host, incorrectly, + // eventually reaching the CA's rate limit for certificate requests + // and making it impossible to obtain actual certificates. + // + // See GetCertificate for more details. + HostPolicy HostPolicy + + // RenewBefore optionally specifies how early certificates should + // be renewed before they expire. + // + // If zero, they're renewed 30 days before expiration. + RenewBefore time.Duration + + // Client is used to perform low-level operations, such as account registration + // and requesting new certificates. + // + // If Client is nil, a zero-value acme.Client is used with DefaultACMEDirectory + // as the directory endpoint. + // If the Client.Key is nil, a new ECDSA P-256 key is generated and, + // if Cache is not nil, stored in cache. + // + // Mutating the field after the first call of GetCertificate method will have no effect. + Client *acme.Client + + // Email optionally specifies a contact email address. + // This is used by CAs, such as Let's Encrypt, to notify about problems + // with issued certificates. + // + // If the Client's account key is already registered, Email is not used. + Email string + + // ForceRSA used to make the Manager generate RSA certificates. It is now ignored. + // + // Deprecated: the Manager will request the correct type of certificate based + // on what each client supports. + ForceRSA bool + + // ExtraExtensions are used when generating a new CSR (Certificate Request), + // thus allowing customization of the resulting certificate. + // For instance, TLS Feature Extension (RFC 7633) can be used + // to prevent an OCSP downgrade attack. + // + // The field value is passed to crypto/x509.CreateCertificateRequest + // in the template's ExtraExtensions field as is. + ExtraExtensions []pkix.Extension + + // ExternalAccountBinding optionally represents an arbitrary binding to an + // account of the CA to which the ACME server is tied. + // See RFC 8555, Section 7.3.4 for more details. + ExternalAccountBinding *acme.ExternalAccountBinding + + clientMu sync.Mutex + client *acme.Client // initialized by acmeClient method + + stateMu sync.Mutex + state map[certKey]*certState + + // renewal tracks the set of domains currently running renewal timers. + renewalMu sync.Mutex + renewal map[certKey]*domainRenewal + + // challengeMu guards tryHTTP01, certTokens and httpTokens. + challengeMu sync.RWMutex + // tryHTTP01 indicates whether the Manager should try "http-01" challenge type + // during the authorization flow. + tryHTTP01 bool + // httpTokens contains response body values for http-01 challenges + // and is keyed by the URL path at which a challenge response is expected + // to be provisioned. + // The entries are stored for the duration of the authorization flow. + httpTokens map[string][]byte + // certTokens contains temporary certificates for tls-alpn-01 challenges + // and is keyed by the domain name which matches the ClientHello server name. + // The entries are stored for the duration of the authorization flow. + certTokens map[string]*tls.Certificate + + // nowFunc, if not nil, returns the current time. This may be set for + // testing purposes. + nowFunc func() time.Time +} + +// certKey is the key by which certificates are tracked in state, renewal and cache. +type certKey struct { + domain string // without trailing dot + isRSA bool // RSA cert for legacy clients (as opposed to default ECDSA) + isToken bool // tls-based challenge token cert; key type is undefined regardless of isRSA +} + +func (c certKey) String() string { + if c.isToken { + return c.domain + "+token" + } + if c.isRSA { + return c.domain + "+rsa" + } + return c.domain +} + +// TLSConfig creates a new TLS config suitable for net/http.Server servers, +// supporting HTTP/2 and the tls-alpn-01 ACME challenge type. +func (m *Manager) TLSConfig() *tls.Config { + return &tls.Config{ + GetCertificate: m.GetCertificate, + NextProtos: []string{ + "h2", "http/1.1", // enable HTTP/2 + acme.ALPNProto, // enable tls-alpn ACME challenges + }, + } +} + +// GetCertificate implements the tls.Config.GetCertificate hook. +// It provides a TLS certificate for hello.ServerName host, including answering +// tls-alpn-01 challenges. +// All other fields of hello are ignored. +// +// If m.HostPolicy is non-nil, GetCertificate calls the policy before requesting +// a new cert. A non-nil error returned from m.HostPolicy halts TLS negotiation. +// The error is propagated back to the caller of GetCertificate and is user-visible. +// This does not affect cached certs. See HostPolicy field description for more details. +// +// If GetCertificate is used directly, instead of via Manager.TLSConfig, package users will +// also have to add acme.ALPNProto to NextProtos for tls-alpn-01, or use HTTPHandler for http-01. +func (m *Manager) GetCertificate(hello *tls.ClientHelloInfo) (*tls.Certificate, error) { + if m.Prompt == nil { + return nil, errors.New("acme/autocert: Manager.Prompt not set") + } + + name := hello.ServerName + if name == "" { + return nil, errors.New("acme/autocert: missing server name") + } + if !strings.Contains(strings.Trim(name, "."), ".") { + return nil, errors.New("acme/autocert: server name component count invalid") + } + + // Note that this conversion is necessary because some server names in the handshakes + // started by some clients (such as cURL) are not converted to Punycode, which will + // prevent us from obtaining certificates for them. In addition, we should also treat + // example.com and EXAMPLE.COM as equivalent and return the same certificate for them. + // Fortunately, this conversion also helped us deal with this kind of mixedcase problems. + // + // Due to the "σςΣ" problem (see https://unicode.org/faq/idn.html#22), we can't use + // idna.Punycode.ToASCII (or just idna.ToASCII) here. + name, err := idna.Lookup.ToASCII(name) + if err != nil { + return nil, errors.New("acme/autocert: server name contains invalid character") + } + + // In the worst-case scenario, the timeout needs to account for caching, host policy, + // domain ownership verification and certificate issuance. + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute) + defer cancel() + + // Check whether this is a token cert requested for TLS-ALPN challenge. + if wantsTokenCert(hello) { + m.challengeMu.RLock() + defer m.challengeMu.RUnlock() + if cert := m.certTokens[name]; cert != nil { + return cert, nil + } + if cert, err := m.cacheGet(ctx, certKey{domain: name, isToken: true}); err == nil { + return cert, nil + } + // TODO: cache error results? + return nil, fmt.Errorf("acme/autocert: no token cert for %q", name) + } + + // regular domain + if err := m.hostPolicy()(ctx, name); err != nil { + return nil, err + } + + ck := certKey{ + domain: strings.TrimSuffix(name, "."), // golang.org/issue/18114 + isRSA: !supportsECDSA(hello), + } + cert, err := m.cert(ctx, ck) + if err == nil { + return cert, nil + } + if err != ErrCacheMiss { + return nil, err + } + + // first-time + cert, err = m.createCert(ctx, ck) + if err != nil { + return nil, err + } + m.cachePut(ctx, ck, cert) + return cert, nil +} + +// wantsTokenCert reports whether a TLS request with SNI is made by a CA server +// for a challenge verification. +func wantsTokenCert(hello *tls.ClientHelloInfo) bool { + // tls-alpn-01 + if len(hello.SupportedProtos) == 1 && hello.SupportedProtos[0] == acme.ALPNProto { + return true + } + return false +} + +func supportsECDSA(hello *tls.ClientHelloInfo) bool { + // The "signature_algorithms" extension, if present, limits the key exchange + // algorithms allowed by the cipher suites. See RFC 5246, section 7.4.1.4.1. + if hello.SignatureSchemes != nil { + ecdsaOK := false + schemeLoop: + for _, scheme := range hello.SignatureSchemes { + const tlsECDSAWithSHA1 tls.SignatureScheme = 0x0203 // constant added in Go 1.10 + switch scheme { + case tlsECDSAWithSHA1, tls.ECDSAWithP256AndSHA256, + tls.ECDSAWithP384AndSHA384, tls.ECDSAWithP521AndSHA512: + ecdsaOK = true + break schemeLoop + } + } + if !ecdsaOK { + return false + } + } + if hello.SupportedCurves != nil { + ecdsaOK := false + for _, curve := range hello.SupportedCurves { + if curve == tls.CurveP256 { + ecdsaOK = true + break + } + } + if !ecdsaOK { + return false + } + } + for _, suite := range hello.CipherSuites { + switch suite { + case tls.TLS_ECDHE_ECDSA_WITH_RC4_128_SHA, + tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, + tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, + tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256, + tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, + tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, + tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305: + return true + } + } + return false +} + +// HTTPHandler configures the Manager to provision ACME "http-01" challenge responses. +// It returns an http.Handler that responds to the challenges and must be +// running on port 80. If it receives a request that is not an ACME challenge, +// it delegates the request to the optional fallback handler. +// +// If fallback is nil, the returned handler redirects all GET and HEAD requests +// to the default TLS port 443 with 302 Found status code, preserving the original +// request path and query. It responds with 400 Bad Request to all other HTTP methods. +// The fallback is not protected by the optional HostPolicy. +// +// Because the fallback handler is run with unencrypted port 80 requests, +// the fallback should not serve TLS-only requests. +// +// If HTTPHandler is never called, the Manager will only use the "tls-alpn-01" +// challenge for domain verification. +func (m *Manager) HTTPHandler(fallback http.Handler) http.Handler { + m.challengeMu.Lock() + defer m.challengeMu.Unlock() + m.tryHTTP01 = true + + if fallback == nil { + fallback = http.HandlerFunc(handleHTTPRedirect) + } + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if !strings.HasPrefix(r.URL.Path, "/.well-known/acme-challenge/") { + fallback.ServeHTTP(w, r) + return + } + // A reasonable context timeout for cache and host policy only, + // because we don't wait for a new certificate issuance here. + ctx, cancel := context.WithTimeout(r.Context(), time.Minute) + defer cancel() + if err := m.hostPolicy()(ctx, r.Host); err != nil { + http.Error(w, err.Error(), http.StatusForbidden) + return + } + data, err := m.httpToken(ctx, r.URL.Path) + if err != nil { + http.Error(w, err.Error(), http.StatusNotFound) + return + } + w.Write(data) + }) +} + +func handleHTTPRedirect(w http.ResponseWriter, r *http.Request) { + if r.Method != "GET" && r.Method != "HEAD" { + http.Error(w, "Use HTTPS", http.StatusBadRequest) + return + } + target := "https://" + stripPort(r.Host) + r.URL.RequestURI() + http.Redirect(w, r, target, http.StatusFound) +} + +func stripPort(hostport string) string { + host, _, err := net.SplitHostPort(hostport) + if err != nil { + return hostport + } + return net.JoinHostPort(host, "443") +} + +// cert returns an existing certificate either from m.state or cache. +// If a certificate is found in cache but not in m.state, the latter will be filled +// with the cached value. +func (m *Manager) cert(ctx context.Context, ck certKey) (*tls.Certificate, error) { + m.stateMu.Lock() + if s, ok := m.state[ck]; ok { + m.stateMu.Unlock() + s.RLock() + defer s.RUnlock() + return s.tlscert() + } + defer m.stateMu.Unlock() + cert, err := m.cacheGet(ctx, ck) + if err != nil { + return nil, err + } + signer, ok := cert.PrivateKey.(crypto.Signer) + if !ok { + return nil, errors.New("acme/autocert: private key cannot sign") + } + if m.state == nil { + m.state = make(map[certKey]*certState) + } + s := &certState{ + key: signer, + cert: cert.Certificate, + leaf: cert.Leaf, + } + m.state[ck] = s + m.startRenew(ck, s.key, s.leaf.NotAfter) + return cert, nil +} + +// cacheGet always returns a valid certificate, or an error otherwise. +// If a cached certificate exists but is not valid, ErrCacheMiss is returned. +func (m *Manager) cacheGet(ctx context.Context, ck certKey) (*tls.Certificate, error) { + if m.Cache == nil { + return nil, ErrCacheMiss + } + data, err := m.Cache.Get(ctx, ck.String()) + if err != nil { + return nil, err + } + + // private + priv, pub := pem.Decode(data) + if priv == nil || !strings.Contains(priv.Type, "PRIVATE") { + return nil, ErrCacheMiss + } + privKey, err := parsePrivateKey(priv.Bytes) + if err != nil { + return nil, err + } + + // public + var pubDER [][]byte + for len(pub) > 0 { + var b *pem.Block + b, pub = pem.Decode(pub) + if b == nil { + break + } + pubDER = append(pubDER, b.Bytes) + } + if len(pub) > 0 { + // Leftover content not consumed by pem.Decode. Corrupt. Ignore. + return nil, ErrCacheMiss + } + + // verify and create TLS cert + leaf, err := validCert(ck, pubDER, privKey, m.now()) + if err != nil { + return nil, ErrCacheMiss + } + tlscert := &tls.Certificate{ + Certificate: pubDER, + PrivateKey: privKey, + Leaf: leaf, + } + return tlscert, nil +} + +func (m *Manager) cachePut(ctx context.Context, ck certKey, tlscert *tls.Certificate) error { + if m.Cache == nil { + return nil + } + + // contains PEM-encoded data + var buf bytes.Buffer + + // private + switch key := tlscert.PrivateKey.(type) { + case *ecdsa.PrivateKey: + if err := encodeECDSAKey(&buf, key); err != nil { + return err + } + case *rsa.PrivateKey: + b := x509.MarshalPKCS1PrivateKey(key) + pb := &pem.Block{Type: "RSA PRIVATE KEY", Bytes: b} + if err := pem.Encode(&buf, pb); err != nil { + return err + } + default: + return errors.New("acme/autocert: unknown private key type") + } + + // public + for _, b := range tlscert.Certificate { + pb := &pem.Block{Type: "CERTIFICATE", Bytes: b} + if err := pem.Encode(&buf, pb); err != nil { + return err + } + } + + return m.Cache.Put(ctx, ck.String(), buf.Bytes()) +} + +func encodeECDSAKey(w io.Writer, key *ecdsa.PrivateKey) error { + b, err := x509.MarshalECPrivateKey(key) + if err != nil { + return err + } + pb := &pem.Block{Type: "EC PRIVATE KEY", Bytes: b} + return pem.Encode(w, pb) +} + +// createCert starts the domain ownership verification and returns a certificate +// for that domain upon success. +// +// If the domain is already being verified, it waits for the existing verification to complete. +// Either way, createCert blocks for the duration of the whole process. +func (m *Manager) createCert(ctx context.Context, ck certKey) (*tls.Certificate, error) { + // TODO: maybe rewrite this whole piece using sync.Once + state, err := m.certState(ck) + if err != nil { + return nil, err + } + // state may exist if another goroutine is already working on it + // in which case just wait for it to finish + if !state.locked { + state.RLock() + defer state.RUnlock() + return state.tlscert() + } + + // We are the first; state is locked. + // Unblock the readers when domain ownership is verified + // and we got the cert or the process failed. + defer state.Unlock() + state.locked = false + + der, leaf, err := m.authorizedCert(ctx, state.key, ck) + if err != nil { + // Remove the failed state after some time, + // making the manager call createCert again on the following TLS hello. + didRemove := testDidRemoveState // The lifetime of this timer is untracked, so copy mutable local state to avoid races. + time.AfterFunc(createCertRetryAfter, func() { + defer didRemove(ck) + m.stateMu.Lock() + defer m.stateMu.Unlock() + // Verify the state hasn't changed and it's still invalid + // before deleting. + s, ok := m.state[ck] + if !ok { + return + } + if _, err := validCert(ck, s.cert, s.key, m.now()); err == nil { + return + } + delete(m.state, ck) + }) + return nil, err + } + state.cert = der + state.leaf = leaf + m.startRenew(ck, state.key, state.leaf.NotAfter) + return state.tlscert() +} + +// certState returns a new or existing certState. +// If a new certState is returned, state.exist is false and the state is locked. +// The returned error is non-nil only in the case where a new state could not be created. +func (m *Manager) certState(ck certKey) (*certState, error) { + m.stateMu.Lock() + defer m.stateMu.Unlock() + if m.state == nil { + m.state = make(map[certKey]*certState) + } + // existing state + if state, ok := m.state[ck]; ok { + return state, nil + } + + // new locked state + var ( + err error + key crypto.Signer + ) + if ck.isRSA { + key, err = rsa.GenerateKey(rand.Reader, 2048) + } else { + key, err = ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + } + if err != nil { + return nil, err + } + + state := &certState{ + key: key, + locked: true, + } + state.Lock() // will be unlocked by m.certState caller + m.state[ck] = state + return state, nil +} + +// authorizedCert starts the domain ownership verification process and requests a new cert upon success. +// The key argument is the certificate private key. +func (m *Manager) authorizedCert(ctx context.Context, key crypto.Signer, ck certKey) (der [][]byte, leaf *x509.Certificate, err error) { + csr, err := certRequest(key, ck.domain, m.ExtraExtensions) + if err != nil { + return nil, nil, err + } + + client, err := m.acmeClient(ctx) + if err != nil { + return nil, nil, err + } + dir, err := client.Discover(ctx) + if err != nil { + return nil, nil, err + } + if dir.OrderURL == "" { + return nil, nil, errPreRFC + } + + o, err := m.verifyRFC(ctx, client, ck.domain) + if err != nil { + return nil, nil, err + } + chain, _, err := client.CreateOrderCert(ctx, o.FinalizeURL, csr, true) + if err != nil { + return nil, nil, err + } + + leaf, err = validCert(ck, chain, key, m.now()) + if err != nil { + return nil, nil, err + } + return chain, leaf, nil +} + +// verifyRFC runs the identifier (domain) order-based authorization flow for RFC compliant CAs +// using each applicable ACME challenge type. +func (m *Manager) verifyRFC(ctx context.Context, client *acme.Client, domain string) (*acme.Order, error) { + // Try each supported challenge type starting with a new order each time. + // The nextTyp index of the next challenge type to try is shared across + // all order authorizations: if we've tried a challenge type once and it didn't work, + // it will most likely not work on another order's authorization either. + challengeTypes := m.supportedChallengeTypes() + nextTyp := 0 // challengeTypes index +AuthorizeOrderLoop: + for { + o, err := client.AuthorizeOrder(ctx, acme.DomainIDs(domain)) + if err != nil { + return nil, err + } + // Remove all hanging authorizations to reduce rate limit quotas + // after we're done. + defer func(urls []string) { + go m.deactivatePendingAuthz(urls) + }(o.AuthzURLs) + + // Check if there's actually anything we need to do. + switch o.Status { + case acme.StatusReady: + // Already authorized. + return o, nil + case acme.StatusPending: + // Continue normal Order-based flow. + default: + return nil, fmt.Errorf("acme/autocert: invalid new order status %q; order URL: %q", o.Status, o.URI) + } + + // Satisfy all pending authorizations. + for _, zurl := range o.AuthzURLs { + z, err := client.GetAuthorization(ctx, zurl) + if err != nil { + return nil, err + } + if z.Status != acme.StatusPending { + // We are interested only in pending authorizations. + continue + } + // Pick the next preferred challenge. + var chal *acme.Challenge + for chal == nil && nextTyp < len(challengeTypes) { + chal = pickChallenge(challengeTypes[nextTyp], z.Challenges) + nextTyp++ + } + if chal == nil { + return nil, fmt.Errorf("acme/autocert: unable to satisfy %q for domain %q: no viable challenge type found", z.URI, domain) + } + // Respond to the challenge and wait for validation result. + cleanup, err := m.fulfill(ctx, client, chal, domain) + if err != nil { + continue AuthorizeOrderLoop + } + defer cleanup() + if _, err := client.Accept(ctx, chal); err != nil { + continue AuthorizeOrderLoop + } + if _, err := client.WaitAuthorization(ctx, z.URI); err != nil { + continue AuthorizeOrderLoop + } + } + + // All authorizations are satisfied. + // Wait for the CA to update the order status. + o, err = client.WaitOrder(ctx, o.URI) + if err != nil { + continue AuthorizeOrderLoop + } + return o, nil + } +} + +func pickChallenge(typ string, chal []*acme.Challenge) *acme.Challenge { + for _, c := range chal { + if c.Type == typ { + return c + } + } + return nil +} + +func (m *Manager) supportedChallengeTypes() []string { + m.challengeMu.RLock() + defer m.challengeMu.RUnlock() + typ := []string{"tls-alpn-01"} + if m.tryHTTP01 { + typ = append(typ, "http-01") + } + return typ +} + +// deactivatePendingAuthz relinquishes all authorizations identified by the elements +// of the provided uri slice which are in "pending" state. +// It ignores revocation errors. +// +// deactivatePendingAuthz takes no context argument and instead runs with its own +// "detached" context because deactivations are done in a goroutine separate from +// that of the main issuance or renewal flow. +func (m *Manager) deactivatePendingAuthz(uri []string) { + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute) + defer cancel() + client, err := m.acmeClient(ctx) + if err != nil { + return + } + for _, u := range uri { + z, err := client.GetAuthorization(ctx, u) + if err == nil && z.Status == acme.StatusPending { + client.RevokeAuthorization(ctx, u) + } + } +} + +// fulfill provisions a response to the challenge chal. +// The cleanup is non-nil only if provisioning succeeded. +func (m *Manager) fulfill(ctx context.Context, client *acme.Client, chal *acme.Challenge, domain string) (cleanup func(), err error) { + switch chal.Type { + case "tls-alpn-01": + cert, err := client.TLSALPN01ChallengeCert(chal.Token, domain) + if err != nil { + return nil, err + } + m.putCertToken(ctx, domain, &cert) + return func() { go m.deleteCertToken(domain) }, nil + case "http-01": + resp, err := client.HTTP01ChallengeResponse(chal.Token) + if err != nil { + return nil, err + } + p := client.HTTP01ChallengePath(chal.Token) + m.putHTTPToken(ctx, p, resp) + return func() { go m.deleteHTTPToken(p) }, nil + } + return nil, fmt.Errorf("acme/autocert: unknown challenge type %q", chal.Type) +} + +// putCertToken stores the token certificate with the specified name +// in both m.certTokens map and m.Cache. +func (m *Manager) putCertToken(ctx context.Context, name string, cert *tls.Certificate) { + m.challengeMu.Lock() + defer m.challengeMu.Unlock() + if m.certTokens == nil { + m.certTokens = make(map[string]*tls.Certificate) + } + m.certTokens[name] = cert + m.cachePut(ctx, certKey{domain: name, isToken: true}, cert) +} + +// deleteCertToken removes the token certificate with the specified name +// from both m.certTokens map and m.Cache. +func (m *Manager) deleteCertToken(name string) { + m.challengeMu.Lock() + defer m.challengeMu.Unlock() + delete(m.certTokens, name) + if m.Cache != nil { + ck := certKey{domain: name, isToken: true} + m.Cache.Delete(context.Background(), ck.String()) + } +} + +// httpToken retrieves an existing http-01 token value from an in-memory map +// or the optional cache. +func (m *Manager) httpToken(ctx context.Context, tokenPath string) ([]byte, error) { + m.challengeMu.RLock() + defer m.challengeMu.RUnlock() + if v, ok := m.httpTokens[tokenPath]; ok { + return v, nil + } + if m.Cache == nil { + return nil, fmt.Errorf("acme/autocert: no token at %q", tokenPath) + } + return m.Cache.Get(ctx, httpTokenCacheKey(tokenPath)) +} + +// putHTTPToken stores an http-01 token value using tokenPath as key +// in both in-memory map and the optional Cache. +// +// It ignores any error returned from Cache.Put. +func (m *Manager) putHTTPToken(ctx context.Context, tokenPath, val string) { + m.challengeMu.Lock() + defer m.challengeMu.Unlock() + if m.httpTokens == nil { + m.httpTokens = make(map[string][]byte) + } + b := []byte(val) + m.httpTokens[tokenPath] = b + if m.Cache != nil { + m.Cache.Put(ctx, httpTokenCacheKey(tokenPath), b) + } +} + +// deleteHTTPToken removes an http-01 token value from both in-memory map +// and the optional Cache, ignoring any error returned from the latter. +// +// If m.Cache is non-nil, it blocks until Cache.Delete returns without a timeout. +func (m *Manager) deleteHTTPToken(tokenPath string) { + m.challengeMu.Lock() + defer m.challengeMu.Unlock() + delete(m.httpTokens, tokenPath) + if m.Cache != nil { + m.Cache.Delete(context.Background(), httpTokenCacheKey(tokenPath)) + } +} + +// httpTokenCacheKey returns a key at which an http-01 token value may be stored +// in the Manager's optional Cache. +func httpTokenCacheKey(tokenPath string) string { + return path.Base(tokenPath) + "+http-01" +} + +// startRenew starts a cert renewal timer loop, one per domain. +// +// The loop is scheduled in two cases: +// - a cert was fetched from cache for the first time (wasn't in m.state) +// - a new cert was created by m.createCert +// +// The key argument is a certificate private key. +// The exp argument is the cert expiration time (NotAfter). +func (m *Manager) startRenew(ck certKey, key crypto.Signer, exp time.Time) { + m.renewalMu.Lock() + defer m.renewalMu.Unlock() + if m.renewal[ck] != nil { + // another goroutine is already on it + return + } + if m.renewal == nil { + m.renewal = make(map[certKey]*domainRenewal) + } + dr := &domainRenewal{m: m, ck: ck, key: key} + m.renewal[ck] = dr + dr.start(exp) +} + +// stopRenew stops all currently running cert renewal timers. +// The timers are not restarted during the lifetime of the Manager. +func (m *Manager) stopRenew() { + m.renewalMu.Lock() + defer m.renewalMu.Unlock() + for name, dr := range m.renewal { + delete(m.renewal, name) + dr.stop() + } +} + +func (m *Manager) accountKey(ctx context.Context) (crypto.Signer, error) { + const keyName = "acme_account+key" + + // Previous versions of autocert stored the value under a different key. + const legacyKeyName = "acme_account.key" + + genKey := func() (*ecdsa.PrivateKey, error) { + return ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + } + + if m.Cache == nil { + return genKey() + } + + data, err := m.Cache.Get(ctx, keyName) + if err == ErrCacheMiss { + data, err = m.Cache.Get(ctx, legacyKeyName) + } + if err == ErrCacheMiss { + key, err := genKey() + if err != nil { + return nil, err + } + var buf bytes.Buffer + if err := encodeECDSAKey(&buf, key); err != nil { + return nil, err + } + if err := m.Cache.Put(ctx, keyName, buf.Bytes()); err != nil { + return nil, err + } + return key, nil + } + if err != nil { + return nil, err + } + + priv, _ := pem.Decode(data) + if priv == nil || !strings.Contains(priv.Type, "PRIVATE") { + return nil, errors.New("acme/autocert: invalid account key found in cache") + } + return parsePrivateKey(priv.Bytes) +} + +func (m *Manager) acmeClient(ctx context.Context) (*acme.Client, error) { + m.clientMu.Lock() + defer m.clientMu.Unlock() + if m.client != nil { + return m.client, nil + } + + client := m.Client + if client == nil { + client = &acme.Client{DirectoryURL: DefaultACMEDirectory} + } + if client.Key == nil { + var err error + client.Key, err = m.accountKey(ctx) + if err != nil { + return nil, err + } + } + if client.UserAgent == "" { + client.UserAgent = "autocert" + } + var contact []string + if m.Email != "" { + contact = []string{"mailto:" + m.Email} + } + a := &acme.Account{Contact: contact, ExternalAccountBinding: m.ExternalAccountBinding} + _, err := client.Register(ctx, a, m.Prompt) + if err == nil || isAccountAlreadyExist(err) { + m.client = client + err = nil + } + return m.client, err +} + +// isAccountAlreadyExist reports whether the err, as returned from acme.Client.Register, +// indicates the account has already been registered. +func isAccountAlreadyExist(err error) bool { + if err == acme.ErrAccountAlreadyExists { + return true + } + ae, ok := err.(*acme.Error) + return ok && ae.StatusCode == http.StatusConflict +} + +func (m *Manager) hostPolicy() HostPolicy { + if m.HostPolicy != nil { + return m.HostPolicy + } + return defaultHostPolicy +} + +func (m *Manager) renewBefore() time.Duration { + if m.RenewBefore > renewJitter { + return m.RenewBefore + } + return 720 * time.Hour // 30 days +} + +func (m *Manager) now() time.Time { + if m.nowFunc != nil { + return m.nowFunc() + } + return time.Now() +} + +// certState is ready when its mutex is unlocked for reading. +type certState struct { + sync.RWMutex + locked bool // locked for read/write + key crypto.Signer // private key for cert + cert [][]byte // DER encoding + leaf *x509.Certificate // parsed cert[0]; always non-nil if cert != nil +} + +// tlscert creates a tls.Certificate from s.key and s.cert. +// Callers should wrap it in s.RLock() and s.RUnlock(). +func (s *certState) tlscert() (*tls.Certificate, error) { + if s.key == nil { + return nil, errors.New("acme/autocert: missing signer") + } + if len(s.cert) == 0 { + return nil, errors.New("acme/autocert: missing certificate") + } + return &tls.Certificate{ + PrivateKey: s.key, + Certificate: s.cert, + Leaf: s.leaf, + }, nil +} + +// certRequest generates a CSR for the given common name. +func certRequest(key crypto.Signer, name string, ext []pkix.Extension) ([]byte, error) { + req := &x509.CertificateRequest{ + Subject: pkix.Name{CommonName: name}, + DNSNames: []string{name}, + ExtraExtensions: ext, + } + return x509.CreateCertificateRequest(rand.Reader, req, key) +} + +// Attempt to parse the given private key DER block. OpenSSL 0.9.8 generates +// PKCS#1 private keys by default, while OpenSSL 1.0.0 generates PKCS#8 keys. +// OpenSSL ecparam generates SEC1 EC private keys for ECDSA. We try all three. +// +// Inspired by parsePrivateKey in crypto/tls/tls.go. +func parsePrivateKey(der []byte) (crypto.Signer, error) { + if key, err := x509.ParsePKCS1PrivateKey(der); err == nil { + return key, nil + } + if key, err := x509.ParsePKCS8PrivateKey(der); err == nil { + switch key := key.(type) { + case *rsa.PrivateKey: + return key, nil + case *ecdsa.PrivateKey: + return key, nil + default: + return nil, errors.New("acme/autocert: unknown private key type in PKCS#8 wrapping") + } + } + if key, err := x509.ParseECPrivateKey(der); err == nil { + return key, nil + } + + return nil, errors.New("acme/autocert: failed to parse private key") +} + +// validCert parses a cert chain provided as der argument and verifies the leaf and der[0] +// correspond to the private key, the domain and key type match, and expiration dates +// are valid. It doesn't do any revocation checking. +// +// The returned value is the verified leaf cert. +func validCert(ck certKey, der [][]byte, key crypto.Signer, now time.Time) (leaf *x509.Certificate, err error) { + // parse public part(s) + var n int + for _, b := range der { + n += len(b) + } + pub := make([]byte, n) + n = 0 + for _, b := range der { + n += copy(pub[n:], b) + } + x509Cert, err := x509.ParseCertificates(pub) + if err != nil || len(x509Cert) == 0 { + return nil, errors.New("acme/autocert: no public key found") + } + // verify the leaf is not expired and matches the domain name + leaf = x509Cert[0] + if now.Before(leaf.NotBefore) { + return nil, errors.New("acme/autocert: certificate is not valid yet") + } + if now.After(leaf.NotAfter) { + return nil, errors.New("acme/autocert: expired certificate") + } + if err := leaf.VerifyHostname(ck.domain); err != nil { + return nil, err + } + // renew certificates revoked by Let's Encrypt in January 2022 + if isRevokedLetsEncrypt(leaf) { + return nil, errors.New("acme/autocert: certificate was probably revoked by Let's Encrypt") + } + // ensure the leaf corresponds to the private key and matches the certKey type + switch pub := leaf.PublicKey.(type) { + case *rsa.PublicKey: + prv, ok := key.(*rsa.PrivateKey) + if !ok { + return nil, errors.New("acme/autocert: private key type does not match public key type") + } + if pub.N.Cmp(prv.N) != 0 { + return nil, errors.New("acme/autocert: private key does not match public key") + } + if !ck.isRSA && !ck.isToken { + return nil, errors.New("acme/autocert: key type does not match expected value") + } + case *ecdsa.PublicKey: + prv, ok := key.(*ecdsa.PrivateKey) + if !ok { + return nil, errors.New("acme/autocert: private key type does not match public key type") + } + if pub.X.Cmp(prv.X) != 0 || pub.Y.Cmp(prv.Y) != 0 { + return nil, errors.New("acme/autocert: private key does not match public key") + } + if ck.isRSA && !ck.isToken { + return nil, errors.New("acme/autocert: key type does not match expected value") + } + default: + return nil, errors.New("acme/autocert: unknown public key algorithm") + } + return leaf, nil +} + +// https://community.letsencrypt.org/t/2022-01-25-issue-with-tls-alpn-01-validation-method/170450 +var letsEncryptFixDeployTime = time.Date(2022, time.January, 26, 00, 48, 0, 0, time.UTC) + +// isRevokedLetsEncrypt returns whether the certificate is likely to be part of +// a batch of certificates revoked by Let's Encrypt in January 2022. This check +// can be safely removed from May 2022. +func isRevokedLetsEncrypt(cert *x509.Certificate) bool { + O := cert.Issuer.Organization + return len(O) == 1 && O[0] == "Let's Encrypt" && + cert.NotBefore.Before(letsEncryptFixDeployTime) +} + +type lockedMathRand struct { + sync.Mutex + rnd *mathrand.Rand +} + +func (r *lockedMathRand) int63n(max int64) int64 { + r.Lock() + n := r.rnd.Int63n(max) + r.Unlock() + return n +} + +// For easier testing. +var ( + // Called when a state is removed. + testDidRemoveState = func(certKey) {} +) diff --git a/third_party/forked/acme/autocert/autocert_test.go b/third_party/forked/acme/autocert/autocert_test.go new file mode 100644 index 00000000000..49419364816 --- /dev/null +++ b/third_party/forked/acme/autocert/autocert_test.go @@ -0,0 +1,996 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package autocert + +import ( + "bytes" + "context" + "crypto" + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "crypto/rsa" + "crypto/tls" + "crypto/x509" + "crypto/x509/pkix" + "encoding/asn1" + "fmt" + "io" + "math/big" + "net/http" + "net/http/httptest" + "reflect" + "strings" + "sync" + "testing" + "time" + + "github.com/cert-manager/cert-manager/third_party/forked/acme" + "github.com/cert-manager/cert-manager/third_party/forked/acme/autocert/internal/acmetest" +) + +var ( + exampleDomain = "example.org" + exampleCertKey = certKey{domain: exampleDomain} + exampleCertKeyRSA = certKey{domain: exampleDomain, isRSA: true} +) + +type memCache struct { + t *testing.T + mu sync.Mutex + keyData map[string][]byte +} + +func (m *memCache) Get(ctx context.Context, key string) ([]byte, error) { + m.mu.Lock() + defer m.mu.Unlock() + + v, ok := m.keyData[key] + if !ok { + return nil, ErrCacheMiss + } + return v, nil +} + +// filenameSafe returns whether all characters in s are printable ASCII +// and safe to use in a filename on most filesystems. +func filenameSafe(s string) bool { + for _, c := range s { + if c < 0x20 || c > 0x7E { + return false + } + switch c { + case '\\', '/', ':', '*', '?', '"', '<', '>', '|': + return false + } + } + return true +} + +func (m *memCache) Put(ctx context.Context, key string, data []byte) error { + if !filenameSafe(key) { + m.t.Errorf("invalid characters in cache key %q", key) + } + + m.mu.Lock() + defer m.mu.Unlock() + + m.keyData[key] = data + return nil +} + +func (m *memCache) Delete(ctx context.Context, key string) error { + m.mu.Lock() + defer m.mu.Unlock() + + delete(m.keyData, key) + return nil +} + +func newMemCache(t *testing.T) *memCache { + return &memCache{ + t: t, + keyData: make(map[string][]byte), + } +} + +func (m *memCache) numCerts() int { + m.mu.Lock() + defer m.mu.Unlock() + + res := 0 + for key := range m.keyData { + if strings.HasSuffix(key, "+token") || + strings.HasSuffix(key, "+key") || + strings.HasSuffix(key, "+http-01") { + continue + } + res++ + } + return res +} + +func dummyCert(pub interface{}, san ...string) ([]byte, error) { + return dateDummyCert(pub, time.Now(), time.Now().Add(90*24*time.Hour), san...) +} + +func dateDummyCert(pub interface{}, start, end time.Time, san ...string) ([]byte, error) { + // use EC key to run faster on 386 + key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if err != nil { + return nil, err + } + t := &x509.Certificate{ + SerialNumber: randomSerial(), + NotBefore: start, + NotAfter: end, + BasicConstraintsValid: true, + KeyUsage: x509.KeyUsageKeyEncipherment, + DNSNames: san, + } + if pub == nil { + pub = &key.PublicKey + } + return x509.CreateCertificate(rand.Reader, t, t, pub, key) +} + +func randomSerial() *big.Int { + serial, err := rand.Int(rand.Reader, new(big.Int).Lsh(big.NewInt(1), 32)) + if err != nil { + panic(err) + } + return serial +} + +type algorithmSupport int + +const ( + algRSA algorithmSupport = iota + algECDSA +) + +func clientHelloInfo(sni string, alg algorithmSupport) *tls.ClientHelloInfo { + hello := &tls.ClientHelloInfo{ + ServerName: sni, + CipherSuites: []uint16{tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305}, + } + if alg == algECDSA { + hello.CipherSuites = append(hello.CipherSuites, tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305) + } + return hello +} + +func testManager(t *testing.T) *Manager { + man := &Manager{ + Prompt: AcceptTOS, + Cache: newMemCache(t), + } + t.Cleanup(man.stopRenew) + return man +} + +func TestGetCertificate(t *testing.T) { + tests := []struct { + name string + hello *tls.ClientHelloInfo + domain string + expectError string + prepare func(t *testing.T, man *Manager, s *acmetest.CAServer) + verify func(t *testing.T, man *Manager, leaf *x509.Certificate) + disableALPN bool + disableHTTP bool + }{ + { + name: "ALPN", + hello: clientHelloInfo("example.org", algECDSA), + domain: "example.org", + disableHTTP: true, + }, + { + name: "HTTP", + hello: clientHelloInfo("example.org", algECDSA), + domain: "example.org", + disableALPN: true, + }, + { + name: "nilPrompt", + hello: clientHelloInfo("example.org", algECDSA), + domain: "example.org", + prepare: func(t *testing.T, man *Manager, s *acmetest.CAServer) { + man.Prompt = nil + }, + expectError: "Manager.Prompt not set", + }, + { + name: "trailingDot", + hello: clientHelloInfo("example.org.", algECDSA), + domain: "example.org", + }, + { + name: "unicodeIDN", + hello: clientHelloInfo("éé.com", algECDSA), + domain: "xn--9caa.com", + }, + { + name: "unicodeIDN/mixedCase", + hello: clientHelloInfo("éÉ.com", algECDSA), + domain: "xn--9caa.com", + }, + { + name: "upperCase", + hello: clientHelloInfo("EXAMPLE.ORG", algECDSA), + domain: "example.org", + }, + { + name: "goodCache", + hello: clientHelloInfo("example.org", algECDSA), + domain: "example.org", + prepare: func(t *testing.T, man *Manager, s *acmetest.CAServer) { + // Make a valid cert and cache it. + c := s.Start().LeafCert(exampleDomain, "ECDSA", + // Use a time before the Let's Encrypt revocation cutoff to also test + // that non-Let's Encrypt certificates are not renewed. + time.Date(2022, time.January, 1, 0, 0, 0, 0, time.UTC), + time.Date(2122, time.January, 1, 0, 0, 0, 0, time.UTC), + ) + if err := man.cachePut(context.Background(), exampleCertKey, c); err != nil { + t.Fatalf("man.cachePut: %v", err) + } + }, + // Break the server to check that the cache is used. + disableALPN: true, disableHTTP: true, + }, + { + name: "expiredCache", + hello: clientHelloInfo("example.org", algECDSA), + domain: "example.org", + prepare: func(t *testing.T, man *Manager, s *acmetest.CAServer) { + // Make an expired cert and cache it. + c := s.Start().LeafCert(exampleDomain, "ECDSA", time.Now().Add(-10*time.Minute), time.Now().Add(-5*time.Minute)) + if err := man.cachePut(context.Background(), exampleCertKey, c); err != nil { + t.Fatalf("man.cachePut: %v", err) + } + }, + }, + { + name: "forceRSA", + hello: clientHelloInfo("example.org", algECDSA), + domain: "example.org", + prepare: func(t *testing.T, man *Manager, s *acmetest.CAServer) { + man.ForceRSA = true + }, + verify: func(t *testing.T, man *Manager, leaf *x509.Certificate) { + if _, ok := leaf.PublicKey.(*ecdsa.PublicKey); !ok { + t.Errorf("leaf.PublicKey is %T; want *ecdsa.PublicKey", leaf.PublicKey) + } + }, + }, + { + name: "goodLetsEncrypt", + hello: clientHelloInfo("example.org", algECDSA), + domain: "example.org", + prepare: func(t *testing.T, man *Manager, s *acmetest.CAServer) { + // Make a valid certificate issued after the TLS-ALPN-01 + // revocation window and cache it. + s.IssuerName(pkix.Name{Country: []string{"US"}, + Organization: []string{"Let's Encrypt"}, CommonName: "R3"}) + c := s.Start().LeafCert(exampleDomain, "ECDSA", + time.Date(2022, time.January, 26, 12, 0, 0, 0, time.UTC), + time.Date(2122, time.January, 1, 0, 0, 0, 0, time.UTC), + ) + if err := man.cachePut(context.Background(), exampleCertKey, c); err != nil { + t.Fatalf("man.cachePut: %v", err) + } + }, + // Break the server to check that the cache is used. + disableALPN: true, disableHTTP: true, + }, + { + name: "revokedLetsEncrypt", + hello: clientHelloInfo("example.org", algECDSA), + domain: "example.org", + prepare: func(t *testing.T, man *Manager, s *acmetest.CAServer) { + // Make a certificate issued during the TLS-ALPN-01 + // revocation window and cache it. + s.IssuerName(pkix.Name{Country: []string{"US"}, + Organization: []string{"Let's Encrypt"}, CommonName: "R3"}) + c := s.Start().LeafCert(exampleDomain, "ECDSA", + time.Date(2022, time.January, 1, 0, 0, 0, 0, time.UTC), + time.Date(2122, time.January, 1, 0, 0, 0, 0, time.UTC), + ) + if err := man.cachePut(context.Background(), exampleCertKey, c); err != nil { + t.Fatalf("man.cachePut: %v", err) + } + }, + verify: func(t *testing.T, man *Manager, leaf *x509.Certificate) { + if leaf.NotBefore.Before(time.Now().Add(-10 * time.Minute)) { + t.Error("certificate was not reissued") + } + }, + }, + { + // TestGetCertificate/tokenCache tests the fallback of token + // certificate fetches to cache when Manager.certTokens misses. + name: "tokenCacheALPN", + hello: clientHelloInfo("example.org", algECDSA), + domain: "example.org", + prepare: func(t *testing.T, man *Manager, s *acmetest.CAServer) { + // Make a separate manager with a shared cache, simulating + // separate nodes that serve requests for the same domain. + man2 := testManager(t) + man2.Cache = man.Cache + // Redirect the verification request to man2, although the + // client request will hit man, testing that they can complete a + // verification by communicating through the cache. + s.ResolveGetCertificate("example.org", man2.GetCertificate) + }, + // Drop the default verification paths. + disableALPN: true, + }, + { + name: "tokenCacheHTTP", + hello: clientHelloInfo("example.org", algECDSA), + domain: "example.org", + prepare: func(t *testing.T, man *Manager, s *acmetest.CAServer) { + man2 := testManager(t) + man2.Cache = man.Cache + s.ResolveHandler("example.org", man2.HTTPHandler(nil)) + }, + disableHTTP: true, + }, + { + name: "ecdsa", + hello: clientHelloInfo("example.org", algECDSA), + domain: "example.org", + verify: func(t *testing.T, man *Manager, leaf *x509.Certificate) { + if _, ok := leaf.PublicKey.(*ecdsa.PublicKey); !ok { + t.Error("an ECDSA client was served a non-ECDSA certificate") + } + }, + }, + { + name: "rsa", + hello: clientHelloInfo("example.org", algRSA), + domain: "example.org", + verify: func(t *testing.T, man *Manager, leaf *x509.Certificate) { + if _, ok := leaf.PublicKey.(*rsa.PublicKey); !ok { + t.Error("an RSA client was served a non-RSA certificate") + } + }, + }, + { + name: "wrongCacheKeyType", + hello: clientHelloInfo("example.org", algECDSA), + domain: "example.org", + prepare: func(t *testing.T, man *Manager, s *acmetest.CAServer) { + // Make an RSA cert and cache it without suffix. + c := s.Start().LeafCert(exampleDomain, "RSA", time.Now(), time.Now().Add(90*24*time.Hour)) + if err := man.cachePut(context.Background(), exampleCertKey, c); err != nil { + t.Fatalf("man.cachePut: %v", err) + } + }, + verify: func(t *testing.T, man *Manager, leaf *x509.Certificate) { + // The RSA cached cert should be silently ignored and replaced. + if _, ok := leaf.PublicKey.(*ecdsa.PublicKey); !ok { + t.Error("an ECDSA client was served a non-ECDSA certificate") + } + if numCerts := man.Cache.(*memCache).numCerts(); numCerts != 1 { + t.Errorf("found %d certificates in cache; want %d", numCerts, 1) + } + }, + }, + { + name: "almostExpiredCache", + hello: clientHelloInfo("example.org", algECDSA), + domain: "example.org", + prepare: func(t *testing.T, man *Manager, s *acmetest.CAServer) { + man.RenewBefore = 24 * time.Hour + // Cache an almost expired cert. + c := s.Start().LeafCert(exampleDomain, "ECDSA", time.Now(), time.Now().Add(10*time.Minute)) + if err := man.cachePut(context.Background(), exampleCertKey, c); err != nil { + t.Fatalf("man.cachePut: %v", err) + } + }, + }, + { + name: "provideExternalAuth", + hello: clientHelloInfo("example.org", algECDSA), + domain: "example.org", + prepare: func(t *testing.T, man *Manager, s *acmetest.CAServer) { + s.ExternalAccountRequired() + + man.ExternalAccountBinding = &acme.ExternalAccountBinding{ + KID: "test-key", + Key: make([]byte, 32), + } + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + man := testManager(t) + s := acmetest.NewCAServer(t) + if !tt.disableALPN { + s.ResolveGetCertificate(tt.domain, man.GetCertificate) + } + if !tt.disableHTTP { + s.ResolveHandler(tt.domain, man.HTTPHandler(nil)) + } + + if tt.prepare != nil { + tt.prepare(t, man, s) + } + + s.Start() + + man.Client = &acme.Client{DirectoryURL: s.URL()} + + tlscert, err := man.GetCertificate(tt.hello) + if tt.expectError != "" { + if err == nil { + t.Fatal("expected error, got certificate") + } + if !strings.Contains(err.Error(), tt.expectError) { + t.Errorf("got %q, expected %q", err, tt.expectError) + } + return + } + if err != nil { + t.Fatalf("man.GetCertificate: %v", err) + } + + leaf, err := x509.ParseCertificate(tlscert.Certificate[0]) + if err != nil { + t.Fatal(err) + } + opts := x509.VerifyOptions{ + DNSName: tt.domain, + Intermediates: x509.NewCertPool(), + Roots: s.Roots(), + } + for _, cert := range tlscert.Certificate[1:] { + c, err := x509.ParseCertificate(cert) + if err != nil { + t.Fatal(err) + } + opts.Intermediates.AddCert(c) + } + if _, err := leaf.Verify(opts); err != nil { + t.Error(err) + } + + if san := leaf.DNSNames[0]; san != tt.domain { + t.Errorf("got SAN %q, expected %q", san, tt.domain) + } + + if tt.verify != nil { + tt.verify(t, man, leaf) + } + }) + } +} + +func TestGetCertificate_failedAttempt(t *testing.T) { + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusBadRequest) + })) + defer ts.Close() + + d := createCertRetryAfter + f := testDidRemoveState + defer func() { + createCertRetryAfter = d + testDidRemoveState = f + }() + createCertRetryAfter = 0 + done := make(chan struct{}) + testDidRemoveState = func(ck certKey) { + if ck != exampleCertKey { + t.Errorf("testDidRemoveState: domain = %v; want %v", ck, exampleCertKey) + } + close(done) + } + + man := &Manager{ + Prompt: AcceptTOS, + Client: &acme.Client{ + DirectoryURL: ts.URL, + }, + } + defer man.stopRenew() + hello := clientHelloInfo(exampleDomain, algECDSA) + if _, err := man.GetCertificate(hello); err == nil { + t.Error("GetCertificate: err is nil") + } + + <-done + man.stateMu.Lock() + defer man.stateMu.Unlock() + if v, exist := man.state[exampleCertKey]; exist { + t.Errorf("state exists for %v: %+v", exampleCertKey, v) + } +} + +func TestRevokeFailedAuthz(t *testing.T) { + ca := acmetest.NewCAServer(t) + // Make the authz unfulfillable on the client side, so it will be left + // pending at the end of the verification attempt. + ca.ChallengeTypes("fake-01", "fake-02") + ca.Start() + + m := testManager(t) + m.Client = &acme.Client{DirectoryURL: ca.URL()} + + _, err := m.GetCertificate(clientHelloInfo("example.org", algECDSA)) + if err == nil { + t.Fatal("expected GetCertificate to fail") + } + + logTicker := time.NewTicker(3 * time.Second) + defer logTicker.Stop() + for { + authz, err := m.Client.GetAuthorization(context.Background(), ca.URL()+"/authz/0") + if err != nil { + t.Fatal(err) + } + if authz.Status == acme.StatusDeactivated { + return + } + + select { + case <-logTicker.C: + t.Logf("still waiting on revocations") + default: + } + time.Sleep(50 * time.Millisecond) + } +} + +func TestHTTPHandlerDefaultFallback(t *testing.T) { + tt := []struct { + method, url string + wantCode int + wantLocation string + }{ + {"GET", "http://example.org", 302, "https://example.org/"}, + {"GET", "http://example.org/foo", 302, "https://example.org/foo"}, + {"GET", "http://example.org/foo/bar/", 302, "https://example.org/foo/bar/"}, + {"GET", "http://example.org/?a=b", 302, "https://example.org/?a=b"}, + {"GET", "http://example.org/foo?a=b", 302, "https://example.org/foo?a=b"}, + {"GET", "http://example.org:80/foo?a=b", 302, "https://example.org:443/foo?a=b"}, + {"GET", "http://example.org:80/foo%20bar", 302, "https://example.org:443/foo%20bar"}, + {"GET", "http://[2602:d1:xxxx::c60a]:1234", 302, "https://[2602:d1:xxxx::c60a]:443/"}, + {"GET", "http://[2602:d1:xxxx::c60a]", 302, "https://[2602:d1:xxxx::c60a]/"}, + {"GET", "http://[2602:d1:xxxx::c60a]/foo?a=b", 302, "https://[2602:d1:xxxx::c60a]/foo?a=b"}, + {"HEAD", "http://example.org", 302, "https://example.org/"}, + {"HEAD", "http://example.org/foo", 302, "https://example.org/foo"}, + {"HEAD", "http://example.org/foo/bar/", 302, "https://example.org/foo/bar/"}, + {"HEAD", "http://example.org/?a=b", 302, "https://example.org/?a=b"}, + {"HEAD", "http://example.org/foo?a=b", 302, "https://example.org/foo?a=b"}, + {"POST", "http://example.org", 400, ""}, + {"PUT", "http://example.org", 400, ""}, + {"GET", "http://example.org/.well-known/acme-challenge/x", 404, ""}, + } + var m Manager + h := m.HTTPHandler(nil) + for i, test := range tt { + r := httptest.NewRequest(test.method, test.url, nil) + w := httptest.NewRecorder() + h.ServeHTTP(w, r) + if w.Code != test.wantCode { + t.Errorf("%d: w.Code = %d; want %d", i, w.Code, test.wantCode) + t.Errorf("%d: body: %s", i, w.Body.Bytes()) + } + if v := w.Header().Get("Location"); v != test.wantLocation { + t.Errorf("%d: Location = %q; want %q", i, v, test.wantLocation) + } + } +} + +func TestAccountKeyCache(t *testing.T) { + m := Manager{Cache: newMemCache(t)} + ctx := context.Background() + k1, err := m.accountKey(ctx) + if err != nil { + t.Fatal(err) + } + k2, err := m.accountKey(ctx) + if err != nil { + t.Fatal(err) + } + if !reflect.DeepEqual(k1, k2) { + t.Errorf("account keys don't match: k1 = %#v; k2 = %#v", k1, k2) + } +} + +func TestCache(t *testing.T) { + ecdsaKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if err != nil { + t.Fatal(err) + } + cert, err := dummyCert(ecdsaKey.Public(), exampleDomain) + if err != nil { + t.Fatal(err) + } + ecdsaCert := &tls.Certificate{ + Certificate: [][]byte{cert}, + PrivateKey: ecdsaKey, + } + + rsaKey, err := rsa.GenerateKey(rand.Reader, 1024) + if err != nil { + t.Fatal(err) + } + cert, err = dummyCert(rsaKey.Public(), exampleDomain) + if err != nil { + t.Fatal(err) + } + rsaCert := &tls.Certificate{ + Certificate: [][]byte{cert}, + PrivateKey: rsaKey, + } + + man := &Manager{Cache: newMemCache(t)} + defer man.stopRenew() + ctx := context.Background() + + if err := man.cachePut(ctx, exampleCertKey, ecdsaCert); err != nil { + t.Fatalf("man.cachePut: %v", err) + } + if err := man.cachePut(ctx, exampleCertKeyRSA, rsaCert); err != nil { + t.Fatalf("man.cachePut: %v", err) + } + + res, err := man.cacheGet(ctx, exampleCertKey) + if err != nil { + t.Fatalf("man.cacheGet: %v", err) + } + if res == nil || !bytes.Equal(res.Certificate[0], ecdsaCert.Certificate[0]) { + t.Errorf("man.cacheGet = %+v; want %+v", res, ecdsaCert) + } + + res, err = man.cacheGet(ctx, exampleCertKeyRSA) + if err != nil { + t.Fatalf("man.cacheGet: %v", err) + } + if res == nil || !bytes.Equal(res.Certificate[0], rsaCert.Certificate[0]) { + t.Errorf("man.cacheGet = %+v; want %+v", res, rsaCert) + } +} + +func TestHostWhitelist(t *testing.T) { + policy := HostWhitelist("example.com", "EXAMPLE.ORG", "*.example.net", "éÉ.com") + tt := []struct { + host string + allow bool + }{ + {"example.com", true}, + {"example.org", true}, + {"xn--9caa.com", true}, // éé.com + {"one.example.com", false}, + {"two.example.org", false}, + {"three.example.net", false}, + {"dummy", false}, + } + for i, test := range tt { + err := policy(nil, test.host) + if err != nil && test.allow { + t.Errorf("%d: policy(%q): %v; want nil", i, test.host, err) + } + if err == nil && !test.allow { + t.Errorf("%d: policy(%q): nil; want an error", i, test.host) + } + } +} + +func TestValidCert(t *testing.T) { + key1, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if err != nil { + t.Fatal(err) + } + key2, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if err != nil { + t.Fatal(err) + } + key3, err := rsa.GenerateKey(rand.Reader, 1024) + if err != nil { + t.Fatal(err) + } + cert1, err := dummyCert(key1.Public(), "example.org") + if err != nil { + t.Fatal(err) + } + cert2, err := dummyCert(key2.Public(), "example.org") + if err != nil { + t.Fatal(err) + } + cert3, err := dummyCert(key3.Public(), "example.org") + if err != nil { + t.Fatal(err) + } + now := time.Now() + early, err := dateDummyCert(key1.Public(), now.Add(time.Hour), now.Add(2*time.Hour), "example.org") + if err != nil { + t.Fatal(err) + } + expired, err := dateDummyCert(key1.Public(), now.Add(-2*time.Hour), now.Add(-time.Hour), "example.org") + if err != nil { + t.Fatal(err) + } + + tt := []struct { + ck certKey + key crypto.Signer + cert [][]byte + ok bool + }{ + {certKey{domain: "example.org"}, key1, [][]byte{cert1}, true}, + {certKey{domain: "example.org", isRSA: true}, key3, [][]byte{cert3}, true}, + {certKey{domain: "example.org"}, key1, [][]byte{cert1, cert2, cert3}, true}, + {certKey{domain: "example.org"}, key1, [][]byte{cert1, {1}}, false}, + {certKey{domain: "example.org"}, key1, [][]byte{{1}}, false}, + {certKey{domain: "example.org"}, key1, [][]byte{cert2}, false}, + {certKey{domain: "example.org"}, key2, [][]byte{cert1}, false}, + {certKey{domain: "example.org"}, key1, [][]byte{cert3}, false}, + {certKey{domain: "example.org"}, key3, [][]byte{cert1}, false}, + {certKey{domain: "example.net"}, key1, [][]byte{cert1}, false}, + {certKey{domain: "example.org"}, key1, [][]byte{early}, false}, + {certKey{domain: "example.org"}, key1, [][]byte{expired}, false}, + {certKey{domain: "example.org", isRSA: true}, key1, [][]byte{cert1}, false}, + {certKey{domain: "example.org"}, key3, [][]byte{cert3}, false}, + } + for i, test := range tt { + leaf, err := validCert(test.ck, test.cert, test.key, now) + if err != nil && test.ok { + t.Errorf("%d: err = %v", i, err) + } + if err == nil && !test.ok { + t.Errorf("%d: err is nil", i) + } + if err == nil && test.ok && leaf == nil { + t.Errorf("%d: leaf is nil", i) + } + } +} + +type cacheGetFunc func(ctx context.Context, key string) ([]byte, error) + +func (f cacheGetFunc) Get(ctx context.Context, key string) ([]byte, error) { + return f(ctx, key) +} + +func (f cacheGetFunc) Put(ctx context.Context, key string, data []byte) error { + return fmt.Errorf("unsupported Put of %q = %q", key, data) +} + +func (f cacheGetFunc) Delete(ctx context.Context, key string) error { + return fmt.Errorf("unsupported Delete of %q", key) +} + +func TestManagerGetCertificateBogusSNI(t *testing.T) { + m := Manager{ + Prompt: AcceptTOS, + Cache: cacheGetFunc(func(ctx context.Context, key string) ([]byte, error) { + return nil, fmt.Errorf("cache.Get of %s", key) + }), + } + tests := []struct { + name string + wantErr string + }{ + {"foo.com", "cache.Get of foo.com"}, + {"foo.com.", "cache.Get of foo.com"}, + {`a\b.com`, "acme/autocert: server name contains invalid character"}, + {`a/b.com`, "acme/autocert: server name contains invalid character"}, + {"", "acme/autocert: missing server name"}, + {"foo", "acme/autocert: server name component count invalid"}, + {".foo", "acme/autocert: server name component count invalid"}, + {"foo.", "acme/autocert: server name component count invalid"}, + {"fo.o", "cache.Get of fo.o"}, + } + for _, tt := range tests { + _, err := m.GetCertificate(clientHelloInfo(tt.name, algECDSA)) + got := fmt.Sprint(err) + if got != tt.wantErr { + t.Errorf("GetCertificate(SNI = %q) = %q; want %q", tt.name, got, tt.wantErr) + } + } +} + +func TestCertRequest(t *testing.T) { + key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if err != nil { + t.Fatal(err) + } + // An extension from RFC7633. Any will do. + ext := pkix.Extension{ + Id: asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 1}, + Value: []byte("dummy"), + } + b, err := certRequest(key, "example.org", []pkix.Extension{ext}) + if err != nil { + t.Fatalf("certRequest: %v", err) + } + r, err := x509.ParseCertificateRequest(b) + if err != nil { + t.Fatalf("ParseCertificateRequest: %v", err) + } + var found bool + for _, v := range r.Extensions { + if v.Id.Equal(ext.Id) { + found = true + break + } + } + if !found { + t.Errorf("want %v in Extensions: %v", ext, r.Extensions) + } +} + +func TestSupportsECDSA(t *testing.T) { + tests := []struct { + CipherSuites []uint16 + SignatureSchemes []tls.SignatureScheme + SupportedCurves []tls.CurveID + ecdsaOk bool + }{ + {[]uint16{ + tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, + }, nil, nil, false}, + {[]uint16{ + tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, + }, nil, nil, true}, + + // SignatureSchemes limits, not extends, CipherSuites + {[]uint16{ + tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, + }, []tls.SignatureScheme{ + tls.PKCS1WithSHA256, tls.ECDSAWithP256AndSHA256, + }, nil, false}, + {[]uint16{ + tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, + }, []tls.SignatureScheme{ + tls.PKCS1WithSHA256, + }, nil, false}, + {[]uint16{ + tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, + }, []tls.SignatureScheme{ + tls.PKCS1WithSHA256, tls.ECDSAWithP256AndSHA256, + }, nil, true}, + + {[]uint16{ + tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, + }, []tls.SignatureScheme{ + tls.PKCS1WithSHA256, tls.ECDSAWithP256AndSHA256, + }, []tls.CurveID{ + tls.CurveP521, + }, false}, + {[]uint16{ + tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, + }, []tls.SignatureScheme{ + tls.PKCS1WithSHA256, tls.ECDSAWithP256AndSHA256, + }, []tls.CurveID{ + tls.CurveP256, + tls.CurveP521, + }, true}, + } + for i, tt := range tests { + result := supportsECDSA(&tls.ClientHelloInfo{ + CipherSuites: tt.CipherSuites, + SignatureSchemes: tt.SignatureSchemes, + SupportedCurves: tt.SupportedCurves, + }) + if result != tt.ecdsaOk { + t.Errorf("%d: supportsECDSA = %v; want %v", i, result, tt.ecdsaOk) + } + } +} + +func TestEndToEndALPN(t *testing.T) { + const domain = "example.org" + + // ACME CA server + ca := acmetest.NewCAServer(t).Start() + + // User HTTPS server. + m := &Manager{ + Prompt: AcceptTOS, + Client: &acme.Client{DirectoryURL: ca.URL()}, + } + us := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte("OK")) + })) + us.TLS = &tls.Config{ + NextProtos: []string{"http/1.1", acme.ALPNProto}, + GetCertificate: func(hello *tls.ClientHelloInfo) (*tls.Certificate, error) { + cert, err := m.GetCertificate(hello) + if err != nil { + t.Errorf("m.GetCertificate: %v", err) + } + return cert, err + }, + } + us.StartTLS() + defer us.Close() + // In TLS-ALPN challenge verification, CA connects to the domain:443 in question. + // Because the domain won't resolve in tests, we need to tell the CA + // where to dial to instead. + ca.Resolve(domain, strings.TrimPrefix(us.URL, "https://")) + + // A client visiting user's HTTPS server. + tr := &http.Transport{ + TLSClientConfig: &tls.Config{ + RootCAs: ca.Roots(), + ServerName: domain, + }, + } + client := &http.Client{Transport: tr} + res, err := client.Get(us.URL) + if err != nil { + t.Fatal(err) + } + defer res.Body.Close() + b, err := io.ReadAll(res.Body) + if err != nil { + t.Fatal(err) + } + if v := string(b); v != "OK" { + t.Errorf("user server response: %q; want 'OK'", v) + } +} + +func TestEndToEndHTTP(t *testing.T) { + const domain = "example.org" + + // ACME CA server. + ca := acmetest.NewCAServer(t).ChallengeTypes("http-01").Start() + + // User HTTP server for the ACME challenge. + m := testManager(t) + m.Client = &acme.Client{DirectoryURL: ca.URL()} + s := httptest.NewServer(m.HTTPHandler(nil)) + defer s.Close() + + // User HTTPS server. + ss := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte("OK")) + })) + ss.TLS = &tls.Config{ + NextProtos: []string{"http/1.1", acme.ALPNProto}, + GetCertificate: func(hello *tls.ClientHelloInfo) (*tls.Certificate, error) { + cert, err := m.GetCertificate(hello) + if err != nil { + t.Errorf("m.GetCertificate: %v", err) + } + return cert, err + }, + } + ss.StartTLS() + defer ss.Close() + + // Redirect the CA requests to the HTTP server. + ca.Resolve(domain, strings.TrimPrefix(s.URL, "http://")) + + // A client visiting user's HTTPS server. + tr := &http.Transport{ + TLSClientConfig: &tls.Config{ + RootCAs: ca.Roots(), + ServerName: domain, + }, + } + client := &http.Client{Transport: tr} + res, err := client.Get(ss.URL) + if err != nil { + t.Fatal(err) + } + defer res.Body.Close() + b, err := io.ReadAll(res.Body) + if err != nil { + t.Fatal(err) + } + if v := string(b); v != "OK" { + t.Errorf("user server response: %q; want 'OK'", v) + } +} diff --git a/third_party/forked/acme/autocert/cache.go b/third_party/forked/acme/autocert/cache.go new file mode 100644 index 00000000000..758ab12cb22 --- /dev/null +++ b/third_party/forked/acme/autocert/cache.go @@ -0,0 +1,135 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package autocert + +import ( + "context" + "errors" + "os" + "path/filepath" +) + +// ErrCacheMiss is returned when a certificate is not found in cache. +var ErrCacheMiss = errors.New("acme/autocert: certificate cache miss") + +// Cache is used by Manager to store and retrieve previously obtained certificates +// and other account data as opaque blobs. +// +// Cache implementations should not rely on the key naming pattern. Keys can +// include any printable ASCII characters, except the following: \/:*?"<>| +type Cache interface { + // Get returns a certificate data for the specified key. + // If there's no such key, Get returns ErrCacheMiss. + Get(ctx context.Context, key string) ([]byte, error) + + // Put stores the data in the cache under the specified key. + // Underlying implementations may use any data storage format, + // as long as the reverse operation, Get, results in the original data. + Put(ctx context.Context, key string, data []byte) error + + // Delete removes a certificate data from the cache under the specified key. + // If there's no such key in the cache, Delete returns nil. + Delete(ctx context.Context, key string) error +} + +// DirCache implements Cache using a directory on the local filesystem. +// If the directory does not exist, it will be created with 0700 permissions. +type DirCache string + +// Get reads a certificate data from the specified file name. +func (d DirCache) Get(ctx context.Context, name string) ([]byte, error) { + name = filepath.Join(string(d), filepath.Clean("/"+name)) + var ( + data []byte + err error + done = make(chan struct{}) + ) + go func() { + data, err = os.ReadFile(name) + close(done) + }() + select { + case <-ctx.Done(): + return nil, ctx.Err() + case <-done: + } + if os.IsNotExist(err) { + return nil, ErrCacheMiss + } + return data, err +} + +// Put writes the certificate data to the specified file name. +// The file will be created with 0600 permissions. +func (d DirCache) Put(ctx context.Context, name string, data []byte) error { + if err := os.MkdirAll(string(d), 0700); err != nil { + return err + } + + done := make(chan struct{}) + var err error + go func() { + defer close(done) + var tmp string + if tmp, err = d.writeTempFile(name, data); err != nil { + return + } + defer os.Remove(tmp) + select { + case <-ctx.Done(): + // Don't overwrite the file if the context was canceled. + default: + newName := filepath.Join(string(d), filepath.Clean("/"+name)) + err = os.Rename(tmp, newName) + } + }() + select { + case <-ctx.Done(): + return ctx.Err() + case <-done: + } + return err +} + +// Delete removes the specified file name. +func (d DirCache) Delete(ctx context.Context, name string) error { + name = filepath.Join(string(d), filepath.Clean("/"+name)) + var ( + err error + done = make(chan struct{}) + ) + go func() { + err = os.Remove(name) + close(done) + }() + select { + case <-ctx.Done(): + return ctx.Err() + case <-done: + } + if err != nil && !os.IsNotExist(err) { + return err + } + return nil +} + +// writeTempFile writes b to a temporary file, closes the file and returns its path. +func (d DirCache) writeTempFile(prefix string, b []byte) (name string, reterr error) { + // TempFile uses 0600 permissions + f, err := os.CreateTemp(string(d), prefix) + if err != nil { + return "", err + } + defer func() { + if reterr != nil { + os.Remove(f.Name()) + } + }() + if _, err := f.Write(b); err != nil { + f.Close() + return "", err + } + return f.Name(), f.Close() +} diff --git a/third_party/forked/acme/autocert/cache_test.go b/third_party/forked/acme/autocert/cache_test.go new file mode 100644 index 00000000000..582e6b05804 --- /dev/null +++ b/third_party/forked/acme/autocert/cache_test.go @@ -0,0 +1,66 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package autocert + +import ( + "context" + "os" + "path/filepath" + "reflect" + "testing" +) + +// make sure DirCache satisfies Cache interface +var _ Cache = DirCache("/") + +func TestDirCache(t *testing.T) { + dir, err := os.MkdirTemp("", "autocert") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(dir) + dir = filepath.Join(dir, "certs") // a nonexistent dir + cache := DirCache(dir) + ctx := context.Background() + + // test cache miss + if _, err := cache.Get(ctx, "nonexistent"); err != ErrCacheMiss { + t.Errorf("get: %v; want ErrCacheMiss", err) + } + + // test put/get + b1 := []byte{1} + if err := cache.Put(ctx, "dummy", b1); err != nil { + t.Fatalf("put: %v", err) + } + b2, err := cache.Get(ctx, "dummy") + if err != nil { + t.Fatalf("get: %v", err) + } + if !reflect.DeepEqual(b1, b2) { + t.Errorf("b1 = %v; want %v", b1, b2) + } + name := filepath.Join(dir, "dummy") + if _, err := os.Stat(name); err != nil { + t.Error(err) + } + + // test put deletes temp file + tmp, err := filepath.Glob(name + "?*") + if err != nil { + t.Error(err) + } + if tmp != nil { + t.Errorf("temp file exists: %s", tmp) + } + + // test delete + if err := cache.Delete(ctx, "dummy"); err != nil { + t.Fatalf("delete: %v", err) + } + if _, err := cache.Get(ctx, "dummy"); err != ErrCacheMiss { + t.Errorf("get: %v; want ErrCacheMiss", err) + } +} diff --git a/third_party/forked/acme/autocert/example_test.go b/third_party/forked/acme/autocert/example_test.go new file mode 100644 index 00000000000..3b6f723a832 --- /dev/null +++ b/third_party/forked/acme/autocert/example_test.go @@ -0,0 +1,35 @@ +// Copyright 2017 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package autocert_test + +import ( + "fmt" + "log" + "net/http" + + "github.com/cert-manager/cert-manager/third_party/forked/acme/autocert" +) + +func ExampleNewListener() { + mux := http.NewServeMux() + mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + fmt.Fprintf(w, "Hello, TLS user! Your config: %+v", r.TLS) + }) + log.Fatal(http.Serve(autocert.NewListener("example.com"), mux)) +} + +func ExampleManager() { + m := &autocert.Manager{ + Cache: autocert.DirCache("secret-dir"), + Prompt: autocert.AcceptTOS, + Email: "example@example.org", + HostPolicy: autocert.HostWhitelist("example.org", "www.example.org"), + } + s := &http.Server{ + Addr: ":https", + TLSConfig: m.TLSConfig(), + } + s.ListenAndServeTLS("", "") +} diff --git a/third_party/forked/acme/autocert/internal/acmetest/ca.go b/third_party/forked/acme/autocert/internal/acmetest/ca.go new file mode 100644 index 00000000000..a1019d5ba80 --- /dev/null +++ b/third_party/forked/acme/autocert/internal/acmetest/ca.go @@ -0,0 +1,796 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package acmetest provides types for testing acme and autocert packages. +// +// TODO: Consider moving this to x/crypto/acme/internal/acmetest for acme tests as well. +package acmetest + +import ( + "context" + "crypto" + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "crypto/rsa" + "crypto/tls" + "crypto/x509" + "crypto/x509/pkix" + "encoding/asn1" + "encoding/base64" + "encoding/json" + "encoding/pem" + "fmt" + "io" + "math/big" + "net" + "net/http" + "net/http/httptest" + "path" + "strconv" + "strings" + "sync" + "testing" + "time" + + "github.com/cert-manager/cert-manager/third_party/forked/acme" +) + +// CAServer is a simple test server which implements ACME spec bits needed for testing. +type CAServer struct { + rootKey crypto.Signer + rootCert []byte // DER encoding + rootTemplate *x509.Certificate + + t *testing.T + server *httptest.Server + issuer pkix.Name + challengeTypes []string + url string + roots *x509.CertPool + eabRequired bool + + mu sync.Mutex + certCount int // number of issued certs + acctRegistered bool // set once an account has been registered + domainAddr map[string]string // domain name to addr:port resolution + domainGetCert map[string]getCertificateFunc // domain name to GetCertificate function + domainHandler map[string]http.Handler // domain name to Handle function + validAuthz map[string]*authorization // valid authz, keyed by domain name + authorizations []*authorization // all authz, index is used as ID + orders []*order // index is used as order ID + errors []error // encountered client errors +} + +type getCertificateFunc func(hello *tls.ClientHelloInfo) (*tls.Certificate, error) + +// NewCAServer creates a new ACME test server. The returned CAServer issues +// certs signed with the CA roots available in the Roots field. +func NewCAServer(t *testing.T) *CAServer { + ca := &CAServer{t: t, + challengeTypes: []string{"fake-01", "tls-alpn-01", "http-01"}, + domainAddr: make(map[string]string), + domainGetCert: make(map[string]getCertificateFunc), + domainHandler: make(map[string]http.Handler), + validAuthz: make(map[string]*authorization), + } + + ca.server = httptest.NewUnstartedServer(http.HandlerFunc(ca.handle)) + + r, err := rand.Int(rand.Reader, big.NewInt(1000000)) + if err != nil { + panic(fmt.Sprintf("rand.Int: %v", err)) + } + ca.issuer = pkix.Name{ + Organization: []string{"Test Acme Co"}, + CommonName: "Root CA " + r.String(), + } + + return ca +} + +func (ca *CAServer) generateRoot() { + key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if err != nil { + panic(fmt.Sprintf("ecdsa.GenerateKey: %v", err)) + } + tmpl := &x509.Certificate{ + SerialNumber: big.NewInt(1), + Subject: ca.issuer, + NotBefore: time.Now(), + NotAfter: time.Now().Add(365 * 24 * time.Hour), + KeyUsage: x509.KeyUsageCertSign, + BasicConstraintsValid: true, + IsCA: true, + } + der, err := x509.CreateCertificate(rand.Reader, tmpl, tmpl, &key.PublicKey, key) + if err != nil { + panic(fmt.Sprintf("x509.CreateCertificate: %v", err)) + } + cert, err := x509.ParseCertificate(der) + if err != nil { + panic(fmt.Sprintf("x509.ParseCertificate: %v", err)) + } + ca.roots = x509.NewCertPool() + ca.roots.AddCert(cert) + ca.rootKey = key + ca.rootCert = der + ca.rootTemplate = tmpl +} + +// IssuerName sets the name of the issuing CA. +func (ca *CAServer) IssuerName(name pkix.Name) *CAServer { + if ca.url != "" { + panic("IssuerName must be called before Start") + } + ca.issuer = name + return ca +} + +// ChallengeTypes sets the supported challenge types. +func (ca *CAServer) ChallengeTypes(types ...string) *CAServer { + if ca.url != "" { + panic("ChallengeTypes must be called before Start") + } + ca.challengeTypes = types + return ca +} + +// URL returns the server address, after Start has been called. +func (ca *CAServer) URL() string { + if ca.url == "" { + panic("URL called before Start") + } + return ca.url +} + +// Roots returns a pool cointaining the CA root. +func (ca *CAServer) Roots() *x509.CertPool { + if ca.url == "" { + panic("Roots called before Start") + } + return ca.roots +} + +// ExternalAccountRequired makes an EAB JWS required for account registration. +func (ca *CAServer) ExternalAccountRequired() *CAServer { + if ca.url != "" { + panic("ExternalAccountRequired must be called before Start") + } + ca.eabRequired = true + return ca +} + +// Start starts serving requests. The server address becomes available in the +// URL field. +func (ca *CAServer) Start() *CAServer { + if ca.url == "" { + ca.generateRoot() + ca.server.Start() + ca.t.Cleanup(ca.server.Close) + ca.url = ca.server.URL + } + return ca +} + +func (ca *CAServer) serverURL(format string, arg ...interface{}) string { + return ca.server.URL + fmt.Sprintf(format, arg...) +} + +func (ca *CAServer) addr(domain string) (string, bool) { + ca.mu.Lock() + defer ca.mu.Unlock() + addr, ok := ca.domainAddr[domain] + return addr, ok +} + +func (ca *CAServer) getCert(domain string) (getCertificateFunc, bool) { + ca.mu.Lock() + defer ca.mu.Unlock() + f, ok := ca.domainGetCert[domain] + return f, ok +} + +func (ca *CAServer) getHandler(domain string) (http.Handler, bool) { + ca.mu.Lock() + defer ca.mu.Unlock() + h, ok := ca.domainHandler[domain] + return h, ok +} + +func (ca *CAServer) httpErrorf(w http.ResponseWriter, code int, format string, a ...interface{}) { + s := fmt.Sprintf(format, a...) + ca.t.Errorf(format, a...) + http.Error(w, s, code) +} + +// Resolve adds a domain to address resolution for the ca to dial to +// when validating challenges for the domain authorization. +func (ca *CAServer) Resolve(domain, addr string) { + ca.mu.Lock() + defer ca.mu.Unlock() + ca.domainAddr[domain] = addr +} + +// ResolveGetCertificate redirects TLS connections for domain to f when +// validating challenges for the domain authorization. +func (ca *CAServer) ResolveGetCertificate(domain string, f getCertificateFunc) { + ca.mu.Lock() + defer ca.mu.Unlock() + ca.domainGetCert[domain] = f +} + +// ResolveHandler redirects HTTP requests for domain to f when +// validating challenges for the domain authorization. +func (ca *CAServer) ResolveHandler(domain string, h http.Handler) { + ca.mu.Lock() + defer ca.mu.Unlock() + ca.domainHandler[domain] = h +} + +type discovery struct { + NewNonce string `json:"newNonce"` + NewAccount string `json:"newAccount"` + NewOrder string `json:"newOrder"` + NewAuthz string `json:"newAuthz"` + + Meta discoveryMeta `json:"meta,omitempty"` +} + +type discoveryMeta struct { + ExternalAccountRequired bool `json:"externalAccountRequired,omitempty"` +} + +type challenge struct { + URI string `json:"uri"` + Type string `json:"type"` + Token string `json:"token"` +} + +type authorization struct { + Status string `json:"status"` + Challenges []challenge `json:"challenges"` + + domain string + id int +} + +type order struct { + Status string `json:"status"` + AuthzURLs []string `json:"authorizations"` + FinalizeURL string `json:"finalize"` // CSR submit URL + CertURL string `json:"certificate"` // already issued cert + + leaf []byte // issued cert in DER format +} + +func (ca *CAServer) handle(w http.ResponseWriter, r *http.Request) { + ca.t.Logf("%s %s", r.Method, r.URL) + w.Header().Set("Replay-Nonce", "nonce") + // TODO: Verify nonce header for all POST requests. + + switch { + default: + ca.httpErrorf(w, http.StatusBadRequest, "unrecognized r.URL.Path: %s", r.URL.Path) + + // Discovery request. + case r.URL.Path == "/": + resp := &discovery{ + NewNonce: ca.serverURL("/new-nonce"), + NewAccount: ca.serverURL("/new-account"), + NewOrder: ca.serverURL("/new-order"), + Meta: discoveryMeta{ + ExternalAccountRequired: ca.eabRequired, + }, + } + if err := json.NewEncoder(w).Encode(resp); err != nil { + panic(fmt.Sprintf("discovery response: %v", err)) + } + + // Nonce requests. + case r.URL.Path == "/new-nonce": + // Nonce values are always set. Nothing else to do. + return + + // Client key registration request. + case r.URL.Path == "/new-account": + ca.mu.Lock() + defer ca.mu.Unlock() + if ca.acctRegistered { + ca.httpErrorf(w, http.StatusServiceUnavailable, "multiple accounts are not implemented") + return + } + ca.acctRegistered = true + + var req struct { + ExternalAccountBinding json.RawMessage + } + + if err := decodePayload(&req, r.Body); err != nil { + ca.httpErrorf(w, http.StatusBadRequest, "%v", err) + return + } + + if ca.eabRequired && len(req.ExternalAccountBinding) == 0 { + ca.httpErrorf(w, http.StatusBadRequest, "registration failed: no JWS for EAB") + return + } + + // TODO: Check the user account key against a ca.accountKeys? + w.Header().Set("Location", ca.serverURL("/accounts/1")) + w.WriteHeader(http.StatusCreated) + w.Write([]byte("{}")) + + // New order request. + case r.URL.Path == "/new-order": + var req struct { + Identifiers []struct{ Value string } + } + if err := decodePayload(&req, r.Body); err != nil { + ca.httpErrorf(w, http.StatusBadRequest, "%v", err) + return + } + ca.mu.Lock() + defer ca.mu.Unlock() + o := &order{Status: acme.StatusPending} + for _, id := range req.Identifiers { + z := ca.authz(id.Value) + o.AuthzURLs = append(o.AuthzURLs, ca.serverURL("/authz/%d", z.id)) + } + orderID := len(ca.orders) + ca.orders = append(ca.orders, o) + w.Header().Set("Location", ca.serverURL("/orders/%d", orderID)) + w.WriteHeader(http.StatusCreated) + if err := json.NewEncoder(w).Encode(o); err != nil { + panic(err) + } + + // Existing order status requests. + case strings.HasPrefix(r.URL.Path, "/orders/"): + ca.mu.Lock() + defer ca.mu.Unlock() + o, err := ca.storedOrder(strings.TrimPrefix(r.URL.Path, "/orders/")) + if err != nil { + ca.httpErrorf(w, http.StatusBadRequest, "%v", err) + return + } + if err := json.NewEncoder(w).Encode(o); err != nil { + panic(err) + } + + // Accept challenge requests. + case strings.HasPrefix(r.URL.Path, "/challenge/"): + parts := strings.Split(r.URL.Path, "/") + typ, id := parts[len(parts)-2], parts[len(parts)-1] + ca.mu.Lock() + supported := false + for _, suppTyp := range ca.challengeTypes { + if suppTyp == typ { + supported = true + } + } + a, err := ca.storedAuthz(id) + ca.mu.Unlock() + if !supported { + ca.httpErrorf(w, http.StatusBadRequest, "unsupported challenge: %v", typ) + return + } + if err != nil { + ca.httpErrorf(w, http.StatusBadRequest, "challenge accept: %v", err) + return + } + ca.validateChallenge(a, typ) + w.Write([]byte("{}")) + + // Get authorization status requests. + case strings.HasPrefix(r.URL.Path, "/authz/"): + var req struct{ Status string } + decodePayload(&req, r.Body) + deactivate := req.Status == "deactivated" + ca.mu.Lock() + defer ca.mu.Unlock() + authz, err := ca.storedAuthz(strings.TrimPrefix(r.URL.Path, "/authz/")) + if err != nil { + ca.httpErrorf(w, http.StatusNotFound, "%v", err) + return + } + if deactivate { + // Note we don't invalidate authorized orders as we should. + authz.Status = "deactivated" + ca.t.Logf("authz %d is now %s", authz.id, authz.Status) + ca.updatePendingOrders() + } + if err := json.NewEncoder(w).Encode(authz); err != nil { + panic(fmt.Sprintf("encoding authz %d: %v", authz.id, err)) + } + + // Certificate issuance request. + case strings.HasPrefix(r.URL.Path, "/new-cert/"): + ca.mu.Lock() + defer ca.mu.Unlock() + orderID := strings.TrimPrefix(r.URL.Path, "/new-cert/") + o, err := ca.storedOrder(orderID) + if err != nil { + ca.httpErrorf(w, http.StatusBadRequest, "%v", err) + return + } + if o.Status != acme.StatusReady { + ca.httpErrorf(w, http.StatusForbidden, "order status: %s", o.Status) + return + } + // Validate CSR request. + var req struct { + CSR string `json:"csr"` + } + decodePayload(&req, r.Body) + b, _ := base64.RawURLEncoding.DecodeString(req.CSR) + csr, err := x509.ParseCertificateRequest(b) + if err != nil { + ca.httpErrorf(w, http.StatusBadRequest, "%v", err) + return + } + // Issue the certificate. + der, err := ca.leafCert(csr) + if err != nil { + ca.httpErrorf(w, http.StatusBadRequest, "new-cert response: ca.leafCert: %v", err) + return + } + o.leaf = der + o.CertURL = ca.serverURL("/issued-cert/%s", orderID) + o.Status = acme.StatusValid + if err := json.NewEncoder(w).Encode(o); err != nil { + panic(err) + } + + // Already issued cert download requests. + case strings.HasPrefix(r.URL.Path, "/issued-cert/"): + ca.mu.Lock() + defer ca.mu.Unlock() + o, err := ca.storedOrder(strings.TrimPrefix(r.URL.Path, "/issued-cert/")) + if err != nil { + ca.httpErrorf(w, http.StatusBadRequest, "%v", err) + return + } + if o.Status != acme.StatusValid { + ca.httpErrorf(w, http.StatusForbidden, "order status: %s", o.Status) + return + } + w.Header().Set("Content-Type", "application/pem-certificate-chain") + pem.Encode(w, &pem.Block{Type: "CERTIFICATE", Bytes: o.leaf}) + pem.Encode(w, &pem.Block{Type: "CERTIFICATE", Bytes: ca.rootCert}) + } +} + +// storedOrder retrieves a previously created order at index i. +// It requires ca.mu to be locked. +func (ca *CAServer) storedOrder(i string) (*order, error) { + idx, err := strconv.Atoi(i) + if err != nil { + return nil, fmt.Errorf("storedOrder: %v", err) + } + if idx < 0 { + return nil, fmt.Errorf("storedOrder: invalid order index %d", idx) + } + if idx > len(ca.orders)-1 { + return nil, fmt.Errorf("storedOrder: no such order %d", idx) + } + + ca.updatePendingOrders() + return ca.orders[idx], nil +} + +// storedAuthz retrieves a previously created authz at index i. +// It requires ca.mu to be locked. +func (ca *CAServer) storedAuthz(i string) (*authorization, error) { + idx, err := strconv.Atoi(i) + if err != nil { + return nil, fmt.Errorf("storedAuthz: %v", err) + } + if idx < 0 { + return nil, fmt.Errorf("storedAuthz: invalid authz index %d", idx) + } + if idx > len(ca.authorizations)-1 { + return nil, fmt.Errorf("storedAuthz: no such authz %d", idx) + } + return ca.authorizations[idx], nil +} + +// authz returns an existing valid authorization for the identifier or creates a +// new one. It requires ca.mu to be locked. +func (ca *CAServer) authz(identifier string) *authorization { + authz, ok := ca.validAuthz[identifier] + if !ok { + authzId := len(ca.authorizations) + authz = &authorization{ + id: authzId, + domain: identifier, + Status: acme.StatusPending, + } + for _, typ := range ca.challengeTypes { + authz.Challenges = append(authz.Challenges, challenge{ + Type: typ, + URI: ca.serverURL("/challenge/%s/%d", typ, authzId), + Token: challengeToken(authz.domain, typ, authzId), + }) + } + ca.authorizations = append(ca.authorizations, authz) + } + return authz +} + +// leafCert issues a new certificate. +// It requires ca.mu to be locked. +func (ca *CAServer) leafCert(csr *x509.CertificateRequest) (der []byte, err error) { + ca.certCount++ // next leaf cert serial number + leaf := &x509.Certificate{ + SerialNumber: big.NewInt(int64(ca.certCount)), + Subject: pkix.Name{Organization: []string{"Test Acme Co"}}, + NotBefore: time.Now(), + NotAfter: time.Now().Add(90 * 24 * time.Hour), + KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, + DNSNames: csr.DNSNames, + BasicConstraintsValid: true, + } + if len(csr.DNSNames) == 0 { + leaf.DNSNames = []string{csr.Subject.CommonName} + } + return x509.CreateCertificate(rand.Reader, leaf, ca.rootTemplate, csr.PublicKey, ca.rootKey) +} + +// LeafCert issues a leaf certificate. +func (ca *CAServer) LeafCert(name, keyType string, notBefore, notAfter time.Time) *tls.Certificate { + if ca.url == "" { + panic("LeafCert called before Start") + } + + ca.mu.Lock() + defer ca.mu.Unlock() + var pk crypto.Signer + switch keyType { + case "RSA": + var err error + pk, err = rsa.GenerateKey(rand.Reader, 1024) + if err != nil { + ca.t.Fatal(err) + } + case "ECDSA": + var err error + pk, err = ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if err != nil { + ca.t.Fatal(err) + } + default: + panic("LeafCert: unknown key type") + } + ca.certCount++ // next leaf cert serial number + leaf := &x509.Certificate{ + SerialNumber: big.NewInt(int64(ca.certCount)), + Subject: pkix.Name{Organization: []string{"Test Acme Co"}}, + NotBefore: notBefore, + NotAfter: notAfter, + KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, + DNSNames: []string{name}, + BasicConstraintsValid: true, + } + der, err := x509.CreateCertificate(rand.Reader, leaf, ca.rootTemplate, pk.Public(), ca.rootKey) + if err != nil { + ca.t.Fatal(err) + } + return &tls.Certificate{ + Certificate: [][]byte{der}, + PrivateKey: pk, + } +} + +func (ca *CAServer) validateChallenge(authz *authorization, typ string) { + var err error + switch typ { + case "tls-alpn-01": + err = ca.verifyALPNChallenge(authz) + case "http-01": + err = ca.verifyHTTPChallenge(authz) + default: + panic(fmt.Sprintf("validation of %q is not implemented", typ)) + } + ca.mu.Lock() + defer ca.mu.Unlock() + if err != nil { + authz.Status = "invalid" + } else { + authz.Status = "valid" + ca.validAuthz[authz.domain] = authz + } + ca.t.Logf("validated %q for %q, err: %v", typ, authz.domain, err) + ca.t.Logf("authz %d is now %s", authz.id, authz.Status) + + ca.updatePendingOrders() +} + +func (ca *CAServer) updatePendingOrders() { + // Update all pending orders. + // An order becomes "ready" if all authorizations are "valid". + // An order becomes "invalid" if any authorization is "invalid". + // Status changes: https://tools.ietf.org/html/rfc8555#section-7.1.6 + for i, o := range ca.orders { + if o.Status != acme.StatusPending { + continue + } + + countValid, countInvalid := ca.validateAuthzURLs(o.AuthzURLs, i) + if countInvalid > 0 { + o.Status = acme.StatusInvalid + ca.t.Logf("order %d is now invalid", i) + continue + } + if countValid == len(o.AuthzURLs) { + o.Status = acme.StatusReady + o.FinalizeURL = ca.serverURL("/new-cert/%d", i) + ca.t.Logf("order %d is now ready", i) + } + } +} + +func (ca *CAServer) validateAuthzURLs(urls []string, orderNum int) (countValid, countInvalid int) { + for _, zurl := range urls { + z, err := ca.storedAuthz(path.Base(zurl)) + if err != nil { + ca.t.Logf("no authz %q for order %d", zurl, orderNum) + continue + } + if z.Status == acme.StatusInvalid { + countInvalid++ + } + if z.Status == acme.StatusValid { + countValid++ + } + } + return countValid, countInvalid +} + +func (ca *CAServer) verifyALPNChallenge(a *authorization) error { + const acmeALPNProto = "acme-tls/1" + + addr, haveAddr := ca.addr(a.domain) + getCert, haveGetCert := ca.getCert(a.domain) + if !haveAddr && !haveGetCert { + return fmt.Errorf("no resolution information for %q", a.domain) + } + if haveAddr && haveGetCert { + return fmt.Errorf("overlapping resolution information for %q", a.domain) + } + + var crt *x509.Certificate + switch { + case haveAddr: + conn, err := tls.Dial("tcp", addr, &tls.Config{ + ServerName: a.domain, + InsecureSkipVerify: true, + NextProtos: []string{acmeALPNProto}, + MinVersion: tls.VersionTLS12, + }) + if err != nil { + return err + } + if v := conn.ConnectionState().NegotiatedProtocol; v != acmeALPNProto { + return fmt.Errorf("CAServer: verifyALPNChallenge: negotiated proto is %q; want %q", v, acmeALPNProto) + } + if n := len(conn.ConnectionState().PeerCertificates); n != 1 { + return fmt.Errorf("len(PeerCertificates) = %d; want 1", n) + } + crt = conn.ConnectionState().PeerCertificates[0] + case haveGetCert: + hello := &tls.ClientHelloInfo{ + ServerName: a.domain, + // TODO: support selecting ECDSA. + CipherSuites: []uint16{tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305}, + SupportedProtos: []string{acme.ALPNProto}, + SupportedVersions: []uint16{tls.VersionTLS12}, + } + c, err := getCert(hello) + if err != nil { + return err + } + crt, err = x509.ParseCertificate(c.Certificate[0]) + if err != nil { + return err + } + } + + if err := crt.VerifyHostname(a.domain); err != nil { + return fmt.Errorf("verifyALPNChallenge: VerifyHostname: %v", err) + } + // See RFC 8737, Section 6.1. + oid := asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 1, 31} + for _, x := range crt.Extensions { + if x.Id.Equal(oid) { + // TODO: check the token. + return nil + } + } + return fmt.Errorf("verifyTokenCert: no id-pe-acmeIdentifier extension found") +} + +func (ca *CAServer) verifyHTTPChallenge(a *authorization) error { + addr, haveAddr := ca.addr(a.domain) + handler, haveHandler := ca.getHandler(a.domain) + if !haveAddr && !haveHandler { + return fmt.Errorf("no resolution information for %q", a.domain) + } + if haveAddr && haveHandler { + return fmt.Errorf("overlapping resolution information for %q", a.domain) + } + + token := challengeToken(a.domain, "http-01", a.id) + path := "/.well-known/acme-challenge/" + token + + var body string + switch { + case haveAddr: + t := &http.Transport{ + DialContext: func(ctx context.Context, network, _ string) (net.Conn, error) { + return (&net.Dialer{}).DialContext(ctx, network, addr) + }, + } + req, err := http.NewRequest("GET", "http://"+a.domain+path, nil) + if err != nil { + return err + } + res, err := t.RoundTrip(req) + if err != nil { + return err + } + if res.StatusCode != http.StatusOK { + return fmt.Errorf("http token: w.Code = %d; want %d", res.StatusCode, http.StatusOK) + } + b, err := io.ReadAll(res.Body) + if err != nil { + return err + } + body = string(b) + case haveHandler: + r := httptest.NewRequest("GET", path, nil) + r.Host = a.domain + w := httptest.NewRecorder() + handler.ServeHTTP(w, r) + if w.Code != http.StatusOK { + return fmt.Errorf("http token: w.Code = %d; want %d", w.Code, http.StatusOK) + } + body = w.Body.String() + } + + if !strings.HasPrefix(body, token) { + return fmt.Errorf("http token value = %q; want 'token-http-01.' prefix", body) + } + return nil +} + +func decodePayload(v interface{}, r io.Reader) error { + var req struct{ Payload string } + if err := json.NewDecoder(r).Decode(&req); err != nil { + return err + } + payload, err := base64.RawURLEncoding.DecodeString(req.Payload) + if err != nil { + return err + } + return json.Unmarshal(payload, v) +} + +func challengeToken(domain, challType string, authzID int) string { + return fmt.Sprintf("token-%s-%s-%d", domain, challType, authzID) +} + +func unique(a []string) []string { + seen := make(map[string]bool) + var res []string + for _, s := range a { + if s != "" && !seen[s] { + seen[s] = true + res = append(res, s) + } + } + return res +} diff --git a/third_party/forked/acme/autocert/listener.go b/third_party/forked/acme/autocert/listener.go new file mode 100644 index 00000000000..460133e0cca --- /dev/null +++ b/third_party/forked/acme/autocert/listener.go @@ -0,0 +1,135 @@ +// Copyright 2017 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package autocert + +import ( + "crypto/tls" + "log" + "net" + "os" + "path/filepath" + "time" +) + +// NewListener returns a net.Listener that listens on the standard TLS +// port (443) on all interfaces and returns *tls.Conn connections with +// LetsEncrypt certificates for the provided domain or domains. +// +// It enables one-line HTTPS servers: +// +// log.Fatal(http.Serve(autocert.NewListener("example.com"), handler)) +// +// NewListener is a convenience function for a common configuration. +// More complex or custom configurations can use the autocert.Manager +// type instead. +// +// Use of this function implies acceptance of the LetsEncrypt Terms of +// Service. If domains is not empty, the provided domains are passed +// to HostWhitelist. If domains is empty, the listener will do +// LetsEncrypt challenges for any requested domain, which is not +// recommended. +// +// Certificates are cached in a "golang-autocert" directory under an +// operating system-specific cache or temp directory. This may not +// be suitable for servers spanning multiple machines. +// +// The returned listener uses a *tls.Config that enables HTTP/2, and +// should only be used with servers that support HTTP/2. +// +// The returned Listener also enables TCP keep-alives on the accepted +// connections. The returned *tls.Conn are returned before their TLS +// handshake has completed. +func NewListener(domains ...string) net.Listener { + m := &Manager{ + Prompt: AcceptTOS, + } + if len(domains) > 0 { + m.HostPolicy = HostWhitelist(domains...) + } + dir := cacheDir() + if err := os.MkdirAll(dir, 0700); err != nil { + log.Printf("warning: autocert.NewListener not using a cache: %v", err) + } else { + m.Cache = DirCache(dir) + } + return m.Listener() +} + +// Listener listens on the standard TLS port (443) on all interfaces +// and returns a net.Listener returning *tls.Conn connections. +// +// The returned listener uses a *tls.Config that enables HTTP/2, and +// should only be used with servers that support HTTP/2. +// +// The returned Listener also enables TCP keep-alives on the accepted +// connections. The returned *tls.Conn are returned before their TLS +// handshake has completed. +// +// Unlike NewListener, it is the caller's responsibility to initialize +// the Manager m's Prompt, Cache, HostPolicy, and other desired options. +func (m *Manager) Listener() net.Listener { + ln := &listener{ + conf: m.TLSConfig(), + } + ln.tcpListener, ln.tcpListenErr = net.Listen("tcp", ":443") + return ln +} + +type listener struct { + conf *tls.Config + + tcpListener net.Listener + tcpListenErr error +} + +func (ln *listener) Accept() (net.Conn, error) { + if ln.tcpListenErr != nil { + return nil, ln.tcpListenErr + } + conn, err := ln.tcpListener.Accept() + if err != nil { + return nil, err + } + tcpConn := conn.(*net.TCPConn) + + // Because Listener is a convenience function, help out with + // this too. This is not possible for the caller to set once + // we return a *tcp.Conn wrapping an inaccessible net.Conn. + // If callers don't want this, they can do things the manual + // way and tweak as needed. But this is what net/http does + // itself, so copy that. If net/http changes, we can change + // here too. + tcpConn.SetKeepAlive(true) + tcpConn.SetKeepAlivePeriod(3 * time.Minute) + + return tls.Server(tcpConn, ln.conf), nil +} + +func (ln *listener) Addr() net.Addr { + if ln.tcpListener != nil { + return ln.tcpListener.Addr() + } + // net.Listen failed. Return something non-nil in case callers + // call Addr before Accept: + return &net.TCPAddr{IP: net.IP{0, 0, 0, 0}, Port: 443} +} + +func (ln *listener) Close() error { + if ln.tcpListenErr != nil { + return ln.tcpListenErr + } + return ln.tcpListener.Close() +} + +func cacheDir() string { + const base = "golang-autocert" + cache, err := os.UserCacheDir() + if err != nil { + // Fall back to the root directory. + cache = "/.cache" + } + + return filepath.Join(cache, base) +} diff --git a/third_party/forked/acme/autocert/renewal.go b/third_party/forked/acme/autocert/renewal.go new file mode 100644 index 00000000000..0df7da78a6f --- /dev/null +++ b/third_party/forked/acme/autocert/renewal.go @@ -0,0 +1,156 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package autocert + +import ( + "context" + "crypto" + "sync" + "time" +) + +// renewJitter is the maximum deviation from Manager.RenewBefore. +const renewJitter = time.Hour + +// domainRenewal tracks the state used by the periodic timers +// renewing a single domain's cert. +type domainRenewal struct { + m *Manager + ck certKey + key crypto.Signer + + timerMu sync.Mutex + timer *time.Timer + timerClose chan struct{} // if non-nil, renew closes this channel (and nils out the timer fields) instead of running +} + +// start starts a cert renewal timer at the time +// defined by the certificate expiration time exp. +// +// If the timer is already started, calling start is a noop. +func (dr *domainRenewal) start(exp time.Time) { + dr.timerMu.Lock() + defer dr.timerMu.Unlock() + if dr.timer != nil { + return + } + dr.timer = time.AfterFunc(dr.next(exp), dr.renew) +} + +// stop stops the cert renewal timer and waits for any in-flight calls to renew +// to complete. If the timer is already stopped, calling stop is a noop. +func (dr *domainRenewal) stop() { + dr.timerMu.Lock() + defer dr.timerMu.Unlock() + for { + if dr.timer == nil { + return + } + if dr.timer.Stop() { + dr.timer = nil + return + } else { + // dr.timer fired, and we acquired dr.timerMu before the renew callback did. + // (We know this because otherwise the renew callback would have reset dr.timer!) + timerClose := make(chan struct{}) + dr.timerClose = timerClose + dr.timerMu.Unlock() + <-timerClose + dr.timerMu.Lock() + } + } +} + +// renew is called periodically by a timer. +// The first renew call is kicked off by dr.start. +func (dr *domainRenewal) renew() { + dr.timerMu.Lock() + defer dr.timerMu.Unlock() + if dr.timerClose != nil { + close(dr.timerClose) + dr.timer, dr.timerClose = nil, nil + return + } + + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Minute) + defer cancel() + // TODO: rotate dr.key at some point? + next, err := dr.do(ctx) + if err != nil { + next = renewJitter / 2 + next += time.Duration(pseudoRand.int63n(int64(next))) + } + testDidRenewLoop(next, err) + dr.timer = time.AfterFunc(next, dr.renew) +} + +// updateState locks and replaces the relevant Manager.state item with the given +// state. It additionally updates dr.key with the given state's key. +func (dr *domainRenewal) updateState(state *certState) { + dr.m.stateMu.Lock() + defer dr.m.stateMu.Unlock() + dr.key = state.key + dr.m.state[dr.ck] = state +} + +// do is similar to Manager.createCert but it doesn't lock a Manager.state item. +// Instead, it requests a new certificate independently and, upon success, +// replaces dr.m.state item with a new one and updates cache for the given domain. +// +// It may lock and update the Manager.state if the expiration date of the currently +// cached cert is far enough in the future. +// +// The returned value is a time interval after which the renewal should occur again. +func (dr *domainRenewal) do(ctx context.Context) (time.Duration, error) { + // a race is likely unavoidable in a distributed environment + // but we try nonetheless + if tlscert, err := dr.m.cacheGet(ctx, dr.ck); err == nil { + next := dr.next(tlscert.Leaf.NotAfter) + if next > dr.m.renewBefore()+renewJitter { + signer, ok := tlscert.PrivateKey.(crypto.Signer) + if ok { + state := &certState{ + key: signer, + cert: tlscert.Certificate, + leaf: tlscert.Leaf, + } + dr.updateState(state) + return next, nil + } + } + } + + der, leaf, err := dr.m.authorizedCert(ctx, dr.key, dr.ck) + if err != nil { + return 0, err + } + state := &certState{ + key: dr.key, + cert: der, + leaf: leaf, + } + tlscert, err := state.tlscert() + if err != nil { + return 0, err + } + if err := dr.m.cachePut(ctx, dr.ck, tlscert); err != nil { + return 0, err + } + dr.updateState(state) + return dr.next(leaf.NotAfter), nil +} + +func (dr *domainRenewal) next(expiry time.Time) time.Duration { + d := expiry.Sub(dr.m.now()) - dr.m.renewBefore() + // add a bit of randomness to renew deadline + n := pseudoRand.int63n(int64(renewJitter)) + d -= time.Duration(n) + if d < 0 { + return 0 + } + return d +} + +var testDidRenewLoop = func(next time.Duration, err error) {} diff --git a/third_party/forked/acme/autocert/renewal_test.go b/third_party/forked/acme/autocert/renewal_test.go new file mode 100644 index 00000000000..94f29450b32 --- /dev/null +++ b/third_party/forked/acme/autocert/renewal_test.go @@ -0,0 +1,269 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package autocert + +import ( + "context" + "crypto" + "crypto/ecdsa" + "testing" + "time" + + "github.com/cert-manager/cert-manager/third_party/forked/acme" + "github.com/cert-manager/cert-manager/third_party/forked/acme/autocert/internal/acmetest" +) + +func TestRenewalNext(t *testing.T) { + now := time.Now() + man := &Manager{ + RenewBefore: 7 * 24 * time.Hour, + nowFunc: func() time.Time { return now }, + } + defer man.stopRenew() + tt := []struct { + expiry time.Time + min, max time.Duration + }{ + {now.Add(90 * 24 * time.Hour), 83*24*time.Hour - renewJitter, 83 * 24 * time.Hour}, + {now.Add(time.Hour), 0, 1}, + {now, 0, 1}, + {now.Add(-time.Hour), 0, 1}, + } + + dr := &domainRenewal{m: man} + for i, test := range tt { + next := dr.next(test.expiry) + if next < test.min || test.max < next { + t.Errorf("%d: next = %v; want between %v and %v", i, next, test.min, test.max) + } + } +} + +func TestRenewFromCache(t *testing.T) { + man := testManager(t) + man.RenewBefore = 24 * time.Hour + + ca := acmetest.NewCAServer(t).Start() + ca.ResolveGetCertificate(exampleDomain, man.GetCertificate) + + man.Client = &acme.Client{ + DirectoryURL: ca.URL(), + } + + // cache an almost expired cert + now := time.Now() + c := ca.LeafCert(exampleDomain, "ECDSA", now.Add(-2*time.Hour), now.Add(time.Minute)) + if err := man.cachePut(context.Background(), exampleCertKey, c); err != nil { + t.Fatal(err) + } + + // verify the renewal happened + defer func() { + // Stop the timers that read and execute testDidRenewLoop before restoring it. + // Otherwise the timer callback may race with the deferred write. + man.stopRenew() + testDidRenewLoop = func(next time.Duration, err error) {} + }() + renewed := make(chan bool, 1) + testDidRenewLoop = func(next time.Duration, err error) { + defer func() { + select { + case renewed <- true: + default: + // The renewal timer uses a random backoff. If the first renewal fails for + // some reason, we could end up with multiple calls here before the test + // stops the timer. + } + }() + + if err != nil { + t.Errorf("testDidRenewLoop: %v", err) + } + // Next should be about 90 days: + // CaServer creates 90days expiry + account for man.RenewBefore. + // Previous expiration was within 1 min. + future := 88 * 24 * time.Hour + if next < future { + t.Errorf("testDidRenewLoop: next = %v; want >= %v", next, future) + } + + // ensure the new cert is cached + after := time.Now().Add(future) + tlscert, err := man.cacheGet(context.Background(), exampleCertKey) + if err != nil { + t.Errorf("man.cacheGet: %v", err) + return + } + if !tlscert.Leaf.NotAfter.After(after) { + t.Errorf("cache leaf.NotAfter = %v; want > %v", tlscert.Leaf.NotAfter, after) + } + + // verify the old cert is also replaced in memory + man.stateMu.Lock() + defer man.stateMu.Unlock() + s := man.state[exampleCertKey] + if s == nil { + t.Errorf("m.state[%q] is nil", exampleCertKey) + return + } + tlscert, err = s.tlscert() + if err != nil { + t.Errorf("s.tlscert: %v", err) + return + } + if !tlscert.Leaf.NotAfter.After(after) { + t.Errorf("state leaf.NotAfter = %v; want > %v", tlscert.Leaf.NotAfter, after) + } + } + + // trigger renew + hello := clientHelloInfo(exampleDomain, algECDSA) + if _, err := man.GetCertificate(hello); err != nil { + t.Fatal(err) + } + <-renewed +} + +func TestRenewFromCacheAlreadyRenewed(t *testing.T) { + ca := acmetest.NewCAServer(t).Start() + man := testManager(t) + man.RenewBefore = 24 * time.Hour + man.Client = &acme.Client{ + DirectoryURL: "invalid", + } + + // cache a recently renewed cert with a different private key + now := time.Now() + newCert := ca.LeafCert(exampleDomain, "ECDSA", now.Add(-2*time.Hour), now.Add(time.Hour*24*90)) + if err := man.cachePut(context.Background(), exampleCertKey, newCert); err != nil { + t.Fatal(err) + } + newLeaf, err := validCert(exampleCertKey, newCert.Certificate, newCert.PrivateKey.(crypto.Signer), now) + if err != nil { + t.Fatal(err) + } + + // set internal state to an almost expired cert + oldCert := ca.LeafCert(exampleDomain, "ECDSA", now.Add(-2*time.Hour), now.Add(time.Minute)) + if err != nil { + t.Fatal(err) + } + oldLeaf, err := validCert(exampleCertKey, oldCert.Certificate, oldCert.PrivateKey.(crypto.Signer), now) + if err != nil { + t.Fatal(err) + } + man.stateMu.Lock() + if man.state == nil { + man.state = make(map[certKey]*certState) + } + s := &certState{ + key: oldCert.PrivateKey.(crypto.Signer), + cert: oldCert.Certificate, + leaf: oldLeaf, + } + man.state[exampleCertKey] = s + man.stateMu.Unlock() + + // verify the renewal accepted the newer cached cert + defer func() { + // Stop the timers that read and execute testDidRenewLoop before restoring it. + // Otherwise the timer callback may race with the deferred write. + man.stopRenew() + testDidRenewLoop = func(next time.Duration, err error) {} + }() + renewed := make(chan bool, 1) + testDidRenewLoop = func(next time.Duration, err error) { + defer func() { + select { + case renewed <- true: + default: + // The renewal timer uses a random backoff. If the first renewal fails for + // some reason, we could end up with multiple calls here before the test + // stops the timer. + } + }() + + if err != nil { + t.Errorf("testDidRenewLoop: %v", err) + } + // Next should be about 90 days + // Previous expiration was within 1 min. + future := 88 * 24 * time.Hour + if next < future { + t.Errorf("testDidRenewLoop: next = %v; want >= %v", next, future) + } + + // ensure the cached cert was not modified + tlscert, err := man.cacheGet(context.Background(), exampleCertKey) + if err != nil { + t.Errorf("man.cacheGet: %v", err) + return + } + if !tlscert.Leaf.NotAfter.Equal(newLeaf.NotAfter) { + t.Errorf("cache leaf.NotAfter = %v; want == %v", tlscert.Leaf.NotAfter, newLeaf.NotAfter) + } + + // verify the old cert is also replaced in memory + man.stateMu.Lock() + defer man.stateMu.Unlock() + s := man.state[exampleCertKey] + if s == nil { + t.Errorf("m.state[%q] is nil", exampleCertKey) + return + } + stateKey := s.key.Public().(*ecdsa.PublicKey) + if !stateKey.Equal(newLeaf.PublicKey) { + t.Error("state key was not updated from cache") + return + } + tlscert, err = s.tlscert() + if err != nil { + t.Errorf("s.tlscert: %v", err) + return + } + if !tlscert.Leaf.NotAfter.Equal(newLeaf.NotAfter) { + t.Errorf("state leaf.NotAfter = %v; want == %v", tlscert.Leaf.NotAfter, newLeaf.NotAfter) + } + } + + // assert the expiring cert is returned from state + hello := clientHelloInfo(exampleDomain, algECDSA) + tlscert, err := man.GetCertificate(hello) + if err != nil { + t.Fatal(err) + } + if !oldLeaf.NotAfter.Equal(tlscert.Leaf.NotAfter) { + t.Errorf("state leaf.NotAfter = %v; want == %v", tlscert.Leaf.NotAfter, oldLeaf.NotAfter) + } + + // trigger renew + man.startRenew(exampleCertKey, s.key, s.leaf.NotAfter) + <-renewed + func() { + man.renewalMu.Lock() + defer man.renewalMu.Unlock() + + // verify the private key is replaced in the renewal state + r := man.renewal[exampleCertKey] + if r == nil { + t.Errorf("m.renewal[%q] is nil", exampleCertKey) + return + } + renewalKey := r.key.Public().(*ecdsa.PublicKey) + if !renewalKey.Equal(newLeaf.PublicKey) { + t.Error("renewal private key was not updated from cache") + } + }() + + // assert the new cert is returned from state after renew + hello = clientHelloInfo(exampleDomain, algECDSA) + tlscert, err = man.GetCertificate(hello) + if err != nil { + t.Fatal(err) + } + if !newLeaf.NotAfter.Equal(tlscert.Leaf.NotAfter) { + t.Errorf("state leaf.NotAfter = %v; want == %v", tlscert.Leaf.NotAfter, newLeaf.NotAfter) + } +} diff --git a/third_party/forked/acme/http.go b/third_party/forked/acme/http.go new file mode 100644 index 00000000000..467602aa4d5 --- /dev/null +++ b/third_party/forked/acme/http.go @@ -0,0 +1,341 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package acme + +import ( + "bytes" + "context" + "crypto" + "crypto/rand" + "encoding/json" + "errors" + "fmt" + "io" + "math/big" + "net/http" + "runtime/debug" + "strconv" + "strings" + "time" +) + +// retryTimer encapsulates common logic for retrying unsuccessful requests. +// It is not safe for concurrent use. +type retryTimer struct { + // backoffFn provides backoff delay sequence for retries. + // See Client.RetryBackoff doc comment. + backoffFn func(n int, r *http.Request, res *http.Response) time.Duration + // n is the current retry attempt. + n int +} + +func (t *retryTimer) inc() { + t.n++ +} + +// backoff pauses the current goroutine as described in Client.RetryBackoff. +func (t *retryTimer) backoff(ctx context.Context, r *http.Request, res *http.Response) error { + d := t.backoffFn(t.n, r, res) + if d <= 0 { + return fmt.Errorf("acme: no more retries for %s; tried %d time(s)", r.URL, t.n) + } + wakeup := time.NewTimer(d) + defer wakeup.Stop() + select { + case <-ctx.Done(): + return ctx.Err() + case <-wakeup.C: + return nil + } +} + +func (c *Client) retryTimer() *retryTimer { + f := c.RetryBackoff + if f == nil { + f = defaultBackoff + } + return &retryTimer{backoffFn: f} +} + +// defaultBackoff provides default Client.RetryBackoff implementation +// using a truncated exponential backoff algorithm, +// as described in Client.RetryBackoff. +// +// The n argument is always bounded between 1 and 30. +// The returned value is always greater than 0. +func defaultBackoff(n int, r *http.Request, res *http.Response) time.Duration { + const maxVal = 10 * time.Second + var jitter time.Duration + if x, err := rand.Int(rand.Reader, big.NewInt(1000)); err == nil { + // Set the minimum to 1ms to avoid a case where + // an invalid Retry-After value is parsed into 0 below, + // resulting in the 0 returned value which would unintentionally + // stop the retries. + jitter = (1 + time.Duration(x.Int64())) * time.Millisecond + } + if v, ok := res.Header["Retry-After"]; ok { + return retryAfter(v[0]) + jitter + } + + if n < 1 { + n = 1 + } + if n > 30 { + n = 30 + } + d := time.Duration(1<= 500 || code == http.StatusTooManyRequests +} + +// responseError creates an error of Error type from resp. +func responseError(resp *http.Response) error { + // don't care if ReadAll returns an error: + // json.Unmarshal will fail in that case anyway + b, _ := io.ReadAll(resp.Body) + e := &wireError{Status: resp.StatusCode} + if err := json.Unmarshal(b, e); err != nil { + // this is not a regular error response: + // populate detail with anything we received, + // e.Status will already contain HTTP response code value + e.Detail = string(b) + if e.Detail == "" { + e.Detail = resp.Status + } + } + return e.error(resp.Header) +} diff --git a/third_party/forked/acme/http_test.go b/third_party/forked/acme/http_test.go new file mode 100644 index 00000000000..12b374819fb --- /dev/null +++ b/third_party/forked/acme/http_test.go @@ -0,0 +1,255 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package acme + +import ( + "context" + "fmt" + "io" + "net/http" + "net/http/httptest" + "reflect" + "strings" + "testing" + "time" +) + +func TestDefaultBackoff(t *testing.T) { + tt := []struct { + nretry int + retryAfter string // Retry-After header + out time.Duration // expected min; max = min + jitter + }{ + {-1, "", time.Second}, // verify the lower bound is 1 + {0, "", time.Second}, // verify the lower bound is 1 + {100, "", 10 * time.Second}, // verify the ceiling + {1, "3600", time.Hour}, // verify the header value is used + {1, "", 1 * time.Second}, + {2, "", 2 * time.Second}, + {3, "", 4 * time.Second}, + {4, "", 8 * time.Second}, + } + for i, test := range tt { + r := httptest.NewRequest("GET", "/", nil) + resp := &http.Response{Header: http.Header{}} + if test.retryAfter != "" { + resp.Header.Set("Retry-After", test.retryAfter) + } + d := defaultBackoff(test.nretry, r, resp) + max := test.out + time.Second // + max jitter + if d < test.out || max < d { + t.Errorf("%d: defaultBackoff(%v) = %v; want between %v and %v", i, test.nretry, d, test.out, max) + } + } +} + +func TestErrorResponse(t *testing.T) { + s := `{ + "status": 400, + "type": "urn:acme:error:xxx", + "detail": "text" + }` + res := &http.Response{ + StatusCode: 400, + Status: "400 Bad Request", + Body: io.NopCloser(strings.NewReader(s)), + Header: http.Header{"X-Foo": {"bar"}}, + } + err := responseError(res) + v, ok := err.(*Error) + if !ok { + t.Fatalf("err = %+v (%T); want *Error type", err, err) + } + if v.StatusCode != 400 { + t.Errorf("v.StatusCode = %v; want 400", v.StatusCode) + } + if v.ProblemType != "urn:acme:error:xxx" { + t.Errorf("v.ProblemType = %q; want urn:acme:error:xxx", v.ProblemType) + } + if v.Detail != "text" { + t.Errorf("v.Detail = %q; want text", v.Detail) + } + if !reflect.DeepEqual(v.Header, res.Header) { + t.Errorf("v.Header = %+v; want %+v", v.Header, res.Header) + } +} + +func TestPostWithRetries(t *testing.T) { + var count int + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + count++ + w.Header().Set("Replay-Nonce", fmt.Sprintf("nonce%d", count)) + if r.Method == "HEAD" { + // We expect the client to do 2 head requests to fetch + // nonces, one to start and another after getting badNonce + return + } + + head, err := decodeJWSHead(r.Body) + switch { + case err != nil: + t.Errorf("decodeJWSHead: %v", err) + case head.Nonce == "": + t.Error("head.Nonce is empty") + case head.Nonce == "nonce1": + // Return a badNonce error to force the call to retry. + w.Header().Set("Retry-After", "0") + w.WriteHeader(http.StatusBadRequest) + w.Write([]byte(`{"type":"urn:ietf:params:acme:error:badNonce"}`)) + return + } + // Make client.Authorize happy; we're not testing its result. + w.WriteHeader(http.StatusCreated) + w.Write([]byte(`{"status":"valid"}`)) + })) + defer ts.Close() + + client := &Client{ + Key: testKey, + DirectoryURL: ts.URL, + dir: &Directory{AuthzURL: ts.URL}, + } + // This call will fail with badNonce, causing a retry + if _, err := client.Authorize(context.Background(), "example.com"); err != nil { + t.Errorf("client.Authorize 1: %v", err) + } + if count != 3 { + t.Errorf("total requests count: %d; want 3", count) + } +} + +func TestRetryErrorType(t *testing.T) { + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Replay-Nonce", "nonce") + w.WriteHeader(http.StatusTooManyRequests) + w.Write([]byte(`{"type":"rateLimited"}`)) + })) + defer ts.Close() + + client := &Client{ + Key: testKey, + RetryBackoff: func(n int, r *http.Request, res *http.Response) time.Duration { + // Do no retries. + return 0 + }, + dir: &Directory{AuthzURL: ts.URL}, + } + + t.Run("post", func(t *testing.T) { + testRetryErrorType(t, func() error { + _, err := client.Authorize(context.Background(), "example.com") + return err + }) + }) + t.Run("get", func(t *testing.T) { + testRetryErrorType(t, func() error { + _, err := client.GetAuthorization(context.Background(), ts.URL) + return err + }) + }) +} + +func testRetryErrorType(t *testing.T, callClient func() error) { + t.Helper() + err := callClient() + if err == nil { + t.Fatal("client.Authorize returned nil error") + } + acmeErr, ok := err.(*Error) + if !ok { + t.Fatalf("err is %v (%T); want *Error", err, err) + } + if acmeErr.StatusCode != http.StatusTooManyRequests { + t.Errorf("acmeErr.StatusCode = %d; want %d", acmeErr.StatusCode, http.StatusTooManyRequests) + } + if acmeErr.ProblemType != "rateLimited" { + t.Errorf("acmeErr.ProblemType = %q; want 'rateLimited'", acmeErr.ProblemType) + } +} + +func TestRetryBackoffArgs(t *testing.T) { + const resCode = http.StatusInternalServerError + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Replay-Nonce", "test-nonce") + w.WriteHeader(resCode) + })) + defer ts.Close() + + // Canceled in backoff. + ctx, cancel := context.WithCancel(context.Background()) + + var nretry int + backoff := func(n int, r *http.Request, res *http.Response) time.Duration { + nretry++ + if n != nretry { + t.Errorf("n = %d; want %d", n, nretry) + } + if nretry == 3 { + cancel() + } + + if r == nil { + t.Error("r is nil") + } + if res.StatusCode != resCode { + t.Errorf("res.StatusCode = %d; want %d", res.StatusCode, resCode) + } + return time.Millisecond + } + + client := &Client{ + Key: testKey, + RetryBackoff: backoff, + dir: &Directory{AuthzURL: ts.URL}, + } + if _, err := client.Authorize(ctx, "example.com"); err == nil { + t.Error("err is nil") + } + if nretry != 3 { + t.Errorf("nretry = %d; want 3", nretry) + } +} + +func TestUserAgent(t *testing.T) { + for _, custom := range []string{"", "CUSTOM_UA"} { + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + t.Log(r.UserAgent()) + if s := "github.com/cert-manager/cert-manager/third_party/forked/acme"; !strings.Contains(r.UserAgent(), s) { + t.Errorf("expected User-Agent to contain %q, got %q", s, r.UserAgent()) + } + if !strings.Contains(r.UserAgent(), custom) { + t.Errorf("expected User-Agent to contain %q, got %q", custom, r.UserAgent()) + } + + w.WriteHeader(http.StatusOK) + w.Write([]byte(`{"newOrder": "sure"}`)) + })) + defer ts.Close() + + client := &Client{ + Key: testKey, + DirectoryURL: ts.URL, + UserAgent: custom, + } + if _, err := client.Discover(context.Background()); err != nil { + t.Errorf("client.Discover: %v", err) + } + } +} + +func TestAccountKidLoop(t *testing.T) { + // if Client.postNoRetry is called with a nil key argument + // then Client.Key must be set, otherwise we fall into an + // infinite loop (which also causes a deadlock). + client := &Client{dir: &Directory{OrderURL: ":)"}} + _, _, err := client.postNoRetry(context.Background(), nil, "", nil) + if err == nil { + t.Fatal("Client.postNoRetry didn't fail with a nil key") + } + expected := "acme: Client.Key must be populated to make POST requests" + if err.Error() != expected { + t.Fatalf("Unexpected error returned: wanted %q, got %q", expected, err.Error()) + } +} diff --git a/third_party/forked/acme/internal/acmeprobe/prober.go b/third_party/forked/acme/internal/acmeprobe/prober.go new file mode 100644 index 00000000000..d9cd124e9c3 --- /dev/null +++ b/third_party/forked/acme/internal/acmeprobe/prober.go @@ -0,0 +1,433 @@ +// Copyright 2019 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// The acmeprober program runs against an actual ACME CA implementation. +// It spins up an HTTP server to fulfill authorization challenges +// or execute a DNS script to provision a response to dns-01 challenge. +// +// For http-01 and tls-alpn-01 challenge types this requires the ACME CA +// to be able to reach the HTTP server. +// +// A usage example: +// +// go run prober.go \ +// -d https://acme-staging-v02.api.letsencrypt.org/directory \ +// -f order \ +// -t http-01 \ +// -a :8080 \ +// -domain some.example.org +// +// The above assumes a TCP tunnel from some.example.org:80 to 0.0.0.0:8080 +// in order for the test to be able to fulfill http-01 challenge. +// To test tls-alpn-01 challenge, 443 port would need to be tunneled +// to 0.0.0.0:8080. +// When running with dns-01 challenge type, use -s argument instead of -a. +package main + +import ( + "context" + "crypto" + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "crypto/tls" + "crypto/x509" + "encoding/pem" + "errors" + "flag" + "fmt" + "log" + "net" + "net/http" + "os" + "os/exec" + "strings" + "time" + + "github.com/cert-manager/cert-manager/third_party/forked/acme" +) + +var ( + // ACME CA directory URL. + // Let's Encrypt v2 prod: https://acme-v02.api.letsencrypt.org/directory + // Let's Encrypt v2 staging: https://acme-staging-v02.api.letsencrypt.org/directory + // See the following for more CAs implementing ACME protocol: + // https://en.wikipedia.org/wiki/Automated_Certificate_Management_Environment#CAs_&_PKIs_that_offer_ACME_certificates + directory = flag.String("d", "", "ACME directory URL.") + reginfo = flag.String("r", "", "ACME account registration info.") + flow = flag.String("f", "", `Flow to run: "order" or "preauthz" (RFC8555).`) + chaltyp = flag.String("t", "", "Challenge type: tls-alpn-01, http-01 or dns-01.") + addr = flag.String("a", "", "Local server address for tls-alpn-01 and http-01.") + dnsscript = flag.String("s", "", "Script to run for provisioning dns-01 challenges.") + domain = flag.String("domain", "", "Space separate domain identifiers.") + ipaddr = flag.String("ip", "", "Space separate IP address identifiers.") +) + +func main() { + flag.Usage = func() { + fmt.Fprintln(flag.CommandLine.Output(), ` +The prober program runs against an actual ACME CA implementation. +It spins up an HTTP server to fulfill authorization challenges +or execute a DNS script to provision a response to dns-01 challenge. + +For http-01 and tls-alpn-01 challenge types this requires the ACME CA +to be able to reach the HTTP server. + +A usage example: + + go run prober.go \ + -d https://acme-staging-v02.api.letsencrypt.org/directory \ + -f order \ + -t http-01 \ + -a :8080 \ + -domain some.example.org + +The above assumes a TCP tunnel from some.example.org:80 to 0.0.0.0:8080 +in order for the test to be able to fulfill http-01 challenge. +To test tls-alpn-01 challenge, 443 port would need to be tunneled +to 0.0.0.0:8080. +When running with dns-01 challenge type, use -s argument instead of -a. + `) + flag.PrintDefaults() + } + flag.Parse() + + identifiers := acme.DomainIDs(strings.Fields(*domain)...) + identifiers = append(identifiers, acme.IPIDs(strings.Fields(*ipaddr)...)...) + if len(identifiers) == 0 { + log.Fatal("at least one domain or IP addr identifier is required") + } + + // Duration of the whole run. + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Minute) + defer cancel() + + // Create and register a new account. + akey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if err != nil { + log.Fatal(err) + } + cl := &acme.Client{Key: akey, DirectoryURL: *directory} + a := &acme.Account{Contact: strings.Fields(*reginfo)} + if _, err := cl.Register(ctx, a, acme.AcceptTOS); err != nil { + log.Fatalf("Register: %v", err) + } + + // Run the desired flow test. + p := &prober{ + client: cl, + chalType: *chaltyp, + localAddr: *addr, + dnsScript: *dnsscript, + } + switch *flow { + case "order": + p.runOrder(ctx, identifiers) + case "preauthz": + p.runPreauthz(ctx, identifiers) + default: + log.Fatalf("unknown flow: %q", *flow) + } + if len(p.errors) > 0 { + os.Exit(1) + } +} + +type prober struct { + client *acme.Client + chalType string + localAddr string + dnsScript string + + errors []error +} + +func (p *prober) errorf(format string, a ...interface{}) { + err := fmt.Errorf(format, a...) + log.Print(err) + p.errors = append(p.errors, err) +} + +func (p *prober) runOrder(ctx context.Context, identifiers []acme.AuthzID) { + // Create a new order and pick a challenge. + // Note that Let's Encrypt will reply with 400 error:malformed + // "NotBefore and NotAfter are not supported" when providing a NotAfter + // value like WithOrderNotAfter(time.Now().Add(24 * time.Hour)). + o, err := p.client.AuthorizeOrder(ctx, identifiers) + if err != nil { + log.Fatalf("AuthorizeOrder: %v", err) + } + + var zurls []string + for _, u := range o.AuthzURLs { + z, err := p.client.GetAuthorization(ctx, u) + if err != nil { + log.Fatalf("GetAuthorization(%q): %v", u, err) + } + log.Printf("%+v", z) + if z.Status != acme.StatusPending { + log.Printf("authz status is %q; skipping", z.Status) + continue + } + if err := p.fulfill(ctx, z); err != nil { + log.Fatalf("fulfill(%s): %v", z.URI, err) + } + zurls = append(zurls, z.URI) + log.Printf("authorized for %+v", z.Identifier) + } + + log.Print("all challenges are done") + if _, err := p.client.WaitOrder(ctx, o.URI); err != nil { + log.Fatalf("WaitOrder(%q): %v", o.URI, err) + } + csr, certkey := newCSR(identifiers) + der, curl, err := p.client.CreateOrderCert(ctx, o.FinalizeURL, csr, true) + if err != nil { + log.Fatalf("CreateOrderCert: %v", err) + } + log.Printf("cert URL: %s", curl) + if err := checkCert(der, identifiers); err != nil { + p.errorf("invalid cert: %v", err) + } + + // Deactivate all authorizations we satisfied earlier. + for _, v := range zurls { + if err := p.client.RevokeAuthorization(ctx, v); err != nil { + p.errorf("RevokAuthorization(%q): %v", v, err) + continue + } + } + // Deactivate the account. We don't need it for any further calls. + if err := p.client.DeactivateReg(ctx); err != nil { + p.errorf("DeactivateReg: %v", err) + } + // Try revoking the issued cert using its private key. + if err := p.client.RevokeCert(ctx, certkey, der[0], acme.CRLReasonCessationOfOperation); err != nil { + p.errorf("RevokeCert: %v", err) + } +} + +func (p *prober) runPreauthz(ctx context.Context, identifiers []acme.AuthzID) { + dir, err := p.client.Discover(ctx) + if err != nil { + log.Fatalf("Discover: %v", err) + } + if dir.AuthzURL == "" { + log.Fatal("CA does not support pre-authorization") + } + + var zurls []string + for _, id := range identifiers { + z, err := authorize(ctx, p.client, id) + if err != nil { + log.Fatalf("AuthorizeID(%+v): %v", z, err) + } + if z.Status == acme.StatusValid { + log.Printf("authz %s is valid; skipping", z.URI) + continue + } + if err := p.fulfill(ctx, z); err != nil { + log.Fatalf("fulfill(%s): %v", z.URI, err) + } + zurls = append(zurls, z.URI) + log.Printf("authorized for %+v", id) + } + + // We should be all set now. + // Expect all authorizations to be satisfied. + log.Print("all challenges are done") + o, err := p.client.AuthorizeOrder(ctx, identifiers) + if err != nil { + log.Fatalf("AuthorizeOrder: %v", err) + } + waitCtx, cancel := context.WithTimeout(ctx, time.Minute) + defer cancel() + if _, err := p.client.WaitOrder(waitCtx, o.URI); err != nil { + log.Fatalf("WaitOrder(%q): %v", o.URI, err) + } + csr, certkey := newCSR(identifiers) + der, curl, err := p.client.CreateOrderCert(ctx, o.FinalizeURL, csr, true) + if err != nil { + log.Fatalf("CreateOrderCert: %v", err) + } + log.Printf("cert URL: %s", curl) + if err := checkCert(der, identifiers); err != nil { + p.errorf("invalid cert: %v", err) + } + + // Deactivate all authorizations we satisfied earlier. + for _, v := range zurls { + if err := p.client.RevokeAuthorization(ctx, v); err != nil { + p.errorf("RevokeAuthorization(%q): %v", v, err) + continue + } + } + // Deactivate the account. We don't need it for any further calls. + if err := p.client.DeactivateReg(ctx); err != nil { + p.errorf("DeactivateReg: %v", err) + } + // Try revoking the issued cert using its private key. + if err := p.client.RevokeCert(ctx, certkey, der[0], acme.CRLReasonCessationOfOperation); err != nil { + p.errorf("RevokeCert: %v", err) + } +} + +func (p *prober) fulfill(ctx context.Context, z *acme.Authorization) error { + var chal *acme.Challenge + for i, c := range z.Challenges { + log.Printf("challenge %d: %+v", i, c) + if c.Type == p.chalType { + log.Printf("picked %s for authz %s", c.URI, z.URI) + chal = c + } + } + if chal == nil { + return fmt.Errorf("challenge type %q wasn't offered for authz %s", p.chalType, z.URI) + } + + switch chal.Type { + case "tls-alpn-01": + return p.runTLSALPN01(ctx, z, chal) + case "http-01": + return p.runHTTP01(ctx, z, chal) + case "dns-01": + return p.runDNS01(ctx, z, chal) + default: + return fmt.Errorf("unknown challenge type %q", chal.Type) + } +} + +func (p *prober) runTLSALPN01(ctx context.Context, z *acme.Authorization, chal *acme.Challenge) error { + tokenCert, err := p.client.TLSALPN01ChallengeCert(chal.Token, z.Identifier.Value) + if err != nil { + return fmt.Errorf("TLSALPN01ChallengeCert: %v", err) + } + s := &http.Server{ + Addr: p.localAddr, + TLSConfig: &tls.Config{ + NextProtos: []string{acme.ALPNProto}, + GetCertificate: func(hello *tls.ClientHelloInfo) (*tls.Certificate, error) { + log.Printf("hello: %+v", hello) + return &tokenCert, nil + }, + }, + Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + log.Printf("%s %s", r.Method, r.URL) + w.WriteHeader(http.StatusNotFound) + }), + } + go s.ListenAndServeTLS("", "") + defer s.Close() + + if _, err := p.client.Accept(ctx, chal); err != nil { + return fmt.Errorf("Accept(%q): %v", chal.URI, err) + } + _, zerr := p.client.WaitAuthorization(ctx, z.URI) + return zerr +} + +func (p *prober) runHTTP01(ctx context.Context, z *acme.Authorization, chal *acme.Challenge) error { + body, err := p.client.HTTP01ChallengeResponse(chal.Token) + if err != nil { + return fmt.Errorf("HTTP01ChallengeResponse: %v", err) + } + s := &http.Server{ + Addr: p.localAddr, + Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + log.Printf("%s %s", r.Method, r.URL) + if r.URL.Path != p.client.HTTP01ChallengePath(chal.Token) { + w.WriteHeader(http.StatusNotFound) + return + } + w.Write([]byte(body)) + }), + } + go s.ListenAndServe() + defer s.Close() + + if _, err := p.client.Accept(ctx, chal); err != nil { + return fmt.Errorf("Accept(%q): %v", chal.URI, err) + } + _, zerr := p.client.WaitAuthorization(ctx, z.URI) + return zerr +} + +func (p *prober) runDNS01(ctx context.Context, z *acme.Authorization, chal *acme.Challenge) error { + token, err := p.client.DNS01ChallengeRecord(chal.Token) + if err != nil { + return fmt.Errorf("DNS01ChallengeRecord: %v", err) + } + + name := fmt.Sprintf("_acme-challenge.%s", z.Identifier.Value) + cmd := exec.CommandContext(ctx, p.dnsScript, name, token) + cmd.Stdin = os.Stdin + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + if err := cmd.Run(); err != nil { + return fmt.Errorf("%s: %v", p.dnsScript, err) + } + + if _, err := p.client.Accept(ctx, chal); err != nil { + return fmt.Errorf("Accept(%q): %v", chal.URI, err) + } + _, zerr := p.client.WaitAuthorization(ctx, z.URI) + return zerr +} + +func authorize(ctx context.Context, client *acme.Client, id acme.AuthzID) (*acme.Authorization, error) { + if id.Type == "ip" { + return client.AuthorizeIP(ctx, id.Value) + } + return client.Authorize(ctx, id.Value) +} + +func checkCert(derChain [][]byte, id []acme.AuthzID) error { + if len(derChain) == 0 { + return errors.New("cert chain is zero bytes") + } + for i, b := range derChain { + crt, err := x509.ParseCertificate(b) + if err != nil { + return fmt.Errorf("%d: ParseCertificate: %v", i, err) + } + log.Printf("%d: serial: 0x%s", i, crt.SerialNumber) + log.Printf("%d: subject: %s", i, crt.Subject) + log.Printf("%d: issuer: %s", i, crt.Issuer) + log.Printf("%d: expires in %.1f day(s)", i, time.Until(crt.NotAfter).Hours()/24) + if i > 0 { // not a leaf cert + continue + } + p := &pem.Block{Type: "CERTIFICATE", Bytes: b} + log.Printf("%d: leaf:\n%s", i, pem.EncodeToMemory(p)) + for _, v := range id { + if err := crt.VerifyHostname(v.Value); err != nil { + return err + } + } + } + return nil +} + +func newCSR(identifiers []acme.AuthzID) ([]byte, crypto.Signer) { + var csr x509.CertificateRequest + for _, id := range identifiers { + switch id.Type { + case "dns": + csr.DNSNames = append(csr.DNSNames, id.Value) + case "ip": + csr.IPAddresses = append(csr.IPAddresses, net.ParseIP(id.Value)) + default: + panic(fmt.Sprintf("newCSR: unknown identifier type %q", id.Type)) + } + } + k, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if err != nil { + panic(fmt.Sprintf("newCSR: ecdsa.GenerateKey for a cert: %v", err)) + } + b, err := x509.CreateCertificateRequest(rand.Reader, &csr, k) + if err != nil { + panic(fmt.Sprintf("newCSR: x509.CreateCertificateRequest: %v", err)) + } + return b, k +} diff --git a/third_party/forked/acme/jws.go b/third_party/forked/acme/jws.go new file mode 100644 index 00000000000..6850275665f --- /dev/null +++ b/third_party/forked/acme/jws.go @@ -0,0 +1,257 @@ +// Copyright 2015 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package acme + +import ( + "crypto" + "crypto/ecdsa" + "crypto/hmac" + "crypto/rand" + "crypto/rsa" + "crypto/sha256" + _ "crypto/sha512" // need for EC keys + "encoding/asn1" + "encoding/base64" + "encoding/json" + "errors" + "fmt" + "math/big" +) + +// KeyID is the account key identity provided by a CA during registration. +type KeyID string + +// noKeyID indicates that jwsEncodeJSON should compute and use JWK instead of a KID. +// See jwsEncodeJSON for details. +const noKeyID = KeyID("") + +// noPayload indicates jwsEncodeJSON will encode zero-length octet string +// in a JWS request. This is called POST-as-GET in RFC 8555 and is used to make +// authenticated GET requests via POSTing with an empty payload. +// See https://tools.ietf.org/html/rfc8555#section-6.3 for more details. +const noPayload = "" + +// noNonce indicates that the nonce should be omitted from the protected header. +// See jwsEncodeJSON for details. +const noNonce = "" + +// jsonWebSignature can be easily serialized into a JWS following +// https://tools.ietf.org/html/rfc7515#section-3.2. +type jsonWebSignature struct { + Protected string `json:"protected"` + Payload string `json:"payload"` + Sig string `json:"signature"` +} + +// jwsEncodeJSON signs claimset using provided key and a nonce. +// The result is serialized in JSON format containing either kid or jwk +// fields based on the provided KeyID value. +// +// The claimset is marshalled using json.Marshal unless it is a string. +// In which case it is inserted directly into the message. +// +// If kid is non-empty, its quoted value is inserted in the protected header +// as "kid" field value. Otherwise, JWK is computed using jwkEncode and inserted +// as "jwk" field value. The "jwk" and "kid" fields are mutually exclusive. +// +// If nonce is non-empty, its quoted value is inserted in the protected header. +// +// See https://tools.ietf.org/html/rfc7515#section-7. +func jwsEncodeJSON(claimset interface{}, key crypto.Signer, kid KeyID, nonce, url string) ([]byte, error) { + if key == nil { + return nil, errors.New("nil key") + } + alg, sha := jwsHasher(key.Public()) + if alg == "" || !sha.Available() { + return nil, ErrUnsupportedKey + } + headers := struct { + Alg string `json:"alg"` + KID string `json:"kid,omitempty"` + JWK json.RawMessage `json:"jwk,omitempty"` + Nonce string `json:"nonce,omitempty"` + URL string `json:"url"` + }{ + Alg: alg, + Nonce: nonce, + URL: url, + } + switch kid { + case noKeyID: + jwk, err := jwkEncode(key.Public()) + if err != nil { + return nil, err + } + headers.JWK = json.RawMessage(jwk) + default: + headers.KID = string(kid) + } + phJSON, err := json.Marshal(headers) + if err != nil { + return nil, err + } + phead := base64.RawURLEncoding.EncodeToString(phJSON) + var payload string + if val, ok := claimset.(string); ok { + payload = val + } else { + cs, err := json.Marshal(claimset) + if err != nil { + return nil, err + } + payload = base64.RawURLEncoding.EncodeToString(cs) + } + hash := sha.New() + hash.Write([]byte(phead + "." + payload)) + sig, err := jwsSign(key, sha, hash.Sum(nil)) + if err != nil { + return nil, err + } + enc := jsonWebSignature{ + Protected: phead, + Payload: payload, + Sig: base64.RawURLEncoding.EncodeToString(sig), + } + return json.Marshal(&enc) +} + +// jwsWithMAC creates and signs a JWS using the given key and the HS256 +// algorithm. kid and url are included in the protected header. rawPayload +// should not be base64-URL-encoded. +func jwsWithMAC(key []byte, kid, url string, rawPayload []byte) (*jsonWebSignature, error) { + if len(key) == 0 { + return nil, errors.New("acme: cannot sign JWS with an empty MAC key") + } + header := struct { + Algorithm string `json:"alg"` + KID string `json:"kid"` + URL string `json:"url,omitempty"` + }{ + // Only HMAC-SHA256 is supported. + Algorithm: "HS256", + KID: kid, + URL: url, + } + rawProtected, err := json.Marshal(header) + if err != nil { + return nil, err + } + protected := base64.RawURLEncoding.EncodeToString(rawProtected) + payload := base64.RawURLEncoding.EncodeToString(rawPayload) + + h := hmac.New(sha256.New, key) + if _, err := h.Write([]byte(protected + "." + payload)); err != nil { + return nil, err + } + mac := h.Sum(nil) + + return &jsonWebSignature{ + Protected: protected, + Payload: payload, + Sig: base64.RawURLEncoding.EncodeToString(mac), + }, nil +} + +// jwkEncode encodes public part of an RSA or ECDSA key into a JWK. +// The result is also suitable for creating a JWK thumbprint. +// https://tools.ietf.org/html/rfc7517 +func jwkEncode(pub crypto.PublicKey) (string, error) { + switch pub := pub.(type) { + case *rsa.PublicKey: + // https://tools.ietf.org/html/rfc7518#section-6.3.1 + n := pub.N + e := big.NewInt(int64(pub.E)) + // Field order is important. + // See https://tools.ietf.org/html/rfc7638#section-3.3 for details. + return fmt.Sprintf(`{"e":"%s","kty":"RSA","n":"%s"}`, + base64.RawURLEncoding.EncodeToString(e.Bytes()), + base64.RawURLEncoding.EncodeToString(n.Bytes()), + ), nil + case *ecdsa.PublicKey: + // https://tools.ietf.org/html/rfc7518#section-6.2.1 + p := pub.Curve.Params() + n := p.BitSize / 8 + if p.BitSize%8 != 0 { + n++ + } + x := pub.X.Bytes() + if n > len(x) { + x = append(make([]byte, n-len(x)), x...) + } + y := pub.Y.Bytes() + if n > len(y) { + y = append(make([]byte, n-len(y)), y...) + } + // Field order is important. + // See https://tools.ietf.org/html/rfc7638#section-3.3 for details. + return fmt.Sprintf(`{"crv":"%s","kty":"EC","x":"%s","y":"%s"}`, + p.Name, + base64.RawURLEncoding.EncodeToString(x), + base64.RawURLEncoding.EncodeToString(y), + ), nil + } + return "", ErrUnsupportedKey +} + +// jwsSign signs the digest using the given key. +// The hash is unused for ECDSA keys. +func jwsSign(key crypto.Signer, hash crypto.Hash, digest []byte) ([]byte, error) { + switch pub := key.Public().(type) { + case *rsa.PublicKey: + return key.Sign(rand.Reader, digest, hash) + case *ecdsa.PublicKey: + sigASN1, err := key.Sign(rand.Reader, digest, hash) + if err != nil { + return nil, err + } + + var rs struct{ R, S *big.Int } + if _, err := asn1.Unmarshal(sigASN1, &rs); err != nil { + return nil, err + } + + rb, sb := rs.R.Bytes(), rs.S.Bytes() + size := pub.Params().BitSize / 8 + if size%8 > 0 { + size++ + } + sig := make([]byte, size*2) + copy(sig[size-len(rb):], rb) + copy(sig[size*2-len(sb):], sb) + return sig, nil + } + return nil, ErrUnsupportedKey +} + +// jwsHasher indicates suitable JWS algorithm name and a hash function +// to use for signing a digest with the provided key. +// It returns ("", 0) if the key is not supported. +func jwsHasher(pub crypto.PublicKey) (string, crypto.Hash) { + switch pub := pub.(type) { + case *rsa.PublicKey: + return "RS256", crypto.SHA256 + case *ecdsa.PublicKey: + switch pub.Params().Name { + case "P-256": + return "ES256", crypto.SHA256 + case "P-384": + return "ES384", crypto.SHA384 + case "P-521": + return "ES512", crypto.SHA512 + } + } + return "", 0 +} + +// JWKThumbprint creates a JWK thumbprint out of pub +// as specified in https://tools.ietf.org/html/rfc7638. +func JWKThumbprint(pub crypto.PublicKey) (string, error) { + jwk, err := jwkEncode(pub) + if err != nil { + return "", err + } + b := sha256.Sum256([]byte(jwk)) + return base64.RawURLEncoding.EncodeToString(b[:]), nil +} diff --git a/third_party/forked/acme/jws_test.go b/third_party/forked/acme/jws_test.go new file mode 100644 index 00000000000..d5f00ba2d32 --- /dev/null +++ b/third_party/forked/acme/jws_test.go @@ -0,0 +1,550 @@ +// Copyright 2015 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package acme + +import ( + "crypto" + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rsa" + "crypto/sha256" + "crypto/x509" + "encoding/base64" + "encoding/json" + "encoding/pem" + "fmt" + "io" + "math/big" + "testing" +) + +// The following shell command alias is used in the comments +// throughout this file: +// alias b64raw="base64 -w0 | tr -d '=' | tr '/+' '_-'" + +const ( + // Modulus in raw base64: + // 4xgZ3eRPkwoRvy7qeRUbmMDe0V-xH9eWLdu0iheeLlrmD2mqWXfP9IeSKApbn34 + // g8TuAS9g5zhq8ELQ3kmjr-KV86GAMgI6VAcGlq3QrzpTCf_30Ab7-zawrfRaFON + // a1HwEzPY1KHnGVkxJc85gNkwYI9SY2RHXtvln3zs5wITNrdosqEXeaIkVYBEhbh + // Nu54pp3kxo6TuWLi9e6pXeWetEwmlBwtWZlPoib2j3TxLBksKZfoyFyek380mHg + // JAumQ_I2fjj98_97mk3ihOY4AgVdCDj1z_GCoZkG5Rq7nbCGyosyKWyDX00Zs-n + // NqVhoLeIvXC4nnWdJMZ6rogxyQQ + testKeyPEM = ` +-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEA4xgZ3eRPkwoRvy7qeRUbmMDe0V+xH9eWLdu0iheeLlrmD2mq +WXfP9IeSKApbn34g8TuAS9g5zhq8ELQ3kmjr+KV86GAMgI6VAcGlq3QrzpTCf/30 +Ab7+zawrfRaFONa1HwEzPY1KHnGVkxJc85gNkwYI9SY2RHXtvln3zs5wITNrdosq +EXeaIkVYBEhbhNu54pp3kxo6TuWLi9e6pXeWetEwmlBwtWZlPoib2j3TxLBksKZf +oyFyek380mHgJAumQ/I2fjj98/97mk3ihOY4AgVdCDj1z/GCoZkG5Rq7nbCGyosy +KWyDX00Zs+nNqVhoLeIvXC4nnWdJMZ6rogxyQQIDAQABAoIBACIEZTOI1Kao9nmV +9IeIsuaR1Y61b9neOF/MLmIVIZu+AAJFCMB4Iw11FV6sFodwpEyeZhx2WkpWVN+H +r19eGiLX3zsL0DOdqBJoSIHDWCCMxgnYJ6nvS0nRxX3qVrBp8R2g12Ub+gNPbmFm +ecf/eeERIVxfifd9VsyRu34eDEvcmKFuLYbElFcPh62xE3x12UZvV/sN7gXbawpP +G+w255vbE5MoaKdnnO83cTFlcHvhn24M/78qP7Te5OAeelr1R89kYxQLpuGe4fbS +zc6E3ym5Td6urDetGGrSY1Eu10/8sMusX+KNWkm+RsBRbkyKq72ks/qKpOxOa+c6 +9gm+Y8ECgYEA/iNUyg1ubRdH11p82l8KHtFC1DPE0V1gSZsX29TpM5jS4qv46K+s +8Ym1zmrORM8x+cynfPx1VQZQ34EYeCMIX212ryJ+zDATl4NE0I4muMvSiH9vx6Xc +7FmhNnaYzPsBL5Tm9nmtQuP09YEn8poiOJFiDs/4olnD5ogA5O4THGkCgYEA5MIL +qWYBUuqbEWLRtMruUtpASclrBqNNsJEsMGbeqBJmoMxdHeSZckbLOrqm7GlMyNRJ +Ne/5uWRGSzaMYuGmwsPpERzqEvYFnSrpjW5YtXZ+JtxFXNVfm9Z1gLLgvGpOUCIU +RbpoDckDe1vgUuk3y5+DjZihs+rqIJ45XzXTzBkCgYBWuf3segruJZy5rEKhTv+o +JqeUvRn0jNYYKFpLBeyTVBrbie6GkbUGNIWbrK05pC+c3K9nosvzuRUOQQL1tJbd +4gA3oiD9U4bMFNr+BRTHyZ7OQBcIXdz3t1qhuHVKtnngIAN1p25uPlbRFUNpshnt +jgeVoHlsBhApcs5DUc+pyQKBgDzeHPg/+g4z+nrPznjKnktRY1W+0El93kgi+J0Q +YiJacxBKEGTJ1MKBb8X6sDurcRDm22wMpGfd9I5Cv2v4GsUsF7HD/cx5xdih+G73 +c4clNj/k0Ff5Nm1izPUno4C+0IOl7br39IPmfpSuR6wH/h6iHQDqIeybjxyKvT1G +N0rRAoGBAKGD+4ZI/E1MoJ5CXB8cDDMHagbE3cq/DtmYzE2v1DFpQYu5I4PCm5c7 +EQeIP6dZtv8IMgtGIb91QX9pXvP0aznzQKwYIA8nZgoENCPfiMTPiEDT9e/0lObO +9XWsXpbSTsRPj0sv1rB+UzBJ0PgjK4q2zOF0sNo7b1+6nlM3BWPx +-----END RSA PRIVATE KEY----- +` + + // This thumbprint is for the testKey defined above. + testKeyThumbprint = "6nicxzh6WETQlrvdchkz-U3e3DOQZ4heJKU63rfqMqQ" + + // openssl ecparam -name secp256k1 -genkey -noout + testKeyECPEM = ` +-----BEGIN EC PRIVATE KEY----- +MHcCAQEEIK07hGLr0RwyUdYJ8wbIiBS55CjnkMD23DWr+ccnypWLoAoGCCqGSM49 +AwEHoUQDQgAE5lhEug5xK4xBDZ2nAbaxLtaLiv85bxJ7ePd1dkO23HThqIrvawF5 +QAaS/RNouybCiRhRjI3EaxLkQwgrCw0gqQ== +-----END EC PRIVATE KEY----- +` + // openssl ecparam -name secp384r1 -genkey -noout + testKeyEC384PEM = ` +-----BEGIN EC PRIVATE KEY----- +MIGkAgEBBDAQ4lNtXRORWr1bgKR1CGysr9AJ9SyEk4jiVnlUWWUChmSNL+i9SLSD +Oe/naPqXJ6CgBwYFK4EEACKhZANiAAQzKtj+Ms0vHoTX5dzv3/L5YMXOWuI5UKRj +JigpahYCqXD2BA1j0E/2xt5vlPf+gm0PL+UHSQsCokGnIGuaHCsJAp3ry0gHQEke +WYXapUUFdvaK1R2/2hn5O+eiQM8YzCg= +-----END EC PRIVATE KEY----- +` + // openssl ecparam -name secp521r1 -genkey -noout + testKeyEC512PEM = ` +-----BEGIN EC PRIVATE KEY----- +MIHcAgEBBEIBSNZKFcWzXzB/aJClAb305ibalKgtDA7+70eEkdPt28/3LZMM935Z +KqYHh/COcxuu3Kt8azRAUz3gyr4zZKhlKUSgBwYFK4EEACOhgYkDgYYABAHUNKbx +7JwC7H6pa2sV0tERWhHhB3JmW+OP6SUgMWryvIKajlx73eS24dy4QPGrWO9/ABsD +FqcRSkNVTXnIv6+0mAF25knqIBIg5Q8M9BnOu9GGAchcwt3O7RDHmqewnJJDrbjd +GGnm6rb+NnWR9DIopM0nKNkToWoF/hzopxu4Ae/GsQ== +-----END EC PRIVATE KEY----- +` + // 1. openssl ec -in key.pem -noout -text + // 2. remove first byte, 04 (the header); the rest is X and Y + // 3. convert each with: echo | xxd -r -p | b64raw + testKeyECPubX = "5lhEug5xK4xBDZ2nAbaxLtaLiv85bxJ7ePd1dkO23HQ" + testKeyECPubY = "4aiK72sBeUAGkv0TaLsmwokYUYyNxGsS5EMIKwsNIKk" + testKeyEC384PubX = "MyrY_jLNLx6E1-Xc79_y-WDFzlriOVCkYyYoKWoWAqlw9gQNY9BP9sbeb5T3_oJt" + testKeyEC384PubY = "Dy_lB0kLAqJBpyBrmhwrCQKd68tIB0BJHlmF2qVFBXb2itUdv9oZ-TvnokDPGMwo" + testKeyEC512PubX = "AdQ0pvHsnALsfqlraxXS0RFaEeEHcmZb44_pJSAxavK8gpqOXHvd5Lbh3LhA8atY738AGwMWpxFKQ1VNeci_r7SY" + testKeyEC512PubY = "AXbmSeogEiDlDwz0Gc670YYByFzC3c7tEMeap7CckkOtuN0Yaebqtv42dZH0MiikzSco2ROhagX-HOinG7gB78ax" + + // echo -n '{"crv":"P-256","kty":"EC","x":"","y":""}' | \ + // openssl dgst -binary -sha256 | b64raw + testKeyECThumbprint = "zedj-Bd1Zshp8KLePv2MB-lJ_Hagp7wAwdkA0NUTniU" +) + +var ( + testKey *rsa.PrivateKey + testKeyEC *ecdsa.PrivateKey + testKeyEC384 *ecdsa.PrivateKey + testKeyEC512 *ecdsa.PrivateKey +) + +func init() { + testKey = parseRSA(testKeyPEM, "testKeyPEM") + testKeyEC = parseEC(testKeyECPEM, "testKeyECPEM") + testKeyEC384 = parseEC(testKeyEC384PEM, "testKeyEC384PEM") + testKeyEC512 = parseEC(testKeyEC512PEM, "testKeyEC512PEM") +} + +func decodePEM(s, name string) []byte { + d, _ := pem.Decode([]byte(s)) + if d == nil { + panic("no block found in " + name) + } + return d.Bytes +} + +func parseRSA(s, name string) *rsa.PrivateKey { + b := decodePEM(s, name) + k, err := x509.ParsePKCS1PrivateKey(b) + if err != nil { + panic(fmt.Sprintf("%s: %v", name, err)) + } + return k +} + +func parseEC(s, name string) *ecdsa.PrivateKey { + b := decodePEM(s, name) + k, err := x509.ParseECPrivateKey(b) + if err != nil { + panic(fmt.Sprintf("%s: %v", name, err)) + } + return k +} + +func TestJWSEncodeJSON(t *testing.T) { + claims := struct{ Msg string }{"Hello JWS"} + // JWS signed with testKey and "nonce" as the nonce value + // JSON-serialized JWS fields are split for easier testing + const ( + // {"alg":"RS256","jwk":{"e":"AQAB","kty":"RSA","n":"..."},"nonce":"nonce","url":"url"} + protected = "eyJhbGciOiJSUzI1NiIsImp3ayI6eyJlIjoiQVFBQiIsImt0eSI6" + + "IlJTQSIsIm4iOiI0eGdaM2VSUGt3b1J2eTdxZVJVYm1NRGUwVi14" + + "SDllV0xkdTBpaGVlTGxybUQybXFXWGZQOUllU0tBcGJuMzRnOFR1" + + "QVM5ZzV6aHE4RUxRM2ttanItS1Y4NkdBTWdJNlZBY0dscTNRcnpw" + + "VENmXzMwQWI3LXphd3JmUmFGT05hMUh3RXpQWTFLSG5HVmt4SmM4" + + "NWdOa3dZSTlTWTJSSFh0dmxuM3pzNXdJVE5yZG9zcUVYZWFJa1ZZ" + + "QkVoYmhOdTU0cHAza3hvNlR1V0xpOWU2cFhlV2V0RXdtbEJ3dFda" + + "bFBvaWIyajNUeExCa3NLWmZveUZ5ZWszODBtSGdKQXVtUV9JMmZq" + + "ajk4Xzk3bWszaWhPWTRBZ1ZkQ0RqMXpfR0NvWmtHNVJxN25iQ0d5" + + "b3N5S1d5RFgwMFpzLW5OcVZob0xlSXZYQzRubldkSk1aNnJvZ3h5" + + "UVEifSwibm9uY2UiOiJub25jZSIsInVybCI6InVybCJ9" + // {"Msg":"Hello JWS"} + payload = "eyJNc2ciOiJIZWxsbyBKV1MifQ" + // printf '.' | openssl dgst -binary -sha256 -sign testKey | b64raw + signature = "YFyl_xz1E7TR-3E1bIuASTr424EgCvBHjt25WUFC2VaDjXYV0Rj_" + + "Hd3dJ_2IRqBrXDZZ2n4ZeA_4mm3QFwmwyeDwe2sWElhb82lCZ8iX" + + "uFnjeOmSOjx-nWwPa5ibCXzLq13zZ-OBV1Z4oN_TuailQeRoSfA3" + + "nO8gG52mv1x2OMQ5MAFtt8jcngBLzts4AyhI6mBJ2w7Yaj3ZCriq" + + "DWA3GLFvvHdW1Ba9Z01wtGT2CuZI7DUk_6Qj1b3BkBGcoKur5C9i" + + "bUJtCkABwBMvBQNyD3MmXsrRFRTgvVlyU_yMaucYm7nmzEr_2PaQ" + + "50rFt_9qOfJ4sfbLtG1Wwae57BQx1g" + ) + + b, err := jwsEncodeJSON(claims, testKey, noKeyID, "nonce", "url") + if err != nil { + t.Fatal(err) + } + var jws struct{ Protected, Payload, Signature string } + if err := json.Unmarshal(b, &jws); err != nil { + t.Fatal(err) + } + if jws.Protected != protected { + t.Errorf("protected:\n%s\nwant:\n%s", jws.Protected, protected) + } + if jws.Payload != payload { + t.Errorf("payload:\n%s\nwant:\n%s", jws.Payload, payload) + } + if jws.Signature != signature { + t.Errorf("signature:\n%s\nwant:\n%s", jws.Signature, signature) + } +} + +func TestJWSEncodeNoNonce(t *testing.T) { + kid := KeyID("https://example.org/account/1") + claims := "RawString" + const ( + // {"alg":"ES256","kid":"https://example.org/account/1","nonce":"nonce","url":"url"} + protected = "eyJhbGciOiJFUzI1NiIsImtpZCI6Imh0dHBzOi8vZXhhbXBsZS5vcmcvYWNjb3VudC8xIiwidXJsIjoidXJsIn0" + // "Raw String" + payload = "RawString" + ) + + b, err := jwsEncodeJSON(claims, testKeyEC, kid, "", "url") + if err != nil { + t.Fatal(err) + } + var jws struct{ Protected, Payload, Signature string } + if err := json.Unmarshal(b, &jws); err != nil { + t.Fatal(err) + } + if jws.Protected != protected { + t.Errorf("protected:\n%s\nwant:\n%s", jws.Protected, protected) + } + if jws.Payload != payload { + t.Errorf("payload:\n%s\nwant:\n%s", jws.Payload, payload) + } + + sig, err := base64.RawURLEncoding.DecodeString(jws.Signature) + if err != nil { + t.Fatalf("jws.Signature: %v", err) + } + r, s := big.NewInt(0), big.NewInt(0) + r.SetBytes(sig[:len(sig)/2]) + s.SetBytes(sig[len(sig)/2:]) + h := sha256.Sum256([]byte(protected + "." + payload)) + if !ecdsa.Verify(testKeyEC.Public().(*ecdsa.PublicKey), h[:], r, s) { + t.Error("invalid signature") + } +} + +func TestJWSEncodeKID(t *testing.T) { + kid := KeyID("https://example.org/account/1") + claims := struct{ Msg string }{"Hello JWS"} + // JWS signed with testKeyEC + const ( + // {"alg":"ES256","kid":"https://example.org/account/1","nonce":"nonce","url":"url"} + protected = "eyJhbGciOiJFUzI1NiIsImtpZCI6Imh0dHBzOi8vZXhhbXBsZS5" + + "vcmcvYWNjb3VudC8xIiwibm9uY2UiOiJub25jZSIsInVybCI6InVybCJ9" + // {"Msg":"Hello JWS"} + payload = "eyJNc2ciOiJIZWxsbyBKV1MifQ" + ) + + b, err := jwsEncodeJSON(claims, testKeyEC, kid, "nonce", "url") + if err != nil { + t.Fatal(err) + } + var jws struct{ Protected, Payload, Signature string } + if err := json.Unmarshal(b, &jws); err != nil { + t.Fatal(err) + } + if jws.Protected != protected { + t.Errorf("protected:\n%s\nwant:\n%s", jws.Protected, protected) + } + if jws.Payload != payload { + t.Errorf("payload:\n%s\nwant:\n%s", jws.Payload, payload) + } + + sig, err := base64.RawURLEncoding.DecodeString(jws.Signature) + if err != nil { + t.Fatalf("jws.Signature: %v", err) + } + r, s := big.NewInt(0), big.NewInt(0) + r.SetBytes(sig[:len(sig)/2]) + s.SetBytes(sig[len(sig)/2:]) + h := sha256.Sum256([]byte(protected + "." + payload)) + if !ecdsa.Verify(testKeyEC.Public().(*ecdsa.PublicKey), h[:], r, s) { + t.Error("invalid signature") + } +} + +func TestJWSEncodeJSONEC(t *testing.T) { + tt := []struct { + key *ecdsa.PrivateKey + x, y string + alg, crv string + }{ + {testKeyEC, testKeyECPubX, testKeyECPubY, "ES256", "P-256"}, + {testKeyEC384, testKeyEC384PubX, testKeyEC384PubY, "ES384", "P-384"}, + {testKeyEC512, testKeyEC512PubX, testKeyEC512PubY, "ES512", "P-521"}, + } + for i, test := range tt { + claims := struct{ Msg string }{"Hello JWS"} + b, err := jwsEncodeJSON(claims, test.key, noKeyID, "nonce", "url") + if err != nil { + t.Errorf("%d: %v", i, err) + continue + } + var jws struct{ Protected, Payload, Signature string } + if err := json.Unmarshal(b, &jws); err != nil { + t.Errorf("%d: %v", i, err) + continue + } + + b, err = base64.RawURLEncoding.DecodeString(jws.Protected) + if err != nil { + t.Errorf("%d: jws.Protected: %v", i, err) + } + var head struct { + Alg string + Nonce string + URL string `json:"url"` + KID string `json:"kid"` + JWK struct { + Crv string + Kty string + X string + Y string + } `json:"jwk"` + } + if err := json.Unmarshal(b, &head); err != nil { + t.Errorf("%d: jws.Protected: %v", i, err) + } + if head.Alg != test.alg { + t.Errorf("%d: head.Alg = %q; want %q", i, head.Alg, test.alg) + } + if head.Nonce != "nonce" { + t.Errorf("%d: head.Nonce = %q; want nonce", i, head.Nonce) + } + if head.URL != "url" { + t.Errorf("%d: head.URL = %q; want 'url'", i, head.URL) + } + if head.KID != "" { + // We used noKeyID in jwsEncodeJSON: expect no kid value. + t.Errorf("%d: head.KID = %q; want empty", i, head.KID) + } + if head.JWK.Crv != test.crv { + t.Errorf("%d: head.JWK.Crv = %q; want %q", i, head.JWK.Crv, test.crv) + } + if head.JWK.Kty != "EC" { + t.Errorf("%d: head.JWK.Kty = %q; want EC", i, head.JWK.Kty) + } + if head.JWK.X != test.x { + t.Errorf("%d: head.JWK.X = %q; want %q", i, head.JWK.X, test.x) + } + if head.JWK.Y != test.y { + t.Errorf("%d: head.JWK.Y = %q; want %q", i, head.JWK.Y, test.y) + } + } +} + +type customTestSigner struct { + sig []byte + pub crypto.PublicKey +} + +func (s *customTestSigner) Public() crypto.PublicKey { return s.pub } +func (s *customTestSigner) Sign(io.Reader, []byte, crypto.SignerOpts) ([]byte, error) { + return s.sig, nil +} + +func TestJWSEncodeJSONCustom(t *testing.T) { + claims := struct{ Msg string }{"hello"} + const ( + // printf '{"Msg":"hello"}' | b64raw + payload = "eyJNc2ciOiJoZWxsbyJ9" + // printf 'testsig' | b64raw + testsig = "dGVzdHNpZw" + + // the example P256 curve point from https://tools.ietf.org/html/rfc7515#appendix-A.3.1 + // encoded as ASN.1… + es256stdsig = "MEUCIA7RIVN5Y2xIPC9/FVgH1AKjsigDOvl8fheBmsMWnqZlAiEA" + + "xQoH04w8cOXY8S2vCEpUgKZlkMXyk1Cajz9/ioOjVNU" + // …and RFC7518 (https://tools.ietf.org/html/rfc7518#section-3.4) + es256jwsig = "DtEhU3ljbEg8L38VWAfUAqOyKAM6-Xx-F4GawxaepmXFCgfTjDxw" + + "5djxLa8ISlSApmWQxfKTUJqPP3-Kg6NU1Q" + + // printf '{"alg":"ES256","jwk":{"crv":"P-256","kty":"EC","x":,"y":},"nonce":"nonce","url":"url"}' | b64raw + es256phead = "eyJhbGciOiJFUzI1NiIsImp3ayI6eyJjcnYiOiJQLTI1NiIsImt0" + + "eSI6IkVDIiwieCI6IjVsaEV1ZzV4SzR4QkRaMm5BYmF4THRhTGl2" + + "ODVieEo3ZVBkMWRrTzIzSFEiLCJ5IjoiNGFpSzcyc0JlVUFHa3Yw" + + "VGFMc213b2tZVVl5TnhHc1M1RU1JS3dzTklLayJ9LCJub25jZSI6" + + "Im5vbmNlIiwidXJsIjoidXJsIn0" + + // {"alg":"RS256","jwk":{"e":"AQAB","kty":"RSA","n":"..."},"nonce":"nonce","url":"url"} + rs256phead = "eyJhbGciOiJSUzI1NiIsImp3ayI6eyJlIjoiQVFBQiIsImt0eSI6" + + "IlJTQSIsIm4iOiI0eGdaM2VSUGt3b1J2eTdxZVJVYm1NRGUwVi14" + + "SDllV0xkdTBpaGVlTGxybUQybXFXWGZQOUllU0tBcGJuMzRnOFR1" + + "QVM5ZzV6aHE4RUxRM2ttanItS1Y4NkdBTWdJNlZBY0dscTNRcnpw" + + "VENmXzMwQWI3LXphd3JmUmFGT05hMUh3RXpQWTFLSG5HVmt4SmM4" + + "NWdOa3dZSTlTWTJSSFh0dmxuM3pzNXdJVE5yZG9zcUVYZWFJa1ZZ" + + "QkVoYmhOdTU0cHAza3hvNlR1V0xpOWU2cFhlV2V0RXdtbEJ3dFda" + + "bFBvaWIyajNUeExCa3NLWmZveUZ5ZWszODBtSGdKQXVtUV9JMmZq" + + "ajk4Xzk3bWszaWhPWTRBZ1ZkQ0RqMXpfR0NvWmtHNVJxN25iQ0d5" + + "b3N5S1d5RFgwMFpzLW5OcVZob0xlSXZYQzRubldkSk1aNnJvZ3h5" + + "UVEifSwibm9uY2UiOiJub25jZSIsInVybCI6InVybCJ9" + ) + + tt := []struct { + alg, phead string + pub crypto.PublicKey + stdsig, jwsig string + }{ + {"ES256", es256phead, testKeyEC.Public(), es256stdsig, es256jwsig}, + {"RS256", rs256phead, testKey.Public(), testsig, testsig}, + } + for _, tc := range tt { + tc := tc + t.Run(tc.alg, func(t *testing.T) { + stdsig, err := base64.RawStdEncoding.DecodeString(tc.stdsig) + if err != nil { + t.Errorf("couldn't decode test vector: %v", err) + } + signer := &customTestSigner{ + sig: stdsig, + pub: tc.pub, + } + + b, err := jwsEncodeJSON(claims, signer, noKeyID, "nonce", "url") + if err != nil { + t.Fatal(err) + } + var j jsonWebSignature + if err := json.Unmarshal(b, &j); err != nil { + t.Fatal(err) + } + if j.Protected != tc.phead { + t.Errorf("j.Protected = %q\nwant %q", j.Protected, tc.phead) + } + if j.Payload != payload { + t.Errorf("j.Payload = %q\nwant %q", j.Payload, payload) + } + if j.Sig != tc.jwsig { + t.Errorf("j.Sig = %q\nwant %q", j.Sig, tc.jwsig) + } + }) + } +} + +func TestJWSWithMAC(t *testing.T) { + // Example from RFC 7520 Section 4.4.3. + // https://tools.ietf.org/html/rfc7520#section-4.4.3 + b64Key := "hJtXIZ2uSN5kbQfbtTNWbpdmhkV8FJG-Onbc6mxCcYg" + rawPayload := []byte("It\xe2\x80\x99s a dangerous business, Frodo, going out your " + + "door. You step onto the road, and if you don't keep your feet, " + + "there\xe2\x80\x99s no knowing where you might be swept off " + + "to.") + protected := "eyJhbGciOiJIUzI1NiIsImtpZCI6IjAxOGMwYWU1LTRkOWItNDcxYi1iZmQ2LW" + + "VlZjMxNGJjNzAzNyJ9" + payload := "SXTigJlzIGEgZGFuZ2Vyb3VzIGJ1c2luZXNzLCBGcm9kbywg" + + "Z29pbmcgb3V0IHlvdXIgZG9vci4gWW91IHN0ZXAgb250byB0aGUgcm9h" + + "ZCwgYW5kIGlmIHlvdSBkb24ndCBrZWVwIHlvdXIgZmVldCwgdGhlcmXi" + + "gJlzIG5vIGtub3dpbmcgd2hlcmUgeW91IG1pZ2h0IGJlIHN3ZXB0IG9m" + + "ZiB0by4" + sig := "s0h6KThzkfBBBkLspW1h84VsJZFTsPPqMDA7g1Md7p0" + + key, err := base64.RawURLEncoding.DecodeString(b64Key) + if err != nil { + t.Fatalf("unable to decode key: %q", b64Key) + } + got, err := jwsWithMAC(key, "018c0ae5-4d9b-471b-bfd6-eef314bc7037", "", rawPayload) + if err != nil { + t.Fatalf("jwsWithMAC() = %q", err) + } + if got.Protected != protected { + t.Errorf("got.Protected = %q\nwant %q", got.Protected, protected) + } + if got.Payload != payload { + t.Errorf("got.Payload = %q\nwant %q", got.Payload, payload) + } + if got.Sig != sig { + t.Errorf("got.Signature = %q\nwant %q", got.Sig, sig) + } +} + +func TestJWSWithMACError(t *testing.T) { + p := "{}" + if _, err := jwsWithMAC(nil, "", "", []byte(p)); err == nil { + t.Errorf("jwsWithMAC(nil, ...) = success; want err") + } +} + +func TestJWKThumbprintRSA(t *testing.T) { + // Key example from RFC 7638 + const base64N = "0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4cbbfAAt" + + "VT86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMstn6" + + "4tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FD" + + "W2QvzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n9" + + "1CbOpbISD08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINH" + + "aQ-G_xBniIqbw0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw" + const base64E = "AQAB" + const expected = "NzbLsXh8uDCcd-6MNwXF4W_7noWXFZAfHkxZsRGC9Xs" + + b, err := base64.RawURLEncoding.DecodeString(base64N) + if err != nil { + t.Fatalf("Error parsing example key N: %v", err) + } + n := new(big.Int).SetBytes(b) + + b, err = base64.RawURLEncoding.DecodeString(base64E) + if err != nil { + t.Fatalf("Error parsing example key E: %v", err) + } + e := new(big.Int).SetBytes(b) + + pub := &rsa.PublicKey{N: n, E: int(e.Uint64())} + th, err := JWKThumbprint(pub) + if err != nil { + t.Error(err) + } + if th != expected { + t.Errorf("thumbprint = %q; want %q", th, expected) + } +} + +func TestJWKThumbprintEC(t *testing.T) { + // Key example from RFC 7520 + // expected was computed with + // printf '{"crv":"P-521","kty":"EC","x":"","y":""}' | \ + // openssl dgst -binary -sha256 | b64raw + const ( + base64X = "AHKZLLOsCOzz5cY97ewNUajB957y-C-U88c3v13nmGZx6sYl_oJXu9A5RkT" + + "KqjqvjyekWF-7ytDyRXYgCF5cj0Kt" + base64Y = "AdymlHvOiLxXkEhayXQnNCvDX4h9htZaCJN34kfmC6pV5OhQHiraVySsUda" + + "QkAgDPrwQrJmbnX9cwlGfP-HqHZR1" + expected = "dHri3SADZkrush5HU_50AoRhcKFryN-PI6jPBtPL55M" + ) + + b, err := base64.RawURLEncoding.DecodeString(base64X) + if err != nil { + t.Fatalf("Error parsing example key X: %v", err) + } + x := new(big.Int).SetBytes(b) + + b, err = base64.RawURLEncoding.DecodeString(base64Y) + if err != nil { + t.Fatalf("Error parsing example key Y: %v", err) + } + y := new(big.Int).SetBytes(b) + + pub := &ecdsa.PublicKey{Curve: elliptic.P521(), X: x, Y: y} + th, err := JWKThumbprint(pub) + if err != nil { + t.Error(err) + } + if th != expected { + t.Errorf("thumbprint = %q; want %q", th, expected) + } +} + +func TestJWKThumbprintErrUnsupportedKey(t *testing.T) { + _, err := JWKThumbprint(struct{}{}) + if err != ErrUnsupportedKey { + t.Errorf("err = %q; want %q", err, ErrUnsupportedKey) + } +} diff --git a/third_party/forked/acme/rfc8555.go b/third_party/forked/acme/rfc8555.go new file mode 100644 index 00000000000..169bf802efe --- /dev/null +++ b/third_party/forked/acme/rfc8555.go @@ -0,0 +1,486 @@ +// Copyright 2019 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package acme + +import ( + "context" + "crypto" + "encoding/base64" + "encoding/json" + "encoding/pem" + "errors" + "fmt" + "io" + "net/http" + "time" +) + +// DeactivateReg permanently disables an existing account associated with c.Key. +// A deactivated account can no longer request certificate issuance or access +// resources related to the account, such as orders or authorizations. +// +// It only works with CAs implementing RFC 8555. +func (c *Client) DeactivateReg(ctx context.Context) error { + if _, err := c.Discover(ctx); err != nil { // required by c.accountKID + return err + } + url := string(c.accountKID(ctx)) + if url == "" { + return ErrNoAccount + } + req := json.RawMessage(`{"status": "deactivated"}`) + res, err := c.post(ctx, nil, url, req, wantStatus(http.StatusOK)) + if err != nil { + return err + } + res.Body.Close() + return nil +} + +// registerRFC is equivalent to c.Register but for CAs implementing RFC 8555. +// It expects c.Discover to have already been called. +func (c *Client) registerRFC(ctx context.Context, acct *Account, prompt func(tosURL string) bool) (*Account, error) { + c.cacheMu.Lock() // guard c.kid access + defer c.cacheMu.Unlock() + + req := struct { + TermsAgreed bool `json:"termsOfServiceAgreed,omitempty"` + Contact []string `json:"contact,omitempty"` + ExternalAccountBinding *jsonWebSignature `json:"externalAccountBinding,omitempty"` + }{ + Contact: acct.Contact, + } + if c.dir.Terms != "" { + req.TermsAgreed = prompt(c.dir.Terms) + } + + // set 'externalAccountBinding' field if requested + if acct.ExternalAccountBinding != nil { + eabJWS, err := c.encodeExternalAccountBinding(acct.ExternalAccountBinding) + if err != nil { + return nil, fmt.Errorf("acme: failed to encode external account binding: %v", err) + } + req.ExternalAccountBinding = eabJWS + } + + res, err := c.post(ctx, c.Key, c.dir.RegURL, req, wantStatus( + http.StatusOK, // account with this key already registered + http.StatusCreated, // new account created + )) + if err != nil { + return nil, err + } + + defer res.Body.Close() + a, err := responseAccount(res) + if err != nil { + return nil, err + } + // Cache Account URL even if we return an error to the caller. + // It is by all means a valid and usable "kid" value for future requests. + c.KID = KeyID(a.URI) + if res.StatusCode == http.StatusOK { + return nil, ErrAccountAlreadyExists + } + return a, nil +} + +// encodeExternalAccountBinding will encode an external account binding stanza +// as described in https://tools.ietf.org/html/rfc8555#section-7.3.4. +func (c *Client) encodeExternalAccountBinding(eab *ExternalAccountBinding) (*jsonWebSignature, error) { + jwk, err := jwkEncode(c.Key.Public()) + if err != nil { + return nil, err + } + return jwsWithMAC(eab.Key, eab.KID, c.dir.RegURL, []byte(jwk)) +} + +// updateRegRFC is equivalent to c.UpdateReg but for CAs implementing RFC 8555. +// It expects c.Discover to have already been called. +func (c *Client) updateRegRFC(ctx context.Context, a *Account) (*Account, error) { + url := string(c.accountKID(ctx)) + if url == "" { + return nil, ErrNoAccount + } + req := struct { + Contact []string `json:"contact,omitempty"` + }{ + Contact: a.Contact, + } + res, err := c.post(ctx, nil, url, req, wantStatus(http.StatusOK)) + if err != nil { + return nil, err + } + defer res.Body.Close() + return responseAccount(res) +} + +// getRegRFC is equivalent to c.GetReg but for CAs implementing RFC 8555. +// It expects c.Discover to have already been called. +func (c *Client) getRegRFC(ctx context.Context) (*Account, error) { + req := json.RawMessage(`{"onlyReturnExisting": true}`) + res, err := c.post(ctx, c.Key, c.dir.RegURL, req, wantStatus(http.StatusOK)) + if e, ok := err.(*Error); ok && e.ProblemType == "urn:ietf:params:acme:error:accountDoesNotExist" { + return nil, ErrNoAccount + } + if err != nil { + return nil, err + } + + defer res.Body.Close() + return responseAccount(res) +} + +func responseAccount(res *http.Response) (*Account, error) { + var v struct { + Status string + Contact []string + Orders string + } + if err := json.NewDecoder(res.Body).Decode(&v); err != nil { + return nil, fmt.Errorf("acme: invalid account response: %v", err) + } + return &Account{ + URI: res.Header.Get("Location"), + Status: v.Status, + Contact: v.Contact, + OrdersURL: v.Orders, + }, nil +} + +// accountKeyRollover attempts to perform account key rollover. +// On success it will change client.Key to the new key. +func (c *Client) accountKeyRollover(ctx context.Context, newKey crypto.Signer) error { + dir, err := c.Discover(ctx) // Also required by c.accountKID + if err != nil { + return err + } + kid := c.accountKID(ctx) + if kid == noKeyID { + return ErrNoAccount + } + oldKey, err := jwkEncode(c.Key.Public()) + if err != nil { + return err + } + payload := struct { + Account string `json:"account"` + OldKey json.RawMessage `json:"oldKey"` + }{ + Account: string(kid), + OldKey: json.RawMessage(oldKey), + } + inner, err := jwsEncodeJSON(payload, newKey, noKeyID, noNonce, dir.KeyChangeURL) + if err != nil { + return err + } + + res, err := c.post(ctx, nil, dir.KeyChangeURL, base64.RawURLEncoding.EncodeToString(inner), wantStatus(http.StatusOK)) + if err != nil { + return err + } + defer res.Body.Close() + c.Key = newKey + return nil +} + +// AuthorizeOrder initiates the order-based application for certificate issuance, +// as opposed to pre-authorization in Authorize. +// It is only supported by CAs implementing RFC 8555. +// +// The caller then needs to fetch each authorization with GetAuthorization, +// identify those with StatusPending status and fulfill a challenge using Accept. +// Once all authorizations are satisfied, the caller will typically want to poll +// order status using WaitOrder until it's in StatusReady state. +// To finalize the order and obtain a certificate, the caller submits a CSR with CreateOrderCert. +func (c *Client) AuthorizeOrder(ctx context.Context, id []AuthzID, opt ...OrderOption) (*Order, error) { + dir, err := c.Discover(ctx) + if err != nil { + return nil, err + } + + req := struct { + Identifiers []wireAuthzID `json:"identifiers"` + NotBefore string `json:"notBefore,omitempty"` + NotAfter string `json:"notAfter,omitempty"` + Profile string `json:"profile,omitempty"` + }{} + for _, v := range id { + req.Identifiers = append(req.Identifiers, wireAuthzID{ + Type: v.Type, + Value: v.Value, + }) + } + for _, o := range opt { + switch o := o.(type) { + case orderNotBeforeOpt: + req.NotBefore = time.Time(o).Format(time.RFC3339) + case orderNotAfterOpt: + req.NotAfter = time.Time(o).Format(time.RFC3339) + case orderProfileOpt: + if !dir.Profiles.isSupported() { + return nil, ErrCADoesNotSupportProfiles + } + profileName := string(o) + if !dir.Profiles.Has(profileName) { + return nil, fmt.Errorf("%w %s", ErrProfileNotInSetOfSupportedProfiles, profileName) + } + req.Profile = profileName + default: + // Package's fault if we let this happen. + panic(fmt.Sprintf("unsupported order option type %T", o)) + } + } + + res, err := c.post(ctx, nil, dir.OrderURL, req, wantStatus(http.StatusCreated)) + if err != nil { + return nil, err + } + defer res.Body.Close() + return responseOrder(res) +} + +// GetOrder retrives an order identified by the given URL. +// For orders created with AuthorizeOrder, the url value is Order.URI. +// +// If a caller needs to poll an order until its status is final, +// see the WaitOrder method. +func (c *Client) GetOrder(ctx context.Context, url string) (*Order, error) { + if _, err := c.Discover(ctx); err != nil { + return nil, err + } + + res, err := c.postAsGet(ctx, url, wantStatus(http.StatusOK)) + if err != nil { + return nil, err + } + defer res.Body.Close() + return responseOrder(res) +} + +// WaitOrder polls an order from the given URL until it is in one of the final states, +// StatusReady, StatusValid or StatusInvalid, the CA responded with a non-retryable error +// or the context is done. +// +// It returns a non-nil Order only if its Status is StatusReady or StatusValid. +// In all other cases WaitOrder returns an error. +// If the Status is StatusInvalid, the returned error is of type *OrderError. +func (c *Client) WaitOrder(ctx context.Context, url string) (*Order, error) { + if _, err := c.Discover(ctx); err != nil { + return nil, err + } + for { + res, err := c.postAsGet(ctx, url, wantStatus(http.StatusOK)) + if err != nil { + return nil, err + } + o, err := responseOrder(res) + res.Body.Close() + switch { + case err != nil: + // Skip and retry. + case o.Status == StatusInvalid: + return nil, &OrderError{OrderURL: o.URI, Status: o.Status} + case o.Status == StatusReady || o.Status == StatusValid: + return o, nil + } + + d := retryAfter(res.Header.Get("Retry-After")) + if d == 0 { + // Default retry-after. + // Same reasoning as in WaitAuthorization. + d = time.Second + } + t := time.NewTimer(d) + select { + case <-ctx.Done(): + t.Stop() + return nil, ctx.Err() + case <-t.C: + // Retry. + } + } +} + +func responseOrder(res *http.Response) (*Order, error) { + var v struct { + Status string + Expires time.Time + Identifiers []wireAuthzID + NotBefore time.Time + NotAfter time.Time + Error *wireError + Authorizations []string + Finalize string + Certificate string + } + if err := json.NewDecoder(res.Body).Decode(&v); err != nil { + return nil, fmt.Errorf("acme: error reading order: %v", err) + } + o := &Order{ + URI: res.Header.Get("Location"), + Status: v.Status, + Expires: v.Expires, + NotBefore: v.NotBefore, + NotAfter: v.NotAfter, + AuthzURLs: v.Authorizations, + FinalizeURL: v.Finalize, + CertURL: v.Certificate, + } + for _, id := range v.Identifiers { + o.Identifiers = append(o.Identifiers, AuthzID{Type: id.Type, Value: id.Value}) + } + if v.Error != nil { + o.Error = v.Error.error(nil /* headers */) + } + return o, nil +} + +// CreateOrderCert submits the CSR (Certificate Signing Request) to a CA at the specified URL. +// The URL is the FinalizeURL field of an Order created with AuthorizeOrder. +// +// If the bundle argument is true, the returned value also contain the CA (issuer) +// certificate chain. Otherwise, only a leaf certificate is returned. +// The returned URL can be used to re-fetch the certificate using FetchCert. +// +// This method is only supported by CAs implementing RFC 8555. See CreateCert for pre-RFC CAs. +// +// CreateOrderCert returns an error if the CA's response is unreasonably large. +// Callers are encouraged to parse the returned value to ensure the certificate is valid and has the expected features. +func (c *Client) CreateOrderCert(ctx context.Context, url string, csr []byte, bundle bool) (der [][]byte, certURL string, err error) { + if _, err := c.Discover(ctx); err != nil { // required by c.accountKID + return nil, "", err + } + + // RFC describes this as "finalize order" request. + req := struct { + CSR string `json:"csr"` + }{ + CSR: base64.RawURLEncoding.EncodeToString(csr), + } + res, err := c.post(ctx, nil, url, req, wantStatus(http.StatusOK)) + if err != nil { + return nil, "", err + } + defer res.Body.Close() + o, err := responseOrder(res) + if err != nil { + return nil, "", err + } + + // Wait for CA to issue the cert if they haven't. + if o.Status != StatusValid { + o, err = c.WaitOrder(ctx, o.URI) + } + if err != nil { + return nil, "", err + } + // The only acceptable status post finalize and WaitOrder is "valid". + if o.Status != StatusValid { + return nil, "", &OrderError{OrderURL: o.URI, Status: o.Status} + } + crt, err := c.fetchCertRFC(ctx, o.CertURL, bundle) + return crt, o.CertURL, err +} + +// fetchCertRFC downloads issued certificate from the given URL. +// It expects the CA to respond with PEM-encoded certificate chain. +// +// The URL argument is the CertURL field of Order. +func (c *Client) fetchCertRFC(ctx context.Context, url string, bundle bool) ([][]byte, error) { + res, err := c.postAsGet(ctx, url, wantStatus(http.StatusOK)) + if err != nil { + return nil, err + } + defer res.Body.Close() + + // Get all the bytes up to a sane maximum. + // Account very roughly for base64 overhead. + const max = maxCertChainSize + maxCertChainSize/33 + b, err := io.ReadAll(io.LimitReader(res.Body, max+1)) + if err != nil { + return nil, fmt.Errorf("acme: fetch cert response stream: %v", err) + } + if len(b) > max { + return nil, errors.New("acme: certificate chain is too big") + } + + // Decode PEM chain. + var chain [][]byte + for { + var p *pem.Block + p, b = pem.Decode(b) + if p == nil { + break + } + if p.Type != "CERTIFICATE" { + return nil, fmt.Errorf("acme: invalid PEM cert type %q", p.Type) + } + + chain = append(chain, p.Bytes) + if !bundle { + return chain, nil + } + if len(chain) > maxChainLen { + return nil, errors.New("acme: certificate chain is too long") + } + } + if len(chain) == 0 { + return nil, errors.New("acme: certificate chain is empty") + } + return chain, nil +} + +// sends a cert revocation request in either JWK form when key is non-nil or KID form otherwise. +func (c *Client) revokeCertRFC(ctx context.Context, key crypto.Signer, cert []byte, reason CRLReasonCode) error { + req := &struct { + Cert string `json:"certificate"` + Reason int `json:"reason"` + }{ + Cert: base64.RawURLEncoding.EncodeToString(cert), + Reason: int(reason), + } + res, err := c.post(ctx, key, c.dir.RevokeURL, req, wantStatus(http.StatusOK)) + if err != nil { + if isAlreadyRevoked(err) { + // Assume it is not an error to revoke an already revoked cert. + return nil + } + return err + } + defer res.Body.Close() + return nil +} + +func isAlreadyRevoked(err error) bool { + e, ok := err.(*Error) + return ok && e.ProblemType == "urn:ietf:params:acme:error:alreadyRevoked" +} + +// ListCertAlternates retrieves any alternate certificate chain URLs for the +// given certificate chain URL. These alternate URLs can be passed to FetchCert +// in order to retrieve the alternate certificate chains. +// +// If there are no alternate issuer certificate chains, a nil slice will be +// returned. +func (c *Client) ListCertAlternates(ctx context.Context, url string) ([]string, error) { + if _, err := c.Discover(ctx); err != nil { // required by c.accountKID + return nil, err + } + + res, err := c.postAsGet(ctx, url, wantStatus(http.StatusOK)) + if err != nil { + return nil, err + } + defer res.Body.Close() + + // We don't need the body but we need to discard it so we don't end up + // preventing keep-alive + if _, err := io.Copy(io.Discard, res.Body); err != nil { + return nil, fmt.Errorf("acme: cert alternates response stream: %v", err) + } + alts := linkHeader(res.Header, "alternate") + return alts, nil +} diff --git a/third_party/forked/acme/rfc8555_test.go b/third_party/forked/acme/rfc8555_test.go new file mode 100644 index 00000000000..b53f3d2ab24 --- /dev/null +++ b/third_party/forked/acme/rfc8555_test.go @@ -0,0 +1,1138 @@ +// Copyright 2019 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package acme + +import ( + "bytes" + "context" + "crypto/hmac" + "crypto/rand" + "crypto/sha256" + "crypto/x509" + "crypto/x509/pkix" + "encoding/base64" + "encoding/json" + "encoding/pem" + "errors" + "fmt" + "io" + "math/big" + "net/http" + "net/http/httptest" + "reflect" + "strings" + "sync" + "testing" + "time" +) + +// While contents of this file is pertinent only to RFC8555, +// it is complementary to the tests in the other _test.go files +// many of which are valid for both pre- and RFC8555. +// This will make it easier to clean up the tests once non-RFC compliant +// code is removed. + +func TestRFC_Discover(t *testing.T) { + const ( + nonce = "https://example.com/acme/new-nonce" + reg = "https://example.com/acme/new-acct" + order = "https://example.com/acme/new-order" + authz = "https://example.com/acme/new-authz" + revoke = "https://example.com/acme/revoke-cert" + keychange = "https://example.com/acme/key-change" + metaTerms = "https://example.com/acme/terms/2017-5-30" + metaWebsite = "https://www.example.com/" + metaCAA = "example.com" + ) + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + fmt.Fprintf(w, `{ + "newNonce": %q, + "newAccount": %q, + "newOrder": %q, + "newAuthz": %q, + "revokeCert": %q, + "keyChange": %q, + "meta": { + "termsOfService": %q, + "website": %q, + "caaIdentities": [%q], + "externalAccountRequired": true + } + }`, nonce, reg, order, authz, revoke, keychange, metaTerms, metaWebsite, metaCAA) + })) + defer ts.Close() + c := &Client{DirectoryURL: ts.URL} + dir, err := c.Discover(context.Background()) + if err != nil { + t.Fatal(err) + } + if dir.NonceURL != nonce { + t.Errorf("dir.NonceURL = %q; want %q", dir.NonceURL, nonce) + } + if dir.RegURL != reg { + t.Errorf("dir.RegURL = %q; want %q", dir.RegURL, reg) + } + if dir.OrderURL != order { + t.Errorf("dir.OrderURL = %q; want %q", dir.OrderURL, order) + } + if dir.AuthzURL != authz { + t.Errorf("dir.AuthzURL = %q; want %q", dir.AuthzURL, authz) + } + if dir.RevokeURL != revoke { + t.Errorf("dir.RevokeURL = %q; want %q", dir.RevokeURL, revoke) + } + if dir.KeyChangeURL != keychange { + t.Errorf("dir.KeyChangeURL = %q; want %q", dir.KeyChangeURL, keychange) + } + if dir.Terms != metaTerms { + t.Errorf("dir.Terms = %q; want %q", dir.Terms, metaTerms) + } + if dir.Website != metaWebsite { + t.Errorf("dir.Website = %q; want %q", dir.Website, metaWebsite) + } + if len(dir.CAA) == 0 || dir.CAA[0] != metaCAA { + t.Errorf("dir.CAA = %q; want [%q]", dir.CAA, metaCAA) + } + if !dir.ExternalAccountRequired { + t.Error("dir.Meta.ExternalAccountRequired is false") + } + if dir.Profiles != nil { + t.Errorf("dir.Profiles is expected to be nil, got %+v", dir.Profiles) + } +} + +func TestDiscover_WithProfiles(t *testing.T) { + const ( + nonce = "https://example.com/acme/new-nonce" + reg = "https://example.com/acme/new-acct" + order = "https://example.com/acme/new-order" + authz = "https://example.com/acme/new-authz" + revoke = "https://example.com/acme/revoke-cert" + keychange = "https://example.com/acme/key-change" + metaTerms = "https://example.com/acme/terms/2017-5-30" + metaWebsite = "https://www.example.com/" + metaCAA = "example.com" + ) + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + fmt.Fprintf(w, `{ + "newNonce": %q, + "newAccount": %q, + "newOrder": %q, + "newAuthz": %q, + "revokeCert": %q, + "keyChange": %q, + "meta": { + "termsOfService": %q, + "website": %q, + "caaIdentities": [%q], + "externalAccountRequired": true, + "profiles": { + "default": "Your favorite default profile", + "tlsserver": "New and improved", + "client": "For all your mutual TLS needs" + } + } + }`, nonce, reg, order, authz, revoke, keychange, metaTerms, metaWebsite, metaCAA) + })) + defer ts.Close() + c := &Client{DirectoryURL: ts.URL} + dir, err := c.Discover(context.Background()) + if err != nil { + t.Fatal(err) + } + expected := Profiles(map[string]string{"default": "Your favorite default profile", "tlsserver": "New and improved", "client": "For all your mutual TLS needs"}) + if dir.Profiles == nil { + t.Errorf("expected directory to be %+v; got nil", expected) + } + + for key, value := range dir.Profiles { + if expValue := expected.GetDescription(key); value != expValue { + t.Errorf("expected key %+q to have description %+q; got %+q", key, expected, value) + } + } +} + +func TestRFC_popNonce(t *testing.T) { + var count int + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // The Client uses only Directory.NonceURL when specified. + // Expect no other URL paths. + if r.URL.Path != "/new-nonce" { + t.Errorf("r.URL.Path = %q; want /new-nonce", r.URL.Path) + } + if count > 0 { + w.WriteHeader(http.StatusTooManyRequests) + return + } + count++ + w.Header().Set("Replay-Nonce", "second") + })) + cl := &Client{ + DirectoryURL: ts.URL, + dir: &Directory{NonceURL: ts.URL + "/new-nonce"}, + } + cl.addNonce(http.Header{"Replay-Nonce": {"first"}}) + + for i, nonce := range []string{"first", "second"} { + v, err := cl.popNonce(context.Background(), "") + if err != nil { + t.Errorf("%d: cl.popNonce: %v", i, err) + } + if v != nonce { + t.Errorf("%d: cl.popNonce = %q; want %q", i, v, nonce) + } + } + // No more nonces and server replies with an error past first nonce fetch. + // Expected to fail. + if _, err := cl.popNonce(context.Background(), ""); err == nil { + t.Error("last cl.popNonce returned nil error") + } +} + +func TestRFC_postKID(t *testing.T) { + var ts *httptest.Server + ts = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + switch r.URL.Path { + case "/new-nonce": + w.Header().Set("Replay-Nonce", "nonce") + case "/new-account": + w.Header().Set("Location", "/account-1") + w.Write([]byte(`{"status":"valid"}`)) + case "/post": + b, _ := io.ReadAll(r.Body) // check err later in decodeJWSxxx + head, err := decodeJWSHead(bytes.NewReader(b)) + if err != nil { + t.Errorf("decodeJWSHead: %v", err) + return + } + if head.KID != "/account-1" { + t.Errorf("head.KID = %q; want /account-1", head.KID) + } + if len(head.JWK) != 0 { + t.Errorf("head.JWK = %q; want zero map", head.JWK) + } + if v := ts.URL + "/post"; head.URL != v { + t.Errorf("head.URL = %q; want %q", head.URL, v) + } + + var payload struct{ Msg string } + decodeJWSRequest(t, &payload, bytes.NewReader(b)) + if payload.Msg != "ping" { + t.Errorf("payload.Msg = %q; want ping", payload.Msg) + } + w.Write([]byte("pong")) + default: + t.Errorf("unhandled %s %s", r.Method, r.URL) + w.WriteHeader(http.StatusBadRequest) + } + })) + defer ts.Close() + + ctx := context.Background() + cl := &Client{ + Key: testKey, + DirectoryURL: ts.URL, + dir: &Directory{ + NonceURL: ts.URL + "/new-nonce", + RegURL: ts.URL + "/new-account", + OrderURL: "/force-rfc-mode", + }, + } + req := json.RawMessage(`{"msg":"ping"}`) + res, err := cl.post(ctx, nil /* use kid */, ts.URL+"/post", req, wantStatus(http.StatusOK)) + if err != nil { + t.Fatal(err) + } + defer res.Body.Close() + b, _ := io.ReadAll(res.Body) // don't care about err - just checking b + if string(b) != "pong" { + t.Errorf("res.Body = %q; want pong", b) + } +} + +// acmeServer simulates a subset of RFC 8555 compliant CA. +// +// TODO: We also have x/crypto/acme/autocert/acmetest and startACMEServerStub in autocert_test.go. +// It feels like this acmeServer is a sweet spot between usefulness and added complexity. +// Also, acmetest and startACMEServerStub were both written for draft-02, no RFC support. +// The goal is to consolidate all into one ACME test server. +type acmeServer struct { + ts *httptest.Server + handler map[string]http.HandlerFunc // keyed by r.URL.Path + + mu sync.Mutex + nnonce int +} + +func newACMEServer() *acmeServer { + return &acmeServer{handler: make(map[string]http.HandlerFunc)} +} + +func (s *acmeServer) handle(path string, f func(http.ResponseWriter, *http.Request)) { + s.handler[path] = http.HandlerFunc(f) +} + +func (s *acmeServer) start() { + s.ts = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + + // Directory request. + if r.URL.Path == "/" { + fmt.Fprintf(w, `{ + "newNonce": %q, + "newAccount": %q, + "newOrder": %q, + "newAuthz": %q, + "revokeCert": %q, + "keyChange": %q, + "meta": {"termsOfService": %q} + }`, + s.url("/acme/new-nonce"), + s.url("/acme/new-account"), + s.url("/acme/new-order"), + s.url("/acme/new-authz"), + s.url("/acme/revoke-cert"), + s.url("/acme/key-change"), + s.url("/terms"), + ) + return + } + + if r.URL.Path == "/directory-with-profiles" { + fmt.Fprintf(w, `{ + "newNonce": %q, + "newAccount": %q, + "newOrder": %q, + "newAuthz": %q, + "revokeCert": %q, + "keyChange": %q, + "meta": {"termsOfService": %q, "profiles": {"default": "Default", "server": "Server", "client": "Client"}} + }`, + s.url("/acme/new-nonce"), + s.url("/acme/new-account"), + s.url("/acme/new-order"), + s.url("/acme/new-authz"), + s.url("/acme/revoke-cert"), + s.url("/acme/key-change"), + s.url("/terms"), + ) + return + } + + // All other responses contain a nonce value unconditionally. + w.Header().Set("Replay-Nonce", s.nonce()) + if r.URL.Path == "/acme/new-nonce" { + return + } + + h := s.handler[r.URL.Path] + if h == nil { + w.WriteHeader(http.StatusBadRequest) + fmt.Fprintf(w, "Unhandled %s", r.URL.Path) + return + } + h.ServeHTTP(w, r) + })) +} + +func (s *acmeServer) close() { + s.ts.Close() +} + +func (s *acmeServer) url(path string) string { + return s.ts.URL + path +} + +func (s *acmeServer) nonce() string { + s.mu.Lock() + defer s.mu.Unlock() + s.nnonce++ + return fmt.Sprintf("nonce%d", s.nnonce) +} + +func (s *acmeServer) error(w http.ResponseWriter, e *wireError) { + w.WriteHeader(e.Status) + json.NewEncoder(w).Encode(e) +} + +func TestRFC_Register(t *testing.T) { + const email = "mailto:user@example.org" + + s := newACMEServer() + s.handle("/acme/new-account", func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Location", s.url("/accounts/1")) + w.WriteHeader(http.StatusCreated) // 201 means new account created + fmt.Fprintf(w, `{ + "status": "valid", + "contact": [%q], + "orders": %q + }`, email, s.url("/accounts/1/orders")) + + b, _ := io.ReadAll(r.Body) // check err later in decodeJWSxxx + head, err := decodeJWSHead(bytes.NewReader(b)) + if err != nil { + t.Errorf("decodeJWSHead: %v", err) + return + } + if len(head.JWK) == 0 { + t.Error("head.JWK is empty") + } + + var req struct{ Contact []string } + decodeJWSRequest(t, &req, bytes.NewReader(b)) + if len(req.Contact) != 1 || req.Contact[0] != email { + t.Errorf("req.Contact = %q; want [%q]", req.Contact, email) + } + }) + s.start() + defer s.close() + + ctx := context.Background() + cl := &Client{ + Key: testKeyEC, + DirectoryURL: s.url("/"), + } + + var didPrompt bool + a := &Account{Contact: []string{email}} + acct, err := cl.Register(ctx, a, func(tos string) bool { + didPrompt = true + terms := s.url("/terms") + if tos != terms { + t.Errorf("tos = %q; want %q", tos, terms) + } + return true + }) + if err != nil { + t.Fatal(err) + } + okAccount := &Account{ + URI: s.url("/accounts/1"), + Status: StatusValid, + Contact: []string{email}, + OrdersURL: s.url("/accounts/1/orders"), + } + if !reflect.DeepEqual(acct, okAccount) { + t.Errorf("acct = %+v; want %+v", acct, okAccount) + } + if !didPrompt { + t.Error("tos prompt wasn't called") + } + if v := cl.accountKID(ctx); v != KeyID(okAccount.URI) { + t.Errorf("account kid = %q; want %q", v, okAccount.URI) + } +} + +func TestRFC_RegisterExternalAccountBinding(t *testing.T) { + eab := &ExternalAccountBinding{ + KID: "kid-1", + Key: []byte("secret"), + } + + type protected struct { + Algorithm string `json:"alg"` + KID string `json:"kid"` + URL string `json:"url"` + } + const email = "mailto:user@example.org" + + s := newACMEServer() + s.handle("/acme/new-account", func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Location", s.url("/accounts/1")) + if r.Method != "POST" { + t.Errorf("r.Method = %q; want POST", r.Method) + } + + var j struct { + Protected string + Contact []string + TermsOfServiceAgreed bool + ExternalaccountBinding struct { + Protected string + Payload string + Signature string + } + } + decodeJWSRequest(t, &j, r.Body) + protData, err := base64.RawURLEncoding.DecodeString(j.ExternalaccountBinding.Protected) + if err != nil { + t.Fatal(err) + } + + var prot protected + err = json.Unmarshal(protData, &prot) + if err != nil { + t.Fatal(err) + } + + if !reflect.DeepEqual(j.Contact, []string{email}) { + t.Errorf("j.Contact = %v; want %v", j.Contact, []string{email}) + } + if !j.TermsOfServiceAgreed { + t.Error("j.TermsOfServiceAgreed = false; want true") + } + + // Ensure same KID. + if prot.KID != eab.KID { + t.Errorf("j.ExternalAccountBinding.KID = %s; want %s", prot.KID, eab.KID) + } + // Ensure expected Algorithm. + if prot.Algorithm != "HS256" { + t.Errorf("j.ExternalAccountBinding.Alg = %s; want %s", + prot.Algorithm, "HS256") + } + + // Ensure same URL as outer JWS. + url := fmt.Sprintf("http://%s/acme/new-account", r.Host) + if prot.URL != url { + t.Errorf("j.ExternalAccountBinding.URL = %s; want %s", + prot.URL, url) + } + + // Ensure payload is base64URL encoded string of JWK in outer JWS + jwk, err := jwkEncode(testKeyEC.Public()) + if err != nil { + t.Fatal(err) + } + decodedPayload, err := base64.RawURLEncoding.DecodeString(j.ExternalaccountBinding.Payload) + if err != nil { + t.Fatal(err) + } + if jwk != string(decodedPayload) { + t.Errorf("j.ExternalAccountBinding.Payload = %s; want %s", decodedPayload, jwk) + } + + // Check signature on inner external account binding JWS + hmac := hmac.New(sha256.New, []byte("secret")) + _, err = hmac.Write([]byte(j.ExternalaccountBinding.Protected + "." + j.ExternalaccountBinding.Payload)) + if err != nil { + t.Fatal(err) + } + mac := hmac.Sum(nil) + encodedMAC := base64.RawURLEncoding.EncodeToString(mac) + + if !bytes.Equal([]byte(encodedMAC), []byte(j.ExternalaccountBinding.Signature)) { + t.Errorf("j.ExternalAccountBinding.Signature = %v; want %v", + []byte(j.ExternalaccountBinding.Signature), encodedMAC) + } + + w.Header().Set("Location", s.url("/accounts/1")) + w.WriteHeader(http.StatusCreated) + b, _ := json.Marshal([]string{email}) + fmt.Fprintf(w, `{"status":"valid","orders":"%s","contact":%s}`, s.url("/accounts/1/orders"), b) + }) + s.start() + defer s.close() + + ctx := context.Background() + cl := &Client{ + Key: testKeyEC, + DirectoryURL: s.url("/"), + } + + var didPrompt bool + a := &Account{Contact: []string{email}, ExternalAccountBinding: eab} + acct, err := cl.Register(ctx, a, func(tos string) bool { + didPrompt = true + terms := s.url("/terms") + if tos != terms { + t.Errorf("tos = %q; want %q", tos, terms) + } + return true + }) + if err != nil { + t.Fatal(err) + } + okAccount := &Account{ + URI: s.url("/accounts/1"), + Status: StatusValid, + Contact: []string{email}, + OrdersURL: s.url("/accounts/1/orders"), + } + if !reflect.DeepEqual(acct, okAccount) { + t.Errorf("acct = %+v; want %+v", acct, okAccount) + } + if !didPrompt { + t.Error("tos prompt wasn't called") + } + if v := cl.accountKID(ctx); v != KeyID(okAccount.URI) { + t.Errorf("account kid = %q; want %q", v, okAccount.URI) + } +} + +func TestRFC_RegisterExisting(t *testing.T) { + s := newACMEServer() + s.handle("/acme/new-account", func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Location", s.url("/accounts/1")) + w.WriteHeader(http.StatusOK) // 200 means account already exists + w.Write([]byte(`{"status": "valid"}`)) + }) + s.start() + defer s.close() + + cl := &Client{Key: testKeyEC, DirectoryURL: s.url("/")} + _, err := cl.Register(context.Background(), &Account{}, AcceptTOS) + if err != ErrAccountAlreadyExists { + t.Errorf("err = %v; want %v", err, ErrAccountAlreadyExists) + } + kid := KeyID(s.url("/accounts/1")) + if v := cl.accountKID(context.Background()); v != kid { + t.Errorf("account kid = %q; want %q", v, kid) + } +} + +func TestRFC_UpdateReg(t *testing.T) { + const email = "mailto:user@example.org" + + s := newACMEServer() + s.handle("/acme/new-account", func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Location", s.url("/accounts/1")) + w.WriteHeader(http.StatusOK) + w.Write([]byte(`{"status": "valid"}`)) + }) + var didUpdate bool + s.handle("/accounts/1", func(w http.ResponseWriter, r *http.Request) { + didUpdate = true + w.Header().Set("Location", s.url("/accounts/1")) + w.WriteHeader(http.StatusOK) + w.Write([]byte(`{"status": "valid"}`)) + + b, _ := io.ReadAll(r.Body) // check err later in decodeJWSxxx + head, err := decodeJWSHead(bytes.NewReader(b)) + if err != nil { + t.Errorf("decodeJWSHead: %v", err) + return + } + if len(head.JWK) != 0 { + t.Error("head.JWK is non-zero") + } + kid := s.url("/accounts/1") + if head.KID != kid { + t.Errorf("head.KID = %q; want %q", head.KID, kid) + } + + var req struct{ Contact []string } + decodeJWSRequest(t, &req, bytes.NewReader(b)) + if len(req.Contact) != 1 || req.Contact[0] != email { + t.Errorf("req.Contact = %q; want [%q]", req.Contact, email) + } + }) + s.start() + defer s.close() + + cl := &Client{Key: testKeyEC, DirectoryURL: s.url("/")} + _, err := cl.UpdateReg(context.Background(), &Account{Contact: []string{email}}) + if err != nil { + t.Error(err) + } + if !didUpdate { + t.Error("UpdateReg didn't update the account") + } +} + +func TestRFC_GetReg(t *testing.T) { + s := newACMEServer() + s.handle("/acme/new-account", func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Location", s.url("/accounts/1")) + w.WriteHeader(http.StatusOK) + w.Write([]byte(`{"status": "valid"}`)) + + head, err := decodeJWSHead(r.Body) + if err != nil { + t.Errorf("decodeJWSHead: %v", err) + return + } + if len(head.JWK) == 0 { + t.Error("head.JWK is empty") + } + }) + s.start() + defer s.close() + + cl := &Client{Key: testKeyEC, DirectoryURL: s.url("/")} + acct, err := cl.GetReg(context.Background(), "") + if err != nil { + t.Fatal(err) + } + okAccount := &Account{ + URI: s.url("/accounts/1"), + Status: StatusValid, + } + if !reflect.DeepEqual(acct, okAccount) { + t.Errorf("acct = %+v; want %+v", acct, okAccount) + } +} + +func TestRFC_GetRegNoAccount(t *testing.T) { + s := newACMEServer() + s.handle("/acme/new-account", func(w http.ResponseWriter, r *http.Request) { + s.error(w, &wireError{ + Status: http.StatusBadRequest, + Type: "urn:ietf:params:acme:error:accountDoesNotExist", + }) + }) + s.start() + defer s.close() + + cl := &Client{Key: testKeyEC, DirectoryURL: s.url("/")} + if _, err := cl.GetReg(context.Background(), ""); err != ErrNoAccount { + t.Errorf("err = %v; want %v", err, ErrNoAccount) + } +} + +func TestRFC_GetRegOtherError(t *testing.T) { + s := newACMEServer() + s.handle("/acme/new-account", func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusBadRequest) + }) + s.start() + defer s.close() + + cl := &Client{Key: testKeyEC, DirectoryURL: s.url("/")} + if _, err := cl.GetReg(context.Background(), ""); err == nil || err == ErrNoAccount { + t.Errorf("GetReg: %v; want any other non-nil err", err) + } +} + +func TestRFC_AccountKeyRollover(t *testing.T) { + s := newACMEServer() + s.handle("/acme/new-account", func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Location", s.url("/accounts/1")) + w.WriteHeader(http.StatusOK) + w.Write([]byte(`{"status": "valid"}`)) + }) + s.handle("/acme/key-change", func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + }) + s.start() + defer s.close() + + cl := &Client{Key: testKeyEC, DirectoryURL: s.url("/")} + if err := cl.AccountKeyRollover(context.Background(), testKeyEC384); err != nil { + t.Errorf("AccountKeyRollover: %v, wanted no error", err) + } else if cl.Key != testKeyEC384 { + t.Error("AccountKeyRollover did not rotate the client key") + } +} + +func TestRFC_DeactivateReg(t *testing.T) { + const email = "mailto:user@example.org" + curStatus := StatusValid + + type account struct { + Status string `json:"status"` + Contact []string `json:"contact"` + AcceptTOS bool `json:"termsOfServiceAgreed"` + Orders string `json:"orders"` + } + + s := newACMEServer() + s.handle("/acme/new-account", func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Location", s.url("/accounts/1")) + w.WriteHeader(http.StatusOK) // 200 means existing account + json.NewEncoder(w).Encode(account{ + Status: curStatus, + Contact: []string{email}, + AcceptTOS: true, + Orders: s.url("/accounts/1/orders"), + }) + + b, _ := io.ReadAll(r.Body) // check err later in decodeJWSxxx + head, err := decodeJWSHead(bytes.NewReader(b)) + if err != nil { + t.Errorf("decodeJWSHead: %v", err) + return + } + if len(head.JWK) == 0 { + t.Error("head.JWK is empty") + } + + var req struct { + Status string `json:"status"` + Contact []string `json:"contact"` + AcceptTOS bool `json:"termsOfServiceAgreed"` + OnlyExisting bool `json:"onlyReturnExisting"` + } + decodeJWSRequest(t, &req, bytes.NewReader(b)) + if !req.OnlyExisting { + t.Errorf("req.OnlyReturnExisting = %t; want = %t", req.OnlyExisting, true) + } + }) + s.handle("/accounts/1", func(w http.ResponseWriter, r *http.Request) { + if curStatus == StatusValid { + curStatus = StatusDeactivated + w.WriteHeader(http.StatusOK) + } else { + s.error(w, &wireError{ + Status: http.StatusUnauthorized, + Type: "urn:ietf:params:acme:error:unauthorized", + }) + } + var req account + b, _ := io.ReadAll(r.Body) // check err later in decodeJWSxxx + head, err := decodeJWSHead(bytes.NewReader(b)) + if err != nil { + t.Errorf("decodeJWSHead: %v", err) + return + } + if len(head.JWK) != 0 { + t.Error("head.JWK is not empty") + } + if !strings.HasSuffix(head.KID, "/accounts/1") { + t.Errorf("head.KID = %q; want suffix /accounts/1", head.KID) + } + + decodeJWSRequest(t, &req, bytes.NewReader(b)) + if req.Status != StatusDeactivated { + t.Errorf("req.Status = %q; want = %q", req.Status, StatusDeactivated) + } + }) + s.start() + defer s.close() + + cl := &Client{Key: testKeyEC, DirectoryURL: s.url("/")} + if err := cl.DeactivateReg(context.Background()); err != nil { + t.Errorf("DeactivateReg: %v, wanted no error", err) + } + if err := cl.DeactivateReg(context.Background()); err == nil { + t.Errorf("DeactivateReg: %v, wanted error for unauthorized", err) + } +} + +func TestRF_DeactivateRegNoAccount(t *testing.T) { + s := newACMEServer() + s.handle("/acme/new-account", func(w http.ResponseWriter, r *http.Request) { + s.error(w, &wireError{ + Status: http.StatusBadRequest, + Type: "urn:ietf:params:acme:error:accountDoesNotExist", + }) + }) + s.start() + defer s.close() + + cl := &Client{Key: testKeyEC, DirectoryURL: s.url("/")} + if err := cl.DeactivateReg(context.Background()); !errors.Is(err, ErrNoAccount) { + t.Errorf("DeactivateReg: %v, wanted ErrNoAccount", err) + } +} + +func TestRFC_AuthorizeOrder(t *testing.T) { + s := newACMEServer() + s.handle("/acme/new-account", func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Location", s.url("/accounts/1")) + w.WriteHeader(http.StatusOK) + w.Write([]byte(`{"status": "valid"}`)) + }) + s.handle("/acme/new-order", func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Location", s.url("/orders/1")) + w.WriteHeader(http.StatusCreated) + fmt.Fprintf(w, `{ + "status": "pending", + "expires": "2019-09-01T00:00:00Z", + "notBefore": "2019-08-31T00:00:00Z", + "notAfter": "2019-09-02T00:00:00Z", + "identifiers": [{"type":"dns", "value":"example.org"}], + "authorizations": [%q] + }`, s.url("/authz/1")) + }) + s.start() + defer s.close() + + cl := &Client{Key: testKeyEC, DirectoryURL: s.url("/")} + o, err := cl.AuthorizeOrder(context.Background(), DomainIDs("example.org"), + WithOrderNotBefore(time.Date(2019, 8, 31, 0, 0, 0, 0, time.UTC)), + WithOrderNotAfter(time.Date(2019, 9, 2, 0, 0, 0, 0, time.UTC)), + ) + if err != nil { + t.Fatal(err) + } + okOrder := &Order{ + URI: s.url("/orders/1"), + Status: StatusPending, + Expires: time.Date(2019, 9, 1, 0, 0, 0, 0, time.UTC), + NotBefore: time.Date(2019, 8, 31, 0, 0, 0, 0, time.UTC), + NotAfter: time.Date(2019, 9, 2, 0, 0, 0, 0, time.UTC), + Identifiers: []AuthzID{AuthzID{Type: "dns", Value: "example.org"}}, + AuthzURLs: []string{s.url("/authz/1")}, + } + if !reflect.DeepEqual(o, okOrder) { + t.Errorf("AuthorizeOrder = %+v; want %+v", o, okOrder) + } +} + +func TestRFC_AuthorizeOrder_WithOrderProfile(t *testing.T) { + s := newACMEServer() + s.handle("/acme/new-account", func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Location", s.url("/accounts/1")) + w.WriteHeader(http.StatusOK) + w.Write([]byte(`{"status": "valid"}`)) + }) + s.handle("/acme/new-order", func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Location", s.url("/orders/1")) + w.WriteHeader(http.StatusCreated) + fmt.Fprintf(w, `{ + "status": "pending", + "expires": "2019-09-01T00:00:00Z", + "notBefore": "2019-08-31T00:00:00Z", + "notAfter": "2019-09-02T00:00:00Z", + "identifiers": [{"type":"dns", "value":"example.org"}], + "authorizations": [%q] + }`, s.url("/authz/1")) + }) + s.start() + defer s.close() + + cl := &Client{Key: testKeyEC, DirectoryURL: s.url("/directory-with-profiles")} + o, err := cl.AuthorizeOrder(context.Background(), DomainIDs("example.org"), + WithOrderNotBefore(time.Date(2019, 8, 31, 0, 0, 0, 0, time.UTC)), + WithOrderNotAfter(time.Date(2019, 9, 2, 0, 0, 0, 0, time.UTC)), + WithOrderProfile("server"), + ) + if err != nil { + t.Fatal(err) + } + okOrder := &Order{ + URI: s.url("/orders/1"), + Status: StatusPending, + Expires: time.Date(2019, 9, 1, 0, 0, 0, 0, time.UTC), + NotBefore: time.Date(2019, 8, 31, 0, 0, 0, 0, time.UTC), + NotAfter: time.Date(2019, 9, 2, 0, 0, 0, 0, time.UTC), + Identifiers: []AuthzID{{Type: "dns", Value: "example.org"}}, + AuthzURLs: []string{s.url("/authz/1")}, + } + if !reflect.DeepEqual(o, okOrder) { + t.Errorf("AuthorizeOrder = %+v; want %+v", o, okOrder) + } +} + +func TestRFC_GetOrder(t *testing.T) { + s := newACMEServer() + s.handle("/acme/new-account", func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Location", s.url("/accounts/1")) + w.WriteHeader(http.StatusOK) + w.Write([]byte(`{"status": "valid"}`)) + }) + s.handle("/orders/1", func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Location", s.url("/orders/1")) + w.WriteHeader(http.StatusOK) + w.Write([]byte(`{ + "status": "invalid", + "expires": "2019-09-01T00:00:00Z", + "notBefore": "2019-08-31T00:00:00Z", + "notAfter": "2019-09-02T00:00:00Z", + "identifiers": [{"type":"dns", "value":"example.org"}], + "authorizations": ["/authz/1"], + "finalize": "/orders/1/fin", + "certificate": "/orders/1/cert", + "error": {"type": "badRequest"} + }`)) + }) + s.start() + defer s.close() + + cl := &Client{Key: testKeyEC, DirectoryURL: s.url("/")} + o, err := cl.GetOrder(context.Background(), s.url("/orders/1")) + if err != nil { + t.Fatal(err) + } + okOrder := &Order{ + URI: s.url("/orders/1"), + Status: StatusInvalid, + Expires: time.Date(2019, 9, 1, 0, 0, 0, 0, time.UTC), + NotBefore: time.Date(2019, 8, 31, 0, 0, 0, 0, time.UTC), + NotAfter: time.Date(2019, 9, 2, 0, 0, 0, 0, time.UTC), + Identifiers: []AuthzID{AuthzID{Type: "dns", Value: "example.org"}}, + AuthzURLs: []string{"/authz/1"}, + FinalizeURL: "/orders/1/fin", + CertURL: "/orders/1/cert", + Error: &Error{ProblemType: "badRequest"}, + } + if !reflect.DeepEqual(o, okOrder) { + t.Errorf("GetOrder = %+v\nwant %+v", o, okOrder) + } +} + +func TestRFC_WaitOrder(t *testing.T) { + for _, st := range []string{StatusReady, StatusValid} { + t.Run(st, func(t *testing.T) { + testWaitOrderStatus(t, st) + }) + } +} + +func testWaitOrderStatus(t *testing.T, okStatus string) { + s := newACMEServer() + s.handle("/acme/new-account", func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Location", s.url("/accounts/1")) + w.WriteHeader(http.StatusOK) + w.Write([]byte(`{"status": "valid"}`)) + }) + var count int + s.handle("/orders/1", func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Location", s.url("/orders/1")) + w.WriteHeader(http.StatusOK) + s := StatusPending + if count > 0 { + s = okStatus + } + fmt.Fprintf(w, `{"status": %q}`, s) + count++ + }) + s.start() + defer s.close() + + cl := &Client{Key: testKeyEC, DirectoryURL: s.url("/")} + order, err := cl.WaitOrder(context.Background(), s.url("/orders/1")) + if err != nil { + t.Fatalf("WaitOrder: %v", err) + } + if order.Status != okStatus { + t.Errorf("order.Status = %q; want %q", order.Status, okStatus) + } +} + +func TestRFC_WaitOrderError(t *testing.T) { + s := newACMEServer() + s.handle("/acme/new-account", func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Location", s.url("/accounts/1")) + w.WriteHeader(http.StatusOK) + w.Write([]byte(`{"status": "valid"}`)) + }) + var count int + s.handle("/orders/1", func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Location", s.url("/orders/1")) + w.WriteHeader(http.StatusOK) + s := StatusPending + if count > 0 { + s = StatusInvalid + } + fmt.Fprintf(w, `{"status": %q}`, s) + count++ + }) + s.start() + defer s.close() + + cl := &Client{Key: testKeyEC, DirectoryURL: s.url("/")} + _, err := cl.WaitOrder(context.Background(), s.url("/orders/1")) + if err == nil { + t.Fatal("WaitOrder returned nil error") + } + e, ok := err.(*OrderError) + if !ok { + t.Fatalf("err = %v (%T); want OrderError", err, err) + } + if e.OrderURL != s.url("/orders/1") { + t.Errorf("e.OrderURL = %q; want %q", e.OrderURL, s.url("/orders/1")) + } + if e.Status != StatusInvalid { + t.Errorf("e.Status = %q; want %q", e.Status, StatusInvalid) + } +} + +func TestRFC_CreateOrderCert(t *testing.T) { + q := &x509.CertificateRequest{ + Subject: pkix.Name{CommonName: "example.org"}, + } + csr, err := x509.CreateCertificateRequest(rand.Reader, q, testKeyEC) + if err != nil { + t.Fatal(err) + } + + tmpl := &x509.Certificate{SerialNumber: big.NewInt(1)} + leaf, err := x509.CreateCertificate(rand.Reader, tmpl, tmpl, &testKeyEC.PublicKey, testKeyEC) + if err != nil { + t.Fatal(err) + } + + s := newACMEServer() + s.handle("/acme/new-account", func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Location", s.url("/accounts/1")) + w.Write([]byte(`{"status": "valid"}`)) + }) + var count int + s.handle("/pleaseissue", func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Location", s.url("/pleaseissue")) + st := StatusProcessing + if count > 0 { + st = StatusValid + } + fmt.Fprintf(w, `{"status":%q, "certificate":%q}`, st, s.url("/crt")) + count++ + }) + s.handle("/crt", func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/pem-certificate-chain") + pem.Encode(w, &pem.Block{Type: "CERTIFICATE", Bytes: leaf}) + }) + s.start() + defer s.close() + ctx := context.Background() + + cl := &Client{Key: testKeyEC, DirectoryURL: s.url("/")} + cert, curl, err := cl.CreateOrderCert(ctx, s.url("/pleaseissue"), csr, true) + if err != nil { + t.Fatalf("CreateOrderCert: %v", err) + } + if _, err := x509.ParseCertificate(cert[0]); err != nil { + t.Errorf("ParseCertificate: %v", err) + } + if !reflect.DeepEqual(cert[0], leaf) { + t.Errorf("cert and leaf bytes don't match") + } + if u := s.url("/crt"); curl != u { + t.Errorf("curl = %q; want %q", curl, u) + } +} + +func TestRFC_AlreadyRevokedCert(t *testing.T) { + s := newACMEServer() + s.handle("/acme/revoke-cert", func(w http.ResponseWriter, r *http.Request) { + s.error(w, &wireError{ + Status: http.StatusBadRequest, + Type: "urn:ietf:params:acme:error:alreadyRevoked", + }) + }) + s.start() + defer s.close() + + cl := &Client{Key: testKeyEC, DirectoryURL: s.url("/")} + err := cl.RevokeCert(context.Background(), testKeyEC, []byte{0}, CRLReasonUnspecified) + if err != nil { + t.Fatalf("RevokeCert: %v", err) + } +} + +func TestRFC_ListCertAlternates(t *testing.T) { + s := newACMEServer() + s.handle("/crt", func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/pem-certificate-chain") + w.Header().Add("Link", `;rel="alternate"`) + w.Header().Add("Link", `; rel="alternate"`) + w.Header().Add("Link", `; rel="index"`) + }) + s.handle("/crt2", func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/pem-certificate-chain") + }) + s.start() + defer s.close() + + cl := &Client{Key: testKeyEC, DirectoryURL: s.url("/")} + crts, err := cl.ListCertAlternates(context.Background(), s.url("/crt")) + if err != nil { + t.Fatalf("ListCertAlternates: %v", err) + } + want := []string{"https://example.com/crt/2", "https://example.com/crt/3"} + if !reflect.DeepEqual(crts, want) { + t.Errorf("ListCertAlternates(/crt): %v; want %v", crts, want) + } + crts, err = cl.ListCertAlternates(context.Background(), s.url("/crt2")) + if err != nil { + t.Fatalf("ListCertAlternates: %v", err) + } + if crts != nil { + t.Errorf("ListCertAlternates(/crt2): %v; want nil", crts) + } +} diff --git a/third_party/forked/acme/types.go b/third_party/forked/acme/types.go new file mode 100644 index 00000000000..4d8fe9c812e --- /dev/null +++ b/third_party/forked/acme/types.go @@ -0,0 +1,670 @@ +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package acme + +import ( + "crypto" + "crypto/x509" + "encoding/json" + "errors" + "fmt" + "net/http" + "strings" + "time" +) + +// ACME status values of Account, Order, Authorization and Challenge objects. +// See https://tools.ietf.org/html/rfc8555#section-7.1.6 for details. +const ( + StatusDeactivated = "deactivated" + StatusExpired = "expired" + StatusInvalid = "invalid" + StatusPending = "pending" + StatusProcessing = "processing" + StatusReady = "ready" + StatusRevoked = "revoked" + StatusUnknown = "unknown" + StatusValid = "valid" +) + +// CRLReasonCode identifies the reason for a certificate revocation. +type CRLReasonCode int + +// CRL reason codes as defined in RFC 5280. +const ( + CRLReasonUnspecified CRLReasonCode = 0 + CRLReasonKeyCompromise CRLReasonCode = 1 + CRLReasonCACompromise CRLReasonCode = 2 + CRLReasonAffiliationChanged CRLReasonCode = 3 + CRLReasonSuperseded CRLReasonCode = 4 + CRLReasonCessationOfOperation CRLReasonCode = 5 + CRLReasonCertificateHold CRLReasonCode = 6 + CRLReasonRemoveFromCRL CRLReasonCode = 8 + CRLReasonPrivilegeWithdrawn CRLReasonCode = 9 + CRLReasonAACompromise CRLReasonCode = 10 +) + +var ( + // ErrUnsupportedKey is returned when an unsupported key type is encountered. + ErrUnsupportedKey = errors.New("acme: unknown key type; only RSA and ECDSA are supported") + + // ErrAccountAlreadyExists indicates that the Client's key has already been registered + // with the CA. It is returned by Register method. + ErrAccountAlreadyExists = errors.New("acme: account already exists") + + // ErrNoAccount indicates that the Client's key has not been registered with the CA. + ErrNoAccount = errors.New("acme: account does not exist") + + // errPreAuthorizationNotSupported indicates that the server does not + // support pre-authorization of identifiers. + errPreAuthorizationNotSupported = errors.New("acme: pre-authorization is not supported") + + // ErrCADoesNotSupportProfiles indicates that [WithOrderProfile] was + // included with a CA that does not advertise support for profiles in + // their directory. + ErrCADoesNotSupportProfiles = errors.New("acme: certificate authority does not support profiles") + + // ErrProfileNotInSetOfSupportedProfiles indicates that the profile + // specified with [WithOrderProfile} is not one supported by the CA + ErrProfileNotInSetOfSupportedProfiles = errors.New("acme: certificate authority does not advertise a profile with name") +) + +// A Subproblem describes an ACME subproblem as reported in an Error. +type Subproblem struct { + // Type is a URI reference that identifies the problem type, + // typically in a "urn:acme:error:xxx" form. + Type string + // Detail is a human-readable explanation specific to this occurrence of the problem. + Detail string + // Instance indicates a URL that the client should direct a human user to visit + // in order for instructions on how to agree to the updated Terms of Service. + // In such an event CA sets StatusCode to 403, Type to + // "urn:ietf:params:acme:error:userActionRequired", and adds a Link header with relation + // "terms-of-service" containing the latest TOS URL. + Instance string + // Identifier may contain the ACME identifier that the error is for. + Identifier *AuthzID +} + +func (sp Subproblem) String() string { + str := fmt.Sprintf("%s: ", sp.Type) + if sp.Identifier != nil { + str += fmt.Sprintf("[%s: %s] ", sp.Identifier.Type, sp.Identifier.Value) + } + str += sp.Detail + return str +} + +// Error is an ACME error, defined in Problem Details for HTTP APIs doc +// http://tools.ietf.org/html/draft-ietf-appsawg-http-problem. +type Error struct { + // StatusCode is The HTTP status code generated by the origin server. + StatusCode int + // ProblemType is a URI reference that identifies the problem type, + // typically in a "urn:acme:error:xxx" form. + ProblemType string + // Detail is a human-readable explanation specific to this occurrence of the problem. + Detail string + // Instance indicates a URL that the client should direct a human user to visit + // in order for instructions on how to agree to the updated Terms of Service. + // In such an event CA sets StatusCode to 403, ProblemType to + // "urn:ietf:params:acme:error:userActionRequired" and a Link header with relation + // "terms-of-service" containing the latest TOS URL. + Instance string + // Header is the original server error response headers. + // It may be nil. + Header http.Header + // Subproblems may contain more detailed information about the individual problems + // that caused the error. This field is only sent by RFC 8555 compatible ACME + // servers. Defined in RFC 8555 Section 6.7.1. + Subproblems []Subproblem +} + +func (e *Error) Error() string { + str := fmt.Sprintf("%d %s: %s", e.StatusCode, e.ProblemType, e.Detail) + if len(e.Subproblems) > 0 { + str += fmt.Sprintf("; subproblems:") + for _, sp := range e.Subproblems { + str += fmt.Sprintf("\n\t%s", sp) + } + } + return str +} + +// AuthorizationError indicates that an authorization for an identifier +// did not succeed. +// It contains all errors from Challenge items of the failed Authorization. +type AuthorizationError struct { + // URI uniquely identifies the failed Authorization. + URI string + + // Identifier is an AuthzID.Value of the failed Authorization. + Identifier string + + // Errors is a collection of non-nil error values of Challenge items + // of the failed Authorization. + Errors []error +} + +func (a *AuthorizationError) Error() string { + e := make([]string, len(a.Errors)) + for i, err := range a.Errors { + e[i] = err.Error() + } + + if a.Identifier != "" { + return fmt.Sprintf("acme: authorization error for %s: %s", a.Identifier, strings.Join(e, "; ")) + } + + return fmt.Sprintf("acme: authorization error: %s", strings.Join(e, "; ")) +} + +// OrderError is returned from Client's order related methods. +// It indicates the order is unusable and the clients should start over with +// AuthorizeOrder. +// +// The clients can still fetch the order object from CA using GetOrder +// to inspect its state. +type OrderError struct { + OrderURL string + Status string +} + +func (oe *OrderError) Error() string { + return fmt.Sprintf("acme: order %s status: %s", oe.OrderURL, oe.Status) +} + +// RateLimit reports whether err represents a rate limit error and +// any Retry-After duration returned by the server. +// +// See the following for more details on rate limiting: +// https://tools.ietf.org/html/draft-ietf-acme-acme-05#section-5.6 +func RateLimit(err error) (time.Duration, bool) { + e, ok := err.(*Error) + if !ok { + return 0, false + } + // Some CA implementations may return incorrect values. + // Use case-insensitive comparison. + if !strings.HasSuffix(strings.ToLower(e.ProblemType), ":ratelimited") { + return 0, false + } + if e.Header == nil { + return 0, true + } + return retryAfter(e.Header.Get("Retry-After")), true +} + +// Account is a user account. It is associated with a private key. +// Non-RFC 8555 fields are empty when interfacing with a compliant CA. +type Account struct { + // URI is the account unique ID, which is also a URL used to retrieve + // account data from the CA. + // When interfacing with RFC 8555-compliant CAs, URI is the "kid" field + // value in JWS signed requests. + URI string + + // Contact is a slice of contact info used during registration. + // See https://tools.ietf.org/html/rfc8555#section-7.3 for supported + // formats. + Contact []string + + // Status indicates current account status as returned by the CA. + // Possible values are StatusValid, StatusDeactivated, and StatusRevoked. + Status string + + // OrdersURL is a URL from which a list of orders submitted by this account + // can be fetched. + OrdersURL string + + // The terms user has agreed to. + // A value not matching CurrentTerms indicates that the user hasn't agreed + // to the actual Terms of Service of the CA. + // + // It is non-RFC 8555 compliant. Package users can store the ToS they agree to + // during Client's Register call in the prompt callback function. + AgreedTerms string + + // Actual terms of a CA. + // + // It is non-RFC 8555 compliant. Use Directory's Terms field. + // When a CA updates their terms and requires an account agreement, + // a URL at which instructions to do so is available in Error's Instance field. + CurrentTerms string + + // Authz is the authorization URL used to initiate a new authz flow. + // + // It is non-RFC 8555 compliant. Use Directory's AuthzURL or OrderURL. + Authz string + + // Authorizations is a URI from which a list of authorizations + // granted to this account can be fetched via a GET request. + // + // It is non-RFC 8555 compliant and is obsoleted by OrdersURL. + Authorizations string + + // Certificates is a URI from which a list of certificates + // issued for this account can be fetched via a GET request. + // + // It is non-RFC 8555 compliant and is obsoleted by OrdersURL. + Certificates string + + // ExternalAccountBinding represents an arbitrary binding to an account of + // the CA which the ACME server is tied to. + // See https://tools.ietf.org/html/rfc8555#section-7.3.4 for more details. + ExternalAccountBinding *ExternalAccountBinding +} + +// ExternalAccountBinding contains the data needed to form a request with +// an external account binding. +// See https://tools.ietf.org/html/rfc8555#section-7.3.4 for more details. +type ExternalAccountBinding struct { + // KID is the Key ID of the symmetric MAC key that the CA provides to + // identify an external account from ACME. + KID string + + // Key is the bytes of the symmetric key that the CA provides to identify + // the account. Key must correspond to the KID. + Key []byte +} + +func (e *ExternalAccountBinding) String() string { + return fmt.Sprintf("&{KID: %q, Key: redacted}", e.KID) +} + +// Directory is ACME server discovery data. +// See https://tools.ietf.org/html/rfc8555#section-7.1.1 for more details. +type Directory struct { + // NonceURL indicates an endpoint where to fetch fresh nonce values from. + NonceURL string + + // RegURL is an account endpoint URL, allowing for creating new accounts. + // Pre-RFC 8555 CAs also allow modifying existing accounts at this URL. + RegURL string + + // OrderURL is used to initiate the certificate issuance flow + // as described in RFC 8555. + OrderURL string + + // AuthzURL is used to initiate identifier pre-authorization flow. + // Empty string indicates the flow is unsupported by the CA. + AuthzURL string + + // CertURL is a new certificate issuance endpoint URL. + // It is non-RFC 8555 compliant and is obsoleted by OrderURL. + CertURL string + + // RevokeURL is used to initiate a certificate revocation flow. + RevokeURL string + + // KeyChangeURL allows to perform account key rollover flow. + KeyChangeURL string + + // Terms is a URI identifying the current terms of service. + Terms string + + // Website is an HTTP or HTTPS URL locating a website + // providing more information about the ACME server. + Website string + + // CAA consists of lowercase hostname elements, which the ACME server + // recognises as referring to itself for the purposes of CAA record validation + // as defined in RFC 6844. + CAA []string + + // ExternalAccountRequired indicates that the CA requires for all account-related + // requests to include external account binding information. + ExternalAccountRequired bool + + // Profiles indicates that the CA supports specifying a profile for an + // order. See also [WithOrderNotAfter]. + Profiles Profiles +} + +// Order represents a client's request for a certificate. +// It tracks the request flow progress through to issuance. +type Order struct { + // URI uniquely identifies an order. + URI string + + // Status represents the current status of the order. + // It indicates which action the client should take. + // + // Possible values are StatusPending, StatusReady, StatusProcessing, StatusValid and StatusInvalid. + // Pending means the CA does not believe that the client has fulfilled the requirements. + // Ready indicates that the client has fulfilled all the requirements and can submit a CSR + // to obtain a certificate. This is done with Client's CreateOrderCert. + // Processing means the certificate is being issued. + // Valid indicates the CA has issued the certificate. It can be downloaded + // from the Order's CertURL. This is done with Client's FetchCert. + // Invalid means the certificate will not be issued. Users should consider this order + // abandoned. + Status string + + // Expires is the timestamp after which CA considers this order invalid. + Expires time.Time + + // Identifiers contains all identifier objects which the order pertains to. + Identifiers []AuthzID + + // NotBefore is the requested value of the notBefore field in the certificate. + NotBefore time.Time + + // NotAfter is the requested value of the notAfter field in the certificate. + NotAfter time.Time + + // AuthzURLs represents authorizations to complete before a certificate + // for identifiers specified in the order can be issued. + // It also contains unexpired authorizations that the client has completed + // in the past. + // + // Authorization objects can be fetched using Client's GetAuthorization method. + // + // The required authorizations are dictated by CA policies. + // There may not be a 1:1 relationship between the identifiers and required authorizations. + // Required authorizations can be identified by their StatusPending status. + // + // For orders in the StatusValid or StatusInvalid state these are the authorizations + // which were completed. + AuthzURLs []string + + // FinalizeURL is the endpoint at which a CSR is submitted to obtain a certificate + // once all the authorizations are satisfied. + FinalizeURL string + + // CertURL points to the certificate that has been issued in response to this order. + CertURL string + + // The error that occurred while processing the order as received from a CA, if any. + Error *Error +} + +// OrderOption allows customizing Client.AuthorizeOrder call. +type OrderOption interface { + privateOrderOpt() +} + +// WithOrderNotBefore sets order's NotBefore field. +func WithOrderNotBefore(t time.Time) OrderOption { + return orderNotBeforeOpt(t) +} + +// WithOrderNotAfter sets order's NotAfter field. +func WithOrderNotAfter(t time.Time) OrderOption { + return orderNotAfterOpt(t) +} + +// WithOrderProfile sets an order's Profile field for servers which support +// profiles. +// See also: +// * https://datatracker.ietf.org/doc/draft-aaron-acme-profiles/ +// * https://letsencrypt.org/docs/profiles/ +func WithOrderProfile(name string) OrderOption { + return orderProfileOpt(name) +} + +type orderNotBeforeOpt time.Time + +func (orderNotBeforeOpt) privateOrderOpt() {} + +type orderNotAfterOpt time.Time + +func (orderNotAfterOpt) privateOrderOpt() {} + +type orderProfileOpt string + +func (orderProfileOpt) privateOrderOpt() {} + +// Authorization encodes an authorization response. +type Authorization struct { + // URI uniquely identifies a authorization. + URI string + + // Status is the current status of an authorization. + // Possible values are StatusPending, StatusValid, StatusInvalid, StatusDeactivated, + // StatusExpired and StatusRevoked. + Status string + + // Identifier is what the account is authorized to represent. + Identifier AuthzID + + // The timestamp after which the CA considers the authorization invalid. + Expires time.Time + + // Wildcard is true for authorizations of a wildcard domain name. + Wildcard bool + + // Challenges that the client needs to fulfill in order to prove possession + // of the identifier (for pending authorizations). + // For valid authorizations, the challenge that was validated. + // For invalid authorizations, the challenge that was attempted and failed. + // + // RFC 8555 compatible CAs require users to fuflfill only one of the challenges. + Challenges []*Challenge + + // A collection of sets of challenges, each of which would be sufficient + // to prove possession of the identifier. + // Clients must complete a set of challenges that covers at least one set. + // Challenges are identified by their indices in the challenges array. + // If this field is empty, the client needs to complete all challenges. + // + // This field is unused in RFC 8555. + Combinations [][]int +} + +// AuthzID is an identifier that an account is authorized to represent. +type AuthzID struct { + Type string // The type of identifier, "dns" or "ip". + Value string // The identifier itself, e.g. "example.org". +} + +// DomainIDs creates a slice of AuthzID with "dns" identifier type. +func DomainIDs(names ...string) []AuthzID { + a := make([]AuthzID, len(names)) + for i, v := range names { + a[i] = AuthzID{Type: "dns", Value: v} + } + return a +} + +// IPIDs creates a slice of AuthzID with "ip" identifier type. +// Each element of addr is textual form of an address as defined +// in RFC 1123 Section 2.1 for IPv4 and in RFC 5952 Section 4 for IPv6. +func IPIDs(addr ...string) []AuthzID { + a := make([]AuthzID, len(addr)) + for i, v := range addr { + a[i] = AuthzID{Type: "ip", Value: v} + } + return a +} + +// wireAuthzID is ACME JSON representation of authorization identifier objects. +type wireAuthzID struct { + Type string `json:"type"` + Value string `json:"value"` +} + +// wireAuthz is ACME JSON representation of Authorization objects. +type wireAuthz struct { + Identifier wireAuthzID + Status string + Expires time.Time + Wildcard bool + Challenges []wireChallenge + Combinations [][]int + Error *wireError +} + +func (z *wireAuthz) authorization(uri string) *Authorization { + a := &Authorization{ + URI: uri, + Status: z.Status, + Identifier: AuthzID{Type: z.Identifier.Type, Value: z.Identifier.Value}, + Expires: z.Expires, + Wildcard: z.Wildcard, + Challenges: make([]*Challenge, len(z.Challenges)), + Combinations: z.Combinations, // shallow copy + } + for i, v := range z.Challenges { + a.Challenges[i] = v.challenge() + } + return a +} + +func (z *wireAuthz) error(uri string) *AuthorizationError { + err := &AuthorizationError{ + URI: uri, + Identifier: z.Identifier.Value, + } + + if z.Error != nil { + err.Errors = append(err.Errors, z.Error.error(nil)) + } + + for _, raw := range z.Challenges { + if raw.Error != nil { + err.Errors = append(err.Errors, raw.Error.error(nil)) + } + } + + return err +} + +// Challenge encodes a returned CA challenge. +// Its Error field may be non-nil if the challenge is part of an Authorization +// with StatusInvalid. +type Challenge struct { + // Type is the challenge type, e.g. "http-01", "tls-alpn-01", "dns-01". + Type string + + // URI is where a challenge response can be posted to. + URI string + + // Token is a random value that uniquely identifies the challenge. + Token string + + // Status identifies the status of this challenge. + // In RFC 8555, possible values are StatusPending, StatusProcessing, StatusValid, + // and StatusInvalid. + Status string + + // Validated is the time at which the CA validated this challenge. + // Always zero value in pre-RFC 8555. + Validated time.Time + + // Error indicates the reason for an authorization failure + // when this challenge was used. + // The type of a non-nil value is *Error. + Error error + + // Payload is the JSON-formatted payload that the client sends + // to the server to indicate it is ready to respond to the challenge. + // When unset, it defaults to an empty JSON object: {}. + // For most challenges, the client must not set Payload, + // see https://tools.ietf.org/html/rfc8555#section-7.5.1. + // Payload is used only for newer challenges (such as "device-attest-01") + // where the client must send additional data for the server to validate + // the challenge. + Payload json.RawMessage +} + +// wireChallenge is ACME JSON challenge representation. +type wireChallenge struct { + URL string `json:"url"` // RFC + URI string `json:"uri"` // pre-RFC + Type string + Token string + Status string + Validated time.Time + Error *wireError +} + +func (c *wireChallenge) challenge() *Challenge { + v := &Challenge{ + URI: c.URL, + Type: c.Type, + Token: c.Token, + Status: c.Status, + } + if v.URI == "" { + v.URI = c.URI // c.URL was empty; use legacy + } + if v.Status == "" { + v.Status = StatusPending + } + if c.Error != nil { + v.Error = c.Error.error(nil) + } + return v +} + +// wireError is a subset of fields of the Problem Details object +// as described in https://tools.ietf.org/html/rfc7807#section-3.1. +type wireError struct { + Status int + Type string + Detail string + Instance string + Subproblems []Subproblem +} + +func (e *wireError) error(h http.Header) *Error { + err := &Error{ + StatusCode: e.Status, + ProblemType: e.Type, + Detail: e.Detail, + Instance: e.Instance, + Header: h, + Subproblems: e.Subproblems, + } + return err +} + +// CertOption is an optional argument type for the TLS ChallengeCert methods for +// customizing a temporary certificate for TLS-based challenges. +type CertOption interface { + privateCertOpt() +} + +// WithKey creates an option holding a private/public key pair. +// The private part signs a certificate, and the public part represents the signee. +func WithKey(key crypto.Signer) CertOption { + return &certOptKey{key} +} + +type certOptKey struct { + key crypto.Signer +} + +func (*certOptKey) privateCertOpt() {} + +// WithTemplate creates an option for specifying a certificate template. +// See x509.CreateCertificate for template usage details. +// +// In TLS ChallengeCert methods, the template is also used as parent, +// resulting in a self-signed certificate. +// The DNSNames field of t is always overwritten for tls-sni challenge certs. +func WithTemplate(t *x509.Certificate) CertOption { + return (*certOptTemplate)(t) +} + +type certOptTemplate x509.Certificate + +func (*certOptTemplate) privateCertOpt() {} + +type Profiles map[string]string + +func (ps Profiles) isSupported() bool { + return len(ps) > 0 +} + +func (ps Profiles) GetDescription(name string) string { + return ps[name] +} + +func (ps Profiles) Has(name string) bool { + _, ok := ps[name] + return ok +} diff --git a/third_party/forked/acme/types_test.go b/third_party/forked/acme/types_test.go new file mode 100644 index 00000000000..59ce7e7602c --- /dev/null +++ b/third_party/forked/acme/types_test.go @@ -0,0 +1,219 @@ +// Copyright 2017 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package acme + +import ( + "errors" + "net/http" + "reflect" + "testing" + "time" +) + +func TestExternalAccountBindingString(t *testing.T) { + eab := ExternalAccountBinding{ + KID: "kid", + Key: []byte("key"), + } + got := eab.String() + want := `&{KID: "kid", Key: redacted}` + if got != want { + t.Errorf("eab.String() = %q, want: %q", got, want) + } +} + +func TestRateLimit(t *testing.T) { + now := time.Date(2017, 04, 27, 10, 0, 0, 0, time.UTC) + f := timeNow + defer func() { timeNow = f }() + timeNow = func() time.Time { return now } + + h120, hTime := http.Header{}, http.Header{} + h120.Set("Retry-After", "120") + hTime.Set("Retry-After", "Tue Apr 27 11:00:00 2017") + + err1 := &Error{ + ProblemType: "urn:ietf:params:acme:error:nolimit", + Header: h120, + } + err2 := &Error{ + ProblemType: "urn:ietf:params:acme:error:rateLimited", + Header: h120, + } + err3 := &Error{ + ProblemType: "urn:ietf:params:acme:error:rateLimited", + Header: nil, + } + err4 := &Error{ + ProblemType: "urn:ietf:params:acme:error:rateLimited", + Header: hTime, + } + + tt := []struct { + err error + res time.Duration + ok bool + }{ + {nil, 0, false}, + {errors.New("dummy"), 0, false}, + {err1, 0, false}, + {err2, 2 * time.Minute, true}, + {err3, 0, true}, + {err4, time.Hour, true}, + } + for i, test := range tt { + res, ok := RateLimit(test.err) + if ok != test.ok { + t.Errorf("%d: RateLimit(%+v): ok = %v; want %v", i, test.err, ok, test.ok) + continue + } + if res != test.res { + t.Errorf("%d: RateLimit(%+v) = %v; want %v", i, test.err, res, test.res) + } + } +} + +func TestAuthorizationError(t *testing.T) { + tests := []struct { + desc string + err *AuthorizationError + msg string + }{ + { + desc: "when auth error identifier is set", + err: &AuthorizationError{ + Identifier: "domain.com", + Errors: []error{ + (&wireError{ + Status: 403, + Type: "urn:ietf:params:acme:error:caa", + Detail: "CAA record for domain.com prevents issuance", + }).error(nil), + }, + }, + msg: "acme: authorization error for domain.com: 403 urn:ietf:params:acme:error:caa: CAA record for domain.com prevents issuance", + }, + + { + desc: "when auth error identifier is unset", + err: &AuthorizationError{ + Errors: []error{ + (&wireError{ + Status: 403, + Type: "urn:ietf:params:acme:error:caa", + Detail: "CAA record for domain.com prevents issuance", + }).error(nil), + }, + }, + msg: "acme: authorization error: 403 urn:ietf:params:acme:error:caa: CAA record for domain.com prevents issuance", + }, + } + + for _, tt := range tests { + if tt.err.Error() != tt.msg { + t.Errorf("got: %s\nwant: %s", tt.err, tt.msg) + } + } +} + +func TestSubproblems(t *testing.T) { + tests := []struct { + wire wireError + expectedOut Error + }{ + { + wire: wireError{ + Status: 1, + Type: "urn:error", + Detail: "it's an error", + }, + expectedOut: Error{ + StatusCode: 1, + ProblemType: "urn:error", + Detail: "it's an error", + }, + }, + { + wire: wireError{ + Status: 1, + Type: "urn:error", + Detail: "it's an error", + Subproblems: []Subproblem{ + { + Type: "urn:error:sub", + Detail: "it's a subproblem", + }, + }, + }, + expectedOut: Error{ + StatusCode: 1, + ProblemType: "urn:error", + Detail: "it's an error", + Subproblems: []Subproblem{ + { + Type: "urn:error:sub", + Detail: "it's a subproblem", + }, + }, + }, + }, + { + wire: wireError{ + Status: 1, + Type: "urn:error", + Detail: "it's an error", + Subproblems: []Subproblem{ + { + Type: "urn:error:sub", + Detail: "it's a subproblem", + Identifier: &AuthzID{Type: "dns", Value: "example"}, + }, + }, + }, + expectedOut: Error{ + StatusCode: 1, + ProblemType: "urn:error", + Detail: "it's an error", + Subproblems: []Subproblem{ + { + Type: "urn:error:sub", + Detail: "it's a subproblem", + Identifier: &AuthzID{Type: "dns", Value: "example"}, + }, + }, + }, + }, + } + + for _, tc := range tests { + out := tc.wire.error(nil) + if !reflect.DeepEqual(*out, tc.expectedOut) { + t.Errorf("Unexpected error: wanted %v, got %v", tc.expectedOut, *out) + } + } +} + +func TestErrorStringerWithSubproblems(t *testing.T) { + err := Error{ + StatusCode: 1, + ProblemType: "urn:error", + Detail: "it's an error", + Subproblems: []Subproblem{ + { + Type: "urn:error:sub", + Detail: "it's a subproblem", + }, + { + Type: "urn:error:sub", + Detail: "it's a subproblem", + Identifier: &AuthzID{Type: "dns", Value: "example"}, + }, + }, + } + expectedStr := "1 urn:error: it's an error; subproblems:\n\turn:error:sub: it's a subproblem\n\turn:error:sub: [dns: example] it's a subproblem" + if err.Error() != expectedStr { + t.Errorf("Unexpected error string: wanted %q, got %q", expectedStr, err.Error()) + } +} diff --git a/third_party/klone.yaml b/third_party/klone.yaml new file mode 100644 index 00000000000..acb2014f3ec --- /dev/null +++ b/third_party/klone.yaml @@ -0,0 +1,15 @@ +# Clone folders from third_party repos and forks. +# More info can be found here: https://github.com/cert-manager/klone +# +# - acme +# We vendor just the acme package, from a cert-manager fork of +# golang.org/x/crypto. The acme-profiles branch has a patch by @sigmavirus24, +# with support for ACME profiles. +# See https://github.com/golang/go/issues/73101#issuecomment-2764923702 +targets: + forked: + - folder_name: acme + repo_url: https://github.com/cert-manager/crypto + repo_ref: acme-profiles + repo_hash: 20ccc126e2ac0b2d9da2e78f84f5bb7649d8100a + repo_path: acme diff --git a/tools/README.md b/tools/README.md new file mode 100644 index 00000000000..4b72d54703b --- /dev/null +++ b/tools/README.md @@ -0,0 +1,4 @@ +This directory used to contain a Golang script which stopped being used. + +We've left the other script in this directory in case it's being used in a script somewhere, but it's just a wrapper +and we might remove this whole directory in the future. diff --git a/tools/cobra/main.go b/tools/cobra/main.go deleted file mode 100644 index 413baf74881..00000000000 --- a/tools/cobra/main.go +++ /dev/null @@ -1,98 +0,0 @@ -/* -Copyright 2020 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. -*/ - -package main - -import ( - "errors" - "fmt" - "os" - "path/filepath" - - "github.com/mitchellh/go-homedir" - "github.com/spf13/cobra" - "github.com/spf13/cobra/doc" - "github.com/spf13/pflag" - - acmesolvercmd "github.com/cert-manager/cert-manager/cmd/acmesolver/app" - cainjectorapp "github.com/cert-manager/cert-manager/cmd/cainjector/app" - controllerapp "github.com/cert-manager/cert-manager/cmd/controller/app" - ctlcmd "github.com/cert-manager/cert-manager/cmd/ctl/cmd" - webhookcmd "github.com/cert-manager/cert-manager/cmd/webhook/app" -) - -func main() { - if err := run(os.Args); err != nil { - fmt.Fprintf(os.Stderr, "error: %s\n", err) - os.Exit(1) - } - - os.Exit(0) -} - -func run(args []string) error { - if len(args) != 2 { - return errors.New("expecting single output directory argument") - } - - // remove all global flags that are imported in - pflag.CommandLine = nil - - root, err := homedir.Expand(args[1]) - if err != nil { - return err - } - - if err := ensureDirectory(root); err != nil { - return err - } - - for _, c := range []*cobra.Command{ - cainjectorapp.NewCommandStartInjectorController(nil, nil, nil), - controllerapp.NewCommandStartCertManagerController(nil), - ctlcmd.NewCertManagerCtlCommand(nil, nil, nil, nil), - webhookcmd.NewServerCommand(nil), - acmesolvercmd.NewACMESolverCommand(nil), - } { - dir := filepath.Join(root, c.Use) - - if err := ensureDirectory(dir); err != nil { - return err - } - - if err := doc.GenMarkdownTree(c, dir); err != nil { - return err - } - } - - return nil -} - -func ensureDirectory(dir string) error { - s, err := os.Stat(dir) - if err != nil { - if os.IsNotExist(err) { - return os.Mkdir(dir, os.FileMode(0755)) - } - return err - } - - if !s.IsDir() { - return fmt.Errorf("path it not directory: %s", dir) - } - - return nil -} diff --git a/tools/cobra/main_test.go b/tools/cobra/main_test.go deleted file mode 100644 index 257f275cf25..00000000000 --- a/tools/cobra/main_test.go +++ /dev/null @@ -1,70 +0,0 @@ -/* -Copyright 2020 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. -*/ - -package main - -import ( - "os" - "path/filepath" - "testing" -) - -func TestRun(t *testing.T) { - rootDir, err := os.MkdirTemp(os.TempDir(), "cert-manager-cobra") - if err != nil { - t.Fatal(err) - } - defer func() { - if err := os.RemoveAll(rootDir); err != nil { - t.Fatal(err) - } - }() - - tests := map[string]struct { - input []string - expDirs []string - expErr bool - }{ - "if no arguments given should error": { - input: []string{"cobra"}, - expErr: true, - }, - "if two arguments given should error": { - input: []string{"cobra", "foo", "bar"}, - expErr: true, - }, - "if directory given, should write docs": { - input: []string{"cobra", filepath.Join(rootDir, "foo")}, - expDirs: []string{"foo/ca-injector", "foo/cert-manager-controller", "foo/cmctl"}, - }, - } - - for name, test := range tests { - t.Run(name, func(t *testing.T) { - err := run(test.input) - if test.expErr != (err != nil) { - t.Errorf("got unexpected error, exp=%t got=%v", - test.expErr, err) - } - - for _, dir := range test.expDirs { - if _, err := os.Stat(filepath.Join(rootDir, dir)); err != nil { - t.Errorf("stat error on expected directory: %s", err) - } - } - }) - } -}